Add FitData.py, modified python GUI for fitting. work but not perfect

This commit is contained in:
Ryan Tang 2024-11-07 14:05:03 -05:00
parent 3a41e13fdf
commit f1b2d4a580
3 changed files with 266 additions and 14 deletions

View File

@ -9,17 +9,27 @@ class PythonHighlighter(QSyntaxHighlighter):
super().__init__(document)
# Define formatting for comments
self.comment_format = QTextCharFormat()
self.comment_format.setForeground(Qt.GlobalColor.darkGreen)
self.comment_format1 = QTextCharFormat()
self.comment_format1.setForeground(Qt.GlobalColor.darkGreen)
self.comment_format2 = QTextCharFormat()
self.comment_format2.setForeground(Qt.GlobalColor.blue)
self.comment_format2.setBackground(Qt.GlobalColor.yellow)
self.comment_format3 = QTextCharFormat()
self.comment_format3.setForeground(Qt.GlobalColor.magenta)
def highlightBlock(self, text):
# Highlight comments
if text.startswith("#"):
self.setFormat(0, len(text), self.comment_format)
if text.startswith("#") and text.startswith("#=") == False:
self.setFormat(0, len(text), self.comment_format1)
if text.startswith("$"):
self.setFormat(0, len(text), self.comment_format)
self.setFormat(0, len(text), self.comment_format1)
if text.startswith("0"):
self.setFormat(0, len(text), self.comment_format)
self.setFormat(0, len(text), self.comment_format1)
if text.startswith("fit"):
self.setFormat(0, len(text), self.comment_format2)
if text.startswith("#="):
self.setFormat(0, len(text), self.comment_format3)
class CustomTextEdit(QTextEdit):
def __init__(self, parent=None):
@ -35,7 +45,6 @@ class CustomTextEdit(QTextEdit):
# Check if Ctrl+D is pressed
if event.key() == Qt.Key.Key_D and event.modifiers() == Qt.KeyboardModifier.ControlModifier:
self.duplicate_line()
event.accept() # Prevent the default behavior of Ctrl+D
else:
super().keyPressEvent(event) # Call the base class to handle other keys

178
Cleopatra/FitExData.py Normal file
View File

@ -0,0 +1,178 @@
#!/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()

View File

