API

SymbolicLongMemorySequences.jl has two public construction paths:

  • use make_generator for standard, named cases across all implemented methods;
  • compose a LatentSource with a Symbolizer for property-based studies that need to vary the numerical driver and the symbolization rule separately;
  • use the method-specific constructors when a study needs full control of the scientific parameters.

Both paths return an LRDGenerator and both use the same generation call:

seq = generate(g, n; rng)

Always pass an explicit rng when a sequence needs to be reproducible.

Property-based generators also support an additive validation helper:

seq, latent = generate_with_latent(g, n; rng)

latent is a width × n numerical matrix containing the LRD driver before symbolization. Use this when a study needs to diagnose the numerical source and the symbolic transform separately.

Standard Method Factory

The factory is the cleanest way to list methods, inspect their standard parameters, and build a generator for examples, smoke tests, and benchmark grids.

using SymbolicLongMemorySequences, StableRNGs

alphabet = [:a, :b, :c]
g = make_generator(:PB1, alphabet; H = 0.8, marginal = [0.2, 0.3, 0.5])
seq = generate(g, 8; rng = StableRNG(42))

Example output:

8-element Vector{Symbol}:
 :a
 :c
 :b
 :c
 :c
 :a
 :c
 :b

id may be a stable method identifier, a string, or an exported type-name alias:

make_generator(:PB1, alphabet)
make_generator("MB1c", alphabet)
make_generator(:SpectralFGN, alphabet)

Use method_ids and method_info to discover supported methods:

method_ids()
method_ids(family = :model_based)
method_info(:MB5).defaults
method_parameters(:MB5)
method_info(:SpectralFGN).id

The entries returned by method_parameters(id) describe the factory keywords accepted by that method:

[(p.name, p.default, p.domain) for p in method_parameters(:PB1)]

Example output:

2-element Vector{Tuple{Symbol, Any, String}}:
 (:H, 0.8, "0.5 < H < 1")
 (:marginal, :uniform, "`:uniform` or a probability vector with one entry per symbol")

All factory methods also require the positional alphabet input. The generated sequence length n is supplied later through generate(g, n; rng).

Factory defaults are standard examples, not finite-sample scientific calibrations. For validation studies that support method-specific claims, prefer explicit constructors so the parameter assumptions remain visible in the script.

Method IDs

Model taxonomy tree

The stable method IDs follow the taxonomy above. :MB4 is kept as a convenient alias for the recommended MB4b SelfExcitingMass standard case; use :MB4a or :MB4c when the normalized Hawkes baseline or logit-contrast variant is needed explicitly.

IDTypeStandard cases
:PB1SpectralFGN:standard
:PB2LGCM:standard
:PB3WaveletMarkov:persistent_regimes, :iid_regimes
:PB4IntermittentMapSymbols:standard
:MB1aLAMP:repeat, :iid
:MB1bDyadicLAMP:repeat, :iid
:MB1cCalibratedAdditiveMarkov:standard, :iid
:MB2OnOffMarkov:persistent_regimes, :iid_regimes
:MB3FSS:standard
:MB4SelfExcitingMass:identity_excitation
:MB4aHawkesSymbol:identity_excitation
:MB4bSelfExcitingMass:identity_excitation
:MB4cLogitSelfExcitingMass:identity_excitation
:MB5DuplicationMutation:standard

Common keywords are shared where they mean the same thing:

  • marginal = :uniform sets or approximates a target marginal when a method has a meaningful marginal-control knob;
  • case selects one of the standard cases listed above;
  • H, beta, alpha, or z select the method's nominal LRD-related parameter;
  • d sets an explicit finite history cutoff for history-based methods.

Composable Property-Based API

Property-based generators can be described as:

PropertyBasedGenerator(source, symbolizer)

The source creates one or more numerical latent LRD series. The symbolizer turns those latent values into categorical symbols. This split makes the PB taxonomy explicit: PB1, PB2, PB3, and PB4 are standard combinations rather than unrelated mechanisms.

using SymbolicLongMemorySequences, StableRNGs

