def random_2in4sat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): """todo""" if num_variables < 4: raise ValueError("a 2in4 problem needs at least 4 variables") if num_clauses > 16 * _nchoosek(num_variables, 4): # 16 different negation patterns raise ValueError("too many clauses") # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) variables = list(range(num_variables)) constraints = set() if satisfiable: values = tuple(vartype.value) planted_solution = {v: choice(values) for v in variables} configurations = [(0, 0, 1, 1), (0, 1, 0, 1), (1, 0, 0, 1), (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 0)] while len(constraints) < num_clauses: # sort the variables because constraints are hashed on configurations/variables # because 2-in-4 sat is symmetric, we would not get a hash conflict for different # variable orders constraint_variables = sorted(sample(variables, 4)) # pick (uniformly) a configuration and determine which variables we need to negate to # match the chosen configuration config = choice(configurations) pos = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] == (planted_solution[v] > 0)) neg = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] != (planted_solution[v] > 0)) const = sat2in4(pos=pos, neg=neg, vartype=vartype) assert const.check(planted_solution) constraints.add(const) else: while len(constraints) < num_clauses: # sort the variables because constraints are hashed on configurations/variables # because 2-in-4 sat is symmetric, we would not get a hash conflict for different # variable orders constraint_variables = sorted(sample(variables, 4)) # randomly determine negations pos = tuple(v for v in constraint_variables if random() > .5) neg = tuple(v for v in constraint_variables if v not in pos) const = sat2in4(pos=pos, neg=neg, vartype=vartype) constraints.add(const) for const in constraints: csp.add_constraint(const) # in case any variables didn't make it in for v in variables: csp.add_variable(v) return csp
def random_xorsat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): """todo""" if num_variables < 3: raise ValueError("a xor problem needs at least 3 variables") if num_clauses > 8 * _nchoosek(num_variables, 3): # 8 different negation patterns raise ValueError("too many clauses") # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) variables = list(range(num_variables)) constraints = set() if satisfiable: values = tuple(vartype.value) planted_solution = {v: choice(values) for v in variables} configurations = [(0, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0)] while len(constraints) < num_clauses: # because constraints are hashed on configurations/variables, and because the inputs # to xor can be swapped without loss of generality, we can order them x, y, z = sample(variables, 3) if y > x: x, y = y, x # get the constraint const = xor_gate([x, y, z], vartype=vartype) # pick (uniformly) a configuration and determine which variables we need to negate to # match the chosen configuration config = choice(configurations) for idx, v in enumerate(const.variables): if config[idx] != (planted_solution[v] > 0): const.flip_variable(v) assert const.check(planted_solution) constraints.add(const) else: while len(constraints) < num_clauses: # because constraints are hashed on configurations/variables, and because the inputs # to xor can be swapped without loss of generality, we can order them x, y, z = sample(variables, 3) if y > x: x, y = y, x # get the constraint const = xor_gate([x, y, z], vartype=vartype) # randomly flip each variable in the constraint for idx, v in enumerate(const.variables): if random() > .5: const.flip_variable(v) assert const.check(planted_solution) constraints.add(const) for const in constraints: csp.add_constraint(const) # in case any variables didn't make it in for v in variables: csp.add_variable(v) return csp
def circuit(nbit, vartype=dimod.BINARY): num_multiplier_bits = num_multiplicand_bits = nbit csp = ConstraintSatisfactionProblem(vartype) a = {i: 'a%d' % i for i in range(nbit)} b = {j: 'b%d' % j for j in range(nbit)} p = {k: 'p%d' % k for k in range(nbit * 2)} AND = defaultdict( dict ) # the output of the AND gate associated with ai, bj is stored in AND[i][j] SUM = defaultdict( dict ) # the sum of the ADDER gate associated with ai, bj is stored in SUM[i][j] CARRY = defaultdict(dict) csp.add_constraint(operator.truth, ['a0']) #setting the and gates for i in range(1, nbit - 1): for j in range(1, nbit - 1): ai = a[i] bj = b[j] andij = AND[i][j] = 'and%s,%s' % (i, j) gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) csp.add_constraint(gate) AND[i][j] = 'and%s,%s' % (i, j) SUM[i][j] = 'sum%d,%d' % (i, j) CARRY[i][j] = 'carry%d,%d' % (i, j) for col in range(0, nbit * 2): """" a1,b1 = 0 a2,b2 = 1 and(a1,b1) = 2 and(a1,b2), and(a2,b1) = 3 and(a2,b2) = 4 sum(3,3) = 5 sum(4,4) = 6 """ #half adder # 1st column # half adder a1 and b1 gate = halfadder_gate([col, b[1], p[1], CARRY[1][1]], vartype=vartype) csp.add_constraint(gate) # # 2nd column # # half adder and(a1,b1) and p2 # gate = halfadder_gate([a[2], AND[1][1], SUM[1][2], CARRY[1][2]], vartype=vartype) # csp.add_constraint(gate) # #full adder # # fulladder of the sum[1][2], carry[0][0] and b2 # gate = fulladder_gate([SUM[2][2], CARRY[0][0], b[2], p[2], CARRY[2][1]], vartype=vartype) # csp.add_constraint(gate) # # 3rd column # # full adder not(and[2][1]), and[1][2] and carry[1][2] # gate = fulladder_gate([not(AND[2][1]), AND[1][2], CARRY[1][2], SUM[3][3], CARRY[3][3]], vartype=vartype) # csp.add_constraint(gate) # # half adder # gate = halfadder_gate([not(SUM[3][3]), not(CARRY[2][1]), not(p[3]), CARRY[5][2]], vartype=vartype) # csp.add_constraint(gate) # # 4th column # # full adder and(p2,q2), q1 and not(p2,q1) # gate = fulladder_gate([AND[2][2], b[1], not(AND[2][1]), SUM[3][0], CARRY[3][0]], vartype=vartype) # csp.add_constraint(gate) # # full adder a1, sum, carry # gate = fulladder_gate([a[1], SUM[3][0], CARRY[3][3], SUM[][], CARRY[][]], vartype=vartype) # csp.add_constraint(gate) # # half adder sum and not(carry) # gate = halfadder_gate([SUM[][], not(CARRY[4][4]), p[4], CARRY[][]], vartype=vartype) # csp.add_constraint(gate) # #5th column # #full adder a2,b2, carry # #full adder sum, carry, carry # #6th column # #half adder carry not, carry not return csp
def multiplication_circuit(nbit, vartype=dimod.BINARY): """Multiplication circuit constraint satisfaction problem. A constraint satisfaction problem that represents the binary multiplication :math:`ab=p`, where the multiplicands are binary variables of length `nbit`; for example, :math:`2^ma_{nbit} + ... + 4a_2 + 2a_1 + a0`. The square below shows a graphic representation of the circuit:: ________________________________________________________________________________ | and20 and10 and00 | | | | | | | and21 add11──and11 add01──and01 | | | |┌───────────┘|┌───────────┘| | | | and22 add12──and12 add02──and02 | | | | |┌───────────┘|┌───────────┘| | | | | add13─────────add03 | | | | | ┌───────────┘| | | | | | | p5 p4 p3 p2 p1 p0 | -------------------------------------------------------------------------------- Args: nbit (int): Number of bits in the multiplicands. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} Returns: CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when variables :math:`a,b,p` are assigned values that correctly solve binary multiplication :math:`ab=p`. Examples: This example creates a multiplication circuit CSP that multiplies two 3-bit numbers, which is then formulated as a binary quadratic model (BQM). It fixes the multiplacands as :math:`a=5, b=3` (:math:`101` and :math:`011`) and uses a simulated annealing sampler to find the product, :math:`p=15` (:math:`001111`). >>> from dwavebinarycsp.factories.csp.circuits import multiplication_circuit >>> import neal >>> csp = multiplication_circuit(3) >>> bqm = dwavebinarycsp.stitch(csp) >>> bqm.fix_variable('a0', 1); bqm.fix_variable('a1', 0); bqm.fix_variable('a2', 1) >>> bqm.fix_variable('b0', 1); bqm.fix_variable('b1', 1); bqm.fix_variable('b2', 0) >>> sampler = neal.SimulatedAnnealingSampler() >>> response = sampler.sample(bqm) >>> p = next(response.samples(n=1, sorted_by='energy')) >>> print(p['p5'], p['p4'], p['p3'], p['p2'], p['p1'], p['p0']) # doctest: +SKIP 0 0 1 1 1 1 """ if nbit < 1: raise ValueError( "num_multiplier_bits, num_multiplicand_bits must be positive integers" ) num_multiplier_bits = num_multiplicand_bits = nbit # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) # throughout, we will use the following convention: # i to refer to the bits of the multiplier # j to refer to the bits of the multiplicand # k to refer to the bits of the product # create the variables corresponding to the input and output wires for the circuit a = {i: 'a%d' % i for i in range(nbit)} b = {j: 'b%d' % j for j in range(nbit)} p = {k: 'p%d' % k for k in range(nbit + nbit)} # we will want to store the internal variables somewhere AND = defaultdict( dict ) # the output of the AND gate associated with ai, bj is stored in AND[i][j] SUM = defaultdict( dict ) # the sum of the ADDER gate associated with ai, bj is stored in SUM[i][j] CARRY = defaultdict( dict ) # the carry of the ADDER gate associated with ai, bj is stored in CARRY[i][j] # we follow a shift adder for i in range(num_multiplier_bits): for j in range(num_multiplicand_bits): ai = a[i] bj = b[j] if i == 0 and j == 0: # in this case there are no inputs from lower bits, so our only input is the AND # gate. And since we only have one bit to add, we don't need an adder, no have a # carry out andij = AND[i][j] = p[0] gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) csp.add_constraint(gate) continue # we always need an AND gate andij = AND[i][j] = 'and%s,%s' % (i, j) gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) csp.add_constraint(gate) # the number of inputs will determine the type of adder inputs = [andij] # determine if there is a carry in if i - 1 in CARRY and j in CARRY[i - 1]: inputs.append(CARRY[i - 1][j]) # determine if there is a sum in if i - 1 in SUM and j + 1 in SUM[i - 1]: inputs.append(SUM[i - 1][j + 1]) # ok, add create adders if necessary if len(inputs) == 1: # we don't need an adder and we don't have a carry SUM[i][j] = andij elif len(inputs) == 2: # we need a HALFADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumij, carryij) gate = halfadder_gate([inputs[0], inputs[1], sumij, carryij], vartype=vartype, name=name) csp.add_constraint(gate) else: assert len(inputs) == 3, 'unexpected number of inputs' # we need a FULLADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) name = 'FULLADDER(%s, %s, %s) = %s, %s' % ( inputs[0], inputs[1], inputs[2], sumij, carryij) gate = fulladder_gate( [inputs[0], inputs[1], inputs[2], sumij, carryij], vartype=vartype, name=name) csp.add_constraint(gate) # now we have a final row of full adders for col in range(nbit - 1): inputs = [CARRY[nbit - 1][col], SUM[nbit - 1][col + 1]] if col == 0: sumout = p[nbit + col] carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumout, carryout) gate = halfadder_gate([inputs[0], inputs[1], sumout, carryout], vartype=vartype, name=name) csp.add_constraint(gate) continue inputs.append(CARRY[nbit][col - 1]) sumout = p[nbit + col] if col < nbit - 2: carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) else: carryout = p[2 * nbit - 1] name = 'FULLADDER(%s, %s, %s) = %s, %s' % (inputs[0], inputs[1], inputs[2], sumout, carryout) gate = fulladder_gate( [inputs[0], inputs[1], inputs[2], sumout, carryout], vartype=vartype, name=name) csp.add_constraint(gate) return csp
def multiplication_circuit(nbit, vartype=dimod.BINARY): """todo and20 and10 and00 | | | and21 add11──and11 add01──and01 | |┌───────────┘|┌───────────┘| | and22 add12──and12 add02──and02 | | |┌───────────┘|┌───────────┘| | | add13─────────add03 | | | ┌───────────┘| | | | | p5 p4 p3 p2 p1 p0 """ if nbit < 1: raise ValueError( "num_multiplier_bits, num_multiplicand_bits must be positive integers" ) num_multiplier_bits = num_multiplicand_bits = nbit # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) # throughout, we will use the following convention: # i to refer to the bits of the multiplier # j to refer to the bits of the multiplicand # k to refer to the bits of the product # create the variables corresponding to the input and output wires for the circuit a = {i: 'a%d' % i for i in range(nbit)} b = {j: 'b%d' % j for j in range(nbit)} p = {k: 'p%d' % k for k in range(nbit + nbit)} # we will want to store the internal variables somewhere AND = defaultdict( dict ) # the output of the AND gate associated with ai, bj is stored in AND[i][j] SUM = defaultdict( dict ) # the sum of the ADDER gate associated with ai, bj is stored in SUM[i][j] CARRY = defaultdict( dict ) # the carry of the ADDER gate associated with ai, bj is stored in CARRY[i][j] # we follow a shift adder for i in range(num_multiplicand_bits): for j in range(num_multiplier_bits): ai = a[i] bj = b[j] if i == 0 and j == 0: # in this case there are no inputs from lower bits, so our only input is the AND # gate. And since we only have one bit to add, we don't need an adder, no have a # carry out andij = AND[i][j] = p[0] gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) csp.add_constraint(gate) continue # we always need an AND gate andij = AND[i][j] = 'and%s,%s' % (i, j) gate = and_gate([ai, bj, andij], vartype=vartype, name='AND(%s, %s) = %s' % (ai, bj, andij)) csp.add_constraint(gate) # the number of inputs will determine the type of adder inputs = [andij] # determine if there is a carry in if i - 1 in CARRY and j in CARRY[i - 1]: inputs.append(CARRY[i - 1][j]) # determine if there is a sum in if i - 1 in SUM and j + 1 in SUM[i - 1]: inputs.append(SUM[i - 1][j + 1]) # ok, add create adders if necessary if len(inputs) == 1: # we don't need an adder and we don't have a carry SUM[i][j] = andij elif len(inputs) == 2: # we need a HALFADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumij, carryij) gate = halfadder_gate([inputs[0], inputs[1], sumij, carryij], vartype=vartype, name=name) csp.add_constraint(gate) else: assert len(inputs) == 3, 'unexpected number of inputs' # we need a FULLADDER so we have a sum and a carry if j == 0: sumij = SUM[i][j] = p[i] else: sumij = SUM[i][j] = 'sum%d,%d' % (i, j) carryij = CARRY[i][j] = 'carry%d,%d' % (i, j) name = 'FULLADDER(%s, %s, %s) = %s, %s' % ( inputs[0], inputs[1], inputs[2], sumij, carryij) gate = fulladder_gate( [inputs[0], inputs[1], inputs[2], sumij, carryij], vartype=vartype, name=name) csp.add_constraint(gate) # now we have a final row of full adders for col in range(nbit - 1): inputs = [CARRY[nbit - 1][col], SUM[nbit - 1][col + 1]] if col == 0: sumout = p[nbit + col] carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) name = 'HALFADDER(%s, %s) = %s, %s' % (inputs[0], inputs[1], sumout, carryout) gate = halfadder_gate([inputs[0], inputs[1], sumout, carryout], vartype=vartype, name=name) csp.add_constraint(gate) continue inputs.append(CARRY[nbit][col - 1]) sumout = p[nbit + col] if col < nbit - 2: carryout = CARRY[nbit][col] = 'carry%d,%d' % (nbit, col) else: carryout = p[2 * nbit - 1] name = 'FULLADDER(%s, %s, %s) = %s, %s' % (inputs[0], inputs[1], inputs[2], sumout, carryout) gate = fulladder_gate( [inputs[0], inputs[1], inputs[2], sumout, carryout], vartype=vartype, name=name) csp.add_constraint(gate) return csp
def random_2in4sat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): """Random two-in-four (2-in-4) constraint satisfaction problem. Args: num_variables (integer): Number of variables (at least four). num_clauses (integer): Number of constraints that together constitute the constraint satisfaction problem. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} satisfiable (bool, optional, default=True): True if the CSP can be satisfied. Returns: CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when its variables are assigned values that satisfy a two-in-four satisfiability problem. Examples: This example creates a CSP with 6 variables and two random constraints and checks whether a particular assignment of variables satisifies it. >>> import dwavebinarycsp >>> import dwavebinarycsp.factories as sat >>> csp = sat.random_2in4sat(6, 2) >>> csp.constraints # doctest: +SKIP [Constraint.from_configurations(frozenset({(1, 0, 1, 0), (1, 0, 0, 1), (1, 1, 1, 1), (0, 1, 1, 0), (0, 0, 0, 0), (0, 1, 0, 1)}), (2, 4, 0, 1), Vartype.BINARY, name='2-in-4'), Constraint.from_configurations(frozenset({(1, 0, 1, 1), (1, 1, 0, 1), (1, 1, 1, 0), (0, 0, 0, 1), (0, 1, 0, 0), (0, 0, 1, 0)}), (1, 2, 4, 5), Vartype.BINARY, name='2-in-4')] >>> csp.check({0: 1, 1: 0, 2: 1, 3: 1, 4: 0, 5: 0}) # doctest: +SKIP True """ if num_variables < 4: raise ValueError("a 2in4 problem needs at least 4 variables") if num_clauses > 16 * _nchoosek(num_variables, 4): # 16 different negation patterns raise ValueError("too many clauses") # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) variables = list(range(num_variables)) constraints = set() if satisfiable: values = tuple(vartype.value) planted_solution = {v: choice(values) for v in variables} configurations = [(0, 0, 1, 1), (0, 1, 0, 1), (1, 0, 0, 1), (0, 1, 1, 0), (1, 0, 1, 0), (1, 1, 0, 0)] while len(constraints) < num_clauses: # sort the variables because constraints are hashed on configurations/variables # because 2-in-4 sat is symmetric, we would not get a hash conflict for different # variable orders constraint_variables = sorted(sample(variables, 4)) # pick (uniformly) a configuration and determine which variables we need to negate to # match the chosen configuration config = choice(configurations) pos = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] == (planted_solution[v] > 0)) neg = tuple(v for idx, v in enumerate(constraint_variables) if config[idx] != (planted_solution[v] > 0)) const = sat2in4(pos=pos, neg=neg, vartype=vartype) assert const.check(planted_solution) constraints.add(const) else: while len(constraints) < num_clauses: # sort the variables because constraints are hashed on configurations/variables # because 2-in-4 sat is symmetric, we would not get a hash conflict for different # variable orders constraint_variables = sorted(sample(variables, 4)) # randomly determine negations pos = tuple(v for v in constraint_variables if random() > .5) neg = tuple(v for v in constraint_variables if v not in pos) const = sat2in4(pos=pos, neg=neg, vartype=vartype) constraints.add(const) for const in constraints: csp.add_constraint(const) # in case any variables didn't make it in for v in variables: csp.add_variable(v) return csp
def random_xorsat(num_variables, num_clauses, vartype=dimod.BINARY, satisfiable=True): """Random XOR constraint satisfaction problem. Args: num_variables (integer): Number of variables (at least three). num_clauses (integer): Number of constraints that together constitute the constraint satisfaction problem. vartype (Vartype, optional, default='BINARY'): Variable type. Accepted input values: * Vartype.SPIN, 'SPIN', {-1, 1} * Vartype.BINARY, 'BINARY', {0, 1} satisfiable (bool, optional, default=True): True if the CSP can be satisfied. Returns: CSP (:obj:`.ConstraintSatisfactionProblem`): CSP that is satisfied when its variables are assigned values that satisfy a XOR satisfiability problem. Examples: This example creates a CSP with 5 variables and two random constraints and checks whether a particular assignment of variables satisifies it. >>> import dwavebinarycsp >>> import dwavebinarycsp.factories as sat >>> csp = sat.random_xorsat(5, 2) >>> csp.constraints # doctest: +SKIP [Constraint.from_configurations(frozenset({(1, 0, 0), (1, 1, 1), (0, 1, 0), (0, 0, 1)}), (4, 3, 0), Vartype.BINARY, name='XOR (0 flipped)'), Constraint.from_configurations(frozenset({(1, 1, 0), (0, 1, 1), (0, 0, 0), (1, 0, 1)}), (2, 0, 4), Vartype.BINARY, name='XOR (2 flipped) (0 flipped)')] >>> csp.check({0: 1, 1: 0, 2: 0, 3: 1, 4: 1}) # doctest: +SKIP True """ if num_variables < 3: raise ValueError("a xor problem needs at least 3 variables") if num_clauses > 8 * _nchoosek(num_variables, 3): # 8 different negation patterns raise ValueError("too many clauses") # also checks the vartype argument csp = ConstraintSatisfactionProblem(vartype) variables = list(range(num_variables)) constraints = set() if satisfiable: values = tuple(vartype.value) planted_solution = {v: choice(values) for v in variables} configurations = [(0, 0, 0), (0, 1, 1), (1, 0, 1), (1, 1, 0)] while len(constraints) < num_clauses: # because constraints are hashed on configurations/variables, and because the inputs # to xor can be swapped without loss of generality, we can order them x, y, z = sample(variables, 3) if y > x: x, y = y, x # get the constraint const = xor_gate([x, y, z], vartype=vartype) # pick (uniformly) a configuration and determine which variables we need to negate to # match the chosen configuration config = choice(configurations) for idx, v in enumerate(const.variables): if config[idx] != (planted_solution[v] > 0): const.flip_variable(v) assert const.check(planted_solution) constraints.add(const) else: while len(constraints) < num_clauses: # because constraints are hashed on configurations/variables, and because the inputs # to xor can be swapped without loss of generality, we can order them x, y, z = sample(variables, 3) if y > x: x, y = y, x # get the constraint const = xor_gate([x, y, z], vartype=vartype) # randomly flip each variable in the constraint for idx, v in enumerate(const.variables): if random() > .5: const.flip_variable(v) assert const.check(planted_solution) constraints.add(const) for const in constraints: csp.add_constraint(const) # in case any variables didn't make it in for v in variables: csp.add_variable(v) return csp