def bitwise_negate(wordlen, input='x', output='not x'): imap, omap = BundleMap({input: wordlen}), BundleMap({output: wordlen}) return aigbv.AIGBV( imap=imap, omap=omap, aig=aiger.bit_flipper(inputs=imap[input], outputs=omap[output]), )
def kmodels(wordlen: int, k: int, input=None, output=None): """Return a circuit taking a wordlen bitvector where only k valuations return True. Uses encoding from [1]. Note that this is equivalent to (~x < k). - TODO: Add automated simplification so that the circuits are equiv. [1]: Chakraborty, Supratik, et al. "From Weighted to Unweighted Model Counting." IJCAI. 2015. """ assert 0 <= k < 2**wordlen if output is None: output = _fresh() if input is None: input = _fresh() imap, omap = BundleMap({input: wordlen}), BundleMap({output: 1}) atoms = map(aiger.atom, imap[input]) active = False expr = aiger.atom(False) for atom, bit in zip(atoms, encode_int(wordlen, k, signed=False)): active |= bit if not active: # Skip until first 1. continue expr = (expr | atom) if bit else (expr & atom) aig = expr.aig['o', {expr.output: omap[output][0]}] aig |= aiger.sink(imap[input]) return aigbv.AIGBV(imap=imap, omap=omap, aig=aig)
def even_popcount_gate(wordlen, input, output): imap, omap = BundleMap({input: wordlen}), BundleMap({output: 1}) return aigbv.AIGBV( imap=imap, omap=omap, aig=aiger.parity_gate(imap[input], omap[output][0]), )
def is_nonzero_gate(wordlen, input='x', output='is_nonzero'): imap, omap = BundleMap({input: wordlen}), BundleMap({output: 1}) return aigbv.AIGBV( imap=imap, omap=omap, aig=aiger.or_gate(imap[input], omap[output][0]), )
def bitwise_binop(binop, wordlen, left='x', right='y', output='x&y'): imap = BundleMap({left: wordlen, right: wordlen}) omap = BundleMap({output: wordlen}) names = zip(imap[left], imap[right], omap[output]) return aigbv.AIGBV( imap=imap, omap=omap, aig=reduce(op.or_, (binop([lft, rht], o) for lft, rht, o in names)), )
def identity_gate(wordlen, input='x', output=None): if output is None: output = input imap, omap = BundleMap({input: wordlen}), BundleMap({output: wordlen}) return aigbv.AIGBV( imap=imap, omap=omap, aig=aiger.identity(inputs=imap[input], outputs=omap[output]), )
def repeat(wordlen, input, output=None): if output is None: output = input imap, omap = BundleMap({input: 1}), BundleMap({output: wordlen}) return aigbv.AIGBV( imap=imap, omap=omap, aig=aiger.tee({imap[input][0]: list(omap[output])}), )
def index_gate(wordlen, idx, input, output=None): assert 0 <= idx < wordlen if output is None: output = input imap, omap = BundleMap({input: wordlen}), BundleMap({output: 1}) inputs, outputs = imap[input], (imap[input][idx], ) aig = aiger.sink(set(inputs) - set(outputs)) | aiger.identity(outputs) relabels = {outputs[0]: omap[output][0]} return aigbv.AIGBV(imap=imap, omap=omap, aig=aig['o', relabels])
def tee(wordlen, iomap): imap = BundleMap({i: wordlen for i in iomap}) omap = BundleMap({o: wordlen for o in fn.cat(iomap.values())}) blasted = defaultdict(list) for i, outs in iomap.items(): for o in outs: for k, v in zip(imap[i], omap[o]): blasted[k].append(v) return aigbv.AIGBV(imap=imap, omap=omap, aig=aiger.tee(blasted))
def unsigned_lt_gate(wordlen, left, right, output): omap = BundleMap({output: 1}) imap = BundleMap({left: wordlen, right: wordlen}) lefts = map(aiger.atom, imap[left]) rights = map(aiger.atom, imap[right]) def test_bit(expr, lr): l, r = lr expr &= ~(l ^ r) # l == r. expr |= ~l & r # l < r. return expr expr = reduce(test_bit, zip(lefts, rights), aiger.atom(False)) aig = expr.aig['o', {expr.output: omap[output][0]}] return aigbv.AIGBV(imap=imap, omap=omap, aig=aig)
def source(wordlen, value, name='x', signed=True): if isinstance(value, int): value = encode_int(wordlen, value, signed) omap = BundleMap({name: wordlen}) aig = aiger.source({name: bit for name, bit in zip(omap[name], value)}) return aigbv.AIGBV(aig=aig, omap=omap)
def _feedback(self, inputs, outputs, initials=None, latches=None, keep_outputs=False): # TODO: remove in next version bump and put into wire. if latches is None: latches = inputs def blast(bmap, vals): return fn.lmapcat(bmap.get, vals) lmap = BundleMap( {l: self.imap[i].size for i, l in zip(inputs, latches)} ) if initials is not None: l2init = dict(self.aig.latch2init) l2init.update( {l: v for l, v in zip(latches, initials) if v is not None} ) initials = fn.lcat(l2init[l] for l in latches) aig = rebundle_aig(self.aig.feedback( inputs=blast(self.imap, inputs), outputs=blast(self.omap, outputs), latches=blast(lmap, latches), keep_outputs=keep_outputs, initials=initials, )) return aig
def split_gate(input, left_wordlen, left, right_wordlen, right): omap = BundleMap({left: left_wordlen, right: right_wordlen}) circ = identity_gate(left_wordlen + right_wordlen, input, input) relabels = fn.merge( dict(zip(circ.omap[input][:left_wordlen], omap[left])), dict(zip(circ.omap[input][left_wordlen:], omap[right])), ) return attr.evolve(circ, omap=omap, aig=circ.aig['o', relabels])
def combine_gate(left_wordlen, left, right_wordlen, right, output): circ = identity_gate(left_wordlen, left, left) \ | identity_gate(right_wordlen, right, right) omap1 = circ.omap relabels = { k: f"{left}[{i + left_wordlen}]" for i, k in enumerate(omap1[right]) } omap2 = BundleMap({left: left_wordlen + right_wordlen}) circ = attr.evolve(circ, omap=omap2, aig=circ.aig['o', relabels]) return circ if left == output else circ['o', {left: output}]
def add_gate(wordlen, left='x', right='y', output='x+y', has_carry=False): carry_name = f'{output}_carry' assert left != carry_name and right != carry_name adder_aig = aiger.source({carry_name: False}) imap = BundleMap({left: wordlen, right: wordlen}) omap = BundleMap({ output: wordlen, has_carry: 1 } if has_carry else {output: wordlen}) for lname, rname, oname in zip(imap[left], imap[right], omap[output]): adder_aig >>= _full_adder(x=lname, y=rname, carry_in=carry_name, result=oname, carry_out=carry_name) if not has_carry: adder_aig >>= aiger.sink([output + '_carry']) return aigbv.AIGBV(imap=imap, omap=omap, aig=adder_aig)
def rebundle_names(names): grouped_names = fn.group_values(map(unpack_name, names)) return BundleMap(pmap(fn.walk_values(to_size, grouped_names)))
def sink(wordlen, inputs): imap = BundleMap({i: wordlen for i in inputs}) return aigbv.AIGBV(imap=imap, aig=aiger.sink(fn.lmapcat(imap.get, inputs)))
def _diagonal_map(keys): return BundleMap({k: 1 for k in keys})
class AIGBV: aig: aiger.AIG imap: BundleMap = BundleMap() omap: BundleMap = BundleMap() lmap: BundleMap = BundleMap() simulate = aiger.AIG.simulate simulator = aiger.AIG.simulator @property def aigbv(self): return self def write(self, path): self.aig.write(path) @property def inputs(self): return set(self.imap.keys()) @property def outputs(self): return set(self.omap.keys()) @property def latches(self): return set(self.lmap.keys()) @property def latch2init(self): return self.lmap.unblast(dict(self.aig.latch2init)) def __call__(self, inputs, latches=None): out2val, latch2val = self.aig( inputs=self.imap.blast(inputs), latches=None if latches is None else self.lmap.blast(latches) ) return self.omap.unblast(out2val), self.lmap.unblast(latch2val) def __lshift__(self, other): return other >> self def __rshift__(self, other): interface = self.outputs & other.inputs assert not self.latches & other.latches assert not (self.outputs - interface) & other.outputs return AIGBV( aig=self.aig >> other.aig, imap=self.imap + other.imap.omit(interface), omap=other.omap + self.omap.omit(interface), lmap=self.lmap + other.lmap, ) def __or__(self, other): assert not self.outputs & other.outputs assert not self.latches & other.latches shared_inputs = self.inputs & other.inputs circ = self if shared_inputs: relabels1 = {n: common._fresh() for n in shared_inputs} relabels2 = {n: common._fresh() for n in shared_inputs} circ, other = circ['i', relabels1], other['i', relabels2] circ = AIGBV( aig=circ.aig | other.aig, imap=circ.imap + other.imap, omap=circ.omap + other.omap, lmap=circ.lmap + other.lmap) if shared_inputs: for orig in shared_inputs: new1, new2 = relabels1[orig], relabels2[orig] circ <<= common.tee(self.imap[orig].size, {orig: [new1, new2]}) return circ def __getitem__(self, others): kind, relabels = others if kind not in {'i', 'o', 'l'}: raise NotImplementedError attr_name = {'i': 'imap', 'o': 'omap', 'l': 'lmap'}.get(kind) bmap1 = getattr(self, attr_name) assert not set(relabels.values()) & set(bmap1.keys()) bmap2 = bmap1.relabel(relabels) circ = attr.evolve(self, **{attr_name: bmap2}) # Update AIG to match new interface. relabels_aig = fn.merge(*( dict(zip(bmap1[k], bmap2[v])) for k, v in relabels.items() if k in bmap1 )) return attr.evolve(circ, aig=circ.aig[kind, relabels_aig]) def loopback(self, *wirings): def wire(circ, wiring): return circ._wire(**wiring) return reduce(wire, wirings, self) def _wire(self, input, output, latch=None, init=None, keep_output=True): if latch is None: latch = input inits = [init] if init is not None else None return self._feedback( [input], [output], inits, [latch], keep_outputs=keep_output ) def feedback( self, inputs, outputs, initials=None, latches=None, keep_outputs=False ): import warnings warnings.warn("deprecated", DeprecationWarning) return self._feedback( inputs, outputs, initials=initials, latches=latches, keep_outputs=keep_outputs ) def _feedback(self, inputs, outputs, initials=None, latches=None, keep_outputs=False): # TODO: remove in next version bump and put into wire. if latches is None: latches = inputs def blast(bmap, vals): return fn.lmapcat(bmap.get, vals) lmap = BundleMap( {l: self.imap[i].size for i, l in zip(inputs, latches)} ) if initials is not None: l2init = dict(self.aig.latch2init) l2init.update( {l: v for l, v in zip(latches, initials) if v is not None} ) initials = fn.lcat(l2init[l] for l in latches) aig = rebundle_aig(self.aig.feedback( inputs=blast(self.imap, inputs), outputs=blast(self.omap, outputs), latches=blast(lmap, latches), keep_outputs=keep_outputs, initials=initials, )) return aig def unroll(self, horizon, *, init=True, omit_latches=True, only_last_outputs=False): aig = self.aig.unroll( horizon, init=init, omit_latches=omit_latches, only_last_outputs=only_last_outputs ) for key in ['inputs', 'outputs', 'latches']: relabels = {k: shuffle_id_time(k) for k in getattr(aig, key)} aig = aig[key[0], relabels] return rebundle_aig(aig) def cutlatches(self, latches=None, renamer=None): if renamer is None: @fn.memoize def renamer(_): return common._fresh() def renamer_bv(name): root, idx = unpack_name(name) return f"{renamer(root)}[{idx}]" aig, lmap = self.aig.cutlatches(latches, renamer=renamer_bv) circ = rebundle_aig(aig) lmap = self.lmap.unblast(lmap) def unblast_vals(vals): name, _ = vals[0] name = unpack_name(name)[0] bdl = Bundle(size=len(vals), name=name) return (name, bdl.unblast(dict(vals))) lmap = fn.walk_values(unblast_vals, lmap) return circ, lmap