Skip to content

Commit a7d5f8d

Browse files
jcurtis2slayoo
andauthored
Add ability to get species names in gas_data (spec_name) and aero_data (spec_name and source_name) (#327)
Co-authored-by: Sylwester Arabas <[email protected]>
1 parent 729c7d9 commit a7d5f8d

File tree

9 files changed

+217
-2
lines changed

9 files changed

+217
-2
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ string(CONCAT
438438
"print(''.join([f'#define {line.split()[3]} {line.split()[5]}\\n' for line in sys.stdin if 'parameter ::' in line]))"
439439
)
440440
include(CheckFunctionExists)
441-
foreach(file aero_data;output)
441+
foreach(file aero_data;gas_data;output)
442442
execute_process(
443443
COMMAND ${PYTHON_EXECUTABLE} "-c" "${cmd}"
444444
INPUT_FILE ${CMAKE_SOURCE_DIR}/gitmodules/partmc/src/${file}.F90

src/aero_data.F90

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,4 +196,48 @@ subroutine f_aero_data_n_source(ptr_c, n_source) bind(C)
196196

197197
end subroutine
198198

199+
subroutine f_aero_data_source_name_by_index(ptr_c, i_source, &
200+
name_data &
201+
) bind(C)
202+
type(aero_data_t), pointer :: ptr_f => null()
203+
type(c_ptr), intent(in) :: ptr_c
204+
character(kind=c_char), dimension(AERO_SOURCE_NAME_LEN) :: name_data
205+
integer(c_int) :: name_size
206+
integer(c_int), intent(in) :: i_source
207+
integer :: i
208+
character(len=1000) :: name
209+
210+
call c_f_pointer(ptr_c, ptr_f)
211+
212+
name = ptr_f%source_name(i_source+1)
213+
name_size = len(trim(ptr_f%source_name(i_source+1)))
214+
do i = 1,name_size
215+
name_data(i) = name(i:i)
216+
end do
217+
name_data(i) = CHAR(0)
218+
219+
end subroutine
220+
221+
subroutine f_aero_data_spec_name_by_index(ptr_c, i_spec, &
222+
name_data &
223+
) bind(C)
224+
type(aero_data_t), pointer :: ptr_f => null()
225+
type(c_ptr), intent(in) :: ptr_c
226+
character(kind=c_char), dimension(AERO_NAME_LEN) :: name_data
227+
integer(c_int) :: name_size
228+
integer(c_int), intent(in) :: i_spec
229+
integer :: i
230+
character(len=AERO_NAME_LEN) :: name
231+
232+
call c_f_pointer(ptr_c, ptr_f)
233+
234+
name = ptr_f%name(i_spec+1)
235+
name_size = len(trim(ptr_f%name(i_spec+1)))
236+
do i = 1,name_size
237+
name_data(i) = name(i:i)
238+
end do
239+
name_data(i) = CHAR(0)
240+
241+
end subroutine
242+
199243
end module

src/aero_data.hpp

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "pmc_resource.hpp"
1010
#include "gimmicks.hpp"
11+
#include "pybind11/stl.h"
1112
#include "aero_data_parameters.hpp"
1213

1314
extern "C" void f_aero_data_ctor(void *ptr) noexcept;
@@ -27,6 +28,10 @@ extern "C" void f_aero_data_vol2rad(const void *ptr, const double*, double*) noe
2728
extern "C" void f_aero_data_diam2vol(const void *ptr, const double*, double*) noexcept;
2829
extern "C" void f_aero_data_vol2diam(const void *ptr, const double*, double*) noexcept;
2930
extern "C" void f_aero_data_get_species_density(const void *ptr, const int *idx, double *val) noexcept;
31+
extern "C" void f_aero_data_source_name_by_index(const void *ptr, const int *i_source,
32+
char *name_data) noexcept;
33+
extern "C" void f_aero_data_spec_name_by_index(const void *ptr, const int *i_spec,
34+
char *name_data) noexcept;
3035

3136
struct AeroData {
3237
PMCResource ptr;
@@ -206,5 +211,46 @@ struct AeroData {
206211
return len;
207212
}
208213

214+
static auto sources(const AeroData &self) {
215+
216+
int len;
217+
f_aero_data_n_source(
218+
self.ptr.f_arg(),
219+
&len
220+
);
221+
222+
char name[AERO_SOURCE_NAME_LEN];
223+
auto names = pybind11::tuple(len);
224+
for (int idx = 0; idx < len; idx++) {
225+
f_aero_data_source_name_by_index(
226+
self.ptr.f_arg(),
227+
&idx,
228+
name
229+
);
230+
names[idx] = std::string(name);
231+
}
232+
return names;
233+
}
234+
235+
static auto names(const AeroData &self) {
236+
237+
int len;
238+
f_aero_data_len(
239+
self.ptr.f_arg(),
240+
&len
241+
);
242+
243+
char name[AERO_NAME_LEN];
244+
auto names = pybind11::tuple(len);
245+
for (int idx = 0; idx < len; idx++) {
246+
f_aero_data_spec_name_by_index(
247+
self.ptr.f_arg(),
248+
&idx,
249+
name
250+
);
251+
names[idx] = std::string(name);
252+
}
253+
return names;
254+
}
209255
};
210256

src/gas_data.F90

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,26 @@ subroutine f_gas_data_spec_by_name(ptr_c, value, name_data, name_size) &
6565

6666
end subroutine
6767

68+
subroutine f_gas_data_spec_name_by_index(ptr_c, i_spec, &
69+
name_data &
70+
) bind(C)
71+
type(gas_data_t), pointer :: ptr_f => null()
72+
type(c_ptr), intent(in) :: ptr_c
73+
character(kind=c_char), dimension(GAS_NAME_LEN) :: name_data
74+
integer(c_int) :: name_size
75+
integer(c_int), intent(in) :: i_spec
76+
integer :: i
77+
character(len=GAS_NAME_LEN) :: name
78+
79+
call c_f_pointer(ptr_c, ptr_f)
80+
81+
name = ptr_f%name(i_spec+1)
82+
name_size = len(trim(ptr_f%name(i_spec+1)))
83+
do i = 1,name_size
84+
name_data(i) = name(i:i)
85+
end do
86+
name_data(i) = CHAR(0)
87+
88+
end subroutine
89+
6890
end module

src/gas_data.hpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "gimmicks.hpp" // TODO #119: rename to something like json_resource.hpp?
1010
#include "pmc_resource.hpp"
11+
#include "gas_data_parameters.hpp"
1112
#include "pybind11/stl.h"
1213
#include "pybind11_json/pybind11_json.hpp"
1314

@@ -18,6 +19,8 @@ extern "C" void f_gas_data_from_json(const void *ptr) noexcept;
1819
extern "C" void f_gas_data_to_json(const void *ptr) noexcept;
1920
extern "C" void f_gas_data_spec_by_name(const void *ptr, int *value, const char *name_data,
2021
const int *name_size) noexcept;
22+
extern "C" void f_gas_data_spec_name_by_index(const void *ptr, const int *i_spec,
23+
char *name_data) noexcept;
2124

2225
struct GasData {
2326
PMCResource ptr;
@@ -64,5 +67,26 @@ struct GasData {
6467
throw std::runtime_error("Element not found.");
6568
return value - 1;
6669
}
70+
71+
static auto names(const GasData &self) {
72+
73+
int len;
74+
f_gas_data_len(
75+
self.ptr.f_arg(),
76+
&len
77+
);
78+
79+
char name[GAS_NAME_LEN];
80+
auto names = pybind11::tuple(len);
81+
for (int idx = 0; idx < len; idx++) {
82+
f_gas_data_spec_name_by_index(
83+
self.ptr.f_arg(),
84+
&idx,
85+
name
86+
);
87+
names[idx] = std::string(name);
88+
}
89+
return names;
90+
}
6791
};
6892

src/pypartmc.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ PYBIND11_MODULE(_PyPartMC, m) {
8888
.def("__len__", AeroData::__len__, "Number of aerosol species")
8989
.def_property_readonly("n_source", AeroData::n_source,
9090
"Number of aerosol sources")
91+
.def_property_readonly("sources", AeroData::sources, "return list of source names")
9192
.def_property("frac_dim", &AeroData::get_frac_dim, &AeroData::set_frac_dim,
9293
"Volume fractal dimension (1)")
9394
.def_property("vol_fill_factor", &AeroData::get_vol_fill_factor,
@@ -105,6 +106,8 @@ PYBIND11_MODULE(_PyPartMC, m) {
105106
"Convert geometric diameter (m) to mass-equivalent volume (m^3).")
106107
.def("vol2diam", AeroData::vol2diam,
107108
"Convert mass-equivalent volume (m^3) to geometric diameter (m).")
109+
.def_property_readonly("species", AeroData::names,
110+
"returns list of aerosol species names")
108111
;
109112

110113
py::class_<AeroParticle>(m, "AeroParticle",
@@ -262,6 +265,7 @@ PYBIND11_MODULE(_PyPartMC, m) {
262265
"returns a string with JSON representation of the object")
263266
.def("spec_by_name", GasData::spec_by_name,
264267
"returns the number of the species in gas with the given name")
268+
.def_property_readonly("species", GasData::names, "returns list of gas species names")
265269
;
266270

267271
py::class_<EnvState>(m,

tests/test_aero_data.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import PyPartMC as ppmc
1111
from PyPartMC import si
1212

13+
# pylint: disable=R0904
14+
1315
AERO_DATA_CTOR_ARG_MINIMAL = (
1416
{"H2O": [1000 * si.kg / si.m**3, 1, 18e-3 * si.kg / si.mol, 0]},
1517
)
@@ -354,6 +356,21 @@ def test_aero_data_density():
354356
# assert
355357
assert density == val[0]
356358

359+
@staticmethod
360+
@pytest.mark.parametrize(
361+
"ctor_arg", (AERO_DATA_CTOR_ARG_MINIMAL, AERO_DATA_CTOR_ARG_FULL)
362+
)
363+
def test_names(ctor_arg):
364+
# arrange
365+
sut = ppmc.AeroData(ctor_arg)
366+
names = sut.species
367+
368+
# assert
369+
for i, item in enumerate(ctor_arg):
370+
key = tuple(item.keys())[0]
371+
# pylint: disable=unsubscriptable-object
372+
assert names[i] == key
373+
357374
@staticmethod
358375
def test_ctor_error_on_nonunique_keys():
359376
# act
@@ -374,3 +391,18 @@ def test_n_source_uninitialized():
374391

375392
# assert
376393
assert str(exc_info.value) == "No sources defined."
394+
395+
@staticmethod
396+
def test_names_immutable():
397+
# arrange
398+
sut = ppmc.AeroData(
399+
AERO_DATA_CTOR_ARG_MINIMAL,
400+
)
401+
names = sut.species
402+
403+
# act
404+
with pytest.raises(TypeError) as exc_info:
405+
names[0] = "Z" # pylint: disable=unsupported-assignment-operation
406+
407+
# assert
408+
assert "not support item assignment" in str(exc_info.value)

tests/test_aero_dist.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import PyPartMC as ppmc
1414
from PyPartMC import si
1515

16-
from .test_aero_data import AERO_DATA_CTOR_ARG_MINIMAL
16+
from .test_aero_data import AERO_DATA_CTOR_ARG_FULL, AERO_DATA_CTOR_ARG_MINIMAL
1717
from .test_aero_mode import (
1818
AERO_MODE_CTOR_LOG_NORMAL,
1919
AERO_MODE_CTOR_LOG_NORMAL_COAGULATION,
@@ -172,6 +172,20 @@ def test_get_mode_is_a_copy(sut_minimal):
172172
# assert
173173
assert sut_minimal.mode(mode_idx).type != new_type
174174

175+
@staticmethod
176+
def test_get_source_names():
177+
# arrange
178+
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_FULL)
179+
_ = ppmc.AeroDist(aero_data, AERO_DIST_CTOR_ARG_AVERAGE)
180+
181+
# act
182+
sources = aero_data.sources
183+
184+
# assert
185+
keys = tuple(AERO_DIST_CTOR_ARG_AVERAGE[0].keys())
186+
for i, key in enumerate(keys):
187+
assert sources[i] == key # pylint: disable=unsubscriptable-object
188+
175189
@staticmethod
176190
def test_ctor_multimode_error_on_repeated_mode_names():
177191
# arrange

tests/test_gas_data.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,32 @@ def test_spec_by_name(ctor_arg):
6262

6363
# assert
6464
assert indices == list(range(len(ctor_arg)))
65+
66+
@staticmethod
67+
@pytest.mark.parametrize(
68+
"ctor_arg", (GAS_DATA_CTOR_ARG_MINIMAL, ("SO2", "NO2"), ("A", "B", "C"))
69+
)
70+
def test_species(ctor_arg):
71+
# arrange
72+
sut = ppmc.GasData(ctor_arg)
73+
74+
# act
75+
names = sut.species
76+
77+
# assert
78+
for i in range(len(sut)):
79+
# pylint: disable=unsubscriptable-object
80+
assert names[i] == ctor_arg[i]
81+
82+
@staticmethod
83+
def test_species_immutable():
84+
# arrange
85+
sut = ppmc.GasData(GAS_DATA_CTOR_ARG_MINIMAL)
86+
names = sut.species
87+
88+
# assert
89+
with pytest.raises(TypeError) as exc_info:
90+
names[0] = "Z" # pylint: disable=unsupported-assignment-operation
91+
92+
# assert
93+
assert "not support item assignment" in str(exc_info.value)

0 commit comments

Comments
 (0)