Exemplo n.º 1
0
    def test_is_x_away(self):
        away2 = And(self.ex_puzzle.X[0,2,2], self.ex_puzzle.X[2,0,0])
        away1 = (OneHot(And(self.ex_puzzle.X[0,2,2], self.ex_puzzle.X[2,0,1]),
        And(self.ex_puzzle.X[0,2,1], self.ex_puzzle.X[2,0,0])))
        away_neg = And(self.ex_puzzle.X[0,2,2], self.ex_puzzle.X[2,0,0])

        self.assertTrue(self.ex_puzzle.is_x_away("Batman", "Starbuck", 2).equivalent(away2))
        self.assertTrue(self.ex_puzzle.is_x_away("Batman", "Starbuck", 1).equivalent(away1))
        self.assertTrue(self.ex_puzzle.is_x_away("Starbuck", "Batman", -2).equivalent(away_neg))
Exemplo n.º 2
0
 def one_in_each(self):
     """
     Gets the formula where every value in a group must belong to different root values
     :return List<Expr>: a list of subformulae that when Anded together creates the formula for this rule
     """
     form = []
     for group in range(0, len(self.groups)):
         f = And(*[
             OneHot(*[
                 self.X[group, idx, root]
                 for idx in range(0, self.items_per)
             ]) for root in range(0, self.items_per)
         ])
         form.append(f.to_dnf())
     return form
Exemplo n.º 3
0
 def only_one_root(self):
     """
     Gets the formula where every group value can only belong to one root value
     :return List<Expr>: a list of subformulae that when Anded together creates the formula for this rule
     """
     form = []
     for group in range(0, len(self.groups)):
         f = And(*[
             OneHot(*[
                 self.X[group, idx, root]
                 for root in range(0, self.items_per)
             ]) for idx in range(0, self.items_per)
         ])
         form.append(f.to_dnf())
     return form
Exemplo n.º 4
0
def forward_depth(g, n, k, s, t, aux):
    c = []
    for i in range(n - 1):
        ninputs = 1 << n
        next = t if i == n - 2 else aux[i]
        prev = s if i == 0 else aux[i - 1]
        for v in range(ninputs):
            if v == vsort(v):
                c.append(next[v])
                continue
            cnot = []
            for j in range(i + 1, n):
                imask = 1 << i
                jmask = 1 << j
                if (not (v & imask)) and (v & jmask):
                    c.append(Implies(g[k, i, j], ~next[v]).to_cnf())
                    cnot.append(~g[k, i, j])
                elif (v & imask) and (not (v & jmask)):
                    w = v + jmask - imask
                    c.append(
                        Implies(g[k, i, j], Equal(next[v],
                                                  prev[v] | prev[w])).to_cnf())
                    cnot.append(~g[k, i, j])
            if len(cnot) > 0:
                c.append(Implies(And(*cnot), Equal(next[v], prev[v])).to_cnf())
            else:
                c.append(Equal(next[v], prev[v]).to_cnf())
    return c
Exemplo n.º 5
0
    def rules_dnf(self):
        """
        Gets the two formulas that represent the basic rules of having exactly one value per category match with each other
        and appends those two formulae And'd together in dnf form to this puzzle's formula. This must be done in merged stages
        because it is too big of a formula to turn to dnf all at once.
        """
        base = self.only_one_root() + self.one_in_each()
        group_by = 4
        while len(base) > 1:
            base_help = []
            for i in range(0, len(base), group_by):
                form = And(*[f for f in base[i:i + group_by]])
                base_help.append(form.to_dnf())
            base = base_help

        return base[0]
Exemplo n.º 6
0
 def test_Card(self):
     n = 11
     p = 3
     a = exprvars('a', n)
     c, s = Card(a, p+1)
     self.assertEqual("[dd[0], c[1], c[2], c[3]]", str(c))
     s.append(~c[p])
     for i in range(n+1):
         for v in combinations(a, i):
             g = s[:]
             vv = list(v) + [~x for x in a if x not in v]
             g = vv + g
             constraint = And(*g)
             point = constraint.satisfy_one()
             if i > p:
                 self.assertTrue(point is None)
             else:
                 self.assertFalse(point is None)
