source: sasview/sansrealspace/src/realspace/VolumeCanvas.py @ f63f5ff

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 f63f5ff was 883a2ef, checked in by Mathieu Doucet <doucetm@…>, 17 years ago

Updated according to code review.
Fixed consecutive 2D sims.

  • Property mode set to 100644
File size: 27.1 KB
Line 
1#!/usr/bin/env python
2""" Volume Canvas
3
4    Simulation canvas for real-space simulation of SANS scattering intensity.
5    The user can create an arrangement of basic shapes and estimate I(q) and
6    I(q_x, q_y). Error estimates on the simulation are also available.
7   
8    Example:
9   
10    import sans.realspace.VolumeCanvas as VolumeCanvas
11    canvas = VolumeCanvas.VolumeCanvas()
12    canvas.setParam('lores_density', 0.01)
13   
14    sphere = SphereDescriptor()
15    handle = canvas.addObject(sphere)
16
17    output, error = canvas.getIqError(q=0.1)
18    output, error = canvas.getIq2DError(0.1, 0.1)
19   
20    or alternatively:
21    iq = canvas.run(0.1)
22    i2_2D = canvas.run([0.1, 1.57])
23   
24"""
25
26from sans.models.BaseComponent import BaseComponent
27from sans.simulation.pointsmodelpy import pointsmodelpy
28from sans.simulation.geoshapespy import geoshapespy
29
30import os.path, math
31
32class ShapeDescriptor:
33    """
34        Class to hold the information about a shape
35        The descriptor holds a dictionary of parameters.
36       
37        Note: if shape parameters are accessed directly
38        from outside VolumeCanvas. The getPr method
39        should be called before evaluating I(q).
40               
41    """
42    def __init__(self):
43        """
44            Initialization
45        """
46        ## Real space object
47        self.shapeObject = None
48        ## Parameters of the object
49        self.params = {}
50        self.params["center"] = [0, 0, 0]
51        # Orientation are angular offsets in degrees with respect to X, Y, Z
52        self.params["orientation"] = [0, 0, 0]
53        # Default to lores shape
54        self.params['is_lores'] = True
55        self.params['order'] = 0
56           
57    def create(self):
58        """
59            Create an instance of the shape
60        """
61        # Set center
62        x0 = self.params["center"][0]
63        y0 = self.params["center"][1]
64        z0 = self.params["center"][2]
65        geoshapespy.set_center(self.shapeObject, x0, y0, z0)
66       
67        # Set orientation
68        x0 = self.params["orientation"][0]
69        y0 = self.params["orientation"][1]
70        z0 = self.params["orientation"][2]
71        geoshapespy.set_orientation(self.shapeObject, x0, y0, z0)
72               
73class SphereDescriptor(ShapeDescriptor):
74    """
75        Descriptor for a sphere
76       
77        The parameters are:
78            - radius [Angstroem] [default = 20 A]
79            - Contrast [A-2] [default = 1 A-2]
80           
81    """
82    def __init__(self):
83        """
84            Initialization
85        """ 
86        ShapeDescriptor.__init__(self)
87        # Default parameters
88        self.params["type"]   = "sphere"
89        # Radius of the sphere
90        self.params["radius"] = 20.0
91        # Constrast parameter
92        self.params["contrast"] = 1.0
93
94    def create(self):
95        """
96            Create an instance of the shape
97            @return: instance of the shape
98        """
99        self.shapeObject = geoshapespy.new_sphere(\
100            self.params["radius"])
101       
102        ShapeDescriptor.create(self)   
103        return self.shapeObject
104   
105class CylinderDescriptor(ShapeDescriptor):
106    """
107        Descriptor for a cylinder
108        Orientation: Default cylinder is along Y
109       
110        Parameters:
111            - Length [default = 40 A]
112            - Radius [default = 10 A]
113            - Contrast [default = 1 A-2]
114    """
115    def __init__(self):
116        """
117            Initialization
118        """ 
119        ShapeDescriptor.__init__(self)
120        # Default parameters
121        self.params["type"]   = "cylinder"
122        # Length of the cylinder
123        self.params["length"] = 40.0
124        # Radius of the cylinder
125        self.params["radius"] = 10.0
126        # Constrast parameter
127        self.params["contrast"] = 1.0
128       
129    def create(self):
130        """
131            Create an instance of the shape
132            @return: instance of the shape
133        """
134        self.shapeObject = geoshapespy.new_cylinder(\
135            self.params["radius"], self.params["length"])
136
137        ShapeDescriptor.create(self)
138        return self.shapeObject
139       
140
141class EllipsoidDescriptor(ShapeDescriptor):
142    """
143        Descriptor for an ellipsoid
144       
145        Parameters:
146            - Radius_x along the x-axis [default = 30 A]
147            - Radius_y along the y-axis [default = 20 A]
148            - Radius_z along the z-axis [default = 10 A]
149            - contrast [default = 1 A-2]
150    """
151    def __init__(self):
152        """
153            Initialization
154        """ 
155        ShapeDescriptor.__init__(self)
156        # Default parameters
157        self.params["type"]   = "ellipsoid"
158        self.params["radius_x"] = 30.0
159        self.params["radius_y"] = 20.0
160        self.params["radius_z"] = 10.0
161        self.params["contrast"] = 1.0
162       
163    def create(self):
164        """
165            Create an instance of the shape
166            @return: instance of the shape
167        """
168        self.shapeObject = geoshapespy.new_ellipsoid(\
169            self.params["radius_x"], self.params["radius_y"], 
170            self.params["radius_z"])
171       
172        ShapeDescriptor.create(self)   
173        return self.shapeObject
174       
175class HelixDescriptor(ShapeDescriptor):
176    """
177        Descriptor for an helix
178       
179        Parameters:
180            -radius_helix: the radius of the helix [default = 10 A]
181            -radius_tube: radius of the "tube" that forms the helix [default = 3 A]
182            -pitch: distance between two consecutive turns of the helix [default = 34 A]
183            -turns: number of turns of the helix [default = 3]
184            -contrast: contrast parameter [default = 1 A-2]
185    """
186    def __init__(self):
187        """
188            Initialization
189        """ 
190        ShapeDescriptor.__init__(self)
191        # Default parameters
192        self.params["type"]   = "singlehelix"
193        self.params["radius_helix"] = 10.0
194        self.params["radius_tube"] = 3.0
195        self.params["pitch"] = 34.0
196        self.params["turns"] = 3.0
197        self.params["contrast"] = 1.0
198
199    def create(self):
200        """
201            Create an instance of the shape
202            @return: instance of the shape
203        """
204        self.shapeObject = geoshapespy.new_singlehelix(\
205            self.params["radius_helix"], self.params["radius_tube"], 
206            self.params["pitch"], self.params["turns"])
207       
208        ShapeDescriptor.create(self)   
209        return self.shapeObject
210       
211class PDBDescriptor(ShapeDescriptor):
212    """
213        Descriptor for a PDB set of points
214       
215        Parameter:
216            - file = name of the PDB file
217    """
218    def __init__(self, filename):
219        """
220            Initialization
221            @param filename: name of the PDB file to load
222        """ 
223        ShapeDescriptor.__init__(self)
224        # Default parameters
225        self.params["type"]   = "pdb"
226        self.params["file"] = filename
227        self.params['is_lores'] = False
228
229    def create(self):
230        """
231            Create an instance of the shape
232            @return: instance of the shape
233        """
234        self.shapeObject = pointsmodelpy.new_pdbmodel()
235        pointsmodelpy.pdbmodel_add(self.shapeObject, self.params['file'])       
236       
237        #ShapeDescriptor.create(self)   
238        return self.shapeObject
239       
240# Define a dictionary for the shape until we find
241# a better way to create them
242shape_dict = {'sphere':SphereDescriptor,
243              'cylinder':CylinderDescriptor,
244              'ellipsoid':EllipsoidDescriptor,
245              'singlehelix':HelixDescriptor}
246       
247class VolumeCanvas(BaseComponent):
248    """
249        Class representing an empty space volume to add
250        geometrical object to.
251       
252        For 1D I(q) simulation, getPr() is called internally for the
253        first call to getIq().
254       
255    """
256   
257    def __init__(self):
258        """
259            Initialization
260        """
261        BaseComponent.__init__(self)
262       
263        ## Maximum value of q reachable
264        self.params['q_max'] = 0.1
265        self.params['lores_density'] = 0.1
266        self.params['scale'] = 1.0
267        self.params['background'] = 0.0
268       
269        self.lores_model = pointsmodelpy.new_loresmodel(self.params['lores_density'])
270        self.complex_model = pointsmodelpy.new_complexmodel()
271        self.shapes = {}
272        self.shapecount = 0       
273        self.points = None
274        self.npts = 0
275        self.hasPr = False       
276       
277    def _model_changed(self):
278        """
279            Reset internal data members to reflect the fact that the
280            real-space model has changed
281        """
282        self.hasPr  = False
283        self.points = None
284       
285    def addObject(self, shapeDesc, id = None):
286        """
287            Adds a real-space object to the canvas.
288       
289            @param shapeDesc: object to add to the canvas [ShapeDescriptor]
290            @param id: string handle for the object [string] [optional]
291            @return: string handle for the object
292        """
293        # If the handle is not provided, create one
294        if id == None:
295            id = shapeDesc.params["type"]+str(self.shapecount)
296         
297        # Self the order number
298        shapeDesc.params['order'] = self.shapecount
299        # Store the shape in a dictionary entry associated
300        # with the handle
301        self.shapes[id] = shapeDesc
302        self.shapecount += 1
303
304        #model changed, need to recalculate P(r)
305        self._model_changed()
306
307        return id
308           
309   
310    def add(self, shape, id = None):
311        """
312            The intend of this method is to eventually be able to use it
313            as a factory for the canvas and unify the simulation with the
314            analytical solutions. For instance, if one adds a cylinder and
315            it is the only shape on the canvas, the analytical solution
316            could be called. If multiple shapes are involved, then
317            simulation has to be performed.
318           
319            This function is deprecated, use addObject().
320       
321            @param shape: name of the object to add to the canvas [string]
322            @param id: string handle for the object [string] [optional]
323            @return: string handle for the object
324        """
325        # If the handle is not provided, create one
326        if id == None:
327            id = "shape"+str(self.shapecount)
328 
329        #shapeDesc = ShapeDescriptor(shape.lower())
330        if shape.lower() in shape_dict:
331            shapeDesc = shape_dict[shape.lower()]()
332        elif os.path.isfile(shape):
333            # A valid filename was supplier, create a PDB object
334            shapeDesc = PDBDescriptor(shape)
335        else:
336            raise ValueError, "VolumeCanvas.add: Unknown shape %s" % shape
337       
338        return self.addObject(shapeDesc, id)
339
340    def delete(self, id):
341        """
342            Delete a shape. The ID for the shape is required.
343            @param id: string handle for the object [string] [optional]
344        """
345
346        if self.shapes.has_key(id):
347            del self.shapes[id]
348        else:
349            raise KeyError, "VolumeCanvas.delete: could not find shape ID"
350
351        #model changed, need to recalculate P(r)
352        self._model_changed()
353
354
355    def setParam(self, name, value):   
356        """
357            Function to set the value of a parameter.
358            Both VolumeCanvas parameters and shape parameters
359            are accessible.
360           
361            Note: if shape parameters are accessed directly
362            from outside VolumeCanvas. The getPr method
363            should be called before evaluating I(q).
364       
365            TODO: implemented a check method to protect
366            against that.
367       
368            @param name: name of the parameter to change
369            @param value: value to give the parameter
370        """
371       
372        # Lowercase for case insensitivity
373        name = name.lower()
374       
375        # Look for shape access
376        toks = name.split('.')
377       
378        # If a shape identifier was given, look the shape up
379        # in the dictionary
380        if len(toks)>1:
381            if toks[0] in self.shapes.keys():
382                # The shape was found, now look for the parameter
383                if toks[1] in self.shapes[toks[0]].params:
384                    # The parameter was found, now change it
385                    self.shapes[toks[0]].params[toks[1]] = value
386                    self._model_changed()
387                else:
388                    raise ValueError, "Could not find parameter %s" % name
389            else:
390                raise ValueError, "Could not find shape %s" % toks[0]
391       
392        else:
393            # If we are not accessing the parameters of a
394            # shape, see if the parameter is part of this object
395            BaseComponent.setParam(self, name, value)
396            self._model_changed()
397
398    def getParam(self, name):   
399        """
400            @param name: name of the parameter to change
401        """
402        #TODO: clean this up
403       
404        # Lowercase for case insensitivity
405        name = name.lower()
406       
407        # Look for sub-model access
408        toks = name.split('.')
409        if len(toks) == 1:
410            try:
411                self.params.has_key(toks[0])
412            except KeyError:
413                raise ValueError, \
414                    "VolumeCanvas.getParam: Could not find %s" % name
415
416            value = self.params[toks[0]]
417            if isinstance(value, ShapeDescriptor):
418                raise ValueError, \
419                    "VolumeCanvas.getParam: Cannot get parameter value." 
420            else:
421                return value
422
423        elif len(toks) == 2:
424            try:
425                self.shapes.has_key(toks[0])
426            except KeyError:
427                raise ValueError, \
428                    "VolumeCanvas.getParam: Could not find %s" % name
429
430            shapeinstance = self.shapes[toks[0]]
431
432            try:
433                shapeinstance.params.has_key(toks[1])
434            except KeyError:
435                raise ValueError, \
436                    "VolumeCanvas.getParam: Could not find %s" % name
437
438            return shapeinstance.params[toks[1]]
439
440        else:
441            raise ValueError, \
442                "VolumeCanvas.getParam: Could not find %s" % name
443           
444    def getParamList(self, shapeid = None):
445        """
446               return a full list of all available parameters from
447           self.params.keys(). If a key in self.params is a instance
448           of ShapeDescriptor, extend the return list to:
449           [param1,param2,shapeid.param1,shapeid.param2.......]
450
451           If shapeid is provided, return the list of parameters that
452           belongs to that shape id only : [shapeid.param1, shapeid.param2...]
453        """
454
455        param_list = []
456        if shapeid == None:       
457            for key1 in self.params.keys():
458                #value1 = self.params[key1]
459                param_list.append(key1)
460            for key2 in self.shapes.keys():
461                value2 = self.shapes[key2]
462                header = key2 + '.'
463                for key3 in value2.params.keys():   
464                    fullname = header + key3                 
465                    param_list.append(fullname)
466     
467        else:
468            try:
469                self.shapes.has_key(shapeid)
470            except KeyError:
471                raise ValueError, \
472                    "VolumeCanvas: getParamList: Could not find %s" % shapeid
473            header = shapeid + '.'
474            param_list = self.shapes[shapeid].params.keys() 
475            for i in range(len(param_list)):
476                param_list[i] = header + param_list[i]
477
478        return param_list
479
480    def getShapeList(self):
481        """
482            Return a list of the shapes
483        """
484        return self.shapes.keys()
485
486    def _addSingleShape(self, shapeDesc):
487        """
488            create shapeobject based on shapeDesc
489            @param shapeDesc: shape description
490        """
491        #Create the object model
492        shapeDesc.create()
493                   
494        if shapeDesc.params['is_lores']:
495            # Add the shape to the lores_model
496            pointsmodelpy.lores_add(self.lores_model, 
497                shapeDesc.shapeObject, shapeDesc.params['contrast']) 
498
499    def _createVolumeFromList(self):
500        """
501            Create a new lores model with all the shapes in our internal list
502            Whenever we change a parameter of a shape, we have to re-create
503            the whole thing.
504           
505            Items with higher 'order' number take precedence for regions
506            of space that are shared with other objects. Points in the
507            overlapping region belonging to objects with lower 'order'
508            will be ignored.
509           
510            Items are added in decreasing 'order' number.
511            The item with the highest 'order' will be added *first*.
512            [That conventions is prescribed by the realSpaceModeling module]
513        """
514       
515        # Create empty model
516        self.lores_model = \
517            pointsmodelpy.new_loresmodel(self.params['lores_density'])
518
519        # Create empty complex model
520        self.complex_model = pointsmodelpy.new_complexmodel()
521       
522        # Order the object first
523        obj_list = []
524   
525        for shape in self.shapes:
526            order = self.shapes[shape].params['order']
527            # find where to place it in the list
528            stored = False
529           
530            for i in range(len(obj_list)):
531                if obj_list[i][0] > order:
532                    obj_list.insert(i, [order, shape])
533                    stored = True
534                    break
535           
536            if not stored:
537                obj_list.append([order, shape])
538               
539        # Add each shape
540        len_list = len(obj_list)
541        for i in range(len_list-1, -1, -1):
542            shapedesc = self.shapes[obj_list[i][1]]
543            self._addSingleShape(shapedesc)
544
545        return 0     
546   
547    def getPr(self):
548        """
549            Calculate P(r) from the objects on the canvas.
550            This method should always be called after the shapes
551            on the VolumeCanvas have changed.
552           
553            @return: calculation output flag
554        """
555        # To find a complete example of the correct call order:
556        # In LORES2, in actionclass.py, method CalculateAction._get_iq()
557       
558        # If there are not shapes, do nothing
559        if len(self.shapes) == 0:
560            self._model_changed()
561            return 0
562       
563        # generate space filling points from shape list
564        self._createVolumeFromList()
565
566        self.points = pointsmodelpy.new_point3dvec()
567
568        pointsmodelpy.complexmodel_add(self.complex_model, 
569                                        self.lores_model, "LORES")
570        for shape in self.shapes:
571            if self.shapes[shape].params['is_lores'] == False:
572                pointsmodelpy.complexmodel_add(self.complex_model, 
573                    self.shapes[shape].shapeObject, "PDB")
574       
575        #pointsmodelpy.get_lorespoints(self.lores_model, self.points)
576        self.npts = pointsmodelpy.get_complexpoints(self.complex_model, self.points)
577       
578        # expecting the rmax is a positive float or 0. The maximum distance.
579        #rmax = pointsmodelpy.get_lores_pr(self.lores_model, self.points)   
580         
581        rmax = pointsmodelpy.get_complex_pr(self.complex_model, self.points) 
582        self.hasPr = True   
583
584        return rmax
585       
586    def run(self, q = 0):
587        """
588            Returns the value of I(q) for a given q-value
589            @param q: q-value ([float] or [list]) ([A-1] or [[A-1], [rad]])
590            @return: I(q) [float] [cm-1]
591        """
592        # Check for 1D q length
593        if q.__class__.__name__ == 'int' \
594            or q.__class__.__name__ == 'float':
595            return self.getIq(q)
596        # Check for 2D q-value
597        elif q.__class__.__name__ == 'list':
598            # Compute (Qx, Qy) from (Q, phi)
599            # Phi is in radian and Q-values are in A-1
600            qx = q[0]*math.cos(q[1])
601            qy = q[0]*math.sin(q[1])
602            return self.getIq2D(qx, qy)
603        # Through an exception if it's not a
604        # type we recognize
605        else:
606            raise ValueError, "run(q): bad type for q"
607   
608    def runXY(self, q = 0):
609        """
610            Standard run command for the canvas.
611            Redirects to the correct method
612            according to the input type.
613            @param q: q-value [float] or [list] [A-1]
614            @return: I(q) [float] [cm-1]
615        """
616        # Check for 1D q length
617        if q.__class__.__name__ == 'int' \
618            or q.__class__.__name__ == 'float':
619            return self.getIq(q)
620        # Check for 2D q-value
621        elif q.__class__.__name__ == 'list':
622            return self.getIq2D(q[0], q[1])
623        # Through an exception if it's not a
624        # type we recognize
625        else:
626            raise ValueError, "runXY(q): bad type for q"
627   
628    def _create_modelObject(self):
629        """
630            Create the simulation model obejct from the list
631            of shapes.
632           
633            This method needs to be called each time a parameter
634            changes because of the way the underlying library
635            was (badly) written. It is impossible to change a
636            parameter, or remove a shape without having to
637            refill the space points.
638           
639            TODO: improve that.
640        """
641        # To find a complete example of the correct call order:
642        # In LORES2, in actionclass.py, method CalculateAction._get_iq()
643       
644        # If there are not shapes, do nothing
645        if len(self.shapes) == 0:
646            self._model_changed()
647            return 0
648       
649        # generate space filling points from shape list
650        self._createVolumeFromList()
651
652        self.points = pointsmodelpy.new_point3dvec()
653
654        pointsmodelpy.complexmodel_add(self.complex_model, 
655                                        self.lores_model, "LORES")
656        for shape in self.shapes:
657            if self.shapes[shape].params['is_lores'] == False:
658                pointsmodelpy.complexmodel_add(self.complex_model, 
659                    self.shapes[shape].shapeObject, "PDB")
660       
661        #pointsmodelpy.get_lorespoints(self.lores_model, self.points)
662        self.npts = pointsmodelpy.get_complexpoints(self.complex_model, self.points)
663       
664       
665    def getIq2D(self, qx, qy):
666        """
667            Returns simulate I(q) for given q_x and q_y values.
668            @param qx: q_x [A-1]
669            @param qy: q_y [A-1]
670            @return: I(q) [cm-1]
671        """
672       
673        # If this is the first simulation call, we need to generate the
674        # space points
675        if self.points == None:
676            self._create_modelObject()
677           
678            # Protect against empty model
679            if self.points == None:
680                return 0
681               
682        # Evalute I(q)
683        norm =  1.0e8/self.params['lores_density']*self.params['scale']
684        return norm*pointsmodelpy.get_complex_iq_2D(self.complex_model, self.points, qx, qy)\
685            + self.params['background']
686               
687       
688    def getIq(self, q):
689        """
690            Returns the value of I(q) for a given q-value
691           
692            This method should remain internal to the class
693            and the run() method should be used instead.
694           
695            @param q: q-value [float]
696            @return: I(q) [float]
697        """
698       
699        if self.hasPr == False:
700            self.getPr()
701
702        # By dividing by the density instead of the actuall V/N,
703        # we have an uncertainty of +-1 on N because the number
704        # of points chosen for the simulation is int(density*volume).
705        # Propagation of error gives:
706        #   delta(1/density^2) = 2*(1/density^2)/N
707        # where N is stored in self.npts
708
709        norm =  1.0e8/self.params['lores_density']*self.params['scale']
710        #return norm*pointsmodelpy.get_lores_i(self.lores_model, q)
711        return norm*pointsmodelpy.get_complex_i(self.complex_model, q)\
712            + self.params['background']
713   
714    def getError(self, q):
715        """
716            Returns the error of I(q) for a given q-value
717            @param q: q-value [float]
718            @return: I(q) [float]
719        """
720       
721        if self.hasPr == False:
722            self.getPr()
723
724        # By dividing by the density instead of the actual V/N,
725        # we have an uncertainty of +-1 on N because the number
726        # of points chosen for the simulation is int(density*volume).
727        # Propagation of error gives:
728        #   delta(1/density^2) = 2*(1/density^2)/N
729        # where N is stored in self.npts
730
731        norm =  1.0e8/self.params['lores_density']*self.params['scale']
732        #return norm*pointsmodelpy.get_lores_i(self.lores_model, q)
733        return norm*pointsmodelpy.get_complex_i_error(self.complex_model, q)\
734            + self.params['background']
735   
736    def getIqError(self, q):
737        """
738            Return the simulated value along with its estimated
739            error for a given q-value
740           
741            Propagation of errors is used to evaluate the
742            uncertainty.
743           
744            @param q: q-value [float]
745            @return: mean, error [float, float]
746        """
747        val = self.getIq(q)
748        # Simulation error (statistical)
749        err = self.getError(q)
750        # Error on V/N
751        simerr = 2*val/self.npts
752        return val, err+simerr
753
754    def getIq2DError(self, qx, qy):
755        """
756            Return the simulated value along with its estimated
757            error for a given q-value
758           
759            Propagation of errors is used to evaluate the
760            uncertainty.
761           
762            @param qx: qx-value [float]
763            @param qy: qy-value [float]
764            @return: mean, error [float, float]
765        """
766        self._create_modelObject()
767               
768        norm =  1.0e8/self.params['lores_density']*self.params['scale']
769        val = norm*pointsmodelpy.get_complex_iq_2D(self.complex_model, self.points, qx, qy)\
770            + self.params['background']
771       
772        # Simulation error (statistical)
773        norm =  1.0e8/self.params['lores_density']*self.params['scale'] \
774                * math.pow(self.npts/self.params['lores_density'], 1.0/3.0)/self.npts
775        err = norm*pointsmodelpy.get_complex_iq_2D_err(self.complex_model, self.points, qx, qy)
776        # Error on V/N
777        simerr = 2*val/self.npts
778       
779        # The error used for the position is over-simplified.
780        # The actual error was empirically found to be about
781        # an order of magnitude larger.
782        return val, 10.0*err+simerr
783       
Note: See TracBrowser for help on using the repository browser.