Simulation
One function handles running the simulation: simulate.
SystemsOfSystems.simulate — Function
simulate(user_data; t, init_fcn, rates_fcn, updates_fcn, seed, options)Runs a simulation, returning a SimHistory containing its log, start and stop times, final model, termination reason, and any hook or resource cleanup failures.
user_data: Can be anything used by theinit_fcnt: A collection of strictly increasing, finite times. The sim will step to exactly each given time, plus as many other steps as are required by the solver and models. At the very least, this must contain a start time and end time.init_fcn: Will be called with(t_start, user_data, seed), wheret_startis the first element of the abovetinput. This must return aModelDescription.rates_fcn: Will be called with(t, model)and is expected to return aRatesOutput.updates_fcn: Will be called with(t, model)and is expected to return anUpdatesOutput, ornothingwhen there are no updates, outputs, replacementt_next, or stop request.seed: A top-level seed (Int) to control all random number generation in the sim. Theinit_fcnreceives this as aBranchingSeed.options: SeeSimOptions.
Simulation Results
simulate returns one SimHistory. It contains the requested start time, the last completed simulation time, the final model, the log, the reason the simulation stopped, and any failures while closing hooks or resources.
history = simulate(...)
history.t_start
history.t_stop
history.model
history.stopJulia's property destructuring is convenient when only part of the result is needed:
(; t_stop, model) = simulate(...)succeeded reports whether the simulation ended without a failure: it is equivalent to !failed(history.stop). Reaching the requested end time, a deliberate stop request, and a catchable interruption count as success; an unexpected exception or numerical solver failure does not. finished distinguishes a nominal end condition from an interruption, but includes deliberate early stops. These functions work for both a newly returned history and a loaded history.
Cleanup failures are independent: history.cleanup_errors lists them in close order. cleanup_context identifies what was being closed, and cleanup_details provides diagnostic text. Both functions work for live errors and loaded summaries. Applications requiring finalized outputs can check isempty(history.cleanup_errors) in addition to succeeded(history).
if !succeeded(history)
@warn "Simulation failed" reason = history.stop
endSystemsOfSystems.SimHistory — Type
A container for simulation results, including fields for:
t_start: The simulation's start timet_stop: The last time completed by the simulationlog: The log containing the time series for each variable of each modelmodel: The final model constructed in the simstop: The normal stop or failure reason that ended the simulationcleanup_errors: Failures while closing hooks or resources, in close order
This type acts like a log itself, so for instance these do the same thing:
history["/models/plant"]["position"]
history.log["/models/plant"]["position"]The keys, values, and pairs functions also pass through to the underlying log.
SystemsOfSystems.succeeded — Function
succeeded(h::SimHistory)Returns true if the simulation ended without an unexpected error or numerical failure. An interruption counts as success. finished(h.stop) reports whether the run reached a nominal end condition, including a deliberate early stop. Cleanup failures do not affect this result; applications requiring finalized resources can also check isempty(h.cleanup_errors).
Recorded Histories
SimHistory forwards the dictionary-like log interface, so users normally do not need to access its log field directly. These expressions are equivalent:
history["/vehicle/controller"]["command"]
history.log["/vehicle/controller"]["command"]Each model path returns a Logs.ModelHistory. Its constants, states, outputs, and submodels can be accessed by string or symbol. Logging policies may omit selected variables while preserving the model-history structure.
Logs.gather_all_time_series collects every recorded TimeSeries into one ordered dictionary. Its keys combine the model path and variable name, making it useful for searching, exporting, or passing a flat collection to another tool.
all_series = Logs.gather_all_time_series(history)
position = all_series["/vehicle:position"]SystemsOfSystems.Logs.ModelHistory — Type
ModelHistoryA container for the recorded history of one model.
The named-tuple fields contain the model's constants, state and output time series, and recursive submodel histories. path identifies the model, using / for the root.
A model logging policy may omit constants and time-series fields from these named tuples. Constants preserve their declaration form: raw constants remain raw values, while constants declared with VariableDescription retain that description and its metadata.
ModelHistory is mutable to give large, recursively parameterized histories reference semantics. Its fields are established during log construction and are not normally reassigned; samples are appended to the contained time series.
SystemsOfSystems.Logs.gather_all_time_series — Function
gather_all_time_series(history)Collects every TimeSeries below a ModelHistory, AbstractLog, or SimHistory into an ordered dictionary. Keys identify both the model and variable, such as "/aircraft/imu:angular_velocity".
Stop Reasons
The history returned from simulate includes a stop field which contains an AbstractTerminationReason, which has the following interface:
| Function | Result |
|---|---|
finished(reason)::Bool | Whether the simulation reached a nominal end condition, including a model or hook stop request. |
failed(reason)::Bool | Whether an unexpected exception or numerical failure prevented propagation. |
interrupted(reason)::Bool | Whether the simulation was interrupted. |
describe(reason)::String | A concise description of why the simulation stopped. |
details(reason)::String | Additional diagnostic text, or an empty string. |
For example, we can report a run's status without depending on its concrete reason type:
using SystemsOfSystems.TerminationReasons
println(describe(history.stop))
if interrupted(history.stop)
println("The run was interrupted before a nominal end condition.")
elseif failed(history.stop)
println(details(history.stop))
endCustom reasons can subtype AbstractTerminationReason and provide custom methods for the interface functions above.
Applications requiring a particular end condition can inspect the concrete reason in a history returned by simulate, for example with history.stop isa ReachedEndTime. finished reports nominal termination generally, so it does not distinguish reaching the requested end time from stopping early at a model's request. The summary's type name and descriptions are descriptive text rather than a stable machine-readable classification of specific reasons.
Interruption
A catchable InterruptException raised during the simulation loop produces Interrupted(history.t_stop). The returned time and model describe the last fully accepted simulation sample, including its discrete update. An interrupted intermediate solver stage is discarded. Hooks and resources follow the normal teardown path and receive the accepted endpoint.
Interruption leaves logging unchanged, just as an unexpected exception does. No final sample is added and no new continuous outputs are evaluated. With regular sampling, the last logged state may therefore be older than history.t_stop; history.model contains the last accepted state.
For an interruption, interrupted(history.stop) is true, while finished(history.stop) and failed(history.stop) are false. succeeded(history) remains true because it reports the absence of failure rather than functional completion. These results are preserved when saving and loading the history.
SystemsOfSystems handles catchable Julia interruptions during the simulation loop. Applications control operating-system signal handling and process exit codes. Uncatchable termination cannot guarantee cleanup or complete log files.
Time-Series Utilities
SystemsOfSystems.select derives a new time series while preserving its timestamps and metadata. It is public but qualified because select is a common name in data-analysis packages.
speed = SystemsOfSystems.select(history["/"]["velocity"]; title = "Speed") do velocity
abs(velocity)
endplot_ts creates a new Makie figure. plot_ts! adds a time series to an existing figure or layout target. Either function requires a loaded Makie backend.
using CairoMakie
figure = Figure()
plot_ts!(figure[1, 1], history["/"]["position"])
figureSystemsOfSystems.TimeSeriesStuff.select — Function
select(f, ts::TimeSeries; kwargs)Applies f to each data element and returns the results as a new TimeSeries. Time and the associated metadata come from ts by default. A derived series representing a new signal can provide a new path and title.
Keyword arguments:
title: Title for the new time seriesdimensions: Dimensions for the transformed datapath: Complete signal path associated with the new time seriesdiscrete: Whether the new time series is discreteinterpolator: Interpolation policy for the new time seriesgroups: Dimension groups for the new time series
Example:
accelerometer_ts = SystemsOfSystems.select(measurements_ts) do measurement
measurement.accelerometer
endselect(ts::TimeSeries, dimension::AbstractString; kwargs)Selects the dimension with the given label as a new TimeSeries.
select(ts::TimeSeries, dimensions::AbstractVector{<:AbstractString}; kwargs)Selects the dimensions with the given labels as a new TimeSeries of tuples.
SystemsOfSystems.TimeSeriesStuff.plot_ts — Function
plot_ts(time_series; kwargs...)Creates a Makie figure containing one or more time series. A Makie backend such as GLMakie or CairoMakie must be loaded before calling this function.
plot_ts(ts::TimeSeries)Plots all of the dimensions of a single TimeSeries, returning the Makie.Figure. Any figure_kwargs will be passed to the Makie.Figure. If there is no content, nothing is returned.
plot_ts(tss::Vector{<:Pair{String, <:TimeSeries}}; skip_units_check = false)This combines multiple time series in a single plot. The input is a vector of string-time-series pairs, where the string becomes the legend label for the plot. This ignores plot groups; every dimension gets its own axis.
By default, this checks to make sure the units are consistent and errors if they are not. Set skip_units_check = true to skip the check.
If there is no content, nothing is returned.
Example:
plot_ts(
[
"truth" => truth_ts,
"measured" => measured_ts,
]
)plot_ts(tss::Vector, figure_kwargs = (;))This combines multiple time series in a single plot, stacked vertically. Any figure_kwargs will be passed to the Makie.Figure. If there is no content, nothing is returned.
Example:
plot_ts([truth_ts, measured_ts])plot_ts(tss::Matrix, figure_kwargs = (;))This combines multiple time series in a single plot, arranged in a matrix. Any figure_kwargs will be passed to the Makie.Figure. If there is no content, nothing is returned.
Example:
plot_ts(
[
ts1 ts2;
ts3 ts4;
]
)SystemsOfSystems.TimeSeriesStuff.plot_ts! — Function
plot_ts!(target, time_series; kwargs...)Adds one or more time series to an existing Makie figure or layout target. A Makie backend such as GLMakie or CairoMakie must be loaded before calling this function.
plot_ts!(f, ts::TimeSeries)Adds a TimeSeries to a given figure (or any "block" within a figure), f. All new axes are returned.
plot_ts!(f, tss::Vector{<:Pair{String, <:TimeSeries}}; skip_units_check = false)This combines multiple time series in a single plot in the given figure (or any "block"), f. The tss input is a vector of string-time-series pairs, where the string becomes the legend label for the plot. This ignores plot groups; every dimension gets its own axis.
By default, this checks to make sure the units are consistent and errors if they are not. Set skip_units_check = true to skip the check.
All new axes are returned.
See plot_ts(tss::Vector{<:Pair{String, <:TimeSeries}}) for more.
Saving and Loading Histories
save_sim_history saves a record of a run to a single HDF5 file. load_sim_history restores its times, termination information, and a disk-backed log. Loading does not read all of the log into memory, so we can select just the series or samples needed for an analysis.
using SystemsOfSystems, HDF5Vectors
save_sim_history("history.h5", history)
loaded = load_sim_history("history.h5")
position = loaded["/vehicle"]["position"].data[:]
Logs.close_log(loaded.log)For an analysis that needs the file only within a block, the filename-based do form closes it automatically, including when the block throws an exception:
position = load_sim_history("history.h5") do loaded
collect(loaded["/vehicle"]["position"].data)
endThe call returns the block's result. Copied samples and computed statistics remain usable afterward; a returned history or time series that still depends on the file does not. Loading options such as history_path and load_model are also accepted. For caller-owned groups, an enclosing HDF5.h5open(...) do fid block can manage the file's lifetime instead.
The caller owns the loaded log's file and can release it with Logs.close_log. A history produced with logging disabled restores a NullLog and keeps no file open. Open HDF5 handles belong to their process; a loaded history should not be sent from a worker to another process. It can instead be loaded in the process that will use it.
File Layout and Models
The HDF5 file format documents the public entries for external readers, including model and time-series paths and sample encodings. Readers can rely on those entries while ignoring additional implementation-specific data. The abbreviated tree below also shows some Julia reconstruction entries, identified as such; it is not an exhaustive list of public keys.
The default groups are /history for run information and /log for logged data. The history's log_path dataset identifies the log group within the same HDF5 file. Start and stop times are ordinary floating-point datasets; loading restores them with exact_time, so it does not preserve distinctions lost in conversion to floating point. Logs.load_hdf5_log can read the log independently of the history metadata.
For example, a run from 0 to 1 second with a root state named x and a child model named child has the following layout. HDF5Vectors storage details are abbreviated as ....
history.h5
├── history/
│ ├── sim_history_version = 1
│ ├── systems_of_systems_version = "..."
│ ├── log_path = "/log"
│ ├── t_start = 0.0
│ ├── t_stop = 1.0
│ ├── stop/
│ │ ├── type = "SystemsOfSystems.TerminationReasons.ReachedEndTime"
│ │ ├── finished = true
│ │ ├── failed = false
│ │ ├── interrupted = false
│ │ ├── summary = "The sim reached the specified end time of 1.0."
│ │ └── details = ""
│ ├── cleanup_errors/
│ │ └── count = 0 # Number of failed hook or resource closes
│ └── model/ ... # Optional final model value
└── log/
├── log_format_version = 1
├── systems_of_systems_version = "..."
├── type = "Nothing"
├── serialized_type = ... # Julia type reconstruction
├── constants/
├── names/ ... # Names and ordering of logged variables
├── timeseries/
│ └── x/
│ ├── title = ...
│ ├── path = "/x"
│ ├── time/ ...
│ ├── data/ ...
│ └── ... # Units, dimensions, interpolation, etc.
└── models/child/ ... # Each child repeats this log layoutWhen logging was disabled, /log contains is_null = true and its format/provenance datasets, with no model tree. History and log format versions are checked independently when loading; the format compatibility policy describes supported versions and legacy files.
The final model is omitted by default. If a model is suitable for storage with HDF5Vectors, we can opt into saving and loading it separately:
save_sim_history("history.h5", history; save_model = true)
loaded = load_sim_history("history.h5"; load_model = true)The model is nothing when it was not saved or loading was not requested. Saving an unsupported model reports an error. Stored model types must be available when loading, and a model with process-local resources is not suitable for restoration merely because its fields can be serialized. As with saved logs, files should come from trusted sources because model and log metadata may use Julia serialization.
Writing Logs and History to the Same File
An HDF5 simulation log starts in /log, so the file can accumulate samples during a run and receive its history metadata afterward:
filename = "flight.h5"
options = SimOptions(; log = Logs.HDF5LogOptions(filename))
history = simulate(model; init_fcn, rates_fcn, t = (0, 10), options)
save_sim_history(filename, history)
Logs.close_log(history.log)Saving adds /history without moving or copying /log. Saving again replaces the history metadata; for example, saving with save_model = false removes a previously saved model. Existing log dataset handles remain usable. Saving to a different filename overwrites that file and copies the log through HDF5 without loading all its samples into memory. A history loaded read-only can be saved to another file, but cannot overwrite its own open file.
Custom Groups and Results Containers
The default group names can be changed when saving and loading:
save_sim_history("results.h5", history;
history_path = "/runs/first/history", log_path = "/runs/first/log")
loaded = load_sim_history("results.h5"; history_path = "/runs/first/history")
Logs.close_log(loaded.log)Only the history path is needed when loading: its log_path dataset locates the log. This is a path within the HDF5 file, so renaming or moving the file does not break it. Direct-to-disk simulations can select their log location with Logs.HDF5LogOptions(; filename, path = "/samples"); saving the history to that file uses the existing log location by default.
For a file containing several runs or other application data, the group-based methods leave file ownership with the caller. Relative paths are resolved within the supplied parent, and the default log path is "log" within that parent. For example, save_sim_history(run_group, "history", history) puts both groups inside run_group. Absolute paths still select locations from the file root:
import HDF5
HDF5.h5open("results.h5", "w") do fid
save_sim_history(fid, "/runs/first/history", history;
log_path = "/runs/first/log")
# Explicit groups offer the same interface without constructing paths in the saver.
hg = HDF5.create_group(fid, "/runs/second/history")
lg = HDF5.create_group(fid, "/runs/second/log")
save_sim_history(hg, history; log_group = lg)
loaded = load_sim_history(hg)
samples = loaded["/"]["x"].data[:]
Logs.close_log(loaded.log) # The caller's file and groups remain open.
close(hg)
close(lg)
endThe history and log groups must be in the same file and must not contain one another. Saving replaces their contents, except that a log already at its destination is preserved. Other groups in the file are untouched. A log loaded through a group remains usable only while the caller's file is open. Logs.save_log_to_hdf5 and Logs.load_hdf5_log likewise accept a group, or a parent file/group and path, for working with logs independently.
Older standalone logs stored their data at the file root. Logs.load_hdf5_log still reads those files. A history using such a log can be saved to a new file with the current layout; saving metadata into that same root log would overlap its storage and is rejected.
Unsuccessful Saves
Saving replaces the destination contents and is not transactional. If a write fails, the destination may contain a partial result, and previous contents may already have been replaced. This includes failures while saving an optional model. When an existing result needs to be preserved until a new save succeeds, the new result can be saved to a separate file first. Saving metadata beside a live log preserves that log, but failed metadata writes may leave an incomplete history group.
Termination Summaries
In general, termination reasons (<: AbstractTerminationReason) can be flexible and store information that cannot always be meaningfully saved and loaded. When saving a SimHistory, this information is discarded, and a TerminationSummary will be saved in its stead. The TerminationSummary stores the results of the API for termination reasons (finished, failed, interrupted, describe, and details), and hence it can be loaded and return the same values for those functions. However, the specific type information for a termination reason is lost on saving.
The original type name is retained in the summary's type field for identification. Reason-specific fields, such as a model path or solver step size, are available only through the saved descriptions when those descriptions include them. Hooks, exceptions, and stack traces are not restored. For EncounteredError, details retains the rendered exception and stack trace as text.
The same summary is useful without saving a history. For example, we can collect portable information for another application:
using SystemsOfSystems.TerminationReasons
summary = TerminationSummary(history.stop)
summary.type
summary.summary
summary.details
finished(summary) == finished(history.stop) # trueSystemsOfSystems.save_sim_history — Function
save_sim_history(filename, history; history_path = "/history", log_path = nothing, save_model = false)
save_sim_history(parent, path, history; log_path = "log", save_model = false)
save_sim_history(group, history; log_group, save_model = false)Saves a simulation's log, start and stop times, termination information, and readable cleanup failures to an HDF5 file. Returns nothing. Importing HDF5Vectors enables this function.
For a filename, history_path selects the metadata group. The log is saved at /log by default, or kept at its existing location when it already resides in the destination file. log_path can select a different location. Saving into a live log's writable file preserves that file; other destination files are overwritten.
The final model is omitted unless save_model = true. A requested model must be representable by HDF5Vectors; an unsupported model causes the save to fail.
For an open HDF5 file or group, save_sim_history(parent, path, history; log_path) selects the two destination groups by path, relative to parent unless an absolute path is used. The default log group is log within that parent. Alternatively, save_sim_history(group, history; log_group) accepts them directly. These methods replace the groups' contents, preserving a log already at its destination and leaving the rest of the file untouched. Both groups must be in the same file, and neither may contain the other. Caller-supplied handles stay open after saving.
The simulation guide gives examples of saving beside a live log, using custom groups, and loading termination records.
SystemsOfSystems.load_sim_history — Function
load_sim_history(filename; history_path = "/history", load_model = false)
load_sim_history(f::Function, filename; kwargs...)
load_sim_history(parent, path; load_model = false)
load_sim_history(group; load_model = false)Loads a saved SimHistory, keeping its logged samples backed by the HDF5 file so callers can read only the data they need. Importing HDF5Vectors enables this function.
For a filename, history_path selects the metadata group, which identifies the log's location in the same file. The returned log owns the open file; Logs.close_log(history.log) releases it. The filename-based do form instead closes the log when the block finishes, including when it throws, and returns the block's result:
samples = load_sim_history("history.h5") do history
collect(history["/vehicle"]["position"].data)
endCopied samples and computed statistics remain usable after the block. Returned histories or time series that still depend on the file do not.
An open metadata group, or a parent file/group and path, can be supplied instead of a filename. The caller then retains ownership of the file and must keep it open while using the loaded log. Closing the log leaves the caller's file and group handles open.
The final model is loaded only when it was saved and load_model = true; otherwise it is nothing. Times are restored with exact_time from the saved floating-point values. Termination reasons load as TerminationSummary, which retains the full API for termination reasons (finished, failed, interrupted, describe, details). Cleanup failures load as CleanupErrorSummary values, preserving cleanup_context and cleanup_details along with the original type name when available. Exception objects and stack traces are not restored.
Unsupported or malformed format versions, and a missing history version, produce an ArgumentError. The HDF5 format guide lists supported history and log versions and the policy for retaining older readers. Writer package versions are informational only.
As with Logs.load_hdf5_log, files should come from trusted sources, and types used by saved models or log metadata must be available in the loading environment.
SystemsOfSystems.TerminationReasons.TerminationSummary — Type
TerminationSummary(; reason_type, finished, failed, interrupted, summary, details)
TerminationSummary(reason::AbstractTerminationReason)Stores the results of the complete API for an AbstractTerminationReason in a portable way. This simple type is easy to save to and load from an HDF5 file or YAML file, etc.
Fields:
reason_type::String: The string representing the original termination reason's typefinished::Bool: The result of callingfinishedon the original terminationfailed::Bool: The result of callingfailedon the original terminationinterrupted::Bool: The result of callinginterruptedon the original terminationsummary::String: The result of callingdescribeon the original terminationdetails::String: The result of callingdetailson the original termination
All defaults are "" or false.
Cleanup Error Records
A live CleanupError retains the original exception and stack trace. Saving converts each cleanup error to a CleanupErrorSummary, which stores its original type name and the results of cleanup_context and cleanup_details. Loading returns these summaries in the same order, without reconstructing exception objects or stack traces.
The CleanupErrors module exports the cleanup types and diagnostic functions. We can construct a summary without saving a history, or report diagnostics through the shared interface:
using SystemsOfSystems.CleanupErrors
for err in history.cleanup_errors
println(cleanup_context(err))
println(cleanup_details(err))
summary = CleanupErrorSummary(err)
println(summary.type)
endCustom cleanup errors subtype AbstractCleanupError and implement cleanup_context and cleanup_details, each returning a string. Summary conversion calls these methods without inspecting other fields. The type name is descriptive text; it does not restore the original type. Older saved entries without a type label load with an empty label, while missing context or diagnostic fields produce a loading error.
SystemsOfSystems.CleanupErrors.AbstractCleanupError — Type
A failure while closing a hook or resource. Custom errors implement cleanup_context and cleanup_details, returning strings that identify the context and describe the failure.
SystemsOfSystems.CleanupErrors.CleanupError — Type
CleanupError(context, exception, trace)A cleanup failure from this process, retaining the thrown value and stack trace.
SystemsOfSystems.CleanupErrors.CleanupErrorSummary — Type
CleanupErrorSummary(err::AbstractCleanupError)
CleanupErrorSummary(; type = "", context = "", details = "")Stores the cleanup-error API results as portable text, without retaining exception objects or stack traces. Constructing a summary from an existing summary returns it unchanged.
Fields:
type::String: The original cleanup error's type name, for identification.context::String: The result ofcleanup_contexton the original error.details::String: The result ofcleanup_detailson the original error.
SystemsOfSystems.CleanupErrors.cleanup_context — Function
cleanup_context(err::AbstractCleanupError)Returns a string identifying the hook or resource that failed to close.
SystemsOfSystems.CleanupErrors.cleanup_details — Function
cleanup_details(err::AbstractCleanupError)Returns a string containing diagnostic information about the cleanup failure. For a CleanupError, this includes the rendered exception and stack trace.
Reference
SystemsOfSystems.TerminationReasons.AbstractTerminationReason — Type
The common supertype for every reason a simulation ceased running.
Normal stop requests and failures are deliberately separate categories. A model or hook request is part of the modeled lifecycle; a numerical or software failure means that lifecycle could not produce another valid sample.
SystemsOfSystems.TerminationReasons.AbstractStopReason — Type
A normal, successfully processed request to stop a simulation.
SystemsOfSystems.TerminationReasons.AbstractFailureReason — Type
A condition that prevented the simulation from producing another valid accepted sample.
SystemsOfSystems.TerminationReasons.ReachedEndTime — Type
The simulation successfully processed its requested final sample.
SystemsOfSystems.TerminationReasons.ModelRequestedStop — Type
The first model encountered in deterministic hierarchy order requested a normal stop.
SystemsOfSystems.TerminationReasons.HookRequestedStop — Type
The first hook encountered in configured order requested a normal stop.
SystemsOfSystems.TerminationReasons.Interrupted — Type
Interrupted(t)A catchable interruption stopped the simulation at its last fully accepted time, t. interrupted returns true, while finished and failed return false. succeeded(history) remains true because an interruption is not a failure.
SystemsOfSystems.TerminationReasons.EncounteredError — Type
User model code or simulation infrastructure raised an unexpected exception.
SystemsOfSystems.Solvers.SolverFailedToConverge — Type
The adaptive solver exhausted its allowed rejected attempts without satisfying tolerance.
SystemsOfSystems.Solvers.SolverStepSizeUnderflow — Type
The proposed floating-point step was too small to produce a later official rational time.
Reporting this explicitly is preferable to repeatedly accepting a zero-duration step, which would leave the simulation loop unable to make progress.
SystemsOfSystems.TerminationReasons.describe — Function
describe(reason::AbstractTerminationReason)Returns a concise, human-readable description of why a simulation stopped.
SystemsOfSystems.TerminationReasons.finished — Function
finished(stop::AbstractTerminationReason)Returns true if the simulation propagated to a nominal end state, like reaching the specified end time or to a model-requested termination.
SystemsOfSystems.TerminationReasons.failed — Function
failed(stop::AbstractTerminationReason)Returns true if the simulation failed to propagate, such as experiencing a numerical failure or exception.
SystemsOfSystems.TerminationReasons.interrupted — Function
interrupted(stop::AbstractTerminationReason)Returns true if the simulation was interrupted before a nominal conclusion, without reporting a failure.
SystemsOfSystems.TerminationReasons.details — Function
details(reason::AbstractTerminationReason)Returns a string containing detailed information about the termination reason.