Changeset 6d6508e in sasmodels for sasmodels/generate.py
- Timestamp:
- Apr 7, 2016 6:57:33 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:
- d2fc9a4
- Parents:
- 3707eee
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
sasmodels/generate.py
r6e7ff6d r6d6508e 155 155 #__all__ = ["model_info", "make_doc", "make_source", "convert_type"] 156 156 157 from os.path import abspath, dirname, join as joinpath, exists, basename, \ 158 splitext, getmtime 157 from os.path import abspath, dirname, join as joinpath, exists, getmtime 159 158 import re 160 159 import string … … 163 162 import numpy as np 164 163 165 from .modelinfo import ModelInfo, Parameter, make_parameter_table, set_demo164 from .modelinfo import Parameter 166 165 from .custom import load_custom_kernel_module 167 166 … … 273 272 Return a list of the sources file paths for the module. 274 273 """ 275 search_path = [dirname(model_info ['filename']),274 search_path = [dirname(model_info.filename), 276 275 abspath(joinpath(dirname(__file__), 'models'))] 277 return [_search(search_path, f) for f in model_info ['source']]276 return [_search(search_path, f) for f in model_info.source] 278 277 279 278 def timestamp(model_info): … … 284 283 source_files = (model_sources(model_info) 285 284 + model_templates() 286 + [model_info ['filename']])285 + [model_info.filename]) 287 286 newest = max(getmtime(f) for f in source_files) 288 287 return newest … … 334 333 Name of the exported kernel symbol. 335 334 """ 336 return model_info ['name']+ "_" + ("Iqxy" if is_2d else "Iq")335 return model_info.name + "_" + ("Iqxy" if is_2d else "Iq") 337 336 338 337 … … 420 419 Uses source files found in the given search path. 421 420 """ 422 if callable(model_info ['Iq']):421 if callable(model_info.Iq): 423 422 return None 424 423 … … 432 431 # for computing volume even if we allow non-disperse volume parameters. 433 432 434 partable = model_info ['parameters']433 partable = model_info.parameters 435 434 436 435 # Identify parameters for Iq, Iqxy, Iq_magnetic and form_volume. … … 448 447 q, qx, qy = [Parameter(name=v) for v in ('q', 'qx', 'qy')] 449 448 # Generate form_volume function, etc. from body only 450 if model_info ['form_volume']is not None:449 if model_info.form_volume is not None: 451 450 pars = partable.form_volume_parameters 452 source.append(_gen_fn('form_volume', pars, model_info ['form_volume']))453 if model_info ['Iq']is not None:451 source.append(_gen_fn('form_volume', pars, model_info.form_volume)) 452 if model_info.Iq is not None: 454 453 pars = [q] + partable.iq_parameters 455 source.append(_gen_fn('Iq', pars, model_info ['Iq']))456 if model_info ['Iqxy']is not None:454 source.append(_gen_fn('Iq', pars, model_info.Iq)) 455 if model_info.Iqxy is not None: 457 456 pars = [qx, qy] + partable.iqxy_parameters 458 source.append(_gen_fn('Iqxy', pars, model_info ['Iqxy']))457 source.append(_gen_fn('Iqxy', pars, model_info.Iqxy)) 459 458 460 459 # Define the parameter table … … 494 493 495 494 # define the Iq kernel 496 source.append("#define KERNEL_NAME %s_Iq"%model_info ['name'])495 source.append("#define KERNEL_NAME %s_Iq"%model_info.name) 497 496 source.append(call_iq) 498 497 source.append(kernel_code) … … 501 500 502 501 # define the Iqxy kernel from the same source with different #defines 503 source.append("#define KERNEL_NAME %s_Iqxy"%model_info ['name'])502 source.append("#define KERNEL_NAME %s_Iqxy"%model_info.name) 504 503 source.append(call_iqxy) 505 504 source.append(kernel_code) … … 508 507 509 508 return '\n'.join(source) 510 511 class CoordinationDetails(object):512 def __init__(self, model_info):513 parameters = model_info['parameters']514 max_pd = parameters.max_pd515 npars = parameters.npars516 par_offset = 4*max_pd517 self.details = np.zeros(par_offset + 3*npars + 4, 'i4')518 519 # generate views on different parts of the array520 self._pd_par = self.details[0*max_pd:1*max_pd]521 self._pd_length = self.details[1*max_pd:2*max_pd]522 self._pd_offset = self.details[2*max_pd:3*max_pd]523 self._pd_stride = self.details[3*max_pd:4*max_pd]524 self._par_offset = self.details[par_offset+0*npars:par_offset+1*npars]525 self._par_coord = self.details[par_offset+1*npars:par_offset+2*npars]526 self._pd_coord = self.details[par_offset+2*npars:par_offset+3*npars]527 528 # theta_par is fixed529 self.details[-1] = parameters.theta_offset530 531 @property532 def ctypes(self): return self.details.ctypes533 534 @property535 def pd_par(self): return self._pd_par536 537 @property538 def pd_length(self): return self._pd_length539 540 @property541 def pd_offset(self): return self._pd_offset542 543 @property544 def pd_stride(self): return self._pd_stride545 546 @property547 def pd_coord(self): return self._pd_coord548 549 @property550 def par_coord(self): return self._par_coord551 552 @property553 def par_offset(self): return self._par_offset554 555 @property556 def num_active(self): return self.details[-4]557 @num_active.setter558 def num_active(self, v): self.details[-4] = v559 560 @property561 def total_pd(self): return self.details[-3]562 @total_pd.setter563 def total_pd(self, v): self.details[-3] = v564 565 @property566 def num_coord(self): return self.details[-2]567 @num_coord.setter568 def num_coord(self, v): self.details[-2] = v569 570 @property571 def theta_par(self): return self.details[-1]572 573 def show(self):574 print("total_pd", self.total_pd)575 print("num_active", self.num_active)576 print("pd_par", self.pd_par)577 print("pd_length", self.pd_length)578 print("pd_offset", self.pd_offset)579 print("pd_stride", self.pd_stride)580 print("par_offsets", self.par_offset)581 print("num_coord", self.num_coord)582 print("par_coord", self.par_coord)583 print("pd_coord", self.pd_coord)584 print("theta par", self.details[-1])585 586 def mono_details(model_info):587 details = CoordinationDetails(model_info)588 # The zero defaults for monodisperse systems are mostly fine589 details.par_offset[:] = np.arange(2, len(details.par_offset)+2)590 return details591 592 def poly_details(model_info, weights):593 #print("weights",weights)594 weights = weights[2:] # Skip scale and background595 596 # Decreasing list of polydispersity lengths597 # Note: the reversing view, x[::-1], does not require a copy598 pd_length = np.array([len(w) for w in weights])599 num_active = np.sum(pd_length>1)600 if num_active > model_info['parameters'].max_pd:601 raise ValueError("Too many polydisperse parameters")602 603 pd_offset = np.cumsum(np.hstack((0, pd_length)))604 idx = np.argsort(pd_length)[::-1][:num_active]605 par_length = np.array([max(len(w),1) for w in weights])606 pd_stride = np.cumprod(np.hstack((1, par_length[idx])))607 par_offsets = np.cumsum(np.hstack((2, par_length)))608 609 details = CoordinationDetails(model_info)610 details.pd_par[:num_active] = idx611 details.pd_length[:num_active] = pd_length[idx]612 details.pd_offset[:num_active] = pd_offset[idx]613 details.pd_stride[:num_active] = pd_stride[:-1]614 details.par_offset[:] = par_offsets[:-1]615 details.total_pd = pd_stride[-1]616 details.num_active = num_active617 # Without constraints coordinated parameters are just the pd parameters618 details.par_coord[:num_active] = idx619 details.pd_coord[:num_active] = 2**np.arange(num_active)620 details.num_coord = num_active621 #details.show()622 return details623 624 def constrained_poly_details(model_info, weights, constraints):625 # Need to find the independently varying pars and sort them626 # Need to build a coordination list for the dependent variables627 # Need to generate a constraints function which takes values628 # and weights, returning par blocks629 raise NotImplementedError("Can't handle constraints yet")630 631 632 def create_vector_Iq(model_info):633 """634 Define Iq as a vector function if it exists.635 """636 Iq = model_info['Iq']637 if callable(Iq) and not getattr(Iq, 'vectorized', False):638 def vector_Iq(q, *args):639 """640 Vectorized 1D kernel.641 """642 return np.array([Iq(qi, *args) for qi in q])643 model_info['Iq'] = vector_Iq644 645 def create_vector_Iqxy(model_info):646 """647 Define Iqxy as a vector function if it exists, or default it from Iq().648 """649 Iq, Iqxy = model_info['Iq'], model_info['Iqxy']650 if callable(Iqxy) and not getattr(Iqxy, 'vectorized', False):651 def vector_Iqxy(qx, qy, *args):652 """653 Vectorized 2D kernel.654 """655 return np.array([Iqxy(qxi, qyi, *args) for qxi, qyi in zip(qx, qy)])656 model_info['Iqxy'] = vector_Iqxy657 elif callable(Iq):658 # Iq is vectorized because create_vector_Iq was already called.659 def default_Iqxy(qx, qy, *args):660 """661 Default 2D kernel.662 """663 return Iq(np.sqrt(qx**2 + qy**2), *args)664 model_info['Iqxy'] = default_Iqxy665 666 def create_default_functions(model_info):667 """668 Autogenerate missing functions, such as Iqxy from Iq.669 670 This only works for Iqxy when Iq is written in python. :func:`make_source`671 performs a similar role for Iq written in C. This also vectorizes672 any functions that are not already marked as vectorized.673 """674 create_vector_Iq(model_info)675 create_vector_Iqxy(model_info) # call create_vector_Iq() first676 509 677 510 def load_kernel_module(model_name): … … 685 518 686 519 687 def make_model_info(kernel_module):688 """689 Interpret the model definition file, categorizing the parameters.690 691 The module can be loaded with a normal python import statement if you692 know which module you need, or with __import__('sasmodels.model.'+name)693 if the name is in a string.694 695 The *model_info* structure contains the following fields:696 697 * *id* is the id of the kernel698 * *name* is the display name of the kernel699 * *filename* is the full path to the module defining the file (if any)700 * *title* is a short description of the kernel701 * *description* is a long description of the kernel (this doesn't seem702 very useful since the Help button on the model page brings you directly703 to the documentation page)704 * *docs* is the docstring from the module. Use :func:`make_doc` to705 * *category* specifies the model location in the docs706 * *parameters* is the model parameter table707 * *single* is True if the model allows single precision708 * *structure_factor* is True if the model is useable in a product709 * *variant_info* contains the information required to select between710 model variants (e.g., the list of cases) or is None if there are no711 model variants712 * *par_type* categorizes the model parameters. See713 :func:`categorize_parameters` for details.714 * *demo* contains the *{parameter: value}* map used in compare (and maybe715 for the demo plot, if plots aren't set up to use the default values).716 If *demo* is not given in the file, then the default values will be used.717 * *tests* is a set of tests that must pass718 * *source* is the list of library files to include in the C model build719 * *Iq*, *Iqxy*, *form_volume*, *ER*, *VR* and *sesans* are python functions720 implementing the kernel for the module, or None if they are not721 defined in python722 * *composition* is None if the model is independent, otherwise it is a723 tuple with composition type ('product' or 'mixture') and a list of724 *model_info* blocks for the composition objects. This allows us to725 build complete product and mixture models from just the info.726 """727 # TODO: maybe turn model_info into a class ModelDefinition728 #print("make parameter table", kernel_module.parameters)729 parameters = make_parameter_table(kernel_module.parameters)730 filename = abspath(kernel_module.__file__)731 kernel_id = splitext(basename(filename))[0]732 name = getattr(kernel_module, 'name', None)733 if name is None:734 name = " ".join(w.capitalize() for w in kernel_id.split('_'))735 model_info = dict(736 id=kernel_id, # string used to load the kernel737 filename=abspath(kernel_module.__file__),738 name=name,739 title=getattr(kernel_module, 'title', name+" model"),740 description=getattr(kernel_module, 'description', 'no description'),741 parameters=parameters,742 composition=None,743 docs=kernel_module.__doc__,744 category=getattr(kernel_module, 'category', None),745 single=getattr(kernel_module, 'single', True),746 structure_factor=getattr(kernel_module, 'structure_factor', False),747 profile_axes=getattr(kernel_module, 'profile_axes', ['x','y']),748 variant_info=getattr(kernel_module, 'invariant_info', None),749 demo=getattr(kernel_module, 'demo', None),750 source=getattr(kernel_module, 'source', []),751 tests=getattr(kernel_module, 'tests', []),752 )753 set_demo(model_info, getattr(kernel_module, 'demo', None))754 # Check for optional functions755 functions = "ER VR form_volume Iq Iqxy profile sesans".split()756 model_info.update((k, getattr(kernel_module, k, None)) for k in functions)757 create_default_functions(model_info)758 # Precalculate the monodisperse parameters759 # TODO: make this a lazy evaluator760 # make_model_info is called for every model on sasview startup761 model_info['mono_details'] = mono_details(model_info)762 return model_info763 520 764 521 section_marker = re.compile(r'\A(?P<first>[%s])(?P=first)*\Z' … … 801 558 Iq_units = "The returned value is scaled to units of |cm^-1| |sr^-1|, absolute scale." 802 559 Sq_units = "The returned value is a dimensionless structure factor, $S(q)$." 803 docs = convert_section_titles_to_boldface(model_info ['docs'])804 subst = dict(id=model_info ['id'].replace('_', '-'),805 name=model_info ['name'],806 title=model_info ['title'],807 parameters=make_partable(model_info ['parameters']),808 returns=Sq_units if model_info ['structure_factor']else Iq_units,560 docs = convert_section_titles_to_boldface(model_info.docs) 561 subst = dict(id=model_info.id.replace('_', '-'), 562 name=model_info.name, 563 title=model_info.title, 564 parameters=make_partable(model_info.parameters), 565 returns=Sq_units if model_info.structure_factor else Iq_units, 809 566 docs=docs) 810 567 return DOC_HEADER % subst … … 815 572 Show how long it takes to process a model. 816 573 """ 574 import datetime 575 from .modelinfo import make_model_info 817 576 from .models import cylinder 818 import datetime 577 819 578 tic = datetime.datetime.now() 820 579 make_source(make_model_info(cylinder)) … … 827 586 """ 828 587 import sys 588 from .modelinfo import make_model_info 589 829 590 if len(sys.argv) <= 1: 830 591 print("usage: python -m sasmodels.generate modelname")
Note: See TracChangeset
for help on using the changeset viewer.