PtolemyGUI/Cleopatra/FitExData.py

164 lines
5.3 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
import numpy as np
from scipy.optimize import curve_fit
2024-11-07 16:17:28 -05:00
from ExtractXsecPy import read_DWBA
2024-11-08 14:23:49 -05:00
from PlotWindow import FitPlotWindow
2024-11-08 14:23:49 -05:00
#========================================================
2024-11-07 16:17:28 -05:00
class Fitting():
def __init__(self):
self.dataName_list = []
self.fitOption = []
self.expData = []
self.dataX = []
self.data = [] # is a 2D array
self.headers = []
2024-11-08 14:23:49 -05:00
# fit parameters for a single data set
self.para = []
self.para_err = []
self.chi_squared = []
self.plot = []
def read_data(self,file_path):
self.headers, self.dataX, self.data = read_DWBA(file_path)
2024-11-08 14:23:49 -05:00
self.headers = self.headers[1:]
def read_expData(self, fileName):
self.dataName_list = []
self.fitOption = []
self.expData = []
current_data = []
with open(fileName, "r") as file:
for line in file:
line = line.strip()
if not line:
continue
2024-11-07 17:18:02 -05:00
if line.startswith("$"):
continue
# Check for dataSet lines
2024-11-07 16:17:28 -05:00
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 dataSet Name
2024-11-08 14:23:49 -05:00
dataName = line.split()[1:]
self.dataName_list.append(" ".join(dataName))
# 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.dataName_list = np.array(self.dataName_list)
self.expData = [np.array(data) for data in self.expData]
# Output the result
print("=========== Number of data set:", len(self.dataName_list))
for i in range(0, len(self.dataName_list)):
print("-------------------------")
2024-11-08 16:54:04 -05:00
print(" data Name:", self.dataName_list[i])
print("Fit Options:", self.fitOption[i])
print(" Data List:\n", self.expData[i])
2024-11-08 14:23:49 -05:00
def FitSingleData(self, expDataID):
print("============================================")
# 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
y_exp = self.expData[expDataID][:, 2] # y positions
y_err = self.expData[expDataID][:, 3] # y errors
self.para = []
self.para_err = []
self.chi_squared = []
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
2024-11-08 14:23:49 -05:00
# 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
2024-11-08 14:23:49 -05:00
lower_bounds = [1e-6] * len(xsecID) # Setting a small positive lower bound
upper_bounds = [np.inf] * len(xsecID) # No upper bound
2024-11-08 14:23:49 -05:00
# 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))
2024-11-08 14:23:49 -05:00
self.para.append(popt)
perr = np.sqrt(np.diag(pcov))# Standard deviation of the parameters
self.para_err.append(perr)
# Get the fitted model values
y_fit = fit_func(x_exp, *popt)
residuals = y_exp - y_fit
self.chi_squared.append(np.sum((residuals / y_err) ** 2))
print(f"Fitted scale for fit {k}: {', '.join([f'{x:.3f}' for x in popt])} +/- {', '.join([f'{x:.3f}' for x in perr])} | Chi^2 : {self.chi_squared[-1]:.4f}")
# print(f"Fitted scale for fit {k}: {popt} +/- {perr} | Chi^2 : {chi_squared[-1]:.4f}")
return self.para, self.para_err, self.chi_squared
def plot_fits(self):
self.plot = []
2024-11-08 16:54:04 -05:00
2024-11-08 14:23:49 -05:00
for k , dN in enumerate(self.dataName_list):
self.FitSingleData(k)
self.plot.append( FitPlotWindow(f"Data-{k}"))
2024-11-08 16:54:04 -05:00
self.plot[-1].set_data(k, self.expData, self.fitOption, dN,
self.dataX, self.data, self.headers,
self.para, self.para_err, self.chi_squared)
2024-11-08 14:23:49 -05:00
self.plot[-1].plot_Fit()
self.plot[-1].show()
2024-11-08 16:54:04 -05:00
def close_plots(self):
for plot in self.plot:
plot.close()