def test_puso_to_pubo_to_puso(): puso = { (0, 1): -4, (0, 2): 3, (): -2, (0, ): 1, (2, ): -2, (0, 1, 2): 3, (0, 2, 3): -1 } assert puso == pubo_to_puso(puso_to_pubo(puso)) # type asserting assert type(puso_to_pubo(puso)) == PUBO assert type(puso_to_pubo(PUSOMatrix(puso))) == PUBOMatrix assert type(puso_to_pubo(PUSO(puso))) == PUBO puso = { ('0', 1): -4, ('0', '2'): 3, (): -2, ('0', ): 1, ('2', ): -2, ('0', 1, '2'): 3, ('0', '2', 3): -1, ('2', 0, 0, '1', 0): -2, (0, 1, 1, 0, 3, 0, 1, 1, 3, 2, 3): -8 } # need to reformat qubo so it is sorted with the same hash assert PUSO(puso) == pubo_to_puso(puso_to_pubo(puso)) # type asserting assert type(puso_to_pubo(puso)) == PUBO assert type(puso_to_pubo(PUSO(puso))) == PUBO
def test_puso_addition(): temp = PUSO({('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 == PUSO(temp3[0]) * -1
def test_set_mapping(): d = PUSO({('a', 'b'): 1, ('a', ): 2}) d.set_mapping({'a': 0, 'b': 2}) assert d.to_puso() == {(0, 2): 1, (0, ): 2} d = PUSO({('a', 'b'): 1, ('a', ): 2}) d.set_reverse_mapping({0: 'a', 2: 'b'}) assert d.to_puso() == {(0, 2): 1, (0, ): 2}
def test_symbols(): a, b = Symbol('a'), Symbol('b') d = PUSO() 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_create_var(): d = PUSO.create_var(0) assert d == {(0, ): 1} assert d.name == 0 assert type(d) == PUSO d = PUSO.create_var('x') assert d == {('x', ): 1} assert d.name == 'x' assert type(d) == PUSO
def test_puso_default_valid(): d = PUSO() assert d[(0, 0)] == 0 d[(0, 0)] += 1 assert d == {(): 1} d = PUSO() assert d[(0, 1)] == 0 d[(0, 1)] += 1 assert d == {(0, 1): 1}
def test_round(): d = PUSO({(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_normalize(): temp = {(0, ): 4, (1, ): -2} d = PUSO(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()} temp = {(0, ): -4, (1, ): 2} d = PUSO(temp) d.normalize() assert d == {k: v / 4 for k, v in temp.items()}
def test_puso_update(): d = PUSO({('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 = PUSO({(0, 0): 1, (0, 1): 2}) d.update(PUSO({(1, 0): 1, (1, 1): -1})) d -= 1 assert d == PUSO({(0, 1): 1, (): -2}) assert d.offset == -2
def test_convert_solution_all_1s(): d = PUSO({(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_puso_on_quso(): problem = PUSO({ ('a', ): -1, ('b', ): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2 }) solution = {'c': -1, 'b': -1, 'a': -1} obj = -10 Problem(problem, solution, obj).runtests()
def test_puso_degree(): d = PUSO() 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 d[(1, 2, 4)] -= 2 assert d.degree == 3 d[(1, 2, 4, 5, 6)] += 2 assert d.degree == 5
def test_puso_on_deg_3_puso(): problem = PUSO({ ('a', ): -1, ('b', ): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, (0, 1, 2): 1, (0, ): 1, (1, ): 1, (2, ): 1 }) solution = {'c': -1, 'b': -1, 'a': -1, 0: -1, 1: -1, 2: -1} obj = -14 Problem(problem, solution, obj).runtests()
def test_pretty_str(): def equal(expression, string): assert expression.pretty_str() == string z = [PUSO() + {(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 test_puso_on_deg_5_puso(): problem = PUSO({ ('a', ): -1, ('b', ): 2, ('a', 'b'): -3, ('b', 'c'): -4, (): -2, (0, 1, 2): 1, (0, ): -1, (1, ): -2, (2, ): 1, ('a', 0, 4, 'b', 'c'): -3, (4, 2, 3, 'a', 'b'): 2, (4, 2, 3, 'b'): -1, ('c', ): 4, (3, ): 1 }) solution = {0: 1, 1: 1, 'c': -1, 2: -1, 4: -1, 3: -1, 'b': -1, 'a': -1} obj = -26 Problem(problem, solution, obj).runtests()
def anneal_puso(H, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None): """anneal_puso. Run a simulated annealing algorithm to try to find the minimum of the PUSO given by ``H``. Please see all of the parameters for details. **Please note** that the ``qv.sim.anneal_quso`` function performs faster than the ``qv.sim.anneal_puso`` function. If your system has degree 2 or less, then you should use the ``qv.sim.anneal_quso`` function. Parameters ---------- H : dict, or any type in ``qubovert.SPIN_MODELS``. Maps spin labels to their values in the Hamiltonian. Please see the docstrings of any of the objects in ``qubovert.SPIN_MODELS`` to see how ``H`` should be formatted. 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(H, spin=True)``. Note that a temperature can only be zero if ``schedule`` is explicitly given or if ``schedule`` is linear. 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. Warns ----- qubovert.utils.QUBOVertWarning If both the ``temperature_range`` and explicit ``schedule`` arguments are provided. qubovert.utils.QUBOVertWarning If the degree of the model is 2 or less then a warning is issued that says you should use the ``anneal_qubo`` or ``anneal_quso`` functions. 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_puso(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( H, anneal_duration, temperature_range, schedule ) # must use type since we don't want errors from inheritance if type(H) in (QUSOMatrix, PUSOMatrix): N = H.max_index + 1 model = H reverse_mapping = dict(enumerate(range(N))) elif type(H) not in (QUSO, PUSO, PCSO): H = PUSO(H) if type(H) in (QUSO, PUSO, PCSO): N = H.num_binary_variables model = H.to_puso() reverse_mapping = H.reverse_mapping if model.degree <= 2: QUBOVertWarning.warn( "The input problem has degree <= 2; consider using the " "``qubovert.sim.anneal_qubo`` or ``qubovert.sim.anneal_quso`` " "functions, which are significantly faster than this function " "because they take advantage of the low degree." ) # solve `model`, convert solutions back to `H` 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 # create terms and couplings terms, couplings, num_couplings = [], [], [] for term, coupling in model.items(): if term: couplings.append(float(coupling)) terms.extend(term) num_couplings.append(len(term)) states, values = c_anneal_puso( N, num_couplings, terms, couplings, # 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_to_enumerated(): d = PUSO({('a', 'b'): 1, ('a', ): 2}) dt = d.to_enumerated() assert type(dt) == PUSOMatrix assert dt == d.to_puso()
def test_puso_degree_reduction_lam(): puso = PUSO({ ('x0', 'x1'): -1, ('x1', ): 1, ('x1', 'x2'): -1, ('x2', ): 1, ('x3', 'x2'): -1, ('x3', ): 1, ('x4', 'x3'): -1, ('x4', ): 1, })**2 # just make sure it runs puso.to_qubo(lam=4) puso.to_qubo(lam=lambda v: v) puso.to_qubo(lam=Symbol('lam')) puso.to_qubo(lam=lambda v: v * Symbol('lam')) puso.to_quso(lam=4) puso.to_quso(lam=lambda v: v) puso.to_quso(lam=Symbol('lam')) puso.to_quso(lam=lambda v: v * Symbol('lam'))
def test_puso_remove_value_when_zero(): d = PUSO() d[(0, 1)] += 1 d[(0, 1)] -= 1 assert d == {}
def test_puso_checkkey(): with assert_raises(KeyError): PUSO({0: -1})
def test_puso_reinitialize_dictionary(): d = PUSO({(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_puso_num_binary_variables(): d = PUSO({(0, ): 1, (0, 3): 2}) assert d.num_binary_variables == 2 assert d.max_index == 1
def test_num_terms(): d = PUSO({(0, ): 1, (0, 3): 2, (0, 2): -1}) assert d.num_terms == len(d)
def test_puso_multiplication(): temp = PUSO({('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 d = PUSO({('0', 1): 1, ('1', 2): -1})**2 assert d**4 == d * d * d * d
def test_properties(): temp = PUSO({('0', '0'): 1, ('0', 1): 2}) assert temp.offset == 1 d = PUSO() 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 = PUSO() d.set_mapping({0: 0}) d[(0, )] += 1 assert d.num_binary_variables == 1 assert d.variables == {0}