Constraints & Derivation
Beyond types, SedaroML captures two further classes of model-time logic:
- Validators — logical constraints a model must satisfy to be realizable
- Derivers — attributes computed from other parts of the model
Both run when a model is finalized — i.e. at configuration time, before a simulation is ever built. This is a core reliability property of the language: a model that finalizes successfully has already had its constraints checked and its derived state populated, so entire classes of errors can never reach the simulator.
Validators
A validator is a named predicate attached to an ontology. It inspects the instance (and, through relationships, the surrounding model) and raises an error if the model is not physically or logically realizable:
@validator
def validate_length(self):
if self.length <= 0:
raise ValueError("Pendulum length must be positive.")
Validators express the "logical value constraints" of the language — range checks (3 < x <= 12), conditional requirements (if y > 7: z must be undefined), cross-field consistency (e.g. "the number of threats must match the number of launch times"), and so on.
Validation is hierarchical: validating an instance validates its children too, and structural rules (such as duplicate IDs) are checked alongside user-declared validators.
Derivers
A deriver computes fields of its own instance from other information in the model — its own fields, its children, or related instances:
@deriver
def order_threats(self):
pairs = sorted(zip(self.launchTimes, self.threats), key=lambda p: p[0])
self.launchTimes = [t for t, _ in pairs]
self.threats = [threat for _, threat in pairs]
Derivation has deliberately constrained semantics:
- Fixed-point evaluation. All derivers in the model tree are run repeatedly until every one succeeds. A deriver that depends on another deriver's output simply fails (harmlessly) until its inputs exist and is retried. If a full pass makes no progress, derivation aborts with the collected errors — this is how circular dependencies surface.
- Single writer. Each field may be written by at most one deriver. Two derivers writing the same field is an error, so derived state has an unambiguous provenance.
- Ownership. A deriver may only write fields of its own instance. Writes to other instances are rejected. Reads are unrestricted — derivers routinely read from related instances (e.g. an
Interceptorderiving its initial position from itsinterceptorSite's coordinates). - Atomicity. A deriver that fails leaves its instance's fields untouched (writes are rolled back before retry).
Together these rules make derivation declarative in spirit: derivers state what a field is in terms of other state, and the framework finds a valid evaluation order — or proves there isn't one.
The Model Lifecycle
Constraints and derivation fit into a defined lifecycle between model authoring and simulation:
- Construct — instances are created with field values; values are coerced as they are set.
- Compose — instances are added to parents; sibling requirements are checked as the tree is assembled.
- Finalize — on the root of the tree:
- Derive runs the fixed-point derivation described above.
- Required fields that are still missing are reported as errors; remaining values are coerced.
- Validate runs all validators, hierarchically.
- Build — the finalized model is handed to the Simulation Compiler, which performs its own further checks (type/unit alignment, query resolution, feasibility).
Finalization happens automatically when a model is submitted for simulation, and can also be invoked directly to check a model without building anything.