1#![no_std]
3use fixed::types::I16F16;
4use fixed_exp2::FixedPowF;
5use fixed_trigonometry::sin;
6pub type Fix = I16F16;
7
8macro_rules! easer {
9 ($f:ident, $t:ident, $e:expr) => {
10 pub struct $t {
11 start: Fix,
12 dist: Fix,
13 step: u64,
14 steps: u64,
15 }
16
17 pub fn $f(start: Fix, end: Fix, steps: u64) -> $t {
18 $t {
19 start,
20 dist: end - start,
21 step: 0,
22 steps,
23 }
24 }
25
26 impl $t {
27 pub fn at(x: Fix, start: Fix, dist: Fix) -> Fix {
28 Fix::from_num($e(x)).mul_add(dist, start)
29 }
30 pub fn at_normalized(x: Fix) -> Fix {
31 Self::at(x, Fix::from_num(0), Fix::from_num(1))
32 }
33 }
34
35 impl Iterator for $t {
36 type Item = Fix;
37
38 fn next(&mut self) -> Option<Fix> {
39 self.step += 1;
40 if self.step > self.steps {
41 None
42 } else {
43 let x: Fix = Fix::from_num(self.step) / Fix::from_num(self.steps);
44 Some(Self::at(x, self.start, self.dist))
45 }
46 }
47 }
48 };
49}
50
51easer!(linear, Linear, |x: Fix| { x });
52easer!(quad_in, QuadIn, |x: Fix| { x * x });
53easer!(quad_out, QuadOut, |x: Fix| {
54 -(x * (x - Fix::from_num(2)))
55});
56easer!(quad_inout, QuadInOut, |x: Fix| -> Fix {
57 if x < Fix::from_num(0.5) {
58 Fix::from_num(2) * x * x
59 } else {
60 (Fix::from_num(-2) * x * x) + x.mul_add(Fix::from_num(4), Fix::from_num(-1))
61 }
62});
63easer!(cubic_in, CubicIn, |x: Fix| { x * x * x });
64easer!(cubic_out, CubicOut, |x: Fix| {
65 let y = x - Fix::from_num(1);
66 y * y * y + Fix::from_num(1)
67});
68easer!(cubic_inout, CubicInOut, |x: Fix| {
69 if x < Fix::from_num(0.5) {
70 Fix::from_num(4) * x * x * x
71 } else {
72 let y = x.mul_add(2.into(), Fix::from_num(-2));
73 (y * y * y).mul_add(Fix::from_num(0.5), Fix::from_num(1))
74 }
75});
76easer!(quartic_in, QuarticIn, |x: Fix| { x * x * x * x });
77easer!(quartic_out, QuarticOut, |x: Fix| {
78 let y = x - Fix::from_num(1);
79 (y * y * y).mul_add(Fix::from_num(1) - x, Fix::from_num(1))
80});
81easer!(quartic_inout, QuarticInOut, |x: Fix| {
82 if x < Fix::from_num(0.5) {
83 Fix::from_num(8) * x * x * x * x
84 } else {
85 let y = x - Fix::from_num(1);
86 (y * y * y * y).mul_add(Fix::from_num(-8), Fix::from_num(1))
87 }
88});
89easer!(sin_in, SinIn, |x: Fix| {
90 let y = (x - Fix::from_num(1)) * Fix::FRAC_PI_2;
91 sin(y) + Fix::from_num(1)
92});
93easer!(sin_out, SinOut, |x: Fix| {
94 sin(x * Fix::FRAC_PI_2)
95});
96easer!(sin_inout, SinInOut, |x: Fix| {
97 if x < Fix::from_num(0.5) {
98 Fix::from_num(0.5)
99 * (Fix::from_num(1) - (x * x).mul_add(Fix::from_num(-4), Fix::from_num(1)).sqrt())
100 } else {
101 Fix::from_num(0.5)
102 * ((x.mul_add(Fix::from_num(-2), Fix::from_num(3))
103 * x.mul_add(Fix::from_num(2), Fix::from_num(-1)))
104 .sqrt()
105 + Fix::from_num(1))
106 }
107});
108easer!(exp_in, ExpIn, |x: Fix| {
109 if x == 0. {
110 Fix::from_num(0)
111 } else {
112 Fix::from_num(2).powf(Fix::from_num(10) * (x - Fix::from_num(1)))
113 }
114});
115
116easer!(exp_out, ExpOut, |x: Fix| {
117 if x == Fix::from_num(1) {
118 Fix::from_num(1)
119 } else {
120 Fix::from_num(2).powf(-Fix::from_num(10) * x) * Fix::from_num(-1) + Fix::from_num(1)
121 }
122});
123easer!(exp_inout, ExpInOut, |x: Fix| {
124 if x == Fix::from_num(1) {
125 Fix::from_num(1)
126 } else if x == 0. {
127 Fix::from_num(0)
128 } else if x < Fix::from_num(0.5) {
129 Fix::from_num(2).powf(x.mul_add(Fix::from_num(20), Fix::from_num(-10))) * Fix::from_num(0.5)
130 } else {
131 Fix::from_num(2)
132 .powf(x.mul_add(Fix::from_num(-20), Fix::from_num(10)))
133 .mul_add(Fix::from_num(-0.5), Fix::from_num(1))
134 }
135});
136easer!(smoothstep, SmoothStep, |x: Fix| {
137 if x == Fix::from_num(1) {
138 Fix::from_num(1)
139 } else if x == 0. {
140 Fix::from_num(0)
141 } else {
142 x * x * (Fix::from_num(3) - Fix::from_num(2) * x)
143 }
144});
145
146#[cfg(test)]
147mod test {
148 const ERROR_MARGIN_FAC: f64 = 0.00015;
150 extern crate std;
151
152 use std::{fs::File, iter::zip, path::PathBuf, vec, vec::Vec, eprintln, format};
153
154 use anyhow::anyhow;
155
156 use super::*;
157 macro_rules! function {
158 () => {{
159 fn f() {}
160 fn type_name_of<T>(_: T) -> &'static str {
161 std::any::type_name::<T>()
162 }
163 let name = type_name_of(f);
164 let full_path = name.strip_suffix("::f").unwrap();
165 full_path.split("::").last().unwrap()
166 }};
167 }
168
169 fn must_be_withing_error_margin_else_write(
171 test_name: &str,
172 ought_data: Vec<f64>,
173 is_data: Vec<Fix>,
174 ) -> anyhow::Result<()> {
175 let min = ought_data
176 .iter()
177 .min_by(|a, b| a.partial_cmp(b).unwrap())
178 .unwrap();
179 let max = ought_data
180 .iter()
181 .max_by(|a, b| a.partial_cmp(b).unwrap())
182 .unwrap();
183 let value_range = max - min;
184
185 let max_error = (value_range * ERROR_MARGIN_FAC).abs();
186 let inside_margin = |is: f64, ought: f64| -> bool {
187 let delta = (is - ought).abs();
188
189 let res = delta <= max_error;
190 if !res {
191 eprintln!("measured error outside of acceptable margin: {is} <> {ought}");
192 }
193 res
194 };
195
196 let mut ok = true;
197 for (ought, is) in zip(ought_data.iter(), is_data.iter()) {
198 let is = is.to_num::<f64>();
199 if !inside_margin(is, *ought) {
200 ok = false;
201 }
202 }
203
204 if !ok {
205 let root = option_env!("CARGO_MANIFEST_DIR")
206 .ok_or(anyhow!("missing env var CARGO_MANIFEST_DIR"))?;
207 let root = PathBuf::from(root);
208 let target_path = root.join("jupyter-tests");
209
210 let ought_file = File::create(target_path.join(format!("{test_name}-ought.json")))?;
211 let is_file = File::create(target_path.join(format!("{test_name}-is.json")))?;
212
213 let is_converted = is_data
214 .into_iter()
215 .map(|fix| fix.to_num())
216 .collect::<Vec<f64>>();
217 serde_json::to_writer(ought_file, &ought_data)?;
218 serde_json::to_writer(is_file, &is_converted)?;
219
220 panic!("{test_name} outside of error {ERROR_MARGIN_FAC} margin: {is_converted:?} <> {ought_data:?}");
221 }
222 Ok(())
223 }
224
225 #[test]
226 fn at() {
227 let res = ExpInOut::at(Fix::from_num(0.5), Fix::from_num(10.), Fix::from_num(100));
228 assert_eq!(res, Fix::from_num(60.));
229 }
230
231 #[test]
232 fn at_normalized() {
233 let res = ExpInOut::at_normalized(Fix::from_num(0.75));
234 assert_eq!(res, Fix::from_num(0.984375));
235 }
236
237 #[test]
238 fn linear_test() -> anyhow::Result<()> {
239 let model = vec![0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0];
240 let res: Vec<Fix> = linear(Fix::from_num(0), Fix::from_num(1), 10).collect();
241 must_be_withing_error_margin_else_write(function!(), model, res)
242 }
243
244 #[test]
245 fn quad_in_test() -> anyhow::Result<()> {
246 let model = vec![
247 100., 400., 900., 1600., 2500., 3600., 4900., 6400., 8100., 10000.,
248 ];
249 let res: Vec<Fix> = quad_in(Fix::from_num(0), Fix::from_num(10000), 10).collect();
250 must_be_withing_error_margin_else_write(function!(), model, res)
251 }
252
253 #[test]
254 fn quad_out_test() -> anyhow::Result<()> {
255 let model = vec![
256 1900., 3600., 5100., 6400., 7500., 8400., 9100., 9600., 9900., 10000.,
257 ];
258 let res: Vec<Fix> = quad_out(Fix::from_num(0), Fix::from_num(10000), 10).collect();
259 must_be_withing_error_margin_else_write(function!(), model, res)
260 }
261
262 #[test]
263 fn quad_inout_test() -> anyhow::Result<()> {
264 let model = vec![
265 200., 800., 1800., 3200., 5000., 6800., 8200., 9200., 9800., 10000.,
266 ];
267 let res: Vec<Fix> = quad_inout(Fix::from_num(0), Fix::from_num(10000), 10).collect();
268 must_be_withing_error_margin_else_write(function!(), model, res)
269 }
270
271 #[test]
272 fn cubic_in_test() -> anyhow::Result<()> {
273 let model = vec![
274 10., 80., 270., 640., 1250., 2160., 3430., 5120., 7290., 10000.,
275 ];
276 let res: Vec<Fix> = cubic_in(Fix::from_num(0), Fix::from_num(10000), 10).collect();
277 must_be_withing_error_margin_else_write(function!(), model, res)
278 }
279
280 #[test]
281 fn cubic_out_test() -> anyhow::Result<()> {
282 let model = vec![
283 2710., 4880., 6570., 7840., 8750., 9360., 9730., 9920., 9990., 10000.,
284 ];
285 let res: Vec<Fix> = cubic_out(Fix::from_num(0), Fix::from_num(10000), 10).collect();
286 must_be_withing_error_margin_else_write(function!(), model, res)
287 }
288
289 #[test]
290 fn quartic_in_test() -> anyhow::Result<()> {
291 let model = vec![1., 16., 81., 256., 625., 1296., 2401., 4096., 6561., 10000.];
292 let res: Vec<Fix> = quartic_in(Fix::from_num(0), Fix::from_num(10000), 10).collect();
293 must_be_withing_error_margin_else_write(function!(), model, res)
294 }
295
296 #[test]
297 fn quartic_out_test() -> anyhow::Result<()> {
298 let model = vec![
299 3439., 5904., 7599., 8704., 9375., 9744., 9919., 9984., 9999., 10000.,
300 ];
301 let res: Vec<Fix> = quartic_out(Fix::from_num(0), Fix::from_num(10000), 10).collect();
302 must_be_withing_error_margin_else_write(function!(), model, res)
303 }
304
305 #[test]
306 fn quartic_inout_test() -> anyhow::Result<()> {
307 let model = vec![
308 8., 128., 648., 2048., 5000., 7952., 9352., 9872., 9992., 10000.,
309 ];
310 let res: Vec<Fix> = quartic_inout(Fix::from_num(0), Fix::from_num(10000), 10).collect();
311 must_be_withing_error_margin_else_write(function!(), model, res)
312 }
313
314 #[test]
315 fn sin_in_test() -> anyhow::Result<()> {
316 let model = vec![
317 123.116594,
318 489.434837,
319 1089.934758,
320 1909.830056,
321 2928.932188,
322 4122.147477,
323 5460.095003,
324 6909.830056,
325 8435.655350,
326 10000.,
327 ];
328 let res: Vec<Fix> = sin_in(Fix::from_num(0), Fix::from_num(10000), 10).collect();
329 must_be_withing_error_margin_else_write(function!(), model, res)
330 }
331
332 #[test]
333 fn sin_out_test() -> anyhow::Result<()> {
334 let model = vec![
335 1564.344650,
336 3090.169944,
337 4539.904997,
338 5877.852523,
339 7071.067812,
340 8090.169944,
341 8910.065242,
342 9510.565163,
343 9876.883406,
344 10000.,
345 ];
346 let res: Vec<Fix> = sin_out(Fix::from_num(0), Fix::from_num(10000), 10).collect();
347 must_be_withing_error_margin_else_write(function!(), model, res)
348 }
349
350 #[test]
351 fn sin_inout_test() -> anyhow::Result<()> {
352 let model = vec![
353 101.020514,
354 417.424305,
355 1000.,
356 2000.,
357 5000.,
358 8000.,
359 9000.,
360 9582.575695,
361 9898.979486,
362 10000.,
363 ];
364 let res: Vec<Fix> = sin_inout(Fix::from_num(0), Fix::from_num(10000), 10).collect();
365 must_be_withing_error_margin_else_write(function!(), model, res)
366 }
367
368 #[test]
369 fn exp_in_test() -> anyhow::Result<()> {
370 let model = vec![
371 19.53125, 39.0625, 78.125, 156.25, 312.5, 625., 1250., 2500., 5000., 10000.,
372 ];
373 let res: Vec<Fix> = exp_in(Fix::from_num(0), Fix::from_num(10000), 10).collect();
374 must_be_withing_error_margin_else_write(function!(), model, res)
375 }
376
377 #[test]
378 fn exp_out_test() -> anyhow::Result<()> {
379 let model = vec![
380 5000., 7500., 8750., 9375., 9687.5, 9843.75, 9921.875, 9960.9375, 9980.46875, 10000.,
381 ];
382 let res: Vec<Fix> = exp_out(Fix::from_num(0), Fix::from_num(10000), 10).collect();
383 must_be_withing_error_margin_else_write(function!(), model, res)
384 }
385
386 #[test]
387 fn exp_inout_test() -> anyhow::Result<()> {
388 let model = vec![
389 19.53125, 78.125, 312.5, 1250., 5000., 8750., 9687.5, 9921.875, 9980.46875, 10000.,
390 ];
391 let res: Vec<Fix> = exp_inout(Fix::from_num(0), Fix::from_num(10000), 10).collect();
392 must_be_withing_error_margin_else_write(function!(), model, res)
393 }
394
395 #[test]
396 fn smoothstep_test() -> anyhow::Result<()> {
397 let model = vec![
398 280.00000000000006,
399 1040.0000000000002,
400 2160.0,
401 3520.000000000001,
402 5000.0,
403 6480.0,
404 7839.999999999999,
405 8960.000000000002,
406 9720.0,
407 10000.,
408 ];
409 let res: Vec<Fix> = smoothstep(Fix::from_num(0), Fix::from_num(10000), 10).collect();
410 must_be_withing_error_margin_else_write(function!(), model, res)
411 }
412}