Effective measure complexity¶
Excess entropy, what emc() actually computes, and why the per-scale ladder is projected before
the total is read off it.
Entropy rate answers "how surprising is the next symbol?" Effective measure complexity answers a different question: how much does the past tell you about the future? A fair coin is maximally surprising and tells you nothing. A constant string is unsurprising and also tells you nothing. Both have zero excess entropy. Everything interesting lives in between.
This page defines the quantity, then describes the estimator in lzcomplexity in full — including
the shape constraint it imposes on its own intermediate results, what that fixes, and the two biases
it does not fix.
The definition¶
Grassberger (1986) defined effective measure complexity as the total of the entropy-rate over-estimates you make when you only ever look at finite blocks.
E = ΣL≥1 [ h(L) − h ] = limL→∞ [ H(L) − L·h ]
- H(L)
- Shannon block entropy of length-L words, with H(0) ≡ 0
- h(L)
- H(L) − H(L−1), the apparent entropy rate at scale L
- h
- lim h(L), the true entropy rate
Grassberger, Int. J. Theor. Phys. 25:907 (1986), Eq. 26. The equality of the two forms is Props. 6 and 7 of Crutchfield & Feldman, Chaos 13:25 (2003).
Read the first form as apparent memory. Observed through a length-L window, a process looks
more random than it is, by exactly h(L) − h bits per symbol. Sum those over-estimates across all
window sizes and you get E.
Read the second form as the subextensive part of the block entropy. H(L) grows like
E + L·h: the slope is the entropy rate, the intercept is the excess entropy.
Crutchfield & Feldman's Prop. 8 supplies the third and most quotable reading — E is the mutual
information between the infinite past and the infinite future.
E = limL→∞ I[ S−L…S−1 ; S0…SL−1 ]
Crutchfield & Feldman (2003), Eq. 53. The units of E are bits (their Eq. 48); E ≥ 0 always (their Eq. 52).
Four communities, four names, one quantity
You will meet this number under names that look unrelated. They are the same object, and the equivalence is a theorem chain (Crutchfield & Feldman 2003, Props. 6–8), not a convention.
| Name | Community | Primary reference |
|---|---|---|
excess entropy E |
computational mechanics | Crutchfield & Packard, Physica D 7:201 (1983) — coinage |
| effective measure complexity | statistical physics | Grassberger, Int. J. Theor. Phys. 25:907 (1986) |
| predictive information | neuroscience, machine learning | Bialek, Nemenman & Tishby, Neural Comp. 13:2409 (2001) |
| stored information | nonlinear dynamics | Shaw, The Dripping Faucet as a Model Chaotic System (1984) |
Grassberger's paper also defines a second quantity, true measure complexity, which is the entropy of the minimal predictive model — the statistical complexity. It is not this. Full citations on References.
Two properties the estimator is held to¶
Everything the library does with its intermediate results follows from one lemma. Write the partial sum of the definition as the finite-scale excess entropy:
E(L) = Σj=1L [ h(j) − h ] = H(L) − L·h
The two forms are the same identity as above, stopped at a finite L rather than taken to the limit.
For a stationary source h(j) is non-increasing and bounded below by h, so every summand
h(j) − h is non-negative (Crutchfield & Feldman, Lemma 1). Two consequences follow immediately,
and they are the only two facts the estimator needs:
E(L) ≥ 0for everyL.E(L)is non-decreasing inL, climbing toE.
A ladder of estimates Ê(1), Ê(2), … that violates either one is telling you about its own noise,
not about the source.
The one hump¶
This is the point of the measure, and it is what distinguishes it from everything else the library computes.
Entropy rate is monotone in randomness. h climbs from 0 for a constant sequence to log k for
a uniform i.i.d. one, and every source sits somewhere on that ladder.
Excess entropy is not. It vanishes at both ends — the perfectly ordered source has nothing to predict, the perfectly random one has nothing predictable — and it is large in between, where a process carries structure the past can be used to forecast.
Measured, n = 20 000, binary alphabet, default settings, seed=1 for the two random families:
| source | h |
emc value |
multi_information |
|---|---|---|---|
constant "0"×20000 |
0.0007 | 0.0000 | 0.0000 |
period-8, motif 00010111 |
0.0029 | 5.3557 | 0.9994 |
| Thue–Morse | 0.0186 | 9.7664 | 0.9937 |
| Markov, P(stay) = 0.99 | 0.0807 | 2.0103 | 0.8880 |
| Markov, P(stay) = 0.95 | 0.2900 | 1.9653 | 0.7130 |
| Markov, P(stay) = 0.90 | 0.4772 | 1.2602 | 0.5351 |
| Markov, P(stay) = 0.80 | 0.7401 | 0.6744 | 0.2743 |
| Markov, P(stay) = 0.70 | 0.8966 | 0.3178 | 0.1214 |
| i.i.d. p = 0.5 | 1.0294 | 0.0000 | −0.0014 |
h rises monotonically down the table. emc does not: zero at the top, zero at the bottom, peaking
at Thue–Morse — deterministic, aperiodic, hierarchically self-similar, the canonical "structured but
not periodic" sequence.
Both extremes are exactly 0.0, and at n = 200 000 they stay there: "0"×200000 and
iid(200_000, 0.5, seed=1) both return 0.0. "01"×100000 returns 0.746252539205446 — a periodic
sequence is not a structureless one, and the estimator no longer confuses the two.
Generators used for this table
import random
import lzcomplexity as lz
def markov(n, q, seed):
rng, s, out = random.Random(seed), "0", []
for _ in range(n):
out.append(s)
if rng.random() >= q:
s = "1" if s == "0" else "0"
return "".join(out)
def iid(n, p, seed):
rng = random.Random(seed)
return "".join("1" if rng.random() < p else "0" for _ in range(n))
def thue_morse(n):
return "".join(str(bin(i).count("1") % 2) for i in range(n))
r = lz.lz76(markov(20_000, 0.95, seed=1))
print(r["h"], r["emc"]["value"], r["emc"]["multi_information"])
# 0.2900405613048538 1.9652748378070268 0.7129568477395176
The complexity–entropy plane is filled, not a curve
Do not read the hump as a function E(h) — two sources with identical entropy rate can have
wildly different excess entropy. Every periodic process has h = 0, yet a period-p process has
E = log₂ p, unbounded over the family (Crutchfield & Feldman, Prop. 10). The h = 0 axis
therefore carries every value of E from 0 to ∞. Feldman, McTague & Crutchfield (2008)
explicitly decline to assume any relationship between the two. Report (h, emc) as a pair —
that pair is the complexity–entropy diagram the literature actually plots — and never infer one
coordinate from the other.
What the library computes¶
The block entropies H(L) cannot be plugged in directly: there are k^L words of length L, and
past L ≈ log_k n you have no counts for them. lzcomplexity substitutes a surrogate-data
construction — a ladder of block shuffles.
For each block size l = 1 … mm:
- Copy the sequence and shuffle it in blocks of length
l: repeatedly transpose two aligned, non-overlapping length-lblocks,⌊n/2⌋times. This destroys correlations longer thanland preserves everything inside a block. - LZ76-factorize the surrogate, giving
C(u^RS(l)). - The surrogate is (idealised) an i.i.d. stream of
l-blocks, whose entropy rate isH(l)/l. Multiply back to recover the block entropy:H_l = l · C(u^RS(l)) · g. - Subtract the extensive part to leave the rung
Ê(l), an estimate ofE(l).
g = logk(n) ⁄ n · ĥ = C(u)·g · Hl = l·C(uRS(l))·g
Ê(l) = Hl − l·ĥ = l · g · ( C(uRS(l)) − C(u) )
- C(·)
- LZ76 factor count — the library's
complexity, which counts complete components only - uRS(l)
- the block-shuffled surrogate at block size l
- k
- log base — the sequence's alphabet size unless you pass
log_base - mm
- the largest block size, returned as
max_block_size
ĥ is exactly the library's entropy density: the same number lz76(...)["h"] returns. Ê(l) is the finite-scale excess entropy E(l) = H(l) − L·h with both terms estimated from LZ76 counts.
That gives mm independent estimates of one increasing curve. The last step turns them into a
number.
C(·) throughout is the library's own factor count. It counts complete LZ76 components only, so
it is one less than the textbook exhaustive-history count whenever the sequence ends mid-component —
see LZ76 factorization. The same convention applies to the original and to every
surrogate, and since each rung is l·g times a difference of two such counts, the convention
shifts each rung by at most l·g in either direction — 0.015 at n = 20 000 and l = mm.
The ladder is projected before it is read¶
Each rung Ê(l) is a legitimate estimate of E(l) on its own. As a sequence they are badly
behaved, because each one rests on its own surrogate draw rather than on slices of one consistent
block-entropy curve: the ladder wanders up and down, and individual rungs fall below zero. The
two properties above say the true curve does neither.
So the library projects the ladder onto the set of sequences that are non-negative and non-decreasing — the smallest correction, in least squares, that restores both properties — and reads the answer off the projection.
Êfit = argminv Σl ( vl − Ê(l) )² subject to 0 ≤ v1 ≤ v2 ≤ … ≤ vmm
summands[l−1] = Êfit(l) − Êfit(l−1) · Ê = Êfit(mm) = Σ summands
Isotonic regression, computed by pool-adjacent-violators (Ayer et al., Ann. Math. Statist. 26:641, 1955); the non-negativity projection is the pointwise positive part, which preserves monotonicity. Implemented as non_negative_isotonic in crates/lzcomplexity-core/src/shuffle.rs.
Pool-adjacent-violators is one left-to-right pass: carry a stack of blocks, and whenever the newest block averages below its predecessor, merge the two and re-check. Every rung enters the fit, and the result has three properties the raw ladder lacks.
Why the raw sum was not usable¶
Versions up to and including 1.0.2 summed the first differences of the ladder directly,
Σ_l [(H_l − H_{l−1}) − ĥ]. That is the definition written as a sum, and it is correct
mathematics — but it is also the identity Σ_l [h(l) − h] = H(L) − L·h, so the H_l telescope and
the whole sum collapses to its own last rung:
Σl=1mm [ ( Hl − Hl−1 ) − ĥ ] = Hmm − mm·ĥ = Ê(mm) = mm · g · ( C(uRS(mm)) − C(u) )
The telescoping is not a bug in the old code — it is the identity. The problem is what the identity leaves you holding: one rung.
The estimator computed mm surrogates and mm factorizations, then let all but the largest cancel
algebraically. The scalar was a two-point contrast between one surrogate at one block size and the
original, multiplied by mm. Three things went wrong, and all three are consequences of that:
| symptom | ≤ 1.0.2 | now |
|---|---|---|
Negative values. Excess entropy is a mutual information; it cannot be negative. One unlucky surrogate could make C(u^RS(mm)) < C(u). |
i.i.d. binary at n = 20 000 returns −0.1950; 9 of 20 draws negative |
0.0; 0 of 20 negative, by construction |
Periodic collapse. The shuffle only permutes phase-aligned blocks, so if the period p divides mm the shuffle is the identity and the top rung is exactly zero — dragging the whole total to zero. |
period 3, 7 and 21 at mm = 21 all return floating-point zero |
1.8193, 4.1974, 6.0262 — the scales that did see structure now carry it |
Single-draw variance. The total inherited the Monte-Carlo noise of one surrogate, amplified by mm. |
s.d. 0.1230 over the i.i.d. null at n = 20 000 |
s.d. 0.0702, a 43 % reduction, at no extra cost |
The projection changes nothing when the ladder is already well behaved: a monotone non-negative
ladder is its own projection, so the value is still exactly Ê(mm). That is why
"01"×64 (2.4609375), "0011"×256 (4.150390625), Thue–Morse (9.7664), the period-8 motif
(5.3557) and markov(8192, 0.9, seed=6) (1.30126953125) are unchanged to the last bit. The
values that moved are the ones that were reporting an artefact.
This is a breaking change, and it is not a bug fix in the surrogate
emc values from 1.0.2 and earlier are not comparable with current ones for any input whose
ladder was not monotone. Nothing about the shuffle, the seeding or the factorization changed —
only how the mm rungs are turned into one number. See Releases.
What the projection does not fix¶
Two biases live in the rungs themselves, not in how they are combined, and the projection cannot touch either. Both were present before and remain.
emc overshoots. Against order-1 Markov sources, where Crutchfield & Feldman's Prop. 11 gives
the answer exactly as E = H(1) − h, the estimator lands 2.2–2.8× high. That is the finite-n LZ76
bias entering each rung twice, without cancelling, and then being multiplied by l.
| P(stay) | analytic E |
emc value |
ratio | multi_information |
|---|---|---|---|---|
| 0.99 | 0.919207 | 2.0103 | 2.19× | 0.8880 |
| 0.95 | 0.713603 | 1.9653 | 2.75× | 0.7130 |
| 0.90 | 0.531004 | 1.2602 | 2.37× | 0.5351 |
| 0.80 | 0.278072 | 0.6744 | 2.43× | 0.2743 |
| 0.70 | 0.118709 | 0.3178 | 2.68× | 0.1214 |
emc scales with mm, and mm is a function of n. The rungs keep climbing past the block
length at which a length-n sample can support a block-entropy estimate at all, so the top of the
ladder is set by undersampling rather than by the source. Sweeping mm by hand on the P(stay) = 0.95
sequence, whose analytic E is 0.7136:
>>> s = markov(20_000, 0.95, seed=1)
>>> for mm in (1, 2, 4, 8, 16, 21, 32, 64):
... print(mm, round(lz.emc(s, max_block_size=mm)[0], 4))
1 0.713
2 0.8844
4 1.0344
8 1.2741
16 1.6645
21 1.9653
32 1.791
64 1.8869
mm = 21 is the value auto-selected at n = 20 000. At mm = 1 the ladder is one rung and the
answer is the l = 1 term alone, which for an order-1 source is the whole of E — 0.713 against
an analytic 0.7136. That is a property of this source, not of mm = 1.
Do not report emc as a bit count
A value of 1.97 does not mean the sequence stores 1.97 bits of predictive information. The
estimator does not recover E = log₂ p for periodic sources and it overshoots order-1 Markov
sources by 2–3×. Treat emc as an ordinal index at fixed n. "A has more multi-scale
structure than B" is supportable; "A stores 1.97 bits" is not.
Choosing mm¶
With max_block_size=-1 (the default) the library solves n = M·2^M by fixed-point iteration, then
adds 10 whenever n > 50. The n = M·2^M rule is the standard undersampling bound: seeing all
2^M binary words of length M takes on the order of M·2^M samples, so M is the largest block
length at which a length-n sample is not hopeless.
n |
M from n = M·2^M |
default mm |
|---|---|---|
| 50 | 4 | 4 |
| 51 | 4 | 14 |
| 100 | 4 | 14 |
| 1 000 | 7 | 17 |
| 10 000 | 10 | 20 |
| 20 000 | 11 | 21 |
| 100 000 | 13 | 23 |
The M rule is citable (Melchert & Hartmann, Phys. Rev. E 91:023306, 2015). The +10 is not: it
carries no stated justification and it deliberately pushes mm past the undersampling bound, which
is exactly where the rungs stop tracking E(l) and start tracking the sample size. Note also the
3.5× discontinuity between n = 50 and n = 51.
The logarithm in the M rule is hardcoded to base 2 regardless of alphabet, so mm comes out the
same for a binary and a 27-letter sequence of equal length. For k = 2 the rationale transfers; for
larger alphabets it does not, and the docs will not pretend otherwise.
multi_information¶
The l = 1 rung gets its own field. It is H_1 − ĥ, the entropy-rate increase caused by destroying
all correlations, which is the multi-information (total correlation) rate of the source. The name
is correct, and the field is reported before the projection — it is a separate diagnostic, not a
term of the sum, and its values are unchanged from 1.0.2 and earlier.
It is also the best-calibrated number the estimator produces — but only for short-memory processes.
For an order-1 Markov chain, Prop. 11 makes the l = 1 term the whole excess entropy, and the
measured 0.712957 against an analytic 0.713603 is the closest this library gets to E. For periodic,
hierarchical or higher-order sources it saturates near H(X₁) ≤ log k and badly under-states E: it
reads ≈0.99 for both the period-8 and the Thue–Morse sequence, whose true excess entropies are 3 bits
and divergent respectively.
multi_information is usually summands[0], but not always
Both are the l = 1 term; the field is raw and summands[0] is fitted. They agree whenever the
first rung survives the projection unpooled, which is the common case — 0.7129568477395176 for
the Markov example, in both places. They differ when rung 1 was clamped or pooled: for
SEQ = "01001010101101010101110101010101010000100101011", multi_information is
0.11818274152505626 while summands[0] is 0.05909137076252813, because rungs 1 and 2 pooled.
Quote multi_information when you want the scale-1 contrast, summands[0] when you want the
first term of the sum.
Reading the output¶
The summands vector strictly dominates the scalar¶
emc() returns (value, summands), where summands[l-1] is the fitted estimate of h(l) − h at
block size l. Every entry is non-negative and they sum to the value. The vector answers a question
the scalar cannot: at what scale does the structure live?
Read it two ways. The increments are the per-scale terms. Their running sum is Ê_fit(l),
the excess entropy accumulated up to scale l, which is the more directly interpretable curve — it
climbs and flattens, and where it flattens is where the source runs out of structure.
Three summand profiles, n = 20 000, mm = 21
An order-1 Markov chain, P(stay) = 0.95. Weight concentrated at l = 1, exactly as an order-1
source should give, and the running sum has covered 65 % of its final value by l = 7:
>>> r = lz.lz76(markov(20_000, 0.95, seed=1))
>>> r["complexity"], r["h"]
(406, 0.2900405613048538)
>>> r["emc"]["value"], r["emc"]["multi_information"], r["emc"]["max_block_size"]
(1.9652748378070268, 0.7129568477395176, 21)
>>> [round(x, 4) for x in r["emc"]["summands"]]
[0.713, 0.1715, 0.0864, 0.0636, 0.1086, 0.0657, 0.0654, 0.0, 0.16, 0.0,
0.0168, 0.0, 0.1225, 0.0, 0.0747, 0.0, 0.0, 0.0967, 0.0, 0.0, 0.2205]
>>> import itertools
>>> [round(x, 3) for x in itertools.accumulate(r["emc"]["summands"])]
[0.713, 0.884, 0.971, 1.034, 1.143, 1.209, 1.274, 1.274, 1.434, 1.434,
1.451, 1.451, 1.573, 1.573, 1.648, 1.648, 1.648, 1.745, 1.745, 1.745, 1.965]
Thue–Morse — hierarchical, self-similar. Weight keeps arriving at every scale, and the running sum never flattens. That is the signature of long-range structure:
>>> r = lz.lz76(thue_morse(20_000))
>>> r["emc"]["value"]
9.766365797041026
>>> [round(x, 4) for x in r["emc"]["summands"]]
[0.9937, 0.1593, 0.8455, 0.0, 1.5749, 0.0, 0.0, 0.0, 1.8549, 0.0, 0.0, 0.0,
0.4879, 0.0, 0.0, 0.0, 1.7013, 0.0, 0.0, 0.0, 2.1489]
An i.i.d. binary sequence. The entire ladder sat below zero, so the projection flattened it to nothing — correctly identifying "no structure":
How to read a profile — rules of thumb, not tests:
Shape of summands |
Interpretation |
|---|---|
weight concentrated at l = 1 |
short memory, order-1-like |
weight decaying over the first few l |
finite-order Markov, order ≈ where it dies |
weight still arriving at large l |
long-range or hierarchical correlation |
| all entries zero, or a single small entry | no detectable structure — check it against a null |
A zero summand means two neighbouring scales were pooled, not that the scale was skipped
The projection replaces a run of violating rungs with their common mean, so the fitted curve is
flat across that run and the increments inside it are exactly 0.0. A zero therefore reads as
"scale l added nothing beyond scale l−1", which is a statement about the estimate, not a gap
in the computation. All mm surrogates were factorized either way. The length of a trailing run
of zeros tells you how many top rungs were pooled — useful in the verification below.
The summands cost nothing extra
All mm surrogate factorizations run whether or not you read the vector, and the Python API
always requests it — len(summands) == max_block_size on every call. Reading only emc()[0]
discards mm − 1 factorizations' worth of information for zero saving in time or memory.
The CLI reports the same numbers¶
-e a:f means "auto block size, and emit the summand terms". Full option grammar on
lzcomplexity.
Verification¶
Three invariants hold on every input, and all three are cheap to assert in a test:
value, summands = lz.emc(seq)
assert all(s >= 0.0 for s in summands) # Lemma 1
assert abs(sum(summands) - value) < 1e-12 # the sum is the value
partials = list(itertools.accumulate(summands))
assert all(a <= b + 1e-12 for a, b in zip(partials, partials[1:])) # monotone
A fourth is more interesting, because it pins the arithmetic. Every rung is l·g times an integer
difference of factor counts, and every fitted level is a mean of rungs, so value / g is always a
rational whose denominator is the size of the top pooled block — which you can read straight off
the trailing run of zeros in summands. Measured:
| sequence | n |
mm |
value / g |
trailing zeros | top block |
|---|---|---|---|---|---|
"01"×64 |
128 | 15 | 45 | 0 | 1 |
"01"×500 |
1 000 | 17 | 204 | 0 | 1 |
markov(8192, 0.9, 6) |
8 192 | 20 | 820 | 0 | 1 |
| Markov P(stay)=0.95 | 20 000 | 21 | 2751 | 0 | 1 |
period-8 00010111 |
20 000 | 21 | 7497 | 0 | 1 |
| Thue–Morse | 20 000 | 21 | 13671 | 0 | 1 |
the same markov with one symbol flipped |
8 192 | 20 | 5512 / 7 | 6 | 7 |
| i.i.d. binary | 20 000 | 21 | 0 | 20 | 21 |
When the top rung survives unpooled — the common case — value / g is a plain integer and the value
still equals the old closed form mm · g · (C(u^RS(mm)) − C(u)). When it is pooled, the denominator
appears, and it always equals the block size. There is no resolution below g / (top block); at
n = 20 000 and an unpooled top, that step is mm·g = 0.0150.
Four rules¶
1. Never compare emc across different sequence lengths¶
The same process reports 16 % lower emc at 100× the length
mm grows with n, and the top of the ladder scales with mm, so changing the length alone
moves the answer. A period-8 sequence has a constant true E = log₂ 8 = 3 bits at every length.
Measured:
n |
1 000 | 5 000 | 20 000 | 100 000 |
|---|---|---|---|---|
mm |
17 | 19 | 21 | 23 |
emc |
5.9296 | 5.8367 | 5.3557 | 4.9701 |
multi_information |
1.0265 | 0.9928 | 0.9994 | 0.9931 |
Truncate or resample every sequence in a study to a common length. If you cannot, pin
max_block_size to one fixed value across the whole study — that removes the mm dependence but
not the O(1/log n) LZ bias, which enters every rung twice with imperfect cancellation.
Short sequences are worse than merely noisy. emc is identically 0.0 for n ≤ 10, because
the block shuffle returns before swapping anything, and the mm jump from 4 to 14 across
n = 50 → 51 means sequences either side of that boundary are not on the same scale at all.
The same caution applies to the log base: every rung is ∝ 1/ln(log_base), so binary and 4-ary
results are not on a common scale unless you pass log_base=2 to both. See
Alphabets.
2. 0.0 now means one thing, and it is a real reading¶
An exact zero means the whole ladder sat at or below zero
Under 1.0.2 an exact 0.0 was ambiguous: it could mean "structureless" or "the source period
divides mm, so the top surrogate was the identity". The second case is gone — a period-p
sequence with p | mm now reports the structure the other mm − 1 scales found:
period p |
motif | p divides 21 |
emc (≤ 1.0.2) |
emc (now) |
|---|---|---|---|---|
| 2 | 01 |
no | 1.7102 | 1.7102 |
| 3 | 001 |
yes | −1.3e−15 | 1.8193 |
| 4 | 0011 |
no | 3.5255 | 3.5255 |
| 5 | 00101 |
no | 4.1106 | 4.1106 |
| 7 | 0010111 |
yes | −2.7e−15 | 4.1974 |
| 8 | 00010111 |
no | 5.3557 | 5.3557 |
| 11 | 00010110011 |
no | 6.1509 | 6.1980 |
| 16 | 0001001101011110 |
no | 7.2610 | 7.2610 |
| 21 | 000100110101111000101 |
yes | −8.9e−16 | 6.0262 |
| 32 | 00010011010111100010101110110011 |
no | 9.4213 | 9.4213 |
emc == 0.0 now means the projection found no monotone rise anywhere in the ladder, which is
what a structureless input looks like. It is a hard zero, not a near-zero: test with
value == 0.0 if you like, though abs(value) < 1e-12 remains the safer habit.
It is still not proof of absence. A short structured sequence can produce it by noise — 8 of 20
i.i.d. draws at n = 2 048 return exactly 0.0, and so would a weak signal at that length.
Rule 3 is how you tell them apart.
3. Establish a null — the floor is one-sided now, and it is not at zero¶
emc ships no error bar. The normal_error and poison_error fields in the lz76() output are
uncertainties on the entropy density, not on emc.
Clamping at zero buys non-negativity at a price: on structureless input the estimator can no longer
err downward, so its noise piles up above zero and the null distribution has a positive mean.
Measured over 20 independent i.i.d. binary sequences per length, iid(n, 0.5, seed) for
seed in range(20):
n |
mm |
mean | s.d. | max | exactly 0.0 |
mean + 2 s.d. |
|---|---|---|---|---|---|---|
| 1 000 | 17 | 0.1091 | 0.1762 | 0.5680 | 12/20 | 0.4615 |
| 2 048 | 18 | 0.1732 | 0.1721 | 0.4834 | 8/20 | 0.5173 |
| 5 000 | 19 | 0.1074 | 0.1056 | 0.3735 | 5/20 | 0.3186 |
| 20 000 | 21 | 0.0613 | 0.0702 | 0.2115 | 7/20 | 0.2018 |
| 100 000 | 23 | 0.0180 | 0.0260 | 0.0993 | 9/20 | 0.0700 |
Read the last column as the level a value has to clear before it is worth interpreting. At
n = 20 000 that is ≈0.20, against ≈0.25 for the old two-sided floor — a modest improvement, and the
floor still falls with n. A small positive emc is the null, not a weak signal. Establish the
floor for your own data: shuffle, re-run 20–30 times, take mean + 2 s.d.
The comparison with 1.0.2 at n = 20 000 is the whole trade in one line — the old floor was
mean 0.0075, s.d. 0.1230, negative in 9/20; the new one is mean 0.0613, s.d. 0.0702, negative in
0/20. Half the spread, no sign violations, a positive offset you have to subtract yourself.
4. Always report h alongside emc¶
Neither coordinate is interpretable alone — that is the entire content of the one-hump section. The
pair (h, emc) is the complexity–entropy diagram, and it is the object the literature plots. See
Entropy density and
Reading the numbers.
Where it works anyway¶
None of the above makes the measure useless. Across a family of equal-length sequences generated by
one parametric process, emc traces the behaviour theory predicts: low in the ordered regime, low in
the fully chaotic regime, elevated in between. The logistic-map sweep in
Dynamical systems works this through end to end — 121 values of
the control parameter, one fixed n, h and emc side by side.
That sweep is also the clearest evidence for the projection. Its peak is unchanged — 7.4410 at
r = 3.570, the first grid point past the Feigenbaum point — but its minimum moved. Under 1.0.2
the smallest value in the sweep was a floating-point zero at the period-3 window near r = 3.83, an
artefact of 3 | mm. It is now 0.2400 at r = 4.000, full chaos, which is where excess-entropy
theory says the minimum belongs. The shape the estimator was supposed to show is the shape it now
shows.
For what it does with recorded rather than generated data, see Neuroscience and Genomics.
Field reference¶
emc() and the "emc" block of lz76() carry their full signatures on the
Python API page. The fields:
| Field | Type | Meaning |
|---|---|---|
emc()[0], lz76()["emc"]["value"] |
float |
Ê — the top of the projected ladder. Always >= 0.0 |
emc()[1], lz76()["emc"]["summands"] |
list[float], length mm |
summands[l-1] is the fitted h(l) − h at block size l. Every entry >= 0.0; they sum to the value |
lz76()["emc"]["max_block_size"] |
int |
the resolved mm |
lz76()["emc"]["multi_information"] |
float |
the raw, unprojected l = 1 contrast H_1 − ĥ. May be slightly negative; usually equals summands[0] |
The shuffle is seeded from an FNV-1a hash of the sequence bytes, so the same input yields the same
emc on any machine at any thread count, and the projection is a deterministic single pass over the
rungs. That is reproducibility of one surrogate realization, not an ensemble estimate — see
Determinism.