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 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)