#[cfg(test)]
pub mod test;
use crate::{
constitutive::{ConstitutiveError, thermal::conduction::ThermalConduction},
fem::block::element::{
Element, FiniteElement, FiniteElementError,
thermal::{ElementNodalTemperatures, ThermalFiniteElement},
},
math::{Scalar, Tensor, TensorRank0List, TensorRank0List2D},
mechanics::{HeatFluxTangents, HeatFluxes},
};
pub type ElementNodalForcesThermal<const D: usize> = TensorRank0List<D>;
pub type ElementNodalStiffnessesThermal<const D: usize> = TensorRank0List2D<D>;
pub trait ThermalConductionFiniteElement<
C,
const G: usize,
const M: usize,
const N: usize,
const P: usize,
> where
C: ThermalConduction,
Self: ThermalFiniteElement<G, M, N, P>,
{
fn potential(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<Scalar, FiniteElementError>;
fn nodal_forces(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<ElementNodalForcesThermal<N>, FiniteElementError>;
fn nodal_stiffnesses(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<ElementNodalStiffnessesThermal<N>, FiniteElementError>;
}
impl<C, const G: usize, const M: usize, const N: usize, const O: usize, const P: usize>
ThermalConductionFiniteElement<C, G, M, N, P> for Element<G, N, O>
where
C: ThermalConduction,
Self: ThermalFiniteElement<G, M, N, P>,
{
fn potential(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<Scalar, FiniteElementError> {
match self
.temperature_gradients(nodal_temperatures)
.iter()
.zip(self.integration_weights())
.map(|(temperature_gradient, integration_weight)| {
Ok::<_, ConstitutiveError>(
constitutive_model.potential(temperature_gradient)? * integration_weight,
)
})
.sum()
{
Ok(potential) => Ok(potential),
Err(error) => Err(FiniteElementError::Upstream(
format!("{error}"),
format!("{self:?}"),
)),
}
}
fn nodal_forces(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<ElementNodalForcesThermal<N>, FiniteElementError> {
match self
.temperature_gradients(nodal_temperatures)
.iter()
.map(|temperature_gradient| constitutive_model.heat_flux(temperature_gradient))
.collect::<Result<HeatFluxes<G>, _>>()
{
Ok(heat_fluxes) => Ok(heat_fluxes
.iter()
.zip(
self.gradient_vectors()
.iter()
.zip(self.integration_weights()),
)
.map(|(heat_flux, (gradient_vectors, integration_weight))| {
gradient_vectors
.iter()
.map(|gradient_vector| -(heat_flux * gradient_vector) * integration_weight)
.collect()
})
.sum()),
Err(error) => Err(FiniteElementError::Upstream(
format!("{error}"),
format!("{self:?}"),
)),
}
}
fn nodal_stiffnesses(
&self,
constitutive_model: &C,
nodal_temperatures: &ElementNodalTemperatures<N>,
) -> Result<ElementNodalStiffnessesThermal<N>, FiniteElementError> {
match self
.temperature_gradients(nodal_temperatures)
.iter()
.map(|temperature_gradient| constitutive_model.heat_flux_tangent(temperature_gradient))
.collect::<Result<HeatFluxTangents<G>, _>>()
{
Ok(heat_flux_tangents) => Ok(heat_flux_tangents
.iter()
.zip(
self.gradient_vectors()
.iter()
.zip(self.integration_weights()),
)
.map(
|(heat_flux_tangent, (gradient_vectors, integration_weight))| {
gradient_vectors
.iter()
.map(|gradient_vector_a| {
gradient_vectors
.iter()
.map(|gradient_vector_b| {
-(gradient_vector_a
* (heat_flux_tangent * gradient_vector_b))
* integration_weight
})
.collect()
})
.collect()
},
)
.sum()),
Err(error) => Err(FiniteElementError::Upstream(
format!("{error}"),
format!("{self:?}"),
)),
}
}
}