1
0
Fork 0
mirror of https://github.com/gwm17/spspy.git synced 2025-12-17 09:55:51 -05:00
This commit is contained in:
Alex Conley 2025-12-09 16:16:46 -06:00 committed by GitHub
commit ee9b3d482f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 161 additions and 70 deletions

3
.gitignore vendored
View File

@ -5,4 +5,5 @@ __pycache__/
.vs/
*.sps
*.spanc
*.csv
*.csv
*.DS_Store

View File

@ -2,9 +2,7 @@
SPSPy is a Python based package of tools for use with the Super-Enge Split-Pole Spectrograph at FSU. Much of the code here is based on Java programs originally written at Yale University by D.W. Visser, C.M. Deibel, and others. Currently the package contains SPSPlot, a tool aimed at informing users which states should appear at the focal plane of the SESPS, and SPANC, a tool for calibrating the position spectra from the focal plane.
## Depencencies and Requirements
The requirements for running SPSPy are outlined in the requirements.txt file located in the repository. It is recommended to install these to a local virtual environment using `pip install -r requirements.txt`. For conda use the environments.yml file to create a conda environment for SPSPy. Simply run `conda env create -f environment.yml` from the SPSPy directory. conda will make a new virtual environment named spsenv with the dependencies outlined in environments.yml. If you already have an environment named spsenv or would like to change the name simply edit the first line of the enviornments.yml.
The recommended install for SPSPy dependencies is via pip.
To ensure capability, use python 3.14. The requirements for running SPSPy are outlined in the requirements.txt file located in the repository. It is recommended to install these to a local virtual environment using `pip install -r requirements.txt`.
### Creating a virtual environment with pip
To create a virtual environment with pip in the terminal for MacOS or Linux use `python3 -m venv env` to create a local virtual environment named `env` (or whatever name you'd like), or on Windows use `py -m venv env` to do the same. To activate your new environment run `source env/bin/activate` in MacOS or Linux, or `.\env\Scripts\activate`. Now you can run the above `pip` command to install all dependencies to the virtual environment. To leave the virtual environment use the command `deactivate` in your terminal.

View File

@ -1,51 +0,0 @@
name: spsenv
channels:
- defaults
dependencies:
- _libgcc_mutex=0.1=main
- _openmp_mutex=5.1=1_gnu
- bzip2=1.0.8=h7b6447c_0
- ca-certificates=2022.10.11=h06a4308_0
- certifi=2022.9.24=py310h06a4308_0
- ld_impl_linux-64=2.38=h1181459_1
- libffi=3.4.2=h6a678d5_6
- libgcc-ng=11.2.0=h1234567_1
- libgomp=11.2.0=h1234567_1
- libstdcxx-ng=11.2.0=h1234567_1
- libuuid=1.41.5=h5eee18b_0
- ncurses=6.3=h5eee18b_3
- openssl=1.1.1s=h7f8727e_0
- pip=22.2.2=py310h06a4308_0
- python=3.10.8=h7a1cb2a_1
- readline=8.2=h5eee18b_0
- setuptools=65.5.0=py310h06a4308_0
- sqlite=3.40.0=h5082296_0
- tk=8.6.12=h1ccaba5_0
- tzdata=2022f=h04d1e81_0
- wheel=0.37.1=pyhd3eb1b0_0
- xz=5.2.8=h5eee18b_0
- zlib=1.2.13=h5eee18b_0
- pip:
- charset-normalizer==2.1.1
- contourpy==1.0.6
- cycler==0.11.0
- fonttools==4.38.0
- idna==3.4
- kiwisolver==1.4.4
- lxml==4.9.1
- matplotlib==3.6.2
- numpy==1.23.5
- packaging==21.3
- pillow==9.3.0
- pycatima==1.71
- pyparsing==3.0.9
- pyqtdarktheme==1.2.1
- pyside6==6.4.1
- pyside6-addons==6.4.1
- pyside6-essentials==6.4.1
- python-dateutil==2.8.2
- requests==2.28.1
- scipy==1.9.3
- shiboken6==6.4.1
- six==1.16.0
- urllib3==1.26.13

View File

@ -1,8 +1,25 @@
lxml==4.9.1
matplotlib==3.6.2
numpy==1.23.5
pycatima==1.71
pyqtdarktheme==1.2.1
PySide6==6.4.1
requests==2.28.1
scipy==1.9.3
certifi==2025.11.12
charset-normalizer==3.4.4
contourpy==1.3.3
cycler==0.12.1
fonttools==4.61.0
idna==3.11
kiwisolver==1.4.9
lxml==6.0.2
matplotlib==3.10.7
numpy==2.3.5
packaging==25.0
pillow==12.0.0
pycatima==1.981
pyparsing==3.2.5
PySide6==6.10.1
PySide6_Addons==6.10.1
PySide6_Essentials==6.10.1
python-dateutil==2.9.0.post0
QDarkStyle==3.2.3
QtPy==2.4.3
requests==2.32.5
scipy==1.16.3
shiboken6==6.10.1
six==1.17.0
urllib3==2.5.0

View File

@ -9,7 +9,7 @@ from .SpancUI import run_spanc_ui, SpancGUI
import sys
import matplotlib as mpl
from qdarktheme import load_stylesheet
import qdarkstyle
class Launcher(QMainWindow):
def __init__(self, parent=None):
@ -43,6 +43,7 @@ def run_launcher() -> None:
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = Launcher()
sys.exit(app.exec_())

View File

@ -13,7 +13,7 @@ from PySide6.QtWidgets import QDoubleSpinBox
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QAction
from qdarktheme import load_stylesheet
import qdarkstyle
from enum import Enum, auto
import matplotlib as mpl
import sys
@ -299,6 +299,7 @@ def run_spsplot_ui():
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = SPSPlotGUI()
sys.exit(app.exec_())

View File

@ -126,4 +126,82 @@ class Spanc:
for calibration in self.calibrations.values():
rxn = self.reactions[calibration.rxnName]
calibration.rho = rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation))
calibration.rhoErr = np.abs(rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation + calibration.excitationErr)) - calibration.rho)
calibration.rhoErr = np.abs(rxn.convert_ejectile_KE_2_rho(rxn.calculate_ejectile_KE(calibration.excitation + calibration.excitationErr)) - calibration.rho)
def get_excitation_curves(self,
x_min: float = -300.0,
x_max: float = 300.0,
n_bins: int = 601):
"""
Compute excitation-energy curves for every reaction by evaluating
Ex = reaction.calculate_excitation( rho(x) ) across a grid of
focal-plane positions.
Returns:
x_vals: array of FP bin centers
ex_curves: { rxn_name: Ex_array_in_MeV }
"""
# Bin centers (e.g. 601 values from -300 to 300)
x_vals = np.linspace(x_min, x_max, n_bins)
# ρ(x) uses your fitted polynomial a0 + a1 x + ... + aN x^N
params = self.fitter.get_parameters() # [a0, a1, ..., aN]
rho_vals = np.polyval(params[::-1], x_vals) # reverse order for numpy poly
ex_curves: dict[str, np.ndarray] = {}
for rxn_name, rxn in self.reactions.items():
ex_vals = np.array(
[rxn.calculate_excitation(rho) for rho in rho_vals],
dtype=float,
)
ex_curves[rxn_name] = ex_vals
return x_vals, ex_curves
def export_excitation_csv(self,
filename: str,
x_min: float = -300.0,
x_max: float = 300.0,
n_bins: int = 601):
"""
Export excitation-energy calibration table as CSV.
CSV format:
FP_x_mm, Ex_rxn1_MeV, Ex_rxn2_MeV, ...
FP_x_mm runs from x_min to x_max with n_bins steps (inclusive).
"""
import csv
import numpy as np
# Generate FP positions (601 values from -300 to 300)
x_vals = np.linspace(x_min, x_max, n_bins)
# Compute rho(x) using fitted polynomial
params = self.fitter.get_parameters() # [a0, a1, ..., aN]
rho_vals = np.polyval(params[::-1], x_vals)
# Compute excitation for each reaction
rxn_names = list(self.reactions.keys())
ex_mev = {rxn: [] for rxn in rxn_names}
for rho in rho_vals:
for rxn_name, rxn in self.reactions.items():
ex = rxn.calculate_excitation(rho)
ex_mev[rxn_name].append(ex)
# Write CSV
with open(filename, "w", newline="") as csvfile:
writer = csv.writer(csvfile)
# Header row
header = ["x_mm"] + [f"Ex_{rxn}_MeV" for rxn in rxn_names]
writer.writerow(header)
# Data rows
for i, x in enumerate(x_vals):
row = [f"{x:.6f}"]
for rxn in rxn_names:
row.append(f"{ex_mev[rxn][i]:.9f}")
writer.writerow(row)

