Skip to content

Commit

Permalink
replacing optional (sic!) normalisation by dv with meaningful specifi…
Browse files Browse the repository at this point in the history
…c=True/False flag in ParticleSizeSpectrum product (#1343)
  • Loading branch information
slayoo authored Jun 19, 2024
1 parent 7bc13b1 commit 0bfc98a
Show file tree
Hide file tree
Showing 9 changed files with 7,640 additions and 57 deletions.
2 changes: 1 addition & 1 deletion PySDM/products/size_spectral/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
ActivatedParticleSpecificConcentration,
)
from .particle_size_spectrum import (
ParticleSizeSpectrumPerMass,
ParticleSizeSpectrumPerMassOfDryAir,
ParticleSizeSpectrumPerVolume,
)
from .particle_volume_versus_radius_logarithm_spectrum import (
Expand Down
28 changes: 14 additions & 14 deletions PySDM/products/size_spectral/particle_size_spectrum.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
wet radius-binned particle size spectra (per mass of dry air or per volume of air)
wet- or dry-radius binned particle size spectra (per mass of dry air or per volume of air)
"""

from abc import ABC
Expand All @@ -10,19 +10,17 @@


class ParticleSizeSpectrum(SpectrumMomentProduct, ABC):
def __init__(
self, *, radius_bins_edges, name, unit, dry=False, normalise_by_dv=False
):
def __init__(self, *, radius_bins_edges, name, unit, dry=False, specific=False):
self.volume_attr = "dry volume" if dry else "volume"
self.radius_bins_edges = radius_bins_edges
self.normalise_by_dv = normalise_by_dv
self.specific = specific
super().__init__(name=name, unit=unit, attr_unit="m^3")

def register(self, builder):
builder.request_attribute(self.volume_attr)

volume_bins_edges = builder.particulator.formulae.trivia.volume(
self.radius_bins_edges
np.asarray(self.radius_bins_edges)
)
self.attr_bins_edges = builder.particulator.backend.Storage.from_ndarray(
volume_bins_edges
Expand All @@ -42,26 +40,28 @@ def _impl(self, **kwargs):
self._download_spectrum_moment_to_buffer(rank=0, bin_number=i)
vals[:, i] = self.buffer.ravel()

if self.normalise_by_dv:
vals[:] /= self.particulator.mesh.dv
vals[:] /= self.particulator.mesh.dv

if self.specific:
self._download_to_buffer(self.particulator.environment["rhod"])

self._download_to_buffer(self.particulator.environment["rhod"])
rhod = self.buffer.ravel()
for i in range(len(self.attr_bins_edges) - 1):
dr = self.formulae.trivia.radius(
volume=self.attr_bins_edges[i + 1]
) - self.formulae.trivia.radius(volume=self.attr_bins_edges[i])
vals[:, i] /= rhod * dr
vals[:, i] /= dr
if self.specific:
vals[:, i] /= self.buffer.ravel()

return np.squeeze(vals.reshape(self.shape))


class ParticleSizeSpectrumPerMass(ParticleSizeSpectrum):
class ParticleSizeSpectrumPerMassOfDryAir(ParticleSizeSpectrum):
def __init__(self, radius_bins_edges, dry=False, name=None, unit="kg^-1 m^-1"):
super().__init__(
radius_bins_edges=radius_bins_edges,
dry=dry,
normalise_by_dv=True,
specific=True,
name=name,
unit=unit,
)
Expand All @@ -72,7 +72,7 @@ def __init__(self, radius_bins_edges, dry=False, name=None, unit="m^-3 m^-1"):
super().__init__(
radius_bins_edges=radius_bins_edges,
dry=dry,
normalise_by_dv=False,
specific=False,
name=name,
unit=unit,
)
3,614 changes: 3,598 additions & 16 deletions examples/PySDM_examples/Lowe_et_al_2019/fig_2.ipynb

Large diffs are not rendered by default.

3,929 changes: 3,909 additions & 20 deletions examples/PySDM_examples/Pyrcel/example_basic_run.ipynb

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion examples/PySDM_examples/Pyrcel/simulation.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ def __init__(
)
n_sd = sum(settings.n_sd_per_mode)
builder = Builder(
n_sd=n_sd, backend=CPU(formulae=settings.formulae), environment=env
n_sd=n_sd,
backend=CPU(
formulae=settings.formulae, override_jit_flags={"parallel": False}
),
environment=env,
)
builder.add_dynamic(AmbientThermodynamics())
builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ def make_default_product_collection(settings):
cloud_range = (settings.aerosol_radius_threshold, settings.drizzle_radius_threshold)
products = [
# Note: consider better radius_bins_edges
PySDM_products.ParticleSizeSpectrumPerMass(
PySDM_products.ParticleSizeSpectrumPerMassOfDryAir(
name="Particles Wet Size Spectrum",
unit="mg^-1 um^-1",
radius_bins_edges=settings.r_bins_edges,
),
PySDM_products.ParticleSizeSpectrumPerMass(
PySDM_products.ParticleSizeSpectrumPerMassOfDryAir(
name="Particles Dry Size Spectrum",
unit="mg^-1 um^-1",
radius_bins_edges=settings.r_bins_edges,
Expand Down
5 changes: 4 additions & 1 deletion tests/unit_tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ def backend_class(request):
return request.param


@pytest.fixture(params=(CPU(), GPU()), scope="session")
@pytest.fixture(
params=(pytest.param(CPU(), id="CPU"), pytest.param(GPU(), id="GPU")),
scope="session",
)
def backend_instance(request):
return request.param
4 changes: 2 additions & 2 deletions tests/unit_tests/products/test_impl.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
MeanVolumeRadius,
NumberSizeSpectrum,
ParcelLiquidWaterPath,
ParticleSizeSpectrumPerMass,
ParticleSizeSpectrumPerMassOfDryAir,
ParticleSizeSpectrumPerVolume,
ParticleVolumeVersusRadiusLogarithmSpectrum,
RadiusBinnedNumberAveragedTerminalVelocity,
Expand All @@ -42,7 +42,7 @@
AqueousMassSpectrum: {"key": "S_VI", "dry_radius_bins_edges": (0, np.inf)},
AqueousMoleFraction: {"key": "S_VI"},
TotalDryMassMixingRatio: {"density": 1},
ParticleSizeSpectrumPerMass: {"radius_bins_edges": (0, np.inf)},
ParticleSizeSpectrumPerMassOfDryAir: {"radius_bins_edges": (0, np.inf)},
GaseousMoleFraction: {"key": "O3"},
FreezableSpecificConcentration: {"temperature_bins_edges": (0, 300)},
DynamicWallTime: {"dynamic": "Condensation"},
Expand Down
105 changes: 105 additions & 0 deletions tests/unit_tests/products/test_particle_size_spectrum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
"""
tests the ParticleSizeSpectrum product against per-mass/per-volume and dry-/wet-size choices
"""

import pytest
import numpy as np
from PySDM import Builder
from PySDM.physics import si
from PySDM.environments import Box
from PySDM.products import (
ParticleSizeSpectrumPerMassOfDryAir,
ParticleSizeSpectrumPerVolume,
)


class TestParticleSizeSpectrum:
@staticmethod
@pytest.mark.parametrize(
"product_class, specific, unit",
(
(ParticleSizeSpectrumPerVolume, False, "1.0 / meter ** 4"),
(ParticleSizeSpectrumPerMassOfDryAir, True, "1.0 / kilogram / meter"),
),
)
def test_specific_flag(product_class, specific, unit, backend_instance):
"""checks if the reported concentration is correctly normalised per
volume or mass of air, and per bin size"""
# arrange
dv = 666 * si.m**3
multiplicity = 1000
rhod = 44 * si.kg / si.m**3
min_size = 0
max_size = 1 * si.mm

n_sd = 1
box = Box(dt=np.nan, dv=dv)
builder = Builder(n_sd=n_sd, backend=backend_instance, environment=box)
sut = product_class(radius_bins_edges=(min_size, max_size))
builder.build(
products=(sut,),
attributes={
"multiplicity": np.ones(n_sd) * multiplicity,
"water mass": np.ones(n_sd) * si.ug,
},
)
box["rhod"] = rhod

# act
actual = sut.get()

# assert
assert sut.unit == unit
assert sut.specific == specific
np.testing.assert_approx_equal(
actual=actual,
desired=multiplicity
/ dv
/ (max_size - min_size)
/ (rhod if specific else 1),
significant=10,
)

@staticmethod
@pytest.mark.parametrize(
"product_class",
(ParticleSizeSpectrumPerVolume, ParticleSizeSpectrumPerMassOfDryAir),
)
@pytest.mark.parametrize("dry", (True, False))
def test_dry_flag(product_class, dry, backend_instance):
"""checks if dry or wet size attribute is correctly picked for moment calculation"""
# arrange
n_sd = 1
dv = 666 * si.m**3
min_size = 0
max_size = 1 * si.mm
multiplicity = 100
rhod = 44 * si.kg / si.m**3
wet_volume = 1 * si.um**3
dry_volume = 0.01 * si.um**3

box = Box(dt=np.nan, dv=dv)
builder = Builder(n_sd=n_sd, backend=backend_instance, environment=box)
sut = product_class(radius_bins_edges=(min_size, max_size), dry=dry)
builder.build(
products=(sut,),
attributes={
"multiplicity": np.ones(n_sd) * multiplicity,
"volume": np.ones(n_sd) * (np.nan if dry else wet_volume),
"dry volume": np.ones(n_sd) * (dry_volume if dry else np.nan),
},
)
box["rhod"] = rhod

# act
actual = sut.get()

# assert
np.testing.assert_approx_equal(
actual=actual,
desired=multiplicity
/ dv
/ (max_size - min_size)
/ (rhod if sut.specific else 1),
significant=10,
)

0 comments on commit 0bfc98a

Please sign in to comment.