From 3ee9dd96bee8940bef615974b2e402a08b1edaa6 Mon Sep 17 00:00:00 2001 From: Jeffrey Curtis Date: Wed, 14 Jun 2023 11:22:02 -0500 Subject: [PATCH] Add access to rand.F90 functions and subroutines (#228) Co-authored-by: Sylwester Arabas --- CMakeLists.txt | 2 +- setup.py | 1 + src/pypartmc.cpp | 13 ++++++++++++- src/rand.F90 | 33 +++++++++++++++++++++++++++++++++ src/rand.cpp | 19 +++++++++++++++++++ src/rand.hpp | 12 ++++++++++++ tests/conftest.py | 3 +++ tests/test_rand.py | 32 ++++++++++++++++++++++++++++++++ 8 files changed, 113 insertions(+), 2 deletions(-) create mode 100644 src/rand.F90 create mode 100644 src/rand.cpp create mode 100644 src/rand.hpp create mode 100644 tests/conftest.py create mode 100644 tests/test_rand.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 9d707be6..d39c76f8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,7 @@ set(PyPartMC_sources run_part.F90 run_part_opt.F90 util.F90 aero_data.F90 aero_state.F90 env_state.F90 gas_data.F90 gas_state.F90 scenario.F90 condense.F90 aero_particle.F90 bin_grid.F90 camp_core.F90 photolysis.F90 aero_mode.F90 aero_dist.F90 bin_grid.cpp condense.cpp run_part.cpp - scenario.cpp util.cpp + scenario.cpp util.cpp rand.cpp rand.F90 ) add_prefix(src/ PyPartMC_sources) diff --git a/setup.py b/setup.py index bca7cf29..50f6de0a 100644 --- a/setup.py +++ b/setup.py @@ -150,6 +150,7 @@ def build_extension(self, ext): # pylint: disable=too-many-branches extras_require={ "tests": [ "pytest", + "pytest-order", "fastcore!=1.5.8", # https://github.com/fastai/fastcore/issues/439 "ghapi", ] diff --git a/src/pypartmc.cpp b/src/pypartmc.cpp index 94ce864e..359d4960 100644 --- a/src/pypartmc.cpp +++ b/src/pypartmc.cpp @@ -9,6 +9,7 @@ #include "pybind11_json/pybind11_json.hpp" #include "util.hpp" +#include "rand.hpp" #include "run_part.hpp" #include "run_part_opt.hpp" #include "aero_data.hpp" @@ -416,6 +417,14 @@ PYBIND11_MODULE(_PyPartMC, m) { "Evaluate a loss rate function." ); + m.def( + "rand_init", &rand_init, "Initializes the random number generator to the state defined by the given seed plus offset. If the seed is 0 then a seed is auto-generated from the current time plus offset" + ); + + m.def( + "rand_normal", &rand_normal, "Generates a normally distributed random number with the given mean and standard deviation" + ); + m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); m.attr("__all__") = py::make_tuple( @@ -446,6 +455,8 @@ PYBIND11_MODULE(_PyPartMC, m) { "sphere_rad2vol", "diam2rad", "loss_rate_dry_dep", - "loss_rate" + "loss_rate", + "rand_init", + "rand_normal" ); } diff --git a/src/rand.F90 b/src/rand.F90 new file mode 100644 index 00000000..b7623f45 --- /dev/null +++ b/src/rand.F90 @@ -0,0 +1,33 @@ +!################################################################################################### +! This file is a part of PyPartMC licensed under the GNU General Public License v3 (LICENSE file) # +! Copyright (C) 2022 University of Illinois Urbana-Champaign # +! Authors: https://github.com/open-atmos/PyPartMC/graphs/contributors # +!################################################################################################### + +module PyPartMC_rand + +use iso_c_binding +use pmc_rand + +implicit none + +contains + + subroutine f_pmc_srand(seed, offset) bind(C) + integer(c_int) :: seed + integer(c_int) :: offset + + call pmc_srand(seed, offset) + + end subroutine + + subroutine f_rand_normal(mean, stddev, val) bind(C) + real(c_double) :: mean + real(c_double) :: stddev + real(c_double) :: val + + val = rand_normal(mean, stddev) + + end subroutine + +end module diff --git a/src/rand.cpp b/src/rand.cpp new file mode 100644 index 00000000..744d8d7b --- /dev/null +++ b/src/rand.cpp @@ -0,0 +1,19 @@ +/*################################################################################################## +# This file is a part of PyPartMC licensed under the GNU General Public License v3 (LICENSE file) # +# Copyright (C) 2022 University of Illinois Urbana-Champaign # +# Authors: https://github.com/open-atmos/PyPartMC/graphs/contributors # +##################################################################################################*/ + +#include "rand.hpp" + +void rand_init(int seed, int offset) { + f_pmc_srand(&seed, &offset); +} + +double rand_normal(double mean, double stddev) { + double val; + + f_rand_normal(&mean, &stddev, &val); + + return val; +} diff --git a/src/rand.hpp b/src/rand.hpp new file mode 100644 index 00000000..602405ce --- /dev/null +++ b/src/rand.hpp @@ -0,0 +1,12 @@ +/*################################################################################################## +# This file is a part of PyPartMC licensed under the GNU General Public License v3 (LICENSE file) # +# Copyright (C) 2022 University of Illinois Urbana-Champaign # +# Authors: https://github.com/open-atmos/PyPartMC/graphs/contributors # +##################################################################################################*/ + +#pragma once + +extern "C" void f_pmc_srand(const int*, const int*); +extern "C" void f_rand_normal(const double*, const double*, double*); +void rand_init(int seed, int offset); +double rand_normal(double mean, double stddev); diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..5e1a0b6f --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,3 @@ +import PyPartMC as ppmc + +ppmc.rand_init(44, 0) diff --git a/tests/test_rand.py b/tests/test_rand.py new file mode 100644 index 00000000..dfb086b4 --- /dev/null +++ b/tests/test_rand.py @@ -0,0 +1,32 @@ +#################################################################################################### +# This file is a part of PyPartMC licensed under the GNU General Public License v3 (LICENSE file) # +# Copyright (C) 2022 University of Illinois Urbana-Champaign # +# Authors: https://github.com/open-atmos/PyPartMC/graphs/contributors # +#################################################################################################### + +import pytest + +import PyPartMC as ppmc + + +@pytest.mark.order(-1) +@pytest.mark.parametrize( + "init_args", + ( + ((44, 1), (44, 1)), + pytest.param(((44, 0), (44, 1)), marks=pytest.mark.xfail(strict=True)), + ), +) +def test_set_rand_seed(init_args): + # arrange + rand_arg = (0, 1) + values = [] + + # act + for init_arg in init_args: + ppmc.rand_init(*init_arg) + values.append(ppmc.rand_normal(*rand_arg)) + + # assert + for value in values[1:]: + assert value == values[0]