source: sasview/src/sas/sasgui/perspectives/simulation/simulation.py @ 5251ec6

magnetic_scattrelease-4.2.2ticket-1009ticket-1249
Last change on this file since 5251ec6 was 5251ec6, checked in by Paul Kienzle <pkienzle@…>, 5 years ago

improved support for py37 in sasgui

  • Property mode set to 100644
File size: 11.7 KB
Line 
1"""
2This software was developed by the University of Tennessee as part of the
3Distributed Data Analysis of Neutron Scattering Experiments (DANSE)
4project funded by the US National Science Foundation.
5
6See the license text in license.txt
7
8copyright 2009, University of Tennessee
9"""
10import wx
11import os
12import numpy as np
13import time
14import logging
15
16# Application imports
17from sas.sascalc.realspace import VolumeCanvas
18from sas.sascalc.data_util.calcthread import CalcThread
19from sas.sasgui.guiframe.dataFitting import Data1D
20from sas.guicomm.events import NewPlotEvent, StatusEvent   
21
22from . import SimCanvas
23from . import ShapeParameters
24from . import ShapeAdapter
25
26logger = logging.getLogger(__name__)
27
28class Calc1D(CalcThread):
29    """
30        Thread object to simulate I(q)
31    """
32   
33    def __init__(self, x, model,
34                 completefn = None,
35                 updatefn   = None,
36                 yieldtime  = 0.01,
37                 worktime   = 0.01
38                 ):
39        CalcThread.__init__(self,completefn,
40                 updatefn,
41                 yieldtime,
42                 worktime)
43        self.x = x
44        self.model = model
45        self.starttime = 0
46       
47    def compute(self):
48        x = self.x
49        output = np.zeros(len(x))
50        error = np.zeros(len(x))
51       
52        self.starttime = time.time()
53       
54        for i_x in range(len(self.x)):
55            # Check whether we need to bail out
56            self.isquit()
57            self.update(output=output, error=error)
58           
59            value, err = self.model.getIqError(float(self.x[i_x]))
60            output[i_x] = value
61            error[i_x] = err
62           
63        elapsed = time.time()-self.starttime
64        self.complete(output=output, error=error, elapsed=elapsed)
65
66## Default minimum q-value for simulation output   
67DEFAULT_Q_MIN = 0.01
68## Default maximum q-value for simulation output
69DEFAULT_Q_MAX = 0.4
70## Default number of q point for simulation output
71DEFAULT_Q_NPTS = 10
72## Default number of real-space points per Angstrom cube
73DEFAULT_PT_DENSITY = 0.1
74   
75class Plugin:
76    """
77        Real-space simulation plug-in for guiframe
78    """
79    ## Minimum q-value for simulation output   
80    q_min = DEFAULT_Q_MIN
81    ## Maximum q-value for simulation output
82    q_max = DEFAULT_Q_MAX
83    ## Number of q point for simulation output
84    q_npts = DEFAULT_Q_NPTS   
85   
86    def __init__(self):
87        ## Plug-in name
88        self.sub_menu = "Simulation"
89        ## Reference to the parent window
90        self.parent = None
91        ## List of panels for the simulation perspective (names)
92        self.perspective = []
93        # Default save location
94        self._default_save_location = os.getcwd()
95        # Log startup
96        logger.info("Simulation plug-in started")
97       
98    def get_panels(self, parent):
99        """
100            Create and return a list of panel objects
101        """
102        self.parent = parent
103       
104        # 3D viewer
105        self.plotPanel  = SimCanvas.SimPanel(self.parent, -1, style=wx.RAISED_BORDER)
106
107        # Central simulation panel
108        self.paramPanel = ShapeParameters.ShapeParameterPanel(self.parent, 
109                                                              q_min = self.q_min,
110                                                              q_max = self.q_max,
111                                                              q_npts = self.q_npts,
112                                                              pt_density = DEFAULT_PT_DENSITY,
113                                                              style=wx.RAISED_BORDER)
114       
115        # Simulation
116        self.volCanvas = VolumeCanvas.VolumeCanvas()
117        self.volCanvas.setParam('lores_density', DEFAULT_PT_DENSITY)
118        self.adapter = ShapeAdapter.ShapeVisitor()
119        self._data_1D = None
120        self.calc_thread_1D = None
121        self.speedCheck = False
122        self.speed = 3.0e-7
123       
124        # Q-values for plotting simulated I(Q)
125        step = (self.q_max-self.q_min)/(self.q_npts-1)
126        self.x = np.arange(self.q_min, self.q_max+step*0.01, step)
127       
128        # Set the list of panels that are part of the simulation perspective
129        self.perspective = []
130        self.perspective.append(self.plotPanel.window_name)
131        self.perspective.append(self.paramPanel.window_name)
132       
133        # Bind state events
134        self.parent.Bind(ShapeParameters.EVT_ADD_SHAPE, self._onAddShape)
135        self.parent.Bind(ShapeParameters.EVT_DEL_SHAPE, self._onDelShape)
136        self.parent.Bind(ShapeParameters.EVT_Q_RANGE, self._on_q_range_changed)
137        self.parent.Bind(ShapeParameters.EVT_PT_DENSITY, self._on_pt_density_changed)
138
139        return [self.plotPanel, self.paramPanel]
140
141    def _onAddShape(self, evt):
142        """
143            Process a user event to add a newly created
144            or modified shape to the canvas
145        """
146        evt.Skip()
147       
148        # Give the new shape to the canvas
149        if evt.new:
150            shape = evt.shape.accept(self.adapter)
151            id = self.volCanvas.addObject(shape)
152            self.plotPanel.canvas.addShape(evt.shape, id)
153        else:
154            self.adapter.update(self.volCanvas, evt.shape)
155       
156        # Compute the simulated I(Q)
157        self._simulate_Iq()
158       
159        # Refresh the 3D viewer
160        self._refresh_3D_viewer()
161       
162    def _onDelShape(self, evt):
163        """
164            Remove a shape from the simulation
165        """
166        # Notify the simulation canvas
167        self.volCanvas.delete(evt.id)
168        # Notify the UI canvas
169        self.plotPanel.canvas.delShape(evt.id)
170       
171        # Compute the simulated I(Q)
172        self._simulate_Iq()
173       
174        # Refresh the 3D viewer
175        self._refresh_3D_viewer()   
176       
177    def _on_q_range_changed(self, evt):
178        """
179            Modify the Q range of the simulation output
180        """
181        if evt.q_min is not None:
182            self.q_min = evt.q_min
183        if evt.q_max is not None:
184            self.q_max = evt.q_max
185        if evt.npts is not None:
186            self.q_npts = evt.npts
187       
188        # Q-values for plotting simulated I(Q)
189        step = (self.q_max-self.q_min)/(self.q_npts-1)
190        self.x = np.arange(self.q_min, self.q_max+step*0.01, step)
191         
192        # Compute the simulated I(Q)
193        self._simulate_Iq()
194       
195    def _on_pt_density_changed(self, evt):
196        """
197            Modify the Q range of the simulation output
198        """
199        if evt.npts is not None:
200            self.volCanvas.setParam('lores_density', evt.npts)
201         
202        # Compute the simulated I(Q)
203        self._simulate_Iq()
204       
205    def _simulate_Iq(self):
206        """
207            Simulate I(q) using the current VolumeCanvas object.
208        """
209        # Check that the VolumeCanvas object exists
210        if not isinstance(self.volCanvas, VolumeCanvas.VolumeCanvas):
211            return
212       
213        # If a computation thread is running, stop it
214        if self.calc_thread_1D is not None and self.calc_thread_1D.isrunning():
215            self.calc_thread_1D.stop()
216            ## stop just raises the flag -- the thread is supposed to
217            ## then kill itself. In August 2014 it was shown that this is
218            ## incorrectly handled by fitting.py and a fix implemented.
219            ## It is not clear that it is improperly used here so no fix
220            ## is being added here.
221            ##
222            ##    -PDB January 25, 2015                 
223           
224        # Create a computation thread
225        self.calc_thread_1D = Calc1D(self.x, self.volCanvas, 
226                            completefn=self._simulation_completed_1D,
227                            updatefn=None)
228        self.calc_thread_1D.queue()
229        self.calc_thread_1D.ready(2.5)
230   
231        # Evaluate maximum number of points on the canvas and the
232        # maximum computation time
233       
234        # TODO: the getMaxVolume should be a call to the VolumeCanvas object.
235        # Since the VolumeCanvas doesn't currently have that functionality, and
236        # since the simulation panel holds the list of graphical representations
237        # for the shapes, we will take the information from there until VolumeCanvas
238        # is updated.
239        npts = self.plotPanel.canvas.getMaxVolume() * self.volCanvas.params['lores_density'] 
240       
241        est = self.speed * npts * npts
242        self.parent.SetStatusText("Calculation started: this might take a moment... [up to %d secs, %g points]" % (int(est), int(npts)))
243
244 
245    def _simulation_completed_1D(self, output, elapsed, error=None):
246        """
247            Called by the computation thread when the simulation is complete.
248            This method processes the simulation output, plots it, and updates
249            the simulation time estimate.
250           
251            @param output: simulated distribution I(q)
252            @param elapsed: simulation time, in seconds
253            @param error: standard deviation on the I(q) points
254        """
255        # Create the plotting event to pop up the I(Q) plot.
256        new_plot = Data1D(x=self.x, y=output, dy=error)
257        new_plot.name = "I(Q) Simulation"
258        new_plot.group_id = "simulation_output"
259        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
260        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
261        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulation I(Q)"))
262       
263        # Create the plotting event to pop up the P(r) plot.
264        r, pr = self.volCanvas.getPrData()
265        new_plot = Data1D(x=r, y=pr, dy=[0]*len(r))
266        new_plot.name = "P(r) Simulation"
267        new_plot.group_id = "simulated_pr"
268        new_plot.xaxis("\\rm{r}", 'A')
269        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}") 
270       
271        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulated P(r)"))       
272       
273       
274        # Notify the user of the simlation time and update the basic
275        # simulation estimate that will allow us to estimate simulation time
276        # next time we are asked to simulate.
277        msg = "Calculation completed in %g secs! [%g points]" % (elapsed, self.volCanvas.npts)
278        wx.PostEvent(self.parent, StatusEvent(status=msg))
279        if self.volCanvas.npts>0:
280            self.speed = elapsed/self.volCanvas.npts**2
281         
282   
283    def get_perspective(self):
284        """
285            Get the list of panel names for this perspective
286        """
287        return self.perspective
288   
289    def populate_menu(self, id, owner):
290        """
291            Create a menu for the plug-in
292        """
293        return [] 
294   
295    def _change_point_density(self, point_density):
296        """
297            Placeholder for changing the simulation point density
298            TODO: refactor this away by writing a single update method for the simulation parameters
299        """
300        self.volCanvas.setParam('lores_density', point_density)
301   
302    def help(self, evt):
303        """
304            Provide help for the simulation
305        """
306        pass
307   
308    def on_perspective(self, event):
309        """
310            Call back function for the perspective menu item.
311            We notify the parent window that the perspective
312            has changed.
313        """
314        self.parent.set_perspective(self.perspective)
315   
316    def post_init(self):
317        """
318            Post initialization call back to close the loose ends
319            [Somehow openGL needs this call]
320        """
321        self.parent.set_perspective(self.perspective) 
322        self.parent._mgr.Update()
323
324    def _refresh_3D_viewer(self):
325        """
326            Refresh the 3D viewer window
327            #TODO: don't access data member directly
328        """
329        self.plotPanel.canvas.Refresh(False)
330        # Give focus back to 3D canvas so that the
331        # zooming works
332        #self.plotPanel.canvas.SetFocus()
333        #self.plotPanel.SetFocus()
Note: See TracBrowser for help on using the repository browser.