PySDM_examples.Pyrcel.simulation
1import numpy as np 2from PySDM_examples.utils import BasicSimulation 3 4from PySDM import Builder 5from PySDM.backends import CPU 6from PySDM.backends.impl_numba.test_helpers import scipy_ode_condensation_solver 7from PySDM.dynamics import AmbientThermodynamics, Condensation 8from PySDM.environments import Parcel 9from PySDM.initialisation.hygroscopic_equilibrium import equilibrate_wet_radii 10from PySDM.initialisation.sampling.spectral_sampling import ConstantMultiplicity 11from PySDM.physics import si 12 13 14class Simulation(BasicSimulation): 15 def __init__( 16 self, 17 settings, 18 *, 19 products=None, 20 scipy_solver=False, 21 rtol_thd=1e-10, 22 rtol_x=1e-10, 23 mass_of_dry_air=44 * si.kg, 24 additional_attributes=None, 25 ): 26 n_sd = sum(settings.n_sd_per_mode) 27 builder = Builder( 28 n_sd=n_sd, 29 backend=CPU( 30 formulae=settings.formulae, override_jit_flags={"parallel": False} 31 ), 32 environment=Parcel( 33 dt=settings.timestep, 34 p0=settings.initial_pressure, 35 initial_water_vapour_mixing_ratio=settings.initial_vapour_mixing_ratio, 36 T0=settings.initial_temperature, 37 w=settings.vertical_velocity, 38 mass_of_dry_air=mass_of_dry_air, 39 ), 40 ) 41 if additional_attributes is not None: 42 for attribute in additional_attributes: 43 builder.request_attribute(attribute) 44 builder.add_dynamic(AmbientThermodynamics()) 45 builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x)) 46 47 volume = ( 48 builder.particulator.environment.mass_of_dry_air 49 / settings.initial_air_density 50 ) 51 attributes = { 52 k: np.empty(0) 53 for k in ("dry volume", "kappa times dry volume", "multiplicity") 54 } 55 for i, (kappa, spectrum) in enumerate(settings.aerosol_modes_by_kappa.items()): 56 sampling = ConstantMultiplicity(spectrum) 57 r_dry, n_per_volume = sampling.sample_deterministic( 58 settings.n_sd_per_mode[i] 59 ) 60 v_dry = settings.formulae.trivia.volume(radius=r_dry) 61 attributes["multiplicity"] = np.append( 62 attributes["multiplicity"], n_per_volume * volume 63 ) 64 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 65 attributes["kappa times dry volume"] = np.append( 66 attributes["kappa times dry volume"], v_dry * kappa 67 ) 68 r_wet = equilibrate_wet_radii( 69 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 70 environment=builder.particulator.environment, 71 kappa_times_dry_volume=attributes["kappa times dry volume"], 72 ) 73 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 74 75 super().__init__( 76 particulator=builder.build(attributes=attributes, products=products) 77 ) 78 if scipy_solver: 79 scipy_ode_condensation_solver.patch_particulator(self.particulator) 80 81 self.output_attributes = { 82 attr: tuple([] for _ in range(self.particulator.n_sd)) 83 for attr in ["volume"] 84 + (list(additional_attributes) if additional_attributes is not None else []) 85 } 86 self.settings = settings 87 88 self.__sanity_checks(attributes, volume) 89 90 def __sanity_checks(self, attributes, volume): 91 for attribute in attributes.values(): 92 assert attribute.shape[0] == self.particulator.n_sd 93 np.testing.assert_approx_equal( 94 sum(attributes["multiplicity"]) / volume, 95 sum( 96 mode.norm_factor 97 for mode in self.settings.aerosol_modes_by_kappa.values() 98 ), 99 significant=4, 100 ) 101 102 def _save(self, output): 103 for key, attr in self.output_attributes.items(): 104 attr_data = self.particulator.attributes[key].to_ndarray() 105 for drop_id in range(self.particulator.n_sd): 106 attr[drop_id].append(attr_data[drop_id]) 107 super()._save(output) 108 109 def run(self, observers=()): 110 for observer in observers: 111 self.particulator.observers.append(observer) 112 output_products = super()._run( 113 self.settings.nt, self.settings.steps_per_output_interval 114 ) 115 return {"products": output_products, "attributes": self.output_attributes}
15class Simulation(BasicSimulation): 16 def __init__( 17 self, 18 settings, 19 *, 20 products=None, 21 scipy_solver=False, 22 rtol_thd=1e-10, 23 rtol_x=1e-10, 24 mass_of_dry_air=44 * si.kg, 25 additional_attributes=None, 26 ): 27 n_sd = sum(settings.n_sd_per_mode) 28 builder = Builder( 29 n_sd=n_sd, 30 backend=CPU( 31 formulae=settings.formulae, override_jit_flags={"parallel": False} 32 ), 33 environment=Parcel( 34 dt=settings.timestep, 35 p0=settings.initial_pressure, 36 initial_water_vapour_mixing_ratio=settings.initial_vapour_mixing_ratio, 37 T0=settings.initial_temperature, 38 w=settings.vertical_velocity, 39 mass_of_dry_air=mass_of_dry_air, 40 ), 41 ) 42 if additional_attributes is not None: 43 for attribute in additional_attributes: 44 builder.request_attribute(attribute) 45 builder.add_dynamic(AmbientThermodynamics()) 46 builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x)) 47 48 volume = ( 49 builder.particulator.environment.mass_of_dry_air 50 / settings.initial_air_density 51 ) 52 attributes = { 53 k: np.empty(0) 54 for k in ("dry volume", "kappa times dry volume", "multiplicity") 55 } 56 for i, (kappa, spectrum) in enumerate(settings.aerosol_modes_by_kappa.items()): 57 sampling = ConstantMultiplicity(spectrum) 58 r_dry, n_per_volume = sampling.sample_deterministic( 59 settings.n_sd_per_mode[i] 60 ) 61 v_dry = settings.formulae.trivia.volume(radius=r_dry) 62 attributes["multiplicity"] = np.append( 63 attributes["multiplicity"], n_per_volume * volume 64 ) 65 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 66 attributes["kappa times dry volume"] = np.append( 67 attributes["kappa times dry volume"], v_dry * kappa 68 ) 69 r_wet = equilibrate_wet_radii( 70 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 71 environment=builder.particulator.environment, 72 kappa_times_dry_volume=attributes["kappa times dry volume"], 73 ) 74 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 75 76 super().__init__( 77 particulator=builder.build(attributes=attributes, products=products) 78 ) 79 if scipy_solver: 80 scipy_ode_condensation_solver.patch_particulator(self.particulator) 81 82 self.output_attributes = { 83 attr: tuple([] for _ in range(self.particulator.n_sd)) 84 for attr in ["volume"] 85 + (list(additional_attributes) if additional_attributes is not None else []) 86 } 87 self.settings = settings 88 89 self.__sanity_checks(attributes, volume) 90 91 def __sanity_checks(self, attributes, volume): 92 for attribute in attributes.values(): 93 assert attribute.shape[0] == self.particulator.n_sd 94 np.testing.assert_approx_equal( 95 sum(attributes["multiplicity"]) / volume, 96 sum( 97 mode.norm_factor 98 for mode in self.settings.aerosol_modes_by_kappa.values() 99 ), 100 significant=4, 101 ) 102 103 def _save(self, output): 104 for key, attr in self.output_attributes.items(): 105 attr_data = self.particulator.attributes[key].to_ndarray() 106 for drop_id in range(self.particulator.n_sd): 107 attr[drop_id].append(attr_data[drop_id]) 108 super()._save(output) 109 110 def run(self, observers=()): 111 for observer in observers: 112 self.particulator.observers.append(observer) 113 output_products = super()._run( 114 self.settings.nt, self.settings.steps_per_output_interval 115 ) 116 return {"products": output_products, "attributes": self.output_attributes}
Simulation( settings, *, products=None, scipy_solver=False, rtol_thd=1e-10, rtol_x=1e-10, mass_of_dry_air=44.0, additional_attributes=None)
16 def __init__( 17 self, 18 settings, 19 *, 20 products=None, 21 scipy_solver=False, 22 rtol_thd=1e-10, 23 rtol_x=1e-10, 24 mass_of_dry_air=44 * si.kg, 25 additional_attributes=None, 26 ): 27 n_sd = sum(settings.n_sd_per_mode) 28 builder = Builder( 29 n_sd=n_sd, 30 backend=CPU( 31 formulae=settings.formulae, override_jit_flags={"parallel": False} 32 ), 33 environment=Parcel( 34 dt=settings.timestep, 35 p0=settings.initial_pressure, 36 initial_water_vapour_mixing_ratio=settings.initial_vapour_mixing_ratio, 37 T0=settings.initial_temperature, 38 w=settings.vertical_velocity, 39 mass_of_dry_air=mass_of_dry_air, 40 ), 41 ) 42 if additional_attributes is not None: 43 for attribute in additional_attributes: 44 builder.request_attribute(attribute) 45 builder.add_dynamic(AmbientThermodynamics()) 46 builder.add_dynamic(Condensation(rtol_thd=rtol_thd, rtol_x=rtol_x)) 47 48 volume = ( 49 builder.particulator.environment.mass_of_dry_air 50 / settings.initial_air_density 51 ) 52 attributes = { 53 k: np.empty(0) 54 for k in ("dry volume", "kappa times dry volume", "multiplicity") 55 } 56 for i, (kappa, spectrum) in enumerate(settings.aerosol_modes_by_kappa.items()): 57 sampling = ConstantMultiplicity(spectrum) 58 r_dry, n_per_volume = sampling.sample_deterministic( 59 settings.n_sd_per_mode[i] 60 ) 61 v_dry = settings.formulae.trivia.volume(radius=r_dry) 62 attributes["multiplicity"] = np.append( 63 attributes["multiplicity"], n_per_volume * volume 64 ) 65 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 66 attributes["kappa times dry volume"] = np.append( 67 attributes["kappa times dry volume"], v_dry * kappa 68 ) 69 r_wet = equilibrate_wet_radii( 70 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 71 environment=builder.particulator.environment, 72 kappa_times_dry_volume=attributes["kappa times dry volume"], 73 ) 74 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 75 76 super().__init__( 77 particulator=builder.build(attributes=attributes, products=products) 78 ) 79 if scipy_solver: 80 scipy_ode_condensation_solver.patch_particulator(self.particulator) 81 82 self.output_attributes = { 83 attr: tuple([] for _ in range(self.particulator.n_sd)) 84 for attr in ["volume"] 85 + (list(additional_attributes) if additional_attributes is not None else []) 86 } 87 self.settings = settings 88 89 self.__sanity_checks(attributes, volume)