Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
121ed92
added new homogeneous freezing example
tluettm Jul 8, 2025
b23b733
expanded freezing temperature unit test, and notebook
tluettm Jul 8, 2025
7f269b1
fix mark_updated calls for freezing
slayoo Jul 8, 2025
fc604f9
Merge pull request #3 from slayoo/tim/new_hom_freezing_example
tluettm Jul 8, 2025
22aa786
finished example setup for threshold homogeneous freezing
tluettm Jul 8, 2025
4b56496
expanded hom freezing example
tluettm Jul 9, 2025
0345a1a
Changed plot function
tluettm Jul 10, 2025
2e65c9f
added back freezing temperature histogram plots
tluettm Jul 10, 2025
87cd676
added ensemble of realisations
tluettm Jul 11, 2025
03529b1
draft of a smoke test + avoiding relative imports in the notebook to …
slayoo Jul 11, 2025
a53d448
note in the noteboo
slayoo Jul 11, 2025
5215b77
added ensemble for numer of super droplets
tluettm Jul 11, 2025
823cbaf
added vertical updraft ensemble
tluettm Jul 14, 2025
3d53216
Merge remote-tracking branch 'upstream' into new_hom_freezing_example
tluettm Jul 14, 2025
e11348e
added updraft ensemble and notebook cleanup
tluettm Jul 16, 2025
c8b7d28
added ensemble for ccn concentration / droplet size
tluettm Jul 17, 2025
2403fa3
new unit test for homogeneous nucleation rate and changed saturation …
tluettm Jul 19, 2025
b4c49ad
addressing pylint
tluettm Jul 21, 2025
21e4d88
added vapour deposition on ice to notebook example
tluettm Jul 23, 2025
3096742
further tinkering with simulation setups
tluettm Jul 27, 2025
83c6659
work done on the example notebook
tluettm Aug 5, 2025
aca1e86
Merge branch 'main' into new_hom_freezing_example
tluettm Aug 6, 2025
90da071
'signed water mass' changes applied to scipy solver. scipy solver too…
tluettm Sep 1, 2025
e12ed99
addressing pylint
tluettm Sep 2, 2025
7d8f046
additions to __init__.py
tluettm Sep 2, 2025
2964499
improvements to hom_freezing_example
tluettm Sep 9, 2025
8b46b6a
splitted hom_freezing.ipynb intwo two parts, improvements to simulati…
tluettm Sep 11, 2025
7a56320
precommit check for newly added files
tluettm Sep 11, 2025
ac5fc4c
added hom. freezing example with coalescence
tluettm Sep 12, 2025
0a053cb
extended hom. freezing example with coalescence
tluettm Sep 12, 2025
6306564
Added plot names to show_plot()
tluettm Sep 17, 2025
223147b
Refresh notebook
tluettm Sep 20, 2025
40c782e
Update to the plots and (temporary) change to module imports
tluettm Sep 22, 2025
65aad6a
added_new_plot_type
tluettm Sep 23, 2025
0e2acaa
added_new_plot variable
tluettm Sep 23, 2025
9cffb9a
added plots for updraft ensemble
tluettm Sep 24, 2025
6ea8681
updated notebooks
tluettm Sep 29, 2025
f66516f
Merge branch 'main' into new_hom_freezing_example
tluettm Sep 29, 2025
843620c
changed dry to wet radius initialisation and added lucky droplet meth…
tluettm Sep 30, 2025
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
3 changes: 3 additions & 0 deletions PySDM/particulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ def homogeneous_freezing_time_dependent(self, *, rand: Storage):
temperature=self.environment["T"],
relative_humidity_ice=self.environment["RH_ice"],
)
self.attributes.mark_updated("signed water mass")

def homogeneous_freezing_threshold(self):
self.backend.homogeneous_freezing_threshold(
Expand All @@ -560,6 +561,7 @@ def homogeneous_freezing_threshold(self):
temperature=self.environment["T"],
relative_humidity_ice=self.environment["RH_ice"],
)
self.attributes.mark_updated("signed water mass")

