n_sweeps = 10000 n_replicas = 10 n_iterations = 10 # replicas are initialized with random samples state = hybrid.State.from_problem(bqm) replicas = hybrid.States(*[state.updated() for _ in range(n_replicas)]) # get a reasonable beta range beta_hot, beta_cold = neal.default_beta_range(bqm) # generate betas for all branches/replicas betas = np.geomspace(beta_hot, beta_cold, n_replicas) # run replicas update/swap for n_iterations # (after each update/sampling step, do n_replicas-1 swap operations) update = hybrid.Branches( *[FixedTemperatureSampler(beta, num_sweeps=n_sweeps) for beta in betas]) swap = SwapReplicasSweepDown(betas) workflow = hybrid.Loop(update | swap, max_iter=n_iterations) \ | hybrid.MergeSamples(aggregate=True) solution = workflow.run(replicas).result() # show execution profile hybrid.profiling.print_counters(workflow) # show results print("Solution: sample={0.samples.first}, energy={0.samples.first.energy}". format(solution))
replicas = hybrid.States(*[state.updated() for _ in range(n_replicas)]) # get a reasonable beta range beta_hot, beta_cold = neal.default_beta_range(bqm) # generate betas for all branches/replicas betas = np.geomspace(beta_hot, beta_cold, n_replicas) # QPU branch: limits the PT workflow to QPU-sized problems qpu = hybrid.IdentityDecomposer() | hybrid.QPUSubproblemAutoEmbeddingSampler( ) | hybrid.IdentityComposer() # use QPU as the hottest temperature sampler and `n_replicas-1` fixed-temperature-samplers update = hybrid.Branches( qpu, *[ FixedTemperatureSampler(beta=beta, num_sweeps=n_sweeps) for beta in betas[1:] ]) # swap step is `n_replicas-1` pairwise potential swaps swap = SwapReplicasDownsweep(betas=betas) # we'll run update/swap sequence for `n_iterations` workflow = hybrid.Loop(update | swap, max_iter=n_iterations) \ | hybrid.MergeSamples(aggregate=True) # execute the workflow solution = workflow.run(replicas).result() # show execution profile hybrid.profiling.print_counters(workflow)
def HybridizedParallelTempering(num_sweeps=10000, num_replicas=10, max_iter=None, max_time=None, convergence=3): """Parallel tempering workflow generator. Args: num_sweeps (int, optional): Number of sweeps in the fixed temperature sampling. num_replicas (int, optional): Number of replicas (parallel states / workflow branches). max_iter (int/None, optional): Maximum number of iterations of the update/swaps loop. max_time (int/None, optional): Maximum wall clock runtime (in seconds) allowed in the update/swaps loop. convergence (int/None, optional): Number of times best energy of the coldest replica has to repeat before we terminate. Returns: Workflow (:class:`~hybrid.core.Runnable` instance). """ # expand single input state into `num_replicas` replica states preprocess = SpawnParallelTemperingReplicas(num_replicas=num_replicas) # QPU branch: limits the PT workflow to QPU-sized problems qpu = (hybrid.IdentityDecomposer() | hybrid.QPUSubproblemAutoEmbeddingSampler() | hybrid.IdentityComposer()) # use QPU as the hottest temperature sampler and `num_replicas-1` fixed-temperature-samplers update = hybrid.Branches( qpu, *[ FixedTemperatureSampler(num_sweeps=num_sweeps) for _ in range(num_replicas - 1) ]) # replica exchange step: do the top-down sweep over adjacent pairs # (good hot samples sink to bottom) swap = SwapReplicasDownsweep() # loop termination key function def key(states): if states is not None: return states[-1].samples.first.energy # replicas update/swap until Loop termination criteria reached loop = hybrid.Loop(update | swap, max_iter=max_iter, max_time=max_time, convergence=convergence, key=key) # collapse all replicas (although the bottom one should be the best) postprocess = hybrid.MergeSamples(aggregate=True) workflow = preprocess | loop | postprocess return workflow