|
| 1 | + |
| 2 | +Advanced Agent Creation |
| 3 | +=============================== |
| 4 | + |
| 5 | +This chapter offers a comprehensive walkthrough of developing a fully functional and feature-rich **Cyclus Facility archetype**. It is intended for users who have completed the basic C++ agent tutorial and are ready to begin writing production-grade archetypes with more advanced capabilities. |
| 6 | + |
| 7 | +By the end of this chapter, you will understand how to: |
| 8 | + |
| 9 | +* Declare validated input parameters and manage internal state. |
| 10 | +* Interact with Cyclus's Dynamic Resource Exchange (DRE) to request and accept trades. |
| 11 | +* Use toolkit classes for material management, querying, and time series. |
| 12 | +* Implement lifecycle methods (`Tick`, `Tock`, `EnterNotify`) effectively. |
| 13 | +* Incorporate logging, testing, and optional output recording. |
| 14 | + |
| 15 | +The concepts are demonstrated using a simplified but illustrative example facility: an **AdvancedReactor**, which consumes fuel, burns it for a defined residence time, and then discharges spent fuel. |
| 16 | + |
| 17 | +--- |
| 18 | + |
| 19 | +1. Agent Structure and Metadata |
| 20 | + |
| 21 | +--- |
| 22 | + |
| 23 | +Begin by defining the agent header file with proper Cyclus metadata and parameter declarations. |
| 24 | + |
| 25 | +.. code-block:: cpp |
| 26 | +
|
| 27 | +// advanced\_reactor.h |
| 28 | +\#ifndef ADVANCED\_REACTOR\_H\_ |
| 29 | +\#define ADVANCED\_REACTOR\_H\_ |
| 30 | + |
| 31 | +\#include <string> |
| 32 | +\#include <queue> |
| 33 | +\#include "cyclus.h" |
| 34 | +\#include "toolkit/resource\_buff.h" |
| 35 | +\#include "toolkit/mat\_query.h" |
| 36 | +\#include "toolkit/commod\_map\_inst.h" |
| 37 | +\#include "toolkit/time\_series.h" |
| 38 | + |
| 39 | +class AdvancedReactor : public cyclus::Facility { |
| 40 | +public: |
| 41 | +AdvancedReactor(cyclus::Context\* ctx); |
| 42 | +virtual \~AdvancedReactor() {} |
| 43 | + |
| 44 | +``` |
| 45 | +#pragma cyclus note { |
| 46 | + "doc": "An advanced reactor facility that models fuel intake, burnup, and discharge." |
| 47 | +} |
| 48 | + |
| 49 | +// Core parameters |
| 50 | +#pragma cyclus var {"tooltip": "Maximum fresh fuel inventory (kg)", "units": "kg"} |
| 51 | +double fresh_fuel_cap; |
| 52 | + |
| 53 | +#pragma cyclus var {"tooltip": "Maximum spent fuel inventory (kg)", "units": "kg"} |
| 54 | +double spent_fuel_cap; |
| 55 | + |
| 56 | +#pragma cyclus var {"tooltip": "Residence time in core (timesteps)", "default": 3, "lbound": 1} |
| 57 | +int residence_time; |
| 58 | + |
| 59 | +#pragma cyclus var {"tooltip": "Input commodity for fresh fuel"} |
| 60 | +std::string in_commodity; |
| 61 | + |
| 62 | +#pragma cyclus var {"tooltip": "Output commodity for spent fuel"} |
| 63 | +std::string out_commodity; |
| 64 | + |
| 65 | +#pragma cyclus var {"tooltip": "Recipe for spent fuel"} |
| 66 | +std::string out_recipe; |
| 67 | + |
| 68 | +virtual std::string str(); |
| 69 | +virtual void EnterNotify(); |
| 70 | +virtual void Tick(); |
| 71 | +virtual void Tock(); |
| 72 | + |
| 73 | +virtual std::set<cyclus::RequestPortfolio<cyclus::Material>::Ptr> GetMatlRequests(); |
| 74 | +virtual std::set<cyclus::BidPortfolio<cyclus::Material>::Ptr> GetMatlBids( |
| 75 | + cyclus::CommodMap<cyclus::Material>::type& commod_requests); |
| 76 | +virtual void AcceptMatlTrades( |
| 77 | + const std::map<cyclus::Trade<cyclus::Material>, cyclus::Material::Ptr>& responses); |
| 78 | +virtual cyclus::Material::Ptr OfferMatl( |
| 79 | + cyclus::Material::Ptr request); |
| 80 | +``` |
| 81 | + |
| 82 | +private: |
| 83 | +cyclus::toolkit::ResourceBuff fresh\_fuel\_; // buffer for incoming fuel |
| 84 | +cyclus::toolkit::ResourceBuff spent\_fuel\_; // buffer for outgoing waste |
| 85 | + |
| 86 | +``` |
| 87 | +std::queue<std::pair<int, cyclus::Material::Ptr>> core_; // (entry_time, material) |
| 88 | + |
| 89 | +cyclus::toolkit::TimeSeries<double> power_output_; // optional logging |
| 90 | +``` |
| 91 | + |
| 92 | +}; |
| 93 | + |
| 94 | +\#endif // ADVANCED\_REACTOR\_H\_ |
| 95 | + |
| 96 | +--- |
| 97 | + |
| 98 | +2. Facility Behavior (Source) |
| 99 | + |
| 100 | +--- |
| 101 | + |
| 102 | +Implement the lifecycle and DRE methods: |
| 103 | + |
| 104 | +.. code-block:: cpp |
| 105 | +
|
| 106 | +\#include "advanced\_reactor.h" |
| 107 | + |
| 108 | +using cyclus::Material; |
| 109 | +using cyclus::Trade; |
| 110 | +using cyclus::Context; |
| 111 | + |
| 112 | +AdvancedReactor::AdvancedReactor(Context\* ctx) |
| 113 | +: cyclus::Facility(ctx), |
| 114 | +fresh\_fuel\_(), spent\_fuel\_(), power\_output\_("power\_output") {} |
| 115 | + |
| 116 | +void AdvancedReactor::EnterNotify() { |
| 117 | +cyclus::Facility::EnterNotify(); |
| 118 | +fresh\_fuel\_.capacity(fresh\_fuel\_cap); |
| 119 | +spent\_fuel\_.capacity(spent\_fuel\_cap); |
| 120 | +} |
| 121 | + |
| 122 | +void AdvancedReactor::Tick() { |
| 123 | +// discharge spent fuel if residence time reached |
| 124 | +while (!core\_.empty() && context()->time() - core\_.front().first >= residence\_time) { |
| 125 | +cyclus::Material::Ptr m = core\_.front().second->Transmute(out\_recipe); |
| 126 | +spent\_fuel\_.Push(m); |
| 127 | +core\_.pop(); |
| 128 | +} |
| 129 | +} |
| 130 | + |
| 131 | +void AdvancedReactor::Tock() { |
| 132 | +// move fresh fuel to core if there's room |
| 133 | +while (!fresh\_fuel\_.empty()) { |
| 134 | +cyclus::Material::Ptr m = fresh\_fuel\_.Pop(); |
| 135 | +core\_.push(std::make\_pair(context()->time(), m)); |
| 136 | +} |
| 137 | +} |
| 138 | + |
| 139 | +std::string AdvancedReactor::str() { |
| 140 | +std::stringstream ss; |
| 141 | +ss << Facility::str() << \n |
| 142 | +<< "Fresh fuel buffer: " << fresh\_fuel\_.quantity() << " / " << fresh\_fuel\_cap << " kg\n" |
| 143 | +<< "Spent fuel buffer: " << spent\_fuel\_.quantity() << " / " << spent\_fuel\_cap << " kg\n" |
| 144 | +<< "Core loading: " << core\_.size() << " assemblies"; |
| 145 | +return ss.str(); |
| 146 | +} |
| 147 | + |
| 148 | +std::set\<RequestPortfolio<Material>::Ptr> AdvancedReactor::GetMatlRequests() { |
| 149 | +std::set\<RequestPortfolio<Material>::Ptr> ports; |
| 150 | +double qty = fresh\_fuel\_.space(); |
| 151 | +if (qty > 0) { |
| 152 | +Material::Ptr dummy = cyclus::NewBlankMaterial(qty); |
| 153 | +RequestPortfolio<Material>::Ptr port(new RequestPortfolio<Material>()); |
| 154 | +port->AddRequest(dummy, this, in\_commodity); |
| 155 | +ports.insert(port); |
| 156 | +} |
| 157 | +return ports; |
| 158 | +} |
| 159 | + |
| 160 | +std::set\<cyclus::BidPortfolio<Material>::Ptr> AdvancedReactor::GetMatlBids( |
| 161 | +cyclus::CommodMap<Material>::type& commod\_requests) { |
| 162 | +std::set\<cyclus::BidPortfolio<Material>::Ptr> ports; |
| 163 | +if (spent\_fuel\_.quantity() <= 0) return ports; |
| 164 | + |
| 165 | +``` |
| 166 | +for (auto& pair : commod_requests[out_commodity]) { |
| 167 | + Material::Ptr offer = spent_fuel_.Peek(); |
| 168 | + cyclus::BidPortfolio<Material>::Ptr port(new cyclus::BidPortfolio<Material>()); |
| 169 | + port->AddBid(pair, offer, this); |
| 170 | + ports.insert(port); |
| 171 | +} |
| 172 | +return ports; |
| 173 | +``` |
| 174 | + |
| 175 | +} |
| 176 | + |
| 177 | +void AdvancedReactor::AcceptMatlTrades( |
| 178 | +const std::map\<Trade<Material>, Material::Ptr>& responses) { |
| 179 | +for (auto& pair : responses) { |
| 180 | +fresh\_fuel\_.Push(pair.second); |
| 181 | +} |
| 182 | +} |
| 183 | + |
| 184 | +Material::Ptr AdvancedReactor::OfferMatl(Material::Ptr request) { |
| 185 | +return spent\_fuel\_.PopQty(request->quantity()); |
| 186 | +} |
| 187 | + |
| 188 | +--- |
| 189 | + |
| 190 | +3. Advanced Features |
| 191 | + |
| 192 | +--- |
| 193 | + |
| 194 | +**a. Material Inspection** |
| 195 | + |
| 196 | +Use `cyclus::toolkit::MatQuery` to inspect isotopic makeup: |
| 197 | + |
| 198 | +.. code-block:: cpp |
| 199 | +
|
| 200 | +cyclus::toolkit::MatQuery mq(mat); |
| 201 | +double u235 = mq.mass\_frac("U235"); |
| 202 | + |
| 203 | +This is helpful for tracking enrichment or decay over time. |
| 204 | + |
| 205 | +**b. Time Series Logging** |
| 206 | + |
| 207 | +Track performance over time: |
| 208 | + |
| 209 | +.. code-block:: cpp |
| 210 | +
|
| 211 | +power\_output\_.Get(context())->AddValue("power\_output", current\_mw); |
| 212 | + |
| 213 | +You can emit curves to the output database for later plotting. |
| 214 | + |
| 215 | +**c. Error Handling and Validation** |
| 216 | + |
| 217 | +Use assertions and logs to guard runtime logic: |
| 218 | + |
| 219 | +.. code-block:: cpp |
| 220 | +
|
| 221 | +if (fresh\_fuel\_.space() <= 0) { |
| 222 | +LOG(cyclus::WARN) << "No room for new fuel"; |
| 223 | +} |
| 224 | + |
| 225 | +Use `lbound`, `ubound`, and `default` pragmas for robust schema validation. |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +4. Testing Notes |
| 230 | + |
| 231 | +--- |
| 232 | + |
| 233 | +* Use Google Test (`gtest`) in the `tests/` directory. |
| 234 | +* Validate: |
| 235 | + |
| 236 | + * Behavior under full/empty buffer conditions |
| 237 | + * Core loading and discharge timing |
| 238 | + * Material transformation into output recipe |
| 239 | + |
| 240 | +--- |
| 241 | + |
| 242 | +5. Summary and Next Steps |
| 243 | + |
| 244 | +--- |
| 245 | + |
| 246 | +This advanced tutorial walked through the full implementation of a robust Cyclus Facility agent with: |
| 247 | + |
| 248 | +* Input validation and toolkit buffers |
| 249 | +* Core DRE interactions for both input and output commodities |
| 250 | +* Material transformation and storage logic |
| 251 | +* Optional logging and diagnostics |
| 252 | + |
| 253 | +Continue on to the specialized chapters for: |
| 254 | + |
| 255 | +.. toctree:: |
| 256 | +dre\_interaction |
| 257 | +marquetry\_query |
| 258 | +custom\_institution |
0 commit comments