Skip to main content

Query Language (SedaroQL)

SedaroQL is a query language for identifying collections of instance field values within a simulation. State managers combine a pure Python or Rust function with a consumed and produced query, specifying which field values to read from and write to the world respectively. For each state manager, the simulator will effectively:

  • Read values specified by the consumed query from the world as arguments
  • Pass those arguments to the function and retrieve the results
  • Write those results back to the world as specified by the produced query

ExternalState blocks expose user-defined consumed/produced queries which behave similarly to enable cosimulation. Each query contains one top-level expression, usually a tuple.

Expression reference​

References and field access​

SyntaxResult
agent!(agentId)A specific agent. agentId is evaluated before the simulation and may be loaded from the model.
block!(blockId)A specific block on the current agent. blockId is evaluated before the simulation and may be loaded from the model.
expr.block!(blockId)A specific block on the agent selected by expr.
root!The current agent's root, including when the query belongs to a block.
target!(expr)A dynamic reference to the agent selected by expr.
fieldA field or relationship on the current block.
expr.fieldA field or relationship on the selected block or blocks. Multi-valued relationships produce lists.
expr.BlockTypeAll blocks of BlockType on the selected agent, as a list.
filter!(relationship, type, BlockType)The members of a relationship whose concrete type is BlockType or a subtype.
ephem!("moon")The current orbital ephemeris of a celestial body.

Values and composition​

SyntaxResult
()An empty tuple.
(a,)A one-element tuple. The trailing comma is required.
(a, b) or (a, b,)A tuple with two or more elements.
collection[index]An item from a list, tuple, or map.
"string", true, falseString and Boolean literals.
expr.(spread1, spread2)A tuple formed by applying each spread element to expr.
expr as targetTypeexpr converted to another unit or frame using a SedaroTS type such as km or ecef.
prev!(expr)The value of expr from the previous step.
match sel { pattern => expr }A match whose selector and selected expression are evaluated on every frame.
static match sel { pattern => expr }A match whose selector is evaluated once before the simulation.
type match sel { BlockType => spread }The first spread whose block type is a supertype of the selector's type.
seed!(agent, all, block, engine, path, sim, sm)A pseudo-random integer seeded by the listed simulation identifiers. Arguments may be combined; for example, seed!(engine, sim).

Window reads and cross-engine sampling​

SyntaxResult
last!(query, n)Up to the newest n timestamped samples of query.
from!(query, timeQuery)Timestamped samples of query from timeQuery through the read time.
read_by!(method, query)query with a method for sampling values that cross an engine or agent boundary.

Relationships and aggregates​

Tuples and lists are aggregates. Field access and spreads apply element-by-element to aggregates. For example, (ReactionWheel, Thruster).torque is equivalent to (ReactionWheel.torque, Thruster.torque).

Filter relationships by type​

Use filter! to narrow a relationship before reading subtype-specific fields:

filter!(units, type, Scout).battery
filter!(root!.units, type, Scout).battery
filter!(agent!("vehicle").units, type, Scout).battery
filter!(block!("PowerLoad_1").modes, type, ActiveMode).active

The filter includes the named type and all of its subtypes, preserves relationship order, and produces a typed empty list when nothing matches. An optional one-sided relationship produces null on a mismatch.

filter! only filters relationships by block type; it is not a general list or predicate filter.

Spreads​

Spreads avoid repeating a common agent or block expression. For example, BatteryPack.(battery.(voltage, current), esr) is equivalent to ((BatteryPack.battery.voltage, BatteryPack.battery.current), BatteryPack.esr).

Spread syntaxMeaning
field or BlockTypeAccess directly from the spread's left side.
spread.fieldAccess from the result of another spread element.
block!(blockId)Access a block on the spread's left side.
spread.block!(blockId)Access a block on the result of another spread element.
spread as targetTypeConvert the spread result to a SedaroTS type.
ephem!("moon")Read an ephemeris without using the spread's left side.
prev!(spread)Read the spread result from the previous step.
static match sel { pattern => spread }Select a spread using a selector relative to the spread's left side.
spread.(spread1, spread2)Apply an inner spread to the outer spread's result.
(a, b,)Apply each tuple element to the spread's left side.

Unit and frame conversions​

Some conversions depend on context such as time, attitude, or position. Supply a dependency explicitly with a where clause when the default field is not the intended value:

root!.position as ecef where {
time = root!.elapsedTime
}

