1use std::collections::HashMap;
17
18use crate::Error;
19use crate::numeric::{
20 EXACT_GRID_STEPS, Range, evaluate_boolean, fround, is_power_of_two_grid, largest_grid, lowest_power_of_two, relu32,
21 smallest_grid,
22};
23use crate::wire::{Lin, Term, Wire};
24
25#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
26pub enum OpKind {
27 Affine,
28 Reglu,
29 Boolean,
30 Index,
31}
32
33#[derive(Copy, Clone, Debug, PartialEq, Eq)]
36pub enum Macro {
37 Multiply { left: Wire, right: Wire },
38 Select { gate: Wire, off: Wire, on: Wire },
39}
40
41#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
43pub enum OutOfRange {
44 Clamp,
46 Zero,
48}
49
50#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
53pub enum RoundingPointKind {
54 Boolean,
55 IntegerBit,
56 SignCascade,
57 Marked,
58}
59
60#[derive(Clone, Debug)]
61pub struct IndexRead {
62 pub table: usize,
63 pub below: OutOfRange,
64 pub above: OutOfRange,
65 pub index_range: Range,
67}
68
69#[derive(Clone, Debug)]
70pub struct CandidateTable {
71 pub candidates: Vec<Wire>,
72 pub maximum_depth: u32,
73}
74
75#[derive(Clone, Debug)]
76pub struct Operation {
77 pub kind: OpKind,
78 pub left_terms: Vec<Term>,
79 pub left_bias: f64,
80 pub gate_terms: Vec<Term>,
82 pub gate_bias: f64,
83 pub depth: u32,
84 pub preserve_rounding: bool,
85 pub macro_op: Option<Macro>,
86 pub index_read: Option<IndexRead>,
87 pub rounding_point: Option<RoundingPointKind>,
88 pub comparison_bit: bool,
90}
91
92#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
94pub enum BooleanGradient {
95 #[default]
96 Reject,
97 StraightThrough,
98}
99
100#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
102pub enum IndexGradient {
103 #[default]
104 Reject,
105 Zero,
106 BackwardDifference,
107}
108
109#[derive(Copy, Clone, Debug, Default)]
110pub struct BackpropOptions {
111 pub boolean_gradient: BooleanGradient,
112 pub index_gradient: IndexGradient,
113}
114
115const AFFINE_SHARE_THRESHOLD: usize = 4;
118
119const INDEX_MAGNITUDE_LIMIT: f64 = 16_777_216.0;
122
123#[derive(Clone, Debug)]
124pub struct Circuit {
125 source_size: usize,
126 pub(crate) operations: Vec<Operation>,
127 depths: Vec<u32>,
128 constants: Vec<Option<f64>>,
129 expression_wires: HashMap<Vec<u64>, Wire>,
130 pub(crate) ranges: Vec<Range>,
131 grids: Vec<Option<f64>>,
132 exact_bits: Vec<bool>,
133 has_readers: Vec<bool>,
134 pub(crate) tables: Vec<CandidateTable>,
135 table_by_content: HashMap<Vec<Wire>, usize>,
136}
137
138impl Circuit {
139 pub fn new(source_size: usize) -> Circuit {
140 Circuit {
141 source_size,
142 operations: Vec::new(),
143 depths: vec![0; source_size],
144 constants: vec![None; source_size],
145 expression_wires: HashMap::new(),
146 ranges: vec![Range::UNKNOWN; source_size],
147 grids: vec![None; source_size],
148 exact_bits: vec![false; source_size],
149 has_readers: vec![false; source_size],
150 tables: Vec::new(),
151 table_by_content: HashMap::new(),
152 }
153 }
154
155 pub fn source_size(&self) -> usize {
156 self.source_size
157 }
158
159 pub fn width(&self) -> usize {
161 self.source_size + self.operations.len()
162 }
163
164 pub fn operations(&self) -> &[Operation] {
165 &self.operations
166 }
167
168 pub fn operation(&self, wire: Wire) -> Option<&Operation> {
169 wire.index().checked_sub(self.source_size).and_then(|index| self.operations.get(index))
170 }
171
172 pub fn range(&self, wire: Wire) -> Range {
173 self.ranges[wire.index()]
174 }
175
176 pub fn grid(&self, wire: Wire) -> Option<f64> {
177 self.grids[wire.index()]
178 }
179
180 pub fn is_exact_bit(&self, wire: Wire) -> bool {
181 self.exact_bits[wire.index()]
182 }
183
184 pub fn constant_value(&self, wire: Wire) -> Option<f64> {
186 self.constants[wire.index()]
187 }
188
189 pub fn depth(&self, wire: Wire) -> u32 {
191 self.depths[wire.index()]
192 }
193
194 pub fn is_non_negative(&self, wire: Wire) -> bool {
195 self.ranges[wire.index()].min >= 0.0
196 }
197
198 pub(crate) fn assert_wire(&self, wire: Wire) {
199 assert!(wire.index() < self.width(), "wire {} is not in the circuit", wire.index());
200 }
201
202 pub fn assume_range(&mut self, wire: Wire, min: f64, max: f64) {
208 self.declare_range(wire, min, max, false);
209 }
210
211 pub fn assume_range_allowing_readers(&mut self, wire: Wire, min: f64, max: f64) {
214 self.declare_range(wire, min, max, true);
215 }
216
217 fn declare_range(&mut self, wire: Wire, min: f64, max: f64, allow_existing_readers: bool) {
218 self.assert_wire(wire);
219 assert!(min <= max, "numeric range is inverted");
220 let current = self.ranges[wire.index()];
221 let narrowed = Range::new(current.min.max(min), current.max.min(max));
222 assert!(narrowed.min <= narrowed.max, "numeric range contradicts known bounds");
223 let narrows = narrowed.min > current.min || narrowed.max < current.max;
224 if narrows && self.has_readers[wire.index()] && !allow_existing_readers {
225 panic!(
226 "operations that already read wire {} were built with its range {}..{}, and declaring {}..{} now \
227 doesn't change them. Declare the range before the first operation that reads the wire",
228 wire.index(),
229 current.min,
230 current.max,
231 min,
232 max
233 );
234 }
235 self.ranges[wire.index()] = narrowed;
236 }
237
238 pub fn assume_grid(&mut self, wire: Wire, step: f64) {
240 self.declare_grid(wire, step, false);
241 }
242
243 pub fn assume_grid_allowing_readers(&mut self, wire: Wire, step: f64) {
244 self.declare_grid(wire, step, true);
245 }
246
247 fn declare_grid(&mut self, wire: Wire, step: f64, allow_existing_readers: bool) {
248 self.assert_wire(wire);
249 assert!(is_power_of_two_grid(step), "a grid step must be a power of two from 2^-149 to 2^127, got {step}");
250 let current = self.grids[wire.index()];
251 let grid = current.unwrap_or(step).max(step);
252 if Some(grid) != current && self.has_readers[wire.index()] && !allow_existing_readers {
253 panic!(
254 "operations that already read wire {} were built with grid {:?}, and declaring the grid {} now \
255 doesn't change them. Declare the grid before the first operation that reads the wire",
256 wire.index(),
257 current,
258 step
259 );
260 }
261 self.grids[wire.index()] = Some(grid);
262 }
263
264 pub fn assume_bit(&mut self, wire: Wire) {
266 self.assume_range(wire, 0.0, 1.0);
267 self.exact_bits[wire.index()] = true;
268 }
269
270 pub fn mark_rounding_point(&mut self, wire: Wire) {
272 self.record_rounding_point(wire, RoundingPointKind::Marked);
273 }
274
275 pub(crate) fn record_rounding_point(&mut self, wire: Wire, kind: RoundingPointKind) {
277 self.assert_wire(wire);
278 let index = wire.index().checked_sub(self.source_size).expect("a rounding point must be an operation");
279 self.operations[index].rounding_point.get_or_insert(kind);
280 }
281
282 pub fn rounding_points(&self) -> Vec<(Wire, RoundingPointKind)> {
284 self.operations
285 .iter()
286 .enumerate()
287 .filter_map(|(index, operation)| {
288 operation.rounding_point.map(|kind| (Wire::from_index(self.source_size + index), kind))
289 })
290 .collect()
291 }
292
293 pub fn source(&self, index: usize) -> Wire {
296 assert!(index < self.source_size, "source cell {index} is outside the source table");
297 Wire::from_index(index)
298 }
299
300 pub fn constant(&mut self, value: f64) -> Wire {
302 self.affine(value)
303 }
304
305 pub fn affine(&mut self, form: impl Into<Lin>) -> Wire {
308 self.append(OpKind::Affine, form.into(), Lin::default(), false, None)
309 }
310
311 pub fn round_affine(&mut self, form: impl Into<Lin>) -> Wire {
314 self.append(OpKind::Affine, form.into(), Lin::default(), true, None)
315 }
316
317 pub fn reglu(&mut self, left: impl Into<Lin>, gate: impl Into<Lin>) -> Wire {
319 self.append(OpKind::Reglu, left.into(), gate.into(), false, None)
320 }
321
322 pub fn relu(&mut self, value: impl Into<Lin>) -> Wire {
324 self.reglu(1.0, value)
325 }
326
327 pub fn boolean(&mut self, score: Wire) -> Wire {
329 let range = self.ranges[score.index()];
330 assert!(range.min >= 0.0 && range.max <= 1.0, "boolean input must be within zero and one");
331 self.append(OpKind::Boolean, Lin::from(score), Lin::default(), false, None)
332 }
333
334 pub fn multiply(&mut self, left: Wire, right: Wire) -> Wire {
336 let macro_op = Some(Macro::Multiply { left, right });
337 if self.is_non_negative(right) {
338 return self.append(OpKind::Reglu, Lin::from(left), Lin::from(right), true, macro_op);
339 }
340 if self.is_non_negative(left) {
341 return self.append(OpKind::Reglu, Lin::from(right), Lin::from(left), true, macro_op);
342 }
343 let positive = self.append(OpKind::Reglu, Lin::from(left), Lin::from(right), true, None);
344 let negative = self.append(OpKind::Reglu, Lin::from(left), -right, true, None);
345 self.append(OpKind::Affine, positive - negative, Lin::default(), true, macro_op)
346 }
347
348 pub fn select(&mut self, gate: Wire, off: Wire, on: Wire) -> Wire {
350 let complement = self.round_affine(1.0 - gate);
351 let selected_on = self.multiply(gate, on);
352 let selected_off = self.multiply(complement, off);
353 self.append(
354 OpKind::Affine,
355 selected_on + selected_off,
356 Lin::default(),
357 true,
358 Some(Macro::Select { gate, off, on }),
359 )
360 }
361
362 pub fn index_read(&mut self, index: Wire, candidates: &[Wire], out_of_range: OutOfRange) -> Wire {
365 self.assert_wire(index);
366 let declared = self.ranges[index.index()];
367 self.index_read_affine(Lin::from(index), candidates, out_of_range, out_of_range, Some(declared))
368 }
369
370 pub fn index_read_affine(
372 &mut self,
373 index: Lin,
374 candidates: &[Wire],
375 below: OutOfRange,
376 above: OutOfRange,
377 declared_index_range: Option<Range>,
378 ) -> Wire {
379 for &(wire, coefficient) in &index.terms {
380 self.assert_wire(wire);
381 assert!(coefficient.is_finite(), "index coefficient must be finite");
382 }
383 assert!(index.bias.is_finite(), "index bias must be finite");
384 let table = self.register_candidate_table(candidates);
385 let (terms, bias) = self.fold_constant_terms(normalize_terms(&index.terms), index.bias);
386 let read_terms = terms.clone();
387 let (terms, bias) = self.compose_affine_terms(&terms, bias);
388 if terms.is_empty() {
389 let constant_index = fround(bias);
390 assert!(!constant_index.is_nan(), "a constant index must not be NaN");
391 let count = self.tables[table].candidates.len();
392 let position = resolve_index_position(constant_index, count, below, above);
393 return if position >= 0 { self.tables[table].candidates[position as usize] } else { self.affine(0.0) };
394 }
395 let mut index_range = evaluate_affine_range(&terms, bias, &self.ranges);
396 #[allow(clippy::neg_cmp_op_on_partial_ord)]
398 if let Some(declared) = declared_index_range {
399 index_range = Range::new(index_range.min.max(declared.min), index_range.max.min(declared.max));
400 assert!(
401 !(index_range.min > index_range.max),
402 "the declared index range contradicts the range inferred from the index form"
403 );
404 }
405 assert!(
406 index_range.min > -INDEX_MAGNITUDE_LIMIT && index_range.max < INDEX_MAGNITUDE_LIMIT,
407 "index read needs an index range declared strictly inside ±2^24, got {}..{}",
408 index_range.min,
409 index_range.max
410 );
411 let mut key = expression_key(OpKind::Index, &[], 0.0, &terms, bias);
412 key.extend([u64::MAX, below as u64, above as u64, table as u64]);
413 if let Some(&existing) = self.expression_wires.get(&key) {
414 return existing;
415 }
416 let mut depth = self.tables[table].maximum_depth;
417 for &(dependency, _) in &terms {
418 depth = depth.max(self.depths[dependency.index()]);
419 }
420 let wire = Wire::from_index(self.width());
421 let range = index_read_range(&self.tables[table], index_range, below, above, &self.ranges);
422 self.operations.push(Operation {
423 kind: OpKind::Index,
424 left_terms: Vec::new(),
425 left_bias: 0.0,
426 gate_terms: terms,
427 gate_bias: bias,
428 depth: depth + 1,
429 preserve_rounding: false,
430 macro_op: None,
431 index_read: Some(IndexRead { table, below, above, index_range }),
432 rounding_point: None,
433 comparison_bit: false,
434 });
435 self.push_wire_metadata(depth + 1, None, None, range);
436 for &(read, _) in &read_terms {
437 self.has_readers[read.index()] = true;
438 }
439 self.expression_wires.insert(key, wire);
440 wire
441 }
442
443 fn register_candidate_table(&mut self, candidates: &[Wire]) -> usize {
444 assert!(!candidates.is_empty(), "an index read needs at least one candidate");
445 if let Some(&table) = self.table_by_content.get(candidates) {
446 return table;
447 }
448 let mut maximum_depth = 0;
449 for &candidate in candidates {
450 self.assert_wire(candidate);
451 maximum_depth = maximum_depth.max(self.depths[candidate.index()]);
452 self.has_readers[candidate.index()] = true;
453 }
454 let table = self.tables.len();
455 self.tables.push(CandidateTable { candidates: candidates.to_vec(), maximum_depth });
456 self.table_by_content.insert(candidates.to_vec(), table);
457 table
458 }
459
460 pub(crate) fn append(
465 &mut self,
466 mut kind: OpKind,
467 left: Lin,
468 gate: Lin,
469 preserve_rounding: bool,
470 macro_op: Option<Macro>,
471 ) -> Wire {
472 debug_assert!(kind != OpKind::Index);
473 for &(wire, _) in left.terms.iter().chain(&gate.terms) {
474 self.assert_wire(wire);
475 }
476 let (mut left_terms, mut left_bias) = (left.terms, left.bias);
477 let (mut gate_terms, mut gate_bias) = (gate.terms, gate.bias);
478 let read_left_terms;
479 let read_gate_terms;
480 if !preserve_rounding {
481 (left_terms, left_bias) = self.fold_constant_terms(normalize_terms(&left_terms), left_bias);
482 (gate_terms, gate_bias) = self.fold_constant_terms(normalize_terms(&gate_terms), gate_bias);
483 read_left_terms = left_terms.clone();
484 read_gate_terms = gate_terms.clone();
485 if kind == OpKind::Affine
486 && let Some(alias) = identity_wire(&left_terms, left_bias)
487 {
488 return alias;
489 }
490 (left_terms, left_bias) = self.compose_affine_terms(&left_terms, left_bias);
491 (gate_terms, gate_bias) = self.compose_affine_terms(&gate_terms, gate_bias);
492 if kind == OpKind::Reglu && gate_terms.is_empty() {
493 let gate_value = if gate_bias.is_nan() { f64::NAN } else { gate_bias.max(0.0) };
494 kind = OpKind::Affine;
495 if gate_value == 0.0 {
496 left_terms.clear();
497 } else {
498 for term in &mut left_terms {
499 term.1 *= gate_value;
500 }
501 }
502 left_bias *= gate_value;
503 gate_bias = 0.0;
504 } else if kind == OpKind::Reglu && left_terms.is_empty() && left_bias == 0.0 {
505 kind = OpKind::Affine;
506 gate_terms.clear();
507 gate_bias = 0.0;
508 }
509 if kind == OpKind::Affine
510 && let Some(alias) = identity_wire(&left_terms, left_bias)
511 {
512 return alias;
513 }
514 } else {
515 read_left_terms = left_terms.clone();
516 read_gate_terms = gate_terms.clone();
517 }
518 let mut key = expression_key(kind, &left_terms, left_bias, &gate_terms, gate_bias);
519 if preserve_rounding {
520 key.push(u64::MAX - 1);
521 }
522 match macro_op {
523 Some(Macro::Multiply { left, right }) => key.extend([u64::MAX - 2, left.0 as u64, right.0 as u64]),
524 Some(Macro::Select { gate, off, on }) => {
525 key.extend([u64::MAX - 3, gate.0 as u64, off.0 as u64, on.0 as u64])
526 }
527 None => {}
528 }
529 if let Some(&existing) = self.expression_wires.get(&key) {
530 return existing;
531 }
532 let wire = Wire::from_index(self.width());
533 let gate_dependencies: &[Term] = if kind == OpKind::Reglu { &gate_terms } else { &[] };
534 let depth = left_terms
535 .iter()
536 .chain(gate_dependencies)
537 .map(|&(dependency, _)| self.depths[dependency.index()])
538 .max()
539 .unwrap_or(0);
540 let constant =
541 if !preserve_rounding && kind == OpKind::Affine && left_terms.is_empty() { Some(left_bias) } else { None };
542 let grid =
543 infer_operation_grid(kind, &left_terms, left_bias, &gate_terms, gate_bias, &self.grids, &self.ranges);
544 let left_range = evaluate_affine_range(&left_terms, left_bias, &self.ranges);
545 let range = match kind {
546 OpKind::Affine => left_range,
547 OpKind::Reglu => {
548 multiply_by_relu_range(left_range, evaluate_affine_range(&gate_terms, gate_bias, &self.ranges))
549 }
550 _ => Range::new(0.0, 1.0),
551 };
552 if !left_terms.is_empty() {
553 for &(read, _) in &read_left_terms {
554 self.has_readers[read.index()] = true;
555 }
556 }
557 if !gate_terms.is_empty() {
558 for &(read, _) in &read_gate_terms {
559 self.has_readers[read.index()] = true;
560 }
561 }
562 self.operations.push(Operation {
563 kind,
564 left_terms,
565 left_bias,
566 gate_terms,
567 gate_bias,
568 depth: depth + 1,
569 preserve_rounding,
570 macro_op,
571 index_read: None,
572 rounding_point: if kind == OpKind::Boolean { Some(RoundingPointKind::Boolean) } else { None },
573 comparison_bit: false,
574 });
575 self.push_wire_metadata(depth + 1, constant, grid, range);
576 self.expression_wires.insert(key, wire);
577 wire
578 }
579
580 fn push_wire_metadata(&mut self, depth: u32, constant: Option<f64>, grid: Option<f64>, range: Range) {
581 self.depths.push(depth);
582 self.constants.push(constant);
583 self.grids.push(grid);
584 self.exact_bits.push(false);
585 self.has_readers.push(false);
586 self.ranges.push(range);
587 }
588
589 fn fold_constant_terms(&self, terms: Vec<Term>, mut bias: f64) -> (Vec<Term>, f64) {
590 let mut dynamic_terms = Vec::with_capacity(terms.len());
591 for (wire, coefficient) in terms {
592 match self.constants[wire.index()] {
593 Some(constant) => bias += coefficient * constant,
594 None => dynamic_terms.push((wire, coefficient)),
595 }
596 }
597 (dynamic_terms, bias)
598 }
599
600 fn compose_affine_terms(&self, terms: &[Term], mut bias: f64) -> (Vec<Term>, f64) {
601 let mut composed = Vec::with_capacity(terms.len());
602 for &(wire, coefficient) in terms {
603 let producer = self.operation(wire);
604 match producer {
605 Some(operation)
606 if operation.kind == OpKind::Affine
607 && !operation.preserve_rounding
608 && operation.left_terms.len() < AFFINE_SHARE_THRESHOLD =>
609 {
610 bias += coefficient * operation.left_bias;
611 for &(dependency, dependency_coefficient) in &operation.left_terms {
612 composed.push((dependency, coefficient * dependency_coefficient));
613 }
614 }
615 _ => composed.push((wire, coefficient)),
616 }
617 }
618 (normalize_terms(&composed), bias)
619 }
620
621 pub fn validate_source_ranges(&self, source: &[f32]) -> Result<(), Error> {
625 if source.len() != self.source_size {
626 return Err(Error::SourceLength { expected: self.source_size, actual: source.len() });
627 }
628 for (index, &value) in source.iter().enumerate() {
629 let range = self.ranges[index];
630 let value = value as f64;
631 if !(value >= range.min && value <= range.max) {
632 return Err(Error::SourceOutOfRange { wire: Wire::from_index(index), value, range });
633 }
634 }
635 Ok(())
636 }
637
638 pub fn evaluate_reference(&self, source: &[f32]) -> Result<Vec<f32>, Error> {
641 self.validate_source_ranges(source)?;
642 Ok(self.evaluate_unchecked(source))
643 }
644
645 pub(crate) fn evaluate_unchecked(&self, source: &[f32]) -> Vec<f32> {
646 let mut values = Vec::with_capacity(self.width());
647 values.extend_from_slice(source);
648 for operation in &self.operations {
649 let left = evaluate_terms(&values, &operation.left_terms, operation.left_bias);
650 let value = match operation.kind {
651 OpKind::Affine => left,
652 OpKind::Reglu => left * relu32(evaluate_terms(&values, &operation.gate_terms, operation.gate_bias)),
653 OpKind::Boolean => evaluate_boolean(left),
654 OpKind::Index => {
655 let read = operation.index_read.as_ref().unwrap();
656 let index = evaluate_terms(&values, &operation.gate_terms, operation.gate_bias);
657 read_indexed_value(&values, &self.tables[read.table].candidates, read, index as f64)
658 }
659 };
660 values.push(value);
661 }
662 values
663 }
664
665 pub fn backpropagate_reference(
669 &self,
670 values: &[f32],
671 output_gradients: &[f32],
672 options: BackpropOptions,
673 ) -> Result<Vec<f32>, Error> {
674 assert_eq!(values.len(), self.width(), "forward values have the wrong length");
675 assert_eq!(output_gradients.len(), self.width(), "output gradients have the wrong length");
676 let mut gradients = output_gradients.to_vec();
677 for (operation_index, operation) in self.operations.iter().enumerate().rev() {
678 let output_wire = self.source_size + operation_index;
679 let output_gradient = gradients[output_wire];
680 if output_gradient == 0.0 {
681 continue;
682 }
683 match operation.macro_op {
684 Some(Macro::Multiply { left, right }) => {
685 accumulate(&mut gradients, &[(left, values[right.index()] as f64)], output_gradient);
686 accumulate(&mut gradients, &[(right, values[left.index()] as f64)], output_gradient);
687 continue;
688 }
689 Some(Macro::Select { gate, off, on }) => {
690 let gate_value = values[gate.index()];
691 accumulate(
692 &mut gradients,
693 &[(off, (1.0 - gate_value) as f64), (on, gate_value as f64)],
694 output_gradient,
695 );
696 let swing = values[on.index()] - values[off.index()];
697 accumulate(&mut gradients, &[(gate, swing as f64)], output_gradient);
698 continue;
699 }
700 None => {}
701 }
702 match operation.kind {
703 OpKind::Index => {
704 let read = operation.index_read.as_ref().unwrap();
705 if options.index_gradient == IndexGradient::Reject {
706 return Err(Error::IndexGradient { wire: Wire::from_index(output_wire) });
707 }
708 let candidates = &self.tables[read.table].candidates;
709 let index_value = evaluate_terms(values, &operation.gate_terms, operation.gate_bias) as f64;
710 let position = resolve_index_position(index_value, candidates.len(), read.below, read.above);
711 if position >= 0 {
712 accumulate(&mut gradients, &[(candidates[position as usize], 1.0)], output_gradient);
713 }
714 if options.index_gradient == IndexGradient::BackwardDifference && index_value.is_finite() {
715 let floor_index = index_value.floor();
716 let difference = read_indexed_value(values, candidates, read, floor_index) as f64
717 - read_indexed_value(values, candidates, read, floor_index - 1.0) as f64;
718 let scaled = (output_gradient as f64 * difference) as f32;
719 accumulate(&mut gradients, &operation.gate_terms, scaled);
720 }
721 }
722 OpKind::Boolean => {
723 if options.boolean_gradient != BooleanGradient::StraightThrough {
724 return Err(Error::BooleanGradient { wire: Wire::from_index(output_wire) });
725 }
726 accumulate(&mut gradients, &operation.left_terms, output_gradient);
727 }
728 OpKind::Affine => accumulate(&mut gradients, &operation.left_terms, output_gradient),
729 OpKind::Reglu => {
730 let left = evaluate_terms(values, &operation.left_terms, operation.left_bias);
731 let gate = evaluate_terms(values, &operation.gate_terms, operation.gate_bias);
732 accumulate(&mut gradients, &operation.left_terms, output_gradient * relu32(gate));
733 if gate > 0.0 {
734 accumulate(&mut gradients, &operation.gate_terms, output_gradient * left);
735 }
736 }
737 }
738 }
739 Ok(gradients)
740 }
741}
742
743fn key_number(value: f64) -> u64 {
746 (value + 0.0).to_bits()
748}
749
750fn expression_key(kind: OpKind, left_terms: &[Term], left_bias: f64, gate_terms: &[Term], gate_bias: f64) -> Vec<u64> {
751 let mut key = Vec::with_capacity(4 + 2 * (left_terms.len() + gate_terms.len()));
752 key.push(kind as u64);
753 key.push(key_number(left_bias));
754 for &(wire, coefficient) in left_terms {
755 key.extend([wire.0 as u64, key_number(coefficient)]);
756 }
757 key.push(u64::MAX - 4);
758 key.push(key_number(gate_bias));
759 for &(wire, coefficient) in gate_terms {
760 key.extend([wire.0 as u64, key_number(coefficient)]);
761 }
762 key
763}
764
765fn identity_wire(terms: &[Term], bias: f64) -> Option<Wire> {
766 match terms {
767 [(wire, coefficient)] if bias == 0.0 && *coefficient == 1.0 => Some(*wire),
768 _ => None,
769 }
770}
771
772pub(crate) fn normalize_terms(terms: &[Term]) -> Vec<Term> {
774 let mut sorted = terms.to_vec();
775 sorted.sort_by_key(|&(wire, _)| wire);
776 let mut normalized: Vec<Term> = Vec::with_capacity(sorted.len());
777 for (wire, coefficient) in sorted {
778 match normalized.last_mut() {
779 Some(previous) if previous.0 == wire => previous.1 += coefficient,
780 _ => normalized.push((wire, coefficient)),
781 }
782 }
783 normalized.retain(|&(_, coefficient)| coefficient != 0.0);
784 normalized
785}
786
787pub(crate) fn evaluate_terms(values: &[f32], terms: &[Term], bias: f64) -> f32 {
789 let mut value = bias as f32;
790 for &(wire, coefficient) in terms {
791 value += coefficient as f32 * values[wire.index()];
792 }
793 value
794}
795
796fn accumulate(gradients: &mut [f32], terms: &[Term], output_gradient: f32) {
797 for &(wire, coefficient) in terms {
798 gradients[wire.index()] += output_gradient * coefficient as f32;
799 }
800}
801
802pub(crate) fn resolve_index_position(index: f64, count: usize, below: OutOfRange, above: OutOfRange) -> i64 {
805 if index.is_nan() {
806 return -2;
807 }
808 let position = index.floor();
809 if position < 0.0 {
810 return if below == OutOfRange::Zero { -1 } else { 0 };
811 }
812 if position > (count - 1) as f64 {
813 return if above == OutOfRange::Zero { -1 } else { count as i64 - 1 };
814 }
815 position as i64
816}
817
818fn read_indexed_value(values: &[f32], candidates: &[Wire], read: &IndexRead, index: f64) -> f32 {
819 match resolve_index_position(index, candidates.len(), read.below, read.above) {
820 -2 => f32::NAN,
821 -1 => 0.0,
822 position => values[candidates[position as usize].index()],
823 }
824}
825
826pub(crate) fn evaluate_affine_range(terms: &[Term], bias: f64, ranges: &[Range]) -> Range {
827 let mut min = fround(bias);
828 let mut max = fround(bias);
829 for &(wire, coefficient) in terms {
830 let range = ranges[wire.index()];
831 let rounded = fround(coefficient);
832 if rounded > 0.0 {
833 min = fround(min + fround(rounded * range.min));
834 max = fround(max + fround(rounded * range.max));
835 } else {
836 min = fround(min + fround(rounded * range.max));
837 max = fround(max + fround(rounded * range.min));
838 }
839 if min.is_nan() || max.is_nan() {
840 return Range::UNKNOWN;
841 }
842 }
843 if min.is_nan() || max.is_nan() {
844 return Range::UNKNOWN;
845 }
846 Range::new(min, max)
847}
848
849fn multiply_by_relu_range(left: Range, gate: Range) -> Range {
850 let gate_min = gate.min.max(0.0);
851 let gate_max = gate.max.max(0.0);
852 let products = [
853 fround(left.min * gate_min),
854 fround(left.min * gate_max),
855 fround(left.max * gate_min),
856 fround(left.max * gate_max),
857 ];
858 if products.iter().any(|product| product.is_nan()) {
860 return Range::UNKNOWN;
861 }
862 Range::new(
863 products.iter().copied().fold(f64::INFINITY, f64::min),
864 products.iter().copied().fold(f64::NEG_INFINITY, f64::max),
865 )
866}
867
868fn index_read_range(
869 table: &CandidateTable,
870 index_range: Range,
871 below: OutOfRange,
872 above: OutOfRange,
873 ranges: &[Range],
874) -> Range {
875 let count = table.candidates.len() as f64;
876 let lowest = index_range.min.floor().min(count).max(-1.0) as i64;
877 let highest = index_range.max.floor().min(count).max(-1.0) as i64;
878 let count = table.candidates.len() as i64;
879 let mut min = f64::INFINITY;
880 let mut max = f64::NEG_INFINITY;
881 let mut include = |range: Range| {
882 min = min.min(range.min);
883 max = max.max(range.max);
884 };
885 for position in lowest.max(0)..=highest.min(count - 1) {
886 include(ranges[table.candidates[position as usize].index()]);
887 }
888 let zero = Range::new(0.0, 0.0);
889 if lowest < 0 {
890 include(if below == OutOfRange::Zero { zero } else { ranges[table.candidates[0].index()] });
891 }
892 if highest > count - 1 {
893 include(if above == OutOfRange::Zero { zero } else { ranges[table.candidates[(count - 1) as usize].index()] });
894 }
895 Range::new(min, max)
896}
897
898struct GridForm {
899 grid: f64,
900 magnitude: f64,
901}
902
903fn infer_form_grid(terms: &[Term], bias: f64, grids: &[Option<f64>], ranges: &[Range]) -> Option<GridForm> {
907 let rounded_bias = fround(bias);
908 let mut grid = if rounded_bias == 0.0 { f64::INFINITY } else { lowest_power_of_two(rounded_bias) };
909 let mut magnitude = rounded_bias.abs();
910 for &(wire, coefficient) in terms {
911 let wire_grid = grids[wire.index()]?;
912 let range = ranges[wire.index()];
913 let largest_operand = range.min.abs().max(range.max.abs());
914 if !largest_operand.is_finite() {
915 return None;
916 }
917 let rounded = fround(coefficient);
918 if rounded == 0.0 {
919 continue;
920 }
921 grid = grid.min(wire_grid * lowest_power_of_two(rounded));
922 magnitude += rounded.abs() * largest_operand;
923 }
924 if !(grid.is_finite() && grid >= smallest_grid() && magnitude <= EXACT_GRID_STEPS * grid) {
925 return None;
926 }
927 Some(GridForm { grid: grid.min(largest_grid()), magnitude })
928}
929
930fn infer_operation_grid(
931 kind: OpKind,
932 left_terms: &[Term],
933 left_bias: f64,
934 gate_terms: &[Term],
935 gate_bias: f64,
936 grids: &[Option<f64>],
937 ranges: &[Range],
938) -> Option<f64> {
939 if kind == OpKind::Boolean {
940 return Some(1.0);
941 }
942 let left = infer_form_grid(left_terms, left_bias, grids, ranges)?;
943 if kind == OpKind::Affine {
944 return Some(left.grid);
945 }
946 let gate = infer_form_grid(gate_terms, gate_bias, grids, ranges)?;
947 let grid = left.grid * gate.grid;
948 if !(grid >= smallest_grid() && left.magnitude * gate.magnitude <= EXACT_GRID_STEPS * grid) {
949 return None;
950 }
951 Some(grid.min(largest_grid()))
952}
953
954#[cfg(test)]
955mod tests {
956 use super::*;
957
958 #[test]
959 fn small_affine_sums_compose_and_constants_fold() {
960 let mut circuit = Circuit::new(2);
961 let (a, b) = (circuit.source(0), circuit.source(1));
962 let three = circuit.constant(3.0);
963 let sum = circuit.affine(a + b * 2.0);
964 let shifted = circuit.affine(sum - three);
965 let operation = circuit.operation(shifted).unwrap();
966 assert_eq!(operation.left_terms, vec![(a, 1.0), (b, 2.0)]);
967 assert_eq!(operation.left_bias, -3.0);
968 assert_eq!(circuit.affine(a + b * 2.0), sum);
970 assert_eq!(circuit.affine(a), a);
972 }
973
974 #[test]
975 fn ranges_and_grids_are_inferred() {
976 let mut circuit = Circuit::new(1);
977 let x = circuit.source(0);
978 circuit.assume_range(x, 0.0, 16_384.0);
979 circuit.assume_grid(x, 1.0 / 64.0);
980 let half = circuit.affine(x * 0.5 - 26.0);
981 assert_eq!(circuit.range(half), Range::new(-26.0, 8_166.0));
982 assert_eq!(circuit.grid(half), Some(1.0 / 128.0));
983 }
984
985 #[test]
986 #[should_panic(expected = "already read wire 0")]
987 fn late_range_declaration_is_refused() {
988 let mut circuit = Circuit::new(1);
989 let x = circuit.source(0);
990 circuit.relu(x);
991 circuit.assume_range(x, 0.0, 1.0);
992 }
993
994 #[test]
995 fn index_read_floors_and_applies_side_rules() {
996 let mut circuit = Circuit::new(1);
997 let index = circuit.source(0);
998 circuit.assume_range(index, -2.0, 10.0);
999 let table: Vec<Wire> = [28.0, 44.0, 28.0, 60.0].iter().map(|&value| circuit.constant(value)).collect();
1000 let clamped = circuit.index_read(index, &table, OutOfRange::Clamp);
1001 let zeroed = circuit.index_read(index, &table, OutOfRange::Zero);
1002 assert_eq!(circuit.range(zeroed), Range::new(0.0, 60.0));
1003 for (input, expected_clamped, expected_zero) in
1004 [(2.5, 28.0, 28.0), (1.0, 44.0, 44.0), (-1.0, 28.0, 0.0), (7.0, 60.0, 0.0)]
1005 {
1006 let values = circuit.evaluate_reference(&[input]).unwrap();
1007 assert_eq!(values[clamped.index()], expected_clamped);
1008 assert_eq!(values[zeroed.index()], expected_zero);
1009 }
1010 let constant_index =
1012 circuit.index_read_affine(Lin::constant(3.0), &table, OutOfRange::Clamp, OutOfRange::Clamp, None);
1013 assert_eq!(constant_index, table[3]);
1014 }
1015
1016 #[test]
1017 fn source_ranges_are_checked() {
1018 let mut circuit = Circuit::new(1);
1019 let x = circuit.source(0);
1020 circuit.assume_range(x, 0.0, 1.0);
1021 assert!(matches!(circuit.evaluate_reference(&[2.0]), Err(Error::SourceOutOfRange { .. })));
1022 assert!(matches!(circuit.evaluate_reference(&[f32::NAN]), Err(Error::SourceOutOfRange { .. })));
1023 }
1024}