@ -17,7 +17,7 @@ from CustomTextEdit import CustomTextEdit
from ExtractXsecPy import extract_xsec
from ExWindow import ExWindow
from MatPlotLibWindow import MatPlotLibWindow
from FitExData import Fitting
################################################## MainWindow
class MyWindow(QMainWindow):
@ -25,15 +25,17 @@ class MyWindow(QMainWindow):
super().__init__()
self.setWindowTitle("Ptolemy GUI")
self.setGeometry(100, 100, 1000, 700)
self.setMinimumSize(400, 600)
self.setGeometry(100, 100, 1000, 800)
self.setMinimumSize(1000, 800)
self.lastDWBARecord = "lastDWBA.txt"
self.DWBAFileName = ""
self.ExpDataFileName = ""
self.LoadLastOpenDWBASource()
self.bashResult = ""
self.plot_window = MatPlotLibWindow()
self.Ex_window = ExWindow()
self.fitting = Fitting()
# Set up Group Box for DWBA Control
self.gbDWBA = QGroupBox("DWBA")
@ -146,6 +148,21 @@ class MyWindow(QMainWindow):
Ex_layout.addWidget(self.sbMaXEx, 1, 1)
Ex_layout.addWidget(buEx, 2, 0, 1, 2)
# ExpData Group
self.gbExpFit = QGroupBox("Exp Data Fit")
fit_layout = QVBoxLayout()
fit_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
self.gbExpFit.setLayout(fit_layout)
bnOpenExpData = QPushButton("Open ExpData")
bnOpenExpData.clicked.connect(self.LoadExpDataToTextBox)
bnFit = QPushButton("Fit")
bnFit.clicked.connect(self.fitData)
fit_layout.addWidget(bnOpenExpData)
fit_layout.addWidget(bnFit)
# Set up the Right Side
self.bnOpenDWBASource = QPushButton("Open DWBA Source")
@ -158,6 +175,16 @@ class MyWindow(QMainWindow):
self.bnSaveFile = QPushButton("Save File")
self.bnSaveFile.clicked.connect(self.SaveFile)
self.bnOpenExpData = QPushButton("Open Exp Data")
self.bnOpenExpData.clicked.connect(self.OpenExpDataFile)
self.leExpDataFileName = QLineEdit("")
self.leExpDataFileName.setReadOnly(True)
self.leExpDataFileName.setText(self.ExpDataFileName)
self.bnSaveExpDataFile = QPushButton("Save Exp Data File")
self.bnSaveExpDataFile.clicked.connect(self.SaveExpDataFile)
self.text_edit = CustomTextEdit(self)
self.leStatus = QLineEdit("")
@ -165,17 +192,25 @@ class MyWindow(QMainWindow):
self.LoadFileToTextBox(self.DWBAFileName, True)
self.bnSaveExpDataFile.setEnabled(False)
# Set up the layout
layout = QGridLayout()
# layout.addWidget(self.gbDWBA, 0, 0, 7, 1)
layout.addWidget(self.gbDWBA, 0, 0, 5, 1)
layout.addWidget(self.gbEx, 5, 0, 2, 1)
layout.addWidget(self.gbExpFit, 7, 0, 2, 1)
layout.addWidget(self.bnOpenDWBASource, 0, 1)
layout.addWidget(self.leFileName, 0, 2, 1, 3)
layout.addWidget(self.bnSaveFile, 0, 5)
layout.addWidget(self.text_edit, 1, 1, 5, 5)
layout.addWidget(self.leStatus, 6, 1, 1, 5)
layout.addWidget(self.bnOpenExpData, 1, 1)
layout.addWidget(self.leExpDataFileName, 1, 2, 1, 3)
layout.addWidget(self.bnSaveExpDataFile, 1, 5)
layout.addWidget(self.text_edit, 2, 1, 6, 5)
layout.addWidget(self.leStatus, 8, 1, 1, 5)
for i in range(layout.columnCount()) :
layout.setColumnStretch(i, 1)
@ -192,12 +227,16 @@ class MyWindow(QMainWindow):
try :
with open(self.lastDWBARecord, 'r') as file:
self.DWBAFileName = file.readline().strip()
self.ExpDataFileName = file.readline().strip()
except:
self.DWBAFileName = "DWBA"
self.ExpDataFileName = ""
def SaveLastOpenDWBASource(self):
with open(self.lastDWBARecord, 'w') as file:
file.write(self.DWBAFileName)
file.write("\n")
file.write(self.ExpDataFileName)
def OnOffXsecOption(self):
self.cbXsec.setEnabled(self.chkExtracrXsec.isChecked())
@ -209,6 +248,20 @@ class MyWindow(QMainWindow):
self.leFileName.setText(self.DWBAFileName)
self.LoadFileToTextBox(self.DWBAFileName)
self.SaveLastOpenDWBASource()
self.bnSaveExpDataFile.setEnabled(False)
def OpenExpDataFile(self):
file_path, _ = QFileDialog.getOpenFileName(self, "Open File", "", "Text File (*.txt)")
if file_path:
self.ExpDataFileName = file_path
self.leExpDataFileName.setText(self.ExpDataFileName)
self.LoadFileToTextBox(self.ExpDataFileName)
self.bnSaveExpDataFile.setEnabled(True)
self.SaveLastOpenDWBASource()
def LoadExpDataToTextBox(self):
self.LoadFileToTextBox(self.ExpDataFileName)
self.bnSaveExpDataFile.setEnabled(True)
def LoadFileToTextBox(self, fileName, moveToButton = False):
# print(fileName)
@ -228,12 +281,20 @@ class MyWindow(QMainWindow):
self.text_edit.setText(f"Failed to load file:\n{e}")
self.leStatus.setText(f"Failed to load file:\n{e}")
self.bnSaveExpDataFile.setEnabled(False)
def SaveFile(self):
file_path = self.leFileName.text()
with open(file_path, 'w') as file:
file.write(self.text_edit.toPlainText())
self.leStatus.setText(f"File saved to: {file_path}")
def SaveExpDataFile(self):
file_path = self.leExpDataFileName.text()
with open(file_path, 'w') as file:
file.write(self.text_edit.toPlainText())
self.leStatus.setText(f"File saved to: {file_path}")
def DeleteinOutXsecFiles(self):
if os.path.exists(self.DWBAFileName + ".in"):
os.remove(self.DWBAFileName + ".in")
@ -303,6 +364,10 @@ class MyWindow(QMainWindow):
self.Ex_window.plot_Ex_graph()
self.Ex_window.show()
def fitData(self):
self.fitting.read_expData(self.ExpDataFileName)
self.fitting.read_data(self.DWBAFileName + ".Xsec.txt")
self.fitting.FitData()
def closeEvent(self, event):
if self.plot_window: