def _bqm_from_2sat(constraint): """create a bqm for a constraint with two variables. bqm will have exactly classical gap 2. """ configurations = constraint.configurations variables = constraint.variables vartype = constraint.vartype u, v = constraint.variables # if all configurations are present, then nothing is infeasible and the bqm is just all # 0.0s if len(configurations) == 4: return dimod.BinaryQuadraticModel.empty(constraint.vartype) # check if the constraint is irreducible, and if so, build the bqm for its two # components components = irreducible_components(constraint) if len(components) > 1: const0 = Constraint.from_configurations( ((config[0], ) for config in configurations), (u, ), vartype) const1 = Constraint.from_configurations( ((config[1], ) for config in configurations), (v, ), vartype) bqm = _bqm_from_1sat(const0) bqm.update(_bqm_from_1sat(const1)) return bqm assert len( configurations) > 1, "single configurations should be irreducible" # if it is not irreducible, and there are infeasible configurations, then it is time to # start building a bqm bqm = dimod.BinaryQuadraticModel.empty(vartype) # if the constraint is not irreducible and has two configurations, then it is either eq or ne if all(operator.eq(*config) for config in configurations): bqm.add_interaction(u, v, -1, vartype=dimod.SPIN) # equality elif all(operator.ne(*config) for config in configurations): bqm.add_interaction(u, v, +1, vartype=dimod.SPIN) # inequality elif (1, 1) not in configurations: bqm.add_interaction(u, v, 2, vartype=dimod.BINARY) # penalize (1, 1) elif (-1, +1) not in configurations and (0, 1) not in configurations: bqm.add_interaction(u, v, -2, vartype=dimod.BINARY) bqm.add_variable(v, 2, vartype=dimod.BINARY) elif (+1, -1) not in configurations and (1, 0) not in configurations: bqm.add_interaction(u, v, -2, vartype=dimod.BINARY) bqm.add_variable(u, 2, vartype=dimod.BINARY) else: # (0, 0) not in configurations bqm.add_interaction(u, v, 2, vartype=dimod.BINARY) bqm.add_variable(u, -2, vartype=dimod.BINARY) bqm.add_variable(v, -2, vartype=dimod.BINARY) return bqm
def halfadder_gate(variables, vartype=dimod.BINARY, name='HALF_ADDER'): """HALF_ADDER adder constraint.""" variables = tuple(variables) if vartype is dimod.BINARY: configs = frozenset([(0, 0, 0, 0), (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 1)]) else: # SPIN, vartype is checked by the decorator configs = frozenset([(-1, -1, -1, -1), (-1, +1, +1, -1), (+1, -1, +1, -1), (+1, +1, -1, +1)]) def func(augend, addend, sum_, carry): total = (augend > 0) + (addend > 0) if total == 0: return (sum_ <= 0) and (carry <= 0) elif total == 1: return (sum_ > 0) and (carry <= 0) elif total == 2: return (sum_ <= 0) and (carry > 0) else: raise ValueError("func recieved unexpected values") return Constraint(func, configs, variables, vartype=vartype, name=name)
def and_gate(variables, vartype=dimod.BINARY, name='AND'): """AND gate.""" variables = tuple(variables) if vartype is dimod.BINARY: configurations = frozenset([(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 1)]) def func(in1, in2, out): return (in1 and in2) == out else: # SPIN, vartype is checked by the decorator configurations = frozenset([(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), (+1, +1, +1)]) def func(in1, in2, out): return ((in1 > 0) and (in2 > 0)) == (out > 0) return Constraint(func, configurations, variables, vartype=vartype, name=name)
def fulladder_gate(variables, vartype=dimod.BINARY, name='FULL_ADDER'): """Full adder. Args: variables (list): Variable labels for the and gate as `[in1, in2, in3, sum, carry]`, where `in1, in2, in3` are inputs to be added and `sum` and 'carry' the resultant outputs. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} name (str, optional, default='FULL_ADDER'): Name for the constraint. Returns: Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are assigned values that match the valid states of a Boolean full adder. Examples: >>> import dwavebinarycsp >>> import dwavebinarycsp.factories.constraint.gates as gates >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) >>> csp.add_constraint(gates.fulladder_gate(['a', 'b', 'c_in', 'total', 'c_out'], name='FA1')) >>> csp.check({'a': 1, 'b': 0, 'c_in': 1, 'total': 0, 'c_out': 1}) True """ variables = tuple(variables) if vartype is dimod.BINARY: configs = frozenset([(0, 0, 0, 0, 0), (0, 0, 1, 1, 0), (0, 1, 0, 1, 0), (0, 1, 1, 0, 1), (1, 0, 0, 1, 0), (1, 0, 1, 0, 1), (1, 1, 0, 0, 1), (1, 1, 1, 1, 1)]) else: # SPIN, vartype is checked by the decorator configs = frozenset([(-1, -1, -1, -1, -1), (-1, -1, +1, +1, -1), (-1, +1, -1, +1, -1), (-1, +1, +1, -1, +1), (+1, -1, -1, +1, -1), (+1, -1, +1, -1, +1), (+1, +1, -1, -1, +1), (+1, +1, +1, +1, +1)]) def func(in1, in2, in3, sum_, carry): total = (in1 > 0) + (in2 > 0) + (in3 > 0) if total == 0: return (sum_ <= 0) and (carry <= 0) elif total == 1: return (sum_ > 0) and (carry <= 0) elif total == 2: return (sum_ <= 0) and (carry > 0) elif total == 3: return (sum_ > 0) and (carry > 0) else: raise ValueError("func recieved unexpected values") return Constraint(func, configs, variables, vartype=vartype, name=name)
def sat2in4(pos, neg=tuple(), vartype=dimod.BINARY, name='2-in-4'): """Return a 2-in-4 constraint. Args: pos (iterable): An iterable of variable labels. """ pos = tuple(pos) neg = tuple(neg) variables = pos + neg if len(variables) != 4: raise ValueError("") if neg and (len(neg) < 4): # because 2-in-4 sat is symmetric, all negated is the same as none negated const = sat2in4(pos=variables, vartype=vartype) # make one that has no negations for v in neg: const.flip_variable(v) const.name = name # overwrite the name directly return const # we can just construct them directly for speed if vartype is dimod.BINARY: configurations = frozenset([(0, 0, 1, 1), (0, 1, 0, 1), (1, 0, 0, 1), (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 0)]) else: # SPIN, vartype is checked by the decorator configurations = frozenset([(-1, -1, +1, +1), (-1, +1, -1, +1), (+1, -1, -1, +1), (-1, +1, +1, -1), (+1, -1, +1, -1), (+1, +1, -1, -1)]) def func(a, b, c, d): if a == b: return (b != c) and (c == d) elif a == c: # a != b return b == d else: # a != b, a != c => b == c return a == d return Constraint(func, configurations, variables, vartype=vartype, name=name)
def and_gate(variables, vartype=dimod.BINARY, name='AND'): """AND gate. Args: variables (list): Variable labels for the and gate as `[in1, in2, out]`, where `in1, in2` are inputs and `out` the gate's output. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} name (str, optional, default='AND'): Name for the constraint. Returns: Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are assigned values that match the valid states of an AND gate. Examples: >>> import dwavebinarycsp >>> import dwavebinarycsp.factories.constraint.gates as gates >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) >>> csp.add_constraint(gates.and_gate(['a', 'b', 'c'], name='AND1')) >>> csp.check({'a': 1, 'b': 0, 'c': 0}) True """ variables = tuple(variables) if vartype is dimod.BINARY: configurations = frozenset([(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 1)]) def func(in1, in2, out): return (in1 and in2) == out else: # SPIN, vartype is checked by the decorator configurations = frozenset([(-1, -1, -1), (-1, +1, -1), (+1, -1, -1), (+1, +1, +1)]) def func(in1, in2, out): return ((in1 > 0) and (in2 > 0)) == (out > 0) return Constraint(func, configurations, variables, vartype=vartype, name=name)
def xor_gate(variables, vartype=dimod.BINARY, name='XOR'): """XOR constraint.""" variables = tuple(variables) if vartype is dimod.BINARY: configs = frozenset([(0, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0)]) def func(in1, in2, out): return (in1 != in2) == out else: # SPIN, vartype is checked by the decorator configs = frozenset([(-1, -1, -1), (-1, +1, +1), (+1, -1, +1), (+1, +1, -1)]) def func(in1, in2, out): return ((in1 > 0) != (in2 > 0)) == (out > 0) return Constraint(func, configs, variables, vartype=vartype, name=name)
def sat2in4(pos, neg=tuple(), vartype=dimod.BINARY, name='2-in-4'): """Two-in-four (2-in-4) satisfiability. Args: pos (iterable): Variable labels, as an iterable, for non-negated variables of the constraint. Exactly four variables are specified by `pos` and `neg` together. neg (tuple): Variable labels, as an iterable, for negated variables of the constraint. Exactly four variables are specified by `pos` and `neg` together. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} name (str, optional, default='2-in-4'): Name for the constraint. Returns: Constraint(:obj:`.Constraint`): Constraint that is satisfied when its variables are assigned values that satisfy a two-in-four satisfiability problem. Examples: >>> import dwavebinarycsp >>> import dwavebinarycsp.factories.constraint.sat as sat >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) >>> csp.add_constraint(sat.sat2in4(['w', 'x', 'y', 'z'], vartype='BINARY', name='sat1')) >>> csp.check({'w': 1, 'x': 1, 'y': 0, 'z': 0}) True """ pos = tuple(pos) neg = tuple(neg) variables = pos + neg if len(variables) != 4: raise ValueError("") if neg and (len(neg) < 4): # because 2-in-4 sat is symmetric, all negated is the same as none negated const = sat2in4(pos=variables, vartype=vartype) # make one that has no negations for v in neg: const.flip_variable(v) const.name = name # overwrite the name directly return const # we can just construct them directly for speed if vartype is dimod.BINARY: configurations = frozenset([(0, 0, 1, 1), (0, 1, 0, 1), (1, 0, 0, 1), (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 0)]) else: # SPIN, vartype is checked by the decorator configurations = frozenset([(-1, -1, +1, +1), (-1, +1, -1, +1), (+1, -1, -1, +1), (-1, +1, +1, -1), (+1, -1, +1, -1), (+1, +1, -1, -1)]) def func(a, b, c, d): if a == b: return (b != c) and (c == d) elif a == c: # a != b return b == d else: # a != b, a != c => b == c return a == d return Constraint(func, configurations, variables, vartype=vartype, name=name)
def add_constraint(self, constraint, variables=tuple()): """Add a constraint. Args: constraint (function/iterable/:obj:`.Constraint`): Constraint definition in one of the supported formats: 1. Function, with input arguments matching the order and :attr:`~.ConstraintSatisfactionProblem.vartype` type of the `variables` argument, that evaluates True when the constraint is satisfied. 2. List explicitly specifying each allowed configuration as a tuple. 3. :obj:`.Constraint` object built either explicitly or by :mod:`dwavebinarycsp.factories`. variables(iterable): Variables associated with the constraint. Not required when `constraint` is a :obj:`.Constraint` object. Examples: This example defines a function that evaluates True when the constraint is satisfied. The function's input arguments match the order and type of the `variables` argument. >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) >>> def all_equal(a, b, c): # works for both dwavebinarycsp.BINARY and dwavebinarycsp.SPIN ... return (a == b) and (b == c) >>> csp.add_constraint(all_equal, ['a', 'b', 'c']) >>> csp.check({'a': 0, 'b': 0, 'c': 0}) True >>> csp.check({'a': 0, 'b': 0, 'c': 1}) False This example explicitly lists allowed configurations. >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.SPIN) >>> eq_configurations = {(-1, -1), (1, 1)} >>> csp.add_constraint(eq_configurations, ['v0', 'v1']) >>> csp.check({'v0': -1, 'v1': +1}) False >>> csp.check({'v0': -1, 'v1': -1}) True This example uses a :obj:`.Constraint` object built by :mod:`dwavebinarycsp.factories`. >>> import dwavebinarycsp.factories.constraint.gates as gates >>> csp = dwavebinarycsp.ConstraintSatisfactionProblem(dwavebinarycsp.BINARY) >>> csp.add_constraint(gates.and_gate(['a', 'b', 'c'])) # add an AND gate >>> csp.add_constraint(gates.xor_gate(['a', 'c', 'd'])) # add an XOR gate >>> csp.check({'a': 1, 'b': 0, 'c': 0, 'd': 1}) True """ if isinstance(constraint, Constraint): if variables and (tuple(variables) != constraint.variables): raise ValueError("mismatched variables and Constraint") elif isinstance(constraint, Callable): constraint = Constraint.from_func(constraint, variables, self.vartype) elif isinstance(constraint, Iterable): constraint = Constraint.from_configurations( constraint, variables, self.vartype) else: raise TypeError("Unknown constraint type given") self.constraints.append(constraint) for v in constraint.variables: self.variables[v].append(constraint)