diff --git a/README.md b/README.md index 8693167..be064f1 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ # 潜进 (HeurAMS) - 启发式辅助记忆程序 ## 概述 -"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的多用途辅助记忆软件, 提供动态规划的优化记忆方案 +"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的开放源代码多用途辅助记忆软件, 提供动态规划的优化记忆方案 ## 关于此仓库 本仓库为 "潜进" 软件组项目的核心部分, 包含核心功能模块以及基于 Textual 框架的基础用户界面(heurams.interface)实现 除了通过用户界面进行学习外, 你也可以在 Python 中导入 `heurams` 库, 使用其中实现的状态机, 算法迭代器和数据模型构建辅助记忆功能 +本仓库在 AGPLv3 下开放源代码(详见 LICENSE 文件) ## 版本日志 - 0.0.x: 简易调度器实现与最小原型. - 0.1.x: 命令行操作的调度器. - 0.2.x: 使用 Textual 构建富文本终端用户界面; 项目可行性验证; 采用 SM-2 原始算法, 评估方式为用户自评估原型. - 0.3.x: 简单的多文件项目; 创建了记忆内容/算法数据结构; 基于 SM-2 改进算法的自动复习测评评估; 重点设计古诗文记忆理解功能; TUI 界面改进; 简单的 TTS 集成. -- 0.4.x: 使用模块管理解耦设计; 增加文档与类型标注; 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现; 支持其他调度算法模块 (SM-2, FSRS) 与谜题模块; 采用日志调试; 更新文件格式; 引入动态数据模式(宏驱动的动态内容生成), 与基于文件的策略调控; 更佳的用户数据处理; 加入模块化扩展集成; 将算法数据格式换为 json 提高性能; 采用 provider-service 抽象架构, 支持切换服务提供者; 整体兼容性改进. -- 0.5.x: 以仓库(repo)对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能; 使用具有列表 - 字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储; 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担; 实现声音形式回顾 "电台" 功能; 改进数据存储结构, 实现选择性持久化; 增强可配置性; 使用 Transitions 状态机库重新实现 reactor 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列. +- 0.4.x: 开发目标转为多用途; 使用模块管理解耦设计; 增加文档与类型标注; 采用上下文设计模式的隐式依赖注入与遵从 IoC, 注册器设计的算法与功能实现; 支持其他调度算法模块 (SM-2, SM-18M 逆向工程实现, FSRS) 与谜题模块; 采用日志调试; 更新文件格式; 引入动态数据模式(宏驱动的动态内容生成), 与基于文件的策略调控; 更佳的用户数据处理; 加入模块化扩展集成; 将算法数据格式换为 json 提高性能; 采用 provider-service 抽象架构, 支持切换服务提供者; 整体兼容性改进. +- 0.5.x: 以仓库(repo)对象作为文件系统与运行时对象间的桥梁, 提高解耦性与性能; 使用具有列表 - 字典 API 同步特性的 "Lict" 对象作为 Repo 数据的内部存储; 将粒子对象作为纯运行时对象, 数据通过引用自动同步至 Repo, 减少负担; 实现声音形式回顾 "电台" 功能; 改进数据存储结构, 实现选择性持久化; 增强可配置性; 使用 Transitions 状态机库重新实现 reactor 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列; 加入状态机快照功能(基于 pickle), 使中断的记忆流程得以恢复; 增加"整体文章引用"功能, 实现从一篇长文本中摘取内容片段记忆并在原文中高亮查看的组织操作. > 下一步? > 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务... diff --git a/data/repo/test/typedef.toml b/data/repo/test/typedef.toml index 736f11c..9a2b7d9 100644 --- a/data/repo/test/typedef.toml +++ b/data/repo/test/typedef.toml @@ -1,5 +1,3 @@ -["古文句"] - [annotation] note = "笔记" keyword_note = "关键词翻译" diff --git a/examples/simplemem.py b/examples/simplemem.py index fc8ea53..dc1f8c1 100644 --- a/examples/simplemem.py +++ b/examples/simplemem.py @@ -1,8 +1,11 @@ import heurams.kernel.repolib as repolib import heurams.kernel.particles as pt +from heurams.services.textproc import truncate from pathlib import Path - +import time repo = repolib.Repo.create_from_repodir(Path("./test_repo")) +alist = list() +print(repo.ident_index) for i in repo.ident_index: n = pt.Nucleon.create_on_nucleonic_data( 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) ) a = pt.Atom(n, e, repo.orbitic_data) - e.activate() - e.revisor(5, True) + alist.append(a) + #e.activate() + #e.revisor(5, True) print(repr(a)) # 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) diff --git a/examples/test_repo/typedef.toml b/examples/test_repo/typedef.toml index d55cc86..5c1d99e 100644 --- a/examples/test_repo/typedef.toml +++ b/examples/test_repo/typedef.toml @@ -1,5 +1,3 @@ -["古文句"] - [annotation] note = "笔记" keyword_note = "关键词翻译" diff --git a/src/heurams/interface/screens/precache.py b/src/heurams/interface/screens/precache.py index e0ec3de..c716788 100644 --- a/src/heurams/interface/screens/precache.py +++ b/src/heurams/interface/screens/precache.py @@ -12,6 +12,7 @@ import heurams.kernel.particles as pt import heurams.services.hasher as hasher from heurams.context import * +cache_dir = pathlib.Path(config_var.get()["paths"]["data"]) / "cache" / 'voice' class PrecachingScreen(Screen): """预缓存音频文件屏幕 @@ -88,7 +89,6 @@ class PrecachingScreen(Screen): """预缓存单段文本的音频""" 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_file = cache_dir / f"{hasher.get_md5(text)}.wav" if not cache_file.exists(): @@ -205,7 +205,7 @@ class PrecachingScreen(Screen): from heurams.context import config_var, rootdir, workdir shutil.rmtree( - f"{config_var.get()["paths"]["data"]}/'cache'", ignore_errors=True + cache_dir, ignore_errors=True ) self.update_status("已清空", "音频缓存已清空", 0) except Exception as e: diff --git a/src/heurams/kernel/particles/__init__.py b/src/heurams/kernel/particles/__init__.py index dffbc82..5714307 100644 --- a/src/heurams/kernel/particles/__init__.py +++ b/src/heurams/kernel/particles/__init__.py @@ -1,4 +1,4 @@ from .atom import Atom from .electron import Electron from .nucleon import Nucleon -from .orbital import Orbital +#from .orbital import Orbital diff --git a/src/heurams/kernel/reactor/phaser.py b/src/heurams/kernel/reactor/phaser.py index fd91bf6..e30885f 100644 --- a/src/heurams/kernel/reactor/phaser.py +++ b/src/heurams/kernel/reactor/phaser.py @@ -126,12 +126,15 @@ class Phaser(Machine): logger.debug("所有 Procession 已完成, 状态设置为 FINISHED") return None - @property - def state(self): - """获取当前状态值""" - current_state = self.get_model_state(self) - # 将字符串状态转换为PhaserState枚举 - for phase in PhaserState: - if phase.value == current_state: - return phase - return PhaserState.UNSURE + def __repr__(self): + from heurams.services.textproc import truncate + from tabulate import tabulate as tabu + lst = [ + { + "Type": "Phaser", + "State": self.state, + "Processions": list(map(lambda f: (f.name_), self.processions)), + "Current Procession": "None" if not self.current_procession() else self.current_procession().name_, # type: ignore + }, + ] + return str(tabu(lst, headers="keys")) + '\n' diff --git a/src/heurams/kernel/reactor/procession.py b/src/heurams/kernel/reactor/procession.py index 256a0de..bebfff7 100644 --- a/src/heurams/kernel/reactor/procession.py +++ b/src/heurams/kernel/reactor/procession.py @@ -1,6 +1,7 @@ import heurams.kernel.particles as pt from heurams.services.logger import get_logger from transitions import Machine +from tabulate import tabulate as tabu from .states import PhaserState, ProcessionState @@ -10,19 +11,19 @@ logger = get_logger(__name__) 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( "Procession.__init__: 原子数量=%d, phase=%s, name='%s'", len(atoms), phase_state.value, - name, + name_, ) - + self.current_atom: pt.Atom | None self.atoms = atoms self.queue = atoms.copy() self.current_atom = atoms[0] if atoms else None self.cursor = 0 - self.name = name + self.name_ = name_ self.phase = phase_state states = [ @@ -61,9 +62,10 @@ class Procession(Machine): logger.debug("Procession 进入 FINISHED 状态") def forward(self, step=1): + """将记忆原子指针向前移动并依情况更新原子(返回 1)或完成队列(返回 0) + """ logger.debug("Procession.forward: step=%d, 当前 cursor=%d", step, self.cursor) self.cursor += step - if self.cursor >= len(self.queue): if self.state != ProcessionState.FINISHED.value: self.finish() # 触发状态转换 @@ -78,10 +80,11 @@ class Procession(Machine): self.current_atom.ident if self.current_atom else "None", ) return 1 # 成功 - return 0 def append(self, atom=None): + """追加(回忆失败的)原子(默认为当前原子)到队列末端 + """ if atom is None: atom = self.current_atom 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) return empty - @property - def state(self): - """获取当前状态值""" - return self.get_model_state(self) - - @state.setter - def state(self, value): - """设置状态值""" - if value == ProcessionState.RUNNING.value: - self.restart() - elif value == ProcessionState.FINISHED.value: - self.finish() + def __repr__(self): + from heurams.services.textproc import truncate + dic = [ + { + "Type": "Procession", + "Name": self.name_, + "State": self.state, + "Progress": f"{self.cursor + 1} / {len(self.queue)}", + "Queue": list(map(lambda f: truncate(f.ident), self.queue)), + "Current Atom": self.current_atom.ident, # type: ignore + } + ] + return str(tabu(dic, headers="keys")) + '\n' diff --git a/src/heurams/kernel/reactor1/__init__.py b/src/heurams/kernel/reactor1/__init__.py deleted file mode 100644 index dc87681..0000000 --- a/src/heurams/kernel/reactor1/__init__.py +++ /dev/null @@ -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("反应堆模块已加载") diff --git a/src/heurams/kernel/reactor1/fission.py b/src/heurams/kernel/reactor1/fission.py deleted file mode 100644 index 01bfe6c..0000000 --- a/src/heurams/kernel/reactor1/fission.py +++ /dev/null @@ -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 diff --git a/src/heurams/kernel/reactor1/phaser.py b/src/heurams/kernel/reactor1/phaser.py deleted file mode 100644 index 250b450..0000000 --- a/src/heurams/kernel/reactor1/phaser.py +++ /dev/null @@ -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 diff --git a/src/heurams/kernel/reactor1/procession.py b/src/heurams/kernel/reactor1/procession.py deleted file mode 100644 index 39811b6..0000000 --- a/src/heurams/kernel/reactor1/procession.py +++ /dev/null @@ -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 diff --git a/src/heurams/kernel/reactor1/states.py b/src/heurams/kernel/reactor1/states.py deleted file mode 100644 index 3e48a96..0000000 --- a/src/heurams/kernel/reactor1/states.py +++ /dev/null @@ -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("状态枚举定义已加载") diff --git a/src/heurams/services/textproc.py b/src/heurams/services/textproc.py new file mode 100644 index 0000000..729cc43 --- /dev/null +++ b/src/heurams/services/textproc.py @@ -0,0 +1,4 @@ +def truncate(text): + if len(text) <= 3: + return text + return text[:3] + ">" \ No newline at end of file diff --git a/src/heurams/utils/lict.py b/src/heurams/utils/lict.py index 446090e..6ad5e48 100644 --- a/src/heurams/utils/lict.py +++ b/src/heurams/utils/lict.py @@ -22,7 +22,7 @@ class Lict(UserList): # TODO: 优化同步(惰性同步), 当前性能为 O(n) self, initlist: list | None = None, initdict: dict | None = None, - forced_order=True, + forced_order=False, ): self.dicted_data = {} if initdict != None: