1
0
forked from IPKM/nmreval

added Gaussian, Lorentzian, Pseudo-Voigt

This commit is contained in:
dominik 2022-04-05 16:54:12 +02:00
parent 531eac22b7
commit 74dacd0180
7 changed files with 418 additions and 24 deletions

View File

@ -2,12 +2,14 @@
# Form implementation generated from reading ui file 'resources/_ui/basewindow.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
# Created by: PyQt5 UI code generator 5.12.3
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_BaseWindow(object):
def setupUi(self, BaseWindow):
BaseWindow.setObjectName("BaseWindow")
@ -355,6 +357,8 @@ class Ui_BaseWindow(object):
self.actionTetris.setObjectName("actionTetris")
self.actionUpdate = QtWidgets.QAction(BaseWindow)
self.actionUpdate.setObjectName("actionUpdate")
self.actionMine = QtWidgets.QAction(BaseWindow)
self.actionMine.setObjectName("actionMine")
self.menuSave.addAction(self.actionSave)
self.menuSave.addAction(self.actionExportGraphic)
self.menuSave.addAction(self.action_save_fit_parameter)
@ -444,6 +448,7 @@ class Ui_BaseWindow(object):
self.menuStuff.addAction(self.actionSnake)
self.menuStuff.addAction(self.actionLife)
self.menuStuff.addAction(self.actionTetris)
self.menuStuff.addAction(self.actionMine)
self.menubar.addAction(self.menuFile.menuAction())
self.menubar.addAction(self.menuWindow.menuAction())
self.menubar.addAction(self.menuData.menuAction())
@ -594,12 +599,12 @@ class Ui_BaseWindow(object):
self.action_no_range.setText(_translate("BaseWindow", "None"))
self.action_x_range.setText(_translate("BaseWindow", "Visible x range"))
self.action_custom_range.setText(_translate("BaseWindow", "Custom"))
self.actionSnake.setText(_translate("BaseWindow", "Worms"))
self.actionSnake.setText(_translate("BaseWindow", "Worm"))
self.actionFunction_editor.setText(_translate("BaseWindow", "Function editor..."))
self.actionLife.setText(_translate("BaseWindow", "Life..."))
self.actionTetris.setText(_translate("BaseWindow", "Not Tetris"))
self.actionUpdate.setText(_translate("BaseWindow", "Look for updates"))
self.actionMine.setText(_translate("BaseWindow", "Mine"))
from ..data.datawidget.datawidget import DataWidget
from ..data.point_select import PointSelectWidget
from ..data.signaledit.editsignalwidget import EditSignalWidget

View File

