def test_moment_0d(): # Arrange n_part = 10000 v_mean = 2e-6 d = 1.2 v_min = 0.01e-6 v_max = 10e-6 n_sd = 32 spectrum = Lognormal(n_part, v_mean, d) v, n = linear(n_sd, spectrum, (v_min, v_max)) T = np.full_like(v, 300.) n = discretise_n(n) particles = DummyCore(backend, n_sd) attribute = {'n': n, 'volume': v, 'temperature': T} particles.build(attribute) state = particles.state true_mean, true_var = spectrum.stats(moments='mv') # TODO: add a moments_0 wrapper moment_0 = particles.backend.Storage.empty((1,), dtype=int) moments = particles.backend.Storage.empty((1, 1), dtype=float) # Act state.moments(moment_0, moments, specs={'volume': (0,)}) discr_zero = moments[0, 0] state.moments(moment_0, moments, specs={'volume': (1,)}) discr_mean = moments[0, 0] state.moments(moment_0, moments, specs={'volume': (2,)}) discr_mean_radius_squared = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (0,)}) discr_zero_T = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (1,)}) discr_mean_T = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (2,)}) discr_mean_T_squared = moments[0, 0] # Assert assert abs(discr_zero - 1) / 1 < 1e-3 assert abs(discr_mean - true_mean) / true_mean < .01e-1 true_mrsq = true_var + true_mean**2 assert abs(discr_mean_radius_squared - true_mrsq) / true_mrsq < .05e-1 assert discr_zero_T == discr_zero assert discr_mean_T == 300. assert discr_mean_T_squared == 300. ** 2
def test_moment_0d(backend): # Arrange n_part = 100000 v_mean = 2e-6 d = 1.2 n_sd = 32 spectrum = Lognormal(n_part, v_mean, d) v, n = Linear(spectrum).sample(n_sd) T = np.full_like(v, 300.) n = discretise_n(n) particles = DummyCore(backend, n_sd) attribute = {'n': n, 'volume': v, 'temperature': T, 'heat': T * v} particles.build(attribute) state = particles.particles true_mean, true_var = spectrum.stats(moments='mv') # TODO #217 : add a moments_0 wrapper moment_0 = particles.backend.Storage.empty((1, ), dtype=int) moments = particles.backend.Storage.empty((1, 1), dtype=float) # Act state.moments(moment_0, moments, specs={'volume': (0, )}) discr_zero = moments[0, 0] state.moments(moment_0, moments, specs={'volume': (1, )}) discr_mean = moments[0, 0] state.moments(moment_0, moments, specs={'volume': (2, )}) discr_mean_radius_squared = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (0, )}) discr_zero_T = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (1, )}) discr_mean_T = moments[0, 0] state.moments(moment_0, moments, specs={'temperature': (2, )}) discr_mean_T_squared = moments[0, 0] # Assert assert abs(discr_zero - 1) / 1 < 1e-3 assert abs(discr_mean - true_mean) / true_mean < .01e-1 true_mrsq = true_var + true_mean**2 assert abs(discr_mean_radius_squared - true_mrsq) / true_mrsq < .05e-1 assert discr_zero_T == discr_zero assert discr_mean_T == 300. np.testing.assert_approx_equal(discr_mean_T_squared, 300.**2, significant=6)
def test_size_distribution_r_mode(): # Arrange s = 1.001 r_mode = 1e-6 sut = Lognormal(1, r_mode, s) # Act m, _ = np.linspace(.01e-6, 100e-6, 10000, retstep=True) sd = sut.size_distribution(m) # Assert assert_approx_equal(m[sd == np.amax(sd)], r_mode, 2)
def test_size_distribution_n_part(): # Arrange s = 1.5 n_part = 256 sut = Lognormal(n_part, .5e-5, s) # Act m, dm = np.linspace(.1e-6, 100e-6, 100, retstep=True) sd = sut.size_distribution(m) # Assert assert_approx_equal(np.sum(sd) * dm, n_part, 4)
def freezing_inp_spec(self): if self.ui_freezing["INP surface"].value == "as dry surface": return None if self.ui_freezing["INP surface"].value == "lognormal(A, sgm_g)": return Lognormal( norm_factor=1, m_mode=10 ** (self.ui_freezing["lognormal_log10_A_um2"].value) * si.um**2, s_geom=np.exp(self.ui_freezing["lognormal_ln_sgm_g"].value), ) raise NotImplementedError()
def test_spectral_discretisation(discretisation): # Arrange n_sd = 100 m_mode = .5e-5 n_part = 256 * 16 s_geom = 1.5 spectrum = Lognormal(n_part, m_mode, s_geom) m_range = (.1e-6, 100e-6) # Act m, n = discretisation(n_sd, spectrum, m_range) # Assert assert m.shape == n.shape assert n.shape == (n_sd, ) assert np.min(m) >= m_range[0] assert np.max(m) <= m_range[1] actual = np.sum(n) desired = spectrum.cumulative(m_range[1]) - spectrum.cumulative(m_range[0]) quotient = actual / desired np.testing.assert_almost_equal(actual=quotient, desired=1.0, decimal=2)
def test_supersaturation_and_temperature_profile(s_max, s_250m, T_250m): # arrange settings = Settings( dz = 1 * si.m, n_sd_per_mode = (5, 5), aerosol_modes_by_kappa = { .54: Lognormal( norm_factor=850 / si.cm ** 3, m_mode=15 * si.nm, s_geom=1.6 ), 1.2: Lognormal( norm_factor=10 / si.cm ** 3, m_mode=850 * si.nm, s_geom=1.2 ) }, vertical_velocity = 1.0 * si.m / si.s, initial_pressure = 775 * si.mbar, initial_temperature = 274 * si.K, initial_relative_humidity = .98, displacement = 250 * si.m, formulae = Formulae(constants={'MAC': .3}) ) simulation = Simulation(settings, products=( ParcelDisplacement( name='z'), PeakSupersaturation( name='S_max', unit='%'), AmbientTemperature( name='T'), )) # act output = simulation.run() # assert np.testing.assert_approx_equal(np.nanmax(output['products']['S_max']), s_max, significant=2) np.testing.assert_approx_equal(output['products']['S_max'][-1], s_250m, significant=2) np.testing.assert_approx_equal(output['products']['T'][-1], T_250m, significant=2)
class TestSum: scale = 1 n_part = 256 exponential = Exponential(n_part, scale) s = 1.001 r_mode = 1e-6 lognormal = Lognormal(1, r_mode, s) @staticmethod def test_size_distribution(): # Arrange sut = Sum((TestSum.exponential, )) # Act x = np.linspace(0, 1) sut_sd = sut.size_distribution(x) exp_sd = TestSum.exponential.size_distribution(x) # Assert np.testing.assert_array_equal(sut_sd, exp_sd) @staticmethod def test_cumulative(): # Arrange sut = Sum((TestSum.exponential, )) # Act x = np.linspace(0, 1) sut_c = sut.cumulative(x) exp_c = TestSum.exponential.cumulative(x) # Assert np.testing.assert_array_equal(sut_c, exp_c) @staticmethod @pytest.mark.parametrize("distributions", [ pytest.param((exponential, ), id="single exponential"), pytest.param((lognormal, ), id="single lognormal"), pytest.param((exponential, exponential), id="2 exponentials") ]) def test_percentiles(distributions): # Arrange sut = Sum(distributions) # Act cdf_values = np.linspace(*default_cdf_range, 100) sut_p = sut.percentiles(cdf_values) exp_p = distributions[0].percentiles(cdf_values) # Assert np.testing.assert_array_almost_equal(sut_p, exp_p, decimal=3)
def __init__(self, n_sd=100, dt_output=1 * si.second, dt_max=1 * si.second): self.n_steps = int(self.total_time / (5 * si.second)) # TODO: rename to n_output self.n_sd = n_sd self.r_dry, self.n = spectral_sampling.Logarithmic( spectrum=Lognormal(norm_factor=1000 / si.milligram * self.mass_of_dry_air, m_mode=50 * si.nanometre, s_geom=1.4), size_range=(10.633 * si.nanometre, 513.06 * si.nanometre)).sample(n_sd) self.dt_max = dt_max self.dt_output = dt_output self.r_bins_edges = np.linspace(0 * si.micrometre, 20 * si.micrometre, 101, endpoint=True)
def test_final_state(croupier): # Arrange n_part = 10000 v_mean = 2e-6 d = 1.2 v_min = 0.01e-6 v_max = 10e-6 n_sd = 64 x = 4 y = 4 attributes = {} spectrum = Lognormal(n_part, v_mean, d) attributes['volume'], attributes['n'] = linear(n_sd, spectrum, (v_min, v_max)) particles = DummyParticles(backend, n_sd) particles.set_environment(DummyEnvironment, {'grid': (x, y)}) particles.croupier = croupier attributes['cell id'] = backend.array((n_sd, ), dtype=int) cell_origin_np = np.concatenate( [np.random.randint(0, x, n_sd), np.random.randint(0, y, n_sd)]).reshape((2, -1)) attributes['cell origin'] = backend.from_ndarray(cell_origin_np) position_in_cell_np = np.concatenate( [np.random.rand(n_sd), np.random.rand(n_sd)]).reshape((2, -1)) attributes['position in cell'] = backend.from_ndarray(position_in_cell_np) particles.get_particles(attributes) # Act u01 = backend.from_ndarray(np.random.random(n_sd)) particles.permute(u01) _ = particles.state.cell_start # Assert assert (np.diff(particles.state['cell id'][particles.state._State__idx]) >= 0).all()
def test_final_state(croupier, backend): from PySDM.backends import ThrustRTC if backend is ThrustRTC: return # TODO # Arrange n_part = 10000 v_mean = 2e-6 d = 1.2 v_min = 0.01e-6 v_max = 10e-6 n_sd = 64 x = 4 y = 4 attributes = {} spectrum = Lognormal(n_part, v_mean, d) attributes['volume'], attributes['n'] = Linear(spectrum, (v_min, v_max)).sample(n_sd) core = DummyCore(backend, n_sd) core.environment = DummyEnvironment(grid=(x, y)) core.croupier = croupier attributes['cell id'] = np.array((n_sd,), dtype=int) cell_origin_np = np.concatenate([np.random.randint(0, x, n_sd), np.random.randint(0, y, n_sd)]).reshape((2, -1)) attributes['cell origin'] = cell_origin_np position_in_cell_np = np.concatenate([np.random.rand(n_sd), np.random.rand(n_sd)]).reshape((2, -1)) attributes['position in cell'] = position_in_cell_np core.build(attributes) # Act u01 = backend.Storage.from_ndarray(np.random.random(n_sd)) core.particles.permutation(u01) _ = core.particles.cell_start # Assert assert (np.diff(core.particles['cell id'][core.particles._Particles__idx]) >= 0).all()
class Setup: backend = Default condensation_coord = 'volume' condensation_rtol_x = condensation.default_rtol_x condensation_rtol_thd = condensation.default_rtol_thd adaptive = True grid = (25, 25) size = (1500 * si.metres, 1500 * si.metres) n_sd_per_gridbox = 20 rho_w_max = .6 * si.metres / si.seconds * (si.kilogram / si.metre**3) # output steps n_steps = 3600 outfreq = 60 dt = 1 * si.seconds v_bins = phys.volume( np.logspace(np.log10(0.01 * si.micrometre), np.log10(100 * si.micrometre), 101, endpoint=True)) @property def steps(self): return np.arange(0, self.n_steps + 1, self.outfreq) # TODO: second mode spectrum_per_mass_of_dry_air = Lognormal(norm_factor=40 / si.centimetre**3 / const.rho_STP, m_mode=0.15 * si.micrometre, s_geom=1.6) processes = { "particle advection": True, "fluid advection": True, "coalescence": False, "condensation": True, "sedimentation": False, # "relaxation": False # TODO } enable_particle_temperatures = False mpdata_iters = 2 mpdata_iga = True mpdata_fct = True mpdata_tot = True th_std0 = 289 * si.kelvins qv0 = 7.5 * si.grams / si.kilogram p0 = 1015 * si.hectopascals kappa = 1 @property def field_values(self): return {'th': phys.th_dry(self.th_std0, self.qv0), 'qv': self.qv0} @property def n_sd(self): return self.grid[0] * self.grid[1] * self.n_sd_per_gridbox def stream_function(self, xX, zZ): X = self.size[0] return -self.rho_w_max * X / np.pi * np.sin(np.pi * zZ) * np.cos( 2 * np.pi * xX) def rhod(self, zZ): Z = self.size[1] z = zZ * Z # :) # hydrostatic profile kappa = const.Rd / const.c_pd arg = np.power( self.p0 / const.p1000, kappa) - z * kappa * const.g / self.th_std0 / phys.R(self.qv0) p = const.p1000 * np.power(arg, 1 / kappa) # np.testing.assert_array_less(p, Setup.p0) # TODO: less or equal # density using "dry" potential temp. pd = p * (1 - self.qv0 / (self.qv0 + const.eps)) rhod = pd / (np.power(p / const.p1000, kappa) * const.Rd * self.th_std0) return rhod # initial dry radius discretisation range r_min = .01 * si.micrometre r_max = 5 * si.micrometre kernel = Gravitational(collection_efficiency=1) # [s-1] # TODO! aerosol_radius_threshold = 1 * si.micrometre n_spin_up = 1 * si.hour / dt
def __init__(self, *, volume=1 * si.cm**3): super().__init__( volume=volume, data={ "Iso1": { "ISA": Lognormal(norm_factor=1000 / volume, m_mode=1e-5 * si.cm**2, s_geom=1), "color": "#298131", "J_het": 1e3 / si.cm**2 / si.s, }, "Iso2": { "ISA": Lognormal(norm_factor=30 / volume, m_mode=1e-5 * si.cm**2, s_geom=1), "color": "#9ACFA4", "J_het": 1e3 / si.cm**2 / si.s, }, "Iso3": { "ISA": Lognormal(norm_factor=1000 / volume, m_mode=1e-5 * si.cm**2, s_geom=10), "color": "#1A62B4", "J_het": 1e3 / si.cm**2 / si.s, }, "Iso4": { "ISA": Lognormal(norm_factor=30 / volume, m_mode=1e-5 * si.cm**2, s_geom=10), "color": "#95BDE1", "J_het": 1e3 / si.cm**2 / si.s, }, "IsoWR": { "ISA": Lognormal( norm_factor=1000 / volume, m_mode=6.4e-3 * si.cm**2, s_geom=9.5, ), "color": "#FED2B0", "J_het": 6e-4 / si.cm**2 / si.s, }, "IsoBR": { "ISA": TopHat( norm_factor=63 / volume, endpoints=(9.4e-8 * si.cm**2, 7.5e-7 * si.cm**2), ), "color": "#FED2B0", "J_het": 2.8e3 / si.cm**2 / si.s, }, "IsoHE1": { "ISA": Lognormal(norm_factor=40 / volume, m_mode=1.2 * si.cm**2, s_geom=2.2), "color": "#FED2B0", "J_het": 4.1e-3 / si.cm**2 / si.s, }, "IsoHE2": { "ISA": Lognormal(norm_factor=40 / volume, m_mode=2e-2 * si.cm**2, s_geom=8.5), "color": "#FED2B0", "J_het": 2e-2 / si.cm**2 / si.s, }, "IsoDI1": { "ISA": Lognormal(norm_factor=45 / volume, m_mode=5.1e-1 * si.cm**2, s_geom=3.2), "J_het": 1.8e-2 / si.cm**2 / si.s, "color": "#9ACFA4", }, "IsoDI2": { "ISA": Lognormal(norm_factor=45 / volume, m_mode=5.1e-2 * si.cm**2, s_geom=3.2), "J_het": 1 / si.cm**2 / si.s, "color": "#FED2B0", }, "IsoDI3": { "ISA": Lognormal(norm_factor=45 / volume, m_mode=5.1e-1 * si.cm**2, s_geom=3.2), "J_het": 1 / si.cm**2 / si.s, "color": "#95BDE1", }, }, )
class Settings: def __dir__(self) -> Iterable[str]: return 'dt', 'grid', 'size', 'n_spin_up', 'versions', 'outfreq' def __init__(self): key_packages = (PySDM, numba, numpy, scipy) self.versions = str( {pkg.__name__: pkg.__version__ for pkg in key_packages}) # TODO: move all below into __init__ as self.* variables condensation_coord = 'volume logarithm' condensation_rtol_x = condensation.default_rtol_x condensation_rtol_thd = condensation.default_rtol_thd adaptive = True grid = (25, 25) size = (1500 * si.metres, 1500 * si.metres) n_sd_per_gridbox = 20 rho_w_max = .6 * si.metres / si.seconds * (si.kilogram / si.metre**3) # output steps n_steps = 5400 outfreq = 60 dt = 1 * si.seconds n_spin_up = 1 * si.hour / dt v_bins = phys.volume( np.logspace(np.log10(0.01 * si.micrometre), np.log10(100 * si.micrometre), 101, endpoint=True)) @property def steps(self): return np.arange(0, self.n_steps + 1, self.outfreq) mode_1 = Lognormal(norm_factor=60 / si.centimetre**3 / const.rho_STP, m_mode=0.04 * si.micrometre, s_geom=1.4) mode_2 = Lognormal(norm_factor=40 / si.centimetre**3 / const.rho_STP, m_mode=0.15 * si.micrometre, s_geom=1.6) spectrum_per_mass_of_dry_air = Sum((mode_1, mode_2)) processes = { "particle advection": True, "fluid advection": True, "coalescence": True, "condensation": True, "sedimentation": True, # "relaxation": False # TODO } enable_particle_temperatures = False mpdata_iters = 2 mpdata_iga = True mpdata_fct = True mpdata_tot = True th_std0 = 289 * si.kelvins qv0 = 7.5 * si.grams / si.kilogram p0 = 1015 * si.hectopascals kappa = 1 @property def field_values(self): return {'th': phys.th_dry(self.th_std0, self.qv0), 'qv': self.qv0} @property def n_sd(self): return self.grid[0] * self.grid[1] * self.n_sd_per_gridbox def stream_function(self, xX, zZ): X = self.size[0] return -self.rho_w_max * X / np.pi * np.sin(np.pi * zZ) * np.cos( 2 * np.pi * xX) def rhod(self, zZ): Z = self.size[1] z = zZ * Z # :(! # TODO: move to PySDM/physics # hydrostatic profile kappa = const.Rd / const.c_pd arg = np.power( self.p0 / const.p1000, kappa) - z * kappa * const.g / self.th_std0 / phys.R(self.qv0) p = const.p1000 * np.power(arg, 1 / kappa) # density using "dry" potential temp. pd = p * (1 - self.qv0 / (self.qv0 + const.eps)) rhod = pd / (np.power(p / const.p1000, kappa) * const.Rd * self.th_std0) return rhod kernel = Geometric(collection_efficiency=1) aerosol_radius_threshold = .5 * si.micrometre drizzle_radius_threshold = 25 * si.micrometre