#[derive(Debug, Clone)]
struct DuchonLogarithmicPsiCorrection {
data: Arc<Array2<f64>>,
centers: Arc<Array2<f64>>,
metric: Vec<f64>,
coefficients: Vec<(usize, f64)>,
}
impl DuchonLogarithmicPsiCorrection {
fn new(
data: ArrayView2<'_, f64>,
centers: ArrayView2<'_, f64>,
eta: &[f64],
kind: &RadialScalarKind,
) -> Option<Arc<Self>> {
let coefficients = Self::coefficients(kind)?;
Some(Arc::new(Self {
data: shared_owned_data_matrix_from_view(data),
centers: shared_owned_centers_matrix_from_view(centers),
metric: centered_aniso_metric_weights(eta),
coefficients,
}))
}
fn coefficients(kind: &RadialScalarKind) -> Option<Vec<(usize, f64)>> {
let RadialScalarKind::Duchon { dim, coeffs, .. } = kind else {
return None;
};
if !dim.is_multiple_of(2) {
return None;
}
let coefficients: Vec<_> = coeffs
.a
.iter()
.enumerate()
.skip(1)
.filter(|(m, a)| **a != 0.0 && 2 * *m >= *dim)
.filter_map(|(m, a)| {
let degree = m - dim / 2;
let (_, log_coefficient) = duchon_polyharmonic_block_taylor_r2j(m, *dim, degree);
(log_coefficient != 0.0).then_some((degree, -a * log_coefficient))
})
.collect();
if coefficients.is_empty() {
return None;
}
Some(coefficients)
}
fn radial(&self, r2: f64) -> (f64, f64) {
Self::evaluate(&self.coefficients, r2)
}
fn evaluate(coefficients: &[(usize, f64)], r2: f64) -> (f64, f64) {
let mut value = KahanSum::default();
let mut radial = KahanSum::default();
for &(degree, coefficient) in coefficients {
value.add(coefficient * r2.powi(degree as i32));
if degree > 0 {
radial.add(2.0 * degree as f64 * coefficient * r2.powi(degree as i32 - 1));
}
}
(value.sum(), radial.sum())
}
}
impl ImplicitDesignPsiDerivative {
fn with_logarithmic_correction(
mut self,
correction: Option<Arc<DuchonLogarithmicPsiCorrection>>,
) -> Self {
self.logarithmic_correction = correction;
self
}
fn logarithmic_axis(&self, axis: usize, components: &[f64]) -> (f64, f64) {
match self.axis_combinations.as_ref() {
Some(_) => self
.transformed_axis_combination(axis)
.iter()
.fold((0.0, 0.0), |(value, total), &(raw, weight)| {
(value + weight * components[raw], total + weight)
}),
None => (components[axis], 1.0),
}
}
fn add_logarithmic_correction(
&self,
key: ProjectedJetKey,
rows: std::ops::Range<usize>,
matrix: &mut Array2<f64>,
) {
let Some(correction) = self.logarithmic_correction.as_ref() else {
return;
};
let mut raw = Array2::<f64>::zeros((rows.len(), self.n_knots));
let mut components = vec![0.0; self.n_axes];
for (local, row) in rows.enumerate() {
for center in 0..self.n_knots {
for (axis, component) in components.iter_mut().enumerate() {
let displacement =
correction.data[[row, axis]] - correction.centers[[center, axis]];
*component = correction.metric[axis] * displacement * displacement;
}
let r2 = components.iter().sum::<f64>();
if r2 == 0.0 {
continue;
}
let (value, radial) = correction.radial(r2);
let scalar = match key {
ProjectedJetKey::FirstRaw(axis) => {
let (_, total) = self.logarithmic_axis(axis, &components);
value * total
}
ProjectedJetKey::SecondDiagonal(axis) => {
let (component, total) = self.logarithmic_axis(axis, &components);
2.0 * total * (radial * component + self.effective_share(axis) * value)
}
ProjectedJetKey::SecondCross(a, b) => {
let (sa, ca) = self.logarithmic_axis(a, &components);
let (sb, cb) = self.logarithmic_axis(b, &components);
radial * (sa * cb + sb * ca)
+ value * (self.effective_share(a) * cb + self.effective_share(b) * ca)
}
};
raw[[local, center]] = self.chart_scale * scalar / self.n_axes as f64;
}
}
match key {
ProjectedJetKey::FirstRaw(_) => *matrix += &raw,
ProjectedJetKey::SecondDiagonal(_) | ProjectedJetKey::SecondCross(_, _) => {
*matrix += &self.project_matrix_rows(raw);
}
}
}
fn logarithmic_jet_chunk(
&self,
key: ProjectedJetKey,
rows: std::ops::Range<usize>,
) -> Result<Array2<f64>, BasisError> {
match key {
ProjectedJetKey::FirstRaw(axis) => self.row_chunk_first_raw(axis, rows),
ProjectedJetKey::SecondDiagonal(axis) => self.row_chunk_second_diag(axis, rows),
ProjectedJetKey::SecondCross(a, b) => self.row_chunk_second_cross(a, b, rows),
}
}
fn logarithmic_transpose(
&self,
key: ProjectedJetKey,
v: &ArrayView1<f64>,
) -> Result<Array1<f64>, BasisError> {
assert_eq!(v.len(), self.n);
let width = match key {
ProjectedJetKey::FirstRaw(_) => self.n_knots,
_ => self.p_out(),
};
let mut result = Array1::<f64>::zeros(width);
for start in (0..self.n).step_by(IMPLICIT_MATVEC_CHUNK_SIZE) {
let end = (start + IMPLICIT_MATVEC_CHUNK_SIZE).min(self.n);
result += &self
.logarithmic_jet_chunk(key, start..end)?
.t()
.dot(&v.slice(ndarray::s![start..end]));
}
Ok(result)
}
fn logarithmic_forward(
&self,
key: ProjectedJetKey,
u: &ArrayView1<f64>,
) -> Result<Array1<f64>, BasisError> {
let mut result = Array1::<f64>::zeros(self.n);
for start in (0..self.n).step_by(IMPLICIT_MATVEC_CHUNK_SIZE) {
let end = (start + IMPLICIT_MATVEC_CHUNK_SIZE).min(self.n);
result
.slice_mut(ndarray::s![start..end])
.assign(&self.logarithmic_jet_chunk(key, start..end)?.dot(u));
}
Ok(result)
}
}