PictModel
PictModel parses a complete PICT-format model — parameters, sub-models, and constraints — into a single object that can directly generate rows. It is exported from a separate entry point so it does not bloat the bundle of consumers who only need make.
CoverTable's model format is a superset of Microsoft PICT. It extends the original syntax with features such as arithmetic expressions ([A] * [B] > 100, ([A] + 1) * [B] <= 500), comment lines (#) in the constraint section, and the ~ negative-value prefix in output.
Models written for CoverTable may not run in the original PICT tool if they use these extensions. If you need compatibility with Microsoft PICT, avoid using arithmetic operators and other extended syntax in your constraints.
import { PictModel } from "covertable/pict";
const model = new PictModel(`
Type: Single, Span, Stripe, Mirror, RAID-5
Size: 10, 100, 500, 1000, 5000, 10000, 40000
Format method: Quick, Slow
File system: FAT, FAT32, NTFS
Cluster size: 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536
Compression: On, Off
IF [File system] = "FAT" THEN [Size] <= 4096;
IF [File system] = "FAT32" THEN [Size] <= 32000;
`);
const rows = model.make();
Constructor
new PictModel(input: string, options?: PictModelOptions)
| Option | Type | Default | Description |
|---|---|---|---|
caseInsensitive | boolean | true | When true (default, matching PICT), constraint comparisons and alias lookups ignore case. |
strict | boolean | false | When true, the constructor throws PictModelError if any error-severity issue is collected. |
The input string is split into three sections:
- Parameters —
Name: value1, value2, ... - Sub-models —
{ A, B, C } @ N - Constraints —
IF [P] = "x" THEN [Q] = "y";or unconditional[P] <> [Q];
Properties
| Property | Type | Description |
|---|---|---|
parameters | Record<string, (string | number)[]> | Parsed parameter values (canonical form, with aliases removed). |
subModels | SubModelType[] | Parsed sub-model definitions. |
constraints | (FilterType | null)[] | Parsed constraint filters. null entries indicate parse failure. |
negatives | Map<string, Set<string | number>> | Values prefixed with ~ (invalid / negative-test values) per parameter. |
weights | Record<string, Record<number, number>> | Weights extracted from (N) syntax. |
issues | PictModelIssue[] | All issues collected during parsing. |
progress | number | Coverage progress (0 to 1) during generation. |
stats | ControllerStats | null | Generation statistics (available after make/makeAsync). |
Issues
interface PictModelIssue {
severity: "error" | "warning";
source: "factor" | "subModel" | "constraint";
index: number; // 0-based index within the source
line: number; // 1-based line number in the original input
message: string;
}
const model = new PictModel(`
A: 1, 2
Empty:
`);
model.issues;
// [{ severity: "error", source: "factor", index: 1, line: 3,
// message: 'No values for parameter "Empty"' }]
Methods
| Method | Description |
|---|---|
filter(row) | Returns true if the row satisfies all constraints and has at most one invalid value. |
make(options?) | Generates rows with the model's parameters, constraints, sub-models, and weights. Accepts the same options as the top-level make. |
makeAsync(options?) | Generator version of make. |
Supported PICT Syntax
| Feature | Example | Notes |
|---|---|---|
| Parameter | Type: Single, Span, Stripe | Basic parameter declaration. |
| Comment | # this is a comment | Lines starting with # are skipped. |
| Quoted strings | Msg: "hello, world" | Required for values containing commas. |
| Parameter reference | B: <A>, extra | Expands to all of parameter A's values. |
| Aliases | OS: Windows | Win, Linux | First value is canonical. Aliases work in constraints. |
| Invalid values | Type: Valid, ~Invalid | Negative-test value. At most one per row. Output includes ~ prefix. |
| Weights | Browser: Chrome (10), Firefox | Biases value selection during completion. |
| Sub-models | { A, B, C } @ 3 | Different N-wise strength for listed parameters. |
| Conditional | IF [A] = 1 THEN [B] = 2; | Standard PICT-style conditional constraints. |
| Unconditional | [A] <> [B]; | Always-applied invariants. |
| Comparisons | =, <>, >, <, >=, <=, IN, LIKE | All standard PICT operators. |
| Logical | AND, OR, NOT | With parentheses for grouping. |
Invalid Values (~)
When a value is prefixed with ~, it is treated as a value that should appear in tests only one at a time. PictModel automatically rejects rows containing two or more invalid values. The ~ prefix is preserved in output rows.
const model = new PictModel(`
Age: 20, 30, ~-1, ~999
Country: Japan, USA, ~"Mars"
`);
const rows = model.make();
// { Age: 20, Country: "Japan" } — both valid
// { Age: "~-1", Country: "Japan" } — Age is invalid, Country is valid
// { Age: 20, Country: "~Mars" } — Age is valid, Country is invalid
// { Age: "~-1", Country: "~Mars" } — REJECTED (two invalid values)
Aliases
Aliases provide alternative names for the same value. The first name is canonical (appears in output), while alternates are accepted in constraints.
const model = new PictModel(`
OS: "Windows 10" | Win10, "Mac OS" | Mac, Linux
Result: pass, fail
IF [OS] IN {"Win10", "Mac"} THEN [Result] = "pass";
`);
weightsByValue
A helper that converts a value-keyed weight map into the index-keyed form expected by weights.
import { make } from "covertable";
import { weightsByValue } from "covertable/pict";
const factors = {
Browser: ["Chrome", "Firefox", "Safari"],
};
make(factors, {
weights: weightsByValue(factors, {
Browser: { Chrome: 10, Safari: 5 },
}),
// equivalent to: weights: { Browser: { 0: 10, 2: 5 } }
});
Try the Compatible PICT tool to experiment with the constraint syntax in your browser.
Differences from the Original PICT
CoverTable's PictModel accepts PICT-format input, but the underlying engine works differently from the original PICT tool developed by Microsoft.
Generation Algorithm
| Original PICT | CoverTable | |
|---|---|---|
| Greedy strategy | Picks the combination with the most uncovered (OPEN) slots in a bitvector, then selects a value that maximizes globally covered pairs | Scores each uncovered pair by how many other uncovered pairs it would cover when added to the current row |
| Value selection | PickValue() maximizes complete (fully-bound combinations) then totalZeros (global coverage) | Greedy selects full pairs; remaining factors are filled by close() using depth-first backtracking |
| Tie-breaking | rand() with a configurable seed | Deterministic hash (FNV1a32) of pair indices + salt string |
Constraint Processing
| Original PICT | CoverTable | |
|---|---|---|
| Approach | Pre-compiles constraints into explicit exclusion sets before generation | Evaluates constraints at runtime using three-valued (Kleene) logic |
| Internal form | IF [A]="x" THEN [B]<>"y" → exclusion {A:"x", B:"y"} | Constraints remain as expressions and are evaluated per candidate |
| AND | Cross-product of exclusion sets (can grow exponentially) | Short-circuit evaluation; returns False as soon as any branch fails |
| Propagation | ExclusionDeriver derives implicit exclusions before generation | forwardCheck prunes domains dynamically (AC-3-style arc consistency) |
| Backtracking | Exclusions are baked into a bitvector; no backtracking needed | Depth-first backtracking fills remaining factors when greedy selection stalls |
| Flexibility | DSL only (IF/THEN/ELSE, comparisons, IN, LIKE) | Supports arbitrary lambda predicates via the custom operator |
Seeding / Presets
| Original PICT | CoverTable | |
|---|---|---|
| API | PictAddSeed() / CLI -e seedfile | presets option (list of partial-row dicts/objects) |
| Format | Array of (parameter, value_index) tuples | Value dictionaries — e.g. { OS: "Win", Browser: "Chrome" } |
| Conflict handling | Seeds violating exclusions are silently removed by fixRowSeeds() | Presets are processed first; close() fills remaining factors respecting constraints |
Sub-models
| Original PICT | CoverTable | |
|---|---|---|
| Structure | Fully hierarchical — sub-models are independent Model instances combined via pseudo-parameters | Flat key-set partitioning with per-set strength |
| Nesting | Sub-models can be nested arbitrarily | Single level only |
| Overlap | Parameters can appear in multiple sub-models; conflicts resolved by exclusion generation | Each parameter belongs to at most one sub-model |
Weights
| Original PICT | CoverTable | |
|---|---|---|
| Syntax | value (weight) in model file / valueWeights[] in API | weights option — { factor: { valueIndex: weight } } |
| Effect | Influences tie-breaking in PickValue() and random-row generation | Influences only the close() (backtracking completion) phase — higher-weight values are tried first |
Randomization and Determinism
| Original PICT | CoverTable | |
|---|---|---|
| Default | Deterministic (seed 0) | Deterministic (hash sorter with empty salt) |
| Randomize | -r [seed] flag; uses srand()/rand() | Change salt for reproducible variation; use random sorter for non-deterministic output |
Output and Streaming
| Original PICT | CoverTable | |
|---|---|---|
| Output | Batch only — all rows generated before any are returned (TSV or JSON from CLI; index array from C API) | Generator-based — rows yielded incrementally via makeAsync() / make_async() |
| Format | Tab-separated table or JSON array of objects | Python list/dict rows; TypeScript typed arrays/objects with full type inference |
Platform
| Original PICT | CoverTable | |
|---|---|---|
| Language | C++ (with C-compatible API via pictapi.h) | Pure Python 3 + pure TypeScript — no native code, no compilation |
| Runtime | Compiled binary; Windows, Linux, macOS | Python: pip install; TypeScript: npm install; also runs in browsers |