boolean_circuit/
circuit.rs

1use std::collections::HashSet;
2
3use itertools::Itertools;
4
5use crate::{
6    gate::{GraphIterator, PostVisitIterator},
7    Gate, Operation,
8};
9
10/// A boolean circuit with named inputs and outputs.
11/// Input variables are identified by their name.
12/// The gates in the circuit are [`Gate`]s.
13#[derive(Default)]
14pub struct Circuit {
15    /// The output gates of the circuit. They contain their predecessors.
16    outputs: Vec<Gate>,
17    /// The names of the (non-constant) input gates, specifying their order.
18    /// If `None`, the input gates are in no particular order and identified
19    /// by their name only. If `Some(_)`, the list of names need to match
20    /// the names of the input gates.
21    input_names: Option<Vec<String>>,
22    /// The names of the output gates. Unnamed outputs use the empty string.
23    output_names: Vec<String>,
24}
25
26impl From<Gate> for Circuit {
27    fn from(gate: Gate) -> Self {
28        Circuit {
29            outputs: vec![gate],
30            input_names: None,
31            output_names: vec![String::new()],
32        }
33    }
34}
35
36impl Circuit {
37    /// Creates a circuit from unnamed outputs. Note that the [`Gate`]s can
38    /// share predecessors. Any two input gates in the circuit with the same
39    /// name are considered identical.
40    pub fn from_unnamed_outputs(outputs: impl IntoIterator<Item = Gate>) -> Self {
41        Self::from_named_outputs(outputs.into_iter().map(|n| (n, String::new())))
42    }
43
44    /// Adds the order of input gates to the circuit, or changes it.
45    pub fn with_input_order(
46        self,
47        input_names: impl IntoIterator<Item = impl ToString>,
48    ) -> Result<Self, String> {
49        let input_names = input_names.into_iter().map(|n| n.to_string()).collect_vec();
50        let inputs_in_circuit = self.input_names_from_traversal().collect::<HashSet<_>>();
51        let input_names_set = input_names
52            .iter()
53            .map(|n| n.as_str())
54            .collect::<HashSet<_>>();
55        if input_names_set.len() != input_names.len() {
56            return Err("Duplicate input names in list.".to_string());
57        }
58        if inputs_in_circuit != input_names_set {
59            return Err(format!(
60                "Input names do not match circuit inputs:\n{}\n  !=\n{}",
61                input_names.iter().sorted().format(", "),
62                inputs_in_circuit.iter().sorted().format(", ")
63            ));
64        }
65
66        Ok(Circuit {
67            input_names: Some(input_names),
68            ..self
69        })
70    }
71
72    /// Creates a circuit from (uniquely) named outputs.  Note that the [`Gate`]s can
73    /// share predecessors. Any two input gates in the circuit with the same
74    /// name are considered identical.
75    ///
76    /// The empty string can be used to not name outputs.
77    ///
78    /// Panics if the output names are not unique.
79    pub fn from_named_outputs(items: impl IntoIterator<Item = (Gate, impl ToString)>) -> Self {
80        let mut seen_names: HashSet<_> = Default::default();
81        let mut circuit = Self::default();
82        for (gate, name) in items {
83            let name = name.to_string();
84            if !name.is_empty() && !seen_names.insert(name.clone()) {
85                panic!("Duplicate output name {name}");
86            }
87            circuit.outputs.push(gate);
88            circuit.output_names.push(name);
89        }
90        circuit
91    }
92
93    /// Returns the names of the input gates either in the order given by [`with_input_order`] or
94    /// otherwise from a circuit traversal.
95    pub fn input_names(&self) -> impl Iterator<Item = &str> + '_ {
96        if let Some(names) = &self.input_names {
97            Box::new(names.iter().map(|n| n.as_str())) as Box<dyn Iterator<Item = &str>>
98        } else {
99            Box::new(self.input_names_from_traversal())
100        }
101    }
102
103    /// Returns the output gates.
104    pub fn outputs(&self) -> &[Gate] {
105        &self.outputs
106    }
107
108    /// Returns the names of the output gates, where unnamed output gates use the empty String.
109    pub fn output_names(&self) -> &[String] {
110        &self.output_names
111    }
112
113    /// Returns an iterator over the output gates and their names, where unnamed output
114    /// gates use the empty string.
115    pub fn named_outputs(&self) -> impl Iterator<Item = (&Gate, &String)> {
116        self.outputs().iter().zip_eq(self.output_names())
117    }
118
119    /// Creates an iterator over the graph (the output gates and all their predecessors)
120    /// that returns each gate exactly once.
121    pub fn iter(&self) -> impl Iterator<Item = &Gate> {
122        GraphIterator::new(&self.outputs)
123    }
124
125    /// Creates an iterator over the graph (the output gates and all their predecessors)
126    /// with post-visit order, visiting each gate exactly once.
127    /// This means that the predecessors of each gate are always visited before the gate itself.
128    pub fn post_visit_iter(&self) -> impl Iterator<Item = &Gate> {
129        PostVisitIterator::new(self.outputs.iter())
130    }
131
132    /// Returns the names of the input gates from a traversal of the circuit,
133    /// not as potentially given by [`with_input_order`].
134    fn input_names_from_traversal(&self) -> impl Iterator<Item = &str> {
135        self.iter()
136            .filter_map(|gate| match gate.operation() {
137                Operation::Variable(name) => Some(name.as_str()),
138                _ => None,
139            })
140            .unique()
141    }
142}
143
144#[cfg(test)]
145mod test {
146    use super::*;
147
148    #[test]
149    fn input_order() {
150        let c = Circuit::from(Gate::from("a") & Gate::from("b"));
151        assert_eq!(c.input_names().collect::<Vec<_>>(), vec!["a", "b"]);
152        let c = c.with_input_order(["b", "a"]).unwrap();
153        assert_eq!(
154            c.input_names().collect::<Vec<_>>(),
155            vec!["b".to_string(), "a".to_string()]
156        );
157    }
158}