galata. GitHub ↗

Executable model files

The experimental galata.model.v1 format describes the continuous-scalar.v1 and continuous-linear.v1 block profiles. It is a signal-flow model, distinct from study YAML. The runnable continuous feedback example contains all five block types and a complete study. The compiler architecture and limits are specified in ADR-0010. The typed linear extension and state-space lowering are specified in ADR-0013 and exercised by the NT-33A graph example.

Every root key is required: schema, profile, blocks, connections. Every block requires id, kind and output. Unknown keys, missing values, duplicate keys/IDs, unsupported versions, aliases, anchors, nonfinite numbers and ambiguous port bindings are errors. There are no implicit clocks, units, block parameters, layout fields or external references.

output is a closed map with dimension and frame. Dimensions are eight integer exponents in the order length, mass, time, current, temperature, amount, luminous intensity, angle. Each exponent lies in [-16,16]. Values use canonical SI units; angle uses radians and is distinguished from dimensionless signals. Frames are none, body or ned, compared exactly without a transformation.

Kind Additional required fields Inputs
constant value: finite binary64 number in the output's units None
gain coefficient: {value: number, dimension: [eight exponents]} One; input dimensions plus coefficient dimensions equal output dimensions
sum signs: [1, -1, ...] One per sign, in port order; all match the output type
integrator initial_value: finite number in the output's units One derivative input, output units divided by seconds
output None One input matching the declared output type
linear_combination terms: [{input: {dimension, frame}, coefficient: {value, dimension}}, ...] One per ordered term; each incoming signal matches its declared input type; available only in continuous-linear.v1

The linear profile admits all five scalar kinds with their existing semantics. It additionally permits explicit matrix row couplings between declared input and output frames. Coefficient dimensions plus each term's input dimensions must equal the output dimensions. These declarations do not compute a frame rotation or infer coordinate conventions from signal names. Ordinary Gain, Sum, Integrator and Output frame checks are unchanged in both profiles.

For example, this block fragment declares an acceleration row from a body velocity and a scalar angle. It needs two connections to ports 0 and 1:

id: acceleration
kind: linear_combination
output: {dimension: [1,0,-2,0,0,0,0,0], frame: body}
terms:
  - input: {dimension: [1,0,-1,0,0,0,0,0], frame: body}
    coefficient: {value: -2, dimension: [0,0,-1,0,0,0,0,0]}
  - input: {dimension: [0,0,0,0,0,0,0,1], frame: none}
    coefficient: {value: 3, dimension: [1,0,-2,0,0,0,0,-1]}

Every nested row map is closed. A row contains 1–64 terms, evaluates each product and then accumulates from positive zero in port order. Products and every intermediate sum must remain finite. A zero coefficient still requires its input and remains a direct dependency in an authored model. Ordered term types, coefficient bits and connections are part of semantic identity. The old scalar profile refuses this block kind and retains its existing canonical-byte contract.

A connection is {source: block_id, target: block_id, input: index}. The source has one output; input is a zero-based integer target port. IDs match [A-Za-z_][A-Za-z0-9_-]{0,63}. Every input is connected exactly once. A producer may feed several ports. Sum signs and port assignments determine a fixed left-to-right reduction; changing declaration order does not change it.

Numerical fields use finite decimal syntax and locale-independent conversion with nearest-even rounding. Conversion preserves the caller's floating-point rounding mode and exception flags; representable subnormals are supported, while nonzero decimal values that round to zero are refused. The C++ serializer emits enough digits for exact binary64 round-trip, including signed zero and subnormal values. Canonical semantic bytes use exact parameter bits rather than printed decimal text. Comments, whitespace and declaration ordering can change raw-source identity while leaving model identity unchanged. Unknown executable fields are refused rather than omitted from the digest. Compilation requires the canonical YAML representation to fit the same source byte cap. A very compact input can therefore parse successfully and still be refused at compilation; required evidence must be serializable before execution.

Headless study interface

version: 1
stages:
  - id: model
    capability: model.compile
    input: {path: model.yaml}
  - id: response
    capability: sim.model
    input:
      model: {from: model}
      step_s: 0.125
      steps: 8
      sample_stride: 3
      csv_path: response.csv
      evidence_path: evidence.json

