pub mod hierarchical_basis_fns;
use super::domain::mesh::{
elem::Elem,
space::{M2D, V2D},
};
use crate::fem_problem::integration::glq::{gauss_quadrature_points, scale_gauss_quad_points};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
pub trait HierCurlBasisFnSpace: Clone + Sync + Send + std::fmt::Debug {
fn with(max_order: usize, points: &[f64], compute_d2: bool) -> Self;
fn norm(&self, n: usize, p: usize) -> f64;
fn norm_d1(&self, n: usize, p: usize) -> f64;
fn norm_d2(&self, n: usize, p: usize) -> f64;
fn tang(&self, n: usize, p: usize) -> f64;
fn tang_d1(&self, n: usize, p: usize) -> f64;
fn tang_d2(&self, n: usize, p: usize) -> f64;
}
pub struct BasisFnSampler<B: HierBasisFn> {
pub i_max: usize,
pub j_max: usize,
pub compute_d2: bool,
u_points: Vec<f64>,
v_points: Vec<f64>,
computed: Arc<Mutex<HashMap<BSDescription, Arc<B>>>>,
}
impl<B: HierBasisFn> BasisFnSampler<B> {
pub fn with(
i_max: usize,
j_max: usize,
num_u_points: Option<usize>,
num_v_points: Option<usize>,
compute_2nd_derivs: bool,
) -> (Self, [Vec<f64>; 2]) {
let (u_points, u_weights) = gauss_quadrature_points(
num_u_points.unwrap_or_else(|| default_ngq(i_max)),
compute_2nd_derivs,
);
let (v_points, v_weights) = gauss_quadrature_points(
num_v_points.unwrap_or_else(|| default_ngq(j_max)),
compute_2nd_derivs,
);
(
Self {
i_max,
j_max,
compute_d2: compute_2nd_derivs,
u_points,
v_points,
computed: Arc::new(Mutex::new(HashMap::new())),
},
[u_weights, v_weights],
)
}
pub fn sample_basis_fn(&mut self, elem: &Elem, over_desc_elem: Option<&Elem>) -> Arc<B> {
let desc = BSDescription::new(elem, over_desc_elem);
match self.computed.lock() {
Ok(mut comp_guard) => {
if let Some(computed_bs) = comp_guard.get(&desc) {
computed_bs.clone()
} else {
let bs = B::defined_over(
elem,
over_desc_elem,
[&self.u_points, &self.v_points],
[self.i_max, self.j_max],
self.compute_d2,
);
comp_guard.insert(desc.clone(), Arc::new(bs));
comp_guard.get(&desc).unwrap().clone()
}
}
Err(_) => Arc::new(B::defined_over(
elem,
over_desc_elem,
[&self.u_points, &self.v_points],
[self.i_max, self.j_max],
self.compute_d2,
)),
}
}
}
impl<B: HierBasisFn> Clone for BasisFnSampler<B> {
fn clone(&self) -> Self {
Self {
i_max: self.i_max,
j_max: self.j_max,
compute_d2: self.compute_d2,
u_points: self.u_points.clone(),
v_points: self.v_points.clone(),
computed: self.computed.clone(),
}
}
}
#[derive(Hash, PartialEq, Eq, Clone, Debug)]
struct BSDescription {
space: [usize; 2],
sample: Option<[usize; 2]>,
base_id: usize,
desc_id: Option<usize>,
}
impl BSDescription {
pub fn new(elem: &Elem, sampled_over: Option<&Elem>) -> Self {
Self {
space: [elem.nodes[0], elem.nodes[3]],
sample: sampled_over.map(|so_elem| [so_elem.nodes[0], so_elem.nodes[3]]),
base_id: elem.id,
desc_id: sampled_over.map(|so_elem| so_elem.id),
}
}
}
fn default_ngq(max_order: usize) -> usize {
let conv = (max_order * 4) as f32;
let conv_p2 = conv.log2().ceil() as i32;
(2.0_f32).powi(conv_p2).round() as usize
}
pub trait HierBasisFn {
fn defined_over(
elem: &Elem,
desc_elem: Option<&Elem>,
uv_points: [&[f64]; 2],
ij_orders: [usize; 2],
compute_d2: bool,
) -> Self;
}
#[derive(Clone, Debug)]
pub struct HierCurlBasisFn<BSpace: HierCurlBasisFnSpace> {
pub jac: Vec<Vec<M2D>>,
pub jac_inv: Vec<Vec<M2D>>,
pub det_jac: Vec<Vec<f64>>,
pub para_scale: V2D,
u_shapes: BSpace,
v_shapes: BSpace,
}
impl<BSpace: HierCurlBasisFnSpace> HierCurlBasisFn<BSpace> {
pub fn f_u(&self, [i, j]: [usize; 2], [m, n]: [usize; 2]) -> V2D {
self.jac_inv[m][n].u * self.u_shapes.norm(i, m) * self.v_shapes.tang(j, n)
}
pub fn f_v(&self, [i, j]: [usize; 2], [m, n]: [usize; 2]) -> V2D {
self.jac_inv[m][n].v * self.u_shapes.tang(i, m) * self.v_shapes.norm(j, n)
}
pub fn f_u_d1(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].u
* V2D::from([
self.u_shapes.norm(i, m) * self.v_shapes.tang_d1(j, n),
self.u_shapes.norm_d1(i, m) * self.v_shapes.tang(j, n),
])
* para_scale
}
pub fn f_v_d1(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].v
* V2D::from([
self.u_shapes.tang(i, m) * self.v_shapes.norm_d1(j, n),
self.u_shapes.tang_d1(i, m) * self.v_shapes.norm(j, n),
])
* para_scale
}
pub fn f_u_d2(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].u
* V2D::from([
self.u_shapes.norm(i, m) * self.v_shapes.tang_d2(j, n),
self.u_shapes.norm_d2(i, m) * self.v_shapes.tang(j, n),
])
* para_scale
* para_scale
}
pub fn f_v_d2(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].v
* V2D::from([
self.u_shapes.tang(i, m) * self.v_shapes.norm_d2(j, n),
self.u_shapes.tang_d2(i, m) * self.v_shapes.norm(j, n),
])
* para_scale
* para_scale
}
pub fn f_u_dd(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].u
* self.u_shapes.norm_d1(i, m)
* self.v_shapes.tang_d1(j, n)
* para_scale[0]
* para_scale[1]
}
pub fn f_v_dd(&self, [i, j]: [usize; 2], [m, n]: [usize; 2], para_scale: &V2D) -> V2D {
self.jac_inv[m][n].v
* self.u_shapes.tang_d1(i, m)
* self.v_shapes.norm_d1(j, n)
* para_scale[0]
* para_scale[1]
}
#[inline]
pub fn glq_scale(&self) -> f64 {
self.para_scale[0] * self.para_scale[1]
}
#[inline]
pub fn edge_glq_scale(&self, edge_idx: usize) -> f64 {
match edge_idx {
0 | 1 => self.para_scale[0],
2 | 3 => self.para_scale[1],
_ => panic!("edge_idx must not exceed 3; cannot get glq scaling factor!"),
}
}
#[inline]
pub fn u_glq_scale(&self) -> f64 {
self.para_scale[0]
}
#[inline]
pub fn v_glq_scale(&self) -> f64 {
self.para_scale[1]
}
#[inline]
pub fn deriv_scale(&self) -> &V2D {
&self.para_scale
}
#[inline]
pub fn sample_scale(&self, [m, n]: [usize; 2]) -> f64 {
self.det_jac[m][n]
}
pub fn max_uv_ratio(&self, [m, n]: [usize; 2]) -> f64 {
let r0 = self.jac[m][n].u[0] / self.jac[m][n].v[1];
let r1 = self.jac[m][n].u[0] / self.jac[m][n].v[1];
std::cmp::max_by(r0, r1, |a, b| a.partial_cmp(b).unwrap())
}
pub fn uv_ratio(&self, [m, n]: [usize; 2]) -> f64 {
self.jac[m][n].u[0] / self.jac[m][n].v[1]
}
pub fn vu_ratio(&self, [m, n]: [usize; 2]) -> f64 {
self.jac[m][n].v[1] / self.jac[m][n].u[0]
}
}
impl<BSpace: HierCurlBasisFnSpace> HierBasisFn for HierCurlBasisFn<BSpace> {
fn defined_over(
elem: &Elem,
desc_elem: Option<&Elem>,
[u_points, v_points]: [&[f64]; 2],
[i_max, j_max]: [usize; 2],
compute_d2: bool,
) -> Self {
let [(u_glq_scale, u_points_scaled), (v_glq_scale, v_points_scaled)] = match desc_elem {
Some(desc_elem_ref) => {
if desc_elem_ref.id == elem.id {
[(1.0, u_points.to_vec()), (1.0, v_points.to_vec())]
} else {
let child_parametric_range = desc_elem_ref.relative_parametric_range(elem.id);
[
scale_gauss_quad_points(
u_points,
child_parametric_range[0][0],
child_parametric_range[0][1],
),
scale_gauss_quad_points(
v_points,
child_parametric_range[1][0],
child_parametric_range[1][1],
),
]
}
}
None => [(1.0, u_points.to_vec()), (1.0, v_points.to_vec())],
};
let t: Vec<Vec<M2D>> = u_points_scaled
.iter()
.map(|u| {
v_points_scaled
.iter()
.map(|v| elem.parametric_mapping(V2D::from([*u, *v]), elem.parametric_range()))
.collect()
})
.collect();
let ti: Vec<Vec<M2D>> = t
.iter()
.map(|row| row.iter().map(|v| v.inverse()).collect())
.collect();
let dt: Vec<Vec<f64>> = t
.iter()
.map(|row| row.iter().map(|v| v.det()).collect())
.collect();
Self {
jac: t,
jac_inv: ti,
det_jac: dt,
para_scale: V2D::from([u_glq_scale, v_glq_scale]),
u_shapes: BSpace::with(i_max, &u_points_scaled, compute_d2),
v_shapes: BSpace::with(j_max, &v_points_scaled, compute_d2),
}
}
}