PySDM_examples.Szumowski_et_al_1998.gui_settings

  1import inspect
  2
  3import numpy as np
  4from PySDM_examples.utils.widgets import (
  5    Accordion,
  6    Checkbox,
  7    Dropdown,
  8    FloatSlider,
  9    IntSlider,
 10    Layout,
 11    RadioButtons,
 12    VBox,
 13)
 14
 15from PySDM import Formulae, formulae, physics
 16from PySDM.initialisation.spectra import Lognormal
 17from PySDM.physics import si
 18
 19
 20class GUISettings:
 21    def __dir__(self):
 22        return self.__settings.__dir__()
 23
 24    def __init__(self, settings):
 25        self.__settings = settings
 26
 27        self.ui_dth0 = FloatSlider(
 28            description="$\\Delta\\theta_0$ [K]", value=0, min=-15, max=15
 29        )
 30        self.ui_delta_initial_water_vapour_mixing_ratio = FloatSlider(
 31            description="$\\Delta initial_water_vapour_mixing_ratio$ [g/kg]",
 32            value=0,
 33            min=-6,
 34            max=6,
 35        )
 36        self.ui_rhod_w_max = FloatSlider(
 37            description="$\\rho_d w_{max}$",
 38            value=settings.rhod_w_max,
 39            min=0.1,
 40            max=4,
 41            step=0.1,
 42        )
 43        self.ui_kappa = FloatSlider(
 44            description="$\\kappa$ [1]", value=settings.kappa, min=0, max=1.5
 45        )
 46
 47        self.ui_freezing = {
 48            "model": RadioButtons(
 49                options=("singular", "time-dependent"),
 50                description="immersion frz",
 51                layout={"width": "max-content"},
 52            ),
 53            "INP surface": RadioButtons(
 54                options=("as dry surface", "lognormal(A, sgm_g)"),
 55                description="INP surface",
 56            ),
 57            "lognormal_log10_A_um2": FloatSlider(
 58                description="log10(A/$μm^2$)", min=-3, max=1, step=0.5
 59            ),
 60            "lognormal_ln_sgm_g": FloatSlider(
 61                description="ln(sgm_g)", min=0.5, max=3, step=0.5
 62            ),
 63            "ABIFM fit": Dropdown(description="ABIFM fit", options=("Nonadecanol",)),
 64            "INAS fit": Dropdown(
 65                description="INAS fit", options=("Niemand et al. 2012",)
 66            ),
 67        }
 68        # TODO #599 cool rate product + sing/tdep diff prod
 69
 70        self.ui_nx = IntSlider(
 71            value=settings.grid[0], min=10, max=100, description="nx"
 72        )
 73        self.ui_nz = IntSlider(
 74            value=settings.grid[1], min=10, max=100, description="nz"
 75        )
 76        self.ui_dt = FloatSlider(
 77            value=settings.dt, min=0.5, max=60, description="dt (Eulerian)"
 78        )
 79        self.ui_simulation_time = IntSlider(
 80            value=settings.simulation_time,
 81            min=1800,
 82            max=7200,
 83            description="simulation time $[s]$",
 84        )
 85        self.ui_condensation_rtol_x = IntSlider(
 86            value=np.log10(settings.condensation_rtol_thd),
 87            min=-9,
 88            max=-3,
 89            description="log$_{10}$(rtol$_x$)",
 90        )
 91        self.ui_condensation_rtol_thd = IntSlider(
 92            value=np.log10(settings.condensation_rtol_thd),
 93            min=-9,
 94            max=-3,
 95            description="log$_{10}$(rtol$_\\theta$)",
 96        )
 97        self.ui_condensation_adaptive = Checkbox(
 98            value=settings.condensation_adaptive,
 99            description="condensation adaptive time-step",
100        )
101        self.ui_coalescence_adaptive = Checkbox(
102            value=settings.condensation_adaptive,
103            description="coalescence adaptive time-step",
104        )
105        self.ui_displacement_rtol = IntSlider(
106            value=np.log10(settings.displacement_rtol),
107            min=-3,
108            max=0,
109            description="log$_{10}$(rtol)",
110        )
111        self.ui_displacement_adaptive = Checkbox(
112            value=settings.displacement_adaptive,
113            description="displacement adaptive time-step",
114        )
115        self.ui_processes = [
116            Checkbox(value=settings.processes[key], description=key)
117            for key in settings.processes.keys()
118        ]
119        self.ui_sdpg = IntSlider(
120            value=settings.n_sd_per_gridbox, description="n_sd/gridbox", min=1, max=1000
121        )
122        self.fct_description = "MPDATA: flux-corrected transport option"
123        self.tot_description = "MPDATA: third-order terms option"
124        self.iga_description = "MPDATA: infinite gauge option"
125        self.nit_description = "MPDATA: number of iterations (1=UPWIND)"
126        self.ui_mpdata_options = [
127            Checkbox(value=settings.mpdata_fct, description=self.fct_description),
128            Checkbox(value=settings.mpdata_tot, description=self.tot_description),
129            Checkbox(value=settings.mpdata_iga, description=self.iga_description),
130            IntSlider(
131                value=settings.mpdata_iters,
132                description=self.nit_description,
133                min=1,
134                max=5,
135            ),
136        ]
137
138        formulae_init_params = inspect.signature(Formulae.__init__).parameters.items()
139        defaults = {k: v.default for k, v in formulae_init_params}
140        defaults["freezing_temperature_spectrum"] = "Niemand_et_al_2012"
141        defaults["heterogeneous_ice_nucleation_rate"] = "ABIFM"
142        self.ui_formulae_options = [
143            Dropdown(
144                description=k,
145                options=formulae._choices(getattr(physics, k)).keys(),
146                value=defaults[k],
147            )
148            for k, v in formulae_init_params
149            if k
150            not in (
151                "self",
152                "fastmath",
153                "seed",
154                "constants",
155                "handle_all_breakups",
156                "terminal_velocity",
157            )
158        ]
159
160        self.ui_formulae_options.append(
161            Checkbox(
162                value=inspect.signature(Formulae.__init__)
163                .parameters["fastmath"]
164                .default,
165                description="fastmath",
166            )
167        )
168        self.ui_output_options = {
169            "interval": IntSlider(
170                description="interval [s]",
171                value=settings.output_interval,
172                min=30,
173                max=60 * 15,
174            ),
175            "aerosol_radius_threshold": FloatSlider(
176                description="aerosol/cloud threshold [um]",
177                value=settings.aerosol_radius_threshold / physics.si.um,
178                min=0.1,
179                max=1,
180                step=0.1,
181            ),
182            "drizzle_radius_threshold": IntSlider(
183                description="cloud/drizzle threshold [um]",
184                value=settings.drizzle_radius_threshold / physics.si.um,
185                min=10,
186                max=100,
187                step=5,
188            ),
189        }
190
191        self.r_bins_edges = settings.r_bins_edges
192        self.T_bins_edges = settings.T_bins_edges
193        self.terminal_velocity_radius_bin_edges = (
194            settings.terminal_velocity_radius_bin_edges
195        )
196
197        self.size = settings.size
198        self.condensation_substeps = settings.condensation_substeps
199        self.condensation_dt_cond_range = settings.condensation_dt_cond_range
200        self.condensation_schedule = settings.condensation_schedule
201        self.kernel = settings.kernel
202        self.spectrum_per_mass_of_dry_air = settings.spectrum_per_mass_of_dry_air
203        self.coalescence_dt_coal_range = settings.coalescence_dt_coal_range
204        self.coalescence_optimized_random = settings.coalescence_optimized_random
205        self.coalescence_substeps = settings.coalescence_substeps
206        self.freezing_inp_frac = settings.freezing_inp_frac
207        self.coalescence_efficiency = settings.coalescence_efficiency
208        self.breakup_efficiency = settings.breakup_efficiency
209        self.breakup_fragmentation = settings.breakup_fragmentation
210
211        for attr in ("rhod_of_zZ", "versions", "n_spin_up"):
212            setattr(self, attr, getattr(settings, attr))
213
214    @property
215    def n_sd(self):
216        return self.grid[0] * self.grid[1] * self.n_sd_per_gridbox
217
218    @property
219    def initial_vapour_mixing_ratio_profile(self):
220        return np.full(
221            self.grid[-1],
222            self.__settings.initial_water_vapour_mixing_ratio
223            + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
224        )
225
226    @property
227    def initial_dry_potential_temperature_profile(self):
228        return np.full(
229            self.grid[-1],
230            self.formulae.state_variable_triplet.th_dry(
231                self.__settings.th_std0 + self.ui_dth0.value,
232                self.__settings.initial_water_vapour_mixing_ratio
233                + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
234            ),
235        )
236
237    @property
238    def aerosol_radius_threshold(self):
239        return self.ui_output_options["aerosol_radius_threshold"].value * physics.si.um
240
241    @property
242    def drizzle_radius_threshold(self):
243        return self.ui_output_options["drizzle_radius_threshold"].value * physics.si.um
244
245    @property
246    def output_interval(self):
247        return self.ui_output_options["interval"].value
248
249    @property
250    def formulae(self) -> Formulae:
251        return Formulae(
252            **{widget.description: widget.value for widget in self.ui_formulae_options},
253            constants={"NIEMAND_A": 0, "NIEMAND_B": 0, "ABIFM_M": 0, "ABIFM_C": 0}
254        )
255
256    @property
257    def steps_per_output_interval(self) -> int:
258        return int(self.output_interval / self.ui_dt.value)
259
260    @property
261    def output_steps(self) -> np.ndarray:
262        return np.arange(0, self.n_steps + 1, self.steps_per_output_interval)
263
264    @property
265    def rhod_w_max(self):
266        return self.ui_rhod_w_max.value
267
268    @property
269    def kappa(self):
270        return self.ui_kappa.value
271
272    @property
273    def freezing_singular(self):
274        return self.ui_freezing["model"].value == "singular"
275
276    @property
277    def grid(self):
278        return self.ui_nx.value, self.ui_nz.value
279
280    @property
281    def dt(self):
282        return self.ui_dt.value
283
284    @property
285    def n_steps(self):
286        return int(self.ui_simulation_time.value / self.ui_dt.value)  # TODO #413
287
288    @property
289    def condensation_rtol_x(self):
290        return 10**self.ui_condensation_rtol_x.value
291
292    @property
293    def condensation_rtol_thd(self):
294        return 10**self.ui_condensation_rtol_thd.value
295
296    @property
297    def condensation_adaptive(self):
298        return self.ui_condensation_adaptive.value
299
300    @property
301    def coalescence_adaptive(self):
302        return self.ui_coalescence_adaptive.value
303
304    @property
305    def displacement_rtol(self):
306        return 10**self.ui_displacement_rtol.value
307
308    @property
309    def displacement_adaptive(self):
310        return self.ui_displacement_adaptive.value
311
312    @property
313    def processes(self):
314        result = {}
315        for checkbox in self.ui_processes:
316            result[checkbox.description] = checkbox.value
317        return result
318
319    @property
320    def n_sd_per_gridbox(self):
321        return self.ui_sdpg.value
322
323    @property
324    def mpdata_tot(self):
325        for widget in self.ui_mpdata_options:
326            if widget.description == self.tot_description:
327                return widget.value
328        raise NotImplementedError()
329
330    @property
331    def mpdata_fct(self):
332        for widget in self.ui_mpdata_options:
333            if widget.description == self.fct_description:
334                return widget.value
335        raise NotImplementedError()
336
337    @property
338    def mpdata_iga(self):
339        for widget in self.ui_mpdata_options:
340            if widget.description == self.iga_description:
341                return widget.value
342        raise NotImplementedError()
343
344    @property
345    def mpdata_iters(self):
346        for widget in self.ui_mpdata_options:
347            if widget.description == self.nit_description:
348                return widget.value
349        raise NotImplementedError()
350
351    @property
352    def stream_function(self):
353        assert hasattr(self.__settings, "rhod_w_max")
354        self.__settings.rhod_w_max = self.ui_rhod_w_max.value
355
356        def fun(xX, zZ, _):
357            return self.__settings.stream_function(xX, zZ, _)
358
359        return fun
360
361    @property
362    def freezing_inp_spec(self):
363        if self.ui_freezing["INP surface"].value == "as dry surface":
364            return None
365        if self.ui_freezing["INP surface"].value == "lognormal(A, sgm_g)":
366            return Lognormal(
367                norm_factor=1,
368                m_mode=10 ** (self.ui_freezing["lognormal_log10_A_um2"].value)
369                * si.um**2,
370                s_geom=np.exp(self.ui_freezing["lognormal_ln_sgm_g"].value),
371            )
372        raise NotImplementedError()
373
374    def box(self):
375        layout = Accordion(
376            children=[
377                VBox(
378                    [
379                        self.ui_dth0,
380                        self.ui_delta_initial_water_vapour_mixing_ratio,
381                        self.ui_kappa,
382                        self.ui_rhod_w_max,
383                        *self.ui_freezing.values(),
384                    ]
385                ),
386                VBox([*self.ui_processes]),
387                VBox(
388                    [
389                        self.ui_nx,
390                        self.ui_nz,
391                        self.ui_sdpg,
392                        self.ui_dt,
393                        self.ui_simulation_time,
394                        self.ui_condensation_rtol_x,
395                        self.ui_condensation_rtol_thd,
396                        self.ui_condensation_adaptive,
397                        self.ui_coalescence_adaptive,
398                        self.ui_displacement_rtol,
399                        self.ui_displacement_adaptive,
400                        *self.ui_mpdata_options,
401                    ]
402                ),
403                VBox([*self.ui_formulae_options]),
404                VBox([*self.ui_output_options.values()]),
405            ]
406        )
407        layout.set_title(0, "parameters")
408        layout.set_title(1, "processes")
409        layout.set_title(2, "discretisation")
410        layout.set_title(3, "formulae")
411        layout.set_title(4, "output")
412
413        layout.observe(self.hide_and_show, names="selected_index")
414        self.hide_and_show()
415
416        return layout
417
418    def hide_and_show(self, _=None):
419        freezing_enabled = self.processes["freezing"]
420        for widget in self.ui_freezing.values():
421            set_visibility(widget, freezing_enabled)
422
423
424def set_visibility(widget, visible):
425    if visible:
426        widget.layout = Layout()
427    else:
428        widget.layout = Layout(visibility="hidden")
class GUISettings:
 21class GUISettings:
 22    def __dir__(self):
 23        return self.__settings.__dir__()
 24
 25    def __init__(self, settings):
 26        self.__settings = settings
 27
 28        self.ui_dth0 = FloatSlider(
 29            description="$\\Delta\\theta_0$ [K]", value=0, min=-15, max=15
 30        )
 31        self.ui_delta_initial_water_vapour_mixing_ratio = FloatSlider(
 32            description="$\\Delta initial_water_vapour_mixing_ratio$ [g/kg]",
 33            value=0,
 34            min=-6,
 35            max=6,
 36        )
 37        self.ui_rhod_w_max = FloatSlider(
 38            description="$\\rho_d w_{max}$",
 39            value=settings.rhod_w_max,
 40            min=0.1,
 41            max=4,
 42            step=0.1,
 43        )
 44        self.ui_kappa = FloatSlider(
 45            description="$\\kappa$ [1]", value=settings.kappa, min=0, max=1.5
 46        )
 47
 48        self.ui_freezing = {
 49            "model": RadioButtons(
 50                options=("singular", "time-dependent"),
 51                description="immersion frz",
 52                layout={"width": "max-content"},
 53            ),
 54            "INP surface": RadioButtons(
 55                options=("as dry surface", "lognormal(A, sgm_g)"),
 56                description="INP surface",
 57            ),
 58            "lognormal_log10_A_um2": FloatSlider(
 59                description="log10(A/$μm^2$)", min=-3, max=1, step=0.5
 60            ),
 61            "lognormal_ln_sgm_g": FloatSlider(
 62                description="ln(sgm_g)", min=0.5, max=3, step=0.5
 63            ),
 64            "ABIFM fit": Dropdown(description="ABIFM fit", options=("Nonadecanol",)),
 65            "INAS fit": Dropdown(
 66                description="INAS fit", options=("Niemand et al. 2012",)
 67            ),
 68        }
 69        # TODO #599 cool rate product + sing/tdep diff prod
 70
 71        self.ui_nx = IntSlider(
 72            value=settings.grid[0], min=10, max=100, description="nx"
 73        )
 74        self.ui_nz = IntSlider(
 75            value=settings.grid[1], min=10, max=100, description="nz"
 76        )
 77        self.ui_dt = FloatSlider(
 78            value=settings.dt, min=0.5, max=60, description="dt (Eulerian)"
 79        )
 80        self.ui_simulation_time = IntSlider(
 81            value=settings.simulation_time,
 82            min=1800,
 83            max=7200,
 84            description="simulation time $[s]$",
 85        )
 86        self.ui_condensation_rtol_x = IntSlider(
 87            value=np.log10(settings.condensation_rtol_thd),
 88            min=-9,
 89            max=-3,
 90            description="log$_{10}$(rtol$_x$)",
 91        )
 92        self.ui_condensation_rtol_thd = IntSlider(
 93            value=np.log10(settings.condensation_rtol_thd),
 94            min=-9,
 95            max=-3,
 96            description="log$_{10}$(rtol$_\\theta$)",
 97        )
 98        self.ui_condensation_adaptive = Checkbox(
 99            value=settings.condensation_adaptive,
100            description="condensation adaptive time-step",
101        )
102        self.ui_coalescence_adaptive = Checkbox(
103            value=settings.condensation_adaptive,
104            description="coalescence adaptive time-step",
105        )
106        self.ui_displacement_rtol = IntSlider(
107            value=np.log10(settings.displacement_rtol),
108            min=-3,
109            max=0,
110            description="log$_{10}$(rtol)",
111        )
112        self.ui_displacement_adaptive = Checkbox(
113            value=settings.displacement_adaptive,
114            description="displacement adaptive time-step",
115        )
116        self.ui_processes = [
117            Checkbox(value=settings.processes[key], description=key)
118            for key in settings.processes.keys()
119        ]
120        self.ui_sdpg = IntSlider(
121            value=settings.n_sd_per_gridbox, description="n_sd/gridbox", min=1, max=1000
122        )
123        self.fct_description = "MPDATA: flux-corrected transport option"
124        self.tot_description = "MPDATA: third-order terms option"
125        self.iga_description = "MPDATA: infinite gauge option"
126        self.nit_description = "MPDATA: number of iterations (1=UPWIND)"
127        self.ui_mpdata_options = [
128            Checkbox(value=settings.mpdata_fct, description=self.fct_description),
129            Checkbox(value=settings.mpdata_tot, description=self.tot_description),
130            Checkbox(value=settings.mpdata_iga, description=self.iga_description),
131            IntSlider(
132                value=settings.mpdata_iters,
133                description=self.nit_description,
134                min=1,
135                max=5,
136            ),
137        ]
138
139        formulae_init_params = inspect.signature(Formulae.__init__).parameters.items()
140        defaults = {k: v.default for k, v in formulae_init_params}
141        defaults["freezing_temperature_spectrum"] = "Niemand_et_al_2012"
142        defaults["heterogeneous_ice_nucleation_rate"] = "ABIFM"
143        self.ui_formulae_options = [
144            Dropdown(
145                description=k,
146                options=formulae._choices(getattr(physics, k)).keys(),
147                value=defaults[k],
148            )
149            for k, v in formulae_init_params
150            if k
151            not in (
152                "self",
153                "fastmath",
154                "seed",
155                "constants",
156                "handle_all_breakups",
157                "terminal_velocity",
158            )
159        ]
160
161        self.ui_formulae_options.append(
162            Checkbox(
163                value=inspect.signature(Formulae.__init__)
164                .parameters["fastmath"]
165                .default,
166                description="fastmath",
167            )
168        )
169        self.ui_output_options = {
170            "interval": IntSlider(
171                description="interval [s]",
172                value=settings.output_interval,
173                min=30,
174                max=60 * 15,
175            ),
176            "aerosol_radius_threshold": FloatSlider(
177                description="aerosol/cloud threshold [um]",
178                value=settings.aerosol_radius_threshold / physics.si.um,
179                min=0.1,
180                max=1,
181                step=0.1,
182            ),
183            "drizzle_radius_threshold": IntSlider(
184                description="cloud/drizzle threshold [um]",
185                value=settings.drizzle_radius_threshold / physics.si.um,
186                min=10,
187                max=100,
188                step=5,
189            ),
190        }
191
192        self.r_bins_edges = settings.r_bins_edges
193        self.T_bins_edges = settings.T_bins_edges
194        self.terminal_velocity_radius_bin_edges = (
195            settings.terminal_velocity_radius_bin_edges
196        )
197
198        self.size = settings.size
199        self.condensation_substeps = settings.condensation_substeps
200        self.condensation_dt_cond_range = settings.condensation_dt_cond_range
201        self.condensation_schedule = settings.condensation_schedule
202        self.kernel = settings.kernel
203        self.spectrum_per_mass_of_dry_air = settings.spectrum_per_mass_of_dry_air
204        self.coalescence_dt_coal_range = settings.coalescence_dt_coal_range
205        self.coalescence_optimized_random = settings.coalescence_optimized_random
206        self.coalescence_substeps = settings.coalescence_substeps
207        self.freezing_inp_frac = settings.freezing_inp_frac
208        self.coalescence_efficiency = settings.coalescence_efficiency
209        self.breakup_efficiency = settings.breakup_efficiency
210        self.breakup_fragmentation = settings.breakup_fragmentation
211
212        for attr in ("rhod_of_zZ", "versions", "n_spin_up"):
213            setattr(self, attr, getattr(settings, attr))
214
215    @property
216    def n_sd(self):
217        return self.grid[0] * self.grid[1] * self.n_sd_per_gridbox
218
219    @property
220    def initial_vapour_mixing_ratio_profile(self):
221        return np.full(
222            self.grid[-1],
223            self.__settings.initial_water_vapour_mixing_ratio
224            + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
225        )
226
227    @property
228    def initial_dry_potential_temperature_profile(self):
229        return np.full(
230            self.grid[-1],
231            self.formulae.state_variable_triplet.th_dry(
232                self.__settings.th_std0 + self.ui_dth0.value,
233                self.__settings.initial_water_vapour_mixing_ratio
234                + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
235            ),
236        )
237
238    @property
239    def aerosol_radius_threshold(self):
240        return self.ui_output_options["aerosol_radius_threshold"].value * physics.si.um
241
242    @property
243    def drizzle_radius_threshold(self):
244        return self.ui_output_options["drizzle_radius_threshold"].value * physics.si.um
245
246    @property
247    def output_interval(self):
248        return self.ui_output_options["interval"].value
249
250    @property
251    def formulae(self) -> Formulae:
252        return Formulae(
253            **{widget.description: widget.value for widget in self.ui_formulae_options},
254            constants={"NIEMAND_A": 0, "NIEMAND_B": 0, "ABIFM_M": 0, "ABIFM_C": 0}
255        )
256
257    @property
258    def steps_per_output_interval(self) -> int:
259        return int(self.output_interval / self.ui_dt.value)
260
261    @property
262    def output_steps(self) -> np.ndarray:
263        return np.arange(0, self.n_steps + 1, self.steps_per_output_interval)
264
265    @property
266    def rhod_w_max(self):
267        return self.ui_rhod_w_max.value
268
269    @property
270    def kappa(self):
271        return self.ui_kappa.value
272
273    @property
274    def freezing_singular(self):
275        return self.ui_freezing["model"].value == "singular"
276
277    @property
278    def grid(self):
279        return self.ui_nx.value, self.ui_nz.value
280
281    @property
282    def dt(self):
283        return self.ui_dt.value
284
285    @property
286    def n_steps(self):
287        return int(self.ui_simulation_time.value / self.ui_dt.value)  # TODO #413
288
289    @property
290    def condensation_rtol_x(self):
291        return 10**self.ui_condensation_rtol_x.value
292
293    @property
294    def condensation_rtol_thd(self):
295        return 10**self.ui_condensation_rtol_thd.value
296
297    @property
298    def condensation_adaptive(self):
299        return self.ui_condensation_adaptive.value
300
301    @property
302    def coalescence_adaptive(self):
303        return self.ui_coalescence_adaptive.value
304
305    @property
306    def displacement_rtol(self):
307        return 10**self.ui_displacement_rtol.value
308
309    @property
310    def displacement_adaptive(self):
311        return self.ui_displacement_adaptive.value
312
313    @property
314    def processes(self):
315        result = {}
316        for checkbox in self.ui_processes:
317            result[checkbox.description] = checkbox.value
318        return result
319
320    @property
321    def n_sd_per_gridbox(self):
322        return self.ui_sdpg.value
323
324    @property
325    def mpdata_tot(self):
326        for widget in self.ui_mpdata_options:
327            if widget.description == self.tot_description:
328                return widget.value
329        raise NotImplementedError()
330
331    @property
332    def mpdata_fct(self):
333        for widget in self.ui_mpdata_options:
334            if widget.description == self.fct_description:
335                return widget.value
336        raise NotImplementedError()
337
338    @property
339    def mpdata_iga(self):
340        for widget in self.ui_mpdata_options:
341            if widget.description == self.iga_description:
342                return widget.value
343        raise NotImplementedError()
344
345    @property
346    def mpdata_iters(self):
347        for widget in self.ui_mpdata_options:
348            if widget.description == self.nit_description:
349                return widget.value
350        raise NotImplementedError()
351
352    @property
353    def stream_function(self):
354        assert hasattr(self.__settings, "rhod_w_max")
355        self.__settings.rhod_w_max = self.ui_rhod_w_max.value
356
357        def fun(xX, zZ, _):
358            return self.__settings.stream_function(xX, zZ, _)
359
360        return fun
361
362    @property
363    def freezing_inp_spec(self):
364        if self.ui_freezing["INP surface"].value == "as dry surface":
365            return None
366        if self.ui_freezing["INP surface"].value == "lognormal(A, sgm_g)":
367            return Lognormal(
368                norm_factor=1,
369                m_mode=10 ** (self.ui_freezing["lognormal_log10_A_um2"].value)
370                * si.um**2,
371                s_geom=np.exp(self.ui_freezing["lognormal_ln_sgm_g"].value),
372            )
373        raise NotImplementedError()
374
375    def box(self):
376        layout = Accordion(
377            children=[
378                VBox(
379                    [
380                        self.ui_dth0,
381                        self.ui_delta_initial_water_vapour_mixing_ratio,
382                        self.ui_kappa,
383                        self.ui_rhod_w_max,
384                        *self.ui_freezing.values(),
385                    ]
386                ),
387                VBox([*self.ui_processes]),
388                VBox(
389                    [
390                        self.ui_nx,
391                        self.ui_nz,
392                        self.ui_sdpg,
393                        self.ui_dt,
394                        self.ui_simulation_time,
395                        self.ui_condensation_rtol_x,
396                        self.ui_condensation_rtol_thd,
397                        self.ui_condensation_adaptive,
398                        self.ui_coalescence_adaptive,
399                        self.ui_displacement_rtol,
400                        self.ui_displacement_adaptive,
401                        *self.ui_mpdata_options,
402                    ]
403                ),
404                VBox([*self.ui_formulae_options]),
405                VBox([*self.ui_output_options.values()]),
406            ]
407        )
408        layout.set_title(0, "parameters")
409        layout.set_title(1, "processes")
410        layout.set_title(2, "discretisation")
411        layout.set_title(3, "formulae")
412        layout.set_title(4, "output")
413
414        layout.observe(self.hide_and_show, names="selected_index")
415        self.hide_and_show()
416
417        return layout
418
419    def hide_and_show(self, _=None):
420        freezing_enabled = self.processes["freezing"]
421        for widget in self.ui_freezing.values():
422            set_visibility(widget, freezing_enabled)
GUISettings(settings)
 25    def __init__(self, settings):
 26        self.__settings = settings
 27
 28        self.ui_dth0 = FloatSlider(
 29            description="$\\Delta\\theta_0$ [K]", value=0, min=-15, max=15
 30        )
 31        self.ui_delta_initial_water_vapour_mixing_ratio = FloatSlider(
 32            description="$\\Delta initial_water_vapour_mixing_ratio$ [g/kg]",
 33            value=0,
 34            min=-6,
 35            max=6,
 36        )
 37        self.ui_rhod_w_max = FloatSlider(
 38            description="$\\rho_d w_{max}$",
 39            value=settings.rhod_w_max,
 40            min=0.1,
 41            max=4,
 42            step=0.1,
 43        )
 44        self.ui_kappa = FloatSlider(
 45            description="$\\kappa$ [1]", value=settings.kappa, min=0, max=1.5
 46        )
 47
 48        self.ui_freezing = {
 49            "model": RadioButtons(
 50                options=("singular", "time-dependent"),
 51                description="immersion frz",
 52                layout={"width": "max-content"},
 53            ),
 54            "INP surface": RadioButtons(
 55                options=("as dry surface", "lognormal(A, sgm_g)"),
 56                description="INP surface",
 57            ),
 58            "lognormal_log10_A_um2": FloatSlider(
 59                description="log10(A/$μm^2$)", min=-3, max=1, step=0.5
 60            ),
 61            "lognormal_ln_sgm_g": FloatSlider(
 62                description="ln(sgm_g)", min=0.5, max=3, step=0.5
 63            ),
 64            "ABIFM fit": Dropdown(description="ABIFM fit", options=("Nonadecanol",)),
 65            "INAS fit": Dropdown(
 66                description="INAS fit", options=("Niemand et al. 2012",)
 67            ),
 68        }
 69        # TODO #599 cool rate product + sing/tdep diff prod
 70
 71        self.ui_nx = IntSlider(
 72            value=settings.grid[0], min=10, max=100, description="nx"
 73        )
 74        self.ui_nz = IntSlider(
 75            value=settings.grid[1], min=10, max=100, description="nz"
 76        )
 77        self.ui_dt = FloatSlider(
 78            value=settings.dt, min=0.5, max=60, description="dt (Eulerian)"
 79        )
 80        self.ui_simulation_time = IntSlider(
 81            value=settings.simulation_time,
 82            min=1800,
 83            max=7200,
 84            description="simulation time $[s]$",
 85        )
 86        self.ui_condensation_rtol_x = IntSlider(
 87            value=np.log10(settings.condensation_rtol_thd),
 88            min=-9,
 89            max=-3,
 90            description="log$_{10}$(rtol$_x$)",
 91        )
 92        self.ui_condensation_rtol_thd = IntSlider(
 93            value=np.log10(settings.condensation_rtol_thd),
 94            min=-9,
 95            max=-3,
 96            description="log$_{10}$(rtol$_\\theta$)",
 97        )
 98        self.ui_condensation_adaptive = Checkbox(
 99            value=settings.condensation_adaptive,
100            description="condensation adaptive time-step",
101        )
102        self.ui_coalescence_adaptive = Checkbox(
103            value=settings.condensation_adaptive,
104            description="coalescence adaptive time-step",
105        )
106        self.ui_displacement_rtol = IntSlider(
107            value=np.log10(settings.displacement_rtol),
108            min=-3,
109            max=0,
110            description="log$_{10}$(rtol)",
111        )
112        self.ui_displacement_adaptive = Checkbox(
113            value=settings.displacement_adaptive,
114            description="displacement adaptive time-step",
115        )
116        self.ui_processes = [
117            Checkbox(value=settings.processes[key], description=key)
118            for key in settings.processes.keys()
119        ]
120        self.ui_sdpg = IntSlider(
121            value=settings.n_sd_per_gridbox, description="n_sd/gridbox", min=1, max=1000
122        )
123        self.fct_description = "MPDATA: flux-corrected transport option"
124        self.tot_description = "MPDATA: third-order terms option"
125        self.iga_description = "MPDATA: infinite gauge option"
126        self.nit_description = "MPDATA: number of iterations (1=UPWIND)"
127        self.ui_mpdata_options = [
128            Checkbox(value=settings.mpdata_fct, description=self.fct_description),
129            Checkbox(value=settings.mpdata_tot, description=self.tot_description),
130            Checkbox(value=settings.mpdata_iga, description=self.iga_description),
131            IntSlider(
132                value=settings.mpdata_iters,
133                description=self.nit_description,
134                min=1,
135                max=5,
136            ),
137        ]
138
139        formulae_init_params = inspect.signature(Formulae.__init__).parameters.items()
140        defaults = {k: v.default for k, v in formulae_init_params}
141        defaults["freezing_temperature_spectrum"] = "Niemand_et_al_2012"
142        defaults["heterogeneous_ice_nucleation_rate"] = "ABIFM"
143        self.ui_formulae_options = [
144            Dropdown(
145                description=k,
146                options=formulae._choices(getattr(physics, k)).keys(),
147                value=defaults[k],
148            )
149            for k, v in formulae_init_params
150            if k
151            not in (
152                "self",
153                "fastmath",
154                "seed",
155                "constants",
156                "handle_all_breakups",
157                "terminal_velocity",
158            )
159        ]
160
161        self.ui_formulae_options.append(
162            Checkbox(
163                value=inspect.signature(Formulae.__init__)
164                .parameters["fastmath"]
165                .default,
166                description="fastmath",
167            )
168        )
169        self.ui_output_options = {
170            "interval": IntSlider(
171                description="interval [s]",
172                value=settings.output_interval,
173                min=30,
174                max=60 * 15,
175            ),
176            "aerosol_radius_threshold": FloatSlider(
177                description="aerosol/cloud threshold [um]",
178                value=settings.aerosol_radius_threshold / physics.si.um,
179                min=0.1,
180                max=1,
181                step=0.1,
182            ),
183            "drizzle_radius_threshold": IntSlider(
184                description="cloud/drizzle threshold [um]",
185                value=settings.drizzle_radius_threshold / physics.si.um,
186                min=10,
187                max=100,
188                step=5,
189            ),
190        }
191
192        self.r_bins_edges = settings.r_bins_edges
193        self.T_bins_edges = settings.T_bins_edges
194        self.terminal_velocity_radius_bin_edges = (
195            settings.terminal_velocity_radius_bin_edges
196        )
197
198        self.size = settings.size
199        self.condensation_substeps = settings.condensation_substeps
200        self.condensation_dt_cond_range = settings.condensation_dt_cond_range
201        self.condensation_schedule = settings.condensation_schedule
202        self.kernel = settings.kernel
203        self.spectrum_per_mass_of_dry_air = settings.spectrum_per_mass_of_dry_air
204        self.coalescence_dt_coal_range = settings.coalescence_dt_coal_range
205        self.coalescence_optimized_random = settings.coalescence_optimized_random
206        self.coalescence_substeps = settings.coalescence_substeps
207        self.freezing_inp_frac = settings.freezing_inp_frac
208        self.coalescence_efficiency = settings.coalescence_efficiency
209        self.breakup_efficiency = settings.breakup_efficiency
210        self.breakup_fragmentation = settings.breakup_fragmentation
211
212        for attr in ("rhod_of_zZ", "versions", "n_spin_up"):
213            setattr(self, attr, getattr(settings, attr))
ui_dth0
ui_delta_initial_water_vapour_mixing_ratio
ui_rhod_w_max
ui_kappa
ui_freezing
ui_nx
ui_nz
ui_dt
ui_simulation_time
ui_condensation_rtol_x
ui_condensation_rtol_thd
ui_condensation_adaptive
ui_coalescence_adaptive
ui_displacement_rtol
ui_displacement_adaptive
ui_processes
ui_sdpg
fct_description
tot_description
iga_description
nit_description
ui_mpdata_options
ui_formulae_options
ui_output_options
r_bins_edges
T_bins_edges
terminal_velocity_radius_bin_edges
size
condensation_substeps
condensation_dt_cond_range
condensation_schedule
kernel
spectrum_per_mass_of_dry_air
coalescence_dt_coal_range
coalescence_optimized_random
coalescence_substeps
freezing_inp_frac
coalescence_efficiency
breakup_efficiency
breakup_fragmentation
n_sd
215    @property
216    def n_sd(self):
217        return self.grid[0] * self.grid[1] * self.n_sd_per_gridbox
initial_vapour_mixing_ratio_profile
219    @property
220    def initial_vapour_mixing_ratio_profile(self):
221        return np.full(
222            self.grid[-1],
223            self.__settings.initial_water_vapour_mixing_ratio
224            + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
225        )
initial_dry_potential_temperature_profile
227    @property
228    def initial_dry_potential_temperature_profile(self):
229        return np.full(
230            self.grid[-1],
231            self.formulae.state_variable_triplet.th_dry(
232                self.__settings.th_std0 + self.ui_dth0.value,
233                self.__settings.initial_water_vapour_mixing_ratio
234                + self.ui_delta_initial_water_vapour_mixing_ratio.value / 1000,
235            ),
236        )
aerosol_radius_threshold
238    @property
239    def aerosol_radius_threshold(self):
240        return self.ui_output_options["aerosol_radius_threshold"].value * physics.si.um
drizzle_radius_threshold
242    @property
243    def drizzle_radius_threshold(self):
244        return self.ui_output_options["drizzle_radius_threshold"].value * physics.si.um
output_interval
246    @property
247    def output_interval(self):
248        return self.ui_output_options["interval"].value
formulae: PySDM.formulae.Formulae
250    @property
251    def formulae(self) -> Formulae:
252        return Formulae(
253            **{widget.description: widget.value for widget in self.ui_formulae_options},
254            constants={"NIEMAND_A": 0, "NIEMAND_B": 0, "ABIFM_M": 0, "ABIFM_C": 0}
255        )
steps_per_output_interval: int
257    @property
258    def steps_per_output_interval(self) -> int:
259        return int(self.output_interval / self.ui_dt.value)
output_steps: numpy.ndarray
261    @property
262    def output_steps(self) -> np.ndarray:
263        return np.arange(0, self.n_steps + 1, self.steps_per_output_interval)
rhod_w_max
265    @property
266    def rhod_w_max(self):
267        return self.ui_rhod_w_max.value
kappa
269    @property
270    def kappa(self):
271        return self.ui_kappa.value
freezing_singular
273    @property
274    def freezing_singular(self):
275        return self.ui_freezing["model"].value == "singular"
grid
277    @property
278    def grid(self):
279        return self.ui_nx.value, self.ui_nz.value
dt
281    @property
282    def dt(self):
283        return self.ui_dt.value
n_steps
285    @property
286    def n_steps(self):
287        return int(self.ui_simulation_time.value / self.ui_dt.value)  # TODO #413
condensation_rtol_x
289    @property
290    def condensation_rtol_x(self):
291        return 10**self.ui_condensation_rtol_x.value
condensation_rtol_thd
293    @property
294    def condensation_rtol_thd(self):
295        return 10**self.ui_condensation_rtol_thd.value
condensation_adaptive
297    @property
298    def condensation_adaptive(self):
299        return self.ui_condensation_adaptive.value
coalescence_adaptive
301    @property
302    def coalescence_adaptive(self):
303        return self.ui_coalescence_adaptive.value
displacement_rtol
305    @property
306    def displacement_rtol(self):
307        return 10**self.ui_displacement_rtol.value
displacement_adaptive
309    @property
310    def displacement_adaptive(self):
311        return self.ui_displacement_adaptive.value
processes
313    @property
314    def processes(self):
315        result = {}
316        for checkbox in self.ui_processes:
317            result[checkbox.description] = checkbox.value
318        return result
n_sd_per_gridbox
320    @property
321    def n_sd_per_gridbox(self):
322        return self.ui_sdpg.value
mpdata_tot
324    @property
325    def mpdata_tot(self):
326        for widget in self.ui_mpdata_options:
327            if widget.description == self.tot_description:
328                return widget.value
329        raise NotImplementedError()
mpdata_fct
331    @property
332    def mpdata_fct(self):
333        for widget in self.ui_mpdata_options:
334            if widget.description == self.fct_description:
335                return widget.value
336        raise NotImplementedError()
mpdata_iga
338    @property
339    def mpdata_iga(self):
340        for widget in self.ui_mpdata_options:
341            if widget.description == self.iga_description:
342                return widget.value
343        raise NotImplementedError()
mpdata_iters
345    @property
346    def mpdata_iters(self):
347        for widget in self.ui_mpdata_options:
348            if widget.description == self.nit_description:
349                return widget.value
350        raise NotImplementedError()
stream_function
352    @property
353    def stream_function(self):
354        assert hasattr(self.__settings, "rhod_w_max")
355        self.__settings.rhod_w_max = self.ui_rhod_w_max.value
356
357        def fun(xX, zZ, _):
358            return self.__settings.stream_function(xX, zZ, _)
359
360        return fun
freezing_inp_spec
362    @property
363    def freezing_inp_spec(self):
364        if self.ui_freezing["INP surface"].value == "as dry surface":
365            return None
366        if self.ui_freezing["INP surface"].value == "lognormal(A, sgm_g)":
367            return Lognormal(
368                norm_factor=1,
369                m_mode=10 ** (self.ui_freezing["lognormal_log10_A_um2"].value)
370                * si.um**2,
371                s_geom=np.exp(self.ui_freezing["lognormal_ln_sgm_g"].value),
372            )
373        raise NotImplementedError()
def box(self):
375    def box(self):
376        layout = Accordion(
377            children=[
378                VBox(
379                    [
380                        self.ui_dth0,
381                        self.ui_delta_initial_water_vapour_mixing_ratio,
382                        self.ui_kappa,
383                        self.ui_rhod_w_max,
384                        *self.ui_freezing.values(),
385                    ]
386                ),
387                VBox([*self.ui_processes]),
388                VBox(
389                    [
390                        self.ui_nx,
391                        self.ui_nz,
392                        self.ui_sdpg,
393                        self.ui_dt,
394                        self.ui_simulation_time,
395                        self.ui_condensation_rtol_x,
396                        self.ui_condensation_rtol_thd,
397                        self.ui_condensation_adaptive,
398                        self.ui_coalescence_adaptive,
399                        self.ui_displacement_rtol,
400                        self.ui_displacement_adaptive,
401                        *self.ui_mpdata_options,
402                    ]
403                ),
404                VBox([*self.ui_formulae_options]),
405                VBox([*self.ui_output_options.values()]),
406            ]
407        )
408        layout.set_title(0, "parameters")
409        layout.set_title(1, "processes")
410        layout.set_title(2, "discretisation")
411        layout.set_title(3, "formulae")
412        layout.set_title(4, "output")
413
414        layout.observe(self.hide_and_show, names="selected_index")
415        self.hide_and_show()
416
417        return layout
def hide_and_show(self, _=None):
419    def hide_and_show(self, _=None):
420        freezing_enabled = self.processes["freezing"]
421        for widget in self.ui_freezing.values():
422            set_visibility(widget, freezing_enabled)
def set_visibility(widget, visible):
425def set_visibility(widget, visible):
426    if visible:
427        widget.layout = Layout()
428    else:
429        widget.layout = Layout(visibility="hidden")