Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 128 additions & 0 deletions source/cep/cep31.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
CEP 31 - Agent Registered Discrete Event Timing
***********************************************************

:CEP: 31
:Title: Agent Registered Discrete Event Timing
:Last-Modified: 2026-02-20
:Author: Meghan Krieg <kriegm@oregonstate.edu>
:BDFP: Madicken Munk/Paul Wilson
:Status: Draft
:Type: Standards Track
:Created: 2026-02-19
:Cyclus-Version: 1.6.0

Background
===========

|Cyclus| currently runs using a fixed increment time advancement mechanism, ``dt``.
Within each ``dt`` of the simulation, cyclus's cardinal function progresses through a distinct ordering
of 6 phases which include the

- Building Phase
- Tick Phase
- Exchange Phase
- Tock Phase
- Decision Phase
- Decomissioning Phase

before advancing to the next time step. One of the core paradigms of this implementation is
that all agents within the simulation must participate in the Dynamic Resource Exchange and
the surrounding time execution steps (Tick/Tock phases). The ordering of these phases allows
the maxiumum transition of resources with building phase completed first and decomissioning last.
In addition to this feature, the phase ordering toggles between two categories: agent phase nad kernel phase.
While the kernel phase represents events that alter the simulation state, agent phases include
actions that update an agent's internal state. For more information, refer to `CEP 20<https://fuelcycle.org/cep/cep20.html>`_.

Where `CEP 20<https://fuelcycle.org/cep/cep20.html>`_ defined cyclus as a broadly discrete event simulation, CEP 31 will push the implementation
further.

Motivation and Rationale
==========================

Simulations with event based or discrete event timing run on an internal clock rather than a fixed simulation
clock (as featured in cyclus). For this internal clock, events that alter the simulation state are registered at distinct timestamps
within the simulation duration. The simulation progresses from scheduled event to scheduled event skipping timestamps
where no actions are registered. For example, an agent may schedule a build-event at timestamp 5, a trade event at timestamp 7, and a decomission event at timestamp 10.
Then, the timeline for a 10 month simulation will proceed as follows

START 3 (build) -- 5 (trade) -- 7 (decom) END

as opposed to the current implementation

START 0 -- 1 -- 2 -- 3 (build) -- 4 -- 5 (trade) -- 6 -- 7 (decom) -- 8 -- 9 -- 10 END

In cyclus, discrete even timing can be implemented by allowing agents to internally check their inventory
and status to register themselves for DRE participation, Build, or Decomission events. In instances
when no agents reigster actions, there will be no events, and the simulation will skip that timestmap. Agents will
dicate the dynamics of the simulation instead of a fixed timestep forcing interactions.

With many timesteps skipped between agent's cycle/event registrations, computational time will be lower.

Conceptualization
===============================

Based on the current structure set up by the kernal and agent steps in the phase suit, the following
discrete event implementation is suggested...

Events in the simulation's internal clock can be requested by agents using their own ``EventRequest()`` function.
Events may only be registered when agents wish to complete the follwing actions:

- Build
- Request Materials
- Decomission

These actions map to the current *kernel* phases of cyclus's cardinal function. Thus, by extent,

- Tick
- Tock
- Decision

may not be considered events because they do *not* change the simulation state. They change
the agent's internal state (agent phase). As these actions are not events, they may never be independently registered by agents.
(There are scenarios in which build/requets/decomission may trigger them, however.)
Comment on lines +74 to +82
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like a key point of discussion/contention. Can you say more about why you want this strict interpretation? If the context will be skipping time steps based on the registered events, how will agents know when it is time to tick or tock? How will they be triggered? What if a tick is necessary to determine whether or not to participate in a trade?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are scenarios in which i can see the Tock and Decision being registrable events based on the outcome of the DRE.

It is mainly the tick that cannot be registrable (in my opinion)! If all traders objects are available bidders every trade event and all bidders need accurate bid information and we don't know who will bid, then all bidders must all tick. In other words, all traders must tick either every scheduled event or every trade event.

I could be really off the mark though.


For the DRE, agents will *only* be able to register for material requests; thus, material demands instead of material
supply will drive the DRE scheduling. When agents go to register an event by requesting a material, they are unaware of the state of other agent's
supplies. So, for the subset of agents that are triggering DRE events through material requests, *all available
agents in the simulation must be accessible to the DRE to request bids and responses*. This ensures that material request events aren't prohibited from being satisfied
even when requested material exists.

Because of the required attendance for all agents in the material bids stage of the DRE, the agent phases Tick/Tock/Decision must be triggered
for all available agents as well. These internal Tick/Tock/Decision updates are crucial for deriving accurate *bid* information for request portfolios. There are further
filtering options that consider deregistering an agent's Tocks/Decisions when they make no material bids.

