CYCAMORE
test_regression.py
Go to the documentation of this file.
1 #! /usr/bin/env python
2 
3 import os
4 import platform
5 
6 import tables
7 import uuid
8 import sqlite3
9 import numpy as np
10 from numpy.testing import assert_array_almost_equal
11 from numpy.testing import assert_almost_equal
12 from nose.tools import assert_equal, assert_true
13 import helper
14 from helper import check_cmd, run_cyclus, table_exist
15 
16 class TestRegression(object):
17  """A base class for all regression tests. A derived class is required for
18  each new input file to be tested. Each derived class *must* declare an `inf`
19  member in their `__init__` function that points to the input file to be
20  tested, e.g., `self.inf_ = ./path/to/my/input_file.xml. See below for
21  examples.
22  """
23  def __init__(self, *args, **kwargs):
24  self.ext = '.sqlite'
25  self.outf = str(uuid.uuid4()) + self.ext
26  self.inf = None
27 
28  def setUp(self):
29  if not self.inf:
30  raise TypeError(("self.inf must be set in derived classes "
31  "to run regression tests."))
32  run_cyclus("cyclus", os.getcwd(), self.inf, self.outf)
33 
34  # Get specific tables and columns
35  if self.ext == '.h5':
36  with tables.open_file(self.outf, mode="r") as f:
37  # Get specific tables and columns
38  self.agent_entry = f.get_node("/AgentEntry")[:]
39  self.agent_exit = f.get_node("/AgentExit")[:] \
40  if "/AgentExit" in f \
41  else None
42  self.enrichments = f.get_node("/Enrichments")[:] \
43  if "/Enrichments" in f \
44  else None
45  self.resources = f.get_node("/Resources")[:]
46  self.transactions = f.get_node("/Transactions")[:]
47  self.compositions = f.get_node("/Compositions")[:]
48  self.info = f.get_node("/Info")[:]
49  self.rsrc_qtys = {
50  x["ResourceId"]: x["Quantity"] for x in self.resources}
51  else:
52  self.conn = sqlite3.connect(self.outf)
53  self.conn.row_factory = sqlite3.Row
54  self.cur = self.conn.cursor()
55  exc = self.cur.execute
56  self.agent_entry = exc('SELECT * FROM AgentEntry').fetchall()
57  self.agent_exit = exc('SELECT * FROM AgentExit').fetchall() \
58  if len(exc(
59  ("SELECT * FROM sqlite_master WHERE "
60  "type='table' AND name='AgentExit'")).fetchall()) > 0 \
61  else None
62  self.enrichments = exc('SELECT * FROM Enrichments').fetchall() \
63  if len(exc(
64  ("SELECT * FROM sqlite_master WHERE "
65  "type='table' AND name='Enrichments'")).fetchall()) > 0 \
66  else None
67  self.resources = exc('SELECT * FROM Resources').fetchall()
68  self.transactions = exc('SELECT * FROM Transactions').fetchall()
69  self.compositions = exc('SELECT * FROM Compositions').fetchall()
70  self.info = exc('SELECT * FROM Info').fetchall()
71  self.rsrc_qtys = {
72  x["ResourceId"]: x["Quantity"] for x in self.resources}
73 
74  def find_ids(self, spec, a, spec_col="Spec", id_col="AgentId"):
75  if self.ext == '.h5':
76  return helper.find_ids(spec, a[spec_col], a[id_col])
77  else:
78  return [x[id_col] for x in a if x[spec_col] == spec]
79 
80  def to_ary(self, a, k):
81  if self.ext == '.sqlite':
82  return np.array([x[k] for x in a])
83  else:
84  return a[k]
85 
86  def tearDown(self):
87  if self.ext == '.sqlite':
88  self.conn.close()
89  if os.path.isfile(self.outf):
90  print("removing {0}".format(self.outf))
91  os.remove(self.outf)
92 
94  """This class tests the 1_Enrichment_2_Reactor.xml file related to the
95  Cyclus Physor 2014 publication. The number of key facilities, the enrichment
96  values, and the transactions to each reactor are tested.
97  """
98  def __init__(self, *args, **kwargs):
99  super(_PhysorEnrichment, self).__init__(*args, **kwargs)
100 
101  def setUp(self):
102  super(_PhysorEnrichment, self).setUp()
103  tbl = self.agent_entry
104  self.rx_id = self.find_ids(":cycamore:Reactor", tbl)
105  self.enr_id = self.find_ids(":cycamore:Enrichment", tbl)
106 
107  def test_deploy(self):
108  assert_equal(len(self.rx_id), 2)
109  assert_equal(len(self.enr_id), 1)
110 
111  def test_swu(self):
112  enr = self.enrichments
113  # this can be updated if/when we can call into the cyclus::toolkit's
114  # enrichment module from python
115  # with old BatchReactor: exp = [6.9, 10, 4.14, 6.9]
116  exp = [6.9, 10., 4.14, 6.9]
117  obs = [np.sum(self.to_ary(enr, "SWU")[
118  self.to_ary(enr, "Time") == t]) for t in range(4)]
119  assert_array_almost_equal(exp, obs, decimal=2)
120 
121  def test_nu(self):
122  enr = self.enrichments
123  # this can be updated if/when we can call into the cyclus::toolkit's
124  # enrichment module from python
125 
126  # with old BatchReactor: exp = [13.03, 16.54, 7.83, 13.03]
127  exp = [13.03, 16.55, 7.82, 13.03]
128  obs = [np.sum(self.to_ary(enr, "Natural_Uranium")[
129  self.to_ary(enr, "Time") == t]) for t in range(4)]
130  assert_array_almost_equal(exp, obs, decimal=2)
131 
132  def test_xactions1(self):
133  # reactor 1 transactions
134  exp = [1, 1, 1, 1]
135  txs = [0, 0, 0, 0]
136  for tx in self.transactions:
137  if tx['ReceiverId'] == self.rx_id[0]:
138  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
139 
140  msg = "Testing that first reactor gets less than it wants."
141  assert_array_almost_equal(exp, txs, decimal=2, err_msg=msg)
142 
143  def test_xactions2(self):
144  # reactor 2 transactions
145  exp = [1, 0.8, 0.2, 1]
146  txs = [0, 0, 0, 0]
147  for tx in self.transactions:
148  if tx['ReceiverId'] == self.rx_id[1]:
149  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
150 
151  msg = "Testing that second reactor gets what it wants."
152  assert_array_almost_equal(exp, txs, decimal=2, err_msg=msg)
153 
155  def __init__(self, *args, **kwargs):
156  super(TestCBCPhysorEnrichment, self).__init__(*args, **kwargs)
157  self.inf = "../input/physor/1_Enrichment_2_Reactor.xml"
158 
160  def __init__(self, *args, **kwargs):
161  super(TestGreedyPhysorEnrichment, self).__init__(*args, **kwargs)
162  self.inf = "../input/physor/greedy_1_Enrichment_2_Reactor.xml"
163 
165  """This class tests the 2_Sources_3_Reactor.xml file related to the Cyclus
166  Physor 2014 publication. Reactor deployment and transactions between
167  suppliers and reactors are tested.
168  """
169  def __init__(self, *args, **kwargs):
170  super(_PhysorSources, self).__init__(*args, **kwargs)
171 
172  def setUp(self):
173  super(_PhysorSources, self).setUp()
174 
175  # identify each reactor and supplier by id
176  tbl = self.agent_entry
177  rx_id = self.find_ids(":cycamore:Reactor", tbl)
178  self.r1, self.r2, self.r3 = tuple(rx_id)
179  s_id = self.find_ids(":cycamore:Source", tbl)
180  self.smox = self.transactions[0]["SenderId"]
181  s_id.remove(self.smox)
182  self.suox = s_id[0]
183 
185  depl_time = {x["AgentId"]: x["EnterTime"] for x in self.agent_entry}
186 
187  assert_equal(depl_time[self.r1], 1)
188  assert_equal(depl_time[self.r2], 2)
189  assert_equal(depl_time[self.r3], 3)
190 
192  mox_exp = [0, 1, 1, 1, 0]
193  txs = [0, 0, 0, 0, 0]
194  for tx in self.transactions:
195  if tx['ReceiverId'] == self.r1 and tx['SenderId'] == self.smox:
196  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
197  assert_array_almost_equal(mox_exp, txs)
198 
199  uox_exp = [0, 0, 0, 0, 1]
200  txs = [0, 0, 0, 0, 0]
201  for tx in self.transactions:
202  if tx['ReceiverId'] == self.r1 and tx['SenderId'] == self.suox:
203  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
204  assert_array_almost_equal(uox_exp, txs)
205 
207  mox_exp = [0, 0, 1, 1, 1]
208  txs = [0, 0, 0, 0, 0]
209  for tx in self.transactions:
210  if tx['ReceiverId'] == self.r2 and tx['SenderId'] == self.smox:
211  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
212  assert_array_almost_equal(mox_exp, txs)
213 
214  uox_exp = [0, 0, 0, 0, 0]
215  txs = [0, 0, 0, 0, 0]
216  for tx in self.transactions:
217  if tx['ReceiverId'] == self.r2 and tx['SenderId'] == self.suox:
218  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
219  assert_array_almost_equal(uox_exp, txs)
220 
222  mox_exp = [0, 0, 0, 0.5, 1]
223  txs = [0, 0, 0, 0, 0]
224  for tx in self.transactions:
225  if tx['ReceiverId'] == self.r3 and tx['SenderId'] == self.smox:
226  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
227  assert_array_almost_equal(mox_exp, txs)
228 
229  uox_exp = [0, 0, 0, 0.5, 0]
230  txs = [0, 0, 0, 0, 0]
231  for tx in self.transactions:
232  if tx['ReceiverId'] == self.r3 and tx['SenderId'] == self.suox:
233  txs[tx['Time']] += self.rsrc_qtys[tx['ResourceId']]
234  assert_array_almost_equal(uox_exp, txs)
235 
237  def __init__(self, *args, **kwargs):
238  super(TestCBCPhysorSources, self).__init__(*args, **kwargs)
239  self.inf = "../input/physor/2_Sources_3_Reactors.xml"
240 
242  def __init__(self, *args, **kwargs):
243  super(TestGreedyPhysorSources, self).__init__(*args, **kwargs)
244  self.inf = "../input/physor/greedy_2_Sources_3_Reactors.xml"
245 
247  """Tests dynamic capacity restraints involving changes in the number of
248  source and sink facilities.
249 
250  A source facility is expected to offer a commodity of amount 1,
251  and a sink facility is expected to request for a commodity of amount 1.
252  Therefore, number of facilities correspond to the amounts of offers
253  and requests.
254 
255  At time step 1, 3 source facilities and 2 sink facilities are deployed, and
256  at time step 2, additional 2 sink facilities are deployed. After time
257  step 2, the older 2 sink facilities are decommissioned.
258  According to this deployment schedule, at time step 1, only 2 transactions
259  are expected, the number of sink facilities being the constraint; whereas,
260  at time step 2, only 3 transactions are expected, the number of source
261  facilities being the constraint. At time step 3, after decommissioning 2
262  older sink facilities, the remaining number of sink facilities becomes
263  the constraint, resulting in the same transaction amount as in time step 1.
264  """
265  def __init__(self, *args, **kwargs):
266  super(TestDynamicCapacitated, self).__init__(*args, **kwargs)
267  self.inf = "./input/dynamic_capacitated.xml"
268 
269  def setUp(self):
270  super(TestDynamicCapacitated, self).setUp()
271 
272  # Find agent ids of source and sink facilities
273  self.agent_ids = self.to_ary(self.agent_entry, "AgentId")
274  self.agent_impl = self.to_ary(self.agent_entry, "Spec")
275  self.depl_time = self.to_ary(self.agent_entry, "EnterTime")
276  self.exit_time = self.to_ary(self.agent_exit, "ExitTime")
277  self.exit_ids = self.to_ary(self.agent_exit, "AgentId")
278  self.source_id = self.find_ids(":cycamore:Source", self.agent_entry)
279  self.sink_id = self.find_ids(":cycamore:Sink", self.agent_entry)
280 
281  # Check transactions
282  self.sender_ids = self.to_ary(self.transactions, "SenderId")
283  self.receiver_ids = self.to_ary(self.transactions, "ReceiverId")
284  self.trans_time = self.to_ary(self.transactions, "Time")
285  self.trans_resource = self.to_ary(self.transactions, "ResourceId")
286 
287  # Track transacted resources
288  self.resource_ids = self.to_ary(self.resources, "ResourceId")
289  self.quantities = self.to_ary(self.resources, "Quantity")
290 
291  def tearDown(self):
292  super(TestDynamicCapacitated, self).tearDown()
293 
295  # test number of sources
296  assert_equal(len(self.source_id), 3)
297  # Test that source facilities are all deployed at time step 1
298  for s in self.source_id:
299  assert_equal(self.depl_time[np.where(self.agent_ids == s)], 1)
300 
302  # test number of sinks
303  assert_equal(len(self.sink_id), 4)
304  # Test that first 2 sink facilities are deployed at time step 1
305  # and decommissioned at time step 2
306  for i in [0, 1]:
307  assert_equal(
308  self.depl_time[np.where(self.agent_ids == self.sink_id[i])][0],
309  1)
310  assert_equal(
311  self.exit_time[np.where(self.exit_ids == self.sink_id[i])][0],
312  2)
313  # Test that second 2 sink facilities are deployed at time step 2
314  # and decommissioned at time step 3
315  for i in [2, 3]:
316  assert_equal(
317  self.depl_time[np.where(self.agent_ids == self.sink_id[i])][0],
318  2)
319  assert_equal(
320  self.exit_time[np.where(self.exit_ids == self.sink_id[i])][0],
321  3)
322 
324  # Check that transactions are between sources and sinks only
325  for s in self.sender_ids:
326  assert_equal(len(np.where(self.source_id == s)[0]), 1)
327  for r in self.receiver_ids:
328  assert_equal(len(np.where(self.sink_id == r)[0]), 1)
329  # Total expected number of transactions
330  assert_equal(len(self.trans_time), 7)
331  # Check that at time step 1, there are 2 transactions
332  assert_equal(len(np.where(self.trans_time == 1)[0]), 2)
333  # Check that at time step 2, there are 3 transactions
334  assert_equal(len(np.where(self.trans_time == 2)[0]), 3)
335  # Check that at time step 3, there are 2 transactions
336  assert_equal(len(np.where(self.trans_time == 3)[0]), 2)
337 
339  # Check that at time step 1, there are 2 transactions with total
340  # amount of 2
341  quantity = 0
342  for t in np.where(self.trans_time == 1)[0]:
343  quantity += self.quantities[
344  np.where(self.resource_ids == self.trans_resource[t])]
345  assert_equal(quantity, 2)
346 
347  # Check that at time step 2, there are 3 transactions with total
348  # amount of 3
349  quantity = 0
350  for t in np.where(self.trans_time == 2)[0]:
351  quantity += self.quantities[
352  np.where(self.resource_ids == self.trans_resource[t])]
353  assert_equal(quantity, 3)
354 
355  # Check that at time step 3, there are 2 transactions with total
356  # amount of 2
357  quantity = 0
358  for t in np.where(self.trans_time == 3)[0]:
359  quantity += self.quantities[
360  np.where(self.resource_ids == self.trans_resource[t])]
361  assert_equal(quantity, 2)
362 
364  """Tests GrowthRegion, ManagerInst, and Source over a 4-time step
365  simulation.
366 
367  A linear growth demand (y = x + 2) is provided to the growth region. Two
368  Sources are allowed in the ManagerInst, with capacities of 2 and 1.1,
369  respectively. At t=1, a 2-capacity Source is expected to be built, and at
370  t=2 and t=3, 1-capacity Sources are expected to be built.
371 
372  A linear growth demand (y = 0x + 3) for a second commodity is provided at t=2
373  to test the demand for multiple commodities.
374  """
375  def __init__(self, *args, **kwargs):
376  super(TestGrowth, self).__init__(*args, **kwargs)
377  self.inf = "./input/growth.xml"
378 
379  def setUp(self):
380  super(TestGrowth, self).setUp()
381 
382  def tearDown(self):
383  super(TestGrowth, self).tearDown()
384 
385  def test_deployment(self):
386  pass
387  agent_ids = self.to_ary(self.agent_entry, "AgentId")
388  proto = self.to_ary(self.agent_entry, "Prototype")
389  enter_time = self.to_ary(self.agent_entry, "EnterTime")
390 
391  source1_id = self.find_ids("Source1", self.agent_entry,
392  spec_col="Prototype")
393  source2_id = self.find_ids("Source2", self.agent_entry,
394  spec_col="Prototype")
395  source3_id = self.find_ids("Source3", self.agent_entry,
396  spec_col="Prototype")
397 
398  assert_equal(len(source2_id), 1)
399  assert_equal(len(source1_id), 2)
400  assert_equal(len(source3_id), 3)
401 
402  assert_equal(enter_time[np.where(agent_ids == source2_id[0])], 1)
403  assert_equal(enter_time[np.where(agent_ids == source1_id[0])], 2)
404  assert_equal(enter_time[np.where(agent_ids == source1_id[1])], 3)
405  for x in source3_id:
406  assert_equal(enter_time[np.where(agent_ids == x)], 2)
407 
409  """This class tests the input/recycle.xml file.
410  """
411  def __init__(self, *args, **kwargs):
412  super(_Recycle, self).__init__(*args, **kwargs)
413 
414  # this test requires separations which isn't supported by hdf5
415  # so we force sqlite:
416  base, _ = os.path.splitext(self.outf)
417  self.ext = '.sqlite'
418  self.outf = base + self.ext
419  self.sql = """
420  SELECT t.time as time,SUM(c.massfrac*r.quantity) as qty FROM transactions as t
421  JOIN resources as r ON t.resourceid=r.resourceid AND r.simid=t.simid
422  JOIN agententry as send ON t.senderid=send.agentid AND send.simid=t.simid
423  JOIN agententry as recv ON t.receiverid=recv.agentid AND recv.simid=t.simid
424  JOIN compositions as c ON c.qualid=r.qualid AND c.simid=r.simid
425  WHERE send.prototype=? AND recv.prototype=? AND c.nucid=?
426  GROUP BY t.time;"""
427 
428  def do_compare(self, fromfac, tofac, nuclide, exp_invs):
429  conn = sqlite3.connect(self.outf)
430  c = conn.cursor()
431  eps = 1e-10
432  simdur = len(exp_invs)
433 
434  invs = [0.0] * simdur
435  for i, row in enumerate(c.execute(self.sql, (fromfac, tofac, nuclide))):
436  t = row[0]
437  invs[t] = row[1]
438 
439  expfname = 'exp_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide)
440  with open(expfname, 'w') as f:
441  for t, val in enumerate(exp_invs):
442  f.write('{0} {1}\n'.format(t, val))
443  obsfname = 'obs_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide)
444  with open(obsfname, 'w') as f:
445  for t, val in enumerate(invs):
446  f.write('{0} {1}\n'.format(t, val))
447 
448  i = 0
449  for exp, obs in zip(invs, exp_invs):
450  assert_almost_equal(
451  exp, obs, err_msg='mismatch at t={}, {} != {}'.format(i, exp, obs))
452  i += 1
453 
454  os.remove(expfname)
455  os.remove(obsfname)
456 
458  simdur = 600
459  exp = [0.0] * simdur
460  exp[18] = 1.70022267
461  exp[37] = 1.70022267
462  exp[56] = 1.70022267
463  exp[75] = 1.70022267
464  exp[94] = 1.70022267
465  exp[113] = 1.70022267
466  exp[132] = 1.70022267
467  exp[151] = 1.70022267
468  exp[170] = 1.70022267
469  exp[189] = 1.70022267
470  exp[208] = 1.70022267
471  exp[246] = 1.70022267
472  exp[265] = 1.70022267
473  exp[284] = 1.70022267
474  exp[303] = 1.70022267
475  exp[322] = 1.70022267
476  exp[341] = 1.70022267
477  exp[360] = 1.70022267
478  exp[379] = 1.70022267
479  exp[417] = 1.70022267
480  exp[436] = 1.70022267
481  exp[455] = 1.70022267
482  exp[474] = 1.70022267
483  exp[493] = 1.70022267
484  exp[512] = 1.70022267
485  exp[531] = 1.70022267
486  exp[569] = 1.70022267
487  exp[588] = 1.70022267
488 
489  self.do_compare('separations', 'repo', 942390000, exp)
490 
492  simdur = 600
493  exp = [0.0] * simdur
494  exp[226] = 420.42772559790944
495  exp[397] = 420.42772559790944
496  exp[549] = 420.42772559790944
497  self.do_compare('reactor', 'repo', 942390000, exp)
498 
500  """This class tests the input/recycle.xml file.
501  """
502  def __init__(self, *args, **kwargs):
503  super(TestGreedyRecycle, self).__init__(*args, **kwargs)
504  self.inf = "../input/greedy_recycle.xml"
505 
507  """This class tests the input/recycle.xml file.
508  """
509  def __init__(self, *args, **kwargs):
510  super(TestCbcRecycle, self).__init__(*args, **kwargs)
511  self.inf = "../input/recycle.xml"
def __init__(self, args, kwargs)
def run_cyclus(cyclus, cwd, in_path, out_path)
Definition: helper.py:60
def __init__(self, args, kwargs)
def do_compare(self, fromfac, tofac, nuclide, exp_invs)
def __init__(self, args, kwargs)
def __init__(self, args, kwargs)
def __init__(self, args, kwargs)
def __init__(self, args, kwargs)
def find_ids(data, data_table, id_table)
Definition: helper.py:32
def find_ids(self, spec, a, spec_col="Spec", id_col="AgentId")
def __init__(self, args, kwargs)