1import matplotlib.pyplot as plt
2import numpy as np
3
4from PySDM.physics import constants as const
5
6
7class _Plot:
8 def __init__(self, fig, ax):
9 self.fig, self.ax = fig, ax
10 self.ax.set_title(" ")
11
12
13class _ImagePlot(_Plot):
14 line_args = {"color": "red", "alpha": 0.666, "linestyle": ":", "linewidth": 3}
15
16 def __init__(
17 self, fig, ax, grid, size, product, show=False, lines=False, cmap="YlGnBu"
18 ):
19 super().__init__(fig, ax)
20 self.nans = np.full(grid, np.nan)
21
22 self.dx = size[0] / grid[0]
23 self.dz = size[1] / grid[1]
24
25 xlim = (0, size[0])
26 zlim = (0, size[1])
27
28 self.ax.set_xlim(xlim)
29 self.ax.set_ylim(zlim)
30
31 if lines:
32 self.lines = {"X": [None] * 2, "Z": [None] * 2}
33 self.lines["X"][0] = plt.plot([-1] * 2, zlim, **self.line_args)[0]
34 self.lines["Z"][0] = plt.plot(xlim, [-1] * 2, **self.line_args)[0]
35 self.lines["X"][1] = plt.plot([-1] * 2, zlim, **self.line_args)[0]
36 self.lines["Z"][1] = plt.plot(xlim, [-1] * 2, **self.line_args)[0]
37
38 data = np.full_like(self.nans, np.nan)
39 label = f"{product.name} [{product.unit}]"
40
41 self.ax.set_xlabel("X [m]")
42 self.ax.set_ylabel("Z [m]")
43
44 self.im = self.ax.imshow(
45 self._transpose(data), origin="lower", extent=(*xlim, *zlim), cmap=cmap
46 )
47 plt.colorbar(self.im, ax=self.ax).set_label(label)
48 if show:
49 plt.show()
50
51 @staticmethod
52 def _transpose(data):
53 if data is not None:
54 return data.T
55 return None
56
57 def update(self, data, step, data_range):
58 data = self._transpose(data)
59 if data is not None:
60 self.im.set_data(data)
61 if data_range is not None:
62 self.im.set_clim(vmin=data_range[0], vmax=data_range[1])
63 nanmin = np.nan
64 nanmax = np.nan
65 if np.isfinite(data).any():
66 nanmin = np.nanmin(data)
67 nanmax = np.nanmax(data)
68 self.ax.set_title(
69 f"min:{nanmin: .3g} max:{nanmax: .3g} t/dt_out:{step: >6}"
70 )
71
72 def update_lines(self, focus_x, focus_z):
73 self.lines["X"][0].set_xdata(x=(focus_x[0] + 0.15) * self.dx)
74 self.lines["Z"][0].set_ydata(y=(focus_z[0] + 0.15) * self.dz)
75 self.lines["X"][1].set_xdata(x=(focus_x[1] - 0.25) * self.dx)
76 self.lines["Z"][1].set_ydata(y=(focus_z[1] - 0.25) * self.dz)
77
78
79class _SpectrumPlot(_Plot):
80 def __init__(self, r_bins, initial_spectrum_per_mass_of_dry_air, show=True):
81 super().__init__(*plt.subplots(1, 1))
82 self.ax.set_xlim(np.amin(r_bins), np.amax(r_bins))
83 self.ax.set_xlabel("particle radius [μm]")
84 self.ax.set_ylabel("specific concentration density [mg$^{-1}$ μm$^{-1}$]")
85 self.ax.set_xscale("log")
86 self.ax.set_yscale("log")
87 self.ax.set_ylim(1, 5e3)
88 self.ax.grid(True)
89 vals = initial_spectrum_per_mass_of_dry_air.size_distribution(
90 r_bins * const.si.um
91 )
92 const.convert_to(vals, const.si.mg**-1 / const.si.um)
93 self.ax.plot(r_bins, vals, label="spectrum sampled at t=0")
94 self.spec_wet = self.ax.step(
95 r_bins,
96 np.full_like(r_bins, np.nan),
97 label="binned super-particle wet sizes",
98 )[0]
99 self.spec_dry = self.ax.step(
100 r_bins,
101 np.full_like(r_bins, np.nan),
102 label="binned super-particle dry sizes",
103 )[0]
104 self.ax.legend()
105 if show:
106 plt.show()
107
108 def update_wet(self, data, step):
109 self.spec_wet.set_ydata(data)
110 self.ax.set_title(f"t/dt_out:{step}")
111
112 def update_dry(self, dry):
113 self.spec_dry.set_ydata(dry)
114
115
116class _TimeseriesPlot(_Plot):
117 def __init__(self, fig, ax, times, show=True):
118 super().__init__(fig, ax)
119 self.ax.set_xlim(0, times[-1])
120 self.ax.set_xlabel("time [s]")
121 self.ax.set_ylabel("rainfall [mm/day]")
122 self.ax.grid(True)
123 self.ydata = np.full_like(times, np.nan, dtype=float)
124 self.timeseries = self.ax.step(times, self.ydata, where="pre")[0]
125 if show:
126 plt.show()
127
128 def update(self, data, data_range):
129 if data is not None:
130 self.ydata[0 : len(data)] = data[:]
131 if data_range[0] != data_range[1]:
132 self.ax.set_ylim(data_range[0], 1.1 * data_range[1])
133 else:
134 self.ydata[:] = np.nan
135 self.timeseries.set_ydata(self.ydata)
136
137
138class _TemperaturePlot(_Plot):
139 def __init__(self, T_bins, formulae, show=True):
140 super().__init__(*plt.subplots(1, 1))
141 self.formulae = formulae
142 self.ax.set_xlim(np.amax(T_bins), np.amin(T_bins))
143 self.ax.set_xlabel("temperature [K]")
144 self.ax.set_ylabel("freezable fraction / cdf [1]")
145 self.ax.set_ylim(-0.05, 1.05)
146 self.ax.grid(True)
147 # self.ax.plot(T_bins, self.formulae.freezing_temperature_spectrum.cdf(T_bins),
148 # label=str(self.formulae.freezing_temperature_spectrum) + " (sampled at t=0)")
149 self.spec = self.ax.step(
150 T_bins,
151 np.full_like(T_bins, np.nan),
152 label="binned super-particle attributes",
153 where="mid",
154 )[0]
155 self.ax.legend()
156 if show:
157 plt.show()
158
159 def update(self, data, step):
160 self.ax.set_title(f"t/dt_out:{step}")
161 self.spec.set_ydata(data)
162
163
164class _TerminalVelocityPlot(_Plot):
165 def __init__(self, radius_bins, formulae, show=True):
166 self.formulae = formulae
167 super().__init__(*plt.subplots(1, 1))
168
169 self.ax.set_xlim(
170 np.amin(radius_bins) / const.si.um, np.amax(radius_bins) / const.si.um
171 )
172 self.ax.set_xlabel("radius [μm]")
173 self.ax.set_ylabel("mean terminal velocity [m/s]")
174 self.ax.set_ylim(0, 0.1)
175 self.ax.grid(True)
176
177 self.radius_bins = radius_bins
178 # self.ax.plot(T_bins, self.formulae.freezing_temperature_spectrum.cdf(T_bins),
179 # label=str(self.formulae.freezing_temperature_spectrum) + " (sampled at t=0)")
180 # nans = np.full_like(radius_bins[:-1], np.nan)
181 # self.spec = self.ax.fill_between(
182 # (radius_bins[:-1] + np.diff(radius_bins)/2) / const.si.um,
183 # nans,
184 # nans,
185 # marker='o'
186 # )[0]
187 # label='binned super-particle attributes',
188 # where='mid'
189 # )[0]
190 # self.ax.legend()
191
192 if show:
193 plt.show()
194
195 def update(self, data_min, data_max, step):
196 self.ax.set_title(f"t/dt_out:{step}")
197 self.ax.collections.clear()
198 self.ax.fill_between(
199 (self.radius_bins[:-1] + np.diff(self.radius_bins) / 2) / const.si.um,
200 data_min,
201 data_max,
202 color="gray",
203 )
204 # self.spec.set_ydata(data)