4
4
import numpy as np
5
5
from typing import Dict , Any , Callable
6
6
import sys
7
+ from functools import partial
7
8
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 \n Please 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 \n Please install PyQt6, and matplotlib to enable interactive "
16
+ "outside of Jupyter notebooks."
17
+ )
18
+ raise
22
19
23
20
24
21
def make_widget (
@@ -31,46 +28,62 @@ def make_widget(
31
28
32
29
33
30
class FloatSlider (QtWidgets .QSlider ):
31
+ # Qt sadly does not have a float slider, so we have to
32
+ # implement one ourselves.
34
33
floatValueChanged = QtCore .pyqtSignal (float )
35
34
36
- def __init__ (self ):
35
+ def __init__ (self , label ):
37
36
super ().__init__ (QtCore .Qt .Orientation .Horizontal )
38
37
super ().setMinimum (0 )
39
- super ().setMaximum (10000 )
40
- super ().setValue (5000 )
38
+ super ().setMaximum (int ( 1e8 ) )
39
+ super ().setValue (int ( 5e7 ) )
41
40
self ._min = 0.0
42
41
self ._max = 1.0
42
+ self ._value = 0.5
43
+ self ._label = label
43
44
self .valueChanged .connect (self ._emit_float_value_changed )
44
45
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 )
48
51
49
52
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 )
51
54
52
55
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 )
54
57
55
58
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
59
61
self ._min = min_value
60
- self .setValue (val )
62
+ self .setValue (self . _value )
61
63
62
64
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
66
67
self ._max = max_value
67
- self .setValue (val )
68
+ self .setValue (self . _value )
68
69
69
70
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 )
71
84
72
85
def value (self ):
73
- return self ._int_to_float ( super (). value ())
86
+ return self ._value
74
87
75
88
76
89
class Parameter (QtWidgets .QGroupBox ):
@@ -96,7 +109,7 @@ def __init__(self, minuit, par, callback):
96
109
alignment = QtCore .Qt .AlignmentFlag .AlignCenter )
97
110
self .value_label .setMinimumSize (QtCore .QSize (50 , 0 ))
98
111
# Add value slider
99
- self .slider = FloatSlider ()
112
+ self .slider = FloatSlider (self . value_label )
100
113
# Add spin boxes for changing the limits
101
114
self .tmin = QtWidgets .QDoubleSpinBox (
102
115
alignment = QtCore .Qt .AlignmentFlag .AlignCenter )
@@ -151,6 +164,9 @@ def __init__(self, minuit, par, callback):
151
164
self .tmin .setSingleStep (1e-1 * (vmax2 - vmin2 ))
152
165
self .tmax .setValue (vmax2 )
153
166
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 )
154
170
# Set up the slider
155
171
self .slider .setMinimum (vmin2 )
156
172
self .slider .setMaximum (vmax2 )
@@ -167,15 +183,15 @@ def __init__(self, minuit, par, callback):
167
183
self .fit .clicked .connect (self .on_fit_toggled )
168
184
169
185
def on_val_change (self , val ):
170
- print ("val change" , val )
171
186
self .minuit .values [self .par ] = val
172
- self .value_label .setText (f"{ val :.3g} " )
173
187
self .callback ()
174
188
175
189
def on_min_change (self ):
176
190
tmin = self .tmin .value ()
177
191
if tmin >= self .tmax .value ():
192
+ self .tmin .blockSignals (True )
178
193
self .tmin .setValue (self .minuit .limits [self .par ][0 ])
194
+ self .tmin .blockSignals (False )
179
195
return
180
196
self .slider .setMinimum (tmin )
181
197
lim = self .minuit .limits [self .par ]
@@ -184,14 +200,15 @@ def on_min_change(self):
184
200
def on_max_change (self ):
185
201
tmax = self .tmax .value ()
186
202
if tmax <= self .tmin .value ():
203
+ self .tmax .blockSignals (True )
187
204
self .tmax .setValue (self .minuit .limits [self .par ][1 ])
205
+ self .tmax .blockSignals (False )
188
206
return
189
207
self .slider .setMaximum (tmax )
190
208
lim = self .minuit .limits [self .par ]
191
209
minuit .limits [self .par ] = (lim [0 ], tmax )
192
210
193
211
def on_fix_toggled (self ):
194
- print ("fix toggled" )
195
212
self .minuit .fixed [self .par ] = self .fix .isChecked ()
196
213
if self .fix .isChecked ():
197
214
self .fit .setChecked (False )
@@ -200,18 +217,26 @@ def on_fit_toggled(self):
200
217
self .slider .setEnabled (not self .fit .isChecked ())
201
218
if self .fit .isChecked ():
202
219
self .fix .setChecked (False )
203
- self .on_fix_toggled ()
220
+ self .minuit . fixed [ self . par ] = False
204
221
self .callback ()
205
222
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
207
237
self .slider .blockSignals (True )
208
238
self .slider .setValue (val )
209
239
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 ])
215
240
self .slider .blockSignals (False )
216
241
217
242
@@ -257,7 +282,7 @@ def __init__(self):
257
282
button_layout = QtWidgets .QHBoxLayout (button_group )
258
283
self .fit_button = QtWidgets .QPushButton ("Fit" , parent = button_group )
259
284
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 ) )
261
286
button_layout .addWidget (self .fit_button )
262
287
self .update_button = QtWidgets .QPushButton ("Continuous" , parent = button_group )
263
288
self .update_button .setCheckable (True )
@@ -292,12 +317,12 @@ def __init__(self):
292
317
parameter_layout .addStretch ()
293
318
# Results tab
294
319
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 )
301
326
# Remember the original values and limits
302
327
self .original_values = minuit .values [:]
303
328
self .original_limits = minuit .limits [:]
@@ -325,7 +350,12 @@ def on_parameter_change(self, from_fit=False,
325
350
minuit .fixed [i ] = not x .fit .isChecked ()
326
351
from_fit = True
327
352
report_success = self .do_fit (plot = False )
353
+ self .results_text .clear ()
354
+ self .results_text .setHtml (minuit ._repr_html_ ())
328
355
minuit .fixed = saved
356
+ else :
357
+ self .results_text .clear ()
358
+ self .results_text .setHtml (minuit ._repr_html_ ())
329
359
330
360
self .canvas .figure .clear ()
331
361
self .plot_with_frame (from_fit , report_success )
@@ -334,7 +364,7 @@ def on_parameter_change(self, from_fit=False,
334
364
def do_fit (self , plot = True ):
335
365
report_success = self .fit ()
336
366
for i , x in enumerate (self .parameters ):
337
- x .reset (minuit .values [i ])
367
+ x .reset (val = minuit .values [i ])
338
368
if not plot :
339
369
return report_success
340
370
self .on_parameter_change (
@@ -349,7 +379,7 @@ def on_reset_button_clicked(self):
349
379
minuit .values = self .original_values
350
380
minuit .limits = self .original_limits
351
381
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 )
353
383
self .on_parameter_change ()
354
384
355
385
def plot_with_frame (self , from_fit , report_success ):
0 commit comments