def satisfy_all(rows_vars, cols_vars, horizontal, vertical):
    """Uses a SAT Solver to satisfy all horizontal and vertical propositions"""
    terms = []
    for i, row_var in enumerate(rows_vars):
        row_terms = []
        for combination in gen_all_combinations(horizontal[i],
                                                len(horizontal)):
            row_terms.append(
                And(*list(v if p else Not(v)
                          for v, p in zip(row_var, combination))))
        terms.append(Or(*row_terms))
    for i, col_var in enumerate(cols_vars):
        col_terms = []
        for combination in gen_all_combinations(vertical[i], len(vertical)):
            col_terms.append(
                And(*list(v if p else Not(v)
                          for v, p in zip(col_var, combination))))
        terms.append(Or(*col_terms))
    return And(*terms).tseitin().satisfy_all()
Exemplo n.º 8
0
    def _get_esop_ast(self, bitmap):
        v = exprvars('v', self._nbits)

        def binstr_to_vars(binstr):
            return [
                       (~v[x[1] - 1] if x[0] == '0' else v[x[1] - 1])
                       for x in zip(binstr, reversed(range(1, self._nbits + 1)))
                   ][::-1]

        if self._optimization == 'off':
            expression = Xor(*[
                And(*binstr_to_vars(term)) for term in
                [np.binary_repr(idx, self._nbits) for idx, v in enumerate(bitmap) if v == '1']])
        else:  # self._optimization == 'qm-dlx':
            ones = [i for i, v in enumerate(bitmap) if v == '1']
            if not ones:
                return ('const', 0,)
            dcs = [i for i, v in enumerate(bitmap) if v == '*' or v == '-' or v.lower() == 'x']
            pis = get_prime_implicants(ones=ones, dcs=dcs)
            cover = get_exact_covers(ones, pis)[-1]
            clauses = []
            for c in cover:
                if len(c) == 1:
                    term = np.binary_repr(c[0], self._nbits)
                    clause = And(*[
                        v for i, v in enumerate(binstr_to_vars(term))
                    ])
                elif len(c) > 1:
                    c_or = reduce(operator.or_, c)
                    c_and = reduce(operator.and_, c)
                    _ = np.binary_repr(c_and ^ c_or, self._nbits)[::-1]
                    clause = And(*[
                        v for i, v in enumerate(binstr_to_vars(np.binary_repr(c_and, self._nbits))) if _[i] == '0'
                    ])
                else:
                    raise AquaError('Unexpected cover term size {}.'.format(len(c)))
                if clause:
                    clauses.append(clause)
            expression = Xor(*clauses)
        return expression.to_ast()
Exemplo n.º 9
0
    def test_only_one_root(self):
        small_puzzle = P.Puzzle(["1", "2"], [["a", "b"]], [])
        sx = small_puzzle.X
        root_rule_form = (And( Or( And(sx[0,0,0], ~sx[0,0,1]), And(~sx[0,0,0], sx[0,0,1])),
        Or( And(sx[0,1,0], ~sx[0,1,1]), And(~sx[0,1,0], sx[0,1,1]))))

        self.assertTrue(And(*small_puzzle.only_one_root()).equivalent(root_rule_form))
Exemplo n.º 10
0
    def extra_clues_help(self, rules, acc, clues):
        """
        Helper to get extraneous clue sets
        :param rules: Expr- the base formula for the rules of the puzzle
        :param acc: List- list of already extraneous clues and checks if other clues are further removable
        :param clues: List- all the clues as Expr formulae
        """
        extra = []
        remaining = [c for id, c in enumerate(clues) if not (id in acc)]
        for id, clue in enumerate(clues):
            if (id in acc):
                break

            lo_formula = [
                f.to_dnf() for idx, f in enumerate(remaining) if (idx != id)
            ]
            fx = And(*(lo_formula + [rules]))
            num_sols = fx.satisfy_count()
            if num_sols == 1:
                extra.append(acc + [id])
                extra = extra + self.extra_clues_help(rules, acc + [id], clues)

        return extra
Exemplo n.º 11
0
    def are_not(self, value1, value2):
        """
        Returns the formula where the two given (non-root) values are a not-- they do not occur at the same root value
        :param value1: str- an english variable that is in self.groups
        :param value1: str- an english variable that is in self.groups, that is not in the same category as value1
        :return Expr: the formula for this clue
        """
        (group_1, val_1) = self.get_val_tuple(value1)
        (group_2, val_2) = self.get_val_tuple(value2)
        f_arenot = Or(*[
            And(self.X[group_1, val_1, idx], ~self.X[group_2, val_2, idx])
            for idx in range(0, self.items_per)
        ])

        return f_arenot
