{ "cells": [ { "cell_type": "markdown", "id": "7af0d4d3", "metadata": {}, "source": [ "Pickle support\n", "============\n", "\n", "Starting from version 0.12.0, all the main classes in heyoka.py support serialisation via the standard Python [pickle module](https://docs.python.org/3/library/pickle.html). Before showing a couple of examples of serialisation in action, we need to emphasise a couple of very important **caveats**:\n", "\n", "* the serialisation format is platform-dependent and it also depends\n", " on the versions of heyoka.py, the heyoka C++ library, LLVM, and Boost. Thus, the serialised\n", " representation of heyoka.py objects is **not portable** across platforms\n", " or across different versions of heyoka.py or its dependencies. Do **not**\n", " try to use the serialised representation of heyoka.py objects as an exchange\n", " format, as this will result in undefined behaviour;\n", "* heyoka.py does not make any attempt to validate the state of a deserialised object.\n", " Thus, a maliciously-crafted pickle could be used\n", " to crash heyoka.py or even execute arbitrary code on the machine.\n", "\n", "The last point is particularly important: because the integrator objects\n", "contain blobs of binary code,\n", "a maliciously-crafted pickle can easily be used\n", "to execute arbitrary code on the host machine.\n", "\n", "Let us repeat again these warnings for visibility:\n", "\n", "> Do **not** load heyoka.py objects from untrusted pickles, as this could lead\n", "> to the execution of malicious code.\n", ">\n", "> Do **not** use heyoka.py pickles as a data exchange format, and make sure that\n", "> all the pickles you load from have been produced with the same versions of heyoka.py,\n", "> the heyoka C++ library, LLVM and Boost that you are currently using.\n", "\n", "With these warnings out of the way, let us proceed to the code.\n", "\n", "A simple example\n", "-------------------------\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 1, "id": "7b4caaba", "metadata": {}, "outputs": [], "source": [ "import heyoka as hy\n", "\n", "# Create the symbolic variables.\n", "x, v = hy.make_vars(\"x\", \"v\")\n", "\n", "# Create the integrator object.\n", "ta = hy.taylor_adaptive(\n", " # Definition of the ODE system:\n", " # x' = v\n", " # v' = -9.8 * sin(x)\n", " sys = [(x, v),\n", " (v, -9.8 * hy.sin(x))],\n", " # Initial conditions for x and v.\n", " state = [0.05, 0.025])" ] }, { "cell_type": "markdown", "id": "f9eb1c78", "metadata": {}, "source": [ "We then integrate for a few timesteps, so that the time coordinate and the state will evolve from their initial values:" ] }, { "cell_type": "code", "execution_count": 2, "id": "babf4108", "metadata": {}, "outputs": [], "source": [ "for _ in range(10):\n", " ta.step()" ] }, { "cell_type": "markdown", "id": "ae18eeee", "metadata": {}, "source": [ "Let us print to screen the time and state:" ] }, { "cell_type": "code", "execution_count": 3, "id": "3ade3b3b", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time : 2.0916676360970685\n", "State: [ 0.05035359 -0.01665554]\n" ] } ], "source": [ "print(\"Time : {}\".format(ta.time))\n", "print(\"State: {}\".format(ta.state))" ] }, { "cell_type": "markdown", "id": "9acbc1ad", "metadata": {}, "source": [ "We can now proceed first to serialise ``ta`` into a ``bytes`` object ..." ] }, { "cell_type": "code", "execution_count": 4, "id": "76fb7bb7", "metadata": {}, "outputs": [], "source": [ "import pickle\n", "ta_pk = pickle.dumps(ta)" ] }, { "cell_type": "markdown", "id": "f854050d", "metadata": {}, "source": [ "... and then to revive it into a new object:" ] }, { "cell_type": "code", "execution_count": 5, "id": "3199b8f7", "metadata": {}, "outputs": [], "source": [ "ta_copy = pickle.loads(ta_pk)" ] }, { "cell_type": "markdown", "id": "a0a88bd1", "metadata": {}, "source": [ "We can verify that indeed the revived object contains the same data as ``ta``:" ] }, { "cell_type": "code", "execution_count": 6, "id": "302a5958", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time : 2.0916676360970685\n", "State: [ 0.05035359 -0.01665554]\n" ] } ], "source": [ "print(\"Time : {}\".format(ta_copy.time))\n", "print(\"State: {}\".format(ta_copy.state))" ] }, { "cell_type": "markdown", "id": "a1e04be0", "metadata": {}, "source": [ "As an additional check, let us perform a few more integration steps on both integrators:" ] }, { "cell_type": "code", "execution_count": 7, "id": "178fbdc8", "metadata": {}, "outputs": [], "source": [ "for _ in range(10):\n", " ta.step()\n", " ta_copy.step()" ] }, { "cell_type": "markdown", "id": "d780716b", "metadata": {}, "source": [ "Let us compare them again:" ] }, { "cell_type": "code", "execution_count": 8, "id": "84fafb1c", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Time (original) : 4.175322858081083\n", "Time (copy) : 4.175322858081083\n", "State (original): [ 0.04766883 -0.053436 ]\n", "State (copy ): [ 0.04766883 -0.053436 ]\n" ] } ], "source": [ "print(\"Time (original) : {}\".format(ta.time))\n", "print(\"Time (copy) : {}\".format(ta_copy.time))\n", "print(\"State (original): {}\".format(ta.state))\n", "print(\"State (copy ): {}\".format(ta_copy.state))" ] }, { "cell_type": "markdown", "id": "36a18d3c", "metadata": {}, "source": [ "On the serialisation of event callbacks\n", "-------------------------------------------------------\n", "\n", "For the (de)serialisation of [event callbacks](<./Event detection.ipynb>), heyoka.py by default employs internally the [cloudpickle](https://github.com/cloudpipe/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.\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": 9, "id": "1640e4a6", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Current backend: \n", "Current backend: \n", "Current backend: \n" ] } ], "source": [ "# Print the current serialisation backend.\n", "print(\"Current backend: \", hy.get_serialization_backend())\n", "\n", "# Switch to the standard pickle module.\n", "hy.set_serialization_backend(\"pickle\")\n", "print(\"Current backend: \", hy.get_serialization_backend())\n", "\n", "# Switch back to cloudpickle.\n", "hy.set_serialization_backend(\"cloudpickle\")\n", "print(\"Current backend: \", hy.get_serialization_backend())" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 5 }