use std::f64::consts::FRAC_PI_2;
use super::ParameterTransform;
use crate::precision::MnMachinePrecision;
pub struct SinTransform;
impl SinTransform {
pub fn dext2int(&self, value: f64, upper: f64, lower: f64, prec: &MnMachinePrecision) -> f64 {
let int = self.ext2int(value, upper, lower, prec);
let d = self.dint2ext(int, upper, lower);
if d.abs() > prec.eps2() { 1.0 / d } else { 0.0 }
}
}
impl ParameterTransform for SinTransform {
fn int2ext(&self, angle: f64, upper: f64, lower: f64) -> f64 {
let width = upper - lower;
lower + 0.5 * width * (angle.sin() + 1.0)
}
fn ext2int(&self, value: f64, upper: f64, lower: f64, prec: &MnMachinePrecision) -> f64 {
let width = upper - lower;
let normalized = 2.0 * (value - lower) / width - 1.0;
let boundary_margin = 8.0 * prec.eps2().sqrt();
let max_angle = FRAC_PI_2 - boundary_margin;
let min_angle = -FRAC_PI_2 + boundary_margin;
match normalized {
n if n <= -1.0 + boundary_margin => min_angle,
n if n >= 1.0 - boundary_margin => max_angle,
n => n.asin(),
}
}
fn dint2ext(&self, angle: f64, upper: f64, lower: f64) -> f64 {
0.5 * (upper - lower).abs() * angle.cos().abs()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let t = SinTransform;
let prec = MnMachinePrecision::new();
let (lo, hi) = (1.0, 10.0);
for &ext in &[2.0, 5.5, 9.0] {
let int = t.ext2int(ext, hi, lo, &prec);
let back = t.int2ext(int, hi, lo);
assert!((back - ext).abs() < 1e-12, "roundtrip failed for {ext}");
}
}
#[test]
fn midpoint() {
let t = SinTransform;
let ext = t.int2ext(0.0, 10.0, 0.0);
assert!((ext - 5.0).abs() < 1e-15);
}
#[test]
fn derivative_positive() {
let t = SinTransform;
let d = t.dint2ext(0.0, 10.0, 0.0);
assert!(d > 0.0);
}
#[test]
fn near_boundary() {
let t = SinTransform;
let prec = MnMachinePrecision::new();
let int = t.ext2int(9.9999999999, 10.0, 0.0, &prec);
let ext = t.int2ext(int, 10.0, 0.0);
assert!((ext - 10.0).abs() < 0.01);
}
#[test]
fn exact_bounds_are_clamped_to_finite_internal_values() {
let t = SinTransform;
let prec = MnMachinePrecision::new();
let lower = t.ext2int(0.0, 10.0, 0.0, &prec);
let upper = t.ext2int(10.0, 10.0, 0.0, &prec);
assert!(lower > -FRAC_PI_2);
assert!(upper < FRAC_PI_2);
assert!(lower.is_finite());
assert!(upper.is_finite());
}
#[test]
fn dext2int_matches_reciprocal_midpoint_derivative() {
let t = SinTransform;
let prec = MnMachinePrecision::new();
let reciprocal = t.dext2int(5.0, 10.0, 0.0, &prec);
assert!((reciprocal - 0.2).abs() < 1e-15);
}
}