// engine.mbt —— 译脉·先知 2.0 (MoonBit) 预知记忆网络(PMN) 引擎
// =====================================================================
// 从「记录型记忆」升级为「预知型记忆」。核心主张:只有能预测未来
// 发展的记忆,才是好记忆。
//
// 把神经网络的「逻辑」(而非重量) 搬进纯标准库实现(零第三方依赖):
// D1 突触图谱 -> MemoryNode + 加权关联边 + 赫布学习
// D2 激活扩散 -> 联想召回 (spreading activation)
// D3 预测前向模型 -> 转移计数 + 下一步需求预测
// D4 预测价值定价 -> 价值公式以「预测效用」为主导项 (BETA)
// D5 情景序列 -> episodic 序列学习
// D6 固化重放 -> 离线剪枝/合并/强化 + 约束契约回滚
// D7 不确定性&可解释 -> 置信度 + 激活/预测路径 (白盒)
// D8 概念抽象 -> 角色级转移,支撑冷启动泛化
//
// 按 D1–D8 规范实现;差异处:用逻辑时钟(self.clock)替代 wall-clock
// 时间戳(确定性、零依赖),算法与常量完全自洽。
// P1 性能:小语料( 权重 (突触)
mut predictive_value : Double
mut hit_count : Int
mut predict_count : Int
mut last_active : Double // 最近一次被激活/命中(逻辑时钟),用于弹性遗忘的真实时差折扣
mut is_term : Bool // 术语节点(美:高初始激活 + 命中高亮,mark_term 置位)
translation : String // TM 译文 / TB 目标术语(#22 TM/TB 一等公民)
// ---- TM 特征缓存(内部派生态,不进 to_json;add_tm 构建 / from_json 后重建)----
mut tm_toks : Array[String] // yimai_tokenize(text)(未去重,供 concordance 计数 + idf 重建复用)
mut tm_ngrams : Map[String, Bool] // char 2-gram 去重集合(P1 Map 化:fuzzy 字符 2-gram 分项零分配)
mut tm_toks_set : Map[String, Bool] // unique(tm_toks) 去重集合(P1 Map 化:fuzzy token 集合分项零分配)
}
// 预测价值分解(D4)的命名返回值:供 _recompute_values 与 explain_card 共用,避免 8 元组位置歧义
priv struct ValueBreakdown {
u_freq : Double
u_rec : Double
u_fb : Double
u_past : Double
u_pred : Double
c_graph : Double
}
// 引擎状态
pub struct ProphecyEngine {
mut memories : Map[String, MemoryNode]
mut transitions : Map[String, Map[String, Double]] // D3 前向模型:src -> {dst: count}(固化前快照,F1 需回滚)
mut episodes : Array[Array[String]]
mut context : Array[String] // 当前上下文窗口 (最近 mids)
mut stats_preds : Int
mut stats_hits : Int
mut stats_remembers : Int
mut stats_evolutions : Int
mut seq : Int
mut last_pred : Array[String] // 最近一次 predict 的 Top-K (空=无)
mut cur_episode : Array[String]
mut explore : Double // 探索度[0,1]:元认知调控
mut meta_hits : Array[Int] // 近期命中(0/1)滚动窗口
mut snapshot : Option[Map[String, MemoryNode]] // 固化前快照(约束契约)
mut snap_trans : Option[Map[String, Map[String, Double]]] // 固化前转移快照(约束契约·F1)
mut snap_role : Option[Map[String, Map[String, Double]]] // 固化前角色转移快照(约束契约·F1)
mut role_trans : Map[String, Map[String, Double]] // D8 角色级转移(固化前快照,F1 需回滚)
mut role_index : Map[String, String] // 角色 -> 代表节点 id
// ---- 路线图拓展字段(内部态,不进入 to_json;from_json 后由 rebuild_* 重建)----
mut role_members : Map[String, Array[String]] // 角色 -> 成员节点倒排索引(快:大图角色限定召回加速)
mut trans2 : Map[String, Map[String, Double]] // 二阶马尔可夫:(w1\0w2) -> {w3: count}(准,#17 实现)
mut domain_bias : Map[String, Double] // 领域偏置 ΔW(LoRA 式,按角色原型)(准,#19 实现)
mut hebb_lr : Double // 动态赫布学习率(#18 自适应,consolidate 中重赋值)
mut attn_alpha : Double // 注意力边权系数 α(#19,默认 0=关,set_attention 开启)
mut attn_beta : Double // 注意力边权系数 β(#19,角色匹配项)
mut pred_cache : Map[String, Json] // 预测缓存(#16 快):引擎变更时全量失效(非 LRU 淘汰)
mut cache_epoch : Int // 引擎变更计数,失效预测缓存
cl_buf : Array[Json] // 在线对比学习缓冲(#19)
mut wal_log : Array[String] // 增量固化 WAL 日志(#21,事件溯源)
mut fed_add : Int // 联邦增量:新增节点计数
mut fed_upd : Int // 联邦增量:更新计数
mut clock : Double // 逻辑时钟(替代 wall-clock)
// ---- S1 检索强化:TM IDF 表(内部态,不进 to_json;from_json 后重建)----
mut tm_df : Map[String, Int] // TM 文档频率:token -> 含该 token 的 TM 数
mut tm_idf : Map[String, Double] // 预计算 IDF:ln((N+1)/(df+1)) + 1
mut tm_idf_dirty : Bool // add_tm/剪枝/restore 后置真,fuzzy_match 惰性重算
mut tm_count : Int // TM 节点数缓存(rebuild_tm_idf 顺带刷新;count_tm O(1) 读)
// ---- P1 倒排剪枝:TM token -> mids 倒排(内部态,不进 to_json;与 tm_idf 同源)----
mut tm_postings : Map[String, Array[String]] // TM 倒排:token -> 含该 token 的 TM mid 列表(fuzzy_match 候选剪枝)
// ---- P4-2 术语首字符倒排(内部态,不进 to_json;from_json 后重建)----
mut term_idx : Map[String, Array[String]] // term 首字符 -> term 节点 mid 列表(enforce/check 候选剪枝加速)
mut term_idx_dirty : Bool // load_tbx/mark_term/剪枝后置真,惰性重建
}
// ---------------------------------------------------------------------------
// 内部辅助
// ---------------------------------------------------------------------------
fn min1(x : Double) -> Double {
if x > 1.0 { 1.0 } else { x }
}
fn max0(a : Double, b : Double) -> Double {
if a > b { a } else { b }
}
// 转移计数自增(一阶马尔可夫边;Map 原地修改跨副本生效)
fn inc_trans(
trans : Map[String, Map[String, Double]],
a : String,
b : String,
) -> Unit {
match trans.get(a) {
Some(inner) => {
let c = match inner.get(b) { Some(x) => x; None => 0.0 }
inner.set(b, c + 1.0)
}
None => {
let inner : Map[String, Double] = Map::from_iter(([] : Array[(String, Double)]).iter())
inner.set(b, 1.0)
trans.set(a, inner)
}
}
}
fn copy_memories(m : Map[String, MemoryNode]) -> Map[String, MemoryNode] {
let out : Map[String, MemoryNode] = Map::from_iter(([] : Array[(String, MemoryNode)]).iter())
for mid, mn in m.iter2() {
out.set(mid, mn)
}
out
}
// 转移表深拷贝(F1:固化前快照须独立副本,否则回滚会引用同一对象)
fn copy_trans(t : Map[String, Map[String, Double]]) -> Map[String, Map[String, Double]] {
let out : Map[String, Map[String, Double]] =
Map::from_iter(([] : Array[(String, Map[String, Double])]).iter())
for a, d in t.iter2() {
let inner : Map[String, Double] = Map::from_iter(([] : Array[(String, Double)]).iter())
for b, c in d.iter2() {
inner.set(b, c)
}
out.set(a, inner)
}
out
}
// 角色倒排索引:增量维护(仅 ADD,避免误删;restore/prune 时整体重建)
fn ProphecyEngine::role_members_add(self : ProphecyEngine, role : String, mid : String) -> Unit {
if role == "" {
return
}
match self.role_members.get(role) {
Some(arr) => {
if !arr_contains(arr, mid) {
arr.push(mid)
}
}
None => {
let a : Array[String] = [mid]
self.role_members.set(role, a)
}
}
}
fn ProphecyEngine::rebuild_role_members(self : ProphecyEngine) -> Unit {
self.role_members = Map::from_iter(([] : Array[(String, Array[String])]).iter())
for mid, m in self.memories.iter2() {
for rk in roles_of(m.text) {
self.role_members_add(rk, mid)
}
}
}
// 预测缓存失效(引擎被修改时调用,保证记忆更新后预测不过期)
fn ProphecyEngine::invalidate_pred_cache(self : ProphecyEngine) -> Unit {
self.pred_cache = Map::from_iter(([] : Array[(String, Json)]).iter())
self.cache_epoch = self.cache_epoch + 1
}
// pred_cache 容量上限:超过后清空整表(预测缓存是内部态,仅影响下一次是否命中,
// 不改变预测结果本身,故对确定性输出零影响;清空比 LRU 更简单且无损正确性)
const PRED_CACHE_MAX : Int = 512
fn ProphecyEngine::edges_count(self : ProphecyEngine) -> Int {
let mut total = 0
for _, m in self.memories.iter2() {
total = total + m.edges.length()
}
total
}
// 取对象字段为 Json 数组
fn get_arr_j(j : Json, key : String) -> Array[Json] {
match j {
Json::Object(m) =>
match m.get(key) {
Some(Json::Array(a)) => a
_ => []
}
_ => []
}
}
// 解析 episodes:Json 数组的数组 -> Array[Array[String]]
fn parse_episodes(a : Array[Json]) -> Array[Array[String]] {
let out : Array[Array[String]] = []
for ep in a {
match ep {
Json::Array(inner) => {
let sa : Array[String] = []
for t in inner {
match t {
Json::String(s) => sa.push(s)
_ => ()
}
}
out.push(sa)
}
_ => ()
}
}
out
}
// 泛型降序排序(稳定自底向上归并,零依赖):按 key 抽取的数值字段对 arr 做原地降序重排。
// 统一取代原 sort_sd / sort_si / sort_scored 三份近重复的选型排序,
// 以及 fuzzy_match / concordance 内联的插入排序。
// P2-1:将原 O(n²) 选择排序替换为 O(n log n),且为稳定归并(相等 key 保持原相对顺序,
// 与原选择排序逐位一致,故对确定性输出零影响)。
fn[T] sort_desc_by_key(arr : Array[T], key : (T) -> Double) -> Unit {
let n = arr.length()
if n <= 1 {
return
}
// 辅助数组(长度与源一致),自底向上归并
let aux : Array[T] = []
for i = 0; i < n; i = i + 1 {
aux.push(arr[i])
}
let mut width = 1
while width < n {
let mut lo = 0
while lo < n {
let mid = if lo + width < n { lo + width } else { n }
let hi = if lo + 2 * width < n { lo + 2 * width } else { n }
// 合并 arr[lo:mid] 与 arr[mid:hi] 到 aux[lo:hi]:取左优先 ⇒ 稳定
let mut i = lo
let mut j = mid
let mut k = lo
while k < hi {
if j >= hi || (i < mid && key(arr[i]) >= key(arr[j])) {
aux[k] = arr[i]
i = i + 1
} else {
aux[k] = arr[j]
j = j + 1
}
k = k + 1
}
lo = lo + 2 * width
}
// 一趟结束:aux 写回 arr,供下一轮读取
for k = 0; k < n; k = k + 1 {
arr[k] = aux[k]
}
width = width * 2
}
}
// 泛型 Top-K 打包(P2-3):对已按 score 降序的候选数组取前 k 个,经 pack 映射为 Json。
// 与 fuzzy_match / fuzzy_match_legacy 原内联 while 循环逐位等价(None 跳过、None 不压缩索引)。
fn[T] pack_topk_json(scored : Array[T], k : Int, pack : (T) -> Option[Json]) -> Array[Json] {
let out : Array[Json] = []
let lim = if k > scored.length() { scored.length() } else { k }
let mut i = 0
while i < lim {
match pack(scored[i]) {
Some(j) => out.push(j)
None => ()
}
i = i + 1
}
out
}
// 节点 -> Json
fn node_to_json(m : MemoryNode) -> Json {
obj([
("id", str_json(m.id)),
("text", str_json(m.text)),
("type", str_json(m.mtype)),
("vec", dmap_to_json(m.vec)),
("created", num_json(m.created)),
("last_used", num_json(m.last_used)),
("use_count", num_json(m.use_count.to_double())),
("feedback", num_json(m.feedback)),
("edges", dmap_to_json(m.edges)),
("predictive_value", num_json(m.predictive_value)),
("hit_count", num_json(m.hit_count.to_double())),
("predict_count", num_json(m.predict_count.to_double())),
("last_active", num_json(m.last_active)),
("is_term", if m.is_term { 1.0 } else { 0.0 }),
("translation", str_json(m.translation)),
])
}
// Json -> 节点
fn node_from_json(j : Json) -> MemoryNode {
{
id: get_str(j, "id"),
text: get_str(j, "text"),
mtype: get_str(j, "type"),
vec: json_to_dmap(get_obj(j, "vec")),
created: get_num(j, "created"),
last_used: get_num(j, "last_used"),
use_count: get_num(j, "use_count").to_int(),
feedback: get_num(j, "feedback"),
edges: json_to_dmap(get_obj(j, "edges")),
predictive_value: get_num(j, "predictive_value"),
hit_count: get_num(j, "hit_count").to_int(),
predict_count: get_num(j, "predict_count").to_int(),
last_active: get_num(j, "last_active"),
is_term: get_num(j, "is_term") != 0.0,
translation: get_str(j, "translation"),
tm_toks: [],
tm_toks_set: Map([]),
tm_ngrams: Map([]),
}
}
fn memories_to_json(m : Map[String, MemoryNode]) -> Json {
let o : Map[String, Json] = Map::from_iter(([] : Array[(String, Json)]).iter())
for mid, mn in m.iter2() {
o.set(mid, node_to_json(mn))
}
o.to_json()
}
// 转移表序列化(F1 修复补全:快照三元组须随状态持久化,
// 否则重载后 restore() 会回滚 memories 却不回滚 transitions/role_trans)
fn trans_map_to_json(t : Map[String, Map[String, Double]]) -> Json {
let o : Map[String, Json] = Map::from_iter(([] : Array[(String, Json)]).iter())
for a, d in t.iter2() {
o.set(a, dmap_to_json(d))
}
o.to_json()
}
fn json_to_trans_map(j : Json) -> Map[String, Map[String, Double]] {
let out : Map[String, Map[String, Double]] =
Map::from_iter(([] : Array[(String, Map[String, Double])]).iter())
match j {
Json::Object(m) => for a, aj in m.iter2() { out.set(a, json_to_dmap(aj)) }
_ => ()
}
out
}
// ---------------------------------------------------------------------------
// 构造 / ID
// ---------------------------------------------------------------------------
pub fn ProphecyEngine::make() -> ProphecyEngine {
{
memories: Map::from_iter(([] : Array[(String, MemoryNode)]).iter()),
transitions: Map::from_iter(([] : Array[(String, Map[String, Double])]).iter()),
episodes: [],
context: [],
stats_preds: 0,
stats_hits: 0,
stats_remembers: 0,
stats_evolutions: 0,
seq: 0,
last_pred: [],
cur_episode: [],
explore: 0.0,
meta_hits: [],
snapshot: None,
snap_trans: None,
snap_role: None,
role_trans: Map::from_iter(([] : Array[(String, Map[String, Double])]).iter()),
role_index: Map::from_iter(([] : Array[(String, String)]).iter()),
role_members: Map::from_iter(([] : Array[(String, Array[String])]).iter()),
trans2: Map::from_iter(([] : Array[(String, Map[String, Double])]).iter()),
domain_bias: Map::from_iter(([] : Array[(String, Double)]).iter()),
hebb_lr: HEBB_LR,
attn_alpha: 0.0,
attn_beta: 0.0,
pred_cache: Map::from_iter(([] : Array[(String, Json)]).iter()),
cache_epoch: 0,
cl_buf: [],
wal_log: [],
fed_add: 0,
fed_upd: 0,
clock: 0.0,
tm_df: Map::from_iter(([] : Array[(String, Int)]).iter()),
tm_idf: Map::from_iter(([] : Array[(String, Double)]).iter()),
tm_idf_dirty: false,
tm_count: 0,
tm_postings: Map::from_iter(([] : Array[(String, Array[String])]).iter()),
term_idx: Map::from_iter(([] : Array[(String, Array[String])]).iter()),
term_idx_dirty: false,
}
}
fn ProphecyEngine::_new_id(self : ProphecyEngine) -> String {
self.seq = self.seq + 1
"m" + self.seq.to_string()
}
// ---------------------------------------------------------------------------
// 序列化(状态跨调用 / 持久化)
// ---------------------------------------------------------------------------
//
// to_json / from_json 是状态 schema 的**事实契约**——`tm_store.json` 落盘 + 跨会话恢复
// 全部依赖它们。改字段名 / 顺序会破坏 backward compat,所以这两个函数保持**显式列出**所有
// 字段(不抽 helper,不动字段名)。
//
// 评估结论(2026-08 评审):当前 to_json 50 行 + from_json 50 行 + schema 21 字段,
// 还在可读范围,**不强拆**。拆分触发条件(任一):
// 1) schema 字段数 > 30(每段 > 8 字段,逻辑才"自然"分组)
// 2) 字段分组出现明显语义边界(例如新增 neural 层 / multimodal OCR / 联邦 WAL 时)
// 3) to_json 或 from_json 任一行数 > 100
//
// 短期可改进方向(如果以后真拆):
// - to_json 拆为 to_json_core / to_json_episodes / to_json_meta / to_json_tuning 四段
// - from_json 同构四段
// - 加 schema_version 字段在 JSON 顶层(目前隐式 v0.1.0,无显式版本号)
pub fn ProphecyEngine::to_json(self : ProphecyEngine) -> Json {
let mem_map : Map[String, Json] = Map::from_iter(([] : Array[(String, Json)]).iter())
for mid, m in self.memories.iter2() {
mem_map.set(mid, node_to_json(m))
}
let trans_map : Map[String, Json] = Map::from_iter(([] : Array[(String, Json)]).iter())
for a, d in self.transitions.iter2() {
trans_map.set(a, dmap_to_json(d))
}
let role_map : Map[String, Json] = Map::from_iter(([] : Array[(String, Json)]).iter())
for a, d in self.role_trans.iter2() {
role_map.set(a, dmap_to_json(d))
}
obj([
("memories", mem_map.to_json()),
("transitions", trans_map.to_json()),
(
"episodes",
arr_json(self.episodes.map(fn(ep) { arr_json(ep.map(fn(s) { str_json(s) })) })),
),
("context", arr_json(self.context.map(fn(s) { str_json(s) }))),
("cur_episode", arr_json(self.cur_episode.map(fn(s) { str_json(s) }))),
("last_pred", arr_json(self.last_pred.map(fn(s) { str_json(s) }))),
(
"stats",
obj([
("preds", num_json(self.stats_preds.to_double())),
("hits", num_json(self.stats_hits.to_double())),
("remembers", num_json(self.stats_remembers.to_double())),
("evolutions", num_json(self.stats_evolutions.to_double())),
]),
),
("seq", num_json(self.seq.to_double())),
("explore", num_json(self.explore)),
("clock", num_json(self.clock)),
("meta_hits", arr_json(self.meta_hits.map(fn(x) { num_json(x.to_double()) }))),
("snapshot", match self.snapshot { Some(s) => memories_to_json(s); None => obj([]) }),
("snap_trans", match self.snap_trans { Some(t) => trans_map_to_json(t); None => obj([]) }),
("snap_role", match self.snap_role { Some(t) => trans_map_to_json(t); None => obj([]) }),
("trans2", trans_map_to_json(self.trans2)),
("domain_bias", dmap_to_json(self.domain_bias)),
("hebb_lr", num_json(self.hebb_lr)),
("attn_alpha", num_json(self.attn_alpha)),
("attn_beta", num_json(self.attn_beta)),
("wal_log", arr_json(self.wal_log.map(fn(s) { str_json(s) }))),
("role_trans", role_map.to_json()),
("role_index", smap_to_json(self.role_index)),
])
}
pub fn ProphecyEngine::from_json(data : Json) -> ProphecyEngine {
let e = ProphecyEngine::make()
match get_obj(data, "memories") {
Json::Object(m) => for mid, mj in m.iter2() { e.memories.set(mid, node_from_json(mj)) }
_ => ()
}
match get_obj(data, "transitions") {
Json::Object(m) => for a, aj in m.iter2() { e.transitions.set(a, json_to_dmap(aj)) }
_ => ()
}
e.episodes = parse_episodes(get_arr_j(data, "episodes"))
e.context = get_str_arr(data, "context")
e.cur_episode = get_str_arr(data, "cur_episode")
e.last_pred = get_str_arr(data, "last_pred")
match get_obj(data, "stats") {
Json::Object(m) => {
e.stats_preds = match m.get("preds") { Some(Json::Number(n, ..)) => n.to_int(); _ => 0 }
e.stats_hits = match m.get("hits") { Some(Json::Number(n, ..)) => n.to_int(); _ => 0 }
e.stats_remembers = match m.get("remembers") { Some(Json::Number(n, ..)) => n.to_int(); _ => 0 }
e.stats_evolutions = match m.get("evolutions") { Some(Json::Number(n, ..)) => n.to_int(); _ => 0 }
}
_ => ()
}
e.seq = get_num(data, "seq").to_int()
e.explore = get_num(data, "explore")
e.clock = get_num(data, "clock")
e.meta_hits = get_arr_j(data, "meta_hits").filter_map(fn(t) {
match t {
Json::Number(n, ..) => Some(n.to_int())
_ => None
}
})
match get_obj(data, "snapshot") {
Json::Object(m) =>
if m.length() > 0 {
let snap : Map[String, MemoryNode] =
Map::from_iter(([] : Array[(String, MemoryNode)]).iter())
for mid, mj in m.iter2() {
snap.set(mid, node_from_json(mj))
}
e.snapshot = Some(snap)
} else {
e.snapshot = None
}
_ => e.snapshot = None
}
let sjt = get_obj(data, "snap_trans")
match sjt {
Json::Object(m) =>
if m.length() > 0 {
e.snap_trans = Some(json_to_trans_map(sjt))
} else {
e.snap_trans = None
}
_ => e.snap_trans = None
}
let sjr = get_obj(data, "snap_role")
match sjr {
Json::Object(m) =>
if m.length() > 0 {
e.snap_role = Some(json_to_trans_map(sjr))
} else {
e.snap_role = None
}
_ => e.snap_role = None
}
match get_obj(data, "role_trans") {
Json::Object(m) => for a, aj in m.iter2() { e.role_trans.set(a, json_to_dmap(aj)) }
_ => ()
}
e.role_index = json_to_smap(get_obj(data, "role_index"))
e.trans2 = json_to_trans_map(get_obj(data, "trans2"))
e.domain_bias = json_to_dmap(get_obj(data, "domain_bias"))
e.hebb_lr = get_num(data, "hebb_lr")
e.attn_alpha = get_num(data, "attn_alpha")
e.attn_beta = get_num(data, "attn_beta")
e.wal_log = get_str_arr(data, "wal_log")
e.rebuild_role_members()
e.rebuild_tm_features() // P2-2:恢复后重建 TM 特征缓存(幂等;P1 须先于 idf——rebuild 复用 tm_toks)
e.rebuild_tm_idf() // S1:内部态不进 to_json,恢复后重建 IDF 表(幂等;P1 复用 tm_toks 并刷新 tm_count)
e.rebuild_term_index() // P4-2:恢复后重建术语首字符索引(幂等)
e
}