Changeset d19962c in sasmodels for sasmodels/modelinfo.py
- Timestamp:
- Mar 27, 2016 6:57:03 PM (8 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:
- 5c028e3
- Parents:
- c499331
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/modelinfo.py
rc499331 rd19962c 4 4 # TODO: turn ModelInfo into a proper class 5 5 ModelInfo = dict 6 7 MAX_PD = 4 6 8 7 9 COMMON_PARAMETERS = [ … … 153 155 *length* is the length of the field if it is a vector field 154 156 *length_control* is the parameter which sets the vector length 157 *is_control* is True if the parameter is a control parameter for a vector 155 158 *polydisperse* is true if the parameter accepts a polydispersity 156 159 *relative_pd* is true if that polydispersity is relative … … 163 166 self.id = name.split('[')[0].strip() 164 167 self.name = name 168 self.units = units 165 169 self.default = default 166 170 self.limits = limits … … 169 173 self.choices = None 170 174 171 # Length and length_control will be filled in by 172 # set_vector_length_from_reference(partable) once the complete 175 # Length and length_control will be filled in once the complete 173 176 # parameter table is available. 174 177 self.length = 1 175 178 self.length_control = None 179 self.is_control = False 176 180 177 181 # TODO: need better control over whether a parameter is polydisperse … … 216 220 return "P<%s>"%self.name 217 221 222 218 223 class ParameterTable(object): 224 """ 225 ParameterTable manages the list of available parameters. 226 227 There are a couple of complications which mean that the list of parameters 228 for the kernel differs from the list of parameters that the user sees. 229 230 (1) Common parameters. Scale and background are implicit to every model, 231 but are not passed to the kernel. 232 233 (2) Vector parameters. Vector parameters are passed to the kernel as a 234 pointer to an array, e.g., thick[], but they are seen by the user as n 235 separate parameters thick1, thick2, ... 236 237 Therefore, the parameter table is organized by how it is expected to be 238 used. The following information is needed to set up the kernel functions: 239 240 * *kernel_parameters* is the list of parameters in the kernel parameter 241 table, with vector parameter p declared as p[]. 242 243 * *iq_parameters* is the list of parameters to the Iq(q, ...) function, 244 with vector parameter p sent as p[]. 245 246 * *iqxy_parameters* is the list of parameters to the Iqxy(qx, qy, ...) 247 function, with vector parameter p sent as p[]. 248 249 * *form_volume_parameters* is the list of parameters to the form_volume(...) 250 function, with vector parameter p sent as p[]. 251 252 Problem details, which sets up the polydispersity loops, requires the 253 following: 254 255 * *theta_offset* is the offset of the theta parameter in the kernel parameter 256 table, with vector parameters counted as n individual parameters 257 p1, p2, ..., or offset is -1 if there is no theta parameter. 258 259 * *max_pd* is the maximum number of polydisperse parameters, with vector 260 parameters counted as n individual parameters p1, p2, ... Note that 261 this number is limited to sasmodels.modelinfo.MAX_PD. 262 263 * *npars* is the total number of parameters to the kernel, with vector 264 parameters counted as n individual parameters p1, p2, ... 265 266 * *call_parameters* is the complete list of parameters to the kernel, 267 including scale and background, with vector parameters recorded as 268 individual parameters p1, p2, ... 269 270 * *active_1d* is the set of names that may be polydisperse for 1d data 271 272 * *active_2d* is the set of names that may be polydisperse for 2d data 273 274 User parameters are the set of parameters visible to the user, including 275 the scale and background parameters that the kernel does not see. User 276 parameters don't use vector notation, and instead use p1, p2, ... 277 278 * *control_parameters* is the 279 280 """ 219 281 # scale and background are implicit parameters 220 282 COMMON = [Parameter(*p) for p in COMMON_PARAMETERS] 221 283 222 284 def __init__(self, parameters): 223 self.parameters = self.COMMON + parameters 224 self._name_table= dict((p.name, p) for p in parameters) 285 self.kernel_parameters = parameters 286 self._set_vector_lengths() 287 self._make_call_parameter_list() 225 288 self._categorize_parameters() 226 227 self._set_vector_lengths()228 289 self._set_defaults() 290 #self._name_table= dict((p.id, p) for p in parameters) 229 291 230 292 def _set_vector_lengths(self): 231 293 # Sort out the length of the vector parameters such as thickness[n] 232 for p in self. parameters:294 for p in self.kernel_parameters: 233 295 if p.length_control: 234 ref = self._name_table[p.length_control] 296 for ref in self.kernel_parameters: 297 if ref.id == p.length_control: 298 break 299 else: 300 raise ValueError("no reference variable %r for %s" 301 % (p.length_control, p.name)) 302 ref.is_control = True 235 303 low, high = ref.limits 236 304 if int(low) != low or int(high) != high or low<0 or high>20: 237 raise ValueError("expected limits on %s to be within [0, 20]"%ref.name) 305 raise ValueError("expected limits on %s to be within [0, 20]" 306 % ref.name) 238 307 p.length = high 239 308 … … 241 310 # Construct default values, including vector defaults 242 311 defaults = {} 243 for p in self. parameters:312 for p in self.call_parameters: 244 313 if p.length == 1: 245 314 defaults[p.id] = p.default 246 315 else: 247 for k in range( p.length):248 defaults["%s [%d]"%(p.id, k)] = p.default316 for k in range(1, p.length+1): 317 defaults["%s%d"%(p.id, k)] = p.default 249 318 self.defaults = defaults 250 319 320 def _make_call_parameter_list(self): 321 full_list = self.COMMON[:] 322 for p in self.kernel_parameters: 323 if p.length == 1: 324 full_list.append(p) 325 else: 326 for k in range(1, p.length+1): 327 pk = Parameter(p.id+str(k), p.units, p.default, 328 p.limits, p.type, p.description) 329 pk.polydisperse = p.polydisperse 330 pk.relative_pd = p.relative_pd 331 full_list.append(pk) 332 self.call_parameters = full_list 333 334 """ # Suppress these for now until we see how they are used 251 335 def __getitem__(self, k): 252 336 if isinstance(k, (int, slice)): … … 259 343 260 344 def __iter__(self): 261 return iter(self.parameters) 262 263 def kernel_pars(self, ptype=None): 264 """ 265 Return the parameters to the user kernel which match the given type. 266 267 Types include '1d' for Iq kernels, '2d' for Iqxy kernels and 268 'volume' for form_volume kernels. 269 """ 270 # Assumes background and scale are the first two parameters 271 if ptype is None: 272 return self.parameters[2:] 345 return iter(self.expanded_parameters) 346 """ 347 348 def _categorize_parameters(self): 349 # Set the kernel parameters. Assumes background and scale are the 350 # first two parameters in the parameter list, but these are not sent 351 # to the underlying kernel functions. 352 self.iq_parameters = [p for p in self.kernel_parameters 353 if p.type not in ('orientation', 'magnetic')] 354 self.iqxy_parameters = [p for p in self.kernel_parameters 355 if p.type != 'magnetic'] 356 self.form_volume_parameters = [p for p in self.kernel_parameters 357 if p.type == 'volume'] 358 359 # Theta offset 360 offset = 0 361 for p in self.kernel_parameters: 362 if p.name == 'theta': 363 self.theta_offset = offset 364 break 365 offset += p.length 273 366 else: 274 return [p for p in self.parameters[2:] if p in self.type[ptype]] 275 276 def _categorize_parameters(self): 277 """ 278 Build parameter categories out of the the parameter definitions. 279 280 Returns a dictionary of categories. 281 282 Note: these categories are subject to change, depending on the needs of 283 the UI and the needs of the kernel calling function. 284 285 The categories are as follows: 286 287 * *volume* list of volume parameter names 288 * *orientation* list of orientation parameters 289 * *magnetic* list of magnetic parameters 290 * *sld* list of parameters that have no type info 291 * *other* list of parameters that have no type info 292 293 Each parameter is in one and only one category. 294 """ 295 pars = self.parameters 296 297 par_type = { 298 'volume': [], 'orientation': [], 'magnetic': [], 'sld': [], 'other': [], 299 } 300 for p in self.parameters: 301 par_type[p.type if p.type else 'other'].append(p) 302 par_type['1d'] = [p for p in pars if p.type not in ('orientation', 'magnetic')] 303 par_type['2d'] = [p for p in pars if p.type != 'magnetic'] 304 par_type['pd'] = [p for p in pars if p.polydisperse] 305 par_type['pd_relative'] = [p for p in pars if p.relative_pd] 306 self.type = par_type 307 308 # find index of theta (or whatever variable is used for spherical 309 # normalization during polydispersity... 310 if 'theta' in par_type['2d']: 311 # TODO: may be an off-by 2 bug due to background and scale 312 # TODO: is theta always the polar coordinate? 313 self.theta_par = [k for k,p in enumerate(pars) if p.name=='theta'][0] 314 else: 315 self.theta_par = -1 316 317 @property 318 def num_pd(self): 319 """ 320 Number of distributional parameters in the model (polydispersity in 321 shape dimensions and orientational distributions). 322 """ 323 return sum(p.length for p in self.type['pd']) 324 325 @property 326 def has_2d(self): 327 return self.type['orientation'] or self.type['magnetic'] 367 self.theta_offset = -1 368 369 # number of polydisperse parameters 370 num_pd = sum(p.length for p in self.kernel_parameters if p.polydisperse) 371 # Don't use more polydisperse parameters than are available in the model 372 # Note: we can do polydispersity on arbitrary parameters, so it is not 373 # clear that this is a good idea; it does however make the poly_details 374 # code easier to write, so we will leave it in for now. 375 self.max_pd = min(num_pd, MAX_PD) 376 377 self.npars = sum(p.length for p in self.kernel_parameters) 378 379 # true if has 2D parameters 380 self.has_2d = any(p.type in ('orientation', 'magnetic') 381 for p in self.kernel_parameters) 382 383 self.pd_1d = set(p.name for p in self.call_parameters 384 if p.polydisperse and p.type not in ('orientation', 'magnetic')) 385 self.pd_2d = set(p.name for p in self.call_parameters 386 if p.polydisperse and p.type != 'magnetic') 387 388 def user_parameters(self, pars, is2d): 389 """ 390 Return the list of parameters for the given data type. 391 392 Vector parameters are expanded as in place. If multiple parameters 393 share the same vector length, then the parameters will be interleaved 394 in the result. The control parameters come first. For example, 395 if the parameter table is ordered as:: 396 397 sld_core 398 sld_shell[num_shells] 399 sld_solvent 400 thickness[num_shells] 401 num_shells 402 403 and *pars[num_shells]=2* then the returned list will be:: 404 405 num_shells 406 scale 407 background 408 sld_core 409 sld_shell1 410 thickness1 411 sld_shell2 412 thickness2 413 sld_solvent 414 415 Note that shell/thickness pairs are grouped together in the result 416 even though they were not grouped in the incoming table. The control 417 parameter is always returned first since the GUI will want to set it 418 early, and rerender the table when it is changed. 419 """ 420 control = [p for p in self.kernel_parameters if p.is_control] 421 422 # Gather entries such as name[n] into groups of the same n 423 dependent = dict((p.id, []) for p in control) 424 for p in self.kernel_parameters: 425 if p.length_control is not None: 426 dependent[p.length_control].append(p) 427 428 # Gather entries such as name[4] into groups of the same length 429 fixed = {} 430 for p in self.kernel_parameters: 431 if p.length > 1 and p.length_control is None: 432 fixed.setdefault(p.length, []).append(p) 433 434 # Using the call_parameters table, we already have expanded forms 435 # for each of the vector parameters; put them in a lookup table 436 expanded_pars = dict((p.name, p) for p in self.call_parameters) 437 438 # Gather the user parameters in order 439 result = control + self.COMMON 440 for p in self.kernel_parameters: 441 if not is2d and p.type in ('orientation', 'magnetic'): 442 pass 443 elif p.is_control: 444 pass # already added 445 elif p.length_control is not None: 446 table = dependent.get(p.length_control, []) 447 if table: 448 # look up length from incoming parameters 449 table_length = int(pars[p.length_control]) 450 del dependent[p.length_control] # first entry seen 451 for k in range(1, table_length+1): 452 for entry in table: 453 result.append(expanded_pars[entry.id+str(k)]) 454 else: 455 pass # already processed all entries 456 elif p.length > 1: 457 table = fixed.get(p.length, []) 458 if table: 459 table_length = p.length 460 del fixed[p.length] 461 for k in range(1, table_length+1): 462 for entry in table: 463 result.append(expanded_pars[entry.id+str(k)]) 464 else: 465 pass # already processed all entries 466 else: 467 result.append(p) 468 469 return result 470 328 471 329 472
Note: See TracChangeset
for help on using the changeset viewer.