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