1use crate::math::{Erase, Quantity};
2use crate::units::{Dimensionless, Stress, UnitDiv};
3use std::ops::{Div, Mul};
4
5use crate::{
6 constitutive::{ConstitutiveError, solid::elastic::internal_variables::ElasticIV},
7 fem::block::element::{
8 Element, ElementNodalCoordinates, FiniteElement, FiniteElementError, GradientVectors,
9 IntegrationWeights,
10 solid::{ElementNodalForcesSolid, ElementNodalStiffnessesSolid, SolidFiniteElement},
11 },
12 math::{
13 ContractSecondFourthWithFirst, HessianBlock, Jacobian, Matrix, Scalar, Solution,
14 SquareMatrix, Tensor, TensorList, Vector,
15 optimize::{EqualityConstraint, FirstOrderRootFinding, NewtonRaphson},
16 },
17 mechanics::{
18 DeformationGradient, FirstPiolaKirchhoffStress, FirstPiolaKirchhoffStressList,
19 FirstPiolaKirchhoffTangentStiffness, FirstPiolaKirchhoffTangentStiffnessList,
20 },
21 units::Volume,
22};
23
24fn free_indices<C, V>(constitutive_model: &C, size: usize) -> Vec<usize>
26where
27 C: ElasticIV<V>,
28{
29 let mut free = vec![true; size];
30 constitutive_model
31 .internal_variables_fixed()
32 .iter()
33 .for_each(|&index| free[index] = false);
34 (0..size).filter(|&index| free[index]).collect()
35}
36
37pub type InternalVariables<const G: usize, V> = TensorList<V, G>;
39
40pub trait ElasticIVFiniteElement<
41 C,
42 const G: usize,
43 const M: usize,
44 const N: usize,
45 const P: usize,
46 V,
47 E,
48> where
49 C: ElasticIV<V>,
50 C::Residual: Erase<Erased = E>,
51 Self: SolidFiniteElement<G, M, N, P>,
52 V: Erase<Erased = E> + Jacobian + Solution,
53 <V as Tensor>::Unit: UnitDiv<<V as Tensor>::Unit, Output = Dimensionless>,
54 E: Tensor,
55 for<'a> &'a C::Residual: Div<C::TangentVv, Output = V>,
56 for<'a> &'a V: Mul<Quantity<Dimensionless>, Output = V> + Mul<Scalar, Output = V>,
57 for<'a> &'a Matrix: Mul<&'a V, Output = Vector>,
58{
59 fn internal_variables_initial(&self, constitutive_model: &C) -> InternalVariables<G, V>;
61 fn internal_variables_root(
67 &self,
68 local_solver: &NewtonRaphson,
69 constitutive_model: &C,
70 nodal_coordinates: &ElementNodalCoordinates<N>,
71 internal_variables: &InternalVariables<G, V>,
72 ) -> Result<InternalVariables<G, V>, FiniteElementError>;
73 fn internal_variables_increment(
76 &self,
77 constitutive_model: &C,
78 nodal_coordinates: &ElementNodalCoordinates<N>,
79 internal_variables: &InternalVariables<G, V>,
80 nodal_decrement: &ElementNodalCoordinates<N>,
81 step: Scalar,
82 ) -> Result<InternalVariables<G, V>, FiniteElementError>;
83 fn nodal_forces(
84 &self,
85 constitutive_model: &C,
86 nodal_coordinates: &ElementNodalCoordinates<N>,
87 internal_variables: &InternalVariables<G, V>,
88 ) -> Result<ElementNodalForcesSolid<N>, FiniteElementError>;
89 fn nodal_forces_eliminated(
92 &self,
93 constitutive_model: &C,
94 nodal_coordinates: &ElementNodalCoordinates<N>,
95 internal_variables: &InternalVariables<G, V>,
96 ) -> Result<ElementNodalForcesSolid<N>, FiniteElementError>;
97 fn nodal_stiffnesses(
103 &self,
104 constitutive_model: &C,
105 nodal_coordinates: &ElementNodalCoordinates<N>,
106 internal_variables: &InternalVariables<G, V>,
107 ) -> Result<ElementNodalStiffnessesSolid<N>, FiniteElementError>;
108}
109
110fn root_at_point<C, V, E>(
116 local_solver: &NewtonRaphson,
117 constitutive_model: &C,
118 deformation_gradient: &DeformationGradient,
119 internal_variables: &V,
120) -> Result<V, ConstitutiveError>
121where
122 C: ElasticIV<V>,
123 C::Residual: Erase<Erased = E>,
124 V: Erase<Erased = E> + Jacobian + Solution,
125 <V as Tensor>::Unit: UnitDiv<<V as Tensor>::Unit, Output = Dimensionless>,
126 E: Tensor,
127 for<'a> &'a C::Residual: Div<C::TangentVv, Output = V>,
128 for<'a> &'a V: Mul<Quantity<Dimensionless>, Output = V> + Mul<Scalar, Output = V>,
129 for<'a> &'a Matrix: Mul<&'a V, Output = Vector>,
130{
131 local_solver
132 .root(
133 |root: &V| {
134 Ok(constitutive_model.internal_variables_residual(deformation_gradient, root)?)
135 },
136 |root: &V| Ok(constitutive_model.tangents(deformation_gradient, root)?.3),
137 internal_variables.clone(),
138 EqualityConstraint::Fixed(constitutive_model.internal_variables_fixed().to_vec()),
139 None,
140 )
141 .map_err(|error| ConstitutiveError::custom(error, deformation_gradient))
142}
143
144fn assemble_forces<const G: usize, const N: usize>(
146 stresses: FirstPiolaKirchhoffStressList<G>,
147 gradient_vectors: &GradientVectors<3, G, N>,
148 integration_weights: &IntegrationWeights<G, Volume>,
149) -> ElementNodalForcesSolid<N> {
150 stresses
151 .iter()
152 .zip(gradient_vectors.iter().zip(integration_weights))
153 .map(|(stress, (gradient_vectors_point, integration_weight))| {
154 gradient_vectors_point
155 .iter()
156 .map(|gradient_vector| (stress * gradient_vector) * integration_weight)
157 .collect()
158 })
159 .sum()
160}
161
162fn local_residual<C, V>(
164 constitutive_model: &C,
165 deformation_gradient: &DeformationGradient,
166 internal_variables: &V,
167) -> Result<Vector, ConstitutiveError>
168where
169 C: ElasticIV<V>,
170 V: Jacobian + Solution,
171{
172 let mut residual = Vector::zero(internal_variables.size());
173 constitutive_model
174 .internal_variables_residual(deformation_gradient, internal_variables)?
175 .fill_into(&mut residual);
176 Ok(residual)
177}
178
179fn local_block<K>(tangent_vv: &K, size: usize, unmap: &[usize]) -> SquareMatrix
182where
183 K: HessianBlock,
184{
185 let mut block = SquareMatrix::zero(size);
186 tangent_vv.fill_into_block(&mut block, 0, 0);
187 let mut local = SquareMatrix::zero(unmap.len());
188 unmap.iter().enumerate().for_each(|(a, &i)| {
189 unmap
190 .iter()
191 .enumerate()
192 .for_each(|(b, &j)| local[a][b] = block[i][j])
193 });
194 local
195}
196
197fn eliminated_at_point<C, V>(
207 constitutive_model: &C,
208 deformation_gradient: &DeformationGradient,
209 internal_variables: &V,
210) -> Result<FirstPiolaKirchhoffStress, ConstitutiveError>
211where
212 C: ElasticIV<V>,
213 V: Jacobian + Solution,
214{
215 let mut stress = constitutive_model
216 .first_piola_kirchhoff_stress(deformation_gradient, internal_variables)?;
217 let size = internal_variables.size();
218 let unmap = free_indices(constitutive_model, size);
219 let residual = local_residual(constitutive_model, deformation_gradient, internal_variables)?;
220 let mut reduced = Vector::zero(unmap.len());
221 unmap
222 .iter()
223 .enumerate()
224 .for_each(|(a, &i)| reduced[a] = residual[i]);
225 let (_, _, tangent_uv, tangent_vv) =
226 constitutive_model.tangents(deformation_gradient, internal_variables)?;
227 let eliminated = local_block(&tangent_vv, size, &unmap)
228 .solve_lu(&reduced)
229 .map_err(|error| ConstitutiveError::custom(error, deformation_gradient))?;
230 let mut cross = SquareMatrix::zero(size);
231 tangent_uv.fill_into_block(&mut cross, 0, 0);
232 (0..3).for_each(|i| {
233 (0..3).for_each(|j| {
234 stress[i][j] -= unmap
235 .iter()
236 .enumerate()
237 .map(|(a, &v)| Quantity::new(cross[3 * i + j][v] * eliminated[a]))
238 .sum::<Quantity<Stress>>()
239 })
240 });
241 Ok(stress)
242}
243
244fn increment_at_point<C, V>(
251 constitutive_model: &C,
252 deformation_gradient: &DeformationGradient,
253 deformation_gradient_decrement: &DeformationGradient,
254 internal_variables: &V,
255 step: Scalar,
256) -> Result<V, ConstitutiveError>
257where
258 C: ElasticIV<V>,
259 V: Jacobian + Solution,
260{
261 let size = internal_variables.size();
262 let unmap = free_indices(constitutive_model, size);
263 let residual = local_residual(constitutive_model, deformation_gradient, internal_variables)?;
264 let (_, tangent_vu, _, tangent_vv) =
265 constitutive_model.tangents(deformation_gradient, internal_variables)?;
266 let mut coupling = SquareMatrix::zero(size);
267 tangent_vu.fill_into_block(&mut coupling, 0, 0);
268 let mut reduced = Vector::zero(unmap.len());
273 unmap.iter().enumerate().for_each(|(a, &i)| {
274 reduced[a] = residual[i]
275 - (0..3)
276 .map(|k| {
277 (0..3)
278 .map(|l| coupling[i][3 * k + l] * deformation_gradient_decrement[k][l])
279 .sum::<Quantity>()
280 })
281 .sum::<Quantity>()
282 .value()
283 });
284 let solution = local_block(&tangent_vv, size, &unmap)
285 .solve_lu(&reduced)
286 .map_err(|error| ConstitutiveError::custom(error, deformation_gradient))?;
287 let mut decrement = Vector::zero(size);
288 unmap
289 .iter()
290 .enumerate()
291 .for_each(|(a, &i)| decrement[i] = solution[a] * step);
292 let mut incremented = internal_variables.clone();
300 incremented.decrement_from(&decrement);
301 Ok(incremented)
302}
303
304fn condensed_at_point<C, V>(
307 constitutive_model: &C,
308 deformation_gradient: &DeformationGradient,
309 internal_variables: &V,
310) -> Result<FirstPiolaKirchhoffTangentStiffness, ConstitutiveError>
311where
312 C: ElasticIV<V>,
313 V: Tensor,
314{
315 let (tangent_uu, tangent_vu, tangent_uv, tangent_vv) =
316 constitutive_model.tangents(deformation_gradient, internal_variables)?;
317 let size = internal_variables.size();
318 let unmap = free_indices(constitutive_model, size);
319 let factorization = local_block(&tangent_vv, size, &unmap)
320 .factorize_lu()
321 .map_err(|error| ConstitutiveError::custom(error, deformation_gradient))?;
322 let mut coupling = SquareMatrix::zero(size);
323 tangent_vu.fill_into_block(&mut coupling, 0, 0);
324 let mut cross = SquareMatrix::zero(size);
325 tangent_uv.fill_into_block(&mut cross, 0, 0);
326 let mut column = Vector::zero(unmap.len());
327 let mut eliminated = vec![Vector::zero(unmap.len()); size];
328 (0..size).for_each(|c| {
329 unmap
330 .iter()
331 .enumerate()
332 .for_each(|(a, &i)| column[a] = coupling[i][c]);
333 factorization.solve_into(&column, &mut eliminated[c])
334 });
335 let mut condensed = tangent_uu;
336 (0..3).for_each(|i| {
337 (0..3).for_each(|j| {
338 (0..3).for_each(|k| {
339 (0..3).for_each(|l| {
340 condensed[i][j][k][l] -= unmap
341 .iter()
342 .enumerate()
343 .map(|(a, &v)| {
344 Quantity::new(cross[3 * i + j][v] * eliminated[3 * k + l][a])
345 })
346 .sum::<Quantity<Stress>>()
347 })
348 })
349 })
350 });
351 Ok(condensed)
352}
353
354impl<C, const G: usize, const N: usize, const O: usize, const P: usize, V, E>
355 ElasticIVFiniteElement<C, G, 3, N, P, V, E> for Element<3, G, N, O>
356where
357 C: ElasticIV<V>,
358 C::Residual: Erase<Erased = E>,
359 Self: SolidFiniteElement<G, 3, N, P>,
360 V: Erase<Erased = E> + Jacobian + Solution,
361 <V as Tensor>::Unit: UnitDiv<<V as Tensor>::Unit, Output = Dimensionless>,
362 E: Tensor,
363 for<'a> &'a C::Residual: Div<C::TangentVv, Output = V>,
364 for<'a> &'a V: Mul<Quantity<Dimensionless>, Output = V> + Mul<Scalar, Output = V>,
365 for<'a> &'a Matrix: Mul<&'a V, Output = Vector>,
366{
367 fn internal_variables_initial(&self, constitutive_model: &C) -> InternalVariables<G, V> {
368 std::array::from_fn(|_| constitutive_model.internal_variables_initial()).into()
369 }
370 fn internal_variables_root(
371 &self,
372 local_solver: &NewtonRaphson,
373 constitutive_model: &C,
374 nodal_coordinates: &ElementNodalCoordinates<N>,
375 internal_variables: &InternalVariables<G, V>,
376 ) -> Result<InternalVariables<G, V>, FiniteElementError> {
377 self.deformation_gradients(nodal_coordinates)
378 .iter()
379 .zip(internal_variables)
380 .map(|(deformation_gradient, internal_variables_point)| {
381 root_at_point(
382 local_solver,
383 constitutive_model,
384 deformation_gradient,
385 internal_variables_point,
386 )
387 })
388 .collect::<Result<InternalVariables<G, V>, _>>()
389 .map_err(|error| FiniteElementError::upstream(error, self))
390 }
391 fn internal_variables_increment(
392 &self,
393 constitutive_model: &C,
394 nodal_coordinates: &ElementNodalCoordinates<N>,
395 internal_variables: &InternalVariables<G, V>,
396 nodal_decrement: &ElementNodalCoordinates<N>,
397 step: Scalar,
398 ) -> Result<InternalVariables<G, V>, FiniteElementError> {
399 self.deformation_gradients(nodal_coordinates)
400 .iter()
401 .zip(self.deformation_gradients(nodal_decrement).iter())
402 .zip(internal_variables)
403 .map(
404 |((deformation_gradient, decrement), internal_variables_point)| {
405 increment_at_point(
406 constitutive_model,
407 deformation_gradient,
408 decrement,
409 internal_variables_point,
410 step,
411 )
412 },
413 )
414 .collect::<Result<InternalVariables<G, V>, _>>()
415 .map_err(|error| FiniteElementError::upstream(error, self))
416 }
417 fn nodal_forces(
418 &self,
419 constitutive_model: &C,
420 nodal_coordinates: &ElementNodalCoordinates<N>,
421 internal_variables: &InternalVariables<G, V>,
422 ) -> Result<ElementNodalForcesSolid<N>, FiniteElementError> {
423 let stresses = self
424 .deformation_gradients(nodal_coordinates)
425 .iter()
426 .zip(internal_variables)
427 .map(|(deformation_gradient, internal_variables_point)| {
428 constitutive_model
429 .first_piola_kirchhoff_stress(deformation_gradient, internal_variables_point)
430 })
431 .collect::<Result<FirstPiolaKirchhoffStressList<G>, _>>()
432 .map_err(|error| FiniteElementError::upstream(error, self))?;
433 Ok(assemble_forces(
434 stresses,
435 self.gradient_vectors(),
436 self.integration_weights(),
437 ))
438 }
439 fn nodal_forces_eliminated(
440 &self,
441 constitutive_model: &C,
442 nodal_coordinates: &ElementNodalCoordinates<N>,
443 internal_variables: &InternalVariables<G, V>,
444 ) -> Result<ElementNodalForcesSolid<N>, FiniteElementError> {
445 let stresses = self
446 .deformation_gradients(nodal_coordinates)
447 .iter()
448 .zip(internal_variables)
449 .map(|(deformation_gradient, internal_variables_point)| {
450 eliminated_at_point(
451 constitutive_model,
452 deformation_gradient,
453 internal_variables_point,
454 )
455 })
456 .collect::<Result<FirstPiolaKirchhoffStressList<G>, _>>()
457 .map_err(|error| FiniteElementError::upstream(error, self))?;
458 Ok(assemble_forces(
459 stresses,
460 self.gradient_vectors(),
461 self.integration_weights(),
462 ))
463 }
464 fn nodal_stiffnesses(
465 &self,
466 constitutive_model: &C,
467 nodal_coordinates: &ElementNodalCoordinates<N>,
468 internal_variables: &InternalVariables<G, V>,
469 ) -> Result<ElementNodalStiffnessesSolid<N>, FiniteElementError> {
470 let condensed = self
471 .deformation_gradients(nodal_coordinates)
472 .iter()
473 .zip(internal_variables)
474 .map(|(deformation_gradient, internal_variables_point)| {
475 condensed_at_point(
476 constitutive_model,
477 deformation_gradient,
478 internal_variables_point,
479 )
480 })
481 .collect::<Result<FirstPiolaKirchhoffTangentStiffnessList<G>, _>>()
482 .map_err(|error| FiniteElementError::upstream(error, self))?;
483 Ok(condensed
484 .iter()
485 .zip(
486 self.gradient_vectors()
487 .iter()
488 .zip(self.integration_weights()),
489 )
490 .map(|(tangent, (gradient_vectors, integration_weight))| {
491 gradient_vectors
492 .iter()
493 .map(|gradient_vector_a| {
494 gradient_vectors
495 .iter()
496 .map(|gradient_vector_b| {
497 tangent.contract_second_fourth_with_first(
498 gradient_vector_a,
499 gradient_vector_b,
500 ) * integration_weight
501 })
502 .collect()
503 })
504 .collect()
505 })
506 .sum())
507 }
508}