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