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