natc 0.1.0__py3-none-any.whl

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.
natc/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """Public package interface for NATC."""
2
+
3
+ from natc.config import NATCConfig
4
+ from natc.model import NATCModel
5
+
6
+ __all__ = ["NATCConfig", "NATCModel"]
7
+
@@ -0,0 +1,123 @@
1
+ """Predictive sparse attention routing."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ import numpy as np
8
+
9
+ from natc.utils import text_embedding
10
+
11
+
12
+ @dataclass(slots=True)
13
+ class SparseAttentionResult:
14
+ """Sparse attention output and its selected graph."""
15
+
16
+ output: np.ndarray
17
+ weights: np.ndarray
18
+ adjacency: np.ndarray
19
+
20
+
21
+ class SparseGraphBuilder:
22
+ """Build top-k token adjacency graphs from similarity scores."""
23
+
24
+ def build(self, scores: np.ndarray, *, top_k: int) -> np.ndarray:
25
+ if scores.ndim != 2:
26
+ raise ValueError("scores must be a 2D matrix")
27
+ top_k = max(1, min(top_k, scores.shape[1]))
28
+ adjacency = np.zeros_like(scores, dtype=bool)
29
+ indices = np.argpartition(scores, -top_k, axis=1)[:, -top_k:]
30
+ rows = np.arange(scores.shape[0])[:, None]
31
+ adjacency[rows, indices] = True
32
+ return adjacency
33
+
34
+
35
+ class AttentionRouter:
36
+ """Predict likely attention paths before dense attention execution."""
37
+
38
+ def __init__(self, *, top_k: int = 32) -> None:
39
+ self.top_k = top_k
40
+ self.graph_builder = SparseGraphBuilder()
41
+
42
+ def route_tokens(self, tokens: list[str], *, top_k: int | None = None) -> np.ndarray:
43
+ if not tokens:
44
+ return np.zeros((0, 0), dtype=bool)
45
+ embeddings = np.stack([text_embedding(token, dimensions=64) for token in tokens])
46
+ scores = embeddings @ embeddings.T
47
+ return self.graph_builder.build(scores, top_k=top_k or self.top_k)
48
+
49
+ def route_embeddings(self, embeddings: np.ndarray, *, top_k: int | None = None) -> np.ndarray:
50
+ embeddings = _as_2d(embeddings)
51
+ scores = embeddings @ embeddings.T / max(1.0, float(embeddings.shape[-1]) ** 0.5)
52
+ return self.graph_builder.build(scores, top_k=top_k or self.top_k)
53
+
54
+
55
+ class PredictiveAttention:
56
+ """Compute sparse scaled dot-product attention using predicted top-k edges."""
57
+
58
+ def __init__(self, *, top_k: int = 32) -> None:
59
+ self.router = AttentionRouter(top_k=top_k)
60
+
61
+ def __call__(
62
+ self,
63
+ query: np.ndarray,
64
+ key: np.ndarray,
65
+ value: np.ndarray,
66
+ *,
67
+ top_k: int | None = None,
68
+ ) -> SparseAttentionResult:
69
+ return self.forward(query, key, value, top_k=top_k)
70
+
71
+ def forward(
72
+ self,
73
+ query: np.ndarray,
74
+ key: np.ndarray,
75
+ value: np.ndarray,
76
+ *,
77
+ top_k: int | None = None,
78
+ ) -> SparseAttentionResult:
79
+ query = _as_2d(query)
80
+ key = _as_2d(key)
81
+ value = _as_2d(value)
82
+ if query.shape[-1] != key.shape[-1]:
83
+ raise ValueError("query and key dimensions must match")
84
+ if key.shape[0] != value.shape[0]:
85
+ raise ValueError("key and value token counts must match")
86
+
87
+ scores = query @ key.T / max(1.0, float(query.shape[-1]) ** 0.5)
88
+ adjacency = SparseGraphBuilder().build(scores, top_k=top_k or self.router.top_k)
89
+ masked_scores = np.where(adjacency, scores, -np.inf)
90
+ weights = _softmax(masked_scores, axis=1)
91
+ output = weights @ value
92
+ return SparseAttentionResult(
93
+ output=output.astype(np.float32, copy=False),
94
+ weights=weights.astype(np.float32, copy=False),
95
+ adjacency=adjacency,
96
+ )
97
+
98
+
99
+ def _softmax(values: np.ndarray, *, axis: int) -> np.ndarray:
100
+ maximum = np.max(values, axis=axis, keepdims=True)
101
+ shifted = np.exp(values - maximum)
102
+ shifted[~np.isfinite(values)] = 0.0
103
+ denom = shifted.sum(axis=axis, keepdims=True)
104
+ denom[denom == 0.0] = 1.0
105
+ return shifted / denom
106
+
107
+
108
+ def _as_2d(array: np.ndarray) -> np.ndarray:
109
+ array = np.asarray(array, dtype=np.float32)
110
+ if array.ndim == 1:
111
+ return array.reshape(1, -1)
112
+ if array.ndim != 2:
113
+ raise ValueError("attention arrays must be 1D or 2D")
114
+ return array
115
+
116
+
117
+ __all__ = [
118
+ "AttentionRouter",
119
+ "PredictiveAttention",
120
+ "SparseAttentionResult",
121
+ "SparseGraphBuilder",
122
+ ]
123
+
@@ -0,0 +1,135 @@
1
+ """Benchmark suite for NATC."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import tempfile
7
+ import time
8
+ import tracemalloc
9
+ from dataclasses import dataclass
10
+ from typing import Any
11
+
12
+ import numpy as np
13
+
14
+ from natc.cache import NeuralCache
15
+ from natc.dna import decode_model, encode_model
16
+ from natc.model import NATCModel
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class BenchmarkResult:
21
+ """NATC benchmark metrics."""
22
+
23
+ compression_ratio: float
24
+ memory_saved: float
25
+ speedup: float
26
+ cpu_efficiency: float
27
+ tokens_per_sec: float
28
+ cache_hit_ratio: float
29
+ latency: float
30
+ throughput: float
31
+
32
+ def to_dict(self) -> dict[str, float]:
33
+ return {
34
+ "compression_ratio": self.compression_ratio,
35
+ "memory_saved": self.memory_saved,
36
+ "speedup": self.speedup,
37
+ "cpu_efficiency": self.cpu_efficiency,
38
+ "tokens_per_sec": self.tokens_per_sec,
39
+ "cache_hit_ratio": self.cache_hit_ratio,
40
+ "latency": self.latency,
41
+ "throughput": self.throughput,
42
+ }
43
+
44
+ def to_json(self) -> str:
45
+ return json.dumps(self.to_dict(), indent=2, sort_keys=True)
46
+
47
+
48
+ class BenchmarkRunner:
49
+ """Run synthetic compression and inference benchmarks."""
50
+
51
+ def __init__(
52
+ self,
53
+ *,
54
+ layers: int = 4,
55
+ rows: int = 128,
56
+ cols: int = 128,
57
+ rank: int = 16,
58
+ seed: int = 13,
59
+ ) -> None:
60
+ self.layers = layers
61
+ self.rows = rows
62
+ self.cols = cols
63
+ self.rank = rank
64
+ self.seed = seed
65
+
66
+ def run(self) -> BenchmarkResult:
67
+ state = self._synthetic_state()
68
+
69
+ tracemalloc.start()
70
+ start = time.perf_counter()
71
+ dense_outputs = _dense_reference(state)
72
+ dense_latency = time.perf_counter() - start
73
+ dense_current, dense_peak = tracemalloc.get_traced_memory()
74
+
75
+ start = time.perf_counter()
76
+ dna = encode_model(state, rank=self.rank)
77
+ reconstructed = decode_model(dna)
78
+ compressed_outputs = _dense_reference(reconstructed)
79
+ compressed_latency = time.perf_counter() - start
80
+ compressed_current, compressed_peak = tracemalloc.get_traced_memory()
81
+ tracemalloc.stop()
82
+
83
+ with tempfile.TemporaryDirectory() as tmpdir:
84
+ cache = NeuralCache(tmpdir)
85
+ cache.put("python pattern", {"value": "cached"})
86
+ cache.get("python pattern")
87
+ cache.get("python patterns")
88
+ cache_hit_ratio = cache.hit_ratio()
89
+
90
+ model = NATCModel.from_state_dict(state, rank=self.rank)
91
+ generated_start = time.perf_counter()
92
+ output = model.generate("Explain a Python matrix multiplication pattern", max_new_tokens=32)
93
+ generated_latency = max(time.perf_counter() - generated_start, 1e-9)
94
+ token_count = max(1, len(output.split()))
95
+
96
+ dense_memory = max(dense_peak, dense_current, 1)
97
+ compressed_memory = max(compressed_peak - dense_peak, compressed_current, 1)
98
+ memory_saved = max(0.0, 1.0 - (compressed_memory / dense_memory))
99
+ speedup = dense_latency / max(compressed_latency, 1e-9)
100
+ throughput = len(compressed_outputs) / max(compressed_latency, 1e-9)
101
+ cpu_efficiency = min(1.0, speedup / max(1.0, self.rank))
102
+
103
+ return BenchmarkResult(
104
+ compression_ratio=dna.compression_ratio(),
105
+ memory_saved=memory_saved,
106
+ speedup=speedup,
107
+ cpu_efficiency=cpu_efficiency,
108
+ tokens_per_sec=token_count / generated_latency,
109
+ cache_hit_ratio=cache_hit_ratio,
110
+ latency=compressed_latency,
111
+ throughput=throughput,
112
+ )
113
+
114
+ def _synthetic_state(self) -> dict[str, np.ndarray]:
115
+ rng = np.random.default_rng(self.seed)
116
+ state = {}
117
+ base_left = rng.normal(size=(self.rows, self.rank)).astype(np.float32)
118
+ base_right = rng.normal(size=(self.rank, self.cols)).astype(np.float32)
119
+ for layer in range(self.layers):
120
+ noise = rng.normal(scale=0.01, size=(self.rows, self.cols)).astype(np.float32)
121
+ state[f"layers.{layer}.weight"] = base_left @ base_right + noise
122
+ return state
123
+
124
+
125
+ def run_benchmark(**kwargs: Any) -> dict[str, float]:
126
+ return BenchmarkRunner(**kwargs).run().to_dict()
127
+
128
+
129
+ def _dense_reference(state: dict[str, np.ndarray]) -> list[np.ndarray]:
130
+ vector = np.ones((next(iter(state.values())).shape[1], 1), dtype=np.float32)
131
+ return [weight @ vector for weight in state.values()]
132
+
133
+
134
+ __all__ = ["BenchmarkResult", "BenchmarkRunner", "run_benchmark"]
135
+
natc/benchmark/cli.py ADDED
@@ -0,0 +1,30 @@
1
+ """Command-line benchmark runner."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from natc.benchmark import BenchmarkRunner
8
+
9
+
10
+ def main() -> None:
11
+ parser = argparse.ArgumentParser(description="Run NATC benchmark suite.")
12
+ parser.add_argument("--layers", type=int, default=4)
13
+ parser.add_argument("--rows", type=int, default=128)
14
+ parser.add_argument("--cols", type=int, default=128)
15
+ parser.add_argument("--rank", type=int, default=16)
16
+ parser.add_argument("--seed", type=int, default=13)
17
+ args = parser.parse_args()
18
+ result = BenchmarkRunner(
19
+ layers=args.layers,
20
+ rows=args.rows,
21
+ cols=args.cols,
22
+ rank=args.rank,
23
+ seed=args.seed,
24
+ ).run()
25
+ print(result.to_json())
26
+
27
+
28
+ if __name__ == "__main__":
29
+ main()
30
+
natc/cache/__init__.py ADDED
@@ -0,0 +1,217 @@
1
+ """Neural fragment cache with RAM and persistent disk storage."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import pickle
7
+ import sqlite3
8
+ import time
9
+ from contextlib import closing
10
+ from dataclasses import dataclass
11
+ from pathlib import Path
12
+ from typing import Any
13
+
14
+ import numpy as np
15
+
16
+ from natc.utils import cosine_similarity, stable_hash, text_embedding
17
+
18
+
19
+ @dataclass(slots=True)
20
+ class CacheRecord:
21
+ """A stored neural fragment."""
22
+
23
+ key: str
24
+ text: str
25
+ payload: Any
26
+ embedding: np.ndarray
27
+ metadata: dict[str, Any]
28
+ created_at: float
29
+ last_accessed: float
30
+
31
+
32
+ class FragmentStore:
33
+ """SQLite-backed persistent fragment store."""
34
+
35
+ def __init__(self, path: str | Path) -> None:
36
+ self.path = Path(path)
37
+ self.path.parent.mkdir(parents=True, exist_ok=True)
38
+ self._initialize()
39
+
40
+ def put(
41
+ self,
42
+ key: str,
43
+ text: str,
44
+ payload: Any,
45
+ embedding: np.ndarray,
46
+ *,
47
+ metadata: dict[str, Any] | None = None,
48
+ ) -> None:
49
+ now = time.time()
50
+ with closing(sqlite3.connect(self.path)) as conn:
51
+ with conn:
52
+ conn.execute(
53
+ """
54
+ INSERT OR REPLACE INTO fragments
55
+ (key, text, payload, embedding, metadata, created_at, last_accessed)
56
+ VALUES (?, ?, ?, ?, ?, COALESCE((SELECT created_at FROM fragments WHERE key = ?), ?), ?)
57
+ """,
58
+ (
59
+ key,
60
+ text,
61
+ pickle.dumps(payload),
62
+ np.asarray(embedding, dtype=np.float32).tobytes(),
63
+ json.dumps(metadata or {}),
64
+ key,
65
+ now,
66
+ now,
67
+ ),
68
+ )
69
+
70
+ def get(self, key: str) -> CacheRecord | None:
71
+ with closing(sqlite3.connect(self.path)) as conn:
72
+ with conn:
73
+ row = conn.execute(
74
+ """
75
+ SELECT key, text, payload, embedding, metadata, created_at, last_accessed
76
+ FROM fragments WHERE key = ?
77
+ """,
78
+ (key,),
79
+ ).fetchone()
80
+ if row is None:
81
+ return None
82
+ conn.execute(
83
+ "UPDATE fragments SET last_accessed = ? WHERE key = ?",
84
+ (time.time(), key),
85
+ )
86
+ return _row_to_record(row)
87
+
88
+ def all(self) -> list[CacheRecord]:
89
+ with closing(sqlite3.connect(self.path)) as conn:
90
+ rows = conn.execute(
91
+ """
92
+ SELECT key, text, payload, embedding, metadata, created_at, last_accessed
93
+ FROM fragments
94
+ """
95
+ ).fetchall()
96
+ return [_row_to_record(row) for row in rows]
97
+
98
+ def _initialize(self) -> None:
99
+ with closing(sqlite3.connect(self.path)) as conn:
100
+ with conn:
101
+ conn.execute(
102
+ """
103
+ CREATE TABLE IF NOT EXISTS fragments (
104
+ key TEXT PRIMARY KEY,
105
+ text TEXT NOT NULL,
106
+ payload BLOB NOT NULL,
107
+ embedding BLOB NOT NULL,
108
+ metadata TEXT NOT NULL,
109
+ created_at REAL NOT NULL,
110
+ last_accessed REAL NOT NULL
111
+ )
112
+ """
113
+ )
114
+
115
+
116
+ class PatternIndexer:
117
+ """Similarity lookup for cached prompt and reasoning fragments."""
118
+
119
+ def __init__(self, store: FragmentStore) -> None:
120
+ self.store = store
121
+
122
+ def lookup(self, text: str, *, top_k: int = 3) -> list[tuple[CacheRecord, float]]:
123
+ query = text_embedding(text)
124
+ scored = [
125
+ (record, cosine_similarity(query, record.embedding))
126
+ for record in self.store.all()
127
+ ]
128
+ scored.sort(key=lambda item: item[1], reverse=True)
129
+ return scored[:top_k]
130
+
131
+
132
+ class NeuralCache:
133
+ """Two-level RAM plus disk neural fragment cache."""
134
+
135
+ def __init__(
136
+ self,
137
+ cache_dir: str | Path,
138
+ *,
139
+ similarity_threshold: float = 0.84,
140
+ max_ram_items: int = 512,
141
+ ) -> None:
142
+ self.cache_dir = Path(cache_dir)
143
+ self.cache_dir.mkdir(parents=True, exist_ok=True)
144
+ self.store = FragmentStore(self.cache_dir / "fragments.sqlite3")
145
+ self.indexer = PatternIndexer(self.store)
146
+ self.similarity_threshold = similarity_threshold
147
+ self.max_ram_items = max_ram_items
148
+ self._ram: dict[str, CacheRecord] = {}
149
+ self.hits = 0
150
+ self.misses = 0
151
+
152
+ def key_for(self, text: str) -> str:
153
+ return stable_hash({"text": text.strip().lower()})
154
+
155
+ def put(
156
+ self,
157
+ text: str,
158
+ payload: Any,
159
+ *,
160
+ key: str | None = None,
161
+ metadata: dict[str, Any] | None = None,
162
+ ) -> str:
163
+ cache_key = key or self.key_for(text)
164
+ embedding = text_embedding(text)
165
+ self.store.put(cache_key, text, payload, embedding, metadata=metadata)
166
+ record = self.store.get(cache_key)
167
+ if record is not None:
168
+ self._remember(record)
169
+ return cache_key
170
+
171
+ def get(self, text_or_key: str, *, exact: bool = False) -> Any | None:
172
+ key = text_or_key if exact else self.key_for(text_or_key)
173
+ record = self._ram.get(key) or self.store.get(key)
174
+ if record is not None:
175
+ self.hits += 1
176
+ self._remember(record)
177
+ return record.payload
178
+ if exact:
179
+ self.misses += 1
180
+ return None
181
+ similar = self.lookup(text_or_key, top_k=1)
182
+ if similar and similar[0][1] >= self.similarity_threshold:
183
+ self.hits += 1
184
+ self._remember(similar[0][0])
185
+ return similar[0][0].payload
186
+ self.misses += 1
187
+ return None
188
+
189
+ def lookup(self, text: str, *, top_k: int = 3) -> list[tuple[CacheRecord, float]]:
190
+ return self.indexer.lookup(text, top_k=top_k)
191
+
192
+ def hit_ratio(self) -> float:
193
+ total = self.hits + self.misses
194
+ return float(self.hits / total) if total else 0.0
195
+
196
+ def _remember(self, record: CacheRecord) -> None:
197
+ if len(self._ram) >= self.max_ram_items:
198
+ oldest = min(self._ram.values(), key=lambda item: item.last_accessed)
199
+ self._ram.pop(oldest.key, None)
200
+ self._ram[record.key] = record
201
+
202
+
203
+ def _row_to_record(row: tuple[Any, ...]) -> CacheRecord:
204
+ key, text, payload_blob, embedding_blob, metadata_json, created_at, last_accessed = row
205
+ embedding = np.frombuffer(embedding_blob, dtype=np.float32).copy()
206
+ return CacheRecord(
207
+ key=str(key),
208
+ text=str(text),
209
+ payload=pickle.loads(payload_blob),
210
+ embedding=embedding,
211
+ metadata=json.loads(metadata_json),
212
+ created_at=float(created_at),
213
+ last_accessed=float(last_accessed),
214
+ )
215
+
216
+
217
+ __all__ = ["CacheRecord", "FragmentStore", "NeuralCache", "PatternIndexer"]
@@ -0,0 +1,160 @@
1
+ """Prompt-routed reasoning capsules."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+
10
+ from natc.utils import cosine_similarity, text_embedding
11
+
12
+
13
+ @dataclass(slots=True)
14
+ class ReasoningCapsule:
15
+ """A lightweight expert region selected from prompt semantics."""
16
+
17
+ name: str
18
+ keywords: tuple[str, ...]
19
+ description: str
20
+ activation_cost: float = 1.0
21
+ metadata: dict[str, Any] = field(default_factory=dict)
22
+
23
+ def score(self, prompt: str) -> float:
24
+ lowered = prompt.lower()
25
+ keyword_hits = sum(1 for keyword in self.keywords if keyword.lower() in lowered)
26
+ keyword_score = keyword_hits / max(1, len(self.keywords))
27
+ semantic_score = cosine_similarity(
28
+ text_embedding(prompt),
29
+ text_embedding(f"{self.name} {self.description} {' '.join(self.keywords)}"),
30
+ )
31
+ return float(0.68 * keyword_score + 0.32 * max(0.0, semantic_score))
32
+
33
+ def execute(self, prompt: str, context: dict[str, Any] | None = None) -> dict[str, Any]:
34
+ return {
35
+ "capsule": self.name,
36
+ "score": self.score(prompt),
37
+ "context": context or {},
38
+ "embedding": text_embedding(prompt, dimensions=32),
39
+ }
40
+
41
+
42
+ class MathCapsule(ReasoningCapsule):
43
+ def __init__(self) -> None:
44
+ super().__init__(
45
+ name="math",
46
+ keywords=("math", "proof", "equation", "calculate", "algebra", "geometry"),
47
+ description="symbolic arithmetic, equations, proofs, and quantitative reasoning",
48
+ )
49
+
50
+
51
+ class CodeCapsule(ReasoningCapsule):
52
+ def __init__(self) -> None:
53
+ super().__init__(
54
+ name="code",
55
+ keywords=("code", "python", "function", "bug", "api", "class", "compile"),
56
+ description="software engineering, debugging, APIs, and programming languages",
57
+ )
58
+
59
+
60
+ class CreativeCapsule(ReasoningCapsule):
61
+ def __init__(self) -> None:
62
+ super().__init__(
63
+ name="creative",
64
+ keywords=("story", "write", "poem", "brand", "creative", "voice", "scene"),
65
+ description="creative writing, style, narrative, and ideation",
66
+ )
67
+
68
+
69
+ class ScienceCapsule(ReasoningCapsule):
70
+ def __init__(self) -> None:
71
+ super().__init__(
72
+ name="science",
73
+ keywords=("physics", "biology", "chemistry", "experiment", "theory", "quantum"),
74
+ description="scientific explanation, experiments, and technical concepts",
75
+ )
76
+
77
+
78
+ class MedicalCapsule(ReasoningCapsule):
79
+ def __init__(self) -> None:
80
+ super().__init__(
81
+ name="medical",
82
+ keywords=("medical", "health", "symptom", "clinical", "diagnosis", "treatment"),
83
+ description="medical terminology, health context, and careful clinical reasoning",
84
+ )
85
+
86
+
87
+ class CapsuleRouter:
88
+ """Score and select capsules for a prompt."""
89
+
90
+ def __init__(self, capsules: list[ReasoningCapsule] | None = None) -> None:
91
+ self.capsules = capsules or [
92
+ MathCapsule(),
93
+ CodeCapsule(),
94
+ CreativeCapsule(),
95
+ ScienceCapsule(),
96
+ MedicalCapsule(),
97
+ ]
98
+
99
+ def route(
100
+ self,
101
+ prompt: str,
102
+ *,
103
+ top_k: int = 3,
104
+ threshold: float = 0.05,
105
+ ) -> list[tuple[ReasoningCapsule, float]]:
106
+ scored = [(capsule, capsule.score(prompt)) for capsule in self.capsules]
107
+ scored.sort(key=lambda item: item[1], reverse=True)
108
+ selected = [(capsule, score) for capsule, score in scored[:top_k] if score >= threshold]
109
+ if selected:
110
+ return selected
111
+ best = scored[0]
112
+ return [best] if best[1] > 0.0 else []
113
+
114
+
115
+ class CapsuleManager:
116
+ """Register, activate, and inspect prompt capsules."""
117
+
118
+ def __init__(self, capsules: list[ReasoningCapsule] | None = None) -> None:
119
+ self.router = CapsuleRouter(capsules)
120
+ self.active: dict[str, float] = {}
121
+
122
+ def select(self, prompt: str, *, top_k: int = 3) -> list[ReasoningCapsule]:
123
+ routed = self.router.route(prompt, top_k=top_k)
124
+ self.active = {capsule.name: score for capsule, score in routed}
125
+ return [capsule for capsule, _ in routed]
126
+
127
+ def activation_vector(self) -> np.ndarray:
128
+ names = [capsule.name for capsule in self.router.capsules]
129
+ return np.asarray([self.active.get(name, 0.0) for name in names], dtype=np.float32)
130
+
131
+
132
+ class CapsuleExecutor:
133
+ """Execute active capsules and merge their activation summaries."""
134
+
135
+ def execute(
136
+ self,
137
+ capsules: list[ReasoningCapsule],
138
+ prompt: str,
139
+ *,
140
+ context: dict[str, Any] | None = None,
141
+ ) -> dict[str, Any]:
142
+ results = [capsule.execute(prompt, context=context) for capsule in capsules]
143
+ if not results:
144
+ return {"capsules": [], "activation": np.zeros(32, dtype=np.float32)}
145
+ activation = np.mean([item["embedding"] for item in results], axis=0)
146
+ return {"capsules": results, "activation": activation.astype(np.float32)}
147
+
148
+
149
+ __all__ = [
150
+ "CapsuleExecutor",
151
+ "CapsuleManager",
152
+ "CapsuleRouter",
153
+ "CodeCapsule",
154
+ "CreativeCapsule",
155
+ "MathCapsule",
156
+ "MedicalCapsule",
157
+ "ReasoningCapsule",
158
+ "ScienceCapsule",
159
+ ]
160
+