Skip to content

Commit db6c6bb

Browse files
committed
Fixed bugs and added results tab
1 parent a99f95e commit db6c6bb

File tree

1 file changed

+82
-52
lines changed

1 file changed

+82
-52
lines changed

src/iminuit/qtwidget.py

+82-52
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,18 @@
44
import numpy as np
55
from typing import Dict, Any, Callable
66
import sys
7+
from functools import partial
78

8-
with warnings.catch_warnings():
9-
# ipywidgets produces deprecation warnings through use of internal APIs :(
10-
warnings.simplefilter("ignore")
11-
try:
12-
from PyQt6 import QtCore, QtGui, QtWidgets
13-
from matplotlib.figure import Figure
14-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
15-
from matplotlib import pyplot as plt
16-
except ModuleNotFoundError as e:
17-
e.msg += (
18-
"\n\nPlease install PyQt6, and matplotlib to enable interactive "
19-
"outside of Jupyter notebooks."
20-
)
21-
raise
9+
try:
10+
from PyQt6 import QtCore, QtGui, QtWidgets
11+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
12+
from matplotlib import pyplot as plt
13+
except ModuleNotFoundError as e:
14+
e.msg += (
15+
"\n\nPlease install PyQt6, and matplotlib to enable interactive "
16+
"outside of Jupyter notebooks."
17+
)
18+
raise
2219

2320

