Skip to content

Commit 9ef00ce

Browse files
authored
Merge pull request #15705 from rmcdermo/master
Python: put scatplot bias and std dev on figure and remove pval
2 parents 6711db7 + 158bfda commit 9ef00ce

File tree

2 files changed

+44
-49
lines changed

2 files changed

+44
-49
lines changed

Utilities/Python/FDS_validation_scatterplot_inputs.csv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,4 @@ Plume Height,Empirical Plume Height (m),Predicted Plume Height (m),10,1000,0.03
3939
Rate of Spread,Measured Rate of Spread (m/s),Predicted Rate of Spread (m/s),0.001,3,0.03 0.96,EastOutside,1.6,10,yes,loglog,FDS_Validation_Guide/SCRIPT_FIGURES/Scatterplots/FDS_Rate_of_Spread
4040
Condensation,Measured Heat Flux (kW/m²),Predicted Heat Flux (kW/m²),0,12,0.03 0.96,EastOutside,1.5,12,yes,linear,FDS_Validation_Guide/SCRIPT_FIGURES/Scatterplots/FDS_Condensation
4141
Aerosol Deposition,Measured Mass (mg),Predicted Mass (mg),0,0.15,0.03 0.96,EastOutside,1.5,12,yes,linear,FDS_Validation_Guide/SCRIPT_FIGURES/Scatterplots/FDS_Aerosol_Deposition
42-
Evaporation Rate,Measured Rate (dD²/dt),Predicted Rate (dD²/dt),1.00E-09,1.00E-07,0.03 0.95,EastOutside,1.5,-1,yes,loglog,FDS_Validation_Guide/SCRIPT_FIGURES/Scatterplots/FDS_Evaporation_Rate
42+
Evaporation Rate,Measured Rate (dD²/dt),Predicted Rate (dD²/dt),1.00E-09,1.00E-07,0.03 0.95,EastOutside,1.3,-1,yes,loglog,FDS_Validation_Guide/SCRIPT_FIGURES/Scatterplots/FDS_Evaporation_Rate

Utilities/Python/fdsplotlib.py

Lines changed: 43 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1126,6 +1126,21 @@ def plot_to_fig(x_data,y_data,**kwargs):
11261126
if isinstance(data_label, str) and data_label.lower() == 'blank':
11271127
data_label = None
11281128

1129+
# ------------------------------------------------------------------
1130+
# PRE-PATCH FOR LOG AXES — PREVENTS log(0) WARNINGS
1131+
# ------------------------------------------------------------------
1132+
xmin = kwargs.get('x_min')
1133+
xmax = kwargs.get('x_max')
1134+
ymin = kwargs.get('y_min')
1135+
ymax = kwargs.get('y_max')
1136+
1137+
if plot_type in ('loglog', 'semilogx', 'semilogy'):
1138+
eps = 1e-12
1139+
if xmin is not None and xmin <= 0: xmin = eps
1140+
if ymin is not None and ymin <= 0: ymin = eps
1141+
if xmin is not None and xmax is not None: ax.set_xlim(xmin, xmax)
1142+
if ymin is not None and ymax is not None: ax.set_ylim(ymin, ymax)
1143+
11291144
# generate the main x,y plot
11301145
if plot_type=='linear':
11311146
ax.plot(x_data,y_data,
@@ -1225,13 +1240,11 @@ def plot_to_fig(x_data,y_data,**kwargs):
12251240
capthick=linewidth,
12261241
)
12271242

1228-
12291243
ticklabel_fontsize=kwargs.get('ticklabel_fontsize',default_ticklabel_fontsize)
12301244
plt.setp( ax.xaxis.get_majorticklabels(), rotation=0, fontsize=ticklabel_fontsize )
12311245
plt.setp( ax.yaxis.get_majorticklabels(), rotation=0, fontsize=ticklabel_fontsize )
12321246

12331247
axeslabel_fontsize=kwargs.get('axeslabel_fontsize',default_axeslabel_fontsize)
1234-
12351248
if not using_existing_figure:
12361249
plt.xlabel(kwargs.get('x_label'), fontsize=axeslabel_fontsize)
12371250
plt.ylabel(kwargs.get('y_label'), fontsize=axeslabel_fontsize)
@@ -2141,7 +2154,7 @@ def _label_at(k, labels):
21412154

