feat(reactor): 状态机进一步改进

This commit is contained in:
2026-01-04 00:28:44 +08:00
parent 94aaef386b
commit 55c656e8f9
15 changed files with 83 additions and 245 deletions

View File

@@ -1,19 +1,20 @@
# 潜进 (HeurAMS) - 启发式辅助记忆程序 # 潜进 (HeurAMS) - 启发式辅助记忆程序
## 概述 ## 概述
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的多用途辅助记忆软件, 提供动态规划的优化记忆方案 "潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的开放源代码多用途辅助记忆软件, 提供动态规划的优化记忆方案
## 关于此仓库 ## 关于此仓库
本仓库为 "潜进" 软件组项目的核心部分, 包含核心功能模块以及基于 Textual 框架的基础用户界面(heurams.interface)实现 本仓库为 "潜进" 软件组项目的核心部分, 包含核心功能模块以及基于 Textual 框架的基础用户界面(heurams.interface)实现
除了通过用户界面进行学习外, 你也可以在 Python 中导入 `heurams` 库, 使用其中实现的状态机, 算法迭代器和数据模型构建辅助记忆功能 除了通过用户界面进行学习外, 你也可以在 Python 中导入 `heurams` 库, 使用其中实现的状态机, 算法迭代器和数据模型构建辅助记忆功能
本仓库在 AGPLv3 下开放源代码(详见 LICENSE 文件)
## 版本日志 ## 版本日志
- 0.0.x: 简易调度器实现与最小原型. - 0.0.x: 简易调度器实现与最小原型.
- 0.1.x: 命令行操作的调度器. - 0.1.x: 命令行操作的调度器.
- 0.2.x: 使用 Textual 构建富文本终端用户界面; 项目可行性验证; 采用 SM-2 原始算法, 评估方式为用户自评估原型. - 0.2.x: 使用 Textual 构建富文本终端用户界面; 项目可行性验证; 采用 SM-2 原始算法, 评估方式为用户自评估原型.
- 0.3.x: 简单的多文件项目; 创建了记忆内容/算法数据结构; 基于 SM-2 改进算法的自动复习测评评估; 重点设计古诗文记忆理解功能; TUI 界面改进; 简单的 TTS 集成. - 0.3.x: 简单的多文件项目; 创建了记忆内容/算法数据结构; 基于 SM-2 改进算法的自动复习测评评估; 重点设计古诗文记忆理解功能; TUI 界面改进; 简单的 TTS 集成.
- 0.4.x: 使用模块管理解耦设计; 增加文档与类型标注; 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现; 支持其他调度算法模块 (SM-2, FSRS) 与谜题模块; 采用日志调试; 更新文件格式; 引入动态数据模式(宏驱动的动态内容生成), 与基于文件的策略调控; 更佳的用户数据处理; 加入模块化扩展集成; 将算法数据格式换为 json 提高性能; 采用 provider-service 抽象架构, 支持切换服务提供者; 整体兼容性改进. - 0.4.x: 开发目标转为多用途; 使用模块管理解耦设计; 增加文档与类型标注; 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现; 支持其他调度算法模块 (SM-2, SM-18M 逆向工程实现, FSRS) 与谜题模块; 采用日志调试; 更新文件格式; 引入动态数据模式(宏驱动的动态内容生成), 与基于文件的策略调控; 更佳的用户数据处理; 加入模块化扩展集成; 将算法数据格式换为 json 提高性能; 采用 provider-service 抽象架构, 支持切换服务提供者; 整体兼容性改进.
- 0.5.x: 以仓库(repo)对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能; 使用具有列表 - 字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储; 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担; 实现声音形式回顾 "电台" 功能; 改进数据存储结构, 实现选择性持久化; 增强可配置性; 使用 Transitions 状态机库重新实现 reactor 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列. - 0.5.x: 以仓库(repo)对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能; 使用具有列表 - 字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储; 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担; 实现声音形式回顾 "电台" 功能; 改进数据存储结构, 实现选择性持久化; 增强可配置性; 使用 Transitions 状态机库重新实现 reactor 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列; 加入状态机快照功能(基于 pickle), 使中断的记忆流程得以恢复; 增加"整体文章引用"功能, 实现从一篇长文本中摘取内容片段记忆并在原文中高亮查看的组织操作.
> 下一步? > 下一步?
> 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务... > 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务...

View File

@@ -1,5 +1,3 @@
["古文句"]
[annotation] [annotation]
note = "笔记" note = "笔记"
keyword_note = "关键词翻译" keyword_note = "关键词翻译"

View File

