TabularSink

class TabularSink(provenance: RunProvenance, path: Path) : TransitionSink(source)

A durable TransitionSink: one flat table of transitions, written through KSL's TabularOutputFile, plus the provenance that makes the table readable by something that has never seen the model.

Two files, and the second one is not optional

A transition is positional. a_Mode = 2.0 means nothing without the declaration saying that lever is CATEGORICAL over ["slow", "normal", "fast"], that it is a SETTING rather than a TRANSACTION — which is a fact about the dynamics, not decoration — and where its bounds are. Naming columns from the descriptor carries position-to-name and nothing else. So this sink writes both:

  • <name>.sqlite — the rows. It is an ordinary SQLite database (that is what a TabularOutputFile is), so it is queryable with any SQL tool and readable from Python.

  • <name>.provenance.json — the RunProvenance: model, experiment, element, policy label, and the full descriptor.

TrajectoryFile pairs them on read and refuses a trajectory whose provenance is missing, rather than guessing. Guessing is how a categorical index quietly becomes a continuous one.

Column names are sanitised, and that is not defensive

TabularOutputFile interpolates column names straight into CREATE TABLE. Measured: a : or a - in a name fails the create outright, and a space is worse — SQLite parses s_L Queue as a column s_L of type Queue, so the file is created with a silently wrong column. KSL element names are colon-qualified by convention (Room:Position), so every realistic name hits this. Names are therefore reduced to [A-Za-z0-9_], and a collision produced by that reduction is refused at construction with both original names in the message.

The schema

For n observations and m levers: 10 + 2n + 3m columns.

ColumnTypeMeaning
element, rep, epochTEXT, NUMERIC, NUMERICwhich element, replication and decision
time, tauNUMERICthe successor's time, and the interval this row covers
s_*, sp_*NUMERICstate and successor state, one column per declared observation
a_*NUMERICthe action applied
p_*NUMERICthe action proposed. Always written; equal to a_* when nothing was repaired
repairedNUMERIC1 when the rule's request was repaired, so p_* differs from a_*
unavail_*NUMERIC1 when that lever's feasible set was empty and it took its neutral
rewardNUMERICalready sign-normalised: larger is better, because COST is negated once at declaration
terminated, truncatedNUMERIC0/1, and deliberately separate — a learner that bootstraps from the last row needs to know which it got
sourceTEXTwhy the episode ended; "" when it had not

Nulls are encoded rather than stored. Row.setNumeric takes a non-null Double and DataType has only NUMERIC and TEXT, so the three nullable fields of a TransitionRecord need a convention. proposedAction and leverUnavailable are always written, with repaired saying whether the proposal is news; source is "" for absent. NaN was rejected as the null marker: a NaN in training data is a trap that surfaces three steps later inside somebody's learner.

Constructors

Link copied to clipboard
constructor(provenance: RunProvenance, path: Path)

Types

Link copied to clipboard
object Companion

Properties

Link copied to clipboard

The provenance beside it.

Link copied to clipboard

The trajectory file.

Link copied to clipboard

How many rows this sink has taken.

Functions

Link copied to clipboard
open override fun close()
Link copied to clipboard
open override fun write(record: TransitionRecord)