Skip to main content

sva_engine/refs/
identity.rs

1// Concern: content-addresses one node, whatever representation it holds | Non-concern: composing a closed form across a ref (mod.rs) | IO: (NodeId) -> Hash
2
3use std::collections::BTreeMap;
4
5use sva_formula::{
6    ClosedForm, Hash, NodeId, Var, hash_closed_form, hash_closed_form_with, hash_spectral_sum,
7    normalize_closed_form,
8};
9
10use crate::error::EngineError;
11use crate::index::Round;
12use crate::typing::{Step, SumSlot, Typing, Value, When};
13
14use super::{cyclic, nodes_in, spectral_sum_of};
15
16/// What keys a closed form's spectral sum wherever a reading composes one.
17pub fn symbolic_hash(typing: &Typing, node: NodeId, want: Var) -> Result<Hash, EngineError> {
18    spectral_sum_of(typing, node, want).map(|n| hash_spectral_sum(&n))
19}
20
21/// What one node is, whatever it holds: its definition and the identities of what it reads,
22/// never where a reader places it.
23pub fn identity(typing: &Typing, node: NodeId) -> Result<Hash, EngineError> {
24    identity_in(typing, node, &mut BTreeMap::new())
25}
26
27/// The same, each node under it named once however many paths reach it.
28pub(crate) fn identity_in(
29    typing: &Typing,
30    node: NodeId,
31    named: &mut BTreeMap<NodeId, Hash>,
32) -> Result<Hash, EngineError> {
33    identity_of(typing, node, &mut Vec::new(), named)
34}
35
36fn identity_of(
37    typing: &Typing,
38    node: NodeId,
39    open: &mut Vec<NodeId>,
40    named: &mut BTreeMap<NodeId, Hash>,
41) -> Result<Hash, EngineError> {
42    if let Some(held) = named.get(&node) {
43        return Ok(*held);
44    }
45    let found = match typing.sum_slots(node) {
46        Some(slots) => {
47            let mut sink = Sink::new();
48            sink.text("terms");
49            for slot in slots {
50                sink.hash(match slot {
51                    SumSlot::Node(id) if *id == node => built(typing, node, open, named)?,
52                    SumSlot::Node(id) => identity_of(typing, *id, open, named)?,
53                    SumSlot::Retired(_) => continue,
54                });
55            }
56            sink.finish()
57        }
58        None => built(typing, node, open, named)?,
59    };
60    named.insert(node, found);
61    Ok(found)
62}
63
64/// A closed form that reads no other node: its own spectral sum, where it has one, so two
65/// spellings of one form are one value; else its written form.
66pub(crate) fn formula_identity(form: &ClosedForm) -> Hash {
67    match normalize_closed_form(form) {
68        Ok(sum) => hash_spectral_sum(&sum),
69        Err(_) => hash_closed_form(form),
70    }
71}
72
73fn built(
74    typing: &Typing,
75    node: NodeId,
76    open: &mut Vec<NodeId>,
77    named: &mut BTreeMap<NodeId, Hash>,
78) -> Result<Hash, EngineError> {
79    if open.contains(&node) {
80        return Err(cyclic(typing, node));
81    }
82    open.push(node);
83    let mut sink = Sink::new();
84    match typing.value(node) {
85        Value::ClosedForm(form) if nodes_in(&form.body).is_empty() => {
86            open.pop();
87            return Ok(formula_identity(form));
88        }
89        Value::ClosedForm(form) => {
90            let mut refused = None;
91            let mut read = |id: NodeId| match identity_of(typing, id, open, named) {
92                Ok(held) => held,
93                Err(e) => {
94                    refused.get_or_insert(e);
95                    Hash(0, 0)
96                }
97            };
98            sink.text("closed form");
99            sink.hash(hash_closed_form_with(form, &mut read));
100            if let Some(e) = refused {
101                return Err(e);
102            }
103        }
104        Value::Cast(cast, source) => {
105            sink.text(cast.name());
106            sink.hash(identity_of(typing, *source, open, named)?);
107        }
108        Value::Read { source, at, .. } => {
109            sink.text("read");
110            sink.hash(identity_of(typing, *source, open, named)?);
111            when(&mut sink, typing, at);
112        }
113        Value::SelfAt { at, .. } => {
114            sink.text("self");
115            when(&mut sink, typing, at);
116        }
117        Value::Noise(seed) => {
118            sink.text("noise");
119            sink.word(*seed);
120        }
121        Value::Stored(held) => {
122            sink.text("stored");
123            sink.hash(held.key);
124        }
125        Value::Solver { params, varying } => {
126            let mut held = (**params).clone();
127            for (key, _) in varying {
128                *crate::lower::field(&mut held, key).expect("a varying field") = f64::NAN;
129            }
130            sink.text(&format!("{held:?}"));
131            for (key, arg) in varying {
132                sink.text(key);
133                sink.hash(identity_of(typing, *arg, open, named)?);
134            }
135        }
136        Value::Filter {
137            shape,
138            x,
139            cutoff,
140            q,
141            gain,
142        } => {
143            sink.text(shape.name());
144            for operand in [x, cutoff, q, gain] {
145                sink.hash(identity_of(typing, *operand, open, named)?);
146            }
147        }
148        Value::Op { name, args } => {
149            sink.text(name);
150            for arg in args {
151                sink.hash(identity_of(typing, *arg, open, named)?);
152            }
153        }
154    }
155    open.pop();
156    Ok(sink.finish())
157}
158
159/// A moving time is named by its closed form, which holds no ref back to the reader.
160pub(super) fn when(sink: &mut Sink, typing: &Typing, at: &When) {
161    sink.text("at");
162    match at {
163        When::At(time) => {
164            sink.text("time");
165            for q in [time.scale, time.shift] {
166                sink.word(q.num() as u64);
167                sink.word((q.num() >> 64) as u64);
168                sink.word(q.den() as u64);
169                sink.word((q.den() >> 64) as u64);
170            }
171        }
172        When::Moving(id) => moving(sink, typing, *id),
173        When::Index(index) => exact(sink, *index),
174        When::Step(step) => {
175            sink.text("step");
176            stepped(sink, typing, step);
177        }
178    }
179}
180
181fn moving(sink: &mut Sink, typing: &Typing, id: NodeId) {
182    match identity(typing, id) {
183        Ok(held) => sink.hash(held),
184        Err(_) => sink.text(typing.name(id)),
185    }
186}
187
188fn exact(sink: &mut Sink, index: crate::index::Index) {
189    round(sink, "index", index.round);
190    match index.time {
191        Some(time) => affine(sink, time),
192        None => sink.text("count"),
193    }
194    sink.word(index.plus as u64);
195}
196
197fn stepped(sink: &mut Sink, typing: &Typing, step: &Step) {
198    let each = |sink: &mut Sink, what: &str, parts: &[Step]| {
199        sink.text(what);
200        sink.word(parts.len() as u64);
201        parts.iter().for_each(|p| stepped(sink, typing, p));
202    };
203    match step {
204        Step::Index(index) => exact(sink, *index),
205        Step::Nearest(time, how) => {
206            round(sink, "nearest", *how);
207            moving(sink, typing, *time);
208        }
209        Step::Add(parts) => each(sink, "sum", parts),
210        Step::Mul(parts) => each(sink, "product", parts),
211        Step::Neg(part) => {
212            sink.text("negated");
213            stepped(sink, typing, part);
214        }
215    }
216}
217
218fn round(sink: &mut Sink, what: &str, round: Round) {
219    let how = match round {
220        Round::Even => "",
221        Round::Floor => " floor",
222        Round::Ceil => " ceil",
223    };
224    sink.text(&format!("{what}{how}"));
225}
226
227fn affine(sink: &mut Sink, time: crate::time::Affine) {
228    for q in [time.scale, time.shift] {
229        rational(sink, q);
230    }
231}
232
233fn rational(sink: &mut Sink, q: crate::time::Q) {
234    sink.word(q.num() as u64);
235    sink.word((q.num() >> 64) as u64);
236    sink.word(q.den() as u64);
237}
238
239const IDENTITY_ROTATE: u32 = 23;
240
241pub(super) struct Sink(sva_formula::Lanes<IDENTITY_ROTATE>);
242
243impl Sink {
244    pub(super) fn new() -> Sink {
245        Sink(sva_formula::Lanes::default())
246    }
247
248    pub(super) fn word(&mut self, part: u64) {
249        self.0.word(part);
250    }
251
252    pub(super) fn text(&mut self, what: &str) {
253        self.word(what.len() as u64);
254        for byte in what.as_bytes() {
255            self.word(u64::from(*byte));
256        }
257    }
258
259    pub(super) fn hash(&mut self, held: Hash) {
260        self.word(held.0);
261        self.word(held.1);
262    }
263
264    pub(super) fn finish(&self) -> Hash {
265        self.0.finish()
266    }
267}