initial_time_s defaults to zero and sample_stride defaults to one. Step size, integer step count and both distinct output paths are required. The paths obey the normal study input/output containment, snapshot and overwrite rules. A successful run retains the initial sample, every stride boundary and the final sample. The step defines numerical execution; it is not an error tolerance.

The CSV labels state and output columns by stable block ID. Their units/frames are in the canonical source embedded in galata.model-run.v1 evidence. That record also contains the semantic digest, ordered state/output/schedule IDs, solver settings, sample count, trajectory path/hash and four evidence fields: execution is completed; numerical accuracy, model validity and engineering acceptance are not_assessed. The existing run manifest binds this record and CSV to original input bytes and build/runtime identity.

An optional model.compile input context_path names a source-origin attachment, limited to 8 MiB. The file must have outer schema galata.project-origin.v1 and pass strict YAML structure checks. Its exact bytes are retained as a manifest input. The project workflow verifies the attachment's complete origin bindings; attaching it to a compiled model does not assign the original Jacobian diagnostics or controller validity to edited rows. Unsupported origin schema versions are refused.

Linear-system and controller adapter

model.linear_graph accepts exactly one upstream system: {from: ...} with kind linear_system or law: {from: ...} with kind control_law. The latter uses the LQR design's original plant and feedback matrix. Required inputs are:

Input Contract
channel_types Closed map of states, inputs, outputs, each an explicit array of {dimension, frame} in source matrix order
initial_state Finite numeric vector with one value per source state
command Finite constant-command vector with one value per source input
model_path Generated executable model YAML output, at most 1 MiB
adapter_path Distinct adapter JSON output, at most 2 MiB

All three channel counts must be between 1 and 16. No types are inferred from names or free-text units. The adapter materializes default C = I and D = 0, then lowers x_dot=A*x+B*u and y=C*x+D*u. A system uses u=command; an LQR law uses u=command-K*x. Command constants, state integrators, typed rows, plant outputs and actual-control outputs are editable graph blocks.

The deterministic linear-rows.v1 lowering omits only exact-zero matrix entries, including signed zero, before constructing graph ports. A row with no retained entries becomes a typed zero constant. This translation rule does not remove zero terms from an already authored graph. Coefficient dimensions are derived from the declared input and output types and must fit the existing exponent bounds. The resulting artifact is an executable_model, directly consumed by sim.model through the ordinary graph executor. It does not add sim.linear's eigenvalue step screening to the graph runtime.

The galata.linear-adapter.v1 record retains the lowering version, generated model path and semantic SHA-256, effective source A/B/C/D matrices and ordered names, description/citation/units, channel types, initial state, command and feedback gain. Its mapping arrays are state_ids, command_ids, control_ids, output_ids and control_output_ids, each in source channel order. feedback_gain is an empty array for an open-loop system. LQR imports also retain lqr_origin Q/R/N/K matrices and CARE solution, residual budget, residual, symmetry, conditioning, separation and closed-loop eigenvalues.

Use the mapping to locate state, plant-output and actual-control CSV columns; compiled output order follows block IDs. The enclosing pipeline manifest retains original source bytes and complete upstream linearization diagnostics. Those diagnostics and the LQR evidence describe source operations only. The adapter does not validate the aircraft, assess integration accuracy or certify that later graph edits preserve the original plant/controller.

Resource bounds and C++ API

Both profiles retain the shared limits: 1 MiB source, 1,024 blocks, 8,192 connections, 64 sum inputs, 64 linear-row terms, 1,000,000 integration steps, 1,000,000 recorded scalars including time, and 100,000,000 scheduled block evaluations. The compiler checks serializability before execution. Runtime budgets apply to the submitted graph and options, independent of its origin.

The C++ API provides parsing, serialization, canonical semantics, compilation, pure evaluation, fixed-step simulation and cooperative cancellation in model.hpp. Link against the installed galata::modeling CMake target. Experimental versioning does not promise a frozen C++ ABI or compatibility with third-party Simulink/SLX files. The pure state-space lowering API is in linear_adapter.hpp; it performs no file I/O and introduces no separate integration solver.