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
86pub 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}