| Crates.io | mutaig |
| lib.rs | mutaig |
| version | 0.4.2 |
| created_at | 2025-06-05 19:22:22.447828+00 |
| updated_at | 2025-08-12 02:17:17.371179+00 |
| description | Mutable AIGs designed for equivalence checking. |
| homepage | |
| repository | https://github.com/remigerme/mutaig |
| max_upload_size | |
| id | 1702210 |
| size | 188,639 |
A library for manipulating And-Inverter Graphs (AIGs), designed for combinational equivalence checking.
As for any other Rust library:
cargo add mutaig
Let's create the following AIG (nodes are representing AND gates, and black dots on the edges are inverters ie NOT gates):
flowchart TD
O@{shape: text, label: output} --o A((" "))
A --- B((" "))
A --o C((" "))
B --o D((false))
B --- E((i1))
C --o E
C --- F((i2))
It represents the boolean function $f(i1, i2) = \neg[(\text{true} \land i1) \land \neg(\neg i1 \land i2)]$.
It can be created manually as follows:
use mutaig::{Aig, AigEdge, AigNode};
let mut aig = Aig::new();
let node_false = aig.add_node(AigNode::False).unwrap();
let i1 = aig.add_node(AigNode::Input(1)).unwrap();
let i2 = aig.add_node(AigNode::Input(2)).unwrap();
let a3 = aig
.add_node(AigNode::and(
3,
AigEdge::new(node_false.clone(), true),
AigEdge::new(i1.clone(), false),
))
.unwrap();
let a4 = aig
.add_node(AigNode::And(
4,
AigEdge::new(i1.clone(), true),
AigEdge::new(i2.clone(), false),
))
.unwrap();
let a5 = aig
.add_node(AigNode::And(
5,
AigEdge::new(a3.clone(), false),
AigEdge::new(a4.clone(), true),
))
.unwrap();
aig.add_output(5, true).unwrap();
aig.update();
In practice, you will probably never have to build an AIG by hand. Instead, you can use the Aig::from_file function:
use mutaig::Aig;
let mut aig = Aig::from_file("myfile.aag").unwrap();
with myfile.aag in the AIGER format (here ASCII) corresponding to the AIG above:
aag 5 2 0 1 2
2
4
7
6 8 11
8 1 2
10 3 4
Note that the Aig::from_file function also supports .aig files, which is the binary AIGER format.
Let's consider the boolean function above once again, and let's simplify it by applying De Morgan's law:
$$ \begin{align*} f(i1, i2) &= \neg[(\text{true} \land i1) \land \neg(\neg i1 \land i2)] \ &= \neg [i1 \land (i1 \lor \neg i2)] \ &= \neg i1 \lor \neg (i1 \lor \neg i2) \ &= \neg i1 \lor (\neg i1 \land i2) \ &= \neg i1 \end{align*} $$
It can be represented by the simpler AIG:
flowchart TD
O@{shape: text, label: output} --o A((" "))
A((i1))
B((i2))
C((false))
This will encoded as follows in optimized.aag:
aag 1 1 0 1 0
2
3
To prove the two AIGs are indeed equivalent, let's create the following miter (reference is using straight lines, optimized uses dotted lines):
flowchart TD
ONE@{shape: text, label: output} --- O
O((XOR)) --o|output 1| A((" "))
A --- B((" "))
A --o C((" "))
B --o D((false))
B --- E((i1))
C --o E
C --- F((i2))
O -..-o|output 2| E
If the output of the miter is one, the AIGs are different (their outputs differ for the same set of inputs). To prove they are equivalent, let's prove the output of the miter can never be true.
To do that, let's assume it is true. Then generate some SAT clauses using Tseytin transformation, and give the obtained CNF to a SAT solver:
In terms of Rust code:
use std::collections::HashMap;
use mutaig::{Aig, miter::Miter};
let reference = Aig::from_file("myfile.aag").unwrap();
let optimized = Aig::from_file("optimized.aag").unwrap();
// In general, an AIG might have multiple outputs.
// So we need to tell the miter which outputs of the reference
// correspond to which outputs of the optimized version.
let output_a = reference.get_outputs()[0];
let id_a = output_a.get_node().borrow().get_id();
let output_b = optimized.get_outputs()[0];
let id_b = output_b.get_node().borrow().get_id();
let outputs_mapping = HashMap::new();
outputs_mapping.insert((id_a, output_a.get_complement()), (id_b, output_b.get_complement()));
let mut miter = Miter::new(&reference, &optimized, outputs_mapping).unwrap();
let cnf = miter.extract_cnf().unwrap();
// Here you can give the CNF to any SAT solver
// You can use the dimacs format:
let dimacs = cnf.to_dimacs();
// If the CNF is UNSAT, AIGs have been proved equivalent using Tseytin transformation and a SAT solver!
When AIGs are too large, the extracted CNF is too large for SAT solvers. Note that modern SAT solvers are incredibly efficient (for example kissat was able to prove a CNF with 50 million of clauses UNSAT in less than 10s). But still, this doesn't scale for very large circuits.
You should look at three very helpful functions:
mergeable: to check if nodes are obviously functionally equivalentextract_cnf_node: to extract a miter+CNF between two internal nodesmerge: to merge two nodes that you have proven equivalent (using mergeable or extract_cnf_node)You can also export AIGs and miters to the Graphviz dot format using their to_dot methods.
use mutaig::Aig;
use mutaig::miter::Miter;
use mutaig::dot::GraphvizStyle;
let aig = Aig::from_file("assets/circuits/half-adder.aag").unwrap();
println!("{}", aig.to_dot(GraphvizStyle::default()));
// Creating a miter between two copies of the circuit
let outputs_map = aig
.get_outputs()
.iter()
.map(|edge| {
let id = edge.get_node().borrow().get_id();
let b = edge.get_complement();
((id, b), (id, b))
})
.collect();
let miter = Miter::new(&aig, &aig, outputs_map).unwrap();
println!("{}", miter.to_dot(GraphvizStyle::default()));
You can then render the graphs using the DOT engine.
The graphs produced by the code above, first the half-adder, then the half-adder mitered with itself:
The docs for the latest release of MUTAIG are available on docs.rs.
Alternatively, if you want the latest version of the docs, you can compile them from source with:
cargo doc
Inspired by aig-rs.