CYCAMORE
src/reactor.cc
Go to the documentation of this file.
1 #include "reactor.h"
2 
3 using cyclus::Material;
4 using cyclus::Composition;
5 using cyclus::toolkit::ResBuf;
6 using cyclus::toolkit::MatVec;
7 using cyclus::KeyError;
8 using cyclus::ValueError;
9 using cyclus::Request;
10 
11 namespace cycamore {
12 
13 Reactor::Reactor(cyclus::Context* ctx)
14  : cyclus::Facility(ctx),
15  n_assem_batch(0),
16  assem_size(0),
17  n_assem_core(0),
18  n_assem_spent(0),
19  n_assem_fresh(0),
20  cycle_time(0),
21  refuel_time(0),
22  cycle_step(0),
23  power_cap(0),
24  power_name("power"),
25  discharged(false),
26  latitude(0.0),
27  longitude(0.0),
29 
30 
31 #pragma cyclus def clone cycamore::Reactor
32 
33 #pragma cyclus def schema cycamore::Reactor
34 
35 #pragma cyclus def annotations cycamore::Reactor
36 
37 #pragma cyclus def infiletodb cycamore::Reactor
38 
39 #pragma cyclus def snapshot cycamore::Reactor
40 
41 #pragma cyclus def snapshotinv cycamore::Reactor
42 
43 #pragma cyclus def initinv cycamore::Reactor
44 
45 void Reactor::InitFrom(Reactor* m) {
46  #pragma cyclus impl initfromcopy cycamore::Reactor
47  cyclus::toolkit::CommodityProducer::Copy(m);
48 }
49 
50 void Reactor::InitFrom(cyclus::QueryableBackend* b) {
51  #pragma cyclus impl initfromdb cycamore::Reactor
52 
53  namespace tk = cyclus::toolkit;
54  tk::CommodityProducer::Add(tk::Commodity(power_name),
55  tk::CommodInfo(power_cap, power_cap));
56 
57  for (int i = 0; i < side_products.size(); i++) {
58  tk::CommodityProducer::Add(tk::Commodity(side_products[i]),
59  tk::CommodInfo(side_product_quantity[i],
61  }
62 }
63 
64 void Reactor::EnterNotify() {
65  cyclus::Facility::EnterNotify();
66 
67  // If the user ommitted fuel_prefs, we set it to zeros for each fuel
68  // type. Without this segfaults could occur - yuck.
69  if (fuel_prefs.size() == 0) {
70  for (int i = 0; i < fuel_outcommods.size(); i++) {
71  fuel_prefs.push_back(cyclus::kDefaultPref);
72  }
73  }
74 
75  // Test if any side products have been defined.
76  if (side_products.size() == 0){
77  hybrid_ = false;
78  }
79 
80  // input consistency checking:
81  int n = recipe_change_times.size();
82  std::stringstream ss;
83  if (recipe_change_commods.size() != n) {
84  ss << "prototype '" << prototype() << "' has "
85  << recipe_change_commods.size()
86  << " recipe_change_commods vals, expected " << n << "\n";
87  }
88  if (recipe_change_in.size() != n) {
89  ss << "prototype '" << prototype() << "' has " << recipe_change_in.size()
90  << " recipe_change_in vals, expected " << n << "\n";
91  }
92  if (recipe_change_out.size() != n) {
93  ss << "prototype '" << prototype() << "' has " << recipe_change_out.size()
94  << " recipe_change_out vals, expected " << n << "\n";
95  }
96 
97  n = pref_change_times.size();
98  if (pref_change_commods.size() != n) {
99  ss << "prototype '" << prototype() << "' has " << pref_change_commods.size()
100  << " pref_change_commods vals, expected " << n << "\n";
101  }
102  if (pref_change_values.size() != n) {
103  ss << "prototype '" << prototype() << "' has " << pref_change_values.size()
104  << " pref_change_values vals, expected " << n << "\n";
105  }
106 
107  if (ss.str().size() > 0) {
108  throw cyclus::ValueError(ss.str());
109  }
110  RecordPosition();
111 }
112 
114  return core.count() == 0 && spent.count() == 0;
115 }
116 
117 void Reactor::Tick() {
118  // The following code must go in the Tick so they fire on the time step
119  // following the cycle_step update - allowing for the all reactor events to
120  // occur and be recorded on the "beginning" of a time step. Another reason
121  // they
122  // can't go at the beginnin of the Tock is so that resource exchange has a
123  // chance to occur after the discharge on this same time step.
124 
125  if (retired()) {
126  Record("RETIRED", "");
127 
128  if (context()->time() == exit_time() + 1) { // only need to transmute once
129  if (decom_transmute_all == true) {
130  Transmute(ceil(static_cast<double>(n_assem_core)));
131  }
132  else {
133  Transmute(ceil(static_cast<double>(n_assem_core) / 2.0));
134  }
135  }
136  while (core.count() > 0) {
137  if (!Discharge()) {
138  break;
139  }
140  }
141  // in case a cycle lands exactly on our last time step, we will need to
142  // burn a batch from fresh inventory on this time step. When retired,
143  // this batch also needs to be discharged to spent fuel inventory.
144  while (fresh.count() > 0 && spent.space() >= assem_size) {
145  spent.Push(fresh.Pop());
146  }
148  Decommission();
149  }
150  return;
151  }
152 
153  if (cycle_step == cycle_time) {
154  Transmute();
155  Record("CYCLE_END", "");
156  }
157 
158  if (cycle_step >= cycle_time && !discharged) {
159  discharged = Discharge();
160  }
161  if (cycle_step >= cycle_time) {
162  Load();
163  }
164 
165  int t = context()->time();
166 
167  // update preferences
168  for (int i = 0; i < pref_change_times.size(); i++) {
169  int change_t = pref_change_times[i];
170  if (t != change_t) {
171  continue;
172  }
173 
174  std::string incommod = pref_change_commods[i];
175  for (int j = 0; j < fuel_incommods.size(); j++) {
176  if (fuel_incommods[j] == incommod) {
178  break;
179  }
180  }
181  }
182 
183  // update recipes
184  for (int i = 0; i < recipe_change_times.size(); i++) {
185  int change_t = recipe_change_times[i];
186  if (t != change_t) {
187  continue;
188  }
189 
190  std::string incommod = recipe_change_commods[i];
191  for (int j = 0; j < fuel_incommods.size(); j++) {
192  if (fuel_incommods[j] == incommod) {
195  break;
196  }
197  }
198  }
199 }
200 
201 std::set<cyclus::RequestPortfolio<Material>::Ptr> Reactor::GetMatlRequests() {
202  using cyclus::RequestPortfolio;
203 
204  std::set<RequestPortfolio<Material>::Ptr> ports;
205  Material::Ptr m;
206 
207  // second min expression reduces assembles to amount needed until
208  // retirement if it is near.
209  int n_assem_order = n_assem_core - core.count() + n_assem_fresh - fresh.count();
210 
211  if (exit_time() != -1) {
212  // the +1 accounts for the fact that the reactor is alive and gets to
213  // operate during its exit_time time step.
214  int t_left = exit_time() - context()->time() + 1;
215  int t_left_cycle = cycle_time + refuel_time - cycle_step;
216  double n_cycles_left = static_cast<double>(t_left - t_left_cycle) /
217  static_cast<double>(cycle_time + refuel_time);
218  n_cycles_left = ceil(n_cycles_left);
219  int n_need = std::max(0.0, n_cycles_left * n_assem_batch - n_assem_fresh + n_assem_core - core.count());
220  n_assem_order = std::min(n_assem_order, n_need);
221  }
222 
223  if (n_assem_order == 0) {
224  return ports;
225  } else if (retired()) {
226  return ports;
227  }
228 
229  for (int i = 0; i < n_assem_order; i++) {
230  RequestPortfolio<Material>::Ptr port(new RequestPortfolio<Material>());
231  std::vector<Request<Material>*> mreqs;
232  for (int j = 0; j < fuel_incommods.size(); j++) {
233  std::string commod = fuel_incommods[j];
234  double pref = fuel_prefs[j];
235  Composition::Ptr recipe = context()->GetRecipe(fuel_inrecipes[j]);
236  m = Material::CreateUntracked(assem_size, recipe);
237 
238  Request<Material>* r = port->AddRequest(m, this, commod, pref, true);
239  mreqs.push_back(r);
240  }
241 
242  std::vector<double>::iterator result;
243  result = std::max_element(fuel_prefs.begin(), fuel_prefs.end());
244  int max_index = std::distance(fuel_prefs.begin(), result);
245 
246  cyclus::toolkit::RecordTimeSeries<double>("demand"+fuel_incommods[max_index], this,
247  assem_size * n_assem_order) ;
248 
249  port->AddMutualReqs(mreqs);
250  ports.insert(port);
251  }
252 
253  return ports;
254 }
255 
257  const std::vector<cyclus::Trade<Material> >& trades,
258  std::vector<std::pair<cyclus::Trade<Material>, Material::Ptr> >&
259  responses) {
260  using cyclus::Trade;
261 
262  std::map<std::string, MatVec> mats = PopSpent();
263  for (int i = 0; i < trades.size(); i++) {
264  std::string commod = trades[i].request->commodity();
265  Material::Ptr m = mats[commod].back();
266  mats[commod].pop_back();
267  responses.push_back(std::make_pair(trades[i], m));
268  res_indexes.erase(m->obj_id());
269  }
270  PushSpent(mats); // return leftovers back to spent buffer
271 }
272 
273 void Reactor::AcceptMatlTrades(const std::vector<
274  std::pair<cyclus::Trade<Material>, Material::Ptr> >& responses) {
275  std::vector<std::pair<cyclus::Trade<cyclus::Material>,
276  cyclus::Material::Ptr> >::const_iterator trade;
277 
278  std::stringstream ss;
279  int nload = std::min((int)responses.size(), n_assem_core - core.count());
280  if (nload > 0) {
281  ss << nload << " assemblies";
282  Record("LOAD", ss.str());
283  }
284 
285  for (trade = responses.begin(); trade != responses.end(); ++trade) {
286  std::string commod = trade->first.request->commodity();
287  Material::Ptr m = trade->second;
288  index_res(m, commod);
289 
290  if (core.count() < n_assem_core) {
291  core.Push(m);
292  } else {
293  fresh.Push(m);
294  }
295  }
296 }
297 
298 std::set<cyclus::BidPortfolio<Material>::Ptr> Reactor::GetMatlBids(
299  cyclus::CommodMap<Material>::type& commod_requests) {
300  using cyclus::BidPortfolio;
301  std::set<BidPortfolio<Material>::Ptr> ports;
302 
303  bool gotmats = false;
304  std::map<std::string, MatVec> all_mats;
305 
306  if (uniq_outcommods_.empty()) {
307  for (int i = 0; i < fuel_outcommods.size(); i++) {
309  }
310  }
311 
312  std::set<std::string>::iterator it;
313  for (it = uniq_outcommods_.begin(); it != uniq_outcommods_.end(); ++it) {
314  std::string commod = *it;
315  std::vector<Request<Material>*>& reqs = commod_requests[commod];
316  if (reqs.size() == 0) {
317  continue;
318  } else if (!gotmats) {
319  all_mats = PeekSpent();
320  }
321 
322  MatVec mats = all_mats[commod];
323  if (mats.size() == 0) {
324  continue;
325  }
326 
327  BidPortfolio<Material>::Ptr port(new BidPortfolio<Material>());
328 
329  for (int j = 0; j < reqs.size(); j++) {
330  Request<Material>* req = reqs[j];
331  double tot_bid = 0;
332  for (int k = 0; k < mats.size(); k++) {
333  Material::Ptr m = mats[k];
334  tot_bid += m->quantity();
335  port->AddBid(req, m, this, true);
336  if (tot_bid >= req->target()->quantity()) {
337  break;
338  }
339  }
340  }
341 
342  double tot_qty = 0;
343  for (int j = 0; j < mats.size(); j++) {
344  tot_qty += mats[j]->quantity();
345  }
346 
347  cyclus::CapacityConstraint<Material> cc(tot_qty);
348  port->AddConstraint(cc);
349  ports.insert(port);
350  }
351 
352  return ports;
353 }
354 
355 void Reactor::Tock() {
356  if (retired()) {
357  return;
358  }
359 
360  // Check that irradiation and refueling periods are over, that
361  // the core is full and that fuel was successfully discharged in this refueling time.
362  // If this is the case, then a new cycle will be initiated.
363  if (cycle_step >= cycle_time + refuel_time && core.count() == n_assem_core && discharged == true) {
364  discharged = false;
365  cycle_step = 0;
366  }
367 
368  if (cycle_step == 0 && core.count() == n_assem_core) {
369  Record("CYCLE_START", "");
370  }
371 
372  if (cycle_step >= 0 && cycle_step < cycle_time &&
373  core.count() == n_assem_core) {
374  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, power_cap);
375  cyclus::toolkit::RecordTimeSeries<double>("supplyPOWER", this, power_cap);
376  RecordSideProduct(true);
377  } else {
378  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, 0);
379  cyclus::toolkit::RecordTimeSeries<double>("supplyPOWER", this, 0);
380  RecordSideProduct(false);
381  }
382 
383  // "if" prevents starting cycle after initial deployment until core is full
384  // even though cycle_step is its initial zero.
385  if (cycle_step > 0 || core.count() == n_assem_core) {
386  cycle_step++;
387  }
388 }
389 
391 
392 void Reactor::Transmute(int n_assem) {
393  MatVec old = core.PopN(std::min(n_assem, core.count()));
394  core.Push(old);
395  if (core.count() > old.size()) {
396  // rotate untransmuted mats back to back of buffer
397  core.Push(core.PopN(core.count() - old.size()));
398  }
399 
400  std::stringstream ss;
401  ss << old.size() << " assemblies";
402  Record("TRANSMUTE", ss.str());
403 
404  for (int i = 0; i < old.size(); i++) {
405  old[i]->Transmute(context()->GetRecipe(fuel_outrecipe(old[i])));
406  }
407 }
408 
409 std::map<std::string, MatVec> Reactor::PeekSpent() {
410  std::map<std::string, MatVec> mapped;
411  MatVec mats = spent.PopN(spent.count());
412  spent.Push(mats);
413  for (int i = 0; i < mats.size(); i++) {
414  std::string commod = fuel_outcommod(mats[i]);
415  mapped[commod].push_back(mats[i]);
416  }
417  return mapped;
418 }
419 
420 bool Reactor::Discharge() {
421  int npop = std::min(n_assem_batch, core.count());
422  if (n_assem_spent - spent.count() < npop) {
423  Record("DISCHARGE", "failed");
424  return false; // not enough room in spent buffer
425  }
426 
427  std::stringstream ss;
428  ss << npop << " assemblies";
429  Record("DISCHARGE", ss.str());
430  spent.Push(core.PopN(npop));
431 
432  std::map<std::string, MatVec> spent_mats;
433  for (int i = 0; i < fuel_outcommods.size(); i++) {
434  spent_mats = PeekSpent();
435  MatVec mats = spent_mats[fuel_outcommods[i]];
436  double tot_spent = 0;
437  for (int j = 0; j<mats.size(); j++){
438  Material::Ptr m = mats[j];
439  tot_spent += m->quantity();
440  }
441  cyclus::toolkit::RecordTimeSeries<double>("supply"+fuel_outcommods[i], this, tot_spent);
442  }
443 
444  return true;
445 }
446 
447 void Reactor::Load() {
448  int n = std::min(n_assem_core - core.count(), fresh.count());
449  if (n == 0) {
450  return;
451  }
452 
453  std::stringstream ss;
454  ss << n << " assemblies";
455  Record("LOAD", ss.str());
456  core.Push(fresh.PopN(n));
457 }
458 
459 std::string Reactor::fuel_incommod(Material::Ptr m) {
460  int i = res_indexes[m->obj_id()];
461  if (i >= fuel_incommods.size()) {
462  throw KeyError("cycamore::Reactor - no incommod for material object");
463  }
464  return fuel_incommods[i];
465 }
466 
467 std::string Reactor::fuel_outcommod(Material::Ptr m) {
468  int i = res_indexes[m->obj_id()];
469  if (i >= fuel_outcommods.size()) {
470  throw KeyError("cycamore::Reactor - no outcommod for material object");
471  }
472  return fuel_outcommods[i];
473 }
474 
475 std::string Reactor::fuel_inrecipe(Material::Ptr m) {
476  int i = res_indexes[m->obj_id()];
477  if (i >= fuel_inrecipes.size()) {
478  throw KeyError("cycamore::Reactor - no inrecipe for material object");
479  }
480  return fuel_inrecipes[i];
481 }
482 
483 std::string Reactor::fuel_outrecipe(Material::Ptr m) {
484  int i = res_indexes[m->obj_id()];
485  if (i >= fuel_outrecipes.size()) {
486  throw KeyError("cycamore::Reactor - no outrecipe for material object");
487  }
488  return fuel_outrecipes[i];
489 }
490 
491 double Reactor::fuel_pref(Material::Ptr m) {
492  int i = res_indexes[m->obj_id()];
493  if (i >= fuel_prefs.size()) {
494  return 0;
495  }
496  return fuel_prefs[i];
497 }
498 
499 void Reactor::index_res(cyclus::Resource::Ptr m, std::string incommod) {
500  for (int i = 0; i < fuel_incommods.size(); i++) {
501  if (fuel_incommods[i] == incommod) {
502  res_indexes[m->obj_id()] = i;
503  return;
504  }
505  }
506  throw ValueError(
507  "cycamore::Reactor - received unsupported incommod material");
508 }
509 
510 std::map<std::string, MatVec> Reactor::PopSpent() {
511  MatVec mats = spent.PopN(spent.count());
512  std::map<std::string, MatVec> mapped;
513  for (int i = 0; i < mats.size(); i++) {
514  std::string commod = fuel_outcommod(mats[i]);
515  mapped[commod].push_back(mats[i]);
516  }
517 
518  // needed so we trade away oldest assemblies first
519  std::map<std::string, MatVec>::iterator it;
520  for (it = mapped.begin(); it != mapped.end(); ++it) {
521  std::reverse(it->second.begin(), it->second.end());
522  }
523 
524  return mapped;
525 }
526 
527 void Reactor::PushSpent(std::map<std::string, MatVec> leftover) {
528  std::map<std::string, MatVec>::iterator it;
529  for (it = leftover.begin(); it != leftover.end(); ++it) {
530  // undo reverse in PopSpent to make sure oldest assemblies come out first
531  std::reverse(it->second.begin(), it->second.end());
532  spent.Push(it->second);
533  }
534 }
535 
536 void Reactor::RecordSideProduct(bool produce){
537  if (hybrid_){
538  double value;
539  for (int i = 0; i < side_products.size(); i++) {
540  if (produce){
541  value = side_product_quantity[i];
542  }
543  else {
544  value = 0;
545  }
546 
547  context()
548  ->NewDatum("ReactorSideProducts")
549  ->AddVal("AgentId", id())
550  ->AddVal("Time", context()->time())
551  ->AddVal("Product", side_products[i])
552  ->AddVal("Value", value)
553  ->Record();
554  }
555  }
556 }
557 
558 void Reactor::Record(std::string name, std::string val) {
559  context()
560  ->NewDatum("ReactorEvents")
561  ->AddVal("AgentId", id())
562  ->AddVal("Time", context()->time())
563  ->AddVal("Event", name)
564  ->AddVal("Value", val)
565  ->Record();
566 }
567 
569  std::string specification = this->spec();
570  context()
571  ->NewDatum("AgentPosition")
572  ->AddVal("Spec", specification)
573  ->AddVal("Prototype", this->prototype())
574  ->AddVal("AgentId", id())
575  ->AddVal("Latitude", latitude)
576  ->AddVal("Longitude", longitude)
577  ->Record();
578 }
579 
580 extern "C" cyclus::Agent* ConstructReactor(cyclus::Context* ctx) {
581  return new Reactor(ctx);
582 }
583 
584 } // namespace cycamore
std::set< std::string > uniq_outcommods_
double fuel_pref(cyclus::Material::Ptr m)
std::map< std::string, cyclus::toolkit::MatVec > PeekSpent()
Returns all spent assemblies indexed by outcommod without removing them from the spent fuel buffer...
std::vector< std::string > recipe_change_out
std::map< std::string, cyclus::toolkit::MatVec > PopSpent()
Returns all spent assemblies indexed by outcommod - removing them from the spent fuel buffer...
std::string fuel_outrecipe(cyclus::Material::Ptr m)
std::vector< std::string > fuel_outrecipes
void Transmute()
Transmute the batch that is about to be discharged from the core to its fully burnt state as defined ...
std::vector< std::string > recipe_change_in
std::vector< double > pref_change_values
Reactor(cyclus::Context *ctx)
cyclus::Agent * ConstructReactor(cyclus::Context *ctx)
std::vector< std::string > side_products
void Load()
Top up core inventory as much as possible.
bool Discharge()
Discharge a batch from the core if there is room in the spent fuel inventory.
double longitude
std::vector< std::string > fuel_incommods
std::map< int, int > res_indexes
void PushSpent(std::map< std::string, cyclus::toolkit::MatVec > leftover)
Complement of PopSpent - must be called with all materials passed that were not traded away to other ...
std::vector< std::string > fuel_inrecipes
std::vector< int > recipe_change_times
cycamore::GrowthRegion string
std::vector< double > fuel_prefs
cyclus::toolkit::ResBuf< cyclus::Material > fresh
virtual std::set< cyclus::RequestPortfolio< cyclus::Material >::Ptr > GetMatlRequests()
std::vector< double > side_product_quantity
virtual std::set< cyclus::BidPortfolio< cyclus::Material >::Ptr > GetMatlBids(cyclus::CommodMap< cyclus::Material >::type &commod_requests)
std::string fuel_inrecipe(cyclus::Material::Ptr m)
virtual void GetMatlTrades(const std::vector< cyclus::Trade< cyclus::Material > > &trades, std::vector< std::pair< cyclus::Trade< cyclus::Material >, cyclus::Material::Ptr > > &responses)
cyclus::toolkit::ResBuf< cyclus::Material > core
cyclus::toolkit::Position coordinates
std::vector< std::string > pref_change_commods
cyclus::toolkit::ResBuf< cyclus::Material > spent
std::string fuel_outcommod(cyclus::Material::Ptr m)
std::vector< int > pref_change_times
void Record(std::string name, std::string val)
Records a reactor event to the output db with the given name and note val.
virtual void AcceptMatlTrades(const std::vector< std::pair< cyclus::Trade< cyclus::Material >, cyclus::Material::Ptr > > &responses)
std::vector< std::string > recipe_change_commods
std::string fuel_incommod(cyclus::Material::Ptr m)
void index_res(cyclus::Resource::Ptr m, std::string incommod)
Store fuel info index for the given resource received on incommod.
std::vector< std::string > fuel_outcommods
void RecordPosition()
Records an agent&#39;s latitude and longitude to the output db.
virtual void InitFrom(cycamore::Reactor *m)
void RecordSideProduct(bool produce)
Records production of side products from the reactor.