def test_qubo_checkkey(): with assert_raises(KeyError): QUBOMatrix({('a', ): -1}) with assert_raises(KeyError): QUBOMatrix({0: -1}) with assert_raises(KeyError): QUBOMatrix({(0, 1, 2): -1})
def test_create_var(): d = QUBOMatrix.create_var(0) assert d == {(0, ): 1} assert d.name == 0 assert type(d) == QUBOMatrix d = QUBOMatrix.create_var(1) assert d == {(1, ): 1} assert d.name == 1 assert type(d) == QUBOMatrix
def test_symbols(): a, b = Symbol('a'), Symbol('b') d = QUBOMatrix() d[(0, )] -= a d[(0, 1)] += 2 d[(1, )] += b assert d == {(0, ): -a, (0, 1): 2, (1, ): b} assert d.subs(a, 2) == {(0, ): -2, (0, 1): 2, (1, ): b} assert d.subs(b, 1) == {(0, ): -a, (0, 1): 2, (1, ): 1} assert d.subs({a: -3, b: 4}) == {(0, ): 3, (0, 1): 2, (1, ): 4}
def test_qubo_remove_value_when_zero(): d = QUBOMatrix() d[(0, 0)] += 1 d[(0, 0)] -= 1 assert d == {} d.refresh() assert d.degree == 0 assert d.num_binary_variables == 0 assert d.variables == set()
def test_qubo_to_quso_to_qubo(): qubo = {(0, ): 1, (0, 1): 1, (1, ): -1, (1, 2): .2, (): -2, (2, ): 1} assert qubo == quso_to_qubo(qubo_to_quso(qubo)) # type asserting assert type(qubo_to_quso(qubo)) == QUSO assert type(qubo_to_quso(QUBOMatrix(qubo))) == QUSOMatrix assert type(qubo_to_quso(QUBO(qubo))) == QUSO qubo = { ('0', ): 1, ('0', 1): 1, (1, ): -1, (1, '2'): .2, (): -2, ('2', ): 1, (0, 0): 1 } # need to reformat qubo so it is sorted with the same hash assert QUBO(qubo) == quso_to_qubo(qubo_to_quso(qubo)) # type asserting assert type(qubo_to_quso(qubo)) == QUSO assert type(qubo_to_quso(QUBO(qubo))) == QUSO
def to_qubo(self, A=None, B=1): r"""to_qubo. Create and return the BILP problem in QUBO form following section 3 of [Lucas]. The Q matrix for the QUBO will be returned as an uppertriangular dictionary. Thus, the problem becomes minimizing :math:`\sum_{i \leq j} x_i x_j Q_{ij}`. ``A`` and ``B`` are parameters to enforce constraints. Parameters ---------- A: positive float (optional, defaults to None). A enforces the constraints. If ``A`` is None, then a default value will be chosen as given in section 3 of [Lucas]. B: positive float that is less than A (optional, defaults to 1). See section 3 of [Lucas]. Return ------ Q : qubovert.utils.QUBOMatrix object. The upper triangular QUBO matrix, a QUBOMatrix object. For most practical purposes, you can use QUBOMatrix in the same way as an ordinary dictionary. For more information, see help(qubovert.utils.QUBOMatrix). """ # all naming conventions follow the paper listed in the docstring if A is None: A = B * self._N Q = QUBOMatrix() # equation 23 for i in range(self._N): Q[(i, )] += B * self._c[i] # equation 22 for j in range(self._m): Qtemp = QUBOMatrix() Qtemp += self._b[j] for i in range(self._N): Qtemp[(i, )] -= self._S[j][i] Qtemp *= A * Qtemp Q += Qtemp return Q
def test_round(): d = QUBOMatrix({(0, ): 3.456, (1, ): -1.53456}) assert round(d) == {(0, ): 3, (1, ): -2} assert round(d, 1) == {(0, ): 3.5, (1, ): -1.5} assert round(d, 2) == {(0, ): 3.46, (1, ): -1.53} assert round(d, 3) == {(0, ): 3.456, (1, ): -1.535}
def test_properties(): Q = QUBOMatrix() Q[(0, )] -= 1 Q[(0, 1)] += 1 Q += 2 assert Q.offset == 2 assert Q.Q == {(0, 0): -1, (0, 1): 1}
def test_qubosimulation_bigrun(): # test that it runs on a big problem model = QUBOMatrix({(i, j): 1 for i in range(2, 200, 3) for j in range(2, 200, 2)}) sim = QUBOSimulation(model) sim.update(3, 1000) sim.update(3, 1000, in_order=True)
def test_qubomatrix_solve_bruteforce(): Q = QUBOMatrix({(0, 1): 1, (1, 2): 1, (1, 1): -1, (2, ): -2}) sol = Q.solve_bruteforce() assert sol == {0: 0, 1: 0, 2: 1} assert allclose(Q.value(sol), -2) Q = QUBOMatrix({(0, 0): 1, (0, 1): -1, (): 1}) sols = Q.solve_bruteforce(True) assert sols == [{0: 0, 1: 0}, {0: 0, 1: 1}, {0: 1, 1: 1}] assert all(allclose(Q.value(s), 1) for s in sols)
def test_normalize(): temp = {(0, ): 4, (1, ): -2} d = QUBOMatrix(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()} temp = {(0, ): -4, (1, ): 2} d = QUBOMatrix(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()}
def test_qubo_degree(): d = QUBOMatrix() assert d.degree == 0 d[(0, )] += 2 assert d.degree == 1 d[(1, )] -= 3 assert d.degree == 1 d[(1, 2)] -= 2 assert d.degree == 2
def test_qubo_addition(): temp = QUBOMatrix({(0, 0): 1, (0, 1): 2}) temp1 = {(0, ): -1, (1, 0): 3} temp2 = {(0, 1): 5} temp3 = {(0, ): 2, (0, 1): -1} # add constant d = temp.copy() d += 5 d[()] -= 2 d == {(0, ): 1, (0, 1): 2, (): 3} # __add__ d = temp.copy() g = d + temp1 assert g == temp2 # __iadd__ d = temp.copy() d += temp1 assert d == temp2 # __radd__ d = temp.copy() g = temp1 + d assert g == temp2 # __sub__ d = temp.copy() g = d - temp1 assert g == temp3 # __isub__ d = temp.copy() d -= temp1 assert d == temp3 # __rsub__ d = temp.copy() g = temp1 - d assert g == QUBOMatrix(temp3) * -1
def test_matrix_to_qubo(): matrix, qubo = [[-3, 1], [-1, 2]], {(0, ): -3, (1, ): 2} assert matrix_to_qubo(matrix) == qubo matrix, qubo = np.array([[-3, 1], [-1, 2]]), {(0, ): -3, (1, ): 2} assert matrix_to_qubo(matrix) == qubo matrix, qubo = [[-3, 1], [-1, 2]], QUBOMatrix({(0, ): -3, (1, ): 2}) assert matrix_to_qubo(matrix) == qubo with assert_raises(ValueError): matrix_to_qubo([[1, 2, 3], [1, 0, 1]])
def to_qubo(self, A=2, B=1): r"""to_qubo. Create and return the vertex cover problem in QUBO form following section 4.3 of [Lucas]. The Q matrix for the QUBO will be returned as an uppertriangular dictionary. Thus, the problem becomes minimizing :math:`\sum_{i \leq j} x_i x_j Q_{ij}`. ``A`` and ``B`` are parameters to enforce constraints. It is formatted such that if all the constraints are satisfied, then the objective function will be equal to the total number of colored verticies. Parameters ---------- A: positive float (optional, defaults to 2). A enforces the constraints. See section 4.3 of [Lucas]. B: positive float that is less than A (optional, defaults to 1). See section 4.3 of [Lucas]. Return ------ Q : qubovert.utils.QUBOMatrix object. The upper triangular QUBO matrix, a QUBOMatrix object. For most practical purposes, you can use QUBOMatrix in the same way as an ordinary dictionary. For more information, see help(qubovert.utils.QUBOMatrix). Example ------- >>> problem = VertexCover({(0, 1), (0, 2)}) >>> Q = problem.to_qubo() """ # all naming conventions follow the paper listed in the docstring Q = QUBOMatrix() # encode H_B (equation 34) for i in range(self._N): Q[(i, )] += B # encode H_A, ie each edge is adjacent to at least one colored vertex. for u, v in self._edges: iu, iv = self._vertex_to_index[u], self._vertex_to_index[v] Q += PCBO().add_constraint_OR(iu, iv, lam=A) return Q
def test_pretty_str(): def equal(expression, string): assert expression.pretty_str() == string x = [QUBOMatrix() + {(i, ): 1} for i in range(3)] a, b = Symbol('a'), Symbol('b') equal(x[0], "x(0)") equal(-x[0], "-x(0)") equal(x[0] * 0, "0") equal(2 * x[0] * x[1] - 3 * x[2], "2 x(0) x(1) - 3 x(2)") equal(0 * x[0] + 1, "1") equal(0 * x[0] - 1, "-1") equal(0 * x[0] + a, "(a)") equal(0 * x[0] + a * b, "(a*b)") equal((a + b) * (x[0] * x[1] - x[2]), "(a + b) x(0) x(1) + (-a - b) x(2)") equal(2 * x[0] * x[1] - x[2], "2 x(0) x(1) - x(2)") equal(-x[2] + x[0] * x[1], "-x(2) + x(0) x(1)") equal(-2 * x[2] + 2 * x[0] * x[1], "-2 x(2) + 2 x(0) x(1)")
def test_qubo_to_matrix(): matrix, qubo = [[-3, 1], [0, 2]], {(0, 0): -3, (0, 1): 1, (1, 1): 2} assert matrix == qubo_to_matrix(qubo, array=False) assert np.all(np.array(matrix) == qubo_to_matrix(qubo)) matrix = [[-3, .5], [.5, 2]] assert matrix == qubo_to_matrix(qubo, array=False, symmetric=True) assert np.all(np.array(matrix) == qubo_to_matrix(qubo, symmetric=True)) matrix = [[-3, 1], [0, 2]] qubo = QUBOMatrix({(0, 0): -3, (0, 1): 1, (1, 1): 2}) assert matrix == qubo_to_matrix(qubo, array=False) assert np.all(np.array(matrix) == qubo_to_matrix(qubo)) matrix = [[-3, .5], [.5, 2]] assert matrix == qubo_to_matrix(qubo, array=False, symmetric=True) assert np.all(np.array(matrix) == qubo_to_matrix(qubo, symmetric=True)) with assert_raises(ValueError): qubo_to_matrix({}) with assert_raises(ValueError): qubo_to_matrix({(): 1, (0, ): -1})
def to_qubo(self, A=2, B=1): r"""to_qubo. Create and return the set cover problem in QUBO form following section 5.1 of [Lucas]. The Q matrix for the QUBO will be returned as an uppertriangular dictionary. Thus, the problem becomes minimizing :math:`\sum_{i \leq j} x_i x_j Q_{ij}`. A and B are parameters to enforce constraints. If all the constraints are satisfied, then the objective function will be equal to the total number of sets in the cover (or for the weighted Set Cover problem, it will equal the total weight of included sets in the cover). Parameters ---------- A: positive float (optional, defaults to 2). A enforces the constraints. See section 5.1 of [Lucas]. B: positive float that is less than A (optional, defaults to 1). See section 5.1 of [Lucas]. Return ------ Q : qubovert.utils.QUBOMatrix object. The upper triangular QUBO matrix, a QUBOMatrix object. For most practical purposes, you can use QUBOMatrix in the same way as an ordinary dictionary. For more information, see ``help(qubovert.utils.QUBOMatrix)``. """ # all naming conventions follow the paper listed in the docstring Q = QUBOMatrix() Q += self._n * A # constant comes from the constraints # encode H_B (equation 46) for i in range(self._N): Q[(i, )] += self._weights[i] * B # encode H_A for alpha in self._U: if not self._log_trick: # (Equation 45) # first constraint for m in range(1, self._M + 1): i = self._x(alpha, m) Q[(i, i)] -= A for mp in range(m + 1, self._M + 1): ip = self._x(alpha, mp) Q[(i, ip)] += 2 * A # second constraint for m in range(1, self._M + 1): i = self._x(alpha, m) Q[(i, i)] += A * m * m for mp in range(m + 1, self._M + 1): ip = self._x(alpha, mp) Q[(i, ip)] += 2 * A * m * mp for j in self._filtered_range(alpha): Q[(j, i)] -= 2 * A * m else: # using the log_trick # no first constraint now, but modify the second constraint. for m in range(self._log_M + 1): i = self._x(alpha, m) Q[(i, i)] += A * (pow(2, 2 * m) + 2 * pow(2, m)) for mp in range(m + 1, self._log_M + 1): ip = self._x(alpha, mp) Q[(i, ip)] += 2 * A * pow(2, m + mp) for j in self._filtered_range(alpha): Q[(j, i)] -= 2 * A * pow(2, m) for i in self._filtered_range(alpha): Q[(i, )] += A if not self._log_trick else -A for j in self._filtered_range(alpha, i + 1): Q[(i, j)] += 2 * A return Q
def test_qubo_max_index(): d = QUBOMatrix({(0, 0): 1, (0, 3): 2}) assert d.max_index == 3
def test_num_terms(): d = QUBOMatrix({(0, ): 1, (0, 3): 2, (0, 2): -1}) assert d.num_terms == len(d)
def test_qubo_num_binary_variables(): d = QUBOMatrix({(0, ): 1, (0, 3): 2}) assert d.num_binary_variables == 2
def test_qubo_reinitialize_dictionary(): d = QUBOMatrix({(0, 0): 1, (1, 0): 2, (2, 0): 0, (0, 1): 1}) assert d == {(0, ): 1, (0, 1): 3}
def test_qubo_default_valid(): d = QUBOMatrix() assert d[(0, 0)] == 0 d[(0, 0)] += 1 assert d == {(0, ): 1}
def to_qubo(self, A=None, B=1): r"""to_qubo. Create and return the job sequencing problem in QUBO form following section 6.3 of [Lucas]. The Q matrix for the QUBO will be returned as an uppertriangular dictionary. Thus, the problem becomes minimizing :math:`\sum_{i \leq j} x_i x_j Q_{ij}`. A and B are parameters to enforce constraints. If all the constraints are satisfied, then the objective function will be equal to the total length of the scheduling. Parameters ---------- A: positive float (optional, defaults to None). ``A`` enforces the constraints. If ``A is None``, then ``A`` will be chosen such that the minimum of the QUBO is `guaranteed` to satisfy the constraints (``A = B*max(L)``). This may not be the best value for any particular QUBO solver, since it may cause a non smooth landscape that is hard to minimize. B: positive float (optional, defaults to 1). ``B`` is the constant in front of the portion of the QUBO to minimize. Return ------ Q : qubovert.utils.QUBOMatrix object. The upper triangular QUBO matrix, a QUBOMatrix object. For most practical purposes, you can use QUBOMatrix in the same way as an ordinary dictionary. For more information, see ``help(qubovert.utils.QUBOMatrix)``. """ # all naming conventions follow the paper listed in the docstring if A is None: A = B * self._max_L Q = QUBOMatrix() Q += self._N * A # offset comes from the first constraint # encode H_B (equation 55) # minimize worker 0's length for job, length in self._lengths.items(): ind = self._x(job, 0) # worker zero Q[(ind, )] += B * length # encode H_A (equation 54) # enforce that each job is covered exactly once. for job in self._lengths: for worker in range(self._m): ind = self._x(job, worker) Q[(ind, )] -= 2 * A for workerp in range(self._m): indp = self._x(job, workerp) Q[(ind, indp)] += A # enforce worker 0's length is larger than all the other workers' # lengths max_M = self._log_M if self._log_trick else self._M for worker in range(1, self._m): # exclude worker 0 for n in range(max_M): ind = self._y(n, worker) for np in range(max_M): indp = self._y(np, worker) if self._log_trick: Q[(ind, indp)] += A * pow(2, n + np) else: Q[(ind, indp)] += A * (n + 1) * (np + 1) for job, length in self._lengths.items(): ind1, ind2 = self._x(job, worker), self._x(job, 0) val = 2 * A * length * (pow(2, n) if self._log_trick else n + 1) Q[(ind, ind1)] += val Q[(ind, ind2)] -= val for job, length in self._lengths.items(): ind1, ind2 = self._x(job, worker), self._x(job, 0) for jobp, lengthp in self._lengths.items(): ind1p, ind2p = self._x(jobp, worker), self._x(jobp, 0) val = A * length * lengthp Q[(ind1, ind1p)] += val Q[(ind2, ind2p)] += val Q[(ind2, ind1p)] -= val Q[(ind1, ind2p)] -= val return Q
def test_qubo_update(): d = QUBOMatrix({(0, 0): 1, (0, 1): 2}) d.update({(0, ): 0, (1, 0): 1, (1, 1): -1}) assert d == {(0, 1): 1, (1, ): -1}
def test_qubo_multiplication(): temp = QUBOMatrix({(0, 0): 1, (0, 1): 2}) temp += 2 # __mul__ d = temp.copy() g = d * 3 assert g == {(0, ): 3, (0, 1): 6, (): 6} d = temp.copy() g = d * 0 assert g == {} # __imul__ d = temp.copy() d *= 3 assert d == {(0, ): 3, (0, 1): 6, (): 6} d = temp.copy() d *= 0 assert d == {} # __rmul__ d = temp.copy() g = 3 * d assert g == {(0, ): 3, (0, 1): 6, (): 6} d = temp.copy() g = 0 * d assert g == {} # __truediv__ d = temp.copy() g = d / 2 assert g == {(0, ): .5, (0, 1): 1, (): 1} # __itruediv__ d = temp.copy() d /= 2 assert d == {(0, ): .5, (0, 1): 1, (): 1} # __floordiv__ d = temp.copy() g = d // 2 assert g == {(0, 1): 1, (): 1} # __ifloordiv__ d = temp.copy() d //= 2 assert d == {(0, 1): 1, (): 1} # __mul__ but with dict d = temp.copy() d *= {(1, ): 2, (0, ): -1} assert d == {(0, ): -3, (0, 1): 4, (1, ): 4} # __pow__ d = temp.copy() d **= 2 assert d == {(0, ): 5, (0, 1): 16, (): 4} temp = d.copy() assert d**3 == d * d * d # should raise a KeyError since can't fit this into QUBO. with assert_raises(KeyError): QUBOMatrix({(0, 1): 1, (1, 2): -1})**2