Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions PySDM/attributes/impl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
from .maximum_attribute import MaximumAttribute
from .attribute_registry import register_attribute, get_attribute_class
from .intensive_attribute import IntensiveAttribute
from .temperature_variation_option_attribute import TemperatureVariationOptionAttribute
18 changes: 18 additions & 0 deletions PySDM/attributes/impl/temperature_variation_option_attribute.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"""common code for attributes offering an option to neglect temperature variation,
intended for use with Parcel environment only"""


class TemperatureVariationOptionAttribute: # pylint: disable=too-few-public-methods
"""base class"""

def __init__(self, builder, neglect_temperature_variations: bool):
if neglect_temperature_variations:
assert builder.particulator.environment.mesh.dimension == 0
self.neglect_temperature_variations = neglect_temperature_variations
self.initial_temperature = (
builder.particulator.Storage.from_ndarray(
builder.particulator.environment["T"].to_ndarray()
)
if neglect_temperature_variations
else None
)
4 changes: 2 additions & 2 deletions PySDM/attributes/physics/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"""

from .area import Area
from .critical_supersaturation import CriticalSupersaturation
from .critical_saturation import CriticalSaturation
from .critical_volume import CriticalVolume, WetToCriticalVolumeRatio
from .dry_radius import DryRadius
from .dry_volume import DryVolume
from .equilibrium_supersaturation import EquilibriumSupersaturation
from .equilibrium_saturation import EquilibriumSaturation
from .heat import Heat
from .hygroscopicity import Kappa, KappaTimesDryVolume
from .water_mass import SignedWaterMass
Expand Down
54 changes: 54 additions & 0 deletions PySDM/attributes/physics/critical_saturation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""
kappa-Koehler critical saturation calculated for either initial or actual environment temperature
"""

from PySDM.attributes.impl import (
DerivedAttribute,
register_attribute,
TemperatureVariationOptionAttribute,
)


@register_attribute()
class CriticalSaturation(DerivedAttribute, TemperatureVariationOptionAttribute):
def __init__(self, builder, neglect_temperature_variations=False):
assert builder.particulator.mesh.dimension == 0

self.v_crit = builder.get_attribute("critical volume")
self.v_dry = builder.get_attribute("dry volume")
self.kappa = builder.get_attribute("kappa")
self.f_org = builder.get_attribute("dry volume organic fraction")
TemperatureVariationOptionAttribute.__init__(
self, builder, neglect_temperature_variations
)
DerivedAttribute.__init__(
self,
builder=builder,
name="critical saturation",
dependencies=(self.v_crit, self.kappa, self.v_dry, self.f_org),
)

def recalculate(self):
temperature = (
self.initial_temperature
if self.neglect_temperature_variations
else self.particulator.environment["T"]
)
r_cr = self.formulae.trivia.radius(self.v_crit.data.data)
rd3 = self.v_dry.data.data / self.formulae.constants.PI_4_3
sgm = self.formulae.surface_tension.sigma(
temperature.data,
self.v_crit.data.data,
self.v_dry.data.data,
self.f_org.data.data,
)

self.data.data[:] = self.formulae.hygroscopicity.RH_eq(
r_cr, T=temperature.data, kp=self.kappa.data.data, rd3=rd3, sgm=sgm
)


@register_attribute()
class CriticalSaturationNeglectingTemperatureVariations(CriticalSaturation):
def __init__(self, builder):
super().__init__(builder, neglect_temperature_variations=True)
37 changes: 0 additions & 37 deletions PySDM/attributes/physics/critical_supersaturation.py

This file was deleted.

61 changes: 52 additions & 9 deletions PySDM/attributes/physics/critical_volume.py
Original file line number Diff line number Diff line change
@@ -1,45 +1,88 @@
"""
critical wet volume (kappa-Koehler, computed using actual temperature)
critical wet volume (kappa-Koehler, computed using actual or initial temperature)
"""

from PySDM.attributes.impl import DerivedAttribute, register_attribute
from PySDM.attributes.impl import (
DerivedAttribute,
register_attribute,
TemperatureVariationOptionAttribute,
)


@register_attribute()
class CriticalVolume(DerivedAttribute):
def __init__(self, builder):
class CriticalVolume(DerivedAttribute, TemperatureVariationOptionAttribute):
def __init__(self, builder, neglect_temperature_variations=False):
self.cell_id = builder.get_attribute("cell id")
self.v_dry = builder.get_attribute("dry volume")
self.v_wet = builder.get_attribute("volume")
self.kappa = builder.get_attribute("kappa")
self.f_org = builder.get_attribute("dry volume organic fraction")
self.environment = builder.particulator.environment
self.particles = builder.particulator

