zpower 1.0.0__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.
Files changed (39) hide show
  1. zpower-1.0.0/PKG-INFO +157 -0
  2. zpower-1.0.0/README.md +128 -0
  3. zpower-1.0.0/pyproject.toml +32 -0
  4. zpower-1.0.0/setup.cfg +4 -0
  5. zpower-1.0.0/tests/test_integration.py +121 -0
  6. zpower-1.0.0/tests/test_nipgraph.py +102 -0
  7. zpower-1.0.0/tests/test_otux.py +134 -0
  8. zpower-1.0.0/tests/test_safe_math.py +92 -0
  9. zpower-1.0.0/tests/test_stabilize.py +140 -0
  10. zpower-1.0.0/tests/test_weights.py +153 -0
  11. zpower-1.0.0/zpower/__init__.py +241 -0
  12. zpower-1.0.0/zpower/_trainer.py +113 -0
  13. zpower-1.0.0/zpower/compat/__init__.py +4 -0
  14. zpower-1.0.0/zpower/compat/torch_wrap.py +178 -0
  15. zpower-1.0.0/zpower/compat/transformers_wrap.py +38 -0
  16. zpower-1.0.0/zpower/compute/__init__.py +3 -0
  17. zpower-1.0.0/zpower/compute/safe_math.py +179 -0
  18. zpower-1.0.0/zpower/memory/__init__.py +3 -0
  19. zpower-1.0.0/zpower/memory/otux.py +322 -0
  20. zpower-1.0.0/zpower/monitor/__init__.py +3 -0
  21. zpower-1.0.0/zpower/monitor/nipgraph.py +176 -0
  22. zpower-1.0.0/zpower/stabilize/__init__.py +5 -0
  23. zpower-1.0.0/zpower/stabilize/grad_shield.py +151 -0
  24. zpower-1.0.0/zpower/stabilize/model_stabilizer.py +94 -0
  25. zpower-1.0.0/zpower/stabilize/stability_core.py +143 -0
  26. zpower-1.0.0/zpower/utils/__init__.py +5 -0
  27. zpower-1.0.0/zpower/utils/config.py +59 -0
  28. zpower-1.0.0/zpower/utils/health.py +16 -0
  29. zpower-1.0.0/zpower/utils/logging.py +24 -0
  30. zpower-1.0.0/zpower/weights/__init__.py +11 -0
  31. zpower-1.0.0/zpower/weights/fisher.py +98 -0
  32. zpower-1.0.0/zpower/weights/guard.py +143 -0
  33. zpower-1.0.0/zpower/weights/surgeon.py +204 -0
  34. zpower-1.0.0/zpower/weights/vault.py +234 -0
  35. zpower-1.0.0/zpower.egg-info/PKG-INFO +157 -0
  36. zpower-1.0.0/zpower.egg-info/SOURCES.txt +37 -0
  37. zpower-1.0.0/zpower.egg-info/dependency_links.txt +1 -0
  38. zpower-1.0.0/zpower.egg-info/requires.txt +24 -0
  39. zpower-1.0.0/zpower.egg-info/top_level.txt +1 -0
