CYCAMORE
src/fuel_fab_tests.cc
Go to the documentation of this file.
1 #include "fuel_fab.h"
2 
3 #include <gtest/gtest.h>
4 #include <sstream>
5 #include "cyclus.h"
6 
7 using pyne::nucname::id;
8 using cyclus::Composition;
9 using cyclus::CompMap;
10 using cyclus::Material;
11 using cyclus::QueryResult;
12 using cyclus::Cond;
13 using cyclus::toolkit::MatQuery;
14 
15 namespace cycamore {
16 namespace fuelfabtests {
17 
18 Composition::Ptr c_uox() {
19  CompMap m;
20  m[id("u235")] = 0.04;
21  m[id("u238")] = 0.96;
22  return Composition::CreateFromMass(m);
23 };
24 
25 Composition::Ptr c_mox() {
26  CompMap m;
27  m[id("u235")] = .7;
28  m[id("u238")] = 100;
29  m[id("pu239")] = 3.3;
30  return Composition::CreateFromMass(m);
31 };
32 
33 Composition::Ptr c_natu() {
34  CompMap m;
35  m[id("u235")] = .007;
36  m[id("u238")] = .993;
37  return Composition::CreateFromMass(m);
38 };
39 
40 Composition::Ptr c_pustream() {
41  CompMap m;
42  m[id("pu239")] = 100;
43  m[id("pu240")] = 10;
44  m[id("pu241")] = 1;
45  m[id("pu242")] = 1;
46  return Composition::CreateFromMass(m);
47 };
48 
49 Composition::Ptr c_pustreamlow() {
50  CompMap m;
51  m[id("pu239")] = 80;
52  m[id("pu240")] = 10;
53  m[id("pu241")] = 1;
54  m[id("pu242")] = 1;
55  return Composition::CreateFromMass(m);
56 };
57 
58 Composition::Ptr c_pustreambad() {
59  CompMap m;
60  m[id("pu239")] = 1;
61  m[id("pu240")] = 10;
62  m[id("pu241")] = 1;
63  m[id("pu242")] = 1;
64  return Composition::CreateFromMass(m);
65 };
66 
67 Composition::Ptr c_water() {
68  CompMap m;
69  m[id("O16")] = 1;
70  m[id("H1")] = 2;
71  return Composition::CreateFromAtom(m);
72 };
73 
74 TEST(FuelFabTests, CosiWeight) {
75  cyclus::Env::SetNucDataPath();
76  CompMap m;
77  m[942390000] = 1;
78  Composition::Ptr c = Composition::CreateFromMass(m);
79  double w = CosiWeight(c, "thermal");
80  EXPECT_DOUBLE_EQ(1.0, w);
81 
82  m.clear();
83  m[922380000] = 1;
84  c = Composition::CreateFromMass(m);
85  w = CosiWeight(c, "thermal");
86  EXPECT_DOUBLE_EQ(0.0, w);
87 
88  m.clear();
89  m[942390000] = 1;
90  c = Composition::CreateFromMass(m);
91  w = CosiWeight(c, "fission_spectrum_ave");
92  EXPECT_DOUBLE_EQ(1.0, w);
93 
94  m.clear();
95  m[922380000] = 1;
96  c = Composition::CreateFromMass(m);
97  w = CosiWeight(c, "fission_spectrum_ave");
98  EXPECT_DOUBLE_EQ(0.0, w);
99 
100  m.clear();
101  m[922380000] = 1;
102  m[942390000] = 1;
103  c = Composition::CreateFromAtom(m);
104  w = CosiWeight(c, "thermal");
105  EXPECT_DOUBLE_EQ(0.5, w) << "might be using mass-fractions instead of atom";
106 
107  m.clear();
108  m[922380000] = 1;
109  m[942390000] = 1;
110  c = Composition::CreateFromAtom(m);
111  w = CosiWeight(c, "fission_spectrum_ave");
112  EXPECT_DOUBLE_EQ(0.5, w) << "might be using mass-fractions instead of atom";
113 
114  m.clear();
115  m[922380000] = 1;
116  m[942390000] = 1;
117  m[922350000] = 1;
118  c = Composition::CreateFromAtom(m);
119  double w_therm = CosiWeight(c, "thermal");
120  double w_fast = CosiWeight(c, "fission_spectrum_ave");
121  EXPECT_GT(w_therm, w_fast);
122 }
123 
124 TEST(FuelFabTests, CosiWeight_Mixed) {
125  cyclus::Env::SetNucDataPath();
126  double w_fill = CosiWeight(c_natu(), "thermal");
127  double w_fiss = CosiWeight(c_pustream(), "thermal");
128  double w_target = CosiWeight(c_uox(), "thermal");
129 
130  double fiss_frac = HighFrac(w_fill, w_target, w_fiss);
131  double fill_frac = LowFrac(w_fill, w_target, w_fiss);
132 
133  // correct frac's from atom-based into mass-based for mixing
134  fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu());
135  fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream());
136 
137  Material::Ptr m1 = Material::CreateUntracked(fiss_frac, c_pustream());
138  Material::Ptr m2 = Material::CreateUntracked(fill_frac, c_natu());
139  m1->Absorb(m2);
140  double got = CosiWeight(m1->comp(), "thermal");
141  EXPECT_LT(std::abs((w_target-got)/w_target), 0.00001) << "mixed composition not within 0.001% of target";
142 }
143 
144 TEST(FuelFabTests, HighFrac) {
145  cyclus::Env::SetNucDataPath();
146  double w_fill = CosiWeight(c_natu(), "thermal");
147  double w_fiss = CosiWeight(c_pustream(), "thermal");
148  double w_target = CosiWeight(c_uox(), "thermal");
149 
150  EXPECT_THROW(HighFrac(w_fiss, w_target, w_fill), cyclus::ValueError);
151  EXPECT_THROW(HighFrac(w_target, w_fill, w_fiss), cyclus::ValueError);
152  EXPECT_THROW(HighFrac(w_target, w_fiss, w_fill), cyclus::ValueError);
153 
154  double f = HighFrac(w_fill, w_target, w_fiss);
155  EXPECT_DOUBLE_EQ((w_target-w_fill)/(w_fiss-w_fill), f);
156 }
157 
158 TEST(FuelFabTests, LowFrac) {
159  cyclus::Env::SetNucDataPath();
160  double w_fill = CosiWeight(c_natu(), "thermal");
161  double w_fiss = CosiWeight(c_pustream(), "thermal");
162  double w_target = CosiWeight(c_uox(), "thermal");
163 
164  EXPECT_THROW(LowFrac(w_fiss, w_target, w_fill), cyclus::ValueError);
165  EXPECT_THROW(LowFrac(w_target, w_fill, w_fiss), cyclus::ValueError);
166  EXPECT_THROW(LowFrac(w_target, w_fiss, w_fill), cyclus::ValueError);
167 
168  double f = LowFrac(w_fill, w_target, w_fiss);
169  EXPECT_DOUBLE_EQ((w_fiss-w_target)/(w_fiss-w_fill), f);
170 }
171 
172 TEST(FuelFabTests, ValidWeights) {
173  cyclus::Env::SetNucDataPath();
174  double w_fill = CosiWeight(c_natu(), "thermal");
175  double w_fiss = CosiWeight(c_pustream(), "thermal");
176  double w_target = CosiWeight(c_uox(), "thermal");
177 
178  EXPECT_EQ(true, ValidWeights(w_fill, w_target, w_fiss));
179  EXPECT_EQ(false, ValidWeights(w_fiss, w_target, w_fill));
180  EXPECT_EQ(false, ValidWeights(w_target, w_fill, w_fiss));
181  EXPECT_EQ(false, ValidWeights(w_fiss, w_fill, w_target));
182  EXPECT_EQ(false, ValidWeights(w_target, w_fiss, w_fill));
183  EXPECT_EQ(false, ValidWeights(w_fill, w_fiss, w_target));
184 }
185 
186 // request (and receive) a specific recipe for fissile stream correctly.
187 TEST(FuelFabTests, FissRecipe) {
188  std::string config =
189  "<fill_commods> <val>dummy</val> </fill_commods>"
190  "<fill_recipe>natu</fill_recipe>"
191  "<fill_size>1</fill_size>"
192  ""
193  "<fiss_commods> <val>stream1</val> <val>stream2</val> <val>stream3</val> </fiss_commods>"
194  "<fiss_size>2.5</fiss_size>"
195  "<fiss_recipe>spentuox</fiss_recipe>"
196  ""
197  "<outcommod>dummyout</outcommod>"
198  "<spectrum>thermal</spectrum>"
199  "<throughput>0</throughput>"
200  ;
201 
202  int simdur = 1;
203  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
204  sim.AddSource("stream1").Finalize();
205  sim.AddRecipe("spentuox", c_pustream());
206  sim.AddRecipe("natu", c_natu());
207  int id = sim.Run();
208 
209  int resid = sim.db().Query("Transactions", NULL).GetVal<int>("ResourceId");
210  CompMap got = sim.GetMaterial(resid)->comp()->mass();
211  CompMap want = c_pustream()->mass();
212  cyclus::compmath::Normalize(&got);
213  cyclus::compmath::Normalize(&want);
214  CompMap::iterator it;
215  for (it = want.begin(); it != want.end(); ++it) {
216  EXPECT_DOUBLE_EQ(it->second, got[it->first]) << "nuclide qty off: " << pyne::nucname::name(it->first);
217  }
218 }
219 
220 // multiple fissile streams can be correctly requested and used as
221 // fissile material inventory.
222 TEST(FuelFabTests, MultipleFissStreams) {
223  std::string config =
224  "<fill_commods> <val>dummy</val> </fill_commods>"
225  "<fill_recipe>natu</fill_recipe>"
226  "<fill_size>1</fill_size>"
227  ""
228  "<fiss_commods> <val>stream1</val> <val>stream2</val> <val>stream3</val> </fiss_commods>"
229  "<fiss_size>2.5</fiss_size>"
230  ""
231  "<outcommod>dummyout</outcommod>"
232  "<spectrum>thermal</spectrum>"
233  "<throughput>0</throughput>"
234  ;
235 
236  int simdur = 1;
237  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
238  sim.AddSource("stream1").recipe("spentuox").capacity(1).Finalize();
239  sim.AddSource("stream2").recipe("spentuox").capacity(1).Finalize();
240  sim.AddSource("stream3").recipe("spentuox").capacity(1).Finalize();
241  sim.AddRecipe("spentuox", c_pustream());
242  sim.AddRecipe("natu", c_natu());
243  int id = sim.Run();
244 
245  QueryResult qr = sim.db().Query("Transactions", NULL);
246  EXPECT_EQ(3, qr.rows.size());
247 
248  std::vector<Cond> conds;
249  conds.push_back(Cond("Commodity", "==", std::string("stream1")));
250  qr = sim.db().Query("Transactions", &conds);
251  EXPECT_EQ(1, qr.rows.size());
252 
253  conds[0] = Cond("Commodity", "==", std::string("stream2"));
254  qr = sim.db().Query("Transactions", &conds);
255  EXPECT_EQ(1, qr.rows.size());
256 
257  conds[0] = Cond("Commodity", "==", std::string("stream3"));
258  qr = sim.db().Query("Transactions", &conds);
259  EXPECT_EQ(1, qr.rows.size());
260 }
261 
262 // fissile stream preferences can be specified.
263 TEST(FuelFabTests, FissStreamPrefs) {
264  std::string config =
265  "<fill_commods> <val>dummy</val> </fill_commods>"
266  "<fill_recipe>natu</fill_recipe>"
267  "<fill_size>1</fill_size>"
268  ""
269  "<fiss_commods> <val>stream1</val> <val>stream2</val> <val>stream3</val> </fiss_commods>"
270  "<fiss_commod_prefs> <val>1.0</val> <val>0.1</val> <val>2.0</val> </fiss_commod_prefs>"
271  "<fiss_size>1.5</fiss_size>"
272  ""
273  "<outcommod>dummyout</outcommod>"
274  "<spectrum>thermal</spectrum>"
275  "<throughput>0</throughput>"
276  ;
277 
278  int simdur = 1;
279  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
280  sim.AddSource("stream1").recipe("spentuox").capacity(1).Finalize();
281  sim.AddSource("stream2").recipe("spentuox").capacity(1).Finalize();
282  sim.AddSource("stream3").recipe("spentuox").capacity(1).Finalize();
283  sim.AddRecipe("spentuox", c_pustream());
284  sim.AddRecipe("natu", c_natu());
285  int id = sim.Run();
286 
287  std::vector<Cond> conds;
288  conds.push_back(Cond("Commodity", "==", std::string("stream1")));
289  QueryResult qr = sim.db().Query("Transactions", &conds);
290  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
291  EXPECT_DOUBLE_EQ(0.5, m->quantity());
292 
293  conds[0] = Cond("Commodity", "==", std::string("stream2"));
294  qr = sim.db().Query("Transactions", &conds);
295  EXPECT_EQ(0, qr.rows.size());
296 
297  conds[0] = Cond("Commodity", "==", std::string("stream3"));
298  qr = sim.db().Query("Transactions", &conds);
299  m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
300  EXPECT_DOUBLE_EQ(1.0, m->quantity());
301 }
302 
303 // zero throughput must not result in a zero capacity constraint excception.
304 TEST(FuelFabTests, ZeroThroughput) {
305  std::string config =
306  "<fill_commods> <val>natu</val> </fill_commods>"
307  "<fill_recipe>natu</fill_recipe>"
308  "<fill_size>3.9</fill_size>"
309  ""
310  "<fiss_commods> <val>spentuox</val> </fiss_commods>"
311  "<fiss_recipe>spentuox</fiss_recipe>"
312  "<fiss_size>3.5</fiss_size>"
313  ""
314  "<topup_commod>uox</topup_commod>"
315  "<topup_recipe>uox</topup_recipe>"
316  "<topup_size>3.3</topup_size>"
317  ""
318  "<outcommod>dummyout</outcommod>"
319  "<spectrum>thermal</spectrum>"
320  "<throughput>0</throughput>"
321  ;
322 
323  int simdur = 10;
324  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
325  sim.AddSource("uox").capacity(1).Finalize();
326  sim.AddSource("spentuox").capacity(1).Finalize();
327  sim.AddSource("natu").capacity(1).Finalize();
328  sim.AddRecipe("uox", c_uox());
329  sim.AddRecipe("spentuox", c_pustream());
330  sim.AddRecipe("natu", c_natu());
331  EXPECT_NO_THROW(sim.Run());
332 }
333 
334 // fill, fiss, and topup inventories are all requested for and
335 // filled as expected. Inventory size constraints are properly
336 // enforced after they are full.
337 TEST(FuelFabTests, FillAllInventories) {
338  std::string config =
339  "<fill_commods> <val>natu</val> </fill_commods>"
340  "<fill_recipe>natu</fill_recipe>"
341  "<fill_size>3.9</fill_size>"
342  ""
343  "<fiss_commods> <val>spentuox</val> </fiss_commods>"
344  "<fiss_recipe>spentuox</fiss_recipe>"
345  "<fiss_size>3.5</fiss_size>"
346  ""
347  "<topup_commod>uox</topup_commod>"
348  "<topup_recipe>uox</topup_recipe>"
349  "<topup_size>3.3</topup_size>"
350  ""
351  "<outcommod>dummyout</outcommod>"
352  "<spectrum>thermal</spectrum>"
353  "<throughput>1</throughput>"
354  ;
355 
356  int simdur = 10;
357  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
358  sim.AddSource("uox").capacity(1).Finalize();
359  sim.AddSource("spentuox").capacity(1).Finalize();
360  sim.AddSource("natu").capacity(1).Finalize();
361  sim.AddRecipe("uox", c_uox());
362  sim.AddRecipe("spentuox", c_pustream());
363  sim.AddRecipe("natu", c_natu());
364  int id = sim.Run();
365 
366  cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare(
367  "SELECT SUM(r.Quantity) FROM Transactions AS t"
368  " INNER JOIN Resources AS r ON r.ResourceId = t.ResourceId"
369  " WHERE t.Commodity = ?;"
370  );
371 
372  stmt->BindText(1, "natu");
373  stmt->Step();
374  EXPECT_DOUBLE_EQ(3.9, stmt->GetDouble(0));
375  stmt->Reset();
376  stmt->BindText(1, "spentuox");
377  stmt->Step();
378  EXPECT_DOUBLE_EQ(3.5, stmt->GetDouble(0));
379  stmt->Reset();
380  stmt->BindText(1, "uox");
381  stmt->Step();
382  EXPECT_DOUBLE_EQ(3.3, stmt->GetDouble(0));
383 }
384 
385 // Meet a request requiring zero fill inventory when we have zero fill
386 // inventory quantity.
387 TEST(FuelFabTests, ProvideStraightFiss_WithZeroFill) {
388  std::string config =
389  "<fill_commods> <val>nothing</val> </fill_commods>"
390  "<fill_recipe>natu</fill_recipe>"
391  "<fill_size>100</fill_size>"
392  ""
393  "<fiss_commods> <val>anything</val> </fiss_commods>"
394  "<fiss_recipe>spentuox</fiss_recipe>"
395  "<fiss_size>100</fiss_size>"
396  ""
397  "<outcommod>recyclefuel</outcommod>"
398  "<spectrum>thermal</spectrum>"
399  "<throughput>100</throughput>"
400  ;
401  int simdur = 6;
402  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
403  sim.AddSource("anything").Finalize();
404  sim.AddSink("recyclefuel").recipe("spentuox").capacity(100).Finalize();
405  sim.AddRecipe("uox", c_uox());
406  sim.AddRecipe("spentuox", c_pustream());
407  sim.AddRecipe("natu", c_natu());
408  int id = sim.Run();
409 
410  std::vector<Cond> conds;
411  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
412  QueryResult qr = sim.db().Query("Transactions", NULL);
413  // 6 = 3 receives of inventory, 3 sells of recycled fuel
414  EXPECT_EQ(6, qr.rows.size());
415 }
416 
417 TEST(FuelFabTests, ProvideStraightFill_ZeroFiss) {
418  std::string config =
419  "<fill_commods> <val>anything</val> </fill_commods>"
420  "<fill_recipe>natu</fill_recipe>"
421  "<fill_size>100</fill_size>"
422  ""
423  "<fiss_commods> <val>nothing</val> </fiss_commods>"
424  "<fiss_recipe>spentuox</fiss_recipe>"
425  "<fiss_size>100</fiss_size>"
426  ""
427  "<outcommod>recyclefuel</outcommod>"
428  "<spectrum>thermal</spectrum>"
429  "<throughput>100</throughput>"
430  ;
431  int simdur = 6;
432  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
433  sim.AddSource("anything").Finalize();
434  sim.AddSink("recyclefuel").recipe("natu").capacity(100).Finalize();
435  sim.AddRecipe("uox", c_uox());
436  sim.AddRecipe("spentuox", c_pustream());
437  sim.AddRecipe("natu", c_natu());
438  int id = sim.Run();
439 
440  std::vector<Cond> conds;
441  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
442  QueryResult qr = sim.db().Query("Transactions", NULL);
443  // 6 = 3 receives of inventory, 3 sells of recycled fuel
444  EXPECT_EQ(6, qr.rows.size());
445 }
446 
447 // throughput is properly restricted when faced with many fuel
448 // requests and with ample material inventory.
449 TEST(FuelFabTests, ThroughputLimit) {
450  std::string config =
451  "<fill_commods> <val>anything</val> </fill_commods>"
452  "<fill_recipe>natu</fill_recipe>"
453  "<fill_size>100</fill_size>"
454  ""
455  "<fiss_commods> <val>anything</val> </fiss_commods>"
456  "<fiss_recipe>pustream</fiss_recipe>"
457  "<fiss_size>100</fiss_size>"
458  ""
459  "<outcommod>recyclefuel</outcommod>"
460  "<spectrum>thermal</spectrum>"
461  "<throughput>3</throughput>"
462  ;
463  double throughput = 3;
464 
465  int simdur = 5;
466  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
467  sim.AddSource("anything").lifetime(1).Finalize();
468  sim.AddSink("recyclefuel").recipe("uox").capacity(2*throughput).Finalize();
469  sim.AddRecipe("uox", c_uox());
470  sim.AddRecipe("pustream", c_pustream());
471  sim.AddRecipe("natu", c_natu());
472  int id = sim.Run();
473 
474  QueryResult qr = sim.db().Query("Transactions", NULL);
475  EXPECT_LT(2, qr.rows.size());
476 
477  cyclus::SqlStatement::Ptr stmt = sim.db().db().Prepare(
478  "SELECT SUM(r.Quantity) FROM Transactions AS t"
479  " INNER JOIN Resources AS r ON r.ResourceId = t.ResourceId"
480  " WHERE t.Commodity = 'recyclefuel';"
481  );
482 
483  stmt->Step();
484  EXPECT_DOUBLE_EQ(throughput * (simdur-1), stmt->GetDouble(0));
485 
486  stmt = sim.db().db().Prepare(
487  "SELECT COUNT(*) FROM Transactions WHERE Commodity = 'recyclefuel';"
488  );
489 
490  stmt->Step();
491  EXPECT_DOUBLE_EQ(simdur-1, stmt->GetDouble(0));
492 }
493 
494 // supplied fuel has proper equivalence weights as requested.
495 TEST(FuelFabTests, CorrectMixing) {
496  std::string config =
497  "<fill_commods> <val>natu</val> </fill_commods>"
498  "<fill_recipe>natu</fill_recipe>"
499  "<fill_size>100</fill_size>"
500  ""
501  "<fiss_commods> <val>pustream</val> </fiss_commods>"
502  "<fiss_recipe>pustream</fiss_recipe>"
503  "<fiss_size>100</fiss_size>"
504  ""
505  "<outcommod>recyclefuel</outcommod>"
506  "<spectrum>thermal</spectrum>"
507  "<throughput>100</throughput>"
508  ;
509  int simdur = 3;
510  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
511  sim.AddSource("pustream").Finalize();
512  sim.AddSource("natu").Finalize();
513  sim.AddSink("recyclefuel").recipe("uox").capacity(10).lifetime(2).Finalize();
514  sim.AddRecipe("uox", c_uox());
515  sim.AddRecipe("pustream", c_pustream());
516  sim.AddRecipe("natu", c_natu());
517  int id = sim.Run();
518 
519  std::vector<Cond> conds;
520  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
521  QueryResult qr = sim.db().Query("Transactions", &conds);
522  EXPECT_EQ(1, qr.rows.size());
523 
524  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
525  double got = CosiWeight(m->comp(), "thermal");
526  double w_target = CosiWeight(c_uox(), "thermal");
527  EXPECT_LT(std::abs((w_target-got)/w_target), 0.00001) << "mixed composition not within 0.001% of target";
528 
529  conds.push_back(Cond("Time", "==", 2));
530 
531  // TODO: do hand calcs to verify expected vals below - they are just regression values currently.
532  conds[0] = Cond("Commodity", "==", std::string("natu"));
533  qr = sim.db().Query("Transactions", &conds);
534  m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
535  EXPECT_NEAR(9.7463873197, m->quantity(), 1e-6) << "mixed wrong amount of Nat. U stream";
536 
537  conds[0] = Cond("Commodity", "==", std::string("pustream"));
538  qr = sim.db().Query("Transactions", &conds);
539  m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
540  EXPECT_NEAR(0.25361268029, m->quantity(), 1e-6) << "mixed wrong amount of Pu stream";
541 }
542 
543 // fuel is requested requiring more filler than is available with plenty of
544 // fissile.
545 TEST(FuelFabTests, FillConstrained) {
546  cyclus::Env::SetNucDataPath();
547  std::string config =
548  "<fill_commods> <val>natu</val> </fill_commods>"
549  "<fill_recipe>natu</fill_recipe>"
550  "<fill_size>1</fill_size>"
551  ""
552  "<fiss_commods> <val>pustream</val> </fiss_commods>"
553  "<fiss_recipe>pustream</fiss_recipe>"
554  "<fiss_size>10000</fiss_size>"
555  ""
556  "<outcommod>recyclefuel</outcommod>"
557  "<spectrum>thermal</spectrum>"
558  "<throughput>10000</throughput>"
559  ;
560  double fillinv = 1;
561  int simdur = 2;
562 
563  double w_fill = CosiWeight(c_natu(), "thermal");
564  double w_fiss = CosiWeight(c_pustream(), "thermal");
565  double w_target = CosiWeight(c_uox(), "thermal");
566  double fiss_frac = HighFrac(w_fill, w_target, w_fiss);
567  double fill_frac = LowFrac(w_fill, w_target, w_fiss);
568  fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu());
569  fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream());
570  double max_provide = fillinv / fill_frac;
571 
572  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
573  sim.AddSource("pustream").lifetime(1).Finalize();
574  sim.AddSource("natu").lifetime(1).Finalize();
575  sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize();
576  sim.AddRecipe("uox", c_uox());
577  sim.AddRecipe("pustream", c_pustream());
578  sim.AddRecipe("natu", c_natu());
579  int id = sim.Run();
580 
581  std::vector<Cond> conds;
582  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
583  QueryResult qr = sim.db().Query("Transactions", &conds);
584  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
585 
586  EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fill than available";
587 }
588 
589 // fuel is requested requiring more fissile material than is available with
590 // plenty of filler.
591 TEST(FuelFabTests, FissConstrained) {
592  cyclus::Env::SetNucDataPath();
593  std::string config =
594  "<fill_commods> <val>natu</val> </fill_commods>"
595  "<fill_recipe>natu</fill_recipe>"
596  "<fill_size>10000</fill_size>"
597  ""
598  "<fiss_commods> <val>pustream</val> </fiss_commods>"
599  "<fiss_recipe>pustream</fiss_recipe>"
600  "<fiss_size>1</fiss_size>"
601  ""
602  "<outcommod>recyclefuel</outcommod>"
603  "<spectrum>thermal</spectrum>"
604  "<throughput>10000</throughput>"
605  ;
606  double fissinv = 1;
607  int simdur = 2;
608 
609  double w_fill = CosiWeight(c_natu(), "thermal");
610  double w_fiss = CosiWeight(c_pustream(), "thermal");
611  double w_target = CosiWeight(c_uox(), "thermal");
612  double fiss_frac = HighFrac(w_fill, w_target, w_fiss);
613  double fill_frac = LowFrac(w_fill, w_target, w_fiss);
614  fiss_frac = AtomToMassFrac(fiss_frac, c_pustream(), c_natu());
615  fill_frac = AtomToMassFrac(fill_frac, c_natu(), c_pustream());
616  double max_provide = fissinv / fiss_frac;
617 
618  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
619  sim.AddSource("pustream").lifetime(1).Finalize();
620  sim.AddSource("natu").lifetime(1).Finalize();
621  sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize();
622  sim.AddRecipe("uox", c_uox());
623  sim.AddRecipe("pustream", c_pustream());
624  sim.AddRecipe("natu", c_natu());
625  int id = sim.Run();
626 
627  std::vector<Cond> conds;
628  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
629  QueryResult qr = sim.db().Query("Transactions", &conds);
630  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
631 
632  EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fill than available";
633 }
634 
635 // swap to topup inventory because fissile has too low reactivity.
636 TEST(FuelFabTests, SwapTopup) {
637  std::string config =
638  "<fill_commods> <val>natu</val> </fill_commods>"
639  "<fill_recipe>natu</fill_recipe>"
640  "<fill_size>10000</fill_size>"
641  ""
642  "<fiss_commods> <val>pustreambad</val> </fiss_commods>"
643  "<fiss_recipe>pustreambad</fiss_recipe>"
644  "<fiss_size>10000</fiss_size>"
645  ""
646  "<topup_commod>pustream</topup_commod>"
647  "<topup_recipe>pustream</topup_recipe>"
648  "<topup_size>10000</topup_size>"
649  ""
650  "<outcommod>recyclefuel</outcommod>"
651  "<spectrum>thermal</spectrum>"
652  "<throughput>10000</throughput>"
653  ;
654  int simdur = 3;
655  double sink_cap = 10;
656 
657  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
658  sim.AddSource("pustream").Finalize();
659  sim.AddSource("pustreambad").Finalize();
660  sim.AddSource("natu").Finalize();
661  sim.AddSink("recyclefuel").recipe("uox").capacity(sink_cap).lifetime(2).Finalize();
662  sim.AddRecipe("uox", c_uox());
663  sim.AddRecipe("pustream", c_pustream());
664  sim.AddRecipe("pustreambad", c_pustreambad());
665  sim.AddRecipe("natu", c_natu());
666  int id = sim.Run();
667 
668  std::vector<Cond> conds;
669  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
670  QueryResult qr = sim.db().Query("Transactions", &conds);
671  ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request";
672  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
673  EXPECT_NEAR(sink_cap, m->quantity(), 1e-10) << "supplied fuel was constrained too much";
674 
675  conds[0] = Cond("Commodity", "==", std::string("natu"));
676  conds.push_back(Cond("Time", "==", 2));
677  qr = sim.db().Query("Transactions", &conds);
678  ASSERT_EQ(0, qr.rows.size()) << "failed to swith to topup - used fill - uh oh";
679 
680  conds[0] = Cond("Commodity", "==", std::string("pustream"));
681  conds.push_back(Cond("Time", "==", 2));
682  qr = sim.db().Query("Transactions", &conds);
683  ASSERT_EQ(1, qr.rows.size()) << "didn't get more topup after supposedly using it - why?";
684 }
685 
686 TEST(FuelFabTests, SwapTopup_ZeroFill) {
687  std::string config =
688  "<fill_commods> <val>natu</val> </fill_commods>"
689  "<fill_recipe>natu</fill_recipe>"
690  "<fill_size>0</fill_size>"
691  ""
692  "<fiss_commods> <val>pustreambad</val> </fiss_commods>"
693  "<fiss_recipe>pustreambad</fiss_recipe>"
694  "<fiss_size>10000</fiss_size>"
695  ""
696  "<topup_commod>pustream</topup_commod>"
697  "<topup_recipe>pustream</topup_recipe>"
698  "<topup_size>10000</topup_size>"
699  ""
700  "<outcommod>recyclefuel</outcommod>"
701  "<spectrum>thermal</spectrum>"
702  "<throughput>10000</throughput>"
703  ;
704  int simdur = 3;
705  double sink_cap = 10;
706 
707  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
708  sim.AddSource("pustream").Finalize();
709  sim.AddSource("pustreambad").Finalize();
710  sim.AddSource("natu").Finalize();
711  sim.AddSink("recyclefuel").recipe("uox").capacity(sink_cap).lifetime(2).Finalize();
712  sim.AddRecipe("uox", c_uox());
713  sim.AddRecipe("pustream", c_pustream());
714  sim.AddRecipe("pustreambad", c_pustreambad());
715  sim.AddRecipe("natu", c_natu());
716  int id = sim.Run();
717 
718  std::vector<Cond> conds;
719  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
720  QueryResult qr = sim.db().Query("Transactions", &conds);
721  ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request";
722  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
723  EXPECT_NEAR(sink_cap, m->quantity(), 1e-10) << "supplied fuel was constrained too much";
724 
725  conds[0] = Cond("Commodity", "==", std::string("pustream"));
726  conds.push_back(Cond("Time", "==", 2));
727  qr = sim.db().Query("Transactions", &conds);
728  ASSERT_EQ(1, qr.rows.size()) << "failed to use topup - why?";
729 
730  conds[0] = Cond("Commodity", "==", std::string("pustreambad"));
731  conds.push_back(Cond("Time", "==", 2));
732  qr = sim.db().Query("Transactions", &conds);
733  ASSERT_EQ(1, qr.rows.size()) << "failed to use fiss - why?";
734 }
735 
736 // swap to topup inventory but are limited by topup inventory quantity. This
737 // test makes sure the provided fuel is much less than requested due to a
738 // small topup inventory despite a surplus of fill that can't be used due to
739 // the fiss stream not having a high enough weight (so we must use topup with
740 // fiss).
741 TEST(FuelFabTests, SwapTopup_TopupConstrained) {
742  cyclus::Env::SetNucDataPath();
743  std::string config =
744  "<fill_commods> <val>natu</val> </fill_commods>"
745  "<fill_recipe>natu</fill_recipe>"
746  "<fill_size>10000</fill_size>"
747  ""
748  "<fiss_commods> <val>pustreambad</val> </fiss_commods>"
749  "<fiss_recipe>pustreambad</fiss_recipe>"
750  "<fiss_size>10000</fiss_size>"
751  ""
752  "<topup_commod>pustream</topup_commod>"
753  "<topup_recipe>pustream</topup_recipe>"
754  "<topup_size>1</topup_size>"
755  ""
756  "<outcommod>recyclefuel</outcommod>"
757  "<spectrum>thermal</spectrum>"
758  "<throughput>10000</throughput>"
759  ;
760  double topupinv = 1;
761  int simdur = 2;
762 
763  // compute max fuel mass providable in a single time step
764  double w_fiss = CosiWeight(c_pustreambad(), "thermal");
765  double w_topup = CosiWeight(c_pustream(), "thermal");
766  double w_target = CosiWeight(c_uox(), "thermal");
767  double topup_frac = HighFrac(w_fiss, w_target, w_topup);
768  double fiss_frac = LowFrac(w_fiss, w_target, w_topup);
769  fiss_frac = AtomToMassFrac(fiss_frac, c_pustreambad(), c_pustream());
770  topup_frac = AtomToMassFrac(topup_frac, c_pustream(), c_pustreambad());
771  double max_provide = topupinv / topup_frac;
772 
773  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
774  sim.AddSource("pustream").Finalize();
775  sim.AddSource("pustreambad").Finalize();
776  sim.AddSource("natu").Finalize();
777  sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize();
778  sim.AddRecipe("uox", c_uox());
779  sim.AddRecipe("pustream", c_pustream());
780  sim.AddRecipe("pustreambad", c_pustreambad());
781  sim.AddRecipe("natu", c_natu());
782  int id = sim.Run();
783 
784  std::vector<Cond> conds;
785  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
786  QueryResult qr = sim.db().Query("Transactions", &conds);
787  ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request";
788  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
789 
790  EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fiss than available";
791 }
792 
793 // swap to topup inventory but are limited by fiss inventory quantity. This
794 // test makes sure the provided fuel is much less than requested due to a
795 // small fiss inventory.
796 TEST(FuelFabTests, SwapTopup_FissConstrained) {
797  cyclus::Env::SetNucDataPath();
798  std::string config =
799  "<fill_commods> <val>natu</val> </fill_commods>"
800  "<fill_recipe>natu</fill_recipe>"
801  "<fill_size>0</fill_size>"
802  ""
803  "<fiss_commods> <val>pustreambad</val> </fiss_commods>"
804  "<fiss_recipe>pustreambad</fiss_recipe>"
805  "<fiss_size>1</fiss_size>"
806  ""
807  "<topup_commod>pustream</topup_commod>"
808  "<topup_recipe>pustream</topup_recipe>"
809  "<topup_size>10000</topup_size>"
810  ""
811  "<outcommod>recyclefuel</outcommod>"
812  "<spectrum>thermal</spectrum>"
813  "<throughput>10000</throughput>"
814  ;
815  double fissinv = 1;
816  int simdur = 2;
817 
818  // compute max fuel mass providable in a single time step
819  double w_fiss = CosiWeight(c_pustreambad(), "thermal");
820  double w_topup = CosiWeight(c_pustream(), "thermal");
821  double w_target = CosiWeight(c_uox(), "thermal");
822  double topup_frac = HighFrac(w_fiss, w_target, w_topup);
823  double fiss_frac = LowFrac(w_fiss, w_target, w_topup);
824  fiss_frac = AtomToMassFrac(fiss_frac, c_pustreambad(), c_pustream());
825  topup_frac = AtomToMassFrac(topup_frac, c_pustream(), c_pustreambad());
826  double max_provide = fissinv / fiss_frac;
827 
828  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
829  sim.AddSource("pustream").Finalize();
830  sim.AddSource("pustreambad").Finalize();
831  sim.AddSource("natu").Finalize();
832  sim.AddSink("recyclefuel").recipe("uox").capacity(2 * max_provide).Finalize();
833  sim.AddRecipe("uox", c_uox());
834  sim.AddRecipe("pustream", c_pustream());
835  sim.AddRecipe("pustreambad", c_pustreambad());
836  sim.AddRecipe("natu", c_natu());
837  int id = sim.Run();
838 
839  std::vector<Cond> conds;
840  conds.push_back(Cond("Commodity", "==", std::string("recyclefuel")));
841  QueryResult qr = sim.db().Query("Transactions", &conds);
842  ASSERT_EQ(1, qr.rows.size()) << "failed to meet fuel request";
843  Material::Ptr m = sim.GetMaterial(qr.GetVal<int>("ResourceId"));
844 
845  EXPECT_NEAR(max_provide, m->quantity(), 1e-10) << "matched trade uses more fiss than available";
846 }
847 
848 // Before this test and a fix, the fuel fab (partially) assumed each entire material
849 // buffer had the same composition as the material on top of the buffer when
850 // calculating stream mixing ratios for material to supply. This problem was
851 // compounded by the fact that material weights are computed on an atom basis
852 // and mixing is done on a mass basis - corresponding conversions resulted in
853 // the fab being matched for more than it could actually supply - due to
854 // thinking it had an inventory of higher quality material than was actually
855 // the case. This test makes sure that doesn't happen again.
856 TEST(FuelFabTests, HomogenousBuffers) {
857  std::string config =
858  "<fill_commods> <val>natu</val> </fill_commods>"
859  "<fill_recipe>natu</fill_recipe>"
860  "<fill_size>40</fill_size>"
861  ""
862  "<fiss_commods> <val>stream1</val> </fiss_commods>"
863  "<fiss_size>4</fiss_size>"
864  "<fiss_recipe>spentuox</fiss_recipe>"
865  ""
866  "<outcommod>out</outcommod>"
867  "<spectrum>thermal</spectrum>"
868  "<throughput>1e10</throughput>"
869  ;
870 
871  CompMap m;
872  m[id("u235")] = 7;
873  m[id("u238")] = 86;
874  // the zr90 is important to force the atom-mass basis conversion to push the
875  // dre to overmatch in the direction we want.
876  m[id("zr90")] = 7;
877  Composition::Ptr c = Composition::CreateFromMass(m);
878 
879  int simdur = 5;
880  cyclus::MockSim sim(cyclus::AgentSpec(":cycamore:FuelFab"), config, simdur);
881  sim.AddSource("stream1").start(0).lifetime(1).capacity(.01).recipe("special").Finalize();
882  sim.AddSource("stream1").start(1).lifetime(1).capacity(3.98).recipe("natu").Finalize();
883  sim.AddSource("natu").lifetime(1).Finalize();
884  sim.AddSink("out").start(2).capacity(4).lifetime(1).recipe("uox").Finalize();
885  sim.AddSink("out").start(2).capacity(4).lifetime(1).recipe("uox").Finalize();
886  sim.AddRecipe("uox", c_uox());
887  sim.AddRecipe("spentuox", c_pustream());
888  sim.AddRecipe("natu", c_natu());
889  sim.AddRecipe("special", c);
890  ASSERT_NO_THROW(sim.Run());
891 }
892 
893 } // namespace fuelfabtests
894 } // namespace cycamore
895 
896 
double HighFrac(double w_low, double w_target, double w_high, double eps)
double AtomToMassFrac(double atomfrac, Composition::Ptr c1, Composition::Ptr c2)
double CosiWeight(cyclus::Composition::Ptr c, const std::string &spectrum)
cycamore::GrowthRegion string
bool ValidWeights(double w_low, double w_target, double w_high)
double LowFrac(double w_low, double w_target, double w_high, double eps)