PySDM_examples.Lowe_et_al_2019.simulation
1import numpy as np 2from PySDM_examples.utils import BasicSimulation 3 4import PySDM.products as PySDM_products 5from PySDM import Builder 6from PySDM.dynamics import AmbientThermodynamics, Condensation 7from PySDM.environments import Parcel 8from PySDM.initialisation import equilibrate_wet_radii 9from PySDM.initialisation.spectra import Sum 10 11 12class Simulation(BasicSimulation): 13 def __init__(self, settings, products=None): 14 n_sd = settings.n_sd_per_mode * len(settings.aerosol.modes) 15 builder = Builder( 16 n_sd=n_sd, 17 backend=settings.backend, 18 environment=Parcel( 19 dt=settings.dt, 20 mass_of_dry_air=settings.mass_of_dry_air, 21 p0=settings.p0, 22 initial_water_vapour_mixing_ratio=settings.initial_water_vapour_mixing_ratio, 23 T0=settings.T0, 24 w=settings.w, 25 ), 26 ) 27 28 attributes = { 29 "dry volume": np.empty(0), 30 "dry volume organic": np.empty(0), 31 "kappa times dry volume": np.empty(0), 32 "multiplicity": np.ndarray(0), 33 } 34 initial_volume = settings.mass_of_dry_air / settings.rho0 35 for mode in settings.aerosol.modes: 36 r_dry, n_in_dv = settings.spectral_sampling( 37 spectrum=mode["spectrum"] 38 ).sample(settings.n_sd_per_mode) 39 v_dry = settings.formulae.trivia.volume(radius=r_dry) 40 attributes["multiplicity"] = np.append( 41 attributes["multiplicity"], n_in_dv * initial_volume 42 ) 43 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 44 attributes["dry volume organic"] = np.append( 45 attributes["dry volume organic"], mode["f_org"] * v_dry 46 ) 47 attributes["kappa times dry volume"] = np.append( 48 attributes["kappa times dry volume"], 49 v_dry * mode["kappa"][settings.model], 50 ) 51 for attribute in attributes.values(): 52 assert attribute.shape[0] == n_sd 53 54 np.testing.assert_approx_equal( 55 np.sum(attributes["multiplicity"]) / initial_volume, 56 Sum( 57 tuple( 58 settings.aerosol.modes[i]["spectrum"] 59 for i in range(len(settings.aerosol.modes)) 60 ) 61 ).norm_factor, 62 significant=5, 63 ) 64 r_wet = equilibrate_wet_radii( 65 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 66 environment=builder.particulator.environment, 67 kappa_times_dry_volume=attributes["kappa times dry volume"], 68 f_org=attributes["dry volume organic"] / attributes["dry volume"], 69 ) 70 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 71 72 if settings.model == "Constant": 73 del attributes["dry volume organic"] 74 75 builder.add_dynamic(AmbientThermodynamics()) 76 builder.add_dynamic(Condensation()) 77 78 products = products or ( 79 PySDM_products.ParcelDisplacement(name="z"), 80 PySDM_products.Time(name="t"), 81 PySDM_products.PeakSaturation(name="S_max"), 82 PySDM_products.AmbientRelativeHumidity(name="RH"), 83 PySDM_products.ActivatedParticleConcentration( 84 name="CDNC_cm3", 85 unit="cm^-3", 86 count_activated=True, 87 count_unactivated=False, 88 ), 89 PySDM_products.ParticleSizeSpectrumPerVolume( 90 radius_bins_edges=settings.wet_radius_bins_edges 91 ), 92 PySDM_products.ActivableFraction(name="Activated Fraction"), 93 PySDM_products.WaterMixingRatio(), 94 PySDM_products.AmbientDryAirDensity(name="rhod"), 95 PySDM_products.ActivatedEffectiveRadius( 96 name="reff", count_activated=True, count_unactivated=False 97 ), 98 PySDM_products.ParcelLiquidWaterPath( 99 name="lwp", count_activated=True, count_unactivated=False 100 ), 101 PySDM_products.CloudOpticalDepth(name="tau"), 102 PySDM_products.CloudAlbedo(name="albedo"), 103 ) 104 105 particulator = builder.build(attributes=attributes, products=products) 106 self.settings = settings 107 super().__init__(particulator=particulator) 108 109 def _save_scalars(self, output): 110 for k, v in self.particulator.products.items(): 111 if len(v.shape) > 1 or k in ("lwp", "Activated Fraction", "tau", "albedo"): 112 continue 113 value = v.get() 114 if isinstance(value, np.ndarray) and value.size == 1: 115 value = value[0] 116 output[k].append(value) 117 118 def _save_final_timestep_products(self, output): 119 output["spectrum"] = self.particulator.products[ 120 "particle size spectrum per volume" 121 ].get() 122 123 for name, args_call in { 124 "Activated Fraction": lambda: {"S_max": np.nanmax(output["S_max"])}, 125 "lwp": lambda: {}, 126 "tau": lambda: { 127 "effective_radius": output["reff"][-1], 128 "liquid_water_path": output["lwp"][0], 129 }, 130 "albedo": lambda: {"optical_depth": output["tau"]}, 131 }.items(): 132 output[name] = self.particulator.products[name].get(**args_call()) 133 134 def run(self): 135 output = {k: [] for k in self.particulator.products} 136 for step in self.settings.output_steps: 137 self.particulator.run(step - self.particulator.n_steps) 138 self._save_scalars(output) 139 self._save_final_timestep_products(output) 140 return output
13class Simulation(BasicSimulation): 14 def __init__(self, settings, products=None): 15 n_sd = settings.n_sd_per_mode * len(settings.aerosol.modes) 16 builder = Builder( 17 n_sd=n_sd, 18 backend=settings.backend, 19 environment=Parcel( 20 dt=settings.dt, 21 mass_of_dry_air=settings.mass_of_dry_air, 22 p0=settings.p0, 23 initial_water_vapour_mixing_ratio=settings.initial_water_vapour_mixing_ratio, 24 T0=settings.T0, 25 w=settings.w, 26 ), 27 ) 28 29 attributes = { 30 "dry volume": np.empty(0), 31 "dry volume organic": np.empty(0), 32 "kappa times dry volume": np.empty(0), 33 "multiplicity": np.ndarray(0), 34 } 35 initial_volume = settings.mass_of_dry_air / settings.rho0 36 for mode in settings.aerosol.modes: 37 r_dry, n_in_dv = settings.spectral_sampling( 38 spectrum=mode["spectrum"] 39 ).sample(settings.n_sd_per_mode) 40 v_dry = settings.formulae.trivia.volume(radius=r_dry) 41 attributes["multiplicity"] = np.append( 42 attributes["multiplicity"], n_in_dv * initial_volume 43 ) 44 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 45 attributes["dry volume organic"] = np.append( 46 attributes["dry volume organic"], mode["f_org"] * v_dry 47 ) 48 attributes["kappa times dry volume"] = np.append( 49 attributes["kappa times dry volume"], 50 v_dry * mode["kappa"][settings.model], 51 ) 52 for attribute in attributes.values(): 53 assert attribute.shape[0] == n_sd 54 55 np.testing.assert_approx_equal( 56 np.sum(attributes["multiplicity"]) / initial_volume, 57 Sum( 58 tuple( 59 settings.aerosol.modes[i]["spectrum"] 60 for i in range(len(settings.aerosol.modes)) 61 ) 62 ).norm_factor, 63 significant=5, 64 ) 65 r_wet = equilibrate_wet_radii( 66 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 67 environment=builder.particulator.environment, 68 kappa_times_dry_volume=attributes["kappa times dry volume"], 69 f_org=attributes["dry volume organic"] / attributes["dry volume"], 70 ) 71 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 72 73 if settings.model == "Constant": 74 del attributes["dry volume organic"] 75 76 builder.add_dynamic(AmbientThermodynamics()) 77 builder.add_dynamic(Condensation()) 78 79 products = products or ( 80 PySDM_products.ParcelDisplacement(name="z"), 81 PySDM_products.Time(name="t"), 82 PySDM_products.PeakSaturation(name="S_max"), 83 PySDM_products.AmbientRelativeHumidity(name="RH"), 84 PySDM_products.ActivatedParticleConcentration( 85 name="CDNC_cm3", 86 unit="cm^-3", 87 count_activated=True, 88 count_unactivated=False, 89 ), 90 PySDM_products.ParticleSizeSpectrumPerVolume( 91 radius_bins_edges=settings.wet_radius_bins_edges 92 ), 93 PySDM_products.ActivableFraction(name="Activated Fraction"), 94 PySDM_products.WaterMixingRatio(), 95 PySDM_products.AmbientDryAirDensity(name="rhod"), 96 PySDM_products.ActivatedEffectiveRadius( 97 name="reff", count_activated=True, count_unactivated=False 98 ), 99 PySDM_products.ParcelLiquidWaterPath( 100 name="lwp", count_activated=True, count_unactivated=False 101 ), 102 PySDM_products.CloudOpticalDepth(name="tau"), 103 PySDM_products.CloudAlbedo(name="albedo"), 104 ) 105 106 particulator = builder.build(attributes=attributes, products=products) 107 self.settings = settings 108 super().__init__(particulator=particulator) 109 110 def _save_scalars(self, output): 111 for k, v in self.particulator.products.items(): 112 if len(v.shape) > 1 or k in ("lwp", "Activated Fraction", "tau", "albedo"): 113 continue 114 value = v.get() 115 if isinstance(value, np.ndarray) and value.size == 1: 116 value = value[0] 117 output[k].append(value) 118 119 def _save_final_timestep_products(self, output): 120 output["spectrum"] = self.particulator.products[ 121 "particle size spectrum per volume" 122 ].get() 123 124 for name, args_call in { 125 "Activated Fraction": lambda: {"S_max": np.nanmax(output["S_max"])}, 126 "lwp": lambda: {}, 127 "tau": lambda: { 128 "effective_radius": output["reff"][-1], 129 "liquid_water_path": output["lwp"][0], 130 }, 131 "albedo": lambda: {"optical_depth": output["tau"]}, 132 }.items(): 133 output[name] = self.particulator.products[name].get(**args_call()) 134 135 def run(self): 136 output = {k: [] for k in self.particulator.products} 137 for step in self.settings.output_steps: 138 self.particulator.run(step - self.particulator.n_steps) 139 self._save_scalars(output) 140 self._save_final_timestep_products(output) 141 return output
Simulation(settings, products=None)
14 def __init__(self, settings, products=None): 15 n_sd = settings.n_sd_per_mode * len(settings.aerosol.modes) 16 builder = Builder( 17 n_sd=n_sd, 18 backend=settings.backend, 19 environment=Parcel( 20 dt=settings.dt, 21 mass_of_dry_air=settings.mass_of_dry_air, 22 p0=settings.p0, 23 initial_water_vapour_mixing_ratio=settings.initial_water_vapour_mixing_ratio, 24 T0=settings.T0, 25 w=settings.w, 26 ), 27 ) 28 29 attributes = { 30 "dry volume": np.empty(0), 31 "dry volume organic": np.empty(0), 32 "kappa times dry volume": np.empty(0), 33 "multiplicity": np.ndarray(0), 34 } 35 initial_volume = settings.mass_of_dry_air / settings.rho0 36 for mode in settings.aerosol.modes: 37 r_dry, n_in_dv = settings.spectral_sampling( 38 spectrum=mode["spectrum"] 39 ).sample(settings.n_sd_per_mode) 40 v_dry = settings.formulae.trivia.volume(radius=r_dry) 41 attributes["multiplicity"] = np.append( 42 attributes["multiplicity"], n_in_dv * initial_volume 43 ) 44 attributes["dry volume"] = np.append(attributes["dry volume"], v_dry) 45 attributes["dry volume organic"] = np.append( 46 attributes["dry volume organic"], mode["f_org"] * v_dry 47 ) 48 attributes["kappa times dry volume"] = np.append( 49 attributes["kappa times dry volume"], 50 v_dry * mode["kappa"][settings.model], 51 ) 52 for attribute in attributes.values(): 53 assert attribute.shape[0] == n_sd 54 55 np.testing.assert_approx_equal( 56 np.sum(attributes["multiplicity"]) / initial_volume, 57 Sum( 58 tuple( 59 settings.aerosol.modes[i]["spectrum"] 60 for i in range(len(settings.aerosol.modes)) 61 ) 62 ).norm_factor, 63 significant=5, 64 ) 65 r_wet = equilibrate_wet_radii( 66 r_dry=settings.formulae.trivia.radius(volume=attributes["dry volume"]), 67 environment=builder.particulator.environment, 68 kappa_times_dry_volume=attributes["kappa times dry volume"], 69 f_org=attributes["dry volume organic"] / attributes["dry volume"], 70 ) 71 attributes["volume"] = settings.formulae.trivia.volume(radius=r_wet) 72 73 if settings.model == "Constant": 74 del attributes["dry volume organic"] 75 76 builder.add_dynamic(AmbientThermodynamics()) 77 builder.add_dynamic(Condensation()) 78 79 products = products or ( 80 PySDM_products.ParcelDisplacement(name="z"), 81 PySDM_products.Time(name="t"), 82 PySDM_products.PeakSaturation(name="S_max"), 83 PySDM_products.AmbientRelativeHumidity(name="RH"), 84 PySDM_products.ActivatedParticleConcentration( 85 name="CDNC_cm3", 86 unit="cm^-3", 87 count_activated=True, 88 count_unactivated=False, 89 ), 90 PySDM_products.ParticleSizeSpectrumPerVolume( 91 radius_bins_edges=settings.wet_radius_bins_edges 92 ), 93 PySDM_products.ActivableFraction(name="Activated Fraction"), 94 PySDM_products.WaterMixingRatio(), 95 PySDM_products.AmbientDryAirDensity(name="rhod"), 96 PySDM_products.ActivatedEffectiveRadius( 97 name="reff", count_activated=True, count_unactivated=False 98 ), 99 PySDM_products.ParcelLiquidWaterPath( 100 name="lwp", count_activated=True, count_unactivated=False 101 ), 102 PySDM_products.CloudOpticalDepth(name="tau"), 103 PySDM_products.CloudAlbedo(name="albedo"), 104 ) 105 106 particulator = builder.build(attributes=attributes, products=products) 107 self.settings = settings 108 super().__init__(particulator=particulator)