Skip to content

Commit 6e3457e

Browse files
authored
normalisation by dv and by zero-th moment as option in make_arbitrary_moment_product (#1308)
1 parent d4d393c commit 6e3457e

File tree

3 files changed

+178
-15
lines changed

3 files changed

+178
-15
lines changed

PySDM/products/size_spectral/arbitrary_moment.py

+49-11
Original file line numberDiff line numberDiff line change
@@ -6,36 +6,74 @@
66

77

88
def make_arbitrary_moment_product(**kwargs):
9+
"""returns a product class to be instantiated and passed to a builder"""
910
for arg in kwargs:
10-
assert arg in ("rank", "attr", "attr_unit")
11+
assert arg in (
12+
"rank",
13+
"attr",
14+
"attr_unit",
15+
"skip_division_by_m0",
16+
"skip_division_by_dv",
17+
)
1118

1219
class ArbitraryMoment(MomentProduct):
1320
def __init__(
14-
self, name=None, unit=f"({kwargs['attr_unit']})**{kwargs['rank']}"
21+
self,
22+
name=None,
23+
unit=f"({kwargs['attr_unit']})**{kwargs['rank']}"
24+
+ ("" if kwargs["skip_division_by_dv"] else " / m**3"),
1525
):
1626
super().__init__(name=name, unit=unit)
17-
self.attr = kwargs["attr"]
18-
self.rank = kwargs["rank"]
1927

20-
def _impl(self, **kwargs):
28+
def _impl(self, **_):
2129
self._download_moment_to_buffer(
22-
attr=self.attr, rank=self.rank, skip_division_by_m0=True
30+
attr=kwargs["attr"],
31+
rank=kwargs["rank"],
32+
skip_division_by_m0=kwargs["skip_division_by_m0"],
2333
)
34+
if not kwargs["skip_division_by_dv"]:
35+
self.buffer /= self.particulator.mesh.dv
2436
return self.buffer
2537

2638
return ArbitraryMoment
2739

2840

29-
ZerothMoment = make_arbitrary_moment_product(rank=0, attr="volume", attr_unit="m^3")
41+
ZerothMoment = make_arbitrary_moment_product(
42+
rank=0,
43+
attr="volume",
44+
attr_unit="m^3",
45+
skip_division_by_m0=True,
46+
skip_division_by_dv=True,
47+
)
3048

3149
VolumeFirstMoment = make_arbitrary_moment_product(
32-
rank=1, attr="volume", attr_unit="m^3"
50+
rank=1,
51+
attr="volume",
52+
attr_unit="m^3",
53+
skip_division_by_m0=True,
54+
skip_division_by_dv=True,
3355
)
3456

3557
VolumeSecondMoment = make_arbitrary_moment_product(
36-
rank=2, attr="volume", attr_unit="m^3"
58+
rank=2,
59+
attr="volume",
60+
attr_unit="m^3",
61+
skip_division_by_m0=True,
62+
skip_division_by_dv=True,
3763
)
3864

39-
RadiusSixthMoment = make_arbitrary_moment_product(rank=6, attr="radius", attr_unit="m")
65+
RadiusSixthMoment = make_arbitrary_moment_product(
66+
rank=6,
67+
attr="radius",
68+
attr_unit="m",
69+
skip_division_by_m0=True,
70+
skip_division_by_dv=True,
71+
)
4072

41-
RadiusFirstMoment = make_arbitrary_moment_product(rank=1, attr="radius", attr_unit="m")
73+
RadiusFirstMoment = make_arbitrary_moment_product(
74+
rank=1,
75+
attr="radius",
76+
attr_unit="m",
77+
skip_division_by_m0=True,
78+
skip_division_by_dv=True,
79+
)

examples/PySDM_examples/Bieli_et_al_2022/simulation.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,14 @@ def make_core(settings, coal_eff):
2626
adaptive=settings.adaptive,
2727
)
2828
builder.add_dynamic(collision)
29-
M0 = am.make_arbitrary_moment_product(rank=0, attr="volume", attr_unit="m^3")
30-
M1 = am.make_arbitrary_moment_product(rank=1, attr="volume", attr_unit="m^3")
31-
M2 = am.make_arbitrary_moment_product(rank=2, attr="volume", attr_unit="m^3")
32-
products = (M0(name="M0"), M1(name="M1"), M2(name="M2"))
29+
common_args = {
30+
"attr": "volume",
31+
"attr_unit": "m^3",
32+
"skip_division_by_m0": True,
33+
"skip_division_by_dv": True,
34+
}
35+
products = tuple(
36+
am.make_arbitrary_moment_product(rank=rank, **common_args)(name=f"M{rank}")
37+
for rank in range(3)
38+
)
3339
return builder.build(attributes, products)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
""" tests of the product-class-generating `make_arbitrary_moment_product` function """
2+
3+
import pytest
4+
import numpy as np
5+
6+
from PySDM.products.size_spectral.arbitrary_moment import make_arbitrary_moment_product
7+
from PySDM import Builder
8+
from PySDM.backends import CPU
9+
from PySDM.environments import Box
10+
from PySDM.physics import si
11+
12+
13+
class TestArbitraryMoment:
14+
"""groups tests to make [de]selecting easier"""
15+
16+
@staticmethod
17+
@pytest.mark.parametrize(
18+
"kwargs, expected_unit",
19+
(
20+
(
21+
{
22+
"rank": 0,
23+
"attr": "radius",
24+
"attr_unit": "m",
25+
"skip_division_by_m0": True,
26+
"skip_division_by_dv": True,
27+
},
28+
"1 dimensionless",
29+
),
30+
(
31+
{
32+
"rank": 1,
33+
"attr": "radius",
34+
"attr_unit": "m",
35+
"skip_division_by_m0": False,
36+
"skip_division_by_dv": True,
37+
},
38+
"1 meter",
39+
),
40+
(
41+
{
42+
"rank": 6,
43+
"attr": "radius",
44+
"attr_unit": "m",
45+
"skip_division_by_m0": True,
46+
"skip_division_by_dv": True,
47+
},
48+
"1 meter ** 6",
49+
),
50+
(
51+
{
52+
"rank": 1,
53+
"attr": "water mass",
54+
"attr_unit": "kg",
55+
"skip_division_by_m0": False,
56+
"skip_division_by_dv": True,
57+
},
58+
"1 kilogram",
59+
),
60+
(
61+
{
62+
"rank": 1,
63+
"attr": "water mass",
64+
"attr_unit": "kg",
65+
"skip_division_by_m0": False,
66+
"skip_division_by_dv": True,
67+
},
68+
"1 kilogram",
69+
),
70+
(
71+
{
72+
"rank": 1,
73+
"attr": "water mass",
74+
"attr_unit": "kg",
75+
"skip_division_by_m0": False,
76+
"skip_division_by_dv": False,
77+
},
78+
"1.0 kilogram / meter ** 3",
79+
),
80+
),
81+
)
82+
def test_unit(kwargs, expected_unit):
83+
"""tests if the product unit respects arguments (incl. division by dv)"""
84+
# arrange
85+
product_class = make_arbitrary_moment_product(**kwargs)
86+
87+
# act
88+
sut = product_class()
89+
90+
# assert
91+
assert sut.unit == expected_unit
92+
93+
@staticmethod
94+
@pytest.mark.parametrize("skip_division_by_dv", (True, False))
95+
def test_division_by_dv(skip_division_by_dv):
96+
"""tests, using a single-superdroplet setup, if the volume normalisation logic works"""
97+
# arrange
98+
dv = 666 * si.m**3
99+
product_class = make_arbitrary_moment_product(
100+
rank=1,
101+
attr="water mass",
102+
attr_unit="kg",
103+
skip_division_by_m0=False,
104+
skip_division_by_dv=skip_division_by_dv,
105+
)
106+
builder = Builder(n_sd=1, backend=CPU(), environment=Box(dv=dv, dt=np.nan))
107+
particulator = builder.build(
108+
attributes={
109+
k: np.ones(builder.particulator.n_sd)
110+
for k in ("multiplicity", "water mass")
111+
},
112+
products=(product_class(name="sut"),),
113+
)
114+
115+
# act
116+
values = particulator.products["sut"].get()
117+
118+
# assert
119+
assert values == 1 / (1 if skip_division_by_dv else dv)

0 commit comments

Comments
 (0)