Result Storage
This notebook will show how to store pypesto result objects to be able to load them later on for visualization and further analysis. This includes sampling, profiling and optimization. Additionally, we will show how to use optimization history to look further into an optimization run and how to store the history.
After this notebook, you will…
know how to store and load optimization, profiling and sampling results
know how to store and load optimization history
know basic plotting functions for optimization history to inspect optimization convergence
[1]:
# install if not done yet
# %pip install pypesto --quiet
Imports
[2]:
import logging
import tempfile
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import Markdown, display
import pypesto.optimize as optimize
import pypesto.petab
import pypesto.profile as profile
import pypesto.sample as sample
import pypesto.visualize as visualize
mpl.rcParams["figure.dpi"] = 100
mpl.rcParams["font.size"] = 18
# set a random seed to get reproducible results
np.random.seed(3142)
%matplotlib inline
0. Objective function and problem definition
We will use the Boehm model from the benchmark initiative in this notebook as an example. We load the model through PEtab, a data format for specifying parameter estimation problems in systems biology.
[3]:
%%capture
# directory of the PEtab problem
petab_yaml = "./conversion_reaction/conversion_reaction.yaml"
importer = pypesto.petab.PetabImporter.from_yaml(petab_yaml)
problem = importer.create_problem(verbose=False)
1. Filling in the result file
We will now run a standard parameter estimation pipeline with this model. Aside from the part on the history, we shall not go into detail here, as this is covered in other tutorials such as Getting Started and AMICI in pyPESTO.
Optimization
[4]:
%%time
# create optimizers
optimizer = optimize.FidesOptimizer(
verbose=logging.ERROR, options={"maxiter": 200}
)
# set number of starts
n_starts = 10 # usually a larger number >=100 is used
# Optimization
result = pypesto.optimize.minimize(
problem=problem, optimizer=optimizer, n_starts=n_starts
)
CPU times: user 353 ms, sys: 374 μs, total: 354 ms
Wall time: 353 ms
[5]:
display(Markdown(result.summary()))
Optimization Result
number of starts: 10
best value: -25.35620048305541, id=7
worst value: 2632.872853377001, id=6
number of non-finite values: 0
execution time summary:
Mean execution time: 0.035s
Maximum execution time: 0.079s, id=7
Minimum execution time: 0.003s, id=6
summary of optimizer messages:
Count
Message
9
Converged according to fval difference
1
Trust Region Radius too small to proceed
best value found (approximately) 7 time(s)
number of plateaus found: 2
A summary of the best run:
Optimizer Result
optimizer used: <FidesOptimizer hessian_update=default verbose=40 options={‘maxiter’: 200}>
message: Trust Region Radius too small to proceed
number of evaluations: 24
time taken to optimize: 0.079s
startpoint: [-1.59454302 3.60344896]
endpoint: [-0.25416632 -0.60833814]
final objective value: -25.35620048305541
final gradient value: [6.87611496e-05 2.03442423e-04]
final hessian value: [[1709.63133665 -881.86382194] [-881.86382194 538.51129532]]
Profiling
[6]:
%%time
# Profiling
result = profile.parameter_profile(
problem=problem,
result=result,
optimizer=optimizer,
profile_index=np.array([0, 1]),
)
Next guess for k1 in direction -1 is -0.2652. Step size: -0.0110.
Optimization successful for k1=-0.2652. Start fval -25.252137, end fval -25.339999.
Next guess for k1 in direction -1 is -0.2677. Step size: -0.0025.
Optimization successful for k1=-0.2677. Start fval -25.331906, end fval -25.331907.
Next guess for k1 in direction -1 is -0.2707. Step size: -0.0030.
Optimization successful for k1=-0.2707. Start fval -25.319768, end fval -25.319768.
Next guess for k1 in direction -1 is -0.2744. Step size: -0.0037.
Optimization successful for k1=-0.2744. Start fval -25.301559, end fval -25.301568.
Next guess for k1 in direction -1 is -0.2789. Step size: -0.0045.
Optimization successful for k1=-0.2789. Start fval -25.274263, end fval -25.274263.
Next guess for k1 in direction -1 is -0.2845. Step size: -0.0055.
Optimization successful for k1=-0.2845. Start fval -25.233312, end fval -25.233313.
Next guess for k1 in direction -1 is -0.2913. Step size: -0.0068.
Optimization successful for k1=-0.2913. Start fval -25.171894, end fval -25.171894.
Next guess for k1 in direction -1 is -0.2996. Step size: -0.0083.
Optimization successful for k1=-0.2996. Start fval -25.079780, end fval -25.079783.
Next guess for k1 in direction -1 is -0.3097. Step size: -0.0101.
Optimization successful for k1=-0.3097. Start fval -24.941633, end fval -24.941639.
Next guess for k1 in direction -1 is -0.3220. Step size: -0.0124.
Optimization successful for k1=-0.3220. Start fval -24.734402, end fval -24.734416.
Next guess for k1 in direction -1 is -0.3371. Step size: -0.0151.
Optimization successful for k1=-0.3371. Start fval -24.423720, end fval -24.423743.
Next guess for k1 in direction -1 is -0.3555. Step size: -0.0184.
Optimization successful for k1=-0.3555. Start fval -23.957966, end fval -23.958029.
Next guess for k1 in direction -1 is -0.3779. Step size: -0.0224.
Optimization successful for k1=-0.3779. Start fval -23.259777, end fval -23.259937.
Next guess for k1 in direction 1 is -0.2229. Step size: 0.0313.
Optimization successful for k1=-0.2229. Start fval -25.227237, end fval -25.227444.
Next guess for k1 in direction 1 is -0.2158. Step size: 0.0071.
Optimization successful for k1=-0.2158. Start fval -25.163147, end fval -25.163156.
Next guess for k1 in direction 1 is -0.2071. Step size: 0.0087.
Optimization successful for k1=-0.2071. Start fval -25.066663, end fval -25.066666.
Next guess for k1 in direction 1 is -0.1964. Step size: 0.0107.
Optimization successful for k1=-0.1964. Start fval -24.921946, end fval -24.921950.
Next guess for k1 in direction 1 is -0.1833. Step size: 0.0131.
Optimization successful for k1=-0.1833. Start fval -24.705014, end fval -24.705021.
Next guess for k1 in direction 1 is -0.1672. Step size: 0.0161.
Optimization successful for k1=-0.1672. Start fval -24.379642, end fval -24.379662.
Next guess for k1 in direction 1 is -0.1473. Step size: 0.0199.
Optimization successful for k1=-0.1473. Start fval -23.891582, end fval -23.891622.
Next guess for k1 in direction 1 is -0.1228. Step size: 0.0245.
Optimization successful for k1=-0.1228. Start fval -23.159421, end fval -23.159514.
Next guess for k2 in direction -1 is -0.6282. Step size: -0.0198.
Optimization successful for k2=-0.6282. Start fval -25.251200, end fval -25.339907.
Next guess for k2 in direction -1 is -0.6326. Step size: -0.0045.
Optimization successful for k2=-0.6326. Start fval -25.331770, end fval -25.331770.
Next guess for k2 in direction -1 is -0.6381. Step size: -0.0055.
Optimization successful for k2=-0.6381. Start fval -25.319560, end fval -25.319560.
Next guess for k2 in direction -1 is -0.6449. Step size: -0.0067.
Optimization successful for k2=-0.6449. Start fval -25.301247, end fval -25.301246.
Next guess for k2 in direction -1 is -0.6532. Step size: -0.0083.
Optimization successful for k2=-0.6532. Start fval -25.273777, end fval -25.273779.
Next guess for k2 in direction -1 is -0.6633. Step size: -0.0102.
Optimization successful for k2=-0.6633. Start fval -25.232583, end fval -25.232586.
Next guess for k2 in direction -1 is -0.6759. Step size: -0.0125.
Optimization successful for k2=-0.6759. Start fval -25.170784, end fval -25.170790.
Next guess for k2 in direction -1 is -0.6913. Step size: -0.0154.
Optimization successful for k2=-0.6913. Start fval -25.078106, end fval -25.078106.
Next guess for k2 in direction -1 is -0.7103. Step size: -0.0190.
Optimization successful for k2=-0.7103. Start fval -24.939122, end fval -24.939134.
Next guess for k2 in direction -1 is -0.7339. Step size: -0.0235.
Optimization successful for k2=-0.7339. Start fval -24.730725, end fval -24.730745.
Next guess for k2 in direction -1 is -0.7630. Step size: -0.0292.
Optimization successful for k2=-0.7630. Start fval -24.418240, end fval -24.418278.
Next guess for k2 in direction -1 is -0.7993. Step size: -0.0363.
Optimization successful for k2=-0.7993. Start fval -23.949662, end fval -23.949764.
Next guess for k2 in direction -1 is -0.8446. Step size: -0.0453.
Optimization successful for k2=-0.8446. Start fval -23.247045, end fval -23.247292.
Next guess for k2 in direction 1 is -0.5535. Step size: 0.0548.
Optimization successful for k2=-0.5535. Start fval -25.227924, end fval -25.228216.
Next guess for k2 in direction 1 is -0.5414. Step size: 0.0122.
Optimization successful for k2=-0.5414. Start fval -25.164255, end fval -25.164268.
Next guess for k2 in direction 1 is -0.5265. Step size: 0.0148.
Optimization successful for k2=-0.5265. Start fval -25.068386, end fval -25.068390.
Next guess for k2 in direction 1 is -0.5085. Step size: 0.0181.
Optimization successful for k2=-0.5085. Start fval -24.924648, end fval -24.924653.
Next guess for k2 in direction 1 is -0.4865. Step size: 0.0220.
Optimization successful for k2=-0.4865. Start fval -24.709162, end fval -24.709178.
Next guess for k2 in direction 1 is -0.4597. Step size: 0.0267.
Optimization successful for k2=-0.4597. Start fval -24.386090, end fval -24.386122.
Next guess for k2 in direction 1 is -0.4273. Step size: 0.0325.
Optimization successful for k2=-0.4273. Start fval -23.901627, end fval -23.901696.
Next guess for k2 in direction 1 is -0.3878. Step size: 0.0394.
Optimization successful for k2=-0.3878. Start fval -23.174978, end fval -23.175122.
CPU times: user 1.4 s, sys: 29.4 ms, total: 1.43 s
Wall time: 1.42 s
Sampling
[7]:
%%time
# Sampling
sampler = sample.AdaptiveMetropolisSampler()
result = sample.sample(
problem=problem,
sampler=sampler,
n_samples=1000, # rather low
result=result,
filename=None,
)
Elapsed time: 0.5211825279999998
CPU times: user 524 ms, sys: 0 ns, total: 524 ms
Wall time: 523 ms
2. Storing the result file
We filled all our analyses into one result file. We can now store this result object into HDF5 format to reload this later on.
[8]:
# create temporary file
fn = tempfile.NamedTemporaryFile(suffix=".hdf5", delete=False)
# write the result with the write_result function.
# Choose which parts of the result object to save with
# corresponding booleans.
pypesto.store.write_result(
result=result,
filename=fn.name,
problem=True,
optimize=True,
profile=True,
sample=True,
)
As easy as we can save the result object, we can also load it again:
[9]:
# load result with read_result function
result_loaded = pypesto.store.read_result(fn.name)
As you can see, when loading the result object, we get a warning regarding the problem. This is the case, as the problem is not fully saved into hdf5, as a big part of the problem is the objective function. Therefore, after loading the result object, we cannot evaluate the objective function anymore. We can, however, still use the result object for plotting and further analysis.
The best practice would be to still create the problem through petab and insert it into the result object after loading it.
[10]:
# dummy call to non-existent objective function would fail
test_parameter = result.optimize_result[0].x[problem.x_free_indices]
# result_loaded.problem.objective(test_parameter)
[11]:
result_loaded.problem = problem
print(
f"Objective function call: {result_loaded.problem.objective(test_parameter)}"
)
print(f"Corresponding saved value: {result_loaded.optimize_result[0].fval}")
Objective function call: -25.35620031164133
Corresponding saved value: -25.35620048305541
To show that for visualizations however, the storage and loading of the result object is accurate, we will plot some result visualizations.
3. Visualization Comparison
Optimization
[12]:
# waterfall plot original
ax = visualize.waterfall(result)
ax.title.set_text("Original Result")

