Feature/gauss+plateau (#312)
Build on RHEL9 / build (push) Successful in 2m27s
Build on RHEL8 / build (push) Successful in 3m1s
Run tests using data on local RHEL8 / build (push) Successful in 3m54s
Build on local RHEL8 / build (push) Successful in 2m36s

Adds three Minuit2-backed spectrum models to the Python-exposed fitting
API:

- `GaussianErfcPlateau`
- `GaussianChargeSharing`
- `GaussianChargeSharingKb`

Closes #297
This commit is contained in:
Khalil Ferjaoui authored and GitHub committed 2026-05-21 08:33:02 +02:00
1 parent de74f12640
commit 52b5cf6b9f
6 files changed
+1319 -1

No files matched your search

+1 -1
View File
@@ -17,7 +17,7 @@ from .ClusterFinder import ClusterFinder, ClusterCollector, ClusterFinderMT, Clu
from .ClusterVector import ClusterVector
from .Cluster import Cluster
from ._aare import Gaussian, RisingScurve, FallingScurve, Pol1, Pol2
from ._aare import 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
+37
View File
@@ -679,6 +679,11 @@ void define_fit_bindings(py::module &m) {
// ── Bind model classes ──────────────────────────────────────────
bind_fit_model<aare::model::Gaussian>(m, "Gaussian");
bind_fit_model<aare::model::GaussianErfcPlateau>(m, "GaussianErfcPlateau");
bind_fit_model<aare::model::GaussianChargeSharing>(m,
"GaussianChargeSharing");
bind_fit_model<aare::model::GaussianChargeSharingKb>(
m, "GaussianChargeSharingKb");
bind_fit_model<aare::model::RisingScurve>(m, "RisingScurve");
bind_fit_model<aare::model::FallingScurve>(m, "FallingScurve");
bind_fit_model<aare::model::Pol1>(m, "Pol1");
@@ -716,6 +721,38 @@ void define_fit_bindings(py::module &m) {
mdl, x, y, y_err_obj, n_threads);
}
// ── GaussianErfcPlateau ───────
if (py::isinstance<aare::FitModel<GaussianErfcPlateau>>(
model_obj)) {
const auto &mdl =
model_obj
.cast<const aare::FitModel<GaussianErfcPlateau> &>();
return fit_dispatch<GaussianErfcPlateau,
Chi2GaussianErfcPlateau>(
mdl, x, y, y_err_obj, n_threads);
}
// ── GaussianChargeSharing ───────
if (py::isinstance<aare::FitModel<GaussianChargeSharing>>(
model_obj)) {
const auto &mdl =
model_obj
.cast<const aare::FitModel<GaussianChargeSharing> &>();
return fit_dispatch<GaussianChargeSharing,
Chi2GaussianChargeSharing>(
mdl, x, y, y_err_obj, n_threads);
}
// ── GaussianChargeSharingKb ───────
if (py::isinstance<aare::FitModel<GaussianChargeSharingKb>>(
model_obj)) {
const auto &mdl = model_obj.cast<
const aare::FitModel<GaussianChargeSharingKb> &>();
return fit_dispatch<GaussianChargeSharingKb,
Chi2GaussianChargeSharingKb>(
mdl, x, y, y_err_obj, n_threads);
}
// ── Rising Scurve ───────
if (py::isinstance<aare::FitModel<RisingScurve>>(model_obj)) {
const auto &mdl =
+367
View File
@@ -0,0 +1,367 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "fbdc2fb9",
"metadata": {},
"source": [
"# Test `GaussianErfcPlateau` Python bindings\n",
"\n",
"Minimal check for the Aare Minuit2 object API against a SciPy `curve_fit` reference.\n",
"\n",
"Model:\n",
"\n",
"\\[\n",
"f(x) =\n",
"A \\exp\\left[-\\frac{1}{2}\\left(\\frac{x-\\mu}{\\sigma}\\right)^2\\right]\n",
"+\n",
"\\frac{S}{2}\\left[\n",
"1 - \\operatorname{erf}\\left(\n",
"\\frac{x-\\mu}{\\sqrt{2}\\sigma}\n",
"\\right)\n",
"\\right]\n",
"\\]\n",
"\n",
"Parameter order:\n",
"\n",
"```text\n",
"[A, S, mu, sigma]\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "2835b05b",
"metadata": {},
"outputs": [],
"source": [
"import time\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",
"from scipy.optimize import curve_fit\n",
"from pprint import pprint\n",
"\n",
"import sys\n",
"sys.path.insert(0, \"/home/ferjao_k/sw/aare/build\")\n",
"\n",
"try:\n",
" from aare import GaussianErfcPlateau\n",
"except ImportError:\n",
" import aare\n",
" print(\"Could not import GaussianErfcPlateau directly.\")\n",
" print(\"Available Gaussian/Erf/Plateau-like names:\")\n",
" pprint([name for name in dir(aare)\n",
" if any(token.lower() in name.lower()\n",
" for token in (\"gauss\", \"erf\", \"plateau\"))])\n",
" raise"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "feb3872d",
"metadata": {},
"outputs": [],
"source": [
"def gaussian_erfc_plateau(x, p):\n",
" A, S, mu, sigma = p\n",
" z = (x - mu) / (np.sqrt(2.0) * sigma)\n",
" return A * np.exp(-0.5 * ((x - mu) / sigma)**2) + 0.5 * S * (1.0 - erf(z))\n",
"\n",
"def gaussian_erfc_plateau_curve_fit(x, A, S, mu, sigma):\n",
" return gaussian_erfc_plateau(x, np.array([A, S, mu, sigma], dtype=float))"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "4e10fc29",
"metadata": {},
"outputs": [
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAAArwAAAGHCAYAAABS74GwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAAB7kElEQVR4nO3dd3gU1foH8O+kk94bpBNJ6KEnCAFpBvQCimC5KAoigorkegUsV8ACYgOUIvciUfwJqBRRQYpIQInUJEpvgYSQkEKSTQKpO78/wi7Z7OxmNyTb8v08Dz7u2TMz7+yZhXfPnDlHEEVRBBERERGRhbIydgBERERERC2JCS8RERERWTQmvERERERk0ZjwEhEREZFFY8JLRERERBaNCS8RERERWTQmvERERERk0ZjwEhEREZFFY8JLRERERBaNCS+RARw6dAhjx45FcHAw7O3t4efnh9jYWPzrX/9q0eMePHgQ8+bNQ3Fxsdp7oaGheOCBB5rtWDdv3sS8efOwb98+tfeSkpIgCAIuX77cbMfLyMjASy+9hOjoaDg5OcHBwQGhoaH45z//id9++w3msIjk5cuXIQgCkpKSjB2KSUhNTUV8fDzc3NwgCAKWLFlikOPu27cPgiBIXruNOXXqFObNm9es17ahzZs3D4IgNGnbb775xmDtRHQ3mPAStbCff/4ZcXFxkMlkWLx4MXbt2oWlS5eif//+2LhxY4se++DBg5g/f75kwtvcbt68ifnz50smDaNGjUJKSgoCAgKa5Vjbtm1Dly5dsG3bNjz11FPYsmULdu7ciTfffBOFhYW47777sHfv3mY5VksKCAhASkoKRo0aZexQTMIzzzyDnJwcbNiwASkpKXj00UeNHVKjTp06hfnz55t1wns3mPCSubAxdgBElm7x4sUICwvDzp07YWNz5yv36KOPYvHixUaMzHB8fHzg4+PTLPu6ePEiHnvsMXTq1Al79uyBq6ur8r34+HhMnjwZ+/btg4eHR7McryXZ29ujX79+xg5DadCgQQgNDTVoj3NtbS1qampgb2+PEydO4Nlnn0VCQoLBjk9ErQN7eIlaWGFhIby9vVWSXQUrqztfwcmTJ8PT0xM3b95Uq3ffffehU6dOyteCIOCFF17AunXrEB0dDUdHR3Tr1g0//fSTss68efPw73//GwAQFhYGQRAkb9v+8ssv6NGjB9q0aYOoqCh88cUXasfPzc3Fc889h3bt2sHOzg5hYWGYP38+ampqANTdmlcktPPnz1cea9KkSQA0D2n45ZdfMGTIELi5ucHR0RHR0dFYuHChlk8T+Pjjj3Hz5k2sWLFCJdmtb9CgQejWrZvy9YULF/D0008jMjISjo6OaNu2LR588EH8/fffKttpilPqlndqaioeeOAB+Pr6wt7eHoGBgRg1ahSuXr2qrPPdd9+hb9++yvMLDw/HM888o3xfakiDrrEqYlq/fj1ef/11BAYGwtXVFUOHDsXZs2e1foYtobFrBLhzvosXL8Y777yDsLAw2NvbY+3atRAEATU1NVi5cqXy+lHIzs7G1KlTERQUBDs7OwQGBmLcuHG4fv261pgU35PPP/8c99xzD+zt7dGxY0ds2LCh0fM5evQoHn30UYSGhqJNmzYIDQ3FY489hitXrijrJCUl4ZFHHgEADB48WBl3/fbcs2cPhgwZAldXVzg6OqJ///749ddfVY7VEtenJj///DO6d+8Oe3t7hIWF4cMPP5Sst3z5cgwcOBC+vr5wcnJCly5dsHjxYlRXVyvrDBo0CD///DOuXLmiPPf67TZ//nz07dsXnp6ecHV1RY8ePbBmzRqzGG5Eloc9vEQtLDY2Fv/73//w0ksv4YknnkCPHj1ga2urVm/mzJn44osv8M0332DKlCnK8lOnTuG3337D8uXLVer//PPPOHLkCBYsWABnZ2csXrwYY8eOxdmzZxEeHo4pU6bgxo0b+PTTT7F582blcIKOHTsq95Geno5//etfmDNnDvz8/PC///0PkydPRvv27TFw4EAAdYlMnz59YGVlhf/85z+IiIhASkoK3nnnHVy+fBlr165FQEAAfvnlF9x///2YPHmyMn5tvbpr1qzBs88+i/j4eKxatQq+vr44d+4cTpw4ofXz3L17NwICAtCrV69GPvk7rl27Bi8vLyxatAg+Pj64ceMGvvzyS/Tt2xepqano0KGDzvsCgPLycgwbNgxhYWFYvnw5/Pz8kJubi99++w2lpaUAgJSUFEyYMAETJkzAvHnz4ODggCtXrjQ61ELfWF977TX0798f//vf/yCTyTB79mw8+OCDOH36NKytrfU6r6bS5Rqpb9myZbjnnnvw4YcfwtXVFe7u7khJSUFsbCzGjRunMrY9OzsbvXv3RnV1NV577TV07doVhYWF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 truncated
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Synthetic data\n",
"rng = np.random.default_rng(42)\n",
"\n",
"x = np.linspace(-4.0, 6.0, 251)\n",
"\n",
"p_true = np.array([\n",
" 1.20, # A: Gaussian amplitude\n",
" 1.00, # S: left plateau height\n",
" 1.00, # mu: shared center / step position\n",
" 0.65, # sigma: shared width\n",
"])\n",
"\n",
"noise_sigma = 0.035\n",
"\n",
"y_true = gaussian_erfc_plateau(x, p_true)\n",
"y = y_true + rng.normal(0.0, noise_sigma, size=x.shape)\n",
"y_err = np.full_like(x, noise_sigma)\n",
"\n",
"plt.figure(figsize=(8, 4))\n",
"plt.plot(x, y_true, label=\"true\")\n",
"plt.scatter(x, y, s=10, label=\"noisy data\")\n",
"plt.xlabel(\"x\")\n",
"plt.ylabel(\"y\")\n",
"plt.title(\"Synthetic Gaussian + erfc plateau data\")\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "20571047",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True params : [1.2 1. 1. 0.65]\n",
"SciPy params: [1.19916 1.000479 1.005638 0.64249 ]\n",
"SciPy abs error: [0.00084 0.000479 0.005638 0.00751 ]\n"
]
}
],
"source": [
"# SciPy reference fit\n",
"p0_ref = np.array([1.0, 0.8, 0.5, 0.8])\n",
"\n",
"bounds_ref = (\n",
" [-np.inf, -np.inf, -np.inf, 1e-12],\n",
" [ np.inf, np.inf, np.inf, np.inf],\n",
")\n",
"\n",
"p_scipy, cov_scipy = curve_fit(\n",
" gaussian_erfc_plateau_curve_fit,\n",
" x,\n",
" y,\n",
" p0=p0_ref,\n",
" sigma=y_err,\n",
" absolute_sigma=True,\n",
" bounds=bounds_ref,\n",
" maxfev=20_000,\n",
")\n",
"\n",
"print(\"True params : \", p_true)\n",
"print(\"SciPy params: \", p_scipy)\n",
"print(\"SciPy abs error:\", np.abs(p_scipy - p_true))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "60ae684f",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Parameter list:\n",
"['A', 'S', 'mu', 'sigma']\n",
"n_par: 4\n",
"\n",
"== Tuned fit settings ==\n",
"max_calls : 1000\n",
"tolerance : 0.01\n",
"compute_errors : True\n",
"\n",
"== Aare / Minuit2 result ==\n",
"{'par': array([1.199158, 1.000478, 1.00564 , 0.642494]),\n",
" 'par_err': array([0.008525, 0.003768, 0.005623, 0.005163]),\n",
" 'chi2': array([218.22232])}\n",
"\n",
"True params : [1.2 1. 1. 0.65]\n",
"SciPy params : [1.19916 1.000479 1.005638 0.64249 ]\n",
"Aare params : [1.199158 1.000478 1.00564 0.642494]\n",
"SciPy abs error : [0.00084 0.000479 0.005638 0.00751 ]\n",
"Aare abs error : [0.000842 0.000478 0.00564 0.007506]\n"
]
}
],
"source": [
"# Aare / Minuit2 object API fit\n",
"model = GaussianErfcPlateau()\n",
"\n",
"print(\"Parameter list:\")\n",
"print(model.par_names)\n",
"print(\"n_par:\", model.n_par)\n",
"print()\n",
"\n",
"# Optional: provide explicit starts close enough to avoid relying only on estimate_par().\n",
"model.SetParameter(\"A\", 1.0)\n",
"model.SetParameter(\"S\", 0.8)\n",
"model.SetParameter(\"mu\", 0.5)\n",
"model.SetParameter(\"sigma\", 0.8)\n",
"\n",
"# Optional fit settings\n",
"model.compute_errors = True\n",
"model.max_calls = 1000\n",
"model.tolerance = 0.01\n",
"\n",
"print(\"== Tuned fit settings ==\")\n",
"print(f\"max_calls : {model.max_calls}\")\n",
"print(f\"tolerance : {model.tolerance}\")\n",
"print(f\"compute_errors : {model.compute_errors}\")\n",
"print()\n",
"\n",
"res_aare = model.fit(x, y, y_err)\n",
"\n",
"print(\"== Aare / Minuit2 result ==\")\n",
"pprint(res_aare, sort_dicts=False)\n",
"\n",
"p_aare = np.array(res_aare[\"par\"], dtype=float)\n",
"\n",
"print()\n",
"print(\"True params : \", p_true)\n",
"print(\"SciPy params : \", p_scipy)\n",
"print(\"Aare params : \", p_aare)\n",
"print(\"SciPy abs error : \", np.abs(p_scipy - p_true))\n",
"print(\"Aare abs error : \", np.abs(p_aare - p_true))"
]
},
{
"cell_type": "code",
"execution_count": 26,
"id": "3f1f13e0",
"metadata": {},
"outputs": [
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAAArwAAAGHCAYAAABS74GwAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACbMElEQVR4nOzdd1hUR9vA4d8uvUsvSlMRsQF2sfdgiVGjptmNMe0z8U3RN8kbTW8mmmqKLTExxpqYaOw1dgUsKDYQCwgoHWm75/sD2bhSBAWWxee+rr30nJ0z5zk7u/rs7JwZlaIoCkIIIYQQQtRRakMHIIQQQgghRHWShFcIIYQQQtRpkvAKIYQQQog6TRJeIYQQQghRp0nCK4QQQggh6jRJeIUQQgghRJ0mCa8QQgghhKjTJOEVQgghhBB1miS8QgghhBCiTpOEV4gadPToUSZOnEijRo2wsrLCysqKgIAAnnrqKQ4dOmTo8Cpk+/btqFQqtm/fbuhQaoUtW7bQtm1bbGxsUKlUrFmzpkbOu2jRIlQqFXFxcZU+ds+ePcycOZO0tLQqj6s6fPHFFzRu3Bhzc3NUKhVpaWmMGzcOPz8/vXLvvfdejb3+xq5Hjx706NHD0GEIUWNUsrSwEDXj22+/5bnnniMwMJDJkyfTvHlzVCoVJ0+eZOnSpfzzzz+cPXuWRo0aGTrUcmVkZBAdHU2zZs2wt7c3dDgGpSgKLi4uNGnShHfeeQcbGxsCAwNxdHSs9nMvWrSI8ePHExsbWyLxu5NPPvmEl19++a6OrWmRkZGEhoYyadIkxo4di6mpKe3atSMuLo6MjAxCQ0N1ZW1tbXn44YdZtGiR4QI2EtHR0QA0a9bMwJEIUTNMDR2AEPeDf/75h2eeeYaBAweyYsUKzM3Ndc/16tWLZ599luXLl2NlZWXAKCvG3t6ejh07GjoMHT8/P8aNG8fMmTNr7JwFBQWoVCquXr3K9evXGTp0KL17966x899PTpw4AcCTTz5J+/btdftr+xfD2ionJwdra2tJdMV9R4Y0CFED3nvvPUxMTPj222/1kt1bjRgxAi8vL932oUOHeOSRR/Dz88PKygo/Pz8effRRLly4oHfczJkzUalUJeor7SfvrVu30qNHD5ydnbGyssLHx4fhw4eTk5OjK/PNN98QHByMra0tdnZ2NG3alP/+97+650sb0lDRWItj2rZtG08//TQuLi44OzszbNgwrly5UqHXsiqdOXOGxx57DDc3NywsLAgKCuKrr77SK1N8vT/99BP/+c9/qF+/PhYWFjzxxBM0aNAAgFdffRWVSqXXW3rq1CkeffRR3N3dsbCwwMfHhzFjxpCXl1dmPHFxcahUKj766CPeffddfHx8sLS0pG3btmzZsuWO17Np0yaGDBlCgwYNsLS0pHHjxjz11FOkpKToysycOZOXX34ZAH9/f1QqVYn2XLZsGZ06dcLGxgZbW1v69+9PRESE3rmq4/15ux49evDEE08A0KFDB1QqFePGjQMoMaRBpVKRnZ3N4sWLddd0p5/s8/LyeOuttwgKCsLS0hJnZ2d69uzJnj17dGVyc3OZMWMG/v7+mJubU79+fZ599tkSw0H8/PwYNGgQf/75J6GhoVhZWREUFMSff/6pu96goCBsbGxo3759iSFM48aNw9bWlhMnTtC7d29sbGxwdXXlueee0/t8Anz11Vd069YNNzc3bGxsaNmyJR999BEFBQUlXr8WLVqwc+dOwsLCsLa2ZsKECbrnbn997vTZBzh+/DhDhgzB0dERS0tLQkJCWLx4sV6Z4s/M0qVLee211/Dy8sLe3p4+ffoQExNTbpsIUV2kh1eIaqbRaNi2bRtt27bF09OzwsfFxcURGBjII488gpOTEwkJCXzzzTe0a9eO6OhoXFxcKhVHXFwcAwcOpGvXrixYsIB69epx+fJl/v77b/Lz87G2tubXX3/lmWee4fnnn+eTTz5BrVZz9uxZ3c+fVRXrpEmTGDhwIL/88gsXL17k5Zdf5oknnmDr1q2VuqZ7ER0dTVhYGD4+PsyePRsPDw82bNjA//3f/5GSksKbb76pV37GjBl06tSJefPmoVaradmyJaNGjWLYsGE8//zzPPbYY1hYWAAQFRVFly5dcHFx4a233iIgIICEhAT++OMP8vPzdeXK8uWXX+Lr68ucOXPQarV89NFHhIeHs2PHDjp16lTmcefOnaNTp05MmjQJBwcH4uLi+PTTT+nSpQvHjh3DzMyMSZMmcf36db744gtWrVqle08W9/i99957vP7664wfP57XX3+d/Px8Pv74Y7p27cqBAwd05ar6/Vmar7/+mqVLl/LOO++wcOFCmjZtiqura6ll9+7dS69evejZsydvvPEGQLlDbgoLCwkPD2fXrl288MIL9OrVi8LCQvbt20d8fDxhYWEoisJDDz3Eli1bmDFjBl27duXo0aO8+eab7N27l7179+q1ZVRUFDNmzOC1117DwcGBWbNmMWzYMGbMmMGWLVt47733UKlUvPrqqwwaNIjY2Fi9X3UKCgoYMGAATz31FNOnT2fPnj288847XLhwgbVr1+rKnTt3jscee0yXhEdFRfHuu+9y6tQpFixYoHedCQkJPPHEE7zyyiu89957qNWl93NV5LMfExNDWFgYbm5ufP755zg7O7NkyRLGjRvH1atXeeWVV/Tq/O9//0vnzp354YcfyMjI4NVXX2Xw4MGcPHkSExOTMttGiGqhCCGqVWJiogIojzzySInnCgsLlYKCAt1Dq9WWWU9hYaGSlZWl2NjYKHPnztXtf/PNN5XSPsoLFy5UACU2NlZRFEVZsWKFAiiRkZFlnuO5555T6tWrV+71bNu2TQGUbdu2VTrW4pieeeYZvfIfffSRAigJCQnlnlur1eq9XgUFBYqvr6/yxhtvlNh/J/3791caNGigpKen6+1/7rnnFEtLS+X69et619utW7cSdcTGxiqA8vHHH+vt79Wrl1KvXj0lKSnpjnGUVp+Xl5dy48YN3f6MjAzFyclJ6dOnj27f7e17u+LX6sKFCwqg/P7777rnPv7441KPjY+PV0xNTZXnn39eb39mZqbi4eGhjBw5sszY7/X9WZbicgcPHtTbP3bsWMXX11dvn42NjTJ27Nhy6yv2448/KoDy/fffl1nm77//VgDlo48+0tu/bNkyBVC+++473T5fX1/FyspKuXTpkm5fZGSkAiienp5Kdna2bv+aNWsUQPnjjz/0rgfQe+0URVHeffddBVB2795daowajUYpKChQfvzxR8XExET3vlUURenevbsCKFu2bClxXPfu3ZXu3bvrtivy2X/kkUcUCwsLJT4+Xm9/eHi4Ym1traSlpSmK8u9nZsCAAXrlfvvtNwVQ9u7dW+55hKgOMqRBCANq06YNZmZmusfs2bN1z2VlZfHqq6/SuHFjTE1NMTU1xdbWluzsbE6ePFnpc4WEhGBubs7kyZNZvHgx58+fL1Gmffv2pKWl8eijj/L777/r/RRensrG+uCDD+ptt2rVCqDEz+G3W7x4sd7rZWZmxoULF3j77bdL7C9Pbm4uW7ZsYejQoVhbW1NYWKh7DBgwgNzcXPbt26d3zPDhwyvyUpCTk8OOHTsYOXJkmb2RdzJs2DAsLS1123Z2dgwePJidO3ei0WjKPC4pKYkpU6bg7e2NqakpZmZm+Pr6AlToPbNhwwYKCwsZM2aM3mtiaWlJ9+7d9YY9VPX7s6atX78eS0tL3U/8pSn+xaF4GEWxESNGYGNjU2KYSUhICPXr19dtBwUFAUXDB6ytrUvsL+39/vjjj+ttP/bYYwBs27ZNty8iIoIHH3wQZ2dnTExMMDMzY8yYMWg0Gk6fPq13vKOjI7169SrzGotV5LO/detWevfujbe3t97+cePGkZOTw969e/X23+3nXIjqIEMahKhmLi4uWFlZlfqP/C+//EJOTg4JCQkl/nN47LHH2LJlC2+88Qbt2rXD3t4elUrFgAEDuHHjRqXjaNSoEZs3b+ajjz7i2WefJTs7m4YNG/J///d/TJ06FYDRo0dTWFjI999/z/Dhw9FqtbRr14533nmHvn37lll3ZWN1dnbW2y7+WfhO1zV48GAOHjyot+/BBx9k0KBBTJ48uUKvA8C1a9coLCzkiy++4Isvvii1zO3/4Vd0OEpqaioajUY3vvdueHh4lLovPz+frKwsHBwcSjyv1Wrp168fV65c4Y033qBly5bY2Nig1Wrp2LFjhd4zV69eBaBdu3alPn/rz+FV/f6sacnJyXh5eZX5Ez8UvU9MTU1LfHFRqVR4eHhw7do1vf1OTk5628Xj9cvan5ubq7ff1NS0xGej+L1QfK74+Hi6du1KYGAgc+fOxc/PD0tLSw4cOMCzzz5b4rWv6Pu2Ip/9a9eulVpf8b0Ht78ed/s5F6I6SMIrRDUzMTGhV69ebNy4kYSEBL3/MG4dD3mr9PR0/vzzT958802mT5+u25+Xl8f169f1yhb3BObl5emNJyyth6Zr16507doVjUbDoUOH+OKLL3jhhRdwd3fnkUceAWD8+PGMHz+e7Oxsdu7cyZtvvsmgQYM4ffq0rrfwbmO9V87OziX+EzU3N8fLy4u2bdtWuB5HR0dMTEwYPXo0zz77bKll/P399bZLu/GqNE5OTpiYmHDp0qUKx3O7xMTEUveZm5tja2tb6jHHjx8nKiqKRYsWMXbsWN3+s2fPVvi8xeNuV6xYUWpbF6uu92dNcnV1Zffu3Wi12jKTXmdnZwoLC0lOTtZLehVFITExscwvBnersLCQa9eu6b3Hi98LxfvWrFlDdnY2q1at0mujyMjIUuus6PsW7vzZd3Z2JiEhocRxxTecVsW4bSGqiwxpEKIGzJgxA41Gw5QpU0rcSV0alUqFoiglbm764YcfSvykXXyn+tGjR/X233qTy+1MTEzo0KGDbkaCI0eOlChjY2NDeHg4r732Gvn5+brpoeLine truncated
"text/plain": [
"<Figure size 800x400 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(8, 4))\n",
"\n",
"# plt.plot(x, y_true, label=\"true\")\n",
"plt.scatter(x, y, s=10, label=\"data\", color=\"tab:blue\")\n",
"plt.plot(x, gaussian_erfc_plateau(x, p_scipy), linewidth=4.0, label=\"SciPy curve_fit\", color=\"tab:red\")\n",
"plt.plot(x, model(x, p_aare), linewidth=1.0, label=\"Aare Minuit2\", color=\"tab:cyan\")\n",
"\n",
"plt.xlabel(\"x\")\n",
"plt.ylabel(\"y\")\n",
"plt.title(\"Gaussian + erfc plateau fit comparison\")\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 27,
"id": "1359c6b6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"SciPy curve_fit : 3.746 ms\n",
"Aare Minuit2 : 0.355 ms\n",
"\n",
"SciPy bench params: [1.19916 1.000479 1.005638 0.64249 ]\n",
"Aare bench params : [1.199158 1.000478 1.00564 0.642494]\n"
]
}
],
"source": [
"def bench(fn, n_repeats=200):\n",
" for _ in range(3):\n",
" fn()\n",
"\n",
" t0 = time.perf_counter()\n",
" for _ in range(n_repeats):\n",
" res = fn()\n",
" t1 = time.perf_counter()\n",
"\n",
" return res, (t1 - t0) / n_repeats\n",
"\n",
"def fit_scipy_once():\n",
" return curve_fit(\n",
" gaussian_erfc_plateau_curve_fit,\n",
" x,\n",
" y,\n",
" p0=p0_ref,\n",
" sigma=y_err,\n",
" absolute_sigma=True,\n",
" bounds=bounds_ref,\n",
" maxfev=20_000,\n",
" )[0]\n",
"\n",
"def fit_aare_once():\n",
" m = GaussianErfcPlateau()\n",
" m.SetParameter(\"A\", 1.0)\n",
" m.SetParameter(\"S\", 0.8)\n",
" m.SetParameter(\"mu\", 0.5)\n",
" m.SetParameter(\"sigma\", 0.8)\n",
" m.max_calls = 1000\n",
" m.tolerance = 0.01\n",
" return m.fit(x, y, y_err)[\"par\"]\n",
"\n",
"p_scipy_bench, t_scipy = bench(fit_scipy_once, n_repeats=200)\n",
"p_aare_bench, t_aare = bench(fit_aare_once, n_repeats=200)\n",
"\n",
"print(f\"SciPy curve_fit : {1e3*t_scipy:.3f} ms\")\n",
"print(f\"Aare Minuit2 : {1e3*t_aare:.3f} ms\")\n",
"print()\n",
"print(\"SciPy bench params:\", p_scipy_bench)\n",
"print(\"Aare bench params :\", p_aare_bench)"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "f6c02fe0-c642-470b-965a-f6931ac74726",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+404
View File
@@ -0,0 +1,404 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "bb29ae9f",
"metadata": {},
"source": [
"# Test `GaussianChargeSharingKb` Python bindings\n",
"\n",
"This notebook tests and benchmarks the Aare Minuit2 object API for the 8-parameter Kα/Kβ charge-sharing model.\n",
"\n",
"\\[\n",
"f(x)=p_0-p_1x+N\\left[\n",
"G_\\alpha(x)+qG_\\beta(x)+C H_\\alpha(x)+qC H_\\beta(x)\n",
"\\right]\n",
"\\]\n",
"\n",
"with\n",
"\n",
"\\[\n",
"\\mu_\\beta = r\\mu_\\alpha\n",
"\\]\n",
"\n",
"Parameter order:\n",
"\n",
"```text\n",
"[p0, p1, mu, sigma, N, C, kb_mean, kb_frac]\n",
"```\n",
"\n",
"where:\n",
"\n",
"```text\n",
"kb_mean = r\n",
"kb_frac = q\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "e6de205a",
"metadata": {},
"outputs": [],
"source": [
"import time\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",
"from scipy.optimize import curve_fit\n",
"from pprint import pprint\n",
"\n",
"import sys\n",
"sys.path.insert(0, \"/home/ferjao_k/sw/aare/build\")\n",
"\n",
"from aare import GaussianChargeSharingKb"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "4ad6bc4a",
"metadata": {},
"outputs": [],
"source": [
"def gaussian_charge_sharing_kb(x, p):\n",
" p0, p1, mu, sigma, N, C, kb_mean, kb_frac = p\n",
"\n",
" mu_b = kb_mean * mu\n",
"\n",
" u = (x - mu) / sigma\n",
" ub = (x - mu_b) / sigma\n",
"\n",
" G = np.exp(-0.5 * u**2)\n",
" Gb = np.exp(-0.5 * ub**2)\n",
"\n",
" H = 0.5 * (1.0 - erf(u / np.sqrt(2.0)))\n",
" Hb = 0.5 * (1.0 - erf(ub / np.sqrt(2.0)))\n",
"\n",
" return p0 - p1 * x + N * (G + kb_frac * Gb + C * H + kb_frac * C * Hb)\n",
"\n",
"def gaussian_charge_sharing_kb_curve_fit(x, p0, p1, mu, sigma, N, C, kb_mean, kb_frac):\n",
" return gaussian_charge_sharing_kb(\n",
" x,\n",
" np.array([p0, p1, mu, sigma, N, C, kb_mean, kb_frac], dtype=float),\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "93e4550c",
"metadata": {},
"outputs": [
{
"data": {
"image/png":
"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3DhcvXoVH330EdauXau7SOrYsaPuGIcOHcKLL76IGTNmIDo6Gv/+978xduxY3HzzzbjzzjsB1CUEt9xyC1QqFd544w20bt0au3btwltvvYVz585h+fLliI2NxY8//oh77rkHY8eO1bXfXO/AsmXL8PTTTyM1NRVLly5FVFQUTp06haNHj5qN56ZNmxAbG4tevXo1EvkbLl26hBYtWmDevHmIjIzE1atX8emnn6JPnz7IyMhAu3btLD4WUDdmfdCgQUhKSsI//vEPREdHIycnB1u3btVdEO3atQsPP/wwHn74YcyePRu+vr44f/58o8OarG3rq6++ittuuw3//ve/UVRUhOnTp2P48OE4ceIEvLy8sG/fPly9ehVPP/20Vd8Q/+Mf/0D79u3x4YcfAqgb+nLvvfciMzMTISEhAOqSmnbt2uGRRx5BeHg4srOz8fHHH6N37944fvw4IiIi9I751FNPYejQofjss89QWloKHx8fvPbaa5g7dy6eeeYZjBo1ChcuXMC4ceNQXV2Ntm3b6h5bVlaG1NRUXLx4Ea+++iq6du2KY8eO4Y033sCRI0ewefNmSJKEXr16wcfHB88//zzeeOMN3HXXXY0OWxs3bhyGDh2KVatW4cKFC3j55Zfx5z//We+1On36NO69915MnToVAQEB+O233zB//nzs2bPH4DWtqqrCfffdh/Hjx2PGjBm65NmUxj7LQF0CPWrUKNx5551Ys2YNampq8N577+Hy5ctGj1ldXY3HH38ca9aswaJFizBlyhST51+8eDESExPx4YcfQpZlLFiwAGlpaUhPT0ffvn1NPu7y5ctITU2Fj48PlixZgujoaHz++eeYPHmyQd0zZ86gb9++GDduHEJCQnDu3DksXLgQt99+O44cOQIfH59Gf2dZ8xoQuSVBRC4tPz9f3H777QKAACB8fHxEv379xNy5c0VxcbFe3dTUVNG9e3e9smeffVYEBwfr1QUgoqOjRVFRka4sJydHqFQqMXfuXF3Zu+++KwCIzMxMg3YlJiYKX19fcf78eV1ZeXm5CA8PF+PHj9eVjR8/XgQGBurVE0KI9957TwAQx44dE0IIkZeXJwCIWbNmGZxr+fLleu0oLi4WwcHB4vbbbxeyLJuInHG+vr7i1ltvNSivra0V1dXVup/a2lqTx6ipqRFVVVWiTZs24oUXXjDZTq2tW7cKAGLr1q1CCCH27dsnAIj169ebPIc2PteuXTNZJzMzUwAQy5cvt7qt2jbde++9evX/7//+TwAQu3btEkIIsXr1agFALF261OQ5jLWpS5cuoqamRle+Z88eAUD897//NdvWkpISERAQIBYtWqQr18b1ySef1Kt/9epVodFoxMMPP6xXvmvXLgFApKam6srmzp0rVCqV2Lt3r17dL7/8UgAQP/zwg65s2bJlIjAwUPeZi42NFU8++aTYvn273mO17Zo4caJe+YIFCwQAkZ2dbfR5yrIsqqurRXp6ugAgDh06pLtv9OjRAoD45JNPDB43evRokZiYqFdm6We5d+/eIj4+XlRWVurKiouLRYsWLUTDS4XExETdc3/11VeNPgchbrzWcXFxory8XFdeVFQkwsPDxd13323ysUIIMX36dCFJkjh48KBe+aBBg/Q+Lw1p43f+/HkBQHz99de6+8z9zjJ2DGOvAZG74vAhIhfXokUL7NixA3v37sW8efNw//3349SpU5g5cya6dOmi133+/PPP4+DBg/jf//4HoG64xGeffYbRo0cjMDBQ77gDBgxAUFCQ7nZ0dDSioqIMhm6Y0717dyQkJOhu+/r6om3btnrH+O677zBgwADExcWhpqZG95OWlgYAequ5WGrnzp0oKirCxIkTbTa+edSoUfDx8dH91P9mtKamBu+88w46duwItVoNb29vqNVqnD59GidOnLD6XDfffDPCwsIwffp0LF26FMePHzeo07t3bwDAQw89hP/7v//DH3/8YdGxrW3rfffdp3e7a9euAGDV+8CYoUOHwsvLy+xxS0pKMH36dNx8883w9vaGt7c3AgMDUVpaarStDzzwgN7t3bt3o7KyEg899JBe+a233mowzOa7775D586d0b17d7334ZAhQwyGdj311FO4ePEiVq1ahSlTpiA+Ph4rV65Eamoq3n33XYN2WRLDs2fP4rHHHkNMTAy8vLzg4+OD1NRUALDouZrT2Ge5tLQU+/btw4gRI6BWq3X1AgMDMXz4cKPH1H62Fy9ebDDsq6FRo0bpTTwOCgrC8OHDsX37dtTW1pp83NatW9GpUye9oXoA8NhjjxnUzc3NxYQJExAfHw9vb2/4+PggMTERgPH4GWPta0DkbpgUELmJXr16Yfr06fjiiy9w6dIlvPDCCzh37pzeZOP7778frVq10g0VWrFiBUpLSzFp0iSD47Vo0cKgTKPRoLy83OI2WXKMy5cv49tvv9W74Pbx8dHNcaif1FgqLy8PANCyZUurH5uQkGD0gvf999/H3r17sXfvXoP7pk2bhtdffx0jRozAt99+i19//RV79+5Ft27drIqXVkhICNLT09G9e3e8+uqr6NSpE+Line truncated
"text/plain": [
"<Figure size 900x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"rng = np.random.default_rng(123)\n",
"\n",
"x = np.linspace(0.0, 140.0, 281)\n",
"\n",
"p_true = np.array([\n",
" 14.0, # p0\n",
" 0.010, # p1, model uses p0 - p1*x\n",
" 62.0, # mu: K-alpha position\n",
" 2.0, # sigma\n",
" 180.0, # N\n",
" 0.10, # C: charge-sharing plateau\n",
" 1.105, # kb_mean: K-beta mean ratio\n",
" 0.3, # kb_frac: K-beta fraction\n",
"])\n",
"\n",
"noise_sigma = 1.5\n",
"y_true = gaussian_charge_sharing_kb(x, p_true)\n",
"y = y_true + rng.normal(0.0, noise_sigma, size=x.shape)\n",
"y_err = np.full_like(x, noise_sigma)\n",
"\n",
"plt.figure(figsize=(9, 5))\n",
"plt.plot(x, y_true, label=\"true\")\n",
"plt.scatter(x, y, s=10, label=\"noisy data\")\n",
"plt.xlabel(\"x\")\n",
"plt.ylabel(\"y\")\n",
"plt.title(\"Synthetic GaussianChargeSharingKb data\")\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "2133a34c",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"True params : [ 14. 0.01 62. 2. 180. 0.1 1.105 0.3 ]\n",
"SciPy params : [ 14.534234 0.015155 62.003314 1.998024 180.396284 0.098433\n",
" 1.104748 0.302732]\n",
"SciPy abs error : [0.534234 0.005155 0.003314 0.001976 0.396284 0.001567 0.000252 0.002732]\n"
]
}
],
"source": [
"# SciPy reference fit\n",
"p0_ref = np.array([12.0, 0.0, 60.0, 6.0, 150.0, 0.20, 1.10, 0.10])\n",
"\n",
"bounds_ref = (\n",
" [-np.inf, -np.inf, 0.0, 1e-12, 0.0, -np.inf, 1.02, 0.0],\n",
" [ np.inf, np.inf, 140.0, 50.0, np.inf, np.inf, 1.40, 1.00],\n",
")\n",
"\n",
"p_scipy, cov_scipy = curve_fit(\n",
" gaussian_charge_sharing_kb_curve_fit,\n",
" x,\n",
" y,\n",
" p0=p0_ref,\n",
" sigma=y_err,\n",
" absolute_sigma=True,\n",
" bounds=bounds_ref,\n",
" maxfev=50_000,\n",
")\n",
"\n",
"print(\"True params :\", p_true)\n",
"print(\"SciPy params :\", p_scipy)\n",
"print(\"SciPy abs error :\", np.abs(p_scipy - p_true))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "2a563865",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Parameter list:\n",
"['p0', 'p1', 'mu', 'sigma', 'N', 'C', 'kb_mean', 'kb_frac']\n",
"n_par: 8\n",
"\n",
"{'par': array([ 14.534164, 0.015155, 62.003317, 1.99802 , 180.396621,\n",
" 0.098433, 1.104748, 0.302733]),\n",
" 'par_err': array([0.564971, 0.005172, 0.009856, 0.008685, 0.698493, 0.00188 ,\n",
" 0.000509, 0.003632]),\n",
" 'chi2': array([262.404385])}\n",
"\n",
"True params : [ 14. 0.01 62. 2. 180. 0.1 1.105 0.3 ]\n",
"SciPy params : [ 14.534234 0.015155 62.003314 1.998024 180.396284 0.098433\n",
" 1.104748 0.302732]\n",
"Aare params : [ 14.534164 0.015155 62.003317 1.99802 180.396621 0.098433\n",
" 1.104748 0.302733]\n",
"SciPy abs error : [0.534234 0.005155 0.003314 0.001976 0.396284 0.001567 0.000252 0.002732]\n",
"Aare abs error : [0.534164 0.005155 0.003317 0.00198 0.396621 0.001567 0.000252 0.002733]\n",
"Aare - SciPy : [-0.00007 -0. 0.000003 -0.000004 0.000337 -0. -0.\n",
" 0.000001]\n"
]
}
],
"source": [
"# Aare / Minuit2 object API fit\n",
"model = GaussianChargeSharingKb()\n",
"\n",
"print(\"Parameter list:\")\n",
"print(model.par_names)\n",
"print(\"n_par:\", model.n_par)\n",
"print()\n",
"\n",
"model.SetParameter(\"p0\", 12.0)\n",
"model.SetParameter(\"p1\", 0.0)\n",
"model.SetParameter(\"mu\", 58.0)\n",
"model.SetParameter(\"sigma\", 4.0)\n",
"model.SetParameter(\"N\", 150.0)\n",
"model.SetParameter(\"C\", 0.10)\n",
"model.SetParameter(\"kb_mean\", 1.20)\n",
"model.SetParameter(\"kb_frac\", 0.40)\n",
"\n",
"model.SetParLimits(\"kb_mean\", 1.02, 1.40)\n",
"model.SetParLimits(\"kb_frac\", 0.0, 1.00)\n",
"\n",
"model.compute_errors = True\n",
"model.max_calls = 4000\n",
"model.tolerance = 0.01\n",
"\n",
"res_aare = model.fit(x, y, y_err)\n",
"pprint(res_aare, sort_dicts=False)\n",
"\n",
"p_aare = np.array(res_aare[\"par\"], dtype=float)\n",
"\n",
"print()\n",
"print(\"True params :\", p_true)\n",
"print(\"SciPy params :\", p_scipy)\n",
"print(\"Aare params :\", p_aare)\n",
"print(\"SciPy abs error :\", np.abs(p_scipy - p_true))\n",
"print(\"Aare abs error :\", np.abs(p_aare - p_true))\n",
"print(\"Aare - SciPy :\", p_aare - p_scipy)"
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "fc217c0e",
"metadata": {},
"outputs": [
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAAA1IAAAHUCAYAAAAwUBnrAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8hTgPZAAAACXBIWXMAAA9hAAAPYQGoP6dpAACieklEQVR4nOzdd3xUVd7H8c+UZNJ7hwChq6BSLKBSFAt2wIquYMW1PLqsDV1X0F3x0bWt7ur6qIAF26ro2hEFdEFFBJEqJRCQhEBIL5NM5j5/TDJk0sskc4Hv+/XKS+bOLb87Z8bML+ec37EYhmEgIiIiIiIirWYNdAAiIiIiIiIHGyVSIiIiIiIibaRESkREREREpI2USImIiIiIiLSREikREREREZE2UiIlIiIiIiLSRkqkRERERERE2kiJlIiIiIiISBspkRIREREREWkjJVIi0iXWrFnDtddeS58+fQgNDSU0NJR+/foxbdo0fvzxx0CH1yqLFy/GYrGwePHiTr+W2+3m1VdfZdy4cSQkJBAUFERSUhLnnnsu//nPf3C73T4x/fvf/+70mDpLXl4eM2bM4MgjjyQ8PJzo6GgGDhzI7373O9asWePdb+bMmVgsFvbt29clcY0ZM4YxY8Z0+nUsFgu33HJLg+0PPPAAFouF3//+97jdbr+09aJFixg+fDjh4eFYLBYWLFjA3LlzsVgsbN++3bvf/Pnzeeqpp9p9ncNJ7ftSRA4/9kAHICKHvn/961/ccsstDBgwgNtuu42jjjoKi8XChg0beOONNzjuuOPYsmULffr0CXSozRo6dCjLly/nyCOP7NTrVFRUcOGFF/LFF19w2WWX8dxzz5GSksLevXv57LPPuPjii3nrrbe44IILOjWOrlBSUsKJJ55ISUkJd955J8cccwzl5eX8+uuvvPfee6xevZqjjz46ILH985//DMh1DcPgtttu45lnnuGee+5h9uzZfjvvJZdcQv/+/fnwww8JDw9nwIABuFwuli9fTmpqqnff+fPns3btWm6//Xa/XPtQdt1113HWWWcFOgwRCQAlUiLSqf773/9y0003cc455/Dvf/+b4OBg73OnnnoqN998M++88w6hoaEBjLJ1oqKiOPHEEzv9OtOnT+fzzz9n3rx5XHXVVT7PTZw4kTvvvJPy8vJOj6OusrIywsLC/H7ed955hy1btvDVV18xduxYn+emT5/u7XnrSrX32tkJc2NcLhfXXHMNr776Ko899hh33HGH3869e/du9u/fz4QJEzjttNN8nktMTPTbdQ4Xte+T7t27071790CHIyIBoKF9ItKpHn74YWw2G//61798kqi6Lr74YtLS0ryPf/zxRy677DJ69epFaGgovXr14vLLL2fHjh0+xzU1pKaxoUpfffUVY8aMIT4+ntDQUHr06MGkSZMoKyvz7vPcc89xzDHHEBERQWRkJAMHDuTee+/1Pt/Y0L7Wxlob09dff83vf/97EhISiI+PZ+LEiezevdu7X05ODi+++CJnnnlmgySqVr9+/Rr00lRVVXHfffeRlpZGVFQU48aNY9OmTT77LFy4kAsuuIDu3bsTEhJC3759mTZtWoOhcrWv608//cRFF11EbGyst7fQ6XTyxz/+kZSUFMLCwhg1ahQrV66kV69eTJ061ec8OTk5TJs2je7duxMcHExGRgazZs3C5XJ598nLywPw6Q2py2pt+Gtqz549XH755URHR5OcnMw111xDYWGhzz7/+Mc/GDVqFElJSYSHhzN48GAeffRRqqqqfPYbM2YMgwYNYunSpYwcOZKwsDCuueYa73N1h/Zt374di8XC3/72N5544gkyMjKIiIhgxIgRfPfddw3i/L//+z/69++Pw+HgyCOPZP78+UydOpVevXo1eq8VFRVMmjSJ+fPn8+KLLzaZRFVUVDB9+nRSUlIIDQ1l9OjRrFq1qtF9a82cOdP7Zf/uu+/GYrF446j/eRkzZgwff/wxO3bswGKxeH9aMn/+fEaMGEFERAQREREce+yxvPTSSz77vPzyyxxzzDGEhIQQFxfHhAkT2LBhg88+U6dOJSIigo0bN3LmmWcSHh5OamoqjzzyCADfffcdJ598MuHh4fTv35958+b5HF97PwsXLuTqq68mLi6O8PBwzjvvPLZt2+azrz8+E439f6g1/7/Zv38/N910E926dSM4OJjevXtz33334XQ6fc5VO/Tz1Vdf5YgjjiAsLIxjjjmGjz76qMU2EZHOpR4pEek01dXVfP311wwfPrzJL8qN2b59OwMGDOCyyy4jLi6O7OxsnnvuOY477jjWr19PQkJCm+LYvn0755xzDqeccgovv/wyMTEx/Pbbb3z22WdUVlYSFhbGm2++yU033cStt97K3/72N6xWK1u2bGH9+vV+jfW6667jnHPOYf78+ezcuZM777yTK6+8kq+++gqAr7/+mqqqKi688MI23eO9997LSSedxIsvvkhRURF333035513Hhs2bMBmswGwdetWRowYwXXXXUd0dDTbt2/niSee4OSTT+aXX34hKCjI55wTJ07ksssu48Ybb6S0tBSAq6++mrfeeou77rqLU089lfXr1zNhwgSKiop8js3JyeH444/HarXy5z//mT59+rB8+XL+8pe/sH37dubMmQPAiBEjALjqqqu49957OeWUU4iPj2/2XidNmsSll17Ktddeyy+//MKMGTMAz5f0Wlu3bmXy5MlkZGQQHBzMzz//zF//+lc2btzosx9AdnY2V155JXfddRcPP/xwo8lbXf/4xz8YOHCgdw7R/fffz9lnn01mZibR0dEAvPDCC0ybNo1Jkybx5JNPUlhYyKxZsxp8Sa5VXFzM+PHjWbZsGW+99RaTJk1q8vr33nsvQ4cO5cUXX6SwsJCZM2cyZswYVq1aRe/evRs95rrrruOYY45h4sSJ3HrrrUyePBmHw9Hovv/85z+54YYb2Lp1K++//36zr0WtP//5zzz00ENMnDiRP/7xj0RHR7N27VqfPyjMnj2be++9l8svv5zZs2eTl5fHzJkzGTFiBCtWrKBfv37efauqqpg4cSI33ngjd955J/Pnz2fGjBkUFRXx7rvvcvfdd9O9e3eeeeYZpk6dyqBBgxg2bJhPTNdeey2nn36697P2pz/9iTFjxrBmzRpiYmIA/3wm6mvN/28qKioYO3YsW7duZdasWRx99NF88803zJ49m9WrV/Pxxx/7nPPjjz9mxYoVPPjgg0RERPDoo48yYcIENm3a1GSbi0gXMEREOklOTo4BGJdddlmD51wul1FVVeX9cbvdTZ7H5XIZJSUlRnh4uPH00097tz/wwANGY/8bmzNnjgEYmZmZhmEYxr///W8DMFavXt3kNW655RYjJiam2fv5+uuvDcD4+uuv2xxrbUw33XSTz/6PPvqoARjZ2dmGYRjGI488YgDGZ5991mws9WM6++yzfba//fbbBmAsX7680ePcbrdRVVVl7NixwwCMDz74wPtc7ev65z//2eeYdevWGYBx9913+2x/4403DMCYMmWKd9u0adOMiIgIY8eOHT77/u1vfzMAY926dd5tDz74oBEcHGwABmBkZGQYN954o/Hzzz/7HFsb16OPPuqz/aabbjJCQkKafA9VV1cbVVVVxiuvvGLYbDZj//793udGjx5tAMaiRYsaHDd69Ghj9OjR3seZmZkGYAwePNhwuVze7T/88IMBGG+88Yb3eikpKcYJJ5zgc74dO3YYQUFBRs+ePX221943YLzwwguN3oNhHGjroUOH+tzr9u3bjaCgIOO6665r8ti68T/22GM+2+t/XgzDMM4555wGcTZl27Zths1mM6644oom98nPzzdCQ0MbvE+zsrIMh8NhTJ482bttypQpBmC8++673m1VVVVGYmKiARg//fSTd3teXp5hs9mM6dOnN7ifCRMm+Fzrv//9rwEYf/nLXxqNsT2fibrP1WrN/2+ef/55AzDefvttn+3/+7//awDGF1984d0GGMnJyUZRUZF3W05OjmG1Wo3Zs2c3eQ0R6Xwa2iciATFs2DCCgoK8P48//rj3uZKSEu6++2769u2L3W7HbrcTERFBaWlpg2FArXHssccSHBzMDTfcwLx58xoM7wE4/vjjKSgo4PLLL+eDDz5odWW4tsZ6/vnn+zyuHaJXfyhgW7XmvLm5udx4442kp6djt9sJCgqiZ8+eAI3GWr9XZMmSJQBccsklPtsvuugi7HbfAQ4fffQRY8eOJS0tDZfL5f0ZP368z7nA06OTlZXFyy+/zLRp04iIiOD5559n2LBhvPHGG62614qKCnJzc73bVq1axfnnn098fDw2m42goCCuuuoqqqur+fXXX32Oj42N5dRTT21wnaacc8453l6+2uvDgdd606ZN5OTkNHidevTowUknndToOU855RRiYmKYNWsWW7Zsafb6kydP9hlK1rNnT0aOHMnXX3/d6nvwp4ULF1JdXc3NN9/c5D7Lly+nvLy8wfDP9PR0Tj31VBYtWuSz3WKxcPbZZ3sf2+12+vbtS2pqKkOGDPFuj4uLIykpqdHPzxVXXOHzeOTIkfTs2dPnderoZ6Ixrfn/zVdffUV4eDgXXXSRz/ba16f+6zF27FgiIyO9j5OTk5u8bxHpOkqkRKTTJCQkEBoa2ugv+/nz57NixQo+/PDDBs9NnjyZZ599luuuu47PP/+cH374gRUrVpCYmNiuIgt9+vThyy+/JCkpiZtvvpk+ffrQp08fnn76ae8+v/vd73j55ZfZsWMHkyZNIikpiRNOOIGFCxc2e+62xlp/2Frt8KrafXv06AFAZmZmm+6xpfO63W7OOOMM3nvvPe666y4WLVrEDz/84J3b01is9Ydj1s5nSk5O9tlut9sbXH/Pnj385z//8UmWg4KCOOqoowAaJKrJyclcffXVPP/886xZs4YlS5YQHBzMbbfd1uZ7zcrK4pRTTuG3337j6aef5pLine truncated
"text/plain": [
"<Figure size 1000x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"plt.figure(figsize=(10, 5))\n",
"# plt.plot(x, y_true, label=\"true\")\n",
"plt.scatter(x, y, s=10, label=\"data\")\n",
"plt.plot(x, gaussian_charge_sharing_kb(x, p_scipy), linewidth=3.0, label=\"SciPy curve_fit\", color=\"tab:orange\")\n",
"plt.plot(x, model(x, p_aare), linewidth=1.5, label=\"Aare Minuit2\", color=\"tab:cyan\")\n",
"plt.xlabel(\"x\")\n",
"plt.ylabel(\"y\")\n",
"plt.title(\"GaussianChargeSharingKb fit comparison\")\n",
"plt.legend()\n",
"plt.grid(True, alpha=0.3)\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "a4a6aec0",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"SciPy curve_fit : 17.591 ms\n",
"Aare Minuit2 : 1.888 ms\n",
"\n",
"SciPy bench params: [ 14.534234 0.015155 62.003314 1.998024 180.396284 0.098433\n",
" 1.104748 0.302732]\n",
"Aare bench params : [ 32.421516 0.162626 62.154113 2.413067 169.643047 0.05319\n",
" 1.271962 0. ]\n"
]
}
],
"source": [
"def bench(fn, n_repeats=200):\n",
" for _ in range(3):\n",
" fn()\n",
"\n",
" t0 = time.perf_counter()\n",
" for _ in range(n_repeats):\n",
" res = fn()\n",
" t1 = time.perf_counter()\n",
"\n",
" return res, (t1 - t0) / n_repeats\n",
"\n",
"def fit_scipy_once():\n",
" return curve_fit(\n",
" gaussian_charge_sharing_kb_curve_fit,\n",
" x,\n",
" y,\n",
" p0=p0_ref,\n",
" sigma=y_err,\n",
" absolute_sigma=True,\n",
" bounds=bounds_ref,\n",
" maxfev=50_000,\n",
" )[0]\n",
"\n",
"def fit_aare_once():\n",
" m = GaussianChargeSharingKb()\n",
" m.SetParameter(\"p0\", 10.0)\n",
" m.SetParameter(\"p1\", 0.0)\n",
" m.SetParameter(\"mu\", 50.0)\n",
" m.SetParameter(\"sigma\", 5.0)\n",
" m.SetParameter(\"N\", 150.0)\n",
" m.SetParameter(\"C\", 0.10)\n",
" m.SetParameter(\"kb_mean\", 1.20)\n",
" m.SetParameter(\"kb_frac\", 0.40)\n",
" \n",
" m.SetParLimits(\"kb_mean\", 1.02, 1.40)\n",
" m.SetParLimits(\"kb_frac\", 0.0, 1.00)\n",
" m.max_calls = 4000\n",
" m.tolerance = 0.01\n",
" return np.array(m.fit(x, y, y_err)[\"par\"], dtype=float)\n",
"\n",
"p_scipy_bench, t_scipy = bench(fit_scipy_once, n_repeats=200)\n",
"p_aare_bench, t_aare = bench(fit_aare_once, n_repeats=200)\n",
"\n",
"print(f\"SciPy curve_fit : {1e3*t_scipy:.3f} ms\")\n",
"print(f\"Aare Minuit2 : {1e3*t_aare:.3f} ms\")\n",
"print()\n",
"print(\"SciPy bench params:\", p_scipy_bench)\n",
"print(\"Aare bench params :\", p_aare_bench)"
]
},
{
"cell_type": "markdown",
"id": "77a15aea",
"metadata": {},
"source": [
"## No-Kβ sanity check\n",
"\n",
"For no-Kβ data, prefer `GaussianChargeSharing`.\n",
"\n",
"You can also use this 8-parameter model and fix:\n",
"\n",
"```python\n",
"model.FixParameter(\"kb_frac\", 0.0)\n",
"```\n",
"\n",
"but the 6-parameter model is cleaner and usually better conditioned."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "4abba19d-f4ca-4a41-adf8-1d0f4946086a",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.5"
}
},
"nbformat": 4,
"nbformat_minor": 5
}