reaction_output.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706
  1. import string
  2. import types
  3. try:
  4. import numpy
  5. except ImportError:
  6. numpy = None
  7. ####################
  8. ## Give an error if the counts in a reaction output file do not agree with
  9. ## those passed in. 'eps' specifies the tolerance with which each value is
  10. ## tested, and may be a tuple or list with one element for each column in the
  11. ## file, including the time column. 'header' specifies the header, which must
  12. ## be present if header is given, and must not be present if header is not
  13. ## given. 'times_vals' contains the expected counts, as a list of tuples, one
  14. ## tuple per row, and in each tuple, one floating point (or integer) value for
  15. ## each column including the time column.
  16. ##
  17. ## Example usage:
  18. ##
  19. ## assertCounts("output.txt", [(f*1e-6, 5) for f in range(0, 101)])
  20. ##
  21. ## Check a count file from a run with a timestep of 1e-6 that ran for
  22. ## 100 iterations, producing a constant value of 5 on each timestep, and
  23. ## had no header, as in the following REACTION_DATA_OUTPUT:
  24. ##
  25. ## REACTION_DATA_OUTPUT
  26. ## {
  27. ## STEP = 1e-6
  28. ## {5} => "output.txt"
  29. ## }
  30. ##
  31. ####################
  32. def assertCounts(fname, times_vals, header=None, eps=1e-8):
  33. try:
  34. got_contents = open(fname).read()
  35. except:
  36. assert False, "Expected reaction output file '%s' was not created" % fname
  37. # Rend file into tiny pieces
  38. lines = [l for l in got_contents.split('\n') if l != '']
  39. # Validate header
  40. if header != None:
  41. assert header == lines[0], "In reaction output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  42. lines = lines[1:]
  43. # Validate number of rows
  44. assert len(lines) == len(times_vals), "In reaction output file '%s', an incorrect number of data rows was found (%d instead of %d)" % (fname, len(lines), len(times_vals))
  45. # If a single epsilon was given, replicate it into a tuple
  46. if type(eps) == types.FloatType or type(eps) == types.IntType:
  47. eps = len(times_vals[0]) * (eps,)
  48. # Check each row's values
  49. for row in range(0, len(times_vals)):
  50. file_row = [float(f.strip()) for f in lines[row].split(' ') if f.strip() != '']
  51. tmpl_row = times_vals[row]
  52. # Make sure we don't have more templates for this row than we did for the first row!
  53. assert len(tmpl_row) == len(eps), "Internal error: For reaction output file '%s', data template row %d has incorrect number of columns (%d instead of %d)" % (fname, row, len(tmpl_row), len(eps))
  54. # Make sure we don't have more or less data for this row than we expect
  55. assert len(file_row) == len(eps), "In reaction output file '%s', data row %d has incorrect number of columns (%d instead of %d)" % (fname, row, len(file_row), len(eps))
  56. # Compare each datum
  57. for col in range(0, len(file_row)):
  58. assert abs(file_row[col] - tmpl_row[col]) < eps[col], "In reaction output file '%s', data row %d, column %d differs by more than epsilon from expected (%.15g instead of %.15g, eps=%.15g)" % (fname, row, col, file_row[col], tmpl_row[col], eps[col])
  59. class RequireCounts:
  60. def __init__(self, name, times_vals, header=None, eps=None):
  61. self.name = name
  62. self.times_vals = times_vals
  63. self.args = {}
  64. if header is not None:
  65. self.args["header"] = header
  66. if eps is not None:
  67. self.args["eps"] = eps
  68. def check(self):
  69. assertCounts(self.name, self.times_vals, **self.args)
  70. ####################
  71. ## Give an error if the counts in a reaction output file are not less than
  72. ## those passed in. 'eps' specifies the tolerance with which each value is
  73. ## tested, and may be a tuple or list with one element for each column in the
  74. ## file, including the time column. 'header' specifies the header, which must
  75. ## be present if header is given, and must not be present if header is not
  76. ## given. 'val' contains the count to compare. The first column in the output
  77. ## file is time column and is excluded from the comparison, all other columns
  78. ## are compared with 'val'.
  79. ##
  80. ##
  81. ## Example usage:
  82. ##
  83. ## assertCountsLessThan("output.txt", 5)
  84. ##
  85. ## Check a count file from a run with a timestep of 1e-6 that ran for
  86. ## 100 iterations, producing a value that is less than 5 on each
  87. ## timestep, and had no header, as in the following REACTION_DATA_OUTPUT:
  88. ##
  89. ## REACTION_DATA_OUTPUT
  90. ## {
  91. ## STEP = 1e-6
  92. ## {3} => "output.txt"
  93. ## }
  94. ##
  95. ####################
  96. def assertCountsLessThan(fname, val, header=None, eps=1e-8):
  97. try:
  98. got_contents = open(fname).read()
  99. except:
  100. assert False, "Expected reaction output file '%s' was not created" % fname
  101. # Rend file into tiny pieces
  102. lines = [l for l in got_contents.split('\n') if l != '']
  103. # Validate header
  104. if header != None:
  105. assert header == lines[0], "In reaction output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  106. lines = lines[1:]
  107. # If a single epsilon was given, replicate it into a tuple
  108. # if type(eps) == types.FloatType or type(eps) == types.IntType:
  109. # eps = len(lines[1]) * (eps,)
  110. # Check each row's values
  111. for row in range(0, len(lines)):
  112. file_row = [float(f.strip()) for f in lines[row].split(' ') if f.strip() != '']
  113. # Make sure we don't have more templates for this row than we did for the first row!
  114. # assert len(file_row) == len(eps), "Internal error: For reaction output file '%s', data template row %d has incorrect number of columns (%d instead of %d)" % (fname, row, len(tmpl_row), len(eps))
  115. # Make sure we don't have more or less data for this row than we expect
  116. # assert len(file_row) == len(eps), "In reaction output file '%s', data row %d has incorrect number of columns (%d instead of %d)" % (fname, row, len(file_row), len(eps))
  117. # Compare each datum
  118. for col in range(1, len(file_row)):
  119. assert abs(file_row[col] - val) < eps, "In reaction output file '%s', data row %d, column %d is not less than expected (%.15g instead of %.15g, eps=%.15g)" % (fname, row, col, file_row[col], val, eps)
  120. class RequireCountsLessThan:
  121. def __init__(self, name, val, header=None, eps=None):
  122. self.name = name
  123. self.val = val
  124. self.args = {}
  125. if header is not None:
  126. self.args["header"] = header
  127. if eps is not None:
  128. self.args["eps"] = eps
  129. def check(self):
  130. assertCountsLessThan(self.name, self.val, **self.args)
  131. ####################
  132. ## Check value in the output file. All columns should be positive,
  133. ## while time column is discarded. 'header' specifies the header, which must
  134. ## be present if header is given, and must not be present if header is not
  135. ## given.
  136. ##
  137. ## Example usage:
  138. ##
  139. ## assertCountsPositive("output.txt")
  140. ##
  141. ## Check a count file from a run with a timestep of 1e-6 that ran for
  142. ## 100 iterations and had no header, as in the following
  143. ## REACTION_DATA_OUTPUT:
  144. ##
  145. ## REACTION_DATA_OUTPUT
  146. ## {
  147. ## STEP = 1e-6
  148. ## {
  149. ## COUNT[A, box1]:"A",
  150. ## COUNT[B, box2]:"B",
  151. ## COUNT[C, box3]:"C"
  152. ## } => "output.txt"
  153. ## }
  154. ##
  155. ####################
  156. def assertCountsPositive(fname, header=None):
  157. try:
  158. got_contents = open(fname).read()
  159. except:
  160. assert False, "Expected reaction output file '%s' was not created" % fname
  161. # Rend file into tiny pieces
  162. lines = [l for l in got_contents.split('\n') if l != '']
  163. # Validate header
  164. if header != None:
  165. assert header == lines[0], "In reaction output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  166. start_row = 1
  167. lines = lines[1:]
  168. else:
  169. start_row = 0
  170. # Check each row's values
  171. for row in range(start_row, len(lines)):
  172. file_row = [float(f.strip()) for f in lines[row].split(' ') if f.strip() != '']
  173. # Compare each datum (time column is discarded)
  174. for col in range(1, len(file_row)):
  175. assert (file_row[col] > 0), "In reaction output file '%s', data row %d, column %d value %.15g is zero or negative." % (fname, row, col, file_row[col])
  176. class RequireCountsPositive:
  177. def __init__(self, name, header=None):
  178. self.name = name
  179. self.args = {}
  180. if header is not None:
  181. self.args["header"] = header
  182. def check(self):
  183. assertCountsPositive(self.name, **self.args)
  184. ####################
  185. ## Check value in the output file. All columns should be positive or zero.
  186. ## while time column is discarded. 'header' specifies the header, which must
  187. ## be present if header is given, and must not be present if header is not
  188. ## given.
  189. ##
  190. ## Example usage:
  191. ##
  192. ## assertCountsPositiveOrZero("output.txt")
  193. ##
  194. ## Check a count file from a run with a timestep of 1e-6 that ran for
  195. ## 100 iterations and had no header, as in the following
  196. ## REACTION_DATA_OUTPUT:
  197. ##
  198. ## REACTION_DATA_OUTPUT
  199. ## {
  200. ## STEP = 1e-6
  201. ## {
  202. ## COUNT[A, box1]:"A",
  203. ## COUNT[B, box2]:"B",
  204. ## COUNT[C, box3]:"C"
  205. ## } => "output.txt"
  206. ## }
  207. ##
  208. ####################
  209. def assertCountsPositiveOrZero(fname, header=None):
  210. try:
  211. got_contents = open(fname).read()
  212. except:
  213. assert False, "Expected reaction output file '%s' was not created" % fname
  214. # Rend file into tiny pieces
  215. lines = [l for l in got_contents.split('\n') if l != '']
  216. # Validate header
  217. if header != None:
  218. assert header == lines[0], "In reaction output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  219. start_row = 1
  220. lines = lines[1:]
  221. else:
  222. start_row = 0
  223. # Check each row's values
  224. for row in range(start_row, len(lines)):
  225. file_row = [float(f.strip()) for f in lines[row].split(' ') if f.strip() != '']
  226. # Compare each datum (time column is discarded)
  227. for col in range(1, len(file_row)):
  228. assert (file_row[col] >= 0), "In reaction output file '%s', data row %d, column %d value %.15g is negative." % (fname, row, col, file_row[col])
  229. class RequireCountsPositiveOrZero:
  230. def __init__(self, name, header=None):
  231. self.name = name
  232. self.args = {}
  233. if header is not None:
  234. self.args["header"] = header
  235. def check(self):
  236. assertCountsPositiveOrZero(self.name, **self.args)
  237. ####################
  238. ## Check values in the output file. Some columns should be positive.
  239. ## Some columns should be zeroes.
  240. ## The values in column 3 should be equal to the sum of values in
  241. ## columns 1 and 2 (indexed from 0).
  242. ## If 'header' is TRUE, we discard the first row in the files under
  243. ## consideration. Otherwise we consider the first
  244. ## row also. The time column is always discarded from consideration.
  245. ##
  246. ## Example usage:
  247. ##
  248. ## assertHitsCrossRelations("A_hits_C_cross.dat", header=None)
  249. ##
  250. ## Check a count file from a run that had no header,
  251. ## as in the following REACTION_DATA_OUTPUT:
  252. ##
  253. ## REACTION_DATA_OUTPUT
  254. ## {
  255. ## STEP = 1e-6
  256. ## {
  257. ## COUNT[A, world.box[r1], FRONT_HITS]: "A_fr_hits",
  258. ## COUNT[A, world.box[r1], BACK_HITS]: "A_back_hits",
  259. ## COUNT[A, world.box[r1], ALL_HITS]: "A_all_hits",
  260. ## COUNT[C, world.box[r1], FRONT_CROSSINGS]: "A_fr_cross",
  261. ## COUNT[C, world.box[r1], BACK_CROSSINGS]: "A_back_cross",
  262. ## COUNT[C, world.box[r1], ALL_CROSSINGS]: "A_all_cross"
  263. ## } => "A_hits_C_cross.dat"
  264. ## }
  265. ##
  266. ####################
  267. def assertHitsCrossRelations(fname, header=None):
  268. try:
  269. get_contents = open(fname).read()
  270. except:
  271. assert False, "Expected reaction output file '%s' was not created" % fname
  272. # Rend files into tiny pieces
  273. lines = [l for l in get_contents.split('\n') if l != '']
  274. # Validate header
  275. if header != None:
  276. assert header == lines[0], "In reaction output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  277. lines = lines[1:]
  278. # Check each row's values
  279. for row in range(0, len(lines)):
  280. file_row = [float(f.strip()) for f in lines[row].split(' ') if f.strip() != '']
  281. # Compare each datum
  282. assert (file_row[2] == 0), "In reaction output file '%s', data row %d, column 2 value %d is nonzero." % (fname, row, file_row[2])
  283. assert (file_row[3] == file_row[1] + file_row[2]), "In reaction output file '%s', data row %d, column 3 is not equal to the sum of columns 1 and 2." % (fname, row)
  284. if(row > 1):
  285. assert (file_row[1] > 0), "In reaction output file '%s', data row %d, column 1 value %d is zero." % (fname, row, file_row[1])
  286. assert (file_row[4] > 0), "In reaction output file '%s', data row %d, column 4 value %d is zero." % (fname, row, file_row[4])
  287. assert (file_row[5] > 0), "In reaction output file '%s', data row %d, column 5 value %d is zero." % (fname, row, file_row[5])
  288. assert (file_row[6] > 0), "In reaction output file '%s', data row %d, column 6 value %d is zero." % (fname, row, file_row[6])
  289. class RequireHitsCrossRelations:
  290. def __init__(self, name, header=None):
  291. self.name = name
  292. self.args = {}
  293. if header is not None:
  294. self.args["header"] = header
  295. def check(self):
  296. assertHitsCrossRelations(self.name, **self.args)
  297. ####################
  298. ## Check numeric constraints on reaction data counts.
  299. ##
  300. ## What this checks is a linear constraint between the various columns in a
  301. ## particular row of reaction data output. For instance, if the simulation has a
  302. ## molecule which undergoes a reversible unimolecular reaction, and is
  303. ## otherwise inert, and the simulation releases 900 molecules divided between
  304. ## the two types at the beginning, and writes their counts to the first two
  305. ## columns of a reaction output file, the constraint 'C1 + C2 == 900' could be
  306. ## checked.
  307. ##
  308. ## Example usage:
  309. ##
  310. ## assertCountConstraints("output.txt", constraints=[(1, 1)], totals=[900])
  311. ##
  312. ## Essentially, this checks if the product of the constraints matrix with each
  313. ## row vector in the output file (excluding the time column) is equal to the
  314. ## totals vector. In the simple case, constraints is a vector, and totals is a
  315. ## scalar. min_time and max_time may also be specified to limit the comparison
  316. ## to a certain time interval.
  317. ####################
  318. def assertCountConstraints(fname, constraints=None, totals=None, min_time=None, max_time=None, header=None, num_vals=None, minimums=None, maximums=None):
  319. try:
  320. file = open(fname)
  321. except:
  322. assert False, "Expected reaction output file '%s' was not created" % fname
  323. try:
  324. # Validate header
  325. if header is not None and header != False:
  326. got_header = file.readline()
  327. if header == True:
  328. assert got_header != '', "In reaction output file '%s', expected at least a header, but none was found" % fname
  329. else:
  330. assert got_header.strip() == header, "In reaction output file '%s', the header is incorrect (expected '%s', got '%s')" % (fname, header, got_header.strip())
  331. # Validate constraints
  332. if constraints is not None or totals != None:
  333. got_vals = 0
  334. if numpy is not None:
  335. if len(constraints) != 0:
  336. m_constraint = numpy.transpose(numpy.matrix(constraints))
  337. else:
  338. m_constraint = None
  339. for line in file:
  340. cols = line.strip().split()
  341. time = float(cols[0])
  342. if min_time != None and min_time > time:
  343. continue
  344. if max_time != None and max_time < time:
  345. break
  346. got_vals += 1
  347. counts = [int(x) for x in cols[1:]]
  348. if constraints == None:
  349. constraints = [(1,) * len(counts)]
  350. if totals == None:
  351. totals = (0,) * len(constraints)
  352. if minimums == None:
  353. minimums = (0,) * len(counts)
  354. if maximums == None:
  355. maximums = (1e300,) * len(counts)
  356. for c in range(0, len(counts)):
  357. assert counts[c] >= minimums[c], "In reaction output file '%s', at time %s, value (%g) is less than minimum value (%g)" % (fname, cols[0], counts[c], minimums[c])
  358. assert counts[c] <= maximums[c], "In reaction output file '%s', at time %s, value (%g) is greater than maximum value (%g)" % (fname, cols[0], counts[c], maximums[c])
  359. # XXX: We might be able to do this faster as a single matrix multiply
  360. if numpy is not None:
  361. if m_constraint is not None:
  362. m_count = numpy.matrix(counts)
  363. m_result = m_count * m_constraint - totals
  364. assert (m_result == 0).all(), "In reaction output file '%s', at time %s, a constraint is not satisfied" % (fname, cols[0])
  365. else:
  366. for c in range(0, len(constraints)):
  367. val = sum(map(lambda x, y: x*y, constraints[c], counts))
  368. assert val == totals[c], "In reaction output file '%s', at time %s, constraint %s*%s == %d is not satisfied" % (fname, cols[0], str(constraints[c]), str(counts), totals[c])
  369. if num_vals is not None:
  370. assert num_vals == got_vals, "In reaction output file '%s', expected %d rows within the selected time window, but only found %d" % (fname, num_vals, got_vals)
  371. finally:
  372. file.close()
  373. class RequireCountConstraints:
  374. def __init__(self, name, constraints=None, totals=None, min_time=None, max_time=None, header=None, num_vals=None, minimums=None, maximums=None):
  375. self.name = name
  376. self.args = {}
  377. if constraints is not None:
  378. self.args["constraints"] = constraints
  379. if totals is not None:
  380. self.args["totals"] = totals
  381. if min_time is not None:
  382. self.args["min_time"] = min_time
  383. if max_time is not None:
  384. self.args["max_time"] = max_time
  385. if header is not None:
  386. self.args["header"] = header
  387. if num_vals is not None:
  388. self.args["num_vals"] = num_vals
  389. if minimums is not None:
  390. self.args["minimums"] = minimums
  391. if maximums is not None:
  392. self.args["maximums"] = maximums
  393. def check(self):
  394. assertCountConstraints(self.name, **self.args)
  395. ####################
  396. ## Check numeric equilibrium of reaction data counts.
  397. ##
  398. ## This check will average a particular range of rows from a reaction output
  399. ## file and compare the computed mean to an expected mean, with a given tolerance.
  400. ##
  401. ## TODO: add a check for the std dev?
  402. ##
  403. ## Example usage:
  404. ##
  405. ## assertCountEquilibrium("output.txt", values=[590.5], tolerances=[5.0], min_time=1e-3)
  406. ##
  407. ####################
  408. def assertCountEquilibrium(fname, values=None, tolerances=None, min_time=None, max_time=None, header=None, num_vals=None):
  409. try:
  410. file = open(fname)
  411. except:
  412. assert False, "Expected reaction output file '%s' was not created" % fname
  413. try:
  414. # Validate header
  415. if header is not None and header != False:
  416. got_header = file.readline()
  417. if header == True:
  418. assert got_header != '', "In reaction output file '%s', expected at least a header, but none was found" % fname
  419. else:
  420. assert got_header.strip() == header, "In reaction output file '%s', the header is incorrect (expected '%s', got '%s')" % (fname, header, got_header.strip())
  421. # account for each line
  422. if values is not None and tolerances is not None:
  423. sums = list((0.0,) * len(values))
  424. count = 0
  425. for line in file:
  426. cols = [x.strip() for x in line.split()]
  427. if min_time is not None and float(cols[0]) < min_time:
  428. continue
  429. if max_time is not None and float(cols[0]) > max_time:
  430. continue
  431. data = [float(x) for x in cols[1:]]
  432. assert len(sums) <= len(data), "In reaction output file '%s', expected at least %d columns, but found only %d" % (fname, len(sums) + 1, len(data) + 1)
  433. for i in range(0, len(sums)):
  434. sums[i] += data[i]
  435. count += 1
  436. # Compute the mean
  437. if num_vals is not None:
  438. assert count == num_vals, "In reaction output file '%s', expected %d matching data rows, but found %d" % (fname, num_vals, count)
  439. assert count != 0, "In reaction output file '%s', found no valid data rows" % fname
  440. avgs = [x / float(count) for x in sums]
  441. for i in range(0, len(avgs)):
  442. assert avgs[i] >= values[i] - tolerances[i] and avgs[i] <= values[i] + tolerances[i], "In reaction output file '%s', expected column %d to have a mean between %.15g and %.15g, but got a mean of %.15g" % (fname, i+1, values[i] - tolerances[i], values[i] + tolerances[i], avgs[i])
  443. finally:
  444. file.close()
  445. class RequireCountEquilibrium:
  446. def __init__(self, name, values, tolerances, min_time=None, max_time=None, header=None, num_vals=None):
  447. self.name = name
  448. self.values = values
  449. self.tolerances = tolerances
  450. self.args = {}
  451. if min_time is not None:
  452. self.args["min_time"] = min_time
  453. if max_time is not None:
  454. self.args["max_time"] = max_time
  455. if header is not None:
  456. self.args["header"] = header
  457. if num_vals is not None:
  458. self.args["num_vals"] = num_vals
  459. def check(self):
  460. assertCountEquilibrium(self.name, self.values, self.tolerances, **self.args)
  461. ####################
  462. ## Check numeric rates of occurrence of reactions.
  463. ##
  464. ## This check will check that the average rate of occurrence of a counted
  465. ## reaction in a count file is within some tolerance of a specified rate.
  466. ##
  467. ## Essentially, the computation we are doing is:
  468. ##
  469. ## count_column / (time_column - base_time)
  470. ##
  471. ## which should be within "tolerance" of the specified value at all times.
  472. ##
  473. ## Example usage:
  474. ##
  475. ## assertCountRxnRate("output.txt", values=[0.1], tolerances=[0.15], min_time=1e-3, base_time=0.0)
  476. ##
  477. ####################
  478. def assertCountRxnRate(fname, values, tolerances, min_time=None, max_time=None, base_time=0.0, header=None):
  479. try:
  480. file = open(fname)
  481. except:
  482. assert False, "Expected reaction output file '%s' was not created" % fname
  483. try:
  484. # Validate header
  485. if header is not None and header != False:
  486. got_header = file.readline()
  487. if header == True:
  488. assert got_header != '', "In reaction output file '%s', expected at least a header, but none was found" % fname
  489. else:
  490. assert got_header.strip() == header, "In reaction output file '%s', the header is incorrect (expected '%s', got '%s')" % (fname, header, got_header.strip())
  491. # account for each line
  492. for line in file:
  493. cols = [x.strip() for x in line.split()]
  494. this_time = float(cols[0])
  495. if min_time is not None and this_time < min_time:
  496. continue
  497. if max_time is not None and this_time > max_time:
  498. continue
  499. data = [float(x) / (this_time - base_time) for x in cols[1:]]
  500. assert len(values) <= len(data), "In reaction output file '%s', expected at least %d columns, but found only %d" % (fname, len(values) + 1, len(data) + 1)
  501. for i in range(len(data)):
  502. assert data[i] >= values[i] - tolerances[i] and data[i] <= values[i] + tolerances[i], "In reaction output file '%s', at time %g, value %g in column %d is outside of tolerance" % (fname, this_time, data[i], i+1)
  503. finally:
  504. file.close()
  505. class RequireCountRxnRate:
  506. def __init__(self, name, values, tolerances, min_time=None, max_time=None, base_time=None, header=None):
  507. self.name = name
  508. self.values = values
  509. self.tolerances = tolerances
  510. self.args = {}
  511. if min_time is not None:
  512. self.args["min_time"] = min_time
  513. if max_time is not None:
  514. self.args["max_time"] = max_time
  515. if base_time is not None:
  516. self.args["base_time"] = base_time
  517. if header is not None:
  518. self.args["header"] = header
  519. def check(self):
  520. assertCountRxnRate(self.name, self.values, self.tolerances, **self.args)
  521. ####################
  522. ## Give an error if the specified trigger file is invalid, according to the
  523. ## specified parameters.
  524. ##
  525. ## Parameters:
  526. ## fname - the filename to check
  527. ## data_cols - number of data columns (0 for reaction, 1 for hits, 2
  528. ## for mol counts)
  529. ## exact_time - True if exact time is enabled, false otherwise
  530. ## header - text of header to expect, or None to expect no header
  531. ## event_titles - list of event titles (trigger labels), or None to expect
  532. ## all trigger labels to be empty. Any trigger whose label
  533. ## does not match at least one of the items in the list is
  534. ## considered an error.
  535. ## itertime - length of an iteration for checking the exact time
  536. ## against the iteration time, if exact_time == True
  537. ## [xyz]range - 2-tuples giving the min and max value for x, y, or z, or
  538. ## None to skip checking the trigger location
  539. ##
  540. ## Example usage:
  541. ##
  542. ## assertValidTriggerOutput("triggers.txt", 2, True, xrange=(-1, 1), yrange=(-1, 1), zrange=(-1, 1))
  543. ##
  544. ## Check a molecule count trigger file with an exact time column,
  545. ## assuming the default time step of 1e-6, and assuming that the
  546. ## molecule triggers are restricted to a 2x2x2 micron box centered at
  547. ## the origin. All events should be unlabelled.
  548. ####################
  549. def assertValidTriggerOutput(fname, data_cols, exact_time=True, header=None, event_titles=None, itertime=1e-6, xrange=None, yrange=None, zrange=None):
  550. try:
  551. got_contents = open(fname).read()
  552. except:
  553. assert False, "Expected trigger output file '%s' was not created" % fname
  554. # Rend file into tiny pieces
  555. lines = [l for l in got_contents.split('\n') if l != '']
  556. # Validate header
  557. if header != None:
  558. assert header == lines[0], "In trigger output file '%s', the header is incorrect ('%s' instead of '%s')" % (fname, header, lines[0])
  559. lines = lines[1:]
  560. # Compute column offsets
  561. total_cols = 1 + 3 + data_cols
  562. first_data = 4
  563. location = 1
  564. # Insert exact time column, if appropriate
  565. if exact_time:
  566. total_cols += 1
  567. first_data += 1
  568. location += 1
  569. # Process each row
  570. for row in range(0, len(lines)):
  571. # Tokenize row
  572. cols = lines[row].split(' ', total_cols)
  573. # Check column count
  574. assert len(cols) >= total_cols, "In trigger output file '%s', output row %d has incorrect number of columns (%d instead of %d)" % (fname, row, len(cols), total_cols)
  575. # Validate trigger label
  576. if event_titles is None:
  577. assert cols[-1] == '', "In trigger output file '%s', output row %d has an unexpected event label ('%s' instead of nothing)" % (fname, row, cols[-1])
  578. else:
  579. assert event_titles.count(cols[-1]) > 0, "In trigger output file '%s', output row %d has an unexpected event label ('%s' instead of one of {%s})" % (fname, row, cols[-1], string.join(event_titles, ','))
  580. # Validate exact time column
  581. if exact_time:
  582. assert float(cols[0]) <= float(cols[1]), "In trigger output file '%s', row %d, exact time precedes iteration time (%s versus %s)" % (fname, row, cols[1], cols[0])
  583. assert float(cols[0]) + itertime > float(cols[1]), "In trigger output file '%s', row %d, exact time doesn't match given iteration time (%s versus %s-%.15g)" % (fname, row, cols[1], cols[0], float(cols[0]) + itertime)
  584. # Validate data columns
  585. if data_cols > 0:
  586. assert cols[first_data] == "0" or cols[first_data] == "-1" or cols[first_data] == "1", "In trigger output file '%s', row %d, an invalid orientation was found (%s, rather than one of {-1,0,1})" % (fname, row, cols[first_data])
  587. if data_cols > 1:
  588. assert cols[first_data+1] == "-1" or cols[first_data+1] == "1", "In trigger output file '%s', row %d, an invalid count was found (%s, rather than one of {-1,1})" % (fname, row, cols[first_data+1])
  589. # Validate location
  590. if xrange != None:
  591. x = float(cols[location])
  592. assert x >= xrange[0] and x <= xrange[1], "In trigger output file '%s', row %d, an out-of-bounds event was found (x=%.15g, instead of %.15g ... %.15g)" % (fname, row, x, xrange[0], xrange[1])
  593. if yrange != None:
  594. y = float(cols[location])
  595. assert y >= yrange[0] and y <= yrange[1], "In trigger output file '%s', row %d, an out-of-bounds event was found (y=%.15g, instead of %.15g ... %.15g)" % (fname, row, y, yrange[0], yrange[1])
  596. if zrange != None:
  597. z = float(cols[location])
  598. assert z >= zrange[0] and z <= zrange[1], "In trigger output file '%s', row %d, an out-of-bounds event was found (z=%.15g, instead of %.15g ... %.15g)" % (fname, row, z, zrange[0], zrange[1])
  599. class RequireValidTriggerOutput:
  600. def __init__(self, name, data_cols, exact_time=False, header=None, event_titles=None, itertime=None, xrange=None, yrange=None, zrange=None):
  601. self.name = name
  602. self.data_cols = data_cols
  603. self.args = {}
  604. if exact_time is not None:
  605. self.args["exact_time"] = exact_time
  606. if header is not None:
  607. self.args["header"] = header
  608. if event_titles is not None:
  609. self.args["event_titles"] = event_titles
  610. if itertime is not None:
  611. self.args["itertime"] = itertime
  612. if xrange is not None:
  613. self.args["xrange"] = xrange
  614. if yrange is not None:
  615. self.args["yrange"] = yrange
  616. if zrange is not None:
  617. self.args["zrange"] = zrange
  618. def check(self):
  619. assertValidTriggerOutput(self.name, self.data_cols, **self.args)