def test_validity(sentence: nnf.NNF): if sentence.valid(): event("Valid sentence") assert all( sentence.satisfied_by(model) for model in nnf.all_models(sentence.vars())) else: event("Invalid sentence") assert any(not sentence.satisfied_by(model) for model in nnf.all_models(sentence.vars()))
def test_models(sentence: nnf.NNF): real_models = [ model for model in all_models(sentence.vars()) if sentence.satisfied_by(model) ] models = list(sentence.models()) assert len(real_models) == len(models) assert model_set(real_models) == model_set(models)
def test_all_models(names): result = list(nnf.all_models(names)) # Proper result size assert len(result) == 2**len(names) # Only real names, only booleans assert all(name in names and isinstance(value, bool) for model in result for name, value in model.items()) # Only complete models assert all(len(model) == len(names) for model in result) # No duplicate models assert len({tuple(model.items()) for model in result}) == len(result)
def test_iff(a: nnf.NNF, b: nnf.NNF): c = operators.iff(a, b) for model in nnf.all_models(c.vars()): assert ((a.satisfied_by(model) == b.satisfied_by(model) ) == c.satisfied_by(model))
def test_implied_by(a: nnf.NNF, b: nnf.NNF): c = operators.implied_by(a, b) for model in nnf.all_models(c.vars()): assert ((b.satisfied_by(model) and not a.satisfied_by(model)) != c.satisfied_by(model))
def test_nor(a: nnf.NNF, b: nnf.NNF): c = operators.nor(a, b) for model in nnf.all_models(c.vars()): assert ((a.satisfied_by(model) or b.satisfied_by(model)) != c.satisfied_by(model))
def test_all_models_basic(): assert list(nnf.all_models([])) == [{}] assert list(nnf.all_models([1])) == [{1: False}, {1: True}] assert len(list(nnf.all_models(range(10)))) == 1024
def test_satisfiable(sentence: nnf.NNF): assert sentence.satisfiable() == any( sentence.satisfied_by(model) for model in all_models(sentence.vars()))
def encode_circuit_search(original_theory, num_gates, models=[]): ######## # Vars # ######## # Inputs to the circuit are the variables of the input theory inputs = [Var(v) for v in original_theory.vars()] if models: input_clones = clone_varset(models, inputs, inputs) else: input_clones = {} # Gates are either & | or ~ gates = [] gate_modalities = {} for i in range(num_gates): gates.append(Var(Gate(i))) gate_modalities[gates[-1]] = {} for m in ['and', 'or', 'not']: gate_modalities[gates[-1]][m] = Var(GateType(gates[-1], m)) if models: gate_clones = clone_varset(models, inputs, gates) else: gate_clones = {} # Single (arbitrary) gate is the circuit output output = gates[0] # Connections between inputs/gates to gates, and transitivity connections = {} unconnections = {} for src in inputs + gates[1:]: connections[src] = {} for dst in gates: if src != dst: connections[src][dst] = Var(Connection(src, dst)) if dst not in unconnections: unconnections[dst] = {} unconnections[dst][src] = connections[src][dst] C = connections # convenience orders = {} for src in gates: orders[src] = {} for dst in gates: orders[src][dst] = Var(Order(src,dst)) ############### # Constraints # ############### ''' add decorators for simplifying adding constraints (i.e. a conjunct)''' conjuncts = [] # Orderings (to forbid cycles in the circuit) for g1 in gates: # Connection implies orders for src in connections: for dst in connections[src]: if isinstance(src.name, Gate) and isinstance(dst.name, Gate): conjuncts.append(~connections[src][dst] | orders[src][dst]) # Can't order before yourself conjuncts.append(~orders[g1][g1]) # Transitive closure for g2 in gates: for g3 in gates: conjuncts.append(~orders[g1][g2] | ~orders[g2][g3] | orders[g1][g3]) if FORCE_TREE: # At max one outgoing connection on a gate for src in gates[1:]: for dst1 in connections[src]: for dst2 in connections[src]: if dst1 != dst2: conjuncts.append(~connections[src][dst1] | ~connections[src][dst2]) # Every gate has at least one input for dst in unconnections: conjuncts.append(Or(unconnections[dst].values())) # Every gate has at most two inputs and negation gates have at most one for j in gates: for i1 in inputs+gates[1:]: for i2 in inputs+gates[1:]: if not unique([j,i1,i2]): continue conjuncts.append(~gate_modalities[j]['not'] | ~C[i1][j] | ~C[i2][j]) for i3 in inputs+gates[1:]: if not unique([j,i1,i2,i3]): continue conjuncts.append(~C[i1][j] | ~C[i2][j] | ~C[i3][j]) # Every gate has exactly one modality for g in gates: # At least one conjuncts.append(Or(gate_modalities[g].values())) # At most one for m1 in ['and', 'or', 'not']: remaining = set(['and', 'or', 'not']) - set([m1]) conjuncts.append(Or([~gate_modalities[g][m2] for m2 in remaining])) # Re-usable theories notneg_cache = {} def notneg(src, dst, cloned_src=None): if not cloned_src: cloned_src = src if (cloned_src,dst) not in notneg_cache: notneg_cache[(cloned_src,dst)] = ~C[src][dst] | cloned_src return notneg_cache[(cloned_src,dst)] notpos_cache = {} def notpos(src, dst, cloned_src=None): if not cloned_src: cloned_src = src if (cloned_src,dst) not in notpos_cache: notpos_cache[(cloned_src,dst)] = ~C[src][dst] | ~cloned_src return notpos_cache[(cloned_src,dst)] # Implement the gates for g in gates: ins = [i for i in inputs+gates[1:] if i != g] conjuncts.append(~gate_modalities[g]['and'] | iff(g, And([notneg(src,g) for src in ins]))) t = Or([notpos(src,g).negate() for src in ins]) conjuncts.append(~gate_modalities[g]['or'] | iff(g, t)) conjuncts.append(~gate_modalities[g]['not'] | iff(g, t.negate())) for m in models: bitvec = model_to_bitvec(m, inputs) orig_mapping = {**{input_clones[bitvec][i]: i for i in inputs if i != g}, **{gate_clones[bitvec][i]: i for i in gates[1:] if i != g}} ins = orig_mapping.keys() conjuncts.append(~gate_modalities[g]['and'] | iff(gate_clones[bitvec][g], And([notneg(orig_mapping[src],g,src) for src in ins]))) t = Or([notpos(orig_mapping[src],g,src).negate() for src in ins]) conjuncts.append(~gate_modalities[g]['or'] | iff(gate_clones[bitvec][g], t)) conjuncts.append(~gate_modalities[g]['not'] | iff(gate_clones[bitvec][g], t.negate())) # Finally, lock in the models if models: for m in models: bitvec = model_to_bitvec(m, inputs) for var in m: if m[var]: conjuncts.append(input_clones[bitvec][Var(var)]) else: conjuncts.append(~input_clones[bitvec][Var(var)]) if original_theory.satisfied_by(m): conjuncts.append(gate_clones[bitvec][output]) else: conjuncts.append(~gate_clones[bitvec][output]) else: for model in all_models(original_theory.vars()): t = false # negating the conjunction because of the implication: flips the signs for var,val in model.items(): if val: t |= ~Var(var) else: t |= Var(var) if original_theory.satisfied_by(model): t |= output else: t |= ~output conjuncts.append(t) versions = {} example = {} for c in conjuncts: cn = c.simplify().to_CNF() stats = "(%d / %d / %d) > (%d / %d / %d)" % (c.simplify().size(), c.simplify().height(), len(c.simplify().vars()), cn.size(), cn.height(), len(cn.vars())) example[stats] = str(c.simplify()) versions[stats] = versions.get(stats, 0) + 1 print("Conjunct stats:") for k in versions: print("\n - (%d) %s: %s" % (versions[k], k, example[k])) T = And(conjuncts) return T.simplify(), inputs, gates, output, connections, unconnections, gate_modalities
def solve_and_output(theory, bound): encoded, inputs, gates, output, connections, unconnections, gate_modalities = encode_circuit_search(theory, bound, list(all_models(theory.vars()))) print(" Size: %d" % encoded.size()) print("Height: %d" % encoded.height()) print(" Vars: %d" % len(encoded.vars())) with open('encoded.cnf', 'w') as f: th = encoded.to_CNF() print("Compiled Vars: %d" % len(th.vars())) print("Compiled Clauses: %d" % len(th.children)) print ("AxVars: %d" % len(list(filter(lambda x: isinstance(x, Aux), th.vars())))) var_labels = dict(enumerate(th.vars(), start=1)) var_labels_inverse = {v: k for k, v in var_labels.items()} dimacs.dump(th, f, mode='cnf', var_labels=var_labels_inverse) with config(sat_backend="kissat"): t0 = time.time() model = encoded.solve() print("\nSolver complete in %.2fsec" % (time.time()-t0)) if not model: print("No solution for bound %d\n" % bound) return print("\nGates:") for var in model: if isinstance(var, GateType) and model[var]: print(var) print("\nConnections:") for var in model: if isinstance(var, Connection) and model[var]: print(var) print("\nOutput: %s" % output) print("\nConfig for 1110:") for var in model: if '1110' in str(var): print("%s: %s" % (str(var), str(model[var]))) print("\n %s" % build_solution(inputs, gates, gate_modalities, model, output, unconnections)) print()