Changeset 132
- Timestamp:
- 03/31/08 19:21:24 (8 months ago)
- Files:
-
- projects/AsynCluster/trunk/pypmc/model.py (modified) (10 diffs)
- projects/AsynCluster/trunk/pypmc/pmc.py (modified) (4 diffs)
- projects/AsynCluster/trunk/pypmc/sample.py (modified) (2 diffs)
- projects/AsynCluster/trunk/pypmc/test (added)
- projects/AsynCluster/trunk/pypmc/test/project.py (added)
- projects/AsynCluster/trunk/pypmc/test/test_model.py (added)
- projects/AsynCluster/trunk/pypmc/test/test_pmc.py (added)
- projects/AsynCluster/trunk/pypmc/test/test_sample.py (added)
- projects/AsynCluster/trunk/pypmc/test/test_tseries.py (added)
- projects/AsynCluster/trunk/pypmc/test/util.py (added)
- projects/AsynCluster/trunk/pypmc/test/__init__.py (added)
- projects/AsynCluster/trunk/pypmc/tseries.py (added)
- projects/AsynCluster/trunk/pypmc/__init__.py (added)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
projects/AsynCluster/trunk/pypmc/model.py
r131 r132 26 26 from twisted.internet import defer, reactor 27 27 28 #from asynqueue import ThreadQueue29 28 from asyncluster.master import jobs 30 29 from twisted_goodies.pybywire.params import Parameterized 31 30 from twisted_goodies.pybywire.pack import Packer, Unpacker 32 31 33 from dist import Distribution 34 35 36 class ThreadQueue(object): 32 33 class NullQueue(object): 34 """ 35 I act like an AsynQueue except that I run everything in the main 36 thread. I'm mostly useful for debugging. 37 """ 37 38 def __init__(self, *args): 38 39 pass … … 45 46 46 47 47 class ModelManager(object):48 """49 I manage an ensemble of L{Model} instances, with a parameter vector that is50 the joint set of all of the models' parameters.51 52 I am constructed with a sequence of parameter names, an equal-length53 sequence of L{Prior} objects for the parameters, and a sequence I{models}54 of 2-tuples for the models.55 56 Each tuple in the models sequence contains:57 58 1. A reference to one of the models, and59 60 2. A sequence of expressions for the normalization and distribution61 parameters as supplied to that model in each parameter vector.62 63 """64 def __init__(self, projectManager):65 ID = id(self)66 self.mgr = projectManager67 for name in ('paramNames', 'priors', 'expressions'):68 setattr(self, name, getattr(projectManager, name))69 self.N_params = len(self.paramNames)70 self.modelMap, self.peMap = {}, {}71 for modelObj, paramExprs in projectManager.models:72 if modelObj.N_params() != len(paramExprs):73 exprString = ", ".join(["'%s'" % x for x in paramExprs])74 msg = "Supplied expressions (%s) " % exprString +\75 "do not match %d model parameters" % modelObj.N_params()76 raise ValueError(msg)77 self.modelMap[ID] = modelObj78 self.peMap[ID] = paramExprs79 ID += 180 self.caller = ModelCaller(self.modelMap)81 82 def getID(self, modelObj):83 """84 Returns the integer ID for the supplied I{modelObj}.85 """86 for ID, thisModelObj in self.modelMap.iteritems():87 if thisModelObj == modelObj:88 return ID89 90 def setLocalMode(self):91 """92 See L{ModelCaller.setLocalMode}.93 """94 return self.caller.setLocalMode()95 96 def setRemoteMode(self, socket):97 """98 See L{ModelCaller.setRemoteMode}.99 """100 return self.caller.setRemoteMode(socket)101 102 def modelParams(self, X):103 """104 Based on the parameter vectors supplied in the 2d array I{X}, or a105 single parameter vector supplied as I{X}, returns a dict of parameter106 vectors evaluated for each of my models from their parameter107 expressions in I{peMap}. The vectors are keyed by the ID of the model108 to which they apply.109 """110 if len(X.shape) == 1:111 X = X.reshape(1, X.shape[0])112 XM = {}113 namespace = {'U':s.ones(len(X))}114 for k, paramName in enumerate(self.paramNames):115 namespace[paramName] = X[:,k]116 for name, expression in self.expressions.iteritems():117 namespace[name] = eval(expression, namespace)118 for ID, paramExprs in self.peMap.iteritems():119 evalCode = "[%s]" % ",".join(paramExprs)120 XM[ID] = s.column_stack(eval(evalCode, namespace))121 return XM122 123 def rPriors(self, N):124 """125 Returns I{N} parameter vectors as random draws from the prior126 distributions of the parameters.127 """128 Y = s.empty((N, self.N_params), s.float32)129 for k, p in enumerate(self.priors):130 Y[:,k] = p.rvs(N)131 return Y132 133 def pPriors(self, X):134 """135 Returns a vector of prior probability densities for each parameter in136 X, or rows of X.137 """138 if len(X.shape) == 1:139 X = X.reshape(1, X.shape[0])140 N = X.shape[0]141 Y = s.zeros((N, self.N_params), s.float32)142 for k, p in enumerate(self.priors):143 Y[:,k] = p.pdf(X[:,k])144 return Y145 146 def rProposal(self, N, wiggle):147 """148 Returns an array of I{N} rows of parameter offsets drawn from the prior149 distributions scaled by a I{wiggle} value between 0 and 1.150 """151 Y = s.empty((N, self.N_params), s.float32)152 for k, p in enumerate(self.priors):153 Y[:,k] = p.rds(N, wiggle)154 return Y155 156 def pProposal(self, X, wiggle):157 """158 Returns a vector of probability densities for each parameter offset in159 X, or rows of X, under the assumption that they were generated from my160 L{rProposal} method with the specified I{wiggle}.161 """162 if len(X.shape) == 1:163 X = X.reshape(1, X.shape[0])164 N = X.shape[0]165 Y = s.zeros((N, self.N_params), s.float32)166 for k, p in enumerate(self.priors):167 Y[:,k] = p.pds(X[:,k], wiggle)168 return Y169 170 @defer.deferredGenerator171 def rDistribution(self, X, N):172 """173 Given the parameter vector I{X}, returns a dict of time series vectors174 of I{N} random variates from the probability distribution for each175 model, keyed by model ID.176 """177 results = {}178 Distribution.rvCache.clear()179 XM = self.modelParams(X)180 for ID, Xm in XM.iteritems():181 wfd = defer.waitForDeferred(182 self.caller.call(ID, 'setParamVector', Xm[0]))183 yield wfd; wfd.getResult()184 wfd = defer.waitForDeferred(185 self.caller.call(ID, 'rDistribution', N))186 yield wfd187 results[ID] = wfd.getResult()188 yield results189 190 def pDistribution(self, X, N, M=1):191 """192 Given the parameter vector I{X}, returns a dict of 2-tuples that each193 contain a 1d array of I{N} equispaced points over the range of the194 normalized data for each model, and a 1d array of probability densities195 for the data points.196 197 The returned vectors are keyed by the ID of the model to which they198 apply.199 200 You can compute the probability densities over a multiple of each201 model's data ranges by setting the keyword I{M} to something greater202 than 1.203 """204 def called(result, ID):205 results[ID] = result206 207 dList, results = [], {}208 XM = self.modelParams(X)209 for ID, Xm in XM.iteritems():210 d = self.caller.call(ID, 'pDistribution', Xm[0], N, M)211 d.addCallback(called, ID)212 dList.append(d)213 return defer.DeferredList(dList).addCallback(lambda _: results)214 215 def likelihoods(self, X):216 """217 Returns the total log likelihoods of the global parameter vector(s) in218 the supplied array I{X}.219 220 Runs via thread or remotely, with a deferred result either way. Tries221 twice for each parameter vector, returning infinitely negative222 likelihood for repeated failures.223 """224 def called(result, paramVectors, k):225 if not isinstance(result, float):226 d = self.caller.likelihood(IDs, paramVectors)227 d.addBoth(triedAgain, k)228 return d229 L[k] = result230 231 def triedAgain(result, k):232 if not isinstance(result, float):233 result = -s.inf234 L[k] = result235 236 XM = self.modelParams(X)237 IDs = XM.keys()238 dList = []239 N = XM.values()[0].shape[0]240 L = s.empty(N)241 for k in xrange(N):242 paramVectors = [XM[ID][k] for ID in IDs]243 d = self.caller.likelihood(IDs, paramVectors)244 d.addBoth(called, paramVectors, k)245 dList.append(d)246 return defer.DeferredList(dList).addCallback(lambda _: L)247 248 249 48 class ModelCaller(object): 250 49 """ 251 I handle deferred calls to models in an ensemble, in either local or remote 252 mode. Calls in remote mode are run through a separate thread. 253 254 Instantiate me with a dict of the models, keyed by unique integer IDs. 50 I handle deferred calls to a L{Model} object in either local or remote 51 mode. Calls in local mode are run through an instance of L{NullQueue} in 52 the main thread. 255 53 """ 256 54 remoteMode = False … … 262 60 from twisted_goodies.pybywire import pack 263 61 264 G = {} 265 266 def newModel(ID, model): 267 G[ID] = model 268 269 def callModel(ID, methodName, *possiblyPackedArgs): 270 method = getattr(G[ID], methodName) 271 return packwrap(method)(*possiblyPackedArgs) 272 273 def likelihood(IDs, paramVectors): 274 L = 0 275 for k, X in enumerate(pack.Unpacker(paramVectors)): 276 ID = IDs[k] 277 L += G[ID].likelihood(X) 278 if not isfinite(L): 279 break 62 M = None 63 64 def newModel(modelObj): 65 global M 66 M = modelObj 67 68 def callModel(methodName, *possiblyPackedArgs): 69 method = getattr(M, methodName) 70 return pack.packwrap(method)(*possiblyPackedArgs) 71 72 def likelihood(packedVector): 73 paramVector = pack.Unpacker(packedVector)() 74 L = M.likelihood(paramVector) 280 75 return str(float(L)) 281 76 282 77 ########################################################################### 283 78 """ 284 285 def __init__(self, modelMap): 286 self.modelMap = modelMap 287 self.threadQueue = ThreadQueue(1) 79 def __init__(self, modelObj): 80 self.modelObj = modelObj 81 self.queue = NullQueue(1) 288 82 reactor.addSystemEventTrigger( 289 'before', 'shutdown', self. threadQueue.shutdown)83 'before', 'shutdown', self.queue.shutdown) 290 84 291 85 def _oops(self, failure): … … 304 98 remotely-run operations can commence. 305 99 """ 306 def dispatchModels(null):307 dList = [308 self.client.update('newModel', ID, modelObj)309 for ID, modelObj in self.modelMap.iteritems()]310 return defer.DeferredList(dList)311 312 100 if hasattr(self, 'client'): 313 101 return defer.succeed(None) … … 318 106 d.addCallback( 319 107 lambda _: self.client.registerClasses(*Parameterized.registry)) 320 d.addCallback(dispatchModels) 108 d.addCallback( 109 lambda _: self.client.update('newModel', modelObj)) 321 110 d.addCallback(lambda _: setattr(self, 'remoteMode', True)) 322 111 d.addErrback(self._oops) … … 330 119 return result 331 120 332 def call(self, ID,methodName, *args, **kw):333 """ 334 For the model specified by integer I{ID}, calls the method identified335 by I{methodName} with any suppliedargs.121 def call(self, methodName, *args, **kw): 122 """ 123 Calls my model's method identified by I{methodName} with any supplied 124 args. 336 125 337 126 If I am in local mode, runs the call in a dedicated thread of the … … 352 141 args = (Packer(*args)(),) 353 142 d = self.client.run( 354 'callModel', ID,methodName, *args, **{'timeout':self.timeout})143 'callModel', methodName, *args, **{'timeout':self.timeout}) 355 144 d.addCallback(self._gotPossiblyPackedResult) 356 145 else: 357 d = self.threadQueue.call( 358 getattr(self.modelMap[ID], methodName), *args) 146 d = self.queue.call(getattr(self.modelObj, methodName), *args) 359 147 d.addErrback(self._oops) 360 148 return d 361 149 362 def likelihood(self, IDs, paramVectors, remote=False, local=False): 363 """ 364 Returns the total log likelihood of the parameter vectors for the 365 models referenced by ID. The first argument is a sequence of the model 366 I{IDs} and the second is a sequence of the models' parameter vectors, 367 in order. 150 def likelihood(self, paramVector, remote=False, local=False): 151 """ 152 Returns the log likelihood of the supplied parameter vector for my 153 model. 368 154 """ 369 155 def gotLikelihood(value): … … 375 161 376 162 if (remote or self.remoteMode) and not local: 377 packedVector s = Packer(*paramVectors)163 packedVector = Packer(*paramVector) 378 164 d = self.client.run( 379 'likelihood', IDs, packedVectors(), timeout=self.timeout)165 'likelihood', packedVector(), timeout=self.timeout) 380 166 d.addCallback(gotLikelihood) 381 167 else: 382 dList = [self.threadQueue.call( 383 self.modelMap[ID].likelihood, paramVectors[k]) 384 for k, ID in enumerate(IDs)] 385 d = defer.gatherResults(dList).addCallback(lambda x: sum(x)) 168 d = self.queue.call(self.modelObj.likelihood, paramVector) 386 169 return d.addErrback(self._oops) 387 170 … … 451 234 """ 452 235 Returns the probability densities of the supplied zero-mean deviates, 453 under the assumption that they were generated from my L{rds} method 454 with the specified I{wiggle}. 455 """ 456 # Taking out divide-by-wiggle helps avoid low-likelihood tagalongs 457 # but makes for very lumpy posteriors. Which is the correct way? 236 under the assumption that they were generated from my Gaussian-PDF 237 L{rds} method with the specified I{wiggle}. 238 239 For my Gaussian-PDF deviate generator, the probability density of a 240 given deviate is inversely proportional to the scaling to its standard 241 deviation imparted by I{wiggle}. Thus, the unit-normal PDF is divided 242 by the I{wiggle} in the result. 243 """ 458 244 return self.rdsObj.pdf(X/wiggle) / wiggle 459 245 … … 461 247 class Model(Parameterized): 462 248 """ 463 Stochastic Model for fitting a random variable, or a linear combination464 thereof, to a setof observations.465 466 The probability distributions of the random variable (s) is embodied in the467 myI{distribution} attribute, an instance of L{dist.Distribution} or, for a249 Stochastic Model for fitting a C{Kx1} vector of random variables to a 250 C{KxN} matrix of observations. 251 252 The probability distributions of the random variables is embodied in the my 253 I{distribution} attribute, an instance of L{dist.Distribution} or, for a 468 254 combination of random variables, an instance of L{dist.Combo_Distribution}. 469 255 projects/AsynCluster/trunk/pypmc/pmc.py
r131 r132 1 # Py Folio: Portfolio Performance Simulation1 # PyPMC: Population Monte Carlo implemented with Python and Twisted 2 2 # 3 # Copyright (C) 2007 by Edwin A. Suominen, http://www.eepatents.com3 # Copyright (C) 2007-2008 by Edwin A. Suominen, http://www.eepatents.com 4 4 # 5 5 # This program is free software; you can redistribute it and/or modify it under … … 30 30 31 31 import asynqueue 32 from twisted_goodies.pybywire.pack import Packer, Unpacker 32 33 from asyncluster.master import jobs 33 from twisted_goodies.pybywire.pack import Packer, Unpacker34 34 35 35 import model … … 91 91 92 92 93 class MC_Base(object):93 class PMC(object): 94 94 """ 95 I provide a common foundation for Monte Carlo simulators.95 Population MCMC with per Cappe et al. 96 96 """ 97 wiggle = 1.0 98 chunkSize = 5000 99 V = [1.0, 0.5, 0.1, 0.05, 0.001] 100 97 101 def __init__(self, modelManager, **kw): 98 102 self.mgr = modelManager … … 131 135 """ 132 136 self.iterationProducer.registerConsumer(consumer) 133 134 135 class MCMC(MC_Base):136 """137 I am a conventional Metropolis-Hastings MCMC sampler, and serve as a base138 class for the DE-MCMC sampler.139 """140 wiggle = 0.05141 reportInterval = 2142 143 def getXL(self, N):144 """145 Generates a (N_pop, N_params+1) population array I{XL}. Each row146 contains the one set of distribution parameters concatenated with the147 scalar value of the log-likelihood of those parameters, given the data.148 149 Returns a deferred that fires with the array.150 """151 def gotLikelihood(L, m, X):152 XL[m,:-1] = X153 XL[m,-1] = L[0]154 155 def gotAllLikelihoods(null):156 XL[:,-1] += s.log(self.mgr.pPriors(XL[:,:-1])).sum(1)157 return XL158 159 # TODO: Use array processing in model manager's likelihoods method160 XL = s.empty((N, self.N_params+1), s.float32)161 X = self.mgr.rPriors(N)162 dList = []163 for m, Xm in enumerate(X):164 d = self.mgr.likelihoods(Xm)165 d.addCallback(gotLikelihood, m, Xm)166 dList.append(d)167 d = defer.DeferredList(dList)168 d.addCallback(gotAllLikelihoods)169 return d170 171 def E(self, wiggle):172 """173 Returns a random parameter offset vector drawn from the model's174 proposal distribution.175 176 Draws the vectors from the model in batches for improved efficiency.177 """178 if not hasattr(self, '_OV') or self._kOV == 499:179 self._kOV = -1180 self._OV = self.mgr.rProposal(500, wiggle)181 self._kOV += 1182 return self._OV[self._kOV,:]183 184 @defer.deferredGenerator185 def run(self, N_chains, **kw):186 """187 Does an MCMC or DE-MCMC run with I{N_chains} parallel chains or188 population members.189 190 Returns a deferred that fires when the run is done. No output value is191 provided via the deferred, however.192 193 Use a provider of L{interfaces.IConsumer} to get the MCMC samples as194 they are produced. Each attached consumer is updated after every195 iteration with my array of parameter vectors (one vector per row) for196 each iteration.197 198 @param N_iter: The number of iterations to produce after burn-in.199 200 @param N_bi: The number of burn-in iterations to discard before201 producing any.202 203 """204 N_iter, N_bi = self.setupRun(N_chains, **kw)205 wfd = defer.waitForDeferred(self.getXL(N_chains))206 yield wfd207 self.XL = wfd.getResult()208 i = 0209 acceptances = []210 while i < N_iter + N_bi:211 d = defer.gatherResults(self.iteration())212 t0 = time.time()213 wfd = defer.waitForDeferred(d)214 yield wfd;215 acceptances.extend(wfd.getResult())216 if i >= N_bi:217 self.iterationProducer.gotNext(self.XL[:,:-1])218 if not i % self.reportInterval:219 acceptanceRate = 100.0 * sum(acceptances) / len(acceptances)220 acceptances = []221 msg = "%4d, %4.2f sec, AR=%3d%%, LPmax=%d: %s" % (222 i, time.time()-t0, acceptanceRate, self.XL[:,-1].max(),223 ", ".join(["%8.3g" % x for x in self.XL[:,:-1].mean(0)]))224 print msg225 i += 1226 self.iterationProducer.stopProducing()227 228 def iteration(self):229 """230 Does a conventional MCMC iteration.231 """232 return [233 self.metropolisHastings(m)234 for m in xrange(self.N_chains)]235 236 def metropolisHastings(self, m, Xd=None):237 """238 Proposes a new parameter vector for population member or chain I{m}239 that is offset from the last one by a draw from the model's proposal240 distribution plus any offset vector I{Xd} that is supplied.241 242 The parameter is accepted with probability proportional to the243 Metropolis-Hastings ratio of its likelihood versus the likelihood of244 the existing vector.245 246 If accepted, the proposal vector overwrites the existing one in my XL247 matrix.248 249 Returns a deferred that fires with C{True} if the proposal was250 accepted, C{False} if not.251 """252 def gotL(Lp):253 Lp = Lp[0]254 # The difference between log probabilities is the same as the ratio255 # of the probabilities themselves.256 if s.isfinite(Lp):257 Lp += s.log(P).sum()258 if (Lp - self.XL[m,-1]) > s.log(s.rand()):259 # The proposal was accepted, so write the new parameter260 # vector and its log likelihood to the XL matrix.261 self.XL[m,:-1] = Xp262 self.XL[m,-1] = Lp263 return True264 # The proposal was rejected, so I'll leave the current copy alone.265 return False266 267 if Xd is None:268 # Standard MCMC, where the wiggle scales the jump269 Xp = self.XL[m,:-1] + self.E(self.wiggle)270 else:271 # DE-MCMC, where the wiggle scales the offset vector (=gamma)272 Xp = self.XL[m,:-1] + self.wiggle*Xd + self.E(0.0005)273 P = self.mgr.pPriors(Xp)274 if s.greater(P, 0).all():275 d = self.mgr.likelihoods(Xp)276 # A failure in the likelihood call is treated like a rejected277 # proposal278 d.addCallbacks(gotL, lambda _: False)279 else:280 # A zero-probability prior guarantees a rejection, so don't bother281 # computing the likelihood282 d = defer.succeed(False)283 return d284 285 286 class DE_MCMC(MCMC):287 """288 Population MCMC with an asynchronous, parallelized version of the289 Differential Evolution solution of Cajo J. F. Ter Braak, Stat Comput (2006)290 16:239-249. The samples of each chain at any given iteration are members291 of a population at one evolutionary generation.292 293 Each proposal is based on a scaled difference of two parameter vectors from294 the population, other than the vector being modified, and a random variate295 vector with the same length as the parameter vector. The random variates296 are scaled and then added to the scaled difference vector, in accordance297 with Ter Braak (2).298 """299 reportInterval = 1300 301 def _get_wiggle(self):302 """303 Implements gamma=1 every tenth iteration, per Ter Braak, p. 245.304 """305 if hasattr(self, '_wiggle'):306 return self._wiggle307 self._gammaCallCount = getattr(self, '_gammaCallCount', 0) + 1308 if self._gammaCallCount % 10:309 return 2.38 / s.sqrt(2*self.N_params)310 return 1.0311 def _set_wiggle(self, value):312 self._wiggle = value313 wiggle = property(_get_wiggle, _set_wiggle)314 315 def getXds(self):316 """317 Returns an array of offset vectors, one for the evolution of each318 population member in the next iteration, with no scaling.319 """320 Xds = s.empty((self.XL.shape[0], self.XL.shape[1]-1), s.float32)321 for m0 in xrange(self.N_chains):322 m12 = []323 while len(m12) < 2:324 r = random.randint(0, self.N_chains)325 if r not in m12:326 m12.append(r)327 Xds[m0,:] = self.XL[m12[1],:-1] - self.XL[m12[0],:-1]328 return Xds329 330 def iteration(self):331 """332 Does a DE-MCMC iteration.333 """334 Xds = self.getXds()335 return [336 self.metropolisHastings(m0, Xd)337 for m0, Xd in enumerate(Xds)]338 339 340 class PMC(MC_Base):341 """342 Population MCMC with per Cappe et al.343 """344 wiggle = 1.0345 chunkSize = 5000346 V = [1.0, 0.5, 0.1, 0.05, 0.001]347 137 348 138 def _get_queue(self): projects/AsynCluster/trunk/pypmc/sample.py
r131 r132 1 # Py Folio: Portfolio Performance Simulation1 # PyPMC: Population Monte Carlo implemented with Python and Twisted 2 2 # 3 # Copyright (C) 2007 by Edwin A. Suominen, http://www.eepatents.com3 # Copyright (C) 2007-2008 by Edwin A. Suominen, http://www.eepatents.com 4 4 # 5 5 # This program is free software; you can redistribute it and/or modify it under … … 83 83 I1 = s.greater(V, x[RI,0]).choose(RI, x[RI,1].astype(int)) 84 84 return I0[I1] 85 86 87 class TrapSampler(object):88 """89 """90 def __init__(self, X, Y):91 self.X, self.Y = X, Y92 self.W = s.diff(X) * (Y[:-1] + Y[1:])93 self.W /= sum(self.W)94 95 def trapSample(self, x0, y0, x1, y1):96 """97 The probability density function (pdf) for variates within a selected98 trapezoidal portion of the piece-wise approximated distribution curve99 is::100 101 ( (y1 - y0) )102 y = C ( (x - x0) --------- + y0 )103 ( (x1 -x0) )104 105 where C is a normalizing constant.106 107 The cumulative density function (cdf) for the trapezoidal pdf is108 based on the definite integral of the pdf::109 110 2 2111 (2 x x0 - x ) y1 + (x - 2 x x1) y0112 y = - -----------------------------------113 2 2114 (x1 - 2 x0 x1) y1 + x1 y0115 116 Solving for x yields the inverse cdf. With inverse transform sampling,117 y is set to a uniform random variate U(0,1).118 """119 N = len(x0)120 y = s.rand(N)121 x = x0 + (x1-x0)*y122 I = s.not_equal(y0, y1).nonzero()123 xI = -y0[I] + s.sqrt(y0[I]**2 + y[I]*(y1[I]**2 - y0[I]**2))124 xI *= (x1[I] - x0[I])/(y1[I] - y0[I])125 x[I] = xI + x0[I]126 return x127 128 def __call__(self, N):129 """130 Call me to get I{N} random variates.131 """132 I = resample(self.W, N)133 return self.trapSample(self.X[I], self.Y[I], self.X[I+1], self.Y[I+1])134
