PySDM_examples.Srivastava_1982.example

  1from collections import namedtuple
  2
  3import numpy as np
  4from matplotlib import pyplot
  5from PySDM_examples.Srivastava_1982.equations import Equations, EquationsHelpers
  6from PySDM_examples.Srivastava_1982.settings import SimProducts
  7from PySDM_examples.Srivastava_1982.simulation import Simulation
  8
  9from PySDM.dynamics import Collision
 10from PySDM.dynamics.collisions.breakup_efficiencies import ConstEb
 11from PySDM.dynamics.collisions.breakup_fragmentations import ConstantMass
 12from PySDM.dynamics.collisions.coalescence_efficiencies import ConstEc
 13from PySDM.dynamics.collisions.collision_kernels import ConstantK
 14
 15NO_BOUNCE = ConstEb(1)
 16
 17
 18def coalescence_and_breakup_eq13(
 19    settings=None, n_steps=256, n_realisations=2, title=None, warn_overflows=True
 20):
 21    # arrange
 22    seeds = list(range(n_realisations))
 23
 24    collision_rate = settings.srivastava_c + settings.srivastava_beta
 25    simulation = Simulation(
 26        n_steps=n_steps,
 27        settings=settings,
 28        collision_dynamic=Collision(
 29            collision_kernel=ConstantK(a=collision_rate),
 30            coalescence_efficiency=ConstEc(settings.srivastava_c / collision_rate),
 31            breakup_efficiency=NO_BOUNCE,
 32            fragmentation_function=ConstantMass(c=settings.frag_mass),
 33            warn_overflows=warn_overflows,
 34        ),
 35    )
 36
 37    x = np.arange(n_steps + 1, dtype=float)
 38    sim_products = simulation.run_convergence_analysis(x, seeds=seeds)
 39
 40    secondary_products = get_pysdm_secondary_products(
 41        products=sim_products, total_volume=settings.total_volume
 42    )
 43
 44    pysdm_results = get_processed_results(secondary_products)
 45
 46    equations = Equations(
 47        M=settings.total_volume * settings.rho / settings.frag_mass,
 48        c=settings.srivastava_c,
 49        beta=settings.srivastava_beta,
 50    )
 51    equation_helper = EquationsHelpers(
 52        settings.total_volume,
 53        settings.total_number_0,
 54        settings.rho,
 55        frag_mass=settings.frag_mass,
 56    )
 57    m0 = equation_helper.m0()
 58
 59    x_log = compute_log_space(x)
 60    analytic_results = get_breakup_coalescence_analytic_results(
 61        equations, settings, m0, x, x_log
 62    )
 63
 64    prods = [
 65        k
 66        for k in list(pysdm_results.values())[0].keys()
 67        if k != SimProducts.PySDM.total_volume.name
 68    ]
 69
 70    add_to_plot_simulation_results(
 71        prods,
 72        settings.n_sds,
 73        x,
 74        pysdm_results=pysdm_results,
 75        analytic_results=analytic_results,
 76        title=title,
 77    )
 78
 79    results = namedtuple("_", "pysdm, analytic")(
 80        pysdm=pysdm_results, analytic=analytic_results
 81    )
 82    return results
 83
 84
 85def get_processed_results(res):
 86    processed = {}
 87    for n_sd in res.keys():
 88        processed[n_sd] = {}
 89        for prod in res[n_sd].keys():
 90            processed[n_sd][prod] = {}
 91            all_runs = np.asarray(list(res[n_sd][prod].values()))
 92
 93            processed[n_sd][prod]["avg"] = np.mean(all_runs, axis=0)
 94            processed[n_sd][prod]["max"] = np.max(all_runs, axis=0)
 95            processed[n_sd][prod]["min"] = np.min(all_runs, axis=0)
 96            processed[n_sd][prod]["std"] = np.std(all_runs, axis=0)
 97
 98    return processed
 99
