Benchmarking Multiphase Monte Carlo Sampling#
Motivation#
As a methods researcher one is often interested in testing new methods and benchmarking them together with state-of-the-art algorithms. hopsy provides the framework for conducting such comparisons, because it allows the methods researcher to focus on the algorithms, while the scaffolding, i.e., I/O, convergence diagnostics, and state-of-the-art implementations of existing algorithms are provided.
We show in this notebook how one can adapt build a multiphase monte carlo sampling plugin for hopsy
Preimplemented Multiphase sampling#
The multiphase Monte Carlo algorithm is also available preimplemented in hopsy, see MultiphaseMonteCarlo.ipynb for a short example
Background#
Instead of rounding before sampling, multiphase monte carlo sampling rounds adaptively based on the samples of the Markov chain during a run. It has been reported to be an efficient strategy, if the underlying Markov chain is efficient enough.
The Idea has found previous adaption in the artificially centered coordinate hit-and-run and the optGP sampler. However, Coordinate Hit-and-Run Rounded showed greater and more stable performance (https://pubmed.ncbi.nlm.nih.gov/28158334/) than both of those algorithms.
The idea is resurrected and shown to be efficient given a better Markov chain implementation. See https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf
[1]:
import hopsy
from matplotlib import pyplot as plt
import numpy as np
from typing import List
import time
import PolyRound
Custom proposals#
In case the proposal you want to benchmark is not already implemented, it is possible to implement it yourself in a small class. It is then possible to seamlessy integrate this proposal into existing workflows with hopsy. As an example, we implement Overrelaxed Hit-and-Run, see https://ui.adsabs.harvard.edu/abs/2015IJMPC..2650010D/abstract
[2]:
class OverrelaxedHitAndRunProposal:
def __init__(
self, problem: hopsy.Problem, starting_point: np.ndarray, epsilon: float = 1e-10
):
self.A = problem.A
self.b = problem.b
self.state = starting_point
self.proposal = self.state
self.epsilon = epsilon
def propose(self, rng) -> np.ndarray:
"""This method is called by hopsy.MarkovChain. Returns the proposal"""
direction = np.random.randn(self.state.shape[0])
direction /= np.linalg.norm(direction, axis=0)
inverse_distances = np.nan_to_num(
np.divide(self.A @ direction, self.b - self.A @ self.state)
)
forward_distance = 1.0 / np.max(inverse_distances)
backward_distance = 1.0 / np.min(inverse_distances)
L = forward_distance - backward_distance
a = 2 * (-backward_distance) / L - 1
if a == 0:
a = self.epsilon
t_1 = (
L * (1 + a - np.sqrt((1 + a) ** 2 - 4 * a * np.random.uniform())) / (2 * a)
)
t_0 = -backward_distance
self.proposal = self.state + (t_1 - t_0) * direction
return self.proposal
More complex custom sampling schemes#
In addition to custom proposals, it is also possible to implement adaptive workflows. In this case we implement the adaptive rounding from https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf using the Billiard walk that is already implemented in hopsy.
[3]:
# rounding based on samples, as suggested in https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf
def svd_rounding(samples, polytope):
# We concatenate them samples to [n_dim, n_iterations] for rounding
stacked_samples = np.vstack(
(samples[0, :, :], samples[1, :, :], samples[2, :, :], samples[3, :, :])
)
mean = np.mean(stacked_samples, axis=0)
stacked_samples = stacked_samples - mean
U, S, Vh = np.linalg.svd(stacked_samples)
# Rescaling as mentioned in https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf
s_ratio = np.max(S) / np.min(S)
S = S / np.min(S)
if np.max(S) >= 2.0:
S[np.where(S < 2.0)] = 1.0
else:
S = np.ones(S.shape)
Vh = np.identity(Vh.shape[0])
rounding_matrix = np.transpose(Vh).dot(np.diag(S))
# Transforms current last samples into new polytope
sub_problem = hopsy.Problem(
polytope.A, polytope.b, transformation=rounding_matrix, shift=mean
)
starting_points = hopsy.transform(
sub_problem, [samples[i, -1, :] for i in range(samples.shape[0])]
)
polytope.apply_shift(mean)
polytope.apply_transformation(rounding_matrix)
return s_ratio, starting_points, polytope, sub_problem
Helper functions#
In this section we implement some helper functions for executing the benchmarks as well as a function which constructs our benchmark problems
[4]:
# Helper functions for calling the sampling algorithms and defining the polytopes
def run_multiphase_sampling(
proposal,
polytope,
seeds: List,
target_ess: float,
steps_per_phase: int,
starting_points: List,
):
limit_singular_ratio_value = 2.3 # from https://drops.dagstuhl.de/opus/volltexte/2021/13820/pdf/LIPIcs-SoCG-2021-21.pdf
assert len(starting_points) == len(seeds)
rngs = [hopsy.RandomNumberGenerator(s) for s in seeds]
ess = 0
iterations = 0
s_ratio = limit_singular_ratio_value + 1
sampling_time = 0
samples = None
last_iteration_did_rounding = True
while ess < target_ess:
iterations += 1
print("\t\titer:", iterations)
internal_polytope = polytope
p = hopsy.Problem(internal_polytope.A, internal_polytope.b)
markov_chains = [
hopsy.MarkovChain(proposal=proposal, problem=p, starting_point=s)
for s in starting_points
]
start = time.time()
acceptance_rate, _samples = hopsy.sample(
markov_chains, rngs, n_samples=steps_per_phase, thinning=1, n_procs=1
)
end = time.time()
assert 0 < np.min(p.b - p.A @ _samples[0, -1, :])
assert 0 < np.min(p.b - p.A @ _samples[1, -1, :])
assert 0 < np.min(p.b - p.A @ _samples[2, -1, :])
assert 0 < np.min(p.b - p.A @ _samples[3, -1, :])
sampling_time += end - start
if s_ratio > limit_singular_ratio_value:
samples = _samples
# also measures the rounding time
start = time.time()
s_ratio, starting_points, internal_polytope, sub_problem = svd_rounding(
samples, internal_polytope
)
end = time.time()
sampling_time += end - start
print("\t\ts_ratio:", s_ratio)
last_iteration_did_rounding = True
else:
if last_iteration_did_rounding:
# next operation transforms last samples to current space before concatenating
for j in range(samples.shape[0]):
samples[j] = hopsy.transform(
sub_problem, [samples[j, i, :] for i in range(samples.shape[1])]
)
last_iteration_did_rounding = False
samples = np.concatenate((samples, _samples), axis=1)
starting_points = [samples[i, -1, :] for i in range(samples.shape[0])]
ess = np.min(hopsy.ess(samples))
print("\t\tess", str(ess))
steps_per_phase += 100
# transforms back to full space
_samples = np.zeros(
(samples.shape[0], samples.shape[1], polytope.transformation.shape[0])
)
for j in range(samples.shape[0]):
_samples[j] = hopsy.back_transform(
hopsy.Problem(
A=internal_polytope.A,
b=internal_polytope.b,
transformation=internal_polytope.transformation,
shift=internal_polytope.shift,
),
[samples[j, i, :] for i in range(samples.shape[1])],
)
return _samples, iterations, ess, sampling_time
def run_sampling(
proposal,
polytope,
seeds: List,
target_ess: float,
steps_per_phase: int,
starting_points: List,
thinning=None,
):
rngs = [hopsy.RandomNumberGenerator(s) for s in seeds]
problem = hopsy.Problem(polytope.A, polytope.b)
thinning = (
thinning if thinning is not None else int(float(problem.A.shape[1] ** 2) / 6)
)
ess = 0
iterations = 0
# also measures the rounding time
start = time.time()
rounded_problem = hopsy.round(problem)
# transforms starting points into rounded space
starting_points = hopsy.transform(rounded_problem, starting_points)
end = time.time()
sampling_time = end - start
samples = None
markov_chains = [
hopsy.MarkovChain(problem=rounded_problem, starting_point=s)
for s in starting_points
]
while ess < target_ess:
iterations += 1
print("\t\titer:", iterations)
for i in range(len(starting_points)):
markov_chains[i].proposal = proposal(
rounded_problem, starting_point=starting_points[i]
)
start = time.time()
acceptance_rate, _samples = hopsy.sample(
markov_chains, rngs, n_samples=steps_per_phase, thinning=thinning, n_procs=1
)
end = time.time()
sampling_time += end - start
samples = (
_samples if samples is None else np.concatenate((samples, _samples), axis=1)
)
starting_points = hopsy.transform(rounded_problem, samples[:, -1, :])
ess = np.min(hopsy.ess(samples))
print("\t\tess", str(ess) + ",", "samples", samples.shape[1])
# transforms back to full space
_samples = np.zeros(
(samples.shape[0], samples.shape[1], polytope.transformation.shape[0])
)
for j in range(samples.shape[0]):
_samples[j] = hopsy.back_transform(
hopsy.Problem(
A=polytope.A,
b=polytope.b,
transformation=polytope.transformation,
shift=polytope.shift,
),
[samples[j, i, :] for i in range(samples.shape[1])],
)
return _samples, iterations, ess, sampling_time
def generate_polytope(name):
if name == "BP5":
bp = hopsy.BirkhoffPolytope(5)
polytope = PolyRound.mutable_classes.polytope.Polytope(A=bp.A, b=bp.b)
polytope.normalize()
parameter_names = ["x" + str(c) for c in polytope.A.columns]
return polytope, "5D Birkhoff Polytope", parameter_names
elif name == "e_coli":
original_polytope = PolyRound.static_classes.parse_sbml_stoichiometry.StoichiometryParser.parse_sbml_cobrapy(
"../extern/hops/resources/e_coli_core/e_coli_core.xml"
)
parameter_names = original_polytope.A.columns
polytope = PolyRound.api.PolyRoundApi.simplify_polytope(original_polytope)
polytope = PolyRound.api.PolyRoundApi.transform_polytope(polytope)
polytope.normalize()
return polytope, "e_coli_core", parameter_names
Benchmark parameters#
Next set up the benchmark parameters
[5]:
target_ess = 1000
proposalTypes = {
"Billiard walk": hopsy.BilliardWalkProposal,
"CHRRT": hopsy.UniformCoordinateHitAndRunProposal,
"OHRR": OverrelaxedHitAndRunProposal,
}
seeds = [1, 2, 3, 4]
problems_to_benchmark = ["BP5", "e_coli"]
Benchmarking#
Sample all problems with all sampling schemes, i.e., two for loops.
[6]:
samples = {}
iterations = {}
ess = {}
times = {}
ess_t = {}
parameter_names = {}
for problem_selection in problems_to_benchmark:
print("Benchmarking {problem_selection}")
samples[problem_selection] = {}
iterations[problem_selection] = {}
ess[problem_selection] = {}
times[problem_selection] = {}
ess_t[problem_selection] = {}
for name, p in proposalTypes.items():
print("\tproposal", name)
# resets problem and starting points
preprocessed_polytope, problem_name, parameter_names[problem_selection] = generate_polytope(
problem_selection
)
steps_per_phase = preprocessed_polytope.A.shape[1] * 20
cheby = hopsy.compute_chebyshev_center(
hopsy.Problem(A=preprocessed_polytope.A, b=preprocessed_polytope.b)
).flatten()
starting_points = [cheby for s in seeds]
if name == "Billiard walk":
(
samples[problem_selection][name],
iterations[problem_selection][name],
ess[problem_selection][name],
times[problem_selection][name],
) = run_multiphase_sampling(
proposal=p,
polytope=preprocessed_polytope,
seeds=seeds,
target_ess=target_ess,
steps_per_phase=steps_per_phase,
starting_points=starting_points,
)
else:
thinning = (
1
if name != "CHRRT"
else max(int(float(preprocessed_polytope.A.shape[1] ** 2) / 6), 1)
)
samples[problem_selection][name], iterations[problem_selection][name], ess[problem_selection][name], times[problem_selection][name] = run_sampling(
proposal=p,
polytope=preprocessed_polytope,
seeds=seeds,
target_ess=target_ess,
steps_per_phase=steps_per_phase,
starting_points=starting_points,
thinning=thinning,
)
ess_t[problem_selection][name] = ess[problem_selection][name] / times[problem_selection][name]
# check convergence
for name, s in samples[problem_selection].items():
print("\t" + name, "rhat:", np.max(hopsy.rhat(s)))
print("\tess", ess[problem_selection])
print("\ttimes", times[problem_selection])
print("\tess/t", ess_t[problem_selection])
Benchmarking {problem_selection}
proposal Billiard walk
Set parameter Username
Academic license - for non-commercial use only - expires 2023-11-30
iter: 1
s_ratio: 1.3847452299003395
ess 231.4835868364245
iter: 2
ess 627.6793244654275
iter: 3
ess 990.5612889618237
iter: 4
ess 1583.2881720652492
proposal CHRRT
iter: 1
ess 447.64227280442987, samples 320
iter: 2
ess 863.8258778056024, samples 640
iter: 3
ess 1324.526979068286, samples 960
proposal OHRR
iter: 1
ess 7.066940654698069, samples 320
iter: 2
ess 8.978791384060571, samples 640
iter: 3
ess 10.822526600431221, samples 960
iter: 4
ess 14.644470085574461, samples 1280
iter: 5
ess 18.35148293619599, samples 1600
iter: 6
ess 16.62222639251434, samples 1920
iter: 7
ess 23.833699742670035, samples 2240
iter: 8
ess 41.20876776311011, samples 2560
iter: 9
ess 57.886912910344414, samples 2880
iter: 10
ess 37.382289849801055, samples 3200
iter: 11
ess 62.344564915244966, samples 3520
iter: 12
ess 79.15850821798082, samples 3840
iter: 13
ess 74.36071172518442, samples 4160
iter: 14
ess 62.95342031751828, samples 4480
iter: 15
ess 113.47926517779857, samples 4800
iter: 16
ess 121.72186371924973, samples 5120
iter: 17
ess 113.3492478009126, samples 5440
iter: 18
ess 127.77424100111836, samples 5760
iter: 19
ess 136.9696137423155, samples 6080
iter: 20
ess 144.16940525794976, samples 6400
iter: 21
ess 139.2192539731686, samples 6720
iter: 22
ess 156.53500273176687, samples 7040
iter: 23
ess 175.14217193906293, samples 7360
iter: 24
ess 182.43273042583925, samples 7680
iter: 25
ess 191.5815328961723, samples 8000
iter: 26
ess 210.41931304680313, samples 8320
iter: 27
ess 224.31970085590208, samples 8640
iter: 28
ess 226.62768566905982, samples 8960
iter: 29
ess 237.75902910720876, samples 9280
iter: 30
ess 243.67493216855976, samples 9600
iter: 31
ess 257.3021980390406, samples 9920
iter: 32
ess 273.8199561717506, samples 10240
iter: 33
ess 294.20112361746465, samples 10560
iter: 34
ess 293.48158227555297, samples 10880
iter: 35
ess 300.45975910082853, samples 11200
iter: 36
ess 317.7535136265973, samples 11520
iter: 37
ess 336.3692862259476, samples 11840
iter: 38
ess 331.9082473463359, samples 12160
iter: 39
ess 346.2916329510095, samples 12480
iter: 40
ess 357.96934334556954, samples 12800
iter: 41
ess 363.54227418015665, samples 13120
iter: 42
ess 383.5858067647125, samples 13440
iter: 43
ess 382.0381927501071, samples 13760
iter: 44
ess 401.5143488947238, samples 14080
iter: 45
ess 420.64939968763684, samples 14400
iter: 46
ess 449.08326901564647, samples 14720
iter: 47
ess 452.80046753169904, samples 15040
iter: 48
ess 450.12839321675506, samples 15360
iter: 49
ess 462.2771927927099, samples 15680
iter: 50
ess 461.9902539847381, samples 16000
iter: 51
ess 481.53977928686703, samples 16320
iter: 52
ess 508.6649066292897, samples 16640
iter: 53
ess 504.20807661962795, samples 16960
iter: 54
ess 521.5147013905246, samples 17280
iter: 55
ess 544.7993417778594, samples 17600
iter: 56
ess 535.5840942302711, samples 17920
iter: 57
ess 551.2635026918439, samples 18240
iter: 58
ess 569.2310737012456, samples 18560
iter: 59
ess 588.0058452604753, samples 18880
iter: 60
ess 593.8488910778029, samples 19200
iter: 61
ess 615.6596139884347, samples 19520
iter: 62
ess 635.4306913517988, samples 19840
iter: 63
ess 646.0405907460345, samples 20160
iter: 64
ess 644.4425753035002, samples 20480
iter: 65
ess 646.8043239236227, samples 20800
iter: 66
ess 664.2919147742286, samples 21120
iter: 67
ess 674.3680356181259, samples 21440
iter: 68
ess 680.3837001518382, samples 21760
iter: 69
ess 698.4208197353897, samples 22080
iter: 70
ess 709.8115678575414, samples 22400
iter: 71
ess 722.2190007599912, samples 22720
iter: 72
ess 729.2378725295657, samples 23040
iter: 73
ess 749.2232824472641, samples 23360
iter: 74
ess 750.6074062950678, samples 23680
iter: 75
ess 779.3189674698466, samples 24000
iter: 76
ess 793.5895044612517, samples 24320
iter: 77
ess 788.1310770304416, samples 24640
iter: 78
ess 813.4174519025988, samples 24960
iter: 79
ess 818.7102630963842, samples 25280
iter: 80
ess 825.1911302157964, samples 25600
iter: 81
ess 824.4797274295319, samples 25920
iter: 82
ess 832.4798666785565, samples 26240
iter: 83
ess 834.0921425432099, samples 26560
iter: 84
ess 856.1355113591557, samples 26880
iter: 85
ess 854.4998018509373, samples 27200
iter: 86
ess 870.6301466142817, samples 27520
iter: 87
ess 875.9356221946373, samples 27840
iter: 88
ess 891.1700748102612, samples 28160
iter: 89
ess 903.7960059093062, samples 28480
iter: 90
ess 886.178835961187, samples 28800
iter: 91
ess 867.117458057039, samples 29120
iter: 92
ess 881.9490684846811, samples 29440
iter: 93
ess 890.5241990028626, samples 29760
iter: 94
ess 913.5607895586063, samples 30080
iter: 95
ess 932.2398882121701, samples 30400
iter: 96
ess 951.138292074147, samples 30720
iter: 97
ess 964.61299347187, samples 31040
iter: 98
ess 983.6391765927045, samples 31360
iter: 99
ess 979.9236321935331, samples 31680
iter: 100
ess 1003.9031482727333, samples 32000
Billiard walk rhat: 1.0061219415504485
CHRRT rhat: 1.006207630257062
OHRR rhat: 1.0070773467273284
ess {'Billiard walk': 1583.2881720652492, 'CHRRT': 1324.526979068286, 'OHRR': 1003.9031482727333}
times {'Billiard walk': 0.07052469253540039, 'CHRRT': 1.0217652320861816, 'OHRR': 6.2099902629852295}
ess/t {'Billiard walk': 22450.12512845066, 'CHRRT': 1296.3124379990331, 'OHRR': 161.65937558010646}
Benchmarking {problem_selection}
proposal Billiard walk
iter: 1
s_ratio: 11.963156850679276
ess 4.872243577042705
iter: 2
s_ratio: 23.96780496221293
ess 5.133013163411473
iter: 3
s_ratio: 33.92514821624308
ess 7.03213882586069
iter: 4
s_ratio: 23.480721222145224
ess 21.114165738738418
iter: 5
s_ratio: 2.3472103720655264
ess 652.8113104108883
iter: 6
s_ratio: 2.1462979782508014
ess 957.7149285139645
iter: 7
ess 2013.7358952432155
proposal CHRRT
iter: 1
ess 524.5375595198544, samples 480
iter: 2
ess 1188.850821488119, samples 960
proposal OHRR
iter: 1
ess 6.269631083291744, samples 480
iter: 2
ess 7.323446416265532, samples 960
iter: 3
ess 8.888495302103292, samples 1440
iter: 4
ess 10.891013536977999, samples 1920
iter: 5
ess 10.43496232034734, samples 2400
iter: 6
ess 13.882887827988935, samples 2880
iter: 7
ess 15.49037801963015, samples 3360
iter: 8
ess 28.348358594863743, samples 3840
iter: 9
ess 33.099764817877734, samples 4320
iter: 10
ess 33.0052664057525, samples 4800
iter: 11
ess 32.11942312187279, samples 5280
iter: 12
ess 31.189888275506522, samples 5760
iter: 13
ess 34.00693166294697, samples 6240
iter: 14
ess 28.89547451072042, samples 6720
iter: 15
ess 38.45378494069976, samples 7200
iter: 16
ess 65.2195031632737, samples 7680
iter: 17
ess 76.91470052686886, samples 8160
iter: 18
ess 80.54628640266824, samples 8640
iter: 19
ess 120.38209736486483, samples 9120
iter: 20
ess 117.68114663878684, samples 9600
iter: 21
ess 124.75070422853102, samples 10080
iter: 22
ess 116.34655760555961, samples 10560
iter: 23
ess 124.18592232344665, samples 11040
iter: 24
ess 123.19323837905226, samples 11520
iter: 25
ess 144.46386173862024, samples 12000
iter: 26
ess 148.09968539854086, samples 12480
iter: 27
ess 147.95311563377672, samples 12960
iter: 28
ess 145.98511902334084, samples 13440
iter: 29
ess 158.51895860514378, samples 13920
iter: 30
ess 169.69689177481715, samples 14400
iter: 31
ess 174.49127005693168, samples 14880
iter: 32
ess 180.42261318891497, samples 15360
iter: 33
ess 192.96616757403754, samples 15840
iter: 34
ess 189.6869567126621, samples 16320
iter: 35
ess 185.9907738397535, samples 16800
iter: 36
ess 192.82170148987922, samples 17280
iter: 37
ess 204.65635395883973, samples 17760
iter: 38
ess 207.05551739174868, samples 18240
iter: 39
ess 216.1649396014175, samples 18720
iter: 40
ess 218.65560668780427, samples 19200
iter: 41
ess 224.67365262048054, samples 19680
iter: 42
ess 233.2337269481389, samples 20160
iter: 43
ess 245.04918297452195, samples 20640
iter: 44
ess 253.35676743095638, samples 21120
iter: 45
ess 268.2333892323007, samples 21600
iter: 46
ess 273.180948810766, samples 22080
iter: 47
ess 272.81001761513676, samples 22560
iter: 48
ess 257.916065225228, samples 23040
iter: 49
ess 253.42112849368127, samples 23520
iter: 50
ess 254.7582764932103, samples 24000
iter: 51
ess 258.4069792053966, samples 24480
iter: 52
ess 273.86342243004873, samples 24960
iter: 53
ess 284.17271357899097, samples 25440
iter: 54
ess 297.20253324691765, samples 25920
iter: 55
ess 302.52499273424024, samples 26400
iter: 56
ess 286.611087547339, samples 26880
iter: 57
ess 280.915655001863, samples 27360
iter: 58
ess 288.3164101913765, samples 27840
iter: 59
ess 300.42292722611387, samples 28320
iter: 60
ess 310.9952497255533, samples 28800
iter: 61
ess 313.1100359628566, samples 29280
iter: 62
ess 289.67804817589433, samples 29760
iter: 63
ess 293.2058222243669, samples 30240
iter: 64
ess 313.40643896165045, samples 30720
iter: 65
ess 293.8918695327726, samples 31200
iter: 66
ess 312.2361613765037, samples 31680
iter: 67
ess 322.9219106586677, samples 32160
iter: 68
ess 323.7545394540001, samples 32640
iter: 69
ess 346.32520571689747, samples 33120
iter: 70
ess 345.85424928547764, samples 33600
iter: 71
ess 358.3048911770299, samples 34080
iter: 72
ess 361.084743896345, samples 34560
iter: 73
ess 365.47086073740763, samples 35040
iter: 74
ess 383.39780160630573, samples 35520
iter: 75
ess 392.63094158894984, samples 36000
iter: 76
ess 398.9072807479651, samples 36480
iter: 77
ess 384.2964391944638, samples 36960
iter: 78
ess 384.7667317498958, samples 37440
iter: 79
ess 391.4770094651921, samples 37920
iter: 80
ess 386.190724216923, samples 38400
iter: 81
ess 382.85858663180676, samples 38880
iter: 82
ess 399.76548583077005, samples 39360
iter: 83
ess 406.45084635182735, samples 39840
iter: 84
ess 414.13634315764835, samples 40320
iter: 85
ess 424.17051913321086, samples 40800
iter: 86
ess 431.3602734254215, samples 41280
iter: 87
ess 422.556096368053, samples 41760
iter: 88
ess 432.7105172995434, samples 42240
iter: 89
ess 442.42073630007786, samples 42720
iter: 90
ess 451.6280904320013, samples 43200
iter: 91
ess 454.8288496773367, samples 43680
iter: 92
ess 459.7049865712104, samples 44160
iter: 93
ess 463.64262501125734, samples 44640
iter: 94
ess 454.3284616055022, samples 45120
iter: 95
ess 458.5231384856301, samples 45600
iter: 96
ess 467.2708211331568, samples 46080
iter: 97
ess 483.29399688389447, samples 46560
iter: 98
ess 489.9114233381726, samples 47040
iter: 99
ess 482.25565920814455, samples 47520
iter: 100
ess 488.76061956249976, samples 48000
iter: 101
ess 501.2216755056666, samples 48480
iter: 102
ess 509.151199607025, samples 48960
iter: 103
ess 512.2207523382951, samples 49440
iter: 104
ess 522.8707944154959, samples 49920
iter: 105
ess 530.9119480393842, samples 50400
iter: 106
ess 534.6566181063171, samples 50880
iter: 107
ess 537.3787114531068, samples 51360
iter: 108
ess 549.4299895513893, samples 51840
iter: 109
ess 545.6206413795908, samples 52320
iter: 110
ess 554.906174875836, samples 52800
iter: 111
ess 573.7119220600656, samples 53280
iter: 112
ess 569.4093051480813, samples 53760
iter: 113
ess 582.5343562401797, samples 54240
iter: 114
ess 592.634209761512, samples 54720
iter: 115
ess 601.744572032569, samples 55200
iter: 116
ess 605.1492755287037, samples 55680
iter: 117
ess 609.6353624312007, samples 56160
iter: 118
ess 613.5433468342083, samples 56640
iter: 119
ess 612.6171354877015, samples 57120
iter: 120
ess 618.69924574814, samples 57600
iter: 121
ess 628.4239828653435, samples 58080
iter: 122
ess 639.0665758373954, samples 58560
iter: 123
ess 640.8951662610747, samples 59040
iter: 124
ess 658.5648101217669, samples 59520
iter: 125
ess 672.2232482053716, samples 60000
iter: 126
ess 692.9192879813564, samples 60480
iter: 127
ess 701.1431658990771, samples 60960
iter: 128
ess 702.7340761365899, samples 61440
iter: 129
ess 707.3541719676651, samples 61920
iter: 130
ess 701.0297752389649, samples 62400
iter: 131
ess 706.8995244779946, samples 62880
iter: 132
ess 701.4908108847167, samples 63360
iter: 133
ess 689.3472197901951, samples 63840
iter: 134
ess 703.0003376779357, samples 64320
iter: 135
ess 708.2335934188095, samples 64800
iter: 136
ess 725.1838266734792, samples 65280
iter: 137
ess 735.3205579025181, samples 65760
iter: 138
ess 737.0318459845154, samples 66240
iter: 139
ess 754.6120288318377, samples 66720
iter: 140
ess 749.599634946338, samples 67200
iter: 141
ess 752.9040639999432, samples 67680
iter: 142
ess 766.2687113077193, samples 68160
iter: 143
ess 781.7276660620391, samples 68640
iter: 144
ess 790.6849726861673, samples 69120
iter: 145
ess 795.7596388427546, samples 69600
iter: 146
ess 799.5584972022341, samples 70080
iter: 147
ess 798.8247767068805, samples 70560
iter: 148
ess 797.1372057741078, samples 71040
iter: 149
ess 794.4879887657187, samples 71520
iter: 150
ess 798.4262537658255, samples 72000
iter: 151
ess 821.3439821865887, samples 72480
iter: 152
ess 836.785381934779, samples 72960
iter: 153
ess 833.192533643803, samples 73440
iter: 154
ess 824.2367310967214, samples 73920
iter: 155
ess 833.027337432633, samples 74400
iter: 156
ess 847.6213170291394, samples 74880
iter: 157
ess 883.55387018975, samples 75360
iter: 158
ess 888.4084908886016, samples 75840
iter: 159
ess 878.3560544642794, samples 76320
iter: 160
ess 867.8099320680594, samples 76800
iter: 161
ess 865.3342989956125, samples 77280
iter: 162
ess 868.7924805999716, samples 77760
iter: 163
ess 879.929282710091, samples 78240
iter: 164
ess 890.2971775291669, samples 78720
iter: 165
ess 906.4353233136444, samples 79200
iter: 166
ess 889.0828926155659, samples 79680
iter: 167
ess 873.9690238366554, samples 80160
iter: 168
ess 877.7212806671587, samples 80640
iter: 169
ess 894.2714684714002, samples 81120
iter: 170
ess 904.8308346920122, samples 81600
iter: 171
ess 912.2855347087361, samples 82080
iter: 172
ess 909.33882150071, samples 82560
iter: 173
ess 897.4864706687231, samples 83040
iter: 174
ess 899.1382480787717, samples 83520
iter: 175
ess 904.4533159265078, samples 84000
iter: 176
ess 910.0445241340408, samples 84480
iter: 177
ess 922.005723922873, samples 84960
iter: 178
ess 928.7932859860817, samples 85440
iter: 179
ess 908.783058613329, samples 85920
iter: 180
ess 895.3797915865479, samples 86400
iter: 181
ess 859.4890207031966, samples 86880
iter: 182
ess 845.2343124097703, samples 87360
iter: 183
ess 839.1122789551176, samples 87840
iter: 184
ess 844.7127039219682, samples 88320
iter: 185
ess 846.3151169334295, samples 88800
iter: 186
ess 842.6175583860781, samples 89280
iter: 187
ess 839.5348864903817, samples 89760
iter: 188
ess 845.649272511435, samples 90240
iter: 189
ess 850.4409680273976, samples 90720
iter: 190
ess 854.5363180594184, samples 91200
iter: 191
ess 864.6976297034453, samples 91680
iter: 192
ess 870.1598273380728, samples 92160
iter: 193
ess 875.8863528631218, samples 92640
iter: 194
ess 880.6299643478253, samples 93120
iter: 195
ess 887.41821530269, samples 93600
iter: 196
ess 893.1249868133013, samples 94080
iter: 197
ess 896.4293587625341, samples 94560
iter: 198
ess 897.194064688584, samples 95040
iter: 199
ess 901.6836008572618, samples 95520
iter: 200
ess 905.9720995421688, samples 96000
iter: 201
ess 915.0415219542131, samples 96480
iter: 202
ess 913.8491612710968, samples 96960
iter: 203
ess 909.5235691609382, samples 97440
iter: 204
ess 901.066821525769, samples 97920
iter: 205
ess 903.6897456054767, samples 98400
iter: 206
ess 905.5696263301422, samples 98880
iter: 207
ess 906.1574968563857, samples 99360
iter: 208
ess 914.8511270258749, samples 99840
iter: 209
ess 919.2598470161788, samples 100320
iter: 210
ess 931.6724515215465, samples 100800
iter: 211
ess 938.2402200850552, samples 101280
iter: 212
ess 943.4875208804775, samples 101760
iter: 213
ess 949.8135147561189, samples 102240
iter: 214
ess 955.7309221867911, samples 102720
iter: 215
ess 970.4592801029347, samples 103200
iter: 216
ess 974.074172962726, samples 103680
iter: 217
ess 979.1099965408358, samples 104160
iter: 218
ess 973.4666570148554, samples 104640
iter: 219
ess 983.7461092409185, samples 105120
iter: 220
ess 988.1624730606339, samples 105600
iter: 221
ess 986.9052574352414, samples 106080
iter: 222
ess 988.65235118233, samples 106560
iter: 223
ess 987.6559013720048, samples 107040
iter: 224
ess 992.0311000409187, samples 107520
iter: 225
ess 984.9431648604796, samples 108000
iter: 226
ess 980.0972109888816, samples 108480
iter: 227
ess 986.1343984234693, samples 108960
iter: 228
ess 986.3988313773265, samples 109440
iter: 229
ess 990.7939674138657, samples 109920
iter: 230
ess 997.9588361482485, samples 110400
iter: 231
ess 1004.1998163525403, samples 110880
Billiard walk rhat: 1.0024606528800506
CHRRT rhat: 1.010601159852741
OHRR rhat: 1.0063622147724012
ess {'Billiard walk': 2013.7358952432155, 'CHRRT': 1188.850821488119, 'OHRR': 1004.1998163525403}
times {'Billiard walk': 1.5347769260406494, 'CHRRT': 0.9749472141265869, 'OHRR': 19.83063769340515}
ess/t {'Billiard walk': 1312.0707387999137, 'CHRRT': 1219.400193428071, 'OHRR': 50.63880606756764}
Visualizing the results#
[7]:
title_fs = 32
label_fs = 20
tick_fs = 16
legend_fs = 16
img_format = "pdf"
[8]:
times_to_plot = {}
ess_t_to_plot = {}
# restructure dicts for easier plotting
for n,p in proposalTypes.items():
times_to_plot[n] = {}
ess_t_to_plot[n] = {}
for problem_selection in problems_to_benchmark:
times_to_plot[n][problem_selection] = times[problem_selection][n]
ess_t_to_plot[n][problem_selection] = ess_t[problem_selection][n]
visualize runtimes#
[9]:
plt.figure(figsize=(1.5 * 6.4, 8))
plt.title(problem_selection + " runtime results", fontsize=title_fs)
X_ticks = problems_to_benchmark
X_axis = np.arange(len(X_ticks))
plt.bar(X_axis - 0.3, times_to_plot["Billiard walk"].values(), 0.2, hatch="/", label="Multiphase Billiard walk")
plt.bar(X_axis - 0.0, times_to_plot["CHRRT"].values(), 0.2, hatch="o", label="CHRRT")
plt.bar(X_axis + .3, times_to_plot["OHRR"].values(), 0.3, hatch='-', label='OHRR')
plt.ylabel(r"$s$", fontsize=label_fs)
plt.xlabel(r"Benchmark Problem", fontsize=label_fs)
plt.xticks(X_axis, X_ticks, fontsize=tick_fs)
plt.yticks(fontsize=tick_fs)
plt.tight_layout()
plt.legend(fontsize=legend_fs)
plt.savefig("BenchmarkRuntimeResults.pdf")
plt.show()

visualize ESS/t [1/s]#
[10]:
plt.figure(figsize=(1.5 * 6.4, 8))
plt.title(problem_selection + " ESS/t results", fontsize=title_fs)
X_ticks = problems_to_benchmark
X_axis = np.arange(len(X_ticks))
plt.bar(X_axis - 0.3, ess_t_to_plot["Billiard walk"].values(), 0.3, hatch="/", label="Multiphase Billiard walk")
plt.bar(X_axis - 0.0, ess_t_to_plot["CHRRT"].values(), 0.3, hatch="o", label="CHRRT")
plt.bar(X_axis + .3, ess_t_to_plot["OHRR"].values(), 0.3, hatch='-', label='OHRR')
plt.ylabel(r"$\frac{\mathit{ESS}}{t} [\frac{1}{s}]$", fontsize=label_fs)
plt.xlabel(r"Benchmark Problem", fontsize=label_fs)
plt.xticks(X_axis, X_ticks, fontsize=tick_fs)
plt.yticks(fontsize=tick_fs)
plt.tight_layout()
plt.legend(fontsize=legend_fs)
plt.savefig("BenchmarkPerformanceResults.pdf")
plt.show()

Conclusion#
In this benchmark the overrelaxed hit and run does not perform well against modern sampling algorithms. Furthermore we can see, that the performance is problem dependendent.
The values of the benchmark are not the main result of this script, however. Much more important is that hopsy is flexible enough to quickly construct fair benchmarks comparing novel algorithms with existing implementations. Furthermore all sampling algorithms access the models (polytopes) via the same interface, which reduces work for the methods researcher.
Appendix#
Visualizing the samples#
[11]:
for problem_selection in samples.keys():
n_dims = samples[problem_selection]["CHRRT"].shape[2]
n_cols = 5
n_rows = int(n_dims / n_cols) + 1
plt.figure(figsize=(n_cols * 4, n_rows * 4))
plt.subplot(n_rows, n_cols, 1)
plt.suptitle(
"Marginal densities for " + problem_selection, y=1.0, fontsize=title_fs
)
for dim in range(n_dims):
plt.subplot(n_rows, n_cols, dim + 1)
plt.title(f"Density for {parameter_names[problem_selection][dim]}", fontsize=title_fs - 4)
_, bins, _ = plt.hist(
np.concatenate(samples[problem_selection]["Billiard walk"], axis=0)[:, dim],
bins=100,
density=True,
label="Multiphase Billiard Walk" if dim == 0 else None,
alpha=0.75,
color="C0",
)
_ = plt.hist(
np.concatenate(samples[problem_selection]["CHRRT"], axis=0)[:, dim],
bins=bins,
alpha=0.5,
density=True,
label="CHRRT" if dim == 0 else None,
color="C1",
)
_ = plt.hist(
np.concatenate(samples[problem_selection]["OHRR"], axis=0)[:, dim],
bins=bins,
alpha=0.25,
density=True,
label="OHRR" if dim == 0 else None,
color="C2",
)
plt.xticks(fontsize=tick_fs)
plt.yticks(fontsize=tick_fs)
plt.figlegend(fontsize=legend_fs)
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()

