diff --git a/nmreval/gui_qt/_py/basewindow.py b/nmreval/gui_qt/_py/basewindow.py
index ab7e51c..5e9fe2a 100644
--- a/nmreval/gui_qt/_py/basewindow.py
+++ b/nmreval/gui_qt/_py/basewindow.py
@@ -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
diff --git a/nmreval/gui_qt/lib/stuff.py b/nmreval/gui_qt/lib/stuff.py
index 3c2440c..9af81bb 100644
--- a/nmreval/gui_qt/lib/stuff.py
+++ b/nmreval/gui_qt/lib/stuff.py
@@ -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}')
+
diff --git a/nmreval/gui_qt/main/mainwindow.py b/nmreval/gui_qt/main/mainwindow.py
index 5eb8e42..cca3b5b 100644
--- a/nmreval/gui_qt/main/mainwindow.py
+++ b/nmreval/gui_qt/main/mainwindow.py
@@ -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:
diff --git a/nmreval/models/__init__.py b/nmreval/models/__init__.py
index 56158df..68c2498 100755
--- a/nmreval/models/__init__.py
+++ b/nmreval/models/__init__.py
@@ -31,4 +31,5 @@ from .bds import *
from .temperature import *
from .transitions import *
from .correlationfuncs import *
+from .spectrum import *
from .wideline import *
diff --git a/nmreval/models/spectrum.py b/nmreval/models/spectrum.py
new file mode 100644
index 0000000..2a7627f
--- /dev/null
+++ b/nmreval/models/spectrum.py
@@ -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
diff --git a/nmreval/models/wideline.py b/nmreval/models/wideline.py
index ccdf5a2..ac5176b 100644
--- a/nmreval/models/wideline.py
+++ b/nmreval/models/wideline.py
@@ -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)
diff --git a/resources/_ui/basewindow.ui b/resources/_ui/basewindow.ui
index 7d72d0e..23dfb11 100644
--- a/resources/_ui/basewindow.ui
+++ b/resources/_ui/basewindow.ui
@@ -308,6 +308,7 @@
+
@@ -970,7 +971,7 @@
- Worms
+ Worm
@@ -993,6 +994,11 @@
Look for updates
+
+
+ Mine
+
+