boolean_circuit/
node.rs

1use std::{collections::HashSet, rc::Rc};
2
3use super::literal::Literal;
4
5/// A node or gate in a boolean circuit.
6///
7/// The inputs of the circuit are variables identified by their name.
8/// Other types of nodes are constant nodes or operations that have
9/// predecessors.
10///
11/// The clone operation only performs a shallow clone and thus
12/// allows sharing of nodes.
13#[derive(Clone, Debug)]
14pub struct Node(Rc<Operation>);
15
16/// The inner operation of a {Node} 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 node.
24    Negation(Node),
25    /// The boolean conjunction of two nodes.
26    Conjunction(Node, Node),
27    /// The boolean disjunction of two nodes.
28    Disjunction(Node, Node),
29    /// The boolean exclusive or of two nodes.
30    Xor(Node, Node),
31}
32
33impl From<&str> for Node {
34    /// Constructs a new node representing an input variable.
35    ///
36    /// Two variables with the same name are considered equal, even
37    /// if they are different node instances.
38    fn from(name: &str) -> Node {
39        name.to_string().into()
40    }
41}
42
43impl From<String> for Node {
44    /// Constructs a new node representing an input variable.
45    ///
46    /// Two variables with the same name are considered equal, even
47    /// if they are different node instances.
48    fn from(name: String) -> Node {
49        Node(Rc::new(Operation::Variable(Rc::new(name))))
50    }
51}
52
53impl From<bool> for Node {
54    /// Creates a constant node.
55    fn from(value: bool) -> Node {
56        Node(Rc::new(Operation::Constant(value)))
57    }
58}
59
60impl From<&Literal> for Node {
61    /// Converts a literal to either a variable or a node that is the negation
62    /// of a variable.
63    fn from(literal: &Literal) -> Node {
64        let v = literal.var().into();
65        if literal.is_negated() {
66            !&v
67        } else {
68            v
69        }
70    }
71}
72
73impl Node {
74    /// Returns an identifier for the node 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 node.
81    pub fn operation(&self) -> &Operation {
82        &self.0
83    }
84
85    /// Creates an iterator over the graph (the node itself and all its predecessors)
86    /// that returns each node exactly once.
87    pub fn iter(&self) -> impl Iterator<Item = &Node> {
88        self.into_iter()
89    }
90
91    /// Creates an iterator over the graph (the node itself and all its predecessors)
92    /// with post-visit order, visiting each node exactly once.
93    /// This means that the predecessors of each node are always visited before the node itself.
94    pub fn post_visit_iter(&self) -> impl Iterator<Item = &Node> {
95        PostVisitIterator::new(self)
96    }
97
98    /// Turns the node and its predecessors into a string representation, repeating shared nodes.
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(node) => format!("!{}", node.to_string_as_tree()),
123        }
124    }
125
126    /// Returns the value of the node if it is a constant input node.
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 gates (nodes 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 Node {
161    type Output = Node;
162
163    fn bitand(self, other: Node) -> Node {
164        Node(Rc::new(Operation::Conjunction(self, other)))
165    }
166}
167
168impl std::ops::BitAnd for &Node {
169    type Output = Node;
170
171    fn bitand(self, other: &Node) -> Node {
172        Node::bitand(self.clone(), other.clone())
173    }
174}
175
176impl std::ops::BitOr for Node {
177    type Output = Node;
178
179    fn bitor(self, other: Node) -> Node {
180        Node(Rc::new(Operation::Disjunction(self, other)))
181    }
182}
183
184impl std::ops::BitOr for &Node {
185    type Output = Node;
186
187    fn bitor(self, other: &Node) -> Node {
188        Node::bitor(self.clone(), other.clone())
189    }
190}
191
192impl std::ops::BitXor for Node {
193    type Output = Node;
194
195    fn bitxor(self, other: Node) -> Node {
196        Node(Rc::new(Operation::Xor(self, other)))
197    }
198}
199
200impl std::ops::BitXor for &Node {
201    type Output = Node;
202
203    fn bitxor(self, other: &Node) -> Node {
204        Node::bitxor(self.clone(), other.clone())
205    }
206}
207
208impl std::ops::Not for Node {
209    type Output = Node;
210
211    fn not(self) -> Node {
212        Node(Rc::new(Operation::Negation(self)))
213    }
214}
215
216impl std::ops::Not for &Node {
217    type Output = Node;
218
219    fn not(self) -> Node {
220        Node::not(self.clone())
221    }
222}
223
224impl<'a> IntoIterator for &'a Node {
225    type Item = &'a Node;
226    type IntoIter = GraphIterator<'a>;
227
228    /// Creates an iterator over the graph that returns each node exactly once.
229    fn into_iter(self) -> Self::IntoIter {
230        GraphIterator::new(self)
231    }
232}
233
234pub struct GraphIterator<'a> {
235    visited: HashSet<usize>,
236    stack: Vec<&'a Node>,
237}
238
239impl<'a> GraphIterator<'a> {
240    fn new(node: &'a Node) -> Self {
241        Self {
242            visited: HashSet::new(),
243            stack: vec![node],
244        }
245    }
246
247    fn add_predecessors(&mut self, node: &'a Node) {
248        match node.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 Node;
265
266    fn next(&mut self) -> Option<Self::Item> {
267        while let Some(node) = self.stack.pop() {
268            if self.visited.insert(node.id()) {
269                self.add_predecessors(node);
270                return Some(node);
271            }
272        }
273        None
274    }
275}
276
277struct PostVisitIterator<'a> {
278    visited: HashSet<usize>,
279    /// Stack of nodes to visit. If the flag is true, we will have
280    /// visited all its predecessors once we reach the node.
281    stack: Vec<(&'a Node, bool)>,
282}
283
284impl<'a> PostVisitIterator<'a> {
285    fn new(node: &'a Node) -> Self {
286        Self {
287            visited: HashSet::new(),
288            stack: vec![(node, false)],
289        }
290    }
291}
292
293impl<'a> Iterator for PostVisitIterator<'a> {
294    type Item = &'a Node;
295
296    fn next(&mut self) -> Option<Self::Item> {
297        while let Some((node, visited_predecessors)) = self.stack.pop() {
298            if visited_predecessors {
299                return Some(node);
300            } else if self.visited.insert(node.id()) {
301                self.stack.push((node, true));
302                match node.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}