1use nalgebra::{SMatrix, SVector, Vector3};
11
12use super::{DofMask, DofRef, ElementForce, NodeView, ShellResponse, TangentSink, VectorSink};
13use crate::model::continuum::validate_triangle;
14use crate::model::{ElementLoad3, Node3Id, ShellSection, ShellVector, SPATIAL_NDF, SPATIAL_NDIM};
15
16type View<'a> = NodeView<'a, SPATIAL_NDIM, SPATIAL_NDF, Node3Id>;
17type Vec18 = SVector<f64, 18>;
18type Mat18 = SMatrix<f64, 18, 18>;
19type Bmat = SMatrix<f64, 8, 18>;
20
21const GAUSS: [([f64; 3], f64); 4] = [
23 ([1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], -9.0 / 16.0),
24 ([0.2, 0.6, 0.2], 25.0 / 48.0),
25 ([0.6, 0.2, 0.2], 25.0 / 48.0),
26 ([0.2, 0.2, 0.6], 25.0 / 48.0),
27];
28
29#[derive(Debug, Clone)]
30pub struct Shell3 {
31 pub nodes: [Node3Id; 3],
32 sections: [ShellSection; 4],
33 cache: Option<Cache>,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq)]
37struct Cache {
38 axes: [Vector3<f64>; 3],
40 points: [PointGeometry; 4],
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44struct PointGeometry {
45 b: Bmat,
47 b_force: Bmat,
49 dvol: f64,
51 n: [f64; 3],
53}
54
55type Frame = ([Vector3<f64>; 3], [[f64; 3]; 2]);
56
57fn frame(coords: &[[f64; 3]; 3]) -> Result<Frame, &'static str> {
58 let x: [Vector3<f64>; 3] = std::array::from_fn(|i| Vector3::from(coords[i]));
59 let mut v1 = x[1] - x[0];
60 let mut v2 = x[2] - x[0];
61 let n1 = v1.norm();
62 if !n1.is_finite() || n1 == 0.0 {
63 return Err("degenerate shell geometry");
64 }
65 v1 /= n1;
66 v2 -= v2.dot(&v1) * v1;
67 let n2 = v2.norm();
68 if !n2.is_finite() || n2 == 0.0 {
69 return Err("degenerate shell geometry");
70 }
71 v2 /= n2;
72 let v3 = v1.cross(&v2);
73 Ok((
74 [v1, v2, v3],
75 [
76 std::array::from_fn(|i| x[i].dot(&v1)),
77 std::array::from_fn(|i| x[i].dot(&v2)),
78 ],
79 ))
80}
81
82fn shape_bend(l: [f64; 3], x: &[[f64; 3]; 2], area: f64) -> [[f64; 9]; 4] {
85 let (x12, x23, x31) = (x[0][0] - x[0][1], x[0][1] - x[0][2], x[0][2] - x[0][0]);
86 let (y12, y23, y31) = (x[1][0] - x[1][1], x[1][1] - x[1][2], x[1][2] - x[1][0]);
87 let (l12, l23, l31) = (
88 x12 * x12 + y12 * y12,
89 x23 * x23 + y23 * y23,
90 x31 * x31 + y31 * y31,
91 );
92 let side = |xs: f64, ys: f64, len: f64| {
94 (
95 -xs / len,
96 0.25 * 3.0 * xs * ys / len,
97 0.25 * (xs * xs - 2.0 * ys * ys) / len,
98 -ys / len,
99 0.25 * (ys * ys - 2.0 * xs * xs) / len,
100 )
101 };
102 let (a4, b4, _c4, d4, _e4) = side(x23, y23, l23);
103 let (a5, b5, _c5, d5, _e5) = side(x31, y31, l31);
104 let (a6, b6, _c6, d6, _e6) = side(x12, y12, l12);
105 let (p4, p5, p6) = (6.0 * a4, 6.0 * a5, 6.0 * a6);
106 let (t4, t5, t6) = (6.0 * d4, 6.0 * d5, 6.0 * d6);
107 let (q4, q5, q6) = (4.0 * b4, 4.0 * b5, 4.0 * b6);
108 let (r4, r5, r6) = (
109 3.0 * y23 * y23 / l23,
110 3.0 * y31 * y31 / l31,
111 3.0 * y12 * y12 / l12,
112 );
113
114 let (l1, l2) = (l[1], l[2]);
115 let hx_xi = [
116 p6 * (1.0 - 2.0 * l1) + (p5 - p6) * l2,
117 q6 * (1.0 - 2.0 * l1) - (q5 + q6) * l2,
118 -4.0 + 6.0 * (l1 + l2) + r6 * (1.0 - 2.0 * l1) - l2 * (r5 + r6),
119 -p6 * (1.0 - 2.0 * l1) + l2 * (p4 + p6),
120 q6 * (1.0 - 2.0 * l1) - l2 * (q6 - q4),
121 -2.0 + 6.0 * l1 + r6 * (1.0 - 2.0 * l1) + l2 * (r4 - r6),
122 -l2 * (p5 + p4),
123 l2 * (q4 - q5),
124 -l2 * (r5 - r4),
125 ];
126 let hx_eta = [
127 -p5 * (1.0 - 2.0 * l2) - l1 * (p6 - p5),
128 q5 * (1.0 - 2.0 * l2) - l1 * (q5 + q6),
129 -4.0 + 6.0 * (l1 + l2) + r5 * (1.0 - 2.0 * l2) - l1 * (r5 + r6),
130 l1 * (p4 + p6),
131 l1 * (q4 - q6),
132 -l1 * (r6 - r4),
133 p5 * (1.0 - 2.0 * l2) - l1 * (p4 + p5),
134 q5 * (1.0 - 2.0 * l2) + l1 * (q4 - q5),
135 -2.0 + 6.0 * l2 + r5 * (1.0 - 2.0 * l2) + l1 * (r4 - r5),
136 ];
137 let hy_xi = [
138 t6 * (1.0 - 2.0 * l1) + l2 * (t5 - t6),
139 1.0 + r6 * (1.0 - 2.0 * l1) - l2 * (r5 + r6),
140 -q6 * (1.0 - 2.0 * l1) + l2 * (q5 + q6),
141 -t6 * (1.0 - 2.0 * l1) + l2 * (t4 + t6),
142 -1.0 + r6 * (1.0 - 2.0 * l1) + l2 * (r4 - r6),
143 -q6 * (1.0 - 2.0 * l1) - l2 * (q4 - q6),
144 -l2 * (t4 + t5),
145 l2 * (r4 - r5),
146 -l2 * (q4 - q5),
147 ];
148 let hy_eta = [
149 -t5 * (1.0 - 2.0 * l2) - l1 * (t6 - t5),
150 1.0 + r5 * (1.0 - 2.0 * l2) - l1 * (r5 + r6),
151 -q5 * (1.0 - 2.0 * l2) + l1 * (q5 + q6),
152 l1 * (t4 + t6),
153 l1 * (r4 - r6),
154 -l1 * (q4 - q6),
155 t5 * (1.0 - 2.0 * l2) - l1 * (t4 + t5),
156 -1.0 + r5 * (1.0 - 2.0 * l2) + l1 * (r4 - r5),
157 -q5 * (1.0 - 2.0 * l2) - l1 * (q4 - q5),
158 ];
159 let s = 1.0 / (2.0 * area);
160 let mut out = [[0.0; 9]; 4];
161 for i in 0..9 {
162 out[0][i] = (hx_xi[i] * y31 + hx_eta[i] * y12) * s;
163 out[1][i] = (hx_xi[i] * -x31 + hx_eta[i] * -x12) * s;
164 out[2][i] = (hy_xi[i] * y31 + hy_eta[i] * y12) * s;
165 out[3][i] = (hy_xi[i] * -x31 + hy_eta[i] * -x12) * s;
166 }
167 out
168}
169
170fn point_geometry(axes: &[Vector3<f64>; 3], x: &[[f64; 3]; 2]) -> [PointGeometry; 4] {
171 let area = 0.5
172 * (x[0][0] * x[1][1] + x[0][1] * x[1][2] + x[0][2] * x[1][0]
173 - x[0][0] * x[1][2]
174 - x[0][1] * x[1][0]
175 - x[0][2] * x[1][1]);
176 let b: [f64; 3] = [x[1][1] - x[1][2], x[1][2] - x[1][0], x[1][0] - x[1][1]];
177 let c: [f64; 3] = [-x[0][1] + x[0][2], -x[0][2] + x[0][0], -x[0][0] + x[0][1]];
178 let xsj = (x[0][1] - x[0][0]) * (x[1][2] - x[1][0]) - (x[1][1] - x[1][0]) * (x[0][2] - x[0][0]);
179
180 std::array::from_fn(|g| {
181 let (l, w) = GAUSS[g];
182 let bend = shape_bend(l, x, area);
183 let mut local = Bmat::zeros();
184 for j in 0..3 {
185 let (nx, ny) = (b[j] / (2.0 * area), c[j] / (2.0 * area));
186 let d = drill_row(j, l, &b, &c, area);
187 let col = 6 * j;
188 local[(0, col)] = nx;
190 local[(0, col + 5)] = d[0];
191 local[(1, col + 1)] = ny;
192 local[(1, col + 5)] = d[3];
193 local[(2, col)] = ny;
194 local[(2, col + 1)] = nx;
195 local[(2, col + 5)] = d[1] + d[2];
196 for k in 0..3 {
198 local[(3, col + 2 + k)] = -bend[0][3 * j + k];
199 local[(4, col + 2 + k)] = -bend[3][3 * j + k];
200 local[(5, col + 2 + k)] = -(bend[1][3 * j + k] + bend[2][3 * j + k]);
201 }
202 }
203 let mut t = SMatrix::<f64, 18, 18>::zeros();
205 for j in 0..3 {
206 for block in 0..2 {
207 for (r, axis) in axes.iter().enumerate() {
208 for k in 0..3 {
209 t[(6 * j + 3 * block + r, 6 * j + 3 * block + k)] = axis[k];
210 }
211 }
212 }
213 }
214 let b_global = local * t;
215 let mut b_force = b_global;
216 let mut local_force = local;
218 for j in 0..3 {
219 for r in 3..6 {
220 for k in 2..5 {
221 local_force[(r, 6 * j + k)] *= -1.0;
222 }
223 }
224 }
225 b_force.copy_from(&(local_force * t));
226 PointGeometry {
227 b: b_global,
228 b_force,
229 dvol: 0.5 * w * xsj,
230 n: l,
231 }
232 })
233}
234
235fn drill_row(i: usize, l: [f64; 3], b: &[f64; 3], c: &[f64; 3], area: f64) -> [f64; 4] {
237 let s = 1.0 / (4.0 * area);
238 match i {
239 0 => {
240 let pb = b[2] * l[1] - b[1] * l[2];
241 let pc = c[2] * l[1] - c[1] * l[2];
242 [b[0] * pb * s, c[0] * pb * s, b[0] * pc * s, c[0] * pc * s]
243 }
244 1 => {
245 let pb = b[0] * l[2] - b[2] * l[0];
246 let pc = c[0] * l[2] - c[2] * l[0];
247 [b[1] * pb * s, c[1] * pb * s, b[1] * pc * s, c[1] * pc * s]
248 }
249 _ => {
250 let pb = b[1] * l[0] - b[0] * l[1];
251 let pc = c[1] * l[0] - c[0] * l[1];
252 [b[2] * pb * s, c[2] * pb * s, b[2] * pc * s, c[2] * pc * s]
253 }
254 }
255}
256
257fn dofs(nodes: &[Node3Id; 3]) -> [DofRef<Node3Id>; 18] {
258 std::array::from_fn(|a| (nodes[a / 6], (a % 6) as u8))
259}
260
261impl Shell3 {
262 pub fn new(nodes: [Node3Id; 3], section: ShellSection) -> Self {
263 Shell3 {
264 nodes,
265 sections: std::array::from_fn(|_| section.clone()),
266 cache: None,
267 }
268 }
269
270 pub fn sections(&self) -> &[ShellSection; 4] {
272 &self.sections
273 }
274
275 fn coords(&self, view: &View<'_>) -> [[f64; 3]; 3] {
276 std::array::from_fn(|i| view.get(self.nodes[i]).coords)
277 }
278
279 fn compute_cache(&self, view: &View<'_>) -> Result<Cache, &'static str> {
280 let (axes, xl) = frame(&self.coords(view))?;
281 Ok(Cache {
282 axes,
283 points: point_geometry(&axes, &xl),
284 })
285 }
286
287 fn cache_or_compute(&self, view: &View<'_>) -> Cache {
288 self.cache.unwrap_or_else(|| {
289 self.compute_cache(view)
290 .expect("unvalidated Shell3 geometry")
291 })
292 }
293
294 pub(super) fn dof_mask(&self) -> DofMask {
295 DofMask::all(SPATIAL_NDF)
296 }
297
298 pub(super) fn validate(&self, view: &View<'_>) -> Result<(), &'static str> {
299 self.sections[0].validate().map_err(|e| e.message())?;
300 let (_, x) = frame(&self.coords(view))?;
301 validate_triangle(&[[x[0][0], x[1][0]], [x[0][1], x[1][1]], [x[0][2], x[1][2]]])
302 }
303
304 pub(super) fn prepare(&mut self, view: &View<'_>) {
305 self.cache = self.compute_cache(view).ok();
306 }
307
308 fn displacement(&self, view: &View<'_>) -> Vec18 {
309 Vec18::from_fn(|a, _| view.get(self.nodes[a / 6]).displacement[a % 6])
310 }
311
312 pub(super) fn assemble_tangent<S: TangentSink<Node3Id>>(&self, view: &View<'_>, sink: &mut S) {
313 let cache = self.cache_or_compute(view);
314 let u = self.displacement(view);
315 let mut k = Mat18::zeros();
316 let mut r = Vec18::zeros();
317 for (g, section) in cache.points.iter().zip(&self.sections) {
318 let (stress, d) = section.trial_resultant(&(g.b * u));
319 k += g.dvol * g.b_force.transpose() * d * g.b;
320 r += g.dvol * g.b_force.transpose() * stress;
321 }
322 sink.add(&dofs(&self.nodes), &k, &r);
323 }
324
325 fn nodal_weights(&self, cache: &Cache) -> [f64; 3] {
327 let mut w = [0.0; 3];
328 for g in &cache.points {
329 for (i, wi) in w.iter_mut().enumerate() {
330 *wi += g.n[i] * g.dvol;
331 }
332 }
333 w
334 }
335
336 pub(super) fn assemble_mass<S: VectorSink<Node3Id>>(&self, view: &View<'_>, sink: &mut S) {
337 let w = self.nodal_weights(&self.cache_or_compute(view));
338 let rho_h = self.sections[0].rho_h();
339 let m = Vec18::from_fn(|a, _| if a % 6 < 3 { rho_h * w[a / 6] } else { 0.0 });
340 sink.add(&dofs(&self.nodes), &m);
341 }
342
343 pub(super) fn load_vector(&self, view: &View<'_>, load: &ElementLoad3) -> Vec18 {
345 let cache = self.cache_or_compute(view);
346 let w = self.nodal_weights(&cache);
347 let rho_h = self.sections[0].rho_h();
348 let normal = cache.axes[2];
349 Vec18::from_fn(|a, _| {
350 let (node, slot) = (a / 6, a % 6);
351 if slot < 3 {
352 w[node] * (rho_h * load.body[slot] + load.pressure * normal[slot])
353 } else {
354 0.0
355 }
356 })
357 }
358
359 pub(super) fn assemble_load<S: VectorSink<Node3Id>>(
360 &self,
361 view: &View<'_>,
362 load: &ElementLoad3,
363 sink: &mut S,
364 ) {
365 sink.add(&dofs(&self.nodes), &self.load_vector(view, load));
366 }
367
368 pub(super) fn commit(&mut self, view: &View<'_>) {
369 let cache = self.cache_or_compute(view);
370 let u = self.displacement(view);
371 for (section, g) in self.sections.iter_mut().zip(&cache.points) {
372 let strain: ShellVector = g.b * u;
373 *section = section.commit(&strain);
374 }
375 }
376
377 pub(super) fn local_force(&self, view: &View<'_>) -> ElementForce {
379 let cache = self.cache_or_compute(view);
380 let u = self.displacement(view);
381 let mut r = Vec18::zeros();
382 for (g, section) in cache.points.iter().zip(&self.sections) {
383 let (stress, _) = section.trial_resultant(&(g.b * u));
384 r += g.dvol * g.b_force.transpose() * stress;
385 }
386 r.into()
387 }
388
389 pub(super) fn shell_responses(&self, view: &View<'_>) -> Vec<ShellResponse> {
391 let cache = self.cache_or_compute(view);
392 let u = self.displacement(view);
393 cache
394 .points
395 .iter()
396 .zip(&self.sections)
397 .map(|(g, section)| {
398 let strain = g.b * u;
399 ShellResponse {
400 strain: strain.into(),
401 resultant: section.trial_resultant(&strain).0.into(),
402 }
403 })
404 .collect()
405 }
406}