PySDM_examples.Shipway_and_Hill_2012.simulation

  1from collections import namedtuple
  2from typing import List, Any
  3
  4import numpy as np
  5from PySDM_examples.Shipway_and_Hill_2012.mpdata_1d import MPDATA_1D
  6
  7import PySDM.products as PySDM_products
  8from PySDM import Particulator
  9from PySDM.backends import CPU
 10from PySDM.dynamics import (
 11    AmbientThermodynamics,
 12    Coalescence,
 13    Condensation,
 14    Displacement,
 15    EulerianAdvection,
 16)
 17from PySDM.environments.kinematic_1d import Kinematic1D
 18from PySDM.impl.mesh import Mesh
 19from PySDM.initialisation.sampling import spatial_sampling, spectral_sampling
 20
 21
 22class Simulation:
 23    def __init__(self, settings, backend=CPU):
 24        self.nt = settings.nt
 25        self.z0 = -settings.particle_reservoir_depth
 26        self.save_spec_and_attr_times = settings.save_spec_and_attr_times
 27        self.number_of_bins = settings.number_of_bins
 28
 29        self.particulator = None
 30        self.output_attributes = None
 31        self.output_products = None
 32
 33        self.mesh = Mesh(
 34            grid=(settings.nz,),
 35            size=(settings.z_max + settings.particle_reservoir_depth,),
 36        )
 37
 38        def zZ_to_z_above_reservoir(zZ):
 39            z_above_reservoir = zZ * (settings.nz * settings.dz) + self.z0
 40            return z_above_reservoir
 41
 42        mpdata = MPDATA_1D(
 43            nz=settings.nz,
 44            dt=settings.dt,
 45            mpdata_settings=settings.mpdata_settings,
 46            advector_of_t=lambda t: settings.rho_times_w(t) * settings.dt / settings.dz,
 47            advectee_of_zZ_at_t0=lambda zZ: settings.water_vapour_mixing_ratio(
 48                zZ_to_z_above_reservoir(zZ)
 49            ),
 50            g_factor_of_zZ=lambda zZ: settings.rhod(zZ_to_z_above_reservoir(zZ)),
 51        )
 52
 53        env = Kinematic1D(
 54            dt=settings.dt,
 55            mesh=self.mesh,
 56            thd_of_z=settings.thd,
 57            rhod_of_z=settings.rhod,
 58            z0=-settings.particle_reservoir_depth,
 59            backend=backend(formulae=settings.formulae, n_dims=1),
 60            solvers=mpdata,
 61        )
 62
 63        _extra_nz = settings.particle_reservoir_depth // settings.dz
 64        _z_vec = settings.dz * np.linspace(
 65            -_extra_nz, settings.nz - _extra_nz, settings.nz + 1
 66        )
 67        self.g_factor_vec = settings.rhod(_z_vec)
 68
 69        dynamics: List[Any] = [AmbientThermodynamics()]
 70
 71        if settings.enable_condensation:
 72            dynamics.append(
 73                Condensation(
 74                    adaptive=settings.condensation_adaptive,
 75                    rtol_thd=settings.condensation_rtol_thd,
 76                    rtol_x=settings.condensation_rtol_x,
 77                    update_thd=settings.condensation_update_thd,
 78                )
 79            )
 80        dynamics.append(EulerianAdvection())
 81
 82        self.products = []
 83        if settings.precip:
 84            self.add_collision_dynamic(dynamics, settings, self.products)
 85
 86        dynamics.append(
 87            Displacement(
 88                enable_sedimentation=settings.precip,
 89                precipitation_counting_level_index=int(
 90                    settings.particle_reservoir_depth / settings.dz
 91                ),
 92            )
 93        )
 94        self.attributes = env.init_attributes(
 95            spatial_discretisation=spatial_sampling.Pseudorandom(),
 96            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
 97                spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air
 98            ),
 99            kappa=settings.kappa,
