conspire/fem/block/element/linear/triangle/
mod.rs

1#[cfg(test)]
2mod test;
3
4use super::*;
5use crate::{
6    constitutive::{Constitutive, Parameters},
7    math::{IDENTITY, tensor_rank_1, tensor_rank_1_list, tensor_rank_1_list_2d},
8    mechanics::Scalar,
9};
10use std::array::from_fn;
11
12const G: usize = 1;
13const M: usize = 2;
14const N: usize = 3;
15const P: usize = 1;
16
17#[cfg(test)]
18const Q: usize = 3;
19
20pub type Triangle<C> = SurfaceElement<C, G, N, P>;
21
22impl<C, Y> SurfaceFiniteElement<C, G, N, P, Y> for Triangle<C>
23where
24    C: Constitutive<Y>,
25    Y: Parameters,
26{
27    fn new(
28        constitutive_model_parameters: Y,
29        reference_nodal_coordinates: ReferenceNodalCoordinates<N>,
30        thickness: &Scalar,
31    ) -> Self {
32        let integration_weights = Self::bases(&reference_nodal_coordinates)
33            .iter()
34            .map(|reference_basis| {
35                reference_basis[0].cross(&reference_basis[1]).norm()
36                    * Self::integration_weight()
37                    * thickness
38            })
39            .collect();
40        let reference_dual_bases = Self::dual_bases(&reference_nodal_coordinates);
41        let gradient_vectors = Self::standard_gradient_operators()
42            .iter()
43            .zip(reference_dual_bases.iter())
44            .map(|(standard_gradient_operator, reference_dual_basis)| {
45                standard_gradient_operator
46                    .iter()
47                    .map(|standard_gradient_operator_a| {
48                        standard_gradient_operator_a
49                            .iter()
50                            .zip(reference_dual_basis.iter())
51                            .map(|(standard_gradient_operator_a_m, reference_dual_basis_m)| {
52                                reference_dual_basis_m * standard_gradient_operator_a_m
53                            })
54                            .sum()
55                    })
56                    .collect()
57            })
58            .collect();
59        let reference_normals = reference_dual_bases
60            .iter()
61            .map(|reference_dual_basis| {
62                reference_dual_basis[0]
63                    .cross(&reference_dual_basis[1])
64                    .normalized()
65            })
66            .collect();
67        Self {
68            constitutive_models: from_fn(|_| <C>::new(constitutive_model_parameters)),
69            gradient_vectors,
70            integration_weights,
71            reference_normals,
72        }
73    }
74}
75
76impl<C> Triangle<C> {
77    const fn integration_weight() -> Scalar {
78        1.0 / 2.0
79    }
80    #[cfg(test)]
81    const fn shape_functions_at_integration_points() -> ShapeFunctionsAtIntegrationPoints<G, Q> {
82        tensor_rank_1_list([tensor_rank_1([1.0 / 3.0; Q])])
83    }
84    const fn standard_gradient_operators() -> StandardGradientOperators<M, N, P> {
85        tensor_rank_1_list_2d([tensor_rank_1_list([
86            tensor_rank_1([-1.0, -1.0]),
87            tensor_rank_1([1.0, 0.0]),
88            tensor_rank_1([0.0, 1.0]),
89        ])])
90    }
91}
92
93impl<C> SurfaceFiniteElementMethodsExtra<M, N, P> for Triangle<C> {
94    fn standard_gradient_operators() -> StandardGradientOperators<M, N, P> {
95        Self::standard_gradient_operators()
96    }
97}
98
99impl<C> FiniteElementMethods<C, G, N> for Triangle<C> {
100    fn constitutive_models(&self) -> &[C; G] {
101        &self.constitutive_models
102    }
103    fn deformation_gradients(
104        &self,
105        nodal_coordinates: &NodalCoordinates<N>,
106    ) -> DeformationGradientList<G> {
107        self.gradient_vectors()
108            .iter()
109            .zip(
110                Self::normals(nodal_coordinates)
111                    .iter()
112                    .zip(self.reference_normals().iter()),
113            )
114            .map(|(gradient_vectors, (normal, reference_normal))| {
115                nodal_coordinates
116                    .iter()
117                    .zip(gradient_vectors.iter())
118                    .map(|(nodal_coordinate, gradient_vector)| {
119                        DeformationGradient::dyad(nodal_coordinate, gradient_vector)
120                    })
121                    .sum::<DeformationGradient>()
122                    + DeformationGradient::dyad(normal, reference_normal)
123            })
124            .collect()
125    }
126    fn deformation_gradient_rates(
127        &self,
128        nodal_coordinates: &NodalCoordinates<N>,
129        nodal_velocities: &NodalVelocities<N>,
130    ) -> DeformationGradientRateList<G> {
131        self.gradient_vectors()
132            .iter()
133            .zip(
134                Self::normal_rates(nodal_coordinates, nodal_velocities)
135                    .iter()
136                    .zip(self.reference_normals().iter()),
137            )
138            .map(|(gradient_vectors, (normal_rate, reference_normal))| {
139                nodal_velocities
140                    .iter()
141                    .zip(gradient_vectors.iter())
142                    .map(|(nodal_velocity, gradient_vector)| {
143                        DeformationGradientRate::dyad(nodal_velocity, gradient_vector)
144                    })
145                    .sum::<DeformationGradientRate>()
146                    + DeformationGradientRate::dyad(normal_rate, reference_normal)
147            })
148            .collect()
149    }
150    fn gradient_vectors(&self) -> &GradientVectors<G, N> {
151        &self.gradient_vectors
152    }
153    fn integration_weights(&self) -> &Scalars<G> {
154        &self.integration_weights
155    }
156}
157
158impl<C> ElasticFiniteElement<C, G, N> for Triangle<C>
159where
160    C: Elastic,
161{
162    fn nodal_forces(
163        &self,
164        nodal_coordinates: &NodalCoordinates<N>,
165    ) -> Result<NodalForces<N>, ConstitutiveError> {
166        Ok(self
167            .constitutive_models()
168            .iter()
169            .zip(self.deformation_gradients(nodal_coordinates).iter())
170            .map(|(constitutive_model, deformation_gradient)| {
171                constitutive_model.first_piola_kirchhoff_stress(deformation_gradient)
172            })
173            .collect::<Result<FirstPiolaKirchhoffStresses<G>, _>>()?
174            .iter()
175            .zip(
176                self.gradient_vectors()
177                    .iter()
178                    .zip(self.integration_weights().iter()),
179            )
180            .map(
181                |(first_piola_kirchhoff_stress, (gradient_vectors, integration_weight))| {
182                    gradient_vectors
183                        .iter()
184                        .map(|gradient_vector| {
185                            (first_piola_kirchhoff_stress * gradient_vector) * integration_weight
186                        })
187                        .collect()
188                },
189            )
190            .sum())
191    }
192    fn nodal_stiffnesses(
193        &self,
194        nodal_coordinates: &NodalCoordinates<N>,
195    ) -> Result<NodalStiffnesses<N>, ConstitutiveError> {
196        Ok(self
197            .constitutive_models()
198            .iter()
199            .zip(self.deformation_gradients(nodal_coordinates).iter())
200            .map(|(constitutive_model, deformation_gradient)| {
201                constitutive_model.first_piola_kirchhoff_tangent_stiffness(deformation_gradient)
202            })
203            .collect::<Result<FirstPiolaKirchhoffTangentStiffnesses<G>, _>>()?
204            .iter()
205            .zip(
206                self.gradient_vectors()
207                    .iter()
208                    .zip(self.integration_weights().iter()
209                    .zip(self.reference_normals().iter()
210                    .zip(Self::normal_gradients(nodal_coordinates).iter())
211                )
212                ),
213            )
214            .map(
215                |(
216                    first_piola_kirchhoff_tangent_stiffness,
217                    (gradient_vectors, (integration_weight, (reference_normal, normal_gradients))),
218                )| {
219                    gradient_vectors.iter()
220                    .map(|gradient_vector_a|
221                        gradient_vectors.iter()
222                        .zip(normal_gradients.iter())
223                        .map(|(gradient_vector_b, normal_gradient_b)|
224                            first_piola_kirchhoff_tangent_stiffness.iter()
225                            .map(|first_piola_kirchhoff_tangent_stiffness_m|
226                                IDENTITY.iter()
227                                .zip(normal_gradient_b.iter())
228                                .map(|(identity_n, normal_gradient_b_n)|
229                                    first_piola_kirchhoff_tangent_stiffness_m.iter()
230                                    .zip(gradient_vector_a.iter())
231                                    .map(|(first_piola_kirchhoff_tangent_stiffness_mj, gradient_vector_a_j)|
232                                        first_piola_kirchhoff_tangent_stiffness_mj.iter()
233                                        .zip(identity_n.iter()
234                                        .zip(normal_gradient_b_n.iter()))
235                                        .map(|(first_piola_kirchhoff_tangent_stiffness_mjk, (identity_nk, normal_gradient_b_n_k))|
236                                            first_piola_kirchhoff_tangent_stiffness_mjk.iter()
237                                            .zip(gradient_vector_b.iter()
238                                            .zip(reference_normal.iter()))
239                                            .map(|(first_piola_kirchhoff_tangent_stiffness_mjkl, (gradient_vector_b_l, reference_normal_l))|
240                                                first_piola_kirchhoff_tangent_stiffness_mjkl * gradient_vector_a_j * (
241                                                    identity_nk * gradient_vector_b_l + normal_gradient_b_n_k * reference_normal_l
242                                                ) * integration_weight
243                                            ).sum::<Scalar>()
244                                        ).sum::<Scalar>()
245                                    ).sum::<Scalar>()
246                                ).collect()
247                            ).collect()
248                        ).collect()
249                    ).collect()
250                }
251            )
252            .sum())
253    }
254}
255
256impl<C> HyperelasticFiniteElement<C, G, N> for Triangle<C>
257where
258    C: Hyperelastic,
259{
260    fn helmholtz_free_energy(
261        &self,
262        nodal_coordinates: &NodalCoordinates<N>,
263    ) -> Result<Scalar, ConstitutiveError> {
264        self.constitutive_models()
265            .iter()
266            .zip(
267                self.deformation_gradients(nodal_coordinates)
268                    .iter()
269                    .zip(self.integration_weights().iter()),
270            )
271            .map(
272                |(constitutive_model, (deformation_gradient, integration_weight))| {
273                    Ok(
274                        constitutive_model.helmholtz_free_energy_density(deformation_gradient)?
275                            * integration_weight,
276                    )
277                },
278            )
279            .sum()
280    }
281}
282
283impl<C> ViscoelasticFiniteElement<C, G, N> for Triangle<C>
284where
285    C: Viscoelastic,
286{
287    fn nodal_forces(
288        &self,
289        nodal_coordinates: &NodalCoordinates<N>,
290        nodal_velocities: &NodalVelocities<N>,
291    ) -> Result<NodalForces<N>, ConstitutiveError> {
292        Ok(self
293            .constitutive_models()
294            .iter()
295            .zip(
296                self.deformation_gradients(nodal_coordinates).iter().zip(
297                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
298                        .iter(),
299                ),
300            )
301            .map(
302                |(constitutive_model, (deformation_gradient, deformation_gradient_rate))| {
303                    constitutive_model.first_piola_kirchhoff_stress(
304                        deformation_gradient,
305                        deformation_gradient_rate,
306                    )
307                },
308            )
309            .collect::<Result<FirstPiolaKirchhoffStresses<G>, _>>()?
310            .iter()
311            .zip(
312                self.gradient_vectors()
313                    .iter()
314                    .zip(self.integration_weights().iter()),
315            )
316            .map(
317                |(first_piola_kirchhoff_stress, (gradient_vectors, integration_weight))| {
318                    gradient_vectors
319                        .iter()
320                        .map(|gradient_vector| {
321                            (first_piola_kirchhoff_stress * gradient_vector) * integration_weight
322                        })
323                        .collect()
324                },
325            )
326            .sum())
327    }
328    fn nodal_stiffnesses(
329        &self,
330        nodal_coordinates: &NodalCoordinates<N>,
331        nodal_velocities: &NodalVelocities<N>,
332    ) -> Result<NodalStiffnesses<N>, ConstitutiveError> {
333        Ok(self
334            .constitutive_models()
335            .iter()
336            .zip(self.deformation_gradients(nodal_coordinates).iter().zip(self.deformation_gradient_rates(nodal_coordinates, nodal_velocities).iter()))
337            .map(|(constitutive_model, (deformation_gradient, deformation_gradient_rate))| {
338                constitutive_model.first_piola_kirchhoff_rate_tangent_stiffness(deformation_gradient, deformation_gradient_rate)
339            })
340            .collect::<Result<FirstPiolaKirchhoffRateTangentStiffnesses<G>, _>>()?
341            .iter()
342            .zip(
343                self.gradient_vectors()
344                    .iter()
345                    .zip(self.integration_weights().iter()
346                    .zip(self.reference_normals().iter()
347                    .zip(Self::normal_gradients(nodal_coordinates).iter())
348                )
349                ),
350            )
351            .map(
352                |(
353                    first_piola_kirchoff_rate_tangent_stiffness_mjkl,
354                    (gradient_vectors, (integration_weight, (reference_normal, normal_gradients))),
355                )| {
356                    gradient_vectors.iter()
357                    .map(|gradient_vector_a|
358                        gradient_vectors.iter()
359                        .zip(normal_gradients.iter())
360                        .map(|(gradient_vector_b, normal_gradient_b)|
361                            first_piola_kirchoff_rate_tangent_stiffness_mjkl.iter()
362                            .map(|first_piola_kirchhoff_rate_tangent_stiffness_m|
363                                IDENTITY.iter()
364                                .zip(normal_gradient_b.iter())
365                                .map(|(identity_n, normal_gradient_b_n)|
366                                    first_piola_kirchhoff_rate_tangent_stiffness_m.iter()
367                                    .zip(gradient_vector_a.iter())
368                                    .map(|(first_piola_kirchhoff_rate_tangent_stiffness_mj, gradient_vector_a_j)|
369                                        first_piola_kirchhoff_rate_tangent_stiffness_mj.iter()
370                                        .zip(identity_n.iter()
371                                        .zip(normal_gradient_b_n.iter()))
372                                        .map(|(first_piola_kirchhoff_rate_tangent_stiffness_mjk, (identity_nk, normal_gradient_b_n_k))|
373                                            first_piola_kirchhoff_rate_tangent_stiffness_mjk.iter()
374                                            .zip(gradient_vector_b.iter()
375                                            .zip(reference_normal.iter()))
376                                            .map(|(first_piola_kirchoff_rate_tangent_stiffness_mjkl, (gradient_vector_b_l, reference_normal_l))|
377                                                first_piola_kirchoff_rate_tangent_stiffness_mjkl * gradient_vector_a_j * (
378                                                    identity_nk * gradient_vector_b_l + normal_gradient_b_n_k * reference_normal_l
379                                                ) * integration_weight
380                                            ).sum::<Scalar>()
381                                        ).sum::<Scalar>()
382                                    ).sum::<Scalar>()
383                                ).collect()
384                            ).collect()
385                        ).collect()
386                    ).collect()
387                }
388            )
389            .sum())
390    }
391}
392
393impl<C> ElasticHyperviscousFiniteElement<C, G, N> for Triangle<C>
394where
395    C: ElasticHyperviscous,
396{
397    fn viscous_dissipation(
398        &self,
399        nodal_coordinates: &NodalCoordinates<N>,
400        nodal_velocities: &NodalVelocities<N>,
401    ) -> Result<Scalar, ConstitutiveError> {
402        self.constitutive_models()
403            .iter()
404            .zip(
405                self.deformation_gradients(nodal_coordinates).iter().zip(
406                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
407                        .iter()
408                        .zip(self.integration_weights().iter()),
409                ),
410            )
411            .map(
412                |(
413                    constitutive_model,
414                    (deformation_gradient, (deformation_gradient_rate, integration_weight)),
415                )| {
416                    Ok(constitutive_model
417                        .viscous_dissipation(deformation_gradient, deformation_gradient_rate)?
418                        * integration_weight)
419                },
420            )
421            .sum()
422    }
423    fn dissipation_potential(
424        &self,
425        nodal_coordinates: &NodalCoordinates<N>,
426        nodal_velocities: &NodalVelocities<N>,
427    ) -> Result<Scalar, ConstitutiveError> {
428        self.constitutive_models()
429            .iter()
430            .zip(
431                self.deformation_gradients(nodal_coordinates).iter().zip(
432                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
433                        .iter()
434                        .zip(self.integration_weights().iter()),
435                ),
436            )
437            .map(
438                |(
439                    constitutive_model,
440                    (deformation_gradient, (deformation_gradient_rate, integration_weight)),
441                )| {
442                    Ok(constitutive_model
443                        .dissipation_potential(deformation_gradient, deformation_gradient_rate)?
444                        * integration_weight)
445                },
446            )
447            .sum()
448    }
449}
450
451impl<C> HyperviscoelasticFiniteElement<C, G, N> for Triangle<C>
452where
453    C: Hyperviscoelastic,
454{
455    fn helmholtz_free_energy(
456        &self,
457        nodal_coordinates: &NodalCoordinates<N>,
458    ) -> Result<Scalar, ConstitutiveError> {
459        self.constitutive_models()
460            .iter()
461            .zip(
462                self.deformation_gradients(nodal_coordinates)
463                    .iter()
464                    .zip(self.integration_weights().iter()),
465            )
466            .map(
467                |(constitutive_model, (deformation_gradient, integration_weight))| {
468                    Ok(
469                        constitutive_model.helmholtz_free_energy_density(deformation_gradient)?
470                            * integration_weight,
471                    )
472                },
473            )
474            .sum()
475    }
476}