@@ -1,8 +1,11 @@
import heurams.kernel.repolib as repolib import heurams.kernel.repolib as repolib
import heurams.kernel.particles as pt import heurams.kernel.particles as pt
from heurams.services.textproc import truncate
from pathlib import Path from pathlib import Path
import time
repo = repolib.Repo.create_from_repodir(Path("./test_repo")) repo = repolib.Repo.create_from_repodir(Path("./test_repo"))
alist = list()
print(repo.ident_index)
for i in repo.ident_index: for i in repo.ident_index:
n = pt.Nucleon.create_on_nucleonic_data( n = pt.Nucleon.create_on_nucleonic_data(
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i) nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
@@ -11,8 +14,38 @@ for i in repo.ident_index:
electronic_data=repo.electronic_data_lict.get_itemic_unit(i) electronic_data=repo.electronic_data_lict.get_itemic_unit(i)
) )
a = pt.Atom(n, e, repo.orbitic_data) a = pt.Atom(n, e, repo.orbitic_data)
e.activate() alist.append(a)
e.revisor(5, True) #e.activate()
#e.revisor(5, True)
print(repr(a)) print(repr(a))
# print(repr(e)) # print(repr(e))
# print(repo) print(repo)
import heurams.kernel.reactor as rt
ph: rt.Phaser = rt.Phaser(alist)
print(ph)
pr: rt.Procession = ph.current_procession() # type: ignore
print(pr)
pr.forward()
print(pr)
pr.forward() # 如果过界了?
print(pr) # 静默设置状态 无报错
pr.forward()
print(pr)
pr = ph.current_procession() # type: ignore # 下一个队列
print(pr)
pr.forward()
print(pr)
pr.append() # 如果记忆失败了?
print(pr)
pr.forward()
pr.append() # 如果记忆失败了?
pr.append() # 如果记忆失败了?
pr.append() # 如果记忆失败了?
pr.append() # 如果记忆失败了?
pr.append() # 如果记忆失败了?
# 重复项目只会占据一个车尾
print(pr)
pr.forward()
print(pr)
pr = ph.current_procession() # type: ignore
print(pr)

View File

@@ -1,5 +1,3 @@
["古文句"]
[annotation] [annotation]
note = "笔记" note = "笔记"
keyword_note = "关键词翻译" keyword_note = "关键词翻译"

View File

@@ -12,6 +12,7 @@ import heurams.kernel.particles as pt
import heurams.services.hasher as hasher import heurams.services.hasher as hasher
from heurams.context import * from heurams.context import *
cache_dir = pathlib.Path(config_var.get()["paths"]["data"]) / "cache" / 'voice'
class PrecachingScreen(Screen): class PrecachingScreen(Screen):
"""预缓存音频文件屏幕 """预缓存音频文件屏幕
@@ -88,7 +89,6 @@ class PrecachingScreen(Screen):
"""预缓存单段文本的音频""" """预缓存单段文本的音频"""
from heurams.context import config_var, rootdir, workdir from heurams.context import config_var, rootdir, workdir
cache_dir = pathlib.Path(config_var.get()["paths"]["data"]) / "cache"
cache_dir.mkdir(parents=True, exist_ok=True) cache_dir.mkdir(parents=True, exist_ok=True)
cache_file = cache_dir / f"{hasher.get_md5(text)}.wav" cache_file = cache_dir / f"{hasher.get_md5(text)}.wav"
if not cache_file.exists(): if not cache_file.exists():
@@ -205,7 +205,7 @@ class PrecachingScreen(Screen):
from heurams.context import config_var, rootdir, workdir from heurams.context import config_var, rootdir, workdir
shutil.rmtree( shutil.rmtree(
f"{config_var.get()["paths"]["data"]}/'cache'", ignore_errors=True cache_dir, ignore_errors=True
) )
self.update_status("已清空", "音频缓存已清空", 0) self.update_status("已清空", "音频缓存已清空", 0)
except Exception as e: except Exception as e:

View File

@@ -1,4 +1,4 @@
from .atom import Atom from .atom import Atom
from .electron import Electron from .electron import Electron
from .nucleon import Nucleon from .nucleon import Nucleon
from .orbital import Orbital #from .orbital import Orbital

View File

@@ -126,12 +126,15 @@ class Phaser(Machine):
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED") logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
return None return None
@property def __repr__(self):
def state(self): from heurams.services.textproc import truncate
"""获取当前状态值""" from tabulate import tabulate as tabu
current_state = self.get_model_state(self) lst = [
# 将字符串状态转换为PhaserState枚举 {
for phase in PhaserState: "Type": "Phaser",
if phase.value == current_state: "State": self.state,
return phase "Processions": list(map(lambda f: (f.name_), self.processions)),
return PhaserState.UNSURE "Current Procession": "None" if not self.current_procession() else self.current_procession().name_, # type: ignore
},
]
return str(tabu(lst, headers="keys")) + '\n'

