diff --git a/src/material.cc b/src/material.cc index 113c26d25b..a995fd5aec 100644 --- a/src/material.cc +++ b/src/material.cc @@ -104,10 +104,17 @@ Material::Ptr Material::ExtractComp(double qty, Composition::Ptr c, void Material::Absorb(Material::Ptr mat) { - // force both mateiral objects to advance to the same decay time - int common_decay_time = std::max(this->prev_decay_time_, mat->prev_decay_time_); - if (ctx_ != NULL && ctx_->sim_info().decay != "never") { + // Handle absorb-specific decay rules + int common_decay_time; + if (HasContext() && mat->HasContext()) { common_decay_time = ctx_->time(); + } else if (!HasContext() && !mat->HasContext() + && mat->prev_decay_time_ > prev_decay_time_) { + throw ValueError("Cannot absorb a material that is more decayed than this one"); + } else if (HasContext() != mat->HasContext()) { + throw cyclus::Error("Cannot combine a tracked and untracked material!"); + } else { + common_decay_time = prev_decay_time_; } mat->Decay(common_decay_time); this->Decay(common_decay_time); @@ -128,7 +135,12 @@ void Material::Absorb(Material::Ptr mat) { comp_ = Composition::CreateFromMass(compmath::Add(v, otherv)); } - qty_ += mat->qty_; + double tot_mass = qty_ + mat->quantity(); + double avg_unit_value = + (qty_ * UnitValue() + mat->quantity() * mat->UnitValue()) / tot_mass; + SetUnitValue(avg_unit_value); + qty_ = tot_mass; + mat->qty_ = 0; tracker_.Absorb(&mat->tracker_); } @@ -204,9 +216,14 @@ void Material::Decay(int curr_time) { curr_time = ctx_->time(); } + int dt = curr_time - prev_decay_time_; - if (dt == 0) { - return; + + // Block decay backwards and past sim time for materials in a context + if (ctx_ && (dt < 0 || curr_time > ctx_->time())) { + std::string msg = "Materials in a context cannot decay backwards or " + "past the current simulation time!"; + throw cyclus::Error(msg); } // eps_decay defined such that tritium (12.32 yr half life) decays over 1 day @@ -243,7 +260,9 @@ void Material::Decay(int curr_time) { } } - prev_decay_time_ = curr_time; // this must go before Transmute call + // Need to set prev_decay_time before Transmute. + prev_decay_time_ = curr_time; + Composition::Ptr decayed = comp_->Decay(dt, secs_per_timestep); Transmute(decayed); } diff --git a/src/material.h b/src/material.h index 9ed29950b4..2e7635069a 100644 --- a/src/material.h +++ b/src/material.h @@ -108,6 +108,9 @@ class Material : public Resource { /// Returns the mass of this material in kg. virtual double quantity() const; + /// Returns true if the material has a context, false otherwise. + virtual bool HasContext() const {return ctx_ != NULL;} + virtual Resource::Ptr ExtractRes(double qty); /// Same as ExtractComp with c = this->comp(). diff --git a/tests/material_tests.cc b/tests/material_tests.cc index a75cc22ee2..48442fa58b 100644 --- a/tests/material_tests.cc +++ b/tests/material_tests.cc @@ -134,6 +134,42 @@ TEST_F(MaterialTest, AbsorbIntoZeroMaterial) { EXPECT_FLOAT_EQ(test_size_, same_as_test_mat->quantity()); } +TEST_F(MaterialTest, AbsorbMatlWithoutContext) { + Material::Ptr no_ctx_mat = Material::CreateUntracked(0, test_comp_); + EXPECT_FALSE(no_ctx_mat->HasContext()); + + // Since test_mat_ and no_ctx_mat both don't have context this is fine + EXPECT_NO_THROW(test_mat_->Absorb(no_ctx_mat)); +} + +TEST_F(MaterialTest, DecayTiming) { + FakeContext* fake_ctx = new FakeContext(&ti, &rec); + TestFacility* fake_fac = new TestFacility(fake_ctx); + + double untracked_qty = 1.0; + + Material::Ptr m1 = Material::Create(fake_fac, 1, diff_comp_); + Material::Ptr m2 = Material::CreateUntracked(untracked_qty, diff_comp_); + + // Set context time to 10 to match the decay time + fake_ctx->time(10); + + m1->Decay(); // decay m1 to time 10 + EXPECT_EQ(10, m1->prev_decay_time()); + EXPECT_ANY_THROW(m1->Decay(9)); // no backwards decay for tracked mats + EXPECT_ANY_THROW(m1->Decay(1000)); // tracked can't decay past sim time + + m2->Decay(10); // decay m2 to time 10 + EXPECT_EQ(10, m2->prev_decay_time()); + EXPECT_NO_THROW(m2->Decay(6)); // Backwards decay is fine if untracked + EXPECT_EQ(6, m2->prev_decay_time()); // correctly updates prev_decay_time_ + EXPECT_NO_THROW(m2->Decay(1000)); // Arbitrary decay fine on untracked + EXPECT_EQ(1000, m2->prev_decay_time()); + + delete fake_fac; + delete fake_ctx; +} + TEST_F(MaterialTest, ExtractMass) { double amt = test_size_ / 3; double diff = test_size_ - amt; @@ -211,15 +247,15 @@ TEST_F(MaterialTest, ExtractInGrams) { TEST_F(MaterialTest, DecayResBuf) { // prequeries - cyclus::toolkit::MatQuery orig(tracked_mat_); + cyclus::toolkit::MatQuery orig(untracked_mat_); double u235_qty = orig.mass(u235_); double pb208_qty = orig.mass(pb208_); double am241_qty = orig.mass(am241_); double sr89_qty = orig.mass(sr89_); - double orig_mass = tracked_mat_->quantity(); + double orig_mass = untracked_mat_->quantity(); cyclus::toolkit::ResBuf res_buf; - res_buf.Push(tracked_mat_); + res_buf.Push(untracked_mat_); // use untracked mat so we can forward decay // decay for 2 months which is just over 1 Sr-89 half-life res_buf.Decay(2); cyclus::Material::Ptr pop_mat = res_buf.Pop(); @@ -236,17 +272,17 @@ TEST_F(MaterialTest, DecayResBuf) { TEST_F(MaterialTest, DecayManual) { // prequeries - cyclus::toolkit::MatQuery orig(tracked_mat_); + cyclus::toolkit::MatQuery orig(untracked_mat_); double u235_qty = orig.mass(u235_); double pb208_qty = orig.mass(pb208_); double am241_qty = orig.mass(am241_); double sr89_qty = orig.mass(sr89_); - double orig_mass = tracked_mat_->quantity(); + double orig_mass = untracked_mat_->quantity(); - tracked_mat_->Decay(100); + untracked_mat_->Decay(100); // postquery - cyclus::toolkit::MatQuery mq(tracked_mat_); + cyclus::toolkit::MatQuery mq(untracked_mat_); // postchecks EXPECT_NE(u235_qty, mq.mass(u235_)); @@ -353,20 +389,22 @@ TEST_F(MaterialTest, DecayCustomTimeStep) { cyclus::Env::SetNucDataPath(); std::string cs137 ("Cs137"); uint64_t custom_timestep = pyne::half_life(cs137); + double qty = 1.0; SimInfo si(10, 2015, 1, "", "manual"); si.dt = custom_timestep; - cyclus::Context ctx(&ti, &rec); - ctx.InitSim(si); - Agent* a = new TestFacility(&ctx); + FakeContext* fake_ctx = new FakeContext(&ti, &rec); + fake_ctx->InitSim(si); CompMap v; v[id("Cs137")] = 1; Composition::Ptr c = Composition::CreateFromAtom(v); CompMap tmp = c->atom(); - Material::Ptr m = Material::Create(a, 1.0, c); + TestFacility* fake_fac = new TestFacility(fake_ctx); + Material::Ptr m = Material::Create(fake_fac, qty, c); - m->Decay(1); + fake_ctx->time(1); + m->Decay(); Composition::Ptr newc = m->comp(); CompMap newv = newc->atom(); @@ -375,14 +413,17 @@ TEST_F(MaterialTest, DecayCustomTimeStep) { // one half of atoms should have decayed away double eps = cyclus::CY_NEAR_ZERO; EXPECT_NEAR(0.5, newv[id("Cs137")], eps) << "one Cs137 half-life duration time step did not decay half of Cs atoms"; + + delete fake_fac; + delete fake_ctx; } TEST_F(MaterialTest, ExtractPrevDecay) { - tracked_mat_->Decay(10); - double qty = tracked_mat_->quantity() / 2; - cyclus::Material::Ptr x = tracked_mat_->ExtractQty(qty); + untracked_mat_->Decay(10); + double qty = untracked_mat_->quantity() / 2; + cyclus::Material::Ptr x = untracked_mat_->ExtractQty(qty); - EXPECT_EQ(tracked_mat_->prev_decay_time(), x->prev_decay_time()); + EXPECT_EQ(untracked_mat_->prev_decay_time(), x->prev_decay_time()); } // Transmute should reset a material's prev_decay_time to the current @@ -408,23 +449,48 @@ TEST_F(MaterialTest, TransmutePrevDecay) { // as coded. We may decide to change the behavior in the future breaking // this test; the test will need to be modified accordingly. // -// This test checks to see that, when materials are absorbed together, the -// previous decay time for the larger quantity material is used as the value -// for the new, combined material. +// This test checks to see that, when materials are absorbed together, both +// materials are decayed prior to the absorption. + TEST_F(MaterialTest, AbsorbPrevDecay) { - Material::Ptr m1 = Material::Create(fac, 1, diff_comp_); - Material::Ptr m2 = Material::Create(fac, 1, diff_comp_); - Material::Ptr m3 = Material::Create(fac, 1000, diff_comp_); - m3->Decay(10); + FakeContext* fake_ctx = new FakeContext(&ti, &rec); + TestFacility* fake_fac = new TestFacility(fake_ctx); + + double untracked_qty = 1.0; + + Material::Ptr m1 = Material::Create(fake_fac, 1, diff_comp_); + Material::Ptr m2 = Material::Create(fake_fac, 1, diff_comp_); + Material::Ptr m3 = Material::Create(fake_fac, 1000, diff_comp_); + Material::Ptr m4 = Material::CreateUntracked(untracked_qty, diff_comp_); + Material::Ptr m5 = Material::CreateUntracked(untracked_qty, diff_comp_); + + // Set context time to 10 to match the decay time + fake_ctx->time(10); + m3->Decay(); // decay m3 to time 10 EXPECT_EQ(0, m1->prev_decay_time()); EXPECT_EQ(0, m2->prev_decay_time()); EXPECT_EQ(10, m3->prev_decay_time()); + fake_ctx->time(11); + m1->Absorb(m3); - EXPECT_EQ(10, m1->prev_decay_time()); + EXPECT_EQ(11, m1->prev_decay_time()); m1->Absorb(m2); - EXPECT_EQ(10, m1->prev_decay_time()); + EXPECT_EQ(11, m1->prev_decay_time()); + + // We shouldn't be able to absorb a more-decayed untracked material + // but we should be able to absorb materials not in a context. + m4->Decay(11); + EXPECT_ANY_THROW(m5->Absorb(m4)); + EXPECT_NO_THROW(m4->Absorb(m5)); + + // Can't combine a tracked an untracked mat + EXPECT_ANY_THROW(m1->Absorb(m5)); + EXPECT_ANY_THROW(m5->Absorb(m1)); + + delete fake_fac; + delete fake_ctx; } TEST_F(MaterialTest, DecayHeatTest) { @@ -449,14 +515,24 @@ TEST_F(MaterialTest, DecaySmallAmount) { v[tritium_id] = 1.0; Composition::Ptr tritium_comp = Composition::CreateFromMass(v); + // Set up the one day time step context + int one_day = 86400; + cyclus::Timer ti_day_timestep; + si_day_timestep = SimInfo(100, 2015, 1, "", "manual"); + si_day_timestep.dt = one_day; + FakeContext* ctx_day_timestep = new FakeContext(&ti_day_timestep, &rec); + ctx_day_timestep->InitSim(si_day_timestep); + TestFacility* fac_day_timestep = new TestFacility(ctx_day_timestep); + Material::Ptr tritium = Material::Create(fac_day_timestep, qty, tritium_comp); cyclus::toolkit::MatQuery mq(tritium); double start_qty = mq.mass(tritium_id); EXPECT_EQ(qty, start_qty); - // Decay forward by one day (we use the one day time step facility) - tritium->Decay(1); + // Decay forward by one day (we have to push the fake_ctx's time forward first) + ctx_day_timestep->time(1); + tritium->Decay(); double decayed_qty = mq.mass(tritium_id); // NOTE: we've already tested that Decay is decaying the correct amt, so we diff --git a/tests/material_tests.h b/tests/material_tests.h index bad25aabfa..efa826f3a9 100644 --- a/tests/material_tests.h +++ b/tests/material_tests.h @@ -11,6 +11,7 @@ #include "test_agents/test_facility.h" #include "recorder.h" #include "timer.h" +#include "test_context.h" namespace cyclus { @@ -26,20 +27,19 @@ class MaterialTest : public ::testing::Test { Material::Ptr diff_mat_; Material::Ptr default_mat_; Material::Ptr tracked_mat_; + Material::Ptr untracked_mat_; Material::Ptr tracked_mat_no_decay_; long int u235_halflife_; int th228_halflife_; double u235_g_per_mol_; cyclus::Timer ti; - cyclus::Timer ti_day_timestep; cyclus::Recorder rec; cyclus::Context* ctx; TestFacility* fac; cyclus::Context* ctx_no_decay; TestFacility* fac_no_decay; - cyclus::Context* ctx_day_timestep; - TestFacility* fac_day_timestep; + // dur 100, y0 = 2015, m0=1, handle="", d="never" SimInfo si; SimInfo si_day_timestep; @@ -49,14 +49,6 @@ class MaterialTest : public ::testing::Test { ctx = new cyclus::Context(&ti, &rec); fac = new TestFacility(ctx); - // Set up the one day time step context - int one_day = 86400; - si_day_timestep = SimInfo(100, 2015, 1, "", "manual"); - si_day_timestep.dt = one_day; - ctx_day_timestep = new cyclus::Context(&ti_day_timestep, &rec); - ctx_day_timestep->InitSim(si_day_timestep); - fac_day_timestep = new TestFacility(ctx_day_timestep); - si = SimInfo(100, 2015, 1, "", "never"); ctx_no_decay = new cyclus::Context(&ti, &rec); ctx_no_decay->InitSim(si); @@ -88,6 +80,7 @@ class MaterialTest : public ::testing::Test { two_test_mat_ = Material::CreateUntracked(2 * test_size_, test_comp_); ten_test_mat_ = Material::CreateUntracked(10 * test_size_, test_comp_); diff_mat_ = Material::CreateUntracked(test_size_, diff_comp_); + untracked_mat_ = Material::CreateUntracked(1000, diff_comp_); // tracked material tracked_mat_ = Material::Create(fac, 1000, diff_comp_);