From 78c7c32f1cc28b3b1aea53784586a0072403da87 Mon Sep 17 00:00:00 2001 From: ppetrak Date: Wed, 14 Dec 2022 15:09:42 +0100 Subject: [PATCH 01/13] (temporary) least squares function for combined fits with dictionaries (+ example) --- examples/example_combined_fit.ipynb | 151 ++++++++++++++++++++++++ pyerrors/__init__.py | 1 + pyerrors/combined_fits.py | 170 ++++++++++++++++++++++++++++ 3 files changed, 322 insertions(+) create mode 100644 examples/example_combined_fit.ipynb create mode 100644 pyerrors/combined_fits.py diff --git a/examples/example_combined_fit.ipynb b/examples/example_combined_fit.ipynb new file mode 100644 index 00000000..0f9e0acd --- /dev/null +++ b/examples/example_combined_fit.ipynb @@ -0,0 +1,151 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "ethical-frontier", + "metadata": {}, + "outputs": [], + "source": [ + "import pyerrors as pe\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "incredible-posting", + "metadata": {}, + "outputs": [], + "source": [ + "x_test = {'a':[0,1,2,3,4,5],'b':[0,1,2,3,4,5]}\n", + "y_test = {'a':[pe.Obs([np.random.normal(i, i*1.5, 1000)],['ensemble1']) for i in range(1,7)],\n", + " 'b':[pe.Obs([np.random.normal(val, val*1.5, 1000)],['ensemble1']) for val in [1.0,2.5,4.0,5.5,7.0,8.5]]}\n", + "for key in y_test.keys():\n", + " [item.gamma_method() for item in y_test[key]]" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "subtle-malaysia", + "metadata": {}, + "outputs": [], + "source": [ + "def func_a(a, x):\n", + " return a[1] * x + a[0]\n", + "\n", + "def func_b(a, x):\n", + " return a[2] * x + a[0]\n", + "\n", + "funcs_test = {\"a\": func_a,\"b\": func_b}" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "modern-relay", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fit with 3 parameters\n", + "Optimization terminated successfully.\n", + "chisquare/d.o.f.: 0.8434205014773611\n", + "fit parameters [1.01510812 0.98190604 1.45453441]\n" + ] + } + ], + "source": [ + "output_test = pe.combined_fits.combined_total_least_squares(x_test,y_test,funcs_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "technological-rolling", + "metadata": {}, + "outputs": [], + "source": [ + "output_test.gamma_method()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "persistent-mathematics", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Goodness of fit:\n", + "χ²/d.o.f. = 0.843421\n", + "Fit parameters:\n", + "0\t 1.015(32)\n", + "1\t 0.982(32)\n", + "2\t 1.455(41)\n", + "\n" + ] + } + ], + "source": [ + "print(output_test)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "wooden-potential", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "
" + ] + }, + "metadata": { + "needs_background": "light" + }, + "output_type": "display_data" + } + ], + "source": [ + "colour= {'a':'red','b':'black'}\n", + "plt.figure()\n", + "for key in funcs_test.keys():\n", + " plt.errorbar(x_test[key],[o.value for o in y_test[key]],ls='none',marker='*',color=colour[key],yerr=[o.dvalue for o in y_test[key]],capsize=3,label=key)\n", + " plt.plot([x_val for x_val in x_test[key]],[funcs_test[key](output_test.fit_parameters,x_val) for x_val in x_test[key]],color=colour[key],label='func_'+key)\n", + "plt.legend()\n", + "plt.show()" + ] + } + ], + "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.7.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyerrors/__init__.py b/pyerrors/__init__.py index a6fefe8d..1949e68a 100644 --- a/pyerrors/__init__.py +++ b/pyerrors/__init__.py @@ -457,6 +457,7 @@ from .obs import * from .correlators import * from .fits import * from .misc import * +from . import combined_fits from . import dirac from . import input from . import linalg diff --git a/pyerrors/combined_fits.py b/pyerrors/combined_fits.py new file mode 100644 index 00000000..2f92a9c2 --- /dev/null +++ b/pyerrors/combined_fits.py @@ -0,0 +1,170 @@ +import iminuit +import autograd.numpy as anp +from autograd import jacobian +from pyerrors.fits import Fit_result +import numpy as np +import pyerrors as pe +from autograd import jacobian as auto_jacobian +from autograd import hessian as auto_hessian +from autograd import elementwise_grad as egrad +from numdifftools import Jacobian as num_jacobian +from numdifftools import Hessian as num_hessian +import scipy.optimize +import scipy.stats + +def combined_total_least_squares(x,y,funcs,silent=False,**kwargs): + r'''Performs a combined non-linear fit. + Parameters + ---------- + 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. + silent : bool, optional + If true all output to the console is omitted (default False). + initial_guess : list + can provide an initial guess for the input parameters. Relevant for + non-linear fits with many parameters. + num_grad : bool + Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). + ''' + + output = Fit_result() + output.fit_function = funcs + + 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) + + # number of fit parameters + n_parms_ls = [] + for key in funcs.keys(): + for i in range(42): + try: + funcs[key](np.arange(i), x_all.T[0]) + except TypeError: + continue + except IndexError: + continue + else: + break + else: + raise RuntimeError("Fit function 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 funcs.keys(): + x_array = np.asarray(x[key]) + model = anp.array(funcs[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 + + 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) + + chisquare = fit_result.fun + + output.method = 'migrad' + output.message = fit_result.message + + 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 + 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) + + # use ordered dicts so the data and fit parameters can be mapped correctly + def chisqfunc_compact(d): + chisq = 0.0 + list_tmp = [] + c1 = 0 + c2 = 0 + for key in funcs.keys(): + x_array = np.asarray(x[key]) + c2+=len(x_array) + model = anp.array(funcs[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 + + 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 + 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.") + + result = [] + for i in range(n_parms): + result.append(pe.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 \ No newline at end of file From 9a05fc916a8c4afc3ab9dc4833728dd35631b742 Mon Sep 17 00:00:00 2001 From: ppetrak Date: Fri, 16 Dec 2022 18:47:25 +0100 Subject: [PATCH 02/13] incorparated (uncorrelated) combined fits in fits.least_squares --- examples/example_combined_fit.ipynb | 338 +++++++++++++++++++++++++++- pyerrors/combined_fits.py | 68 +++++- pyerrors/fits.py | 212 ++++++++++++++++- 3 files changed, 594 insertions(+), 24 deletions(-) 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. From 500c5234cfd1e4b8cf2209e00cd332d8033efb2e Mon Sep 17 00:00:00 2001 From: ppetrak Date: Fri, 16 Dec 2022 18:55:43 +0100 Subject: [PATCH 03/13] clean-up --- examples/example_combined_fit.ipynb | 216 ++++++++++------------------ pyerrors/combined_fits.py | 216 ---------------------------- pyerrors/fits.py | 18 +-- 3 files changed, 81 insertions(+), 369 deletions(-) delete mode 100644 pyerrors/combined_fits.py diff --git a/examples/example_combined_fit.ipynb b/examples/example_combined_fit.ipynb index 811ee84c..e658e7da 100644 --- a/examples/example_combined_fit.ipynb +++ b/examples/example_combined_fit.ipynb @@ -55,19 +55,19 @@ "Fit with 3 parameters\n", "Method: migrad\n", "Optimization terminated successfully.\n", - "chisquare/d.o.f.: 1.1407448193242595\n", - "fit parameters [0.98418071 0.95797691 1.52431702]\n", - "chisquare/expected_chisquare: 1.1485431097238927\n" + "chisquare/d.o.f.: 0.3395164548834892\n", + "fit parameters [0.98791658 1.00784727 1.56875359]\n", + "chisquare/expected_chisquare: 0.339844373345418\n" ] } ], "source": [ - "output_test = pe.fits.least_squares(x_test,y_test,funcs_test,method='migrad',expected_chisquare=True)" + "output_test = pe.fits.least_squares(x_test,y_test,funcs_test,expected_chisquare=True)" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 5, "id": "technological-rolling", "metadata": {}, "outputs": [], @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 6, "id": "persistent-mathematics", "metadata": {}, "outputs": [ @@ -86,13 +86,13 @@ "output_type": "stream", "text": [ "Goodness of fit:\n", - "χ²/d.o.f. = 1.140745\n", - "χ²/χ²exp = 1.148543\n", - "p-value = 0.3293\n", + "χ²/d.o.f. = 0.339516\n", + "χ²/χ²exp = 0.339844\n", + "p-value = 0.9620\n", "Fit parameters:\n", - "0\t 0.984(33)\n", - "1\t 0.958(32)\n", - "2\t 1.524(42)\n", + "0\t 0.988(35)\n", + "1\t 1.008(32)\n", + "2\t 1.569(42)\n", "\n" ] } @@ -103,13 +103,13 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "wooden-potential", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAD4CAYAAADFAawfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAyL0lEQVR4nO3de3zO9f/H8cd7TttkFMmcIpnmEDKHSlFSKn0pScwhKSXbzPnQD6MccmZO6UvfDTnEVE6Rcowwch5zHGNYzszGdr1/f7ydKoex69rn2nW97rfbdfvucO1zva7R8/v2+rwPSmuNEEII5+VhdQFCCCHuToJaCCGcnAS1EEI4OQlqIYRwchLUQgjh5LI74qIFChTQJUqUcMSlhRDCJW3atOkvrfWjt/ueQ4K6RIkSREdHO+LSQgjhkpRScXf6nrQ+hBDCyUlQCyGEk5OgFkIIJ+eQHvXtXL16lfj4eJKTkzPrJZ2ap6cnRYsWJUeOHFaXIoRwcpkW1PHx8eTJk4cSJUqglMqsl3VKWmtOnTpFfHw8JUuWtLocIYSTy7TWR3JyMvnz53f7kAZQSpE/f37514UQIl0ytUctIX2T/C6EEOklNxOFEMLJSVALIUQGhIWFoZT61yMsLMxur+FWQT1mzBj8/f0JDAy0uhQhhIsICwtDa02tWrWoVasWWmu01nYN6kyb9eEMxo8fz7JlyyhatKjVpQghRLpZE9ShobBli32vWakSjBp1x29/+umnHDhwgNdff53Dhw/Tu3dvunTpAkD58uVZsGABAK+//jo1a9Zk7dq1FClShB9//BEvLy/27dvHp59+SmJiItmyZeP777+nVKlS/3qdixcv0qBBA86cOcPVq1f58ssvadCggX3fqxDCrbhN62PixIkULlyY5cuX07Fjxzs+b+/evbRv356dO3eSL18+5s6dC0BgYCDt27dn69atrF27Fl9f39v+vKenJ/PmzWPz5s0sX76czp07I+dSCiEywpoR9V1GvlYrWbIklSpVAqBKlSocOnSICxcucPToUd5++23AhPGdaK3p1asXq1atwsPDg6NHj3LixAkKFSqUGeULIVyQW/Wor8uePTs2m+3G57cuPMmVK9eNj7Nly8bly5fv69rTp08nMTGRTZs2kSNHDkqUKCELW4QQGeI2rY9blShRgs2bNwOwefNmDh48eNfn58mTh6JFi/LDDz8AkJKSQlJS0m2fe+7cOQoWLEiOHDlYvnw5cXF33GJWCOFCUlJS2LJlC8ePH7f7td0yqBs1asTp06cpV64cY8eOxc/P754/M3XqVMaMGcPTTz/Nc889d8c/jMDAQKKjo6lQoQKRkZE89dRT9i5fCOGE4uLiOHfuHP3797f7tZUjbnQFBATof57wEhMTg7+/v91fKyuT34kQWZ+Xl9dt25uenp731TpVSm3SWgfc7ntuOaIWQgh7OXDgAM2aNcPDw8Spt7c3gYGB92yp3g+3vJloD9u3b6dFixZ/+1quXLlYv369RRUJIazg6+uLj48PNpsNDw8PkpOT8fHxsetMLwnqB1ShQgW22HvRjhAiSzpx4gSFCxfG19eX6tWrk5CQYNfrS1ALIUQGRUVFUbt2bQDGjRtn9+tLj1oIIZycjKiFEMIO0tLS7ri+IqMkqIUQIgO01sydO5eNGzdis9lISkrC29vbrq/hVq2PzNiPesWKFdSvX99h1xdCOI9du3ZRt25dGjduTPbs2SlbtqzdQxrcbEQt+1ELIezh3LlzhIWFER4ejo+PD+PGjWPWrFkOOwvVkqAODQ21+9S2SpUqMcoJ9qMGOH/+PG+++Sb79u3jpZdeYvz48Tcmwwshsi6bzUZkZCTdu3cnMTGRjz/+mLx589K+ffsbz7ke1n379rXbKS9ukx6ZtR81wIYNGwgPD2fXrl3s37+fqKgou78fIUTmio6O5vnnn6d169aUKlWKjRs38vXXXzNkyJAbx2/d+sjyR3HdbeRrtYzuRw1QrVo1nnjiCQCaNm3KmjVrePfddx1atxDCMRITE+nVqxeTJ0+mYMGCRERE0Lx580z9V7LbjKhvdT/7Uaempt739f/Zp3JU30oI4Tipqak3dtf83//+R6dOnYiNjaVly5aZ3sp0y6B25H7UYFofBw8exGazMWvWLGrWrGm32oUQjrdy5UqeeeYZgoODCQgIYNu2bQwbNgwfHx9L6nHLoHbkftQAVatWJSgoCH9/f0qWLHmjZSKEcG7x8fE0bdqU2rVrc/78eebOncvSpUst345Y9qO2kPxOhHAOKSkpjBgxggEDBpCWlkb37t3p1q2bQ+ZE38nd9qN2q3nUQgjxT4sWLaJDhw7s27ePhg0bMmLECEqWLGl1WX8jQf2AZD9qIbK2ffv20bFjRxYsWECZMmVYsmQJr776qtVl3Va6glop1RH4CNDAdqC11tqtj9aW/aiFyJouXbrEwIEDGTZsGDlz5mTo0KGEhISQM2dOq0u7o3veTFRKFQFCgACtdXkgG/C+owsTQgh70loza9YsnnrqKQYOHEiTJk2IjY2lS5cuTh3SkP5ZH9kBL6VUdsAbOOa4koQQwr62b9/Oyy+/zPvvv0+BAgVYs2YNkZGRd11h7EzuGdRa66PAMOAwkACc01ov/efzlFJtlVLRSqnoxMTEjFUVFgZK/fthxyWZQgjXd/bsWUJCQqhcuTLbtm1jwoQJN5aCZyXpaX08DDQASgKFgdxKqeb/fJ7WepLWOkBrHfDoo49mrKqwMNAaatUyD63NQ4JaCJEONpuNyZMn4+fnx7hx42jbti2xsbF8+umnZMuWzery7lt6Wh+vAAe11ola66tAFPCcY8u6JiUFtmyBuywuEUKIW23YsIEaNWrw0Ucf4efnR3R0NOPHjyd//vxWl/bA0hPUh4EaSilvZTatqAPEOLasa+Li4Nw56N/fLpdr2LAhVapUoVy5ckyaNMku1xRCOIeTJ0/Spk0bqlevTnx8PNOmTWP16tVUrlzZ6tIyLD096vXAHGAzZmqeB+DYlPPyMj3p60euT5hgPvfyytBlp0yZwqZNm4iOjmbMmDGcOnXKDsUKIax09epVRo8ejZ+fH1OnTqVr167s2bOHwMBAl9kQLV2zPrTWfbXWT2mty2utW2itUxxa1YED0KwZXN+hytsbAgPhHpsn3cuYMWOoWLEiNWrU4MiRI+zdu9cOxQohrLJ8+XIqV65MaGgoNWrUYPv27QwZMoQ8efJYXZpdOeemTL6+4OMDNpsJ6+Rk83mhQg98yRUrVrBs2TLWrVvH1q1bqVy58t+2NxVCZB2HDx/mvffe4+WXXyYpKYkffviBxYsXU6ZMGatLcwjnXUJ+4gQULmxCu3r1m22QB3Tu3DkefvhhvL292b17N3/88YedChVCZJbk5GSGDRvGwIED0VrTr18/unbtilcG26LOznmDOioKatc2H48bl+HL1atXj4kTJ+Lv70+ZMmWoUaNGhq8phMgcWmsWLFhAaGgoBw4coFGjRgwfPpzHH3/c6tIyhfMGtZ3lypWLxYsXW12GEOI+xcbGEhoayuLFi/H39+eXX37hlVdesbqsTOWcPerrKxNXrjQPWZkohNu5ePEiPXr0oHz58vz++++MGDGCrVu3ul1Ig7OOqMPCJJSFcFNaa2bMmEHXrl05duwYH3zwAYMGDaJQBiYTZHXOOaIWQrilrVu3Urt2bQIDA/H19WXt2rV8++23bh3SIEEthHACp0+fJigoiGeeeYadO3cyadIk1q9fz7PPPmt1aU7BOVsfQgi3kJaWxuTJk+nVqxdnzpzhs88+o3///jz88MNWl+ZUnHJEHRYWhlLqX48w6VsL4TLWrVtH9erV+eSTTyhbtiybN28mPDxcQvo2nDaotdbUqlWLWrVqobVGa52hoD506BDly5e3X5FCiH9JzyDr+PHjfPDBBzz33HMkJCTw3XffsXLlSipWrGhd4U7OKYP6upSUFLZs2cJx2eZUiCzhboOsq1evMmLECPz8/Pjuu+/o0aMHe/bsoWnTpi6zeZKjOHVQx8XFce7cOfrbaZvT1NRUAgMD8ff359133yUpKcku1xVC3N2yZcuoWLEinTt3pmbNmuzYsYNBgwbx0EMPWV1aluCUQe3l5YVSioRr+3tMmDABpVSG1/Pv2bOHzz77jJiYGHx8fBg/frw9yhVC3EFcXByNGjWibt26pKSk8NNPP7Fw4UL8/PysLi1LccqgPnDgAM2aNcPj2jan3t7eBAYGcjCD25wWK1bsxllpzZs3Z82aNRmuVQjxb5cvX+aPP/6gTJkyLF68mC+//JKdO3fy1ltvSZvjATjl9DxfX198fHyw2Wx4eHiQnJyMj49Phie9//MviPyFEcK+tNb8+OOPbNq0ibS0NEqVKsXy5cspVqyY1aVlaU4Z1AAnTpygcOHC+Pr6Ur169RttkIw4fPgw69at49lnn+W7776jZs2adqhUCAGwe/duypUrh81mu/G1/fv3U7x4cTw9Pbl8+bKF1WVtTtn6AIiKiqJ06dI89NBDjBs3jqioqAxfs0yZMowbNw5/f3/OnDlDu3bt7FCpEO7t/PnzdO3alQoVKpA7d26eeeYZu7ct3Z3TjqjtrUSJEuzevdvqMoRwGVprpk+fTteuXTl+/DgffvghgwYNom/fvmzevNmubUt355Qj6uuT5leuXMnKlStlZaIQTubPP//khRdeoEWLFhQrVoz169czefJkChYseKNtWblyZT799FNZB2EHSmtt94sGBATo6Ojov30tJiYGf39/u79WVia/E5HVnDp1iv/7v//j66+/pkCBAgwaNIjWrVvfaHVcV/va6UwrVqzI/CKzKKXUJq11wO2+l6kjakf8n0JWJb8LkZWkpaUxYcIE/Pz8+OabbwgODiY2NpY2bdr8K6SF/WXab9jT05NTp05JQGFC+tSpU3h6elpdihD3tGbNGgICAvjss894+umn+fPPPxk9ejT58uWzujS3kWk3E4sWLUp8fDyJiYmZ9ZJOzdPTk6JFi1pdhhB3lJCQQLdu3Zg2bRpFixZl1qxZNG7cWNYfWCDTgjpHjhyULFkys15OCPGArly5wujRo+nfvz9Xrlzh888/p2fPnuTOnfuePxsWFka/fv1ufH491Pv27SuTATIg024mCiGc35IlS+jQoQN79uyhfv36jBw5kieffNLqstyC09xMFEI4p4MHD9KwYUPq1atHWloaCxcuZP78+RLSTkKCWgg3lpSURJ8+ffD392fZsmUMGjSIHTt28MYbb1hdmriF26xMFELcpLUmKiqKTp06cfjwYZo2bcqQIUPkBreTkqAWws3s2rWLkJAQfv31VypUqMCKFSuoVauW1WWJu5DWhxBu4ty5c3Tq1ImKFSuyadMmwsPD2bx5s4R0FiBBLYSLs9lsREREUKZMGUaNGkXr1q2JjY0lKCiI7NnlH9UZFhYGSv37YcfpiPKnJIQL27RpE0FBQfzxxx/UqFGDBQsWEBBw2xlg4kGFhZnHtf1NcMD+JjKiFsIF/fXXX7Rt25aqVaty4MABvv32W37//XcJ6SxKgloIF5KamsrYsWMpXbo0U6ZMITQ0lNjYWD744APZPMnRUlJgyxZwwLau8icnhItYtWoVVapUITg4mCpVqrBt2zZGjBhB3rx5rS7NPcTFwblz0L+/3S8tQS1EFnf06FGaNWtGrVq1OHv2LHPmzOGXX36hbNmyVpfmHry8zM3D6+e6TphgPvfysttLSFALkUWlpKQwePBgypQpQ1RUFH369CEmJoZGjRrJDneZ6cABaNYMrreWvL0hMBDseE5kumZ9KKXyAf8FygMa+FBrvc5uVQgh7suiRYsIDQ1l7969NGjQgBEjRvDEE09YXZZ78vUFHx+w2UxYJyebz+14TmR6p+eNBn7WWr+rlMoJeNutAiFEuu3fv5/Q0FAWLFiAn58fP//8M6+99prVZYkTJ6BwYRPa1avfbIPYyT2DWimVF3gR+ABAa30FuGLXKoQQd3Xp0iUGDhzIsGHDyJkzJ0OGDKFDhw7kzJnT6tIEQFTUzXnU48bZ/fLpGVGXBBKBb5VSFYFNQAet9aVbn6SUagu0BShevLi96xTCLWmt+f777+ncuTPx8fE0b96cr776isKFC1tdmshE6bmZmB14Bpigta4MXAJ6/PNJWutJWusArXXAo48+aucyhXA/O3bsoE6dOjRp0oQCBQqwevVqpk6dKiHthtIT1PFAvNZ6/bXP52CCWwjhAGfPnqVDhw5UqlSJrVu3Mn78eKKjo6lZs6bVpQmL3DOotdbHgSNKqTLXvlQH2OXQqoRwQzabjSlTpuDn50d4eDgff/wxsbGxtGvXjmzZslldnriT65syrVxpHg7YlCldZyYqpSphpuflBA4ArbXWZ+70fDkzUYj7s2HDBoKDg9mwYQPPPfccY8eOpXLlylaXJdJLa1i3DmJioE2bB7rE3c5MTNf0PK31FkB2cxHCzk6ePEnPnj2ZMmUKhQoVIjIykubNm8uClawiLg6mToXISNi7FwoUgBYtwM6zcWRlohAWSE1NZfTo0fj5+REZGUmXLl3Ys2cPLVq0kJB2dhcvQkQEvPwylCgBvXubOdRTpphVig6YMin7UQuRyZYvX05wcDA7d+7k1VdfZfTo0Tz11FNWlyXuxmYz+0xHRMDcuXDpEpQqBf36mRF0yZIOfXkJaiEcJCwsjH79+t32eyVKlGDevHk0aNBARtDObO9eE85Tp8Lhw2ZpeNOm0KoVPP+8uWmYCdJ1M/F+yc1EIW568cUXOXLkCCdPnsRms9GzZ0+6du2Klx13VxN2dPYszJplAnrdOrN/R926JpwbNrTrrni3yvDNRCHEg1mwYAEbN24kOTmZd955h+HDh1OiRAmryxL/lJoKS5eacP7xR3MIQNmy8NVX0Ly56UFbSIJaCAfYu3cvoaGhLFq0CC8vL7y9vRk3bhyF7LijmrCD7dtNOE+fbk5myZ8fPv7YjJ6rVMm01sa9yKwPIezo4sWL9OzZk/Lly7N69WqGDx9Ovnz5SEpKor8DTv4QDyAxEUaPhmeegaefNh/XqGE2Vjp2DMLDISDAaUIapEcthF1orZk5cyZdu3bl6NGjtGrVipkzZ5KSkvKv53p6enL58mULqnRjKSmwcKEZPS9aZFodzzxjRs5Nm4IT7E90tx61jKiFyKBt27ZRu3ZtmjVrxmOPPcbatWv53//+x8GDB2nWrNmNQ2W9vb0JDAzkoB1P/hB3oTVs3AhBQabH3KgRbNgAoaGm5bFpE4SEOEVI34v0qIV4QGfOnKFPnz6MHz+ehx9+mK+//po2bdrc2JfD19cXHx8fbDYbHh4eJCcn4+PjI31qRzt6FKZNM6PnmBjIlcvM1mjVyszeyJ71Yi/rVSyExdLS0pgyZQq9evXi9OnTtGvXjv79+/PII4/867knTpygcOHC+Pr6Ur16dRLsfPKHuCYpCX74wYTzsmVmgcpzz8HXX8N770G+fFZXmCHSoxbiPvzxxx8EBQWxadMmXnjhBcLDw6lYseJdf6b2tZM/VqxY4fgC3YnWsGaNCefZs+HCBSheHFq2NI/Spa2u8L7IPGohMuj48eP06NGDiIgIChcuzPTp02natKmsKrTCwYNmE6TISLO3Ru7c8O67prVRq9bN08BdiAS1EHdx9epVwsPD6devH5cvX6Z79+58/vnn5MmTx+rS3Mv58zBnjhk9r1plps69/LLZ8/ntt+Ghh6yu0LG01nZ/VKlSRQuR1S1btkz7+/trQL/++ut6z5499/Xzffv21cC/Hn379nVMwa4mNVXrpUu1DgzU2stLa9Daz0/rAQO0jouzujq7A6L1HTJVetRC/ENcXBydO3dm7ty5PPHEE4waNYr69etLmyOz7N59cyOko0fNjcD33zetjerVnWohij1Jj1qIdLh8+TJDhw5l8ODBAHzxxRd06dIFT09PiytzA6dPw8yZJqA3bIBs2aBePRg5Et56C9z8z0CCWrg9rTU//fQToaGhHDp0iMaNGzNs2DCKFy9udWmu7epV+PlnE87z58OVK2ZJ9/Dh0KwZyHzzGySohVvbs2cPHTp0YMmSJZQrV45ff/2Vl19+2eqyXNuWLTc3QkpMNCsDP/vMtDYqVbK6OqckQS3c0oULF/jiiy8YNWoUXl5ejBw5kvbt25MjRw6rS3NNx4+bYI6MhG3bzHFVb71lwrlePZDf+11JUAu3orVm+vTpdOvWjYSEBFq3bs2gQYN47LHHrC7NNYSFmeOp/kkps0ClWjUYNw6aNDFbiop0kaAWbuPPP/8kODiY33//nYCAAObNm0f16tWtLsu19O1rRsjvvGPaGqmpUKSIOVewZUvw97e6wixJglq4vFOnTtG7d2++/vprHnnkEb755hs+/PDDG7vaCTs4csRMp4uIgNhYszqwQAGzOdLLL5tZHOKByd9U4bLS0tKYOHEifn5+TJo0ifbt2xMbG8tHH30kIW0Ply6ZcH7lFXj8cfj8czNTY/JkczpKSgpUqCAhbQfyt1W4pN9//52qVavSrl07KlSowJ9//smYMWN4+OGHrS4ta7PZYMUKaN3ahHLLlmbvjb59Yf9+WLkSPvwQ4uPh3DmQU23sQlofwqUkJCTQrVs3pk2bRtGiRZk5cybvvfeerCrMqH37bm6EFBcHefKYG4KtWkHNmjdXC3p5QXLyzZ+bMME8PD1BTrV5YDKiFi7hypUrDB06FD8/P2bPnk2vXr3YvXs3TZo0kZB+UGfPwqRJ8PzzZsvQL7+EMmVuHgT73//CCy/8fUn3gQNmscr11pK3NwQGmlG3eGAyohZZ3tKlSwkJCWHPnj28+eabjBo1iieffNLqsrKm1FT45RdzU/DHH83o2N8fBg+G5s3NDI678fUFHx/TIvHwMD/v4yOrDDNIglpkWQcPHqRTp0788MMPlCpVigULFvDmm29aXVbWtGPHzdWCCQnwyCPQpo1pbdzvidwnTpgzCn19zSZKcqpNhklQiywnKSmJr776iiFDhuDh4cGAAQPo1KmTbJ50v/76C777zgT05s3mLME33jDh/Oab5qzBBxEVBddOtWHcOLuV684kqEWWobVm3rx5dOrUibi4OJo0acLQoUMpVqyY1aVlHVeuwMKFJpwXLjStjsqVYdQoaNoUCha0ukJxGxLUIkuIiYkhJCSEZcuWUb58eZYvX37jLEJxD1rDpk0mnGfMgFOn4LHHoEMHM3quUMHqCsU9yKwPkWnCwsJQSv3rERYWdsefOX/+PJ07d+bpp58mOjqaMWPG8Oeff0pIp8exYzBkCJQvD1WrwjffQJ06ZiQdHw/Dhtk/pMPCTD975UrzUMo87vJnLO5NTngRmS49p3LbbDamTp1K9+7dOXnyJG3atGHgwIE8+uijmVNkVnX5Mvzwgxk9//KLmX3x7LNm5PzeeyALfpyWnPAinEpKSgoxMTEcP36cQreZtrV582aCgoJYt24d1apVY/78+VStWtWCSrMIreH33004z55tDoItXhx69jQrB/38rK5QZJC0PkSmi4uL49y5c/T/x/Liv/76i08++YSAgAD279/PlClTWLdunYT0nRw6ZJZoly5tFp7MmAENG8Kvv5oFJl9+KSHtIqT1ITKNl5cXybcuL77G09OTYcOG0bt3b86fP09wcDB9+/YlX758mV+ks7twAebMMaPnlSvN1156ybQ2GjWChx6ytj7xwO7W+pARtcg0Bw4coFmzZjd2rvP29qZu3bqULFmSoKAgKleuzNatWxk5cqSE9K3S0mDZMrOnc6FCZtOjY8fgiy/MqPq330xQS0i7rHT3qJVS2YBo4KjWur7jShKuytfXFx8fH2w2G0opkpKS+OWXXyhWrBjff/89jRo1kn05brVnjxk5T51qZmnkzWvCulUrqFHj/lYLiiztfm4mdgBiAB8H1SLcQEJCAnny5OHixYt4eHhQunRpNm3aRO7cua0uzTmcOQMzZ5qAXr/e7JdRr545mfs//zG70Am3k66gVkoVBd4EBgCdHFqRcFmLFy9m165dXLhwgfz587NhwwaeeOIJq8uy3tWrsGSJCeeffjKrBytUMPOcAwNlQyOR7hH1KKAbkOdOT1BKtQXaAhQvXjzDhQnXsX//fjp27Mj8+fMpXbo05cuXJ3/+/BLSW7fe3Ajp5ElzdFW7dqa1UamStDbEDfe8maiUqg+c1FpvutvztNaTtNYBWusAWZQgwGye1Lt3b8qVK8dvv/1GnTp12Lt3Lzt27GDlypXpWpnock6cgJEjTRBXqgRjx5qN93/80dwgHDXK7L0hIS1ukZ4R9fPAf5RSbwCegI9SaprWurljSxNZldaaOXPm0LlzZ44cOUKzZs0YMmQIRe61l7GrSkmB+fPN6HnxYjOLo2pVE9Lvvw/581tdoXBy9wxqrXVPoCeAUqo20EVCWtzJzp07CQ4OZvny5VSsWJHp06fzwgsvWF1W5tMaNmww4TxzprlJWLgwdOliVguWLWt1hSILkSXkwi7Onj1LWFgYY8eOxcfHh3HjxtG2bVuyZ3ezv2Lx8WY6XUSEmV7n5QVvv236znXqyInc4oHc139FWusVwAqHVCKyJJvNRkREBD169CAxMZGPP/6YAQMGUKBAAatLyzyXLsG8eSacf/3VjKZfeAG6doXGjc1RVEJkgJsNd4Q9bdy4keDgYNavX8+zzz7LokWLqFKlitVlZQ6bDVavNuH8/fdw8SKULAl9+pjWhrvPaBF2JUEt7ltiYiI9e/ZkypQpFCxYkIiICJo3b35jabhL278fIiPN49AhyJPHbB/aqpWZveEOvwOR6eRvlUi31NRUxowZQ+nSpYmIiKBTp07ExsbSsmVL1w7pc+fgv/817YwnnzR7bJQuDdOmwfHjMHkyvPjiv0P6+ib6/3y403REYReye55IlxUrVhAcHMyOHTuoW7cuo0ePxt/f3+qyHOf6RkgREab/nJwMTz1lRs7Nm0PRoum/1rPPQkwM7N4tqwzFHcnueeKBxcfH8/777/PSSy9x4cIFoqKiWLJkieuG9M6d0K0bFCtm9tj4+WezW9369bBrF/TocX8hDRAXZ0bl/9h/W4j0khG1uK2UlBSGDx/OgAEDsNlsdO/enW7duuHt7W11afb3119m0/2ICHMIbPbs8PrrZvRcvz7kyvVg1/XyMiPxf/L0NEdmCXELGVGL+7Jw4ULKlSvH559/zmuvvUZMTAxhYWGuFdJXrpizBd9+2yxECQkx7Y6RI+HoUbM5UqNGDx7SAAcOQLNmN3vX3t5mk6WDB+3yFoT7kFkf4oZ9+/YRGhrKwoULeeqpp1i6dCl169a1uiz70Ro2bzYj5xkzzEj6sccgONiMnp9+2r6v5+tr5lDbbCask5PN59KnFvdJglpw6dIlBgwYwPDhw8mZMydDhw4lJCSEnDlzWl2afSQkmBkaERGmB50zJzRoYML5tddMq8NRTpwwI3ZfX6he3dQixH2SoHZjWmtmzZpFly5dOHr0KC1atOCrr77C19fX6tIy7vJlsyNdRAQsXWpGtTVqwIQJ0KQJPPxw5tQRFQW1a5uPx43LnNcULkeC2k1t376d4OBgVq5cSeXKlZk9ezbPPfec1WVljNawdq0J59mzzUyLYsXMTI2WLaFMGasrFOKByM1EN3PmzBlCQkKoXLky27dvZ+LEiWzcuDFrh3RcnFmE4udnVgdOn26OrVq2zKweHDDAmpC+vuBl5UrzkAUv4gHJ9Dw3YbPZmDJlCj179uT06dN8+umnfPHFFzzyyCNWl/ZgLl6EOXPM6HnFCvO12rVN37lRI7O0W4gs5G7T86T14QbWr19PUFAQ0dHR1KxZk/DwcCpVqmR1WffPZoPly004z50LSUlmSXf//uZ07hIlrK5QCIeQ1ocLO3HiBB9++CE1atTg6NGjTJs2jVWrVlkX0g+690VsLHz+uQniV14xNwkDA2HNGvO93r0lpIVr01rb/VGlShUtrHPlyhU9cuRI7ePjo3PkyKG7deumz58/b3VZN/n6ag1at2t35+ecPq31hAla16hhnuvhoXW9elrPmKF1UlLm1SpEJgGi9R0yVXrULua3334jJCSEnTt3Uq9ePUaNGkUZZ5ntcK8l1ampsGSJaW389JM5a7B8edN3btbMzEcWwkXJEnI3cPjwYRo3bkydOnVISkrixx9/ZNGiRc4T0nDnJdULF0Lnzmazo/r1TR/6k0/MvhvbtplzBiWkhRuTm4lZXHJyMsOGDWPgwIEA9O/fn65du+Lp6WlxZbdx65Jqpcwo+uefzXS6HDlMSLdqZTZEcpVVkULYgQR1FqW1Zv78+XTs2JEDBw7w7rvvMmzYMB5//HGrS7uzlBSz10auXOZjMBshhYfD+++DO52zKMR9kKDOgmJjY+nQoQM///wzZcuWZdmyZdSpU8fqsm5Pa9i48eZGSGfOmNFysWKweDGUK2d1hUI4PelRZyEXLlyge/fulC9fnrVr1zJy5Ei2bNninCEdHw+DB0PZsmYzoilToEgR870rV+DIEXOjUFbqCXFPMqLOArTWzJgxg65du3Ls2DE++OADBg8ezGOPPWZ1aX+XlGQ2IYqMNMu3tTZLur/5Bho3hrx5ra5QiCxJgtrJbd26leDgYFavXk1AQABz586lRo0aVpd1k80Gq1eb1sb335ul3SVKmEUoLVtCqVJWVyhElidB7aROnz5N7969mThxIo888gjffPMNH374ofOc9r1/vxk5T51qTix56CEzam7VypzW7Sx1CuECJKidTFpaGpMnT6ZXr16cOXOG9u3b069fPx7OrP2T7+bcOTNqjogwy7eVgjp1zF4bb78NuXNbXaEQLkmC2omsW7eOoKAgNm/ezIsvvkh4eDhP2/t4qPuVlmb6zRERMG+eWVlYpgwMHAjNm5vZG0IIh5KgdgLHjx+ne/fuREZGUqRIEWbMmEGTJk1QSllX1K5dJpynTYNjx8yJKK1bm9ZGtWpmNC2EyBQS1Ba6evUq4eHhhIWFkZKSQs+ePenVqxcPPfSQNQWdOgUzZ5qA3rgRsmUzqwRHj4a33srYidxCiAcmQW2RFi1aMG3atL99bdCgQeTMmZOwzJxXfPUqLFpkwnnBAvN5xYowYoTZl8PZpgAK4YYkqDPZoUOH6Ny5M1FRUZQqVYozZ85w+vRp2rVrx/jx4zOnCK3hzz9NOH/3Hfz1FxQsCEFBprVRsWLm1CGESBfZ5jSTXL58mSFDhjB48GA8PDy4cuUKqamp/3qep6cnly9fdkwRCQlmA6SICNixwyzl/s9/TDi/9prZGEkIYQnZ5tRCWmvmzZtH2bJlCQsLo0GDBuzevZvDhw/TrFmzG/Oivb29CQwM5ODBg/YtIDkZZs2CN94w24h27Wqm0Y0fb4L7++/NrnUS0kI4LWl9ONDu3bvp0KEDS5cupXz58ixfvpzatWvf+L6Pjw82mw0PDw+Sk5Px8fGhUKFCGX9hrWHdOjNynjXLzH8uWhS6dzerBZ96KuOvIYTINBLUDnD+/Hm++OILRo0aRe7cuRkzZgzt2rUje/a//7pPnDhB4cKF8fX1pXr16iQkJGTshePizErByEjYu9dszP/OO6a18dJLZhaHECLrudMZXRl5uOuZiTabTUdGRupChQpppZRu06aNPnHixG2f27dvXw3869G3b9/7e9ELF7T+3/+0fuklc7YgaF2rltZTpmjtTOckCiHuCjkz0fE2b95McHAwa9eupVq1aowdO5aqVas65sVsNlixwrQ25s6FS5fM5kctW0KLFlCypGNeVwjhMHe7mSitjww6deoUn3/+OZMmTeLRRx9lypQptGrVyjGbJ+3da8J56lQ4fNgca9W0qWltPP+8rBYUwkXdM02UUsWUUsuVUruUUjuVUh0yozBnl5aWxoQJE/Dz8+O///0vHTp0YM+ePbRu3Tp9IR0WZoL1n49/LnY5exa+/hqeew78/GDQIPD3N/Ofjx83ez3XrCkhLYQru1NP5PoD8AWeufZxHiAWKHu3n3H1HvXq1at1pUqVNKBfeuklvX379ge/mK+v6Su3a3fza1evar1wodbvvad1rlzm+2XLav3VV1ofPZrxNyCEcDrcpUd9z9aH1joBSLj28QWlVAxQBNjlmP/rcF7Hjh2jW7duTJ8+nWLFijF79mzefffdB9s8ycvLzHG+bsIE88iWDR591IyW8+eHjz82rY0qVWTULISbuq9GqlKqBFAZWH+b77VVSkUrpaITExPtVJ5zuHLlCkOGDKFMmTLMmTOH//u//yMmJobGjRs/+A53Bw6YvTSut0luvU6NGuZIq2PHzAndAQES0kK4sXTfTFRKPQTMBUK11uf/+X2t9SRgEphZH3ar0GJLliwhJCSE2NhY3nrrLUaOHEmpjB4vlZJiFqT8/ruZwQFmYt3zz8MPP0CBAhmuWwjhOtI1olZK5cCE9HStdZRjS3IOBw4coGHDhtSrVw+tNYsWLeKnn3568JDW2mwdGhQEhQtDo0Zmgcqtfv8dxo7NePFCCJdyzxG1Mv+2nwzEaK1HOL4kayUlJTF48GCGDBlC9uzZGTx4MKGhoeR60L2Yjx41m+9HREBMDHh6QsOGpu/8yiuQXWZICiHuLj0p8TzQAtiulNpy7Wu9tNaLHFaVBbTWzJ07l86dO9/YMGnIkCEUKVLk/i+WlGRaGBER5hgrm820NSZNMgfA5stn7/KFEC4sPbM+1gAufSdr165dhISE8Ouvv/L0008zdepUXnzxxfu7iNbmwNeICJg9Gy5cgMcfh88/NysGn3zSMcULIVyeW/+7+9y5c/Tr14/w8HDy5MnD2LFj+eSTT/61edJdHTxoNkGKjDQzOXLnNqPmVq3gxRdvzuoQQogH5JZBbbPZiIyMpEePHpw8eZKPP/6YAQMGUCC9sy3On4c5c8zoedUqM3Xu5ZfNqsK33warzjwUQrgktwvq6OhogoOD+eOPP3j22WdZuHAhVapUufcPpqXBb7+ZcI6KgsuXzZLuAQOgeXMoXtzxxQsh3JLbBHViYiK9evVi8uTJFCxYkIiICJo3b37vfTl27zbhPG0axMebG4GtWplH9eqyEEUI4XAuH9SpqalMnDiR3r17c/HiRTp27EifPn3ImzfvnX/o9GmYOdME9IYNZll3vXrmZO633jJT7IQQIpO4dFCvWrWKoKAgtm/fziuvvMKYMWPw9/e//ZOvXoWffzbhPH8+XLkCFSrA8OFmqbc9jsgSQogH4JJBHR8fT9euXZk5cybFixdnzpw5vPPOO7ffl2PLFhPO330HJ0+aDZHatTOtjUqVpLUhhLCcSwV1SkoKI0aMYMCAAaSmptKnTx+6d++Ot7f335944gRMn24Cets2cwL3W2+ZcH79dTmRWwjhVFwmqBctWkSHDh3Yt28fDRs2ZMSIEZS89Uiq5GTT0oiIMC2OtDSoVg3GjYMmTcyWokII4YSyfFDv27ePjh07smDBAsqUKcOSJUt49dVXzTe1hvXrTTjPnGlOSylSBLp2NasF79SvFkIIJ5Jlg/rSpUsMHDiQYcOGkTNnToYOHUpISAg5c+aEI0fMuYKRkbBnj9mk/+234YMPzMKUbNmsLl8IIdItywW11prZs2fTpUsX4uPjad68OV999RWF8+aFWbPM6Pm338xo+oUXzOi5cWNzEKwQQmRBWSqot2/fTkhICCtWrKBSpUrM/O47nk9LMxsfzZkDFy9CyZLQp49pbTzxhNUlCyFEhmWJoD579ix9+/Zl3Lhx5M2blwn9+vHxlStka9kSDh2CPHngvffMrI2aNWUjJCGES3HqoLbZbHz77bf07NmTU6dO8ckLL/DF5cvk79vXzG+uWxe+/NL0n/85BU8IIVyEcw09ExKgVi04fpwNGzZQo3p1PvroI/y0Jjp7dsavXEn+8+dh0CA4fBiWLIHAQAlpIYRLc6oRdUKPHry/ahXB1arR+MgRfD08mAoEpqaiPvrItDaqVpXVgkIIt+IcQZ0jB6Sm8gWwBih35AgaM8NDzZkD9evDg55ZKIQQWZxTBHUOrUm95fMJ1x7ZPTy42qiRRVUJIYRzcIoe9eEOHWgGXO80ewOBwJHQUMtqEkIIZ+EUQf11njz8BVzGnKJ7GUgEJsqRVkIIgdJa2/2iAQEBOjo6+r5+5p133sHX15e2bdsyadIkEhISiIqKsnttQgjhjJRSm7TWAbf9nrMEtRBCuLO7BbVTtD6EEELcmQS1EEI4OQlqIYRwchLUQgjh5CSohRDCyUlQCyGEk5OgFkIIJ+eQedRKqUQg7gF/vADwlx3LyQrkPbs+d3u/IO/5fj2utX70dt9wSFBnhFIq+k6Tvl2VvGfX527vF+Q925O0PoQQwslJUAshhJNzxqCeZHUBFpD37Prc7f2CvGe7cboetRBCiL9zxhG1EEKIW0hQCyGEk3OaoFZK1VNK7VFK7VNK9bC6nsyglJqilDqplNphdS2ZQSlVTCm1XCm1Sym1UynVweqaHE0p5amU2qCU2nrtPfezuqbMopTKppT6Uym1wOpaMoNS6pBSartSaotSyq4b8jtFj1oplQ2IBeoC8cBGoKnWepelhTmYUupF4CIQqbUub3U9jqaU8gV8tdablVJ5gE1AQ1f+c1ZKKSC31vqiUioHsAbooLX+w+LSHE4p1QkIAHy01vWtrsfRlFKHgACttd0X+TjLiLoasE9rfUBrfQWYCTSwuCaH01qvAk5bXUdm0VonaK03X/v4AhADFLG2KsfSxsVrn+a49rB+dORgSqmiwJvAf62uxRU4S1AXAY7c8nk8Lv4fsLtTSpUAKgPrLS7F4a61ALYAJ4FftNYu/56BUUA3wGZxHZlJA0uVUpuUUm3teWFnCWrhRpRSDwFzgVCt9Xmr63E0rXWa1roSUBSoppRy6TaXUqo+cFJrvcnqWjJZTa31M8DrQPtrrU27cJagPgoUu+Xzote+JlzMtT7tXGC61tqtjpnXWp8FlgP1LC7F0Z4H/nOtZzsTeFkpNc3akhxPa3302v+eBOZhWrp24SxBvREorZQqqZTKCbwP/GRxTcLOrt1YmwzEaK1HWF1PZlBKPaqUynftYy/MDfPdlhblYFrrnlrrolrrEpj/ln/TWje3uCyHUkrlvnaDHKVUbuBVwG6zuZwiqLXWqUAQsARzg2m21nqntVU5nlJqBrAOKKOUildKtbG6Jgd7HmiBGWFtufZ4w+qiHMwXWK6U2oYZkPyitXaL6Wpu5jFgjVJqK7ABWKi1/tleF3eK6XlCCCHuzClG1EIIIe5MgloIIZycBLUQQjg5CWohhHByEtRCCOHkJKiFEMLJSVALIYST+388TUYcEE4rnAAAAABJRU5ErkJggg==\n", + "image/png": "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\n", "text/plain": [ "
" ] @@ -132,12 +132,12 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "3b82d1c6", "metadata": {}, "outputs": [], "source": [ - "x_const = {'c':list(np.arange(0,10)),'d':list(np.arange(10,20))}\n", + "x_const = {'c':[0,1,2,3,4,5,6,7,8,9],'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", @@ -148,20 +148,22 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "7c1f7950", "metadata": {}, "outputs": [], "source": [ - "def func_const(a, x):\n", - " return a[0]\n", + "#needs to be vectorized for expected chi2 to work (jacobian matrix incorrect dim. otherwise)\n", + "#@anp.vectorize\n", + "def func_const(a,x):\n", + " return a[0]#*anp.ones(len(x))\n", "\n", "funcs_const = {\"c\": func_const,\"d\": func_const}" ] }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 10, "id": "82e0cdb6", "metadata": {}, "outputs": [ @@ -172,39 +174,18 @@ "Fit with 1 parameter\n", "Method: migrad\n", "Optimization terminated successfully.\n", - "chisquare/d.o.f.: 0.7268201670950173\n", - "fit parameters [0.99968989]\n" + "chisquare/d.o.f.: 1.444161495357013\n", + "fit parameters [0.9997047]\n" ] } ], "source": [ - "output_const = pe.combined_fits.combined_fit(x_const,y_const,funcs_const,method='migrad')" + "output_const = pe.fits.least_squares(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, + "execution_count": 11, "id": "ab5c5bef", "metadata": {}, "outputs": [], @@ -214,25 +195,25 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 12, "id": "d6abfe4f", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - " chisquare: 13.80958317480533\n", - " chisquare_by_dof: 0.7268201670950173\n", + " chisquare: 27.439068411783246\n", + " chisquare_by_dof: 1.444161495357013\n", " dof: 19\n", - " fit_function: {'c': , 'd': }\n", - " fit_parameters: [Obs[0.99969(22)]]\n", + " fit_function: {'c': , 'd': }\n", + " fit_parameters: [Obs[0.99970(22)]]\n", " iterations: 15\n", " message: 'Optimization terminated successfully.'\n", " method: 'migrad'\n", - " p_value: 0.7946762502119166" + " p_value: 0.09483431965197764" ] }, - "execution_count": 14, + "execution_count": 12, "metadata": {}, "output_type": "execute_result" } @@ -243,7 +224,31 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 13, + "id": "50e3de50", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Fit with 1 parameter\n", + "Method: migrad\n", + "Optimization terminated successfully.\n", + "chisquare/d.o.f.: 1.444161495357013\n", + "fit parameters [0.9997047]\n" + ] + } + ], + "source": [ + "output_const = pe.fits.least_squares(x_const,y_const,funcs_const,method='migrad')" + ] + }, + { + "cell_type": "code", + "execution_count": 14, "id": "efd3d4d0", "metadata": {}, "outputs": [], @@ -256,7 +261,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 15, "id": "57d65824", "metadata": {}, "outputs": [ @@ -264,7 +269,7 @@ "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" + "[Obs[1.0101(80)], Obs[0.9908(97)], Obs[0.99919(32)], Obs[0.9962(64)], Obs[0.965(17)], Obs[1.004(42)], Obs[1.0094(82)], Obs[1.004(13)], Obs[0.9974(31)], Obs[0.954(34)], Obs[1.004(16)], Obs[1.058(37)], Obs[0.9893(84)], Obs[0.9895(85)], Obs[0.9914(96)], Obs[1.00028(33)], Obs[1.0005(62)], Obs[0.957(32)], Obs[0.988(13)], Obs[1.0040(32)]]\n" ] } ], @@ -274,7 +279,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 16, "id": "731552bc", "metadata": {}, "outputs": [ @@ -285,7 +290,7 @@ "Fit with 1 parameter\n", "Method: Levenberg-Marquardt\n", "`ftol` termination condition is satisfied.\n", - "chisquare/d.o.f.: 0.7268201670947627\n" + "chisquare/d.o.f.: 1.4441614953561615\n" ] } ], @@ -295,7 +300,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 17, "id": "019583b5", "metadata": {}, "outputs": [], @@ -305,25 +310,25 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 18, "id": "f28a3478", "metadata": {}, "outputs": [ { "data": { "text/plain": [ - " chisquare: 13.809583174800492\n", - " chisquare_by_dof: 0.7268201670947627\n", + " chisquare: 27.439068411767067\n", + " chisquare_by_dof: 1.4441614953561615\n", " dof: 19\n", - " fit_function: \n", - " fit_parameters: [Obs[0.99969(22)]]\n", + " fit_function: \n", + " fit_parameters: [Obs[0.99970(22)]]\n", " iterations: 7\n", " message: '`ftol` termination condition is satisfied.'\n", " method: 'Levenberg-Marquardt'\n", - " p_value: 0.7946762502121925" + " p_value: 0.0948343196523247" ] }, - "execution_count": 19, + "execution_count": 18, "metadata": {}, "output_type": "execute_result" } @@ -334,13 +339,13 @@ }, { "cell_type": "code", - "execution_count": 20, + "execution_count": 19, "id": "466cd303", "metadata": {}, "outputs": [ { "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXoAAAD4CAYAAADiry33AAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAkcUlEQVR4nO3de3xU9Z3/8dcnCE0iRtSgBNBCXVoF5RpBu2oo66MV18Liar2ggq3LImq3trSrP1YYUHpZabtq2bRqkaW1xarR4q+4aot4WaUKGpDLWhGhhoYQocQLJIL57B9zEodcJ5mZzMzJ+/l4nEfmnO/3zPnk5OQzZ77ne77H3B0REQmvnHQHICIiqaVELyISckr0IiIhp0QvIhJySvQiIiF3RLoDaKqwsNAHDRqU7jBERLLKunXr3nX3vi2VZVyiHzRoEGvXrk13GCIiWcXMdrRWpqYbEZGQU6IXEQk5JXoRkZBTohcRCTklehGRkFOiFxEJOSV6EZGQU6IXEQk5JXoR6dYikQhm1myKRCLpDi1pLNMePFJcXOy6M1ZEutr48eMBWL16dVrj6CwzW+fuxS2V6YxeRCTklOhFREJOiV5EJOSU6EVEQk6JXkQk5JToRURCToleRCTklOhFREJOiV5EJOSU6EVEQk6JXkQk5JToRURCrt1Eb2ZLzGy3mW1spdzM7C4z22pmG8xsdEzZSWb2lJltMbPNZjYoibGLiEgc4jmjXwqc30b5RGBIMM0ASmPKlgF3uPupwFhgd+fCFBGRzjqivQru/lw7Z+KTgWUeHe94jZn1MbMi4BjgCHd/OnifD5IRsIiIdEwy2ugHAO/EzFcEyz4L7DOzMjN7zczuMLMeSdieiIh0QCovxh4BnAPMBs4APgNMb6mimc0ws7Vmtra6ujqFIYmIdD/JSPQ7gRNj5gcGyyqAcnff5u6HgMeA0c1XB3e/x92L3b24b9++SQhJREQaJCPRrwCuDnrfnAnUuHsl8ArQx8waMvcEYHMSticiIh3Q7sVYM/s1MB4oNLMKYB7QE8DdfwqsBC4AtgL7gWuCso/NbDbwBzMzYB1wbwp+BxERaUM8vW4ub6fcgetbKXsaGN650EREJBl0Z6yISMgp0YuIhJwSvYhIyCnRi4iEnBK9iEjIKdGLiIScEr2ISMgp0YuIhJwSvYhIyCnRi4iEnBK9iEjIKdGLiIScEr2ISMgp0YuIhJwSvYhIyCnRi4iEnBK9iEjIKdGLiIScEr2ISMgp0YuIhFy7id7MlpjZbjPb2Eq5mdldZrbVzDaY2egm5QVmVmFmP0lW0CJJFYmAWfMpEkl3ZNKF6urqKC8vZ9euXekOJeniOaNfCpzfRvlEYEgwzQBKm5TfBjzXmeBEukQkAu5QUhKd3KOTEn23smPHDmpqaliwYEG6Q0m6dhO9uz8H7G2jymRgmUetAfqYWRGAmY0BTgCeSkawIiLJlpeXh5lRWVkJQGlpKWZGXl5emiNLnmS00Q8A3omZrwAGmFkO8ENgdhK2ISKSEtu2beOKK64gJyeaDvPz85k6dSpvv/12miNLnlRejJ0FrHT3ivYqmtkMM1trZmurq6tTGJKIyOGKioooKCigvr6enJwcamtrKSgooF+/fukOLWmOSMJ77AROjJkfGCw7CzjHzGYBvYFeZvaBu9/c9A3c/R7gHoDi4mJPQkwiInGrqqqif//+FBUVMW7cuMZmnLBIRqJfAdxgZsuBcUCNu1cCUxsqmNl0oLilJC8ikm5lZWWMHz8egMWLF6c3mBRoN9Gb2a+B8UChmVUA84CeAO7+U2AlcAGwFdgPXJOqYEVEpOPaTfTufnk75Q5c306dpUS7aUomikRg/vzmy+fNUxdDkRBIRtONZLtIJDoFX11ZvTp9sYhI0mkIBBGRkFOiDwPdwi8ibVDTTRio6UVE2qAzehGRkFOiFxEJOSV6EZGQU6IXEQk5JXoRkZBTohcRCTklehGRkFOiFxEJOSV6EZGQU6IXEQk5JXoRkZBTohcRCTklehGRkFOiFxEJOSV6EZGQU6IXEUlAJBLBzJpNkQx68E+7id7MlpjZbjPb2Eq5mdldZrbVzDaY2ehg+Ugze8nMNgXLL0128CIi6RaJRHB3SkpKKCkpwd1x9+xK9MBS4Pw2yicCQ4JpBlAaLN8PXO3uw4L1/8PM+nQ6UhER6ZR2HyXo7s+Z2aA2qkwGlrm7A2vMrI+ZFbn7n2Le4y9mthvoC+xLMGYREemAZLTRDwDeiZmvCJY1MrOxQC/grSRsT0REOiDlF2PNrAj4BXCNu9e3UmeGma01s7XV1dWpDim86uqgvBx27Up3JCKSQZKR6HcCJ8bMDwyWYWYFwO+AOe6+prU3cPd73L3Y3Yv79u2bhJC6qR07oKYGFixIdyQikkGSkehXAFcHvW/OBGrcvdLMegGPEm2/fzgJ20mdSATMmk8ZdNW8TXl50XgrK6PzpaXR+by89MYlIhkhnu6VvwZeAj5nZhVm9jUzm2lmM4MqK4FtwFbgXmBWsPwrwLnAdDMrD6aRSf8NkiESAXcoKYlO7tEpWxL9tm1wxRWQE/w58/Nh6lR4++30xiUiGSGeXjeXt1PuwPUtLP8l8MvOhyZxKyqCggKor48m+9ra6Hy/fl2z/UgE5s9vvnzevOz5sBQJMd0ZGxZVVdC/P4waBTNndu0F2Wz/RiQJyYY7Q7u7ds/oJUuUlcH48dHXixenNRTpXiKRCJFIhPHB8bd69eq0xiPN6YxeRCTklOhFREJOiV5EJAnq6uooLy9nVwbesKhEL+mX7fcxiAA7duygpqaGBRl4w6ISvaSfeu1IFsvLy8PMqAxuWCwtLcXMyMugGxaV6GNprBgR6aBt27ZxxRVXkBPcsJifn8/UqVN5O4NuWFSij6WxYhKT7R+U2R6/pEVRUREFBQXU19eTk5NDbW0tBQUF9OuqGxbjoEQPGismWbL9gzLb45e0qaqqon///owaNYqZM2dm3AVZJXpI/1gx2X4xMts/KLM9fkm7srIyhgwZQu/evVm8eDFlZWXpDukwSvSQGWPFZPPFyHR/UCYq2+PPEJncvbAtDUM4PPvsszz77LOhHMJBib5BOseKyXbp/qBMVLbHnyEyuXthWxoe7t10ClOi11g3DTRWTGIaPiiLimDcuE+aQbJFtsefRnl5edTW1jbOl5aWUlpaSm5uLgcOHEhjZNJAZ/SSHGVlMGQI9O4d/aDMsDbKdiUQf3cfvTEbuhd2d0r0Iglq+OpfUlJCSUlJ1n31T/SDKhu6F3Z3SvSZRP24u6V0fyNIxgdVpncv7O6U6DOJ+nF3S9n+jQDS270w3R+U2UCJPhOoH7dIp4XhgzLVlOgzgfpxi0gKtZvozWyJme02s42tlJuZ3WVmW81sg5mNjimbZmZvBtO0ZAYeKurHLSIpFM8Z/VLg/DbKJwJDgmkGUApgZscC84BxwFhgnpkdk0iwGa+yMnpna2cuROmGLV2M7uYqKyspKSnRhdwUaDfRu/tzwN42qkwGlnnUGqCPmRUBXwKedve97v5X4Gna/sDIfrfdBi+80LmLqdneDz0ZdDG6W7vtttt44YUXsu7O2myQjDtjBwDvxMxXBMtaW54y63fXMH3yBc2Wj//7f2DK1V+l9sB+/nX6Zc3Kz7/4ciZecjn7rv06834wH87628PKJ195DRO+PIXdf9nJwpuua7b+K+te4YiDBz9ZUFoKpaXUmXHmuLO46sZvUXx2CW9uep2fLJjTbP1/+va/cVrxWDZOvIh7f/nzZtu/Ye5Chgw7nbUvPMsv7v5hs/W/df03Ocnhf0afxW+eWAGnDosW5OZCbi5zflzK8f0HsOrxR/ntL+9vtv780vvpc+xxPDFiLP+96slm2//B0uXk5uXz6LIlrP7dY83Wv/MHd8GO7SwvOomXytcdtv1eRx/DHcseBOC/7lzEqy8+d9i6BX2OZdUzT9Ojrq7F/fflyf/Iv935UwDunj+HrZtfP2z9gYNP5tvf/zEAd9x8ExVvv3VY+d8MPZ0b5y0E4PZ/mUn1rr8cVj5s9BnM+NdbAbj1o4957/33Dvv9R3/+XKb9y2wAvn31pXxUd/idnmdN+CKX/fMNAOzc81cARsWsH/ext3dPi+u3d+x95Z9m8bfnnc+f33qTH/6/bzUrj/vYW/syO3btZtc7f+a0MWfQs1cvII5j77s/5KSTh/A/v//vFuOP59i7eNzpHPzoo8ZlDXfWmhn/vWVH28fegysAWP6znzSLv9en8to99m772VIA7vnBbWx69ZXDyvv269+hY6/p79+hY++fp/Pevr0s/e1KRhx/dLPfM1EZcTHWzGaY2VozW1tdXd31Abz7Ljy7Gt6rgUOHYN++6BRzW3db/vNbc6g662wO9ewJwAEzfldQwN8PPS1lIR9mwAAoGQ+nnQZ9+nwy5eZ2zfYHDYpu/zMnd2r7rzy/jqrJ/0hdjx4AHMjJ4XfHFfL3o8akKGBpyd7qKmr3f8iuinfar5xEi5avYMDJQzAzAMyMIwsKOLmD/z/pin/79u1UVlZy6NAhDh06xL59+9i3bx/vvfde3OtXv1vNvn37uPM/7kxN99CWBvNpOgGDgI2tlP0MuDxm/g2gCLgc+Flr9VqbxowZ41ln3ryG8SYPn+bN69j7lJREp3RJ5/Znzozus5yc6HTddfGvmyH7v6SkxEvSuH5n5ebmOtBsys3N7dD7JBL/zJkzPScnx3Nzcz0nJ8ev68DfP1nxp9uZZ57pRx99tFdWVnZqfWCtt5JXk3FGvwK4Ouh9cyZQ4+6VwJPAF83smOAi7BeDZcmX7vHcIxGYMgVmzYpeTJw1KzqvfrzxS+RidMMwz2eeCUcfHb0onk3DPKdZJoxVU1VVxcyZM1mzZk2H76zNhPiTIZWjf7bbRm9mvwbGA4VmVkG0J01PAHf/KbASuADYCuwHrgnK9prZbUBDw9cCd2/rom7nRSLRqWH0ydWrU7KZNsVePNXolx2XjNFDYy/m/ud/Ji20sMuEsWpi76Rd3MG/fybEn4iuGP0znl43l7t7kbv3dPeB7v5zd/9pkOQJvjVc7+4nu/vp7r42Zt0l7v43wdT8SoxENXwjefbZ6JRtT5hKN91ZnLBExqrJhAd3ZPNYO13xjUTj0WeChm8k0jnbtsHs2bB8efSms/z8aNPZokXpjixrlJWVMT74RtXRM+pIJJL24QYSiT/duuIbSUb0uhFJSDe/s1iDemW/VH8j0Rm9hEM3fkJUwxl1wxnt6nRco5KEpPobiRK9hIMeBSnSKjXdiIikUVdczA5XotegWN1ThvRaqquro7y8PKt6fEj6NYyn33RSom+NBsXqnhpumGo6dXGiT+UNL9K6TOjemenCkejVj1rSKC8vDzOjMjj+GgbkytPx1yW64ow424Uj0esJTZJGYbkFX8IrHIm+m/ejThpd4+iUbL8FX00f4Ree7pXduB910mismE5ruOGlqKiIcePGNTbjZINMuLNVUis8iV79qDsvL+/wsfeDB3+QmwtJGlQp7DLhFvy6ujq2bNnCrl27subbhHSNcDTdSGJ0jSMU1OtHWqNEL+m/xpEh/eCzlXr9SHuU6CUqkQd/JCpD+sFnK/X6kfaEp41eEqNrHFkr23v9SOop0YuEQDb3+pHUU6IXCYFM6PUjmUtt9CIiIReORK9eGyIirYor0ZvZ+Wb2hpltNbObWyj/tJn9wcw2mNlqMxsYU/bvZrbJzLaY2V1mZsn8BQD12hARaUO7id7MegCLgYnAUOByMxvapNoiYJm7DwcWAN8L1v088LfAcOA04AygJGnRi4hIu+I5ox8LbHX3be7+EbAcmNykzlBgVfD6mZhyB3KBXsCngJ5AVaJBi4hI/OJJ9AOAd2LmK4JlsdYDFwWvpwBHmdlx7v4S0cRfGUxPuvuWxEIWEZGOSNbF2NlAiZm9RrRpZifwsZn9DXAqMJDoh8MEMzun6cpmNsPM1prZ2urq6iSFJCIiEF+i3wmcGDM/MFjWyN3/4u4XufsoYE6wbB/Rs/s17v6Bu38APAGc1XQD7n6Puxe7e3Hfvn0795uIiEiL4kn0rwBDzGywmfUCLgNWxFYws0Iza3ivW4Alwes/Ez3TP8LMehI921fTjYhIF2o30bv7IeAG4EmiSfo37r7JzBaY2aSg2njgDTP7E3ACsDBY/jDwFvA60Xb89e7+eHJ/BZHuTU+IkvbENQSCu68EVjZZNjfm9cNEk3rT9T4G/jnBGEWkDXpClLQnHHfGiohIqzSomUgkAvPnfzLfcPP2vHm6u7qJgwcPUlFRQW3soyelS+Xm5jJw4EB69uwZ9zpK9CKRiBJ6nCoqKjjqqKMYNGgQqRjNRNrm7uzZs4eKigoGDx4c93pquhFJUHe6GFpbW8txxx2nJJ8mZsZxxx3X4W9UOqMXSVB3uxiqJJ9endn/OqMXyRB1dXWUl5ezqyuf1yvdghK9SIbYsWMHNTU1LFiwIN2hdDu9e/ducfncuXP5/e9/n5RtjB8/nrVr1zZb/vzzzzNs2DBGjhzJzp07ufjiiwEoLy9n5cqVzep3hhK9SJrl5eVhZo3PeS0tLcXMyMvLS3NksmDBAs4777yUbuOBBx7glltuoby8nAEDBvDww9FbkpKZ6NVGL5Jm27ZtY/bs2Sxfvpz6+nry8/OZMmUKixYtSndobVq/u4aa2kNJfc+jc49gxPFHt1ln2bJlLFq0CDNj+PDh/OIXv2D79u189atf5d1336Vv377cf//9nHTSSUyfPp28vDxee+01du/ezZIlS1i2bBkvvfQS48aNY+nSpY3ve9NNN/HUU0/Rr18/li9fTt++fZk+fToXXnghF198MYMGDWLatGk8/vjjHDx4kIceeohTTjmFDz/8kBtvvJGNGzdy8OBBIpEIkydP5sCBA1xzzTWsX7+eU045hQMHDjT7Xe677z5+85vf8OSTT/LEE0+wcOFCLrzwQl599VXmzp3LgQMHeOGFF7jlllu49NJLO71fdUYvkmZFRUUUFBRQX19PTk4OtbW1FBQU0K9fv3SHlnE2bdrE7bffzqpVq1i/fj133nknADfeeCPTpk1jw4YNTJ06la9//euN6/z1r3/lpZde4sc//jGTJk3ipptuYtOmTbz++uuUl5cD8OGHH1JcXMymTZsoKSlhfux9FTEKCwt59dVXue666xo/iBcuXMiECRN4+eWXeeaZZ/j2t7/Nhx9+SGlpKfn5+WzZsoX58+ezbt26Zu937bXXMmnSJO644w4eeOCBxuW9evViwYIFXHrppZSXlyeU5EFn9CIZoaqqiv79+1NUVMS4ceMam3EyWXtn3qmwatUqLrnkEgoLCwE49thjAXjppZcoKysD4KqrruI73/lO4zpf/vKXMTNOP/10TjjhBE4//XQAhg0bxvbt2xk5ciQ5OTmNyfTKK6/koosuoiUNy8eMGdO4vaeeeooVK1Y0Jv7a2lr+/Oc/89xzzzV+4AwfPpzhw4cndV90hBK9SAYoKytj/PjxACxevDi9wYTMpz71KQBycnIaXzfMHzrUctNTa10YG9bv0aNH47ruziOPPMLnPve5ZIadVGq6EZGsMWHCBB566CH27NkDwN69ewH4/Oc/z/Lly4Hoxc1zzmn2fKM21dfXN14E/dWvfsXZZ58d97pf+tKXuPvuu3F3AF577TUAzj33XH71q18BsHHjRjZs2NChmI466ijef//9Dq3TGiV6Eckaw4YNY86cOZSUlDBixAi++c1vAnD33Xdz//33N16cbWi7j9eRRx7Jyy+/zGmnncaqVauYO3du+ysFbr31Vg4ePMjw4cMZNmwYt956KwDXXXcdH3zwAaeeeipz585lzJgxHYrpC1/4Aps3b2bkyJE8+OCDHVq3KWv4FMoUxcXF3lJfU+kCQdMBq1enM4puq6HpZnUG7/8tW7Zw6qmnpjuMbq+lv4OZrXP34pbq64xeRCTklOhFREJOiV5EJOSU6EVEQk6JXkQk5OJK9GZ2vpm9YWZbzezmFso/bWZ/MLMNZrbazAbGlJ1kZk+Z2RYz22xmg5IYv4iItKPdRG9mPYDFwERgKHC5mQ1tUm0RsMzdhwMLgO/FlC0D7nD3U4GxwO5kBC4ikix1dXWcd955jX3Wr732WjZv3gzAd7/73TRHl7h4zujHAlvdfZu7fwQsByY3qTMUWBW8fqahPPhAOMLdnwZw9w/cfX9SIhcRSZKGu1kbBhC77777GDo0ej4bhkQfz1g3A4B3YuYrgHFN6qwHLgLuBKYAR5nZccBngX1mVgYMBn4P3OzuH8eubGYzgBkAJ510Uid+DRFJh4abvGJ95StfYdasWezfv58LLrigWfn06dOZPn067777buNDNhrEc7PYj370I5YsWQJER3/8xje+wfbt25k4cSJnn302L774IgMGDOC3v/0teXl5vPXWW1x//fVUV1eTn5/PvffeyymnnNL4frt37+bKK6+kurqakSNH8sgjj/C1r32NRYsW8fDDD3PgwAFGjhzJsGHDDhthMpsk62LsbKDEzF4DSoCdwMdEP0jOCcrPAD4DTG+6srvf4+7F7l7ct2/fJIUkImGzbt067r//fv74xz+yZs0a7r333saz8TfffJPrr7+eTZs20adPHx555BEAZsyYwd133826detYtGgRs2bNOuw9jz/+eO677z7OOeccysvLOfnkkxvLvv/975OXl0d5eXnWJnmI74x+J3BizPzAYFkjd/8L0TN6zKw38I/uvs/MKoByd98WlD0GnAn8PPHQRSTd2joDz8/Pb7O8sLCww8M9vPDCC0yZMoUjjzwSiA4b/PzzzzNp0iQGDx7MyJEjgegwwtu3b+eDDz7gxRdf5JJLLml8j7q6ug5tMwziSfSvAEPMbDDRBH8ZcEVsBTMrBPa6ez1wC7AkZt0+ZtbX3auBCYAGshGRpIsdgrhHjx4cOHCA+vp6+vTp0/iAke6q3aYbdz8E3AA8CWwBfuPum8xsgZlNCqqNB94wsz8BJwALg3U/Jtps8wczex0w4N6k/xYi0i2cc845PPbYY+zfv58PP/yQRx99tM0hiQsKChg8eDAPPfQQEB07fv369R3aZs+ePTl48GBCcadbXA8ecfeVwMomy+bGvH4YeLiVdZ8G0vdoFREJjdGjRzN9+nTGjh0LRC/Gjho1iu3bt7e6zgMPPMB1113H7bffzsGDB7nssssYMWJE3NucMWMGw4cPZ/To0VnbTq9hiuUTGqY4rTRMscRLwxSLiMhhlOhFREJOiV5EJOSU6EVEQk6JXkRSIxIBs+ZTJJLuyLqduLpXioh0WCQSndSbK+10Ri8iqVVXB+XlsGtXuiPptpToRSS1duyAmhpYsCDdkXRbSvQikhp5edE2+crK6HxpaXQ+Ly+ht122bBnDhw9nxIgRXHXVVUkINPzURi8iqbFtG8yeDcuXQ3095OfDlCmwaFGn33LTpk3cfvvtvPjiixQWFrJ3794kBhxeOqMXkdQoKoKCgmiSz8mB2trofL9+nX7LVatWcckll1BYWAjAsccem6xoQ01n9CKSOlVV0L9/NOmPG/dJM450KZ3Ri0jqlJXBkCHQuzcsXhydT8CECRN46KGH2LNnD4CabuKkM3oRyRrDhg1jzpw5lJSU0KNHD0aNGsXSpUvTHVbGU6IXkdSIRGD+/E/mzaI/581L6O7YadOmMW3atIRC626U6EUkNRrujJW0Uxu9iEjIKdGLiIScEr2ISMjFlejN7Hwze8PMtprZzS2Uf9rM/mBmG8xstZkNbFJeYGYVZvaTZAUuIpktEolgZs2miNrtu1y7id7MegCLgYnAUOByMxvapNoiYJm7DwcWAN9rUn4b8Fzi4YpItohEIrg7JSUllJSU4O64uxJ9GsRzRj8W2Oru29z9I2A5MLlJnaHAquD1M7HlZjYGOAF4KvFwRSTb1NXVUV5ezq4UDFMciURYlMDYOd1FPIl+APBOzHxFsCzWeuCi4PUU4CgzO87McoAfArPb2oCZzTCztWa2trq6Or7IRSQr7Nixg5qaGhZomOK0SdbF2NlAiZm9BpQAO4GPgVnASnevaGtld7/H3Yvdvbhv375JCklE0ikvLw8zozIY36a0tBQzIy/BYYoXLlzIZz/7Wc4++2zeeOONZIQaevEk+p3AiTHzA4Nljdz9L+5+kbuPAuYEy/YBZwE3mNl2ou34V5vZ95MQtyRTw7M9n302OunZnpIE27Zt44orriAnJ5pm8vPzmTp1Km+//Xan33PdunUsX76c8vJyVq5cySuvvJKscEMtnjtjXwGGmNlgogn+MuCK2ApmVgjsdfd64BZgCYC7T42pMx0odvdmvXYkzXQHo6RAUVERBQUF1NfXk5OTQ21tLQUFBfRLYJji559/nilTppCfnw/ApEmTkhVuqLWb6N39kJndADwJ9ACWuPsmM1sArHX3FcB44Htm5kR711yfwphFJEtUVVXRv39/ioqKGDduXGMzjnStuNro3X2lu3/W3U9294XBsrlBksfdH3b3IUGda929roX3WOruNyQ3fBHJZGVlZQwZMoTevXuzePFiyhIcpvjcc8/lscce48CBA7z//vs8/vjjSYo03DSomYhkjdGjR3PppZcyYsQIjj/+eM4444x0h5QdGm5iyJRpzJgxLtKdzJs3z4Fm07x589IdWjObN2+Ou242/V7ZpqW/A9Gm9BbzqkXLM0dxcbGvXbs23WGISAu2bNnCqaeemu4wur2W/g5mts7di1uqr0HNRERCToleRDok01oBupvO7H8lehGJW25uLnv27FGyTxN3Z8+ePeTm5nZoPfW6EZG4DRw4kIqKCjQmVfrk5uYycODA9ivGUKIXkbj17NmTwYMHpzsM6SA13YiIhJwSvYhIyCnRi4iEXMbdMGVm1cCOBN6iEHg3SeGkguJLjOJLjOJLTCbH92l3b/GBHhmX6BNlZmtbuzssEyi+xCi+xCi+xGR6fK1R042ISMgp0YuIhFwYE/096Q6gHYovMYovMYovMZkeX4tC10YvIiKHC+MZvYiIxFCiFxEJuaxM9GZ2vpm9YWZbzezmFso/ZWYPBuV/NLNBXRjbiWb2jJltNrNNZvYvLdQZb2Y1ZlYeTHO7Kr6YGLab2evB9ps96cWi7gr24QYzG92FsX0uZt+Um9l7ZvaNJnW6dB+a2RIz221mG2OWHWtmT5vZm8HPY1pZd1pQ500zm9aF8d1hZv8b/P0eNbM+razb5rGQwvgiZrYz5m94QSvrtvn/nsL4HoyJbbuZlbeybsr3X8Jae/RUpk5AD+At4DNAL2A9MLRJnVnAT4PXlwEPdmF8RcDo4PVRwJ9aiG888P/TvB+3A4VtlF8APAEYcCbwxzT+vXcRvRkkbfsQOBcYDWyMWfbvwM3B65uBH7Sw3rHAtuDnMcHrY7oovi8CRwSvf9BSfPEcCymMLwLMjuPv3+b/e6ria1L+Q2BuuvZfolM2ntGPBba6+zZ3/whYDkxuUmcy8F/B64eBvzMz64rg3L3S3V8NXr8PbAEGdMW2k2wysMyj1gB9zKwoDXH8HfCWuydyt3TC3P05YG+TxbHH2X8B/9DCql8Cnnb3ve7+V+Bp4PyuiM/dn3L3Q8HsGqBjY9smUSv7Lx7x/L8nrK34gtzxFeDXyd5uV8nGRD8AeCdmvoLmibSxTnCg1wDHdUl0MYImo1HAH1soPsvM1pvZE2Y2rGsjA6IPan7KzNaZ2YwWyuPZz13hMlr/B0v3PjzB3SuD17uAE1qokyn78atEv6G1pL1jIZVuCJqWlrTS9JUJ++8coMrd32ylPJ37Ly7ZmOizgpn1Bh4BvuHu7zUpfpVoU8QI4G7gsS4OD+Bsdx8NTASuN7Nz0xBDm8ysFzAJeKiF4kzYh408+h0+I/sqm9kc4BDwQCtV0nUslAInAyOBSqLNI5nocto+m8/4/6VsTPQ7gRNj5gcGy1qsY2ZHAEcDe7okuug2exJN8g+4e1nTcnd/z90/CF6vBHqaWWFXxRdsd2fwczfwKNGvyLHi2c+pNhF41d2rmhZkwj4Eqhqas4Kfu1uok9b9aGbTgQuBqcGHUTNxHAsp4e5V7v6xu9cD97ay3XTvvyOAi4AHW6uTrv3XEdmY6F8BhpjZ4OCM7zJgRZM6K4CG3g0XA6taO8iTLWjP+zmwxd1/1Eqdfg3XDMxsLNG/Q1d+EB1pZkc1vCZ60W5jk2orgKuD3jdnAjUxzRRdpdUzqXTvw0DscTYN+G0LdZ4EvmhmxwRNE18MlqWcmZ0PfAeY5O77W6kTz7GQqvhir/lMaWW78fy/p9J5wP+6e0VLhencfx2S7qvBnZmI9gj5E9Gr8XOCZQuIHtAAuUS/7m8FXgY+04WxnU30K/wGoDyYLgBmAjODOjcAm4j2IFgDfL6L999ngm2vD+Jo2IexMRqwONjHrwPFXRzjkUQT99Exy9K2D4l+4FQCB4m2E3+N6HWfPwBvAr8Hjg3qFgP3xaz71eBY3Apc04XxbSXavt1wHDb0ROsPrGzrWOii+H4RHFsbiCbvoqbxBfPN/t+7Ir5g+dKGYy6mbpfvv0QnDYEgIhJy2dh0IyIiHaBELyISckr0IiIhp0QvIhJySvQiIiGnRC8iEnJK9CIiIfd/BJeklr6HMykAAAAASUVORK5CYII=\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAD5CAYAAAAuneICAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAnYklEQVR4nO3de3wU9b3/8deHmwliRAxqAFtoD20xCAEj2FYNRY9FT4vCzwsXFbSWg1bb09b+1HKElUqPPcfWWqVRRERaK96i0pYebYtEqUoJNqKAF4ygoREoKl64iOVz/pgJLrluspvdTeb9fDzmkZ3vd76zn53d7Gdnvt+ZMXdHRESip1OmAxARkcxQAhARiSglABGRiFICEBGJKCUAEZGIUgIQEYmoLs0tYGYLgK8BW919cAP1XwDuAoYDM9z9xri6McDNQGdgvrvfEJYPABYDhwOrgQvc/aPmYsnPz/f+/fsn8LJERKTW6tWr/+HuveuWW3PnAZjZycAHwKJGEsARwKeBs4B3ahOAmXUGXgH+FagGVgET3X2dmd0PlLn7YjO7DXje3UubexHFxcVeUVHR3GIiIhLHzFa7e3Hd8mYPAbn7k8DbTdRvdfdVwN46VSOADe5eFf66XwycaWYGjAYeDJe7myB5iIhIGrVlH0Bf4M24+eqw7HDgXXf/uE65iIikUdZ3ApvZNDOrMLOKbdu2ZTocEZEOo9lO4CRsBo6Om+8Xlm0HeppZl3AvoLa8Qe4+D5gHQR9A24UrIq21d+9eqqur2b17d6ZDibScnBz69etH165dE1q+LRPAKmBgOOJnMzABmOTubmZPAGcT9AtMAR5twzhEpI1VV1dzyCGH0L9/f4JuPkk3d2f79u1UV1czYMCAhNokMgz0XmAUkG9m1cAsoGv4hLeZ2VFABZAH7DOz/wCOcff3zOxy4DGCYaAL3H1tuNqrgMVmdj3wN+DOxF+miGSb3bt368s/w8yMww8/nJYcKm82Abj7xGbq3yI4jNNQ3VJgaQPlVQSjhESkg9CXf+a19D3I+k5gERFpG0oAIlkqFothZvWmWCyW6dAio0ePHg2Wz5w5kz/96U8peY5Ro0bR0AmuTz31FIWFhRQVFbF582bOPvtsACorK1m6tN6BlVZRAhDJUrFYDHenpKSEkpIS3B13VwLIArNnz+bUU09t0+e45557uOaaa6isrKRv3748+GBw7qwSgIhIHYsWLWLIkCEMHTqUCy64AICNGzcyevRohgwZwimnnMIbb7wBwNSpU7n00ks54YQT+MxnPsPy5cu5+OKLGTRoEFOnTj1gvd/97ncpLCzklFNO2d/BOnXq1P1fyP3792fWrFkMHz6cY489lpdeegmADz/8kIsvvpgRI0YwbNgwHn00GOy4a9cuJkyYwKBBgxg3bhy7du2q91rmz5/P/fffz7XXXsvkyZPZuHEjgwcP5qOPPmLmzJncd999FBUVcd999yW1zdpyGKiIRNDzW3ewY/fHzS/YAofmdGHoEYc2Wr927Vquv/56nn76afLz83n77eDqNVdccQVTpkxhypQpLFiwgG9/+9s88sgjALzzzjs888wzLFmyhLFjx/KXv/yF+fPnc/zxx1NZWUlRUREffvghxcXF3HTTTcyePZvrrruOW2+9td7z5+fn89xzz/HLX/6SG2+8kfnz5zNnzhxGjx7NggULePfddxkxYgSnnnoqt99+O927d2f9+vWsWbOG4cOH11vfJZdcwooVK/ja177G2WefzcaNGwHo1q0bs2fPpqKiosE4Wkp7ACLS7i1btoxzzjmH/Px8AHr16gXAM888w6RJkwC44IILWLFixf42X//61zEzjj32WI488kiOPfZYOnXqRGFh4f4v3E6dOnHeeecBcP755x/QPt748eMBOO644/a3ffzxx7nhhhsoKipi1KhR7N69mzfeeIMnn3yS888/H4AhQ4YwZMiQ1G6MFtAegIikVFO/1LPJQQcdBARf8rWPa+c//rjhPZjGhlnWtu/cufP+tu7OQw89xOc///lUhp1S2gMQkXZv9OjRPPDAA2zfvh1g/yGgL33pSyxevBgIOlVPOumkFq133759+4/1/+Y3v+HEE09MuO1Xv/pVbrnlFmovuf+3v/0NgJNPPpnf/OY3ALz44ousWbOmRTEdcsghvP/++y1q0xglABFp9woLC5kxYwYlJSUMHTqU733vewDccsst3HXXXQwZMoRf/epX3HzzzS1a78EHH8xf//pXBg8ezLJly5g5c2bCba+99lr27t3LkCFDKCws5NprrwXg0ksv5YMPPmDQoEHMnDmT4447rkUxfeUrX2HdunUp6QRu9oYw2UQ3hJEoGjVqFADLly/PaBxNWb9+PYMGDcp0GELD70WrbwgjIiIdkxKAiEhEKQGIiESUEoCISEQpAYiIRJQSgIhIRCkBiIg0Ys+ePZx66qn7x9xfcsklrFu3DoAf//jHGY4ueboUhIhII2rP3q2srATYf10gCBLAD3/4w0yElTLaAxCRDuFnP/sZgwcPZvDgwfz85z8HgstBDxo0iG9+85sUFhZy2mmn7b/88muvvcaYMWM47rjjOOmkk/ZfxrnW1q1bOf/881m1ahVFRUW89tpr+2/ecvXVV7Nr1y6KioqYPHlyul9qymgPQERSrvbs5Xjnnnsul112GTt37uSMM86oVz916lSmTp3KP/7xj/13v6rV3FnQq1ev5q677mLlypW4OyNHjqSkpITDDjuMV199lXvvvZc77riDc889l4ceeojzzz+fadOmcdtttzFw4EBWrlzJZZddxrJly/av84gjjmD+/PnceOON/O53vzvg+W644QZuvfXW/XsG7VWzCcDMFgBfA7a6++AG6g24GTgD2AlMdffnzOwrwE1xi34BmODuj5jZQqAE2BHWTXX3ymReiIhE14oVKxg3bhwHH3wwEFye+amnnmLs2LEMGDCAoqIi4JPLNX/wwQc8/fTTnHPOOfvXsWfPnkyEnlGJ7AEsBG4FFjVSfzowMJxGAqXASHd/AigCMLNewAbg8bh2P3D3B1sVtYhktaZ+sXfv3r3J+vz8/JRe9yj+Us+dO3dm165d7Nu3j549e7b7X/DJarYPwN2fBN5uYpEzgUUeeBboaWYFdZY5G/iDu+9sfagiIg076aSTeOSRR9i5cycffvghDz/8cJOXfs7Ly2PAgAE88MADQHDt/ueff75Fz9m1a1f27t2bVNyZlopO4L7Am3Hz1WFZvAnAvXXK5pjZGjO7ycwOohFmNs3MKsysovZ+nCIi8YYPH87UqVMZMWIEI0eO5JJLLmHYsGFNtrnnnnu48847GTp0KIWFhfvv2ZuoadOmMWTIkHbdCZzQ5aDNrD/wu0b6AH4H3ODuK8L5PwNXuXtFOF8ArAH6uPveuLK3gG7APOA1d5/dXBy6HLREkS4HLS2R7stBbwaOjpvvF5bVOhd4uPbLH8Dda8JDRnuAu4ARKYhDRERaIBUJYAlwoQVOAHa4e01c/UTqHP6p7SMIRxCdBbyYgjhERKQFEhkGei8wCsg3s2pgFtAVwN1vA5YSDAHdQDAM9KK4tv0J9g7K66z2HjPrDRhQCUxP7mWIiEhLNZsA3H1iM/UOfKuRuo3U7xDG3UcnGJ+IiLQRXQpCRCSilABEJL1iMTCrP8VimY4scnQtIBFJr1gsmGqvF5TFw1s7Ou0BiEhm7NkDlZXw1luZjiSylABEJDM2bYIdO2B2s+eAJmTRokUMGTKEoUOHcsEFF6RknR2dDgGJSHrl5sLu3Z/Ml5YGU04OhNfqb6m1a9dy/fXX8/TTT5Ofn8/bbzd1+TKppT0AEUmvqiqYNAk6hV8/3bvD5Mnw+uutXuWyZcs455xzyM/PB6BXr16piLTDUwIQkfQqKIC8PNi3L0gCu3cH80cdlenIIkcJQETSb8sW6NMHhg2D6dOT7ggePXo0DzzwANu3bwfQIaAEqQ9ARNKvrOyTYaBz5ya9usLCQmbMmEFJSQmdO3dm2LBhLFy4MOn1dnRKACLSIUyZMoUpU6ZkOox2RYeARLLcnj17qKys5K2OMl6+9kzg8vJg0pnAGaMEIJLlNm3axI4dO5idovHyGReLgXv9SQkg7ZQARLJUbm4uZkZNTXB7jdLSUsyM3NzcDEcmHYUSgEiWqqqqYtKkSXQKx8t3796dyZMn83oS4+VF4ikBiGSpgoIC8vLy2LdvH506dWL37t3k5eVxlMbLS4ooAYhksS1bttCnTx+GDRvG9OnTO0RHcCwWw8zqTTH1AaSdBTf0ah+Ki4u9oqIi02GIpNWocLz88iy+bPL69esZNGhQi9q05euKxWL06NGDK6+8MuXrznYNvRdmttrdi+suqz0AEcmIDje8tR1qNgGY2QIz22pmLzZSb2b2CzPbYGZrzGx4XN0/zawynJbElQ8ws5Vhm/vMrFtqXo6ItBepHt46Z84cPve5z3HiiSfy8ssvp2SdHV0iewALgTFN1J8ODAynaUBpXN0udy8Kp7Fx5T8BbnL3fwHeAb7RoqhFpN1qi+Gtq1evZvHixVRWVrJ06VJWrVqVqnA7tGYTgLs/CTR1ZaUzgUUeeBboaWYFjS1sZgaMBh4Mi+4Gzko4YhFp19pieOtTTz3FuHHj6N69O3l5eYwdO7b5RpKSPoC+wJtx89VhGUCOmVWY2bNmdlZYdjjwrrt/3MDyItLBaXhr9mjrTuBPhz3Pk4Cfm9lnW7oCM5sWJpGKbdu2pT5CEUm7VA9vPfnkk3nkkUfYtWsX77//Pr/97W9TFGnHloqrgW4Gjo6b7xeW4e61f6vMbDkwDHiI4DBRl3AvYP/yDXH3ecA8CIaBpiBeEcmwsrKy/cNA56bgctDDhw/nvPPOY+jQoRxxxBEcf/zxSa8zClKRAJYAl5vZYmAksMPda8zsMGCnu+8xs3zgy8B/u7ub2RPA2cBiYArwaAriEJEImzFjBjNmzMh0GO1KIsNA7wWeAT5vZtVm9g0zm25m08NFlgJVwAbgDuCysHwQUGFmzwNPADe4+7qw7irge2a2gaBP4M6UvSIRyWq1ZwKXl5dTXl6uM4EzqNk9AHef2Ey9A99qoPxp4NhG2lQBIxKMUTIlFoPrrqtfPmuWLt0rrRaLxfRlnyV0JrA0rva67SUlwaTrtksT2tNlZTqqlr4HSgAikrScnBy2b9+uJJBB7s727dvJyclJuI3uCSwiSevXrx/V1dVoqHZm5eTk0K9fv4SXVwIQkaR17dqVAQMGZDoMaSEdAhIRiSglABGRiFICEBGJKCUAEZGIUgIQEYkoJQARkYhSAhARiSglABGRiFICEBGJKCUAEZGIUgIQEYkoJQARkYhSAhARiSglABGRiFICEBGJqERuCr/AzLaa2YuN1JuZ/cLMNpjZGjMbHpYXmdkzZrY2LD8vrs1CM3vdzCrDqShlr0hERBKSyB7AQmBME/WnAwPDaRpQGpbvBC5098Kw/c/NrGdcux+4e1E4VbYwbhERSVKzdwRz9yfNrH8Ti5wJLPLgZqDPmllPMytw91fi1vF3M9sK9AbeTTJmERFJgVT0AfQF3oybrw7L9jOzEUA34LW44jnhoaGbzOygFMQhIiIt0OadwGZWAPwKuMjd94XF1wBfAI4HegFXNdF+mplVmFmFbjgtIpI6qUgAm4Gj4+b7hWWYWR7we2CGuz9bu4C713hgD3AXMKKxlbv7PHcvdvfi3r17pyBcERGB1CSAJcCF4WigE4Ad7l5jZt2Ahwn6Bx6MbxDuFWBmBpwFNDjCSERE2k6zncBmdi8wCsg3s2pgFtAVwN1vA5YCZwAbCEb+XBQ2PRc4GTjczKaGZVPDET/3mFlvwIBKYHpKXo2IiCQskVFAE5upd+BbDZT/Gvh1I21GJxqgSFTFYjGuu+66/fPBDjPMmjWLWCyWoaikI9GZwCJtJBaLYWb1pkS/vGOxGO5eb9KXv6SKBT/g24fi4mKvqKjIdBjRM2pU8Hf58kxG0W6NCrffcm0/yRAzW+3uxXXLtQcgIhJRHTsBxGJgVn/SLnR6aPuLZLWOnwDcoaQkmNyDSV9A6ZHs9lcCEWlTzY4CEsmYWCyY1Ach0iY69h6AiIg0SglARCSilABERCIqGglgzx6orIS33sp0JCIiWSMaCWDTJtixA2bPznQk0aQELJKVOnYCyM0Nhg3W1ATzpaXBfG5uZuOKGiVgkazUsRNAVRVMmgSdwpfZvTtMngyvv57ZuKJCCVgkq3XsBFBQAHl5sG9fkAR27w7mjzoq05FFgxKwSFbr+CeCbdkCffoEyWDkyE9+jUrbUwIWyWodew8AoKwMBg6EHj1g7txgXtKnNgEPGwbTp0eyI3jPnj1UVlbyVgRfu7RespcTT0THTwCSvGRG8aQiAbfzUUSbNm1ix44dzFYneFql4wu0LdXeD+KEE07g0EMPpaamJuX3g1ACkOZlehRPpp+/lXJzczEzasLDjqWlpZgZueoET4vaL9CSkhJKSkra7Q112vIHhBKANC7To3gy/fxJqqqqYtKkSXQKO8G7d+/O5MmTeV2d4JKAdPyAUAKQxmV6FE+mnz9JBQUF5OXlsW/fPjp16sTu3bvJy8vjKHWCSwLS8QMioQRgZgvMbKuZvdhIvZnZL8xsg5mtMbPhcXVTzOzVcJoSV36cmb0QtvmF1d7xWj6R6evhZ3oUT6afPwW2bNlCnz59GDZsGNOnT1dHcCvU1NRQUlLS6m3XXjvh0/EDItE9gIXAmCbqTwcGhtM0oBTAzHoBs4CRwAhglpkdFrYpBb4Z166p9bdO7RdoeXkwZeqGIjU1wQ1RWvoBzIYb2mR6FE+mnz9JZWVlDBw4kB49ejB37lzKNAqtxX70ox+xYsWKVh8Db8+d8G3+A6K2Y6S5CegPvNhI3e3AxLj5l4ECYCJwe93lwrqX4soPWK6x6bjjjvN26dJL3Tt1Cv62RklJMGVKa59/1qzalHXgNGtWep4/S5SUlHhJO46/tWbNmuVAvWlWgu9/Tk5Og+1zcnLS0j7Tkt1+8YAKb+A7NVUngvUF3oybrw7LmiqvbqC8HjObRrBXwac+9alWBff81h1MPfOMeuWj/u0sxl14Mbt37eSqqRPq1Y8Z9a+cfsyxvPv+e8y65cZPKnJyICeHM8+/iNFfH8fWv29mzncvrdd+1epVdNm795OC0lIoLWWPGSeM/CIXXPF9ik8s4dW1L3Dr7Bn12n/zB//J4OIRvHj6eO749Z3wxS8fUH/5zDkMLDyWihXl/OqWn9Zr//0f/5RPfXYgf/nT/3L/Hb+sVz/jplKO6NOXZb99mEd/fVe9+utK76Jnr8P5w9AR/O+yx+o9/08WLiYntzsPL1rA8t8/Uq/9zfctgYuvYPHtt/LMssc/qXjsj3RbvoL/WXQfAHfffCPPPf3kAW3zevbiR7cvBGBefgFrX153wPP3PqoP/3nzbQDcct0MNqx74YD2/QZ8lh9M/w5s2sj80pv5duVqru7bj+1dukBODv8y/HiumDUHgOu/M51tb/39gPaFw49n2lXXAnDtv0/lvXffPqB++JdOZsp3rgTgBxeex0d7dh1Q/8XRpzHh3y8HYPP2dwAYFhd/s5+9sydy+jkTefft7cy69KJ69c199s795mV8+dQxvPHaq/z0h9+vV5/wZ6/ir9zxP9fXq0/kszf64is46FMDWXDTfwPQo0cPAB597I8MHjO+2c/eD0vv5vbr/5O/V23A3TEzuh9yCL0L+vL4y282+9n7zZMVXHXRBDasWxu079SJQw/rRf/PfYEn39gOJPDZ+8mPWPvcqgPqE/rs3XATGzdu5BezrmZ7nc/W5wYPZcZPbwGa/uyNvvgKnli5mvfefZuFjy5l6BGH1nudycr6TmB3n+fuxe5e3Lt37/Q+eX4+lIyCvEOhSxfo2TOYcnISav7L789gyxdP5OOuXQHYZcbv8/L4t2MGt1nIEqd/fygZxYQ3NzF8106mvbejRe+fZFbJ6FPofcQR+7/83Z2cnFx6HnZY842Bw488iq7dDvqk/b59dO7cmW7dDmrjyAP9+/enoKCALl260KVLF3r27EnPnj3Jy8tLy/MnwoK9gwQWNOsP/M7d6317mdntwHJ3vzecfxkYVTu5+7/HLxdOT7j7F8LyifHLNaa4uNgrKioSijelWntP2lgMrruufvmsWS07jv/FL8L69fDSS5npAM30PXlb+/y5uUHHcV05ObBrV/3yNjIqjH95RO9pnMzrHz9+PAUFBUybNo158+ZRU1PTon6U8ePHs3LlSgoKChg5cmSL26dCNrz/Zrba3YvrlqdqD2AJcGE4GugEYIe71wCPAaeZ2WFh5+9pwGNh3XtmdkI4+udC4NEUxZI9YjEYNw4uuyw4k/Wyy4L5lnbittMToTKunQ8jTVayZ8Jmw5m0ZWVlzJ07l6FDh7aqE12d8E1LqA/AzO4l+DWfb2bVBCN7ugK4+23AUuAMYAOwE7gorHvbzH4E1B5Em+3utQdTLyMYXZQL/CGcOp74D9zcuS1rW/cXbNiHkO5fsO1WBxhGmoxYLEYsFmv1L9Bk20v2SygBuPvEZuod+FYjdQuABQ2UVwA6GN6Uqiq48kpYvDj4EuvePdiDuPHG5ttKQFeDFWlUx78cdHsW8V+wKVFW9kkfQkv3wEQ6uKwfBRR57fxEqIzKlhMBIy5TZ+LW9mGUl5dTXl7e7q4Gmg5KAInI5OWIdT+D1qs9k7rupC+AtMrUmbi1VwOtOykBfEIJIBEahSPtWLK/wFvbXpfDzn5KAE1p55cjFoHkf4G3tr0uh539lACaEvFx5BmnY/hJSfYXeLLtdTnsQDZfjVQJoCmZHoUT9S9AHcNPSrK/wFPxC16Xw87uq5EqATQnk6Nw9AWYWZm+H0OSkv0Fnopf8FE+E7c99IEoATRHo3CiK8n7MWTDMMRkf4FH+Rd8spfCaA99IDoRTKSN1F5KIZPKysr2X8phbitOhEu2fXuW7KUw2kMfiBKAiEgbqd2Dir8aaTZRAhARaSPZvgelPgARkYhSAhARiSglABGRiFIC6Mja+Th2ad+yYRisNE0JoCnt/UzcJMexS/uW7BdwKtrrapzZLeGbwmeDjN0Uvr1L9qbumb4pfKZF/fVHXLK3xMyGW2q29U3hRUSknUkoAZjZGDN72cw2mNnVDdR/2sz+bGZrzGy5mfULy79iZpVx024zOyusW2hmr8fVFaXyhYmISNOaPRHMzDoDc4F/BaqBVWa2xN3XxS12I7DI3e82s9HAfwEXuPsTQFG4nl7ABuDxuHY/cPcHU/JKRESkRRLZAxgBbHD3Knf/CFgMnFlnmWOAZeHjJxqoBzgb+IO772xtsCIikjqJJIC+wJtx89VhWbzngfHh43HAIWZ2eJ1lJgD31imbEx42usnMDkowZhERSYFUdQJfCZSY2d+AEmAz8M/aSjMrAI4FHotrcw3wBeB4oBdwVUMrNrNpZlZhZhXbtm1LUbgiIm2rPZwHkUgC2AwcHTffLyzbz93/7u7j3X0YMCMsezdukXOBh919b1ybGg/sAe4iONRUj7vPc/didy/u3bt3Iq9JRCTj2sN5EIkkgFXAQDMbYGbdCA7lLIlfwMzyzax2XdcAC+qsYyJ1Dv+EewWYmQFnAS+2OHoREWm1ZhOAu38MXE5w+GY9cL+7rzWz2WY2NlxsFPCymb0CHAnMqW1vZv0J9iDK66z6HjN7AXgByAeuT+6liIhISyR0PwB3XwosrVM2M+7xg0CDwzndfSP1O41x99EtCVRERFJLZwKLiESUEoCISEQpAYiIRJQSQBTs2QOVlfDWW5mORESyiBJAFGzaBDt2wOzZmY5ERLKIEkBHlpsb3MCmpiaYLy0N5nNzMxuXiGQFJYCOrKoKJk2CTuHb3L07TJ4Mr7+e2bhEJCsoAXRkBQWQlwf79gVJYPfuYP6oozIdmYhkgYROBJN2bMsW6NMnSAYjR35yOEhEIk8JoKMrK/vknrZz52Y0FBHJLjoEJI2LxYJO4/LyYDILpiy6mqGItJ72AKRxsZi+7EU6MO0BiIg0Yc+ePVRWVvJWBzyRUglARKQJmzZtYseOHczugCdSKgGIiDQgNzcXM6MmHDlXWlqKmZHbgU6kVAIQaY6upRRJVVVVTJo0iU7hiZTdu3dn8uTJvN6BTqRUAhBpjq6lFEkFBQXk5eWxb98+OnXqxO7du8nLy+OoDnQipUYBiTQmNzc4e7pWaWkw5eTArl2Zi0vSZsuWLfTp04eCggJGjhy5/3BQR6E9AJHG6FpKkVdWVsbAgQPp0aMHc+fOpaysLNMhpVRCCcDMxpjZy2a2wcyubqD+02b2ZzNbY2bLzaxfXN0/zawynJbElQ8ws5XhOu8zs26peUkiKaJrKUkH12wCMLPOwFzgdOAYYKKZHVNnsRuBRe4+BJgN/Fdc3S53LwqnsXHlPwFucvd/Ad4BvpHE6xBpG7XXUho2DKZPV0ewdCiJ7AGMADa4e5W7fwQsBs6ss8wxwLLw8RMN1B/AzAwYDTwYFt0NnJVgzCLpU1YGAwdCjx7BtZQ62CEAibZEEkBf4M24+eqwLN7zwPjw8TjgEDM7PJzPMbMKM3vWzM4Kyw4H3nX3j5tYp4iItKFUdQJfCZSY2d+AEmAz8M+w7tPuXgxMAn5uZp9tyYrNbFqYQCq2bduWonBFRCSRBLAZODpuvl9Ytp+7/93dx7v7MGBGWPZu+Hdz+LcKWA4MA7YDPc2sS2PrjFv3PHcvdvfi3r17J/iyRESkOYkkgFXAwHDUTjdgArAkfgEzyzez2nVdAywIyw8zs4NqlwG+DKxzdyfoKzg7bDMFeDTZFyN16HLOItKEZhNAeJz+cuAxYD1wv7uvNbPZZlY7qmcU8LKZvQIcCcwJywcBFWb2PMEX/g3uvi6suwr4npltIOgTuDNFr0lqxWLgXn9SAhAREjwT2N2XAkvrlM2Me/wgn4zoiV/maeDYRtZZRTDCSEREMkBnAouIRJQSgIhIRCkBiIhElBKAiEhEKQGIiESUEoCISEQpAYiIRJQSgIhIRCkBiIhElBKAiEhEKQGIiESUEoCISEQpAYiIRJQSgIhIRCkBiIhElBKAiEhEKQGIiESUEoCISEQpAYiIRJQSgIhIRCWUAMxsjJm9bGYbzOzqBuo/bWZ/NrM1ZrbczPqF5UVm9oyZrQ3rzotrs9DMXjezynAqStmrEhGRZjWbAMysMzAXOB04BphoZsfUWexGYJG7DwFmA/8Vlu8ELnT3QmAM8HMz6xnX7gfuXhROlUm9EhERaZFE9gBGABvcvcrdPwIWA2fWWeYYYFn4+Inaend/xd1fDR//HdgK9E5F4CIikpxEEkBf4M24+eqwLN7zwPjw8TjgEDM7PH4BMxsBdANeiyueEx4ausnMDmroyc1smplVmFnFtm3bEghXRCR5sVgMM6O8vJzy8nLMDDMjFotlOrSUSVUn8JVAiZn9DSgBNgP/rK00swLgV8BF7r4vLL4G+AJwPNALuKqhFbv7PHcvdvfi3r218yAi6RGLxXD3elNHSgBdElhmM3B03Hy/sGy/8PDOeAAz6wH8P3d/N5zPA34PzHD3Z+Pa1IQP95jZXQRJRERE0iSRPYBVwEAzG2Bm3YAJwJL4Bcws38xq13UNsCAs7wY8TNBB/GCdNgXhXwPOAl5M4nWIiEgLNZsA3P1j4HLgMWA9cL+7rzWz2WY2NlxsFPCymb0CHAnMCcvPBU4GpjYw3PMeM3sBeAHIB65P0WsSEZEEmLtnOoaEFRcXe0VFRabDkKgZNSr4u3x5JqMQaTUzW+3uxXXLdSawSGNiMTCD8vJgMgumDtQJKNGmPQARkQ5OewAiInIAJQARkYhSAhARiSglABGRiFICEBGJKCUAEZGIUgIQEYkoJQARkYhqVyeCmdk2YFMrm+cD/0hhOKmm+JKj+JKj+JKT7fF92t3rXU+/XSWAZJhZRUNnwmULxZccxZccxZecbI+vMToEJCISUUoAIiIRFaUEMC/TATRD8SVH8SVH8SUn2+NrUGT6AERE5EBR2gMQEZE4HS4BmNkYM3vZzDaY2dUN1B9kZveF9SvNrH8aYzvazJ4ws3VmttbMvtPAMqPMbEfcLTRnpiu+8Pk3mtkL4XPXu/mCBX4Rbr81ZjY8jbF9Pm67VJrZe2b2H3WWSev2M7MFZrbVzF6MK+tlZn80s1fDv4c10nZKuMyrZjYljfH9j5m9FL5/D5tZz0baNvlZaMP4Yma2Oe49PKORtk3+r7dhfPfFxbbRzCobadvm2y9p7t5hJqAz8BrwGaAb8DxwTJ1lLgNuCx9PAO5LY3wFwPDw8SHAKw3ENwr4XQa34UYgv4n6M4A/AAacAKzM4Hv9FsH45oxtP4J7Xg8HXowr+2/g6vDx1cBPGmjXC6gK/x4WPj4sTfGdBnQJH/+kofgS+Sy0YXwx4MoE3v8m/9fbKr469T8FZmZq+yU7dbQ9gBHABnevcvePgMXAmXWWORO4O3z8IHCKmVk6gnP3Gnd/Lnz8PrAe6JuO506hM4FFHngW6GlmBRmI4xTgNXdv7YmBKeHuTwJv1ymO/4zdDZzVQNOvAn9097fd/R3gj8CYdMTn7o+7+8fh7LNAv1Q/b6Ia2X6JSOR/PWlNxRd+b5wL3Jvq502XjpYA+gJvxs1XU/8Ldv8y4T/BDuDwtEQXJzz0NAxY2UD1F83seTP7g5kVpjcyHHjczFab2bQG6hPZxukwgcb/8TK5/QCOdPea8PFbwJENLJMt2/Figj26hjT3WWhLl4eHqBY0cggtG7bfScAWd3+1kfpMbr+EdLQE0C6YWQ/gIeA/3P29OtXPERzWGArcAjyS5vBOdPfhwOnAt8zs5DQ/f7PMrBswFniggepMb78DeHAsICuH2pnZDOBj4J5GFsnUZ6EU+CxQBNQQHGbJRhNp+td/1v8vdbQEsBk4Om6+X1jW4DJm1gU4FNieluiC5+xK8OV/j7uX1a139/fc/YPw8VKgq5nlpys+d98c/t0KPEywqx0vkW3c1k4HnnP3LXUrMr39QltqD4uFf7c2sExGt6OZTQW+BkwOk1Q9CXwW2oS7b3H3f7r7PuCORp4309uvCzAeuK+xZTK1/VqioyWAVcBAMxsQ/kqcACyps8wSoHbExdnAssb+AVItPGZ4J7De3X/WyDJH1fZJmNkIgvcoLQnKzA42s0NqHxN0Fr5YZ7ElwIXhaKATgB1xhzvSpdFfXpncfnHiP2NTgEcbWOYx4DQzOyw8xHFaWNbmzGwM8P+Bse6+s5FlEvkstFV88X1K4xp53kT+19vSqcBL7l7dUGUmt1+LZLoXOtUTwSiVVwhGCMwIy2YTfNgBcggOHWwA/gp8Jo2xnUhwOGANUBlOZwDTgenhMpcDawlGNTwLfCmN8X0mfN7nwxhqt198fAbMDbfvC0Bxmt/fgwm+0A+NK8vY9iNIRDXAXoLj0N8g6FP6M/Aq8CegV7hsMTA/ru3F4edwA3BRGuPbQHD8vPYzWDsqrg+wtKnPQpri+1X42VpD8KVeUDe+cL7e/3o64gvLF9Z+5uKWTfv2S3bSmcAiIhHV0Q4BiYhIgpQAREQiSglARCSilABERCJKCUBEJKKUAEREIkoJQEQkopQAREQi6v8ATpaeMY2jPMAAAAAASUVORK5CYII=\n", "text/plain": [ "
" ] @@ -364,83 +369,6 @@ "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 deleted file mode 100644 index 1099305e..00000000 --- a/pyerrors/combined_fits.py +++ /dev/null @@ -1,216 +0,0 @@ -import iminuit -import autograd.numpy as anp -from autograd import jacobian -from pyerrors.fits import Fit_result -import numpy as np -import pyerrors as pe -from autograd import jacobian as auto_jacobian -from autograd import hessian as auto_hessian -from autograd import elementwise_grad as egrad -from numdifftools import Jacobian as num_jacobian -from numdifftools import Hessian as num_hessian -import scipy.optimize -import scipy.stats - -def combined_fit(x,y,funcs,silent=False,**kwargs): - r'''Performs a combined non-linear fit. - Parameters - ---------- - 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. - silent : bool, optional - If true all output to the console is omitted (default False). - initial_guess : list - can provide an initial guess for the input parameters. Relevant for - non-linear fits with many parameters. - num_grad : bool - Use numerical differentation instead of automatic differentiation to perform the error propagation (default False). - ''' - - output = Fit_result() - output.fit_function = funcs - - 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 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]) - 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 funcs.keys(): - x_array = np.asarray(x[key]) - model = anp.array(funcs[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) - - # use ordered dicts so the data and fit parameters can be mapped correctly - def chisqfunc_compact(d): - chisq = 0.0 - list_tmp = [] - c1 = 0 - c2 = 0 - for key in funcs.keys(): - x_array = np.asarray(x[key]) - c2+=len(x_array) - model = anp.array(funcs[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(): # 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: - 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.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(pe.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 \ No newline at end of file diff --git a/pyerrors/fits.py b/pyerrors/fits.py index e2998b25..180cc440 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -106,11 +106,11 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): 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 + x : dict dict of lists. - y : ordered dict + y : dict dict of lists of Obs. - funcs : ordered dict + funcs : dict dict of objects fit functions have to be of the form (here a[0] is the common fit parameter) ```python @@ -141,7 +141,7 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): can be used to choose an alternative method for the minimization of chisquare. The possible methods are the ones which can be used for scipy.optimize.minimize and migrad of iminuit. If no method is specified, Levenberg-Marquard is used. - Reliable alternatives are migrad, Powell and Nelder-Mead. + Reliable alternatives are migrad (default for combined fit), Powell and Nelder-Mead. correlated_fit : bool If True, use the full inverse covariance matrix in the definition of the chisquare cost function. For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. @@ -744,6 +744,10 @@ def _combined_fit(x,y,func,silent=False,**kwargs): raise Exception('Initial guess does not have the correct length: %d vs. %d' % (len(x0), n_parms)) else: x0 = [0.1] * n_parms + + output.method = kwargs.get('method', 'migrad') + if not silent: + print('Method:', output.method) def chisqfunc(p): chisq = 0.0 @@ -756,10 +760,6 @@ def _combined_fit(x,y,func,silent=False,**kwargs): 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 @@ -816,7 +816,7 @@ def _combined_fit(x,y,func,silent=False,**kwargs): 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.append(jacobian(func[key])(fit_result.x, x_array)) hat_vector = [item for sublist in hat_vector for item in sublist] return hat_vector From 3d6ec7b3979b6f6dff20652b1b4c4473b9a2678c Mon Sep 17 00:00:00 2001 From: ppetrak Date: Mon, 19 Dec 2022 14:03:45 +0100 Subject: [PATCH 04/13] fix: flak8 & pytest --- examples/my_db.sqlite | Bin 0 -> 8192 bytes pyerrors/__init__.py | 1 - pyerrors/fits.py | 112 ++++++++++++++++++++---------------------- 3 files changed, 54 insertions(+), 59 deletions(-) create mode 100644 examples/my_db.sqlite diff --git a/examples/my_db.sqlite b/examples/my_db.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..880c4275a7a77d3ddbbb866b9ecba01ecb3bccd4 GIT binary patch literal 8192 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 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 : dict dict of lists. y : dict @@ -117,16 +116,16 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): 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 @@ -160,10 +159,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): + + 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) @@ -688,39 +687,40 @@ def _standard_fit(x, y, func, silent=False, **kwargs): return output -def _combined_fit(x,y,func,silent=False,**kwargs): - + +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 += 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.') + 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') + 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]) @@ -731,41 +731,41 @@ def _combined_fit(x,y,func,silent=False,**kwargs): else: break else: - raise RuntimeError("Fit function (key="+ key + ") 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) 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 - + output.method = kwargs.get('method', 'migrad') if not silent: print('Method:', output.method) - + 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)) + 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)) + 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 - + 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 + fit_result = iminuit.minimize(chisqfunc, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef output.iterations = fit_result.nfev else: tolerance = 1e-12 @@ -776,23 +776,23 @@ def _combined_fit(x,y,func,silent=False,**kwargs): 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.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) - + print('chisquare/d.o.f.:', output.chisquare_by_dof) + print('fit parameters', fit_result.x) + def chisqfunc_compact(d): chisq = 0.0 list_tmp = [] @@ -800,38 +800,37 @@ def _combined_fit(x,y,func,silent=False,**kwargs): 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)) + 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) + 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): + if (len(x_array) != 0): hat_vector.append(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] + 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 @@ -839,29 +838,26 @@ def _combined_fit(x,y,func,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)' + 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 + output.fit_parameters = result + + return output def fit_lin(x, y, **kwargs): From c14d162f7ebc49a4663f90a6d4d485a9990375f4 Mon Sep 17 00:00:00 2001 From: ppetrak Date: Mon, 19 Dec 2022 14:09:24 +0100 Subject: [PATCH 05/13] clean-up --- examples/my_db.sqlite | Bin 8192 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 examples/my_db.sqlite diff --git a/examples/my_db.sqlite b/examples/my_db.sqlite deleted file mode 100644 index 880c4275a7a77d3ddbbb866b9ecba01ecb3bccd4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8192 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 Date: Mon, 19 Dec 2022 15:15:24 +0100 Subject: [PATCH 06/13] fix: Combined fit can now handle list and array inputs for x-values, test added. --- pyerrors/fits.py | 3 +-- tests/fits_test.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 722f9bc0..44ce8ae4 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -706,10 +706,9 @@ def _combined_fit(x, y, func, silent=False, **kwargs): x_all = [] y_all = [] for key in x.keys(): - x_all += x[key] y_all += y[key] - x_all = np.asarray(x_all) + x_all = np.concatenate([np.array(o) for o in x.values()]) if len(x_all.shape) > 2: raise Exception('Unknown format for x values') diff --git a/tests/fits_test.py b/tests/fits_test.py index 828c0cbe..d275a848 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -608,6 +608,22 @@ def test_ks_test(): pe.fits.ks_test(fit_res) +def test_combined_fit_list_v_array(): + res = [] + y_test = {'a': [pe.Obs([np.random.normal(i, 0.5, 1000)], ['ensemble1']) for i in range(1, 7)]} + for x_test in [{'a': [0, 1, 2, 3, 4, 5]}, {'a': np.arange(6)}]: + for key in y_test.keys(): + [item.gamma_method() for item in y_test[key]] + def func_a(a, x): + return a[1] * x + a[0] + + funcs_test = {"a": func_a} + res.append(pe.fits.least_squares(x_test, y_test, funcs_test)) + + assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) + assert (res[0][1] - res[1][1]).is_zero(atol=1e-8) + + def fit_general(x, y, func, silent=False, **kwargs): """Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. From 33ff2219ba467975fa03c38fa457326c3b23ac9b Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Mon, 19 Dec 2022 16:06:12 +0100 Subject: [PATCH 07/13] fix: Combined fit can now handle list and array inputs for y-values, test added. --- pyerrors/fits.py | 6 +----- tests/fits_test.py | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 44ce8ae4..f5f0850e 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -703,12 +703,8 @@ def _combined_fit(x, y, func, silent=False, **kwargs): jacobian = auto_jacobian hessian = auto_hessian - x_all = [] - y_all = [] - for key in x.keys(): - y_all += y[key] - x_all = np.concatenate([np.array(o) for o in x.values()]) + y_all = np.concatenate([np.array(o) for o in y.values()]) if len(x_all.shape) > 2: raise Exception('Unknown format for x values') diff --git a/tests/fits_test.py b/tests/fits_test.py index d275a848..9cf7b23a 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -610,18 +610,19 @@ def test_ks_test(): def test_combined_fit_list_v_array(): res = [] - y_test = {'a': [pe.Obs([np.random.normal(i, 0.5, 1000)], ['ensemble1']) for i in range(1, 7)]} - for x_test in [{'a': [0, 1, 2, 3, 4, 5]}, {'a': np.arange(6)}]: - for key in y_test.keys(): - [item.gamma_method() for item in y_test[key]] - def func_a(a, x): - return a[1] * x + a[0] + for y_test in [{'a': [pe.Obs([np.random.normal(i, 0.5, 1000)], ['ensemble1']) for i in range(1, 7)]}, + {'a': np.array([pe.Obs([np.random.normal(i, 0.5, 1000)], ['ensemble1']) for i in range(1, 7)])}]: + for x_test in [{'a': [0, 1, 2, 3, 4, 5]}, {'a': np.arange(6)}]: + for key in y_test.keys(): + [item.gamma_method() for item in y_test[key]] + def func_a(a, x): + return a[1] * x + a[0] - funcs_test = {"a": func_a} - res.append(pe.fits.least_squares(x_test, y_test, funcs_test)) + funcs_test = {"a": func_a} + res.append(pe.fits.least_squares(x_test, y_test, funcs_test)) - assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) - assert (res[0][1] - res[1][1]).is_zero(atol=1e-8) + assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) + assert (res[0][1] - res[1][1]).is_zero(atol=1e-8) def fit_general(x, y, func, silent=False, **kwargs): From 80c8a0f979944480ef4e83c9dcfc672f6aeb4636 Mon Sep 17 00:00:00 2001 From: ppetrak Date: Tue, 20 Dec 2022 15:26:13 +0100 Subject: [PATCH 08/13] feat: added (default) method Levenberg-Marquardt, test added --- examples/example_combined_fit.ipynb | 283 ++-------------------------- pyerrors/fits.py | 62 +++--- tests/fits_test.py | 26 +++ 3 files changed, 77 insertions(+), 294 deletions(-) diff --git a/examples/example_combined_fit.ipynb b/examples/example_combined_fit.ipynb index e658e7da..a492c674 100644 --- a/examples/example_combined_fit.ipynb +++ b/examples/example_combined_fit.ipynb @@ -45,7 +45,7 @@ { "cell_type": "code", "execution_count": 4, - "id": "modern-relay", + "id": "45f67973", "metadata": {}, "outputs": [ { @@ -55,14 +55,14 @@ "Fit with 3 parameters\n", "Method: migrad\n", "Optimization terminated successfully.\n", - "chisquare/d.o.f.: 0.3395164548834892\n", - "fit parameters [0.98791658 1.00784727 1.56875359]\n", - "chisquare/expected_chisquare: 0.339844373345418\n" + "chisquare/d.o.f.: 0.8085703524653507\n", + "fit parameters [0.97737577 1.01063624 1.47900852]\n", + "chisquare/expected_chisquare: 0.8121288230401409\n" ] } ], "source": [ - "output_test = pe.fits.least_squares(x_test,y_test,funcs_test,expected_chisquare=True)" + "output_test = pe.fits.least_squares(x_test,y_test,funcs_test,method='migrad',expected_chisquare=True)" ] }, { @@ -78,38 +78,12 @@ { "cell_type": "code", "execution_count": 6, - "id": "persistent-mathematics", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Goodness of fit:\n", - "χ²/d.o.f. = 0.339516\n", - "χ²/χ²exp = 0.339844\n", - "p-value = 0.9620\n", - "Fit parameters:\n", - "0\t 0.988(35)\n", - "1\t 1.008(32)\n", - "2\t 1.569(42)\n", - "\n" - ] - } - ], - "source": [ - "print(output_test)" - ] - }, - { - "cell_type": "code", - "execution_count": 7, "id": "wooden-potential", "metadata": {}, "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWoAAAD4CAYAAADFAawfAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAA0/ElEQVR4nO3de3zO9f/H8ccbwxajRI2lUQ5DIguhEB2IEJWQIiFttmQ5xUZCyaE5bU79hJRYKYeQr0MizPmwOZ82w3JYTttse//+eA+Fsdl1XZ9r2+t+u123bLuuz/Wabp577/15v19vpbVGCCGE88pjdQFCCCHuTIJaCCGcnAS1EEI4OQlqIYRwchLUQgjh5PLZ46IPPvig9vLysselhRAiR9q8efPfWuvit/uaXYLay8uLiIgIe1xaCCFyJKXU0fS+JlMfQgjh5CSohRDCyUlQCyGEk7PLHPXtXL16lejoaBISEhz1lk6tYMGCeHp64uLiYnUpQggn57Cgjo6OpnDhwnh5eaGUctTbOiWtNWfOnCE6OpoyZcpYXY4QwsllaOpDKeWvlNqllNqtlAq4lzdKSEigWLFiuT6kAZRSFCtWTH67EEJkyF2DWilVBXgfqAk8CTRTSj1+L28mIX2D/F0IITIqIyNqb2CD1vqy1joZWA28Zt+yhBBCXJORoN4FPKuUKqaUcgOaAo/c/CSlVFelVIRSKiIuLs7WdQohhFMKDg5GKXXLIzg42Gbvcdeg1lpHAl8Ay4DfgG1Aym2eN1lr7aO19ile/La7IC0XEhKCt7c37du3t7oUIUQOERwcjNaa+vXrU79+fbTWaK1tGtQZWvWhtZ4GTANQSg0Dom1WgQNNnDiR33//HU9PT6tLEUKIDMtQUCulSmitTyulSmPmp2tn6V0DAmDbtixd4hbVqsHYsel+uXv37hw6dIgmTZpw7NgxBg4cSO/evQGoUqUKCxcuBKBJkybUq1ePdevWUapUKRYsWICrqysHDhyge/fuxMXFkTdvXn788Ucee+yxW97n4sWLtGjRgnPnznH16lWGDh1KixYtbPu9CiFylYzuTJyvlNoD/Ap8qLU+b7+S7CM0NJSSJUuycuVKPvroo3Sft3//fj788EN2795N0aJFmT9/PgDt27fnww8/ZPv27axbtw4PD4/bvr5gwYL89NNPbNmyhZUrV/Lxxx8j51IKIbIio1Mfz9r0Xe8w8rVamTJlqFatGgA1atTgyJEjXLhwgZiYGFq1agWYME6P1pr+/fuzZs0a8uTJQ0xMDKdOneLhhx92RPlCiBzIYTsTnUm+fPlITU29/vG/N54UKFDg+p/z5s3LlStXMnXt2bNnExcXx+bNm3FxccHLy0s2tgghsiRXNmXy8vJiy5YtAGzZsoXDhw/f8fmFCxfG09OTn3/+GYDExEQuX7582+fGx8dTokQJXFxcWLlyJUePpttiVgiRgyQmJrJt2zZOnjxp82vnyqBu3bo1Z8+epXLlyowfP57y5cvf9TUzZ84kJCSEqlWrUqdOnXT/Z7Rv356IiAieeOIJvv32WypWrGjr8oUQTujo0aPEx8czZMgQm19b2eNGl4+Pj775hJfIyEi8vb1t/l7ZmfydCJH9ubq63nZ6s2DBgpmaOlVKbdZa+9zua7lyRC2EELZy6NAh2rVrR548Jk7d3Nxo3779XadUMyNX3ky0hZ07d/L222//53MFChRgw4YNFlUkhLCCh4cH7u7upKamkidPHhISEnB3d7fpSi8J6nv0xBNPsM3Wm3aEENnSqVOnKFmyJB4eHtSqVYvY2FibXl+CWgghsig8PJwGDRoAMGHCBJtfX4JaCCFsQGtNUlKSXa4tQS2EEFm0bds2tm3bRlJSEleuXMHV1dWm15dVH0IIcY/Onj1Ljx49qFGjBleuXKF06dL/2d1sK7kqqB3Rj3rVqlU0a9bMbtcXQlgvJSWFsLAwypcvT1hYGB9++CFPP/00Hh4e15fp2VKuCuqJEyeyfPlyZs+ebXUpQohsat26ddSsWZPu3btTuXJltm7dSkhICC4uLnZ7T0vmqAMCAmy+tK1atWqMdYJ+1AD//PMPr7zyCgcOHKBhw4ZMnDjRLj9lhRCOc/LkSfr06cO3335LqVKlmDNnDm+++aZDDqrONenhqH7UABs3bmTcuHHs2bOHgwcPEh4ebvPvRwjhGFevXmX06NGUL1+eOXPm0LdvX6Kiomjbtq1DQhosGlHfaeRrtaz2owaoWbMmZcuWBeCtt95i7dq1tGnTxq51CyFs7/fff6dnz55ERkbSpEkTvv76a8qVK/ef5wQHBzN48ODrH18L76CgIJudm5ihEbVS6iOl1G6l1C6l1Byl1J2Tysllph91cnJypq9/809ZR/3UFULYxtGjR2nTpg0vvPACiYmJ/PLLLyxatOiWkIYbh9ve/HDoKeRKqVJAT8BHa10FyAu0tVkFFrBnP2owUx+HDx8mNTWVH374gXr16tmsdiGE/Vy5coUhQ4bg7e3N4sWLGTp0KLt376Z58+aWDrgyOkedD3BVSuUD3IAT9ivJ/uzZjxrg6aefxtfXF29vb8qUKXN9ykQI4Zy01ixYsIDKlSsTFBREs2bNiIqKYsCAAXed6nSEDPWjVkr5A58DV4BlWutbFiIrpboCXQFKly5d4+aTTaT38q3k70QI6+3duxd/f3+WLl1KpUqVGDduHM8//7zD68hSP2ql1P1AC6AMUBK4TynV4ebnaa0na619tNY+xYsXz2rNQghhVxcuXKBPnz488cQTrF+/njFjxrBt2zZLQvpuMrLqozFwWGsdB6CUCgfqALPsWZizk37UQmRPWmvmzJlDYGAgJ06c4N1332XEiBE89NBDVpeWrowE9TGgtlLKDTP10QiIuPNLcj7pRy1E9rN9+3b8/Pz4448/qFGjBvPnz6d27dpWl3VXd5360FpvAOYBW4Cdaa+ZbOe6hBDCZs6ePYuvry9PPfUUe/bsYfLkyWzYsCFbhDRkcMOL1joICLJzLUIIYVMpKSlMnz6dfv36ce7cOT744AOGDBnCAw88YHVpmeKcW8iDg0GpWx82XEAuhMjZ/vrrL2rVqkXXrl2pVKkSW7ZsYfz48dkupMGZg1prqF/fPLQ2DwlqIcRdnDp1ik6dOvHMM88QGxvL7NmzWb16NU8++aTVpd0z5wzqaxITYds2uMPmEiGEANM8aezYsZQvX57Zs2fTp08foqKiaNeuXbZv4+DcQX30KMTHw5AhNrlcy5YtqVGjBpUrV2byZLkfKkRO8b///Y/q1avz0UcfUadOHXbu3MmIESMoXLiw1aXZhHMGtaurmZO+duT6pEnm4yyeQzZ9+nQ2b95MREQEISEhnDlzxgbFCiGscuzYMd544w0aNWrE5cuXWbBgAYsXL6ZChQpWl2ZTzhnUhw5Bu3Zwrdm+mxu0bw93aZ50NyEhITz55JPUrl2b48ePs3//fhsUK4RwtISEBD7//HMqVqzIr7/+ypAhQ9i9ezevvvpqtp/muB3nPIXcwwPc3SE11YR1QoL5+OGH7/mSq1at4vfff2f9+vW4ubnRoEGD/7Q3FUJkDwsXLsTf359Dhw7RunVrRo0axaOPPmp1WXblnEENcOoUlCxpQrtWrRvTIPcoPj6e+++/Hzc3N6Kiovjrr79sVKgQwhH2799PQEAAixcvpmLFiixfvpzGjRtbXZZDOG9Qh4dDgwbmzxMmZPlyL7/8MqGhoXh7e1OhQoVssyNJiNzu4sWLDBs2jFGjRlGgQAG++uor/Pz8yJ8/v9WlOYzzBrWNFShQgCVLllhdhhAig7TW/PDDD/Tu3ZuYmBg6duzIiBEj7nheaU7lnDcTr+1MXL3aPGRnohC5ys6dO2nYsCFvvfUWJUqU4M8//2TGjBm5MqTBmYP62m7Efz8kqIXI0c6fP0/Pnj2pXr06O3fuJDQ0lE2bNlGnTh2rS7NUrpn6EEI4r9TUVL755hv69evHmTNn6NatG5999hnFihWzujSn4JwjaiFErrFx40Zq165Nly5dKF++PBEREUycOFFC+l8kqIUQljh9+jTvvfcetWrV4vjx48ycOZM//viD6tWrW12a08nImYkVlFLb/vX4RykVYM+igoODUUrd8giWOWohsr3k5GRCQkIoX7483377Lb1792bv3r106NAhR+4qtIWMnPCyV2tdTWtdDagBXAZ+smdRwcHBaK2pX78+9evXR2uN1jpLQX3kyBGqVKliuyKFEJm2atUqqlevjr+/PzVr1mTnzp2MHDkSd3d3q0tzapmd+mgEHNRaH7VHMTdLTExk27ZtnJQ2p0JkC+n9NtyrVy/atm1Lw4YNuXDhAuHh4SxdupSKFStaXXK2kNmgbgvMud0XlFJdlVIRSqmIuLi4rFcGHD16lPj4eIbYqM1pcnIy7du3x9vbmzZt2nD58mWbXFcIYdz823BCQgLDhg0jLCyMBQsWEBQURGRkJK1atZJpjkzIcFArpfIDrwI/3u7rWuvJWmsfrbVP8eLFs1SUq6srSili0/p7TJo0CaUUrllsc7p371569OhBZGQk7u7uTJw4MUvXE0Kk78yZM1SpUoX+/fvz0ksvERkZSXBwcJb/HedGmRlRNwG2aK1P2auYaw4dOkS7du3Ik9bm1M3Njfbt23M4i21OH3nkEerWrQtAhw4dWLt2bZZrFUL814EDB9i5cye7du0ib968LF26lPDwcLy8vKwuLdvKzIaXt0hn2sPWPDw8cHd3JzU1lTx58pCQkIC7uzsPZ6HNKXDLr1ryq5cQtnPp0iWGDRvGV199RUpKCmXLlmXHjh25qnmSvWRoRK2Uug94AQi3bzk3nDp1ipIlS1K9enW6d+9ukxuKx44dY/369QB899131KtXL8vXFCK301rz448/UrFiRYYNG8Ybb7zBk08+yZkzZzh79qzV5eUIGQpqrfUlrXUxrXW8vQu6Jjw8nHLlylGoUCEmTJhAeHjWf0ZUqFCBCRMm4O3tzblz5/jggw9sUKkQudfu3btp1KgRb7zxBg8++CB//PEHM2fOJDY21qYLAXK7XNPrw8vLi6ioKKvLECJHiI+PJzg4mHHjxl2/Md+1a1cKFSr0n5OTJk2axKRJkyhYsCBXrlyxsOLszSm3kF9bi7l69WpWr14tOxOFcBLXmieVL1+er7/+mi5durBv3z4++OAD8ubNa7eFALmdU46og4ODJZSFcDIRERH4+vqyYcMGnnnmGZYsWcJTTz31n+fYayFAbufQoNZay0qLNFprq0sQIkPi4uLo378/06ZNo0SJEsyYMYMOHTpcHzXf7NpCAA8PD2rVqnV9P4S4dw4L6oIFC3LmzBmKFSuW68Naa82ZM2coWLCg1aUIka7k5GRCQ0MZOHAgFy9e5KOPPiIoKOiufTnCw8NpkHbe6QQbnHcqHBjUnp6eREdHY6vt5dldwYIF8fT0tLoMIW5rzZo1+Pn5sWPHDho3bkxISAje3t5Wl5VrOSyoXVxcKFOmjKPeTghxD2JiYggMDGTOnDmULl2aefPm8dprr+X634Kt5pSrPoQQjpWYmMgXX3xBhQoVCA8PZ+DAgURGRtK6dWsJaSfglKs+hBCO89tvv9GzZ0/279/Pq6++ypgxYyhbtqzVZYl/kRG1ELnUoUOHaNGiBU2aNAFgyZIlLFiwIEshLXsg7EPZY5mYj4+PjoiIsPl1hRBZd/nyZUaMGMGXX35Jvnz5GDhwIAEBARQoUMDq0nI1pdRmrbXP7b4mUx9C5BJaa8LDw+nVqxfHjh2jXbt2fPnll5QqVcrq0sRdyNSHELnAnj17eOGFF2jTpg1FixZl9erVzJ49W0I6m5CgFiIH++eff/j444958skn2bx5M+PHj2fz5s0899xzVpcmMkGmPoTIgVJTU5k1axaffPIJp0+fpkuXLnz++edk9Zg8YY2MHhxQVCk1TykVpZSKVEo9Y+/ChBD3ZsuWLdSrV4933nkHLy8vNm7cyOTJkyWks7GMTn18Dfymta4IPAlE2q8kIcS9OHPmDN27d8fHx4eDBw/yzTffsG7dOnx8bruQQNia1nDihF0ufdegVkoVAZ4DppladJLW+rxdqhFCZFpKSgqTJk2iXLlyTJ06FX9/f/bt28e7776bboc7YUPnzsHXX0PlylC3LqSk2PwtMvJ/sQwQB3yjlNqqlJqadobifyiluiqlIpRSEdJ4SQjHWLt2LT4+PvTo0YNq1aqxfft2xowZQ5EiRawuLWfTGtavh3ffhZIlISAACheGQYMgNdXmb5eRoM4HPAVM0lpXBy4BfW9+ktZ6stbaR2vtI3NhQthXbGwsb7/9Ns8++yxnzpxh7ty5rFixgsqVK1tdWs72zz8waRJUqwZ16sD8+Sast26FDRugUydwcbH522Zk1Uc0EK213pD28TxuE9RCCPtLSkoiJCSEwYMHk5SUxIABA+jXrx/33XfLL7nCljZvhtBQmDMHLl2C6tUhLAzeesuMpO3sriNqrfVJ4LhSqkLapxoBe+xalRDiFsuWLaNq1aoEBgbSsGFD9uzZw9ChQyWk7eXiRZg6FXx8zGP2bHjzTdi40QR3164mpIODQalbHzbsb5KhXh9KqWrAVCA/cAjopLU+l97zpdeHELZz5MgRevXqxU8//cTjjz/O119/TdOmTa0uK+fascOMlmfOhAsXoEoV6NYNOnSAokXTf13aqTasWnVPb5vlXh9a622ArPERwoGuXLnCl19+yYgRI8iTJw/Dhw/no48+kuZJ9nDlCsydawJ6/XooUADeeAO6d4dnnjEjZAvJzkQhnIzWmp9//plevXpx5MgR2rZty8iRI+XoNnuIjDThPGMGnD8PFSrA6NHQsSMUK2Z1dddJUAvhRKKiovD392fZsmVUqVKFlStXXj8oVthIYqJZrREWBmvWmFUarVub6Y369e999JyYaIL/5El4+GGbliyr4YVwAhcuXCAwMJAnnniCDRs2EBISwtatWyWkbWn/fggMBE9PaN8eYmLgiy8gOtqs5mjQIGtTHEePQnw8DBlis5KvkRG1EBbSWjN79mw++eQTTp48SefOnRk2bBglSpSwurSc4epVWLDALK1bsQLy5oWWLc3ouVEjsMXOTVdXSEi48fGkSeZRsKCZ+7YBGVELYZFt27bx7LPP8vbbb+Pp6clff/3F1KlTJaRt4fBh6N8fHnkEXn/djKaHDoXjx2HePHjhBduENMChQ9Cu3Y3rubmZEfvhw7a5PjKiFsLhzp49y6effkpYWBjFihVj6tSpdOrUSfpyZFVyMixaZEbPS5eaaYxXXjErN156yYym7cHDA9zdzdbxPHnM6Nrd3abz1BLUQjhISkoKU6dOZcCAAZw/fx5fX18GDx5M0TutzRV3Fx1tNqZMnWrmnUuWhIEDoUsXM6J2hFOnzPt6eECtWhAba9PLS1AL4QDr16/H19eXLVu2UL9+fcaNG8cTTzxhdVnZV0qKGTWHhcHChaZJ0ksvwYQJZhSdz8HRFh5+Y8PLhAk2v7wEtRB2EhwczODBg2/5fP369SWk71VsLEyfDlOmmFUWDz0EffrA++9DmTJWV2c3EtRC2MHVq1dxd3encOHCXLx4EU9PT/bs2UOhQoWsLi37SU01KzbCwswKjuRks2Jj5Eho0QLy57e6QruToBbCxlasWIGfnx+RkZE0bdqUuLg43NzcJKQzKy4OvvkGJk+GgwfNTsGAANMMqVw5q6u7ITgY/v2b07W12EFBNmvMJLeZhbCRo0eP0qZNGxo3bkxSUhK//vorixYtws3NzerSsg+tYfVq0z60VCkzrVGqlOlcFx1tRtHOFNJgwljrWx827J4nI2ohsighIYGRI0cyfPhwAIYOHcrHH39MwYIFAUhMTCQyMpKTJ0/ysI23FucYZ8+afhuTJ0NUlOlS16OHGT1XqmR1dZaTEbUQ90hrzS+//EKlSpUYNGgQzZo1IyoqigEDBlwPaTAj7fj4eIbYYWtxtqY1rFtnGiCVLAm9epmA/r//M8vsxo6VkE4jQS3EPdi3bx9NmzalRYsWuLm5sWLFCubOnUvp0qWvP8fV1RWlFLFpa2onTZqEUgpXV1eryra/jDTRj483S9iqVjWHwf78M3TuDNu2mRaj77xjdveJG7TWNn/UqFFDC5ETXbhwQffp00e7uLhod3d3PWbMGJ2UlHTb5544cUK3a9dO58mTRwPazc1Nt2/fXsfGxjq4agvUr28e16Smar1xo9bvvae1m5uZxa1RQ+spU7S+cMGqKp0KEKHTydQMzVErpY4AF4AUIFmncwqBEDmV1po5c+YQGBjIiRMn6NSpE8OHD+ehhx5K9zUeHh64u7uTmppKnjx5SEhIwN3dPXfNU1+4YDrThYaaA2Dd3ExfjG7dzPFWIkMyczOxodb6b7tVIoST2rFjB35+fqxZs4YaNWowf/58ateunaHXnjp1ipIlS+Lh4UGtWrWuT4PkeOfOmd7MDz8Mly+baY4JE0yzoiJFrK4u25FVH0Kk49y5cwwaNIiJEydy//33M3nyZDp37kzeTDT3CQ8Pv95TeoIdthY7lcuX4YcfzOh5xw7zubJlzc3BWrUsP84qO8toUGtgmVJKA2Fa68k3P0Ep1RXoCvznhooQ2U1KSgrTp0+nf//+nD17lg8++IAhQ4bwwAMPWF2ac9q92+wa/PZbc6Pw3/buNWcO2rA3c26U0VUf9bTWTwFNgA+VUs/d/ASt9WSttY/W2qd48eI2LVIIR9mwYQO1a9ema9eueHt7s2XLFsaPHy8hfbOEBJg1C5591pzSHRZmmiGFh5vNKnbszZwbZSiotdYxaf89DfwE1LRnUUI42qlTp+jUqRO1a9fmxIkTzJ49m9WrV/Pkk09aXZpz2bsXPv7Y7BZ8+21zPuDIkWbd8+zZ0KqVmYO2Y2/m3OiuQa2Uuk8pVfjan4EXgV32LkwIR7h69Spjx46lfPnyzJ49mz59+hAVFUW7du1QWZxTDQ4ORinF6tWrWb16NUoplFIE23BrsUMkJZm55+efh4oVISTENEX6/XcT3L17w4MP3nj+td7M1aubpv0nT1pXew6hzPK9OzxBqbKYUTSYOe3vtNaf3+k1Pj4+OiIiwjYVCmEn//vf/+jZsye7d+/m5ZdfZuzYsVSoUMHqspzHoUNmS/c338Dp0+DlZbZ0d+p09xHytd7Mq1bZucicQym1Ob2lz3e9mai1PgTI738ixzh27Bi9e/fmxx9/pEyZMixYsIDmzZtneQSdI1y9Cr/+auacly0zx1c1b27WPb/4ou3OGRSZIsvzRK6RkJDAqFGj+Pzzz9FaM2TIEHr37p2zt3Rn1LFjphn/tGmmOb+np2nd+d57Zj5aWEqCWuQKCxcuxN/fn0OHDtG6dWtGjRrFo48+anVZ1kpJgSVLzLrnJUtMk6QmTcxoukmTezvOygG9mXOju85R3wuZoxbOYv/+/QQEBLB48WK8vb0JCQmhcePGVpdlrZgYM3KeOhWOHzfzzV26mEdu/+FloSzNUQuRHV28eJFhw4YxatQoChQowKhRo/Dz88PFxcXq0qyRmgrLl5vR86+/mtH0Cy+YVqLNm0Nu/XvJJiSoRY6iteaHH36gd+/exMTE0LFjR0aMGIGHh4fVpVnj1Kkbx1kdPgzFi5vldO+/D489ZnV1IoMkqEWOsXPnTvz8/Fi9ejXVq1dn7ty51KlTx+qyHE9rWLnSjJ5//tms5GjQAIYPh5YtoUABiwsUmSVBLbK98+fPX2+eVKRIEUJDQ+nSpUummiflCH//bY6zCguD/fvhgQfA19esfa5Y0erqRBbIokiRbaWmpjJt2jTKly/PhAkT6Nq1K/v27aNbt27OEdIZOe0kq7SGP/4w/TRKlTLTGiVKwMyZ5qbh6NES0jmArPoQ2dLGjRvx9fVl06ZN1K1bl3HjxlG9enWry7q9Z54xvZmjomzX8+LcORPGYWGwZ4/pr/H222ZjSpUqtnkP4VB3WvUhI2qRrZw+fZr33nuPWrVqcfz4cWbOnMkff/zhvCENcPSoaf+Z1cNttYa//oJ33zW9NPz9oVAhs9QuJgbGjZOQzqFkRC2yheTkZCZOnMigQYO4dOkSAQEBDBw4EHd3d6tLS5+rq+ked7PM9mb+5x/Tme5aQ/5ChcxUR7dupvGRyBFkRC2ytVWrVlG9enX8/f2pWbMmO3fuZOTIkc4d0mCaGrVrd++9mTdvNjcCS5aEHj1M343QUDhxwvxXQjrXkKAWDnOt7efNj/TafkZHR9O2bVsaNmzIhQsXCA8PZ+nSpVTMLjfHPDxML+bM9Ga+eNHsGPTxMY9Zs+DNN2HDBhPc3bpB4cKO+x6EU5CpD+Fw184QXJVOC8zExERGjx7N0KFDSU1NpU+fPvTp0yd7Nk967TUTsh4e5tzA2FhzCsrNduwwNwZnzjQnd1epYkK5QwcoWtThZQvHky3kwqkkJiYSGRnJyZMnefim0eWiRYsICAjgwIEDtGrVitGjR+Pl5WVNobYQHn6jN/PNh9teuQJz55qAXr/ebER54w0T0HXqyGGw4roMT30opfIqpbYqpRbasyCR8x09epT4+HiG/GsVxIEDB2jevDnNmjUjT548/Pbbb4SHh2fvkE5PZCQEBJi553ffhTNnzHrnmBhzQGzduhLS4j8yPPWhlOoF+ADuWutmd3quTH2I23F1dSXhNqsg8ubNS968ecmfPz+DBg3C39+f/PnzW1Chjd3c8vPfXFygdWszeq5fX4JZZH3Vh1LKE3gFmGrLwkTucujQIdq1a0eetFUQ+fPnx9XVlZSUFF5//XX27t1LYGBgzghpMEG9b99/zxR87DH44guIjoY5c8y0iIS0uIuMzlGPBT4B0r3drJTqCnQFKF26dJYLEzmPh4cH7u7upKamApCUlESxYsVYtmwZ9erVs7g6G0pKggULzNzzihVmWV2LFuag10aN5DgrkWl3DWqlVDPgtNZ6s1KqQXrP01pPBiaDmfqwVYEi5zh//jzLly8HzHRHnTp1KFasWM4J6cOHzXFW06eb9qKlS8PQodC5s1n1IcQ9ysiIui7wqlKqKVAQcFdKzdJad7BvaSKnSE1NZcaMGfTt25e4uDg8PDwoU6YMa9assbq0rEtOhoULzeh56VIzjdGsmZl7fuklM5oWIovu+juY1rqf1tpTa+0FtAX+JyEtMmrTpk3UqVOHzp0789hjj7Fp0ybKly+f/U9aOX7cnAPo5QWtWpl10IMGwZEjZtqjaVMJaWEzso5a2EVcXBz9+/dn2rRplChRghkzZnDw4EF8fG7c1FZpN9GCgoLS3Z3oVFJSzKg5NBQWLTJNkl56yayPfuWVezsMVogMkJ2JwqaSk5MJDQ1l4MCBXLx4kZ49ezJo0CCKFClidWn3LjbWdKibMgWOHYOHHoL33jOHwZYpY3V1IoeQnYnCIdasWYOvry87d+6kUaNGhISEUKlSJavLujepqWbFRmgo/PKLmYtu1AhGjYJXX4WcsoRQZAsS1CLLYmJiCAwMZM6cOZQuXZp58+bx2muvXZ/ayFZOn4b/+z9zGOzBg1CsmNlF2LUrlCtndXUil5KgFvcsMTGRsWPH8tlnn5GcnMzAgQPp27cvbm5uVpeWOVrD6tVm5cb8+eYw2OeeM43+W7eWw2CF5SSoxT357bff6NmzJ/v37+fVV19lzJgxlC1b1uqyMufs2RuHwe7da7rU9ehhRs/ZdcpG5EgS1CJTDh06xEcffcQvv/xCuXLlWLJkCS+//LLVZWWc1rBunQnnuXMhMdGcafh//2c612XHVqoix5OgFhly+fJlRowYwZdffkm+fPkYMWIEAQEBFMgu0wLx8TcOg921yzTff+89szGlalWrqxPijiSoxR1prZk/fz4ff/wxx44do127dnz55ZeUKlXK6tLuTmvYtMmE8/ffw+XL5tSUKVOgbVtz9qAQ2YAEtUjXnj176NmzJytWrKBq1arMnDmT5557zuqy7u7CBfjuOxPQW7fCfffdOAy2Rg2rqxMi0ySoxS3i4+MZPHgw48aNo1ChQowfP55u3bqRz9l33m3dasJ59mxz9mDVqjBxoglpZz8IV4g7cPJ/ecKRUlNTmTlzJn369OH06dN06dKFzz//nOLFi1tdWvouXYIffjABvXEjFCxopjW6dTNnFGbHtdxC3ESCWgCwZcsWfH19Wb9+PbVq1WLhwoX/6cvhdHbtunEYbHw8eHvD11/D22/D/fdbXZ0QNiVBncv9/fffDBgwgClTplC8eHG++eYbOnbseP0UFqeSkADz5plt3X/+abZxt2ljGvLXqyejZ5FjSVDnUikpKYSFhfHpp5/yzz//4O/vT3BwsHM2T9q714yeZ8wwm1TKlYOvvoJ33rlxxJUQOZgEdS60du1afH192b59Ow0bNmTcuHFUrlzZ6rL+KykJfvrJjJ5XrTItRFu1MqPnhg1l9CxyFSf8/VbYy4kTJ+jQoQPPPvssZ8+eZe7cuaxYscJxIR0cbAL25se/e1EfPAh9+4Knp7kpeOQIDBtmGvXPnQvPPy8hLXIfrfUdH5jjtzYC24HdwOC7vaZGjRpaOI/ExET95Zdf6kKFCun8+fPrAQMG6IsXL1pXUO3aWhcponVsrPk4KUnr+fO1fuEFrUHrvHm1btlS699+0zolxbo6hXAgIEKnk6kZmfpIBJ7XWl9USrkAa5VSS7TWf9nrh4ewnaVLl+Lv78/evXtp3rw5Y8aM4bHHHrO2qKNHzUqNwEBzlNW0aaY5v6cnDB5stnZnh52PQjjIXYM6Lekvpn3okvaQU8ad3OHDh+nVqxc///wzjz/+OIsWLaJp06bWFuXqalZuXDNrlvlvnjymOX+TJnKclRC3kaE5aqVUXqXUNuA0sFxrveE2z+mqlIpQSkXExcXZuEyRUZcvXyYoKIhKlSqxbNkyhg8fzq5du6wP6ZgY8PX9b3e6fPmgRQvztebNJaSFSEeGglprnaK1rgZ4AjWVUlVu85zJWmsfrbWPU+9ky6G01oSHh1OpUiWGDBlCy5Yt2bt3L3379rWuw11qKvz2m1mt8eijZkldsWLma0qZr5csCQ8/bE19QmQTmVr1obU+D6wEslED4pwvKiqKl156idatW1O4cGFWrlzJnDlz8PT0tKagU6dg+HB47DEznfHnn9C7Nxw4AE8/bcL5qafMUruTJ62pUYjsJL27jPrGqo/iQNG0P7sCfwDN7vQaWfXhGPHx8frjjz/W+fLl00WKFNEhISH66tWr1hSTkqL1779r/frrWufLZ1ZvNGyo9fffa52QYJ4TFGQ+f/MjKMiamoVwItxh1YcyX0+fUqoqMAPIixmBz9VaD7nTa3x8fHRERESWf4iI29NaM2vWLD755BNOnTpF586dGTZsGCVKlHB8MX//feMw2P374YEH4N13zXFWFSo4vh4hsiml1Gat9W0b7GRk1ccOoLrNqxL3ZOvWrfj6+rJu3TqefvppFixYQM2aNR1bhNawdq3ZNThvntlFWLcuDBpkem8ULOjYeoTI4eQ2ezZx5swZPv30U8LCwnjwwQeZOnUqnTp1cmzzpHPn4NtvTd+NyEgoUsS0E+3aFarccn9ZCGEjEtROLiUlhSlTpjBgwADi4+Px8/Nj8ODBFC1a1DEFaA1//WXC+YcfzDromjVh+nR4801wc3NMHULkYhLUTmzdunX4+vqydetW6tevz7hx43jiiScc8+b//GNOSgkNhR07zPmC77xjRtDVZSZMCEeSpkxOKDY2lo4dO1K3bl1Onz7N999/z8qVKx0T0ps3w/vvmyV0PXpA3rxmNH3ihAltCWkhHE5G1BYJDg5m8ODBt3z+hRde4K+//iIxMZF+/frRv39/Ctn7tOyLF2HOHBPImzeb3YNvvWVGz08/Ld3qhLDYXZfn3QtZnpdxJUuWJDY2lmbNmnHgwAGioqJo2rQpY8eOpVy5cvZ98x07zCh51ixzcneVKiacO3QAR82BCyGAOy/Pk6C2iKurKwn/blCUxsXFhaSkJPu98ZUrpq9zaKi5SVigALzxhgnoOnVk9CyERe4U1DJHbZHdu3f/Z87ZxcWFtm3bcuzYMfu8YWQkBASYued33zVHWo0ebRoiffutWQctIS2EU5I5agfTWvPLL78QEBDAkSNHAFBKkZKSwv3338/DtmxQlJgI8+ebuec1a8DFBVq3NqPn+vUlmIXIJmRE7UB79+6lSZMmtGzZkkuXLl3/vNaa1NRUJk2aRPC/j6W6V/v3m6b8pUpB+/Zm1PzFFxAdbW4aNmggIS1ENiIjage4cOECQ4cOZcyYMbi6ujJmzBg+/PBDXFxcbPcmSUmwYIEZPa9YYZbVtWxpRs+NGpnm/EKIbEmC2o601nz33XcEBgYSGxtLp06dGD58OA899JDt3uTwYZgyxewUPHUKSpeGoUOhc2fw8LDd+wghLCNBbSfbt2/Hz8+PP/74Ax8fH8LDw6ldu7ZtLp6cDAsXmtHz0qVmGuOVV0x/55deMqNpIUSOIUFtY2fPnmXgwIGEhobywAMPMGXKFDp37myb5knHj8PUqeYw2JgYs4Jj4EDo0gUeeSTr1xdCOCUJahtJSUlh2rRp9O/fn3PnztGjRw+GDBnC/fffn9ULm+OswsJg0SLTJOmll2DCBDOKlnMGhcjx5A6TDaxfv55atWrRrVs3KlWqxJYtWxg3blzGQjo21iyVu/lIqthYM9dctiw0awYbN0KfPnDwICxZYg6FlZAWIle46790pdQjwLfAQ4AGJmutv7Z3YdnByZMn6du3LzNmzKBkyZJ89913tG3bFpWRpW/BwfDvXh/Xbvx16ACXL8Mvv5i56EaNzKGwLVpA/vx2+T6EEM4tI0dxeQAeWustSqnCwGagpdZ6T3qvyelbyK9evcr48eMJDg7mypUr9OrVi08//TRzzZNcXU1v59spVgw6dTIN+e3d70MI4RSytIVcax2rtd6S9ucLQCRQyrYlZh8rVqygWrVq9OrVizp16rBr1y5GjBiR+Q53Bw+a0fK/lShh5p6jo2HkSAlpIQSQyTlqpZQX5vzEDbf5WlelVIRSKiIuLs5G5TmPY8eO8frrr9O4cWOuXLnCggULWLx4MeXLl8/chc6ehTFj4PnnzcaUa5Qy27t79JAzB4UQ/5Hhu1FKqULAfCBAa/3PzV/XWk8GJoOZ+rBZhRZLSEjgq6++YtiwYQAMGTKEwMBACmYmTLWGdetMx7offzQ9OJ55Bp56yjTkL1UKatUyNxCFEOJmWuu7PgAXYCnQKyPPr1Gjhs7uUlNT9YIFC3TZsmU1oFu3bq2PHDmSuYucO6f1uHFaV6miNWhduLDWPXpovX271kFB5nM3P4KC7PDdCCGcHRCh08nUjKz6UMA0IFJrPdqePzScxb59+wgICGDJkiV4e3uzfPlyGjdunLEXaw2bNpl1z3PmmP7PNWqYbd5t25qzBwGqVjUrP4QQ4i4yMvVRF3gb2KmU2pb2uf5a68V2q8oiFy9eZOjQoYwePZqCBQsyatQo/Pz8MtY86cIF+O47E9Bbt8J995mldt26maAWQoh7dNeg1lqvBXJ0T0ytNd9//z2BgYHExMTQsWNHvvjii4z1ht661YTz7Nnm7MGqVWHiRNNe1N3d/sULIXK8XL+1bceOHfj5+bFmzRqeeuop5s6dS506de78okuX4IcfTEBv3GhWabz5pmmKVKuW9HoWQthUrg3qc+fOERQUxIQJEyhatCihoaF06dKFvHfqPLdrlwnnmTMhPh68vWHsWOjYEbLa00MIIdKR64I6NTWV6dOn069fP86ePUv37t357LPPeOCBB27/goQEmDfPLK3780+zjbtNGzP3/OyzMnoWQthdrgrqjRs34uvry6ZNm6hbty7jx4+nWrVqt3/y3r1m9DxjhtmkUq6c2S347rvw4IOOLFsIkcvliqA+ffo0/fr1Y/r06Xh4eDBr1izatWt3a/OkpCT46Sczel61ynSna9XKzD03aCDHWQkhLJGjgzo5OZkJEyYQFBTEpUuXCAwMZODAgRQuXPi/Tzx48MZxVnFx4OUFw4aZxki2PBVcCCHuQY4N6lWrVuHn58euXbt48cUX+frrr6lYseKNJ1y9Cr/+akbPy5eb46uaNzdzzy++KKNnIYTTyHFBffz4cXr37s3cuXPx8vLip59+okWLFjemOY4evXGcVWwseHqavtDvvWd6bgghhJPJMUGdmJjIqFGj+Pzzz0lNTSU4OJhPPvkEV1dXc5zV4sXm5uDitA2VTZua0XOTJnJSihDCqeWIhFq0aBH+/v4cPHiQVq1aMXr0aLy8vMwBsNOmmRH08eNmvnnAAHMY7KOPWl22EEJkSLaeiD1w4ADNmjWjWbNmuLi4sGzZMsLnzcMrKsqs1nj0UQgKgooVYf58OHYMPvtMQloIka1ky6C+dOkSAwYMoHLlyqxevZqRI0eyfdkyXoiIgMcfN9MZf/4JvXvDgQOwbBm89hpkpLmSEEI4mWw19aG1Zu7cufTu3Zvo6Gg6tG/PF82bU3L+fOjXzxwG26ABDB8OLVtCgQJWlyyEEFmWbYJ6165d+Pn5sWrVKqpVqcKcV1+l3vLlpmvdAw+An585DPbfS/CEECIHcPqgPn/+PMHBwYwfPx53NzcmPv00XbdtI++uXVC3LgwaZHpvyDmDQogc6q5z1Eqp6Uqp00qpXXavJjYW6teHkyevN08qX64cISEhdClcmH0XLvDBvn3k7dYNdu6EtWtNc34JaSFEDpaREfX/AeOBb+1bCmZFxtq1nPrwQ16NimLjnj08oxS/ac1T5cubdc9vvmlOTxFCiFwiIye8rFFKedm1ChcXSE4mFmgL/BAezgYgGcjz/vvk6d4dqle3awlCCOGsbLY8TynVVSkVoZSKiIuLy9yLjx0jsUULBgFrgSAgqUYN8u3fT56wMAlpIUSupswp5Xd5khlRL9RaV8nIRX18fHRERESGi3DJm5fk1NRbPp8vTx6upqRk+DpCCJFdKaU2a619bvc1p9jwciw6mnaenuRP+9gtXz7ae3pyPCbG0rqEEMIZOEVQh4WF8V10NElpH19OTmZ2dDShoaGW1iWEEM7grjcTlVJzgAbAg0qpaCBIaz3NlkUEBwezY8cOPDw86Nq1K5MnTyY2Npbg4GBbvo0QQmRLGZqjzqzMzlELIURu5/Rz1EIIIdInQS2EEE5OgloIIZycBLUQQjg5CWohhHByEtRCCOHkJKiFEMLJ2WUdtVIqDjh6jy9/EPjbhuVkB/I953y57fsF+Z4z61GtdfHbfcEuQZ0VSqmI9BZ951TyPed8ue37BfmebUmmPoQQwslJUAshhJNzxqCebHUBFpDvOefLbd8vyPdsM043Ry2EEOK/nHFELYQQ4l8kqIUQwsk5TVArpV5WSu1VSh1QSvW1uh5HUEpNV0qdVkrtsroWR1BKPaKUWqmU2qOU2q2U8re6JntTShVUSm1USm1P+54HW12Toyil8iqltiqlFlpdiyMopY4opXYqpbYppWzakN8p5qiVUnmBfcALQDSwCXhLa73H0sLsTCn1HHAR+DajBwdnZ0opD8BDa71FKVUY2Ay0zMn/n5VSCrhPa31RKeUCrAX8tdZ/WVya3SmlegE+gLvWupnV9dibUuoI4KO1tvkmH2cZUdcEDmitD2mtk4DvgRYW12R3Wus1wFmr63AUrXWs1npL2p8vAJFAKWursi9tXEz70CXtYf3oyM6UUp7AK8BUq2vJCZwlqEsBx//1cTQ5/B9wbqeU8gKqAxssLsXu0qYAtgGngeVa6xz/PQNjgU+AVIvrcCQNLFNKbVZKdbXlhZ0lqEUuopQqBMwHArTW/1hdj71prVO01tUAT6CmUipHT3MppZoBp7XWm62uxcHqaa2fApoAH6ZNbdqEswR1DPDIvz72TPucyGHS5mnnA7O11uFW1+NIWuvzwErgZYtLsbe6wKtpc7bfA88rpWZZW5L9aa1j0v57GvgJM6VrE84S1JuAckqpMkqp/EBb4BeLaxI2lnZjbRoQqbUebXU9jqCUKq6UKpr2Z1fMDfMoS4uyM611P621p9baC/Nv+X9a6w4Wl2VXSqn70m6Qo5S6D3gRsNlqLqcIaq11MuALLMXcYJqrtd5tbVX2p5SaA6wHKiilopVS71ldk53VBd7GjLC2pT2aWl2UnXkAK5VSOzADkuVa61yxXC2XeQhYq5TaDmwEFmmtf7PVxZ1ieZ4QQoj0OcWIWgghRPokqIUQwslJUAshhJOToBZCCCcnQS2EEE5OgloIIZycBLUQQji5/weO8BTT/ODOrwAAAABJRU5ErkJggg==\n", "text/plain": [ "
" ] @@ -129,246 +103,6 @@ "plt.legend()\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "3b82d1c6", - "metadata": {}, - "outputs": [], - "source": [ - "x_const = {'c':[0,1,2,3,4,5,6,7,8,9],'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": 9, - "id": "7c1f7950", - "metadata": {}, - "outputs": [], - "source": [ - "#needs to be vectorized for expected chi2 to work (jacobian matrix incorrect dim. otherwise)\n", - "#@anp.vectorize\n", - "def func_const(a,x):\n", - " return a[0]#*anp.ones(len(x))\n", - "\n", - "funcs_const = {\"c\": func_const,\"d\": func_const}" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "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.: 1.444161495357013\n", - "fit parameters [0.9997047]\n" - ] - } - ], - "source": [ - "output_const = pe.fits.least_squares(x_const,y_const,funcs_const,method='migrad')" - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "ab5c5bef", - "metadata": {}, - "outputs": [], - "source": [ - "output_const.gamma_method()" - ] - }, - { - "cell_type": "code", - "execution_count": 12, - "id": "d6abfe4f", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - " chisquare: 27.439068411783246\n", - " chisquare_by_dof: 1.444161495357013\n", - " dof: 19\n", - " fit_function: {'c': , 'd': }\n", - " fit_parameters: [Obs[0.99970(22)]]\n", - " iterations: 15\n", - " message: 'Optimization terminated successfully.'\n", - " method: 'migrad'\n", - " p_value: 0.09483431965197764" - ] - }, - "execution_count": 12, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "output_const" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "50e3de50", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Fit with 1 parameter\n", - "Method: migrad\n", - "Optimization terminated successfully.\n", - "chisquare/d.o.f.: 1.444161495357013\n", - "fit parameters [0.9997047]\n" - ] - } - ], - "source": [ - "output_const = pe.fits.least_squares(x_const,y_const,funcs_const,method='migrad')" - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "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": 15, - "id": "57d65824", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[Obs[1.0101(80)], Obs[0.9908(97)], Obs[0.99919(32)], Obs[0.9962(64)], Obs[0.965(17)], Obs[1.004(42)], Obs[1.0094(82)], Obs[1.004(13)], Obs[0.9974(31)], Obs[0.954(34)], Obs[1.004(16)], Obs[1.058(37)], Obs[0.9893(84)], Obs[0.9895(85)], Obs[0.9914(96)], Obs[1.00028(33)], Obs[1.0005(62)], Obs[0.957(32)], Obs[0.988(13)], Obs[1.0040(32)]]\n" - ] - } - ], - "source": [ - "print(y_const_ls)" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "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.: 1.4441614953561615\n" - ] - } - ], - "source": [ - "output_const2 = pe.fits.least_squares(list(np.arange(0,20)),y_const_ls, func_const)" - ] - }, - { - "cell_type": "code", - "execution_count": 17, - "id": "019583b5", - "metadata": {}, - "outputs": [], - "source": [ - "output_const2.gamma_method()" - ] - }, - { - "cell_type": "code", - "execution_count": 18, - "id": "f28a3478", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - " chisquare: 27.439068411767067\n", - " chisquare_by_dof: 1.4441614953561615\n", - " dof: 19\n", - " fit_function: \n", - " fit_parameters: [Obs[0.99970(22)]]\n", - " iterations: 7\n", - " message: '`ftol` termination condition is satisfied.'\n", - " method: 'Levenberg-Marquardt'\n", - " p_value: 0.0948343196523247" - ] - }, - "execution_count": 18, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "output_const2" - ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "466cd303", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYAAAAD5CAYAAAAuneICAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjUuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/YYfK9AAAACXBIWXMAAAsTAAALEwEAmpwYAAAnYklEQVR4nO3de3wU9b3/8deHmwliRAxqAFtoD20xCAEj2FYNRY9FT4vCzwsXFbSWg1bb09b+1HKElUqPPcfWWqVRRERaK96i0pYebYtEqUoJNqKAF4ygoREoKl64iOVz/pgJLrluspvdTeb9fDzmkZ3vd76zn53d7Gdnvt+ZMXdHRESip1OmAxARkcxQAhARiSglABGRiFICEBGJKCUAEZGIUgIQEYmoLs0tYGYLgK8BW919cAP1XwDuAoYDM9z9xri6McDNQGdgvrvfEJYPABYDhwOrgQvc/aPmYsnPz/f+/fsn8LJERKTW6tWr/+HuveuWW3PnAZjZycAHwKJGEsARwKeBs4B3ahOAmXUGXgH+FagGVgET3X2dmd0PlLn7YjO7DXje3UubexHFxcVeUVHR3GIiIhLHzFa7e3Hd8mYPAbn7k8DbTdRvdfdVwN46VSOADe5eFf66XwycaWYGjAYeDJe7myB5iIhIGrVlH0Bf4M24+eqw7HDgXXf/uE65iIikUdZ3ApvZNDOrMLOKbdu2ZTocEZEOo9lO4CRsBo6Om+8Xlm0HeppZl3AvoLa8Qe4+D5gHQR9A24UrIq21d+9eqqur2b17d6ZDibScnBz69etH165dE1q+LRPAKmBgOOJnMzABmOTubmZPAGcT9AtMAR5twzhEpI1VV1dzyCGH0L9/f4JuPkk3d2f79u1UV1czYMCAhNokMgz0XmAUkG9m1cAsoGv4hLeZ2VFABZAH7DOz/wCOcff3zOxy4DGCYaAL3H1tuNqrgMVmdj3wN+DOxF+miGSb3bt368s/w8yMww8/nJYcKm82Abj7xGbq3yI4jNNQ3VJgaQPlVQSjhESkg9CXf+a19D3I+k5gERFpG0oAIlkqFothZvWmWCyW6dAio0ePHg2Wz5w5kz/96U8peY5Ro0bR0AmuTz31FIWFhRQVFbF582bOPvtsACorK1m6tN6BlVZRAhDJUrFYDHenpKSEkpIS3B13VwLIArNnz+bUU09t0+e45557uOaaa6isrKRv3748+GBw7qwSgIhIHYsWLWLIkCEMHTqUCy64AICNGzcyevRohgwZwimnnMIbb7wBwNSpU7n00ks54YQT+MxnPsPy5cu5+OKLGTRoEFOnTj1gvd/97ncpLCzklFNO2d/BOnXq1P1fyP3792fWrFkMHz6cY489lpdeegmADz/8kIsvvpgRI0YwbNgwHn00GOy4a9cuJkyYwKBBgxg3bhy7du2q91rmz5/P/fffz7XXXsvkyZPZuHEjgwcP5qOPPmLmzJncd999FBUVcd999yW1zdpyGKiIRNDzW3ewY/fHzS/YAofmdGHoEYc2Wr927Vquv/56nn76afLz83n77eDqNVdccQVTpkxhypQpLFiwgG9/+9s88sgjALzzzjs888wzLFmyhLFjx/KXv/yF+fPnc/zxx1NZWUlRUREffvghxcXF3HTTTcyePZvrrruOW2+9td7z5+fn89xzz/HLX/6SG2+8kfnz5zNnzhxGjx7NggULePfddxkxYgSnnnoqt99+O927d2f9+vWsWbOG4cOH11vfJZdcwooVK/ja177G2WefzcaNGwHo1q0bs2fPpqKiosE4Wkp7ACLS7i1btoxzzjmH/Px8AHr16gXAM888w6RJkwC44IILWLFixf42X//61zEzjj32WI488kiOPfZYOnXqRGFh4f4v3E6dOnHeeecBcP755x/QPt748eMBOO644/a3ffzxx7nhhhsoKipi1KhR7N69mzfeeIMnn3yS888/H4AhQ4YwZMiQ1G6MFtAegIikVFO/1LPJQQcdBARf8rWPa+c//rjhPZjGhlnWtu/cufP+tu7OQw89xOc///lUhp1S2gMQkXZv9OjRPPDAA2zfvh1g/yGgL33pSyxevBgIOlVPOumkFq133759+4/1/+Y3v+HEE09MuO1Xv/pVbrnlFmovuf+3v/0NgJNPPpnf/OY3ALz44ousWbOmRTEdcsghvP/++y1q0xglABFp9woLC5kxYwYlJSUMHTqU733vewDccsst3HXXXQwZMoRf/epX3HzzzS1a78EHH8xf//pXBg8ezLJly5g5c2bCba+99lr27t3LkCFDKCws5NprrwXg0ksv5YMPPmDQoEHMnDmT4447rkUxfeUrX2HdunUp6QRu9oYw2UQ3hJEoGjVqFADLly/PaBxNWb9+PYMGDcp0GELD70WrbwgjIiIdkxKAiEhE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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()" - ] } ], "metadata": { @@ -388,6 +122,11 @@ "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.3" + }, + "vscode": { + "interpreter": { + "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6" + } } }, "nbformat": 4, diff --git a/pyerrors/fits.py b/pyerrors/fits.py index f5f0850e..a82a947c 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -140,7 +140,9 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): can be used to choose an alternative method for the minimization of chisquare. The possible methods are the ones which can be used for scipy.optimize.minimize and migrad of iminuit. If no method is specified, Levenberg-Marquard is used. - Reliable alternatives are migrad (default for combined fit), Powell and Nelder-Mead. + Reliable alternatives are migrad, Powell and Nelder-Mead. + tol: float, optional + can only be used for combined fits and methods other than Levenberg-Marquard correlated_fit : bool If True, use the full inverse covariance matrix in the definition of the chisquare cost function. For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. @@ -706,6 +708,9 @@ def _combined_fit(x, y, func, silent=False, **kwargs): x_all = np.concatenate([np.array(o) for o in x.values()]) y_all = np.concatenate([np.array(o) for o in y.values()]) + y_f = [o.value for o in y_all] + dy_f = [o.dvalue for o in y_all] + if len(x_all.shape) > 2: raise Exception('Unknown format for x values') @@ -740,10 +745,6 @@ def _combined_fit(x, y, func, silent=False, **kwargs): else: x0 = [0.1] * n_parms - output.method = kwargs.get('method', 'migrad') - if not silent: - print('Method:', output.method) - def chisqfunc(p): chisq = 0.0 for key in func.keys(): @@ -756,27 +757,46 @@ def _combined_fit(x, y, func, silent=False, **kwargs): chisq += anp.sum((y_f - model) @ C_inv @ (y_f - model)) return chisq - 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 + output.method = kwargs.get('method', 'Levenberg-Marquardt') + if not silent: + print('Method:', output.method) + + if output.method != 'Levenberg-Marquardt': + 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 + + else: + def chisqfunc_residuals(p): + model = np.concatenate([np.array(func[key](p, np.asarray(x[key]))) for key in func.keys()]) + chisq = ((y_f - model) / dy_f) + return chisq + if 'tol' in kwargs: + print('tol cannot be set for Levenberg-Marquardt') + fit_result = scipy.optimize.least_squares(chisqfunc_residuals, x0, method='lm', ftol=1e-15, gtol=1e-15, xtol=1e-15) + + chisquare = np.sum(fit_result.fun ** 2) + assert np.isclose(chisquare, chisqfunc(fit_result.x), atol=1e-14) + output.iterations = fit_result.nfev - 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.chisquare = chisquare 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) @@ -815,8 +835,6 @@ def _combined_fit(x, y, func, silent=False, **kwargs): 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.') @@ -842,7 +860,7 @@ def _combined_fit(x, y, func, silent=False, **kwargs): 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 + output.chisquare_by_expected_chisquare = output.chisquare / expected_chisquare if not silent: print('chisquare/expected_chisquare:', output.chisquare_by_expected_chisquare) diff --git a/tests/fits_test.py b/tests/fits_test.py index 9cf7b23a..8bcca5f4 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -625,6 +625,32 @@ def test_combined_fit_list_v_array(): assert (res[0][1] - res[1][1]).is_zero(atol=1e-8) +def test_combined_fit_vs_standard_fit(): + + x_const = {'a':[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], 'b':np.arange(10, 20)} + y_const = {'a':[pe.Obs([np.random.normal(1, val, 1000)], ['ensemble1']) + for val in [0.25, 0.3, 0.01, 0.2, 0.5, 1.3, 0.26, 0.4, 0.1, 1.0]], + 'b':[pe.Obs([np.random.normal(1, val, 1000)], ['ensemble1']) + for val in [0.5, 1.12, 0.26, 0.25, 0.3, 0.01, 0.2, 1.0, 0.38, 0.1]]} + for key in y_const.keys(): + [item.gamma_method() for item in y_const[key]] + y_const_ls = np.concatenate([np.array(o) for o in y_const.values()]) + x_const_ls = np.arange(0, 20) + + def func_const(a,x): + return 0 * x + a[0] + + funcs_const = {"a": func_const,"b": func_const} + for method_kw in ['Levenberg-Marquardt', 'migrad', 'Powell', 'Nelder-Mead']: + res = [] + res.append(pe.fits.least_squares(x_const, y_const, funcs_const, method = method_kw, expected_chisquare=True)) + res.append(pe.fits.least_squares(x_const_ls, y_const_ls, func_const, method = method_kw, expected_chisquare=True)) + [item.gamma_method for item in res] + assert np.isclose(0.0, (res[0].chisquare_by_dof - res[1].chisquare_by_dof), 1e-14, 1e-8) + assert np.isclose(0.0, (res[0].chisquare_by_expected_chisquare - res[1].chisquare_by_expected_chisquare), 1e-14, 1e-8) + assert np.isclose(0.0, (res[0].p_value - res[1].p_value), 1e-14, 1e-8) + assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) + def fit_general(x, y, func, silent=False, **kwargs): """Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. From 4b3fac8ee4cf187ca1513d05222c3b7093f0bacd Mon Sep 17 00:00:00 2001 From: ppetrak Date: Tue, 20 Dec 2022 18:26:15 +0100 Subject: [PATCH 09/13] tests: invalid fit functions, num_grad vs auto_grad --- tests/fits_test.py | 118 ++++++++++++++++++++++++++++++++++++++------- 1 file changed, 100 insertions(+), 18 deletions(-) diff --git a/tests/fits_test.py b/tests/fits_test.py index 8bcca5f4..de6068a3 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -108,24 +108,6 @@ def test_prior_fit_num_grad(): auto = pe.fits.least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False, piors=y[:2]) -def test_least_squares_num_grad(): - x = [] - y = [] - for i in range(2, 5): - x.append(i * 0.01) - y.append(pe.pseudo_Obs(i * 0.01, 0.0001, "ens")) - - num = pe.fits.least_squares(x, y, lambda a, x: np.exp(a[0] * x) + a[1], num_grad=True) - auto = pe.fits.least_squares(x, y, lambda a, x: anp.exp(a[0] * x) + a[1], num_grad=False) - - assert(num[0] == auto[0]) - assert(num[1] == auto[1]) - - - assert(num[0] == auto[0]) - assert(num[1] == auto[1]) - - def test_total_least_squares_num_grad(): x = [] y = [] @@ -651,6 +633,106 @@ def test_combined_fit_vs_standard_fit(): assert np.isclose(0.0, (res[0].p_value - res[1].p_value), 1e-14, 1e-8) assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) + +def test_combined_fit_invalid_fit_functions(): + def func1(a, x): + return a[0] + a[1] * x + a[2] * anp.sinh(x) + a[199] + + def func2(a, x, y): + return a[0] + a[1] * x + + def func3(x): + return x + + def func_valid(a,x): + return a[0] + a[1] * x + + xvals =[] + yvals =[] + err = 0.1 + + for x in range(1, 8, 2): + xvals.append(x) + yvals.append(pe.pseudo_Obs(x + np.random.normal(0.0, err), err, 'test1') + pe.pseudo_Obs(0, err / 100, 'test2', samples=87)) + [o.gamma_method() for o in yvals] + for func in [func1, func2, func3]: + with pytest.raises(Exception): + pe.least_squares({'a':xvals}, {'a':yvals}, {'a':func}) + with pytest.raises(Exception): + pe.least_squares({'a':xvals, 'b':xvals}, {'a':yvals, 'b':yvals}, {'a':func, 'b':func_valid}) + with pytest.raises(Exception): + pe.least_squares({'a':xvals, 'b':xvals}, {'a':yvals, 'b':yvals}, {'a':func_valid, 'b':func}) + + +def test_combined_fit_no_autograd(): + + def func_exp1(x): + return 0.3*np.exp(0.5*x) + + def func_exp2(x): + return 0.3*np.exp(0.8*x) + + xvals_b = np.arange(0,6) + xvals_a = np.arange(0,8) + + def func_a(a,x): + return a[0]*np.exp(a[1]*x) + + def func_b(a,x): + return a[0]*np.exp(a[2]*x) + + funcs = {'a':func_a, 'b':func_b} + xs = {'a':xvals_a, 'b':xvals_b} + ys = {'a':[pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)], + 'b':[pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)]} + + for key in funcs.keys(): + [item.gamma_method() for item in ys[key]] + + with pytest.raises(Exception): + pe.least_squares(xs, ys, funcs) + + pe.least_squares(xs, ys, funcs, num_grad=True) + + +def test_combined_fit_num_grad(): + def func_exp1(x): + return 0.3*np.exp(0.5*x) + + def func_exp2(x): + return 0.3*np.exp(0.8*x) + + xvals_b = np.arange(0,6) + xvals_a = np.arange(0,8) + + def func_num_a(a,x): + return a[0]*np.exp(a[1]*x) + + def func_num_b(a,x): + return a[0]*np.exp(a[2]*x) + + def func_auto_a(a,x): + return a[0]*anp.exp(a[1]*x) + + def func_auto_b(a,x): + return a[0]*anp.exp(a[2]*x) + + funcs_num = {'a':func_num_a, 'b':func_num_b} + funcs_auto = {'a':func_auto_a, 'b':func_auto_b} + xs = {'a':xvals_a, 'b':xvals_b} + ys = {'a':[pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)], + 'b':[pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)]} + + for key in funcs_num.keys(): + [item.gamma_method() for item in ys[key]] + + num = pe.fits.least_squares(xs, ys, funcs_num, num_grad=True) + auto = pe.fits.least_squares(xs, ys, funcs_auto, num_grad=False) + + assert(num[0] == auto[0]) + assert(num[1] == auto[1]) + + def fit_general(x, y, func, silent=False, **kwargs): """Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. From 32dcb7438c77662b6743f7a84064af9a5308ce30 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Wed, 21 Dec 2022 12:58:00 +0100 Subject: [PATCH 10/13] build: combined fit example notebook renamed. --- .../{example_combined_fit.ipynb => 08_combined_fit_example.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{example_combined_fit.ipynb => 08_combined_fit_example.ipynb} (100%) diff --git a/examples/example_combined_fit.ipynb b/examples/08_combined_fit_example.ipynb similarity index 100% rename from examples/example_combined_fit.ipynb rename to examples/08_combined_fit_example.ipynb From 80371a0898d9ec8ed297e7a04bc90a18ddd09396 Mon Sep 17 00:00:00 2001 From: ppetrak Date: Mon, 30 Jan 2023 14:26:47 +0100 Subject: [PATCH 11/13] fix/tests: Combined fit now also works when the keys of the x,y & func input dictionaries are not in the same order, build: improvements in performance --- pyerrors/fits.py | 62 +++++++++++++-------------- tests/fits_test.py | 102 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 129 insertions(+), 35 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index a82a947c..f76532e6 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -102,9 +102,6 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): 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 : dict dict of lists. y : dict @@ -142,7 +139,10 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): migrad of iminuit. If no method is specified, Levenberg-Marquard is used. Reliable alternatives are migrad, Powell and Nelder-Mead. tol: float, optional - can only be used for combined fits and methods other than Levenberg-Marquard + can be used (only for combined fits and methods other than Levenberg-Marquard) to set the tolerance for convergence + to a different value to either speed up convergence at the cost of a larger error on the fitted parameters (and possibly + invalid estimates for parameter uncertainties) or smaller values to get more accurate parameter values + The stopping criterion depends on the method, e.g. migrad: edm_max = 0.002 * tol * errordef (EDM criterion: edm < edm_max) correlated_fit : bool If True, use the full inverse covariance matrix in the definition of the chisquare cost function. For details about how the covariance matrix is estimated see `pyerrors.obs.covariance`. @@ -705,8 +705,19 @@ def _combined_fit(x, y, func, silent=False, **kwargs): jacobian = auto_jacobian hessian = auto_hessian - x_all = np.concatenate([np.array(o) for o in x.values()]) - y_all = np.concatenate([np.array(o) for o in y.values()]) + key_ls = sorted(list(x.keys())) + + if sorted(list(y.keys())) != key_ls: + raise Exception('x and y dictionaries do not contain the same keys.') + + if sorted(list(func.keys())) != key_ls: + raise Exception('x and func dictionaries do not contain the same keys.') + + if sorted(list(func.keys())) != sorted(list(y.keys())): + raise Exception('y and func dictionaries do not contain the same keys.') + + x_all = np.concatenate([np.array(x[key]) for key in key_ls]) + y_all = np.concatenate([np.array(y[key]) for key in key_ls]) y_f = [o.value for o in y_all] dy_f = [o.dvalue for o in y_all] @@ -716,12 +727,12 @@ def _combined_fit(x, y, func, silent=False, **kwargs): # number of fit parameters n_parms_ls = [] - for key in func.keys(): + for key in key_ls: 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): + for i in range(100): try: func[key](np.arange(i), x_all.T[0]) except TypeError: @@ -746,15 +757,9 @@ def _combined_fit(x, y, func, silent=False, **kwargs): 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)) + func_list = np.concatenate([[func[k]] * len(x[k]) for k in key_ls]) + model = anp.array([func_list[i](p, x_all[i]) for i in range(len(x_all))]) + chisq = anp.sum(((y_f - model) / dy_f) ** 2) return chisq output.method = kwargs.get('method', 'Levenberg-Marquardt') @@ -763,7 +768,7 @@ def _combined_fit(x, y, func, silent=False, **kwargs): if output.method != 'Levenberg-Marquardt': if output.method == 'migrad': - tolerance = 1e-4 + tolerance = 1e-1 # default value set by iminuit if 'tol' in kwargs: tolerance = kwargs.get('tol') fit_result = iminuit.minimize(chisqfunc, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef @@ -779,7 +784,7 @@ def _combined_fit(x, y, func, silent=False, **kwargs): else: def chisqfunc_residuals(p): - model = np.concatenate([np.array(func[key](p, np.asarray(x[key]))) for key in func.keys()]) + model = np.concatenate([np.array(func[key](p, np.asarray(x[key]))) for key in key_ls]) chisq = ((y_f - model) / dy_f) return chisq if 'tol' in kwargs: @@ -809,25 +814,14 @@ def _combined_fit(x, y, func, silent=False, **kwargs): 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] - 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) + func_list = np.concatenate([[func[k]] * len(x[k]) for k in key_ls]) + model = anp.array([func_list[i](d[:n_parms], x_all[i]) for i in range(len(x_all))]) + chisq = anp.sum(((d[n_parms:] - model) / dy_f) ** 2) return chisq def prepare_hat_matrix(): hat_vector = [] - for key in func.keys(): + for key in key_ls: x_array = np.asarray(x[key]) if (len(x_array) != 0): hat_vector.append(jacobian(func[key])(fit_result.x, x_array)) diff --git a/tests/fits_test.py b/tests/fits_test.py index de6068a3..8a9a4f33 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -618,7 +618,7 @@ def test_combined_fit_vs_standard_fit(): [item.gamma_method() for item in y_const[key]] y_const_ls = np.concatenate([np.array(o) for o in y_const.values()]) x_const_ls = np.arange(0, 20) - + def func_const(a,x): return 0 * x + a[0] @@ -633,6 +633,35 @@ def test_combined_fit_vs_standard_fit(): assert np.isclose(0.0, (res[0].p_value - res[1].p_value), 1e-14, 1e-8) assert (res[0][0] - res[1][0]).is_zero(atol=1e-8) +def test_combined_fit_no_autograd(): + + def func_exp1(x): + return 0.3*np.exp(0.5*x) + + def func_exp2(x): + return 0.3*np.exp(0.8*x) + + xvals_b = np.arange(0,6) + xvals_a = np.arange(0,8) + + def func_a(a,x): + return a[0]*np.exp(a[1]*x) + + def func_b(a,x): + return a[0]*np.exp(a[2]*x) + + funcs = {'a':func_a, 'b':func_b} + xs = {'a':xvals_a, 'b':xvals_b} + ys = {'a':[pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)], + 'b':[pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)]} + + for key in funcs.keys(): + [item.gamma_method() for item in ys[key]] + + with pytest.raises(Exception): + pe.least_squares(xs, ys, funcs) + + pe.least_squares(xs, ys, funcs, num_grad=True) def test_combined_fit_invalid_fit_functions(): def func1(a, x): @@ -663,6 +692,17 @@ def test_combined_fit_invalid_fit_functions(): with pytest.raises(Exception): pe.least_squares({'a':xvals, 'b':xvals}, {'a':yvals, 'b':yvals}, {'a':func_valid, 'b':func}) +def test_combined_fit_invalid_input(): + xvals =[] + yvals =[] + err = 0.1 + def func_valid(a,x): + return a[0] + a[1] * x + for x in range(1, 8, 2): + xvals.append(x) + yvals.append(pe.pseudo_Obs(x + np.random.normal(0.0, err), err, 'test1') + pe.pseudo_Obs(0, err / 100, 'test2', samples=87)) + with pytest.raises(Exception): + pe.least_squares({'a':xvals}, {'b':yvals}, {'a':func_valid}) def test_combined_fit_no_autograd(): @@ -732,6 +772,66 @@ def test_combined_fit_num_grad(): assert(num[0] == auto[0]) assert(num[1] == auto[1]) +def test_combined_fit_dictkeys_no_order(): + def func_exp1(x): + return 0.3*np.exp(0.5*x) + + def func_exp2(x): + return 0.3*np.exp(0.8*x) + + xvals_b = np.arange(0,6) + xvals_a = np.arange(0,8) + + def func_num_a(a,x): + return a[0]*np.exp(a[1]*x) + + def func_num_b(a,x): + return a[0]*np.exp(a[2]*x) + + def func_auto_a(a,x): + return a[0]*anp.exp(a[1]*x) + + def func_auto_b(a,x): + return a[0]*anp.exp(a[2]*x) + + funcs = {'a':func_auto_a, 'b':func_auto_b} + funcs_no_order = {'b':func_auto_b, 'a':func_auto_a} + xs = {'a':xvals_a, 'b':xvals_b} + xs_no_order = {'b':xvals_b, 'a':xvals_a} + yobs_a = [pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)] + yobs_b = [pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)] + ys = {'a': yobs_a, 'b': yobs_b} + ys_no_order = {'b': yobs_b, 'a': yobs_a} + + for key in funcs.keys(): + [item.gamma_method() for item in ys[key]] + [item.gamma_method() for item in ys_no_order[key]] + for method_kw in ['Levenberg-Marquardt', 'migrad', 'Powell', 'Nelder-Mead']: + order = pe.fits.least_squares(xs, ys, funcs,method = method_kw) + no_order_func = pe.fits.least_squares(xs, ys, funcs_no_order,method = method_kw) + no_order_x = pe.fits.least_squares(xs_no_order, ys, funcs,method = method_kw) + no_order_y = pe.fits.least_squares(xs, ys_no_order, funcs,method = method_kw) + no_order_func_x = pe.fits.least_squares(xs_no_order, ys, funcs_no_order,method = method_kw) + no_order_func_y = pe.fits.least_squares(xs, ys_no_order, funcs_no_order,method = method_kw) + no_order_x_y = pe.fits.least_squares(xs_no_order, ys_no_order, funcs,method = method_kw) + + assert(no_order_func[0] == order[0]) + assert(no_order_func[1] == order[1]) + + assert(no_order_x[0] == order[0]) + assert(no_order_x[1] == order[1]) + + assert(no_order_y[0] == order[0]) + assert(no_order_y[1] == order[1]) + + assert(no_order_func_x[0] == order[0]) + assert(no_order_func_x[1] == order[1]) + + assert(no_order_func_y[0] == order[0]) + assert(no_order_func_y[1] == order[1]) + + assert(no_order_x_y[0] == order[0]) + assert(no_order_x_y[1] == order[1]) def fit_general(x, y, func, silent=False, **kwargs): """Performs a non-linear fit to y = func(x) and returns a list of Obs corresponding to the fit parameters. From 59d22fceeefd3d69bb3e9f7f16e94a38ced079da Mon Sep 17 00:00:00 2001 From: ppetrak Date: Mon, 30 Jan 2023 15:16:41 +0100 Subject: [PATCH 12/13] fix: reduced the migrad solver tolerance + removed unnecessary check --- pyerrors/fits.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index f76532e6..4a2680f4 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -713,9 +713,6 @@ def _combined_fit(x, y, func, silent=False, **kwargs): if sorted(list(func.keys())) != key_ls: raise Exception('x and func dictionaries do not contain the same keys.') - if sorted(list(func.keys())) != sorted(list(y.keys())): - raise Exception('y and func dictionaries do not contain the same keys.') - x_all = np.concatenate([np.array(x[key]) for key in key_ls]) y_all = np.concatenate([np.array(y[key]) for key in key_ls]) @@ -768,7 +765,7 @@ def _combined_fit(x, y, func, silent=False, **kwargs): if output.method != 'Levenberg-Marquardt': if output.method == 'migrad': - tolerance = 1e-1 # default value set by iminuit + tolerance = 1e-4 # default value of 1e-1 set by iminuit can be problematic if 'tol' in kwargs: tolerance = kwargs.get('tol') fit_result = iminuit.minimize(chisqfunc, x0, tol=tolerance) # Stopping criterion 0.002 * tol * errordef From 088f4d1239634100bff63b87af5ae0db712696fa Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Thu, 2 Feb 2023 14:58:35 +0000 Subject: [PATCH 13/13] feat: additional check for invalid input to least_squares added. Co-authored-by: Simon Kuberski --- pyerrors/fits.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 4a2680f4..e51df9c9 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -164,7 +164,8 @@ def least_squares(x, y, func, priors=None, silent=False, **kwargs): elif (type(x) == dict and type(y) == dict and type(func) == dict): return _combined_fit(x, y, func, silent=silent, **kwargs) - + elif (type(x) == dict or type(y) == dict or type(func) == dict): + raise TypeError("All arguments have to be dictionaries in order to perform a combined fit.") else: return _standard_fit(x, y, func, silent=silent, **kwargs)