1use crate::{
2 context::{Node, indexed::Index},
3 var::Var,
4};
5use ordered_float::OrderedFloat;
6
7#[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#[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 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 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#[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 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 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 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 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 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}