1use serde::{Deserialize, Serialize};
2use std::hash::Hash;
3
4#[derive(Clone, Serialize, Deserialize, Debug)]
10pub enum TransformType {
11 Linear,
14 Arcsinh { cofactor: f32 },
18 Biexponential {
23 top_of_scale: f32,
25 positive_decades: f32,
27 negative_decades: f32,
29 width: f32,
31 },
32}
33
34impl TransformType {
35 pub fn create_from_str(s: Option<&str>) -> Self {
37 match s {
38 Some("linear") => TransformType::Linear,
39 Some("arcsinh") => TransformType::Arcsinh { cofactor: 200.0 },
40 Some("biexponential") | Some("logicle") => TransformType::Biexponential {
41 top_of_scale: 262144.0,
42 positive_decades: 4.5,
43 negative_decades: 0.0,
44 width: 0.5,
45 },
46 _ => TransformType::default(),
47 }
48 }
49}
50
51pub trait Transformable {
56 fn transform(&self, value: &f32) -> f32;
57 fn inverse_transform(&self, value: &f32) -> f32;
58}
59#[allow(unused)]
64pub trait Formattable {
65 fn format(&self, value: &f32) -> String;
66}
67
68impl Transformable for TransformType {
69 fn transform(&self, value: &f32) -> f32 {
70 match self {
71 TransformType::Linear => *value,
72 TransformType::Arcsinh { cofactor } => (value / cofactor).asinh(),
73 TransformType::Biexponential {
74 top_of_scale,
75 positive_decades,
76 negative_decades,
77 width: _,
78 } => {
79 let ln_10 = 10.0_f32.ln();
83 let m_ln10 = positive_decades * ln_10;
84 let sinh_m_ln10 = m_ln10.sinh();
85 let a_ln10 = negative_decades * ln_10;
86
87 if *top_of_scale == 0.0 {
89 return *value;
90 }
91
92 let scaled_x = value * sinh_m_ln10 / top_of_scale;
93 scaled_x.asinh() + a_ln10
94 }
95 }
96 }
97 fn inverse_transform(&self, value: &f32) -> f32 {
98 match self {
99 TransformType::Linear => *value,
100 TransformType::Arcsinh { cofactor } => {
101 eprintln!(
102 "🔧 [INVERSE_TRANSFORM] Arcsinh inverse: value={}, cofactor={}",
103 value, cofactor
104 );
105 let sinh_result = value.sinh();
106 eprintln!("🔧 [INVERSE_TRANSFORM] sinh({}) = {}", value, sinh_result);
107 let final_result = sinh_result * cofactor;
108 eprintln!(
109 "🔧 [INVERSE_TRANSFORM] final result: {} * {} = {}",
110 sinh_result, cofactor, final_result
111 );
112 final_result
113 }
114 TransformType::Biexponential {
115 top_of_scale,
116 positive_decades,
117 negative_decades,
118 width: _,
119 } => {
120 let ln_10 = 10.0_f32.ln();
123 let m_ln10 = positive_decades * ln_10;
124 let sinh_m_ln10 = m_ln10.sinh();
125 let a_ln10 = negative_decades * ln_10;
126
127 let y_minus_a = value - a_ln10;
128 let sinh_y_minus_a = y_minus_a.sinh();
129
130 top_of_scale * sinh_y_minus_a / sinh_m_ln10
131 }
132 }
133 }
134}
135impl Formattable for TransformType {
136 fn format(&self, value: &f32) -> String {
137 match self {
138 TransformType::Linear => format!("{:.1e}", value),
139 TransformType::Arcsinh { cofactor: _ } => {
140 let original_value = self.inverse_transform(value);
142
143 format!("{:.1e}", original_value)
145 }
146 TransformType::Biexponential { .. } => {
147 let original_value = self.inverse_transform(value);
149
150 format!("{:.1e}", original_value)
152 }
153 }
154 }
155}
156impl Default for TransformType {
157 fn default() -> Self {
158 TransformType::Arcsinh { cofactor: 200.0 }
159 }
160}
161impl Hash for TransformType {
162 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
163 match self {
164 TransformType::Linear => "linear".hash(state),
165 TransformType::Arcsinh { cofactor: _ } => "arcsinh".hash(state),
166 TransformType::Biexponential { .. } => "biexponential".hash(state),
167 }
168 }
169}
170
171#[test]
172fn test_transform() {
173 let t = TransformType::Linear;
174 assert_eq!(t.transform(&1.0), 1.0);
175 assert_eq!(t.inverse_transform(&1.0), 1.0);
176
177 let t = TransformType::Arcsinh { cofactor: 200.0 };
178 let transformed = t.transform(&1.0);
180 assert!(
181 (transformed - 0.005).abs() < 1e-6,
182 "Expected ~0.005, got {}",
183 transformed
184 );
185 let inverse = t.inverse_transform(&0.005);
186 assert!(
188 (inverse - 1.0).abs() < 1e-5,
189 "Expected ~1.0, got {}",
190 inverse
191 );
192 assert!(!t.transform(&-1.0).is_nan());
194 assert!(!t.transform(&0.0).is_nan());
195 assert!(!t.transform(&-200.0).is_nan());
196}