Runnable examples and toy applications

Install the package first. Each program below generates its own data, uses a fixed seed where randomness is involved, runs without network access and prints numerical results. Run the programs from the repository root:

python example/toy_models.py
python example/export_codebook.py
python example/tolerance_sweep.py
python example/shared_codebook.py

The CI executes these programs against an installed wheel outside the source tree. This also checks that examples use packaged public APIs.

Five toy signals

The constant and linear signals exercise zero-variance grouping. The sine wave illustrates repeated patterns; the step exercises abrupt changes; the noisy sine illustrates the tradeoff between detail and compactness. Each row reports sample count, symbol count, alphabet size and reconstruction RMSE. These counts are representation sizes, not measured byte compression ratios.

"""Five reproducible signals: run `python example/toy_models.py` after installation."""
import numpy as np
from fABBA import fABBA


def main():
    t = np.linspace(0, 4 * np.pi, 240)
    rng = np.random.default_rng(42)
    signals = {
        "constant": np.full(t.size, 3.0),
        "linear trend": 2 + 0.2 * t,
        "periodic": np.sin(t),
        "step": np.where(t < 2 * np.pi, 0.0, 1.0),
        "noisy periodic": np.sin(t) + 0.05 * rng.normal(size=t.size),
    }
    print(f"{'signal':18s} {'samples':>7s} {'symbols':>7s} {'alphabet':>8s} {'RMSE':>10s}")
    for name, x in signals.items():
        model = fABBA(tol=0.001, alpha=0.05, verbose=0)
        symbols = model.fit_transform(x)
        y = np.asarray(model.inverse_transform(symbols, start=x[0]))
        assert y.shape == x.shape
        rmse = np.sqrt(np.mean((x - y) ** 2))
        print(f"{name:18s} {len(x):7d} {len(symbols):7d} {len(model.parameters.centers):8d} {rmse:10.5f}")
        print("  symbols:", symbols[:80])
        # Rows of centers are [segment length, increment], in original units.
        assert np.isfinite(model.parameters.centers).all()


if __name__ == "__main__":
    main()

Parameter sweep

This program reports polygonal and symbolic errors separately. Its final row checks the zero-tolerance limit. Do not infer a universal monotonic relationship from this single signal; see Parameters, units and error interpretation and Testing, builds and numerical contracts.

"""Measure compression-only and full-pipeline errors independently."""
import numpy as np
from fABBA import fABBA, inverse_compress


def main():
    x = np.sin(np.linspace(0, 12, 300)) + 0.1 * np.cos(np.linspace(0, 37, 300))
    print("tol       alpha     pieces  alphabet  polygon RMSE  symbolic RMSE")
    for tol, alpha in [(0.1, 0.5), (0.01, 0.1), (0.001, 0.01), (0.0, 0.0)]:
        model = fABBA(tol=tol, alpha=alpha, verbose=0)
        symbols = model.fit_transform(x)
        polygon = np.asarray(inverse_compress(model.pieces_, start=x[0]))
        decoded = np.asarray(model.inverse_transform(symbols, start=x[0]))
        polygon_rmse = np.sqrt(np.mean((polygon - x) ** 2))
        symbolic_rmse = np.sqrt(np.mean((decoded - x) ** 2))
        print(f"{tol:<9g} {alpha:<9g} {len(symbols):6d} {len(model.parameters.centers):9d} "
              f"{polygon_rmse:13.6g} {symbolic_rmse:14.6g}")
        if tol == 0:
            np.testing.assert_allclose(decoded, x, atol=1e-12)
    # Greedy segmentation and clustering can change memberships abruptly.
    # Do not assume monotonically decreasing end-to-end RMSE at every step.


if __name__ == "__main__":
    main()

Shared-codebook classification

The following toy application distinguishes low and high frequencies using normalized symbol counts and nearest-neighbor classification. It fits the codebook on the training data only. The same character then has the same meaning in both training and test features. Histograms discard ordering, so this is a workflow example, not an evaluated classifier or anomaly detector.

"""Toy classification using a JABBA codebook fitted only on training signals."""
import numpy as np
from fABBA import JABBA


def main():
    t = np.linspace(0, 4 * np.pi, 180)
    train = np.asarray([np.sin(t), np.sin(t + 0.15), np.sin(3*t), np.sin(3*t + 0.15)])
    labels = np.array([0, 0, 1, 1])  # low versus high frequency
    test = np.asarray([np.sin(t + 0.08), np.sin(3*t + 0.08)])
    model = JABBA(tol=0.001, alpha=0.1, verbose=0, random_state=42)
    train_symbols = model.fit_transform(train, n_jobs=1)
    centers_before = model.parameters.centers.copy()
    test_symbols, test_starts = model.transform(test, n_jobs=1)
    np.testing.assert_array_equal(centers_before, model.parameters.centers)
    alphabet = list(model.parameters.alphabets)

    def histogram(symbols):
        counts = np.array([list(symbols).count(symbol) for symbol in alphabet], dtype=float)
        return counts / counts.sum()

    features = np.asarray([histogram(s) for s in train_symbols])
    queries = np.asarray([histogram(s) for s in test_symbols])
    distances = np.linalg.norm(queries[:, None, :] - features[None, :, :], axis=2)
    predictions = labels[distances.argmin(axis=1)]
    reconstructed = model.inverse_transform(test_symbols, start_set=test_starts, n_jobs=1)
    print("Predicted frequency classes:", predictions.tolist())
    print("Expected toy classes:      ", [0, 1])
    print("Shared alphabet size:", len(alphabet))
    print("Decoded lengths:", [len(row) for row in reconstructed])
    assert np.isfinite(features).all()
    # Symbol histograms ignore ordering. This illustrates an API workflow,
    # not a validated classifier or a claim about real-world performance.


if __name__ == "__main__":
    main()

For exporting results to another process, see the complete JSON example in Inspect, export and restore a codebook. Historical notebooks under exp/ are research materials and may require external datasets; they are separate from this tested gallery.