diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cee90df82..c34321768 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -109,7 +109,7 @@ jobs: matrix: platform: [ubuntu-24.04, macos-13, macos-14, windows-latest] python-version: ["3.9", "3.12"] - test-suite: ["unit_tests/!(dynamics)", "unit_tests/dynamics/!(collisions)", "unit_tests/dynamics/collisions", "smoke_tests/no_env", "smoke_tests/box", "smoke_tests/parcel_a", "smoke_tests/parcel_b", "smoke_tests/parcel_c", "smoke_tests/parcel_d", "smoke_tests/kinematic_1d", "smoke_tests/kinematic_2d", "tutorials_tests"] + test-suite: ["unit_tests/!(dynamics)", "unit_tests/dynamics/!(collisions)", "unit_tests/dynamics/collisions", "smoke_tests/no_env", "smoke_tests/box", "smoke_tests/parcel_a", "smoke_tests/parcel_b", "smoke_tests/parcel_c", "smoke_tests/parcel_d", "smoke_tests/kinematic_1d", "smoke_tests/kinematic_2d", "smoke_tests/chamber", "tutorials_tests"] exclude: - platform: "macos-14" python-version: "3.9" diff --git a/.gitignore b/.gitignore index f0b0fc0fb..1c298872a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ PySDM_examples/utils/temporary_files/* *.pkl *.vtu *.vts +*.png +*.csv +*.xlsx # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/PySDM/backends/impl_numba/methods/__init__.py b/PySDM/backends/impl_numba/methods/__init__.py index 617c61ede..aca582ae5 100644 --- a/PySDM/backends/impl_numba/methods/__init__.py +++ b/PySDM/backends/impl_numba/methods/__init__.py @@ -12,4 +12,4 @@ from .pair_methods import PairMethods from .physics_methods import PhysicsMethods from .terminal_velocity_methods import TerminalVelocityMethods -from .seeding_methods import SeedingMethods +from .spawning_methods import SpawningMethods diff --git a/PySDM/backends/impl_numba/methods/seeding_methods.py b/PySDM/backends/impl_numba/methods/spawning_methods.py similarity index 54% rename from PySDM/backends/impl_numba/methods/seeding_methods.py rename to PySDM/backends/impl_numba/methods/spawning_methods.py index 83fb64dc5..653e27f19 100644 --- a/PySDM/backends/impl_numba/methods/seeding_methods.py +++ b/PySDM/backends/impl_numba/methods/spawning_methods.py @@ -1,4 +1,4 @@ -"""CPU implementation of backend methods for particle injections""" +"""CPU implementation of backend methods for particle spawning""" from functools import cached_property @@ -7,62 +7,62 @@ from PySDM.backends.impl_common.backend_methods import BackendMethods -class SeedingMethods(BackendMethods): # pylint: disable=too-few-public-methods +class SpawningMethods(BackendMethods): # pylint: disable=too-few-public-methods @cached_property - def _seeding(self): + def _spawning(self): @numba.njit(**{**self.default_jit_flags, "parallel": False}) def body( # pylint: disable=too-many-arguments idx, multiplicity, extensive_attributes, - seeded_particle_index, - seeded_particle_multiplicity, - seeded_particle_extensive_attributes, - number_of_super_particles_to_inject: int, + spawned_particle_index, + spawned_particle_multiplicity, + spawned_particle_extensive_attributes, + number_of_super_particles_to_spawn: int, ): number_of_super_particles_already_injected = 0 # TODO #1387 start enumerating from the end of valid particle set for i, mult in enumerate(multiplicity): if ( - number_of_super_particles_to_inject + number_of_super_particles_to_spawn == number_of_super_particles_already_injected ): break if mult == 0: idx[i] = -1 - s = seeded_particle_index[ + s = spawned_particle_index[ number_of_super_particles_already_injected ] number_of_super_particles_already_injected += 1 - multiplicity[i] = seeded_particle_multiplicity[s] + multiplicity[i] = spawned_particle_multiplicity[s] for a in range(len(extensive_attributes)): extensive_attributes[a, i] = ( - seeded_particle_extensive_attributes[a, s] + spawned_particle_extensive_attributes[a, s] ) assert ( - number_of_super_particles_to_inject + number_of_super_particles_to_spawn == number_of_super_particles_already_injected ) return body - def seeding( + def spawning( self, *, idx, multiplicity, extensive_attributes, - seeded_particle_index, - seeded_particle_multiplicity, - seeded_particle_extensive_attributes, - number_of_super_particles_to_inject: int, + spawned_particle_index, + spawned_particle_multiplicity, + spawned_particle_extensive_attributes, + number_of_super_particles_to_spawn: int, ): - self._seeding( + self._spawning( idx=idx.data, multiplicity=multiplicity.data, extensive_attributes=extensive_attributes.data, - seeded_particle_index=seeded_particle_index.data, - seeded_particle_multiplicity=seeded_particle_multiplicity.data, - seeded_particle_extensive_attributes=seeded_particle_extensive_attributes.data, - number_of_super_particles_to_inject=number_of_super_particles_to_inject, + spawned_particle_index=spawned_particle_index.data, + spawned_particle_multiplicity=spawned_particle_multiplicity.data, + spawned_particle_extensive_attributes=spawned_particle_extensive_attributes.data, + number_of_super_particles_to_spawn=number_of_super_particles_to_spawn, ) diff --git a/PySDM/backends/numba.py b/PySDM/backends/numba.py index a42063265..f805d1e39 100644 --- a/PySDM/backends/numba.py +++ b/PySDM/backends/numba.py @@ -28,7 +28,7 @@ class Numba( # pylint: disable=too-many-ancestors,duplicate-code methods.DisplacementMethods, methods.TerminalVelocityMethods, methods.IsotopeMethods, - methods.SeedingMethods, + methods.SpawningMethods, ): Storage = ImportedStorage Random = ImportedRandom @@ -77,4 +77,4 @@ def __init__(self, formulae=None, double_precision=True, override_jit_flags=None methods.DisplacementMethods.__init__(self) methods.TerminalVelocityMethods.__init__(self) methods.IsotopeMethods.__init__(self) - methods.SeedingMethods.__init__(self) + methods.SpawningMethods.__init__(self) diff --git a/PySDM/dynamics/__init__.py b/PySDM/dynamics/__init__.py index 66056cf0d..bfbc375f6 100644 --- a/PySDM/dynamics/__init__.py +++ b/PySDM/dynamics/__init__.py @@ -16,3 +16,4 @@ from PySDM.dynamics.freezing import Freezing from PySDM.dynamics.relaxed_velocity import RelaxedVelocity from PySDM.dynamics.seeding import Seeding +from PySDM.dynamics.homogeneous_liquid_nucleation import HomogeneousLiquidNucleation diff --git a/PySDM/dynamics/homogeneous_liquid_nucleation.py b/PySDM/dynamics/homogeneous_liquid_nucleation.py new file mode 100644 index 000000000..b1e3a531b --- /dev/null +++ b/PySDM/dynamics/homogeneous_liquid_nucleation.py @@ -0,0 +1,72 @@ +"""nucleation of droplets in CCN-void air that spawns new super-droplets""" + +import numpy as np +from PySDM.dynamics.impl import register_dynamic +from PySDM.dynamics.impl import SuperParticleSpawningDynamic + + +@register_dynamic() +class HomogeneousLiquidNucleation(SuperParticleSpawningDynamic): + def __init__(self): + self.particulator = None + self.formulae = None + self.index = None + + def register(self, builder): + self.particulator = builder.particulator + self.formulae = builder.formulae + self.index = self.particulator.Index.identity_index(1) + + def __call__(self): + env = { + k: self.particulator.environment[k].to_ndarray()[0] for k in ("T", "RH") + } # TODO #1492: >0D + + if env["RH"] < 1: + return + + e_s = self.formulae.saturation_vapour_pressure.pvs_water(env["T"]) + j = self.formulae.homogeneous_liquid_nucleation_rate.j_liq_homo( + env["T"], env["RH"], e_s + ) + + # TODO #1492: take care of cases where round yields zero -> MC sampling? + new_sd_multiplicity = round( + j * self.particulator.environment.mesh.dv * self.particulator.dt + ) + + if new_sd_multiplicity > 0: + r_wet = self.formulae.homogeneous_liquid_nucleation_rate.r_liq_homo( + env["T"], env["RH"] + ) + v_wet = self.formulae.trivia.volume(radius=r_wet) + new_sd_extensive_attributes = { + "water mass": (v_wet * self.formulae.constants.rho_w,), + "dry volume": (0,), + "kappa times dry volume": (0,), + } + + # TODO #1492: to be done once, not every time we spawn + self.check_extensive_attribute_keys( + particulator_attributes=self.particulator.attributes, + spawned_attributes=new_sd_extensive_attributes, + ) + + # TODO #1492: allocate once, reuse + new_sd_extensive_attributes = self.particulator.IndexedStorage.from_ndarray( + self.index, + np.asarray(list(new_sd_extensive_attributes.values())), + ) + # TODO #1492: ditto + new_sd_multiplicity = self.particulator.IndexedStorage.from_ndarray( + self.index, + np.asarray((new_sd_multiplicity,), dtype=np.int64), + ) + + self.particulator.spawn( + spawned_particle_index=self.index, + number_of_super_particles_to_spawn=1, + spawned_particle_multiplicity=new_sd_multiplicity, + spawned_particle_extensive_attributes=new_sd_extensive_attributes, + ) + # TODO #1492: subtract the water mass from ambient vapour diff --git a/PySDM/dynamics/impl/__init__.py b/PySDM/dynamics/impl/__init__.py index 2843142c6..455352bc1 100644 --- a/PySDM/dynamics/impl/__init__.py +++ b/PySDM/dynamics/impl/__init__.py @@ -1,3 +1,4 @@ """stuff not intended to be imported from user code""" from .register_dynamic import register_dynamic +from .super_particle_spawning_dynamic import SuperParticleSpawningDynamic diff --git a/PySDM/dynamics/impl/super_particle_spawning_dynamic.py b/PySDM/dynamics/impl/super_particle_spawning_dynamic.py new file mode 100644 index 000000000..64497d654 --- /dev/null +++ b/PySDM/dynamics/impl/super_particle_spawning_dynamic.py @@ -0,0 +1,23 @@ +"""common code for dynamics that spawn new super-droplets""" + +from PySDM.impl.particle_attributes import ParticleAttributes + + +class SuperParticleSpawningDynamic: # pylint: disable=too-few-public-methods + """base class for dynamics that spawn new super-droplets""" + + @staticmethod + def check_extensive_attribute_keys( + particulator_attributes: ParticleAttributes, + spawned_attributes: dict, + ): + """checks if keys (and their order) in user-supplied `spawned_attributes` match + attributes used in the `particulator_attributes`""" + if tuple(particulator_attributes.get_extensive_attribute_keys()) != tuple( + spawned_attributes.keys() + ): + raise ValueError( + f"extensive attributes ({spawned_attributes.keys()})" + " do not match those used in particulator" + f" ({particulator_attributes.get_extensive_attribute_keys()})" + ) diff --git a/PySDM/dynamics/seeding.py b/PySDM/dynamics/seeding.py index 410d8abb2..894f78302 100644 --- a/PySDM/dynamics/seeding.py +++ b/PySDM/dynamics/seeding.py @@ -8,10 +8,11 @@ from PySDM.dynamics.impl import register_dynamic from PySDM.initialisation import discretise_multiplicities +from PySDM.dynamics.impl import SuperParticleSpawningDynamic @register_dynamic() -class Seeding: +class Seeding(SuperParticleSpawningDynamic): def __init__( self, *, @@ -33,15 +34,10 @@ def register(self, builder): self.particulator = builder.particulator def post_register_setup_when_attributes_are_known(self): - if tuple(self.particulator.attributes.get_extensive_attribute_keys()) != tuple( - self.seeded_particle_extensive_attributes.keys() - ): - raise ValueError( - f"extensive attributes ({self.seeded_particle_extensive_attributes.keys()})" - " do not match those used in particulator" - f" ({self.particulator.attributes.get_extensive_attribute_keys()})" - ) - + SuperParticleSpawningDynamic.check_extensive_attribute_keys( + particulator_attributes=self.particulator.attributes, + spawned_attributes=self.seeded_particle_extensive_attributes, + ) self.index = self.particulator.Index.identity_index( len(self.seeded_particle_multiplicity) ) @@ -86,9 +82,9 @@ def __call__(self): # or if the number of super particles to inject # is equal to the number of possible seeds self.index.shuffle(self.u01) - self.particulator.seeding( - seeded_particle_index=self.index, - number_of_super_particles_to_inject=number_of_super_particles_to_inject, - seeded_particle_multiplicity=self.seeded_particle_multiplicity, - seeded_particle_extensive_attributes=self.seeded_particle_extensive_attributes, + self.particulator.spawn( + spawned_particle_index=self.index, + number_of_super_particles_to_spawn=number_of_super_particles_to_inject, + spawned_particle_multiplicity=self.seeded_particle_multiplicity, + spawned_particle_extensive_attributes=self.seeded_particle_extensive_attributes, ) diff --git a/PySDM/environments/__init__.py b/PySDM/environments/__init__.py index f9e9d18f9..e4fb48fdc 100644 --- a/PySDM/environments/__init__.py +++ b/PySDM/environments/__init__.py @@ -8,3 +8,4 @@ from .kinematic_1d import Kinematic1D from .kinematic_2d import Kinematic2D from .parcel import Parcel +from .expansion_chamber import ExpansionChamber diff --git a/PySDM/environments/expansion_chamber.py b/PySDM/environments/expansion_chamber.py new file mode 100644 index 000000000..88ad00db2 --- /dev/null +++ b/PySDM/environments/expansion_chamber.py @@ -0,0 +1,93 @@ +""" +Zero-dimensional expansion chamber framework +""" + +from typing import Optional, List + +from PySDM.environments.impl.moist_lagrangian import MoistLagrangian +from PySDM.impl.mesh import Mesh +from PySDM.environments.impl import register_environment + + +@register_environment() +class ExpansionChamber(MoistLagrangian): + def __init__( + self, + *, + dt, + volume: float, + initial_pressure: float, + initial_temperature: float, + initial_relative_humidity: float, + delta_pressure: float, + delta_time: float, + variables: Optional[List[str]] = None, + mixed_phase=False, + ): + variables = (variables or []) + ["rhod"] + + super().__init__(dt, Mesh.mesh_0d(), variables, mixed_phase=mixed_phase) + + self.dv = volume + self.initial_pressure = initial_pressure + self.initial_temperature = initial_temperature + self.initial_relative_humidity = initial_relative_humidity + self.delta_time = delta_time + self.dp_dt = delta_pressure / delta_time + + def register(self, builder): + self.mesh.dv = self.dv + + super().register(builder) + + formulae = self.particulator.formulae + pv0 = ( + self.initial_relative_humidity + * formulae.saturation_vapour_pressure.pvs_water(self.initial_temperature) + ) + th_std = formulae.trivia.th_std( + p=self.initial_pressure, T=self.initial_temperature + ) + initial_water_vapour_mixing_ratio = ( + formulae.constants.eps * pv0 / (self.initial_pressure - pv0) + ) + + self["rhod"][:] = formulae.state_variable_triplet.rho_d( + p=self.initial_pressure, + water_vapour_mixing_ratio=initial_water_vapour_mixing_ratio, + theta_std=th_std, + ) + self["thd"][:] = formulae.state_variable_triplet.th_dry( + th_std, initial_water_vapour_mixing_ratio + ) + self["water_vapour_mixing_ratio"][:] = initial_water_vapour_mixing_ratio + + self.post_register() + + def advance_moist_vars(self): + """compute new values of dry density and thd, and write them to self._tmp and self["thd"] + assuming water-vapour mixing ratio is not altered by the expansion""" + dt = self.particulator.dt + t_new = (self.particulator.n_steps + 1) * dt + if t_new > self.delta_time: + return + + formulae = self.particulator.formulae + p_new = self["p"][0] + self.dp_dt * dt + vapour_mixing_ratio = self["water_vapour_mixing_ratio"][0] + gg = ( + 1 - formulae.adiabatic_exponent.gamma(vapour_mixing_ratio) + ) / formulae.adiabatic_exponent.gamma(vapour_mixing_ratio) + T_new = self.initial_temperature * (self.initial_pressure / p_new) ** gg + wvmr_new = self._tmp["water_vapour_mixing_ratio"][ + 0 + ] # TODO #1492 - should _tmp or self[] be used? + pv_new = wvmr_new * p_new / (wvmr_new + formulae.constants.eps) + pd_new = p_new - pv_new + + self._tmp["rhod"][:] = pd_new / T_new / formulae.constants.Rd + self["thd"][:] = formulae.trivia.th_std(p=pd_new, T=T_new) + + def sync_moist_vars(self): + for var in self.variables: + self._tmp[var][:] = self[var][:] diff --git a/PySDM/environments/impl/moist.py b/PySDM/environments/impl/moist.py index bef7c02dc..35009a166 100644 --- a/PySDM/environments/impl/moist.py +++ b/PySDM/environments/impl/moist.py @@ -71,6 +71,8 @@ def _recalculate_temperature_pressure_relative_humidity(self, target): ) def sync(self): + """fills the "predicted" set of thermodynamic variables with derived values freshly + computed from `self.get_thd()` and `self.get_water_vapour_mixing_ratio()`""" target = self._tmp target["water_vapour_mixing_ratio"].ravel(self.get_water_vapour_mixing_ratio()) target["thd"].ravel(self.get_thd()) @@ -108,6 +110,8 @@ def get_thd(self) -> np.ndarray: raise NotImplementedError() def notify(self): + """invalidates the "predicted" set of thermodynamic variables storing its + contents into the "current" set of vars""" if self._values["predicted"] is None: return diff --git a/PySDM/environments/impl/moist_lagrangian.py b/PySDM/environments/impl/moist_lagrangian.py new file mode 100644 index 000000000..805d4b8d4 --- /dev/null +++ b/PySDM/environments/impl/moist_lagrangian.py @@ -0,0 +1,31 @@ +""" +Zero-dimensional (Lagrangian) moist environment commons +""" + +from PySDM.environments.impl.moist import Moist + + +class MoistLagrangian(Moist): + """base class for moist Lagrangian environments (parcel, chamber, ...)""" + + def get_thd(self): + return self["thd"] + + def get_water_vapour_mixing_ratio(self): + return self["water_vapour_mixing_ratio"] + + def sync(self): + self.sync_moist_vars() + self.advance_moist_vars() + super().sync() + + def post_register(self): + self.sync_moist_vars() + super().sync() + self.notify() + + def sync_moist_vars(self): + raise NotImplementedError() + + def advance_moist_vars(self): + raise NotImplementedError() diff --git a/PySDM/environments/parcel.py b/PySDM/environments/parcel.py index 5355007f8..e574fbfbc 100644 --- a/PySDM/environments/parcel.py +++ b/PySDM/environments/parcel.py @@ -6,7 +6,7 @@ import numpy as np -from PySDM.environments.impl.moist import Moist +from PySDM.environments.impl.moist_lagrangian import MoistLagrangian from PySDM.impl.mesh import Mesh from PySDM.initialisation.equilibrate_wet_radii import ( default_rtol, @@ -16,7 +16,7 @@ @register_environment() -class Parcel(Moist): # pylint: disable=too-many-instance-attributes +class Parcel(MoistLagrangian): # pylint: disable=too-many-instance-attributes def __init__( self, *, @@ -56,19 +56,16 @@ def register(self, builder): rhod0, self.mass_of_dry_air ) - Moist.register(self, builder) + super().register(builder) self["water_vapour_mixing_ratio"][:] = self.initial_water_vapour_mixing_ratio self["thd"][:] = formulae.trivia.th_std(pd0, self.T0) self["rhod"][:] = rhod0 self["z"][:] = self.z0 - self._tmp["water_vapour_mixing_ratio"][ : ] = self.initial_water_vapour_mixing_ratio - self.sync_parcel_vars() - Moist.sync(self) - self.notify() + self.post_register() def init_attributes( self, @@ -98,7 +95,7 @@ def init_attributes( attributes["dry volume"] = dry_volume return attributes - def advance_parcel_vars(self): + def advance_moist_vars(self): """compute new values of displacement, dry-air density and volume, and write them to self._tmp and self.mesh.dv""" dt = self.particulator.dt @@ -134,21 +131,10 @@ def advance_parcel_vars(self): (self._tmp["rhod"][0] + self["rhod"][0]) / 2, self.mass_of_dry_air ) - def get_thd(self): - return self["thd"] - - def get_water_vapour_mixing_ratio(self): - return self["water_vapour_mixing_ratio"] - - def sync_parcel_vars(self): + def sync_moist_vars(self): self.delta_liquid_water_mixing_ratio = ( self._tmp["water_vapour_mixing_ratio"][0] - self["water_vapour_mixing_ratio"][0] ) for var in self.variables: self._tmp[var][:] = self[var][:] - - def sync(self): - self.sync_parcel_vars() - self.advance_parcel_vars() - super().sync() diff --git a/PySDM/formulae.py b/PySDM/formulae.py index 74d1d8123..41e22092e 100644 --- a/PySDM/formulae.py +++ b/PySDM/formulae.py @@ -44,6 +44,7 @@ def __init__( # pylint: disable=too-many-locals hydrostatics: str = "Default", freezing_temperature_spectrum: str = "Null", heterogeneous_ice_nucleation_rate: str = "Null", + homogeneous_liquid_nucleation_rate: str = "Null", fragmentation_function: str = "AlwaysN", isotope_equilibrium_fractionation_factors: str = "Null", isotope_meteoric_water_line: str = "Null", @@ -57,6 +58,7 @@ def __init__( # pylint: disable=too-many-locals terminal_velocity: str = "GunnKinzer1949", air_dynamic_viscosity: str = "ZografosEtAl1987", bulk_phase_partitioning: str = "Null", + adiabatic_exponent: str = "Dry", handle_all_breakups: bool = False, ): # initialisation of the fields below is just to silence pylint and to enable code hints @@ -77,6 +79,7 @@ def __init__( # pylint: disable=too-many-locals self.hydrostatics = hydrostatics self.freezing_temperature_spectrum = freezing_temperature_spectrum self.heterogeneous_ice_nucleation_rate = heterogeneous_ice_nucleation_rate + self.homogeneous_liquid_nucleation_rate = homogeneous_liquid_nucleation_rate self.fragmentation_function = fragmentation_function self.isotope_equilibrium_fractionation_factors = ( isotope_equilibrium_fractionation_factors @@ -90,6 +93,7 @@ def __init__( # pylint: disable=too-many-locals self.air_dynamic_viscosity = air_dynamic_viscosity self.terminal_velocity = terminal_velocity self.bulk_phase_partitioning = bulk_phase_partitioning + self.adiabatic_exponent = adiabatic_exponent self._components = tuple( i diff --git a/PySDM/particulator.py b/PySDM/particulator.py index 88608efd3..b5573376f 100644 --- a/PySDM/particulator.py +++ b/PySDM/particulator.py @@ -439,39 +439,39 @@ def isotopic_fractionation(self, heavy_isotopes: tuple): for isotope in heavy_isotopes: self.attributes.mark_updated(f"moles_{isotope}") - def seeding( + def spawn( self, *, - seeded_particle_index, - seeded_particle_multiplicity, - seeded_particle_extensive_attributes, - number_of_super_particles_to_inject, + spawned_particle_index, + spawned_particle_multiplicity, + spawned_particle_extensive_attributes, + number_of_super_particles_to_spawn, ): n_null = self.n_sd - self.attributes.super_droplet_count if n_null == 0: raise ValueError( - "No available seeds to inject. Please provide particles with nan filled attributes." + "No available null SDs to spawn. Please provide SDs with nan filled attributes." ) - if number_of_super_particles_to_inject > n_null: + if number_of_super_particles_to_spawn > n_null: raise ValueError( - "Trying to inject more super particles than space available." + "Trying to spawn more super particles than space available." ) - if number_of_super_particles_to_inject > len(seeded_particle_multiplicity): + if number_of_super_particles_to_spawn > len(spawned_particle_multiplicity): raise ValueError( - "Trying to inject multiple super particles with the same attributes. \ - Instead increase multiplicity of injected particles." + "Trying to spawn multiple super particles with the same attributes. \ + Instead increase multiplicity of spawned particles." ) - self.backend.seeding( + self.backend.spawning( idx=self.attributes._ParticleAttributes__idx, multiplicity=self.attributes["multiplicity"], extensive_attributes=self.attributes.get_extensive_attribute_storage(), - seeded_particle_index=seeded_particle_index, - seeded_particle_multiplicity=seeded_particle_multiplicity, - seeded_particle_extensive_attributes=seeded_particle_extensive_attributes, - number_of_super_particles_to_inject=number_of_super_particles_to_inject, + spawned_particle_index=spawned_particle_index, + spawned_particle_multiplicity=spawned_particle_multiplicity, + spawned_particle_extensive_attributes=spawned_particle_extensive_attributes, + number_of_super_particles_to_spawn=number_of_super_particles_to_spawn, ) self.attributes.reset_idx() self.attributes.sanitize() diff --git a/PySDM/physics/__init__.py b/PySDM/physics/__init__.py index 65ad336cb..d50d8fc33 100644 --- a/PySDM/physics/__init__.py +++ b/PySDM/physics/__init__.py @@ -25,6 +25,7 @@ fragmentation_function, freezing_temperature_spectrum, heterogeneous_ice_nucleation_rate, + homogeneous_liquid_nucleation_rate, hydrostatics, hygroscopicity, impl, @@ -47,5 +48,6 @@ air_dynamic_viscosity, terminal_velocity, bulk_phase_partitioning, + adiabatic_exponent, ) from .constants import convert_to, in_unit, si diff --git a/PySDM/physics/adiabatic_exponent/__init__.py b/PySDM/physics/adiabatic_exponent/__init__.py new file mode 100644 index 000000000..d852b87f3 --- /dev/null +++ b/PySDM/physics/adiabatic_exponent/__init__.py @@ -0,0 +1,6 @@ +""" +Alternative formulations of adiabatic exponent +""" + +from .dry import Dry +from .moist_leading_terms import MoistLeadingTerms diff --git a/PySDM/physics/adiabatic_exponent/dry.py b/PySDM/physics/adiabatic_exponent/dry.py new file mode 100644 index 000000000..07db362c7 --- /dev/null +++ b/PySDM/physics/adiabatic_exponent/dry.py @@ -0,0 +1,12 @@ +""" +adiabatic exponent, dry air +""" + + +class Dry: # pylint: disable=too-few-public-methods + def __init__(self, _): + pass + + @staticmethod + def gamma(const, qv): # pylint: disable=unused-argument + return 1 + const.Rd / const.c_vd diff --git a/PySDM/physics/adiabatic_exponent/moist_leading_terms.py b/PySDM/physics/adiabatic_exponent/moist_leading_terms.py new file mode 100644 index 000000000..1e5e3bac2 --- /dev/null +++ b/PySDM/physics/adiabatic_exponent/moist_leading_terms.py @@ -0,0 +1,19 @@ +""" +adiabatic exponent, moist air, expanding to first order in qv, assuming qt=qv +""" + + +class MoistLeadingTerms: # pylint: disable=too-few-public-methods + def __init__(self, _): + pass + + @staticmethod + def gamma(const, vapour_mixing_ratio): + return ( + 1 + + const.Rd / const.c_vd + + (const.Rv * vapour_mixing_ratio) + / (const.c_vd * (1 - vapour_mixing_ratio)) + - (const.Rd * vapour_mixing_ratio * const.c_vv) + / (const.c_vd**2 * (1 - vapour_mixing_ratio)) + ) diff --git a/PySDM/physics/constants_defaults.py b/PySDM/physics/constants_defaults.py index 5f4a19a85..c358e6d4f 100644 --- a/PySDM/physics/constants_defaults.py +++ b/PySDM/physics/constants_defaults.py @@ -299,6 +299,12 @@ J_HET = np.nan """ constant ice nucleation rate """ +J_LIQ_HOMO = np.nan +""" constant liquid homogeneous-nucleation rate """ + +R_LIQ_HOMO = np.nan +""" constant particle size for liquid homogeneous nucleation """ + STRAUB_E_D1 = 0.04 * si.cm """ [Straub et al. 2010](https://doi.org/10.1175/2009JAS3175.1) """ STRAUB_MU2 = 0.095 * si.cm @@ -681,6 +687,11 @@ def compute_derived_values(c: dict): c["Rd_over_c_pd"] = c["Rd"] / c["c_pd"] + c["c_vd"] = c["c_pd"] - c["Rd"] + c["c_vv"] = c["c_pv"] - c["Rv"] + c["water_molar_volume"] = c["Mv"] / c["rho_w"] c["rho_STP"] = c["p_STP"] / c["Rd"] / c["T_STP"] c["H_u"] = c["M"] / c["p_STP"] + + c["k_B"] = c["R_str"] / c["N_A"] diff --git a/PySDM/physics/homogeneous_liquid_nucleation_rate/__init__.py b/PySDM/physics/homogeneous_liquid_nucleation_rate/__init__.py new file mode 100644 index 000000000..361803925 --- /dev/null +++ b/PySDM/physics/homogeneous_liquid_nucleation_rate/__init__.py @@ -0,0 +1,7 @@ +""" +homogeneous liquid droplet nucleation rate formulations +""" + +from PySDM.impl.null_physics_class import Null +from .cnt import CNT +from .constant import Constant diff --git a/PySDM/physics/homogeneous_liquid_nucleation_rate/cnt.py b/PySDM/physics/homogeneous_liquid_nucleation_rate/cnt.py new file mode 100644 index 000000000..23d47531f --- /dev/null +++ b/PySDM/physics/homogeneous_liquid_nucleation_rate/cnt.py @@ -0,0 +1,32 @@ +""" +classical nucleation theory (CNT) +formulae from [Seinfeld and Pandis](https://archive.org/details/0237-pdf-atmospheric-chemistry-and-physics-2nd-ed-j.-seinfeld-s.-pandis-wiley-2006-ww) +Eq (11.47) and (11.52) +""" # pylint: disable=line-too-long + +import numpy as np + + +class CNT: + def __init__(self, _): + return + + @staticmethod + def j_liq_homo(const, T, S, e_s): + mass_per_moleculue = const.Mv / const.N_A + volume_per_molecule = mass_per_moleculue / const.rho_w + N1 = (S * e_s) / (mass_per_moleculue * const.Rv * T) # molecules per m3 + return ( + ((2 * const.sgm_w) / (np.pi * mass_per_moleculue)) ** (1 / 2) + * (volume_per_molecule * N1**2 / S) + * np.exp( + (-16 * np.pi * volume_per_molecule**2 * const.sgm_w**3) + / (3 * const.k_B**3 * T**3 * np.log(S) ** 2) + ) + ) + + @staticmethod + def r_liq_homo(const, T, S): + mass_per_moleculue = const.Mv / const.N_A + volume_per_molecule = mass_per_moleculue / const.rho_w + return (2 * const.sgm_w * volume_per_molecule) / (const.k_B * T * np.log(S)) diff --git a/PySDM/physics/homogeneous_liquid_nucleation_rate/constant.py b/PySDM/physics/homogeneous_liquid_nucleation_rate/constant.py new file mode 100644 index 000000000..49dd74796 --- /dev/null +++ b/PySDM/physics/homogeneous_liquid_nucleation_rate/constant.py @@ -0,0 +1,19 @@ +""" +constant rate formulation (for tests) +""" + +import numpy as np + + +class Constant: + def __init__(self, const): + assert np.isfinite(const.J_LIQ_HOMO) + assert np.isfinite(const.R_LIQ_HOMO) + + @staticmethod + def j_liq_homo(const, T, S, e_s): # pylint: disable=unused-argument + return const.J_LIQ_HOMO + + @staticmethod + def r_liq_homo(const, T, S): # pylint: disable=unused-argument + return const.R_LIQ_HOMO diff --git a/PySDM/physics/impl/fake_unit_registry.py b/PySDM/physics/impl/fake_unit_registry.py index c68cf0648..263a39814 100644 --- a/PySDM/physics/impl/fake_unit_registry.py +++ b/PySDM/physics/impl/fake_unit_registry.py @@ -28,6 +28,7 @@ def __init__(self, si): "hour", "newton", "watt", + "dyne", ): self.__setattr__(prefix + unit, _fake(si.__getattr__(prefix + unit))) self.__setattr__( @@ -52,5 +53,6 @@ def __init__(self, si): "bar", # note: "b" is barn !!! "N", "W", + "dyn", ): self.__setattr__(prefix + unit, _fake(si.__getattr__(prefix + unit))) diff --git a/docs/bibliography.json b/docs/bibliography.json index 7df882139..2bb2a77ab 100644 --- a/docs/bibliography.json +++ b/docs/bibliography.json @@ -706,6 +706,23 @@ "title": "Vapor Pressure Formulation for Water in Range 0 to 100 °C. A Revision", "label": "Wexler 1976 (J. Res. NBS A Phys. Ch. 80A)" }, + "https://doi.org/10.48550/arXiv.2501.01467": { + "usages": [ + "examples/PySDM_examples/Erinin_et_al_2025/__init__.py", + "examples/PySDM_examples/Erinin_et_al_2025/aerosol.py", + "tests/smoke_tests/chamber/erinin_et_al_2025/test_fig_3.py", + "examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb"], + "title": "Droplet Nucleation In a Rapid Expansion Aerosol Chamber", + "label": "Erinin et al. 2024 (arXiv:2501.01467)" + }, + "https://archive.org/details/0237-pdf-atmospheric-chemistry-and-physics-2nd-ed-j.-seinfeld-s.-pandis-wiley-2006-ww": { + "usages": [ + "PySDM/physics/homogeneous_liquid_nucleation_rate/cnt.py", + "PySDM/physics/constants_defaults.py" + ], + "title": "Atmospheric Chemistry and Physics: From Air Pollution to Climate Change, 2nd Edition", + "label": "Seinfeld & Pandis 2006 (Wiley)" + }, "https://archive.org/details/shortcourseinclo0000roge_m3k2": { "usages": ["PySDM/physics/constants_defaults.py", "PySDM/physics/terminal_velocity/rogers_yau.py"], "title": "A short course in clouds physics", @@ -741,11 +758,6 @@ "title": "Microphysics of Clouds and Precipitation", "label": "Pruppacher & Klett 2010 (Springer Dordrecht)" }, - "https://archive.org/details/0237-pdf-atmospheric-chemistry-and-physics-2nd-ed-j.-seinfeld-s.-pandis-wiley-2006-ww": { - "usages": ["PySDM/physics/constants_defaults.py"], - "title": "Microphysics of Clouds and Precipitation", - "label": "Seinfeld & Pandis 2006 (Wiley)" - }, "https://doi.org/10.1038/187857a0": { "usages": [ "PySDM/physics/constants_defaults.py", diff --git a/examples/PySDM_examples/Erinin_et_al_2025/__init__.py b/examples/PySDM_examples/Erinin_et_al_2025/__init__.py new file mode 100644 index 000000000..3916bea26 --- /dev/null +++ b/examples/PySDM_examples/Erinin_et_al_2025/__init__.py @@ -0,0 +1,4 @@ +# pylint: disable=invalid-name +""" +expansion chamber example based on [Erinin et al. 2025](https://doi.org/10.48550/arXiv.2501.01467) +""" diff --git a/examples/PySDM_examples/Erinin_et_al_2025/aerosol.py b/examples/PySDM_examples/Erinin_et_al_2025/aerosol.py new file mode 100644 index 000000000..36dadaa51 --- /dev/null +++ b/examples/PySDM_examples/Erinin_et_al_2025/aerosol.py @@ -0,0 +1,47 @@ +"""aerosol parameters from [Erinin et al. 2025](https://doi.org/10.48550/arXiv.2501.01467)""" + +from chempy import Substance +from pystrict import strict + +from PySDM.initialisation import spectra +from PySDM.initialisation.aerosol_composition import DryAerosolMixture +from PySDM.physics import si + + +@strict +class AerosolChamber(DryAerosolMixture): + def __init__( + self, + water_molar_volume: float, + N: float = 1e5 / si.cm**3, + ): + mode1 = {"CaCO3": 1.0} + + super().__init__( + compounds=("CaCO3",), + molar_masses={ + "CaCO3": Substance.from_formula("CaCO3").mass * si.g / si.mole + }, + densities={ + "CaCO3": 2.71 * si.g / si.cm**3, + }, + is_soluble={ + "CaCO3": True, + }, + ionic_dissociation_phi={ + "CaCO3": 1, + }, + ) + self.modes = ( + { + "kappa": self.kappa( + mass_fractions=mode1, + water_molar_volume=water_molar_volume, + ), + "spectrum": spectra.Gaussian( + norm_factor=N, + loc=316 / 2 * si.nm, + scale=257 / 2 * si.nm, + ), + }, + ) diff --git a/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb b/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb new file mode 100644 index 000000000..d4b936771 --- /dev/null +++ b/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb @@ -0,0 +1,10586 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[![preview notebook](https://img.shields.io/static/v1?label=render%20on&logo=github&color=87ce3e&message=GitHub)](https://github.com/open-atmos/PySDM/blob/main/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb)\n", + "[![launch on mybinder.org](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/open-atmos/PySDM.git/main?urlpath=lab/tree/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb)\n", + "[![launch on Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/open-atmos/PySDM/blob/main/examples/PySDM_examples/Erinin_et_al_2025/expansion_experiment.ipynb)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "modeling expansion-chamber experiments described in [Erinin et al. 2025](https://doi.org/10.48550/arXiv.2501.01467)" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-30T20:21:31.584604Z", + "start_time": "2025-01-30T20:21:31.573353Z" + } + }, + "outputs": [], + "source": [ + "import sys\n", + "if 'google.colab' in sys.modules:\n", + " !pip --quiet install open-atmos-jupyter-utils\n", + " from open_atmos_jupyter_utils import pip_install_on_colab\n", + " pip_install_on_colab('PySDM-examples')" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-30T20:21:37.280272Z", + "start_time": "2025-01-30T20:21:31.850352Z" + } + }, + "outputs": [], + "source": [ + "import numpy as np\n", + "from matplotlib import pyplot\n", + "from open_atmos_jupyter_utils import show_plot\n", + "from PySDM import Formulae\n", + "from PySDM.physics import si, in_unit\n", + "from PySDM import products as PySDM_products\n", + "from PySDM_examples.Erinin_et_al_2025.aerosol import AerosolChamber\n", + "from PySDM_examples.Erinin_et_al_2025.expansion_simulation import run_expansion" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "dry_radius_bin_edges = np.geomspace(50 * si.nm, 2000 * si.nm, 40, endpoint=False)\n", + "wet_radius_bin_edges = np.geomspace(1 * si.um, 40 * si.um, 40, endpoint=False)\n", + "\n", + "products = (\n", + " PySDM_products.SuperDropletCountPerGridbox(name=\"SD count\"),\n", + " PySDM_products.WaterMixingRatio(unit=\"g/kg\", name=r\"$q_\\ell$\"),\n", + " PySDM_products.PeakSupersaturation(name=\"Saturation ratio\"),\n", + " PySDM_products.AmbientRelativeHumidity(name=\"RH\"),\n", + " PySDM_products.AmbientTemperature(name=\"Temperature\", var=\"T\"),\n", + " PySDM_products.AmbientPressure(name=\"Pressure\", var=\"p\", unit=\"hPa\"),\n", + " PySDM_products.AmbientWaterVapourMixingRatio(\n", + " unit=\"g/kg\", name=\"$q_v$\", var=\"water_vapour_mixing_ratio\"\n", + " ),\n", + " PySDM_products.Time(name=\"t\"),\n", + " PySDM_products.ParticleSizeSpectrumPerVolume(\n", + " name=\"dry:dN/dR\",\n", + " unit=\"m^-3 m^-1\",\n", + " radius_bins_edges=dry_radius_bin_edges,\n", + " dry=True,\n", + " ),\n", + " PySDM_products.ParticleSizeSpectrumPerVolume(\n", + " name=\"wet:dN/dR\",\n", + " unit=\"m^-3 m^-1\",\n", + " radius_bins_edges=wet_radius_bin_edges,\n", + " dry=False,\n", + " ),\n", + " PySDM_products.ActivatedEffectiveRadius(\n", + " name=\"act_reff\", unit=\"um\", count_activated=True, count_unactivated=False\n", + " ),\n", + " PySDM_products.EffectiveRadius(\n", + " name=\"$r_{eff}$\",\n", + " unit=\"um\",\n", + " ),\n", + " PySDM_products.ParticleConcentration(\n", + " name=\"$N_d$\",\n", + " unit=\"cm^-3\",\n", + " # radius_range=(0.5 * si.um, 25 * si.um),\n", + " ),\n", + ") " + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "FORMULAE = Formulae(\n", + " adiabatic_exponent=\"MoistLeadingTerms\",\n", + " homogeneous_liquid_nucleation_rate='CNT',\n", + ")\n", + "CONST = FORMULAE.constants\n", + "\n", + "N_SD_PER_MODE = 20\n", + "DT = .05 * si.s\n", + "\n", + "C0_arr = np.array([1e2, 2.5e2, 5e2, 7.5e2, 1e3]) / si.cm**3\n", + "C0_arr = np.array([4e2, 4.25e2, 4.5e2, 5e2, 5.5e2, 6e2]) / si.cm**3" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-30T20:23:04.715504Z", + "start_time": "2025-01-30T20:21:37.309294Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "C0=4.00e+08\n", + "C0=4.25e+08\n", + "C0=4.50e+08\n", + "C0=5.00e+08\n", + "C0=5.50e+08\n", + "C0=6.00e+08\n" + ] + } + ], + "source": [ + "output_all = {}\n", + "\n", + "for Na in C0_arr:\n", + " key = f\"C0={Na:.2e}\"\n", + " print(key)\n", + " aerosol = AerosolChamber(\n", + " water_molar_volume=CONST.Mv / CONST.rho_w,\n", + " N=Na,\n", + " )\n", + " output = run_expansion(\n", + " formulae=FORMULAE,\n", + " aerosol=aerosol,\n", + " n_sd_per_mode=N_SD_PER_MODE,\n", + " n_sd_homo_liq_nucleation=100,\n", + " total_time=4*si.s,\n", + " dt=DT,\n", + " products=products,\n", + " )\n", + "\n", + " output_all[key] = output\n" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "4.021660843931165\n", + "0.33964378383596744\n", + "0.0197293310945584\n", + "7.870157403148062e-05\n", + "4.28580000171432e-07\n", + "0.0\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2025-02-13T20:58:52.082465\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d5c1bb95a07e4ff7b732ae5711345dbf", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(HTML(value=\"./C0_sweep_traces.pdf
\"), HT…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "variables = [\"Pressure\", \"Temperature\", \"Saturation ratio\", \"SD count\", r\"$q_\\ell$\", \"$q_v$\", \"$r_{eff}$\", \"$N_d$\"]\n", + "fig, axes = pyplot.subplots(2,4,figsize=(12,5), sharex=True, sharey=False, constrained_layout=True)\n", + "\n", + "for Na in C0_arr:\n", + " key = f\"C0={Na:.2e}\"\n", + " output = output_all[key]\n", + " init_mult = output.attributes[\"multiplicity\"][0]\n", + " final_mult = output.attributes[\"multiplicity\"][-1]\n", + " print((np.sum(final_mult) - np.sum(init_mult)) / np.sum(init_mult))\n", + " for i, ax in enumerate(axes.flatten()):\n", + " y = np.array(output.profile[variables[i]])\n", + " ax.plot(output.profile[\"t\"], y, label=f\"{in_unit(Na, 1/si.cm**3):.2e}\")\n", + " if i == 7:\n", + " ax.set_yscale(\"log\")\n", + " ax.set_ylim(3e2,8e2)\n", + " ax.set_xlabel(f\"Time [{output.units['t']}]\")\n", + " ax.set_ylabel(f\"{variables[i]} [{output.units[variables[i]]}]\")\n", + "\n", + "axes[0, 0].legend(title=\"$C_0$ [cm$^{-3}$]\")\n", + "show_plot(\"C0_sweep_traces.pdf\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-30T20:23:04.785100072Z", + "start_time": "2025-01-26T23:04:07.407334Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "dpi=100.0 case='base'\n", + "dpi=100.0 case='clean'\n", + "dpi=100.0 case='dry'\n", + "dpi=10000.0 case='base'\n", + "dpi=10000.0 case='clean'\n", + "dpi=10000.0 case='dry'\n", + "dpi=20000.0 case='base'\n", + "dpi=20000.0 case='clean'\n", + "dpi=20000.0 case='dry'\n", + "dpi=30000.0 case='base'\n", + "dpi=30000.0 case='clean'\n", + "dpi=30000.0 case='dry'\n", + "dpi=40000.0 case='base'\n", + "dpi=40000.0 case='clean'\n", + "dpi=40000.0 case='dry'\n", + "dpi=50000.0 case='base'\n", + "dpi=50000.0 case='clean'\n", + "dpi=50000.0 case='dry'\n" + ] + } + ], + "source": [ + "DP = np.insert(np.linspace(100, 500, 5), 0, 1) * si.hPa\n", + "P0 = 1000 * si.hPa\n", + "\n", + "cases = {\n", + " 'base': {'N': 2000 / si.cm**3, 'RH0': 0.5, 'color': 'green'},\n", + " 'clean': {'N': 1 / si.cm**3, 'RH0': 0.5, 'color': 'turquoise'},\n", + " 'dry': {'N': 1 / si.cm**3, 'RH0': 0, 'color': 'black'},\n", + "}\n", + "output = {\n", + " 'Smax': {case: [] for case in cases},\n", + " 'Tmin': {case: [] for case in cases},\n", + "}\n", + "for dpi in DP:\n", + " for case, case_params in cases.items():\n", + " print(f\"{dpi=} {case=}\")\n", + " aerosol = AerosolChamber(water_molar_volume=CONST.Mv / CONST.rho_w, N=case_params['N'])\n", + " out = run_expansion(\n", + " formulae=FORMULAE,\n", + " aerosol=aerosol,\n", + " n_sd_per_mode=N_SD_PER_MODE,\n", + " RH0=case_params['RH0'],\n", + " pf=(P0 - dpi),\n", + " dt=DT,\n", + " p0=P0,\n", + " products=products\n", + " )\n", + " output['Tmin'][case].append(np.nanmin(out.profile[\"Temperature\"]))\n", + " output['Smax'][case].append(np.nanmax(out.profile[\"Saturation ratio\"]))" + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "ExecuteTime": { + "end_time": "2025-01-30T20:18:02.702242840Z", + "start_time": "2025-01-26T23:10:40.873425Z" + } + }, + "outputs": [], + "source": [ + "def plot(_axes, _output, _cases, var):\n", + " for _case, _case_params in _cases.items():\n", + " _axes.plot(\n", + " DP / P0,\n", + " _output[var][_case],\n", + " color=_case_params['color'],\n", + " marker=\"o\",\n", + " ls=\"--\",\n", + " label=f\"$S_0={_case_params['RH0']}, C_0={in_unit(_case_params['N'], si.cm**-3)}$ cm$^{{-3}}$\"\n", + " )" + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": {}, + "outputs": [ + { + "data": { + "image/svg+xml": [ + "\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2025-02-13T22:46:14.753708\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ee6aed70075c4f35ae142be20468587b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(HTML(value=\"./Tmin_dp.pdf
\"), HTML(value=\"\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2025-02-13T21:24:00.638971\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "4970459fb58448c9bdd12c1471dea3d0", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(HTML(value=\"./dp_sweep.pdf
\"), HTML(value=\"\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " 2025-02-13T14:48:32.948908\n", + " image/svg+xml\n", + " \n", + " \n", + " Matplotlib v3.8.1, https://matplotlib.org/\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "\n" + ], + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f7bc96feb07a4b7a9a6738491c8d97c6", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "HBox(children=(HTML(value=\"./fig_3.pdf
\"), HTML(value=\"