use crate::errors::QlResult;
use crate::math::comparison::close;
use crate::math::interpolations::Interpolation;
use crate::math::interpolations::linear::LinearInterpolation;
use crate::require;
use crate::types::{Real, Size};
use crate::utilities::null::Null;
use super::fdm1dmesher::Fdm1dMesher;
#[allow(clippy::neg_cmp_op_on_partial_ord)]
pub fn concentrating_1d_mesher(
start: Real,
end: Real,
size: Size,
c_point: Option<(Real, Real)>,
require_c_point: bool,
) -> QlResult<Fdm1dMesher> {
require!(end > start, "end must be larger than start");
require!(
!require_c_point || c_point.is_some(),
"cPoint is required in grid but not given"
);
let dx = 1.0 / (size - 1) as Real;
let mut locations = vec![0.0; size];
let mut dplus = vec![0.0; size];
let mut dminus = vec![0.0; size];
if let Some((c_point, density)) = c_point {
let density = density * (end - start);
require!(
c_point >= start && c_point <= end,
"cPoint must be between start and end"
);
require!(density > 0.0, "density > 0 required");
let c1 = ((start - c_point) / density).asinh();
let c2 = ((end - c_point) / density).asinh();
let transform = if require_c_point {
let mut u = vec![0.0];
let mut z = vec![0.0];
if !close(c_point, start) && !close(c_point, end) {
let z0 = -c1 / (c2 - c1);
let steps = (size - 1) as i64;
let u0 = std::cmp::max(
std::cmp::min((z0 * steps as Real).round() as i64, size as i64 - 2),
1,
) as Real
/ (size - 1) as Real;
u.push(u0);
z.push(z0);
}
u.push(1.0);
z.push(1.0);
Some(LinearInterpolation::new(u, z)?)
} else {
None
};
for (i, location) in locations.iter_mut().enumerate().take(size - 1).skip(1) {
let li = match &transform {
Some(transform) => transform.value(i as Real * dx)?,
None => i as Real * dx,
};
*location = c_point + density * (c1 * (1.0 - li) + c2 * li).sinh();
}
} else {
for (i, location) in locations.iter_mut().enumerate().take(size - 1).skip(1) {
*location = start + i as Real * dx * (end - start);
}
}
locations[0] = start;
locations[size - 1] = end;
for i in 0..size - 1 {
let gap = locations[i + 1] - locations[i];
dplus[i] = gap;
dminus[i + 1] = gap;
}
dplus[size - 1] = Real::null();
dminus[0] = Real::null();
Ok(Fdm1dMesher::new(locations, dplus, dminus))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::methods::finitedifferences::meshers::uniform_1d_mesher;
fn is_increasing(mesher: &Fdm1dMesher) -> bool {
mesher.locations().windows(2).all(|w| w[1] > w[0])
}
#[test]
fn grid_spans_the_range_and_packs_points_around_the_critical_point() {
let (start, end, size) = (-1.0, 1.6, 21);
let c_point = 0.0;
let mesher =
concentrating_1d_mesher(start, end, size, Some((c_point, 0.1)), false).unwrap();
assert_eq!(mesher.size(), size);
assert_eq!(mesher.location(0), start);
assert_eq!(mesher.location(size - 1), end);
assert!(is_increasing(&mesher));
let nearest = (0..size)
.min_by(|&i, &j| {
(mesher.location(i) - c_point)
.abs()
.total_cmp(&(mesher.location(j) - c_point).abs())
})
.unwrap();
let gaps: Vec<Real> = (0..size - 1).map(|i| mesher.dplus(i)).collect();
let tightest = (0..size - 1)
.min_by(|&i, &j| gaps[i].total_cmp(&gaps[j]))
.unwrap();
assert!(tightest.abs_diff(nearest) <= 1);
assert!(gaps[0] > gaps[tightest]);
assert!(gaps[size - 2] > gaps[tightest]);
}
#[test]
fn gaps_mirror_the_locations_with_null_at_the_boundaries() {
let size = 11;
let mesher = concentrating_1d_mesher(-3.0, 4.0, size, Some((1.0, 0.01)), false).unwrap();
for i in 0..size - 1 {
let gap = mesher.location(i + 1) - mesher.location(i);
assert_eq!(mesher.dplus(i), gap);
assert_eq!(mesher.dminus(i + 1), gap);
}
assert!(mesher.dplus(size - 1).is_null());
assert!(mesher.dminus(0).is_null());
}
#[test]
fn without_a_critical_point_the_grid_is_equidistant() {
let (start, end, size) = (-2.0, 1.0, 9);
let mesher = concentrating_1d_mesher(start, end, size, None, false).unwrap();
let uniform = uniform_1d_mesher(start, end, size).unwrap();
assert_eq!(mesher.locations(), uniform.locations());
}
#[test]
fn a_required_critical_point_lands_on_a_grid_node() {
let (start, end, size) = (-1.0, 1.6, 21);
for c_point in [0.0, -0.4, 1.2] {
let mesher =
concentrating_1d_mesher(start, end, size, Some((c_point, 0.1)), true).unwrap();
assert!(is_increasing(&mesher));
assert!(
mesher.locations().iter().any(|&x| close(x, c_point)),
"cPoint {c_point} is not on the grid"
);
}
}
#[test]
fn a_critical_point_on_a_boundary_needs_no_interior_node() {
let (start, end, size) = (-1.0, 1.6, 21);
for c_point in [start, end] {
let mesher =
concentrating_1d_mesher(start, end, size, Some((c_point, 0.1)), true).unwrap();
assert!(is_increasing(&mesher));
assert_eq!(mesher.location(0), start);
assert_eq!(mesher.location(size - 1), end);
}
}
#[test]
fn a_required_critical_point_must_be_given() {
let err = concentrating_1d_mesher(-1.0, 1.6, 21, None, true).unwrap_err();
assert_eq!(err.message(), "cPoint is required in grid but not given");
}
#[test]
fn the_range_the_critical_point_and_the_density_are_checked() {
let err = concentrating_1d_mesher(1.0, 1.0, 21, None, false).unwrap_err();
assert_eq!(err.message(), "end must be larger than start");
let err = concentrating_1d_mesher(-1.0, 1.6, 21, Some((2.0, 0.1)), false).unwrap_err();
assert_eq!(err.message(), "cPoint must be between start and end");
let err = concentrating_1d_mesher(-1.0, 1.6, 21, Some((0.0, 0.0)), false).unwrap_err();
assert_eq!(err.message(), "density > 0 required");
}
}