def test_cyclic_core_recursion(): """One cyclic core.""" fol = _fol.Context() fol.declare(x=(0, 1), y=(0, 1), z=(0, 1)) s = r''' ( \/ (z = 1 /\ y = 0) \/ (x = 0 /\ z = 1) \/ (y = 1 /\ x = 0) \/ (y = 1 /\ z = 0) \/ (x = 1 /\ z = 0) \/ (x = 1 /\ y = 0) ) ''' f = fol.add_expr(s) care = fol.true # setup variables and lattice prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) # covering problem fcare = f | ~care x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) # enumerative check enumerated_covers(x, y, prm, fol) # symbolically minimize mincovers = cov_enum.minimize(f, care, fol) n = len(mincovers) assert n == 2, (n, mincovers) for cover in mincovers: n = fol.count(cover) primes = list(fol.pick_iter(cover)) assert n == 3, (n, primes)
def test_enumerate_mincovers_below(): """Test the function `enumerate_mincovers_below`.""" fol = _fol.Context() fol.declare(x=(0, 5)) u = fol.add_expr(r' x \in 0..1 ') care = fol.true prm = lat.setup_aux_vars(u, care, fol) lat.setup_lattice(prm, fol) x = fol.add_expr(r' a_x = 0 /\ b_x = 2 ') y = fol.add_expr(r''' \/ (a_x = 0 /\ b_x = 1) \/ (a_x = 0 /\ b_x = 3) \/ (a_x = 0 /\ b_x = 4) ''') cover_from_max = fol.add_expr(r''' a_x = 0 /\ b_x = 4 ''') mincovers_below = cov_enum._enumerate_mincovers_below( cover_from_max, x, y, prm, fol) mincovers_below_ = cov_enum._enumerate_mincovers_below_set_based( cover_from_max, x, y, prm, fol) assert mincovers_below == mincovers_below_, mincovers_below r = fol.add_expr(r''' \/ (a_x = 0 /\ b_x = 3) \/ (a_x = 0 /\ b_x = 4) ''') mincovers_below_ = set(fol.assign_from(d) for d in fol.pick_iter(r)) assert mincovers_below == mincovers_below_, (mincovers_below, mincovers_below_)
def cyclic_core(f, care, fol): """Shallow minimal cover, only up to cyclic core.""" log.info('cyclic core computation') t0 = time.time() # assert assert f in fol.bdd, f assert care in fol.bdd, care assert care != fol.false, 'empty care set' assert f != fol.false, 'nothing to cover' assert f != fol.true or care != fol.true, ( 'no variables involved in problem') prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) fcare = ~ care | f bab = _BranchAndBound(prm, fol) # covering problem x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) # assert problem is feasible assert x != fol.false assert y != fol.false assert _covers(y, f, prm, fol) xcore, ycore, essential = _cyclic_core_fixpoint( x, y, bab, fol) if xcore == fol.false: assert _covers(essential, f, prm, fol) _print_cyclic_core( x, y, xcore, ycore, essential, t0, bab.prm, fol) return xcore, ycore, essential
def cyclic_core(f, care, fol): """Shallow minimal cover, only up to cyclic core.""" log.info('cyclic core computation') t0 = time.time() # assert assert f in fol.bdd, f assert care in fol.bdd, care assert care != fol.false, 'empty care set' assert f != fol.false, 'nothing to cover' assert f != fol.true or care != fol.true, ( 'no variables involved in problem') prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) fcare = ~care | f bab = _BranchAndBound(prm, fol) # covering problem x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) # assert problem is feasible assert x != fol.false assert y != fol.false assert _covers(y, f, prm, fol) xcore, ycore, essential = _cyclic_core_fixpoint(x, y, bab, fol) if xcore == fol.false: assert _covers(essential, f, prm, fol) _print_cyclic_core(x, y, xcore, ycore, essential, t0, bab.prm, fol) return xcore, ycore, essential
def _assert_correct_cover(cover, f, care, fol): """Raise `AssertionError` if `cover` not of `f`.""" prm = lat.setup_aux_vars(f, care, fol) # in implicant space low = care & ~f assert _none_covered(cover, low, prm, fol) # in concrete space g = _concretize_implicants(cover, prm, fol) assert (g & care) == (f & care)
def _assert_correct_cover(cover, f, care, fol): """Raise `AssertionError` if `cover` not of `f`.""" prm = lat.setup_aux_vars(f, care, fol) # in implicant space low = care & ~ f assert _none_covered(cover, low, prm, fol) # in concrete space g = _concretize_implicants(cover, prm, fol) assert (g & care) == (f & care)
def dumps_cover( cover, f, care, fol, latex=False, show_dom=False, show_limits=False, comment=True): """Return disjunction of orthotopes in `cover`, one per line. @param latex: use `pf.sty` commands @param show_dom: if `care` implies type hints, then conjoin type hints (`fol.vars[var]['dom']`) @param show_limits: conjoin limits of bitfield values @param comment: if `True`, then list support of `f`, `cover` @rtype: `str` """ prm = lat.setup_aux_vars(f, care, fol) c = list() if show_limits: r = tyh._list_limits(prm.x_vars, fol.vars) c.extend(r) show_dom = show_dom and _care_implies_type_hints(f, care, fol) if show_dom: r = tyh._list_type_hints(prm.x_vars, fol.vars) c.extend(r) else: log.info( 'type hints omitted') r = lat.list_expr( cover, prm, fol, use_dom=show_dom, latex=latex) s = stx.vertical_op(r, op='or', latex=latex, spacing=2) c.append(s) if care != fol.true: c.append('care expression') s = stx.vertical_op(c, op='and', latex=latex) f_vars = fol.support(f) care_vars = fol.support(care) s_comment = ( '(* `f` depends on: {f_vars} *)\n' '(* `care` depends on: {care_vars} *)\n' '(* The minimal cover is: *)').format( f_vars=_comma_sorted(f_vars), care_vars=_comma_sorted(care_vars)) if comment: s = '{comment}\n{s}'.format(comment=s_comment, s=s) # could add option to find minimal cover for care too # postcondition r = lat.list_expr( cover, prm, fol, use_dom=show_dom) r = stx.disj(r) g = fol.add_expr(r) # ensure that `g` equals `f` inside `care` # `g` can be arbitrary outside of `care` assert (g & care) == (f & care), r return s
def minimize(f, care, fol): """Compute minimal DNF of predicate `f` over integers. @param f: predicate over integer-valued variables @param care: care set as predicate over same variables @type f, care: BDD node @type fol: `omega.symbolic.fol.Context` @return: minimal cover as BDD over parameters @rtype: BDD node """ # reasons for permisiveness here: # # - enable inspecting env violations of assumption # - make errors visible # - use entire instantiation domain # - permit computing DNF for care set using same `fol.vars` # - tests if not _care_implies_type_hints(f, care, fol): log.warning('care set should imply type hints') # could let # f &= care # but explicit is better. # Also, this permits working outside type hints. if not _f_implies_care(f, care, fol): log.warning('f should imply care set') if (f | ~ care) == fol.true: log.warning('f covers care set, so trivial cover') log.info('---- branching ----') path_cost = 0.0 prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) # covering problem fcare = f | ~ care # the slack is introduced by having more primes # (those for `fcare`) to cover the same minterms (`f`) x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) bab = _BranchAndBound(prm, fol) # initialize upper bound bab.upper_bound = _upper_bound( x, y, prm.p_leq_q, prm.p_to_q, fol) # assert covers(bab.best_cover, f, prm, fol) cover, _ = _traverse(x, y, path_cost, bab, fol) if cover is None: cover, _ = _some_cover(x, y, prm.p_leq_q, p_to_q, fol) assert cover is not None cover = unfloors(cover, y, fol, bab) assert_is_a_cover_from_y( cover, y, f, prm, fol) low = care & ~ f assert _none_covered(cover, low, prm, fol) log.info('==== branching ==== ') return cover
def minimize(f, care, fol): """Compute minimal DNF of predicate `f` over integers. @param f: predicate over integer-valued variables @param care: care set as predicate over same variables @type f, care: BDD node @type fol: `omega.symbolic.fol.Context` @return: minimal cover as BDD over parameters @rtype: BDD node """ # reasons for permisiveness here: # # - enable inspecting env violations of assumption # - make errors visible # - use entire instantiation domain # - permit computing DNF for care set using same `fol.vars` # - tests if not _care_implies_type_hints(f, care, fol): log.warning('care set should imply type hints') # could let # f &= care # but explicit is better. # Also, this permits working outside type hints. if not _f_implies_care(f, care, fol): log.warning('f should imply care set') if (f | ~ care) == fol.true: log.warning('f covers care set, so trivial cover') log.info('---- branching ----') path_cost = 0.0 prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) # covering problem fcare = f | ~ care # the slack is introduced by having more primes # (those for `fcare`) to cover the same minterms (`f`) x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) bab = _BranchAndBound(prm, fol) # initialize upper bound bab.upper_bound = _upper_bound( x, y, prm.p_leq_q, prm.p_to_q, fol) # assert covers(bab.best_cover, f, prm, fol) cover, _ = _traverse(x, y, path_cost, bab, fol) if cover is None: cover, _ = _some_cover(x, y, prm.p_leq_q, p_to_q, fol) assert cover is not None cover = unfloors(cover, y, fol, bab) assert_is_a_cover_from_y( cover, y, f, prm, fol) low = care & ~ f assert _none_covered(cover, low, prm, fol) log.info('==== branching ==== ') return cover
def dumps_cover( cover, f, care, fol, latex=False, show_dom=False, show_limits=False): """Return disjunction of orthotopes in `cover`, one per line. @param latex: use `pf.sty` commands @param show_dom: if `care` implies type hints, then conjoin type hints (`fol.vars[var]['dom']`) @param show_limits: conjoin limits of bitfield values @rtype: `str` """ prm = lat.setup_aux_vars(f, care, fol) c = list() if show_limits: r = tyh._list_limits(prm.x_vars, fol.vars) c.extend(r) show_dom = show_dom and _care_implies_type_hints(f, care, fol) if show_dom: r = tyh._list_type_hints(prm.x_vars, fol.vars) c.extend(r) else: log.info( 'type hints omitted (care does not imply them)') r = lat.list_expr( cover, prm, fol, use_dom=show_dom, latex=latex) s = stx.vertical_op(r, op='or', latex=latex) c.append(s) n_expr = len(r) if care != fol.true: c.append('care expression') s = stx.vertical_op(c, op='and', latex=latex) f_vars = fol.support(f) care_vars = fol.support(care) s = ( '(* `f` depends on: {f_vars} *)\n' '(* `care` depends on: {care_vars} *)\n' '(* The minimal cover is: *)\n{s}').format( f_vars=_comma_sorted(f_vars), care_vars=_comma_sorted(care_vars), s=s) # could add option to find minimal cover for care too # postcondition r = lat.list_expr( cover, prm, fol, use_dom=show_dom) r = stx.disj(r) g = fol.add_expr(r) # ensure that `g` equals `f` inside `care` # `g` can be arbitrary outside of `care` assert (g & care) == (f & care), r return s
def minimize(f, care, fol): """Compute minimal DNF of predicate `f` over integers. @param f: predicate over integer-valued variables @param care: care set as predicate over same variables @type f, care: BDD node @type fol: `omega.symbolic.fol.Context` @return: minimal covers as BDDs over parameters @rtype: set of BDD nodes """ # reasons for permisiveness here: # # - enable inspecting env violations of assumption # - make errors visible # - use entire instantiation domain # - permit computing DNF for care set using same `fol.vars` # - tests if not cov._care_implies_type_hints(f, care, fol): log.warning('care set should imply type hints') # could let # f &= care # but explicit is better. # Also, this permits working outside type hints. if not cov._f_implies_care(f, care, fol): log.warning('f should imply care set') if (f | ~care) == fol.true: log.warning('f covers care set, so trivial cover') log.info('---- branch and bound search ----') prm = lat.setup_aux_vars(f, care, fol) lat.setup_lattice(prm, fol) # covering problem fcare = f | ~care x = lat.embed_as_implicants(f, prm, fol) y = lat.prime_implicants(fcare, prm, fol) bab = cov._BranchAndBound(prm, fol) # initialize upper bound bab.upper_bound = cov._upper_bound(x, y, prm.p_leq_q, prm.p_to_q, fol) path_cost = 0.0 mincovers = _cyclic_core_fixpoint_recursive(x, y, path_cost, bab, fol) # assert assert mincovers for cover in mincovers: cov.assert_is_a_cover_from_y(cover, y, f, prm, fol) low = care & ~f assert cov._none_covered(cover, low, prm, fol) log.info('==== branch and bound search ==== ') return mincovers
def test_setup_aux_vars(): fol = _fol.Context() fol.declare(x=(-4, 5), y=(-7, 15)) f = fol.add_expr('x = 2') care = fol.true prm = lat.setup_aux_vars(f, care, fol) vrs = prm.x_vars px = prm._px qx = prm._qx p_to_q = prm.p_to_q vrs_ = {'x'} assert vrs == vrs_, (vrs, vrs_) px_ = dict(x=dict(a='a_x', b='b_x')) assert px == px_, (px, px_) qx_ = dict(x=dict(a='u_x', b='v_x')) assert qx == qx_, (qx, qx_) p_to_q_ = dict(a_x='u_x', b_x='v_x') assert p_to_q == p_to_q_, (p_to_q, p_to_q_)
def _minimize_two_managers(f, care, fol): """Optimized version of `minimize` for large problems.""" if not _care_implies_type_hints(f, care, fol): log.warning('care set should imply type hints') if not _f_implies_care(f, care, fol): log.warning('f should imply care set') if (f | ~ care) == fol.true: log.warning('f covers care set, so trivial cover') log.info('---- branching ----') path_cost = 0.0 # x_vars, px, qx, p_to_q prm = lat.setup_aux_vars(f, care, fol) # manager where optimization happens fol_2 = type(fol)() fol_2.add_vars(fol.vars) # x (to be covered) log.info('embed implicants') x = lat.embed_as_implicants(f, prm, fol) x = fol.copy(x, fol_2) # covering problem fcare = f | ~ care lat.setup_lattice(prm, fol_2) # y (to use in cover) log.info('primes') fcare_2 = fol.copy(fcare, fol_2) y = lat.prime_implicants(fcare_2, prm, fol_2) del fcare_2 bab = _BranchAndBound(prm, fol_2) # initialize upper bound bab.upper_bound = _upper_bound( x, y, prm.p_leq_q, prm.p_to_q, fol_2) # assert _covers(bab.best_cover, f, prm, fol_2) log.info('traverse') cover, _ = _traverse(x, y, path_cost, bab, fol_2) if cover is None: cover, _ = _some_cover(x, y, prm.p_leq_q, prm.p_to_q, fol_2) assert cover is not None cover = unfloors(cover, y, fol_2, bab) log.info('==== branching ==== ') del fcare, prm, bab cover = fol_2.copy(cover, fol) return cover
def _minimize_two_managers(f, care, fol): """Optimized version of `minimize` for large problems.""" if not _care_implies_type_hints(f, care, fol): log.warning('care set should imply type hints') if not _f_implies_care(f, care, fol): log.warning('f should imply care set') if (f | ~ care) == fol.true: log.warning('f covers care set, so trivial cover') log.info('---- branching ----') path_cost = 0.0 # x_vars, px, qx, p_to_q prm = lat.setup_aux_vars(f, care, fol) # manager where optimization happens fol_2 = type(fol)() fol_2.add_vars(fol.vars) # x (to be covered) log.info('embed implicants') x = lat.embed_as_implicants(f, prm, fol) x = fol.copy(x, fol_2) # covering problem fcare = f | ~ care lat.setup_lattice(prm, fol_2) # y (to use in cover) log.info('primes') fcare_2 = fol.copy(fcare, fol_2) y = lat.prime_implicants(fcare_2, prm, fol_2) del fcare_2 bab = _BranchAndBound(prm, fol_2) # initialize upper bound bab.upper_bound = _upper_bound( x, y, prm.p_leq_q, prm.p_to_q, fol_2) # assert _covers(bab.best_cover, f, prm, fol_2) log.info('traverse') cover, _ = _traverse(x, y, path_cost, bab, fol_2) if cover is None: cover, _ = _some_cover(x, y, prm.p_leq_q, prm.p_to_q, fol_2) assert cover is not None cover = unfloors(cover, y, fol_2, bab) log.info('==== branching ==== ') del fcare, prm, bab cover = fol_2.copy(cover, fol) return cover