lab-physics-tools 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lab_physics_tools-0.1.0/.gitignore +23 -0
- lab_physics_tools-0.1.0/PKG-INFO +22 -0
- lab_physics_tools-0.1.0/README.md +9 -0
- lab_physics_tools-0.1.0/docs/chi_squared_fit_manual.pdf +0 -0
- lab_physics_tools-0.1.0/docs/example_chi_squared_2d_map.pdf +0 -0
- lab_physics_tools-0.1.0/docs/plot_typo_manual.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_10_chi_squared_2d_map_manual.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_1_fit_pipeline.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_2_odr_difference_fit.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_3_linear_fit.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_4_gussian_fit.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_5_global_guess_parameters_recovery.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_6_gaussian_scatter.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_7_gaussian_residuals.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_8_log_residuals.pdf +0 -0
- lab_physics_tools-0.1.0/examples/example_9_scatter_log_both_axes.pdf +0 -0
- lab_physics_tools-0.1.0/pyproject.toml +25 -0
- lab_physics_tools-0.1.0/src/labphysicstools/__init__.py +9 -0
- lab_physics_tools-0.1.0/src/labphysicstools/chi_squared_2d_map.py +421 -0
- lab_physics_tools-0.1.0/src/labphysicstools/chi_squared_fit.py +764 -0
- lab_physics_tools-0.1.0/src/labphysicstools/plot_typo.py +341 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Byte-compiled / cache
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Packaging & Build
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.egg-info/
|
|
10
|
+
|
|
11
|
+
# LaTeX ausiliari (mantieni i .tex e i .pdf finali)
|
|
12
|
+
*.aux
|
|
13
|
+
*.log
|
|
14
|
+
*.out
|
|
15
|
+
*.toc
|
|
16
|
+
*.synctex.gz
|
|
17
|
+
*.fls
|
|
18
|
+
*.fdb_latexmk
|
|
19
|
+
|
|
20
|
+
# Ambienti virtuali
|
|
21
|
+
.venv/
|
|
22
|
+
venv/
|
|
23
|
+
env/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lab-physics-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Toolkit Python per fit chi-quadro, mappe 2D e tipografia scientifica per laboratori di fisica
|
|
5
|
+
Project-URL: Source, https://github.com/el-coda-coda/LabPhysicsTools.git
|
|
6
|
+
Author: Andrea Codarin
|
|
7
|
+
License: MIT
|
|
8
|
+
Requires-Python: >=3.9
|
|
9
|
+
Requires-Dist: matplotlib>=3.5
|
|
10
|
+
Requires-Dist: numpy>=1.20
|
|
11
|
+
Requires-Dist: scipy>=1.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# LabPhysicsTools
|
|
15
|
+
|
|
16
|
+
LabPhysicsTools è una libreria Python sviluppata per semplificare, standardizzare e automatizzare le analisi statistiche e la resa grafica nei laboratori di fisica sperimentale. Il pacchetto racchiude routine complete di regressione non lineare basate sulla minimizzazione del chi-quadro, calcolo dei profili di confidenza e pipeline di plotting ottimizzate per report tecnici e documenti LaTeX.
|
|
17
|
+
|
|
18
|
+
## Caratteristiche Principali
|
|
19
|
+
|
|
20
|
+
1. Regressione $\chi^2$ e Metodi ODR (chi_squared_fit): routine di fit non lineare con stima rigorosa degli errori sui parametri ottimali, supporto per incertezze su entrambi gli assi tramite regressione a distanza ortogonale (ODR), calcolo automatico di $\chi^2$, gradi di libertà ($ndf$), p-value e matrici di covarianza, algoritmi di recupero per la stima iniziale dei parametri (global guess parameter recovery).
|
|
21
|
+
3. Mappatura 2D del $\chi^2$ ed Ellissoidi di Confidenza (chi_squared_2d_map): generazione di mappe bidimensionali di $\chi^2$ nello spazio dei parametri per individuare correlazioni e minimi locali, tracciamento diretto dei contorni di confidenza a $1\sigma$, $2\sigma$ e $3\sigma$ ($\Delta\chi^2$), esportazione di grafici diagnostici per la verifica dell'approssimazione parabolica attorno al minimo.
|
|
22
|
+
6. Tipografia e Visualizzazione Scientifica (plot_typo): layout coerenti per figure a singolo e doppio pannello (es. dato sperimentale + pannello dei residui normalizzati), gestione avanzata delle scale lineari e logaritmiche con spaziatura e griglie ad alta leggibilità, configurazione tipografica integrata con gli standard di impaginazione accademica (font sizing, etichette asse e formattazione LaTeX-ready).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# LabPhysicsTools
|
|
2
|
+
|
|
3
|
+
LabPhysicsTools è una libreria Python sviluppata per semplificare, standardizzare e automatizzare le analisi statistiche e la resa grafica nei laboratori di fisica sperimentale. Il pacchetto racchiude routine complete di regressione non lineare basate sulla minimizzazione del chi-quadro, calcolo dei profili di confidenza e pipeline di plotting ottimizzate per report tecnici e documenti LaTeX.
|
|
4
|
+
|
|
5
|
+
## Caratteristiche Principali
|
|
6
|
+
|
|
7
|
+
1. Regressione $\chi^2$ e Metodi ODR (chi_squared_fit): routine di fit non lineare con stima rigorosa degli errori sui parametri ottimali, supporto per incertezze su entrambi gli assi tramite regressione a distanza ortogonale (ODR), calcolo automatico di $\chi^2$, gradi di libertà ($ndf$), p-value e matrici di covarianza, algoritmi di recupero per la stima iniziale dei parametri (global guess parameter recovery).
|
|
8
|
+
3. Mappatura 2D del $\chi^2$ ed Ellissoidi di Confidenza (chi_squared_2d_map): generazione di mappe bidimensionali di $\chi^2$ nello spazio dei parametri per individuare correlazioni e minimi locali, tracciamento diretto dei contorni di confidenza a $1\sigma$, $2\sigma$ e $3\sigma$ ($\Delta\chi^2$), esportazione di grafici diagnostici per la verifica dell'approssimazione parabolica attorno al minimo.
|
|
9
|
+
6. Tipografia e Visualizzazione Scientifica (plot_typo): layout coerenti per figure a singolo e doppio pannello (es. dato sperimentale + pannello dei residui normalizzati), gestione avanzata delle scale lineari e logaritmiche con spaziatura e griglie ad alta leggibilità, configurazione tipografica integrata con gli standard di impaginazione accademica (font sizing, etichette asse e formattazione LaTeX-ready).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lab-physics-tools"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Toolkit Python per fit chi-quadro, mappe 2D e tipografia scientifica per laboratori di fisica"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Andrea Codarin" }
|
|
14
|
+
]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"numpy>=1.20",
|
|
17
|
+
"scipy>=1.7",
|
|
18
|
+
"matplotlib>=3.5",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
"Source" = "https://github.com/el-coda-coda/LabPhysicsTools.git"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/lab_physics"]
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import Optional, Tuple, List, Union, Dict, Any
|
|
3
|
+
import numpy as np
|
|
4
|
+
import matplotlib.pyplot as plt
|
|
5
|
+
import matplotlib.ticker as ticker
|
|
6
|
+
import matplotlib.gridspec as gridspec
|
|
7
|
+
from scipy import ndimage
|
|
8
|
+
from iminuit import Minuit
|
|
9
|
+
import sys
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
import matplotlib.patheffects as patheffects
|
|
12
|
+
|
|
13
|
+
typo_params = {
|
|
14
|
+
"textsize" : 12,
|
|
15
|
+
"labelsize" : 12,
|
|
16
|
+
"ticksize" : 10,
|
|
17
|
+
"legendsize" : 10,
|
|
18
|
+
"titlesize" : 12,
|
|
19
|
+
"width" : 10,
|
|
20
|
+
"height" : 10
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
plt.rcParams.update({
|
|
24
|
+
"text.usetex": True,
|
|
25
|
+
"font.family": "serif",
|
|
26
|
+
"font.serif": ["Palatino", "URW Palladio L", "TeX Gyre Pagella"],
|
|
27
|
+
# Pacchetti LaTeX per formule e testo in Palatino coerente
|
|
28
|
+
"text.latex.preamble": r"""
|
|
29
|
+
\usepackage{amsmath}
|
|
30
|
+
\usepackage{amssymb}
|
|
31
|
+
\usepackage{mathpazo} % Font Palatino classico per testo e math
|
|
32
|
+
% In alternativa a mathpazo puoi usare i pacchetti moderni:
|
|
33
|
+
% \usepackage{newpxtext}
|
|
34
|
+
% \usepackage{newpxmath}
|
|
35
|
+
""",
|
|
36
|
+
# Dimensioni proporzionate del font
|
|
37
|
+
"font.size": typo_params["textsize"],
|
|
38
|
+
"axes.labelsize": typo_params["labelsize"],
|
|
39
|
+
"xtick.labelsize": typo_params["ticksize"],
|
|
40
|
+
"ytick.labelsize": typo_params["ticksize"],
|
|
41
|
+
"legend.fontsize": typo_params["legendsize"],
|
|
42
|
+
"figure.titlesize": typo_params["titlesize"],
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
# Importa FitPipeline direttamente dal tuo file chi_squared_fit.py
|
|
46
|
+
CURRENT_DIR = Path(__file__).resolve().parent
|
|
47
|
+
PROJECT_ROOT = CURRENT_DIR.parent
|
|
48
|
+
PATH_FIT_MODULE = PROJECT_ROOT / "chi_squared_fit"
|
|
49
|
+
|
|
50
|
+
if str(PATH_FIT_MODULE) not in sys.path:
|
|
51
|
+
sys.path.insert(0, str(PATH_FIT_MODULE))
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
from labphysicstools.chi_squared_fit import FitPipeline, RisultatiFit
|
|
55
|
+
except ModuleNotFounddError:
|
|
56
|
+
# Fallback se lo script viene eseguito direttamente dalla root di progetto
|
|
57
|
+
from LABORATORIO_FISICA_CODICE.chi_squared_fit.chi_squared_fit import FitPipeline, RisultatiFit
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
61
|
+
# Funzione per la generazione della Mappa Chi-Quadro Profilata (Metodo A)
|
|
62
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
63
|
+
def chi2_map_pipeline(
|
|
64
|
+
risultati: Any,
|
|
65
|
+
param_x: str,
|
|
66
|
+
param_y: str,
|
|
67
|
+
param_info: Optional[Dict[str, Dict[str, str]]] = None,
|
|
68
|
+
n_sigma: float = 3.5,
|
|
69
|
+
grid_size: int = 35,
|
|
70
|
+
zoom_factor: int = 4,
|
|
71
|
+
show_clabels: bool = True,
|
|
72
|
+
manual_clabels: Union[bool, List[Tuple[float, float]]] = False,
|
|
73
|
+
clabel_fontsize: float = typo_params["legendsize"],
|
|
74
|
+
stats_box_loc: str = "inside_map", # 'inside_map', 'top', o 'custom'
|
|
75
|
+
stats_box_pos: Optional[Tuple[float, float]] = None,
|
|
76
|
+
show_stats_box: bool = True,
|
|
77
|
+
spacing: float = 0.01,
|
|
78
|
+
cmap: str = "viridis_r",
|
|
79
|
+
save_path: Optional[str] = None,
|
|
80
|
+
show: bool = True,
|
|
81
|
+
colorbar_loc: str = "right", # oppure "bottom"
|
|
82
|
+
) -> Tuple[plt.Figure, Tuple[plt.Axes, plt.Axes, plt.Axes]]:
|
|
83
|
+
"""
|
|
84
|
+
Genera la mappa 2D profilata del Delta chi^2 per la coppia (param_x, param_y)
|
|
85
|
+
con profili 1D marginali e gestione elastica delle etichette.
|
|
86
|
+
"""
|
|
87
|
+
m: Minuit = risultati.minuit_obj
|
|
88
|
+
|
|
89
|
+
# 1. Limiti e deviazioni
|
|
90
|
+
x_val = m.values[param_x]
|
|
91
|
+
y_val = m.values[param_y]
|
|
92
|
+
|
|
93
|
+
def _extract_err(param_name: str) -> float:
|
|
94
|
+
if param_name in m.merrors:
|
|
95
|
+
me = m.merrors[param_name]
|
|
96
|
+
return max(abs(me.lower), abs(me.upper))
|
|
97
|
+
return m.errors[param_name]
|
|
98
|
+
|
|
99
|
+
err_x = _extract_err(param_x)
|
|
100
|
+
err_y = _extract_err(param_y)
|
|
101
|
+
|
|
102
|
+
x_min, x_max = x_val - n_sigma * err_x, x_val + n_sigma * err_x
|
|
103
|
+
y_min, y_max = y_val - n_sigma * err_y, y_val + n_sigma * err_y
|
|
104
|
+
global_fval = float(m.fval)
|
|
105
|
+
|
|
106
|
+
# 2. Scansione profilata con Metodo A e Warm-Start sequenziale
|
|
107
|
+
x_vals = np.linspace(x_min, x_max, grid_size)
|
|
108
|
+
y_vals = np.linspace(y_min, y_max, grid_size)
|
|
109
|
+
X_map, Y_map = np.meshgrid(x_vals, y_vals)
|
|
110
|
+
Z_profiled = np.zeros_like(X_map)
|
|
111
|
+
|
|
112
|
+
orig_values = list(m.values)
|
|
113
|
+
param_names = list(m.parameters)
|
|
114
|
+
current_cache = {p: m.values[p] for p in param_names}
|
|
115
|
+
|
|
116
|
+
for i in range(grid_size):
|
|
117
|
+
j_indices = range(grid_size) if (i % 2 == 0) else reversed(range(grid_size))
|
|
118
|
+
for j in j_indices:
|
|
119
|
+
for p in param_names:
|
|
120
|
+
m.values[p] = current_cache[p]
|
|
121
|
+
|
|
122
|
+
m.fixed[param_x] = True
|
|
123
|
+
m.fixed[param_y] = True
|
|
124
|
+
m.values[param_x] = X_map[i, j]
|
|
125
|
+
m.values[param_y] = Y_map[i, j]
|
|
126
|
+
m.migrad()
|
|
127
|
+
|
|
128
|
+
if not m.valid:
|
|
129
|
+
for idx, p in enumerate(param_names):
|
|
130
|
+
m.values[p] = orig_values[idx]
|
|
131
|
+
m.values[param_x] = X_map[i, j]
|
|
132
|
+
m.values[param_y] = Y_map[i, j]
|
|
133
|
+
m.migrad()
|
|
134
|
+
|
|
135
|
+
Z_profiled[i, j] = m.fval
|
|
136
|
+
for p in param_names:
|
|
137
|
+
current_cache[p] = m.values[p]
|
|
138
|
+
|
|
139
|
+
# Ripristino stato Minuit originale
|
|
140
|
+
m.fixed[param_x] = False
|
|
141
|
+
m.fixed[param_y] = False
|
|
142
|
+
for idx, p in enumerate(param_names):
|
|
143
|
+
m.values[p] = orig_values[idx]
|
|
144
|
+
m.migrad()
|
|
145
|
+
|
|
146
|
+
dz = Z_profiled - global_fval
|
|
147
|
+
dz_smooth = ndimage.zoom(dz, zoom_factor, order=3)
|
|
148
|
+
x_smooth = np.linspace(x_min, x_max, dz_smooth.shape[1])
|
|
149
|
+
y_smooth = np.linspace(y_min, y_max, dz_smooth.shape[0])
|
|
150
|
+
|
|
151
|
+
cbar_pad = 0.1
|
|
152
|
+
|
|
153
|
+
if colorbar_loc == "right":
|
|
154
|
+
fig = plt.figure(figsize=(8.2, 7.2))
|
|
155
|
+
gs = gridspec.GridSpec(
|
|
156
|
+
2, 4,
|
|
157
|
+
width_ratios=[3.5, 1.0, cbar_pad, 0.15],
|
|
158
|
+
height_ratios=[1.0, 3.5]
|
|
159
|
+
)
|
|
160
|
+
gs.update(wspace=spacing, hspace=spacing)
|
|
161
|
+
|
|
162
|
+
ax1 = fig.add_subplot(gs[1, 0])
|
|
163
|
+
ax_t = fig.add_subplot(gs[0, 0], sharex=ax1)
|
|
164
|
+
ax_r = fig.add_subplot(gs[1, 1], sharey=ax1)
|
|
165
|
+
cax = fig.add_subplot(gs[1, 3])
|
|
166
|
+
cbar_orient = "vertical"
|
|
167
|
+
|
|
168
|
+
elif colorbar_loc == "bottom":
|
|
169
|
+
fig = plt.figure(figsize=(7.5, 8.2))
|
|
170
|
+
gs = gridspec.GridSpec(
|
|
171
|
+
4, 2,
|
|
172
|
+
width_ratios=[3.5, 1.0],
|
|
173
|
+
height_ratios=[1.0, 3.5, cbar_pad, 0.15]
|
|
174
|
+
)
|
|
175
|
+
gs.update(wspace=spacing, hspace=spacing)
|
|
176
|
+
|
|
177
|
+
ax1 = fig.add_subplot(gs[1, 0])
|
|
178
|
+
ax_t = fig.add_subplot(gs[0, 0], sharex=ax1)
|
|
179
|
+
ax_r = fig.add_subplot(gs[1, 1], sharey=ax1)
|
|
180
|
+
cax = fig.add_subplot(gs[3, 0])
|
|
181
|
+
cbar_orient = "horizontal"
|
|
182
|
+
|
|
183
|
+
else:
|
|
184
|
+
fig = plt.figure(figsize=(7.5, 7.5))
|
|
185
|
+
gs = gridspec.GridSpec(2, 2, width_ratios=[3.5, 1.0], height_ratios=[1.0, 3.5])
|
|
186
|
+
gs.update(wspace=spacing, hspace=spacing)
|
|
187
|
+
ax1 = fig.add_subplot(gs[1, 0])
|
|
188
|
+
ax_t = fig.add_subplot(gs[0, 0], sharex=ax1)
|
|
189
|
+
ax_r = fig.add_subplot(gs[1, 1], sharey=ax1)
|
|
190
|
+
cax = None
|
|
191
|
+
|
|
192
|
+
# ── 4. Rendering Mappa con salvataggio oggetto ScalarMappable ─────────────
|
|
193
|
+
im = ax1.pcolormesh(
|
|
194
|
+
x_smooth, y_smooth, dz_smooth,
|
|
195
|
+
shading='gouraud', cmap=cmap, vmax=25, rasterized=True
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# 4. Mappa 2D e contorni
|
|
199
|
+
ax1.pcolormesh(x_smooth, y_smooth, dz_smooth, shading='gouraud', cmap=cmap, vmax=25, rasterized=True)
|
|
200
|
+
|
|
201
|
+
cs_2d = ax1.contour(
|
|
202
|
+
x_smooth, y_smooth, dz_smooth,
|
|
203
|
+
levels=[2.30, 6.18, 11.83],
|
|
204
|
+
colors=['white', 'white', 'white'],
|
|
205
|
+
linewidths=[1.6, 1.2, 0.9],
|
|
206
|
+
linestyles=['-', '--', ':']
|
|
207
|
+
)
|
|
208
|
+
cs_1d = ax1.contour(
|
|
209
|
+
x_smooth, y_smooth, dz_smooth,
|
|
210
|
+
levels=[1.00],
|
|
211
|
+
colors=["#000000"],
|
|
212
|
+
linewidths=[1.1],
|
|
213
|
+
linestyles=['-.']
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
if show_clabels:
|
|
217
|
+
labels_dict = {2.30: r'$1\sigma_{2D}$', 6.18: r'$2\sigma_{2D}$', 11.83: r'$3\sigma_{2D}$', 1.00: r'$1\sigma_{1D}$'}
|
|
218
|
+
fmt_clabel = lambda v: labels_dict.get(round(v, 2), f"{v:.1f}")
|
|
219
|
+
clabel_opts = dict(
|
|
220
|
+
inline=True,
|
|
221
|
+
fontsize=clabel_fontsize,
|
|
222
|
+
fmt=fmt_clabel,
|
|
223
|
+
manual=manual_clabels,
|
|
224
|
+
inline_spacing=5,
|
|
225
|
+
colors="black",
|
|
226
|
+
)
|
|
227
|
+
halo = [patheffects.withStroke(linewidth=2.5, foreground="white")]
|
|
228
|
+
clbl_2d = ax1.clabel(cs_2d, **clabel_opts)
|
|
229
|
+
plt.setp(clbl_2d, path_effects=halo)
|
|
230
|
+
|
|
231
|
+
clbl_1d = ax1.clabel(cs_1d, **clabel_opts)
|
|
232
|
+
plt.setp(clbl_1d, path_effects=halo)
|
|
233
|
+
|
|
234
|
+
# 5. Profili 1D marginali Minos
|
|
235
|
+
ax1.plot(x_val, y_val, marker='+', markersize=10, color='red', mew=2.0, zorder=5)
|
|
236
|
+
|
|
237
|
+
for p_name, ax_proj, is_x in [(param_x, ax_t, True), (param_y, ax_r, False)]:
|
|
238
|
+
if p_name in m.merrors:
|
|
239
|
+
v_low = m.values[p_name] + m.merrors[p_name].lower
|
|
240
|
+
v_up = m.values[p_name] + m.merrors[p_name].upper
|
|
241
|
+
else:
|
|
242
|
+
v_low = m.values[p_name] - m.errors[p_name]
|
|
243
|
+
v_up = m.values[p_name] + m.errors[p_name]
|
|
244
|
+
|
|
245
|
+
for v in [v_low, v_up]:
|
|
246
|
+
if is_x:
|
|
247
|
+
ax1.axvline(v, color="red", ls='--', lw=1.2, alpha=1, zorder=1)
|
|
248
|
+
ax_t.axvline(v, color="red", ls='--', lw=1.2, alpha=1, zorder=1)
|
|
249
|
+
else:
|
|
250
|
+
ax1.axhline(v, color="red", ls='--', lw=1.2, alpha=1, zorder=1)
|
|
251
|
+
ax_r.axhline(v, color="red", ls='--', lw=1.2, alpha=1, zorder=1)
|
|
252
|
+
|
|
253
|
+
ax_t.axhline(1.0, color='green', ls='--', lw=1.2)
|
|
254
|
+
ax_r.axvline(1.0, color='green', ls='--', lw=1.2)
|
|
255
|
+
|
|
256
|
+
xp, yp = m.mnprofile(param_x, size=100, bound=(x_min, x_max))[:2]
|
|
257
|
+
ax_t.plot(xp, yp - global_fval, color="black", lw=1.6)
|
|
258
|
+
|
|
259
|
+
xp2, yp2 = m.mnprofile(param_y, size=100, bound=(y_min, y_max))[:2]
|
|
260
|
+
ax_r.plot(yp2 - global_fval, xp2, color="black", lw=1.6)
|
|
261
|
+
|
|
262
|
+
if cax is not None:
|
|
263
|
+
cbar = fig.colorbar(im, cax=cax, orientation=cbar_orient)
|
|
264
|
+
cbar.set_label(r'$\Delta\chi^2$', fontsize=typo_params["labelsize"])
|
|
265
|
+
cbar.ax.tick_params(direction='inout', length=5, width=0.8)
|
|
266
|
+
|
|
267
|
+
# Tacche discrete sulla colorbar coerenti con i livelli significativi
|
|
268
|
+
cbar.set_ticks([0, 2.30, 6.18, 11.83, 20])
|
|
269
|
+
cbar.set_ticklabels(['0', r'$1\sigma_{2D}$', r'$2\sigma_{2D}$', r'$3\sigma_{2D}$', '20'])
|
|
270
|
+
|
|
271
|
+
# 6. Formattazione assi
|
|
272
|
+
def _sci_label(ax, vals, nome, axis_letter):
|
|
273
|
+
base = param_info[nome].get('label', nome) if (param_info and nome in param_info) else nome
|
|
274
|
+
unit = param_info[nome].get('unit', '') if (param_info and nome in param_info) else ''
|
|
275
|
+
amax = np.max(np.abs(vals))
|
|
276
|
+
esp = int(math.floor(math.log10(amax))) if amax > 0 else 0
|
|
277
|
+
scale = 10**esp
|
|
278
|
+
suffix = (rf"\, [$\times 10^{{{esp}}}$ {unit}]" if esp != 0 else (f"\, [{unit}]" if unit else ""))
|
|
279
|
+
fmt = ticker.FuncFormatter(lambda v, _: f"{v / scale:.2f}")
|
|
280
|
+
getattr(ax, f"{axis_letter}axis").set_major_formatter(fmt)
|
|
281
|
+
return f"{base}{suffix}"
|
|
282
|
+
|
|
283
|
+
ax1.set_xlabel(_sci_label(ax1, [x_min, x_max], param_x, 'x'))
|
|
284
|
+
ax1.set_ylabel(_sci_label(ax1, [y_min, y_max], param_y, 'y'))
|
|
285
|
+
ax_t.set_ylabel(r"$\Delta\chi^2$")
|
|
286
|
+
ax_r.set_xlabel(r"$\Delta\chi^2$")
|
|
287
|
+
|
|
288
|
+
ax1.set_xlim(x_min, x_max)
|
|
289
|
+
ax1.set_ylim(y_min, y_max)
|
|
290
|
+
ax_t.set_ylim(0, 5.5)
|
|
291
|
+
ax_r.set_xlim(0, 5.5)
|
|
292
|
+
|
|
293
|
+
ax1.tick_params(axis='both', which='both', labelbottom=True, labelleft=True, right=True, top=True)
|
|
294
|
+
ax_t.tick_params(axis='x', bottom=True, labelbottom=False)
|
|
295
|
+
ax_r.tick_params(axis='y', left=True, labelleft=False)
|
|
296
|
+
ax_t.set_yticks([0, 1, 2, 4])
|
|
297
|
+
ax_r.set_xticks([0, 1, 2, 4])
|
|
298
|
+
|
|
299
|
+
for ax_m in [ax_t, ax_r]:
|
|
300
|
+
ax_m.grid(True, ls=':', color='gray', alpha=0.5)
|
|
301
|
+
|
|
302
|
+
for ax in [ax1, ax_t, ax_r]:
|
|
303
|
+
ax.tick_params(
|
|
304
|
+
axis='both',
|
|
305
|
+
which='both',
|
|
306
|
+
direction='inout',
|
|
307
|
+
length=8,
|
|
308
|
+
width=1,
|
|
309
|
+
color='black'
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
# 7. Box Statistiche
|
|
313
|
+
if show_stats_box:
|
|
314
|
+
lx = param_info[param_x]['label'].replace('$', '') if (param_info and param_x in param_info) else param_x
|
|
315
|
+
ly = param_info[param_y]['label'].replace('$', '') if (param_info and param_y in param_info) else param_y
|
|
316
|
+
cov_val = m.covariance[param_x, param_y]
|
|
317
|
+
corr_val = m.covariance.correlation()[param_x, param_y]
|
|
318
|
+
|
|
319
|
+
def _fmt_sci(val: float) -> Tuple[str, int]:
|
|
320
|
+
if val == 0:
|
|
321
|
+
return '0', 0
|
|
322
|
+
e = int(math.floor(math.log10(abs(val))))
|
|
323
|
+
return f"{val / 10**e:.2g}", e
|
|
324
|
+
|
|
325
|
+
cv_s, cv_e = _fmt_sci(cov_val)
|
|
326
|
+
cov_tex = rf"{cv_s} \times 10^{{{cv_e}}}" if cv_e != 0 else cv_s
|
|
327
|
+
box_kw = dict(boxstyle='square,pad=0.3', facecolor='white', alpha=1, edgecolor='black', lw=0.6)
|
|
328
|
+
|
|
329
|
+
if stats_box_loc == "inside_map":
|
|
330
|
+
txt = rf"$\mathrm{{Cov}}({lx}, {ly}) = {cov_tex}$" + "\n" + rf"$\rho_{{{lx},{ly}}} = {corr_val:+.3f}$"
|
|
331
|
+
ax1.text(0.96, 0.96, txt, transform=ax1.transAxes, fontsize=typo_params["textsize"], ha='right', va='top', bbox=box_kw, zorder=10)
|
|
332
|
+
elif stats_box_loc == "top":
|
|
333
|
+
txt = rf"$\mathrm{{Cov}}({lx}, {ly}) = {cov_tex}\quad\rho_{{{lx},{ly}}} = {corr_val:+.3f}$"
|
|
334
|
+
ax_t.text(0.5, 1.15, txt, transform=ax_t.transAxes, fontsize=typo_params["textsize"], ha='center', va='bottom', bbox=box_kw)
|
|
335
|
+
elif stats_box_loc == "custom" and stats_box_pos is not None:
|
|
336
|
+
txt = rf"$\mathrm{{Cov}}({lx}, {ly}) = {cov_tex}$" + "\n" + rf"$\rho_{{{lx},{ly}}} = {corr_val:+.3f}$"
|
|
337
|
+
fig.text(stats_box_pos[0], stats_box_pos[1], txt, fontsize=typo_params["textsize"], bbox=box_kw)
|
|
338
|
+
|
|
339
|
+
fig.tight_layout(pad=0.5)
|
|
340
|
+
|
|
341
|
+
if save_path:
|
|
342
|
+
fig.savefig(save_path, dpi=300, bbox_inches='tight')
|
|
343
|
+
print(f"Mappa salvata con successo in: {save_path}")
|
|
344
|
+
|
|
345
|
+
if show:
|
|
346
|
+
plt.show()
|
|
347
|
+
|
|
348
|
+
return fig, (ax1, ax_t, ax_r)
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
352
|
+
# Test del flusso completo (Generazione Dati -> Fit ODR Metodo A -> Mappa 2D)
|
|
353
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
354
|
+
if __name__ == "__main__":
|
|
355
|
+
|
|
356
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
357
|
+
# ESEMPIO 10: Mappa 2D del chi quadro
|
|
358
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
359
|
+
|
|
360
|
+
# 1. Definizione del modello fisico
|
|
361
|
+
def modello_lineare(x, m, q):
|
|
362
|
+
return m * x + q
|
|
363
|
+
|
|
364
|
+
# 2. Generazione dati sintetici con errori su x e su y
|
|
365
|
+
rng = np.random.default_rng(123)
|
|
366
|
+
m_vero, q_vero = 2.45, 1.10
|
|
367
|
+
n_punti = 35
|
|
368
|
+
|
|
369
|
+
x_pulito = np.linspace(1.0, 10.0, n_punti)
|
|
370
|
+
y_pulito = modello_lineare(x_pulito, m_vero, q_vero)
|
|
371
|
+
|
|
372
|
+
err_x = np.full(n_punti, 0.20)
|
|
373
|
+
err_y = np.full(n_punti, 0.45)
|
|
374
|
+
|
|
375
|
+
x_misurato = x_pulito + rng.normal(0, err_x, n_punti)
|
|
376
|
+
y_misurato = y_pulito + rng.normal(0, err_y, n_punti)
|
|
377
|
+
|
|
378
|
+
# 3. Esecuzione FitPipeline con Metodo A (odr_lin_Newton=True)
|
|
379
|
+
pipeline = FitPipeline(
|
|
380
|
+
model=modello_lineare,
|
|
381
|
+
param_names=["m", "q"],
|
|
382
|
+
p0=[1.0, 0.0],
|
|
383
|
+
run_minos=True,
|
|
384
|
+
verbose=True,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
risultati_fit = pipeline.fit(
|
|
388
|
+
x=x_misurato,
|
|
389
|
+
y=y_misurato,
|
|
390
|
+
err_x=err_x,
|
|
391
|
+
err_y=err_y,
|
|
392
|
+
odr_lin_Newton=True, # Attiva il Metodo A privo di bias a varianza congelata
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# 4. Generazione Mappa Delta-Chi2 con layout flessibile
|
|
396
|
+
info_parametri = {
|
|
397
|
+
"m": {"label": r"$m$", "unit": r"V/s"},
|
|
398
|
+
"q": {"label": r"$q$", "unit": r"V"},
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
current_dir = Path(__file__).resolve().parent
|
|
402
|
+
save_folder = current_dir / "examples"
|
|
403
|
+
save_folder.mkdir(parents=True, exist_ok=True) # Crea la cartella se non esiste
|
|
404
|
+
|
|
405
|
+
print("\nGenerazione mappa 2D in corso...")
|
|
406
|
+
fig, axes = chi2_map_pipeline(
|
|
407
|
+
risultati=risultati_fit,
|
|
408
|
+
param_x="m",
|
|
409
|
+
param_y="q",
|
|
410
|
+
param_info=info_parametri,
|
|
411
|
+
n_sigma=3.2,
|
|
412
|
+
grid_size=30,
|
|
413
|
+
stats_box_loc="custom",
|
|
414
|
+
stats_box_pos= (0.725,0.85),
|
|
415
|
+
show_clabels=True,
|
|
416
|
+
manual_clabels=False,
|
|
417
|
+
save_path=save_folder / "example_chi_squared_2d_map.pdf",
|
|
418
|
+
show=True,
|
|
419
|
+
cmap="viridis_r",
|
|
420
|
+
spacing=0,
|
|
421
|
+
)
|