@ -1,4 +1,5 @@
import random
import time
import numpy as np
@ -144,9 +145,9 @@ class Board(QtWidgets.QFrame):
class SnakeBoard(Board):
SPEED = 100
WIDTH = 30
HEIGHT = 30
SPEED = 125
WIDTH = 35
HEIGHT = 35
def __init__(self, parent=None):
super().__init__(parent=parent)
@ -155,6 +156,9 @@ class SnakeBoard(Board):
[int(SnakeBoard.WIDTH//2)+1, int(SnakeBoard.HEIGHT//2)]]
self.current_x_head, self.current_y_head = self.snake[0]
self.direction = 'l'
self.next_direction = []
self._direction_pairs = {'l': 'r', 'u': 'd', 'r': 'l', 'd': 'u'}
self.food = None
self.grow_snake = False
@ -176,6 +180,12 @@ class SnakeBoard(Board):
self.update()
def snake_move(self):
print(self.next_direction)
if self.next_direction:
turn = self.next_direction.pop()
if self.direction != self._direction_pairs[turn]:
self.direction = turn
if self.direction == 'l':
self.current_x_head -= 1
elif self.direction == 'r':
@ -187,7 +197,7 @@ class SnakeBoard(Board):
elif self.direction == 'd':
self.current_y_head += 1
head = [self.current_x_head, self.current_y_head]
head = (self.current_x_head, self.current_y_head)
self.snake.insert(0, head)
if not self.grow_snake:
@ -207,11 +217,11 @@ class SnakeBoard(Board):
x = random.randint(3, SnakeBoard.WIDTH-3)
y = random.randint(3, SnakeBoard.HEIGHT-3)
while [x, y] == self.snake[0]:
while (x, y) in self.snake:
x = random.randint(3, SnakeBoard.WIDTH-3)
y = random.randint(3, SnakeBoard.HEIGHT-3)
self.food = [x, y]
self.food = (x, y)
def check_death(self):
rip_message = ''
@ -234,20 +244,16 @@ class SnakeBoard(Board):
def keyPressEvent(self, event):
key = event.key()
if key in (QtCore.Qt.Key_Left, QtCore.Qt.Key_A):
if self.direction != 'r':
self.direction = 'l'
self.next_direction.append('l')
elif key in (QtCore.Qt.Key_Right, QtCore.Qt.Key_D):
if self.direction != 'l':
self.direction = 'r'
self.next_direction.append('r')
elif key in (QtCore.Qt.Key_Down, QtCore.Qt.Key_S):
if self.direction != 'u':
self.direction = 'd'
self.next_direction.append('d')
elif key in (QtCore.Qt.Key_Up, QtCore.Qt.Key_W):
if self.direction != 'd':
self.direction = 'u'
self.next_direction.append('u')
else:
return super().keyPressEvent(event)
@ -493,3 +499,280 @@ class MirrorL(Tetromino):
SHAPE = np.array([[1, 0, 0, 0],
[-1, -1, 0, 1]])
color = 'lightGray'
class Field(QtWidgets.QToolButton):
NUM_COLORS = {
1: '#1e46a4',
2: '#f28e2b',
3: '#e15759',
4: '#76b7b2',
5: '#59a14f',
6: '#edc948',
7: '#b07aa1',
8: '#ff9da7',
'X': '#ff0000',
}
flag_change = QtCore.pyqtSignal(bool)
def __init__(self, x, y, parent=None):
super(Field, self).__init__(parent=parent)
self.setFixedSize(QtCore.QSize(30, 30))
f = self.font()
f.setPointSize(24)
f.setWeight(75)
self.setFont(f)
self.x = x
self.y = y
self.neighbors = []
self.grid = self.parent()
_size = self.grid.gridsize
for x_i in range(max(0, self.x-1), min(self.x+2, _size[0])):
for y_i in range(max(0, self.y-1), min(self.y+2, _size[1])):
if (x_i, y_i) == (self.x, self.y):
continue
self.neighbors.append((x_i, y_i))
self.is_mine = False
self.is_flagged = False
self.is_known = False
self.has_died = False
def mousePressEvent(self, evt: QtGui.QMouseEvent) -> None:
if self.grid.status == 'finished':
return
if evt.button() == QtCore.Qt.RightButton:
self.set_flag()
elif evt.button() == QtCore.Qt.LeftButton:
self.select()
else:
super().mousePressEvent(evt)
def __str__(self) -> str:
if self.is_mine:
return 'X'
else:
return str(self.visible_mines)
@property
def visible_mines(self) -> int:
return sum(self.grid.map[x_i][y_i].is_mine for x_i, y_i in self.neighbors)
@QtCore.pyqtSlot()
def select(self):
if self.is_flagged:
return
if self.is_mine:
self.setText('X')
num = 'X'
self.has_died = True
self.setStyleSheet(f'color: {self.NUM_COLORS[num]}')
else:
self.is_known = True
self.setEnabled(False)
self.setAutoRaise(True)
num = self.visible_mines
if num != 0:
self.setText(str(num))
self.setStyleSheet(f'color: {self.NUM_COLORS[num]}')
else:
self.grid.reveal_neighbors(self)
self.clicked.emit()
@QtCore.pyqtSlot()
def set_flag(self):
if self.is_flagged:
self.setText('')
else:
self.setText('!')
self.is_flagged = not self.is_flagged
self.has_died = False
self.clicked.emit()
self.flag_change.emit(self.is_flagged)
class QMines(QtWidgets.QMainWindow):
LEVELS = {
'easy': ((9, 9), 10),
'middle': ((16, 16), 40),
'hard': ((16, 30), 99),
'very hard': ((16, 30), 10),
}
def __init__(self, parent=None):
super().__init__(parent=parent)
self.map = []
self.status = 'waiting'
self.open_fields = 0
self.num_flags = 0
self._start = 0
self.gridsize, self.mines = (1, 1), 1
self._init_ui()
def _init_ui(self):
layout = QtWidgets.QGridLayout()
layout.setSpacing(0)
self.central = QtWidgets.QWidget()
self.setCentralWidget(self.central)
layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding),
0, 0)
self.grid_layout = QtWidgets.QGridLayout()
self.grid_layout.setSpacing(0)
self.map_widget = QtWidgets.QFrame()
self.map_widget.setFrameStyle(2)
self.map_widget.setLayout(self.grid_layout)
layout.addWidget(self.map_widget, 1, 1)
self.new_game = QtWidgets.QPushButton('New game')
self.new_game.pressed.connect(self._init_map)
layout.addWidget(self.new_game, 0, 1)
layout.addItem(QtWidgets.QSpacerItem(0, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding),
2, 2)
self.central.setLayout(layout)
self.timer = QtCore.QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.update_time)
self.timer_message = QtWidgets.QLabel('0 s')
self.statusBar().addWidget(self.timer_message)
self.mine_message = QtWidgets.QLabel(f'0 / {self.mines}')
self.statusBar().addWidget(self.mine_message)
self.dead_message = QtWidgets.QLabel('')
self.statusBar().addWidget(self.dead_message)
self.level_cb = QtWidgets.QComboBox()
self.level_cb.addItems(list(self.LEVELS.keys()))
self.level_cb.currentTextChanged.connect(self._init_map)
self.statusBar().addPermanentWidget(self.level_cb)
self._init_map('easy')
def _init_map(self, lvl: str = None):
if lvl is None:
lvl = self.level_cb.currentText()
self._clear_map()
self.gridsize, self.mines = QMines.LEVELS[lvl]
w, h = self.gridsize
self.map = [[None] * h for _ in range(w)]
for x in range(w):
for y in range(h):
pos = Field(x, y, parent=self)
pos.clicked.connect(self.start_game)
pos.clicked.connect(self.update_status)
pos.flag_change.connect(self.update_flag)
self.grid_layout.addWidget(pos, x+1, y+1)
self.map[x][y] = pos
self.set_mines()
def _clear_map(self):
self.status = 'waiting'
self.open_fields = 0
self.num_flags = 0
self._start = 0
self.map = []
while self.grid_layout.count():
child = self.grid_layout.takeAt(0)
w = child.widget()
if w:
self.grid_layout.removeWidget(w)
w.deleteLater()
else:
self.grid_layout.removeItem(child)
def set_mines(self):
count = 0
w, h = self.gridsize
while count < self.mines:
n = random.randint(0, w * h - 1)
row = n // h
col = n % h
pos = self.map[row][col] # type: Field
if pos.is_mine:
continue
pos.is_mine = True
count += 1
self.status = 'waiting'
self.open_fields = 0
self.num_flags = 0
self._start = 0
self.timer_message.setText('0 s')
self.mine_message.setText(f'0 / {self.mines}')
self.dead_message.setText('')
def reveal_neighbors(self, pos: Field):
for x_i, y_i in pos.neighbors:
field_i = self.map[x_i][y_i] # type: Field
if field_i.isEnabled():
field_i.select()
def start_game(self):
if self.status == 'waiting':
self.status = 'running'
self._start = time.time()
self.timer.start(1000)
def update_time(self):
if self.status == 'running':
self.timer_message.setText(f'{time.time()-self._start:3.0f} s')
def update_status(self):
if self.status == 'finished':
return
pos = self.sender() # type: Field
if pos.is_known:
self.open_fields += 1
if self.open_fields == self.gridsize[0] * self.gridsize[1] - self.mines:
self.timer.stop()
_ = QtWidgets.QMessageBox.information(self, 'Game finished', 'Game finished!!!')
self.status = 'finished'
elif pos.has_died:
dead_cnt = self.dead_message.text()
if dead_cnt == '':
self.dead_message.setText('(Deaths: 1)')
else:
self.dead_message.setText(f'(Deaths: {int(dead_cnt[9:-1])+1})')
@QtCore.pyqtSlot(bool)
def update_flag(self, state: bool):
num_mines = int(self.mine_message.text().split()[0])
if state:
num_mines += 1
else:
num_mines -= 1
self.mine_message.setText(f'{num_mines} / {self.mines}')

