PySDM_examples.Shima_et_al_2009.spectrum_plotter

  1import matplotlib
  2import numpy as np
  3from matplotlib import pyplot
  4from open_atmos_jupyter_utils import show_plot
  5from packaging import version
  6from PySDM_examples.Shima_et_al_2009.error_measure import error_measure
  7
  8from PySDM.physics.constants import si
  9
 10_matplotlib_version_3_3_3 = version.parse("3.3.0")
 11_matplotlib_version_actual = version.parse(matplotlib.__version__)
 12
 13
 14class SpectrumColors:
 15    def __init__(self, begining="#2cbdfe", end="#b317b1"):
 16        self.b = begining
 17        self.e = end
 18
 19    def __call__(self, value: float):
 20        bR, bG, bB = int(self.b[1:3], 16), int(self.b[3:5], 16), int(self.b[5:7], 16)
 21        eR, eG, eB = int(self.e[1:3], 16), int(self.e[3:5], 16), int(self.e[5:7], 16)
 22        R = bR + int((eR - bR) * value)
 23        G = bG + int((eG - bG) * value)
 24        B = bB + int((eB - bB) * value)
 25        result = f"#{hex(R)[2:4]}{hex(G)[2:4]}{hex(B)[2:4]}"
 26        return result
 27
 28
 29class SpectrumPlotter:
 30    def __init__(self, settings, title=None, grid=True, legend=True, log_base=10):
 31        self.settings = settings
 32        self.format = "pdf"
 33        self.colors = SpectrumColors()
 34        self.smooth = False
 35        self.smooth_scope = 2
 36        self.legend = legend
 37        self.grid = grid
 38        self.title = title
 39        self.xlabel = "particle radius [µm]"
 40        self.ylabel = "dm/dlnr [g/m^3/(unit dr/r)]"
 41        self.log_base = log_base
 42        self.ax = pyplot
 43        self.fig = pyplot
 44        self.finished = False
 45
 46    def finish(self):
 47        if self.finished:
 48            return
 49        self.finished = True
 50        if self.grid:
 51            self.ax.grid()
 52
 53        base_arg = {
 54            "base"
 55            + (
 56                "x" if _matplotlib_version_actual < _matplotlib_version_3_3_3 else ""
 57            ): self.log_base
 58        }
 59        if self.title is not None:
 60            try:
 61                self.ax.title(self.title)
 62            except TypeError:
 63                self.ax.set_title(self.title)
 64        try:
 65            self.ax.xscale("log", **base_arg)
 66            self.ax.xlabel(self.xlabel)
 67            self.ax.ylabel(self.ylabel)
 68        except AttributeError:
 69            self.ax.set_xscale("log", **base_arg)
 70            self.ax.set_xlabel(self.xlabel)
 71            self.ax.set_ylabel(self.ylabel)
 72        if self.legend:
 73            self.ax.legend()
 74
 75    def show(self):
 76        self.finish()
 77        pyplot.tight_layout()
 78        show_plot()
 79
 80    def save(self, file):
 81        self.finish()
 82        pyplot.savefig(file, format=self.format)
 83
 84    def plot(self, spectrum, t):
 85        error = self.plot_analytic_solution(self.settings, t, spectrum)
 86        self.plot_data(self.settings, t, spectrum)
 87        return error
 88
 89    def plot_analytic_solution(self, settings, t, spectrum=None):
 90        if t == 0:
 91            analytic_solution = settings.spectrum.size_distribution
 92        else:
 93
 94            def analytic_solution(x):
 95                return settings.norm_factor * settings.kernel.analytic_solution(
 96                    x=x, t=t, x_0=settings.X0, N_0=settings.n_part
 97                )
 98
 99        volume_bins_edges = self.settings.formulae.trivia.volume(
100            settings.radius_bins_edges
101        )
102        dm = np.diff(volume_bins_edges)
103        dr = np.diff(settings.radius_bins_edges)
104
105        pdf_m_x = volume_bins_edges[:-1] + dm / 2
106        pdf_m_y = analytic_solution(pdf_m_x)
107
108        pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2
109        pdf_r_y = pdf_m_y * dm / dr * pdf_r_x
110
111        x = pdf_r_x * si.metres / si.micrometres
112        y_true = (
113            pdf_r_y
114            * self.settings.formulae.trivia.volume(radius=pdf_r_x)
115            * settings.rho
116            / settings.dv
117            * si.kilograms
118            / si.grams
119        )
120
121        self.ax.plot(x, y_true, color="black")
122
123        if spectrum is not None:
124            y = spectrum * si.kilograms / si.grams
125            error = error_measure(y, y_true, x)
126            self.title = f"error measure: {error:.2f}"  # TODO #327 relative error
127            return error
128        return None
129
130    def plot_data(self, settings, t, spectrum):
131        if self.smooth:
132            scope = self.smooth_scope
133            if t != 0:
134                new = np.copy(spectrum)
135                for _ in range(2):
136                    for i in range(scope, len(spectrum) - scope):
137                        new[i] = np.mean(spectrum[i - scope : i + scope + 1])
138                    scope = 1
139                    for i in range(scope, len(spectrum) - scope):
140                        spectrum[i] = np.mean(new[i - scope : i + scope + 1])
141
142            x = settings.radius_bins_edges[:-scope]
143            dx = np.diff(x)
144            self.ax.plot(
145                (x[:-1] + dx / 2) * si.metres / si.micrometres,
146                spectrum[:-scope] * si.kilograms / si.grams,
147                label=f"t = {t}s",
148                color=self.colors(
149                    t / (self.settings.output_steps[-1] * self.settings.dt)
150                ),
151            )
152        else:
153            self.ax.step(
154                settings.radius_bins_edges[:-1] * si.metres / si.micrometres,
155                spectrum * si.kilograms / si.grams,
156                where="post",
157                label=f"t = {t}s",
158                color=self.colors(
159                    t / (self.settings.output_steps[-1] * self.settings.dt)
160                ),
161            )
class SpectrumColors:
15class SpectrumColors:
16    def __init__(self, begining="#2cbdfe", end="#b317b1"):
17        self.b = begining
18        self.e = end
19
20    def __call__(self, value: float):
21        bR, bG, bB = int(self.b[1:3], 16), int(self.b[3:5], 16), int(self.b[5:7], 16)
22        eR, eG, eB = int(self.e[1:3], 16), int(self.e[3:5], 16), int(self.e[5:7], 16)
23        R = bR + int((eR - bR) * value)
24        G = bG + int((eG - bG) * value)
25        B = bB + int((eB - bB) * value)
26        result = f"#{hex(R)[2:4]}{hex(G)[2:4]}{hex(B)[2:4]}"
27        return result
SpectrumColors(begining='#2cbdfe', end='#b317b1')
16    def __init__(self, begining="#2cbdfe", end="#b317b1"):
17        self.b = begining
18        self.e = end
b
e
class SpectrumPlotter:
 30class SpectrumPlotter:
 31    def __init__(self, settings, title=None, grid=True, legend=True, log_base=10):
 32        self.settings = settings
 33        self.format = "pdf"
 34        self.colors = SpectrumColors()
 35        self.smooth = False
 36        self.smooth_scope = 2
 37        self.legend = legend
 38        self.grid = grid
 39        self.title = title
 40        self.xlabel = "particle radius [µm]"
 41        self.ylabel = "dm/dlnr [g/m^3/(unit dr/r)]"
 42        self.log_base = log_base
 43        self.ax = pyplot
 44        self.fig = pyplot
 45        self.finished = False
 46
 47    def finish(self):
 48        if self.finished:
 49            return
 50        self.finished = True
 51        if self.grid:
 52            self.ax.grid()
 53
 54        base_arg = {
 55            "base"
 56            + (
 57                "x" if _matplotlib_version_actual < _matplotlib_version_3_3_3 else ""
 58            ): self.log_base
 59        }
 60        if self.title is not None:
 61            try:
 62                self.ax.title(self.title)
 63            except TypeError:
 64                self.ax.set_title(self.title)
 65        try:
 66            self.ax.xscale("log", **base_arg)
 67            self.ax.xlabel(self.xlabel)
 68            self.ax.ylabel(self.ylabel)
 69        except AttributeError:
 70            self.ax.set_xscale("log", **base_arg)
 71            self.ax.set_xlabel(self.xlabel)
 72            self.ax.set_ylabel(self.ylabel)
 73        if self.legend:
 74            self.ax.legend()
 75
 76    def show(self):
 77        self.finish()
 78        pyplot.tight_layout()
 79        show_plot()
 80
 81    def save(self, file):
 82        self.finish()
 83        pyplot.savefig(file, format=self.format)
 84
 85    def plot(self, spectrum, t):
 86        error = self.plot_analytic_solution(self.settings, t, spectrum)
 87        self.plot_data(self.settings, t, spectrum)
 88        return error
 89
 90    def plot_analytic_solution(self, settings, t, spectrum=None):
 91        if t == 0:
 92            analytic_solution = settings.spectrum.size_distribution
 93        else:
 94
 95            def analytic_solution(x):
 96                return settings.norm_factor * settings.kernel.analytic_solution(
 97                    x=x, t=t, x_0=settings.X0, N_0=settings.n_part
 98                )
 99
