|
| 1 | +# KERS — Kernel/Elimination Result Sparse format |
| 2 | + |
| 3 | +KERS is a compact binary format for sparse integer matrices. |
| 4 | +It is used by PetriSpot both for **input** (incidence matrices) and **output** (invariant bases), |
| 5 | +enabling efficient program-to-program transfer without going through PNML or ASCII representations. |
| 6 | + |
| 7 | +## Design goals |
| 8 | + |
| 9 | +- Single format for input and output: the caller reads the result the same way it wrote the input. |
| 10 | +- Suitable for large models: 10⁵ × 10⁵ matrices with millions of non-zeros load in milliseconds. |
| 11 | +- No external dependencies: plain binary, no compression, no serialisation library. |
| 12 | +- Self-describing dimensions: a reader can allocate correctly before parsing any entries. |
| 13 | + |
| 14 | +## File structure |
| 15 | + |
| 16 | +All multi-byte integers are **little-endian**. |
| 17 | + |
| 18 | +### Header (16 bytes) |
| 19 | + |
| 20 | +| Offset | Size | Type | Description | |
| 21 | +|--------|------|--------|------------------------------------| |
| 22 | +| 0 | 4 | char[4]| Magic: `K` `E` `R` `S` (0x4B 0x45 0x52 0x53) | |
| 23 | +| 4 | 1 | uint8 | Version: `1` | |
| 24 | +| 5 | 1 | uint8 | Flags: reserved, must be `0` | |
| 25 | +| 6 | 4 | uint32 | `nrows` — number of rows | |
| 26 | +| 10 | 4 | uint32 | `ncols` — number of columns | |
| 27 | +| 14 | 2 | — | Padding (zero) | |
| 28 | + |
| 29 | +### Body — column entries |
| 30 | + |
| 31 | +Only non-empty columns are written, in **ascending column order**. |
| 32 | + |
| 33 | +For each non-empty column: |
| 34 | + |
| 35 | +| Size | Type | Description | |
| 36 | +|------|--------|------------------------------------------| |
| 37 | +| 4 | uint32 | Column index (0-based) | |
| 38 | +| 4 | uint32 | `nnz` — number of non-zero entries in this column | |
| 39 | +| 4×nnz | uint32 | Row index (0-based, repeated nnz times as `(row, value)` pairs — see below) | |
| 40 | + |
| 41 | +Each entry is a `(row_index, value)` pair: |
| 42 | + |
| 43 | +| Size | Type | Description | |
| 44 | +|------|--------|-------------------| |
| 45 | +| 4 | uint32 | Row index (0-based)| |
| 46 | +| 8 | int64 | Value (signed) | |
| 47 | + |
| 48 | +Row indices within a column are **sorted in ascending order**. |
| 49 | +This allows readers to use `append()` (amortised O(1)) rather than `put()` (O(log n)). |
| 50 | + |
| 51 | +### Terminator |
| 52 | + |
| 53 | +After the last column, a 4-byte sentinel marks end of data: |
| 54 | + |
| 55 | +| Size | Type | Value | |
| 56 | +|------|--------|--------------| |
| 57 | +| 4 | uint32 | `0xFFFFFFFF` | |
| 58 | + |
| 59 | +## Semantic conventions |
| 60 | + |
| 61 | +### Input matrix (incidence matrix) |
| 62 | + |
| 63 | +When used as input to PetriSpot via `--loadKERS`: |
| 64 | + |
| 65 | +- `nrows` = number of **variables** (places for P-flows, transitions for T-flows) |
| 66 | +- `ncols` = number of **constraints** (transitions for P-flows, places for T-flows) |
| 67 | +- The matrix is the **incidence matrix** C = flowTP − flowPT |
| 68 | +- For T-flows/T-semiflows, pass the same C; PetriSpot transposes internally |
| 69 | + |
| 70 | +### Output matrix (invariant basis) |
| 71 | + |
| 72 | +When written by PetriSpot via `--basisKERS`: |
| 73 | + |
| 74 | +- `nrows` = number of variables (same as input) |
| 75 | +- `ncols` = number of basis vectors (invariants) |
| 76 | +- Each column is one basis vector: a sparse integer vector in the null space of C |
| 77 | + |
| 78 | +No constant terms are stored (constants depend on the initial marking and are not part of the basis). |
| 79 | + |
| 80 | +## Usage with PetriSpot |
| 81 | + |
| 82 | +``` |
| 83 | +# Export incidence matrix of a Petri net in KERS format |
| 84 | +petri64 -i model.pnml --exportAsKERS=model.kers |
| 85 | +
|
| 86 | +# Compute P-semiflows from a KERS matrix, export result as KERS |
| 87 | +petri64 --loadKERS=model.kers --Psemiflows --basisKERS=basis.kers |
| 88 | +
|
| 89 | +# Equivalent: compute directly from PNML, export basis |
| 90 | +petri64 -i model.pnml --Psemiflows --basisKERS=basis.kers |
| 91 | +``` |
| 92 | + |
| 93 | +## kersconv — ASCII conversion utility |
| 94 | + |
| 95 | +`kersconv` is a small companion tool for inspecting and constructing KERS files without PetriSpot. |
| 96 | +It is built alongside the `petri*` binaries by the autoconf/automake build system. |
| 97 | + |
| 98 | +### Usage |
| 99 | + |
| 100 | +``` |
| 101 | +kersconv --decode <input.kers> [output.txt] # KERS binary → ASCII (stdout if no output file) |
| 102 | +kersconv --encode <input.txt> <output.kers> # ASCII → KERS binary |
| 103 | +``` |
| 104 | + |
| 105 | +### ASCII format |
| 106 | + |
| 107 | +The text representation is the same as `--exportAsMatrix`: |
| 108 | + |
| 109 | +``` |
| 110 | +<nrows> <ncols> |
| 111 | +<rowIdx> <colIdx>:<val> <colIdx>:<val> ... |
| 112 | +... |
| 113 | +``` |
| 114 | + |
| 115 | +- First line: matrix dimensions. |
| 116 | +- Each subsequent line encodes one **non-empty row**: the row index followed by space-separated `colIdx:value` pairs. |
| 117 | +- Empty rows are omitted. |
| 118 | +- Values are signed integers. |
| 119 | + |
| 120 | +### Example |
| 121 | + |
| 122 | +```sh |
| 123 | +# Inspect a basis file |
| 124 | +kersconv --decode basis.kers |
| 125 | + |
| 126 | +# Roundtrip: decode then re-encode |
| 127 | +kersconv --decode basis.kers basis.txt |
| 128 | +kersconv --encode basis.txt basis2.kers |
| 129 | +``` |
| 130 | + |
| 131 | +### Consistency with PNML-based output |
| 132 | + |
| 133 | +When comparing `--loadKERS` output to direct PNML computation, the invariant **coefficients and |
| 134 | +count are identical**. The following differences are expected and by design: |
| 135 | + |
| 136 | +| Property | PNML path | KERS path | |
| 137 | +|---|---|---| |
| 138 | +| Variable names | Real place/transition names from PNML | `x0`, `x1`, … (no name table in binary format) | |
| 139 | +| RHS constant | Dot product with initial marking (e.g., `= 1`) | Always `0` (no marking stored in KERS) | |
| 140 | +| Log lines | Includes parsing and model info | Only computation lines | |
| 141 | +| "Computed" line | `Computed N P flows in T ms.` | `Computed N P flows in T ms.` (same) | |
| 142 | + |
| 143 | +For pure **flows** (as opposed to semiflows), `= 0` on the RHS is mathematically correct regardless. |
| 144 | +For **semiflows**, the PNML path annotates each invariant with the conserved quantity value |
| 145 | +(the invariant dotted with the initial marking), which is unavailable in the KERS path. |
| 146 | + |
| 147 | +## Size estimate |
| 148 | + |
| 149 | +For a matrix with `N` non-zero entries: |
| 150 | +- Header: 16 bytes |
| 151 | +- Per non-empty column header: 8 bytes |
| 152 | +- Per entry: 12 bytes (4 row + 8 value) |
| 153 | +- Terminator: 4 bytes |
| 154 | + |
| 155 | +Total ≈ 16 + 8×ncols_nonempty + 12×N bytes. |
| 156 | + |
| 157 | +For N = 10⁶ entries: approximately **12 MB**. |
| 158 | + |
| 159 | +## Overflow behaviour |
| 160 | + |
| 161 | +PetriSpot ships three binaries: `petri32` (int), `petri64` (long), `petri128` (long long). |
| 162 | +All three read and write values as `int64` in the file. |
| 163 | +When reading into a narrower type (e.g. `petri32`), values outside `[-2³¹, 2³¹-1]` trigger |
| 164 | +a warning on stderr and are truncated. Use `petri64` or `petri128` for large coefficients. |
0 commit comments