From 8057a147520137ac9aae0f5f879e6e6df1c64884 Mon Sep 17 00:00:00 2001 From: Dean Krueger Date: Mon, 16 Jun 2025 15:55:14 -0500 Subject: [PATCH 1/4] Addded the foundation of an advanced Cyclus Agent development tutorial using ChatGPT --- source/arche/tutorial_cpp/advanced.rst | 258 +++++++++++++++++++++++ source/arche/tutorial_cpp/custominst.rst | 177 ++++++++++++++++ source/arche/tutorial_cpp/dre.rst | 177 ++++++++++++++++ source/arche/tutorial_cpp/index.rst | 4 + source/arche/tutorial_cpp/matquery.rst | 140 ++++++++++++ 5 files changed, 756 insertions(+) create mode 100644 source/arche/tutorial_cpp/advanced.rst create mode 100644 source/arche/tutorial_cpp/custominst.rst create mode 100644 source/arche/tutorial_cpp/dre.rst create mode 100644 source/arche/tutorial_cpp/matquery.rst diff --git a/source/arche/tutorial_cpp/advanced.rst b/source/arche/tutorial_cpp/advanced.rst new file mode 100644 index 00000000..0be65d1a --- /dev/null +++ b/source/arche/tutorial_cpp/advanced.rst @@ -0,0 +1,258 @@ + +Advanced Agent Creation +=============================== + +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. + +By the end of this chapter, you will understand how to: + +* Declare validated input parameters and manage internal state. +* Interact with Cyclus's Dynamic Resource Exchange (DRE) to request and accept trades. +* Use toolkit classes for material management, querying, and time series. +* Implement lifecycle methods (`Tick`, `Tock`, `EnterNotify`) effectively. +* Incorporate logging, testing, and optional output recording. + +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. + +--- + +1. Agent Structure and Metadata + +--- + +Begin by defining the agent header file with proper Cyclus metadata and parameter declarations. + +.. code-block:: cpp + +// advanced\_reactor.h +\#ifndef ADVANCED\_REACTOR\_H\_ +\#define ADVANCED\_REACTOR\_H\_ + +\#include +\#include +\#include "cyclus.h" +\#include "toolkit/resource\_buff.h" +\#include "toolkit/mat\_query.h" +\#include "toolkit/commod\_map\_inst.h" +\#include "toolkit/time\_series.h" + +class AdvancedReactor : public cyclus::Facility { +public: +AdvancedReactor(cyclus::Context\* ctx); +virtual \~AdvancedReactor() {} + +``` +#pragma cyclus note { + "doc": "An advanced reactor facility that models fuel intake, burnup, and discharge." +} + +// Core parameters +#pragma cyclus var {"tooltip": "Maximum fresh fuel inventory (kg)", "units": "kg"} +double fresh_fuel_cap; + +#pragma cyclus var {"tooltip": "Maximum spent fuel inventory (kg)", "units": "kg"} +double spent_fuel_cap; + +#pragma cyclus var {"tooltip": "Residence time in core (timesteps)", "default": 3, "lbound": 1} +int residence_time; + +#pragma cyclus var {"tooltip": "Input commodity for fresh fuel"} +std::string in_commodity; + +#pragma cyclus var {"tooltip": "Output commodity for spent fuel"} +std::string out_commodity; + +#pragma cyclus var {"tooltip": "Recipe for spent fuel"} +std::string out_recipe; + +virtual std::string str(); +virtual void EnterNotify(); +virtual void Tick(); +virtual void Tock(); + +virtual std::set::Ptr> GetMatlRequests(); +virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); +virtual void AcceptMatlTrades( + const std::map, cyclus::Material::Ptr>& responses); +virtual cyclus::Material::Ptr OfferMatl( + cyclus::Material::Ptr request); +``` + +private: +cyclus::toolkit::ResourceBuff fresh\_fuel\_; // buffer for incoming fuel +cyclus::toolkit::ResourceBuff spent\_fuel\_; // buffer for outgoing waste + +``` +std::queue> core_; // (entry_time, material) + +cyclus::toolkit::TimeSeries power_output_; // optional logging +``` + +}; + +\#endif // ADVANCED\_REACTOR\_H\_ + +--- + +2. Facility Behavior (Source) + +--- + +Implement the lifecycle and DRE methods: + +.. code-block:: cpp + +\#include "advanced\_reactor.h" + +using cyclus::Material; +using cyclus::Trade; +using cyclus::Context; + +AdvancedReactor::AdvancedReactor(Context\* ctx) +: cyclus::Facility(ctx), +fresh\_fuel\_(), spent\_fuel\_(), power\_output\_("power\_output") {} + +void AdvancedReactor::EnterNotify() { +cyclus::Facility::EnterNotify(); +fresh\_fuel\_.capacity(fresh\_fuel\_cap); +spent\_fuel\_.capacity(spent\_fuel\_cap); +} + +void AdvancedReactor::Tick() { +// discharge spent fuel if residence time reached +while (!core\_.empty() && context()->time() - core\_.front().first >= residence\_time) { +cyclus::Material::Ptr m = core\_.front().second->Transmute(out\_recipe); +spent\_fuel\_.Push(m); +core\_.pop(); +} +} + +void AdvancedReactor::Tock() { +// move fresh fuel to core if there's room +while (!fresh\_fuel\_.empty()) { +cyclus::Material::Ptr m = fresh\_fuel\_.Pop(); +core\_.push(std::make\_pair(context()->time(), m)); +} +} + +std::string AdvancedReactor::str() { +std::stringstream ss; +ss << Facility::str() << \n +<< "Fresh fuel buffer: " << fresh\_fuel\_.quantity() << " / " << fresh\_fuel\_cap << " kg\n" +<< "Spent fuel buffer: " << spent\_fuel\_.quantity() << " / " << spent\_fuel\_cap << " kg\n" +<< "Core loading: " << core\_.size() << " assemblies"; +return ss.str(); +} + +std::set\::Ptr> AdvancedReactor::GetMatlRequests() { +std::set\::Ptr> ports; +double qty = fresh\_fuel\_.space(); +if (qty > 0) { +Material::Ptr dummy = cyclus::NewBlankMaterial(qty); +RequestPortfolio::Ptr port(new RequestPortfolio()); +port->AddRequest(dummy, this, in\_commodity); +ports.insert(port); +} +return ports; +} + +std::set\::Ptr> AdvancedReactor::GetMatlBids( +cyclus::CommodMap::type& commod\_requests) { +std::set\::Ptr> ports; +if (spent\_fuel\_.quantity() <= 0) return ports; + +``` +for (auto& pair : commod_requests[out_commodity]) { + Material::Ptr offer = spent_fuel_.Peek(); + cyclus::BidPortfolio::Ptr port(new cyclus::BidPortfolio()); + port->AddBid(pair, offer, this); + ports.insert(port); +} +return ports; +``` + +} + +void AdvancedReactor::AcceptMatlTrades( +const std::map\, Material::Ptr>& responses) { +for (auto& pair : responses) { +fresh\_fuel\_.Push(pair.second); +} +} + +Material::Ptr AdvancedReactor::OfferMatl(Material::Ptr request) { +return spent\_fuel\_.PopQty(request->quantity()); +} + +--- + +3. Advanced Features + +--- + +**a. Material Inspection** + +Use `cyclus::toolkit::MatQuery` to inspect isotopic makeup: + +.. code-block:: cpp + +cyclus::toolkit::MatQuery mq(mat); +double u235 = mq.mass\_frac("U235"); + +This is helpful for tracking enrichment or decay over time. + +**b. Time Series Logging** + +Track performance over time: + +.. code-block:: cpp + +power\_output\_.Get(context())->AddValue("power\_output", current\_mw); + +You can emit curves to the output database for later plotting. + +**c. Error Handling and Validation** + +Use assertions and logs to guard runtime logic: + +.. code-block:: cpp + +if (fresh\_fuel\_.space() <= 0) { +LOG(cyclus::WARN) << "No room for new fuel"; +} + +Use `lbound`, `ubound`, and `default` pragmas for robust schema validation. + +--- + +4. Testing Notes + +--- + +* Use Google Test (`gtest`) in the `tests/` directory. +* Validate: + + * Behavior under full/empty buffer conditions + * Core loading and discharge timing + * Material transformation into output recipe + +--- + +5. Summary and Next Steps + +--- + +This advanced tutorial walked through the full implementation of a robust Cyclus Facility agent with: + +* Input validation and toolkit buffers +* Core DRE interactions for both input and output commodities +* Material transformation and storage logic +* Optional logging and diagnostics + +Continue on to the specialized chapters for: + +.. toctree:: +dre\_interaction +marquetry\_query +custom\_institution diff --git a/source/arche/tutorial_cpp/custominst.rst b/source/arche/tutorial_cpp/custominst.rst new file mode 100644 index 00000000..5629ac69 --- /dev/null +++ b/source/arche/tutorial_cpp/custominst.rst @@ -0,0 +1,177 @@ +.. \_custom\_institution: + +\=============================== +Creating a Custom Institution +============================= + +This chapter walks through the creation of a **custom Cyclus Institution archetype**, illustrating how institutions can coordinate deployment, set policy, or aggregate data across child agents. + +We will explore: + +* Institution design patterns +* Managing and deploying facilities dynamically +* Using `toolkit::CommodityProducerManager` and `toolkit::Position` +* Storing and interpreting time-based policy or scenario logic + +--- + +1. Institution Basics + +--- + +A Cyclus Institution is an agent that owns and manages a set of child facilities. It does **not** directly interact with the DRE unless explicitly programmed to do so. + +Institutions are useful for: + +* Controlling facility deployment +* Enforcing policy constraints +* Emitting cross-cutting diagnostics + +--- + +2. A Sample Archetype: SmartDeployInst + +--- + +Let’s define a new Institution called `SmartDeployInst`. This agent dynamically deploys a set of child facilities according to a time-series demand signal and manages their geographic position metadata. + +.. code-block:: cpp + +\#ifndef SMART\_DEPLOY\_INST\_H\_ +\#define SMART\_DEPLOY\_INST\_H\_ + +\#include "cyclus.h" +\#include "toolkit/commodity\_producer\_manager.h" +\#include "toolkit/time\_series.h" +\#include "toolkit/position.h" + +class SmartDeployInst : public cyclus::Institution { +public: +SmartDeployInst(cyclus::Context\* ctx); +virtual \~SmartDeployInst() {} + +``` +#pragma cyclus note {"doc": "Institution that deploys facilities in response to demand"} + +#pragma cyclus var {"tooltip": "Facility to deploy"} +std::string prototype; + +#pragma cyclus var {"tooltip": "Target commodity to match"} +std::string target_commod; + +#pragma cyclus var {"tooltip": "Demand signal name"} +std::string demand_series; + +virtual void Build(cyclus::Agent* parent); +virtual void Tick(); +``` + +private: +cyclus::toolkit::CommodityProducerManager manager\_; +int last\_build\_time\_; +}; + +\#endif // SMART\_DEPLOY\_INST\_H\_ + +--- + +3. Behavior Implementation + +--- + +.. code-block:: cpp + +\#include "smart\_deploy\_inst.h" + +using cyclus::Context; + +SmartDeployInst::SmartDeployInst(Context\* ctx) +: cyclus::Institution(ctx), last\_build\_time\_(-1) {} + +void SmartDeployInst::Build(cyclus::Agent\* parent) { +Institution::Build(parent); +manager\_.RegisterAllChildren(this); +} + +void SmartDeployInst::Tick() { +double produced = manager\_.TotalCapacity(target\_commod); +double required = cyclus::toolkit::TimeSeries::Get(context()) +->Get(demand\_series, context()->time()); + +``` +if (produced < required && context()->time() > last_build_time_) { + context()->SchedBuild(prototype); + last_build_time_ = context()->time(); + + LOG(cyclus::INFO) << "Deploying facility " << prototype + << " to meet demand for " << target_commod; +} +``` + +} + +--- + +4. Toolkit Features + +--- + +**a. CommodityProducerManager** + +* Automatically tracks child facilities producing a given commodity. +* Allows querying current capacity or growth trends. + +**b. TimeSeries** + +* Use `TimeSeries` to load and compare demand curves over time. +* Ideal for policy-driven modeling or economics. + +**c. Position and Mapping** + +* Optional: Assign facilities to physical coordinates using `toolkit::Position`. +* Enables geospatial or regional modeling. + +--- + +5. Deployment Policy Patterns + +--- + +Institutions can also: + +* Deploy multiple facilities per timestep +* Use economic filters (e.g., cost-benefit tests) +* Read deployment orders from external files or parameters + +Example: + +.. code-block:: cpp + +if (produced < required && available\_budget >= cost\_per\_facility) { +context()->SchedBuild("EnrichmentFacility"); +available\_budget -= cost\_per\_facility; +} + +--- + +6. Logging and Diagnostics + +--- + +* Use `LOG(cyclus::INFO)` for deploy notices +* Record metrics with `RecordPosition`, `RecordTimeSeries`, or custom tables +* Consider emitting institutional summary reports in `Tock()` + +--- + +7. Summary + +--- + +Custom institutions are powerful coordination points in Cyclus simulations. By extending basic `Tick()` logic and using toolkit helpers, your institution can: + +* Monitor market activity and react accordingly +* Control dynamic deployment of facilities +* Support geographically or economically nuanced behaviors + +You now have all the tools necessary to design complex institutional agents that implement scenario logic, enforce system-wide policy, and guide the evolution of your simulation. diff --git a/source/arche/tutorial_cpp/dre.rst b/source/arche/tutorial_cpp/dre.rst new file mode 100644 index 00000000..f071a126 --- /dev/null +++ b/source/arche/tutorial_cpp/dre.rst @@ -0,0 +1,177 @@ + +Advanced DRE Interaction +======================== + +This chapter explains how to interact with Cyclus's **Dynamic Resource Exchange (DRE)** system using advanced methods. Facilities and Institutions use the DRE to participate in resource markets by issuing **requests**, placing **bids**, and accepting or rejecting **trades**. + +This guide covers: + +* Full lifecycle of a material trade in Cyclus +* Implementation of request, bid, and trade handlers +* Conditional bidding strategies +* Interaction with multiple commodities +* Resource validation and transformation + +--- + +1. The DRE Lifecycle + +--- + +Each simulation timestep, the DRE coordinates a market: + +1. Agents issue **requests** for resources (`GetMatlRequests()` or `GetGenRequests()`) +2. Agents issue **bids** in response to requests (`GetMatlBids()` or `GetGenBids()`) +3. The DRE matches trades +4. Agents execute trades using: + + * `AcceptMatlTrades()` / `AcceptGenTrades()` to receive material + * `OfferMatl()` / `OfferGen()` to provide material + +--- + +2. Requesting Material + +--- + +Requests are issued when the agent has capacity for a resource. Each request: + +* Specifies a desired quantity +* Indicates the input commodity +* Can include a recipe or preference weighting + +.. code-block:: cpp + +std::set\::Ptr> Reactor::GetMatlRequests() { +std::set\::Ptr> ports; + +``` +double amt = fresh_fuel_.space(); +if (amt <= 0) return ports; + +Material::Ptr dummy = cyclus::NewBlankMaterial(amt); +RequestPortfolio::Ptr port(new RequestPortfolio()); +port->AddRequest(dummy, this, in_commodity); +ports.insert(port); +return ports; +``` + +} + +--- + +3. Bidding Material + +--- + +Bids are generated when an agent has material to offer. Bids: + +* Respond to specific requests +* Specify quantity and provider +* May include constraints (exclusive, mutual, etc.) + +.. code-block:: cpp + +std::set\::Ptr> Reactor::GetMatlBids( +cyclus::CommodMap::type& commod\_requests) { + +``` +std::set::Ptr> ports; +if (spent_fuel_.quantity() <= 0) return ports; + +for (auto& req : commod_requests[out_commodity]) { + Material::Ptr offer = spent_fuel_.Peek(); + BidPortfolio::Ptr port(new BidPortfolio()); + port->AddBid(req, offer, this); + ports.insert(port); +} +return ports; +``` + +} + +You can conditionally bid based on isotopic content, facility state, or request attributes. + +--- + +4. Accepting Material + +--- + +After the market resolves, agents are delivered trades. + +.. code-block:: cpp + +void Reactor::AcceptMatlTrades( +const std::map\, Material::Ptr>& responses) { +for (auto& pair : responses) { +fresh\_fuel\_.Push(pair.second); +} +} + +This is typically where tracking, transformation, or validation occurs. + +--- + +5. Providing Material + +--- + +Outgoing trades are fulfilled using `OfferMatl()`: + +.. code-block:: cpp + +Material::Ptr Reactor::OfferMatl(Material::Ptr request) { +return spent\_fuel\_.PopQty(request->quantity()); +} + +You may return: + +* Raw buffer contents +* A transmuted material (`mat->Transmute("NewRecipe")`) +* A composition-weighted mixture (see toolkit::Mix()) + +--- + +6. Tips and Best Practices + +--- + +* Use Peek() to inspect buffer contents before bidding. +* Always guard bids and requests with quantity checks. +* Prefer making bids conditional on request content (e.g., recipe match). +* Store incoming trades by commodity if multiple inputs exist. +* Log commodity mismatches or undersupplied trades. + +--- + +7. Beyond Facilities + +--- + +Institutions can also interact with the DRE, though less commonly. They may: + +* Mediate trades for child agents +* Collect price or commodity data +* Implement regional trade quotas + +This often requires custom mapping or override of Institution DRE hooks. + +--- + +8. Summary + +--- + +Advanced DRE interaction involves five core functions and optional filtering or logic. By understanding this flow, developers can: + +* Precisely control when and how trades occur +* Encode policy and preference logic +* Track material flow for safety or economics + +Continue with the next chapter on inspecting materials with the `Marquetry` toolkit. + +.. seealso:: + +* \:ref:`marquetry_query` +* \:ref:`custom_institution` diff --git a/source/arche/tutorial_cpp/index.rst b/source/arche/tutorial_cpp/index.rst index 4f33f0fa..22ac8d18 100644 --- a/source/arche/tutorial_cpp/index.rst +++ b/source/arche/tutorial_cpp/index.rst @@ -27,6 +27,10 @@ This tutorial has the following steps: toolkit testing input_files + advanced + dre + matquery + custominst .. Given enough time, the following extra topics may be covered: diff --git a/source/arche/tutorial_cpp/matquery.rst b/source/arche/tutorial_cpp/matquery.rst new file mode 100644 index 00000000..e522e03d --- /dev/null +++ b/source/arche/tutorial_cpp/matquery.rst @@ -0,0 +1,140 @@ +.. \_marquetry\_query: + +\=============================== +Material Inspection with Marquetry +================================== + +This chapter introduces the use of **cyclus::toolkit::MatQuery** and related utilities to inspect and analyze nuclear material compositions inside Cyclus agents. These tools are essential for modeling enrichment, burnup, quality control, and any behavior conditioned on isotopic content. + +Covered Topics: + +* Introduction to `MatQuery` +* Retrieving isotopic mass fractions and total masses +* Composition filtering and enrichment logic +* Recipe matching and decay handling + +--- + +1. What is MatQuery? + +--- + +`MatQuery` is a Cyclus toolkit class that wraps a `cyclus::Material::Ptr` and provides methods to easily extract isotopic data. + +It enables: + +* Retrieval of mass fractions: `mq.mass_frac("U235")` +* Retrieval of total mass of isotope: `mq.mass("Pu239")` +* Access to full isotope maps: `mq.comp()->mass()` +* Composition property tests (e.g., `IsFissile`, `HasElement`, etc.) + +--- + +2. Basic Usage + +--- + +.. code-block:: cpp + +void EnrichmentFacility::AcceptMatlTrades( +const std::map\& responses) { +for (auto& pair : responses) { +cyclus::Material::Ptr mat = pair.second; +cyclus::toolkit::MatQuery mq(mat); + +``` + double u235_frac = mq.mass_frac("U235"); + double total_u = mq.mass("U235") + mq.mass("U238"); + + if (u235_frac < tails_assay_) { + LOG(cyclus::ERROR) << "Incoming material under-enriched"; + } + + feed_buffer_.Push(mat); +} +``` + +} + +--- + +3. Conditional Bidding or Acceptance + +--- + +`MatQuery` can be used to evaluate trade viability. For instance: + +.. code-block:: cpp + +bool IsAcceptableFuel(cyclus::Material::Ptr mat) { +cyclus::toolkit::MatQuery mq(mat); +double pu\_frac = mq.mass\_frac("Pu239"); +return pu\_frac > 0.02; +} + +This lets your agent filter trade offers or gate reactor core loading based on fuel specs. + +--- + +4. Filtering and Enrichment Modeling + +--- + +When performing enrichment or isotope separation, you'll often: + +* Extract uranium mass vector +* Normalize target enrichment +* Compute SWU requirements (via toolkit::Separations) + +.. code-block:: cpp + +double feed\_assay = mq.mass\_frac("U235") + mq.mass\_frac("U234"); +double swu = toolkit::Separations::SwuRequired( +feed\_mass, feed\_assay, product\_assay, tails\_assay); + +You may optionally track the material path and produce a blended result using `Material::Blend()` or `toolkit::Mix()`. + +--- + +5. Advanced Composition Access + +--- + +Use the `comp()` method to directly access the underlying `cyclus::Composition::Ptr`: + +.. code-block:: cpp + +cyclus::CompMap cm = mq.comp()->mass(); +for (auto iso : cm) { +std::cout << "Isotope: " << iso.first << ", mass = " << iso.second << std::endl; +} + +You can use this for generating custom reports or modeling precise decay chains. + +--- + +6. Considerations for Decay and Recipes + +--- + +* Materials do **not** decay unless `Material::Decay()` is explicitly called. +* Use `mat->Transmute("new_recipe")` to apply predefined compositions. +* Validate incoming recipes using `mat->comp()->Equals(Context()->GetRecipe("name"))`. + +--- + +7. Summary + +--- + +The `MatQuery` toolkit provides intuitive, composable access to isotopic material data. It is useful for: + +* Enrichment and burnup modeling +* Safety validation and material rejection +* Conditional processing logic + +Next, we explore how institutions can leverage Cyclus features to coordinate agents and implement deployment or policy logic. + +.. seealso:: + +* \:ref:`custom_institution` From d5bb84d53e5d48166d702e8f2c747d32de832bb2 Mon Sep 17 00:00:00 2001 From: Dean Krueger Date: Mon, 16 Jun 2025 16:58:58 -0500 Subject: [PATCH 2/4] started working out the formatting errors Chat had. Noticed that it also made some stuff up with how the DRE works... --- source/arche/tutorial_cpp/advanced.rst | 407 ++++++++++++----------- source/arche/tutorial_cpp/custominst.rst | 2 - source/arche/tutorial_cpp/dre.rst | 15 +- source/arche/tutorial_cpp/matquery.rst | 2 - 4 files changed, 217 insertions(+), 209 deletions(-) diff --git a/source/arche/tutorial_cpp/advanced.rst b/source/arche/tutorial_cpp/advanced.rst index 0be65d1a..bd32ad4f 100644 --- a/source/arche/tutorial_cpp/advanced.rst +++ b/source/arche/tutorial_cpp/advanced.rst @@ -14,221 +14,246 @@ By the end of this chapter, you will understand how to: 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. ---- 1. Agent Structure and Metadata - ---- +-------------------------------- Begin by defining the agent header file with proper Cyclus metadata and parameter declarations. -.. code-block:: cpp +.. code-block:: c++ + + // advanced_reactor.h + #ifndef ADVANCED_REACTOR_H_ + #define ADVANCED_REACTOR_H_ + + #include + #include + #include "cyclus.h" + #include "toolkit/resource_buff.h" + #include "toolkit/mat_query.h" + #include "toolkit/commod_map_inst.h" + #include "toolkit/time_series.h" + + class AdvancedReactor : public cyclus::Facility { + public: + AdvancedReactor(cyclus::Context* ctx); + virtual ~AdvancedReactor() {} + + #pragma cyclus note { + "doc": "An advanced reactor facility that models fuel intake, burnup, and discharge." + } + + +Next, add some core parameters to the advanced reactor: + +.. code-block:: c++ + + #pragma cyclus var {"tooltip": "Maximum fresh fuel inventory (kg)", "units": "kg"} + double fresh_fuel_cap; + + #pragma cyclus var {"tooltip": "Maximum spent fuel inventory (kg)", "units": "kg"} + double spent_fuel_cap; + + #pragma cyclus var {"tooltip": "Residence time in core (timesteps)", "default": 3, "lbound": 1} + int residence_time; + + #pragma cyclus var {"tooltip": "Input commodity for fresh fuel"} + std::string in_commodity; + + #pragma cyclus var {"tooltip": "Output commodity for spent fuel"} + std::string out_commodity; + + #pragma cyclus var {"tooltip": "Recipe for spent fuel"} + std::string out_recipe; + + +Add some basic functions to the header file. These will be defined more thoroughly +later on. + +.. code-block:: c++ + + virtual std::string str(); + virtual void EnterNotify(); + virtual void Tick(); + virtual void Tock(); + + // DRE Functions: + virtual std::set::Ptr> GetMatlRequests(); + virtual std::set::Ptr> GetMatlBids( + cyclus::CommodMap::type& commod_requests); + virtual void AcceptMatlTrades( + const std::map, cyclus::Material::Ptr>& responses); + virtual cyclus::Material::Ptr OfferMatl( + cyclus::Material::Ptr request); + +Finally, we can add some material buffers and a TimeSeries, then cap the file +off! + +.. code-block:: c++ + + private: + cyclus::toolkit::ResourceBuff fresh_fuel_; // buffer for incoming fuel + cyclus::toolkit::ResourceBuff spent_fuel_; // buffer for outgoing waste + + std::queue> core_; // (entry_time, material) + + cyclus::toolkit::TimeSeries power_output_; // optional logging + + + }; + + #endif // ADVANCED_REACTOR_H_ -// advanced\_reactor.h -\#ifndef ADVANCED\_REACTOR\_H\_ -\#define ADVANCED\_REACTOR\_H\_ -\#include -\#include -\#include "cyclus.h" -\#include "toolkit/resource\_buff.h" -\#include "toolkit/mat\_query.h" -\#include "toolkit/commod\_map\_inst.h" -\#include "toolkit/time\_series.h" +2. Defining Facility Behavior in the .cc File +--------------------------------------------- -class AdvancedReactor : public cyclus::Facility { -public: -AdvancedReactor(cyclus::Context\* ctx); -virtual \~AdvancedReactor() {} +Implement the generic facility methods: -``` -#pragma cyclus note { - "doc": "An advanced reactor facility that models fuel intake, burnup, and discharge." -} +.. code-block:: c++ + + #include "advanced_reactor.h" + + using cyclus::Material; + using cyclus::Trade; + using cyclus::Context; + + AdvancedReactor::AdvancedReactor(Context* ctx) + : cyclus::Facility(ctx), + fresh_fuel_(), spent_fuel_(), power_output_("power_output") {} + + std::string AdvancedReactor::str() { + std::stringstream ss; + ss << Facility::str() << "\n" + << "Fresh fuel buffer: " << fresh_fuel_.quantity() << " / " << fresh_fuel_cap << " kg\n" + << "Spent fuel buffer: " << spent_fuel_.quantity() << " / " << spent_fuel_cap << " kg\n" + << "Core loading: " << core_.size() << " assemblies"; + return ss.str(); + } + + void AdvancedReactor::EnterNotify() { + cyclus::Facility::EnterNotify(); + fresh_fuel_.capacity(fresh_fuel_cap); + spent_fuel_.capacity(spent_fuel_cap); + } + +In this facility, the tick function will simply check if the fuel has +been in the core for a certain amount of time, and then "discharge" it if so. +Tock, on the other hand, will move fresh fuel to the core if there's room to do +so. + +.. code-block:: c++ + + void AdvancedReactor::Tick() { + // discharge spent fuel if residence time reached + while (!core_.empty() && context()->time() - core_.front().first >= residence_time) { + cyclus::Material::Ptr m = core_.front().second->Transmute(out_recipe); + spent_fuel_.Push(m); + core_.pop(); + } + } + + void AdvancedReactor::Tock() { + // move fresh fuel to core if there's room + while (!fresh_fuel_.empty()) { + cyclus::Material::Ptr m = fresh_fuel_.Pop(); + core_.push(std::make_pair(context()->time(), m)); + } + } + +Next, we will implement the simplest version of the DRE functions. A more detailed +explaination of these functions, and interacting with the DRE can be found in the +next chapter of this tutorial. + +.. code-block:: c++ + + std::set::Ptr> AdvancedReactor::GetMatlRequests() { + std::set::Ptr> ports; + double qty = fresh_fuel_.space(); + + if (qty > 0) { + Material::Ptr dummy = cyclus::NewBlankMaterial(qty); + RequestPortfolio::Ptr port(new RequestPortfolio()); + port->AddRequest(dummy, this, in_commodity); + ports.insert(port); + } + + return ports; + } + + std::set::Ptr> AdvancedReactor::GetMatlBids( + cyclus::CommodMap::type& commod_requests) { + + std::set::Ptr> ports; + if (spent_fuel_.quantity() <= 0) return ports; + + for (auto& pair : commod_requests[out_commodity]) { + Material::Ptr offer = spent_fuel_.Peek(); + cyclus::BidPortfolio::Ptr port(new cyclus::BidPortfolio()); + port->AddBid(pair, offer, this); + ports.insert(port); + } + + return ports; + } + + void AdvancedReactor::AcceptMatlTrades( + const std::map, Material::Ptr>& responses) { + for (auto& pair : responses) { + fresh_fuel_.Push(pair.second); + } + } + + Material::Ptr AdvancedReactor::OfferMatl(Material::Ptr request) { + return spent_fuel_.PopQty(request->quantity()); + } -// Core parameters -#pragma cyclus var {"tooltip": "Maximum fresh fuel inventory (kg)", "units": "kg"} -double fresh_fuel_cap; - -#pragma cyclus var {"tooltip": "Maximum spent fuel inventory (kg)", "units": "kg"} -double spent_fuel_cap; - -#pragma cyclus var {"tooltip": "Residence time in core (timesteps)", "default": 3, "lbound": 1} -int residence_time; - -#pragma cyclus var {"tooltip": "Input commodity for fresh fuel"} -std::string in_commodity; - -#pragma cyclus var {"tooltip": "Output commodity for spent fuel"} -std::string out_commodity; - -#pragma cyclus var {"tooltip": "Recipe for spent fuel"} -std::string out_recipe; - -virtual std::string str(); -virtual void EnterNotify(); -virtual void Tick(); -virtual void Tock(); - -virtual std::set::Ptr> GetMatlRequests(); -virtual std::set::Ptr> GetMatlBids( - cyclus::CommodMap::type& commod_requests); -virtual void AcceptMatlTrades( - const std::map, cyclus::Material::Ptr>& responses); -virtual cyclus::Material::Ptr OfferMatl( - cyclus::Material::Ptr request); -``` - -private: -cyclus::toolkit::ResourceBuff fresh\_fuel\_; // buffer for incoming fuel -cyclus::toolkit::ResourceBuff spent\_fuel\_; // buffer for outgoing waste - -``` -std::queue> core_; // (entry_time, material) - -cyclus::toolkit::TimeSeries power_output_; // optional logging -``` - -}; - -\#endif // ADVANCED\_REACTOR\_H\_ - ---- - -2. Facility Behavior (Source) - ---- - -Implement the lifecycle and DRE methods: - -.. code-block:: cpp - -\#include "advanced\_reactor.h" - -using cyclus::Material; -using cyclus::Trade; -using cyclus::Context; - -AdvancedReactor::AdvancedReactor(Context\* ctx) -: cyclus::Facility(ctx), -fresh\_fuel\_(), spent\_fuel\_(), power\_output\_("power\_output") {} - -void AdvancedReactor::EnterNotify() { -cyclus::Facility::EnterNotify(); -fresh\_fuel\_.capacity(fresh\_fuel\_cap); -spent\_fuel\_.capacity(spent\_fuel\_cap); -} - -void AdvancedReactor::Tick() { -// discharge spent fuel if residence time reached -while (!core\_.empty() && context()->time() - core\_.front().first >= residence\_time) { -cyclus::Material::Ptr m = core\_.front().second->Transmute(out\_recipe); -spent\_fuel\_.Push(m); -core\_.pop(); -} -} - -void AdvancedReactor::Tock() { -// move fresh fuel to core if there's room -while (!fresh\_fuel\_.empty()) { -cyclus::Material::Ptr m = fresh\_fuel\_.Pop(); -core\_.push(std::make\_pair(context()->time(), m)); -} -} - -std::string AdvancedReactor::str() { -std::stringstream ss; -ss << Facility::str() << \n -<< "Fresh fuel buffer: " << fresh\_fuel\_.quantity() << " / " << fresh\_fuel\_cap << " kg\n" -<< "Spent fuel buffer: " << spent\_fuel\_.quantity() << " / " << spent\_fuel\_cap << " kg\n" -<< "Core loading: " << core\_.size() << " assemblies"; -return ss.str(); -} - -std::set\::Ptr> AdvancedReactor::GetMatlRequests() { -std::set\::Ptr> ports; -double qty = fresh\_fuel\_.space(); -if (qty > 0) { -Material::Ptr dummy = cyclus::NewBlankMaterial(qty); -RequestPortfolio::Ptr port(new RequestPortfolio()); -port->AddRequest(dummy, this, in\_commodity); -ports.insert(port); -} -return ports; -} - -std::set\::Ptr> AdvancedReactor::GetMatlBids( -cyclus::CommodMap::type& commod\_requests) { -std::set\::Ptr> ports; -if (spent\_fuel\_.quantity() <= 0) return ports; - -``` -for (auto& pair : commod_requests[out_commodity]) { - Material::Ptr offer = spent_fuel_.Peek(); - cyclus::BidPortfolio::Ptr port(new cyclus::BidPortfolio()); - port->AddBid(pair, offer, this); - ports.insert(port); -} -return ports; -``` - -} - -void AdvancedReactor::AcceptMatlTrades( -const std::map\, Material::Ptr>& responses) { -for (auto& pair : responses) { -fresh\_fuel\_.Push(pair.second); -} -} - -Material::Ptr AdvancedReactor::OfferMatl(Material::Ptr request) { -return spent\_fuel\_.PopQty(request->quantity()); -} - ---- 3. Advanced Features +-------------------- ---- +Some additional, often helpful, options that are available in Cyclus are covered +briefly here. `MatQuery` is covered in more depth later in this tutorial, while +other functionality can be found elsewhere on the website. **a. Material Inspection** -Use `cyclus::toolkit::MatQuery` to inspect isotopic makeup: +If desired, you can use `cyclus::toolkit::MatQuery` to inspect the isotopic +makeup of a `Cyclus::Material`: -.. code-block:: cpp +.. code-block:: c++ -cyclus::toolkit::MatQuery mq(mat); -double u235 = mq.mass\_frac("U235"); + cyclus::toolkit::MatQuery mq(mat); + double u235 = mq.mass_frac("U235"); -This is helpful for tracking enrichment or decay over time. +This is helpful for tracking enrichment or decay over time, and can be a powerful +tool for many more advanced features you may wish to implement in your agents. **b. Time Series Logging** -Track performance over time: +The `TimeSeries` feature can be used to track the value of a variable over time +in the SQLite output file via a unique table: -.. code-block:: cpp +.. code-block:: c++ -power\_output\_.Get(context())->AddValue("power\_output", current\_mw); - -You can emit curves to the output database for later plotting. + power_output_.Get(context())->AddValue("power_output", current_mw); **c. Error Handling and Validation** -Use assertions and logs to guard runtime logic: - -.. code-block:: cpp +Logging is one of the most helpful tools we have in CYCLYS to inform users of +the internal state of Agents while the simulation is running. This is a great +debugging tool, and should be used whenever possible. For more information on +the Cyclus logging system, search for "Logging" on the website. -if (fresh\_fuel\_.space() <= 0) { -LOG(cyclus::WARN) << "No room for new fuel"; -} +.. code-block:: c++ -Use `lbound`, `ubound`, and `default` pragmas for robust schema validation. + if (fresh_fuel_.space() <= 0) { + LOG(cyclus::WARN) << "No room for new fuel"; + } ---- 4. Testing Notes - ---- +---------------- * Use Google Test (`gtest`) in the `tests/` directory. * Validate: @@ -237,22 +262,14 @@ Use `lbound`, `ubound`, and `default` pragmas for robust schema validation. * Core loading and discharge timing * Material transformation into output recipe ---- 5. Summary and Next Steps +------------------------- ---- - -This advanced tutorial walked through the full implementation of a robust Cyclus Facility agent with: +This advanced tutorial walked through the full implementation of a robust Cyclus +Facility agent with: * Input validation and toolkit buffers * Core DRE interactions for both input and output commodities * Material transformation and storage logic -* Optional logging and diagnostics - -Continue on to the specialized chapters for: - -.. toctree:: -dre\_interaction -marquetry\_query -custom\_institution +* Optional logging and diagnostics \ No newline at end of file diff --git a/source/arche/tutorial_cpp/custominst.rst b/source/arche/tutorial_cpp/custominst.rst index 5629ac69..7a14aa3d 100644 --- a/source/arche/tutorial_cpp/custominst.rst +++ b/source/arche/tutorial_cpp/custominst.rst @@ -1,6 +1,4 @@ -.. \_custom\_institution: -\=============================== Creating a Custom Institution ============================= diff --git a/source/arche/tutorial_cpp/dre.rst b/source/arche/tutorial_cpp/dre.rst index f071a126..a841cf99 100644 --- a/source/arche/tutorial_cpp/dre.rst +++ b/source/arche/tutorial_cpp/dre.rst @@ -12,27 +12,22 @@ This guide covers: * Interaction with multiple commodities * Resource validation and transformation ---- - 1. The DRE Lifecycle - ---- +-------------------- Each simulation timestep, the DRE coordinates a market: -1. Agents issue **requests** for resources (`GetMatlRequests()` or `GetGenRequests()`) -2. Agents issue **bids** in response to requests (`GetMatlBids()` or `GetGenBids()`) +1. Agents issue **requests** for resources (`GetMatlRequests()` or `GetProductRequests()`) +2. Agents issue **bids** in response to requests (`GetMatlBids()` or `GetProductBids()`) 3. The DRE matches trades 4. Agents execute trades using: - * `AcceptMatlTrades()` / `AcceptGenTrades()` to receive material + * `AcceptMatlTrades()` / `AcceptProductTrades()` to receive material * `OfferMatl()` / `OfferGen()` to provide material ---- 2. Requesting Material - ---- +---------------------- Requests are issued when the agent has capacity for a resource. Each request: diff --git a/source/arche/tutorial_cpp/matquery.rst b/source/arche/tutorial_cpp/matquery.rst index e522e03d..270837ee 100644 --- a/source/arche/tutorial_cpp/matquery.rst +++ b/source/arche/tutorial_cpp/matquery.rst @@ -1,6 +1,4 @@ -.. \_marquetry\_query: -\=============================== Material Inspection with Marquetry ================================== From 2ec1a81dd58036cb72c9b81558c91b5e29f37e85 Mon Sep 17 00:00:00 2001 From: Dean Krueger Date: Fri, 25 Jul 2025 21:48:27 -0500 Subject: [PATCH 3/4] removed old tutorial files and added some new (better) ones --- source/arche/tutorial_cpp/advanced.rst | 275 -------- source/arche/tutorial_cpp/advanced_agent.rst | 619 +++++++++++++++++++ source/arche/tutorial_cpp/dre.rst | 172 ------ source/arche/tutorial_cpp/dre_overview.rst | 329 ++++++++++ source/arche/tutorial_cpp/index.rst | 6 +- source/arche/tutorial_cpp/matquery.rst | 138 ----- 6 files changed, 950 insertions(+), 589 deletions(-) delete mode 100644 source/arche/tutorial_cpp/advanced.rst create mode 100644 source/arche/tutorial_cpp/advanced_agent.rst delete mode 100644 source/arche/tutorial_cpp/dre.rst create mode 100644 source/arche/tutorial_cpp/dre_overview.rst delete mode 100644 source/arche/tutorial_cpp/matquery.rst diff --git a/source/arche/tutorial_cpp/advanced.rst b/source/arche/tutorial_cpp/advanced.rst deleted file mode 100644 index bd32ad4f..00000000 --- a/source/arche/tutorial_cpp/advanced.rst +++ /dev/null @@ -1,275 +0,0 @@ - -Advanced Agent Creation -=============================== - -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. - -By the end of this chapter, you will understand how to: - -* Declare validated input parameters and manage internal state. -* Interact with Cyclus's Dynamic Resource Exchange (DRE) to request and accept trades. -* Use toolkit classes for material management, querying, and time series. -* Implement lifecycle methods (`Tick`, `Tock`, `EnterNotify`) effectively. -* Incorporate logging, testing, and optional output recording. - -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. - - -1. Agent Structure and Metadata --------------------------------- - -Begin by defining the agent header file with proper Cyclus metadata and parameter declarations. - -.. code-block:: c++ - - // advanced_reactor.h - #ifndef ADVANCED_REACTOR_H_ - #define ADVANCED_REACTOR_H_ - - #include - #include - #include "cyclus.h" - #include "toolkit/resource_buff.h" - #include "toolkit/mat_query.h" - #include "toolkit/commod_map_inst.h" - #include "toolkit/time_series.h" - - class AdvancedReactor : public cyclus::Facility { - public: - AdvancedReactor(cyclus::Context* ctx); - virtual ~AdvancedReactor() {} - - #pragma cyclus note { - "doc": "An advanced reactor facility that models fuel intake, burnup, and discharge." - } - - -Next, add some core parameters to the advanced reactor: - -.. code-block:: c++ - - #pragma cyclus var {"tooltip": "Maximum fresh fuel inventory (kg)", "units": "kg"} - double fresh_fuel_cap; - - #pragma cyclus var {"tooltip": "Maximum spent fuel inventory (kg)", "units": "kg"} - double spent_fuel_cap; - - #pragma cyclus var {"tooltip": "Residence time in core (timesteps)", "default": 3, "lbound": 1} - int residence_time; - - #pragma cyclus var {"tooltip": "Input commodity for fresh fuel"} - std::string in_commodity; - - #pragma cyclus var {"tooltip": "Output commodity for spent fuel"} - std::string out_commodity; - - #pragma cyclus var {"tooltip": "Recipe for spent fuel"} - std::string out_recipe; - - -Add some basic functions to the header file. These will be defined more thoroughly -later on. - -.. code-block:: c++ - - virtual std::string str(); - virtual void EnterNotify(); - virtual void Tick(); - virtual void Tock(); - - // DRE Functions: - virtual std::set::Ptr> GetMatlRequests(); - virtual std::set::Ptr> GetMatlBids( - cyclus::CommodMap::type& commod_requests); - virtual void AcceptMatlTrades( - const std::map, cyclus::Material::Ptr>& responses); - virtual cyclus::Material::Ptr OfferMatl( - cyclus::Material::Ptr request); - -Finally, we can add some material buffers and a TimeSeries, then cap the file -off! - -.. code-block:: c++ - - private: - cyclus::toolkit::ResourceBuff fresh_fuel_; // buffer for incoming fuel - cyclus::toolkit::ResourceBuff spent_fuel_; // buffer for outgoing waste - - std::queue> core_; // (entry_time, material) - - cyclus::toolkit::TimeSeries power_output_; // optional logging - - - }; - - #endif // ADVANCED_REACTOR_H_ - - -2. Defining Facility Behavior in the .cc File ---------------------------------------------- - -Implement the generic facility methods: - -.. code-block:: c++ - - #include "advanced_reactor.h" - - using cyclus::Material; - using cyclus::Trade; - using cyclus::Context; - - AdvancedReactor::AdvancedReactor(Context* ctx) - : cyclus::Facility(ctx), - fresh_fuel_(), spent_fuel_(), power_output_("power_output") {} - - std::string AdvancedReactor::str() { - std::stringstream ss; - ss << Facility::str() << "\n" - << "Fresh fuel buffer: " << fresh_fuel_.quantity() << " / " << fresh_fuel_cap << " kg\n" - << "Spent fuel buffer: " << spent_fuel_.quantity() << " / " << spent_fuel_cap << " kg\n" - << "Core loading: " << core_.size() << " assemblies"; - return ss.str(); - } - - void AdvancedReactor::EnterNotify() { - cyclus::Facility::EnterNotify(); - fresh_fuel_.capacity(fresh_fuel_cap); - spent_fuel_.capacity(spent_fuel_cap); - } - -In this facility, the tick function will simply check if the fuel has -been in the core for a certain amount of time, and then "discharge" it if so. -Tock, on the other hand, will move fresh fuel to the core if there's room to do -so. - -.. code-block:: c++ - - void AdvancedReactor::Tick() { - // discharge spent fuel if residence time reached - while (!core_.empty() && context()->time() - core_.front().first >= residence_time) { - cyclus::Material::Ptr m = core_.front().second->Transmute(out_recipe); - spent_fuel_.Push(m); - core_.pop(); - } - } - - void AdvancedReactor::Tock() { - // move fresh fuel to core if there's room - while (!fresh_fuel_.empty()) { - cyclus::Material::Ptr m = fresh_fuel_.Pop(); - core_.push(std::make_pair(context()->time(), m)); - } - } - -Next, we will implement the simplest version of the DRE functions. A more detailed -explaination of these functions, and interacting with the DRE can be found in the -next chapter of this tutorial. - -.. code-block:: c++ - - std::set::Ptr> AdvancedReactor::GetMatlRequests() { - std::set::Ptr> ports; - double qty = fresh_fuel_.space(); - - if (qty > 0) { - Material::Ptr dummy = cyclus::NewBlankMaterial(qty); - RequestPortfolio::Ptr port(new RequestPortfolio()); - port->AddRequest(dummy, this, in_commodity); - ports.insert(port); - } - - return ports; - } - - std::set::Ptr> AdvancedReactor::GetMatlBids( - cyclus::CommodMap::type& commod_requests) { - - std::set::Ptr> ports; - if (spent_fuel_.quantity() <= 0) return ports; - - for (auto& pair : commod_requests[out_commodity]) { - Material::Ptr offer = spent_fuel_.Peek(); - cyclus::BidPortfolio::Ptr port(new cyclus::BidPortfolio()); - port->AddBid(pair, offer, this); - ports.insert(port); - } - - return ports; - } - - void AdvancedReactor::AcceptMatlTrades( - const std::map, Material::Ptr>& responses) { - for (auto& pair : responses) { - fresh_fuel_.Push(pair.second); - } - } - - Material::Ptr AdvancedReactor::OfferMatl(Material::Ptr request) { - return spent_fuel_.PopQty(request->quantity()); - } - - -3. Advanced Features --------------------- - -Some additional, often helpful, options that are available in Cyclus are covered -briefly here. `MatQuery` is covered in more depth later in this tutorial, while -other functionality can be found elsewhere on the website. - -**a. Material Inspection** - -If desired, you can use `cyclus::toolkit::MatQuery` to inspect the isotopic -makeup of a `Cyclus::Material`: - -.. code-block:: c++ - - cyclus::toolkit::MatQuery mq(mat); - double u235 = mq.mass_frac("U235"); - -This is helpful for tracking enrichment or decay over time, and can be a powerful -tool for many more advanced features you may wish to implement in your agents. - -**b. Time Series Logging** - -The `TimeSeries` feature can be used to track the value of a variable over time -in the SQLite output file via a unique table: - -.. code-block:: c++ - - power_output_.Get(context())->AddValue("power_output", current_mw); - -**c. Error Handling and Validation** - -Logging is one of the most helpful tools we have in CYCLYS to inform users of -the internal state of Agents while the simulation is running. This is a great -debugging tool, and should be used whenever possible. For more information on -the Cyclus logging system, search for "Logging" on the website. - -.. code-block:: c++ - - if (fresh_fuel_.space() <= 0) { - LOG(cyclus::WARN) << "No room for new fuel"; - } - - -4. Testing Notes ----------------- - -* Use Google Test (`gtest`) in the `tests/` directory. -* Validate: - - * Behavior under full/empty buffer conditions - * Core loading and discharge timing - * Material transformation into output recipe - - -5. Summary and Next Steps -------------------------- - -This advanced tutorial walked through the full implementation of a robust Cyclus -Facility agent with: - -* Input validation and toolkit buffers -* Core DRE interactions for both input and output commodities -* Material transformation and storage logic -* Optional logging and diagnostics \ No newline at end of file diff --git a/source/arche/tutorial_cpp/advanced_agent.rst b/source/arche/tutorial_cpp/advanced_agent.rst new file mode 100644 index 00000000..a566aaa0 --- /dev/null +++ b/source/arche/tutorial_cpp/advanced_agent.rst @@ -0,0 +1,619 @@ +Advanced Archetype Techniques +============================= + +In this lesson, we will explore advanced techniques for developing sophisticated +archetypes in Cyclus. These techniques go beyond the basic toolkit features and +enable more complex, realistic, and powerful agent behaviors. + +In this lesson, we will: + +1. Master the MatQuery toolkit for material analysis +2. Implement direct DRE functions without toolkit policies +3. Use time series recording for advanced analytics +4. Add position tracking and geospatial capabilities +5. Implement custom database tables and logging +6. Explore advanced toolkit features and patterns + +Material Query and Analysis +-------------------------- + +The ``cyclus::toolkit::MatQuery`` class provides powerful tools for analyzing +material compositions and properties. This is essential for sophisticated +archetypes that need to make decisions based on material characteristics. + +Basic MatQuery Usage +++++++++++++++++++++ + +MatQuery allows you to easily extract information from material objects: + +.. code-block:: c++ + + #include "toolkit/mat_query.h" + + void MyFacility::AnalyzeMaterial(Material::Ptr mat) { + cyclus::toolkit::MatQuery mq(mat); + + // Get basic quantities + double total_mass = mq.qty(); // Total mass in kg + + // Get nuclide-specific information + double u235_mass = mq.mass(922350000); // Mass of U-235 in kg + double u235_moles = mq.moles(922350000); // Moles of U-235 + double u235_mass_frac = mq.mass_frac(922350000); // Mass fraction + double u235_atom_frac = mq.atom_frac(922350000); // Atom fraction + + // Work with sets of nuclides + std::set uranium_nuclides; + uranium_nuclides.insert(922350000); + uranium_nuclides.insert(922380000); + double total_u_mass_frac = mq.mass_frac(uranium_nuclides); + } + +Advanced Material Analysis ++++++++++++++++++++++++++ + +MatQuery is particularly useful for enrichment facilities and other +composition-sensitive processes: + +.. code-block:: c++ + + bool EnrichmentFacility::ValidFeed(Material::Ptr mat) { + cyclus::toolkit::MatQuery mq(mat); + + // Check for sufficient uranium content + double u235_frac = mq.atom_frac(922350000); + double u238_frac = mq.atom_frac(922380000); + double total_u_frac = u235_frac + u238_frac; + + // Must have uranium and reasonable enrichment + if (total_u_frac < 0.001) return false; // No uranium + if (u235_frac / total_u_frac > 0.05) return false; // Too enriched + + return true; + } + + double EnrichmentFacility::CalculateSWU(Material::Ptr mat, double product_assay) { + cyclus::toolkit::MatQuery mq(mat); + + // Calculate feed assay + double u235_mass = mq.mass(922350000); + double u238_mass = mq.mass(922380000); + double feed_assay = u235_mass / (u235_mass + u238_mass); + + // Use enrichment toolkit for SWU calculation + cyclus::toolkit::Assays assays(feed_assay, product_assay, tails_assay_); + return cyclus::toolkit::SwuRequired(mat->quantity(), assays); + } + +Direct DRE Implementation +------------------------ + +While the toolkit provides convenient buy/sell policies, sometimes you need +more control over the DRE process. Here's how to implement DRE functions +directly without using toolkit policies. + +Request Generation ++++++++++++++++++ + +Implementing ``GetMatlRequests()`` gives you complete control over what your +agent requests: + +.. code-block:: c++ + + std::set::Ptr> MyFacility::GetMatlRequests() { + std::set::Ptr> ports; + + // Check if we need material + double needed = CalculateNeededAmount(); + if (needed <= 0) return ports; + + // Create request portfolio + RequestPortfolio::Ptr port(new RequestPortfolio()); + + // Create material request + Material::Ptr dummy = Material::CreateUntracked(needed, + context()->GetRecipe("nat_u")); + + // Add request with commodity and preference + Request* req = port->AddRequest(dummy, this, "uranium", 1.0); + + // Add capacity constraint if needed + CapacityConstraint cc(needed); + port->AddConstraint(cc); + + ports.insert(port); + return ports; + } + +Bid Generation ++++++++++++++ + +Implementing ``GetMatlBids()`` allows you to respond to requests with custom logic: + +.. code-block:: c++ + + std::set::Ptr> MyFacility::GetMatlBids( + CommodMap::type& commod_requests) { + std::set::Ptr> ports; + + // Check if we have material to offer + if (inventory.quantity() <= 0) return ports; + + // Create bid portfolio + BidPortfolio::Ptr port(new BidPortfolio()); + + // Respond to requests for our commodity + std::vector*>& requests = commod_requests[out_commodity]; + for (std::vector*>::iterator it = requests.begin(); + it != requests.end(); ++it) { + + double available = inventory.quantity(); + double requested = (*it)->quantity(); + double offer_qty = std::min(available, requested); + + if (offer_qty > 0) { + Material::Ptr offer = inventory.Pop(offer_qty); + port->AddBid(**it, offer, this); + } + } + + // Add capacity constraint + CapacityConstraint cc(inventory.quantity()); + port->AddConstraint(cc); + + ports.insert(port); + return ports; + } + +Preference Adjustment +++++++++++++++++++++ + +Implementing ``AdjustMatlPrefs()`` allows you to dynamically adjust trade +preferences based on material characteristics: + +.. code-block:: c++ + + void MyFacility::AdjustMatlPrefs(PrefMap::type& prefs) { + PrefMap::type::iterator pmit; + for (pmit = prefs.begin(); pmit != prefs.end(); ++pmit) { + Request* req = pmit->first; + + // Adjust preferences based on bid characteristics + for (mit = pmit->second.begin(); mit != pmit->second.end(); ++mit) { + Bid* bid = mit->first; + Material::Ptr offer = bid->offer(); + + // Prefer material with higher U-235 content + cyclus::toolkit::MatQuery mq(offer); + double u235_frac = mq.atom_frac(922350000); + mit->second = mit->second + u235_frac * 100; + + // Penalize material from certain facility types + if (dynamic_cast(bid->bidder()) != NULL) { + mit->second = mit->second - 50; // Prefer fresh fuel + } + } + } + } + +Trade Execution +++++++++++++++ + +Implementing trade execution functions gives you control over how trades are +processed: + +.. code-block:: c++ + + void MyFacility::AcceptMatlTrades( + const std::vector, Material::Ptr>>& responses) { + + for (std::vector, Material::Ptr>>::const_iterator it = + responses.begin(); it != responses.end(); ++it) { + + Material::Ptr mat = it->second; + std::string commod = it->first.request->commodity(); + + // Process based on commodity + if (commod == "uranium") { + uranium_inventory.Push(mat); + } else if (commod == "fuel") { + fuel_inventory.Push(mat); + } + + // Record trade details + RecordTrade(mat, commod); + } + } + + void MyFacility::GetMatlTrades( + const std::vector>& trades, + std::vector, Material::Ptr>>& responses) { + + for (std::vector>::const_iterator it = trades.begin(); + it != trades.end(); ++it) { + + double requested_qty = it->amt; + Material::Ptr response = inventory.Pop(requested_qty); + + responses.push_back(std::make_pair(*it, response)); + } + } + +Time Series Recording +-------------------- + +Cyclus provides powerful time series recording capabilities that allow you to +track agent performance over time. + +Basic Time Series Recording +++++++++++++++++++++++++++ + +Use the toolkit's time series functions to record agent metrics: + +.. code-block:: c++ + + #include "toolkit/timeseries.h" + + void MyReactor::Tock() { + // Record power output + if (is_operating) { + cyclus::toolkit::RecordTimeSeries(this, power_output); + cyclus::toolkit::RecordTimeSeries("supplyPOWER", this, power_output); + } else { + cyclus::toolkit::RecordTimeSeries(this, 0); + cyclus::toolkit::RecordTimeSeries("supplyPOWER", this, 0); + } + + // Record custom metrics + cyclus::toolkit::RecordTimeSeries("fuel_inventory", this, + fuel_inventory.quantity()); + cyclus::toolkit::RecordTimeSeries("core_temperature", this, + core_temperature); + } + +Custom Time Series ++++++++++++++++++ + +You can create custom time series for any metric: + +.. code-block:: c++ + + void EnrichmentFacility::Tock() { + // Record enrichment-specific metrics + cyclus::toolkit::RecordTimeSeries(this, + swu_used); + cyclus::toolkit::RecordTimeSeries(this, + feed_used); + + // Record custom metrics + cyclus::toolkit::RecordTimeSeries("tails_assay", this, tails_assay); + cyclus::toolkit::RecordTimeSeries("product_assay", this, product_assay); + cyclus::toolkit::RecordTimeSeries("separation_factor", this, + separation_factor); + } + +Position Tracking +---------------- + +The position toolkit allows you to add geographic coordinates to your agents, +enabling spatial analysis and regional modeling. + +Adding Position to Archetypes +++++++++++++++++++++++++++++ + +Include the position snippet in your archetype header: + +.. code-block:: c++ + + #include "toolkit/position.h" + + class MyFacility : public cyclus::Facility { + public: + MyFacility(cyclus::Context* ctx); + virtual ~MyFacility() {} + + virtual void EnterNotify(); + + private: + // Include the position snippet + #include "toolkit/position.cycpp.h" + + // Your other member variables... + }; + +Initializing Position +++++++++++++++++++++ + +Initialize the position in your ``EnterNotify()`` method: + +.. code-block:: c++ + + void MyFacility::EnterNotify() { + // Initialize position with user-provided coordinates + InitializePosition(); + + // You can also set position programmatically + coordinates.set_position(latitude, longitude); + coordinates.RecordPosition(this); + } + +Using Position Data +++++++++++++++++++ + +You can use position data for distance calculations and regional analysis: + +.. code-block:: c++ + + void MyFacility::AdjustMatlPrefs(PrefMap::type& prefs) { + PrefMap::type::iterator pmit; + for (pmit = prefs.begin(); pmit != prefs.end(); ++pmit) { + for (mit = pmit->second.begin(); mit != pmit->second.end(); ++mit) { + Bid* bid = mit->first; + Agent* bidder = bid->bidder(); + + // Prefer nearby suppliers + if (dynamic_cast(bidder) != NULL) { + cyclus::Facility* facility = dynamic_cast(bidder); + + // Calculate distance (if both have positions) + double distance = coordinates.Distance(facility->coordinates); + + // Prefer closer suppliers + mit->second = mit->second + (1000.0 / (distance + 1.0)); + } + } + } + } + +Custom Database Tables +--------------------- + +Cyclus allows you to create custom database tables for detailed analysis and +reporting. + +Creating Custom Tables ++++++++++++++++++++++ + +Use the context's ``NewDatum()`` function to create custom tables: + +.. code-block:: c++ + + void MyFacility::Tick() { + // Calculate metrics + double efficiency = CalculateEfficiency(); + double cost = CalculateOperatingCost(); + double inventory_level = inventory.quantity(); + + // Record to custom table + context()->NewDatum("MyFacilityMetrics") + ->AddVal("AgentId", id()) + ->AddVal("Time", context()->time()) + ->AddVal("Efficiency", efficiency) + ->AddVal("OperatingCost", cost) + ->AddVal("InventoryLevel", inventory_level) + ->AddVal("Status", is_operating ? "operating" : "shutdown") + ->Record(); + } + +Complex Data Recording ++++++++++++++++++++++ + +You can record complex data structures and relationships: + +.. code-block:: c++ + + void MyReactor::RecordFuelCycle() { + // Record fuel cycle information + context()->NewDatum("FuelCycleData") + ->AddVal("AgentId", id()) + ->AddVal("Time", context()->time()) + ->AddVal("CycleNumber", current_cycle) + ->AddVal("FuelAssemblies", core.count()) + ->AddVal("PowerOutput", power_output) + ->AddVal("Burnup", average_burnup) + ->Record(); + + // Record individual assembly data + for (int i = 0; i < core.count(); i++) { + Material::Ptr assembly = core.Peek(i); + cyclus::toolkit::MatQuery mq(assembly); + + context()->NewDatum("AssemblyData") + ->AddVal("AgentId", id()) + ->AddVal("Time", context()->time()) + ->AddVal("AssemblyIndex", i) + ->AddVal("U235Content", mq.mass(922350000)) + ->AddVal("Burnup", assembly_burnup[i]) + ->Record(); + } + } + +Advanced Toolkit Features +------------------------ + +Cyclus provides several advanced toolkit features for sophisticated archetypes. + +Commodity Producer Manager +++++++++++++++++++++++++++ + +The commodity producer manager helps track and manage facilities that produce +specific commodities: + +.. code-block:: c++ + + #include "toolkit/commodity_producer_manager.h" + + class MyInstitution : public cyclus::Institution { + private: + cyclus::toolkit::CommodityProducerManager manager_; + + public: + virtual void Build(cyclus::Agent* parent) { + Institution::Build(parent); + manager_.RegisterAllChildren(this); + } + + virtual void Tick() { + // Get total capacity for a commodity + double total_capacity = manager_.TotalCapacity("electricity"); + + // Check if we need to build more facilities + if (total_capacity < required_capacity) { + context()->SchedBuild("PowerPlant"); + } + } + }; + +Symbolic Functions +++++++++++++++++++ + +The symbolic function toolkit allows you to create mathematical expressions: + +.. code-block:: c++ + + #include "toolkit/symb_func.h" + + void MyFacility::SetupCostFunction() { + // Create a cost function based on throughput + cyclus::toolkit::SymbFunctionFactory factory; + + // Cost = base_cost + throughput * variable_cost + std::string expr = "base_cost + throughput * variable_cost"; + cost_function_ = factory.Create(expr); + + // Set parameters + cost_function_->SetVariable("base_cost", 1000.0); + cost_function_->SetVariable("variable_cost", 50.0); + } + + double MyFacility::CalculateCost(double throughput) { + cost_function_->SetVariable("throughput", throughput); + return cost_function_->Eval(); + } + +Enrichment Toolkit +++++++++++++++++++ + +The enrichment toolkit provides specialized functions for enrichment calculations: + +.. code-block:: c++ + + #include "toolkit/enrichment.h" + + double EnrichmentFacility::CalculateEnrichment(Material::Ptr feed, + double product_assay, + double tails_assay) { + cyclus::toolkit::MatQuery mq(feed); + double feed_assay = mq.atom_frac(922350000); + + // Use enrichment toolkit + cyclus::toolkit::Assays assays(feed_assay, product_assay, tails_assay); + double swu_required = cyclus::toolkit::SwuRequired(feed->quantity(), assays); + double feed_required = cyclus::toolkit::FeedQty(feed->quantity(), assays); + + return swu_required; + } + +Best Practices for Advanced Archetypes +------------------------------------- + +Performance Optimization +++++++++++++++++++++++ + +* Use MatQuery efficiently - create one instance and reuse it +* Minimize database writes in tight loops +* Use appropriate data structures for your use case + +.. code-block:: c++ + + class OptimizedFacility : public cyclus::Facility { + private: + // Cache MatQuery objects + std::map mq_cache_; + + public: + void AnalyzeMaterial(Material::Ptr mat) { + // Reuse MatQuery objects + if (mq_cache_.find(mat) == mq_cache_.end()) { + mq_cache_[mat] = cyclus::toolkit::MatQuery(mat); + } + + cyclus::toolkit::MatQuery& mq = mq_cache_[mat]; + // Use mq for analysis... + } + }; + +Error Handling +++++++++++++++ + +* Always check for null pointers and valid data +* Use appropriate exception handling +* Provide meaningful error messages + +.. code-block:: c++ + + void MyFacility::ProcessMaterial(Material::Ptr mat) { + if (!mat) { + throw cyclus::ValueError("Material pointer is null"); + } + + cyclus::toolkit::MatQuery mq(mat); + if (mq.qty() <= 0) { + throw cyclus::ValueError("Material has zero or negative quantity"); + } + + // Process material... + } + +Testing Advanced Features +++++++++++++++++++++++++ + +* Test MatQuery with known compositions +* Verify time series recording +* Test position calculations +* Validate custom database tables + +.. code-block:: c++ + + TEST(AdvancedFeaturesTest, MatQueryAnalysis) { + // Create test material + CompMap comp; + comp[922350000] = 0.05; // 5% U-235 + comp[922380000] = 0.95; // 95% U-238 + Composition::Ptr c = Composition::CreateFromAtom(comp); + Material::Ptr mat = Material::CreateUntracked(100.0, c); + + // Test MatQuery + cyclus::toolkit::MatQuery mq(mat); + EXPECT_DOUBLE_EQ(5.0, mq.mass(922350000)); + EXPECT_DOUBLE_EQ(0.05, mq.atom_frac(922350000)); + } + +Debugging Advanced Features +++++++++++++++++++++++++++ + +* Use logging to track complex behaviors +* Add debug output for time series data +* Verify position calculations +* Check database table contents + +.. code-block:: c++ + + void MyFacility::DebugDRE() { + LOG(cyclus::LEV_INFO4, "MyFacility") + << "Processing DRE at time " << context()->time(); + + // Log material analysis + for (auto& mat : inventory.PopN(inventory.count())) { + cyclus::toolkit::MatQuery mq(mat); + LOG(cyclus::LEV_INFO5, "MyFacility") + << "Material: " << mq.qty() << " kg, " + << "U-235: " << mq.atom_frac(922350000); + } + } + +The advanced techniques covered in this tutorial enable you to create +sophisticated, realistic archetypes that can model complex fuel cycle +scenarios. By mastering these tools, you can develop archetypes that provide +detailed analysis, accurate physics modeling, and comprehensive reporting +capabilities. \ No newline at end of file diff --git a/source/arche/tutorial_cpp/dre.rst b/source/arche/tutorial_cpp/dre.rst deleted file mode 100644 index a841cf99..00000000 --- a/source/arche/tutorial_cpp/dre.rst +++ /dev/null @@ -1,172 +0,0 @@ - -Advanced DRE Interaction -======================== - -This chapter explains how to interact with Cyclus's **Dynamic Resource Exchange (DRE)** system using advanced methods. Facilities and Institutions use the DRE to participate in resource markets by issuing **requests**, placing **bids**, and accepting or rejecting **trades**. - -This guide covers: - -* Full lifecycle of a material trade in Cyclus -* Implementation of request, bid, and trade handlers -* Conditional bidding strategies -* Interaction with multiple commodities -* Resource validation and transformation - -1. The DRE Lifecycle --------------------- - -Each simulation timestep, the DRE coordinates a market: - -1. Agents issue **requests** for resources (`GetMatlRequests()` or `GetProductRequests()`) -2. Agents issue **bids** in response to requests (`GetMatlBids()` or `GetProductBids()`) -3. The DRE matches trades -4. Agents execute trades using: - - * `AcceptMatlTrades()` / `AcceptProductTrades()` to receive material - * `OfferMatl()` / `OfferGen()` to provide material - - -2. Requesting Material ----------------------- - -Requests are issued when the agent has capacity for a resource. Each request: - -* Specifies a desired quantity -* Indicates the input commodity -* Can include a recipe or preference weighting - -.. code-block:: cpp - -std::set\::Ptr> Reactor::GetMatlRequests() { -std::set\::Ptr> ports; - -``` -double amt = fresh_fuel_.space(); -if (amt <= 0) return ports; - -Material::Ptr dummy = cyclus::NewBlankMaterial(amt); -RequestPortfolio::Ptr port(new RequestPortfolio()); -port->AddRequest(dummy, this, in_commodity); -ports.insert(port); -return ports; -``` - -} - ---- - -3. Bidding Material - ---- - -Bids are generated when an agent has material to offer. Bids: - -* Respond to specific requests -* Specify quantity and provider -* May include constraints (exclusive, mutual, etc.) - -.. code-block:: cpp - -std::set\::Ptr> Reactor::GetMatlBids( -cyclus::CommodMap::type& commod\_requests) { - -``` -std::set::Ptr> ports; -if (spent_fuel_.quantity() <= 0) return ports; - -for (auto& req : commod_requests[out_commodity]) { - Material::Ptr offer = spent_fuel_.Peek(); - BidPortfolio::Ptr port(new BidPortfolio()); - port->AddBid(req, offer, this); - ports.insert(port); -} -return ports; -``` - -} - -You can conditionally bid based on isotopic content, facility state, or request attributes. - ---- - -4. Accepting Material - ---- - -After the market resolves, agents are delivered trades. - -.. code-block:: cpp - -void Reactor::AcceptMatlTrades( -const std::map\, Material::Ptr>& responses) { -for (auto& pair : responses) { -fresh\_fuel\_.Push(pair.second); -} -} - -This is typically where tracking, transformation, or validation occurs. - ---- - -5. Providing Material - ---- - -Outgoing trades are fulfilled using `OfferMatl()`: - -.. code-block:: cpp - -Material::Ptr Reactor::OfferMatl(Material::Ptr request) { -return spent\_fuel\_.PopQty(request->quantity()); -} - -You may return: - -* Raw buffer contents -* A transmuted material (`mat->Transmute("NewRecipe")`) -* A composition-weighted mixture (see toolkit::Mix()) - ---- - -6. Tips and Best Practices - ---- - -* Use Peek() to inspect buffer contents before bidding. -* Always guard bids and requests with quantity checks. -* Prefer making bids conditional on request content (e.g., recipe match). -* Store incoming trades by commodity if multiple inputs exist. -* Log commodity mismatches or undersupplied trades. - ---- - -7. Beyond Facilities - ---- - -Institutions can also interact with the DRE, though less commonly. They may: - -* Mediate trades for child agents -* Collect price or commodity data -* Implement regional trade quotas - -This often requires custom mapping or override of Institution DRE hooks. - ---- - -8. Summary - ---- - -Advanced DRE interaction involves five core functions and optional filtering or logic. By understanding this flow, developers can: - -* Precisely control when and how trades occur -* Encode policy and preference logic -* Track material flow for safety or economics - -Continue with the next chapter on inspecting materials with the `Marquetry` toolkit. - -.. seealso:: - -* \:ref:`marquetry_query` -* \:ref:`custom_institution` diff --git a/source/arche/tutorial_cpp/dre_overview.rst b/source/arche/tutorial_cpp/dre_overview.rst new file mode 100644 index 00000000..08d9325b --- /dev/null +++ b/source/arche/tutorial_cpp/dre_overview.rst @@ -0,0 +1,329 @@ +Understanding the Dynamic Resource Exchange (DRE) +================================================= + +In this lesson, we will explore the Dynamic Resource Exchange (DRE), which is the +heart of every Cyclus simulation time step. The DRE coordinates the exchange of +resources between agents in the simulation, enabling complex supply chain modeling. + +In this lesson, we will: + +1. Understand the overall DRE process and its role in simulation +2. Learn about the five phases of the DRE +3. Explore how agents participate in the exchange +4. Understand the relationship between Tick, DRE, and Tock phases +5. See how preferences and constraints work in the exchange + +Overview +-------------- + +The Dynamic Resource Exchange (DRE) is the core mechanism that enables agents in +a Cyclus simulation to trade resources with each other. Every ``Trader`` that is +registered with the simulation ``Context`` is automatically included in the exchange. +Agents can either implement the ``Trader`` interface as a mixin or can be composed +of one or more traders. Note that the ``Facility`` class derives from the +``Trader`` interface, and therefore all agents that derive from ``Facility`` are +also traders. + +On each time step, there is a separate ``ResourceExchange`` instance for each +concrete ``Resource`` type (i.e., ``Materials`` and ``Products``) of which the +kernel is aware. For example, there is an exchange for ``Material`` resources and +another for ``Product`` resources. + +The DRE operates as a market mechanism where: + +* **Consumers** (agents that need resources) issue requests for bids +* **Producers** (agents that have resources) respond with bids +* **Preferences** are assigned to bid-request pairs +* **Constraints** are applied to limit exchanges +* **Trades** are executed based on the solution + +The DRE is comprised of five phases which execute in series: + +* Request for Bids (RFB) Phase +* Response to Request for Bids (RRFB) Phase +* Preference Adjustment (PA) Phase +* Solution Phase +* Trade Execution Phase + +Understanding the Time Step Structure +------------------------------------ + +Before diving into the DRE phases, it's important to understand how the DRE fits +into the overall simulation time step structure. Each time step in Cyclus follows +this sequence: + +.. code-block:: c++ + + // From Timer::RunSim() in src/timer.cc + DoBuild(); // Build new agents + DoTick(); // Agent Tick phase + DoResEx(&matl_manager, &genrsrc_manager); // DRE execution + DoTock(); // Agent Tock phase + DoDecision(); // Agent Decision phase + DoDecom(); // Decommission agents + +The DRE occurs between the ``Tick()`` and ``Tock()`` phases, which means: + +* **Tick()** - Agents prepare for the exchange (e.g., update capacity, set preferences) +* **DRE** - The exchange itself occurs +* **Tock()** - Agents respond to the exchange results (e.g., process received materials) + +This ordering is critical because it ensures that: +1. All agents have updated their state before the exchange +2. The exchange has access to the most current information +3. Agents can react to exchange results before the next time step + +The Five DRE Phases +-------------------- + +Request for Bids (RFB) Phase ++++++++++++++++++++++++++++++ + +In the Request for Bids (RFB) phase, the exchange queries all registered traders +regarding their demand for a given resource type. Querying is provided through +the ``Trader`` interface's "get requests" functions for a given resource type, +e.g., ``GetMatlRequests()`` (C++) or ``get_material_requests()`` (Python). + +Requests are modeled as collections of ``RequestPortfolio`` instances, where each +portfolio includes a collection of ``Request`` objects and a collection of +``CapacityConstraint`` objects. A portfolio is sufficiently met if one or more +of its constituent requests are met and all of its constraints are satisfied. + +A request provides: +* A target resource (quantity and composition) +* A commodity identifier +* A preference for that commodity-resource combination + +For example, a reactor facility might request fuel: + +.. code-block:: c++ + + std::set::Ptr> Reactor::GetMatlRequests() { + std::set::Ptr> ports; + + double amt = fresh_fuel_.space(); + if (amt <= 0) return ports; + + Material::Ptr dummy = cyclus::NewBlankMaterial(amt); + RequestPortfolio::Ptr port(new RequestPortfolio()); + port->AddRequest(dummy, this, in_commodity); + ports.insert(port); + return ports; + } + +Response to Request for Bids (RRFB) Phase ++++++++++++++++++++++++++++++++++++++++++ + +In the Response to Request for Bids (RRFB) phase, the exchange queries all +registered traders regarding their supply for a given resource type. Querying is +provided through the ``Trader`` interface's "get bids" functions for a given +resource type, e.g., ``GetMatlBids()`` (C++) or ``get_material_bids()`` (Python). + +Bids are modeled as collections of ``BidPortfolio`` instances, where each +portfolio includes a collection of ``Bid`` objects and a collection of +``CapacityConstraint`` objects. A portfolio is not violated if any of its +constituent bids are connected to their requests and all of its constraints are +satisfied. + +A bid is comprised of: +* A request to which it is responding +* A resource that it is offering in response to the request +* Optional constraints on the bidder's capacity + +For example, a fuel fabrication facility might respond to fuel requests: + +.. code-block:: c++ + + std::set::Ptr> FuelFab::GetMatlBids( + CommodMap::type& commod_requests) { + std::set::Ptr> ports; + + std::vector*>& requests = commod_requests[out_commodity]; + for (std::vector*>::iterator it = requests.begin(); + it != requests.end(); ++it) { + double qty = std::min((*it)->quantity(), inventory.quantity()); + if (qty > 0) { + Material::Ptr offer = inventory.Pop(qty); + BidPortfolio::Ptr port(new BidPortfolio()); + port->AddBid(**it, offer, this); + ports.insert(port); + } + } + return ports; + } + +Preference Adjustment (PA) Phase +++++++++++++++++++++++++++++++++ + +In the Preference Adjustment (PA) phase, requesters are allowed to view which +bids were matched to their requests, and adjust their preference for the given +bid-request pairing. Querying is provided through the ``Agent`` interface, so all +Cyclus archetypes may adjust preferences. The "adjust prefs" functions are based +on a given resource type, e.g., ``AdjustMaterialPrefs`` (C++) or +``adjust_material_prefs()`` (Python). + +Preferences are used by resource exchange solvers to inform their solution +method. The default preference for all bids is one (1). Agents will only utilize +the PA phase if there is a reason to update preferences over the default +provided in their original request. + +Preferences can be adjusted by both the original ``Trader`` placing requests as +well as any parent ``Agent`` instances, with the trader adjusting first and the +most senior parent adjusting last. In the supported Region-Institution-Facility +agent relationship, Facilities adjust first, followed by Institution and Region +parent agents. + +For example, an agent might prefer trades with agents of the same type: + +.. code-block:: c++ + + virtual void Reactor::AdjustMatlPrefs(PrefMap::type& prefs) { + PrefMap::type::iterator pmit; + for (pmit = prefs.begin(); pmit != prefs.end(); ++pmit) { + Request* req = pmit->first; + Reactor* cast = dynamic_cast(req->requester()); + if (cast != NULL) { + // We prefer trading with other reactors + for (mit = pmit->second.begin(); mit != pmit->second.end(); ++mit) { + mit->second = mit->second + 10; + } + } + } + } + +Solution Phase ++++++++++++++ + +The Solution Phase is straightforward from a module developer point of view. +Given requests, bids for those requests, and preferences for each request-bid +pairing, a ``ExchangeSolver`` selects request-bid pairs to satisfy and the +quantity of each resource to assign to each satisfied request-bid pairing. + +The solution timing and actual pairings will depend on the concrete solver that +is employed by the Cyclus kernel. The solver uses optimization techniques to +maximize the global preference while satisfying all constraints. + +Trade Execution Phase +++++++++++++++++++++ + +When satisfactory request-bid pairings are determined, a final communication is +executed for each bidder and requester during the Trade Execution Phase. +Bidders are notified of their winning bids through the ``Trader`` "get trades" +functions (e.g., ``GetMatlTrades()`` in C++ and ``get_material_trades()`` in +Python), and requesters are provided their satisfied requests through the +``Trader`` "accept trades" functions (e.g., ``AcceptMatlTrades()`` in C++ and +``accept_material_trades()`` in Python). + +For example, a facility might accept material trades: + +.. code-block:: c++ + + void Reactor::AcceptMatlTrades(const std::vector>& trades) { + std::vector>::const_iterator it; + for (it = trades.begin(); it != trades.end(); ++it) { + fresh_fuel_.Push(it->bid->offer()); + } + } + +And offer material in trades: + +.. code-block:: c++ + + void Reactor::GetMatlTrades(const std::vector>& trades, + std::vector, Material::Ptr>>& responses) { + std::vector>::const_iterator it; + for (it = trades.begin(); it != trades.end(); ++it) { + Material::Ptr response = spent_fuel_.Pop(it->bid->offer()->quantity()); + responses.push_back(std::make_pair(*it, response)); + } + } + +Understanding Constraints +------------------------ + +Constraints play a crucial role in the DRE by limiting what trades can be made. +There are several types of constraints: + +**Capacity Constraints** - Limit the total quantity that can be exchanged +**Exclusive Constraints** - Ensure only one request in a portfolio is satisfied +**Mutual Constraints** - Ensure all requests in a portfolio are satisfied together + +For example, an enrichment facility might have a SWU (Separative Work Unit) +constraint: + +.. code-block:: c++ + + // The facility can only provide 500 SWUs total + CapacityConstraint swu_constraint(500.0, swu_conversion); + port->AddConstraint(swu_constraint); +``` + +This ensures that the total SWUs used across all bids cannot exceed 500. + +Understanding Preferences +----------------------- + +Preferences determine which trades are favored in the exchange. Higher preference +values indicate more desirable trades. Preferences can be: + +* **Static** - Set when the request is created +* **Dynamic** - Adjusted during the PA phase based on bid characteristics + +For example, a repository might prefer material with lower heat load: + +.. code-block:: c++ + + virtual void Repository::AdjustMatlPrefs(PrefMap::type& prefs) { + PrefMap::type::iterator pmit; + for (pmit = prefs.begin(); pmit != prefs.end(); ++pmit) { + for (mit = pmit->second.begin(); mit != pmit->second.end(); ++mit) { + double heat_load = CalculateHeatLoad(mit->first->bid->offer()); + // Lower heat load = higher preference + mit->second = mit->second - heat_load; + } + } + } + +Debugging the DRE +----------------- + +The DRE can be complex, and debugging it can be challenging. Cyclus provides +several tools to help: + +**Environment Variable Debugging** +Set the environment variable to enable detailed DRE logging: + +.. code-block:: console + + $ export CYCLUS_DEBUG_DRE=1 + $ cyclus input.xml + +**Logging Levels** +Use different verbosity levels to see DRE information: + +.. code-block:: console + + $ cyclus -v 3 input.xml # Shows DRE phase information + $ cyclus -v 4 input.xml # Shows detailed DRE debugging + +**Common Issues** +* **No trades executed** - Check if requests and bids are being generated +* **Unexpected trade quantities** - Check capacity constraints +* **Wrong trades selected** - Check preference values +* **Missing agents** - Ensure agents are properly registered as traders + +Best Practices +-------------- + +When implementing DRE interactions in your archetypes: + +1. **Always check capacity** before making requests or bids +2. **Use meaningful preference values** to guide trade selection +3. **Implement constraints carefully** to avoid over-constraining +4. **Test with simple cases first** before complex scenarios +5. **Use logging** to understand what's happening in the exchange +6. **Consider the timing** - Tick() prepares, DRE executes, Tock() responds + +The DRE is a powerful mechanism that enables complex supply chain modeling in +Cyclus. Understanding how it works will help you create more effective and +realistic archetypes. \ No newline at end of file diff --git a/source/arche/tutorial_cpp/index.rst b/source/arche/tutorial_cpp/index.rst index 22ac8d18..e73a5b31 100644 --- a/source/arche/tutorial_cpp/index.rst +++ b/source/arche/tutorial_cpp/index.rst @@ -27,10 +27,8 @@ This tutorial has the following steps: toolkit testing input_files - advanced - dre - matquery - custominst + dre_overview + advanced_agent .. Given enough time, the following extra topics may be covered: diff --git a/source/arche/tutorial_cpp/matquery.rst b/source/arche/tutorial_cpp/matquery.rst deleted file mode 100644 index 270837ee..00000000 --- a/source/arche/tutorial_cpp/matquery.rst +++ /dev/null @@ -1,138 +0,0 @@ - -Material Inspection with Marquetry -================================== - -This chapter introduces the use of **cyclus::toolkit::MatQuery** and related utilities to inspect and analyze nuclear material compositions inside Cyclus agents. These tools are essential for modeling enrichment, burnup, quality control, and any behavior conditioned on isotopic content. - -Covered Topics: - -* Introduction to `MatQuery` -* Retrieving isotopic mass fractions and total masses -* Composition filtering and enrichment logic -* Recipe matching and decay handling - ---- - -1. What is MatQuery? - ---- - -`MatQuery` is a Cyclus toolkit class that wraps a `cyclus::Material::Ptr` and provides methods to easily extract isotopic data. - -It enables: - -* Retrieval of mass fractions: `mq.mass_frac("U235")` -* Retrieval of total mass of isotope: `mq.mass("Pu239")` -* Access to full isotope maps: `mq.comp()->mass()` -* Composition property tests (e.g., `IsFissile`, `HasElement`, etc.) - ---- - -2. Basic Usage - ---- - -.. code-block:: cpp - -void EnrichmentFacility::AcceptMatlTrades( -const std::map\& responses) { -for (auto& pair : responses) { -cyclus::Material::Ptr mat = pair.second; -cyclus::toolkit::MatQuery mq(mat); - -``` - double u235_frac = mq.mass_frac("U235"); - double total_u = mq.mass("U235") + mq.mass("U238"); - - if (u235_frac < tails_assay_) { - LOG(cyclus::ERROR) << "Incoming material under-enriched"; - } - - feed_buffer_.Push(mat); -} -``` - -} - ---- - -3. Conditional Bidding or Acceptance - ---- - -`MatQuery` can be used to evaluate trade viability. For instance: - -.. code-block:: cpp - -bool IsAcceptableFuel(cyclus::Material::Ptr mat) { -cyclus::toolkit::MatQuery mq(mat); -double pu\_frac = mq.mass\_frac("Pu239"); -return pu\_frac > 0.02; -} - -This lets your agent filter trade offers or gate reactor core loading based on fuel specs. - ---- - -4. Filtering and Enrichment Modeling - ---- - -When performing enrichment or isotope separation, you'll often: - -* Extract uranium mass vector -* Normalize target enrichment -* Compute SWU requirements (via toolkit::Separations) - -.. code-block:: cpp - -double feed\_assay = mq.mass\_frac("U235") + mq.mass\_frac("U234"); -double swu = toolkit::Separations::SwuRequired( -feed\_mass, feed\_assay, product\_assay, tails\_assay); - -You may optionally track the material path and produce a blended result using `Material::Blend()` or `toolkit::Mix()`. - ---- - -5. Advanced Composition Access - ---- - -Use the `comp()` method to directly access the underlying `cyclus::Composition::Ptr`: - -.. code-block:: cpp - -cyclus::CompMap cm = mq.comp()->mass(); -for (auto iso : cm) { -std::cout << "Isotope: " << iso.first << ", mass = " << iso.second << std::endl; -} - -You can use this for generating custom reports or modeling precise decay chains. - ---- - -6. Considerations for Decay and Recipes - ---- - -* Materials do **not** decay unless `Material::Decay()` is explicitly called. -* Use `mat->Transmute("new_recipe")` to apply predefined compositions. -* Validate incoming recipes using `mat->comp()->Equals(Context()->GetRecipe("name"))`. - ---- - -7. Summary - ---- - -The `MatQuery` toolkit provides intuitive, composable access to isotopic material data. It is useful for: - -* Enrichment and burnup modeling -* Safety validation and material rejection -* Conditional processing logic - -Next, we explore how institutions can leverage Cyclus features to coordinate agents and implement deployment or policy logic. - -.. seealso:: - -* \:ref:`custom_institution` From 4b6e60b0d80b755afd80c4ab587d466c96c69dcf Mon Sep 17 00:00:00 2001 From: Dean Krueger Date: Sun, 27 Jul 2025 12:05:50 -0500 Subject: [PATCH 4/4] added more to advanced tutorial, did some checking and found some (very minor) errors. --- source/arche/tutorial_cpp/advanced_agent.rst | 4 +- .../tutorial_cpp/creating_conversion.rst | 452 ++++++++++++++++++ .../creating_random_event_inst.rst | 287 +++++++++++ .../tutorial_cpp/creating_tariff_region.rst | 320 +++++++++++++ source/arche/tutorial_cpp/index.rst | 3 + 5 files changed, 1063 insertions(+), 3 deletions(-) create mode 100644 source/arche/tutorial_cpp/creating_conversion.rst create mode 100644 source/arche/tutorial_cpp/creating_random_event_inst.rst create mode 100644 source/arche/tutorial_cpp/creating_tariff_region.rst diff --git a/source/arche/tutorial_cpp/advanced_agent.rst b/source/arche/tutorial_cpp/advanced_agent.rst index a566aaa0..fc16ded2 100644 --- a/source/arche/tutorial_cpp/advanced_agent.rst +++ b/source/arche/tutorial_cpp/advanced_agent.rst @@ -152,7 +152,7 @@ Implementing ``GetMatlBids()`` allows you to respond to requests with custom log double offer_qty = std::min(available, requested); if (offer_qty > 0) { - Material::Ptr offer = inventory.Pop(offer_qty); + Material::Ptr offer = Material::CreateUntracked(offer_qty, output.Peek()->comp()); port->AddBid(**it, offer, this); } } @@ -220,8 +220,6 @@ processed: fuel_inventory.Push(mat); } - // Record trade details - RecordTrade(mat, commod); } } diff --git a/source/arche/tutorial_cpp/creating_conversion.rst b/source/arche/tutorial_cpp/creating_conversion.rst new file mode 100644 index 00000000..c81036f4 --- /dev/null +++ b/source/arche/tutorial_cpp/creating_conversion.rst @@ -0,0 +1,452 @@ +Creating Conversion +=================== + +In this tutorial, you will learn how to build your very first full Cyclus Agent: +a Conversion Facility. This guide is designed for beginners—especially new +Cyclus users—who are just starting with Cyclus and C++ agent +development. We will walk through every step, clearly indicating what code +goes in the header (.h) file and what goes in the implementation (.cc) file. +We will also introduce the basics of testing your agent. + + +Introduction +------------ + +A conversion facility is a key part of the nuclear fuel cycle. It receives +uranium ore (U3O8) as input and produces uranium hexafluoride (UF6) as output. +In Cyclus, this means: + +* Requesting U3O8 as a feed commodity +* Offering UF6 as a product commodity +* Managing inventories and conversion rates +* Recording operational metrics and geospatial data + +This tutorial will guide you through creating a robust, extensible agent ready +for integration into complex fuel cycle simulations. + +File Structure: .h vs .cc +------------------------- + +In C++ projects, code is split into header files (.h) and implementation files +(.cc): + +* The **header file (.h)** declares the class, its member variables, and its + functions (methods). Think of it as the "blueprint" for your agent. +* The **implementation file (.cc)** contains the actual code for the functions +you declared in the header. This is where the logic lives. + +We will clearly indicate which code belongs in which file as we go. + +Header File (.h): Step-by-Step +++++++++++++++++++++++++++++++ + +Let's start by building the header file for your new agent. We'll break it down +into sections and explain what each part does. + +1. Include Guards and Includes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Include guards prevent the file from being included more than once, which can +cause errors. The includes bring in the necessary Cyclus and toolkit headers. +Note that, depending on your desired functionality, you may need to include +additional headers. + +.. code-block:: c++ + + // conversion_facility.h + #ifndef CONVERSION_FACILITY_H_ + #define CONVERSION_FACILITY_H_ + + #include "cyclus.h" + #include "toolkit/mat_query.h" + #include "toolkit/timeseries.h" + #include "toolkit/position.h" + +2. Class Declaration +~~~~~~~~~~~~~~~~~~~~ +This is where you declare your ConversionFacility class, which inherits from +cyclus::Facility. The class declaration lists all the functions and variables +your agent will have. + +.. code-block:: c++ + + class ConversionFacility : public cyclus::Facility { + public: + ConversionFacility(cyclus::Context* ctx); + virtual ~ConversionFacility(); + + // Cyclus lifecycle methods + virtual void EnterNotify(); + virtual void Tick(); + virtual void Tock(); + + // DRE (Dynamic Resource Exchange) methods + virtual std::set::Ptr> GetMatlRequests(); + virtual std::set::Ptr> GetMatlBids( + CommodMap::type& commod_requests); + virtual void AdjustMatlPrefs(PrefMap::type& prefs); + virtual void AcceptMatlTrades( + const std::vector, Material::Ptr>>& responses); + virtual void GetMatlTrades( + const std::vector>& trades, + std::vector, Material::Ptr>>& responses); + + private: + // Position tracking (adds latitude/longitude support) + #include "toolkit/position.cycpp.h" + + // State variables (with Cyclus annotations) + #pragma cyclus var { \ + "doc": "Input commodity (e.g., U3O8)", \ + "uitype": "incommodity" \ + } + std::string feed_commod; + + #pragma cyclus var { \ + "doc": "Output commodity (e.g., UF6)", \ + "uitype": "outcommodity" \ + } + std::string product_commod; + + #pragma cyclus var { \ + "doc": "Conversion capacity per timestep (kg)", \ + "units": "kg" \ + } + double capacity; + + // Inventories for input and output materials + cyclus::toolkit::ResBuf feed_inventory; + cyclus::toolkit::ResBuf product_inventory; + + // Example operational metric + double operating_power; + }; + +3. End of Include Guard +~~~~~~~~~~~~~~~~~~~~~~~ +Always close your include guard at the end of the file. + +.. code-block:: c++ + + #endif // CONVERSION_FACILITY_H_ + +Implementation File (.cc): Step-by-Step ++++++++++++++++++++++++++++++++++++++++ + +Now, let's implement the logic in the .cc file. We'll break it down and explain +each part. + +1. Includes +~~~~~~~~~~~ +You need to include your header file so the compiler knows about your class and +its members. + +.. code-block:: c++ + + // conversion_facility.cc + #include "conversion_facility.h" + +2. Constructor and Destructor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The constructor initializes the base class. The destructor cleans up if needed. + +.. code-block:: c++ + + // Constructor + ConversionFacility::ConversionFacility(cyclus::Context* ctx) + : cyclus::Facility(ctx) {} + + // Destructor + ConversionFacility::~ConversionFacility() {} + +3. EnterNotify: Position Initialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is called when the agent enters the simulation. Here, we initialize +position tracking. + +.. code-block:: c++ + + void ConversionFacility::EnterNotify() { + InitializePosition(); + } + +4. Material Request Logic (GetMatlRequests) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function tells Cyclus what feed material (U3O8) your facility wants to +request. It checks how much is needed, creates a request, and adds a capacity +constraint. + +.. code-block:: c++ + + std::set::Ptr> + ConversionFacility::GetMatlRequests() { + + // Create a set of request portfolios + std::set::Ptr> ports; + + // Check if we need more feed material + double needed = std::max(0.0, capacity - feed_inventory.quantity()); + + // If we don't need more, return empty set + if (needed <= 0) return ports; + + // Create a request portfolio + RequestPortfolio::Ptr port(new RequestPortfolio()); + + // Create a dummy material request + Material::Ptr dummy = Material::CreateUntracked( + needed, context()->GetRecipe(feed_commod)); + + // Add the request to the portfolio + port->AddRequest(dummy, this, feed_commod, 1.0); + + // Add a capacity constraint + CapacityConstraint cc(needed); + port->AddConstraint(cc); + + // Add the portfolio to the set + ports.insert(port); + + // Return the set of request portfolios + return ports; + } + +5. Material Bid Logic (GetMatlBids) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function tells Cyclus what product material (UF6) your facility can offer. +It checks available inventory, creates bids for requests, and adds a capacity +constraint. + +.. code-block:: c++ + + std::set::Ptr> + ConversionFacility::GetMatlBids( + CommodMap::type& commod_requests) { + + // Create a set of bid portfolios + std::set::Ptr> ports; + + // If we don't have any product inventory, return empty set + if (product_inventory.quantity() <= 0) return ports; + + // Create a bid portfolio + BidPortfolio::Ptr port(new BidPortfolio()); + std::vector*>& requests = + commod_requests[product_commod]; + + // Iterate over the requests + for (std::vector*>::iterator it = requests.begin(); + it != requests.end(); ++it) { + + // Check if we have enough product inventory + double available = product_inventory.quantity(); + double requested = (*it)->quantity(); + double offer_qty = std::min(available, requested); + + // If we have enough product inventory, create a bid + if (offer_qty > 0) { + Material::Ptr offer = product_inventory.Pop(offer_qty); + port->AddBid(**it, offer, this); + } + } + + // Add a capacity constraint + CapacityConstraint cc(product_inventory.quantity()); + port->AddConstraint(cc); + + // Add the portfolio to the set + ports.insert(port); + return ports; + } + +6. Preference Adjustment (AdjustMatlPrefs) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This function allows you to adjust trade preferences based on material +characteristics. Here, we prefer material with more uranium and penalize +material from reactors. + +.. code-block:: c++ + + void ConversionFacility::AdjustMatlPrefs( + PrefMap::type& prefs) { + + // Iterate over the preferences + for (auto& p : prefs) { + + // Iterate over the materials + for (auto& m : p.second) { + + // Get the bid + Bid* bid = m.first; + + // Get the offer + Material::Ptr offer = bid->offer(); + + // Get the material query + cyclus::toolkit::MatQuery mq(offer); + double u_content = mq.mass(922350000) + mq.mass(922380000); + + // Prefer more uranium, note that the second element of m is the pref + m.second += u_content * 10; + + // Penalize reactor-origin material + if (dynamic_cast(bid->bidder()) != nullptr) { + m.second -= 50; + } + } + } + } + +7. Trade Execution (AcceptMatlTrades, GetMatlTrades) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These functions update your facility's inventories when trades are accepted or +fulfilled. + +.. code-block:: c++ + + void ConversionFacility::AcceptMatlTrades( + const std::vector, Material::Ptr>>& responses) { + + // Iterate over the responses + for (const auto& r : responses) { + + // If the request is for feed material, push the offer to the feed inventory + if (r.first.request->commodity() == feed_commod) { + feed_inventory.Push(r.second); + + // If the request is for product material, push the offer to the product inventory + } else if (r.first.request->commodity() == product_commod) { + product_inventory.Push(r.second); + } + } + } + + void ConversionFacility::GetMatlTrades( + const std::vector>& trades, + std::vector, Material::Ptr>>& responses) { + + // Iterate over the trades + for (const auto& t : trades) { + + // Pop the offer from the product inventory, the response is the offer + // given to the requesting agent. + double qty = t.amt; + Material::Ptr response = product_inventory.Pop(qty); + responses.push_back(std::make_pair(t, response)); + } + } + +8. Data Recording and Inventory Management (Tick, Tock) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These methods record operational metrics and time series data for analysis and +visualization. + +.. code-block:: c++ + + void ConversionFacility::Tick() { + + // Convert feed material into product material + double conversion_fraction = 0.96; + double to_convert_qty = std::min(capacity, feed_inventory.quantity()); + Material::Ptr to_convert = feed_inventory.Pop(to_convert_qty); + + double converted_material_qty = to_convert_qty * conversion_fraction; + Material::Ptr converted_material = Material::Create(this, + converted_material_qty, + to_convert->comp()); + product_inventory.Push(converted_material); + + // Record the conversion metrics + context()->NewDatum("ConversionMetrics") + ->AddVal("AgentId", id()) + ->AddVal("Time", context()->time()) + ->AddVal("FeedInventory", feed_inventory.quantity()) + ->AddVal("ProductInventory", product_inventory.quantity()) + ->AddVal("OperatingPower", operating_power) + ->Record(); + } + + void ConversionFacility::Tock() { + cyclus::toolkit::RecordTimeSeries( + this, operating_power); + cyclus::toolkit::RecordTimeSeries( + "feed_inventory", this, feed_inventory.quantity()); + cyclus::toolkit::RecordTimeSeries( + "product_inventory", this, product_inventory.quantity()); + } + +Why Testing Matters +------------------- + +Testing is a critical part of developing any Cyclus agent. Good tests help you: + +* Catch bugs early +* Ensure your agent behaves as expected +* Make future changes with confidence + +Cyclus uses the Google Test framework (gtest) for C++ unit tests. Below is a +simple example of how you might set up tests for your Conversion Facility. + +Example Test Files +------------------ + +Header File: conversion_tests.h ++++++++++++++++++++++++++++++++ + +This file declares a test fixture class for your agent. The fixture sets up and +tears down a ConversionFacility for each test. + +.. code-block:: c++ + + // conversion_tests.h + #ifndef CONVERSION_TESTS_H_ + #define CONVERSION_TESTS_H_ + + #include "gtest/gtest.h" + #include "conversion_facility.h" + + class ConversionFacilityTest : public ::testing::Test { + protected: + virtual void SetUp(); + virtual void TearDown(); + ConversionFacility* facility; + }; + + #endif // CONVERSION_TESTS_H_ + +Implementation File: conversion_tests.cc ++++++++++++++++++++++++++++++++++++++++ + +This file implements the test fixture and some basic tests. You can add more +tests as you develop your agent. + +.. code-block:: c++ + + // conversion_tests.cc + #include "conversion_tests.h" + + void ConversionFacilityTest::SetUp() { + cyclus::Context* ctx = nullptr; // In real tests, use a mock or real context + facility = new ConversionFacility(ctx); + } + + void ConversionFacilityTest::TearDown() { + delete facility; + } + + TEST_F(ConversionFacilityTest, Construction) { + ASSERT_NE(facility, nullptr); + } + + TEST_F(ConversionFacilityTest, InitialInventory) { + EXPECT_DOUBLE_EQ(facility->feed_inventory.quantity(), 0.0); + EXPECT_DOUBLE_EQ(facility->product_inventory.quantity(), 0.0); + } + +Conclusion +---------- + +Congratulations! You have now created and tested your first Cyclus agent—a +Conversion Facility. This agent can be extended with additional features such +as dynamic conversion rates, maintenance schedules, or more detailed material +tracking. Try integrating your new facility into a Cyclus simulation and +explore its behavior in the fuel cycle! \ No newline at end of file diff --git a/source/arche/tutorial_cpp/creating_random_event_inst.rst b/source/arche/tutorial_cpp/creating_random_event_inst.rst new file mode 100644 index 00000000..eae0bc8a --- /dev/null +++ b/source/arche/tutorial_cpp/creating_random_event_inst.rst @@ -0,0 +1,287 @@ +Creating RandomEventInst +======================= + +In this tutorial, you will learn how to build a Cyclus Institution Agent that +models real-world policy fluctuations and unpredictable events: the +RandomEventInst. This agent will occasionally deploy or retire facilities at +random intervals, simulating things like sudden policy changes, regional +demands, or unexpected retirements. This guide is designed for beginners— +especially new Cyclus users—who are just starting with Cyclus and C++ +agent development. We will walk through every step, clearly indicating what +code goes in the header (.h) file and what goes in the implementation (.cc) +file. We will also introduce the basics of testing your agent. + + +Introduction +------------ + +Institutions in Cyclus manage groups of facilities and can control when new +facilities are built or old ones are retired. The RandomEventInst will show how +you can use randomness to model real-world unpredictability, such as: + +* Sudden policy changes that force a facility to retire early +* Regional signals that trigger new facility builds +* Random events that affect the fuel cycle + +This tutorial will guide you through creating a robust, extensible institution +agent ready for integration into complex fuel cycle simulations. + +File Structure: .h vs .cc +------------------------- + +In C++ projects, code is split into header files (.h) and implementation files +(.cc): + +* The **header file (.h)** declares the class, its member variables, and its + functions (methods). Think of it as the "blueprint" for your agent. +* The **implementation file (.cc)** contains the actual code for the functions +you declared in the header. This is where the logic lives. + +We will clearly indicate which code belongs in which file as we go. + +Header File (.h): Step-by-Step +++++++++++++++++++++++++++++++ + +Let's start by building the header file for your new institution. We'll break +it down into sections and explain what each part does. + +1. Include Guards and Includes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Include guards prevent the file from being included more than once, which can +cause errors. The includes bring in the necessary Cyclus and toolkit headers. + +.. code-block:: c++ + + // random_event_inst.h + #ifndef RANDOM_EVENT_INST_H_ + #define RANDOM_EVENT_INST_H_ + + #include "cyclus.h" + #include + #include + #include + +2. Class Declaration +~~~~~~~~~~~~~~~~~~~~ +This is where you declare your RandomEventInst class, which inherits from +cyclus::Institution. The class declaration lists all the functions and +variables your agent will have. + +.. code-block:: c++ + + class RandomEventInst : public cyclus::Institution { + public: + RandomEventInst(cyclus::Context* ctx); + virtual ~RandomEventInst(); + + // Cyclus lifecycle methods + virtual void Tick(); + virtual void Tock(); + virtual void EnterNotify(); + + // Helper methods for random events + void MaybeDeployFacility(); + void MaybeRetireFacility(); + + private: + // Random number generator state + std::mt19937 rng_; + std::uniform_real_distribution uniform_dist_; + + // Parameters for event probabilities + #pragma cyclus var { \ + "doc": "Probability of deploying a facility each timestep (0-1)", \ + "default": 0.1 \ + } + double deploy_prob_; + + #pragma cyclus var { \ + "doc": "Probability of retiring a facility each timestep (0-1)", \ + "default": 0.05 \ + } + double retire_prob_; + + #pragma cyclus var { \ + "doc": "Prototype name of facility to deploy", \ + "uitype": "prototype" \ + } + std::string facility_proto_; + + // Track built facilities for possible retirement + std::vector built_facilities_; + }; + + #endif // RANDOM_EVENT_INST_H_ + +Implementation File (.cc): Step-by-Step ++++++++++++++++++++++++++++++++++++++++ + +Now, let's implement the logic in the .cc file. We'll break it down and explain +each part. + +1. Includes +~~~~~~~~~~~ +You need to include your header file so the compiler knows about your class and +its members. + +.. code-block:: c++ + + // random_event_inst.cc + #include "random_event_inst.h" + #include + +2. Constructor and Destructor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The constructor initializes the base class and sets up the random number +generator. The destructor cleans up if needed. + +.. code-block:: c++ + + RandomEventInst::RandomEventInst(cyclus::Context* ctx) + : cyclus::Institution(ctx), + rng_(std::random_device{}()), + uniform_dist_(0.0, 1.0) {} + + RandomEventInst::~RandomEventInst() {} + +3. EnterNotify: Initialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is called when the agent enters the simulation. Here, you can +initialize any state or register for services. + +.. code-block:: c++ + + void RandomEventInst::EnterNotify() { + Institution::EnterNotify(); + // Optionally, initialize or log here + } + +4. Tick and Tock: Random Event Logic +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The Tick and Tock methods are called every timestep. We'll use Tick to maybe +deploy a facility, and Tock to maybe retire one. + +.. code-block:: c++ + + void RandomEventInst::Tick() { + MaybeDeployFacility(); + } + + void RandomEventInst::Tock() { + MaybeRetireFacility(); + } + +5. MaybeDeployFacility: Random Deployment +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This helper method uses the random number generator to decide whether to deploy +a new facility this timestep. + +.. code-block:: c++ + + void RandomEventInst::MaybeDeployFacility() { + if (uniform_dist_(rng_) < deploy_prob_) { + cyclus::Agent* fac = context()->CreateAgent(facility_proto_); + context()->SchedBuild(fac, this); + built_facilities_.push_back(fac); + // Optionally, log or record the event + } + } + +6. MaybeRetireFacility: Random Retirement +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This helper method randomly selects one of the built facilities and retires it, +if the random event occurs. + +.. code-block:: c++ + + void RandomEventInst::MaybeRetireFacility() { + if (built_facilities_.empty()) return; + if (uniform_dist_(rng_) < retire_prob_) { + // Pick a random facility to retire + size_t idx = static_cast(uniform_dist_(rng_) * built_facilities_.size()); + idx = std::min(idx, built_facilities_.size() - 1); + cyclus::Agent* fac = built_facilities_[idx]; + context()->SchedDecom(fac); + built_facilities_.erase(built_facilities_.begin() + idx); + // Optionally, log or record the event + } + } + +Why Testing Matters +------------------- + +Testing is a critical part of developing any Cyclus agent. Good tests help you: + +* Catch bugs early +* Ensure your agent behaves as expected +* Make future changes with confidence + +Cyclus uses the Google Test framework (gtest) for C++ unit tests. Below is a +simple example of how you might set up tests for your RandomEventInst. + +Example Test Files +------------------ + +Header File: random_event_inst_tests.h +++++++++++++++++++++++++++++++++++++++ + +This file declares a test fixture class for your agent. The fixture sets up and +tears down a RandomEventInst for each test. + +.. code-block:: c++ + + // random_event_inst_tests.h + #ifndef RANDOM_EVENT_INST_TESTS_H_ + #define RANDOM_EVENT_INST_TESTS_H_ + + #include "gtest/gtest.h" + #include "random_event_inst.h" + + class RandomEventInstTest : public ::testing::Test { + protected: + virtual void SetUp(); + virtual void TearDown(); + RandomEventInst* inst; + }; + + #endif // RANDOM_EVENT_INST_TESTS_H_ + +Implementation File: random_event_inst_tests.cc +++++++++++++++++++++++++++++++++++++++++++++++ + +This file implements the test fixture and some basic tests. You can add more +tests as you develop your agent. + +.. code-block:: c++ + + // random_event_inst_tests.cc + #include "random_event_inst_tests.h" + + void RandomEventInstTest::SetUp() { + cyclus::Context* ctx = nullptr; // In real tests, use a mock or real context + inst = new RandomEventInst(ctx); + } + + void RandomEventInstTest::TearDown() { + delete inst; + } + + TEST_F(RandomEventInstTest, Construction) { + ASSERT_NE(inst, nullptr); + } + + TEST_F(RandomEventInstTest, InitialProbabilities) { + EXPECT_GE(inst->deploy_prob_, 0.0); + EXPECT_LE(inst->deploy_prob_, 1.0); + EXPECT_GE(inst->retire_prob_, 0.0); + EXPECT_LE(inst->retire_prob_, 1.0); + } + +Conclusion +---------- + +Congratulations! You have now created and tested a Cyclus institution agent +that models random real-world events. This agent can be extended with +additional features such as more complex event logic, logging, or integration +with regional signals. Try integrating your new institution into a Cyclus +simulation and explore its behavior in the fuel cycle! \ No newline at end of file diff --git a/source/arche/tutorial_cpp/creating_tariff_region.rst b/source/arche/tutorial_cpp/creating_tariff_region.rst new file mode 100644 index 00000000..d25fa1e3 --- /dev/null +++ b/source/arche/tutorial_cpp/creating_tariff_region.rst @@ -0,0 +1,320 @@ +Creating TariffRegion +==================== + +In this tutorial, you will learn how to build a Cyclus Region Agent that +models economic tariffs: the TariffRegion. This agent will use preference +adjustment to apply "tariffs" (penalties) to trades coming from facilities in +other regions, simulating real-world trade barriers. This guide is designed for +beginners—especially new Cyclus users—who are just starting with Cyclus +and C++ agent development. We will walk through every step, clearly indicating +what code goes in the header (.h) file and what goes in the implementation +(.cc) file. We will also introduce the basics of testing your agent. + + +Introduction +------------ + +Regions in Cyclus represent geographic or political groupings of institutions +and facilities. They can influence trade by adjusting preferences, which affect +how the Dynamic Resource Exchange (DRE) matches requests and offers. The +TariffRegion will show how you can use preference adjustment to penalize trades +from outside your region, simulating tariffs or trade barriers. + +This tutorial will guide you through creating a robust, extensible region agent +ready for integration into complex fuel cycle simulations. + +File Structure: .h vs .cc +------------------------- + +In C++ projects, code is split into header files (.h) and implementation files +(.cc): + +* The **header file (.h)** declares the class, its member variables, and its + functions (methods). Think of it as the "blueprint" for your agent. +* The **implementation file (.cc)** contains the actual code for the functions +you declared in the header. This is where the logic lives. + +We will clearly indicate which code belongs in which file as we go. + +Header File (.h): Step-by-Step +++++++++++++++++++++++++++++++ + +Let's start by building the header file for your new region. We'll break it +down into sections and explain what each part does. + +1. Include Guards and Includes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Include guards prevent the file from being included more than once, which can +cause errors. The includes bring in the necessary Cyclus and toolkit headers. + +.. code-block:: c++ + + // tariff_region.h + #ifndef TARIFF_REGION_H_ + #define TARIFF_REGION_H_ + + #include "cyclus.h" + #include + +2. Class Declaration +~~~~~~~~~~~~~~~~~~~~ +This is where you declare your TariffRegion class, which inherits from +cyclus::Region. The class declaration lists all the functions and variables +your agent will have. + +.. code-block:: c++ + + class TariffRegion : public cyclus::Region { + public: + TariffRegion(cyclus::Context* ctx); + virtual ~TariffRegion(); + + // Cyclus lifecycle methods + virtual void EnterNotify(); + virtual void Tick(); + virtual void Tock(); + + // Preference adjustment for tariffs + virtual void AdjustMatlPrefs(cyclus::PrefMap::type& prefs); + virtual void AdjustProductPrefs(cyclus::PrefMap::type& prefs); + + private: + // Tariff penalty to apply to trades from other regions + #pragma cyclus var { \ + "doc": "Tariff penalty to apply to trades from other regions", \ + "default": 50.0 \ + } + double tariff_penalty_; + }; + + #endif // TARIFF_REGION_H_ + +Implementation File (.cc): Step-by-Step ++++++++++++++++++++++++++++++++++++++++ + +Now, let's implement the logic in the .cc file. We'll break it down and explain +each part. + +1. Includes +~~~~~~~~~~~ +You need to include your header file so the compiler knows about your class and +its members. + +.. code-block:: c++ + + // tariff_region.cc + #include "tariff_region.h" + +2. Constructor and Destructor +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The constructor initializes the base class. The destructor cleans up if needed. + +.. code-block:: c++ + + // Constructor + TariffRegion::TariffRegion(cyclus::Context* ctx) + : cyclus::Region(ctx) {} + + // Destructor + TariffRegion::~TariffRegion() {} + +3. EnterNotify: Initialization +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This method is called when the agent enters the simulation. In `EnterNotify()`, +you can perform any setup that needs to happen after the region is fully +constructed and added to the simulation. This might include: + +- **Initializing state:** Setting up variables or data structures that depend on +the simulation context, or that require information about other agents (such as +institutions or facilities) that are now available. +- **Registering for services:** If your region needs to listen for simulation +events (like time steps, builds, or decommissions), you can register as a +listener here. For example, you might call `context()->RegisterTimeListener(this);` +if you want your region to receive `Tick()` and `Tock()` calls. Note that +typically this is not done. +- **Logging or diagnostics:** You can print messages or record information for +debugging or analysis. +- **Interacting with other agents:** If your region needs to communicate with its +institutions or facilities, or set up relationships, this is a good place to do +it, since all agents are now present in the simulation. + +Example: + +.. code-block:: c++ + + void TariffRegion::EnterNotify() { + Region::EnterNotify(); + // Register for time step notifications if needed + // context()->RegisterTimeListener(this); + + // Initialize or reset any state variables here + + // Optionally, log that the region has entered the simulation + // LOG(cyclus::LEV_INFO3, "TariffRegion") << "TariffRegion has entered the simulation."; + } + +4. Tick and Tock: (Optional) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +The Tick and Tock methods are called every timestep. For this simple region, +you will not need to do anything, but you can use these methods for logging or +future extensions. If you are following this tutorial, you will not need to +implement these methods. However, more advanced users may want to use these +for logging or future extensions. + +.. code-block:: c++ + + void TariffRegion::Tick() { + // No-op for now + } + + void TariffRegion::Tock() { + // No-op for now + } + +5. Preference Adjustment: Applying Tariffs +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +This is the key feature! These methods adjust trade preferences to penalize +trades from facilities in other regions. We'll show the material version, but +the product version is analogous. + +To determine the region of a supplier, you need to traverse the agent hierarchy +using `parent()` and `dynamic_cast`. For a facility, `parent()` returns its +institution, and `parent()->parent()` returns its region. For an institution, +`parent()` returns its region. There is no `region()` method on Agent. + +.. code-block:: c++ + + void TariffRegion::AdjustMatlPrefs(cyclus::PrefMap::type& prefs) { + for (auto& req_pair : prefs) { + + // Iterate over the bids in the request portfolio + for (auto& bid_pair : req_pair.second) { + + // Get the bid + cyclus::Bid* bid = bid_pair.first; + + // Get the supplier + cyclus::Agent* supplier = bid->bidder(); + + // Traverse up the hierarchy to get the supplier's region + cyclus::Region* supplier_region = nullptr; + if (supplier != nullptr && supplier->parent() != nullptr) { + cyclus::Agent* inst = supplier->parent(); + if (inst->parent() != nullptr) { + supplier_region = dynamic_cast(inst->parent()); + } + } + + // If the supplier is from a different region, apply the tariff penalty + if (supplier_region != this) { + bid_pair.second -= tariff_penalty_; + } + } + } + } + + void TariffRegion::AdjustProductPrefs(cyclus::PrefMap::type& prefs) { + + // Iterate over the preferences + for (auto& req_pair : prefs) { + // Iterate over the bids in the request portfolio + for (auto& bid_pair : req_pair.second) { + + // Get the bid + cyclus::Bid* bid = bid_pair.first; + + // Get the supplier + cyclus::Agent* supplier = bid->bidder(); + + // Traverse up the hierarchy to get the supplier's region + cyclus::Region* supplier_region = nullptr; + if (supplier != nullptr && supplier->parent() != nullptr) { + cyclus::Agent* inst = supplier->parent(); + if (inst->parent() != nullptr) { + supplier_region = dynamic_cast(inst->parent()); + } + } + + // If the supplier is from a different region, apply the tariff penalty + if (supplier_region != this) { + bid_pair.second -= tariff_penalty_; + } + } + } + } + +Why Testing Matters +------------------- + +Testing is a critical part of developing any Cyclus agent. Good tests help you: + +* Catch bugs early +* Ensure your agent behaves as expected +* Make future changes with confidence + +Cyclus uses the Google Test framework (gtest) for C++ unit tests. Below is a +simple example of how you might set up tests for your TariffRegion. + +Example Test Files +------------------ + +Header File: tariff_region_tests.h +++++++++++++++++++++++++++++++++++ + +This file declares a test fixture class for your agent. The fixture sets up and +tears down a TariffRegion for each test. + +.. code-block:: c++ + + // tariff_region_tests.h + #ifndef TARIFF_REGION_TESTS_H_ + #define TARIFF_REGION_TESTS_H_ + + #include "gtest/gtest.h" + #include "tariff_region.h" + + class TariffRegionTest : public ::testing::Test { + protected: + virtual void SetUp(); + virtual void TearDown(); + TariffRegion* region; + }; + + #endif // TARIFF_REGION_TESTS_H_ + +Implementation File: tariff_region_tests.cc +++++++++++++++++++++++++++++++++++++++++++ + +This file implements the test fixture and some basic tests. You can add more +tests as you develop your agent. + +.. code-block:: c++ + + // tariff_region_tests.cc + #include "tariff_region_tests.h" + + void TariffRegionTest::SetUp() { + cyclus::Context* ctx = nullptr; // In real tests, use a mock or real context + region = new TariffRegion(ctx); + } + + void TariffRegionTest::TearDown() { + delete region; + } + + TEST_F(TariffRegionTest, Construction) { + ASSERT_NE(region, nullptr); + } + + TEST_F(TariffRegionTest, TariffPenaltyDefault) { + EXPECT_DOUBLE_EQ(region->tariff_penalty_, 50.0); + } + +Conclusion +---------- + +Congratulations! You have now created and tested a Cyclus region agent that +models tariffs and trade barriers. This agent can be extended with additional +features such as dynamic tariffs, logging, or integration with policy signals. +Try integrating your new region into a Cyclus simulation and explore its +behavior in the fuel cycle! \ No newline at end of file diff --git a/source/arche/tutorial_cpp/index.rst b/source/arche/tutorial_cpp/index.rst index e73a5b31..2447f553 100644 --- a/source/arche/tutorial_cpp/index.rst +++ b/source/arche/tutorial_cpp/index.rst @@ -29,6 +29,9 @@ This tutorial has the following steps: input_files dre_overview advanced_agent + creating_conversion + creating_random_event_inst + creating_tariff_region .. Given enough time, the following extra topics may be covered: