1use crate::circuit::{Circuit, OpKind, RoundingPointKind};
7use crate::numeric::{
8 EXACT_GRID_STEPS, Range, fround, largest_grid, lowest_power_of_two, pow2, power_of_two_exponent_at_least,
9 smallest_grid,
10};
11use crate::wire::{Lin, Term, Wire};
12
13pub fn emit_relu(circuit: &mut Circuit, value: Wire) -> Wire {
15 circuit.reglu(1.0, value)
16}
17
18pub fn emit_maximum(circuit: &mut Circuit, a: Wire, b: Wire) -> Wire {
21 let difference = circuit.round_affine(a - b);
22 let positive = emit_relu(circuit, difference);
23 let result = circuit.round_affine(b + positive);
24 let (a_range, b_range) = (circuit.range(a), circuit.range(b));
25 if a_range.min >= 0.0 && a_range.max <= 1.0 && b_range.min >= 0.0 && b_range.max <= 1.0 {
26 circuit.assume_range(result, 0.0, 1.0);
27 }
28 result
29}
30
31pub fn emit_minimum(circuit: &mut Circuit, a: Wire, b: Wire) -> Wire {
34 let difference = circuit.round_affine(a - b);
35 let positive = emit_relu(circuit, difference);
36 let result = circuit.round_affine(a - positive);
37 if circuit.is_non_negative(a)
38 && circuit.is_non_negative(b)
39 && circuit.range(a).is_finite()
40 && circuit.range(b).is_finite()
41 {
42 let maximum = fround(circuit.range(a).max);
43 circuit.assume_range(result, 0.0, maximum);
44 }
45 result
46}
47
48pub fn emit_select(circuit: &mut Circuit, gate: Wire, off: Wire, on: Wire) -> Wire {
50 circuit.select(gate, off, on)
51}
52
53pub fn emit_not(circuit: &mut Circuit, bit: Wire) -> Wire {
55 circuit.affine(1.0 - bit)
56}
57
58pub fn emit_and(circuit: &mut Circuit, bits: &[Wire]) -> Wire {
60 let sum = Lin::new(bits.iter().map(|&bit| (bit, 1.0)).collect(), 1.0 - bits.len() as f64);
61 circuit.reglu(1.0, sum)
62}
63
64pub fn emit_or(circuit: &mut Circuit, bits: &[Wire]) -> Wire {
66 let missing = Lin::new(bits.iter().map(|&bit| (bit, -1.0)).collect(), 1.0);
67 let none = circuit.relu(missing);
68 circuit.affine(1.0 - none)
69}
70
71pub fn emit_round(circuit: &mut Circuit, value: Wire) -> Wire {
75 const SHIFT: f64 = 12_582_912.0; let shifted = circuit.round_affine(value + SHIFT);
77 let rounded = circuit.round_affine(shifted - SHIFT);
78 circuit.mark_rounding_point(shifted);
79 circuit.mark_rounding_point(rounded);
80 rounded
81}
82
83#[derive(Copy, Clone, Debug, PartialEq, Eq)]
84pub enum Relation {
85 Greater,
86 AtLeast,
87 Less,
88 AtMost,
89 Equal,
90}
91
92impl Relation {
93 pub fn symbol(self) -> &'static str {
95 match self {
96 Relation::Greater => ">",
97 Relation::AtLeast => ">=",
98 Relation::Less => "<",
99 Relation::AtMost => "<=",
100 Relation::Equal => "==",
101 }
102 }
103
104 pub fn holds(self, a: f32, b: f32) -> bool {
106 match self {
107 Relation::Greater => a > b,
108 Relation::AtLeast => a >= b,
109 Relation::Less => a < b,
110 Relation::AtMost => a <= b,
111 Relation::Equal => a == b,
112 }
113 }
114}
115
116#[derive(Copy, Clone, Debug, Default)]
117pub struct CompareOptions {
118 pub end_with_boolean: bool,
122}
123
124#[derive(Copy, Clone, Debug, PartialEq, Eq)]
125enum SignTest {
126 Greater,
127 AtLeast,
128 Equal,
129}
130
131#[derive(Copy, Clone, Debug)]
132struct CompareOperand {
133 wire: Option<Wire>,
134 value: f64,
135 range: Range,
136 grid: Option<f64>,
137}
138
139fn compare_operand(circuit: &Circuit, operand: Wire) -> CompareOperand {
140 circuit.assert_wire(operand);
141 if let Some(constant) = circuit.constant_value(operand) {
142 let value = fround(constant);
143 assert!(value.is_finite(), "a constant comparison operand must be finite");
144 let value = if value == 0.0 { 0.0 } else { value };
146 return CompareOperand {
147 wire: None,
148 value,
149 range: Range::new(value, value),
150 grid: Some(if value == 0.0 { f64::INFINITY } else { lowest_power_of_two(value) }),
151 };
152 }
153 CompareOperand { wire: Some(operand), value: 0.0, range: circuit.range(operand), grid: circuit.grid(operand) }
154}
155
156pub fn emit_compare(circuit: &mut Circuit, a: Wire, relation: Relation, b: Wire) -> Wire {
162 emit_compare_with(circuit, a, relation, b, CompareOptions::default())
163}
164
165pub fn emit_compare_with(circuit: &mut Circuit, a: Wire, relation: Relation, b: Wire, options: CompareOptions) -> Wire {
166 let mut left = compare_operand(circuit, a);
167 let mut right = compare_operand(circuit, b);
168 if matches!(relation, Relation::Less | Relation::AtMost) {
169 std::mem::swap(&mut left, &mut right);
170 }
171 let test = match relation {
172 Relation::Greater | Relation::Less => SignTest::Greater,
173 Relation::AtLeast | Relation::AtMost => SignTest::AtLeast,
174 Relation::Equal => SignTest::Equal,
175 };
176 if left.wire.is_none() && right.wire.is_none() {
177 let holds = match test {
178 SignTest::Greater => left.value > right.value,
179 SignTest::AtLeast => left.value >= right.value,
180 SignTest::Equal => left.value == right.value,
181 };
182 return circuit.constant(if holds { 1.0 } else { 0.0 });
183 }
184 for operand in [left, right] {
185 assert!(
186 operand.range.is_finite(),
187 "emit_compare needs finite declared ranges on both operands, got {}..{}",
188 operand.range.min,
189 operand.range.max
190 );
191 }
192 if left.wire == right.wire {
194 return circuit.constant(if test == SignTest::Greater { 0.0 } else { 1.0 });
195 }
196 let difference_bound = (left.range.max - right.range.min).abs().max((left.range.min - right.range.max).abs());
197 assert!(fround(difference_bound).is_finite(), "the difference of the declared operand ranges can overflow FP32");
198 let mut terms: Vec<Term> = Vec::new();
199 if let Some(wire) = left.wire {
200 terms.push((wire, 1.0));
201 }
202 if let Some(wire) = right.wire {
203 terms.push((wire, -1.0));
204 }
205 let bias = left.value - right.value;
207 let bit = match emit_grid_compare(circuit, &left, &right, &terms, bias, test, options.end_with_boolean) {
208 Some(bit) => bit,
209 None => emit_cascade_compare(circuit, &terms, bias, fround(difference_bound), test, options.end_with_boolean),
210 };
211 circuit.assume_range(bit, 0.0, 1.0);
212 circuit.assume_grid(bit, 1.0);
213 if bit.index() >= circuit.source_size() {
214 let index = bit.index() - circuit.source_size();
215 circuit.operations[index].comparison_bit = true;
216 }
217 bit
218}
219
220fn largest_partial_sum(terms: &[Term], bias: f64, ranges: &[Range]) -> f64 {
223 let (mut minimum, mut maximum, mut largest) = (bias, bias, bias.abs());
224 for &(wire, coefficient) in terms {
225 let range = ranges[wire.index()];
226 let (low, high) = if coefficient >= 0.0 {
227 (coefficient * range.min, coefficient * range.max)
228 } else {
229 (coefficient * range.max, coefficient * range.min)
230 };
231 minimum += low;
232 maximum += high;
233 largest = largest.max(low.abs()).max(high.abs()).max(minimum.abs()).max(maximum.abs());
234 }
235 largest
236}
237
238fn scaled_terms(terms: &[Term], factor: f64) -> Vec<Term> {
239 terms.iter().map(|&(wire, coefficient)| (wire, coefficient * factor)).collect()
240}
241
242fn emit_grid_compare(
245 circuit: &mut Circuit,
246 left: &CompareOperand,
247 right: &CompareOperand,
248 terms: &[Term],
249 bias: f64,
250 test: SignTest,
251 end_with_boolean: bool,
252) -> Option<Wire> {
253 let grid = left.grid?.min(right.grid?);
254 let largest_steps = |circuit: &Circuit, sign: f64, offset: f64| {
255 largest_partial_sum(&scaled_terms(terms, sign / grid), sign * bias / grid + offset, &circuit.ranges)
256 };
257 let mut emitted = Vec::new();
258 let bit;
259 if end_with_boolean {
260 let score_scale = pow2(-24) / grid;
263 let fits = match test {
264 SignTest::Equal => {
265 largest_steps(circuit, 1.0, 0.0) <= pow2(22) && largest_steps(circuit, -1.0, 0.0) <= pow2(22)
266 }
267 _ => largest_steps(circuit, 1.0, if test == SignTest::Greater { -1.0 } else { 0.0 }) <= pow2(22),
268 };
269 if !(fits && score_scale >= smallest_grid() && score_scale <= largest_grid()) {
270 return None;
271 }
272 let mut emit_atom = |circuit: &mut Circuit, sign: f64, offset: f64| {
273 let score =
274 Lin::new(scaled_terms(terms, sign * score_scale), 0.5 + (sign * bias / grid - offset) * pow2(-24));
275 let atom = circuit.append(OpKind::Boolean, score, Lin::default(), true, None);
276 emitted.push(atom);
277 atom
278 };
279 if test == SignTest::Equal {
280 let at_least = emit_atom(circuit, 1.0, 0.0);
281 let at_most = emit_atom(circuit, -1.0, 0.0);
282 bit = circuit.reglu(1.0, at_least + at_most - 1.0);
283 emitted.push(bit);
284 } else {
285 bit = emit_atom(circuit, 1.0, if test == SignTest::Greater { 1.0 } else { 0.0 });
286 }
287 } else {
288 let scale = 1.0 / grid;
289 let fits = match test {
291 SignTest::Equal => {
292 largest_steps(circuit, 1.0, 0.0) < EXACT_GRID_STEPS
293 && largest_steps(circuit, -1.0, 0.0) < EXACT_GRID_STEPS
294 }
295 _ => largest_steps(circuit, -1.0, if test == SignTest::Greater { 1.0 } else { 0.0 }) < EXACT_GRID_STEPS,
296 };
297 if !(fits && scale <= largest_grid()) {
298 return None;
299 }
300 bit = emit_integer_bit(circuit, test, &mut emitted, |circuit, sign, offset| {
301 let ramp = Lin::new(scaled_terms(terms, sign * scale), sign * bias / grid + offset);
302 circuit.append(OpKind::Reglu, Lin::constant(1.0), ramp, true, None)
303 });
304 }
305 for wire in emitted {
306 circuit.record_rounding_point(wire, RoundingPointKind::IntegerBit);
307 }
308 Some(bit)
309}
310
311fn emit_integer_bit(
316 circuit: &mut Circuit,
317 test: SignTest,
318 emitted: &mut Vec<Wire>,
319 mut emit_ramp: impl FnMut(&mut Circuit, f64, f64) -> Wire,
320) -> Wire {
321 let ramps = if test == SignTest::Equal {
322 vec![emit_ramp(circuit, 1.0, 0.0), emit_ramp(circuit, -1.0, 0.0)]
323 } else {
324 vec![emit_ramp(circuit, -1.0, if test == SignTest::Greater { 1.0 } else { 0.0 })]
325 };
326 let bit = circuit.reglu(1.0, Lin::new(ramps.iter().map(|&ramp| (ramp, -1.0)).collect(), 1.0));
327 emitted.extend(ramps);
328 emitted.push(bit);
329 bit
330}
331
332const FULL_LIFT_EXPONENT: i32 = 149;
334const ROUNDING_STAGE_LIFT_EXPONENT: i32 = 104;
337
338#[derive(Copy, Clone, Debug, PartialEq)]
339struct CascadeStage {
340 scale: f64,
341 limit: f64,
342}
343
344fn plan_sign_cascade(difference_bound: f64, lift_exponent: i32) -> Vec<CascadeStage> {
348 let bound_exponent = power_of_two_exponent_at_least(difference_bound.max(smallest_grid()));
349 let mut stages = Vec::new();
350 let mut limit_exponent = bound_exponent;
351 let mut gain_exponent = 0;
352 while stages.is_empty() || limit_exponent > 0 || gain_exponent < lift_exponent {
353 let next_limit_exponent = (limit_exponent - 24).max(0);
354 let room = (24 + next_limit_exponent - limit_exponent).min(127);
356 let scale_exponent = room.min((lift_exponent - gain_exponent).max(0));
357 stages.push(CascadeStage { scale: pow2(scale_exponent), limit: pow2(next_limit_exponent) });
358 limit_exponent = next_limit_exponent;
359 gain_exponent += scale_exponent;
360 }
361 stages
362}
363
364struct Cascade {
365 difference: Wire,
366 difference_sign: f64,
367}
368
369impl Cascade {
370 fn emit_clamp_stages(
372 &self,
373 circuit: &mut Circuit,
374 sign: f64,
375 stages: &[CascadeStage],
376 emitted: &mut Vec<Wire>,
377 ) -> Wire {
378 let mut stage_output = self.difference;
379 for (index, &CascadeStage { scale, limit }) in stages.iter().enumerate() {
380 let (scaled_term, above_limit) = if index == 0 {
381 let gate: Vec<Term> = vec![(self.difference, sign * self.difference_sign * scale)];
383 let scaled_positive =
384 circuit.append(OpKind::Reglu, Lin::constant(1.0), Lin::new(gate.clone(), 0.0), true, None);
385 let above_limit = circuit.append(OpKind::Reglu, Lin::constant(1.0), Lin::new(gate, -limit), true, None);
386 emitted.push(scaled_positive);
387 ((scaled_positive, 1.0), above_limit)
388 } else {
389 let above_limit = circuit.reglu(1.0, stage_output * scale - limit);
390 ((stage_output, scale), above_limit)
391 };
392 stage_output = circuit.round_affine(Lin::new(vec![scaled_term, (above_limit, -1.0)], 0.0));
394 circuit.assume_range(stage_output, 0.0, limit);
395 emitted.push(above_limit);
396 emitted.push(stage_output);
397 }
398 stage_output
399 }
400}
401
402fn emit_cascade_compare(
403 circuit: &mut Circuit,
404 terms: &[Term],
405 bias: f64,
406 difference_bound: f64,
407 test: SignTest,
408 end_with_boolean: bool,
409) -> Wire {
410 let mut emitted = Vec::new();
411 let cascade = if bias == 0.0 && terms.len() == 1 {
414 Cascade { difference: terms[0].0, difference_sign: terms[0].1 }
415 } else {
416 let difference = circuit.round_affine(Lin::new(terms.to_vec(), bias));
417 emitted.push(difference);
418 Cascade { difference, difference_sign: 1.0 }
419 };
420 let lift = if end_with_boolean { ROUNDING_STAGE_LIFT_EXPONENT } else { FULL_LIFT_EXPONENT };
421 let stages = plan_sign_cascade(difference_bound, lift);
422 let bit;
423 if end_with_boolean {
424 let score_scale = pow2(22);
425 if test == SignTest::Greater {
426 let stage_output = cascade.emit_clamp_stages(circuit, 1.0, &stages, &mut emitted);
427 let just_below_half = 0.5 - pow2(-25);
429 let score_above_zero = circuit.reglu(1.0, stage_output * score_scale + just_below_half);
430 let score_above_one = circuit.reglu(1.0, stage_output * score_scale + (just_below_half - 1.0));
431 let score = circuit.affine(score_above_zero - score_above_one);
432 circuit.assume_range(score, 0.0, 1.0);
433 emitted.push(score_above_zero);
434 emitted.push(score_above_one);
435 bit = circuit.boolean(score);
436 } else {
437 let mut gate = vec![(cascade.emit_clamp_stages(circuit, -1.0, &stages, &mut emitted), -score_scale)];
438 if test == SignTest::Equal {
439 gate.push((cascade.emit_clamp_stages(circuit, 1.0, &stages, &mut emitted), -score_scale));
440 }
441 let score = circuit.reglu(1.0, Lin::new(gate, 0.5));
442 emitted.push(score);
443 bit = circuit.boolean(score);
444 }
445 emitted.push(bit);
446 } else {
447 let final_scale = stages.last().unwrap().scale;
448 let clamp_stages = &stages[..stages.len() - 1];
449 if clamp_stages.is_empty() {
450 bit = emit_integer_bit(circuit, test, &mut emitted, |circuit, sign, offset| {
452 let gate = Lin::new(vec![(cascade.difference, sign * cascade.difference_sign * final_scale)], offset);
453 circuit.append(OpKind::Reglu, Lin::constant(1.0), gate, true, None)
454 });
455 } else if test == SignTest::Greater {
456 let stage = cascade.emit_clamp_stages(circuit, 1.0, clamp_stages, &mut emitted);
458 let not_greater = circuit.reglu(1.0, 1.0 - stage * final_scale);
459 bit = circuit.reglu(1.0, 1.0 - not_greater);
460 emitted.push(not_greater);
461 emitted.push(bit);
462 } else {
463 let mut gate = vec![(cascade.emit_clamp_stages(circuit, -1.0, clamp_stages, &mut emitted), -final_scale)];
464 if test == SignTest::Equal {
465 gate.push((cascade.emit_clamp_stages(circuit, 1.0, clamp_stages, &mut emitted), -final_scale));
466 }
467 bit = circuit.reglu(1.0, Lin::new(gate, 1.0));
468 emitted.push(bit);
469 }
470 }
471 for wire in emitted {
472 circuit.record_rounding_point(wire, RoundingPointKind::SignCascade);
473 }
474 bit
475}
476
477pub fn emit_minimum_exact(circuit: &mut Circuit, a: Wire, b: Wire) -> Wire {
480 let a_is_smaller = emit_compare(circuit, a, Relation::AtMost, b);
481 let a_range = compare_operand(circuit, a).range;
482 let b_range = compare_operand(circuit, b).range;
483 emit_select_operand(circuit, a_is_smaller, b, a, a_range.min.min(b_range.min), a_range.max.min(b_range.max))
484}
485
486pub fn emit_maximum_exact(circuit: &mut Circuit, a: Wire, b: Wire) -> Wire {
488 let a_is_larger = emit_compare(circuit, a, Relation::AtLeast, b);
489 let a_range = compare_operand(circuit, a).range;
490 let b_range = compare_operand(circuit, b).range;
491 emit_select_operand(circuit, a_is_larger, b, a, a_range.min.max(b_range.min), a_range.max.max(b_range.max))
492}
493
494pub fn emit_clamp_exact(circuit: &mut Circuit, value: Wire, minimum: Wire, maximum: Wire) -> Wire {
496 let at_least_minimum = emit_maximum_exact(circuit, value, minimum);
497 emit_minimum_exact(circuit, at_least_minimum, maximum)
498}
499
500fn emit_select_operand(circuit: &mut Circuit, bit: Wire, off: Wire, on: Wire, minimum: f64, maximum: f64) -> Wire {
503 let selected_on = circuit.reglu(on, bit);
504 let selected_off = circuit.reglu(off, 1.0 - bit);
505 let result = circuit.affine(selected_on + selected_off);
506 circuit.assume_range(result, minimum, maximum);
507 result
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513
514 #[test]
515 fn cascade_plan_matches_handwired_comment() {
516 let scales: Vec<f64> =
518 plan_sign_cascade(16_384.0, FULL_LIFT_EXPONENT).iter().map(|stage| stage.scale.log2()).collect();
519 assert_eq!(scales, vec![10.0, 24.0, 24.0, 24.0, 24.0, 24.0, 19.0]);
520 assert!(plan_sign_cascade(16_384.0, FULL_LIFT_EXPONENT).iter().all(|stage| stage.limit == 1.0));
521 }
522}