From 71c1903a6cc86e633a6b316f37acfd921b18fb12 Mon Sep 17 00:00:00 2001 From: Jeff Curtis Date: Tue, 11 Nov 2025 09:10:28 -0600 Subject: [PATCH 1/3] add access to more EnvState properties --- src/env_state.F90 | 66 +++++++++++++++++++++++++++++++++++++++++ src/env_state.hpp | 59 +++++++++++++++++++++++++++++++++++- src/pypartmc.cpp | 6 ++++ tests/test_env_state.py | 36 ++++++++++++++++++++++ 4 files changed, 166 insertions(+), 1 deletion(-) diff --git a/src/env_state.F90 b/src/env_state.F90 index fd66e1cd..b38f0bf7 100644 --- a/src/env_state.F90 +++ b/src/env_state.F90 @@ -171,4 +171,70 @@ subroutine f_env_state_air_dens(ptr_c, air_density) bind(C) end subroutine + subroutine f_env_state_get_latitude(ptr_c, latitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(out) :: latitude + + call c_f_pointer(ptr_c, ptr_f) + + latitude = ptr_f%latitude + + end subroutine + + subroutine f_env_state_set_latitude(ptr_c, latitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(inout) :: ptr_c + real(c_double), intent(in) :: latitude + + call c_f_pointer(ptr_c, ptr_f) + + ptr_f%latitude = latitude + + end subroutine + + subroutine f_env_state_get_longitude(ptr_c, longitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(out) :: longitude + + call c_f_pointer(ptr_c, ptr_f) + + longitude = ptr_f%longitude + + end subroutine + + subroutine f_env_state_set_longitude(ptr_c, longitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(inout) :: ptr_c + real(c_double), intent(in) :: longitude + + call c_f_pointer(ptr_c, ptr_f) + + ptr_f%longitude = longitude + + end subroutine + + subroutine f_env_state_get_altitude(ptr_c, altitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(out) :: altitude + + call c_f_pointer(ptr_c, ptr_f) + + altitude = ptr_f%altitude + + end subroutine + + subroutine f_env_state_set_altitude(ptr_c, altitude) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(inout) :: ptr_c + real(c_double), intent(in) :: altitude + + call c_f_pointer(ptr_c, ptr_f) + + ptr_f%altitude = altitude + + end subroutine + end module diff --git a/src/env_state.hpp b/src/env_state.hpp index 4fa1bd8c..6d81ccc7 100644 --- a/src/env_state.hpp +++ b/src/env_state.hpp @@ -24,7 +24,12 @@ extern "C" void f_env_state_get_pressure(const void *ptr, double *pressure) noex extern "C" void f_env_state_get_elapsed_time(const void *ptr, double *elapsed_time) noexcept; extern "C" void f_env_state_get_start_time(const void *ptr, double *start_time) noexcept; extern "C" void f_env_state_air_dens(const void *ptr, double *air_density) noexcept; - +extern "C" void f_env_state_set_latitude(const void *ptr, const double *latitude) noexcept; +extern "C" void f_env_state_get_latitude(const void *ptr, double *latitude) noexcept; +extern "C" void f_env_state_set_longitude(const void *ptr, const double *longitude) noexcept; +extern "C" void f_env_state_get_longitude(const void *ptr, double *longitude) noexcept; +extern "C" void f_env_state_set_altitude(const void *ptr, const double *altitude) noexcept; +extern "C" void f_env_state_get_altitude(const void *ptr, double *altitude) noexcept; struct EnvState { PMCResource ptr; @@ -148,4 +153,56 @@ struct EnvState { ); return air_density; } + + static void set_latitude(const EnvState &self, const double latitude) { + f_env_state_set_latitude( + self.ptr.f_arg(), + &latitude + ); + } + + static auto get_latitude(const EnvState &self) { + double latitude; + + f_env_state_get_latitude( + self.ptr.f_arg(), + &latitude + ); + return latitude; + } + + static void set_longitude(const EnvState &self, const double longitude) { + f_env_state_set_longitude( + self.ptr.f_arg(), + &longitude + ); + } + + static auto get_longitude(const EnvState &self) { + double longitude; + + f_env_state_get_longitude( + self.ptr.f_arg(), + &longitude + ); + return longitude; + } + + static void set_altitude(const EnvState &self, const double altitude) { + f_env_state_set_altitude( + self.ptr.f_arg(), + &altitude + ); + } + + static auto get_altitude(const EnvState &self) { + double altitude; + + f_env_state_get_altitude( + self.ptr.f_arg(), + &altitude + ); + return altitude; + } + }; diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index 621f8772..58a9e48d 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -424,6 +424,12 @@ NB_MODULE(_PyPartMC, m) { "Box height (m).") .def_prop_rw("pressure", &EnvState::get_pressure, &EnvState::set_pressure, "Ambient pressure (Pa).") + .def_prop_rw("latitude", &EnvState::get_latitude, &EnvState::set_latitude, + "Latitude (degrees).") + .def_prop_rw("longitude", &EnvState::get_longitude, &EnvState::set_longitude, + "Longitude (degrees).") + .def_prop_rw("altitude", &EnvState::get_altitude, &EnvState::set_altitude, + "Altitude (m).") .def_prop_ro("air_density", &EnvState::air_density, "Air density (kg m^{-3}).") .def_prop_rw("additive_kernel_coefficient", &EnvState::get_additive_kernel_coefficient, &EnvState::set_additive_kernel_coefficient, diff --git a/tests/test_env_state.py b/tests/test_env_state.py index 45ce141f..4703e564 100644 --- a/tests/test_env_state.py +++ b/tests/test_env_state.py @@ -72,6 +72,42 @@ def test_pressure(): # assert assert value == sut.pressure + @staticmethod + def test_latitude(): + # arrange + sut = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + value = 40.0 + + # act + sut.latitude = value + + # assert + assert value == sut.latitude + + @staticmethod + def test_longitude(): + # arrange + sut = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + value = 180.0 + + # act + sut.longitude = value + + # assert + assert value == sut.longitude + + @staticmethod + def test_altitude(): + # arrange + sut = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + value = 200.0 + + # act + sut.altitude = value + + # assert + assert value == sut.altitude + @staticmethod def test_additive_kernel_coefficient(): # arrange From af3536058df237ebca03d86124f78bb54a125524 Mon Sep 17 00:00:00 2001 From: Jeff Curtis Date: Tue, 11 Nov 2025 09:55:08 -0600 Subject: [PATCH 2/3] add ppb and conc conversions --- src/env_state.F90 | 24 ++++++++++++++++++++++++ src/env_state.hpp | 24 ++++++++++++++++++++++++ src/pypartmc.cpp | 2 ++ tests/test_env_state.py | 22 ++++++++++++++++++++++ 4 files changed, 72 insertions(+) diff --git a/src/env_state.F90 b/src/env_state.F90 index b38f0bf7..2ed9fd36 100644 --- a/src/env_state.F90 +++ b/src/env_state.F90 @@ -237,4 +237,28 @@ subroutine f_env_state_set_altitude(ptr_c, altitude) bind(C) end subroutine + subroutine f_env_state_ppb_to_conc(ptr_c, ppb, conc) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(in) :: ppb + real(c_double), intent(out) :: conc + + call c_f_pointer(ptr_c, ptr_f) + + conc = env_state_ppb_to_conc(ptr_f, ppb) + + end subroutine + + subroutine f_env_state_conc_to_ppb(ptr_c, conc, ppb) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(out) :: ppb + real(c_double), intent(in) :: conc + + call c_f_pointer(ptr_c, ptr_f) + + ppb = env_state_conc_to_ppb(ptr_f, conc) + + end subroutine + end module diff --git a/src/env_state.hpp b/src/env_state.hpp index 6d81ccc7..f1865e59 100644 --- a/src/env_state.hpp +++ b/src/env_state.hpp @@ -30,6 +30,8 @@ extern "C" void f_env_state_set_longitude(const void *ptr, const double *longitu extern "C" void f_env_state_get_longitude(const void *ptr, double *longitude) noexcept; extern "C" void f_env_state_set_altitude(const void *ptr, const double *altitude) noexcept; extern "C" void f_env_state_get_altitude(const void *ptr, double *altitude) noexcept; +extern "C" void f_env_state_ppb_to_conc(const void *ptr, const double *ppb, double *conc) noexcept; +extern "C" void f_env_state_conc_to_ppb(const void *ptr, const double *conc, double *ppb) noexcept; struct EnvState { PMCResource ptr; @@ -205,4 +207,26 @@ struct EnvState { return altitude; } + static auto ppb_to_conc(const EnvState &self, const double ppb) { + double conc; + + f_env_state_ppb_to_conc( + self.ptr.f_arg(), + &ppb, + &conc + ); + return conc; + } + + static auto conc_to_ppb(const EnvState &self, const double conc) { + double ppb; + + f_env_state_conc_to_ppb( + self.ptr.f_arg(), + &conc, + &ppb + ); + return ppb; + } + }; diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index 58a9e48d..240d590f 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -434,6 +434,8 @@ NB_MODULE(_PyPartMC, m) { "Air density (kg m^{-3}).") .def_prop_rw("additive_kernel_coefficient", &EnvState::get_additive_kernel_coefficient, &EnvState::set_additive_kernel_coefficient, "Scaling coefficient for additive coagulation kernel.") + .def("ppb_to_conc", &EnvState::ppb_to_conc, "Convert (ppb) to (molecules m^{-3}).") + .def("conc_to_ppb", &EnvState::conc_to_ppb, "Convert (molecules m^{-3}) to (ppb).") ; nb::class_(m, diff --git a/tests/test_env_state.py b/tests/test_env_state.py index 4703e564..2ee9862c 100644 --- a/tests/test_env_state.py +++ b/tests/test_env_state.py @@ -6,6 +6,7 @@ import gc +import numpy as np import pytest import PyPartMC as ppmc @@ -155,3 +156,24 @@ def test_air_density(): # assert assert 1 * si.kg / si.m**3 < env_state.air_density < 1.5 * si.kg / si.m**3 + + @staticmethod + def test_conc_ppb_conversions(): + # arrange + gas_data = ppmc.GasData(GAS_DATA_CTOR_ARG_MINIMAL) + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + scenario = ppmc.Scenario(gas_data, aero_data, SCENARIO_CTOR_ARG_MINIMAL) + env_state = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + scenario.init_env_state(env_state, 0.0) + + # act + conc_orig = 1.0 + ppb = env_state.conc_to_ppb(conc_orig) + ppb_orig = 1.0 + conc = env_state.ppb_to_conc(ppb_orig) + + # assert + assert ppb < conc_orig + assert ppb_orig < conc + np.testing.assert_almost_equal(env_state.ppb_to_conc(ppb), conc_orig) + np.testing.assert_almost_equal(env_state.conc_to_ppb(conc), ppb_orig) From fde71380666050a6c26cdcffc1eee9dac5fa4fb1 Mon Sep 17 00:00:00 2001 From: Jeff Curtis Date: Tue, 11 Nov 2025 10:37:26 -0600 Subject: [PATCH 3/3] add air_molar_density --- src/env_state.F90 | 11 +++++++++++ src/env_state.hpp | 11 +++++++++++ src/pypartmc.cpp | 2 ++ tests/test_env_state.py | 14 ++++++++++++++ 4 files changed, 38 insertions(+) diff --git a/src/env_state.F90 b/src/env_state.F90 index 2ed9fd36..b00b8f85 100644 --- a/src/env_state.F90 +++ b/src/env_state.F90 @@ -171,6 +171,17 @@ subroutine f_env_state_air_dens(ptr_c, air_density) bind(C) end subroutine + subroutine f_env_state_air_molar_dens(ptr_c, air_molar_density) bind(C) + type(env_state_t), pointer :: ptr_f => null() + type(c_ptr), intent(in) :: ptr_c + real(c_double), intent(out) :: air_molar_density + + call c_f_pointer(ptr_c, ptr_f) + + air_molar_density = env_state_air_molar_den(ptr_f) + + end subroutine + subroutine f_env_state_get_latitude(ptr_c, latitude) bind(C) type(env_state_t), pointer :: ptr_f => null() type(c_ptr), intent(in) :: ptr_c diff --git a/src/env_state.hpp b/src/env_state.hpp index f1865e59..a96d232d 100644 --- a/src/env_state.hpp +++ b/src/env_state.hpp @@ -24,6 +24,7 @@ extern "C" void f_env_state_get_pressure(const void *ptr, double *pressure) noex extern "C" void f_env_state_get_elapsed_time(const void *ptr, double *elapsed_time) noexcept; extern "C" void f_env_state_get_start_time(const void *ptr, double *start_time) noexcept; extern "C" void f_env_state_air_dens(const void *ptr, double *air_density) noexcept; +extern "C" void f_env_state_air_molar_dens(const void *ptr, double *air_molar_density) noexcept; extern "C" void f_env_state_set_latitude(const void *ptr, const double *latitude) noexcept; extern "C" void f_env_state_get_latitude(const void *ptr, double *latitude) noexcept; extern "C" void f_env_state_set_longitude(const void *ptr, const double *longitude) noexcept; @@ -156,6 +157,16 @@ struct EnvState { return air_density; } + static auto air_molar_density(const EnvState &self) { + double air_molar_density; + + f_env_state_air_molar_dens( + self.ptr.f_arg(), + &air_molar_density + ); + return air_molar_density; + } + static void set_latitude(const EnvState &self, const double latitude) { f_env_state_set_latitude( self.ptr.f_arg(), diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index 240d590f..02998c8c 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -432,6 +432,8 @@ NB_MODULE(_PyPartMC, m) { "Altitude (m).") .def_prop_ro("air_density", &EnvState::air_density, "Air density (kg m^{-3}).") + .def_prop_ro("air_molar_density", &EnvState::air_molar_density, + "Air molar density (mol m^{-3}).") .def_prop_rw("additive_kernel_coefficient", &EnvState::get_additive_kernel_coefficient, &EnvState::set_additive_kernel_coefficient, "Scaling coefficient for additive coagulation kernel.") .def("ppb_to_conc", &EnvState::ppb_to_conc, "Convert (ppb) to (molecules m^{-3}).") diff --git a/tests/test_env_state.py b/tests/test_env_state.py index 2ee9862c..b2f07537 100644 --- a/tests/test_env_state.py +++ b/tests/test_env_state.py @@ -157,6 +157,20 @@ def test_air_density(): # assert assert 1 * si.kg / si.m**3 < env_state.air_density < 1.5 * si.kg / si.m**3 + @staticmethod + def test_air_molar_density(): + # arrange + gas_data = ppmc.GasData(GAS_DATA_CTOR_ARG_MINIMAL) + aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL) + scenario = ppmc.Scenario(gas_data, aero_data, SCENARIO_CTOR_ARG_MINIMAL) + env_state = ppmc.EnvState(ENV_STATE_CTOR_ARG_MINIMAL) + scenario.init_env_state(env_state, 0.0) + + # assert + assert ( + 1 * si.mol / si.m**3 < env_state.air_molar_density < 100 * si.mol / si.m**3 + ) + @staticmethod def test_conc_ppb_conversions(): # arrange