source = SpectralFGNSource(0.8)
symbolizer = QuantileSymbolizer([:a, :b, :c], [0.2, 0.3, 0.5])
g = PropertyBasedGenerator(source, symbolizer)

generate(g, 8; rng = StableRNG(42))

Example output:

8-element Vector{Symbol}:
 :a
 :c
 :b
 :c
 :c
 :a
 :c
 :b

Available latent sources are:

SourceLatent behaviorWidth
SpectralFGNSource(H)approximate spectral fGnany positive width
HaarLRDSource(H)Haar-like multiscale driverany positive width
IntermittentMapSource(z)intermittent-map dynamicsone stream

Available symbolizers are:

SymbolizerTransformationRequired width
QuantileSymbolizerrank-bin one latent stream to target counts1
ArgmaxSymbolizerchoose the largest offset latent streamalphabet size
MarkovRegimeSymbolizerrank-bin one latent stream to Markov regimes1

Some combinations are intentionally invalid. For example, IntermittentMapSource cannot feed ArgmaxSymbolizer, because the intermittent map source currently produces only one latent stream and argmax symbolization needs one stream per symbol.

PB3's regime construction can also be built directly:

P1 = [0.95 0.05; 0.10 0.90]
P2 = [0.30 0.70; 0.70 0.30]
source = HaarLRDSource(0.8)
symbolizer = MarkovRegimeSymbolizer([:a, :b], [P1, P2])
g = PropertyBasedGenerator(source, symbolizer)

Use the composable API when the study is about transformations. Use make_generator when the study only needs a standard named method.

To inspect the numerical driver used by a property-based generator:

seq, latent = generate_with_latent(g, 8; rng = StableRNG(42))
size(latent)

Example output:

(1, 8)

Explicit Constructors

Use explicit constructors when a model needs exact transition matrices, excitation matrices, rates, or calibration parameters.

using SymbolicLongMemorySequences, StableRNGs

P1 = [0.9 0.1; 0.2 0.8]
P2 = [0.3 0.7; 0.6 0.4]
Q = [0.2 0.8; 0.8 0.2]

g = OnOffMarkov(1.5, [:a, :b], [P1, P2], Q; L_min = 50.0)
generate(g, 6; rng = StableRNG(7))

Example output:

6-element Vector{Symbol}:
 :a
 :b
 :b
 :b
 :b
 :b

The constructors validate alphabets, probability vectors, transition matrices, rates, and parameter ranges at construction time. Invalid construction should raise ArgumentError.

Controls

Every generator accepts an ordered alphabet and emits only values from that alphabet. Duplicate alphabet entries are rejected because they make empirical frequency tables ambiguous.

Use control_capabilities to ask what a generator claims to control:

g = make_generator(:MB1c, [:a, :b]; beta = 0.5, d = 500)
control_capabilities(g).marginal

Example output:

:centered

Use the empirical helpers for short, transparent diagnostics:

seq = [:a, :b, :a, :a]
empirical_marginal(seq, [:a, :b])

Example output:

2-element Vector{Float64}:
 0.75
 0.25

First-order local structure is represented by MarkovSpec:

spec = MarkovSpec([:a, :b], [0.9 0.1; 0.2 0.8])
local_structure_order(spec)

Example output:

1

MarkovSpec is currently used by WaveletMarkov and OnOffMarkov. The abstract LocalStructureSpec is the extension point for a future trigram or higher-order specification, but SymbolicLongMemorySequences.jl does not yet expose trigram control.

Output And Provenance

Use save_sequence to write generated data with provenance metadata in INC format:

g = make_generator(:PB1, [:a, :b]; H = 0.75)
seq = generate(g, 100; rng = StableRNG(11))
save_sequence("seq_pb1.inc", seq, g)

The file records the generator type, method identifier, sequence length, creation date, and generator parameters. Retained validation outputs should use the same provenance discipline.

Tests

The main test path checks the public contracts:

julia --project=. -e 'using Pkg; Pkg.test()'

The fast tests cover construction errors, output length and element type, alphabet membership, reproducible RNG use, provenance metadata, factory discovery, and small bounded checks for declared controls. Broader empirical studies belong on the Validation and Benchmarks page rather than in the fast package test suite.