A TypeScript/JavaScript implementation of the Comparison Framework - a framework for processing measurement data.
Use git.
The best way to learn how the library works is by looking at the examples. They are very well documented, start simple, and cover most use cases.
There is also a work-in-progress spec, in the docs folder.
The library uses idiomatic TypeScript OOP. Many concepts are expressed as classes and generics are used to keep types precise.
Opaque types aren’t a native TS feature, so the library uses brands to encode intent at compile‑time:
CFUint32is runtime‑numberbut intended to be a 32‑bit unsigned safe integer.- Callers enforce this by validation (e.g.,
isUint32(x)) or explicit casts where appropriate.
This reduces runtime guards in the core and encourages callers to make intent explicit.
Bare JS numbers are wrapped in the CFReal brand and operated on by CFAlgebraReal:
- All values must satisfy
Number.isFinite(x). - Relations (equality, ≤, …) are approximate with configurable precision.
- Algebraic ops that yield invalid results (NaN, ±∞, undefined intervals) produce
undefined.
While the value algebra is generic, the reference implementation uses intervals for performance and clarity. Intervals and their algebra is defined in CFValueAlgebraIval.
Unit functions (including comparison functions) map (...units, s) to a value. They are composed into expression trees rather than eagerly evaluated. Algebraic nodes (e.g., arith, tensor) point to children and the operator, so a call to .get(u, s) computes a value on demand. When appropriate, .materialize() flattens a tree to a storage‑backed structure for faster repeated queries.
Basic unit function (leaf) types come in three flavors: const, dense, and sparse.
- Has a dimension,
dim, which is the number of unit arguments. - Returns the same value for all inputs, stored in its
valuefield.
-
Stores a linearized array
values: CFIval[]. -
Each entry corresponds to a tuple
(u₀, u₁, …, u_{dim−1}, s). -
With
Uunits and dimensiondim, the index is:index = s*U^dim + u0*U^(dim-1) + u1*U^(dim-2) + ... + u_{dim-2}*U + u_{dim-1}
- Backed by a compact bitset and row pointers.
- For a tuple (excluding the last unit index):
idx = s*U^(dim-1) + u0*U^(dim-2) + ... + u_{dim-3}*U + u_{dim-2} row = idx * wordsPerRow wordsPerRow = ceil(U / 32) word = floor(u_{dim-1} / 32) bit = u_{dim-1} % 32 - A bit value of 1 means a non‑null value is present in
values; 0 means absent.
This invariant is preserved through all bitset transforms. rowPtr[i + 1] - rowPtr[i]equals the rank(i) (count of set bits up to row end).
Operations create nodes rather than new tables. Example:
const ufSum = fu.add(fv);
// Later:
const v = ufSum.get(u, s); // computes fu.get(u, s) + fv.get(u, s)When a subgraph is compact enough or repeatedly queried, call:
const flat = ufSum.materialize(); // produces a storage-backed UF (e.g., sparse)flat.get becomes a direct lookup with no per‑call arithmetic.
All values (intervals) and unit functions are immutable.
- ✅ Safety & predictability.
⚠️ Overhead: more allocations and GC churn vs. in‑place mutation.
The overhead is considered acceptable for an educational library.
Run examples directly from the repo root:
npx ts-node examples/e1_measuring_a_plank.tsBrowse the examples to see:
- Available functions and their signatures
- How to build and compose unit functions
- How branded types and algebras interact
- Practical applications of the Comparison Framework
MIT. See LICENSE.txt file.