Starting from version 0.12.0, all the main classes in heyoka.py support serialisation via the standard Python pickle module. Before showing a couple of examples of serialisation in action, we need to emphasise a couple of very important caveats:
The last point is particularly important: because the integrator objects contain blobs of binary code, a maliciously-crafted pickle can easily be used to execute arbitrary code on the host machine.
Let us repeat again these warnings for visibility:
Do not load heyoka.py objects from untrusted pickles, as this could lead to the execution of malicious code.
Do not use heyoka.py pickles as a data exchange format, and make sure that all the pickles you load from have been produced with the same versions of heyoka.py, the heyoka C++ library, LLVM and Boost that you are currently using.
With these warnings out of the way, let us proceed to the code.
In order to illustrate the (de)serialisation workflow, we will be using our good old friend, the simple pendulum. We begin as usual with the definition of the symbolic variables and the integrator object:
import heyoka as hy
# Create the symbolic variables.
x, v = hy.make_vars("x", "v")
# Create the integrator object.
ta = hy.taylor_adaptive(
# Definition of the ODE system:
# x' = v
# v' = -9.8 * sin(x)
sys = [(x, v),
(v, -9.8 * hy.sin(x))],
# Initial conditions for x and v.
state = [0.05, 0.025])
We then integrate for a few timesteps, so that the time coordinate and the state will evolve from their initial values:
for _ in range(10):
ta.step()
Let us print to screen the time and state:
print("Time : {}".format(ta.time))
print("State: {}".format(ta.state))
Time : 2.0916676360970685 State: [ 0.05035359 -0.01665554]
We can now proceed first to serialise ta
into a bytes
object ...
import pickle
ta_pk = pickle.dumps(ta)
... and then to revive it into a new object:
ta_copy = pickle.loads(ta_pk)
We can verify that indeed the revived object contains the same data as ta
:
print("Time : {}".format(ta_copy.time))
print("State: {}".format(ta_copy.state))
Time : 2.0916676360970685 State: [ 0.05035359 -0.01665554]
As an additional check, let us perform a few more integration steps on both integrators:
for _ in range(10):
ta.step()
ta_copy.step()
Let us compare them again:
print("Time (original) : {}".format(ta.time))
print("Time (copy) : {}".format(ta_copy.time))
print("State (original): {}".format(ta.state))
print("State (copy ): {}".format(ta_copy.state))
Time (original) : 4.175322858081083 Time (copy) : 4.175322858081083 State (original): [ 0.04766883 -0.053436 ] State (copy ): [ 0.04766883 -0.053436 ]
For the (de)serialisation of [event callbacks](<./Event detection.ipynb>), heyoka.py by default employs internally the cloudpickle module instead of the standard pickle module. The motivation behind this choice is that cloudpickle is able to (de)serialise objects which the standard pickle module cannot. In particular, cloudpickle is able to (de)serialise lambdas and objects defined in an interactive session.
If, for any reason, cloudpickle is to be avoided, heyoka.py's internal serialisation backend can be switched back to the standard pickle module via the set_serialisation_backend()
function:
# Print the current serialisation backend.
print("Current backend: ", hy.get_serialization_backend())
# Switch to the standard pickle module.
hy.set_serialization_backend("pickle")
print("Current backend: ", hy.get_serialization_backend())
# Switch back to cloudpickle.
hy.set_serialization_backend("cloudpickle")
print("Current backend: ", hy.get_serialization_backend())
Current backend: <module 'cloudpickle' from '/home/yardbird/miniconda3/envs/heyoka_py_devel/lib/python3.8/site-packages/cloudpickle/__init__.py'> Current backend: <module 'pickle' from '/home/yardbird/miniconda3/envs/heyoka_py_devel/lib/python3.8/pickle.py'> Current backend: <module 'cloudpickle' from '/home/yardbird/miniconda3/envs/heyoka_py_devel/lib/python3.8/site-packages/cloudpickle/__init__.py'>