pasm2 2.0.0a9__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.
pasm2/__init__.py ADDED
@@ -0,0 +1,55 @@
1
+ """PASM v2.0 —— 通用 AI 认知底座(独立演进包,与 pasm/ 平行,互不侵入)。
2
+
3
+ 定位
4
+ ----
5
+ PASM v2.0:从「仿生认知引擎」走向「通用 AI 认知底座」。
6
+ 情感为核,逻辑为骨,语言为口,安全为界。给 AI 应用认知器官,而不是给 AI 应用指令。
7
+
8
+ 与 pasm/(0.7.x 现役引擎)的关系
9
+ --------------------------------
10
+ · 本包是**独立文件包**:不 import pasm/,pasm/ 也不 import 本包;
11
+ 桌面端(PASM Studio)、PyPI 发行、现有发版链路均不受影响。
12
+ · 唯一的显式关联是 `pasm2/engine_api.py`——它是 `pasm/engine_api.py`
13
+ 的**同源镜像**(阶段 0 冻结基线,api 1.1),由
14
+ `tools/sync_engine_api_mirror.py --lite pasm2/engine_api.py --note pasm2`
15
+ 同步并做漂移检测。
16
+ · 两条版本轴各自独立:
17
+ · pasm/ 引擎轴 → pasm.__version__(0.7.x,现役,不因 v2.0 改动)
18
+ · pasm2/ 底座轴 → pasm2.__version__(2.0.0a0,从阶段 0 起步)
19
+ · 桌面产品轴 → desktop/appinfo.py 的 APP_VERSION(完全不沾)
20
+
21
+ 阶段 0(基线冻结)交付物
22
+ ------------------------
23
+ · engine_api.py 契约冻结镜像(api 1.1)
24
+ · config.py 消融开关框架 —— 所有 v2.0 新模块默认全关
25
+ · safety.py 层 -2 安全层入口:价值锚点(不可修改)+ 骨架
26
+ · evalkit/ 成长日记 Schema(GrowthTrace)
27
+ · modules/ 十九层架构规划注册表(只登记、不实现)
28
+ · tests/ tests/test_pasm2_baseline.py 回归基线
29
+ · 校验 tools/verify_pasm2_baseline.py
30
+
31
+ 开发纪律(源自 v2.0 方案)
32
+ --------------------------
33
+ 契约先行 · 开关驱动 · 消融优先 · 异步写盘 · 可复现 · 回放验证;
34
+ 先安全后象量,先象量后符号,先验证后回写,先系统 1 后系统 2;
35
+ 每加一层必须有开关、有指标、有收益;守住 CPU 毫秒级、可解释、契约稳定三条底线。
36
+ """
37
+
38
+ __version__ = "2.0.0a9"
39
+ __author__ = "arronZheng (小志)"
40
+ __stage__ = "9-final-brainwide" # 阶段 9:小脑/DMN/显著网/自传体(收官)(应用开发者门面)
41
+ __api_version__ = "1.1" # 冻结的引擎接口契约版本(与 engine_api 镜像一致)
42
+
43
+ from . import engine_api # noqa: E402,F401 契约冻结镜像(零依赖,随包即用)
44
+ from . import safety # noqa: E402,F401 层 -2 安全层入口(价值锚点常开)
45
+
46
+
47
+ def version_info() -> dict:
48
+ """版本自述(基线报告与桌面看板用)。"""
49
+ return {
50
+ "package": "pasm2",
51
+ "version": __version__,
52
+ "stage": __stage__,
53
+ "api_version": __api_version__,
54
+ "sibling_engine": "pasm 0.7.x(独立,互不依赖)",
55
+ }
pasm2/capabilities.py ADDED
@@ -0,0 +1,136 @@
1
+ # -*- coding: utf-8 -*-
2
+ """pasm2 能力申报面(api 2.0-surface)——v2.0 九阶段收官(2026-09-29)。
3
+
4
+ 定位与纪律
5
+ ----------
6
+ ``pasm2/engine_api.py`` 是主仓 ``pasm/engine_api.py``(api 1.1)的**冻结镜像**,
7
+ 由 ``tools/sync_engine_api_mirror.py`` 做漂移检测——pasm2 不得单方面改动它。
8
+
9
+ 本模块是 pasm2 **自有**的能力申报层(surface,非契约镜像):
10
+ · 应用层可据此探测"当前配置下引擎开了哪些能力"(诚实申报,不编造);
11
+ · surface 版本随阶段推进(a9 = 九阶段全实装);
12
+ · 冻结镜像 api 1.1 与本 surface 并存,互不干扰(单向隔离铁律不破)。
13
+
14
+ 用法
15
+ ----
16
+ from pasm2.capabilities import capabilities, SURFACE_VERSION
17
+ caps = capabilities(PASM2Config()) # 全关 → enabled 为空(诚实)
18
+ caps = capabilities(PASM2Config(use_cerebellum=True))
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Dict, List
23
+
24
+ __all__ = ["SURFACE_VERSION", "STAGES", "STAGE_FLAGS", "capabilities",
25
+ "capability_diff", "selftest"]
26
+
27
+ SURFACE_VERSION = "2.0-surface.a9"
28
+
29
+ # 九阶段总览(与 MODULE_PLAN 对齐;status 以 MODULE_PLAN 为准,此处只做展示)
30
+ STAGES: List[Dict[str, str]] = [
31
+ {"stage": "0", "title": "基线冻结(契约镜像 + 安全层 + 成长记录)"},
32
+ {"stage": "1", "title": "仿脑件(边界守卫/实体自组织/丘脑/四通道/工作记忆/ACC)"},
33
+ {"stage": "2", "title": "符号化梯度 + 符号注册 + 三角校验 + 失衡监测"},
34
+ {"stage": "3", "title": "记忆图关联 + 岛叶内感受 + 反事实推理"},
35
+ {"stage": "4", "title": "SLH 单向桥(符号↔外部映射 + 四路路由)"},
36
+ {"stage": "5", "title": "CognitiveKit SDK(三档门面)"},
37
+ {"stage": "6", "title": "数学脑 v2(mathlab)+ 路由动态分值 + 记忆图增量"},
38
+ {"stage": "7", "title": "双系统门控 + 量子叠加决策"},
39
+ {"stage": "8", "title": "CognitiveAssembler(声明式流水线装配)"},
40
+ {"stage": "9", "title": "小脑时序 + 显著性网络 + DMN + 自传体记忆"},
41
+ ]
42
+
43
+ # 开关 → 所属阶段(selftest 校验每个键都是 PASM2Config 真实字段,fail-fast)
44
+ STAGE_FLAGS: Dict[str, str] = {
45
+ # 阶段 1(注:安全层/价值锚定是内核一部分,不设开关,故不在此申报)
46
+ "use_entity_attractor": "1",
47
+ "use_thalamus": "1", "use_brainstem": "1",
48
+ "dopamine_channel": "1", "serotonin_channel": "1",
49
+ "norepinephrine_channel": "1", "acetylcholine_channel": "1",
50
+ "use_prospective_memory": "1",
51
+ "use_acc": "1", "use_emotional_memory": "1",
52
+ # 阶段 2
53
+ "use_predictive_context": "2", "use_symbolization": "2",
54
+ "use_triangulation": "2", "use_imbalance_monitor": "2",
55
+ # 阶段 3
56
+ "use_pattern_separation": "3", "use_system_consolidation": "3",
57
+ "use_reconsolidation": "3", "use_memory_graph": "3",
58
+ "use_insula": "3", "use_counterfactual": "3",
59
+ # 阶段 4
60
+ "use_symbol_mapping": "4", "use_four_way_routing": "4",
61
+ # 阶段 6
62
+ "use_quantization": "6", "use_dynamic_routing_score": "6",
63
+ # 阶段 7
64
+ "use_dual_system": "7", "use_quantum": "7",
65
+ # 阶段 9
66
+ "use_cerebellum": "9", "use_dmn": "9",
67
+ "use_salience_network": "9", "use_autobiographical": "9",
68
+ }
69
+
70
+
71
+ def capabilities(config: Any) -> Dict[str, Any]:
72
+ """给定配置,诚实申报开启的能力面(按阶段分组)。
73
+
74
+ 返回:
75
+ {
76
+ "surface": SURFACE_VERSION,
77
+ "engine_stage": config 所在 pasm2.__stage__,
78
+ "enabled": {"1": [flags...], "2": [...]}, # 只含开启项
79
+ "disabled": n(关闭的受管开关数),
80
+ "total_managed": n(受管开关总数),
81
+ }
82
+ """
83
+ import dataclasses
84
+ fields = {f.name for f in dataclasses.fields(config)}
85
+ enabled: Dict[str, List[str]] = {}
86
+ disabled = 0
87
+ for flag, stage in STAGE_FLAGS.items():
88
+ if flag not in fields:
89
+ continue # 非受管/历史字段:跳过不申报
90
+ if getattr(config, flag):
91
+ enabled.setdefault(stage, []).append(flag)
92
+ else:
93
+ disabled += 1
94
+ import pasm2 as _p2
95
+ return {"surface": SURFACE_VERSION,
96
+ "engine_stage": getattr(_p2, "__stage__", "unknown"),
97
+ "enabled": dict(sorted(enabled.items())),
98
+ "disabled": disabled,
99
+ "total_managed": len([f for f in STAGE_FLAGS
100
+ if f in fields])}
101
+
102
+
103
+ def capability_diff(base: Any, other: Any) -> Dict[str, List[str]]:
104
+ """对比两配置的能力差集(other 相对 base 新增开启的开关)。"""
105
+ cb, co = capabilities(base), capabilities(other)
106
+ fb = {f for v in cb["enabled"].values() for f in v}
107
+ fo = {f for v in co["enabled"].values() for f in v}
108
+ return {"added": sorted(fo - fb), "removed": sorted(fb - fo)}
109
+
110
+
111
+ def selftest() -> str:
112
+ import dataclasses
113
+ import pasm2.config as p2config
114
+
115
+ fields = {f.name for f in dataclasses.fields(p2config.PASM2Config)}
116
+ bad = [f for f in STAGE_FLAGS if f not in fields]
117
+ assert not bad, "STAGE_FLAGS 含不存在的字段: %s" % bad
118
+
119
+ # 全关基线 → enabled 为空(诚实申报)
120
+ caps0 = capabilities(p2config.PASM2Config())
121
+ assert caps0["enabled"] == {} and caps0["disabled"] == caps0["total_managed"]
122
+ # 全开 → 覆盖阶段 1-9(5/8 是 SDK 层无开关,不在此申报)
123
+ cfg = p2config.PASM2Config(**{f: True for f in STAGE_FLAGS})
124
+ caps1 = capabilities(cfg)
125
+ assert set(caps1["enabled"]) == {"1", "2", "3", "4", "6", "7", "9"}, caps1
126
+ assert caps1["disabled"] == 0
127
+ # diff
128
+ d = capability_diff(p2config.PASM2Config(), cfg)
129
+ assert len(d["added"]) == len(STAGE_FLAGS) and d["removed"] == []
130
+ return ("capabilities selftest OK: %s / %d managed flags / stages %s"
131
+ % (SURFACE_VERSION, caps1["total_managed"],
132
+ sorted(caps1["enabled"])))
133
+
134
+
135
+ if __name__ == "__main__":
136
+ print(selftest())
@@ -0,0 +1,9 @@
1
+ """pasm2.cognitive —— 阶段 6:认知体的高层数值/分析能力层。
2
+
3
+ mathlab(数学脑 v2)是本包首个成员:与主仓 pasm/cognitive/mathlab.py 同源
4
+ 命名、pasm2 内自持实现(单向隔离铁律)。量化统计(quantize_vector /
5
+ transition_matrix)是记忆图 δ 项与四路路由动态分值的离散统计底座。
6
+ """
7
+ from . import mathlab
8
+
9
+ __all__ = ["mathlab"]
@@ -0,0 +1,318 @@
1
+ """pasm2.cognitive.mathlab —— 阶段 6:离散与线性分析(数学脑 v2)。
2
+
3
+ 定位:给 v2.0 认知体装上「会算」的脑子——纯 numpy,无重依赖,CPU 毫秒级。
4
+ 与主仓 `pasm/cognitive/mathlab.py`(v0.27.0 落地)**API 同源命名**,pasm2 内
5
+ 自持实现(pasm/ 单向隔离铁律:不 import pasm,也不被 pasm import)。
6
+
7
+ 能力清单(对齐 docs/roadmap_multimodal_quantum_math.md Phase A):
8
+ 线性 矩阵运算、行列式/秩、线性方程组求解(Gauss 消元)
9
+ 线性 最小二乘线性回归(斜率/截距/R²/残差)、多元回归
10
+ 统计 均值/方差/分位数/相关系数矩阵、离群点检测(IQR/z-score)
11
+ 离散 图分析:拓扑排序(环检测)、最短路(Dijkstra)、连通分量
12
+ 离散 排列组合、马尔可夫链稳态
13
+ 量化 向量/序列离散化 + 转移矩阵统计(认知管线接入点:记忆图 δ 项
14
+ 与四路路由分值的统计底座)
15
+
16
+ 诚实性纪律:输入退化(空序列/奇异矩阵/不连通图)如实申报,不编造数值。
17
+ """
18
+ from __future__ import annotations
19
+
20
+ import math
21
+ from itertools import combinations
22
+ from typing import Any, Dict, List, Optional, Sequence, Tuple
23
+
24
+ import numpy as np
25
+
26
+ __all__ = [
27
+ "fit_linear", "fit_multi_linear", "describe", "corr_matrix",
28
+ "solve_linear_system", "matrix_facts",
29
+ "topo_sort", "has_cycle", "dijkstra", "connected_components",
30
+ "markov_steady", "choose",
31
+ "quantize_vector", "quantize_series", "transition_matrix",
32
+ ]
33
+
34
+
35
+ def _f(x: Any) -> float:
36
+ return float(x)
37
+
38
+
39
+ # ================= 线性 =================
40
+ def fit_linear(x: Sequence[float], y: Sequence[float]) -> dict:
41
+ """最小二乘一元线性回归:slope/intercept/r2/rmse/n(不足 2 点诚实报错)。"""
42
+ if len(x) != len(y) or len(x) < 2:
43
+ return {"ok": False, "reason": "need len(x)==len(y)>=2"}
44
+ xs = np.asarray(x, dtype=np.float64)
45
+ ys = np.asarray(y, dtype=np.float64)
46
+ if float(np.std(xs)) < 1e-12:
47
+ return {"ok": False, "reason": "x is constant"}
48
+ slope, intercept = np.polyfit(xs, ys, 1)
49
+ pred = slope * xs + intercept
50
+ resid = ys - pred
51
+ ss_res = float(np.sum(resid ** 2))
52
+ ss_tot = float(np.sum((ys - ys.mean()) ** 2))
53
+ r2 = 1.0 - ss_res / ss_tot if ss_tot > 1e-12 else 1.0
54
+ return {"ok": True, "slope": round(float(slope), 6),
55
+ "intercept": round(float(intercept), 6), "r2": round(r2, 6),
56
+ "rmse": round(math.sqrt(ss_res / len(xs)), 6), "n": len(xs)}
57
+
58
+
59
+ def fit_multi_linear(X: Sequence[Sequence[float]], y: Sequence[float],
60
+ ridge: float = 0.0) -> dict:
61
+ """多元最小二乘(可加 ridge 正则);奇异/欠定时诚实申报。"""
62
+ A = np.asarray(X, dtype=np.float64)
63
+ t = np.asarray(y, dtype=np.float64)
64
+ if A.ndim != 2 or A.shape[0] != t.size or A.shape[0] == 0:
65
+ return {"ok": False, "reason": "shape mismatch"}
66
+ M = np.hstack([A, np.ones((A.shape[0], 1))])
67
+ G = M.T @ M + ridge * np.eye(M.shape[1])
68
+ try:
69
+ w = np.linalg.solve(G, M.T @ t)
70
+ except np.linalg.LinAlgError:
71
+ return {"ok": False, "reason": "singular"}
72
+ pred = M @ w
73
+ resid = t - pred
74
+ ss_res = float(np.sum(resid ** 2))
75
+ ss_tot = float(np.sum((t - t.mean()) ** 2))
76
+ r2 = 1.0 - ss_res / ss_tot if ss_tot > 1e-12 else 1.0
77
+ return {"ok": True, "coef": [round(float(v), 6) for v in w[:-1]],
78
+ "intercept": round(float(w[-1]), 6), "r2": round(r2, 6),
79
+ "rmse": round(math.sqrt(ss_res / len(t)), 6)}
80
+
81
+
82
+ def describe(series: Sequence[float], name: str = "") -> dict:
83
+ """描述统计:n/mean/std/min/q25/median/q75/max/IQR 离群点。"""
84
+ a = np.asarray(series, dtype=np.float64)
85
+ if a.size == 0:
86
+ return {"ok": False, "reason": "empty series", "name": name}
87
+ q1, med, q3 = (float(np.percentile(a, p)) for p in (25, 50, 75))
88
+ iqr = q3 - q1
89
+ lo, hi = q1 - 1.5 * iqr, q3 + 1.5 * iqr
90
+ outliers = [round(float(v), 6) for v in a[(a < lo) | (a > hi)]]
91
+ return {"ok": True, "name": name, "n": int(a.size),
92
+ "mean": round(float(a.mean()), 6),
93
+ "std": round(float(a.std()), 6),
94
+ "min": round(float(a.min()), 6), "q25": round(q1, 6),
95
+ "median": round(med, 6), "q75": round(q3, 6),
96
+ "max": round(float(a.max()), 6), "iqr": round(iqr, 6),
97
+ "outliers_iqr": outliers}
98
+
99
+
100
+ def corr_matrix(rows: Sequence[Sequence[float]],
101
+ cols: Optional[List[str]] = None) -> dict:
102
+ """相关系数矩阵(列数 < 2 或常数列诚实申报)。"""
103
+ A = np.asarray(rows, dtype=np.float64)
104
+ if A.ndim != 2 or A.shape[0] < 2 or A.shape[1] < 2:
105
+ return {"ok": False, "reason": "need >=2 rows and >=2 cols"}
106
+ with np.errstate(divide="ignore", invalid="ignore"):
107
+ C = np.corrcoef(A, rowvar=False)
108
+ C = np.where(np.isfinite(C), C, 0.0) # 常数列相关系数无定义 → 0 并申报
109
+ degenerate = [j for j in range(A.shape[1])
110
+ if float(np.std(A[:, j])) < 1e-12]
111
+ names = cols or ["c%d" % j for j in range(A.shape[1])]
112
+ return {"ok": True, "cols": names,
113
+ "matrix": [[round(float(v), 6) for v in row] for row in C],
114
+ "degenerate_cols": degenerate}
115
+
116
+
117
+ def solve_linear_system(A: Sequence[Sequence[float]],
118
+ b: Sequence[float]) -> dict:
119
+ """Gauss 消元解 Ax=b(奇异时诚实申报,不凑数)。"""
120
+ M = np.asarray(A, dtype=np.float64)
121
+ v = np.asarray(b, dtype=np.float64)
122
+ if M.ndim != 2 or M.shape[0] != v.size:
123
+ return {"ok": False, "reason": "shape mismatch"}
124
+ try:
125
+ x = np.linalg.solve(M, v)
126
+ except np.linalg.LinAlgError as e:
127
+ return {"ok": False, "reason": "singular: %s" % e}
128
+ return {"ok": True,
129
+ "solution": [round(float(val), 6) for val in x]}
130
+
131
+
132
+ def matrix_facts(A: Sequence[Sequence[float]]) -> dict:
133
+ """行列式/秩/迹/条件数。"""
134
+ M = np.asarray(A, dtype=np.float64)
135
+ if M.ndim != 2 or M.size == 0:
136
+ return {"ok": False, "reason": "need 2-D matrix"}
137
+ return {"ok": True, "shape": list(M.shape),
138
+ "det": round(float(np.linalg.det(M)), 6) if M.shape[0] == M.shape[1] else None,
139
+ "rank": int(np.linalg.matrix_rank(M)),
140
+ "trace": round(float(np.trace(M)), 6),
141
+ "cond": (round(float(np.linalg.cond(M)), 6)
142
+ if M.shape[0] == M.shape[1] else None)}
143
+
144
+
145
+ # ================= 离散(图) =================
146
+ def topo_sort(n: int, edges: Sequence[Tuple[int, int]]) -> dict:
147
+ """Kahn 拓扑排序;有环返回 ok=False + 环上节点(流程依赖校验用)。"""
148
+ adj: List[List[int]] = [[] for _ in range(n)]
149
+ indeg = [0] * n
150
+ for u, v in edges:
151
+ if not (0 <= u < n and 0 <= v < n):
152
+ return {"ok": False, "reason": "node out of range"}
153
+ adj[u].append(v)
154
+ indeg[v] += 1
155
+ queue = [i for i in range(n) if indeg[i] == 0]
156
+ order: List[int] = []
157
+ while queue:
158
+ u = queue.pop(0)
159
+ order.append(u)
160
+ for v in adj[u]:
161
+ indeg[v] -= 1
162
+ if indeg[v] == 0:
163
+ queue.append(v)
164
+ if len(order) != n:
165
+ cyclic = sorted(i for i in range(n) if indeg[i] > 0)
166
+ return {"ok": False, "reason": "cycle", "cyclic_nodes": cyclic}
167
+ return {"ok": True, "order": order}
168
+
169
+
170
+ def has_cycle(n: int, edges: Sequence[Tuple[int, int]]) -> bool:
171
+ return not topo_sort(n, edges)["ok"]
172
+
173
+
174
+ def dijkstra(graph: Dict[int, List[Tuple[int, float]]],
175
+ src: int) -> Dict[int, float]:
176
+ """最短路(不可达节点不出现在结果里)。"""
177
+ import heapq
178
+ dist: Dict[int, float] = {src: 0.0}
179
+ heap = [(0.0, src)]
180
+ seen = set()
181
+ while heap:
182
+ d, u = heapq.heappop(heap)
183
+ if u in seen:
184
+ continue
185
+ seen.add(u)
186
+ for v, w in graph.get(u, ()):
187
+ nd = d + float(w)
188
+ if nd < dist.get(v, math.inf):
189
+ dist[v] = nd
190
+ heapq.heappush(heap, (nd, v))
191
+ return {k: round(v, 6) for k, v in dist.items() if k != src}
192
+
193
+
194
+ def connected_components(n: int, edges: Sequence[Tuple[int, int]]) -> dict:
195
+ """无向连通分量(并查集)。"""
196
+ parent = list(range(n))
197
+
198
+ def find(x: int) -> int:
199
+ while parent[x] != x:
200
+ parent[x] = parent[parent[x]]
201
+ x = parent[x]
202
+ return x
203
+
204
+ for u, v in edges:
205
+ ru, rv = find(u), find(v)
206
+ if ru != rv:
207
+ parent[ru] = rv
208
+ groups: Dict[int, List[int]] = {}
209
+ for i in range(n):
210
+ groups.setdefault(find(i), []).append(i)
211
+ comps = sorted((sorted(g) for g in groups.values()), key=len, reverse=True)
212
+ return {"ok": True, "n_components": len(comps),
213
+ "components": comps, "largest": comps[0] if comps else []}
214
+
215
+
216
+ # ================= 离散(组合 / 马尔可夫) =================
217
+ def choose(n: int, k: int) -> int:
218
+ if k < 0 or k > n:
219
+ return 0
220
+ return math.comb(n, k)
221
+
222
+
223
+ def markov_steady(P: Sequence[Sequence[float]],
224
+ iters: int = 2000) -> dict:
225
+ """行随机矩阵幂法求稳态分布;非行随机诚实申报。"""
226
+ M = np.asarray(P, dtype=np.float64)
227
+ if M.ndim != 2 or M.shape[0] != M.shape[1] or M.shape[0] == 0:
228
+ return {"ok": False, "reason": "need square matrix"}
229
+ row_sums = M.sum(axis=1)
230
+ if not np.allclose(row_sums, 1.0, atol=1e-6):
231
+ return {"ok": False, "reason": "not row-stochastic",
232
+ "row_sums": [round(float(s), 6) for s in row_sums]}
233
+ pi = np.full(M.shape[0], 1.0 / M.shape[0])
234
+ for _ in range(iters):
235
+ pi = pi @ M
236
+ return {"ok": True,
237
+ "steady": [round(float(v), 6) for v in pi]}
238
+
239
+
240
+ # ================= 量化(认知管线接入点) =================
241
+ def quantize_vector(z: Sequence[float], levels: int = 4,
242
+ lo: Optional[float] = None,
243
+ hi: Optional[float] = None) -> List[int]:
244
+ """连续向量 → 均匀离散码(0..levels-1)。
245
+
246
+ lo/hi 缺省用向量自身 min/max(自适应);lo==hi(常向量)→ 全 0 码,
247
+ 如实退化不报错。记忆图统计相关项(δ 项)与路由分值的离散统计底座。
248
+ """
249
+ a = np.asarray(z, dtype=np.float64)
250
+ if a.size == 0 or levels < 1:
251
+ return []
252
+ lo_v = float(a.min()) if lo is None else float(lo)
253
+ hi_v = float(a.max()) if hi is None else float(hi)
254
+ if hi_v - lo_v < 1e-12:
255
+ return [0] * a.size
256
+ idx = np.clip(((a - lo_v) / (hi_v - lo_v) * levels).astype(int),
257
+ 0, levels - 1)
258
+ return [int(v) for v in idx]
259
+
260
+
261
+ def quantize_series(series: Sequence[float], levels: int = 4) -> List[int]:
262
+ """标量序列量化(等价于 quantize_vector 的一维情形)。"""
263
+ return quantize_vector(series, levels=levels)
264
+
265
+
266
+ def transition_matrix(codes: Sequence[int], n_states: Optional[int] = None,
267
+ alpha: float = 0.0) -> dict:
268
+ """离散码序列 → 转移计数/概率矩阵(可选 Laplace 平滑 alpha)。
269
+
270
+ 返回 {ok, n_states, counts, probs, n_transitions};probs 行归一
271
+ (平滑后),可与 markov_steady 串联求稳态分布。
272
+ """
273
+ c = list(codes)
274
+ if len(c) < 2:
275
+ return {"ok": False, "reason": "need >=2 codes"}
276
+ n = n_states if n_states is not None else (max(c) + 1)
277
+ if min(c) < 0 or max(c) >= n:
278
+ return {"ok": False, "reason": "code out of range"}
279
+ counts = np.full((n, n), float(alpha))
280
+ for a, b in zip(c[:-1], c[1:]):
281
+ counts[a][b] += 1.0
282
+ row_sums = counts.sum(axis=1, keepdims=True)
283
+ probs = np.divide(counts, np.maximum(row_sums, 1e-12))
284
+ return {"ok": True, "n_states": int(n), "n_transitions": len(c) - 1,
285
+ "counts": counts.astype(int).tolist(),
286
+ "probs": [[round(float(v), 6) for v in row] for row in probs]}
287
+
288
+
289
+ # ================= 自检 =================
290
+ def selftest() -> str:
291
+ rng = np.random.RandomState(0)
292
+ xs = list(range(20))
293
+ ys = [2.0 * x + 1.0 + rng.randn() * 0.1 for x in xs]
294
+ fl = fit_linear(xs, ys)
295
+ assert fl["ok"] and abs(fl["slope"] - 2.0) < 0.05 and fl["r2"] > 0.99
296
+ sol = solve_linear_system([[2, 1], [1, 3]], [5, 10])
297
+ assert sol["ok"] and abs(sol["solution"][0] - 1.0) < 1e-6
298
+ assert solve_linear_system([[1, 2], [2, 4]], [1, 2])["ok"] is False
299
+ ts = topo_sort(3, [(0, 1), (1, 2)])
300
+ assert ts["ok"] and ts["order"] == [0, 1, 2]
301
+ assert has_cycle(2, [(0, 1), (1, 0)]) is True
302
+ d = dijkstra({0: [(1, 1.0), (2, 4.0)], 1: [(2, 1.0)]}, 0)
303
+ assert d[2] == 2.0
304
+ cc = connected_components(4, [(0, 1), (2, 3)])
305
+ assert cc["n_components"] == 2
306
+ ms = markov_steady([[0.5, 0.5], [0.5, 0.5]])
307
+ assert ms["ok"] and abs(ms["steady"][0] - 0.5) < 1e-3
308
+ assert markov_steady([[0.5, 0.4], [0.5, 0.6]])["ok"] is False
309
+ ds = describe([1, 2, 3, 4, 100])
310
+ assert ds["ok"] and 100 in ds["outliers_iqr"]
311
+ q = quantize_vector([0.0, 0.24, 0.26, 1.0], levels=4)
312
+ assert q == [0, 0, 1, 3]
313
+ tm = transition_matrix([0, 1, 0, 1, 1])
314
+ assert tm["ok"] and tm["n_transitions"] == 4
315
+ ms2 = markov_steady(tm["probs"])
316
+ assert ms2["ok"]
317
+ return ("mathlab selftest OK: linear/stats/graph/markov/quantize "
318
+ "all honest")