Skip to main content

handwired_rs/
lib.rs

1//! Exact programs as neural nets: a Rust port of the core of
2//! [handwired](https://github.com/chenglou/handwired).
3//!
4//! A program is a fixed list of f32 operations, mostly weighted sums and
5//! products with a ReLU, written with builder code that runs once. Because it
6//! uses only these primitives, it compiles into layers of sparse matrix
7//! multiplications and stays differentiable, while computing exactly what the
8//! equivalent ordinary code computes.
9//!
10//! ```
11//! use handwired_rs::{emit_compare, Circuit, Relation};
12//!
13//! // const result = (a > 4) ? 10 : 12, for an integer a within 0..100.
14//! let mut circuit = Circuit::new(1);
15//! let a = circuit.source(0);
16//! circuit.assume_range(a, 0.0, 100.0);
17//! circuit.assume_grid(a, 1.0);
18//! let four = circuit.constant(4.0);
19//! let above_four = emit_compare(&mut circuit, a, Relation::Greater, four);
20//! let result = circuit.affine(above_four * -2.0 + 12.0);
21//!
22//! for value in 0..=100 {
23//!     let values = circuit.evaluate_reference(&[value as f32]).unwrap();
24//!     assert_eq!(values[result.index()], if value > 4 { 10.0 } else { 12.0 });
25//! }
26//! ```
27//!
28//! Builder methods panic when a contract is violated, e.g. a comparison on an
29//! operand without a finite declared range, since that is a bug in the
30//! program being built. Evaluation returns [`Error`] for bad input data.
31
32mod cells;
33mod circuit;
34pub mod dsl;
35mod emit;
36mod numeric;
37mod tensor;
38mod wire;
39
40pub use cells::{CellSpec, Policy, SourceTable, constant, event, input, input_bit, state, state_bit};
41pub use circuit::{
42    BackpropOptions, BooleanGradient, CandidateTable, Circuit, IndexGradient, IndexRead, Macro, OpKind, Operation,
43    OutOfRange, RoundingPointKind,
44};
45pub use emit::{
46    CompareOptions, Relation, emit_and, emit_clamp_exact, emit_compare, emit_compare_with, emit_maximum,
47    emit_maximum_exact, emit_minimum, emit_minimum_exact, emit_not, emit_or, emit_relu, emit_round, emit_select,
48};
49pub use handwired_rs_macros::handwired;
50pub use numeric::Range;
51pub use tensor::{IndexReadRow, TensorExecutor, TensorLayer, TensorProgram};
52pub use wire::{Lin, Term, Wire};
53
54#[derive(Clone, Debug, PartialEq)]
55pub enum Error {
56    SourceLength {
57        expected: usize,
58        actual: usize,
59    },
60    SourceOutOfRange {
61        wire: Wire,
62        value: f64,
63        range: Range,
64    },
65    /// A nonzero gradient reached a boolean atom without `BooleanGradient::StraightThrough`.
66    BooleanGradient {
67        wire: Wire,
68    },
69    /// A nonzero gradient reached an index read without an `IndexGradient` rule.
70    IndexGradient {
71        wire: Wire,
72    },
73}
74
75impl std::fmt::Display for Error {
76    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        match self {
78            Error::SourceLength { expected, actual } => {
79                write!(formatter, "source table has {actual} cells, expected {expected}")
80            }
81            Error::SourceOutOfRange { wire, value, range } => write!(
82                formatter,
83                "source wire {} is {value}, outside its declared range {}..{}",
84                wire.index(),
85                range.min,
86                range.max
87            ),
88            Error::BooleanGradient { wire } => {
89                write!(formatter, "cannot backpropagate through the boolean atom at wire {}", wire.index())
90            }
91            Error::IndexGradient { wire } => {
92                write!(formatter, "cannot backpropagate into the index of the index read at wire {}", wire.index())
93            }
94        }
95    }
96}
97
98impl std::error::Error for Error {}