def test_single_collision_bounce(params, backend_class = CPU): # Arrange n_sd = 2 builder = Builder(n_sd, backend_class()) builder.set_environment(Box(dv=np.NaN, dt=np.NaN)) n_init = [1, 1] particulator = builder.build(attributes = { "n": np.asarray(n_init), "volume": np.asarray([100*si.um**3, 100*si.um**3]) }, products = ()) pairwise_zeros = particulator.PairwiseStorage.from_ndarray(np.array([0.0])) general_zeros = particulator.Storage.from_ndarray(np.array([0.0])) gamma = particulator.PairwiseStorage.from_ndarray(np.array([params["gamma"]])) rand = particulator.PairwiseStorage.from_ndarray(np.array([params["rand"]])) n_fragment = particulator.PairwiseStorage.from_ndarray(np.array([4])) is_first_in_pair = make_PairIndicator(backend_class)(n_sd) # Act particulator.collision_coalescence_breakup( enable_breakup=True, gamma=gamma, rand=rand, Ec=pairwise_zeros, Eb=pairwise_zeros, n_fragment=n_fragment, coalescence_rate=general_zeros, breakup_rate=general_zeros, is_first_in_pair=is_first_in_pair ) # Assert assert (particulator.attributes['n'].to_ndarray() == n_init).all()
def test_multiplicity_overflow(backend = CPU()): # Arrange params = {"gamma": [100.0], "n_init": [1, 1], "v_init": [1, 1], "is_first_in_pair": [True, False], "n_fragment": [4]} n_init = params["n_init"] n_sd = len(n_init) builder = Builder(n_sd, backend) builder.set_environment(Box(dv=np.NaN, dt=np.NaN)) particulator = builder.build(attributes = { "n": np.asarray(n_init), "volume": np.asarray(params["v_init"]) }, products=()) n_pairs = n_sd // 2 rand = [1.0] * n_pairs Eb = [1.0] * n_pairs pairwise_zeros = particulator.PairwiseStorage.from_ndarray(np.array([0.0] * n_pairs)) general_zeros = particulator.Storage.from_ndarray(np.array([0.0] * n_sd)) gamma = particulator.PairwiseStorage.from_ndarray(np.array(params["gamma"])) rand = particulator.PairwiseStorage.from_ndarray(np.array(rand)) Eb = particulator.PairwiseStorage.from_ndarray(np.array(Eb)) breakup_rate = particulator.Storage.from_ndarray(np.array([0.0])) n_fragment = particulator.PairwiseStorage.from_ndarray(np.array(params["n_fragment"])) is_first_in_pair = particulator.PairIndicator(n_sd) is_first_in_pair.indicator[:] = particulator.Storage.from_ndarray( np.asarray(params["is_first_in_pair"])) # Act particulator.collision_coalescence_breakup( enable_breakup=True, gamma=gamma, rand=rand, Ec=pairwise_zeros, Eb=Eb, n_fragment=n_fragment, coalescence_rate=general_zeros, breakup_rate=breakup_rate, is_first_in_pair=is_first_in_pair )
def test_critical_supersaturation(): # arrange T = 300 * si.K n_sd = 100 S_max = .01 vdry = np.linspace(.001, 1, n_sd) * si.um**3 builder = Builder(n_sd=n_sd, backend=CPU()) env = Box(dt=np.nan, dv=np.nan) builder.set_environment(env) env['T'] = T particulator = builder.build(attributes={ 'n': np.ones(n_sd), 'volume': np.linspace(.01, 10, n_sd) * si.um**3, 'dry volume': vdry, 'kappa times dry volume': .9 * vdry, 'dry volume organic': np.zeros(n_sd) }, products=[ActivableFraction()]) # act AF = particulator.products['activable fraction'].get(S_max) # assert assert 0 < AF < 1
def test_ambient_relative_humidity(backend_class): # arrange n_sd = 1 builder = Builder(n_sd, backend=backend_class()) env = Parcel(dt=np.nan, mixed_phase=True, mass_of_dry_air=np.nan, p0=1000 * si.hPa, q0=1 * si.g / si.kg, T0=260 * si.K, w=np.nan) builder.set_environment(env) attributes = {'n': np.ones(n_sd), 'volume': np.ones(n_sd)} particulator = builder.build( attributes=attributes, products=( products.AmbientRelativeHumidity(name='RHw', var='RH'), products.AmbientRelativeHumidity(name='RHi', var='RH', ice=True), )) # act values = {} for name, product in particulator.products.items(): values[name] = product.get()[0] # assert assert values['RHw'] < values['RHi']
def __init__(self, backend_class, n_sd=0, formulae=None, grid=None): backend = backend_class(formulae) Builder.__init__(self, n_sd, backend) Particulator.__init__(self, n_sd, backend) self.particulator = self Builder.set_environment(self, DummyEnvironment(grid=grid)) self.req_attr = {'n': Multiplicities(self), 'cell id': CellID(self)} self.attributes = None
def _make_particulator(): builder = Builder(n_sd=n_sd, backend=CPU()) env = Box(dt=dt, dv=np.nan) builder.set_environment(env) env['T'] = T return builder.build(attributes={ 'n': np.ones(n_sd), 'volume': np.linspace(.01, 10, n_sd) * si.um**3 }, products=(CoolingRate(), ))
def test_noninteger_fragments(params, flag, backend_class = CPU): # Arrange n_init = params["n_init"] n_sd = len(n_init) builder = Builder(n_sd, backend_class()) builder.set_environment(Box(dv=np.NaN, dt=np.NaN)) particulator = builder.build(attributes = { "n": np.asarray(n_init), "volume": np.asarray(params["v_init"]) }, products = ()) n_pairs = n_sd // 2 rand = [1.0] * n_pairs Eb = [1.0] * n_pairs pairwise_zeros = particulator.PairwiseStorage.from_ndarray(np.array([0.0] * n_pairs)) general_zeros = particulator.Storage.from_ndarray(np.array([0.0] * n_sd)) gamma = particulator.PairwiseStorage.from_ndarray(np.array(params["gamma"])) rand = particulator.PairwiseStorage.from_ndarray(np.array(rand)) Eb = particulator.PairwiseStorage.from_ndarray(np.array(Eb)) breakup_rate = particulator.Storage.from_ndarray(np.array([0.0])) n_fragment = particulator.PairwiseStorage.from_ndarray(np.array(params["n_fragment"])) is_first_in_pair = particulator.PairIndicator(n_sd) is_first_in_pair.indicator[:] = particulator.Storage.from_ndarray( np.asarray(params["is_first_in_pair"])) # Act particulator.collision_coalescence_breakup( enable_breakup=True, gamma=gamma, rand=rand, Ec=pairwise_zeros, Eb=Eb, n_fragment=n_fragment, coalescence_rate=general_zeros, breakup_rate=breakup_rate, is_first_in_pair=is_first_in_pair ) # Assert { 'n': lambda: np.testing.assert_array_equal(particulator.attributes['n'].to_ndarray(), np.array(params["n_expected"])), 'v': lambda: np.testing.assert_array_almost_equal(particulator.attributes['volume'].to_ndarray(), np.array(params["v_expected"]), decimal=6), 'conserve': lambda: np.testing.assert_almost_equal(np.sum(particulator.attributes['n'].to_ndarray() * particulator.attributes['volume'].to_ndarray()), np.sum(np.array(params["n_init"]) * np.array(params["v_init"])), decimal=6) }[flag]()
def __init__(self, settings, backend=CPU): dt_output = (settings.total_time / settings.n_steps ) # TODO #334 overwritten in notebook self.n_substeps = 1 # TODO #334 use condensation substeps while dt_output / self.n_substeps >= settings.dt_max: self.n_substeps += 1 self.formulae = Formulae( condensation_coordinate=settings.coord, saturation_vapour_pressure="AugustRocheMagnus", ) self.bins_edges = self.formulae.trivia.volume(settings.r_bins_edges) builder = Builder(backend=backend(formulae=self.formulae), n_sd=settings.n_sd) builder.set_environment( Parcel( dt=dt_output / self.n_substeps, mass_of_dry_air=settings.mass_of_dry_air, p0=settings.p0, q0=settings.q0, T0=settings.T0, w=settings.w, z0=settings.z0, )) environment = builder.particulator.environment builder.add_dynamic(AmbientThermodynamics()) condensation = Condensation( adaptive=settings.adaptive, rtol_x=settings.rtol_x, rtol_thd=settings.rtol_thd, dt_cond_range=settings.dt_cond_range, ) builder.add_dynamic(condensation) products = [ PySDM_products.ParticleSizeSpectrumPerVolume( name="Particles Wet Size Spectrum", radius_bins_edges=settings.r_bins_edges, ), PySDM_products.CondensationTimestepMin(name="dt_cond_min"), PySDM_products.CondensationTimestepMax(name="dt_cond_max"), PySDM_products.RipeningRate(), ] attributes = environment.init_attributes(n_in_dv=settings.n, kappa=settings.kappa, r_dry=settings.r_dry) self.particulator = builder.build(attributes, products) self.n_steps = settings.n_steps
def __init__(self, backend, n_sd=-1, max_iters=-1, multiplicity=-1, dt=np.nan, dv=np.nan, rhod=np.nan, thd=np.nan, qv=np.nan, T=np.nan, p=np.nan, RH=np.nan, dry_volume=np.nan, wet_radius=np.nan): builder = Builder(n_sd=n_sd, backend=backend()) builder.set_environment( _TestEnv(dt=dt, dv=dv, rhod=rhod, thd=thd, qv=qv, T=T, p=p, RH=RH)) builder.add_dynamic(Condensation(max_iters=max_iters)) self.particulator = builder.build( attributes={ 'n': np.full(n_sd, multiplicity), 'volume': np.full(n_sd, wet_radius), 'dry volume': np.full(n_sd, dry_volume), 'kappa times dry volume': np.ones(n_sd), })
def test_courant_product(courant_field): # arrange n_sd = 1 builder = Builder(n_sd=n_sd, backend=CPU()) builder.set_environment(Kinematic2D( dt=1, grid=GRID, size=(100, 100), rhod_of=lambda x: x*0+1 )) builder.add_dynamic(Displacement()) particulator = builder.build( attributes={ 'n': np.ones(n_sd), 'volume': np.ones(n_sd), 'cell id': np.zeros(n_sd, dtype=int) }, products=(MaxCourantNumber(),) ) sut = particulator.products['max courant number'] # act particulator.dynamics['Displacement'].upload_courant_field(courant_field) max_courant = sut.get() # assert np.testing.assert_allclose(actual=max_courant, desired=np.maximum( np.maximum(abs(courant_field[0][1:, :]), abs(courant_field[0][:-1, :])), np.maximum(abs(courant_field[1][:, 1:]), abs(courant_field[1][:, :-1])) ))
def __init__( self, settings, products=None, scipy_solver=False, rtol_thd=1e-10, rtol_x=1e-10 ): env = Parcel( dt=settings.timestep, p0=settings.initial_pressure, q0=settings.initial_vapour_mixing_ratio, T0=settings.initial_temperature, w=settings.vertical_velocity, mass_of_dry_air=44 * si.kg, ) n_sd = sum(settings.n_sd_per_mode) builder = Builder(n_sd=n_sd, backend=CPU(formulae=settings.formulae)) builder.set_environment(env) builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x)) volume = env.mass_of_dry_air / settings.initial_air_density attributes = { k: np.empty(0) for k in ("dry volume", "kappa times dry volume", "n") } for i, (kappa, spectrum) in enumerate(settings.aerosol_modes_by_kappa.items()): sampling = ConstantMultiplicity(spectrum) r_dry, n_per_volume = sampling.sample(settings.n_sd_per_mode[i]) v_dry = settings.formulae.trivia.volume(radius=r_dry) attributes["n"] = np.append(attributes["n"], n_per_volume * volume) attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) attributes["kappa times dry volume"] = np.append( attributes["kappa times dry volume"], v_dry * kappa ) r_wet = equilibrate_wet_radii( r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), environment=env, kappa_times_dry_volume=attributes["kappa times dry volume"], ) attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) super().__init__( particulator=builder.build(attributes=attributes, products=products) ) if scipy_solver: bdf.patch_particulator(self.particulator) self.output_attributes = { "volume": tuple([] for _ in range(self.particulator.n_sd)) } self.settings = settings self.__sanity_checks(attributes, volume)
def test_freeze_time_dependent(plot=False): # Arrange cases = ( {'dt': 5e5, 'N': 1}, {'dt': 1e6, 'N': 1}, {'dt': 5e5, 'N': 8}, {'dt': 1e6, 'N': 8}, {'dt': 5e5, 'N': 32}, {'dt': 1e6, 'N': 32}, ) rate = 1e-9 immersed_surface_area = 1 constant.J_het = rate / immersed_surface_area number_of_real_droplets = 1024 total_time = 2e9 # effectively interpretted here as seconds, i.e. cycle = 1 * si.s # dummy (but must-be-set) values vol = 44 # just to enable sign flipping (ice water uses negative volumes), actual value does not matter dv = 666 # products use concentration, just dividing there and multiplying back here, actual value does not matter hgh = lambda t: np.exp(-0.8 * rate * (t - total_time / 10)) low = lambda t: np.exp(-1.2 * rate * (t + total_time / 10)) # Act output = {} for case in cases: n_sd = int(number_of_real_droplets // case['N']) assert n_sd == number_of_real_droplets / case['N'] assert total_time // case['dt'] == total_time / case['dt'] key = f"{case['dt']}:{case['N']}" output[key] = {'unfrozen_fraction': [], 'dt': case['dt'], 'N': case['N']} formulae = Formulae(heterogeneous_ice_nucleation_rate='Constant') builder = Builder(n_sd=n_sd, backend=CPU(formulae=formulae)) env = Box(dt=case['dt'], dv=dv) builder.set_environment(env) builder.add_dynamic(Freezing(singular=False)) attributes = { 'n': np.full(n_sd, int(case['N'])), 'immersed surface area': np.full(n_sd, immersed_surface_area), 'volume': np.full(n_sd, vol) } products = (IceWaterContent(specific=False),) particulator = builder.build(attributes=attributes, products=products) env['a_w_ice'] = np.nan cell_id = 0 for i in range(int(total_time / case['dt']) + 1): particulator.run(0 if i == 0 else 1) ice_mass_per_volume = particulator.products['qi'].get()[cell_id] ice_mass = ice_mass_per_volume * dv ice_number = ice_mass / (const.rho_w * vol) unfrozen_fraction = 1 - ice_number / number_of_real_droplets output[key]['unfrozen_fraction'].append(unfrozen_fraction)
def make_particulator( *, constants, n_sd, dt, initial_temperature, singular, seed, shima_T_fz, ABIFM_spec, droplet_volume, total_particle_number, volume ): attributes = {"volume": np.ones(n_sd) * droplet_volume} formulae_ctor_args = {"seed": seed, "constants": constants} if singular: formulae_ctor_args["freezing_temperature_spectrum"] = shima_T_fz else: formulae_ctor_args["heterogeneous_ice_nucleation_rate"] = "ABIFM" formulae = Formulae(**formulae_ctor_args) if singular: sampling = SpectroGlacialSampling( freezing_temperature_spectrum=formulae.freezing_temperature_spectrum, insoluble_surface_spectrum=ABIFM_spec, seed=formulae.seed, ) attributes["freezing temperature"], _, attributes["n"] = sampling.sample(n_sd) else: sampling = ConstantMultiplicity( spectrum=ABIFM_spec, # seed=formulae.seed ) attributes["immersed surface area"], attributes["n"] = sampling.sample(n_sd) attributes["n"] *= total_particle_number builder = Builder(n_sd, CPU(formulae)) env = Box(dt, volume) builder.set_environment(env) env["T"] = initial_temperature env["RH"] = A_VALUE_LARGER_THAN_ONE builder.add_dynamic(Freezing(singular=singular)) return builder.build( attributes=attributes, products=( PySDM_products.Time(name="t"), PySDM_products.AmbientTemperature(name="T"), PySDM_products.SpecificIceWaterContent(name="qi"), ), )
def test_breakup_counters(params, backend_class = CPU): # Arrange n_init = params["n_init"] n_sd = len(n_init) builder = Builder(n_sd, backend_class()) builder.set_environment(Box(dv=np.NaN, dt=np.NaN)) particulator = builder.build(attributes = { "n": np.asarray(n_init), "volume": np.asarray([100*si.um**3] * n_sd) }, products = ()) n_pairs = n_sd // 2 pairwise_zeros = particulator.PairwiseStorage.from_ndarray(np.array([0.0] * n_pairs)) general_zeros = particulator.Storage.from_ndarray(np.array([0.0] * n_sd)) gamma = particulator.PairwiseStorage.from_ndarray(np.array([params["gamma"]] * n_pairs)) rand = particulator.PairwiseStorage.from_ndarray(np.array([params["rand"]] * n_pairs)) Eb = particulator.PairwiseStorage.from_ndarray(np.array([params["Eb"]] * n_pairs)) breakup_rate = particulator.Storage.from_ndarray(np.array([0.0])) n_fragment = particulator.PairwiseStorage.from_ndarray(np.array([4] * n_pairs)) is_first_in_pair = particulator.PairIndicator(n_sd) is_first_in_pair.indicator[:] = particulator.Storage.from_ndarray( np.asarray(params["is_first_in_pair"])) # Act particulator.collision_coalescence_breakup( enable_breakup=True, gamma=gamma, rand=rand, Ec=pairwise_zeros, Eb=Eb, n_fragment=n_fragment, coalescence_rate=general_zeros, breakup_rate=breakup_rate, is_first_in_pair=is_first_in_pair ) # Assert cell_id = 0 assert (breakup_rate.to_ndarray()[cell_id] == np.sum(params["gamma"] * get_smaller_of_pairs(is_first_in_pair, n_init)))
def test_nonadaptive_same_results_regardless_of_dt(dt, backend_class = CPU): # Arrange attributes = {"n": np.asarray([1, 1]), "volume": np.asarray([100*si.um**3, 100*si.um**3])} breakup = Breakup(ConstantK(1 * si.cm**3 / si.s), AlwaysN(4), adaptive=False) nsteps = 10 n_sd = len(attributes["n"]) builder = Builder(n_sd, backend_class()) builder.set_environment(Box(dv=1*si.cm**3, dt=dt)) builder.add_dynamic(breakup) particulator = builder.build(attributes = attributes, products = ()) # Act particulator.run(nsteps) # Assert assert (particulator.attributes['n'].to_ndarray() > 0).all() assert (particulator.attributes['n'].to_ndarray() != attributes['n']).any() assert (np.sum(particulator.attributes['n'].to_ndarray()) >= np.sum(attributes['n'])) assert (particulator.attributes['n'].to_ndarray() == np.array([1024, 1024])).all()
def run_parcel( w, sol2, N2, rad2, n_sd_per_mode, RH0=1.0, T0=294 * si.K, p0=1e5 * si.Pa, n_steps=50, mass_of_dry_air=1e3 * si.kg, dt=2 * si.s, ): products = ( PySDM_products.WaterMixingRatio(unit="g/kg", name="ql"), PySDM_products.PeakSupersaturation(name="S max"), PySDM_products.AmbientRelativeHumidity(name="RH"), PySDM_products.ParcelDisplacement(name="z"), ) formulae = Formulae() const = formulae.constants pv0 = RH0 * formulae.saturation_vapour_pressure.pvs_Celsius(T0 - const.T0) q0 = const.eps * pv0 / (p0 - pv0) env = Parcel(dt=dt, mass_of_dry_air=mass_of_dry_air, p0=p0, q0=q0, w=w, T0=T0) aerosol = AerosolARG(M2_sol=sol2, M2_N=N2, M2_rad=rad2) n_sd = n_sd_per_mode * len(aerosol.modes) builder = Builder(backend=CPU(), n_sd=n_sd) builder.set_environment(env) builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic(Condensation()) builder.add_dynamic(Magick()) attributes = { k: np.empty(0) for k in ("dry volume", "kappa times dry volume", "n") } for i, mode in enumerate(aerosol.modes): kappa, spectrum = mode["kappa"]["CompressedFilmOvadnevaite"], mode[ "spectrum"] r_dry, concentration = ConstantMultiplicity(spectrum).sample( n_sd_per_mode) v_dry = builder.formulae.trivia.volume(radius=r_dry) specific_concentration = concentration / builder.formulae.constants.rho_STP attributes["n"] = np.append( attributes["n"], specific_concentration * env.mass_of_dry_air) attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) attributes["kappa times dry volume"] = np.append( attributes["kappa times dry volume"], v_dry * kappa) r_wet = equilibrate_wet_radii( r_dry=builder.formulae.trivia.radius(volume=attributes["dry volume"]), environment=env, kappa_times_dry_volume=attributes["kappa times dry volume"], ) attributes["volume"] = builder.formulae.trivia.volume(radius=r_wet) particulator = builder.build(attributes, products=products) bdf.patch_particulator(particulator) output = {product.name: [] for product in particulator.products.values()} output_attributes = { "n": tuple([] for _ in range(particulator.n_sd)), "volume": tuple([] for _ in range(particulator.n_sd)), "critical volume": tuple([] for _ in range(particulator.n_sd)), "critical supersaturation": tuple([] for _ in range(particulator.n_sd)), } for _ in range(n_steps): particulator.run(steps=1) for product in particulator.products.values(): value = product.get() output[product.name].append(value[0]) for key, attr in output_attributes.items(): attr_data = particulator.attributes[key].to_ndarray() for drop_id in range(particulator.n_sd): attr[drop_id].append(attr_data[drop_id]) error = np.zeros(len(aerosol.modes)) activated_fraction_S = np.zeros(len(aerosol.modes)) activated_fraction_V = np.zeros(len(aerosol.modes)) for j, mode in enumerate(aerosol.modes): activated_drops_j_S = 0 activated_drops_j_V = 0 RHmax = np.nanmax(np.asarray(output["RH"])) for i, volume in enumerate(output_attributes["volume"]): if j * n_sd_per_mode <= i < (j + 1) * n_sd_per_mode: if output_attributes["critical supersaturation"][i][-1] < RHmax: activated_drops_j_S += output_attributes["n"][i][-1] if output_attributes["critical volume"][i][-1] < volume[-1]: activated_drops_j_V += output_attributes["n"][i][-1] Nj = np.asarray(output_attributes["n"])[j * n_sd_per_mode:(j + 1) * n_sd_per_mode, -1] max_multiplicity_j = np.max(Nj) sum_multiplicity_j = np.sum(Nj) error[j] = max_multiplicity_j / sum_multiplicity_j activated_fraction_S[j] = activated_drops_j_S / sum_multiplicity_j activated_fraction_V[j] = activated_drops_j_V / sum_multiplicity_j Output = namedtuple( "Output", [ "profile", "attributes", "aerosol", "activated_fraction_S", "activated_fraction_V", "error", ], ) return Output( profile=output, attributes=output_attributes, aerosol=aerosol, activated_fraction_S=activated_fraction_S, activated_fraction_V=activated_fraction_V, error=error, )
def __init__(self, settings, backend=CPU): self.nt = settings.nt builder = Builder(backend=backend, n_sd=settings.n_sd, formulae=settings.formulae) mesh = Mesh(grid=(settings.nz, ), size=(settings.z_max, )) env = Kinematic1D(dt=settings.dt, mesh=mesh, thd_of_z=settings.thd, rhod_of_z=settings.rhod) mpdata = MPDATA_1D( nz=settings.nz, dt=settings.dt, mpdata_settings=settings.mpdata_settings, advector_of_t=lambda t: settings.w(t) * settings.dt / settings.dz, advectee_of_zZ_at_t0=lambda zZ: settings.qv(zZ * settings.dz), g_factor_of_zZ=lambda zZ: settings.rhod(zZ * settings.dz)) builder.set_environment(env) builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic( Condensation(adaptive=settings.condensation_adaptive, rtol_thd=settings.condensation_rtol_thd, rtol_x=settings.condensation_rtol_x, kappa=settings.kappa)) builder.add_dynamic(EulerianAdvection(mpdata)) if settings.precip: builder.add_dynamic( Coalescence(kernel=Geometric(collection_efficiency=1), adaptive=settings.coalescence_adaptive)) builder.add_dynamic( Displacement(enable_sedimentation=True, courant_field=(np.zeros(settings.nz + 1), ))) # TODO #414 attributes = env.init_attributes( spatial_discretisation=spatial_sampling.Pseudorandom(), spectral_discretisation=spectral_sampling.ConstantMultiplicity( spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air), kappa=settings.kappa) products = [ PySDM_products.RelativeHumidity(), PySDM_products.Pressure(), PySDM_products.Temperature(), PySDM_products.WaterVapourMixingRatio(), PySDM_products.WaterMixingRatio( name='ql', description_prefix='cloud', radius_range=settings.cloud_water_radius_range), PySDM_products.WaterMixingRatio( name='qr', description_prefix='rain', radius_range=settings.rain_water_radius_range), PySDM_products.DryAirDensity(), PySDM_products.DryAirPotentialTemperature(), PySDM_products.ParticlesDrySizeSpectrum( v_bins=settings.v_bin_edges), PySDM_products.ParticlesWetSizeSpectrum( v_bins=settings.v_bin_edges), PySDM_products.CloudDropletConcentration( radius_range=settings.cloud_water_radius_range), PySDM_products.AerosolConcentration( radius_threshold=settings.cloud_water_radius_range[0]), PySDM_products.ParticleMeanRadius(), PySDM_products.RipeningRate(), PySDM_products.ActivatingRate(), PySDM_products.DeactivatingRate(), PySDM_products.CloudDropletEffectiveRadius( radius_range=settings.cloud_water_radius_range), PySDM_products.PeakSupersaturation() ] self.core = builder.build(attributes=attributes, products=products)
def __init__(self, settings, products=None): env = Parcel( dt=settings.dt, mass_of_dry_air=settings.mass_of_dry_air, p0=settings.p0, q0=settings.q0, T0=settings.T0, w=settings.w, ) builder = Builder(n_sd=settings.n_sd, backend=CPU(formulae=settings.formulae)) builder.set_environment(env) attributes = env.init_attributes(n_in_dv=settings.n_in_dv, kappa=settings.kappa, r_dry=settings.r_dry) attributes = { **attributes, **settings.starting_amounts, "pH": np.zeros(settings.n_sd), } builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic(Condensation()) builder.add_dynamic( AqueousChemistry( environment_mole_fractions=settings.ENVIRONMENT_MOLE_FRACTIONS, system_type=settings.system_type, n_substep=settings.n_substep, dry_rho=settings.DRY_RHO, dry_molar_mass=settings.dry_molar_mass, )) products = products or ( PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"), PySDM_products.WaterMixingRatio( name="ql", radius_range=[1 * si.um, np.inf], unit="g/kg"), PySDM_products.ParcelDisplacement(name="z"), PySDM_products.AmbientPressure(name="p"), PySDM_products.AmbientTemperature(name="T"), PySDM_products.AmbientDryAirDensity(name="rhod"), PySDM_products.AmbientWaterVapourMixingRatio(name="qv", unit="g/kg"), PySDM_products.Time(name="t"), *(PySDM_products.AqueousMoleFraction( comp, unit="ppb", name=f"aq_{comp}_ppb") for comp in AQUEOUS_COMPOUNDS), *(PySDM_products.GaseousMoleFraction( comp, unit="ppb", name=f"gas_{comp}_ppb") for comp in GASEOUS_COMPOUNDS), PySDM_products.Acidity( name="pH_pH_number_weighted", radius_range=settings.cloud_radius_range, weighting="number", attr="pH", ), PySDM_products.Acidity( name="pH_pH_volume_weighted", radius_range=settings.cloud_radius_range, weighting="volume", attr="pH", ), PySDM_products.Acidity( name="pH_conc_H_number_weighted", radius_range=settings.cloud_radius_range, weighting="number", attr="conc_H", ), PySDM_products.Acidity( name="pH_conc_H_volume_weighted", radius_range=settings.cloud_radius_range, weighting="volume", attr="conc_H", ), PySDM_products.TotalDryMassMixingRatio( settings.DRY_RHO, name="q_dry", unit="ug/kg"), PySDM_products.PeakSupersaturation(unit="%", name="S_max"), PySDM_products.ParticleSpecificConcentration( radius_range=settings.cloud_radius_range, name="n_c_mg", unit="mg^-1"), PySDM_products.AqueousMassSpectrum( key="S_VI", dry_radius_bins_edges=settings.dry_radius_bins_edges, name="dm_S_VI/dlog_10(dry diameter)", unit='ug / m^3"', ), ) particulator = builder.build(attributes=attributes, products=products) self.settings = settings super().__init__(particulator=particulator)
def __init__(self, settings, backend=CPU): self.nt = settings.nt self.z0 = -settings.particle_reservoir_depth builder = Builder(n_sd=settings.n_sd, backend=backend(formulae=settings.formulae)) mesh = Mesh( grid=(settings.nz, ), size=(settings.z_max + settings.particle_reservoir_depth, ), ) env = Kinematic1D( dt=settings.dt, mesh=mesh, thd_of_z=settings.thd, rhod_of_z=settings.rhod, z0=-settings.particle_reservoir_depth, ) def zZ_to_z_above_reservoir(zZ): z_above_reservoir = zZ * (settings.nz * settings.dz) + self.z0 return z_above_reservoir self.mpdata = MPDATA_1D( nz=settings.nz, dt=settings.dt, mpdata_settings=settings.mpdata_settings, advector_of_t=lambda t: settings.rho_times_w(t) * settings.dt / settings.dz, advectee_of_zZ_at_t0=lambda zZ: settings.qv( zZ_to_z_above_reservoir(zZ)), g_factor_of_zZ=lambda zZ: settings.rhod(zZ_to_z_above_reservoir(zZ) ), ) _extra_nz = settings.particle_reservoir_depth // settings.dz _z_vec = settings.dz * np.linspace(-_extra_nz, settings.nz - _extra_nz, settings.nz + 1) self.g_factor_vec = settings.rhod(_z_vec) builder.set_environment(env) builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic( Condensation( adaptive=settings.condensation_adaptive, rtol_thd=settings.condensation_rtol_thd, rtol_x=settings.condensation_rtol_x, )) builder.add_dynamic(EulerianAdvection(self.mpdata)) if settings.precip: if settings.breakup: builder.add_dynamic( Collision( collision_kernel=Geometric(collection_efficiency=1), coalescence_efficiency=ConstEc(Ec=0.95), breakup_efficiency=ConstEb(Eb=1.0), fragmentation_function=ExponFrag(scale=100 * si.um), adaptive=settings.coalescence_adaptive, )) else: builder.add_dynamic( Coalescence( collision_kernel=Geometric(collection_efficiency=1), adaptive=settings.coalescence_adaptive, )) displacement = Displacement( enable_sedimentation=settings.precip, precipitation_counting_level_index=int( settings.particle_reservoir_depth / settings.dz), ) builder.add_dynamic(displacement) attributes = env.init_attributes( spatial_discretisation=spatial_sampling.Pseudorandom(), spectral_discretisation=spectral_sampling.ConstantMultiplicity( spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air), kappa=settings.kappa, ) products = [ PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"), PySDM_products.AmbientPressure(name="p"), PySDM_products.AmbientTemperature(name="T"), PySDM_products.AmbientWaterVapourMixingRatio(name="qv"), PySDM_products.WaterMixingRatio( name="ql", unit="g/kg", radius_range=settings.cloud_water_radius_range), PySDM_products.WaterMixingRatio( name="qr", unit="g/kg", radius_range=settings.rain_water_radius_range), PySDM_products.AmbientDryAirDensity(name="rhod"), PySDM_products.AmbientDryAirPotentialTemperature(name="thd"), PySDM_products.ParticleSizeSpectrumPerVolume( name="dry spectrum", radius_bins_edges=settings.r_bins_edges, dry=True), PySDM_products.ParticleSizeSpectrumPerVolume( name="wet spectrum", radius_bins_edges=settings.r_bins_edges), PySDM_products.ParticleConcentration( name="nc", radius_range=settings.cloud_water_radius_range), PySDM_products.ParticleConcentration( name="na", radius_range=(0, settings.cloud_water_radius_range[0])), PySDM_products.MeanRadius(), PySDM_products.RipeningRate(), PySDM_products.ActivatingRate(), PySDM_products.DeactivatingRate(), PySDM_products.EffectiveRadius( radius_range=settings.cloud_water_radius_range), PySDM_products.PeakSupersaturation(unit="%"), PySDM_products.SuperDropletCountPerGridbox(), ] self.particulator = builder.build(attributes=attributes, products=products)
def __init__(self, settings, products=None): env = Parcel(dt=settings.dt, mass_of_dry_air=settings.mass_of_dry_air, p0=settings.p0, q0=settings.q0, T0=settings.T0, w=settings.w, g=settings.g) builder = Builder(n_sd=settings.n_sd, backend=CPU, formulae=settings.formulae) builder.set_environment(env) attributes = env.init_attributes(n_in_dv=settings.n_in_dv, kappa=settings.kappa, r_dry=settings.r_dry) attributes = { **attributes, **settings.starting_amounts, 'pH': np.zeros(settings.n_sd) } builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic(Condensation(kappa=settings.kappa)) builder.add_dynamic( AqueousChemistry(settings.ENVIRONMENT_MOLE_FRACTIONS, system_type=settings.system_type, n_substep=settings.n_substep, dry_rho=settings.DRY_RHO, dry_molar_mass=settings.dry_molar_mass)) products = products or ( PySDM_products.RelativeHumidity(), PySDM_products.WaterMixingRatio(name='ql', description_prefix='liquid', radius_range=[1 * si.um, np.inf]), PySDM_products.ParcelDisplacement(), PySDM_products.Pressure(), PySDM_products.Temperature(), PySDM_products.DryAirDensity(), PySDM_products.WaterVapourMixingRatio(), PySDM_products.Time(), *[ PySDM_products.AqueousMoleFraction(compound) for compound in AQUEOUS_COMPOUNDS.keys() ], *[ PySDM_products.GaseousMoleFraction(compound) for compound in GASEOUS_COMPOUNDS.keys() ], PySDM_products.pH(radius_range=settings.cloud_radius_range, weighting='number', attr='pH'), PySDM_products.pH(radius_range=settings.cloud_radius_range, weighting='volume', attr='pH'), PySDM_products.pH(radius_range=settings.cloud_radius_range, weighting='number', attr='conc_H'), PySDM_products.pH(radius_range=settings.cloud_radius_range, weighting='volume', attr='conc_H'), PySDM_products.TotalDryMassMixingRatio( settings.DRY_RHO), PySDM_products.PeakSupersaturation(), PySDM_products.CloudDropletConcentration( radius_range=settings.cloud_radius_range), PySDM_products.AqueousMassSpectrum("S_VI", settings.dry_radius_bins_edges)) self.core = builder.build(attributes=attributes, products=products) self.settings = settings
def __init__(self, settings, products=None): env = Parcel( dt=settings.dt, mass_of_dry_air=settings.mass_of_dry_air, p0=settings.p0, q0=settings.q0, T0=settings.T0, w=settings.w, ) n_sd = settings.n_sd_per_mode * len(settings.aerosol.modes) builder = Builder(n_sd=n_sd, backend=CPU(formulae=settings.formulae)) builder.set_environment(env) attributes = { "dry volume": np.empty(0), "dry volume organic": np.empty(0), "kappa times dry volume": np.empty(0), "n": np.ndarray(0), } for mode in settings.aerosol.modes: r_dry, n_in_dv = settings.spectral_sampling( spectrum=mode["spectrum"]).sample(settings.n_sd_per_mode) V = settings.mass_of_dry_air / settings.rho0 N = n_in_dv * V v_dry = settings.formulae.trivia.volume(radius=r_dry) attributes["n"] = np.append(attributes["n"], N) attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) attributes["dry volume organic"] = np.append( attributes["dry volume organic"], mode["f_org"] * v_dry) attributes["kappa times dry volume"] = np.append( attributes["kappa times dry volume"], v_dry * mode["kappa"][settings.model], ) for attribute in attributes.values(): assert attribute.shape[0] == n_sd np.testing.assert_approx_equal( np.sum(attributes["n"]) / V, Sum( tuple( settings.aerosol.modes[i]["spectrum"] for i in range(len(settings.aerosol.modes)))).norm_factor, significant=5, ) r_wet = equilibrate_wet_radii( r_dry=settings.formulae.trivia.radius( volume=attributes["dry volume"]), environment=env, kappa_times_dry_volume=attributes["kappa times dry volume"], f_org=attributes["dry volume organic"] / attributes["dry volume"], ) attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) if settings.model == "Constant": del attributes["dry volume organic"] builder.add_dynamic(AmbientThermodynamics()) builder.add_dynamic(Condensation()) products = products or ( PySDM_products.ParcelDisplacement(name="z"), PySDM_products.Time(name="t"), PySDM_products.PeakSupersaturation(unit="%", name="S_max"), PySDM_products.AmbientRelativeHumidity(unit="%", name="RH"), PySDM_products.ParticleConcentration( name="n_c_cm3", unit="cm^-3", radius_range=settings.cloud_radius_range), PySDM_products.ParticleSizeSpectrumPerVolume( radius_bins_edges=settings.wet_radius_bins_edges), PySDM_products.ActivableFraction(), ) particulator = builder.build(attributes=attributes, products=products) if settings.BDF: bdf.patch_particulator(particulator) self.settings = settings super().__init__(particulator=particulator)
def simulation( *, constants, seed, n_sd, time_step, volume, spectrum, droplet_volume, multiplicity, total_time, number_of_real_droplets, cooling_rate=0, heterogeneous_ice_nucleation_rate="Constant", initial_temperature=np.nan, ): formulae = Formulae( seed=seed, heterogeneous_ice_nucleation_rate=heterogeneous_ice_nucleation_rate, constants=constants, ) builder = Builder(n_sd=n_sd, backend=CPU(formulae=formulae)) env = Box(dt=time_step, dv=volume) builder.set_environment(env) builder.add_dynamic(Freezing(singular=False)) if hasattr(spectrum, "s_geom") and spectrum.s_geom == 1: _isa, _conc = np.full(n_sd, spectrum.m_mode), np.full( n_sd, multiplicity / volume) else: _isa, _conc = spectral_sampling.ConstantMultiplicity(spectrum).sample( n_sd) attributes = { "n": discretise_multiplicities(_conc * volume), "immersed surface area": _isa, "volume": np.full(n_sd, droplet_volume), } np.testing.assert_almost_equal(attributes["n"], multiplicity) products = ( IceWaterContent(name="qi"), TotalUnfrozenImmersedSurfaceArea(name="A_tot"), ) particulator = builder.build(attributes=attributes, products=products) temperature = initial_temperature env["a_w_ice"] = np.nan svp = particulator.formulae.saturation_vapour_pressure cell_id = 0 f_ufz = [] a_tot = [] for i in range(int(total_time / time_step) + 1): if cooling_rate != 0: temperature -= cooling_rate * time_step / 2 env["a_w_ice"] = svp.ice_Celsius( temperature - const.T0) / svp.pvs_Celsius(temperature - const.T0) particulator.run(0 if i == 0 else 1) if cooling_rate != 0: temperature -= cooling_rate * time_step / 2 ice_mass_per_volume = particulator.products["qi"].get()[cell_id] ice_mass = ice_mass_per_volume * volume ice_number = ice_mass / (formulae.constants.rho_w * droplet_volume) unfrozen_fraction = 1 - ice_number / number_of_real_droplets f_ufz.append(unfrozen_fraction) a_tot.append(particulator.products["A_tot"].get()[cell_id]) return f_ufz, a_tot