View File

@@ -1,6 +1,7 @@
import heurams.kernel.particles as pt import heurams.kernel.particles as pt
from heurams.services.logger import get_logger from heurams.services.logger import get_logger
from transitions import Machine from transitions import Machine
from tabulate import tabulate as tabu
from .states import PhaserState, ProcessionState from .states import PhaserState, ProcessionState
@@ -10,19 +11,19 @@ logger = get_logger(__name__)
class Procession(Machine): class Procession(Machine):
"""队列: 标识单次记忆流程""" """队列: 标识单次记忆流程"""
def __init__(self, atoms: list, phase_state: PhaserState, name: str = ""): def __init__(self, atoms: list, phase_state: PhaserState, name_: str = ""):
logger.debug( logger.debug(
"Procession.__init__: 原子数量=%d, phase=%s, name='%s'", "Procession.__init__: 原子数量=%d, phase=%s, name='%s'",
len(atoms), len(atoms),
phase_state.value, phase_state.value,
name, name_,
) )
self.current_atom: pt.Atom | None
self.atoms = atoms self.atoms = atoms
self.queue = atoms.copy() self.queue = atoms.copy()
self.current_atom = atoms[0] if atoms else None self.current_atom = atoms[0] if atoms else None
self.cursor = 0 self.cursor = 0
self.name = name self.name_ = name_
self.phase = phase_state self.phase = phase_state
states = [ states = [
@@ -61,9 +62,10 @@ class Procession(Machine):
logger.debug("Procession 进入 FINISHED 状态") logger.debug("Procession 进入 FINISHED 状态")
def forward(self, step=1): def forward(self, step=1):
"""将记忆原子指针向前移动并依情况更新原子(返回 1)或完成队列(返回 0)
"""
logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor) logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor)
self.cursor += step self.cursor += step
if self.cursor >= len(self.queue): if self.cursor >= len(self.queue):
if self.state != ProcessionState.FINISHED.value: if self.state != ProcessionState.FINISHED.value:
self.finish() # 触发状态转换 self.finish() # 触发状态转换
@@ -78,10 +80,11 @@ class Procession(Machine):
self.current_atom.ident if self.current_atom else "None", self.current_atom.ident if self.current_atom else "None",
) )
return 1 # 成功 return 1 # 成功
return 0 return 0
def append(self, atom=None): def append(self, atom=None):
"""追加(回忆失败的)原子(默认为当前原子)到队列末端
"""
if atom is None: if atom is None:
atom = self.current_atom atom = self.current_atom
logger.debug("Procession.append: atom=%s", atom.ident if atom else "None") logger.debug("Procession.append: atom=%s", atom.ident if atom else "None")
@@ -113,15 +116,16 @@ class Procession(Machine):
logger.debug("Procession.is_empty: %s", empty) logger.debug("Procession.is_empty: %s", empty)
return empty return empty
@property def __repr__(self):
def state(self): from heurams.services.textproc import truncate
"""获取当前状态值""" dic = [
return self.get_model_state(self) {
"Type": "Procession",
@state.setter "Name": self.name_,
def state(self, value): "State": self.state,
"""设置状态值""" "Progress": f"{self.cursor + 1} / {len(self.queue)}",
if value == ProcessionState.RUNNING.value: "Queue": list(map(lambda f: truncate(f.ident), self.queue)),
self.restart() "Current Atom": self.current_atom.ident, # type: ignore
elif value == ProcessionState.FINISHED.value: }
self.finish() ]
return str(tabu(dic, headers="keys")) + '\n'

View File

@@ -1,12 +0,0 @@
from heurams.services.logger import get_logger
from .fission import Fission
from .phaser import Phaser
from .procession import Procession
from .states import PhaserState, ProcessionState
logger = get_logger(__name__)
__all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"]
logger.debug("反应堆模块已加载")

View File

@@ -1,45 +0,0 @@
import random
import heurams.kernel.evaluators as puz
import heurams.kernel.particles as pt
from heurams.services.logger import get_logger
from .states import PhaserState
class Fission:
"""裂变器: 单原子调度展开器"""
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
self.logger = get_logger(__name__)
self.atom = atom
# print(f"{phase.value}")
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
self.orbital_puzzles = atom.registry["orbital"]["puzzles"]
# print(self.orbital_schedule)
self.puzzles = list()
for item, possibility in self.orbital_schedule: # type: ignore
print(f"ad:{item}")
self.logger.debug(f"开始处理 orbital 项: {item}")
if not isinstance(possibility, float):
possibility = float(possibility)
while possibility > 1:
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
possibility -= 1
if random.random() <= possibility:
self.puzzles.append(
{
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
"alia": item,
}
)
print(f"ok:{item}")
self.logger.debug(f"orbital 项处理完成: {item}")
def generate(self):
yield from self.puzzles

