def _print_cyclic_core(x, y, xcore, ycore, essential, t0, prm, fol): """Print results of cyclic core computation. Assert support and covering properties. """ if log.getEffectiveLevel() > logging.INFO: return # assert if essential != fol.false: assert support_issubset(essential, prm.p_vars, fol) if xcore != fol.false: assert support_issubset(xcore, prm.p_vars, fol) if ycore != fol.false: assert support_issubset(ycore, prm.p_vars, fol) # print m = fol.count(x) n = fol.count(y) log.info(('(x={m}, y={n}) implicants of ' 'covering problem').format(m=humanize.intcomma(m), n=humanize.intcomma(n))) m = fol.count(xcore) n = fol.count(ycore) log.info(('(x={m}, y={n}) implicants after ' 'removing essential elements').format(m=humanize.intcomma(m), n=humanize.intcomma(n))) n = fol.count(essential) log.info('{n} primes are essential'.format(n=humanize.intcomma(n))) t1 = time.time() dt = t1 - t0 log.info('cyclic core took {dt}'.format(dt=humanize.naturaldelta(dt)))
def _maxima(u, bab, fol): """Return maximal elements of `u` wrt `bab.p_leq_q`. @param u: function of `bab.p_vars` """ assert support_issubset(u, bab.p_vars, fol) v = fol.let(bab.p_to_q, u) # r = v & bab.p_leq_q r = ~ r | bab.p_eq_q r = fol.forall(bab.q_vars, r) r &= u ''' q = ', '.join(bab.q_vars) s = ( '{u} /\ ' '\A {q}: ({v} /\ {p_leq_q}) => ({p_eq_q})').format( u=u, v=v, p_leq_q=bab.p_leq_q, p_eq_q=bab.p_eq_q, q=q) r = fol.add_expr(s) ''' assert support_issubset(r, bab.p_vars, fol) return r
def _cyclic_core_fixpoint(x, y, bab, fol): """Return cyclic core and essential elements.""" log.debug('\n\n---- cyclic core fixpoint ----') assert x in fol.bdd, x assert y in fol.bdd, y assert support_issubset(x, bab.p_vars, fol) assert support_issubset(y, bab.p_vars, fol) # compute essential = fol.false xold, yold = None, None i = 0 while x != xold or y != yold: log.debug('starting iteration {i}'.format(i=i)) t0 = time.time() xold, yold = x, y x = _max_transpose(x, y, bab, fol, signatures=True) e = x & y x = x & ~e y = y & ~e essential |= e y = _max_transpose(x, y, bab, fol) t1 = time.time() dt = t1 - t0 log.debug('iteration {i} took {dt:1.2f} sec'.format(i=i, dt=dt)) i += 1 _assert_fixpoint(x, y, xold, yold, essential, bab.p_vars, fol) log.debug('==== cyclic core fixpoint ====\n') return x, y, essential
def _cyclic_core_fixpoint(x, y, bab, fol): """Return cyclic core and essential elements.""" log.debug('\n\n---- cyclic core fixpoint ----') assert x in fol.bdd, x assert y in fol.bdd, y assert support_issubset(x, bab.p_vars, fol) assert support_issubset(y, bab.p_vars, fol) # compute essential = fol.false xold, yold = None, None i = 0 while x != xold or y != yold: log.debug('starting iteration {i}'.format(i=i)) t0 = time.time() xold, yold = x, y x = _max_transpose(x, y, bab, fol, signatures=True) e = x & y x = x & ~ e y = y & ~ e essential |= e y = _max_transpose(x, y, bab, fol) t1 = time.time() dt = t1 - t0 log.debug('iteration {i} took {dt:1.2f} sec'.format( i=i, dt=dt)) i += 1 _assert_fixpoint( x, y, xold, yold, essential, bab.p_vars, fol) log.debug('==== cyclic core fixpoint ====\n') return x, y, essential
def _maxima(u, bab, fol): """Return maximal elements of `u` wrt `bab.p_leq_q`. @param u: function of `bab.p_vars` """ assert support_issubset(u, bab.p_vars, fol) v = fol.let(bab.p_to_q, u) # r = v & bab.p_leq_q r = ~r | bab.p_eq_q r = fol.forall(bab.q_vars, r) r &= u ''' q = ', '.join(bab.q_vars) s = ( '{u} /\ ' '\A {q}: ({v} /\ {p_leq_q}) => ({p_eq_q})').format( u=u, v=v, p_leq_q=bab.p_leq_q, p_eq_q=bab.p_eq_q, q=q) r = fol.add_expr(s) ''' assert support_issubset(r, bab.p_vars, fol) return r
def _cyclic_core_fixpoint_recursive(x, y, path_cost, bab, fol): """Return all minimal covers of `x` made with elements from `y`.""" log.info('\n\n---- cyclic core ----') assert x in fol.bdd, x assert y in fol.bdd, y assert support_issubset(x, bab.p_vars, fol) assert support_issubset(y, bab.p_vars, fol) xold, yold = x, y # assert `x` refines `y` yq = fol.let(bab.p_to_q, y) assert cov._cover_refines(x, yq, bab.p_leq_q, bab.p_vars, bab.q_vars, fol) # max ceilings and max floors xt = cov._max_transpose(xold, yold, bab, fol, signatures=True) yt = cov._max_transpose(xt, yold, bab, fol) # Note: This order of computation is different than # in the function `cover._cyclic_core_fixpoint`. y_floors = cov._floor(xt, yold, bab, fol, signatures=False) e = xt & yt # essential elements x = xt & ~e y = yt & ~e # path_cost + cost(essential) new_path_cost = path_cost + cov._cost(e, bab.prm, fol) if (x == xold and y == yold) or (x == fol.false): mincovers_core = _traverse_exhaustive(x, y, new_path_cost, bab, fol) else: mincovers_core = _cyclic_core_fixpoint_recursive( x, y, new_path_cost, bab, fol) if not mincovers_core: return set() assert mincovers_core assert (fol.count(e) > 0) or (fol.count(y) > 0) # add essential elements r = set(mincovers_core) mincovers_core = set() for cover in r: assert y_floors | ~cover == fol.true cover |= e assert fol.count(cover) > 0 mincovers_core.add(cover) # enumerate all minimal covers assert y_floors | ~yt == fol.true assert y_floors | ~e == fol.true _assert_are_covers(xt, mincovers_core, bab, fol) _assert_covers_from(mincovers_core, yt, fol) _assert_uniform_cardinality(mincovers_core, fol) mincovers_floor = _mincovers_from_floor(mincovers_core, xt, y_floors, bab, fol) assert mincovers_floor _assert_are_covers(xt, mincovers_floor, bab, fol) _assert_covers_from(mincovers_floor, y_floors, fol) _assert_uniform_cardinality(mincovers_floor, fol) mincovers = _mincovers_from_unfloor(mincovers_floor, yold, bab, fol) assert mincovers _assert_are_covers(xold, mincovers, bab, fol) _assert_covers_from(mincovers, yold, fol) _assert_uniform_cardinality(mincovers, fol) log.info('==== cyclic core ====\n') return mincovers
def _concretize_implicants(implicants_p, prm, fol): """Return covered set as function of `x`.""" assert support_issubset(implicants_p, prm.p_vars, fol) # concretize x_in_p = lat.x_in_implicant(prm, fol) u = x_in_p & implicants_p covered_x = fol.exist(prm.p_vars, u) assert support_issubset(covered_x, prm.x_vars, fol) return covered_x
def _assert_are_covers(x, covers, prm, fol): """Assert that each element of `covers` covers `x`.""" assert support_issubset(x, prm.p_vars, fol) for cover in covers: assert support_issubset(cover, prm.p_vars, fol) yq = fol.let(prm.p_to_q, cover) assert support_issubset(yq, prm.q_vars, fol) assert cov._cover_refines(x, yq, prm.p_leq_q, prm.p_vars, prm.q_vars, fol)
def _concretize_implicants(implicants_p, prm, fol): """Return covered set as function of `x`.""" assert support_issubset(implicants_p, prm.p_vars, fol) # concretize x_in_p = lat.x_in_implicant(prm, fol) u = x_in_p & implicants_p covered_x = fol.exist(prm.p_vars, u) assert support_issubset(covered_x, prm.x_vars, fol) return covered_x
def _y_unfloor(yfloor, y, prm, fol): """Compute the elements from `y` that are above `yfloor`.""" assert support_issubset(yfloor, prm.p_vars, fol) d = fol.pick(yfloor) assert set(d) == prm.p_vars, d yq = fol.let(prm.p_to_q, y) # y(q) p_leq_y = prm.p_leq_q & yq # Leq[z(p), y(q)] y_over_q = fol.let(d, p_leq_y) # ThoseOver(y, yfloor)(q) y_over = fol.let(prm.q_to_p, y_over_q) # ThoseOver(y, yfloor)(p) assert support_issubset(y_over, prm.p_vars, fol) assert y_over != fol.false return y_over
def _floor(p_is_signature, p_is_prime, bab, fol, signatures=False): """Transpose primes (tau_X(Y)) or signatures (tau_Y(X)). Floor(S) = Join(S) Ceil(S) = Meet(S) @param p_is_prime: some primes, function of `p` @param p_is_signature: function of `p` @param signatures: if `True`, then transpose signatures, otherwise primes. """ log.info('---- tranpose ----') p_leq_u = bab.p_leq_u u_leq_p = bab.u_leq_p if signatures: p_is_signature, p_is_prime = p_is_prime, p_is_signature u_leq_p, p_leq_u = p_leq_u, u_leq_p # assert assert support_issubset(p_is_prime, bab.p_vars, fol) assert support_issubset(p_is_signature, bab.p_vars, fol) # p' used as u u_is_signature = fol.let(bab.p_to_u, p_is_signature) # same coverage p_like_q = _contains_covered( u_is_signature, u_leq_p, bab, fol) u_like_q = fol.let(bab.p_to_u, p_like_q) # u_is_prime = fol.let(p_to_u, p_is_prime) q_is_prime = fol.let(bab.p_to_q, p_is_prime) # r = ~ u_like_q | p_leq_u r = fol.forall(bab.u_vars, r) r &= p_like_q r &= q_is_prime r = fol.exist(bab.q_vars, r) ''' q = ', '.join(iter(p_to_q.values())) u = ', '.join(iter(p_to_u.values())) s = ( '\E {q}: {q_is_prime} /\ {p_like_q} /\ ' ' \A {u}: {u_like_q} => {p_leq_u}').format( q=q, u=u, q_is_prime=q_is_prime, p_like_q=p_like_q, u_like_q=u_like_q, p_leq_u=p_leq_u) r = fol.add_expr(s) ''' assert support_issubset(r, bab.p_vars, fol) log.info('==== tranpose ====') return r
def _floor(p_is_signature, p_is_prime, bab, fol, signatures=False): """Transpose primes (tau_X(Y)) or signatures (tau_Y(X)). Floor(S) = Join(S) Ceil(S) = Meet(S) @param p_is_prime: some primes, function of `p` @param p_is_signature: function of `p` @param signatures: if `True`, then transpose signatures, otherwise primes. """ log.info('---- tranpose ----') p_leq_u = bab.p_leq_u u_leq_p = bab.u_leq_p if signatures: p_is_signature, p_is_prime = p_is_prime, p_is_signature u_leq_p, p_leq_u = p_leq_u, u_leq_p # assert assert support_issubset(p_is_prime, bab.p_vars, fol) assert support_issubset(p_is_signature, bab.p_vars, fol) # p' used as u u_is_signature = fol.let(bab.p_to_u, p_is_signature) # same coverage p_like_q = _contains_covered( u_is_signature, u_leq_p, bab, fol) u_like_q = fol.let(bab.p_to_u, p_like_q) # u_is_prime = fol.let(p_to_u, p_is_prime) q_is_prime = fol.let(bab.p_to_q, p_is_prime) # r = ~ u_like_q | p_leq_u r = fol.forall(bab.u_vars, r) r &= p_like_q r &= q_is_prime r = fol.exist(bab.q_vars, r) ''' q = ', '.join(iter(p_to_q.values())) u = ', '.join(iter(p_to_u.values())) s = ( '\E {q}: {q_is_prime} /\ {p_like_q} /\ ' ' \A {u}: {u_like_q} => {p_leq_u}').format( q=q, u=u, q_is_prime=q_is_prime, p_like_q=p_like_q, u_like_q=u_like_q, p_leq_u=p_leq_u) r = fol.add_expr(s) ''' assert support_issubset(r, bab.p_vars, fol) log.info('==== tranpose ====') return r
def _assert_fixpoint(x, y, xold, yold, essential, pvars, fol): """Assert `x, y = xold, yold` and check supports.""" assert x == xold assert y == yold e = x & y assert e == fol.false, e e = x & essential assert e == fol.false, e e = y & essential assert e == fol.false, e assert support_issubset(x, pvars, fol) assert support_issubset(y, pvars, fol) assert support_issubset(essential, pvars, fol)
def _assert_fixpoint(x, y, xold, yold, essential, pvars, fol): """Assert `x, y = xold, yold` and check supports.""" assert x == xold assert y == yold e = x & y assert e == fol.false, e e = x & essential assert e == fol.false, e e = y & essential assert e == fol.false, e assert support_issubset(x, pvars, fol) assert support_issubset(y, pvars, fol) assert support_issubset(essential, pvars, fol)
def _branch_exhaustive(x, y, path_cost, bab, fol): log.info('\n\n---- branch ----') mincovers = set() assert support_issubset(x, bab.p_vars, fol) assert support_issubset(y, bab.p_vars, fol) d = fol.pick(y) assert set(d) == bab.p_vars, (d, bab.p_vars, fol.support(y)) # must be specific implicant # This condition follows because Y is an antichain when we pick. log.info('picked branching y: {d}'.format(d=d)) y_branch = fol.assign_from(d) ynew = y & ~y_branch assert ynew != y # r(p) == p <= y_branch dq = {bab.p_to_q[k]: v for k, v in d.items()} r = fol.let(dq, bab.p_leq_q) # those x under y_branch x_minus_y = x & ~r assert x_minus_y != x # must prove always the case log.info('left branch') mincovers_left = _cyclic_core_fixpoint_recursive(x_minus_y, ynew, path_cost + 1, bab, fol) # add `y_branch` to `mincovers_left` r = set(mincovers_left) mincovers_left = set() for mincover in r: mincover |= y_branch mincovers_left.add(mincover) log.info('right branch') mincovers_right = _cyclic_core_fixpoint_recursive(x, ynew, path_cost, bab, fol) if not mincovers_left: return mincovers_right if not mincovers_right: return mincovers_left # pick smaller covers, or # take union if of same cardinality assert mincovers_left, mincovers_left assert mincovers_right, mincovers_right e_left = next(iter(mincovers_left)) e_right = next(iter(mincovers_right)) cost_left = cov._cost(e_left, bab.prm, fol) cost_right = cov._cost(e_right, bab.prm, fol) if cost_left < cost_right: mincovers = mincovers_left elif cost_left > cost_right: mincovers = mincovers_right elif cost_left == cost_right: mincovers = mincovers_left mincovers.update(mincovers_right) log.info('==== branch ====\n') return mincovers
def _independent_set( x, y, p_leq_q, p_to_q, fol, only_size=False): """Return an independent set of implicants and its size. Requires that each implicant in `x` be covered by at least one implicant in `y`. @param only_size: Do not return the independent set. This avoids creating the BDD of a sparse set. """ log.debug('---- independent set ----') p = set(p_to_q) q = set(p_to_q.values()) assert support_issubset(x, p, fol), (fol.support(x), p) assert support_issubset(y, p, fol), (fol.support(y), p) yq = fol.let(p_to_q, y) assert _cover_refines(x, yq, p_leq_q, p, q, fol) rem = x z = fol.false k = 0 n = fol.count(rem) assert n >= 0, n while rem != fol.false: x0 = fol.pick(rem) assert set(x0) == p, x0 if not only_size: z |= fol.assign_from(x0) # umbrella of x0 # # r(p) == \E q: /\ x0 <= q # /\ p <= q # /\ y(q) r = yq & fol.let(x0, p_leq_q) r &= p_leq_q r = fol.exist(q, r) # update assert support_issubset(r, p, fol) rem &= ~ r k += 1 # variant nold = n n = fol.count(rem) assert n < nold, (n, nold) assert fol.count(rem) == 0, 'some x not covered' _assert_possible_cover_size(k, x, fol) log.debug('==== independent set ====') if only_size: return None, k k_ = fol.count(z) assert k == k_, (k, k_) return z, k
def _independent_set( x, y, p_leq_q, p_to_q, fol, only_size=False): """Return an independent set of implicants and its size. Requires that each implicant in `x` be covered by at least one implicant in `y`. @param only_size: Do not return the independent set. This avoids creating the BDD of a sparse set. """ log.debug('---- independent set ----') p = set(p_to_q) q = set(p_to_q.values()) assert support_issubset(x, p, fol), (fol.support(x), p) assert support_issubset(y, p, fol), (fol.support(y), p) yq = fol.let(p_to_q, y) assert _cover_refines(x, yq, p_leq_q, p, q, fol), r rem = x z = fol.false k = 0 n = fol.count(rem) assert n >= 0, n while rem != fol.false: x0 = fol.pick(rem) assert set(x0) == p, x0 if not only_size: z |= fol.assign_from(x0) # umbrella of x0 # # r(p) == \E q: /\ x0 <= q # /\ p <= q # /\ y(q) r = yq & fol.let(x0, p_leq_q) r &= p_leq_q r = fol.exist(q, r) # update assert support_issubset(r, p, fol) rem &= ~ r k += 1 # variant nold = n n = fol.count(rem) assert n < nold, (n, nold) assert fol.count(rem) == 0, 'some x not covered' _assert_possible_cover_size(k, x, fol) log.debug('==== independent set ====') if only_size: return None, k k_ = fol.count(z) assert k == k_, (k, k_) return z, k
def prime_implicants(f, prm, fol): """Return dominators of implicants.""" log.info('----- prime orthotopes ----') assert support_issubset(f, prm.x_vars, fol) p_is_implicant = _implicant_orthotopes(f, prm, fol) q_is_implicant = fol.let(prm.p_to_q, p_is_implicant) r = q_is_implicant & prm.p_leq_q r = prm.p_eq_q | ~ r r = fol.forall(prm.q_vars, r) r &= p_is_implicant ''' q = ', '.join(prm.q_vars) s = ( '{p_is_implicant} /\ ' r'\A {q}: ( ' ' ({q_is_implicant} /\ {p_leq_q})' ' => {p_eq_q}' ')').format( p_is_implicant=p_is_implicant, q_is_implicant=q_is_implicant, p_leq_q=prm.p_leq_q, p_eq_q=prm.p_eq_q, q=prm.q_vars) r = fol.add_expr(s) ''' log.info('==== prime orthotopes ====') return r
def prime_implicants(f, prm, fol): """Return dominators of implicants.""" log.info('----- prime orthotopes ----') assert support_issubset(f, prm.x_vars, fol) p_is_implicant = _implicant_orthotopes(f, prm, fol) q_is_implicant = fol.let(prm.p_to_q, p_is_implicant) r = q_is_implicant & prm.p_leq_q r = prm.p_eq_q | ~r r = fol.forall(prm.q_vars, r) r &= p_is_implicant ''' q = ', '.join(prm.q_vars) s = ( '{p_is_implicant} /\ ' r'\A {q}: ( ' ' ({q_is_implicant} /\ {p_leq_q})' ' => {p_eq_q}' ')').format( p_is_implicant=p_is_implicant, q_is_implicant=q_is_implicant, p_leq_q=prm.p_leq_q, p_eq_q=prm.p_eq_q, q=prm.q_vars) r = fol.add_expr(s) ''' log.info('==== prime orthotopes ====') return r
def _some_cover(x, y, p_leq_q, p_to_q, fol, only_size=False): """Return a cover and its size, possibly not minimal. Signature similar to `_independent_set`. """ log.debug('---- some cover ----') p = set(p_to_q) q = set(p_to_q.values()) assert support_issubset(x, p, fol), (fol.support(x), p) assert support_issubset(y, p, fol), (fol.support(y), p) yq = fol.let(p_to_q, y) assert _cover_refines(x, yq, p_leq_q, p, q, fol), r rem = x z = fol.false k = 0 n = fol.count(rem) assert n >= 0, n while rem != fol.false: x0 = fol.pick(rem) # x0 assert set(x0) == p, x0 # ys that cover x0 # # r(q) == /\ x0 <= q # /\ y(q) r = yq & fol.let(x0, p_leq_q) y0 = fol.pick(r) assert set(y0) == q, y0 if not only_size: z |= fol.assign_from(y0) # x that y0 does not cover # rem(p) /\ ~ (p <= y0) rem &= ~fol.let(y0, p_leq_q) k += 1 # variant nold = n n = fol.count(rem) assert n < nold, (n, nold) assert fol.count(rem) == 0, 'some x not covered' _assert_possible_cover_size(k, x, fol) log.debug('==== some cover ====') if only_size: return None, k q_to_p = {v: k for k, v in p_to_q.items()} zp = fol.let(q_to_p, z) k_ = fol.count(z) assert k == k_, (k, k_) return zp, k
def _some_cover(x, y, p_leq_q, p_to_q, fol, only_size=False): """Return a cover and its size, possibly not minimal. Signature similar to `_independent_set`. """ log.debug('---- some cover ----') p = set(p_to_q) q = set(p_to_q.values()) assert support_issubset(x, p, fol), (fol.support(x), p) assert support_issubset(y, p, fol), (fol.support(y), p) yq = fol.let(p_to_q, y) assert _cover_refines(x, yq, p_leq_q, p, q, fol), r rem = x z = fol.false k = 0 n = fol.count(rem) assert n >= 0, n while rem != fol.false: x0 = fol.pick(rem) # x0 assert set(x0) == p, x0 # ys that cover x0 # # r(q) == /\ x0 <= q # /\ y(q) r = yq & fol.let(x0, p_leq_q) y0 = fol.pick(r) assert set(y0) == q, y0 if not only_size: z |= fol.assign_from(y0) # x that y0 does not cover # rem(p) /\ ~ (p <= y0) rem &= ~ fol.let(y0, p_leq_q) k += 1 # variant nold = n n = fol.count(rem) assert n < nold, (n, nold) assert fol.count(rem) == 0, 'some x not covered' _assert_possible_cover_size(k, x, fol) log.debug('==== some cover ====') if only_size: return None, k q_to_p = {v: k for k, v in p_to_q.items()} zp = fol.let(q_to_p, z) k_ = fol.count(z) assert k == k_, (k, k_) return zp, k
def _max_transpose(p_is_signature, p_is_prime, bab, fol, signatures=False): """Maximal transposed primes or signatures. (max tau_Y(X) or max tau_X(Y)) @param signatures: if `True`, then transpose signatures, otherwise primes. Requires that `p, p', q` be in `fol.vars` and be refined by the same number of bits each. """ log.info('---- max transpose ----') assert support_issubset(p_is_prime, bab.p_vars, fol) assert support_issubset(p_is_signature, bab.p_vars, fol) # compute u = _floor(p_is_signature, p_is_prime, bab, fol, signatures=signatures) r = _maxima(u, bab, fol) assert support_issubset(r, bab.p_vars, fol) log.info('==== max transpose ====') return r
def _covers_naive(cover_p, f, prm, fol): """Return `True` if `cover_p` covers `f`. Same as `covers`. Here the computation happens over the concrete variables (`x`), so it is less efficient. """ assert support_issubset(f, prm.x_vars, fol) # concretize x_in_cover = _concretize_implicants(cover_p, prm, fol) covered = ~f | x_in_cover return covered == fol.true
def _covers_naive(cover_p, f, prm, fol): """Return `True` if `cover_p` covers `f`. Same as `covers`. Here the computation happens over the concrete variables (`x`), so it is less efficient. """ assert support_issubset(f, prm.x_vars, fol) # concretize x_in_cover = _concretize_implicants(cover_p, prm, fol) covered = ~ f | x_in_cover return covered == fol.true
def _make_table(u, aut, care_source=None, care_target=None, care_bits=None): """Return symbol table with primed vars and care relation. The variables in `a.vars` should be unprimed. """ bdd = aut.bdd care_relation = _care_relation(care_source, care_target, aut.prime, bdd) t = symbolic._prime_and_order_table(aut.vars) if care_bits is not None: assert scope.support_issubset(u, care_bits, bdd), (support, care_bits) return t, care_relation
def _no_duplicate(u_p, prm, fol): """Return `True` if no doubly parameterized implicant in `u_p`.""" assert support_issubset(u_p, prm.p_vars, fol) # checks that parameterization restricted to `u` is injective # \A p, q \in u: eq(p, q) => (p = q) u_q = fol.let(prm.p_to_q, u_p) eq_implicants = prm.p_eq_q eq_parameters = _equality_of_pairs(prm.p_to_q.items(), fol) bound = u_p & u_q r = ~bound | ~eq_implicants | eq_parameters r = fol.forall(prm.p_vars | prm.q_vars, r) return r == fol.true
def _no_duplicate(u_p, prm, fol): """Return `True` if no doubly parameterized implicant in `u_p`.""" assert support_issubset(u_p, prm.p_vars, fol) # checks that parameterization restricted to `u` is injective # \A p, q \in u: eq(p, q) => (p = q) u_q = fol.let(prm.p_to_q, u_p) eq_implicants = prm.p_eq_q eq_parameters = _equality_of_pairs(prm.p_to_q.items(), fol) bound = u_p & u_q r = ~ bound | ~ eq_implicants | eq_parameters r = fol.forall(prm.p_vars | prm.q_vars, r) return r == fol.true
def _lower_bound_naive(x, y, p_leq_q, p_to_q, fol): """Return lower bound. Naive computation that greedily constructs an independent set. """ log.debug('---- naive lower bound ----') z, n = _independent_set(x, y, p_leq_q, p_to_q, fol) assert support_issubset(z, p_to_q, fol) _assert_possible_cover_size(n, x, fol) log.info('lower bound = {n}'.format(n=n)) log.debug('==== naive lower bound ====') return n
def _upper_bound_naive(x, y, p_leq_q, p_to_q, fol): """Return upper bound. Naive computation that greedily constructs an irredundant cover. """ log.debug('---- naive upper bound ----') cover, n = _some_cover(x, y, p_leq_q, p_to_q, fol) assert support_issubset(cover, p_to_q, fol) _assert_possible_cover_size(n, x, fol) log.info('upper bound = {n}'.format(n=n)) log.debug('==== naive upper bound ====') return n
def _upper_bound_naive(x, y, p_leq_q, p_to_q, fol): """Return upper bound. Naive computation that greedily constructs an irredundant cover. """ log.debug('---- naive upper bound ----') cover, n = _some_cover(x, y, p_leq_q, p_to_q, fol) assert support_issubset(cover, p_to_q, fol) _assert_possible_cover_size(n, x, fol) log.info('upper bound = {n}'.format(n=n)) log.debug('==== naive upper bound ====') return n
def _lower_bound_naive(x, y, p_leq_q, p_to_q, fol): """Return lower bound. Naive computation that greedily constructs an independent set. """ log.debug('---- naive lower bound ----') z, n = _independent_set(x, y, p_leq_q, p_to_q, fol) assert support_issubset(z, p_to_q, fol) _assert_possible_cover_size(n, x, fol) log.info('lower bound = {n}'.format(n=n)) log.debug('==== naive lower bound ====') return n
def _contains_covered(u_is_signature, u_leq_p, bab, fol): """Return primes that cover all signatures under prime. CAUTION: keep `u_leq_p` in the arguments, because the function `_floor` swaps with `p_leq_u` before calling `_contains_covered`. In the proof, this operator is equivalent to: `IsAbove(p, ThoseUnder(u_is_signature, q, Leq))` @param signatures: function of `u` """ log.info('---- contains covered ----') # assert pq_vars = bab.p_vars.union(bab.q_vars) pu_vars = bab.p_vars.union(bab.u_vars) assert support_issubset(u_is_signature, bab.u_vars, fol) assert support_issubset(u_leq_p, pu_vars, fol) # compute u_leq_q = fol.let(bab.p_to_q, u_leq_p) r = u_is_signature & u_leq_q r = ~ r | u_leq_p r = fol.forall(bab.u_vars, r) ''' uvars = ', '.join(bab.u_vars) s = ( '\A {uvars}: ' ' ({sig_u} /\ {u_leq_q}) ' ' => {u_leq_p}').format( uvars=uvars, sig_u=u_is_signature, u_leq_q=u_leq_q, u_leq_p=u_leq_p) r = fol.add_expr(s) ''' assert support_issubset(r, pq_vars, fol) log.info('==== contains covered ====') return r
def _below_and_suff(ymax, cover, x, y, prm, fol): """Return subset of `y` that is below `ymax` and suffices. A `yk \in y` suffices if it covers all elements in `x` that are covered by `ymax` but not by any element of `y` other than `ymax`. """ assert support_issubset(ymax, prm.p_vars, fol) assert support_issubset(cover, prm.p_vars, fol) assert support_issubset(x, prm.p_vars, fol) assert support_issubset(y, prm.p_vars, fol) assert ymax != fol.false assert (cover | ~ymax) == fol.true # `cover` is Q other_y = cover & ~ymax p_leq_q = prm.p_leq_q other_yq = fol.let(prm.p_to_q, other_y) u = x & p_leq_q & other_yq u = fol.exist(prm.q_vars, u) # x covered by only `ymax`, among those y in` cover` x_sig_ymax = x & ~u # Only(ymax, cover) assert x_sig_ymax != fol.false # find the y above these x, # and below ymax assert (y | ~ymax) == fol.true yq = fol.let(prm.p_to_q, y) ymax_q = fol.let(prm.p_to_q, ymax) u = (p_leq_q & yq) | ~x_sig_ymax u = fol.forall(prm.p_vars, u) assert u != fol.false # Yonly == {y \in Y: \A q \in Only(ymax, cover): Leq[q, y]} y_only = fol.let(prm.q_to_p, u) u = y_only & p_leq_q & ymax_q # {y \in Yonly: Leq[y, ymax]} yk_set = fol.exist(prm.q_vars, u) assert support_issubset(yk_set, prm.p_vars, fol) assert yk_set != fol.false return yk_set
def _print_cyclic_core( x, y, xcore, ycore, essential, t0, prm, fol): """Print results of cyclic core computation. Assert support and covering properties. """ if log.getEffectiveLevel() > logging.INFO: return # assert if essential != fol.false: assert support_issubset(essential, prm.p_vars, fol) if xcore != fol.false: assert support_issubset(xcore, prm.p_vars, fol) if ycore != fol.false: assert support_issubset(ycore, prm.p_vars, fol) # print m = fol.count(x) n = fol.count(y) log.info(( '(x={m}, y={n}) implicants of ' 'covering problem').format( m=humanize.intcomma(m), n=humanize.intcomma(n))) m = fol.count(xcore) n = fol.count(ycore) log.info(( '(x={m}, y={n}) implicants after ' 'removing essential elements').format( m=humanize.intcomma(m), n=humanize.intcomma(n))) n = fol.count(essential) log.info('{n} primes are essential'.format( n=humanize.intcomma(n))) t1 = time.time() dt = t1 - t0 log.info('cyclic core took {dt}'.format( dt=humanize.naturaldelta(dt)))
def _contains_covered(u_is_signature, u_leq_p, bab, fol): """Return primes that cover all signatures under prime. CAUTION: keep `u_leq_p` in the arguments, because the function `_floor` swaps with `p_leq_u` before calling `_contains_covered`. In the proof, this operator is equivalent to: `IsAbove(p, ThoseUnder(u_is_signature, q, Leq))` @param signatures: function of `u` """ log.info('---- contains covered ----') # assert pq_vars = bab.p_vars.union(bab.q_vars) pu_vars = bab.p_vars.union(bab.u_vars) assert support_issubset(u_is_signature, bab.u_vars, fol) assert support_issubset(u_leq_p, pu_vars, fol) # compute u_leq_q = fol.let(bab.p_to_q, u_leq_p) r = u_is_signature & u_leq_q r = ~r | u_leq_p r = fol.forall(bab.u_vars, r) ''' uvars = ', '.join(bab.u_vars) s = ( '\A {uvars}: ' ' ({sig_u} /\ {u_leq_q}) ' ' => {u_leq_p}').format( uvars=uvars, sig_u=u_is_signature, u_leq_q=u_leq_q, u_leq_p=u_leq_p) r = fol.add_expr(s) ''' assert support_issubset(r, pq_vars, fol) log.info('==== contains covered ====') return r
def setup_lattice(prm, fol): """Store the lattice BDDs in `prm`.""" log.info('partial order') u_leq_p, p_leq_u = partial_order(prm._px, fol) log.info('subseteq') p_leq_q = subseteq(prm._varmap, fol) log.info('eq') p_eq_q = eq(prm._varmap, fol) pq_vars = prm.p_vars.union(prm.q_vars) assert support_issubset(p_leq_q, pq_vars, fol) prm.u_leq_p = u_leq_p prm.p_leq_u = p_leq_u prm.p_leq_q = p_leq_q prm.p_eq_q = p_eq_q
def _max_transpose(p_is_signature, p_is_prime, bab, fol, signatures=False): """Maximal transposed primes or signatures. (max tau_Y(X) or max tau_X(Y)) @param signatures: if `True`, then transpose signatures, otherwise primes. Requires that `p, p', q` be in `fol.vars` and be refined by the same number of bits each. """ log.info('---- max transpose ----') assert support_issubset(p_is_prime, bab.p_vars, fol) assert support_issubset(p_is_signature, bab.p_vars, fol) # compute u = _floor( p_is_signature, p_is_prime, bab, fol, signatures=signatures) r = _maxima(u, bab, fol) assert support_issubset(r, bab.p_vars, fol) log.info('==== max transpose ====') return r
def _cost(u, prm, fol): """Return numerical cost of cover `u`. ASSUME that no two assignments that satisfy `u` represent the same implicant (no duplicates). """ if u is None: return float('inf') # cost of each implicant = 1 # cost of a cover = number of implicants it contains assert _no_duplicate(u, prm, fol) assert support_issubset(u, prm.p_vars, fol) n = fol.count(u) return n
def setup_lattice(prm, fol): """Store the lattice BDDs in `prm`.""" log.info('partial order') u_leq_p, p_leq_u = partial_order(prm._px, fol) log.info('subseteq') p_leq_q = subseteq(prm._varmap, fol) log.info('eq') p_eq_q = eq(prm._varmap, fol) pq_vars = prm.p_vars.union(prm.q_vars) assert support_issubset(p_leq_q, pq_vars, fol) prm.u_leq_p = u_leq_p prm.p_leq_u = p_leq_u prm.p_leq_q = p_leq_q prm.p_eq_q = p_eq_q
def _cost(u, prm, fol): """Return numerical cost of cover `u`. ASSUME that no two assignments that satisfy `u` represent the same implicant (no duplicates). """ if u is None: return float('inf') # cost of each implicant = 1 # cost of a cover = number of implicants it contains assert _no_duplicate(u, prm, fol) assert support_issubset(u, prm.p_vars, fol) n = fol.count(u) return n
def print_nodes(u, dvars, bdd, care_set=None, care_bits=None): """Enumerate first-order models of a set. A set of nodes is defined over unprimed variables. @param dvars: table of unprimed variables @type bdd: `BDD` """ assert scope.is_state_predicate(u), u.support if u == bdd.false: print('empty set') return if care_bits is not None: assert scope.support_issubset(u, care_bits, bdd), (support, care_bits) _print_enumeration(u, bdd, dvars, care_set, care_bits)
def _implicant_orthotopes(f, prm, fol): """Return orthotopes that imply `f`. Caution: `fol` type hints are ignored. """ log.info('---- implicant orthotopes ----') x_vars = prm.x_vars assert support_issubset(f, x_vars, fol) x = ', '.join(x_vars) h = x_in_implicant(prm, fol) nonempty = _orthotope_nonempty(prm._px, fol) s = ('{nonempty} /\ ' '\A {x}: {h} => {f} ').format(x=x, h=h, f=f, nonempty=nonempty) r = fol.add_expr(s) log.info('==== implicant orthotopes ====') return r
def _implicant_orthotopes(f, prm, fol): """Return orthotopes that imply `f`. Caution: `fol` type hints are ignored. """ log.info('---- implicant orthotopes ----') x_vars = prm.x_vars assert support_issubset(f, x_vars, fol) x = ', '.join(x_vars) h = x_in_implicant(prm, fol) nonempty = _orthotope_nonempty(prm._px, fol) s = ( '{nonempty} /\ ' '\A {x}: {h} => {f} ').format( x=x, h=h, f=f, nonempty=nonempty) r = fol.add_expr(s) log.info('==== implicant orthotopes ====') return r
def test_assert_support(): fol = setup() u = fol.add_expr('x = -3 /\ ~ y') assert prm.support_issubset(u, ['x', 'y'], fol) assert not prm.support_issubset(u, ['x'], fol)