diff --git a/.gitignore b/.gitignore
index 835e504e75..ad488f2da1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,5 @@ suffix.h
*.pyc
rs.cred
*.h5
-tests/run_inputs.py
\ No newline at end of file
+*.dat
+tests/run_inputs.py
diff --git a/input/recycle.xml b/input/recycle.xml
new file mode 100644
index 0000000000..406994c642
--- /dev/null
+++ b/input/recycle.xml
@@ -0,0 +1,183 @@
+
+ flat
+
+ 600
+ 1
+ 2000
+
+
+
+ cycamore Source
+ cycamore Sink
+ cycamore Enrichment
+ cycamore Reactor
+ cycamore FuelFab
+ cycamore Separations
+
+
+
+ enrichment
+
+
+ natl_u
+ natl_u
+ uox
+ 0.003
+ waste
+ 1e100
+ 1e100
+
+
+
+
+
+ separations
+
+
+
+ sep_stream
+
+ 1e100
+
+ Pu .99
+
+
+
+
+ waste
+ 30001
+ 30001
+ spent_uox
+ 2.0
+
+
+
+
+
+ fuelfab
+
+
+ depleted_u
+ depleted_u
+ 30001
+
+ sep_stream
+ 15000
+
+ thermal
+ mox
+ 30001
+
+
+
+
+
+ reactor
+
+
+ fresh_uox fresh_mox
+ spent_uox spent_mox
+ uox mox
+ spent_uox waste
+ 1.0 2.0
+
+ 17
+ 2
+ 30000
+ 3
+ 1
+
+
+
+
+
+ repo
+
+
+ waste
+ 1e100
+
+
+
+
+
+ depleted_src
+
+
+
+
+
+ repo1 repo
+ r1 reactor
+ depleted1 depleted_src
+ fab1 fuelfab
+ sep1 separations
+ enrich1 enrichment
+
+
+ natl_u
+ mass
+ U235 0.711
+ U238 99.289
+
+
+
+ fresh_uox
+ mass
+ U2350.04
+ U2380.96
+
+
+
+ depleted_u
+ mass
+ U2350.003
+ U2380.997
+
+
+
+ fresh_mox
+ mass
+ U235 0.0027381
+ U238 0.9099619
+ Pu238 0.001746
+ Pu239 0.045396
+ Pu240 0.020952
+ Pu241 0.013095
+ Pu242 0.005238
+
+
+
+ spent_mox
+ mass
+ U235 0.0017381
+ U238 0.90
+ Pu238 0.001746
+ Pu239 0.0134
+ Pu240 0.020952
+ Pu241 0.013095
+ Pu242 0.005238
+
+
+
+ spent_uox
+ mass
+ U235 156.729
+ U236 102.103
+ U238 18280.324
+ Np237 13.656
+ Pu238 5.043
+ Pu239 106.343
+ Pu240 41.357
+ Pu241 36.477
+ Pu242 15.387
+ Am241 1.234
+ Am243 3.607
+ Cm244 0.431
+ Cm245 1.263
+
+
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 5701534c73..6a29fcff22 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -6,6 +6,8 @@ USE_CYCLUS("cycamore" "fuel_fab")
USE_CYCLUS("cycamore" "enrichment_facility")
+USE_CYCLUS("cycamore" "separations")
+
USE_CYCLUS("cycamore" "sink")
USE_CYCLUS("cycamore" "source")
diff --git a/src/separations.cc b/src/separations.cc
new file mode 100644
index 0000000000..a81e26e211
--- /dev/null
+++ b/src/separations.cc
@@ -0,0 +1,288 @@
+#include "separations.h"
+
+using cyclus::Material;
+using cyclus::Composition;
+using cyclus::toolkit::ResBuf;
+using cyclus::toolkit::MatVec;
+using cyclus::KeyError;
+using cyclus::ValueError;
+using cyclus::Request;
+using cyclus::CompMap;
+
+namespace cycamore {
+
+Separations::Separations(cyclus::Context* ctx) : cyclus::Facility(ctx) {
+ cyclus::Warn(
+ "the Separations archetype "
+ "is experimental");
+}
+
+cyclus::Inventories Separations::SnapshotInv() {
+ cyclus::Inventories invs;
+
+ // these inventory names are intentionally convoluted so as to not clash
+ // with the user-specified stream commods that are used as the separations
+ // streams inventory names.
+ invs["leftover-inv-name"] = leftover.PopNRes(leftover.count());
+ leftover.Push(invs["leftover-inv-name"]);
+ invs["feed-inv-name"] = feed.PopNRes(feed.count());
+ feed.Push(invs["feed-inv-name"]);
+
+ std::map >::iterator it;
+ for (it = streambufs.begin(); it != streambufs.end(); ++it) {
+ invs[it->first] = it->second.PopNRes(it->second.count());
+ it->second.Push(invs[it->first]);
+ }
+
+ return invs;
+}
+
+void Separations::InitInv(cyclus::Inventories& inv) {
+ leftover.Push(inv["leftover-inv-name"]);
+ feed.Push(inv["feed-inv-name"]);
+
+ cyclus::Inventories::iterator it;
+ for (it = inv.begin(); it != inv.end(); ++it) {
+ streambufs[it->first].Push(it->second);
+ }
+}
+
+typedef std::pair > Stream;
+typedef std::map StreamSet;
+
+void Separations::EnterNotify() {
+ cyclus::Facility::EnterNotify();
+ StreamSet::iterator it;
+ for (it = streams_.begin(); it != streams_.end(); ++it) {
+ std::string name = it->first;
+ Stream stream = it->second;
+ double cap = stream.first;
+ if (cap >= 0) {
+ streambufs[name].capacity(cap);
+ }
+ }
+
+ if (feed_commod_prefs.size() == 0) {
+ for (int i = 0; i < feed_commods.size(); i++) {
+ feed_commod_prefs.push_back(0);
+ }
+ }
+}
+
+void Separations::Tick() {
+ if (feed.count() == 0) {
+ return;
+ }
+
+ Material::Ptr mat = feed.Pop(std::min(throughput, feed.quantity()));
+ double orig_qty = mat->quantity();
+
+ StreamSet::iterator it;
+ double maxfrac = 1;
+ std::map stagedsep;
+ for (it = streams_.begin(); it != streams_.end(); ++it) {
+ Stream info = it->second;
+ std::string name = it->first;
+ stagedsep[name] = SepMaterial(info.second, mat);
+ double frac = streambufs[name].space() / stagedsep[name]->quantity();
+ if (frac < maxfrac) {
+ maxfrac = frac;
+ }
+ }
+
+ std::map::iterator itf;
+ for (itf = stagedsep.begin(); itf != stagedsep.end(); ++itf) {
+ std::string name = itf->first;
+ Material::Ptr m = itf->second;
+ if (m->quantity() > 0) {
+ streambufs[name].Push(
+ mat->ExtractComp(m->quantity() * maxfrac, m->comp()));
+ }
+ }
+
+ if (maxfrac == 1) {
+ if (mat->quantity() > 0) {
+ // unspecified separations fractions go to leftovers
+ leftover.Push(mat);
+ }
+ } else { // maxfrac is < 1
+ // push back any leftover feed due to separated stream inv size constraints
+ feed.Push(mat->ExtractQty((1 - maxfrac) * orig_qty));
+ if (mat->quantity() > 0) {
+ // unspecified separations fractions go to leftovers
+ leftover.Push(mat);
+ }
+ }
+}
+
+// Note that this returns an untracked material that should just be used for
+// its composition and qty - not in any real inventories, etc.
+Material::Ptr SepMaterial(std::map effs, Material::Ptr mat) {
+ CompMap cm = mat->comp()->mass();
+ cyclus::compmath::Normalize(&cm, mat->quantity());
+ double tot_qty = 0;
+ CompMap sepcomp;
+
+ CompMap::iterator it;
+ for (it = cm.begin(); it != cm.end(); ++it) {
+ int nuc = it->first;
+ int elem = (nuc / 10000000) * 10000000;
+ double eff = 0;
+ if (effs.count(nuc) > 0) {
+ eff = effs[nuc];
+ } else if (effs.count(elem) > 0) {
+ eff = effs[elem];
+ } else {
+ continue;
+ }
+
+ double qty = it->second;
+ double sepqty = qty * eff;
+ sepcomp[nuc] = sepqty;
+ tot_qty += sepqty;
+ }
+
+ Composition::Ptr c = Composition::CreateFromMass(sepcomp);
+ return Material::CreateUntracked(tot_qty, c);
+};
+
+std::set::Ptr>
+Separations::GetMatlRequests() {
+ using cyclus::RequestPortfolio;
+ std::set::Ptr> ports;
+ bool exclusive = false;
+
+ if (feed.space() > cyclus::eps()) {
+ RequestPortfolio::Ptr port(new RequestPortfolio());
+
+ Material::Ptr m = cyclus::NewBlankMaterial(feed.space());
+ if (!feed_recipe.empty()) {
+ Composition::Ptr c = context()->GetRecipe(feed_recipe);
+ m = Material::CreateUntracked(feed.space(), c);
+ }
+
+ std::vector*> reqs;
+ for (int i = 0; i < feed_commods.size(); i++) {
+ std::string commod = feed_commods[i];
+ double pref = feed_commod_prefs[i];
+ reqs.push_back(port->AddRequest(m, this, commod, pref, exclusive));
+ }
+ port->AddMutualReqs(reqs);
+ ports.insert(port);
+ }
+
+ return ports;
+}
+
+void Separations::GetMatlTrades(
+ const std::vector >& trades,
+ std::vector, Material::Ptr> >&
+ responses) {
+ using cyclus::Trade;
+
+ std::vector >::const_iterator it;
+ for (int i = 0; i < trades.size(); i++) {
+ std::string commod = trades[i].request->commodity();
+ if (commod == leftover_commod) {
+ double amt = std::min(leftover.quantity(), trades[i].amt);
+ Material::Ptr m = leftover.Pop(amt);
+ responses.push_back(std::make_pair(trades[i], m));
+ } else if (streambufs.count(commod) > 0) {
+ double amt = std::min(streambufs[commod].quantity(), trades[i].amt);
+ Material::Ptr m = streambufs[commod].Pop(amt);
+ responses.push_back(std::make_pair(trades[i], m));
+ } else {
+ throw ValueError("invalid commodity " + commod +
+ " on trade matched to prototype " + prototype());
+ }
+ }
+}
+
+void Separations::AcceptMatlTrades(const std::vector<
+ std::pair, Material::Ptr> >& responses) {
+ std::vector,
+ cyclus::Material::Ptr> >::const_iterator trade;
+
+ for (trade = responses.begin(); trade != responses.end(); ++trade) {
+ feed.Push(trade->second);
+ }
+}
+
+std::set::Ptr> Separations::GetMatlBids(
+ cyclus::CommodMap::type& commod_requests) {
+ using cyclus::BidPortfolio;
+
+ bool exclusive = false;
+ std::set::Ptr> ports;
+
+ // bid streams
+ std::map >::iterator it;
+ for (it = streambufs.begin(); it != streambufs.end(); ++it) {
+ std::string commod = it->first;
+ std::vector*>& reqs = commod_requests[commod];
+ if (reqs.size() == 0) {
+ continue;
+ } else if (streambufs[commod].quantity() < cyclus::eps()) {
+ continue;
+ }
+
+ MatVec mats = streambufs[commod].PopN(streambufs[commod].count());
+ streambufs[commod].Push(mats);
+
+ BidPortfolio::Ptr port(new BidPortfolio());
+
+ for (int j = 0; j < reqs.size(); j++) {
+ Request* req = reqs[j];
+ double tot_bid = 0;
+ for (int k = 0; k < mats.size(); k++) {
+ Material::Ptr m = mats[k];
+ tot_bid += m->quantity();
+ port->AddBid(req, m, this, exclusive);
+ if (tot_bid >= req->target()->quantity()) {
+ break;
+ }
+ }
+ }
+
+ double tot_qty = streambufs[commod].quantity();
+ cyclus::CapacityConstraint cc(tot_qty);
+ port->AddConstraint(cc);
+ ports.insert(port);
+ }
+
+ // bid leftovers
+ std::vector*>& reqs = commod_requests[leftover_commod];
+ if (reqs.size() > 0 && leftover.quantity() >= cyclus::eps()) {
+ MatVec mats = leftover.PopN(leftover.count());
+ leftover.Push(mats);
+
+ BidPortfolio::Ptr port(new BidPortfolio());
+
+ for (int j = 0; j < reqs.size(); j++) {
+ Request* req = reqs[j];
+ double tot_bid = 0;
+ for (int k = 0; k < mats.size(); k++) {
+ Material::Ptr m = mats[k];
+ tot_bid += m->quantity();
+ port->AddBid(req, m, this, exclusive);
+ if (tot_bid >= req->target()->quantity()) {
+ break;
+ }
+ }
+ }
+
+ cyclus::CapacityConstraint cc(leftover.quantity());
+ port->AddConstraint(cc);
+ ports.insert(port);
+ }
+
+ return ports;
+}
+
+void Separations::Tock() {}
+
+extern "C" cyclus::Agent* ConstructSeparations(cyclus::Context* ctx) {
+ return new Separations(ctx);
+}
+
+} // namespace cycamore
diff --git a/src/separations.h b/src/separations.h
new file mode 100644
index 0000000000..da118ad6d4
--- /dev/null
+++ b/src/separations.h
@@ -0,0 +1,182 @@
+#ifndef CYCAMORE_SRC_SEPARATIONS_H_
+#define CYCAMORE_SRC_SEPARATIONS_H_
+
+#include "cyclus.h"
+
+namespace cycamore {
+
+/// SepMaterial returns a material object that represents the composition and
+/// quantity resulting from the separation of material from mat using the given
+/// mass-based efficiencies. Each key in effs represents a nuclide or element
+/// (canonical PyNE form), and each value is the corresponding mass-based
+/// separations efficiency for that nuclide or element. Note that this returns
+/// an untracked material that should only be used for its composition and qty
+/// - not in any real inventories, etc.
+cyclus::Material::Ptr SepMaterial(std::map effs,
+ cyclus::Material::Ptr mat);
+
+/// Separations processes feed material into one or more streams containing
+/// specific elements and/or nuclides. It uses mass-based efficiencies.
+///
+/// User defined separations streams are specified as groups of
+/// component-efficiency pairs where 'component' means either a particular
+/// element or a particular nuclide. Each component's paired efficiency
+/// represents the mass fraction of that component in the feed that is
+/// separated into that stream. The efficiencies of a particular component
+/// across all streams must sum up to less than or equal to one. If less than
+/// one, the remainining material is sent to a waste inventory and
+/// (potentially) traded away from there.
+///
+/// The facility receives material into a feed inventory that it processes with
+/// a specified throughput each time step. Each output stream has a
+/// corresponding output inventory size/limit. If the facility is unable to
+/// reduce its stocks by trading and hits this limit for any of its output
+/// streams, further processing/separations of feed material will halt until
+/// room is again available in the output streams.
+class Separations : public cyclus::Facility {
+#pragma cyclus note { \
+ "doc": \
+ "Separations processes feed material into one or more streams containing" \
+ " specific elements and/or nuclides. It uses mass-based efficiencies." \
+ "\n\n" \
+ "User defined separations streams are specified as groups of" \
+ " component-efficiency pairs where 'component' means either a particular" \
+ " element or a particular nuclide. Each component's paired efficiency" \
+ " represents the mass fraction of that component in the feed that is" \
+ " separated into that stream. The efficiencies of a particular component" \
+ " across all streams must sum up to less than or equal to one. If less than" \
+ " one, the remainining material is sent to a waste inventory and" \
+ " (potentially) traded away from there." \
+ "\n\n" \
+ "The facility receives material into a feed inventory that it processes with" \
+ " a specified throughput each time step. Each output stream has a" \
+ " corresponding output inventory size/limit. If the facility is unable to" \
+ " reduce its stocks by trading and hits this limit for any of its output" \
+ " streams, further processing/separations of feed material will halt until" \
+ " room is again available in the output streams." \
+ "", \
+}
+ public:
+ Separations(cyclus::Context* ctx);
+ virtual ~Separations(){};
+
+ virtual void Tick();
+ virtual void Tock();
+ virtual void EnterNotify();
+
+ virtual void AcceptMatlTrades(const std::vector, cyclus::Material::Ptr> >& responses);
+
+ virtual std::set::Ptr>
+ GetMatlRequests();
+
+ virtual std::set::Ptr> GetMatlBids(
+ cyclus::CommodMap::type& commod_requests);
+
+ virtual void GetMatlTrades(
+ const std::vector >& trades,
+ std::vector,
+ cyclus::Material::Ptr> >& responses);
+
+ #pragma cyclus clone
+ #pragma cyclus initfromcopy
+ #pragma cyclus infiletodb
+ #pragma cyclus initfromdb
+ #pragma cyclus schema
+ #pragma cyclus annotations
+ #pragma cyclus snapshot
+ // the following pragmas are ommitted and the functions are written
+ // manually in order to handle the vector of resource buffers:
+ //
+ // #pragma cyclus snapshotinv
+ // #pragma cyclus initinv
+
+ virtual cyclus::Inventories SnapshotInv();
+ virtual void InitInv(cyclus::Inventories& inv);
+
+ private:
+ #pragma cyclus var { \
+ "doc" : "Maximum quantity of feed material that can be processed per time step.", \
+ "units": "kg", \
+ }
+ double throughput;
+
+ #pragma cyclus var { \
+ "doc": "Ordered list of commodities on which to request feed material to separate." \
+ " Order only matters for matching up with feed commodity preferences if specified.", \
+ "uitype": ["oneormore", "incommodity"], \
+ }
+ std::vector feed_commods;
+
+ #pragma cyclus var { \
+ "default": [], \
+ "doc": "Feed commodity request preferences for each of the given feed commodities (same order)." \
+ " If unspecified, default is to use zero for all preferences.", \
+ }
+ std::vector feed_commod_prefs;
+
+ #pragma cyclus var { \
+ "doc": "Name for recipe to be used in feed requests." \
+ " Empty string results in use of a dummy recipe.", \
+ "uitype": "recipe", \
+ "default": "", \
+ }
+ std::string feed_recipe;
+
+ #pragma cyclus var { \
+ "doc" : "Maximum amount of feed material to keep on hand.", \
+ "units" : "kg", \
+ }
+ double feedbuf_size;
+
+ #pragma cyclus var { \
+ "capacity" : "feedbuf_size", \
+ }
+ cyclus::toolkit::ResBuf feed;
+
+ #pragma cyclus var { \
+ "doc" : "Maximum amount of leftover separated material (not included in" \
+ " any other stream) that can be stored." \
+ " If full, the facility halts operation until space becomes available.", \
+ "default": 1e299, \
+ "units": "kg", \
+ }
+ double leftoverbuf_size;
+
+ #pragma cyclus var { \
+ "doc": "Commodity on which to trade the leftover separated material stream." \
+ " This MUST NOT be the same as any commodity used to define the other separations streams.", \
+ "uitype": "outcommodity", \
+ "default": "default-waste-stream", \
+ }
+ std::string leftover_commod;
+
+ #pragma cyclus var { \
+ "capacity" : "leftoverbuf_size", \
+ }
+ cyclus::toolkit::ResBuf leftover;
+
+ #pragma cyclus var { \
+ "alias": ["streams", "commod", ["info", "buf_size", ["efficiencies", "comp", "eff"]]], \
+ "uitype": ["oneormore", "outcommodity", ["pair", "double", ["oneormore", "nuclide", "double"]]], \
+ "doc": "Output streams for separations." \
+ " Each stream must have a unique name identifying the commodity on which its material is traded," \
+ " a max buffer capacity in kg (neg values indicate infinite size)," \
+ " and a set of component efficiencies." \
+ " 'comp' is a component to be separated into the stream" \
+ " (e.g. U, Pu, etc.) and 'eff' is the mass fraction of the component" \
+ " that is separated from the feed into this output stream." \
+ " If any stream buffer is full, the facility halts operation until space becomes available." \
+ " The sum total of all component efficiencies across streams must be less than or equal to 1" \
+ " (e.g. sum of U efficiencies for all streams must be <= 1).", \
+ }
+ std::map > > streams_;
+
+ // custom SnapshotInv and InitInv and EnterNotify are used to persist this
+ // state var.
+ std::map > streambufs;
+};
+
+} // namespace cycamore
+
+#endif // CYCAMORE_SRC_SEPARATIONS_H_
diff --git a/src/separations_tests.cc b/src/separations_tests.cc
new file mode 100644
index 0000000000..0fb1b42c60
--- /dev/null
+++ b/src/separations_tests.cc
@@ -0,0 +1,90 @@
+#include "separations.h"
+
+#include
+#include
+#include "cyclus.h"
+
+using pyne::nucname::id;
+using cyclus::Composition;
+using cyclus::CompMap;
+using cyclus::Material;
+using cyclus::QueryResult;
+using cyclus::Cond;
+using cyclus::toolkit::MatQuery;
+
+namespace cycamore {
+
+TEST(SeparationsTests, SepMaterial) {
+ CompMap comp;
+ comp[id("U235")] = 10;
+ comp[id("U238")] = 90;
+ comp[id("Pu239")] = 1;
+ comp[id("Pu240")] = 2;
+ comp[id("Am241")] = 3;
+ comp[id("Am242")] = 2.8;
+ double qty = 100;
+ Composition::Ptr c = Composition::CreateFromMass(comp);
+ Material::Ptr mat = Material::CreateUntracked(qty, c);
+
+ std::map effs;
+ effs[id("U")] = .7;
+ effs[id("Pu")] = .4;
+ effs[id("Am241")] = .4;
+
+ Material::Ptr sep = SepMaterial(effs, mat);
+ MatQuery mqorig(mat);
+ MatQuery mqsep(sep);
+
+ EXPECT_DOUBLE_EQ(effs[id("U")] * mqorig.mass("U235"), mqsep.mass("U235"));
+ EXPECT_DOUBLE_EQ(effs[id("U")] * mqorig.mass("U238"), mqsep.mass("U238"));
+ EXPECT_DOUBLE_EQ(effs[id("Pu")] * mqorig.mass("Pu239"), mqsep.mass("Pu239"));
+ EXPECT_DOUBLE_EQ(effs[id("Pu")] * mqorig.mass("Pu240"), mqsep.mass("Pu240"));
+ EXPECT_DOUBLE_EQ(effs[id("Am241")] * mqorig.mass("Am241"), mqsep.mass("Am241"));
+ EXPECT_DOUBLE_EQ(0, mqsep.mass("Am242"));
+}
+
+TEST(SeparationsTests, SepMixElemAndNuclide) {
+ std::string config =
+ ""
+ " stream1"
+ " "
+ " -1"
+ " "
+ " U 0.6"
+ " Pu239 .7"
+ " "
+ " "
+ ""
+ ""
+ "waste"
+ "100"
+ "100"
+ " feed "
+ ;
+
+ CompMap m;
+ m[id("u235")] = 0.08;
+ m[id("u238")] = 0.9;
+ m[id("Pu239")] = .01;
+ m[id("Pu240")] = .01;
+ Composition::Ptr c = Composition::CreateFromMass(m);
+
+ int simdur = 2;
+ cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:Separations"), config, simdur);
+ sim.AddSource("feed").recipe("recipe1").Finalize();
+ sim.AddSink("stream1").capacity(100).Finalize();
+ sim.AddRecipe("recipe1", c);
+ int id = sim.Run();
+
+ std::vector conds;
+ conds.push_back(Cond("SenderId", "==", id));
+ int resid = sim.db().Query("Transactions", &conds).GetVal("ResourceId");
+ MatQuery mq (sim.GetMaterial(resid));
+ EXPECT_DOUBLE_EQ(m[922350000]*0.6*100, mq.mass("U235"));
+ EXPECT_DOUBLE_EQ(m[922380000]*0.6*100, mq.mass("U238"));
+ EXPECT_DOUBLE_EQ(m[942390000]*0.7*100, mq.mass("Pu239"));
+ EXPECT_DOUBLE_EQ(0, mq.mass("Pu240"));
+}
+
+} // namespace cycamore
+
diff --git a/tests/test_regression.py b/tests/test_regression.py
index 9c315b3a9f..449f7d9cd9 100644
--- a/tests/test_regression.py
+++ b/tests/test_regression.py
@@ -375,3 +375,93 @@ def test_deployment(self):
assert_equal(depl_time[np.where(agent_ids == source1_id[0])], 2)
assert_equal(depl_time[np.where(agent_ids == source1_id[1])], 3)
+class TestRecycle(TestRegression):
+ """This class tests the input/recycle.xml file.
+ """
+ def __init__(self, *args, **kwargs):
+ super(TestRecycle, self).__init__(*args, **kwargs)
+
+ # this test requires separations which isn't supported by hdf5
+ # so we force sqlite:
+ base, _ = os.path.splitext(self.outf)
+ self.ext = '.sqlite'
+ self.outf = base + self.ext
+ self.inf = "../input/recycle.xml"
+ self.sql = """
+ SELECT t.time as time,SUM(c.massfrac*r.quantity) as qty FROM transactions as t
+ JOIN resources as r ON t.resourceid=r.resourceid AND r.simid=t.simid
+ JOIN agententry as send ON t.senderid=send.agentid AND send.simid=t.simid
+ JOIN agententry as recv ON t.receiverid=recv.agentid AND recv.simid=t.simid
+ JOIN compositions as c ON c.qualid=r.qualid AND c.simid=r.simid
+ WHERE send.prototype=? AND recv.prototype=? AND c.nucid=?
+ GROUP BY t.time;"""
+
+ def do_compare(self, fromfac, tofac, nuclide, exp_invs):
+ conn = sqlite3.connect(self.outf)
+ c = conn.cursor()
+ eps = 1e-10
+ simdur = len(exp_invs)
+
+ invs = [0.0] * simdur
+ for i, row in enumerate(c.execute(self.sql, (fromfac, tofac, nuclide))):
+ t = row[0]
+ invs[t] = row[1]
+
+ expfname = 'exp_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide)
+ with open(expfname, 'w') as f:
+ for t, val in enumerate(exp_invs):
+ f.write('{0} {1}\n'.format(t, val))
+ obsfname = 'obs_recycle_{0}-{1}-{2}.dat'.format(fromfac, tofac, nuclide)
+ with open(obsfname, 'w') as f:
+ for t, val in enumerate(invs):
+ f.write('{0} {1}\n'.format(t, val))
+
+ i = 0
+ for exp, obs in zip(invs, exp_invs):
+ i += 1
+ self.assertAlmostEquals(exp, obs, msg='mismatch at t={0}'.format(i))
+
+ os.remove(expfname)
+ os.remove(obsfname)
+
+ def test_pu239_sep_repo(self):
+ simdur = 600
+ exp = [0.0] * simdur
+ exp[18] = 1.700222672
+ exp[37] = 1.700222672
+ exp[56] = 1.700222672
+ exp[75] = 1.700222672
+ exp[94] = 1.700222672
+ exp[113] = 1.700222672
+ exp[132] = 1.700222672
+ exp[151] = 1.700222672
+ exp[170] = 1.700222672
+ exp[189] = 1.700222672
+ exp[208] = 1.700222672
+ exp[227] = 1.700222672
+ exp[246] = 1.700222672
+ exp[284] = 1.700222672
+ exp[303] = 1.700222672
+ exp[322] = 1.700222672
+ exp[341] = 1.700222672
+ exp[360] = 1.700222672
+ exp[379] = 1.700222672
+ exp[398] = 1.700222672
+ exp[417] = 1.700222672
+ exp[436] = 1.700222672
+ exp[474] = 1.700222672
+ exp[493] = 1.700222672
+ exp[512] = 1.700222672
+ exp[531] = 1.700222672
+ exp[550] = 1.700222672
+ exp[569] = 1.700222672
+ exp[588] = 1.700222672
+ self.do_compare('separations', 'repo', 942390000, exp)
+
+ def test_pu239_reactor_repo(self):
+ simdur = 600
+ exp = [0.0] * simdur
+ exp[264] = 420.42772559790944
+ exp[454] = 420.42772559790944
+ self.do_compare('reactor', 'repo', 942390000, exp)
+