Skip to main content

fidget_core/context/
op.rs

1use crate::{
2    context::{Node, indexed::Index},
3    var::Var,
4};
5use ordered_float::OrderedFloat;
6
7/// A one-argument math operation
8#[allow(missing_docs)]
9#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
10pub enum UnaryOpcode {
11    Neg,
12    Abs,
13    Recip,
14    Sqrt,
15    Square,
16    Floor,
17    Ceil,
18    Round,
19    Sin,
20    Cos,
21    Tan,
22    Asin,
23    Acos,
24    Atan,
25    Exp,
26    Ln,
27    Not,
28}
29
30/// A two-argument math operation
31#[allow(missing_docs)]
32#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
33pub enum BinaryOpcode {
34    Add,
35    Sub,
36    Mul,
37    Div,
38    Atan,
39    Min,
40    Max,
41    Compare,
42    Mod,
43    And,
44    Or,
45}
46
47impl BinaryOpcode {
48    /// Evaluates the opcode
49    pub fn eval(&self, a: f64, b: f64) -> f64 {
50        match self {
51            BinaryOpcode::Add => a + b,
52            BinaryOpcode::Sub => a - b,
53            BinaryOpcode::Mul => a * b,
54            BinaryOpcode::Div => a / b,
55            BinaryOpcode::Atan => a.atan2(b),
56            BinaryOpcode::Min => a.min(b),
57            BinaryOpcode::Max => a.max(b),
58            BinaryOpcode::Compare => a
59                .partial_cmp(&b)
60                .map(|i| i as i8 as f64)
61                .unwrap_or(f64::NAN),
62            BinaryOpcode::Mod => a.rem_euclid(b),
63            BinaryOpcode::And => {
64                if a == 0.0 {
65                    a
66                } else {
67                    b
68                }
69            }
70            BinaryOpcode::Or => {
71                if a != 0.0 {
72                    a
73                } else {
74                    b
75                }
76            }
77        }
78    }
79}
80
81impl UnaryOpcode {
82    /// Evaluates the opcode
83    pub fn eval(&self, a: f64) -> f64 {
84        match self {
85            UnaryOpcode::Neg => -a,
86            UnaryOpcode::Abs => a.abs(),
87            UnaryOpcode::Recip => 1.0 / a,
88            UnaryOpcode::Sqrt => a.sqrt(),
89            UnaryOpcode::Square => a * a,
90            UnaryOpcode::Floor => a.floor(),
91            UnaryOpcode::Ceil => a.ceil(),
92            UnaryOpcode::Round => a.round(),
93            UnaryOpcode::Sin => a.sin(),
94            UnaryOpcode::Cos => a.cos(),
95            UnaryOpcode::Tan => a.tan(),
96            UnaryOpcode::Asin => a.asin(),
97            UnaryOpcode::Acos => a.acos(),
98            UnaryOpcode::Atan => a.atan(),
99            UnaryOpcode::Exp => a.exp(),
100            UnaryOpcode::Ln => a.ln(),
101            UnaryOpcode::Not => (a == 0.0).into(),
102        }
103    }
104}
105
106/// An operation in a math expression
107///
108/// `Op`s should be constructed by calling functions on
109/// [`Context`](crate::context::Context), e.g.
110/// [`Context::add`](crate::context::Context::add) will generate an
111/// `Op::Binary(BinaryOpcode::Add, .., ..)` node and return an opaque handle.
112///
113/// Each `Op` is tightly coupled to the [`Context`](crate::context::Context)
114/// which generated it, and will not be valid for a different `Context`.
115#[allow(missing_docs)]
116#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)]
117pub enum Op {
118    Input(Var),
119    Const(OrderedFloat<f64>),
120    Binary(BinaryOpcode, Node, Node),
121    Unary(UnaryOpcode, Node),
122}
123
124fn dot_color_to_rgb(s: &str) -> &'static str {
125    match s {
126        "red" => "#FF0000",
127        "green" => "#00FF00",
128        "goldenrod" => "#DAA520",
129        "dodgerblue" => "#1E90FF",
130        s => panic!("Unknown X11 color '{s}'"),
131    }
132}
133
134impl Op {
135    /// Returns the color to be used in a GraphViz drawing for this node
136    pub fn dot_node_color(&self) -> &str {
137        match self {
138            Op::Const(..) => "green",
139            Op::Input(..) => "red",
140            Op::Binary(BinaryOpcode::Min | BinaryOpcode::Max, ..) => {
141                "dodgerblue"
142            }
143            Op::Binary(..) | Op::Unary(..) => "goldenrod",
144        }
145    }
146
147    /// Returns the shape to be used in a GraphViz drawing for this node
148    pub fn dot_node_shape(&self) -> &str {
149        match self {
150            Op::Const(..) => "oval",
151            Op::Input(..) => "circle",
152            Op::Binary(..) | Op::Unary(..) => "box",
153        }
154    }
155
156    /// Iterates over children, producing 0, 1, or 2 values
157    pub fn iter_children(&self) -> impl Iterator<Item = Node> {
158        let out = match self {
159            Op::Binary(_, a, b) => [Some(*a), Some(*b)],
160            Op::Unary(_, a) => [Some(*a), None],
161            Op::Input(..) | Op::Const(..) => [None, None],
162        };
163        out.into_iter().flatten()
164    }
165
166    /// Returns a GraphViz string of edges from this node to its children
167    pub fn dot_edges(&self, i: Node) -> String {
168        let mut out = String::new();
169        for c in self.iter_children() {
170            out += &self.dot_edge(i, c, "FF");
171        }
172        out
173    }
174
175    /// Returns a single edge with user-specified transparency
176    pub fn dot_edge(&self, a: Node, b: Node, alpha: &str) -> String {
177        let color = dot_color_to_rgb(self.dot_node_color()).to_owned() + alpha;
178        format!("n{} -> n{} [color = \"{color}\"]\n", a.get(), b.get(),)
179    }
180}