Changes in / [3448301:eeb772e] in sasmodels


Ignore:
Location:
sasmodels
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/mixture.py

    rb297ba9 rb2f0e5d  
    117117            combined_pars.append(p) 
    118118    parameters = ParameterTable(combined_pars) 
     119    # Allow for the scenario in which each component has all its PD parameters 
     120    # active simultaneously.  details.make_details() will throw an error if 
     121    # too many are used from any one component. 
    119122    parameters.max_pd = sum(part.parameters.max_pd for part in parts) 
    120123 
  • sasmodels/modelinfo.py

    ra34b811 r7eb2a4d  
    7070        processed.append(parse_parameter(*p)) 
    7171    partable = ParameterTable(processed) 
    72     partable.check_angles() 
     72    partable.check_angles(strict=True) 
    7373    return partable 
    7474 
     
    446446        # properties, such as default=0.0 for structure factor backgrounds. 
    447447        self.common_parameters = [Parameter(*p) for p in COMMON_PARAMETERS] 
    448  
    449448        self.kernel_parameters = parameters 
    450449        self._set_vector_lengths() 
     
    495494        self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse) 
    496495 
     496        # Final checks 
     497        self.check_duplicates() 
     498        self.check_angles() 
     499 
    497500    def set_zero_background(self): 
    498501        """ 
     
    506509        self.defaults = self._get_defaults() 
    507510 
    508     def check_angles(self): 
     511    def check_angles(self, strict=False): 
    509512        """ 
    510513        Check that orientation angles are theta, phi and possibly psi. 
     514 
     515        *strict* should be True when checking a parameter table defined 
     516        in a model file, but False when checking from mixture models, etc., 
     517        where the parameters aren't being passed to a calculator directly. 
    511518        """ 
    512519        theta = phi = psi = -1 
     
    524531                if p.type != 'orientation': 
    525532                    raise TypeError("psi must be an orientation parameter") 
    526             elif p.type == 'orientation': 
     533            elif strict and p.type == 'orientation': 
    527534                raise TypeError("only theta, phi and psi can be orientation parameters") 
    528535        if theta >= 0 and phi >= 0: 
     
    532539            if psi >= 0 and psi != phi+1: 
    533540                raise TypeError("psi must follow phi") 
    534             if (psi >= 0 and psi != last_par) or (psi < 0 and phi != last_par): 
     541            # TODO: Why must theta/phi/psi be at the end?  Consistency only? 
     542            if strict and phi != last_par and psi != last_par: 
    535543                raise TypeError("orientation parameters must appear at the " 
    536544                                "end of the parameter table") 
    537545        elif theta >= 0 or phi >= 0 or psi >= 0: 
    538546            raise TypeError("oriented shapes must have both theta and phi and maybe psi") 
     547 
     548    def check_duplicates(self): 
     549        """ 
     550        Check for duplicate parameter names 
     551        """ 
     552        checked, dups = set(), set() 
     553        for p in self.call_parameters: 
     554            if p.id in checked: 
     555                dups.add(p.id) 
     556            else: 
     557                checked.add(p.id) 
     558        if dups: 
     559            raise TypeError("Duplicate parameters: {}" 
     560                            .format(", ".join(sorted(dups)))) 
    539561 
    540562    def __getitem__(self, key): 
  • sasmodels/product.py

    r065d77d r065d77d  
    1010To use it, first load form factor P and structure factor S, then create 
    1111*make_product_info(P, S)*. 
     12 
     13The P@S models is somewhat complicated because there are many special 
     14parameters that need to be handled in particular ways.  Much of the 
     15code is used to figure out what special parameters we have, where to 
     16find them in the P@S model inputs and how to distribute them to the underlying 
     17P and S model calculators. 
     18 
     19The parameter packet received by the P@S is a :class:`details.CallDetails` 
     20structure along with a data vector. The CallDetails structure indicates which 
     21parameters are polydisperse, the length of the distribution, and where to 
     22find it in the data vector.  The distributions are ordered from longest to 
     23shortest, with length 1 distributions filling out the distribution set.  That 
     24way the kernel calculator doesn't have to check if it needs another nesting 
     25level since it is always there.  The data vector consists of a list of target 
     26values for the parameters, followed by a concatenation of the distribution 
     27values, and then followed by a concatenation of the distribution weights. 
     28Given the combined details and data for P@S, we must decompose them in to 
     29details for P and details for S separately, which unfortunately requires 
     30intimate knowledge of the data structures and tricky code. 
     31 
     32The special parameters are: 
     33 
     34* *scale* and *background*: 
     35    First two parameters of the value list in each of P, S and P@S. 
     36    When decomposing P@S parameters, ignore *scale* and *background*, 
     37    instead using 1 and 0 for the first two slots of both P and S. 
     38    After calling P and S individually, the results are combined as 
     39    :code:`volfraction*scale*P*S + background`.  The *scale* and 
     40    *background* do not show up in the polydispersity structure so 
     41    they are easy to handle. 
     42 
     43* *volfraction*: 
     44    Always the first parameter of S, but it may also be in P. If it is in P, 
     45    then *P.volfraction* is used in the combined P@S list, and 
     46    *S.volfraction* is elided, otherwise *S.volfraction* is used. If we 
     47    are using *volfraction* from P we can treat it like all the other P 
     48    parameters when calling P, but when calling S we need to insert the 
     49    *P.volfraction* into data vector for S and assign a slot of length 1 
     50    in the distribution. Because we are using the original layout of the 
     51    distribution vectors from P@S, but copying it into private data 
     52    vectors for S and P, we are free to "borrow" a P slots to store the 
     53    missing *S.volfraction* distribution.  We use the *P.volfraction* 
     54    slot itself but any slot will work. 
     55 
     56    For hollow shapes, *volfraction* represents the volume fraction of 
     57    material but S needs the volume fraction enclosed by the shape. The 
     58    answer is to scale the user specified volume fraction by the form:shell 
     59    ratio computed from the average form volume and average shell volume 
     60    returned from P. Use the original *volfraction* divided by *shell_volume* 
     61    to compute the number density, and scale P@S by that to get absolute 
     62    scaling on the final *I(q)*. The *scale* for P@S should therefore usually 
     63    be one. 
     64 
     65* *radius_effective*: 
     66    Always the second parameter of S and always part of P@S, but never in P. 
     67    The value may be calculated using *P.radius_effective()* or it 
     68    may be set to the *radius_effective* value in P@S, depending on 
     69    *radius_effective_mode*.  If part of S, the value may be polydisperse. 
     70    If calculated by P, then it will be the weighted average of effective 
     71    radii computed for the polydisperse shape parameters. 
     72 
     73* *structure_factor_mode* 
     74    If P@S supports beta approximation (i.e., if it has the *Fq* function that 
     75    returns <FF*> and <F><F*>), then *structure_factor_mode* will be added 
     76    to the P@S parameters right after the S parameters.  This mode may be 0 
     77    for the monodisperse approximation or 1 for the beta approximation.  We 
     78    will add more values here as we implemented more complicated operations, 
     79    but for now P and S must be computed separately.  If beta, then we 
     80    return *I = scale volfrac/volume ( <FF> + <F>^2 (S-1)) + background*. 
     81    If not beta then return *I = scale/volume P S + background* .  In both 
     82    cases, return the appropriate immediate values. 
     83 
     84* *radius_effective_mode* 
     85    If P defines the *radius_effective* function (and therefore 
     86    *P.info.radius_effective_modes* is a list of effective radius modes), 
     87    then *radius_effective_mode* will be the final parameter in P@S.  Mode 
     88    will be zero if *radius_effective* is defined by the user using the S 
     89    parameter; any other value and the *radius_effective* parameter will be 
     90    filled in from the value computed in P.  In the latter case, the 
     91    polydispersity information for *S.radius_effective* will need to be 
     92    suppressed, with pd length set to 1, the first value set to the 
     93    effective radius and the first weight set to 1.  Do this after composing 
     94    the S data vector so the inputs are left untouched. 
     95 
     96* *regular parameters* 
     97    The regular P parameters form a block of length *P.info.npars* at the 
     98    start of the data vector (after scale and background).  These will be 
     99    followed by *S.effective_radius*, and *S.volfraction* (if *P.volfraction* 
     100    is absent), and then the regular S parameters.  The P and S blocks can 
     101    be copied as a group into the respective P and S data vectors. 
     102    We can copy the distribution value and weight vectors untouched to both 
     103    the P and S data vectors since they are referenced by offset and length. 
     104    We can update the radius_effective slots in the P data vector with 
     105    *P.radius_effective()* if needed. 
     106 
     107* *magnetic parameters* 
     108    For each P parameter that is an SLD there will be a set of three magnetic 
     109    parameters tacked on to P@S after the regular P and S and after the 
     110    special *structure_factor_mode* and *radius_effective_mode*.  These 
     111    can be copied as a group after the regular P parameters.  There won't 
     112    be any magnetic S parameters. 
     113 
    12114""" 
    13115from __future__ import print_function, division 
     
    84186    if not s_info.parameters.magnetism_index == []: 
    85187        raise TypeError("S should not have SLD parameters") 
     188    if RADIUS_ID in p_info.parameters: 
     189        raise TypeError("P should not have {}".format(RADIUS_ID)) 
    86190    p_id, p_name, p_pars = p_info.id, p_info.name, p_info.parameters 
    87191    s_id, s_name, s_pars = s_info.id, s_info.name, s_info.parameters 
    88  
    89     # Create list of parameters for the combined model.  If there 
    90     # are any names in P that overlap with those in S, modify the name in S 
    91     # to distinguish it. 
     192    p_has_volfrac = VOLFRAC_ID in p_info.parameters 
     193 
     194    # Create list of parameters for the combined model.  If a name in 
     195    # S overlaps a name in P, tag the S parameter name to distinguish it. 
     196    # If the tagged name also collides it will be caught by the parameter 
     197    # table builder.  Similarly if any special names are abused.  Need the 
     198    # pairs to create the translation table for random model generation. 
    92199    p_set = set(p.id for p in p_pars.kernel_parameters) 
    93     s_list = [(_tag_parameter(par) if par.id in p_set else par) 
    94               for par in s_pars.kernel_parameters] 
    95     # Check if still a collision after renaming.  This could happen if for 
    96     # example S has volfrac and P has both volfrac and volfrac_S. 
    97     if any(p.id in p_set for p in s_list): 
    98         raise TypeError("name collision: P has P.name and P.name_S while S has S.name") 
    99  
    100     # make sure effective radius is not a polydisperse parameter in product 
    101     s_list[0] = copy(s_list[0]) 
    102     s_list[0].polydisperse = False 
    103  
    104     translate_name = dict((old.id, new.id) for old, new 
    105                           in zip(s_pars.kernel_parameters, s_list)) 
     200    s_pairs = [(par, (_tag_parameter(par) if par.id in p_set else par)) 
     201               for par in s_pars.kernel_parameters 
     202               # Note: exclude volfraction from s_list if volfraction in p 
     203               if par.id != VOLFRAC_ID or not p_has_volfrac] 
     204    s_list = [pair[0] for pair in s_pairs] 
     205 
     206    # Build combined parameter table 
    106207    combined_pars = p_pars.kernel_parameters + s_list + make_extra_pars(p_info) 
    107208    parameters = ParameterTable(combined_pars) 
    108     parameters.max_pd = p_pars.max_pd + s_pars.max_pd 
     209    # Allow for the scenario in which each component has all its PD parameters 
     210    # active simultaneously.  details.make_details() will throw an error if 
     211    # too many are used from any one component. 
     212    parameters.Pumax_pd = p_pars.max_pd + s_pars.max_pd 
     213 
     214    # TODO: does user-defined polydisperse S.radius_effective make sense? 
     215    # make sure effective radius is not a polydisperse parameter in product 
     216    #s_list[0] = copy(s_list[0]) 
     217    #s_list[0].polydisperse = False 
     218 
     219    s_translate = {old.id: new.id for old, new in s_pairs} 
    109220    def random(): 
    110221        """Random set of model parameters for product model""" 
    111222        combined_pars = p_info.random() 
    112         s_names = set(par.id for par in s_pars.kernel_parameters) 
    113         combined_pars.update((translate_name[k], v) 
     223        combined_pars.update((s_translate[k], v) 
    114224                             for k, v in s_info.random().items() 
    115                              if k in s_names) 
     225                             if k in s_translate) 
    116226        return combined_pars 
    117227 
     
    173283 
    174284def _intermediates( 
    175         F1,               # type: np.ndarray 
    176         F2,               # type: np.ndarray 
     285        F,                # type: np.ndarray 
     286        Fsq,              # type: np.ndarray 
    177287        S,                # type: np.ndarray 
    178288        scale,            # type: float 
     
    190300        # TODO: 2. consider implications if there are intermediate results in P(Q) 
    191301        parts = OrderedDict(( 
    192             ("P(Q)", scale*F2), 
     302            ("P(Q)", scale*Fsq), 
    193303            ("S(Q)", S), 
    194             ("beta(Q)", F1**2 / F2), 
    195             ("S_eff(Q)", 1 + (F1**2 / F2)*(S-1)), 
     304            ("beta(Q)", F**2 / Fsq), 
     305            ("S_eff(Q)", 1 + (F**2 / Fsq)*(S-1)), 
    196306            ("effective_radius", radius_effective), 
    197307            ("radius_effective", radius_effective), 
    198             # ("I(Q)", scale*(F2 + (F1**2)*(S-1)) + bg), 
     308            # ("I(Q)", scale*(Fsq + (F**2)*(S-1)) + bg), 
    199309        )) 
    200310    else: 
    201311        parts = OrderedDict(( 
    202             ("P(Q)", scale*F2), 
     312            ("P(Q)", scale*Fsq), 
    203313            ("S(Q)", S), 
    204314            ("effective_radius", radius_effective), 
     
    264374        self.results = []  # type: List[np.ndarray] 
    265375 
     376        # Find index of volfraction parameter in parameter list 
     377        for k, p in enumerate(model_info.parameters.call_parameters): 
     378            if p.id == VOLFRAC_ID: 
     379                self._volfrac_index = k 
     380                break 
     381        else: 
     382            raise RuntimeError("no %s parameter in %s"%(VOLFRAC_ID, self)) 
     383 
     384        p_info, s_info = self.info.composition[1] 
     385        p_npars = p_info.parameters.npars 
     386        s_npars = s_info.parameters.npars 
     387 
     388        have_beta_mode = p_info.have_Fq 
     389        have_er_mode = p_info.radius_effective_modes is not None 
     390        volfrac_in_p = self._volfrac_index < p_npars + 2  # scale & background 
     391 
     392        # Slices into the details length/offset structure for P@S. 
     393        # Made complicated by the possibly missing volfraction in S. 
     394        self._p_detail_slice = slice(0, p_npars) 
     395        self._s_detail_slice = slice(p_npars, p_npars+s_npars-volfrac_in_p) 
     396        self._volfrac_in_p = volfrac_in_p 
     397 
     398        # P block from data vector, without scale and background 
     399        first_p = 2 
     400        last_p = p_npars + 2 
     401        self._p_value_slice = slice(first_p, last_p) 
     402 
     403        # radius_effective is the first parameter in S from the data vector. 
     404        self._er_index = last_p 
     405 
     406        # S block from data vector, without scale, background, volfrac or er. 
     407        first_s = last_p + 2 - volfrac_in_p 
     408        last_s = first_s + s_npars - 2 
     409        self._s_value_slice = slice(first_s, last_s) 
     410 
     411        # S distribution block in S data vector starts after all S values 
     412        self._s_dist_slice = slice(2 + s_npars, None) 
     413 
     414        # structure_factor_mode is the first parameter after P and S.  Skip 
     415        # 2 for scale and background, and subtract 1 in case there is no 
     416        # volfraction in S. 
     417        self._beta_mode_index = last_s if have_beta_mode else 0 
     418 
     419        # radius_effective_mode is the second parameter after P and S 
     420        # unless structure_factor_mode isn't available, in which case it 
     421        # is first. 
     422        self._er_mode_index = last_s + have_beta_mode if have_er_mode else 0 
     423 
     424        # Magnetic parameters are after everything else.  If they exist, 
     425        # they will only be for form factor P, not structure factor S. 
     426        first_mag = last_s + have_beta_mode + have_er_mode 
     427        mag_pars = 3*p_info.parameters.nmagnetic 
     428        last_mag = first_mag + (mag_pars + 3 if mag_pars else 0) 
     429        self._magentic_slice = slice(first_mag, last_mag) 
     430 
    266431    def Iq(self, call_details, values, cutoff, magnetic): 
    267432        # type: (CallDetails, np.ndarray, float, bool) -> np.ndarray 
    268  
    269433        p_info, s_info = self.info.composition[1] 
    270         p_npars = p_info.parameters.npars 
    271         p_length = call_details.length[:p_npars] 
    272         p_offset = call_details.offset[:p_npars] 
    273         s_npars = s_info.parameters.npars 
    274         s_length = call_details.length[p_npars:p_npars+s_npars] 
    275         s_offset = call_details.offset[p_npars:p_npars+s_npars] 
    276  
    277         # Beta mode parameter is the first parameter after P and S parameters 
    278         have_beta_mode = p_info.have_Fq 
    279         beta_mode_offset = 2+p_npars+s_npars 
    280         beta_mode = (values[beta_mode_offset] > 0) if have_beta_mode else False 
    281         if beta_mode and self.p_kernel.dim == '2d': 
    282             raise NotImplementedError("beta not yet supported for 2D") 
    283  
    284         # R_eff type parameter is the second parameter after P and S parameters 
    285         # unless the model doesn't support beta mode, in which case it is first 
    286         have_radius_type = p_info.radius_effective_modes is not None 
    287         #print(p_npars,s_npars) 
    288         radius_type_offset = 2+p_npars+s_npars + (1 if have_beta_mode else 0) 
    289         #print(values[radius_type_offset]) 
    290         radius_type = int(values[radius_type_offset]) if have_radius_type else 0 
    291  
    292         # Retrieve the volume fraction, which is the second of the 
    293         # 'S' parameters in the parameter list, or 2+np in 0-origin, 
    294         # as well as the scale and background. 
    295         volfrac = values[3+p_npars] 
     434 
     435        # Retrieve values from the data vector 
    296436        scale, background = values[0], values[1] 
    297  
    298         # if there are magnetic parameters, they will only be on the 
    299         # form factor P, not the structure factor S. 
    300         nmagnetic = len(self.info.parameters.magnetism_index) 
    301         if nmagnetic: 
    302             spin_index = self.info.parameters.npars + 2 
    303             magnetism = values[spin_index: spin_index+3+3*nmagnetic] 
    304         else: 
    305             magnetism = [] 
     437        volfrac = values[self._volfrac_index] 
     438        er_mode = (int(values[self._er_mode_index]) 
     439                   if self._er_mode_index > 0 else 0) 
     440        beta_mode = (values[self._beta_mode_index] > 0 
     441                     if self._beta_mode_index > 0 else False) 
     442 
    306443        nvalues = self.info.parameters.nvalues 
    307444        nweights = call_details.num_weights 
    308445        weights = values[nvalues:nvalues + 2*nweights] 
    309446 
     447        # Can't do 2d and beta_mode just yet 
     448        if beta_mode and self.p_kernel.dim == '2d': 
     449            raise NotImplementedError("beta not yet supported for 2D") 
     450 
    310451        # Construct the calling parameters for P. 
     452        p_length = call_details.length[self._p_detail_slice] 
     453        p_offset = call_details.offset[self._p_detail_slice] 
    311454        p_details = make_details(p_info, p_length, p_offset, nweights) 
    312455        p_values = [ 
    313456            [1., 0.], # scale=1, background=0, 
    314             values[2:2+p_npars], 
    315             magnetism, 
     457            values[self._p_value_slice], 
     458            values[self._magentic_slice], 
    316459            weights] 
    317460        spacer = (32 - sum(len(v) for v in p_values)%32)%32 
     
    319462        p_values = np.hstack(p_values).astype(self.p_kernel.dtype) 
    320463 
     464        # Call the form factor kernel to compute <F> and <F^2>. 
     465        # If the model doesn't support Fq the returned <F> will be None. 
     466        F, Fsq, radius_effective, shell_volume, volume_ratio \ 
     467            = self.p_kernel.Fq(p_details, p_values, cutoff, magnetic, er_mode) 
     468 
     469        # TODO: async call to the GPU 
     470 
    321471        # Construct the calling parameters for S. 
    322         if radius_type > 0: 
    323             # If R_eff comes from form factor, make sure it is monodisperse. 
    324             # weight is set to 1 later, after the value array is created 
     472        s_length = call_details.length[self._s_detail_slice] 
     473        s_offset = call_details.offset[self._s_detail_slice] 
     474        if self._volfrac_in_p: 
     475            # Volfrac is in P and missing from S so insert a slot for it.  Say 
     476            # the distribution is length 1 and use the slot for volfraction 
     477            # from the P distribution. 
     478            s_length = np.insert(s_length, 1, 1) 
     479            s_offset = np.insert(s_offset, 1, p_offset[self._volfrac_index - 2]) 
     480        if er_mode > 0: 
     481            # If effective_radius comes from P, make sure it is monodisperse. 
     482            # Weight is set to 1 later, after the value array is created 
    325483            s_length[0] = 1 
    326484        s_details = make_details(s_info, s_length, s_offset, nweights) 
    327485        s_values = [ 
    328             [1., 0.], # scale=1, background=0, 
    329             values[2+p_npars:2+p_npars+s_npars], 
     486            [1., # scale=1 
     487             0., # background=0, 
     488             values[self._er_index], # S.radius_effective; may be replaced by P 
     489             0.], # volfraction; will be replaced by volfrac * volume_ratio 
     490            # followed by S parameters after effective_radius and volfraction 
     491            values[self._s_value_slice], 
    330492            weights, 
    331493        ] 
     
    334496        s_values = np.hstack(s_values).astype(self.s_kernel.dtype) 
    335497 
    336         # Call the form factor kernel to compute <F> and <F^2>. 
    337         # If the model doesn't support Fq the returned <F> will be None. 
    338         F1, F2, radius_effective, shell_volume, volume_ratio = self.p_kernel.Fq( 
    339             p_details, p_values, cutoff, magnetic, radius_type) 
    340  
    341         # Call the structure factor kernel to compute S. 
    342498        # Plug R_eff from the form factor into structure factor parameters 
    343499        # and scale volume fraction by form:shell volume ratio. These changes 
     
    347503        #print("R_eff=%d:%g, volfrac=%g, volume ratio=%g" 
    348504        #      % (radius_type, radius_effective, volfrac, volume_ratio)) 
    349         if radius_type > 0: 
     505        s_dist = s_values[self._s_dist_slice] 
     506        if er_mode > 0: 
    350507            # set the value to the model R_eff and set the weight to 1 
    351             s_values[2] = s_values[2+s_npars+s_offset[0]] = radius_effective 
    352             s_values[2+s_npars+s_offset[0]+nweights] = 1.0 
    353         s_values[3] = s_values[2+s_npars+s_offset[1]] = volfrac*volume_ratio 
     508            s_values[2] = s_dist[s_offset[0]] = radius_effective 
     509            s_dist[s_offset[0]+nweights] = 1.0 
     510        s_values[3] = s_dist[s_offset[1]] = volfrac*volume_ratio 
     511        s_dist[s_offset[1]+nweights] = 1.0 
     512 
     513        # Call the structure factor kernel to compute S. 
    354514        S = self.s_kernel.Iq(s_details, s_values, cutoff, False) 
     515        #print("P", Fsq[:10]) 
     516        #print("S", S[:10]) 
     517        #print(radius_effective, volfrac*volume_ratio) 
     518 
     519        # Combine form factor and structure factor 
     520        #print("beta", beta_mode, F, Fsq, S) 
     521        PS = Fsq + F**2*(S-1) if beta_mode else Fsq*S 
    355522 
    356523        # Determine overall scale factor. Hollow shapes are weighted by 
    357         # shell_volume, so that is needed for volume normalization.  For 
    358         # solid shapes we can use shell_volume as well since it is equal 
    359         # to form volume. 
    360         combined_scale = scale*volfrac/shell_volume 
    361  
    362         # Combine form factor and structure factor 
    363         #print("beta", beta_mode, F1, F2, S) 
    364         PS = F2 + F1**2*(S-1) if beta_mode else F2*S 
    365         final_result = combined_scale*PS + background 
     524        # shell_volume, so that is needed for number density estimation. 
     525        # For solid shapes we can use shell_volume as well since it is 
     526        # equal to form volume.  If P already has a volfraction parameter, 
     527        # then assume that it is already on absolute scale, and don't 
     528        # include volfrac in the combined_scale. 
     529        combined_scale = scale*(volfrac if not self._volfrac_in_p else 1.0) 
     530        final_result = combined_scale/shell_volume*PS + background 
    366531 
    367532        # Capture intermediate values so user can see them.  These are 
     
    375540        # the results directly rather than through a lazy evaluator. 
    376541        self.results = lambda: _intermediates( 
    377             F1, F2, S, combined_scale, radius_effective, beta_mode) 
     542            F, Fsq, S, combined_scale, radius_effective, beta_mode) 
    378543 
    379544        return final_result 
Note: See TracChangeset for help on using the changeset viewer.