Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

support for AeroMode instantiation with sampled mode type (incl. fixes in spec file logic) + sampled and mono mode types coverage in unit tests (in test_aero_[mode,state,dist].py) #357

Merged
merged 18 commits into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/aero_mode.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,24 @@ struct AeroMode {
throw std::runtime_error("mass_frac value must be a list of single-element dicts");
if (!InputJSONResource::unique_keys(mass_frac))
throw std::runtime_error("mass_frac keys must be unique");
if (mode["mode_type"] == "sampled") {
if (mode.find("size_dist") == mode.end())
throw std::runtime_error("size_dist key must be set for mode_type=sampled");
auto sd = mode["size_dist"];
if (
sd.size() != 2 ||
!sd[0].is_object() ||
sd[0].size() != 1 ||
sd[1].size() != 1 ||
sd[0].find("num_conc") == sd[0].end() ||
sd[1].find("diam") == sd[1].end()
)
throw std::runtime_error("size_dist value must be an iterable of two single-element dicts (first with 'num_conc', second with 'diam' as keys)");
auto num_conc = *sd[0].find("num_conc");
auto diam = *sd[1].find("diam");
if (diam.size() != num_conc.size() + 1)
throw std::runtime_error("size_dist['num_conc'] must have len(size_dist['diam'])-1 elements");
}
}

static auto get_num_conc(const AeroMode &self){
Expand Down Expand Up @@ -291,7 +309,7 @@ struct AeroMode {
int type;
f_aero_mode_get_type(self.ptr.f_arg(), &type);

if (type < 0 || (unsigned int)type >= AeroMode::types().size())
if (type <= 0 || (unsigned int)type > AeroMode::types().size())
throw std::logic_error("Unknown mode type.");

return AeroMode::types()[type - 1];
Expand Down
2 changes: 2 additions & 0 deletions src/spec_file_pypartmc.F90
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ subroutine spec_file_read_real_named_array(file, max_lines, names, vals)
n_rows = max_lines
end if

if (allocated(names)) deallocate(names)
if (allocated(vals)) deallocate(vals)
allocate(names(n_rows))
allocate(vals(n_rows, n_cols))
allocate(vals_row(max(1, n_cols)))
Expand Down
19 changes: 19 additions & 0 deletions tests/test_aero_dist.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,3 +217,22 @@ def test_ctor_error_on_repeated_massfrac_keys():

# assert
assert str(exc_info.value) == "mass_frac keys must be unique"

@staticmethod
def test_ctor_sampled_mode():
# arrange
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL)
ctor_arg = copy.deepcopy(AERO_DIST_CTOR_ARG_MINIMAL)
ctor_arg[0]["test_mode"]["mode_type"] = "sampled"
ctor_arg[0]["test_mode"]["size_dist"] = [
{"num_conc": [1, 2, 3]},
{"diam": [1, 2, 3, 4]},
]

# act
sut = ppmc.AeroDist(aero_data, ctor_arg)

# assert
assert sut.mode(0).num_conc == sum(
ctor_arg[0]["test_mode"]["size_dist"][0]["num_conc"]
)
96 changes: 96 additions & 0 deletions tests/test_aero_mode.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,3 +289,99 @@ def test_segfault_case(): # TODO #319
)
print(fishy_ctor_arg)
ppmc.AeroMode(aero_data, fishy_ctor_arg)

@staticmethod
@pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348")
def test_sampled_without_size_dist():
# arrange
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL)
fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL)
fishy_ctor_arg["test_mode"]["mode_type"] = "sampled"

# act
with pytest.raises(Exception) as exc_info:
ppmc.AeroMode(aero_data, fishy_ctor_arg)

# assert
assert str(exc_info.value) == "size_dist key must be set for mode_type=sampled"

@staticmethod
@pytest.mark.parametrize(
"fishy",
(
None,
[],
[{}, {}, {}],
[{}, []],
[{"diam": None}, {}],
[{"num_conc": None}, {}],
[{"diam": None, "": None}, {}],
[{"num_conc": None, "": None}, {}],
),
)
@pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348")
def test_sampled_with_fishy_size_dist(fishy):
# arrange
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL)
fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL)
fishy_ctor_arg["test_mode"]["mode_type"] = "sampled"
fishy_ctor_arg["test_mode"]["size_dist"] = fishy

# act
with pytest.raises(Exception) as exc_info:
ppmc.AeroMode(aero_data, fishy_ctor_arg)

# assert
assert (
str(exc_info.value)
== "size_dist value must be an iterable of two single-element dicts"
+ " (first with 'num_conc', second with 'diam' as keys)"
)

@staticmethod
@pytest.mark.skipif(platform.machine() == "arm64", reason="TODO #348")
def test_sampled_with_diam_of_different_len_than_num_conc():
# arrange
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL)
fishy_ctor_arg = copy.deepcopy(AERO_MODE_CTOR_LOG_NORMAL)
fishy_ctor_arg["test_mode"]["mode_type"] = "sampled"
fishy_ctor_arg["test_mode"]["size_dist"] = [
{"num_conc": [1, 2, 3]},
{"diam": [1, 2, 3]},
]

# act
with pytest.raises(Exception) as exc_info:
ppmc.AeroMode(aero_data, fishy_ctor_arg)

# assert
assert (
str(exc_info.value)
== "size_dist['num_conc'] must have len(size_dist['diam'])-1 elements"
)

@staticmethod
def test_sampled():
# arrange
aero_data = ppmc.AeroData(AERO_DATA_CTOR_ARG_MINIMAL)

# act
num_concs = [100, 200, 300]
sut = ppmc.AeroMode(
aero_data,
{
"test_mode": {
"mass_frac": [{"H2O": [1]}],
"diam_type": "geometric",
"mode_type": "sampled",
"size_dist": [
{"num_conc": num_concs},
{"diam": [1, 2, 3, 4]},
],
}
},
)

# assert
assert sut.type == "sampled"
assert sut.num_conc == np.sum(num_concs)
Loading