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}