Only dependencies used by the conversion are accepted, and each dependency name may appear once.

For an omitted dependency, a direct field conversion first uses a same-named field on the source field's parent block. If the parent does not provide it—or the converted value is not a direct field access—the same-named field on root! is used. An explicit where entry takes precedence over both defaults.

History windows​

History results are ordered oldest-first and have type [(day, T)], where each day timestamp is the source sample time.

Last samples: last!​

last!(temperature, 10)
last!(agent!("vehicle").position, 3)

last!(query, n) returns the newest samples at or before the query's read time. It may return fewer than n samples near simulation startup. The count must be a literal integer from 1 through 50,000.

Samples from a time: from!​

from!(temperature, startTime)
from!(agent!("vehicle").position, root!.windowStart)

from!(query, timeQuery) returns samples from the time-typed lower bound through the read time, with both endpoints included. Each query instance's lower bound must stay constant or move forward; moving it backward is an error. A fixed early bound creates an ever-growing result, so prefer a moving bound for long simulations when full history is unnecessary.

Window limitations​

  • Cross-engine and cross-agent source-clock history is guaranteed for direct field values, such as last!(agent!("vehicle").position, 3). Composite values, inner prev!, conversions, nested windows, and query-operator results may instead be sampled on the reading engine's cadence and can omit faster source samples.
  • A window value must resolve to one producer clock. Expressions combining independently produced values are unsupported.
  • History windows cannot be used as produced destinations.
  • Wrapping a window in prev! delays the completed list by one reader step; it does not move the source window's read time. Put prev! inside the window only when the delayed value itself is required, subject to the direct-field limitation above.

Cross-engine read methods​

read_by!(method, query) controls how values inside a consumed query are sampled when they come from another engine or agent. Local values are unchanged. A nested read_by! overrides the outer method for its inner query.

read_by!(floor, agent!("navigation").position)
read_by!(interpolation, agent!("navigation").position)
read_by!(extrapolation, agent!("navigation").position)
read_by!(exact, agent!("navigation").mode)

read_by!(extrapolation, (x, read_by!(floor, y)))
MethodSource samples used at requested time t
floorThe source sample that covers an interval including t.
interpolationOne sample before t and one at or after t, sampled to t according to its SedaroTS type.
extrapolationThe two newest samples at or before t, sampled to t using the same unrestricted type-directed operation. A single startup sample is held constant.
exactThe source state evaluated at t.

Interpolation and extrapolation preserve the query value's complete SedaroTS type, including nested units, frames, and field names. Float and ordinary integer leaves interpolate linearly; pure plane angles follow the shortest arc; and four-float body_eci or body_ecef quaternion tensors use shortest-path SLERP. Products, tensors, equal-length lists, equal-key maps, and same-variant sums or optionals recurse into their values.

Other leaves—including identifiers, strings, Booleans, and sets—use a left-continuous hold: the older value is retained between source samples and the newer value is retained after the second sample. A changed list length, map key set, or sum/optional variant holds the entire value. Integer results truncate toward zero and saturate to their declared range.

Exact source endpoints are preserved. Extrapolated results are not clamped and can leave declared type bounds; validate bounds separately when required.

exact cannot read from an engine containing an external state manager. Source state-manager functions used by exact must also be deterministic, free of external side effects, and must not mutate supplied values. Dependency cycles involving exact, or cycles in which every read requires interpolation, are rejected.

A registered query operator may also be used as a method when it declares a fixed trailing or surrounding source window. Such an operator receives ([(day, T)], day) and returns T; it must handle startup windows that contain fewer samples than requested. A trailing or surrounding window can request at most 50,000 samples.

read_by! is not valid in produced, outputs, or lerps queries.

Patterns and match pairs​

Each match pair has a pattern and an expression separated by =>. String literals match equal selector values. _ matches any value not matched by an earlier pair.

Reserved names​

These query names are reserved and cannot be redefined:

  • time — current simulation time in MJD
  • timeStep — simulation time step in days
  • elapsedTime — elapsed simulation time in days
  • startTime — simulation start time in MJD
  • stopTime — simulation stop time in MJD

The following syntax keywords cannot be used as identifiers:

agent all as block children
dynamic dynatic else engine ephem
field if lerp match path
prev root seed target sim
sm static Some type where

filter, last, from, and read_by are soft keywords: those names remain valid for fields except when followed by the corresponding !(...) query form.