100            collisions_only=not settings.enable_condensation,
101            z_part=settings.z_part,
102            n_sd=settings.n_sd,
103        )
104        self.products += [
105            PySDM_products.WaterMixingRatio(
106                name="cloud water mixing ratio",
107                unit="g/kg",
108                radius_range=settings.cloud_water_radius_range,
109            ),
110            PySDM_products.WaterMixingRatio(
111                name="rain water mixing ratio",
112                unit="g/kg",
113                radius_range=settings.rain_water_radius_range,
114            ),
115            PySDM_products.AmbientDryAirDensity(name="rhod"),
116            PySDM_products.AmbientDryAirPotentialTemperature(name="thd"),
117            PySDM_products.ParticleSizeSpectrumPerVolume(
118                name="wet spectrum", radius_bins_edges=settings.r_bins_edges
119            ),
120            PySDM_products.ParticleConcentration(
121                name="nc", radius_range=settings.cloud_water_radius_range
122            ),
123            PySDM_products.ParticleConcentration(
124                name="nr", radius_range=settings.rain_water_radius_range
125            ),
126            PySDM_products.ParticleConcentration(
127                name="na", radius_range=(0, settings.cloud_water_radius_range[0])
128            ),
129            PySDM_products.MeanRadius(),
130            PySDM_products.EffectiveRadius(
131                radius_range=settings.cloud_water_radius_range
132            ),
133            PySDM_products.SuperDropletCountPerGridbox(),
134            PySDM_products.AveragedTerminalVelocity(
135                name="rain averaged terminal velocity",
136                radius_range=settings.rain_water_radius_range,
137            ),
138            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
139            PySDM_products.AmbientPressure(name="p"),
140            PySDM_products.AmbientTemperature(name="T"),
141            PySDM_products.AmbientWaterVapourMixingRatio(
142                name="water_vapour_mixing_ratio"
143            ),
144        ]
145        if settings.enable_condensation:
146            self.products.extend(
147                [
148                    PySDM_products.RipeningRate(name="ripening"),
149                    PySDM_products.ActivatingRate(name="activating"),
150                    PySDM_products.DeactivatingRate(name="deactivating"),
151                    PySDM_products.PeakSaturation(),
152                    PySDM_products.ParticleSizeSpectrumPerVolume(
153                        name="dry spectrum",
154                        radius_bins_edges=settings.r_bins_edges_dry,
155                        dry=True,
156                    ),
157                ]
158            )
159        if settings.precip:
160            self.products.extend(
161                [
162                    PySDM_products.CollisionRatePerGridbox(
163                        name="collision_rate",
164                    ),
165                    PySDM_products.CollisionRateDeficitPerGridbox(
166                        name="collision_deficit",
167                    ),
168                    PySDM_products.CoalescenceRatePerGridbox(
169                        name="coalescence_rate",
170                    ),
171                    PySDM_products.SurfacePrecipitation(),
172                ]
173            )
174        self.particulator = Particulator(
175            n_sd=settings.n_sd,
176            environment=env,
177            dynamics=dynamics,
178            attributes=self.attributes,
179            products=tuple(self.products),
180        )
181
182        self.output_attributes = {
183            "cell origin": [],
184            "position in cell": [],
185            "radius": [],
186            "multiplicity": [],
187        }
188        self.output_products = {}
189        for k, v in self.particulator.products.items():
190            if len(v.shape) == 0:
191                self.output_products[k] = np.zeros(self.nt + 1)
192            elif len(v.shape) == 1:
193                self.output_products[k] = np.zeros((self.mesh.grid[-1], self.nt + 1))
194            elif len(v.shape) == 2:
195                number_of_time_sections = len(self.save_spec_and_attr_times)
196                self.output_products[k] = np.zeros(
197                    (self.mesh.grid[-1], self.number_of_bins, number_of_time_sections)
198                )
199
200    @staticmethod
201    def add_collision_dynamic(dynamics, settings, _):
202        dynamics.append(
203            Coalescence(
204                collision_kernel=settings.collision_kernel,
205                adaptive=settings.coalescence_adaptive,
206            )
207        )
208
209    def save_scalar(self, step):
210        for k, v in self.particulator.products.items():
211            if len(v.shape) > 1:
212                continue
213            if len(v.shape) == 1:
214                self.output_products[k][:, step] = v.get()
215            else:
216                self.output_products[k][step] = v.get()
217
218    def save_spectrum(self, index):
219        for k, v in self.particulator.products.items():
220            if len(v.shape) == 2:
221                self.output_products[k][:, :, index] = v.get()
222
223    def save_attributes(self):
224        for k, v in self.output_attributes.items():
225            v.append(self.particulator.attributes[k].to_ndarray())
226
227    def save(self, step):
228        self.save_scalar(step)
229        time = step * self.particulator.dt
230        if len(self.save_spec_and_attr_times) > 0 and (
231            np.min(
232                np.abs(
233                    np.ones_like(self.save_spec_and_attr_times) * time
234                    - np.array(self.save_spec_and_attr_times)
235                )
236            )
237            < 0.1
238        ):
239            save_index = np.argmin(
240                np.abs(
241                    np.ones_like(self.save_spec_and_attr_times) * time
242                    - np.array(self.save_spec_and_attr_times)
243                )
244            )
245            self.save_spectrum(save_index)
246            self.save_attributes()
247
248    def run(self):
249        mesh = self.particulator.mesh
250
251        assert "t" not in self.output_products and "z" not in self.output_products
252        self.output_products["t"] = np.linspace(
253            0, self.nt * self.particulator.dt, self.nt + 1, endpoint=True
254        )
255        self.output_products["z"] = np.linspace(
256            self.z0 + mesh.dz / 2,
257            self.z0 + (mesh.grid[-1] - 1 / 2) * mesh.dz,
258            mesh.grid[-1],
259            endpoint=True,
260        )
261
262        self.save(0)
263        for step in range(self.nt):
264            mpdata = self.particulator.environment.solvers
265            mpdata.update_advector_field()
266            if "Displacement" in self.particulator.dynamics:
267                self.particulator.dynamics["Displacement"].upload_courant_field(
268                    (mpdata.advector / self.g_factor_vec,)
269                )
270            self.particulator.run(steps=1)
271            self.save(step + 1)
272
273        Outputs = namedtuple("Outputs", "products attributes")
274        output_results = Outputs(self.output_products, self.output_attributes)
275        return output_results
class Simulation:
 23class Simulation:
 24    def __init__(self, settings, backend=CPU):
 25        self.nt = settings.nt
 26        self.z0 = -settings.particle_reservoir_depth
 27        self.save_spec_and_attr_times = settings.save_spec_and_attr_times
 28        self.number_of_bins = settings.number_of_bins
 29
 30        self.particulator = None
 31        self.output_attributes = None
 32        self.output_products = None
 33
 34        self.mesh = Mesh(
 35            grid=(settings.nz,),
 36            size=(settings.z_max + settings.particle_reservoir_depth,),
 37        )
 38
 39        def zZ_to_z_above_reservoir(zZ):
 40            z_above_reservoir = zZ * (settings.nz * settings.dz) + self.z0
 41            return z_above_reservoir
 42
 43        mpdata = MPDATA_1D(
 44            nz=settings.nz,
 45            dt=settings.dt,
 46            mpdata_settings=settings.mpdata_settings,
 47            advector_of_t=lambda t: settings.rho_times_w(t) * settings.dt / settings.dz,
 48            advectee_of_zZ_at_t0=lambda zZ: settings.water_vapour_mixing_ratio(
 49                zZ_to_z_above_reservoir(zZ)
 50            ),
 51            g_factor_of_zZ=lambda zZ: settings.rhod(zZ_to_z_above_reservoir(zZ)),
 52        )
 53
 54        env = Kinematic1D(
 55            dt=settings.dt,
 56            mesh=self.mesh,
 57            thd_of_z=settings.thd,
 58            rhod_of_z=settings.rhod,
 59            z0=-settings.particle_reservoir_depth,
 60            backend=backend(formulae=settings.formulae, n_dims=1),
 61            solvers=mpdata,
 62        )
 63
 64        _extra_nz = settings.particle_reservoir_depth // settings.dz
 65        _z_vec = settings.dz * np.linspace(
 66            -_extra_nz, settings.nz - _extra_nz, settings.nz + 1
 67        )
 68        self.g_factor_vec = settings.rhod(_z_vec)
 69
 70        dynamics: List[Any] = [AmbientThermodynamics()]
 71
 72        if settings.enable_condensation:
 73            dynamics.append(
 74                Condensation(
 75                    adaptive=settings.condensation_adaptive,
 76                    rtol_thd=settings.condensation_rtol_thd,
 77                    rtol_x=settings.condensation_rtol_x,
 78                    update_thd=settings.condensation_update_thd,
 79                )
 80            )
 81        dynamics.append(EulerianAdvection())
 82
 83        self.products = []
 84        if settings.precip:
 85            self.add_collision_dynamic(dynamics, settings, self.products)
 86
 87        dynamics.append(
 88            Displacement(
 89                enable_sedimentation=settings.precip,
 90                precipitation_counting_level_index=int(
 91                    settings.particle_reservoir_depth / settings.dz
 92                ),
 93            )
 94        )
 95        self.attributes = env.init_attributes(
 96            spatial_discretisation=spatial_sampling.Pseudorandom(),
 97            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
 98                spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air
 99            ),
