mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-11-14 16:03:54 +01:00
Feature/minuit2 wrapper (#279)
## Unified Minuit2 fitting framework with FitModel API ### Models (`Models.hpp`) Consolidate all model structs (Gaussian, RisingScurve, FallingScurve) into a single header. Each model provides: `eval`, `eval_and_grad`, `is_valid`, `estimate_par`, `compute_steps`, and `param_info` metadata. No Minuit2 dependency. ### Chi2 functors (`Chi2.hpp`) Generic `Chi2Model1DGrad` (analytic gradient) templated on the model struct. Replaces the separate Chi2Gaussian, Chi2GaussianGradient, Chi2Scurves, and Chi2ScurvesGradient headers. ### FitModel (`FitModel.hpp`) Configuration object wrapping `MnUserParameters`, strategy, tolerance, and user-override tracking. User constraints (fixed parameters, start values, limits) always take precedence over automatic data-driven estimates. ### Fit functions (`Fit.hpp`) - `fit_pixel<Model, FCN>(model, x, y, y_err)` -> single-pixel, self-contained - `fit_pixel<Model, FCN>(model, upar_local, x, y, y_err)` -> pre-cloned upar for hot loops - `fit_3d<Model, FCN>(model, x, y, y_err, ..., n_threads)` -> row-parallel over pixel grid ### Python bindings - `Pol1`, `Pol2`, `Gaussian`, `RisingScurve`, `FallingScurve` model classes with `FixParameter`, `SetParLimits`, `SetParameter`, and properties for `max_calls`, `tolerance`, `compute_errors` - Single `fit(model, x, y, y_err, n_threads)` dispatch replacing the old `fit_gaus_minuit`, `fit_gaus_minuit_grad`, `fit_scurve_minuit_grad`, etc. ### Benchmarks - Updated `fit_benchmark.cpp` (Google Benchmark) to use the new FitModel API - Jupyter notebooks for 1D and 3D S-curve fitting (lmfit vs Minuit2 analytic) - ~1.8x speedup over lmfit, near-linear thread scaling up to physical core count --------- Co-authored-by: Erik Fröjdh <erik.frojdh@psi.ch>
This commit is contained in:
1 parent
05e6e69c66
commit
a6afa45b3b
19 files changed
+3356
-197
No files matched your search
@@ -26,6 +26,10 @@ set_target_properties(_aare PROPERTIES
|
||||
)
|
||||
target_link_libraries(_aare PRIVATE aare_core aare_compiler_flags)
|
||||
|
||||
target_include_directories(_aare SYSTEM PRIVATE
|
||||
$<TARGET_PROPERTY:Minuit2::Minuit2,INTERFACE_INCLUDE_DIRECTORIES>
|
||||
)
|
||||
|
||||
# List of python files to be copied to the build directory
|
||||
set( PYTHON_FILES
|
||||
aare/__init__.py
|
||||
|
||||
@@ -17,7 +17,8 @@ from .ClusterFinder import ClusterFinder, ClusterCollector, ClusterFinderMT, Clu
|
||||
from .ClusterVector import ClusterVector
|
||||
from .Cluster import Cluster
|
||||
|
||||
|
||||
from ._aare import Gaussian, RisingScurve, FallingScurve, Pol1, Pol2
|
||||
from ._aare import fit
|
||||
from ._aare import fit_gaus, fit_pol1, fit_scurve, fit_scurve2
|
||||
from ._aare import Interpolator
|
||||
from ._aare import calculate_eta2, calculate_eta3, calculate_cross_eta3, calculate_full_eta2
|
||||
|
||||
+44
-18
@@ -1,8 +1,11 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import sys
|
||||
sys.path.insert(0, '/home/kferjaoui/sw/aare/build')
|
||||
from aare import fit_gaus, fit_pol1
|
||||
from aare import gaus, pol1
|
||||
from aare import Gaussian, fit
|
||||
from aare import pol1
|
||||
|
||||
textpm = f"±" #
|
||||
textmu = f"μ" #
|
||||
@@ -15,35 +18,57 @@ textsigma = f"σ" #
|
||||
mu = np.random.uniform(1, 100) # Mean of Gaussian
|
||||
sigma = np.random.uniform(4, 20) # Standard deviation
|
||||
num_points = 10000 # Number of points for smooth distribution
|
||||
noise_sigma = 100
|
||||
# noise_sigma = 10
|
||||
|
||||
# Generate Gaussian distribution
|
||||
data = np.random.normal(mu, sigma, num_points)
|
||||
counts, edges = np.histogram(data, bins=100)
|
||||
|
||||
# Generate errors for each point
|
||||
errors = np.abs(np.random.normal(0, sigma, num_points)) # Errors with mean 0, std 0.5
|
||||
x = 0.5 * (edges[:-1] + edges[1:]) # proper bin centers
|
||||
y = counts.astype(np.float64)
|
||||
|
||||
# Poisson noise
|
||||
yerr = np.sqrt(np.maximum(y, 1))
|
||||
# yerr = np.abs(np.random.normal(0, noise_sigma, len(x)))
|
||||
|
||||
# Create subplot
|
||||
fig0, ax0 = plt.subplots(1, 1, num=0, figsize=(12, 8))
|
||||
|
||||
x = np.histogram(data, bins=30)[1][:-1] + 0.05
|
||||
y = np.histogram(data, bins=30)[0]
|
||||
yerr = errors[:30]
|
||||
|
||||
|
||||
# Add the errors as error bars in the step plot
|
||||
ax0.errorbar(x, y, yerr=yerr, fmt=". ", capsize=5)
|
||||
ax0.grid()
|
||||
|
||||
par, err = fit_gaus(x, y, yerr)
|
||||
print(par, err)
|
||||
# Fit with lmfit
|
||||
result_lm = fit_gaus(x, y, yerr)
|
||||
par_lm = result_lm["par"]
|
||||
err_lm = result_lm["par_err"]
|
||||
chi2_lm = result_lm["chi2"]
|
||||
print("[lmfit] fit_gaus: ", par_lm, err_lm, chi2_lm)
|
||||
|
||||
# Fit with Minuit2 + analytic gradient + Hesse errors
|
||||
gaussian = Gaussian()
|
||||
gaussian.compute_errors = True
|
||||
result_m2 = gaussian.fit(x, y, yerr)
|
||||
par_m2 = result_m2['par']
|
||||
err_m2 = result_m2['par_err']
|
||||
chi2_m2 = result_m2['chi2']
|
||||
print(f"[minuit2] gaussian.fit: par={par_m2}, err={err_m2}, chi2={chi2_m2}")
|
||||
|
||||
x = np.linspace(x[0], x[-1], 1000)
|
||||
ax0.plot(x, gaus(x, par), marker="")
|
||||
ax0.set(xlabel="x", ylabel="Counts", title=f"A0 = {par[0]:0.2f}{textpm}{err[0]:0.2f}\n"
|
||||
f"{textmu} = {par[1]:0.2f}{textpm}{err[1]:0.2f}\n"
|
||||
f"{textsigma} = {par[2]:0.2f}{textpm}{err[2]:0.2f}\n"
|
||||
f"(init: {textmu}: {mu:0.2f}, {textsigma}: {sigma:0.2f})")
|
||||
ax0.plot(x, gaussian(x, par_lm), marker="", label="fit_gaus")
|
||||
ax0.plot(x, gaussian(x, par_m2), marker="", linestyle=":", label="fit_gaus_minuit_grad")
|
||||
ax0.legend()
|
||||
ax0.set(xlabel="x", ylabel="Counts",
|
||||
title=(
|
||||
f"fit_gaus: A={par_lm[0]:0.2f}{textpm}{err_lm[0]:0.2f} "
|
||||
f"{textmu}={par_lm[1]:0.2f}{textpm}{err_lm[1]:0.2f} "
|
||||
f"{textsigma}={par_lm[2]:0.2f}{textpm}{err_lm[2]:0.2f}\n"
|
||||
f"minuit_grad: A={par_m2[0]:0.2f}{textpm}{err_m2[0]:0.2f} "
|
||||
f"{textmu}={par_m2[1]:0.2f}{textpm}{err_m2[1]:0.2f} "
|
||||
f"{textsigma}={par_m2[2]:0.2f}{textpm}{err_m2[2]:0.2f}\n"
|
||||
f"(truth: {textmu}={mu:0.2f}, {textsigma}={sigma:0.2f})"
|
||||
),
|
||||
)
|
||||
fig0.tight_layout()
|
||||
|
||||
|
||||
@@ -66,8 +91,9 @@ y_values = slope * x_values + intercept + var_points
|
||||
|
||||
fig1, ax1 = plt.subplots(1, 1, num=1, figsize=(12, 8))
|
||||
ax1.errorbar(x_values, y_values, yerr=errors, fmt=". ", capsize=5)
|
||||
par, err = fit_pol1(x_values, y_values, errors)
|
||||
|
||||
result_pol = fit_pol1(x_values, y_values, errors)
|
||||
par = result_pol["par"]
|
||||
err = result_pol["par_err"]
|
||||
|
||||
x = np.linspace(np.min(x_values), np.max(x_values), 1000)
|
||||
ax1.plot(x, pol1(x, par), marker="")
|
||||
|
||||
+341
-60
@@ -6,10 +6,204 @@
|
||||
#include <pybind11/stl_bind.h>
|
||||
|
||||
#include "aare/Fit.hpp"
|
||||
#include "aare/FitModel.hpp"
|
||||
#include "aare/Models.hpp"
|
||||
#include "aare/Chi2.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
using namespace pybind11::literals;
|
||||
|
||||
template <typename Model, typename FCN>
|
||||
py::object fit_dispatch(
|
||||
const aare::FitModel<Model>& model,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::object y_err_obj,
|
||||
int n_threads);
|
||||
|
||||
template <typename Model>
|
||||
void bind_fit_model(py::module& m, const char* name) {
|
||||
using FM = aare::FitModel<Model>;
|
||||
using FCN = aare::func::Chi2Model1DGrad<Model>;
|
||||
py::class_<FM>(m, name)
|
||||
.def(py::init<unsigned int, unsigned int, double, bool>(),
|
||||
py::arg("strategy") = 0,
|
||||
py::arg("max_calls") = 100,
|
||||
py::arg("tolerance") = 0.5,
|
||||
py::arg("compute_errors") = false)
|
||||
.def("SetParLimits", &FM::SetParLimits, py::arg("idx"), py::arg("lo"), py::arg("hi"))
|
||||
.def("FixParameter", &FM::FixParameter, py::arg("idx"), py::arg("val"))
|
||||
.def("ReleaseParameter", &FM::ReleaseParameter, py::arg("idx"))
|
||||
.def("SetParameter", &FM::SetParameter, py::arg("idx"), py::arg("val"))
|
||||
.def_property("max_calls", &FM::max_calls, &FM::SetMaxCalls)
|
||||
.def_property("tolerance", &FM::tolerance, &FM::SetTolerance)
|
||||
.def_property("compute_errors", &FM::compute_errors, &FM::SetComputeErrors)
|
||||
.def("__call__",
|
||||
[](const FM& /*self*/,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> par)
|
||||
{
|
||||
auto x_view = make_view_1d(x);
|
||||
auto p_view = make_view_1d(par);
|
||||
|
||||
std::vector<double> pvec(p_view.begin(), p_view.end());
|
||||
|
||||
auto* result = new aare::NDArray<double, 1>({x_view.size()});
|
||||
for (ssize_t i = 0; i < x_view.size(); ++i)
|
||||
(*result)(i) = Model::eval(x_view[i], pvec);
|
||||
|
||||
return return_image_data(result);
|
||||
},
|
||||
py::arg("x"), py::arg("par"))
|
||||
.def("fit",
|
||||
[](const FM& self,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::object y_err_obj,
|
||||
int n_threads) -> py::object
|
||||
{
|
||||
return fit_dispatch<Model, FCN>(self, x, y, y_err_obj, n_threads);
|
||||
},
|
||||
R"doc(
|
||||
Fit this model to 1D or 3D data using Minuit2.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like, shape (n_scan,)
|
||||
Scan points.
|
||||
y : array_like, shape (n_scan,) or (rows, cols, n_scan)
|
||||
Measured data.
|
||||
y_err : array_like or None
|
||||
Per-point uncertainties. None for unweighted fit.
|
||||
n_threads : int
|
||||
Number of threads for 3D parallel loop.
|
||||
)doc",
|
||||
py::arg("x"),
|
||||
py::arg("y"),
|
||||
py::arg("y_err") = py::none(),
|
||||
py::arg("n_threads") = 4);
|
||||
}
|
||||
|
||||
template <typename Model>
|
||||
py::dict pack_1d_result_dict(const aare::NDArray<double, 1>& result,
|
||||
bool compute_errors)
|
||||
{
|
||||
constexpr std::size_t npar = Model::npar;
|
||||
|
||||
auto res = result.view();
|
||||
|
||||
auto par_out = new NDArray<double, 1>({npar}, 0.0);
|
||||
auto chi2_out = new NDArray<double, 1>({1}, 0.0);
|
||||
|
||||
auto par_view = par_out->view();
|
||||
auto chi2_view = chi2_out->view();
|
||||
|
||||
for (std::size_t i = 0; i < npar; ++i) {
|
||||
par_view(i) = res(i);
|
||||
}
|
||||
|
||||
if (compute_errors) {
|
||||
auto err_out = new NDArray<double, 1>({npar}, 0.0);
|
||||
auto err_view = err_out->view();
|
||||
|
||||
for (std::size_t i = 0; i < npar; ++i) {
|
||||
err_view(i) = res(npar + i);
|
||||
}
|
||||
|
||||
chi2_view(0) = res(2 * npar);
|
||||
|
||||
return py::dict(
|
||||
"par"_a = return_image_data(par_out),
|
||||
"par_err"_a = return_image_data(err_out),
|
||||
"chi2"_a = return_image_data(chi2_out));
|
||||
} else {
|
||||
chi2_view(0) = res(npar);
|
||||
|
||||
return py::dict(
|
||||
"par"_a = return_image_data(par_out),
|
||||
"chi2"_a = return_image_data(chi2_out));
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: typed dispatch for one Model, handles 1D/3D + y_err logic
|
||||
template <typename Model, typename FCN>
|
||||
py::object fit_dispatch(
|
||||
const aare::FitModel<Model>& model,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::object y_err_obj,
|
||||
int n_threads)
|
||||
{
|
||||
constexpr std::size_t npar = Model::npar;
|
||||
|
||||
if (y.ndim() == 3) {
|
||||
auto par_out = new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
|
||||
auto chi2_out= new NDArray<double, 2>({y.shape(0), y.shape(1)}, 0.0);
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
|
||||
if (!y_err_obj.is_none()) {
|
||||
auto y_err = py::cast<py::array_t<double,
|
||||
py::array::c_style | py::array::forcecast>>(y_err_obj);
|
||||
|
||||
if (y_err.ndim() != 3) {
|
||||
throw std::runtime_error("For 3D input y, y_err must also be 3D.");
|
||||
}
|
||||
|
||||
auto err_out = new NDArray<double, 3>({y.shape(0), y.shape(1), npar}, 0.0);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
|
||||
aare::fit_3d<Model, FCN>(model, x_view, y_view, y_view_err,
|
||||
par_out->view(), err_out->view(), chi2_out->view(), n_threads);
|
||||
|
||||
if (model.compute_errors()) {
|
||||
return py::dict("par"_a = return_image_data(par_out),
|
||||
"par_err"_a = return_image_data(err_out),
|
||||
"chi2"_a = return_image_data(chi2_out));
|
||||
} else {
|
||||
delete err_out;
|
||||
return py::dict("par"_a = return_image_data(par_out),
|
||||
"chi2"_a = return_image_data(chi2_out));
|
||||
}
|
||||
} else {
|
||||
|
||||
NDView<double, 3> dummy_err{};
|
||||
NDView<double, 3> dummy_err_out{};
|
||||
|
||||
aare::fit_3d<Model, FCN>(model, x_view, y_view, dummy_err,
|
||||
par_out->view(), dummy_err_out, chi2_out->view(), n_threads);
|
||||
|
||||
return py::dict("par"_a = return_image_data(par_out),
|
||||
"chi2"_a = return_image_data(chi2_out));
|
||||
}
|
||||
} else if (y.ndim() == 1) {
|
||||
NDArray<double, 1> result{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
|
||||
if (!y_err_obj.is_none()) {
|
||||
auto y_err = py::cast<py::array_t<double,
|
||||
py::array::c_style | py::array::forcecast>>(y_err_obj);
|
||||
|
||||
if (y_err.ndim() != 1) {
|
||||
throw std::runtime_error("For 1D input y, y_err must also be 1D.");
|
||||
}
|
||||
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
result = aare::fit_pixel<Model, FCN>(model, x_view, y_view, y_view_err);
|
||||
} else {
|
||||
result = aare::fit_pixel<Model, FCN>(model, x_view, y_view);
|
||||
}
|
||||
|
||||
return pack_1d_result_dict<Model>(result, model.compute_errors());
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D.");
|
||||
}
|
||||
}
|
||||
|
||||
void define_fit_bindings(py::module &m) {
|
||||
|
||||
// TODO! Evaluate without converting to double
|
||||
@@ -121,18 +315,17 @@ void define_fit_bindings(py::module &m) {
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D Gaussian to data.
|
||||
|
||||
Fit a 1D Gaussian to data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
@@ -187,22 +380,22 @@ n_threads : int, optional
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D Gaussian to data with error estimates.
|
||||
|
||||
Fit a 1D Gaussian to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
|
||||
m.def(
|
||||
"fit_pol1",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
@@ -273,19 +466,19 @@ n_threads : int, optional
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
//=========
|
||||
@@ -359,21 +552,22 @@ n_threads : int, optional
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
|
||||
m.def(
|
||||
"fit_scurve2",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
@@ -444,18 +638,105 @@ n_threads : int, optional
|
||||
}
|
||||
},
|
||||
R"(
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
Fit a 1D polynomial to data with error estimates.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The x values.
|
||||
y : array_like
|
||||
The y values.
|
||||
y_err : array_like
|
||||
The error in the y values.
|
||||
n_threads : int, optional
|
||||
The number of threads to use. Default is 4.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
|
||||
// ── Bind model classes ──────────────────────────────────────────
|
||||
bind_fit_model<aare::model::Gaussian>(m, "Gaussian");
|
||||
bind_fit_model<aare::model::RisingScurve>(m, "RisingScurve");
|
||||
bind_fit_model<aare::model::FallingScurve>(m, "FallingScurve");
|
||||
bind_fit_model<aare::model::Pol1>(m, "Pol1");
|
||||
bind_fit_model<aare::model::Pol2>(m, "Pol2");
|
||||
|
||||
m.def("fit",
|
||||
[](py::object model_obj,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
py::object y_err_obj,
|
||||
int n_threads) -> py::object
|
||||
{
|
||||
using namespace aare::model;
|
||||
using namespace aare::func;
|
||||
|
||||
// ── Polynomial of degree 1 ───────
|
||||
if(py::isinstance< aare::FitModel<Pol1> >(model_obj)) {
|
||||
const auto& mdl = model_obj.cast< const aare::FitModel<Pol1>& >();
|
||||
return fit_dispatch<Pol1, Chi2Pol1>(mdl, x, y, y_err_obj, n_threads);
|
||||
}
|
||||
|
||||
// ── Polynomial of degree 2 ───────
|
||||
if(py::isinstance< aare::FitModel<Pol2> >(model_obj)) {
|
||||
const auto& mdl = model_obj.cast< const aare::FitModel<Pol2>& >();
|
||||
return fit_dispatch<Pol2, Chi2Pol2>(mdl, x, y, y_err_obj, n_threads);
|
||||
}
|
||||
// ── Gaussian ───────
|
||||
if(py::isinstance< aare::FitModel<Gaussian> >(model_obj)) {
|
||||
const auto& mdl = model_obj.cast< const aare::FitModel<Gaussian>& >();
|
||||
return fit_dispatch<Gaussian, Chi2Gaussian>(mdl, x, y, y_err_obj, n_threads);
|
||||
}
|
||||
|
||||
// ── Rising Scurve ───────
|
||||
if(py::isinstance< aare::FitModel<RisingScurve> >(model_obj)) {
|
||||
const auto& mdl = model_obj.cast< const aare::FitModel<RisingScurve>& >();
|
||||
return fit_dispatch<RisingScurve, Chi2RisingScurve>(mdl, x, y, y_err_obj, n_threads);
|
||||
}
|
||||
|
||||
// ── Falling Scurve ───────
|
||||
if(py::isinstance< aare::FitModel<FallingScurve> >(model_obj)) {
|
||||
const auto& mdl = model_obj.cast< const aare::FitModel<FallingScurve>& >();
|
||||
return fit_dispatch<FallingScurve, Chi2FallingScurve>(mdl, x, y, y_err_obj, n_threads);
|
||||
}
|
||||
|
||||
throw std::runtime_error(
|
||||
"Unknown model type. Expected Pol1, Pol2, Gaussian, RisingScurve or FallingScurve."
|
||||
);
|
||||
},
|
||||
R"(
|
||||
Fit a model to 1D or 3D data using Minuit2.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
model : Pol1, Pol2, Gaussian, RisingScurve, or FallingScurve
|
||||
Configured model object. User-set limits, fixed parameters,
|
||||
and start values take precedence over automatic estimates.
|
||||
x : array_like, shape (n_scan,)
|
||||
Scan points (e.g. energy or threshold values).
|
||||
y : array_like, shape (n_scan,) or (rows, cols, n_scan)
|
||||
Measured data. 1D for a single pixel, 3D for a detector image.
|
||||
y_err : array_like or None
|
||||
Per-point uncertainties. Same shape as y. None → unweighted fit.
|
||||
n_threads : int
|
||||
Number of threads for the 3D parallel loop.
|
||||
|
||||
Returns
|
||||
-------
|
||||
For 1D input:
|
||||
numpy array of shape (2*npar+1,) if compute_errors else (npar+1,).
|
||||
Layout: [params..., (errors...,) chi2].
|
||||
|
||||
For 3D input:
|
||||
dict with keys:
|
||||
"par" : (rows, cols, npar) fitted parameters.
|
||||
"par_err" : (rows, cols, npar) parameter errors (if compute_errors).
|
||||
"chi2" : (rows, cols) chi-squared per pixel.
|
||||
)",
|
||||
py::arg("model"),
|
||||
py::arg("x"),
|
||||
py::arg("y"),
|
||||
py::arg("y_err") = py::none(),
|
||||
py::arg("n_threads") = 4
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,552 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib.gridspec import GridSpec\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, '/home/ferjao_k/sw/aare/build')\n",
|
||||
"from aare import fit_gaus # lmfit\n",
|
||||
"from aare import Gaussian, fit # minuit2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ROWS = 100\n",
|
||||
"COLS = 100\n",
|
||||
"N_SCAN = 100\n",
|
||||
"NOISE_FRAC = 0.05\n",
|
||||
"SEED = 42\n",
|
||||
"N_THREADS = 4\n",
|
||||
"\n",
|
||||
"N_REPEATS = 7\n",
|
||||
"N_WARMUP = 3 # untimed iterations (icache + branch predictor warmup)\n",
|
||||
"COOLDOWN = 2.0 # seconds between (method, thread_count) pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "be455445-7df8-47cc-9f39-d2a38856e0bd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Data generator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def generate_3d_data(rows, cols, n_scan, noise_frac, seed):\n",
|
||||
" \"\"\"\n",
|
||||
" Generate a synthetic detector image stack where each pixel has a\n",
|
||||
" Gaussian response curve with per-pixel variation in A, mu, sigma.\n",
|
||||
"\n",
|
||||
" Returns x (n_scan,), y (rows, cols, n_scan), y_err (rows, cols, n_scan),\n",
|
||||
" and the ground-truth parameter arrays.\n",
|
||||
" \"\"\"\n",
|
||||
" rng = np.random.default_rng(seed)\n",
|
||||
"\n",
|
||||
" # Per-pixel true params each of shape: [rows, cols, 1]\n",
|
||||
" A_true = rng.uniform(200, 1000, size=(rows, cols))\n",
|
||||
" mu_true = rng.uniform(20, 80, size=(rows, cols))\n",
|
||||
" sig_true = rng.uniform(3, 12, size=(rows, cols))\n",
|
||||
" \n",
|
||||
" # One common binned energy array\n",
|
||||
" x = np.linspace(0, 100, n_scan) # shape [1, 1, nscan]\n",
|
||||
"\n",
|
||||
" # Build ground truth signals per-pixel\n",
|
||||
" exponent = -0.5 * ((x[None, None, :] - mu_true[:, :, None]) / sig_true[:,:, None])**2 # shape [rows, cols, nscan]\n",
|
||||
" y_clean = A_true[:, :, None] * np.exp(exponent)\n",
|
||||
"\n",
|
||||
" # Perturb with noise\n",
|
||||
" noise_sigma = noise_frac * A_true[:, :, None] * np.ones_like(y_clean) # shape [rows, cols, nscan]\n",
|
||||
" noise = rng.normal(0, noise_sigma)\n",
|
||||
" y = y_clean + noise\n",
|
||||
"\n",
|
||||
" y_err = noise_sigma.copy()\n",
|
||||
"\n",
|
||||
" return x, y, y_err, A_true, mu_true, sig_true "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "30ed6bb8-4798-497f-9990-4c518f885855",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Profiling function"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_warmup=N_WARMUP, n_repeats=N_REPEATS):\n",
|
||||
" \"\"\"\n",
|
||||
" Warmup then time `fn` over `n_repeats` calls.\n",
|
||||
" Returns (last_result, list_of_walltimes_in_seconds).\n",
|
||||
" \"\"\"\n",
|
||||
" # warmup: primes icache, branch predictor, and lets CPU ramp to boost clock\n",
|
||||
" for _ in range(n_warmup):\n",
|
||||
" res = fn()\n",
|
||||
"\n",
|
||||
" times = []\n",
|
||||
" for _ in range(n_repeats):\n",
|
||||
" t0 = time.perf_counter()\n",
|
||||
" res = fn()\n",
|
||||
" t1 = time.perf_counter()\n",
|
||||
" times.append(t1 - t0)\n",
|
||||
" return res, times"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "377fc820-95b2-48aa-b104-a272c50e4103",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Quick check on small (2x2) frame"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "9cc999b9-e0d8-4deb-ae29-e8bd0141534b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"== True Gaussian params == \n",
|
||||
"A_true = \n",
|
||||
" [[819.16483884 551.1027518 ]\n",
|
||||
" [886.87833593 757.89442325]]\n",
|
||||
"mu_true = \n",
|
||||
" [[25.65064087 78.5373411 ]\n",
|
||||
" [65.66838212 67.16385832]]\n",
|
||||
"sig_true = \n",
|
||||
" [[ 4.15302269 7.05347344]\n",
|
||||
" [ 6.33718222 11.3408849 ]]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"== Fit results ==\n",
|
||||
"A_fit = \n",
|
||||
" [[812.09277132 559.04069721]\n",
|
||||
" [899.09335849 759.24481682]]\n",
|
||||
"mu_fit = \n",
|
||||
" [[25.6598209 78.40461782]\n",
|
||||
" [65.52261318 66.84540995]]\n",
|
||||
"sig_fit = \n",
|
||||
" [[ 4.2778026 7.041045 ]\n",
|
||||
" [ 6.29190225 11.34233504]]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Generate 2 x 2 dataset of Gaussian-like profiles for each pixel\n",
|
||||
"x2, y2, yerr2, true_A2, true_mu2, true_sig2 = generate_3d_data(\n",
|
||||
" 2, 2, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"model_g = Gaussian()\n",
|
||||
"model_g.compute_errors = True\n",
|
||||
"result = model_g.fit(x2, y2, yerr2)\n",
|
||||
"\n",
|
||||
"from pprint import pprint\n",
|
||||
"print(\"== True Gaussian params == \")\n",
|
||||
"print(\"A_true = \\n\", true_A2)\n",
|
||||
"print(\"mu_true = \\n\", true_mu2)\n",
|
||||
"print(\"sig_true = \\n\",true_sig2)\n",
|
||||
"print(\"\\n\")\n",
|
||||
"\n",
|
||||
"print(\"== Fit results ==\")\n",
|
||||
"par = result['par']\n",
|
||||
"# print(par)\n",
|
||||
"A_fit = par[:, :, 0]\n",
|
||||
"mu_fit = par[:, :, 1]\n",
|
||||
"sig_fit = par[:, :, 2]\n",
|
||||
"print(\"A_fit = \\n\", A_fit)\n",
|
||||
"print(\"mu_fit = \\n\", mu_fit)\n",
|
||||
"print(\"sig_fit = \\n\", sig_fit)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "a98a41f3-fd2e-4bfc-9ec6-0d23dd38e896",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAAKoCAYAAACIioc5AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOzdeXhU1fnA8e+dJZN9XyYJIYR9CTuKbAKyuKFVVGrVKtatorQo1tbaVlx+4C4tWK2WCkqRaivuyg6KgIR9XxPIQnayL7Pe3x+TGQlrQia5k+T9PM88D7lz5t73DjNz553znnMUVVVVhBBCCCGEEEII4RU6rQMQQgghhBBCCCHaEkm0hRBCCCGEEEIIL5JEWwghhBBCCCGE8CJJtIUQQgghhBBCCC+SRFsIIYQQQgghhPAiSbSFEEIIIYQQQggvkkRbCCGEEEIIIYTwIkm0hRBCCCGEEEIIL5JEWwghhBBCCCGE8CJJtNu43bt3c99999GlSxcCAgIICAigW7duPPTQQ2zdulXr8Bpk3bp1KIrCunXrWvzYs2bNQlGUc97mz59/zti+/vprZs2a1eBjLFmyhLlz53o99vMZM2YMY8aMuaTHzp49m08//dSr8UDTYmqqqVOn0qlTp2bbf0Nfv6e/1oKDg8+6f/v27YwfP57g4GDCw8OZPHky6enpTYqtoKCAqVOnEh0dTWBgIMOGDWP16tVntRswYIAntkmTJjXpmEII75NrfdPItb4+udY3nq9e67Ozs5kxYwajR48mPDwcRVFYuHDhOdvKtd77DFoHIJrPP/7xDx599FF69OjBb3/7W/r06YOiKBw4cIAPP/yQyy67jKNHj9KlSxetQ72gQYMGsWnTJnr37q1ZDN9++y1hYWH1tqWkpBAQEHBWbF9//TVvvvlmgy/AS5YsYe/evcyYMcOLETeP2bNnc+utt3LTTTd5db9///vfvbq/xvjzn//Mb3/7W82Of6ZNmzah1+vrbTt48CBjxoxhwIABfPTRR9TW1vKXv/yFUaNGsXPnTmJiYhp9HIvFwrhx4ygtLeWvf/0rsbGxvPnmm1xzzTWsWrWK0aNHe9p+8MEHVFVVcfPNNzf5/IQQ3iXXeu+Ra72LXOubX0td648ePcq///1vBgwYwHXXXceHH3543rZyrfc+SbTbqB9++IFp06Zx/fXX89///hc/Pz/PfVdddRWPPPIIH3/8MQEBARpG2TChoaFcccUVmsYwePBgoqOjz3mf1rG1BVp+sfK1L5/nej395S9/wWQy8eWXXxIaGgq4XpPdunXj1Vdf5aWXXmr0cRYsWMDevXvZuHEjw4YNA2Ds2LH079+fJ598kh9//NHTtm/fvgCYTKZLOSUhRDORa713ybW+ecm1/ictda2/8sorKSwsBGDr1q0XTLTlWu99UjreRs2ePRu9Xs8//vGPehfe0912220kJCR4/t66dSu33347nTp1IiAggE6dOvGLX/yCEydO1Hucu+zlTAsXLkRRFI4fP+7ZtmbNGsaMGUNUVBQBAQF07NiRW265herqak+bt956i/79+xMcHExISAg9e/bkj3/8o+f+c5XjNDRWd0xr167l4YcfJjo6mqioKCZPnszJkycb9FxeyJmxTZ06lTfffBOgXunZ6c/J6caMGcNXX33FiRMn6rV3O3XqFNOmTSMxMRE/Pz86d+7M008/jcViuWhsqqry8ssvk5ycjL+/P4MGDeKbb745q11tbS0zZ85kwIABhIWFERkZybBhw/jss8/qtVMUhaqqKhYtWuSJ010CVlhYyLRp0+jduzfBwcHExsZy1VVX8f333zfgWTy7nOz48eMoisKrr77K66+/TkpKCsHBwQwbNozNmzdfdH/u//eVK1dy7733EhkZSVBQEDfccMNZJVhnlpMtXbrUUy54umeeeQa9Xs/KlSs927Zu3cqNN95IZGQk/v7+DBw4kI8++qhB59xQdrudL7/8kltuucVz4QVITk5m7NixLFu27JL2u2zZMnr06OFJsgEMBgN33XUXW7ZsIScnp8mxCyGal1zr68ck13q51su1vj6dTlI9LUmPdhvkcDhYu3YtQ4YMIT4+vsGPO378OD169OD2228nMjKS3Nxc3nrrLS677DL2799/3l95L7S/66+/nlGjRvGvf/2L8PBwcnJy+Pbbb7FarQQGBrJ06VKmTZvG9OnTefXVV9HpdBw9epT9+/d7Ndb777+f66+/niVLlpCVlcXvfvc77rrrLtasWdOgc3E4HNjtds/fiqKcVfIDrtKkqqoq/vvf/7Jp0ybP9vP9P/z973/nwQcf5NixY2d9iNbW1jJ27FiOHTvGs88+S79+/fj++++ZM2cOO3fu5KuvvrpgzM8++yzPPvss9913H7feeitZWVk88MADOBwOevTo4WlnsVg4deoUTzzxBImJiVitVlatWsXkyZN57733uPvuuwFXmdNVV13F2LFj+fOf/wzguRicOnUKcF2gzGYzlZWVLFu2jDFjxrB69epLHpP15ptv0rNnT8+4tj//+c9cd911ZGRknFXedy733XcfEyZM8Py//+lPf2LMmDHs3r2b8PDwcz7m9ttvZ/369cycOZMrrriCIUOGsGbNGl544QX++Mc/MmHCBADWrl3LNddcw9ChQ3n77bcJCwtj6dKl/PznP6e6upqpU6de0jmf6dixY9TU1NCvX7+z7uvXrx8rV66ktrYWf3//Ru137969jBo16pz7BNi3bx+JiYmXFrQQotnJtV6u9SDXepBrvfBhqmhz8vLyVEC9/fbbz7rPbrerNpvNc3M6nefdj91uVysrK9WgoCD1r3/9q2f7M888o57rpfPee++pgJqRkaGqqqr+97//VQF1586d5z3Go48+qoaHh1/wfNauXasC6tq1axsdqzumadOm1Wv/8ssvq4Cam5t7wWO7z/XMW2Ji4nlje+SRR875/JzP9ddfryYnJ5+1/e2331YB9aOPPqq3/aWXXlIBdcWKFefdZ0lJierv76/efPPN9bb/8MMPKqCOHj36vI91v0buu+8+deDAgfXuCwoKUu+5556LnpN7H+PGjTsrhnMZPXp0vZgyMjJUQO3bt69qt9s927ds2aIC6ocffnjB/bn/3893/i+88IJn2z333HPW819bW6sOHDhQTUlJUffv36/GxcWpo0ePrhdLz5491YEDB6o2m63eYydNmqTGx8erDodDVdWGvX5V9fzvK3fM5zrn2bNnq4B68uTJC+77XIxGo/rQQw+dtX3jxo0qoC5ZsuSs+5KTk9Xrr7++0ccSQnifXOvlWi/XernWN0ZaWpoKqO+9994F28m13nuknqCdGTx4MEaj0XN77bXXPPdVVlby+9//nq5du2IwGDAYDAQHB1NVVcWBAwcafawBAwbg5+fHgw8+yKJFi845a+Lll19OaWkpv/jFL/jss88oKipq0L4bG+uNN95Y72/3L4Znlp+dz6pVq0hLS/Pcvv766wY9rinWrFlDUFAQt956a73t7l9PzzU7tNumTZuora3lzjvvrLd9+PDhJCcnn9X+448/ZsSIEQQHB2MwGDAajSxYsKBR/+9vv/02gwYNwt/f37OP1atXX9Jrx+3666+v15vQ2P+3853/2rVrL/g4k8nERx99RHFxMYMGDUJVVT788ENPLEePHuXgwYOe/dvtds/tuuuuIzc3l0OHDjX4PBviXCWcDbmvpfcphNCeXOtd5Fpfn1zr62sv13qhHUm026Do6GgCAgLO+QG1ZMkS0tLS+Pzzz8+674477mD+/Pncf//9LF++nC1btpCWlkZMTAw1NTWNjqNLly6sWrWK2NhYHnnkEbp06UKXLl3461//6mnzy1/+kn/961+cOHGCW265hdjYWIYOHVpvbMy5NDbWqKioen+7J3po6Hn179+fIUOGeG7nKu3xtuLiYsxm81kfrLGxsRgMBoqLiy/4WACz2XzWfWdu++STT5gyZQqJiYksXryYTZs2kZaWxq9+9Stqa2sbFOvrr7/Oww8/zNChQ/nf//7H5s2bSUtL45prrrmk145bU//fznf+F3ru3Lp27cqoUaM8X2JOLwnMz88H4Iknnqj3ZdZoNDJt2jSABn+RvBj3c3CumE+dOoWiKOctjbvYfs+3T4DIyMhG71MI0XLkWi/XernWu8i1XvgqGaPdBun1eq666ipWrFhBbm5uvQ8N94yPZ07YUVZWxpdffskzzzzDH/7wB89295ie07nHh1gslnozE57rw2bUqFGMGjUKh8PB1q1bmTdvHjNmzCAuLo7bb78dgHvvvZd7772XqqoqvvvuO5555hkmTZrE4cOHz/mLbGNibc2ioqL48ccfUVW13gW4oKAAu91+wXF07g/svLy8s+7Ly8urNyHI4sWLSUlJ4T//+U+94zRkEpbT9zFmzBjeeuutetsrKioavI/mcL7z79q160Uf+89//pOvvvqKyy+/nPnz5/Pzn/+coUOHAnie+6eeeorJkyef8/Gnj41rCve6uHv27Dnrvj179tC1a9dLGrPVt2/f8+4TIDU1tfHBCiFajFzr2wa51jedXOuFr5Ie7TbqqaeewuFw8Otf/xqbzXbR9oqioKrqWVP6//Of/8ThcNTb5v7g3r17d73tX3zxxXn3r9frGTp0qGeWzu3bt5/VJigoiGuvvZann34aq9XKvn37mhyrFhr7S6zJZDpn23HjxlFZWcmnn35ab/v777/vuf98rrjiCvz9/fn3v/9db/vGjRvP6v1QFAU/P796F968vLyzZiK9UKyKopz1/7F79+56k8Ro4Xznf7EJW/bs2cNvfvMb7r77br7//nv69evHz3/+c0pKSgDXhbVbt27s2rWrXu/H6beQkBCvnIPBYOCGG27gk08+qfdlJjMzk7Vr15734n8xN998MwcPHqy3jJfdbmfx4sUMHTq03izFQgjfJNd67ci13kWu9b59rRfakh7tNmrEiBG8+eabTJ8+nUGLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x800 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(2, 2, figsize=(12,8))\n",
|
||||
"\n",
|
||||
"# Gaussians in 2x2 frame: True vs Fit\n",
|
||||
"for row in range(2):\n",
|
||||
" for col in range(2):\n",
|
||||
" ax[row, col].plot(x2, y2[row, col,:], label=\"data\")\n",
|
||||
" ax[row, col].plot(x2, model_g(x2, result['par'][row, col,:]), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
" ax[row, col].set_title(f\"Gaussian Fit to data in pixel [{row}, {col}]\")\n",
|
||||
" ax[row, col].legend()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "fcf52481-0278-4f95-8676-829a6d61eff8",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit data with different backends"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generating synthetic data: 100x100 pixels, 100 scan points, noise_frac=0.05\n",
|
||||
"\n",
|
||||
"model.max_calls = 100\n",
|
||||
"model.tolerance = 0.5\n",
|
||||
"model.compute_errors = False\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# DATA GENERATION\n",
|
||||
"# ===============\n",
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
"\n",
|
||||
"x, y, yerr, true_A, true_mu, true_sig = generate_3d_data(\n",
|
||||
" ROWS, COLS, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"model = Gaussian()\n",
|
||||
"print(f\"model.max_calls = {model.max_calls}\")\n",
|
||||
"print(f\"model.tolerance = {model.tolerance}\")\n",
|
||||
"print(\"model.compute_errors =\", model.compute_errors)\n",
|
||||
"METHOD_DEFS = [\n",
|
||||
" (\"lmfit (LM)\",\n",
|
||||
" lambda nt: lambda: fit_gaus(x, y, n_threads=nt),\n",
|
||||
" \"#2196F3\", {\"linewidth\": 3.0, \"linestyle\": \"-\"}),\n",
|
||||
"\n",
|
||||
" (\"Minuit2 (obj API)\",\n",
|
||||
" lambda nt: lambda: model.fit(x, y, n_threads=nt),\n",
|
||||
" \"#FF9800\", {\"linewidth\": 2.5, \"linestyle\": \":\"}),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"colors = {label: c for label, _, c, _ in METHOD_DEFS}\n",
|
||||
"styles = {label: s for label, _, _, s in METHOD_DEFS}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Method time (ms) med|dA| med|dMu| med|dSig|\n",
|
||||
"--------------------------------------------------------------------------------\n",
|
||||
"[lmfit (LM) ] 189.58 ms 6.272 0.0940 0.0949\n",
|
||||
"[Minuit2 (obj API) ] 124.59 ms 6.272 0.0940 0.0949 chi2/ndf=880.9946\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ====================================\n",
|
||||
"# SINGLE-CALL BENCHMARK (at N_THREADS)\n",
|
||||
"# ====================================\n",
|
||||
"def extract_result(label, res):\n",
|
||||
" \"\"\"Normalize return values across fitters into a common dict.\"\"\"\n",
|
||||
" if isinstance(res, dict):\n",
|
||||
" out = {\"par\": res[\"par\"]}\n",
|
||||
" if \"par_err\" in res:\n",
|
||||
" out[\"par_err\"] = res[\"par_err\"]\n",
|
||||
" if \"chi2\" in res:\n",
|
||||
" out[\"chi2\"] = res[\"chi2\"]\n",
|
||||
" return out\n",
|
||||
" # fit_gaus without y_err returns a raw array\n",
|
||||
" return {\"par\": res}\n",
|
||||
" \n",
|
||||
"methods = {}\n",
|
||||
"for label, factory, _, _ in METHOD_DEFS:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" res, times = bench(factory(N_THREADS))\n",
|
||||
" entry = extract_result(label, res)\n",
|
||||
" entry[\"times\"] = times\n",
|
||||
" methods[label] = entry\n",
|
||||
"\n",
|
||||
"# ---- Print summary ----\n",
|
||||
"ndf = N_SCAN - 3\n",
|
||||
"print(f\"{'Method':24s} {'time (ms)':>10s} {'med|dA|':>10s} {'med|dMu|':>10s} {'med|dSig|':>10s}\")\n",
|
||||
"print(\"-\" * 80)\n",
|
||||
"for name, m in methods.items():\n",
|
||||
" par = m[\"par\"]\n",
|
||||
" med_t = np.median(m[\"times\"]) * 1e3\n",
|
||||
" dA = np.median(np.abs(par[:,:,0] - true_A))\n",
|
||||
" dMu = np.median(np.abs(par[:,:,1] - true_mu))\n",
|
||||
" dSig = np.median(np.abs(par[:,:,2] - true_sig))\n",
|
||||
"\n",
|
||||
" chi2_str = \"\"\n",
|
||||
" if \"chi2\" in m:\n",
|
||||
" chi2_str = f\" chi2/ndf={np.median(m['chi2'] / ndf):.4f}\"\n",
|
||||
"\n",
|
||||
" print(f\"[{name:22s}] {med_t:8.2f} ms \"\n",
|
||||
" f\"{dA:10.3f} {dMu:10.4f} {dSig:10.4f}{chi2_str}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" Minuit2 (obj API) n_threads= 1 456.26 ± 10.76 ms (45.6262 ± 1.0762 μs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 1 752.77 ± 108.16 ms (75.2771 ± 10.8165 μs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (obj API) n_threads= 2 238.34 ± 26.57 ms (23.8345 ± 2.6566 μs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 2 410.95 ± 84.96 ms (41.0945 ± 8.4959 μs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 4 205.44 ± 35.26 ms (20.5445 ± 3.5259 μs/pixel)\n",
|
||||
" Minuit2 (obj API) n_threads= 4 139.22 ± 14.06 ms (13.9224 ± 1.4060 μs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (obj API) n_threads= 8 130.12 ± 3.27 ms (13.0118 ± 0.3269 μs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 8 199.97 ± 6.88 ms (19.9968 ± 0.6877 μs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (obj API) n_threads=16 134.81 ± 10.13 ms (13.4807 ± 1.0130 μs/pixel)\n",
|
||||
" lmfit (LM) n_threads=16 188.32 ± 9.86 ms (18.8322 ± 0.9860 μs/pixel)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# ===============\n",
|
||||
"# THREAD SCALING\n",
|
||||
"# ===============\n",
|
||||
"thread_counts = [1, 2, 4, 8, 16]\n",
|
||||
"\n",
|
||||
"thread_times = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"ttimes_stddev = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"\n",
|
||||
"for nt in thread_counts:\n",
|
||||
" # shuffle method order per thread count to decorrelate thermal bias\n",
|
||||
" run_order = list(METHOD_DEFS)\n",
|
||||
" random.shuffle(run_order)\n",
|
||||
"\n",
|
||||
" for label, factory, _, _ in run_order:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" _, times = bench(factory(nt))\n",
|
||||
"\n",
|
||||
" med = np.median(times) * 1e3\n",
|
||||
" std = np.std(times) * 1e3\n",
|
||||
" thread_times[label].append(med)\n",
|
||||
" ttimes_stddev[label].append(std)\n",
|
||||
"\n",
|
||||
" per_px = med / (ROWS * COLS) * 1e3\n",
|
||||
" per_px_std = std / (ROWS * COLS) * 1e3\n",
|
||||
" print(f\" {label:22s} n_threads={nt:2d} \"\n",
|
||||
" f\"{med:8.2f} ± {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} ± {per_px_std:.4f} μs/pixel)\")\n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1500x500 with 3 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAHqCAYAAAB/bWzAAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAADM5ElEQVR4nOzdd3RU1d7G8WcmPaSRRggQOoFQQ0sgkSJIrwqCiCIoolyvIveq2CiKYhevHQtFBbEAFxHs9N57J4CUGCCQQvrMef/gzVzGJBAgySTk+1kra+Xss885z5yZzEx+s2cfk2EYhgAAAAAAAAAApYLZ0QEAAAAAAAAAAP9D0RYAAAAAAAAAShGKtgAAAAAAAABQilC0BQAAAAAAAIBShKItAAAAAAAAAJQiFG0BAAAAAAAAoBShaAsAAAAAAAAApQhFWwAAAAAAAAAoRSjaAgAAAAAAAEApQtEWKOPWr1+v/v37KywsTG5ubqpUqZLatGmjf/3rX3b9OnTooA4dOhR7HpPJpIkTJxb7ca7HjBkzZDKZdPToUVvbfffdpxo1ajgs0+7duzV69Gi1adNGFSpUkMlk0rJlywrs//XXX6tZs2Zyd3dXaGioxowZo9TU1Dz9UlNTNWbMGIWGhsrd3V3NmjXT119/XWS5V61apQceeEAtWrSQm5tbnvP6d++++67q168vNzc31axZU5MmTVJ2dnaefgkJCbrvvvsUGBgoT09PtWnTRr///nuR5S4pzz33nEwmkxo1alSo/rNnz9bUqVPztB89elQmk0lvvPFGEScsHo7+ewIAwNFMJlOhfpYtW6Zly5bJZDLpu+++c3TsQimp/yeuVX65SvP/JABQWM6ODgDg+v3444/q06ePOnTooNdee02VK1fW6dOntWnTJn399dd68803bX0/+OADByZFQTZt2qQFCxYoMjJSnTp10g8//FBg36+++kpDhw7VAw88oLffflsHDhzQU089pT179uiXX36x63v77bdr48aNeuWVV1SvXj3Nnj1bd911l6xWq4YMGXLDuX///Xf99ttvioyMlI+PzxULzS+99JKef/55jRs3Tl26dNHGjRv13HPP6eTJk5o2bZqtX2Zmpjp16qQLFy7onXfeUXBwsN5//31169ZNv/32m9q3b3/DuUvCtm3b9MYbb6hSpUqF3mb27NnatWuXxowZU3zBAABAsVu7dq3d8osvvqilS5fqjz/+sGuPiIjQli1bSjJaubJ27VpVrVrV0TEA4IZQtAXKsNdee001a9bUzz//LGfn//05Dx48WK+99ppd34iIiJKOh0K45557NGzYMEnSd999V2DR1mKx6IknnlCXLl30ySefSJI6duwob29v3X333VqyZIm6d+8uSVq8eLF+/fVXW6E2t++xY8f0xBNPaNCgQXJycrLte9WqVapZs6aqVKmS57iZmZn66aef1LdvX7v2559/XhMmTJAkvfHGGwUWbc+dO6fJkydr5MiRevnllyVdGg2RnZ2t5557TmPGjLE9Nj/77DPt2rVLa9asUZs2bWy5mzZtqieffFLr16+/+gl1sJycHA0fPlyjRo3S9u3bdfbsWYfmSUtLk6enp0MzAABQnkRHR9stBwUFyWw252kvCrzOF6w4zjcAlDSmRwDKsHPnzikwMNCuYJvLbLb/8/7714Yu/9r1W2+9pZo1a8rLy0tt2rTRunXr8uzvk08+Ub169eTm5qaIiAjNnj270F+Fjo+P16hRo1S1alW5urravh6fk5NTqNs5e/ZstWnTRl5eXvLy8lKzZs302Wef2db/+uuv6tu3r6pWrSp3d3fVqVNHo0aNcnjBrDD+fj8VZN26dTp9+rSGDx9u1z5w4EB5eXlp/vz5trb58+fLy8tLAwcOtOs7fPhwnTp1yq74abVa9cgjj6hdu3Y6duyYXf+0tDT16tVL9957r+Lj468r908//aSMjIw8uYcPHy7DMLRgwQK73OHh4baCrSQ5Oztr6NCh2rBhg06ePHnFY3Xo0EGNGjXSypUrFR0dLQ8PD1WpUkXPP/+8LBaLrd8rr7wis9mcp0B+3333ydPTUzt37izUbcvPK6+8osTERL300kuF3qZDhw768ccfdezYMbuvTf7d1f5O77vvPnl5eWnnzp3q0qWLvL291alTJ0lSVlaWJk+ebJuiIigoSMOHD9eZM2fs9jF37lx16dJFlStXloeHhxo0aKBx48bp4sWLefLMmDFD4eHhcnNzU4MGDTRr1qx8b9+HH36opk2bysvLS97e3qpfv76eeeaZQp8fAABudtnZ2Xr22WcVGhoqHx8fde7cWfv377frk/s+Z8WKFWrbtq08PT01YsQISVJycrL+/e9/q2bNmnJ1dVWVKlU0ZsyYPK/f77//vtq1a6fg4GBV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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# =============================\n",
|
||||
"# FIGURE 1: Residual histograms\n",
|
||||
"# =============================\n",
|
||||
"param_names = [\"A\", \"μ\", \"σ\"]\n",
|
||||
"param_truths = [true_A, true_mu, true_sig]\n",
|
||||
"\n",
|
||||
"fig1, axes1 = plt.subplots(1, 3, figsize=(15, 5))\n",
|
||||
"fig1.suptitle(f\"Parameter Residuals — {ROWS}×{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" fontsize=14, fontweight=\"bold\")\n",
|
||||
"\n",
|
||||
"for col, (pname, truth) in enumerate(zip(param_names, param_truths)):\n",
|
||||
" ax = axes1[col]\n",
|
||||
"\n",
|
||||
" # collect residuals across all methods for shared bin edges\n",
|
||||
" res_by_method = {}\n",
|
||||
" all_res = []\n",
|
||||
" for mname, m in methods.items():\n",
|
||||
" residual = (m[\"par\"][:, :, col] - truth).ravel()\n",
|
||||
" res_by_method[mname] = residual\n",
|
||||
" all_res.append(residual)\n",
|
||||
" all_res = np.concatenate(all_res)\n",
|
||||
"\n",
|
||||
" lo, hi = np.percentile(all_res, [0.5, 99.5])\n",
|
||||
" edges = np.linspace(lo, hi, 101)\n",
|
||||
"\n",
|
||||
" for mname, r in res_by_method.items():\n",
|
||||
" ax.hist(r, bins=edges, histtype=\"step\", label=mname,\n",
|
||||
" color=colors[mname],\n",
|
||||
" linewidth=styles[mname][\"linewidth\"],\n",
|
||||
" linestyle=styles[mname][\"linestyle\"])\n",
|
||||
"\n",
|
||||
" ax.axvline(0, color=\"k\", linestyle=\"--\", linewidth=1, alpha=0.7)\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} − True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"Δ{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig1.tight_layout()\n",
|
||||
"# fig1.savefig(\"fig1_residual_histograms.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"\\nSaved fig1_residual_histograms.png\")\n",
|
||||
"\n",
|
||||
"# ====================================================\n",
|
||||
"# FIGURE 2: Performance — bar chart + thread scaling\n",
|
||||
"# ====================================================\n",
|
||||
"fig2 = plt.figure(figsize=(14, 5))\n",
|
||||
"gs = GridSpec(1, 2, figure=fig2, width_ratios=[1, 1.3])\n",
|
||||
"\n",
|
||||
"# -- Left: bar chart at N_THREADS --\n",
|
||||
"ax2a = fig2.add_subplot(gs[0])\n",
|
||||
"names = list(methods.keys())\n",
|
||||
"medians = [np.median(methods[n][\"times\"]) * 1e3 for n in names]\n",
|
||||
"bars = ax2a.barh(names, medians,\n",
|
||||
" color=[colors[n] for n in names],\n",
|
||||
" edgecolor=\"white\", height=0.5)\n",
|
||||
"ax2a.set_xlabel(\"Median wall time (ms)\")\n",
|
||||
"ax2a.set_title(f\"Single call — {ROWS}×{COLS} px, {N_THREADS} threads\")\n",
|
||||
"for bar, val in zip(bars, medians):\n",
|
||||
" ax2a.text(bar.get_width() + max(medians) * 0.02,\n",
|
||||
" bar.get_y() + bar.get_height() / 2,\n",
|
||||
" f\"{val:.1f} ms\", va=\"center\", fontsize=10)\n",
|
||||
"ax2a.grid(axis=\"x\", alpha=0.3)\n",
|
||||
"ax2a.set_xlim(0, max(medians) * 1.25)\n",
|
||||
"\n",
|
||||
"# -- Right: thread scaling with error bars --\n",
|
||||
"ax2b = fig2.add_subplot(gs[1])\n",
|
||||
"for label, _, _, _ in METHOD_DEFS:\n",
|
||||
" tt = thread_times[label]\n",
|
||||
" sd = ttimes_stddev[label]\n",
|
||||
" speedup = [tt[0] / t for t in tt]\n",
|
||||
" # propagate uncertainty: S = t0/t → δS/S = sqrt((δt0/t0)² + (δt/t)²)\n",
|
||||
" speedup_err = [\n",
|
||||
" s * np.sqrt((sd[0] / tt[0])**2 + (sd[i] / tt[i])**2)\n",
|
||||
" for i, s in enumerate(speedup)\n",
|
||||
" ]\n",
|
||||
" ax2b.errorbar(thread_counts, speedup, yerr=speedup_err,\n",
|
||||
" fmt=\"o-\", label=label, color=colors[label],\n",
|
||||
" linewidth=2, markersize=7, capsize=4)\n",
|
||||
"\n",
|
||||
"ax2b.plot(thread_counts, thread_counts, \"k--\", alpha=0.4, label=\"Ideal linear\")\n",
|
||||
"ax2b.set_xlabel(\"Number of threads\")\n",
|
||||
"ax2b.set_ylabel(\"Speedup vs 1 thread\")\n",
|
||||
"ax2b.set_title(\"Thread scaling\")\n",
|
||||
"ax2b.set_xticks(thread_counts)\n",
|
||||
"ax2b.legend(fontsize=9)\n",
|
||||
"ax2b.grid(alpha=0.3)\n",
|
||||
"\n",
|
||||
"fig2.tight_layout()\n",
|
||||
"# fig2.savefig(\"fig2_performance.png\", dpi=150, bbox_inches=\"tight\")\n",
|
||||
"# print(\"Saved fig2_performance.png\")\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "92a8c724-e139-45d4-a354-1b8408637ead",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"import sys\n",
|
||||
"sys.path.insert(0, '/home/ferjao_k/sw/aare/build')\n",
|
||||
"from aare import Pol1, Pol2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8449f9e9-59ee-4194-b477-d014b8efc93b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Ground truth\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = np.linspace(0, 10, 50)\n",
|
||||
" \n",
|
||||
"# Pol1: y = 3.0 + 1.5*x\n",
|
||||
"true_pol1 = [3.0, 1.5]\n",
|
||||
"y_pol1 = true_pol1[0] + true_pol1[1] * x\n",
|
||||
"y_pol1_noisy = y_pol1 + np.random.default_rng(42).normal(0, 0.5, x.size)\n",
|
||||
" \n",
|
||||
"# Pol2: y = 2.0 - 0.5*x + 0.3*x^2\n",
|
||||
"true_pol2 = [2.0, -0.5, 0.3]\n",
|
||||
"y_pol2 = true_pol2[0] + true_pol2[1] * x + true_pol2[2] * x**2\n",
|
||||
"y_pol2_noisy = y_pol2 + np.random.default_rng(7).normal(0, 1.0, x.size)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a318ab93-fe18-4798-9ddb-6ffc2c2e3875",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit with error estimation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"=== Pol1 ===\n",
|
||||
" True: p0=3.0000 p1=1.5000\n",
|
||||
" Fitted: p0=2.9325 p1=1.5226\n",
|
||||
" Chi2: [7.00870822]\n",
|
||||
"\n",
|
||||
"=== Pol2 ===\n",
|
||||
" True: p0=2.0000 p1=-0.5000 p2=0.3000\n",
|
||||
" Fitted: p0=2.1391 p1=-0.8442 p2=0.3383\n",
|
||||
" Chi2: [34.11224393]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"m1 = Pol1()\n",
|
||||
"m2 = Pol2()\n",
|
||||
" \n",
|
||||
"res1 = m1.fit(x, y_pol1_noisy)\n",
|
||||
"res2 = m2.fit(x, y_pol2_noisy)\n",
|
||||
"\n",
|
||||
"p1 = res1['par']\n",
|
||||
"print(\"=== Pol1 ===\")\n",
|
||||
"print(f\" True: p0={true_pol1[0]:.4f} p1={true_pol1[1]:.4f}\")\n",
|
||||
"print(f\" Fitted: p0={p1[0]:.4f} p1={p1[1]:.4f}\")\n",
|
||||
"print(f\" Chi2: {res1['chi2']}\")\n",
|
||||
"print()\n",
|
||||
"\n",
|
||||
"p2 = res2['par']\n",
|
||||
"print(\"=== Pol2 ===\")\n",
|
||||
"print(f\" True: p0={true_pol2[0]:.4f} p1={true_pol2[1]:.4f} p2={true_pol2[2]:.4f}\")\n",
|
||||
"print(f\" Fitted: p0={p2[0]:.4f} p1={p2[1]:.4f} p2={p2[2]:.4f}\")\n",
|
||||
"print(f\" Chi2: {res2['chi2']}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7e185144-3b72-41f4-80d0-3c3504e48bba",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Plot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "9a0b3292-7c57-4483-aecc-7ef683ff0b62",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABKUAAAHqCAYAAADVi/1VAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAADH/0lEQVR4nOzde3zO9f/H8cfn2uHaeYwdMHNIq2zoQAmFvo3kVDmmRDoocqyUjhQp5ZBIB/0ckkqKjpgUOuAbUbHChGnMNFw72Obars/vj4uLfZ3Zdl3bnvfbza193p/39ble117W3l6f9/v9MUzTNBERERERERERESlFFncHICIiIiIiIiIiFY+KUiIiIiIiIiIiUupUlBIRERERERERkVKnopSIiIiIiIiIiJQ6FaVERERERERERKTUqSglIiIiIiIiIiKlTkUpEREREREREREpdSpKiYiIiIiIiIhIqVNRSkRERERERERESp2KUiLi0WbNmoVhGK4/3t7eREdHc++995Kamnpe12rVqhWtWrUq0jZnzhx69uzJZZddhsVioXbt2sUXvIiIiIgblOT4ae/evTzzzDNcf/31VK1alZCQEK655hreeecdCgsLi/mTiEh55+3uAEREzsXMmTO5/PLLyc3NZdWqVYwbN46VK1fyxx9/EBgYeMHXff/990lLS+Paa6/F4XBgt9uLMWoRERER9ymJ8dP69euZM2cO99xzD88++yw+Pj4sXryYhx9+mDVr1vB///d/xfwpRKQ8U1FKRMqE+Ph4GjduDEDr1q0pLCzkxRdfZNGiRdx1110XfN2lS5disTgnjXbo0IFNmzYVS7wiIiIi7lYS46fmzZuzfft2fHx8XG0JCQkcOXKEadOmMXr0aGrWrFks8YtI+afleyJSJjVt2hSAXbt2kZeXx8iRI6lTpw6+vr7UqFGDgQMHcujQobNe51hBSkRERKS8K47xU+XKlYsUpI659tprAfjnn3+KPW4RKb80U0pEyqTk5GQAwsPDue2221i+fDkjR47khhtu4Pfff+f5559n9erVrF69GqvV6uZoRURERNyvJMdP3333Hd7e3sTGxpZE6CJSTqkoJSJlQmFhIQUFBeTl5bFy5UrGjBlDcHAwISEhLF26lPHjx/P4448DzinkNWvWpEePHsyZM4cHHnjAzdGLiIiIlL7SGj8lJiby/vvvM2TIEKpUqVJSH0dEyiGtWxGRMqFp06b4+PgQHBxMhw4diIqKYvHixfz6668A9O3bt0j/bt26ERgYyPLly90QrYiIiIj7lcb46ddff6V79+40bdqUcePGFWf4IlIBaKaUiJQJc+bM4YorrsDb25vIyEiqVasGOJ8q4+3tTXh4eJH+hmEQFRVFRkaGO8IVERERcbuSHj9t2LCBhIQELr30Ur755httmSAi501FKREpE6644grX02NOVKVKFQoKCti/f3+RgZVpmqSlpdGkSZPSDFNERETEY5Tk+GnDhg3cfPPN1KpVi8TEREJDQ4s1dhGpGLR8T0TKtP/85z8AzJ07t0j7p59+Sk5Ojuu8iIiIiDhd7Php48aN3HzzzURHR7Ns2TIqV65cYrGKSPmmmVIiUqYlJCTQtm1bnnjiCTIzM2nevLnr6TFXXXUVvXv3PuPrk5KSSEpKAiAtLY3Dhw+zYMECAOrXr0/9+vVL/DOIiIiIlKaLGT9t2bKFm2++GYCxY8eybds2tm3b5jp/ySWXnLQsUETkdFSUEpEyzTAMFi1axKhRo5g5cyZjx46latWq9O7dm5deeumsexvMnz+f0aNHF2nr1q0bAM8//zyjRo0qqdBFRERE3OJixk+rV6927TnVsWPHk87PnDnzpA3URUROxzBN03R3ECIiIiIiIiIiUrFoTykRERERERERESl1KkqJiIiIiIiIiEipU1FKRERERERERERKnYpSIiIiIiIiIiJS6lSUEhERERERERGRUqeilIiIiIiIiIiIlDpvdwfwvxwOB3v27CE4OBjDMNwdjoiIiFRgpmmSlZVFcHAwISEhHj020RhKREREPMWxMVT16tWxWE4/H8rjilJ79uyhZs2a7g5DREREpAibzUZISIi7wzgtjaFERETE0+zevZvo6OjTnve4olRwcDDgDLykBn4Oh4P9+/cTHh5+xoqdlA7lw7MoH55F+fAsyodnKY18ZGZmUrNmTXbv3u0ao1yI6dOnM336dHbu3AlAXFwczz33HO3atQOcdxNHjx7NO++8w8GDB7nuuuuYNm0acXFx5/weGkNVPMqHZ1E+PIvy4VmUD89SmmOos42fPK4odWy6eUhISIkOqPLy8ggJCdEPhAdQPjyL8uFZlA/Ponx4ltLMx8Uu3YuOjubll1+mXr16AMyePZvOnTuzYcMG4uLiGD9+PBMnTmTWrFnExsYyZswYEhIS2LJlyzkXwzSGqniUD8+ifHgW5cOzKB+epTTzcbbxk/42iIiIiJSwjh07cuuttxIbG0tsbCxjx44lKCiINWvWYJomkydP5umnn+aOO+4gPj6e2bNnc/jwYebNm+fu0EVERERKjIpSIiIiIqWosLCQjz76iJycHK6//np27NhBWloabdq0cfWxWq20bNmSn3/+2Y2RioiIiJQsj1u+JyIiIlIe/fHHH1x//fXk5eURFBTEwoULqV+/vqvwFBkZWaR/ZGQku3btOu318vPzyc/Pdx1nZmYCzin5DoejBD6B89qmaZbY9eX8KB+eRfnwLMqHZ1E+PEtp5ONcr11mi1KFhYXY7fYLeq3D4cBut5OXl1eh17P6+Pjg5eXl7jBEREQqhMsuu4yNGzdy6NAhPv30U/r06cPKlStd5/93zwXTNM+4D8O4ceMYPXr0Se379+8nLy/vtK9zOBwUFhZewCc4/nhnu91+UXtslWVeXl4eM350OBzYbDZM0/SYmCoy5cOzKB+eRfnwLKWRj6ysrHPqV+aKUqZpkpaWxqFDhy7qGg6Hg6ysrAo7oDqmUqVKREVFVfjvg4iISEnz9fV1bXTeuHFjfvnlF15//XWeeOIJANLS0qhWrZqrf3p6+kmzp040cuRIhg8f7jo+9pSb8PDwU250bpom+/btu6gx1LHrZGdnX9Q1yrpKlSoRGRnp9vGTw+HAMAw9zcpDKB+eRfnwLMqHZymNfPj5+Z1TvzJXlDpWkIqIiCAgIOCCBgOmaVJQUIC3t7fbBxPuYpomhw8fJj09HaDIIFhERERKnmma5OfnU6dOHaKioli2bBlXXXUVAEeOHGHlypW88sorp3291WrFarWe1G6xWE45wNy7dy82m43IyEiNoS7QieMnwzA8YvxkGMZpcy6lT/nwLMqHZ1E+PEtJ5+Ncr1umilKFhYWuglSVKlUu+DoVfUB1jL+/P+C8ExsREaGlfCIiIiXkqaeeol27dtSsWZOsrCw++ugjVqxYwZIlSzAMg6FDh/LSSy9x6aWXcumll/LSSy8REBBAr169iuX9NYYqPho/iYiIFJ8yVZQ6todUQECAmyMpP459L+12uwZVIiIiJWTfvn307t2bvXv3EhoaSsOGDVmyZAkJCQkAjBgxgtzcXAYMGMDBgwe57rrrSExMJDg4uFjeX2Oo4qXxk4iISPEoU0WpYyrqnbmSoO+liIhIyXvvvffOeN4wDEaNGsWoUaNKNA793i8e+j6KiIgUDy3mFBERERERERGRUqeilJu1atWKoUOHujsMERERkTJD4ycREZHyQUWpMmTFihUYhnHRj3IWERERqSg0fhIREfFcKkqJiIiIiIiIiEipq7BFqc17Mpm4bCtDPtrAhMQtbEq1lfh75uTkcM899xAUFES1atWYMGFCkfNz586lcePGBAcHExUVRa9evUhPTwdg586dtG7dGoDKlStjGAZ9+/YFYMmSJbRo0YJKlSpRpUoVOnTowPbt20v884iIiEjFsnmPjcnLkxn68UaNn0REROSiVcii1OY9NqZ8l8zq7f+SmVvA6u0ZTFq2tcQHVo8//jjff/89CxcuJDExkRUrVrB+/XrX+SNHjvDiiy/y22+/sWjRInbs2OEaONWsWZNPP/0UgC1btrB3715ef/11wDlYGz58OL/88gvLly/HYrFw++2343A4SvTziIiIuJtx8KC7Q6gwNqXamLRsG2v+PkBmrl3jJxERkTJoU6qNicu28vziHUwshd/jZ+Pt1nd3k6Wb92HLLeDSyCAMw0JkiJXk9GwSk/YRXyO0RN4zOzub9957jzlz5pCQkADA7NmziY6OdvXp16+f6+u6desyZcoUrr32WrKzswkKCiIsLAyAiIgIKlWq5OrbpUuXIu/13nvvERERQVJSEvHx8SXyeURERNzuyy8J79MH3noLevZ0dzTl3tLNadhyj3BJeCBeXhYiQ9D4SUREpAxx3mDaii33CL6GgzV/Z/Dn3iyGJcSW2O/ysznvmVKrVq2iY8eOVK9eHcMwWLRoUZHz2dnZPPLII0RHR+Pv788VV1zB9OnTiyveYpFy4DBBft4YhgGAYRgE+/mwKyOnxN5z+/btHDlyhOuvv97VFhYWxmWXXeY63rBhA507d6ZWrVoEBwfTqlUrZ7wpKWe9dq9evahbty4hISHUqVPnnF4nIiJSJhUUwJNPYrntNiw2G8YDD8CWLe6OqtxLOXCYYD8fjZ9ERETKKOcNJjv1woOICPKlXngQtlw7iUn73BbTeRelcnJyaNSoEVOnTj3l+WHDhrFkyRLmzp3Ln3/+ybBhwxg0aBCff/75RQdbXGLCAsjOK8A0TQBM0yQrz07tKoEl9p7H3ut0cnJyaNOmDUFBQcydO5dffvmFhQsXAs5p6WfSsWNHMjIyePfdd1m7di1r1649p9eJiIh4mk2pNiYkbjn9no9paXDzzfDKK66mbY2uJ8nhX8qRVjwxYQFk5dk1fhIRESmj3HGD6WzOuyjVrl07xowZwx133HHK86tXr6ZPnz60atWK2rVr8+CDD9KoUSPWrVt30cELine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))\n",
|
||||
" \n",
|
||||
"# Pol1\n",
|
||||
"ax1.plot(x, y_pol1_noisy, 'o', ms=4, alpha=0.6, label='data')\n",
|
||||
"ax1.plot(x, y_pol1, 'k--', label='truth')\n",
|
||||
"ax1.plot(x, m1(x, res1['par']), 'r-', lw=2, label='fit')\n",
|
||||
"ax1.set_title('Pol1')\n",
|
||||
"ax1.legend()\n",
|
||||
"ax1.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
"# Pol2\n",
|
||||
"ax2.plot(x, y_pol2_noisy, 'o', ms=4, alpha=0.6, label='data')\n",
|
||||
"ax2.plot(x, y_pol2, 'k--', label='truth')\n",
|
||||
"ax2.plot(x, m2(x, res2['par']), 'r-', lw=2, label='fit')\n",
|
||||
"ax2.set_title('Pol2')\n",
|
||||
"ax2.legend()\n",
|
||||
"ax2.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
"fig.tight_layout()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e694c18a-a203-4132-a472-c93c4d3ce853",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,386 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"np.set_printoptions(suppress=True, precision=6)\n",
|
||||
"\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from scipy.special import erf\n",
|
||||
"\n",
|
||||
"import sys\n",
|
||||
"# sys.path.insert(0, '/home/ferjao_k/sw/aare/build')\n",
|
||||
"sys.path.insert(0, '/home/kferjaoui/sw/aare/build')\n",
|
||||
"\n",
|
||||
"from aare import fit_scurve, fit_scurve2 # lmfit\n",
|
||||
"from aare import RisingScurve, FallingScurve, fit # minuit2 (object based API)\n",
|
||||
"\n",
|
||||
"from pprint import pprint"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "8449f9e9-59ee-4194-b477-d014b8efc93b",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Model curves"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def scurve(x, p): # rising Scurve\n",
|
||||
" p0, p1, p2, p3, p4, p5 = p\n",
|
||||
" return (p0 + p1*x) + 0.5 *(1 + erf((x-p2) / (np.sqrt(2)*p3))) * (p4 + p5*(x-p2))\n",
|
||||
"\n",
|
||||
"def scurve2(x, p): #falling Scurve\n",
|
||||
" p0, p1, p2, p3, p4, p5 = p\n",
|
||||
" return (p0 + p1*x) + 0.5 *(1 - erf((x-p2) / (np.sqrt(2)*p3))) * (p4 + p5*(x-p2))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a318ab93-fe18-4798-9ddb-6ffc2c2e3875",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Generate data (1D)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"x = np.linspace(0,120, 121)\n",
|
||||
"\n",
|
||||
"rng = np.random.default_rng(42)\n",
|
||||
"0\n",
|
||||
"p_true_rising = np.array([100.0, 0.25, 60.0, 6.0, 120.0, 1.0])\n",
|
||||
"p_true_falling = np.array([100.0, 0.25, 60.0, 6.0, 120.0, -1.0])\n",
|
||||
"\n",
|
||||
"y_true_rising = scurve(x, p_true_rising)\n",
|
||||
"y_true_falling = scurve2(x, p_true_falling)\n",
|
||||
"\n",
|
||||
"noise_sigma = 4\n",
|
||||
"\n",
|
||||
"y_rising = y_true_rising + rng.normal(0, noise_sigma, size=x.shape)\n",
|
||||
"# y_err_r = np.full_like(x, noise_sigma)\n",
|
||||
"\n",
|
||||
"y_falling = y_true_falling + rng.normal(0, noise_sigma, size=x.shape)\n",
|
||||
"# y_err_f = np.full_like(x, noise_sigma)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7e185144-3b72-41f4-80d0-3c3504e48bba",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Plot synthetic data"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "9a0b3292-7c57-4483-aecc-7ef683ff0b62",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAA9oAAAF0CAYAAADcnkHwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACSjElEQVR4nOzdeXwTZf4H8M8kbdP7phcNpSBHS7lBCogUQS4BURBUUFFg/SmwssDqIiqIIh67HosrqwsCUhBPQAVB7kPkKmdpuXvSC3qlV9I0md8fITFpkp5p0+Pzfr2izWQyeSYkmfnO8zzfryCKoggiIiIiIiIisgmJvRtARERERERE1JIw0CYiIiIiIiKyIQbaRERERERERDbEQJuIiIiIiIjIhhhoExEREREREdkQA20iIiIiIiIiG2KgTURERERERGRDDLSJiIiIiIiIbIiBNhEREREREZENMdAmsmL9+vUQBMFwc3BwQHBwMB5//HFcu3bNbP2YmBjExMTU6jUOHjwIQRBw8OBB2zS6lhITE/HUU0+hQ4cOcHZ2hr+/P/r06YO5c+dCoVDYpU1ERESNofJx3vi2aNGiGm8nOTkZgiBg/fr1ZttOTk42LJsxYwbat29vux2oBVEUsWXLFgwZMgQBAQFwdnZGaGgoRo0ahTVr1tilTUQtnYO9G0DU1K1btw5du3aFUqnE77//jhUrVuDAgQO4fPkyfHx8DOt99tlntd52nz598McffyAyMtKWTa6Rs2fPYvDgwYiIiMAbb7yB9u3b486dOzh//jy2bNmCRYsWwdPTs9HbRURE1Jj0x3ljISEhNn+d119/HS+99JLNt1sTixcvxnvvvYfZs2fj73//Ozw8PJCSkoL9+/dj+/btmDVrll3aRdSSMdAmqkZUVBT69esHQNdrrdFosHTpUmzbtg3PPvusYb26BMuenp6Ijo62WVtr4+OPP4ZEIsHBgwfh4eFhWD558mS89dZbEEXRLu0yJooilEolXFxc7N0UIiJqoYyP8w2pY8eODf4alpSVleHjjz/G008/jS+++MLksRkzZkCr1dqlXZWVlZXB2dkZgiDYuylENsGh40S1pD8YZ2dnmyy3NHR89erV6NmzJ9zd3eHh4YGuXbvi1VdfNTxuaej4jBkz4O7ujuvXr2Ps2LFwd3eHXC7HwoULoVKpTLafnp6OyZMnw8PDA97e3pg2bRpOnTplNoTNktzcXHh6esLd3d3i4zU90J04cQLjx4+Hn58fnJ2d0bFjR8yfP99kfywNlVu2bJnZawiCgLlz5+K///0vIiIiIJPJsGbNGgQEBOCpp54y20ZBQQFcXFywYMECwzKFQoFFixYhPDwcTk5OaNu2LebPn4+SkpIa7Q8REREAXL9+Hc8++yw6deoEV1dXtG3bFuPHj8fFixfrtD1Lx0P9cW/jxo2IiIiAq6srevbsiV9++cXs+du3b0ePHj0gk8nQoUMHfPLJJxaPpZWVlJRApVIhODjY4uMSSc3CAa1Wi1WrVqFXr15wcXGBt7c3oqOj8dNPP5nsz7Jly8ye2759e8yYMcNwXz+0/rfffsNzzz2HNm3awNXVFd988w0EQcC+ffvMtrF69WoIgoALFy4Ylp0+fRoTJkyAr68vnJ2d0bt3b3z77bc12h+ihsYebaJaSkpKAgB07ty5yvW2bNmCF198EfPmzcM///lPSCQSXL9+HQkJCdW+hlqtxoQJEzBz5kwsXLgQhw8fxltvvQUvLy+88cYbAHQHzmHDhiEvLw/vvfce7rnnHuzatQtTp06t0X4MHDgQO3bswLRp0/D888/j3nvvrXXP8e7duzF+/HhERETgww8/RLt27ZCcnIzffvutVtsxtm3bNhw5cgRvvPEGgoKCEBAQgKSkJPz3v//Ff/7zH5Ph7F9//TWUSqVhZEFpaSmGDh2K9PR0vPrqq+jRowcuXbqEN954AxcvXsTevXt5pZyIiExoNBpUVFSYLHNwcEBGRgb8/Pzw7rvvok2bNsjLy8OGDRswYMAAnD17Fl26dLHJ6+/YsQOnTp3C8uXL4e7ujvfffx+PPPIIrly5gg4dOgAAdu3ahUcffRT3338/vvnmG1RUVOCf//yn2UV/S/z9/XHPPffgs88+Q0BAAMaOHYsuXbrU+ng4Y8YMxMbGYubMmVi+fDmcnJxw5swZk3notfXcc8/hoYcewsaNG1FSUoJx48YhICAA69atw/Dhw03WXb9+Pfr06YMePXoAAA4cOIDRo0djwIAB+O9//wsvLy9s2bIFU6dORWlpqUlgT2QXIhFZtG7dOhGAePz4cVGtVotFRUXirl27xKCgIPH+++8X1Wq1yfpDhw4Vhw4darg/d+5c0dvbu8rXOHDggAhAPHDggGHZM888IwIQv/32W5N1x44dK3bp0sVw/z//+Y8IQPz1119N1nv++edFAOK6deuqfG2lUilOnDhRBCACEKVSqdi7d29xyZIlYk5OTpXP1evYsaPYsWNHsayszOo6zzzzjBgWFma2fOnSpWLlnyAAopeXl5iXl2ey/MKFCyIA8YsvvjBZfu+994p9+/Y13F+5cqUokUjEU6dOmaz3/fffiwDEnTt31mi/iIio5dMf5y3dKh/jRVEUKyoqxPLycrFTp07i3/72N8PypKQks+OufttJSUmGZZaOhwDEwMBAUaFQGJZlZWWJEolEXLlypWFZ//79RblcLqpUKsOyoqIi0c/Pz+xYasnJkyfFdu3aGfbPw8NDHDdunPjVV1+JWq222ucfPnxYBCAuWbKkyvUAiEuXLjVbHhYWJj7zzDOG+/r35+mnnzZbd8GCBaKLi4tYUFBgWJaQkCACEFetWmVY1rVrV7F3795m/1bjxo0Tg4ODRY1GU+1+ETUkDh0nqkZ0dDQcHR3h4eGB0aNHw8fHB9u3b4eDQ9UDQu69914UFBTgiSeewPbt23Hnzp0av6YgCBg/frzJsh49eiAlJcVw/9ChQ4Y2GXviiSdq9BoymQxbt25FQkICPvroIzz++OO4ffs2VqxYgYiICFy5cgWAbp50RUWFyQ0Arl69ihs3bmDmzJlwdnau8b5V54EHHjBJMgcA3bt3R9++fbFu3TrDssTERJw8eRLPPfecYdkvv/yCqKgo9OrVy6S9o0aNsmt2dyIiarq++uornDp1yuTm4OCAiooKvPPOO4iMjISTkxMcHBzg5OSEa9euITEx0WavP2zYMJNcKYGBgQgICDAc80tKSnD69GlMnDgRTk5OhvXc3d3NzhWs6d+/P65fv45du3bh1VdfxcCBA7Fv3z48/fTTmDBhgiEvi753X3/Tz9/+9ddfAQBz5syxyT7rTZo0yWzZc889h7KyMnzzzTeGZevWrYNMJsOTTz4JQDes//Lly5g2bRoAmLR57NixyMzMNJzHENkLA22iaugPwPv378fzzz+PxMTEGgWzTz31FL788kukpKRg0qRJCAgIwIABA7Bnz55qn+vq6moWvMpkMiiVSsP93NxcBAYGmj3X0rKqREREYP78+YiNjUVqaio+/PBD5Obm4vXXXwcAbNiwAY6OjiY3ALh9+zYAIDQ0tFavVx1rc8iee+45/PHHH7h8+TKAPw+6xv8W2dnZuHDhgll7PTw8IIpirS52EBFR6xAREYF+/fqZ3ABgwYIFeP311zFx4kT8/PPPOHHiBE6dOoWePXuirKzMZq/v5+dntkwmkxleIz8/H6Io1vuY7+joiFGjRmHFihXYvXs30tLSEBMTg19++cUQSA8fPtzk+Km/mH379m1IpVIEBQXVZRetsnTM79atG/r372+4uK7RaBAbG4uHH34Yvr6+AP7Mk7No0SKzY/6LL74IADzmk91xjjZRNfQHYEB31Vmj0WDNmjX4/vvvMXny5Cqf++yzz+LZZ59FSUkJDh8+jKVLl2LcuHG4evUqwsLC6tUuPz8/nDx50mx5VlZWnbcpCAL+9re/Yfny5YiPjwcAjB8/HqdOnTJbt02bNgB0Cdmq4uzsbJbEDbB+ALQ2Z+yJJ57AggULsH79eqxYsQIbN27ExIkTTXq//f394eLigi+//NLiNvz9/atsKxERkV5sbCyefvppvPPOOybL79y5A29v70Zrh4+PDwRBsDgfuz7HfD8/P8yfPx8HDx5EfHw8xo4di88//xxFRUWGdfTHzTZt2kCj0SArK8vqBXFAd4HA0jE/NzfX4vrWjvnPPvssXnzxRSQmJuLmzZvIzMw0qfSib9fixYvx6KOPWtyGrebQE9UVe7SJaun999+Hj48P3njjjRqXxHBzc8OYMWOwZMkSlJeX49KlS/Vux9ChQ1FUVGS4Cq23ZcuWGj0/MzPT4vKMjAwoFApDDVE/Pz+LV/o7d+6Mjh074ssvv7R4UNVr3749cnJyTE4QysvLsXv37hq1U8/HxwcTJ07EV199hV9++QVZWVkmw8YBYNy4cbhx44bFNvfr189i9nMiIiJLBEGATCYzWbZjxw7cunWrUdvh5uaGfv36Ydu2bSgvLzcsLy4utpidvDK1Wm010NUPgdcf87t06WLxuDlmzBgAuszfVWnfvr1JVnAA2L9/P4qLi6ttp7EnnngCzs7OWL9+PdavX4+2bdti5MiRhse7dOmCTp064fz58xaP9/369TMZjk9kD+zRJqolHx8fLF68GC+//DI2b96M6dOnW1xv9uzZcHFxweDBgxEcHIysrCysXLkSXl5e6N+/f73b8cwzz+Cjjz7C9OnT8fbbb+Oee+7Br7/+aghgqyvX8Ze//AUFBQWYNGkSoqKiIJVKcfnyZXz00UeQSCR45ZVXqm3Df/7zH4wfPx7R0dH429/+hnbt2iE1NRW7d+/Gpk2bAABTp07FG2+8gccffxx///vfoVQq8e9//xsajabW+/zcc8/hm2++wdy5cxEaGooRI0aYPD5//nz88MMPuP/++/G3v/0NPXr0gFarRWpqKn777TcsXLgQAwYMqPXrEhFR6zNu3DisX78Line truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(12,4), sharex=True)\n",
|
||||
"\n",
|
||||
"ax[0].plot(x, y_true_rising, label=\"true\")\n",
|
||||
"ax[0].scatter(x, y_rising, s=12, label=\"noisy\")\n",
|
||||
"ax[0].set_title(\"Rising S-curve\")\n",
|
||||
"ax[0].legend()\n",
|
||||
"\n",
|
||||
"ax[1].plot(x, y_true_falling, label=\"true\")\n",
|
||||
"ax[1].scatter(x, y_falling, s=12, label=\"noisy\")\n",
|
||||
"ax[1].set_title(\"Falling S-curve\")\n",
|
||||
"ax[1].legend()\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "95c826d1-9223-4fb2-9ada-d9fc1acad2fb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit the Rising S-curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "37d2fda4-40b8-4ea2-8f2c-6c3f2384d158",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"== Tuned fit settings ==\n",
|
||||
"max_calls : 500\n",
|
||||
"tolerance : 0.01\n",
|
||||
"compute_erros: True\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"== Results ==\n",
|
||||
"True rising params: [100. 0.25 60. 6. 120. 1. ]\n",
|
||||
"lmfit rising result: [ 99.467831 0.287017 60.078405 5.715461 117.257939 0.967234]\n",
|
||||
"minuit_grad (obj api ) rising result:\n",
|
||||
"{'par': array([ 99.288864, 0.291575, 60.091759, 5.675642, 117.08836 ,\n",
|
||||
" 0.963865]),\n",
|
||||
" 'par_err': array([2.853807, 0.104648, 0.764892, 0.930086, 8.309872, 0.191738]),\n",
|
||||
" 'chi2': array([7.28966])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"res_r_lmfit = fit_scurve(x, y_rising)\n",
|
||||
"\n",
|
||||
"model_r = RisingScurve()\n",
|
||||
"# model_r.FixParameter(0, 100) # Fixed p0 = 100 (optimizer does not touch the value)\n",
|
||||
"model_r.SetParameter(4, 100) # Start value p4 = 100 \n",
|
||||
"print(\"== Tuned fit settings ==\")\n",
|
||||
"model_r.compute_errors = True\n",
|
||||
"model_r.max_calls = 500\n",
|
||||
"model_r.tolerance = 0.01\n",
|
||||
"print(f\"max_calls : {model_r.max_calls}\")\n",
|
||||
"print(f\"tolerance : {model_r.tolerance}\")\n",
|
||||
"print(f\"compute_erros: {model_r.compute_errors}\")\n",
|
||||
"print(\"\\n\")\n",
|
||||
"print(\"== Results ==\")\n",
|
||||
"res_r_munuit_obj = model_r.fit(x, y_rising, np.sqrt(y_rising))\n",
|
||||
"\n",
|
||||
"print(\"True rising params: \", p_true_rising)\n",
|
||||
"print(\"lmfit rising result: \", res_r_lmfit)\n",
|
||||
"print(\"minuit_grad (obj api ) rising result:\")\n",
|
||||
"pprint(res_r_munuit_obj, sort_dicts=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "5ebf1328-954a-43ce-953b-c402fd7998f9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Fit the Falling S-curve"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"id": "5ce22d2d-1adb-47d9-bf36-6cb535fa8b77",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"True falling params: [100. 0.25 60. 6. 120. -1. ]\n",
|
||||
"lmfit falling result: [101.734479 0.225786 60.251971 6.092915 118.476735 -0.998677]\n",
|
||||
"minuit_grad (obj api ) falling result\n",
|
||||
"{'par': array([101.734403, 0.225778, 60.251667, 6.093245, 118.479457,\n",
|
||||
" -0.998603]),\n",
|
||||
" 'chi2': array([1865.728851])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"res_f_lmfit = fit_scurve2(x, y_falling)\n",
|
||||
"\n",
|
||||
"model_f = FallingScurve()\n",
|
||||
"model_f.SetParameter(2, 20) # set start p2 = 20\n",
|
||||
"model_f.SetParameter(4, 90) # set start p4 = 90\n",
|
||||
"model_f.SetParLimits(4, 80, 130)\n",
|
||||
"model_f.max_calls = 150 # this is limits the number of calls done by the minimizer (increasing the `max_calls` may help if the fit fails) \n",
|
||||
"res_f_munuit_obj = model_f.fit(x, y_falling)\n",
|
||||
"\n",
|
||||
"print(\"True falling params: \", p_true_falling)\n",
|
||||
"print(\"lmfit falling result: \", res_f_lmfit)\n",
|
||||
"print(\"minuit_grad (obj api ) falling result\")\n",
|
||||
"pprint(res_f_munuit_obj, sort_dicts=False)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"id": "fe72503a-fc77-4b17-9b17-b98055e2fc87",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(12,4))\n",
|
||||
"\n",
|
||||
"# rising\n",
|
||||
"ax[0].plot(x, y_true_rising, label=\"true\")\n",
|
||||
"ax[0].scatter(x, y_rising, s=12, label=\"data\")\n",
|
||||
"ax[0].scatter(x, scurve(x, res_r_lmfit[:6]), s=20, marker=\"x\", label=\"lmfit\")\n",
|
||||
"ax[0].plot(x, model_r(x, res_r_munuit_obj['par']), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
"ax[0].set_title(\"Rising S-curve fits\")\n",
|
||||
"ax[0].legend()\n",
|
||||
"\n",
|
||||
"# falling\n",
|
||||
"ax[1].plot(x, y_true_falling, label=\"true\")\n",
|
||||
"ax[1].scatter(x, y_falling, s=12, label=\"data\")\n",
|
||||
"ax[1].scatter(x, scurve2(x, res_f_lmfit[:6]), s=20, marker=\"x\", label=\"lmfit\")\n",
|
||||
"ax[1].plot(x, model_f(x, res_f_munuit_obj['par']), linewidth=1, color=\"green\", label=\"minuit\")\n",
|
||||
"ax[1].set_title(\"Falling S-curve fits\")\n",
|
||||
"ax[1].legend()\n",
|
||||
"\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "2c42ad91-fd59-47ba-b90d-8300f97eb27a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Quick error check"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"id": "3ba40e9a-0c64-4ef6-ba90-b1116f99b862",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Rising abs error lmfit : [0.532169 0.037017 0.078405 0.284539 2.742061 0.032766]\n",
|
||||
"Rising abs error minuit_grad: [0.711136 0.041575 0.091759 0.324358 2.91164 0.036135]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Falling abs error lmfit : [1.734479 0.024214 0.251971 0.092915 1.523265 0.001323]\n",
|
||||
"Falling abs error minuit_grad: [1.734403 0.024222 0.251667 0.093245 1.520543 0.001397]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(\"Rising abs error lmfit : \", np.abs(res_r_lmfit[:6] - p_true_rising))\n",
|
||||
"print(\"Rising abs error minuit_grad: \", np.abs(res_r_munuit_obj['par'] - p_true_rising))\n",
|
||||
"print(\"\\n\")\n",
|
||||
"print(\"Falling abs error lmfit : \", np.abs(res_f_lmfit[:6] - p_true_falling))\n",
|
||||
"print(\"Falling abs error minuit_grad: \", np.abs(res_f_munuit_obj['par'] - p_true_falling))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c9bbabfd-e24e-43ec-8622-983b3757d7a9",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Benchmark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Rising lmfit : 0.445 ms\n",
|
||||
"Rising minuit2 : 0.322 ms\n",
|
||||
"\n",
|
||||
"Falling lmfit : 0.392 ms\n",
|
||||
"Falling minuit2 : 0.309 ms\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def bench(fn, n_repeats=200):\n",
|
||||
" # warmup\n",
|
||||
" for _ in range(3):\n",
|
||||
" fn()\n",
|
||||
"\n",
|
||||
" t0 = time.perf_counter()\n",
|
||||
" for _ in range(n_repeats):\n",
|
||||
" res = fn()\n",
|
||||
" t1 = time.perf_counter()\n",
|
||||
" return res, (t1 - t0) / n_repeats\n",
|
||||
"\n",
|
||||
"model_rising = RisingScurve()\n",
|
||||
"model_falling = FallingScurve()\n",
|
||||
"\n",
|
||||
"res_r_lmfit, t_r_lmfit = bench(lambda: fit_scurve(x, y_rising))\n",
|
||||
"res_r_minuit, t_r_minuit_grad = bench(lambda: model_rising.fit(x, y_rising))\n",
|
||||
"\n",
|
||||
"res_f_lmfit, t_f_lmfit = bench(lambda: fit_scurve2(x, y_falling))\n",
|
||||
"res_f_minuit, t_f_minuit_grad = bench(lambda: model_falling.fit(x, y_falling))\n",
|
||||
"\n",
|
||||
"print(f\"Rising lmfit : {1e3*t_r_lmfit:.3f} ms\")\n",
|
||||
"print(f\"Rising minuit2 : {1e3*t_r_minuit_grad:.3f} ms\")\n",
|
||||
"print()\n",
|
||||
"print(f\"Falling lmfit : {1e3*t_f_lmfit:.3f} ms\")\n",
|
||||
"print(f\"Falling minuit2 : {1e3*t_f_minuit_grad:.3f} ms\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e694c18a-a203-4132-a472-c93c4d3ce853",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import time\n",
|
||||
"import random\n",
|
||||
"import numpy as np\n",
|
||||
"from scipy.special import erf\n",
|
||||
"import matplotlib.pyplot as plt\n",
|
||||
"from matplotlib.gridspec import GridSpec\n",
|
||||
"import sys\n",
|
||||
"# sys.path.insert(0, '/home/ferjao_k/sw/aare/build')\n",
|
||||
"sys.path.insert(0, '/home/kferjaoui/sw/aare/build')\n",
|
||||
"\n",
|
||||
"from aare import fit_scurve2\n",
|
||||
"from aare import FallingScurve, fit"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "2536939d-8f34-438e-850a-6f3bdf48f658",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'/home/kferjaoui/sw/aare/build/aare/__init__.py'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import aare\n",
|
||||
"aare.__file__"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"ROWS = 100\n",
|
||||
"COLS = 100\n",
|
||||
"N_SCAN = 100\n",
|
||||
"NOISE_FRAC = 0.05 # fraction of step height (p4)\n",
|
||||
"SEED = 42\n",
|
||||
"N_THREADS = 4\n",
|
||||
"\n",
|
||||
"N_REPEATS = 10\n",
|
||||
"N_WARMUP = 3 # untimed iterations (icache + branch predictor warmup)\n",
|
||||
"COOLDOWN = 2.0 # seconds between (method, thread_count) pairs"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1d35abe9-8d28-4417-ad49-ff1e18e298c1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Synthetic data genrator"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# Models in use\n",
|
||||
"def scurve(x, p0, p1, p2, p3, p4, p5): # rising Scurve\n",
|
||||
" z = (x - p2) / (np.sqrt(2) * p3)\n",
|
||||
" return (p0 + p1 * x) + 0.5 * (1 + erf(z)) * (p4 + p5 * (x - p2))\n",
|
||||
"\n",
|
||||
"def scurve2(x, p0, p1, p2, p3, p4, p5): # Falling Scurve\n",
|
||||
" z = (x - p2) / (np.sqrt(2) * p3)\n",
|
||||
" return (p0 + p1 * x) + 0.5 * (1 - erf(z)) * (p4 + p5 * (x - p2))\n",
|
||||
" \n",
|
||||
"def generate_3d_scurve_data(fn, rows, cols, n_scan, noise_frac, seed):\n",
|
||||
" \"\"\"\n",
|
||||
" Synthetic detector image stack with per-pixel rising S-curves.\n",
|
||||
" \n",
|
||||
" Returns\n",
|
||||
" -------\n",
|
||||
" x : (n_scan,)\n",
|
||||
" y : (rows, cols, n_scan)\n",
|
||||
" y_err : (rows, cols, n_scan)\n",
|
||||
" truths : dict {name: (rows, cols) array}\n",
|
||||
" \"\"\"\n",
|
||||
" rng = np.random.default_rng(seed)\n",
|
||||
" \n",
|
||||
" # Per-pixel ground truth - shapes (rows, cols)\n",
|
||||
" p0_true = rng.uniform(-5, 5, (rows, cols)) # baseline offset\n",
|
||||
" p1_true = rng.uniform(-0.02, 0.02, (rows, cols)) # baseline slope\n",
|
||||
" p2_true = rng.uniform(30, 70, (rows, cols)) # threshold\n",
|
||||
" p3_true = rng.uniform(2, 8, (rows, cols)) # width\n",
|
||||
" p4_true = rng.uniform(200, 800, (rows, cols)) # step height\n",
|
||||
" p5_true = rng.uniform(-0.5, 0.5, (rows, cols)) # post-step slope\n",
|
||||
" \n",
|
||||
" x = np.linspace(0, 100, n_scan)\n",
|
||||
" \n",
|
||||
" # broadcast: x -> (1, 1, n_scan), params -> (rows, cols, 1)\n",
|
||||
" y_clean = fn(\n",
|
||||
" x[None, None, :],\n",
|
||||
" p0_true[:, :, None], p1_true[:, :, None],\n",
|
||||
" p2_true[:, :, None], p3_true[:, :, None],\n",
|
||||
" p4_true[:, :, None], p5_true[:, :, None],\n",
|
||||
" )\n",
|
||||
" \n",
|
||||
" noise_sigma = noise_frac * p4_true[:, :, None] * np.ones_like(y_clean)\n",
|
||||
" y = y_clean + rng.normal(0, noise_sigma)\n",
|
||||
" y_err = noise_sigma.copy()\n",
|
||||
" \n",
|
||||
" truths = dict(p0=p0_true, p1=p1_true, p2=p2_true,\n",
|
||||
" p3=p3_true, p4=p4_true, p5=p5_true)\n",
|
||||
" return x, y, y_err, truths "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_warmup=N_WARMUP, n_repeats=N_REPEATS):\n",
|
||||
" for _ in range(n_warmup):\n",
|
||||
" res = fn()\n",
|
||||
" times = []\n",
|
||||
" for _ in range(n_repeats):\n",
|
||||
" t0 = time.perf_counter()\n",
|
||||
" res = fn()\n",
|
||||
" t1 = time.perf_counter()\n",
|
||||
" times.append(t1 - t0)\n",
|
||||
" return res, times"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "24ec20a3-8117-4718-9236-19d37897e04e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Data generation"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "13bcd5a6-8f95-4300-a002-04023d0f9bab",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Generating synthetic data: 100x100 pixels, 100 scan points, noise_frac=0.05\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
"\n",
|
||||
"x, y, yerr, truths = generate_3d_scurve_data(\n",
|
||||
" scurve2, ROWS, COLS, N_SCAN, NOISE_FRAC, SEED\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e16e2559-059b-4058-88c9-a9632988fb74",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define the tested/benched methods"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 13,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"model = FallingScurve()\n",
|
||||
"METHOD_DEFS = [\n",
|
||||
" (\"lmfit (LM)\",\n",
|
||||
" lambda nt: lambda: fit_scurve2(x, y, n_threads=nt),\n",
|
||||
" \"#FF9800\", {\"linewidth\": 3.0, \"linestyle\": \"-\"}),\n",
|
||||
"\n",
|
||||
" (\"Minuit2 (analytic)\",\n",
|
||||
" lambda nt: lambda: model.fit(x, y, n_threads=nt),\n",
|
||||
" \"#4CAF50\", {\"linewidth\": 4.0, \"linestyle\": \":\"}),\n",
|
||||
"]\n",
|
||||
"\n",
|
||||
"colors = {label: c for label, _, c, _ in METHOD_DEFS}\n",
|
||||
"styles = {label: s for label, _, _, s in METHOD_DEFS}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "bc2f50a4-ea9d-4b0d-86f6-39b2f71e9592",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Single-call benchmard"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 14,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Method time (ms) med|dp0| med|dp1| med|dp2| med|dp3| med|dp4| med|dp5|\n",
|
||||
"--------------------------------------------------------------------------------------------------------------\n",
|
||||
"[lmfit (LM) ] 1030.91 ms 22.1470 0.2336 0.2184 0.3132 14.3955 0.3997\n",
|
||||
"[Minuit2 (analytic) ] 435.57 ms 17.1099 0.2147 0.2293 0.2664 11.9425 0.3518 chi2/ndf=596.8385\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"PARAM_NAMES = [\"p0\", \"p1\", \"p2\", \"p3\", \"p4\", \"p5\"]\n",
|
||||
"ndf = N_SCAN - len(PARAM_NAMES)\n",
|
||||
"# NOTE: fit_scurve returns Ndf = n_scan - 2 in its dict, which looks wrong\n",
|
||||
"# for a 6-parameter model. We use our own ndf = n_scan - 6 everywhere.\n",
|
||||
"\n",
|
||||
"def extract_result(label, res):\n",
|
||||
" if isinstance(res, dict):\n",
|
||||
" out = {\"par\": res[\"par\"]}\n",
|
||||
" if \"par_err\" in res:\n",
|
||||
" out[\"par_err\"] = res[\"par_err\"]\n",
|
||||
" if \"chi2\" in res:\n",
|
||||
" out[\"chi2\"] = res[\"chi2\"]\n",
|
||||
" return out\n",
|
||||
" # fallback: raw array, assume shape (rows, cols, 6)\n",
|
||||
" return {\"par\": res}\n",
|
||||
"\n",
|
||||
"methods = {}\n",
|
||||
"for label, factory, _, _ in METHOD_DEFS:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" res, times = bench(factory(N_THREADS))\n",
|
||||
" entry = extract_result(label, res)\n",
|
||||
" entry[\"times\"] = times\n",
|
||||
" methods[label] = entry\n",
|
||||
"\n",
|
||||
"# ---- summary table ----\n",
|
||||
"header = f\"{'Method':24s} {'time (ms)':>10s}\"\n",
|
||||
"for pn in PARAM_NAMES:\n",
|
||||
" header += f\" {'med|d' + pn + '|':>10s}\"\n",
|
||||
"print(header)\n",
|
||||
"print(\"-\" * (26 + 12 + 12 * len(PARAM_NAMES)))\n",
|
||||
" \n",
|
||||
"for name, m in methods.items():\n",
|
||||
" par = m[\"par\"]\n",
|
||||
" med_t = np.median(m[\"times\"]) * 1e3\n",
|
||||
" \n",
|
||||
" deltas = \" \".join(\n",
|
||||
" f\"{np.median(np.abs(par[:, :, i] - truths[pn])):10.4f}\"\n",
|
||||
" for i, pn in enumerate(PARAM_NAMES)\n",
|
||||
" )\n",
|
||||
" chi2_str = \"\"\n",
|
||||
" if \"chi2\" in m:\n",
|
||||
" chi2_str = f\" chi2/ndf={np.median(m['chi2'] / ndf):.4f}\"\n",
|
||||
" \n",
|
||||
" print(f\"[{name:22s}] {med_t:8.2f} ms {deltas}{chi2_str}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7b6a3419-52a7-4b1b-92b6-b160d4227f64",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Thread scaling"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 15,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" Minuit2 (analytic) n_threads= 1 1616.61 ± 22.09 ms (161.6606 ± 2.2089 µs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 1 2927.48 ± 36.18 ms (292.7479 ± 3.6182 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 2 1471.15 ± 23.35 ms (147.1150 ± 2.3353 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 2 825.64 ± 68.98 ms (82.5642 ± 6.8976 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (analytic) n_threads= 4 507.67 ± 93.00 ms (50.7666 ± 9.2997 µs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 4 965.44 ± 137.53 ms (96.5436 ± 13.7534 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (analytic) n_threads= 8 352.28 ± 41.26 ms (35.2275 ± 4.1260 µs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 8 555.64 ± 25.59 ms (55.5636 ± 2.5588 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"thread_counts = [1, 2, 4, 8]\n",
|
||||
" \n",
|
||||
"thread_times = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
"ttimes_stddev = {label: [] for label, _, _, _ in METHOD_DEFS}\n",
|
||||
" \n",
|
||||
"for nt in thread_counts:\n",
|
||||
" run_order = list(METHOD_DEFS)\n",
|
||||
" random.shuffle(run_order)\n",
|
||||
" \n",
|
||||
" for label, factory, _, _ in run_order:\n",
|
||||
" time.sleep(COOLDOWN)\n",
|
||||
" _, times = bench(factory(nt))\n",
|
||||
" \n",
|
||||
" med = np.median(times) * 1e3\n",
|
||||
" std = np.std(times) * 1e3\n",
|
||||
" thread_times[label].append(med)\n",
|
||||
" ttimes_stddev[label].append(std)\n",
|
||||
" \n",
|
||||
" per_px = med / (ROWS * COLS) * 1e3\n",
|
||||
" per_px_std = std / (ROWS * COLS) * 1e3\n",
|
||||
" print(f\" {label:22s} n_threads={nt:2d} \"\n",
|
||||
" f\"{med:8.2f} ± {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} ± {per_px_std:.4f} µs/pixel)\")\n",
|
||||
" \n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "7aedaf85-0c70-449f-94c1-4acd6ff991eb",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Visualization: Residuals & Performance"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 16,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABjUAAAN2CAYAAACxdwtsAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3XlcVNX/P/DXwAzDvm+iqKiAC6goirgk5p6KZYqmmbtWlrlmVib4cckll6+aqR/3NbOPpZmWS2mJ+1ZuZAokCoLIvsPc3x/8uHGZYRWYxdfz8ZiH3nPPvfM+Z+6dOdxz7zkyQRAEEBERERERERERERER6TgjbQdARERERERERERERERUEezUICIiIiIiIiIiIiIivcBODSIiIiIiIiIiIiIi0gvs1CAiIiIiIiIiIiIiIr3ATg0iIiIiIiIiIiIiItIL7NQgIiIiIiIiIiIiIiK9wE4NIiIiIiIiIiIiIiLSC+zUICIiIiIiIiIiIiIivcBODSIiIiIiIiIiIiIi0gvs1CAiIiKiUo0ePRoymQwymQxBQUEV3i4oKEjcbvTo0TUWX1l+/fVXMQaZTIaoqCitxEFUmm3btkmOUW0o/v7btm3TSgxEQNV/b4iIiOjFw04NIiIiHVXygmzxl6WlJZo3b473338fDx480HaoekFXL9yV9jkbGxvD1tYWbdq0wezZsxEXF6ftUEnLjh8/jg8++AAdO3aEubl5pTps9u/fj549e8LBwQFKpRINGzbEuHHjcO/evVK3iY2NxQcffABPT0+YmZnBzs4OnTt3xsaNG1FQUFDNpSu0adMmjB07Fr6+vpDL5WL5GjZsWOZ22dnZWLp0Kdq2bQtra2tYWFjAx8cHn376KVJSUkrd7vLlyxg2bBjc3NygVCrh4uKC4OBgnDhxoppLRqVJTk7GkiVLMGTIEDRs2FByXJfXIVrVY7Qq5wPpp+LHVGhoqLbDISIiomoi13YAREREVHkZGRm4c+cO7ty5gy1btuD7779Hjx49tB0WVSOVSoWUlBRcu3YN165dw44dO3Dx4kW4u7vXahzDhg2Dj48PANT6e5PUunXr8P3331dqG0EQMGbMGGzfvl2SHh0djS1btmDPnj349ttv8corr0jWX758Gb1798azZ8/EtOzsbJw9exZnz57F//73P3z33XcwNTWteoE0mDVrVpmdEJokJiaiZ8+euHbtmiT91q1buHXrFnbv3o1Tp07Bw8NDsv6///0vJk2aBJVKJabFx8fj8OHDOHz4MD777DOEhYVVvTAV0K5dOyxbtqxG30PXRUVF4aOPPqr0dlU5Rqt6PlDt4O8NERERVRQ7NYiIiPTE0KFD4e/vj9zcXJw7dw4//PADACAzMxMjR45EVFQUlEpltb9vfn4+8vLyYGZmVu37NnTZ2dkwNjaGQqGo8DZFn3Nqaiq+++47/PnnnwCAuLg4rFy5EitWrKipcDXq06cP+vTpU6vvSZrJZDLUq1cP/v7+KCgowOHDh8vdZt26dZILuMOGDUPz5s2xb98+3L59G9nZ2Rg+fDhu374NNzc3AIWdpoMHDxYvFru7u2PMmDGIi4vD5s2bUVBQgJ9++glz585VuyCfnZ2N8ePH45NPPkGzZs00xjR37lz4+flh0KBBauuMjY3RrFkz+Pv7488//8T169fLLeOkSZPEDg0zMzNMmjQJpqam2LRpExITExEVFYU33ngD4eHhMDIqfFD9+vXreOedd8QOjQ4dOqB///44e/Ysjh49CgCYP38+2rdvj379+pUbQ1W1aNECLVq0qLH96wtTU1O0bNkS/v7+2Lt3L5KSksrMX9VjtCrnA9Ue/t4QERFRhQlERESkk3755RcBgPjaunWrZP2IESMk60+ePCkIgiAcP35cGDNmjNC6dWvBxcVFMDExEczMzIQmTZoIY8aMEf744w+19xo1apS4n65duwr3798XQkJCBAcHB0Emkwm//PKLIAiC8NVXXwmDBw8WvL29BQcHB0EulwtWVlZC69athdmzZwsJCQlq+27QoIG473nz5gk//vij0KFDB8HMzEyoW7eu8Mknnwi5ubmCIAjC+vXrhWbNmglKpVLw8PAQFi5cKKhUKrV95ufnC9u2bRO6d+8uODo6CnK5XHBychKCg4OFU6dOSfJ27dpVUk8lXw0aNJDkf/z4sTB79mzB19dXsLS0FJRKpeDp6SlMmzZNiI2NVYul+P5HjRolXL16Vejbt69ga2srABAiIyNL+4gFQSj7c05OThZMTEzEdb179y51H0OGDBHq1asnmJiYCNbW1kLnzp2F//73v0JBQYFa/jNnzgivvvqq4ObmJigUCsHCwkJo0KCB0KdPH2HevHlCcnKymLfksVHS//73P6Fdu3aCqamp4OzsLIwdO1Z48uSJWr2UVt6S9VNaXTx58kSYOXOm0K1bN6F+/fqCpaWloFAoBGdnZ6Fnz57Czp071Y6Vst4rPT1dCAsLE/z8/ARLS0vxGGrVqpUwfvx44ejRoxrrWpsyMzPF/2/durXMehSEwvPEzc1NzDN8+HBxXWJiomBlZSWumz17trjuyy+/FNNlMplw9+5dcd3HH38srjM1NRWePXsmec+hQ4cKAARXV1chIiJCLaZPP/1UACAoFArh3LlzZZax+LFX8jwtcvv2bUk9bNy4UVz3888/S9YV/0xDQkLEdA8PDyEnJ0dc16lTJ3FdQECAxvctqeSxdv/+fWHlypXi91ndunWFGTNmCGlpaZLtSn6ORT788EMxzc7OTnj06JG47sSJE4JMJhPXf/PNN5J9Xr16VRg9erTg4eEhKJVKwdLSUvD39xe++OILISsrSy32sn5ntm7dKnTt2lX8vre1tRW8vLyEkJAQYd26dRWqm/Lk5uYKeXl54nLx34zi3x3FVeUYrer5UJa8vDxh5cqVQocOHQQbGxvB2NhYsLe3F5o3by6MHDlS2Lt3r9o2CQkJwrx584R27doJNjY2gomJiVCvXj3hlVdeEb7//nsx371794QpU6YInTp1EurVqyeYm5sLJiYmQt26dYUBAwYIhw8fVtt3yeMpKytLCA0NFRo3biyYmJgIDRo0EMLCwjT+LpSm5G/4+fPnhZ49ewpWVlaCpaWl0KdPH+Hq1asat42JiRFmzJghtGjRQrCwsBCUSqXQqFEjYezYsRVqixRX8jg9evSo0KVLF8Hc3FywsbERXnvtNSEqKkrjvkp7FYmKihImTpwoNGnSRDA1NRWUSqXg5uYmdOzYUZg2bZpw+/btCtcXERER1Q52ahAREemo8jo11q5dK1m/e/duQRAEYfLkyWX+EW9iYiIcP35csq/if/x7enoKzs7Okm2KOjVatGhR5r7r1q0rufgmCNILIn5+fpKLccUvXH3wwQca9zl37lzJ/jIyMoRu3bqVGcfChQvF/JXp1Pj9998Fe3v7UvM6OzsL165dk8RTfP9+fn6Cubl5uRebK/M5F4+n+EW4IrNnzy6zfP369RM7jQSh8IKosbFxmdvcuXNH47FR8iLTV199pXF7Dw8PoXnz5hovTFa1U+PSpUvlXqAaM2ZMmXVb/L2CgoLK3NfQoUPL/Ny0rSKdGufPn5fk+fbbbyXrBwwYIK5r1qyZmN6nTx8x3dfXV7LNlStXJPv8+uuvJesvX74sdui5ubkJ9+7dE9d99tln4naDBw+WXMjWpCKdGp9//rkknsTERHGdSqUSrK2txXXvvPOOIAiFF7ctLCzE9Pfff1+yzy+++EKyzydPnpQZpyCoH2svv/yyxuOqQ4cOQnZ2trhdaZ0aubm5gr+/v+Q8FgRBSEtLk3ynjhs3ThLHmjVryjy/27VrJ+m0FITSz7l58+aVeY64uLiUWy9VUZFOjaoco1U9H8pS3oXzkp1i58+fV/t9Lf4qXt5vvvmm3O+8sLAwyf5LHk/FO+iKvz7++OMKlU8QpJ9H586dBYVCobY/c3NzITw8XLLd6dOnxe8CTS+FQiFs27at1Posq1OjY8eOGvfZuHFjseOuop0aT548EZycnMrMt379+grXFxEREdUODj9FRESkp86dOydZdnV1BQBYWlqiW7duaNGiBezt7WFmZobExEQcOXIEd+7cQW5uLqZMmYLbt29r3O+9e/cgk8kwZMgQ+Pr6IioqChYWFgAAFxcXNGnSBI0aNYK9vT1kMhkePXqE/fv3IzExEY8ePcKCBQvw5Zdfatz3tWvX0KJFCwwaNAhHjx7F5cuXAUAcDqRTp054+eWXsXv3bnEC9NWrV+PTTz+FiYkJAGDq1Kn45ZdfAABKpRLDhw9Ho0aNcO3aNfzvf/8DAHzyySfw9/dHr1698M4776B///6YNWuWGEfREE8AYGNjAwBISUnBa6+9Jg5n0qhRI4SEhEChUGD//v2IiIhAfHw8Bg0ahDt37mgc6uvatWtQKBQYPXo0GjdujFu3blVq6KniUlNTsW3bNsl48SEhIZI8e/bswZIlS8Tlfv36oUOHDnj06BG2b9+OrKwsHDlyBPPmzcOiRYsAQDKBbtOmTTFkyBDI5XL8888/uH79Oq5evVqh+GJiYjB16lRx2crKCuPGjYORkRG2bNmCyMjIKpW7NEZGRmjRogXatWsHFxcX2NraIjs7G9euXcPhw4chCAK2bt2Kt99+G+3bty9zX3fu3MGvv/4q7vett96Cl5cXnj59isjISHGdvvvjjz8ky40aNSp1OSIiAjk5OVAqlZLtytqm6D2KH5dt27bFTz/9hJ49e+Lx48fo1q0bTp8+jV27dmH+/PkAgEGDBmHv3r2Qy5//T5HisdrY2MDe3l5clslk8PDwwI0bNyR5Hzx4gIyMjEqVsbJzFp06dQoDBw5Eq1atcPToUVy6dAkAcP78eSxbtgyffvppmdsrFArs2bMHfn5+yMjIwJEjR7Bt2zacO3cO0dHRAAAvLy+sXr1a3Obs2bOYMmUKBEEAAHTu3Bk9evRAcnIytm/fjqSkJFy6dAnLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x900 with 6 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 0 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABW0AAAHqCAYAAAB/bWzAAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAA5gFJREFUeJzs3XlYVNX/B/D3nWHfdwHZVwVUVBRZVExzya3ct1JTs6xfmq1WbmVaVmZ9W63UrNzSsrS00tTEFfddUQFFQPZN1pl7f3+MTI4DiAhcwPfreXhqzj333s9ch2F4c+45giRJEoiIiIiIiIiIiIioUVDIXQARERERERERERER/YehLREREREREREREVEjwtCWiIiIiIiIiIiIqBFhaEtERERERERERETUiDC0JSIiIiIiIiIiImpEGNoSERERERERERERNSIMbYmIiIiIiIiIiIgaEYa2RERERERERERERI0IQ1siIiIiIiIiIiKiRoShLVEzdvDgQTz22GPw8PCAsbExWrRogYiICLz44os6/WJiYhATE1Pv9QiCgHnz5tX7eWpj5cqVEAQBiYmJ2rYJEybAy8tLtprOnDmDadOmISIiAubm5hAEAbt27aqy/9q1axEaGgoTExO4urpixowZKCws1OtXWFiIGTNmwNXVFSYmJggNDcXatWvrrO7Y2FhMnjwZHTt2hLGxsd51vdP//vc/tGrVCsbGxvD29sb8+fNRXl6u1y89PR0TJkyAg4MDzMzMEBERgR07dtRZ3Q3lzTffhCAICAkJqVH/1atXY+nSpXrtiYmJEAQBH3zwQR1XWD/k/n4iIiJqCIIg1Ohr165d2LVrFwRBwIYNG+Quu0Ya6neGe1VZXY359w4iopoykLsAIqofv//+OwYNGoSYmBgsXrwYLi4uSE1NxeHDh7F27Vp8+OGH2r6ff/65jJVSVQ4fPoxNmzahffv26NmzJzZv3lxl3x9//BHjxo3D5MmT8dFHH+HixYt49dVXcfbsWfz11186fYcMGYK4uDi8++67CAgIwOrVqzF69GiIoogxY8bcd907duzA9u3b0b59e1hZWVUbNL/zzjuYPXs2XnvtNfTu3RtxcXF48803cf36dSxbtkzbr7S0FD179kRubi4+/vhjODk54bPPPkPfvn2xfft2dO/e/b7rbgjHjx/HBx98gBYtWtR4n9WrV+P0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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# FIGURE 1: Residual histograms (6 panels)\n",
|
||||
"truth_arrays = [truths[pn] for pn in PARAM_NAMES]\n",
|
||||
" \n",
|
||||
"fig1, axes1 = plt.subplots(2, 3, figsize=(16, 9))\n",
|
||||
"fig1.suptitle(\n",
|
||||
" f\"Parameter Residuals — {ROWS}×{COLS} pixels, {N_SCAN} scan points\",\n",
|
||||
" fontsize=14, fontweight=\"bold\")\n",
|
||||
" \n",
|
||||
"for idx, (pname, truth) in enumerate(zip(PARAM_NAMES, truth_arrays)):\n",
|
||||
" ax = axes1.flat[idx]\n",
|
||||
" \n",
|
||||
" res_by_method = {}\n",
|
||||
" all_res = []\n",
|
||||
" for mname, m in methods.items():\n",
|
||||
" residual = (m[\"par\"][:, :, idx] - truth).ravel()\n",
|
||||
" res_by_method[mname] = residual\n",
|
||||
" all_res.append(residual)\n",
|
||||
" all_res = np.concatenate(all_res)\n",
|
||||
" \n",
|
||||
" lo, hi = np.percentile(all_res, [0.5, 99.5])\n",
|
||||
" edges = np.linspace(lo, hi, 101)\n",
|
||||
" \n",
|
||||
" for mname, r in res_by_method.items():\n",
|
||||
" ax.hist(r, bins=edges, histtype=\"step\", label=mname,\n",
|
||||
" color=colors[mname],\n",
|
||||
" linewidth=styles[mname][\"linewidth\"],\n",
|
||||
" linestyle=styles[mname][\"linestyle\"])\n",
|
||||
" \n",
|
||||
" ax.axvline(0, color=\"k\", linestyle=\"--\", linewidth=1, alpha=0.7)\n",
|
||||
" ax.set_xlabel(f\"Fitted {pname} − True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"Δ{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
"fig1.tight_layout()\n",
|
||||
"\n",
|
||||
"# FIGURE 2: bar chart + thread scaling\n",
|
||||
"fig2 = plt.figure(figsize=(14, 5))\n",
|
||||
"gs = GridSpec(1, 2, figure=fig2, width_ratios=[1, 1.3])\n",
|
||||
"\n",
|
||||
"# -- Left: bar chart at N_THREADS --\n",
|
||||
"fig2 = plt.figure(figsize=(14, 5))\n",
|
||||
"gs = GridSpec(1, 2, figure=fig2, width_ratios=[1, 1.3])\n",
|
||||
" \n",
|
||||
"ax2a = fig2.add_subplot(gs[0])\n",
|
||||
"names = list(methods.keys())\n",
|
||||
"medians = [np.median(methods[n][\"times\"]) * 1e3 for n in names]\n",
|
||||
"bars = ax2a.barh(names, medians,\n",
|
||||
" color=[colors[n] for n in names],\n",
|
||||
" edgecolor=\"white\", height=0.5)\n",
|
||||
"ax2a.set_xlabel(\"Median wall time (ms)\")\n",
|
||||
"ax2a.set_title(f\"Single call — {ROWS}×{COLS} px, {N_THREADS} threads\")\n",
|
||||
"for bar, val in zip(bars, medians):\n",
|
||||
" ax2a.text(bar.get_width() + max(medians) * 0.02,\n",
|
||||
" bar.get_y() + bar.get_height() / 2,\n",
|
||||
" f\"{val:.1f} ms\", va=\"center\", fontsize=10)\n",
|
||||
"ax2a.grid(axis=\"x\", alpha=0.3)\n",
|
||||
"ax2a.set_xlim(0, max(medians) * 1.25)\n",
|
||||
" \n",
|
||||
"ax2b = fig2.add_subplot(gs[1])\n",
|
||||
"for label, _, _, _ in METHOD_DEFS:\n",
|
||||
" tt = thread_times[label]\n",
|
||||
" sd = ttimes_stddev[label]\n",
|
||||
" speedup = [tt[0] / t for t in tt]\n",
|
||||
" speedup_err = [\n",
|
||||
" s * np.sqrt((sd[0] / tt[0])**2 + (sd[i] / tt[i])**2)\n",
|
||||
" for i, s in enumerate(speedup)\n",
|
||||
" ]\n",
|
||||
" ax2b.errorbar(thread_counts, speedup, yerr=speedup_err,\n",
|
||||
" fmt=\"o-\", label=label, color=colors[label],\n",
|
||||
" linewidth=2, markersize=7, capsize=4)\n",
|
||||
" \n",
|
||||
"ax2b.plot(thread_counts, thread_counts, \"k--\", alpha=0.4, label=\"Ideal linear\")\n",
|
||||
"ax2b.set_xlabel(\"Number of threads\")\n",
|
||||
"ax2b.set_ylabel(\"Speedup vs 1 thread\")\n",
|
||||
"ax2b.set_title(\"Thread scaling\")\n",
|
||||
"ax2b.set_xticks(thread_counts)\n",
|
||||
"ax2b.legend(fontsize=9)\n",
|
||||
"ax2b.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
"fig2.tight_layout()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cda3dc45-eec8-466a-a0e1-6d62fa37d5da",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3 (ipykernel)",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in new issue
Block a user