dependencies = [self.v_dry, self.v_wet, self.cell_id]
super().__init__(builder, name="critical volume", dependencies=dependencies)
TemperatureVariationOptionAttribute.__init__(
self, builder, neglect_temperature_variations
)
DerivedAttribute.__init__(
self, builder, name="critical volume", dependencies=dependencies
)

def recalculate(self):
temperature = (
self.initial_temperature
if self.neglect_temperature_variations
else self.environment["T"]
)
self.particulator.backend.critical_volume(
v_cr=self.data,
kappa=self.kappa.get(),
f_org=self.f_org.get(),
v_dry=self.v_dry.get(),
v_wet=self.v_wet.get(),
T=self.environment["T"],
T=temperature,
cell=self.cell_id.get(),
)


@register_attribute()
class WetToCriticalVolumeRatio(DerivedAttribute):
class CriticalVolumeNeglectingTemperatureVariations(CriticalVolume):
def __init__(self, builder):
self.critical_volume = builder.get_attribute("critical volume")
super().__init__(builder, neglect_temperature_variations=True)


@register_attribute()
class WetToCriticalVolumeRatio(DerivedAttribute):
def __init__(
self,
builder,
neglect_temperature_variations=False,
name="wet to critical volume ratio",
):
self.critical_volume = builder.get_attribute(
"critical volume"
+ (
" neglecting temperature variations"
if neglect_temperature_variations
else ""
)
)
self.volume = builder.get_attribute("volume")
super().__init__(
builder,
name="wet to critical volume ratio",
name=name,
dependencies=(self.critical_volume, self.volume),
)

def recalculate(self):
self.data.ratio(self.volume.get(), self.critical_volume.get())


@register_attribute()
class WetToCriticalVolumeRatioNeglectingTemperatureVariations(WetToCriticalVolumeRatio):
def __init__(self, builder):
super().__init__(
builder,
neglect_temperature_variations=True,
name="wet to critical volume ratio neglecting temperature variations",
)
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""
kappa-Koehler equilibrium supersaturation calculated for actual environment temperature
kappa-Koehler equilibrium saturation calculated for actual environment temperature
"""

from PySDM.attributes.impl import DerivedAttribute, register_attribute


@register_attribute()
class EquilibriumSupersaturation(DerivedAttribute):
class EquilibriumSaturation(DerivedAttribute):
def __init__(self, builder):
self.r_wet = builder.get_attribute("radius")
self.v_wet = builder.get_attribute("volume")
Expand All @@ -16,7 +16,7 @@ def __init__(self, builder):

super().__init__(
builder=builder,
name="equilibrium supersaturation",
name="equilibrium saturation",
dependencies=(self.kappa, self.v_dry, self.f_org, self.r_wet),
)

Expand Down
4 changes: 2 additions & 2 deletions PySDM/environments/parcel.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Zero-dimensional adiabatic parcel framework
"""

from typing import List, Optional
from typing import List, Optional, Union

import numpy as np

