diff --git a/.gitmodules b/.gitmodules index 31608b609..f1281c31d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "vendor/run-clang-format"] path = vendor/run-clang-format url = https://github.com/Sarcasm/run-clang-format.git +[submodule "vendor/catalyst-2.0.0"] + path = vendor/catalyst-2.0.0 + url = https://gitlab.kitware.com/paraview/catalyst diff --git a/AUTHORS b/AUTHORS index 402523718..fc82df51f 100644 --- a/AUTHORS +++ b/AUTHORS @@ -25,6 +25,9 @@ Benoit Martin - CEA (bmartin@cea.fr) * support for const data in `PDI_share`, `PDI_expose` and `PDI_multi_expose` * Initial implementation of the cmake test +François Mazen - Kitware (francois.mazen@kitware.com) +* Catalyst plugin creation + François-Xavier Mordant - CEA (francois-xavier.mordant@cea.fr) * Fixed CMake issues, internal API enhancement * Bug fix, JSON plugin diff --git a/CHANGELOG.md b/CHANGELOG.md index 27e19259a..2b6ccb24d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,8 @@ and this project adheres to ## [Unreleased] -### For users +### Added +* Add Catalyst plugin from Kitware [#496](https://github.com/pdidev/pdi/pull/496) #### Added diff --git a/CMakeLists.txt b/CMakeLists.txt index 0b077743a..e64660c4a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ #============================================================================= # Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# Copyright (C) 2024-2025 Kitware SAS # # All rights reserved. # @@ -76,7 +77,7 @@ option(BUILD_SHARED_LIBS "Build shared libraries rather than static ones" option(BUILD_TRACE_PLUGIN "Build Trace plugin" ON) option(BUILD_USER_CODE_PLUGIN "Build User-code plugin" ON) option(BUILD_JSON_PLUGIN "Build JSON plugin" OFF) -option(ENABLE_BENCHMARKING "Activate benchmarks in the test suite" OFF) +option(BUILD_CATALYST_PLUGIN "Build Catalyst plugin" "${BUILD_UNSTABLE}") @@ -419,6 +420,23 @@ if("${BUILD_PYTHON}") ) endif() +## Catalyst +if("${BUILD_CATALYST_PLUGIN}") + set(CATALYST_WRAP_PYTHON OFF) + if("${BUILD_PYTHON}") + set(CATALYST_WRAP_PYTHON ON) + endif() + sbuild_add_dependency(catalyst "${USE_DEFAULT}" + EMBEDDED_PATH "vendor/catalyst-2.0.0" + CMAKE_CACHE_ARGS + "-DBUILD_TESTING:BOOL=OFF" + "-DCATALYST_BUILD_TESTING:BOOL=OFF" + "-DCATALYST_USE_MPI:BOOL=ON" + "-DCATALYST_WRAP_FORTRAN:BOOL=OFF" + "-DCATALYST_WRAP_PYTHON:BOOL=${CATALYST_WRAP_PYTHON}" + VERSION 2.0.0 + ) +endif() ## JSON @@ -536,3 +554,10 @@ sbuild_add_module(PDI_API_TESTS INSTALL_COMMAND "" SUBSTEPS test ) + +sbuild_add_module(CATALYST_PLUGIN + ENABLE_BUILD_FLAG BUILD_CATALYST_PLUGIN + SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/plugins/catalyst" + DEPENDS PDI catalyst + SUBSTEPS test +) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 30a736faa..904ac06dc 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -91,11 +91,9 @@ end module endif() endif() - add_executable(PDI_example_C example.c) target_link_libraries(PDI_example_C PRIVATE PDI::PDI_C paraconf::paraconf MPI::MPI_C m) - if("${BUILD_FORTRAN}") add_executable(PDI_example_F example.F90) target_link_libraries(PDI_example_F PRIVATE PDI::PDI_f90 paraconf::paraconf_f90 MPI_with_mod m) @@ -161,3 +159,31 @@ add_test(NAME PDI_example_trace_P COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" set_property(TEST PDI_example_trace_P PROPERTY TIMEOUT 15) set_property(TEST PDI_example_trace_P PROPERTY PROCESSORS 3) endif("${BUILD_PYTHON}") + +if("${BUILD_CATALYST_PLUGIN}") +find_package(catalyst REQUIRED) +## These examples are only valid with MPI, check if catalyst is compiling with MPI +if("${CATALYST_USE_MPI}") + +## set directory to find the catalyst python script +set(CATALYST_SCRIPT_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}) +## configure the yaml file with the previous directory +configure_file(catalyst_serial.yml.in ${CMAKE_BINARY_DIR}/catalyst_serial.yml) ## keep serial for personal testing +configure_file(catalyst.yml.in ${CMAKE_BINARY_DIR}/catalyst.yml) + +add_test(NAME PDI_example_catalyst_C_serial COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 1 ${MPIEXEC_PREFLAGS} "$" ${MPIEXEC_POSTFLAGS} "${CMAKE_BINARY_DIR}/catalyst_serial.yml") +set_property(TEST PDI_example_catalyst_C_serial PROPERTY TIMEOUT 15) +set_property(TEST PDI_example_catalyst_C_serial PROPERTY PROCESSORS 1) + +add_test(NAME PDI_example_catalyst_C COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 3 ${MPIEXEC_PREFLAGS} "$" ${MPIEXEC_POSTFLAGS} "${CMAKE_BINARY_DIR}/catalyst.yml") +set_property(TEST PDI_example_catalyst_C PROPERTY TIMEOUT 15) +set_property(TEST PDI_example_catalyst_C PROPERTY PROCESSORS 3) + +if("${BUILD_PYTHON}" AND "${CATALYST_USE_PYTHON}") +add_test(NAME PDI_example_catalyst_P COMMAND "${MPIEXEC}" "${MPIEXEC_NUMPROC_FLAG}" 1 ${MPIEXEC_PREFLAGS} "$" "${Python3_EXECUTABLE}" ${MPIEXEC_POSTFLAGS} "${CMAKE_CURRENT_SOURCE_DIR}/example.py" "${CMAKE_BINARY_DIR}/catalyst_serial.yml") +set_property(TEST PDI_example_catalyst_P PROPERTY TIMEOUT 15) +set_property(TEST PDI_example_catalyst_P PROPERTY PROCESSORS 1) +endif("${BUILD_PYTHON}" AND "${CATALYST_USE_PYTHON}") + +endif("${CATALYST_USE_MPI}") +endif("${BUILD_CATALYST_PLUGIN}") diff --git a/example/catalyst.yml.in b/example/catalyst.yml.in new file mode 100644 index 000000000..7cf08f6be --- /dev/null +++ b/example/catalyst.yml.in @@ -0,0 +1,56 @@ +# duration in seconds +duration: 1.75 +# global [height, width] (excluding boundary conditions or ghosts) +datasize: [60, 12] +# degree of parallelism +parallelism: { height: 3, width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type of values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: double } + + plugins: + mpi: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "newiter" + execute: + state: + timestep: '$iter' + time: '1.0*$iter' + multiblock: 0 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "uniform" + dims: { i: '1+$dsize[1]', j: '1+$dsize[0]' } + origin: + x: '1.0*$pcoord[1]*($dsize[1]-2.0)-1.0' + y: '1.0*$pcoord[0]*($dsize[0]-2.0)-1.0' + spacing: { dx: 1.0, dy: 1.0 } + topologies: + my_mesh: + type: "uniform" + coordset: "my_coords" +# elements: +# dims: {offsets: [] strides: []} + fields: + temperature: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "main_field" + size: '$dsize[0]*$dsize[1]' + ghost_layers: + my_mesh: { association: "element", start: ['1', '1'], size: ['$dsize[1]-2', '$dsize[0]-2'] } \ No newline at end of file diff --git a/example/catalyst_pipeline_with_rendering.py b/example/catalyst_pipeline_with_rendering.py new file mode 100644 index 000000000..247612c67 --- /dev/null +++ b/example/catalyst_pipeline_with_rendering.py @@ -0,0 +1,131 @@ +# script-version: 2.0 +from paraview.simple import * +from paraview import catalyst +import time + +# registrationName must match the channel name used in the +# 'CatalystAdaptor'. +producer = TrivialProducer(registrationName="grid") + +# ---------------------------------------------------------------- +# setup views used in the visualization +# ---------------------------------------------------------------- + +# ######## render view temperature + +# Create a new 'Render View' +renderView1 = CreateView('RenderView') +# renderView1.Set( +# ViewSize=[800, 600], +# InteractionMode='2D', +# CenterOfRotation=[20.0, 3.0, 0.0], +# CameraPosition=[20.0, 30.0, 408.7], +# CameraFocalPoint=[20.0, 30.0, 0.0], +# CameraFocalDisk=1.0, +# CameraParallelScale=32.0, +# ) + +renderView1.ViewSize=[800, 600] +renderView1.InteractionMode='2D' +renderView1.CenterOfRotation=[20.0, 3.0, 0.0] +renderView1.CameraPosition=[20.0, 30.0, 408.7] +renderView1.CameraFocalPoint=[20.0, 30.0, 0.0] +renderView1.CameraFocalDisk=1.0, +renderView1.CameraParallelScale=32.0 + + +# get color transfer function/color map for 'temperature' +temperatureLUT = GetColorTransferFunction('temperature') +## RGB: first line: min value, last line: max value +# temperatureLUT.Set( +# RGBPoints=GenerateRGBPoints( +# range_min=0.0, +# range_max=200.0, +# ), +# ScalarRangeInitialized=1.0, +# ) + + +temperatureLUT.RGBPoints=[0.0, 0.231373, 0.298039, 0.752941, + 500000.0, 0.865003, 0.865003, 0.865003, + 1000000, 0.705882, 0.0156863, 0.14902] + +temperatureLUT.ScalarRangeInitialized=1.0 + + + +# show data from grid +## wgridDisplay = Show(producer, renderView1, 'UnstructuredGridRepresentation') +gridDisplay = Show(producer, renderView1, 'StructuredGridRepresentation') + +gridDisplay.Representation = 'Surface With Edges' +gridDisplay.ColorArrayName = ['CELLS', 'temperature'] +gridDisplay.LookupTable = temperatureLUT + +# get color legend/bar for temperatureLUT in view renderView1 +temperatureLUTColorBar = GetScalarBar(temperatureLUT, renderView1) +temperatureLUTColorBar.Title = 'temperature' + +# set color bar visibility +temperatureLUTColorBar.Visibility = 1 + +# show color legend +gridDisplay.SetScalarBarVisibility(renderView1, True) + +# # ---------------------------------------------------------------- +# # setup extractors +# # ---------------------------------------------------------------- + +SetActiveView(renderView1) +# create extractor +pNG2= CreateExtractor('PNG', renderView1, registrationName='PNG2') +# trace defaults for the extractor. +pNG2.Trigger = 'TimeStep' + +# init the 'PNG' selected for 'Writer' +pNG2.Writer.FileName = 'temperature_screenshot_{timestep:06d}.png' +pNG2.Writer.ImageResolution=[800, 600] +pNG2.Writer.Format = 'PNG' + +# # ---------------------------------------------------------------- +# # setup extractor for saving the solution in VTK file +# # ---------------------------------------------------------------- + +extractor_vtk_file = None + +mesh_grid = producer.GetClientSideObject().GetOutputDataObject(0) +if mesh_grid.IsA('vtkUnstructuredGrid'): + extractor_vtk_file = CreateExtractor('VTU', producer, registrationName='VTU') +elif mesh_grid.IsA('vtkMultiBlockDataSet'): + extractor_vtk_file = CreateExtractor('VTM', producer, registrationName='VTM') +elif mesh_grid.IsA('vtkPartitionedDataSet'): + extractor_vtk_file = CreateExtractor('VTPD', producer, registrationName='VTPD') +else: + raise RuntimeError("Unsupported data type: %s. Check that the adaptor is providing channel named %s", + mesh_grid.GetClassName(), "grid") + + +# ------------------------------------------------------------------------------ +# Catalyst options +options = catalyst.Options() +## 0: no client, generate the png images and vtk files. +## 1: interactive +options.EnableCatalystLive = 0 + + +# Greeting to ensure that ctest knows this script is being imported +print("#############################################################") +print("executing catalyst_pipeline") +print("#############################################################") +def catalyst_execute(info): + global producer + producer.UpdatePipeline() + print("-----------------------------------") + print("executing (cycle={}, time={})".format(info.cycle, info.time)) + print("bounds:", producer.GetDataInformation().GetBounds()) + print("temperature-range:", producer.CellData["temperature"].GetRange(0)) + # In a real simulation sleep is not needed. We use it here to slow down the + # "simulation" and make sure ParaView client can catch up with the produced + # results instead of having all of them flashing at once. + if options.EnableCatalystLive: + time.sleep(0.1) diff --git a/example/catalyst_serial.yml.in b/example/catalyst_serial.yml.in new file mode 100644 index 000000000..75fed7163 --- /dev/null +++ b/example/catalyst_serial.yml.in @@ -0,0 +1,58 @@ +# duration in seconds +duration: 2.75 +# global [height, width] (excluding boundary conditions or ghosts) +datasize: [60, 12] +# degree of parallelism +parallelism: { height: 1 , width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type of values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: double } + + logging: debug + plugins: + mpi: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "newiter" + execute: + state: + timestep: $iter + time: 1.0*$iter + multiblock: 0 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "uniform" + dims: { i: '1+$dsize[1]', j: '1+$dsize[0]' } + origin: + x: 1.0*$pcoord[1]*($dsize[1]-2.0)-1.0 + y: 1.0*$pcoord[0]*($dsize[0]-2.0)-1.0 + spacing: { dx: 1.0, dy: 1.0 } + topologies: + my_mesh: + type: "uniform" + coordset: "my_coords" + fields: + temperature: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "main_field" + size: $dsize[0]*$dsize[1] + ghost_layers: + my_mesh: + association: "element" + start: ['1', '1'] + size: ['$dsize[1]-2', '$dsize[0]-2'] \ No newline at end of file diff --git a/example/example.c b/example/example.c index 1c827212b..00cb1ba29 100644 --- a/example/example.c +++ b/example/example.c @@ -214,7 +214,7 @@ int main(int argc, char* argv[]) #ifndef WITHOUT_PARACONF PC_double(PC_get(conf, ".duration"), &duration); #else - duration = 0.1; + duration = 1.75; #endif // get local & add ghosts to sizes diff --git a/plugins/catalyst/CMakeLists.txt b/plugins/catalyst/CMakeLists.txt new file mode 100644 index 000000000..4d3ca9241 --- /dev/null +++ b/plugins/catalyst/CMakeLists.txt @@ -0,0 +1,68 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +cmake_minimum_required(VERSION 3.16...3.29) +project(pdi_catalyst_plugin LANGUAGES C CXX) + +option(BUILD_CATALYST_PLUGIN "Build Catalyst plugin" ON) +option(BUILD_FORTRAN "Enable Fortran support" OFF) +option(BUILD_CATALYST_PARALLEL "Enable Catalyst parallel build" ON) + +include(CTest) + +# if("${BUILD_TESTING}" AND "${BUILD_FORTRAN}") +# enable_language(Fortran) +# set(PDI_COMPONENTS f90) +# endif() + +include(GNUInstallDirs) + +# PDI +#find_package(PDI REQUIRED COMPONENTS plugins) + +# Python +find_package(Python3 REQUIRED COMPONENTS Interpreter Development) + +# Catalyst +# find_package(catalyst REQUIRED) +find_package(catalyst REQUIRED) +if("${BUILD_CATALYST_PARALLEL}" AND NOT "${CATALYST_USE_MPI}") + message(FATAL_ERROR "Catalyst with MPI support required, sequential catalyst only found. Please set -DBUILD_CATALYST_PARALLEL=OFF to disable parallel Catalyst") +endif() + +# MPI +if("${CATALYST_USE_MPI}") + # if("${BUILD_TESTING}" AND "${BUILD_FORTRAN}") + # set(MPI_COMPONENTS Fortran) + # endif() + find_package(MPI REQUIRED COMPONENTS C) # ${MPI_COMPONENTS}) + set(CATALYST_DEPS catalyst::catalyst MPI::MPI_C) +endif() + +option(CATALYST_IS_PARALLEL "Catalyst is build in parallel" "${CATALYST_USE_MPI}") +message(WARNING "CATALYST_IS_PARALLEL=${CATALYST_IS_PARALLEL}") +message(WARNING "${CATALYST_USE_MPI}=${CATALYST_USE_MPI}") + +# PDI +find_package(PDI REQUIRED COMPONENTS plugins) # ${PDI_COMPONENTS}) + +# The Plugin +add_library(pdi_catalyst_plugin MODULE pdi_catalyst_plugin.cxx) +if("${CATALYST_USE_MPI}") + target_compile_definitions(pdi_catalyst_plugin PUBLIC CATALYST_IS_PARALLEL=ON) +endif() +target_link_libraries(pdi_catalyst_plugin PDI::PDI_plugins ${CATALYST_DEPS}) + +# Installation +set(INSTALL_PDIPLUGINDIR "${PDI_DEFAULT_PLUGINDIR}" CACHE PATH "PDI plugins (${PDI_DEFAULT_PLUGINDIR})") +install(TARGETS pdi_catalyst_plugin + LIBRARY DESTINATION "${INSTALL_PDIPLUGINDIR}" +) + +# Tests +if("${BUILD_TESTING}") + enable_testing() + add_subdirectory(tests) + add_subdirectory(tests_ghost) +endif() \ No newline at end of file diff --git a/plugins/catalyst/LICENSE b/plugins/catalyst/LICENSE new file mode 100644 index 000000000..d9a10c0d8 --- /dev/null +++ b/plugins/catalyst/LICENSE @@ -0,0 +1,176 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/plugins/catalyst/NOTICE b/plugins/catalyst/NOTICE new file mode 100644 index 000000000..81ea1e139 --- /dev/null +++ b/plugins/catalyst/NOTICE @@ -0,0 +1,13 @@ +Copyright 2024 Kitware, SAS + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/plugins/catalyst/README.md b/plugins/catalyst/README.md new file mode 100644 index 000000000..1c5c436b8 --- /dev/null +++ b/plugins/catalyst/README.md @@ -0,0 +1,117 @@ +# The PDI Catalyst Plugin {#PDI_Catalyst_plugin} + +This PDI plugin pushes PDI shared data to the Catalyst 2 API. The goal is to leverage the numerous Catalyst implementations like [Catalyst-ParaView](https://gitlab.kitware.com/paraview/paraview) or [Catalyst-ADIOS2](https://gitlab.kitware.com/paraview/adioscatalyst), helping massive data analysis and visualization at exascale. + +## Build Instructions + + - Build and Install [PDI](https://pdi.dev/master/index.html) + - Build and Install [Catalyst](https://gitlab.kitware.com/paraview/catalyst), with MPI support. + - Configure with CMake with variables: + * `PDI_DIR` points to `pdi/install/folder/share/pdi/cmake` + * `paraconf_DIR` points to `pdi/install/folder/share/paraconf/cmake` + * `catalyst_DIR` points to `catalyst/install/folder/lib/cmake/catalyst-2.0` + * optional: `BUILD_TESTING=ON` to build the example test + * in case of you used vendored version of libraries during your PDI build, instead of system libraries, you may have to define additional PDI dependencies locations. For example `spdlog_DIR` CMake variable for the spdlog library. + - Build with `make` or `ninja` + +## Running the Test + +The test executable expects the config yaml file as arguments. + +## Use Catalyst-Paraview + +To use the Catalyst-ParaView implementation, you should also set the following environment variables: + - `CATALYST_IMPLEMENTATION_NAME=paraview` + - `CATALYST_IMPLEMENTATION_PATHS=path/to/paraview/install/lib/catalyst` + +and likely add the catalyst lib folder to `LD_LIBRARY_PATH` if the catalyst library is installed in a non-standard location. + +## Design Considerations + +*This is a work-in-progress. This paragraph is subject to change.* + +PDI describes data through a [Specification Tree](https://pdi.dev/master/Concepts.html#Specification_tree), written in the YAML format and provided to PDI at initialization. Catalyst describes data with [Conduit](https://llnl-conduit.readthedocs.io/en/latest/index.html) nodes and [Mesh Blueprint](https://llnl-conduit.readthedocs.io/en/latest/blueprint_mesh.html#) protocol, provided at execution. + +Both protocols are very similars, because they are just hierarchical dictionnary with metadata about the shared memories. +However, Catalyst requires additional semantic about meanings of the data, to map the memory chunk to mesh description (structured mesh, unstructured mesh, image data, AMR, etc.). +The current approach is to add this semantic to the PDI Specification Tree under the `catalyst` key. See the [example file](test/pdi.yml.in) for actual implementation. + +PDI is very flexible about the timing of the data sharing using an advanced event mechanism, whereas Catalyst needs all data at the same point in time. +So, the user of this plugin should set an event name referenced by the `on_event` key in the yaml config, in order to trigger the call to `catalyst_execute`. Data should have been shared during the event using the `PDI_multi_expose` function. + +Internally, `catalyst_initialize` is called by `PDI_Init` and `catalyst_finalize` is called by `PDI_finalize`. + +## Configuration grammar + +*This is a work-in-progress. This paragraph is subject to change.* + +Simple plugin build: +```yaml +plugins: + catalyst: // name of the plugin + scripts: // list of run scripts on event ("iter" in this example) + script1: "script.py" // name of the script following by ':' with the filename of this script + on_event: "iter" // name of the event when 'catalyst_execute' is executed + execute: // contains the information (conduit_node) needed to run 'catalyst_execute' + state: + timestep: '$iter' // integral value for current timestep (paraview only?) + time: '1.0*$iter' // float64 value for current time + channels: // list of channels + grid: // name of a channel + type: "mesh" // type of channel considering + data: // A conduit Mesh Blueprint and object to define vtkGhostType + coordsets: // list of coordset supported by Mesh Blueprint + ... + topologies: // list of topology supported by Mesh Blueprint + ... + fields: // list of field supported by Mesh Blueprint + ... + ghost_layers: // allow to define vtkGhostType for structured mesh and uniform mesh with ghost layers. + my_topology: // name of a topology defined in this channel + association: "element" // consider vtkGhostType defined on element + start: ['$dstart[1]', '$dstart[0]'] // starting index of the domain without ghost + size: ['$dend[1]-$dstart[1]', '$dend[0]-$dstart[0]'] // size of domain without ghost +``` + +In paraview implemementation for catalyst, a description of variables (conduit_node) +can be found in https://docs.paraview.org/en/v6.1.0/Catalyst/blueprints.html. +All variables in each protocol are not supported by this plugin. + + +### IMPORTANT NOTICE: YAML FILE + +In the sub-tree corresponding to the catalyst plugin, a double quoted value is evaluated as a string. + +In the specification tree, the `PDI_data_array` key indicates that the conduit node data should be set as external pointer to a data array from the PDI data store. The value of this key corresponds to the name of the data in PDI data store. There is several keys to describe this array like `size`, `offset`, `stride` to try to match every possible memory layout cases. In this case, these integers values are evaluated as conduit index type. + +By default other integer are evaluated as `long`. Excepted if the integer value depend on a data defined in PDI data store as `numXPoints` +in this example: +```yaml +dims: { i: '$numXPoints', j: '60', k: 44 } +``` +Be careful, if you compile conduit with 32-bits index (option `CONDUIT_INDEX_32`), you recommand to define a metadata/data for the index and pass the data as `i` in the previous example. + +In the case of real value, the value is evaluated as `double`. Excepted if the real value depend on a data defined in PDI data store. + +For real or integer value, we recommend to use simple quoted value as `i: '$numXPoints'` for example instead of plain. + +Summary: + +| scalar type | yaml scalar style | example | +| --- | --- | --- | +| string | double quoted | "mystring" | +| --- | --- | --- | +| integer | plain or simple quoted | 32 or '32' | +| --- | --- | --- | +| real | plain or simple quoted | 1.22 or '1.22'| + + +## License + +This repository is under the Apache 2.0 license, see NOTICE and LICENSE file. + +The test case (in '/plugins/catalyst/tests') is a modification of the Catalyst2 CxxFullExample code from the ParaView source code, licenced under BSD-3-Clauses. + +Developed by Kitware SAS (Kitware Europe), motivated by the [NumPEx](https://numpex.org/) program. + +Reach us at https://www.kitware.com/contact/ \ No newline at end of file diff --git a/plugins/catalyst/catalyst_plugin_structured_ghost.h b/plugins/catalyst/catalyst_plugin_structured_ghost.h new file mode 100644 index 000000000..f93b1623b --- /dev/null +++ b/plugins/catalyst/catalyst_plugin_structured_ghost.h @@ -0,0 +1,314 @@ +#ifndef catalyst_plugin_structured_ghost_H +#define catalyst_plugin_structured_ghost_H + +#include "catalyst.hpp" + +#include +#include +#include + +#include +#include + +class Catalyst_plugin_structured_ghost +{ + /// Context of this object + PDI::Context& m_ctx; + + /// The tree representing the ghost config + PC_tree_t m_ghost_tree; + /// The parent tree of m_ghost_tree for specification tree error message + PC_tree_t m_parent_tree; + + /// name of the mesh (It correspond to the topology name in the mesh blue print) + std::string m_topology_name; // topology name + + /// dimensions of the mesh in each direction (including ghosts) + std::vector m_dimensions; + + /// start and size in each direction + std::vector m_start; + std::vector m_size; + std::string m_association; + + /// path in the conduit node for catalyst + std::string m_parent_node_path; + + /// vtkGhostType vector for paraview + std::vector m_vtk_ghost_type; + +public: + Catalyst_plugin_structured_ghost(PDI::Context& ctx, conduit_node* parent_node, PC_tree_t& tree, PC_tree_t& parent_tree, const int& index) + : m_ctx{ctx} + , m_ghost_tree(tree) + , m_parent_tree(parent_tree) + , m_vtk_ghost_type{1, 0} + { + // get the name of mesh(topology) + auto m_topology_name_spec = PC_get(m_ghost_tree, "{%d}", index); + if (!PC_status(m_topology_name_spec)) { + m_topology_name = PDI::to_string(m_topology_name_spec); + } else { + throw PDI::Spectree_error{m_ghost_tree, "The name of the topology is not defined."}; + } + + // A TESTER: + std::string value_type = get_name_from_parent_node(parent_node, "type"); + + m_ctx.logger().info("topology type is `{}'", value_type); + + //=============================== + // topo=structured + if (value_type == "structured") { + std::string path_to_dims = "topologies/" + m_topology_name + "/elements/dims/"; + std::string PC_to_dataname = ".topologies." + m_topology_name; + + get_dimension(parent_node, path_to_dims, PC_to_dataname, "topology", m_topology_name); + } + //================ + // topo=uniform + else if (value_type == "uniform") + { + // A TESTER: + std::string value_coordset = get_name_from_parent_node(parent_node, "coordset"); + + m_ctx.logger().info("For the uniform topology `{}', the name of coordset is `{}'", m_topology_name, value_coordset); + + std::string path_to_origins = "topologies/" + m_topology_name + "/origin"; + + if (conduit_cpp::cpp_node(parent_node).has_path(path_to_origins)) { + throw PDI::Spectree_error{ + m_ghost_tree, + "For uniform topology, we dont support origin keyword to generate vtkGhostType for paraview." + }; + } + + std::string path_to_dims = "coordsets/" + value_coordset + "/dims/"; + std::string PC_to_dataname = ".coordsets." + value_coordset; + + get_dimension(parent_node, path_to_dims, PC_to_dataname, "coordset", value_coordset); + + // The value in dims_vecGhost corresponding to the number of points in each direction + // We remove 1 because we consider the number of elements in each direction when we create vtkGhostType + for (int ii = 0; ii < m_dimensions.size(); ++ii) { + m_dimensions[ii]--; + } + } else { + // Config error is return because we are in a constructor. + throw PDI::Spectree_error{ + m_ghost_tree, + "ghost_layers yaml node is only valid with uniform and structured topology. The topology is `{}'", + value_type + }; + } + + // define the conduit for the vtkGhostType + + auto mask_ghost_spec = PC_get(m_ghost_tree, "<%d>", index); + + PDI::each(mask_ghost_spec, [&](PC_tree_t key_tree, PC_tree_t value) { + std::string key = PDI::to_string(key_tree); + m_ctx.logger().debug("read key= {} in ghost layers section.", key); + if (key == "size") { + PDI::opt_each(value, [&](PC_tree_t size) { m_size.emplace_back(PDI::to_string(size)); }); + } else if (key == "start") { + PDI::opt_each(value, [&](PC_tree_t start) { m_start.emplace_back(PDI::to_string(start)); }); + } else if (key == "association") { + m_association = PDI::to_string(value); + } else { + throw PDI::Spectree_error{key_tree, "Invalid configuration key in mask_ghost for topology `{}': `{}'", m_topology_name, key}; + } + }); + + if (m_size.size() != m_start.size()) { + throw PDI::Spectree_error{ + m_ghost_tree, + "Invalid configuration in mask_ghost for topology `{}' the number of elements in size and in start are not the same.", + m_topology_name + }; + } + if (m_size.size() != m_dimensions.size()) { + throw PDI::Spectree_error{ + m_parent_tree, + "Invalid configuration in mask_ghost for topology `{}', the dimension of the problem `{}' is not equal to `{}' the number of " + "elements in size and in start.", + m_topology_name, + m_dimensions.size(), + m_size.size() + }; + } + + // check size + start + dims (TODO: en dernier) + + m_ctx.logger().info("space dimension {}", m_dimensions.size()); + for (int ii = 0; ii < m_dimensions.size(); ++ii) { + m_ctx.logger().info("`{}'-th dimensions of the mesh `{}'", ii, m_dimensions[ii]); + } + + m_parent_node_path = conduit_cpp::cpp_node(parent_node).path(); + m_ctx.logger().info("conduit node path for the parent node is `{}'", m_parent_node_path); + } + + ~Catalyst_plugin_structured_ghost() {} + + /// creation of the mask ghost (VtkGhostType) need by paraview + void create_vtk_ghost_type() + { + int space_dimension = m_dimensions.size(); + + std::vector last(space_dimension); + std::vector start(space_dimension); + + for (int size_id = 0; size_id < space_dimension; ++size_id) { + last[size_id] = m_size[size_id].to_long(m_ctx); + start[size_id] = m_start[size_id].to_long(m_ctx); + } + + std::transform(start.begin(), start.end(), last.begin(), last.begin(), [](long start, long last) { return last + start; }); + + for (int ii = 0; ii < space_dimension; ++ii) { + m_ctx.logger().info("start: `{}'-th dimensions of the mesh `{}'", ii, start[ii]); + m_ctx.logger().info("last: `{}'-th dimensions of the mesh `{}'", ii, last[ii]); + } + + if (space_dimension == 2) { + size_t vsize = m_dimensions[0] * m_dimensions[1]; + m_vtk_ghost_type.resize(vsize); + + for (int ii = 0; ii < m_dimensions[1]; ++ii) { + for (int jj = 0; jj < m_dimensions[0]; ++jj) { + if (jj < start[0] || jj >= last[0] || ii < start[1] || ii >= last[1]) { + m_vtk_ghost_type[ii * (m_dimensions[0]) + jj] = (uint8_t)1; + } else { + m_vtk_ghost_type[ii * (m_dimensions[0]) + jj] = (uint8_t)0; + } + } + } + } else if (space_dimension == 3) { + size_t vsize = m_dimensions[0] * m_dimensions[1] * m_dimensions[2]; + m_vtk_ghost_type.resize(vsize); + for (int ii = 0; ii < m_dimensions[2]; ++ii) { + for (int jj = 0; jj < m_dimensions[1]; ++jj) { + for (int kk = 0; kk < m_dimensions[0]; ++kk) { + if (kk < start[0] || jj < start[1] || ii < start[2]) { + m_vtk_ghost_type[ii * (m_dimensions[1] * m_dimensions[0]) + jj * m_dimensions[0] + kk] = 1; + } else if (kk >= last[0] || jj >= last[1] || ii >= last[2]) { + m_vtk_ghost_type[ii * (m_dimensions[1] * m_dimensions[0]) + jj * m_dimensions[0] + kk] = 1; + } else { + m_vtk_ghost_type[ii * (m_dimensions[1] * m_dimensions[0]) + jj * m_dimensions[0] + kk] = 0; + } + } + } + } + } else { + std::cout << " Error in the creation of the vtkGhostType for the users: The dimension for the mesh must be 2 or 3." << std::endl; + } + } + + /// return the pointer to the mask ghost + uint8_t* get_vector() { return m_vtk_ghost_type.data(); } + + /// get the size of the pointer of the mask ghost + size_t get_size() { return m_vtk_ghost_type.size(); } + + /// get the name path in the conduit node + const std::string& get_node_path() const { return m_parent_node_path; } + + /// @brief get the name of topology(mesh) + const std::string& get_topology_name() const { return m_topology_name; } + + /// @brief retrieve the corresponding PC_tree for a given coordset or a given topology + PC_tree_t retrieve_pc_tree_from_parent_node(const std::string& structname, const std::string& dataname) + { + std::string index_all = "." + structname + "." + m_topology_name + "." + dataname; + auto dataname_tree = PC_get(m_parent_tree, index_all.c_str()); + + return dataname_tree; + } + + std::string get_name_from_parent_node(conduit_node* parent_node, const std::string& dataname) + { + std::string path_to_type = "topologies/" + m_topology_name + "/" + dataname; + std::string PC_to_type = ".topologies." + m_topology_name + "." + dataname; + + bool dataname_is_empty = conduit_cpp::cpp_node(parent_node)[path_to_type].dtype().is_empty(); + if (!dataname_is_empty) { + bool dataname_is_string = conduit_cpp::cpp_node(parent_node)[path_to_type].dtype().is_string(); + if (dataname_is_string) { + return conduit_cpp::cpp_node(parent_node)[path_to_type].as_string(); + } else { + PC_tree_t msg_tree = retrieve_pc_tree_from_parent_node("topologies", dataname); + throw PDI::Spectree_error{ + msg_tree, + "... Vec Ghost Type catalyst ... The {} for topology `{}' is not defined as a string.", + dataname, + m_topology_name + }; + } + } else { + throw PDI::Spectree_error{ + m_parent_tree, + "... Vec Ghost Type catalyst ... The {} for topology `{}' is not defined.", + dataname, + m_topology_name + }; + } + } + + /// @brief Retrieve the dimension of the mask ghost + /// @param parent_node + /// @param path_to_dims // path in the conduit node where the dimensions are + /// @param PC_to_dataname // path in the PC_tree to get the PC_tree for error message + /// @param data_type // type of data (coordset or topology) where the dimensions are defiened for error message + /// @param data_type_name // name of coordset or name of topology for error message + void get_dimension( + conduit_node* parent_node, + std::string& path_to_dims, + std::string& PC_to_dataname, + std::string data_type, + std::string& data_type_name + ) + { + std::string msg_data = data_type + " " + data_type_name; + PC_tree_t msg_tree = PC_get(m_parent_tree, PC_to_dataname.c_str()); + + if (PC_status(msg_tree)) { + throw PDI::Spectree_error(msg_tree, ""); + } else { + if (conduit_cpp::cpp_node(parent_node).has_path(path_to_dims)) { + std::list list_dims{"i", "j", "k"}; + for (auto&& elem: list_dims) { + // verify dims/{elem} exist in the node m_ghost_tree + std::string path_leaf = path_to_dims + elem; + if (conduit_cpp::cpp_node(parent_node).has_path(path_leaf)) { + auto node_path = conduit_cpp::cpp_node(parent_node)[path_leaf]; + // check the variable is an integer + if (node_path.dtype().is_integer()) { + int tmp_int = (int)node_path.to_int(); + m_dimensions.emplace_back(tmp_int); + m_ctx.logger().info("dims/`{}' = `{}' for the `{}'.", elem, tmp_int, msg_data); + } else if (node_path.dtype().is_long()) { + int tmp_int = (int)node_path.to_long(); + m_dimensions.emplace_back(tmp_int); + m_ctx.logger().info("dims/`{}' = `{}' for the `{}'.", elem, tmp_int, msg_data); + } else { + throw PDI::Spectree_error{msg_tree, "For `{}' the value of dims/`{}' is not an integer or a long", msg_data, elem}; + } + } else { + // info message in case of dims/i, dims/j, dims/k doesn't exist. + m_ctx.logger().info("No dims/`{}' is not defined for the `{}'.", elem, msg_data); + } + } + if (m_dimensions.size() == 0) { + throw PDI::Spectree_error{msg_tree, "No dims/i , dims/j and dims/k are defined for the `{}'", msg_data}; + } + } else { + throw PDI::Spectree_error(msg_tree, "For the `{}', we need dims keyword to generate vtkGhostType for catalyst.", msg_data); + } + } + } +}; + + + +#endif // catalyst_plugin_structured_ghost_H diff --git a/plugins/catalyst/pdi_catalyst_plugin.cxx b/plugins/catalyst/pdi_catalyst_plugin.cxx new file mode 100644 index 000000000..dfc51d738 --- /dev/null +++ b/plugins/catalyst/pdi_catalyst_plugin.cxx @@ -0,0 +1,785 @@ +/* +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 +*/ + +#ifdef CATALYST_IS_PARALLEL +#include +#endif + +#include "catalyst.hpp" + +#include +#include +#include +#include + +#include +#include + +#include "pdi_catalyst_plugin.h" + +catalyst_plugin::catalyst_plugin(PDI::Context& ctx, PC_tree_t spec_tree) + : Plugin{ctx} + , m_spec_tree(spec_tree) + , catalyst_is_initialize{false} +{ + ctx.callbacks().add_init_callback([this]() { this->process_pdi_init(); }); + // ctx.callbacks().add_data_callback([this](const std::string& data_name, PDI::Ref ref) { this->process_data(data_name, ref); }); + ctx.callbacks().add_event_callback([this](const std::string& event_name) { this->process_event(event_name); }); +} + +catalyst_plugin::~catalyst_plugin() noexcept(false) +{ + try { + run_catalyst_finalize(); + } catch (const std::exception& e) { + if (std::uncaught_exceptions()) { + throw; + } else { + context().logger().error("When closing catalyst plugin `{}'", e.what()); + } + } catch (...) { + if (std::uncaught_exceptions()) { + throw; + } else { + context().logger().error("When closing catalyst plugin"); + } + } + context().logger().info("Closing plugin"); +} + +void catalyst_plugin::process_pdi_init() +{ + this->run_catalyst_initialize(); + this->m_pdi_execute_event_name = this->read_pdi_execute_event_name(); +} + +// void catalyst_plugin::process_data(const std::string& data_name, PDI::Ref ref) +// { +// context().logger().debug("User has shared a data named `{}'", data_name); +// auto it = this->m_current_pdi_data.find(data_name); +// if (it != this->m_current_pdi_data.end()) { +// context().logger().warn("Data named '{}' already recorded, the previous value will overwritten.", data_name); +// it->second = ref.copy(); +// } else { +// this->m_current_pdi_data.emplace(data_name, ref); +// } +// } + +void catalyst_plugin::process_event(const std::string& event_name) +{ + if (event_name == this->m_pdi_execute_event_name) { + context().logger().info("call run_catalyst_execute in event `{}'...", event_name); + run_catalyst_execute(); + } +} + +void catalyst_plugin::run_catalyst_initialize() +{ + conduit_cpp::Node node; + + context().logger().info("Read information for script."); + auto scripts_spec = PC_get(this->m_spec_tree, ".scripts"); + if (PC_status(scripts_spec)) { + throw PDI::Spectree_error(m_spec_tree, "No scripts tree is defiend for catalyst plugin."); + } + + int script_number = 0; + PC_len(scripts_spec, &script_number); + if (script_number == 0) { + throw PDI::Spectree_error(scripts_spec, "Zero python script is defined for catalyst python."); + } else { + context().logger().debug("The number of python script is `{}'", script_number); + } + + auto scripts_node = node["catalyst/scripts"]; + for (int index = 0; index < script_number; ++index) { + auto key = PC_get(scripts_spec, "{%d}", index); + auto value = PC_get(scripts_spec, "<%d>", index); + scripts_node[PDI::to_string(key)] = PDI::to_string(value); + } + + // Remark: Each script is defined as a string (i.e. node["catalyst/scripts/[name_of_the_script]"] = filename ) + // + // We don't consider yet the object script supported by the last version of paraview. + // In others word, the following node is not supported: + // node["catalyst/scripts/[name_of_the_script]/filename"] = string + // node["catalyst/scripts/[name_of_the_script]/args"] = string + +#ifdef CATALYST_IS_PARALLEL + context().logger().info("Read mpi_comm."); + + auto communicator_spec = PC_get(this->m_spec_tree, ".communicator"); + if (!PC_status(communicator_spec)) { + PDI::Expression communicator = PDI::to_string(communicator_spec); + MPI_Comm tmp_comm = *(static_cast(PDI::Ref_r{communicator.to_ref(context())}.get())); + + // create communicator node + auto communicator_node = node["catalyst/mpi_comm"]; + + // set the fortran MPI_COMMUNICATOR + communicator_node.set_int64(static_cast(MPI_Comm_c2f(tmp_comm))); + + context().logger().debug("value of the communicator is {}:", static_cast(MPI_Comm_c2f(tmp_comm))); + } else { + // context().logger().warn("value of the communicator is {}:", static_cast(MPI_Comm_c2f(tmp_comm))); + //throw PDI::Spectree_error{communicator_spec, "No communicator is given."}; + context().logger().warn("No communicator is given by default the communicator is MPI_COMM_WORD."); + } +#else + context().logger().info("Catalyst is used with no mpi"); + auto communicator_spec = PC_get(this->m_spec_tree, ".communicator"); + if (!PC_status(communicator_spec)) { + context().logger().info("Used Catalyst with no mpi support. Invalid communicator: `{}'", PDI::to_string(communicator_spec)); + throw PDI::Spectree_error{ + communicator_spec, + "Used Catalyst with no mpi support. Invalid communicator: `{}'", + PDI::to_string(communicator_spec) + }; + } +#endif + + // The following node is supported in the last version of Paraview + // These nodes are not defined yet because we need some investigations. + // node["catalyst_load/implementation"].set("stub") ; + // node["catalyst_load/search_paths"].set("/path/to/install/catalyst/lib/catalyst/"); + // node["catalyst/pipelines"] + // node["catalyst/python_path"] + + if (context().logger().level() == spdlog::level::debug || context().logger().level() == spdlog::level::trace) { + context().logger().debug("Print node before catalyst_initialize call..."); + node.print(); + } + + context().logger().debug("catalyst_initialize call..."); + auto result = catalyst_initialize(conduit_cpp::c_node(&node)); + if (result != catalyst_status_ok) { + // context().logger().error("catalyst_initialize failure"); + throw PDI::System_error("catalyst_initialize failure"); + } + catalyst_is_initialize = true; +} + +void catalyst_plugin::read_info_for_creating_vtk_ghost( + conduit_node* execute_node, + PC_tree_t& execute_spec, + std::vector& list_vtkGhostType_to_create +) +{ + // walk the spec tree and create corresponding catalyst nodes. + struct Spec_tree_node { + PC_tree_t tree; + std::string name; + conduit_node* parent_node; + }; + + // value to keep the parent tree of the current.tree + PC_tree_t current_parent_tree; + + + std::stack remaining_tree_and_parent_node; + remaining_tree_and_parent_node.push({execute_spec, "catalyst", execute_node}); + current_parent_tree = execute_spec; // initialize parent tree as execute_spec + + while (!remaining_tree_and_parent_node.empty()) { + auto current = remaining_tree_and_parent_node.top(); + remaining_tree_and_parent_node.pop(); + + context().logger().debug("Read node of name`{}'", current.name); + if (current.name == "ghost_layers") { + context().logger().debug("Ghost layer node: `{}'", current.name); + + if (current.tree.node->type == YAML_MAPPING_NODE) { + int data_tree_size = PDI::len(current.tree); + if (data_tree_size == 0) { + throw PDI::Spectree_error(current.tree, "ghost_layers node defined with a mapping node of size 0."); + } else { + context().logger().info("number of meshes(topologies) to consider = `{}'", data_tree_size); + } + + // loop over the meshes in the ghost layers tree + for (int index = data_tree_size - 1; index >= 0; --index) { + list_vtkGhostType_to_create.emplace_back(context(), current.parent_node, current.tree, current_parent_tree, index); + } + } else { + throw PDI::Spectree_error(current.tree, "ghost_layers node only support yaml mapping node."); + } + } else { + if (current.tree.node->type == YAML_MAPPING_NODE) { + int data_tree_size = PDI::len(current.tree); + std::list name_to_skip{"coordsets", "topologies", "fields", "matsets", "adjsets", "state"}; + + // reverse order to get the correct order when poping the stack. + for (int index = data_tree_size - 1; index >= 0; --index) { + auto key = PC_get(current.tree, "{%d}", index); + std::string keyname = PDI::to_string(key); + + bool skip_key = false; + for (auto&& elem: name_to_skip) { + if (elem == keyname) { + skip_key = true; + break; + } + } + if (!skip_key) { + auto value = PC_get(current.tree, "<%d>", index); + if (conduit_cpp::cpp_node(current.parent_node).has_path(current.name)) { + auto current_node + = conduit_cpp::cpp_node(current.parent_node)[current.name]; // Attention: creation of the node if doesn't exit + remaining_tree_and_parent_node.push({value, PDI::to_string(key), conduit_cpp::c_node(¤t_node)}); + + current_parent_tree = current.tree; + + } else { + throw PDI::System_error("Error in creating vtkGhostType: a conduit node doesn't exist !!"); + } + } + } + } + } + } +} + +void catalyst_plugin::create_catalyst_execute_conduit_node(conduit_node* execute_node, PC_tree_t& execute_spec) +{ + // walk the spec tree and create corresponding catalyst nodes. + struct Spec_tree_node { + PC_tree_t tree; + std::string name; + conduit_node* parent_node; + }; + + std::stack remaining_tree_and_parent_node; + remaining_tree_and_parent_node.push({execute_spec, "catalyst", execute_node}); + while (!remaining_tree_and_parent_node.empty()) { + auto current = remaining_tree_and_parent_node.top(); + remaining_tree_and_parent_node.pop(); + context().logger().info("Read node of name`{}'", current.name); + if (current.name == "ghost_layers") { + context().logger().info(" ghost_layers keys will be read after"); + // } else if (current.name == "elements") { + // int data_tree_size = PDI::len(current.tree); + // // Check for dynamic PDI Data array + // bool pdi_data_array = false; + // for (int index = data_tree_size - 1; index >= 0; --index) { + // auto key = PC_get(current.tree, "{%d}", index); + // if (PDI::to_string(key) == "dims") { + // PDI::Spectree_error{current.tree, "I found dims dans elements `{}'", current.name}; + // } else { + // PDI::Spectree_error{current.tree, "The key is not dims and it is `{}'", PDI::to_string(key)}; + // } + // } + } else { + context().logger().info("Read node of name`{}'", current.name); + auto current_node = conduit_cpp::cpp_node(current.parent_node)[current.name]; + switch (current.tree.node->type) { + case YAML_NO_NODE: + throw PDI::Spectree_error{current.tree, "Unsupported Empty YAML Node for variable `{}'", current.name}; + // context().logger().error("Unsupported Empty YAML Node for variable `{}'", current.name); + break; + case YAML_SCALAR_NODE: + switch (current.tree.node->data.scalar.style) { + case YAML_PLAIN_SCALAR_STYLE: + case YAML_SINGLE_QUOTED_SCALAR_STYLE: + case YAML_DOUBLE_QUOTED_SCALAR_STYLE: { + // handle integer or float/double type that depend perhaps on scalar PDI data + context().logger().info("$$ read value of an integer or float or string "); + std::string data_name{PDI::to_string(current.tree)}; + context().logger().info("data_name=`{}'", data_name); + PDI::Expression data_expression{PDI::to_string(current.tree)}; + + PDI::Ref_r spec_ref = data_expression.to_ref(context()); + if (!spec_ref) { + context().logger().info("problem !spec_ref"); + throw PDI::Value_error{"Error of right access for: `{}'", data_name}; + } + auto data_type = spec_ref.type()->evaluate(context()); + if (!data_type) { + context().logger().info("problem !data_type"); + throw PDI::Spectree_error{current.tree, "Error of right access for: `{}'", data_name}; + } + if (auto&& scalar_datatype = std::dynamic_pointer_cast(data_type)) { + context().logger().debug("## read a scalar type `{}' ##", data_name); + set_value_for_pdi_scalar_datatype(conduit_cpp::c_node(¤t_node), current.tree, data_name, *scalar_datatype, spec_ref); + } else if (auto&& array_datatype = std::dynamic_pointer_cast(data_type)) { + context().logger().debug("## read an array type `{}' ##", data_name); + + // check the array_datatype is a string + PDI::Datatype_sptr type = array_datatype->subtype(); + // case multi dimensional array ?? + while (auto&& array_type = std::dynamic_pointer_cast(type)) { + type = array_type->subtype(); + } + auto array_scalar_datatype = std::dynamic_pointer_cast(type); + if (!array_scalar_datatype) { + // context().logger().error("Array subtype of variable {} should be scalar type.", name); + throw PDI::Spectree_error{current.tree, "Array subtype of variable `{}' should be scalar type.", current.name}; + } + PDI::Scalar_kind scalar_kind = array_scalar_datatype->kind(); + if (scalar_kind == PDI::Scalar_kind::SIGNED && array_scalar_datatype->buffersize() == sizeof(char)) { + context().logger().debug("## scalar_kind is signed"); + current_node.set_string(PDI::to_string(current.tree)); + } else if (scalar_kind == PDI::Scalar_kind::UNSIGNED && array_scalar_datatype->buffersize() == sizeof(unsigned char)) { + context().logger().info("## scalar_kind is unsigned"); + current_node.set_string(PDI::to_string(current.tree)); + } else { + throw PDI::Spectree_error{current.tree, "The scalar type must be a string for `{}'.", current.name}; + } + } + } break; + case YAML_LITERAL_SCALAR_STYLE: + case YAML_FOLDED_SCALAR_STYLE: + case YAML_ANY_SCALAR_STYLE: + throw PDI::Spectree_error{current.tree, "Unsupported YAML scalar style for variable `{}'", current.name}; + break; + } + break; + case YAML_SEQUENCE_NODE: + throw PDI::Spectree_error{current.tree, "Unsupported Sequence YAML Node for variable `{}'", current.name}; + break; + case YAML_MAPPING_NODE: + int data_tree_size = PDI::len(current.tree); + // Check for dynamic PDI Data array + bool pdi_data_array = false; + for (int index = data_tree_size - 1; index >= 0; --index) { + auto key = PC_get(current.tree, "{%d}", index); + if (PDI::to_string(key) == "PDI_data_array") { + this->fill_node_with_pdi_data_array(conduit_cpp::c_node(¤t_node), current.tree); + pdi_data_array = true; + break; // break the loop + } + } + if (pdi_data_array) { + break; // break the case + } + // reverse order to get the correct order when poping the stack. + for (int index = data_tree_size - 1; index >= 0; --index) { + auto key = PC_get(current.tree, "{%d}", index); + auto value = PC_get(current.tree, "<%d>", index); + remaining_tree_and_parent_node.push({value, PDI::to_string(key), conduit_cpp::c_node(¤t_node)}); + } + } + } + } +} + +void catalyst_plugin::run_catalyst_execute() +{ + context().logger().info("run_catalyst_execute()"); + conduit_cpp::Node node; + std::vector list_vtkGhostType_to_create; // object contain vector vtkGhostType + auto execute_spec = PC_get(this->m_spec_tree, ".execute"); + + context().logger().info("get m_spec_tree execute"); + conduit_node* node_pointer = conduit_cpp::c_node(&node); + // create the conduit node for catalyst_execute + context().logger().info("create conduit_node"); + create_catalyst_execute_conduit_node(node_pointer, execute_spec); + + context().logger().info("read vtk_ghost"); + // read information to create the vtkGhostType for paraview (read "ghost_layers" node in the yaml file) + //create_node_for_mask_ghost( node_pointer, execute_spec, list_vtkGhostType_to_create); + read_info_for_creating_vtk_ghost(node_pointer, execute_spec, list_vtkGhostType_to_create); + + // creation vtkGhostType vector + for (auto&& vtk_ghost_type: list_vtkGhostType_to_create) { + std::string tmp_path = vtk_ghost_type.get_node_path(); + std::string meshname = vtk_ghost_type.get_topology_name(); + vtk_ghost_type.create_vtk_ghost_type(); + + // create the node corresponding to the different vtkGhostType + node[tmp_path + "/fields/vtkGhostType/association"].set("element"); + node[tmp_path + "/fields/vtkGhostType/topology"].set(meshname); + node[tmp_path + "/fields/vtkGhostType/volume_dependent"].set("false"); + node[tmp_path + "/fields/vtkGhostType/values"].set_external(vtk_ghost_type.get_vector(), vtk_ghost_type.get_size(), 0, 1, sizeof(uint8_t)); + } + + context().logger().info("Print conduit node including vtk ghost type created ..."); + if (context().logger().level() == spdlog::level::debug || context().logger().level() == spdlog::level::trace) { + node.print(); + } + + context().logger().info("catalyst_execute call..."); + auto result = catalyst_execute(node_pointer); + if (result != catalyst_status_ok) { + throw PDI::System_error{"catalyst_execute failure"}; + } + context().logger().info("end catalyst_execute call..."); +} + +void catalyst_plugin::run_catalyst_finalize() +{ + if (catalyst_is_initialize) { + context().logger().debug("catalyst_finalize call..."); + conduit_cpp::Node node; + auto result = catalyst_finalize(conduit_cpp::c_node(&node)); + if (result != catalyst_status_ok) { + // context().logger().error("catalyst_finalize failure"); + throw PDI::System_error{"catalyst_finalize failure"}; + } + } +} + +void catalyst_plugin::fill_node_with_pdi_data_array(conduit_node* node, PC_tree_t& tree) +{ + // check the function is called with a PC_tree containg + auto name_spec = PC_get(tree, ".PDI_data_array"); + if (PC_status(name_spec)) { + // context().logger().error("No \"name\" child in PDI_data_array spec."); + throw PDI::Spectree_error{tree, "No \"name\" child in PDI_data_array spec."}; + //return; + } + + std::string name = PDI::to_string(name_spec); + PDI::Ref_r ref_r = context()[name].ref(); + // check the data can be read from PDI + if (!ref_r) { + context().logger().warn("Cannot read `{}' this data is not available", name); + // Remark: This error can arrive outside PDI_initilialize. This implies that is not really a config error + throw PDI::System_error{"No \"name\" child in PDI_data_array spec `{}'.", name}; + } + + auto data_type = ref_r.type(); + if (auto array_datatype = std::dynamic_pointer_cast(data_type)) { + set_value_for_pdi_array_datatype(node, name, tree, *array_datatype, ref_r); + } else { + // Remark: This error can arrive outside PDI_initilialize. This implies that is not really a config error + throw PDI::System_error{"Unsupported datatype for variable: `{}'. The type should be array type.", name}; + } +} + +void catalyst_plugin::set_value_for_pdi_scalar_datatype( + conduit_node* node, + PC_tree_t& tree, + const std::string& name, + const PDI::Scalar_datatype& scalar_datatype, + PDI::Ref_r& ref_r +) +{ + // remark: the different type of conduit integer and float is defined in the configuration step of cmake. + PDI::Scalar_kind scalar_kind = scalar_datatype.kind(); + if (scalar_kind == PDI::Scalar_kind::SIGNED) { + auto buffer_size = scalar_datatype.buffersize(); + if (buffer_size == sizeof(conduit_int8)) { + catalyst_conduit_node_set_int8(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_int16)) { + catalyst_conduit_node_set_int16(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_int32)) { + catalyst_conduit_node_set_int32(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_int64)) { + catalyst_conduit_node_set_int64(node, *static_cast(ref_r.get())); + } else { + context().logger().info("Unknown SIGNED buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::Spectree_error{tree, "Unknown SIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else if (scalar_kind == PDI::Scalar_kind::UNSIGNED) { + auto buffer_size = scalar_datatype.buffersize(); + if (buffer_size == sizeof(conduit_uint8)) { + catalyst_conduit_node_set_uint8(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_uint16)) { + catalyst_conduit_node_set_uint16(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_uint32)) { + catalyst_conduit_node_set_uint32(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_uint64)) { + catalyst_conduit_node_set_uint64(node, *static_cast(ref_r.get())); + } else { + context().logger().info("Unknown UNSIGNED buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::Spectree_error{tree, "Unknown UNSIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else if (scalar_kind == PDI::Scalar_kind::FLOAT) { + auto buffer_size = scalar_datatype.buffersize(); + if (buffer_size == sizeof(conduit_float32)) { + catalyst_conduit_node_set_float32(node, *static_cast(ref_r.get())); + } else if (buffer_size == sizeof(conduit_float64)) { + catalyst_conduit_node_set_float64(node, *static_cast(ref_r.get())); + } else { + context().logger().info("Unknown FLOAT buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::Spectree_error{tree, "Unknown FLOAT buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else { + context().logger().info("Unknown Scalar Type for variable `{}'", name); + throw PDI::Spectree_error{tree, "Unknown Scalar Type for variable `{}'", name}; + } +} + +void catalyst_plugin::get_conduit_index_t_value(PC_tree_t& spec, const std::string& name, conduit_index_t& value) +{ + if (!PC_status(spec)) { + long tmp_value; + if (spec.node->type == YAML_SCALAR_NODE) { + PDI::Expression data_expression{PDI::to_string(spec)}; + PDI::Ref_r spec_ref = data_expression.to_ref(context()); + if (!spec_ref) { + // context().logger().error("The PDIData named \"{}\" is not readable.", name); + throw PDI::System_error("The PDIData named \"{}\" is not readable.", name); + } + auto data_type = spec_ref.type()->evaluate(context()); + if (auto scalar_datatype = std::dynamic_pointer_cast(data_type)) { + PDI::Scalar_kind scalar_kind = (*scalar_datatype).kind(); + if (scalar_kind == PDI::Scalar_kind::SIGNED || scalar_kind == PDI::Scalar_kind::UNSIGNED) { + // return spec_ref.scalar_value(); + tmp_value = data_expression.to_long(context()); + } else { + throw PDI::Spectree_error{ + spec, + "Unknown Scalar Type for variable `{}'. The type must be an integer signed or unsigned)", + PDI::to_string(spec) + }; + } + } else { + throw PDI::Spectree_error{spec, "The datatype must be a scalar datatype for variable: `{}'", PDI::to_string(spec)}; + } + } else { + throw PDI::Spectree_error{spec, "Supported only YAML_SCALAR_NODE for variable `{}'", name}; + } + + // return value in conduit_index_t + if (std::is_same::value) { + value = tmp_value; + } else { + // case conduit_index_t is 32-bits + value = static_cast(tmp_value); + if (value != tmp_value) { + throw PDI::System_error{"Error in cast of a type conduit_index_t in long. `{}' != `{}'", value, tmp_value}; + } + } + } +} + +void catalyst_plugin::set_value_for_pdi_array_datatype( + conduit_node* node, + const std::string& name, + PC_tree_t& tree, + const PDI::Array_datatype& array_datatype, + PDI::Ref_r& ref_r +) +{ + PDI::Datatype_sptr type = array_datatype.subtype(); + while (auto&& array_type = std::dynamic_pointer_cast(type)) { + type = array_type->subtype(); + } + auto scalar_datatype = std::dynamic_pointer_cast(type); + if (!scalar_datatype) { + // context().logger().error("Array subtype of variable {} should be scalar type.", name); + throw PDI::Spectree_error{tree, "Array subtype of variable `{}' should be scalar type.", name}; + return; + } + + conduit_index_t num_elements = 0; + auto size_spec = PC_get(tree, ".size"); + get_conduit_index_t_value(size_spec, name, num_elements); + + if (num_elements == 0) { + // context().logger().error("Unknown the size of an array of name {} passed to catalyst.", name); + throw PDI::System_error("Unknown the size of an array of name `{}' passed to catalyst.", name); + } + + conduit_index_t offset = 0; + auto offset_spec = PC_get(tree, ".offset"); + get_conduit_index_t_value(offset_spec, name, offset); + + conduit_index_t stride = 1; + auto stride_spec = PC_get(tree, ".stride"); + get_conduit_index_t_value(stride_spec, name, stride); + + // computer endianness is used + conduit_index_t endianness = CONDUIT_ENDIANNESS_DEFAULT_ID; + + PDI::Scalar_kind scalar_kind = scalar_datatype->kind(); + if (scalar_kind == PDI::Scalar_kind::SIGNED) { + auto buffer_size = scalar_datatype->buffersize(); + if (buffer_size == sizeof(conduit_int8)) { + conduit_index_t element_bytes = 1; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_int8_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_int16)) { + conduit_index_t element_bytes = 2; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_int16_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_int32)) { + conduit_index_t element_bytes = 4; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_int32_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_int64)) { + conduit_index_t element_bytes = 8; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_int64_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else { + // throw PDI::Spectree_error{tree, "Unknown SIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + // context().logger().error("Unknown SIGNED buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::System_error{"Unknown SIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else if (scalar_kind == PDI::Scalar_kind::UNSIGNED) { + auto buffer_size = scalar_datatype->buffersize(); + if (buffer_size == sizeof(conduit_uint8)) { + conduit_index_t element_bytes = 1; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_uint8_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_uint16)) { + conduit_index_t element_bytes = 2; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_uint16_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_uint32)) { + conduit_index_t element_bytes = 4; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_uint32_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_uint64)) { + conduit_index_t element_bytes = 8; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_uint64_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else { + //throw PDI::Spectree_error{tree, "Unknown UNSIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + // context().logger().error("Unknown UNSIGNED buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::System_error{"Unknown UNSIGNED buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else if (scalar_kind == PDI::Scalar_kind::FLOAT) { + auto buffer_size = scalar_datatype->buffersize(); + if (buffer_size == sizeof(conduit_float32)) { + conduit_index_t element_bytes = 4; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_float32_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else if (buffer_size == sizeof(conduit_float64)) { + conduit_index_t element_bytes = 8; + auto pointer = const_cast(static_cast(ref_r.get())); + catalyst_conduit_node_set_external_float64_ptr_detailed( + node, + pointer, + num_elements, + offset * element_bytes, + stride * element_bytes, + element_bytes, + endianness + ); + } else { + // throw PDI::Spectree_error{tree, "Unknown FLOAT buffer size of `{}' for variable `{}'", buffer_size, name}; + // context().logger().error("Unknown FLOAT buffer size of {} for variable `{}'", buffer_size, name); + throw PDI::System_error{"Unknown FLOAT buffer size of `{}' for variable `{}'", buffer_size, name}; + } + } else { + // context().logger().error("Unknown Scalar Type for variable `{}'", name); + // throw PDI::Spectree_error{tree, "Unknown Scalar Type for variable `{}'", name}; + throw PDI::System_error{"Unknown Scalar Type for variable `{}'", name}; + } +} + +// long catalyst_plugin::get_long_value_from_spec_node(PC_tree_t& spec, const std::string& name) +// { +// if (spec.node->type == YAML_SCALAR_NODE) { +// PDI::Expression data_expression{PDI::to_string(spec)}; +// PDI::Ref_r spec_ref = data_expression.to_ref(context()); +// if (!spec_ref) { +// context().logger().error("The PDIData named \"{}\" is not readable.", name); +// return 0; +// } +// auto data_type = spec_ref.type()->evaluate(context()); +// if (auto scalar_datatype = std::dynamic_pointer_cast(data_type)) { +// PDI::Scalar_kind scalar_kind = (*scalar_datatype).kind(); +// if (scalar_kind == PDI::Scalar_kind::SIGNED || scalar_kind == PDI::Scalar_kind::UNSIGNED) { +// // return spec_ref.scalar_value(); +// return data_expression.to_long(context()); +// } else { +// throw PDI::Spectree_error{ +// spec, +// "Unknown Scalar Type for variable `{}'. The type must be an integerc (signed or unsigned)", +// PDI::to_string(spec) +// }; +// // context().logger().error("Unknown Scalar Type for variable {}. The type must be an integerc (signed or unsigned)", PDI::to_string(spec)); +// } +// } else { +// throw PDI::Spectree_error{spec, "The datatype must be a scalar datatype for variable: `{}'", PDI::to_string(spec)}; +// // context().logger().error("The datatype must be a scalar datatype for variable: `{}'", PDI::to_string(spec)); +// } +// return 0; +// } else { +// // context().logger().error("Supported only YAML_SCALAR_NODE for variable `{}'", name); +// throw PDI::Spectree_error{spec, "Supported only YAML_SCALAR_NODE for variable `{}'", name}; +// } +// return 0; +// } + +std::string catalyst_plugin::read_pdi_execute_event_name() +{ + std::string event_name; + auto execute_spec = PC_get(this->m_spec_tree, ".on_event"); + if (PC_status(execute_spec) == PC_OK) { + event_name = PDI::to_string(execute_spec); + } else { + throw PDI::Spectree_error{execute_spec, "No event name for catalyst plugin is given"}; + } + context().logger().info("result: read_pdi_execute_event_name `{}'.", event_name); + return event_name; +} diff --git a/plugins/catalyst/pdi_catalyst_plugin.h b/plugins/catalyst/pdi_catalyst_plugin.h new file mode 100644 index 000000000..0764583b8 --- /dev/null +++ b/plugins/catalyst/pdi_catalyst_plugin.h @@ -0,0 +1,128 @@ +#ifndef CATALYST_PLUGIN_H +#define CATALYST_PLUGIN_H + +#include "catalyst.hpp" // ??? +#include "catalyst_plugin_structured_ghost.h" // ??? + +#include +#include +#include + +struct conduit_node_impl; // ?? +typedef struct conduit_node_impl conduit_node; // ?? + +/** + * @brief Translates PDI calls to Catalyst calls. + * + * The PDI Catalyst Plugin is an adapter to call Catalyst API (initialize, execute, finalize) + * from PDI API calls (PDI_init, PDI_multi_expose, PDI_finalize). + * + * It leverages the specification tree to copy only pointer to data. The conduit node structure of + * the catalyst_execute call is defined in the spec tree. + * + */ +class catalyst_plugin: public PDI::Plugin +{ +public: + /// @brief Builds a catalsyt_plugin specification tree from its yaml config + catalyst_plugin(PDI::Context& ctx, PC_tree_t spec_tree); + + ~catalyst_plugin() noexcept(false); + +private: + /// @brief callback used at pdi_init + void process_pdi_init(); + + /// @brief trigger action of catalyst plugin when a data is exposed to pdi + /// @param data_name: name of the current data exposed to pdi + /// @param ref: reference of the data exposed to pdi + void process_data(const std::string& data_name, PDI::Ref ref); + + /// @brief trigger action of catalyst plugin when an event occur + /// @param event_name: name of the current event + void process_event(const std::string& event_name); + + /// @brief function running in pdi_init + void run_catalyst_initialize(); + + /// @brief function running in the event corresponding to catalyst_execute + void run_catalyst_execute(); + + /// @brief function running in pdi_finalize + void run_catalyst_finalize(); + + /// @brief TO COMPLET + /// @param execute_node + /// @param execute_spec + /// @param list_vtkGhostType_to_create + void read_info_for_creating_vtk_ghost( + conduit_node* execute_node, + PC_tree_t& execute_spec, + std::vector& list_vtkGhostType_to_create + ); + + /// @brief creates a conduit_node for catalyst_excute from yaml tree + /// @param execute_node conduit node that will be created + /// @param execute_spec The tree representing the execute section + void create_catalyst_execute_conduit_node(conduit_node* execute_node, PC_tree_t& execute_spec); + + /// @brief Fills a conduit node corresponding to array shared with pdi from a yaml tree. + /// @param the node in which to operate + /// @param tree specification tree containing a PDI_data_array + void fill_node_with_pdi_data_array(conduit_node* node, PC_tree_t& tree); + + /// @brief Sets value of a conduit node corresponding to a pdi scalar datatype from a yaml tree + /// @param node the node in which we set the value + /// @param name name of the array + /// @param tree specification tree containing a PDI_data_array + /// @param scalar_datatype type of the scalar + /// @param ref_r reference of the array + void set_value_for_pdi_scalar_datatype( + conduit_node* node, + PC_tree_t& tree, + const std::string& name, + const PDI::Scalar_datatype& scalar_datatype, + PDI::Ref_r& ref_r + ); + + /// @brief Sets values of a conduit node corresponding to a pdi array datatype from a yaml tree + /// @param node the node in which we set the value + /// @param name name of the array + /// @param tree specification tree containing a PDI_data_array + /// @param array_datatype type of the array + /// @param ref_r reference of the array + void set_value_for_pdi_array_datatype( + conduit_node* node, + const std::string& name, + PC_tree_t& tree, + const PDI::Array_datatype& array_datatype, + PDI::Ref_r& ref_r + ); + + /// @brief return an index description (i.e. size, offset, stride) of the array that correspond to a conduit node + /// @param spec : specification tree where the index is defined + /// @param name : The name of the data that corresponding to the index. + /// @param value: value of the index + void get_conduit_index_t_value(PC_tree_t& spec, const std::string& name, conduit_index_t& value); + + /// @brief return the event name to call catalyst_execute + std::string read_pdi_execute_event_name(); + + /// @brief variable to know if catalyst_initialize is called. + /// Remark: The call of catalyst_finalize doesn't return a okay status if catalyst_initialize is not called before. + /// Example: In case of config error in the yaml file needed by catalyst_initialize. Moreover, the config error message cannot be see by the user. + bool catalyst_is_initialize; + + /// @brief specification tree for catalyst plugin + PC_tree_t m_spec_tree; + + /// @brief list of pdi data array passed to catalyst in catalyst_execute + /// std::unordered_map m_current_pdi_data; + + /// @brief name of event use to call catalyst_execute + std::string m_pdi_execute_event_name; +}; + +PDI_PLUGIN(catalyst) + +#endif // CATALYST_PLUGIN_H diff --git a/plugins/catalyst/tests/CMakeLists.txt b/plugins/catalyst/tests/CMakeLists.txt new file mode 100644 index 000000000..166a86fa1 --- /dev/null +++ b/plugins/catalyst/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +# Creation of executable +add_executable(TestPDICatalyst + grid.cxx + grid.h + attributes.h + attributes.cxx + main.cxx + pdi_adaptor.h + pdi_adaptor.cxx) + +# MPI +find_package(MPI COMPONENTS C CXX REQUIRED) + +target_link_libraries(TestPDICatalyst + PRIVATE PDI::PDI_C MPI::MPI_C) + +set(CATALYST_SCRIPT_FOLDER ${CMAKE_SOURCE_DIR}/tests) +configure_file(pdi.yml.in pdi.yml) + +find_package(Python3 COMPONENTS Interpreter) +add_test(NAME TestPDICatalystSerial COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/tests/run_test.py" "${CMAKE_BINARY_DIR}/tests/" "${CMAKE_SOURCE_DIR}/tests/") + +# test with MPI +if(${CATALYST_USE_MPI}) + add_test(NAME TestPDICatalystMPI COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/tests/run_test_mpi.py" "${CMAKE_BINARY_DIR}/tests/" "${CMAKE_SOURCE_DIR}/tests/" "${MPIEXEC_EXECUTABLE}") +endif() diff --git a/plugins/catalyst/tests/attributes.cxx b/plugins/catalyst/tests/attributes.cxx new file mode 100644 index 000000000..68b6c3b67 --- /dev/null +++ b/plugins/catalyst/tests/attributes.cxx @@ -0,0 +1,54 @@ +#include "attributes.h" + +#include "grid.h" + +Attributes::Attributes(Grid* grid) +{ + this->m_grid_ptr = grid; +} + +Attributes::~Attributes() +{ + this->m_grid_ptr = nullptr; +} + +void Attributes::update_fields(double time) +{ + size_t num_points = this->m_grid_ptr->get_number_of_points(); + this->m_velocity.resize(num_points * 3); + for (size_t pt = 0; pt < num_points; pt++) { + const double* coord = this->m_grid_ptr->get_point(pt); + this->m_velocity[pt] = coord[1] * time; + } + std::fill(this->m_velocity.begin() + num_points, this->m_velocity.end(), 0.); + + size_t num_cells = this->m_grid_ptr->get_number_of_cells(); + this->m_pressure.resize(num_cells); + + double tmp_var = (num_cells * time * 0.5); + size_t first_cells; + if (tmp_var < 0) { + first_cells = 0; + } else { + first_cells = (size_t)tmp_var; + } + + std::fill(this->m_pressure.begin(), this->m_pressure.end(), -1.f); + std::fill(this->m_pressure.begin() + first_cells, this->m_pressure.end(), 1.f); +} + +double* Attributes::get_velocity_array() +{ + if (this->m_velocity.empty()) { + return nullptr; + } + return &this->m_velocity[0]; +} + +float* Attributes::get_pressure_array() +{ + if (this->m_pressure.empty()) { + return nullptr; + } + return &this->m_pressure[0]; +} diff --git a/plugins/catalyst/tests/attributes.h b/plugins/catalyst/tests/attributes.h new file mode 100644 index 000000000..e5680ea84 --- /dev/null +++ b/plugins/catalyst/tests/attributes.h @@ -0,0 +1,27 @@ +#ifndef ATTRIBUTES_H +#define ATTRIBUTES_H + +#include +class Grid; + +class Attributes +{ + // A class for generating and storing point and cell fields. + // Velocity is stored at the points and pressure is stored + // for the cells. The current velocity profile is for a + // shearing flow with U(y,t) = y*t, V = 0 and W = 0. + // Pressure is constant through the domain. +public: + Attributes(Grid* grid); + ~Attributes(); + void update_fields(double time); + double* get_velocity_array(); + float* get_pressure_array(); + +private: + std::vector m_velocity; + std::vector m_pressure; + Grid* m_grid_ptr; +}; + +#endif // ATTRIBUTES_H diff --git a/plugins/catalyst/tests/catalyst_pipeline.py b/plugins/catalyst/tests/catalyst_pipeline.py new file mode 100644 index 000000000..7451835fb --- /dev/null +++ b/plugins/catalyst/tests/catalyst_pipeline.py @@ -0,0 +1,17 @@ +from paraview.simple import * + +# Greeting to ensure that ctest knows this script is being imported +print("executing catalyst_pipeline") + +# registrationName must match the channel name used in the +# 'CatalystAdaptor'. +producer = TrivialProducer(registrationName="grid") + +def catalyst_execute(info): + global producer + producer.UpdatePipeline() + print("-----------------------------------") + print("executing (cycle={}, time={})".format(info.cycle, info.time)) + print("bounds:", producer.GetDataInformation().GetBounds()) + print("velocity-magnitude-range:", producer.PointData["velocity"].GetRange(-1)) + print("pressure-range:", producer.CellData["pressure"].GetRange(0)) diff --git a/plugins/catalyst/tests/catalyst_pipeline_with_rendering.py b/plugins/catalyst/tests/catalyst_pipeline_with_rendering.py new file mode 100644 index 000000000..e2d8c5291 --- /dev/null +++ b/plugins/catalyst/tests/catalyst_pipeline_with_rendering.py @@ -0,0 +1,84 @@ +# script-version: 2.0 +from paraview.simple import * +from paraview import catalyst +import time + +# registrationName must match the channel name used in the +# 'CatalystAdaptor'. +producer = TrivialProducer(registrationName="grid") + +# ---------------------------------------------------------------- +# setup views used in the visualization +# ---------------------------------------------------------------- + +# Create a new 'Render View' +renderView1 = CreateView('RenderView') +renderView1.ViewSize = [1600,800] +renderView1.CameraPosition = [157.90070691620653, 64.91180236667495, 167.90421495515105] +renderView1.CameraFocalPoint = [19.452526958533134, 28.491610229010647, 10.883993417012459] +renderView1.CameraViewUp = [0.07934883419275315, 0.953396338566962, -0.2910999555468221] +renderView1.CameraFocalDisk = 1.0 +renderView1.CameraParallelScale = 54.99504523136608 + +# get color transfer function/color map for 'velocity' +velocityLUT = GetColorTransferFunction('velocity') +velocityLUT.RGBPoints = [0.0, 0.231373, 0.298039, 0.752941, 29.205000000000002, 0.865003, 0.865003, 0.865003, 58.410000000000004, 0.705882, 0.0156863, 0.14902] +velocityLUT.ScalarRangeInitialized = 1.0 + +# show data from grid +gridDisplay = Show(producer, renderView1, 'UnstructuredGridRepresentation') + +gridDisplay.Representation = 'Surface' +gridDisplay.ColorArrayName = ['POINTS', 'velocity'] +gridDisplay.LookupTable = velocityLUT + +# get color legend/bar for velocityLUT in view renderView1 +velocityLUTColorBar = GetScalarBar(velocityLUT, renderView1) +velocityLUTColorBar.Title = 'velocity' +velocityLUTColorBar.ComponentTitle = 'Magnitude' + +# set color bar visibility +velocityLUTColorBar.Visibility = 1 + +# show color legend +gridDisplay.SetScalarBarVisibility(renderView1, True) + + +# ---------------------------------------------------------------- +# setup extractors +# ---------------------------------------------------------------- + +SetActiveView(renderView1) +# create extractor +pNG1 = CreateExtractor('PNG', renderView1, registrationName='PNG1') +# trace defaults for the extractor. +pNG1.Trigger = 'TimeStep' + +# init the 'PNG' selected for 'Writer' +pNG1.Writer.FileName = 'screenshot_{timestep:06d}.png' +pNG1.Writer.ImageResolution = [1600,800] +pNG1.Writer.Format = 'PNG' + +# ------------------------------------------------------------------------------ +# Catalyst options +options = catalyst.Options() +## 0: no client, generate the images +## 1: live visualization +options.EnableCatalystLive = 1 + + +# Greeting to ensure that ctest knows this script is being imported +def catalyst_execute(info): + global producer + producer.UpdatePipeline() + print("-----------------------------------") + print("executing (cycle={}, time={})".format(info.cycle, info.time)) + print("bounds:", producer.GetDataInformation().GetBounds()) + print("velocity-magnitude-range:", producer.PointData["velocity"].GetRange(-1)) + print("pressure-range:", producer.CellData["pressure"].GetRange(0)) + + # In a real simulation sleep is not needed. We use it here to slow down the + # "simulation" and make sure ParaView client can catch up with the produced + # results instead of having all of them flashing at once. + if options.EnableCatalystLive: + time.sleep(1) diff --git a/plugins/catalyst/tests/grid.cxx b/plugins/catalyst/tests/grid.cxx new file mode 100644 index 000000000..7d75daa0c --- /dev/null +++ b/plugins/catalyst/tests/grid.cxx @@ -0,0 +1,90 @@ +#include "grid.h" + +#include +#include +#include +#include + +Grid::Grid(const unsigned int num_points[3], const double spacing[3]) +{ + if (num_points[0] == 0 || num_points[1] == 0 || num_points[2] == 0) { + throw std::runtime_error("Must have a non-zero amount of points in each direction."); + } + // in parallel, we do a simple partitioning in the x-direction. + int mpi_size = 1; + int mpi_rank = 0; + MPI_Comm_rank(MPI_COMM_WORLD, &mpi_rank); + MPI_Comm_size(MPI_COMM_WORLD, &mpi_size); + + unsigned int start_x_point = mpi_rank * num_points[0] / mpi_size; + unsigned int end_x_point = (mpi_rank + 1) * num_points[0] / mpi_size; + if (mpi_size != mpi_rank + 1) { + end_x_point++; + } + + // create the points -- slowest in the x and fastest in the z directions + double coord[3] = {0, 0, 0}; + for (unsigned int i = start_x_point; i < end_x_point; i++) { + coord[0] = i * spacing[0]; + for (unsigned int j = 0; j < num_points[1]; j++) { + coord[1] = j * spacing[1]; + for (unsigned int k = 0; k < num_points[2]; k++) { + coord[2] = k * spacing[2]; + // add the coordinate to the end of the vector + std::copy(coord, coord + 3, std::back_inserter(this->m_points)); + } + } + } + // create the hex cells + unsigned int cell_points[8]; + unsigned int numXPoints = end_x_point - start_x_point; + for (unsigned int i = 0; i < numXPoints - 1; i++) { + for (unsigned int j = 0; j < num_points[1] - 1; j++) { + for (unsigned int k = 0; k < num_points[2] - 1; k++) { + cell_points[0] = i * num_points[1] * num_points[2] + j * num_points[2] + k; + cell_points[1] = (i + 1) * num_points[1] * num_points[2] + j * num_points[2] + k; + cell_points[2] = (i + 1) * num_points[1] * num_points[2] + (j + 1) * num_points[2] + k; + cell_points[3] = i * num_points[1] * num_points[2] + (j + 1) * num_points[2] + k; + cell_points[4] = i * num_points[1] * num_points[2] + j * num_points[2] + k + 1; + cell_points[5] = (i + 1) * num_points[1] * num_points[2] + j * num_points[2] + k + 1; + cell_points[6] = (i + 1) * num_points[1] * num_points[2] + (j + 1) * num_points[2] + k + 1; + cell_points[7] = i * num_points[1] * num_points[2] + (j + 1) * num_points[2] + k + 1; + std::copy(cell_points, cell_points + 8, std::back_inserter(this->m_cells)); + } + } + } +} + +size_t Grid::get_number_of_points() const +{ + return this->m_points.size() / 3; +} + +size_t Grid::get_number_of_cells() const +{ + return this->m_cells.size() / 8; +} + +const double* Grid::get_points_array() const +{ + if (this->m_points.empty()) { + return nullptr; + } + return this->m_points.data(); +} + +const double* Grid::get_point(size_t pointId) const +{ + if (pointId >= this->m_points.size()) { + return nullptr; + } + return &(this->m_points[pointId * 3]); +} + +const unsigned int* Grid::get_cell_points(size_t cellId) const +{ + if (cellId >= this->m_cells.size()) { + return nullptr; + } + return &(this->m_cells[cellId * 8]); +} diff --git a/plugins/catalyst/tests/grid.h b/plugins/catalyst/tests/grid.h new file mode 100644 index 000000000..0c2e96782 --- /dev/null +++ b/plugins/catalyst/tests/grid.h @@ -0,0 +1,22 @@ +#ifndef GRID_H +#define GRID_H + +#include +#include + +class Grid +{ +public: + Grid(const unsigned int num_points[3], const double spacing[3]); + size_t get_number_of_points() const; + size_t get_number_of_cells() const; + const double* get_points_array() const; + const double* get_point(size_t pointId) const; + const unsigned int* get_cell_points(size_t cellId) const; + +private: + std::vector m_points; + std::vector m_cells; +}; + +#endif diff --git a/plugins/catalyst/tests/main.cxx b/plugins/catalyst/tests/main.cxx new file mode 100644 index 000000000..386ba0cc2 --- /dev/null +++ b/plugins/catalyst/tests/main.cxx @@ -0,0 +1,48 @@ +#include "attributes.h" +#include "grid.h" +#include "pdi_adaptor.h" + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + MPI_Init(&argc, &argv); + unsigned int num_points[3] = {70, 60, 44}; + double spacing[3] = {1, 1.1, 1.3}; + Grid grid(num_points, spacing); + Attributes attributes(&grid); + + if (argc < 2) { + std::cerr << "expecting the pdi yaml config as argument" << std::endl; + return EXIT_FAILURE; + } + auto code = pdi_adaptor::initialize(std::string(argv[1]), grid); + if (!code) { + std::cerr << "pdi_adaptor::Initialize failure" << std::endl; + return EXIT_FAILURE; + } + + unsigned int number_of_time_steps = 10; + for (unsigned int time_step = 0; time_step < number_of_time_steps; time_step++) { + // use a time step length of 0.1 + double time = time_step * 0.1; + attributes.update_fields(time); + + code = pdi_adaptor::execute(time_step, time, grid, attributes); + if (!code) { + std::cerr << "pdi_adaptor::Execute failure" << std::endl; + return EXIT_FAILURE; + } + } + + code = pdi_adaptor::finalize(); + if (!code) { + std::cerr << "pdi_adaptor::Finalize failure" << std::endl; + return EXIT_FAILURE; + } + + MPI_Finalize(); + return EXIT_SUCCESS; +} diff --git a/plugins/catalyst/tests/pdi.yml.in b/plugins/catalyst/tests/pdi.yml.in new file mode 100644 index 000000000..fa86c191e --- /dev/null +++ b/plugins/catalyst/tests/pdi.yml.in @@ -0,0 +1,100 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +logging: + level: "debug" +metadata: + points_array_size: size_t + cell_points_size: size_t + velocity_array_size: size_t + pressure_array_size: size_t +data: + cycle: int + time: double + number_of_points: size_t + number_of_cells: size_t + points_array: + type: array + subtype: double + size: $points_array_size + cell_points: + type: array + subtype: uint32 + size: $cell_points_size + velocity_array: + type: array + subtype: double + size: $velocity_array_size + pressure_array: + type: array + subtype: float + size: $pressure_array_size +plugins: + trace: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "catalyst_execute" + execute: + state: + timestep: '$cycle' + time: '$time' + multiblock: 1 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "explicit" + values: + x: + PDI_data_array: "points_array" + size: '$number_of_points' + offset: 0 + stride: 3 + y: + PDI_data_array: "points_array" + size: '$number_of_points' + offset: 1 + stride: 3 + z: + PDI_data_array: "points_array" + size: '$number_of_points' + offset: 2 + stride: 3 + topologies: + my_mesh: + type: "unstructured" + coordset: "my_coords" + elements: + shape: "hex" + connectivity: + PDI_data_array: "cell_points" + size: '8*$number_of_cells' + fields: + velocity: + association: "vertex" + topology: "my_mesh" + volume_dependent: "false" + values: + x: + PDI_data_array: "velocity_array" + size: '$number_of_points' + offset: 0 + y: + PDI_data_array: "velocity_array" + size: '$number_of_points' + offset: '$number_of_points' + z: + PDI_data_array: "velocity_array" + size: '$number_of_points' + offset: '2*$number_of_points' + pressure: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "pressure_array" + size: '$number_of_cells' diff --git a/plugins/catalyst/tests/pdi_adaptor.cxx b/plugins/catalyst/tests/pdi_adaptor.cxx new file mode 100644 index 000000000..b76def5c2 --- /dev/null +++ b/plugins/catalyst/tests/pdi_adaptor.cxx @@ -0,0 +1,99 @@ +#include "pdi_adaptor.h" + +#include "attributes.h" +#include "grid.h" + +#include +#include +#include + +namespace pdi_adaptor { + +bool initialize(const std::string& pdi_yaml_config_file_path, const Grid& grid) +{ + PC_tree_t conf = PC_parse_path(pdi_yaml_config_file_path.c_str()); + auto status = PDI_init(PC_get(conf, "")); + if (status != PDI_status_t::PDI_OK) { + return false; + } + + auto points_array_size = grid.get_number_of_points() * 3; + status = PDI_expose("points_array_size", &points_array_size, PDI_OUT); + if (status != PDI_status_t::PDI_OK) { + return false; + } + + auto number_of_cells = grid.get_number_of_cells() * 8; + status = PDI_expose("cell_points_size", &number_of_cells, PDI_OUT); + if (status != PDI_status_t::PDI_OK) { + return false; + } + + auto velocity_array_size = grid.get_number_of_points() * 3; + status = PDI_expose("velocity_array_size", &velocity_array_size, PDI_OUT); + if (status != PDI_status_t::PDI_OK) { + return false; + } + + auto pressure_array_size = grid.get_number_of_cells(); + status = PDI_expose("pressure_array_size", &pressure_array_size, PDI_OUT); + if (status != PDI_status_t::PDI_OK) { + return false; + } + + return true; +} + +bool execute(int cycle, double time, Grid& grid, Attributes& attribs) +{ + auto number_of_points = grid.get_number_of_points(); + auto number_of_cells = grid.get_number_of_cells(); + + auto status = PDI_multi_expose( + // + "catalyst_execute", + // + "cycle", + &cycle, + PDI_OUT, + // + "time", + &time, + PDI_OUT, + // + "number_of_points", + &number_of_points, + PDI_OUT, + // + "points_array", + grid.get_points_array(), + PDI_OUT, + // + "number_of_cells", + &number_of_cells, + PDI_OUT, + // + "cell_points", + grid.get_cell_points(0), + PDI_OUT, + // + "velocity_array", + attribs.get_velocity_array(), + PDI_OUT, + // + "pressure_array", + attribs.get_pressure_array(), + PDI_OUT, + // + NULL + ); + + return status == PDI_status_t::PDI_OK; +} + +bool finalize() +{ + auto status = PDI_finalize(); + return status == PDI_status_t::PDI_OK; +} +} // namespace pdi_adaptor diff --git a/plugins/catalyst/tests/pdi_adaptor.h b/plugins/catalyst/tests/pdi_adaptor.h new file mode 100644 index 000000000..6c7a6af89 --- /dev/null +++ b/plugins/catalyst/tests/pdi_adaptor.h @@ -0,0 +1,15 @@ +#ifndef PDI_ADAPTOR_H +#define PDI_ADAPTOR_H + +#include + +class Grid; +class Attributes; + +namespace pdi_adaptor { +bool initialize(const std::string& pdi_yaml_config_file_path, const Grid& grid); +bool execute(int cycle, double time, Grid& grid, Attributes& attribs); +bool finalize(); +} // namespace pdi_adaptor + +#endif diff --git a/plugins/catalyst/tests/references/execute_reference.json b/plugins/catalyst/tests/references/execute_reference.json new file mode 100644 index 000000000..9754b475a --- /dev/null +++ b/plugins/catalyst/tests/references/execute_reference.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "little"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "little"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 184800,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 184800,"offset": 1478434,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 184800,"offset": 2956834,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 4435234,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 4435247,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 4435257,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "connectivity": {"dtype":"uint32","number_of_elements": 1400424,"offset": 4435261,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 10036957,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 10036964,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 10036972,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 184800,"offset": 10036978,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 184800,"offset": 11515378,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 184800,"offset": 12993778,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 14472178,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 14472186,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 14472194,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": {"dtype":"float32","number_of_elements": 175053,"offset": 14472200,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/execute_reference_rank0.json b/plugins/catalyst/tests/references/execute_reference_rank0.json new file mode 100644 index 000000000..0c6da72c5 --- /dev/null +++ b/plugins/catalyst/tests/references/execute_reference_rank0.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "little"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "little"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/execute_reference_rank1.json b/plugins/catalyst/tests/references/execute_reference_rank1.json new file mode 100644 index 000000000..e22927616 --- /dev/null +++ b/plugins/catalyst/tests/references/execute_reference_rank1.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "little"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "little"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 50160,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 50160,"offset": 401314,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 50160,"offset": 802594,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1203874,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1203887,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1203897,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "connectivity": {"dtype":"uint32","number_of_elements": 365328,"offset": 1203901,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2665213,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2665220,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2665228,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 50160,"offset": 2665234,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 50160,"offset": 3066514,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 50160,"offset": 3467794,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3869074,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3869082,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3869090,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": {"dtype":"float32","number_of_elements": 45666,"offset": 3869096,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/execute_reference_rank2.json b/plugins/catalyst/tests/references/execute_reference_rank2.json new file mode 100644 index 000000000..0c6da72c5 --- /dev/null +++ b/plugins/catalyst/tests/references/execute_reference_rank2.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "little"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "little"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/execute_reference_rank3.json b/plugins/catalyst/tests/references/execute_reference_rank3.json new file mode 100644 index 000000000..0c6da72c5 --- /dev/null +++ b/plugins/catalyst/tests/references/execute_reference_rank3.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "little"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "little"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "little"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "little"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "little"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "little"} + } + } + } + } + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/finalize_reference.json b/plugins/catalyst/tests/references/finalize_reference.json new file mode 100644 index 000000000..4d10ad555 --- /dev/null +++ b/plugins/catalyst/tests/references/finalize_reference.json @@ -0,0 +1 @@ +{"dtype":"empty"} \ No newline at end of file diff --git a/plugins/catalyst/tests/references/initialize_reference.json b/plugins/catalyst/tests/references/initialize_reference.json new file mode 100644 index 000000000..0457a7e38 --- /dev/null +++ b/plugins/catalyst/tests/references/initialize_reference.json @@ -0,0 +1,10 @@ + +{ + "catalyst": + { + "scripts": + { + "script1": {"dtype":"char8_str","number_of_elements": 110,"offset": 0,"stride": 1,"element_bytes": 1,"endianness": "little"} + } + } +} \ No newline at end of file diff --git a/plugins/catalyst/tests/references_big_endian/execute_reference.json b/plugins/catalyst/tests/references_big_endian/execute_reference.json new file mode 100644 index 000000000..25aaf430e --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/execute_reference.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "big"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "big"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 184800,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 184800,"offset": 1478434,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 184800,"offset": 2956834,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 4435234,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 4435247,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 4435257,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "connectivity": {"dtype":"uint32","number_of_elements": 1400424,"offset": 4435261,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 10036957,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 10036964,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 10036972,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 184800,"offset": 10036978,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 184800,"offset": 11515378,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 184800,"offset": 12993778,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 14472178,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 14472186,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 14472194,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": {"dtype":"float32","number_of_elements": 175053,"offset": 14472200,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + } + } + } + } +} diff --git a/plugins/catalyst/tests/references_big_endian/execute_reference_rank0.json b/plugins/catalyst/tests/references_big_endian/execute_reference_rank0.json new file mode 100644 index 000000000..cee6b2350 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/execute_reference_rank0.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "big"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "big"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + } + } + } + } +} diff --git a/plugins/catalyst/tests/references_big_endian/execute_reference_rank1.json b/plugins/catalyst/tests/references_big_endian/execute_reference_rank1.json new file mode 100644 index 000000000..44858f2e9 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/execute_reference_rank1.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "big"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "big"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 50160,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 50160,"offset": 401314,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 50160,"offset": 802594,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1203874,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1203887,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1203897,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "connectivity": {"dtype":"uint32","number_of_elements": 365328,"offset": 1203901,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2665213,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2665220,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2665228,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 50160,"offset": 2665234,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 50160,"offset": 3066514,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 50160,"offset": 3467794,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3869074,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3869082,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3869090,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": {"dtype":"float32","number_of_elements": 45666,"offset": 3869096,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + } + } + } + } +} diff --git a/plugins/catalyst/tests/references_big_endian/execute_reference_rank2.json b/plugins/catalyst/tests/references_big_endian/execute_reference_rank2.json new file mode 100644 index 000000000..cee6b2350 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/execute_reference_rank2.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "big"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "big"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + } + } + } + } +} diff --git a/plugins/catalyst/tests/references_big_endian/execute_reference_rank3.json b/plugins/catalyst/tests/references_big_endian/execute_reference_rank3.json new file mode 100644 index 000000000..cee6b2350 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/execute_reference_rank3.json @@ -0,0 +1,70 @@ + +{ + "catalyst": + { + "state": + { + "timestep": {"dtype":"int32","number_of_elements": 1,"offset": 0,"stride": 4,"element_bytes": 4,"endianness": "big"}, + "time": {"dtype":"float64","number_of_elements": 1,"offset": 4,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "multiblock": {"dtype":"int64","number_of_elements": 1,"offset": 12,"stride": 8,"element_bytes": 8,"endianness": "big"} + }, + "channels": + { + "grid": + { + "type": {"dtype":"char8_str","number_of_elements": 5,"offset": 20,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "data": + { + "coordsets": + { + "my_coords": + { + "type": {"dtype":"char8_str","number_of_elements": 9,"offset": 25,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 34,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 380194,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 760354,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + } + }, + "topologies": + { + "my_mesh": + { + "type": {"dtype":"char8_str","number_of_elements": 13,"offset": 1140514,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "coordset": {"dtype":"char8_str","number_of_elements": 10,"offset": 1140527,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "elements": + { + "shape": {"dtype":"char8_str","number_of_elements": 4,"offset": 1140537,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "connectivity": {"dtype":"uint32","number_of_elements": 345032,"offset": 1140541,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + }, + "fields": + { + "velocity": + { + "association": {"dtype":"char8_str","number_of_elements": 7,"offset": 2520669,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 2520676,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 2520684,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": + { + "x": {"dtype":"float64","number_of_elements": 47520,"offset": 2520690,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "y": {"dtype":"float64","number_of_elements": 47520,"offset": 2900850,"stride": 8,"element_bytes": 8,"endianness": "big"}, + "z": {"dtype":"float64","number_of_elements": 47520,"offset": 3281010,"stride": 8,"element_bytes": 8,"endianness": "big"} + } + }, + "pressure": + { + "association": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661170,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "topology": {"dtype":"char8_str","number_of_elements": 8,"offset": 3661178,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "volume_dependent": {"dtype":"char8_str","number_of_elements": 6,"offset": 3661186,"stride": 1,"element_bytes": 1,"endianness": "big"}, + "values": {"dtype":"float32","number_of_elements": 43129,"offset": 3661192,"stride": 4,"element_bytes": 4,"endianness": "big"} + } + } + } + } + } + } +} diff --git a/plugins/catalyst/tests/references_big_endian/finalize_reference.json b/plugins/catalyst/tests/references_big_endian/finalize_reference.json new file mode 100644 index 000000000..4d10ad555 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/finalize_reference.json @@ -0,0 +1 @@ +{"dtype":"empty"} \ No newline at end of file diff --git a/plugins/catalyst/tests/references_big_endian/initialize_reference.json b/plugins/catalyst/tests/references_big_endian/initialize_reference.json new file mode 100644 index 000000000..90c952641 --- /dev/null +++ b/plugins/catalyst/tests/references_big_endian/initialize_reference.json @@ -0,0 +1,10 @@ + +{ + "catalyst": + { + "scripts": + { + "script1": {"dtype":"char8_str","number_of_elements": 110,"offset": 0,"stride": 1,"element_bytes": 1,"endianness": "big"} + } + } +} diff --git a/plugins/catalyst/tests/run_test.py b/plugins/catalyst/tests/run_test.py new file mode 100644 index 000000000..30063a7f4 --- /dev/null +++ b/plugins/catalyst/tests/run_test.py @@ -0,0 +1,57 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +import subprocess +import sys +import os +import filecmp +import json + +binary_folder = sys.argv[1] +source_folder = sys.argv[2] + +env = os.environ.copy() +env["CATALYST_DATA_DUMP_DIRECTORY"] = binary_folder +env["CATALYST_IMPLEMENTATION_NAME"] = 'stub' # need to get the conduit json file for comparison +env["PDI_PLUGIN_PATH"] = binary_folder + '/..' +result = subprocess.run([binary_folder + "/TestPDICatalyst", binary_folder + "/pdi.yml"], env=env) + +if(result.returncode != 0): + exit(result.returncode) + +# get endiannes of the computer +endianness = sys.byteorder + +reference_directory = 'references' # if(endianness == 'little'); +if(endianness == 'big'): + reference_directory = reference_directory + '_big_endian' + +# Check the initialize json dump +reference_initialize_json = source_folder + "/" + reference_directory + "/initialize_reference.json" +actual_initialize_json = binary_folder + "initialize_params.conduit_bin.1.0_json" +with open(reference_initialize_json) as ref_file: + with open(actual_initialize_json) as actual_file: + ref_json = json.load(ref_file) + actual_json = json.load(actual_file) + if ref_json.items() != actual_json.items(): + # Ignore the length of the script path which depends on platform. + actual_json["catalyst"]["scripts"]["script1"]["number_of_elements"] = ref_json["catalyst"]["scripts"]["script1"]["number_of_elements"] + if ref_json.items() != actual_json.items(): + print(f'Differences detected in file "{actual_initialize_json}" compared to reference "{reference_initialize_json}') + exit(1) + +# Check the execute json dump +reference_execute_json = source_folder + "/" + reference_directory + "/execute_reference.json" +for step in range(9): + filepath = binary_folder + f"execute_invc{step}_params.conduit_bin.1.0_json" + if not filecmp.cmp(reference_execute_json, filepath): + print(f'Differences detected in file "{filepath}" compared to reference "{reference_execute_json}') + exit(1) + +# Check the finalize json dump +reference_finalize_json = source_folder + "/" + reference_directory + "/finalize_reference.json" +actual_finalize_json = binary_folder + "finalize_params.conduit_bin.1.0_json" +if not filecmp.cmp(reference_finalize_json, actual_finalize_json): + print(f'Differences detected in file "{actual_finalize_json}" compared to reference "{reference_finalize_json}') + exit(1) \ No newline at end of file diff --git a/plugins/catalyst/tests/run_test_mpi.py b/plugins/catalyst/tests/run_test_mpi.py new file mode 100644 index 000000000..6cde15492 --- /dev/null +++ b/plugins/catalyst/tests/run_test_mpi.py @@ -0,0 +1,61 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +import subprocess +import sys +import os +import filecmp +import json + +binary_folder = sys.argv[1] +source_folder = sys.argv[2] +mpi_exec = sys.argv[3] + +env = os.environ.copy() +env["CATALYST_DATA_DUMP_DIRECTORY"] = binary_folder +env["CATALYST_IMPLEMENTATION_NAME"] = 'stub' # need to get the conduit json file for comparison +env["PDI_PLUGIN_PATH"] = binary_folder + '/..' +result = subprocess.run([mpi_exec, "-np", "4", binary_folder + "/TestPDICatalyst", binary_folder + "/pdi.yml"], env=env) + +if(result.returncode != 0): + exit(result.returncode) + +# get endiannes of the computer +endianness = sys.byteorder + +reference_directory = 'references' +if(endianness == 'big'): + reference_directory = reference_directory + '_big_endian' + +# Check the initialize json dump for each rank. +reference_initialize_json = source_folder + "/" + reference_directory + "/initialize_reference.json" +for rank in range(4): + actual_initialize_json = binary_folder + f"initialize_params.conduit_bin.4.{rank}_json" + with open(reference_initialize_json) as ref_file: + with open(actual_initialize_json) as actual_file: + ref_json = json.load(ref_file) + actual_json = json.load(actual_file) + if ref_json.items() != actual_json.items(): + # Ignore the length of the script path which depends on platform. + actual_json["catalyst"]["scripts"]["script1"]["number_of_elements"] = ref_json["catalyst"]["scripts"]["script1"]["number_of_elements"] + if ref_json.items() != actual_json.items(): + print(f'Differences detected in file "{actual_initialize_json}" compared to reference "{reference_initialize_json}') + exit(1) + +# Check the execute json dump for each rank. +for rank in range(4): + reference_execute_json = source_folder + "/" + reference_directory + f"/execute_reference_rank{rank}.json" + for step in range(9): + filepath = binary_folder + f"execute_invc{step}_params.conduit_bin.4.{rank}_json" + if not filecmp.cmp(reference_execute_json, filepath): + print(f'Differences detected in file "{filepath}" compared to reference "{reference_execute_json}') + exit(1) + +# Check the finalize json dump for each rank. +reference_finalize_json = source_folder + "/" + reference_directory + "/finalize_reference.json" +for rank in range(4): + actual_finalize_json = binary_folder + f"finalize_params.conduit_bin.4.{rank}_json" + if not filecmp.cmp(reference_finalize_json, actual_finalize_json): + print(f'Differences detected in file "{actual_finalize_json}" compared to reference "{reference_finalize_json}') + exit(1) \ No newline at end of file diff --git a/plugins/catalyst/tests_ghost/CMakeLists.txt b/plugins/catalyst/tests_ghost/CMakeLists.txt new file mode 100644 index 000000000..6f02364be --- /dev/null +++ b/plugins/catalyst/tests_ghost/CMakeLists.txt @@ -0,0 +1,51 @@ +#============================================================================= +# Copyright (C) 2015-2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# Copyright (C) 2020 Institute of Bioorganic Chemistry Polish Academy of Science (PSNC) +# +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the names of CEA, nor the names of the contributors may be used to +# endorse or promote products derived from this software without specific +# prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +#============================================================================= + +cmake_minimum_required(VERSION 3.22...4.2) + +# Creation of executable +add_executable(TestPDICatalystGhost + example.c) + +# MPI +find_package(MPI COMPONENTS C CXX REQUIRED) + +target_link_libraries(TestPDICatalystGhost + PRIVATE PDI::PDI_C MPI::MPI_C) + +set(CATALYST_SCRIPT_FOLDER ${CMAKE_SOURCE_DIR}/tests_ghost) +configure_file(catalyst_serial.yml.in pdi.yml) + +set(CATALYST_SCRIPT_FOLDER ${CMAKE_SOURCE_DIR}/tests_ghost) +configure_file(catalyst_serial_structured.yml.in pdi_structured.yml) + +find_package(Python3 COMPONENTS Interpreter) +add_test(NAME TestPDICatalystGhost_uniform COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/tests_ghost/run_test.py" "${CMAKE_BINARY_DIR}/tests_ghost/" "${CMAKE_SOURCE_DIR}/tests_ghost/" "true_uniform") +add_test(NAME TestPDICatalystGhost_structured COMMAND ${Python3_EXECUTABLE} "${CMAKE_SOURCE_DIR}/tests_ghost/run_test.py" "${CMAKE_BINARY_DIR}/tests_ghost/" "${CMAKE_SOURCE_DIR}/tests_ghost/" "true_structured") \ No newline at end of file diff --git a/plugins/catalyst/tests_ghost/catalyst.yml.in b/plugins/catalyst/tests_ghost/catalyst.yml.in new file mode 100644 index 000000000..b0ce25c0a --- /dev/null +++ b/plugins/catalyst/tests_ghost/catalyst.yml.in @@ -0,0 +1,55 @@ +# duration in seconds +duration: 1.75 +# global [height, width] (excluding boundary conditions or ghosts) +datasize: [60, 12] +# degree of parallelism +parallelism: { height: 3, width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type of values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: double } + + logging: debug + plugins: + mpi: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "newiter" + execute: + state: + timestep: $iter + time: 1.0*$iter + multiblock: 0 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "uniform" + dims: { i: '1+$dsize[1]', j: '1+$dsize[0]' } + origin: + x: 1.0*$pcoord[1]*($dsize[1]-2.0)-1.0 + y: 1.0*$pcoord[0]*($dsize[0]-2.0)-1.0 + spacing: { dx: 1.0, dy: 1.0 } + topologies: + my_mesh: + type: "uniform" + coordset: "my_coords" + fields: + temperature: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "main_field" + size: $dsize[0]*$dsize[1] + ghost_layers: + my_mesh: { association: "element", start: ['1', '1'], size: ['$dsize[1]-2', '$dsize[0]-2'] } diff --git a/plugins/catalyst/tests_ghost/catalyst_pipeline_with_rendering.py b/plugins/catalyst/tests_ghost/catalyst_pipeline_with_rendering.py new file mode 100644 index 000000000..cc3819847 --- /dev/null +++ b/plugins/catalyst/tests_ghost/catalyst_pipeline_with_rendering.py @@ -0,0 +1,133 @@ +# script-version: 2.0 +from paraview.simple import * +from paraview import catalyst +import time + +# registrationName must match the channel name used in the +# 'CatalystAdaptor'. +producer = TrivialProducer(registrationName="grid") + +# ---------------------------------------------------------------- +# setup views used in the visualization +# ---------------------------------------------------------------- + +# ######## render view temperature + +# Create a new 'Render View' +renderView1 = CreateView('RenderView') +# renderView1.Set( +# ViewSize=[800, 600], +# InteractionMode='2D', +# CenterOfRotation=[20.0, 3.0, 0.0], +# CameraPosition=[20.0, 30.0, 408.7], +# CameraFocalPoint=[20.0, 30.0, 0.0], +# CameraFocalDisk=1.0, +# CameraParallelScale=32.0, +# ) + +renderView1.ViewSize=[800, 600] +renderView1.InteractionMode='2D' +renderView1.CenterOfRotation=[40.0, 12.0, 0.0] +renderView1.CameraPosition=[40.0, 12.0, 208.7] +renderView1.CameraFocalPoint=[5.0, 12.0, 0.0] +renderView1.CameraFocalDisk=1.0, +renderView1.CameraParallelScale=20.0 #32.0 + + +# get color transfer function/color map for 'temperature' +temperatureLUT = GetColorTransferFunction('temperature') +## RGB: first line: min value, last line: max value +# temperatureLUT.Set( +# RGBPoints=GenerateRGBPoints( +# range_min=0.0, +# range_max=200.0, +# ), +# ScalarRangeInitialized=1.0, +# ) + + +temperatureLUT.RGBPoints=[-4.0, 0.231373, 0.298039, 0.752941, + 0.0, 0.865003, 0.865003, 0.865003, + 4.0, 0.705882, 0.0156863, 0.14902] + +temperatureLUT.ScalarRangeInitialized=1.0 + + +# show data from grid +## wgridDisplay = Show(producer, renderView1, 'UnstructuredGridRepresentation') +gridDisplay = Show(producer, renderView1, 'StructuredGridRepresentation') + +gridDisplay.Representation = 'Surface With Edges' +gridDisplay.ColorArrayName = ['CELLS', 'temperature'] +gridDisplay.LookupTable = temperatureLUT + +# get color legend/bar for temperatureLUT in view renderView1 +temperatureLUTColorBar = GetScalarBar(temperatureLUT, renderView1) +temperatureLUTColorBar.Title = 'temperature' + +# set color bar visibility +temperatureLUTColorBar.Visibility = 1 + +# show color legend +gridDisplay.SetScalarBarVisibility(renderView1, True) + +# # ---------------------------------------------------------------- +# # setup extractors +# # ---------------------------------------------------------------- + +SetActiveView(renderView1) +# create extractor +pNG2= CreateExtractor('PNG', renderView1, registrationName='PNG2') +# trace defaults for the extractor. +pNG2.Trigger = 'TimeStep' + +# init the 'PNG' selected for 'Writer' +pNG2.Writer.FileName = 'temperature_screenshot_{timestep:06d}.png' +pNG2.Writer.ImageResolution=[800, 600] +pNG2.Writer.Format = 'PNG' + +# # ---------------------------------------------------------------- +# # setup extractor for saving the solution in VTK file +# # ---------------------------------------------------------------- + +extractor_vtk_file = None + +mesh_grid = producer.GetClientSideObject().GetOutputDataObject(0) +if mesh_grid.IsA('vtkUnstructuredGrid'): + extractor_vtk_file = CreateExtractor('VTU', producer, registrationName='VTU') +elif mesh_grid.IsA('vtkMultiBlockDataSet'): + extractor_vtk_file = CreateExtractor('VTM', producer, registrationName='VTM') +elif mesh_grid.IsA('vtkPartitionedDataSet'): + extractor_vtk_file = CreateExtractor('VTPD', producer, registrationName='VTPD') +else: + raise RuntimeError("Unsupported data type: %s. Check that the adaptor is providing channel named %s", + mesh_grid.GetClassName(), "grid") + + +# ------------------------------------------------------------------------------ +# Catalyst options +options = catalyst.Options() +## 0: no client, generate the png images and vtk files. +## 1: interactive +options.EnableCatalystLive = 0 + + +# Greeting to ensure that ctest knows this script is being imported +print("#############################################################") +print("executing catalyst_pipeline") +print("#############################################################") +def catalyst_execute(info): + global producer + producer.UpdatePipeline() + print("-----------------------------------") + print("executing (cycle={}, time={})".format(info.cycle, info.time)) + print("bounds:", producer.GetDataInformation().GetBounds()) + print("temperature-range:", producer.CellData["temperature"].GetRange(0)) + # In a real simulation sleep is not needed. We use it here to slow down the + # "simulation" and make sure ParaView client can catch up with the produced + # results instead of having all of them flashing at once. + + time.sleep(1) + + if options.EnableCatalystLive: + time.sleep(0.1) diff --git a/plugins/catalyst/tests_ghost/catalyst_serial.yml.in b/plugins/catalyst/tests_ghost/catalyst_serial.yml.in new file mode 100644 index 000000000..0d03f54e5 --- /dev/null +++ b/plugins/catalyst/tests_ghost/catalyst_serial.yml.in @@ -0,0 +1,59 @@ +# global [height, width] (excluding boundary conditions or ghosts) +globalsize: [30, 12] + +# degree of parallelism +parallelism: { height: 1 , width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + dstart: { size: 2, type: array, subtype: int } # + dend: { size: 2, type: array, subtype: int } # + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type odstartf values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: int } + + logging: debug + plugins: + mpi: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "newiter" + execute: + state: + timestep: $iter + time: 1.0*$iter + multiblock: 0 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "uniform" + dims: { i: '1+$dsize[1]', j: '1+$dsize[0]' } + origin: + x: 1.0*$pcoord[1]*($dend[1]-$dstart[1])-$dstart[1] + y: 1.0*$pcoord[0]*($dend[0]-$dstart[0])-$dstart[0] + spacing: { dx: 1.0, dy: 1.0 } + topologies: + my_mesh: + type: "uniform" + coordset: "my_coords" + fields: + temperature: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "main_field" + size: $dsize[0]*$dsize[1] + ghost_layers: + my_mesh: + association: "element" + start: ['$dstart[1]', '$dstart[0]'] + size: ['$dend[1]-$dstart[1]', '$dend[0]-$dstart[0]'] \ No newline at end of file diff --git a/plugins/catalyst/tests_ghost/catalyst_serial_structured.yml.in b/plugins/catalyst/tests_ghost/catalyst_serial_structured.yml.in new file mode 100644 index 000000000..d04c85c99 --- /dev/null +++ b/plugins/catalyst/tests_ghost/catalyst_serial_structured.yml.in @@ -0,0 +1,62 @@ +# global [height, width] (excluding boundary conditions or ghosts) +globalsize: [30, 12] + +# degree of parallelism +parallelism: { height: 1 , width: 1 } + +# only the following config is passed to PDI +pdi: + metadata: # type of small values for which PDI keeps a copy + iter: int # current iteration id + dsize: { size: 2, type: array, subtype: int } # local data size including ghosts/boundary + dstart: { size: 2, type: array, subtype: int } # + dend: { size: 2, type: array, subtype: int } # + psize: { size: 2, type: array, subtype: int } # number of processes in each dimension + pcoord: { size: 2, type: array, subtype: int } # coordinate of the process + data: # type odstartf values for which PDI does not keep a copy + main_field: { size: [ '$dsize[0]', '$dsize[1]' ], type: array, subtype: int } + coords_x: { size: 2, type: array, subtype: double } # coordinate of the vertices in x-direction + coords_y: { size: 2, type: array, subtype: double } # coordinate of the vertices in y-direction + + logging: debug + plugins: + mpi: + catalyst: + scripts: + script1: "@CATALYST_SCRIPT_FOLDER@/catalyst_pipeline_with_rendering.py" + on_event: "newiter" + execute: + state: + timestep: $iter + time: 1.0*$iter + multiblock: 0 + channels: + grid: + type: "mesh" + data: + coordsets: + my_coords: + type: "explicit" + dims: { i: '1+$dsize[1]', j: '1+$dsize[0]' } + values: + x : { PDI_data_array: "coords_x", size: '($dsize[0]+1)*($dsize[1]+1)' } + y : { PDI_data_array: "coords_y", size: '($dsize[0]+1)*($dsize[1]+1)' } + topologies: + my_mesh: + type: "structured" + coordset: "my_coords" + elements: + dims: { i: '$dsize[1]', j: '$dsize[0]'} # {, offsets: [,,], strides: [,,]} + fields: + temperature: + association: "element" + topology: "my_mesh" + volume_dependent: "false" + values: + PDI_data_array: "main_field" + size: $dsize[0]*$dsize[1] + ghost_layers: + my_mesh: + association: "element" + start: ['$dstart[1]', '$dstart[0]'] + size: ['$dend[1]-$dstart[1]', '$dend[0]-$dstart[0]'] \ No newline at end of file diff --git a/plugins/catalyst/tests_ghost/example.c b/plugins/catalyst/tests_ghost/example.c new file mode 100644 index 000000000..3519e1928 --- /dev/null +++ b/plugins/catalyst/tests_ghost/example.c @@ -0,0 +1,189 @@ +/******************************************************************************* + * Copyright (C) 2026 Commissariat a l'energie atomique et aux energies alternatives (CEA) + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of CEA nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific + * prior written permission. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "pdi.h" + +void create_coordinate_of_vertices( + int dsize[2], + int dstart[2], + int local_size[2], + int pcoord[2], + double coords_x[dsize[0] + 1][dsize[1] + 1], + double coords_y[dsize[0] + 1][dsize[1] + 1] +) +{ + // catalyst variables + int cells_ghost = 1; + + size_t number_of_points[2]; + number_of_points[0] = dsize[0] + 1; + number_of_points[1] = dsize[1] + 1; + size_t total_number_of_points = number_of_points[0] * number_of_points[1]; + + // the first axis correspond to the y-coordinate. + for (int ix = 0; ix < number_of_points[0]; ix++) { + for (int iy = 0; iy < number_of_points[1]; iy++) { + coords_y[ix][iy] = 1.0 * (ix - dstart[0]) + pcoord[0] * (local_size[0]); + coords_x[ix][iy] = 1.0 * (iy - dstart[1]) + pcoord[1] * (local_size[1]); + } + } +} + +int main(int argc, char* argv[]) +{ + MPI_Init(&argc, &argv); + + if (argc != 2) { + fprintf(stderr, "Usage: %s \n", argv[0]); + exit(1); + } + + PC_tree_t conf = PC_parse_path(argv[1]); + MPI_Comm main_comm = MPI_COMM_WORLD; + + PDI_init(PC_get(conf, ".pdi")); + PDI_expose("mpi_comm", &main_comm, PDI_INOUT); + + int psize_1d; + MPI_Comm_size(main_comm, &psize_1d); + int pcoord_1d; + MPI_Comm_rank(main_comm, &pcoord_1d); + + PDI_expose("mpi_rank", &pcoord_1d, PDI_OUT); + PDI_expose("mpi_size", &psize_1d, PDI_OUT); + PDI_event("init"); + + long longval; + + int dsize[2]; + PC_int(PC_get(conf, ".globalsize[0]"), &longval); + dsize[0] = longval; + PC_int(PC_get(conf, ".globalsize[1]"), &longval); + dsize[1] = longval; + + int psize[2]; + PC_int(PC_get(conf, ".parallelism.height"), &longval); + psize[0] = longval; + PC_int(PC_get(conf, ".parallelism.width"), &longval); + psize[1] = longval; + + // check on distribution of MPI process + assert(dsize[0] % psize[0] == 0); + assert(dsize[1] % psize[1] == 0); + assert(psize[1] * psize[0] == psize_1d); + + int cart_period[2] = {0, 0}; + MPI_Comm cart_com; + MPI_Cart_create(main_comm, 2, psize, cart_period, 1, &cart_com); + int pcoord[2]; + MPI_Cart_coords(cart_com, pcoord_1d, 2, pcoord); + + int dstart[2]; + int dend[2]; + + int ghost_height[2]; // number of ghost in height direction + ghost_height[0] = 1; + ghost_height[1] = 1; + + int ghost_width[2]; // number of ghost in width direction + ghost_width[0] = 2; + ghost_width[1] = 2; + + int local_size[2]; // size of the local domain without ghost + local_size[0] = dsize[0] / psize[0]; + local_size[1] = dsize[1] / psize[1]; + + // + dsize[0] = local_size[0] + ghost_height[0] + ghost_height[1]; + dstart[0] = ghost_height[0]; + dend[0] = dsize[0] - ghost_height[1]; + + // + dsize[1] = local_size[1] + ghost_width[0] + ghost_width[1]; + dstart[1] = ghost_width[0]; + dend[1] = dsize[1] - ghost_width[1]; + + int ii = 0; + + PDI_expose("iter", &ii, PDI_OUT); + PDI_expose("dsize", dsize, PDI_OUT); + PDI_expose("dstart", dstart, PDI_OUT); + PDI_expose("dend", dend, PDI_OUT); + PDI_expose("psize", psize, PDI_OUT); + PDI_expose("pcoord", pcoord, PDI_OUT); + + int(*cur)[dsize[1]] = malloc(sizeof(int) * dsize[1] * dsize[0]); + + // initialize + for (int yy = 0; yy < dsize[0]; ++yy) { + for (int xx = 0; xx < dsize[1]; ++xx) { + cur[yy][xx] = -(pcoord_1d + 1); + } + } + + for (int yy = dstart[0]; yy < dend[0]; ++yy) { + for (int xx = dstart[1]; xx < dend[1]; ++xx) { + cur[yy][xx] = (pcoord_1d + 1); + } + } + + // allocate variables for the vertex of the mesh + double(*coords_x)[dsize[1] + 1] = malloc(sizeof(double) * (dsize[1] + 1) * (dsize[0] + 1)); + double(*coords_y)[dsize[1] + 1] = malloc(sizeof(double) * (dsize[1] + 1) * (dsize[0] + 1)); + + create_coordinate_of_vertices(dsize, dstart, local_size, pcoord, coords_x, coords_y); + + PDI_multi_expose( + "newiter", + "iter", + &ii, + PDI_INOUT, + "main_field", + cur, + PDI_INOUT, + "coords_x", + coords_x, + PDI_INOUT, + "coords_y", + coords_y, + PDI_INOUT, + NULL + ); + + // free cur + free(cur); + + PDI_finalize(); + PC_tree_destroy(&conf); + + MPI_Finalize(); +} diff --git a/plugins/catalyst/tests_ghost/run_test.py b/plugins/catalyst/tests_ghost/run_test.py new file mode 100644 index 000000000..ea0c24d39 --- /dev/null +++ b/plugins/catalyst/tests_ghost/run_test.py @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +import subprocess +import sys +import os +import filecmp +import json +import numpy as np + +binary_folder = sys.argv[1] +source_folder = sys.argv[2] +test_name = sys.argv[3] + +env = os.environ.copy() +env["CATALYST_DATA_DUMP_DIRECTORY"] = binary_folder + '/' + test_name +env["CATALYST_IMPLEMENTATION_NAME"] = 'stub' # need to get the conduit json file for comparison +# env["CATALYST_IMPLEMENTATION_PATHS"] = '/local/home/jm280892/local_9_12_paraview_catalyst/build_catalyst3/lib/catalyst/' + +env["PDI_PLUGIN_PATH"] = binary_folder + '/..' + +if test_name == 'true_uniform': + result = subprocess.run([binary_folder + "/TestPDICatalystGhost", binary_folder + "/pdi.yml"], env=env) +elif test_name == 'true_structured': + result = subprocess.run([binary_folder + "/TestPDICatalystGhost", binary_folder + "/pdi_structured.yml"], env=env) + +if(result.returncode != 0): + exit(result.returncode) + +################################################################ + +class leaf_value_info(object): + def __init__(self, leaf_node): + self.dtype = leaf_node['dtype'] + self.number_of_elements = leaf_node['number_of_elements'] + self.offset = leaf_node['offset'] + self.stride = leaf_node['stride'] + self.elements_byte = leaf_node['element_bytes'] + self.endianness = leaf_node['endianness'] + +def expected_vtk_ghost_type(): + array_exact = np.zeros((32,16), dtype=np.uint8) + array_exact[0,:] = 1 + array_exact[31,:] = 1 + + array_exact[:,0] = 1 + array_exact[:,1] = 1 + + array_exact[:,14] = 1 + array_exact[:,15] = 1 + + return array_exact + +def check_ghost_type(file_bin_json, expected_solution): + description_json = file_bin_json+"_json" + + with open(description_json) as ff: + data_description = json.load(ff) + + ghost_obj_description = data_description["catalyst"]["channels"]["grid"]["data"]["fields"]["vtkGhostType"] + + ghost_values = ghost_obj_description["values"] + + # get information of data corresponding to the leaf node + node_info = leaf_value_info(ghost_values) + + with open(file_bin_json,'rb') as ff_bin: + ff_bin.seek(node_info.offset) + chunk = ff_bin.read(node_info.elements_byte*node_info.number_of_elements) + print("Position actuelle :", ff_bin.tell()) + array = np.frombuffer(chunk, dtype=np.uint8) + array = array.reshape([32,16]) + + return np.array_equal(array, expected_solution) + +################################################################ + +file_bin_json = binary_folder + "/" + test_name + "/execute_invc0_params.conduit_bin.1.0" + +expected_solution = expected_vtk_ghost_type() +check_ghost_type(file_bin_json, expected_solution) + +if check_ghost_type(file_bin_json, expected_solution) != True: + print(f'Differences detected in vtkGhostType') + exit(1) +else: + print(f'No differences detected in vtkGhostType') diff --git a/plugins/catalyst/tests_ghost/run_test_mpi.py b/plugins/catalyst/tests_ghost/run_test_mpi.py new file mode 100644 index 000000000..c45ab8895 --- /dev/null +++ b/plugins/catalyst/tests_ghost/run_test_mpi.py @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: Copyright (c) 2024-2025 Kitware SAS +# SPDX-FileCopyrightText: Copyright (c) 2025 Commissariat a l'energie atomique et aux energies alternatives (CEA) +# SPDX-License-Identifier: Apache 2.0 + +import subprocess +import sys +import os +import filecmp +import json + +binary_folder = sys.argv[1] +source_folder = sys.argv[2] +mpi_exec = sys.argv[3] + +env = os.environ.copy() +env["CATALYST_DATA_DUMP_DIRECTORY"] = binary_folder +env["CATALYST_IMPLEMENTATION_NAME"] = 'stub' # need to get the conduit json file for comparison +env["PDI_PLUGIN_PATH"] = binary_folder + '/..' +result = subprocess.run([mpi_exec, "-np", "4", binary_folder + "/TestPDICatalyst", binary_folder + "/pdi.yml"], env=env) + +if(result.returncode != 0): + exit(result.returncode) + +# get endiannes of the computer +endianness = sys.byteorder + +if(endianness == 'little'): + # Check the initialize json dump for each rank. + reference_initialize_json = source_folder + "/references/initialize_reference.json" + for rank in range(4): + actual_initialize_json = binary_folder + f"initialize_params.conduit_bin.4.{rank}_json" + with open(reference_initialize_json) as ref_file: + with open(actual_initialize_json) as actual_file: + ref_json = json.load(ref_file) + actual_json = json.load(actual_file) + if ref_json.items() != actual_json.items(): + # Ignore the length of the script path which depends on platform. + actual_json["catalyst"]["scripts"]["script1"]["number_of_elements"] = ref_json["catalyst"]["scripts"]["script1"]["number_of_elements"] + if ref_json.items() != actual_json.items(): + print(f'Differences detected in file "{actual_initialize_json}" compared to reference "{reference_initialize_json}') + exit(1) + + # Check the execute json dump for each rank. + for rank in range(4): + reference_execute_json = source_folder + f"/references/execute_reference_rank{rank}.json" + for step in range(9): + filepath = binary_folder + f"execute_invc{step}_params.conduit_bin.4.{rank}_json" + if not filecmp.cmp(reference_execute_json, filepath): + print(f'Differences detected in file "{filepath}" compared to reference "{reference_execute_json}') + exit(1) + + # Check the finalize json dump for each rank. + reference_finalize_json = source_folder + "/references/finalize_reference.json" + for rank in range(4): + actual_finalize_json = binary_folder + f"finalize_params.conduit_bin.4.{rank}_json" + if not filecmp.cmp(reference_finalize_json, actual_finalize_json): + print(f'Differences detected in file "{actual_finalize_json}" compared to reference "{reference_finalize_json}') + exit(1) + +else: + print(f'The reference solution is based on little endian. So it is not possible to check with big endian.') + print(f'The test is marked as failed anyway for the moment.') + exit(1) diff --git a/vendor/catalyst-2.0.0 b/vendor/catalyst-2.0.0 new file mode 160000 index 000000000..ed6151a29 --- /dev/null +++ b/vendor/catalyst-2.0.0 @@ -0,0 +1 @@ +Subproject commit ed6151a298c6bcc888353e2bdf92a40e6ed8de30