Exemplo n.º 12
0
    def is_x_away(self, val1, val2, steps):
        """
        Returns the formula for the comparison clue where val1 is some number of value steps away from val2,
        where the comparison factor is the root group, and val2 must be at one of the root values that makes
        such an implication possible. in dnf form
        :param steps: int- can be positive or negative where val1 + steps = val2 in terms of number of values away
                      eg. for values [$2, $4, $6], $2 more is 1 step, $4 is 2 steps.
        :param val1: str- one of the non root values being compared
        :param val2: str- one of the non root values being compared
        :return Expr: the formula for this clue
        """
        (group_1, v_1) = self.get_val_tuple(val1)
        (group_2, v_2) = self.get_val_tuple(val2)
        f_away = []
        for curr in range(0, self.items_per):
            if (curr - steps >= 0 and curr - steps < self.items_per):
                f_away.append(
                    And(self.X[group_2, v_2, curr], self.X[group_1, v_1,
                                                           curr - steps]))

        f_away = OneHot(*[f for f in f_away])
        return f_away.to_dnf()
Exemplo n.º 13
0
    def run(self, op):
        """
        Run the grid puzzle evaluation based on the given process type and prints the results
        :param op: str- the type of additional operations to run on the logic puzzle
        """
        rules = self.rules_dnf()
        self.formula.append(rules)
        try:
            clue_form = [self.eval_clue(clue) for clue in self.clueset]
        except Exception as e:
            print("Invalid input: check the format of puzzle clues")
            print(e)
            return
        self.formula = self.formula + clue_form
        self.formula = And(*[f.to_dnf() for f in self.formula])

        sol, count = self.solve()
        print("Number of possible solutions: ", count)
        print("Solution:")
        print(sol)

        if (op == "NONE"):
            return

        if (op == "MIN" or op == "ALL"):
            print("\n\nMinimized formula:")
            print(self.translate_f(self.eval_espresso()))

        if (op == "RED" or op == "ALL"):
            print(
                "\n\nSets of clues that can be removed to still provide a single solution:"
            )
            print(self.extra_clues(rules))

        if (op == "ALT" or op == "ALL"):
            print("\n\nAlternative equivalent clueset example:")
            pprint(self.alt_clueset())
Exemplo n.º 14
0
    def test_translate_f(self):
        f = (Or(And(self.ex_puzzle.X[0,2,2], self.ex_puzzle.X[2,0,1]),
        And(self.ex_puzzle.X[0,2,1], self.ex_puzzle.X[2,0,0])))
        f_str = 'Or(And(Starbuck_3, Batman_2), And(Starbuck_2, Batman_1))'

        self.assertEqual(self.ex_puzzle.translate_f(f), f_str)
Exemplo n.º 15
0
    def _get_esop_ast(self, bitmap):
        v = exprvars('v', self._nbits)

        def binstr_to_vars(binstr):
            return [
                       (~v[x[1] - 1] if x[0] == '0' else v[x[1] - 1])
                       for x in zip(binstr, reversed(range(1, self._nbits + 1)))
                   ][::-1]

        if self._optimization == 'off':
            expression = Xor(*[
                And(*binstr_to_vars(term)) for term in
                [np.binary_repr(idx, self._nbits) for idx, v in enumerate(bitmap) if v == '1']])
        else:  # self._optimization == 'qm-dlx':
            ones = [i for i, v in enumerate(bitmap) if v == '1']
            if not ones:
                return ('const', 0,)
            dcs = [i for i, v in enumerate(bitmap) if v == '*' or v == '-' or v.lower() == 'x']
            pis = get_prime_implicants(ones=ones, dcs=dcs)
            cover = get_exact_covers(ones, pis)[-1]
            clauses = []
            for c in cover:
                if len(c) == 1:
                    term = np.binary_repr(c[0], self._nbits)
                    clause = And(*[
                        v for i, v in enumerate(binstr_to_vars(term))
                    ])
                elif len(c) > 1:
                    c_or = reduce(operator.or_, c)
                    c_and = reduce(operator.and_, c)
                    _ = np.binary_repr(c_and ^ c_or, self._nbits)[::-1]
                    clause = And(*[
                        v for i, v in enumerate(binstr_to_vars(np.binary_repr(c_and, self._nbits))) if _[i] == '0'
                    ])
                else:
                    raise AquaError('Unexpected cover term size {}.'.format(len(c)))
                if clause:
                    clauses.append(clause)
            expression = Xor(*clauses)

        raw_ast = expression.to_ast()
        idx_mapping = {
            u: v + 1 for u, v in zip(sorted(expression.usupport), [v.indices[0] for v in sorted(expression.support)])
        }

        if raw_ast[0] == 'and' or raw_ast[0] == 'or' or raw_ast[0] == 'xor':
            clauses = []
            for c in raw_ast[1:]:
                if c[0] == 'lit':
                    clauses.append(('lit', (idx_mapping[c[1]]) if c[1] > 0 else (-idx_mapping[-c[1]])))
                elif (c[0] == 'or' or c[0] == 'and') and (raw_ast[0] != c[0]):
                    clause = []
                    for l in c[1:]:
                        clause.append(('lit', (idx_mapping[l[1]]) if l[1] > 0 else (-idx_mapping[-l[1]])))
                    clauses.append((c[0], *clause))
                else:
                    raise AquaError('Unrecognized logic expression: {}'.format(raw_ast))
        elif raw_ast[0] == 'const' or raw_ast[0] == 'lit':
            return raw_ast
        else:
            raise AquaError('Unrecognized root expression type: {}.'.format(raw_ast[0]))
        ast = (raw_ast[0], *clauses)
        return ast
