Changeset 174
- Timestamp:
- 05/08/08 12:49:59 (8 months ago)
- Files:
-
- projects/AsynCluster/trunk/doc/svpmc/example/svpmc.conf (modified) (1 diff)
- projects/AsynCluster/trunk/svpmc/params.py (modified) (11 diffs)
- projects/AsynCluster/trunk/svpmc/pmc.py (modified) (14 diffs)
- projects/AsynCluster/trunk/svpmc/project.py (modified) (4 diffs)
- projects/AsynCluster/trunk/svpmc/test/test_params.py (modified) (6 diffs)
- projects/AsynCluster/trunk/svpmc/test/test_pmc.py (modified) (2 diffs)
- projects/AsynCluster/trunk/svpmc/test/test_project.py (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
projects/AsynCluster/trunk/doc/svpmc/example/svpmc.conf
r173 r174 22 22 ------------------------------------------------------------------------------- 23 23 Foreign Currency Returns over U.S. Dollar 24 25 26 27 Jumps 28 ------------------------------------------------------------------------------- 29 0.5, 0.1, 0.02, 0.004 30 24 31 25 32 projects/AsynCluster/trunk/svpmc/params.py
r173 r174 87 87 88 88 def __iter__(self): 89 self._ k = -189 self._iterator = iter(self._I) 90 90 return self 91 91 92 92 def next(self): 93 self._k += 1 94 if self._k < self._N: 95 return self(self._I[self._k]) 96 raise StopIteration 93 return self(self._iterator.next()) 97 94 98 95 def __getitem__(self, key): … … 153 150 supplied value. 154 151 155 If the value is an array (or L{FlexArray}) with the same shape as me ,156 each object's attribute value will be set to the value ofthe157 corresponding array element.152 If the value is an array (or L{FlexArray}) with the same shape as me 153 (not a list or tuple), each object's attribute value will be set to the 154 value of the corresponding array element. 158 155 """ 159 156 if name.startswith('_'): 160 157 object.__setattr__(self, name, value) 161 158 else: 162 if s.shape(value)== self.shape:159 if hasattr(value, 'shape') and value.shape == self.shape: 163 160 values = value.ravel() 164 161 else: … … 167 164 setattr(self._O[k], name, thisValue) 168 165 169 def call(self, methodName, *args ):166 def call(self, methodName, *args, **kw): 170 167 """ 171 168 For each of my objects, call the method specified by I{methodName} with 172 any further args and keywords supplied. For any argument that is an 173 array (or L{FlexArray}) with the same shape as me, only the 174 corresponding array element will be supplied as the argument to each 175 object's method call. 176 169 any further args and keywords supplied. 170 171 For any argument that is an array (or L{FlexArray}) with the same shape 172 as me (not a list or tuple), only the corresponding array element will 173 be supplied as the argument to each object's method call. You can force 174 an array argument to be supplied to the call in its entirety (not 175 element-by-element) even if its shape matches mine by including its 176 position index (with 0 as the first argument after the method name) in 177 a sequence defined via the keyword I{arrayArgs}. For example, you can 178 force the first argument (after the method name) to be an array 179 argument to all method calls with C{arrayArgs=(1,)}. 180 177 181 Returns the result of each call in an array of the same shape as my 178 182 object array. The results must all be numeric scalars. … … 183 187 """ 184 188 indicesOfArrayArgs = [ 185 k for k, arg in enumerate(args) if s.shape(arg) == self.shape] 189 k for k, arg in enumerate(args) 190 if hasattr(arg, 'shape') and \ 191 arg.shape == self.shape and \ 192 k not in kw.get('arrayArgs', [])] 193 # This is a shallow list copy 186 194 theseArgs = [arg for arg in args] 187 195 x = [] … … 275 283 """ 276 284 return float(self.distObj.pdf(x)) 277 285 278 286 def rvs(self): 279 287 """ … … 282 290 return self.distObj.rvs(1)[0] 283 291 284 def rJump(self, wiggle): 285 """ 286 Returns a value that jumps from zero by a random amount that, before 287 scaling by the supplied I{wiggle}, is drawn from a normal distribution 288 with a pre-scaled standard deviation that is set to approximate the 70% 289 probability width of my distribution. (That is the +/- 1 standard 290 deviation width of a normal distribution.) 291 """ 292 return wiggle * self.rdsObj.rvs(1)[0] 293 294 def pJump(self, x, wiggle): 295 """ 296 Returns the probability density of the supplied jump value I{x}, given 297 that it was generated by a call to my L{rJump} method with the 298 specified I{wiggle}. 299 300 For my Gaussian-PDF deviate generator, the probability density of the 301 jump is inversely proportional to the scaling to its standard deviation 302 imparted by I{wiggle}. Thus, the unit-normal PDF is divided by the 303 I{wiggle} in the result. 304 """ 305 return self.rdsObj.pdf(x/wiggle) / wiggle 292 def rJump(self): 293 """ 294 Returns a value that jumps from zero by a random amount that is drawn 295 from a normal distribution with a pre-scaled standard deviation that is 296 set to approximate the 70% probability width of my distribution. (That 297 is the +/- 1 standard deviation width of a normal distribution.) 298 """ 299 return self.rdsObj.rvs(1)[0] 300 301 def pJump(self, x, V, pV): 302 """ 303 Returns the probability density of the supplied jump value I{x} given a 304 mixture distribution of normals with standard deviations in the 305 supplied 1-D array I{V} of jump deviations and like-dimensioned array 306 I{pV} of mixture weights for normals having those deviations. 307 """ 308 densities = self.rdsObj.pdf(x/V) / V 309 return (pV * densities).sum() 306 310 307 311 … … 317 321 @ivar paramNames: A list of the parameter names in sequence. 318 322 323 @ivar V: A 1-D array of all the jump deviations that are used in 324 computing probability densities of jumps made via my prior objects. 325 319 326 """ 320 327 attempts = 30 321 328 typeChecking = True 322 329 323 def __init__(self, p, n ):330 def __init__(self, p, n, V): 324 331 self.p = p 325 332 self.n = n 333 self.V = V 326 334 327 335 def priorArray(self, paramName, *shape): … … 348 356 return paramContainer 349 357 350 # <-- TODO -> 351 def proposal(self, paramContainer, v, vMixInfo): 358 def proposal(self, paramContainer, vIndex, pV): 352 359 """ 353 360 Returns a new instance of L{ParameterContainer} with a random-walk 354 proposal based on the supplied I{paramContainer}, my priors, and the 355 supplied jump deviation I{v}. 361 proposal based on the supplied I{paramContainer}, my priors, the jump 362 deviation defined at the I{vIndex} position of my 1-D arrayt of jump 363 deviations, and a 1-D array I{pV} of mixture weights for the jump 364 deviations in the jump distribution. 356 365 357 366 The returned parameter container object will have a copy of the 358 supplied instance's latent parameter array I{z} and new values of I{Lp} and 359 I{Lj} corresponding to the proposal. 360 361 The value of I{Lj} is 362 """ 363 if wiggle < 0.0: 367 supplied instance's latent parameter array I{z} and new values of I{Lp} 368 and I{Lj} corresponding to the proposal. 369 370 The value of I{Lj} is computed for each parameter element from a jump 371 distribution that is a mixture of normal distributions. Each of the 372 normals has a standard deviation defined by one element of my jump 373 deviation array with a corresponding mixture weight in the supplied 374 I{pV} array. 375 """ 376 if vIndex < 0 or vIndex >= len(self.V): 377 raise ValueError("Invalid jump deviation index %d" % vIndex) 378 if len(pV) != len(self.V): 364 379 raise ValueError( 365 " Specify a positive wiggle value, '%s' obj invalid"\366 % str(wiggle))380 "Mixture weight sequence must have one element per "+\ 381 "jump deviation") 367 382 if self.typeChecking and \ 368 383 not isinstance(paramContainer, ParameterContainer): … … 370 385 "You must supply a ParameterContainer as the first "+\ 371 386 "argument, not a '%s'" % type(paramContainer)) 387 vJump = self.V[vIndex] 372 388 newVersion = ParameterContainer( 373 389 paramNames=self.paramNames, z=s.array(paramContainer.z).copy()) … … 376 392 priorFlexArray = getattr(self, name) 377 393 for attempt in xrange(self.attempts): 378 jumps = priorFlexArray.call('rJump', wiggle)394 jumps = vJump * priorFlexArray.call('rJump') 379 395 paramArray = getattr(paramContainer, name) + jumps 380 396 pPriors = priorFlexArray.call('pdf', paramArray) … … 385 401 return paramContainer 386 402 Lp += s.log(pPriors).sum() 387 Lj += s.log(priorFlexArray.call('pJump', jumps, wiggle)).sum() 403 pMixtures = priorFlexArray.call( 404 'pJump', jumps, self.V, pV, arrayArgs=(1,2)) 405 Lj += s.log(pMixtures).sum() 388 406 setattr(newVersion, name, paramArray) 389 407 newVersion.Lp = Lp projects/AsynCluster/trunk/svpmc/pmc.py
r173 r174 33 33 class Allocator(object): 34 34 """ 35 Construct me with a population size I{N} and a sequence I{V} of jump 36 deviations. 37 """ 38 def __init__(self, N, V): 39 if N < 2*len(V): 35 Construct me with a population size I{N} and an integer number of 36 allocation bins I{D}. 37 38 @ivar W: A 1-D array containing the fraction of population members, i.e., 39 the population weight, currently allocated to each bin. 40 41 @ivar R: A 1-D array containing the number of population members currently 42 allocated to each bin. 43 44 """ 45 def __init__(self, N, D): 46 if N < 2*D: 40 47 raise ValueError( 41 "Population size must be at least twice the number "+\ 42 "of jump deviations.") 48 "Population size must be at least twice the number of bins") 43 49 self.N = N 44 self.V = V 45 self.P = len(V) 50 self.D = D 46 51 self.rMin = max([2, int(round(0.01 * N))]) 47 self.rMax = N - self.rMin*(self. P-1)52 self.rMax = N - self.rMin*(self.D-1) 48 53 self.updateAllocations() 49 50 def allocations(self):51 """52 Returns a list of 2-tuples containing each jump deviation and the53 number of population members currently allocated to it.54 """55 result = []56 for k, v in enumerate(self.V):57 result.append((v, self.R[k]))58 return result59 54 60 55 def subsetIndex(self, k): 61 56 """ 62 57 Returns a subset index for population members that correspond to the 63 jump deviation for the supplied indexI{k}.58 bin indexed by I{k}. 64 59 """ 65 60 I = sampleWOR(self.Is, self.R[k]) … … 72 67 assembled results. 73 68 74 For each of my jump deviations, the method will yield the deviation 75 value, a 1-D array of indices for the subset of my population allocated 76 to that deviation value, and a fresh deferred already fired with 77 C{None}. You must add a callback to the deferred for each iteration 78 that returns a tuple of 1-D arrays, each being the same length as 79 the yielded index array. 69 For each of my bins, the method will yield the bin index, a 1-D array 70 of indices for the subset of my population allocated to that bin, and a 71 fresh deferred already fired with C{None}. You must add a callback to 72 the deferred for each iteration that returns a tuple of 1-D arrays, 73 each being the same length as the yielded index array. 80 74 81 75 Each returned array for each subset will be assembled back into a … … 100 94 raise ValueError( 101 95 "You must supply an empty list to hold your assembled results") 102 self.II = [None] * self. P96 self.II = [None] * self.D 103 97 for k in s.flipud(s.argsort(self.R)): 104 98 I = self.II[k] = self.subsetIndex(k) 105 99 d = defer.succeed(None) 106 yield self.V[k], I, d100 yield k, I, d 107 101 d.addCallback(gotResults, I) 108 102 109 103 def updateAllocations(self, I=None): 110 104 """ 111 Rescales my allocations based on the supplied 1-D array of resampling 105 Rescales my allocations based on the supplied 1-D array I{I} of 106 resampling indices. Allocations with lots of repeated indices were 107 highly successful and those with lots of missing indices were not. 108 112 109 Highly successful allocations will have a large portion of the next 113 110 allocation even if they last operated on only a few members of the 114 111 population. 112 113 If no index array is supplied, the allocations will be equalized. 115 114 """ 116 115 if I is None: 117 R = s.ones(self. P) / self.P116 R = s.ones(self.D) / self.D 118 117 elif hasattr(self, 'II'): 119 118 R = s.array( 120 [sum([x in self.II[k] for x in I]) for k in xrange(self. P)])119 [sum([x in self.II[k] for x in I]) for k in xrange(self.D)]) 121 120 R = R.astype(float) / R.sum() 122 121 else: … … 127 126 # Turn the scaled array into an array of subsample sizes, with a 128 127 # minimum size of two apiece. 129 R = s.clip(s.round_(self.N*R), self.rMin, self.rMax) .astype(int)128 R = s.clip(s.round_(self.N*R), self.rMin, self.rMax) 130 129 # Twiddle the biggest one as needed to keep sum = Number of members 131 130 R[s.argmax(R)] += self.N - sum(R) 132 # Replace the old list and subset index 133 self.R = R 131 # Replace the old allocation arrays and subset index 132 self.W = R / R.sum() 133 self.R = R.astype(int) 134 134 self.Is = s.arange(self.N) 135 135 … … 141 141 """ 142 142 chunkSize = 100 143 V = [0.5, 0.1, 0.02, 0.004]143 initialSigma = 0.05 144 144 145 145 def __init__(self, projectManager, socket=None): … … 149 149 self.queue = projectManager.queue 150 150 self.resampler = Resampler(logWeights=True) 151 self.allocator = Allocator(projectManager.m, len(projectManager.V)) 151 152 152 153 def initialPopulation(self, N): … … 163 164 return X 164 165 165 def proposals(self, X, v ):166 def proposals(self, X, vIndex): 166 167 """ 167 168 Returns a 1-D FlexArray of parameter containers resulting in proposals 168 169 from the supplied FlexArray of parameter containers I{X}, given the 169 specified jump deviation I{v}.170 jump deviation corresponding to the specified I{vIndex}. 170 171 171 172 Proposals with zero probability of occuring, given the parameters' … … 175 176 XP = params.FlexArray(len(X)) 176 177 for k, paramContainer in enumerate(X): 177 XP[k] = self.pm.priors.proposal(paramContainer, v) 178 XP[k] = self.pm.priors.proposal( 179 paramContainer, vIndex, self.allocator.W) 178 180 return XP 179 181 180 182 @defer.deferredGenerator 181 def weightedProposals(self, X, v ):183 def weightedProposals(self, X, vIndex): 182 184 """ 183 185 Returns a deferred that fires with a 1-D FlexArray of proposals from 184 186 the parameter containers in the supplied FlexArray I{X}, given the 185 specified proposal variance I{v}, along with a like-dimensioned array 186 of linear, fractional importance weights for the proposals. 187 specified proposal deviation index I{vIndex}, along with a 188 like-dimensioned array of linear, fractional importance weights for the 189 proposals. 187 190 188 191 The importance weights are to be combined with those from other calls … … 196 199 def weight(paramContainer): 197 200 if s.isfinite(paramContainer.Lx): 198 Lj = 199 L = paramContainer.Lx + paramContainer.Lp - Lj 201 L = paramContainer.Lx + paramContainer.Lp - paramContainer.Lj 200 202 print "-+"[int(L>-1000)], 201 203 else: … … 208 210 N = len(X) 209 211 W = s.empty(len(X)) 210 wfd = defer.waitForDeferred(self.queue.call(self.proposals, X, v)) 212 # TODO: Adapt sigma over iterations for optimum acceptance rate in 213 # volatility simulations 214 sigma = self.initialSigma 215 wfd = defer.waitForDeferred(self.queue.call(self.proposals, X, vIndex)) 211 216 yield wfd 212 217 XP = wfd.getResult() … … 215 220 # Here's where the vast majority of the CPU time is expended! 216 221 dList = [ 217 self.mm.likelihood(XP[k], v).addCallback(weight)222 self.mm.likelihood(XP[k], sigma).addCallback(weight) 218 223 for k in xrange(j, j+N_this)] 219 224 wfd = defer.waitForDeferred(defer.gatherResults(dList)) … … 242 247 yield wfd; wfd.getResult() 243 248 N_members = self.pm.m 244 allocator = Allocator(N_members, self.V)245 249 # Initialize some arrays 246 250 X = self.initialPopulation(N_members) … … 250 254 for i in xrange(N_iter): 251 255 print "\n%03d:" % (i+1,), 256 self.allocator.setAllocations(self.R) 252 257 dList, resultList = [], [] 253 self.mixInfo = allocator.allocations() 254 for v, I, d in allocator.assembler(resultList): 255 d.addCallback(lambda _: self.weightedProposals(X[I], v)) 258 for vIndex, I, d in self.allocator.assembler(resultList): 259 d.addCallback(lambda _: self.weightedProposals(X[I], vIndex)) 256 260 dList.append(d) 257 261 # Record the previous iteration's results while the nodes work on … … 269 273 I1 = self.resampler(W[I0], N_members) 270 274 X = XP[I0][I1] 271 allocator.updateAllocations(I)275 self.allocator.updateAllocations(I) 272 276 else: 273 277 # Nothing in the proposed population had any plausibility 274 allocator.updateAllocations()278 self.allocator.updateAllocations() 275 279 wfd = defer.waitForDeferred(self.pm.done()) 276 280 yield wfd projects/AsynCluster/trunk/svpmc/project.py
r173 r174 44 44 45 45 46 class CDF_Wrapper(object):47 """48 With python2.5, as of 2008-05-01, accessing a NetCDF variable object with a49 slice raises a 'Floating Point Exception'! So I am a wrapper for the poor50 vulnerable little thing...51 52 B{YAGNI Strikes Again!!!!}53 54 Just installed the latest development version (2.7.8) of Scientific from55 http://sourcesup.cru.fr/frs/download.php/1835/ScientificPython-2.7.8.tar.gz56 and problem solved.57 """58 class VariableDict_Wrapper(object):59 """60 I emulate the I{variables} dict-like object of an open NetCDF file61 object.62 """63 class Variable_Wrapper(object):64 """65 I emulate a single variable of an open NetCDF file object.66 """67 def __init__(self, var, *dims):68 self.var = var69 self.dims = dims70 71 def __getitem__(self, key):72 # TODO73 pass74 75 def __setitem__(self, key, value):76 print "SI", key, value77 78 79 def __init__(self, variables, dimensions):80 self._variables = {}81 self.variables = variables82 self.dimensions = dimensions83 84 def __getitem__(self, name):85 if name not in self._variables:86 var = self.variables[name]87 dims = [self.dimensions[dimName] for dimName in var.dimensions]88 self._variables[name] = self.Variable_Wrapper(var, *dims)89 return self._variables[name]90 91 92 def __init__(self, cdf):93 self.cdf = cdf94 self.variables = self.VariableDict_Wrapper(95 self.cdf.variables, self.cdf.dimensions)96 97 def __getattr__(self, name):98 return getattr(self.cdf, name)99 100 def __setattr__(self, name, value):101 if name not in ('cdf', 'variables'):102 setattr(self.cdf, name, value)103 else:104 object.__setattr__(self, name, value)105 106 107 46 class ProjectManager(object): 108 47 """ … … 127 66 @ivar paramNames: A list of the names of parameter arrays in the parameter 128 67 sequences used in the project. 68 69 @ivar V: A 1-D array of the jump deviations used by the D-kernel PMC 70 inference engine that will be running the project. 129 71 130 72 @ivar priors: An instance of L{params.PriorContainer} constructed in … … 144 86 self.m = m 145 87 self.iteration = 0 146 # Use the NullQueue for debugging88 #--- Use the NullQueue for debugging, thread queue normally ----------- 147 89 self.queue = NullQueue() 148 90 #self.queue = asynqueue.ThreadQueue(1) 91 #---------------------------------------------------------------------- 149 92 specDir = os.path.dirname(specFile) 150 93 self.tables = self._parseSpec(specFile) 94 # Jump Deviations 95 self.V = s.array([ 96 float(x) for x in self.tables['jumps'][0][0].split(',')]) 97 # Observations 151 98 tsData, seriesTitles = self._setupTimeSeries(specDir) 152 99 self.p, self.n = tsData.shape 100 # Parameters 153 101 paramTitles, dimensions = self._setupParams() 154 102 self.mgr = model.ModelManager(self, tsData) … … 258 206 titles, dimensions = [], [] 259 207 xcorrs = self.xcorrs = int(0.5*(self.p**2 + self.p)) 260 self.priors = params.PriorContainer(self.p, self.n )208 self.priors = params.PriorContainer(self.p, self.n, self.V) 261 209 for name, dims, title in self.tables['parameter']: 262 210 shape = parseDims(dims) projects/AsynCluster/trunk/svpmc/test/test_params.py
r171 r174 101 101 self.failUnlessEqual(x[1,k], expected) 102 102 103 def test_iterate(self): 104 x = self._make_stringArray(3, 4) 105 for j, xj in enumerate(x): 106 self.failUnlessEqual(xj.shape, (4,)) 107 for k in xrange(4): 108 self.failUnlessEqual(xj[k], "%d:%d" % (j, k)) 109 self.failUnlessEqual(j, 2) 110 103 111 104 112 class Test_FlexArray_ops(util.TestCase): … … 160 168 for k in xrange(4): 161 169 self.failUnlessEqual(self.x[j, k].c, 4*j+k) 170 171 def test_setattr_listNotArray(self): 172 x = params.FlexArray(12) 173 for j in xrange(12): 174 x[j] = self.Thingy(0) 175 x.c = range(12) 176 for j in xrange(12): 177 self.failUnlessEqual(x.c[j], range(12)) 162 178 163 179 def test_call_scalarArgs(self): … … 209 225 class Test_PriorContainer(util.TestCase): 210 226 MPC = util.Mock_ParameterContainer 227 V = s.array([1.0, 0.1, 0.01]) 211 228 212 229 def setUp(self): 213 self.priorContainer = params.PriorContainer(self.MPC.p, self.MPC.N) 230 self.priorContainer = params.PriorContainer( 231 self.MPC.p, self.MPC.N, self.V) 214 232 self.priorContainer.typeChecking = False 215 233 self.priorContainer.paramNames = self.MPC.paramNames … … 272 290 def test_proposal_basic(self): 273 291 old = util.Mock_ParameterContainer(0.0) 274 new = self.priorContainer.proposal(old, 1.0)292 new = self.priorContainer.proposal(old, 0, [1.0, 0.0, 0.0]) 275 293 return self._check_basic(new) 276 294 277 295 def test_proposal_dist(self): 278 296 N = 1000 279 sigma = 0.12280 297 paramArray = params.FlexArray(N) 281 298 print "\nRunning MCMC: " … … 283 300 #X = self.priorContainer.new() 284 301 for k in xrange(N): 285 XP = self.priorContainer.proposal(X, sigma)302 XP = self.priorContainer.proposal(X, 1, [0.0, 1.0, 0.0]) 286 303 if k == 0 or XP.Lp > X.Lp or s.log(s.rand()) < (XP.Lp - X.Lp): 287 304 print "+", … … 292 309 paramArray[k] = X 293 310 print "\n" 294 scale = 0.1 * s igma* s.arange(311 scale = 0.1 * self.V[1] * s.arange( 295 312 1, len(self.priorContainer.paramNames)+1) 296 313 return self._check_dist(paramArray, scale) projects/AsynCluster/trunk/svpmc/test/test_pmc.py
r173 r174 37 37 self.N = 100 38 38 self.V = [0.1, 0.01, 0.001] 39 self.allocator = pmc.Allocator(self.N, self.V)39 self.allocator = pmc.Allocator(self.N, len(self.V)) 40 40 41 41 def test_init(self): 42 42 self.failUnlessElementsEqual(self.allocator.R, [34, 33, 33]) 43 43 44 def test_allocations(self):45 self.failUnlessEqual(46 self.allocator.allocations(),47 [(0.1,34), (0.01,33), (0.001,33)])48 49 44 def test_subsetIndex(self): 50 45 R = self.allocator.R = [10, 20, 70] … … 73 68 X = s.arange(self.N) 74 69 dList, resultList = [], [] 75 for v, I, d in self.allocator.assembler(resultList):76 d.addCallback(lambda _: (X[I], X[I]+ v))70 for k, I, d in self.allocator.assembler(resultList): 71 d.addCallback(lambda _: (X[I], X[I]+self.V[k])) 77 72 dList.append(d) 78 73 return defer.DeferredList(dList).addCallback(check) projects/AsynCluster/trunk/svpmc/test/test_project.py
r173 r174 35 35 self.p = 3 36 36 self.n = 937 37 self.V = s.array([1.0, 0.1, 0.01]) 37 38 38 39 … … 55 56 elif name == 'parameter': 56 57 self.failUnlessEqual(len(rows), 8) 57 elif name != 'project': 58 elif name == 'jumps': 59 try: 60 jumps = [float(x) for x in rows[0][0].split(',')] 61 self.failUnlessEqual(len(jumps), 4) 62 except: 63 self.fail("Couldn't parse jumps sequence") 64 elif name not in ('project', 'jumps'): 58 65 self.failUnless(name in paramNames) 59 66
