def test_warning_no_fit_performed(self): _fit = XYFit(xy_data=self._ref_data) _fit.add_error('y', 0.1) _plot = Plot(fit_objects=_fit) with self.assertWarns(Warning) as w: _plot.plot() self.assertIn("Did you forget to run fit.do_fit()?", str(w.warning))
def _get_xy_fit(xy_data, minimizer, err_x=0.01, err_y=0.01): _xy_fit = XYFit(xy_data=xy_data, model_function=quadratic_model, cost_function='chi2', minimizer=minimizer) _xy_fit.add_error('x', err_x) _xy_fit.add_error('y', err_y) return _xy_fit
class TestXYPlot(unittest.TestCase): def setUp(self): self._ref_data = [[1, 2, 3], [1, 2, 3]] self._ref_dataset_label = 'My Dataset' self._ref_x_label = '$U$ [V]' self._ref_y_label = '$I$ [A]' self._ref_model_label = 'My Model' self._ref_error_label = self._ref_model_label + r' $\pm 1\sigma$' self.fit = XYFit(xy_data=self._ref_data) self.fit.add_error('y', 0.1) self.fit.do_fit() self.fit.data_container.label = self._ref_dataset_label self.fit.data_container.axis_labels = (self._ref_x_label, self._ref_y_label) self.fit.model_label = self._ref_model_label self.plot = Plot(self.fit) def test_labels_after_plotting(self): self.plot.plot() self.assertEqual(self.plot.axes[0]['main'].get_xlabel(), self._ref_x_label) self.assertEqual(self.plot.axes[0]['main'].get_ylabel(), self._ref_y_label) _, labels = self.plot.axes[0]['main'].get_legend_handles_labels() self.assertIn(self._ref_dataset_label, labels) self.assertIn(self._ref_model_label, labels) self.assertIn(self._ref_error_label, labels) def test_remove_labels(self): self.fit.data_container.label = '__del__' self.fit.data_container.axis_labels = ('__del__', '__del__') self.fit.model_label = '__del__' self.plot.plot() _, labels = self.plot.axes[0]['main'].get_legend_handles_labels() self.assertEqual(self.plot.axes[0]['main'].get_xlabel(), '') self.assertEqual(self.plot.axes[0]['main'].get_ylabel(), '') self.assertTrue(len(labels) == 0) def test_warning_no_fit_performed(self): _fit = XYFit(xy_data=self._ref_data) _fit.add_error('y', 0.1) _plot = Plot(fit_objects=_fit) with self.assertWarns(Warning) as w: _plot.plot() self.assertIn("Did you forget to run fit.do_fit()?", str(w.warning)) def test_plot_with_asymmetric_errors(self): self.plot.plot(asymmetric_parameter_errors=True)
def setUp(self): self._ref_data = [[1, 2, 3], [1, 2, 3]] self._ref_dataset_label = 'My Dataset' self._ref_x_label = '$U$ [V]' self._ref_y_label = '$I$ [A]' self._ref_model_label = 'My Model' self._ref_error_label = self._ref_model_label + r' $\pm 1\sigma$' self.fit = XYFit(xy_data=self._ref_data) self.fit.add_error('y', 0.1) self.fit.do_fit() self.fit.data_container.label = self._ref_dataset_label self.fit.data_container.axis_labels = (self._ref_x_label, self._ref_y_label) self.fit.model_label = self._ref_model_label self.plot = Plot(self.fit)
def _get_xy_fit(self, xy_data): _err_x = dict(axis="x", err_val=0.01, name="x") if self._x_error is not None else None _err_y = dict(axis="y", err_val=0.1, relative=True, reference="model", name="y") \ if self._relative_model_error else dict(axis="y", err_val=0.01, name="y") if self._x_error is None: _xy_fit = XYFit(xy_data=xy_data, model_function=quadratic_model, cost_function='chi2', minimizer=self._minimizer) else: _xy_fit = XYFit(xy_data=xy_data, model_function=quadratic_model, cost_function='chi2', minimizer=self._minimizer, dynamic_error_algorithm=self._x_error) if _err_x is not None: _xy_fit.add_error(**_err_x) _xy_fit.add_error(**_err_y) return _xy_fit
def _get_multifit( self, hist_fit=True, xy_fit_1=True, indexed_fit=True, xy_fit_2=True, reverse=False): _fits = [] if hist_fit: _hist_container = HistContainer( n_bins=10, bin_range=(-5, 5), fill_data=self._hist_data) _hist_fit = HistFit(_hist_container) _fits.append(_hist_fit) if xy_fit_1: _xy_fit_1 = XYFit(xy_data=[self._x_data, self._y_data_1]) _xy_fit_1.add_error("y", 1.0) _fits.append(_xy_fit_1) if indexed_fit: _indexed_fit = IndexedFit( self._indexed_data, TestSharedErrorLogic.indexed_model_function) _indexed_fit.add_error(1.0) _fits.append(_indexed_fit) if xy_fit_2: _xy_fit_2 = XYFit(xy_data=[self._x_data, self._y_data_2]) _xy_fit_2.add_error("y", 1.0) _fits.append(_xy_fit_2) if reverse: _fits = reversed(_fits) return MultiFit(_fits)
return x / (R0 * (1.0 + _temperature * alph)) # -- Next, read the data from an external file # load all data into numpy arrays U, I, T = np.loadtxt('OhmsLawExperiment.dat', unpack=True) # data sigU, sigI, sigT = 0.1, 0.1, 0.1 # uncertainties T0 = 273.15 # 0 degrees C as absolute Temperature (in Kelvin) T -= T0 # Measurements are in Kelvin, convert to °C # -- Finally, go through the fitting procedure # Step 1: construct the singular fit objects fit_1 = XYFit(xy_data=[U, T], model_function=empirical_T_U_model) fit_1.add_error(axis='y', err_val=sigT) # declare errors on T fit_2 = XYFit(xy_data=[U, I], model_function=I_U_model) fit_2.add_error(axis='y', err_val=sigI) # declare errors on I # Step 2: construct a MultiFit object multi_fit = MultiFit(fit_list=[fit_1, fit_2], minimizer='iminuit') # Step 3: Add a shared error error for the x axis. multi_fit.add_error(axis='x', err_val=sigU, fits='all') # (Optional): assign names for models and parameters multi_fit.assign_parameter_latex_names(x='U', p2='p_2', p1='p_1',
# Our first model is a simple linear function return a * x + b def exponential_model(x, A0=1., x0=5.): # Our second model is a simple exponential function # The kwargs in the function header specify parameter defaults. return A0 * np.exp(x / x0) # Read in the measurement data from a yaml file. # For more information on reading/writing kafe2 objects from/to files see TODO xy_data = XYContainer.from_file("data.yml") # Create 2 XYFit objects with the same data but with different model functions linear_fit = XYFit(xy_data=xy_data, model_function=linear_model) exponential_fit = XYFit(xy_data=xy_data, model_function=exponential_model) # Optional: Assign LaTeX strings to parameters and model functions. linear_fit.assign_parameter_latex_names(a='a', b='b') linear_fit.assign_model_function_latex_expression("{a}{x} + {b}") exponential_fit.assign_parameter_latex_names(A0='A_0', x0='x_0') exponential_fit.assign_model_function_latex_expression("{A0} e^{{{x}/{x0}}}") # Perform the fits. linear_fit.do_fit() exponential_fit.do_fit() # Optional: Print out a report on the result of each fit. linear_fit.report() exponential_fit.report()
difference in the x direction would translate to the y direction. Unfortunately this approach is not perfect though. Since we're extrapolating the derivative at the x data values, we will only receive valid results if the derivative doesn't change too much at the scale of the x error. Also, since the effective y error has now become dependent on the derivative of the model function it will vary depending on our choice of model parameters. This distorts our likelihood function - the minimum of a chi2 cost function will no longer be shaped like a parabola (with a model parameter on the x axis and chi2 on the y axis). The effects of this deformation are explained in the non_linear_fit.py example. """ import matplotlib.pyplot as plt from kafe2 import XYContainer, XYFit, Plot from kafe2.fit.tools import ContoursProfiler # Construct a fit with data loaded from a yaml file. The model function is the default of f(x) = a * x + b nonlinear_fit = XYFit(xy_data=XYContainer.from_file('x_errors.yml')) # The x errors are much bigger than the y errors. This will cause a distortion of the likelihood function. nonlinear_fit.add_error('x', 1.0) nonlinear_fit.add_error('y', 0.1) # Perform the fit. nonlinear_fit.do_fit() # Optional: Print out a report on the fit results on the console. # Note the asymmetric_parameter_errors flag nonlinear_fit.report(asymmetric_parameter_errors=True) # Optional: Create a plot of the fit results using Plot. # Note the asymmetric_parameter_errors flag plot = Plot(nonlinear_fit)
# x = years of death in the ancient calendar # Delta_t = difference between the ancient and the modern calendar in years # T_12_C14 = half life of carbon-14 in years, read as T 1/2 carbon-14 def expected_activity_per_day(x, Delta_t=5000, T_12_C14=5730): # activity = number of radioactive decays expected_initial_activity_per_day = expected_initial_num_c14_atoms * np.log(2) / (T_12_C14 * days_per_year) total_years_since_death = Delta_t + current_year - x return expected_initial_activity_per_day * np.exp(-np.log(2) * total_years_since_death / T_12_C14) # This is where we tell the fit to assume a poisson distribution for our data. xy_fit = XYFit( xy_data=[years_of_death, measured_c14_activity], model_function=expected_activity_per_day, cost_function=XYCostFunction_NegLogLikelihood(data_point_distribution='poisson') ) # The half life of carbon-14 is only known with a precision of +-40 years xy_fit.add_parameter_constraint(name='T_12_C14', value=5730, uncertainty=40) # Perform the fit # Note that since for a Poisson distribution the data error is directly linked to the mean. # Because of this fits can be performed without explicitly adding data errors. xy_fit.do_fit() # Optional: print out a report on the fit results on the console xy_fit.report() # Optional: create a plot of the fit results using Plot
from kafe2 import XYFit, Plot, ContoursProfiler def exponential(x, A0=1, tau=1): return A0 * np.exp(-x/tau) # define the data as simple Python lists x = [8.018943e-01, 1.839664e+00, 1.941974e+00, 1.276013e+00, 2.839654e+00, 3.488302e+00, 3.775855e+00, 4.555187e+00, 4.477186e+00, 5.376026e+00] xerr = 3.000000e-01 y = [2.650644e-01, 1.472682e-01, 8.077234e-02, 1.850181e-01, 5.326301e-02, 1.984233e-02, 1.866309e-02, 1.230001e-02, 9.694612e-03, 2.412357e-03] yerr = [1.060258e-01, 5.890727e-02, 3.230893e-02, 7.400725e-02, 2.130520e-02, 7.936930e-03, 7.465238e-03, 4.920005e-03, 3.877845e-03, 9.649427e-04] # create a fit object from the data arrays fit = XYFit(xy_data=[x, y], model_function=exponential) fit.add_error(axis='x', err_val=xerr) # add the x-error to the fit fit.add_error(axis='y', err_val=yerr) # add the y-errors to the fit fit.do_fit() # perform the fit fit.report(asymmetric_parameter_errors=True) # print a report with asymmetric uncertainties # Optional: create a plot plot = Plot(fit) plot.plot(asymmetric_parameter_errors=True, ratio=True) # add the ratio data/function and asymmetric errors # Optional: create the contours profiler cpf = ContoursProfiler(fit) cpf.plot_profiles_contours_matrix() # plot the contour profile matrix for all parameters plt.show()
# Model function for a pendulum as a one-dimensional, damped harmonic oscillator with zero initial speed # x = time, y_0 = initial_amplitude, l = length of the string, # r = radius of the steel ball, g = gravitational acceleration, c = damping coefficient def damped_harmonic_oscillator(x, y_0, l, r, g, c): l_total = l + r # effective length of the pendulum = length of the string + radius of the steel ball omega_0 = np.sqrt(g / l_total) # phase speed of an undamped pendulum omega_d = np.sqrt(omega_0**2 - c**2) # phase speed of a damped pendulum return y_0 * np.exp( -c * x) * (np.cos(omega_d * x) + c / omega_d * np.sin(omega_d * x)) # Load data from yaml, contains data and errors data = XYContainer.from_file(filename='data.yml') # Create fit object from data and model function fit = XYFit(xy_data=data, model_function=damped_harmonic_oscillator) # Constrain model parameters to measurements fit.add_parameter_constraint(name='l', value=l, uncertainty=delta_l) fit.add_parameter_constraint(name='r', value=r, uncertainty=delta_r) fit.add_parameter_constraint(name='y_0', value=y_0, uncertainty=delta_y_0, relative=True) # Because the model function is oscillating the fit needs to be initialized with near guesses for unconstrained # parameters in order to converge g_initial = 9.81 # initial guess for g c_initial = 0.01 # initial guess for c fit.set_parameter_values(g=g_initial, c=c_initial)
def construct_multi_fit(shared_x_error): fit_1 = XYFit(xy_data=[U, T], model_function=empirical_T_U_model) fit_1.add_error(axis='y', err_val=sigT) # declare errors on T fit_2 = XYFit(xy_data=[U, I], model_function=I_U_model) fit_2.add_error(axis='y', err_val=sigI) # declare errors on I multi_fit = MultiFit(fit_list=[fit_1, fit_2], minimizer='iminuit') if shared_x_error: multi_fit.add_error(axis='x', err_val=sigU, fits='all') else: fit_1.add_error(axis='x', err_val=sigU) fit_2.add_error(axis='x', err_val=sigU) return multi_fit
# x = years of death in the ancient calendar # Delta_t = difference between the ancient and the modern calendar in years # T_12_C14 = half life of carbon-14 in years, read as T 1/2 carbon-14 def expected_activity_per_day(x, Delta_t=5000, T_12_C14=5730): # activity = number of radioactive decays expected_initial_activity_per_day = expected_initial_num_c14_atoms * np.log( 2) / (T_12_C14 * days_per_year) total_years_since_death = Delta_t + current_year - x return expected_initial_activity_per_day * np.exp( -np.log(2) * total_years_since_death / T_12_C14) # This is where we tell the fit to assume a poisson distribution for our data. xy_fit = XYFit(xy_data=[years_of_death, measured_c14_activity], model_function=expected_activity_per_day, cost_function="nll-poisson") # The half life of carbon-14 is only known with a precision of +-40 years xy_fit.add_parameter_constraint(name='T_12_C14', value=5730, uncertainty=40) # Perform the fit # Note that since for a Poisson distribution the data error is directly linked to the mean. # Because of this fits can be performed without explicitly adding data errors. xy_fit.do_fit() # Optional: print out a report on the fit results on the console xy_fit.report() # Optional: create a plot of the fit results using Plot xy_plot = Plot(xy_fit)
return x / (R0 * (1.0 + _temperature * alph)) # -- Next, read the data from an external file # load all data into numpy arrays U, I, T = np.loadtxt('OhmsLawExperiment.dat', unpack=True) # data sigU, sigI, sigT = 0.1, 0.1, 0.1 # uncertainties T0 = 273.15 # 0 degrees C as absolute Temperature (in Kelvin) T -= T0 # Measurements are in Kelvin, convert to °C # -- Finally, go through the fitting procedure # Step 1: perform an "auxiliary" fit to the T(U) data auxiliary_fit = XYFit(xy_data=[U, T], model_function=empirical_T_U_model) # (Optional): Assign names for models and parameters auxiliary_fit.assign_parameter_latex_names(x='U', p2='p_2', p1='p_1', p0='p_0') auxiliary_fit.assign_model_function_expression('{1}*{x}^2 + {2}*{x} + {3}') auxiliary_fit.assign_model_function_latex_expression( r'{1}\,{x}^2 + {2}\,{x} + {3}') # declare errors on U auxiliary_fit.add_error(axis='x', err_val=sigU) # declare errors on T auxiliary_fit.add_error(axis='y', err_val=sigT) # perform the auxiliary fit auxiliary_fit.do_fit()
# x = years of death in the ancient calendar # Delta_t = difference between the ancient and the modern calendar in years # T_12_C14 = half life of carbon-14 in years, read as T 1/2 carbon-14 def expected_activity_per_two_min(x, Delta_t=5000, T_12_C14=5730): # activity = number of radioactive decays expected_initial_activity_per_day = expected_initial_num_c14_atoms * np.log(2) / (T_12_C14 * two_min_per_year) total_years_since_death = Delta_t + current_year - x return expected_initial_activity_per_day * np.exp(-np.log(2) * total_years_since_death / T_12_C14) # We define a fit as per normal, with xy data and a model function. # Since no cost function is provided it will use the default (chi2 -> Gaussian data errors). xy_fit_gaussian_sparse = XYFit( xy_data=[years_of_death, measured_c14_activity_sparse], model_function=expected_activity_per_two_min ) # We use the Gaussian approximation of the Poisson distribution sqrt(y) for our y data error # Because we only have about 10 events per measurement this approximation will be bad. xy_fit_gaussian_sparse.add_error(axis='y', err_val=np.sqrt(measured_c14_activity_sparse)) # The half life of carbon-14 is only known with a precision of +-40 years xy_fit_gaussian_sparse.add_parameter_constraint(name='T_12_C14', value=5730, uncertainty=40) # Perform the fit xy_fit_gaussian_sparse.do_fit() # We create another fit, this time with a Poisson distribution, to compare the Gaussian approximation against. xy_fit_poisson_sparse = XYFit( xy_data=[years_of_death, measured_c14_activity_sparse],
def expected_activity_per_day(x, Delta_t=5000, T_12_C14=5730): # activity = number of radioactive decays expected_initial_activity_per_day = expected_initial_num_c14_atoms * np.log( 2) / (T_12_C14 * days_per_year) total_years_since_death = Delta_t + current_year - x return expected_initial_activity_per_day * np.exp( -np.log(2) * total_years_since_death / T_12_C14) ######################################### # This is where things start to change. # ######################################### # We define a fit as per normal, with xy data and a model function. # Since no cost function is provided it will use the default (chi2 -> Gaussian data errors). xy_fit = XYFit(xy_data=[years_of_death, measured_c14_activity], model_function=expected_activity_per_day) # We use the Gaussian approximation of the Poisson distribution sqrt(y) for our y data error xy_fit.add_error(axis='y', err_val=np.sqrt(measured_c14_activity)) # The half life of carbon-14 is only known with a precision of +-40 years xy_fit.add_parameter_constraint(name='T_12_C14', value=5730, uncertainty=40) # Perform the fit xy_fit.do_fit() # Optional: print out a report on the fit results on the console xy_fit.report() # Optional: create a plot of the fit results using Plot xy_plot = Plot(xy_fit)
from kafe2 import XYContainer, XYFit, Plot import matplotlib.pyplot as plt # Create an XYContainer object to hold the xy data for the fit. xy_data = XYContainer(x_data=[1.0, 2.0, 3.0, 4.0], y_data=[2.3, 4.2, 7.5, 9.4]) # x_data and y_data are combined depending on their order. # The above translates to the points (1.0, 2.3), (2.0, 4.2), and (4.0, 9.4). # Important: Specify uncertainties for the data. xy_data.add_error(axis='x', err_val=0.1) xy_data.add_error(axis='y', err_val=0.4) # Create an XYFit object from the xy data container. # By default, a linear function f=a*x+b will be used as the model function. line_fit = XYFit(xy_data=xy_data) # Perform the fit: Find values for a and b that minimize the # difference between the model function and the data. line_fit.do_fit() # This will throw an exception if no errors were specified. # Optional: Print out a report on the fit results on the console. line_fit.report() # Optional: Create a plot of the fit results using Plot. plot = Plot(fit_objects=line_fit) # Create a kafe2 plot object. plot.plot() # Do the plot. # Show the fit result. plt.show()