Skip to main content

sva_formula/
closed_form.rs

1// Concern: declares ClosedForm and its Body, as written before normalization | Non-concern: the canonical atom sum (spectral_sum/) | IO: none
2
3use crate::complex::C64;
4use crate::env::{NodeId, ParamId};
5use crate::origin::Origin;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum Var {
9    T,
10    F,
11}
12
13impl Var {
14    pub fn as_str(self) -> &'static str {
15        match self {
16            Var::T => "t",
17            Var::F => "f",
18        }
19    }
20}
21
22/// One `Var` for the whole of it: `t` and `f` never meet inside one.
23#[derive(Clone, Debug, PartialEq)]
24pub struct ClosedForm {
25    pub var: Var,
26    pub body: Body,
27    pub origin: Origin,
28}
29
30#[derive(Clone, Debug, PartialEq)]
31pub struct Part {
32    pub origin: Origin,
33    pub body: Box<Body>,
34}
35
36impl Part {
37    pub fn new(origin: Origin, body: Body) -> Part {
38        Part {
39            origin,
40            body: Box::new(body),
41        }
42    }
43
44    pub fn bare(body: Body) -> Part {
45        Part::new(Origin::UNKNOWN, body)
46    }
47}
48
49/// An infinite bound is a variant, so no sort key or hash ever sees a non-finite `f64`.
50#[derive(Clone, Copy, Debug, PartialEq, Eq)]
51pub enum Edge {
52    NegInf,
53    At(u64),
54    PosInf,
55}
56
57/// Numeric: by bit pattern a negative bound sorts above every positive one.
58impl Ord for Edge {
59    fn cmp(&self, other: &Edge) -> std::cmp::Ordering {
60        self.value().total_cmp(&other.value())
61    }
62}
63
64impl PartialOrd for Edge {
65    fn partial_cmp(&self, other: &Edge) -> Option<std::cmp::Ordering> {
66        Some(self.cmp(other))
67    }
68}
69
70impl Edge {
71    pub fn at(x: f64) -> Edge {
72        match x {
73            x if x == f64::NEG_INFINITY => Edge::NegInf,
74            x if x == f64::INFINITY => Edge::PosInf,
75            x => Edge::At(crate::complex::canonical(x)),
76        }
77    }
78
79    pub fn value(self) -> f64 {
80        match self {
81            Edge::NegInf => f64::NEG_INFINITY,
82            Edge::At(bits) => f64::from_bits(bits),
83            Edge::PosInf => f64::INFINITY,
84        }
85    }
86}
87
88#[derive(Clone, Copy, Debug, PartialEq, Eq)]
89pub enum Unary {
90    Sin,
91    Cos,
92    Exp,
93    Tanh,
94    Sat,
95    Abs,
96    Log,
97    Sqrt,
98}
99
100impl Unary {
101    pub fn name(self) -> &'static str {
102        match self {
103            Unary::Sin => "sin",
104            Unary::Cos => "cos",
105            Unary::Exp => "exp",
106            Unary::Tanh => "tanh",
107            Unary::Sat => "sat",
108            Unary::Abs => "abs",
109            Unary::Log => "log",
110            Unary::Sqrt => "sqrt",
111        }
112    }
113
114    pub const ALL: &'static [Unary] = &[
115        Unary::Sin,
116        Unary::Cos,
117        Unary::Exp,
118        Unary::Tanh,
119        Unary::Sat,
120        Unary::Abs,
121        Unary::Log,
122        Unary::Sqrt,
123    ];
124
125    pub fn from_name(name: &str) -> Option<Unary> {
126        Unary::ALL.iter().copied().find(|u| u.name() == name)
127    }
128
129    /// A is closed under these three on an affine argument and under nothing else.
130    pub fn is_closed(self) -> bool {
131        matches!(self, Unary::Sin | Unary::Cos | Unary::Exp)
132    }
133}
134
135#[derive(Clone, Copy, Debug, PartialEq, Eq)]
136pub enum Fold {
137    Max,
138    Min,
139    Mod,
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
143pub struct IndexId(pub u32);
144
145#[derive(Clone, Copy, Debug, PartialEq, Eq)]
146pub enum Bound {
147    Finite(i64),
148    Infinite,
149}
150
151/// A value, not an expansion: truncated once, at collapse.
152#[derive(Clone, Debug, PartialEq)]
153pub struct Series {
154    pub index: IndexId,
155    pub lo: i64,
156    pub hi: Bound,
157    pub term: Part,
158}
159
160#[derive(Clone, Copy, Debug, PartialEq)]
161pub struct Mode {
162    pub omega: f64,
163    pub tau: f64,
164    pub amp: f64,
165    pub phase: f64,
166}
167
168#[derive(Clone, Copy, Debug, PartialEq)]
169pub enum Excitation {
170    HammerPulse { f0: f64, t0: f64, contact: f64 },
171    Impulse { t0: f64 },
172}
173
174#[derive(Clone, Debug, PartialEq)]
175pub struct ModalBank {
176    pub modes: Vec<Mode>,
177    pub excite: Excitation,
178}
179
180/// Residues expand on demand, never stored, so one rational has one spelling.
181#[derive(Clone, Debug, PartialEq)]
182pub struct Rational {
183    pub zeros: Vec<C64>,
184    pub poles: Vec<C64>,
185    pub gain: C64,
186}
187
188#[derive(Clone, Debug, PartialEq)]
189pub enum Body {
190    Const(C64),
191    Line,
192    Index(IndexId),
193    /// The unit-interval hash `rand` folds to, and the phase a noise line reads.
194    Keyed {
195        seed: u64,
196        of: Part,
197    },
198    Param(ParamId),
199    Node(NodeId),
200    Add(Vec<Part>),
201    Mul(Vec<Part>),
202    Div(Part, Part),
203    Pow(Part, i32),
204    Apply(Unary, Part),
205    Fold(Fold, Vec<Part>),
206    Delta {
207        at: Part,
208        order: u16,
209    },
210    Pv(Part),
211    Shift {
212        by: f64,
213        of: Part,
214    },
215    /// `of` read at the time `at` names, which is a shift only where `at` is `t` plus a
216    /// constant.
217    Warp {
218        at: Part,
219        of: Part,
220    },
221    Deriv {
222        order: u16,
223        of: Part,
224    },
225    /// `rise` and `fall` are raised-cosine shoulders inside `[l, r)`; zero is a hard edge.
226    Crop {
227        of: Part,
228        l: Edge,
229        r: Edge,
230        rise: f64,
231        fall: f64,
232    },
233    Join(Vec<Part>),
234    Channel(Part, u8),
235    Rational(Rational),
236    Series(Box<Series>),
237    Modal(ModalBank),
238}
239
240/// The subterms of one node: a walker states its recursion once, and every new variant
241/// reaches every walker at once.
242pub fn children(f: &Body) -> Vec<&Part> {
243    match f {
244        Body::Const(_)
245        | Body::Line
246        | Body::Index(_)
247        | Body::Param(_)
248        | Body::Node(_)
249        | Body::Rational(_)
250        | Body::Modal(_) => Vec::new(),
251        Body::Keyed { of, .. } => vec![of],
252        Body::Add(parts) | Body::Mul(parts) | Body::Join(parts) | Body::Fold(_, parts) => {
253            parts.iter().collect()
254        }
255        Body::Div(a, b) | Body::Warp { at: a, of: b } => vec![a, b],
256        Body::Pow(of, _)
257        | Body::Apply(_, of)
258        | Body::Pv(of)
259        | Body::Channel(of, _)
260        | Body::Delta { at: of, .. }
261        | Body::Shift { of, .. }
262        | Body::Deriv { of, .. }
263        | Body::Crop { of, .. } => vec![of],
264        Body::Series(s) => vec![&s.term],
265    }
266}
267
268pub fn map_children(f: &Body, mut g: impl FnMut(&Part) -> Part) -> Body {
269    let one = |p: &Part, g: &mut dyn FnMut(&Part) -> Part| g(p);
270    match f {
271        Body::Const(_)
272        | Body::Line
273        | Body::Index(_)
274        | Body::Param(_)
275        | Body::Node(_)
276        | Body::Rational(_)
277        | Body::Modal(_) => f.clone(),
278        Body::Keyed { seed, of } => Body::Keyed {
279            seed: *seed,
280            of: one(of, &mut g),
281        },
282        Body::Add(parts) => Body::Add(parts.iter().map(g).collect()),
283        Body::Mul(parts) => Body::Mul(parts.iter().map(g).collect()),
284        Body::Join(parts) => Body::Join(parts.iter().map(g).collect()),
285        Body::Fold(op, parts) => Body::Fold(*op, parts.iter().map(g).collect()),
286        Body::Div(a, b) => Body::Div(g(a), g(b)),
287        Body::Warp { at, of } => Body::Warp {
288            at: g(at),
289            of: g(of),
290        },
291        Body::Pow(of, n) => Body::Pow(g(of), *n),
292        Body::Apply(op, of) => Body::Apply(*op, g(of)),
293        Body::Pv(of) => Body::Pv(g(of)),
294        Body::Channel(of, k) => Body::Channel(g(of), *k),
295        Body::Delta { at, order } => Body::Delta {
296            at: g(at),
297            order: *order,
298        },
299        Body::Shift { by, of } => Body::Shift { by: *by, of: g(of) },
300        Body::Deriv { order, of } => Body::Deriv {
301            order: *order,
302            of: g(of),
303        },
304        Body::Crop {
305            of,
306            l,
307            r,
308            rise,
309            fall,
310        } => Body::Crop {
311            of: g(of),
312            l: *l,
313            r: *r,
314            rise: *rise,
315            fall: *fall,
316        },
317        Body::Series(s) => Body::Series(Box::new(Series {
318            term: g(&s.term),
319            ..(**s).clone()
320        })),
321    }
322}
323
324/// The whole of shifting a series. `1[l,r)(u - by)` reads the free variable too, so a
325/// window moves its own bounds.
326pub fn shift_line(f: &Body, by: f64) -> Body {
327    let walk = |p: &Part| Part::new(p.origin, shift_line(&p.body, by));
328    match f {
329        Body::Line => Body::Add(vec![
330            Part::bare(Body::Line),
331            Part::bare(Body::Const(crate::complex::C64::real(-by))),
332        ]),
333        Body::Crop {
334            of,
335            l,
336            r,
337            rise,
338            fall,
339        } => Body::Crop {
340            of: walk(of),
341            l: Edge::at(l.value() + by),
342            r: Edge::at(r.value() + by),
343            rise: *rise,
344            fall: *fall,
345        },
346        other => map_children(other, walk),
347    }
348}
349
350/// A leaf substitution cannot walk into: the free variable hides behind a name.
351pub fn shifts_opaquely(f: &Body) -> bool {
352    matches!(
353        f,
354        Body::Node(_) | Body::Param(_) | Body::Rational(_) | Body::Modal(_)
355    ) || children(f).iter().any(|p| shifts_opaquely(&p.body))
356}