A Scheduler is used to generate the order in which the Components of a Composition are executed. By default, a Scheduler executes Components in an order determined by the pattern of Projections among the Mechanisms in the Composition, with each Mechanism executed once per PASS through the Composition. For example, in a System in which a Mechanism A projects to a Mechanism B that projects to a Mechanism C, A will execute first followed by B, and then C in each PASS through the System. However, a Scheduler can be used to implement more complex patterns of execution, by specifying Conditions that determine when and how many times individual Components execute, and whether and how this depends on the execution of other Components. Any executable Component in a Composition can be assigned a Condition, and Conditions can be combined in arbitrary ways to generate any pattern of execution of the Components in a Composition that is logically possible.


In general, Mechanisms are the Components of a Composition that are most commonly associated with Conditions, and assigned to a Scheduler for execution. However, in some circumstances, Projections can also be assigned for execution (e.g., during learning to insure that MappingProjections are updated in the proper order).

Creating a Scheduler

A Scheduler can be created explicitly using its constructor. However, more commonly it is created automatically for a Composition when it is created. When creating a Scheduler explicitly, the set of Components to be executed and their order must be specified in the Scheduler’s constructor using one the following:

  • a Composition in the composition argument - if a Composition is specified, the Scheduler is created using the nodes and edges in the Composition’s graph.
  • a graph specification dictionary in the graph argument - each entry of the dictionary must be a Component of a Composition, and the value of each entry must be a set of zero or more Components that project directly to the key. The graph must be acyclic; an error is generated if any cycles (e.g., recurrent dependencies) are detected. The Scheduler computes a toposort from the graph that is used as the default order of executions, subject to any Condition`s that have been specified (see `below).

If both a Composition and a graph are specified, the Composition takes precedence, and the graph is ignored.

Conditions can be added to a Scheduler when it is created by specifying a ConditionSet (a set of Conditions) in the conditions argument of its constructor. Individual Conditions and/or ConditionSets can also be added after the Scheduler has been created, using its add_condition and add_condition_set methods, respectively.


When a Scheduler is created, it constructs a consideration_queue: a list of consideration_sets that defines the order in which Components are eligible to be executed. This is based on the pattern of projections among them specified in the Composition, or on the dependencies specified in the graph specification dictionary, whichever was provided in the Scheduler’s constructor. Each consideration_set is a set of Components that are eligible to execute at the same time/TIME_STEP (i.e., that appear at the same “depth” in a sequence of dependencies, and among which there are no dependencies). The first consideration_set consists of only ORIGIN Mechanisms. The second consists of all Components that receive Projections from the Mechanisms in the first consideration_set. The third consists of Components that receive Projections from Components in the first two consideration_sets, and so forth. When the Scheduler is run, it uses the consideration_queue to determine which Components are eligible to execute in each TIME_STEP of a PASS, and then evaluates the Condition associated with each Component in the current consideration_set to determine which should actually be assigned for execution.


consideration_queue <- list(toposort(graph))

