boolean_circuit/
gate.rs

1use std::{collections::HashSet, rc::Rc};
2
3use super::literal::Literal;
4
5/// A gate in a boolean circuit.
6///
7/// The inputs of the circuit are variables identified by their name.
8/// Other types of gates are constant gates or operations that have
9/// predecessors.
10///
11/// The clone operation only performs a shallow clone and thus
12/// allows sharing of gates.
13#[derive(Clone, Debug)]
14pub struct Gate(Rc<Operation>);
15
16/// The inner operation of a {Gate} and the predecessors.
17#[derive(Clone, Debug)]
18pub enum Operation {
19    /// An input variable.
20    Variable(Rc<String>),
21    /// A constant value.
22    Constant(bool),
23    /// The boolean negation of another gate.
24    Negation(Gate),
25    /// The boolean conjunction of two gates.
26    Conjunction(Gate, Gate),
27    /// The boolean disjunction of two gates.
28    Disjunction(Gate, Gate),
29    /// The boolean exclusive or of two gates.
30    Xor(Gate, Gate),
31}
32
33impl From<&str> for Gate {
34    /// Constructs a new gate representing an input variable.
35    ///
36    /// Two variables with the same name are considered equal, even
37    /// if they are different {Gate} instances.
38    fn from(name: &str) -> Gate {
39        name.to_string().into()
40    }
41}
42
43impl From<String> for Gate {
44    /// Constructs a new gate representing an input variable.
45    ///
46    /// Two variables with the same name are considered equal, even
47    /// if they are different gate instances.
48    fn from(name: String) -> Gate {
49        Gate(Rc::new(Operation::Variable(Rc::new(name))))
50    }
51}
52
53impl From<bool> for Gate {
54    /// Creates a constant gate.
55    fn from(value: bool) -> Gate {
56        Gate(Rc::new(Operation::Constant(value)))
57    }
58}
59
60impl From<&Literal> for Gate {
61    /// Converts a literal to either a variable or a gate that is the negation
62    /// of a variable.
63    fn from(literal: &Literal) -> Gate {
64        let v = literal.var().into();
65        if literal.is_negated() {
66            !&v
67        } else {
68            v
69        }
70    }
71}
72
73impl Gate {
74    /// Returns an identifier for the gate which is unique in the circuit
75    /// but not stable across program reloads.
76    pub fn id(&self) -> usize {
77        Rc::as_ptr(&self.0) as usize
78    }
79
80    /// Returns the operation of the gate.
81    pub fn operation(&self) -> &Operation {
82        &self.0
83    }
84
85    /// Creates an iterator over the graph (the gate itself and all its predecessors)
86    /// that returns each gate exactly once.
87    pub fn iter(&self) -> impl Iterator<Item = &Gate> {
88        self.into_iter()
89    }
90
91    /// Creates an iterator over the graph (the gate itself and all its predecessors)
92    /// with post-visit order, visiting each gate exactly once.
93    /// This means that the predecessors of each gate are always visited before the gate itself.
94    pub fn post_visit_iter(&self) -> impl Iterator<Item = &Gate> {
95        PostVisitIterator::new(std::iter::once(self))
96    }
97
98    /// Turns the gate and its predecessors into a string representation, repeating shared gates.
99    pub fn to_string_as_tree(&self) -> String {
100        match self.operation() {
101            Operation::Variable(name) => format!("{name}"),
102            Operation::Constant(value) => format!("{value}"),
103            Operation::Conjunction(left, right) => {
104                format!(
105                    "({} & {})",
106                    left.to_string_as_tree(),
107                    right.to_string_as_tree()
108                )
109            }
110            Operation::Disjunction(left, right) => {
111                format!(
112                    "({} | {})",
113                    left.to_string_as_tree(),
114                    right.to_string_as_tree()
115                )
116            }
117            Operation::Xor(left, right) => format!(
118                "({} ^ {})",
119                left.to_string_as_tree(),
120                right.to_string_as_tree()
121            ),
122            Operation::Negation(gate) => format!("!{}", gate.to_string_as_tree()),
123        }
124    }
125
126    /// Returns the value of the gate if it is a constant input gate.
127    pub fn try_to_constant(&self) -> Option<bool> {
128        match self.operation() {
129            Operation::Constant(value) => Some(*value),
130            _ => None,
131        }
132    }
133
134    /// Returns the number of variables and inner gates (gates not counting
135    /// variables or constants) in the circuit.
136    ///
137    /// This depends on how the circuit was constructed, i.e. it does not deduplicate
138    /// sub-circuits, but it does deduplicate variables with the same name.
139    pub fn gate_count(&self) -> (usize, usize) {
140        let mut vars: HashSet<_> = Default::default();
141        let mut gate_count = 0;
142        for n in self {
143            match n.operation() {
144                Operation::Variable(v) => {
145                    vars.insert(v.as_str());
146                }
147                Operation::Constant(_) => {}
148                Operation::Negation(..)
149                | Operation::Conjunction(..)
150                | Operation::Disjunction(..)
151                | Operation::Xor(..) => {
152                    gate_count += 1;
153                }
154            }
155        }
156        (vars.len(), gate_count)
157    }
158}
159
160impl std::ops::BitAnd for Gate {
161    type Output = Gate;
162
163    fn bitand(self, other: Gate) -> Gate {
164        Gate(Rc::new(Operation::Conjunction(self, other)))
165    }
166}
167
168impl std::ops::BitAnd for &Gate {
169    type Output = Gate;
170
171    fn bitand(self, other: &Gate) -> Gate {
172        Gate::bitand(self.clone(), other.clone())
173    }
174}
175
176impl std::ops::BitOr for Gate {
177    type Output = Gate;
178
179    fn bitor(self, other: Gate) -> Gate {
180        Gate(Rc::new(Operation::Disjunction(self, other)))
181    }
182}
183
184impl std::ops::BitOr for &Gate {
185    type Output = Gate;
186
187    fn bitor(self, other: &Gate) -> Gate {
188        Gate::bitor(self.clone(), other.clone())
189    }
190}
191
192impl std::ops::BitXor for Gate {
193    type Output = Gate;
194
195    fn bitxor(self, other: Gate) -> Gate {
196        Gate(Rc::new(Operation::Xor(self, other)))
197    }
198}
199
200impl std::ops::BitXor for &Gate {
201    type Output = Gate;
202
203    fn bitxor(self, other: &Gate) -> Gate {
204        Gate::bitxor(self.clone(), other.clone())
205    }
206}
207
208impl std::ops::Not for Gate {
209    type Output = Gate;
210
211    fn not(self) -> Gate {
212        Gate(Rc::new(Operation::Negation(self)))
213    }
214}
215
216impl std::ops::Not for &Gate {
217    type Output = Gate;
218
219    fn not(self) -> Gate {
220        Gate::not(self.clone())
221    }
222}
223
224impl<'a> IntoIterator for &'a Gate {
225    type Item = &'a Gate;
226    type IntoIter = GraphIterator<'a>;
227
228    /// Creates an iterator over the graph that returns each gate exactly once.
229    fn into_iter(self) -> Self::IntoIter {
230        GraphIterator::new(std::iter::once(self))
231    }
232}
233
234pub struct GraphIterator<'a> {
235    visited: HashSet<usize>,
236    stack: Vec<&'a Gate>,
237}
238
239impl<'a> GraphIterator<'a> {
240    pub(crate) fn new(gates: impl IntoIterator<Item = &'a Gate>) -> Self {
241        Self {
242            visited: HashSet::new(),
243            stack: gates.into_iter().collect(),
244        }
245    }
246
247    fn add_predecessors(&mut self, gate: &'a Gate) {
248        match gate.operation() {
249            Operation::Variable(_) | Operation::Constant(_) => {}
250            Operation::Conjunction(left, right)
251            | Operation::Disjunction(left, right)
252            | Operation::Xor(left, right) => {
253                self.stack.push(right);
254                self.stack.push(left);
255            }
256            Operation::Negation(inner) => {
257                self.stack.push(inner);
258            }
259        }
260    }
261}
262
263impl<'a> Iterator for GraphIterator<'a> {
264    type Item = &'a Gate;
265
266    fn next(&mut self) -> Option<Self::Item> {
267        while let Some(gate) = self.stack.pop() {
268            if self.visited.insert(gate.id()) {
269                self.add_predecessors(gate);
270                return Some(gate);
271            }
272        }
273        None
274    }
275}
276
277pub(crate) struct PostVisitIterator<'a> {
278    visited: HashSet<usize>,
279    /// Stack of gates to visit. If the flag is true, we will have
280    /// visited all its predecessors once we reach the gate.
281    stack: Vec<(&'a Gate, bool)>,
282}
283
284impl<'a> PostVisitIterator<'a> {
285    pub(crate) fn new(gates: impl IntoIterator<Item = &'a Gate>) -> Self {
286        Self {
287            visited: HashSet::new(),
288            stack: gates.into_iter().map(|n| (n, false)).collect(),
289        }
290    }
291}
292
293impl<'a> Iterator for PostVisitIterator<'a> {
294    type Item = &'a Gate;
295
296    fn next(&mut self) -> Option<Self::Item> {
297        while let Some((gate, visited_predecessors)) = self.stack.pop() {
298            if visited_predecessors {
299                return Some(gate);
300            } else if self.visited.insert(gate.id()) {
301                self.stack.push((gate, true));
302                match gate.operation() {
303                    Operation::Variable(_) | Operation::Constant(_) => {}
304                    Operation::Conjunction(left, right)
305                    | Operation::Disjunction(left, right)
306                    | Operation::Xor(left, right) => {
307                        self.stack.push((right, false));
308                        self.stack.push((left, false));
309                    }
310                    Operation::Negation(inner) => {
311                        self.stack.push((inner, false));
312                    }
313                }
314            }
315        }
316        None
317    }
318}