diff --git a/source/arche/tutorial_cpp/advanced_agent.rst b/source/arche/tutorial_cpp/advanced_agent.rst new file mode 100644 index 00000000..fc16ded2 --- /dev/null +++ b/source/arche/tutorial_cpp/advanced_agent.rst @@ -0,0 +1,617 @@ +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 = Material::CreateUntracked(offer_qty, output.Peek()->comp()); + 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); + } + + } + } + + 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/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/custominst.rst b/source/arche/tutorial_cpp/custominst.rst new file mode 100644 index 00000000..7a14aa3d --- /dev/null +++ b/source/arche/tutorial_cpp/custominst.rst @@ -0,0 +1,175 @@ + +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_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 4f33f0fa..2447f553 100644 --- a/source/arche/tutorial_cpp/index.rst +++ b/source/arche/tutorial_cpp/index.rst @@ -27,6 +27,11 @@ This tutorial has the following steps: toolkit testing 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: