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    /// `step(0) = 1`, as a crop's `[l, r)` opens.
99    Step,
100}
101
102impl Unary {
103    pub fn name(self) -> &'static str {
104        match self {
105            Unary::Sin => "sin",
106            Unary::Cos => "cos",
107            Unary::Exp => "exp",
108            Unary::Tanh => "tanh",
109            Unary::Sat => "sat",
110            Unary::Abs => "abs",
111            Unary::Log => "log",
112            Unary::Sqrt => "sqrt",
113            Unary::Step => "step",
114        }
115    }
116
117    pub const ALL: &'static [Unary] = &[
118        Unary::Sin,
119        Unary::Cos,
120        Unary::Exp,
121        Unary::Tanh,
122        Unary::Sat,
123        Unary::Abs,
124        Unary::Log,
125        Unary::Sqrt,
126        Unary::Step,
127    ];
128
129    pub fn from_name(name: &str) -> Option<Unary> {
130        Unary::ALL.iter().copied().find(|u| u.name() == name)
131    }
132
133    /// A is closed under these three on an affine argument and under nothing else.
134    pub fn is_closed(self) -> bool {
135        matches!(self, Unary::Sin | Unary::Cos | Unary::Exp)
136    }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140pub enum Fold {
141    Max,
142    Min,
143    Mod,
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
147pub struct IndexId(pub u32);
148
149#[derive(Clone, Copy, Debug, PartialEq, Eq)]
150pub enum Bound {
151    Finite(i64),
152    Infinite,
153}
154
155/// A value, not an expansion: truncated once, at collapse.
156#[derive(Clone, Debug, PartialEq)]
157pub struct Series {
158    pub index: IndexId,
159    pub lo: i64,
160    pub hi: Bound,
161    pub term: Part,
162}
163
164#[derive(Clone, Copy, Debug, PartialEq)]
165pub struct Mode {
166    pub omega: f64,
167    pub tau: f64,
168    pub amp: f64,
169    pub phase: f64,
170}
171
172#[derive(Clone, Copy, Debug, PartialEq)]
173pub enum Excitation {
174    HammerPulse { f0: f64, t0: f64, contact: f64 },
175    Impulse { t0: f64 },
176}
177
178#[derive(Clone, Debug, PartialEq)]
179pub struct ModalBank {
180    pub modes: Vec<Mode>,
181    pub excite: Excitation,
182}
183
184/// Residues expand on demand, never stored, so one rational has one spelling.
185#[derive(Clone, Debug, PartialEq)]
186pub struct Rational {
187    pub zeros: Vec<C64>,
188    pub poles: Vec<C64>,
189    pub gain: C64,
190}
191
192#[derive(Clone, Debug, PartialEq)]
193pub enum Body {
194    Const(C64),
195    Line,
196    Index(IndexId),
197    /// The unit-interval hash `rand` folds to, and the phase a noise line reads.
198    Keyed {
199        seed: u64,
200        of: Part,
201    },
202    Param(ParamId),
203    Node(NodeId),
204    Add(Vec<Part>),
205    Mul(Vec<Part>),
206    Div(Part, Part),
207    Pow(Part, i32),
208    Apply(Unary, Part),
209    Fold(Fold, Vec<Part>),
210    Delta {
211        at: Part,
212        order: u16,
213    },
214    Pv(Part),
215    Shift {
216        by: f64,
217        of: Part,
218    },
219    /// `of` read at the time `at` names, which is a shift only where `at` is `t` plus a
220    /// constant.
221    Warp {
222        at: Part,
223        of: Part,
224    },
225    Deriv {
226        order: u16,
227        of: Part,
228    },
229    /// `rise` and `fall` are raised-cosine shoulders inside `[l, r)`; zero is a hard edge.
230    Crop {
231        of: Part,
232        l: Edge,
233        r: Edge,
234        rise: f64,
235        fall: f64,
236    },
237    Join(Vec<Part>),
238    Channel(Part, u8),
239    Rational(Rational),
240    Series(Box<Series>),
241    Modal(ModalBank),
242    Run(Box<crate::run::Run>),
243}
244
245/// The subterms of one node: a walker states its recursion once, and every new variant
246/// reaches every walker at once.
247pub fn children(f: &Body) -> Vec<&Part> {
248    match f {
249        Body::Const(_)
250        | Body::Line
251        | Body::Index(_)
252        | Body::Param(_)
253        | Body::Node(_)
254        | Body::Rational(_)
255        | Body::Modal(_)
256        | Body::Run(_) => Vec::new(),
257        Body::Keyed { of, .. } => vec![of],
258        Body::Add(parts) | Body::Mul(parts) | Body::Join(parts) | Body::Fold(_, parts) => {
259            parts.iter().collect()
260        }
261        Body::Div(a, b) | Body::Warp { at: a, of: b } => vec![a, b],
262        Body::Pow(of, _)
263        | Body::Apply(_, of)
264        | Body::Pv(of)
265        | Body::Channel(of, _)
266        | Body::Delta { at: of, .. }
267        | Body::Shift { of, .. }
268        | Body::Deriv { of, .. }
269        | Body::Crop { of, .. } => vec![of],
270        Body::Series(s) => vec![&s.term],
271    }
272}
273
274pub fn map_children(f: &Body, mut g: impl FnMut(&Part) -> Part) -> Body {
275    let one = |p: &Part, g: &mut dyn FnMut(&Part) -> Part| g(p);
276    match f {
277        Body::Const(_)
278        | Body::Line
279        | Body::Index(_)
280        | Body::Param(_)
281        | Body::Node(_)
282        | Body::Rational(_)
283        | Body::Modal(_)
284        | Body::Run(_) => f.clone(),
285        Body::Keyed { seed, of } => Body::Keyed {
286            seed: *seed,
287            of: one(of, &mut g),
288        },
289        Body::Add(parts) => Body::Add(parts.iter().map(g).collect()),
290        Body::Mul(parts) => Body::Mul(parts.iter().map(g).collect()),
291        Body::Join(parts) => Body::Join(parts.iter().map(g).collect()),
292        Body::Fold(op, parts) => Body::Fold(*op, parts.iter().map(g).collect()),
293        Body::Div(a, b) => Body::Div(g(a), g(b)),
294        Body::Warp { at, of } => Body::Warp {
295            at: g(at),
296            of: g(of),
297        },
298        Body::Pow(of, n) => Body::Pow(g(of), *n),
299        Body::Apply(op, of) => Body::Apply(*op, g(of)),
300        Body::Pv(of) => Body::Pv(g(of)),
301        Body::Channel(of, k) => Body::Channel(g(of), *k),
302        Body::Delta { at, order } => Body::Delta {
303            at: g(at),
304            order: *order,
305        },
306        Body::Shift { by, of } => Body::Shift { by: *by, of: g(of) },
307        Body::Deriv { order, of } => Body::Deriv {
308            order: *order,
309            of: g(of),
310        },
311        Body::Crop {
312            of,
313            l,
314            r,
315            rise,
316            fall,
317        } => Body::Crop {
318            of: g(of),
319            l: *l,
320            r: *r,
321            rise: *rise,
322            fall: *fall,
323        },
324        Body::Series(s) => Body::Series(Box::new(Series {
325            term: g(&s.term),
326            ..(**s).clone()
327        })),
328    }
329}
330
331/// `1[l,r)(u - by)` reads the free variable too, so a window moves its own bounds; a warp
332/// moves only its time.
333pub fn shift_line(f: &Body, by: f64) -> Body {
334    let walk = |p: &Part| Part::new(p.origin, shift_line(&p.body, by));
335    match f {
336        Body::Line => Body::Add(vec![
337            Part::bare(Body::Line),
338            Part::bare(Body::Const(crate::complex::C64::real(-by))),
339        ]),
340        Body::Crop {
341            of,
342            l,
343            r,
344            rise,
345            fall,
346        } => Body::Crop {
347            of: walk(of),
348            l: Edge::at(l.value() + by),
349            r: Edge::at(r.value() + by),
350            rise: *rise,
351            fall: *fall,
352        },
353        Body::Warp { at, of } => Body::Warp {
354            at: walk(at),
355            of: of.clone(),
356        },
357        other => map_children(other, walk),
358    }
359}
360
361/// An edge holds no formula, so a window is warped whole.
362pub fn read_at(f: &Body, at: &Body) -> Body {
363    match f {
364        Body::Line => at.clone(),
365        Body::Crop { .. } => Body::Warp {
366            at: Part::bare(at.clone()),
367            of: Part::bare(f.clone()),
368        },
369        Body::Warp { at: inner, of } => Body::Warp {
370            at: Part::new(inner.origin, read_at(&inner.body, at)),
371            of: of.clone(),
372        },
373        other => map_children(other, |p| Part::new(p.origin, read_at(&p.body, at))),
374    }
375}
376
377/// A leaf substitution cannot walk into: the free variable hides behind a name.
378pub fn shifts_opaquely(f: &Body) -> bool {
379    matches!(
380        f,
381        Body::Node(_) | Body::Param(_) | Body::Rational(_) | Body::Modal(_) | Body::Run(_)
382    ) || children(f).iter().any(|p| shifts_opaquely(&p.body))
383}