Changeset d19962c in sasmodels for sasmodels/modelinfo.py


Ignore:
Timestamp:
Mar 27, 2016 6:57:03 PM (8 years ago)
Author:
Paul Kienzle <pkienzle@…>
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
Message:

working vector parameter example using dll engine

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/modelinfo.py

    rc499331 rd19962c  
    44# TODO: turn ModelInfo into a proper class 
    55ModelInfo = dict 
     6 
     7MAX_PD = 4 
    68 
    79COMMON_PARAMETERS = [ 
     
    153155    *length* is the length of the field if it is a vector field 
    154156    *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 
    155158    *polydisperse* is true if the parameter accepts a polydispersity 
    156159    *relative_pd* is true if that polydispersity is relative 
     
    163166        self.id = name.split('[')[0].strip() 
    164167        self.name = name 
     168        self.units = units 
    165169        self.default = default 
    166170        self.limits = limits 
     
    169173        self.choices = None 
    170174 
    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 
    173176        # parameter table is available. 
    174177        self.length = 1 
    175178        self.length_control = None 
     179        self.is_control = False 
    176180 
    177181        # TODO: need better control over whether a parameter is polydisperse 
     
    216220        return "P<%s>"%self.name 
    217221 
     222 
    218223class 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    """ 
    219281    # scale and background are implicit parameters 
    220282    COMMON = [Parameter(*p) for p in COMMON_PARAMETERS] 
    221283 
    222284    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() 
    225288        self._categorize_parameters() 
    226  
    227         self._set_vector_lengths() 
    228289        self._set_defaults() 
     290        #self._name_table= dict((p.id, p) for p in parameters) 
    229291 
    230292    def _set_vector_lengths(self): 
    231293        # 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: 
    233295            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 
    235303                low, high = ref.limits 
    236304                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) 
    238307                p.length = high 
    239308 
     
    241310        # Construct default values, including vector defaults 
    242311        defaults = {} 
    243         for p in self.parameters: 
     312        for p in self.call_parameters: 
    244313            if p.length == 1: 
    245314                defaults[p.id] = p.default 
    246315            else: 
    247                 for k in range(p.length): 
    248                     defaults["%s[%d]"%(p.id, k)] = p.default 
     316                for k in range(1, p.length+1): 
     317                    defaults["%s%d"%(p.id, k)] = p.default 
    249318        self.defaults = defaults 
    250319 
     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 
    251335    def __getitem__(self, k): 
    252336        if isinstance(k, (int, slice)): 
     
    259343 
    260344    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 
    273366        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 
    328471 
    329472 
Note: See TracChangeset for help on using the changeset viewer.