Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Model package

A model package is what taktwerk runs: compiled code and a declaration of its interface. It comes in one of two forms, both read by the same engine with the same semantics:

formkindwhat it isdetail
FMU"fmi"an FMI 3 (or FMI 2) co-simulation FMU, as an .fmu archive or an extracted directoryFMI 3
raw"raw"a directory with a C shared library per architecture and a descriptor, taktwerk-model.tomlHand-written C, the descriptor

taktwerk defines no ABI of its own: an FMU follows the FMI standard, a raw library keeps its C interface and the descriptor says how to call it. New C code follows the recommended shape, which the importer reads without review.

Files and identity

<package>/                        a raw package
  taktwerk-model.toml             the descriptor
  lib/x86_64/<name>.so            one library per architecture: x86_64, aarch64, arm
  lib/aarch64/<name>.so
  lib/arm/<name>.so               32-bit hard-float ARM

<model>.fmu                       an FMU: modelDescription.xml, binaries/<platform>/, resources/

A package is identified by its content: the SHA-256 over the files that make it (a raw package’s descriptor and lib/; an FMU archive; an extracted FMU’s modelDescription.xml, binaries/ and resources/), as run.json records it (see run.json) and a campaign’s spec hash covers it. Other files in the directory (sources, build scripts, records, campaigns) are not part of the package. taktwerk reads no version number of a package and the descriptor carries no format number; a descriptor key taktwerk does not know is refused, not ignored.

What a package declares

  • A name, informational.

  • Dimensions: named sizes with an optional min, max and default. An FMU declares them as structural parameters, a descriptor as [[dimensions]]. Every instance binds each one; a dimension without a default must be bound by the project.

  • Variables, each with a name (unique in the model), a causality, an element type, a shape and a layout, and optionally a unit and a description:

    causalitythe enginethe model
    inputwrites it before every stepreads it
    outputreads it after every stepwrites it every step
    parametersets it once, before initreads it; never changes for the instance
    tunablesets it before init, and again before the next step after a changereads it every step
  • Element types: f64, f32, i64, i32, i16, i8, u64, u32, u16, u8, bool, and text (below).

  • Shape: empty for a scalar, else one entry per axis: a dimension name or a literal length ([n], [nx, nx], [3]). Layout: row-major or column-major for more than one axis; FMI 3 arrays are row-major.

  • Instances: multiple when every instance keeps its own state (behind a handle, or in structs the engine owns per instance); single when the library keeps state in globals, so each further instance loads a private copy of the library. An FMU declares single with canBeInstantiatedOnlyOncePerProcess.

Conventions

  • Text is a u8 variable of one literal length, the capacity with the terminating NUL included. The image holds bytes; connectors, check, inspect and the TUI show a string, and a connector refuses text that does not fit. An FMU’s String is text of 256 bytes or the capacity a taktwerk annotation gives; a raw char * member of literal length is text.
  • Start values: an input, parameter or tunable starts at its declared start when nothing else sets it: an FMU’s start, a descriptor’s start, zero for a raw variable without one. The project’s parameters win over it, a scenario’s overrides over those, --set over everything. After init the image holds every tunable’s value from the model.
  • Ranges: a numeric tunable may declare the values it admits (an FMU’s min and max, a descriptor’s range); the image refuses a write outside it and logs the refusal. A project may replace it per instance.
  • Seeds: a model that draws random numbers takes its seed through a numeric scalar parameter or tunable named seed. A run with --seed N gives instance id the value splitmix64(N xor fnv1a(id)), reduced to what the variable holds exactly; a project names another variable with seed_var. No annotation is needed. See Seeds.
  • Time: a step receives the engine time it advances from and advances by the instance’s step size, every × tick_ms. After skipped ticks the next step’s time jumps; a model that integrates reads the gap from time (see Stale inputs).
  • Status: the engine does not interpret a model’s outputs. A package that can produce outputs a consumer must not use should say so in an integer status output, one bit per reason, so a connector’s write gate can hold them. See Status and the write gate for the recommended bits.

Calling contract

  • Lifecycle per instance: load (the library, or the FMU’s binary for the running platform), instantiate with the bound dimensions and parameters, init once with the instance’s inputs and tunables, one step per period, terminate once, also after an error. Instances init and step in the project’s declared order.
  • Threads: a raw library’s init, steps and terminate, and an FMU’s initialization, steps and termination, are called on the engine’s cycle thread, after its real-time settings apply. Loading and an FMU’s instantiation happen before.
  • Memory: the engine owns every buffer the model exchanges, sizes it at init from the bound dimensions and keeps its address for the instance’s lifetime, so init may keep pointers. A library may allocate at init, never per step; no blocking I/O in a step.
  • Errors: a raw call that returns a code outside ok_codes (default 0), or an FMU status other than OK and Warning (or a step that asks to terminate), fails the call. By default that stops the engine (fail-stop) and the service manager restarts it; an instance with on_error = "hold" or "safe" is held instead while the others step, and may be re-initialised (Faults). A step that cannot continue fails; a step that can continue but whose outputs are not valid succeeds and says so in its status.
  • Sizes: the project is the size authority. A raw library that reports its own lengths has them checked after init and every step, never adopted (reported lengths).

Checking a package

taktwerk inspect <package> --kind raw     # or <model>.fmu --kind fmi
taktwerk new <package> --kind raw -o try.toml
taktwerk check try.toml

inspect prints the interface exactly as the engine reads it. Testing a package goes on from there.