def test_puso_quso_equal(): random.seed(321) quso = {(i, j): random.random() for i in range(7) for j in range(7)} quso.update({(i, ): random.random() for i in range(7)}) quso[()] = random.random() for sol in itertools.product((-1, 1), repeat=7): assert_allclose(puso_value(sol, quso), quso_value(sol, quso))
def test_pubo_puso_equal(): random.seed(518) pubo = {(i, j, k): random.random() for i in range(7) for j in range(7) for k in range(7)} pubo.update({(i, j): random.random() for i in range(7) for j in range(7)}) pubo.update({(i, ): random.random() for i in range(7)}) pubo[()] = random.random() for sol in itertools.product((0, 1), repeat=7): assert_allclose(pubo_value(sol, pubo), puso_value(boolean_to_spin(sol), pubo_to_puso(pubo)))
def schedule_update(self, schedule, in_order=False, seed=None): """schedule_update. Update the simulation with a schedule. Parameters ---------- schedule : iterable of tuples. Each element in ``schedule`` is a pair ``(T, n)`` which designates a temperature and a number of updates. See `Notes` below. in_order : bool (optional, defaults to False). 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 ``random`` with. If ``seed is None``, then ``random.seed`` will not be called. Notes ----- The following two code blocks perform exactly the same thing. >>> sim = PUSOSimulation(10) >>> for T in (3, 2): >>> sim.update(T, 100) >>> sim.update(1, 50) >>> sim = PUSOSimulation(10) >>> schedule = (3, 100), (2, 100), (1, 50) >>> sim.schedule_update(schedule) """ if seed is not None: random.seed(seed) for T, n in schedule: if n < 0: raise ValueError("Cannot update a negative number of times") for _ in range(n): self._add_past_state() vars_to_update = (self._variables if in_order else random.choices(self._variables, k=len(self._variables))) for i in vars_to_update: # the change in energy from flipping variable i is equal # to -2 * (the energy of the subgraph depending on i) dE = -2 * puso_value(self._state, self._subgraphs[i]) if dE <= 0 or (T and random.random() < exp(-dE / T)): self._state[i] *= -1
def runtests(self): assert self.problem.solve_bruteforce() == self.solution e, sol = solve_qubo_bruteforce(self.problem.to_qubo()) assert self.is_valid(e, sol, False) e, sol = solve_quso_bruteforce(self.problem.to_quso()) assert self.is_valid(e, sol, True) for deg in (None, ) + tuple(range(2, self.problem.degree + 1)): e, sol = solve_puso_bruteforce(self.problem.to_puso(deg)) assert self.is_valid(e, sol, True) e, sol = solve_pubo_bruteforce(self.problem.to_pubo(deg)) assert self.is_valid(e, sol, False) assert (self.problem.value(self.solution) == puso_value( self.solution, self.problem) == e)
def _anneal_spin(model, spin_simulation, num_anneals=1, anneal_duration=1000, initial_state=None, temperature_range=None, schedule='geometric', in_order=True, seed=None): """_anneal_spin. Run a simulated annealing algorithm to try to find the minimum of the spin model given by ``model``. ``_anneal_spin`` uses a cooling schedule with the ``spin_simulation`` object. Please see all of the parameters for details. Both ``qv.sim.anneal_puso`` and ``qv.sim.anneal_quso`` run through this function. Since ``qv.sim.QUSOSimulation`` is faster than ``qv.sim.PUSOSimulation``, we send in different simulation objects for ``anneal_quso`` and ``anneal_puso``. Parameters ---------- model : 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. spin_simulation : qv.sim.PUSOSimulation or qv.sim.QUSOSimulation object. Should be a ``qv.sim.QUSOSimulation`` object if this function is called from ``qv.sim.anneal_quso``, or a ``qv.sim.PUSOSimulation`` object if this function is called from ``qv.sim.anneal_puso``. 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 iterable of tuple (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, you can supply an explicit schedule. In this case, ``schedule`` should be an iterable of tuples, where each tuple is a ``(T, n)`` pair, where ``T`` denotes the temperature to update the simulation, and ``n`` denote the number of times to update the simulation at that temperature. This schedule will be sent directly into the ``qubovert.sim.PUSOSimulation.schedule_update`` method. 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. """ if seed is not None: random.seed(seed) if schedule in ('linear', 'geometric'): T0, Tf = temperature_range or anneal_temperature_range(model, spin=True) if T0 < Tf: raise ValueError("The final temperature must be less than the " "initial temperature") # in the case that H is empty or just an offset and the user didn't # supply a temperature range, then T0 and Tf will be 0. if temperature_range is None and T0 == Tf == 0: T0 = Tf = 1 Ts = ( np.linspace(T0, Tf, anneal_duration) if schedule == 'linear' else np.geomspace(T0, Tf, anneal_duration) ) schedule = tuple((T, 1) for T in Ts) elif isinstance(schedule, str): raise ValueError( "Invalid schedule. Must be either 'linear', 'geometric', or an " "explicit temperature schedule. See the docstring for more info." ) elif temperature_range: QUBOVertWarning.warn( "Both a temperature range and an explicit schedule was provided. " "The temperature range will be ignored and the schedule used " "instead." ) sim = spin_simulation(model, initial_state) result = AnnealResults(True) for _ in range(num_anneals): if initial_state is None: sim.set_state({v: random.choice((-1, 1)) for v in sim._variables}) sim.schedule_update( schedule, in_order=in_order, seed=random.randint(0, 1 << 16) if seed is not None else None ) state = sim.state result.add_state(state, puso_value(state, model)) sim.reset() return result