Expand All @@ -25,7 +25,7 @@ def __init__(
p0: float,
initial_water_vapour_mixing_ratio: float,
T0: float,
w: [float, callable],
w: Union[float, callable],
z0: float = 0,
mixed_phase=False,
variables: Optional[List[str]] = None,
Expand Down
2 changes: 1 addition & 1 deletion PySDM/products/condensation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
from .activable_fraction import ActivableFraction
from .condensation_timestep import CondensationTimestepMax, CondensationTimestepMin
from .event_rates import ActivatingRate, DeactivatingRate, RipeningRate
from .peak_supersaturation import PeakSupersaturation
from .peak_saturation import PeakSaturation
23 changes: 17 additions & 6 deletions PySDM/products/condensation/activable_fraction.py
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
"""
fraction of particles with critical supersaturation lower than a given supersaturation
fraction of particles with critical saturation lower than a given saturation
(passed as keyword argument while calling `get()`)
"""

import numpy as np
from PySDM.products.impl import MomentProduct, register_product


@register_product()
class ActivableFraction(MomentProduct):
def __init__(self, unit="dimensionless", name=None):
def __init__(
self, unit="dimensionless", name=None, filter_attr="critical saturation"
):
super().__init__(name=name, unit=unit)
self.filter_attr = filter_attr

def register(self, builder):
super().register(builder)
builder.request_attribute("critical supersaturation")
builder.request_attribute(self.filter_attr)

def _impl(self, **kwargs):
s_max = kwargs["S_max"]
if self.filter_attr.startswith("critical saturation"):
s_max = kwargs["S_max"]
assert not np.isfinite(s_max) or 0 < s_max < 1.1
filter_range = (0, s_max)
elif self.filter_attr.startswith("wet to critical volume ratio"):
filter_range = (1, np.inf)
else:
assert False

Check warning on line 30 in PySDM/products/condensation/activable_fraction.py

View check run for this annotation

Codecov / codecov/patch

PySDM/products/condensation/activable_fraction.py#L30

Added line #L30 was not covered by tests
self._download_moment_to_buffer(
attr="volume",
rank=0,
filter_range=(0, 1 + s_max / 100),
filter_attr="critical supersaturation",
filter_range=filter_range,
filter_attr=self.filter_attr,
)
frac = self.buffer.copy()
self._download_moment_to_buffer(attr="volume", rank=0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
highest supersaturation encountered while solving for condensation/evaporation (takes into account
highest saturation encountered while solving for condensation/evaporation (takes into account
substeps thus values might differ from ambient saturation reported via
`PySDM.products.ambient_thermodynamics.ambient_relative_humidity.AmbientRelativeHumidity`;
fetching a value resets the maximum value)
Expand All @@ -11,7 +11,7 @@


@register_product()
class PeakSupersaturation(Product):
class PeakSaturation(Product):
def __init__(self, unit="dimensionless", name=None):
super().__init__(unit=unit, name=name)
self.condensation = None
Expand All @@ -28,8 +28,8 @@ def register(self, builder):
self.RH_max = np.full_like(self.buffer, np.nan)

def _impl(self, **kwargs):
self.buffer[:] = self.RH_max[:] - 1
self.RH_max[:] = -1
self.buffer[:] = self.RH_max[:]
self.RH_max[:] = 0
return self.buffer

def notify(self):
Expand Down
8 changes: 4 additions & 4 deletions docs/markdown/pysdm_landing.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ causes a subset of particles to activate into cloud droplets.
Results of the simulation are plotted against vertical
[`ParcelDisplacement`](https://open-atmos.github.io/PySDM/PySDM/products/housekeeping/parcel_displacement.html)
and depict the evolution of
[`PeakSupersaturation`](https://open-atmos.github.io/PySDM/PySDM/products/condensation/peak_supersaturation.html),
[`PeakSaturation`](https://open-atmos.github.io/PySDM/PySDM/products/condensation/peak_saturation.html),
[`EffectiveRadius`](https://open-atmos.github.io/PySDM/PySDM/products/size_spectral/effective_radius.html),
[`ParticleConcentration`](https://open-atmos.github.io/PySDM/PySDM/products/size_spectral/particle_concentration.html#ParticleConcentration)
and the
Expand Down Expand Up @@ -367,7 +367,7 @@ attributes["kappa times dry volume"] = kappa * v_dry
attributes["volume"] = formulae.trivia.volume(radius=r_wet)

particulator = builder.build(attributes, products=[
products.PeakSupersaturation(name="S_max", unit="%"),
products.PeakSaturation(name="S_max_percent", unit="%"),
products.EffectiveRadius(name="r_eff", unit="um", radius_range=cloud_range),
products.ParticleConcentration(name="n_c_cm3", unit="cm^-3", radius_range=cloud_range),
products.WaterMixingRatio(name="liquid water mixing ratio", unit="g/kg", radius_range=cloud_range),
Expand Down Expand Up @@ -455,7 +455,7 @@ attributes = py.dict(pyargs( ...
));

particulator = builder.build(attributes, py.list({ ...
products.PeakSupersaturation(pyargs('name', 'S_max', 'unit', '%')), ...
products.PeakSaturation(pyargs('name', 'S_max_percent', 'unit', '%')), ...
products.EffectiveRadius(pyargs('name', 'r_eff', 'unit', 'um', 'radius_range', cloud_range)), ...
products.ParticleConcentration(pyargs('name', 'n_c_cm3', 'unit', 'cm^-3', 'radius_range', cloud_range)), ...
products.WaterMixingRatio(pyargs('name', 'liquid water mixing ratio', 'unit', 'g/kg', 'radius_range', cloud_range)) ...
Expand Down Expand Up @@ -549,7 +549,7 @@ attributes = {
}

particulator = builder.build(attributes, products=[
products.PeakSupersaturation(name='S_max', unit='%'),
products.PeakSaturation(name='S_max_percent', unit='%'),
products.EffectiveRadius(name='r_eff', unit='um', radius_range=cloud_range),
products.ParticleConcentration(name='n_c_cm3', unit='cm^-3', radius_range=cloud_range),
products.WaterMixingRatio(name='liquid water mixing ratio', unit='g/kg', radius_range=cloud_range),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@
}
],
"source": [
"for drop_id, Scrit in enumerate(output.attributes['critical supersaturation']):\n",
"for drop_id, Scrit in enumerate(output.attributes['critical saturation']):\n",
" if drop_id < n_sd_per_mode:\n",
" pyplot.plot(\n",
" np.asarray(Scrit) - 1,\n",
Expand Down
Loading
Loading