fidget_core/var/
mod.rs

1//! Input variables to math expressions
2//!
3//! A [`Var`] maintains a persistent identity from
4//! [`Tree`](crate::context::Tree) to [`Context`](crate::context::Node) (where
5//! it is wrapped in a [`Op::Input`](crate::context::Op::Input)) to evaluation
6//! (where [`Tape::vars`](crate::eval::Tape::vars) maps from `Var` to index in
7//! the argument list).
8use crate::Error;
9use crate::context::{Context, IntoNode, Node};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13/// The [`Var`] type is an input to a math expression
14///
15/// We pre-define common variables (e.g. `X`, `Y`, `Z`) but also allow for fully
16/// customized values (using [`Var::V`]).
17///
18/// Variables are "global", in that every instance of `Var::X` represents the
19/// same thing.  To generate a "local" variable, [`Var::new`] picks a random
20/// 64-bit value, which is very unlikely to collide with anything else.
21#[derive(
22    Copy,
23    Clone,
24    Debug,
25    Hash,
26    Eq,
27    PartialEq,
28    Ord,
29    PartialOrd,
30    Serialize,
31    Deserialize,
32)]
33pub enum Var {
34    /// Variable representing the X axis for 2D / 3D shapes
35    X,
36    /// Variable representing the Y axis for 2D / 3D shapes
37    Y,
38    /// Variable representing the Z axis for 3D shapes
39    Z,
40    /// Generic variable
41    V(VarIndex),
42}
43
44/// Type for a variable index (implemented as a `u64`), used in [`Var::V`]
45#[derive(
46    Copy,
47    Clone,
48    Debug,
49    Hash,
50    Eq,
51    PartialEq,
52    Ord,
53    PartialOrd,
54    Serialize,
55    Deserialize,
56)]
57#[serde(transparent)]
58pub struct VarIndex(u64);
59
60impl Var {
61    /// Returns a new variable, with a random 64-bit index
62    ///
63    /// The odds of collision with any previous variable are infintesimally
64    /// small; if you are generating billions of random variables, something
65    /// else in the system is likely to break before collisions become an issue.
66    #[allow(clippy::new_without_default)]
67    pub fn new() -> Self {
68        let v: u64 = rand::random();
69        Var::V(VarIndex(v))
70    }
71
72    /// Returns the [`VarIndex`] from a [`Var::V`] instance, or `None`
73    pub fn index(&self) -> Option<VarIndex> {
74        if let Var::V(i) = *self { Some(i) } else { None }
75    }
76}
77
78impl std::fmt::Display for Var {
79    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80        match self {
81            Var::X => write!(f, "X"),
82            Var::Y => write!(f, "Y"),
83            Var::Z => write!(f, "Z"),
84            Var::V(VarIndex(v)) if *v < 256 => write!(f, "v_{v}"),
85            Var::V(VarIndex(v)) => write!(f, "V({v:x})"),
86        }
87    }
88}
89
90impl IntoNode for Var {
91    fn into_node(self, ctx: &mut Context) -> Result<Node, Error> {
92        Ok(ctx.var(self))
93    }
94}
95
96/// Map from [`Var`] to a particular index
97///
98/// Variable indexes are automatically assigned the first time
99/// [`VarMap::insert`] is called on that variable.
100///
101/// Indexes are guaranteed to be tightly packed, i.e. a map `vars` will contains
102/// values from `0..vars.len()`.
103///
104/// For efficiency, this type does not allocate heap memory for `Var::X/Y/Z`.
105#[derive(Default, Serialize, Deserialize)]
106pub struct VarMap {
107    x: Option<usize>,
108    y: Option<usize>,
109    z: Option<usize>,
110    v: HashMap<VarIndex, usize>,
111}
112
113#[allow(missing_docs)]
114impl VarMap {
115    pub fn new() -> Self {
116        Self::default()
117    }
118    pub fn len(&self) -> usize {
119        self.x.is_some() as usize
120            + self.y.is_some() as usize
121            + self.z.is_some() as usize
122            + self.v.len()
123    }
124    pub fn is_empty(&self) -> bool {
125        self.x.is_none()
126            && self.y.is_none()
127            && self.z.is_none()
128            && self.v.is_empty()
129    }
130    pub fn get(&self, v: &Var) -> Option<usize> {
131        match v {
132            Var::X => self.x,
133            Var::Y => self.y,
134            Var::Z => self.z,
135            Var::V(v) => self.v.get(v).cloned(),
136        }
137    }
138    /// Inserts a variable if not already present in the map
139    ///
140    /// The index is automatically assigned.
141    pub fn insert(&mut self, v: Var) {
142        let next = self.len();
143        match v {
144            Var::X => self.x.get_or_insert(next),
145            Var::Y => self.y.get_or_insert(next),
146            Var::Z => self.z.get_or_insert(next),
147            Var::V(v) => self.v.entry(v).or_insert(next),
148        };
149    }
150
151    /// Checks whether tracing arguments are valid
152    pub fn check_tracing_arguments<T>(&self, vars: &[T]) -> Result<(), Error> {
153        if vars.len() < self.len() {
154            // It's okay to be passed extra vars, because expressions may have
155            // been simplified.
156            Err(Error::BadVarSlice(vars.len(), self.len()))
157        } else {
158            Ok(())
159        }
160    }
161
162    /// Check whether bulk arguments are valid
163    pub fn check_bulk_arguments<T, V: std::ops::Deref<Target = [T]>>(
164        &self,
165        vars: &[V],
166    ) -> Result<(), Error> {
167        // It's fine if the caller has given us extra variables (e.g. due to
168        // tape simplification), but it must have given us enough.
169        if vars.len() < self.len() {
170            Err(Error::BadVarSlice(vars.len(), self.len()))
171        } else {
172            let Some(n) = vars.first().map(|v| v.len()) else {
173                return Ok(());
174            };
175            if vars.iter().any(|v| v.len() == n) {
176                Ok(())
177            } else {
178                Err(Error::MismatchedSlices)
179            }
180        }
181    }
182}
183
184impl std::ops::Index<&Var> for VarMap {
185    type Output = usize;
186    fn index(&self, v: &Var) -> &Self::Output {
187        match v {
188            Var::X => self.x.as_ref().unwrap(),
189            Var::Y => self.y.as_ref().unwrap(),
190            Var::Z => self.z.as_ref().unwrap(),
191            Var::V(v) => &self.v[v],
192        }
193    }
194}
195
196#[cfg(test)]
197mod test {
198    use super::*;
199
200    #[test]
201    fn var_identity() {
202        let v1 = Var::new();
203        let v2 = Var::new();
204        assert_ne!(v1, v2);
205    }
206
207    #[test]
208    fn var_map() {
209        let v = Var::new();
210        let mut m = VarMap::new();
211        assert!(m.get(&v).is_none());
212        m.insert(v);
213        assert_eq!(m.get(&v), Some(0));
214        m.insert(v);
215        assert_eq!(m.get(&v), Some(0));
216
217        let u = Var::new();
218        assert!(m.get(&u).is_none());
219        m.insert(u);
220        assert_eq!(m.get(&u), Some(1));
221    }
222}