Implementation
===============================

Material requests, Build, and Decomissioin event registration will be handled by individual agents. Cyclus already creates a preconditioned timeline
for discrete-build and decomission events. An additional ``EventRequest()`` member funcition for all cycamore archetypes will check a facility's inventory.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If such a function is necessary, it should perhaps be added to the agent class from which these all derive.


1. If inventory not at capacity, agent will register for the (+1) next immediate time step to attempt another request.
2. If inventory at capacity, agent will register its next request event for a fixed ``+ cycle_length`` time from the current event.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is too rooted in the current set of archetypes. We need to think beyond the concept of cycle length and reactors.


These material request events will be registered within Context in a dynamic dictionary that contains the event's timestamp and a list of ``Trader`` objects. To ensure that all
facility agents' ``EventRequest()`` functions are checked regularly, A look-ahead function will be added to cyclus's cardinal phase suite after the decomissioning phase.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this degenerate to time steps again? I don't know what the purpose of a look-ahead function is and when/how it gets triggered?

Copy link
Contributor Author

@meg-krieg meg-krieg Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pasted some general code below in case my words are really vague! Maybe this makes what I am thinking a little clearer? It is pretty bare-bones but just for a general idea -- in timer.cc

void Timer::RunSim() {
...
ctx_->Populate(0); // at time 0 everyone must register for a trade event to get started
 while ( (time_ < si_.duration) && (prev_time_ != time_)) {

   CLOG(LEV_INFO1) << "Current time: " << time_;
   if (want_snapshot_) {
     want_snapshot_ = false;
     SimInit::Snapshot(ctx_);
   }
   // run through phases
   DoBuild();
   CLOG(LEV_INFO2) << "Beginning Tick for time: " << time_;
   DoTick();
   CLOG(LEV_INFO2) << "Beginning DRE for time: " << time_;
   DoResEx(&matl_manager, &genrsrc_manager);
   CLOG(LEV_INFO2) << "Beginning Tock for time: " << time_;
   DoTock();
   CLOG(LEV_INFO2) << "Beginning Decision for time: " << time_;
   DoDecision();
   DoDecom();
   DoLookAhead();

#ifdef CYCLUS_WITH_PYTHON
   EventLoop();
#endif
   prev_time_ = time_;
   time_ = NextEvent();
...
}

Then, later in timer.cc, something similar to this will happen where timer checks for the next closest event registered in the timeline. (this is not meant to be accurate code).

void Timer::DoLookAhead() {
  std::set<Trader*> all_traders = ctx_->traders();
  for(Trader* m : all_traders){
    m->EventRequest(); 
  };
// another check will probably go here
}

int Timer::NextEvent(){
  auto reg_traders = ctx_->EventRequesters(); //the list of traders who have registered for events in the timeline
  int t_p = time_ +1; // time plus +1 
  std::vector<int> event_lists = {decom_queue_.upper_bound(t_p)->first,build_queue_.upper_bound(t_p)->first}, reg_traders.upper_bound(t_p)->first};
  return *std::min_element(event_lists.begin(), event_lists.end());
}

So, the simulation finishes some event and it looks for the next closest timestamp that has an event. That timestamp could have events registered under any 3, 2, or 1 of build/trade/decom actions scheduled. The simulation knows it has the event at the timestamp but does not know which type (this could change maybe), so it checks each phase for that event. If the phase is filled with participants, that phase is one of the events and is triggered.

This means that the phases will have some conditional check... for example

void Timer::DoBuild() {
  if(build_queue_.count(time_)==0){
         continue;}
else{
  std::vector<std::pair<std::string, Agent*>> build_list = build_queue_[time_];
  for (int i = 0; i < build_list.size(); ++i) {
    Agent* m = ctx_->CreateAgent<Agent>(build_list[i].first);
    Agent* parent = build_list[i].second;
...
   }
...
  }

Overall, this treatment should skip the timeline from event to event instead of timestep to next timestep when fully fleshed out.


.. code-block:: c++
DoBuild();
DoTick();
DoResEx(&matl_manager, &genrsrc_manager);
DoTock();
DoDecision();
DoDecom();
DoLookAhead();

The cardinal phase suite will maintain its current ordering for the reasons described in CEP 20. Each of the 6 phases will be checked during each event (as opposed to each time step) but the phase will
only be triggered and completed if it has participants registered. Only the newly introduced ``DoLookAhead()`` that checks each agents ``EventRequest()`` will be executed for all agents each event.

The simulation will be terminated fully when ``DoLookAhead()`` registers no new events and the simulation has completed the last event registered.

Backwards Compatibility
========================

These changes will not be backwards compatible with cyclus v1.6.0 and may require a new release. An optional simulation parameter could be introduced to switch this treatment on and off
such that the original time implemenation can be used.