def register(self, builder): self.core = builder.core for key, compound in GASEOUS_COMPOUNDS.items(): shape = (1, ) # TODO #440 self.environment_mixing_ratios[compound] = np.full( shape, self.core.formulae.trivia.mole_fraction_2_mixing_ratio( self.environment_mole_fractions[compound], SPECIFIC_GRAVITY[compound])) self.environment_mole_fractions = None if self.pH_H_max is None: self.pH_H_max = self.core.formulae.trivia.pH2H(default_pH_min) if self.pH_H_min is None: self.pH_H_min = self.core.formulae.trivia.pH2H(default_pH_max) for key in AQUEOUS_COMPOUNDS.keys(): builder.request_attribute("conc_" + key) for key in self.core.backend.KINETIC_CONST.KINETIC_CONST.keys(): self.kinetic_consts[key] = self.core.Storage.empty( self.core.mesh.n_cell, dtype=float) for key in self.core.backend.EQUILIBRIUM_CONST.EQUILIBRIUM_CONST.keys( ): self.equilibrium_consts[key] = self.core.Storage.empty( self.core.mesh.n_cell, dtype=float) for key in DIFFUSION_CONST.keys(): self.dissociation_factors[key] = self.core.Storage.empty( self.core.n_sd, dtype=float) self.do_chemistry_flag = self.core.Storage.empty(self.core.n_sd, dtype=bool)
def __init__(self, builder): self.conc = {} for k, v in AQUEOUS_COMPOUNDS.items(): if len(v) > 1: self.conc[k] = builder.get_attribute('conc_' + k) super().__init__(builder, name='pH', dependencies=self.conc.values()) self.environment = builder.particulator.environment self.cell_id = builder.get_attribute('cell id') self.particles = builder.particulator
def test_at_t_0(): # Arrange settings = Settings(n_sd=100, dt=1 * si.s, n_substep=5) settings.t_max = 0 simulation = Simulation(settings) # Act output = simulation.run() # Assert np.testing.assert_allclose(output['RH_env'][0], 95) np.testing.assert_allclose(output['gas_S_IV_ppb'][0], 0.2) np.testing.assert_allclose(output['gas_N_mIII_ppb'][0], 0.1) np.testing.assert_allclose(output['gas_H2O2_ppb'], 0.5) np.testing.assert_allclose(output['gas_N_V_ppb'], 0.1) np.testing.assert_allclose(output['gas_O3_ppb'], 50) np.testing.assert_allclose(output['gas_C_IV_ppb'], 360 * 1000) rtol = 0.15 mass_conc_SO4mm = 2 mass_conc_NH4p = 0.375 num_conc_SO4mm = mass_conc_SO4mm / Substance.from_formula( "SO4").mass * si.gram / si.mole num_conc_NH4p = mass_conc_NH4p / Substance.from_formula( "NH4").mass * si.gram / si.mole np.testing.assert_allclose(num_conc_NH4p, num_conc_SO4mm, rtol=.005) mass_conc_H = num_conc_NH4p * Substance.from_formula( "H").mass * si.gram / si.mole np.testing.assert_allclose(actual=np.asarray(output['q_dry']) * np.asarray(output['rhod_env']), desired=mass_conc_NH4p + mass_conc_SO4mm + mass_conc_H, rtol=rtol) expected = {k: 0 for k in AQUEOUS_COMPOUNDS.keys()} expected['S_VI'] = mass_conc_SO4mm * si.ug / si.m**3 expected['N_mIII'] = mass_conc_NH4p * si.ug / si.m**3 for key in expected.keys(): mole_fraction = np.asarray(output[f"aq_{key}_ppb"]) convert_to(mole_fraction, 1 / ppb) compound = AQUEOUS_COMPOUNDS[key][0] # sic! np.testing.assert_allclose( actual=(settings.formulae.trivia.mole_fraction_2_mixing_ratio( mole_fraction, specific_gravity=SPECIFIC_GRAVITY[compound]) * np.asarray(output['rhod_env'])), desired=expected[key], rtol=rtol)
from PySDM.attributes.chemistry.concentration import Concentration from PySDM.attributes.chemistry.pH import pH from PySDM.attributes.chemistry.hydrogen_ion_concentration import HydrogenIonConcentration from PySDM.physics.aqueous_chemistry.support import AQUEOUS_COMPOUNDS from functools import partial attributes = { 'n': lambda _: Multiplicities, 'volume': lambda _: Volume, 'dry volume': lambda dynamics: DryVolumeDynamic if 'AqueousChemistry' in dynamics else DryVolumeStatic, 'radius': lambda _: Radius, 'dry radius': lambda _: DryRadius, 'terminal velocity': lambda _: TerminalVelocity, 'cell id': lambda _: CellID, 'cell origin': lambda _: CellOrigin, 'position in cell': lambda _: PositionInCell, 'temperature': lambda _: Temperature, 'heat': lambda _: Heat, 'critical volume': lambda _: CriticalVolume, **{"moles_" + compound: partial(lambda _, c: MoleAmount(c), c=compound) for compound in AQUEOUS_COMPOUNDS.keys()}, **{"conc_" + compound: partial(lambda _, c: Concentration(c), c=compound) for compound in AQUEOUS_COMPOUNDS.keys()}, 'pH': lambda _: pH, 'conc_H': lambda _: HydrogenIonConcentration } def get_class(name, dynamics): return attributes[name](dynamics)
def __init__(self, dt: float, n_sd: int, n_substep: int, spectral_sampling: spec_sampling. SpectralSampling = spec_sampling.Logarithmic): self.formulae = Formulae( saturation_vapour_pressure='AugustRocheMagnus') self.DRY_RHO = 1800 * si.kg / (si.m**3) self.dry_molar_mass = Substance.from_formula( "NH4HSO4").mass * si.gram / si.mole self.system_type = 'closed' self.t_max = (2400 + 196) * si.s self.output_interval = 10 * si.s self.dt = dt self.w = .5 * si.m / si.s self.g = 10 * si.m / si.s**2 self.n_sd = n_sd self.n_substep = n_substep self.p0 = 950 * si.mbar self.T0 = 285.2 * si.K pv0 = .95 * self.formulae.saturation_vapour_pressure.pvs_Celsius( self.T0 - const.T0) self.q0 = const.eps * pv0 / (self.p0 - pv0) self.kappa = .61 self.cloud_radius_range = (.5 * si.micrometre, 25 * si.micrometre) self.mass_of_dry_air = 44 # note: rho is not specified in the paper rho0 = 1 self.r_dry, self.n_in_dv = spectral_sampling( spectrum=spectra.Lognormal(norm_factor=566 / si.cm**3 / rho0 * self.mass_of_dry_air, m_mode=.08 * si.um / 2, s_geom=2)).sample(n_sd) self.ENVIRONMENT_MOLE_FRACTIONS = { "SO2": 0.2 * const.ppb, "O3": 50 * const.ppb, "H2O2": 0.5 * const.ppb, "CO2": 360 * const.ppm, "HNO3": 0.1 * const.ppb, "NH3": 0.1 * const.ppb, } self.starting_amounts = { "moles_" + k: self.formulae.trivia.volume(self.r_dry) * self.DRY_RHO / self.dry_molar_mass if k in ("N_mIII", "S_VI") else np.zeros(self.n_sd) for k in AQUEOUS_COMPOUNDS.keys() } self.dry_radius_bins_edges = np.logspace( np.log10(.01 * si.um), np.log10(1 * si.um), 51, endpoint=True) / 2
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