def thaw_instantaneous(self):
self.backend.thaw_instantaneous(
Expand All @@ -569,3 +571,4 @@ def thaw_instantaneous(self):
cell=self.attributes["cell id"],
temperature=self.environment["T"],
)
self.attributes.mark_updated("signed water mass")
5 changes: 5 additions & 0 deletions examples/PySDM_examples/homogeneous_freezing/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Homogeneous freezing example
"""

from .settings import Settings
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's add a smoke test, e.g. checking the Tfz histograms

Large diffs are not rendered by default.

107 changes: 107 additions & 0 deletions examples/PySDM_examples/homogeneous_freezing/settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import time

import numpy as np
from pystrict import strict

from PySDM import Formulae
from PySDM.physics.constants import si
from PySDM.initialisation.spectra import Lognormal
from PySDM.initialisation.sampling import spectral_sampling
from tests.unit_tests.backends.test_oxidation import formulae


class Settings:
def __init__(
self,
*,
n_sd: int,
w_updraft: float,
T0: float,
dt: float,
N_dv_droplet_distribution: float,
r_mean_droplet_distribution: float,
sigma_droplet_distribution: float = None,
type_droplet_distribution: str,
p0: float = 200 * si.hectopascals,
RH_0: float = 1.0,
kappa: float = 0.64,
condensation_enable: bool=True,
deposition_enable: bool=True,
hom_freezing: str=None,
):

print("Setting up simulation with " + hom_freezing)
self.n_sd = n_sd
self.w_updraft = w_updraft
self.N_dv_droplet_distribution = N_dv_droplet_distribution
self.r_mean_droplet_distribution = r_mean_droplet_distribution
self.sigma_droplet_distribution = sigma_droplet_distribution
self.type_droplet_distribution = type_droplet_distribution

self.mass_of_dry_air = 1000 * si.kilogram
self.initial_pressure = p0
self.initial_water_supersaturation = RH_0
# self.initial_ice_supersaturation = RHi_0
self.kappa = kappa
self.initial_temperature = T0

self.condensation_enable = condensation_enable
self.deposition_enable = deposition_enable



if hom_freezing == "threshold":
hom_nucleation_rate = "Null"
self.hom_freezing_type = "threshold"
else:
hom_nucleation_rate = hom_freezing
self.hom_freezing_type = "time-dependent"

formulae = Formulae(
particle_shape_and_density="MixedPhaseSpheres",
homogeneous_ice_nucleation_rate=hom_nucleation_rate,
seed=time.time_ns()
)

self.formulae = formulae

const = self.formulae.constants
# pvs_i = self.formulae.saturation_vapour_pressure.pvs_ice(self.initial_temperature)
pvs_w = self.formulae.saturation_vapour_pressure.pvs_water(
self.initial_temperature
)
self.initial_water_vapour_mixing_ratio = const.eps / (
self.initial_pressure / self.initial_water_supersaturation / pvs_w - 1
)

dry_air_density = (
self.formulae.trivia.p_d(
self.initial_pressure, self.initial_water_vapour_mixing_ratio
)
/ self.initial_temperature
/ const.Rd
)

if self.type_droplet_distribution == ("monodisperse"):
self.r_dry = np.ones(self.n_sd) * r_mean_droplet_distribution
self.specific_concentration = (
np.ones(self.n_sd)
* N_dv_droplet_distribution
/ self.n_sd
/ dry_air_density
)

elif self.type_droplet_distribution == ("lognormal"):
spectrum = Lognormal(
norm_factor=N_dv_droplet_distribution / dry_air_density,
m_mode=r_mean_droplet_distribution,
s_geom=sigma_droplet_distribution,
)

self.r_dry, self.specific_concentration = spectral_sampling.Linear(
spectrum
).sample(n_sd)

self.t_max_duration = 7200 # 3600 * 1.5 # total duration of simulation
self.dt = dt
self.n_output = 10 # int(self.t_duration / 100) #100 # number of output steps
174 changes: 174 additions & 0 deletions examples/PySDM_examples/homogeneous_freezing/simulation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import numpy as np

import PySDM.products as PySDM_products
from PySDM.backends import CPU
from PySDM.builder import Builder
from PySDM.dynamics import (
AmbientThermodynamics,
Condensation,
Freezing,
VapourDepositionOnIce,
)
from PySDM.environments import Parcel
from PySDM.physics import constants as const
from PySDM.initialisation import discretise_multiplicities, equilibrate_wet_radii


class Simulation:
def __init__(self, settings, backend=CPU):

dt = settings.dt

formulae = settings.formulae

env = Parcel(
mixed_phase=True,
dt=dt,
mass_of_dry_air=settings.mass_of_dry_air,
p0=settings.initial_pressure,
initial_water_vapour_mixing_ratio=settings.initial_water_vapour_mixing_ratio,
T0=settings.initial_temperature,
w=settings.w_updraft,
)

builder = Builder(
backend=backend(
formulae=settings.formulae,
**(
{"override_jit_flags": {"parallel": False}}
if backend == CPU
else {}
),
),
n_sd=settings.n_sd,
environment=env,
)

builder.add_dynamic(AmbientThermodynamics())
if settings.condensation_enable:
builder.add_dynamic(Condensation())
if settings.deposition_enable:
builder.add_dynamic(VapourDepositionOnIce())
builder.add_dynamic(
Freezing(
homogeneous_freezing=settings.hom_freezing_type, immersion_freezing=None
)
)

self.n_sd = settings.n_sd
self.multiplicities = discretise_multiplicities(
settings.specific_concentration * env.mass_of_dry_air
)
self.r_dry = settings.r_dry
v_dry = settings.formulae.trivia.volume(radius=self.r_dry)
kappa = settings.kappa

self.r_wet = equilibrate_wet_radii(
r_dry=self.r_dry,
environment=builder.particulator.environment,
kappa_times_dry_volume=kappa * v_dry,
)
attributes = {
"multiplicity": self.multiplicities,
"dry volume": v_dry,
"kappa times dry volume": kappa * v_dry,
"signed water mass": formulae.particle_shape_and_density.radius_to_mass(
self.r_wet
),
}
builder.request_attribute("temperature of last freezing")

products = [
PySDM_products.ParcelDisplacement(name="z"),
PySDM_products.Time(name="t"),
PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
PySDM_products.AmbientRelativeHumidity(name="RH_ice", unit="%"),
PySDM_products.AmbientTemperature(name="T"),
PySDM_products.AmbientPressure(name="p", unit="hPa"),
PySDM_products.WaterMixingRatio(name="water", radius_range=(0, np.inf)),
PySDM_products.WaterMixingRatio(name="ice", radius_range=(-np.inf, 0)),
PySDM_products.WaterMixingRatio(
name="total", radius_range=(-np.inf, np.inf)
),
PySDM_products.AmbientWaterVapourMixingRatio(
name="vapour", var="water_vapour_mixing_ratio"
),
PySDM_products.ParticleConcentration(
name="n_s", unit="1/cm**3", radius_range=(0, np.inf)
),
PySDM_products.ParticleConcentration(
name="n_i", unit="1/cm**3", radius_range=(-np.inf, 0)
),
PySDM_products.MeanRadius(name="r_s", unit="µm", radius_range=(0, np.inf)),
PySDM_products.MeanRadius(name="r_i", unit="µm", radius_range=(-np.inf, 0)),
]

self.particulator = builder.build(attributes, products)

self.n_output = settings.n_output
self.n_substeps = int(self.n_output / dt)
self.t_max_duration = settings.t_max_duration

def save(self, output):
cell_id = 0

output["z"].append(self.particulator.products["z"].get()[cell_id])
output["t"].append(self.particulator.products["t"].get())
output["RH"].append(self.particulator.products["RH"].get()[cell_id])
output["RHi"].append(self.particulator.products["RH_ice"].get()[cell_id])
output["T"].append(self.particulator.products["T"].get()[cell_id])
output["P"].append(self.particulator.products["p"].get()[cell_id])
output["LWC"].append(self.particulator.products["water"].get()[cell_id])
output["IWC"].append(self.particulator.products["ice"].get()[cell_id])
# output["TWC"].append(self.particulator.products["total"].get()[cell_id])
output["qv"].append(self.particulator.products["vapour"].get()[cell_id])
output["ns"].append(self.particulator.products["n_s"].get()[cell_id])
output["ni"].append(self.particulator.products["n_i"].get()[cell_id])
output["rs"].append(self.particulator.products["r_s"].get()[cell_id])
output["ri"].append(self.particulator.products["r_i"].get()[cell_id])
output["water_mass"].append(
self.particulator.attributes["signed water mass"].data.tolist()
)
output["T_frz"].append(
self.particulator.attributes["temperature of last freezing"].data.tolist()
)

def run(self):

print( "Starting simulation..." )

output = {
"t": [],
"z": [],
"RH": [],
"RHi": [],
"T": [],
"P": [],
"LWC": [],
"IWC": [],
"qv": [],
"ns": [],
"ni": [],
"rs": [],
"ri": [],
"water_mass": [],
"T_frz": [],
}

self.save(output)

while True:

self.particulator.run(self.n_substeps)
self.save(output)

# print( output["t"][-1], output["T"][-1], output["LWC"][-1], output["IWC"][-1] )

if output["LWC"][-1] == 0:
print("break due to LWC")
break
if output["t"][-1] >= self.t_max_duration:
print("time exceeded")
break

return output
Loading
Loading