100
101# TODO #1045 (not needed)
102def get_pysdm_secondary_products(products, total_volume):
103    pysdm_results = products
104    for n_sd in products.keys():
105        pysdm_results[n_sd][
106            SimProducts.Computed.mean_drop_volume_total_volume_ratio.name
107        ] = {}
108
109        for k in pysdm_results[n_sd][SimProducts.PySDM.total_numer.name].keys():
110            pysdm_results[n_sd][
111                SimProducts.Computed.mean_drop_volume_total_volume_ratio.name
112            ][k] = compute_drop_volume_total_volume_ratio(
113                mean_volume=total_volume
114                / products[n_sd][SimProducts.PySDM.total_numer.name][k],
115                total_volume=total_volume,
116            )
117    return pysdm_results
118
119
120def get_coalescence_analytic_results(equations, settings, m0, x, x_log):
121    mean_mass10 = (
122        equations.eq10(m0, equations.tau(x * settings.dt)) * settings.frag_mass
123    )
124    mean_mass_ratio_log = equations.eq10(m0, equations.tau(x_log * settings.dt))
125
126    return get_analytic_results(equations, settings, mean_mass10, mean_mass_ratio_log)
127
128
129def get_breakup_coalescence_analytic_results(equations, settings, m0, x, x_log):
130    mean_mass13 = (
131        equations.eq13(m0, equations.tau(x * settings.dt)) * settings.frag_mass
132    )
133    mean_mass_ratio_log = equations.eq13(m0, equations.tau(x_log * settings.dt))
134
135    return get_analytic_results(equations, settings, mean_mass13, mean_mass_ratio_log)
136
137
138def get_analytic_results(equations, settings, mean_mass, mean_mass_ratio):
139    res = {}
140    res[SimProducts.Computed.mean_drop_volume_total_volume_ratio.name] = (
141        compute_drop_volume_total_volume_ratio(
142            mean_volume=mean_mass / settings.rho, total_volume=settings.total_volume
143        )
144    )
145    res[SimProducts.PySDM.total_numer.name] = equations.M / mean_mass_ratio
146    return res
147
148
149def compute_log_space(x, shift=0, num_points=1000, eps=1e-1):
150    assert eps < x[1]
151    return (
152        np.logspace(np.log10(x[0] if x[0] != 0 else eps), np.log10(x[-1]), num_points)
153        + shift
154    )
155
156
157# TODO #1045 (not needed)
158def compute_drop_volume_total_volume_ratio(mean_volume, total_volume):
159    return mean_volume / total_volume * 100
160
161
162def add_to_plot_simulation_results(
163    prods,
164    n_sds,
165    x,
166    pysdm_results=None,
167    analytic_results=None,
168    title=None,
169):
170    fig = pyplot.figure(layout="constrained", figsize=(10, 4))
171    _wide = 14
172    _shrt = 8
173    _mrgn = 1
174    gs = fig.add_gridspec(nrows=20, ncols=2 * _wide + _shrt + 2 * _mrgn)
175    axs = (
176        fig.add_subplot(gs[2:, _shrt + _mrgn : _shrt + _mrgn + _wide]),
177        fig.add_subplot(gs[2:, 0:_shrt]),
178        fig.add_subplot(gs[2:, _shrt + 2 * _mrgn + _wide :]),
179    )
180    axs[1].set_ylim([6, 3500])
181
182    expons = [3, 5, 7, 9, 11]
183
184    axs[1].set_yscale(SimProducts.PySDM.super_particle_count.plot_yscale)
185    axs[1].set_yticks([2**e for e in expons], [f"$2^{{{e}}}$" for e in expons])
186
187    if title:
188        fig.suptitle(title)
189
190    ylims = {}
191    for prod in prods:
192        ylims[prod] = (np.inf, -np.inf)
193        for n_sd in n_sds:
194            ylims[prod] = (
195                min(ylims[prod][0], 0.75 * np.amin(pysdm_results[n_sd][prod]["avg"])),
196                max(ylims[prod][1], 1.25 * np.amax(pysdm_results[n_sd][prod]["avg"])),
197            )
198
199    for i, prod in enumerate(prods):
200        # plot numeric
201        if pysdm_results:
202            for n_sd in n_sds:
203                y_model = pysdm_results[n_sd][prod]
204
205                axs[i].step(
206                    x,
207                    y_model["avg"],
208                    where="mid",
209                    label=f"initial #SD: {n_sd}",
210                    linewidth=1 + np.log(n_sd) / 3,
211                )
212                axs[i].fill_between(
213                    x,
214                    y_model["avg"] - y_model["std"],
215                    y_model["avg"] + y_model["std"],
216                    alpha=0.2,
217                )
218
219        # plot analytic
220        if analytic_results:
221            add_analytic_result_to_axs(axs[i], prod, x, analytic_results)
222
223        # cosmetics
224        axs[i].set_ylabel(SimProducts.get_prod_by_name(prod).plot_title)
225
226        axs[i].grid()
227        axs[i].set_xlabel("step: t / dt")
228
229        if prod != SimProducts.PySDM.super_particle_count.name:
230            bottom = ylims[prod][0]
231            top = ylims[prod][1]
232
233            slope = (
234                pysdm_results[n_sds[-1]][prod]["avg"][-1]
235                - pysdm_results[n_sds[-1]][prod]["avg"][0]
236            )
237            if prod == SimProducts.PySDM.total_numer.name and slope < 0:
238                axs[i].set_ylim(0.2 * bottom, top)
239            else:
240                axs[i].set_ylim(bottom, top)
241
242    axs[0].legend()
243    return fig, axs
244
245
246def add_analytic_result_to_axs(axs_i, prod, x, res, key=""):
247    if prod != SimProducts.PySDM.super_particle_count.name:
248        x_theory = x
249        y_theory = res[prod]
250
251        if prod == SimProducts.PySDM.total_numer.name:
252            if y_theory.shape != x_theory.shape:
253                x_theory = compute_log_space(x)
254
255                axs_i.set_yscale(SimProducts.PySDM.total_numer.plot_yscale)
256                axs_i.set_xscale(SimProducts.PySDM.total_numer.plot_xscale)
257                axs_i.set_xlim(x_theory[0], None)
258
259        axs_i.plot(
260            x_theory,
261            y_theory,
262            label=f"analytic {key}",
263            linestyle=":",
264            linewidth=2,
265            color="black",
266        )
def coalescence_and_breakup_eq13( settings=None, n_steps=256, n_realisations=2, title=None, warn_overflows=True):
19def coalescence_and_breakup_eq13(
20    settings=None, n_steps=256, n_realisations=2, title=None, warn_overflows=True
21):
22    # arrange
23    seeds = list(range(n_realisations))
24
25    collision_rate = settings.srivastava_c + settings.srivastava_beta
26    simulation = Simulation(
27        n_steps=n_steps,
28        settings=settings,
29        collision_dynamic=Collision(
30            collision_kernel=ConstantK(a=collision_rate),
31            coalescence_efficiency=ConstEc(settings.srivastava_c / collision_rate),
32            breakup_efficiency=NO_BOUNCE,
33            fragmentation_function=ConstantMass(c=settings.frag_mass),
34            warn_overflows=warn_overflows,
35        ),
36    )
37
38    x = np.arange(n_steps + 1, dtype=float)
39    sim_products = simulation.run_convergence_analysis(x, seeds=seeds)
40
41    secondary_products = get_pysdm_secondary_products(
42        products=sim_products, total_volume=settings.total_volume
43    )
44
45    pysdm_results = get_processed_results(secondary_products)
46
47    equations = Equations(
48        M=settings.total_volume * settings.rho / settings.frag_mass,
49        c=settings.srivastava_c,
50        beta=settings.srivastava_beta,
51    )
52    equation_helper = EquationsHelpers(
53        settings.total_volume,
54        settings.total_number_0,
55        settings.rho,
56        frag_mass=settings.frag_mass,
57    )
58    m0 = equation_helper.m0()
59
60    x_log = compute_log_space(x)
61    analytic_results = get_breakup_coalescence_analytic_results(
62        equations, settings, m0, x, x_log
63    )
64
65    prods = [
66        k
67        for k in list(pysdm_results.values())[0].keys()
68        if k != SimProducts.PySDM.total_volume.name
69    ]
70
71    add_to_plot_simulation_results(
72        prods,
73        settings.n_sds,
74        x,
75        pysdm_results=pysdm_results,
76        analytic_results=analytic_results,
77        title=title,
78    )
79
80    results = namedtuple("_", "pysdm, analytic")(
81        pysdm=pysdm_results, analytic=analytic_results
82    )
83    return results
def get_processed_results(res):
86def get_processed_results(res):
87    processed = {}
88    for n_sd in res.keys():
89        processed[n_sd] = {}
90        for prod in res[n_sd].keys():
91            processed[n_sd][prod] = {}
92            all_runs = np.asarray(list(res[n_sd][prod].values()))
93
94            processed[n_sd][prod]["avg"] = np.mean(all_runs, axis=0)
95            processed[n_sd][prod]["max"] = np.max(all_runs, axis=0)
96            processed[n_sd][prod]["min"] = np.min(all_runs, axis=0)
97            processed[n_sd][prod]["std"] = np.std(all_runs, axis=0)
98
99    return processed
def get_pysdm_secondary_products(products, total_volume):
103def get_pysdm_secondary_products(products, total_volume):
104    pysdm_results = products
105    for n_sd in products.keys():
106        pysdm_results[n_sd][
107            SimProducts.Computed.mean_drop_volume_total_volume_ratio.name
108        ] = {}
109
110        for k in pysdm_results[n_sd][SimProducts.PySDM.total_numer.name].keys():
111            pysdm_results[n_sd][
112                SimProducts.Computed.mean_drop_volume_total_volume_ratio.name
113            ][k] = compute_drop_volume_total_volume_ratio(
114                mean_volume=total_volume
115                / products[n_sd][SimProducts.PySDM.total_numer.name][k],
116                total_volume=total_volume,
117            )
118    return pysdm_results
def get_coalescence_analytic_results(equations, settings, m0, x, x_log):
121def get_coalescence_analytic_results(equations, settings, m0, x, x_log):
122    mean_mass10 = (
123        equations.eq10(m0, equations.tau(x * settings.dt)) * settings.frag_mass
124    )
125    mean_mass_ratio_log = equations.eq10(m0, equations.tau(x_log * settings.dt))
126
127    return get_analytic_results(equations, settings, mean_mass10, mean_mass_ratio_log)
def get_breakup_coalescence_analytic_results(equations, settings, m0, x, x_log):
130def get_breakup_coalescence_analytic_results(equations, settings, m0, x, x_log):
131    mean_mass13 = (
132        equations.eq13(m0, equations.tau(x * settings.dt)) * settings.frag_mass
133    )
134    mean_mass_ratio_log = equations.eq13(m0, equations.tau(x_log * settings.dt))
135
136    return get_analytic_results(equations, settings, mean_mass13, mean_mass_ratio_log)
def get_analytic_results(equations, settings, mean_mass, mean_mass_ratio):
139def get_analytic_results(equations, settings, mean_mass, mean_mass_ratio):
140    res = {}
141    res[SimProducts.Computed.mean_drop_volume_total_volume_ratio.name] = (
142        compute_drop_volume_total_volume_ratio(
143            mean_volume=mean_mass / settings.rho, total_volume=settings.total_volume
144        )
145    )
146    res[SimProducts.PySDM.total_numer.name] = equations.M / mean_mass_ratio
147    return res
def compute_log_space(x, shift=0, num_points=1000, eps=0.1):
150def compute_log_space(x, shift=0, num_points=1000, eps=1e-1):
151    assert eps < x[1]
152    return (
153        np.logspace(np.log10(x[0] if x[0] != 0 else eps), np.log10(x[-1]), num_points)
154        + shift
155    )
def compute_drop_volume_total_volume_ratio(mean_volume, total_volume):
159def compute_drop_volume_total_volume_ratio(mean_volume, total_volume):
160    return mean_volume / total_volume * 100
def add_to_plot_simulation_results( prods, n_sds, x, pysdm_results=None, analytic_results=None, title=None):
163def add_to_plot_simulation_results(
164    prods,
165    n_sds,
166    x,
167    pysdm_results=None,
168    analytic_results=None,
169    title=None,
170):
171    fig = pyplot.figure(layout="constrained", figsize=(10, 4))
172    _wide = 14
173    _shrt = 8
174    _mrgn = 1
175    gs = fig.add_gridspec(nrows=20, ncols=2 * _wide + _shrt + 2 * _mrgn)
176    axs = (
177        fig.add_subplot(gs[2:, _shrt + _mrgn : _shrt + _mrgn + _wide]),
178        fig.add_subplot(gs[2:, 0:_shrt]),
179        fig.add_subplot(gs[2:, _shrt + 2 * _mrgn + _wide :]),
180    )
181    axs[1].set_ylim([6, 3500])
182
183    expons = [3, 5, 7, 9, 11]
184
185    axs[1].set_yscale(SimProducts.PySDM.super_particle_count.plot_yscale)
186    axs[1].set_yticks([2**e for e in expons], [f"$2^{{{e}}}$" for e in expons])
187
188    if title:
189        fig.suptitle(title)
190
191    ylims = {}
192    for prod in prods:
193        ylims[prod] = (np.inf, -np.inf)
194        for n_sd in n_sds:
195            ylims[prod] = (
196                min(ylims[prod][0], 0.75 * np.amin(pysdm_results[n_sd][prod]["avg"])),
197                max(ylims[prod][1], 1.25 * np.amax(pysdm_results[n_sd][prod]["avg"])),
198            )
199
200    for i, prod in enumerate(prods):
201        # plot numeric
202        if pysdm_results:
203            for n_sd in n_sds:
204                y_model = pysdm_results[n_sd][prod]
205
206                axs[i].step(
207                    x,
208                    y_model["avg"],
209                    where="mid",
210                    label=f"initial #SD: {n_sd}",
211                    linewidth=1 + np.log(n_sd) / 3,
212                )
213                axs[i].fill_between(
214                    x,
215                    y_model["avg"] - y_model["std"],
216                    y_model["avg"] + y_model["std"],
217                    alpha=0.2,
218                )
219
220        # plot analytic
221        if analytic_results:
222            add_analytic_result_to_axs(axs[i], prod, x, analytic_results)
223
224        # cosmetics
225        axs[i].set_ylabel(SimProducts.get_prod_by_name(prod).plot_title)
226
227        axs[i].grid()
228        axs[i].set_xlabel("step: t / dt")
229
230        if prod != SimProducts.PySDM.super_particle_count.name:
231            bottom = ylims[prod][0]
232            top = ylims[prod][1]
233
234            slope = (
235                pysdm_results[n_sds[-1]][prod]["avg"][-1]
236                - pysdm_results[n_sds[-1]][prod]["avg"][0]
237            )
238            if prod == SimProducts.PySDM.total_numer.name and slope < 0:
239                axs[i].set_ylim(0.2 * bottom, top)
240            else:
241                axs[i].set_ylim(bottom, top)
242
243    axs[0].legend()
244    return fig, axs
def add_analytic_result_to_axs(axs_i, prod, x, res, key=''):
247def add_analytic_result_to_axs(axs_i, prod, x, res, key=""):
248    if prod != SimProducts.PySDM.super_particle_count.name:
249        x_theory = x
250        y_theory = res[prod]
251
252        if prod == SimProducts.PySDM.total_numer.name:
253            if y_theory.shape != x_theory.shape:
254                x_theory = compute_log_space(x)
255
256                axs_i.set_yscale(SimProducts.PySDM.total_numer.plot_yscale)
257                axs_i.set_xscale(SimProducts.PySDM.total_numer.plot_xscale)
258                axs_i.set_xlim(x_theory[0], None)
259
260        axs_i.plot(
261            x_theory,
262            y_theory,
263            label=f"analytic {key}",
264            linestyle=":",
265            linewidth=2,
266            color="black",
267        )