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:
| form | kind | what it is | detail |
|---|---|---|---|
| FMU | "fmi" | an FMI 3 (or FMI 2) co-simulation FMU, as an .fmu archive or an extracted directory | FMI 3 |
| raw | "raw" | a directory with a C shared library per architecture and a descriptor, taktwerk-model.toml | Hand-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,maxanddefault. An FMU declares them as structural parameters, a descriptor as[[dimensions]]. Every instance binds each one; a dimension without adefaultmust 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:
causality the engine the model inputwrites it before every step reads it outputreads it after every step writes it every step parametersets it once, before init reads it; never changes for the instance tunablesets it before init, and again before the next step after a change reads 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-majororcolumn-majorfor more than one axis; FMI 3 arrays are row-major. -
Instances:
multiplewhen every instance keeps its own state (behind a handle, or in structs the engine owns per instance);singlewhen the library keeps state in globals, so each further instance loads a private copy of the library. An FMU declaressinglewithcanBeInstantiatedOnlyOncePerProcess.
Conventions
- Text is a
u8variable of one literal length, the capacity with the terminating NUL included. The image holds bytes; connectors,check,inspectand the TUI show a string, and a connector refuses text that does not fit. An FMU’sStringis text of 256 bytes or the capacity ataktwerkannotation gives; a rawchar *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’sstart, zero for a raw variable without one. The project’sparameterswin over it, a scenario’s overrides over those,--setover 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
minandmax, a descriptor’srange); 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 Ngives instanceidthe valuesplitmix64(N xor fnv1a(id)), reduced to what the variable holds exactly; a project names another variable withseed_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 fromtime(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(default0), or an FMU status other thanOKandWarning(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 withon_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.