nested-recd 0.2.0__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nested_recd-0.2.0/src/nested_recd.egg-info → nested_recd-0.2.3}/PKG-INFO +41 -10
- {nested_recd-0.2.0 → nested_recd-0.2.3}/README.md +38 -7
- {nested_recd-0.2.0 → nested_recd-0.2.3}/pyproject.toml +3 -3
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd/__init__.py +16 -3
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd/ordinal_levels.py +366 -39
- {nested_recd-0.2.0 → nested_recd-0.2.3/src/nested_recd.egg-info}/PKG-INFO +41 -10
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd.egg-info/SOURCES.txt +1 -0
- nested_recd-0.2.3/tests/test_joint_counting.py +144 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/tests/test_smoke.py +63 -1
- {nested_recd-0.2.0 → nested_recd-0.2.3}/LICENSE +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/setup.cfg +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd/py.typed +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd/surrogates.py +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd.egg-info/dependency_links.txt +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd.egg-info/requires.txt +0 -0
- {nested_recd-0.2.0 → nested_recd-0.2.3}/src/nested_recd.egg-info/top_level.txt +0 -0
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nested-recd
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Nested ordinal RECD: Φ1–Φ3, continuous excess³ (
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Nested ordinal RECD: Φ1–Φ3, continuous excess³ (exportable Level-3 proxy), Res_pair (strong residual), and λ-weighted Discrete Extramental Clock
|
|
5
5
|
Author-email: Johel Padilla-Villanueva <joel.padilla2@upr.edu>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/johelpadilla/nested-recd
|
|
8
8
|
Project-URL: Repository, https://github.com/johelpadilla/nested-recd
|
|
9
9
|
Project-URL: Issues, https://github.com/johelpadilla/nested-recd/issues
|
|
10
10
|
Project-URL: Documentation, https://github.com/johelpadilla/nested-recd#readme
|
|
11
|
-
Project-URL: DOI, https://doi.org/10.5281/zenodo.
|
|
11
|
+
Project-URL: DOI, https://doi.org/10.5281/zenodo.22970079
|
|
12
12
|
Keywords: RECD,ordinal patterns,nested time,Systemic Tau,complexity,early-warning,network physiology,Bandt-Pompe
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -35,13 +35,26 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
# nested-recd
|
|
37
37
|
|
|
38
|
+
> **Correction (0.2.3).** Versions ≤ 0.2.2 computed `H_joint` and the pairwise
|
|
39
|
+
> entropies inside `Syn` with `np.unique` on a list of tuples and no `axis=0`.
|
|
40
|
+
> NumPy flattens that input, so `Syn`, `excess3` and binary `phi3` were not the
|
|
41
|
+
> published definition when `N ≥ 3`. `Surp` and `Res_pair` were not affected.
|
|
42
|
+
> For `N = 2`, `Syn` is 0 either way. 0.2.3 counts joint rows.
|
|
43
|
+
> `legacy_pooled_counting=True` reproduces the old numbers and warns.
|
|
44
|
+
> See `CHANGELOG.md`. This version:
|
|
45
|
+
> [10.5281/zenodo.22970079](https://doi.org/10.5281/zenodo.22970079).
|
|
46
|
+
> The 0.2.2 deposit remains
|
|
47
|
+
> [10.5281/zenodo.21937204](https://doi.org/10.5281/zenodo.21937204).
|
|
48
|
+
|
|
38
49
|
[](https://pypi.org/project/nested-recd/)
|
|
39
50
|
[](https://pypi.org/project/nested-recd/)
|
|
40
51
|
[](LICENSE)
|
|
41
52
|
|
|
42
|
-
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 proxy, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
53
|
+
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 *proxy* (T-IV; not the strong residual), optional **Res_pair** \(=\mathrm{KL}(P\|P^{(2)})\), Gibbs-α weights, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
54
|
+
|
|
55
|
+
This is the **canonical nested-clock software** for the Systemic Tau / RECD stack (foundations paper, excess³ methods preprint, cardiac CCTP/SDDB pilot, Academy Learning Tau).
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
**Léxico (no negociable):** *strong Level-3* := `Res_pair`. *excess3* := exportable proxy. Report `w` with any Res_pair figure. Abundance (excess3) ≠ aggregation share \(f_3\).
|
|
45
58
|
|
|
46
59
|
## Install
|
|
47
60
|
|
|
@@ -88,6 +101,15 @@ print("weights:", out["params"]["alpha_syn"], out["params"]["alpha_surp"])
|
|
|
88
101
|
print("final T_recd:", float(out["T_recd"][-1]))
|
|
89
102
|
```
|
|
90
103
|
|
|
104
|
+
### Strong residual Res_pair (optional)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
out = compute_recd_from_conjunctions(X, compute_res=True)
|
|
108
|
+
# or, on a symbol window:
|
|
109
|
+
from nested_recd import compute_res_pair_window
|
|
110
|
+
# Always report window length with Res_pair (Nota 4: w=13 can bias chaos).
|
|
111
|
+
```
|
|
112
|
+
|
|
91
113
|
### Continuous excess³ only
|
|
92
114
|
|
|
93
115
|
```python
|
|
@@ -114,7 +136,7 @@ out = compute_recd_from_conjunctions(X, tau_s=tau_s)
|
|
|
114
136
|
out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
115
137
|
```
|
|
116
138
|
|
|
117
|
-
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For
|
|
139
|
+
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For **exportable / scalable** Level-3 reporting, use continuous **excess³** and nulls on `|Δ excess3|`. For **strong** Level-3 claims, report **`res_pair`** (enable `compute_res=True`) **and the window `w`**. Do not identify the two.
|
|
118
140
|
|
|
119
141
|
## What it computes
|
|
120
142
|
|
|
@@ -122,10 +144,11 @@ out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
|
122
144
|
|--------|---------|
|
|
123
145
|
| **Φ₁** | Co-occurrence of identical ordinal symbols across variable pairs |
|
|
124
146
|
| **Φ₂** | Persistence of pairwise ordinal relations over lag `d` |
|
|
125
|
-
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**
|
|
147
|
+
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**exportable Level-3 proxy**; T-IV) |
|
|
126
148
|
| **Φ₃** | Binary ticks of excess³ above `theta3` (**secondary**) |
|
|
149
|
+
| **Res_pair** | Strong Level-3 residual `KL(P ‖ P^(2))` (optional; always report `w`) |
|
|
127
150
|
| **λ** | Chaos / reorganization intensity (from `τ_s` or `lam_override`) |
|
|
128
|
-
| **α(λ)** |
|
|
151
|
+
| **α(λ)** | Design-engine weights (template or Gibbs-α); **not** a joint observable |
|
|
129
152
|
| **ΔRECD** | `α₁Φ₁ + α₂Φ₂ + α₃Φ₃` (legacy clock; binary Φ₃) |
|
|
130
153
|
| **T_recd** | Cumulative sum of ΔRECD |
|
|
131
154
|
|
|
@@ -150,7 +173,8 @@ from nested_recd import (
|
|
|
150
173
|
compute_phi1, compute_phi2, compute_phi3,
|
|
151
174
|
compute_phi3_excess, compute_excess3_window,
|
|
152
175
|
mean_excess_pre_post, surrogate_pvalue_delta_excess3,
|
|
153
|
-
compute_lambda, alpha_weights, regime_lambda_proxy,
|
|
176
|
+
compute_lambda, alpha_weights, alpha_weights_gibbs, regime_lambda_proxy,
|
|
177
|
+
compute_res_pair, compute_res_pair_window,
|
|
154
178
|
compute_weighted_contributions, high_level3_rate,
|
|
155
179
|
generate_multivariate_symbols,
|
|
156
180
|
phase_shuffle_independent, generate_surrogate_ensemble,
|
|
@@ -173,6 +197,12 @@ If you use excess³ / Level-3 from this package, cite the **methods** preprint (
|
|
|
173
197
|
|
|
174
198
|
> Padilla-Villanueva, J. (2026). *excess³: A Pre-Specified Continuous Proxy for Order-3 Synergistic Surplus* (methods). Zenodo. https://doi.org/10.5281/zenodo.21385937
|
|
175
199
|
|
|
200
|
+
This software (0.2.3):
|
|
201
|
+
|
|
202
|
+
> Padilla-Villanueva, J. (2026). *nested-recd 0.2.3: Nested ordinal RECD, excess³ proxy, and Res_pair*. Zenodo. https://doi.org/10.5281/zenodo.22970079
|
|
203
|
+
|
|
204
|
+
The previous release (0.2.2), which pooled symbols inside Syn, remains citable as https://doi.org/10.5281/zenodo.21937204.
|
|
205
|
+
|
|
176
206
|
Software / related stack:
|
|
177
207
|
|
|
178
208
|
> Padilla-Villanueva, J. (2026). Systemic Tau software archive. Zenodo. https://doi.org/10.5281/zenodo.20576241
|
|
@@ -187,7 +217,8 @@ CCTP/SDDB pilot:
|
|
|
187
217
|
title = {nested-recd: Nested ordinal RECD levels and continuous excess³},
|
|
188
218
|
year = {2026},
|
|
189
219
|
url = {https://github.com/johelpadilla/nested-recd},
|
|
190
|
-
version = {0.2.
|
|
220
|
+
version = {0.2.3},
|
|
221
|
+
doi = {10.5281/zenodo.22970079}
|
|
191
222
|
}
|
|
192
223
|
```
|
|
193
224
|
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
# nested-recd
|
|
2
2
|
|
|
3
|
+
> **Correction (0.2.3).** Versions ≤ 0.2.2 computed `H_joint` and the pairwise
|
|
4
|
+
> entropies inside `Syn` with `np.unique` on a list of tuples and no `axis=0`.
|
|
5
|
+
> NumPy flattens that input, so `Syn`, `excess3` and binary `phi3` were not the
|
|
6
|
+
> published definition when `N ≥ 3`. `Surp` and `Res_pair` were not affected.
|
|
7
|
+
> For `N = 2`, `Syn` is 0 either way. 0.2.3 counts joint rows.
|
|
8
|
+
> `legacy_pooled_counting=True` reproduces the old numbers and warns.
|
|
9
|
+
> See `CHANGELOG.md`. This version:
|
|
10
|
+
> [10.5281/zenodo.22970079](https://doi.org/10.5281/zenodo.22970079).
|
|
11
|
+
> The 0.2.2 deposit remains
|
|
12
|
+
> [10.5281/zenodo.21937204](https://doi.org/10.5281/zenodo.21937204).
|
|
13
|
+
|
|
3
14
|
[](https://pypi.org/project/nested-recd/)
|
|
4
15
|
[](https://pypi.org/project/nested-recd/)
|
|
5
16
|
[](LICENSE)
|
|
6
17
|
|
|
7
|
-
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 proxy, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
18
|
+
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 *proxy* (T-IV; not the strong residual), optional **Res_pair** \(=\mathrm{KL}(P\|P^{(2)})\), Gibbs-α weights, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
19
|
+
|
|
20
|
+
This is the **canonical nested-clock software** for the Systemic Tau / RECD stack (foundations paper, excess³ methods preprint, cardiac CCTP/SDDB pilot, Academy Learning Tau).
|
|
8
21
|
|
|
9
|
-
|
|
22
|
+
**Léxico (no negociable):** *strong Level-3* := `Res_pair`. *excess3* := exportable proxy. Report `w` with any Res_pair figure. Abundance (excess3) ≠ aggregation share \(f_3\).
|
|
10
23
|
|
|
11
24
|
## Install
|
|
12
25
|
|
|
@@ -53,6 +66,15 @@ print("weights:", out["params"]["alpha_syn"], out["params"]["alpha_surp"])
|
|
|
53
66
|
print("final T_recd:", float(out["T_recd"][-1]))
|
|
54
67
|
```
|
|
55
68
|
|
|
69
|
+
### Strong residual Res_pair (optional)
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
out = compute_recd_from_conjunctions(X, compute_res=True)
|
|
73
|
+
# or, on a symbol window:
|
|
74
|
+
from nested_recd import compute_res_pair_window
|
|
75
|
+
# Always report window length with Res_pair (Nota 4: w=13 can bias chaos).
|
|
76
|
+
```
|
|
77
|
+
|
|
56
78
|
### Continuous excess³ only
|
|
57
79
|
|
|
58
80
|
```python
|
|
@@ -79,7 +101,7 @@ out = compute_recd_from_conjunctions(X, tau_s=tau_s)
|
|
|
79
101
|
out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
80
102
|
```
|
|
81
103
|
|
|
82
|
-
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For
|
|
104
|
+
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For **exportable / scalable** Level-3 reporting, use continuous **excess³** and nulls on `|Δ excess3|`. For **strong** Level-3 claims, report **`res_pair`** (enable `compute_res=True`) **and the window `w`**. Do not identify the two.
|
|
83
105
|
|
|
84
106
|
## What it computes
|
|
85
107
|
|
|
@@ -87,10 +109,11 @@ out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
|
87
109
|
|--------|---------|
|
|
88
110
|
| **Φ₁** | Co-occurrence of identical ordinal symbols across variable pairs |
|
|
89
111
|
| **Φ₂** | Persistence of pairwise ordinal relations over lag `d` |
|
|
90
|
-
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**
|
|
112
|
+
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**exportable Level-3 proxy**; T-IV) |
|
|
91
113
|
| **Φ₃** | Binary ticks of excess³ above `theta3` (**secondary**) |
|
|
114
|
+
| **Res_pair** | Strong Level-3 residual `KL(P ‖ P^(2))` (optional; always report `w`) |
|
|
92
115
|
| **λ** | Chaos / reorganization intensity (from `τ_s` or `lam_override`) |
|
|
93
|
-
| **α(λ)** |
|
|
116
|
+
| **α(λ)** | Design-engine weights (template or Gibbs-α); **not** a joint observable |
|
|
94
117
|
| **ΔRECD** | `α₁Φ₁ + α₂Φ₂ + α₃Φ₃` (legacy clock; binary Φ₃) |
|
|
95
118
|
| **T_recd** | Cumulative sum of ΔRECD |
|
|
96
119
|
|
|
@@ -115,7 +138,8 @@ from nested_recd import (
|
|
|
115
138
|
compute_phi1, compute_phi2, compute_phi3,
|
|
116
139
|
compute_phi3_excess, compute_excess3_window,
|
|
117
140
|
mean_excess_pre_post, surrogate_pvalue_delta_excess3,
|
|
118
|
-
compute_lambda, alpha_weights, regime_lambda_proxy,
|
|
141
|
+
compute_lambda, alpha_weights, alpha_weights_gibbs, regime_lambda_proxy,
|
|
142
|
+
compute_res_pair, compute_res_pair_window,
|
|
119
143
|
compute_weighted_contributions, high_level3_rate,
|
|
120
144
|
generate_multivariate_symbols,
|
|
121
145
|
phase_shuffle_independent, generate_surrogate_ensemble,
|
|
@@ -138,6 +162,12 @@ If you use excess³ / Level-3 from this package, cite the **methods** preprint (
|
|
|
138
162
|
|
|
139
163
|
> Padilla-Villanueva, J. (2026). *excess³: A Pre-Specified Continuous Proxy for Order-3 Synergistic Surplus* (methods). Zenodo. https://doi.org/10.5281/zenodo.21385937
|
|
140
164
|
|
|
165
|
+
This software (0.2.3):
|
|
166
|
+
|
|
167
|
+
> Padilla-Villanueva, J. (2026). *nested-recd 0.2.3: Nested ordinal RECD, excess³ proxy, and Res_pair*. Zenodo. https://doi.org/10.5281/zenodo.22970079
|
|
168
|
+
|
|
169
|
+
The previous release (0.2.2), which pooled symbols inside Syn, remains citable as https://doi.org/10.5281/zenodo.21937204.
|
|
170
|
+
|
|
141
171
|
Software / related stack:
|
|
142
172
|
|
|
143
173
|
> Padilla-Villanueva, J. (2026). Systemic Tau software archive. Zenodo. https://doi.org/10.5281/zenodo.20576241
|
|
@@ -152,7 +182,8 @@ CCTP/SDDB pilot:
|
|
|
152
182
|
title = {nested-recd: Nested ordinal RECD levels and continuous excess³},
|
|
153
183
|
year = {2026},
|
|
154
184
|
url = {https://github.com/johelpadilla/nested-recd},
|
|
155
|
-
version = {0.2.
|
|
185
|
+
version = {0.2.3},
|
|
186
|
+
doi = {10.5281/zenodo.22970079}
|
|
156
187
|
}
|
|
157
188
|
```
|
|
158
189
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nested-recd"
|
|
7
|
-
version = "0.2.
|
|
8
|
-
description = "Nested ordinal RECD: Φ1–Φ3, continuous excess³ (
|
|
7
|
+
version = "0.2.3"
|
|
8
|
+
description = "Nested ordinal RECD: Φ1–Φ3, continuous excess³ (exportable Level-3 proxy), Res_pair (strong residual), and λ-weighted Discrete Extramental Clock"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
11
11
|
license = "MIT"
|
|
@@ -49,7 +49,7 @@ Homepage = "https://github.com/johelpadilla/nested-recd"
|
|
|
49
49
|
Repository = "https://github.com/johelpadilla/nested-recd"
|
|
50
50
|
Issues = "https://github.com/johelpadilla/nested-recd/issues"
|
|
51
51
|
Documentation = "https://github.com/johelpadilla/nested-recd#readme"
|
|
52
|
-
DOI = "https://doi.org/10.5281/zenodo.
|
|
52
|
+
DOI = "https://doi.org/10.5281/zenodo.22970079"
|
|
53
53
|
|
|
54
54
|
[tool.setuptools.packages.find]
|
|
55
55
|
where = ["src"]
|
|
@@ -3,7 +3,8 @@ nested-recd
|
|
|
3
3
|
===========
|
|
4
4
|
|
|
5
5
|
Nested ordinal RECD: Φ₁ / Φ₂ / Φ₃ conjunction levels, continuous excess³
|
|
6
|
-
(
|
|
6
|
+
(exportable Level-3 proxy; not the strong residual), optional Res_pair,
|
|
7
|
+
and λ-weighted Discrete Extramental Clock.
|
|
7
8
|
|
|
8
9
|
Quick start
|
|
9
10
|
-----------
|
|
@@ -11,7 +12,7 @@ Quick start
|
|
|
11
12
|
>>> from nested_recd import compute_recd_from_conjunctions
|
|
12
13
|
>>> X = np.random.randn(500, 3).cumsum(axis=0)
|
|
13
14
|
>>> out = compute_recd_from_conjunctions(X)
|
|
14
|
-
>>> float(np.nanmean(out["excess3"])) # continuous
|
|
15
|
+
>>> float(np.nanmean(out["excess3"])) # continuous Level-3 *proxy* (T-IV)
|
|
15
16
|
"""
|
|
16
17
|
|
|
17
18
|
from nested_recd.ordinal_levels import (
|
|
@@ -32,10 +33,16 @@ from nested_recd.ordinal_levels import (
|
|
|
32
33
|
compute_phi3,
|
|
33
34
|
compute_phi3_excess,
|
|
34
35
|
compute_excess3_window,
|
|
36
|
+
compute_res_pair_window,
|
|
37
|
+
compute_res_pair,
|
|
38
|
+
pairwise_maxent_ipf,
|
|
39
|
+
kl_divergence,
|
|
35
40
|
mean_excess_pre_post,
|
|
36
41
|
surrogate_pvalue_delta_excess3,
|
|
37
42
|
compute_lambda,
|
|
38
43
|
alpha_weights,
|
|
44
|
+
alpha_weights_gibbs,
|
|
45
|
+
alpha_compare_template_gibbs,
|
|
39
46
|
regime_lambda_proxy,
|
|
40
47
|
compute_recd_from_conjunctions,
|
|
41
48
|
simple_level_classification,
|
|
@@ -51,7 +58,7 @@ from nested_recd.surrogates import (
|
|
|
51
58
|
compute_null_distribution,
|
|
52
59
|
)
|
|
53
60
|
|
|
54
|
-
__version__ = "0.2.
|
|
61
|
+
__version__ = "0.2.3"
|
|
55
62
|
|
|
56
63
|
__all__ = [
|
|
57
64
|
"__version__",
|
|
@@ -72,10 +79,16 @@ __all__ = [
|
|
|
72
79
|
"compute_phi3",
|
|
73
80
|
"compute_phi3_excess",
|
|
74
81
|
"compute_excess3_window",
|
|
82
|
+
"compute_res_pair_window",
|
|
83
|
+
"compute_res_pair",
|
|
84
|
+
"pairwise_maxent_ipf",
|
|
85
|
+
"kl_divergence",
|
|
75
86
|
"mean_excess_pre_post",
|
|
76
87
|
"surrogate_pvalue_delta_excess3",
|
|
77
88
|
"compute_lambda",
|
|
78
89
|
"alpha_weights",
|
|
90
|
+
"alpha_weights_gibbs",
|
|
91
|
+
"alpha_compare_template_gibbs",
|
|
79
92
|
"regime_lambda_proxy",
|
|
80
93
|
"compute_recd_from_conjunctions",
|
|
81
94
|
"simple_level_classification",
|
|
@@ -189,62 +189,76 @@ def compute_phi2(
|
|
|
189
189
|
# NIVEL 3: Emergencia / Sinergia (proxy)
|
|
190
190
|
# ============================================================
|
|
191
191
|
|
|
192
|
-
def
|
|
192
|
+
def _entropy_from_counts(counts: np.ndarray) -> float:
|
|
193
|
+
p = counts / counts.sum()
|
|
194
|
+
return float(-np.sum(p * np.log2(p + 1e-12)))
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _joint_entropy_and_marginals(
|
|
198
|
+
S_window: np.ndarray,
|
|
199
|
+
legacy_pooled_counting: bool = False,
|
|
200
|
+
) -> Tuple[float, float, float]:
|
|
193
201
|
"""
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
H(joint), sum of marginal entropies, and the Syn proxy on one window.
|
|
203
|
+
|
|
204
|
+
Correct joint counting (default since 0.2.3) uses
|
|
205
|
+
``np.unique(..., axis=0)`` on rows. ``legacy_pooled_counting=True``
|
|
206
|
+
reproduces <= 0.2.2, where ``np.unique`` on a list of tuples flattened
|
|
207
|
+
the tuples, so H_joint and each pairwise H were entropies of pooled
|
|
208
|
+
symbol values. Use the legacy mode only to reproduce archived numbers.
|
|
209
|
+
|
|
210
|
+
TC = sum_i H(S_i) - H(S)
|
|
211
|
+
MI = H(S_i) + H(S_j) - H(S_i, S_j)
|
|
212
|
+
Syn = max(0, TC - (N-1) * mean MI)
|
|
197
213
|
"""
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
214
|
+
S_window = np.asarray(S_window)
|
|
215
|
+
_, N = S_window.shape
|
|
216
|
+
|
|
217
|
+
if legacy_pooled_counting:
|
|
218
|
+
_, counts_j = np.unique(S_window.ravel(), return_counts=True)
|
|
219
|
+
else:
|
|
220
|
+
_, counts_j = np.unique(S_window, axis=0, return_counts=True)
|
|
221
|
+
H_joint = _entropy_from_counts(counts_j)
|
|
204
222
|
|
|
205
|
-
# Marginales por variable
|
|
206
223
|
H_margs = []
|
|
207
224
|
for k in range(N):
|
|
208
225
|
_, c = np.unique(S_window[:, k], return_counts=True)
|
|
209
|
-
|
|
210
|
-
H_margs.append(-np.sum(p * np.log2(p + 1e-12)))
|
|
211
|
-
H_marg_mean = float(np.mean(H_margs))
|
|
226
|
+
H_margs.append(_entropy_from_counts(c))
|
|
212
227
|
H_marg_sum = float(np.sum(H_margs))
|
|
213
228
|
|
|
214
|
-
# Pairwise MI promedio (aprox rápida)
|
|
215
229
|
pair_mi = []
|
|
216
230
|
for i in range(N):
|
|
217
231
|
for j in range(i + 1, N):
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Hi = -np.sum(pi * np.log2(pi + 1e-12))
|
|
226
|
-
|
|
227
|
-
_, cj2 = np.unique(S_window[:, j], return_counts=True)
|
|
228
|
-
pj2 = cj2 / cj2.sum()
|
|
229
|
-
Hj = -np.sum(pj2 * np.log2(pj2 + 1e-12))
|
|
230
|
-
|
|
231
|
-
mi = Hi + Hj - H2
|
|
232
|
-
pair_mi.append(max(0.0, mi))
|
|
232
|
+
pair = S_window[:, [i, j]]
|
|
233
|
+
if legacy_pooled_counting:
|
|
234
|
+
_, cj = np.unique(pair.ravel(), return_counts=True)
|
|
235
|
+
else:
|
|
236
|
+
_, cj = np.unique(pair, axis=0, return_counts=True)
|
|
237
|
+
H2 = _entropy_from_counts(cj)
|
|
238
|
+
pair_mi.append(max(0.0, H_margs[i] + H_margs[j] - H2))
|
|
233
239
|
mi_pair_avg = float(np.mean(pair_mi)) if pair_mi else 0.0
|
|
234
240
|
|
|
235
|
-
# Total correlation approx = sum H - H_joint
|
|
236
241
|
tc = H_marg_sum - H_joint
|
|
237
|
-
# "Synergy beyond pairwise" rough: tc - (N-1)*mi_pair_avg (heurística; puede ser negativa)
|
|
238
242
|
synergy_proxy = max(0.0, tc - (N - 1) * mi_pair_avg)
|
|
239
|
-
|
|
240
243
|
return H_joint, H_marg_sum, synergy_proxy
|
|
241
244
|
|
|
242
245
|
|
|
246
|
+
def _warn_legacy() -> None:
|
|
247
|
+
warnings.warn(
|
|
248
|
+
"legacy_pooled_counting=True reproduces the joint-counting bug of "
|
|
249
|
+
"nested-recd <= 0.2.2 (pooled symbol values instead of joint tuples "
|
|
250
|
+
"in H_joint and pairwise H). Use only to reproduce archived results.",
|
|
251
|
+
UserWarning,
|
|
252
|
+
stacklevel=3,
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
|
|
243
256
|
def compute_excess3_window(
|
|
244
257
|
win: np.ndarray,
|
|
245
258
|
use_surprise: bool = True,
|
|
246
259
|
alpha_syn: float = ALPHA_SYN,
|
|
247
260
|
alpha_surp: float = ALPHA_SURP,
|
|
261
|
+
legacy_pooled_counting: bool = False,
|
|
248
262
|
) -> float:
|
|
249
263
|
"""
|
|
250
264
|
excess³ score on a single window of joint ordinal symbols (shape (w, N)).
|
|
@@ -259,6 +273,9 @@ def compute_excess3_window(
|
|
|
259
273
|
Surp: observed-vs-independence joint surprise (frequency-weighted log-ratio).
|
|
260
274
|
|
|
261
275
|
This is a **proxy**, not a complete PID synergy atom.
|
|
276
|
+
|
|
277
|
+
``legacy_pooled_counting`` (default False) reproduces the <= 0.2.2
|
|
278
|
+
pooled-symbol bug. Surp is the same in both modes.
|
|
262
279
|
"""
|
|
263
280
|
win = np.asarray(win)
|
|
264
281
|
if win.ndim != 2 or win.shape[0] == 0:
|
|
@@ -266,7 +283,9 @@ def compute_excess3_window(
|
|
|
266
283
|
if win.shape[1] < 2:
|
|
267
284
|
return 0.0
|
|
268
285
|
|
|
269
|
-
_, _, syn = _joint_entropy_and_marginals(
|
|
286
|
+
_, _, syn = _joint_entropy_and_marginals(
|
|
287
|
+
win, legacy_pooled_counting=legacy_pooled_counting
|
|
288
|
+
)
|
|
270
289
|
if not use_surprise:
|
|
271
290
|
return float(syn)
|
|
272
291
|
|
|
@@ -297,6 +316,7 @@ def compute_phi3_excess(
|
|
|
297
316
|
alpha_syn: float = ALPHA_SYN,
|
|
298
317
|
alpha_surp: float = ALPHA_SURP,
|
|
299
318
|
fill: bool = False,
|
|
319
|
+
legacy_pooled_counting: bool = False,
|
|
300
320
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
301
321
|
"""
|
|
302
322
|
Continuous excess³ (primary) and binary Φ₃ (secondary) on symbol matrix S.
|
|
@@ -313,7 +333,11 @@ def compute_phi3_excess(
|
|
|
313
333
|
fill : bool
|
|
314
334
|
If True, forward-fill NaNs after the first finite score (plot convenience).
|
|
315
335
|
Default False preserves sparse stride semantics.
|
|
336
|
+
legacy_pooled_counting : bool
|
|
337
|
+
Default False (joint tuples). True reproduces the <= 0.2.2 bug.
|
|
316
338
|
"""
|
|
339
|
+
if legacy_pooled_counting:
|
|
340
|
+
_warn_legacy()
|
|
317
341
|
S = np.asarray(S)
|
|
318
342
|
T_eff, N = S.shape
|
|
319
343
|
phi3 = np.full(T_eff, np.nan)
|
|
@@ -328,6 +352,7 @@ def compute_phi3_excess(
|
|
|
328
352
|
use_surprise=use_surprise,
|
|
329
353
|
alpha_syn=alpha_syn,
|
|
330
354
|
alpha_surp=alpha_surp,
|
|
355
|
+
legacy_pooled_counting=legacy_pooled_counting,
|
|
331
356
|
)
|
|
332
357
|
excess[t] = score
|
|
333
358
|
phi3[t] = 1.0 if score > theta else 0.0
|
|
@@ -352,6 +377,7 @@ def compute_phi3(
|
|
|
352
377
|
use_surprise: bool = True,
|
|
353
378
|
alpha_syn: float = ALPHA_SYN,
|
|
354
379
|
alpha_surp: float = ALPHA_SURP,
|
|
380
|
+
legacy_pooled_counting: bool = False,
|
|
355
381
|
) -> Tuple[np.ndarray, np.ndarray]:
|
|
356
382
|
"""
|
|
357
383
|
Φ₃(t) binary + continuous excess³ series.
|
|
@@ -370,9 +396,201 @@ def compute_phi3(
|
|
|
370
396
|
alpha_syn=alpha_syn,
|
|
371
397
|
alpha_surp=alpha_surp,
|
|
372
398
|
fill=False,
|
|
399
|
+
legacy_pooled_counting=legacy_pooled_counting,
|
|
373
400
|
)
|
|
374
401
|
|
|
375
402
|
|
|
403
|
+
# ============================================================
|
|
404
|
+
# Strong Level-3 residual: Res_pair = KL(P || P^(2))
|
|
405
|
+
# ============================================================
|
|
406
|
+
|
|
407
|
+
def _empirical_joint_table(win: np.ndarray) -> Tuple[np.ndarray, list]:
|
|
408
|
+
"""
|
|
409
|
+
Build dense joint probability table over observed symbol alphabet.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
p : ndarray shape (k_0, k_1, ..., k_{N-1})
|
|
414
|
+
Empirical frequencies (sums to 1).
|
|
415
|
+
levels : list of 1d arrays
|
|
416
|
+
Sorted unique symbols per coordinate (axis order of ``p``).
|
|
417
|
+
"""
|
|
418
|
+
win = np.asarray(win)
|
|
419
|
+
if win.ndim != 2 or win.shape[0] == 0:
|
|
420
|
+
raise ValueError("win must be (w, N) with w>0")
|
|
421
|
+
T, N = win.shape
|
|
422
|
+
levels = [np.unique(win[:, i]) for i in range(N)]
|
|
423
|
+
shape = tuple(len(lev) for lev in levels)
|
|
424
|
+
# Map symbols to indices
|
|
425
|
+
idx = np.zeros((T, N), dtype=int)
|
|
426
|
+
for i in range(N):
|
|
427
|
+
# searchsorted works because levels[i] is sorted unique
|
|
428
|
+
idx[:, i] = np.searchsorted(levels[i], win[:, i])
|
|
429
|
+
p = np.zeros(shape, dtype=float)
|
|
430
|
+
for t in range(T):
|
|
431
|
+
p[tuple(idx[t])] += 1.0
|
|
432
|
+
p /= p.sum()
|
|
433
|
+
return p, levels
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _pair_marginal(p: np.ndarray, i: int, j: int) -> np.ndarray:
|
|
437
|
+
"""Marginal of joint tensor p over all axes except i and j."""
|
|
438
|
+
axes = tuple(a for a in range(p.ndim) if a not in (i, j))
|
|
439
|
+
if axes:
|
|
440
|
+
m = p.sum(axis=axes)
|
|
441
|
+
else:
|
|
442
|
+
m = p.copy()
|
|
443
|
+
# Ensure axes order (i, j): if i>j, sum order may swap
|
|
444
|
+
if i > j:
|
|
445
|
+
m = m.T
|
|
446
|
+
return m
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
def pairwise_maxent_ipf(
|
|
450
|
+
p_emp: np.ndarray,
|
|
451
|
+
max_iter: int = 200,
|
|
452
|
+
tol: float = 1e-10,
|
|
453
|
+
) -> np.ndarray:
|
|
454
|
+
"""
|
|
455
|
+
Fit discrete pairwise maxent / IPF model matching all bivariate marginals.
|
|
456
|
+
|
|
457
|
+
Starts from the product of univariate marginals and iteratively rescales
|
|
458
|
+
so that each pair marginal matches that of ``p_emp``. On finite alphabets
|
|
459
|
+
this is the standard iterative proportional fitting procedure toward the
|
|
460
|
+
I-projection onto the pairwise exponential family (when it exists in the
|
|
461
|
+
relative interior).
|
|
462
|
+
|
|
463
|
+
Parameters
|
|
464
|
+
----------
|
|
465
|
+
p_emp : ndarray
|
|
466
|
+
Empirical joint (N-way table).
|
|
467
|
+
max_iter, tol :
|
|
468
|
+
Convergence controls.
|
|
469
|
+
|
|
470
|
+
Returns
|
|
471
|
+
-------
|
|
472
|
+
p2 : ndarray
|
|
473
|
+
Pairwise-consistent joint with same shape as ``p_emp``.
|
|
474
|
+
"""
|
|
475
|
+
p_emp = np.asarray(p_emp, dtype=float)
|
|
476
|
+
if p_emp.ndim < 2:
|
|
477
|
+
return p_emp.copy()
|
|
478
|
+
N = p_emp.ndim
|
|
479
|
+
# Univariate marginals
|
|
480
|
+
marg1 = []
|
|
481
|
+
for i in range(N):
|
|
482
|
+
axes = tuple(a for a in range(N) if a != i)
|
|
483
|
+
mi = p_emp.sum(axis=axes)
|
|
484
|
+
marg1.append(mi)
|
|
485
|
+
# Initialize as product of marginals
|
|
486
|
+
p = marg1[0]
|
|
487
|
+
for i in range(1, N):
|
|
488
|
+
p = p[..., None] * marg1[i]
|
|
489
|
+
# p may need reshape to full shape
|
|
490
|
+
p = np.broadcast_to(p, p_emp.shape).astype(float).copy()
|
|
491
|
+
# Target pair marginals
|
|
492
|
+
targets = {}
|
|
493
|
+
for i in range(N):
|
|
494
|
+
for j in range(i + 1, N):
|
|
495
|
+
targets[(i, j)] = _pair_marginal(p_emp, i, j)
|
|
496
|
+
|
|
497
|
+
for _ in range(max_iter):
|
|
498
|
+
max_dev = 0.0
|
|
499
|
+
for i in range(N):
|
|
500
|
+
for j in range(i + 1, N):
|
|
501
|
+
cur = _pair_marginal(p, i, j)
|
|
502
|
+
tgt = targets[(i, j)]
|
|
503
|
+
# Avoid div by zero: only rescale cells with positive cur
|
|
504
|
+
ratio = np.ones_like(tgt)
|
|
505
|
+
mask = cur > 0
|
|
506
|
+
ratio[mask] = tgt[mask] / cur[mask]
|
|
507
|
+
# Broadcast ratio onto full joint
|
|
508
|
+
# Build scaling tensor
|
|
509
|
+
scale = ratio
|
|
510
|
+
# Insert singleton dims for axes other than i,j
|
|
511
|
+
shape = [1] * N
|
|
512
|
+
shape[i] = p.shape[i]
|
|
513
|
+
shape[j] = p.shape[j]
|
|
514
|
+
# ratio is (k_i, k_j); reshape
|
|
515
|
+
scale = scale.reshape(shape)
|
|
516
|
+
p = p * scale
|
|
517
|
+
# Renormalize lightly for numerical stability
|
|
518
|
+
s = p.sum()
|
|
519
|
+
if s > 0:
|
|
520
|
+
p /= s
|
|
521
|
+
max_dev = max(max_dev, float(np.max(np.abs(cur - tgt))))
|
|
522
|
+
if max_dev < tol:
|
|
523
|
+
break
|
|
524
|
+
return p
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
def kl_divergence(p: np.ndarray, q: np.ndarray, eps: float = 1e-12) -> float:
|
|
528
|
+
"""KL(p||q) in bits over a common support table."""
|
|
529
|
+
p = np.asarray(p, dtype=float)
|
|
530
|
+
q = np.asarray(q, dtype=float)
|
|
531
|
+
mask = p > 0
|
|
532
|
+
q_safe = np.maximum(q, eps)
|
|
533
|
+
return float(np.sum(p[mask] * np.log2(p[mask] / q_safe[mask])))
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def compute_res_pair_window(win: np.ndarray, max_iter: int = 200) -> float:
|
|
537
|
+
"""
|
|
538
|
+
Strong Level-3 residual on one window of joint symbols.
|
|
539
|
+
|
|
540
|
+
Res_pair = KL(P_emp || P^(2))
|
|
541
|
+
|
|
542
|
+
where P^(2) is the pairwise IPF / maxent fit to all bipolar marginals
|
|
543
|
+
(Foundations paper / Nota formal 2).
|
|
544
|
+
|
|
545
|
+
For N<3 the residual is identically 0 (no order-3 structure possible).
|
|
546
|
+
"""
|
|
547
|
+
win = np.asarray(win)
|
|
548
|
+
if win.ndim != 2 or win.shape[0] == 0:
|
|
549
|
+
return float("nan")
|
|
550
|
+
if win.shape[1] < 3:
|
|
551
|
+
return 0.0
|
|
552
|
+
try:
|
|
553
|
+
p_emp, _ = _empirical_joint_table(win)
|
|
554
|
+
except ValueError:
|
|
555
|
+
return float("nan")
|
|
556
|
+
# Degenerate: if support is too thin, IPF still runs on observed alphabet
|
|
557
|
+
p2 = pairwise_maxent_ipf(p_emp, max_iter=max_iter)
|
|
558
|
+
return kl_divergence(p_emp, p2)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
def compute_res_pair(
|
|
562
|
+
S: np.ndarray,
|
|
563
|
+
window: int = DEFAULT_WINDOW_TAU,
|
|
564
|
+
stride: int = 1,
|
|
565
|
+
max_iter: int = 200,
|
|
566
|
+
fill: bool = False,
|
|
567
|
+
) -> np.ndarray:
|
|
568
|
+
"""
|
|
569
|
+
Windowed strong Level-3 residual Res_pair(t) on symbol matrix S (T, N).
|
|
570
|
+
|
|
571
|
+
Primary theoretical Level-3 object; excess3 remains the scalable proxy.
|
|
572
|
+
Computational cost grows with alphabet product; intended for small N
|
|
573
|
+
(e.g. m=3 Bandt–Pompe, N<=4–5) or as a validation diagnostic.
|
|
574
|
+
"""
|
|
575
|
+
S = np.asarray(S)
|
|
576
|
+
T_eff, N = S.shape
|
|
577
|
+
out = np.full(T_eff, np.nan)
|
|
578
|
+
if T_eff < window or N < 3:
|
|
579
|
+
if N < 3:
|
|
580
|
+
out[:] = 0.0
|
|
581
|
+
return out
|
|
582
|
+
for t in range(window - 1, T_eff, stride):
|
|
583
|
+
out[t] = compute_res_pair_window(S[t - window + 1 : t + 1], max_iter=max_iter)
|
|
584
|
+
if fill:
|
|
585
|
+
last = np.nan
|
|
586
|
+
for t in range(T_eff):
|
|
587
|
+
if np.isfinite(out[t]):
|
|
588
|
+
last = out[t]
|
|
589
|
+
elif np.isfinite(last):
|
|
590
|
+
out[t] = last
|
|
591
|
+
return out
|
|
592
|
+
|
|
593
|
+
|
|
376
594
|
def mean_excess_pre_post(
|
|
377
595
|
excess3: np.ndarray,
|
|
378
596
|
split: int,
|
|
@@ -404,6 +622,7 @@ def surrogate_pvalue_delta_excess3(
|
|
|
404
622
|
delay: int = DEFAULT_DELAY,
|
|
405
623
|
alpha_syn: float = ALPHA_SYN,
|
|
406
624
|
alpha_surp: float = ALPHA_SURP,
|
|
625
|
+
legacy_pooled_counting: bool = False,
|
|
407
626
|
) -> Dict[str, float]:
|
|
408
627
|
"""
|
|
409
628
|
Two-sided surrogate p-value for |Δ excess3| under independent phase-shuffle
|
|
@@ -444,6 +663,7 @@ def surrogate_pvalue_delta_excess3(
|
|
|
444
663
|
stride=stride,
|
|
445
664
|
alpha_syn=alpha_syn,
|
|
446
665
|
alpha_surp=alpha_surp,
|
|
666
|
+
legacy_pooled_counting=legacy_pooled_counting,
|
|
447
667
|
)
|
|
448
668
|
_, _, d = mean_excess_pre_post(excess, split_s)
|
|
449
669
|
return float(d)
|
|
@@ -515,7 +735,10 @@ def alpha_weights(
|
|
|
515
735
|
delta3: float = 2.0
|
|
516
736
|
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
517
737
|
"""
|
|
518
|
-
|
|
738
|
+
Template α family (Via A / design engine defaults).
|
|
739
|
+
|
|
740
|
+
Returns α1(λ), α2(λ), α3(λ). Not normalized; used as relative weights
|
|
741
|
+
in ΔRECD and f_ℓ shares. Foundations Note 3.
|
|
519
742
|
"""
|
|
520
743
|
a1 = alpha10 * np.exp(-beta1 * lam)
|
|
521
744
|
a2 = alpha20 * (1.0 + gamma2 * lam)
|
|
@@ -523,6 +746,93 @@ def alpha_weights(
|
|
|
523
746
|
return a1, a2, a3
|
|
524
747
|
|
|
525
748
|
|
|
749
|
+
def alpha_weights_gibbs(
|
|
750
|
+
lam: np.ndarray,
|
|
751
|
+
u: Tuple[float, float, float] = (0.0, 1.0, 2.0),
|
|
752
|
+
beta0: float = 0.0,
|
|
753
|
+
kappa: float = 1.5,
|
|
754
|
+
normalize: bool = False,
|
|
755
|
+
) -> Tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
756
|
+
"""
|
|
757
|
+
Gibbs / maxent depth weights (Via B, Foundations Note 3).
|
|
758
|
+
|
|
759
|
+
α_ℓ^G(λ) ∝ exp( β(λ) · u(ℓ) ), β(λ) = β0 + κ·λ ≥ 0
|
|
760
|
+
|
|
761
|
+
with u(1) < u(2) < u(3). Relative monotonicity α3/α1 = exp(β·Δu)
|
|
762
|
+
increases in λ when κ≥0. This is an *admissible design engine*, not
|
|
763
|
+
an observable of the joint S_t.
|
|
764
|
+
|
|
765
|
+
Parameters
|
|
766
|
+
----------
|
|
767
|
+
lam : array-like
|
|
768
|
+
Regime intensity λ ≥ 0.
|
|
769
|
+
u : triple
|
|
770
|
+
Depth utilities (u1, u2, u3), strictly increasing recommended.
|
|
771
|
+
beta0, kappa :
|
|
772
|
+
β(λ) = max(0, β0 + κ·λ).
|
|
773
|
+
normalize :
|
|
774
|
+
If True, return simplex weights (sum_ℓ α_ℓ = 1). If False (default),
|
|
775
|
+
return unnormalized positive weights comparable in spirit to the
|
|
776
|
+
template family (which is also unnormalized).
|
|
777
|
+
|
|
778
|
+
Returns
|
|
779
|
+
-------
|
|
780
|
+
a1, a2, a3 : ndarrays
|
|
781
|
+
Gibbs depth weights.
|
|
782
|
+
"""
|
|
783
|
+
lam = np.asarray(lam, dtype=float)
|
|
784
|
+
u1, u2, u3 = u
|
|
785
|
+
beta = np.maximum(0.0, beta0 + kappa * lam)
|
|
786
|
+
e1 = np.exp(beta * u1)
|
|
787
|
+
e2 = np.exp(beta * u2)
|
|
788
|
+
e3 = np.exp(beta * u3)
|
|
789
|
+
if normalize:
|
|
790
|
+
z = e1 + e2 + e3
|
|
791
|
+
return e1 / z, e2 / z, e3 / z
|
|
792
|
+
return e1, e2, e3
|
|
793
|
+
|
|
794
|
+
|
|
795
|
+
def alpha_compare_template_gibbs(
|
|
796
|
+
lam_grid: Optional[np.ndarray] = None,
|
|
797
|
+
u: Tuple[float, float, float] = (0.0, 1.0, 2.0),
|
|
798
|
+
beta0: float = 0.0,
|
|
799
|
+
kappa: float = 1.5,
|
|
800
|
+
**template_kwargs,
|
|
801
|
+
) -> Dict[str, np.ndarray]:
|
|
802
|
+
"""
|
|
803
|
+
Compare normalized template vs Gibbs α on a λ grid (Conj. bridge Note 3).
|
|
804
|
+
|
|
805
|
+
Both families are L1-normalized per λ so shapes are comparable.
|
|
806
|
+
Returns dict with lam, a*_template, a*_gibbs, L1 and L∞ errors.
|
|
807
|
+
"""
|
|
808
|
+
if lam_grid is None:
|
|
809
|
+
lam_grid = np.linspace(0.0, 2.0, 41)
|
|
810
|
+
lam_grid = np.asarray(lam_grid, dtype=float)
|
|
811
|
+
t1, t2, t3 = alpha_weights(lam_grid, **template_kwargs)
|
|
812
|
+
g1, g2, g3 = alpha_weights_gibbs(
|
|
813
|
+
lam_grid, u=u, beta0=beta0, kappa=kappa, normalize=False
|
|
814
|
+
)
|
|
815
|
+
# L1 normalize each family
|
|
816
|
+
ts = t1 + t2 + t3
|
|
817
|
+
gs = g1 + g2 + g3
|
|
818
|
+
nt = (t1 / ts, t2 / ts, t3 / ts)
|
|
819
|
+
ng = (g1 / gs, g2 / gs, g3 / gs)
|
|
820
|
+
l1 = np.abs(nt[0] - ng[0]) + np.abs(nt[1] - ng[1]) + np.abs(nt[2] - ng[2])
|
|
821
|
+
linf = np.maximum.reduce(
|
|
822
|
+
[np.abs(nt[0] - ng[0]), np.abs(nt[1] - ng[1]), np.abs(nt[2] - ng[2])]
|
|
823
|
+
)
|
|
824
|
+
return {
|
|
825
|
+
"lam": lam_grid,
|
|
826
|
+
"t1": nt[0], "t2": nt[1], "t3": nt[2],
|
|
827
|
+
"g1": ng[0], "g2": ng[1], "g3": ng[2],
|
|
828
|
+
"L1": l1,
|
|
829
|
+
"Linf": linf,
|
|
830
|
+
"max_L1": np.array([float(np.max(l1))]),
|
|
831
|
+
"max_Linf": np.array([float(np.max(linf))]),
|
|
832
|
+
"mean_L1": np.array([float(np.mean(l1))]),
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
|
|
526
836
|
def regime_lambda_proxy(
|
|
527
837
|
r: float,
|
|
528
838
|
r_onset: float = 3.57,
|
|
@@ -561,17 +871,22 @@ def compute_recd_from_conjunctions(
|
|
|
561
871
|
stride: int = 1,
|
|
562
872
|
alpha_syn: float = ALPHA_SYN,
|
|
563
873
|
alpha_surp: float = ALPHA_SURP,
|
|
874
|
+
compute_res: bool = False,
|
|
875
|
+
legacy_pooled_counting: bool = False,
|
|
564
876
|
**alpha_kwargs
|
|
565
877
|
) -> Dict[str, np.ndarray]:
|
|
566
878
|
"""
|
|
567
879
|
Full nested ordinal RECD pipeline:
|
|
568
880
|
- Bandt–Pompe symbols
|
|
569
|
-
- Φ1, Φ2, Φ3 (binary) + continuous excess³ (
|
|
881
|
+
- Φ1, Φ2, Φ3 (binary) + continuous excess³ (exportable Level-3 proxy)
|
|
882
|
+
- optional ``res_pair`` strong residual (IPF / KL to pairwise maxent)
|
|
570
883
|
- λ and α(λ) regime weights
|
|
571
884
|
- ΔRECD and cumulative T_recd
|
|
572
885
|
|
|
573
|
-
Level-3 contract (excess³ methods):
|
|
574
|
-
- Continuous ``excess3`` is the primary
|
|
886
|
+
Level-3 contract (excess³ methods + foundations paper):
|
|
887
|
+
- Continuous ``excess3`` is the scalable primary *proxy* for order-3 claims.
|
|
888
|
+
- ``res_pair`` (if ``compute_res=True``) is the strong theoretical residual
|
|
889
|
+
Res_pair = KL(P || P^(2)); costly for large alphabets / N.
|
|
575
890
|
- ``phi3`` is a secondary threshold discretisation of excess3.
|
|
576
891
|
- ``delta_recd`` keeps the legacy λ-weighted clock using **binary** Φ₃
|
|
577
892
|
(back-compat with CCTP / Discrete Extramental Clock nesting). Do not
|
|
@@ -591,6 +906,12 @@ def compute_recd_from_conjunctions(
|
|
|
591
906
|
stride=stride,
|
|
592
907
|
alpha_syn=alpha_syn,
|
|
593
908
|
alpha_surp=alpha_surp,
|
|
909
|
+
legacy_pooled_counting=legacy_pooled_counting,
|
|
910
|
+
)
|
|
911
|
+
res_pair = (
|
|
912
|
+
compute_res_pair(S, window=window_tau, stride=stride)
|
|
913
|
+
if compute_res
|
|
914
|
+
else None
|
|
594
915
|
)
|
|
595
916
|
|
|
596
917
|
# Determinar λ: override > tau-derived > zero
|
|
@@ -619,7 +940,7 @@ def compute_recd_from_conjunctions(
|
|
|
619
940
|
delta_recd = a1 * phi1 + a2 * phi2 + a3 * phi3_safe
|
|
620
941
|
T_recd = np.nancumsum(delta_recd)
|
|
621
942
|
|
|
622
|
-
|
|
943
|
+
result = {
|
|
623
944
|
"S": S,
|
|
624
945
|
"phi1": phi1,
|
|
625
946
|
"phi2": phi2,
|
|
@@ -641,9 +962,15 @@ def compute_recd_from_conjunctions(
|
|
|
641
962
|
"alpha_surp": alpha_surp,
|
|
642
963
|
"level3_primary": "excess3",
|
|
643
964
|
"level3_secondary": "phi3",
|
|
965
|
+
"level3_strong": "res_pair",
|
|
966
|
+
"compute_res": compute_res,
|
|
967
|
+
"legacy_pooled_counting": legacy_pooled_counting,
|
|
644
968
|
**alpha_kwargs,
|
|
645
969
|
},
|
|
646
970
|
}
|
|
971
|
+
if res_pair is not None:
|
|
972
|
+
result["res_pair"] = res_pair
|
|
973
|
+
return result
|
|
647
974
|
|
|
648
975
|
|
|
649
976
|
# ============================================================
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nested-recd
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Nested ordinal RECD: Φ1–Φ3, continuous excess³ (
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Nested ordinal RECD: Φ1–Φ3, continuous excess³ (exportable Level-3 proxy), Res_pair (strong residual), and λ-weighted Discrete Extramental Clock
|
|
5
5
|
Author-email: Johel Padilla-Villanueva <joel.padilla2@upr.edu>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/johelpadilla/nested-recd
|
|
8
8
|
Project-URL: Repository, https://github.com/johelpadilla/nested-recd
|
|
9
9
|
Project-URL: Issues, https://github.com/johelpadilla/nested-recd/issues
|
|
10
10
|
Project-URL: Documentation, https://github.com/johelpadilla/nested-recd#readme
|
|
11
|
-
Project-URL: DOI, https://doi.org/10.5281/zenodo.
|
|
11
|
+
Project-URL: DOI, https://doi.org/10.5281/zenodo.22970079
|
|
12
12
|
Keywords: RECD,ordinal patterns,nested time,Systemic Tau,complexity,early-warning,network physiology,Bandt-Pompe
|
|
13
13
|
Classifier: Development Status :: 4 - Beta
|
|
14
14
|
Classifier: Intended Audience :: Science/Research
|
|
@@ -35,13 +35,26 @@ Dynamic: license-file
|
|
|
35
35
|
|
|
36
36
|
# nested-recd
|
|
37
37
|
|
|
38
|
+
> **Correction (0.2.3).** Versions ≤ 0.2.2 computed `H_joint` and the pairwise
|
|
39
|
+
> entropies inside `Syn` with `np.unique` on a list of tuples and no `axis=0`.
|
|
40
|
+
> NumPy flattens that input, so `Syn`, `excess3` and binary `phi3` were not the
|
|
41
|
+
> published definition when `N ≥ 3`. `Surp` and `Res_pair` were not affected.
|
|
42
|
+
> For `N = 2`, `Syn` is 0 either way. 0.2.3 counts joint rows.
|
|
43
|
+
> `legacy_pooled_counting=True` reproduces the old numbers and warns.
|
|
44
|
+
> See `CHANGELOG.md`. This version:
|
|
45
|
+
> [10.5281/zenodo.22970079](https://doi.org/10.5281/zenodo.22970079).
|
|
46
|
+
> The 0.2.2 deposit remains
|
|
47
|
+
> [10.5281/zenodo.21937204](https://doi.org/10.5281/zenodo.21937204).
|
|
48
|
+
|
|
38
49
|
[](https://pypi.org/project/nested-recd/)
|
|
39
50
|
[](https://pypi.org/project/nested-recd/)
|
|
40
51
|
[](LICENSE)
|
|
41
52
|
|
|
42
|
-
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 proxy, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
53
|
+
**Nested ordinal RECD** — pure-NumPy implementation of nested ordinal conjunction levels (Φ₁, Φ₂, Φ₃), the continuous **excess³** Level-3 *proxy* (T-IV; not the strong residual), optional **Res_pair** \(=\mathrm{KL}(P\|P^{(2)})\), Gibbs-α weights, and λ-weighted Discrete Extramental Clock (RECD) accumulation.
|
|
54
|
+
|
|
55
|
+
This is the **canonical nested-clock software** for the Systemic Tau / RECD stack (foundations paper, excess³ methods preprint, cardiac CCTP/SDDB pilot, Academy Learning Tau).
|
|
43
56
|
|
|
44
|
-
|
|
57
|
+
**Léxico (no negociable):** *strong Level-3* := `Res_pair`. *excess3* := exportable proxy. Report `w` with any Res_pair figure. Abundance (excess3) ≠ aggregation share \(f_3\).
|
|
45
58
|
|
|
46
59
|
## Install
|
|
47
60
|
|
|
@@ -88,6 +101,15 @@ print("weights:", out["params"]["alpha_syn"], out["params"]["alpha_surp"])
|
|
|
88
101
|
print("final T_recd:", float(out["T_recd"][-1]))
|
|
89
102
|
```
|
|
90
103
|
|
|
104
|
+
### Strong residual Res_pair (optional)
|
|
105
|
+
|
|
106
|
+
```python
|
|
107
|
+
out = compute_recd_from_conjunctions(X, compute_res=True)
|
|
108
|
+
# or, on a symbol window:
|
|
109
|
+
from nested_recd import compute_res_pair_window
|
|
110
|
+
# Always report window length with Res_pair (Nota 4: w=13 can bias chaos).
|
|
111
|
+
```
|
|
112
|
+
|
|
91
113
|
### Continuous excess³ only
|
|
92
114
|
|
|
93
115
|
```python
|
|
@@ -114,7 +136,7 @@ out = compute_recd_from_conjunctions(X, tau_s=tau_s)
|
|
|
114
136
|
out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
115
137
|
```
|
|
116
138
|
|
|
117
|
-
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For
|
|
139
|
+
**Note:** `delta_recd` / `T_recd` use **binary Φ₃** in the α-weighted clock (legacy Discrete Extramental Clock). For **exportable / scalable** Level-3 reporting, use continuous **excess³** and nulls on `|Δ excess3|`. For **strong** Level-3 claims, report **`res_pair`** (enable `compute_res=True`) **and the window `w`**. Do not identify the two.
|
|
118
140
|
|
|
119
141
|
## What it computes
|
|
120
142
|
|
|
@@ -122,10 +144,11 @@ out = compute_recd_from_conjunctions(X, lam_override=0.5)
|
|
|
122
144
|
|--------|---------|
|
|
123
145
|
| **Φ₁** | Co-occurrence of identical ordinal symbols across variable pairs |
|
|
124
146
|
| **Φ₂** | Persistence of pairwise ordinal relations over lag `d` |
|
|
125
|
-
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**
|
|
147
|
+
| **excess³** | Continuous hybrid: `0.6·Syn + 0.4·Surp` (**exportable Level-3 proxy**; T-IV) |
|
|
126
148
|
| **Φ₃** | Binary ticks of excess³ above `theta3` (**secondary**) |
|
|
149
|
+
| **Res_pair** | Strong Level-3 residual `KL(P ‖ P^(2))` (optional; always report `w`) |
|
|
127
150
|
| **λ** | Chaos / reorganization intensity (from `τ_s` or `lam_override`) |
|
|
128
|
-
| **α(λ)** |
|
|
151
|
+
| **α(λ)** | Design-engine weights (template or Gibbs-α); **not** a joint observable |
|
|
129
152
|
| **ΔRECD** | `α₁Φ₁ + α₂Φ₂ + α₃Φ₃` (legacy clock; binary Φ₃) |
|
|
130
153
|
| **T_recd** | Cumulative sum of ΔRECD |
|
|
131
154
|
|
|
@@ -150,7 +173,8 @@ from nested_recd import (
|
|
|
150
173
|
compute_phi1, compute_phi2, compute_phi3,
|
|
151
174
|
compute_phi3_excess, compute_excess3_window,
|
|
152
175
|
mean_excess_pre_post, surrogate_pvalue_delta_excess3,
|
|
153
|
-
compute_lambda, alpha_weights, regime_lambda_proxy,
|
|
176
|
+
compute_lambda, alpha_weights, alpha_weights_gibbs, regime_lambda_proxy,
|
|
177
|
+
compute_res_pair, compute_res_pair_window,
|
|
154
178
|
compute_weighted_contributions, high_level3_rate,
|
|
155
179
|
generate_multivariate_symbols,
|
|
156
180
|
phase_shuffle_independent, generate_surrogate_ensemble,
|
|
@@ -173,6 +197,12 @@ If you use excess³ / Level-3 from this package, cite the **methods** preprint (
|
|
|
173
197
|
|
|
174
198
|
> Padilla-Villanueva, J. (2026). *excess³: A Pre-Specified Continuous Proxy for Order-3 Synergistic Surplus* (methods). Zenodo. https://doi.org/10.5281/zenodo.21385937
|
|
175
199
|
|
|
200
|
+
This software (0.2.3):
|
|
201
|
+
|
|
202
|
+
> Padilla-Villanueva, J. (2026). *nested-recd 0.2.3: Nested ordinal RECD, excess³ proxy, and Res_pair*. Zenodo. https://doi.org/10.5281/zenodo.22970079
|
|
203
|
+
|
|
204
|
+
The previous release (0.2.2), which pooled symbols inside Syn, remains citable as https://doi.org/10.5281/zenodo.21937204.
|
|
205
|
+
|
|
176
206
|
Software / related stack:
|
|
177
207
|
|
|
178
208
|
> Padilla-Villanueva, J. (2026). Systemic Tau software archive. Zenodo. https://doi.org/10.5281/zenodo.20576241
|
|
@@ -187,7 +217,8 @@ CCTP/SDDB pilot:
|
|
|
187
217
|
title = {nested-recd: Nested ordinal RECD levels and continuous excess³},
|
|
188
218
|
year = {2026},
|
|
189
219
|
url = {https://github.com/johelpadilla/nested-recd},
|
|
190
|
-
version = {0.2.
|
|
220
|
+
version = {0.2.3},
|
|
221
|
+
doi = {10.5281/zenodo.22970079}
|
|
191
222
|
}
|
|
192
223
|
```
|
|
193
224
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Regression tests for the joint-counting fix (0.2.3).
|
|
2
|
+
|
|
3
|
+
nested-recd <= 0.2.2 computed H_joint and the pairwise H(S_i, S_j) inside
|
|
4
|
+
``_joint_entropy_and_marginals`` over pooled symbol values (np.unique on a
|
|
5
|
+
list of tuples flattens it). These tests pin the joint-tuple values.
|
|
6
|
+
``test_xor_synergy_is_one_bit`` and ``test_joint_entropy_counts_tuples``
|
|
7
|
+
fail on 0.2.2 and pass on 0.2.3.
|
|
8
|
+
"""
|
|
9
|
+
import inspect
|
|
10
|
+
|
|
11
|
+
import numpy as np
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from nested_recd import compute_excess3_window, compute_phi3_excess
|
|
15
|
+
from nested_recd.ordinal_levels import _joint_entropy_and_marginals
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _xor_window(reps: int = 3) -> np.ndarray:
|
|
19
|
+
# columns: a, b independent fair bits; c = a XOR b; d constant
|
|
20
|
+
base = np.array(
|
|
21
|
+
[
|
|
22
|
+
[0, 0, 0, 0],
|
|
23
|
+
[0, 1, 1, 0],
|
|
24
|
+
[1, 0, 1, 0],
|
|
25
|
+
[1, 1, 0, 0],
|
|
26
|
+
]
|
|
27
|
+
)
|
|
28
|
+
return np.tile(base, (reps, 1))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_xor_synergy_is_one_bit():
|
|
32
|
+
"""XOR: every pair is independent (MI = 0) but the triple carries 1 bit.
|
|
33
|
+
|
|
34
|
+
H_joint = 2, sum H_i = 3, TC = 1, mean MI = 0 -> Syn = 1 bit.
|
|
35
|
+
0.2.2 returns H_joint = 0.954 and Syn = 0.263.
|
|
36
|
+
"""
|
|
37
|
+
H_joint, H_sum, syn = _joint_entropy_and_marginals(_xor_window())
|
|
38
|
+
assert H_joint == pytest.approx(2.0, abs=1e-9)
|
|
39
|
+
assert H_sum == pytest.approx(3.0, abs=1e-9)
|
|
40
|
+
assert syn == pytest.approx(1.0, abs=1e-9)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_joint_entropy_counts_tuples():
|
|
44
|
+
"""13 distinct joint tuples in a window of 13 -> H_joint = log2(13)."""
|
|
45
|
+
rng = np.random.default_rng(0)
|
|
46
|
+
while True:
|
|
47
|
+
win = rng.integers(0, 6, size=(13, 4))
|
|
48
|
+
if len({tuple(r) for r in win}) == 13:
|
|
49
|
+
break
|
|
50
|
+
H_joint, _, _ = _joint_entropy_and_marginals(win)
|
|
51
|
+
assert H_joint == pytest.approx(np.log2(13), abs=1e-9)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_identical_columns_have_no_synergy():
|
|
55
|
+
"""Four copies of one series: TC = 3 H, every MI = H -> Syn = 0."""
|
|
56
|
+
rng = np.random.default_rng(1)
|
|
57
|
+
x = rng.integers(0, 6, size=40)
|
|
58
|
+
win = np.stack([x, x, x, x], axis=1)
|
|
59
|
+
H_joint, H_sum, syn = _joint_entropy_and_marginals(win)
|
|
60
|
+
assert H_sum == pytest.approx(4 * H_joint, abs=1e-9)
|
|
61
|
+
assert syn == pytest.approx(0.0, abs=1e-9)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_matches_counter_reference():
|
|
65
|
+
"""Random windows agree with an independent Counter-over-tuples version."""
|
|
66
|
+
from collections import Counter
|
|
67
|
+
|
|
68
|
+
def H(counter):
|
|
69
|
+
c = np.array(list(counter.values()), float)
|
|
70
|
+
p = c / c.sum()
|
|
71
|
+
return float(-np.sum(p * np.log2(p + 1e-12)))
|
|
72
|
+
|
|
73
|
+
rng = np.random.default_rng(2)
|
|
74
|
+
for _ in range(200):
|
|
75
|
+
win = rng.integers(0, 6, size=(13, 4))
|
|
76
|
+
N = win.shape[1]
|
|
77
|
+
rows = [tuple(r) for r in win.tolist()]
|
|
78
|
+
Hj = H(Counter(rows))
|
|
79
|
+
Hm = [H(Counter(win[:, k].tolist())) for k in range(N)]
|
|
80
|
+
mi = [
|
|
81
|
+
max(
|
|
82
|
+
0.0,
|
|
83
|
+
Hm[i]
|
|
84
|
+
+ Hm[j]
|
|
85
|
+
- H(Counter(zip(win[:, i].tolist(), win[:, j].tolist()))),
|
|
86
|
+
)
|
|
87
|
+
for i in range(N)
|
|
88
|
+
for j in range(i + 1, N)
|
|
89
|
+
]
|
|
90
|
+
syn_ref = max(0.0, sum(Hm) - Hj - (N - 1) * np.mean(mi))
|
|
91
|
+
H_joint, H_sum, syn = _joint_entropy_and_marginals(win)
|
|
92
|
+
assert H_joint == pytest.approx(Hj, abs=1e-12)
|
|
93
|
+
assert syn == pytest.approx(syn_ref, abs=1e-12)
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_legacy_flag_reproduces_022():
|
|
97
|
+
"""legacy_pooled_counting=True reproduces nested-recd 0.2.2."""
|
|
98
|
+
assert "legacy_pooled_counting" in inspect.signature(
|
|
99
|
+
_joint_entropy_and_marginals
|
|
100
|
+
).parameters
|
|
101
|
+
H_joint, _, syn = _joint_entropy_and_marginals(
|
|
102
|
+
_xor_window(), legacy_pooled_counting=True
|
|
103
|
+
)
|
|
104
|
+
assert H_joint == pytest.approx(LEGACY_XOR_HJOINT, abs=1e-12)
|
|
105
|
+
assert syn == pytest.approx(LEGACY_XOR_SYN, abs=1e-12)
|
|
106
|
+
rng = np.random.default_rng(3)
|
|
107
|
+
win = rng.integers(0, 6, size=(13, 4))
|
|
108
|
+
assert compute_excess3_window(
|
|
109
|
+
win, legacy_pooled_counting=True
|
|
110
|
+
) == pytest.approx(LEGACY_RANDOM_EXCESS3, abs=1e-12)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_legacy_flag_warns():
|
|
114
|
+
S = np.random.default_rng(4).integers(0, 6, size=(40, 4))
|
|
115
|
+
with pytest.warns(UserWarning, match="0.2.2"):
|
|
116
|
+
compute_phi3_excess(S, legacy_pooled_counting=True)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_surp_unchanged_by_fix():
|
|
120
|
+
"""Surp always counted tuples: with Syn switched off both modes agree."""
|
|
121
|
+
rng = np.random.default_rng(5)
|
|
122
|
+
for _ in range(50):
|
|
123
|
+
win = rng.integers(0, 6, size=(13, 4))
|
|
124
|
+
a = compute_excess3_window(win, alpha_syn=0.0, alpha_surp=1.0)
|
|
125
|
+
b = compute_excess3_window(
|
|
126
|
+
win, alpha_syn=0.0, alpha_surp=1.0, legacy_pooled_counting=True
|
|
127
|
+
)
|
|
128
|
+
assert a == pytest.approx(b, abs=1e-15)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_bivariate_synergy_is_zero():
|
|
132
|
+
"""For N=2, Syn = TC - MI of that single pair, so both modes give 0."""
|
|
133
|
+
rng = np.random.default_rng(8)
|
|
134
|
+
win = rng.integers(0, 6, size=(13, 2))
|
|
135
|
+
_, _, syn = _joint_entropy_and_marginals(win)
|
|
136
|
+
_, _, legacy = _joint_entropy_and_marginals(win, legacy_pooled_counting=True)
|
|
137
|
+
assert syn == pytest.approx(0.0, abs=1e-12)
|
|
138
|
+
assert legacy == pytest.approx(0.0, abs=1e-12)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Frozen outputs of nested-recd 0.2.2
|
|
142
|
+
LEGACY_XOR_HJOINT = 0.9544340029220797
|
|
143
|
+
LEGACY_XOR_SYN = 0.2624831837630126
|
|
144
|
+
LEGACY_RANDOM_EXCESS3 = 2.355186195068367
|
|
@@ -9,11 +9,15 @@ from nested_recd import (
|
|
|
9
9
|
ALPHA_SURP,
|
|
10
10
|
DEFAULT_THETA3,
|
|
11
11
|
DEFAULT_THETA3_CARDIO,
|
|
12
|
+
alpha_weights,
|
|
13
|
+
alpha_weights_gibbs,
|
|
14
|
+
alpha_compare_template_gibbs,
|
|
12
15
|
compute_recd_from_conjunctions,
|
|
13
16
|
compute_phi1,
|
|
14
17
|
compute_phi3,
|
|
15
18
|
compute_phi3_excess,
|
|
16
19
|
compute_excess3_window,
|
|
20
|
+
compute_res_pair_window,
|
|
17
21
|
compute_weighted_contributions,
|
|
18
22
|
generate_multivariate_symbols,
|
|
19
23
|
mean_excess_pre_post,
|
|
@@ -24,7 +28,29 @@ from nested_recd import (
|
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
def test_version():
|
|
27
|
-
assert __version__ == "0.2.
|
|
31
|
+
assert __version__ == "0.2.3"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_gibbs_alpha_monotonicity():
|
|
35
|
+
"""α3/α1 increases with λ when κ>0 (Note 3 Prop. Gibbs admissible)."""
|
|
36
|
+
lam = np.array([0.0, 0.5, 1.0, 2.0])
|
|
37
|
+
a1, a2, a3 = alpha_weights_gibbs(lam, kappa=1.5, normalize=False)
|
|
38
|
+
ratio = a3 / np.maximum(a1, 1e-15)
|
|
39
|
+
assert np.all(np.diff(ratio) >= -1e-12)
|
|
40
|
+
assert float(ratio[-1]) > float(ratio[0])
|
|
41
|
+
# normalized simplex
|
|
42
|
+
n1, n2, n3 = alpha_weights_gibbs(lam, normalize=True)
|
|
43
|
+
assert np.allclose(n1 + n2 + n3, 1.0)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def test_template_gibbs_bridge_bounded():
|
|
47
|
+
"""Conj. bridge: L∞ distance of normalized families on [0,2] is finite."""
|
|
48
|
+
cmp = alpha_compare_template_gibbs()
|
|
49
|
+
assert float(cmp["max_Linf"][0]) < 1.0
|
|
50
|
+
assert float(cmp["max_L1"][0]) < 2.0
|
|
51
|
+
# both put more mass on level 3 at high λ than at λ=0
|
|
52
|
+
assert float(cmp["t3"][-1]) > float(cmp["t3"][0])
|
|
53
|
+
assert float(cmp["g3"][-1]) > float(cmp["g3"][0])
|
|
28
54
|
|
|
29
55
|
|
|
30
56
|
def test_weights_a_priori():
|
|
@@ -132,6 +158,42 @@ def test_excess3_window_weights():
|
|
|
132
158
|
assert score >= 0.0
|
|
133
159
|
|
|
134
160
|
|
|
161
|
+
def test_res_pair_xor_vs_common_driver():
|
|
162
|
+
"""Strong L3: XOR residual > common-driver residual (Nota 2 geometry)."""
|
|
163
|
+
rng = np.random.default_rng(42)
|
|
164
|
+
n = 400
|
|
165
|
+
# XOR / parity lock
|
|
166
|
+
a = rng.integers(0, 2, size=n)
|
|
167
|
+
b = rng.integers(0, 2, size=n)
|
|
168
|
+
c = a ^ b
|
|
169
|
+
win_xor = np.column_stack([a, b, c]).astype(int)
|
|
170
|
+
res_xor = compute_res_pair_window(win_xor)
|
|
171
|
+
# Common driver: b,c ≈ a
|
|
172
|
+
driver = rng.integers(0, 2, size=n)
|
|
173
|
+
noise = rng.random(n) < 0.05
|
|
174
|
+
b2 = np.where(noise, 1 - driver, driver)
|
|
175
|
+
noise2 = rng.random(n) < 0.05
|
|
176
|
+
c2 = np.where(noise2, 1 - driver, driver)
|
|
177
|
+
win_drv = np.column_stack([driver, b2, c2]).astype(int)
|
|
178
|
+
res_drv = compute_res_pair_window(win_drv)
|
|
179
|
+
assert res_xor >= 0.0 and res_drv >= 0.0
|
|
180
|
+
assert res_xor > res_drv + 0.05 # clear geometric separation
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def test_res_pair_d2_is_zero():
|
|
184
|
+
win = np.column_stack([np.zeros(30, dtype=int), np.ones(30, dtype=int)])
|
|
185
|
+
assert compute_res_pair_window(win) == 0.0
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_pipeline_optional_res_pair():
|
|
189
|
+
rng = np.random.default_rng(9)
|
|
190
|
+
X = rng.normal(size=(180, 3)).cumsum(axis=0)
|
|
191
|
+
out = compute_recd_from_conjunctions(X, compute_res=True, window_tau=20, stride=5)
|
|
192
|
+
assert "res_pair" in out
|
|
193
|
+
assert out["res_pair"].shape == out["excess3"].shape
|
|
194
|
+
assert np.nanmin(out["res_pair"][np.isfinite(out["res_pair"])]) >= -1e-9
|
|
195
|
+
|
|
196
|
+
|
|
135
197
|
def test_mean_excess_pre_post():
|
|
136
198
|
x = np.array([0.0, 0.0, 1.0, 1.0, 1.0])
|
|
137
199
|
pre, post, delta = mean_excess_pre_post(x, split=2)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|