Esempio n. 1
0
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
Esempio n. 2
0
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
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
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
Esempio n. 7
0
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