diff --git a/examples/example_combined_fit.ipynb b/examples/example_combined_fit.ipynb index 0f9e0acd..811ee84c 100644 --- a/examples/example_combined_fit.ipynb +++ b/examples/example_combined_fit.ipynb @@ -53,19 +53,21 @@ "output_type": "stream", "text": [ "Fit with 3 parameters\n", + "Method: migrad\n", "Optimization terminated successfully.\n", - "chisquare/d.o.f.: 0.8434205014773611\n", - "fit parameters [1.01510812 0.98190604 1.45453441]\n" + "chisquare/d.o.f.: 1.1407448193242595\n", + "fit parameters [0.98418071 0.95797691 1.52431702]\n", + "chisquare/expected_chisquare: 1.1485431097238927\n" ] } ], "source": [ - "output_test = pe.combined_fits.combined_total_least_squares(x_test,y_test,funcs_test)" + "output_test = pe.fits.least_squares(x_test,y_test,funcs_test,method='migrad',expected_chisquare=True)" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "technological-rolling", "metadata": {}, "outputs": [], @@ -75,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "id": "persistent-mathematics", "metadata": {}, "outputs": [ @@ -84,11 +86,13 @@ "output_type": "stream", "text": [ "Goodness of fit:\n", - "χ²/d.o.f. = 0.843421\n", + "χ²/d.o.f. = 1.140745\n", + "χ²/χ²exp = 1.148543\n", + "p-value = 0.3293\n", "Fit parameters:\n", - "0\t 1.015(32)\n", - "1\t 0.982(32)\n", - "2\t 1.455(41)\n", + "0\t 0.984(33)\n", + "1\t 0.958(32)\n", + "2\t 1.524(42)\n", "\n" ] } @@ -99,13 +103,13 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "id": "wooden-potential", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -125,6 +129,318 @@ "plt.legend()\n", "plt.show()" ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "3b82d1c6", + "metadata": {}, + "outputs": [], + "source": [ + "x_const = {'c':list(np.arange(0,10)),'d':list(np.arange(10,20))}\n", + "y_const = {'c':[pe.Obs([np.random.normal(1, val, 1000)],['ensemble1']) \n", + " for val in [0.25,0.3,0.01,0.2,0.5,1.3,0.26,0.4,0.1,1.0]],\n", + " 'd':[pe.Obs([np.random.normal(1, val, 1000)],['ensemble1'])\n", + " for val in [0.5,1.12,0.26,0.25,0.3,0.01,0.2,1.0,0.38,0.1]]}\n", + "for key in y_const.keys():\n", + " [item.gamma_method() for item in y_const[key]]" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "7c1f7950", + "metadata": {}, + "outputs": [], + "source": [ + "def func_const(a, x):\n", + " return a[0]\n", + "\n", + "funcs_const = {\"c\": func_const,\"d\": func_const}" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "82e0cdb6", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fit with 1 parameter\n", + "Method: migrad\n", + "Optimization terminated successfully.\n", + "chisquare/d.o.f.: 0.7268201670950173\n", + "fit parameters [0.99968989]\n" + ] + } + ], + "source": [ + "output_const = pe.combined_fits.combined_fit(x_const,y_const,funcs_const,method='migrad')" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "53021f73", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13.80958317480533" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output_const.chisquare" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "id": "ab5c5bef", + "metadata": {}, + "outputs": [], + "source": [ + "output_const.gamma_method()" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "id": "d6abfe4f", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " chisquare: 13.80958317480533\n", + " chisquare_by_dof: 0.7268201670950173\n", + " dof: 19\n", + " fit_function: {'c': , 'd': }\n", + " fit_parameters: [Obs[0.99969(22)]]\n", + " iterations: 15\n", + " message: 'Optimization terminated successfully.'\n", + " method: 'migrad'\n", + " p_value: 0.7946762502119166" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output_const" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "efd3d4d0", + "metadata": {}, + "outputs": [], + "source": [ + "y_const_ls = []\n", + "for key in y_const:\n", + " for item in y_const[key]:\n", + " y_const_ls.append(item)" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "id": "57d65824", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[Obs[0.9905(78)], Obs[1.0090(96)], Obs[0.99960(32)], Obs[1.0032(62)], Obs[1.018(18)], Obs[0.988(49)], Obs[1.0084(83)], Obs[1.000(13)], Obs[0.9960(32)], Obs[1.009(34)], Obs[0.990(16)], Obs[0.970(35)], Obs[0.9865(91)], Obs[0.9981(80)], Obs[1.0065(97)], Obs[0.99983(31)], Obs[0.9985(61)], Obs[1.040(32)], Obs[1.011(12)], Obs[0.9966(31)]]\n" + ] + } + ], + "source": [ + "print(y_const_ls)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "id": "731552bc", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fit with 1 parameter\n", + "Method: Levenberg-Marquardt\n", + "`ftol` termination condition is satisfied.\n", + "chisquare/d.o.f.: 0.7268201670947627\n" + ] + } + ], + "source": [ + "output_const2 = pe.fits.least_squares(list(np.arange(0,20)),y_const_ls, func_const)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "id": "019583b5", + "metadata": {}, + "outputs": [], + "source": [ + "output_const2.gamma_method()" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "f28a3478", + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + " chisquare: 13.809583174800492\n", + " chisquare_by_dof: 0.7268201670947627\n", + " dof: 19\n", + " fit_function: \n", + " fit_parameters: [Obs[0.99969(22)]]\n", + " iterations: 7\n", + " message: '`ftol` termination condition is satisfied.'\n", + " method: 'Levenberg-Marquardt'\n", + " p_value: 0.7946762502121925" + ] + }, + "execution_count": 19, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "output_const2" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "id": "466cd303", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "colour= {'c':'red','d':'black'}\n", + "plt.figure()\n", + "for key in funcs_const.keys():\n", + " plt.errorbar(x_const[key],[o.value for o in y_const[key]],ls='none',marker='*',\n", + " color=colour[key],yerr=[o.dvalue for o in y_const[key]],capsize=3,label=key)\n", + "plt.plot(np.arange(0,20),[func_const(output_const.fit_parameters,x_val) for x_val in list(np.arange(0,20))],\n", + " label='combined fit',color ='lightblue')\n", + "plt.plot(np.arange(0,20),[func_const(output_const2.fit_parameters,x_val) for x_val in list(np.arange(0,20))],\n", + " label='one fit',color='black',ls='--')\n", + "plt.legend()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "id": "dd14c5dc", + "metadata": {}, + "outputs": [], + "source": [ + "def func_const_wrong():\n", + " a=x" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "d4e8adbc", + "metadata": {}, + "outputs": [], + "source": [ + "funcs_const_wrong = {\"c\": 4,\"d\": func_const_wrong}" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "id": "27f8d77c", + "metadata": {}, + "outputs": [ + { + "ename": "TypeError", + "evalue": "func (key=c) is not a function.", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_55611/20019894.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0moutput_const2_wrong\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mpe\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcombined_fits\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcombined_fit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx_const\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my_const\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mfuncs_const_wrong\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'migrad'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/phd/develop_pyerrors/piapyerrors/pyerrors/combined_fits.py\u001b[0m in \u001b[0;36mcombined_fit\u001b[0;34m(x, y, funcs, silent, **kwargs)\u001b[0m\n\u001b[1;32m 71\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mfuncs\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mkeys\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 72\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0;32mnot\u001b[0m \u001b[0mcallable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mfuncs\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 73\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'func (key='\u001b[0m\u001b[0;34m+\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m') is not a function.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 74\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 75\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'x and y input (key='\u001b[0m\u001b[0;34m+\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m') do not have the same length'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: func (key=c) is not a function." + ] + } + ], + "source": [ + "output_const2_wrong = pe.combined_fits.combined_fit(x_const,y_const,funcs_const_wrong,method='migrad')" + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "id": "e7110837", + "metadata": {}, + "outputs": [], + "source": [ + "x_const_wrong = {'c':list(np.arange(0,11)),'d':list(np.arange(10,20))}" + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "id": "2dae0db9", + "metadata": {}, + "outputs": [ + { + "ename": "Exception", + "evalue": "x and y input (key=c) do not have the same length", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mException\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m/tmp/ipykernel_55611/2795677260.py\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mpe\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcombined_fits\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcombined_fit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx_const_wrong\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0my_const\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mfuncs_const\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mmethod\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;34m'migrad'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m~/phd/develop_pyerrors/piapyerrors/pyerrors/combined_fits.py\u001b[0m in \u001b[0;36mcombined_fit\u001b[0;34m(x, y, funcs, silent, **kwargs)\u001b[0m\n\u001b[1;32m 73\u001b[0m \u001b[0;32mraise\u001b[0m \u001b[0mTypeError\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'func (key='\u001b[0m\u001b[0;34m+\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m') is not a function.'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 74\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mx\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m!=\u001b[0m \u001b[0mlen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0my\u001b[0m\u001b[0;34m[\u001b[0m\u001b[0mkey\u001b[0m\u001b[0;34m]\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m---> 75\u001b[0;31m \u001b[0;32mraise\u001b[0m \u001b[0mException\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m'x and y input (key='\u001b[0m\u001b[0;34m+\u001b[0m \u001b[0mkey\u001b[0m \u001b[0;34m+\u001b[0m \u001b[0;34m') do not have the same length'\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 76\u001b[0m \u001b[0;32mfor\u001b[0m \u001b[0mi\u001b[0m \u001b[0;32min\u001b[0m \u001b[0mrange\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;36m42\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 77\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mException\u001b[0m: x and y input (key=c) do not have the same length" + ] + } + ], + "source": [ + "pe.combined_fits.combined_fit(x_const_wrong,y_const,funcs_const,method='migrad')" + ] } ], "metadata": { diff --git a/pyerrors/combined_fits.py b/pyerrors/combined_fits.py index 2f92a9c2..1099305e 100644 --- a/pyerrors/combined_fits.py +++ b/pyerrors/combined_fits.py @@ -12,7 +12,7 @@ from numdifftools import Hessian as num_hessian import scipy.optimize import scipy.stats -def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): +def combined_fit(x,y,funcs,silent=False,**kwargs): r'''Performs a combined non-linear fit. Parameters ---------- @@ -62,10 +62,17 @@ def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): y_all+=y[key] x_all = np.asarray(x_all) - + + if len(x_all.shape) > 2: + raise Exception('Unknown format for x values') + # number of fit parameters n_parms_ls = [] for key in funcs.keys(): + if not callable(funcs[key]): + raise TypeError('func (key='+ key + ') is not a function.') + if len(x[key]) != len(y[key]): + raise Exception('x and y input (key='+ key + ') do not have the same length') for i in range(42): try: funcs[key](np.arange(i), x_all.T[0]) @@ -76,7 +83,7 @@ def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): else: break else: - raise RuntimeError("Fit function is not valid.") + raise RuntimeError("Fit function (key="+ key + ") is not valid.") n_parms = i n_parms_ls.append(n_parms) n_parms = max(n_parms_ls) @@ -102,22 +109,34 @@ def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): chisq += anp.sum((y_f - model)@ C_inv @(y_f - model)) return chisq - if 'tol' in kwargs: - fit_result = iminuit.minimize(chisqfunc, x0,tol=kwargs.get('tol')) - fit_result = iminuit.minimize(chisqfunc, fit_result.x,tol=kwargs.get('tol')) - else: - fit_result = iminuit.minimize(chisqfunc, x0,tol=1e-4) - fit_result = iminuit.minimize(chisqfunc, fit_result.x,tol=1e-4) + output.method = kwargs.get('method', 'Levenberg-Marquardt') + if not silent: + print('Method:', output.method) + if output.method == 'migrad': + tolerance = 1e-4 + if 'tol' in kwargs: + tolerance = kwargs.get('tol') + fit_result = iminuit.minimize(chisqfunc, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef + output.iterations = fit_result.nfev + else: + tolerance = 1e-12 + if 'tol' in kwargs: + tolerance = kwargs.get('tol') + fit_result = scipy.optimize.minimize(chisqfunc, x0, method=kwargs.get('method'), tol=tolerance) + output.iterations = fit_result.nit + chisquare = fit_result.fun - - output.method = 'migrad' output.message = fit_result.message + + if not fit_result.success: + raise Exception('The minimization procedure did not converge.') if x_all.shape[-1] - n_parms > 0: output.chisquare = chisqfunc(fit_result.x) output.dof = x_all.shape[-1] - n_parms output.chisquare_by_dof = output.chisquare/output.dof + output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof) else: output.chisquare_by_dof = float('nan') @@ -145,9 +164,22 @@ def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): chisq = anp.sum(list_tmp) return chisq + def prepare_hat_matrix(): # should be cross-checked again + hat_vector = [] + for key in funcs.keys(): + x_array = np.asarray(x[key]) + if (len(x_array)!= 0): + hat_vector.append(anp.array(jacobian(funcs[key])(fit_result.x, x_array))) + hat_vector = [item for sublist in hat_vector for item in sublist] + return hat_vector + fitp = fit_result.x y_f = [o.value for o in y_all] # y_f is constructed based on the ordered dictionary if the order is changed then the y values are not allocated to the the correct x and func values in the hessian dy_f = [o.dvalue for o in y_all] # the same goes for dy_f + + if np.any(np.asarray(dy_f) <= 0.0): + raise Exception('No y errors available, run the gamma method first.') + try: hess = hessian(chisqfunc)(fitp) except TypeError: @@ -160,6 +192,20 @@ def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:]) except np.linalg.LinAlgError: raise Exception("Cannot invert hessian matrix.") + + + if kwargs.get('expected_chisquare') is True: + if kwargs.get('correlated_fit') is not True: + W = np.diag(1 / np.asarray(dy_f)) + cov = covariance(y_all) + hat_vector = prepare_hat_matrix() + A = W @ hat_vector #hat_vector = 'jacobian(func)(fit_result.x, x)' + P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T + expected_chisquare = np.trace((np.identity(x.shape[-1]) - P_phi) @ W @ cov @ W) + output.chisquare_by_expected_chisquare = chisquare / expected_chisquare + if not silent: + print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare) + result = [] for i in range(n_parms): diff --git a/pyerrors/fits.py b/pyerrors/fits.py index c7dac075..e2998b25 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -70,9 +70,13 @@ class Fit_result(Sequence): def least_squares(x, y, func, priors=None, silent=False, **kwargs): r'''Performs a non-linear fit to y = func(x). + + ``` Parameters ---------- + For an uncombined fit: + x : list list of floats. y : list @@ -94,9 +98,35 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): (x1, x2) = x return a[0] * x1 ** 2 + a[1] * x2 ``` - It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation will not work. + + OR For a combined fit: + + Do not need to use ordered dictionaries: python version >= 3.7: Dictionary order is guaranteed to be insertion order. + (https://docs.python.org/3/library/stdtypes.html#dict-views) Ensures that x, y and func values are mapped correctly. + + x : ordered dict + dict of lists. + y : ordered dict + dict of lists of Obs. + funcs : ordered dict + dict of objects + fit functions have to be of the form (here a[0] is the common fit parameter) + ```python + import autograd.numpy as anp + funcs = {"a": func_a, + "b": func_b} + + def func_a(a, x): + return a[1] * anp.exp(-a[0] * x) + + def func_b(a, x): + return a[2] * anp.exp(-a[0] * x) + + It is important that all numpy functions refer to autograd.numpy, otherwise the differentiation + will not work. + priors : list, optional priors has to be a list with an entry for every parameter in the fit. The entries can either be Obs (e.g. results from a previous fit) or strings containing a value and an error formatted like @@ -130,6 +160,10 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): ''' if priors is not None: return _prior_fit(x, y, func, priors, silent=silent, **kwargs) + + elif (type(x)==dict and type(y)==dict and type(func)==dict): + return _combined_fit(x, y, func, silent=silent, **kwargs) + else: return _standard_fit(x, y, func, silent=silent, **kwargs) @@ -462,7 +496,6 @@ def _prior_fit(x, y, func, priors, silent=False, **kwargs): def _standard_fit(x, y, func, silent=False, **kwargs): - output = Fit_result() output.fit_function = func @@ -655,6 +688,181 @@ def _standard_fit(x, y, func, silent=False, **kwargs): return output +def _combined_fit(x,y,func,silent=False,**kwargs): + + if kwargs.get('correlated_fit') is True: + raise Exception("Correlated fit has not been implemented yet") + + output = Fit_result() + output.fit_function = func + + if kwargs.get('num_grad') is True: + jacobian = num_jacobian + hessian = num_hessian + else: + jacobian = auto_jacobian + hessian = auto_hessian + + x_all = [] + y_all = [] + for key in x.keys(): + x_all+=x[key] + y_all+=y[key] + + x_all = np.asarray(x_all) + + if len(x_all.shape) > 2: + raise Exception('Unknown format for x values') + + # number of fit parameters + n_parms_ls = [] + for key in func.keys(): + if not callable(func[key]): + raise TypeError('func (key='+ key + ') is not a function.') + if len(x[key]) != len(y[key]): + raise Exception('x and y input (key='+ key + ') do not have the same length') + for i in range(42): + try: + func[key](np.arange(i), x_all.T[0]) + except TypeError: + continue + except IndexError: + continue + else: + break + else: + raise RuntimeError("Fit function (key="+ key + ") is not valid.") + n_parms = i + n_parms_ls.append(n_parms) + n_parms = max(n_parms_ls) + if not silent: + print('Fit with', n_parms, 'parameter' + 's' * (n_parms > 1)) + + if 'initial_guess' in kwargs: + x0 = kwargs.get('initial_guess') + if len(x0) != n_parms: + raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) + else: + x0 = [0.1] * n_parms + + def chisqfunc(p): + chisq = 0.0 + for key in func.keys(): + x_array = np.asarray(x[key]) + model = anp.array(func[key](p,x_array)) + y_obs = y[key] + y_f = [o.value for o in y_obs] + dy_f = [o.dvalue for o in y_obs] + C_inv = np.diag(np.diag(np.ones((len(x_array),len(x_array)))))/dy_f/dy_f + chisq += anp.sum((y_f - model)@ C_inv @(y_f - model)) + return chisq + + output.method = kwargs.get('method', 'Levenberg-Marquardt') + if not silent: + print('Method:', output.method) + + if output.method == 'migrad': + tolerance = 1e-4 + if 'tol' in kwargs: + tolerance = kwargs.get('tol') + fit_result = iminuit.minimize(chisqfunc, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef + output.iterations = fit_result.nfev + else: + tolerance = 1e-12 + if 'tol' in kwargs: + tolerance = kwargs.get('tol') + fit_result = scipy.optimize.minimize(chisqfunc, x0, method=kwargs.get('method'), tol=tolerance) + output.iterations = fit_result.nit + + chisquare = fit_result.fun + output.message = fit_result.message + + if not fit_result.success: + raise Exception('The minimization procedure did not converge.') + + if x_all.shape[-1] - n_parms > 0: + output.chisquare = chisqfunc(fit_result.x) + output.dof = x_all.shape[-1] - n_parms + output.chisquare_by_dof = output.chisquare/output.dof + output.p_value = 1 - scipy.stats.chi2.cdf(output.chisquare, output.dof) + else: + output.chisquare_by_dof = float('nan') + + if not silent: + print(fit_result.message) + print('chisquare/d.o.f.:', output.chisquare_by_dof ) + print('fit parameters',fit_result.x) + + def chisqfunc_compact(d): + chisq = 0.0 + list_tmp = [] + c1 = 0 + c2 = 0 + for key in func.keys(): + x_array = np.asarray(x[key]) + c2+=len(x_array) + model = anp.array(func[key](d[:n_parms],x_array)) + y_obs = y[key] + y_f = [o.value for o in y_obs] + dy_f = [o.dvalue for o in y_obs] + C_inv = np.diag(np.diag(np.ones((len(x_array),len(x_array)))))/dy_f/dy_f + list_tmp.append(anp.sum((d[n_parms+c1:n_parms+c2]- model)@ C_inv @(d[n_parms+c1:n_parms+c2]- model))) + c1+=len(x_array) + chisq = anp.sum(list_tmp) + return chisq + + def prepare_hat_matrix(): + hat_vector = [] + for key in func.keys(): + x_array = np.asarray(x[key]) + if (len(x_array)!= 0): + hat_vector.append(anp.array(jacobian(func[key])(fit_result.x, x_array))) + hat_vector = [item for sublist in hat_vector for item in sublist] + return hat_vector + + fitp = fit_result.x + y_f = [o.value for o in y_all] + dy_f = [o.dvalue for o in y_all] + + if np.any(np.asarray(dy_f) <= 0.0): + raise Exception('No y errors available, run the gamma method first.') + + try: + hess = hessian(chisqfunc)(fitp) + except TypeError: + raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None + + jac_jac_y = hessian(chisqfunc_compact)(np.concatenate((fitp, y_f))) + + # Compute hess^{-1} @ jac_jac_y[:n_parms + m, n_parms + m:] using LAPACK dgesv + try: + deriv_y = -scipy.linalg.solve(hess, jac_jac_y[:n_parms, n_parms:]) + except np.linalg.LinAlgError: + raise Exception("Cannot invert hessian matrix.") + + + if kwargs.get('expected_chisquare') is True: + if kwargs.get('correlated_fit') is not True: + W = np.diag(1 / np.asarray(dy_f)) + cov = covariance(y_all) + hat_vector = prepare_hat_matrix() + A = W @ hat_vector #hat_vector = 'jacobian(func)(fit_result.x, x)' + P_phi = A @ np.linalg.pinv(A.T @ A) @ A.T + expected_chisquare = np.trace((np.identity(x_all.shape[-1]) - P_phi) @ W @ cov @ W) + output.chisquare_by_expected_chisquare = chisquare / expected_chisquare + if not silent: + print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare) + + + result = [] + for i in range(n_parms): + result.append(derived_observable(lambda x_all, **kwargs: (x_all[0] + np.finfo(np.float64).eps) / (y_all[0].value + np.finfo(np.float64).eps) * fitp[i], list(y_all), man_grad=list(deriv_y[i]))) + + output.fit_parameters = result + + return output + + def fit_lin(x, y, **kwargs): """Performs a linear fit to y = n + m * x and returns two Obs n, m.