orchestrator¶
context¶
- class BaseModelState(context: T)¶
Bases:
State[ConnectedModel],ABC- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = ()¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = ()¶
- class Busy(context: T)¶
Bases:
WaitingForMessageBase class for if a model is doing something and a response is required. While busy, one or more Command-s may come in, which need to be processed later. These are stored until the model returns and they can be processed
- on_enter()¶
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = (<class 'movici_simulation_core.messages.ErrorMessage'>,)¶
- class ConnectedModel(name: str, timeline: ~movici_simulation_core.services.orchestrator.context.TimelineController, send: ~typing.Callable[[~movici_simulation_core.messages.Message], None], logger: ~logging.Logger = <factory>, publishes_to: list[~movici_simulation_core.services.orchestrator.context.ConnectedModel] = <factory>, subscribed_to: list[~movici_simulation_core.services.orchestrator.context.ConnectedModel] = <factory>, pub: dict | None = <factory>, sub: dict | None = <factory>, fsm_config: ~movici_simulation_core.services.orchestrator.fsm.FSMConfig[~movici_simulation_core.services.orchestrator.context.ConnectedModel] | None = None)¶
Bases:
objectHolds connection state and other data concerning a (connected) model
- busy: bool = False¶
- failed: bool = False¶
- fsm: FSM[ConnectedModel, Message]¶
- fsm_config: FSMConfig[ConnectedModel] | None = None¶
- logger: Logger¶
- name: str¶
- next_time: int | None = None¶
- pending_new_time: NewTimeMessage | None = None¶
- pending_quit: QuitMessage | None = None¶
- pending_updates: list[UpdateMessage]¶
- pub: dict | None¶
- publishes_to: list[ConnectedModel]¶
- send_command(message: NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage) None¶
Send a command message to the model
- start()¶
- sub: dict | None¶
- subscribed_to: list[ConnectedModel]¶
- timeline: TimelineController¶
- class Context(models: 'ModelCollection', timeline: 'TimelineController', global_timer: 'Stopwatch' = None, phase_timer: 'Stopwatch' = None, logger: 'logging.Logger' = <factory>, orchestrator_failed: 'bool' = False)¶
Bases:
object- property failed¶
- finalize()¶
- log_elapsed_global_time(seconds: float)¶
- log_elapsed_phase_time(seconds: float)¶
- log_finalize_message()¶
- log_interconnectivity_matrix()¶
- log_new_phase(phase: str)¶
- log_new_time()¶
- logger: Logger¶
- models: ModelCollection¶
- orchestrator_failed: bool = False¶
- queue_models_for_next_time()¶
- recv_message(model: ConnectedModel, message: Message)¶
- timeline: TimelineController¶
- class Done(context: T)¶
Bases:
BaseModelStateThe model is either done or failed, ignore any further messages
- handle_event(event)¶
- requires_event = True¶
- class Finalizing(context: T)¶
Bases:
BusyA QuitMessage has been sent to the model, which needs to acknowledge it and shut down, ignore all commands
- process_command(msg: NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage)¶
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.NewTimeMessage'>, <class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = (<class 'movici_simulation_core.messages.AcknowledgeMessage'>, <class 'movici_simulation_core.messages.ErrorMessage'>)¶
- class HasPendingNewTimeAndIdle(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class HasPendingQuitAndIdle(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class HasPendingUpdatesAndIdle(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class Idle(context: T)¶
Bases:
WaitingForMessageBase class for if a model is awaiting further instructions
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.NewTimeMessage'>, <class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = ()¶
- class IsIdle(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class ModelCollection¶
Bases:
dict[bytes,ConnectedModel]- property busy¶
- determine_interdependency()¶
calculate the subscribers for every model based on the pub/sub mask.
- property failed¶
- property next_time¶
- reset_model_timers()¶
- class ModelHasFailed(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class ModelWaitingForDependencies(context: T)¶
Bases:
Condition[ConnectedModel]- met() bool¶
- class NewTime(context: T)¶
Bases:
BusyA NewTime message will be sent to the model, and it needs to Acknowledge that
- on_enter()¶
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = (<class 'movici_simulation_core.messages.AcknowledgeMessage'>, <class 'movici_simulation_core.messages.ErrorMessage'>)¶
- class NoUpdateMessage¶
Bases:
MessageA NoUpdateMessage is sent to a subscribed (dependent) ConnectedModel to indicate that it’s dependency has finished calculating (but not produced any data) so that the subscribed ConnectedModel can determine whether to send out any pending updates
- class PendingMoreUpdates(context: T)¶
Bases:
IdleThe model has one or more dependencies that are still calculating. We wait until a dependency returns before re-evaluating whether we can send the update to the model
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = ()¶
- class ProcessPendingQuit(context: T)¶
Bases:
BaseModelStateWhile the model was Busy, a QuitMessage came in which needs to be processed, this state doesn’t wait for messages
- run()¶
- class ProcessPendingUpdates(context: T)¶
Bases:
BaseModelStateWhile the model was Busy, one or more updates came in which needs to be processed, this state doesn’t wait for messages. If the model has one or more dependencies that are still calculating, we can’t send the update yet but have to wait until all dependencies are finished
- process_pending_updates()¶
- run()¶
- class Registration(context: T)¶
Bases:
BusyA RegistrationMessage is expected from the model
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.QuitMessage'>,)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = (<class 'movici_simulation_core.messages.RegistrationMessage'>, <class 'movici_simulation_core.messages.ErrorMessage'>)¶
- class TimelineController(start: 'int', end: 'int', current_time: 'int | None' = None)¶
Bases:
object- current_time: int | None = None¶
- end: int¶
- set_model_to_start(model: ConnectedModel)¶
- set_next_time(model: ConnectedModel, next_time: int | None = None)¶
- start: int¶
- class Updating(context: T)¶
Bases:
BusyThe model is processing an UpdateMessage and calculating a ResultMessage is expected
- valid_commands: tuple[Type[NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage], ...] = (<class 'movici_simulation_core.messages.UpdateMessage'>, <class 'movici_simulation_core.services.orchestrator.context.NoUpdateMessage'>, <class 'movici_simulation_core.messages.QuitMessage'>)¶
- valid_responses: tuple[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage], ...] = (<class 'movici_simulation_core.messages.ResultMessage'>, <class 'movici_simulation_core.messages.ErrorMessage'>)¶
- class WaitingForMessage(context: T)¶
Bases:
BaseModelState- handle_invalid_response(msg: RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage, valid_messages: Iterable[Type[RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage]])¶
- handle_response(msg: RegistrationMessage | AcknowledgeMessage | ResultMessage | ErrorMessage)¶
- notify_subscribers(command: UpdateMessage | NoUpdateMessage)¶
Notify subscribers that this ConnectedModel’s model has returned.
- process_command(msg: NewTimeMessage | UpdateMessage | NoUpdateMessage | UpdateSeriesMessage | QuitMessage)¶
- process_new_time(msg: NewTimeMessage)¶
- process_no_update(msg: NoUpdateMessage)¶
- process_quit(msg: QuitMessage)¶
- process_update(msg: UpdateMessage)¶
- requires_event = True¶
fsm¶
- class FSM(config: FSMConfig[T], context: T = None, raise_on_done=True)¶
Bases:
Generic[T,E]Implementation of a Finite State Machine runner. Requires a
FSMConfigas a definition of the available states and the possible transitions. The config contains aninitial_statethat is entered when theFSMis started by invoking theFSM.start()method. If the initial state is configured withrequires_event=True, then theFSMwaits until theFSM.handle_event()method is called upon which it will pass the event through to the active state by calling the state’shandle_eventmethod, which the State must have implemented if it hasrequires_eventset toTrue. If the active state does not haverequires_event=Trueit will call therunmethod instead. After every state is invoked (by calling either thehandle_eventorrunmethod) the configured transitions for the state (as given by theFSMConfig) are evaluated against the context. The FSM transitions to State belonging to the first matched transition, which will become the active state. If no condition is matched, then the current state remains the active state for the next iteration. The FSM continues to call the active state’srunmethod and transition until it encounters a state that hasrequires_event=Trueupon which it will wait forFSM.handle_event()to be called.The FSM may terminate iff a state raises an
FSMException, usuallyFSMDoneto indicate a normal end to the FSM, orFSMErrorin case of an abnormal end to theFSM. If anFSMErrorwas raised then theFSM.failureflag will be set. If no valid state ever raises anFSMExceptionthen theFSMmay run indefinitely- Parameters:
config – An
FSMConfigfor the finite state machine.context – An arbitrary, mutalbe object that is kept for the lifetime of the FSM. It can be used to store state that must be shared between States and will be used to evaluate any transition conditions
raise_on_done – A boolean to indicate whether to raise an
FSMExceptionin case the FSM terminates. The exception will be the same as the exception that terminated theFSM
- catch_fsmexceptions()¶
- ensure_not_done()¶
- ensure_not_started()¶
- handle_event(event: E)¶
- run_until_event_required()¶
- start()¶
Start the FSM
- transition()¶
- class FSMConfig(initial_state: ~typing.Type[~movici_simulation_core.services.orchestrator.fsm.State[~movici_simulation_core.services.orchestrator.fsm.T]], states: dict[~typing.Type[~movici_simulation_core.services.orchestrator.fsm.State[~movici_simulation_core.services.orchestrator.fsm.T]], ~typing.Sequence[tuple[~typing.Type[~movici_simulation_core.services.orchestrator.fsm.Condition], ~typing.Type[~movici_simulation_core.services.orchestrator.fsm.State]]]] = <factory>, strict: bool = True)¶
Bases:
Generic[T]A config for setting up a
FSMfinal state machine- Parameters:
initial_state – the initial state
states – a dictionary of all possible states as
type``s and their transitions. Transitions are a sequence of ``(type[Condition], type[State])tuples. When determining which State to transition to, the transitions are checked starting from the top entry in the transitions sequence. The FSM transitions to the state belonging to the first transition that is matched.strict – a boolean whether to validate that all states mentioned in the transitions and
initialstate must have an entry in the states dictionary
- strict: bool = True¶
- class State(context: T)¶
Bases:
ABC,Generic[T]Base class for FSM States. When subclassing a State, implement one of the
runorhandle_eventmethods. If a state is a transient state and needs to run its logic and transition to a new state immediately, implement therunmethod. If a state needs to wait for an event to be sent, set theState.requires_eventclass variable toTrueand implement thehandle_eventmethod- handle_event(event: Any)¶
Entry point for a state that requires an event. This method will be called by the FSM when the class variable
requires_eventis set toTrueand it receives an event from upstream
- on_enter()¶
Called once by the
FSMwhen entering a state
- requires_event = False¶
- run()¶
Entry point for a state that does not require an event. This method will be called unconditionally by the
FSMwhenrequires_eventis set toFalse(the default). States that do no haverequires_eventset are generally transient states that need to run some logic before immediately transitioning to another state
interconnectivity¶
service¶
- class Orchestrator¶
Bases:
ServiceThe class that manages the timeline and acts as a broker between models
- classmethod install(sim: Simulation)¶
- logger: Logger¶
- make_send(identifier: str)¶
create a send function that a can be used to send a message to a specific client connected to the ZMQ Router
- restart_model_timer(model: str)¶
Allow resetting a specific model timer. This is used when running using the InProcessSimulationRunner. Orchestrator starts the timer by default when it sends out the message, but we need to actually start the timer when we start processing the message
- run()¶
- setup(*, settings: Settings, stream: BaseStream, logger: Logger, **_)¶
- start()¶
Start the orchestrator FSM but do not run the stream (yet)
- stream: BaseStream¶
- timeline: TimelineController¶
states¶
- class AllModelsDone(context: T)¶
Bases:
OrchestratorCondition- met() bool¶
- class AllModelsReady(context: T)¶
Bases:
OrchestratorCondition- met() bool¶
- class EndFinalizingPhase(context: T)¶
Bases:
OrchestratorState- run()¶
- class Failed(context: T)¶
Bases:
OrchestratorCondition- met() bool¶
- class FinalizingWaitForModels(context: T)¶
Bases:
WaitForModels
- class ModelsRegistration(context: T)¶
Bases:
WaitForModels
- class NewTime(context: T)¶
Bases:
OrchestratorState- run()¶
- class StartFinalizingPhase(context: T)¶
Bases:
OrchestratorState- run()¶
- class StartInitializingPhase(context: T)¶
Bases:
OrchestratorState- run()¶
- class StartRunningPhase(context: T)¶
Bases:
OrchestratorState- run()¶
- class WaitForModels(context: T)¶
Bases:
OrchestratorState,ABC- requires_event = True¶
- class WaitForResults(context: T)¶
Bases:
WaitForModels
stopwatch¶
- class ReportingStopwatch(on_stop: ~typing.Callable[[float], None] | None = None, on_reset: ~typing.Callable[[float], None] | None = None, now_func: ~typing.Callable[[], float] = <built-in function monotonic>, ignore_errors=False)¶
Bases:
Stopwatch- reset() float¶
- stop() float¶
- class Stopwatch(now_func: Callable[[], float] = None, ignore_errors=False)¶
Bases:
object- property elapsed¶
- reset() float¶
returns Stopwatch.total_elapsed
- restart() float¶
- restart_current()¶
Restart the current timer, without recording the current progress
- property running¶
- start()¶
- stop() float¶
returns Stopwatch.elapsed
- property total_elapsed¶