.. _usage-firstwrite: First Write =========== Step-by-step: how to write scientific data with openPMD-api? .. raw:: html Include / Import ---------------- After successful :ref:`installation `, you can start using openPMD-api as follows: C++17 ^^^^^ .. code-block:: cpp #include // example: data handling #include // std::iota #include // std::vector namespace io = openPMD; Python ^^^^^^ .. code-block:: python3 import openpmd_api as io # example: data handling import numpy as np Open ---- Write into a new openPMD series in ``myOutput/data_<00...N>.h5``. Further file formats than ``.h5`` (`HDF5 `_) are supported: ``.bp`` (`ADIOS2 `_) or ``.json`` (`JSON `_). C++17 ^^^^^ .. code-block:: cpp auto series = io::Series( "myOutput/data_%05T.h5", io::Access::CREATE); Python ^^^^^^ .. code-block:: python3 series = io.Series( "myOutput/data_%05T.h5", io.Access.create) Iteration --------- Grouping by an arbitrary, nonnegative integer number ```` in a series: C++17 ^^^^^ .. code-block:: cpp auto i = series.iterations[42]; Python ^^^^^^ .. code-block:: python3 i = series.iterations[42] Attributes ---------- Everything in openPMD can be extended and user-annotated. Let us try this by writing some meta data: C++17 ^^^^^ .. code-block:: cpp series.setAuthor( "Axel Huebl "); series.setMachine( "Hall Probe 5000, Model 3"); series.setAttribute( "dinner", "Pizza and Coke"); i.setAttribute( "vacuum", true); Python ^^^^^^ .. code-block:: python3 series.author = \ "Axel Huebl " series.machine = "Hall Probe 5000, Model 3" series.set_attribute( "dinner", "Pizza and Coke") i.set_attribute( "vacuum", True) Data ---- Let's prepare some data that we want to write. For example, a magnetic field slice :math:`\vec B(i, j)` in two spatial dimensions with three components :math:`(B_x, B_y, B_z)^\intercal` of which the :math:`B_y` component shall be constant for all :math:`(i, j)` indices. C++17 ^^^^^ .. code-block:: cpp std::vector x_data( 150 * 300); std::iota( x_data.begin(), x_data.end(), 0.); float y_data = 4.f; std::vector z_data(x_data); for( auto& c : z_data ) c -= 8000.f; Python ^^^^^^ .. code-block:: python3 x_data = np.arange( 150 * 300, dtype=np.float64 ).reshape(150, 300) y_data = 4. z_data = x_data.copy() - 8000. Record ------ An openPMD record can be either structured (mesh) or unstructured (particles). We prepared a vector field in 2D above, which is a mesh: C++17 ^^^^^ .. code-block:: cpp // record auto B = i.meshes["B"]; // record components auto B_x = B["x"]; auto B_y = B["y"]; auto B_z = B["z"]; auto dataset = io::Dataset( io::determineDatatype(), {150, 300}); B_x.resetDataset(dataset); B_y.resetDataset(dataset); B_z.resetDataset(dataset); Python ^^^^^^ .. code-block:: python3 # record B = i.meshes["B"] # record components B_x = B["x"] B_y = B["y"] B_z = B["z"] dataset = io.Dataset( x_data.dtype, x_data.shape) B_x.reset_dataset(dataset) B_y.reset_dataset(dataset) B_z.reset_dataset(dataset) Units ----- Let's describe this magnetic field :math:`\vec B` in more detail. Independent of the absolute unit system, a magnetic field has the `physical dimension `_ of [mass (M)\ :sup:`1` :math:`\cdot` electric current (I)\ :sup:`-1` :math:`\cdot` time (T)\ :sup:`-2`]. Ouch, our magnetic field was measured in `cgs units `_! Quick, let's also store the conversion factor 10\ :sup:`-4` from `Gauss `_ (cgs) to `Tesla `_ (SI). C++17 ^^^^^ .. code-block:: cpp // unit system agnostic dimension B.setUnitDimension({ {io::UnitDimension::M, 1}, {io::UnitDimension::I, -1}, {io::UnitDimension::T, -2} }); // conversion to SI B_x.setUnitSI(1.e-4); B_y.setUnitSI(1.e-4); B_z.setUnitSI(1.e-4); Python ^^^^^^ .. code-block:: python3 # unit system agnostic dimension B.unit_dimension = { io.Unit_Dimension.M: 1, io.Unit_Dimension.I: -1, io.Unit_Dimension.T: -2 } # conversion to SI B_x.unit_SI = 1.e-4 B_y.unit_SI = 1.e-4 B_z.unit_SI = 1.e-4 .. tip:: Annotating the *physical dimension* (``unitDimension``) of a record allows us to read data sets with *arbitrary names* and understand their purpose simply by `dimensional analysis `_. The dimensional `base quantities `_ in openPMD are length (``L``), mass (``M``), time (``T``), electric current (``I``), thermodynamic temperature (``theta``), amount of substance (``N``), luminous intensity (``J``) after the international system of quantities (ISQ). The *factor to SI* (``unitSI``) on the other hand allows us to convert values between absolute unit systems. Register Chunk -------------- We can write record components partially and in parallel or at once. Writing very small data one by one is is a performance killer for I/O. Therefore, we register all data to be written first and then flush it out collectively. C++17 ^^^^^ .. code-block:: cpp B_x.storeChunk( x_data, {0, 0}, {150, 300}); B_z.storeChunk( z_data, {0, 0}, {150, 300}); B_y.makeConstant(y_data); Python ^^^^^^ .. code-block:: python3 B_x[:, :] = x_data B_z[:, :] = z_data B_y.make_constant(y_data) .. attention:: After registering a data chunk such as ``x_data`` and ``y_data``, it MUST NOT be modified or deleted until the ``flush()`` step is performed! Flush Chunk ----------- We now flush the registered data chunks to the I/O backend. Flushing several chunks at once allows to increase I/O performance significantly. After that, the variables ``x_data`` and ``y_data`` can be used again. C++17 ^^^^^ .. code-block:: cpp series.flush(); Python ^^^^^^ .. code-block:: python3 series.flush() Close ----- Finally, the Series is fully closed (and newly registered data or attributes since the last ``.flush()`` is written) when its destructor is called. C++17 ^^^^^ .. code-block:: cpp series.close() Python ^^^^^^ .. code-block:: python3 series.close()