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 
27 #pragma cyclus def clone cycamore::Reactor
28 
29 #pragma cyclus def schema cycamore::Reactor
30 
31 #pragma cyclus def annotations cycamore::Reactor
32 
33 #pragma cyclus def infiletodb cycamore::Reactor
34 
35 #pragma cyclus def snapshot cycamore::Reactor
36 
37 #pragma cyclus def snapshotinv cycamore::Reactor
38 
39 #pragma cyclus def initinv cycamore::Reactor
40 
41 void Reactor::InitFrom(Reactor* m) {
42  #pragma cyclus impl initfromcopy cycamore::Reactor
43  cyclus::toolkit::CommodityProducer::Copy(m);
44 }
45 
46 void Reactor::InitFrom(cyclus::QueryableBackend* b) {
47  #pragma cyclus impl initfromdb cycamore::Reactor
48 
49  namespace tk = cyclus::toolkit;
50  tk::CommodityProducer::Add(tk::Commodity(power_name),
51  tk::CommodInfo(power_cap, power_cap));
52 }
53 
54 void Reactor::EnterNotify() {
55  cyclus::Facility::EnterNotify();
56 
57  // If the user ommitted fuel_prefs, we set it to zeros for each fuel
58  // type. Without this segfaults could occur - yuck.
59  if (fuel_prefs.size() == 0) {
60  for (int i = 0; i < fuel_outcommods.size(); i++) {
61  fuel_prefs.push_back(cyclus::kDefaultPref);
62  }
63  }
64 
65  // input consistency checking:
66  int n = recipe_change_times.size();
67  std::stringstream ss;
68  if (recipe_change_commods.size() != n) {
69  ss << "prototype '" << prototype() << "' has "
70  << recipe_change_commods.size()
71  << " recipe_change_commods vals, expected " << n << "\n";
72  }
73  if (recipe_change_in.size() != n) {
74  ss << "prototype '" << prototype() << "' has " << recipe_change_in.size()
75  << " recipe_change_in vals, expected " << n << "\n";
76  }
77  if (recipe_change_out.size() != n) {
78  ss << "prototype '" << prototype() << "' has " << recipe_change_out.size()
79  << " recipe_change_out vals, expected " << n << "\n";
80  }
81 
82  n = pref_change_times.size();
83  if (pref_change_commods.size() != n) {
84  ss << "prototype '" << prototype() << "' has " << pref_change_commods.size()
85  << " pref_change_commods vals, expected " << n << "\n";
86  }
87  if (pref_change_values.size() != n) {
88  ss << "prototype '" << prototype() << "' has " << pref_change_values.size()
89  << " pref_change_values vals, expected " << n << "\n";
90  }
91 
92  if (ss.str().size() > 0) {
93  throw cyclus::ValueError(ss.str());
94  }
95 }
96 
98  return core.count() == 0 && spent.count() == 0;
99 }
100 
101 void Reactor::Tick() {
102  // The following code must go in the Tick so they fire on the time step
103  // following the cycle_step update - allowing for the all reactor events to
104  // occur and be recorded on the "beginning" of a time step. Another reason
105  // they
106  // can't go at the beginnin of the Tock is so that resource exchange has a
107  // chance to occur after the discharge on this same time step.
108 
109  if (retired()) {
110  Record("RETIRED", "");
111 
112  // record the last time series entry if the reactor was operating at the
113  // time of retirement.
114  if (exit_time() == context()->time()) {
115  if (cycle_step > 0 && cycle_step <= cycle_time &&
116  core.count() == n_assem_core) {
117  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, power_cap);
118  } else {
119  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, 0);
120  }
121  }
122 
123  if (context()->time() == exit_time()) { // only need to transmute once
124  Transmute(ceil(static_cast<double>(n_assem_core) / 2.0));
125  }
126  while (core.count() > 0) {
127  if (!Discharge()) {
128  break;
129  }
130  }
131  // in case a cycle lands exactly on our last time step, we will need to
132  // burn a batch from fresh inventory on this time step. When retired,
133  // this batch also needs to be discharged to spent fuel inventory.
134  while (fresh.count() > 0 && spent.space() >= assem_size) {
135  spent.Push(fresh.Pop());
136  }
137  return;
138  }
139 
140  if (cycle_step == cycle_time) {
141  Transmute();
142  Record("CYCLE_END", "");
143  }
144 
145  if (cycle_step >= cycle_time && !discharged) {
146  discharged = Discharge();
147  }
148  if (cycle_step >= cycle_time) {
149  Load();
150  }
151 
152  int t = context()->time();
153 
154  // update preferences
155  for (int i = 0; i < pref_change_times.size(); i++) {
156  int change_t = pref_change_times[i];
157  if (t != change_t) {
158  continue;
159  }
160 
161  std::string incommod = pref_change_commods[i];
162  for (int j = 0; j < fuel_incommods.size(); j++) {
163  if (fuel_incommods[j] == incommod) {
165  break;
166  }
167  }
168  }
169 
170  // update recipes
171  for (int i = 0; i < recipe_change_times.size(); i++) {
172  int change_t = recipe_change_times[i];
173  if (t != change_t) {
174  continue;
175  }
176 
177  std::string incommod = recipe_change_commods[i];
178  for (int j = 0; j < fuel_incommods.size(); j++) {
179  if (fuel_incommods[j] == incommod) {
182  break;
183  }
184  }
185  }
186 }
187 
188 std::set<cyclus::RequestPortfolio<Material>::Ptr> Reactor::GetMatlRequests() {
189  using cyclus::RequestPortfolio;
190 
191  std::set<RequestPortfolio<Material>::Ptr> ports;
192  Material::Ptr m;
193 
194  // second min expression reduces assembles to amount needed until
195  // retirement if it is near.
196  int n_assem_order = n_assem_core - core.count() + n_assem_fresh - fresh.count();
197 
198  if (exit_time() != -1) {
199  // the +1 accounts for the fact that the reactor is alive and gets to
200  // operate during its exit_time time step.
201  int t_left = exit_time() - context()->time() + 1;
202  int t_left_cycle = cycle_time + refuel_time - cycle_step;
203  double n_cycles_left = static_cast<double>(t_left - t_left_cycle) /
204  static_cast<double>(cycle_time + refuel_time);
205  n_cycles_left = ceil(n_cycles_left);
206  int n_need = std::max(0.0, n_cycles_left * n_assem_batch - n_assem_fresh + n_assem_core - core.count());
207  n_assem_order = std::min(n_assem_order, n_need);
208  }
209 
210  if (n_assem_order == 0) {
211  return ports;
212  } else if (retired()) {
213  return ports;
214  }
215 
216  for (int i = 0; i < n_assem_order; i++) {
217  RequestPortfolio<Material>::Ptr port(new RequestPortfolio<Material>());
218  std::vector<Request<Material>*> mreqs;
219  for (int j = 0; j < fuel_incommods.size(); j++) {
220  std::string commod = fuel_incommods[j];
221  double pref = fuel_prefs[j];
222  Composition::Ptr recipe = context()->GetRecipe(fuel_inrecipes[j]);
223  m = Material::CreateUntracked(assem_size, recipe);
224  Request<Material>* r = port->AddRequest(m, this, commod, pref, true);
225  mreqs.push_back(r);
226  }
227  port->AddMutualReqs(mreqs);
228  ports.insert(port);
229  }
230 
231  return ports;
232 }
233 
235  const std::vector<cyclus::Trade<Material> >& trades,
236  std::vector<std::pair<cyclus::Trade<Material>, Material::Ptr> >&
237  responses) {
238  using cyclus::Trade;
239 
240  std::map<std::string, MatVec> mats = PopSpent();
241  for (int i = 0; i < trades.size(); i++) {
242  std::string commod = trades[i].request->commodity();
243  Material::Ptr m = mats[commod].back();
244  mats[commod].pop_back();
245  responses.push_back(std::make_pair(trades[i], m));
246  res_indexes.erase(m->obj_id());
247  }
248  PushSpent(mats); // return leftovers back to spent buffer
249 }
250 
251 void Reactor::AcceptMatlTrades(const std::vector<
252  std::pair<cyclus::Trade<Material>, Material::Ptr> >& responses) {
253  std::vector<std::pair<cyclus::Trade<cyclus::Material>,
254  cyclus::Material::Ptr> >::const_iterator trade;
255 
256  std::stringstream ss;
257  int nload = std::min((int)responses.size(), n_assem_core - core.count());
258  if (nload > 0) {
259  ss << nload << " assemblies";
260  Record("LOAD", ss.str());
261  }
262 
263  for (trade = responses.begin(); trade != responses.end(); ++trade) {
264  std::string commod = trade->first.request->commodity();
265  Material::Ptr m = trade->second;
266  index_res(m, commod);
267 
268  if (core.count() < n_assem_core) {
269  core.Push(m);
270  } else {
271  fresh.Push(m);
272  }
273  }
274 }
275 
276 std::set<cyclus::BidPortfolio<Material>::Ptr> Reactor::GetMatlBids(
277  cyclus::CommodMap<Material>::type& commod_requests) {
278  using cyclus::BidPortfolio;
279 
280  std::set<BidPortfolio<Material>::Ptr> ports;
281 
282  bool gotmats = false;
283  std::map<std::string, MatVec> all_mats;
284 
285  if (uniq_outcommods_.empty()) {
286  for (int i = 0; i < fuel_outcommods.size(); i++) {
288  }
289  }
290 
291  std::set<std::string>::iterator it;
292  for (it = uniq_outcommods_.begin(); it != uniq_outcommods_.end(); ++it) {
293  std::string commod = *it;
294  std::vector<Request<Material>*>& reqs = commod_requests[commod];
295  if (reqs.size() == 0) {
296  continue;
297  } else if (!gotmats) {
298  all_mats = PeekSpent();
299  }
300 
301  MatVec mats = all_mats[commod];
302  if (mats.size() == 0) {
303  continue;
304  }
305 
306  BidPortfolio<Material>::Ptr port(new BidPortfolio<Material>());
307 
308  for (int j = 0; j < reqs.size(); j++) {
309  Request<Material>* req = reqs[j];
310  double tot_bid = 0;
311  for (int k = 0; k < mats.size(); k++) {
312  Material::Ptr m = mats[k];
313  tot_bid += m->quantity();
314  port->AddBid(req, m, this, true);
315  if (tot_bid >= req->target()->quantity()) {
316  break;
317  }
318  }
319  }
320 
321  double tot_qty = 0;
322  for (int j = 0; j < mats.size(); j++) {
323  tot_qty += mats[j]->quantity();
324  }
325  cyclus::CapacityConstraint<Material> cc(tot_qty);
326  port->AddConstraint(cc);
327  ports.insert(port);
328  }
329 
330  return ports;
331 }
332 
333 void Reactor::Tock() {
334  if (retired()) {
335  return;
336  }
337 
338  if (cycle_step >= cycle_time + refuel_time && core.count() == n_assem_core) {
339  discharged = false;
340  cycle_step = 0;
341  }
342 
343  if (cycle_step == 0 && core.count() == n_assem_core) {
344  Record("CYCLE_START", "");
345  }
346 
347  if (cycle_step >= 0 && cycle_step < cycle_time &&
348  core.count() == n_assem_core) {
349  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, power_cap);
350  } else {
351  cyclus::toolkit::RecordTimeSeries<cyclus::toolkit::POWER>(this, 0);
352  }
353 
354  // "if" prevents starting cycle after initial deployment until core is full
355  // even though cycle_step is its initial zero.
356  if (cycle_step > 0 || core.count() == n_assem_core) {
357  cycle_step++;
358  }
359 }
360 
362 
363 void Reactor::Transmute(int n_assem) {
364  MatVec old = core.PopN(std::min(n_assem, core.count()));
365  core.Push(old);
366  if (core.count() > old.size()) {
367  // rotate untransmuted mats back to back of buffer
368  core.Push(core.PopN(core.count() - old.size()));
369  }
370 
371  std::stringstream ss;
372  ss << old.size() << " assemblies";
373  Record("TRANSMUTE", ss.str());
374 
375  for (int i = 0; i < old.size(); i++) {
376  old[i]->Transmute(context()->GetRecipe(fuel_outrecipe(old[i])));
377  }
378 }
379 
380 std::map<std::string, MatVec> Reactor::PeekSpent() {
381  std::map<std::string, MatVec> mapped;
382  MatVec mats = spent.PopN(spent.count());
383  spent.Push(mats);
384  for (int i = 0; i < mats.size(); i++) {
385  std::string commod = fuel_outcommod(mats[i]);
386  mapped[commod].push_back(mats[i]);
387  }
388  return mapped;
389 }
390 
391 bool Reactor::Discharge() {
392  int npop = std::min(n_assem_batch, core.count());
393  if (n_assem_spent - spent.count() < npop) {
394  Record("DISCHARGE", "failed");
395  return false; // not enough room in spent buffer
396  }
397 
398  std::stringstream ss;
399  ss << npop << " assemblies";
400  Record("DISCHARGE", ss.str());
401 
402  spent.Push(core.PopN(npop));
403  return true;
404 }
405 
406 void Reactor::Load() {
407  int n = std::min(n_assem_core - core.count(), fresh.count());
408  if (n == 0) {
409  return;
410  }
411 
412  std::stringstream ss;
413  ss << n << " assemblies";
414  Record("LOAD", ss.str());
415  core.Push(fresh.PopN(n));
416 }
417 
418 std::string Reactor::fuel_incommod(Material::Ptr m) {
419  int i = res_indexes[m->obj_id()];
420  if (i >= fuel_incommods.size()) {
421  throw KeyError("cycamore::Reactor - no incommod for material object");
422  }
423  return fuel_incommods[i];
424 }
425 
426 std::string Reactor::fuel_outcommod(Material::Ptr m) {
427  int i = res_indexes[m->obj_id()];
428  if (i >= fuel_outcommods.size()) {
429  throw KeyError("cycamore::Reactor - no outcommod for material object");
430  }
431  return fuel_outcommods[i];
432 }
433 
434 std::string Reactor::fuel_inrecipe(Material::Ptr m) {
435  int i = res_indexes[m->obj_id()];
436  if (i >= fuel_inrecipes.size()) {
437  throw KeyError("cycamore::Reactor - no inrecipe for material object");
438  }
439  return fuel_inrecipes[i];
440 }
441 
442 std::string Reactor::fuel_outrecipe(Material::Ptr m) {
443  int i = res_indexes[m->obj_id()];
444  if (i >= fuel_outrecipes.size()) {
445  throw KeyError("cycamore::Reactor - no outrecipe for material object");
446  }
447  return fuel_outrecipes[i];
448 }
449 
450 double Reactor::fuel_pref(Material::Ptr m) {
451  int i = res_indexes[m->obj_id()];
452  if (i >= fuel_prefs.size()) {
453  return 0;
454  }
455  return fuel_prefs[i];
456 }
457 
458 void Reactor::index_res(cyclus::Resource::Ptr m, std::string incommod) {
459  for (int i = 0; i < fuel_incommods.size(); i++) {
460  if (fuel_incommods[i] == incommod) {
461  res_indexes[m->obj_id()] = i;
462  return;
463  }
464  }
465  throw ValueError(
466  "cycamore::Reactor - received unsupported incommod material");
467 }
468 
469 std::map<std::string, MatVec> Reactor::PopSpent() {
470  MatVec mats = spent.PopN(spent.count());
471  std::map<std::string, MatVec> mapped;
472  for (int i = 0; i < mats.size(); i++) {
473  std::string commod = fuel_outcommod(mats[i]);
474  mapped[commod].push_back(mats[i]);
475  }
476 
477  // needed so we trade away oldest assemblies first
478  std::map<std::string, MatVec>::iterator it;
479  for (it = mapped.begin(); it != mapped.end(); ++it) {
480  std::reverse(it->second.begin(), it->second.end());
481  }
482 
483  return mapped;
484 }
485 
486 void Reactor::PushSpent(std::map<std::string, MatVec> leftover) {
487  std::map<std::string, MatVec>::iterator it;
488  for (it = leftover.begin(); it != leftover.end(); ++it) {
489  // undo reverse in PopSpent to make sure oldest assemblies come out first
490  std::reverse(it->second.begin(), it->second.end());
491  spent.Push(it->second);
492  }
493 }
494 
495 void Reactor::Record(std::string name, std::string val) {
496  context()
497  ->NewDatum("ReactorEvents")
498  ->AddVal("AgentId", id())
499  ->AddVal("Time", context()->time())
500  ->AddVal("Event", name)
501  ->AddVal("Value", val)
502  ->Record();
503 }
504 
505 extern "C" cyclus::Agent* ConstructReactor(cyclus::Context* ctx) {
506  return new Reactor(ctx);
507 }
508 
509 } // 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)
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.
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
home mouginot work app cyclus cycamore _tmp_build cycamore reactor cc
cycamore::GrowthRegion string
std::vector< double > fuel_prefs
cyclus::toolkit::ResBuf< cyclus::Material > fresh
virtual std::set< cyclus::RequestPortfolio< cyclus::Material >::Ptr > GetMatlRequests()
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
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
virtual void InitFrom(cycamore::Reactor *m)