Ontologies & Models
An ontology is a reusable type of system element. It bundles, in one cohesive unit:
- Fields — typed, documented attributes (see Types & Units)
- Relationships — typed references to other ontologies (see Relationships)
- Constraints — validators and derived attributes (see Constraints & Derivation)
- Behaviors — how state evolves during simulation (see Behaviors)
- Documentation — first-class docs for the ontology and each field
An instance of an ontology holds concrete values. Instances compose into a tree — the model — and a root model handed to the Simulator is an agent.
Declaring an Ontology
An ontology declares named, typed fields. Each field carries its own documentation, which travels with the model through schemas, generated reference material, and error messages:
class Pendulum(Ontology):
"""A simple pendulum."""
length: float
"""The length of the pendulum in meters."""
angle: Annotated[TypedDatum, "rad"] = 0.0
"""The angle of the pendulum in radians."""
A field with a declared default is optional at construction; a field without one is required and must be set before the model is finalized.
Identity and Reserved Fields
Every ontology automatically carries a small set of framework-defined fields:
| Field | Type | Semantics |
|---|---|---|
id | ID | An identifier, unique among all instances within an agent. Auto-generated deterministically if not provided, so repeated model construction yields stable IDs. |
type | String | The ontology type name. Reserved — derived from the ontology's name and cannot be set manually. |
name | String | An optional human-oriented name. |
parent | Relationship | The parent instance in the composition tree, if any. |
children | Relationship | The list of child instances. |
ids are unique within an agent, so within a model two instances are the same exactly when their ids are equal. IDs are not guaranteed unique across different agents.
Inheritance
Ontologies may extend one or more other ontologies. A subtype inherits its supertypes' fields, relationships, constraints, and behaviors, and may add new members or override inherited ones. Subtyping is visible to the rest of the platform:
- Queries by type match subtypes (querying for
Actuatoralso returnsReactionWheelinstances). - SedaroQL type accesses and
type matchexpressions resolve against the type hierarchy. - The type hierarchy is captured explicitly in serialized schemas (
supersis a list of supertypes).
Multiple inheritance is supported — an ontology may declare several supertypes. When more than one supertype contributes a member of the same name, it is resolved by C3 linearization, the same method-resolution-order algorithm Python uses. Each ontology therefore has a single, deterministic linear order over its ancestors that governs field, relationship, constraint, and behavior resolution.
Overriding respects subtyping. A subtype may override an inherited field, but the override must be a subtype of the declaration it replaces — overrides may only narrow, never widen. This applies in two ways:
- Along the ontology hierarchy — a
Carwithpower: Enginemay be specialized by a subtype that narrows the field topower: TurboEngine(whereTurboEngineextendsEngine), but not to an unrelatedBattery. - Through the type system's refinements — a typed datum may be refined to a stricter restriction, e.g.
{m | >0, <180} ~> {m | >0, <90}(the narrower range is a subtype of the wider one).
Because an override can only narrow, an invariant established by a supertype — "every Car's power is an Engine" — is guaranteed to hold for every subtype. Implementors such as the Simulation Compiler can rely on those invariants for optimization.
Composition
Instances form a tree via the built-in parent/children relationship pair. Composition is how independent models are combined into larger ones without modification — the pendulum stays a pendulum whether it is the root of its own simulation or one component among hundreds.
Composition rules:
- An instance can have at most one parent, and re-parenting is not permitted — an instance must be detached (or cloned) before joining a different tree.
- Sibling requirements: an ontology may declare that it can only be composed alongside instances of certain other ontology types. Adding an instance whose requirements are not met by its new siblings is an error.
- IDs must be unique throughout a tree: no two instances anywhere under a common root may share an
id, and duplicates are rejected during validation.
Querying Models
A model is a queryable object graph. The core operations, available in every representation of the language:
- Get by ID — resolve an instance anywhere in the tree by its unique
id. - Query by type — find children of a given ontology type, including subtypes.
- Query by field values — find children whose fields equal given values (instances lacking the field are excluded).
- Deep query — the same, recursively, to a configurable depth.
These queries operate on the model at rest (configuration time). Queries evaluated during simulation — including cross-instance and cross-agent state access — are expressed in SedaroQL and declared on behaviors.
Documentation as Data
Ontology and field documentation is part of the model, not commentary around it. Docs are:
- attached to each ontology and field at declaration,
- carried through schema generation for editors and UIs,
- exportable as human-readable reference material (e.g. Markdown) directly from an ontology.
This guarantees the description of a system and the system itself cannot drift apart.