mod common;
use common::{Rng, SOURCES, random_circuit, random_source};
use handwired_rs::{Circuit, Lin, Wire};
#[test]
fn inlined_circuits_become_one_graph_and_share_matching_expressions() {
let mut child = Circuit::new(2);
let child_sum = child.affine(child.source(0) + child.source(1));
let child_product = child.reglu(child_sum, child.source(0));
let mut parent = Circuit::new(2);
let existing_sum = parent.affine(parent.source(0) + parent.source(1));
let map = parent.inline_circuit(&child, &[parent.source(0), parent.source(1)], false);
assert_eq!(map[child_sum.index()], existing_sum);
let output = map[child_product.index()];
let program = parent.compile(Some(&[output]));
assert_eq!(parent.evaluate_tensor(&[2.0, 3.0], &program).unwrap()[output.index()], 10.0);
}
#[test]
fn explicit_rounding_survives_consumers_duplicate_terms_and_inlining() {
let mut circuit = Circuit::new(2);
let (a, b) = (circuit.source(0), circuit.source(1));
let sum = circuit.round_affine(a + b);
let difference = circuit.affine(sum - a);
let duplicate = circuit.round_affine(Lin::new(vec![(a, 1.0), (a, 1.0), (a, 1.0)], 0.0));
let mut parent = Circuit::new(2);
let map = parent.inline_circuit(&circuit, &[parent.source(0), parent.source(1)], false);
let source = [16_777_216.0f32, 1.0];
let values = circuit.evaluate_reference(&source).unwrap();
assert_eq!(values[difference.index()], 0.0);
assert_eq!(values[duplicate.index()], (source[0] + source[0]) + source[0]);
let parent_values = parent.evaluate_reference(&source).unwrap();
assert_eq!(parent_values[map[difference.index()].index()], 0.0);
let program = parent.compile(None);
assert_eq!(parent.evaluate_tensor(&source, &program).unwrap()[map[difference.index()].index()], 0.0);
}
#[test]
fn explicit_term_order_survives_a_permutation_of_the_cells() {
let mut circuit = Circuit::new(3);
let sum = circuit.round_affine(circuit.source(0) + circuit.source(1) + circuit.source(2));
let mut parent = Circuit::new(3);
let map = parent.inline_circuit(&circuit, &[parent.source(0), parent.source(2), parent.source(1)], false);
let original = [16_777_216.0f32, 1.0, -16_777_216.0];
let permuted = [16_777_216.0f32, -16_777_216.0, 1.0];
assert_eq!(circuit.evaluate_reference(&original).unwrap()[sum.index()], 0.0);
assert_eq!(parent.evaluate_reference(&permuted).unwrap()[map[sum.index()].index()], 0.0);
let reordered = parent.round_affine(parent.source(0) + parent.source(1) + parent.source(2));
assert_ne!(reordered, map[sum.index()]);
assert_eq!(parent.evaluate_reference(&permuted).unwrap()[reordered.index()], 1.0);
}
#[test]
fn declarations_landing_on_read_wires_need_permission() {
let mut child = Circuit::new(1);
child.assume_range(child.source(0), 0.0, 1.0);
let mut parent = Circuit::new(1);
let cell = parent.source(0);
parent.multiply(cell, cell);
let mut refused = parent.clone();
let panicked = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
refused.inline_circuit(&child, &[cell], false);
}));
assert!(panicked.is_err());
parent.inline_circuit(&child, &[cell], true);
assert_eq!(parent.range(cell).max, 1.0);
}
#[test]
fn an_exact_bit_lands_on_its_mapped_wire_only() {
let build = |bit: bool| {
let mut inner = Circuit::new(2);
let (enabled, width) = (inner.source(0), inner.source(1));
if bit {
inner.assume_bit(enabled);
} else {
inner.assume_range(enabled, 0.0, 1.0);
}
inner.assume_range(width, 0.0, 100.0);
inner.assume_grid(width, 1.0);
inner.multiply(enabled, width);
let mut outer = Circuit::new(3);
for cell in 0..3 {
outer.assume_range(outer.source(cell), 0.0, 100.0);
}
let map = outer.inline_circuit(&inner, &[outer.source(0), outer.source(1)], true);
(outer, map)
};
let (with_bit, map) = build(true);
let (without, _) = build(false);
let bits: Vec<usize> =
(0..with_bit.width()).filter(|&wire| with_bit.is_exact_bit(Wire::from_index(wire))).collect();
assert_eq!(bits, [0]);
assert!((0..without.width()).all(|wire| !without.is_exact_bit(Wire::from_index(wire))));
assert_eq!(with_bit.operations().len(), without.operations().len());
let program = with_bit.compile(Some(&[*map.last().unwrap()]));
assert_eq!(with_bit.evaluate_tensor(&[1.0, 42.0, 0.0], &program).unwrap()[map.last().unwrap().index()], 42.0);
}
#[test]
fn one_hot_groups_carry_over_and_still_lower() {
let mut child = Circuit::new(4);
let bits = [child.source(0), child.source(1)];
let values = [child.source(2), child.source(3)];
child.assume_one_hot(&bits, true);
for value in values {
child.assume_range(value, 0.0, 100.0);
}
let first = child.multiply(bits[0], values[0]);
let second = child.multiply(bits[1], values[1]);
let read = child.affine(first + second);
let mut parent = Circuit::new(5);
for cell in 1..5 {
parent.assume_range(parent.source(cell), 0.0, if cell < 3 { 1.0 } else { 100.0 });
}
let mapping: Vec<Wire> = (1..5).map(|cell| parent.source(cell)).collect();
let map = parent.inline_circuit(&child, &mapping, false);
assert_eq!(parent.one_hot_groups().len(), 1);
assert_eq!(parent.one_hot_groups()[0].members, [parent.source(1), parent.source(2)]);
assert!(parent.one_hot_groups()[0].allow_all_zero);
let output = map[read.index()];
let program = parent.compile(Some(&[output]));
assert_eq!(program.gather_read_count(), 1);
let source = [9.0, 0.0, 1.0, 5.0, 7.0];
let tensor = parent.evaluate_tensor(&source, &program).unwrap();
assert_eq!(tensor[output.index()], 7.0);
parent.validate_tensor_evaluation(&source, &program, &tensor).unwrap();
}
#[test]
fn random_circuits_inline_and_keep_every_value() {
let mut rng = Rng::new(17);
for _ in 0..100 {
let child = random_circuit(&mut rng, 50);
let mut parent = Circuit::new(2 * SOURCES + 1);
let mapping: Vec<Wire> = (0..SOURCES).map(|cell| parent.source(2 * cell + 1)).collect();
let map = parent.inline_circuit(&child, &mapping, false);
for _ in 0..10 {
let source = random_source(&mut rng);
let mut parent_source = vec![0.0f32; 2 * SOURCES + 1];
for (cell, &value) in source.iter().enumerate() {
parent_source[2 * cell + 1] = value;
}
let child_values = child.evaluate_reference(&source).unwrap();
let parent_values = parent.evaluate_reference(&parent_source).unwrap();
for (wire, &mapped) in map.iter().enumerate() {
let (want, got) = (child_values[wire], parent_values[mapped.index()]);
assert!(want == got || (want.is_nan() && got.is_nan()), "wire {wire}: {want} vs {got}");
}
let roots: Vec<Wire> = map[SOURCES..].to_vec();
let program = parent.compile(Some(&roots));
let tensor = parent.evaluate_tensor(&parent_source, &program).unwrap();
parent.validate_tensor_evaluation(&parent_source, &program, &tensor).unwrap();
}
}
}