Skip to main content

runmat_analysis_fea/
fixtures.rs

1use runmat_analysis_core::{
2    AnalysisModel, AnalysisModelId, AnalysisStep, AnalysisStepKind, BeamElementModel,
3    BeamSectionModel, BoundaryCondition, BoundaryConditionKind, EvidenceConfidence, LoadCase,
4    LoadKind, MaterialAssignment, MaterialMechanicalModel, MaterialModel, MaterialThermalModel,
5    ReferenceFrame, ShellElementModel, ShellSectionModel, StructuralElement, StructuralElementKind,
6    StructuralModel, StructuralNode,
7};
8use runmat_geometry_core::UnitSystem;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum FixtureId {
12    CantileverLinearStatic,
13    CantileverLoadSweep,
14    CantileverLargeLoadSweep,
15    StructuralAxialBarReference,
16    StructuralBeamBendingReference,
17    StructuralBeamCantileverEndMomentReference,
18    StructuralBeamTorsionReference,
19    StructuralBeamForceAndMomentReference,
20    StructuralShellPlateMomentReference,
21    StructuralInvalidMomentWithoutRotationalDofs,
22    ModalLarge,
23    TransientLong,
24    TransientShock,
25    NonlinearAssembly,
26    NonlinearAssemblyStress,
27    NonlinearSofteningBenchmark,
28    NonlinearLoadPathMix,
29    NonlinearContactFrictionlessReference,
30    NonlinearContactFrictionlessReferenceComplex,
31    NonlinearPlasticHardeningReference,
32    NonlinearPlasticHardeningReferenceComplex,
33    ThermoMechanicalKickoff,
34    ThermoGradientBenign,
35    ThermoGradientPathological,
36    ThermoRampSmooth,
37    ThermoShockOscillatory,
38    ElectroThermalJouleBenign,
39    ElectroThermalJoulePathological,
40    MultiMaterialAssembly,
41    MissingMaterials,
42    MissingLoads,
43}
44
45pub fn fixture_model(fixture: FixtureId) -> AnalysisModel {
46    match fixture {
47        FixtureId::CantileverLinearStatic => cantilever_linear_static(),
48        FixtureId::CantileverLoadSweep => cantilever_load_sweep(),
49        FixtureId::CantileverLargeLoadSweep => cantilever_large_load_sweep(),
50        FixtureId::StructuralAxialBarReference => structural_axial_bar_reference(),
51        FixtureId::StructuralBeamBendingReference => structural_beam_bending_reference(),
52        FixtureId::StructuralBeamCantileverEndMomentReference => {
53            structural_beam_cantilever_end_moment_reference()
54        }
55        FixtureId::StructuralBeamTorsionReference => structural_beam_torsion_reference(),
56        FixtureId::StructuralBeamForceAndMomentReference => {
57            structural_beam_force_and_moment_reference()
58        }
59        FixtureId::StructuralShellPlateMomentReference => structural_shell_plate_moment_reference(),
60        FixtureId::StructuralInvalidMomentWithoutRotationalDofs => {
61            structural_invalid_moment_without_rotational_dofs()
62        }
63        FixtureId::ModalLarge => modal_large_fixture(),
64        FixtureId::TransientLong => transient_long_fixture(),
65        FixtureId::TransientShock => transient_shock_fixture(),
66        FixtureId::NonlinearAssembly => nonlinear_assembly_fixture(),
67        FixtureId::NonlinearAssemblyStress => nonlinear_assembly_stress_fixture(),
68        FixtureId::NonlinearSofteningBenchmark => nonlinear_softening_benchmark_fixture(),
69        FixtureId::NonlinearLoadPathMix => nonlinear_load_path_mix_fixture(),
70        FixtureId::NonlinearContactFrictionlessReference => {
71            nonlinear_contact_frictionless_reference_fixture()
72        }
73        FixtureId::NonlinearContactFrictionlessReferenceComplex => {
74            nonlinear_contact_frictionless_reference_complex_fixture()
75        }
76        FixtureId::NonlinearPlasticHardeningReference => {
77            nonlinear_plastic_hardening_reference_fixture()
78        }
79        FixtureId::NonlinearPlasticHardeningReferenceComplex => {
80            nonlinear_plastic_hardening_reference_complex_fixture()
81        }
82        FixtureId::ThermoMechanicalKickoff => thermo_mechanical_kickoff_fixture(),
83        FixtureId::ThermoGradientBenign => thermo_gradient_benign_fixture(),
84        FixtureId::ThermoGradientPathological => thermo_gradient_pathological_fixture(),
85        FixtureId::ThermoRampSmooth => thermo_ramp_smooth_fixture(),
86        FixtureId::ThermoShockOscillatory => thermo_shock_oscillatory_fixture(),
87        FixtureId::ElectroThermalJouleBenign => electro_thermal_joule_benign_fixture(),
88        FixtureId::ElectroThermalJoulePathological => electro_thermal_joule_pathological_fixture(),
89        FixtureId::MultiMaterialAssembly => multi_material_assembly(),
90        FixtureId::MissingMaterials => missing_materials(),
91        FixtureId::MissingLoads => missing_loads(),
92    }
93}
94
95fn cantilever_linear_static() -> AnalysisModel {
96    AnalysisModel {
97        model_id: AnalysisModelId("cantilever".to_string()),
98        geometry_id: "geo:cantilever".to_string(),
99        geometry_revision: 1,
100        units: UnitSystem::Meter,
101        frame: ReferenceFrame::Global,
102        materials: vec![MaterialModel {
103            material_id: "mat_steel".to_string(),
104            name: "Steel".to_string(),
105            mechanical: MaterialMechanicalModel {
106                youngs_modulus_pa: 200e9,
107                poisson_ratio: 0.3,
108                density_kg_per_m3: 7850.0,
109            },
110            thermal: MaterialThermalModel {
111                reference_temperature_k: 293.15,
112                modulus_temp_coeff_per_k: -2.5e-4,
113                ..MaterialThermalModel::default()
114            },
115            acoustic: None,
116            electrical: None,
117            plastic: None,
118        }],
119        material_assignments: vec![MaterialAssignment {
120            region_id: "tip".to_string(),
121            expected_material_id: "mat_steel".to_string(),
122            assigned_material_id: "mat_steel".to_string(),
123            confidence: EvidenceConfidence::Verified,
124        }],
125        structural: None,
126        thermo_mechanical: None,
127        electro_thermal: None,
128        electromagnetic: None,
129        cfd: None,
130        interfaces: Vec::new(),
131        boundary_conditions: vec![BoundaryCondition {
132            bc_id: "bc_root".to_string(),
133            region_id: "root".to_string(),
134            kind: BoundaryConditionKind::Fixed,
135        }],
136        loads: vec![LoadCase {
137            load_id: "tip_load".to_string(),
138            region_id: "tip".to_string(),
139            kind: LoadKind::Force {
140                fx: 0.0,
141                fy: -1000.0,
142                fz: 0.0,
143            },
144        }],
145        steps: vec![AnalysisStep {
146            step_id: "static_1".to_string(),
147            kind: AnalysisStepKind::Static,
148        }],
149    }
150}
151
152fn cantilever_load_sweep() -> AnalysisModel {
153    let mut model = cantilever_linear_static();
154    model.model_id = AnalysisModelId("cantilever_load_sweep".to_string());
155    model.loads = (0..128)
156        .map(|i| {
157            let scale = 1.0 + (i as f64) * 0.01;
158            LoadCase {
159                load_id: format!("tip_load_{i}"),
160                region_id: format!("tip_{i}"),
161                kind: LoadKind::Force {
162                    fx: 0.0,
163                    fy: -1000.0 * scale,
164                    fz: 0.0,
165                },
166            }
167        })
168        .collect();
169    model
170}
171
172fn cantilever_large_load_sweep() -> AnalysisModel {
173    let mut model = cantilever_linear_static();
174    model.model_id = AnalysisModelId("cantilever_large_load_sweep".to_string());
175    model.loads = (0..512)
176        .map(|i| {
177            let scale = 1.0 + (i as f64) * 0.005;
178            LoadCase {
179                load_id: format!("tip_load_large_{i}"),
180                region_id: format!("tip_large_{i}"),
181                kind: LoadKind::Force {
182                    fx: 0.0,
183                    fy: -800.0 * scale,
184                    fz: 0.0,
185                },
186            }
187        })
188        .collect();
189    model
190}
191
192fn structural_axial_bar_reference() -> AnalysisModel {
193    let mut model = cantilever_linear_static();
194    model.model_id = AnalysisModelId("structural_axial_bar_reference".to_string());
195    model.geometry_id = "geo:structural_axial_bar".to_string();
196    model.loads = (0..12)
197        .map(|i| LoadCase {
198            load_id: format!("axial_bar_tension_{i}"),
199            region_id: format!("bar_station_{i}"),
200            kind: LoadKind::Force {
201                fx: 2_000.0,
202                fy: 0.0,
203                fz: 0.0,
204            },
205        })
206        .collect();
207    model.material_assignments = vec![MaterialAssignment {
208        region_id: "bar_span".to_string(),
209        expected_material_id: "mat_steel".to_string(),
210        assigned_material_id: "mat_steel".to_string(),
211        confidence: EvidenceConfidence::Verified,
212    }];
213    model
214}
215
216fn structural_beam_bending_reference() -> AnalysisModel {
217    let mut model = cantilever_linear_static();
218    model.model_id = AnalysisModelId("structural_beam_bending_reference".to_string());
219    model.geometry_id = "geo:structural_beam_bending".to_string();
220    model.loads = (0..12)
221        .map(|i| {
222            let span_fraction = (i + 1) as f64 / 12.0;
223            LoadCase {
224                load_id: format!("beam_bending_station_{i}"),
225                region_id: format!("beam_station_{i}"),
226                kind: LoadKind::Force {
227                    fx: 0.0,
228                    fy: -500.0 * span_fraction,
229                    fz: 0.0,
230                },
231            }
232        })
233        .collect();
234    model.material_assignments = vec![MaterialAssignment {
235        region_id: "beam_span".to_string(),
236        expected_material_id: "mat_steel".to_string(),
237        assigned_material_id: "mat_steel".to_string(),
238        confidence: EvidenceConfidence::Verified,
239    }];
240    model
241}
242
243fn structural_beam_cantilever_end_moment_reference() -> AnalysisModel {
244    structural_beam_reference_model(
245        "structural_beam_cantilever_end_moment_reference",
246        "geo:structural_beam_end_moment",
247        vec![LoadCase {
248            load_id: "tip_moment_z".to_string(),
249            region_id: "node:2".to_string(),
250            kind: LoadKind::Moment {
251                mx: 0.0,
252                my: 0.0,
253                mz: 125.0,
254            },
255        }],
256    )
257}
258
259fn structural_beam_torsion_reference() -> AnalysisModel {
260    structural_beam_reference_model(
261        "structural_beam_torsion_reference",
262        "geo:structural_beam_torsion",
263        vec![LoadCase {
264            load_id: "tip_torque_x".to_string(),
265            region_id: "node:2".to_string(),
266            kind: LoadKind::Moment {
267                mx: 80.0,
268                my: 0.0,
269                mz: 0.0,
270            },
271        }],
272    )
273}
274
275fn structural_beam_force_and_moment_reference() -> AnalysisModel {
276    structural_beam_reference_model(
277        "structural_beam_force_and_moment_reference",
278        "geo:structural_beam_force_and_moment",
279        vec![
280            LoadCase {
281                load_id: "tip_force_y".to_string(),
282                region_id: "node:2".to_string(),
283                kind: LoadKind::Force {
284                    fx: 0.0,
285                    fy: 500.0,
286                    fz: 0.0,
287                },
288            },
289            LoadCase {
290                load_id: "tip_moment_z".to_string(),
291                region_id: "node:2".to_string(),
292                kind: LoadKind::Moment {
293                    mx: 0.0,
294                    my: 0.0,
295                    mz: 90.0,
296                },
297            },
298        ],
299    )
300}
301
302fn structural_invalid_moment_without_rotational_dofs() -> AnalysisModel {
303    let mut model = cantilever_linear_static();
304    model.model_id = AnalysisModelId("structural_invalid_moment_without_rotational_dofs".into());
305    model.geometry_id = "geo:structural_invalid_moment_without_rotational_dofs".to_string();
306    model.loads = vec![LoadCase {
307        load_id: "solid_tip_moment".to_string(),
308        region_id: "tip".to_string(),
309        kind: LoadKind::Moment {
310            mx: 0.0,
311            my: 0.0,
312            mz: 125.0,
313        },
314    }];
315    model
316}
317
318fn structural_shell_plate_moment_reference() -> AnalysisModel {
319    let mut model = cantilever_linear_static();
320    model.model_id = AnalysisModelId("structural_shell_plate_moment_reference".to_string());
321    model.geometry_id = "geo:structural_shell_plate_moment".to_string();
322    model.boundary_conditions = vec![BoundaryCondition {
323        bc_id: "fixed_corner".to_string(),
324        region_id: "node:1".to_string(),
325        kind: BoundaryConditionKind::Fixed,
326    }];
327    model.loads = vec![LoadCase {
328        load_id: "free_corner_moment_y".to_string(),
329        region_id: "node:3".to_string(),
330        kind: LoadKind::Moment {
331            mx: 0.0,
332            my: 10.0,
333            mz: 0.0,
334        },
335    }];
336    model.material_assignments = vec![MaterialAssignment {
337        region_id: "shell_panel".to_string(),
338        expected_material_id: "mat_steel".to_string(),
339        assigned_material_id: "mat_steel".to_string(),
340        confidence: EvidenceConfidence::Verified,
341    }];
342    model.structural = Some(StructuralModel {
343        nodes: vec![
344            StructuralNode {
345                node_id: 1,
346                coordinates_m: [0.0, 0.0, 0.0],
347            },
348            StructuralNode {
349                node_id: 2,
350                coordinates_m: [1.0, 0.0, 0.0],
351            },
352            StructuralNode {
353                node_id: 3,
354                coordinates_m: [0.0, 1.0, 0.0],
355            },
356        ],
357        elements: vec![StructuralElement {
358            element_id: "shell_1".to_string(),
359            region_id: "shell_panel".to_string(),
360            kind: StructuralElementKind::Shell(ShellElementModel {
361                node_ids: [1, 2, 3],
362                section_id: "panel_2mm".to_string(),
363                reference_axis: [1.0, 0.0, 0.0],
364            }),
365        }],
366        beam_sections: Vec::new(),
367        shell_sections: vec![ShellSectionModel {
368            section_id: "panel_2mm".to_string(),
369            thickness_m: 0.002,
370            shear_correction: 5.0 / 6.0,
371            drilling_stiffness_scale: 1.0e-4,
372        }],
373    });
374    model
375}
376
377fn structural_beam_reference_model(
378    model_id: &str,
379    geometry_id: &str,
380    loads: Vec<LoadCase>,
381) -> AnalysisModel {
382    let mut model = cantilever_linear_static();
383    model.model_id = AnalysisModelId(model_id.to_string());
384    model.geometry_id = geometry_id.to_string();
385    model.boundary_conditions = vec![BoundaryCondition {
386        bc_id: "fixed_root".to_string(),
387        region_id: "node:1".to_string(),
388        kind: BoundaryConditionKind::Fixed,
389    }];
390    model.loads = loads;
391    model.material_assignments = vec![MaterialAssignment {
392        region_id: "beam_span".to_string(),
393        expected_material_id: "mat_steel".to_string(),
394        assigned_material_id: "mat_steel".to_string(),
395        confidence: EvidenceConfidence::Verified,
396    }];
397    model.structural = Some(StructuralModel {
398        nodes: vec![
399            StructuralNode {
400                node_id: 1,
401                coordinates_m: [0.0, 0.0, 0.0],
402            },
403            StructuralNode {
404                node_id: 2,
405                coordinates_m: [1.0, 0.0, 0.0],
406            },
407        ],
408        elements: vec![StructuralElement {
409            element_id: "beam_1".to_string(),
410            region_id: "beam_span".to_string(),
411            kind: StructuralElementKind::Beam(BeamElementModel {
412                node_ids: [1, 2],
413                section_id: "rect".to_string(),
414                reference_axis: [0.0, 1.0, 0.0],
415            }),
416        }],
417        beam_sections: vec![BeamSectionModel {
418            section_id: "rect".to_string(),
419            area_m2: 2.0e-4,
420            iy_m4: 1.6e-9,
421            iz_m4: 6.4e-9,
422            torsion_j_m4: 2.4e-9,
423            outer_fiber_y_m: 0.01,
424            outer_fiber_z_m: 0.005,
425            torsion_outer_radius_m: 0.011_180_339_887_498_949,
426        }],
427        shell_sections: Vec::new(),
428    });
429    model
430}
431
432fn modal_large_fixture() -> AnalysisModel {
433    let mut model = cantilever_large_load_sweep();
434    model.model_id = AnalysisModelId("modal_large_fixture".to_string());
435    model.steps = vec![AnalysisStep {
436        step_id: "modal_large_1".to_string(),
437        kind: AnalysisStepKind::Modal,
438    }];
439    model
440}
441
442fn transient_long_fixture() -> AnalysisModel {
443    let mut model = cantilever_load_sweep();
444    model.model_id = AnalysisModelId("transient_long_fixture".to_string());
445    model.steps = vec![AnalysisStep {
446        step_id: "transient_long_1".to_string(),
447        kind: AnalysisStepKind::Transient,
448    }];
449    model
450}
451
452fn transient_shock_fixture() -> AnalysisModel {
453    let mut model = cantilever_large_load_sweep();
454    model.model_id = AnalysisModelId("transient_shock_fixture".to_string());
455    model.boundary_conditions.push(BoundaryCondition {
456        bc_id: "bc_mid_prescribed".to_string(),
457        region_id: "mid_support".to_string(),
458        kind: BoundaryConditionKind::PrescribedDisplacement,
459    });
460    model.loads = (0..256)
461        .map(|i| {
462            let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
463            let scale = 1.0 + (i as f64) * 0.01;
464            LoadCase {
465                load_id: format!("shock_load_{i}"),
466                region_id: format!("shock_region_{i}"),
467                kind: LoadKind::Force {
468                    fx: 50.0 * scale,
469                    fy: sign * -1500.0 * scale,
470                    fz: 0.0,
471                },
472            }
473        })
474        .collect();
475    model.steps = vec![AnalysisStep {
476        step_id: "transient_shock_1".to_string(),
477        kind: AnalysisStepKind::Transient,
478    }];
479    model
480}
481
482fn nonlinear_assembly_fixture() -> AnalysisModel {
483    let mut model = transient_shock_fixture();
484    model.model_id = AnalysisModelId("nonlinear_assembly_fixture".to_string());
485    model.steps = vec![AnalysisStep {
486        step_id: "nonlinear_assembly_1".to_string(),
487        kind: AnalysisStepKind::Nonlinear,
488    }];
489    model
490}
491
492fn nonlinear_assembly_stress_fixture() -> AnalysisModel {
493    let mut model = nonlinear_assembly_fixture();
494    model.model_id = AnalysisModelId("nonlinear_assembly_stress_fixture".to_string());
495    model.boundary_conditions.push(BoundaryCondition {
496        bc_id: "bc_stress_mid_support".to_string(),
497        region_id: "mid_support_stress".to_string(),
498        kind: BoundaryConditionKind::PrescribedDisplacement,
499    });
500    model.loads = (0..640)
501        .map(|i| {
502            let phase = if i % 3 == 0 { -1.0 } else { 1.0 };
503            let scale = 1.0 + (i as f64) * 0.003;
504            LoadCase {
505                load_id: format!("nonlinear_stress_load_{i}"),
506                region_id: format!("nonlinear_stress_region_{}", i % 48),
507                kind: LoadKind::Force {
508                    fx: 75.0 * scale,
509                    fy: phase * -1800.0 * scale,
510                    fz: 20.0 * scale,
511                },
512            }
513        })
514        .collect();
515    model.steps = vec![AnalysisStep {
516        step_id: "nonlinear_stress_1".to_string(),
517        kind: AnalysisStepKind::Nonlinear,
518    }];
519    model
520}
521
522fn nonlinear_softening_benchmark_fixture() -> AnalysisModel {
523    let mut model = nonlinear_assembly_stress_fixture();
524    model.model_id = AnalysisModelId("nonlinear_softening_benchmark_fixture".to_string());
525    model.materials = vec![
526        MaterialModel {
527            material_id: "mat_soft_polymer".to_string(),
528            name: "Soft Polymer".to_string(),
529            mechanical: MaterialMechanicalModel {
530                youngs_modulus_pa: 1.4e9,
531                poisson_ratio: 0.39,
532                density_kg_per_m3: 1200.0,
533            },
534            thermal: MaterialThermalModel {
535                reference_temperature_k: 293.15,
536                modulus_temp_coeff_per_k: -1.2e-3,
537                ..MaterialThermalModel::default()
538            },
539            acoustic: None,
540            electrical: None,
541            plastic: None,
542        },
543        MaterialModel {
544            material_id: "mat_aluminum".to_string(),
545            name: "Aluminum".to_string(),
546            mechanical: MaterialMechanicalModel {
547                youngs_modulus_pa: 69e9,
548                poisson_ratio: 0.33,
549                density_kg_per_m3: 2700.0,
550            },
551            thermal: MaterialThermalModel {
552                reference_temperature_k: 293.15,
553                modulus_temp_coeff_per_k: -3.6e-4,
554                ..MaterialThermalModel::default()
555            },
556            acoustic: None,
557            electrical: None,
558            plastic: None,
559        },
560    ];
561    model.material_assignments = vec![
562        MaterialAssignment {
563            region_id: "nonlinear_soft_region_root".to_string(),
564            expected_material_id: "mat_soft_polymer".to_string(),
565            assigned_material_id: "mat_soft_polymer".to_string(),
566            confidence: EvidenceConfidence::Verified,
567        },
568        MaterialAssignment {
569            region_id: "nonlinear_soft_region_tip".to_string(),
570            expected_material_id: "mat_aluminum".to_string(),
571            assigned_material_id: "mat_aluminum".to_string(),
572            confidence: EvidenceConfidence::Verified,
573        },
574    ];
575    model.loads = (0..720)
576        .map(|i| {
577            let phase = if i % 4 == 0 { -1.0 } else { 1.0 };
578            let drift = 1.0 + (i as f64) * 0.0025;
579            LoadCase {
580                load_id: format!("nonlinear_softening_load_{i}"),
581                region_id: format!("nonlinear_softening_region_{}", i % 64),
582                kind: LoadKind::Force {
583                    fx: 65.0 * drift,
584                    fy: phase * -2100.0 * drift,
585                    fz: 28.0 * drift,
586                },
587            }
588        })
589        .collect();
590    model.steps = vec![AnalysisStep {
591        step_id: "nonlinear_softening_1".to_string(),
592        kind: AnalysisStepKind::Nonlinear,
593    }];
594    model
595}
596
597fn nonlinear_load_path_mix_fixture() -> AnalysisModel {
598    let mut model = multi_material_assembly();
599    model.model_id = AnalysisModelId("nonlinear_load_path_mix_fixture".to_string());
600    model.boundary_conditions.push(BoundaryCondition {
601        bc_id: "bc_mix_path_support".to_string(),
602        region_id: "mix_path_support".to_string(),
603        kind: BoundaryConditionKind::PrescribedDisplacement,
604    });
605    model.loads = (0..480)
606        .map(|i| {
607            let scale = 1.0 + (i as f64) * 0.0035;
608            if i % 3 == 0 {
609                LoadCase {
610                    load_id: format!("mix_force_{i}"),
611                    region_id: format!("mix_force_region_{}", i % 36),
612                    kind: LoadKind::Force {
613                        fx: 90.0 * scale,
614                        fy: -1300.0 * scale,
615                        fz: 40.0 * scale,
616                    },
617                }
618            } else if i % 3 == 1 {
619                LoadCase {
620                    load_id: format!("mix_pressure_{i}"),
621                    region_id: format!("mix_pressure_region_{}", i % 24),
622                    kind: LoadKind::Pressure {
623                        magnitude_pa: 9.0e5 * scale,
624                    },
625                }
626            } else {
627                let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
628                LoadCase {
629                    load_id: format!("mix_body_{i}"),
630                    region_id: format!("mix_body_region_{}", i % 18),
631                    kind: LoadKind::BodyForce {
632                        gx: 0.35 * scale,
633                        gy: sign * -9.81 * scale,
634                        gz: 0.12 * scale,
635                    },
636                }
637            }
638        })
639        .collect();
640    model.steps = vec![AnalysisStep {
641        step_id: "nonlinear_mix_1".to_string(),
642        kind: AnalysisStepKind::Nonlinear,
643    }];
644    model
645}
646
647fn nonlinear_contact_frictionless_reference_fixture() -> AnalysisModel {
648    let mut model = nonlinear_load_path_mix_fixture();
649    model.model_id =
650        AnalysisModelId("nonlinear_contact_frictionless_reference_fixture".to_string());
651    model.material_assignments = vec![
652        MaterialAssignment {
653            region_id: "tip_steel".to_string(),
654            expected_material_id: "mat_steel".to_string(),
655            assigned_material_id: "mat_steel".to_string(),
656            confidence: EvidenceConfidence::Verified,
657        },
658        MaterialAssignment {
659            region_id: "mid_aluminum".to_string(),
660            expected_material_id: "mat_aluminum".to_string(),
661            assigned_material_id: "mat_aluminum".to_string(),
662            confidence: EvidenceConfidence::Verified,
663        },
664        MaterialAssignment {
665            region_id: "polymer_segment".to_string(),
666            expected_material_id: "mat_polymer".to_string(),
667            assigned_material_id: "mat_polymer".to_string(),
668            confidence: EvidenceConfidence::Verified,
669        },
670    ];
671    model
672}
673
674fn nonlinear_contact_frictionless_reference_complex_fixture() -> AnalysisModel {
675    let mut model = nonlinear_contact_frictionless_reference_fixture();
676    model.model_id =
677        AnalysisModelId("nonlinear_contact_frictionless_reference_complex_fixture".to_string());
678    model.loads = (0..560)
679        .map(|i| {
680            let scale = 1.0 + (i as f64) * 0.004;
681            if i % 2 == 0 {
682                LoadCase {
683                    load_id: format!("contact_ref_force_{i}"),
684                    region_id: format!("contact_ref_force_region_{}", i % 32),
685                    kind: LoadKind::Force {
686                        fx: 80.0 * scale,
687                        fy: -1400.0 * scale,
688                        fz: 32.0 * scale,
689                    },
690                }
691            } else {
692                LoadCase {
693                    load_id: format!("contact_ref_pressure_{i}"),
694                    region_id: format!("contact_ref_pressure_region_{}", i % 28),
695                    kind: LoadKind::Pressure {
696                        magnitude_pa: 1.05e6 * scale,
697                    },
698                }
699            }
700        })
701        .collect();
702    model
703}
704
705fn nonlinear_plastic_hardening_reference_fixture() -> AnalysisModel {
706    let mut model = nonlinear_load_path_mix_fixture();
707    model.model_id = AnalysisModelId("nonlinear_plastic_hardening_reference_fixture".to_string());
708    model.material_assignments = vec![
709        MaterialAssignment {
710            region_id: "tip_steel".to_string(),
711            expected_material_id: "mat_steel".to_string(),
712            assigned_material_id: "mat_steel".to_string(),
713            confidence: EvidenceConfidence::Verified,
714        },
715        MaterialAssignment {
716            region_id: "mid_aluminum".to_string(),
717            expected_material_id: "mat_aluminum".to_string(),
718            assigned_material_id: "mat_aluminum".to_string(),
719            confidence: EvidenceConfidence::Verified,
720        },
721        MaterialAssignment {
722            region_id: "polymer_segment".to_string(),
723            expected_material_id: "mat_polymer".to_string(),
724            assigned_material_id: "mat_polymer".to_string(),
725            confidence: EvidenceConfidence::Verified,
726        },
727    ];
728    model
729}
730
731fn nonlinear_plastic_hardening_reference_complex_fixture() -> AnalysisModel {
732    let mut model = nonlinear_plastic_hardening_reference_fixture();
733    model.model_id =
734        AnalysisModelId("nonlinear_plastic_hardening_reference_complex_fixture".to_string());
735    model.loads = (0..620)
736        .map(|i| {
737            let scale = 1.0 + (i as f64) * 0.0045;
738            if i % 3 == 0 {
739                LoadCase {
740                    load_id: format!("plastic_ref_force_{i}"),
741                    region_id: format!("plastic_ref_force_region_{}", i % 36),
742                    kind: LoadKind::Force {
743                        fx: 85.0 * scale,
744                        fy: -1450.0 * scale,
745                        fz: 28.0 * scale,
746                    },
747                }
748            } else if i % 3 == 1 {
749                LoadCase {
750                    load_id: format!("plastic_ref_pressure_{i}"),
751                    region_id: format!("plastic_ref_pressure_region_{}", i % 30),
752                    kind: LoadKind::Pressure {
753                        magnitude_pa: 1.1e6 * scale,
754                    },
755                }
756            } else {
757                LoadCase {
758                    load_id: format!("plastic_ref_body_{i}"),
759                    region_id: format!("plastic_ref_body_region_{}", i % 22),
760                    kind: LoadKind::BodyForce {
761                        gx: 0.28 * scale,
762                        gy: -9.81 * scale,
763                        gz: 0.09 * scale,
764                    },
765                }
766            }
767        })
768        .collect();
769    model
770}
771
772fn thermo_mechanical_kickoff_fixture() -> AnalysisModel {
773    let mut model = multi_material_assembly();
774    model.model_id = AnalysisModelId("thermo_mechanical_kickoff_fixture".to_string());
775    model.loads = (0..240)
776        .map(|i| {
777            let scale = 1.0 + (i as f64) * 0.004;
778            LoadCase {
779                load_id: format!("thermo_mech_force_{i}"),
780                region_id: format!("thermo_mech_region_{}", i % 24),
781                kind: LoadKind::Force {
782                    fx: 35.0 * scale,
783                    fy: -900.0 * scale,
784                    fz: 12.0 * scale,
785                },
786            }
787        })
788        .collect();
789    model.steps = vec![AnalysisStep {
790        step_id: "thermo_mech_transient_1".to_string(),
791        kind: AnalysisStepKind::Transient,
792    }];
793    model
794}
795
796fn thermo_gradient_benign_fixture() -> AnalysisModel {
797    let mut model = multi_material_assembly();
798    model.model_id = AnalysisModelId("thermo_gradient_benign_fixture".to_string());
799    model.material_assignments = vec![
800        MaterialAssignment {
801            region_id: "tip_steel".to_string(),
802            expected_material_id: "mat_steel".to_string(),
803            assigned_material_id: "mat_steel".to_string(),
804            confidence: EvidenceConfidence::Verified,
805        },
806        MaterialAssignment {
807            region_id: "mid_aluminum".to_string(),
808            expected_material_id: "mat_aluminum".to_string(),
809            assigned_material_id: "mat_aluminum".to_string(),
810            confidence: EvidenceConfidence::Verified,
811        },
812        MaterialAssignment {
813            region_id: "polymer_segment".to_string(),
814            expected_material_id: "mat_polymer".to_string(),
815            assigned_material_id: "mat_polymer".to_string(),
816            confidence: EvidenceConfidence::Probable,
817        },
818    ];
819    model.loads = (0..260)
820        .map(|i| {
821            let scale = 1.0 + (i as f64) * 0.003;
822            LoadCase {
823                load_id: format!("thermo_grad_benign_load_{i}"),
824                region_id: format!("thermo_grad_benign_region_{}", i % 28),
825                kind: LoadKind::Force {
826                    fx: 30.0 * scale,
827                    fy: -850.0 * scale,
828                    fz: 14.0 * scale,
829                },
830            }
831        })
832        .collect();
833    model.steps = vec![AnalysisStep {
834        step_id: "thermo_grad_benign_transient_1".to_string(),
835        kind: AnalysisStepKind::Transient,
836    }];
837    model
838}
839
840fn thermo_gradient_pathological_fixture() -> AnalysisModel {
841    let mut model = multi_material_assembly();
842    model.model_id = AnalysisModelId("thermo_gradient_pathological_fixture".to_string());
843    model.material_assignments = vec![
844        MaterialAssignment {
845            region_id: "tip_steel".to_string(),
846            expected_material_id: "mat_steel".to_string(),
847            assigned_material_id: "mat_polymer".to_string(),
848            confidence: EvidenceConfidence::Verified,
849        },
850        MaterialAssignment {
851            region_id: "mid_aluminum".to_string(),
852            expected_material_id: "mat_aluminum".to_string(),
853            assigned_material_id: "mat_polymer".to_string(),
854            confidence: EvidenceConfidence::Verified,
855        },
856        MaterialAssignment {
857            region_id: "polymer_segment".to_string(),
858            expected_material_id: "mat_polymer".to_string(),
859            assigned_material_id: "mat_steel".to_string(),
860            confidence: EvidenceConfidence::Inferred,
861        },
862    ];
863    model.loads = (0..320)
864        .map(|i| {
865            let scale = 1.0 + (i as f64) * 0.0038;
866            LoadCase {
867                load_id: format!("thermo_grad_pathological_load_{i}"),
868                region_id: format!("thermo_grad_pathological_region_{}", i % 32),
869                kind: LoadKind::Force {
870                    fx: 52.0 * scale,
871                    fy: -1150.0 * scale,
872                    fz: 26.0 * scale,
873                },
874            }
875        })
876        .collect();
877    model.steps = vec![AnalysisStep {
878        step_id: "thermo_grad_pathological_transient_1".to_string(),
879        kind: AnalysisStepKind::Transient,
880    }];
881    model
882}
883
884fn thermo_ramp_smooth_fixture() -> AnalysisModel {
885    let mut model = thermo_gradient_benign_fixture();
886    model.model_id = AnalysisModelId("thermo_ramp_smooth_fixture".to_string());
887    model.loads = (0..280)
888        .map(|i| {
889            let scale = 0.6 + (i as f64) * 0.0025;
890            LoadCase {
891                load_id: format!("thermo_ramp_smooth_load_{i}"),
892                region_id: format!("thermo_ramp_smooth_region_{}", i % 30),
893                kind: LoadKind::Force {
894                    fx: 24.0 * scale,
895                    fy: -760.0 * scale,
896                    fz: 10.0 * scale,
897                },
898            }
899        })
900        .collect();
901    model
902}
903
904fn thermo_shock_oscillatory_fixture() -> AnalysisModel {
905    let mut model = thermo_gradient_pathological_fixture();
906    model.model_id = AnalysisModelId("thermo_shock_oscillatory_fixture".to_string());
907    model.loads = (0..360)
908        .map(|i| {
909            let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
910            let scale = 1.0 + (i as f64) * 0.004;
911            LoadCase {
912                load_id: format!("thermo_shock_osc_load_{i}"),
913                region_id: format!("thermo_shock_osc_region_{}", i % 36),
914                kind: LoadKind::Force {
915                    fx: sign * 48.0 * scale,
916                    fy: sign * -1320.0 * scale,
917                    fz: sign * 28.0 * scale,
918                },
919            }
920        })
921        .collect();
922    model
923}
924
925fn electro_thermal_joule_benign_fixture() -> AnalysisModel {
926    let mut model = transient_long_fixture();
927    model.model_id = AnalysisModelId("electro_thermal_joule_benign_fixture".to_string());
928    model
929}
930
931fn electro_thermal_joule_pathological_fixture() -> AnalysisModel {
932    let mut model = transient_shock_fixture();
933    model.model_id = AnalysisModelId("electro_thermal_joule_pathological_fixture".to_string());
934    model
935}
936
937fn multi_material_assembly() -> AnalysisModel {
938    let mut model = cantilever_linear_static();
939    model.model_id = AnalysisModelId("multi_material_assembly".to_string());
940    model.materials = vec![
941        MaterialModel {
942            material_id: "mat_steel".to_string(),
943            name: "Steel".to_string(),
944            mechanical: MaterialMechanicalModel {
945                youngs_modulus_pa: 200e9,
946                poisson_ratio: 0.3,
947                density_kg_per_m3: 7850.0,
948            },
949            thermal: MaterialThermalModel {
950                reference_temperature_k: 293.15,
951                modulus_temp_coeff_per_k: -2.5e-4,
952                ..MaterialThermalModel::default()
953            },
954            acoustic: None,
955            electrical: None,
956            plastic: None,
957        },
958        MaterialModel {
959            material_id: "mat_aluminum".to_string(),
960            name: "Aluminum".to_string(),
961            mechanical: MaterialMechanicalModel {
962                youngs_modulus_pa: 69e9,
963                poisson_ratio: 0.33,
964                density_kg_per_m3: 2700.0,
965            },
966            thermal: MaterialThermalModel {
967                reference_temperature_k: 293.15,
968                modulus_temp_coeff_per_k: -3.6e-4,
969                ..MaterialThermalModel::default()
970            },
971            acoustic: None,
972            electrical: None,
973            plastic: None,
974        },
975        MaterialModel {
976            material_id: "mat_polymer".to_string(),
977            name: "Polymer".to_string(),
978            mechanical: MaterialMechanicalModel {
979                youngs_modulus_pa: 3.2e9,
980                poisson_ratio: 0.37,
981                density_kg_per_m3: 1150.0,
982            },
983            thermal: MaterialThermalModel {
984                reference_temperature_k: 293.15,
985                modulus_temp_coeff_per_k: -8.0e-4,
986                ..MaterialThermalModel::default()
987            },
988            acoustic: None,
989            electrical: None,
990            plastic: None,
991        },
992    ];
993
994    model.boundary_conditions = vec![
995        BoundaryCondition {
996            bc_id: "bc_root".to_string(),
997            region_id: "root".to_string(),
998            kind: BoundaryConditionKind::Fixed,
999        },
1000        BoundaryCondition {
1001            bc_id: "bc_interface".to_string(),
1002            region_id: "interface".to_string(),
1003            kind: BoundaryConditionKind::PrescribedDisplacement,
1004        },
1005    ];
1006
1007    model.loads = vec![
1008        LoadCase {
1009            load_id: "load_tip_force".to_string(),
1010            region_id: "tip_steel".to_string(),
1011            kind: LoadKind::Force {
1012                fx: 0.0,
1013                fy: -1200.0,
1014                fz: 0.0,
1015            },
1016        },
1017        LoadCase {
1018            load_id: "load_mid_pressure".to_string(),
1019            region_id: "mid_aluminum".to_string(),
1020            kind: LoadKind::Pressure {
1021                magnitude_pa: 8.5e5,
1022            },
1023        },
1024        LoadCase {
1025            load_id: "load_body".to_string(),
1026            region_id: "polymer_segment".to_string(),
1027            kind: LoadKind::BodyForce {
1028                gx: 0.0,
1029                gy: -9.81,
1030                gz: 0.0,
1031            },
1032        },
1033    ];
1034
1035    model.material_assignments = vec![
1036        MaterialAssignment {
1037            region_id: "tip_steel".to_string(),
1038            expected_material_id: "mat_steel".to_string(),
1039            assigned_material_id: "mat_steel".to_string(),
1040            confidence: EvidenceConfidence::Verified,
1041        },
1042        MaterialAssignment {
1043            region_id: "mid_aluminum".to_string(),
1044            expected_material_id: "mat_aluminum".to_string(),
1045            assigned_material_id: "mat_polymer".to_string(),
1046            confidence: EvidenceConfidence::Inferred,
1047        },
1048        MaterialAssignment {
1049            region_id: "polymer_segment".to_string(),
1050            expected_material_id: "mat_polymer".to_string(),
1051            assigned_material_id: "mat_polymer".to_string(),
1052            confidence: EvidenceConfidence::Probable,
1053        },
1054    ];
1055
1056    model
1057}
1058
1059fn missing_materials() -> AnalysisModel {
1060    let mut model = cantilever_linear_static();
1061    model.model_id = AnalysisModelId("missing_materials".to_string());
1062    model.materials.clear();
1063    model
1064}
1065
1066fn missing_loads() -> AnalysisModel {
1067    let mut model = cantilever_linear_static();
1068    model.model_id = AnalysisModelId("missing_loads".to_string());
1069    model.loads.clear();
1070    model
1071}