Parallel tempering

Parallel tempering is a method to improve the statistics and ergodicity of a Markov-chain Monte Carlo simulation by running multiple copies with different parameters in parallel and allowing updates that exchange Monte Carlo configurations between the different parameters. A classical example would be to simulate a glassy model at different temperatures so that decorrelated high-temperature configurations continuously enter the low-temperature region and sample the glassy configuration space efficiently.

Carlo.jl provides an implementation of parallel tempering through a “meta” implementation of the AbstractMC interface, ParallelTemperingMC. It is a Monte Carlo algorithm that can run any other AbstractMC implementation with parallel tempering.

In order to work with parallel tempering, the child algorithm only has to implement the following two methods.

Carlo.parallel_tempering_log_weight_ratioFunction
Carlo.parallel_tempering_log_weight_ratio(mc::YourMC, parameter_name::Symbol, new_value)

Let $W(x, p)$ be the the weight of the Monte Carlo configuration $x$ at the current value of the parameter $p$ (specified by parameter_name), and $W(x,p')$ be the weight after the parameter has been changed to new_value.

This function then returns $\log W(x,p')/W(x,p)$.

source
Carlo.parallel_tempering_change_parameter!Function
Carlo.parallel_tempering_change_parameter!(mc::YourMC, parameter_name::Symbol, new_value)

During a parallel tempering simulation, changes the parameter named parameter_name to new_value and performs all necessary updates to the internal structure of YourMC.

source

The algorithm works by orchestrating a number of Monte Carlo processes along a chain of parameter values. Alternatingly, all adjacent even and odd pairs compare their configuration weights and propose a switch of their configurations. If the switch is accepted, the Monte Carlo processes exchange their positions on the chain.

Configuration

When running a simulation, ParallelTemperingMC is configured through the parallel_tempering task parameter

using Carlo
using Carlo.JobTools

num_steps = 10

tm = TaskMaker()

tm.parallel_tempering = (
    mc = YourMC,
    parameter = :T,
    values = range(1, 2, num_steps),
    interval = 20,
)

tm.sweeps = 10000
tm.thermalization = 1000
tm.binsize = 100
# [set other parameters here]

task(tm)

job = JobInfo(
    "my_job",
    ParallelTemperingMC;
    checkpoint_time = "45:00",
    run_time = "24:00:00",
    tasks = make_tasks(tm),
    ranks_per_run = num_steps,
)
JobInfo("my_job", "my_job.data", ParallelTemperingMC, Random.Xoshiro, TaskInfo[TaskInfo("task0001", Dict{Symbol, Any}(:thermalization => 1000, :binsize => 100, :sweeps => 10000, :parallel_tempering => (mc = Main.YourMC, parameter = :T, values = 1.0:0.1111111111111111:2.0, interval = 20)))], Dates.Second(2700), Dates.Second(86400), 10)

Here, mc is the type of the child Monte Carlo algorithm that should be run with parallel tempering. parameter is the name of the parameter that should be exchanged in the parallel tempering and values is a chain of values along which exchanges take place. Care should be taken that the probability distributions for adjacent values have some overlap. interval sets the number of Monte Carlo sweeps between a tempering update. A single task set up like that will simulate the entire chain of parameter values at once. It is also possible to simulate multiple independent chains by creating multiple tasks.

In JobInfo your Monte Carlo type is then replaced by ParallelTemperingMC. Under the hood, ParallelTemperingMC runs in parallel run mode and ranks_per_run has to be set to the number of parameters in the tempering chain. The simulation then has to be run with MPI and the appropriate number of ranks, n * ranks_per_run + 1.

Note

At the moment, the child Monte Carlo algorithm can only run in single run mode. However, lifting this limitation should not be too hard. If you intend to run parallel tempering on a parallel run mode code, open an issue on GitHub.

Results

Each observable and evaluable that is calculated by the child code is stacked into a vector where each entry corresponds to a parameter value in the parallel tempering chain. For example, if the child code in the above example calculates the energy, the output for it will be a vector with num_steps values corresponding to the different temperatures in tm.parallel_tempering.values. Observables which are already vectors or higher order arrays in the child code will gain a new last dimension which corresponds to the parallel tempering parameter.

Additionally, the observable ParallelTemperingPermutation records the permutation of configurations on the parallel tempering chain. This can be used to gauge the ergodicity of the parallel tempering updates.

Another self-contained example of parallel tempering is contained in the Ising reference implementation.