2525On Windows you will need to remove the quotes.
2626"""
2727
28-
2928import datetime
3029import math
3130import os
3231import re
3332import sys
3433import traceback
35-
36- # pylint: disable=unused-import
37- from typing import Callable
34+ from dataclasses import dataclass
35+ from typing import TYPE_CHECKING , Protocol
3836
3937import numpy as np # type: ignore
4038
4442from .generate import FLOAT_RE , set_integration_size
4543from .modelinfo import ModelInfo , ParameterSet
4644
47- try :
48- # With python 3.8+ we can indicate that calculator takes floats.
49- from typing import Protocol
50- class Calculator (Protocol ):
51- """Kernel calculator takes *par=value* keyword arguments."""
52- def __call__ (self , ** par : float ) -> np .ndarray : ...
53- except ImportError :
54- #: Kernel calculator takes *par=value* keyword arguments.
55- Calculator = Callable [..., np .ndarray ]
56- # pylint: enable=unused-import
45+ # Only use optional bumps dependency for type checking
46+ if TYPE_CHECKING :
47+ from bumps .names import Parameter
48+
49+ class Calculator (Protocol ):
50+ """Kernel calculator takes *par=value* keyword arguments."""
51+ def __call__ (self , ** par : float ) -> np .ndarray : ...
5752
5853USAGE = """
5954usage: sascomp model [options...] [key=val]
@@ -108,7 +103,8 @@ def __call__(self, **par: float) -> np.ndarray: ...
108103 -profile shows the sld profile if the model has a plottable sld profile
109104
110105 === output options ===
111- -edit starts the parameter explorer
106+ -edit starts the parameter explorer (bumps webview)
107+ -wxedit starts the parameter explore (bumps wxgui)
112108 -help/-html shows the model docs instead of running the model
113109
114110 === help ===
@@ -1039,7 +1035,7 @@ def plot_models(opts, result, limits=None, setnum=0):
10391035 'half' , 'fast' , 'single' , 'double' , 'single!' , 'double!' , 'quad!' ,
10401036
10411037 # Output options
1042- 'help' , 'html' , 'edit' ,
1038+ 'help' , 'html' , 'edit' , 'wxedit' ,
10431039
10441040 # Help options
10451041 'h' , '?' , 'models' , 'models='
@@ -1194,7 +1190,7 @@ def parse_opts(argv):
11941190 'show_pars' : False ,
11951191 'show_hist' : False ,
11961192 'rel_err' : True ,
1197- 'explore' : False ,
1193+ 'explore' : None ,
11981194 'zero' : False ,
11991195 'html' : False ,
12001196 'title' : None ,
@@ -1263,7 +1259,8 @@ def parse_opts(argv):
12631259 elif arg == '-single!' : opts ['engine' ] = 'single!'
12641260 elif arg == '-double!' : opts ['engine' ] = 'double!'
12651261 elif arg == '-quad!' : opts ['engine' ] = 'quad!'
1266- elif arg == '-edit' : opts ['explore' ] = True
1262+ elif arg == '-edit' : opts ['explore' ] = 'web'
1263+ elif arg == '-wxedit' : opts ['explore' ] = 'wx'
12671264 elif arg == '-weights' : opts ['show_weights' ] = True
12681265 elif arg == '-profile' : opts ['show_profile' ] = True
12691266 elif arg == '-html' : opts ['html' ] = True
@@ -1568,23 +1565,54 @@ def explore(opts):
15681565 """
15691566 explore the model using the bumps gui.
15701567 """
1568+ from bumps .names import FitProblem # type: ignore
1569+
1570+ model = Explore (opts )
1571+ problem = FitProblem (model )
1572+ name = ":" .join (opts ['name' ]) if opts ['name' ][0 ] == opts ['name' ][1 ] else opts ['name' ][0 ]
1573+ problem .name = name
1574+
1575+ if opts ['explore' ] == 'wx' :
1576+ wx_explore (problem )
1577+ else :
1578+ webview_explore (problem )
1579+
1580+ def webview_explore (problem ):
1581+ import logging
1582+
1583+ import bumps .webview .server .webserver as server
1584+ from aiohttp import web
1585+ from bumps .webview .server import api
1586+ from bumps .webview .server .cli import BumpsOptions
1587+
1588+ logging .getLogger ("webview" ).setLevel (logging .WARNING )
1589+
1590+ server .init_web_app ()
1591+ server .app .on_startup .append (lambda App : api .set_problem (problem ))
1592+ options = BumpsOptions ()
1593+ # CRUFT: options.threads does not have a default value
1594+ if not hasattr (options , 'threads' ):
1595+ options .threads = False
1596+ sock = server .setup_app (options = options )
1597+ web .run_app (server .app , sock = sock )
1598+
1599+ def wx_explore (problem ):
15711600 import wx # type: ignore
15721601 from bumps .gui import signal
15731602 from bumps .gui .app_frame import AppFrame # type: ignore
1574- from bumps .names import FitProblem # type: ignore
15751603
15761604 is_mac = "cocoa" in wx .version ()
15771605 # Create an app if not running embedded
15781606 app = wx .App () if wx .GetApp () is None else None
1579- model = Explore (opts )
1580- problem = FitProblem (model )
15811607 frame = AppFrame (parent = None , title = "explore" , size = (1000 , 700 ))
15821608 if not is_mac :
15831609 frame .Show ()
15841610 frame .panel .set_model (model = problem )
15851611 frame .panel .Layout ()
15861612 frame .panel .aui .Split (0 , wx .TOP )
1613+ # Hack to revert parameters to initial values: override the reload model tool
15871614 def _reset_parameters (event ):
1615+ model = next (problem .models )
15881616 model .revert_values ()
15891617 signal .update_parameters (problem )
15901618 frame .Bind (wx .EVT_TOOL , _reset_parameters , frame .ToolBar .GetToolByPos (1 ))
@@ -1594,18 +1622,23 @@ def _reset_parameters(event):
15941622 if app :
15951623 app .MainLoop ()
15961624
1625+ @dataclass
15971626class Explore :
15981627 """
15991628 Bumps wrapper for a SAS model comparison.
16001629
16011630 The resulting object can be used as a Bumps fit problem so that
16021631 parameters can be adjusted in the GUI, with plots updated on the fly.
16031632 """
1633+ pars : dict [str , "Parameter" ]
1634+ name = "sasmodels"
1635+
16041636 def __init__ (self , opts ):
16051637 # type: (Dict[str, Any]) -> None
16061638 from bumps .cli import config_matplotlib # type: ignore
16071639
16081640 from . import bumps_model
1641+
16091642 config_matplotlib ()
16101643 self .opts = opts
16111644 opts ['pars' ] = list (opts ['pars' ])
@@ -1673,19 +1706,36 @@ def plot(self, view='log'):
16731706 """
16741707 Plot the data and residuals.
16751708 """
1709+ import matplotlib .pyplot as plt
1710+
16761711 pars = dict ((k , v .value ) for k , v in self .pars .items ())
16771712 pars .update (self .pd_types )
16781713 self .opts ['pars' ][0 ] = pars
16791714 if not self .fix_p2 :
16801715 self .opts ['pars' ][1 ] = pars
1681- result = run_models (self .opts )
1716+ try :
1717+ result = run_models (self .opts )
1718+ except Exception as exc :
1719+ print ("Exception %s while evaluating" % (exc .__class__ .__name__ ))
1720+ import traceback
1721+ traceback .print_exc ()
1722+ plt .clf ()
1723+ return
16821724 limits = plot_models (self .opts , result , limits = self .limits )
1683- if self .limits is None :
1684- vmin , vmax = limits
1685- self .limits = vmax * 1e-7 , 1.3 * vmax
1686- import pylab
1687- pylab .clf ()
1725+
1726+ is_sf = self .opts ['info' ][0 ].structure_factor
1727+ if 0 and self .limits is None :
1728+ # Set limits based on first plot
1729+ if is_sf :
1730+ self .limits = 0.01 , 4
1731+ else :
1732+ vmin , vmax = limits
1733+ self .limits = vmax * 1e-7 , 1.3 * vmax
1734+ plt .clf ()
16881735 plot_models (self .opts , result , limits = self .limits )
1736+ if is_sf :
1737+ plt .xscale ('linear' )
1738+ plt .yscale ('linear' )
16891739
16901740
16911741def main (* argv ):
0 commit comments