1use serde::{Deserialize, Serialize};
2use std::hash::Hash;
3
4#[derive(Clone, Serialize, Deserialize, Debug, PartialEq)]
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 final_result = (*value).sinh() * *cofactor;
106 eprintln!(
107 "🔧 [INVERSE_TRANSFORM] final result: {} * {} = {}",
108 value.sinh(),
109 cofactor,
110 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 } => {
166 "arcsinh".hash(state);
167 cofactor.to_bits().hash(state);
168 }
169 TransformType::Biexponential {
170 top_of_scale,
171 positive_decades,
172 negative_decades,
173 width,
174 } => {
175 "biexponential".hash(state);
176 top_of_scale.to_bits().hash(state);
177 positive_decades.to_bits().hash(state);
178 negative_decades.to_bits().hash(state);
179 width.to_bits().hash(state);
180 }
181 }
182 }
183}
184
185#[test]
186fn test_transform() {
187 let t = TransformType::Linear;
188 assert_eq!(t.transform(&1.0), 1.0);
189 assert_eq!(t.inverse_transform(&1.0), 1.0);
190
191 let t = TransformType::Arcsinh { cofactor: 200.0 };
192 let transformed = t.transform(&1.0);
194 assert!(
195 (transformed - 0.005).abs() < 1e-6,
196 "Expected ~0.005, got {}",
197 transformed
198 );
199 let inverse = t.inverse_transform(&0.005);
200 assert!(
202 (inverse - 1.0).abs() < 1e-5,
203 "Expected ~1.0, got {}",
204 inverse
205 );
206 assert!(!t.transform(&-1.0).is_nan());
208 assert!(!t.transform(&0.0).is_nan());
209 assert!(!t.transform(&-200.0).is_nan());
210}
211
212#[test]
213fn test_transform_type_partial_eq_and_hash_consistency() {
214 use std::hash::{Hash, Hasher};
215 let a = TransformType::Arcsinh { cofactor: 200.0 };
216 let b = TransformType::Arcsinh { cofactor: 200.0 };
217 let c = TransformType::Arcsinh { cofactor: 150.0 };
218 assert_eq!(a, b);
219 assert_ne!(a, c);
220 let mut hasher_a = std::collections::hash_map::DefaultHasher::new();
222 let mut hasher_b = std::collections::hash_map::DefaultHasher::new();
223 a.hash(&mut hasher_a);
224 b.hash(&mut hasher_b);
225 assert_eq!(hasher_a.finish(), hasher_b.finish());
226 c.hash(&mut hasher_a);
227 b.hash(&mut hasher_b);
228 assert_ne!(hasher_a.finish(), hasher_b.finish());
229}