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}