Changeset 98e3f24 in sasview
- Timestamp:
- Jun 23, 2017 4:31:13 PM (7 years ago)
- Branches:
- master, ESS_GUI, ESS_GUI_Docs, ESS_GUI_batch_fitting, ESS_GUI_bumps_abstraction, ESS_GUI_iss1116, ESS_GUI_iss879, ESS_GUI_iss959, ESS_GUI_opencl, ESS_GUI_ordering, ESS_GUI_sync_sascalc, magnetic_scatt, release-4.2.2, ticket-1009, ticket-1094-headless, ticket-1242-2d-resolution, ticket-1243, ticket-1249, ticket885, unittest-saveload
- Children:
- 65f3930
- Parents:
- ba8d326
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
src/sas/sascalc/realspace/VolumeCanvas.py
r235f514 r98e3f24 4 4 Simulation canvas for real-space simulation of SAS scattering intensity. 5 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 6 I(q_x, q_y). Error estimates on the simulation are also available. 7 8 8 Example: 9 9 10 10 import sas.sascalc.realspace.VolumeCanvas as VolumeCanvas 11 11 canvas = VolumeCanvas.VolumeCanvas() 12 12 canvas.setParam('lores_density', 0.01) 13 13 14 14 sphere = SphereDescriptor() 15 15 handle = canvas.addObject(sphere) … … 17 17 output, error = canvas.getIqError(q=0.1) 18 18 output, error = canvas.getIq2DError(0.1, 0.1) 19 19 20 20 or alternatively: 21 21 iq = canvas.run(0.1) 22 22 i2_2D = canvas.run([0.1, 1.57]) 23 23 24 24 """ 25 25 26 from sas. models.BaseComponent import BaseComponent26 from sas.sascalc.calculator.BaseComponent import BaseComponent 27 27 from sas.sascalc.simulation.pointsmodelpy import pointsmodelpy 28 28 from sas.sascalc.simulation.geoshapespy import geoshapespy … … 31 31 import os.path, math 32 32 33 class ShapeDescriptor :33 class ShapeDescriptor(object): 34 34 """ 35 35 Class to hold the information about a shape 36 36 The descriptor holds a dictionary of parameters. 37 37 38 38 Note: if shape parameters are accessed directly 39 39 from outside VolumeCanvas. The getPr method 40 40 should be called before evaluating I(q). 41 41 42 42 """ 43 43 def __init__(self): … … 55 55 self.params['is_lores'] = True 56 56 self.params['order'] = 0 57 57 58 58 def create(self): 59 59 """ … … 65 65 z0 = self.params["center"][2] 66 66 geoshapespy.set_center(self.shapeObject, x0, y0, z0) 67 67 68 68 # Set orientation 69 69 x0 = self.params["orientation"][0] … … 71 71 z0 = self.params["orientation"][2] 72 72 geoshapespy.set_orientation(self.shapeObject, x0, y0, z0) 73 73 74 74 class SphereDescriptor(ShapeDescriptor): 75 75 """ 76 76 Descriptor for a sphere 77 77 78 78 The parameters are: 79 79 - radius [Angstroem] [default = 20 A] 80 80 - Contrast [A-2] [default = 1 A-2] 81 81 82 82 """ 83 83 def __init__(self): 84 84 """ 85 85 Initialization 86 """ 86 """ 87 87 ShapeDescriptor.__init__(self) 88 88 # Default parameters 89 self.params["type"] 89 self.params["type"] = "sphere" 90 90 # Radius of the sphere 91 91 self.params["radius"] = 20.0 … … 100 100 self.shapeObject = geoshapespy.new_sphere(\ 101 101 self.params["radius"]) 102 103 ShapeDescriptor.create(self) 102 103 ShapeDescriptor.create(self) 104 104 return self.shapeObject 105 105 106 106 class CylinderDescriptor(ShapeDescriptor): 107 107 """ 108 108 Descriptor for a cylinder 109 109 Orientation: Default cylinder is along Y 110 110 111 111 Parameters: 112 112 - Length [default = 40 A] … … 117 117 """ 118 118 Initialization 119 """ 119 """ 120 120 ShapeDescriptor.__init__(self) 121 121 # Default parameters 122 self.params["type"] 122 self.params["type"] = "cylinder" 123 123 # Length of the cylinder 124 124 self.params["length"] = 40.0 … … 127 127 # Constrast parameter 128 128 self.params["contrast"] = 1.0 129 129 130 130 def create(self): 131 131 """ … … 138 138 ShapeDescriptor.create(self) 139 139 return self.shapeObject 140 140 141 141 142 142 class EllipsoidDescriptor(ShapeDescriptor): 143 143 """ 144 144 Descriptor for an ellipsoid 145 145 146 146 Parameters: 147 147 - Radius_x along the x-axis [default = 30 A] … … 153 153 """ 154 154 Initialization 155 """ 155 """ 156 156 ShapeDescriptor.__init__(self) 157 157 # Default parameters 158 self.params["type"] 158 self.params["type"] = "ellipsoid" 159 159 self.params["radius_x"] = 30.0 160 160 self.params["radius_y"] = 20.0 161 161 self.params["radius_z"] = 10.0 162 162 self.params["contrast"] = 1.0 163 163 164 164 def create(self): 165 165 """ … … 168 168 """ 169 169 self.shapeObject = geoshapespy.new_ellipsoid(\ 170 self.params["radius_x"], self.params["radius_y"], 170 self.params["radius_x"], self.params["radius_y"], 171 171 self.params["radius_z"]) 172 173 ShapeDescriptor.create(self) 172 173 ShapeDescriptor.create(self) 174 174 return self.shapeObject 175 175 176 176 class HelixDescriptor(ShapeDescriptor): 177 177 """ 178 178 Descriptor for an helix 179 179 180 180 Parameters: 181 181 -radius_helix: the radius of the helix [default = 10 A] … … 188 188 """ 189 189 Initialization 190 """ 190 """ 191 191 ShapeDescriptor.__init__(self) 192 192 # Default parameters 193 self.params["type"] 193 self.params["type"] = "singlehelix" 194 194 self.params["radius_helix"] = 10.0 195 195 self.params["radius_tube"] = 3.0 … … 204 204 """ 205 205 self.shapeObject = geoshapespy.new_singlehelix(\ 206 self.params["radius_helix"], self.params["radius_tube"], 206 self.params["radius_helix"], self.params["radius_tube"], 207 207 self.params["pitch"], self.params["turns"]) 208 209 ShapeDescriptor.create(self) 208 209 ShapeDescriptor.create(self) 210 210 return self.shapeObject 211 211 212 212 class PDBDescriptor(ShapeDescriptor): 213 213 """ 214 214 Descriptor for a PDB set of points 215 215 216 216 Parameter: 217 217 - file = name of the PDB file … … 221 221 Initialization 222 222 @param filename: name of the PDB file to load 223 """ 223 """ 224 224 ShapeDescriptor.__init__(self) 225 225 # Default parameters 226 self.params["type"] 226 self.params["type"] = "pdb" 227 227 self.params["file"] = filename 228 228 self.params['is_lores'] = False … … 234 234 """ 235 235 self.shapeObject = pointsmodelpy.new_pdbmodel() 236 pointsmodelpy.pdbmodel_add(self.shapeObject, self.params['file']) 237 238 #ShapeDescriptor.create(self) 236 pointsmodelpy.pdbmodel_add(self.shapeObject, self.params['file']) 237 238 #ShapeDescriptor.create(self) 239 239 return self.shapeObject 240 240 241 241 # Define a dictionary for the shape until we find 242 242 # a better way to create them … … 245 245 'ellipsoid':EllipsoidDescriptor, 246 246 'singlehelix':HelixDescriptor} 247 247 248 248 class VolumeCanvas(BaseComponent): 249 249 """ 250 Class representing an empty space volume to add 250 Class representing an empty space volume to add 251 251 geometrical object to. 252 252 253 253 For 1D I(q) simulation, getPr() is called internally for the 254 254 first call to getIq(). 255 256 """ 257 255 256 """ 257 258 258 def __init__(self): 259 259 """ … … 261 261 """ 262 262 BaseComponent.__init__(self) 263 263 264 264 ## Maximum value of q reachable 265 265 self.params['q_max'] = 0.1 … … 267 267 self.params['scale'] = 1.0 268 268 self.params['background'] = 0.0 269 269 270 270 self.lores_model = pointsmodelpy.new_loresmodel(self.params['lores_density']) 271 271 self.complex_model = pointsmodelpy.new_complexmodel() 272 272 self.shapes = {} 273 self.shapecount = 0 273 self.shapecount = 0 274 274 self.points = None 275 275 self.npts = 0 276 self.hasPr = False 277 276 self.hasPr = False 277 278 278 def _model_changed(self): 279 279 """ 280 Reset internal data members to reflect the fact that the 280 Reset internal data members to reflect the fact that the 281 281 real-space model has changed 282 282 """ 283 self.hasPr 283 self.hasPr = False 284 284 self.points = None 285 286 def addObject(self, shapeDesc, id =None):285 286 def addObject(self, shapeDesc, id=None): 287 287 """ 288 288 Adds a real-space object to the canvas. 289 289 290 290 @param shapeDesc: object to add to the canvas [ShapeDescriptor] 291 291 @param id: string handle for the object [string] [optional] … … 295 295 if id is None: 296 296 id = shapeDesc.params["type"]+str(self.shapecount) 297 297 298 298 # Self the order number 299 299 shapeDesc.params['order'] = self.shapecount … … 307 307 308 308 return id 309 310 311 def add(self, shape, id =None):309 310 311 def add(self, shape, id=None): 312 312 """ 313 313 The intend of this method is to eventually be able to use it … … 315 315 analytical solutions. For instance, if one adds a cylinder and 316 316 it is the only shape on the canvas, the analytical solution 317 could be called. If multiple shapes are involved, then 317 could be called. If multiple shapes are involved, then 318 318 simulation has to be performed. 319 319 320 320 This function is deprecated, use addObject(). 321 321 322 322 @param shape: name of the object to add to the canvas [string] 323 323 @param id: string handle for the object [string] [optional] … … 327 327 if id is None: 328 328 id = "shape"+str(self.shapecount) 329 329 330 330 # shapeDesc = ShapeDescriptor(shape.lower()) 331 331 if shape.lower() in shape_dict: … … 336 336 else: 337 337 raise ValueError("VolumeCanvas.add: Unknown shape %s" % shape) 338 338 339 339 return self.addObject(shapeDesc, id) 340 340 … … 354 354 355 355 356 def setParam(self, name, value): 357 """ 358 Function to set the value of a parameter. 356 def setParam(self, name, value): 357 """ 358 Function to set the value of a parameter. 359 359 Both VolumeCanvas parameters and shape parameters 360 are accessible. 361 360 are accessible. 361 362 362 Note: if shape parameters are accessed directly 363 363 from outside VolumeCanvas. The getPr method 364 364 should be called before evaluating I(q). 365 365 366 366 TODO: implemented a check method to protect 367 367 against that. 368 368 369 369 @param name: name of the parameter to change 370 370 @param value: value to give the parameter 371 371 """ 372 372 373 373 # Lowercase for case insensitivity 374 374 name = name.lower() 375 375 376 376 # Look for shape access 377 377 toks = name.split('.') 378 378 379 379 # If a shape identifier was given, look the shape up 380 380 # in the dictionary … … 390 390 else: 391 391 raise ValueError("Could not find shape %s" % toks[0]) 392 392 393 393 else: 394 # If we are not accessing the parameters of a 394 # If we are not accessing the parameters of a 395 395 # shape, see if the parameter is part of this object 396 396 BaseComponent.setParam(self, name, value) 397 397 self._model_changed() 398 398 399 def getParam(self, name): 399 def getParam(self, name): 400 400 """ 401 401 @param name: name of the parameter to change 402 402 """ 403 403 #TODO: clean this up 404 404 405 405 # Lowercase for case insensitivity 406 406 name = name.lower() 407 407 408 408 # Look for sub-model access 409 409 toks = name.split('.') … … 435 435 else: 436 436 raise ValueError("VolumeCanvas.getParam: Could not find %s" % name) 437 437 438 438 def getParamList(self, shapeid=None): 439 439 """ 440 return a full list of all available parameters from 440 return a full list of all available parameters from 441 441 self.params.keys(). If a key in self.params is a instance 442 of ShapeDescriptor, extend the return list to: 442 of ShapeDescriptor, extend the return list to: 443 443 [param1,param2,shapeid.param1,shapeid.param2.......] 444 444 … … 456 456 header = key2 + '.' 457 457 for key3 in value2.params: 458 fullname = header + key3 458 fullname = header + key3 459 459 param_list.append(fullname) 460 460 461 461 else: 462 462 if not shapeid in self.shapes: … … 470 470 def getShapeList(self): 471 471 """ 472 Return a list of the shapes 472 Return a list of the shapes 473 473 """ 474 474 return self.shapes.keys() … … 481 481 # Create the object model 482 482 shapeDesc.create() 483 483 484 484 if shapeDesc.params['is_lores']: 485 485 # Add the shape to the lores_model 486 pointsmodelpy.lores_add(self.lores_model, 487 shapeDesc.shapeObject, shapeDesc.params['contrast']) 486 pointsmodelpy.lores_add(self.lores_model, 487 shapeDesc.shapeObject, 488 shapeDesc.params['contrast']) 488 489 489 490 def _createVolumeFromList(self): … … 492 493 Whenever we change a parameter of a shape, we have to re-create 493 494 the whole thing. 494 495 495 496 Items with higher 'order' number take precedence for regions 496 of space that are shared with other objects. Points in the 497 of space that are shared with other objects. Points in the 497 498 overlapping region belonging to objects with lower 'order' 498 499 will be ignored. 499 500 500 501 Items are added in decreasing 'order' number. 501 502 The item with the highest 'order' will be added *first*. 502 503 [That conventions is prescribed by the realSpaceModeling module] 503 504 """ 504 505 505 506 # Create empty model 506 507 self.lores_model = \ … … 509 510 # Create empty complex model 510 511 self.complex_model = pointsmodelpy.new_complexmodel() 511 512 512 513 # Order the object first 513 514 obj_list = [] 514 515 515 516 for shape in self.shapes: 516 517 order = self.shapes[shape].params['order'] 517 518 # find where to place it in the list 518 519 stored = False 519 520 520 521 for i in range(len(obj_list)): 521 522 if obj_list[i][0] > order: … … 523 524 stored = True 524 525 break 525 526 526 527 if not stored: 527 528 obj_list.append([order, shape]) 528 529 529 530 # Add each shape 530 531 len_list = len(obj_list) … … 533 534 self._addSingleShape(shapedesc) 534 535 535 return 0 536 536 return 0 537 537 538 def getPr(self): 538 539 """ … … 540 541 This method should always be called after the shapes 541 542 on the VolumeCanvas have changed. 542 543 @return: calculation output flag 543 544 @return: calculation output flag 544 545 """ 545 546 # To find a complete example of the correct call order: 546 547 # In LORES2, in actionclass.py, method CalculateAction._get_iq() 547 548 548 549 # If there are not shapes, do nothing 549 550 if len(self.shapes) == 0: 550 551 self._model_changed() 551 552 return 0 552 553 553 554 # generate space filling points from shape list 554 555 self._createVolumeFromList() … … 556 557 self.points = pointsmodelpy.new_point3dvec() 557 558 558 pointsmodelpy.complexmodel_add(self.complex_model, 559 559 pointsmodelpy.complexmodel_add(self.complex_model, 560 self.lores_model, "LORES") 560 561 for shape in self.shapes: 561 if self.shapes[shape].params['is_lores'] == False:562 pointsmodelpy.complexmodel_add(self.complex_model, 562 if not self.shapes[shape].params['is_lores']: 563 pointsmodelpy.complexmodel_add(self.complex_model, 563 564 self.shapes[shape].shapeObject, "PDB") 564 565 565 566 #pointsmodelpy.get_lorespoints(self.lores_model, self.points) 566 567 self.npts = pointsmodelpy.get_complexpoints(self.complex_model, self.points) 567 568 568 569 # expecting the rmax is a positive float or 0. The maximum distance. 569 #rmax = pointsmodelpy.get_lores_pr(self.lores_model, self.points) 570 571 rmax = pointsmodelpy.get_complex_pr(self.complex_model, self.points) 572 self.hasPr = True 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 573 574 574 575 return rmax 575 576 def run(self, q =0):576 577 def run(self, q=0): 577 578 """ 578 579 Returns the value of I(q) for a given q-value … … 595 596 else: 596 597 raise ValueError("run(q): bad type for q") 597 598 def runXY(self, q =0):598 599 def runXY(self, q=0): 599 600 """ 600 601 Standard run command for the canvas. 601 Redirects to the correct method 602 Redirects to the correct method 602 603 according to the input type. 603 604 @param q: q-value [float] or [list] [A-1] … … 615 616 else: 616 617 raise ValueError("runXY(q): bad type for q") 617 618 618 619 def _create_modelObject(self): 619 620 """ 620 621 Create the simulation model obejct from the list 621 622 of shapes. 622 623 623 624 This method needs to be called each time a parameter 624 625 changes because of the way the underlying library 625 was (badly) written. It is impossible to change a 626 parameter, or remove a shape without having to 626 was (badly) written. It is impossible to change a 627 parameter, or remove a shape without having to 627 628 refill the space points. 628 629 629 630 TODO: improve that. 630 631 """ 631 632 # To find a complete example of the correct call order: 632 633 # In LORES2, in actionclass.py, method CalculateAction._get_iq() 633 634 634 635 # If there are not shapes, do nothing 635 636 if len(self.shapes) == 0: 636 637 self._model_changed() 637 638 return 0 638 639 639 640 # generate space filling points from shape list 640 641 self._createVolumeFromList() … … 642 643 self.points = pointsmodelpy.new_point3dvec() 643 644 644 pointsmodelpy.complexmodel_add(self.complex_model, 645 645 pointsmodelpy.complexmodel_add(self.complex_model, 646 self.lores_model, "LORES") 646 647 for shape in self.shapes: 647 if self.shapes[shape].params['is_lores'] == False:648 pointsmodelpy.complexmodel_add(self.complex_model, 648 if not self.shapes[shape].params['is_lores']: 649 pointsmodelpy.complexmodel_add(self.complex_model, 649 650 self.shapes[shape].shapeObject, "PDB") 650 651 651 652 #pointsmodelpy.get_lorespoints(self.lores_model, self.points) 652 653 self.npts = pointsmodelpy.get_complexpoints(self.complex_model, self.points) 653 654 654 655 655 656 def getIq2D(self, qx, qy): 656 657 """ … … 660 661 @return: I(q) [cm-1] 661 662 """ 662 663 663 664 # If this is the first simulation call, we need to generate the 664 665 # space points 665 666 if self.points is None: 666 667 self._create_modelObject() 667 668 668 669 # Protect against empty model 669 670 if self.points is None: 670 671 return 0 671 672 # Evalute I(q) 673 norm = 672 673 # Evalute I(q) 674 norm = 1.0e8/self.params['lores_density']*self.params['scale'] 674 675 return norm*pointsmodelpy.get_complex_iq_2D(self.complex_model, self.points, qx, qy)\ 675 676 + self.params['background'] 676 677 677 678 def write_pr(self, filename): 678 679 """ 679 680 Write P(r) to an output file 680 681 @param filename: file name for P(r) output 681 """ 682 if self.hasPr == False:682 """ 683 if not self.hasPr: 683 684 self.getPr() 684 685 685 686 pointsmodelpy.outputPR(self.complex_model, filename) 686 687 687 688 def getPrData(self): 688 689 """ 689 690 Write P(r) to an output file 690 691 @param filename: file name for P(r) output 691 """ 692 if self.hasPr == False:692 """ 693 if not self.hasPr: 693 694 self.getPr() 694 695 695 696 return pointsmodelpy.get_pr(self.complex_model) 696 697 697 698 def getIq(self, q): 698 699 """ 699 700 Returns the value of I(q) for a given q-value 700 701 701 702 This method should remain internal to the class 702 703 and the run() method should be used instead. 703 704 704 705 @param q: q-value [float] 705 706 @return: I(q) [float] 706 707 """ 707 708 if self.hasPr == False:708 709 if not self.hasPr: 709 710 self.getPr() 710 711 711 # By dividing by the density instead of the actuall V/N, 712 # we have an uncertainty of +-1 on N because the number 712 # By dividing by the density instead of the actuall V/N, 713 # we have an uncertainty of +-1 on N because the number 713 714 # of points chosen for the simulation is int(density*volume). 714 715 # Propagation of error gives: … … 716 717 # where N is stored in self.npts 717 718 718 norm = 719 norm = 1.0e8/self.params['lores_density']*self.params['scale'] 719 720 #return norm*pointsmodelpy.get_lores_i(self.lores_model, q) 720 721 return norm*pointsmodelpy.get_complex_i(self.complex_model, q)\ 721 722 + self.params['background'] 722 723 723 724 def getError(self, q): 724 725 """ … … 727 728 @return: I(q) [float] 728 729 """ 729 730 if self.hasPr == False:730 731 if not self.hasPr: 731 732 self.getPr() 732 733 733 # By dividing by the density instead of the actual V/N, 734 # we have an uncertainty of +-1 on N because the number 734 # By dividing by the density instead of the actual V/N, 735 # we have an uncertainty of +-1 on N because the number 735 736 # of points chosen for the simulation is int(density*volume). 736 737 # Propagation of error gives: … … 738 739 # where N is stored in self.npts 739 740 740 norm = 741 norm = 1.0e8/self.params['lores_density']*self.params['scale'] 741 742 #return norm*pointsmodelpy.get_lores_i(self.lores_model, q) 742 743 return norm*pointsmodelpy.get_complex_i_error(self.complex_model, q)\ 743 744 + self.params['background'] 744 745 745 746 def getIqError(self, q): 746 747 """ 747 748 Return the simulated value along with its estimated 748 749 error for a given q-value 749 750 750 751 Propagation of errors is used to evaluate the 751 752 uncertainty. 752 753 753 754 @param q: q-value [float] 754 755 @return: mean, error [float, float] … … 765 766 Return the simulated value along with its estimated 766 767 error for a given q-value 767 768 768 769 Propagation of errors is used to evaluate the 769 770 uncertainty. 770 771 771 772 @param qx: qx-value [float] 772 773 @param qy: qy-value [float] … … 774 775 """ 775 776 self._create_modelObject() 776 777 norm = 777 778 norm = 1.0e8/self.params['lores_density']*self.params['scale'] 778 779 val = norm*pointsmodelpy.get_complex_iq_2D(self.complex_model, self.points, qx, qy)\ 779 780 + self.params['background'] 780 781 781 782 # Simulation error (statistical) 782 norm = 783 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 784 785 err = norm*pointsmodelpy.get_complex_iq_2D_err(self.complex_model, self.points, qx, qy) 785 786 # Error on V/N 786 787 simerr = 2*val/self.npts 787 788 788 789 # The error used for the position is over-simplified. 789 790 # The actual error was empirically found to be about 790 791 # an order of magnitude larger. 791 792 return val, 10.0*err+simerr 792
Note: See TracChangeset
for help on using the changeset viewer.