1use runmat_analysis_core::{AnalysisField, AnalysisFieldValues, AnalysisModel};
2use runmat_analysis_fea::diagnostics::FeaDiagnostic;
3use runmat_analysis_fea::{ComputeBackend, FeaRunResult};
4use runmat_geometry_core::GeometryAsset;
5use runmat_meshing_core::RegionMeshMapping;
6use serde::{Deserialize, Serialize};
7
8fn default_prep_coordinate_span_m() -> f64 {
9 1.0
10}
11
12fn default_prep_coordinate_secondary_span_m() -> f64 {
13 0.0
14}
15
16fn default_prep_coordinate_active_dimension_count() -> usize {
17 1
18}
19
20fn default_prep_coordinate_characteristic_length_m() -> f64 {
21 1.0
22}
23
24fn default_zero_usize() -> usize {
25 0
26}
27
28fn default_zero_f64() -> f64 {
29 0.0
30}
31
32fn default_reference_element_coordinates_m() -> [[f64; 3]; 3] {
33 [[0.0; 3]; 3]
34}
35
36fn default_element_topology_sample_edge_nodes() -> [[u32; 2]; 8] {
37 [[0; 2]; 8]
38}
39
40fn default_element_topology_sample_node_coordinates_m() -> [[f64; 3]; 8] {
41 [[0.0; 3]; 8]
42}
43
44fn default_element_topology_sample_element_edges() -> [[u32; 3]; 4] {
45 [[0; 3]; 4]
46}
47
48fn default_element_topology_sample_element_orientations() -> [[i8; 3]; 4] {
49 [[0; 3]; 4]
50}
51
52fn default_element_topology_sample_element_areas_m2() -> [f64; 4] {
53 [0.0; 4]
54}
55
56fn default_element_topology_node_coordinates_m() -> Vec<[f64; 3]> {
57 Vec::new()
58}
59
60fn default_element_topology_edge_nodes() -> Vec<[u32; 2]> {
61 Vec::new()
62}
63
64fn default_element_topology_element_edges() -> Vec<[u32; 3]> {
65 Vec::new()
66}
67
68fn default_element_topology_element_orientations() -> Vec<[i8; 3]> {
69 Vec::new()
70}
71
72fn default_element_topology_element_areas_m2() -> Vec<f64> {
73 Vec::new()
74}
75
76#[derive(Debug, Clone, PartialEq)]
77pub struct AnalysisValidateResult {
78 pub valid: bool,
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82pub struct AnalysisCreateModelIntentSpec {
83 pub model_id: String,
84 pub profile: AnalysisCreateModelProfile,
85 #[serde(default)]
86 pub prep_context: Option<AnalysisCreateModelPrepContext>,
87}
88
89#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
90pub struct AnalysisCreateModelPrepContext {
91 pub source_geometry_id: String,
92 pub source_geometry_revision: u32,
93 pub region_mappings: Vec<RegionMeshMapping>,
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct AnalysisRunPrepContext {
98 pub prepared_mesh_count: usize,
99 pub prepared_node_count: usize,
100 pub prepared_element_count: usize,
101 pub mapped_region_count: usize,
102 pub min_scaled_jacobian: f64,
103 pub mean_aspect_ratio: f64,
104 pub inverted_element_count: usize,
105 pub mapped_load_count: usize,
106 pub mapped_bc_count: usize,
107 pub layout_seed: u64,
108 pub topology_dof_multiplier: f64,
109 pub topology_bandwidth_estimate: u32,
110 pub mapped_region_participation_ratio: f64,
111 pub topology_surface_patch_ratio: f64,
112 pub topology_volume_core_ratio: f64,
113 pub topology_mixed_family_ratio: f64,
114 pub topology_region_span_mean: f64,
115 pub topology_region_block_count: usize,
116 pub topology_region_mesh_mean: f64,
117 pub topology_region_mesh_variance: f64,
118 pub topology_triangle_family_ratio: f64,
119 pub topology_quad_family_ratio: f64,
120 pub topology_tet_family_ratio: f64,
121 pub topology_hex_family_ratio: f64,
122 #[serde(default = "default_prep_coordinate_span_m")]
123 pub coordinate_span_x_m: f64,
124 #[serde(default = "default_prep_coordinate_secondary_span_m")]
125 pub coordinate_span_y_m: f64,
126 #[serde(default = "default_prep_coordinate_secondary_span_m")]
127 pub coordinate_span_z_m: f64,
128 #[serde(default = "default_prep_coordinate_active_dimension_count")]
129 pub coordinate_active_dimension_count: usize,
130 #[serde(default = "default_prep_coordinate_characteristic_length_m")]
131 pub coordinate_characteristic_length_m: f64,
132 #[serde(default = "default_zero_usize")]
133 pub element_geometry_node_count: usize,
134 #[serde(default = "default_zero_usize")]
135 pub element_geometry_edge_count: usize,
136 #[serde(default = "default_zero_f64")]
137 pub mean_element_edge_length_m: f64,
138 #[serde(default = "default_zero_f64")]
139 pub mean_element_area_m2: f64,
140 #[serde(default = "default_zero_f64")]
141 pub element_geometry_coverage_ratio: f64,
142 #[serde(default = "default_reference_element_coordinates_m")]
143 pub reference_element_coordinates_m: [[f64; 3]; 3],
144 #[serde(default = "default_zero_f64")]
145 pub reference_element_area_m2: f64,
146 #[serde(default = "default_zero_usize")]
147 pub control_volume_cell_count: usize,
148 #[serde(default = "default_zero_usize")]
149 pub control_volume_face_count: usize,
150 #[serde(default = "default_zero_usize")]
151 pub control_volume_internal_face_count: usize,
152 #[serde(default = "default_zero_usize")]
153 pub control_volume_boundary_face_count: usize,
154 #[serde(default = "default_zero_f64")]
155 pub control_volume_connectivity_coverage_ratio: f64,
156 #[serde(default = "default_zero_usize")]
157 pub element_topology_sample_element_count: usize,
158 #[serde(default = "default_zero_usize")]
159 pub element_topology_sample_edge_count: usize,
160 #[serde(default = "default_element_topology_sample_edge_nodes")]
161 pub element_topology_sample_edge_nodes: [[u32; 2]; 8],
162 #[serde(default = "default_element_topology_sample_node_coordinates_m")]
163 pub element_topology_sample_node_coordinates_m: [[f64; 3]; 8],
164 #[serde(default = "default_element_topology_sample_element_edges")]
165 pub element_topology_sample_element_edges: [[u32; 3]; 4],
166 #[serde(default = "default_element_topology_sample_element_orientations")]
167 pub element_topology_sample_element_orientations: [[i8; 3]; 4],
168 #[serde(default = "default_element_topology_sample_element_areas_m2")]
169 pub element_topology_sample_element_areas_m2: [f64; 4],
170 #[serde(default = "default_element_topology_node_coordinates_m")]
171 pub element_topology_node_coordinates_m: Vec<[f64; 3]>,
172 #[serde(default = "default_element_topology_edge_nodes")]
173 pub element_topology_edge_nodes: Vec<[u32; 2]>,
174 #[serde(default = "default_element_topology_element_edges")]
175 pub element_topology_element_edges: Vec<[u32; 3]>,
176 #[serde(default = "default_element_topology_element_orientations")]
177 pub element_topology_element_orientations: Vec<[i8; 3]>,
178 #[serde(default = "default_element_topology_element_areas_m2")]
179 pub element_topology_element_areas_m2: Vec<f64>,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
183#[serde(rename_all = "snake_case")]
184pub enum AnalysisCreateModelProfile {
185 LinearStaticStructural,
186 ThermoMechanicalCoupled,
187 ThermalStandalone,
188 ModalStructural,
189 AcousticHarmonic,
190 TransientStructural,
191 NonlinearStructural,
192 ElectromagneticStatic,
193 CfdSteadyState,
194 CfdTransient,
195 ChtCoupled,
196 FsiCoupled,
197}
198
199#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
200#[serde(rename_all = "snake_case")]
201pub enum PrecisionMode {
202 Fp32,
203 Fp64,
204 Mixed,
205}
206
207#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(rename_all = "snake_case")]
209pub enum PreconditionerMode {
210 Auto,
211 Jacobi,
212 Amg,
213 Ilu,
214}
215
216#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
217#[serde(rename_all = "snake_case")]
218pub enum QualityPolicy {
219 Strict,
220 Balanced,
221 Exploratory,
222}
223
224#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
225#[serde(rename_all = "snake_case")]
226pub enum PrepCalibrationProfile {
227 Auto,
228 Fast,
229 Balanced,
230 Conservative,
231}
232
233#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
234pub struct ThermoRegionTemperatureDelta {
235 pub region_id: String,
236 pub temperature_delta_k: f64,
237}
238
239#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
240pub struct ThermoTimeProfilePoint {
241 pub normalized_time: f64,
242 pub scale: f64,
243}
244
245#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
246#[serde(rename_all = "snake_case")]
247pub enum ThermoFieldInterpolationMode {
248 Linear,
249 Step,
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct ThermoFieldSource {
254 pub source_id: String,
255 pub revision: u32,
256 #[serde(default)]
257 pub interpolation_mode: Option<ThermoFieldInterpolationMode>,
258 #[serde(default)]
259 pub expected_region_ids: Vec<String>,
260}
261
262#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub struct ThermoMechanicalCouplingOptions {
264 pub enabled: bool,
265 pub reference_temperature_k: f64,
266 pub applied_temperature_delta_k: f64,
267 pub thermal_expansion_coefficient: f64,
268 #[serde(default)]
269 pub field_artifact_id: Option<String>,
270 #[serde(default)]
271 pub field_source: Option<ThermoFieldSource>,
272 #[serde(default)]
273 pub region_temperature_deltas: Vec<ThermoRegionTemperatureDelta>,
274 #[serde(default)]
275 pub time_profile: Vec<ThermoTimeProfilePoint>,
276}
277
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct ElectroRegionConductivityScale {
280 pub region_id: String,
281 pub conductivity_scale: f64,
282}
283
284#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
285pub struct ElectroTimeProfilePoint {
286 pub normalized_time: f64,
287 pub current_scale: f64,
288}
289
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
291pub struct ElectroThermalCouplingOptions {
292 pub enabled: bool,
293 pub reference_temperature_k: f64,
294 pub applied_voltage_v: f64,
295 pub base_electrical_conductivity_s_per_m: f64,
296 pub resistive_heating_coefficient: f64,
297 #[serde(default)]
298 pub region_conductivity_scales: Vec<ElectroRegionConductivityScale>,
299 #[serde(default)]
300 pub time_profile: Vec<ElectroTimeProfilePoint>,
301}
302
303#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
304pub struct PlasticityConstitutiveOptions {
305 pub enabled: bool,
306 pub yield_strain: f64,
307 pub hardening_modulus_ratio: f64,
308 pub saturation_exponent: f64,
309}
310
311#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
312pub struct ContactInterfaceOptions {
313 pub enabled: bool,
314 pub penalty_stiffness_scale: f64,
315 pub max_penetration_ratio: f64,
316 pub friction_coefficient: f64,
317}
318
319#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
320#[serde(rename_all = "snake_case")]
321pub enum QualityReasonCode {
322 MaterialAssignmentConflict,
323 SolverNotConverged,
324 SolverBackendFallback,
325 FieldPromotionFallback,
326 ModalResidualExceeded,
327 ModalOrthogonalityExceeded,
328 ModalSeparationLow,
329 TransientResidualExceeded,
330 TransientStabilityExceeded,
331 TransientStepFailure,
332 ThermoMechanicalTransientStress,
333 ThermoMechanicalConstitutiveSpreadHigh,
334 ThermoMechanicalAssignmentHeterogeneityHigh,
335 ThermoMechanicalGradientInstability,
336 ThermoMechanicalFieldCoverageLow,
337 ThermoMechanicalFieldExtrapolationHigh,
338 ElectroThermalTransientStress,
339 ElectroThermalNonlinearStress,
340 ElectromagneticSolveQualityLow,
341 ElectromagneticConductivitySpreadHigh,
342 ElectromagneticMaterialHeterogeneityHigh,
343 ElectromagneticAssignmentCoverageLow,
344 ElectromagneticRegionContrastHigh,
345 ElectromagneticConditioningHigh,
346 ElectromagneticSourceRealizationLow,
347 ElectromagneticSourceRegionCoverageLow,
348 ElectromagneticSourceMaterialAlignmentLow,
349 ElectromagneticSourceOverlapHigh,
350 ElectromagneticSourceInterferenceHigh,
351 ElectromagneticBoundaryLocalizationLow,
352 ElectromagneticGroundAnchorEffectivenessLow,
353 ElectromagneticInsulationLeakageHigh,
354 ElectromagneticBoundaryAnchoringLow,
355 ElectromagneticFluxDivergenceHigh,
356 ElectromagneticEnergyImbalanceHigh,
357 ElectromagneticBoundaryEnergyLow,
358 ElectromagneticBoundaryPenaltyConditioningHigh,
359 ElectromagneticSourceRegionEnergyConsistencyLow,
360 ElectromagneticRealResidualHigh,
361 ElectromagneticImagResidualHigh,
362 ElectromagneticSweepCoverageLow,
363 ElectromagneticResonanceSharpnessLow,
364 PlasticityNonlinearStress,
365 ContactNonlinearStress,
366 NonlinearResidualExceeded,
367 NonlinearIncrementFailure,
368 ThermoMechanicalNonlinearStress,
369 ThermalResidualExceeded,
370 ThermalConstitutiveSpreadHigh,
371}
372
373#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
374pub struct QualityReason {
375 pub code: QualityReasonCode,
376 pub detail: String,
377}
378
379#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
380pub struct AnalysisRunOptions {
381 pub deterministic_mode: bool,
382 pub precision_mode: PrecisionMode,
383 pub preconditioner_mode: PreconditionerMode,
384 pub quality_policy: QualityPolicy,
385 #[serde(default)]
386 pub prep_context: Option<AnalysisRunPrepContext>,
387 #[serde(default)]
388 pub prep_artifact_id: Option<String>,
389 #[serde(default)]
390 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
391}
392
393#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
394pub struct AnalysisElectromagneticRunOptions {
395 pub deterministic_mode: bool,
396 pub precision_mode: PrecisionMode,
397 pub quality_policy: QualityPolicy,
398 pub residual_target: f64,
399 pub harmonic_tolerance: f64,
400 pub harmonic_max_iterations: usize,
401 #[serde(default)]
402 pub prep_context: Option<AnalysisRunPrepContext>,
403 #[serde(default)]
404 pub prep_artifact_id: Option<String>,
405 #[serde(default)]
406 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
407 #[serde(default)]
408 pub sweep_enabled: bool,
409 #[serde(default)]
410 pub sweep_frequency_hz: Vec<f64>,
411}
412
413impl Default for AnalysisElectromagneticRunOptions {
414 fn default() -> Self {
415 Self {
416 deterministic_mode: false,
417 precision_mode: PrecisionMode::Fp64,
418 quality_policy: QualityPolicy::Balanced,
419 residual_target: 1.0e-6,
420 harmonic_tolerance: 1.0e-7,
421 harmonic_max_iterations: 96,
422 prep_context: None,
423 prep_artifact_id: None,
424 prep_calibration_profile: None,
425 sweep_enabled: false,
426 sweep_frequency_hz: Vec::new(),
427 }
428 }
429}
430
431impl Default for AnalysisRunOptions {
432 fn default() -> Self {
433 Self {
434 deterministic_mode: false,
435 precision_mode: PrecisionMode::Fp64,
436 preconditioner_mode: PreconditionerMode::Auto,
437 quality_policy: QualityPolicy::Balanced,
438 prep_context: None,
439 prep_artifact_id: None,
440 prep_calibration_profile: None,
441 }
442 }
443}
444
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct AnalysisTransientRunOptions {
447 pub deterministic_mode: bool,
448 pub precision_mode: PrecisionMode,
449 pub quality_policy: QualityPolicy,
450 pub time_step_s: f64,
451 pub min_time_step_s: f64,
452 pub max_time_step_s: f64,
453 pub step_count: usize,
454 pub max_linear_iters: usize,
455 pub tolerance: f64,
456 pub residual_target: f64,
457 pub adaptive_time_step: bool,
458 pub max_step_retries: usize,
459 pub adapt_min_scale: f64,
460 pub adapt_max_scale: f64,
461 pub adapt_growth_exponent: f64,
462 pub adapt_retry_growth_cap: f64,
463 pub adapt_nonconverged_shrink: f64,
464 pub dt_bucket_rel_tolerance: f64,
465 #[serde(default)]
466 pub prep_context: Option<AnalysisRunPrepContext>,
467 #[serde(default)]
468 pub prep_artifact_id: Option<String>,
469 #[serde(default)]
470 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
471}
472
473impl Default for AnalysisTransientRunOptions {
474 fn default() -> Self {
475 Self {
476 deterministic_mode: false,
477 precision_mode: PrecisionMode::Fp64,
478 quality_policy: QualityPolicy::Balanced,
479 time_step_s: 1.0e-3,
480 min_time_step_s: 1.0e-6,
481 max_time_step_s: 2.0e-2,
482 step_count: 10,
483 max_linear_iters: 128,
484 tolerance: 1.0e-8,
485 residual_target: 1.0e-6,
486 adaptive_time_step: true,
487 max_step_retries: 4,
488 adapt_min_scale: 0.8,
489 adapt_max_scale: 1.25,
490 adapt_growth_exponent: 0.35,
491 adapt_retry_growth_cap: 1.05,
492 adapt_nonconverged_shrink: 0.75,
493 dt_bucket_rel_tolerance: 0.0,
494 prep_context: None,
495 prep_artifact_id: None,
496 prep_calibration_profile: None,
497 }
498 }
499}
500
501#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
502pub struct AnalysisAcousticRunOptions {
503 pub deterministic_mode: bool,
504 pub precision_mode: PrecisionMode,
505 pub quality_policy: QualityPolicy,
506 pub mode_count: usize,
507 pub residual_warn_threshold: f64,
508 #[serde(default)]
509 pub prep_context: Option<AnalysisRunPrepContext>,
510 #[serde(default)]
511 pub prep_artifact_id: Option<String>,
512 #[serde(default)]
513 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
514}
515
516impl Default for AnalysisAcousticRunOptions {
517 fn default() -> Self {
518 Self {
519 deterministic_mode: false,
520 precision_mode: PrecisionMode::Fp64,
521 quality_policy: QualityPolicy::Balanced,
522 mode_count: 3,
523 residual_warn_threshold: 1.0e-3,
524 prep_context: None,
525 prep_artifact_id: None,
526 prep_calibration_profile: None,
527 }
528 }
529}
530
531#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
532pub struct AnalysisCfdRunOptions {
533 pub deterministic_mode: bool,
534 pub precision_mode: PrecisionMode,
535 pub quality_policy: QualityPolicy,
536 pub time_step_s: f64,
537 pub step_count: usize,
538 pub max_linear_iters: usize,
539 pub tolerance: f64,
540 pub residual_warn_threshold: f64,
541 #[serde(default)]
542 pub prep_context: Option<AnalysisRunPrepContext>,
543 #[serde(default)]
544 pub prep_artifact_id: Option<String>,
545 #[serde(default)]
546 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
547}
548
549impl Default for AnalysisCfdRunOptions {
550 fn default() -> Self {
551 Self {
552 deterministic_mode: false,
553 precision_mode: PrecisionMode::Fp64,
554 quality_policy: QualityPolicy::Balanced,
555 time_step_s: 1.0e-3,
556 step_count: 12,
557 max_linear_iters: 128,
558 tolerance: 1.0e-8,
559 residual_warn_threshold: 1.0e-5,
560 prep_context: None,
561 prep_artifact_id: None,
562 prep_calibration_profile: None,
563 }
564 }
565}
566
567#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
568pub struct AnalysisChtRunOptions {
569 pub deterministic_mode: bool,
570 pub precision_mode: PrecisionMode,
571 pub quality_policy: QualityPolicy,
572 pub time_step_s: f64,
573 pub step_count: usize,
574 pub max_linear_iters: usize,
575 pub tolerance: f64,
576 pub residual_warn_threshold: f64,
577 #[serde(default)]
578 pub prep_context: Option<AnalysisRunPrepContext>,
579 #[serde(default)]
580 pub prep_artifact_id: Option<String>,
581 #[serde(default)]
582 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
583}
584
585impl Default for AnalysisChtRunOptions {
586 fn default() -> Self {
587 Self {
588 deterministic_mode: false,
589 precision_mode: PrecisionMode::Fp64,
590 quality_policy: QualityPolicy::Balanced,
591 time_step_s: 1.0e-3,
592 step_count: 12,
593 max_linear_iters: 128,
594 tolerance: 1.0e-8,
595 residual_warn_threshold: 1.0e-4,
596 prep_context: None,
597 prep_artifact_id: None,
598 prep_calibration_profile: None,
599 }
600 }
601}
602
603#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
604pub struct AnalysisFsiRunOptions {
605 pub deterministic_mode: bool,
606 pub precision_mode: PrecisionMode,
607 pub quality_policy: QualityPolicy,
608 pub time_step_s: f64,
609 pub step_count: usize,
610 pub max_linear_iters: usize,
611 pub tolerance: f64,
612 pub residual_warn_threshold: f64,
613 #[serde(default)]
614 pub prep_context: Option<AnalysisRunPrepContext>,
615 #[serde(default)]
616 pub prep_artifact_id: Option<String>,
617 #[serde(default)]
618 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
619}
620
621impl Default for AnalysisFsiRunOptions {
622 fn default() -> Self {
623 Self {
624 deterministic_mode: false,
625 precision_mode: PrecisionMode::Fp64,
626 quality_policy: QualityPolicy::Balanced,
627 time_step_s: 1.0e-3,
628 step_count: 12,
629 max_linear_iters: 128,
630 tolerance: 1.0e-8,
631 residual_warn_threshold: 1.0e-4,
632 prep_context: None,
633 prep_artifact_id: None,
634 prep_calibration_profile: None,
635 }
636 }
637}
638
639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
640pub struct AnalysisThermalRunOptions {
641 pub deterministic_mode: bool,
642 pub precision_mode: PrecisionMode,
643 pub quality_policy: QualityPolicy,
644 pub step_count: usize,
645 pub time_step_s: f64,
646 pub residual_warn_threshold: f64,
647 #[serde(default)]
648 pub prep_context: Option<AnalysisRunPrepContext>,
649 #[serde(default)]
650 pub prep_artifact_id: Option<String>,
651 #[serde(default)]
652 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
653}
654
655impl Default for AnalysisThermalRunOptions {
656 fn default() -> Self {
657 Self {
658 deterministic_mode: false,
659 precision_mode: PrecisionMode::Fp64,
660 quality_policy: QualityPolicy::Balanced,
661 step_count: 10,
662 time_step_s: 1.0e-2,
663 residual_warn_threshold: 1.0e-4,
664 prep_context: None,
665 prep_artifact_id: None,
666 prep_calibration_profile: None,
667 }
668 }
669}
670
671impl AnalysisTransientRunOptions {
672 pub fn coarse() -> Self {
673 Self {
674 deterministic_mode: false,
675 precision_mode: PrecisionMode::Fp32,
676 quality_policy: QualityPolicy::Exploratory,
677 time_step_s: 5.0e-3,
678 min_time_step_s: 5.0e-4,
679 max_time_step_s: 2.0e-2,
680 step_count: 6,
681 max_linear_iters: 64,
682 tolerance: 1.0e-6,
683 residual_target: 1.0e-4,
684 adaptive_time_step: true,
685 max_step_retries: 2,
686 adapt_min_scale: 0.75,
687 adapt_max_scale: 1.3,
688 adapt_growth_exponent: 0.3,
689 adapt_retry_growth_cap: 1.02,
690 adapt_nonconverged_shrink: 0.7,
691 dt_bucket_rel_tolerance: 0.02,
692 prep_context: None,
693 prep_artifact_id: None,
694 prep_calibration_profile: None,
695 }
696 }
697
698 pub fn balanced() -> Self {
699 Self::default()
700 }
701
702 pub fn production_recommended() -> Self {
703 Self {
704 quality_policy: QualityPolicy::Balanced,
705 deterministic_mode: true,
706 precision_mode: PrecisionMode::Fp64,
707 dt_bucket_rel_tolerance: 0.01,
708 ..Self::balanced()
709 }
710 }
711
712 pub fn high_accuracy() -> Self {
713 Self {
714 deterministic_mode: true,
715 precision_mode: PrecisionMode::Fp64,
716 quality_policy: QualityPolicy::Strict,
717 time_step_s: 5.0e-4,
718 min_time_step_s: 5.0e-6,
719 max_time_step_s: 2.0e-3,
720 step_count: 24,
721 max_linear_iters: 256,
722 tolerance: 1.0e-10,
723 residual_target: 1.0e-7,
724 adaptive_time_step: true,
725 max_step_retries: 8,
726 adapt_min_scale: 0.85,
727 adapt_max_scale: 1.2,
728 adapt_growth_exponent: 0.45,
729 adapt_retry_growth_cap: 1.03,
730 adapt_nonconverged_shrink: 0.8,
731 dt_bucket_rel_tolerance: 0.005,
732 prep_context: None,
733 prep_artifact_id: None,
734 prep_calibration_profile: None,
735 }
736 }
737}
738
739#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
740pub struct AnalysisModalRunOptions {
741 pub deterministic_mode: bool,
742 pub precision_mode: PrecisionMode,
743 pub quality_policy: QualityPolicy,
744 pub mode_count: usize,
745 pub residual_warn_threshold: f64,
746 #[serde(default)]
747 pub prep_context: Option<AnalysisRunPrepContext>,
748 #[serde(default)]
749 pub prep_artifact_id: Option<String>,
750 #[serde(default)]
751 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
752}
753
754#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
755pub struct AnalysisNonlinearRunOptions {
756 pub deterministic_mode: bool,
757 pub precision_mode: PrecisionMode,
758 pub quality_policy: QualityPolicy,
759 pub increment_count: usize,
760 pub max_newton_iters: usize,
761 pub tolerance: f64,
762 pub residual_convergence_factor: f64,
763 pub increment_norm_tolerance: f64,
764 pub line_search: bool,
765 pub max_line_search_backtracks: usize,
766 pub line_search_reduction: f64,
767 pub tangent_refresh_interval: usize,
768 #[serde(default)]
769 pub prep_context: Option<AnalysisRunPrepContext>,
770 #[serde(default)]
771 pub prep_artifact_id: Option<String>,
772 #[serde(default)]
773 pub prep_calibration_profile: Option<PrepCalibrationProfile>,
774}
775
776impl Default for AnalysisNonlinearRunOptions {
777 fn default() -> Self {
778 Self {
779 deterministic_mode: false,
780 precision_mode: PrecisionMode::Fp64,
781 quality_policy: QualityPolicy::Balanced,
782 increment_count: 12,
783 max_newton_iters: 24,
784 tolerance: 1.0e-6,
785 residual_convergence_factor: 5.0,
786 increment_norm_tolerance: 1.0e-7,
787 line_search: true,
788 max_line_search_backtracks: 6,
789 line_search_reduction: 0.5,
790 tangent_refresh_interval: 2,
791 prep_context: None,
792 prep_artifact_id: None,
793 prep_calibration_profile: None,
794 }
795 }
796}
797
798impl AnalysisNonlinearRunOptions {
799 pub fn coarse() -> Self {
800 Self {
801 deterministic_mode: false,
802 precision_mode: PrecisionMode::Fp32,
803 quality_policy: QualityPolicy::Exploratory,
804 increment_count: 8,
805 max_newton_iters: 16,
806 tolerance: 5.0e-6,
807 residual_convergence_factor: 8.0,
808 increment_norm_tolerance: 5.0e-7,
809 line_search: false,
810 max_line_search_backtracks: 0,
811 line_search_reduction: 0.6,
812 tangent_refresh_interval: 4,
813 prep_context: None,
814 prep_artifact_id: None,
815 prep_calibration_profile: None,
816 }
817 }
818
819 pub fn balanced() -> Self {
820 Self::default()
821 }
822
823 pub fn high_accuracy() -> Self {
824 Self {
825 deterministic_mode: true,
826 precision_mode: PrecisionMode::Fp64,
827 quality_policy: QualityPolicy::Strict,
828 increment_count: 24,
829 max_newton_iters: 40,
830 tolerance: 1.0e-7,
831 residual_convergence_factor: 3.0,
832 increment_norm_tolerance: 5.0e-8,
833 line_search: true,
834 max_line_search_backtracks: 10,
835 line_search_reduction: 0.5,
836 tangent_refresh_interval: 1,
837 prep_context: None,
838 prep_artifact_id: None,
839 prep_calibration_profile: None,
840 }
841 }
842
843 pub fn production_recommended() -> Self {
844 Self {
845 deterministic_mode: true,
846 precision_mode: PrecisionMode::Fp64,
847 quality_policy: QualityPolicy::Balanced,
848 increment_count: 24,
849 max_newton_iters: 28,
850 tolerance: 1.0e-6,
851 residual_convergence_factor: 4.0,
852 increment_norm_tolerance: 8.0e-8,
853 line_search: true,
854 max_line_search_backtracks: 8,
855 line_search_reduction: 0.5,
856 tangent_refresh_interval: 2,
857 prep_context: None,
858 prep_artifact_id: None,
859 prep_calibration_profile: None,
860 }
861 }
862}
863
864impl Default for AnalysisModalRunOptions {
865 fn default() -> Self {
866 Self {
867 deterministic_mode: false,
868 precision_mode: PrecisionMode::Fp64,
869 quality_policy: QualityPolicy::Balanced,
870 mode_count: 3,
871 residual_warn_threshold: 1.0e-3,
872 prep_context: None,
873 prep_artifact_id: None,
874 prep_calibration_profile: None,
875 }
876 }
877}
878
879impl AnalysisModalRunOptions {
880 pub fn coarse() -> Self {
881 Self {
882 deterministic_mode: false,
883 precision_mode: PrecisionMode::Fp32,
884 quality_policy: QualityPolicy::Exploratory,
885 mode_count: 2,
886 residual_warn_threshold: 5.0e-3,
887 prep_context: None,
888 prep_artifact_id: None,
889 prep_calibration_profile: None,
890 }
891 }
892
893 pub fn balanced() -> Self {
894 Self::default()
895 }
896
897 pub fn high_accuracy() -> Self {
898 Self {
899 deterministic_mode: true,
900 precision_mode: PrecisionMode::Fp64,
901 quality_policy: QualityPolicy::Strict,
902 mode_count: 8,
903 residual_warn_threshold: 5.0e-4,
904 prep_context: None,
905 prep_artifact_id: None,
906 prep_calibration_profile: None,
907 }
908 }
909}
910
911#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
912#[serde(rename_all = "snake_case")]
913pub enum QualityGate {
914 Pass,
915 Warn,
916 Fail,
917}
918
919#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
920#[serde(rename_all = "snake_case")]
921pub enum RunStatus {
922 Publishable,
923 Degraded,
924 Rejected,
925}
926
927#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
928pub struct RunProvenance {
929 pub backend: ComputeBackend,
930 pub solver_backend: String,
931 pub solver_device_apply_k_ratio: f64,
932 pub solver_host_sync_count: u32,
933 pub precision_mode: String,
934 pub deterministic_mode: bool,
935 pub solver_method: String,
936 pub preconditioner: String,
937 pub quality_policy: String,
938 pub fallback_events: Vec<String>,
939}
940
941#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
942pub struct AnalysisRenderTopology {
943 pub schema_version: String,
944 pub source: AnalysisRenderTopologySource,
945 pub meshes: Vec<AnalysisRenderMesh>,
946}
947
948#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
949#[serde(rename_all = "snake_case")]
950pub enum AnalysisRenderTopologySource {
951 SolverPrep,
952}
953
954#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
955pub struct AnalysisRenderMesh {
956 pub mesh_id: String,
957 pub vertices: Vec<[f64; 3]>,
958 pub triangles: Vec<[u32; 3]>,
959}
960
961#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
962pub struct AnalysisRunResult {
963 pub run_id: String,
964 pub run: FeaRunResult,
965 #[serde(default, skip_serializing_if = "Option::is_none")]
966 pub render_topology: Option<AnalysisRenderTopology>,
967 pub modal_results: Option<ModalResultsData>,
968 #[serde(default)]
969 pub thermal_results: Option<ThermalResultsData>,
970 pub transient_results: Option<TransientResultsData>,
971 pub nonlinear_results: Option<NonlinearResultsData>,
972 #[serde(default)]
973 pub electromagnetic_results: Option<ElectromagneticResultsData>,
974 pub model_validity: QualityGate,
975 pub solver_convergence: QualityGate,
976 pub result_quality: QualityGate,
977 pub run_status: RunStatus,
978 pub publishable: bool,
979 pub quality_reasons: Vec<QualityReason>,
980 pub provenance: RunProvenance,
981}
982
983#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
984pub struct AnalysisArtifactRecord {
985 pub run_id: String,
986 pub created_at: String,
987 pub op_version: String,
988 pub field_ids: Vec<String>,
989}
990
991#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
992pub struct AnalysisResultsQuery {
993 pub include_fields: Vec<String>,
994 pub include_field_values: bool,
995 pub include_diagnostics: bool,
996 pub diagnostic_codes: Vec<String>,
997 pub include_modal_results: bool,
998 pub mode_indices: Vec<usize>,
999 pub include_transient_results: bool,
1000 pub transient_snapshot_indices: Vec<usize>,
1001 pub include_nonlinear_results: bool,
1002 pub include_electromagnetic_results: bool,
1003}
1004
1005impl Default for AnalysisResultsQuery {
1006 fn default() -> Self {
1007 Self {
1008 include_fields: Vec::new(),
1009 include_field_values: true,
1010 include_diagnostics: true,
1011 diagnostic_codes: Vec::new(),
1012 include_modal_results: true,
1013 mode_indices: Vec::new(),
1014 include_transient_results: true,
1015 transient_snapshot_indices: Vec::new(),
1016 include_nonlinear_results: true,
1017 include_electromagnetic_results: true,
1018 }
1019 }
1020}
1021
1022impl AnalysisResultsQuery {
1023 pub fn metadata_only() -> Self {
1024 Self {
1025 include_field_values: false,
1026 include_modal_results: false,
1027 include_transient_results: false,
1028 include_nonlinear_results: false,
1029 include_electromagnetic_results: false,
1030 ..Self::default()
1031 }
1032 }
1033
1034 pub fn field_values(field_id: impl Into<String>) -> Self {
1035 Self {
1036 include_fields: vec![field_id.into()],
1037 include_field_values: true,
1038 include_diagnostics: false,
1039 include_modal_results: false,
1040 include_transient_results: false,
1041 include_nonlinear_results: false,
1042 include_electromagnetic_results: false,
1043 ..Self::default()
1044 }
1045 }
1046}
1047
1048#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1049#[serde(rename_all = "snake_case")]
1050pub enum AnalysisFieldKind {
1051 Scalar,
1052 Vector,
1053 Tensor,
1054 Unknown,
1055}
1056
1057#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1058#[serde(rename_all = "snake_case")]
1059pub enum AnalysisFieldStorage {
1060 HostF64,
1061 DeviceRef,
1062}
1063
1064#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
1065#[serde(rename_all = "snake_case")]
1066pub enum AnalysisFieldLocation {
1067 Node,
1068 Element,
1069 Edge,
1070 BoundaryFace,
1071 InterfaceFace,
1072 Mode,
1073 Global,
1074 #[default]
1075 Unknown,
1076}
1077
1078#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1079pub struct AnalysisFieldDescriptor {
1080 pub field_id: String,
1081 #[serde(default)]
1082 pub family: String,
1083 #[serde(default)]
1084 pub quantity: String,
1085 pub class_name: String,
1086 pub kind: AnalysisFieldKind,
1087 pub dtype: String,
1088 #[serde(default)]
1089 pub unit: Option<String>,
1090 #[serde(default)]
1091 pub location: AnalysisFieldLocation,
1092 pub shape: Vec<usize>,
1093 pub element_count: usize,
1094 pub component_count: Option<usize>,
1095 pub residency: String,
1096 pub storage: AnalysisFieldStorage,
1097 pub size_bytes: Option<u64>,
1098}
1099
1100impl AnalysisFieldDescriptor {
1101 pub fn from_field(field: &AnalysisField) -> Self {
1102 let storage = match &field.values {
1103 AnalysisFieldValues::HostF64(_) => AnalysisFieldStorage::HostF64,
1104 AnalysisFieldValues::DeviceRef(_) => AnalysisFieldStorage::DeviceRef,
1105 };
1106 let kind = infer_field_kind(&field.field_id, &field.shape);
1107 let class_name = match kind {
1108 AnalysisFieldKind::Scalar => "fea.ScalarField",
1109 AnalysisFieldKind::Vector => "fea.VectorField",
1110 AnalysisFieldKind::Tensor => "fea.TensorField",
1111 AnalysisFieldKind::Unknown => "fea.Field",
1112 }
1113 .to_string();
1114 let residency = match storage {
1115 AnalysisFieldStorage::HostF64 => "cpu",
1116 AnalysisFieldStorage::DeviceRef => "gpu",
1117 }
1118 .to_string();
1119 let element_count = field.element_count();
1120 Self {
1121 field_id: field.field_id.clone(),
1122 family: infer_field_family(&field.field_id).to_string(),
1123 quantity: infer_field_quantity(&field.field_id).to_string(),
1124 class_name,
1125 kind,
1126 dtype: "double".to_string(),
1127 unit: infer_field_unit(&field.field_id).map(str::to_string),
1128 location: infer_field_location(&field.field_id),
1129 shape: field.shape.clone(),
1130 element_count,
1131 component_count: infer_component_count(&field.field_id, &field.shape),
1132 residency,
1133 storage,
1134 size_bytes: element_count
1135 .checked_mul(std::mem::size_of::<f64>())
1136 .map(|bytes| bytes as u64),
1137 }
1138 }
1139}
1140
1141fn infer_field_family(field_id: &str) -> &str {
1142 field_id
1143 .split_once('.')
1144 .map_or("unknown", |(family, _)| family)
1145}
1146
1147fn infer_field_quantity(field_id: &str) -> &str {
1148 let Some((_, rest)) = field_id.split_once('.') else {
1149 return field_id;
1150 };
1151 let Some((quantity, suffix)) = rest.rsplit_once('.') else {
1152 return rest;
1153 };
1154 if suffix.chars().all(|ch| ch.is_ascii_digit()) {
1155 quantity
1156 } else {
1157 rest
1158 }
1159}
1160
1161fn infer_field_unit(field_id: &str) -> Option<&'static str> {
1162 let normalized = field_id.to_ascii_lowercase();
1163 if normalized.contains("frequency_hz") || normalized.contains("frequency_response") {
1164 return Some("Hz");
1165 }
1166 if normalized.contains("eigenvalue") {
1167 return Some("rad^2/s^2");
1168 }
1169 if normalized.contains("sound_pressure_level_db") {
1170 return Some("dB");
1171 }
1172 if normalized.contains("temperature_gradient") {
1173 return Some("K/m");
1174 }
1175 if normalized.contains("temperature") || normalized.contains("temperature_jump") {
1176 return Some("K");
1177 }
1178 if normalized.contains("electric_potential") {
1179 return Some("V");
1180 }
1181 if normalized.contains("electric_field") {
1182 return Some("V/m");
1183 }
1184 if normalized.contains("electric_flux_density") {
1185 return Some("C/m^2");
1186 }
1187 if normalized.contains("current_density") {
1188 return Some("A/m^2");
1189 }
1190 if normalized.contains("vector_potential") {
1191 return Some("Wb/m");
1192 }
1193 if normalized.contains("magnetic_flux_density") {
1194 return Some("T");
1195 }
1196 if normalized.contains("magnetic_field") {
1197 return Some("A/m");
1198 }
1199 if normalized.contains("poynting_vector")
1200 || normalized.contains("boundary_heat_flux")
1201 || normalized.contains("interface_heat_flux")
1202 || normalized.ends_with(".heat_flux")
1203 || normalized.contains(".heat_flux.")
1204 {
1205 return Some("W/m^2");
1206 }
1207 if normalized.contains("power_loss_density")
1208 || normalized.contains("joule_heat")
1209 || normalized.contains("heat_source")
1210 {
1211 return Some("W/m^3");
1212 }
1213 if normalized.contains("energy_density") {
1214 return Some("J/m^3");
1215 }
1216 if normalized.contains("kinetic_energy")
1217 || normalized.contains("strain_energy")
1218 || normalized.contains("total_strain_energy")
1219 {
1220 return Some("J");
1221 }
1222 if normalized.contains("modal_mass") {
1223 return Some("kg");
1224 }
1225 if normalized.contains("modal_stiffness") {
1226 return Some("N/m");
1227 }
1228 if normalized.contains("wall_shear_stress")
1229 || normalized.contains("contact_pressure")
1230 || normalized.contains("interface_pressure")
1231 || normalized.contains("fluid_pressure")
1232 || normalized.contains(".pressure")
1233 || normalized.contains("stress")
1234 || normalized.contains("traction")
1235 || normalized.contains("von_mises")
1236 {
1237 return Some("Pa");
1238 }
1239 if normalized.contains("reaction_force")
1240 || normalized.contains("beam_axial_force")
1241 || normalized.contains("beam_shear_force")
1242 || normalized.contains("shell_membrane_force")
1243 || normalized.contains("shell_transverse_shear")
1244 {
1245 return Some("N");
1246 }
1247 if normalized.contains("reaction_moment")
1248 || normalized.contains("beam_torsion_moment")
1249 || normalized.contains("beam_bending_moment")
1250 || normalized.contains("shell_bending_moment")
1251 {
1252 return Some("N*m");
1253 }
1254 if normalized.contains("rotation") {
1255 return Some("rad");
1256 }
1257 if normalized.contains("contact_gap")
1258 || normalized.contains("displacement")
1259 || normalized.contains("mode_shape")
1260 {
1261 return Some("m");
1262 }
1263 if normalized.contains("velocity") {
1264 return Some("m/s");
1265 }
1266 if normalized.contains("acceleration") {
1267 return Some("m/s^2");
1268 }
1269 if normalized.contains("strain")
1270 || normalized.contains("residual")
1271 || normalized.contains("equation_scale")
1272 || normalized.contains("load_factor")
1273 || normalized.contains("coupling_iteration_count")
1274 || normalized.contains("reynolds_number")
1275 || normalized.contains("phase")
1276 || normalized.contains("orthogonality")
1277 || normalized.contains("participation_factor")
1278 || normalized.contains("relative_frequency_separation")
1279 {
1280 return Some("1");
1281 }
1282 None
1283}
1284
1285fn infer_field_location(field_id: &str) -> AnalysisFieldLocation {
1286 let normalized = field_id.to_ascii_lowercase();
1287 if normalized.contains("vector_potential") {
1288 return AnalysisFieldLocation::Edge;
1289 }
1290 if normalized.contains("wall_shear_stress") || normalized.contains("boundary_heat_flux") {
1291 return AnalysisFieldLocation::BoundaryFace;
1292 }
1293 if normalized.contains("interface_")
1294 || normalized.contains("contact_pressure")
1295 || normalized.contains("contact_gap")
1296 {
1297 return AnalysisFieldLocation::InterfaceFace;
1298 }
1299 if normalized.starts_with("modal.")
1300 && !normalized.starts_with("modal.mode_shape.")
1301 && !normalized.contains("orthogonality")
1302 {
1303 return AnalysisFieldLocation::Mode;
1304 }
1305 if normalized.contains("residual")
1306 || normalized.contains("equation_scale")
1307 || normalized.contains("energy")
1308 || normalized.contains("load_factor")
1309 || normalized.contains("coupling_iteration_count")
1310 || normalized.contains("orthogonality")
1311 {
1312 return AnalysisFieldLocation::Global;
1313 }
1314 if normalized.starts_with("acoustic.") {
1315 return AnalysisFieldLocation::Node;
1316 }
1317 if normalized.contains("beam_") || normalized.contains("shell_") {
1318 return AnalysisFieldLocation::Element;
1319 }
1320 if normalized.contains("temperature_gradient")
1321 || normalized.contains("heat_flux")
1322 || normalized.contains("heat_source")
1323 || normalized.contains("joule_heat")
1324 || normalized.contains("magnetic_flux_density")
1325 || normalized.contains("magnetic_field")
1326 || normalized.contains("electric_field")
1327 || normalized.contains("electric_flux_density")
1328 || normalized.contains("current_density")
1329 || normalized.contains("poynting_vector")
1330 || normalized.contains("vorticity")
1331 || normalized.contains("pressure")
1332 || normalized.contains("stress")
1333 || normalized.contains("strain")
1334 || normalized.contains("von_mises")
1335 {
1336 return AnalysisFieldLocation::Element;
1337 }
1338 if normalized.contains("displacement")
1339 || normalized.contains("mode_shape")
1340 || normalized.contains("temperature")
1341 || normalized.starts_with("acoustic.")
1342 || normalized.contains("electric_potential")
1343 {
1344 return AnalysisFieldLocation::Node;
1345 }
1346 AnalysisFieldLocation::Element
1347}
1348
1349fn infer_field_kind(field_id: &str, shape: &[usize]) -> AnalysisFieldKind {
1350 let normalized = field_id.to_ascii_lowercase();
1351 if normalized.contains("magnitude") {
1352 return AnalysisFieldKind::Scalar;
1353 }
1354 if normalized.contains("heat_flux") {
1355 return match shape {
1356 [_, components] if (2..=3).contains(components) => AnalysisFieldKind::Vector,
1357 _ => AnalysisFieldKind::Scalar,
1358 };
1359 }
1360 if normalized.contains("beam_shear_force")
1361 || normalized.contains("beam_bending_moment")
1362 || normalized.contains("beam_bending_stress")
1363 || normalized.contains("shell_membrane_force")
1364 || normalized.contains("shell_bending_moment")
1365 || normalized.contains("shell_transverse_shear")
1366 {
1367 return AnalysisFieldKind::Vector;
1368 }
1369 if normalized.contains("shell_von_mises") {
1370 return AnalysisFieldKind::Scalar;
1371 }
1372 if normalized.contains("beam_axial_force")
1373 || normalized.contains("beam_torsion_moment")
1374 || normalized.contains("beam_torsion_stress")
1375 {
1376 return AnalysisFieldKind::Scalar;
1377 }
1378 if normalized.contains("temperature_gradient")
1379 || normalized.contains("wall_shear_stress")
1380 || normalized.contains("vorticity")
1381 || normalized.contains("velocity")
1382 || normalized.contains("traction")
1383 || normalized.contains("magnetic_field")
1384 || normalized.contains("electric_field")
1385 || normalized.contains("current_density")
1386 {
1387 return AnalysisFieldKind::Vector;
1388 }
1389 if normalized.contains("von_mises")
1390 || normalized.contains("equivalent_plastic_strain")
1391 || normalized.contains("pressure")
1392 || normalized.contains("phase")
1393 || normalized.contains("reynolds_number")
1394 || normalized.contains("temperature")
1395 || normalized.contains("energy")
1396 || normalized.contains("residual")
1397 {
1398 return AnalysisFieldKind::Scalar;
1399 }
1400 if normalized.contains("stress") || normalized.contains("strain") {
1401 return AnalysisFieldKind::Tensor;
1402 }
1403 if normalized.contains("orthogonality") {
1404 return AnalysisFieldKind::Tensor;
1405 }
1406 if normalized.contains("displacement")
1407 || normalized.contains("rotation")
1408 || normalized.contains("mode_shape")
1409 || normalized.contains("reaction_force")
1410 || normalized.contains("reaction_moment")
1411 || normalized.contains("vector")
1412 || normalized.contains("flux")
1413 {
1414 return AnalysisFieldKind::Vector;
1415 }
1416 match shape {
1417 [] | [_] => AnalysisFieldKind::Scalar,
1418 [_, 1] => AnalysisFieldKind::Scalar,
1419 [_, 2] | [_, 3] => AnalysisFieldKind::Vector,
1420 [_, _, ..] => AnalysisFieldKind::Tensor,
1421 }
1422}
1423
1424fn infer_component_count(field_id: &str, shape: &[usize]) -> Option<usize> {
1425 let normalized = field_id.to_ascii_lowercase();
1426 if normalized.contains("magnitude") {
1427 return None;
1428 }
1429 if normalized.contains("equivalent_plastic_strain") {
1430 return None;
1431 }
1432 if normalized.contains("heat_flux") {
1433 return match shape {
1434 [_, components] if (2..=3).contains(components) => Some(*components),
1435 _ => None,
1436 };
1437 }
1438 if normalized.contains("beam_shear_force")
1439 || normalized.contains("beam_bending_moment")
1440 || normalized.contains("beam_bending_stress")
1441 {
1442 return Some(2);
1443 }
1444 if normalized.contains("shell_membrane_force") || normalized.contains("shell_bending_moment") {
1445 return Some(3);
1446 }
1447 if normalized.contains("shell_transverse_shear") {
1448 return Some(2);
1449 }
1450 if normalized.contains("shell_von_mises") {
1451 return None;
1452 }
1453 if normalized.contains("beam_axial_force")
1454 || normalized.contains("beam_torsion_moment")
1455 || normalized.contains("beam_torsion_stress")
1456 {
1457 return None;
1458 }
1459 if normalized.contains("temperature_gradient")
1460 || normalized.contains("wall_shear_stress")
1461 || normalized.contains("vorticity")
1462 || normalized.contains("velocity")
1463 || normalized.contains("traction")
1464 || normalized.contains("magnetic_field")
1465 || normalized.contains("electric_field")
1466 || normalized.contains("current_density")
1467 {
1468 return Some(
1469 shape
1470 .last()
1471 .copied()
1472 .filter(|value| (2..=3).contains(value))
1473 .unwrap_or(3),
1474 );
1475 }
1476 if normalized.contains("von_mises")
1477 || normalized.contains("equivalent_plastic_strain")
1478 || normalized.contains("pressure")
1479 || normalized.contains("phase")
1480 || normalized.contains("reynolds_number")
1481 || normalized.contains("temperature")
1482 || normalized.contains("energy")
1483 || normalized.contains("residual")
1484 {
1485 return None;
1486 }
1487 if normalized.contains("displacement")
1488 || normalized.contains("rotation")
1489 || normalized.contains("mode_shape")
1490 || normalized.contains("reaction_force")
1491 || normalized.contains("reaction_moment")
1492 || normalized.contains("particle_velocity")
1493 || normalized.contains("vector")
1494 || normalized.contains("flux")
1495 {
1496 return Some(
1497 shape
1498 .last()
1499 .copied()
1500 .filter(|value| (2..=6).contains(value))
1501 .unwrap_or(3),
1502 );
1503 }
1504 if normalized.contains("stress") || normalized.contains("strain") {
1505 return Some(
1506 shape
1507 .last()
1508 .copied()
1509 .filter(|value| *value == 6)
1510 .unwrap_or(6),
1511 );
1512 }
1513 if normalized.contains("orthogonality") {
1514 return None;
1515 }
1516 match shape {
1517 [_, count] if (1..=6).contains(count) => Some(*count),
1518 _ => None,
1519 }
1520}
1521
1522#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1523pub struct AnalysisResultsSummary {
1524 pub field_count: usize,
1525 pub total_elements: usize,
1526 pub mode_count: usize,
1527 pub available_mode_indices: Vec<usize>,
1528 pub min_frequency_hz: Option<f64>,
1529 pub max_frequency_hz: Option<f64>,
1530 pub max_modal_residual_norm: Option<f64>,
1531 pub first_mode_converged: Option<bool>,
1532 pub snapshot_count: usize,
1533 pub time_start_s: Option<f64>,
1534 pub time_end_s: Option<f64>,
1535 pub max_transient_residual_norm: Option<f64>,
1536 pub final_step_converged: Option<bool>,
1537 pub increment_count: usize,
1538 pub failed_increment_count: Option<usize>,
1539 pub max_nonlinear_residual_norm: Option<f64>,
1540 pub max_nonlinear_increment_norm: Option<f64>,
1541 pub max_nonlinear_iteration_count: Option<usize>,
1542 pub final_increment_converged: Option<bool>,
1543 pub nonlinear_line_search_backtracks: Option<usize>,
1544 pub nonlinear_max_backtracks_per_increment: Option<usize>,
1545 pub nonlinear_tangent_rebuild_count: Option<usize>,
1546 pub nonlinear_iteration_spike_count: Option<usize>,
1547 pub nonlinear_convergence_stall_count: Option<usize>,
1548 pub nonlinear_backtrack_burst_count: Option<usize>,
1549 pub prep_calibration_profile: Option<String>,
1550 pub prep_calibration_fingerprint: Option<u64>,
1551 pub prep_acceptance_score: Option<f64>,
1552 pub prep_acceptance_passed: Option<bool>,
1553 pub prep_acceptance_fingerprint: Option<u64>,
1554 pub thermo_coupling_enabled: Option<bool>,
1555 pub thermo_coupling_fingerprint: Option<u64>,
1556 pub thermo_constitutive_temperature_factor: Option<f64>,
1557 pub thermo_effective_modulus_scale: Option<f64>,
1558 pub thermo_constitutive_material_spread_ratio: Option<f64>,
1559 pub thermo_assignment_heterogeneity_index: Option<f64>,
1560 pub thermo_region_delta_count: Option<f64>,
1561 pub thermo_spatial_coverage_ratio: Option<f64>,
1562 pub thermo_field_extrapolation_ratio: Option<f64>,
1563 pub thermo_field_clamp_ratio: Option<f64>,
1564 pub thermo_transient_severity: Option<f64>,
1565 pub thermo_nonlinear_severity: Option<f64>,
1566 pub electro_thermal_coupling_enabled: Option<bool>,
1567 pub electro_thermal_coupling_fingerprint: Option<u64>,
1568 pub electro_joule_heating_scale: Option<f64>,
1569 pub electro_conductivity_spread_ratio: Option<f64>,
1570 pub electro_transient_severity: Option<f64>,
1571 pub electro_transient_time_scale_mean: Option<f64>,
1572 pub electro_nonlinear_severity: Option<f64>,
1573 pub electro_nonlinear_time_scale_mean: Option<f64>,
1574 pub plastic_nonlinear_severity: Option<f64>,
1575 pub plastic_nonlinear_severity_mean: Option<f64>,
1576 pub plastic_load_realization_ratio: Option<f64>,
1577 pub plastic_load_amplification_ratio: Option<f64>,
1578 pub contact_nonlinear_severity: Option<f64>,
1579 pub contact_nonlinear_severity_mean: Option<f64>,
1580 pub contact_load_realization_ratio: Option<f64>,
1581 pub contact_load_amplification_ratio: Option<f64>,
1582 pub thermal_max_residual_norm: Option<f64>,
1583 pub thermal_min_temperature_k: Option<f64>,
1584 pub thermal_max_temperature_k: Option<f64>,
1585 pub thermal_conductivity_spread_ratio: Option<f64>,
1586 pub thermal_heat_capacity_spread_ratio: Option<f64>,
1587 pub thermal_spatial_gradient_index: Option<f64>,
1588 pub thermal_monotonic_response_fraction: Option<f64>,
1589 pub thermal_response_realization_ratio: Option<f64>,
1590 pub electromagnetic_enabled: Option<bool>,
1591 pub electromagnetic_formulation_coverage_ratio: Option<f64>,
1592 pub electromagnetic_magnetostatic_curl_curl_coverage_ratio: Option<f64>,
1593 pub electromagnetic_magnetoquasistatic_eddy_current_coverage_ratio: Option<f64>,
1594 pub electromagnetic_full_wave_displacement_current_coverage_ratio: Option<f64>,
1595 pub electromagnetic_displacement_to_conduction_ratio: Option<f64>,
1596 pub electromagnetic_material_frequency_response_coverage_ratio: Option<f64>,
1597 pub electromagnetic_reference_frequency_hz: Option<f64>,
1598 pub electromagnetic_applied_current_a: Option<f64>,
1599 pub electromagnetic_solve_quality: Option<f64>,
1600 pub electromagnetic_conductivity_spread_ratio: Option<f64>,
1601 pub electromagnetic_relative_permittivity_spread_ratio: Option<f64>,
1602 pub electromagnetic_relative_permeability_spread_ratio: Option<f64>,
1603 pub electromagnetic_material_heterogeneity_index: Option<f64>,
1604 pub electromagnetic_assignment_coverage_ratio: Option<f64>,
1605 pub electromagnetic_assigned_coefficient_coverage_ratio: Option<f64>,
1606 pub electromagnetic_region_coefficient_contrast_index: Option<f64>,
1607 pub electromagnetic_condition_number_estimate: Option<f64>,
1608 pub electromagnetic_source_realization_ratio: Option<f64>,
1609 pub electromagnetic_source_region_coverage_ratio: Option<f64>,
1610 pub electromagnetic_source_material_alignment_ratio: Option<f64>,
1611 pub electromagnetic_source_localization_ratio: Option<f64>,
1612 pub electromagnetic_source_overlap_ratio: Option<f64>,
1613 pub electromagnetic_source_interference_index: Option<f64>,
1614 pub electromagnetic_boundary_anchor_ratio: Option<f64>,
1615 pub electromagnetic_boundary_condition_localization_ratio: Option<f64>,
1616 pub electromagnetic_ground_anchor_effectiveness_ratio: Option<f64>,
1617 pub electromagnetic_insulation_leakage_ratio: Option<f64>,
1618 pub electromagnetic_flux_divergence_ratio: Option<f64>,
1619 pub electromagnetic_energy_imbalance_ratio: Option<f64>,
1620 pub electromagnetic_boundary_energy_ratio: Option<f64>,
1621 pub electromagnetic_boundary_penalty_conditioning_contribution: Option<f64>,
1622 pub electromagnetic_source_region_energy_consistency_ratio: Option<f64>,
1623 pub electromagnetic_real_residual_norm: Option<f64>,
1624 pub electromagnetic_imag_residual_norm: Option<f64>,
1625 pub electromagnetic_sweep_count: Option<f64>,
1626 pub electromagnetic_resonance_peak_frequency_hz: Option<f64>,
1627 pub electromagnetic_resonance_peak_flux_density: Option<f64>,
1628 pub electromagnetic_resonance_bandwidth_hz: Option<f64>,
1629 pub electromagnetic_resonance_quality_factor: Option<f64>,
1630 pub electromagnetic_resonance_flux_gain: Option<f64>,
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1634pub struct AnalysisResultsData {
1635 #[serde(default)]
1636 pub field_descriptors: Vec<AnalysisFieldDescriptor>,
1637 pub fields: Vec<AnalysisField>,
1638 pub modal_results: Option<ModalResultsData>,
1639 #[serde(default)]
1640 pub thermal_results: Option<ThermalResultsData>,
1641 pub transient_results: Option<TransientResultsData>,
1642 pub nonlinear_results: Option<NonlinearResultsData>,
1643 #[serde(default)]
1644 pub electromagnetic_results: Option<ElectromagneticResultsData>,
1645 pub diagnostics: Option<Vec<FeaDiagnostic>>,
1646 pub run_status: RunStatus,
1647 pub publishable: bool,
1648 pub quality_reasons: Vec<QualityReason>,
1649 pub provenance: RunProvenance,
1650 pub summary: AnalysisResultsSummary,
1651}
1652
1653#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1654pub struct AnalysisResultsCompareQuery {
1655 pub baseline_run_id: String,
1656 pub candidate_run_id: String,
1657}
1658
1659#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1660pub struct AnalysisResultsCompareData {
1661 pub baseline_run_id: String,
1662 pub candidate_run_id: String,
1663 pub publishable_changed: bool,
1664 pub run_status_changed: bool,
1665 pub quality_reason_count_delta: i64,
1666 pub failed_increment_delta: Option<i64>,
1667 pub max_iteration_delta: Option<i64>,
1668 pub nonlinear_spike_count_delta: Option<i64>,
1669 pub nonlinear_stall_count_delta: Option<i64>,
1670 pub solve_ms_delta: Option<f64>,
1671}
1672
1673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1674pub struct AnalysisTrendsQuery {
1675 pub window_size: usize,
1676}
1677
1678impl Default for AnalysisTrendsQuery {
1679 fn default() -> Self {
1680 Self { window_size: 16 }
1681 }
1682}
1683
1684#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1685pub struct AnalysisStudySpec {
1686 pub study_id: String,
1687 pub geometry: GeometryAsset,
1688 pub create_model_intent: AnalysisCreateModelIntentSpec,
1689 #[serde(default)]
1690 pub model: Option<AnalysisModel>,
1691 pub run_kind: AnalysisRunKind,
1692 pub backend: ComputeBackend,
1693 #[serde(default)]
1694 pub linear_static_run_options: Option<AnalysisRunOptions>,
1695 #[serde(default)]
1696 pub modal_run_options: Option<AnalysisModalRunOptions>,
1697 #[serde(default)]
1698 pub acoustic_run_options: Option<AnalysisAcousticRunOptions>,
1699 #[serde(default)]
1700 pub thermal_run_options: Option<AnalysisThermalRunOptions>,
1701 #[serde(default)]
1702 pub transient_run_options: Option<AnalysisTransientRunOptions>,
1703 #[serde(default)]
1704 pub cfd_run_options: Option<AnalysisCfdRunOptions>,
1705 #[serde(default)]
1706 pub cht_run_options: Option<AnalysisChtRunOptions>,
1707 #[serde(default)]
1708 pub fsi_run_options: Option<AnalysisFsiRunOptions>,
1709 #[serde(default)]
1710 pub nonlinear_run_options: Option<AnalysisNonlinearRunOptions>,
1711 #[serde(default)]
1712 pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
1713}
1714
1715#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1716pub struct AnalysisStudyValidateResult {
1717 pub valid: bool,
1718 pub issue_codes: Vec<String>,
1719 #[serde(default)]
1720 pub issues: Vec<AnalysisStudyIssue>,
1721 pub evidence_artifact_path: String,
1722}
1723
1724#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1725pub struct AnalysisStudyIssue {
1726 pub code: String,
1727 pub message: String,
1728}
1729
1730#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1731pub struct AnalysisStudyPlanData {
1732 pub study_id: String,
1733 pub model_id: String,
1734 pub run_kind: AnalysisRunKind,
1735 pub backend: ComputeBackend,
1736 #[serde(default)]
1737 pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
1738 #[serde(default)]
1739 pub run_options: serde_json::Value,
1740 pub operation_sequence: Vec<String>,
1741 pub run_operation: String,
1742 pub run_op_version: String,
1743 pub study_fingerprint: String,
1744 pub evidence_artifact_path: String,
1745}
1746
1747#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1748pub struct AnalysisStudyRunData {
1749 pub study_id: String,
1750 pub model_id: String,
1751 pub run_kind: AnalysisRunKind,
1752 pub backend: ComputeBackend,
1753 #[serde(default)]
1754 pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
1755 #[serde(default)]
1756 pub run_options: serde_json::Value,
1757 #[serde(default)]
1758 pub prep_artifact_id: Option<String>,
1759 pub study_fingerprint: String,
1760 pub operation_sequence: Vec<String>,
1761 pub run_operation: String,
1762 pub run_op_version: String,
1763 pub run_id: String,
1764 pub run_status: RunStatus,
1765 pub publishable: bool,
1766 pub solver_convergence: QualityGate,
1767 pub result_quality: QualityGate,
1768 #[serde(default)]
1769 pub quality_reasons: Vec<QualityReason>,
1770 pub provenance: RunProvenance,
1771 pub evidence_artifact_path: String,
1772}
1773
1774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1775pub struct AnalysisStudySweepSpec {
1776 pub sweep_id: String,
1777 pub studies: Vec<AnalysisStudySpec>,
1778 #[serde(default = "default_study_sweep_fail_fast")]
1779 pub fail_fast: bool,
1780}
1781
1782#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1783pub struct AnalysisStudySweepValidateEntry {
1784 pub study_id: String,
1785 pub valid: bool,
1786 pub issue_codes: Vec<String>,
1787 #[serde(default)]
1788 pub issues: Vec<AnalysisStudyIssue>,
1789}
1790
1791#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1792pub struct AnalysisStudySweepValidateData {
1793 pub sweep_id: String,
1794 pub valid: bool,
1795 pub issue_codes: Vec<String>,
1796 pub study_entries: Vec<AnalysisStudySweepValidateEntry>,
1797 pub evidence_artifact_path: String,
1798}
1799
1800#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1801pub struct AnalysisStudySweepPlanEntry {
1802 pub study_id: String,
1803 pub model_id: String,
1804 pub run_kind: AnalysisRunKind,
1805 pub backend: ComputeBackend,
1806 #[serde(default)]
1807 pub electromagnetic_run_options: Option<AnalysisElectromagneticRunOptions>,
1808 #[serde(default)]
1809 pub run_options: serde_json::Value,
1810 pub operation_sequence: Vec<String>,
1811 pub run_operation: String,
1812 pub run_op_version: String,
1813 pub study_fingerprint: String,
1814}
1815
1816#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1817pub struct AnalysisStudySweepPlanData {
1818 pub sweep_id: String,
1819 pub study_count: usize,
1820 pub planned_count: usize,
1821 pub failed_count: usize,
1822 #[serde(default)]
1823 pub failure_entries: Vec<AnalysisStudySweepFailureEntry>,
1824 pub plan_entries: Vec<AnalysisStudySweepPlanEntry>,
1825 pub evidence_artifact_path: String,
1826}
1827
1828#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1829pub struct AnalysisStudySweepRunEntry {
1830 pub study_id: String,
1831 pub run_kind: AnalysisRunKind,
1832 pub run_id: String,
1833 pub run_status: RunStatus,
1834 pub publishable: bool,
1835 pub run_operation: String,
1836 pub run_op_version: String,
1837}
1838
1839#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1840pub struct AnalysisStudySweepData {
1841 pub sweep_id: String,
1842 pub study_count: usize,
1843 pub success_count: usize,
1844 pub failed_count: usize,
1845 #[serde(default)]
1846 pub failure_entries: Vec<AnalysisStudySweepFailureEntry>,
1847 pub run_entries: Vec<AnalysisStudySweepRunEntry>,
1848 pub evidence_artifact_path: String,
1849}
1850
1851#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1852pub struct AnalysisStudySweepFailureEntry {
1853 pub study_id: String,
1854 pub study_index: usize,
1855 pub error_code: String,
1856 pub message: String,
1857}
1858
1859fn default_study_sweep_fail_fast() -> bool {
1860 true
1861}
1862
1863#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
1864#[serde(rename_all = "snake_case")]
1865pub enum AnalysisRunKind {
1866 LinearStatic,
1867 Modal,
1868 Acoustic,
1869 Thermal,
1870 Transient,
1871 Cfd,
1872 Cht,
1873 Fsi,
1874 Nonlinear,
1875 Electromagnetic,
1876}
1877
1878impl AnalysisCreateModelProfile {
1879 pub fn derived_run_kind(self) -> AnalysisRunKind {
1880 match self {
1881 AnalysisCreateModelProfile::LinearStaticStructural => AnalysisRunKind::LinearStatic,
1882 AnalysisCreateModelProfile::ThermoMechanicalCoupled => AnalysisRunKind::Transient,
1883 AnalysisCreateModelProfile::ThermalStandalone => AnalysisRunKind::Thermal,
1884 AnalysisCreateModelProfile::ModalStructural => AnalysisRunKind::Modal,
1885 AnalysisCreateModelProfile::AcousticHarmonic => AnalysisRunKind::Acoustic,
1886 AnalysisCreateModelProfile::TransientStructural => AnalysisRunKind::Transient,
1887 AnalysisCreateModelProfile::NonlinearStructural => AnalysisRunKind::Nonlinear,
1888 AnalysisCreateModelProfile::ElectromagneticStatic => AnalysisRunKind::Electromagnetic,
1889 AnalysisCreateModelProfile::CfdSteadyState
1890 | AnalysisCreateModelProfile::CfdTransient => AnalysisRunKind::Cfd,
1891 AnalysisCreateModelProfile::ChtCoupled => AnalysisRunKind::Cht,
1892 AnalysisCreateModelProfile::FsiCoupled => AnalysisRunKind::Fsi,
1893 }
1894 }
1895}
1896
1897#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1898pub struct AnalysisTrendKindSummary {
1899 pub run_kind: AnalysisRunKind,
1900 pub sample_count: usize,
1901 pub median_solve_ms: Option<f64>,
1902 pub p95_solve_ms: Option<f64>,
1903 pub publishable_rate: f64,
1904 pub failed_increment_rate: Option<f64>,
1905 pub mean_spike_count: Option<f64>,
1906 pub mean_stall_count: Option<f64>,
1907 pub prep_acceptance_rate: Option<f64>,
1908 pub prep_calibration_fast_rate: Option<f64>,
1909 pub prep_calibration_balanced_rate: Option<f64>,
1910 pub prep_calibration_conservative_rate: Option<f64>,
1911 pub thermo_coupling_enabled_rate: Option<f64>,
1912 pub thermo_transient_warn_rate: Option<f64>,
1913 pub thermo_nonlinear_warn_rate: Option<f64>,
1914 pub thermo_spread_breach_rate: Option<f64>,
1915 pub thermo_heterogeneity_breach_rate: Option<f64>,
1916 pub electro_thermal_coupling_enabled_rate: Option<f64>,
1917 pub electro_transient_warn_rate: Option<f64>,
1918 pub electro_nonlinear_warn_rate: Option<f64>,
1919 pub plastic_nonlinear_warn_rate: Option<f64>,
1920 pub contact_nonlinear_warn_rate: Option<f64>,
1921 pub thermal_stability_warn_rate: Option<f64>,
1922 pub thermal_constitutive_warn_rate: Option<f64>,
1923 pub thermal_spread_breach_rate: Option<f64>,
1924 pub electromagnetic_solve_warn_rate: Option<f64>,
1925 pub electromagnetic_spread_breach_rate: Option<f64>,
1926 pub electromagnetic_heterogeneity_breach_rate: Option<f64>,
1927 pub electromagnetic_coverage_breach_rate: Option<f64>,
1928 pub electromagnetic_contrast_breach_rate: Option<f64>,
1929 pub electromagnetic_conditioning_breach_rate: Option<f64>,
1930 pub electromagnetic_source_realization_breach_rate: Option<f64>,
1931 pub electromagnetic_source_region_coverage_breach_rate: Option<f64>,
1932 pub electromagnetic_source_material_alignment_breach_rate: Option<f64>,
1933 pub electromagnetic_source_overlap_breach_rate: Option<f64>,
1934 pub electromagnetic_source_interference_breach_rate: Option<f64>,
1935 pub electromagnetic_boundary_anchor_breach_rate: Option<f64>,
1936 pub electromagnetic_boundary_localization_breach_rate: Option<f64>,
1937 pub electromagnetic_ground_effectiveness_breach_rate: Option<f64>,
1938 pub electromagnetic_insulation_leakage_breach_rate: Option<f64>,
1939 pub electromagnetic_divergence_breach_rate: Option<f64>,
1940 pub electromagnetic_energy_imbalance_breach_rate: Option<f64>,
1941 pub electromagnetic_boundary_energy_breach_rate: Option<f64>,
1942 pub electromagnetic_boundary_penalty_contribution_breach_rate: Option<f64>,
1943 pub electromagnetic_source_region_energy_consistency_breach_rate: Option<f64>,
1944 pub electromagnetic_real_residual_breach_rate: Option<f64>,
1945 pub electromagnetic_imag_residual_breach_rate: Option<f64>,
1946 pub electromagnetic_sweep_coverage_breach_rate: Option<f64>,
1947 pub electromagnetic_resonance_sharpness_breach_rate: Option<f64>,
1948}
1949
1950#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1951pub struct AnalysisTrendsData {
1952 pub window_size: usize,
1953 pub summaries: Vec<AnalysisTrendKindSummary>,
1954}
1955
1956#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1957pub struct ModalResultsData {
1958 pub modal_payload_version: String,
1959 pub eigenvalues_hz: Vec<f64>,
1960 pub mode_shapes: Vec<AnalysisField>,
1961 pub residual_norms: Vec<f64>,
1962 pub mode_units: ModalFrequencyUnits,
1963 pub frequency_basis: ModalFrequencyBasis,
1964}
1965
1966#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1967pub struct ThermalResultsData {
1968 pub thermal_payload_version: String,
1969 pub time_points_s: Vec<f64>,
1970 pub temperature_snapshots: Vec<AnalysisField>,
1971 #[serde(default)]
1972 pub temperature_gradient_snapshots: Vec<AnalysisField>,
1973 #[serde(default)]
1974 pub heat_flux_snapshots: Vec<AnalysisField>,
1975 #[serde(default)]
1976 pub heat_source_snapshots: Vec<AnalysisField>,
1977 #[serde(default)]
1978 pub boundary_heat_flux_snapshots: Vec<AnalysisField>,
1979 pub residual_norms: Vec<f64>,
1980 pub reference_temperature_k: f64,
1981}
1982
1983#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1984pub struct TransientResultsData {
1985 pub transient_payload_version: String,
1986 pub time_points_s: Vec<f64>,
1987 pub displacement_snapshots: Vec<AnalysisField>,
1988 #[serde(default)]
1989 pub rotation_snapshots: Vec<AnalysisField>,
1990 #[serde(default)]
1991 pub velocity_snapshots: Vec<AnalysisField>,
1992 #[serde(default)]
1993 pub angular_velocity_snapshots: Vec<AnalysisField>,
1994 #[serde(default)]
1995 pub acceleration_snapshots: Vec<AnalysisField>,
1996 #[serde(default)]
1997 pub angular_acceleration_snapshots: Vec<AnalysisField>,
1998 #[serde(default)]
1999 pub von_mises_snapshots: Vec<AnalysisField>,
2000 #[serde(default)]
2001 pub kinetic_energy_snapshots: Vec<AnalysisField>,
2002 #[serde(default)]
2003 pub strain_energy_snapshots: Vec<AnalysisField>,
2004 #[serde(default)]
2005 pub residual_norm_snapshots: Vec<AnalysisField>,
2006 #[serde(default)]
2007 pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
2008 #[serde(default)]
2009 pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
2010 #[serde(default)]
2011 pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
2012 #[serde(default)]
2013 pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
2014 #[serde(default)]
2015 pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
2016 #[serde(default)]
2017 pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
2018 #[serde(default)]
2019 pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
2020 #[serde(default)]
2021 pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
2022 pub residual_norms: Vec<f64>,
2023 pub integration_method: TransientIntegrationMethod,
2024}
2025
2026#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2027pub struct NonlinearResultsData {
2028 pub nonlinear_payload_version: String,
2029 pub load_factors: Vec<f64>,
2030 pub displacement_snapshots: Vec<AnalysisField>,
2031 #[serde(default)]
2032 pub rotation_snapshots: Vec<AnalysisField>,
2033 #[serde(default)]
2034 pub von_mises_snapshots: Vec<AnalysisField>,
2035 #[serde(default)]
2036 pub plastic_strain_snapshots: Vec<AnalysisField>,
2037 #[serde(default)]
2038 pub equivalent_plastic_strain_snapshots: Vec<AnalysisField>,
2039 #[serde(default)]
2040 pub contact_pressure_snapshots: Vec<AnalysisField>,
2041 #[serde(default)]
2042 pub contact_gap_snapshots: Vec<AnalysisField>,
2043 #[serde(default)]
2044 pub load_factor_snapshots: Vec<AnalysisField>,
2045 #[serde(default)]
2046 pub residual_norm_snapshots: Vec<AnalysisField>,
2047 #[serde(default)]
2048 pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
2049 #[serde(default)]
2050 pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
2051 #[serde(default)]
2052 pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
2053 #[serde(default)]
2054 pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
2055 #[serde(default)]
2056 pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
2057 #[serde(default)]
2058 pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
2059 #[serde(default)]
2060 pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
2061 #[serde(default)]
2062 pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
2063 pub residual_norms: Vec<f64>,
2064 #[serde(default)]
2065 pub increment_norms: Vec<f64>,
2066 #[serde(default)]
2067 pub iteration_counts: Vec<usize>,
2068 #[serde(default)]
2069 pub failed_increments: usize,
2070 #[serde(default)]
2071 pub line_search_backtracks: usize,
2072 #[serde(default)]
2073 pub max_line_search_backtracks_per_increment: usize,
2074 #[serde(default)]
2075 pub tangent_rebuild_count: usize,
2076 #[serde(default)]
2077 pub iteration_spike_count: usize,
2078 #[serde(default)]
2079 pub convergence_stall_count: usize,
2080 #[serde(default)]
2081 pub backtrack_burst_count: usize,
2082 pub method: NonlinearMethod,
2083}
2084
2085#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
2086pub struct ElectromagneticResultsData {
2087 pub electromagnetic_payload_version: String,
2088 pub reference_frequency_hz: f64,
2089 pub applied_current_a: f64,
2090 pub vector_potential_real: AnalysisField,
2091 pub vector_potential_imag: AnalysisField,
2092 pub magnetic_flux_density_real: AnalysisField,
2093 pub magnetic_flux_density_imag: AnalysisField,
2094 pub magnetic_flux_density_magnitude: AnalysisField,
2095 pub magnetic_field_real: AnalysisField,
2096 pub magnetic_field_imag: AnalysisField,
2097 pub current_density_real: AnalysisField,
2098 pub current_density_imag: AnalysisField,
2099 pub electric_field_real: AnalysisField,
2100 pub electric_field_imag: AnalysisField,
2101 pub power_loss_density: AnalysisField,
2102 pub energy_density: AnalysisField,
2103 pub residual_real: AnalysisField,
2104 pub residual_imag: AnalysisField,
2105 pub electric_flux_density_real: AnalysisField,
2106 pub electric_flux_density_imag: AnalysisField,
2107 pub poynting_vector_real: AnalysisField,
2108 pub poynting_vector_imag: AnalysisField,
2109 #[serde(default)]
2110 pub sweep_frequency_hz: Vec<f64>,
2111 #[serde(default)]
2112 pub sweep_peak_flux_density: Vec<f64>,
2113 #[serde(default)]
2114 pub sweep_solve_quality: Vec<f64>,
2115 #[serde(default)]
2116 pub resonance_peak_frequency_hz: Option<f64>,
2117 #[serde(default)]
2118 pub resonance_peak_flux_density: Option<f64>,
2119 #[serde(default)]
2120 pub resonance_bandwidth_hz: Option<f64>,
2121 #[serde(default)]
2122 pub resonance_quality_factor: Option<f64>,
2123 #[serde(default)]
2124 pub resonance_flux_gain: Option<f64>,
2125}
2126
2127#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2128#[serde(rename_all = "snake_case")]
2129pub enum TransientIntegrationMethod {
2130 ImplicitEuler,
2131}
2132
2133#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2134#[serde(rename_all = "snake_case")]
2135pub enum NonlinearMethod {
2136 IncrementalNewtonRaphson,
2137}
2138
2139#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2140#[serde(rename_all = "snake_case")]
2141pub enum ModalFrequencyUnits {
2142 Hz,
2143}
2144
2145#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
2146#[serde(rename_all = "snake_case")]
2147pub enum ModalFrequencyBasis {
2148 NativeEigenSolve,
2149}
2150
2151pub(crate) fn format_precision_mode(mode: PrecisionMode) -> String {
2152 match mode {
2153 PrecisionMode::Fp32 => "fp32".to_string(),
2154 PrecisionMode::Fp64 => "fp64".to_string(),
2155 PrecisionMode::Mixed => "mixed".to_string(),
2156 }
2157}
2158
2159pub(crate) fn format_quality_policy(mode: QualityPolicy) -> String {
2160 match mode {
2161 QualityPolicy::Strict => "strict".to_string(),
2162 QualityPolicy::Balanced => "balanced".to_string(),
2163 QualityPolicy::Exploratory => "exploratory".to_string(),
2164 }
2165}