1use runmat_analysis_core::AnalysisField;
2use runmat_meshing_core::AnalysisMeshArtifact;
3use serde::{Deserialize, Serialize};
4use thiserror::Error;
5
6use crate::solve::{
7 backend::kind::LinearAlgebraBackendKind, preconditioner::SpdPreconditionerKind,
8};
9
10pub const FEA_FIELD_STRUCTURAL_DISPLACEMENT: &str = "structural.displacement";
11pub const FEA_FIELD_STRUCTURAL_ROTATION: &str = "structural.rotation";
12pub const FEA_FIELD_STRUCTURAL_VON_MISES: &str = "structural.von_mises";
13pub const FEA_FIELD_STRUCTURAL_NODAL_VON_MISES: &str = "structural.nodal_von_mises";
14pub const FEA_FIELD_STRUCTURAL_STRAIN: &str = "structural.strain";
15pub const FEA_FIELD_STRUCTURAL_STRESS: &str = "structural.stress";
16pub const FEA_FIELD_STRUCTURAL_REACTION_FORCE: &str = "structural.reaction_force";
17pub const FEA_FIELD_STRUCTURAL_REACTION_MOMENT: &str = "structural.reaction_moment";
18pub const FEA_FIELD_STRUCTURAL_STRAIN_ENERGY_DENSITY: &str = "structural.strain_energy_density";
19pub const FEA_FIELD_STRUCTURAL_TOTAL_STRAIN_ENERGY: &str = "structural.total_strain_energy";
20pub const FEA_FIELD_STRUCTURAL_RESIDUAL_NORM: &str = "structural.residual_norm";
21pub const FEA_FIELD_STRUCTURAL_EQUATION_SCALE: &str = "structural.equation_scale";
22pub const FEA_FIELD_STRUCTURAL_BEAM_AXIAL_FORCE: &str = "structural.beam_axial_force";
23pub const FEA_FIELD_STRUCTURAL_BEAM_SHEAR_FORCE: &str = "structural.beam_shear_force";
24pub const FEA_FIELD_STRUCTURAL_BEAM_TORSION_MOMENT: &str = "structural.beam_torsion_moment";
25pub const FEA_FIELD_STRUCTURAL_BEAM_BENDING_MOMENT: &str = "structural.beam_bending_moment";
26pub const FEA_FIELD_STRUCTURAL_BEAM_BENDING_STRESS: &str = "structural.beam_bending_stress";
27pub const FEA_FIELD_STRUCTURAL_BEAM_TORSION_STRESS: &str = "structural.beam_torsion_stress";
28pub const FEA_FIELD_STRUCTURAL_SHELL_MEMBRANE_FORCE: &str = "structural.shell_membrane_force";
29pub const FEA_FIELD_STRUCTURAL_SHELL_BENDING_MOMENT: &str = "structural.shell_bending_moment";
30pub const FEA_FIELD_STRUCTURAL_SHELL_TRANSVERSE_SHEAR: &str = "structural.shell_transverse_shear";
31pub const FEA_FIELD_STRUCTURAL_SHELL_VON_MISES: &str = "structural.shell_von_mises";
32pub const FEA_FIELD_EM_VECTOR_POTENTIAL_REAL: &str = "em.vector_potential_real";
33pub const FEA_FIELD_EM_VECTOR_POTENTIAL_IMAG: &str = "em.vector_potential_imag";
34pub const FEA_FIELD_EM_MAGNETIC_FLUX_DENSITY_REAL: &str = "em.magnetic_flux_density_real";
35pub const FEA_FIELD_EM_MAGNETIC_FLUX_DENSITY_IMAG: &str = "em.magnetic_flux_density_imag";
36pub const FEA_FIELD_EM_MAGNETIC_FLUX_DENSITY_MAGNITUDE: &str = "em.magnetic_flux_density_magnitude";
37pub const FEA_FIELD_EM_MAGNETIC_FIELD_REAL: &str = "em.magnetic_field_real";
38pub const FEA_FIELD_EM_MAGNETIC_FIELD_IMAG: &str = "em.magnetic_field_imag";
39pub const FEA_FIELD_EM_CURRENT_DENSITY_REAL: &str = "em.current_density_real";
40pub const FEA_FIELD_EM_CURRENT_DENSITY_IMAG: &str = "em.current_density_imag";
41pub const FEA_FIELD_EM_ELECTRIC_FIELD_REAL: &str = "em.electric_field_real";
42pub const FEA_FIELD_EM_ELECTRIC_FIELD_IMAG: &str = "em.electric_field_imag";
43pub const FEA_FIELD_EM_POWER_LOSS_DENSITY: &str = "em.power_loss_density";
44pub const FEA_FIELD_EM_ENERGY_DENSITY: &str = "em.energy_density";
45pub const FEA_FIELD_EM_RESIDUAL_REAL: &str = "em.residual_real";
46pub const FEA_FIELD_EM_RESIDUAL_IMAG: &str = "em.residual_imag";
47pub const FEA_FIELD_EM_ELECTRIC_FLUX_DENSITY_REAL: &str = "em.electric_flux_density_real";
48pub const FEA_FIELD_EM_ELECTRIC_FLUX_DENSITY_IMAG: &str = "em.electric_flux_density_imag";
49pub const FEA_FIELD_EM_POYNTING_VECTOR_REAL: &str = "em.poynting_vector_real";
50pub const FEA_FIELD_EM_POYNTING_VECTOR_IMAG: &str = "em.poynting_vector_imag";
51
52pub fn fea_modal_mode_shape_field_id(mode_number: usize) -> String {
53 format!("modal.mode_shape.{mode_number}")
54}
55
56pub const FEA_FIELD_MODAL_FREQUENCY_HZ: &str = "modal.frequency_hz";
57pub const FEA_FIELD_MODAL_EIGENVALUE: &str = "modal.eigenvalue";
58pub const FEA_FIELD_MODAL_MODAL_MASS: &str = "modal.modal_mass";
59pub const FEA_FIELD_MODAL_MODAL_STIFFNESS: &str = "modal.modal_stiffness";
60pub const FEA_FIELD_MODAL_PARTICIPATION_FACTOR: &str = "modal.participation_factor";
61pub const FEA_FIELD_MODAL_RESIDUAL_NORM: &str = "modal.residual_norm";
62pub const FEA_FIELD_MODAL_RELATIVE_FREQUENCY_SEPARATION: &str =
63 "modal.relative_frequency_separation";
64pub const FEA_FIELD_MODAL_M_ORTHOGONALITY: &str = "modal.m_orthogonality";
65
66pub const FEA_FIELD_ACOUSTIC_PRESSURE_REAL: &str = "acoustic.pressure_real";
67pub const FEA_FIELD_ACOUSTIC_PRESSURE_IMAG: &str = "acoustic.pressure_imag";
68pub const FEA_FIELD_ACOUSTIC_PRESSURE_MAGNITUDE: &str = "acoustic.pressure_magnitude";
69pub const FEA_FIELD_ACOUSTIC_PHASE: &str = "acoustic.phase";
70pub const FEA_FIELD_ACOUSTIC_SOUND_PRESSURE_LEVEL_DB: &str = "acoustic.sound_pressure_level_db";
71pub const FEA_FIELD_ACOUSTIC_PARTICLE_VELOCITY: &str = "acoustic.particle_velocity";
72
73pub fn fea_acoustic_frequency_response_field_id(frequency_hz: f64) -> String {
74 let scaled_hz = (frequency_hz.max(0.0) * 1_000.0).round() as u64;
75 format!("acoustic.frequency_response.hz_x1000_{scaled_hz}")
76}
77
78pub const FEA_FIELD_CFD_VELOCITY: &str = "cfd.velocity";
79pub const FEA_FIELD_CFD_PRESSURE: &str = "cfd.pressure";
80pub const FEA_FIELD_CFD_VORTICITY: &str = "cfd.vorticity";
81pub const FEA_FIELD_CFD_WALL_SHEAR_STRESS: &str = "cfd.wall_shear_stress";
82pub const FEA_FIELD_CFD_RESIDUAL_MOMENTUM: &str = "cfd.residual_momentum";
83pub const FEA_FIELD_CFD_RESIDUAL_CONTINUITY: &str = "cfd.residual_continuity";
84pub const FEA_FIELD_CFD_REYNOLDS_NUMBER: &str = "cfd.reynolds_number";
85
86pub const FEA_FIELD_CHT_FLUID_VELOCITY: &str = "cht.fluid_velocity";
87pub const FEA_FIELD_CHT_FLUID_PRESSURE: &str = "cht.fluid_pressure";
88
89pub fn fea_cht_fluid_temperature_field_id(step_index: usize) -> String {
90 format!("cht.fluid_temperature.{step_index}")
91}
92
93pub fn fea_cht_solid_temperature_field_id(step_index: usize) -> String {
94 format!("cht.solid_temperature.{step_index}")
95}
96
97pub fn fea_cht_interface_heat_flux_field_id(step_index: usize) -> String {
98 format!("cht.interface_heat_flux.{step_index}")
99}
100
101pub fn fea_cht_interface_temperature_jump_field_id(step_index: usize) -> String {
102 format!("cht.interface_temperature_jump.{step_index}")
103}
104
105pub fn fea_cht_energy_residual_field_id(step_index: usize) -> String {
106 format!("cht.energy_residual.{step_index}")
107}
108
109pub fn fea_fsi_fluid_velocity_field_id(step_index: usize) -> String {
110 format!("fsi.fluid_velocity.{step_index}")
111}
112
113pub fn fea_fsi_fluid_pressure_field_id(step_index: usize) -> String {
114 format!("fsi.fluid_pressure.{step_index}")
115}
116
117pub fn fea_fsi_structural_displacement_field_id(step_index: usize) -> String {
118 format!("fsi.structural_displacement.{step_index}")
119}
120
121pub fn fea_fsi_interface_pressure_field_id(step_index: usize) -> String {
122 format!("fsi.interface_pressure.{step_index}")
123}
124
125pub fn fea_fsi_interface_traction_field_id(step_index: usize) -> String {
126 format!("fsi.interface_traction.{step_index}")
127}
128
129pub fn fea_fsi_interface_displacement_field_id(step_index: usize) -> String {
130 format!("fsi.interface_displacement.{step_index}")
131}
132
133pub fn fea_fsi_interface_residual_field_id(step_index: usize) -> String {
134 format!("fsi.interface_residual.{step_index}")
135}
136
137pub fn fea_fsi_coupling_iteration_count_field_id(step_index: usize) -> String {
138 format!("fsi.coupling_iteration_count.{step_index}")
139}
140
141pub fn fea_thermal_temperature_field_id(snapshot_index: usize) -> String {
142 format!("thermal.temperature.{snapshot_index}")
143}
144
145pub fn fea_thermal_temperature_gradient_field_id(snapshot_index: usize) -> String {
146 format!("thermal.temperature_gradient.{snapshot_index}")
147}
148
149pub fn fea_thermal_heat_flux_field_id(snapshot_index: usize) -> String {
150 format!("thermal.heat_flux.{snapshot_index}")
151}
152
153pub fn fea_thermal_heat_source_field_id(snapshot_index: usize) -> String {
154 format!("thermal.heat_source.{snapshot_index}")
155}
156
157pub fn fea_thermal_boundary_heat_flux_field_id(snapshot_index: usize) -> String {
158 format!("thermal.boundary_heat_flux.{snapshot_index}")
159}
160
161pub fn fea_transient_displacement_field_id(snapshot_index: usize) -> String {
162 format!("transient.displacement.{snapshot_index}")
163}
164
165pub fn fea_transient_rotation_field_id(snapshot_index: usize) -> String {
166 format!("transient.rotation.{snapshot_index}")
167}
168
169pub fn fea_transient_velocity_field_id(snapshot_index: usize) -> String {
170 format!("transient.velocity.{snapshot_index}")
171}
172
173pub fn fea_transient_angular_velocity_field_id(snapshot_index: usize) -> String {
174 format!("transient.angular_velocity.{snapshot_index}")
175}
176
177pub fn fea_transient_acceleration_field_id(snapshot_index: usize) -> String {
178 format!("transient.acceleration.{snapshot_index}")
179}
180
181pub fn fea_transient_angular_acceleration_field_id(snapshot_index: usize) -> String {
182 format!("transient.angular_acceleration.{snapshot_index}")
183}
184
185pub fn fea_transient_von_mises_field_id(snapshot_index: usize) -> String {
186 format!("transient.von_mises.{snapshot_index}")
187}
188
189pub fn fea_transient_kinetic_energy_field_id(snapshot_index: usize) -> String {
190 format!("transient.kinetic_energy.{snapshot_index}")
191}
192
193pub fn fea_transient_strain_energy_field_id(snapshot_index: usize) -> String {
194 format!("transient.strain_energy.{snapshot_index}")
195}
196
197pub fn fea_transient_residual_norm_field_id(snapshot_index: usize) -> String {
198 format!("transient.residual_norm.{snapshot_index}")
199}
200
201pub fn fea_nonlinear_displacement_field_id(increment_index: usize) -> String {
202 format!("nonlinear.displacement.{increment_index}")
203}
204
205pub fn fea_nonlinear_rotation_field_id(increment_index: usize) -> String {
206 format!("nonlinear.rotation.{increment_index}")
207}
208
209pub fn fea_nonlinear_von_mises_field_id(increment_index: usize) -> String {
210 format!("nonlinear.von_mises.{increment_index}")
211}
212
213pub fn fea_nonlinear_plastic_strain_field_id(increment_index: usize) -> String {
214 format!("nonlinear.plastic_strain.{increment_index}")
215}
216
217pub fn fea_nonlinear_equivalent_plastic_strain_field_id(increment_index: usize) -> String {
218 format!("nonlinear.equivalent_plastic_strain.{increment_index}")
219}
220
221pub fn fea_nonlinear_contact_pressure_field_id(increment_index: usize) -> String {
222 format!("nonlinear.contact_pressure.{increment_index}")
223}
224
225pub fn fea_nonlinear_contact_gap_field_id(increment_index: usize) -> String {
226 format!("nonlinear.contact_gap.{increment_index}")
227}
228
229pub fn fea_nonlinear_load_factor_field_id(increment_index: usize) -> String {
230 format!("nonlinear.load_factor.{increment_index}")
231}
232
233pub fn fea_nonlinear_residual_norm_field_id(increment_index: usize) -> String {
234 format!("nonlinear.residual_norm.{increment_index}")
235}
236
237pub fn fea_thermo_mechanical_temperature_field_id(step_index: usize) -> String {
238 format!("thermo_mechanical.temperature.{step_index}")
239}
240
241pub fn fea_thermo_mechanical_thermal_strain_field_id(step_index: usize) -> String {
242 format!("thermo_mechanical.thermal_strain.{step_index}")
243}
244
245pub fn fea_thermo_mechanical_thermal_stress_field_id(step_index: usize) -> String {
246 format!("thermo_mechanical.thermal_stress.{step_index}")
247}
248
249pub fn fea_thermo_mechanical_displacement_field_id(step_index: usize) -> String {
250 format!("thermo_mechanical.displacement.{step_index}")
251}
252
253pub fn fea_thermo_mechanical_von_mises_field_id(step_index: usize) -> String {
254 format!("thermo_mechanical.von_mises.{step_index}")
255}
256
257pub fn fea_thermo_mechanical_coupling_residual_field_id(step_index: usize) -> String {
258 format!("thermo_mechanical.coupling_residual.{step_index}")
259}
260
261pub const FEA_FIELD_ELECTRO_THERMAL_ELECTRIC_POTENTIAL: &str = "electro_thermal.electric_potential";
262pub const FEA_FIELD_ELECTRO_THERMAL_ELECTRIC_FIELD: &str = "electro_thermal.electric_field";
263pub const FEA_FIELD_ELECTRO_THERMAL_CURRENT_DENSITY: &str = "electro_thermal.current_density";
264pub const FEA_FIELD_ELECTRO_THERMAL_JOULE_HEAT: &str = "electro_thermal.joule_heat";
265
266pub fn fea_electro_thermal_temperature_field_id(step_index: usize) -> String {
267 format!("electro_thermal.temperature.{step_index}")
268}
269
270pub fn fea_electro_thermal_thermal_residual_field_id(step_index: usize) -> String {
271 format!("electro_thermal.thermal_residual.{step_index}")
272}
273
274#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
275#[serde(rename_all = "snake_case")]
276pub enum ComputeBackend {
277 Cpu,
278 Gpu,
279}
280
281#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
282pub struct FeaRunResult {
283 pub backend: ComputeBackend,
284 pub solver_backend: String,
285 pub solver_device_apply_k_ratio: f64,
286 pub solver_method: String,
287 pub preconditioner: String,
288 pub solver_host_sync_count: u32,
289 pub diagnostics: Vec<crate::diagnostics::FeaDiagnostic>,
290 pub fields: Vec<AnalysisField>,
291}
292
293impl FeaRunResult {
294 pub fn field(&self, field_id: &str) -> Option<&AnalysisField> {
295 self.fields.iter().find(|field| field.field_id == field_id)
296 }
297
298 pub fn fields_are_empty(&self) -> bool {
299 self.fields.is_empty() || self.fields.iter().all(AnalysisField::is_empty)
300 }
301
302 pub fn field_ids(&self) -> Vec<String> {
303 self.fields
304 .iter()
305 .map(|field| field.field_id.clone())
306 .collect()
307 }
308}
309
310fn default_prep_coordinate_span_m() -> f64 {
311 1.0
312}
313
314fn default_prep_coordinate_secondary_span_m() -> f64 {
315 0.0
316}
317
318fn default_prep_coordinate_active_dimension_count() -> usize {
319 1
320}
321
322fn default_prep_coordinate_characteristic_length_m() -> f64 {
323 1.0
324}
325
326fn default_zero_usize() -> usize {
327 0
328}
329
330fn default_zero_f64() -> f64 {
331 0.0
332}
333
334fn default_reference_element_coordinates_m() -> [[f64; 3]; 3] {
335 [[0.0; 3]; 3]
336}
337
338fn default_element_topology_sample_edge_nodes() -> [[u32; 2]; 8] {
339 [[0; 2]; 8]
340}
341
342fn default_element_topology_sample_node_coordinates_m() -> [[f64; 3]; 8] {
343 [[0.0; 3]; 8]
344}
345
346fn default_element_topology_sample_element_edges() -> [[u32; 3]; 4] {
347 [[0; 3]; 4]
348}
349
350fn default_element_topology_sample_element_orientations() -> [[i8; 3]; 4] {
351 [[0; 3]; 4]
352}
353
354fn default_element_topology_sample_element_areas_m2() -> [f64; 4] {
355 [0.0; 4]
356}
357
358fn default_element_topology_node_coordinates_m() -> Vec<[f64; 3]> {
359 Vec::new()
360}
361
362fn default_element_topology_edge_nodes() -> Vec<[u32; 2]> {
363 Vec::new()
364}
365
366fn default_element_topology_element_edges() -> Vec<[u32; 3]> {
367 Vec::new()
368}
369
370fn default_element_topology_element_orientations() -> Vec<[i8; 3]> {
371 Vec::new()
372}
373
374fn default_element_topology_element_areas_m2() -> Vec<f64> {
375 Vec::new()
376}
377
378#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
379pub struct FeaPrepContext {
380 pub prepared_mesh_count: usize,
381 pub prepared_node_count: usize,
382 pub prepared_element_count: usize,
383 pub mapped_region_count: usize,
384 pub min_scaled_jacobian: f64,
385 pub mean_aspect_ratio: f64,
386 pub inverted_element_count: usize,
387 pub mapped_load_count: usize,
388 pub mapped_bc_count: usize,
389 pub layout_seed: u64,
390 pub topology_dof_multiplier: f64,
391 pub topology_bandwidth_estimate: u32,
392 pub mapped_region_participation_ratio: f64,
393 pub topology_surface_patch_ratio: f64,
394 pub topology_volume_core_ratio: f64,
395 pub topology_mixed_family_ratio: f64,
396 pub topology_region_span_mean: f64,
397 pub topology_region_block_count: usize,
398 pub topology_region_mesh_mean: f64,
399 pub topology_region_mesh_variance: f64,
400 pub topology_triangle_family_ratio: f64,
401 pub topology_quad_family_ratio: f64,
402 pub topology_tetrahedron_family_ratio: f64,
403 pub topology_hex_family_ratio: f64,
404 #[serde(default = "default_prep_coordinate_span_m")]
405 pub coordinate_span_x_m: f64,
406 #[serde(default = "default_prep_coordinate_secondary_span_m")]
407 pub coordinate_span_y_m: f64,
408 #[serde(default = "default_prep_coordinate_secondary_span_m")]
409 pub coordinate_span_z_m: f64,
410 #[serde(default = "default_prep_coordinate_active_dimension_count")]
411 pub coordinate_active_dimension_count: usize,
412 #[serde(default = "default_prep_coordinate_characteristic_length_m")]
413 pub coordinate_characteristic_length_m: f64,
414 #[serde(default = "default_zero_usize")]
415 pub element_geometry_node_count: usize,
416 #[serde(default = "default_zero_usize")]
417 pub element_geometry_edge_count: usize,
418 #[serde(default = "default_zero_f64")]
419 pub mean_element_edge_length_m: f64,
420 #[serde(default = "default_zero_f64")]
421 pub mean_element_area_m2: f64,
422 #[serde(default = "default_zero_f64")]
423 pub element_geometry_coverage_ratio: f64,
424 #[serde(default = "default_reference_element_coordinates_m")]
425 pub reference_element_coordinates_m: [[f64; 3]; 3],
426 #[serde(default = "default_zero_f64")]
427 pub reference_element_area_m2: f64,
428 #[serde(default = "default_zero_usize")]
429 pub element_topology_sample_element_count: usize,
430 #[serde(default = "default_zero_usize")]
431 pub element_topology_sample_edge_count: usize,
432 #[serde(default = "default_element_topology_sample_edge_nodes")]
433 pub element_topology_sample_edge_nodes: [[u32; 2]; 8],
434 #[serde(default = "default_element_topology_sample_node_coordinates_m")]
435 pub element_topology_sample_node_coordinates_m: [[f64; 3]; 8],
436 #[serde(default = "default_element_topology_sample_element_edges")]
437 pub element_topology_sample_element_edges: [[u32; 3]; 4],
438 #[serde(default = "default_element_topology_sample_element_orientations")]
439 pub element_topology_sample_element_orientations: [[i8; 3]; 4],
440 #[serde(default = "default_element_topology_sample_element_areas_m2")]
441 pub element_topology_sample_element_areas_m2: [f64; 4],
442 #[serde(default = "default_element_topology_node_coordinates_m")]
443 pub element_topology_node_coordinates_m: Vec<[f64; 3]>,
444 #[serde(default = "default_element_topology_edge_nodes")]
445 pub element_topology_edge_nodes: Vec<[u32; 2]>,
446 #[serde(default = "default_element_topology_element_edges")]
447 pub element_topology_element_edges: Vec<[u32; 3]>,
448 #[serde(default = "default_element_topology_element_orientations")]
449 pub element_topology_element_orientations: Vec<[i8; 3]>,
450 #[serde(default = "default_element_topology_element_areas_m2")]
451 pub element_topology_element_areas_m2: Vec<f64>,
452 pub calibration_profile_override: Option<FeaPrepCalibrationProfile>,
453}
454
455#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
456pub struct FeaThermoRegionTemperatureDelta {
457 pub region_id: String,
458 pub temperature_delta_k: f64,
459}
460
461#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
462pub struct FeaThermoTimeProfilePoint {
463 pub normalized_time: f64,
464 pub scale: f64,
465}
466
467#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
468#[serde(rename_all = "snake_case")]
469pub enum FeaThermoFieldInterpolationMode {
470 Linear,
471 Step,
472}
473
474#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
475pub struct FeaThermoFieldSource {
476 pub source_id: String,
477 pub revision: u32,
478 pub interpolation_mode: Option<FeaThermoFieldInterpolationMode>,
479 pub expected_region_ids: Vec<String>,
480}
481
482#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
483pub struct FeaThermoMechanicalContext {
484 pub enabled: bool,
485 pub reference_temperature_k: f64,
486 pub applied_temperature_delta_k: f64,
487 pub thermal_expansion_coefficient: f64,
488 pub field_source: Option<FeaThermoFieldSource>,
489 pub region_temperature_deltas: Vec<FeaThermoRegionTemperatureDelta>,
490 pub time_profile: Vec<FeaThermoTimeProfilePoint>,
491}
492
493#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
494pub struct FeaElectroRegionConductivityScale {
495 pub region_id: String,
496 pub conductivity_scale: f64,
497}
498
499#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
500pub struct FeaElectroTimeProfilePoint {
501 pub normalized_time: f64,
502 pub current_scale: f64,
503}
504
505#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
506pub struct FeaElectroThermalContext {
507 pub enabled: bool,
508 pub reference_temperature_k: f64,
509 pub applied_voltage_v: f64,
510 pub base_electrical_conductivity_s_per_m: f64,
511 pub resistive_heating_coefficient: f64,
512 pub region_conductivity_scales: Vec<FeaElectroRegionConductivityScale>,
513 pub time_profile: Vec<FeaElectroTimeProfilePoint>,
514}
515
516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
517pub struct FeaPlasticityConstitutiveContext {
518 pub enabled: bool,
519 pub yield_strain: f64,
520 pub hardening_modulus_ratio: f64,
521 pub saturation_exponent: f64,
522}
523
524#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
525pub struct FeaContactInterfaceContext {
526 pub enabled: bool,
527 pub penalty_stiffness_scale: f64,
528 pub max_penetration_ratio: f64,
529 pub friction_coefficient: f64,
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
533#[serde(rename_all = "snake_case")]
534pub enum FeaPrepCalibrationProfile {
535 Fast,
536 Balanced,
537 Conservative,
538}
539
540#[derive(Debug, Clone, PartialEq)]
541pub struct LinearStaticSolveOptions {
542 pub preconditioner_kind: SpdPreconditionerKind,
543 pub algebra_backend_kind: LinearAlgebraBackendKind,
544 pub prep_context: Option<FeaPrepContext>,
545 pub analysis_mesh_artifact_path: Option<String>,
546 pub analysis_mesh: Option<AnalysisMeshArtifact>,
547 pub require_analysis_mesh_for_solid: bool,
548 pub thermo_mechanical_context: Option<FeaThermoMechanicalContext>,
549 pub electro_thermal_context: Option<FeaElectroThermalContext>,
550}
551
552impl Default for LinearStaticSolveOptions {
553 fn default() -> Self {
554 Self {
555 preconditioner_kind: SpdPreconditionerKind::Jacobi,
556 algebra_backend_kind: LinearAlgebraBackendKind::CpuReference,
557 prep_context: None,
558 analysis_mesh_artifact_path: None,
559 analysis_mesh: None,
560 require_analysis_mesh_for_solid: true,
561 thermo_mechanical_context: None,
562 electro_thermal_context: None,
563 }
564 }
565}
566
567#[derive(Debug, Clone, PartialEq)]
568pub struct ModalSolveOptions {
569 pub mode_count: usize,
570 pub prep_context: Option<FeaPrepContext>,
571 pub thermo_mechanical_context: Option<FeaThermoMechanicalContext>,
572 pub electro_thermal_context: Option<FeaElectroThermalContext>,
573}
574
575impl Default for ModalSolveOptions {
576 fn default() -> Self {
577 Self {
578 mode_count: 3,
579 prep_context: None,
580 thermo_mechanical_context: None,
581 electro_thermal_context: None,
582 }
583 }
584}
585
586#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
587pub struct FeaModalRunResult {
588 pub run: FeaRunResult,
589 pub eigenvalues_hz: Vec<f64>,
590 pub mode_shapes: Vec<AnalysisField>,
591 pub residual_norms: Vec<f64>,
592}
593
594#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
595pub struct FeaTransientRunResult {
596 pub run: FeaRunResult,
597 pub time_points_s: Vec<f64>,
598 pub displacement_snapshots: Vec<AnalysisField>,
599 pub rotation_snapshots: Vec<AnalysisField>,
600 pub velocity_snapshots: Vec<AnalysisField>,
601 pub angular_velocity_snapshots: Vec<AnalysisField>,
602 pub acceleration_snapshots: Vec<AnalysisField>,
603 pub angular_acceleration_snapshots: Vec<AnalysisField>,
604 pub von_mises_snapshots: Vec<AnalysisField>,
605 pub kinetic_energy_snapshots: Vec<AnalysisField>,
606 pub strain_energy_snapshots: Vec<AnalysisField>,
607 pub residual_norm_snapshots: Vec<AnalysisField>,
608 pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
609 pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
610 pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
611 pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
612 pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
613 pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
614 pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
615 pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
616 pub residual_norms: Vec<f64>,
617}
618
619#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
620pub struct FeaThermalRunResult {
621 pub run: FeaRunResult,
622 pub time_points_s: Vec<f64>,
623 pub temperature_snapshots: Vec<AnalysisField>,
624 pub temperature_gradient_snapshots: Vec<AnalysisField>,
625 pub heat_flux_snapshots: Vec<AnalysisField>,
626 pub heat_source_snapshots: Vec<AnalysisField>,
627 pub boundary_heat_flux_snapshots: Vec<AnalysisField>,
628 pub residual_norms: Vec<f64>,
629 pub reference_temperature_k: f64,
630}
631
632#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
633pub struct FeaElectromagneticRunResult {
634 pub run: FeaRunResult,
635 pub reference_frequency_hz: f64,
636 pub applied_current_a: f64,
637 pub vector_potential_real_field: AnalysisField,
638 pub vector_potential_imag_field: AnalysisField,
639 pub magnetic_flux_density_real_field: AnalysisField,
640 pub magnetic_flux_density_imag_field: AnalysisField,
641 pub magnetic_flux_density_magnitude_field: AnalysisField,
642 pub magnetic_field_real_field: AnalysisField,
643 pub magnetic_field_imag_field: AnalysisField,
644 pub current_density_real_field: AnalysisField,
645 pub current_density_imag_field: AnalysisField,
646 pub electric_field_real_field: AnalysisField,
647 pub electric_field_imag_field: AnalysisField,
648 pub power_loss_density_field: AnalysisField,
649 pub energy_density_field: AnalysisField,
650 pub residual_real_field: AnalysisField,
651 pub residual_imag_field: AnalysisField,
652 pub electric_flux_density_real_field: AnalysisField,
653 pub electric_flux_density_imag_field: AnalysisField,
654 pub poynting_vector_real_field: AnalysisField,
655 pub poynting_vector_imag_field: AnalysisField,
656 pub max_residual_norm: f64,
657 pub solve_quality: f64,
658}
659
660#[derive(Debug, Clone, PartialEq)]
661pub struct ElectromagneticSolveOptions {
662 pub prep_context: Option<FeaPrepContext>,
663 pub residual_target: f64,
664 pub harmonic_tolerance: f64,
665 pub harmonic_max_iterations: usize,
666}
667
668impl Default for ElectromagneticSolveOptions {
669 fn default() -> Self {
670 Self {
671 prep_context: None,
672 residual_target: 1.0e-6,
673 harmonic_tolerance: 1.0e-7,
674 harmonic_max_iterations: 96,
675 }
676 }
677}
678
679#[derive(Debug, Clone, PartialEq)]
680pub struct ThermalSolveOptions {
681 pub step_count: usize,
682 pub time_step_s: f64,
683 pub residual_target: f64,
684 pub prep_context: Option<FeaPrepContext>,
685 pub thermo_mechanical_context: Option<FeaThermoMechanicalContext>,
686}
687
688impl Default for ThermalSolveOptions {
689 fn default() -> Self {
690 Self {
691 step_count: 10,
692 time_step_s: 1.0e-2,
693 residual_target: 1.0e-4,
694 prep_context: None,
695 thermo_mechanical_context: None,
696 }
697 }
698}
699
700#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
701pub struct FeaNonlinearRunResult {
702 pub run: FeaRunResult,
703 pub load_factors: Vec<f64>,
704 pub displacement_snapshots: Vec<AnalysisField>,
705 pub rotation_snapshots: Vec<AnalysisField>,
706 pub von_mises_snapshots: Vec<AnalysisField>,
707 pub plastic_strain_snapshots: Vec<AnalysisField>,
708 pub equivalent_plastic_strain_snapshots: Vec<AnalysisField>,
709 pub contact_pressure_snapshots: Vec<AnalysisField>,
710 pub contact_gap_snapshots: Vec<AnalysisField>,
711 pub load_factor_snapshots: Vec<AnalysisField>,
712 pub residual_norm_snapshots: Vec<AnalysisField>,
713 pub thermo_mechanical_temperature_snapshots: Vec<AnalysisField>,
714 pub thermo_mechanical_thermal_strain_snapshots: Vec<AnalysisField>,
715 pub thermo_mechanical_thermal_stress_snapshots: Vec<AnalysisField>,
716 pub thermo_mechanical_displacement_snapshots: Vec<AnalysisField>,
717 pub thermo_mechanical_von_mises_snapshots: Vec<AnalysisField>,
718 pub thermo_mechanical_coupling_residual_snapshots: Vec<AnalysisField>,
719 pub electro_thermal_temperature_snapshots: Vec<AnalysisField>,
720 pub electro_thermal_thermal_residual_snapshots: Vec<AnalysisField>,
721 pub residual_norms: Vec<f64>,
722 pub increment_norms: Vec<f64>,
723 pub iteration_counts: Vec<usize>,
724 pub failed_increments: usize,
725 pub line_search_backtracks: usize,
726 pub max_line_search_backtracks_per_increment: usize,
727 pub tangent_rebuild_count: usize,
728 pub iteration_spike_count: usize,
729 pub convergence_stall_count: usize,
730 pub backtrack_burst_count: usize,
731}
732
733#[derive(Debug, Error)]
734pub enum FeaRunError {
735 #[error("FEA_MODEL_INVALID: {0}")]
736 InvalidModel(String),
737 #[error("FEA_ASSEMBLY_FAILED: {0}")]
738 Assembly(String),
739 #[error("FEA_CANCELLED: execution cancelled by user")]
740 Cancelled,
741}