use std::collections::HashMap;
use crate::circuit::{Circuit, Macro, OneHotGroup, OpKind, expression_key};
use crate::wire::{Term, Wire};
#[derive(Clone, Debug)]
pub(crate) struct GatherPlan {
pub group: usize,
pub candidates: Vec<Wire>,
}
struct MaskedProduct {
coefficient: f64,
left: Wire,
gate: Wire,
gate_coefficient: f64,
exact_multiply: bool,
}
pub(crate) fn plan_gather_lowering(circuit: &Circuit) -> HashMap<usize, GatherPlan> {
let mut plans = HashMap::new();
if circuit.one_hot_groups.is_empty() {
return plans;
}
let mut groups_by_wire: HashMap<Wire, Vec<usize>> = HashMap::new();
let positions_by_group: Vec<HashMap<Wire, usize>> = circuit
.one_hot_groups
.iter()
.enumerate()
.map(|(group_index, group)| {
let mut positions = HashMap::new();
for (position, &member) in group.members.iter().enumerate() {
positions.insert(member, position);
groups_by_wire.entry(member).or_default().push(group_index);
}
positions
})
.collect();
for (operation_index, operation) in circuit.operations.iter().enumerate() {
if operation.kind != OpKind::Affine || operation.left_terms.is_empty() {
continue;
}
let products: Option<Vec<MaskedProduct>> = operation
.left_terms
.iter()
.map(|&(wire, coefficient)| {
if coefficient != 1.0 && coefficient != -1.0 {
return None;
}
parse_masked_product(circuit, wire, coefficient)
})
.collect();
let Some(products) = products else { continue };
let first = &products[0];
let mut candidate_groups: Vec<usize> = Vec::new();
for wire in [first.gate, first.left] {
for &group in groups_by_wire.get(&wire).into_iter().flatten() {
if !candidate_groups.contains(&group) {
candidate_groups.push(group);
}
}
}
for group in candidate_groups {
let group_positions = &positions_by_group[group];
if let Some(candidates) =
attempt_gather_match(circuit, &products, &circuit.one_hot_groups[group], group_positions)
{
plans.insert(operation_index, GatherPlan { group, candidates });
break;
}
}
}
plans
}
fn resolve_affine_side(circuit: &Circuit, terms: &[Term], bias: f64, allow_negated: bool) -> Option<(Wire, f64)> {
if bias == 0.0 && terms.len() == 1 {
let (wire, coefficient) = terms[0];
if coefficient == 1.0 {
return Some((wire, 1.0));
}
if coefficient == -1.0 && allow_negated {
return Some((wire, -1.0));
}
}
if terms.is_empty() {
return None;
}
if let Some(&wire) = circuit.expression_wires.get(&expression_key(OpKind::Affine, terms, bias, &[], 0.0)) {
return Some((wire, 1.0));
}
if !allow_negated {
return None;
}
let negated: Vec<Term> = terms.iter().map(|&(wire, coefficient)| (wire, -coefficient)).collect();
circuit.expression_wires.get(&expression_key(OpKind::Affine, &negated, -bias, &[], 0.0)).map(|&wire| (wire, -1.0))
}
fn parse_masked_product(circuit: &Circuit, wire: Wire, coefficient: f64) -> Option<MaskedProduct> {
let operation = circuit.operation(wire)?;
if let Some(Macro::Multiply { left, right }) = operation.macro_op {
return Some(MaskedProduct { coefficient, left, gate: right, gate_coefficient: 1.0, exact_multiply: true });
}
if operation.kind != OpKind::Reglu {
return None;
}
let (left, _) = resolve_affine_side(circuit, &operation.left_terms, operation.left_bias, false)?;
let (gate, gate_coefficient) = resolve_affine_side(circuit, &operation.gate_terms, operation.gate_bias, true)?;
Some(MaskedProduct { coefficient, left, gate, gate_coefficient, exact_multiply: false })
}
fn has_finite_range(circuit: &Circuit, wire: Wire) -> bool {
let range = circuit.range(wire);
(range.min as f32).is_finite() && (range.max as f32).is_finite()
}
fn attempt_gather_match(
circuit: &Circuit,
products: &[MaskedProduct],
group: &OneHotGroup,
position_of: &HashMap<Wire, usize>,
) -> Option<Vec<Wire>> {
let mut negative_arms: HashMap<(Wire, Wire), bool> = HashMap::new();
for product in products {
if product.coefficient == -1.0
&& product.gate_coefficient == -1.0
&& position_of.contains_key(&product.left)
&& !position_of.contains_key(&product.gate)
&& negative_arms.insert((product.left, product.gate), false).is_some()
{
return None;
}
}
let mut options_by_term: Vec<Vec<(usize, Wire)>> = Vec::new();
for product in products {
let &MaskedProduct { coefficient, left, gate, gate_coefficient, exact_multiply } = product;
if coefficient == 1.0 && gate_coefficient == 1.0 {
let mut options = Vec::new();
if let Some(&position) = position_of.get(&gate)
&& has_finite_range(circuit, left)
{
options.push((position, left));
}
if let Some(&position) = position_of.get(&left) {
let gate_range = circuit.range(gate);
if exact_multiply && has_finite_range(circuit, gate) {
options.push((position, gate));
} else if let Some(used) = negative_arms.get_mut(&(left, gate))
&& !*used
&& has_finite_range(circuit, gate)
{
*used = true;
options.push((position, gate));
} else if gate_range.min >= 0.0 && gate_range.max.is_finite() {
options.push((position, gate));
}
}
if options.is_empty() {
return None;
}
options_by_term.push(options);
} else if gate_coefficient == -1.0 && position_of.contains_key(&gate) {
if !has_finite_range(circuit, left) {
return None;
}
} else if coefficient == -1.0 && gate_coefficient == -1.0 && position_of.contains_key(&left) {
} else {
return None;
}
}
if negative_arms.values().any(|&used| !used) {
return None;
}
let mut candidates: Vec<Option<Wire>> = vec![None; group.members.len()];
let mut flexible = Vec::new();
for options in options_by_term {
if let [(position, value)] = options[..] {
if candidates[position].is_some() {
return None;
}
candidates[position] = Some(value);
} else {
flexible.push(options);
}
}
for options in flexible {
let &(position, value) = options.iter().find(|&&(position, _)| candidates[position].is_none())?;
candidates[position] = Some(value);
}
candidates.into_iter().collect()
}