100            kappa=settings.kappa,
101            collisions_only=not settings.enable_condensation,
102            z_part=settings.z_part,
103            n_sd=settings.n_sd,
104        )
105        self.products += [
106            PySDM_products.WaterMixingRatio(
107                name="cloud water mixing ratio",
108                unit="g/kg",
109                radius_range=settings.cloud_water_radius_range,
110            ),
111            PySDM_products.WaterMixingRatio(
112                name="rain water mixing ratio",
113                unit="g/kg",
114                radius_range=settings.rain_water_radius_range,
115            ),
116            PySDM_products.AmbientDryAirDensity(name="rhod"),
117            PySDM_products.AmbientDryAirPotentialTemperature(name="thd"),
118            PySDM_products.ParticleSizeSpectrumPerVolume(
119                name="wet spectrum", radius_bins_edges=settings.r_bins_edges
120            ),
121            PySDM_products.ParticleConcentration(
122                name="nc", radius_range=settings.cloud_water_radius_range
123            ),
124            PySDM_products.ParticleConcentration(
125                name="nr", radius_range=settings.rain_water_radius_range
126            ),
127            PySDM_products.ParticleConcentration(
128                name="na", radius_range=(0, settings.cloud_water_radius_range[0])
129            ),
130            PySDM_products.MeanRadius(),
131            PySDM_products.EffectiveRadius(
132                radius_range=settings.cloud_water_radius_range
133            ),
134            PySDM_products.SuperDropletCountPerGridbox(),
135            PySDM_products.AveragedTerminalVelocity(
136                name="rain averaged terminal velocity",
137                radius_range=settings.rain_water_radius_range,
138            ),
139            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
140            PySDM_products.AmbientPressure(name="p"),
141            PySDM_products.AmbientTemperature(name="T"),
142            PySDM_products.AmbientWaterVapourMixingRatio(
143                name="water_vapour_mixing_ratio"
144            ),
145        ]
146        if settings.enable_condensation:
147            self.products.extend(
148                [
149                    PySDM_products.RipeningRate(name="ripening"),
150                    PySDM_products.ActivatingRate(name="activating"),
151                    PySDM_products.DeactivatingRate(name="deactivating"),
152                    PySDM_products.PeakSaturation(),
153                    PySDM_products.ParticleSizeSpectrumPerVolume(
154                        name="dry spectrum",
155                        radius_bins_edges=settings.r_bins_edges_dry,
156                        dry=True,
157                    ),
158                ]
159            )
160        if settings.precip:
161            self.products.extend(
162                [
163                    PySDM_products.CollisionRatePerGridbox(
164                        name="collision_rate",
165                    ),
166                    PySDM_products.CollisionRateDeficitPerGridbox(
167                        name="collision_deficit",
168                    ),
169                    PySDM_products.CoalescenceRatePerGridbox(
170                        name="coalescence_rate",
171                    ),
172                    PySDM_products.SurfacePrecipitation(),
173                ]
174            )
175        self.particulator = Particulator(
176            n_sd=settings.n_sd,
177            environment=env,
178            dynamics=dynamics,
179            attributes=self.attributes,
180            products=tuple(self.products),
181        )
182
183        self.output_attributes = {
184            "cell origin": [],
185            "position in cell": [],
186            "radius": [],
187            "multiplicity": [],
188        }
189        self.output_products = {}
190        for k, v in self.particulator.products.items():
191            if len(v.shape) == 0:
192                self.output_products[k] = np.zeros(self.nt + 1)
193            elif len(v.shape) == 1:
194                self.output_products[k] = np.zeros((self.mesh.grid[-1], self.nt + 1))
195            elif len(v.shape) == 2:
196                number_of_time_sections = len(self.save_spec_and_attr_times)
197                self.output_products[k] = np.zeros(
198                    (self.mesh.grid[-1], self.number_of_bins, number_of_time_sections)
199                )
200
201    @staticmethod
202    def add_collision_dynamic(dynamics, settings, _):
203        dynamics.append(
204            Coalescence(
205                collision_kernel=settings.collision_kernel,
206                adaptive=settings.coalescence_adaptive,
207            )
208        )
209
210    def save_scalar(self, step):
211        for k, v in self.particulator.products.items():
212            if len(v.shape) > 1:
213                continue
214            if len(v.shape) == 1:
215                self.output_products[k][:, step] = v.get()
216            else:
217                self.output_products[k][step] = v.get()
218
219    def save_spectrum(self, index):
220        for k, v in self.particulator.products.items():
221            if len(v.shape) == 2:
222                self.output_products[k][:, :, index] = v.get()
223
224    def save_attributes(self):
225        for k, v in self.output_attributes.items():
226            v.append(self.particulator.attributes[k].to_ndarray())
227
228    def save(self, step):
229        self.save_scalar(step)
230        time = step * self.particulator.dt
231        if len(self.save_spec_and_attr_times) > 0 and (
232            np.min(
233                np.abs(
234                    np.ones_like(self.save_spec_and_attr_times) * time
235                    - np.array(self.save_spec_and_attr_times)
236                )
237            )
238            < 0.1
239        ):
240            save_index = np.argmin(
241                np.abs(
242                    np.ones_like(self.save_spec_and_attr_times) * time
243                    - np.array(self.save_spec_and_attr_times)
244                )
245            )
246            self.save_spectrum(save_index)
247            self.save_attributes()
248
249    def run(self):
250        mesh = self.particulator.mesh
251
252        assert "t" not in self.output_products and "z" not in self.output_products
253        self.output_products["t"] = np.linspace(
254            0, self.nt * self.particulator.dt, self.nt + 1, endpoint=True
255        )
256        self.output_products["z"] = np.linspace(
257            self.z0 + mesh.dz / 2,
258            self.z0 + (mesh.grid[-1] - 1 / 2) * mesh.dz,
259            mesh.grid[-1],
260            endpoint=True,
261        )
262
263        self.save(0)
264        for step in range(self.nt):
265            mpdata = self.particulator.environment.solvers
266            mpdata.update_advector_field()
267            if "Displacement" in self.particulator.dynamics:
268                self.particulator.dynamics["Displacement"].upload_courant_field(
269                    (mpdata.advector / self.g_factor_vec,)
270                )
271            self.particulator.run(steps=1)
272            self.save(step + 1)
273
274        Outputs = namedtuple("Outputs", "products attributes")
275        output_results = Outputs(self.output_products, self.output_attributes)
276        return output_results
Simulation( settings, backend=functools.partial(<function _cached_backend>, backend_class=<class 'PySDM.backends.Numba'>))
 24    def __init__(self, settings, backend=CPU):
 25        self.nt = settings.nt
 26        self.z0 = -settings.particle_reservoir_depth
 27        self.save_spec_and_attr_times = settings.save_spec_and_attr_times
 28        self.number_of_bins = settings.number_of_bins
 29
 30        self.particulator = None
 31        self.output_attributes = None
 32        self.output_products = None
 33
 34        self.mesh = Mesh(
 35            grid=(settings.nz,),
 36            size=(settings.z_max + settings.particle_reservoir_depth,),
 37        )
 38
 39        def zZ_to_z_above_reservoir(zZ):
 40            z_above_reservoir = zZ * (settings.nz * settings.dz) + self.z0
 41            return z_above_reservoir
 42
 43        mpdata = MPDATA_1D(
 44            nz=settings.nz,
 45            dt=settings.dt,
 46            mpdata_settings=settings.mpdata_settings,
 47            advector_of_t=lambda t: settings.rho_times_w(t) * settings.dt / settings.dz,
 48            advectee_of_zZ_at_t0=lambda zZ: settings.water_vapour_mixing_ratio(
 49                zZ_to_z_above_reservoir(zZ)
 50            ),
 51            g_factor_of_zZ=lambda zZ: settings.rhod(zZ_to_z_above_reservoir(zZ)),
 52        )
 53
 54        env = Kinematic1D(
 55            dt=settings.dt,
 56            mesh=self.mesh,
 57            thd_of_z=settings.thd,
 58            rhod_of_z=settings.rhod,
 59            z0=-settings.particle_reservoir_depth,
 60            backend=backend(formulae=settings.formulae, n_dims=1),
 61            solvers=mpdata,
 62        )
 63
 64        _extra_nz = settings.particle_reservoir_depth // settings.dz
 65        _z_vec = settings.dz * np.linspace(
 66            -_extra_nz, settings.nz - _extra_nz, settings.nz + 1
 67        )
 68        self.g_factor_vec = settings.rhod(_z_vec)
 69
 70        dynamics: List[Any] = [AmbientThermodynamics()]
 71
 72        if settings.enable_condensation:
 73            dynamics.append(
 74                Condensation(
 75                    adaptive=settings.condensation_adaptive,
 76                    rtol_thd=settings.condensation_rtol_thd,
 77                    rtol_x=settings.condensation_rtol_x,
 78                    update_thd=settings.condensation_update_thd,
 79                )
 80            )
 81        dynamics.append(EulerianAdvection())
 82
 83        self.products = []
 84        if settings.precip:
 85            self.add_collision_dynamic(dynamics, settings, self.products)
 86
 87        dynamics.append(
 88            Displacement(
 89                enable_sedimentation=settings.precip,
 90                precipitation_counting_level_index=int(
 91                    settings.particle_reservoir_depth / settings.dz
 92                ),
 93            )
 94        )
 95        self.attributes = env.init_attributes(
 96            spatial_discretisation=spatial_sampling.Pseudorandom(),
 97            spectral_discretisation=spectral_sampling.ConstantMultiplicity(
 98                spectrum=settings.wet_radius_spectrum_per_mass_of_dry_air
 99            ),
100            kappa=settings.kappa,
101            collisions_only=not settings.enable_condensation,
102            z_part=settings.z_part,
103            n_sd=settings.n_sd,
104        )
105        self.products += [
106            PySDM_products.WaterMixingRatio(
107                name="cloud water mixing ratio",
108                unit="g/kg",
109                radius_range=settings.cloud_water_radius_range,
110            ),
111            PySDM_products.WaterMixingRatio(
112                name="rain water mixing ratio",
113                unit="g/kg",
114                radius_range=settings.rain_water_radius_range,
115            ),
116            PySDM_products.AmbientDryAirDensity(name="rhod"),
117            PySDM_products.AmbientDryAirPotentialTemperature(name="thd"),
118            PySDM_products.ParticleSizeSpectrumPerVolume(
119                name="wet spectrum", radius_bins_edges=settings.r_bins_edges
120            ),
121            PySDM_products.ParticleConcentration(
122                name="nc", radius_range=settings.cloud_water_radius_range
123            ),
124            PySDM_products.ParticleConcentration(
125                name="nr", radius_range=settings.rain_water_radius_range
126            ),
127            PySDM_products.ParticleConcentration(
128                name="na", radius_range=(0, settings.cloud_water_radius_range[0])
129            ),
130            PySDM_products.MeanRadius(),
131            PySDM_products.EffectiveRadius(
132                radius_range=settings.cloud_water_radius_range
133            ),
134            PySDM_products.SuperDropletCountPerGridbox(),
135            PySDM_products.AveragedTerminalVelocity(
136                name="rain averaged terminal velocity",
137                radius_range=settings.rain_water_radius_range,
138            ),
139            PySDM_products.AmbientRelativeHumidity(name="RH", unit="%"),
140            PySDM_products.AmbientPressure(name="p"),
141            PySDM_products.AmbientTemperature(name="T"),
142            PySDM_products.AmbientWaterVapourMixingRatio(
143                name="water_vapour_mixing_ratio"
144            ),
145        ]
146        if settings.enable_condensation:
147            self.products.extend(
148                [
149                    PySDM_products.RipeningRate(name="ripening"),
150                    PySDM_products.ActivatingRate(name="activating"),
151                    PySDM_products.DeactivatingRate(name="deactivating"),
152                    PySDM_products.PeakSaturation(),
153                    PySDM_products.ParticleSizeSpectrumPerVolume(
154                        name="dry spectrum",
155                        radius_bins_edges=settings.r_bins_edges_dry,
156                        dry=True,
157                    ),
158                ]
159            )
160        if settings.precip:
161            self.products.extend(
162                [
163                    PySDM_products.CollisionRatePerGridbox(
164                        name="collision_rate",
165                    ),
166                    PySDM_products.CollisionRateDeficitPerGridbox(
167                        name="collision_deficit",
168                    ),
169                    PySDM_products.CoalescenceRatePerGridbox(
170                        name="coalescence_rate",
171                    ),
172                    PySDM_products.SurfacePrecipitation(),
173                ]
174            )
175        self.particulator = Particulator(
176            n_sd=settings.n_sd,
177            environment=env,
178            dynamics=dynamics,
179            attributes=self.attributes,
180            products=tuple(self.products),
181        )
182
183        self.output_attributes = {
184            "cell origin": [],
185            "position in cell": [],
186            "radius": [],
187            "multiplicity": [],
188        }
189        self.output_products = {}
190        for k, v in self.particulator.products.items():
191            if len(v.shape) == 0:
192                self.output_products[k] = np.zeros(self.nt + 1)
193            elif len(v.shape) == 1:
194                self.output_products[k] = np.zeros((self.mesh.grid[-1], self.nt + 1))
195            elif len(v.shape) == 2:
196                number_of_time_sections = len(self.save_spec_and_attr_times)
197                self.output_products[k] = np.zeros(
198                    (self.mesh.grid[-1], self.number_of_bins, number_of_time_sections)
199                )
nt
z0
save_spec_and_attr_times
number_of_bins
particulator
output_attributes
output_products
mesh
g_factor_vec
products
attributes
@staticmethod
def add_collision_dynamic(dynamics, settings, _):
201    @staticmethod
202    def add_collision_dynamic(dynamics, settings, _):
203        dynamics.append(
204            Coalescence(
205                collision_kernel=settings.collision_kernel,
206                adaptive=settings.coalescence_adaptive,
207            )
208        )
def save_scalar(self, step):
210    def save_scalar(self, step):
211        for k, v in self.particulator.products.items():
212            if len(v.shape) > 1:
213                continue
214            if len(v.shape) == 1:
215                self.output_products[k][:, step] = v.get()
216            else:
217                self.output_products[k][step] = v.get()
def save_spectrum(self, index):
219    def save_spectrum(self, index):
220        for k, v in self.particulator.products.items():
221            if len(v.shape) == 2:
222                self.output_products[k][:, :, index] = v.get()
def save_attributes(self):
224    def save_attributes(self):
225        for k, v in self.output_attributes.items():
226            v.append(self.particulator.attributes[k].to_ndarray())
def save(self, step):
228    def save(self, step):
229        self.save_scalar(step)
230        time = step * self.particulator.dt
231        if len(self.save_spec_and_attr_times) > 0 and (
232            np.min(
233                np.abs(
234                    np.ones_like(self.save_spec_and_attr_times) * time
235                    - np.array(self.save_spec_and_attr_times)
236                )
237            )
238            < 0.1
239        ):
240            save_index = np.argmin(
241                np.abs(
242                    np.ones_like(self.save_spec_and_attr_times) * time
243                    - np.array(self.save_spec_and_attr_times)
244                )
245            )
246            self.save_spectrum(save_index)
247            self.save_attributes()
def run(self):
249    def run(self):
250        mesh = self.particulator.mesh
251
252        assert "t" not in self.output_products and "z" not in self.output_products
253        self.output_products["t"] = np.linspace(
254            0, self.nt * self.particulator.dt, self.nt + 1, endpoint=True
255        )
256        self.output_products["z"] = np.linspace(
257            self.z0 + mesh.dz / 2,
258            self.z0 + (mesh.grid[-1] - 1 / 2) * mesh.dz,
259            mesh.grid[-1],
260            endpoint=True,
261        )
262
263        self.save(0)
264        for step in range(self.nt):
265            mpdata = self.particulator.environment.solvers
266            mpdata.update_advector_field()
267            if "Displacement" in self.particulator.dynamics:
268                self.particulator.dynamics["Displacement"].upload_courant_field(
269                    (mpdata.advector / self.g_factor_vec,)
270                )
271            self.particulator.run(steps=1)
272            self.save(step + 1)
273
274        Outputs = namedtuple("Outputs", "products attributes")
275        output_results = Outputs(self.output_products, self.output_attributes)
276        return output_results