100        volume_bins_edges = self.settings.formulae.trivia.volume(
101            settings.radius_bins_edges
102        )
103        dm = np.diff(volume_bins_edges)
104        dr = np.diff(settings.radius_bins_edges)
105
106        pdf_m_x = volume_bins_edges[:-1] + dm / 2
107        pdf_m_y = analytic_solution(pdf_m_x)
108
109        pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2
110        pdf_r_y = pdf_m_y * dm / dr * pdf_r_x
111
112        x = pdf_r_x * si.metres / si.micrometres
113        y_true = (
114            pdf_r_y
115            * self.settings.formulae.trivia.volume(radius=pdf_r_x)
116            * settings.rho
117            / settings.dv
118            * si.kilograms
119            / si.grams
120        )
121
122        self.ax.plot(x, y_true, color="black")
123
124        if spectrum is not None:
125            y = spectrum * si.kilograms / si.grams
126            error = error_measure(y, y_true, x)
127            self.title = f"error measure: {error:.2f}"  # TODO #327 relative error
128            return error
129        return None
130
131    def plot_data(self, settings, t, spectrum):
132        if self.smooth:
133            scope = self.smooth_scope
134            if t != 0:
135                new = np.copy(spectrum)
136                for _ in range(2):
137                    for i in range(scope, len(spectrum) - scope):
138                        new[i] = np.mean(spectrum[i - scope : i + scope + 1])
139                    scope = 1
140                    for i in range(scope, len(spectrum) - scope):
141                        spectrum[i] = np.mean(new[i - scope : i + scope + 1])
142
143            x = settings.radius_bins_edges[:-scope]
144            dx = np.diff(x)
145            self.ax.plot(
146                (x[:-1] + dx / 2) * si.metres / si.micrometres,
147                spectrum[:-scope] * si.kilograms / si.grams,
148                label=f"t = {t}s",
149                color=self.colors(
150                    t / (self.settings.output_steps[-1] * self.settings.dt)
151                ),
152            )
153        else:
154            self.ax.step(
155                settings.radius_bins_edges[:-1] * si.metres / si.micrometres,
156                spectrum * si.kilograms / si.grams,
157                where="post",
158                label=f"t = {t}s",
159                color=self.colors(
160                    t / (self.settings.output_steps[-1] * self.settings.dt)
161                ),
162            )
SpectrumPlotter(settings, title=None, grid=True, legend=True, log_base=10)
31    def __init__(self, settings, title=None, grid=True, legend=True, log_base=10):
32        self.settings = settings
33        self.format = "pdf"
34        self.colors = SpectrumColors()
35        self.smooth = False
36        self.smooth_scope = 2
37        self.legend = legend
38        self.grid = grid
39        self.title = title
40        self.xlabel = "particle radius [µm]"
41        self.ylabel = "dm/dlnr [g/m^3/(unit dr/r)]"
42        self.log_base = log_base
43        self.ax = pyplot
44        self.fig = pyplot
45        self.finished = False
settings
format
colors
smooth
smooth_scope
legend
grid
title
xlabel
ylabel
log_base
ax
fig
finished
def finish(self):
47    def finish(self):
48        if self.finished:
49            return
50        self.finished = True
51        if self.grid:
52            self.ax.grid()
53
54        base_arg = {
55            "base"
56            + (
57                "x" if _matplotlib_version_actual < _matplotlib_version_3_3_3 else ""
58            ): self.log_base
59        }
60        if self.title is not None:
61            try:
62                self.ax.title(self.title)
63            except TypeError:
64                self.ax.set_title(self.title)
65        try:
66            self.ax.xscale("log", **base_arg)
67            self.ax.xlabel(self.xlabel)
68            self.ax.ylabel(self.ylabel)
69        except AttributeError:
70            self.ax.set_xscale("log", **base_arg)
71            self.ax.set_xlabel(self.xlabel)
72            self.ax.set_ylabel(self.ylabel)
73        if self.legend:
74            self.ax.legend()
def show(self):
76    def show(self):
77        self.finish()
78        pyplot.tight_layout()
79        show_plot()
def save(self, file):
81    def save(self, file):
82        self.finish()
83        pyplot.savefig(file, format=self.format)
def plot(self, spectrum, t):
85    def plot(self, spectrum, t):
86        error = self.plot_analytic_solution(self.settings, t, spectrum)
87        self.plot_data(self.settings, t, spectrum)
88        return error
def plot_analytic_solution(self, settings, t, spectrum=None):
 90    def plot_analytic_solution(self, settings, t, spectrum=None):
 91        if t == 0:
 92            analytic_solution = settings.spectrum.size_distribution
 93        else:
 94
 95            def analytic_solution(x):
 96                return settings.norm_factor * settings.kernel.analytic_solution(
 97                    x=x, t=t, x_0=settings.X0, N_0=settings.n_part
 98                )
 99