2421
def make_widget(
@@ -31,46 +28,62 @@ def make_widget(
3128

3229

3330
class FloatSlider(QtWidgets.QSlider):
31+
# Qt sadly does not have a float slider, so we have to
32+
# implement one ourselves.
3433
floatValueChanged = QtCore.pyqtSignal(float)
3534

36-
def __init__(self):
35+
def __init__(self, label):
3736
super().__init__(QtCore.Qt.Orientation.Horizontal)
3837
super().setMinimum(0)
39-
super().setMaximum(10000)
40-
super().setValue(5000)
38+
super().setMaximum(int(1e8))
39+
super().setValue(int(5e7))
4140
self._min = 0.0
4241
self._max = 1.0
42+
self._value = 0.5
43+
self._label = label
4344
self.valueChanged.connect(self._emit_float_value_changed)
4445

45-
def _emit_float_value_changed(self, value):
46-
float_value = self._int_to_float(value)
47-
self.floatValueChanged.emit(float_value)
46+
def _emit_float_value_changed(self, value=None):
47+
if value is not None:
48+
self._value = self._int_to_float(value)
49+
self._label.setText(f"{self._value:.3g}")
50+
self.floatValueChanged.emit(self._value)
4851

4952
def _int_to_float(self, value):
50-
return self._min + (value / 10000) * (self._max - self._min)
53+
return self._min + (value / 1e8) * (self._max - self._min)
5154

5255
def _float_to_int(self, value):
53-
return int((value - self._min) / (self._max - self._min) * 10000)
56+
return int((value - self._min) / (self._max - self._min) * 1e8)
5457

5558
def setMinimum(self, min_value):
56-
val = self.value()
57-
if val <= min_value:
58-
val = min_value
59+
if self._max <= min_value:
60+
return
5961
self._min = min_value
60-
self.setValue(val)
62+
self.setValue(self._value)
6163

6264
def setMaximum(self, max_value):
63-
val = self.value()
64-
if val >= max_value:
65-
val = max_value
65+
if self._min >= max_value:
66+
return
6667
self._max = max_value
67-
self.setValue(val)
68+
self.setValue(self._value)
6869

6970
def setValue(self, value):
70-
super().setValue(self._float_to_int(value))
71+
if value < self._min:
72+
self._value = self._min
73+
super().setValue(0)
74+
self._emit_float_value_changed()
75+
elif value > self._max:
76+
self._value = self._max
77+
super().setValue(int(1e8))
78+
self._emit_float_value_changed()
79+
else:
80+
self._value = value
81+
self.blockSignals(True)
82+
super().setValue(self._float_to_int(value))
83+
self.blockSignals(False)
7184

7285
def value(self):
73-
return self._int_to_float(super().value())
86+
return self._value
7487

7588

7689
class Parameter(QtWidgets.QGroupBox):
@@ -96,7 +109,7 @@ def __init__(self, minuit, par, callback):
96109
alignment=QtCore.Qt.AlignmentFlag.AlignCenter)
97110
self.value_label.setMinimumSize(QtCore.QSize(50, 0))
98111
# Add value slider
99-
self.slider = FloatSlider()
112+
self.slider = FloatSlider(self.value_label)
100113
# Add spin boxes for changing the limits
101114
self.tmin = QtWidgets.QDoubleSpinBox(
102115
alignment=QtCore.Qt.AlignmentFlag.AlignCenter)
@@ -151,6 +164,9 @@ def __init__(self, minuit, par, callback):
151164
self.tmin.setSingleStep(1e-1 * (vmax2 - vmin2))
152165
self.tmax.setValue(vmax2)
153166
self.tmax.setSingleStep(1e-1 * (vmax2 - vmin2))
167+
# Remember the original values and limits
168+
self.original_value = val
169+
self.original_limits = (vmin2, vmax2)
154170
# Set up the slider
155171
self.slider.setMinimum(vmin2)
156172
self.slider.setMaximum(vmax2)
@@ -167,15 +183,15 @@ def __init__(self, minuit, par, callback):
167183
self.fit.clicked.connect(self.on_fit_toggled)
168184

169185
def on_val_change(self, val):
170-
print("val change", val)
171186
self.minuit.values[self.par] = val
172-
self.value_label.setText(f"{val:.3g}")
173187
self.callback()
174188

175189
def on_min_change(self):
176190
tmin = self.tmin.value()
177191
if tmin >= self.tmax.value():
192+
self.tmin.blockSignals(True)
178193
self.tmin.setValue(self.minuit.limits[self.par][0])
194+
self.tmin.blockSignals(False)
179195
return
180196
self.slider.setMinimum(tmin)
181197
lim = self.minuit.limits[self.par]
@@ -184,14 +200,15 @@ def on_min_change(self):
184200
def on_max_change(self):
185201
tmax = self.tmax.value()
186202
if tmax <= self.tmin.value():
203+
self.tmax.blockSignals(True)
187204
self.tmax.setValue(self.minuit.limits[self.par][1])
205+
self.tmax.blockSignals(False)
188206
return
189207
self.slider.setMaximum(tmax)
190208
lim = self.minuit.limits[self.par]
191209
minuit.limits[self.par] = (lim[0], tmax)
192210

193211
def on_fix_toggled(self):
194-
print("fix toggled")
195212
self.minuit.fixed[self.par] = self.fix.isChecked()
196213
if self.fix.isChecked():
197214
self.fit.setChecked(False)
@@ -200,18 +217,26 @@ def on_fit_toggled(self):
200217
self.slider.setEnabled(not self.fit.isChecked())
201218
if self.fit.isChecked():
202219
self.fix.setChecked(False)
203-
self.on_fix_toggled()
220+
self.minuit.fixed[self.par] = False
204221
self.callback()
205222

206-
def reset(self, val, limits=None):
223+
def reset(self, val=None, limits=False):
224+
if limits:
225+
self.slider.blockSignals(True)
226+
self.slider.setMinimum(self.original_limits[0])
227+
self.slider.blockSignals(True)
228+
self.slider.setMaximum(self.original_limits[1])
229+
self.tmin.blockSignals(True)
230+
self.tmin.setValue(self.original_limits[0])
231+
self.tmin.blockSignals(False)
232+
self.tmax.blockSignals(True)
233+
self.tmax.setValue(self.original_limits[1])
234+
self.tmax.blockSignals(False)
235+
if val is None:
236+
val = self.original_value
207237
self.slider.blockSignals(True)
208238
self.slider.setValue(val)
209239
self.value_label.setText(f"{val:.3g}")
210-
if limits:
211-
self.slider.setMinimum(limits[0])
212-
self.slider.setMaximum(limits[1])
213-
self.tmin.setValue(limits[0])
214-
self.tmax.setValue(limits[1])
215240
self.slider.blockSignals(False)
216241

217242

@@ -257,7 +282,7 @@ def __init__(self):
257282
button_layout = QtWidgets.QHBoxLayout(button_group)
258283
self.fit_button = QtWidgets.QPushButton("Fit", parent=button_group)
259284
self.fit_button.setStyleSheet("background-color: #2196F3; color: white")
260-
self.fit_button.clicked.connect(self.do_fit)
285+
self.fit_button.clicked.connect(partial(self.do_fit, plot=True))
261286
button_layout.addWidget(self.fit_button)
262287
self.update_button = QtWidgets.QPushButton("Continuous", parent=button_group)
263288
self.update_button.setCheckable(True)
@@ -292,12 +317,12 @@ def __init__(self):
292317
parameter_layout.addStretch()
293318
# Results tab
294319
results_layout = QtWidgets.QVBoxLayout(results_tab)
295-
results_text = QtWidgets.QPlainTextEdit(parent=results_tab)
296-
font = QtGui.QFont()
297-
font.setFamily("FreeMono")
298-
results_text.setFont(font)
299-
results_text.setReadOnly(True)
300-
results_layout.addWidget(results_text)
320+
self.results_text = QtWidgets.QTextEdit(parent=results_tab)
321+
#font = QtGui.QFont()
322+
#font.setFamily("FreeMono")
323+
#self.results_text.setFont(font)
324+
self.results_text.setReadOnly(True)
325+
results_layout.addWidget(self.results_text)
301326
# Remember the original values and limits
302327
self.original_values = minuit.values[:]
303328
self.original_limits = minuit.limits[:]
@@ -325,7 +350,12 @@ def on_parameter_change(self, from_fit=False,
325350
minuit.fixed[i] = not x.fit.isChecked()
326351
from_fit = True
327352
report_success = self.do_fit(plot=False)
353+
self.results_text.clear()
354+
self.results_text.setHtml(minuit._repr_html_())
328355
minuit.fixed = saved
356+
else:
357+
self.results_text.clear()
358+
self.results_text.setHtml(minuit._repr_html_())
329359

330360
self.canvas.figure.clear()
331361
self.plot_with_frame(from_fit, report_success)
@@ -334,7 +364,7 @@ def on_parameter_change(self, from_fit=False,
334364
def do_fit(self, plot=True):
335365
report_success = self.fit()
336366
for i, x in enumerate(self.parameters):
337-
x.reset(minuit.values[i])
367+
x.reset(val=minuit.values[i])
338368
if not plot:
339369
return report_success
340370
self.on_parameter_change(
@@ -349,7 +379,7 @@ def on_reset_button_clicked(self):
349379
minuit.values = self.original_values
350380
minuit.limits = self.original_limits
351381
for i, x in enumerate(self.parameters):
352-
x.reset(minuit.values[i], minuit.limits[i])
382+
x.reset(val=minuit.values[i], limits=True)
353383
self.on_parameter_change()
354384

355385
def plot_with_frame(self, from_fit, report_success):

0 commit comments

Comments
 (0)