refraction/src/fns.rs
2024-09-28 12:59:00 +03:00

240 lines
6.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! Functions used to make metrics.
use crate::mathx::FloatExt2;
pub trait Limiter {
fn value(&self, x: f32) -> f32;
fn derivative(&self, x: f32) -> f32;
}
pub struct SmoothstepLimiter {
pub min: f32,
pub max: f32,
}
impl Limiter for SmoothstepLimiter {
fn value(&self, x: f32) -> f32 {
let y = (self.min, self.max).inverse_lerp(x.abs()).clamp(0.0, 1.0);
3.0 * y * y - 2.0 * y * y * y
}
fn derivative(&self, x: f32) -> f32 {
if x.abs() > self.min && x.abs() < self.max {
let t = (self.min, self.max).inverse_lerp(x.abs());
6.0 * x.signum() * t * (1.0 - t) / (self.max - self.min)
} else {
0.0
}
}
}
pub struct SmootherstepLimiter {
pub min: f32,
pub max: f32,
}
impl Limiter for SmootherstepLimiter {
fn value(&self, x: f32) -> f32 {
let y = (self.min, self.max).inverse_lerp(x.abs()).clamp(0.0, 1.0);
6.0 * y.powi(5) - 15.0 * y.powi(4) + 10.0 * y.powi(3)
}
fn derivative(&self, x: f32) -> f32 {
if x.abs() > self.min && x.abs() < self.max {
let t = (self.min, self.max).inverse_lerp(x.abs());
30.0 * (t * (1.0 - t)).powi(2) * x.signum() / (self.max - self.min)
} else {
0.0
}
}
}
pub struct QuadraticAccelerator {
pub internal: f32,
pub external: f32,
}
/// Продолжает функцию f с [-lim, lim] линейно в предположении f(±lim) = ±val, f'(±lim) = 1.
fn extend_linear(t: f32, f: impl FnOnce(f32) -> f32, lim: f32, val: f32) -> f32 {
if t.abs() <= lim {
f(t)
} else {
t + t.signum() * (val - lim)
}
}
/// Продолжает функцию f с [-lim, lim] константой в предположении f(±lim) = val, f'(±lim) = 0.
fn extend_const(t: f32, f: impl FnOnce(f32) -> f32, lim: f32, val: f32) -> f32 {
if t.abs() <= lim {
f(t)
} else {
val
}
}
impl QuadraticAccelerator {
fn a(&self) -> f32 {
-(self.external - self.internal) / self.internal.powi(2)
}
fn b(&self) -> f32 {
2.0 * self.external / self.internal - 1.0
}
fn root(&self, x: f32) -> f32 {
(self.b().powi(2) + 4.0 * self.a() * x.abs()).sqrt()
}
pub fn x(&self, u: f32) -> f32 {
extend_linear(
u,
|u| (self.a() * u.abs() + self.b()) * u,
self.internal,
self.external,
)
}
pub fn u(&self, x: f32) -> f32 {
extend_linear(
x,
|x| 0.5 * x.signum() * (-self.b() + self.root(x)) / self.a(),
self.external,
self.internal,
)
}
pub fn dx(&self, u: f32) -> f32 {
extend_const(
u,
|u| 2.0 * self.a() * u.abs() + self.b(),
self.internal,
1.0,
)
}
pub fn du(&self, x: f32) -> f32 {
extend_const(x, |x| 1.0 / self.root(x), self.external, 1.0)
}
pub fn d2u(&self, x: f32) -> f32 {
extend_const(
x,
|x| -2.0 * x.signum() * self.a() * self.root(x).powi(-3),
self.external,
0.0,
)
}
}
#[cfg(test)]
mod test {
use approx::{abs_diff_eq, assert_abs_diff_eq};
use super::*;
fn test_limiter(testee: impl Limiter, min: f32, max: f32, δ: f32) {
let ε = 1.0e-4f32;
let margin = 1.0 / 16.0;
let mul = 1.0 + margin;
for x in itertools_num::linspace(0., min, 10) {
assert_abs_diff_eq!(testee.value(x), 0., epsilon = ε);
assert_abs_diff_eq!(testee.value(-x), 0., epsilon = ε);
}
for x in itertools_num::linspace(max, mul * max, 10) {
assert_abs_diff_eq!(testee.value(x), 1., epsilon = ε);
assert_abs_diff_eq!(testee.value(-x), 1., epsilon = ε);
}
for x in itertools_num::linspace(-mul * max, mul * max, 100) {
let df_num = (testee.value(x + δ) - testee.value(x - δ)) / (2. * δ);
let df_expl = testee.derivative(x);
assert!(
abs_diff_eq!(df_expl, df_num, epsilon = ε),
"At x={x}, df/dx:\nnumerical: {df_num}\nexplicit: {df_expl}\n"
);
}
}
#[test]
fn test_smoothstep_limiter() {
test_limiter(
SmoothstepLimiter {
min: 20.0,
max: 30.0,
},
20.0,
30.0,
1.0 / 32.0,
);
}
#[test]
fn test_smootherstep_limiter() {
test_limiter(
SmootherstepLimiter {
min: 20.0,
max: 30.0,
},
20.0,
30.0,
1.0 / 32.0,
);
}
#[test]
fn test_quadratic_accelerator() {
let testee = super::QuadraticAccelerator {
internal: 100.0,
external: 150.0,
};
let ε = 1.0e-4f32;
let δ = 1.0 / 8.0; // Mathematically, you want this to be small. Computationally, you dont.
let margin = 1.0 / 16.0;
let mul = 1.0 + margin;
assert_abs_diff_eq!(testee.u(testee.external), testee.internal, epsilon = ε);
assert_abs_diff_eq!(testee.u(-testee.external), -testee.internal, epsilon = ε);
assert_abs_diff_eq!(testee.du(testee.external), 1., epsilon = ε);
assert_abs_diff_eq!(testee.du(-testee.external), 1., epsilon = ε);
for x in itertools_num::linspace(-mul * testee.external, mul * testee.external, 100) {
let ux = testee.u(x);
let xux = testee.x(ux);
assert!(
abs_diff_eq!(x, xux, epsilon = ε),
"At x={x}:\nu(x): {ux}\nx(u(x)): {xux}\n"
);
let du_num = (testee.u(x + δ) - testee.u(x - δ)) / (2. * δ);
let du_expl = testee.du(x);
assert!(
abs_diff_eq!(du_expl, du_num, epsilon = ε),
"At x={x}, du/dx:\nnumerical: {du_num}\nexplicit: {du_expl}\n"
);
let dudx = du_expl * testee.dx(ux);
assert!(
abs_diff_eq!(dudx, 1.0, epsilon = ε),
"At x={x}:\ndu/dx * dx/du: {dudx}\n"
);
let d2u_num = (testee.du(x + δ) - testee.du(x - δ)) / (2. * δ);
let d2u_expl = testee.d2u(x);
assert!(
abs_diff_eq!(d2u_expl, d2u_num, epsilon = ε),
"At x={x}, d^2u/dx^2:\nnumerical: {d2u_num}\nexplicit: {d2u_expl}\n"
);
}
for u in itertools_num::linspace(-mul * testee.internal, mul * testee.internal, 100) {
let xu = testee.x(u);
let uxu = testee.u(xu);
assert!(
abs_diff_eq!(u, uxu, epsilon = ε),
"At u={u}:\nx(u): {xu}\nu(x(u)): {uxu}\n"
);
let dx_num = (testee.x(u + δ) - testee.x(u - δ)) / (2. * δ);
let dx_expl = testee.dx(u);
assert!(
abs_diff_eq!(dx_expl, dx_num, epsilon = ε),
"At u={u}, dx/du:\nnumerical: {dx_num}\nexplicit: {dx_expl}\n"
);
let dudx = testee.du(xu) * dx_expl;
assert!(
abs_diff_eq!(dudx, 1.0, epsilon = ε),
"At u={u}:\ndu/dx * dx/du: {dudx}\n"
);
}
}
}