mirror of
https://github.com/slsdetectorgroup/aare.git
synced 2026-11-10 20:43:53 +01:00
Removing deprecated use of lmfit (#344)
Removed the lmfit based fitting and the old API for calling functions closes #296
This commit is contained in:
1 parent
e26db97b5c
commit
4f6b69fba6
18 files changed
+635
-1923
No files matched your search
+4
-60
@@ -62,7 +62,6 @@ option(AARE_FETCH_PYBIND11 "Use FetchContent to download pybind11" ON)
|
||||
option(AARE_FETCH_CATCH "Use FetchContent to download catch2" ON)
|
||||
option(AARE_FETCH_JSON "Use FetchContent to download nlohmann::json" ON)
|
||||
option(AARE_FETCH_ZMQ "Use FetchContent to download libzmq" ON)
|
||||
option(AARE_FETCH_LMFIT "Use FetchContent to download lmfit" ON)
|
||||
option(AARE_FETCH_MINUIT2 "Use FetchContent to download Minuit2" ON)
|
||||
|
||||
option(AARE_WARNINGS_AS_ERRORS "Treat warnings as errors during compilation"
|
||||
@@ -87,8 +86,8 @@ if(AARE_SYSTEM_LIBRARIES)
|
||||
set(AARE_FETCH_ZMQ
|
||||
OFF
|
||||
CACHE BOOL "Disabled FetchContent for libzmq" FORCE)
|
||||
# Still fetch lmfit and Minuit2 when setting AARE_SYSTEM_LIBRARIES since these
|
||||
# are not available on conda-forge
|
||||
# Still fetch Minuit2 when setting AARE_SYSTEM_LIBRARIES since it is not
|
||||
# available on conda-forge
|
||||
endif()
|
||||
|
||||
if(AARE_BENCHMARKS)
|
||||
@@ -97,61 +96,6 @@ endif()
|
||||
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
if(AARE_FETCH_LMFIT)
|
||||
# TODO! Should we fetch lmfit from the web or inlcude a tar.gz in the repo?
|
||||
set(LMFIT_PATCH_COMMAND git apply
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/patches/lmfit.patch)
|
||||
|
||||
# For cmake < 3.28 we can't supply EXCLUDE_FROM_ALL to FetchContent_Declare so
|
||||
# we need this workaround
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.28")
|
||||
FetchContent_Declare(
|
||||
lmfit
|
||||
GIT_REPOSITORY https://jugit.fz-juelich.de/mlz/lmfit.git
|
||||
GIT_TAG main
|
||||
PATCH_COMMAND ${LMFIT_PATCH_COMMAND}
|
||||
UPDATE_DISCONNECTED 1)
|
||||
else()
|
||||
FetchContent_Declare(
|
||||
lmfit
|
||||
GIT_REPOSITORY https://jugit.fz-juelich.de/mlz/lmfit.git
|
||||
GIT_TAG main
|
||||
PATCH_COMMAND ${LMFIT_PATCH_COMMAND}
|
||||
UPDATE_DISCONNECTED 1
|
||||
EXCLUDE_FROM_ALL 1)
|
||||
endif()
|
||||
|
||||
# Disable what we don't need from lmfit
|
||||
set(BUILD_TESTING
|
||||
OFF
|
||||
CACHE BOOL "")
|
||||
set(LMFIT_CPPTEST
|
||||
OFF
|
||||
CACHE BOOL "")
|
||||
set(LIB_MAN
|
||||
OFF
|
||||
CACHE BOOL "")
|
||||
set(LMFIT_CPPTEST
|
||||
OFF
|
||||
CACHE BOOL "")
|
||||
set(BUILD_SHARED_LIBS
|
||||
OFF
|
||||
CACHE BOOL "")
|
||||
|
||||
if(${CMAKE_VERSION} VERSION_LESS "3.28")
|
||||
if(NOT lmfit_POPULATED)
|
||||
FetchContent_Populate(lmfit)
|
||||
add_subdirectory(${lmfit_SOURCE_DIR} ${lmfit_BINARY_DIR} EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
else()
|
||||
FetchContent_MakeAvailable(lmfit)
|
||||
endif()
|
||||
|
||||
set_property(TARGET lmfit PROPERTY POSITION_INDEPENDENT_CODE ON)
|
||||
else()
|
||||
find_package(lmfit REQUIRED)
|
||||
endif()
|
||||
|
||||
if(AARE_FETCH_MINUIT2)
|
||||
# We are building Minuit2 from sources.
|
||||
|
||||
@@ -457,8 +401,7 @@ target_link_libraries(
|
||||
aare_core
|
||||
PUBLIC fmt::fmt nlohmann_json::nlohmann_json ${STD_FS_LIB} # from
|
||||
# helpers.cmake
|
||||
PRIVATE aare_compiler_flags Threads::Threads $<BUILD_INTERFACE:lmfit>
|
||||
$<BUILD_INTERFACE:aare::Minuit2>)
|
||||
PRIVATE aare_compiler_flags Threads::Threads $<BUILD_INTERFACE:aare::Minuit2>)
|
||||
|
||||
target_include_directories(
|
||||
aare_core SYSTEM
|
||||
@@ -490,6 +433,7 @@ if(AARE_TESTS)
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/defs.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/decode.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Dtype.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Fit.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Frame.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/DetectorGeometry.test.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/Interpolation.test.cpp
|
||||
|
||||
+12
-1
@@ -1,5 +1,17 @@
|
||||
# Release notes
|
||||
|
||||
## Next
|
||||
|
||||
### API Changes:
|
||||
|
||||
- Removed the lmfit dependency and the legacy ``fit_gaus``, ``fit_pol1``,
|
||||
``fit_scurve``, and ``fit_scurve2`` APIs. Use ``Gaussian``, ``Pol1``,
|
||||
``RisingScurve``, or ``FallingScurve`` and call ``model.fit(...)`` (or
|
||||
``fit(model, ...)``) instead.
|
||||
- Removed the legacy ``gaus``, ``pol1``, ``scurve``, and ``scurve2`` function
|
||||
evaluators. Model objects are callable and provide the replacement, for
|
||||
example ``Gaussian()(x, par)``.
|
||||
|
||||
## 2026.7.2
|
||||
|
||||
|
||||
@@ -151,4 +163,3 @@ dhanya.thattil@psi.ch
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -75,24 +75,7 @@ static void report_accuracy(benchmark::State &state, const TestCase &tc,
|
||||
// Benchmarks
|
||||
// ----------
|
||||
|
||||
// 1. lmcurve
|
||||
static void BM_FitGausLm(benchmark::State &state) {
|
||||
const auto &tc = get_test_cases()[state.range(0)];
|
||||
auto data = generate_gaussian_data(tc);
|
||||
auto xv = data.x.view();
|
||||
auto yv = data.y.view();
|
||||
|
||||
aare::NDArray<double, 1> result;
|
||||
for (auto _ : state) {
|
||||
result = aare::fit_gaus(xv, yv);
|
||||
benchmark::DoNotOptimize(result.data());
|
||||
}
|
||||
|
||||
report_accuracy(state, tc, result);
|
||||
state.SetLabel(tc.name);
|
||||
}
|
||||
|
||||
// 2. Minuit2, analytic gradient (no Hesse)
|
||||
// Minuit2, analytic gradient (no Hesse)
|
||||
static void BM_FitGausMinuitGrad(benchmark::State &state) {
|
||||
const auto &tc = get_test_cases()[state.range(0)];
|
||||
auto data = generate_gaussian_data(tc);
|
||||
@@ -115,7 +98,7 @@ static void BM_FitGausMinuitGrad(benchmark::State &state) {
|
||||
state.SetLabel(tc.name);
|
||||
}
|
||||
|
||||
// 3. Minuit2, analytic gradient + Hesse
|
||||
// Minuit2, analytic gradient + Hesse
|
||||
static void BM_FitGausMinuitGradHesse(benchmark::State &state) {
|
||||
const auto &tc = get_test_cases()[state.range(0)];
|
||||
auto data = generate_gaussian_data(tc);
|
||||
@@ -145,8 +128,6 @@ static void BM_FitGausMinuitGradHesse(benchmark::State &state) {
|
||||
state.SetLabel(tc.name);
|
||||
}
|
||||
|
||||
BENCHMARK(BM_FitGausLm)->DenseRange(0, 5)->Unit(benchmark::kMicrosecond);
|
||||
|
||||
BENCHMARK(BM_FitGausMinuitGrad)
|
||||
->DenseRange(0, 5)
|
||||
->Unit(benchmark::kMicrosecond);
|
||||
@@ -155,4 +136,4 @@ BENCHMARK(BM_FitGausMinuitGradHesse)
|
||||
->DenseRange(0, 5)
|
||||
->Unit(benchmark::kMicrosecond);
|
||||
|
||||
BENCHMARK_MAIN();
|
||||
BENCHMARK_MAIN();
|
||||
@@ -14,7 +14,7 @@ Requirements
|
||||
To simplify deployment we build and statically link a few libraries.
|
||||
|
||||
- fmt
|
||||
- lmfit - https://jugit.fz-juelich.de/mlz/lmfit
|
||||
- Minuit2
|
||||
- nlohmann_json
|
||||
- pybind11
|
||||
- ZeroMQ
|
||||
@@ -23,4 +23,4 @@ To simplify deployment we build and statically link a few libraries.
|
||||
|
||||
- Sphinx
|
||||
- Breathe
|
||||
- Doxygen
|
||||
- Doxygen
|
||||
+20
-9
@@ -1,19 +1,30 @@
|
||||
|
||||
Fit
|
||||
========
|
||||
Fitting
|
||||
-------
|
||||
|
||||
.. py:currentmodule:: aare
|
||||
|
||||
Aare fits one-dimensional scans and three-dimensional pixel data with
|
||||
Minuit2. Create a model object and call its :meth:`fit` method::
|
||||
|
||||
**Functions**
|
||||
model = Gaussian(compute_errors=True)
|
||||
result = model.fit(x, y, y_err)
|
||||
|
||||
.. autofunction:: gaus
|
||||
The model object is also callable, which evaluates it at the supplied points::
|
||||
|
||||
.. autofunction:: pol1
|
||||
fitted_y = model(x, result["par"])
|
||||
|
||||
The available models are ``Gaussian``, ``GaussianErfcPlateau``,
|
||||
``GaussianChargeSharing``, ``GaussianChargeSharingKb``, ``Pol1``, ``Pol2``,
|
||||
``RisingScurve``, and ``FallingScurve``. The module-level :func:`fit` function
|
||||
accepts the same model objects when a functional interface is preferred.
|
||||
|
||||
**Fitting**
|
||||
For three-dimensional data, pass an array with shape
|
||||
``(rows, columns, scan_points)`` and select the worker count with
|
||||
``n_threads``::
|
||||
|
||||
.. autofunction:: fit_gaus
|
||||
result = model.fit(x, image_data, image_errors, n_threads=8)
|
||||
|
||||
.. autofunction:: fit_pol1
|
||||
The result dictionary contains ``par`` and ``chi2``. It also contains
|
||||
``par_err`` when ``compute_errors`` is enabled.
|
||||
|
||||
.. autofunction:: fit
|
||||
@@ -1,111 +1,11 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#pragma once
|
||||
|
||||
#include <cmath>
|
||||
#include <vector>
|
||||
|
||||
#include "aare/FitModel.hpp"
|
||||
#include "aare/NDArray.hpp"
|
||||
#include "aare/utils/par.hpp"
|
||||
#include "aare/utils/task.hpp"
|
||||
|
||||
namespace aare {
|
||||
|
||||
namespace func {
|
||||
double gaus(const double x, const double *par);
|
||||
NDArray<double, 1> gaus(NDView<double, 1> x, NDView<double, 1> par);
|
||||
|
||||
double pol1(const double x, const double *par);
|
||||
NDArray<double, 1> pol1(NDView<double, 1> x, NDView<double, 1> par);
|
||||
|
||||
double scurve(const double x, const double *par);
|
||||
NDArray<double, 1> scurve(NDView<double, 1> x, NDView<double, 1> par);
|
||||
|
||||
double scurve2(const double x, const double *par);
|
||||
NDArray<double, 1> scurve2(NDView<double, 1> x, NDView<double, 1> par);
|
||||
|
||||
} // namespace func
|
||||
|
||||
static constexpr int DEFAULT_NUM_THREADS = 4;
|
||||
|
||||
/**
|
||||
* @brief Fit a 1D Gaussian to data.
|
||||
* @param data data to fit
|
||||
* @param x x values
|
||||
*/
|
||||
NDArray<double, 1> fit_gaus(NDView<double, 1> x, NDView<double, 1> y);
|
||||
|
||||
/**
|
||||
* @brief Fit a 1D Gaussian to each pixel. Data layout [row, col, values]
|
||||
* @param x x values
|
||||
* @param y y values, layout [row, col, values]
|
||||
* @param n_threads number of threads to use
|
||||
*/
|
||||
NDArray<double, 3> fit_gaus(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads = DEFAULT_NUM_THREADS);
|
||||
|
||||
/**
|
||||
* @brief Fit a 1D Gaussian with error estimates
|
||||
* @param x x values
|
||||
* @param y y values, layout [row, col, values]
|
||||
* @param y_err error in y, layout [row, col, values]
|
||||
* @param par_out output parameters
|
||||
* @param par_err_out output error parameters
|
||||
*/
|
||||
void fit_gaus(NDView<double, 1> x, NDView<double, 1> y, NDView<double, 1> y_err,
|
||||
NDView<double, 1> par_out, NDView<double, 1> par_err_out,
|
||||
double &chi2);
|
||||
|
||||
/**
|
||||
* @brief Fit a 1D Gaussian to each pixel with error estimates. Data layout
|
||||
* [row, col, values]
|
||||
* @param x x values
|
||||
* @param y y values, layout [row, col, values]
|
||||
* @param y_err error in y, layout [row, col, values]
|
||||
* @param par_out output parameters, layout [row, col, values]
|
||||
* @param par_err_out output parameter errors, layout [row, col, values]
|
||||
* @param n_threads number of threads to use
|
||||
*/
|
||||
void fit_gaus(NDView<double, 1> x, NDView<double, 3> y, NDView<double, 3> y_err,
|
||||
NDView<double, 3> par_out, NDView<double, 3> par_err_out,
|
||||
NDView<double, 2> chi2_out, int n_threads = DEFAULT_NUM_THREADS);
|
||||
|
||||
NDArray<double, 1> fit_pol1(NDView<double, 1> x, NDView<double, 1> y);
|
||||
|
||||
NDArray<double, 3> fit_pol1(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads = DEFAULT_NUM_THREADS);
|
||||
|
||||
void fit_pol1(NDView<double, 1> x, NDView<double, 1> y, NDView<double, 1> y_err,
|
||||
NDView<double, 1> par_out, NDView<double, 1> par_err_out,
|
||||
double &chi2);
|
||||
|
||||
// TODO! not sure we need to offer the different version in C++
|
||||
void fit_pol1(NDView<double, 1> x, NDView<double, 3> y, NDView<double, 3> y_err,
|
||||
NDView<double, 3> par_out, NDView<double, 3> par_err_out,
|
||||
NDView<double, 2> chi2_out, int n_threads = DEFAULT_NUM_THREADS);
|
||||
|
||||
NDArray<double, 1> fit_scurve(NDView<double, 1> x, NDView<double, 1> y);
|
||||
NDArray<double, 3> fit_scurve(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads);
|
||||
void fit_scurve(NDView<double, 1> x, NDView<double, 1> y,
|
||||
NDView<double, 1> y_err, NDView<double, 1> par_out,
|
||||
NDView<double, 1> par_err_out, double &chi2);
|
||||
void fit_scurve(NDView<double, 1> x, NDView<double, 3> y,
|
||||
NDView<double, 3> y_err, NDView<double, 3> par_out,
|
||||
NDView<double, 3> par_err_out, NDView<double, 2> chi2_out,
|
||||
int n_threads);
|
||||
|
||||
NDArray<double, 1> fit_scurve2(NDView<double, 1> x, NDView<double, 1> y);
|
||||
NDArray<double, 3> fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads);
|
||||
void fit_scurve2(NDView<double, 1> x, NDView<double, 1> y,
|
||||
NDView<double, 1> y_err, NDView<double, 1> par_out,
|
||||
NDView<double, 1> par_err_out, double &chi2);
|
||||
void fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
|
||||
NDView<double, 3> y_err, NDView<double, 3> par_out,
|
||||
NDView<double, 3> par_err_out, NDView<double, 2> chi2_out,
|
||||
int n_threads);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Minuit2-based pixel fitting.
|
||||
// Template bodies and explicit instantiations live in src/Fit.cpp.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt
|
||||
index 4efb7ed..6533660 100644
|
||||
--- a/lib/CMakeLists.txt
|
||||
+++ b/lib/CMakeLists.txt
|
||||
@@ -11,7 +11,7 @@ target_compile_definitions(${lib} PRIVATE "LMFIT_EXPORT") # for Windows DLL expo
|
||||
|
||||
target_include_directories(${lib}
|
||||
PUBLIC
|
||||
- $<BUILD_INTERFACE:${CMAKE_SOURCE_DIR}/>
|
||||
+ $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/>
|
||||
$<INSTALL_INTERFACE:include/>
|
||||
)
|
||||
|
||||
@@ -41,7 +41,6 @@ set(PYTHON_FILES
|
||||
aare/Cluster.py
|
||||
aare/calibration.py
|
||||
aare/experimental.py
|
||||
aare/func.py
|
||||
aare/RawFile.py
|
||||
aare/transform.py
|
||||
aare/ScanParameters.py
|
||||
|
||||
@@ -26,7 +26,6 @@ from .Cluster import Cluster
|
||||
|
||||
from ._aare import Gaussian, RisingScurve, FallingScurve, Pol1, Pol2, GaussianErfcPlateau, GaussianChargeSharing, GaussianChargeSharingKb
|
||||
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
|
||||
from ._aare import reduce_to_2x2, reduce_to_3x3
|
||||
@@ -42,9 +41,6 @@ from .ScanParameters import ScanParameters
|
||||
from .utils import random_pixels, random_pixel, flat_list, add_colorbar, Timer
|
||||
|
||||
|
||||
#make functions available in the top level API
|
||||
from .func import *
|
||||
|
||||
from .calibration import *
|
||||
from ._aare import apply_calibration, count_switching_pixels
|
||||
from ._aare import calculate_pedestal, calculate_pedestal_float, calculate_pedestal_g0, calculate_pedestal_g0_float
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
from ._aare import gaus, pol1, scurve, scurve2
|
||||
+42
-70
@@ -1,106 +1,78 @@
|
||||
# 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 Gaussian, fit
|
||||
from aare import pol1
|
||||
|
||||
textpm = f"±" #
|
||||
textmu = f"μ" #
|
||||
textsigma = f"σ" #
|
||||
from aare import Gaussian, Pol1
|
||||
|
||||
textpm = "±"
|
||||
textmu = "μ"
|
||||
textsigma = "σ"
|
||||
|
||||
|
||||
# ================================= Gauss fit =================================
|
||||
# Parameters
|
||||
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 = 10
|
||||
|
||||
# Generate Gaussian distribution
|
||||
data = np.random.normal(mu, sigma, num_points)
|
||||
mu = np.random.uniform(1, 100)
|
||||
sigma = np.random.uniform(4, 20)
|
||||
data = np.random.normal(mu, sigma, 10000)
|
||||
counts, edges = np.histogram(data, bins=100)
|
||||
|
||||
x = 0.5 * (edges[:-1] + edges[1:]) # proper bin centers
|
||||
x = 0.5 * (edges[:-1] + edges[1:])
|
||||
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))
|
||||
|
||||
# Add the errors as error bars in the step plot
|
||||
ax0.errorbar(x, y, yerr=yerr, fmt=". ", capsize=5)
|
||||
ax0.grid()
|
||||
|
||||
# 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)
|
||||
gaussian = Gaussian(compute_errors=True)
|
||||
result = gaussian.fit(x, y, yerr)
|
||||
par = result["par"]
|
||||
err = result["par_err"]
|
||||
chi2 = result["chi2"]
|
||||
print(f"Gaussian.fit: par={par}, err={err}, chi2={chi2}")
|
||||
|
||||
# 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, gaussian(x, par_lm), marker="", label="fit_gaus")
|
||||
ax0.plot(x, gaussian(x, par_m2), marker="", linestyle=":", label="fit_gaus_minuit_grad")
|
||||
x_plot = np.linspace(x[0], x[-1], 1000)
|
||||
ax0.plot(x_plot, gaussian(x_plot, par), marker="", label="Gaussian.fit")
|
||||
ax0.legend()
|
||||
ax0.set(xlabel="x", ylabel="Counts",
|
||||
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"A={par[0]:0.2f}{textpm}{err[0]:0.2f} "
|
||||
f"{textmu}={par[1]:0.2f}{textpm}{err[1]:0.2f} "
|
||||
f"{textsigma}={par[2]:0.2f}{textpm}{err[2]:0.2f}\n"
|
||||
f"(truth: {textmu}={mu:0.2f}, {textsigma}={sigma:0.2f})"
|
||||
),
|
||||
)
|
||||
fig0.tight_layout()
|
||||
|
||||
|
||||
|
||||
# ================================= pol1 fit =================================
|
||||
# Parameters
|
||||
# ================================= Pol1 fit =================================
|
||||
n_points = 40
|
||||
|
||||
# Generate random slope and intercept (origin)
|
||||
slope = np.random.uniform(-10, 10) # Random slope between 0.5 and 2.0
|
||||
intercept = np.random.uniform(-10, 10) # Random intercept between -10 and 10
|
||||
|
||||
# Generate random x values
|
||||
slope = np.random.uniform(-10, 10)
|
||||
intercept = np.random.uniform(-10, 10)
|
||||
x_values = np.random.uniform(-10, 10, n_points)
|
||||
|
||||
# Calculate y values based on the linear function y = mx + b + error
|
||||
errors = np.abs(np.random.normal(0, np.random.uniform(1, 5), n_points))
|
||||
var_points = np.random.normal(0, np.random.uniform(0.1, 2), n_points)
|
||||
y_values = slope * x_values + intercept + var_points
|
||||
y_values = slope * x_values + intercept + np.random.normal(0, 1, n_points)
|
||||
|
||||
fig1, ax1 = plt.subplots(1, 1, num=1, figsize=(12, 8))
|
||||
ax1.errorbar(x_values, y_values, yerr=errors, fmt=". ", capsize=5)
|
||||
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="")
|
||||
ax1.set(xlabel="x", ylabel="y", title=f"a = {par[0]:0.2f}{textpm}{err[0]:0.2f}\n"
|
||||
f"b = {par[1]:0.2f}{textpm}{err[1]:0.2f}\n"
|
||||
f"(init: {slope:0.2f}, {intercept:0.2f})")
|
||||
pol1 = Pol1(compute_errors=True)
|
||||
result = pol1.fit(x_values, y_values, errors)
|
||||
par = result["par"]
|
||||
err = result["par_err"]
|
||||
|
||||
x_plot = np.linspace(np.min(x_values), np.max(x_values), 1000)
|
||||
ax1.plot(x_plot, pol1(x_plot, par), marker="")
|
||||
ax1.set(
|
||||
xlabel="x",
|
||||
ylabel="y",
|
||||
title=(
|
||||
f"intercept = {par[0]:0.2f}{textpm}{err[0]:0.2f}\n"
|
||||
f"slope = {par[1]:0.2f}{textpm}{err[1]:0.2f}\n"
|
||||
f"(truth: {intercept:0.2f}, {slope:0.2f})"
|
||||
),
|
||||
)
|
||||
fig1.tight_layout()
|
||||
|
||||
plt.show()
|
||||
|
||||
+1
-447
@@ -227,452 +227,6 @@ fit_dispatch(const aare::FitModel<Model> &model,
|
||||
}
|
||||
|
||||
void define_fit_bindings(py::module &m) {
|
||||
|
||||
// TODO! Evaluate without converting to double
|
||||
m.def(
|
||||
"gaus",
|
||||
[](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 par_view = make_view_1d(par);
|
||||
auto y = new NDArray<double, 1>{aare::func::gaus(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D Gaussian function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the Gaussian function.
|
||||
par : array_like
|
||||
The parameters of the Gaussian function. The first element is the amplitude, the second element is the mean, and the third element is the standard deviation.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"pol1",
|
||||
[](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 par_view = make_view_1d(par);
|
||||
auto y = new NDArray<double, 1>{aare::func::pol1(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D polynomial function for all points in x using parameters par. (p0+p1*x)
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the polynomial function.
|
||||
par : array_like
|
||||
The parameters of the polynomial function. The first element is the intercept, and the second element is the slope.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"scurve",
|
||||
[](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 par_view = make_view_1d(par);
|
||||
auto y =
|
||||
new NDArray<double, 1>{aare::func::scurve(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D scurve function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the scurve function.
|
||||
par : array_like
|
||||
The parameters of the scurve function. The first element is the background slope, the second element is the background intercept, the third element is the mean, the fourth element is the standard deviation, the fifth element is inflexion point count number, and the sixth element is C.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"scurve2",
|
||||
[](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 par_view = make_view_1d(par);
|
||||
auto y =
|
||||
new NDArray<double, 1>{aare::func::scurve2(x_view, par_view)};
|
||||
return return_image_data(y);
|
||||
},
|
||||
R"(
|
||||
Evaluate a 1D scurve2 function for all points in x using parameters par.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
The points at which to evaluate the scurve function.
|
||||
par : array_like
|
||||
The parameters of the scurve2 function. The first element is the background slope, the second element is the background intercept, the third element is the mean, the fourth element is the standard deviation, the fifth element is inflexion point count number, and the sixth element is C.
|
||||
)",
|
||||
py::arg("x"), py::arg("par"));
|
||||
|
||||
m.def(
|
||||
"fit_gaus",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
auto y_view = make_view_3d(y);
|
||||
auto x_view = make_view_1d(x);
|
||||
*par = aare::fit_gaus(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto y_view = make_view_1d(y);
|
||||
auto x_view = make_view_1d(x);
|
||||
*par = aare::fit_gaus(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
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.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_gaus",
|
||||
[](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::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
// Allocate memory for the output
|
||||
// Need to have pointers to allow python to manage
|
||||
// the memory
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 3});
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 3});
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
// Make views of the numpy arrays
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
aare::fit_gaus(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 3);
|
||||
} else if (y.ndim() == 1) {
|
||||
// Allocate memory for the output
|
||||
// Need to have pointers to allow python to manage
|
||||
// the memory
|
||||
auto par = new NDArray<double, 1>({3});
|
||||
auto par_err = new NDArray<double, 1>({3});
|
||||
|
||||
// Decode the numpy arrays
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
aare::fit_gaus(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 3);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
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.
|
||||
)",
|
||||
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,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_pol1(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_pol1(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_pol1",
|
||||
[](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::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 2});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 2});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_pol1(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({2});
|
||||
auto par_err = new NDArray<double, 1>({2});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_pol1(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
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.
|
||||
)",
|
||||
py::arg("x"), py::arg("y"), py::arg("y_err"), py::arg("n_threads") = 4);
|
||||
|
||||
//=========
|
||||
m.def(
|
||||
"fit_scurve",
|
||||
[](py::array_t<double, py::array::c_style | py::array::forcecast> x,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_scurve(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_scurve(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_scurve",
|
||||
[](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::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_scurve(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({2});
|
||||
auto par_err = new NDArray<double, 1>({2});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_scurve(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
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.
|
||||
)",
|
||||
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,
|
||||
py::array_t<double, py::array::c_style | py::array::forcecast> y,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>{};
|
||||
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_3d(y);
|
||||
*par = aare::fit_scurve2(x_view, y_view, n_threads);
|
||||
return return_image_data(par);
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>{};
|
||||
auto x_view = make_view_1d(x);
|
||||
auto y_view = make_view_1d(y);
|
||||
*par = aare::fit_scurve2(x_view, y_view);
|
||||
return return_image_data(par);
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
py::arg("x"), py::arg("y"), py::arg("n_threads") = 4);
|
||||
|
||||
m.def(
|
||||
"fit_scurve2",
|
||||
[](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::array_t<double, py::array::c_style | py::array::forcecast> y_err,
|
||||
int n_threads) {
|
||||
if (y.ndim() == 3) {
|
||||
auto par = new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto par_err =
|
||||
new NDArray<double, 3>({y.shape(0), y.shape(1), 6});
|
||||
|
||||
auto y_view = make_view_3d(y);
|
||||
auto y_view_err = make_view_3d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
auto chi2 = new NDArray<double, 2>({y.shape(0), y.shape(1)});
|
||||
|
||||
aare::fit_scurve2(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2->view(), n_threads);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = return_image_data(chi2),
|
||||
"Ndf"_a = y.shape(2) - 2);
|
||||
|
||||
} else if (y.ndim() == 1) {
|
||||
auto par = new NDArray<double, 1>({6});
|
||||
auto par_err = new NDArray<double, 1>({6});
|
||||
|
||||
auto y_view = make_view_1d(y);
|
||||
auto y_view_err = make_view_1d(y_err);
|
||||
auto x_view = make_view_1d(x);
|
||||
|
||||
double chi2 = 0;
|
||||
|
||||
aare::fit_scurve2(x_view, y_view, y_view_err, par->view(),
|
||||
par_err->view(), chi2);
|
||||
return py::dict("par"_a = return_image_data(par),
|
||||
"par_err"_a = return_image_data(par_err),
|
||||
"chi2"_a = chi2, "Ndf"_a = y.size() - 2);
|
||||
|
||||
} else {
|
||||
throw std::runtime_error("Data must be 1D or 3D");
|
||||
}
|
||||
},
|
||||
R"(
|
||||
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.
|
||||
)",
|
||||
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::GaussianErfcPlateau>(m, "GaussianErfcPlateau");
|
||||
@@ -794,4 +348,4 @@ void define_fit_bindings(py::module &m) {
|
||||
)",
|
||||
py::arg("model"), py::arg("x"), py::arg("y"),
|
||||
py::arg("y_err") = py::none(), py::arg("n_threads") = 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
# SPDX-License-Identifier: MPL-2.0
|
||||
import numpy as np
|
||||
import aare
|
||||
|
||||
|
||||
|
||||
def test_gaussian_model_evaluates_and_fits_data():
|
||||
x = np.linspace(-5.0, 5.0, 51)
|
||||
expected = np.array([20.0, 0.5, 1.2])
|
||||
model = aare.Gaussian()
|
||||
|
||||
y = model(x, expected)
|
||||
result = model.fit(x, y)
|
||||
|
||||
np.testing.assert_allclose(result["par"], expected, atol=2e-3)
|
||||
@@ -1,488 +1,413 @@
|
||||
{
|
||||
"cells":
|
||||
[
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"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/aare/build')\n",
|
||||
"from aare import fit_gaus # lmfit\n",
|
||||
"from aare import Gaussian, fit # minuit2"
|
||||
]
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"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",
|
||||
"from aare import Gaussian\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"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": null,
|
||||
"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": null,
|
||||
"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": null,
|
||||
"id": "9cc999b9-e0d8-4deb-ae29-e8bd0141534b",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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": null,
|
||||
"id": "a98a41f3-fd2e-4bfc-9ec6-0d23dd38e896",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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": null,
|
||||
"id": "1f6bc651-80c1-41dd-8a05-7f15aba006aa",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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",
|
||||
" (\"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": null,
|
||||
"id": "5a417145-ce42-4c3a-a7bf-05ff97ba0450",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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",
|
||||
" \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": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"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": 4,
|
||||
"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": 5,
|
||||
"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": 6,
|
||||
"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": 7,
|
||||
"id": "a98a41f3-fd2e-4bfc-9ec6-0d23dd38e896",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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 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": 8,
|
||||
"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": 9,
|
||||
"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) ] 90.30 ms 6.272 0.0940 0.0949\n",
|
||||
"[Minuit2 (obj API) ] 59.60 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": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [],
|
||||
"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": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"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": []
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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} \u00b1 {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} \u00b1 {per_px_std:.4f} \u03bcs/pixel)\")\n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"# =============================\n",
|
||||
"# FIGURE 1: Residual histograms\n",
|
||||
"# =============================\n",
|
||||
"param_names = [\"A\", \"\u03bc\", \"\u03c3\"]\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 \u2014 {ROWS}\u00d7{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} \u2212 True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"\u0394{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 \u2014 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 \u2014 {ROWS}\u00d7{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 \u2192 \u03b4S/S = sqrt((\u03b4t0/t0)\u00b2 + (\u03b4t/t)\u00b2)\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)",
|
||||
|
||||
@@ -2,26 +2,19 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"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",
|
||||
"\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"
|
||||
"from aare import RisingScurve, FallingScurve\n",
|
||||
"from pprint import pprint\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -34,7 +27,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -58,7 +51,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": null,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -92,22 +85,10 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": null,
|
||||
"id": "9a0b3292-7c57-4483-aecc-7ef683ff0b62",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"fig, ax = plt.subplots(1, 2, figsize=(12,4), sharex=True)\n",
|
||||
"\n",
|
||||
@@ -134,38 +115,11 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 18,
|
||||
"execution_count": null,
|
||||
"id": "37d2fda4-40b8-4ea2-8f2c-6c3f2384d158",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Paramter list of the Scurve:\n",
|
||||
"['p0', 'p1', 'mu', 'sigma', 'A', 'C']\n",
|
||||
"6 \n",
|
||||
"\n",
|
||||
"== 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([100. , 0.269137, 60.050193, 5.729932, 117.777635,\n",
|
||||
" 0.984073]),\n",
|
||||
" 'par_err': array([1. , 0.054406, 0.747753, 0.909864, 7.887499, 0.174058]),\n",
|
||||
" 'chi2': array([7.351743])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"res_r_lmfit = fit_scurve(x, y_rising)\n",
|
||||
"\n",
|
||||
"model_r = RisingScurve()\n",
|
||||
"print(\"Paramter list of the Scurve:\")\n",
|
||||
"# print(model_r.GetParNames())\n",
|
||||
@@ -187,7 +141,6 @@
|
||||
"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)"
|
||||
]
|
||||
@@ -202,26 +155,11 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 19,
|
||||
"execution_count": null,
|
||||
"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.73448 0.225786 60.251971 6.092915 118.476735 -0.998677]\n",
|
||||
"minuit_grad (obj api ) falling result\n",
|
||||
"{'par': array([101.741754, 0.225723, 60.251604, 6.092887, 118.473268,\n",
|
||||
" -0.998565]),\n",
|
||||
" 'chi2': array([1865.728797])}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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",
|
||||
@@ -230,36 +168,22 @@
|
||||
"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": 20,
|
||||
"execution_count": null,
|
||||
"id": "fe72503a-fc77-4b17-9b17-b98055e2fc87",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"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p06duuR9appGmzZt8i2rVasWhw4d8jxft26dp015Pfnkk5e0D7PZzMKFC9m5cyeTJk3iiSee4OTJk4wdO5aqVauyZ88ewD1P2uFw5HsA7N27l3///ZdevXrh5eV1ycd2MQ888EC+JHMANWvW5K677mLmzJmeZbt27eKPP/6gZ8+enmU//vgjNWrU4M4778zX3hYtWly37O533303sbGxvP3222zcuBG73X7N9ymEEOLKzZ49m02bNuV7GAwGHA4H77zzDtWqVcNkMmEwGDCZTOzbt49du3YV2f6bNm2aL1dKREQE4eHhnnN+ZmYmf/75J+3atcNkMnnW8/PzO+9aoTD16tVj//79LF26lDfeeIMGDRqwatUqunXrxqOPPurJy5Lbu5/7yJ2//fPPPwPQr1+/IjnmXB06dDhvWc+ePcnOzmb+/PmeZTNnzsRsNtOlSxfAPax/9+7dPPXUUwD52tyqVSvi4+M91zHXyp133onJZKJPnz7MmjWLAwcOXNP9iVuPBNrippd7Al69ejXPPfccu3btuqRg9umnn2bGjBkcOnSIDh06EB4ezj333MOKFSsu+l4fH5/zglez2YzVavU8P336NBEREee9t6BlF1K1alUGDhzInDlzOHz4MBMnTuT06dO8+eabAMyaNQuj0ZjvAe65yECBw6uvRmFzyHr27Mlvv/3G7t27gbMn3bw/ixMnTrBt27bz2muxWFBKXdbNjis1f/58unfvzvTp02nQoAHBwcF069aNhISEa75vIYQQl69q1arUrVs33wNg0KBBvPnmm7Rr147Fixfz+++/s2nTJu644w6ys7OLbP8hISHnLTObzZ59JCcno5S66nO+0WikRYsWjB07lmXLlnHkyBGaNGnCjz/+6AmkH3zwwXznz9yb2SdPnkSv1xMZGXklh1iogs751atXp169ep6b606nkzlz5tC2bVuCg4OBs3lyhgwZct45v2/fvgDX/JxfsWJFVq5cSXh4OP369aNixYpUrFiRyZMnX9P9iluHzNEWN73cEzC47zo7nU6mT5/OggUL6Nix4wXf+8wzz/DMM8+QmZnJL7/8wsiRI2ndujV79+4lJibmqtoVEhLCH3/8cd7yqwnoNE3j5ZdfZsyYMZ4M3G3atGHTpk3nrZs7J/no0aMX3KaXl9d5Sdyg8BNgYXPGnnzySQYNGkRsbCxjx47liy++oF27dvl6v0NDQ/H29mbGjBkFbiM0NPSCbS0KoaGhfPDBB3zwwQccPnyYH374gddff53ExESWLl16zfcvhBCiaMyZM4du3brxzjvv5Ft+6tSp61rmKSgoCE3TCpyPfTXn/JCQEAYOHMjatWvZvn07rVq14pNPPiE9Pd2zTuLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1200x400 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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",
|
||||
@@ -267,7 +191,6 @@
|
||||
"# 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",
|
||||
@@ -285,29 +208,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 21,
|
||||
"execution_count": null,
|
||||
"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. 0.019137 0.050193 0.270068 2.222365 0.015927]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Falling abs error lmfit : [1.73448 0.024214 0.251971 0.092915 1.523265 0.001323]\n",
|
||||
"Falling abs error minuit_grad: [1.741754 0.024277 0.251604 0.092887 1.526732 0.001435]\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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))"
|
||||
"print(\"Rising absolute parameter error:\", np.abs(res_r_munuit_obj['par'] - p_true_rising))\n",
|
||||
"print(\"Falling absolute parameter error:\", np.abs(res_f_munuit_obj['par'] - p_true_falling))\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -320,25 +227,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"execution_count": null,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Rising lmfit : 0.334 ms\n",
|
||||
"Rising minuit2 : 0.239 ms\n",
|
||||
"\n",
|
||||
"Falling lmfit : 0.294 ms\n",
|
||||
"Falling minuit2 : 0.214 ms\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def bench(fn, n_repeats=200):\n",
|
||||
" # warmup\n",
|
||||
" for _ in range(3):\n",
|
||||
" fn()\n",
|
||||
"\n",
|
||||
@@ -348,20 +242,14 @@
|
||||
" t1 = time.perf_counter()\n",
|
||||
" return res, (t1 - t0) / n_repeats\n",
|
||||
"\n",
|
||||
"model_rising = RisingScurve()\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",
|
||||
"_, t_rising = bench(lambda: model_rising.fit(x, y_rising))\n",
|
||||
"_, t_falling = bench(lambda: model_falling.fit(x, y_falling))\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\")"
|
||||
"print(f\"Rising Minuit2: {1e3*t_rising:.3f} ms\")\n",
|
||||
"print(f\"Falling Minuit2: {1e3*t_falling:.3f} ms\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": null,
|
||||
"id": "efef8e20-6571-4561-8f0b-6048b57907de",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -13,30 +13,15 @@
|
||||
"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/aare/build')\n",
|
||||
"\n",
|
||||
"from aare import fit_scurve2\n",
|
||||
"from aare import FallingScurve, fit"
|
||||
"from aare import FallingScurve\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": null,
|
||||
"id": "2536939d-8f34-438e-850a-6f3bdf48f658",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"'/home/ferjao_k/aare/build/aare/__init__.py'"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import aare\n",
|
||||
"aare.__file__"
|
||||
@@ -44,7 +29,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": null,
|
||||
"id": "35772e2c-37c6-4986-a9c0-7dca4a034827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -71,7 +56,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": null,
|
||||
"id": "06c8fddb-56d9-4f84-8b21-4ffd8b7bd26f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -127,7 +112,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": null,
|
||||
"id": "dc0b52b6-9fc3-453d-b6eb-e325a2b8342e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -154,19 +139,10 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": null,
|
||||
"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"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"print(f\"Generating synthetic data: {ROWS}x{COLS} pixels, \"\n",
|
||||
" f\"{N_SCAN} scan points, noise_frac={NOISE_FRAC}\\n\")\n",
|
||||
@@ -186,17 +162,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": null,
|
||||
"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",
|
||||
@@ -216,27 +188,13 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"execution_count": null,
|
||||
"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) ] 359.31 ms 18.6946 0.2249 0.2177 0.2663 11.9347 0.3635\n",
|
||||
"[Minuit2 (analytic) ] 257.18 ms 17.1035 0.2147 0.2293 0.2668 11.9449 0.3518 chi2/ndf=596.8385\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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",
|
||||
@@ -245,8 +203,6 @@
|
||||
" 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",
|
||||
@@ -288,35 +244,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"execution_count": null,
|
||||
"id": "55ecc77c-0823-408d-a811-8e4f4900f332",
|
||||
"metadata": {
|
||||
"scrolled": true
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
" lmfit (LM) n_threads= 1 1404.39 ± 11.48 ms (140.4392 ± 1.1478 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 1 1022.42 ± 1.69 ms (102.2417 ± 0.1690 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 2 708.78 ± 6.14 ms (70.8775 ± 0.6142 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 2 521.15 ± 0.96 ms (52.1151 ± 0.0962 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" Minuit2 (analytic) n_threads= 4 256.73 ± 2.40 ms (25.6728 ± 0.2399 µs/pixel)\n",
|
||||
" lmfit (LM) n_threads= 4 352.83 ± 0.77 ms (35.2833 ± 0.0767 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
" lmfit (LM) n_threads= 8 226.62 ± 1.48 ms (22.6616 ± 0.1479 µs/pixel)\n",
|
||||
" Minuit2 (analytic) n_threads= 8 164.39 ± 0.51 ms (16.4386 ± 0.0509 µs/pixel)\n",
|
||||
"\n",
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"thread_counts = [1, 2, 4, 8]\n",
|
||||
" \n",
|
||||
@@ -339,8 +272,8 @@
|
||||
" 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",
|
||||
" f\"{med:8.2f} \u00b1 {std:6.2f} ms \"\n",
|
||||
" f\"({per_px:.4f} \u00b1 {per_px_std:.4f} \u00b5s/pixel)\")\n",
|
||||
" \n",
|
||||
" print(\"\\n\")"
|
||||
]
|
||||
@@ -355,49 +288,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"execution_count": null,
|
||||
"id": "d5f3152d-be84-420e-8045-9c42ac5c24cb",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png":
|
||||
"iVBORw0KGgoAAAANSUhEUgAABjUAAAN2CAYAAACxdwtsAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd0VNXWwPGdQioBQkjo3RCQoogIogKKFBXQh4oVbE95gigKYsGCigqIgB9gQyyAgKgPlfcUxQYqVREVxYCCCNIC0tOT+dY+vDvMnUxCyiRT8v+tdTOZO3fu3DYzZ84+Z58Qh8PhEAAAAAAAAAAAAD8X6usNAAAAAAAAAAAAKA6CGgAAAAAAAAAAICAQ1AAAAAAAAAAAAAGBoAYAAAAAAAAAAAgIBDUAAAAAAAAAAEBAIKgBAAAAAAAAAAACAkENAAAAAAAAAAAQEAhqAAAAAAAAAACAgEBQAwAAAAAAAAAABASCGgAAACjUjTfeKCEhIWbq3r17sY+ULms9T9fhC19++aVzG3T6448/fLIdQGFef/112zXqC66vr9sDBNr3DQAAqHwIagAA4KfcK2Rdp6pVq8qpp54qw4cPly1btvh6UwOCv1bcFXaew8LCpEaNGnLGGWfIfffdJ7t37/b1psLHli5dKnfddZd06dJFYmJiShSwWbhwofTs2VMSEhIkMjJSmjRpIrfccots3ry50Ofs2rXLvF5ycrJER0dLfHy8nHvuufLyyy9LXl5eOeyhyMyZM+Xmm2+Wtm3bSnh4uHP/dHuLkpmZKRMnTpQOHTpItWrVJDY2Vtq0aSMPPfSQHDp0qNDnffvtt3L11VdLvXr1zHGpXbu29O/fXz799NNy2Dt4cvDgQZkwYYJceeWV5jy7XtcnC4iW9hotzfsBgcn1mho7dqyvNwcAAHhJuLdWBAAAKs6xY8dk48aNZnr11Vfl/ffflwsvvJBTEETy8/NNZez3339vptmzZ8uaNWukYcOGFbodWuGrlcOqol8bdjNmzDDv9ZJwOBxy0003yRtvvGGbv23bNvPZMW/ePHn33Xfl4osvLlDZ37t3b/n7779tgYNvvvnGTP/+97/lvffek6ioKK+epnvvvbfIIIQn+/fvNxXU+j5x9fPPP5vpzTfflM8//1yaNm1qe/yVV16RIUOGmPeaZe/evbJ48WIzPfLII/LYY49JeerYsaM888wzUplpQO7+++8v8fNKc42W9v2AisH3DQAAKC6CGgAABIirrrpKzjzzTMnOzpaVK1fKf/7zHzM/PT1dBg0aZCqGtMWpt+Xm5kpOTo5pBYuS0Qo27XFRpUqVEp/nw4cPmwq5n376yczXnhpTpkyRyZMnV+hp6NOnj5nge9rSuEGDBub60FboWvFenECIawWuVhpqL68FCxbIL7/8Yq7Ra6+91vyvvRWsoOkVV1zhrCzWYJZWBOs1OGvWLPPaH3/8sTz88MMFKuR1ff/85z9lzJgx0qpVK4/bpM9r3769DBgwoMBj+n7R5+k+6rW/fv36k+6jBiasgIZ+Tul9rcjWXh8a8NDPxmuuuUZWrFghoaHHO6rrem+//XZnQKNz587St29fUxn+0UcfmXmPP/64nHXWWXLJJZdIeWndurWZKjs9X+3atTPnff78+XLgwIEily/tNVqa9wMqDt83AACg2BwAAMAvffHFFw79qram1157zfb4ddddZ3v8s88+M/OXLl3quOmmmxynn366o3bt2o6IiAhHdHS045RTTjHzf/zxxwKvdcMNNzjX061bN8fvv//uGDhwoCMhIcEREhJitkW9+OKLjiuuuMKRkpJiHgsPD3fExcWZ17rvvvscaWlpBdbduHFj57offfRRx4cffujo3Lmz2ab69es7xowZ48jOzjbLvvDCC45WrVo5IiMjHU2bNnU8+eSTjvz8/ALrzM3Ndbz++uuOHj16OGrVqmW2IzEx0dG/f3/H559/bltW98f1OLlPun2udu7cafalbdu2jqpVq5ptSU5Odtx9992OXbt2FdgW1/XrcVy3bp3joosuctSoUcPM27p1a6nP88GDB835sx7r3bt3oeu48sorHQ0aNDDLV6tWzXHuuec6XnnlFUdeXl6B5ZcvX+647LLLHPXq1XNUqVLFERsba45Dnz59zDnS1y3s2nD373//29GxY0dHVFSUIykpyXHzzTc79uzZU+C4FLa/7sensGOh6xw1apTj/PPPdzRq1MicG912fc2ePXs65syZU+BaKeq1jh496njssccc7du3N+uyrqHTTjvN8c9//tPx0UcfOfxNenq68389NkUdR+t9oufYWubaa691PrZ//37z3rUe02ve8vzzzzvn6/v/119/dT724IMPOh/Tc/7333/bXvOqq64yj9WpU8eRmppaYJse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 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":
|
||||
"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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1400x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"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",
|
||||
" f\"Parameter Residuals \u2014 {ROWS}\u00d7{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",
|
||||
@@ -421,9 +322,9 @@
|
||||
" 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_xlabel(f\"Fitted {pname} \u2212 True {pname}\")\n",
|
||||
" ax.set_ylabel(\"Pixel count\")\n",
|
||||
" ax.set_title(f\"Δ{pname}\")\n",
|
||||
" ax.set_title(f\"\u0394{pname}\")\n",
|
||||
" ax.legend(fontsize=8)\n",
|
||||
" ax.grid(alpha=0.3)\n",
|
||||
" \n",
|
||||
@@ -444,7 +345,7 @@
|
||||
" 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",
|
||||
"ax2a.set_title(f\"Single call \u2014 {ROWS}\u00d7{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",
|
||||
|
||||
+1
-452
@@ -4,7 +4,6 @@
|
||||
#include "Minuit2/FunctionMinimum.h"
|
||||
#include "Minuit2/MnHesse.h"
|
||||
#include "Minuit2/MnMigrad.h"
|
||||
#include "Minuit2/MnPrint.h"
|
||||
#include "Minuit2/MnStrategy.h"
|
||||
#include "Minuit2/MnUserParameters.h"
|
||||
#include "aare/Models.hpp"
|
||||
@@ -12,461 +11,11 @@
|
||||
#include "aare/utils/task.hpp"
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <lmcurve2.h>
|
||||
#include <lmfit.hpp>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <thread>
|
||||
#include <type_traits>
|
||||
|
||||
namespace aare {
|
||||
|
||||
namespace func {
|
||||
|
||||
double gaus(const double x, const double *par) {
|
||||
return par[0] * exp(-pow(x - par[1], 2) / (2 * pow(par[2], 2)));
|
||||
}
|
||||
|
||||
NDArray<double, 1> gaus(NDView<double, 1> x, NDView<double, 1> par) {
|
||||
NDArray<double, 1> y({x.shape(0)}, 0);
|
||||
for (ssize_t i = 0; i < x.size(); i++) {
|
||||
y(i) = gaus(x(i), par.data());
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
double pol1(const double x, const double *par) { return par[0] * x + par[1]; }
|
||||
|
||||
NDArray<double, 1> pol1(NDView<double, 1> x, NDView<double, 1> par) {
|
||||
NDArray<double, 1> y({x.shape()}, 0);
|
||||
for (ssize_t i = 0; i < x.size(); i++) {
|
||||
y(i) = pol1(x(i), par.data());
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
double scurve(const double x, const double *par) {
|
||||
return (par[0] + par[1] * x) +
|
||||
0.5 * (1 + erf((x - par[2]) / (sqrt(2) * par[3]))) *
|
||||
(par[4] + par[5] * (x - par[2]));
|
||||
}
|
||||
|
||||
NDArray<double, 1> scurve(NDView<double, 1> x, NDView<double, 1> par) {
|
||||
NDArray<double, 1> y({x.shape()}, 0);
|
||||
for (ssize_t i = 0; i < x.size(); i++) {
|
||||
y(i) = scurve(x(i), par.data());
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
double scurve2(const double x, const double *par) {
|
||||
return (par[0] + par[1] * x) +
|
||||
0.5 * (1 - erf((x - par[2]) / (sqrt(2) * par[3]))) *
|
||||
(par[4] + par[5] * (x - par[2]));
|
||||
}
|
||||
|
||||
NDArray<double, 1> scurve2(NDView<double, 1> x, NDView<double, 1> par) {
|
||||
NDArray<double, 1> y({x.shape()}, 0);
|
||||
for (ssize_t i = 0; i < x.size(); i++) {
|
||||
y(i) = scurve2(x(i), par.data());
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
} // namespace func
|
||||
|
||||
NDArray<double, 1> fit_gaus(NDView<double, 1> x, NDView<double, 1> y) {
|
||||
NDArray<double, 1> result = model::Gaussian::estimate_par(x, y);
|
||||
lm_status_struct status;
|
||||
|
||||
lmcurve(result.size(), result.data(), x.size(), x.data(), y.data(),
|
||||
aare::func::gaus, &lm_control_double, &status);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NDArray<double, 3> fit_gaus(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads) {
|
||||
NDArray<double, 3> result({y.shape(0), y.shape(1), 3}, 0);
|
||||
|
||||
auto process = [&x, &y, &result](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> values(&y(row, col, 0), {y.shape(2)});
|
||||
auto res = fit_gaus(x, values);
|
||||
result(row, col, 0) = res(0);
|
||||
result(row, col, 1) = res(1);
|
||||
result(row, col, 2) = res(2);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
return result;
|
||||
}
|
||||
|
||||
void fit_gaus(NDView<double, 1> x, NDView<double, 1> y, NDView<double, 1> y_err,
|
||||
NDView<double, 1> par_out, NDView<double, 1> par_err_out,
|
||||
double &chi2) {
|
||||
|
||||
// Check that we have the correct sizes
|
||||
if (y.size() != x.size() || y.size() != y_err.size() ||
|
||||
par_out.size() != 3 || par_err_out.size() != 3) {
|
||||
throw std::runtime_error("Data, x, data_err must have the same size "
|
||||
"and par_out, par_err_out must have size 3");
|
||||
}
|
||||
|
||||
// /* Collection of output parameters for status info. */
|
||||
// typedef struct {
|
||||
// double fnorm; /* norm of the residue vector fvec. */
|
||||
// int nfev; /* actual number of iterations. */
|
||||
// int outcome; /* Status indicator. Nonnegative values are used as
|
||||
// index
|
||||
// for the message text lm_infmsg, set in lmmin.c. */
|
||||
// int userbreak; /* Set when function evaluation requests termination.
|
||||
// */
|
||||
// } lm_status_struct;
|
||||
|
||||
lm_status_struct status;
|
||||
par_out = model::Gaussian::estimate_par(x, y);
|
||||
std::array<double, 9> cov{0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
// void lmcurve2( const int n_par, double *par, double *parerr, double
|
||||
// *covar, const int m_dat, const double *t, const double *y, const double
|
||||
// *dy, double (*f)( const double ti, const double *par ), const
|
||||
// lm_control_struct *control, lm_status_struct *status); n_par - Number of
|
||||
// free variables. Length of parameter vector par. par - Parameter vector.
|
||||
// On input, it must contain a reasonable guess. On output, it contains the
|
||||
// solution found to minimize ||r||. parerr - Parameter uncertainties
|
||||
// vector. Array of length n_par or NULL. On output, unless it or covar is
|
||||
// NULL, it contains the weighted parameter uncertainties for the found
|
||||
// parameters. covar - Covariance matrix. Array of length n_par * n_par or
|
||||
// NULL. On output, unless it is NULL, it contains the covariance matrix.
|
||||
// m_dat - Number of data points. Length of vectors t, y, dy. Must statisfy
|
||||
// n_par <= m_dat. t - Array of length m_dat. Contains the abcissae (time,
|
||||
// or "x") for which function f will be evaluated. y - Array of length
|
||||
// m_dat. Contains the ordinate values that shall be fitted. dy - Array of
|
||||
// length m_dat. Contains the standard deviations of the values y. f - A
|
||||
// user-supplied parametric function f(ti;par). control - Parameter
|
||||
// collection for tuning the fit procedure. In most cases, the default
|
||||
// &lm_control_double is adequate. If f is only computed with
|
||||
// single-precision accuracy, &lm_control_float should be used. Parameters
|
||||
// are explained in lmmin2(3). status - A record used to return information
|
||||
// about the minimization process: For details, see lmmin2(3).
|
||||
|
||||
lmcurve2(par_out.size(), par_out.data(), par_err_out.data(), cov.data(),
|
||||
x.size(), x.data(), y.data(), y_err.data(), aare::func::gaus,
|
||||
&lm_control_double, &status);
|
||||
|
||||
// Calculate chi2
|
||||
chi2 = 0;
|
||||
for (ssize_t i = 0; i < y.size(); i++) {
|
||||
chi2 +=
|
||||
std::pow((y(i) - func::gaus(x(i), par_out.data())) / y_err(i), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void fit_gaus(NDView<double, 1> x, NDView<double, 3> y, NDView<double, 3> y_err,
|
||||
NDView<double, 3> par_out, NDView<double, 3> par_err_out,
|
||||
NDView<double, 2> chi2_out,
|
||||
|
||||
int n_threads) {
|
||||
|
||||
auto process = [&](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> y_view(&y(row, col, 0), {y.shape(2)});
|
||||
NDView<double, 1> y_err_view(&y_err(row, col, 0),
|
||||
{y_err.shape(2)});
|
||||
NDView<double, 1> par_out_view(&par_out(row, col, 0),
|
||||
{par_out.shape(2)});
|
||||
NDView<double, 1> par_err_out_view(&par_err_out(row, col, 0),
|
||||
{par_err_out.shape(2)});
|
||||
|
||||
fit_gaus(x, y_view, y_err_view, par_out_view, par_err_out_view,
|
||||
chi2_out(row, col));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
}
|
||||
|
||||
void fit_pol1(NDView<double, 1> x, NDView<double, 1> y, NDView<double, 1> y_err,
|
||||
NDView<double, 1> par_out, NDView<double, 1> par_err_out,
|
||||
double &chi2) {
|
||||
|
||||
// Check that we have the correct sizes
|
||||
if (y.size() != x.size() || y.size() != y_err.size() ||
|
||||
par_out.size() != 2 || par_err_out.size() != 2) {
|
||||
throw std::runtime_error("Data, x, data_err must have the same size "
|
||||
"and par_out, par_err_out must have size 2");
|
||||
}
|
||||
|
||||
lm_status_struct status;
|
||||
par_out = model::Pol1::estimate_par(x, y);
|
||||
std::array<double, 4> cov{0, 0, 0, 0};
|
||||
|
||||
lmcurve2(par_out.size(), par_out.data(), par_err_out.data(), cov.data(),
|
||||
x.size(), x.data(), y.data(), y_err.data(), aare::func::pol1,
|
||||
&lm_control_double, &status);
|
||||
|
||||
// Calculate chi2
|
||||
chi2 = 0;
|
||||
for (ssize_t i = 0; i < y.size(); i++) {
|
||||
chi2 +=
|
||||
std::pow((y(i) - func::pol1(x(i), par_out.data())) / y_err(i), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void fit_pol1(NDView<double, 1> x, NDView<double, 3> y, NDView<double, 3> y_err,
|
||||
NDView<double, 3> par_out, NDView<double, 3> par_err_out,
|
||||
NDView<double, 2> chi2_out, int n_threads) {
|
||||
|
||||
auto process = [&](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> y_view(&y(row, col, 0), {y.shape(2)});
|
||||
NDView<double, 1> y_err_view(&y_err(row, col, 0),
|
||||
{y_err.shape(2)});
|
||||
NDView<double, 1> par_out_view(&par_out(row, col, 0),
|
||||
{par_out.shape(2)});
|
||||
NDView<double, 1> par_err_out_view(&par_err_out(row, col, 0),
|
||||
{par_err_out.shape(2)});
|
||||
|
||||
fit_pol1(x, y_view, y_err_view, par_out_view, par_err_out_view,
|
||||
chi2_out(row, col));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
}
|
||||
|
||||
NDArray<double, 1> fit_pol1(NDView<double, 1> x, NDView<double, 1> y) {
|
||||
// // Check that we have the correct sizes
|
||||
// if (y.size() != x.size() || y.size() != y_err.size() ||
|
||||
// par_out.size() != 2 || par_err_out.size() != 2) {
|
||||
// throw std::runtime_error("Data, x, data_err must have the same size "
|
||||
// "and par_out, par_err_out must have size 2");
|
||||
// }
|
||||
NDArray<double, 1> par = model::Pol1::estimate_par(x, y);
|
||||
|
||||
lm_status_struct status;
|
||||
lmcurve(par.size(), par.data(), x.size(), x.data(), y.data(),
|
||||
aare::func::pol1, &lm_control_double, &status);
|
||||
|
||||
return par;
|
||||
}
|
||||
|
||||
NDArray<double, 3> fit_pol1(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads) {
|
||||
NDArray<double, 3> result({y.shape(0), y.shape(1), 2}, 0);
|
||||
|
||||
auto process = [&](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> values(&y(row, col, 0), {y.shape(2)});
|
||||
auto res = fit_pol1(x, values);
|
||||
result(row, col, 0) = res(0);
|
||||
result(row, col, 1) = res(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
|
||||
RunInParallel(process, tasks);
|
||||
return result;
|
||||
}
|
||||
|
||||
// ~~ S-CURVES ~~
|
||||
|
||||
// - No error
|
||||
NDArray<double, 1> fit_scurve(NDView<double, 1> x, NDView<double, 1> y) {
|
||||
NDArray<double, 1> result = model::RisingScurve::estimate_par(x, y);
|
||||
lm_status_struct status;
|
||||
|
||||
lmcurve(result.size(), result.data(), x.size(), x.data(), y.data(),
|
||||
aare::func::scurve, &lm_control_double, &status);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NDArray<double, 3> fit_scurve(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads) {
|
||||
NDArray<double, 3> result({y.shape(0), y.shape(1), 6}, 0);
|
||||
|
||||
auto process = [&x, &y, &result](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> values(&y(row, col, 0), {y.shape(2)});
|
||||
auto res = fit_scurve(x, values);
|
||||
result(row, col, 0) = res(0);
|
||||
result(row, col, 1) = res(1);
|
||||
result(row, col, 2) = res(2);
|
||||
result(row, col, 3) = res(3);
|
||||
result(row, col, 4) = res(4);
|
||||
result(row, col, 5) = res(5);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
return result;
|
||||
}
|
||||
|
||||
// - Error
|
||||
void fit_scurve(NDView<double, 1> x, NDView<double, 1> y,
|
||||
NDView<double, 1> y_err, NDView<double, 1> par_out,
|
||||
NDView<double, 1> par_err_out, double &chi2) {
|
||||
|
||||
// Check that we have the correct sizes
|
||||
if (y.size() != x.size() || y.size() != y_err.size() ||
|
||||
par_out.size() != 6 || par_err_out.size() != 6) {
|
||||
throw std::runtime_error("Data, x, data_err must have the same size "
|
||||
"and par_out, par_err_out must have size 6");
|
||||
}
|
||||
|
||||
lm_status_struct status;
|
||||
par_out = model::RisingScurve::estimate_par(x, y);
|
||||
std::array<double, 36> cov = {0}; // size 6x6
|
||||
// std::array<double, 4> cov{0, 0, 0, 0};
|
||||
|
||||
lmcurve2(par_out.size(), par_out.data(), par_err_out.data(), cov.data(),
|
||||
x.size(), x.data(), y.data(), y_err.data(), aare::func::scurve,
|
||||
&lm_control_double, &status);
|
||||
|
||||
// Calculate chi2
|
||||
chi2 = 0;
|
||||
for (ssize_t i = 0; i < y.size(); i++) {
|
||||
chi2 +=
|
||||
std::pow((y(i) - func::pol1(x(i), par_out.data())) / y_err(i), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void fit_scurve(NDView<double, 1> x, NDView<double, 3> y,
|
||||
NDView<double, 3> y_err, NDView<double, 3> par_out,
|
||||
NDView<double, 3> par_err_out, NDView<double, 2> chi2_out,
|
||||
int n_threads) {
|
||||
|
||||
auto process = [&](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> y_view(&y(row, col, 0), {y.shape(2)});
|
||||
NDView<double, 1> y_err_view(&y_err(row, col, 0),
|
||||
{y_err.shape(2)});
|
||||
NDView<double, 1> par_out_view(&par_out(row, col, 0),
|
||||
{par_out.shape(2)});
|
||||
NDView<double, 1> par_err_out_view(&par_err_out(row, col, 0),
|
||||
{par_err_out.shape(2)});
|
||||
|
||||
fit_scurve(x, y_view, y_err_view, par_out_view,
|
||||
par_err_out_view, chi2_out(row, col));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
}
|
||||
|
||||
// SCURVE2 ---
|
||||
|
||||
// - No error
|
||||
NDArray<double, 1> fit_scurve2(NDView<double, 1> x, NDView<double, 1> y) {
|
||||
NDArray<double, 1> result = model::FallingScurve::estimate_par(x, y);
|
||||
lm_status_struct status;
|
||||
|
||||
lmcurve(result.size(), result.data(), x.size(), x.data(), y.data(),
|
||||
aare::func::scurve2, &lm_control_double, &status);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
NDArray<double, 3> fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
|
||||
int n_threads) {
|
||||
NDArray<double, 3> result({y.shape(0), y.shape(1), 6}, 0);
|
||||
|
||||
auto process = [&x, &y, &result](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> values(&y(row, col, 0), {y.shape(2)});
|
||||
auto res = fit_scurve2(x, values);
|
||||
result(row, col, 0) = res(0);
|
||||
result(row, col, 1) = res(1);
|
||||
result(row, col, 2) = res(2);
|
||||
result(row, col, 3) = res(3);
|
||||
result(row, col, 4) = res(4);
|
||||
result(row, col, 5) = res(5);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
return result;
|
||||
}
|
||||
|
||||
// - Error
|
||||
void fit_scurve2(NDView<double, 1> x, NDView<double, 1> y,
|
||||
NDView<double, 1> y_err, NDView<double, 1> par_out,
|
||||
NDView<double, 1> par_err_out, double &chi2) {
|
||||
|
||||
// Check that we have the correct sizes
|
||||
if (y.size() != x.size() || y.size() != y_err.size() ||
|
||||
par_out.size() != 6 || par_err_out.size() != 6) {
|
||||
throw std::runtime_error("Data, x, data_err must have the same size "
|
||||
"and par_out, par_err_out must have size 6");
|
||||
}
|
||||
|
||||
lm_status_struct status;
|
||||
par_out = model::FallingScurve::estimate_par(x, y);
|
||||
std::array<double, 36> cov = {0}; // size 6x6
|
||||
// std::array<double, 4> cov{0, 0, 0, 0};
|
||||
|
||||
lmcurve2(par_out.size(), par_out.data(), par_err_out.data(), cov.data(),
|
||||
x.size(), x.data(), y.data(), y_err.data(), aare::func::scurve2,
|
||||
&lm_control_double, &status);
|
||||
|
||||
// Calculate chi2
|
||||
chi2 = 0;
|
||||
for (ssize_t i = 0; i < y.size(); i++) {
|
||||
chi2 +=
|
||||
std::pow((y(i) - func::pol1(x(i), par_out.data())) / y_err(i), 2);
|
||||
}
|
||||
}
|
||||
|
||||
void fit_scurve2(NDView<double, 1> x, NDView<double, 3> y,
|
||||
NDView<double, 3> y_err, NDView<double, 3> par_out,
|
||||
NDView<double, 3> par_err_out, NDView<double, 2> chi2_out,
|
||||
int n_threads) {
|
||||
|
||||
auto process = [&](ssize_t first_row, ssize_t last_row) {
|
||||
for (ssize_t row = first_row; row < last_row; row++) {
|
||||
for (ssize_t col = 0; col < y.shape(1); col++) {
|
||||
NDView<double, 1> y_view(&y(row, col, 0), {y.shape(2)});
|
||||
NDView<double, 1> y_err_view(&y_err(row, col, 0),
|
||||
{y_err.shape(2)});
|
||||
NDView<double, 1> par_out_view(&par_out(row, col, 0),
|
||||
{par_out.shape(2)});
|
||||
NDView<double, 1> par_err_out_view(&par_err_out(row, col, 0),
|
||||
{par_err_out.shape(2)});
|
||||
|
||||
fit_scurve2(x, y_view, y_err_view, par_out_view,
|
||||
par_err_out_view, chi2_out(row, col));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
auto tasks = split_task(0, y.shape(0), n_threads);
|
||||
RunInParallel(process, tasks);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// FitModel<Model> — method definitions
|
||||
// (constructor, destructor, copy, and all methods that touch Minuit2 state)
|
||||
@@ -756,4 +305,4 @@ AARE_INSTANTIATE_FIT(model::FallingScurve)
|
||||
#undef AARE_INSTANTIATE_FIT
|
||||
// NOLINTEND
|
||||
|
||||
} // namespace aare
|
||||
} // namespace aare
|
||||
@@ -0,0 +1,81 @@
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
#include "aare/Fit.hpp"
|
||||
#include "aare/FitModel.hpp"
|
||||
#include "aare/Models.hpp"
|
||||
|
||||
#include <catch2/catch_approx.hpp>
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr ssize_t n_points = 61;
|
||||
|
||||
void fill_gaussian(aare::NDArray<double, 1> &x, aare::NDArray<double, 1> &y,
|
||||
double amplitude, double mean, double sigma) {
|
||||
for (ssize_t i = 0; i < n_points; ++i) {
|
||||
x(i) = -6.0 + 0.2 * static_cast<double>(i);
|
||||
const double z = (x(i) - mean) / sigma;
|
||||
y(i) = amplitude * std::exp(-0.5 * z * z);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("Minuit2 fits weighted and unweighted Gaussian data", "[fit]") {
|
||||
aare::NDArray<double, 1> x({n_points});
|
||||
aare::NDArray<double, 1> y({n_points});
|
||||
aare::NDArray<double, 1> y_err({n_points}, 1.0);
|
||||
fill_gaussian(x, y, 120.0, 0.8, 1.3);
|
||||
|
||||
const aare::FitModel<aare::model::Gaussian> unweighted_model;
|
||||
const auto unweighted =
|
||||
aare::fit_pixel(unweighted_model, x.view(), y.view());
|
||||
|
||||
REQUIRE(unweighted.size() == 4);
|
||||
CHECK(unweighted(0) == Catch::Approx(120.0).epsilon(1e-5));
|
||||
CHECK(unweighted(1) == Catch::Approx(0.8).epsilon(1e-5));
|
||||
CHECK(unweighted(2) == Catch::Approx(1.3).epsilon(1e-5));
|
||||
CHECK(unweighted(3) == Catch::Approx(0.0).margin(1e-4));
|
||||
|
||||
const aare::FitModel<aare::model::Gaussian> weighted_model(0, 100, 0.5,
|
||||
true);
|
||||
const auto weighted =
|
||||
aare::fit_pixel(weighted_model, x.view(), y.view(), y_err.view());
|
||||
|
||||
REQUIRE(weighted.size() == 7);
|
||||
CHECK(weighted(0) == Catch::Approx(120.0).epsilon(1e-5));
|
||||
CHECK(weighted(1) == Catch::Approx(0.8).epsilon(1e-5));
|
||||
CHECK(weighted(2) == Catch::Approx(1.3).epsilon(1e-5));
|
||||
CHECK(weighted(6) == Catch::Approx(0.0).margin(1e-4));
|
||||
}
|
||||
|
||||
TEST_CASE("Minuit2 fits a Gaussian data cube in parallel", "[fit]") {
|
||||
aare::NDArray<double, 1> x({n_points});
|
||||
aare::NDArray<double, 1> values({n_points});
|
||||
fill_gaussian(x, values, 80.0, -0.6, 0.9);
|
||||
|
||||
aare::NDArray<double, 3> y({2, 2, n_points});
|
||||
for (ssize_t row = 0; row < 2; ++row) {
|
||||
for (ssize_t col = 0; col < 2; ++col) {
|
||||
for (ssize_t i = 0; i < n_points; ++i)
|
||||
y(row, col, i) = values(i);
|
||||
}
|
||||
}
|
||||
|
||||
aare::NDArray<double, 3> par({2, 2, 3});
|
||||
aare::NDArray<double, 2> chi2({2, 2});
|
||||
const aare::FitModel<aare::model::Gaussian> model;
|
||||
|
||||
aare::fit_3d(model, x.view(), y.view(), aare::NDView<double, 3>{},
|
||||
par.view(), aare::NDView<double, 3>{}, chi2.view(), 2);
|
||||
|
||||
for (ssize_t row = 0; row < 2; ++row) {
|
||||
for (ssize_t col = 0; col < 2; ++col) {
|
||||
CHECK(par(row, col, 0) == Catch::Approx(80.0).epsilon(1e-5));
|
||||
CHECK(par(row, col, 1) == Catch::Approx(-0.6).epsilon(1e-5));
|
||||
CHECK(par(row, col, 2) == Catch::Approx(0.9).epsilon(1e-5));
|
||||
CHECK(chi2(row, col) == Catch::Approx(0.0).margin(1e-8));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user