Changeset 69aa451 in sasmodels for sasmodels/generate.py
- Timestamp:
- Mar 23, 2016 11:39:08 AM (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:
- e9b1663d
- Parents:
- 6fd8de0
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/generate.py
rea1f14d r69aa451 69 69 The constructor code will generate the necessary vectors for computing 70 70 them with the desired polydispersity. 71 72 The available kernel parameters are defined as a list, with each parameter73 defined as a sublist with the following elements:74 75 *name* is the name that will be used in the call to the kernel76 function and the name that will be displayed to the user. Names77 should be lower case, with words separated by underscore. If78 acronyms are used, the whole acronym should be upper case.79 80 *units* should be one of *degrees* for angles, *Ang* for lengths,81 *1e-6/Ang^2* for SLDs.82 83 *default value* will be the initial value for the model when it84 is selected, or when an initial value is not otherwise specified.85 86 *limits = [lb, ub]* are the hard limits on the parameter value, used to87 limit the polydispersity density function. In the fit, the parameter limits88 given to the fit are the limits on the central value of the parameter.89 If there is polydispersity, it will evaluate parameter values outside90 the fit limits, but not outside the hard limits specified in the model.91 If there are no limits, use +/-inf imported from numpy.92 93 *type* indicates how the parameter will be used. "volume" parameters94 will be used in all functions. "orientation" parameters will be used95 in *Iqxy* and *Imagnetic*. "magnetic* parameters will be used in96 *Imagnetic* only. If *type* is the empty string, the parameter will97 be used in all of *Iq*, *Iqxy* and *Imagnetic*. "sld" parameters98 can automatically be promoted to magnetic parameters, each of which99 will have a magnitude and a direction, which may be different from100 other sld parameters.101 102 *description* is a short description of the parameter. This will103 be displayed in the parameter table and used as a tool tip for the104 parameter value in the user interface.105 106 71 The kernel module must set variables defining the kernel meta data: 107 72 … … 216 181 from __future__ import print_function 217 182 218 # TODO: identify model files which have changed since loading and reload them. 183 #TODO: determine which functions are useful outside of generate 184 #__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 219 185 220 186 import sys … … 224 190 import string 225 191 import warnings 226 from collections import namedtuple227 192 228 193 import numpy as np 229 194 230 # TODO: promote Parameter and model_info to classes 231 PARAMETER_FIELDS = ['name', 'units', 'default', 'limits', 'type', 'description'] 232 Parameter = namedtuple('Parameter', PARAMETER_FIELDS) 233 234 #TODO: determine which functions are useful outside of generate 235 #__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 195 from .modelinfo import ModelInfo, Parameter, make_parameter_table 196 197 # TODO: identify model files which have changed since loading and reload them. 236 198 237 199 TEMPLATE_ROOT = dirname(__file__) … … 246 208 except TypeError: 247 209 F128 = None 248 249 # Scale and background, which are parameters common to every form factor250 COMMON_PARAMETERS = [251 ["scale", "", 1, [0, np.inf], "", "Source intensity"],252 ["background", "1/cm", 1e-3, [0, np.inf], "", "Source background"],253 ]254 210 255 211 # Conversion from units defined in the parameter table for each model … … 354 310 return [_search(search_path, f) for f in model_info['source']] 355 311 312 def timestamp(model_info): 313 """ 314 Return a timestamp for the model corresponding to the most recently 315 changed file or dependency. 316 """ 317 source_files = (model_sources(model_info) 318 + model_templates() 319 + [model_info['filename']]) 320 newest = max(getmtime(f) for f in source_files) 321 return newest 322 356 323 def convert_type(source, dtype): 357 324 """ … … 448 415 } 449 416 """ 450 par_decl = ', '.join( 'double ' + pfor p in pars) if pars else 'void'417 par_decl = ', '.join(p.as_argument() for p in pars) if pars else 'void' 451 418 return _FN_TEMPLATE % {'name': name, 'body': body, 'pars': par_decl} 452 419 … … 455 422 Return a list of *prefix.parameter* from parameter items. 456 423 """ 457 prefix += "." 458 return [prefix+p for p in pars] 424 return [p.as_call_reference(prefix) for p in pars] 459 425 460 426 _IQXY_PATTERN = re.compile("^((inline|static) )? *(double )? *Iqxy *([(]|$)", … … 499 465 # for computing volume even if we allow non-disperse volume parameters. 500 466 501 # kernel_iq assumes scale and background are the first parameters; 502 # they should be first for 1d and 2d parameter lists as well. 503 assert model_info['parameters'][0].name == 'scale' 504 assert model_info['parameters'][1].name == 'background' 505 506 # Identify parameter types 507 iq_parameters = model_info['par_type']['1d'][2:] 508 iqxy_parameters = model_info['par_type']['2d'][2:] 509 vol_parameters = model_info['par_type']['volume'] 467 partable = model_info['parameters'] 468 469 # Identify parameters for Iq, Iqxy, Iq_magnetic and form_volume. 470 # Note that scale and volume are not possible types. 510 471 511 472 # Load templates and user code … … 517 478 source = [kernel_header] + user_code 518 479 480 vol_parameters = partable.kernel_pars('volume') 481 iq_parameters = partable.kernel_pars('1d') 482 iqxy_parameters = partable.kernel_pars('2d') 483 484 # Make parameters for q, qx, qy so that we can use them in declarations 485 q, qx, qy = [Parameter(name=v) for v in ('q', 'qx', 'qy')] 519 486 # Generate form_volume function, etc. from body only 520 487 if model_info['form_volume'] is not None: 521 p names = [p for p in vol_parameters]522 source.append(_gen_fn('form_volume', p names, model_info['form_volume']))488 pars = vol_parameters 489 source.append(_gen_fn('form_volume', pars, model_info['form_volume'])) 523 490 if model_info['Iq'] is not None: 524 p names = ['q'] + [p for p in iq_parameters]525 source.append(_gen_fn('Iq', p names, model_info['Iq']))491 pars = [q] + iq_parameters 492 source.append(_gen_fn('Iq', pars, model_info['Iq'])) 526 493 if model_info['Iqxy'] is not None: 527 p names = ['qx', 'qy'] + [p for p in iqxy_parameters]528 source.append(_gen_fn('Iqxy', p names, model_info['Iqxy']))494 pars = [qx, qy] + iqxy_parameters 495 source.append(_gen_fn('Iqxy', pars, model_info['Iqxy'])) 529 496 530 497 # Define the parameter table 531 498 source.append("#define PARAMETER_TABLE \\") 532 source.append("\\\n ".join("double %s;"%p.name533 499 source.append("\\\n".join(p.as_definition() 500 for p in model_info['parameters'][2:])) 534 501 535 502 # Define the function calls 536 503 if vol_parameters: 537 refs = ",".join(_call_pars("v", vol_parameters))538 call_volume = "#define CALL_VOLUME(v) form_volume(%s)" %refs504 refs = _call_pars("v.", vol_parameters) 505 call_volume = "#define CALL_VOLUME(v) form_volume(%s)" % (",".join(refs)) 539 506 else: 540 507 # Model doesn't have volume. We could make the kernel run a little … … 544 511 source.append(call_volume) 545 512 546 refs = ["q[i]"] + _call_pars("v ", iq_parameters)513 refs = ["q[i]"] + _call_pars("v.", iq_parameters) 547 514 call_iq = "#define CALL_IQ(q,i,v) Iq(%s)" % (",".join(refs)) 548 515 if _have_Iqxy(user_code): 549 516 # Call 2D model 550 refs = ["q[2*i]", "q[2*i+1]"] + _call_pars("v ", iqxy_parameters)517 refs = ["q[2*i]", "q[2*i+1]"] + _call_pars("v.", iqxy_parameters) 551 518 call_iqxy = "#define CALL_IQ(q,i,v) Iqxy(%s)" % (",".join(refs)) 552 519 else: … … 559 526 # Fill in definitions for numbers of parameters 560 527 source.append("#define MAX_PD %s"%model_info['max_pd']) 561 source.append("#define NPARS %d"%(len( model_info['parameters'])-2))528 source.append("#define NPARS %d"%(len(partable.kernel_pars()))) 562 529 563 530 # TODO: allow mixed python/opencl kernels? … … 597 564 """ 598 565 par_set = {} 599 par_set['1d'] = [p.name for p in pars if p.type not in ('orientation', 'magnetic')]600 par_set['2d'] = [p.name for p in pars if p.type != 'magnetic']601 par_set['magnetic'] = [p.name for p in pars]602 par_set['pd'] = [p.name for p in pars if p.type in ('volume', 'orientation')]603 par_set['pd_relative'] = [p.name for p in pars if p.type == 'volume']604 if 'theta' in par_set['2d']:605 # find id of theta in parameter set (or whatever variable is606 # used for spherical normalization during polydispersity...607 par_set['theta_par'] = [k for k,p in enumerate(pars) if p.name=='theta'][0]608 else:609 par_set['theta_par'] = -1610 return par_set611 612 def collect_types(pars):613 """614 Build parameter categories out of the the parameter definitions.615 616 Returns a dictionary of categories.617 618 Note: these categories are subject to change, depending on the needs of619 the UI and the needs of the kernel calling function.620 621 The categories are as follows:622 623 * *volume* list of volume parameter names624 * *orientation* list of orientation parameters625 * *magnetic* list of magnetic parameters626 * *sld* list of parameters that have no type info627 * *other* list of parameters that have no type info628 629 Each parameter is in one and only one category.630 """631 par_type = {632 'volume': [], 'orientation': [], 'magnetic': [], 'sld': [], 'other': [],633 }634 for p in pars:635 par_type[p.type if p.type else 'other'].append(p.name)636 return par_type637 638 566 639 567 def process_parameters(model_info): … … 641 569 Process parameter block, precalculating parameter details. 642 570 """ 643 # convert parameters into named tuples 644 for p in model_info['parameters']: 645 if p[4] == '' and (p[0].startswith('sld') or p[0].endswith('sld')): 646 p[4] = 'sld' 647 # TODO: make sure all models explicitly label their sld parameters 648 #raise ValueError("%s.%s needs to be explicitly set to type 'sld'" %(model_info['id'], p[0])) 649 650 pars = [Parameter(*p) for p in model_info['parameters']] 651 # Fill in the derived attributes 652 par_type = collect_types(pars) 653 par_type.update(categorize_parameters(pars)) 654 model_info['parameters'] = pars 655 model_info['par_type'] = par_type 571 partable = model_info['parameters'] 656 572 if model_info.get('demo', None) is None: 657 model_info['demo'] = dict((p.name, p.default) for p in pars)658 model_info['has_2d'] = par_type['orientation'] or par_type['magnetic'] 573 model_info['demo'] = partable.defaults 574 659 575 # Don't use more polydisperse parameters than are available in the model 660 576 # Note: we can do polydispersity on arbitrary parameters, so it is not 661 577 # clear that this is a good idea; it does however make the poly_details 662 578 # code easier to write, so we will leave it in for now. 663 model_info['max_pd'] = min( len(par_type['pd']), MAX_PD)579 model_info['max_pd'] = min(partable.num_pd, MAX_PD) 664 580 665 581 def mono_details(model_info): 582 # TODO: move max_pd into ParameterTable? 666 583 max_pd = model_info['max_pd'] 667 npars = len(model_info['parameters']) - 2 668 p = 5*max_pd 669 c = p + 3*npars 670 671 details = np.zeros(c + 2, 'int32') 584 pars = model_info['parameters'].kernel_pars() 585 npars = len(pars) 586 par_offset = 5*max_pd 587 constants_offset = par_offset + 3*npars 588 589 details = np.zeros(constants_offset + 2, 'int32') 672 590 details[0*max_pd:1*max_pd] = range(max_pd) # pd_par: arbitrary order; use first 673 591 details[1*max_pd:2*max_pd] = [1]*max_pd # pd_length: only one element … … 675 593 details[3*max_pd:4*max_pd] = [1]*max_pd # pd_stride: vectors of length 1 676 594 details[4*max_pd:5*max_pd] = [0]*max_pd # pd_isvol: doens't matter if no norm 677 details[p +0*npars:p+1*npars] = range(2, npars+2) # par_offset: skip scale and background678 details[p +1*npars:p+2*npars] = [0]*npars # no coordination595 details[par_offset+0*npars:par_offset+1*npars] = range(2, npars+2) # par_offset: skip scale and background 596 details[par_offset+1*npars:par_offset+2*npars] = [0]*npars # no coordination 679 597 #details[p+npars] = 1 # par_coord[0] is coordinated with the first par? 680 details[p +2*npars:p+3*npars] = 0 # fast coord with 0681 details[c ] = 1 # fast_coord_count: one fast index682 details[c +1] = -1 # theta_par: None598 details[par_offset+2*npars:par_offset+3*npars] = 0 # fast coord with 0 599 details[constants_offset] = 1 # fast_coord_count: one fast index 600 details[constants_offset+1] = -1 # theta_par: None 683 601 return details 684 602 685 603 def poly_details(model_info, weights): 686 print("entering poly",weights)687 688 print([p.name for p in model_info['parameters']])689 pars = model_info['parameters'][2:] # skip scale and background690 604 weights = weights[2:] 605 606 # TODO: move max_pd into ParameterTable? 691 607 max_pd = model_info['max_pd'] 692 npars = len(pars) # scale and background already removed 608 pars = model_info['parameters'].kernel_pars 609 npars = len(pars) 693 610 par_offset = 5*max_pd 694 611 constants_offset = par_offset + 3*npars … … 745 662 """ 746 663 if model_info['Iq'] is not None and model_info['Iqxy'] is None: 747 if model_info['par_type']['1d'] != model_info['par_type']['2d']: 664 partable = model_info['parameters'] 665 if partable.type['1d'] != partable.type['2d']: 748 666 raise ValueError("Iqxy model is missing") 749 667 Iq = model_info['Iq'] … … 751 669 return Iq(np.sqrt(qx**2 + qy**2), **kw) 752 670 model_info['Iqxy'] = Iqxy 671 753 672 754 673 def make_model_info(kernel_module): … … 800 719 """ 801 720 # TODO: maybe turn model_info into a class ModelDefinition 802 parameters = COMMON_PARAMETERS + kernel_module.parameters 721 #print("make parameter table", kernel_module.parameters) 722 parameters = make_parameter_table(kernel_module.parameters) 803 723 filename = abspath(kernel_module.__file__) 804 724 kernel_id = splitext(basename(filename))[0]
Note: See TracChangeset
for help on using the changeset viewer.