View File

@ -905,6 +905,7 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
@QtCore.pyqtSlot(name='on_actionSnake_triggered')
@QtCore.pyqtSlot(name='on_actionTetris_triggered')
@QtCore.pyqtSlot(name='on_actionLife_triggered')
@QtCore.pyqtSlot(name='on_actionMine_triggered')
def spannung_spiel_und_spass(self):
if self.sender() == self.actionLife:
@ -913,6 +914,12 @@ class NMRMainWindow(QtWidgets.QMainWindow, Ui_BaseWindow):
game.setWindowModality(QtCore.Qt.NonModal)
game.show()
elif self.sender() == self.actionMine:
from ..lib.stuff import QMines
game = QMines(parent=self)
game.setWindowModality(QtCore.Qt.NonModal)
game.show()
else:
from ..lib.stuff import Game
if self.sender() == self.actionSnake:

View File

@ -31,4 +31,5 @@ from .bds import *
from .temperature import *
from .transitions import *
from .correlationfuncs import *
from .spectrum import *
from .wideline import *

View File

@ -0,0 +1,92 @@
from typing import Union
import numpy as np
__all__ = ['Gaussian', 'Lorentzian', 'PseudoVoigt']
ArrayLike = Union[float, np.ndarray]
class Gaussian:
name = 'Gaussian'
equation = 'A*((4ln2/\pi)^{0.5}*(1/w) exp(-4ln2 [(x-\mu)/w]^{2})+A_{0}'
params = ['A', r'\mu', 'w', 'A_{0}']
type = 'Spectrum'
bounds = [(0, None), (None, None), (0, None), (None, None)]
@staticmethod
def func(x: ArrayLike, a: float, mu: float, sigma: float, off: float) -> ArrayLike:
r"""
Calculates Gaussian lineshape
.. math::
f(x) = \frac{A}{\sqrt{2\pi} \sigma} \exp\left[-\frac{(x-\mu)^2}{2\sigma^2} \right] + A_0
Args:
x (float): Frequncy axis
a (float): Amplitude
mu (float): Maximum position
sigma (float): FWHM
off (float): Baseline
"""
return a / (np.sqrt(4*np.log(2)/np.pi) * sigma) * np.exp(-4*np.log(2) * ((x-mu) / sigma)**2) + off
class Lorentzian:
type = 'Spectrum'
name = 'Lorentzian'
equation = 'A (2/\pi)w/[4*(x-\mu)^{2} + w^{2}] + A_{0}'
params = ['A', '\mu', 'w', 'A_{0}']
ext_params = None
bounds = [(0, None), (None, None), (0, None), (None, None)]
@staticmethod
def func(x: ArrayLike, a: float, mu: float, sigma: float, off: float) -> ArrayLike:
r"""
Calculate Lorentzian lineshape
.. math::
f(x) = \frac{A\sigma}{(x-\mu)^2 + \sigma^2} + A_0
Args:
x (float): Frequency axis
a (float): Amplitude
mu (float): Maximum position
sigma (float): FWHM
off (float): baseline
"""
return (a/np.pi) * 2*sigma / (4*(x-mu)**2 + sigma**2) + off
class PseudoVoigt:
type = 'Spectrum'
name = 'Pseudo Voigt'
equation = 'A [R*2/\pi*w/[4*(x-\mu)^{2} + w^{2}] + ' \
'(1-R)*sqrt(4*ln(2)/pi)/w*exp(-4*ln(2)[(x-\mu)/w]^{2})] + A_{0}'
params = ['A', 'R', '\mu', 'w', 'A_{0}']
ext_params = None
bounds = [(0, None), (0, 1), (None, None), (0, None)]
@staticmethod
def func(x: ArrayLike, a: float, r_lor: float, mu: float, sigma: float, off: float) -> ArrayLike:
r"""
Calculate Pseudo-Voigt lineshape, superposition of Lorentzian and Gaussian with shared center and FWHM.
.. math::
f(x) = A \left[ R \frac{2}{\pi} \frac{\sigma}{4(x-\mu)^2 + \sigma^2} +
(1-R)\sqrt{\frac{4\ln2}{\pi}} \frac{1}{\sigma} \exp\left(-4\ln2 \frac{(x-\mu)^2}{\sigma^2} \right) \right] + A_0
Args:
x (array-like): Frequency
a (float): Amplitude
r_lor (float): Relative Lorentzian contribution
mu (float): Maximum position
sigma (float): FWHM
off (float): Baseline
"""
return a * (r_lor * (2/np.pi) * (sigma/(4*(x-mu)**2 + sigma**2)) +
(1-r_lor) * np.sqrt(4*np.log(2)/np.pi)/sigma * np.exp(-4*np.log(2)*((x-mu)/sigma)**2)) + off

