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 {}