diff --git a/.github/workflows/mac.yml b/.github/workflows/mac.yml index 247d035b..ecc09b54 100644 --- a/.github/workflows/mac.yml +++ b/.github/workflows/mac.yml @@ -30,10 +30,12 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-python@v5 - - run: pip install -r python/requirements.txt + - run: pip install -r python/requirements.txt --user - name: Run Cmake - run: cmake -S . -B build -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_ENABLE_PYTHON_LIBRARY=ON + run: | + PYTHON_PATH=$(which python) + cmake -S . -B build -D CMAKE_CXX_FLAGS=-Wl,-ld_classic -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_ENABLE_PYTHON_LIBRARY=ON -D Python3_EXECUTABLE=${PYTHON_PATH} - name: Build run: cmake --build build --parallel @@ -58,7 +60,7 @@ jobs: run: brew install netcdf netcdf-fortran - 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_CXX_FLAGS=-Wl,-ld_classic -D CMAKE_BUILD_TYPE=${{ matrix.build_type }} -D MUSICA_BUILD_FORTRAN_INTERFACE=ON - name: Build run: cmake --build build --parallel diff --git a/cmake/dependencies.cmake b/cmake/dependencies.cmake index 061ef184..feba77d2 100644 --- a/cmake/dependencies.cmake +++ b/cmake/dependencies.cmake @@ -33,7 +33,7 @@ endif() if (MUSICA_ENABLE_MICM) FetchContent_Declare(micm GIT_REPOSITORY https://github.com/NCAR/micm.git - GIT_TAG v3.5.0-release-candidate + GIT_TAG 2a5cd4e11a6973974f3c584dfa9841d70e0a42d5 ) FetchContent_MakeAvailable(micm) endif() diff --git a/fortran/micm_core.F90 b/fortran/micm_core.F90 index 378a2e31..3c0c8845 100644 --- a/fortran/micm_core.F90 +++ b/fortran/micm_core.F90 @@ -1,6 +1,7 @@ module micm_core - - use iso_c_binding, only: c_ptr, c_char, c_int, c_bool, c_double, c_null_char, c_size_t, c_f_pointer + use iso_c_binding, only: c_ptr, c_char, c_int, c_bool, c_double, c_null_char, & + c_size_t, c_f_pointer, c_funptr, c_null_ptr, c_associated + use musica_util, only: error_t_c, is_success implicit none public :: micm_t, mapping_t @@ -13,20 +14,24 @@ module micm_core end type mapping_t interface - function create_micm_c(config_path, error_code) bind(C, name="create_micm") + function create_micm_c(config_path, error) bind(C, name="create_micm") + use musica_util, only: error_t_c import c_ptr, c_int, c_char - character(kind=c_char), intent(in) :: config_path(*) - integer(kind=c_int), intent(out) :: error_code - type(c_ptr) :: create_micm_c + character(kind=c_char), intent(in) :: config_path(*) + type(error_t_c), intent(inout) :: error + type(c_ptr) :: create_micm_c end function create_micm_c - subroutine delete_micm_c(micm) bind(C, name="delete_micm") + subroutine delete_micm_c(micm, error) bind(C, name="delete_micm") + use musica_util, only: error_t_c import c_ptr - type(c_ptr), intent(in) :: micm + type(c_ptr), value, intent(in) :: micm + type(error_t_c), intent(inout) :: error end subroutine delete_micm_c subroutine micm_solve_c(micm, time_step, temperature, pressure, num_concentrations, concentrations, & - num_user_defined_reaction_rates, user_defined_reaction_rates) bind(C, name="micm_solve") + num_user_defined_reaction_rates, user_defined_reaction_rates, error) bind(C, name="micm_solve") + use musica_util, only: error_t_c import c_ptr, c_double, c_int type(c_ptr), value, intent(in) :: micm real(kind=c_double), value, intent(in) :: time_step @@ -36,56 +41,69 @@ subroutine micm_solve_c(micm, time_step, temperature, pressure, num_concentratio real(kind=c_double), intent(inout) :: concentrations(num_concentrations) integer(kind=c_int), value, intent(in) :: num_user_defined_reaction_rates real(kind=c_double), intent(inout) :: user_defined_reaction_rates(num_user_defined_reaction_rates) + type(error_t_c), intent(inout) :: error end subroutine micm_solve_c - function get_species_property_string_c(micm, species_name, property_name) bind(c, name="get_species_property_string") - use musica_util, only: string_t_c + function get_species_property_string_c(micm, species_name, property_name, error) bind(c, name="get_species_property_string") + use musica_util, only: error_t_c, string_t_c import :: c_ptr, c_char - type(c_ptr), value :: micm + 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 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) bind(c, name="get_species_property_double") + function get_species_property_double_c(micm, species_name, property_name, error) bind(c, name="get_species_property_double") + use musica_util, only: error_t_c import :: c_ptr, c_char, c_double - type(c_ptr), value :: micm + 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) bind(c, name="get_species_property_int") + function get_species_property_int_c(micm, species_name, property_name, error) bind(c, name="get_species_property_int") + use musica_util, only: error_t_c import :: c_ptr, c_char, c_int - type(c_ptr), value :: micm + 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) bind(c, name="get_species_property_bool") + function get_species_property_bool_c(micm, species_name, property_name, error) bind(c, name="get_species_property_bool") + use musica_util, only: error_t_c import :: c_ptr, c_char, c_bool - type(c_ptr), value :: micm + 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 - function get_species_ordering(micm, array_size) bind(c, name="get_species_ordering") + function get_species_ordering(micm, array_size, error) bind(c, name="get_species_ordering") + use musica_util, only: error_t_c import :: c_ptr, c_size_t - type(c_ptr), value :: micm + type(c_ptr), value, intent(in) :: micm integer(kind=c_size_t), intent(out) :: array_size + type(error_t_c), intent(inout) :: error type(c_ptr) :: get_species_ordering end function get_species_ordering - type(c_ptr) function get_user_defined_reaction_rates_ordering(micm, array_size) & + type(c_ptr) function get_user_defined_reaction_rates_ordering(micm, array_size, error) & bind(c, name="get_user_defined_reaction_rates_ordering") + use musica_util, only: error_t_c import :: c_ptr, c_size_t - type(c_ptr), value :: micm + type(c_ptr), value, intent(in) :: micm integer(kind=c_size_t), intent(out) :: array_size + type(error_t_c), intent(inout) :: error end function get_user_defined_reaction_rates_ordering end interface type :: micm_t - type(mapping_t), pointer :: species_ordering(:), user_defined_reaction_rates(:) + type(mapping_t), pointer :: species_ordering(:) => null() + type(mapping_t), pointer :: user_defined_reaction_rates(:) => null() integer(kind=c_size_t) :: species_ordering_length, user_defined_reaction_rates_length - type(c_ptr), private :: ptr + type(c_ptr), private :: ptr = c_null_ptr contains ! Solve the chemical system procedure :: solve @@ -104,12 +122,12 @@ end function get_user_defined_reaction_rates_ordering contains - function constructor(config_path, errcode) result( this ) - type(micm_t), pointer :: this - character(len=*), intent(in) :: config_path - integer, intent(out) :: errcode - character(len=1, kind=c_char) :: c_config_path(len_trim(config_path)+1) - integer :: n, i + function constructor(config_path, error) result( this ) + type(micm_t), pointer :: this + character(len=*), intent(in) :: config_path + type(error_t_c), 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 allocate( this ) @@ -120,73 +138,90 @@ function constructor(config_path, errcode) result( this ) end do c_config_path(n+1) = c_null_char - this%ptr = create_micm_c(c_config_path, errcode) - - if (errcode /= 0) then + this%ptr = create_micm_c(c_config_path, error) + if (.not. is_success(error)) then + deallocate(this) + nullify(this) return end if - mappings_ptr = get_species_ordering(this%ptr, this%species_ordering_length) + mappings_ptr = get_species_ordering(this%ptr, this%species_ordering_length, error) call c_f_pointer(mappings_ptr, this%species_ordering, [this%species_ordering_length]) + if (.not. is_success(error)) then + deallocate(this) + nullify(this) + return + end if - - mappings_ptr = get_user_defined_reaction_rates_ordering(this%ptr, this%user_defined_reaction_rates_length) + mappings_ptr = get_user_defined_reaction_rates_ordering(this%ptr, this%user_defined_reaction_rates_length, error) call c_f_pointer(mappings_ptr, this%user_defined_reaction_rates, [this%user_defined_reaction_rates_length]) + if (.not. is_success(error)) then + deallocate(this) + nullify(this) + return + end if end function constructor subroutine solve(this, time_step, temperature, pressure, num_concentrations, concentrations, & - num_user_defined_reaction_rates, user_defined_reaction_rates) - class(micm_t) :: this - real(c_double), intent(in) :: time_step - real(c_double), intent(in) :: temperature - real(c_double), intent(in) :: pressure - integer(c_int), intent(in) :: num_concentrations - real(c_double), intent(inout) :: concentrations(*) - integer(c_int), intent(in) :: num_user_defined_reaction_rates - real(c_double), intent(inout) :: user_defined_reaction_rates(*) + num_user_defined_reaction_rates, user_defined_reaction_rates, error) + class(micm_t) :: this + real(c_double), intent(in) :: time_step + real(c_double), intent(in) :: temperature + real(c_double), intent(in) :: pressure + integer(c_int), intent(in) :: num_concentrations + real(c_double), intent(inout) :: concentrations(*) + integer(c_int), intent(in) :: num_user_defined_reaction_rates + real(c_double), intent(inout) :: user_defined_reaction_rates(*) + type(error_t_c), intent(inout) :: error call micm_solve_c(this%ptr, time_step, temperature, pressure, num_concentrations, concentrations, & - num_user_defined_reaction_rates, user_defined_reaction_rates) + num_user_defined_reaction_rates, user_defined_reaction_rates, error) end subroutine solve - function get_species_property_string(this, species_name, property_name) result(value) + function get_species_property_string(this, species_name, property_name, error) result(value) use musica_util, only: to_f_string, to_c_string - class(micm_t) :: this - character(len=*), intent(in) :: species_name, property_name - character(len=:), allocatable :: value + class(micm_t) :: this + character(len=*), intent(in) :: species_name, property_name + type(error_t_c), intent(inout) :: error + character(len=:), allocatable :: value value = to_f_string(get_species_property_string_c(this%ptr, & - to_c_string(species_name), to_c_string(property_name))) + to_c_string(species_name), to_c_string(property_name), error)) end function get_species_property_string - function get_species_property_double(this, species_name, property_name) result(value) + function get_species_property_double(this, species_name, property_name, error) result(value) use musica_util, only: to_c_string - class(micm_t) :: this - character(len=*), intent(in) :: species_name, property_name - real(c_double) :: value + class(micm_t) :: this + character(len=*), intent(in) :: species_name, property_name + type(error_t_c), intent(inout) :: error + real(c_double) :: value value = get_species_property_double_c(this%ptr, & - to_c_string(species_name), to_c_string(property_name)) + to_c_string(species_name), to_c_string(property_name), error) end function get_species_property_double - function get_species_property_int(this, species_name, property_name) result(value) + function get_species_property_int(this, species_name, property_name, error) result(value) use musica_util, only: to_c_string - class(micm_t) :: this - character(len=*), intent(in) :: species_name, property_name - integer(c_int) :: value + class(micm_t) :: this + character(len=*), intent(in) :: species_name, property_name + type(error_t_c), intent(inout) :: error + integer(c_int) :: value value = get_species_property_int_c(this%ptr, & - to_c_string(species_name), to_c_string(property_name)) + to_c_string(species_name), to_c_string(property_name), error) end function get_species_property_int - function get_species_property_bool(this, species_name, property_name) result(value) + function get_species_property_bool(this, species_name, property_name, error) result(value) use musica_util, only: to_c_string - class(micm_t) :: this - character(len=*), intent(in) :: species_name, property_name - logical :: value + class(micm_t) :: this + character(len=*), intent(in) :: species_name, property_name + type(error_t_c), intent(inout) :: error + logical :: value value = get_species_property_bool_c(this%ptr, & - to_c_string(species_name), to_c_string(property_name)) + to_c_string(species_name), to_c_string(property_name), error) end function get_species_property_bool subroutine finalize(this) type(micm_t), intent(inout) :: this - call delete_micm_c(this%ptr) + type(error_t_c) :: error + call delete_micm_c(this%ptr, error) + this%ptr = c_null_ptr end subroutine finalize end module micm_core diff --git a/fortran/test/fetch_content_integration/test_micm_fort_api.F90 b/fortran/test/fetch_content_integration/test_micm_fort_api.F90 index 0159fa1f..1d80436a 100644 --- a/fortran/test/fetch_content_integration/test_micm_fort_api.F90 +++ b/fortran/test/fetch_content_integration/test_micm_fort_api.F90 @@ -1,12 +1,16 @@ program test_micm_fort_api - use iso_c_binding + use, intrinsic :: iso_c_binding + use, intrinsic :: ieee_arithmetic use micm_core, only: micm_t, mapping_t - use musica_util, only: assert + use musica_util, only: assert, error_t_c, is_error, is_success - implicit none +#include "micm/util/error.hpp" #define ASSERT( expr ) call assert( expr, __FILE__, __LINE__ ) #define ASSERT_EQ( a, b ) call assert( a == b, __FILE__, __LINE__ ) +#define ASSERT_NE( a, b ) call assert( a /= b, __FILE__, __LINE__ ) + + implicit none type(micm_t), pointer :: micm real(c_double) :: time_step @@ -15,13 +19,14 @@ program test_micm_fort_api integer(c_int) :: num_concentrations, num_user_defined_reaction_rates real(c_double), dimension(5) :: concentrations real(c_double), dimension(3) :: user_defined_reaction_rates - integer :: errcode, i + integer :: i character(len=256) :: config_path type(mapping_t) :: the_mapping character(len=:), allocatable :: string_value real(c_double) :: double_value integer(c_int) :: int_value logical(c_bool) :: bool_value + type(error_t_c) :: error time_step = 200 temperature = 272.5 @@ -34,7 +39,8 @@ program test_micm_fort_api write(*,*) "[test micm fort api] Creating MICM solver..." - micm => micm_t(config_path, errcode) + micm => micm_t(config_path, error) + ASSERT( is_success( error ) ) do i = 1, micm%species_ordering_length the_mapping = micm%species_ordering(i) @@ -45,28 +51,46 @@ program test_micm_fort_api print *, "User Defined Reaction Rate Name:", the_mapping%name(:the_mapping%string_length), ", Index:", the_mapping%index end do - if (errcode /= 0) then - write(*,*) "[test micm fort api] Failed in creating solver." - stop 3 - endif - write(*,*) "[test micm fort api] Initial concentrations", concentrations write(*,*) "[test micm fort api] Solving starts..." call micm%solve(time_step, temperature, pressure, num_concentrations, concentrations, & - num_user_defined_reaction_rates, user_defined_reaction_rates) + num_user_defined_reaction_rates, user_defined_reaction_rates, error) + ASSERT( is_success( error ) ) write(*,*) "[test micm fort api] After solving, concentrations", concentrations - string_value = micm%get_species_property_string( "O3", "__long name" ) + string_value = micm%get_species_property_string( "O3", "__long name", error ) + ASSERT( is_success( error ) ) ASSERT_EQ( string_value, "ozone" ) - double_value = micm%get_species_property_double( "O3", "molecular weight [kg mol-1]" ) + double_value = micm%get_species_property_double( "O3", "molecular weight [kg mol-1]", error ) + ASSERT( is_success( error ) ) ASSERT_EQ( double_value, 0.048_c_double ) - int_value = micm%get_species_property_int( "O3", "__atoms" ) + int_value = micm%get_species_property_int( "O3", "__atoms", error ) + ASSERT( is_success( error ) ) ASSERT_EQ( int_value, 3_c_int ) - bool_value = micm%get_species_property_bool( "O3", "__do advect" ) + bool_value = micm%get_species_property_bool( "O3", "__do advect", error ) + ASSERT( is_success( error ) ) ASSERT( logical( bool_value ) ) + string_value = micm%get_species_property_string( "O3", "missing property", error ) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_SPECIES, \ + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND ) ) + double_value = micm%get_species_property_double( "O3", "missing property", error ) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_SPECIES, \ + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND ) ) + int_value = micm%get_species_property_int( "O3", "missing property", error ) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_SPECIES, \ + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND ) ) + bool_value = micm%get_species_property_bool( "O3", "missing property", error ) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_SPECIES, \ + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND ) ) + deallocate( micm ) + micm => micm_t( "configs/invalid", error ) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_CONFIGURATION, \ + MICM_CONFIGURATION_ERROR_CODE_INVALID_FILE_PATH ) ) + ASSERT( .not. associated( micm ) ) + write(*,*) "[test micm fort api] Finished." end program diff --git a/fortran/test/fetch_content_integration/test_micm_fort_api_invalid.F90 b/fortran/test/fetch_content_integration/test_micm_fort_api_invalid.F90 index 93ef7fae..23588d21 100644 --- a/fortran/test/fetch_content_integration/test_micm_fort_api_invalid.F90 +++ b/fortran/test/fetch_content_integration/test_micm_fort_api_invalid.F90 @@ -1,28 +1,36 @@ -subroutine test_micm_fort_api_invalid() - use iso_c_binding - use micm_core, only: micm_t +program test_micm_api + use, intrinsic :: iso_c_binding + use musica_util, only: assert, is_error + +#include "micm/util/error.hpp" + +#define ASSERT( expr ) call assert( expr, __FILE__, __LINE__ ) +#define ASSERT_EQ( a, b ) call assert( a == b, __FILE__, __LINE__ ) +#define ASSERT_NE( a, b ) call assert( a /= b, __FILE__, __LINE__ ) implicit none - type(micm_t), pointer :: micm - integer :: errcode - character(len=7) :: config_path + call test_micm_fort_api_invalid() - config_path = "invalid_config" +contains - write(*,*) "[test micm fort api] Creating MICM solver..." - micm => micm_t(config_path, errcode) + subroutine test_micm_fort_api_invalid() + use musica_util, only: error_t_c + use micm_core, only: micm_t - if (errcode /= 0) then - write(*,*) "[test micm fort api] Failed in creating solver. Expected failure. Error code: ", errcode - stop 0 - else - write(*,*) "[test micm fort api] Unexpected error code: ", errcode - stop 3 - endif + implicit none -end subroutine + type(micm_t), pointer :: micm + character(len=7) :: config_path + type(error_t_c) :: error + + config_path = "invalid_config" + + write(*,*) "[test micm fort api] Creating MICM solver..." + micm => micm_t(config_path, error) + ASSERT( is_error( error, MICM_ERROR_CATEGORY_CONFIGURATION, \ + MICM_CONFIGURATION_ERROR_CODE_INVALID_FILE_PATH ) ) + + end subroutine -program test_micm_api - call test_micm_fort_api_invalid() end program diff --git a/fortran/util.F90 b/fortran/util.F90 index 90cadedb..406a68c3 100644 --- a/fortran/util.F90 +++ b/fortran/util.F90 @@ -2,12 +2,13 @@ ! SPDX-License-Identifier: Apache-2.0 module musica_util - use iso_c_binding, only: c_ptr, c_size_t + use iso_c_binding, only: c_int, c_ptr, c_size_t implicit none private - public :: string_t_c, to_c_string, to_f_string, assert + public :: string_t_c, error_t_c, to_c_string, to_f_string, assert, & + is_success, is_error, code, category, message !> Wrapper for a c string type, bind(c) :: string_t_c @@ -15,6 +16,13 @@ module musica_util integer(c_size_t) :: size_ end type string_t_c + !> Wrapper for an error condition + type, bind(c) :: error_t_c + integer(c_int) :: code_ + type(string_t_c) :: category_ + type(string_t_c) :: message_ + end type error_t_c + contains !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! @@ -81,6 +89,66 @@ subroutine assert( condition, file, line, error_message ) end subroutine assert +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Check if an error_t_c object represents a success condition + logical function is_success( error ) + + type(error_t_c), intent(in) :: error + + is_success = error%code_ == 0_c_int + + end function is_success + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Check if an error_t_c object matches a specific error condition + logical function is_error( error, category, code ) + + type(error_t_c), intent(in) :: error + character(len=*), intent(in) :: category + integer, intent(in) :: code + + is_error = int( error%code_ ) == code .and. & + to_f_string( error%category_ ) == category + + end function is_error + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Extract the error code from an error_t_c object + integer function code( error ) + + type(error_t_c), intent(in) :: error + + code = int( error%code_ ) + + end function code + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Extract the error category from an error_t_c object + function category( error ) + + type(error_t_c), intent(in) :: error + character(len=:), allocatable :: category + + category = to_f_string( error%category_ ) + + end function category + +!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + + !> Extract the error message from an error_t_c object + function message( error ) + + type(error_t_c), intent(in) :: error + character(len=:), allocatable :: message + + message = to_f_string( error%message_ ) + + end function message + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! end module musica_util \ No newline at end of file diff --git a/include/musica/micm.hpp b/include/musica/micm.hpp index 448a0d2d..69e855c9 100644 --- a/include/musica/micm.hpp +++ b/include/musica/micm.hpp @@ -1,10 +1,8 @@ -/** - * This file contains the defintion of the MICM class, which represents a multi-component - * reactive transport model. It also includes functions for creating and deleting MICM instances with c binding - * Copyright (C) 2023-2024 National Center for Atmospheric Research, - * - * SPDX-License-Identifier: Apache-2.0* creating solvers, and solving the model. - */ +// Copyright (C) 2023-2024 National Center for Atmospheric Research, +// SPDX-License-Identifier: Apache-2.0* creating solvers, and solving the model. +// +// This file contains the defintion of the MICM class, which represents a multi-component +// reactive transport model. It also includes functions for creating and deleting MICM instances with c bindings. #pragma once #include @@ -23,15 +21,15 @@ extern "C" { #endif - MICM *create_micm(const char *config_path, int *error_code); - void delete_micm(const MICM *micm); - void micm_solve(MICM *micm, double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters); - Mapping *get_species_ordering(MICM *micm, size_t *array_size); - Mapping *get_user_defined_reaction_rates_ordering(MICM *micm, size_t *array_size); - String get_species_property_string(MICM *micm, const char *species_name, const char *property_name); - double get_species_property_double(MICM *micm, const char *species_name, const char *property_name); - int get_species_property_int(MICM *micm, const char *species_name, const char *property_name); - bool get_species_property_bool(MICM *micm, const char *species_name, const char *property_name); + MICM *create_micm(const char *config_path, Error *error); + void delete_micm(const MICM *micm, Error *error); + void micm_solve(MICM *micm, double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters, Error *error); + Mapping *get_species_ordering(MICM *micm, size_t *array_size, Error *error); + Mapping *get_user_defined_reaction_rates_ordering(MICM *micm, size_t *array_size, Error *error); + String get_species_property_string(MICM *micm, const char *species_name, const char *property_name, Error *error); + double get_species_property_double(MICM *micm, const char *species_name, const char *property_name, Error *error); + int get_species_property_int(MICM *micm, const char *species_name, const char *property_name, Error *error); + bool get_species_property_bool(MICM *micm, const char *species_name, const char *property_name, Error *error); #ifdef __cplusplus } @@ -42,32 +40,39 @@ class MICM public: /// @brief Create a solver by reading and parsing configuration file /// @param config_path Path to configuration file or directory containing configuration file + /// @param error Error struct to indicate success or failure /// @return 0 on success, 1 on failure in parsing configuration file - int create_solver(const std::string &config_path); + void create_solver(const std::string &config_path, Error *error); /// @brief Solve the system /// @param time_step Time [s] to advance the state by - /// @para/ @briefm temperature Temperature [K] - /// @param pressure Pressure/ [P@brief C@param num_concentrations The number oconfiguration file - /// @param config_path Path to configuration file or directory containing configuration file + /// @param temperature Temperature [K] + /// @param pressure Pressure + /// @param num_concentrations The size of the concentrations array + /// @param concentrations Array of species' concentrations + /// @param num_custom_rate_parameters The size of the custom_rate_parameters array + /// @param custom_rate_parameters Array of custom rate parameters + /// @param error Error struct to indicate success or failure /// @return 0 on success, 1 on failure in parsing species' concentrations - /// @param concentrations Species's concentrations - void solve(double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters); + void solve(double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters, Error *error); /// @brief Get a property for a chemical species /// @param species_name Name of the species /// @param property_name Name of the property + /// @param error Error struct to indicate success or failure /// @return Value of the property template - T get_species_property(const std::string &species_name, const std::string &property_name); + T get_species_property(const std::string &species_name, const std::string &property_name, Error *error); /// @brief Get the ordering of species + /// @param error Error struct to indicate success or failure /// @return Map of species names to their indices - std::map get_species_ordering(); + std::map get_species_ordering(Error *error); /// @brief Get the ordering of user-defined reaction rates + /// @param error Error struct to indicate success or failure /// @return Map of reaction rate names to their indices - std::map get_user_defined_reaction_rates_ordering(); + std::map get_user_defined_reaction_rates_ordering(Error *error); static constexpr size_t NUM_GRID_CELLS = 1; @@ -77,19 +82,26 @@ class MICM }; template -inline T MICM::get_species_property(const std::string &species_name, const std::string &property_name) +inline T MICM::get_species_property(const std::string &species_name, const std::string &property_name, Error *error) { + *error = NoError(); for (const auto &species : solver_parameters_->system_.gas_phase_.species_) { if (species.name_ == species_name) { - try { + try + { return species.GetProperty(property_name); + } catch (const std::system_error &e) { + *error = ToError(e); + return T(); } - catch (const std::exception &e) { - throw std::runtime_error(std::string(e.what()) + " for species '" + species_name + "'"); - } + return species.GetProperty(property_name); } } - throw std::runtime_error("Species '" + species_name + "' not found"); + std::string msg = "Species '" + species_name + "' not found"; + *error = ToError(MUSICA_ERROR_CATEGORY, + MUSICA_ERROR_CODE_SPECIES_NOT_FOUND, + msg.c_str()); + return T(); } \ No newline at end of file diff --git a/include/musica/util.hpp b/include/musica/util.hpp index e0c1815d..10e8113c 100644 --- a/include/musica/util.hpp +++ b/include/musica/util.hpp @@ -4,7 +4,12 @@ #include +#define MUSICA_ERROR_CATEGORY "MUSICA Error" +#define MUSICA_ERROR_CODE_SPECIES_NOT_FOUND 1 + #ifdef __cplusplus +#include + extern "C" { #endif @@ -16,6 +21,21 @@ struct String size_t size_; }; +/// @brief A struct to represent a const string +struct ConstString +{ + const char* value_; + size_t size_; +}; + +/// @brief A struct to describe failure conditions +struct Error +{ + int code_; + ConstString category_; + ConstString message_; +}; + /// @brief A struct to represent a mapping between a string and an index struct Mapping { @@ -29,10 +49,47 @@ struct Mapping /// @return The casted String String ToString(char* value); +/// @brief Casts a const char* to a String +ConstString ToConstString(const char* value); + /// @brief Deletes a String /// @param str The String to delete void DeleteString(String str); #ifdef __cplusplus } + +/// @brief Creates an Error indicating no error +/// @return The Error +Error NoError(); + +/// @brief Creates an Error from a category and code +/// @param category The category of the Error +/// @param code The code of the Error +/// @return The Error +Error ToError(const char* category, int code); + +/// @brief Creates an Error from a category, code, and message +/// @param category The category of the Error +/// @param code The code of the Error +/// @param message The message of the Error +/// @return The Error +Error ToError(const char* category, int code, const char* message); + +/// @brief Creates an Error from syd::system_error +/// @param e The std::system_error to convert +/// @return The Error +Error ToError(const std::system_error& e); + +/// @brief Overloads the equality operator for Error types +/// @param lhs The left-hand side Error +/// @param rhs The right-hand side Error +/// @return True if the Errors are equal, false otherwise +bool operator==(const Error& lhs, const Error& rhs); + +/// @brief Overloads the inequality operator for Error types +/// @param lhs The left-hand side Error +/// @param rhs The right-hand side Error +/// @return True if the Errors are not equal, false otherwise +bool operator!=(const Error& lhs, const Error& rhs); #endif \ No newline at end of file diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index 9711a941..310d8314 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -2,6 +2,7 @@ pybind11_add_module(musica_python wrapper.cpp ${PROJECT_SOURCE_DIR}/src/micm/micm.cpp ${PROJECT_SOURCE_DIR}/src/component_versions.c + ${PROJECT_SOURCE_DIR}/src/util.cpp ${CMAKE_BINARY_DIR}/version.c ) diff --git a/python/wrapper.cpp b/python/wrapper.cpp index b1cec1a8..4f36176f 100644 --- a/python/wrapper.cpp +++ b/python/wrapper.cpp @@ -15,8 +15,8 @@ PYBIND11_MODULE(musica, m) m.def("create_micm", [](const char *config_path) { - int error_code; - MICM* micm = create_micm(config_path, &error_code); + Error error; + MICM* micm = create_micm(config_path, &error); return micm; }); m.def("delete_micm", &delete_micm); @@ -37,9 +37,11 @@ PYBIND11_MODULE(musica, m) } } + Error error; micm_solve(micm, time_step, temperature, pressure, concentrations_cpp.size(), concentrations_cpp.data(), - custom_rate_parameters_cpp.size(), custom_rate_parameters_cpp.data()); + custom_rate_parameters_cpp.size(), custom_rate_parameters_cpp.data(), + &error); // Update the concentrations list after solving for (size_t i = 0; i < concentrations_cpp.size(); ++i) { @@ -49,11 +51,13 @@ PYBIND11_MODULE(musica, m) m.def( "species_ordering", [](MICM *micm) - { return micm->get_species_ordering(); }, + { Error error; + return micm->get_species_ordering(&error); }, "Return map of get_species_ordering rates"); m.def( "user_defined_reaction_rates", [](MICM *micm) - { return micm->get_user_defined_reaction_rates_ordering(); }, + { Error error; + return micm->get_user_defined_reaction_rates_ordering(&error); }, "Return map of reaction rates"); } \ No newline at end of file diff --git a/src/micm/micm.cpp b/src/micm/micm.cpp index 54c3d392..0555f706 100644 --- a/src/micm/micm.cpp +++ b/src/micm/micm.cpp @@ -1,180 +1,193 @@ -/** - * This file contains the implementation of the MICM class, which represents a multi-component - * reactive transport model. It also includes functions for creating and deleting MICM instances, - * Copyright (C) 2023-2024 National Center for Atmospheric Research, - * - * SPDX-License-Identifier: Apache-2.0* creating solvers, and solving the model. - */ +// Copyright (C) 2023-2024 National Center for Atmospheric Research, +// SPDX-License-Identifier: Apache-2.0 +// +// This file contains the implementation of the MICM class, which represents a +// multi-component reactive transport model. It also includes functions for +// creating and deleting MICM instances, creating solvers, and solving the model. +#include #include #include -#include #include #include #include -MICM *create_micm(const char *config_path, int *error_code) -{ - try - { - MICM *micm = new MICM(); - *error_code = micm->create_solver(std::string(config_path)); - return micm; - } - catch (const std::bad_alloc &e) - { - *error_code = 1; - return nullptr; - } +MICM *create_micm(const char *config_path, Error *error) { + MICM *micm = new MICM(); + micm->create_solver(std::string(config_path), error); + if (*error != NoError()) { + delete micm; + return nullptr; + } + return micm; } -void delete_micm(const MICM *micm) -{ +void delete_micm(const MICM *micm, Error *error) { + if (micm == nullptr) { + *error = NoError(); + return; + } + try { delete micm; + *error = NoError(); + } catch (const std::system_error &e) { + *error = ToError(e); + } } -void micm_solve(MICM *micm, double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters) -{ - micm->solve(time_step, temperature, pressure, num_concentrations, concentrations, num_custom_rate_parameters, custom_rate_parameters); +void micm_solve(MICM *micm, double time_step, double temperature, + double pressure, int num_concentrations, double *concentrations, + int num_custom_rate_parameters, + double *custom_rate_parameters, Error *error) { + micm->solve(time_step, temperature, pressure, num_concentrations, + concentrations, num_custom_rate_parameters, + custom_rate_parameters, error); } -Mapping *get_species_ordering(MICM *micm, size_t *array_size) -{ - auto map = micm->get_species_ordering(); - Mapping *species_ordering = new Mapping[map.size()]; - - // Copy data from the map to the array of structs - size_t i = 0; - for (const auto &entry : map) - { - std::strcpy(species_ordering[i].name, entry.first.c_str()); - species_ordering[i].index = entry.second; - species_ordering[i].string_length = entry.first.size(); - ++i; - } - - // Set the size of the array - *array_size = map.size(); - - return species_ordering; +Mapping *get_species_ordering(MICM *micm, size_t *array_size, Error *error) { + auto map = micm->get_species_ordering(error); + Mapping *species_ordering = new Mapping[map.size()]; + + // Copy data from the map to the array of structs + size_t i = 0; + for (const auto &entry : map) { + std::strcpy(species_ordering[i].name, entry.first.c_str()); + species_ordering[i].index = entry.second; + species_ordering[i].string_length = entry.first.size(); + ++i; + } + + // Set the size of the array + *array_size = map.size(); + return species_ordering; } -Mapping *get_user_defined_reaction_rates_ordering(MICM *micm, size_t *array_size) -{ - auto map = micm->get_user_defined_reaction_rates_ordering(); - Mapping *reactionRates = new Mapping[map.size()]; - - // Copy data from the map to the array of structs - size_t i = 0; - for (const auto &entry : map) - { - std::strcpy(reactionRates[i].name, entry.first.c_str()); - reactionRates[i].index = entry.second; - reactionRates[i].string_length = entry.first.size(); - ++i; - } - - // Set the size of the array - *array_size = map.size(); - - return reactionRates; +Mapping *get_user_defined_reaction_rates_ordering(MICM *micm, + size_t *array_size, Error *error) { + auto map = micm->get_user_defined_reaction_rates_ordering(error); + Mapping *reactionRates = new Mapping[map.size()]; + + // Copy data from the map to the array of structs + size_t i = 0; + for (const auto &entry : map) { + std::strcpy(reactionRates[i].name, entry.first.c_str()); + reactionRates[i].index = entry.second; + reactionRates[i].string_length = entry.first.size(); + ++i; + } + + // Set the size of the array + *array_size = map.size(); + return reactionRates; } -String get_species_property_string(MICM *micm, const char *species_name, const char *property_name) -{ - std::string species_name_str(species_name); - std::string property_name_str(property_name); - const std::string value_str = micm->get_species_property(species_name_str, property_name_str); - String value; - value.size_ = value_str.length(); - value.value_ = new char[value.size_ + 1]; - std::strcpy(value.value_, value_str.c_str()); - - return value; +String get_species_property_string(MICM *micm, const char *species_name, + const char *property_name, Error *error) { + std::string species_name_str(species_name); + std::string property_name_str(property_name); + const std::string value_str = micm->get_species_property( + species_name_str, property_name_str, error); + String value; + value.size_ = value_str.length(); + value.value_ = new char[value.size_ + 1]; + std::strcpy(value.value_, value_str.c_str()); + + return value; } -double get_species_property_double(MICM *micm, const char *species_name, const char *property_name) -{ - std::string species_name_str(species_name); - std::string property_name_str(property_name); - return micm->get_species_property(species_name_str, property_name_str); +double get_species_property_double(MICM *micm, const char *species_name, + const char *property_name, Error *error) { + std::string species_name_str(species_name); + std::string property_name_str(property_name); + return micm->get_species_property(species_name_str, + property_name_str, + error); } -int get_species_property_int(MICM *micm, const char *species_name, const char *property_name) -{ - std::string species_name_str(species_name); - std::string property_name_str(property_name); - return micm->get_species_property(species_name_str, property_name_str); +int get_species_property_int(MICM *micm, const char *species_name, + const char *property_name, Error *error) { + std::string species_name_str(species_name); + std::string property_name_str(property_name); + return micm->get_species_property(species_name_str, + property_name_str, + error); } -bool get_species_property_bool(MICM *micm, const char *species_name, const char *property_name) -{ - std::string species_name_str(species_name); - std::string property_name_str(property_name); - return micm->get_species_property(species_name_str, property_name_str); +bool get_species_property_bool(MICM *micm, const char *species_name, + const char *property_name, Error *error) { + std::string species_name_str(species_name); + std::string property_name_str(property_name); + return micm->get_species_property(species_name_str, + property_name_str, + error); } -int MICM::create_solver(const std::string &config_path) -{ - int parsing_status = 0; // 0 on success, 1 on failure - try { - micm::SolverConfig<> solver_config; - micm::ConfigParseStatus status = solver_config.ReadAndParse(std::filesystem::path(config_path)); - - if (status == micm::ConfigParseStatus::Success) - { - solver_parameters_ = std::make_unique(solver_config.GetSolverParams()); - auto params = micm::RosenbrockSolverParameters::three_stage_rosenbrock_parameters(NUM_GRID_CELLS); - params.ignore_unused_species_ = true; - solver_ = std::make_unique>(solver_parameters_->system_, - solver_parameters_->processes_, - params); - } - else - { - parsing_status = 1; - } - } - catch(std::exception &e) - { - std::cerr << "Error: " << e.what() << std::endl; - parsing_status = 1; - } - - - return parsing_status; +void MICM::create_solver(const std::string &config_path, Error *error) { + try { + micm::SolverConfig<> solver_config; + solver_config.ReadAndParse(std::filesystem::path(config_path)); + solver_parameters_ = std::make_unique( + solver_config.GetSolverParams()); + auto params = + micm::RosenbrockSolverParameters::three_stage_rosenbrock_parameters( + NUM_GRID_CELLS); + params.ignore_unused_species_ = true; + solver_ = std::make_unique>( + solver_parameters_->system_, solver_parameters_->processes_, params); + *error = NoError(); + } catch (const std::system_error &e) { + *error = ToError(e); + } } -void MICM::solve(double time_step, double temperature, double pressure, int num_concentrations, double *concentrations, int num_custom_rate_parameters, double *custom_rate_parameters) -{ +void MICM::solve(double time_step, double temperature, double pressure, + int num_concentrations, double *concentrations, + int num_custom_rate_parameters, + double *custom_rate_parameters, Error *error) { + try { micm::State state = solver_->GetState(); - for (size_t i{}; i < NUM_GRID_CELLS; i++) - { - state.conditions_[i].temperature_ = temperature; - state.conditions_[i].pressure_ = pressure; + for (size_t i{}; i < NUM_GRID_CELLS; i++) { + state.conditions_[i].temperature_ = temperature; + state.conditions_[i].pressure_ = pressure; } - state.variables_.AsVector().assign(concentrations, concentrations + num_concentrations); + state.variables_.AsVector().assign(concentrations, + concentrations + num_concentrations); - state.custom_rate_parameters_.AsVector().assign(custom_rate_parameters, custom_rate_parameters + num_custom_rate_parameters); + state.custom_rate_parameters_.AsVector().assign( + custom_rate_parameters, + custom_rate_parameters + num_custom_rate_parameters); - auto result = solver_->Solve(time_step, state); + auto result = solver_->Solve(time_step, state); - for (int i = 0; i < result.result_.AsVector().size(); i++) - { - concentrations[i] = result.result_.AsVector()[i]; + for (int i = 0; i < result.result_.AsVector().size(); i++) { + concentrations[i] = result.result_.AsVector()[i]; } + *error = NoError(); + } catch (const std::system_error &e) { + *error = ToError(e); + } } -std::map MICM::get_species_ordering() -{ +std::map MICM::get_species_ordering(Error *error) { + try { micm::State state = solver_->GetState(); + *error = NoError(); return state.variable_map_; + } catch (const std::system_error &e) { + *error = ToError(e); + return std::map(); + } } -std::map MICM::get_user_defined_reaction_rates_ordering() -{ +std::map MICM::get_user_defined_reaction_rates_ordering(Error *error) { + try { micm::State state = solver_->GetState(); + *error = NoError(); return state.custom_rate_parameter_map_; + } catch (const std::system_error &e) { + *error = ToError(e); + return std::map(); + } } diff --git a/src/test/connections/micm_c_api.cpp b/src/test/connections/micm_c_api.cpp index 726957dd..de900a50 100644 --- a/src/test/connections/micm_c_api.cpp +++ b/src/test/connections/micm_c_api.cpp @@ -1,46 +1,171 @@ +#include #include +#include #include // Test fixture for the MICM C API class MicmCApiTest : public ::testing::Test { protected: MICM* micm; - int error_code; const char* config_path = "configs/chapman"; void SetUp() override { micm = nullptr; - error_code = 0; - micm = create_micm(config_path, &error_code); + Error error; + micm = create_micm(config_path, &error); + ASSERT_EQ(error, NoError()); } void TearDown() override { - delete_micm(micm); + Error error; + delete_micm(micm, &error); + ASSERT_EQ(error, NoError()); } }; +// Test case for bad configuration file path +TEST_F(MicmCApiTest, BadConfigurationFilePath) { + Error error = NoError(); + auto micm_bad_config = create_micm("bad config path", &error); + ASSERT_EQ(micm_bad_config, nullptr); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_CONFIGURATION, + MICM_CONFIGURATION_ERROR_CODE_INVALID_FILE_PATH)); +} + +// Test case for missing species property +TEST_F(MicmCApiTest, MissingSpeciesProperty) { + Error error = NoError(); + String string_value; + string_value = get_species_property_string(micm, "O3", "bad property", &error); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_SPECIES, + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND)); + ASSERT_STREQ(string_value.value_, ""); + DeleteString(string_value); + error = NoError(); + ASSERT_EQ(get_species_property_double(micm, "O3", "bad property", &error), 0.0); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_SPECIES, + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND)); + error = NoError(); + ASSERT_EQ(get_species_property_int(micm, "O3", "bad property", &error), 0); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_SPECIES, + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND)); + error = NoError(); + ASSERT_FALSE(get_species_property_bool(micm, "O3", "bad property", &error)); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_SPECIES, + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND)); +} + // Test case for creating the MICM instance TEST_F(MicmCApiTest, CreateMicmInstance) { - ASSERT_EQ(error_code, 0); ASSERT_NE(micm, nullptr); } +// Test case for getting species ordering +TEST_F(MicmCApiTest, GetSpeciesOrdering) +{ + Error error; + size_t array_size; + Mapping* species_ordering = get_species_ordering(micm, &array_size, &error); + ASSERT_EQ(error, NoError()); + ASSERT_EQ(array_size, 5); + bool found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(species_ordering[i].name, "O3") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(species_ordering[i].name, "O") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(species_ordering[i].name, "O2") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(species_ordering[i].name, "M") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(species_ordering[i].name, "O1D") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + delete[] species_ordering; +} + +// Test case for getting user-defined reaction rates ordering +TEST_F(MicmCApiTest, GetUserDefinedReactionRatesOrdering) +{ + Error error; + size_t array_size; + Mapping* reaction_rates_ordering = get_user_defined_reaction_rates_ordering(micm, &array_size, &error); + ASSERT_EQ(error, NoError()); + ASSERT_EQ(array_size, 3); + bool found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(reaction_rates_ordering[i].name, "PHOTO.R1") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(reaction_rates_ordering[i].name, "PHOTO.R3") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + found = false; + for (size_t i = 0; i < array_size; i++) { + if (strcmp(reaction_rates_ordering[i].name, "PHOTO.R5") == 0) { + found = true; + break; + } + } + ASSERT_TRUE(found); + delete[] reaction_rates_ordering; +} + // Test case for solving the MICM instance TEST_F(MicmCApiTest, SolveMicmInstance) { + Error error; double time_step = 200.0; double temperature = 272.5; double pressure = 101253.3; int num_concentrations = 5; double concentrations[] = {0.75, 0.4, 0.8, 0.01, 0.02}; - auto ordering = micm->get_user_defined_reaction_rates_ordering(); + auto ordering = micm->get_user_defined_reaction_rates_ordering(&error); + ASSERT_EQ(error, NoError()); + int num_custom_rate_parameters = ordering.size(); std::vector custom_rate_parameters(num_custom_rate_parameters, 0.0); for(auto& entry : ordering) { custom_rate_parameters[entry.second] = 0.0; } - micm_solve(micm, time_step, temperature, pressure, num_concentrations, concentrations, custom_rate_parameters.size(), custom_rate_parameters.data()); + micm_solve(micm, time_step, temperature, pressure, num_concentrations, concentrations, custom_rate_parameters.size(), custom_rate_parameters.data(), &error); + ASSERT_EQ(error, NoError()); // Add assertions to check the solved concentrations ASSERT_EQ(concentrations[0], 0.75); @@ -52,24 +177,22 @@ TEST_F(MicmCApiTest, SolveMicmInstance) { // Test case for getting species properties TEST_F(MicmCApiTest, GetSpeciesProperty) { + Error error; String string_value; - string_value = get_species_property_string(micm, "O3", "__long name"); + string_value = get_species_property_string(micm, "O3", "__long name", &error); + ASSERT_EQ(error, NoError()); ASSERT_STREQ(string_value.value_, "ozone"); DeleteString(string_value); - ASSERT_EQ(get_species_property_double(micm, "O3", "molecular weight [kg mol-1]"), 0.048); - ASSERT_TRUE(get_species_property_bool(micm, "O3", "__do advect")); - ASSERT_EQ(get_species_property_int(micm, "O3", "__atoms"), 3); -// these exceptions are not caught by ASSERT_THROW when using clang-cl -#ifndef MUSICA_USING_CLANGCL - ASSERT_THROW({ - try { - get_species_property_bool(micm, "bad species", "__is gas"); - } catch (const std::runtime_error& e) { - ASSERT_STREQ(e.what(), "Species 'bad species' not found"); - throw; - }}, std::runtime_error); - EXPECT_ANY_THROW( - get_species_property_double(micm, "O3", "bad property") - ); -#endif + ASSERT_EQ(get_species_property_double(micm, "O3", "molecular weight [kg mol-1]", &error), 0.048); + ASSERT_EQ(error, NoError()); + ASSERT_TRUE(get_species_property_bool(micm, "O3", "__do advect", &error)); + ASSERT_EQ(error, NoError()); + ASSERT_EQ(get_species_property_int(micm, "O3", "__atoms", &error), 3); + ASSERT_EQ(error, NoError()); + get_species_property_bool(micm, "bad species", "__is gas", &error); + ASSERT_EQ(error, ToError(MUSICA_ERROR_CATEGORY, + MUSICA_ERROR_CODE_SPECIES_NOT_FOUND)); + get_species_property_double(micm, "O3", "bad property", &error); + ASSERT_EQ(error, ToError(MICM_ERROR_CATEGORY_SPECIES, + MICM_SPECIES_ERROR_CODE_PROPERTY_NOT_FOUND)); } \ No newline at end of file diff --git a/src/util.cpp b/src/util.cpp index b99b3aca..6483c618 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -12,7 +12,54 @@ String ToString(char* value) return str; } +ConstString ToConstString(const char* value) +{ + ConstString str; + str.value_ = value; + str.size_ = std::strlen(value); + return str; +} + void DeleteString(String str) { delete[] str.value_; +} + +Error NoError() +{ + return ToError("", 0, "Success"); +} + +Error ToError(const char* category, int code) +{ + return ToError(category, code, ""); +} + +Error ToError(const char* category, int code, const char* message) +{ + Error error; + error.code_ = code; + error.category_ = ToConstString(category); + error.message_ = ToConstString(message); + return error; +} + +Error ToError(const std::system_error& e) +{ + return ToError(e.code().category().name(), e.code().value(), e.what()); +} + +bool operator==(const Error& lhs, const Error& rhs) +{ + if (lhs.code_ == 0 && rhs.code_ == 0) + { + return true; + } + return lhs.code_ == rhs.code_ && + std::strcmp(lhs.category_.value_, rhs.category_.value_) == 0; +} + +bool operator!=(const Error& lhs, const Error& rhs) +{ + return !(lhs == rhs); } \ No newline at end of file