reset TimeScale.TRIAL counters
while TimeScale.TRIAL termination conditions are not satisfied:
    reset TimeScale.PASS counters
    cur_index <- 0

    while TimeScale.TRIAL termination conditions are not satisfied
          and cur_index < len(consideration_queue):

        cur_consideration_set <- consideration_queue[cur_index]
            cur_consideration_set_has_changed <- False
            for cur_node in cur_consideration_set:
                if  cur_node not in cur_time_step
                    and cur_node`s Condition is satisfied:

                    cur_consideration_set_has_changed <- True
                    add cur_node to cur_time_step
                    increment execution and time counters
        while cur_consideration_set_has_changed

        if cur_time_step is not empty:
            yield cur_time_step

        increment cur_index
        increment time counters


When a Scheduler is run, it provides a set of Components that should be run next, based on their dependencies in the Composition or graph specification dictionary, and any Conditions, specified in the Scheduler’s constructor. For each call to the run method, the Scheduler sequentially evaluates its consideration_sets in their order in the consideration_queue. For each set, it determines which Components in the set are allowed to execute, based on whether their associated Condition has been met. Any Component that does not have a Condition explicitly specified is assigned a Condition that causes it to be executed whenever it is under consideration and all its structural parents have been executed at least once since the Component’s last execution. All of the Components within a consideration_set that are allowed to execute comprise a TIME_STEP of execution. These Components are considered as executing simultaneously.


The ordering of the Components specified within a TIME_STEP is arbitrary (and is irrelevant, as there are no graph dependencies among Components within the same consideration_set). However, the execution of a Component within a time_step may trigger the execution of another Component within its consideration_set, as in the example below:

  ↗ ↖
 A   B

scheduler.add_condition(B, pnl.EveryNCalls(A, 2))
scheduler.add_condition(C, pnl.EveryNCalls(B, 1))

time steps: [{A}, {A, B}, {C}, ...]

Since there are no graph dependencies between A and B, they may execute in the same TIME_STEP. Morever, A and B are in the same consideration_set. Since B is specified to run every two times A runs, A’s second execution in the second TIME_STEP allows B to run within that TIME_STEP, rather than waiting for the next PASS.

For each TIME_STEP, the Scheduler evaluates whether any specified termination Conditions have been met, and terminates if so. Otherwise, it returns the set of Components that should be executed in the current TIME_STEP. Each subsequent call to the run method returns the set of Components in the following TIME_STEP.

Processing of all of the consideration_sets in the consideration_queue constitutes a PASS of execution, over which every Component in the Composition has been considered for execution. Subsequent calls to the run method cycle back through the consideration_queue, evaluating the consideration_sets in the same order as previously. Different subsets of Components within the same consideration_set may be assigned to execute on each PASS, since different Conditions may be satisfied.

The Scheduler continues to make PASS`es through the `consideration_queue until a termination Condition is satisfied. If no termination Conditions are specified, the Scheduler terminates a TRIAL when every Component has been specified for execution at least once (corresponding to the AllHaveRun Condition). However, other termination Conditions can be specified, that may cause the Scheduler to terminate a TRIAL earlier or later (e.g., when the Condition for a particular Component or set of Components is met). When the Scheduler terminates a TRIAL, the Composition begins processing the next input specified in the call to its run method. Thus, a TRIAL is defined as the scope of processing associated with a given input to the Composition.

Termination Conditions

Termination conditions are Conditions that specify when the open-ended units of time - TRIAL and RUN - have ended. By default, the termination condition for a TRIAL is AllHaveRun, which is satisfied when all Components have run at least once within the trial, and the termination condition for a RUN is when all of its constituent trials have terminated. These defaults may be overriden when running a Composition, by passing a dictionary mapping TimeScales to Conditions in the termination_processing argument of a call to (to terminate the execution of processing):
    termination_processing={TimeScale.TRIAL: WhenFinished(ddm)}


Please see Condition for a list of all supported Conditions and their behavior.

  • Basic phasing in a linear process:

    >>> import psyneulink as pnl
    >>> A = pnl.TransferMechanism(name='A')
    >>> B = pnl.TransferMechanism(name='B')
    >>> C = pnl.TransferMechanism(name='C')
    >>> comp = pnl.Composition()
    >>> comp.add_linear_processing_pathway([A, B, C])
    [(TransferMechanism A), (MappingProjection MappingProjection from A[RESULTS] to B[InputState-0]), (TransferMechanism B), (MappingProjection MappingProjection from B[RESULTS] to C[InputState-0]), (TransferMechanism C)]
    >>> # implicit condition of Always for A
    >>> comp.scheduler.add_condition(B, pnl.EveryNCalls(A, 2))
    >>> comp.scheduler.add_condition(C, pnl.EveryNCalls(B, 3))
    >>> # implicit AllHaveRun Termination condition
    >>> execution_sequence = list(
    >>> execution_sequence
    [{(TransferMechanism A)}, {(TransferMechanism A)}, {(TransferMechanism B)}, {(TransferMechanism A)}, {(TransferMechanism A)}, {(TransferMechanism B)}, {(TransferMechanism A)}, {(TransferMechanism A)}, {(TransferMechanism B)}, {(TransferMechanism C)}]
  • Alternate basic phasing in a linear process:

    >>> comp = pnl.Composition()
    >>> comp.add_linear_processing_pathway([A, B])
    [(TransferMechanism A), (MappingProjection MappingProjection from A[RESULTS] to B[InputState-0]), (TransferMechanism B)]
    >>> comp.scheduler.add_condition(
    ...     A,
    ...     pnl.Any(
    ...         pnl.AtPass(0),
    ...         pnl.EveryNCalls(B, 2)
    ...     )
    ... )
    >>> comp.scheduler.add_condition(
    ...     B,
    ...     pnl.Any(
    ...         pnl.EveryNCalls(A, 1),
    ...         pnl.EveryNCalls(B, 1)
    ...     )
    ... )
    >>> termination_conds = {
    ...     pnl.TimeScale.TRIAL: pnl.AfterNCalls(B, 4, time_scale=pnl.TimeScale.TRIAL)
    ... }
    >>> execution_sequence = list(
    >>> execution_sequence 
    [{(TransferMechanism A)}, {(TransferMechanism B)}, {(TransferMechanism B)}, {(TransferMechanism A)}, {(TransferMechanism B)}, {(TransferMechanism B)}]
  • Basic phasing in two processes:

    >>> comp = pnl.Composition()
    >>> comp.add_linear_processing_pathway([A, C])
    [(TransferMechanism A), (MappingProjection MappingProjection from A[RESULTS] to C[InputState-0]), (TransferMechanism C)]
    >>> comp.add_linear_processing_pathway([B, C])
    [(TransferMechanism B), (MappingProjection MappingProjection from B[RESULTS] to C[InputState-0]), (TransferMechanism C)]
    >>> comp.scheduler.add_condition(A, pnl.EveryNPasses(1))
    >>> comp.scheduler.add_condition(B, pnl.EveryNCalls(A, 2))
    >>> comp.scheduler.add_condition(
    ...     C,
    ...     pnl.Any(
    ...         pnl.AfterNCalls(A, 3),
    ...         pnl.AfterNCalls(B, 3)
    ...     )
    ... )
    >>> termination_conds = {
    ...     pnl.TimeScale.TRIAL: pnl.AfterNCalls(C, 4, time_scale=pnl.TimeScale.TRIAL)
    ... }
    >>> execution_sequence = list(
    >>> execution_sequence  
    [{(TransferMechanism A)}, {(TransferMechanism A), (TransferMechanism B)}, {(TransferMechanism A)}, {(TransferMechanism C)}, {(TransferMechanism A), (TransferMechanism B)}, {(TransferMechanism C)}, {(TransferMechanism A)}, {(TransferMechanism C)}, {(TransferMechanism A), (TransferMechanism B)}, {(TransferMechanism C)}]

Class Reference

class psyneulink.core.scheduling.scheduler.Scheduler(composition=None, graph=None, conditions=None, termination_conds={<TimeScale.RUN: 3>: <psyneulink.core.scheduling.condition.Never object>, <TimeScale.TRIAL: 2>: <psyneulink.core.scheduling.condition.AllHaveRun object>}, default_execution_id=None, **kwargs)

Generates an order of execution for Components in a Composition or graph specification dictionary, possibly determined by a set of Conditions.

  • composition (Composition) – specifies the Components to be ordered for execution, and any dependencies among them, based on the Composition’s graph.
  • conditions (ConditionSet) – set of Conditions that specify when individual Components in composition execute and any dependencies among them
  • graph (Dict[Component: set(Component)]) – a graph specification dictionary - each entry of the dictionary must be a Component, and the value of each entry must be a set of zero or more Components that project directly to the key.

ConditionSet – the set of Conditions the Scheduler uses when running


list – the full history of time steps the Scheduler has produced


list – a list form of the Scheduler’s toposort ordering of its nodes


Dict[TimeScale: Condition] – a mapping from TimeScales to Conditions that, when met, terminate the execution of the specified TimeScale.


Dict[TimeScale: Dict[TimeScale: int]] – a structure counting the number of occurrences of a certain TimeScale within the scope of another TimeScale. For example, times[TimeScale.RUN][TimeScale.PASS] is the number of PASS`es that have occurred in the current `RUN that the Scheduler is scheduling at the time it is accessed


Clock – a Clock object that stores the current time in this Scheduler

add_condition(owner, condition)

Adds a Condition to the Scheduler. If owner already has a Condition, it is overwritten with the new one. If you want to add multiple conditions to a single owner, use the composite Conditions to accurately specify the desired behavior.

  • owner (Component) – specifies the Component with which the condition should be associated. condition will govern the execution behavior of owner
  • condition (Condition) – specifies the Condition, associated with the owner to be added to the ConditionSet.

Adds a set of Conditions (in the form of a dict or another ConditionSet) to the Scheduler. Any Condition added here will overwrite an existing Condition for a given owner. If you want to add multiple conditions to a single owner, add a single Composite Condition to accurately specify the desired behavior.

Parameters:conditions (dict[Component: Condition], ConditionSet) –

specifies collection of Conditions to be added to this ConditionSet,

if a dict is provided:
each entry should map an owner Component (the Component whose execution behavior will be governed) to a Condition
run(termination_conds=None, context=None, base_context=<psyneulink.core.globals.context.Context object>, skip_trial_time_increment=False)

run is a python generator, that when iterated over provides the next TIME_STEP of executions at each iteration

Parameters:termination_conds – (dict) - a mapping from TimeScales to Conditions that when met terminate the execution of the specified TimeScale