Changes in / [eeb772e:3448301] in sasmodels


Ignore:
Location:
sasmodels
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • sasmodels/mixture.py

    rb2f0e5d rb297ba9  
    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. 
    122119    parameters.max_pd = sum(part.parameters.max_pd for part in parts) 
    123120 
  • sasmodels/modelinfo.py

    r7eb2a4d ra34b811  
    7070        processed.append(parse_parameter(*p)) 
    7171    partable = ParameterTable(processed) 
    72     partable.check_angles(strict=True) 
     72    partable.check_angles() 
    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 
    448449        self.kernel_parameters = parameters 
    449450        self._set_vector_lengths() 
     
    494495        self.pd_2d = set(p.name for p in self.call_parameters if p.polydisperse) 
    495496 
    496         # Final checks 
    497         self.check_duplicates() 
    498         self.check_angles() 
    499  
    500497    def set_zero_background(self): 
    501498        """ 
     
    509506        self.defaults = self._get_defaults() 
    510507 
    511     def check_angles(self, strict=False): 
     508    def check_angles(self): 
    512509        """ 
    513510        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. 
    518511        """ 
    519512        theta = phi = psi = -1 
     
    531524                if p.type != 'orientation': 
    532525                    raise TypeError("psi must be an orientation parameter") 
    533             elif strict and p.type == 'orientation': 
     526            elif p.type == 'orientation': 
    534527                raise TypeError("only theta, phi and psi can be orientation parameters") 
    535528        if theta >= 0 and phi >= 0: 
     
    539532            if psi >= 0 and psi != phi+1: 
    540533                raise TypeError("psi must follow phi") 
    541             # TODO: Why must theta/phi/psi be at the end?  Consistency only? 
    542             if strict and phi != last_par and psi != last_par: 
     534            if (psi >= 0 and psi != last_par) or (psi < 0 and phi != last_par): 
    543535                raise TypeError("orientation parameters must appear at the " 
    544536                                "end of the parameter table") 
    545537        elif theta >= 0 or phi >= 0 or psi >= 0: 
    546538            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)))) 
    561539 
    562540    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  
    13 The P@S models is somewhat complicated because there are many special 
    14 parameters that need to be handled in particular ways.  Much of the 
    15 code is used to figure out what special parameters we have, where to 
    16 find them in the P@S model inputs and how to distribute them to the underlying 
    17 P and S model calculators. 
    18  
    19 The parameter packet received by the P@S is a :class:`details.CallDetails` 
    20 structure along with a data vector. The CallDetails structure indicates which 
    21 parameters are polydisperse, the length of the distribution, and where to 
    22 find it in the data vector.  The distributions are ordered from longest to 
    23 shortest, with length 1 distributions filling out the distribution set.  That 
    24 way the kernel calculator doesn't have to check if it needs another nesting 
    25 level since it is always there.  The data vector consists of a list of target 
    26 values for the parameters, followed by a concatenation of the distribution 
    27 values, and then followed by a concatenation of the distribution weights. 
    28 Given the combined details and data for P@S, we must decompose them in to 
    29 details for P and details for S separately, which unfortunately requires 
    30 intimate knowledge of the data structures and tricky code. 
    31  
    32 The 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  
    11412""" 
    11513from __future__ import print_function, division 
     
    18684    if not s_info.parameters.magnetism_index == []: 
    18785        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)) 
    19086    p_id, p_name, p_pars = p_info.id, p_info.name, p_info.parameters 
    19187    s_id, s_name, s_pars = s_info.id, s_info.name, s_info.parameters 
    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. 
     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. 
    19992    p_set = set(p.id for p in p_pars.kernel_parameters) 
    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 
     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)) 
    207106    combined_pars = p_pars.kernel_parameters + s_list + make_extra_pars(p_info) 
    208107    parameters = ParameterTable(combined_pars) 
    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} 
     108    parameters.max_pd = p_pars.max_pd + s_pars.max_pd 
    220109    def random(): 
    221110        """Random set of model parameters for product model""" 
    222111        combined_pars = p_info.random() 
    223         combined_pars.update((s_translate[k], v) 
     112        s_names = set(par.id for par in s_pars.kernel_parameters) 
     113        combined_pars.update((translate_name[k], v) 
    224114                             for k, v in s_info.random().items() 
    225                              if k in s_translate) 
     115                             if k in s_names) 
    226116        return combined_pars 
    227117 
     
    283173 
    284174def _intermediates( 
    285         F,                # type: np.ndarray 
    286         Fsq,              # type: np.ndarray 
     175        F1,               # type: np.ndarray 
     176        F2,               # type: np.ndarray 
    287177        S,                # type: np.ndarray 
    288178        scale,            # type: float 
     
    300190        # TODO: 2. consider implications if there are intermediate results in P(Q) 
    301191        parts = OrderedDict(( 
    302             ("P(Q)", scale*Fsq), 
     192            ("P(Q)", scale*F2), 
    303193            ("S(Q)", S), 
    304             ("beta(Q)", F**2 / Fsq), 
    305             ("S_eff(Q)", 1 + (F**2 / Fsq)*(S-1)), 
     194            ("beta(Q)", F1**2 / F2), 
     195            ("S_eff(Q)", 1 + (F1**2 / F2)*(S-1)), 
    306196            ("effective_radius", radius_effective), 
    307197            ("radius_effective", radius_effective), 
    308             # ("I(Q)", scale*(Fsq + (F**2)*(S-1)) + bg), 
     198            # ("I(Q)", scale*(F2 + (F1**2)*(S-1)) + bg), 
    309199        )) 
    310200    else: 
    311201        parts = OrderedDict(( 
    312             ("P(Q)", scale*Fsq), 
     202            ("P(Q)", scale*F2), 
    313203            ("S(Q)", S), 
    314204            ("effective_radius", radius_effective), 
     
    374264        self.results = []  # type: List[np.ndarray] 
    375265 
    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)) 
     266    def Iq(self, call_details, values, cutoff, magnetic): 
     267        # type: (CallDetails, np.ndarray, float, bool) -> np.ndarray 
    383268 
    384269        p_info, s_info = self.info.composition[1] 
    385270        p_npars = p_info.parameters.npars 
     271        p_length = call_details.length[:p_npars] 
     272        p_offset = call_details.offset[:p_npars] 
    386273        s_npars = s_info.parameters.npars 
    387  
     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 
    388278        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  
    431     def Iq(self, call_details, values, cutoff, magnetic): 
    432         # type: (CallDetails, np.ndarray, float, bool) -> np.ndarray 
    433         p_info, s_info = self.info.composition[1] 
    434  
    435         # Retrieve values from the data vector 
     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] 
    436296        scale, background = values[0], values[1] 
    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  
     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 = [] 
    443306        nvalues = self.info.parameters.nvalues 
    444307        nweights = call_details.num_weights 
    445308        weights = values[nvalues:nvalues + 2*nweights] 
    446309 
    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  
    451310        # 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] 
    454311        p_details = make_details(p_info, p_length, p_offset, nweights) 
    455312        p_values = [ 
    456313            [1., 0.], # scale=1, background=0, 
    457             values[self._p_value_slice], 
    458             values[self._magentic_slice], 
     314            values[2:2+p_npars], 
     315            magnetism, 
    459316            weights] 
    460317        spacer = (32 - sum(len(v) for v in p_values)%32)%32 
     
    462319        p_values = np.hstack(p_values).astype(self.p_kernel.dtype) 
    463320 
    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  
    471321        # Construct the calling parameters for S. 
    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 
     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 
    483325            s_length[0] = 1 
    484326        s_details = make_details(s_info, s_length, s_offset, nweights) 
    485327        s_values = [ 
    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], 
     328            [1., 0.], # scale=1, background=0, 
     329            values[2+p_npars:2+p_npars+s_npars], 
    492330            weights, 
    493331        ] 
     
    496334        s_values = np.hstack(s_values).astype(self.s_kernel.dtype) 
    497335 
     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. 
    498342        # Plug R_eff from the form factor into structure factor parameters 
    499343        # and scale volume fraction by form:shell volume ratio. These changes 
     
    503347        #print("R_eff=%d:%g, volfrac=%g, volume ratio=%g" 
    504348        #      % (radius_type, radius_effective, volfrac, volume_ratio)) 
    505         s_dist = s_values[self._s_dist_slice] 
    506         if er_mode > 0: 
     349        if radius_type > 0: 
    507350            # set the value to the model R_eff and set the weight to 1 
    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. 
     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 
    514354        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) 
     355 
     356        # 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 
    518361 
    519362        # 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 
    522  
    523         # Determine overall scale factor. Hollow shapes are weighted by 
    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 
     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 
    531366 
    532367        # Capture intermediate values so user can see them.  These are 
     
    540375        # the results directly rather than through a lazy evaluator. 
    541376        self.results = lambda: _intermediates( 
    542             F, Fsq, S, combined_scale, radius_effective, beta_mode) 
     377            F1, F2, S, combined_scale, radius_effective, beta_mode) 
    543378 
    544379        return final_result 
Note: See TracChangeset for help on using the changeset viewer.