conspire/fem/block/element/
mod.rs

1#[cfg(test)]
2mod test;
3
4pub mod composite;
5pub mod linear;
6
7use super::*;
8use crate::{
9    constitutive::{Constitutive, Parameters},
10    math::{IDENTITY, LEVI_CIVITA, tensor_rank_1_zero},
11    mechanics::Scalar,
12};
13
14pub struct Element<C, const G: usize, const N: usize> {
15    constitutive_models: [C; G],
16    gradient_vectors: GradientVectors<G, N>,
17    integration_weights: Scalars<G>,
18}
19
20pub struct SurfaceElement<C, const G: usize, const N: usize, const P: usize> {
21    constitutive_models: [C; G],
22    gradient_vectors: GradientVectors<G, N>,
23    integration_weights: Scalars<G>,
24    reference_normals: ReferenceNormals<P>,
25}
26
27pub trait FiniteElement<C, const G: usize, const N: usize, Y>
28where
29    C: Constitutive<Y>,
30    Self: FiniteElementMethods<C, G, N>,
31    Y: Parameters,
32{
33    fn new(
34        constitutive_model_parameters: Y,
35        reference_nodal_coordinates: ReferenceNodalCoordinates<N>,
36    ) -> Self;
37}
38
39pub trait SurfaceFiniteElement<C, const G: usize, const N: usize, const P: usize, Y>
40where
41    C: Constitutive<Y>,
42    Self: FiniteElementMethods<C, G, N>,
43    Y: Parameters,
44{
45    fn new(
46        constitutive_model_parameters: Y,
47        reference_nodal_coordinates: ReferenceNodalCoordinates<N>,
48        thickness: &Scalar,
49    ) -> Self;
50}
51
52pub trait FiniteElementMethods<C, const G: usize, const N: usize> {
53    fn constitutive_models(&self) -> &[C; G];
54    fn deformation_gradients(
55        &self,
56        nodal_coordinates: &NodalCoordinates<N>,
57    ) -> DeformationGradientList<G>;
58    fn deformation_gradient_rates(
59        &self,
60        nodal_coordinates: &NodalCoordinates<N>,
61        nodal_velocities: &NodalVelocities<N>,
62    ) -> DeformationGradientRateList<G>;
63    fn gradient_vectors(&self) -> &GradientVectors<G, N>;
64    fn integration_weights(&self) -> &Scalars<G>;
65}
66
67pub trait SurfaceFiniteElementMethods<
68    const G: usize,
69    const M: usize,
70    const N: usize,
71    const P: usize,
72> where
73    Self: SurfaceFiniteElementMethodsExtra<M, N, P>,
74{
75    fn bases<const I: usize>(nodal_coordinates: &Coordinates<I, N>) -> Bases<I, P>;
76    fn dual_bases<const I: usize>(nodal_coordinates: &Coordinates<I, N>) -> Bases<I, P>;
77    fn normals(nodal_coordinates: &NodalCoordinates<N>) -> Normals<P>;
78    fn normal_gradients(nodal_coordinates: &NodalCoordinates<N>) -> NormalGradients<N, P>;
79    fn normal_rates(
80        nodal_coordinates: &NodalCoordinates<N>,
81        nodal_velocities: &NodalVelocities<N>,
82    ) -> NormalRates<P>;
83    fn reference_normals(&self) -> &ReferenceNormals<P>;
84}
85
86// make this a const fn and remove inherent impl of it once Rust stabilizes const fn trait methods
87pub trait SurfaceFiniteElementMethodsExtra<const M: usize, const N: usize, const P: usize> {
88    fn standard_gradient_operators() -> StandardGradientOperators<M, N, P>;
89}
90
91impl<C, const G: usize, const N: usize> FiniteElementMethods<C, G, N> for Element<C, G, N> {
92    fn constitutive_models(&self) -> &[C; G] {
93        &self.constitutive_models
94    }
95    fn deformation_gradients(
96        &self,
97        nodal_coordinates: &NodalCoordinates<N>,
98    ) -> DeformationGradientList<G> {
99        self.gradient_vectors()
100            .iter()
101            .map(|gradient_vectors| {
102                nodal_coordinates
103                    .iter()
104                    .zip(gradient_vectors.iter())
105                    .map(|(nodal_coordinate, gradient_vector)| {
106                        DeformationGradient::dyad(nodal_coordinate, gradient_vector)
107                    })
108                    .sum()
109            })
110            .collect()
111    }
112    fn deformation_gradient_rates(
113        &self,
114        _: &NodalCoordinates<N>,
115        nodal_velocities: &NodalVelocities<N>,
116    ) -> DeformationGradientRateList<G> {
117        self.gradient_vectors()
118            .iter()
119            .map(|gradient_vectors| {
120                nodal_velocities
121                    .iter()
122                    .zip(gradient_vectors.iter())
123                    .map(|(nodal_velocity, gradient_vector)| {
124                        DeformationGradientRate::dyad(nodal_velocity, gradient_vector)
125                    })
126                    .sum()
127            })
128            .collect()
129    }
130    fn gradient_vectors(&self) -> &GradientVectors<G, N> {
131        &self.gradient_vectors
132    }
133    fn integration_weights(&self) -> &Scalars<G> {
134        &self.integration_weights
135    }
136}
137
138impl<C, const G: usize, const M: usize, const N: usize, const P: usize>
139    SurfaceFiniteElementMethods<G, M, N, P> for SurfaceElement<C, G, N, P>
140where
141    Self: SurfaceFiniteElementMethodsExtra<M, N, P>,
142{
143    fn bases<const I: usize>(nodal_coordinates: &Coordinates<I, N>) -> Bases<I, P> {
144        Self::standard_gradient_operators()
145            .iter()
146            .map(|standard_gradient_operator| {
147                standard_gradient_operator
148                    .iter()
149                    .zip(nodal_coordinates.iter())
150                    .map(|(standard_gradient_operator_a, nodal_coordinate_a)| {
151                        standard_gradient_operator_a
152                            .iter()
153                            .map(|standard_gradient_operator_a_m| {
154                                nodal_coordinate_a * standard_gradient_operator_a_m
155                            })
156                            .collect()
157                    })
158                    .sum()
159            })
160            .collect()
161    }
162    fn dual_bases<const I: usize>(nodal_coordinates: &Coordinates<I, N>) -> Bases<I, P> {
163        Self::bases(nodal_coordinates)
164            .iter()
165            .map(|basis_vectors| {
166                basis_vectors
167                    .iter()
168                    .map(|basis_vectors_m| {
169                        basis_vectors
170                            .iter()
171                            .map(|basis_vectors_n| basis_vectors_m * basis_vectors_n)
172                            .collect()
173                    })
174                    .collect::<TensorRank2<2, I, I>>()
175                    .inverse()
176                    .iter()
177                    .map(|metric_tensor_m| {
178                        metric_tensor_m
179                            .iter()
180                            .zip(basis_vectors.iter())
181                            .map(|(metric_tensor_mn, basis_vectors_n)| {
182                                basis_vectors_n * metric_tensor_mn
183                            })
184                            .sum()
185                    })
186                    .collect()
187            })
188            .collect()
189    }
190    fn normals(nodal_coordinates: &NodalCoordinates<N>) -> Normals<P> {
191        Self::bases(nodal_coordinates)
192            .iter()
193            .map(|basis_vectors| basis_vectors[0].cross(&basis_vectors[1]).normalized())
194            .collect()
195    }
196    fn normal_gradients(nodal_coordinates: &NodalCoordinates<N>) -> NormalGradients<N, P> {
197        let levi_civita_symbol = LEVI_CIVITA;
198        let mut normalization: Scalar = 0.0;
199        let mut normal_vector = tensor_rank_1_zero();
200        Self::standard_gradient_operators().iter()
201        .zip(Self::bases(nodal_coordinates).iter())
202        .map(|(standard_gradient_operator, basis_vectors)|{
203            normalization = basis_vectors[0].cross(&basis_vectors[1]).norm();
204            normal_vector = basis_vectors[0].cross(&basis_vectors[1])/normalization;
205            standard_gradient_operator.iter()
206            .map(|standard_gradient_operator_a|
207                levi_civita_symbol.iter()
208                .map(|levi_civita_symbol_m|
209                    IDENTITY.iter()
210                    .zip(normal_vector.iter())
211                    .map(|(identity_i, normal_vector_i)|
212                        levi_civita_symbol_m.iter()
213                        .zip(basis_vectors[0].iter()
214                        .zip(basis_vectors[1].iter()))
215                        .map(|(levi_civita_symbol_mn, (basis_vector_0_n, basis_vector_1_n))|
216                            levi_civita_symbol_mn.iter()
217                            .zip(identity_i.iter()
218                            .zip(normal_vector.iter()))
219                            .map(|(levi_civita_symbol_mno, (identity_io, normal_vector_o))|
220                                levi_civita_symbol_mno * (identity_io - normal_vector_i * normal_vector_o)
221                            ).sum::<Scalar>() * (
222                                standard_gradient_operator_a[0] * basis_vector_1_n
223                              - standard_gradient_operator_a[1] * basis_vector_0_n
224                            )
225                        ).sum::<Scalar>() / normalization
226                    ).collect()
227                ).collect()
228            ).collect()
229        }).collect()
230    }
231    fn normal_rates(
232        nodal_coordinates: &NodalCoordinates<N>,
233        nodal_velocities: &NodalVelocities<N>,
234    ) -> NormalRates<P> {
235        let identity = IDENTITY;
236        let levi_civita_symbol = LEVI_CIVITA;
237        let mut normalization = 0.0;
238        Self::bases(nodal_coordinates)
239            .iter()
240            .zip(Self::normals(nodal_coordinates).iter()
241            .zip(Self::standard_gradient_operators().iter()))
242            .map(|(basis, (normal, standard_gradient_operator))| {
243                normalization = basis[0].cross(&basis[1]).norm();
244                identity.iter()
245                .zip(normal.iter())
246                .map(|(identity_i, normal_vector_i)|
247                    nodal_velocities.iter()
248                    .zip(standard_gradient_operator.iter())
249                    .map(|(nodal_velocity_a, standard_gradient_operator_a)|
250                        levi_civita_symbol.iter()
251                        .zip(nodal_velocity_a.iter())
252                        .map(|(levi_civita_symbol_m, nodal_velocity_a_m)|
253                            levi_civita_symbol_m.iter()
254                            .zip(basis[0].iter()
255                            .zip(basis[1].iter()))
256                            .map(|(levi_civita_symbol_mn, (basis_vector_0_n, basis_vector_1_n))|
257                                levi_civita_symbol_mn.iter()
258                                .zip(identity_i.iter()
259                                .zip(normal.iter()))
260                                .map(|(levi_civita_symbol_mno, (identity_io, normal_vector_o))|
261                                    levi_civita_symbol_mno * (identity_io - normal_vector_i * normal_vector_o)
262                                ).sum::<Scalar>() * (
263                                    standard_gradient_operator_a[0] * basis_vector_1_n
264                                - standard_gradient_operator_a[1] * basis_vector_0_n
265                                )
266                            ).sum::<Scalar>() * nodal_velocity_a_m
267                        ).sum::<Scalar>()
268                    ).sum::<Scalar>() / normalization
269                ).collect()
270        }).collect()
271    }
272    fn reference_normals(&self) -> &ReferenceNormals<P> {
273        &self.reference_normals
274    }
275}
276
277pub trait ElasticFiniteElement<C, const G: usize, const N: usize>
278where
279    C: Elastic,
280    Self: FiniteElementMethods<C, G, N>,
281{
282    fn nodal_forces(
283        &self,
284        nodal_coordinates: &NodalCoordinates<N>,
285    ) -> Result<NodalForces<N>, ConstitutiveError>;
286    fn nodal_stiffnesses(
287        &self,
288        nodal_coordinates: &NodalCoordinates<N>,
289    ) -> Result<NodalStiffnesses<N>, ConstitutiveError>;
290}
291
292pub trait HyperelasticFiniteElement<C, const G: usize, const N: usize>
293where
294    C: Hyperelastic,
295    Self: ElasticFiniteElement<C, G, N>,
296{
297    fn helmholtz_free_energy(
298        &self,
299        nodal_coordinates: &NodalCoordinates<N>,
300    ) -> Result<Scalar, ConstitutiveError>;
301}
302
303pub trait ViscoelasticFiniteElement<C, const G: usize, const N: usize>
304where
305    C: Viscoelastic,
306    Self: FiniteElementMethods<C, G, N>,
307{
308    fn nodal_forces(
309        &self,
310        nodal_coordinates: &NodalCoordinates<N>,
311        nodal_velocities: &NodalVelocities<N>,
312    ) -> Result<NodalForces<N>, ConstitutiveError>;
313    fn nodal_stiffnesses(
314        &self,
315        nodal_coordinates: &NodalCoordinates<N>,
316        nodal_velocities: &NodalVelocities<N>,
317    ) -> Result<NodalStiffnesses<N>, ConstitutiveError>;
318}
319
320pub trait ElasticHyperviscousFiniteElement<C, const G: usize, const N: usize>
321where
322    C: ElasticHyperviscous,
323    Self: ViscoelasticFiniteElement<C, G, N>,
324{
325    fn viscous_dissipation(
326        &self,
327        nodal_coordinates: &NodalCoordinates<N>,
328        nodal_velocities: &NodalVelocities<N>,
329    ) -> Result<Scalar, ConstitutiveError>;
330    fn dissipation_potential(
331        &self,
332        nodal_coordinates: &NodalCoordinates<N>,
333        nodal_velocities: &NodalVelocities<N>,
334    ) -> Result<Scalar, ConstitutiveError>;
335}
336
337pub trait HyperviscoelasticFiniteElement<C, const G: usize, const N: usize>
338where
339    C: Hyperviscoelastic,
340    Self: ElasticHyperviscousFiniteElement<C, G, N>,
341{
342    fn helmholtz_free_energy(
343        &self,
344        nodal_coordinates: &NodalCoordinates<N>,
345    ) -> Result<Scalar, ConstitutiveError>;
346}
347
348impl<C, const G: usize, const N: usize> ElasticFiniteElement<C, G, N> for Element<C, G, N>
349where
350    C: Elastic,
351{
352    fn nodal_forces(
353        &self,
354        nodal_coordinates: &NodalCoordinates<N>,
355    ) -> Result<NodalForces<N>, ConstitutiveError> {
356        Ok(self
357            .constitutive_models()
358            .iter()
359            .zip(self.deformation_gradients(nodal_coordinates).iter())
360            .map(|(constitutive_model, deformation_gradient)| {
361                constitutive_model.first_piola_kirchhoff_stress(deformation_gradient)
362            })
363            .collect::<Result<FirstPiolaKirchhoffStresses<G>, _>>()?
364            .iter()
365            .zip(
366                self.gradient_vectors()
367                    .iter()
368                    .zip(self.integration_weights().iter()),
369            )
370            .map(
371                |(first_piola_kirchhoff_stress, (gradient_vectors, integration_weight))| {
372                    gradient_vectors
373                        .iter()
374                        .map(|gradient_vector| {
375                            (first_piola_kirchhoff_stress * gradient_vector) * integration_weight
376                        })
377                        .collect()
378                },
379            )
380            .sum())
381    }
382    fn nodal_stiffnesses(
383        &self,
384        nodal_coordinates: &NodalCoordinates<N>,
385    ) -> Result<NodalStiffnesses<N>, ConstitutiveError> {
386        Ok(self
387            .constitutive_models()
388            .iter()
389            .zip(self.deformation_gradients(nodal_coordinates).iter())
390            .map(|(constitutive_model, deformation_gradient)| {
391                constitutive_model.first_piola_kirchhoff_tangent_stiffness(deformation_gradient)
392            })
393            .collect::<Result<FirstPiolaKirchhoffTangentStiffnesses<G>, _>>()?
394            .iter()
395            .zip(
396                self.gradient_vectors()
397                    .iter()
398                    .zip(self.integration_weights().iter()),
399            )
400            .map(
401                |(
402                    first_piola_kirchhoff_tangent_stiffness,
403                    (gradient_vectors, integration_weight),
404                )| {
405                    gradient_vectors
406                        .iter()
407                        .map(|gradient_vector_a| {
408                            gradient_vectors
409                                .iter()
410                                .map(|gradient_vector_b| {
411                                    first_piola_kirchhoff_tangent_stiffness
412                                        .contract_second_fourth_indices_with_first_indices_of(
413                                            gradient_vector_a,
414                                            gradient_vector_b,
415                                        )
416                                        * integration_weight
417                                })
418                                .collect()
419                        })
420                        .collect()
421                },
422            )
423            .sum())
424    }
425}
426
427impl<C, const G: usize, const N: usize> HyperelasticFiniteElement<C, G, N> for Element<C, G, N>
428where
429    C: Hyperelastic,
430{
431    fn helmholtz_free_energy(
432        &self,
433        nodal_coordinates: &NodalCoordinates<N>,
434    ) -> Result<Scalar, ConstitutiveError> {
435        self.constitutive_models()
436            .iter()
437            .zip(
438                self.deformation_gradients(nodal_coordinates)
439                    .iter()
440                    .zip(self.integration_weights().iter()),
441            )
442            .map(
443                |(constitutive_model, (deformation_gradient, integration_weight))| {
444                    Ok(
445                        constitutive_model.helmholtz_free_energy_density(deformation_gradient)?
446                            * integration_weight,
447                    )
448                },
449            )
450            .sum()
451    }
452}
453
454impl<C, const G: usize, const N: usize> ViscoelasticFiniteElement<C, G, N> for Element<C, G, N>
455where
456    C: Viscoelastic,
457{
458    fn nodal_forces(
459        &self,
460        nodal_coordinates: &NodalCoordinates<N>,
461        nodal_velocities: &NodalVelocities<N>,
462    ) -> Result<NodalForces<N>, ConstitutiveError> {
463        Ok(self
464            .constitutive_models()
465            .iter()
466            .zip(
467                self.deformation_gradients(nodal_coordinates).iter().zip(
468                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
469                        .iter(),
470                ),
471            )
472            .map(
473                |(constitutive_model, (deformation_gradient, deformation_gradient_rate))| {
474                    constitutive_model.first_piola_kirchhoff_stress(
475                        deformation_gradient,
476                        deformation_gradient_rate,
477                    )
478                },
479            )
480            .collect::<Result<FirstPiolaKirchhoffStresses<G>, _>>()?
481            .iter()
482            .zip(
483                self.gradient_vectors()
484                    .iter()
485                    .zip(self.integration_weights().iter()),
486            )
487            .map(
488                |(first_piola_kirchhoff_stress, (gradient_vectors, integration_weight))| {
489                    gradient_vectors
490                        .iter()
491                        .map(|gradient_vector| {
492                            (first_piola_kirchhoff_stress * gradient_vector) * integration_weight
493                        })
494                        .collect()
495                },
496            )
497            .sum())
498    }
499    fn nodal_stiffnesses(
500        &self,
501        nodal_coordinates: &NodalCoordinates<N>,
502        nodal_velocities: &NodalVelocities<N>,
503    ) -> Result<NodalStiffnesses<N>, ConstitutiveError> {
504        Ok(self
505            .constitutive_models()
506            .iter()
507            .zip(
508                self.deformation_gradients(nodal_coordinates).iter().zip(
509                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
510                        .iter(),
511                ),
512            )
513            .map(
514                |(constitutive_model, (deformation_gradient, deformation_gradient_rate))| {
515                    constitutive_model.first_piola_kirchhoff_rate_tangent_stiffness(
516                        deformation_gradient,
517                        deformation_gradient_rate,
518                    )
519                },
520            )
521            .collect::<Result<FirstPiolaKirchhoffRateTangentStiffnesses<G>, _>>()?
522            .iter()
523            .zip(
524                self.gradient_vectors().iter().zip(
525                    self.gradient_vectors()
526                        .iter()
527                        .zip(self.integration_weights().iter()),
528                ),
529            )
530            .map(
531                |(
532                    first_piola_kirchhoff_rate_tangent_stiffness,
533                    (gradient_vectors_a, (gradient_vectors_b, integration_weight)),
534                )| {
535                    gradient_vectors_a
536                        .iter()
537                        .map(|gradient_vector_a| {
538                            gradient_vectors_b
539                                .iter()
540                                .map(|gradient_vector_b| {
541                                    first_piola_kirchhoff_rate_tangent_stiffness
542                                        .contract_second_fourth_indices_with_first_indices_of(
543                                            gradient_vector_a,
544                                            gradient_vector_b,
545                                        )
546                                        * integration_weight
547                                })
548                                .collect()
549                        })
550                        .collect()
551                },
552            )
553            .sum())
554    }
555}
556
557impl<C, const G: usize, const N: usize> ElasticHyperviscousFiniteElement<C, G, N>
558    for Element<C, G, N>
559where
560    C: ElasticHyperviscous,
561{
562    fn viscous_dissipation(
563        &self,
564        nodal_coordinates: &NodalCoordinates<N>,
565        nodal_velocities: &NodalVelocities<N>,
566    ) -> Result<Scalar, ConstitutiveError> {
567        self.constitutive_models()
568            .iter()
569            .zip(
570                self.deformation_gradients(nodal_coordinates).iter().zip(
571                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
572                        .iter()
573                        .zip(self.integration_weights().iter()),
574                ),
575            )
576            .map(
577                |(
578                    constitutive_model,
579                    (deformation_gradient, (deformation_gradient_rate, integration_weight)),
580                )| {
581                    Ok(constitutive_model
582                        .viscous_dissipation(deformation_gradient, deformation_gradient_rate)?
583                        * integration_weight)
584                },
585            )
586            .sum()
587    }
588    fn dissipation_potential(
589        &self,
590        nodal_coordinates: &NodalCoordinates<N>,
591        nodal_velocities: &NodalVelocities<N>,
592    ) -> Result<Scalar, ConstitutiveError> {
593        self.constitutive_models()
594            .iter()
595            .zip(
596                self.deformation_gradients(nodal_coordinates).iter().zip(
597                    self.deformation_gradient_rates(nodal_coordinates, nodal_velocities)
598                        .iter()
599                        .zip(self.integration_weights().iter()),
600                ),
601            )
602            .map(
603                |(
604                    constitutive_model,
605                    (deformation_gradient, (deformation_gradient_rate, integration_weight)),
606                )| {
607                    Ok(constitutive_model
608                        .dissipation_potential(deformation_gradient, deformation_gradient_rate)?
609                        * integration_weight)
610                },
611            )
612            .sum()
613    }
614}
615
616impl<C, const G: usize, const N: usize> HyperviscoelasticFiniteElement<C, G, N> for Element<C, G, N>
617where
618    C: Hyperviscoelastic,
619{
620    fn helmholtz_free_energy(
621        &self,
622        nodal_coordinates: &NodalCoordinates<N>,
623    ) -> Result<Scalar, ConstitutiveError> {
624        self.constitutive_models()
625            .iter()
626            .zip(
627                self.deformation_gradients(nodal_coordinates)
628                    .iter()
629                    .zip(self.integration_weights().iter()),
630            )
631            .map(
632                |(constitutive_model, (deformation_gradient, integration_weight))| {
633                    Ok(
634                        constitutive_model.helmholtz_free_energy_density(deformation_gradient)?
635                            * integration_weight,
636                    )
637                },
638            )
639            .sum()
640    }
641}