View File

@@ -1,51 +0,0 @@
# 移相器类定义
import heurams.kernel.particles as pt
from heurams.services.logger import get_logger
from .procession import Procession
from .states import PhaserState, ProcessionState
logger = get_logger(__name__)
class Phaser:
"""全局调度阶段管理器"""
def __init__(self, atoms: list[pt.Atom]) -> None:
logger.debug("Phaser.__init__: 原子数量=%d", len(atoms))
new_atoms = list()
old_atoms = list()
self.state = PhaserState.UNSURE
for i in atoms:
if not i.registry["electron"].is_activated():
new_atoms.append(i)
else:
old_atoms.append(i)
logger.debug("新原子数量=%d, 旧原子数量=%d", len(new_atoms), len(old_atoms))
self.processions = list()
if len(old_atoms):
self.processions.append(
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
)
logger.debug("创建初始复习 Procession")
if len(new_atoms):
self.processions.append(
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
)
logger.debug("创建新记忆 Procession")
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
logger.debug("创建总体复习 Procession")
logger.debug("Phaser 初始化完成, processions 数量=%d", len(self.processions))
def current_procession(self):
logger.debug("Phaser.current_procession 被调用")
for i in self.processions:
i: Procession
if not i.state == ProcessionState.FINISHED:
self.state = i.phase
logger.debug("找到未完成的 Procession: phase=%s", i.phase)
return i
self.state = PhaserState.FINISHED
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
return 0

View File

@@ -1,74 +0,0 @@
import heurams.kernel.particles as pt
from heurams.services.logger import get_logger
from .states import PhaserState, ProcessionState
logger = get_logger(__name__)
class Procession:
"""队列: 标识单次记忆流程"""
def __init__(self, atoms: list, phase: PhaserState, name: str = ""):
logger.debug(
"Procession.__init__: 原子数量=%d, phase=%s, name='%s'",
len(atoms),
phase.value,
name,
)
self.atoms = atoms
self.queue = atoms.copy()
self.current_atom = atoms[0]
self.cursor = 0
self.name = name
self.phase = phase
self.state: ProcessionState = ProcessionState.RUNNING
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.queue))
def forward(self, step=1):
logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor)
self.cursor += step
if self.cursor == len(self.queue):
self.state = ProcessionState.FINISHED
logger.debug("Procession 已完成")
else:
self.state = ProcessionState.RUNNING
try:
logger.debug("cursor 更新为: %d", self.cursor)
self.current_atom = self.queue[self.cursor]
logger.debug("当前原子更新为: %s", self.current_atom.ident)
return 1 # 成功
except IndexError as e:
logger.debug("IndexError: %s", e)
self.state = ProcessionState.FINISHED
logger.debug("Procession 因索引错误而完成")
return 0
def append(self, atom=None):
if atom == None:
atom = self.current_atom
logger.debug("Procession.append: atom=%s", atom.ident if atom else "None")
if self.queue[len(self.queue) - 1] != atom or len(self) <= 1:
self.queue.append(atom)
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.queue))
else:
logger.debug("原子未追加(重复或队列长度<=1)")
def __len__(self):
length = len(self.queue) - self.cursor
logger.debug("Procession.__len__: 剩余长度=%d", length)
return length
def process(self):
logger.debug("Procession.process: cursor=%d", self.cursor)
return self.cursor
def total_length(self):
total = len(self.queue)
logger.debug("Procession.total_length: %d", total)
return total
def is_empty(self):
empty = len(self.queue)
logger.debug("Procession.is_empty: %d", empty)
return empty

View File

@@ -1,21 +0,0 @@
from enum import Enum, auto
from heurams.services.logger import get_logger
logger = get_logger(__name__)
class PhaserState(Enum):
UNSURE = "unsure"
QUICK_REVIEW = "quick_review"
RECOGNITION = "recognition"
FINAL_REVIEW = "final_review"
FINISHED = "finished"
class ProcessionState(Enum):
RUNNING = auto()
FINISHED = auto()
logger.debug("状态枚举定义已加载")

View File

@@ -0,0 +1,4 @@
def truncate(text):
if len(text) <= 3:
return text
return text[:3] + ">"

View File

@@ -22,7 +22,7 @@ class Lict(UserList): # TODO: 优化同步(惰性同步), 当前性能为 O(n)
self, self,
initlist: list | None = None, initlist: list | None = None,
initdict: dict | None = None, initdict: dict | None = None,
forced_order=True, forced_order=False,
): ):
self.dicted_data = {} self.dicted_data = {}
if initdict != None: if initdict != None: