[3570545] | 1 | # This program is public domain |
---|
| 2 | """ |
---|
| 3 | Define a park fitting model. |
---|
| 4 | |
---|
| 5 | Usage |
---|
| 6 | ----- |
---|
| 7 | |
---|
| 8 | The simplest sort of fitting model is something like the following:: |
---|
| 9 | |
---|
| 10 | import numpy |
---|
| 11 | import park |
---|
| 12 | def G(x,mu,sigma): |
---|
| 13 | return numpy.exp(-0.5*(x-mu)**2/sigma**2) |
---|
| 14 | |
---|
| 15 | class Gauss(park.Model): |
---|
| 16 | parameters = ['center','width','scale'] |
---|
| 17 | def eval(self,x): |
---|
| 18 | return self.scale * G(x,self.center,self.width) |
---|
| 19 | |
---|
| 20 | It has a function which is evaluated at a series of x values and |
---|
| 21 | a set of adjustable parameters controlling the shape of f(x). |
---|
| 22 | |
---|
| 23 | You can check your module with something like the following:: |
---|
| 24 | |
---|
| 25 | $ ipython -pylab |
---|
| 26 | |
---|
| 27 | from gauss import Gauss |
---|
| 28 | |
---|
| 29 | g = Gauss(center=5,width=1,scale=10) |
---|
| 30 | x = asarray([1,2,3,4,5]) |
---|
| 31 | y = g(x) |
---|
| 32 | plot(x,y) |
---|
| 33 | |
---|
| 34 | This should produce a plot of the Gaussian peak. |
---|
| 35 | |
---|
| 36 | You will then want to try your model with some data. Create a file |
---|
| 37 | with some dummy data, such as gauss.dat:: |
---|
| 38 | |
---|
| 39 | # x y |
---|
| 40 | 4 2 |
---|
| 41 | 5 6 |
---|
| 42 | 6 7 |
---|
| 43 | 7 3 |
---|
| 44 | |
---|
| 45 | In order to match the model to data, you need to define a fitness |
---|
| 46 | object. This shows the difference between the model and the data, |
---|
| 47 | which you can then plot or sum to create a weighted chisq value:: |
---|
| 48 | |
---|
| 49 | f = park.Fitness(g, 'gauss.dat') |
---|
| 50 | plot(f.data.fit_x, f.residuals()) |
---|
| 51 | |
---|
| 52 | The data file can have up to four columns, either x,y or x,y,dy |
---|
| 53 | or x,dx,y,dy where x,y are the measurement points and values, |
---|
| 54 | dx is the instrument resolution in x and dy is the uncertainty |
---|
| 55 | in the measurement value y. You can pass any keyword arguments |
---|
| 56 | to data.load that are accepted by numpy.loadtxt. For example, |
---|
| 57 | you can reorder columns or skip rows. Additionally, you can modify |
---|
| 58 | data attributes directly x,y,dx,dy and calc_x. See help on park.Data1D |
---|
| 59 | for details. |
---|
| 60 | |
---|
| 61 | Once you have your model debugged you can use it in a fit:: |
---|
| 62 | |
---|
| 63 | g = Gauss(center=[3,5],scale=7.2,width=[1,3]) |
---|
| 64 | result = park.fit((g, 'gauss.dat')) |
---|
| 65 | result.plot() |
---|
| 66 | |
---|
| 67 | In this example, center and width are allowed to vary but scale is fixed. |
---|
| 68 | |
---|
| 69 | Existing models can be readily adapted to Park:: |
---|
| 70 | |
---|
| 71 | class Gauss(object): |
---|
| 72 | "Existing model" |
---|
| 73 | center,width,scale = 0,1,1 |
---|
| 74 | def __init__(self,**kw): |
---|
| 75 | for k,v in kw.items(): setattr(self,k,v) |
---|
| 76 | def eval(self,x): |
---|
| 77 | return self.scale *G(x,self.center,self.width) |
---|
| 78 | |
---|
| 79 | class GaussAdaptor(Gauss,Model): |
---|
| 80 | "PARK adaptor" |
---|
| 81 | parameters = ['center','width','scale'] |
---|
| 82 | def __init__(self,*args,**kw): |
---|
| 83 | Model.__init__(self,*args) |
---|
| 84 | Gauss.__init__(self,**kw) |
---|
| 85 | |
---|
| 86 | g = GaussAdaptor(center=[3,5],scale=7.2,width=[1,3]) |
---|
| 87 | result = park.fit((g, 'gauss.dat')) |
---|
| 88 | result.plot() |
---|
| 89 | |
---|
| 90 | Models can become much more complex than the ones described above, |
---|
| 91 | including multilevel models where fitting parameters can be added |
---|
| 92 | and removed dynamically. |
---|
| 93 | |
---|
| 94 | In many cases the park optimizer will need an adaptor for pre-existing |
---|
| 95 | models. The adaptor above relies on python properties to translate |
---|
| 96 | model.par access into model._par.get() and model._par.set() where _par |
---|
| 97 | is the internal name for par. This technique works for simple static |
---|
| 98 | models, but will not work well for sophisticated models which have, |
---|
| 99 | for example, a dynamic parameter set where the model parameters cannot |
---|
| 100 | be set as properties. A solution to this problem is to subclass the |
---|
| 101 | park.Parameter and override the value attribute as a property. |
---|
| 102 | |
---|
| 103 | Once models are defined they can be used in a variety of contexts, such |
---|
| 104 | as simultaneous fitting with constraints between the parameters. With |
---|
| 105 | some care in defining the model, computationally intensive fits can |
---|
| 106 | be distributed across multiple processors. We provide a simple user |
---|
| 107 | interface for interacting with the model parameters and managing fits. |
---|
| 108 | This can be extended with model specialized model editors which format |
---|
| 109 | the parameters in a sensible way for the model, or allow direct manipulation |
---|
| 110 | of the model structure. The underlying fitting engine can also be |
---|
| 111 | used directly from your own user interface. |
---|
| 112 | |
---|
| 113 | """ |
---|
| 114 | |
---|
| 115 | __all__ = ['Model'] |
---|
| 116 | |
---|
| 117 | import park |
---|
| 118 | from park.parameter import Parameter, ParameterSet |
---|
| 119 | from copy import copy, deepcopy |
---|
| 120 | |
---|
| 121 | |
---|
| 122 | class ParameterProperty(object): |
---|
| 123 | """ |
---|
| 124 | Attribute accessor for a named parameter. |
---|
| 125 | |
---|
| 126 | Objects of class ParameterProperty act similarly to normal property |
---|
| 127 | objects in that assignment to object.attr triggers the __set__ |
---|
| 128 | method of ParameterProperty and queries of the value object.attr |
---|
| 129 | triggers the __get__ method. These methods look up the actual |
---|
| 130 | parameter in the dictionary model.parameterset, delegating the |
---|
| 131 | the set/get methods of the underlying parameter object. |
---|
| 132 | |
---|
| 133 | For example:: |
---|
| 134 | |
---|
| 135 | model.P1 = 5 |
---|
| 136 | print model.P1 |
---|
| 137 | |
---|
| 138 | is equivalent to:: |
---|
| 139 | |
---|
| 140 | model.parameterset['P1'].set(5) |
---|
| 141 | print model.parameterset['P1'].get() |
---|
| 142 | |
---|
| 143 | To enable this behaviour, the model developer must assign a |
---|
| 144 | ParameterProperty object to a class attribute of the model. It |
---|
| 145 | must be a class attribute. Properties assigned as an instance |
---|
| 146 | attribute in the __init__ of the class will not be recognized |
---|
| 147 | as properties. |
---|
| 148 | |
---|
| 149 | An example model will look something like the following:: |
---|
| 150 | |
---|
| 151 | class MyModel: |
---|
| 152 | # A property must be assigned as a class attribute! |
---|
| 153 | P1 = ParameterProperty('P1') |
---|
| 154 | P2 = ParameterProperty('P2') |
---|
| 155 | def __init__(self, P1=None, P2=None): |
---|
| 156 | parP1 = Parameter('P1') |
---|
| 157 | if P1 is not None: parP1.set(P1) |
---|
| 158 | parP2 = Parameter('P2') |
---|
| 159 | if P2 is not None: parP2.set(P2) |
---|
| 160 | self.parameterset = { 'P1': parP1, 'P2': parP2 } |
---|
| 161 | |
---|
| 162 | This work is done implicitly by MetaModel, and by subclassing |
---|
| 163 | the class Model, the model developer does not ever need to |
---|
| 164 | use ParameterProperty. |
---|
| 165 | """ |
---|
| 166 | def __init__(self,name,**kw): |
---|
| 167 | self.name = name |
---|
| 168 | def __get__(self,instance,cls): |
---|
| 169 | return instance.parameterset[self.name].get() |
---|
| 170 | def __set__(self,instance,value): |
---|
| 171 | instance.parameterset[self.name].set(value) |
---|
| 172 | |
---|
| 173 | |
---|
| 174 | class MetaModel(type): |
---|
| 175 | """ |
---|
| 176 | Interpret parameters. |
---|
| 177 | |
---|
| 178 | This is a meta class, and the usual meta class rules apply. That is, |
---|
| 179 | the Model base class should be defined like:: |
---|
| 180 | |
---|
| 181 | class Model(object): |
---|
| 182 | __metaclass__ = MetaModel |
---|
| 183 | ... |
---|
| 184 | |
---|
| 185 | The MetaModel.__new__ method is called whenever a new Model |
---|
| 186 | class is created. The name of the model, its superclasses and |
---|
| 187 | its attributes are passed to MetaModel.__new__ which creates the |
---|
| 188 | actual class. It is not called for new instances of the model. |
---|
| 189 | |
---|
| 190 | MetaModel is designed to simplify the definition of parameters for |
---|
| 191 | the model. When processing the class, MetaModel defines |
---|
| 192 | parameters which is a list of names of all the parameters in the |
---|
| 193 | model, and parameterset, which is a dictionary of the actual |
---|
| 194 | parameters and any parameter sets in the model. |
---|
| 195 | |
---|
| 196 | Parameters can be defined using a list of names in the parameter |
---|
| 197 | attribute, or by defining the individual parameters as separate |
---|
| 198 | attributes of class Parameter. |
---|
| 199 | |
---|
| 200 | For example, the following defines parameters P1, P2, and P3:: |
---|
| 201 | |
---|
| 202 | class MyModel(Model): |
---|
| 203 | parameters = ['P1','P2'] |
---|
| 204 | P2 = Parameter(range=[0,inf]) |
---|
| 205 | P3 = Parameter() |
---|
| 206 | |
---|
| 207 | For each parameter, MetaModel will define a parameter accessor, |
---|
| 208 | and add the parameter definition to the parameter set. The accessor |
---|
| 209 | delegates query and assignment to the Parameter get/set methods. The |
---|
| 210 | attributes of the particular parameter instance can be |
---|
| 211 | adjusted using model.parameterset['name'].attribute = value. |
---|
| 212 | """ |
---|
| 213 | def __new__(cls, name, bases, vars): |
---|
| 214 | #print 'calling model meta',vars |
---|
| 215 | |
---|
| 216 | # Find all the parameters for the model. The listed parameters |
---|
| 217 | # are defined using:: |
---|
| 218 | # parameters = ['P1', 'P2', ...] |
---|
| 219 | # The remaining parameters are defined individually:: |
---|
| 220 | # P3 = Parameter() |
---|
| 221 | # P4 = Parameter() |
---|
| 222 | # The undefined parameters are those that are listed but not defined. |
---|
| 223 | listed = vars.get('parameters',[]) |
---|
| 224 | defined = [k for k,v in vars.items() if isinstance(v,Parameter)] |
---|
| 225 | undefined = [k for k in listed if k not in defined] |
---|
| 226 | #print listed,defined,undefined |
---|
| 227 | |
---|
| 228 | # Define a getter/setter for every parameter so that the user |
---|
| 229 | # can say model.name to access parameter name. |
---|
| 230 | # |
---|
| 231 | # Create a parameter object for every undefined parameter. |
---|
| 232 | # Check if the base class defines a default value, and use |
---|
| 233 | # that for the initial value. We don't want to do this for |
---|
| 234 | # parameters explicitly defined since the user may have |
---|
| 235 | # given them a default value already. |
---|
| 236 | # |
---|
| 237 | # For predefined parameters we must set the name explicitly. |
---|
| 238 | # This saves the user from having to use, e.g.:: |
---|
| 239 | # P1 = Parameter('P1') |
---|
| 240 | pars = [] |
---|
| 241 | for p in undefined: |
---|
| 242 | # Check if the attribute value is initialized in a base class |
---|
| 243 | for b in bases: |
---|
| 244 | if hasattr(b,p): |
---|
| 245 | value = getattr(b,p) |
---|
| 246 | break |
---|
| 247 | else: |
---|
| 248 | value = 0. |
---|
| 249 | #print "looking up value in base as",value |
---|
| 250 | pars.append(Parameter(name=p,value=value)) |
---|
| 251 | vars[p] = ParameterProperty(p) |
---|
| 252 | for p in defined: |
---|
| 253 | # Make sure parameter name is correct. Note that we are using |
---|
| 254 | # _name rather than .name to access the name attribute since |
---|
| 255 | # name is a read-only parameter. |
---|
| 256 | vars[p]._name = p |
---|
| 257 | pars.append(vars[p]) |
---|
| 258 | vars[p] = ParameterProperty(p) |
---|
| 259 | |
---|
| 260 | # Construct the class attributes 'parameters' and 'parameterset'. |
---|
| 261 | # Listed parameters are given first, with unlisted parameters |
---|
| 262 | # following alphabetically. For hierarchical parameter sets, |
---|
| 263 | # we also need to include the defined sets. |
---|
| 264 | unlisted = list(set(defined+undefined) - set(listed)) |
---|
| 265 | unlisted.sort() |
---|
| 266 | parameters = listed + unlisted |
---|
| 267 | parsets = [k for k,v in vars.items() if isinstance(v,ParameterSet)] |
---|
| 268 | vars['parameters'] = parameters |
---|
| 269 | vars['parameterset'] = ParameterSet(pars=pars+parsets) |
---|
| 270 | #print 'pars',vars['parameters'] |
---|
| 271 | #print 'parset',vars['parameterset'] |
---|
| 272 | |
---|
| 273 | # Return a new specialized instance of the class with parameters |
---|
| 274 | # and parameterset made explicit. |
---|
| 275 | return type.__new__(cls, name, bases, vars) |
---|
| 276 | |
---|
| 277 | class Model(object): |
---|
| 278 | """ |
---|
| 279 | Model definition. |
---|
| 280 | |
---|
| 281 | The model manages attribute access to the fitting parameters and |
---|
| 282 | also manages the dataset. |
---|
| 283 | |
---|
| 284 | derivatives ['p1','p2',...] |
---|
| 285 | List of parameters for which the model can calculate |
---|
| 286 | derivatives. The derivs |
---|
| 287 | The model function can compute the derivative with respect |
---|
| 288 | to this parameter. The function model.derivs(x,[p1,p2,...]) |
---|
| 289 | will return (f(x),df/dp1(x), ...). The parameters and their |
---|
| 290 | order are determined by the fitting engine. |
---|
| 291 | |
---|
| 292 | Note: This is a property of the model, not the fit. |
---|
| 293 | Numerical derivatives will be used if the parameter is |
---|
| 294 | used in an expression or if no analytic derivative is |
---|
| 295 | available for the parameter. Automatic differentiation |
---|
| 296 | on parameter expressions is possible, but beyond the scope |
---|
| 297 | of this project. |
---|
| 298 | |
---|
| 299 | eval(x) |
---|
| 300 | Evaluate the model at x. This must be defined by the subclass. |
---|
| 301 | |
---|
| 302 | eval_deriv(x,pars=[]) |
---|
| 303 | Evaluate the model and the derivatives at x. This must be |
---|
| 304 | defined by the subclass. |
---|
| 305 | |
---|
| 306 | parameters |
---|
| 307 | The names of the model parameters. If this is not provided, then |
---|
| 308 | the model will search the subclass for park.Parameter attributes |
---|
| 309 | and construct the list of names from that. Any parameters in the |
---|
| 310 | list not already defined as park.Parameter attributes will be |
---|
| 311 | defined as parameters with a default of 0. |
---|
| 312 | |
---|
| 313 | parameterset |
---|
| 314 | The set of parameters defined by the model. These are the |
---|
| 315 | parameters themselves, gathered into a park.ParameterSet. |
---|
| 316 | |
---|
| 317 | The minimum fittng model if you choose not to subclass park.Model |
---|
| 318 | requires parameterset and a residuals() method. |
---|
| 319 | """ |
---|
| 320 | __metaclass__ = MetaModel |
---|
| 321 | derivatives = [] |
---|
| 322 | def __init__(self,*args,**kw): |
---|
| 323 | """ |
---|
| 324 | Initialize the model. |
---|
| 325 | |
---|
| 326 | Model('name',P1=value, P2=Value, ...) |
---|
| 327 | |
---|
| 328 | When overriding __init__ in the subclass be sure to call |
---|
| 329 | Model.__init__(self, *args, **kw). This makes a private |
---|
| 330 | copy of the parameterset for the model and initializes |
---|
| 331 | any parameters set using keyword arguments. |
---|
| 332 | """ |
---|
| 333 | #print 'calling model init on',id(self) |
---|
| 334 | # To avoid trashing the Model.parname = Parameter() template |
---|
| 335 | # when we create an instance and accessor for the parameter |
---|
| 336 | # we need to make sure that we are using our own copy of the |
---|
| 337 | # model dictionary stored in vars |
---|
| 338 | if len(args)>1: raise TypeError("expect name as model parameter") |
---|
| 339 | name = args[0] if len(args)>=1 else 'unknown' |
---|
| 340 | self.parameterset = deepcopy(self.parameterset) |
---|
| 341 | for k,v in kw.items(): |
---|
| 342 | self.parameterset[k].set(v) |
---|
| 343 | self.parameterset._name = name |
---|
| 344 | #print 'done',id(self) |
---|
| 345 | |
---|
| 346 | def __call__(self, x, pars=[]): |
---|
| 347 | return self.eval(x) |
---|
| 348 | |
---|
| 349 | def eval(self, x): |
---|
| 350 | """ |
---|
| 351 | Evaluate the model at x. |
---|
| 352 | |
---|
| 353 | This method needs to be specialized in the model to evaluate the |
---|
| 354 | model function. Alternatively, the model can implement is own |
---|
| 355 | version of residuals which calculates the residuals directly |
---|
| 356 | instead of calling eval. |
---|
| 357 | """ |
---|
| 358 | raise NotImplementedError('Model needs to implement eval') |
---|
| 359 | |
---|
| 360 | def eval_derivs(self, x, pars=[]): |
---|
| 361 | """ |
---|
| 362 | Evaluate the model and derivatives wrt pars at x. |
---|
| 363 | |
---|
| 364 | pars is a list of the names of the parameters for which derivatives |
---|
| 365 | are desired. |
---|
| 366 | |
---|
| 367 | This method needs to be specialized in the model to evaluate the |
---|
| 368 | model function. Alternatively, the model can implement is own |
---|
| 369 | version of residuals which calculates the residuals directly |
---|
| 370 | instead of calling eval. |
---|
| 371 | """ |
---|
| 372 | raise NotImplementedError('Model needs to implement eval_derivs') |
---|
| 373 | |
---|
| 374 | def set(self, **kw): |
---|
| 375 | """ |
---|
| 376 | Set the initial value for a set of parameters. |
---|
| 377 | |
---|
| 378 | E.g., model.set(width=3,center=5) |
---|
| 379 | """ |
---|
| 380 | # This is a convenience funciton for the user. |
---|
| 381 | # |
---|
| 382 | for k,v in kw.items(): |
---|
| 383 | self.parameterset[k].set(v) |
---|
| 384 | |
---|
| 385 | |
---|
| 386 | def add_parameter(model, name, **kw): |
---|
| 387 | """ |
---|
| 388 | Add a parameter to an existing park model. |
---|
| 389 | |
---|
| 390 | Note: this may not work if it is operating on a BaseModel. |
---|
| 391 | """ |
---|
| 392 | setattr(model.__class__, name, park.model.ParameterProperty(name)) |
---|
| 393 | model.parameterset.append(park.Parameter(name=name, **kw)) |
---|
| 394 | |
---|
| 395 | |
---|
| 396 | def test(): |
---|
| 397 | # Gauss theory function |
---|
| 398 | import numpy |
---|
| 399 | eps,inf = numpy.finfo('d').eps,numpy.inf |
---|
| 400 | def G(x,mu,sigma): |
---|
| 401 | mu,sigma = numpy.asarray(mu),numpy.asarray(sigma) |
---|
| 402 | return numpy.exp(-0.5*(x-mu)**2/sigma**2) |
---|
| 403 | |
---|
| 404 | # === Minimal model === |
---|
| 405 | # just list the fitting parameters and define the function. |
---|
| 406 | class Gauss(Model): |
---|
| 407 | parameters = ['center','width','scale'] |
---|
| 408 | def eval(self,x): |
---|
| 409 | return self.scale * G(x,self.center,self.width) |
---|
| 410 | |
---|
| 411 | # Create a couple of models and make sure they don't conflict |
---|
| 412 | g1 = Gauss(center=5,width=1,scale=2) |
---|
| 413 | assert g1.center == 5 |
---|
| 414 | g2 = Gauss(center=6,width=1) |
---|
| 415 | assert g1.center == 5 |
---|
| 416 | assert g2.center == 6 |
---|
| 417 | assert g1(5) == 2 |
---|
| 418 | assert g2(6) == 0 |
---|
| 419 | |
---|
| 420 | # === explore parameters === |
---|
| 421 | # dedicated model using park parameters directly, and defining derivatives |
---|
| 422 | class Gauss(Model): |
---|
| 423 | center = Parameter(value=0, |
---|
| 424 | tip='Peak center') |
---|
| 425 | width = Parameter(value=1, |
---|
| 426 | limits=(eps,inf), |
---|
| 427 | tip='Peak width (1-sigma)') |
---|
| 428 | scale = Parameter(value=1, |
---|
| 429 | limits=(0,inf), |
---|
| 430 | tip='Peak scale (>0)') |
---|
| 431 | def eval(self,x): |
---|
| 432 | return self.scale * G(x,self.center,self.width) |
---|
| 433 | |
---|
| 434 | # Derivatives |
---|
| 435 | derivatives = ['center','width','scale'] |
---|
| 436 | def dscale(self, g, x): |
---|
| 437 | return g/self.scale |
---|
| 438 | def dcenter(self, g, x): |
---|
| 439 | return 2*(x-self.center)/self.width*g |
---|
| 440 | def dwidth(self, g, x): |
---|
| 441 | return 2*(x-self.center)/self.width**3*g |
---|
| 442 | dmap = dict(scale=dscale,center=dcenter,width=dwidth) |
---|
| 443 | def eval_derivs(self,x,pars=[]): |
---|
| 444 | """ |
---|
| 445 | Calculate function value and derivatives wrt to the parameters. |
---|
| 446 | |
---|
| 447 | pars is a list of parameter names, possibly consisting of any |
---|
| 448 | parameter with deriv=True. |
---|
| 449 | """ |
---|
| 450 | g = self.eval(x) |
---|
| 451 | dg = [self.dmap[p](self,g,x) for p in pars] |
---|
| 452 | return [g]+dg |
---|
| 453 | |
---|
| 454 | g1 = Gauss(center=5) |
---|
| 455 | g1.parameterset['center'].tip = 'This is the center' |
---|
| 456 | assert g1.center == 5 |
---|
| 457 | g2 = Gauss(center=6) |
---|
| 458 | assert g1.center == 5 |
---|
| 459 | assert g2.center == 6 |
---|
| 460 | assert g1(5) == 1 |
---|
| 461 | assert g2(6) == 1 |
---|
| 462 | |
---|
| 463 | # ====== Test wrapper ======= |
---|
| 464 | # delegate to existing model via inheritence |
---|
| 465 | class Gauss(object): |
---|
| 466 | """Pre-existing model""" |
---|
| 467 | center,width,scale = 0,1,1 |
---|
| 468 | def __init__(self,**kw): |
---|
| 469 | #print "calling BaseGauss init on",id(self) |
---|
| 470 | for k,v in kw.items(): setattr(self,k,v) |
---|
| 471 | #print "done",id(self) |
---|
| 472 | def eval(self,x): |
---|
| 473 | return self.scale *G(x,self.center,self.width) |
---|
| 474 | |
---|
| 475 | class GaussAdaptor(Gauss,Model): |
---|
| 476 | """PARK wrapper""" |
---|
| 477 | parameters = ['center','width','scale'] |
---|
| 478 | def __init__(self,*args,**kw): |
---|
| 479 | #print "calling Gauss init on",id(self) |
---|
| 480 | Model.__init__(self,*args) |
---|
| 481 | Gauss.__init__(self,**kw) |
---|
| 482 | #print "done",id(self) |
---|
| 483 | |
---|
| 484 | g1 = GaussAdaptor(center=5) |
---|
| 485 | g2 = GaussAdaptor(center=6) |
---|
| 486 | g3 = GaussAdaptor() |
---|
| 487 | assert g1.center == 5 |
---|
| 488 | assert g2.center == 6 |
---|
| 489 | assert g3.scale == 1 |
---|
| 490 | assert g1(5) == 1 |
---|
| 491 | assert g2(6) == 1 |
---|
| 492 | |
---|
| 493 | g4 = GaussAdaptor('M3',center=6) |
---|
| 494 | assert g4.parameterset.name == 'M3' |
---|
| 495 | |
---|
| 496 | # dedicated multilevel model using park parameters directly |
---|
| 497 | class MultiGauss(Model): |
---|
| 498 | def add(self,name,model): |
---|
| 499 | pass |
---|
| 500 | |
---|
| 501 | # wrapped model using park parameters indirectly |
---|
| 502 | class BaseMultiGauss(object): |
---|
| 503 | def __init__(self): |
---|
| 504 | self.models = [] |
---|
| 505 | def add(self,**kw): |
---|
| 506 | self.models.append(BaseGauss(**kw)) |
---|
| 507 | class WrapMultiGauss(BaseMultiGauss,Model): |
---|
| 508 | def __init__(self): |
---|
| 509 | pass |
---|
| 510 | |
---|
| 511 | if __name__ == "__main__": test() |
---|