Skip to content

Commit

Permalink
Merge pull request ornladios#4301 from anagainaru/derived-documentation
Browse files Browse the repository at this point in the history
Documentation and fixing the API for derived variables

(cherry picked from commit 9face74)
  • Loading branch information
anagainaru authored and vicentebolea committed Oct 23, 2024
1 parent b34d096 commit 3bc6912
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 7 deletions.
4 changes: 2 additions & 2 deletions bindings/C/adios2/c/adios2_c_io.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -161,11 +161,11 @@ adios2_derived_variable *adios2_define_derived_variable(adios2_io *io, const cha
{
adios2::helper::CheckForNullptr(io, "for adios2_io, in call to adios2_define_variable");
adios2::core::IO &ioCpp = *reinterpret_cast<adios2::core::IO *>(io);
adios2::DerivedVarType typeCpp = adios2::DerivedVarType::MetadataOnly;
adios2::DerivedVarType typeCpp = adios2::DerivedVarType::StatsOnly;
switch (type)
{
case adios2_derived_var_type_metadata_only:
typeCpp = adios2::DerivedVarType::MetadataOnly;
typeCpp = adios2::DerivedVarType::StatsOnly;
break;
case adios2_derived_var_type_expression_string:
typeCpp = adios2::DerivedVarType::ExpressionString;
Expand Down
5 changes: 2 additions & 3 deletions bindings/CXX11/adios2/cxx11/IO.h
Original file line number Diff line number Diff line change
Expand Up @@ -155,9 +155,8 @@ class IO
const Dims &start = Dims(), const Dims &count = Dims(),
const bool constantDims = false);
#ifdef ADIOS2_HAVE_DERIVED_VARIABLE
VariableDerived
DefineDerivedVariable(const std::string &name, const std::string &expression,
const DerivedVarType varType = DerivedVarType::MetadataOnly);
VariableDerived DefineDerivedVariable(const std::string &name, const std::string &expression,
const DerivedVarType varType = DerivedVarType::StatsOnly);
#endif
VariableNT DefineVariable(const DataType type, const std::string &name,
const Dims &shape = Dims(), const Dims &start = Dims(),
Expand Down
3 changes: 3 additions & 0 deletions bindings/Python/py11IO.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ class IO
const Dims &shape, const Dims &start, const Dims &count,
const bool isConstantDims);

VariableDerived DefineDerivedVariable(const std::string &name, const std::string &expression,
const DerivedVarType varType = DerivedVarType::StatsOnly);

Variable InquireVariable(const std::string &name);

Attribute DefineAttribute(const std::string &name, const pybind11::array &array,
Expand Down
14 changes: 14 additions & 0 deletions bindings/Python/py11glue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ PYBIND11_MODULE(ADIOS2_PYTHON_MODULE_NAME, m)
.value("OtherError", adios2::StepStatus::OtherError)
.export_values();

pybind11::enum_<adios2::DerivedVarType>(m, "DerivedVarType")
.value("StatsOnly", adios2::DerivedVarType::StatsOnly)
.value("ExpressionString", adios2::DerivedVarType::ExpressionString)
.value("StoreData", adios2::DerivedVarType::StoreData)
.export_values();

pybind11::class_<adios2::py11::ADIOS>(m, "ADIOS")
// Python 2
.def("__nonzero__",
Expand Down Expand Up @@ -219,6 +225,14 @@ PYBIND11_MODULE(ADIOS2_PYTHON_MODULE_NAME, m)
adios2::py11::IO::DefineVariable,
pybind11::return_value_policy::move, pybind11::arg("name"))

.def("DefineDerivedVariable",
(adios2::py11::VariableDerived(adios2::py11::IO::*)(
const std::string &, const std::string &, const adios2::DerivedVarType)) &
adios2::py11::IO::DefineDerivedVariable,
pybind11::return_value_policy::move, pybind11::arg("name"),
pybind11::arg("expression"),
pybind11::arg("vartype") = adios2::DerivedVarType::StatsOnly)

.def("InquireVariable", &adios2::py11::IO::InquireVariable,
pybind11::return_value_policy::move)

Expand Down
95 changes: 95 additions & 0 deletions docs/user_guide/source/advanced/derived_variables.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#################
Derived variables
#################

Derived quantities are obtained by mathematical transformations of primary data, and they typically allow researchers to focus on specific aspects of the simulation.
For instance, in combustion simulations that generate velocity data, calculating the magnitude of the velocity creates a derived variable that effectively identifies areas of high interest, such as regions with intense burning.
Applications can offload the computation of derived variables to ADIOS2.

.. note::
Examples for defining and storing derived variables can be found in examples/hello/bpStepsWriteReadDerived folder

The API to define a derived variable on the write side requires providing a math expression over primary variables and the desired type of derived variable.

The math expression defines aliases for ADIOS2 variables that will be used in the expression and math operations over the aliases (example provided below). A list of supported math operations is provided at the bottom of this page.

.. code-block:: c++

enum class DerivedVarType
{
StatsOnly, // only stats are saved
StoreData // data is stored in addition to metadata
}

There are currently two types of derived variables accepted by ADIOS2, one that saves only stats about the variables (min/max for each block) and one that saves data in addition to the stats, just like a primary variable.

.. code-block:: c++

auto Ux = bpOut.DefineVariable<float>("var/Ux", {Nx, Ny}, {0, 0}, {Nx, Ny});
auto Uy = bpOut.DefineVariable<float>("var/Uy", {Nx, Ny}, {0, 0}, {Nx, Ny});
bpOut.DefineDerivedVariable("derived/magnitude,
"x = var/Ux \n"
"y = var/Uy \n"
"magnitude(x, y)",
adios2::DerivedVarType::StatsOnly);

Derived variables can be defined at any time and are computed (and potentially stored) during the ``EndStep`` operation.

The ``bpls`` utility can identify derived variables and show the math expression when the ``--show-derived`` option.
Derived variables that store data become primary data once the write operation is done, so they are not identified as derive variables by bpls.

.. code-block:: text
$ bpls StepsWriteReadDerived.bp --show-derived -l
float var/Ux 10*{60000} = 0 / 45
float var/Uy 10*{60000} = 0 / 90
float derived/magnitude 10*{60000} = 0 / 100.623
Derived variable with expression: MAGNITUDE({var/Ux},{var/Uy})
.. note::
Derived variables are currently supported only by the BP5 engine

Build ADIOS2 with support for derived variables
--------------------------

By default the derived variables are ``OFF``. Building ADIOS2 with derived variables turned on requires ``-DADIOS2_USE_Derived_Variables=ON``.

#################
Supported derived operations
#################

In the current implementation, all input variables for a derived operation need to have the same type.

.. list-table:: Supported derived operations
:widths: 20 40 40
:header-rows: 1

* - Operation
- Input
- Output
- Expression
* - Addition, Subtraction, Multiplication, Division
- All inputs must have the same dimension. Can work on multiple variables at once.
- Output variables will have the same type and dimension as the input variables.
- a+b, add(a,b), a-b, subtract(a,b), a*b, multily(a,b), a/b, divide(a,b)
* - Sqrt, Power
- Can only be applied on single variables.
- Return variables of the same dimension as the input variable, but of type ``long double`` (for ``long double`` input variable) or ``double`` (for the anything else).
- sqrt(a), pow(a)
* - Magnitude
- All inputs must have the same dimension. Can work on multiple variables at once.
- Output variables will have the same type and dimension as the input variables.
- magnitude(a, b)
* - Curl3D
- All inputs must have the same dimension. Must receive 3 variables.
- Output variables will have the same type and dimension as the input variables. The shape of the variable will have an extra dimension equal to 3 (e.g. for inputs of shape (d1, d2, d3), the curl variable will have shape (d1, d2, d3, 3))
- curl(a, b, c)


The math operations in the table above can be combined to create complex derived expressions that are evaluated one by one. The dimensions and types need to correspond to the requirements of each operation (like in the following example).

.. code-block:: text
expression= "sqrt(curl(a,b,c)) + y"
The variables corresponding to a, b and c need to have the same shape and same type (example ``<int>(d1, d2, d3)``). The curl operation will generate a variable of shape (d1, d2, d3, 3) and the sqrt will generate a double typed variable of shape (d1, d2, d3, 3). For the add operation to be applied, the y variable needs to be of type double and shape (d1, d2, d3, 3).
4 changes: 4 additions & 0 deletions examples/hello/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,7 @@ if(ADIOS2_HAVE_SST)
add_subdirectory(sstReader)
add_subdirectory(sstWriter)
endif()

if(ADIOS2_HAVE_Derived_Variable)
add_subdirectory(bpStepsWriteReadDerived)
endif()
31 changes: 31 additions & 0 deletions examples/hello/bpStepsWriteReadDerived/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#------------------------------------------------------------------------------#
# Distributed under the OSI-approved Apache License, Version 2.0. See
# accompanying file Copyright.txt for details.
#------------------------------------------------------------------------------#

cmake_minimum_required(VERSION 3.12)
project(ADIOS2HelloBPStepsWriteReadDerived)

if(NOT TARGET adios2_core)
set(_components CXX)

find_package(MPI COMPONENTS C)
if(MPI_FOUND)
# Workaround for various MPI implementations forcing the link of C++ bindings
add_definitions(-DOMPI_SKIP_MPICXX -DMPICH_SKIP_MPICXX)

list(APPEND _components MPI)
endif()

find_package(ADIOS2 REQUIRED COMPONENTS ${_components})
endif()

add_executable(adios2_hello_bpStepsWriteReadDerived bpStepsWriteReadDerived.cpp)
target_link_libraries(adios2_hello_bpStepsWriteReadDerived adios2::cxx11)
install(TARGETS adios2_hello_bpStepsWriteReadDerived RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

if(ADIOS2_HAVE_MPI)
add_executable(adios2_hello_bpStepsWriteReadDerived_mpi bpStepsWriteReadDerived.cpp)
target_link_libraries(adios2_hello_bpStepsWriteReadDerived_mpi adios2::cxx11_mpi MPI::MPI_CXX)
install(TARGETS adios2_hello_bpStepsWriteReadDerived_mpi RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
endif()
149 changes: 149 additions & 0 deletions examples/hello/bpStepsWriteReadDerived/bpStepsWriteReadDerived.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Distributed under the OSI-approved Apache License, Version 2.0. See
* accompanying file Copyright.txt for details.
*
* bpStepsWriteReadDerived.cpp Simple example of writing and reading two derived variables
* one that only stores stats and one that stores data
*/
#include <algorithm>
#include <ios>
#include <iostream>
#if ADIOS2_USE_MPI
#include <mpi.h>
#endif
#include <stdexcept>
#include <vector>

#include <adios2.h>

void update_array(std::vector<float> &array, int val)
{
std::transform(array.begin(), array.end(), array.begin(),
[val](float v) -> float { return v + static_cast<float>(val); });
}

void writer(adios2::ADIOS &adios, const std::string &fname, const size_t Nx, unsigned int nSteps,
int rank, int size)
{
std::vector<float> simData1(Nx, 0);
std::vector<float> simData2(Nx, 0);

adios2::IO bpIO = adios.DeclareIO("WriteIO");

const adios2::Dims shape{static_cast<size_t>(size * Nx)};
const adios2::Dims start{static_cast<size_t>(rank * Nx)};
const adios2::Dims count{Nx};
auto bpFloats1 = bpIO.DefineVariable<float>("bpFloats1", shape, start, count);
auto bpFloats2 = bpIO.DefineVariable<float>("bpFloats2", shape, start, count);

bpIO.DefineDerivedVariable("derived/magnitude",
"x = bpFloats1 \n"
"y = bpFloats2 \n"
"magnitude(x, y)",
adios2::DerivedVarType::StatsOnly);
bpIO.DefineDerivedVariable("derived/sqrt",
"x = bpFloats1 \n"
"sqrt(x)",
adios2::DerivedVarType::StoreData);
adios2::Engine bpWriter = bpIO.Open(fname, adios2::Mode::Write);

for (unsigned int step = 0; step < nSteps; ++step)
{
bpWriter.BeginStep();
bpWriter.Put(bpFloats1, simData1.data());
bpWriter.Put(bpFloats2, simData2.data());
bpWriter.EndStep();

// Update values in the simulation data
update_array(simData1, 5);
update_array(simData2, 10);
}

bpWriter.Close();
std::cout << "Done writing " << nSteps << " steps" << std::endl;
}

void reader(adios2::ADIOS &adios, const std::string &fname, const size_t Nx, int rank)
{
adios2::IO bpIO = adios.DeclareIO("ReadIO");

adios2::Engine bpReader = bpIO.Open(fname, adios2::Mode::Read);

std::vector<float> magData(Nx, 0);
std::vector<double> sqrtData(Nx, 0);
unsigned int step;
for (step = 0; bpReader.BeginStep() == adios2::StepStatus::OK; ++step)
{
auto bpMag = bpIO.InquireVariable<float>("derived/magnitude");
if (bpMag)
{
const adios2::Box<adios2::Dims> sel({{Nx * rank}, {Nx}});
bpMag.SetSelection(sel);
bpReader.Get(bpMag, magData.data());
}
auto bpSqrt = bpIO.InquireVariable<double>("derived/sqrt");
if (bpSqrt)
{
const adios2::Box<adios2::Dims> sel({{Nx * rank}, {Nx}});
bpSqrt.SetSelection(sel);
bpReader.Get(bpSqrt, sqrtData.data());
}

bpReader.EndStep();
}
bpReader.Close();
std::cout << "Done reading " << step << " steps" << std::endl;
}

int main(int argc, char *argv[])
{
int rank, size;

#if ADIOS2_USE_MPI
int provided;
MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided);
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);
#else
rank = 0;
size = 1;
#endif

const std::string filename = "StepsWriteReadDerived.bp";
const unsigned int nSteps = 10;
const unsigned int Nx = 60000;
try
{
#if ADIOS2_USE_MPI
adios2::ADIOS adios(MPI_COMM_WORLD);
#else
adios2::ADIOS adios;
#endif

writer(adios, filename, Nx, nSteps, rank, size);
reader(adios, filename, Nx, rank);
}
catch (std::invalid_argument &e)
{
std::cout << "Invalid argument exception, STOPPING PROGRAM from rank " << rank << "\n";
std::cout << e.what() << "\n";
}
catch (std::ios_base::failure &e)
{
std::cout << "IO System base failure exception, STOPPING PROGRAM "
"from rank "
<< rank << "\n";
std::cout << e.what() << "\n";
}
catch (std::exception &e)
{
std::cout << "Exception, STOPPING PROGRAM from rank " << rank << "\n";
std::cout << e.what() << "\n";
}

#if ADIOS2_USE_MPI
MPI_Finalize();
#endif

return 0;
}
25 changes: 25 additions & 0 deletions python/adios2/io.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,31 @@ def remove_all_variables(self):
"""
self.impl.RemoveAllVariables()

def define_derived_variable(self, name, expression, etype=None):
"""
Define a derived variable with an expression
Parameters
name
name as it appears in variable list in the output
expression
expression string using other variable names, operators and functions
type
DerivedVarType.StatsOnly : store only the metadata of the derived variable
DerivedVarType.ExpressionString : store only the definition, nothing else
DerivedVarType.StoreData : store as a complete variable (data and metadata)
"""
var_impl = None

if etype is None:
var_impl = self.impl.DefineDerivedVariable(name, expression)
else:
var_impl = self.impl.DefineDerivedVariable(name, expression, etype)

return DerivedVariable(var_impl)

def open(self, name, mode, comm=None):
"""
Open an engine
Expand Down
Loading

0 comments on commit 3bc6912

Please sign in to comment.