|
| 1 | +# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring |
| 2 | +import numpy as np |
| 3 | +import pytest |
| 4 | +from matplotlib import pyplot |
| 5 | +from scipy import signal |
| 6 | + |
| 7 | +from PySDM import Builder |
| 8 | +from PySDM import products as PySDM_products |
| 9 | +from PySDM.backends import CPU |
| 10 | +from PySDM.backends.impl_numba.test_helpers import bdf |
| 11 | +from PySDM.dynamics import AmbientThermodynamics, Condensation |
| 12 | +from PySDM.environments import Parcel |
| 13 | +from PySDM.initialisation import equilibrate_wet_radii |
| 14 | +from PySDM.initialisation.sampling.spectral_sampling import ConstantMultiplicity |
| 15 | +from PySDM.initialisation.spectra import Lognormal |
| 16 | +from PySDM.physics import si |
| 17 | + |
| 18 | + |
| 19 | +@pytest.mark.parametrize( |
| 20 | + "rtol_thd", |
| 21 | + ( |
| 22 | + pytest.param(1e-6, marks=pytest.mark.xfail(strict=True)), |
| 23 | + pytest.param(1e-7, marks=pytest.mark.xfail(strict=True)), |
| 24 | + 1e-8, |
| 25 | + 1e-9, |
| 26 | + ), |
| 27 | +) |
| 28 | +@pytest.mark.parametrize("rtol_x", (1e-7,)) |
| 29 | +@pytest.mark.parametrize("adaptive", (True,)) |
| 30 | +@pytest.mark.parametrize("scheme", ("PySDM",)) |
| 31 | +def test_single_supersaturation_peak(adaptive, scheme, rtol_x, rtol_thd, plot=False): |
| 32 | + # arrange |
| 33 | + products = ( |
| 34 | + PySDM_products.WaterMixingRatio(unit="g/kg", name="ql"), |
| 35 | + PySDM_products.PeakSupersaturation(name="S max"), |
| 36 | + PySDM_products.AmbientRelativeHumidity(name="RH"), |
| 37 | + PySDM_products.ParcelDisplacement(name="z"), |
| 38 | + ) |
| 39 | + env = Parcel( |
| 40 | + dt=2 * si.s, |
| 41 | + mass_of_dry_air=1e3 * si.kg, |
| 42 | + p0=1000 * si.hPa, |
| 43 | + q0=22.76 * si.g / si.kg, |
| 44 | + w=0.5 * si.m / si.s, |
| 45 | + T0=300 * si.K, |
| 46 | + ) |
| 47 | + n_steps = 70 |
| 48 | + n_sd = 2 |
| 49 | + kappa = 0.4 |
| 50 | + spectrum = Lognormal(norm_factor=5000 / si.cm**3, m_mode=50.0 * si.nm, s_geom=2.0) |
| 51 | + builder = Builder(backend=CPU(), n_sd=n_sd) |
| 52 | + builder.set_environment(env) |
| 53 | + builder.add_dynamic(AmbientThermodynamics()) |
| 54 | + builder.add_dynamic( |
| 55 | + Condensation( |
| 56 | + adaptive=adaptive, |
| 57 | + rtol_x=rtol_x, |
| 58 | + rtol_thd=rtol_thd, |
| 59 | + ) |
| 60 | + ) |
| 61 | + |
| 62 | + r_dry, concentration = ConstantMultiplicity(spectrum).sample(n_sd) |
| 63 | + v_dry = builder.formulae.trivia.volume(radius=r_dry) |
| 64 | + r_wet = equilibrate_wet_radii( |
| 65 | + r_dry=r_dry, environment=env, kappa_times_dry_volume=kappa * v_dry |
| 66 | + ) |
| 67 | + specific_concentration = concentration / builder.formulae.constants.rho_STP |
| 68 | + attributes = { |
| 69 | + "n": specific_concentration * env.mass_of_dry_air, |
| 70 | + "dry volume": v_dry, |
| 71 | + "kappa times dry volume": kappa * v_dry, |
| 72 | + "volume": builder.formulae.trivia.volume(radius=r_wet), |
| 73 | + } |
| 74 | + |
| 75 | + particulator = builder.build(attributes, products=products) |
| 76 | + |
| 77 | + if scheme == "BDF": |
| 78 | + bdf.patch_particulator(particulator) |
| 79 | + |
| 80 | + output = {product.name: [] for product in particulator.products.values()} |
| 81 | + output_attributes = {"volume": tuple([] for _ in range(particulator.n_sd))} |
| 82 | + |
| 83 | + # act |
| 84 | + for _ in range(n_steps): |
| 85 | + particulator.run(steps=1) |
| 86 | + for product in particulator.products.values(): |
| 87 | + value = product.get() |
| 88 | + output[product.name].append(value[0]) |
| 89 | + for key, attr in output_attributes.items(): |
| 90 | + attr_data = particulator.attributes[key].to_ndarray() |
| 91 | + for drop_id in range(particulator.n_sd): |
| 92 | + attr[drop_id].append(attr_data[drop_id]) |
| 93 | + |
| 94 | + # plot |
| 95 | + for drop_id, volume in enumerate(output_attributes["volume"]): |
| 96 | + pyplot.semilogx( |
| 97 | + particulator.formulae.trivia.radius(volume=np.asarray(volume)) / si.um, |
| 98 | + output["z"], |
| 99 | + color="black", |
| 100 | + label="drop size (bottom axis)", |
| 101 | + ) |
| 102 | + pyplot.xlabel("radius [um]") |
| 103 | + pyplot.ylabel("z [m]") |
| 104 | + twin = pyplot.twiny() |
| 105 | + twin.plot(output["S max"], output["z"], label="S max (top axis)") |
| 106 | + twin.plot(np.asarray(output["RH"]) - 1, output["z"], label="ambient RH (top axis)") |
| 107 | + twin.legend(loc="upper center") |
| 108 | + twin.set_xlim(-0.001, 0.0015) |
| 109 | + pyplot.legend(loc="lower right") |
| 110 | + pyplot.grid() |
| 111 | + pyplot.title(f"rtol_thd={rtol_thd}; rtol_x={rtol_x}") |
| 112 | + if plot: |
| 113 | + pyplot.show() |
| 114 | + |
| 115 | + # assert |
| 116 | + assert signal.argrelextrema(np.asarray(output["RH"]), np.greater)[0].shape[0] == 1 |
0 commit comments