feat: 改进状态机

This commit is contained in:
2026-01-03 13:08:08 +08:00
parent aacf4fdbdf
commit 94aaef386b
24 changed files with 275 additions and 157 deletions

View File

@@ -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 # 包入口点

View File

@@ -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 = "选择正确项: " }

View File

@@ -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)))

View File

@@ -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",

View File

@@ -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)
# print(repr(e))
# print(repo)

View File

@@ -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:
"""
功能完备的上下文管理器

View File

@@ -12,6 +12,7 @@ from .screens.synctool import SyncScreen
logger = get_logger(__name__)
def environment_check():
from pathlib import Path

View File

@@ -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:
"""退出应用程序"""

View File

@@ -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)

View File

@@ -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:

View File

@@ -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

View File

@@ -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"
)

View File

@@ -99,5 +99,6 @@ class Atom:
def __repr__(self):
from pprint import pformat
s = pformat(self.registry, indent=4)
return s

View File

@@ -31,6 +31,7 @@ class Electron:
def __repr__(self):
from pprint import pformat
s = pformat(self.algodata, indent=4)
return s

View File

@@ -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

View File

@@ -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'], 这样设计是为了兼容多种不同谜题实现的记忆单元, 尽管如此, 你也可见其谜题调度方式必须是相同的.
"""

View File

@@ -9,4 +9,4 @@ logger = get_logger(__name__)
__all__ = ["PhaserState", "ProcessionState", "Procession", "Fission", "Phaser"]
logger.debug("反应堆模块已加载")
logger.debug("反应堆模块已加载")

View File

@@ -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
def get_puzzles_list(self):
yield from self.puzzles

View File

@@ -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
return PhaserState.UNSURE

View File

@@ -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()
self.finish()

View File

@@ -18,4 +18,4 @@ class ProcessionState(Enum):
FINISHED = "finished"
logger.debug("状态枚举定义已加载")
logger.debug("状态枚举定义已加载")

View File

@@ -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
return lst

View File

@@ -1,4 +1,4 @@
class Evalizer():
class Evalizer:
"""几乎无副作用的模板系统
接受环境信息并创建一个模板解析工具, 工具传入参数支持list, dict及其嵌套

View File

@@ -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()