use std::ops::RangeInclusive;
pub trait RangeMapper: Send + Sync {
fn normalize(&self, plain: f64) -> f64;
fn denormalize(&self, normalized: f64) -> f64;
fn range(&self) -> (f64, f64);
fn default_normalized(&self, plain_default: f64) -> f64 {
self.normalize(plain_default)
}
}
#[derive(Debug, Clone)]
pub struct LinearMapper {
min: f64,
max: f64,
}
impl LinearMapper {
pub fn new(range: RangeInclusive<f64>) -> Self {
Self {
min: *range.start(),
max: *range.end(),
}
}
}
impl RangeMapper for LinearMapper {
fn normalize(&self, plain: f64) -> f64 {
if (self.max - self.min).abs() < f64::EPSILON {
return 0.5;
}
((plain - self.min) / (self.max - self.min)).clamp(0.0, 1.0)
}
fn denormalize(&self, normalized: f64) -> f64 {
let normalized = normalized.clamp(0.0, 1.0);
self.min + normalized * (self.max - self.min)
}
fn range(&self) -> (f64, f64) {
(self.min, self.max)
}
}
#[derive(Debug, Clone)]
pub struct LogMapper {
min: f64,
max: f64,
min_log: f64,
max_log: f64,
}
impl LogMapper {
pub fn new(range: RangeInclusive<f64>) -> Self {
let min = *range.start();
let max = *range.end();
assert!(
min > 0.0,
"LogMapper requires positive range start, got min={}",
min
);
assert!(
max > min,
"LogMapper requires max > min, got min={}, max={}",
min, max
);
Self {
min,
max,
min_log: min.ln(),
max_log: max.ln(),
}
}
}
impl RangeMapper for LogMapper {
fn normalize(&self, plain: f64) -> f64 {
if (self.max_log - self.min_log).abs() < f64::EPSILON {
return 0.5;
}
let plain = plain.max(self.min); let plain_log = plain.ln();
((plain_log - self.min_log) / (self.max_log - self.min_log)).clamp(0.0, 1.0)
}
fn denormalize(&self, normalized: f64) -> f64 {
let normalized = normalized.clamp(0.0, 1.0);
(self.min_log + normalized * (self.max_log - self.min_log)).exp()
}
fn range(&self) -> (f64, f64) {
(self.min, self.max)
}
}
#[derive(Debug, Clone)]
pub struct PowerMapper {
min: f64,
max: f64,
inv_exponent: f64,
}
impl PowerMapper {
pub fn new(range: RangeInclusive<f64>, exponent: f64) -> Self {
let min = *range.start();
let max = *range.end();
assert!(
max > min,
"PowerMapper requires max > min, got min={}, max={}",
min, max
);
assert!(
exponent > 0.0,
"PowerMapper requires positive exponent, got {}",
exponent
);
Self {
min,
max,
inv_exponent: 1.0 / exponent,
}
}
}
impl RangeMapper for PowerMapper {
fn normalize(&self, plain: f64) -> f64 {
if (self.max - self.min).abs() < f64::EPSILON {
return 0.5;
}
let linear = ((plain - self.min) / (self.max - self.min)).clamp(0.0, 1.0);
linear.powf(1.0 / self.inv_exponent)
}
fn denormalize(&self, normalized: f64) -> f64 {
let normalized = normalized.clamp(0.0, 1.0);
let linear = normalized.powf(self.inv_exponent);
self.min + linear * (self.max - self.min)
}
fn range(&self) -> (f64, f64) {
(self.min, self.max)
}
}
#[derive(Debug, Clone)]
pub struct LogOffsetMapper {
min: f64,
max: f64,
offset: f64,
min_log: f64,
max_log: f64,
}
impl LogOffsetMapper {
pub fn new(range: RangeInclusive<f64>) -> Self {
let min = *range.start();
let max = *range.end();
assert!(
max > min,
"LogOffsetMapper requires max > min, got min={}, max={}",
min, max
);
let offset = if min < 0.0 { min.abs() + 1.0 } else { 1.0 };
let min_offset = min + offset;
let max_offset = max + offset;
Self {
min,
max,
offset,
min_log: min_offset.ln(),
max_log: max_offset.ln(),
}
}
}
impl RangeMapper for LogOffsetMapper {
fn normalize(&self, plain: f64) -> f64 {
if (self.max_log - self.min_log).abs() < f64::EPSILON {
return 0.5;
}
let plain_offset = (plain + self.offset).max(self.min + self.offset);
let plain_log = plain_offset.ln();
((plain_log - self.min_log) / (self.max_log - self.min_log)).clamp(0.0, 1.0)
}
fn denormalize(&self, normalized: f64) -> f64 {
let normalized = normalized.clamp(0.0, 1.0);
let plain_offset = (self.min_log + normalized * (self.max_log - self.min_log)).exp();
plain_offset - self.offset
}
fn range(&self) -> (f64, f64) {
(self.min, self.max)
}
}