Skip to main content

carapace_wasm/input_v1/
materials.rs

1//! Wire-format material arena and its resolution into `core::Material`
2//! values. Composite
3//! materials reference other arena entries by index rather than duplicating
4//! them inline, mirroring `core`'s own arena-of-indices philosophy.
5
6use carapace_core::model::{Material, Pinching4DmgCyc};
7use serde::{Deserialize, Serialize};
8
9use super::error::DecodeError;
10
11/// Wire-format mirror of `core::Pinching4DmgCyc` (which carries no `serde`
12/// derives of its own — `core` stays free of any wasm/serde awareness, see
13/// `decode.rs`'s module doc comment).
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, tsify::Tsify)]
15#[serde(rename_all = "camelCase")]
16pub enum Pinching4DmgCycSpec {
17    EnergyBased,
18    CycleBased,
19}
20
21/// One arena entry. Composite variants reference other entries by their
22/// `u32` index into the same arena, resolved by [`resolve_materials`].
23#[derive(Debug, Clone, Serialize, Deserialize, tsify::Tsify)]
24#[serde(
25    tag = "kind",
26    rename_all = "camelCase",
27    rename_all_fields = "camelCase"
28)]
29pub enum MaterialSpec {
30    Elastic {
31        e: f64,
32    },
33    ElasticPp {
34        e: f64,
35        eyp: f64,
36    },
37    Gap {
38        e: f64,
39        gap: f64,
40    },
41    Ent {
42        e: f64,
43    },
44    Steel01 {
45        fy: f64,
46        e0: f64,
47        b: f64,
48        a1: f64,
49        a2: f64,
50        a3: f64,
51        a4: f64,
52    },
53    Concrete01 {
54        fpc: f64,
55        epsc0: f64,
56        fpcu: f64,
57        epscu: f64,
58    },
59    #[allow(clippy::too_many_arguments)]
60    Steel02 {
61        fy: f64,
62        e0: f64,
63        b: f64,
64        r0: f64,
65        cr1: f64,
66        cr2: f64,
67        a1: f64,
68        a2: f64,
69        a3: f64,
70        a4: f64,
71    },
72    Concrete02 {
73        fc: f64,
74        epsc0: f64,
75        fcu: f64,
76        epscu: f64,
77        rat: f64,
78        ft: f64,
79        ets: f64,
80    },
81    /// Each child arena index paired with its parallel factor.
82    Parallel {
83        children: Vec<(u32, f64)>,
84    },
85    /// Child arena indices, combined with `core`'s default series
86    /// tolerance/iteration cap (`Material::series`'s `tol=1e-10,
87    /// max_iter=30`) — not yet wire-configurable.
88    Series {
89        children: Vec<u32>,
90    },
91    MinMax {
92        inner: u32,
93        min_strain: f64,
94        max_strain: f64,
95    },
96    /// Trilinear moment-rotation envelope with pinching/stiffness/strength
97    /// degradation — see `core::Material::hysteretic`'s doc comment. Field
98    /// order matches the constructor's positional parameters.
99    #[allow(clippy::too_many_arguments)]
100    Hysteretic {
101        mom1p: f64,
102        rot1p: f64,
103        mom2p: f64,
104        rot2p: f64,
105        mom3p: f64,
106        rot3p: f64,
107        mom1n: f64,
108        rot1n: f64,
109        mom2n: f64,
110        rot2n: f64,
111        mom3n: f64,
112        rot3n: f64,
113        pinch_x: f64,
114        pinch_y: f64,
115        damfc1: f64,
116        damfc2: f64,
117        beta: f64,
118    },
119    /// Quadrilinear backbone with pinching and cyclic damage — see
120    /// `core::Material::pinching4`'s doc comment. Field order matches the
121    /// constructor's positional parameters.
122    #[allow(clippy::too_many_arguments)]
123    Pinching4 {
124        stress1p: f64,
125        strain1p: f64,
126        stress2p: f64,
127        strain2p: f64,
128        stress3p: f64,
129        strain3p: f64,
130        stress4p: f64,
131        strain4p: f64,
132        stress1n: f64,
133        strain1n: f64,
134        stress2n: f64,
135        strain2n: f64,
136        stress3n: f64,
137        strain3n: f64,
138        stress4n: f64,
139        strain4n: f64,
140        r_disp_p: f64,
141        r_force_p: f64,
142        u_force_p: f64,
143        r_disp_n: f64,
144        r_force_n: f64,
145        u_force_n: f64,
146        gamma_k_params: [f64; 4],
147        gamma_k_limit: f64,
148        gamma_d_params: [f64; 4],
149        gamma_d_limit: f64,
150        gamma_f_params: [f64; 4],
151        gamma_f_limit: f64,
152        gamma_e: f64,
153        dmg_cyc: Pinching4DmgCycSpec,
154    },
155}
156
157/// Resolves every entry of a wire-format material arena into a `core`
158/// `Material`, in arena order. Composite entries may reference any other
159/// arena index (not just earlier ones); a reference cycle is rejected
160/// explicitly rather than overflowing the call stack.
161pub fn resolve_materials(arena: &[MaterialSpec]) -> Result<Vec<Material>, DecodeError> {
162    let mut resolved: Vec<Option<Material>> = vec![None; arena.len()];
163    for index in 0..arena.len() {
164        resolve_one(arena, index, &mut resolved, &mut Vec::new())?;
165    }
166    Ok(resolved
167        .into_iter()
168        .map(|m| m.expect("every arena index visited above"))
169        .collect())
170}
171
172fn resolve_one(
173    arena: &[MaterialSpec],
174    index: usize,
175    resolved: &mut [Option<Material>],
176    stack: &mut Vec<usize>,
177) -> Result<Material, DecodeError> {
178    if let Some(material) = &resolved[index] {
179        return Ok(material.clone());
180    }
181    if stack.contains(&index) {
182        return Err(DecodeError::CyclicMaterialReference {
183            index: index as u32,
184        });
185    }
186    let spec = arena.get(index).ok_or(DecodeError::UnknownMaterialIndex {
187        table: "materials",
188        row: index as u32,
189    })?;
190
191    stack.push(index);
192    let material = match spec {
193        MaterialSpec::Elastic { e } => Material::Elastic { e: *e },
194        MaterialSpec::ElasticPp { e, eyp } => Material::elastic_pp(*e, *eyp),
195        MaterialSpec::Gap { e, gap } => Material::Gap { e: *e, gap: *gap },
196        MaterialSpec::Ent { e } => Material::Ent { e: *e },
197        MaterialSpec::Steel01 {
198            fy,
199            e0,
200            b,
201            a1,
202            a2,
203            a3,
204            a4,
205        } => Material::steel01(*fy, *e0, *b, *a1, *a2, *a3, *a4),
206        MaterialSpec::Concrete01 {
207            fpc,
208            epsc0,
209            fpcu,
210            epscu,
211        } => Material::concrete01(*fpc, *epsc0, *fpcu, *epscu),
212        MaterialSpec::Steel02 {
213            fy,
214            e0,
215            b,
216            r0,
217            cr1,
218            cr2,
219            a1,
220            a2,
221            a3,
222            a4,
223        } => Material::steel02(*fy, *e0, *b, *r0, *cr1, *cr2, *a1, *a2, *a3, *a4),
224        MaterialSpec::Concrete02 {
225            fc,
226            epsc0,
227            fcu,
228            epscu,
229            rat,
230            ft,
231            ets,
232        } => Material::concrete02(*fc, *epsc0, *fcu, *epscu, *rat, *ft, *ets),
233        MaterialSpec::Parallel { children } => {
234            let mut resolved_children = Vec::with_capacity(children.len());
235            for (child_index, factor) in children {
236                let child = resolve_one(arena, *child_index as usize, resolved, stack)?;
237                resolved_children.push((child, *factor));
238            }
239            Material::Parallel(resolved_children)
240        }
241        MaterialSpec::Series { children } => {
242            let resolved_children = children
243                .iter()
244                .map(|child_index| resolve_one(arena, *child_index as usize, resolved, stack))
245                .collect::<Result<Vec<_>, _>>()?;
246            Material::series(resolved_children)
247        }
248        MaterialSpec::MinMax {
249            inner,
250            min_strain,
251            max_strain,
252        } => {
253            let inner = resolve_one(arena, *inner as usize, resolved, stack)?;
254            Material::min_max(inner, *min_strain, *max_strain)
255        }
256        MaterialSpec::Hysteretic {
257            mom1p,
258            rot1p,
259            mom2p,
260            rot2p,
261            mom3p,
262            rot3p,
263            mom1n,
264            rot1n,
265            mom2n,
266            rot2n,
267            mom3n,
268            rot3n,
269            pinch_x,
270            pinch_y,
271            damfc1,
272            damfc2,
273            beta,
274        } => Material::hysteretic(
275            *mom1p, *rot1p, *mom2p, *rot2p, *mom3p, *rot3p, *mom1n, *rot1n, *mom2n, *rot2n, *mom3n,
276            *rot3n, *pinch_x, *pinch_y, *damfc1, *damfc2, *beta,
277        ),
278        MaterialSpec::Pinching4 {
279            stress1p,
280            strain1p,
281            stress2p,
282            strain2p,
283            stress3p,
284            strain3p,
285            stress4p,
286            strain4p,
287            stress1n,
288            strain1n,
289            stress2n,
290            strain2n,
291            stress3n,
292            strain3n,
293            stress4n,
294            strain4n,
295            r_disp_p,
296            r_force_p,
297            u_force_p,
298            r_disp_n,
299            r_force_n,
300            u_force_n,
301            gamma_k_params,
302            gamma_k_limit,
303            gamma_d_params,
304            gamma_d_limit,
305            gamma_f_params,
306            gamma_f_limit,
307            gamma_e,
308            dmg_cyc,
309        } => Material::pinching4(
310            *stress1p,
311            *strain1p,
312            *stress2p,
313            *strain2p,
314            *stress3p,
315            *strain3p,
316            *stress4p,
317            *strain4p,
318            *stress1n,
319            *strain1n,
320            *stress2n,
321            *strain2n,
322            *stress3n,
323            *strain3n,
324            *stress4n,
325            *strain4n,
326            *r_disp_p,
327            *r_force_p,
328            *u_force_p,
329            *r_disp_n,
330            *r_force_n,
331            *u_force_n,
332            *gamma_k_params,
333            *gamma_k_limit,
334            *gamma_d_params,
335            *gamma_d_limit,
336            *gamma_f_params,
337            *gamma_f_limit,
338            *gamma_e,
339            match dmg_cyc {
340                Pinching4DmgCycSpec::EnergyBased => Pinching4DmgCyc::EnergyBased,
341                Pinching4DmgCycSpec::CycleBased => Pinching4DmgCyc::CycleBased,
342            },
343        ),
344    };
345    stack.pop();
346
347    resolved[index] = Some(material.clone());
348    Ok(material)
349}