100        volume_bins_edges = self.settings.formulae.trivia.volume(
101            settings.radius_bins_edges
102        )
103        dm = np.diff(volume_bins_edges)
104        dr = np.diff(settings.radius_bins_edges)
105
106        pdf_m_x = volume_bins_edges[:-1] + dm / 2
107        pdf_m_y = analytic_solution(pdf_m_x)
108
109        pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2
110        pdf_r_y = pdf_m_y * dm / dr * pdf_r_x
111
112        x = pdf_r_x * si.metres / si.micrometres
113        y_true = (
114            pdf_r_y
115            * self.settings.formulae.trivia.volume(radius=pdf_r_x)
116            * settings.rho
117            / settings.dv
118            * si.kilograms
119            / si.grams
120        )
121
122        self.ax.plot(x, y_true, color="black")
123
124        if spectrum is not None:
125            y = spectrum * si.kilograms / si.grams
126            error = error_measure(y, y_true, x)
127            self.title = f"error measure: {error:.2f}"  # TODO #327 relative error
128            return error
129        return None
def plot_data(self, settings, t, spectrum):
131    def plot_data(self, settings, t, spectrum):
132        if self.smooth:
133            scope = self.smooth_scope
134            if t != 0:
135                new = np.copy(spectrum)
136                for _ in range(2):
137                    for i in range(scope, len(spectrum) - scope):
138                        new[i] = np.mean(spectrum[i - scope : i + scope + 1])
139                    scope = 1
140                    for i in range(scope, len(spectrum) - scope):
141                        spectrum[i] = np.mean(new[i - scope : i + scope + 1])
142
143            x = settings.radius_bins_edges[:-scope]
144            dx = np.diff(x)
145            self.ax.plot(
146                (x[:-1] + dx / 2) * si.metres / si.micrometres,
147                spectrum[:-scope] * si.kilograms / si.grams,
148                label=f"t = {t}s",
149                color=self.colors(
150                    t / (self.settings.output_steps[-1] * self.settings.dt)
151                ),
152            )
153        else:
154            self.ax.step(
155                settings.radius_bins_edges[:-1] * si.metres / si.micrometres,
156                spectrum * si.kilograms / si.grams,
157                where="post",
158                label=f"t = {t}s",
159                color=self.colors(
160                    t / (self.settings.output_steps[-1] * self.settings.dt)
161                ),
162            )