From 7ebb513698565b8e88f7539705e3cb285afaa5d6 Mon Sep 17 00:00:00 2001 From: Matt Dawson Date: Thu, 29 Aug 2024 10:52:52 -0700 Subject: [PATCH] Add index-index mapping for arrays (#207) * add index map to C API * add file-based index mapping test * add Mappings struct for arrays of name-index maps * update Fortran mapping sets * add index mapping test in fortran * finish index mapping fortran tests * update python dependencies to include yaml-cpp * make yaml-cpp private * update actions to build with current commit * add yaml-cpp to export set * remove yaml-cpp from tuv export set * fix index mapping returned errors * update python dockerfile and action * output error message in python test * debugging * fix temporary string bug * remove debug code and update docker files * install yaml-cpp for python wrapper * try updated TUV * addres fortran memory issues * try to fix pip dependency issue on ubunutu * try to fix pip build * try again with pip action * update actions * update mac gcc action versions * update ubuntu action * remove older fortran compiler tests bc of ice * update python install script * fix merge problem and update TUV-x commit * remove python wrapper tests from CMake; let the pip action test them instead * debugging * one last desparate attempt to get the test to pass * add const for config arg --- .github/workflows/docker.yml | 6 +- .github/workflows/mac.yml | 7 +- .github/workflows/pip.yml | 2 +- .github/workflows/ubuntu.yml | 7 +- .github/workflows/windows.yml | 8 +- cmake/dependencies.cmake | 13 +- cmake/test_util.cmake | 2 +- docker/Dockerfile | 4 +- docker/Dockerfile.docs | 10 +- docker/Dockerfile.fortran-gcc | 3 +- docker/Dockerfile.fortran-gcc.integration | 3 +- docker/Dockerfile.fortran-intel | 4 +- docker/Dockerfile.fortran-nvhpc | 16 +- docker/Dockerfile.memcheck | 4 +- docker/Dockerfile.mpi | 4 +- docker/Dockerfile.mpi_openmp | 4 +- docker/Dockerfile.openmp | 4 +- docker/Dockerfile.pip | 38 ++ docker/Dockerfile.python | 4 + fortran/micm.F90 | 102 ++-- .../test_micm_api.F90 | 80 ++- .../test_micm_box_model.F90 | 7 +- fortran/test/unit/util.F90 | 115 ++++- fortran/util.F90 | 457 ++++++++++++++++-- include/musica/micm.hpp | 6 +- include/musica/util.hpp | 82 +++- python/CMakeLists.txt | 18 +- python/test/CMakeLists.txt | 9 - src/CMakeLists.txt | 5 + src/micm/micm.cpp | 44 +- src/packaging/CMakeLists.txt | 2 +- src/test/CMakeLists.txt | 4 +- .../data/util_index_mapping_from_file.json | 11 + src/test/unit/micm/micm_c_api.cpp | 98 ++-- src/test/unit/util.cpp | 98 +++- src/tuvx/tuvx_util.F90 | 22 +- src/util.cpp | 130 ++++- 37 files changed, 1135 insertions(+), 298 deletions(-) create mode 100644 docker/Dockerfile.pip delete mode 100644 python/test/CMakeLists.txt create mode 100644 src/test/data/util_index_mapping_from_file.json diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f1da711a..58ac3bb1 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,6 +27,8 @@ jobs: - Dockerfile.mpi - Dockerfile.mpi_openmp - Dockerfile.python + - Dockerfile.pip + build_type: [Release, Debug] steps: - name: Checkout code uses: actions/checkout@v4 @@ -37,10 +39,10 @@ jobs: run: rm -rf /opt/hostedtoolcache - name: Build Docker image - run: docker build -t musica -f docker/${{ matrix.dockerfile }} . + run: docker build -t musica -f docker/${{ matrix.dockerfile }} . --build-arg MUSICA_GIT_TAG=${{ github.sha }} --build-arg BUILD_TYPE=${{ matrix.build_type }} - name: Run tests in container - if: matrix.dockerfile != 'Dockerfile.coverage' + if: matrix.dockerfile != 'Dockerfile.coverage' && matrix.dockerfile != 'Dockerfile.pip' run: docker run --name test-container -t musica bash -c 'make test ARGS="--rerun-failed --output-on-failure -j8"' - name: Run coverage tests in container diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 1e3b28bf..b6a006cf 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -20,6 +20,7 @@ jobs: compiler: - { cpp: g++-12, c: gcc-12, fc: gfortran-12} - { cpp: g++-13, c: gcc-13, fc: gfortran-13} + - { cpp: g++-14, c: gcc-14, fc: gfortran-14} - { cpp: clang++, c: clang, fc: gfortran-12} - { cpp: g++-14, c: gcc-14, fc: gfortran-14} build_type: [Release] @@ -44,6 +45,7 @@ jobs: cmake -S . -B build \ -D CMAKE_CXX_FLAGS=-Wl,-ld_classic \ -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -D MUSICA_GIT_TAG=${{ github.sha }} \ -D MUSICA_ENABLE_PYTHON_LIBRARY=ON \ -D Python3_EXECUTABLE=$PYTHON_PATH @@ -57,6 +59,7 @@ jobs: cmake -S . -B build \ -D CMAKE_CXX_FLAGS=-Wl,-ld_classic \ -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} \ + -D MUSICA_GIT_TAG=${{ github.sha }} \ -D MUSICA_ENABLE_PYTHON_LIBRARY=ON \ -D Python3_EXECUTABLE=$PYTHON_PATH \ -D CMAKE_OSX_SYSROOT="" @@ -73,7 +76,7 @@ jobs: runs-on: macos-latest strategy: matrix: - gcc_version: [12, 13, 14] + gcc_version: [13, 14] build_type: [Release] env: FC: gfortran-${{ matrix.gcc_version }} @@ -85,7 +88,7 @@ jobs: run: brew install netcdf netcdf-fortran - name: Run Cmake - run: cmake -S . -B build -D CMAKE_CXX_FLAGS=-Wl,-ld_classic -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_BUILD_FORTRAN_INTERFACE=ON + run: cmake -S . -B build -D CMAKE_CXX_FLAGS=-Wl,-ld_classic -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_BUILD_FORTRAN_INTERFACE=ON - name: Build run: cmake --build build --verbose --parallel 10 diff --git a/.github/workflows/pip.yml b/.github/workflows/pip.yml index e7c81a32..724bbc0a 100644 --- a/.github/workflows/pip.yml +++ b/.github/workflows/pip.yml @@ -34,4 +34,4 @@ jobs: run: pip install --verbose .[test] - name: Test - run: python -m unittest discover -s python/test \ No newline at end of file + run: python -m unittest discover -v -s python/test \ No newline at end of file diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 510c1c6c..7f28cc16 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -41,7 +41,7 @@ jobs: sudo apt-get install -y python3-numpy - name: Run Cmake - run: cmake -S . -B build -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_ENABLE_PYTHON_LIBRARY=${{ matrix.options.python }} -D MUSICA_ENABLE_TUVX=${{ matrix.matrix.options.tuvx }} + run: cmake -S . -B build -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_ENABLE_PYTHON_LIBRARY=${{ matrix.options.python }} -D MUSICA_ENABLE_TUVX=${{ matrix.matrix.options.tuvx }} - name: Build run: cmake --build build --verbose @@ -52,9 +52,10 @@ jobs: ctest -C ${{ matrix.build_type }} --rerun-failed --output-on-failure . --verbose fortran: runs-on: ubuntu-24.04 + continue-on-error: true strategy: matrix: - gcc_version: [12, 13, 14] + gcc_version: [14] build_type: [Debug, Release] env: CXX: g++-${{ matrix.gcc_version }} @@ -70,7 +71,7 @@ jobs: sudo apt-get install -y libnetcdf-dev netcdf-bin libnetcdff-dev liblapack-dev libblas-dev - name: Run Cmake - run: cmake -S . -B build -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_BUILD_FORTRAN_INTERFACE=ON + run: cmake -S . -B build -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_BUILD_FORTRAN_INTERFACE=ON - name: Build run: cmake --build build --verbose --parallel 1 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 4d0ad1cc..db8ebc4d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -30,7 +30,7 @@ jobs: - run: pip install numpy - name: Run Cmake - run: cmake -S . -B build -G "MinGW Makefiles" -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON + run: cmake -S . -B build -G "MinGW Makefiles" -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON - name: Build run: cmake --build build --parallel @@ -61,7 +61,7 @@ jobs: - run: pip install numpy - name: Run CMake - run: cmake -S . -B build -G "Visual Studio 17 2022" -A ${{ matrix.architecture }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON + run: cmake -S . -B build -G "Visual Studio 17 2022" -A ${{ matrix.architecture }} -D MUSICA_GIT_TAG=${{ github.sha }} -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON - name: Build run: cmake --build build --config ${{ matrix.build_type }} --parallel @@ -86,7 +86,7 @@ jobs: - run: pip install numpy - name: Run CMake - run: cmake -S . -B build -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang.exe" -G"MinGW Makefiles" -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON + run: cmake -S . -B build -DCMAKE_CXX_COMPILER="C:/Program Files/LLVM/bin/clang++.exe" -DCMAKE_C_COMPILER="C:/Program Files/LLVM/bin/clang.exe" -G"MinGW Makefiles" -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON - name: Build run: cmake --build build --parallel @@ -115,7 +115,7 @@ jobs: - run: pip install numpy - name: Run CMake - run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -T ClangCL -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON + run: cmake -S . -B build -G "Visual Studio 16 2019" -A ${{ matrix.architecture }} -T ClangCL -D MUSICA_GIT_TAG=${{ github.sha }} -D MUSICA_ENABLE_TUVX=OFF -D MUSICA_ENABLE_PYTHON_LIBRARY=ON - name: Build run: cmake --build build --config Debug --parallel diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 52719f9f..11849256 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -20,6 +20,17 @@ if (MUSICA_BUILD_FORTRAN_INTERFACE) pkg_check_modules(netcdfc IMPORTED_TARGET REQUIRED netcdf) endif() +# ############################################################################## +# yaml-cpp + +FetchContent_Declare( + yaml-cpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp/ + GIT_TAG 0.8.0 + GIT_PROGRESS NOT + ${FETCHCONTENT_QUIET}) +FetchContent_MakeAvailable(yaml-cpp) + ################################################################################ # google test if(MUSICA_ENABLE_TESTS) @@ -75,7 +86,7 @@ if (MUSICA_ENABLE_TUVX AND MUSICA_BUILD_C_CXX_INTERFACE) set(TUVX_INSTALL_INCLUDE_DIR ${MUSICA_INSTALL_INCLUDE_DIR} CACHE STRING "" FORCE) set_git_default(TUVX_GIT_REPOSITORY https://github.com/NCAR/tuv-x.git) - set_git_default(TUVX_GIT_TAG 674ee1e72853bb44d23c36602fa73c955b2f021d) + set_git_default(TUVX_GIT_TAG ccda76c0553064a9b7b0eba73162ddeee6d8eaff) FetchContent_Declare(tuvx GIT_REPOSITORY ${TUVX_GIT_REPOSITORY} diff --git a/cmake/test_util.cmake b/cmake/test_util.cmake index 39cafea1..8b3dce01 100644 --- a/cmake/test_util.cmake +++ b/cmake/test_util.cmake @@ -41,7 +41,7 @@ function(create_standard_test_cxx) include(CMakeParseArguments) cmake_parse_arguments(${prefix} " " "${singleValues}" "${multiValues}" ${ARGN}) add_executable(test_${TEST_NAME} ${TEST_SOURCES}) - target_link_libraries(test_${TEST_NAME} PUBLIC musica::musica GTest::gtest_main) + target_link_libraries(test_${TEST_NAME} PUBLIC musica::musica GTest::gtest_main yaml-cpp) if(MUSICA_ENABLE_OPENMP) target_link_libraries(test_${TEST_NAME} PUBLIC OpenMP::OpenMP_CXX) endif() diff --git a/docker/Dockerfile b/docker/Dockerfile index 31f319f2..6eea82ee 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Release RUN dnf -y update \ @@ -25,6 +26,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ && cd build \ && make install -j 8 diff --git a/docker/Dockerfile.docs b/docker/Dockerfile.docs index b073c621..007582f5 100644 --- a/docker/Dockerfile.docs +++ b/docker/Dockerfile.docs @@ -1,4 +1,7 @@ -FROM fedora:37 +FROM fedora:latest + +ARG MUSICA_GIT_TAG=main +ARG BUILD_TYPE=Release RUN dnf -y update \ && dnf -y install \ @@ -17,7 +20,8 @@ RUN dnf -y update \ COPY . /musica/ -RUN pip3 install -r /musica/docs/requirements.txt +RUN python3 -m pip install --upgrade pip setuptools \ + && python3 -m pip install -r /musica/docs/requirements.txt ARG SUFFIX="" ENV SWITCHER_SUFFIX=$SUFFIX @@ -28,6 +32,8 @@ RUN mkdir /build \ && cd /build \ && cmake -D MUSICA_BUILD_DOCS=ON \ -D MUSICA_ENABLE_TUVX=OFF \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ + -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ /musica \ && make docs diff --git a/docker/Dockerfile.fortran-gcc b/docker/Dockerfile.fortran-gcc index 0a181191..f8ac3054 100644 --- a/docker/Dockerfile.fortran-gcc +++ b/docker/Dockerfile.fortran-gcc @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Release RUN dnf -y update \ diff --git a/docker/Dockerfile.fortran-gcc.integration b/docker/Dockerfile.fortran-gcc.integration index 610eebc8..3beb2c30 100644 --- a/docker/Dockerfile.fortran-gcc.integration +++ b/docker/Dockerfile.fortran-gcc.integration @@ -1,4 +1,4 @@ -FROM fedora:35 +FROM fedora:latest ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Release @@ -38,6 +38,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ && cd build \ && make install -j diff --git a/docker/Dockerfile.fortran-intel b/docker/Dockerfile.fortran-intel index 34bb0eb7..a3f3ec94 100644 --- a/docker/Dockerfile.fortran-intel +++ b/docker/Dockerfile.fortran-intel @@ -8,6 +8,7 @@ RUN wget -qO - https://repositories.intel.com/gpu/intel-graphics.key | \ gpg --yes --dearmor --output /usr/share/keyrings/intel-graphics-archive-keyring.gpg ARG MUSICA_GIT_TAG=main +ARG BUILD_TYPE=Release RUN apt update \ && apt -y install \ @@ -52,6 +53,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ && cd build \ && make install -j @@ -63,7 +65,7 @@ ENV FC=ifx RUN cd musica/fortran/test/fetch_content_integration \ && mkdir build && cd build \ && cmake .. \ - -D CMAKE_BUILD_TYPE=Release \ + -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ -D CMAKE_EXE_LINKER_FLAGS="-Wl,--copy-dt-needed-entries" \ -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D MUSICA_ENABLE_MEMCHECK=ON \ diff --git a/docker/Dockerfile.fortran-nvhpc b/docker/Dockerfile.fortran-nvhpc index ffdfc9b5..5bd1d09b 100644 --- a/docker/Dockerfile.fortran-nvhpc +++ b/docker/Dockerfile.fortran-nvhpc @@ -49,23 +49,25 @@ COPY . musica # Build and install MUSICA RUN cd musica \ - && cmake -S . \ - -B build \ - -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ - && cd build \ - && make install -j +&& cmake -S . \ +-B build \ +-D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ +-D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ +&& cd build \ +&& make install -j # Set environment variables to build MUSICA-Fortran using nvidia compilers ENV CXX=nvc++ ENV CC=nvc ENV FC=nvfortran +# (There is a runtime test failure when building MUSICA with nvhpc in Release mode, so we build in Debug mode) RUN cd musica/fortran/test/fetch_content_integration \ && mkdir build && cd build \ && cmake .. \ - -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -D CMAKE_EXE_LINKER_FLAGS="-Wl,--copy-dt-needed-entries" \ + -D CMAKE_BUILD_TYPE=Debug \ -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ + -D CMAKE_EXE_LINKER_FLAGS="-Wl,--copy-dt-needed-entries" \ -D MUSICA_ENABLE_MEMCHECK=ON \ && make -j diff --git a/docker/Dockerfile.memcheck b/docker/Dockerfile.memcheck index 8b330b51..6d9f879a 100644 --- a/docker/Dockerfile.memcheck +++ b/docker/Dockerfile.memcheck @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Debug RUN dnf -y update \ @@ -24,6 +25,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D MUSICA_ENABLE_MEMCHECK=ON \ && cd build \ && make install -j 8 diff --git a/docker/Dockerfile.mpi b/docker/Dockerfile.mpi index 8935ca62..80690e95 100644 --- a/docker/Dockerfile.mpi +++ b/docker/Dockerfile.mpi @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Debug RUN dnf -y update \ @@ -40,6 +41,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D MUSICA_ENABLE_TESTS=ON \ -D MUSICA_ENABLE_MPI=ON \ -D CMAKE_Fortran_COMPILER=/usr/lib64/openmpi/bin/mpif90 \ diff --git a/docker/Dockerfile.mpi_openmp b/docker/Dockerfile.mpi_openmp index 7a5eb9b8..860a7ba7 100644 --- a/docker/Dockerfile.mpi_openmp +++ b/docker/Dockerfile.mpi_openmp @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Debug RUN dnf -y update \ @@ -40,6 +41,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D MUSICA_ENABLE_MPI=ON \ -D MUSICA_ENABLE_OPENMP=ON \ -D MUSICA_ENABLE_TESTS=ON \ diff --git a/docker/Dockerfile.openmp b/docker/Dockerfile.openmp index cb612509..3caa1f3c 100644 --- a/docker/Dockerfile.openmp +++ b/docker/Dockerfile.openmp @@ -1,5 +1,6 @@ -FROM fedora:35 +FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Debug RUN dnf -y update \ @@ -39,6 +40,7 @@ RUN cd musica \ && cmake -S . \ -B build \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D MUSICA_ENABLE_OPENMP=ON \ -D MUSICA_ENABLE_TESTS=ON \ -D CMAKE_Fortran_COMPILER=/usr/lib64/openmpi/bin/mpif90 \ diff --git a/docker/Dockerfile.pip b/docker/Dockerfile.pip new file mode 100644 index 00000000..d9955f34 --- /dev/null +++ b/docker/Dockerfile.pip @@ -0,0 +1,38 @@ +FROM ubuntu:latest + +ARG MUSICA_GIT_TAG=main +ARG BUILD_TYPE=Release + +RUN apt-get update \ + && apt-get install -y \ + cmake \ + gcc \ + g++ \ + gfortran \ + gdb \ + git \ + libhdf5-dev \ + libnetcdf-dev \ + libjson-c-dev \ + lcov \ + make \ + valgrind \ + vim \ + python3 \ + python3-pip \ + zlib1g-dev \ + && apt-get clean + +# Copy the musica code +COPY . musica + +# Add requirements +RUN python3 -m pip install --ignore-installed --upgrade wheel setuptools --break-system-packages + +# Build and install musica +RUN cd musica \ + && pip install --verbose .[test] --break-system-packages + +# Test +RUN cd musica \ + && python3 -m unittest discover -v -s python/test \ No newline at end of file diff --git a/docker/Dockerfile.python b/docker/Dockerfile.python index 8484ddc7..c8267316 100644 --- a/docker/Dockerfile.python +++ b/docker/Dockerfile.python @@ -1,5 +1,6 @@ FROM fedora:latest +ARG MUSICA_GIT_TAG=main ARG BUILD_TYPE=Release RUN dnf -y update \ @@ -14,6 +15,7 @@ RUN dnf -y update \ pip \ python \ python-devel \ + valgrind \ && dnf clean all # Copy the musica code @@ -46,9 +48,11 @@ RUN pip install -r requirements.txt RUN cd musica \ && cmake -S . \ -B build \ + -D MUSICA_GIT_TAG=${MUSICA_GIT_TAG} \ -D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ -D MUSICA_ENABLE_PYTHON_LIBRARY=ON \ -D MUSICA_ENABLE_TUVX=OFF \ + -D MUSICA_ENABLE_MEMCHECK=ON \ && cd build \ && make install -j 8 diff --git a/fortran/micm.F90 b/fortran/micm.F90 index b9685d30..1a818b8e 100644 --- a/fortran/micm.F90 +++ b/fortran/micm.F90 @@ -6,7 +6,7 @@ module musica_micm use iso_c_binding, only: c_ptr, c_char, c_int, c_int64_t, c_bool, c_double, c_null_char, & c_size_t, c_f_pointer, c_funptr, c_null_ptr, c_associated use iso_fortran_env, only: int64 - use musica_util, only: assert, mapping_t, string_t, string_t_c + use musica_util, only: assert, mappings_t, string_t, string_t_c implicit none public :: micm_t, solver_stats_t, get_micm_version @@ -34,7 +34,8 @@ module musica_micm end enum interface - function create_micm_c(config_path, solver_type, num_grid_cells, error) bind(C, name="CreateMicm") + function create_micm_c(config_path, solver_type, num_grid_cells, error) & + bind(C, name="CreateMicm") use musica_util, only: error_t_c import c_ptr, c_int, c_char character(kind=c_char), intent(in) :: config_path(*) @@ -73,7 +74,8 @@ function get_micm_version_c() bind(C, name="MicmVersion") type(string_t_c) :: get_micm_version_c end function get_micm_version_c - function get_species_property_string_c(micm, species_name, property_name, error) bind(c, name="GetSpeciesPropertyString") + function get_species_property_string_c(micm, species_name, property_name, error) & + bind(c, name="GetSpeciesPropertyString") use musica_util, only: error_t_c, string_t_c import c_ptr, c_char type(c_ptr), value, intent(in) :: micm @@ -82,60 +84,56 @@ function get_species_property_string_c(micm, species_name, property_name, error) type(string_t_c) :: get_species_property_string_c end function get_species_property_string_c - function get_species_property_double_c(micm, species_name, property_name, error) bind(c, name="GetSpeciesPropertyDouble") - use musica_util, only: error_t_c - import c_ptr, c_char, c_double - type(c_ptr), value, intent(in) :: micm - character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) - type(error_t_c), intent(inout) :: error - real(kind=c_double) :: get_species_property_double_c + function get_species_property_double_c(micm, species_name, property_name, error) & + bind(c, name="GetSpeciesPropertyDouble") + use musica_util, only: error_t_c + import c_ptr, c_char, c_double + type(c_ptr), value, intent(in) :: micm + character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) + type(error_t_c), intent(inout) :: error + real(kind=c_double) :: get_species_property_double_c end function get_species_property_double_c - function get_species_property_int_c(micm, species_name, property_name, error) bind(c, name="GetSpeciesPropertyInt") - use musica_util, only: error_t_c - import c_ptr, c_char, c_int - type(c_ptr), value, intent(in) :: micm - character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) - type(error_t_c), intent(inout) :: error - integer(kind=c_int) :: get_species_property_int_c + function get_species_property_int_c(micm, species_name, property_name, error) & + bind(c, name="GetSpeciesPropertyInt") + use musica_util, only: error_t_c + import c_ptr, c_char, c_int + type(c_ptr), value, intent(in) :: micm + character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) + type(error_t_c), intent(inout) :: error + integer(kind=c_int) :: get_species_property_int_c end function get_species_property_int_c - function get_species_property_bool_c(micm, species_name, property_name, error) bind(c, name="GetSpeciesPropertyBool") - use musica_util, only: error_t_c - import c_ptr, c_char, c_bool - type(c_ptr), value, intent(in) :: micm - character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) - type(error_t_c), intent(inout) :: error - logical(kind=c_bool) :: get_species_property_bool_c + function get_species_property_bool_c(micm, species_name, property_name, error) & + bind(c, name="GetSpeciesPropertyBool") + use musica_util, only: error_t_c + import c_ptr, c_char, c_bool + type(c_ptr), value, intent(in) :: micm + character(len=1, kind=c_char), intent(in) :: species_name(*), property_name(*) + type(error_t_c), intent(inout) :: error + logical(kind=c_bool) :: get_species_property_bool_c end function get_species_property_bool_c - type(c_ptr) function get_species_ordering_c(micm, array_size, error) bind(c, name="GetSpeciesOrdering") - use musica_util, only: error_t_c - import c_ptr, c_size_t - type(c_ptr), value, intent(in) :: micm - integer(kind=c_size_t), intent(out) :: array_size - type(error_t_c), intent(inout) :: error + type(mappings_t_c) function get_species_ordering_c(micm, error) & + bind(c, name="GetSpeciesOrdering") + use musica_util, only: error_t_c, mappings_t_c + import c_ptr, c_size_t + type(c_ptr), value, intent(in) :: micm + type(error_t_c), intent(inout) :: error end function get_species_ordering_c - type(c_ptr) function get_user_defined_reaction_rates_ordering_c(micm, array_size, error) & + type(mappings_t_c) function get_user_defined_reaction_rates_ordering_c(micm, error) & bind(c, name="GetUserDefinedReactionRatesOrdering") - use musica_util, only: error_t_c - import c_ptr, c_size_t - type(c_ptr), value, intent(in) :: micm - integer(kind=c_size_t), intent(out) :: array_size - type(error_t_c), intent(inout) :: error + use musica_util, only: error_t_c, mappings_t_c + import c_ptr, c_size_t + type(c_ptr), value, intent(in) :: micm + type(error_t_c), intent(inout) :: error end function get_user_defined_reaction_rates_ordering_c - - subroutine delete_mappings_c(mappings, array_size) bind(C, name="DeleteMappings") - import c_ptr, c_size_t - type(c_ptr), value, intent(in) :: mappings - integer(kind=c_size_t), value, intent(in) :: array_size - end subroutine delete_mappings_c end interface type :: micm_t - type(mapping_t), allocatable :: species_ordering(:) - type(mapping_t), allocatable :: user_defined_reaction_rates(:) + type(mappings_t), pointer :: species_ordering => null() + type(mappings_t), pointer :: user_defined_reaction_rates => null() type(c_ptr), private :: ptr = c_null_ptr contains ! Solve the chemical system @@ -199,8 +197,6 @@ function constructor(config_path, solver_type, num_grid_cells, error) result( t type(error_t), intent(inout) :: error character(len=1, kind=c_char) :: c_config_path(len_trim(config_path)+1) integer :: n, i - type(c_ptr) :: mappings_ptr - integer(c_size_t) :: mappings_length type(error_t_c) :: error_c allocate( this ) @@ -219,26 +215,22 @@ function constructor(config_path, solver_type, num_grid_cells, error) result( t return end if - mappings_ptr = get_species_ordering_c(this%ptr, mappings_length, error_c) + this%species_ordering => mappings_t( get_species_ordering_c(this%ptr, error_c) ) error = error_t(error_c) if (.not. error%is_success()) then deallocate(this) nullify(this) return end if - this%species_ordering = copy_mappings(mappings_ptr, mappings_length) - call delete_mappings_c(mappings_ptr, mappings_length) - mappings_ptr = get_user_defined_reaction_rates_ordering_c(this%ptr, & - mappings_length, error_c) + this%user_defined_reaction_rates => & + mappings_t( get_user_defined_reaction_rates_ordering_c(this%ptr, error_c) ) error = error_t(error_c) if (.not. error%is_success()) then deallocate(this) nullify(this) return end if - this%user_defined_reaction_rates = copy_mappings(mappings_ptr, mappings_length) - call delete_mappings_c(mappings_ptr, mappings_length) end function constructor @@ -446,6 +438,10 @@ subroutine finalize(this) type(error_t_c) :: error_c type(error_t) :: error + + if (associated(this%species_ordering)) deallocate(this%species_ordering) + if (associated(this%user_defined_reaction_rates)) & + deallocate(this%user_defined_reaction_rates) call delete_micm_c(this%ptr, error_c) this%ptr = c_null_ptr error = error_t(error_c) diff --git a/fortran/test/fetch_content_integration/test_micm_api.F90 b/fortran/test/fetch_content_integration/test_micm_api.F90 index 6f6d200a..f027c407 100644 --- a/fortran/test/fetch_content_integration/test_micm_api.F90 +++ b/fortran/test/fetch_content_integration/test_micm_api.F90 @@ -66,7 +66,6 @@ subroutine test_api() integer :: i integer :: O2_index, O_index, O1D_index, O3_index integer :: jO2_index, jO3a_index, jO3b_index - logical :: found config_path = "configs/chapman" solver_type = Rosenbrock @@ -77,21 +76,21 @@ subroutine test_api() micm => micm_t(config_path, solver_type, num_grid_cells, error) ASSERT( error%is_success() ) - O2_index = find_mapping_index( micm%species_ordering, "O2", found ) - ASSERT( found ) - O_index = find_mapping_index( micm%species_ordering, "O", found ) - ASSERT( found ) - O1D_index = find_mapping_index( micm%species_ordering, "O1D", found ) - ASSERT( found ) - O3_index = find_mapping_index( micm%species_ordering, "O3", found ) - ASSERT( found ) - - jO2_index = find_mapping_index( micm%user_defined_reaction_rates, "PHOTO.jO2", found ) - ASSERT( found ) - jO3a_index = find_mapping_index( micm%user_defined_reaction_rates, "PHOTO.jO3->O", found ) - ASSERT( found ) - jO3b_index = find_mapping_index( micm%user_defined_reaction_rates, "PHOTO.jO3->O1D", found ) - ASSERT( found ) + O2_index = micm%species_ordering%index( "O2", error ) + ASSERT( error%is_success() ) + O_index = micm%species_ordering%index( "O", error ) + ASSERT( error%is_success() ) + O1D_index = micm%species_ordering%index( "O1D", error ) + ASSERT( error%is_success() ) + O3_index = micm%species_ordering%index( "O3", error ) + ASSERT( error%is_success() ) + + jO2_index = micm%user_defined_reaction_rates%index( "PHOTO.jO2", error ) + ASSERT( error%is_success() ) + jO3a_index = micm%user_defined_reaction_rates%index( "PHOTO.jO3->O", error ) + ASSERT( error%is_success() ) + jO3b_index = micm%user_defined_reaction_rates%index( "PHOTO.jO3->O1D", error ) + ASSERT( error%is_success() ) temperature(1) = 272.5 pressure(1) = 101253.4 @@ -108,15 +107,13 @@ subroutine test_api() micm_version = get_micm_version() print *, "[test micm fort api] MICM version ", micm_version%get_char_array() - do i = 1, size( micm%species_ordering ) - associate(the_mapping => micm%species_ordering(i)) - print *, "Species Name:", the_mapping%name(), ", Index:", the_mapping%index() - end associate + do i = 1, micm%species_ordering%size() + print *, "Species Name:", micm%species_ordering%name(i), & + ", Index:", micm%species_ordering%index(i) end do - do i = 1, size( micm%user_defined_reaction_rates ) - associate(the_mapping => micm%user_defined_reaction_rates(i)) - print *, "User Defined Reaction Rate Name:", the_mapping%name(), ", Index:", the_mapping%index() - end associate + do i = 1, micm%user_defined_reaction_rates%size() + print *, "User Defined Reaction Rate Name:", micm%user_defined_reaction_rates%name(i), & + ", Index:", micm%user_defined_reaction_rates%index(i) end do write(*,*) "[test micm fort api] Initial concentrations", concentrations @@ -195,29 +192,28 @@ subroutine test_multiple_grid_cells(micm, NUM_GRID_CELLS) real(c_double) :: k1, k2, k3, k4 real(c_double) :: A, B, C, D, E, F integer :: i_cell - logical :: found real :: temp type(ArrheniusReaction) :: r1, r2 time_step = 200 - A_index = find_mapping_index( micm%species_ordering, "A", found ) - ASSERT( found ) - B_index = find_mapping_index( micm%species_ordering, "B", found ) - ASSERT( found ) - C_index = find_mapping_index( micm%species_ordering, "C", found ) - ASSERT( found ) - D_index = find_mapping_index( micm%species_ordering, "D", found ) - ASSERT( found ) - E_index = find_mapping_index( micm%species_ordering, "E", found ) - ASSERT( found ) - F_index = find_mapping_index( micm%species_ordering, "F", found ) - ASSERT( found ) - - R1_index = find_mapping_index( micm%user_defined_reaction_rates, "USER.reaction 1", found ) - ASSERT( found ) - R2_index = find_mapping_index( micm%user_defined_reaction_rates, "USER.reaction 2", found ) - ASSERT( found ) + A_index = micm%species_ordering%index( "A", error ) + ASSERT( error%is_success() ) + B_index = micm%species_ordering%index( "B", error ) + ASSERT( error%is_success() ) + C_index = micm%species_ordering%index( "C", error ) + ASSERT( error%is_success() ) + D_index = micm%species_ordering%index( "D", error ) + ASSERT( error%is_success() ) + E_index = micm%species_ordering%index( "E", error ) + ASSERT( error%is_success() ) + F_index = micm%species_ordering%index( "F", error ) + ASSERT( error%is_success() ) + + R1_index = micm%user_defined_reaction_rates%index( "USER.reaction 1", error ) + ASSERT( error%is_success() ) + R2_index = micm%user_defined_reaction_rates%index( "USER.reaction 2", error ) + ASSERT( error%is_success() ) do i_cell = 1, NUM_GRID_CELLS call random_number( temp ) diff --git a/fortran/test/fetch_content_integration/test_micm_box_model.F90 b/fortran/test/fetch_content_integration/test_micm_box_model.F90 index 20bb5443..e1056884 100644 --- a/fortran/test/fetch_content_integration/test_micm_box_model.F90 +++ b/fortran/test/fetch_content_integration/test_micm_box_model.F90 @@ -51,10 +51,9 @@ subroutine box_model() write(*,*) "Creating MICM solver..." micm => micm_t(config_path, solver_type, num_grid_cells, error) - do i = 1, size( micm%species_ordering ) - associate(the_mapping => micm%species_ordering(i)) - print *, "Species Name:", the_mapping%name(), ", Index:", the_mapping%index() - end associate + do i = 1, micm%species_ordering%size() + print *, "Species Name:", micm%species_ordering%name(i), & + ", Index:", micm%species_ordering%index(i) end do write(*,*) "Solving starts..." diff --git a/fortran/test/unit/util.F90 b/fortran/test/unit/util.F90 index b75c7179..0fa09513 100644 --- a/fortran/test/unit/util.F90 +++ b/fortran/test/unit/util.F90 @@ -9,12 +9,14 @@ program test_util #define ASSERT_NE( a, b ) call assert( a /= b, __FILE__, __LINE__ ) use, intrinsic :: iso_c_binding, only: c_char, c_ptr, c_loc, c_size_t, c_null_char + use musica_constants, only: dk => musica_dk use musica_util implicit none call test_string_t() call test_error_t() call test_mapping_t() + call test_index_mapping_t() contains @@ -139,12 +141,15 @@ end subroutine test_error_t subroutine test_mapping_t() - type(mapping_t) :: a, b, c + type(mapping_t), pointer :: a, b, c type(mapping_t_c) :: a_c, b_c, c_c type(mapping_t_c), target :: c_mappings(3) type(c_ptr) :: c_mappings_ptr type(mapping_t), allocatable :: f_mappings(:) + type(mappings_t), pointer :: mappings logical :: found + type(error_t) :: error + integer :: index a_c%index_ = 3 a_c%name_ = create_c_string( "foo" ) @@ -156,9 +161,9 @@ subroutine test_mapping_t() c_c%name_ = create_c_string( "baz" ) ! construct and take ownership of the c mapping - a = mapping_t( a_c ) - b = mapping_t( b_c ) - c = mapping_t( c_c ) + a => mapping_t( a_c ) + b => mapping_t( b_c ) + c => mapping_t( c_c ) ASSERT_EQ( a%index(), 4 ) ASSERT_EQ( a%name(), "foo" ) @@ -185,8 +190,10 @@ subroutine test_mapping_t() ! copy assignment of c mapping a = a_c b = a_c + ! construct and take ownership of the c mapping - c = mapping_t( a_c ) + deallocate( c ) + c => mapping_t( a_c ) ASSERT_EQ( a%index(), 14 ) ASSERT_EQ( a%name(), "qux" ) @@ -197,6 +204,10 @@ subroutine test_mapping_t() ASSERT_EQ( c%index(), 14 ) ASSERT_EQ( c%name(), "qux" ) + deallocate( a ) + deallocate( b ) + deallocate( c ) + c_mappings(1)%index_ = 21 c_mappings(1)%name_ = create_c_string( "quux" ) c_mappings(2)%index_ = 34 @@ -205,7 +216,7 @@ subroutine test_mapping_t() c_mappings(3)%name_ = create_c_string( "grault" ) c_mappings_ptr = c_loc( c_mappings ) - f_mappings = copy_mappings( c_mappings_ptr, 3_c_size_t ) + call copy_mappings( c_mappings_ptr, 3_c_size_t, f_mappings ) call delete_string_c( c_mappings(1)%name_ ) call delete_string_c( c_mappings(2)%name_ ) call delete_string_c( c_mappings(3)%name_ ) @@ -229,8 +240,100 @@ subroutine test_mapping_t() ASSERT_EQ( find_mapping_index( f_mappings, "foo", found ), -1 ) ASSERT( .not. found ) + ! create mappings object from array + mappings => mappings_t( f_mappings ) + ASSERT_EQ( mappings%size(), 3 ) + ASSERT_EQ( mappings%index( 1 ), 22 ) + ASSERT_EQ( mappings%name( 1 ), "quux" ) + ASSERT_EQ( mappings%index( 2 ), 35 ) + ASSERT_EQ( mappings%name( 2 ), "corge" ) + ASSERT_EQ( mappings%index( 3 ), 56 ) + ASSERT_EQ( mappings%name( 3 ), "grault" ) + + ! find mappings by name + ASSERT_EQ( mappings%index( "quux", error ), 22 ) + ASSERT( error%is_success() ) + ASSERT_EQ( mappings%index( "corge", error ), 35 ) + ASSERT( error%is_success() ) + ASSERT_EQ( mappings%index( "grault", error ), 56 ) + ASSERT( error%is_success() ) + index = mappings%index( "foo", error ) + ASSERT( .not. error%is_success() ) + deallocate( mappings ) + end subroutine test_mapping_t +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + subroutine build_and_check_index_mapping_t(config) + + type(configuration_t), intent(inout) :: config + + type(mapping_t), pointer :: map + type(mapping_t), allocatable :: f_map(:) + type(mappings_t), pointer :: source_map, target_map + type(index_mappings_t), pointer :: index_mappings + type(error_t) :: error + real(dk), allocatable :: source_data(:), target_data(:) + + allocate( f_map( 2 ) ) + map => mapping_t( "Test", 2 ) + f_map( 1 ) = map + deallocate( map ) + map => mapping_t( "Test2", 5 ) + f_map( 2 ) = map + deallocate( map ) + source_map => mappings_t( f_map ) + map => mapping_t( "Test2", 3 ) + f_map( 1 ) = map + deallocate( map ) + map => mapping_t( "Test3", 1 ) + f_map( 2 ) = map + deallocate( map ) + target_map => mappings_t( f_map ) + deallocate( f_map ) + + index_mappings => index_mappings_t( config, source_map, target_map, error ) + ASSERT( error%is_success() ) + + source_data = (/ 1.0_dk, 2.0_dk, 3.0_dk, 4.0_dk, 5.0_dk /) + target_data = (/ 10.0_dk, 20.0_dk, 30.0_dk, 40.0_dk /) + + call index_mappings%copy_data( source_data, target_data ) + ASSERT_EQ( target_data( 1 ), 5.0_dk * 0.82_dk ) + ASSERT_EQ( target_data( 2 ), 20.0_dk ) + ASSERT_EQ( target_data( 3 ), 2.0_dk ) + ASSERT_EQ( target_data( 4 ), 40.0_dk ) + deallocate( index_mappings ) + deallocate( source_map ) + deallocate( target_map ) + + end subroutine build_and_check_index_mapping_t + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Tests the index_mapping_t type + subroutine test_index_mapping_t() + + type(configuration_t) :: config + type(error_t) :: error + + call config%load_from_string( & + "- source: Test"//new_line('a')// & + " target: Test2"//new_line('a')// & + "- source: Test2"//new_line('a')// & + " target: Test3"//new_line('a')// & + " scale factor: 0.82"//new_line('a'), error ) + ASSERT( error%is_success() ) + call build_and_check_index_mapping_t( config ) + + call config%load_from_file( "test/data/util_index_mapping_from_file.json", & + error ) + ASSERT( error%is_success() ) + call build_and_check_index_mapping_t( config ) + + end subroutine test_index_mapping_t + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end program test_util \ No newline at end of file diff --git a/fortran/util.F90 b/fortran/util.F90 index 38694a8c..780a3d2c 100644 --- a/fortran/util.F90 +++ b/fortran/util.F90 @@ -9,7 +9,8 @@ module musica_util implicit none private - public :: string_t_c, string_t, error_t_c, error_t, mapping_t_c, mapping_t, & + public :: string_t_c, string_t, error_t_c, error_t, configuration_t, & + mapping_t_c, mapping_t, mappings_t_c, mappings_t, index_mappings_t, & to_c_string, to_f_string, assert, copy_mappings, delete_string_c, & create_string_c, musica_rk, musica_dk, find_mapping_index @@ -27,16 +28,14 @@ module musica_util !> String type type :: string_t - private character(len=:), allocatable :: value_ contains procedure :: get_char_array => get_char_array_string_t procedure, private, pass(from) :: char_assign_string procedure, private, pass(to) :: string_assign_char - procedure, private, pass(to) :: string_assign_string procedure, private, pass(to) :: string_assign_string_t_c generic :: assignment(=) => char_assign_string, string_assign_char, & - string_assign_string, string_assign_string_t_c + string_assign_string_t_c end type string_t interface string_t @@ -68,6 +67,20 @@ module musica_util module procedure error_t_constructor end interface error_t + !> Wrapper for a c configuration + type, bind(c) :: configuration_t_c + type(c_ptr) :: data_ = c_null_ptr + end type configuration_t_c + + !> Configuration type + type :: configuration_t + type(configuration_t_c) :: configuration_c_ + contains + procedure :: load_from_string => configuration_load_from_string + procedure :: load_from_file => configuration_load_from_file + final :: configuration_finalize + end type configuration_t + !> Wrapper for a c name-to-index mapping !! !! Index is 0-based for use in C @@ -81,29 +94,135 @@ module musica_util !! Indexing is 1-based for use in Fortran type :: mapping_t private - type(string_t) :: name_ - integer :: index_ = 1 + type(mapping_t_c) :: mapping_c_ contains procedure :: mapping_assign_mapping_t_c - generic :: assignment(=) => mapping_assign_mapping_t_c + procedure :: mapping_assign_mapping_t + generic :: assignment(=) => mapping_assign_mapping_t_c, & + mapping_assign_mapping_t procedure :: name => mapping_name procedure :: index => mapping_index + final :: mapping_finalize end type mapping_t interface mapping_t module procedure mapping_constructor + module procedure mapping_constructor_from_data end interface mapping_t + !> Wrapper for a c array of name-to-index mappings + type, bind(c) :: mappings_t_c + type(c_ptr) :: mappings_ = c_null_ptr + integer(c_size_t) :: size_ = 0_c_size_t + end type mappings_t_c + + !> Array of name-to-index mappings + type :: mappings_t + private + type(mappings_t_c) :: mappings_c_ + contains + procedure :: name => mappings_name + procedure, private :: mappings_index + procedure, private :: mappings_index_by_name + generic :: index => mappings_index, mappings_index_by_name + procedure :: size => mappings_size + final :: mappings_finalize + end type mappings_t + + interface mappings_t + module procedure mappings_constructor_from_mappings_t_c + module procedure mappings_constructor_from_mapping_t_array + end interface mappings_t + + !> Wrapper for a c index-to-index mapping array + type, bind(c) :: index_mappings_t_c + type(c_ptr) :: mappings_ = c_null_ptr + integer(c_size_t) :: size_ = 0_c_size_t + end type index_mappings_t_c + + !> Array of index-to-index mappings + type :: index_mappings_t + private + type(index_mappings_t_c) :: mappings_c_ + contains + procedure :: copy_data + final :: index_mappings_finalize + end type index_mappings_t + + interface index_mappings_t + module procedure index_mappings_constructor + end interface index_mappings_t + interface function create_string_c( string ) bind(c, name="CreateString") import :: string_t_c, c_char character(kind=c_char, len=1), intent(in) :: string(*) type(string_t_c) :: create_string_c end function create_string_c - subroutine delete_string_c( string ) bind(c, name="DeleteString") + pure subroutine delete_string_c( string ) bind(c, name="DeleteString") import :: string_t_c type(string_t_c), intent(inout) :: string end subroutine delete_string_c + function load_configuration_from_string_c( string, error ) & + bind(c, name="LoadConfigurationFromString") + import :: configuration_t_c, c_char, error_t_c + character(kind=c_char, len=1), intent(in) :: string(*) + type(error_t_c), intent(inout) :: error + type(configuration_t_c) :: load_configuration_from_string_c + end function load_configuration_from_string_c + function load_configuration_from_file_c( file, error ) & + bind(c, name="LoadConfigurationFromFile") + import :: configuration_t_c, c_char, error_t_c + character(kind=c_char, len=1), intent(in) :: file(*) + type(error_t_c), intent(inout) :: error + type(configuration_t_c) :: load_configuration_from_file_c + end function load_configuration_from_file_c + pure subroutine delete_configuration_c( configuration ) & + bind(c, name="DeleteConfiguration") + import :: configuration_t_c + type(configuration_t_c), intent(inout) :: configuration + end subroutine delete_configuration_c + function create_mappings_c( size ) bind(c, name="CreateMappings") + import :: mappings_t_c, c_size_t + integer(c_size_t), value, intent(in) :: size + type(mappings_t_c) :: create_mappings_c + end function create_mappings_c + function create_index_mappings_c(configuration, source, target, error) & + bind(c, name="CreateIndexMappings") + import :: index_mappings_t_c, configuration_t_c, error_t_c, mappings_t_c + type(configuration_t_c), value, intent(in) :: configuration + type(mappings_t_c), value, intent(in) :: source + type(mappings_t_c), value, intent(in) :: target + type(error_t_c), intent(inout) :: error + type(index_mappings_t_c) :: create_index_mappings_c + end function create_index_mappings_c + pure subroutine delete_mapping_c( mapping ) bind(c, name="DeleteMapping") + import :: mapping_t_c + type(mapping_t_c), intent(inout) :: mapping + end subroutine delete_mapping_c + function find_mapping_index_c( mappings, name, error ) result( index ) & + bind(c, name="FindMappingIndex") + import :: mappings_t_c, error_t_c, c_char, c_size_t + type(mappings_t_c), value, intent(in) :: mappings + character(kind=c_char, len=1), intent(in) :: name(*) + type(error_t_c), intent(inout) :: error + integer(c_size_t) :: index + end function find_mapping_index_c + pure subroutine delete_mappings_c( mappings ) bind(c, name="DeleteMappings") + import :: mappings_t_c + type(mappings_t_c), intent(inout) :: mappings + end subroutine delete_mappings_c + pure subroutine delete_index_mappings_c( mappings ) & + bind(c, name="DeleteIndexMappings") + import :: index_mappings_t_c + type(index_mappings_t_c), intent(inout) :: mappings + end subroutine delete_index_mappings_c + subroutine copy_data_c(mappings, source, target) bind(c, name="CopyData") + import :: index_mappings_t_c, c_ptr + type(index_mappings_t_c), value, intent(in) :: mappings + type(c_ptr), value, intent(in) :: source + type(c_ptr), value, intent(in) :: target + end subroutine copy_data_c end interface contains @@ -111,11 +230,16 @@ end subroutine delete_string_c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! function get_char_array_string_t( this ) result ( value ) + class(string_t), intent(in) :: this character(len=:), allocatable :: value + allocate( value, source = this%value_ ) + end function get_char_array_string_t +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + function string_t_constructor_from_string_t_c( c_string ) result( new_string ) type(string_t_c), intent(inout) :: c_string @@ -158,28 +282,74 @@ end subroutine string_assign_char !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !> Copy a string - subroutine string_assign_string( to, from ) + !> Copy a c string to a string_t + subroutine string_assign_string_t_c( to, from ) class(string_t), intent(inout) :: to - class(string_t), intent(in) :: from + type(string_t_c), intent(in) :: from - if (allocated(to%value_)) deallocate(to%value_) - allocate( to%value_, source = from%value_ ) + to%value_ = to_f_string( from ) - end subroutine string_assign_string + end subroutine string_assign_string_t_c !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !> Copy a c string to a string_t - subroutine string_assign_string_t_c( to, from ) + !> Load a configuration from a string + subroutine configuration_load_from_string( this, string, error ) - class(string_t), intent(inout) :: to - type(string_t_c), intent(in) :: from + use iso_c_binding, only: c_associated - to%value_ = to_f_string( from ) + class(configuration_t), intent(inout) :: this + character(len=*), intent(in) :: string + type(error_t), intent(out) :: error - end subroutine string_assign_string_t_c + type(error_t_c) :: error_c + + if (c_associated(this%configuration_c_%data_)) then + call delete_configuration_c(this%configuration_c_) + end if + this%configuration_c_ = & + load_configuration_from_string_c( to_c_string( string ), error_c ) + error = error_t( error_c ) + + end subroutine configuration_load_from_string + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Load a configuration from a file + subroutine configuration_load_from_file( this, file, error ) + + use iso_c_binding, only: c_associated + + class(configuration_t), intent(inout) :: this + character(len=*), intent(in) :: file + type(error_t), intent(out) :: error + + type(error_t_c) :: error_c + + if (c_associated(this%configuration_c_%data_)) then + call delete_configuration_c(this%configuration_c_) + end if + this%configuration_c_ = & + load_configuration_from_file_c( to_c_string( file ), error_c ) + error = error_t( error_c ) + + end subroutine configuration_load_from_file + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Finalize a configuration + elemental subroutine configuration_finalize( this ) + + use iso_c_binding, only: c_associated + + type(configuration_t), intent(inout) :: this + + if (c_associated(this%configuration_c_%data_)) then + call delete_configuration_c(this%configuration_c_) + end if + + end subroutine configuration_finalize !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -192,6 +362,11 @@ function error_t_constructor( c_error ) result( new_error ) new_error%code_ = int( c_error%code_ ) new_error%category_ = string_t( c_error%category_ ) new_error%message_ = string_t( c_error%message_ ) + c_error%category_%ptr_ = c_null_ptr + c_error%category_%size_ = 0_c_size_t + c_error%message_%ptr_ = c_null_ptr + c_error%message_%size_ = 0_c_size_t + c_error%code_ = 0_c_int end function error_t_constructor @@ -266,13 +441,32 @@ end function error_t_is_error function mapping_constructor( c_mapping ) result( new_mapping ) type(mapping_t_c), intent(inout) :: c_mapping - type(mapping_t) :: new_mapping + type(mapping_t), pointer :: new_mapping - new_mapping%name_ = string_t( c_mapping%name_ ) - new_mapping%index_ = int( c_mapping%index_ ) + 1 + allocate( new_mapping ) + new_mapping%mapping_c_%name_%ptr_ = c_mapping%name_%ptr_ + new_mapping%mapping_c_%name_%size_ = c_mapping%name_%size_ + c_mapping%name_%ptr_ = c_null_ptr + c_mapping%name_%size_ = 0_c_size_t + new_mapping%mapping_c_%index_ = c_mapping%index_ end function mapping_constructor +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Constructor for a mapping_t object from a name and index + function mapping_constructor_from_data( name, index ) result( new_mapping ) + + character(len=*), intent(in) :: name + integer, intent(in) :: index + type(mapping_t), pointer :: new_mapping + + allocate( new_mapping ) + new_mapping%mapping_c_%name_ = create_string_c( to_c_string( name ) ) + new_mapping%mapping_c_%index_ = int( index - 1, c_size_t ) + + end function mapping_constructor_from_data + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !> Copies a mapping_t_c object to a mapping_t object @@ -281,11 +475,33 @@ subroutine mapping_assign_mapping_t_c( to, from ) class(mapping_t), intent(inout) :: to type(mapping_t_c), intent(in) :: from - to%name_ = from%name_ - to%index_ = int( from%index_ ) + 1 + character(kind=c_char, len=1), pointer :: name(:) + + call delete_string_c( to%mapping_c_%name_ ) + call c_f_pointer( from%name_%ptr_, name, [ from%name_%size_ + 1 ] ) + to%mapping_c_%name_ = create_string_c( name ) + to%mapping_c_%index_ = from%index_ end subroutine mapping_assign_mapping_t_c +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Copies a mapping_t object to a mapping_t object + subroutine mapping_assign_mapping_t( to, from ) + + class(mapping_t), intent(inout) :: to + class(mapping_t), intent(in) :: from + + character(kind=c_char, len=1), pointer :: name(:) + + call delete_mapping_c( to%mapping_c_ ) + call c_f_pointer( from%mapping_c_%name_%ptr_, name, & + [ from%mapping_c_%name_%size_ + 1 ] ) + to%mapping_c_%name_ = create_string_c( name ) + to%mapping_c_%index_ = from%mapping_c_%index_ + + end subroutine mapping_assign_mapping_t + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !> Get the name for a mapping_t object @@ -294,7 +510,7 @@ function mapping_name( this ) result( name ) class(mapping_t), intent(in) :: this character(len=:), allocatable :: name - name = this%name_ + name = to_f_string( this%mapping_c_%name_ ) end function mapping_name @@ -306,29 +522,41 @@ function mapping_index( this ) result( index ) class(mapping_t), intent(in) :: this integer :: index - index = this%index_ + index = int( this%mapping_c_%index_ ) + 1 end function mapping_index !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !> Copies mappings from a c array to a fortran array - function copy_mappings( c_mappings, n_mappings ) result( f_mappings ) + subroutine copy_mappings( c_mappings, n_mappings, f_mappings ) - type(c_ptr), intent(in) :: c_mappings - integer(c_size_t), intent(in) :: n_mappings - type(mapping_t), allocatable :: f_mappings(:) + type(c_ptr), intent(in) :: c_mappings + integer(c_size_t), intent(in) :: n_mappings + type(mapping_t), allocatable, intent(inout) :: f_mappings(:) integer :: i type(mapping_t_c), pointer :: mappings(:) call c_f_pointer( c_mappings, mappings, [ n_mappings ] ) + if ( allocated( f_mappings ) ) deallocate( f_mappings ) allocate( f_mappings( n_mappings ) ) do i = 1, n_mappings f_mappings(i) = mappings(i) end do - end function copy_mappings + end subroutine copy_mappings + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Free memory for a mapping_t object + elemental subroutine mapping_finalize( this ) + + type(mapping_t), intent(inout) :: this + + call delete_mapping_c( this%mapping_c_ ) + + end subroutine mapping_finalize !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -354,6 +582,171 @@ function find_mapping_index( mappings, name, found ) result( index ) end function find_mapping_index +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Constructor for a mappings_t object from a mappings_t_c object + !! that takes ownership of the mappings_t_c object + function mappings_constructor_from_mappings_t_c( c_mappings ) & + result( mappings ) + + type(mappings_t_c), intent(in) :: c_mappings + type(mappings_t), pointer :: mappings + + allocate( mappings ) + mappings%mappings_c_ = c_mappings + + end function mappings_constructor_from_mappings_t_c + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Constructor for a mappings_t object from an array of mapping_t objects + !! that copies the mapping_t objects + function mappings_constructor_from_mapping_t_array( mappings ) & + result( new_mappings ) + + type(mapping_t), intent(in) :: mappings(:) + type(mappings_t), pointer :: new_mappings + + integer :: i + type(mapping_t_c), pointer :: mappings_c(:) + + allocate( new_mappings ) + new_mappings%mappings_c_ = & + create_mappings_c( int( size( mappings ), c_size_t ) ) + call c_f_pointer( new_mappings%mappings_c_%mappings_, mappings_c, & + [ new_mappings%mappings_c_%size_ ] ) + do i = 1, size( mappings ) + mappings_c(i)%name_ = & + create_string_c( to_c_string( mappings(i)%name() ) ) + mappings_c(i)%index_ = mappings(i)%index() - 1 + end do + + end function mappings_constructor_from_mapping_t_array + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Get the name for a mappings_t array element + function mappings_name( this, index ) result( name ) + + class(mappings_t), intent(in) :: this + integer, intent(in) :: index + character(len=:), allocatable :: name + + type(mapping_t_c), pointer :: mappings(:) + + call c_f_pointer( this%mappings_c_%mappings_, mappings, & + [ this%mappings_c_%size_ ] ) + name = to_f_string( mappings(index)%name_ ) + + end function mappings_name + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Get the index for a mappings_t array element + function mappings_index( this, map_index ) result( index ) + + class(mappings_t), intent(in) :: this + integer, intent(in) :: map_index + integer :: index + + type(mapping_t_c), pointer :: mappings(:) + + call c_f_pointer( this%mappings_c_%mappings_, mappings, & + [ this%mappings_c_%size_ ] ) + index = int( mappings(map_index)%index_ ) + 1 + + end function mappings_index + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Get the index for a mappings_t array element by name + function mappings_index_by_name( this, name, error ) result( index ) + + class(mappings_t), intent(in) :: this + character(len=*), intent(in) :: name + type(error_t), intent(out) :: error + integer :: index + + character(kind=c_char, len=1), allocatable :: name_c(:) + type(error_t_c) :: error_c + + name_c = to_c_string( name ) + index = find_mapping_index_c( this%mappings_c_, name_c, error_c ) + 1 + error = error_t( error_c ) + + end function mappings_index_by_name + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Get the size of a mappings_t object + function mappings_size( this ) result( size ) + + class(mappings_t), intent(in) :: this + integer :: size + + size = int( this%mappings_c_%size_ ) + + end function mappings_size + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Finalize a mappings_t object + elemental subroutine mappings_finalize( this ) + + type(mappings_t), intent(inout) :: this + + call delete_mappings_c( this%mappings_c_ ) + + end subroutine mappings_finalize + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Constructor for an index_mappings_t object + function index_mappings_constructor( configuration, source, target, error ) & + result( mappings ) + + type(configuration_t), intent(in) :: configuration + type(mappings_t), intent(in) :: source + type(mappings_t), intent(in) :: target + type(error_t), intent(out) :: error + type(index_mappings_t), pointer :: mappings + + type(error_t_c) :: error_c + + allocate( mappings ) + mappings%mappings_c_ = create_index_mappings_c( & + configuration%configuration_c_, source%mappings_c_, & + target%mappings_c_, error_c ) + error = error_t( error_c ) + + end function index_mappings_constructor + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Copy data from a source to a target array using index mappings + subroutine copy_data( this, source, target ) + + use iso_c_binding, only: c_loc + + class(index_mappings_t), intent(inout) :: this + real(kind=musica_dk), target, intent(in) :: source(:) + real(kind=musica_dk), target, intent(in) :: target(:) + + call copy_data_c( this%mappings_c_, c_loc( source ), c_loc( target ) ) + + end subroutine copy_data + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Finalize an index_mappings_t object + elemental subroutine index_mappings_finalize( this ) + + type(index_mappings_t), intent(inout) :: this + + call delete_index_mappings_c( this%mappings_c_ ) + + end subroutine index_mappings_finalize + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !> Convert a fortran character array to a c string diff --git a/include/musica/micm.hpp b/include/musica/micm.hpp index a059c38a..d71c2524 100644 --- a/include/musica/micm.hpp +++ b/include/musica/micm.hpp @@ -144,17 +144,15 @@ namespace musica /// @brief Get the ordering of species /// @param micm Pointer to MICM object - /// @param array_size Size of the array /// @param error Error struct to indicate success or failure /// @return Array of species' name-index pairs - Mapping *GetSpeciesOrdering(MICM *micm, std::size_t *array_size, Error *error); + Mappings GetSpeciesOrdering(MICM *micm, Error *error); /// @brief Get the ordering of user-defined reaction rates /// @param micm Pointer to MICM object - /// @param array_size Size of the array /// @param error Error struct to indicate success or failure /// @return Array of reaction rate name-index pairs - Mapping *GetUserDefinedReactionRatesOrdering(MICM *micm, std::size_t *array_size, Error *error); + Mappings GetUserDefinedReactionRatesOrdering(MICM *micm, Error *error); /// @brief Get a property for a chemical species /// @param micm Pointer to MICM object diff --git a/include/musica/util.hpp b/include/musica/util.hpp index f681d475..0d20fcd3 100644 --- a/include/musica/util.hpp +++ b/include/musica/util.hpp @@ -8,15 +8,18 @@ #define MUSICA_ERROR_CODE_SPECIES_NOT_FOUND 1 #define MUSICA_ERROR_CODE_SOLVER_TYPE_NOT_FOUND 2 #define MUSICA_ERROR_CODE_MAPPING_NOT_FOUND 3 +#define MUSICA_ERROR_CODE_PARSING_FAILED 4 #ifdef __cplusplus #include + #include namespace musica { extern "C" { + typedef YAML::Node Yaml; #endif /// @brief A struct to represent a string @@ -29,11 +32,17 @@ namespace musica /// @brief A struct to describe failure conditions struct Error { - int code_; + int code_ = 0; String category_; String message_; }; + /// @brief A set of configuration data + struct Configuration + { + Yaml* data_; + }; + /// @brief A struct to represent a mapping between a string and an index struct Mapping { @@ -41,6 +50,28 @@ namespace musica std::size_t index_; }; + /// @brief A struct to represent an array of Mappings + struct Mappings + { + Mapping* mappings_ = nullptr; + std::size_t size_ = 0; + }; + + /// @brief A struct to represent the mapping between indices in two arrays + struct IndexMapping + { + std::size_t source_; + std::size_t target_; + double scale_factor_ = 1.0; // Scaling factor applied to the source data + }; + + /// @brief A struct to represent an array of IndexMappings + struct IndexMappings + { + IndexMapping* mappings_ = nullptr; + std::size_t size_ = 0; + }; + /// @brief Casts a char* to a String /// @param value The char* to cast /// @return The casted String @@ -61,26 +92,67 @@ namespace musica /// @return The Error Error ToError(const char* category, int code, const char* message); + /// @brief Loads a set of configuration data from a string + /// @param data The string to load + /// @param error The Error to populate if the data cannot be loaded + /// @return The Configuration + Configuration LoadConfigurationFromString(const char* data, Error* error); + + /// @brief Loads a set of configuration data from a file + /// @param filename The file to load + /// @param error The Error to populate if the data cannot be loaded + /// @return The Configuration + Configuration LoadConfigurationFromFile(const char* filename, Error* error); + + /// @brief Allocate a new Mappings struct + /// @param size The size of the Mappings + /// @return The Mappings + Mappings CreateMappings(std::size_t size); + /// @brief Finds the index of a Mapping by name /// @param mappings The array of Mappings - /// @param size The size of the array /// @param name The name to search for /// @param error The Error to populate if the Mapping is not found /// @return The index of the Mapping - std::size_t FindMappingIndex(const Mapping* mappings, std::size_t size, const char* name, Error* error); + std::size_t FindMappingIndex(const Mappings mappings, const char* name, Error* error); + + /// @brief Creates a set of index mappings + /// @param configuration The Configuration containing the mappings + /// @param source The source array of name-index Mappings + /// @param target The target array of name-index Mappings + /// @param error The Error to populate if a Mapping is not found + /// @return The array of IndexMappings + IndexMappings CreateIndexMappings(const Configuration configuration, const Mappings source, const Mappings target, Error* error); + + /// @brief Copies data from one array to another using IndexMappings + /// @param mappings The array of IndexMappings + /// @param source The source array + /// @param target The target array + void CopyData(const IndexMappings mappings, const double* source, double* target); /// @brief Deletes an Error /// @param error The Error to delete void DeleteError(Error* error); + /// @brief Deletes a Configuration + /// @param config The Configuration to delete + void DeleteConfiguration(Configuration* config); + /// @brief Deletes a Mapping /// @param mapping The Mapping to delete void DeleteMapping(Mapping* mapping); /// @brief Deletes an array of Mappings /// @param mappings The array of Mappings to delete - /// @param size The size of the array - void DeleteMappings(Mapping* mappings, std::size_t size); + void DeleteMappings(Mappings* mappings); + + /// @brief Deletes an IndexMapping + /// @param mapping The IndexMapping to delete + void DeleteIndexMapping(IndexMapping* mapping); + + /// @brief Deletes an array of IndexMappings + /// @param mappings The array of IndexMappings to delete + void DeleteIndexMappings(IndexMappings* mappings); #ifdef __cplusplus } diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 43c056ad..56052ef3 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -6,7 +6,7 @@ pybind11_add_module(musica_python ${CMAKE_BINARY_DIR}/version.cpp ) -target_link_libraries(musica_python PRIVATE nlohmann_json::nlohmann_json) +target_link_libraries(musica_python PRIVATE nlohmann_json::nlohmann_json yaml-cpp) target_compile_features(musica_python PUBLIC cxx_std_20) @@ -24,7 +24,18 @@ target_include_directories(musica_python $ ) -install(TARGETS musica_python +# Set the rpath for the shared library +if(APPLE) + message(STATUS "Building for MacOS") +elseif(UNIX) + message(STATUS "Building for Linux") + set_target_properties(musica_python PROPERTIES + INSTALL_RPATH "$ORIGIN" + BUILD_WITH_INSTALL_RPATH TRUE + ) +endif() + +install(TARGETS musica_python yaml-cpp LIBRARY DESTINATION . ) @@ -39,6 +50,3 @@ if(WIN32) else() set(PYTHON_MODULE_PATH "${CMAKE_CURRENT_BINARY_DIR}") endif() - - -add_subdirectory(test) \ No newline at end of file diff --git a/python/test/CMakeLists.txt b/python/test/CMakeLists.txt deleted file mode 100644 index ac4d8e9c..00000000 --- a/python/test/CMakeLists.txt +++ /dev/null @@ -1,9 +0,0 @@ -add_test(NAME musica_python_chapman_test - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${PYTHON_MODULE_PATH} - ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_chapman.py) - -add_test(NAME musica_python_analytical_test - WORKING_DIRECTORY ${CMAKE_BINARY_DIR} - COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${PYTHON_MODULE_PATH} - ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test_analytical.py) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 54a5f60f..fafde227 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -44,6 +44,11 @@ target_sources(musica ${CMAKE_BINARY_DIR}/version.cpp ) +target_link_libraries(musica + PRIVATE + yaml-cpp +) + target_compile_definitions(musica PUBLIC ${musica_compile_definitions}) set_target_properties(musica PROPERTIES diff --git a/src/micm/micm.cpp b/src/micm/micm.cpp index be240501..dbeac193 100644 --- a/src/micm/micm.cpp +++ b/src/micm/micm.cpp @@ -158,7 +158,7 @@ namespace musica return CreateString(micm::GetMicmVersion()); } - Mapping *GetSpeciesOrdering(MICM *micm, std::size_t *array_size, Error *error) + Mappings GetSpeciesOrdering(MICM *micm, Error *error) { DeleteError(error); @@ -180,27 +180,31 @@ namespace musica { map = micm->GetSpeciesOrdering(micm->backward_euler_standard_, error); } + else + { + std::string msg = "Solver type '" + std::to_string(micm->solver_type_) + "' not found"; + *error = ToError(MUSICA_ERROR_CATEGORY, MUSICA_ERROR_CODE_SOLVER_TYPE_NOT_FOUND, msg.c_str()); + } if (!IsSuccess(*error)) { - return nullptr; + return Mappings(); } - Mapping *species_ordering = new Mapping[map.size()]; + Mappings species_ordering; + species_ordering.mappings_ = new Mapping[map.size()]; + species_ordering.size_ = map.size(); // Copy data from the map to the array of structs std::size_t i = 0; for (const auto &entry : map) { - species_ordering[i] = ToMapping(entry.first.c_str(), entry.second); + species_ordering.mappings_[i] = ToMapping(entry.first.c_str(), entry.second); ++i; } - - // Set the size of the array - *array_size = map.size(); return species_ordering; } - Mapping *GetUserDefinedReactionRatesOrdering(MICM *micm, std::size_t *array_size, Error *error) + Mappings GetUserDefinedReactionRatesOrdering(MICM *micm, Error *error) { DeleteError(error); @@ -222,24 +226,28 @@ namespace musica { map = micm->GetUserDefinedReactionRatesOrdering(micm->backward_euler_standard_, error); } + else + { + std::string msg = "Solver type '" + std::to_string(micm->solver_type_) + "' not found"; + *error = ToError(MUSICA_ERROR_CATEGORY, MUSICA_ERROR_CODE_SOLVER_TYPE_NOT_FOUND, msg.c_str()); + } if (!IsSuccess(*error)) { - return nullptr; + return Mappings(); } - Mapping *reactionRates = new Mapping[map.size()]; + Mappings reaction_rates; + reaction_rates.mappings_ = new Mapping[map.size()]; + reaction_rates.size_ = map.size(); // Copy data from the map to the array of structs std::size_t i = 0; for (const auto &entry : map) { - reactionRates[i] = ToMapping(entry.first.c_str(), entry.second); + reaction_rates.mappings_[i] = ToMapping(entry.first.c_str(), entry.second); ++i; } - - // Set the size of the array - *array_size = map.size(); - return reactionRates; + return reaction_rates; } String GetSpeciesPropertyString(MICM *micm, const char *species_name, const char *property_name, Error *error) @@ -281,6 +289,7 @@ namespace musica void MICM::CreateRosenbrock(const std::string &config_path, Error *error) { + DeleteError(error); try { micm::SolverConfig<> solver_config; @@ -299,18 +308,17 @@ namespace musica .SetIgnoreUnusedSpecies(true) .Build()); - DeleteError(error); *error = NoError(); } catch (const std::system_error &e) { - DeleteError(error); *error = ToError(e); } } void MICM::CreateRosenbrockStandardOrder(const std::string &config_path, Error *error) { + DeleteError(error); try { micm::SolverConfig<> solver_config; @@ -386,12 +394,10 @@ namespace musica .SetIgnoreUnusedSpecies(true) .Build()); - DeleteError(error); *error = NoError(); } catch (const std::system_error &e) { - DeleteError(error); *error = ToError(e); } } diff --git a/src/packaging/CMakeLists.txt b/src/packaging/CMakeLists.txt index 9d04933f..a44fcf40 100644 --- a/src/packaging/CMakeLists.txt +++ b/src/packaging/CMakeLists.txt @@ -3,6 +3,7 @@ include(CMakePackageConfigHelpers) install( TARGETS musica + yaml-cpp EXPORT musica_Exports LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -66,7 +67,6 @@ if (MUSICA_ENABLE_TUVX) install( TARGETS tuvx_object - yaml-cpp EXPORT musica_Exports ) diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 275e84d6..02c013a1 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -7,4 +7,6 @@ add_subdirectory(unit) # Copy test data add_custom_target(copy_unit_test_configs ALL ${CMAKE_COMMAND} -E copy_directory - ${MUSICA_PROJECT_SRC_DIR}/configs ${CMAKE_BINARY_DIR}/configs) \ No newline at end of file + ${MUSICA_PROJECT_SRC_DIR}/configs ${CMAKE_BINARY_DIR}/configs) +add_custom_target(copy_unit_test_data ALL ${CMAKE_COMMAND} -E copy_directory + ${MUSICA_PROJECT_SRC_DIR}/src/test/data ${CMAKE_BINARY_DIR}/test/data) \ No newline at end of file diff --git a/src/test/data/util_index_mapping_from_file.json b/src/test/data/util_index_mapping_from_file.json new file mode 100644 index 00000000..2ab5a3a7 --- /dev/null +++ b/src/test/data/util_index_mapping_from_file.json @@ -0,0 +1,11 @@ +[ + { + "source": "Test", + "target": "Test2" + }, + { + "source": "Test2", + "target": "Test3", + "scale factor": 0.82 + } +] \ No newline at end of file diff --git a/src/test/unit/micm/micm_c_api.cpp b/src/test/unit/micm/micm_c_api.cpp index 776f408d..2cc54b07 100644 --- a/src/test/unit/micm/micm_c_api.cpp +++ b/src/test/unit/micm/micm_c_api.cpp @@ -94,15 +94,14 @@ TEST_F(MicmCApiTest, CreateMicmInstance) TEST_F(MicmCApiTest, GetSpeciesOrdering) { Error error; - std::size_t array_size; - Mapping* species_ordering = GetSpeciesOrdering(micm, &array_size, &error); + Mappings species_ordering = GetSpeciesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); + ASSERT_EQ(species_ordering.size_, 4); DeleteError(&error); - ASSERT_EQ(array_size, 4); bool found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < species_ordering.size_; i++) { - if (strcmp(species_ordering[i].name_.value_, "O3") == 0) + if (strcmp(species_ordering.mappings_[i].name_.value_, "O3") == 0) { found = true; break; @@ -110,9 +109,9 @@ TEST_F(MicmCApiTest, GetSpeciesOrdering) } ASSERT_TRUE(found); found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < species_ordering.size_; i++) { - if (strcmp(species_ordering[i].name_.value_, "O") == 0) + if (strcmp(species_ordering.mappings_[i].name_.value_, "O") == 0) { found = true; break; @@ -120,9 +119,9 @@ TEST_F(MicmCApiTest, GetSpeciesOrdering) } ASSERT_TRUE(found); found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < species_ordering.size_; i++) { - if (strcmp(species_ordering[i].name_.value_, "O2") == 0) + if (strcmp(species_ordering.mappings_[i].name_.value_, "O2") == 0) { found = true; break; @@ -130,31 +129,30 @@ TEST_F(MicmCApiTest, GetSpeciesOrdering) } ASSERT_TRUE(found); found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < species_ordering.size_; i++) { - if (strcmp(species_ordering[i].name_.value_, "O1D") == 0) + if (strcmp(species_ordering.mappings_[i].name_.value_, "O1D") == 0) { found = true; break; } } ASSERT_TRUE(found); - DeleteMappings(species_ordering, array_size); + DeleteMappings(&species_ordering); } // Test case for getting user-defined reaction rates ordering TEST_F(MicmCApiTest, GetUserDefinedReactionRatesOrdering) { Error error; - std::size_t array_size; - Mapping* reaction_rates_ordering = GetUserDefinedReactionRatesOrdering(micm, &array_size, &error); + Mappings reaction_rates_ordering = GetUserDefinedReactionRatesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); DeleteError(&error); - ASSERT_EQ(array_size, 3); + ASSERT_EQ(reaction_rates_ordering.size_, 3); bool found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < reaction_rates_ordering.size_; i++) { - if (strcmp(reaction_rates_ordering[i].name_.value_, "PHOTO.jO2") == 0) + if (strcmp(reaction_rates_ordering.mappings_[i].name_.value_, "PHOTO.jO2") == 0) { found = true; break; @@ -162,9 +160,9 @@ TEST_F(MicmCApiTest, GetUserDefinedReactionRatesOrdering) } ASSERT_TRUE(found); found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < reaction_rates_ordering.size_; i++) { - if (strcmp(reaction_rates_ordering[i].name_.value_, "PHOTO.jO3->O") == 0) + if (strcmp(reaction_rates_ordering.mappings_[i].name_.value_, "PHOTO.jO3->O") == 0) { found = true; break; @@ -172,16 +170,16 @@ TEST_F(MicmCApiTest, GetUserDefinedReactionRatesOrdering) } ASSERT_TRUE(found); found = false; - for (std::size_t i = 0; i < array_size; i++) + for (std::size_t i = 0; i < reaction_rates_ordering.size_; i++) { - if (strcmp(reaction_rates_ordering[i].name_.value_, "PHOTO.jO3->O1D") == 0) + if (strcmp(reaction_rates_ordering.mappings_[i].name_.value_, "PHOTO.jO3->O1D") == 0) { found = true; break; } } ASSERT_TRUE(found); - DeleteMappings(reaction_rates_ordering, array_size); + DeleteMappings(&reaction_rates_ordering); } void TestSingleGridCell(MICM* micm) @@ -195,36 +193,35 @@ void TestSingleGridCell(MICM* micm) double concentrations[num_concentrations]; const std::size_t num_user_defined_reaction_rates = 3; double user_defined_reaction_rates[num_user_defined_reaction_rates]; - std::size_t temp_size; Error error; // Get species ordering - Mapping* species_ordering = GetSpeciesOrdering(micm, &temp_size, &error); + Mappings species_ordering = GetSpeciesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); - ASSERT_EQ(temp_size, num_concentrations); - std::size_t O2_index = FindMappingIndex(species_ordering, num_concentrations, "O2", &error); + ASSERT_EQ(species_ordering.size_, num_concentrations); + std::size_t O2_index = FindMappingIndex(species_ordering, "O2", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t O_index = FindMappingIndex(species_ordering, num_concentrations, "O", &error); + std::size_t O_index = FindMappingIndex(species_ordering, "O", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t O1D_index = FindMappingIndex(species_ordering, num_concentrations, "O1D", &error); + std::size_t O1D_index = FindMappingIndex(species_ordering, "O1D", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t O3_index = FindMappingIndex(species_ordering, num_concentrations, "O3", &error); + std::size_t O3_index = FindMappingIndex(species_ordering, "O3", &error); ASSERT_TRUE(IsSuccess(error)); - DeleteMappings(species_ordering, num_concentrations); + DeleteMappings(&species_ordering); // Get user-defined reaction rates ordering - Mapping* reaction_rates_ordering = GetUserDefinedReactionRatesOrdering(micm, &temp_size, &error); + Mappings reaction_rates_ordering = GetUserDefinedReactionRatesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); - ASSERT_EQ(temp_size, num_user_defined_reaction_rates); - std::size_t jO2_index = FindMappingIndex(reaction_rates_ordering, num_user_defined_reaction_rates, "PHOTO.jO2", &error); + ASSERT_EQ(reaction_rates_ordering.size_, num_user_defined_reaction_rates); + std::size_t jO2_index = FindMappingIndex(reaction_rates_ordering, "PHOTO.jO2", &error); ASSERT_TRUE(IsSuccess(error)); std::size_t jO3_O_index = - FindMappingIndex(reaction_rates_ordering, num_user_defined_reaction_rates, "PHOTO.jO3->O", &error); + FindMappingIndex(reaction_rates_ordering, "PHOTO.jO3->O", &error); ASSERT_TRUE(IsSuccess(error)); std::size_t jO3_O1D_index = - FindMappingIndex(reaction_rates_ordering, num_user_defined_reaction_rates, "PHOTO.jO3->O1D", &error); + FindMappingIndex(reaction_rates_ordering, "PHOTO.jO3->O1D", &error); ASSERT_TRUE(IsSuccess(error)); - DeleteMappings(reaction_rates_ordering, num_user_defined_reaction_rates); + DeleteMappings(&reaction_rates_ordering); temperatures[0] = 272.5; pressures[0] = 101253.4; @@ -356,35 +353,34 @@ void TestMultipleGridCells(MICM* micm, const size_t num_grid_cells) double* user_defined_reaction_rates = new double[num_grid_cells * num_user_defined_reaction_rates]; Error error; - size_t temp_size; // Get species indices in concentration array - Mapping* species_ordering = GetSpeciesOrdering(micm, &temp_size, &error); + Mappings species_ordering = GetSpeciesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); - ASSERT_EQ(temp_size, num_concentrations); - std::size_t A_index = FindMappingIndex(species_ordering, num_concentrations, "A", &error); + ASSERT_EQ(species_ordering.size_, num_concentrations); + std::size_t A_index = FindMappingIndex(species_ordering, "A", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t B_index = FindMappingIndex(species_ordering, num_concentrations, "B", &error); + std::size_t B_index = FindMappingIndex(species_ordering, "B", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t C_index = FindMappingIndex(species_ordering, num_concentrations, "C", &error); + std::size_t C_index = FindMappingIndex(species_ordering, "C", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t D_index = FindMappingIndex(species_ordering, num_concentrations, "D", &error); + std::size_t D_index = FindMappingIndex(species_ordering, "D", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t E_index = FindMappingIndex(species_ordering, num_concentrations, "E", &error); + std::size_t E_index = FindMappingIndex(species_ordering, "E", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t F_index = FindMappingIndex(species_ordering, num_concentrations, "F", &error); + std::size_t F_index = FindMappingIndex(species_ordering, "F", &error); ASSERT_TRUE(IsSuccess(error)); - DeleteMappings(species_ordering, num_concentrations); + DeleteMappings(&species_ordering); // Get user-defined reaction rates indices in user-defined reaction rates array - Mapping* rate_ordering = GetUserDefinedReactionRatesOrdering(micm, &temp_size, &error); + Mappings rate_ordering = GetUserDefinedReactionRatesOrdering(micm, &error); ASSERT_TRUE(IsSuccess(error)); - ASSERT_EQ(temp_size, num_user_defined_reaction_rates); - std::size_t R1_index = FindMappingIndex(rate_ordering, num_user_defined_reaction_rates, "USER.reaction 1", &error); + ASSERT_EQ(rate_ordering.size_, num_user_defined_reaction_rates); + std::size_t R1_index = FindMappingIndex(rate_ordering, "USER.reaction 1", &error); ASSERT_TRUE(IsSuccess(error)); - std::size_t R2_index = FindMappingIndex(rate_ordering, num_user_defined_reaction_rates, "USER.reaction 2", &error); + std::size_t R2_index = FindMappingIndex(rate_ordering, "USER.reaction 2", &error); ASSERT_TRUE(IsSuccess(error)); - DeleteMappings(rate_ordering, num_user_defined_reaction_rates); + DeleteMappings(&rate_ordering); for (int i = 0; i < num_grid_cells; ++i) { diff --git a/src/test/unit/util.cpp b/src/test/unit/util.cpp index 96922268..cf2a25eb 100644 --- a/src/test/unit/util.cpp +++ b/src/test/unit/util.cpp @@ -73,16 +73,100 @@ TEST(Util, ToMapping) TEST(Util, FindMappingIndex) { - Mapping mappings[] = { ToMapping("Test", 1), ToMapping("Test2", 4), ToMapping("Test3", 9) }; + Mappings mappings; + Mapping mapping_array[] = { ToMapping("Test", 1), ToMapping("Test2", 4), ToMapping("Test3", 9) }; + mappings.mappings_ = mapping_array; + mappings.size_ = 3; Error error = NoError(); - EXPECT_EQ(FindMappingIndex(mappings, 3, "Test", &error), 1); + EXPECT_EQ(FindMappingIndex(mappings, "Test", &error), 1); EXPECT_TRUE(IsSuccess(error)); - EXPECT_EQ(FindMappingIndex(mappings, 3, "Test3", &error), 9); + EXPECT_EQ(FindMappingIndex(mappings, "Test3", &error), 9); EXPECT_TRUE(IsSuccess(error)); - EXPECT_EQ(FindMappingIndex(mappings, 3, "Test2", &error), 4); + EXPECT_EQ(FindMappingIndex(mappings, "Test2", &error), 4); EXPECT_TRUE(IsSuccess(error)); DeleteError(&error); - DeleteMapping(&(mappings[0])); - DeleteMapping(&(mappings[1])); - DeleteMapping(&(mappings[2])); + DeleteMapping(&(mapping_array[0])); + DeleteMapping(&(mapping_array[1])); + DeleteMapping(&(mapping_array[2])); } + +TEST(Util, IndexMappingFromString) +{ + Error error = NoError(); + Configuration config = LoadConfigurationFromString( + "- source: Test\n" + " target: Test2\n" + "- source: Test2\n" + " target: Test3\n" + " scale factor: 0.82\n", &error); + EXPECT_TRUE(IsSuccess(error)); + Mappings source_map; + Mappings target_map; + Mapping source_map_array[] = { ToMapping("Test", 1), ToMapping("Test2", 4) }; + Mapping target_map_array[] = { ToMapping("Test2", 2), ToMapping("Test3", 0) }; + source_map.mappings_ = source_map_array; + source_map.size_ = 2; + target_map.mappings_ = target_map_array; + target_map.size_ = 2; + IndexMappings index_mappings = CreateIndexMappings(config, source_map, target_map, &error); + EXPECT_TRUE(IsSuccess(error)); + EXPECT_EQ(index_mappings.mappings_[0].source_, 1); + EXPECT_EQ(index_mappings.mappings_[0].target_, 2); + EXPECT_EQ(index_mappings.mappings_[0].scale_factor_, 1.0); + EXPECT_EQ(index_mappings.mappings_[1].source_, 4); + EXPECT_EQ(index_mappings.mappings_[1].target_, 0); + EXPECT_EQ(index_mappings.mappings_[1].scale_factor_, 0.82); + EXPECT_EQ(index_mappings.size_, 2); + double source[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; + double target[] = { 10.0, 20.0, 30.0, 40.0 }; + CopyData(index_mappings, source, target); + EXPECT_EQ(target[0], 5.0 * 0.82); + EXPECT_EQ(target[1], 20.0); + EXPECT_EQ(target[2], 2.0); + EXPECT_EQ(target[3], 40.0); + DeleteIndexMappings(&index_mappings); + DeleteMapping(&(source_map_array[0])); + DeleteMapping(&(source_map_array[1])); + DeleteMapping(&(target_map_array[0])); + DeleteMapping(&(target_map_array[1])); + DeleteConfiguration(&config); + DeleteError(&error); +} + +TEST(Util, IndexMappingFromFile) +{ + Error error = NoError(); + Configuration config = LoadConfigurationFromFile("test/data/util_index_mapping_from_file.json", &error); + EXPECT_TRUE(IsSuccess(error)); + Mappings source_map; + Mappings target_map; + Mapping source_map_array[] = { ToMapping("Test", 1), ToMapping("Test2", 4) }; + Mapping target_map_array[] = { ToMapping("Test2", 2), ToMapping("Test3", 0) }; + source_map.mappings_ = source_map_array; + source_map.size_ = 2; + target_map.mappings_ = target_map_array; + target_map.size_ = 2; + IndexMappings index_mappings = CreateIndexMappings(config, source_map, target_map, &error); + EXPECT_TRUE(IsSuccess(error)); + EXPECT_EQ(index_mappings.mappings_[0].source_, 1); + EXPECT_EQ(index_mappings.mappings_[0].target_, 2); + EXPECT_EQ(index_mappings.mappings_[0].scale_factor_, 1.0); + EXPECT_EQ(index_mappings.mappings_[1].source_, 4); + EXPECT_EQ(index_mappings.mappings_[1].target_, 0); + EXPECT_EQ(index_mappings.mappings_[1].scale_factor_, 0.82); + EXPECT_EQ(index_mappings.size_, 2); + double source[] = { 1.0, 2.0, 3.0, 4.0, 5.0 }; + double target[] = { 10.0, 20.0, 30.0, 40.0 }; + CopyData(index_mappings, source, target); + EXPECT_EQ(target[0], 5.0 * 0.82); + EXPECT_EQ(target[1], 20.0); + EXPECT_EQ(target[2], 2.0); + EXPECT_EQ(target[3], 40.0); + DeleteIndexMappings(&index_mappings); + DeleteMapping(&(source_map_array[0])); + DeleteMapping(&(source_map_array[1])); + DeleteMapping(&(target_map_array[0])); + DeleteMapping(&(target_map_array[1])); + DeleteConfiguration(&config); + DeleteError(&error); +} \ No newline at end of file diff --git a/src/tuvx/tuvx_util.F90 b/src/tuvx/tuvx_util.F90 index 8718f2f1..ad6b6ebb 100644 --- a/src/tuvx/tuvx_util.F90 +++ b/src/tuvx/tuvx_util.F90 @@ -9,7 +9,7 @@ module musica_tuvx_util private public :: string_t_c, string_t, error_t_c, error_t, mapping_t_c, mapping_t, & - to_c_string, to_f_string, assert, copy_mappings, delete_string_c + to_c_string, to_f_string, assert, delete_string_c !> Wrapper for a c string type, bind(c) :: string_t_c @@ -291,26 +291,6 @@ function mapping_index( this ) result( index ) end function mapping_index -!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - - !> Copies mappings from a c array to a fortran array - function copy_mappings( c_mappings, n_mappings ) result( f_mappings ) - - type(c_ptr), intent(in) :: c_mappings - integer(c_size_t), intent(in) :: n_mappings - type(mapping_t), allocatable :: f_mappings(:) - - integer :: i - type(mapping_t_c), pointer :: mappings(:) - - call c_f_pointer( c_mappings, mappings, [ n_mappings ] ) - allocate( f_mappings( n_mappings ) ) - do i = 1, n_mappings - f_mappings(i) = mappings(i) - end do - - end function copy_mappings - !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! !> Convert a fortran character array to a c string diff --git a/src/util.cpp b/src/util.cpp index d51d266f..453580f5 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -4,6 +4,15 @@ #include #include +#include + +namespace +{ + struct Yaml + { + YAML::Node node_; + }; +} namespace musica { @@ -81,6 +90,47 @@ namespace musica return !(lhs == rhs); } + Configuration LoadConfigurationFromString(const char* data, Error* error) + { + DeleteError(error); + Configuration config; + try + { + config.data_ = new YAML::Node(YAML::Load(data)); + *error = NoError(); + } + catch(const std::exception& e) + { + config.data_ = nullptr; + *error = ToError(MUSICA_ERROR_CATEGORY, MUSICA_ERROR_CODE_PARSING_FAILED, e.what()); + } + return config; + } + + Configuration LoadConfigurationFromFile(const char* filename, Error* error) + { + DeleteError(error); + Configuration config; + try + { + config.data_ = new YAML::Node(YAML::LoadFile(filename)); + *error = NoError(); + } + catch(const std::exception& e) + { + config.data_ = nullptr; + *error = ToError(MUSICA_ERROR_CATEGORY, MUSICA_ERROR_CODE_PARSING_FAILED, e.what()); + } + return config; + } + + void DeleteConfiguration(Configuration* config) + { + if (config->data_ != nullptr) + delete config->data_; + config->data_ = nullptr; + } + Mapping ToMapping(const char* name, std::size_t index) { Mapping mapping; @@ -89,15 +139,23 @@ namespace musica return mapping; } - std::size_t FindMappingIndex(const Mapping* mappings, std::size_t size, const char* name, Error* error) + Mappings CreateMappings(std::size_t size) + { + Mappings mappings; + mappings.mappings_ = new Mapping[size]; + mappings.size_ = size; + return mappings; + } + + std::size_t FindMappingIndex(const Mappings mappings, const char* name, Error* error) { DeleteError(error); - for (std::size_t i = 0; i < size; i++) + for (std::size_t i = 0; i < mappings.size_; i++) { - if (std::strcmp(mappings[i].name_.value_, name) == 0) + if (std::strcmp(mappings.mappings_[i].name_.value_, name) == 0) { *error = NoError(); - return mappings[i].index_; + return mappings.mappings_[i].index_; } } std::string msg = "Mapping element '" + std::string(name) + "' not found"; @@ -110,13 +168,71 @@ namespace musica DeleteString(&(mapping->name_)); } - void DeleteMappings(Mapping* mappings, std::size_t size) + void DeleteMappings(Mappings* mappings) { + if (mappings->mappings_ == nullptr) return; + for (std::size_t i = 0; i < mappings->size_; i++) + { + DeleteMapping(&(mappings->mappings_[i])); + } + delete[] mappings->mappings_; + } + + IndexMappings CreateIndexMappings(const Configuration configuration, const Mappings source, const Mappings target, Error* error) + { + DeleteError(error); + std::size_t size = configuration.data_->size(); + IndexMappings index_mappings; + index_mappings.mappings_ = new IndexMapping[size]; + index_mappings.size_ = size; for (std::size_t i = 0; i < size; i++) { - DeleteMapping(&(mappings[i])); + const YAML::Node& node = (*configuration.data_)[i]; + std::string source_name = node["source"].as(); + std::string target_name = node["target"].as(); + std::size_t source_index = FindMappingIndex(source, source_name.c_str(), error); + if (!IsSuccess(*error)) + { + DeleteIndexMappings(&index_mappings); + return index_mappings; + } + std::size_t target_index = FindMappingIndex(target, target_name.c_str(), error); + if (!IsSuccess(*error)) + { + DeleteIndexMappings(&index_mappings); + return index_mappings; + } + index_mappings.mappings_[i].source_ = source_index; + index_mappings.mappings_[i].target_ = target_index; + if (node["scale factor"].IsDefined()) + { + index_mappings.mappings_[i].scale_factor_ = node["scale factor"].as(); + } + } + return index_mappings; + } + + void CopyData(const IndexMappings mappings, const double* source, double* target) + { + for (std::size_t i = 0; i < mappings.size_; i++) + { + target[mappings.mappings_[i].target_] = source[mappings.mappings_[i].source_] * mappings.mappings_[i].scale_factor_; + } + } + + void DeleteIndexMapping(IndexMapping* mapping) + { + // Nothing to do + } + + void DeleteIndexMappings(IndexMappings* mappings) + { + if (mappings->mappings_ == nullptr) return; + for (std::size_t i = 0; i < mappings->size_; i++) + { + DeleteIndexMapping(&(mappings->mappings_[i])); } - delete[] mappings; + delete[] mappings->mappings_; } } // namespace musica \ No newline at end of file