Commit 47e94833 authored by Zolnierczuk, Piotr's avatar Zolnierczuk, Piotr
Browse files

bug fixes and work on echo simulator

parent 3e122264
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -24,6 +24,8 @@ build: build-ui build-docs
build-dist: build
	@$(PYTHON) setup.py  bdist

install: install-global

install-global:	build-ui
	pip install .

+7 −2
Original line number Diff line number Diff line
@@ -41,8 +41,13 @@ def main():
    loglevel  = kwargs.pop('loglevel')
    filenames = kwargs.pop('filename')
    outdir    = kwargs.pop('out')
    setup_logger(loglevel)
    log       = setup_logger(loglevel)
    try:
        convert_files(filenames, outdir, **kwargs)
    except OSError as exc:
        log.error("OS Error: %s", exc)
    except RuntimeError as exc:
        log.error("%s", exc)


if __name__ == "__main__":
+6 −11
Original line number Diff line number Diff line
@@ -254,7 +254,7 @@ def convert_to_taco(filename, outdir, **kwargs):
    file_type = get_nsefiletype(filename)
    writer_class = WriterClassType.get(file_type, None)
    if not isinstance(writer_class, type):
        raise RuntimeError(f"Unknown file type {file_type}")
        raise RuntimeError(f"{filename}: unknown file type {file_type}")
    writer = writer_class()
    if not writer.read_nexus(filename, **kwargs):
        return None
