source: sasview/src/sas/sasgui/perspectives/simulation/simulation.py @ 20fa5fe

magnetic_scattrelease-4.2.2ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since 20fa5fe was 7432acb, checked in by andyfaff, 8 years ago

MAINT: search+replace '!= None' by 'is not None'

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