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 xy_plot = Plot(xy_fit) xy_plot.plot(fit_info=True) plt.show()
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], model_function=expected_activity_per_two_min, cost_function=XYCostFunction_NegLogLikelihood(data_point_distribution='poisson') ) xy_fit_poisson_sparse.add_parameter_constraint(name='T_12_C14', value=5730, uncertainty=40) xy_fit_poisson_sparse.do_fit() # We calculate and print out the relative error on our estimate of Delta_T from using a Gaussian approximation. Delta_T_poisson_sparse = xy_fit_poisson_sparse.parameter_values[0]
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) # Optional: Set the initial values of the remaining parameters to correspond to their constraint values # (this may help some minimization algorithms converge) fit.set_parameter_values(y_0=y_0, l=l, r=r)