echidna/bytecode_tape/
forward.rs1use std::collections::HashMap;
2use std::sync::Arc;
3
4use crate::bytecode_tape::CustomOp;
5use crate::float::Float;
6use crate::opcode::{self, OpCode, UNUSED};
7
8fn forward_dispatch<F: Float>(
12 opcodes: &[OpCode],
13 arg_indices: &[[u32; 2]],
14 values: &mut [F],
15 custom_ops: &[Arc<dyn CustomOp<F>>],
16 custom_second_args: &HashMap<u32, u32>,
17) {
18 for i in 0..opcodes.len() {
19 match opcodes[i] {
20 OpCode::Input | OpCode::Const => continue,
21 OpCode::Custom => {
22 let [a_idx, cb_idx] = arg_indices[i];
23 let a = values[a_idx as usize];
24 let b = custom_second_args
25 .get(&(i as u32))
26 .map(|&bi| values[bi as usize])
27 .unwrap_or(F::zero());
28 values[i] = custom_ops[cb_idx as usize].eval(a, b);
29 }
30 op => {
31 let [a_idx, b_idx] = arg_indices[i];
32 let a = values[a_idx as usize];
33 if op == OpCode::Powi {
34 let exp = opcode::powi_exp_decode_raw(b_idx);
35 values[i] = a.powi(exp);
36 continue;
37 }
38 let b = if b_idx != UNUSED {
39 values[b_idx as usize]
40 } else {
41 F::zero()
42 };
43 values[i] = opcode::eval_forward(op, a, b);
44 }
45 }
46 }
47}
48
49impl<F: Float> super::BytecodeTape<F> {
50 pub fn forward(&mut self, inputs: &[F]) {
54 assert_eq!(
55 inputs.len(),
56 self.num_inputs as usize,
57 "wrong number of inputs"
58 );
59
60 debug_assert!(
61 self.opcodes[..self.num_inputs as usize]
62 .iter()
63 .all(|&op| op == OpCode::Input),
64 "input slots must be contiguous Input opcodes at the start of the tape"
65 );
66 for (i, &v) in inputs.iter().enumerate() {
67 self.values[i] = v;
68 }
69
70 forward_dispatch(
71 &self.opcodes,
72 &self.arg_indices,
73 &mut self.values,
74 &self.custom_ops,
75 &self.custom_second_args,
76 );
77 }
78
79 pub fn forward_nonsmooth(&mut self, inputs: &[F]) -> crate::nonsmooth::NonsmoothInfo<F> {
92 self.forward(inputs);
93
94 let mut kinks = Vec::new();
95 for i in 0..self.opcodes.len() {
96 let op = self.opcodes[i];
97 if !opcode::is_nonsmooth(op) {
98 continue;
99 }
100
101 let [a_idx, b_idx] = self.arg_indices[i];
102 let a = self.values[a_idx as usize];
103
104 match op {
105 OpCode::Abs => {
106 kinks.push(crate::nonsmooth::KinkEntry {
107 tape_index: i as u32,
108 opcode: op,
109 switching_value: a,
110 branch: if a >= F::zero() { 1 } else { -1 },
111 });
112 }
113 OpCode::Max => {
114 let b = self.values[b_idx as usize];
115 kinks.push(crate::nonsmooth::KinkEntry {
116 tape_index: i as u32,
117 opcode: op,
118 switching_value: a - b,
119 branch: if a >= b { 1 } else { -1 },
120 });
121 }
122 OpCode::Min => {
123 let b = self.values[b_idx as usize];
124 kinks.push(crate::nonsmooth::KinkEntry {
125 tape_index: i as u32,
126 opcode: op,
127 switching_value: a - b,
128 branch: if a <= b { 1 } else { -1 },
129 });
130 }
131 OpCode::Signum => {
132 kinks.push(crate::nonsmooth::KinkEntry {
134 tape_index: i as u32,
135 opcode: op,
136 switching_value: a,
137 branch: if a >= F::zero() { 1 } else { -1 },
138 });
139 }
140 OpCode::Floor | OpCode::Ceil | OpCode::Trunc => {
141 kinks.push(crate::nonsmooth::KinkEntry {
145 tape_index: i as u32,
146 opcode: op,
147 switching_value: a - a.round(),
148 branch: if a - a.floor() < F::from(0.5).unwrap() {
149 1
150 } else {
151 -1
152 },
153 });
154 }
155 OpCode::Fract => {
156 kinks.push(crate::nonsmooth::KinkEntry {
158 tape_index: i as u32,
159 opcode: op,
160 switching_value: a - a.round(),
161 branch: if a.fract() >= F::zero() { 1 } else { -1 },
162 });
163 }
164 OpCode::Round => {
165 let half = F::from(0.5).unwrap();
169 let shifted = a + half;
170 kinks.push(crate::nonsmooth::KinkEntry {
171 tape_index: i as u32,
172 opcode: op,
173 switching_value: shifted - shifted.round(),
174 branch: if a - a.floor() < half { 1 } else { -1 },
175 });
176 }
177 _ => unreachable!(),
178 }
179 }
180
181 crate::nonsmooth::NonsmoothInfo { kinks }
182 }
183
184 pub fn forward_into(&self, inputs: &[F], values_buf: &mut Vec<F>) {
190 assert_eq!(
191 inputs.len(),
192 self.num_inputs as usize,
193 "wrong number of inputs"
194 );
195
196 debug_assert!(
201 self.opcodes[..self.num_inputs as usize]
202 .iter()
203 .all(|&op| op == OpCode::Input),
204 "input slots must be contiguous Input opcodes at the start of the tape"
205 );
206
207 let n = self.num_variables as usize;
208 values_buf.clear();
209 values_buf.resize(n, F::zero());
210
211 values_buf.copy_from_slice(&self.values[..n]);
213 for (i, &v) in inputs.iter().enumerate() {
214 values_buf[i] = v;
215 }
216
217 forward_dispatch(
218 &self.opcodes,
219 &self.arg_indices,
220 values_buf,
221 &self.custom_ops,
222 &self.custom_second_args,
223 );
224 }
225}