runmat 0.5.5

High-performance MATLAB/Octave syntax mathematical runtime
use anyhow::{Context, Result};
use log::info;
use runmat_config::runtime::RunMatRuntimeConfig;
use runmat_runtime::analysis::{
    analysis_run_study_op, analysis_run_study_sweep_op, load_fea_document_from_path_async,
    AnalysisStudyRunData, AnalysisStudySweepData, FeaResolvedDocument,
};
use runmat_runtime::operations::{OperationContext, OperationEnvelope, OperationErrorEnvelope};
use serde::Serialize;
use std::path::PathBuf;

use crate::cli::Cli;
use crate::commands::accel::dump_provider_telemetry_if_requested;
use crate::AlreadyReportedCliError;

pub async fn execute_fea_path(
    path: PathBuf,
    _cli: &Cli,
    config: &RunMatRuntimeConfig,
    json: bool,
) -> Result<()> {
    info!("Executing FEA document: {path:?}");
    if !json {
        eprintln!("Running {}", path.display());
        eprintln!("  loading study document");
    }
    let document = load_fea_document_from_path_async(&path)
        .await
        .map_err(|err| anyhow::anyhow!("Failed to load FEA file {}: {err}", path.display()))?;
    let context = OperationContext::new(None, None);

    match document {
        FeaResolvedDocument::Study(spec) => {
            if !json {
                eprintln!(
                    "  study: {} ({:?}, {:?})",
                    spec.study_id, spec.run_kind, spec.backend
                );
                eprintln!(
                    "  geometry: {} regions, {} meshes",
                    spec.geometry.regions.len(),
                    spec.geometry.meshes.len()
                );
                eprintln!("  validating, planning, and solving");
            }
            let envelope = analysis_run_study_op(&spec, context).map_err(report_operation_error)?;
            if json {
                print_envelope(&envelope, config.runtime.verbose)?;
            } else {
                print_study_run_summary(&envelope.data, config.runtime.verbose);
            }
        }
        FeaResolvedDocument::Sweep(spec) => {
            if !json {
                eprintln!(
                    "  sweep: {} ({} studies, fail_fast: {})",
                    spec.sweep_id,
                    spec.studies.len(),
                    spec.fail_fast
                );
                eprintln!("  validating, planning, and solving studies");
            }
            let envelope =
                analysis_run_study_sweep_op(&spec, context).map_err(report_operation_error)?;
            if json {
                print_envelope(&envelope, config.runtime.verbose)?;
            } else {
                print_sweep_run_summary(&envelope.data);
            }
        }
    }

    dump_provider_telemetry_if_requested();
    Ok(())
}

fn print_study_run_summary(data: &AnalysisStudyRunData, verbose: bool) {
    println!("FEA run complete: {}", data.study_id);
    println!("  run id: {}", data.run_id);
    println!("  kind: {:?}", data.run_kind);
    println!("  backend: {:?}", data.backend);
    println!(
        "  status: {:?} (publishable: {})",
        data.run_status, data.publishable
    );
    println!("  solver: {:?}", data.solver_convergence);
    println!("  quality: {:?}", data.result_quality);
    println!("  evidence: {}", data.evidence_artifact_path);
    println!("  results: fea.results(\"{}\")", data.run_id);
    if !data.quality_reasons.is_empty() {
        println!("  quality reasons:");
        for reason in &data.quality_reasons {
            println!("    {:?}: {}", reason.code, reason.detail);
        }
    }
    if verbose {
        println!(
            "  operation: {} ({})",
            data.run_operation, data.run_op_version
        );
        println!("  study fingerprint: {}", data.study_fingerprint);
        println!("  operations:");
        for operation in &data.operation_sequence {
            println!("    {operation}");
        }
    }
}

fn print_sweep_run_summary(data: &AnalysisStudySweepData) {
    println!("FEA sweep complete: {}", data.sweep_id);
    println!("  studies: {}", data.study_count);
    println!("  succeeded: {}", data.success_count);
    println!("  failed: {}", data.failed_count);
    println!("  evidence: {}", data.evidence_artifact_path);
    if !data.run_entries.is_empty() {
        println!("  runs:");
        for entry in &data.run_entries {
            println!(
                "    {}  {}  {:?}  publishable: {}",
                entry.study_id, entry.run_id, entry.run_status, entry.publishable
            );
        }
    }
    if !data.failure_entries.is_empty() {
        println!("  failures:");
        for entry in &data.failure_entries {
            println!(
                "    {}: {} ({})",
                entry.study_id, entry.message, entry.error_code
            );
        }
    }
}

fn print_envelope<T: Serialize>(
    envelope: &OperationEnvelope<T>,
    include_metadata: bool,
) -> Result<()> {
    if include_metadata {
        println!(
            "{}",
            serde_json::to_string_pretty(envelope).context("Failed to serialize FEA result")?
        );
    } else {
        println!(
            "{}",
            serde_json::to_string_pretty(&envelope.data)
                .context("Failed to serialize FEA result")?
        );
    }
    Ok(())
}

fn report_operation_error(error: OperationErrorEnvelope) -> anyhow::Error {
    match serde_json::to_string_pretty(&error) {
        Ok(payload) => eprintln!("{payload}"),
        Err(_) => eprintln!("{}: {}", error.error_code, error.message.replace('\n', " ")),
    }
    AlreadyReportedCliError.into()
}