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