21422155
# --- Call histogram BEFORE filtering to match MATLAB (includes zeros/Infs) ---
21432156
try:
2144-
hist_file, pval = statistics_histogram(
2157+
hist_file = statistics_histogram(
21452158
Measured_Values, Predicted_Values,
21462159
Plot_Filename, Manuals_Dir, Scatter_Plot_Title
21472160
)
@@ -2164,16 +2177,23 @@ def _label_at(k, labels):
21642177
print(f"[scatplot] Skipping {Scatter_Plot_Title} (no valid data)")
21652178
continue
21662179

2167-
# --- Compute statistics ---
2180+
# --- Compute statistics (MATLAB logic restored) ---
21682181
weight = np.ones_like(Measured_Values)
21692182
log_E_bar = np.sum(np.log(Measured_Values) * weight) / np.sum(weight)
21702183
log_M_bar = np.sum(np.log(Predicted_Values) * weight) / np.sum(weight)
21712184
u2 = np.sum((((np.log(Predicted_Values) - np.log(Measured_Values))
21722185
- (log_M_bar - log_E_bar)) ** 2) * weight) / (np.sum(weight) - 1)
21732186
u = np.sqrt(u2)
2174-
Sigma_E = min(u / np.sqrt(2), Sigma_E_input / 100.0)
2175-
Sigma_M = np.sqrt(max(0.0, u ** 2 - Sigma_E ** 2))
2176-
delta = np.exp(log_M_bar - log_E_bar + 0.5 * Sigma_M ** 2 - 0.5 * Sigma_E ** 2)
2187+
2188+
# Restore MATLAB logic:
2189+
# If no Sigma_E is supplied, experimental sigma = u/sqrt(2)
2190+
if Sigma_E_input > 0:
2191+
Sigma_E = Sigma_E_input / 100.0
2192+
else:
2193+
Sigma_E = u / np.sqrt(2)
2194+
2195+
Sigma_M = np.sqrt(max(0.0, u**2 - Sigma_E**2))
2196+
delta = np.exp(log_M_bar - log_E_bar + 0.5 * Sigma_M**2 - 0.5 * Sigma_E**2)
21772197

21782198
# --- Scatter Plot ---
21792199
fig = fdsplotlib.plot_to_fig(x_data=[-1], y_data=[-1],
@@ -2188,12 +2208,21 @@ def _label_at(k, labels):
21882208
legend_expand=row["Paper_Width_Factor"])
21892209

21902210
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=[Plot_Min, Plot_Max], line_style="k-", figure_handle=fig)
2191-
if Sigma_E > 0:
2192-
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * (1 + 2 * Sigma_E), line_style="k--", figure_handle=fig)
2193-
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) / (1 + 2 * Sigma_E), line_style="k--", figure_handle=fig)
2194-
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta, line_style="r-", figure_handle=fig)
2195-
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta * (1 + 2 * Sigma_M), line_style="r--", figure_handle=fig)
2196-
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta / (1 + 2 * Sigma_M), line_style="r--", figure_handle=fig)
2211+
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * (1 + 2 * Sigma_E), line_style="k--", figure_handle=fig)
2212+
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) / (1 + 2 * Sigma_E), line_style="k--", figure_handle=fig)
2213+
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta, line_style="r-", figure_handle=fig)
2214+
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta * (1 + 2 * Sigma_M), line_style="r--", figure_handle=fig)
2215+
fdsplotlib.plot_to_fig(x_data=[Plot_Min, Plot_Max], y_data=np.array([Plot_Min, Plot_Max]) * delta / (1 + 2 * Sigma_M), line_style="r--", figure_handle=fig)
2216+
2217+
# --- Add statistics annotation ---
2218+
ax = fig.gca()
2219+
xnorm = 0.03 # 3% from left
2220+
ynorm = 0.97 # 3% from top
2221+
dy = 0.05 # vertical spacing in normalized units
2222+
ax.text(xnorm, ynorm,f"{Scatter_Plot_Title}",fontsize=plot_style["Scat_Title_Font_Size"],ha="left", va="top",transform=ax.transAxes)
2223+
ax.text(xnorm, ynorm - dy,f"Exp. Rel. Std. Dev.: {Sigma_E:.2f}",fontsize=plot_style["Scat_Label_Font_Size"],ha="left", va="top",transform=ax.transAxes)
2224+
ax.text(xnorm, ynorm - 2*dy,f"Model Rel. Std. Dev.: {Sigma_M:.2f}",fontsize=plot_style["Scat_Label_Font_Size"],ha="left", va="top",transform=ax.transAxes)
2225+
ax.text(xnorm, ynorm - 3*dy,f"Model Bias Factor: {delta:.2f}",fontsize=plot_style["Scat_Label_Font_Size"],ha="left", va="top",transform=ax.transAxes)
21972226

