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::presentation;
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 {
let styles = presentation::stderr();
eprintln!(
"{} {}",
styles.heading("Running"),
styles.path(path.display())
);
eprintln!(" {}", styles.muted("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 {
let styles = presentation::stderr();
eprintln!(
" {} {} ({:?}, {:?})",
styles.label("study:"),
styles.identifier(&spec.study_id),
spec.run_kind,
spec.backend
);
eprintln!(
" {} {} regions, {} meshes",
styles.label("geometry:"),
spec.geometry.regions.len(),
spec.geometry.meshes.len()
);
eprintln!(" {}", styles.info("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 {
let styles = presentation::stderr();
eprintln!(
" {} {} ({} studies, fail_fast: {})",
styles.label("sweep:"),
styles.identifier(&spec.sweep_id),
spec.studies.len(),
spec.fail_fast
);
eprintln!(
" {}",
styles.info("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) {
let styles = presentation::stdout();
println!(
"{}: {}",
styles.success("FEA run complete"),
styles.identifier(&data.study_id)
);
println!(
" {} {}",
styles.label("run id:"),
styles.identifier(&data.run_id)
);
println!(" {} {:?}", styles.label("kind:"), data.run_kind);
println!(" {} {:?}", styles.label("backend:"), data.backend);
println!(
" {} {:?} (publishable: {})",
styles.label("status:"),
data.run_status,
data.publishable
);
println!(
" {} {:?}",
styles.label("solver:"),
data.solver_convergence
);
println!(" {} {:?}", styles.label("quality:"), data.result_quality);
println!(
" {} {}",
styles.label("evidence:"),
styles.path(&data.evidence_artifact_path)
);
println!(
" {} {}",
styles.label("results:"),
styles.help(format!("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) {
let styles = presentation::stdout();
println!(
"{}: {}",
styles.success("FEA sweep complete"),
styles.identifier(&data.sweep_id)
);
println!(" studies: {}", data.study_count);
println!(" succeeded: {}", data.success_count);
println!(" failed: {}", data.failed_count);
println!(
" {} {}",
styles.label("evidence:"),
styles.path(&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!(" {}", styles.error("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()
}