zpower-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,157 @@
1
+ Metadata-Version: 2.4
2
+ Name: zpower
3
+ Version: 1.0.0
4
+ Summary: Intelligence layer for AI/ML — stabilization, selective memory, weight surgery
5
+ Author: NNN Bhoi
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nnbhoi/zpower
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: numpy>=1.24
11
+ Provides-Extra: torch
12
+ Requires-Dist: torch>=2.0; extra == "torch"
13
+ Provides-Extra: hf
14
+ Requires-Dist: transformers>=4.35; extra == "hf"
15
+ Requires-Dist: torch>=2.0; extra == "hf"
16
+ Provides-Extra: audio
17
+ Requires-Dist: torch>=2.0; extra == "audio"
18
+ Requires-Dist: torchaudio>=2.0; extra == "audio"
19
+ Provides-Extra: full
20
+ Requires-Dist: torch>=2.0; extra == "full"
21
+ Requires-Dist: transformers>=4.35; extra == "full"
22
+ Requires-Dist: pandas>=2.0; extra == "full"
23
+ Requires-Dist: scikit-learn>=1.3; extra == "full"
24
+ Requires-Dist: torchaudio>=2.0; extra == "full"
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: black; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+
30
+ # ZPower (zp) — v1
31
+
32
+ **Intelligence layer for AI/ML systems.**
33
+ Works *with* PyTorch and HuggingFace — not against them.
34
+
35
+ > Stabilize training. Protect good weights. Detect anomalies. Reduce wasted compute.
36
+
37
+ ---
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ pip install zpower # numpy only (core features)
43
+ pip install zpower[torch] # + PyTorch support
44
+ pip install zpower[hf] # + HuggingFace Transformers
45
+ pip install zpower[full] # everything
46
+ ```
47
+
48
+ ---
49
+
50
+ ## Quick Start
51
+
52
+ ### Mode A — Pre-trained model (attach ZPower)
53
+
54
+ ```python
55
+ import zpower as zp
56
+
57
+ # Any PyTorch or HuggingFace model
58
+ zp_model = zp.attach(
59
+ my_model,
60
+ stabilize = True, # GradShield + StabilityCore
61
+ monitor = True, # NipGraph anomaly detection
62
+ weight_vault = True, # Record best weight checkpoints
63
+ )
64
+
65
+ # Forward pass — completely unchanged
66
+ output = zp_model(input_tensor)
67
+
68
+ # After loss + backward — ZPower hooks are active automatically
69
+ loss.backward()
70
+ ```
71
+
72
+ ### Mode B — Training from scratch
73
+
74
+ ```python
75
+ import zpower as zp
76
+
77
+ trainer = zp.Trainer(
78
+ my_model,
79
+ stabilize = True,
80
+ weight_vault = True,
81
+ weight_guard = False, # enable after first good run
82
+ )
83
+
84
+ trainer.fit(train_loader, epochs=20, lr=1e-3)
85
+
86
+ print(trainer.weight_report())
87
+ # { layers_vaulted: 12, total_snapshots: 47, overall_vault_score: 0.84 }
88
+
89
+ trainer.render_monitor()
90
+ # NipGraph ASCII dashboard
91
+ ```
92
+
93
+ ### Multi-model weight selection
94
+
95
+ ```python
96
+ from zpower.weights import WeightSurgeon
97
+
98
+ surgeon = WeightSurgeon(conflict_resolution="highest_performer")
99
+ surgeon.add_source("run_jan.pt", label="jan", perf_score=0.72)
100
+ surgeon.add_source("run_mar.pt", label="mar", perf_score=0.89)
101
+ surgeon.add_source("run_may.pt", label="may", perf_score=0.81)
102
+
103
+ best_weights = surgeon.select_best()
104
+ new_model.load_state_dict(best_weights)
105
+
106
+ print(surgeon.selection_report())
107
+ # { 'transformer.layer.0.attn': 'mar', 'transformer.layer.1.ffn': 'may', ... }
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Module Overview
113
+
114
+ | Module | Purpose |
115
+ |---|---|
116
+ | `zp.memory.OtuxStore` | Selective context-aware memory — stores only important information |
117
+ | `zp.stabilize.GradShield` | Real-time gradient health: detects vanishing / exploding |
118
+ | `zp.stabilize.StabilityCore` | Loss EMA + plateau detection + LR signal |
119
+ | `zp.stabilize.ModelStabilizer` | Unified stabilization API |
120
+ | `zp.monitor.NipGraph` | Parity-aware training anomaly detection |
121
+ | `zp.weights.WeightVault` | Performance-gated weight snapshots |
122
+ | `zp.weights.WeightSurgeon` | Multi-model best weight selection (Fisher-guided) |
123
+ | `zp.weights.WeightGuard` | EWC-style catastrophic forgetting prevention |
124
+ | `zp.compute.safe_loss` | NaN-safe loss computation with rational fallback |
125
+ | `zp.compute.safe_divide` | Exact fraction arithmetic for recurring decimals |
126
+ | `zp.compat.ZPowerModel` | PyTorch model wrapper |
127
+ | `zp.compat.augment` | HuggingFace model augmentation |
128
+
129
+ ---
130
+
131
+ ## How ZPower Reduces Load
132
+
133
+ | Problem | ZPower Solution | Effect |
134
+ |---|---|---|
135
+ | NaN crash mid-training | GradShield clips before NaN | No wasted restart |
136
+ | Plateau wastes epochs | StabilityCore signals LR reduction | Converges faster |
137
+ | Catastrophic forgetting | WeightGuard EWC penalty | Retains good knowledge |
138
+ | Starting training from scratch | WeightSurgeon merges best of past runs | Better initialization |
139
+ | Storing all context blindly | OTUX-S importance gate | 70%+ memory reduction |
140
+ | Anomaly found too late | NipGraph detects at first sign | Early intervention |
141
+
142
+ ---
143
+
144
+ ## Research Origins
145
+
146
+ All core systems are original research by **NNN Bhoi**:
147
+
148
+ - **OTUX-S** — from PERATHINK research paper (selective 3D coordinate memory)
149
+ - **NipGraph** — from NipGraph research paper (parity-aware state tracking)
150
+ - **GradShield + StabilityCore** — from MUSAI vGPU engine (M2 + M4)
151
+ - **WeightVault / Surgeon / Guard** — original ZPower research
152
+
153
+ ---
154
+
155
+ ## License
156
+
157
+ MIT — free to use in any project.
zpower-1.0.0/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # ZPower (zp) — v1
2
+
3
+ **Intelligence layer for AI/ML systems.**
4
+ Works *with* PyTorch and HuggingFace — not against them.
5
+
6
+ > Stabilize training. Protect good weights. Detect anomalies. Reduce wasted compute.
7
+
8
+ ---
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ pip install zpower # numpy only (core features)
14
+ pip install zpower[torch] # + PyTorch support
15
+ pip install zpower[hf] # + HuggingFace Transformers
16
+ pip install zpower[full] # everything
17
+ ```
18
+
19
+ ---
20
+
21
+ ## Quick Start
22
+
23
+ ### Mode A — Pre-trained model (attach ZPower)
24
+
25
+ ```python
26
+ import zpower as zp
27
+
28
+ # Any PyTorch or HuggingFace model
29
+ zp_model = zp.attach(
30
+ my_model,
31
+ stabilize = True, # GradShield + StabilityCore
32
+ monitor = True, # NipGraph anomaly detection
33
+ weight_vault = True, # Record best weight checkpoints
34
+ )
35
+
36
+ # Forward pass — completely unchanged
37
+ output = zp_model(input_tensor)
38
+
39
+ # After loss + backward — ZPower hooks are active automatically
40
+ loss.backward()
41
+ ```
42
+
43
+ ### Mode B — Training from scratch
44
+
45
+ ```python
46
+ import zpower as zp
47
+
48
+ trainer = zp.Trainer(
49
+ my_model,
50
+ stabilize = True,
51
+ weight_vault = True,
52
+ weight_guard = False, # enable after first good run
53
+ )
54
+
55
+ trainer.fit(train_loader, epochs=20, lr=1e-3)
56
+
57
+ print(trainer.weight_report())
58
+ # { layers_vaulted: 12, total_snapshots: 47, overall_vault_score: 0.84 }
59
+
60
+ trainer.render_monitor()
61
+ # NipGraph ASCII dashboard
62
+ ```
63
+
64
+ ### Multi-model weight selection
65
+
66
+ ```python
67
+ from zpower.weights import WeightSurgeon
68
+
69
+ surgeon = WeightSurgeon(conflict_resolution="highest_performer")
70
+ surgeon.add_source("run_jan.pt", label="jan", perf_score=0.72)
71
+ surgeon.add_source("run_mar.pt", label="mar", perf_score=0.89)
72
+ surgeon.add_source("run_may.pt", label="may", perf_score=0.81)
73
+
74
+ best_weights = surgeon.select_best()
75
+ new_model.load_state_dict(best_weights)
76
+
77
+ print(surgeon.selection_report())
78
+ # { 'transformer.layer.0.attn': 'mar', 'transformer.layer.1.ffn': 'may', ... }
79
+ ```
80
+
81
+ ---
82
+
83
+ ## Module Overview
84
+
85
+ | Module | Purpose |
86
+ |---|---|
87
+ | `zp.memory.OtuxStore` | Selective context-aware memory — stores only important information |
88
+ | `zp.stabilize.GradShield` | Real-time gradient health: detects vanishing / exploding |
89
+ | `zp.stabilize.StabilityCore` | Loss EMA + plateau detection + LR signal |
90
+ | `zp.stabilize.ModelStabilizer` | Unified stabilization API |
91
+ | `zp.monitor.NipGraph` | Parity-aware training anomaly detection |
92
+ | `zp.weights.WeightVault` | Performance-gated weight snapshots |
93
+ | `zp.weights.WeightSurgeon` | Multi-model best weight selection (Fisher-guided) |
94
+ | `zp.weights.WeightGuard` | EWC-style catastrophic forgetting prevention |
95
+ | `zp.compute.safe_loss` | NaN-safe loss computation with rational fallback |
96
+ | `zp.compute.safe_divide` | Exact fraction arithmetic for recurring decimals |
97
+ | `zp.compat.ZPowerModel` | PyTorch model wrapper |
98
+ | `zp.compat.augment` | HuggingFace model augmentation |
99
+
100
+ ---
101
+
102
+ ## How ZPower Reduces Load
103
+
104
+ | Problem | ZPower Solution | Effect |
105
+ |---|---|---|
106
+ | NaN crash mid-training | GradShield clips before NaN | No wasted restart |
107
+ | Plateau wastes epochs | StabilityCore signals LR reduction | Converges faster |
108
+ | Catastrophic forgetting | WeightGuard EWC penalty | Retains good knowledge |
109
+ | Starting training from scratch | WeightSurgeon merges best of past runs | Better initialization |
110
+ | Storing all context blindly | OTUX-S importance gate | 70%+ memory reduction |
111
+ | Anomaly found too late | NipGraph detects at first sign | Early intervention |
112
+
113
+ ---
114
+
115
+ ## Research Origins
116
+
117
+ All core systems are original research by **NNN Bhoi**:
118
+
119
+ - **OTUX-S** — from PERATHINK research paper (selective 3D coordinate memory)
120
+ - **NipGraph** — from NipGraph research paper (parity-aware state tracking)
121
+ - **GradShield + StabilityCore** — from MUSAI vGPU engine (M2 + M4)
122
+ - **WeightVault / Surgeon / Guard** — original ZPower research
123
+
124
+ ---
125
+
126
+ ## License
127
+
128
+ MIT — free to use in any project.
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["setuptools>=42", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "zpower"
7
+ version = "1.0.0"
8
+ description = "Intelligence layer for AI/ML — stabilization, selective memory, weight surgery"
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "NNN Bhoi" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["numpy>=1.24"]
14
+
15
+ [project.optional-dependencies]
16
+ torch = ["torch>=2.0"]
17
+ hf = ["transformers>=4.35", "torch>=2.0"]
18
+ audio = ["torch>=2.0", "torchaudio>=2.0"]
19
+ full = ["torch>=2.0", "transformers>=4.35", "pandas>=2.0",
20
+ "scikit-learn>=1.3", "torchaudio>=2.0"]
21
+ dev = ["pytest>=7.0", "black", "mypy"]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/nnbhoi/zpower"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["."]
28
+ include = ["zpower*"]
29
+
30
+ [tool.pytest.ini_options]
31
+ testpaths = ["tests"]
32
+ addopts = "-v --tb=short"
zpower-1.0.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,121 @@
1
+ # tests/test_integration.py — Full pipeline, no torch required
2
+ import numpy as np
3
+ import pytest
4
+ import zpower as zp
5
+ from zpower.memory.otux import OtuxStore
6
+ from zpower.stabilize.model_stabilizer import ModelStabilizer
7
+ from zpower.monitor.nipgraph import NipGraph
8
+ from zpower.weights.vault import WeightVault
9
+ from zpower.weights.surgeon import WeightSurgeon
10
+ from zpower.compute.safe_math import safe_loss, safe_divide
11
+
12
+
13
+ def test_import_and_version():
14
+ assert zp.__version__ == "1.0.0"
15
+ assert zp.__author__ == "NNN Bhoi"
16
+
17
+
18
+ def test_info_no_crash(capsys):
19
+ zp.info()
20
+ captured = capsys.readouterr()
21
+ assert "ZPower" in captured.out
22
+
23
+
24
+ # ── Full numpy pipeline ────────────────────────────────────────────────────
25
+
26
+ def test_full_otux_pipeline():
27
+ store = OtuxStore(dim=16, mode="selective")
28
+ rng = np.random.default_rng(0)
29
+
30
+ # Write several entries
31
+ for i in range(20):
32
+ v = rng.standard_normal(16).astype(np.float32)
33
+ store.write(f"entry_{i}", v, x=i, y=i % 3, z=i % 5, reward=rng.uniform(0, 1))
34
+
35
+ stats = store.filter_stats()
36
+ assert stats["total_seen"] == 20
37
+ assert stats["currently_stored"] <= 20
38
+
39
+
40
+ def test_full_stabilizer_pipeline():
41
+ ms = ModelStabilizer()
42
+ ng = NipGraph(variables=["loss", "grad_norm"])
43
+ ms._nipgraph = ng
44
+ ms.stability_core._nipgraph = ng
45
+
46
+ losses = [3.0, 2.5, 2.0, 1.8, 1.5, 1.3, 1.2, 1.1, 1.05, 1.02]
47
+ for L in losses:
48
+ info = ms.on_loss(L)
49
+ assert "state" in info
50
+
51
+ # Gradient shield
52
+ g_healthy = np.ones(4, dtype=np.float32) * 0.5
53
+ g_exploding = np.ones(4, dtype=np.float32) * 100.0
54
+ assert ms.grad_shield.check(g_healthy) == "healthy"
55
+ assert ms.grad_shield.check(g_exploding) == "exploding"
56
+
57
+ shielded = ms.grad_shield.shield(g_exploding)
58
+ assert float(np.linalg.norm(shielded)) <= 5.0 + 1e-4
59
+
60
+
61
+ def test_vault_and_surgeon_pipeline():
62
+ rng = np.random.default_rng(42)
63
+
64
+ class FakeModel:
65
+ def __init__(self, seed):
66
+ _rng = np.random.default_rng(seed)
67
+ self.fc1 = _rng.standard_normal((8, 4)).astype(np.float32)
68
+ self.fc2 = _rng.standard_normal((4, 2)).astype(np.float32)
69
+
70
+ # Vault: record good model
71
+ vault = WeightVault(vault_threshold=0.0)
72
+ vault.record(FakeModel(0), {
73
+ "loss": 0.1, "loss_reference": 2.0,
74
+ "val_accuracy": 0.95, "confidence": 0.90,
75
+ "grad_health": "healthy", "curvature": "flat",
76
+ })
77
+ assert vault.summary()["total_snapshots"] > 0
78
+
79
+ # Surgeon: merge two models
80
+ surgeon = WeightSurgeon(conflict_resolution="highest_performer")
81
+ sd1 = {"fc1": FakeModel(0).fc1, "fc2": FakeModel(0).fc2}
82
+ sd2 = {"fc1": FakeModel(1).fc1, "fc2": FakeModel(1).fc2}
83
+ surgeon.add_source(sd1, label="model_a", perf_score=0.6)
84
+ surgeon.add_source(sd2, label="model_b", perf_score=0.9)
85
+ best = surgeon.select_best()
86
+ assert "fc1" in best
87
+ assert "fc2" in best
88
+
89
+
90
+ def test_safe_math_pipeline():
91
+ # Normal loss
92
+ p = np.array([0.3, 0.3, 0.4])
93
+ t = np.array([0.0, 0.0, 1.0])
94
+ loss = safe_loss(p, t, "mse")
95
+ assert loss > 0
96
+
97
+ # Recurring division
98
+ token = safe_divide(1, 3)
99
+ assert abs(float(token) - 1/3) < 1e-9
100
+
101
+ # Deduplication
102
+ token2 = safe_divide(1, 3)
103
+ assert token.name == token2.name
104
+
105
+
106
+ def test_nipgraph_training_simulation():
107
+ ng = NipGraph(variables=["loss", "accuracy"])
108
+
109
+ # Simulate healthy training
110
+ for i in range(30):
111
+ loss = 2.0 * np.exp(-0.1 * i) + 0.01 * np.random.default_rng(i).standard_normal()
112
+ acc = 1.0 - loss / 2.0
113
+ ng.update("loss", max(loss, 0.01))
114
+ ng.update("accuracy", min(max(acc, 0.0), 1.0))
115
+
116
+ state = ng.check()
117
+ assert "loss" in state
118
+ assert "accuracy" in state
119
+ # No alerts expected in healthy training
120
+ # (may or may not converge depending on values — just check no crash)
121
+ assert isinstance(state["loss"]["track"], str)
@@ -0,0 +1,102 @@
1
+ # tests/test_nipgraph.py
2
+ import pytest
3
+ from zpower.monitor.nipgraph import NipGraph
4
+
5
+
6
+ def test_basic_update_no_alert():
7
+ ng = NipGraph(variables=["loss"], band_width=0.5)
8
+ for i in range(5):
9
+ ng.update("loss", 1.0 - i * 0.05)
10
+ state = ng.check()
11
+ assert "loss" in state
12
+ assert state["loss"]["alert"] is False
13
+
14
+
15
+ def test_track_classification_positive_stable():
16
+ ng = NipGraph(variables=["loss"], band_width=0.5)
17
+ for _ in range(10):
18
+ ng.update("loss", 1.0)
19
+ state = ng.check()
20
+ assert state["loss"]["track"] in ("x_M", "x_W")
21
+
22
+
23
+ def test_alert_fires_on_bad_jump():
24
+ ng = NipGraph(variables=["loss"], band_width=0.05)
25
+ # Establish stable positive trend
26
+ for _ in range(10):
27
+ ng.update("loss", 1.0)
28
+ # Sudden negative spike (EMA positive, value hugely negative)
29
+ ng.update("loss", -100.0)
30
+ state = ng.check()
31
+ # Track should have jumped to Y_W territory
32
+ alerts = ng.alerts()
33
+ # Either direct alert or track is Y_W
34
+ assert state["loss"]["track"] in ("Y_M", "Y_W") or len(alerts) > 0
35
+
36
+
37
+ def test_convergence_detection():
38
+ ng = NipGraph(variables=["loss"], band_width=0.5)
39
+ # Feed very stable values — should converge
40
+ for _ in range(25):
41
+ ng.update("loss", 0.001)
42
+ assert ng.is_converged() is True
43
+
44
+
45
+ def test_not_converged_with_variance():
46
+ ng = NipGraph(variables=["loss"])
47
+ import random
48
+ rng = random.Random(42)
49
+ for _ in range(25):
50
+ ng.update("loss", rng.uniform(0.5, 5.0))
51
+ assert ng.is_converged() is False
52
+
53
+
54
+ def test_multiple_variables():
55
+ ng = NipGraph(variables=["loss", "grad_norm", "accuracy"])
56
+ ng.update("loss", 2.0)
57
+ ng.update("grad_norm", 0.5)
58
+ ng.update("accuracy", 0.8)
59
+ state = ng.check()
60
+ assert set(state.keys()) == {"loss", "grad_norm", "accuracy"}
61
+
62
+
63
+ def test_auto_add_new_variable():
64
+ ng = NipGraph(variables=["loss"])
65
+ ng.update("new_metric", 1.0) # not in initial list
66
+ state = ng.check()
67
+ assert "new_metric" in state
68
+
69
+
70
+ def test_clear_alerts():
71
+ ng = NipGraph(variables=["loss"], band_width=0.01)
72
+ for _ in range(5):
73
+ ng.update("loss", 1.0)
74
+ ng.update("loss", -999.0)
75
+ ng.clear_alerts()
76
+ assert ng.alerts() == []
77
+
78
+
79
+ def test_locate_fault_no_anomaly():
80
+ ng = NipGraph(variables=["loss"])
81
+ ng.update("loss", 1.0)
82
+ msg = ng.locate_fault("loss")
83
+ assert "No anomaly" in msg
84
+
85
+
86
+ def test_render_panels_no_crash(capsys):
87
+ ng = NipGraph(variables=["loss", "accuracy"])
88
+ for _ in range(3):
89
+ ng.update("loss", 1.0)
90
+ ng.update("accuracy", 0.9)
91
+ ng.render_panels()
92
+ captured = capsys.readouterr()
93
+ assert "NipGraph" in captured.out
94
+
95
+
96
+ def test_status_keys():
97
+ ng = NipGraph(variables=["loss"])
98
+ s = ng.status()
99
+ assert "step" in s
100
+ assert "variables" in s
101
+ assert "converged" in s
102
+ assert "alerts" in s
@@ -0,0 +1,134 @@
1
+ # tests/test_otux.py
2
+ import numpy as np
3
+ import pytest
4
+ from zpower.memory.otux import OtuxStore, ImportanceWeights
5
+
6
+
7
+ def make_vec(dim=256, seed=None):
8
+ rng = np.random.default_rng(seed)
9
+ v = rng.standard_normal(dim).astype(np.float32)
10
+ return v / (np.linalg.norm(v) + 1e-10)
11
+
12
+
13
+ # ── ImportanceWeights ──────────────────────────────────────────────────────
14
+
15
+ def test_weights_sum_to_one():
16
+ w = ImportanceWeights(0.35, 0.30, 0.20, 0.15)
17
+ assert abs(w.novelty + w.reward + w.context_fit + w.recurrence - 1.0) < 1e-5
18
+
19
+
20
+ def test_weights_bad_sum():
21
+ with pytest.raises(ValueError):
22
+ ImportanceWeights(0.5, 0.5, 0.5, 0.5)
23
+
24
+
25
+ # ── OtuxStore basic ────────────────────────────────────────────────────────
26
+
27
+ def test_store_and_len():
28
+ store = OtuxStore(dim=8, mode="full")
29
+ store.write("hello", make_vec(8, 0))
30
+ store.write("world", make_vec(8, 1))
31
+ assert len(store) == 2
32
+
33
+
34
+ def test_query_returns_results():
35
+ store = OtuxStore(dim=8, mode="full")
36
+ v = make_vec(8, 42)
37
+ store.write("alpha", v)
38
+ results = store.query(v, top_k=1)
39
+ assert len(results) == 1
40
+ assert results[0]["token"] == "alpha"
41
+ assert results[0]["sim"] > 0.99
42
+
43
+
44
+ def test_query_empty_store():
45
+ store = OtuxStore(dim=8)
46
+ results = store.query(make_vec(8), top_k=5)
47
+ assert results == []
48
+
49
+
50
+ def test_coord_retrieval():
51
+ store = OtuxStore(dim=8, mode="full")
52
+ store.write("deep", make_vec(8, 0), x=0, y=0, z=3)
53
+ store.write("surface", make_vec(8, 1), x=0, y=0, z=0)
54
+ deep_results = store.query_by_coord(z=3, tol=0.1)
55
+ assert len(deep_results) == 1
56
+ assert deep_results[0].token == "deep"
57
+
58
+
59
+ def test_force_write_bypasses_gate():
60
+ store = OtuxStore(dim=8, mode="selective", importance_threshold=0.99)
61
+ result = store.write("forced", make_vec(8, 0), reward=0.0, force=True)
62
+ assert result == "forced"
63
+ assert len(store) == 1
64
+
65
+
66
+ # ── Importance gate ────────────────────────────────────────────────────────
67
+
68
+ def test_discard_low_reward():
69
+ store = OtuxStore(
70
+ dim=8,
71
+ mode="selective",
72
+ importance_threshold=0.65,
73
+ forget_threshold=0.30,
74
+ weights={"novelty": 0.35, "reward": 0.30, "context_fit": 0.20, "recurrence": 0.15},
75
+ )
76
+ # reward=0.0, no context → score will be low → discard
77
+ result = store.write("junk", make_vec(8, 99), reward=0.0)
78
+ # First entry: novelty=1.0, reward=0.0, context=0.5, recurrence=log(2)/5
79
+ # score = 0.35*1.0 + 0.30*0.0 + 0.20*0.5 + 0.15*~0.14 ≈ 0.47 → below 0.65
80
+ # Could be buffered or discarded — just not stored immediately
81
+ assert result in ("discarded", "buffered")
82
+
83
+
84
+ def test_high_reward_stored():
85
+ store = OtuxStore(dim=8, mode="selective", importance_threshold=0.65)
86
+ result = store.write("important", make_vec(8, 1), reward=1.0)
87
+ # novelty=1.0 (first entry), reward=1.0 → score ≈ 0.35+0.30+0.10+0.02 = 0.77 → stored
88
+ assert result == "stored"
89
+
90
+
91
+ def test_buffer_three_strikes():
92
+ store = OtuxStore(
93
+ dim=8, mode="selective",
94
+ importance_threshold=0.90, # high threshold → most go to buffer
95
+ forget_threshold=0.05,
96
+ buffer_strikes=3,
97
+ )
98
+ v = make_vec(8, 7)
99
+ for _ in range(3):
100
+ store.write("repeat", v, reward=0.4)
101
+ # After 3 strikes → promoted to store
102
+ assert len(store) >= 1
103
+
104
+
105
+ # ── Filter stats ───────────────────────────────────────────────────────────
106
+
107
+ def test_filter_stats_keys():
108
+ store = OtuxStore(dim=8, mode="full")
109
+ store.write("a", make_vec(8, 0))
110
+ s = store.filter_stats()
111
+ assert "stored" in s
112
+ assert "discarded" in s
113
+ assert "compression_ratio" in s
114
+ assert "currently_stored" in s
115
+
116
+
117
+ def test_clear():
118
+ store = OtuxStore(dim=8, mode="full")
119
+ store.write("x", make_vec(8))
120
+ store.clear()
121
+ assert len(store) == 0
122
+
123
+
124
+ # ── Vector validation ──────────────────────────────────────────────────────
125
+
126
+ def test_wrong_dim_raises():
127
+ store = OtuxStore(dim=8)
128
+ with pytest.raises(ValueError):
129
+ store.write("bad", np.ones(16, dtype=np.float32))
130
+
131
+
132
+ def test_repr():
133
+ store = OtuxStore(dim=8, mode="full")
134
+ assert "OtuxStore" in repr(store)