source: sasview/src/sas/sascalc/realspace/VolumeCanvas.py @ f0b9bce

ticket-1094-headless
Last change on this file since f0b9bce was e090ba90, checked in by Paul Kienzle <pkienzle@…>, 6 years ago

remove errors and warnings from py37 tests of sascalc

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