def test_quso_checkkey(): with assert_raises(KeyError): QUSO({0: -1}) with assert_raises(KeyError): QUSO({(0, 1, 2): -1})
def test_set_mapping(): d = QUSO({('a', 'b'): 1, ('a',): 2}) d.set_mapping({'a': 0, 'b': 2}) assert d.to_quso() == {(0, 2): 1, (0,): 2} d = QUSO({('a', 'b'): 1, ('a',): 2}) d.set_reverse_mapping({0: 'a', 2: 'b'}) assert d.to_quso() == {(0, 2): 1, (0,): 2}
def test_normalize(): temp = {(0,): 4, (1,): -2} d = QUSO(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()} temp = {(0,): -4, (1,): 2} d = QUSO(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()}
def test_quso_default_valid(): d = QUSO() assert d[(0, 0)] == 0 d[(0, 0)] += 1 assert d == {(): 1} d = QUSO() assert d[(0, 1)] == 0 d[(0, 1)] += 1 assert d == {(0, 1): 1}
def test_quso_update(): d = QUSO({('0',): 1, ('0', 1): 2}) d.update({('0', '0'): 0, (1, '0'): 1, (1, 1): -1}) assert d in ({('0',): 1, (): -1, (1, '0'): 1}, {('0',): 1, (): -1, ('0', 1): 1}) d = QUSO({(0, 0): 1, (0, 1): 2}) d.update(QUSO({(1, 0): 1, (1, 1): -1})) d -= 1 assert d == QUSO({(0, 1): 1, (): -2}) assert d.offset == -2
def test_properties(): temp = QUSO({('0', '0'): 1, ('0', 1): 2}) assert temp.offset == 1 d = QUSO() d[(0,)] += 1 d[(1,)] += 2 assert d == d.to_quso() == {(0,): 1, (1,): 2} assert d.mapping == d.reverse_mapping == {0: 0, 1: 1} d.set_mapping({1: 0, 0: 1}) assert d.to_quso() == {(1,): 1, (0,): 2} assert d.mapping == d.reverse_mapping == {0: 1, 1: 0}
def test_convert_solution_all_1s(): d = QUSO({(0,): 1}) assert d.convert_solution({0: 0}) == {0: 1} assert d.convert_solution({0: -1}) == {0: -1} assert d.convert_solution({0: 1}) == {0: 1} assert d.convert_solution({0: 1}, False) == {0: -1}
def test_symbols(): a, b = Symbol('a'), Symbol('b') quso = { (0, ): 1.0 * a, (0, 1): 1., (1, ): -1.0 * a, (1, 2): 1., (): -2. * b, (2, ): 1.0 * a } quso1 = qubo_to_quso(quso_to_qubo(quso)) quso1.simplify() quso = QUSO(quso) quso.simplify() assert quso == quso1 a, b = Symbol('a'), Symbol('b') qubo = { (0, ): 1.0 * a, (0, 1): 1., (1, ): -1.0 * a, (1, 2): 1., (): -2.0 * b, (2, ): 1.0 * a } qubo1 = quso_to_qubo(qubo_to_quso(qubo)) qubo1.simplify() qubo = QUBO(qubo) qubo.simplify() assert qubo == qubo1
def test_qusosimulation_reset(): ising = sum(-spin_var(i) * spin_var(i + 1) for i in range(3)) initial_state = {0: 1, 1: 1, 2: -1, 3: 1} sim = QUSOSimulation(ising, initial_state) sim.schedule_update([(2, 100)]) sim.update(2, 2000) sim.reset() assert sim.state == initial_state == sim.initial_state # test the same thing but with matrix ising = QUSOMatrix(sum(-spin_var(i) * spin_var(i + 1) for i in range(3))) initial_state = {0: 1, 1: 1, 2: -1, 3: 1} sim = QUSOSimulation(ising, initial_state) sim.schedule_update([(2, 100)]) sim.update(2, 2000) sim.reset() assert sim.state == initial_state == sim.initial_state # test the same thing but with QUSO ising = QUSO(sum(-spin_var(i) * spin_var(i + 1) for i in range(3))) initial_state = {0: 1, 1: 1, 2: -1, 3: 1} sim = QUSOSimulation(ising, initial_state) sim.schedule_update([(2, 100)]) sim.update(2, 2000) sim.reset() assert sim.state == initial_state == sim.initial_state
def test_quso_to_qubo_to_quso(): quso = {(0, 1): -4, (0, 2): 3, (): -2, (0, ): 1, (2, ): -2} assert quso == qubo_to_quso(quso_to_qubo(quso)) quso = {('0', 1): -4, ('0', '2'): 3, (): -2, ('0', ): 1, ('2', '2'): -2} # need to reformat quso so it is sorted with the same hash and squashed key assert QUSO(quso) == qubo_to_quso(quso_to_qubo(quso))
def test_quso_to_qubo_to_quso(): quso = {(0, 1): -4, (0, 2): 3, (): -2, (0, ): 1, (2, ): -2} assert quso == qubo_to_quso(quso_to_qubo(quso)) # type asserting assert type(quso_to_qubo(quso)) == QUBO assert type(quso_to_qubo(QUSOMatrix(quso))) == QUBOMatrix assert type(quso_to_qubo(QUSO(quso))) == QUBO quso = {('0', 1): -4, ('0', '2'): 3, (): -2, ('0', ): 1, ('2', '2'): -2} # need to reformat quso so it is sorted with the same hash and squashed key assert QUSO(quso) == qubo_to_quso(quso_to_qubo(quso)) # type asserting assert type(quso_to_qubo(quso)) == QUBO assert type(quso_to_qubo(QUSO(quso))) == QUBO
def test_round(): d = QUSO({(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_quso_degree(): d = QUSO() 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_symbols(): a, b = Symbol('a'), Symbol('b') d = QUSO() 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_quso_addition(): temp = QUSO({('0', '0'): 1, ('0', 1): 2}) temp1 = {('0',): -1, (1, '0'): 3} temp2 = {(1, '0'): 5, (): 1, ('0',): -1}, {('0', 1): 5, (): 1, ('0',): -1} temp3 = {(): 1, (1, '0'): -1, ('0',): 1}, {(): 1, ('0', 1): -1, ('0',): 1} # constant d = temp.copy() d += 5 assert d in ({(1, '0'): 2, (): 6}, {('0', 1): 2, (): 6}) # __add__ d = temp.copy() g = d + temp1 assert g in temp2 # __iadd__ d = temp.copy() d += temp1 assert d in temp2 # __radd__ d = temp.copy() g = temp1 + d assert g in temp2 # __sub__ d = temp.copy() g = d - temp1 assert g in temp3 # __isub__ d = temp.copy() d -= temp1 assert d in temp3 # __rsub__ d = temp.copy() g = temp1 - d assert g == QUSO(temp3[0])*-1
def test_properties(): temp = QUSO({('0', '0'): 1, ('0', 1): 2}) assert temp.offset == 1 d = QUSO() d[(0,)] += 1 d[(1,)] += 2 assert d == d.to_quso() == {(0,): 1, (1,): 2} assert d.mapping == d.reverse_mapping == {0: 0, 1: 1} d.set_mapping({1: 0, 0: 1}) assert d.to_quso() == {(1,): 1, (0,): 2} assert d.mapping == d.reverse_mapping == {0: 1, 1: 0} # an old bug d = QUSO() d.set_mapping({0: 0}) d[(0,)] += 1 assert d.num_binary_variables == 1 assert d.variables == {0}
def test_pretty_str(): def equal(expression, string): assert expression.pretty_str() == string z = [QUSO() + {(i,): 1} for i in range(3)] a, b = Symbol('a'), Symbol('b') equal(z[0], "z(0)") equal(-z[0], "-z(0)") equal(z[0] * 0, "0") equal(2*z[0]*z[1] - 3*z[2], "2 z(0) z(1) - 3 z(2)") equal(0*z[0] + 1, "1") equal(0*z[0] - 1, "-1") equal(0*z[0] + a, "(a)") equal(0*z[0] + a * b, "(a*b)") equal((a+b)*(z[0]*z[1] - z[2]), "(a + b) z(0) z(1) + (-a - b) z(2)") equal(2*z[0]*z[1] - z[2], "2 z(0) z(1) - z(2)") equal(-z[2] + z[0]*z[1], "-z(2) + z(0) z(1)") equal(-2*z[2] + 2*z[0]*z[1], "-2 z(2) + 2 z(0) z(1)")
def anneal_quso(L, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None): """anneal_quso. Run a simulated annealing algorithm to try to find the minimum of the QUSO given by ``L``. Please see all of the parameters for details. Parameters ---------- L : dict, ``qubovert.utils.QUSOMatrix`` or ``qubovert.QUSO``. Maps spin labels to their values in the objective function. Please see the docstring of ``qubovert.QUSO`` for more info on how to format ``L``. num_anneals : int >= 1 (optional, defaults to 1). The number of times to run the simulated annealing algorithm. anneal_duration : int >= 1 (optional, defaults to 1000). The total number of updates to the simulation during the anneal. This is related to the amount of time we spend in the cooling schedule. If an explicit schedule is provided, then ``anneal_duration`` will be ignored. initial_state : dict (optional, defaults to None). The initial state to start the anneal in. ``initial_state`` must map the spin label names to their values in {1, -1}. If ``initial_state`` is None, then a random state will be chosen to start each anneal. Otherwise, ``initial_state`` will be the starting state for all of the anneals. temperature_range : tuple (optional, defaults to None). The temperature to start and end the anneal at. ``temperature = (T0, Tf)``. ``T0`` must be >= ``Tf``. To see more details on picking a temperature range, please see the function ``qubovert.sim.anneal_temperature_range``. If ``temperature_range`` is None, then it will by default be set to ``T0, Tf = qubovert.sim.anneal_temperature_range(L, spin=True)``. schedule : str, or list of floats (optional, defaults to ``'geometric'``). What type of cooling schedule to use. If ``schedule == 'linear'``, then the cooling schedule will be a linear interpolation between the values in ``temperature_range``. If ``schedule == 'geometric'``, then the cooling schedule will be a geometric interpolation between the values in ``temperature_range``. Otherwise, ``schedule`` must be an iterable of floats being the explicit temperature schedule for the anneal to follow. in_order : bool (optional, defaults to True). Whether to iterate through the variables in order or randomly during an update step. When ``in_order`` is False, the simulation is more physically realistic, but when using the simulation for annealing, often it is better to have ``in_order = True``. seed : number (optional, defaults to None). The number to seed Python's builtin ``random`` module with. If ``seed is None``, then ``random.seed`` will not be called. Returns ------- res : qubovert.sim.AnnealResults object. ``res`` contains information on the final states of the simulations. See Examples below for an example of how to read from ``res``. See ``help(qubovert.sim.AnnealResults)`` for more info. Raises ------ ValueError If the ``schedule`` argument provided is formatted incorrectly. See the Parameters section. ValueError If the initial temperature is less than the final temperature. ValueError If ``L`` is not degree 2 or less. Warns ----- qubovert.utils.QUBOVertWarning If both the ``temperature_range`` and explicit ``schedule`` arguments are provided. Example ------- Consider the example of finding the ground state of the 1D antiferromagnetic Ising chain of length 5. >>> import qubovert as qv >>> >>> H = sum(qv.spin_var(i) * qv.spin_var(i+1) for i in range(4)) >>> anneal_res = qv.sim.anneal_quso(H, num_anneals=3) >>> >>> print(anneal_res.best.value) -4 >>> print(anneal_res.best.state) {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} >>> # now sort the results >>> anneal_res.sort() >>> >>> # now iterate through all of the results in the sorted order >>> for res in anneal_res: >>> print(res.value, res.state) -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} -4, {0: -1, 1: 1, 2: -1, 3: 1, 4: -1} -4, {0: 1, 1: -1, 2: 1, 3: -1, 4: 1} """ if num_anneals <= 0: return AnnealResults() Ts = _create_spin_schedule( L, anneal_duration, temperature_range, schedule ) # must use type since we don't want errors from inheritance if type(L) == QUSOMatrix: N = L.max_index + 1 model = L reverse_mapping = dict(enumerate(range(N))) # mapping = reverse_mapping elif type(L) != QUSO: L = QUSO(L) if type(L) == QUSO: N = L.num_binary_variables model = L.to_quso() # mapping = L.mapping reverse_mapping = L.reverse_mapping # solve `model`, convert solutions back to `L` if not N: return AnnealResults( AnnealResult({}, model.offset, True) for _ in range(num_anneals) ) if initial_state is not None: init_state = [1] * N for k, v in reverse_mapping.items(): init_state[k] = initial_state[v] else: init_state = [] # create arguments for the C function h, num_neighbors = [0.] * N, [0] * N neighbors, J = [[] for _ in range(N)], [[] for _ in range(N)] for k, v in model.items(): val = float(v) if len(k) == 1: h[k[0]] = val elif len(k) == 2: i, j = k neighbors[i].append(j) neighbors[j].append(i) num_neighbors[i] += 1 num_neighbors[j] += 1 J[i].append(val) J[j].append(val) # flatten the arrays. J, neighbors = list(chain(*J)), list(chain(*neighbors)) states, values = c_anneal_quso( h, num_neighbors, neighbors, J, # describe the problem Ts, num_anneals, int(in_order), init_state, # describe the algorithm seed if seed is not None else -1 ) return _package_spin_results( states, values, model.offset, reverse_mapping )
def test_qusosimulation_set_state(): ising = sum(-spin_var(i) * spin_var(i + 1) for i in range(9)) sim = QUSOSimulation(ising) assert sim.state == {i: 1 for i in ising.variables} sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) assert sim.state == {i: -1 for i in ising.variables} with assert_raises(ValueError): sim.set_state({i: 3 for i in ising.variables}) with assert_raises(ValueError): QUSOSimulation(ising, {i: 0 for i in ising.variables}) sim = QUSOSimulation({(0, ): 1}) with assert_raises(ValueError): sim.set_state([0]) sim.set_state([-1]) assert sim.state == {0: -1} # test the same but with matrix ising = QUSOMatrix(sum(-spin_var(i) * spin_var(i + 1) for i in range(9))) sim = QUSOSimulation(ising) assert sim.state == {i: 1 for i in ising.variables} sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) assert sim.state == {i: -1 for i in ising.variables} with assert_raises(ValueError): sim.set_state({i: 3 for i in ising.variables}) with assert_raises(ValueError): QUSOSimulation(ising, {i: 0 for i in ising.variables}) sim = QUSOSimulation({(0, ): 1}) with assert_raises(ValueError): sim.set_state([0]) sim.set_state([-1]) assert sim.state == {0: -1} # test the same for QUSO ising = QUSO(sum(-spin_var(i) * spin_var(i + 1) for i in range(9))) sim = QUSOSimulation(ising) assert sim.state == {i: 1 for i in ising.variables} sim = QUSOSimulation(ising, {i: -1 for i in ising.variables}) assert sim.state == {i: -1 for i in ising.variables} with assert_raises(ValueError): sim.set_state({i: 3 for i in ising.variables}) with assert_raises(ValueError): QUSOSimulation(ising, {i: 0 for i in ising.variables}) sim = QUSOSimulation({(0, ): 1}) with assert_raises(ValueError): sim.set_state([0]) sim.set_state([-1]) assert sim.state == {0: -1}
def __init__(self, L, initial_state=None): """__init__. Parameters ---------- L : dict, ``qubovert.utils.QUSOMatrix``, or ``qubovert.QUSO`` object. The QUSO to simulate. This should map tuples of spin variable labels to their respective coefficient in the Hamiltonian. For more information, see the docstrings for ``qubovert.utils.QUSOMatrix`` and ``qubovert.QUSO``. initial_state : dict (optional, defaults to None). The initial state to start the simulation in. ``initial_state`` should map spin label names to their initial values, where each value is either 1 or -1. If ``initial_state`` is None, then it will be initialized to all 1s. """ # must use type since we don't want errors from inheritance if type(L) == QUSOMatrix: N = L.max_index + 1 model = L self._mapping = dict(enumerate(range(N))) self._reverse_mapping = self._mapping self._variables = set(self._mapping.keys()) elif type(L) != QUSO: L = QUSO(L) if type(L) == QUSO: N = L.num_binary_variables model = L.to_quso() self._mapping = L.mapping self._reverse_mapping = L.reverse_mapping self._variables = L.variables self._initial_state = (initial_state.copy() if initial_state is not None else {v: 1 for v in self._variables}) self._state = [1] * N self.set_state(self._initial_state) # C arguments # create model arrays h, num_neighbors = [0.] * N, [0] * N neighbors, J = [[] for _ in range(N)], [[] for _ in range(N)] for k, v in model.items(): val = float(v) if len(k) == 1: h[k[0]] = val elif len(k) == 2: i, j = k neighbors[i].append(j) neighbors[j].append(i) num_neighbors[i] += 1 num_neighbors[j] += 1 J[i].append(val) J[j].append(val) # ignore offset. # flatten the arrays. J, neighbors = list(chain(*J)), list(chain(*neighbors)) self._c_args = h, num_neighbors, neighbors, J
def test_create_var(): d = QUSO.create_var(0) assert d == {(0,): 1} assert d.name == 0 assert type(d) == QUSO d = QUSO.create_var('x') assert d == {('x',): 1} assert d.name == 'x' assert type(d) == QUSO problem = QUSO({('a',): -1, ('b',): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2}) solution = {'c': -1, 'b': -1, 'a': -1} def test_quso_qubo_solve(): e, sols = solve_qubo_bruteforce(problem.to_qubo()) sol = problem.convert_solution(sols, False) assert problem.is_solution_valid(sol) assert problem.is_solution_valid(sols) assert sol == solution assert allclose(e, -10) def test_quso_quso_solve():
def test_to_enumerated(): d = QUSO({('a', 'b'): 1, ('a',): 2}) dt = d.to_enumerated() assert type(dt) == QUSOMatrix assert dt == d.to_quso()
def test_quso_remove_value_when_zero(): d = QUSO() d[(0, 1)] += 1 d[(0, 1)] -= 1 assert d == {}
def test_quso_reinitialize_dictionary(): d = QUSO({(0, 0): 1, ('1', 0): 2, (2, 0): 0, (0, '1'): 1}) assert d in ({(): 1, ('1', 0): 3}, {(): 1, (0, '1'): 3})
def test_quso_num_binary_variables(): d = QUSO({(0,): 1, (0, 3): 2}) assert d.num_binary_variables == 2 assert d.max_index == 1
def test_num_terms(): d = QUSO({(0,): 1, (0, 3): 2, (0, 2): -1}) assert d.num_terms == len(d)
def test_quso_multiplication(): temp = QUSO({('0', '0'): 1, ('0', 1): 2}) temp1 = {(): 3, (1, '0'): 6}, {(): 3, ('0', 1): 6} temp2 = {(): .5, (1, '0'): 1}, {(): .5, ('0', 1): 1} temp3 = {(1, '0'): 1}, {('0', 1): 1} # constant d = temp.copy() d += 3 d *= -2 assert d in ({(1, '0'): -4, (): -8}, {('0', 1): -4, (): -8}) # __mul__ d = temp.copy() g = d * 3 assert g in temp1 d = temp.copy() g = d * 0 assert g == {} # __imul__ d = temp.copy() d *= 3 assert d in temp1 d = temp.copy() d *= 0 assert d == {} # __rmul__ d = temp.copy() g = 3 * d assert g in temp1 d = temp.copy() g = 0 * d assert g == {} # __truediv__ d = temp.copy() g = d / 2 assert g in temp2 # __itruediv__ d = temp.copy() d /= 2 assert d in temp2 # __floordiv__ d = temp.copy() g = d // 2 assert g in temp3 # __ifloordiv__ d = temp.copy() d //= 2 assert d in temp3 # __mul__ but with dict d = temp.copy() d *= {(1,): 2, ('0', '0'): -1} assert d in ({(1,): 2, (): -1, ('0',): 4, ('0', 1): -2}, {(1,): 2, (): -1, ('0',): 4, (1, '0'): -2}) # __pow__ d = temp.copy() d -= 2 d **= 2 assert d in ({(): 5, ('0', 1): -4}, {(): 5, (1, '0'): -4}) d = temp.copy() assert d ** 2 == d * d assert d ** 3 == d * d * d # should raise a KeyError since can't fit this into QUSO. with assert_raises(KeyError): QUSO({('0', 1): 1, ('1', 2): -1})**2