PySDM_examples.featured.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.featured.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 self.ax.title(self.title) 61 self.ax.xscale("log", **base_arg) 62 self.ax.xlabel(self.xlabel) 63 self.ax.ylabel(self.ylabel) 64 65 if self.legend: 66 self.ax.legend() 67 68 def show(self): 69 self.finish() 70 pyplot.tight_layout() 71 show_plot() 72 73 def save(self, file): 74 self.finish() 75 pyplot.savefig(file, format=self.format) 76 77 def plot( 78 self, spectrum, t, label=None, color=None, title=None, add_error_to_label=False 79 ): 80 error = self.plot_analytic_solution(self.settings, t, spectrum, title) 81 if label is not None and add_error_to_label: 82 label += f" error={error:.4g}" 83 self.plot_data(self.settings, t, spectrum, label, color) 84 return error 85 86 def plot_analytic_solution(self, settings, t, spectrum, title): 87 if t == 0: 88 analytic_solution = settings.spectrum.size_distribution 89 else: 90 91 def analytic_solution(x): 92 return settings.norm_factor * settings.kernel.analytic_solution( 93 x=x, t=t, x_0=settings.X0, N_0=settings.n_part 94 ) 95 96 volume_bins_edges = self.settings.formulae.trivia.volume( 97 settings.radius_bins_edges 98 ) 99 dm = np.diff(volume_bins_edges) 100 dr = np.diff(settings.radius_bins_edges) 101 102 pdf_m_x = volume_bins_edges[:-1] + dm / 2 103 pdf_m_y = analytic_solution(pdf_m_x) 104 105 pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2 106 pdf_r_y = pdf_m_y * dm / dr * pdf_r_x 107 108 x = pdf_r_x * si.metres / si.micrometres 109 y_true = ( 110 pdf_r_y 111 * self.settings.formulae.trivia.volume(radius=pdf_r_x) 112 * settings.rho 113 / settings.dv 114 * si.kilograms 115 / si.grams 116 ) 117 118 self.ax.plot(x, y_true, color="black") 119 120 if spectrum is not None: 121 y = spectrum * si.kilograms / si.grams 122 error = error_measure(y, y_true, x) 123 self.title = ( 124 title or f"error measure: {error:.2f}" 125 ) # TODO #327 relative error 126 return error 127 return None 128 129 def plot_data(self, settings, t, spectrum, label, color): 130 if self.smooth: 131 scope = self.smooth_scope 132 if t != 0: 133 new = np.copy(spectrum) 134 for _ in range(2): 135 for i in range(scope, len(spectrum) - scope): 136 new[i] = np.mean(spectrum[i - scope : i + scope + 1]) 137 scope = 1 138 for i in range(scope, len(spectrum) - scope): 139 spectrum[i] = np.mean(new[i - scope : i + scope + 1]) 140 141 x = settings.radius_bins_edges[:-scope] 142 dx = np.diff(x) 143 self.ax.plot( 144 (x[:-1] + dx / 2) * si.metres / si.micrometres, 145 spectrum[:-scope] * si.kilograms / si.grams, 146 label=label or f"t = {t}s", 147 color=color 148 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 149 ) 150 else: 151 self.ax.step( 152 settings.radius_bins_edges[:-1] * si.metres / si.micrometres, 153 spectrum * si.kilograms / si.grams, 154 where="post", 155 label=label or f"t = {t}s", 156 color=color 157 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 158 )
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
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 self.ax.title(self.title) 62 self.ax.xscale("log", **base_arg) 63 self.ax.xlabel(self.xlabel) 64 self.ax.ylabel(self.ylabel) 65 66 if self.legend: 67 self.ax.legend() 68 69 def show(self): 70 self.finish() 71 pyplot.tight_layout() 72 show_plot() 73 74 def save(self, file): 75 self.finish() 76 pyplot.savefig(file, format=self.format) 77 78 def plot( 79 self, spectrum, t, label=None, color=None, title=None, add_error_to_label=False 80 ): 81 error = self.plot_analytic_solution(self.settings, t, spectrum, title) 82 if label is not None and add_error_to_label: 83 label += f" error={error:.4g}" 84 self.plot_data(self.settings, t, spectrum, label, color) 85 return error 86 87 def plot_analytic_solution(self, settings, t, spectrum, title): 88 if t == 0: 89 analytic_solution = settings.spectrum.size_distribution 90 else: 91 92 def analytic_solution(x): 93 return settings.norm_factor * settings.kernel.analytic_solution( 94 x=x, t=t, x_0=settings.X0, N_0=settings.n_part 95 ) 96 97 volume_bins_edges = self.settings.formulae.trivia.volume( 98 settings.radius_bins_edges 99 ) 100 dm = np.diff(volume_bins_edges) 101 dr = np.diff(settings.radius_bins_edges) 102 103 pdf_m_x = volume_bins_edges[:-1] + dm / 2 104 pdf_m_y = analytic_solution(pdf_m_x) 105 106 pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2 107 pdf_r_y = pdf_m_y * dm / dr * pdf_r_x 108 109 x = pdf_r_x * si.metres / si.micrometres 110 y_true = ( 111 pdf_r_y 112 * self.settings.formulae.trivia.volume(radius=pdf_r_x) 113 * settings.rho 114 / settings.dv 115 * si.kilograms 116 / si.grams 117 ) 118 119 self.ax.plot(x, y_true, color="black") 120 121 if spectrum is not None: 122 y = spectrum * si.kilograms / si.grams 123 error = error_measure(y, y_true, x) 124 self.title = ( 125 title or f"error measure: {error:.2f}" 126 ) # TODO #327 relative error 127 return error 128 return None 129 130 def plot_data(self, settings, t, spectrum, label, color): 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=label or f"t = {t}s", 148 color=color 149 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 150 ) 151 else: 152 self.ax.step( 153 settings.radius_bins_edges[:-1] * si.metres / si.micrometres, 154 spectrum * si.kilograms / si.grams, 155 where="post", 156 label=label or f"t = {t}s", 157 color=color 158 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 159 )
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
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 self.ax.title(self.title) 62 self.ax.xscale("log", **base_arg) 63 self.ax.xlabel(self.xlabel) 64 self.ax.ylabel(self.ylabel) 65 66 if self.legend: 67 self.ax.legend()
def
plot( self, spectrum, t, label=None, color=None, title=None, add_error_to_label=False):
78 def plot( 79 self, spectrum, t, label=None, color=None, title=None, add_error_to_label=False 80 ): 81 error = self.plot_analytic_solution(self.settings, t, spectrum, title) 82 if label is not None and add_error_to_label: 83 label += f" error={error:.4g}" 84 self.plot_data(self.settings, t, spectrum, label, color) 85 return error
def
plot_analytic_solution(self, settings, t, spectrum, title):
87 def plot_analytic_solution(self, settings, t, spectrum, title): 88 if t == 0: 89 analytic_solution = settings.spectrum.size_distribution 90 else: 91 92 def analytic_solution(x): 93 return settings.norm_factor * settings.kernel.analytic_solution( 94 x=x, t=t, x_0=settings.X0, N_0=settings.n_part 95 ) 96 97 volume_bins_edges = self.settings.formulae.trivia.volume( 98 settings.radius_bins_edges 99 ) 100 dm = np.diff(volume_bins_edges) 101 dr = np.diff(settings.radius_bins_edges) 102 103 pdf_m_x = volume_bins_edges[:-1] + dm / 2 104 pdf_m_y = analytic_solution(pdf_m_x) 105 106 pdf_r_x = settings.radius_bins_edges[:-1] + dr / 2 107 pdf_r_y = pdf_m_y * dm / dr * pdf_r_x 108 109 x = pdf_r_x * si.metres / si.micrometres 110 y_true = ( 111 pdf_r_y 112 * self.settings.formulae.trivia.volume(radius=pdf_r_x) 113 * settings.rho 114 / settings.dv 115 * si.kilograms 116 / si.grams 117 ) 118 119 self.ax.plot(x, y_true, color="black") 120 121 if spectrum is not None: 122 y = spectrum * si.kilograms / si.grams 123 error = error_measure(y, y_true, x) 124 self.title = ( 125 title or f"error measure: {error:.2f}" 126 ) # TODO #327 relative error 127 return error 128 return None
def
plot_data(self, settings, t, spectrum, label, color):
130 def plot_data(self, settings, t, spectrum, label, color): 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=label or f"t = {t}s", 148 color=color 149 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 150 ) 151 else: 152 self.ax.step( 153 settings.radius_bins_edges[:-1] * si.metres / si.micrometres, 154 spectrum * si.kilograms / si.grams, 155 where="post", 156 label=label or f"t = {t}s", 157 color=color 158 or self.colors(t / (self.settings.output_steps[-1] * self.settings.dt)), 159 )