source: sasview/simview/perspectives/simulation/simulation.py @ 836a762

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 836a762 was 6ef7ac5a, checked in by Mathieu Doucet <doucetm@…>, 15 years ago

simview: update simulation when Q range and point density are changed from the control panel; show P(r) plot.

  • Property mode set to 100644
File size: 11.6 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 sys
12import os
13import numpy
14import time
15from copy import deepcopy
16import logging
17
18# Application imports
19import SimCanvas
20import ShapeParameters
21import ShapeAdapter
22from sans.guiframe.dataFitting import Data1D
23# Real-space simulation import
24import sans.realspace.VolumeCanvas as VolumeCanvas
25
26from data_util.calcthread import CalcThread
27from sans.guicomm.events import NewPlotEvent, StatusEvent   
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 = numpy.zeros(len(x))
51        error = numpy.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        logging.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 = numpy.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 = numpy.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 != None and self.calc_thread_1D.isrunning():
216            self.calc_thread_1D.stop()
217           
218        # Create a computation thread
219        self.calc_thread_1D = Calc1D(self.x, self.volCanvas, 
220                            completefn=self._simulation_completed_1D,
221                            updatefn=None)
222        self.calc_thread_1D.queue()
223        self.calc_thread_1D.ready(2.5)
224   
225        # Evaluate maximum number of points on the canvas and the
226        # maximum computation time
227       
228        # TODO: the getMaxVolume should be a call to the VolumeCanvas object.
229        # Since the VolumeCanvas doesn't currently have that functionality, and
230        # since the simulation panel holds the list of graphical representations
231        # for the shapes, we will take the information from there until VolumeCanvas
232        # is updated.
233        npts = self.plotPanel.canvas.getMaxVolume() * self.volCanvas.params['lores_density'] 
234       
235        est = self.speed * npts * npts
236        self.parent.SetStatusText("Calculation started: this might take a moment... [up to %d secs, %g points]" % (int(est), int(npts)))
237
238 
239    def _simulation_completed_1D(self, output, elapsed, error=None):
240        """
241            Called by the computation thread when the simulation is complete.
242            This method processes the simulation output, plots it, and updates
243            the simulation time estimate.
244           
245            @param output: simulated distribution I(q)
246            @param elapsed: simulation time, in seconds
247            @param error: standard deviation on the I(q) points
248        """
249        # Create the plotting event to pop up the I(Q) plot.
250        new_plot = Data1D(x=self.x, y=output, dy=error)
251        new_plot.name = "I(Q) Simulation"
252        new_plot.group_id = "simulation_output"
253        new_plot.xaxis("\\rm{Q}", 'A^{-1}')
254        new_plot.yaxis("\\rm{Intensity} ","cm^{-1}")
255        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulation I(Q)"))
256       
257        # Create the plotting event to pop up the P(r) plot.
258        r, pr = self.volCanvas.getPrData()
259        new_plot = Data1D(x=r, y=pr, dy=[0]*len(r))
260        new_plot.name = "P(r) Simulation"
261        new_plot.group_id = "simulated_pr"
262        new_plot.xaxis("\\rm{r}", 'A')
263        new_plot.yaxis("\\rm{P(r)} ","cm^{-3}") 
264       
265        wx.PostEvent(self.parent, NewPlotEvent(plot=new_plot, title="Simulated P(r)"))       
266       
267       
268        # Notify the user of the simlation time and update the basic
269        # simulation estimate that will allow us to estimate simulation time
270        # next time we are asked to simulate.
271        msg = "Calculation completed in %g secs! [%g points]" % (elapsed, self.volCanvas.npts)
272        wx.PostEvent(self.parent, StatusEvent(status=msg))
273        if self.volCanvas.npts>0:
274            self.speed = elapsed/self.volCanvas.npts**2
275         
276   
277    def get_perspective(self):
278        """
279            Get the list of panel names for this perspective
280        """
281        return self.perspective
282   
283    def populate_menu(self, id, owner):
284        """
285            Create a menu for the plug-in
286        """
287        return [] 
288   
289    def _change_point_density(self, point_density):
290        """
291            Placeholder for changing the simulation point density
292            TODO: refactor this away by writing a single update method for the simulation parameters
293        """
294        self.volCanvas.setParam('lores_density', point_density)
295   
296    def help(self, evt):
297        """
298            Provide help for the simulation
299        """
300        pass
301   
302    def on_perspective(self, event):
303        """
304            Call back function for the perspective menu item.
305            We notify the parent window that the perspective
306            has changed.
307        """
308        self.parent.set_perspective(self.perspective)
309   
310    def post_init(self):
311        """
312            Post initialization call back to close the loose ends
313            [Somehow openGL needs this call]
314        """
315        self.parent.set_perspective(self.perspective) 
316        self.parent._mgr.Update()
317
318    def _refresh_3D_viewer(self):
319        """
320            Refresh the 3D viewer window
321            #TODO: don't access data member directly
322        """
323        self.plotPanel.canvas.Refresh(False)
324        # Give focus back to 3D canvas so that the
325        # zooming works
326        #self.plotPanel.canvas.SetFocus()
327        #self.plotPanel.SetFocus()
Note: See TracBrowser for help on using the repository browser.