From c121459257dda7b5bb6a2f7a8d19297521f99907 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Mon, 18 Oct 2021 00:41:54 -0500 Subject: [PATCH 1/2] addinf critical supersaturation attribute and activable fraction product --- PySDM/attributes/impl/mapper.py | 4 ++- .../physics/critical_supersaturation.py | 36 +++++++++++++++++++ .../dynamics/condensation/__init__.py | 1 + .../condensation/activable_fraction.py | 29 +++++++++++++++ 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 PySDM/attributes/physics/critical_supersaturation.py create mode 100644 PySDM/products/dynamics/condensation/activable_fraction.py diff --git a/PySDM/attributes/impl/mapper.py b/PySDM/attributes/impl/mapper.py index e330ce317..7c5a4f199 100644 --- a/PySDM/attributes/impl/mapper.py +++ b/PySDM/attributes/impl/mapper.py @@ -7,6 +7,7 @@ from PySDM.attributes.ice import FreezingTemperature, ImmersedSurfaceArea from PySDM.attributes.numerics import CellID, CellOrigin, PositionInCell from PySDM.attributes.chemistry import MoleAmount, Concentration, pH, HydrogenIonConcentration +from PySDM.attributes.physics.critical_supersaturation import CriticalSupersaturation from PySDM.physics.aqueous_chemistry.support import AQUEOUS_COMPOUNDS from PySDM.physics.surface_tension import Constant @@ -43,7 +44,8 @@ 'pH': lambda _: pH, 'conc_H': lambda _: HydrogenIonConcentration, 'freezing temperature': lambda _: FreezingTemperature, - 'immersed surface area': lambda _: ImmersedSurfaceArea + 'immersed surface area': lambda _: ImmersedSurfaceArea, + 'critical supersaturation': lambda _: CriticalSupersaturation } diff --git a/PySDM/attributes/physics/critical_supersaturation.py b/PySDM/attributes/physics/critical_supersaturation.py new file mode 100644 index 000000000..0b43d2041 --- /dev/null +++ b/PySDM/attributes/physics/critical_supersaturation.py @@ -0,0 +1,36 @@ +from PySDM.attributes.impl.derived_attribute import DerivedAttribute +from PySDM.physics import constants as const + + +class CriticalSupersaturation(DerivedAttribute): + def __init__(self, builder): + 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') + + super().__init__( + builder=builder, + name='critical supersaturation', + dependencies=(self.v_crit, self.kappa, self.v_dry, self.f_org) + ) + self.formulae = builder.particulator.formulae + self.environment = builder.particulator.environment + + def recalculate(self): + if len(self.environment['T']) != 1: + raise NotImplementedError() + T = self.environment['T'][0] + r_cr = self.formulae.trivia.radius(self.v_crit.data.data) + rd3 = self.v_dry.data.data / const.pi_4_3 + sgm = self.formulae.surface_tension.sigma( + T, 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=T, + kp=self.kappa.data.data, + rd3=rd3, + sgm=sgm + ) diff --git a/PySDM/products/dynamics/condensation/__init__.py b/PySDM/products/dynamics/condensation/__init__.py index c467f7536..067cd4196 100644 --- a/PySDM/products/dynamics/condensation/__init__.py +++ b/PySDM/products/dynamics/condensation/__init__.py @@ -1,3 +1,4 @@ from .condensation_timestep import CondensationTimestepMin, CondensationTimestepMax from .event_rates import RipeningRate, ActivatingRate, DeactivatingRate from .peak_supersaturation import PeakSupersaturation +from .activable_fraction import ActivableFraction diff --git a/PySDM/products/dynamics/condensation/activable_fraction.py b/PySDM/products/dynamics/condensation/activable_fraction.py new file mode 100644 index 000000000..2c1fb42a8 --- /dev/null +++ b/PySDM/products/dynamics/condensation/activable_fraction.py @@ -0,0 +1,29 @@ +from ...product import MomentProduct + + +class ActivableFraction(MomentProduct): + def __init__(self): + super().__init__( + name="activable fraction", + unit="1", + description="" + ) + + def register(self, builder): + super().register(builder) + builder.request_attribute('critical supersaturation') + + def get(self, S_max): + self.download_moment_to_buffer( + 'volume', + rank=0, + filter_range=(0, 1 + S_max / 100), + filter_attr='critical supersaturation' + ) + frac = self.buffer.copy() + self.download_moment_to_buffer( + 'volume', + rank=0 + ) + frac /= self.buffer + return frac From e0417afd53164d6fb57408b5ba4b99e51015d225 Mon Sep 17 00:00:00 2001 From: Sylwester Arabas Date: Mon, 18 Oct 2021 10:01:22 -0500 Subject: [PATCH 2/2] cleanups; adding unit test for critical critical supersaturation --- PySDM/attributes/impl/mapper.py | 10 ++++-- PySDM/attributes/physics/__init__.py | 1 + PySDM/attributes/physics/dry_radius.py | 1 - .../dynamics/freezing/ice_water_content.py | 5 +-- .../physics/test_critical_supersaturation.py | 35 +++++++++++++++++++ 5 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 tests/unit_tests/attributes/physics/test_critical_supersaturation.py diff --git a/PySDM/attributes/impl/mapper.py b/PySDM/attributes/impl/mapper.py index 7c5a4f199..943e6a3a4 100644 --- a/PySDM/attributes/impl/mapper.py +++ b/PySDM/attributes/impl/mapper.py @@ -16,14 +16,20 @@ 'volume': lambda _: Volume, 'dry volume organic': lambda dynamics: ( DummyAttributeImpl('dry volume organic') - if isinstance(dynamics['Condensation'].particulator.formulae.surface_tension, Constant) + if 'Condensation' in dynamics and isinstance( + dynamics['Condensation'].particulator.formulae.surface_tension, + Constant + ) else DryVolumeOrganic ), 'dry volume': lambda dynamics: DryVolumeDynamic if 'AqueousChemistry' in dynamics else DryVolume, 'dry volume organic fraction': lambda dynamics: ( DummyAttributeImpl('dry volume organic fraction') - if isinstance(dynamics['Condensation'].particulator.formulae.surface_tension, Constant) + if 'Condensation' in dynamics and isinstance( + dynamics['Condensation'].particulator.formulae.surface_tension, + Constant + ) else OrganicFraction ), 'kappa times dry volume': lambda _: KappaTimesDryVolume, diff --git a/PySDM/attributes/physics/__init__.py b/PySDM/attributes/physics/__init__.py index 2bb51a763..e9d8a0aad 100644 --- a/PySDM/attributes/physics/__init__.py +++ b/PySDM/attributes/physics/__init__.py @@ -7,3 +7,4 @@ from .temperature import Temperature from .heat import Heat from .critical_volume import CriticalVolume +from .critical_supersaturation import CriticalSupersaturation diff --git a/PySDM/attributes/physics/dry_radius.py b/PySDM/attributes/physics/dry_radius.py index 13d3aa938..420b28479 100644 --- a/PySDM/attributes/physics/dry_radius.py +++ b/PySDM/attributes/physics/dry_radius.py @@ -9,6 +9,5 @@ def __init__(self, builder): super().__init__(builder, name='dry radius', dependencies=dependencies) def recalculate(self): - self.data.idx = self.volume_dry.data.idx self.data.product(self.volume_dry.get(), 1/const.pi_4_3) self.data **= 1/3 diff --git a/PySDM/products/dynamics/freezing/ice_water_content.py b/PySDM/products/dynamics/freezing/ice_water_content.py index cc8e43312..3057f6a09 100644 --- a/PySDM/products/dynamics/freezing/ice_water_content.py +++ b/PySDM/products/dynamics/freezing/ice_water_content.py @@ -1,6 +1,7 @@ +import numpy as np from ...product import MomentProduct from ....physics import constants as const -import numpy as np + class IceWaterContent(MomentProduct): @@ -24,4 +25,4 @@ def get(self): self.download_to_buffer(self.particulator.environment['rhod']) result[:] /= self.buffer const.convert_to(result, const.si.gram / const.si.kilogram) - return result \ No newline at end of file + return result diff --git a/tests/unit_tests/attributes/physics/test_critical_supersaturation.py b/tests/unit_tests/attributes/physics/test_critical_supersaturation.py new file mode 100644 index 000000000..c23da15fe --- /dev/null +++ b/tests/unit_tests/attributes/physics/test_critical_supersaturation.py @@ -0,0 +1,35 @@ +import numpy as np +from PySDM.products.dynamics.condensation import ActivableFraction +from PySDM import Builder +from PySDM.backends import CPU +from PySDM.environments import Box +from PySDM.physics import si + + +def test_critical_supersaturation(): + # arrange + T = 300 * si.K + n_sd = 100 + S_max = .01 + vdry = np.linspace(.001, 1, n_sd) * si.um**3 + + builder = Builder(n_sd=n_sd, backend=CPU()) + env = Box(dt=np.nan, dv=np.nan) + builder.set_environment(env) + env['T'] = T + particulator = builder.build( + attributes={ + 'n': np.ones(n_sd), + 'volume': np.linspace(.01, 10, n_sd) * si.um**3, + 'dry volume': vdry, + 'kappa times dry volume': .9 * vdry, + 'dry volume organic': np.zeros(n_sd) + }, + products=[ActivableFraction()] + ) + + # act + AF = particulator.products['activable fraction'].get(S_max) + + # assert + assert 0 < AF < 1