diff --git a/CMakeLists.txt b/CMakeLists.txt index 691476a..e791f1d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,6 +15,10 @@ if(TINYOBJLOADER_FOUND) set(DEFINE_HAVE_TINYOBJLOADER "#define HAVE_TINYOBJLOADER 1") endif() +if(catalyst_FOUND) + set(DEFINE_HAVE_CATALYST "#define HAVE_CATALYST") +endif() + include_directories(SYSTEM ${MPI_INCLUDE_PATH}) add_subdirectory (src) @@ -23,5 +27,6 @@ get_directory_property(hasParent PARENT_DIRECTORY) if(hasParent) set(DEFINE_HAVE_TINYOBJLOADER ${DEFINE_HAVE_TINYOBJLOADER} CACHE INTERNAL "") set(DEFINE_HAVE_HDF5 ${DEFINE_HAVE_HDF5} CACHE INTERNAL "") + set(DEFINE_HAVE_CATALYST ${DEFINE_HAVE_CATALYST} CACHE INTERNAL "") endif() diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f114444..d3416a0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -21,12 +21,14 @@ if ( CUDA_ON_BACKEND STREQUAL "HIP" AND HIP_FOUND ) hip_add_executable(io main.cpp MetaParser/MetaParser_unit_test.cpp + Catalyst/catalyst_unit_test.cpp ${CUDA_SOURCES} ObjReader/ObjReader_unit_test.cpp CSVReader/tests/CSVReader_unit_test.cpp) else() add_executable(io main.cpp MetaParser/MetaParser_unit_test.cpp + Catalyst/catalyst_unit_test.cpp ${CUDA_SOURCES} ObjReader/ObjReader_unit_test.cpp CSVReader/tests/CSVReader_unit_test.cpp) @@ -66,6 +68,7 @@ target_include_directories (io PUBLIC ${CMAKE_CUDA_TOOLKIT_INCLUDE_DIRECTORIES}) target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../openfpm_devices/src/) target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../openfpm_vcluster/src/) +target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../openfpm_pdata/src/) target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../openfpm_data/src/) target_include_directories (io PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../src/) target_include_directories (io PUBLIC ${CMAKE_BINARY_DIR}/config) @@ -74,6 +77,26 @@ target_include_directories (io PUBLIC ${TINYOBJLOADER_INCLUDE_DIRS} ) target_include_directories (io PUBLIC ${Boost_INCLUDE_DIRS}) target_include_directories (io PUBLIC ${ALPAKA_ROOT}/include) +if (catalyst_FOUND) + target_link_libraries(io catalyst::catalyst) + + # libraries for Catalyst unit tests + target_include_directories (io PUBLIC ${METIS_ROOT}/include) + target_include_directories (io PUBLIC ${PARMETIS_ROOT}/include) + target_include_directories (io PUBLIC ${LIBHILBERT_INCLUDE_DIRS}) + target_link_libraries(io ${PARMETIS_LIBRARIES}) + + if (Vc_FOUND) + target_include_directories (io PUBLIC ${Vc_INCLUDE_DIR}) + target_link_libraries(io ${Vc_LIBRARIES}) + endif() + + if (PNG_FOUND) + target_include_directories (io PUBLIC ${PNG_INCLUDE_DIRS}) + target_link_libraries(io ${PNG_LIBRARIES}) + endif() +endif() + if (PETSC_FOUND) target_include_directories(io PUBLIC ${PETSC_INCLUDES}) endif() @@ -165,3 +188,8 @@ install(FILES Plot/GoogleChart.hpp Plot/util.hpp DESTINATION openfpm_io/include/Plot COMPONENT OpenFPM) +install(FILES Catalyst/catalyst_adaptor.hpp + Catalyst/catalyst_adaptor_grid_dist.hpp + Catalyst/catalyst_adaptor_vector_dist.hpp + DESTINATION openfpm_io/include/Catalyst + COMPONENT OpenFPM) diff --git a/src/Catalyst/catalyst_adaptor.hpp b/src/Catalyst/catalyst_adaptor.hpp new file mode 100644 index 0000000..cd2ddc6 --- /dev/null +++ b/src/Catalyst/catalyst_adaptor.hpp @@ -0,0 +1,452 @@ +#ifndef CATALYST_ADAPTOR_HPP_ +#define CATALYST_ADAPTOR_HPP_ + +#include "config.h" + +#ifdef HAVE_CATALYST +#include +#include + +/** + * @brief Template for setting properties of particles as fields in Counduit node, following Mesh Blueprint. + * + * @tparam T Reserved to pass dimensions of array/vector/tensor structures. + */ +template +struct set_prop_val +{ + /** + * @brief Generic function for property setter to Conduit node. + * + * @param fields Reference to "fields" sub-node in Conduit hierarchical node. + * @param prop_name Name of the property. + * @param prop_storage_row Pointer to the contiguous/interleaved storage of property values. + * @param num_elements Number of particles in simulation. + */ + static void set(conduit_cpp::Node &fields, const std::string &prop_name, void *prop_storage_row, size_t num_elements) + { + // Skip unsupported properties + } +}; + +/** + * @brief Pass scalar `double` property values to Conduit node. + */ +template <> +struct set_prop_val +{ + /** + * scalar `double` properties are saved into `prop_storage` as contiguous `double[n]` array, + * where `n` is the number of particles. + */ + static void set(conduit_cpp::Node &fields, const std::string &prop_name, double &prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + fields[prop_name + "/values"].set_external((double *)&prop_storage_row, num_elements); + } +}; + +/** + * @brief Pass scalar `float` property values to Conduit node + */ +template <> +struct set_prop_val +{ + /** + * scalar `float` properties are saved into `prop_storage` as contiguous `float[n]` array, + * where n is the number of particles. + */ + static void set(conduit_cpp::Node &fields, const std::string &prop_name, float &prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + fields[prop_name + "/values"].set_external((float *)&prop_storage_row, num_elements); + } +}; + +/** + * @brief Pass `double[2]`/`double[3]` property values to Conduit node. + */ +template +struct set_prop_val +{ + /** + * `double[3]` properties are saved into `prop_storage` as `multi_array[3]`. + * It consists of 3 pointers to contiguous `double[n]` arrays for x/y/z projections: + * `prop_storage_row[0] = [val_0.x, val_1.x, ..., val_n.x]`, + * `prop_storage_row[1] = [val_0.y, val_1.y, ..., val_n.y]`, + * `prop_storage_row[2] = [val_0.z, val_1.z, ..., val_n.z]`, + * where `val_i` is value of given property for i-th particle. + * + * Similarly for `double[2]`. + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + if constexpr (N == 3) + { + fields[prop_name + "/values/x"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + fields[prop_name + "/values/y"].set_external((double *)&prop_storage_row[1], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + fields[prop_name + "/values/z"].set_external((double *)&prop_storage_row[2], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + } + else if constexpr (N == 2) + { + fields[prop_name + "/values/x"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + fields[prop_name + "/values/y"].set_external((double *)&prop_storage_row[1], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + } + } +}; + +/** + * @brief Pass `float[2]`/`float[3]` property values to Conduit node. + */ +template +struct set_prop_val +{ + /** + * `float[3]` properties are saved into `prop_storage` as `multi_array[3]`. + * It consists of 3 pointers to contiguous `float[n]` arrays for x/y/z projections: + * `prop_storage_row[0] = [val_0.x, val_1.x, ..., val_n.x]`, + * `prop_storage_row[1] = [val_0.y, val_1.y, ..., val_n.y]`, + * `prop_storage_row[2] = [val_0.z, val_1.z, ..., val_n.z]`, + * where `val_i` is value of given property for i-th particle. + * + * Similarly for `float[2]`. + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + if constexpr (N == 3) + { + fields[prop_name + "/values/x"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + fields[prop_name + "/values/y"].set_external((float *)&prop_storage_row[1], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + fields[prop_name + "/values/z"].set_external((float *)&prop_storage_row[2], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + } + else if constexpr (N == 2) + { + fields[prop_name + "/values/x"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + fields[prop_name + "/values/y"].set_external((float *)&prop_storage_row[1], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + } + } +}; + +/** + * @brief Pass `VectorS<2 / 3, double>` property values to Conduit node. + */ +template +struct set_prop_val> +{ + /** + * `VectorS<3, double>` properties are saved into `prop_storage` as sequence of `Point<3, double>` objects. + * x/y/z projections are stored in interleaved fashion: + * `prop_storage_row = [val_0.x, val_0.y, val_0.z, val_1.x, val_1.y, val_1.z, ..., ]` + * where `val_i` is value of given property for i-th particle. + * Iterate over `prop_storage_row` with `stride = 3 * sizeof(double)` and proper offset to extract x/y/z projections. + + * Similarly for `VectorS<2, double>`. + */ + static void set(conduit_cpp::Node &fields, const std::string &prop_name, VectorS &prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + if constexpr (N == 3) + { + fields[prop_name + "/values/x"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/3 * sizeof(double)); + fields[prop_name + "/values/y"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/sizeof(double), /*stride=*/3 * sizeof(double)); + fields[prop_name + "/values/z"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/2 * sizeof(double), /*stride=*/3 * sizeof(double)); + } + else if constexpr (N == 2) + { + fields[prop_name + "/values/x"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/2 * sizeof(double)); + fields[prop_name + "/values/y"].set_external((double *)&prop_storage_row[0], num_elements, /*offset=*/sizeof(double), /*stride=*/2 * sizeof(double)); + } + } +}; + +/** + * @brief Pass `VectorS<2 / 3, float>` property values to Conduit node. + */ +template +struct set_prop_val> +{ + /** + * `VectorS<3, float>` properties are saved into `prop_storage` as sequence of `Point<3, float>` objects. + * x/y/z projections are stored in interleaved fashion: + * `prop_storage_row = [val_0.x, val_0.y, val_0.z, val_1.x, val_1.y, val_1.z, ..., ]` + * where `val_i` is value of given property for i-th particle. + * Iterate over `prop_storage_row` with `stride = 3 * sizeof(float)` and proper offset to extract x/y/z projections. + + * Similarly for `VectorS<2, float>`. + */ + static void set(conduit_cpp::Node &fields, const std::string &prop_name, VectorS &prop_storage_row, size_t num_elements) + { + fields[prop_name + "/association"].set("vertex"); + fields[prop_name + "/topology"].set("mesh"); + fields[prop_name + "/volume_dependent"].set("false"); + if constexpr (N == 3) + { + fields[prop_name + "/values/x"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/3 * sizeof(float)); + fields[prop_name + "/values/y"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/sizeof(float), /*stride=*/3 * sizeof(float)); + fields[prop_name + "/values/z"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/2 * sizeof(float), /*stride=*/3 * sizeof(float)); + } + else if constexpr (N == 2) + { + fields[prop_name + "/values/x"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/0, /*stride=*/2 * sizeof(float)); + fields[prop_name + "/values/y"].set_external((float *)&prop_storage_row[0], num_elements, /*offset=*/sizeof(float), /*stride=*/2 * sizeof(float)); + } + } +}; + +/** + * @brief Pass `double[N][M]` tensor property values to Conduit node, as N*M scalar fields. + */ +template +struct set_prop_val +{ + /** + * `double[N][M]` properties are saved into prop_storage as `multi_array[N][M]`. + * It consists of N*M pointers to contiguous `double[n]` arrays for respective tensor indices projections: + * `prop_storage_row[0][0] = [val_0.0_0, val_1.0_0, ..., val_n.0_0]`, + * `prop_storage_row[0][1] = [val_0.0_1, val_1.0_1, ..., val_n.0_1]`, + * ... + * `prop_storage_row[N-1][M-1] = [val_0.N-1_M-1, val_1.N-1_M-1, ..., val_n.N-1_M-1]`, + * where `val_i` is value of given property for i-th particle. + * + * Tensor is unrolled into N*M scalar properties, named `{tensor}_{i}_{j}`. + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + for (int i = 0; i < N; i++) + { + for (int j = 0; j < M; j++) + { + std::string prop_name_ij = prop_name + "_" + std::to_string(i) + "_" + std::to_string(j); + fields[prop_name_ij + "/association"].set("vertex"); + fields[prop_name_ij + "/topology"].set("mesh"); + fields[prop_name_ij + "/volume_dependent"].set("false"); + fields[prop_name_ij + "/values"].set_external((double *)&prop_storage_row[i][j], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + } + } + } +}; + +/** + * @brief Pass `float[N][M]` tensor property values to Conduit node, as N*M scalar fields. + */ +template +struct set_prop_val +{ + /** + * `float[N][M]` properties are saved into prop_storage as `multi_array[N][M]`. + * It consists of N*M pointers to contiguous `float[n]` arrays for respective tensor indices projections: + * `prop_storage_row[0][0] = [val_0.0_0, val_1.0_0, ..., val_n.0_0]`, + * `prop_storage_row[0][1] = [val_0.0_1, val_1.0_1, ..., val_n.0_1]`, + * ... + * `prop_storage_row[N-1][M-1] = [val_0.N-1_M-1, val_1.N-1_M-1, ..., val_n.N-1_M-1]`, + * where `val_i` is value of given property for i-th particle. + * + * Tensor is unrolled into N*M scalar properties, named `{tensor}_{i}_{j}`. + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + for (int i = 0; i < N; i++) + { + for (int j = 0; j < M; j++) + { + std::string prop_name_ij = prop_name + "_" + std::to_string(i) + "_" + std::to_string(j); + fields[prop_name_ij + "/association"].set("vertex"); + fields[prop_name_ij + "/topology"].set("mesh"); + fields[prop_name_ij + "/volume_dependent"].set("false"); + fields[prop_name_ij + "/values"].set_external((float *)&prop_storage_row[i][j], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + } + } + } +}; + +// Pass double[N][M][L] tensor property values to Conduit node, as N*M*L scalar fields. +template +struct set_prop_val +{ + /* + double[N][M][L] properties are saved into prop_storage as multi_array[N][M][L]. + It consists of N*M*L pointers to contiguous double[n] arrays for respective tensor indices projections: + prop_storage_row[0][0][0] = [val_0.0_0_0, val_1.0_0_0, ..., val_n.0_0_0], + prop_storage_row[0][0][1] = [val_0.0_0_1, val_1.0_0_1, ..., val_n.0_0_1], + ... + prop_storage_row[N-1][M-1][L-1] = [val_0.N-1_M-1_L-1, val_1.N-1_M-1_L-1, ..., val_n.N-1_M-1_L-1], + where val_i is value of given property for i-th particle. + + Tensor is unrolled into N*M*L scalar properties, named "{tensor}_{i}_{j}_{k}". + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + for (int i = 0; i < N; i++) + { + for (int j = 0; j < M; j++) + { + for (int k = 0; k < L; k++) + { + std::string prop_name_ijk = prop_name + "_" + std::to_string(i) + "_" + std::to_string(j) + "_" + std::to_string(k); + fields[prop_name_ijk + "/association"].set("vertex"); + fields[prop_name_ijk + "/topology"].set("mesh"); + fields[prop_name_ijk + "/volume_dependent"].set("false"); + fields[prop_name_ijk + "/values"].set_external((double *)&prop_storage_row[i][j][k], num_elements, /*offset=*/0, /*stride=*/sizeof(double)); + } + } + } + } +}; + +// Pass float[N][M][L] tensor property values to Conduit node, as N*M*L scalar fields. +template +struct set_prop_val +{ + /* + float[N][M][L] properties are saved into prop_storage as multi_array[N][M][L]. + It consists of N*M*L pointers to contiguous float[n] arrays for respective tensor indices projections: + prop_storage_row[0][0][0] = [val_0.0_0_0, val_1.0_0_0, ..., val_n.0_0_0], + prop_storage_row[0][0][1] = [val_0.0_0_1, val_1.0_0_1, ..., val_n.0_0_1], + ... + prop_storage_row[N-1][M-1][L-1] = [val_0.N-1_M-1_L-1, val_1.N-1_M-1_L-1, ..., val_n.N-1_M-1_L-1], + where val_i is value of given property for i-th particle. + + Tensor is unrolled into N*M*L scalar properties, named "{tensor}_{i}_{j}_{k}". + */ + template + static void set(conduit_cpp::Node &fields, const std::string &prop_name, multi_array prop_storage_row, size_t num_elements) + { + for (int i = 0; i < N; i++) + { + for (int j = 0; j < M; j++) + { + for (int k = 0; k < L; k++) + { + std::string prop_name_ijk = prop_name + "_" + std::to_string(i) + "_" + std::to_string(j) + "_" + std::to_string(k); + fields[prop_name_ijk + "/association"].set("vertex"); + fields[prop_name_ijk + "/topology"].set("mesh"); + fields[prop_name_ijk + "/volume_dependent"].set("false"); + fields[prop_name_ijk + "/values"].set_external((float *)&prop_storage_row[i][j][k], num_elements, /*offset=*/0, /*stride=*/sizeof(float)); + } + } + } + } +}; + +/** + * @brief For-each functor, applied to each property in `prop_storage` to pass its values array to Conduit node. + * + * @tparam particles_struct Type of particles data structure: `grid_dist` or `vector_dist`. + * @tparam prop_soa Type of `vector_soa` storage of particles properties values. + * @tparam props Indices of properties to be visualized. + */ +template +struct set_prop_val_functor +{ + const openfpm::vector &prop_names; + conduit_cpp::Node &fields; + /** + * `prop_storage` is heterogeneous `vector_soa` + * storing properties values of particles in SoA fashion; + * `prop_object` is list of chosen properties types. + */ + prop_soa &prop_storage; + + typedef typename to_boost_vmpl::type vprp; + + __device__ __host__ inline set_prop_val_functor(const openfpm::vector &prop_names, conduit_cpp::Node &fields, prop_soa &prop_storage) + : prop_names(prop_names), fields(fields), prop_storage(prop_storage){}; + + template + __device__ __host__ inline void operator()(T &t) const + { + typedef typename boost::mpl::at::type prp_val; + typedef typename boost::mpl::at::type prp_type; + + std::string prop_name; + if (prp_val::value < prop_names.size()) + { + prop_name = prop_names.get(prp_val::value); + } + // Unnamed properties + else + { + prop_name = "attr" + std::to_string(prp_val::value); + } + + set_prop_val::set(fields, prop_name, prop_storage.template get(0), prop_storage.size()); + } +}; + +enum catalyst_adaptor_impl +{ + GRID_DIST_IMPL = 0, + VECTOR_DIST_IMPL = 1 +}; + +template +struct vis_props +{ +}; + +/** + * @brief Catalyst Adaptor interface. Specializations define how to translate particles mesh into Conduit node format. + * + * @tparam particles_struct Type of particles data structure: `grid_dist` or `vector_dist`. + * @tparam vis_props Indices of properties to be visualized. + * @tparam impl Specialization of adaptor: `GRID_DIST_IMPL` or `VECTOR_DIST_IMPL`. + * + * @note Currently, properties indices must cover entire [0, N] segment where N is the maximum index + * among the properties chosen for visualization. "Indices trick" fix is required to map M arbitrary + * properties indices onto [0, M-1] numbering to correctly extract their values from `prop_storage`. + */ +template +class catalyst_adaptor +{ + +public: + catalyst_adaptor() + { + } + + // Initialize Catalyst, pass visualization pipeline scripts from ParaView. + void initialize() + { + std::cout << __FILE__ << ":" << __LINE__ << " The implementetion has not been selected or is unknown " << std::endl; + } + + // Finalize Catalyst. + void finalize() + { + std::cout << __FILE__ << ":" << __LINE__ << " The implementetion has not been selected or is unknown " << std::endl; + } + + /** + * Catalyst in-situ visualization, called by either `catalyst_execute` in the script + * or triggered by pre-defined trigger, e.g. each k-th timestep. + * Translates simulation data structures into Conduit Mesh Blueprint format. + */ + void execute(particles_struct &data) + { + std::cout << __FILE__ << ":" << __LINE__ << " The implementetion has not been selected or is unknown " << std::endl; + } +}; + +#include "catalyst_adaptor_grid_dist.hpp" +#include "catalyst_adaptor_vector_dist.hpp" + +#endif /* HAVE_CATALYST */ + +#endif /* CATALYST_ADAPTOR_HPP_ */ \ No newline at end of file diff --git a/src/Catalyst/catalyst_adaptor_grid_dist.hpp b/src/Catalyst/catalyst_adaptor_grid_dist.hpp new file mode 100644 index 0000000..cfffd07 --- /dev/null +++ b/src/Catalyst/catalyst_adaptor_grid_dist.hpp @@ -0,0 +1,147 @@ +#include "config.h" + +#ifdef HAVE_CATALYST +#include "catalyst_adaptor.hpp" + +#include +#include +#include + +/** + * @brief Catalyst Adaptor for structured particles mesh, stored in `grid_dist`-like data structures. + * + * @tparam grid_type Type of particles data structure. + * @tparam props Indices of properties to be visualized. + */ +template +class catalyst_adaptor, catalyst_adaptor_impl::GRID_DIST_IMPL> +{ + // Create typename for chosen properties. + typedef object::type> prop_object; + // SoA storage for chosen properties, split across local sub-grids. + openfpm::vector> prop_storage; + // Type of particles coordinates - float/double. + typedef typename grid_type::stype stype; + +public: + catalyst_adaptor() + { + } + + void initialize(const openfpm::vector &scripts) + { + conduit_cpp::Node node; + + for (int i = 0; i < scripts.size(); i++) + { + node["catalyst/scripts/script" + std::to_string(i)].set_string(scripts.get(i)); + } + + catalyst_status err = catalyst_initialize(conduit_cpp::c_node(&node)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to initialize Catalyst: " << err << std::endl; + } + } + + void finalize() + { + conduit_cpp::Node node; + catalyst_status err = catalyst_finalize(conduit_cpp::c_node(&node)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to finalize Catalyst: " << err << std::endl; + } + } + + void execute(grid_type &grid, size_t cycle = 0, double time = 0, const std::string &channel_name = "grid") + { + // Simulation state + conduit_cpp::Node exec_params; + auto state = exec_params["catalyst/state"]; + state["timestep"].set(cycle); + state["time"].set(time); + + // Mesh + auto channel = exec_params["catalyst/channels/" + channel_name]; + channel["type"].set("mesh"); + auto mesh = channel["data"]; + mesh["coordsets/coords/type"].set("uniform"); + + // Particles coordinates and properties + // Iterate over local sub-grids stored on current processor and fill data about coordinates and properties. + prop_storage.resize(grid.getN_loc_grid()); + + for (size_t grid_num = 0; grid_num < grid.getN_loc_grid(); grid_num++) + { + auto box = grid.getLocalGridsInfo().get(grid_num).Dbox; + auto offset = grid.getLocalGridsInfo().get(grid_num).origin; + + // FIXME: What is happening here? + for (int dim = 0; dim < grid_type::dims; dim++) + { + if (offset.get(dim) + box.getHigh(dim) + 1 != grid.size(dim)) + { + box.setHigh(dim, box.getHigh(dim) + 1); + } + } + + // Extract local sub-grid, iterate over its particles and copy (subset of) their properties into SoA storage. + auto &grid_loc = grid.get_loc_grid(grid_num); + prop_storage.get(grid_num).resize(box.getVolumeKey()); + auto &prop_storage_loc = prop_storage.get(grid_num); + + auto it = grid_loc.getIterator(box.getKP1(), box.getKP2()); + size_t particle_idx = 0; + + while (it.isNext()) + { + auto particle = it.get(); + object_s_di(grid_loc.template get_o(particle), prop_storage_loc.template get(particle_idx)); + ++it; + ++particle_idx; + } + + // Set size of sub-grid along XYZ axes. + mesh["coordsets/coords/dims/i"].set(box.getHigh(0) - box.getLow(0) + 1); + mesh["coordsets/coords/dims/j"].set(box.getHigh(1) - box.getLow(1) + 1); + mesh["coordsets/coords/dims/k"].set(box.getHigh(2) - box.getLow(2) + 1); + + // FIXME: What is happening here? + Point poffset; + for (int i = 0; i < grid_type::dims; i++) + { + poffset.get(i) = (offset.get(i) + box.getLow(i)) * grid.getSpacing()[i]; + } + + // Set origin of local sub-grid. + mesh["coordsets/coords/origin/x"].set(poffset.get(0)); + mesh["coordsets/coords/origin/y"].set(poffset.get(1)); + mesh["coordsets/coords/origin/z"].set(poffset.get(2)); + + // Set spacing of local sub-grid. + mesh["coordsets/coords/spacing/dx"].set(grid.getSpacing()[0]); + mesh["coordsets/coords/spacing/dy"].set(grid.getSpacing()[1]); + mesh["coordsets/coords/spacing/dz"].set(grid.getSpacing()[2]); + + // Topology + mesh["topologies/mesh/type"].set("uniform"); + mesh["topologies/mesh/coordset"].set("coords"); + + // Fields + auto fields = mesh["fields"]; + auto &prop_names = grid.getPropNames(); + set_prop_val_functor, props...> prop_setter(prop_names, fields, prop_storage_loc); + boost::mpl::for_each_ref>(prop_setter); + } + + // Execute + catalyst_status err = catalyst_execute(conduit_cpp::c_node(&exec_params)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to execute Catalyst: " << err << std::endl; + } + } +}; + +#endif /* HAVE_CATALYST */ \ No newline at end of file diff --git a/src/Catalyst/catalyst_adaptor_vector_dist.hpp b/src/Catalyst/catalyst_adaptor_vector_dist.hpp new file mode 100644 index 0000000..778de87 --- /dev/null +++ b/src/Catalyst/catalyst_adaptor_vector_dist.hpp @@ -0,0 +1,121 @@ +#include "config.h" + +#ifdef HAVE_CATALYST +#include "catalyst_adaptor.hpp" + +#include +#include +#include + +/** + * @brief Catalyst Adaptor for unstructured particles mesh, stored in `vector_dist`-like data structures. + * + * @tparam vector_type Type of particles data structure. + * @tparam props Indices of properties to be visualized. + */ +template +class catalyst_adaptor, catalyst_adaptor_impl::VECTOR_DIST_IMPL> +{ + // Create typename for chosen properties. + typedef object::type> prop_object; + // SoA storage for chosen properties. + openfpm::vector_soa prop_storage; + // Type of particles coordinates (float/double). + typedef typename vector_type::stype stype; + +public: + catalyst_adaptor() + { + } + + void initialize(const openfpm::vector &scripts) + { + conduit_cpp::Node node; + + for (int i = 0; i < scripts.size(); i++) + { + node["catalyst/scripts/script" + std::to_string(i)].set_string(scripts.get(i)); + } + + catalyst_status err = catalyst_initialize(conduit_cpp::c_node(&node)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to initialize Catalyst: " << err << std::endl; + } + } + + void finalize() + { + conduit_cpp::Node node; + catalyst_status err = catalyst_finalize(conduit_cpp::c_node(&node)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to finalize Catalyst: " << err << std::endl; + } + } + + void execute(vector_type &particles, size_t cycle = 0, double time = 0, const std::string &channel_name = "particles") + { + size_t num_particles = particles.size_local(); + + // Simulation state + conduit_cpp::Node exec_params; + auto state = exec_params["catalyst/state"]; + state["timestep"].set(cycle); + state["time"].set(time); + + // Mesh + auto channel = exec_params["catalyst/channels/" + channel_name]; + channel["type"].set_string("mesh"); + auto mesh = channel["data"]; + mesh["coordsets/coords/type"].set_string("explicit"); + + // Particles coordinates + auto pos_vector = particles.getPosVector(); + + // Coordinates are stored in pos_vector in interleaved fashion: + // pos_vector = [pos_0.x, pos_0.y, pos_0.z, pos_1.x, pos_1.y, pos_1.z, ..., ]. + // Iterate over pos_vector with stride = 3 * sizeof(stype) and proper offset to extract x/y/z projections. + if constexpr (vector_type::dims == 3) { + mesh["coordsets/coords/values/x"].set_external((stype *)&pos_vector.template get<0>(0)[0], num_particles, /*offset=*/0, /*stride=*/3 * sizeof(stype)); + mesh["coordsets/coords/values/y"].set_external((stype *)&pos_vector.template get<0>(0)[0], num_particles, /*offset=*/sizeof(stype), /*stride=*/3 * sizeof(stype)); + mesh["coordsets/coords/values/z"].set_external((stype *)&pos_vector.template get<0>(0)[0], num_particles, /*offset=*/2 * sizeof(stype), /*stride=*/3 * sizeof(stype)); + } else if constexpr (vector_type::dims == 2) { + mesh["coordsets/coords/values/x"].set_external((stype *)&pos_vector.template get<0>(0)[0], num_particles, /*offset=*/0, /*stride=*/2 * sizeof(stype)); + mesh["coordsets/coords/values/y"].set_external((stype *)&pos_vector.template get<0>(0)[0], num_particles, /*offset=*/sizeof(stype), /*stride=*/2 * sizeof(stype)); + } + + // Topology + mesh["topologies/mesh/type"].set_string("unstructured"); + mesh["topologies/mesh/coordset"].set_string("coords"); + mesh["topologies/mesh/elements/shape"].set_string("point"); + // Connectivity is represented by index array of particles. + openfpm::vector connectivity(num_particles); + std::iota(connectivity.begin(), connectivity.end(), 0); + mesh["topologies/mesh/elements/connectivity"].set_external(&connectivity.get(0), connectivity.size()); + + // Fields + auto fields = mesh["fields"]; + // Iterate over particles and copy their chosen properties into SoA storage. + prop_storage.resize(num_particles); + auto prop_vector = particles.getPropVector(); + for (size_t i = 0; i < num_particles; i++) + { + object_s_di(prop_vector.template get(i), prop_storage.template get(i)); + } + + // Iterate over properties and pass respective fields values to Conduit node with for-each functor. + auto &prop_names = particles.getPropNames(); + set_prop_val_functor, props...> prop_setter(prop_names, fields, prop_storage); + boost::mpl::for_each_ref>(prop_setter); + + // Execute + catalyst_status err = catalyst_execute(conduit_cpp::c_node(&exec_params)); + if (err != catalyst_status_ok) + { + std::cerr << "Failed to execute Catalyst: " << err << std::endl; + } + } +}; + +#endif /* HAVE_CATALYST */ \ No newline at end of file diff --git a/src/Catalyst/catalyst_unit_test.cpp b/src/Catalyst/catalyst_unit_test.cpp new file mode 100644 index 0000000..a73e383 --- /dev/null +++ b/src/Catalyst/catalyst_unit_test.cpp @@ -0,0 +1,199 @@ +#define BOOST_TEST_DYN_LINK +#include +#include "Grid/grid_dist_id.hpp" +#include "Vector/vector_dist_subset.hpp" +#include "Grid/map_grid.hpp" + +#include "config.h" + +#ifdef HAVE_CATALYST +#include +#include "Catalyst/catalyst_adaptor.hpp" + +BOOST_AUTO_TEST_SUITE(catalyst_test_suite) + +#ifdef HAVE_PNG +#include +#include + +// Test utilities to compare Catalyst-produced image with ground truth. +struct PixelInserter +{ + openfpm::vector &storage; + + PixelInserter(openfpm::vector &s) : storage(s) {} + + void operator()(boost::gil::rgb8_pixel_t p) const + { + storage.add(boost::gil::at_c<0>(p)); + storage.add(boost::gil::at_c<1>(p)); + storage.add(boost::gil::at_c<2>(p)); + } +}; + +void check_png(const std::string &img1_path, const std::string &img2_path) +{ + auto &v_cl = create_vcluster(); + + if (v_cl.rank() == 0) + { + // Check the file has been generated (we do not check the content) + boost::gil::rgb8_image_t img1, img2; + // FIXME: Ensure both images are accessible + boost::gil::read_image(img1_path.c_str(), img1, boost::gil::png_tag()); + boost::gil::read_image(img2_path.c_str(), img2, boost::gil::png_tag()); + + openfpm::vector storage1, storage2; + + for_each_pixel(const_view(img1), PixelInserter(storage1)); + for_each_pixel(const_view(img2), PixelInserter(storage2)); + + BOOST_REQUIRE(storage1.size() != 0); + BOOST_REQUIRE(storage2.size() != 0); + + BOOST_REQUIRE_EQUAL(storage1.size(), storage2.size()); + + size_t diff = 0; + for (int i = 0; i < storage1.size(); i++) + { + diff += abs(storage1.get(i) - storage2.get(i)); + } + + BOOST_REQUIRE(diff < 3000000); + } +} +#endif + +BOOST_AUTO_TEST_CASE(catalyst_grid_dist_test) +{ + Vcluster<> &v_cl = create_vcluster(); + size_t sz[3] = {40, 40, 40}; + size_t bc[3] = {NON_PERIODIC, NON_PERIODIC, NON_PERIODIC}; + Box<3, double> domain({0, 0, 0}, {2.0, 2.0, 2.0}); + Ghost<3, double> g(3. * 2. / (sz[0] - 1)); + grid_dist_id<3, double, aggregate, double[3][3]>> grid(sz, domain, g); + + openfpm::vector prop_names({"scalar", "vector", "tensor"}); + enum + { + SCALAR, + VECTOR, + TENSOR + }; + grid.setPropNames(prop_names); + + auto it = grid.getDomainIterator(); + while (it.isNext()) + { + auto key = it.get(); + auto pos = it.getGKey(key); + + double x = pos.get(0) * grid.getSpacing()[0]; + double y = pos.get(1) * grid.getSpacing()[1]; + double z = pos.get(2) * grid.getSpacing()[2]; + + grid.get(key) = x + y + z; + + grid.get(key)[0] = sin(x + y); + grid.get(key)[1] = cos(x + y); + grid.get(key)[2] = 0.0; + + grid.get(key)[0][0] = x*x; + grid.get(key)[0][1] = x*y; + grid.get(key)[0][2] = x*z; + + grid.get(key)[1][0] = y*x; + grid.get(key)[1][1] = y*y; + grid.get(key)[1][2] = y*z; + + grid.get(key)[2][0] = z*x; + grid.get(key)[2][1] = z*y; + grid.get(key)[2][2] = z*z; + + + ++it; + } + + grid.map(); + grid.ghost_get(); + // Generate representative dataset to create visualization pipeline in ParaView + //grid.write("grid_dist_test_data"); + + // Visualization with Catalyst Adaptor + // Images are written to ./datasets/ folder by default + catalyst_adaptor, catalyst_adaptor_impl::GRID_DIST_IMPL> adaptor; + openfpm::vector scripts({{"./test_data/catalyst_grid_dist_pipeline.py"}}); + adaptor.initialize(scripts); + adaptor.execute(grid); + adaptor.finalize(); + +#ifdef HAVE_PNG + check_png("./datasets/catalyst_grid_dist.png", "./test_data/catalyst_grid_dist_ground_truth.png"); +#endif +} + +BOOST_AUTO_TEST_CASE(catalyst_vector_dist_test) +{ + Vcluster<> &v_cl = create_vcluster(); + size_t sz[3] = {40, 40, 40}; + size_t bc[3] = {NON_PERIODIC, NON_PERIODIC, NON_PERIODIC}; + Box<3, double> domain({0, 0, 0}, {2.0, 2.0, 2.0}); + Ghost<3, double> g(3. * 2. / (sz[0] - 1)); + vector_dist_ws<3, double, aggregate, double[3][3]>> particles(0, domain, bc, g); + openfpm::vector prop_names({"scalar", "vector", "tensor"}); + enum + { + SCALAR, + VECTOR, + TENSOR + }; + particles.setPropNames(prop_names); + + // Assign scalar = x + y + z, vector = {sin(x + y), cos(x + y), 0}, tensor = {{x^2, xy}, {-xy, y^2}} properties to the particles + auto it = particles.getGridIterator(sz); + while (it.isNext()) + { + particles.add(); + auto key = it.get(); + double x = key.get(0) * it.getSpacing(0); + particles.getLastPos()[0] = x; + double y = key.get(1) * it.getSpacing(1); + particles.getLastPos()[1] = y; + double z = key.get(2) * it.getSpacing(2); + particles.getLastPos()[2] = z; + + particles.getLastProp() = x + y + z; + + particles.getLastProp()[0] = sin(x + y); + particles.getLastProp()[1] = cos(x + y); + particles.getLastProp()[2] = 0.0; + + particles.getLastProp()[0][0] = x*x; + particles.getLastProp()[0][1] = x*y; + particles.getLastProp()[1][0] = -x*y; + particles.getLastProp()[1][1] = y*y; + + ++it; + } + + particles.map(); + particles.ghost_get(); + // Generate representative dataset to create visualization pipeline in ParaView + //particles.write("vector_dist_test_data"); + + // Visualization with Catalyst Adaptor + // Images are written to ./datasets/ folder by default + catalyst_adaptor, catalyst_adaptor_impl::VECTOR_DIST_IMPL> adaptor; + openfpm::vector scripts({{"./test_data/catalyst_vector_dist_pipeline.py"}}); + adaptor.initialize(scripts); + adaptor.execute(particles); + adaptor.finalize(); + +#ifdef HAVE_PNG + check_png("./datasets/catalyst_vector_dist.png", "./test_data/catalyst_vector_dist_ground_truth.png"); +#endif +} + +BOOST_AUTO_TEST_SUITE_END() + +#endif /* HAVE_CATALYST */ \ No newline at end of file diff --git a/test_data/catalyst_grid_dist_ground_truth.png b/test_data/catalyst_grid_dist_ground_truth.png new file mode 100644 index 0000000..85dc2a4 Binary files /dev/null and b/test_data/catalyst_grid_dist_ground_truth.png differ diff --git a/test_data/catalyst_grid_dist_pipeline.py b/test_data/catalyst_grid_dist_pipeline.py new file mode 100644 index 0000000..bb0d1e1 --- /dev/null +++ b/test_data/catalyst_grid_dist_pipeline.py @@ -0,0 +1,190 @@ +# script-version: 2.0 +# Catalyst state generated using paraview version 5.11.1-1889-ge9d6cfcddb +import paraview +paraview.compatibility.major = 5 +paraview.compatibility.minor = 11 + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# ---------------------------------------------------------------- +# setup views used in the visualization +# ---------------------------------------------------------------- + +# Create a new 'Render View' +renderView1 = CreateView('RenderView') +renderView1.ViewSize = [1248, 701] +renderView1.AxesGrid = 'Grid Axes 3D Actor' +renderView1.CenterOfRotation = [1.0, 1.0, 1.0] +renderView1.StereoType = 'Crystal Eyes' +renderView1.CameraPosition = [6.407338041347557, 2.4224459328701355, -1.3001175492369867] +renderView1.CameraFocalPoint = [0.4220476159958419, 0.8479647449941072, 1.2458434096234228] +renderView1.CameraViewUp = [-0.18527777360981507, 0.9689621393402568, 0.16365976637934837] +renderView1.CameraFocalDisk = 1.0 +renderView1.CameraParallelScale = 1.7320508075688772 +renderView1.LegendGrid = 'Legend Grid Actor' + +SetActiveView(None) + +# ---------------------------------------------------------------- +# setup view layouts +# ---------------------------------------------------------------- + +# create new layout object 'Layout #1' +layout1 = CreateLayout(name='Layout #1') +layout1.AssignView(0, renderView1) +layout1.SetSize(1248, 701) + +# ---------------------------------------------------------------- +# restore active view +SetActiveView(renderView1) +# ---------------------------------------------------------------- + +# ---------------------------------------------------------------- +# setup the data processing pipelines +# ---------------------------------------------------------------- + +# create a new 'XML PolyData Reader' +grid = TrivialProducer(registrationName='grid') +grid.PointArrayStatus = ['scalar', 'tensor_0_0', 'tensor_0_1', 'tensor_0_2', 'tensor_1_0', 'tensor_1_1', 'tensor_1_2', 'tensor_2_0', 'tensor_2_1', 'tensor_2_2', 'vector'] +grid.TimeArray = 'None' + +# create a new 'Glyph' +glyph = Glyph(registrationName='Glyph', Input=grid, + GlyphType='Arrow') +glyph.OrientationArray = ['POINTS', 'vector'] +glyph.ScaleArray = ['POINTS', 'scalar'] +glyph.ScaleFactor = 0.04 +glyph.GlyphTransform = 'Transform2' + +# ---------------------------------------------------------------- +# setup the visualization in view 'renderView1' +# ---------------------------------------------------------------- + +# show data from glyph +glyphDisplay = Show(glyph, renderView1, 'GeometryRepresentation') + +# get 2D transfer function for 'tensor_1_1' +tensor_1_1TF2D = GetTransferFunction2D('tensor_1_1') +tensor_1_1TF2D.ScalarRangeInitialized = 1 +tensor_1_1TF2D.Range = [0.0, 4.0, 0.0, 1.0] + +# get color transfer function/color map for 'tensor_1_1' +tensor_1_1LUT = GetColorTransferFunction('tensor_1_1') +tensor_1_1LUT.TransferFunction2D = tensor_1_1TF2D +tensor_1_1LUT.RGBPoints = [0.0, 0.231373, 0.298039, 0.752941, 2.0, 0.865003, 0.865003, 0.865003, 4.0, 0.705882, 0.0156863, 0.14902] +tensor_1_1LUT.ScalarRangeInitialized = 1.0 + +# trace defaults for the display properties. +glyphDisplay.Representation = 'Surface' +glyphDisplay.ColorArrayName = ['POINTS', 'tensor_1_1'] +glyphDisplay.LookupTable = tensor_1_1LUT +glyphDisplay.SelectTCoordArray = 'None' +glyphDisplay.SelectNormalArray = 'None' +glyphDisplay.SelectTangentArray = 'None' +glyphDisplay.OSPRayScaleArray = 'scalar' +glyphDisplay.OSPRayScaleFunction = 'Piecewise Function' +glyphDisplay.Assembly = '' +glyphDisplay.SelectOrientationVectors = 'None' +glyphDisplay.ScaleFactor = 0.27553581036627295 +glyphDisplay.SelectScaleArray = 'None' +glyphDisplay.GlyphType = 'Arrow' +glyphDisplay.GlyphTableIndexArray = 'None' +glyphDisplay.GaussianRadius = 0.013776790518313646 +glyphDisplay.SetScaleArray = ['POINTS', 'scalar'] +glyphDisplay.ScaleTransferFunction = 'Piecewise Function' +glyphDisplay.OpacityArray = ['POINTS', 'scalar'] +glyphDisplay.OpacityTransferFunction = 'Piecewise Function' +glyphDisplay.DataAxesGrid = 'Grid Axes Representation' +glyphDisplay.PolarAxes = 'Polar Axes Representation' +glyphDisplay.SelectInputVectors = ['POINTS', 'vector'] +glyphDisplay.WriteLog = '' + +# init the 'Piecewise Function' selected for 'ScaleTransferFunction' +glyphDisplay.ScaleTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 5.948717948717949, 1.0, 0.5, 0.0] + +# init the 'Piecewise Function' selected for 'OpacityTransferFunction' +glyphDisplay.OpacityTransferFunction.Points = [0.0, 0.0, 0.5, 0.0, 5.948717948717949, 1.0, 0.5, 0.0] + +# setup the color legend parameters for each legend in this view + +# get color legend/bar for tensor_1_1LUT in view renderView1 +tensor_1_1LUTColorBar = GetScalarBar(tensor_1_1LUT, renderView1) +tensor_1_1LUTColorBar.Title = 'tensor_1_1' +tensor_1_1LUTColorBar.ComponentTitle = '' + +# set color bar visibility +tensor_1_1LUTColorBar.Visibility = 1 + +# show color legend +glyphDisplay.SetScalarBarVisibility(renderView1, True) + +# ---------------------------------------------------------------- +# setup color maps and opacity maps used in the visualization +# note: the Get..() functions create a new object, if needed +# ---------------------------------------------------------------- + +# get opacity transfer function/opacity map for 'tensor_1_1' +tensor_1_1PWF = GetOpacityTransferFunction('tensor_1_1') +tensor_1_1PWF.Points = [0.0, 0.0, 0.5, 0.0, 4.0, 1.0, 0.5, 0.0] +tensor_1_1PWF.ScalarRangeInitialized = 1 + +# ---------------------------------------------------------------- +# setup animation scene, tracks and keyframes +# note: the Get..() functions create a new object, if needed +# ---------------------------------------------------------------- + +# get the time-keeper +timeKeeper1 = GetTimeKeeper() + +# initialize the timekeeper + +# get time animation track +timeAnimationCue1 = GetTimeTrack() + +# initialize the animation track + +# get animation scene +animationScene1 = GetAnimationScene() + +# initialize the animation scene +animationScene1.ViewModules = renderView1 +animationScene1.Cues = timeAnimationCue1 +animationScene1.AnimationTime = 0.0 + +# initialize the animation scene + +# ---------------------------------------------------------------- +# setup extractors +# ---------------------------------------------------------------- + +# create extractor +pNG1 = CreateExtractor('PNG', renderView1, registrationName='PNG1') +# trace defaults for the extractor. +pNG1.Trigger = 'Time Step' + +# init the 'PNG' selected for 'Writer' +pNG1.Writer.FileName = 'catalyst_grid_dist.png' +pNG1.Writer.ImageResolution = [1920, 1080] +pNG1.Writer.Format = 'PNG' + +# ---------------------------------------------------------------- +# restore active source +SetActiveSource(grid) +# ---------------------------------------------------------------- + +# ------------------------------------------------------------------------------ +# Catalyst options +from paraview import catalyst +options = catalyst.Options() +options.GlobalTrigger = 'Time Step' +options.CatalystLiveTrigger = 'Time Step' + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + from paraview.simple import SaveExtractsUsingCatalystOptions + # Code for non in-situ environments; if executing in post-processing + # i.e. non-Catalyst mode, let's generate extracts using Catalyst options + SaveExtractsUsingCatalystOptions(options) diff --git a/test_data/catalyst_vector_dist_ground_truth.png b/test_data/catalyst_vector_dist_ground_truth.png new file mode 100644 index 0000000..85dc2a4 Binary files /dev/null and b/test_data/catalyst_vector_dist_ground_truth.png differ diff --git a/test_data/catalyst_vector_dist_pipeline.py b/test_data/catalyst_vector_dist_pipeline.py new file mode 100644 index 0000000..e4f1dd9 --- /dev/null +++ b/test_data/catalyst_vector_dist_pipeline.py @@ -0,0 +1,188 @@ +# script-version: 2.0 +# Catalyst state generated using paraview version 5.11.1-1889-ge9d6cfcddb +import paraview +paraview.compatibility.major = 5 +paraview.compatibility.minor = 11 + +#### import the simple module from the paraview +from paraview.simple import * +#### disable automatic camera reset on 'Show' +paraview.simple._DisableFirstRenderCameraReset() + +# ---------------------------------------------------------------- +# setup views used in the visualization +# ---------------------------------------------------------------- + +# Create a new 'Render View' +renderView1 = CreateView('RenderView') +renderView1.ViewSize = [1248, 701] +renderView1.AxesGrid = 'Grid Axes 3D Actor' +renderView1.CenterOfRotation = [1.0, 1.0, 1.0] +renderView1.StereoType = 'Crystal Eyes' +renderView1.CameraPosition = [6.407338041347557, 2.4224459328701355, -1.3001175492369867] +renderView1.CameraFocalPoint = [0.4220476159958419, 0.8479647449941072, 1.2458434096234228] +renderView1.CameraViewUp = [-0.18527777360981507, 0.9689621393402568, 0.16365976637934837] +renderView1.CameraFocalDisk = 1.0 +renderView1.CameraParallelScale = 1.7320508075688772 +renderView1.LegendGrid = 'Legend Grid Actor' + +SetActiveView(None) + +# ---------------------------------------------------------------- +# setup view layouts +# ---------------------------------------------------------------- + +# create new layout object 'Layout #1' +layout1 = CreateLayout(name='Layout #1') +layout1.AssignView(0, renderView1) +layout1.SetSize(1248, 701) + +# ---------------------------------------------------------------- +# restore active view +SetActiveView(renderView1) +# ---------------------------------------------------------------- + +# ---------------------------------------------------------------- +# setup the data processing pipelines +# ---------------------------------------------------------------- + +# create a new 'XML PolyData Reader' +particles = TrivialProducer(registrationName='particles') +particles.PointArrayStatus = ['scalar', 'tensor_0_0', 'tensor_0_1', 'tensor_0_2', 'tensor_1_0', 'tensor_1_1', 'tensor_1_2', 'tensor_2_0', 'tensor_2_1', 'tensor_2_2', 'vector'] +particles.TimeArray = 'None' + +# create a new 'Glyph' +glyph = Glyph(registrationName='Glyph', Input=particles, + GlyphType='Arrow') +glyph.OrientationArray = ['POINTS', 'vector'] +glyph.ScaleArray = ['POINTS', 'scalar'] +glyph.ScaleFactor = 0.04 +glyph.GlyphTransform = 'Transform2' + +# ---------------------------------------------------------------- +# setup the visualization in view 'renderView1' +# ---------------------------------------------------------------- + +# show data from glyph +glyphDisplay = Show(glyph, renderView1, 'GeometryRepresentation') + +# get 2D transfer function for 'tensor_1_1' +tensor_1_1TF2D = GetTransferFunction2D('tensor_1_1') + +# get color transfer function/color map for 'tensor_1_1' +tensor_1_1LUT = GetColorTransferFunction('tensor_1_1') +tensor_1_1LUT.TransferFunction2D = tensor_1_1TF2D +tensor_1_1LUT.RGBPoints = [0.0, 0.231373, 0.298039, 0.752941, 2.0, 0.865003, 0.865003, 0.865003, 4.0, 0.705882, 0.0156863, 0.14902] +tensor_1_1LUT.ScalarRangeInitialized = 1.0 + +# trace defaults for the display properties. +glyphDisplay.Representation = 'Surface' +glyphDisplay.ColorArrayName = ['POINTS', 'tensor_1_1'] +glyphDisplay.LookupTable = tensor_1_1LUT +glyphDisplay.SelectTCoordArray = 'None' +glyphDisplay.SelectNormalArray = 'None' +glyphDisplay.SelectTangentArray = 'None' +glyphDisplay.OSPRayScaleArray = 'scalar' +glyphDisplay.OSPRayScaleFunction = 'Piecewise Function' +glyphDisplay.Assembly = '' +glyphDisplay.SelectOrientationVectors = 'None' +glyphDisplay.ScaleFactor = 0.21802423987537622 +glyphDisplay.SelectScaleArray = 'None' +glyphDisplay.GlyphType = 'Arrow' +glyphDisplay.GlyphTableIndexArray = 'None' +glyphDisplay.GaussianRadius = 0.010901211993768811 +glyphDisplay.SetScaleArray = ['POINTS', 'scalar'] +glyphDisplay.ScaleTransferFunction = 'Piecewise Function' +glyphDisplay.OpacityArray = ['POINTS', 'scalar'] +glyphDisplay.OpacityTransferFunction = 'Piecewise Function' +glyphDisplay.DataAxesGrid = 'Grid Axes Representation' +glyphDisplay.PolarAxes = 'Polar Axes Representation' +glyphDisplay.SelectInputVectors = ['POINTS', 'vector'] +glyphDisplay.WriteLog = '' + +# init the 'Piecewise Function' selected for 'ScaleTransferFunction' +glyphDisplay.ScaleTransferFunction.Points = [0.1538461538461539, 0.0, 0.5, 0.0, 5.794871794871795, 1.0, 0.5, 0.0] + +# init the 'Piecewise Function' selected for 'OpacityTransferFunction' +glyphDisplay.OpacityTransferFunction.Points = [0.1538461538461539, 0.0, 0.5, 0.0, 5.794871794871795, 1.0, 0.5, 0.0] + +# setup the color legend parameters for each legend in this view + +# get color legend/bar for tensor_1_1LUT in view renderView1 +tensor_1_1LUTColorBar = GetScalarBar(tensor_1_1LUT, renderView1) +tensor_1_1LUTColorBar.Title = 'tensor_1_1' +tensor_1_1LUTColorBar.ComponentTitle = '' + +# set color bar visibility +tensor_1_1LUTColorBar.Visibility = 1 + +# show color legend +glyphDisplay.SetScalarBarVisibility(renderView1, True) + +# ---------------------------------------------------------------- +# setup color maps and opacity maps used in the visualization +# note: the Get..() functions create a new object, if needed +# ---------------------------------------------------------------- + +# get opacity transfer function/opacity map for 'tensor_1_1' +tensor_1_1PWF = GetOpacityTransferFunction('tensor_1_1') +tensor_1_1PWF.Points = [0.0, 0.0, 0.5, 0.0, 4.0, 1.0, 0.5, 0.0] +tensor_1_1PWF.ScalarRangeInitialized = 1 + +# ---------------------------------------------------------------- +# setup animation scene, tracks and keyframes +# note: the Get..() functions create a new object, if needed +# ---------------------------------------------------------------- + +# get the time-keeper +timeKeeper1 = GetTimeKeeper() + +# initialize the timekeeper + +# get time animation track +timeAnimationCue1 = GetTimeTrack() + +# initialize the animation track + +# get animation scene +animationScene1 = GetAnimationScene() + +# initialize the animation scene +animationScene1.ViewModules = renderView1 +animationScene1.Cues = timeAnimationCue1 +animationScene1.AnimationTime = 0.0 + +# initialize the animation scene + +# ---------------------------------------------------------------- +# setup extractors +# ---------------------------------------------------------------- + +# create extractor +pNG1 = CreateExtractor('PNG', renderView1, registrationName='PNG1') +# trace defaults for the extractor. +pNG1.Trigger = 'Time Step' + +# init the 'PNG' selected for 'Writer' +pNG1.Writer.FileName = 'catalyst_vector_dist.png' +pNG1.Writer.ImageResolution = [1920, 1080] +pNG1.Writer.Format = 'PNG' + +# ---------------------------------------------------------------- +# restore active source +SetActiveSource(pNG1) +# ---------------------------------------------------------------- + +# ------------------------------------------------------------------------------ +# Catalyst options +from paraview import catalyst +options = catalyst.Options() +options.GlobalTrigger = 'Time Step' +options.CatalystLiveTrigger = 'Time Step' + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + from paraview.simple import SaveExtractsUsingCatalystOptions + # Code for non in-situ environments; if executing in post-processing + # i.e. non-Catalyst mode, let's generate extracts using Catalyst options + SaveExtractsUsingCatalystOptions(options)