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