From 7f364500799992551c61b1503859b51c3118bff8 Mon Sep 17 00:00:00 2001 From: Wang Zhiyu Date: Sat, 3 Jan 2026 13:08:08 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=B9=E8=BF=9B=E7=8A=B6=E6=80=81?= =?UTF-8?q?=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++--- data/repo/test/typedef.toml | 2 +- examples/jiebatest.py | 6 +- examples/repo.ipynb | 61 ++++++++++++----- examples/simplemem.py | 15 +++-- src/heurams/context.py | 19 ++++-- src/heurams/interface/__init__.py | 1 + src/heurams/interface/screens/dashboard.py | 40 ++++++----- src/heurams/interface/screens/precache.py | 10 ++- src/heurams/interface/screens/preparation.py | 24 +++++-- src/heurams/interface/screens/repocreator.py | 3 +- src/heurams/kernel/algorithms/sm15m.py | 17 +++-- src/heurams/kernel/particles/atom.py | 1 + src/heurams/kernel/particles/electron.py | 1 + src/heurams/kernel/particles/nucleon.py | 3 +- src/heurams/kernel/particles/orbital.py | 27 ++++---- src/heurams/kernel/reactor/__init__.py | 2 +- src/heurams/kernel/reactor/fission.py | 30 +++++---- src/heurams/kernel/reactor/phaser.py | 70 +++++++++++++------- src/heurams/kernel/reactor/procession.py | 57 ++++++++++------ src/heurams/kernel/reactor/states.py | 2 +- src/heurams/kernel/repolib/repo.py | 11 +-- src/heurams/utils/evalizor.py | 2 +- tests/interface/test_synctool.py | 11 +-- 24 files changed, 275 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index 1b3ef80..8693167 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,16 @@ "潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的多用途辅助记忆软件, 提供动态规划的优化记忆方案 ## 关于此仓库 -"潜进" 软件组项目包含多个子项目 -此仓库包含了 "潜进" 项目的核心和基于 Textual 的基本用户界面的实现 +本仓库为 "潜进" 软件组项目的核心部分, 包含核心功能模块以及基于 Textual 框架的基础用户界面(heurams.interface)实现 +除了通过用户界面进行学习外, 你也可以在 Python 中导入 `heurams` 库, 使用其中实现的状态机, 算法迭代器和数据模型构建辅助记忆功能 -## 开发进程 +## 版本日志 - 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.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 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列. > 下一步? > 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务... @@ -103,7 +104,7 @@ verify_ssl = true # SSL 证书验证 ## 项目结构 -### 架构图 +### 架构图(待更新 0.5.0) 以下 Mermaid 图展示了 HeurAMS 的主要组件及其关系: @@ -164,7 +165,7 @@ graph TB Algorithms --> Files ``` -### 目录结构 +### 目录结构(待更新 0.5.0) ``` src/heurams/ ├── __init__.py # 包入口点 diff --git a/data/repo/test/typedef.toml b/data/repo/test/typedef.toml index d55cc86..736f11c 100644 --- a/data/repo/test/typedef.toml +++ b/data/repo/test/typedef.toml @@ -12,7 +12,7 @@ tts_text = "文本转语音文本" delimiter = "/" tts_text = "eval:payload['content'].replace('/', '')" -["puzzles"] # 谜题定义 +["common.puzzles"] # 谜题定义, 也可以单独定义到 payload, common 不会对 payload 已有内容进行覆盖, 参见 nucleon.py 第16行 # 我们称 "Recognition" 为 recognition 谜题的 alia "Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:payload['content']", secondary = ["eval:payload['keyword_note']", "eval:payload['note']"], top_dim = ["eval:payload['translation']"] } "SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:payload['content']", primary = "eval:payload['content']", mapping = "eval:payload['keyword_note']", jammer = "eval:list(payload['keyword_note'].values())", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " } diff --git a/examples/jiebatest.py b/examples/jiebatest.py index 21c3e48..df2a89b 100644 --- a/examples/jiebatest.py +++ b/examples/jiebatest.py @@ -1,9 +1,9 @@ # encoding=utf-8 import jieba -#jieba.enable_paddle()# 启动paddle模式。 0.40版之后开始支持,早期版本不支持 -strs=["我来到北京清华大学","乒乓球拍卖完了","中国科学技术大学"] -#for str in strs: +# jieba.enable_paddle()# 启动paddle模式。 0.40版之后开始支持,早期版本不支持 +strs = ["我来到北京清华大学", "乒乓球拍卖完了", "中国科学技术大学"] +# for str in strs: # seg_list = jieba.cut(str,use_paddle=True) # 使用paddle模式 # print("Paddle Mode: " + '/'.join(list(seg_list))) diff --git a/examples/repo.ipynb b/examples/repo.ipynb index 5feb1ae..c6bbeed 100644 --- a/examples/repo.ipynb +++ b/examples/repo.ipynb @@ -101,9 +101,11 @@ } ], "source": [ - "import heurams.kernel.repolib as repolib # 这是 RepoLib 子模块, 用于管理和结构化 repo(中文含义: 仓库) 数据结构与本地文件间的联系\n", - "import heurams.kernel.particles as pt # 这是 Particles(中文含义: 粒子) 子模块, 用于运行时的记忆管理操作\n", - "from pathlib import Path # 这是 Python 的 Pathlib 模块, 用于表示文件路径, 在整个项目中, 都使用此模块表示路径" + "import heurams.kernel.repolib as repolib # 这是 RepoLib 子模块, 用于管理和结构化 repo(中文含义: 仓库) 数据结构与本地文件间的联系\n", + "import heurams.kernel.particles as pt # 这是 Particles(中文含义: 粒子) 子模块, 用于运行时的记忆管理操作\n", + "from pathlib import (\n", + " Path,\n", + ") # 这是 Python 的 Pathlib 模块, 用于表示文件路径, 在整个项目中, 都使用此模块表示路径" ] }, { @@ -231,6 +233,7 @@ "source": [ "test_repo_dic = test_repo.export_to_single_dict()\n", "from pprint import pprint\n", + "\n", "pprint(test_repo_dic)" ] }, @@ -287,7 +290,10 @@ } ], "source": [ - "test_repo.persist_to_repodir(save_list=[\"schedule\", \"payload\", \"manifest\", \"typedef\", \"algodata\"], source=Path(\"test_new_repo\"))\n", + "test_repo.persist_to_repodir(\n", + " save_list=[\"schedule\", \"payload\", \"manifest\", \"typedef\", \"algodata\"],\n", + " source=Path(\"test_new_repo\"),\n", + ")\n", "!tree" ] }, @@ -336,11 +342,12 @@ ], "source": [ "from heurams.utils.lict import Lict\n", - "lct = Lict() # 空的\n", - "lct = Lict(initlist=[(\"name\", \"tom\"), (\"age\", 12), (\"enemy\", \"jerry\")]) # 基于列表\n", + "\n", + "lct = Lict() # 空的\n", + "lct = Lict(initlist=[(\"name\", \"tom\"), (\"age\", 12), (\"enemy\", \"jerry\")]) # 基于列表\n", "print(lct)\n", - "lct = Lict(initdict={\"name\": \"tom\", \"age\": 12, \"enemy\": \"jerry\"}) # 基于字典\n", - "print(lct)\n" + "lct = Lict(initdict={\"name\": \"tom\", \"age\": 12, \"enemy\": \"jerry\"}) # 基于字典\n", + "print(lct)" ] }, { @@ -404,7 +411,7 @@ "\n", "# 错误的方式\n", "lct.dicted_data[\"type\"] = \"cat\"\n", - "print(lct) # 将不会同步修改\n", + "print(lct) # 将不会同步修改\n", "\n", "# 不推荐, 但可用的方式\n", "lct.dicted_data[\"type\"] = \"cat\"\n", @@ -412,7 +419,7 @@ "print(lct)\n", "\n", "# 推荐方式\n", - "lct['is_human'] = False\n", + "lct[\"is_human\"] = False\n", "print(lct)" ] }, @@ -446,7 +453,7 @@ "# 由于 jupyter 的环境处理, 请不要重复运行此单元格, 如果想再看一遍, 请重启 jupyter 后再全部运行\n", "\n", "# 唯一推荐方式\n", - "lct.append(('enemy_2', 'spike'))\n", + "lct.append((\"enemy_2\", \"spike\"))\n", "print(lct.dicted_data)" ] }, @@ -507,7 +514,16 @@ } ], "source": [ - "lct = Lict(initdict={'age': 12, 'enemy': 'jerry', 'is_human': False, 'name': 'tom', 'type': 'cat', 'enemy_2': 'spike'})\n", + "lct = Lict(\n", + " initdict={\n", + " \"age\": 12,\n", + " \"enemy\": \"jerry\",\n", + " \"is_human\": False,\n", + " \"name\": \"tom\",\n", + " \"type\": \"cat\",\n", + " \"enemy_2\": \"spike\",\n", + " }\n", + ")\n", "print(lct)\n", "print(lct.dicted_data)\n", "print(\"------\")\n", @@ -517,7 +533,16 @@ "while len(lct) > 0:\n", " print(lct.pop())\n", " print(lct)\n", - "lct = Lict(initdict={'age': 12, 'enemy': 'jerry', 'is_human': False, 'name': 'tom', 'type': 'cat', 'enemy_2': 'spike'})\n", + "lct = Lict(\n", + " initdict={\n", + " \"age\": 12,\n", + " \"enemy\": \"jerry\",\n", + " \"is_human\": False,\n", + " \"name\": \"tom\",\n", + " \"type\": \"cat\",\n", + " \"enemy_2\": \"spike\",\n", + " }\n", + ")\n", "..." ] }, @@ -654,10 +679,14 @@ } ], "source": [ - "repo = repolib.Repo.create_from_repodir(Path('./test_repo'))\n", + "repo = repolib.Repo.create_from_repodir(Path(\"./test_repo\"))\n", "for i in repo.ident_index:\n", - " n = pt.Nucleon.create_on_nucleonic_data(nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i))\n", - " e = pt.Electron.create_on_electonic_data(electronic_data=repo.electronic_data_lict.get_itemic_unit(i))\n", + " n = pt.Nucleon.create_on_nucleonic_data(\n", + " nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)\n", + " )\n", + " e = pt.Electron.create_on_electonic_data(\n", + " electronic_data=repo.electronic_data_lict.get_itemic_unit(i)\n", + " )\n", " e.activate()\n", " e.revisor(5, True)\n", " print(repr(n))\n", diff --git a/examples/simplemem.py b/examples/simplemem.py index 01b84d0..fc8ea53 100644 --- a/examples/simplemem.py +++ b/examples/simplemem.py @@ -1,13 +1,18 @@ import heurams.kernel.repolib as repolib import heurams.kernel.particles as pt from pathlib import Path -repo = repolib.Repo.create_from_repodir(Path('./test_repo')) + +repo = repolib.Repo.create_from_repodir(Path("./test_repo")) for i in repo.ident_index: - n = pt.Nucleon.create_on_nucleonic_data(nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)) - e = pt.Electron.create_on_electonic_data(electronic_data=repo.electronic_data_lict.get_itemic_unit(i)) + n = pt.Nucleon.create_on_nucleonic_data( + nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i) + ) + e = pt.Electron.create_on_electonic_data( + electronic_data=repo.electronic_data_lict.get_itemic_unit(i) + ) a = pt.Atom(n, e, repo.orbitic_data) e.activate() e.revisor(5, True) print(repr(a)) - #print(repr(e)) -#print(repo) \ No newline at end of file + # print(repr(e)) +# print(repo) diff --git a/src/heurams/context.py b/src/heurams/context.py index 1c90268..439640a 100644 --- a/src/heurams/context.py +++ b/src/heurams/context.py @@ -26,23 +26,30 @@ if pathlib.Path(workdir / "data" / "config" / "config_dev.toml").exists(): print("使用开发设置") logger.debug("使用开发设置") config_var: ContextVar[ConfigFile] = ContextVar( - "config_var", default=ConfigFile(workdir / "data" / "config" / "config_dev.toml") + "config_var", + default=ConfigFile(workdir / "data" / "config" / "config_dev.toml"), ) else: try: config_var: ContextVar[ConfigFile] = ContextVar( - "config_var", default=ConfigFile(workdir / "data" / "config" / "config.toml") + "config_var", + default=ConfigFile(workdir / "data" / "config" / "config.toml"), ) # 配置文件 except Exception as e: input("按下回车以创建新的配置文件, 或按下 Ctrl + C 以终止程序 ") - (workdir / "data" / 'config').mkdir(parents=True, exist_ok=True) - (workdir / "data" / 'config' / 'config').unlink(missing_ok=True) - shutil.copy((rootdir / 'default' / 'config' / 'config.toml'), workdir / "data" / "config" / "config.toml") + (workdir / "data" / "config").mkdir(parents=True, exist_ok=True) + (workdir / "data" / "config" / "config").unlink(missing_ok=True) + shutil.copy( + (rootdir / "default" / "config" / "config.toml"), + workdir / "data" / "config" / "config.toml", + ) finally: config_var: ContextVar[ConfigFile] = ContextVar( - "config_var", default=ConfigFile(workdir / "data" / "config" / "config.toml") + "config_var", + default=ConfigFile(workdir / "data" / "config" / "config.toml"), ) # 配置文件 + class ConfigContext: """ 功能完备的上下文管理器 diff --git a/src/heurams/interface/__init__.py b/src/heurams/interface/__init__.py index c1bfccb..f825008 100644 --- a/src/heurams/interface/__init__.py +++ b/src/heurams/interface/__init__.py @@ -12,6 +12,7 @@ from .screens.synctool import SyncScreen logger = get_logger(__name__) + def environment_check(): from pathlib import Path diff --git a/src/heurams/interface/screens/dashboard.py b/src/heurams/interface/screens/dashboard.py index 4008e0d..3fb9aee 100644 --- a/src/heurams/interface/screens/dashboard.py +++ b/src/heurams/interface/screens/dashboard.py @@ -5,8 +5,7 @@ import pathlib from textual.app import ComposeResult from textual.containers import ScrollableContainer from textual.screen import Screen -from textual.widgets import (Button, Footer, Header, Label, ListItem, ListView, - Static) +from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static import heurams.services.timer as timer import heurams.services.version as version @@ -48,29 +47,33 @@ class DashboardScreen(Screen): Label(f"使用算法: {config_var.get()['algorithm']['default']}"), Label("选择待学习或待修改的仓库:", classes="title-label"), ListView(id="repo-list", classes="repo-list-view"), - Label( - f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} ' - ), + Label(f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} '), ) yield Footer() def _load_data(self): - self.repo_dirs = Repo.probe_vaild_repos_in_dir(Path(config_var.get()['paths']['data']) / 'repo') + self.repo_dirs = Repo.probe_vaild_repos_in_dir( + Path(config_var.get()["paths"]["data"]) / "repo" + ) for repo_dir in self.repo_dirs: repo = Repo.create_from_repodir(repo_dir) self._analyse_repo(repo) def _analyse_repo(self, repo: Repo): - dirname = repo.source.name # type: ignore + dirname = repo.source.name # type: ignore title = repo.manifest["title"] is_due = 0 unit_sum = len(repo) activated_sum = 0 - nextdate = 0x3f3f3f3f - is_unfinished = (unit_sum > activated_sum) + nextdate = 0x3F3F3F3F + is_unfinished = unit_sum > activated_sum for i in repo.ident_index: - nucleon = pt.Nucleon.create_on_nucleonic_data(nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)) - electron = pt.Electron.create_on_electonic_data(electronic_data=repo.electronic_data_lict.get_itemic_unit(i)) + nucleon = pt.Nucleon.create_on_nucleonic_data( + nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i) + ) + electron = pt.Electron.create_on_electonic_data( + electronic_data=repo.electronic_data_lict.get_itemic_unit(i) + ) if electron.is_activated(): activated_sum += 1 if electron.is_due(): @@ -102,10 +105,10 @@ class DashboardScreen(Screen): # 按下次复习时间排序 repodirs = sorted( self.repo_dirs, - key=lambda f: self.repostat[f.name]['nextdate'], + key=lambda f: self.repostat[f.name]["nextdate"], reverse=True, ) - repotitles = map(lambda f: self.repostat[f.name]['title'], repodirs) + repotitles = map(lambda f: self.repostat[f.name]["title"], repodirs) # 填充列表 if not repodirs: repo_list_widget.append( @@ -120,11 +123,11 @@ class DashboardScreen(Screen): return for repotitle in repotitles: - prompt = self.repostat[self.title2dirname[repotitle]]['prompt'] + prompt = self.repostat[self.title2dirname[repotitle]]["prompt"] list_item = ListItem(Label(prompt)) repo_list_widget.append(list_item) - #if not self.stay_enabled[repodir]: + # if not self.stay_enabled[repodir]: # list_item.disabled = True def on_list_view_selected(self, event) -> None: @@ -141,10 +144,13 @@ class DashboardScreen(Screen): # 提取文件名 selected_repotitle = label_text.partition("\0")[0].replace("*", "") selected_repo = self.title2repo[label_text.partition("\0")[0].replace("*", "")] - # 跳转到准备屏幕 - self.app.push_screen(PreparationScreen(selected_repo, self.repostat[self.title2dirname[selected_repotitle]])) + self.app.push_screen( + PreparationScreen( + selected_repo, self.repostat[self.title2dirname[selected_repotitle]] + ) + ) def action_quit_app(self) -> None: """退出应用程序""" diff --git a/src/heurams/interface/screens/precache.py b/src/heurams/interface/screens/precache.py index b78d73d..e0ec3de 100644 --- a/src/heurams/interface/screens/precache.py +++ b/src/heurams/interface/screens/precache.py @@ -88,7 +88,7 @@ class PrecachingScreen(Screen): """预缓存单段文本的音频""" from heurams.context import config_var, rootdir, workdir - cache_dir = pathlib.Path(config_var.get()["paths"]["data"]) / 'cache' + 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(): @@ -149,7 +149,7 @@ class PrecachingScreen(Screen): from heurams.context import config_var, rootdir, workdir from heurams.kernel.repolib import Repo - repo_path = pathlib.Path(config_var.get()["paths"]["data"]) / 'repo' + repo_path = pathlib.Path(config_var.get()["paths"]["data"]) / "repo" repo_dirs = Repo.probe_vaild_repos_in_dir(repo_path) repos = map(Repo.create_from_repodir, repo_dirs) @@ -159,7 +159,11 @@ class PrecachingScreen(Screen): for repo in repos: try: for i in repo.ident_index: - nucleon_list.append(pt.Nucleon.create_on_nucleonic_data(repo.nucleonic_data_lict.get_itemic_unit(i))) + nucleon_list.append( + pt.Nucleon.create_on_nucleonic_data( + repo.nucleonic_data_lict.get_itemic_unit(i) + ) + ) except: continue self.total = len(nucleon_list) diff --git a/src/heurams/interface/screens/preparation.py b/src/heurams/interface/screens/preparation.py index 2cdb8a5..b563744 100644 --- a/src/heurams/interface/screens/preparation.py +++ b/src/heurams/interface/screens/preparation.py @@ -73,7 +73,9 @@ class PreparationScreen(Screen): def _get_full_content(self): content = "" for i in self.repo.ident_index: - n = pt.Nucleon.create_on_nucleonic_data(nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)) + n = pt.Nucleon.create_on_nucleonic_data( + nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i) + ) content += f"- {n['content']} \n" return content @@ -85,8 +87,14 @@ class PreparationScreen(Screen): lst = list() for i in self.repo.ident_index: - lst.append(pt.Nucleon.create_on_nucleonic_data(self.repo.nucleonic_data_lict.get_itemic_unit(i))) - precache_screen = PrecachingScreen(nucleons=lst, desc=self.repo.manifest["title"]) + lst.append( + pt.Nucleon.create_on_nucleonic_data( + self.repo.nucleonic_data_lict.get_itemic_unit(i) + ) + ) + precache_screen = PrecachingScreen( + nucleons=lst, desc=self.repo.manifest["title"] + ) self.app.push_screen(precache_screen) def action_quit_app(self): @@ -98,8 +106,12 @@ class PreparationScreen(Screen): if event.button.id == "start_memorizing_button": atoms = list() for i in self.repo.ident_index: - n = pt.Nucleon.create_on_nucleonic_data(nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)) - e = pt.Electron.create_on_electonic_data(electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i)) + n = pt.Nucleon.create_on_nucleonic_data( + nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i) + ) + e = pt.Electron.create_on_electonic_data( + electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i) + ) a = pt.Atom(n, e, self.repo.orbitic_data) atoms.append(a) @@ -107,7 +119,7 @@ class PreparationScreen(Screen): left_new = self.scheduled_num for i in atoms: i: pt.Atom - if i.registry['electron'].is_activated(): + if i.registry["electron"].is_activated(): if i.registry["electron"].is_due(): atoms_to_provide.append(i) else: diff --git a/src/heurams/interface/screens/repocreator.py b/src/heurams/interface/screens/repocreator.py index 503cc3b..6b2e916 100644 --- a/src/heurams/interface/screens/repocreator.py +++ b/src/heurams/interface/screens/repocreator.py @@ -6,8 +6,7 @@ import toml from textual.app import ComposeResult from textual.containers import ScrollableContainer from textual.screen import Screen -from textual.widgets import (Button, Footer, Header, Input, Label, Markdown, - Select) +from textual.widgets import Button, Footer, Header, Input, Label, Markdown, Select from heurams.context import config_var from heurams.services.version import ver diff --git a/src/heurams/kernel/algorithms/sm15m.py b/src/heurams/kernel/algorithms/sm15m.py index 2423d41..a909d58 100644 --- a/src/heurams/kernel/algorithms/sm15m.py +++ b/src/heurams/kernel/algorithms/sm15m.py @@ -14,13 +14,22 @@ import pathlib from typing import TypedDict from heurams.context import config_var -from heurams.kernel.algorithms.sm15m_calc import (MAX_AF, MIN_AF, NOTCH_AF, - RANGE_AF, RANGE_REPETITION, - SM, THRESHOLD_RECALL, Item) +from heurams.kernel.algorithms.sm15m_calc import ( + MAX_AF, + MIN_AF, + NOTCH_AF, + RANGE_AF, + RANGE_REPETITION, + SM, + THRESHOLD_RECALL, + Item, +) # 全局状态文件路径 _GLOBAL_STATE_FILE = os.path.expanduser( - pathlib.Path(config_var.get()["paths"]["data"]) / 'global' / "sm15m_global_state.json" + pathlib.Path(config_var.get()["paths"]["data"]) + / "global" + / "sm15m_global_state.json" ) diff --git a/src/heurams/kernel/particles/atom.py b/src/heurams/kernel/particles/atom.py index 3e8cc33..3cd22a0 100644 --- a/src/heurams/kernel/particles/atom.py +++ b/src/heurams/kernel/particles/atom.py @@ -99,5 +99,6 @@ class Atom: def __repr__(self): from pprint import pformat + s = pformat(self.registry, indent=4) return s diff --git a/src/heurams/kernel/particles/electron.py b/src/heurams/kernel/particles/electron.py index ab7ff31..ba58db5 100644 --- a/src/heurams/kernel/particles/electron.py +++ b/src/heurams/kernel/particles/electron.py @@ -31,6 +31,7 @@ class Electron: def __repr__(self): from pprint import pformat + s = pformat(self.algodata, indent=4) return s diff --git a/src/heurams/kernel/particles/nucleon.py b/src/heurams/kernel/particles/nucleon.py index 1c1c738..61e4d56 100644 --- a/src/heurams/kernel/particles/nucleon.py +++ b/src/heurams/kernel/particles/nucleon.py @@ -13,7 +13,7 @@ class Nucleon: self.ident = ident env = {"payload": payload} self.evalizer = Evalizer(environment=env) - self.data: dict = self.evalizer(deepcopy((payload | common))) # type: ignore + self.data: dict = self.evalizer(deepcopy((payload | common))) # type: ignore def __getitem__(self, key): if isinstance(key, str): @@ -45,6 +45,7 @@ class Nucleon: def __repr__(self): from pprint import pformat + s = pformat(self.data, indent=4) return s diff --git a/src/heurams/kernel/particles/orbital.py b/src/heurams/kernel/particles/orbital.py index ca5f0d7..8d76364 100644 --- a/src/heurams/kernel/particles/orbital.py +++ b/src/heurams/kernel/particles/orbital.py @@ -1,16 +1,17 @@ -from heurams.utils.evalizor import Evalizer -from heurams.utils.lict import Lict +"""轨道对象""" +# 似乎没有实现这个类的必要... +# 那不妨在这儿写点文档 -class Orbital: - @classmethod - def create_orbital(cls, schedule: list, phase_def: dict): - phase_def = Lict(initdict=phase_def) # type: ignore - orbital = Lict() - for i in schedule: - orbital[i] = Lict(phase_def[i]) - return orbital +""" +orbital, 即轨道, 是定义队列式复习阶段流程的数据结构, 其实就是个字典, 至于为何不用typeddict, 因为懒. - @classmethod - def create_orbital_on_orbitic_data(cls, orbitic_data): - return cls.create_orbital(orbitic_data["schedule"], orbitic_data["phases"]) +orbital_example = { + "schedule": [列表 存储阶段(phases)名称] + "phases":{ + 阶段名称 = [["谜题(puzzle 现称 evaluator 评估器)名称", "概率系数 可大于1(整数部分为重复次数) 注意使用字符串包裹(toml 规范)"], ...], + ... + } +} +至于谜题定义 放在 nucleon['puzzles'], 这样设计是为了兼容多种不同谜题实现的记忆单元, 尽管如此, 你也可见其谜题调度方式必须是相同的. +""" diff --git a/src/heurams/kernel/reactor/__init__.py b/src/heurams/kernel/reactor/__init__.py index 278b6b1..dc87681 100644 --- a/src/heurams/kernel/reactor/__init__.py +++ b/src/heurams/kernel/reactor/__init__.py @@ -9,4 +9,4 @@ logger = get_logger(__name__) __all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"] -logger.debug("反应堆模块已加载") \ No newline at end of file +logger.debug("反应堆模块已加载") diff --git a/src/heurams/kernel/reactor/fission.py b/src/heurams/kernel/reactor/fission.py index ef6e881..a7499a0 100644 --- a/src/heurams/kernel/reactor/fission.py +++ b/src/heurams/kernel/reactor/fission.py @@ -8,24 +8,26 @@ from .states import PhaserState class Fission: - """裂变器: 单原子调度展开器""" + """单原子调度展开器""" def __init__(self, atom: pt.Atom, phase_state=PhaserState.RECOGNITION): self.logger = get_logger(__name__) self.atom = atom - - #NOTE: phase 为 PhaserState 枚举实例,需要获取其value - phase_value = phase_state.value if isinstance(phase_state, PhaserState) else phase_state - - self.orbital_schedule = atom.registry["orbital"]["schedule"][phase_value] # type: ignore - self.orbital_puzzles = atom.registry["orbital"]["puzzles"] - + + # NOTE: phase 为 PhaserState 枚举实例,需要获取其value + phase_value = ( + phase_state.value if isinstance(phase_state, PhaserState) else phase_state + ) + + self.orbital_schedule = atom.registry["phases"][phase_value] # type: ignore + self.orbital_puzzles = atom.registry["nucleon"]["puzzles"] + self.puzzles = list() for item, possibility in self.orbital_schedule: # type: ignore - self.logger.debug(f"开始处理 orbital 项: {item}") + self.logger.debug(f"开始处理: {item}") if not isinstance(possibility, float): possibility = float(possibility) - + while possibility > 1: self.puzzles.append( { @@ -34,7 +36,7 @@ class Fission: } ) possibility -= 1 - + if random.random() <= possibility: self.puzzles.append( { @@ -42,8 +44,8 @@ class Fission: "alia": item, } ) - + self.logger.debug(f"orbital 项处理完成: {item}") - def generate(self): - yield from self.puzzles \ No newline at end of file + def get_puzzles_list(self): + yield from self.puzzles diff --git a/src/heurams/kernel/reactor/phaser.py b/src/heurams/kernel/reactor/phaser.py index 70922c8..fd91bf6 100644 --- a/src/heurams/kernel/reactor/phaser.py +++ b/src/heurams/kernel/reactor/phaser.py @@ -13,20 +13,20 @@ class Phaser(Machine): def __init__(self, atoms: list[pt.Atom]) -> None: logger.debug("Phaser.__init__: 原子数量=%d", len(atoms)) - + new_atoms = list() old_atoms = list() - + 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() - #TODO: 改进为基于配置文件的可变复习阶段管理 + # TODO: 改进为基于配置文件的可变复习阶段管理 if len(old_atoms): self.processions.append( Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习") @@ -42,27 +42,47 @@ class Phaser(Machine): self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习")) logger.debug("创建总体复习 Procession") logger.debug("Phaser 初始化完成, processions 数量=%d", len(self.processions)) - + # 设置transitions状态机 states = [ - {'name': PhaserState.UNSURE.value, 'on_enter': 'on_unsure'}, - {'name': PhaserState.QUICK_REVIEW.value, 'on_enter': 'on_quick_review'}, - {'name': PhaserState.RECOGNITION.value, 'on_enter': 'on_recognition'}, - {'name': PhaserState.FINAL_REVIEW.value, 'on_enter': 'on_final_review'}, - {'name': PhaserState.FINISHED.value, 'on_enter': 'on_finished'} + {"name": PhaserState.UNSURE.value, "on_enter": "on_unsure"}, + {"name": PhaserState.QUICK_REVIEW.value, "on_enter": "on_quick_review"}, + {"name": PhaserState.RECOGNITION.value, "on_enter": "on_recognition"}, + {"name": PhaserState.FINAL_REVIEW.value, "on_enter": "on_final_review"}, + {"name": PhaserState.FINISHED.value, "on_enter": "on_finished"}, ] - + transitions = [ - {'trigger': 'to_unsure', 'source': '*', 'dest': PhaserState.UNSURE.value}, - {'trigger': 'to_quick_review', 'source': '*', 'dest': PhaserState.QUICK_REVIEW.value}, - {'trigger': 'to_recognition', 'source': '*', 'dest': PhaserState.RECOGNITION.value}, - {'trigger': 'to_final_review', 'source': '*', 'dest': PhaserState.FINAL_REVIEW.value}, - {'trigger': 'to_finished', 'source': '*', 'dest': PhaserState.FINISHED.value} + {"trigger": "to_unsure", "source": "*", "dest": PhaserState.UNSURE.value}, + { + "trigger": "to_quick_review", + "source": "*", + "dest": PhaserState.QUICK_REVIEW.value, + }, + { + "trigger": "to_recognition", + "source": "*", + "dest": PhaserState.RECOGNITION.value, + }, + { + "trigger": "to_final_review", + "source": "*", + "dest": PhaserState.FINAL_REVIEW.value, + }, + { + "trigger": "to_finished", + "source": "*", + "dest": PhaserState.FINISHED.value, + }, ] - - Machine.__init__(self, states=states, transitions=transitions, - initial=PhaserState.UNSURE.value) - + + Machine.__init__( + self, + states=states, + transitions=transitions, + initial=PhaserState.UNSURE.value, + ) + self.to_unsure() def on_unsure(self): @@ -97,15 +117,15 @@ class Phaser(Machine): self.to_recognition() elif i.phase == PhaserState.FINAL_REVIEW: self.to_final_review() - + logger.debug("找到未完成的 Procession: phase=%s", i.phase) return i - + # 所有Procession都已完成 self.to_finished() logger.debug("所有 Procession 已完成, 状态设置为 FINISHED") return None - + @property def state(self): """获取当前状态值""" @@ -114,4 +134,4 @@ class Phaser(Machine): for phase in PhaserState: if phase.value == current_state: return phase - return PhaserState.UNSURE \ No newline at end of file + return PhaserState.UNSURE diff --git a/src/heurams/kernel/reactor/procession.py b/src/heurams/kernel/reactor/procession.py index 46f7611..256a0de 100644 --- a/src/heurams/kernel/reactor/procession.py +++ b/src/heurams/kernel/reactor/procession.py @@ -17,27 +17,39 @@ class Procession(Machine): phase_state.value, name, ) - - # 初始化原子队列 + self.atoms = atoms self.queue = atoms.copy() self.current_atom = atoms[0] if atoms else None self.cursor = 0 self.name = name self.phase = phase_state - - # 设置transitions状态机 - states = [{'name': ProcessionState.RUNNING.value, 'on_enter': 'on_running'}, - {'name': ProcessionState.FINISHED.value, 'on_enter': 'on_finished'}] - - transitions = [ - {'trigger': 'finish', 'source': ProcessionState.RUNNING.value, 'dest': ProcessionState.FINISHED.value}, - {'trigger': 'restart', 'source': ProcessionState.FINISHED.value, 'dest': ProcessionState.RUNNING.value} + + states = [ + {"name": ProcessionState.RUNNING.value, "on_enter": "on_running"}, + {"name": ProcessionState.FINISHED.value, "on_enter": "on_finished"}, ] - - Machine.__init__(self, states=states, transitions=transitions, - initial=ProcessionState.RUNNING.value) - + + transitions = [ + { + "trigger": "finish", + "source": ProcessionState.RUNNING.value, + "dest": ProcessionState.FINISHED.value, + }, + { + "trigger": "restart", + "source": ProcessionState.FINISHED.value, + "dest": ProcessionState.RUNNING.value, + }, + ] + + Machine.__init__( + self, + states=states, + transitions=transitions, + initial=ProcessionState.RUNNING.value, + ) + logger.debug("Procession 初始化完成, 队列长度=%d", len(self.queue)) def on_running(self): @@ -51,7 +63,7 @@ class Procession(Machine): 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): if self.state != ProcessionState.FINISHED.value: self.finish() # 触发状态转换 @@ -61,16 +73,19 @@ class Procession(Machine): self.restart() # 确保在RUNNING状态 self.current_atom = self.queue[self.cursor] logger.debug("cursor 更新为: %d", self.cursor) - logger.debug("当前原子更新为: %s", self.current_atom.ident if self.current_atom else "None") + logger.debug( + "当前原子更新为: %s", + 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") - + if not self.queue or self.queue[-1] != atom or len(self) <= 1: self.queue.append(atom) logger.debug("原子已追加到队列, 新队列长度=%d", len(self.queue)) @@ -97,16 +112,16 @@ class Procession(Machine): empty = len(self.queue) == 0 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() \ No newline at end of file + self.finish() diff --git a/src/heurams/kernel/reactor/states.py b/src/heurams/kernel/reactor/states.py index 4c9afa1..ae4a8db 100644 --- a/src/heurams/kernel/reactor/states.py +++ b/src/heurams/kernel/reactor/states.py @@ -18,4 +18,4 @@ class ProcessionState(Enum): FINISHED = "finished" -logger.debug("状态枚举定义已加载") \ No newline at end of file +logger.debug("状态枚举定义已加载") diff --git a/src/heurams/kernel/repolib/repo.py b/src/heurams/kernel/repolib/repo.py index 2377afe..d492d67 100644 --- a/src/heurams/kernel/repolib/repo.py +++ b/src/heurams/kernel/repolib/repo.py @@ -9,11 +9,13 @@ import heurams.kernel.particles as pt from ...utils.lict import Lict + class RepoManifest(TypedDict): title: str author: str desc: str + class Repo: file_mapping = { "schedule": "schedule.toml", @@ -43,7 +45,7 @@ class Repo: source=None, ) -> None: self.schedule: dict = schedule - self.manifest: RepoManifest = manifest # type: ignore + self.manifest: RepoManifest = manifest # type: ignore self.typedef: dict = typedef self.payload: Lict = payload self.algodata: Lict = algodata @@ -61,9 +63,7 @@ class Repo: def generate_particles_data(self): self.nucleonic_data_lict = Lict( - initlist=list(map( - self._nucleonic_proc, - self.payload)) + initlist=list(map(self._nucleonic_proc, self.payload)) ) self.orbitic_data = self.schedule self.ident_index = self.nucleonic_data_lict.keys() @@ -88,6 +88,7 @@ class Repo: def __repr__(self): from pprint import pformat + s = pformat(self.database, indent=4) return s @@ -172,4 +173,4 @@ class Repo: if i.is_dir(): if cls.check_repodir(i): lst.append(i) - return lst \ No newline at end of file + return lst diff --git a/src/heurams/utils/evalizor.py b/src/heurams/utils/evalizor.py index 9abb580..b58138e 100644 --- a/src/heurams/utils/evalizor.py +++ b/src/heurams/utils/evalizor.py @@ -1,4 +1,4 @@ -class Evalizer(): +class Evalizer: """几乎无副作用的模板系统 接受环境信息并创建一个模板解析工具, 工具传入参数支持list, dict及其嵌套 diff --git a/tests/interface/test_synctool.py b/tests/interface/test_synctool.py index b70d8a3..096ac8a 100644 --- a/tests/interface/test_synctool.py +++ b/tests/interface/test_synctool.py @@ -10,8 +10,12 @@ from unittest.mock import MagicMock, Mock, patch from heurams.context import ConfigContext from heurams.services.config import ConfigFile -from heurams.services.sync_service import (ConflictStrategy, SyncConfig, - SyncMode, SyncService) +from heurams.services.sync_service import ( + ConflictStrategy, + SyncConfig, + SyncMode, + SyncService, +) class TestSyncServiceUnit(unittest.TestCase): @@ -202,8 +206,7 @@ class TestSyncServiceUnit(unittest.TestCase): mock_config.data = config_data mock_config_var.get.return_value = mock_config - from heurams.services.sync_service import \ - create_sync_service_from_config + from heurams.services.sync_service import create_sync_service_from_config service = create_sync_service_from_config()