Exemplo n.º 16
0
 def _to_pyeda_expr(self, val_to_sym: MutableMapping[T, str]) -> Expression:
     return And(*(expr._to_pyeda_expr(val_to_sym) for expr in self.exprs))
Exemplo n.º 17
0
def search(n, d, s, u=0, epsilon=1/4, opt='d', solver='picosat', lprefix=None, wsize=0, task=None, nthreads=4):

    if wsize <= 0:
        wsize = n + wsize

    print("n:", n, "d:", d, "s:", s, "u:", u, "opt:", opt, "solver:", solver,
          "wsize:", wsize, "task:", task, "prefix:", lprefix, "nthreads:", nthreads, file=sys.stderr)

    if opt.startswith('h'):
        # Comparator variables
        g = variables(n, d)
        
        # Empty prefix
        c_prefix = []

        # valid constraints
        c_valid = valid_depth1(g, n, d)

        # size constraints
        if s:
            ncard = s
            gcard = [g[k, i, j] for i in range(n) for j in range(i+1, n) for k in range(d)]
            c, c_size = Card(gcard, ncard + 1, prefix='g_')
            if len(c) > ncard:
                c_size += [~c[ncard]]
        else:
            c_size = []

        c_sorts = halver(g, n, 0, d, epsilon)

    elif opt.startswith('e'):
        m = n//2
        g = exprvars('g', d*m, m)
        c_valid = valid_ehalver(g, n, d)
        c_prefix = [g[i, m+i] for i in range(m)]
        d0 = 1
        c_size = []
        net = Network(to_ehalver(c_prefix, g, n, d0))
        xlist = net.outputs(n, range(1<<n))
        c_sorts = halver(g, n, d0, d, epsilon, xlist)

    elif opt.startswith('s1'):
        d = s
        g = variables(n, d)
        c_prefix = []
        c_valid = valid_size1(g, n, d)
        c_size = []
        c_sorts = sorts_backward_size(g, n, 0, d, [], u)

    elif opt.startswith('s'):
        d = s
        g = variables(n, d)

        if lprefix is None:
            lprefix  = [[(0, 1)]]
            c_prefix = [g[k, i, j] if (i, j) in layer else ~g[k, i, j] for k, layer in enumerate(lprefix) for i in range(n-1) for j in range(i+1, n)]
            if n > 3:
                c_prefix.append(Or(g[1, 0, 2], g[1, 2, 3]))
        else:
            c_prefix = [g[k, i, j] if (i, j) in layer else ~g[k, i, j] for k, layer in enumerate(lprefix) for i in range(n-1) for j in range(i+1, n)]
        net = Network(lprefix)
        d0 = len(net)

        c_valid = valid_size(g, n, d0, d)

        c_size = []

        inputs = net.not_sorted(n)
        inputs = [x for x in inputs if window_size(x, n) > 2 and window_size(x, n) <= wsize]
        xlist = net.outputs(n, inputs)

        print("d0:", d0, "len(xlist):", len(xlist), file=sys.stderr)
        c_sorts = sorts(g, n, d0, d, xlist)

    elif opt.startswith('d1'):
        g = variables(n, d)

        c_valid = valid_depth1(g, n, d)

        c_prefix = []

        if s:
            c, c_size = Card([g[k, i, j] for i in range(0, n) for j in range(i+1, n) for k in range(d)], s+1, prefix='g_')
            c_size += [~c[s]]
        else:
            c_size = []

        c_sorts = sorts_backward_depth(g, n, 0, d, None, u)

    else:
        # Comparator variables
        g = variables(n, d)

        # Prefix constraints
        if lprefix is None:
            lprefix  = [[(i, n-i-1) for i in range(n//2)]]
        sprefix = sum(len(layer) for layer in lprefix)
        c_prefix = [g[k, i, j] if (i, j) in layer else ~g[k, i, j] for k, layer in enumerate(lprefix) for i in range(n-1) for j in range(i+1, n)]
        net = Network(lprefix)
        d0 = len(net)

        # valid constraints
        c_valid = valid_depth(g, n, d0, d, s)

        # size constraints
        if s:
            ncard = s - sprefix
            gcard = [g[k, i, j] for i in range(n) for j in range(i+1, n) for k in range(d0, d)]
            c, c_size = Card(gcard, ncard + 1, prefix='g_')
            if len(c) > ncard:
                c_size += [~c[ncard]]
        else:
            c_size = []

        inputs = net.not_sorted(n)
        inputs = [x for x in inputs if window_size(x, n) > 2 and window_size(x, n) <= wsize]
        xlist = net.outputs(n, inputs)
        print("d0:", d0, "len(xlist):", len(xlist), file=sys.stderr)
        c_sorts = sorts(g, n, d0, d, xlist)

    print("c_valid:  ", len(c_valid), elapsed_time(), file=sys.stderr)
    print("c_prefix: ", len(c_prefix), elapsed_time(), file=sys.stderr)
    print("c_size:   ", len(c_size),  elapsed_time(), file=sys.stderr)
    print("c_sorts:  ", len(c_sorts), elapsed_time(), file=sys.stderr)
    clauses = c_valid + c_prefix + c_sorts + c_size
    constraint = And(*clauses)
    print("Is cnf?", constraint.is_cnf(), "Size: ", len(clauses), elapsed_time(), file=sys.stderr)

    while True:
        if solver == "picosat":
            point = constraint.satisfy_one()
            res = point is not None
        else:
            litmap, cnf = expr2dimacscnf(constraint)
            task = task if task is not None else int(time.monotonic())
            dimacscnf = "dimacscnf_{}.txt".format(task)
            litmapcnf = "litmap_{}.txt".format(task)
            with open(dimacscnf, "wt") as fp:
                print(cnf, file=fp)
            with open(litmapcnf, "wt") as fp:
                print(litmap, file=fp)
            ofile = "output_{}_{}_{}_{}.txt".format(n, d, s, task)
            with open(ofile+".log", "wt") as out:
                if solver == "minisat":
                    subprocess.call(["minisat", dimacscnf, ofile], stdout=out)
                elif solver == "glucose":
                    subprocess.call(["../../glucose-syrup-4.1/simp/glucose_release", "-model", dimacscnf], stdout=out)
                elif solver == "glucose-syrup":
                    subprocess.call(["../../glucose-syrup-4.1/parallel/glucose-syrup_release", "-nthreads={}".format(nthreads), "-model", dimacscnf, ofile], stdout=out)
                else:
                    subprocess.call(["../../cryptominisat-5.0.1/cryptominisat5", "-t", str(nthreads), dimacscnf], stdout=out)
            if solver == "minisat":
                res, soln = dimacs.read(ofile, minisat=True)
            else:
                res, soln = dimacs.read(ofile+".log")
            point = ConjNormalForm.soln2point(soln, litmap)

        print("len(point): ", len(point) if point else None, elapsed_time(), file=sys.stderr)

        if res is None:
            return 'UNDETERMINED'
        elif not res:
            return 'UNSATISFIABLE'
        else:
            if opt.startswith('h'):
                net = Halver(to_network(point, g, n, d))
                return net
            elif opt.startswith('e'):
                net = Halver(to_ehalver(point, g, n, d))
                return net
            else:
                net = to_network(point, g, n, d)
                xnew = net.not_sorted(n, log=False)
            
                if len(xnew) == u or opt not in ['d', 's']:
                    return net
                else:
                    m = 16
                    print("Unsorted:", len(xnew), file=sys.stderr)
                    new_clauses = sorts(g, n, d0, d, xnew[:m])
                    clauses += new_clauses
                    constraint = And(*clauses)