Skip to main content

sva_samples/machine/
renderer.rs

1// Concern: the node renderer sva-engine hands this crate | Non-concern: building it (sva-engine lower.rs), running it (mod.rs, ops.rs) | IO: none
2
3use sva_formula::{Body, C64, Shape, SpectralSum};
4
5use crate::collapse::Extent;
6use crate::error::CollapseError;
7use crate::physics::Params;
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub struct BufId(pub u32);
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub struct SiteId(pub u32);
14
15/// Reader sample `n` reads source sample `(a*n + b)/d`, `d > 0`: exactly, or rounded down or
16/// to the nearest, ties to even. An exact map is whole, `d = 1`; nothing reads between samples.
17#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
18pub struct Map {
19    pub a: i128,
20    pub b: i128,
21    pub d: i128,
22    pub between: Between,
23}
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
26pub enum Between {
27    Exact,
28    Floor,
29    Even,
30}
31
32impl Map {
33    pub const fn shift(b: i64) -> Map {
34        Map::whole(1, b)
35    }
36
37    /// Reader sample `n` reads source sample `a*n + b`.
38    pub const fn whole(a: i128, b: i64) -> Map {
39        Map {
40            a,
41            b: b as i128,
42            d: 1,
43            between: Between::Exact,
44        }
45    }
46
47    /// Lowest terms; `None` where a sample lands between two, or past what a map holds.
48    pub fn new(a: i128, b: i128, d: i128) -> Option<Map> {
49        Map::rounded(a, b, d, Between::Exact)
50    }
51
52    /// A rounding every sample takes alike is folded into an exact map.
53    pub fn rounded(a: i128, b: i128, d: i128, between: Between) -> Option<Map> {
54        let g = gcd(gcd(a.abs(), b.abs()), d.abs()).max(1);
55        let sign = d.signum();
56        let limit = 1i128 << 100;
57        let map = Map {
58            a: sign * a / g,
59            b: sign * b / g,
60            d: d.abs() / g,
61            between,
62        };
63        let held = map.d > 0 && map.a.abs() < limit && map.b.abs() < limit && map.d < limit;
64        held.then(|| map.settled())
65            .filter(|m| m.d == 1 || m.between != Between::Exact)
66    }
67
68    fn settled(self) -> Map {
69        let exact = match self.between {
70            _ if self.d == 1 => true,
71            Between::Exact => false,
72            Between::Floor => self.a % self.d == 0,
73            Between::Even => {
74                let tie = 2 * self.b.rem_euclid(self.d) == self.d;
75                self.a % self.d == 0 && (!tie || (self.a / self.d) % 2 == 0)
76            }
77        };
78        match exact {
79            true => Map {
80                a: self.a / self.d,
81                b: self.index_at(0),
82                d: 1,
83                between: Between::Exact,
84            },
85            false => self,
86        }
87    }
88
89    pub fn at(self, n: i64) -> i64 {
90        let clamp = |k: i128| k.clamp(i128::from(i64::MIN), i128::from(i64::MAX)) as i64;
91        self.whole_at(n)
92            .unwrap_or_else(|| clamp(self.index_at(i128::from(n))))
93    }
94
95    fn whole_at(self, n: i64) -> Option<i64> {
96        if self.d != 1 {
97            return None;
98        }
99        let (a, b) = (i64::try_from(self.a).ok()?, i64::try_from(self.b).ok()?);
100        a.checked_mul(n)?.checked_add(b)
101    }
102
103    pub fn ahead(self) -> bool {
104        self.a != self.d || self.lead() > 0
105    }
106
107    pub fn lead(self) -> i64 {
108        let most = match self.between {
109            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
110            Between::Even => (2 * self.b + self.d).div_euclid(2 * self.d),
111        };
112        most.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
113    }
114
115    pub fn least(self) -> i64 {
116        let least = match self.between {
117            Between::Exact | Between::Floor => self.b.div_euclid(self.d),
118            Between::Even => (2 * self.b + self.d - 1).div_euclid(2 * self.d),
119        };
120        least.clamp(i128::from(i64::MIN / 2), i128::from(i64::MAX / 2)) as i64
121    }
122
123    pub fn image(self, over: Extent) -> Extent {
124        if over.is_empty() {
125            return over;
126        }
127        if self.a == 0 {
128            let at = self.at(0);
129            return Extent::new(at, at.saturating_add(1));
130        }
131        let ends = (first(over), last(over));
132        let at = |n: Option<i128>| n.map(|n| self.index_at(n));
133        let (lo, hi) = match self.a > 0 {
134            true => (at(ends.0), at(ends.1)),
135            false => (at(ends.1), at(ends.0)),
136        };
137        extent(lo, hi.map(|h| h + 1))
138    }
139
140    /// The reader samples whose reading lands in `into`. Rounding to even reads at most one
141    /// below rounding half up.
142    pub fn preimage(self, into: Extent) -> Extent {
143        if into.is_empty() {
144            return into;
145        }
146        if self.between == Between::Even {
147            let up = Map {
148                a: 2 * self.a,
149                b: 2 * self.b + self.d,
150                d: 2 * self.d,
151                between: Between::Floor,
152            };
153            let end = match into.end {
154                i64::MAX => i64::MAX,
155                e => e.saturating_add(1),
156            };
157            return up.preimage(Extent::new(into.start, end));
158        }
159        if self.a == 0 {
160            return match into.contains(self.at(0)) {
161                true => Extent::EVERYWHERE,
162                false => Extent::NOWHERE,
163            };
164        }
165        let (a, b, d) = (self.a, self.b, self.d);
166        let (first, last) = (first(into), last(into));
167        let lowest = |m: i128| ceil_div(m * d - b, a);
168        let highest = |m: i128| floor_div((m + 1) * d - 1 - b, a);
169        let (lo, hi) = match self.a > 0 {
170            true => (first.map(lowest), last.map(highest)),
171            false => (
172                last.map(|m| ceil_div((m + 1) * d - 1 - b, a)),
173                first.map(|m| floor_div(m * d - b, a)),
174            ),
175        };
176        extent(lo, hi.map(|h| h + 1))
177    }
178
179    fn index_at(self, n: i128) -> i128 {
180        let num = self.a.saturating_mul(n).saturating_add(self.b);
181        let (floor, rem) = (num.div_euclid(self.d), num.rem_euclid(self.d));
182        match self.between {
183            Between::Exact | Between::Floor => floor,
184            Between::Even => match (2 * rem).cmp(&self.d) {
185                std::cmp::Ordering::Less => floor,
186                std::cmp::Ordering::Greater => floor + 1,
187                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
188            },
189        }
190    }
191}
192
193fn gcd(a: i128, b: i128) -> i128 {
194    match b {
195        0 => a,
196        b => gcd(b, a % b),
197    }
198}
199
200fn first(e: Extent) -> Option<i128> {
201    (e.start != i64::MIN).then(|| i128::from(e.start))
202}
203
204fn last(e: Extent) -> Option<i128> {
205    (e.end != i64::MAX).then(|| i128::from(e.end) - 1)
206}
207
208fn floor_div(num: i128, den: i128) -> i128 {
209    match den < 0 {
210        true => (-num).div_euclid(-den),
211        false => num.div_euclid(den),
212    }
213}
214
215fn ceil_div(num: i128, den: i128) -> i128 {
216    -floor_div(-num, den)
217}
218
219fn extent(lo: Option<i128>, hi: Option<i128>) -> Extent {
220    let clamp = |n: i128| n.clamp(i128::from(i64::MIN + 1), i128::from(i64::MAX - 1)) as i64;
221    let (start, end) = (lo.map_or(i64::MIN, clamp), hi.map_or(i64::MAX, clamp));
222    match start < end {
223        true => Extent::new(start, end),
224        false => Extent::NOWHERE,
225    }
226}
227
228/// Sample `n` stands at `a*n/d` samples of `rate`, in lowest terms, `a, d > 0`: every grid
229/// starts at t = 0, so a sample index is the one clock every read and key counts in.
230#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
231pub struct Grid {
232    pub rate: u32,
233    pub a: i128,
234    pub d: i128,
235}
236
237impl Grid {
238    pub const fn of(rate: u32) -> Grid {
239        Grid { rate, a: 1, d: 1 }
240    }
241
242    /// `scale` samples to each of `rate`'s, as an alias reference reads it.
243    pub fn finer(rate: u32, scale: usize) -> Grid {
244        Grid {
245            rate,
246            a: 1,
247            d: scale as i128,
248        }
249    }
250
251    pub fn is_rate(&self) -> bool {
252        (self.a, self.d) == (1, 1)
253    }
254
255    fn exact(&self, n: i64) -> Option<(i128, i128)> {
256        let num = self.a.checked_mul(i128::from(n))?;
257        Some((num, self.d.checked_mul(i128::from(self.rate))?))
258    }
259
260    /// One quotient: correctly rounded while both integers are under 2^53; past that each
261    /// integer rounds once converting and the quotient once more.
262    pub fn instant(&self, n: i64) -> f64 {
263        if self.is_rate() {
264            return n as f64 / f64::from(self.rate);
265        }
266        let num = self.a.saturating_mul(i128::from(n));
267        num as f64 / self.d.saturating_mul(i128::from(self.rate)) as f64
268    }
269
270    pub fn position(&self, n: i64) -> f64 {
271        self.a.saturating_mul(i128::from(n)) as f64 / self.d as f64
272    }
273
274    pub fn sr(&self) -> f64 {
275        f64::from(self.rate) * self.d as f64 / self.a as f64
276    }
277
278    pub fn count(&self, t: f64) -> f64 {
279        t * f64::from(self.rate) * self.d as f64 / self.a as f64
280    }
281
282    /// The step `t` falls nearest, rounded exactly from `t`'s own binary value; `None` where
283    /// that is past what the integers hold.
284    pub fn step_at(&self, t: f64, round: Round) -> Option<i64> {
285        if !t.is_finite() {
286            return None;
287        }
288        let bits = t.abs().to_bits();
289        let (exp, frac) = ((bits >> 52) as i32, (bits & ((1 << 52) - 1)) as i128);
290        let (mantissa, shift) = match exp {
291            0 => (frac, 1074),
292            e => (frac | (1 << 52), 1075 - e),
293        };
294        let zeros = mantissa.trailing_zeros().min(127) as i32;
295        let (mantissa, shift) = match mantissa {
296            0 => (0, 0),
297            m => (m >> zeros, shift - zeros),
298        };
299        let mantissa = if t < 0.0 { -mantissa } else { mantissa };
300        let (num, den) = match shift {
301            s if s <= 0 => (mantissa.checked_mul(1i128.checked_shl((-s) as u32)?)?, 1),
302            s if s < 127 => (mantissa, 1i128 << s),
303            _ => return None,
304        };
305        let top = num.checked_mul(self.d.checked_mul(i128::from(self.rate))?)?;
306        let bottom = self.a.checked_mul(den)?;
307        let (floor, rem) = (top.div_euclid(bottom), top.rem_euclid(bottom));
308        let k = match round {
309            Round::Floor => floor,
310            Round::Ceil => floor + i128::from(rem != 0),
311            Round::Even => match (2 * rem).cmp(&bottom) {
312                std::cmp::Ordering::Less => floor,
313                std::cmp::Ordering::Greater => floor + 1,
314                std::cmp::Ordering::Equal => floor + floor.rem_euclid(2),
315            },
316        };
317        i64::try_from(k).ok()
318    }
319}
320
321/// `scale*t + shift + gain*((inner_scale*t + inner_shift) mod period)` at sample `n`'s
322/// instant, each a rational `(num, den)` with `den > 0` and `period > 0`. The remainder and
323/// the sum are integers over one denominator, so which side of a jump an instant falls on is
324/// decided exactly and only the quotient that states the sum rounds.
325#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
326pub struct Wrap {
327    pub scale: (i128, i128),
328    pub shift: (i128, i128),
329    pub gain: (i128, i128),
330    pub inner: [(i128, i128); 2],
331    pub period: (i128, i128),
332}
333
334impl Wrap {
335    /// `None` where its constants overflow.
336    pub fn on(self, grid: Grid) -> Option<Stepped> {
337        let (_, rate) = grid.exact(0)?;
338        let [(s, sd), (o, od)] = self.inner;
339        let over = sd.checked_mul(rate)?;
340        let den = lcm(lcm(over, od)?, self.period.1)?;
341        let line = self.scale.1.checked_mul(rate)?;
342        let wrapped = self.gain.1.checked_mul(den)?;
343        let whole = lcm(lcm(line, self.shift.1)?, wrapped)?;
344        Some(Stepped {
345            step: grid.a,
346            inner: s,
347            over: den / over,
348            offset: o.checked_mul(den / od)?,
349            period: self.period.0.checked_mul(den / self.period.1)?,
350            scale: self.scale.0,
351            line: whole / line,
352            shift: self.shift.0.checked_mul(whole / self.shift.1)?,
353            gain: self.gain.0,
354            wrapped: whole / wrapped,
355            whole,
356        })
357    }
358
359    /// The largest magnitude it takes over instants no later than `t`.
360    pub fn most(self, t: f64) -> f64 {
361        let q = |(num, den): (i128, i128)| num as f64 / den as f64;
362        q(self.scale).abs() * t.abs() + q(self.shift).abs() + q(self.gain).abs() * q(self.period)
363    }
364}
365
366#[derive(Clone, Copy, Debug, PartialEq, Eq)]
367pub struct Stepped {
368    step: i128,
369    inner: i128,
370    over: i128,
371    offset: i128,
372    period: i128,
373    scale: i128,
374    line: i128,
375    shift: i128,
376    gain: i128,
377    wrapped: i128,
378    whole: i128,
379}
380
381impl Stepped {
382    /// `None` where the integers it is computed in would overflow.
383    pub fn at(self, n: i64) -> Option<f64> {
384        let n = self.step.checked_mul(i128::from(n))?;
385        let x = self
386            .inner
387            .checked_mul(n)?
388            .checked_mul(self.over)?
389            .checked_add(self.offset)?;
390        let rem = x.rem_euclid(self.period);
391        let sum = self
392            .scale
393            .checked_mul(n)?
394            .checked_mul(self.line)?
395            .checked_add(self.shift)?
396            .checked_add(self.gain.checked_mul(rem)?.checked_mul(self.wrapped)?)?;
397        Some(sum as f64 / self.whole as f64)
398    }
399}
400
401fn lcm(a: i128, b: i128) -> Option<i128> {
402    (a / gcd(a, b)).checked_mul(b)
403}
404
405#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
406pub enum Round {
407    Even,
408    Floor,
409    Ceil,
410}
411
412/// An integer every sample evaluates exactly, `None` past `i64`.
413#[derive(Clone, Debug, PartialEq)]
414pub enum Index<T = Box<NodeRenderer>> {
415    At(Map),
416    Step(T, Round),
417    Add(Vec<Index<T>>),
418    Neg(Box<Index<T>>),
419    Mul(Vec<Index<T>>),
420}
421
422impl<T> Index<T> {
423    pub fn times(&self) -> Vec<&T> {
424        let mut out = Vec::new();
425        self.each(&mut |t| out.push(t));
426        out
427    }
428
429    fn each<'a>(&'a self, f: &mut impl FnMut(&'a T)) {
430        match self {
431            Index::At(_) => {}
432            Index::Step(t, _) => f(t),
433            Index::Add(parts) | Index::Mul(parts) => parts.iter().for_each(|p| p.each(f)),
434            Index::Neg(p) => p.each(f),
435        }
436    }
437
438    pub fn mapped<U, E>(&self, f: &mut impl FnMut(&T) -> Result<U, E>) -> Result<Index<U>, E> {
439        let mut each = |parts: &[Index<T>]| -> Result<Vec<Index<U>>, E> {
440            parts.iter().map(|p| p.mapped(f)).collect()
441        };
442        Ok(match self {
443            Index::At(map) => Index::At(*map),
444            Index::Step(t, round) => Index::Step(f(t)?, *round),
445            Index::Add(parts) => Index::Add(each(parts)?),
446            Index::Mul(parts) => Index::Mul(each(parts)?),
447            Index::Neg(p) => Index::Neg(Box::new(p.mapped(f)?)),
448        })
449    }
450
451    pub fn at(&self, n: i64, grid: Grid, time: &impl Fn(&T) -> f64) -> Option<i64> {
452        match self {
453            Index::At(map) => map
454                .whole_at(n)
455                .or_else(|| i64::try_from(map.index_at(i128::from(n))).ok()),
456            Index::Step(t, round) => grid.step_at(time(t), *round),
457            Index::Add(parts) => parts
458                .iter()
459                .try_fold(0i64, |held, p| held.checked_add(p.at(n, grid, time)?)),
460            Index::Mul(parts) => parts
461                .iter()
462                .try_fold(1i64, |held, p| held.checked_mul(p.at(n, grid, time)?)),
463            Index::Neg(p) => p.at(n, grid, time)?.checked_neg(),
464        }
465    }
466}
467
468/// Another node's samples, or this node's own past.
469#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
470pub enum Slot {
471    Read(BufId),
472    Own,
473}
474
475#[derive(Clone, Copy, Debug, PartialEq, Eq)]
476pub enum Unary {
477    Sin,
478    Cos,
479    Exp,
480    Sqrt,
481    Abs,
482    Tanh,
483    Log,
484    Sat,
485    Step,
486}
487
488#[derive(Clone, Copy, Debug, PartialEq, Eq)]
489pub enum Binary {
490    Max,
491    Min,
492    Mod,
493}
494
495/// What the machine evaluates at an instant it computes, a closed form truncated to the band
496/// or the noise.
497#[derive(Clone, Debug, PartialEq)]
498pub enum Formula {
499    Sum(Box<SpectralSum>),
500    Written(Box<Body>),
501    Drawn { seed: u64, rate: u32 },
502}
503
504impl Formula {
505    pub fn at(&self, component: usize, t: f64) -> Result<f64, CollapseError> {
506        let value: C64 =
507            match self {
508                Formula::Drawn { seed, rate } => {
509                    let step = Grid::of(*rate).step_at(t, Round::Even).ok_or(
510                        CollapseError::NotEvaluable("a draw at an instant past any step"),
511                    )?;
512                    return Ok(sva_formula::draw(*seed, step));
513                }
514                Formula::Sum(sum) => crate::collapse::eval_spectral_sum_at(sum, component, t)?,
515                Formula::Written(body) => {
516                    crate::collapse::eval_written_at(body, component, t, &crate::collapse::NoRefs)?
517                }
518            };
519        Ok(value.re)
520    }
521
522    /// One operation per atom or written subterm.
523    pub fn ops(&self) -> usize {
524        fn terms(body: &Body) -> usize {
525            1 + sva_formula::closed_form::children(body)
526                .iter()
527                .map(|p| terms(&p.body))
528                .sum::<usize>()
529        }
530        match self {
531            Formula::Sum(sum) => sum.atoms().count().max(1),
532            Formula::Written(body) => terms(body),
533            Formula::Drawn { .. } => 1,
534        }
535    }
536}
537
538/// Every closed form-typed subterm was collapsed to a buffer, inlined or held as a formula
539/// before this tree was built, so there is no oscillator, no series and no delta here.
540#[derive(Clone, Debug, PartialEq)]
541pub enum NodeRenderer {
542    Const(f64),
543    Time,
544    Wrap(Wrap),
545    Read {
546        slot: Slot,
547        map: Map,
548    },
549    /// A closed form at the instant `time` names, `width` components wide.
550    Formula {
551        formula: Formula,
552        width: usize,
553        time: Box<NodeRenderer>,
554    },
555    Noise(u64),
556    /// The stored sample at the integer `index` names, within `reach` of the sample being
557    /// written where that is known.
558    Indexed {
559        slot: Slot,
560        index: Index,
561        reach: Option<(i64, i64)>,
562    },
563    /// The instant of the sample `index` names.
564    Instant(Index),
565    Add(Vec<NodeRenderer>),
566    Mul(Vec<NodeRenderer>),
567    Sub(Box<NodeRenderer>, Box<NodeRenderer>),
568    Div(Box<NodeRenderer>, Box<NodeRenderer>),
569    Pow(Box<NodeRenderer>, Box<NodeRenderer>),
570    Map(Unary, Box<NodeRenderer>),
571    Zip(Binary, Box<NodeRenderer>, Box<NodeRenderer>),
572    /// `x` over the samples `window` holds, the instants `[a, b)` it was written as, with a
573    /// raised-cosine `rise` and `fall` inside it; zero at every sample outside.
574    Crop {
575        x: Box<NodeRenderer>,
576        window: (i64, i64),
577        a: f64,
578        b: f64,
579        rise: f64,
580        fall: f64,
581    },
582    Join(Vec<NodeRenderer>),
583    Channel {
584        x: Box<NodeRenderer>,
585        k: usize,
586    },
587    /// Zero, stepping nothing, before `from`, where its state starts.
588    Filter {
589        site: SiteId,
590        from: i64,
591        x: Box<NodeRenderer>,
592        cutoff: Box<NodeRenderer>,
593        q: Box<NodeRenderer>,
594        gain: Box<NodeRenderer>,
595    },
596    Physics {
597        site: SiteId,
598        from: i64,
599        args: Vec<NodeRenderer>,
600    },
601}
602
603impl NodeRenderer {
604    /// Holds no call site and reads none of its own past, so skipping a sample of it changes
605    /// no later one.
606    pub fn stateless(&self) -> bool {
607        !self.holds_state() && self.operands().into_iter().all(NodeRenderer::stateless)
608    }
609
610    pub(crate) fn holds_state(&self) -> bool {
611        matches!(
612            self,
613            NodeRenderer::Filter { .. }
614                | NodeRenderer::Physics { .. }
615                | NodeRenderer::Read {
616                    slot: Slot::Own,
617                    ..
618                }
619                | NodeRenderer::Indexed {
620                    slot: Slot::Own,
621                    ..
622                }
623        )
624    }
625
626    /// Every operand in the order the op array lowers them.
627    pub(crate) fn operands(&self) -> Vec<&NodeRenderer> {
628        match self {
629            NodeRenderer::Add(set) | NodeRenderer::Mul(set) | NodeRenderer::Join(set) => {
630                set.iter().collect()
631            }
632            NodeRenderer::Sub(a, b)
633            | NodeRenderer::Div(a, b)
634            | NodeRenderer::Pow(a, b)
635            | NodeRenderer::Zip(_, a, b) => vec![a, b],
636            NodeRenderer::Map(_, x)
637            | NodeRenderer::Crop { x, .. }
638            | NodeRenderer::Channel { x, .. } => vec![x],
639            NodeRenderer::Filter {
640                x, cutoff, q, gain, ..
641            } => vec![x, cutoff, q, gain],
642            NodeRenderer::Physics { args, .. } => args.iter().collect(),
643            NodeRenderer::Formula { time, .. } => vec![time],
644            NodeRenderer::Indexed { index, .. } | NodeRenderer::Instant(index) => {
645                index.times().into_iter().map(|t| &**t).collect()
646            }
647            NodeRenderer::Const(_)
648            | NodeRenderer::Time
649            | NodeRenderer::Wrap(_)
650            | NodeRenderer::Noise(_)
651            | NodeRenderer::Read { .. } => Vec::new(),
652        }
653    }
654}
655
656#[derive(Clone, Debug, PartialEq)]
657pub enum Site {
658    Filter(Shape),
659    Physics(Box<Params>),
660}
661
662impl From<sva_formula::Unary> for Unary {
663    fn from(written: sva_formula::Unary) -> Unary {
664        match written {
665            sva_formula::Unary::Sin => Unary::Sin,
666            sva_formula::Unary::Cos => Unary::Cos,
667            sva_formula::Unary::Exp => Unary::Exp,
668            sva_formula::Unary::Sqrt => Unary::Sqrt,
669            sva_formula::Unary::Abs => Unary::Abs,
670            sva_formula::Unary::Tanh => Unary::Tanh,
671            sva_formula::Unary::Log => Unary::Log,
672            sva_formula::Unary::Sat => Unary::Sat,
673            sva_formula::Unary::Step => Unary::Step,
674        }
675    }
676}
677
678impl Unary {
679    pub fn apply(self, x: f64) -> f64 {
680        match self {
681            Unary::Sin => x.sin(),
682            Unary::Cos => x.cos(),
683            Unary::Exp => x.exp(),
684            Unary::Sqrt => x.sqrt(),
685            Unary::Abs => x.abs(),
686            Unary::Tanh => x.tanh(),
687            Unary::Log => x.ln(),
688            Unary::Sat => x.clamp(-1.0, 1.0),
689            Unary::Step => sva_formula::affine::step(x),
690        }
691    }
692}
693
694impl Binary {
695    pub fn apply(self, a: f64, b: f64) -> f64 {
696        match self {
697            Binary::Max => a.max(b),
698            Binary::Min => a.min(b),
699            Binary::Mod => a.rem_euclid(b),
700        }
701    }
702}