PtolemyGUI/Cleopatra/FitExData.py

178 lines
6.2 KiB
Python

#!/usr/bin/python3
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from ExtractXsecPy import read_DWBA
class Fitting:
def __init__(self):
self.ExList = []
self.fitOption = []
self.expData = []
self.dataX = []
self.data = [] # is a 2D array
self.headers = []
def read_data(self,file_path):
self.headers, self.dataX, self.data = read_DWBA(file_path)
print(self.headers)
def read_expData(self, fileName):
self.ExList = []
self.fitOption = []
self.expData = []
current_data = []
with open(fileName, "r") as file:
for line in file:
line = line.strip()
if not line:
continue
# Check for excitation energy lines
if line.startswith("#======================"):
# If there's an existing data block, save it
if current_data:
self.expData.append(np.array(current_data, dtype=float))
current_data = []
# Extract excitation energy
Ex_energy = line.split()[1]
self.ExList.append(float(Ex_energy))
# Check for fit option lines
elif line.startswith("fit"):
# Add fit option parameters
fit_params = [x.strip(',') for x in line.split()[1:]]
self.fitOption.append(fit_params)
# Parse data lines
elif not line.startswith("#"):
values = [float(x) for x in line.split()]
current_data.append(values)
# Append the last block
if current_data:
self.expData.append(np.array(current_data, dtype=float))
# Convert to numpy arrays
self.ExList = np.array(self.ExList)
self.expData = [np.array(data) for data in self.expData]
# Output the result
print("=========== Number of data set:", len(self.ExList))
for i in range(0, len(self.ExList)):
print("-------------------------")
print(" ExList:", self.ExList[i])
print("Fit Options:", self.fitOption[i])
print(" Data List:\n", self.expData[i])
def FitData(self):
# Set initial offset values
x_offset, y_offset = 2000, 100
figure = []
for expDataID in range(len(self.expData)):
# Get the number of fit options and cross-sections
nFit = len(self.fitOption[expDataID])
nXsec = len(self.data)
# Extract experimental data (x, y, and errors)
x_exp = self.expData[expDataID][:, 0] # x positions
x_err = self.expData[expDataID][:, 1] # x uncertainties (errors)
y_exp = self.expData[expDataID][:, 2] # y positions
y_err = self.expData[expDataID][:, 3] # y errors
fitTheory = []
fitTheory_lower = []
fitTheory_upper = []
for k in range(nFit):
# Get the cross-section IDs for the current fit option and strip extra spaces
xsecIDStr = self.fitOption[expDataID][k].strip()
xsecID = [int(part.strip()) for part in xsecIDStr.split('+')] if '+' in xsecIDStr else [int(xsecIDStr)]
# Ensure all cross-section IDs are valid
processFlag = True
for id in range(len(xsecID)):
if xsecID[id] >= nXsec:
print(f"Error: Requested Xsec-{xsecID[id]} exceeds the number of available cross-sections ({nXsec})")
processFlag = False
if processFlag == False :
continue
# Define the fitting function: a weighted sum of the selected data
def fit_func(x, *scale):
y = np.zeros_like(x)
for p, id in enumerate(xsecID):
y += scale[p] * np.interp(x, self.dataX, self.data[id])
return y
lower_bounds = [1e-6] * len(xsecID) # Setting a small positive lower bound
upper_bounds = [np.inf] * len(xsecID) # No upper bound
# Perform curve fitting using the fit_func and experimental data with y-errors as weights
popt, pcov = curve_fit(fit_func, x_exp, y_exp, sigma=y_err, absolute_sigma=True,
p0=np.ones(len(xsecID)), # Initial guess for scale parameters
bounds=(lower_bounds, upper_bounds))
perr = np.sqrt(np.diag(pcov)) # Standard deviation of the parameters
print(f"Fitted scale for fit {k+1}: {popt} +/- {perr}")
# Append the theoretical fit for this fit option
fitTheory.append(np.zeros_like(self.dataX))
for p, id in enumerate(xsecID):
fitTheory[k] += popt[p] * np.interp(self.dataX, self.dataX, self.data[id])
# Optionally, you can plot the uncertainty as shaded regions (confidence intervals)
# Create the upper and lower bounds of the theoretical model with uncertainties
fitTheory_upper.append(np.zeros_like(self.dataX))
fitTheory_lower.append(np.zeros_like(self.dataX))
for p, id in enumerate(xsecID):
fitTheory_upper[k] += (popt[p] + perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fitTheory_lower[k] += (popt[p] - perr[p]) * np.interp(self.dataX, self.dataX, self.data[id])
fig = plt.figure()
figure.append(fig)
# Plot results
plt.errorbar(x_exp, y_exp, xerr=x_err, yerr=y_err, fmt='o', label='Experimental Data', color='blue')
# Plot all fit theories
for i, fit in enumerate(fitTheory):
plt.plot(self.dataX, fit, label=f'Xsec:{self.fitOption[expDataID][i]} Fit')
plt.fill_between(self.dataX, fitTheory_lower[i], fitTheory_upper[i], alpha=0.2)
# Customize plot
plt.xlabel('Angle_CM [deg]')
plt.ylabel('X-Sec [a.u.]')
plt.legend()
plt.autoscale(enable=True, axis='x', tight=True)
plt.tight_layout()
plt.yscale('log')
# Replace plt.title() with plt.text() to position the title inside the plot
plt.text(0.05, 0.05, f'Fit for Exp Data : {self.ExList[expDataID]} MeV', transform=plt.gca().transAxes,
fontsize=12, verticalalignment='bottom', horizontalalignment='left', color='black')
manager = plt.get_current_fig_manager()
# manager.window.wm_geometry(f"+{x_offset}+{y_offset}")
manager.set_window_title(f"Exp Data : {self.ExList[expDataID]} MeV")
x_offset += 100
y_offset += 100
plt.show()