View File

@ -12,7 +12,7 @@ from PySide6.QtWidgets import QPushButton, QTextEdit, QSpinBox
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QAction
from qdarktheme import load_stylesheet, load_palette
import qdarkstyle
import matplotlib as mpl
import numpy as np
from numpy.typing import NDArray
@ -95,6 +95,17 @@ class SpancGUI(QMainWindow):
fitOptionLayout.addWidget(QLabel("Polynomial Order", self.fitOptionGroup))
fitOptionLayout.addWidget(self.fitOrderBox)
fitOptionLayout.addWidget(self.fitButton)
# NEW: button to plot excitation curves for all reactions
self.exCurveButton = QPushButton("Plot Ex vs x", self.fitOptionGroup)
self.exCurveButton.clicked.connect(self.plot_excitation_curves)
fitOptionLayout.addWidget(self.exCurveButton)
# Button to export excitation calibration CSV
self.exportCSVButton = QPushButton("Export Ex(x) CSV", self.fitOptionGroup)
self.exportCSVButton.clicked.connect(self.handle_export_excitation_csv)
fitOptionLayout.addWidget(self.exportCSVButton)
self.fitOptionGroup.setLayout(fitOptionLayout)
fitLayout.addWidget(QLabel("Fit", self.fitCanvas))
@ -374,13 +385,48 @@ class SpancGUI(QMainWindow):
f"## Parameter Uncertanties (ua0 -> uaN): {np.array_str(self.spanc.fitter.get_parameter_errors(), precision=3)} \n \n"
f"## Residuals (x0 -> xN): {np.array_str(residuals, precision=3)} \n \n"
f"## Studentized Residuals (x0 -> xN): {np.array_str(studentizedResiduals, precision=3)} \n \n")
self.fitResultText.setMarkdown(markdownString)
def plot_excitation_curves(self):
# Require a fit so rho(x) is defined
if not self.spanc.isFit:
print("Run the calibration fit first before plotting Ex vs x.")
return
# Compute Ex at 600 bin centers from -300 to 300 for all reactions
x_vals, ex_curves = self.spanc.get_excitation_curves(
x_min=-300.0,
x_max=300.0,
n_bins=600,
)
self.fitCanvas.axes.cla()
for rxn_name, ex_vals in ex_curves.items():
self.fitCanvas.axes.plot(x_vals, ex_vals, label=rxn_name)
self.fitCanvas.axes.set_xlabel(r"$x$ (mm)")
self.fitCanvas.axes.set_ylabel(r"$E_x$ (MeV)")
self.fitCanvas.axes.set_title("Excitation energy vs focal-plane position")
self.fitCanvas.axes.grid(True)
self.fitCanvas.axes.legend()
self.fitCanvas.fig.tight_layout()
self.fitCanvas.draw()
def handle_export_excitation_csv(self):
fileName = QFileDialog.getSaveFileName(
self, "Export Excitation CSV", "./", "CSV Files (*.csv)"
)
if fileName[0]:
self.spanc.export_excitation_csv(fileName[0])
print(f"Exported excitation calibration to {fileName[0]}")
def run_spanc_ui() :
mpl.use("Qt5Agg")
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setStyleSheet(load_stylesheet())
# app.setStyleSheet(load_stylesheet())
app.setStyleSheet(qdarkstyle.load_stylesheet())
window = SpancGUI()
sys.exit(app.exec_())