Changeset a86f6c0 in sasmodels
- Timestamp:
- Apr 8, 2016 5:22:43 PM (9 years ago)
- Branches:
- master, core_shell_microgels, costrafo411, magnetic_model, release_v0.94, release_v0.95, ticket-1257-vesicle-product, ticket_1156, ticket_1265_superball, ticket_822_more_unit_tests
- Children:
- 7edce22
- Parents:
- 2afc26d
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/modelinfo.py
r4bfd277 ra86f6c0 5 5 6 6 from .details import mono_details 7 8 # Optional typing 9 try: 10 from typing import Tuple, List, Union, Dict, Optional, Any, Callable 11 except ImportError: 12 pass 13 else: 14 from .details import CallDetails 15 Limits = Tuple[float, float] 16 LimitsOrChoice = Union[Limits, Tuple[str]] 17 ParameterDef = Tuple[str, str, float, LimitsOrChoice, str, str] 18 ParameterSetUser = Dict[str, Union[float, List[float]]] 19 ParameterSet = Dict[str, float] 20 TestInput = Union[str, float, List[float], Tuple[float, float], List[Tuple[float, float]]] 21 TestValue = Union[float, List[float]] 22 TestCondition = Tuple[ParameterSetUser, TestInput, TestValue] 7 23 8 24 MAX_PD = 4 … … 25 41 # depends on the some polydisperse parameter with the current implementation 26 42 43 27 44 def make_parameter_table(pars): 45 # type: (List[ParameterDefinition) -> ParameterTable 28 46 processed = [] 29 47 for p in pars: 30 if not isinstance(p, list) or len(p) != 6:48 if not isinstance(p, (list, tuple)) or len(p) != 6: 31 49 raise ValueError("Parameter should be [name, units, default, limits, type, desc], but got %r" 32 50 %str(p)) … … 35 53 return partable 36 54 37 def parse_parameter(name, units='', default=None, 38 limits=(-np.inf, np.inf), type='', description=''): 55 def parse_parameter(name, units='', default=np.NaN, 56 limits=(-np.inf, np.inf), ptype='', description=''): 57 # type: (str, str, float, LimitsOrChoice, str, str) -> Parameter 39 58 # Parameter is a user facing class. Do robust type checking. 40 59 if not isstr(name): … … 68 87 % (default, name)) 69 88 70 if type not in ("volume", "orientation", "sld", "magnetic", ""):71 raise ValueError("unexpected type %r for %s" % ( type, name))89 if ptype not in ("volume", "orientation", "sld", "magnetic", ""): 90 raise ValueError("unexpected type %r for %s" % (ptype, name)) 72 91 73 92 if not isstr(description): … … 86 105 87 106 # automatically identify sld types 88 if type=='' and (pid.startswith('sld') or pid.endswith('sld')):89 type = 'sld'107 if ptype== '' and (pid.startswith('sld') or pid.endswith('sld')): 108 ptype = 'sld' 90 109 91 110 # Check if using a vector definition, name[k], as the parameter name … … 96 115 length = int(ref) 97 116 control = None 98 except Exception:117 except ValueError: 99 118 length = None 100 119 control = ref … … 105 124 # Build the parameter 106 125 parameter = Parameter(name=name, units=units, default=default, 107 limits=limits, type=type, description=description)126 limits=limits, ptype=ptype, description=description) 108 127 109 128 # TODO: need better control over whether a parameter is polydisperse 110 parameter.polydisperse = type in ('orientation', 'volume')111 parameter.relative_pd = type in ('volume')129 parameter.polydisperse = ptype in ('orientation', 'volume') 130 parameter.relative_pd = ptype == 'volume' 112 131 parameter.choices = choices 113 132 parameter.length = length … … 118 137 119 138 def expand_pars(partable, pars): 139 # type: (ParameterTable, ParameterSetUser) -> ParameterSet 120 140 """ 121 141 Create demo parameter set from key-value pairs. … … 159 179 160 180 def prefix_parameter(par, prefix): 181 # type: (Parameter, str) -> Parameter 161 182 """ 162 183 Return a copy of the parameter with its name prefixed. … … 167 188 168 189 def suffix_parameter(par, suffix): 190 # type: (Parameter, str) -> Parameter 169 191 """ 170 192 Return a copy of the parameter with its name prefixed. … … 233 255 """ 234 256 def __init__(self, name, units='', default=None, limits=(-np.inf, np.inf), 235 type='', description=''): 236 self.id = name.split('[')[0].strip() 237 self.name = name 238 self.units = units 239 self.default = default 240 self.limits = limits 241 self.type = type 242 self.description = description 257 ptype='', description=''): 258 # type: (str, str, float, Limits, str, str) 259 self.id = name.split('[')[0].strip() # type: str 260 self.name = name # type: str 261 self.units = units # type: str 262 self.default = default # type: float 263 self.limits = limits # type: Limits 264 self.type = ptype # type: str 265 self.description = description # type: str 243 266 244 267 # Length and length_control will be filled in once the complete 245 268 # parameter table is available. 246 self.length = 1 247 self.length_control = None 248 self.is_control = False 269 self.length = 1 # type: int 270 self.length_control = None # type: Optional[str] 271 self.is_control = False # type: bool 249 272 250 273 # TODO: need better control over whether a parameter is polydisperse 251 self.polydisperse = False 252 self.relative_pd = False 274 self.polydisperse = False # type: bool 275 self.relative_pd = False # type: bool 253 276 254 277 # choices are also set externally. 255 self.choices = [] 278 self.choices = [] # type: List[str] 256 279 257 280 def as_definition(self): 281 # type: () -> str 258 282 """ 259 283 Declare space for the variable in a parameter structure. … … 269 293 270 294 def as_function_argument(self): 295 # type: () -> str 271 296 """ 272 297 Declare the variable as a function argument. … … 282 307 283 308 def as_call_reference(self, prefix=""): 309 # type: () -> str 284 310 # Note: if the parameter is a struct type, then we will need to use 285 311 # &prefix+id. For scalars and vectors we can just use prefix+id. … … 287 313 288 314 def __str__(self): 315 # type: () -> str 289 316 return "<%s>"%self.name 290 317 291 318 def __repr__(self): 319 # type: () -> str 292 320 return "P<%s>"%self.name 293 321 … … 355 383 356 384 def __init__(self, parameters): 385 # type: (List[Parameter]) -> None 357 386 self.kernel_parameters = parameters 358 387 self._set_vector_lengths() 359 self._make_call_parameter_list() 360 self._categorize_parameters() 361 self._set_defaults() 388 self.call_parameters = self._get_call_parameters() 389 self.defaults = self._get_defaults() 362 390 #self._name_table= dict((p.id, p) for p in parameters) 363 391 392 # Set the kernel parameters. Assumes background and scale are the 393 # first two parameters in the parameter list, but these are not sent 394 # to the underlying kernel functions. 395 self.iq_parameters = [p for p in self.kernel_parameters 396 if p.type not in ('orientation', 'magnetic')] 397 self.iqxy_parameters = [p for p in self.kernel_parameters 398 if p.type != 'magnetic'] 399 self.form_volume_parameters = [p for p in self.kernel_parameters 400 if p.type == 'volume'] 401 402 # Theta offset 403 offset = 0 404 for p in self.kernel_parameters: 405 if p.name == 'theta': 406 self.theta_offset = offset 407 break 408 offset += p.length 409 else: 410 self.theta_offset = -1 411 412 # number of polydisperse parameters 413 num_pd = sum(p.length for p in self.kernel_parameters if p.polydisperse) 414 # Don't use more polydisperse parameters than are available in the model 415 # Note: we can do polydispersity on arbitrary parameters, so it is not 416 # clear that this is a good idea; it does however make the poly_details 417 # code easier to write, so we will leave it in for now. 418 self.max_pd = min(num_pd, MAX_PD) 419 420 self.npars = sum(p.length for p in self.kernel_parameters) 421 422 # true if has 2D parameters 423 self.has_2d = any(p.type in ('orientation', 'magnetic') 424 for p in self.kernel_parameters) 425 426 self.pd_1d = set(p.name for p in self.call_parameters 427 if p.polydisperse and p.type not in ('orientation', 'magnetic')) 428 self.pd_2d = set(p.name for p in self.call_parameters 429 if p.polydisperse and p.type != 'magnetic') 430 431 364 432 def _set_vector_lengths(self): 433 # type: () -> None 434 """ 435 Walk the list of kernel parameters, setting the length field of the 436 vector parameters from the upper limit of the reference parameter. 437 438 This needs to be done once the entire parameter table is available 439 since the reference may still be undefined when the parameter is 440 initially created. 441 442 Note: This modifies the underlying parameter object. 443 """ 365 444 # Sort out the length of the vector parameters such as thickness[n] 366 445 for p in self.kernel_parameters: … … 374 453 ref.is_control = True 375 454 low, high = ref.limits 376 if int(low) != low or int(high) != high or low <0 or high>20:455 if int(low) != low or int(high) != high or low < 0 or high > 20: 377 456 raise ValueError("expected limits on %s to be within [0, 20]" 378 457 % ref.name) 458 # TODO: may want to make a copy of the parameter before updating 459 # this introduces other potential problems, since the same 460 # parameter may be referenced elsewhere 379 461 p.length = high 380 462 381 def _set_defaults(self): 463 def _get_defaults(self): 464 # type: () -> ParameterSet 465 """ 466 Get a list of parameter defaults from the parameters. 467 468 Expands vector parameters into parameter id+number. 469 """ 382 470 # Construct default values, including vector defaults 383 471 defaults = {} … … 388 476 for k in range(1, p.length+1): 389 477 defaults["%s%d"%(p.id, k)] = p.default 390 self.defaults = defaults 391 392 def _make_call_parameter_list(self): 478 return defaults 479 480 def _get_call_parameters(self): 481 # type: () -> List[Parameter] 393 482 full_list = self.COMMON[:] 394 483 for p in self.kernel_parameters: … … 402 491 pk.relative_pd = p.relative_pd 403 492 full_list.append(pk) 404 self.call_parameters = full_list 405 406 def _categorize_parameters(self): 407 # Set the kernel parameters. Assumes background and scale are the 408 # first two parameters in the parameter list, but these are not sent 409 # to the underlying kernel functions. 410 self.iq_parameters = [p for p in self.kernel_parameters 411 if p.type not in ('orientation', 'magnetic')] 412 self.iqxy_parameters = [p for p in self.kernel_parameters 413 if p.type != 'magnetic'] 414 self.form_volume_parameters = [p for p in self.kernel_parameters 415 if p.type == 'volume'] 416 417 # Theta offset 418 offset = 0 419 for p in self.kernel_parameters: 420 if p.name == 'theta': 421 self.theta_offset = offset 422 break 423 offset += p.length 424 else: 425 self.theta_offset = -1 426 427 # number of polydisperse parameters 428 num_pd = sum(p.length for p in self.kernel_parameters if p.polydisperse) 429 # Don't use more polydisperse parameters than are available in the model 430 # Note: we can do polydispersity on arbitrary parameters, so it is not 431 # clear that this is a good idea; it does however make the poly_details 432 # code easier to write, so we will leave it in for now. 433 self.max_pd = min(num_pd, MAX_PD) 434 435 self.npars = sum(p.length for p in self.kernel_parameters) 436 437 # true if has 2D parameters 438 self.has_2d = any(p.type in ('orientation', 'magnetic') 439 for p in self.kernel_parameters) 440 441 self.pd_1d = set(p.name for p in self.call_parameters 442 if p.polydisperse and p.type not in ('orientation', 'magnetic')) 443 self.pd_2d = set(p.name for p in self.call_parameters 444 if p.polydisperse and p.type != 'magnetic') 493 return full_list 445 494 446 495 def user_parameters(self, pars={}, is2d=True): 496 # type: (Dict[str, float], bool) -> List[str] 447 497 """ 448 498 Return the list of parameters for the given data type. … … 530 580 531 581 def isstr(x): 582 # type: (Any) -> bool 532 583 # TODO: 2-3 compatible tests for str, including unicode strings 533 584 return isinstance(x, str) … … 557 608 info.structure_factor = getattr(kernel_module, 'structure_factor', False) 558 609 info.profile_axes = getattr(kernel_module, 'profile_axes', ['x','y']) 559 info.variant_info = getattr(kernel_module, 'invariant_info', None) 560 info.demo = getattr(kernel_module, 'demo', None) 610 info.variant_info = getattr(kernel_module, 'variant_info', None) 561 611 info.source = getattr(kernel_module, 'source', []) 562 612 info.tests = getattr(kernel_module, 'tests', []) … … 613 663 build complete product and mixture models from just the info. 614 664 """ 615 id = None 616 filename = None 617 name = None 618 title = None 619 description = None 620 parameters = None 621 demo = None 622 composition = None 623 docs = None 624 category = None 625 single = None 626 structure_factor = None 627 profile_axes = None 628 variant_info = None 629 demo = None 630 source = None 631 tests = None 632 ER = None 633 VR = None 634 form_volume = None 635 Iq = None 636 Iqxy = None 665 id = None # type: str 666 filename = None # type: str 667 name = None # type: str 668 title = None # type: str 669 description = None # type: str 670 parameters = None # type: ParameterTable 671 demo = None # type: Dict[str, float] 672 composition = None # type: Optional[Tuple[str, List[ModelInfo]]] 673 docs = None # type: str 674 category = None # type: Optional[str] 675 single = None # type: bool 676 structure_factor = None # type: bool 677 profile_axes = None # type: Tuple[str, str] 678 variant_info = None # type: Optional[List[str]] 679 source = None # type: List[str] 680 tests = None # type: List[TestCondition] 681 ER = None # type: Optional[Callable[[np.ndarray, ...], float]] 682 VR = None # type: Optional[Callable[[np.ndarray, ...], float]] 683 form_volume = None # type: Optional[Callable[[np.ndarray, ...], float]] 684 Iq = None # type: Optional[Callable[[np.ndarray, ...], np.ndarray]] 685 Iqxy = None # type: Optional[Callable[[np.ndarray, ...], np.ndarray]] 637 686 profile = None 638 687 sesans = None 639 mono_details = None 688 mono_details = None # type: CallDetails 640 689 641 690 def __init__(self): 691 # type: () -> None 642 692 pass 643 693
Note: See TracChangeset
for help on using the changeset viewer.