use crate::allocators::{BiDimAllocator, DimAllocator};
use crate::connectivity::Connectivity;
use crate::nalgebra::MatrixViewMut;
use crate::{Real, SmallDim};
use fenris_geometry::AxisAlignedBoundingBox;
use fenris_optimize::newton::NewtonSettings;
use nalgebra::allocator::Allocator;
use nalgebra::OPoint;
use nalgebra::{DVectorView, DVectorViewMut, DimName, Dyn};
use nalgebra::{DefaultAllocator, DimMin, OMatrix, OVector, Scalar, U1};
use num::Zero;
use numeric_literals::replace_float_literals;
use std::error::Error;
use std::fmt::Debug;
mod hexahedron;
mod quadrilateral;
mod segment;
mod tetrahedron;
mod triangle;
pub use hexahedron::*;
pub use quadrilateral::*;
pub use segment::*;
pub use tetrahedron::*;
pub use triangle::*;
pub trait ReferenceFiniteElement<T>
where
T: Scalar,
DefaultAllocator: DimAllocator<T, Self::ReferenceDim>,
{
type ReferenceDim: SmallDim;
fn num_nodes(&self) -> usize;
fn populate_basis(&self, basis_values: &mut [T], reference_coords: &OPoint<T, Self::ReferenceDim>);
fn populate_basis_gradients(
&self,
basis_gradients: MatrixViewMut<T, Self::ReferenceDim, Dyn>,
reference_coords: &OPoint<T, Self::ReferenceDim>,
);
}
pub trait FixedNodesReferenceFiniteElement<T>
where
T: Scalar,
DefaultAllocator: DimAllocator<T, Self::ReferenceDim>
+ Allocator<T, U1, Self::NodalDim>
+ Allocator<T, Self::ReferenceDim, Self::NodalDim>,
{
type ReferenceDim: SmallDim;
type NodalDim: SmallDim;
fn evaluate_basis(&self, reference_coords: &OPoint<T, Self::ReferenceDim>) -> OMatrix<T, U1, Self::NodalDim>;
fn gradients(
&self,
reference_coords: &OPoint<T, Self::ReferenceDim>,
) -> OMatrix<T, Self::ReferenceDim, Self::NodalDim>;
}
macro_rules! impl_reference_finite_element_for_fixed {
($element:ty) => {
impl<T> ReferenceFiniteElement<T> for $element
where
T: Scalar,
$element: FixedNodesReferenceFiniteElement<T>,
DefaultAllocator: BiDimAllocator<
T,
<$element as FixedNodesReferenceFiniteElement<T>>::NodalDim,
<$element as FixedNodesReferenceFiniteElement<T>>::ReferenceDim,
>,
{
type ReferenceDim = <Self as FixedNodesReferenceFiniteElement<T>>::ReferenceDim;
fn num_nodes(&self) -> usize {
use nalgebra::DimName;
<Self as FixedNodesReferenceFiniteElement<T>>::NodalDim::dim()
}
fn populate_basis(&self, result: &mut [T], reference_coords: &OPoint<T, Self::ReferenceDim>) {
let basis_values = <Self as crate::element::FixedNodesReferenceFiniteElement<T>>::evaluate_basis(
self,
reference_coords,
);
result.clone_from_slice(&basis_values.as_slice());
}
fn populate_basis_gradients(
&self,
mut result: nalgebra::MatrixViewMut<T, Self::ReferenceDim, nalgebra::Dyn>,
reference_coords: &OPoint<T, Self::ReferenceDim>,
) {
let gradients =
<Self as crate::element::FixedNodesReferenceFiniteElement<T>>::gradients(self, reference_coords);
result.copy_from(&gradients);
}
}
};
}
impl_reference_finite_element_for_fixed!(Tri3d2Element<T>);
impl_reference_finite_element_for_fixed!(Tri6d2Element<T>);
impl_reference_finite_element_for_fixed!(Quad4d2Element<T>);
impl_reference_finite_element_for_fixed!(Quad9d2Element<T>);
impl_reference_finite_element_for_fixed!(Segment2d1Element<T>);
impl_reference_finite_element_for_fixed!(Segment2d2Element<T>);
impl_reference_finite_element_for_fixed!(Tet4Element<T>);
impl_reference_finite_element_for_fixed!(Hex8Element<T>);
impl_reference_finite_element_for_fixed!(Hex27Element<T>);
impl_reference_finite_element_for_fixed!(Hex20Element<T>);
impl_reference_finite_element_for_fixed!(Tri3d3Element<T>);
impl_reference_finite_element_for_fixed!(Tet10Element<T>);
impl_reference_finite_element_for_fixed!(Tet20Element<T>);
pub trait FiniteElement<T>: ReferenceFiniteElement<T>
where
T: Scalar,
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
type GeometryDim: SmallDim;
fn reference_jacobian(
&self,
reference_coords: &OPoint<T, Self::ReferenceDim>,
) -> OMatrix<T, Self::GeometryDim, Self::ReferenceDim>;
fn map_reference_coords(&self, reference_coords: &OPoint<T, Self::ReferenceDim>) -> OPoint<T, Self::GeometryDim>;
fn diameter(&self) -> T;
}
pub trait ElementConnectivity<T>: Debug + Connectivity
where
T: Scalar,
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
type Element: FiniteElement<T, GeometryDim = Self::GeometryDim, ReferenceDim = Self::ReferenceDim>;
type GeometryDim: SmallDim;
type ReferenceDim: SmallDim;
fn element(&self, all_vertices: &[OPoint<T, Self::GeometryDim>]) -> Option<Self::Element>;
fn populate_element_variables<'a, SolutionDim>(
&self,
mut u_local: MatrixViewMut<T, SolutionDim, Dyn>,
u_global: impl Into<DVectorView<'a, T>>,
) where
T: Zero,
SolutionDim: DimName,
{
let u_global = u_global.into();
let indices = self.vertex_indices();
let sol_dim = SolutionDim::dim();
for (i_local, i_global) in indices.iter().enumerate() {
u_local
.index_mut((.., i_local))
.copy_from(&u_global.index((sol_dim * i_global..sol_dim * i_global + sol_dim, ..)));
}
}
}
pub trait VolumetricFiniteElement<T>: FiniteElement<T, ReferenceDim = <Self as FiniteElement<T>>::GeometryDim>
where
T: Scalar,
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
}
impl<T, E> VolumetricFiniteElement<T> for E
where
T: Scalar,
E: FiniteElement<T, ReferenceDim = <Self as FiniteElement<T>>::GeometryDim>,
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
}
pub trait SurfaceFiniteElement<T>: FiniteElement<T>
where
T: Scalar,
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
fn normal(&self, xi: &OPoint<T, Self::ReferenceDim>) -> OVector<T, Self::GeometryDim>;
}
pub type ElementForConnectivity<T, Connectivity> = <Connectivity as ElementConnectivity<T>>::Element;
pub type ConnectivityGeometryDim<T, Conn> = <Conn as ElementConnectivity<T>>::GeometryDim;
pub type ConnectivityReferenceDim<T, Conn> = <Conn as ElementConnectivity<T>>::ReferenceDim;
pub type ElementGeometryDim<T, Element> = <Element as FiniteElement<T>>::GeometryDim;
#[replace_float_literals(T::from_f64(literal).unwrap())]
#[inline(always)]
fn phi_linear_1d<T>(alpha: T, xi: T) -> T
where
T: Real,
{
(1.0 + alpha * xi) / 2.0
}
#[replace_float_literals(T::from_f64(literal).unwrap())]
#[inline(always)]
fn phi_linear_1d_grad<T>(alpha: T) -> T
where
T: Real,
{
alpha / 2.0
}
#[replace_float_literals(T::from_f64(literal).unwrap())]
#[inline(always)]
fn phi_quadratic_1d<T>(alpha: T, xi: T) -> T
where
T: Real,
{
let alpha2 = alpha * alpha;
let xi2 = xi * xi;
(3.0 / 2.0 * alpha2 - 1.0) * xi2 + 0.5 * alpha * xi + 1.0 - alpha2
}
#[replace_float_literals(T::from_f64(literal).unwrap())]
#[inline(always)]
fn phi_quadratic_1d_grad<T>(alpha: T, xi: T) -> T
where
T: Real,
{
let alpha2 = alpha * alpha;
2.0 * (3.0 / 2.0 * alpha2 - 1.0) * xi + 0.5 * alpha
}
pub fn map_physical_coordinates<T, Element, GeometryDim>(
element: &Element,
x: &OPoint<T, GeometryDim>,
) -> Result<OPoint<T, GeometryDim>, Box<dyn Error>>
where
T: Real,
Element: FiniteElement<T, GeometryDim = GeometryDim, ReferenceDim = GeometryDim>,
GeometryDim: DimName + DimMin<GeometryDim, Output = GeometryDim>,
DefaultAllocator: DimAllocator<T, GeometryDim>,
{
use fenris_optimize::calculus::VectorFunctionBuilder;
use fenris_optimize::newton::newton;
let f = VectorFunctionBuilder::with_dimension(GeometryDim::dim())
.with_function(|f, xi| {
let xi = OPoint::from(
xi.generic_view((0, 0), (GeometryDim::name(), U1::name()))
.clone_owned(),
);
f.copy_from(&(element.map_reference_coords(&xi).coords - &x.coords));
})
.with_jacobian_solver(
|sol: &mut DVectorViewMut<T>, xi: &DVectorView<T>, rhs: &DVectorView<T>| {
let xi = OPoint::from(
xi.generic_view((0, 0), (GeometryDim::name(), U1::name()))
.clone_owned(),
);
let j = element.reference_jacobian(&xi);
let lu = j.full_piv_lu();
sol.copy_from(rhs);
if lu.solve_mut(sol) {
Ok(())
} else {
Err(Box::<dyn Error>::from(
"LU decomposition failed. Jacobian not invertible?",
))
}
},
);
let settings = NewtonSettings {
max_iterations: Some(20),
tolerance: T::from_f64(1e-12).unwrap() * element.diameter(),
};
let mut xi = OVector::<T, GeometryDim>::zeros();
let mut f_val = OVector::<T, GeometryDim>::zeros();
let mut dx = OVector::<T, GeometryDim>::zeros();
macro_rules! slice {
($e:expr) => {
$e.generic_view_with_steps_mut((0, 0), (GeometryDim::name(), U1::name()), (0, 0))
};
}
newton(f, &mut slice!(xi), &mut slice!(f_val), &mut slice!(dx), settings)?;
Ok(OPoint::from(xi))
}
#[allow(non_snake_case)]
pub fn project_physical_coordinates<T, Element>(
element: &Element,
x: &OPoint<T, Element::GeometryDim>,
) -> Result<OPoint<T, Element::ReferenceDim>, Box<dyn Error>>
where
T: Real,
Element: FiniteElement<T>,
Element::ReferenceDim: DimName + DimMin<Element::ReferenceDim, Output = Element::ReferenceDim>,
DefaultAllocator: BiDimAllocator<T, Element::GeometryDim, Element::ReferenceDim>,
{
assert!(
Element::ReferenceDim::dim() <= Element::GeometryDim::dim(),
"ReferenceDim must be smaller or equal to GeometryDim."
);
let tolerance = T::from_f64(1e-12).unwrap() * element.diameter();
let x = &x.coords;
let mut xi = OPoint::<T, Element::ReferenceDim>::origin();
let mut f = element.map_reference_coords(&xi).coords;
let mut j = element.reference_jacobian(&xi);
let mut jT = j.transpose();
let mut iter = 0;
while (&jT * (&f - x)).norm() > tolerance {
let jTj = &jT * j;
let lu = jTj.full_piv_lu();
let rhs = -jT * (&f - x);
if let Some(sol) = lu.solve(&rhs) {
xi += sol;
} else {
return Err(Box::from(
"LU decomposition failed. Normal equation for Jacobian not invertible?",
));
}
f = element.map_reference_coords(&xi).coords;
j = element.reference_jacobian(&xi);
jT = j.transpose();
iter += 1;
if iter > 1000 {
eprintln!("Exceeded 1000 iterations for project_physical_coordinates");
}
}
Ok(OPoint::from(xi))
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClosestPoint<T, D>
where
T: Scalar,
D: DimName,
DefaultAllocator: Allocator<T, D>,
{
InElement(OPoint<T, D>),
ClosestPoint(OPoint<T, D>),
}
impl<T, D> ClosestPoint<T, D>
where
T: Scalar,
D: DimName,
DefaultAllocator: Allocator<T, D>,
{
pub fn point(&self) -> &OPoint<T, D> {
match self {
ClosestPoint::InElement(point) | ClosestPoint::ClosestPoint(point) => point,
}
}
}
pub trait ClosestPointInElement<T: Scalar>: FiniteElement<T>
where
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
fn closest_point(&self, p: &OPoint<T, Self::GeometryDim>) -> ClosestPoint<T, Self::ReferenceDim>;
}
pub trait BoundsForElement<T: Scalar>: FiniteElement<T>
where
DefaultAllocator: BiDimAllocator<T, Self::GeometryDim, Self::ReferenceDim>,
{
fn element_bounds(&self) -> AxisAlignedBoundingBox<T, Self::GeometryDim>;
}