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

ESS_GUIESS_GUI_DocsESS_GUI_batch_fittingESS_GUI_bumps_abstractionESS_GUI_iss1116ESS_GUI_iss879ESS_GUI_iss959ESS_GUI_openclESS_GUI_orderingESS_GUI_sync_sascalccostrafo411magnetic_scattrelease-4.1.1release-4.1.2release-4.2.2release_4.0.1ticket-1009ticket-1094-headlessticket-1242-2d-resolutionticket-1243ticket-1249ticket885unittest-saveload
Last change on this file since b64b87c was d85c194, checked in by Piotr Rozyczko <piotr.rozyczko@…>, 8 years ago

Remaining modules refactored

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