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.
@@ -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).
@@ -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,9 @@
1
+ """
2
+ lab_physics: Toolkit per fit chi-quadro, mappe 2D e grafica scientifica.
3
+ """
4
+
5
+ from .chi_squared_fit import *
6
+ from .chi_squared_2d_map import *
7
+ from .plot_typo import *
8
+
9
+ __version__ = "0.1.0"
@@ -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
+ )