@@ -266,17 +266,12 @@ ConverterType = { 'to_taco' : convert_to_taco,

def convert_files(filenames, outdir, **kwargs):
    """convert *.nxs.h5 file to/from old TACO style files"""
    log = logging.getLogger()
    converter = ConverterType.get(kwargs.pop('converter', 'from_taco'))
    results = []
    log = logging.getLogger()
    for filename in filenames:
        try:
        basename = os.path.basename(filename)
        outfile = converter(filename, outdir, **kwargs)
        log.info('converted %s to %s', basename, outfile)
        results.append(outfile)
        except RuntimeError as exc:
            log.warning("error converting %s: (%s)", basename, exc)
        except OSError as exc:
            log.warning("error converting %s: (%s)", basename, exc.strerror)
    return results
+2 −2
Original line number Diff line number Diff line
@@ -3,8 +3,8 @@ PySEN revision module
"""
import sys
__version__  = "2.1"
__release__  = "a3"
__date__     = "Mar 24, 2025"
__release__  = "a4"
__date__     = "Mar 25, 2025"

def version(full=False):
    "get pysen version number"
+108 −82
Original line number Diff line number Diff line
"""
Main GUI window module.
"""
#import os
#import time
#pylint: disable=invalid-name
import numpy as np

try:
    from PyQt5.QtCore import pyqtSlot
    from PyQt5.QtWidgets import qApp, QMainWindow, QMessageBox #,QVBoxLayout #, QTabWidget, QWidget)
    from PyQt5.QtGui import QDoubleValidator
    from matplotlib.backends.backend_qt5agg import FigureCanvas
except ImportError:
    pass
from matplotlib.figure import Figure
from numpy import pi, exp, log10


from ..config import wavelength_bandwidth
from ..constants import ANGSTROM, OMEGA_N
from ..echo.fit import flux_weighted_eshape as echo_shape
#pylint: disable=import-error
from .ui_SimpleEchoSimulator import Ui_SimpleEchoSimMainWindow as Ui_MainWindow

MICRO=1e-6

def f_exp(t, t0=1, beta=1):
    "Stretched exp"
    return np.exp(-(t/t0)**beta)
def f_exp(t, t0=1):
    "stretched exponential"
    return exp(-(t/t0))

def sqt(t, coh, tcoh, inc, tinc, beta=1):
def sqt(t, coh, tcoh, inc, tinc, bgr=0):
    "S(Q,t)/S(Q,0)"
    return coh*f_exp(t,tcoh,beta)-1/3*inc*f_exp(t,tinc,beta)
    return coh*f_exp(t,tcoh)-1/3*(inc*f_exp(t,tinc)+bgr)

class MainWindow(QMainWindow, Ui_MainWindow):
    """Main  for viewing and deleting available groups."""
    #pylint: disable=too-many-instance-attributes
    def __init__(self, parent=None):
    def __init__(self, parent=None, **kwargs):
        """init MainWindow"""
        super().__init__(parent)
        self.setupUi(self)
        self.menuBar.setNativeMenuBar(False)
        #self.menuBar.setCornerWidget(self.menuHelp, Qt.TopRightCorner)
        #
        self.pos   = kwargs.pop('pos',  'p2')
        self.lmax  = kwargs.pop('lmax', 8.000)
        self.lmin  = self.lmax - wavelength_bandwidth(pos=self.pos)
        self.ntbin = kwargs.pop('ntbin', 42)  # number of TOF bins
        self.tmin, self.tmax = 1e-3, 1e+3
        #print(self.pos, self.lmax, self.lmin, self.ntbin)

        #
        dynamic_canvas = FigureCanvas(Figure(figsize=(5, 3)))
@@ -50,121 +60,137 @@ class MainWindow(QMainWindow, Ui_MainWindow):
        self.dblSpinBoxBgr.valueChanged.connect(self.handleSpinBoxBgr)
        #
        self.hSliderTauCoh.valueChanged.connect(self.handleSliderTauCoh)
        self.dblSpinBoxTcoh.valueChanged.connect(self.handleSpinBoxTauCoh)
        #
        self.hSliderTauInc.valueChanged.connect(self.handleSliderTauInc)
        self.dblSpinBoxTinc.valueChanged.connect(self.handleSpinBoxTauInc)
        #
        self.hSliderTauEcho.valueChanged.connect(self.handleSliderTauEcho)
        self.dblSpinBoxTecho.valueChanged.connect(self.handleSpinBoxTauEcho)
        #
        #self.dblSpinBoxTcoh.valueChanged.connect(self.handleSpinBoxTauCoh)
        #self.dblSpinBoxTinc.valueChanged.connect(self.handleSpinBoxTauInc)
        #self.dblSpinBoxTecho.valueChanged.connect(self.handleSpinBoxTauEcho)
        #
        self.inpTauCoh.setValidator(QDoubleValidator(1e-6, 1e6, 3))
        self.inpTauInc.setValidator(QDoubleValidator(1e-6, 1e6, 3))
        self.inpTauEcho.setValidator(QDoubleValidator(1e-3, 1e3, 3))
        #
        self.inpTauCoh.returnPressed.connect(self.handleInpTauCoh)
        self.inpTauInc.returnPressed.connect(self.handleInpTauInc)
        self.inpTauEcho.returnPressed.connect(self.handleInpTauEcho)

        # File menu actions
        self.actionExit.triggered.connect(qApp.quit)
        self.actionReset.triggered.connect(self.reset)
        self.actionAbout.triggered.connect(self.about_box)

        #
        self._reset_values()


    def _fmt_time(self, label, value):
        "format time"
        if value>=1e6:
            label.setText("{0:8.1f} ms".format(value/1e6))
            label.setText(f"{value/1e6:8.1f} ms")
        elif value>=1e3:
            label.setText("{0:8.1f} us".format(value/1e3))
            label.setText(f"{value/1e3:8.1f} us")
        elif value>=1:
            label.setText("{0:8.1f} ns".format(value))
            label.setText(f"{value:8.1f} ns")
        elif value>=1e-3:
            label.setText("{0:8.1f} ps".format(value*1e3))
            label.setText(f"{value*1e3:8.1f} ps")
        else:
            label.setText("{0:8.1f} fs".format(value*1e6))
        #elif value>1e-6:
        #else:
        #    label.setText("{0:8.3g} ns".format(value))
            label.setText(f"{value*1e6:8.1f} fs")

    # Intensities
    @pyqtSlot(float)
    def handleSpinBoxCoh(self, _value):
        "handler"
        "Icoh handler"
        self._update_canvas()

    @pyqtSlot(float)
    def handleSpinBoxInc(self, _value):
        "handler"
        "Iinc handler"
        self._update_canvas()

    @pyqtSlot(float)
    def handleSpinBoxBgr(self, _value):
        "handler"
        "Ibgr handler"
        self._update_canvas()


    # Tau sliders
    @pyqtSlot(int)
    def handleSliderTauCoh(self, value):
        "handler"
        #val = 10**(value/1000.0)
        #self.dblSpinBoxTcoh.setValue(val)
        self.dblSpinBoxTcoh.setValue(value/1000.0)
        self._update_canvas()

    @pyqtSlot(float)
    def handleSpinBoxTauCoh(self, value):
        "handler"
        #val = int(np.log10(value)*1000)
        #self.hSliderTauCoh.setValue(val)
        self.hSliderTauCoh.setValue(int(value*1000))
        "tau coh slider handler"
        tau = 10**(value/1000.0)
        self.inpTauCoh.setText(f"{tau:.4g}")
        self._update_canvas()

    @pyqtSlot(int)
    def handleSliderTauInc(self, value):
        "handler"
        self.dblSpinBoxTinc.setValue(value/1000.0)
        "tau inc slider handler"
        tau = 10**(value/1000.0)
        self.inpTauInc.setText(f"{tau:.4g}")
        self._update_canvas()

    @pyqtSlot(float)
    def handleSpinBoxTauInc(self, value):
        "handler"
        self.hSliderTauInc.setValue(int(value*1000))
        self._update_canvas()

    @pyqtSlot(int)
    def handleSliderTauEcho(self, value):
        "handler"
        self.dblSpinBoxTecho.setValue(value/1000.0)
        "tau echo slider handler"
        tau = 10**(value/1000.0)
        self.inpTauEcho.setText(f"{tau:.4g}")
        self._update_canvas()

    @pyqtSlot(float)
    def handleSpinBoxTauEcho(self, value):
        "handler"
        self.hSliderTauEcho.setValue(int(value*1000))
    def handleInpTauCoh(self):
        "input tau coh handler"
        text   = self.inpTauCoh.text()
        slider = int(log10(float(text))*1000)
        self.hSliderTauCoh.setValue(slider)
        self._update_canvas()

    def handleInpTauInc(self):
        "input tau inc handler"
        text   = self.inpTauInc.text()
        slider = int(log10(float(text))*1000)
        self.hSliderTauInc.setValue(slider)
        self._update_canvas()

    def handleInpTauEcho(self):
        "input tau echo handler"
        text   = self.inpTauEcho.text()
        slider = int(log10(float(text))*1000)
        self.hSliderTauEcho.setValue(slider)
        self._update_canvas()

    def _reset_values(self):
        "reset simulator values"
        self.dblSpinBoxCoh.setValue(90)
        self.dblSpinBoxInc.setValue(30)
        self.dblSpinBoxCoh.setValue(104)
        self.dblSpinBoxInc.setValue(12)
        self.dblSpinBoxBgr.setValue(0)
        self.hSliderTauCoh.setValue(10000)
        self.hSliderTauInc.setValue(0)
        self.hSliderTauEcho.setValue(1000)
        #self.inpTauCoh.setText("100.000")
        #self.inpTauInc.setText("0.010")
        #self.inpTauEcho.setText("1.00")
        self.hSliderTauCoh.setValue( 2000)
        self.hSliderTauInc.setValue(-3000)
        self.hSliderTauEcho.setValue(1)

    def _init_canvas(self):
        "initialize canvas"
        tmin, tmax = 1e-3, 1e+3
        ntbin = 42 # number of TOF bins
        lbin  = np.linspace(5,8,ntbin+1)*ANGSTROM # neutron wavelength bins
        lbin  = np.linspace(self.lmin,self.lmax,self.ntbin+1)*ANGSTROM # neutron wavelength bins
        xt    = np.logspace(log10(self.tmin), log10(self.tmax), 201) # for S(Q,t)
        djmax = 3*pi/(self.lmax*ANGSTROM)/OMEGA_N # field integral range (-3pi,+3pi)
        self.lave  = (lbin[1:]+lbin[:-1])/2 # average wavelength
        self.dlam  = (lbin[1:]-lbin[:-1])/2 # delta-lambda
        self.flux  = (self.lave[0]/self.lave)**2
        self.djmax = 3*np.pi/np.average(lbin)/OMEGA_N # field integral range (-3pi,+3pi)
        self.dj    = np.linspace(-self.djmax, self.djmax, 201)  # for plotting dj points
        self.xt    = np.logspace(np.log10(tmin), np.log10(tmax), 201) # for plotting tau points 10^{-3},10^{+3}
        self.dj    = np.linspace(-djmax, djmax, 201)  # for plotting dj points
        self.dj2   = np.linspace(-djmax, djmax,  11)  # for plotting dj points
        self.xt    = xt

        ax = self._ax[0]
        self.lines = {}
        self.lines['sqt'], = ax.plot(self.xt, np.zeros_like(self.xt), 'r-',  lw=2, label='I(Q,t)')
        self.lines['coh'], = ax.plot(self.xt, np.zeros_like(self.xt), 'g--', lw=1, label=r'$f_{coh}$')
        self.lines['inc'], = ax.plot(self.xt, np.zeros_like(self.xt), 'b-.', lw=1, label=r'$f_{inc}$')
        self.lines['sqt'], = ax.plot(xt, np.zeros_like(xt), 'r-',  lw=2, label='I(Q,t)')
        self.lines['coh'], = ax.plot(xt, np.zeros_like(xt), 'g--', lw=1, label=r'$f_{coh}$')
        self.lines['inc'], = ax.plot(xt, np.zeros_like(xt), 'b-.', lw=1, label=r'$f_{inc}$')
        self.lines['tau']  = ax.axvline(0,          lw=1.0, ls='-', color='k')
        #self.lines['1/e']  = ax.axhline(exp(-1), lw=0.5, ls='--', color='k')
        ax.set_xscale('log')
        ax.set_xlim(min(self.xt), max(self.xt))
        ax.set_xlim(min(xt), max(xt))
        ax.set_xlabel(r'$\tau$ [ns]')
        ax.set_ylabel(r'$I(Q,\tau)$')
        ax.legend(loc=0)
@@ -172,6 +198,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):

        ax = self._ax[1]
        self.lines['echo'],  = ax.plot(self.dj/MICRO, np.zeros_like(self.dj),  'k-', lw=2)
        self.lines['echo2'], = ax.plot(self.dj2/MICRO, np.zeros_like(self.dj2),'ro', lw=2)
        self.lines['up']    = ax.axhline(2.0, lw=1, ls='--', color='red')
        self.lines['dn']    = ax.axhline(1.0, lw=1, ls='--', color='blue')
        self.lines['ave']   = ax.axhline(1.5, lw=2, ls='-',  color='k')
@@ -184,7 +211,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
        "draw all figures"
        #pylint: disable=too-many-statements
        #FIXME: refactor
        beta = 1
        coh   = self.dblSpinBoxCoh.value()
        inc   = self.dblSpinBoxInc.value()
        bgr   = self.dblSpinBoxBgr.value()
@@ -192,34 +218,29 @@ class MainWindow(QMainWindow, Ui_MainWindow):
        tinc  = 10**(self.hSliderTauInc.value()/1000.0)
        techo = 10**(self.hSliderTauEcho.value()/1000.0)

        #self._fmt_time(self.lblTauCoh, tcoh)
        #self._fmt_time(self.lblTauInc, tinc)
        #self._fmt_time(self.lblTauEcho, techo)

        ax = self._ax[1]
        # check inputs
        if inc<0 or coh<0:
            ax.set_title('!!! Coherent or Incoherent cannot be negative !!!')
            ax.figure.canvas.draw()
            return
        dn  = 2/3*inc+bgr
        up  = coh+1/3*inc+bgr
        dn  = 2/3*(inc+bgr)
        up  = coh+1/3*(inc+bgr)
        if up>0 and dn>0:
            fratio = up/dn
        else:
            ax.set_title('!!! Up and Down counts must be positive')
            ax.figure.canvas.draw()
            return
        norm = coh - 1/3*inc + bgr
        if norm==0:
        if (up-dn)==0:
            ax.set_title('!!! Invalid input')
            ax.figure.canvas.draw()
            return

        ax.set_title(r'FR=%.3g (U=%.0f/D=%.0f)' % (fratio,up,dn))
        ax.set_title(rf'FR={fratio:.3g} (U={up:.0f}/D={dn:.0f})')

        ave = (up+dn)/2
        amp = (up-dn)/2*sqt(techo,coh,tcoh,inc,tinc,beta)/norm
        amp = (up-dn)/2*sqt(techo,coh,tcoh,inc,tinc,bgr)/(up-dn)

        dj   = self.dj
        lave = self.lave
@@ -229,6 +250,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):

        yecho  = echo_shape(dj, lave, dlam,flux)*amp + ave
        self.lines['echo'].set_data(dj/MICRO, yecho)

        yecho2 = echo_shape(self.dj2, lave, dlam,flux)*amp + ave
        self.lines['echo2'].set_data(self.dj2/MICRO, yecho2)

        self.lines['up'].set_data(dj/MICRO,up*np.ones_like(dj))
        self.lines['dn'].set_data(dj/MICRO,dn*np.ones_like(dj))
        self.lines['ave'].set_data(dj/MICRO,ave*np.ones_like(dj))
@@ -237,9 +262,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
        top = max(np.amax(yecho), up, dn)*1.1
        ax.set_ylim(bottom=bot, top=top)

        ysqt = sqt(xt, coh, tcoh, inc, tinc,beta)/norm
        ysqt = sqt(xt, coh, tcoh, inc, tinc)/(up-dn)
        ycoh = f_exp(xt, tcoh)
        yinc = f_exp(xt, tinc)

        self.lines['sqt'].set_data(xt, ysqt)
        self.lines['coh'].set_data(xt, ycoh)
        self.lines['inc'].set_data(xt, yinc)
Loading