[13]:
# waterfall plot loaded
ax = visualize.waterfall(result_loaded)
ax.title.set_text("Loaded Result")

Profiling
[14]:
# profile plot original
ax = visualize.profiles(result)

[15]:
# profile plot loaded
ax = visualize.profiles(result_loaded)

Sampling
[16]:
# sampling plot original
ax = visualize.sampling_fval_traces(result)
/home/docs/checkouts/readthedocs.org/user_builds/pypesto/envs/stable/lib/python3.11/site-packages/pypesto/visualize/sampling.py:79: UserWarning: Burn in index not found in the results, the full chain will be shown.
You may want to use, e.g., `pypesto.sample.geweke_test`.
_, params_fval, _, _, _ = get_data_to_plot(

[17]:
# sampling plot loaded
ax = visualize.sampling_fval_traces(result_loaded)
/home/docs/checkouts/readthedocs.org/user_builds/pypesto/envs/stable/lib/python3.11/site-packages/pypesto/visualize/sampling.py:79: UserWarning: Burn in index not found in the results, the full chain will be shown.
You may want to use, e.g., `pypesto.sample.geweke_test`.
_, params_fval, _, _, _ = get_data_to_plot(

We can see that we are perfectly able to reproduce the plots from the loaded result object. With this, we can reuse the result object for further analysis and visualization again and again without spending time and resources on rerunning the analyses.
4. Optimization History
During optimization, it is possible to regularly write the objective function trace to file. This is useful, e.g., when runs fail, or for various diagnostics. Currently, pyPESTO can save histories to 3 backends: in-memory, as CSV files, or to HDF5 files.
Memory History
To record the history in-memory, just set trace_record=True
in the pypesto.HistoryOptions
. Then, the optimization result contains those histories:
[18]:
%%time
# record the history
history_options = pypesto.HistoryOptions(trace_record=True)
# Run optimizations
result = optimize.minimize(
problem=problem,
optimizer=optimizer,
n_starts=n_starts,
history_options=history_options,
filename=None,
)
CPU times: user 279 ms, sys: 3.65 ms, total: 282 ms
Wall time: 282 ms
Now, in addition to queries on the result, we can also access the history.
[19]:
print("History type: ", type(result.optimize_result.list[0].history))
# print("Function value trace of best run: ", result.optimize_result.list[0].history.get_fval_trace())
fig, ax = plt.subplots(1, 2)
visualize.waterfall(result, ax=ax[0])
visualize.optimizer_history(result, ax=ax[1])
fig.set_size_inches((15, 5))
History type: <class 'pypesto.history.memory.MemoryHistory'>

CSV History
The in-memory storage is, however, not stored anywhere. To do that, it is possible to store either to CSV or HDF5. This is specified via the storage_file
option. If it ends in .csv
, a pypesto.objective.history.CsvHistory
will be employed; if it ends in .hdf5
a pypesto.objective.history.Hdf5History
. Occurrences of the substring {id}
in the filename are replaced by the multistart id, allowing to maintain a separate file per run (this is necessary for CSV as otherwise runs
are overwritten).
[20]:
%%time
# create temporary file
with tempfile.NamedTemporaryFile(suffix="_{id}.csv") as fn_csv:
# record the history and store to CSV
history_options = pypesto.HistoryOptions(
trace_record=True, storage_file=fn_csv.name
)
# Run optimizations
result = optimize.minimize(
problem=problem,
optimizer=optimizer,
n_starts=n_starts,
history_options=history_options,
filename=None,
)
CPU times: user 572 ms, sys: 294 μs, total: 572 ms
Wall time: 586 ms
Note that for this simple cost function, saving to CSV takes a considerable amount of time. This overhead decreases for more costly simulators, e.g., using ODE simulations via AMICI.
[21]:
print("History type: ", type(result.optimize_result.list[0].history))
# print("Function value trace of best run: ", result.optimize_result.list[0].history.get_fval_trace())
fig, ax = plt.subplots(1, 2)
visualize.waterfall(result, ax=ax[0])
visualize.optimizer_history(result, ax=ax[1])
fig.set_size_inches((15, 5))
History type: <class 'pypesto.history.amici.CsvAmiciHistory'>

HDF5 History
Just as in CSV, writing the history to HDF5 takes a considerable amount of time. If a user specifies a HDF5 output file named my_results.hdf5
and uses a parallelization engine, then:
a folder is created to contain partial results, named
my_results/
(the stem of the output filename)files are created to store the results of each start, named
my_results/my_results_{START_INDEX}.hdf5
a file is created to store the combined result from all starts, named
my_results.hdf5
. Note that this file depends on the files in themy_results/
directory, so cease to function ifmy_results/
is deleted.
[22]:
%%time
# create temporary file
f_hdf5 = tempfile.NamedTemporaryFile(suffix=".hdf5", delete=False)
fn_hdf5 = f_hdf5.name
# record the history and store to CSV
history_options = pypesto.HistoryOptions(
trace_record=True, storage_file=fn_hdf5
)
# Run optimizations
result = optimize.minimize(
problem=problem,
optimizer=optimizer,
n_starts=n_starts,
history_options=history_options,
filename=fn_hdf5,
)
CPU times: user 852 ms, sys: 31.2 ms, total: 883 ms
Wall time: 883 ms
[23]:
print("History type: ", type(result.optimize_result.list[0].history))
# print("Function value trace of best run: ", result.optimize_result.list[0].history.get_fval_trace())
fig, ax = plt.subplots(1, 2)
visualize.waterfall(result, ax=ax[0])
visualize.optimizer_history(result, ax=ax[1])
fig.set_size_inches((15, 5))
History type: <class 'pypesto.history.amici.Hdf5AmiciHistory'>

For the HDF5 history, it is possible to load the history from file, and to plot it, together with the optimization result.
[24]:
# load the history
result_loaded_w_history = pypesto.store.read_result(fn_hdf5)
fig, ax = plt.subplots(1, 2)
visualize.waterfall(result_loaded_w_history, ax=ax[0])
visualize.optimizer_history(result_loaded_w_history, ax=ax[1])
fig.set_size_inches((15, 5))
Loading the profiling result failed. It is highly likely that no profiling result exists within /tmp/tmpawwpn8ip.hdf5.
Loading the sampling result failed. It is highly likely that no sampling result exists within /tmp/tmpawwpn8ip.hdf5.

[25]:
# close the temporary file
f_hdf5.close()