View File

@ -9,7 +9,7 @@ from ..math.orientations import zcw_spherical as crystallites
class Pake:
type = 'Wideline'
type = 'Spectrum'
name = 'Pake'
equation = ''
params = ['A', r'\delta', r'\eta', r'\Sigma_{B}', r't_{pulse}']
@ -44,7 +44,7 @@ class Pake:
omega_1 = pi/2/t_pulse
attn = omega_1 * np.sin(t_pulse*np.sqrt(omega_1**2+0.5*(2*pi*x)**2)) / \
np.sqrt(omega_1**2+(np.pi*x)**2)
np.sqrt(omega_1**2+(np.pi*x)**2)
ret_val *= attn
@ -52,7 +52,7 @@ class Pake:
class CSA:
type = 'Wideline'
type = 'Spectrum'
name = 'CSA'
equation = ''
params = ['A', r'\delta', r'\eta', r'\omega_{iso}', r'\Sigma_{B}']
@ -85,8 +85,8 @@ class CSA:
class SecCentralLine:
type = 'Wideline'
name = 'Central Transition 2nd order'
type = 'Spectrum'
name = 'Central Transition (2nd order)'
equation = ''
params = ['A', 'C_{Q}', r'\eta', r'\omega_{iso}', 'GB', r'\omega_{L}']
bounds = [(0, None), (0, None), (0, 1), (None, None), (0, None), (0, None)]
@ -129,4 +129,4 @@ class SecCentralLine:
else:
ret_val = s
return c * ret_val / trapz(ret_val, x)
return c * ret_val / simpson(ret_val, x)

View File

@ -308,6 +308,7 @@
<addaction name="actionSnake"/>
<addaction name="actionLife"/>
<addaction name="actionTetris"/>
<addaction name="actionMine"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuWindow"/>
@ -970,7 +971,7 @@
</action>
<action name="actionSnake">
<property name="text">
<string>Worms</string>
<string>Worm</string>
</property>
</action>
<action name="actionFunction_editor">
@ -993,6 +994,11 @@
<string>Look for updates</string>
</property>
</action>
<action name="actionMine">
<property name="text">
<string>Mine</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>