21982227
# --- Plot each dataset with its own marker and color ---
21992228
seen_labels = set()
@@ -2528,30 +2557,6 @@ def histogram_output(Histogram_Tex_Output, Output_Histograms):
25282557
print(f"[histogram_output] Wrote LaTeX histogram file: {Histogram_Tex_Output}")
25292558

25302559

2531-
def spiegel_test(x):
2532-
"""Exact translation of MATLAB spiegel_test.m with Inf/NaN handling identical to MATLAB."""
2533-
import numpy as np
2534-
from math import sqrt, erf
2535-
2536-
x = np.asarray(x, dtype=float)
2537-
2538-
# MATLAB arithmetic allows Inf and NaN to propagate but doesn't remove them
2539-
xm = np.nanmean(x)
2540-
xs = np.nanstd(x, ddof=0)
2541-
xz = (x - xm) / xs
2542-
xz2 = xz ** 2
2543-
2544-
# MATLAB behavior: Inf * 0 -> NaN, which is ignored by sum()
2545-
with np.errstate(invalid='ignore', divide='ignore'):
2546-
term = xz2 * np.log(xz2)
2547-
2548-
N = np.nansum(term)
2549-
n = np.sum(np.isfinite(xz2)) # count finite entries only
2550-
ts = (N - 0.73 * n) / (0.8969 * sqrt(n))
2551-
pval = 1 - abs(erf(ts / sqrt(2))) # 2-sided
2552-
return pval
2553-
2554-
25552560
def statistics_histogram(Measured_Values, Predicted_Values,
25562561
Plot_Filename, Manuals_Dir, Scatter_Plot_Title,
25572562
Figure_Visibility='off', Paper_Width=5.0,
@@ -2566,13 +2571,6 @@ def statistics_histogram(Measured_Values, Predicted_Values,
25662571
with np.errstate(divide='ignore', invalid='ignore'):
25672572
ln_M_E = np.log(Predicted_Values) - np.log(Measured_Values)
25682573

2569-
# Normality test before filtering — this is the key
2570-
if len(ln_M_E) >= 4:
2571-
pval = spiegel_test(ln_M_E)
2572-
else:
2573-
print(f"[statistics_histogram] Not enough data for {Scatter_Plot_Title}")
2574-
return None, None
2575-
25762574
# MATLAB's hist() ignores NaN/Inf implicitly
25772575
valid = np.isfinite(ln_M_E)
25782576
ln_M_E = ln_M_E[valid]
@@ -2600,8 +2598,6 @@ def statistics_histogram(Measured_Values, Predicted_Values,
26002598
ax.set_xticks(xcenters)
26012599
ax.set_xticklabels([str(i) for i in range(1, len(xcenters) + 1)])
26022600
ax.text(0.03, 0.90, Scatter_Plot_Title, transform=ax.transAxes)
2603-
ax.text(0.03, 0.82, "Normality Test", transform=ax.transAxes)
2604-
ax.text(0.03, 0.74, f"p-value = {pval:.2f}", transform=ax.transAxes)
26052601

26062602
outpath = os.path.join(Manuals_Dir, f"{Plot_Filename}_Histogram.pdf")
26072603
os.makedirs(os.path.dirname(outpath), exist_ok=True)
@@ -2610,8 +2606,7 @@ def statistics_histogram(Measured_Values, Predicted_Values,
26102606
plt.close(fig)
26112607
plt.figure().clear()
26122608

2613-
print(f"[statistics_histogram] {Scatter_Plot_Title}: p = {pval:.2f}")
2614-
return f"{os.path.basename(Plot_Filename)}_Histogram", pval
2609+
return f"{os.path.basename(Plot_Filename)}_Histogram"
26152610

26162611

26172612
def set_ticks_like_matlab(fig):

0 commit comments

Comments
 (0)