def test_to_aig(): x = aiger.atom('x') c1 = aiger.to_aig(x) c2 = aiger.to_aig(c1) c3 = aiger.to_aig(str(c2)) with tempfile.TemporaryDirectory() as d: path = pathlib.Path(d) / "foo.aag" c3.write(path) c4 = aiger.to_aig(path) for c in [c1, c2, c3, c4]: assert isinstance(c1, aiger.AIG) assert isinstance(c2, aiger.AIG) assert isinstance(c3, aiger.AIG) assert isinstance(c4, aiger.AIG)
def aig2cnf(circ, *, outputs=None, fresh=None, force_true=True) -> CNF: """Convert an AIGER circuit to CNF via the Tseitin transformation.""" if fresh is None: max_var = 0 def fresh(_): nonlocal max_var max_var += 1 return max_var circ = aiger.to_aig(circ, allow_lazy=True) assert len(circ.latches) == 0 # Interpret circuit over Lit Boolean Algebra. clauses, gate2lit = [], SymbolTable(fresh) def lift(obj) -> LitWrapper: assert isinstance(obj, (Input, bool)) if isinstance(obj, bool): assert not obj obj = ConstFalse() gate2lit[obj] # defaultdict. force add literal for obj. return LitWrapper(gate=obj, gate2lit=gate2lit, clauses=clauses) inputs = {i: aiger.aig.Input(i) for i in circ.inputs} out2lit, _ = circ(inputs=inputs, lift=lift) out2lit = {k: v.lit for k, v in out2lit.items()} # Remove Lit wrapper. in2lit = bidict({i: gate2lit[aiger.aig.Input(i)] for i in circ.inputs}) # Force True/False variable to be true/false. if ConstFalse() in gate2lit: clauses.append((-gate2lit[ConstFalse()], )) # Force outputs to appear as positive variables. for name, gate in circ.node_map.items(): if not isinstance(gate, aiger.aig.Inverter): continue oldv = out2lit[name] = fresh(gate) newv = gate2lit[gate] clauses.append((-newv, oldv)) clauses.append((newv, -oldv)) if force_true: if outputs is None: outputs = circ.outputs for name in outputs: clauses.append((out2lit[name], )) return CNF(clauses, in2lit, out2lit, circ.comments)
def to_bdd(circ_or_expr, output=None, manager=None, renamer=None, levels=None): if renamer is None: _count = 0 def renamer(*_): nonlocal _count _count += 1 return f"x{_count}" if not isinstance(circ_or_expr, aiger.BoolExpr): circ = aiger.to_aig(circ_or_expr, allow_lazy=True) assert len(circ.latches) == 0 if output is None: assert len(circ.outputs) == 1 output = fn.first(circ.outputs) expr = aiger.BoolExpr(circ) else: expr = circ_or_expr manager = BDD() if manager is None else manager input_refs_to_var = { ref: renamer(i, ref) for i, ref in enumerate(expr.inputs) } manager.declare(*input_refs_to_var.values()) if levels is not None: assert set(manager.vars.keys()) <= set(levels.keys()) levels = fn.project(levels, manager.vars.keys()) levels = fn.walk_keys(input_refs_to_var.get, levels) manager.reorder(levels) manager.configure(reordering=False) def lift(obj): if isinstance(obj, bool): return manager.true if obj else manager.false return obj inputs = {i: manager.var(input_refs_to_var[i]) for i in expr.inputs} out = expr(inputs, lift=lift) return out, out.bdd, bidict(input_refs_to_var)
def simplify(circ, verbose=False, abc_cmd='abc', aigtoaig_cmd='aigtoaig'): circ = aiger.to_aig(circ) # avoids confusion and guarantees deletion on exit with tempfile.TemporaryDirectory() as tmpdirname: tmpdir = Path(tmpdirname) aag_path = tmpdir / 'input.aag' aig_path = tmpdir / 'input.aig' circ.write(aag_path) call([aigtoaig_cmd, aag_path, aig_path]) command = [ abc_cmd, '-c', SIMPLIFY_TEMPLATE.format(aig_path) ] call(command) if verbose else call(command, stdout=PIPE) call([aigtoaig_cmd, aig_path, aag_path]) return aiger.parser.load(aag_path)
def aig2cnf(circ, *, outputs=None, fresh=None, force_true=True): """Convert an AIGER circuit to CNF via the Tseitin transformation.""" if fresh is None: max_var = 0 def fresh(_): nonlocal max_var max_var += 1 return max_var circ = aiger.to_aig(circ, allow_lazy=True) assert len(circ.latches) == 0 # Define Boolean Algebra over clauses. clauses, gate2lit = [], SymbolTable(fresh) @attr.s(auto_attribs=True, frozen=True) class LitWrapper: lit: Hashable gate: Node @fn.memoize def __and__(self, other): gate = AndGate(self.gate, other.gate) wrapped = LitWrapper(gate2lit[gate], gate) out, left, right = wrapped.lit, self.lit, other.lit clauses.append((-left, -right, out)) # (left /\ right) -> out clauses.append((-out, left)) # out -> left clauses.append((-out, right)) # out -> right return wrapped def __invert__(self): gate = Inverter(self.gate) gate2lit[gate] = -self.lit return LitWrapper(gate2lit[gate], gate) def lift(obj) -> LitWrapper: assert isinstance(obj, (Input, bool)) if isinstance(obj, bool): assert not obj obj = ConstFalse() return LitWrapper(gate2lit[obj], obj) # Interpret circ over Lit Boolean Algebra. inputs = {i: aiger.aig.Input(i) for i in circ.inputs} out2lit, _ = circ(inputs=inputs, lift=lift) out2lit = {k: v.lit for k, v in out2lit.items()} # Remove Lit wrapper. in2lit = bidict({i: gate2lit[aiger.aig.Input(i)] for i in circ.inputs}) # Force True/False variable to be true/false. if ConstFalse() in gate2lit: clauses.append((-gate2lit[ConstFalse()],)) # Force outputs to appear as positive variables. for name, gate in circ.node_map.items(): if not isinstance(gate, aiger.aig.Inverter): continue oldv = out2lit[name] = fresh(gate) newv = gate2lit[gate] clauses.append((-newv, oldv)) clauses.append((newv, -oldv)) if force_true: if outputs is None: outputs = circ.outputs for name in outputs: clauses.append((out2lit[name],)) return CNF(clauses, in2lit, out2lit, circ.comments)
def to_js(circ, suffix="aig", with_header=True) -> str: """ Outputs string with Javascript for stepping through AIG represented by circ. - suffix: controls suffix of generated step and spec code. - with_header: Includes prelude necessary for running generated code. Only needed once. """ circ = to_aig(circ) count = 0 def fresh(): nonlocal count count += 1 return f'x{count}' with io.StringIO() as buff: @attr.s(frozen=True, auto_attribs=True) class Writer: var: str gate: Node @fn.memoize def __and__(self, other): writer = lift(self.gate & other.gate) left, right = self.var, other.var buff.write(f' var {writer.var} = {left} && {right};\n') return writer @fn.memoize def __invert__(self): writer = lift(~self.gate) buff.write(f' var {writer.var} = !{self.var};\n') return writer @fn.memoize def lift(gate) -> Writer: if isinstance(gate, Input): var = f'inputs["{gate.name}"]' elif isinstance(gate, LatchIn): var = f'latches["{gate.name}"]' elif isinstance(gate, bool): var = 'false' else: var = fresh() return Writer(var=var, gate=gate) # Add latch init code: for name, init in circ.latch2init.items(): init = "true" if init else "false" buff.write(f' if (!("{name}" in latches)) {{ ') buff.write(f'latches["{name}"] = {init}') buff.write('}\n') buff.write('\n') # Inline gates as straight line program. inputs = {i: Input(i) for i in circ.inputs} latches = {i: LatchIn(i) for i in circ.latches} omap, lmap = circ(inputs, latches, lift=lift) # Collect outputs. for name, writer in omap.items(): buff.write(f'\n outputs["{name}"] = {writer.var};') # Collect outputs. for name, writer in lmap.items(): buff.write(f'\n latch_outs["{name}"] = {writer.var};') hdr = HEADER if with_header else "" return hdr + TEMPLATE.format(suffix, buff.getvalue())