use crate::allocators::{BiDimAllocator, TriDimAllocator};
use crate::assembly::global::assemble_scalar;
use crate::assembly::local::QuadratureTable;
use crate::element::VolumetricFiniteElement;
use crate::integrate::dependency::DependsOnGrad;
use crate::integrate::{
integrate_over_element, integrate_over_volume_element, ElementIntegralAssemblerBuilder, FnFunction,
IntegrationWorkspace, UFunction, UGradFunction,
};
use crate::nalgebra::DVectorView;
use crate::nalgebra::{DefaultAllocator, OPoint, OVector};
use crate::space::{InterpolateGradientInSpace, InterpolateInSpace, VolumetricFiniteElementSpace};
use crate::{Real, SmallDim};
use nalgebra::{OMatrix, Scalar, Vector1, U1};
pub trait SolutionFunction<T, GeometryDim, SolutionDim>
where
T: Scalar,
GeometryDim: SmallDim,
SolutionDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
fn evaluate(&self, x: &OPoint<T, GeometryDim>) -> OVector<T, SolutionDim>;
}
impl<T, F, GeometryDim, SolutionDim> SolutionFunction<T, GeometryDim, SolutionDim> for F
where
T: Scalar,
F: Fn(&OPoint<T, GeometryDim>) -> OVector<T, SolutionDim>,
GeometryDim: SmallDim,
SolutionDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
fn evaluate(&self, x: &OPoint<T, GeometryDim>) -> OVector<T, SolutionDim> {
self(x)
}
}
pub trait SolutionGradient<T, GeometryDim, SolutionDim>
where
T: Scalar,
GeometryDim: SmallDim,
SolutionDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
fn evaluate_grad(&self, x: &OPoint<T, GeometryDim>) -> OMatrix<T, GeometryDim, SolutionDim>;
}
impl<T, F, GeometryDim, SolutionDim> SolutionGradient<T, GeometryDim, SolutionDim> for F
where
T: Scalar,
F: Fn(&OPoint<T, GeometryDim>) -> OMatrix<T, GeometryDim, SolutionDim>,
GeometryDim: SmallDim,
SolutionDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
fn evaluate_grad(&self, x: &OPoint<T, GeometryDim>) -> OMatrix<T, GeometryDim, SolutionDim> {
self(x)
}
}
pub struct SpaceInterpolationFn<'a, Space, Weights>(pub &'a Space, pub Weights);
impl<'a, T, Space, Weights, SolutionDim> SolutionFunction<T, Space::GeometryDim, SolutionDim>
for SpaceInterpolationFn<'a, Space, Weights>
where
T: Real,
SolutionDim: SmallDim,
Space: InterpolateInSpace<T, SolutionDim>,
Weights: Copy + Into<DVectorView<'a, T>>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
fn evaluate(&self, x: &OPoint<T, Space::GeometryDim>) -> OVector<T, SolutionDim> {
self.0.interpolate_at_point(x, self.1.into())
}
}
impl<'a, T, Space, Weights, SolutionDim> SolutionGradient<T, Space::GeometryDim, SolutionDim>
for SpaceInterpolationFn<'a, Space, Weights>
where
T: Real,
SolutionDim: SmallDim,
Space: InterpolateGradientInSpace<T, SolutionDim>,
Weights: Copy + Into<DVectorView<'a, T>>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
fn evaluate_grad(&self, x: &OPoint<T, Space::GeometryDim>) -> OMatrix<T, Space::GeometryDim, SolutionDim> {
self.0.interpolate_gradient_at_point(x, self.1.into())
}
}
#[allow(non_snake_case)]
pub fn estimate_element_L2_error_squared<T, Element, SolutionDim>(
element: &Element,
u: &impl SolutionFunction<T, Element::GeometryDim, SolutionDim>,
u_h_element: DVectorView<T>,
quadrature_weights: &[T],
quadrature_points: &[OPoint<T, Element::ReferenceDim>],
workspace: &mut IntegrationWorkspace<T>,
) -> T
where
T: Real,
Element: VolumetricFiniteElement<T>,
SolutionDim: SmallDim,
DefaultAllocator: TriDimAllocator<T, Element::GeometryDim, Element::ReferenceDim, SolutionDim>,
{
let n = element.num_nodes();
assert_eq!(u_h_element.len(), n * SolutionDim::dim());
let result_as_vector = integrate_over_element(
&make_L2_error_squared_integrand(u),
element,
(quadrature_weights, quadrature_points),
u_h_element,
workspace,
);
result_as_vector[0]
}
#[allow(non_snake_case)]
pub fn estimate_element_H1_seminorm_error_squared<T, Element, SolutionDim>(
element: &Element,
u_grad: &impl SolutionGradient<T, Element::GeometryDim, SolutionDim>,
u_h_element: DVectorView<T>,
quadrature_weights: &[T],
quadrature_points: &[OPoint<T, Element::ReferenceDim>],
workspace: &mut IntegrationWorkspace<T>,
) -> T
where
T: Real,
Element: VolumetricFiniteElement<T>,
SolutionDim: SmallDim,
DefaultAllocator: TriDimAllocator<T, Element::GeometryDim, Element::ReferenceDim, SolutionDim>,
{
let n = element.num_nodes();
assert_eq!(u_h_element.len(), n * SolutionDim::dim());
let result_as_vector: Vector1<T> = integrate_over_volume_element(
&make_H1_seminorm_error_squared_integrand(u_grad),
element,
(quadrature_weights, quadrature_points),
u_h_element,
workspace,
)
.expect("TODO: Handle the case where this might fail (due to e.g. singular Jacobian)");
result_as_vector[0]
}
#[allow(non_snake_case)]
pub fn estimate_element_H1_seminorm_error<T, Element, SolutionDim>(
element: &Element,
u_grad: &impl SolutionGradient<T, Element::GeometryDim, SolutionDim>,
u_h_element: DVectorView<T>,
quadrature_weights: &[T],
quadrature_points: &[OPoint<T, Element::ReferenceDim>],
workspace: &mut IntegrationWorkspace<T>,
) -> T
where
T: Real,
Element: VolumetricFiniteElement<T>,
SolutionDim: SmallDim,
DefaultAllocator: TriDimAllocator<T, Element::GeometryDim, Element::ReferenceDim, SolutionDim>,
{
estimate_element_H1_seminorm_error_squared(
element,
u_grad,
u_h_element,
quadrature_weights,
quadrature_points,
workspace,
)
.sqrt()
}
#[allow(non_snake_case)]
pub fn estimate_element_L2_error<T, Element, SolutionDim>(
element: &Element,
u: &impl SolutionFunction<T, Element::GeometryDim, SolutionDim>,
u_h_element: DVectorView<T>,
quadrature_weights: &[T],
quadrature_points: &[OPoint<T, Element::ReferenceDim>],
workspace: &mut IntegrationWorkspace<T>,
) -> T
where
T: Real,
Element: VolumetricFiniteElement<T>,
SolutionDim: SmallDim,
DefaultAllocator: TriDimAllocator<T, Element::GeometryDim, Element::ReferenceDim, SolutionDim>,
{
estimate_element_L2_error_squared(
element,
u,
u_h_element,
quadrature_weights,
quadrature_points,
workspace,
)
.sqrt()
}
#[allow(non_snake_case)]
fn make_L2_error_squared_integrand<'a, T, SolutionDim, GeometryDim>(
u: &'a (impl SolutionFunction<T, GeometryDim, SolutionDim> + ?Sized),
) -> impl 'a + UFunction<T, GeometryDim, SolutionDim, OutputDim = U1>
where
T: Real,
SolutionDim: SmallDim,
GeometryDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
let function = move |x: &OPoint<T, GeometryDim>, u_h: &OVector<T, SolutionDim>| {
let u_at_x = u.evaluate(&x);
let error = u_h - u_at_x;
Vector1::new(error.norm_squared())
};
FnFunction::new(function)
}
#[allow(non_snake_case)]
fn make_H1_seminorm_error_squared_integrand<'a, T, SolutionDim, GeometryDim>(
u_grad: &'a impl SolutionGradient<T, GeometryDim, SolutionDim>,
) -> impl 'a + UGradFunction<T, GeometryDim, SolutionDim, OutputDim = U1>
where
T: Real,
SolutionDim: SmallDim,
GeometryDim: SmallDim,
DefaultAllocator: BiDimAllocator<T, GeometryDim, SolutionDim>,
{
let function = move |x: &OPoint<T, GeometryDim>, u_h_grad: &OMatrix<T, GeometryDim, SolutionDim>| {
let u_grad_at_x = u_grad.evaluate_grad(&x);
let error = u_h_grad - u_grad_at_x;
Vector1::new(error.norm_squared())
};
FnFunction::new(function).with_dependencies::<DependsOnGrad>()
}
#[allow(non_snake_case)]
pub fn estimate_L2_error_squared<'a, T, SolutionDim, Space, QTable>(
space: &Space,
u: &(impl SolutionFunction<T, Space::GeometryDim, SolutionDim> + ?Sized),
u_h: impl Into<DVectorView<'a, T>>,
qtable: &QTable,
) -> eyre::Result<T>
where
T: Real,
SolutionDim: SmallDim,
Space: VolumetricFiniteElementSpace<T>,
QTable: QuadratureTable<T, Space::ReferenceDim>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
let assembler = ElementIntegralAssemblerBuilder::new()
.with_space(space)
.with_quadrature_table(qtable)
.with_interpolation_weights(u_h.into())
.with_integrand(make_L2_error_squared_integrand(u))
.build_integrator();
assemble_scalar(&assembler)
}
#[allow(non_snake_case)]
pub fn estimate_L2_error<'a, T, SolutionDim, Space, QTable>(
space: &Space,
u: &(impl SolutionFunction<T, Space::GeometryDim, SolutionDim> + ?Sized),
u_h: impl Into<DVectorView<'a, T>>,
qtable: &QTable,
) -> eyre::Result<T>
where
T: Real,
SolutionDim: SmallDim,
Space: VolumetricFiniteElementSpace<T>,
QTable: QuadratureTable<T, Space::ReferenceDim>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
Ok(estimate_L2_error_squared(space, u, u_h, qtable)?.sqrt())
}
#[allow(non_snake_case)]
pub fn estimate_H1_seminorm_error_squared<'a, T, SolutionDim, Space, QTable>(
space: &Space,
u_grad: &impl SolutionGradient<T, Space::GeometryDim, SolutionDim>,
u_h: impl Into<DVectorView<'a, T>>,
qtable: &QTable,
) -> eyre::Result<T>
where
T: Real,
SolutionDim: SmallDim,
Space: VolumetricFiniteElementSpace<T>,
QTable: QuadratureTable<T, Space::ReferenceDim>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
let assembler = ElementIntegralAssemblerBuilder::new()
.with_space(space)
.with_quadrature_table(qtable)
.with_interpolation_weights(u_h.into())
.with_integrand(make_H1_seminorm_error_squared_integrand(u_grad))
.build_volume_integrator();
assemble_scalar(&assembler)
}
#[allow(non_snake_case)]
pub fn estimate_H1_seminorm_error<'a, T, SolutionDim, Space, QTable>(
space: &Space,
u_grad: &impl SolutionGradient<T, Space::GeometryDim, SolutionDim>,
u_h: impl Into<DVectorView<'a, T>>,
qtable: &QTable,
) -> eyre::Result<T>
where
T: Real,
SolutionDim: SmallDim,
Space: VolumetricFiniteElementSpace<T>,
QTable: QuadratureTable<T, Space::ReferenceDim>,
DefaultAllocator: TriDimAllocator<T, Space::GeometryDim, Space::ReferenceDim, SolutionDim>,
{
estimate_H1_seminorm_error_squared(space, u_grad, u_h, qtable).map(|err2| err2.sqrt())
}