def count_solutions(self, lits=[]): if lits: T = And(self.constraints + lits) else: T = And(self.constraints) return dsharp.compile(T.to_CNF(), executable='bin/dsharp').model_count()
def test_implicates_implicants_negation_rule_example(): """These failed an old version of the previous test. See issue #3.""" sentence = Or({And({~Var(1), Var(2)}), And({~Var(3), Var(1)})}) assert (sentence.negate().implicants().negate().children >= sentence.implicates().children) assert (sentence.negate().implicates().negate().children <= sentence.implicants().children)
def check_correct(self): var = [] ret_string = "" chars = list(self.characters.keys()) sentence_vals = {x: False for x in chars} chars.remove('weapon') chars.remove('location') for char in chars: var.append(Var(char)) weapon = self.characters['weapon'].goals['murderweapon'].points sus_weapon = self.characters['weapon'].goals['suspectedweapon'].points location = self.characters['location'].goals['murderlocation'].points sus_location = self.characters['location'].goals['suspectedlocation'].points for char in chars: if self.characters[char].goals['issuspect'].points == 1: sus_killer = char if self.characters[char].goals['iskiller'].points == 1: killer = char w = Var('weapon') l = Var('location') sentence = And({w, l, Or(tuple(var))}) if killer == sus_killer: sentence_vals[killer] = True ret_string += "You got the killer!\n" else: ret_string += "The killer got away!\n" if weapon == sus_weapon: sentence_vals['weapon'] = True ret_string += "You correctly identified the weapon!\n" else: ret_string += "You didn't identify the murder weapon correctly.\n" if location == sus_location: sentence_vals['location'] = True ret_string += "You deduced the correct location!\n\n" else: ret_string += "The murder took place in another location.\n\n" if sentence.satisfied_by(sentence_vals): ret_string += "Congratulations, you solved the mystery!\n" else: ret_string += "Unfortunately, you didn't solve the mystery completely.\n" return ret_string
def process_theory(node: NNF): if node.is_CNF(): return node res = [] if isinstance(node, Var): res.append(node) assert isinstance(node, Internal) if len(node.children) == 1: [child] = node.children res.append(process_node(child)) else: left, right = node.children if isinstance(node, And): left_to_cnf = distribute(left) right_to_cnf = distribute(right) return And({left_to_cnf, right_to_cnf}) elif isinstance(node, Or): left_to_cnf = distribute(left) right_to_cnf = distribute(right) return merge_cnf(left_to_cnf, right_to_cnf) return res
def _parse_sat(tokens: 't.Deque[str]') -> NNF: cur = tokens.popleft() if cur == '(': content = _parse_sat(tokens) close = tokens.popleft() if close != ')': raise DecodeError( "Expected closing paren, found {!r}".format(close)) return content elif cur == '-': content = _parse_sat(tokens) if not isinstance(content, Var): raise DecodeError( "Only variables can be negated, not {!r}".format(content)) return ~content elif cur == '*(': children = [] while tokens[0] != ')': children.append(_parse_sat(tokens)) tokens.popleft() if children: return And(children) else: return true elif cur == '+(': children = [] while tokens[0] != ')': children.append(_parse_sat(tokens)) tokens.popleft() if children: return Or(children) else: return false else: return Var(_parse_int(cur))
def _parse_cnf(tokens: t.Iterable[str]) -> And[Or[Var]]: clauses = set() # type: t.Set[Or[Var]] clause = set() # type: t.Set[Var] for token in tokens: if token == '0': clauses.add(Or(clause)) clause = set() elif token == '%': # Some example files end with: # 0 # % # 0 # I don't know why. break elif token.startswith('-'): clause.add(Var(_parse_int(token[1:]), False)) else: clause.add(Var(_parse_int(token))) if clause: # A file may or may not end with a 0 # Adding an empty clause is not desirable clauses.add(Or(clause)) sentence = And(clauses) NNF._is_CNF_loose.set(sentence, True) return sentence
def load(fp: t.TextIO, var_labels: t.Optional[t.Dict[int, Name]] = None) -> NNF: """Load a sentence from an open file. An optional ``var_labels`` dictionary can map integers to other names. """ def decode_name(num: int) -> Name: if var_labels is not None: return var_labels[num] return num fmt, nodecount, edges, varcount = fp.readline().split() node_specs = dict(enumerate(line.split() for line in fp)) assert fmt == 'nnf' nodes = {} # type: t.Dict[int, NNF] for num, spec in node_specs.items(): if spec[0] == 'L': if spec[1].startswith('-'): nodes[num] = Var(decode_name(int(spec[1][1:])), False) else: nodes[num] = Var(decode_name(int(spec[1]))) elif spec[0] == 'A': nodes[num] = And(nodes[int(n)] for n in spec[2:]) elif spec[0] == 'O': nodes[num] = Or(nodes[int(n)] for n in spec[3:]) else: raise ValueError("Can't parse line {}: {}".format(num, spec)) if int(nodecount) == 0: raise ValueError("The sentence doesn't have any nodes.") return nodes[int(nodecount) - 1]
def test_is_DNF_examples(): assert Or().is_DNF() assert Or().is_DNF(strict=True) assert Or({And()}).is_DNF() assert Or({And()}).is_DNF(strict=True) assert Or({And({a, ~b})}).is_DNF() assert Or({And({a, ~b})}).is_DNF(strict=True) assert Or({And({a, ~b}), And({c, ~c})}).is_DNF() assert not Or({And({a, ~b}), And({c, ~c})}).is_DNF(strict=True)
def test_is_CNF_examples(): assert And().is_CNF() assert And().is_CNF(strict=True) assert And({Or()}).is_CNF() assert And({Or()}).is_CNF(strict=True) assert And({Or({a, ~b})}).is_CNF() assert And({Or({a, ~b})}).is_CNF(strict=True) assert And({Or({a, ~b}), Or({c, ~c})}).is_CNF() assert not And({Or({a, ~b}), Or({c, ~c})}).is_CNF(strict=True)
def test_dsharp_compile_converting_names(sentence: And[Or[Var]]): sentence = And( Or(Var(str(var.name), var.true) for var in clause) for clause in sentence) compiled = dsharp.compile(sentence) assert all(isinstance(name, str) for name in compiled.vars()) if sentence.satisfiable(): assert sentence.equivalent(compiled)
def test_dimacs_cnf_serialize(): sample_input = """c Example CNF format file c p cnf 4 3 1 3 -4 0 4 0 2 -3 """ assert dimacs.loads(sample_input) == And( {Or({Var(1), Var(3), ~Var(4)}), Or({Var(4)}), Or({Var(2), ~Var(3)})})
def test_dimacs_sat_serialize(): # http://www.domagoj-babic.com/uploads/ResearchProjects/Spear/dimacs-cnf.pdf sample_input = """c Sample SAT format c p sat 4 (*(+(1 3 -4) +(4) +(2 3))) """ assert dimacs.loads(sample_input) == And( {Or({Var(1), Var(3), ~Var(4)}), Or({Var(4)}), Or({Var(2), Var(3)})})
def implies_all(self, inputs: dict, left: list, right: list) -> NNF: """All left variables imply all right variables. Arguments --------- inputs: dict left : list[nnf.Var] right: list[nnf.Var] Returns ------- nnf.NNF: And(Or(~left_i, right_j)) """ clauses = [] # constraint created by function if not inputs: if left and right: left_vars = list(map(lambda var: ~var, left)) clauses = list( map(lambda clause: Or(clause), product(left_vars, right))) return And(clauses) assert isinstance(inputs, dict) # constraint from decorator for key, value in inputs.items(): left_vars = left + [key] right_vars = right + value negated_left = list(map(lambda var: ~var, left_vars)) res = list( map(lambda clause: Or(clause), product(negated_left, right_vars))) clauses.extend(res) self.add_to_instance_constraints(tuple(left_vars), res) return And(clauses)
def count_solutions(self, lits=[]): if lits: T = And(self.constraints + lits) else: T = And(self.constraints) if not T.satisfiable(): return 0 return dsharp.compile(T.to_CNF(), executable='bin/dsharp', smooth=True).model_count()
def reduce_(node: NNF) -> NNF: if isinstance(node, Or): best = add_neut candidates = [] # type: t.List[NNF] for child in node.children: value = eval_(child) if value > best: # type: ignore best = value candidates = [child] elif value == best: candidates.append(child) return Or(reduce_(candidate) for candidate in candidates) elif isinstance(node, And): return And(reduce_(child) for child in node.children) else: return node
def merge_cnf(left: NNF, right: NNF): res = [] left_clauses = [left] if isinstance( left, Var) else [clause for clause in left.children] right_clauses = [right] if isinstance( right, Var) else [clause for clause in right.children] if len(left_clauses) > len(right_clauses): for index, clause in enumerate(left_clauses): res.append((clause, right_clauses[index % len(right_clauses)])) else: for index, clause in enumerate(right_clauses): res.append((clause, left_clauses[index % len(left_clauses)])) return And(res)
def count_solutions(base_formula, lits=[]): """Counts the number of solutions to a given formula.""" def _nnfify(lit): if type(lit).__name__ == "CustomNNF": assert lit.typ == 'not', "Literal must be a variable or negated variable." return ~(lit.args[0].args[0]) else: return lit._var T = base_formula if lits: T = T & And([_nnfify(l) for l in lits]) if not T.satisfiable(): return 0 return dsharp.compile(T.to_CNF(), smooth=True).model_count()
def test_mark_deterministic(): s = And() t = And() assert not s.marked_deterministic() assert not t.marked_deterministic() s.mark_deterministic() assert s.marked_deterministic() assert not t.marked_deterministic() t.mark_deterministic() assert s.marked_deterministic() assert t.marked_deterministic() del s assert t.marked_deterministic()
def exactly_one(self, inputs: list) -> NNF: """ Exactly one variable can be true of the input Arguments --------- inputs : list[nnf.Var] Returns ------- nnf.NNF: And(at_most_one, at_least_one) """ at_most_one = _ConstraintBuilder.at_most_one(self, inputs) at_least_one = _ConstraintBuilder.at_least_one(self, inputs) if not (at_most_one and at_least_one): raise ValueError return And({at_most_one, at_least_one})
def at_most_k(self, inputs: list, k: int) -> NNF: """ At most k variables can be true. Arguments --------- inputs : list[nnf.Var] k : int Returns ------- nnf.NNF """ if not 1 <= k <= len(inputs): raise ValueError(f"The provided k={k} is greater" " than the number of propositional" f" variables (i.e. {len(inputs)} variables)" f" for {self}.") elif k == 1: return _ConstraintBuilder.at_most_one(inputs) if k >= len(inputs): warnings.warn(f"The provided k={k} for building the at most K" " constraint is greater than or equal to" f" the number of variables, which is {len(inputs)}." f" We're setting k = {len(inputs) - 1} as a result.") k = len(inputs) - 1 clauses = set() # avoid adding duplicate clauses inputs = list(map(lambda var: ~var, inputs)) # combinations from choosing k from n inputs for 1 <= k <n chosen = list(combinations(inputs, k)) for combo in chosen: combo = list(combo) excludes_combo = [x for x in inputs if x not in combo] for x in excludes_combo: clause = Or(combo + [x]) clauses.add(clause) self.add_to_instance_constraints(tuple(combo), clause) return And(clauses)
def at_most_one(self, inputs: list) -> NNF: """At most one of the inputs are true. Arguments --------- inputs : list[nnf.Var] Returns ------- theory : nnf.NNF And(Or(~a, ~b)) for all a,b in input """ if not inputs: raise ValueError(f"Inputs are empty for {self}") clauses = [] for var in inputs: # negate variables that aren't the current var excludes_var = [~x for x in inputs if x != var] clause = list(map(lambda c: Or(c), product([~var], excludes_var))) clauses.extend(clause) self.add_to_instance_constraints(str(var), clause) return And(clauses)
def test_tseitin_required_detection(): assert a.to_CNF() == And({Or({a})}) assert And().to_CNF() == And() assert Or().to_CNF() == And({Or()}) assert (a | b).to_CNF() == And({a | b}) assert And({a | b, b | c}).to_CNF() == And({a | b, b | c}) assert And({And({Or({And({~a})})})}).to_CNF() == And({Or({~a})})
def CNF(draw): sentence = And(draw(st.frozensets(clauses()))) return sentence
def models(draw): return And( Var(name, draw(st.booleans())) for name in range(1, draw(st.integers(min_value=1, max_value=9))))
def solve(self): return And(self.constraints).solve()
def is_satisfiable(self): return And(self.constraints).satisfiable()
def negate(self): return And(self.constraints).negate()
def valid(self): return And(self.constraints).valid()
def MODS(draw): num = draw(st.integers(min_value=1, max_value=9)) amount = draw(st.integers(min_value=0, max_value=10)) return Or( And(Var(name, draw(st.booleans())) for name in range(1, num)) for _ in range(amount))
def build(self, propositions) -> 'NNF': """Builds a SAT constraint from a ConstraintBuilder instance. To handle a user using the groupby feature, the partition helper function is used to partition a constraint's inputs. We then apply the SAT constraint over each partitioned set of inputs. Note ---- There is unique handling for the implies all constraint where a user could have the following cases, 1) implies_all used on a class or bound method: For this case, we can have inputs (list of dictionaries) and left or right attributes, which must be validated by utils/unpack_variables. We set left and right as empty lists if they're None to merging the propositional variables simple in _ConstraintBuilder.implies_all 2) implies_all used for direct constraint addition: If called as a function, the user must provide both a left and right side of the implication. This is ensured in bauhaus/core.constraint.constraint_by_function. No positional arguments are allowed to be passed based on the function definition of core/constraint.implies_all, so inputs will be empty for this case. Arguments --------- propositions : defaultdict(weakref.WeakValueDictionary) Stores instances in the form [classname] -> [instance_id: object] Returns ------- constraint : nnf.NNF A built NNF constraint """ if self._constraint is _ConstraintBuilder.implies_all: left_vars = unpack(self._left, propositions) if self._left else [] right_vars = unpack(self._right, propositions) if self._right else [] if not self._func: inputs = [] else: # retrieve dictionary of inputs inputs = self.get_implication_inputs(propositions) if not any(inputs.values()) and not right_vars: raise ValueError( f"The '{self}' cannot be built" " as it is decorating a class and" " the right implication variables are not" " provided. If it is decorating a method," " ensure that the method's return is" " valid for bauhaus or for the nnf library." " Check your decorator signature and set" " the 'right' keyword argument to such a value.") constraints = [] for input_set in self.partition(inputs): constraints.append( self._constraint(self, input_set, left_vars, right_vars)) return And(constraints) inputs = self.get_inputs(propositions) if not inputs: raise ValueError(inputs) constraints = [] for input_set in self.partition(inputs): if self._constraint is _ConstraintBuilder.at_most_k: constraints.append(self._constraint(self, input_set, k=self._k)) else: constraints.append(self._constraint(self, input_set)) return And(constraints)