Compare commits
12 Commits
main
...
5d883b015e
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d883b015e | |||
| a689604021 | |||
| 55c656e8f9 | |||
| 94aaef386b | |||
| aacf4fdbdf | |||
| eced6130f1 | |||
| 9b32a01a10 | |||
| 94839c6369 | |||
| 573bf22b2b | |||
| eaa38fb880 | |||
| b65dad6a1f | |||
| c13b8bed98 |
20
README.md
20
README.md
@@ -1,18 +1,20 @@
|
|||||||
# 潜进 (HeurAMS) - 启发式辅助记忆程序
|
# 潜进 (HeurAMS) - 启发式辅助记忆程序
|
||||||
|
|
||||||
## 概述
|
## 概述
|
||||||
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的多用途辅助记忆软件, 提供动态规划的优化记忆方案
|
"潜进" (HeurAMS: Heuristic Auxiliary Memorizing Scheduler, 启发式记忆辅助调度器) 是为习题册, 古诗词, 及其他问答/记忆/理解型知识设计的开放源代码多用途辅助记忆软件, 提供动态规划的优化记忆方案
|
||||||
|
|
||||||
## 关于此仓库
|
## 关于此仓库
|
||||||
"潜进" 软件组项目包含多个子项目
|
本仓库为 "潜进" 软件组项目的核心部分, 包含核心功能模块以及基于 Textual 框架的基础用户界面(heurams.interface)实现
|
||||||
此仓库包含了 "潜进" 项目的核心和基于 Textual 的基本用户界面的实现
|
除了通过用户界面进行学习外, 你也可以在 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 状态机, 增强可维护性; 实现整体回顾记忆功能, 与队列式记忆功能并列; 加入状态机快照功能(基于 pickle), 使中断的记忆流程得以恢复; 增加"整体文章引用"功能, 实现从一篇长文本中摘取内容片段记忆并在原文中高亮查看的组织操作.
|
||||||
> 下一步?
|
> 下一步?
|
||||||
> 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务...
|
> 使用 Flutter 构建酷酷的现代化前端, 增加云同步/文档源服务...
|
||||||
|
|
||||||
@@ -103,7 +105,7 @@ verify_ssl = true # SSL 证书验证
|
|||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
### 架构图
|
### 架构图(待更新 0.5.0)
|
||||||
|
|
||||||
以下 Mermaid 图展示了 HeurAMS 的主要组件及其关系:
|
以下 Mermaid 图展示了 HeurAMS 的主要组件及其关系:
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ graph TB
|
|||||||
Algorithms --> Files
|
Algorithms --> Files
|
||||||
```
|
```
|
||||||
|
|
||||||
### 目录结构
|
### 目录结构(待更新 0.5.0)
|
||||||
```
|
```
|
||||||
src/heurams/
|
src/heurams/
|
||||||
├── __init__.py # 包入口点
|
├── __init__.py # 包入口点
|
||||||
|
|||||||
59
data/config/config.toml
Normal file
59
data/config/config.toml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# [调试] 将更改保存到文件
|
||||||
|
persist_to_file = 1
|
||||||
|
|
||||||
|
# [调试] 覆写时间, 设为 -1 以禁用
|
||||||
|
daystamp_override = -1
|
||||||
|
timestamp_override = -1
|
||||||
|
|
||||||
|
# [调试] 一键通过
|
||||||
|
quick_pass = 1
|
||||||
|
|
||||||
|
# 对于每个项目的默认新记忆原子数量
|
||||||
|
scheduled_num = 8
|
||||||
|
|
||||||
|
# UTC 时间戳修正 仅用于 UNIX 日时间戳的生成修正, 单位为秒
|
||||||
|
timezone_offset = +28800 # 中国标准时间 (UTC+8)
|
||||||
|
|
||||||
|
[interface]
|
||||||
|
|
||||||
|
[interface.memorizor]
|
||||||
|
autovoice = true # 自动语音播放, 仅限于 recognition 组件
|
||||||
|
|
||||||
|
[algorithm]
|
||||||
|
default = "SM-2" # 主要算法; 可选项: SM-2, SM-15M, FSRS
|
||||||
|
|
||||||
|
[puzzles] # 谜题默认配置
|
||||||
|
|
||||||
|
[puzzles.mcq]
|
||||||
|
max_riddles_num = 2
|
||||||
|
|
||||||
|
[puzzles.cloze]
|
||||||
|
min_denominator = 3
|
||||||
|
|
||||||
|
[paths] # 相对于配置文件的 ".." (即工作目录) 而言 或绝对路径
|
||||||
|
data = "./data"
|
||||||
|
cache = "./data/cache"
|
||||||
|
config = "./data/config"
|
||||||
|
global = "./data/global"
|
||||||
|
repo = "./data/repo"
|
||||||
|
[services] # 定义服务到提供者的映射
|
||||||
|
audio = "playsound" # 可选项: playsound(通用), termux(仅用于支持 Android Termux), mpg123(TODO)
|
||||||
|
tts = "edgetts" # 可选项: edgetts
|
||||||
|
llm = "openai" # 可选项: openai
|
||||||
|
sync = "webdav" # 可选项: 留空, webdav
|
||||||
|
|
||||||
|
[providers.tts.edgetts] # EdgeTTS 设置
|
||||||
|
voice = "zh-CN-XiaoxiaoNeural" # 可选项: zh-CN-YunjianNeural (男声), zh-CN-XiaoxiaoNeural (女声)
|
||||||
|
|
||||||
|
[providers.llm.openai] # 与 OpenAI 相容的语言模型接口服务设置
|
||||||
|
url = ""
|
||||||
|
key = ""
|
||||||
|
|
||||||
|
[providers.sync.webdav] # WebDAV 同步设置
|
||||||
|
url = ""
|
||||||
|
username = ""
|
||||||
|
password = ""
|
||||||
|
remote_path = "/heurams/"
|
||||||
|
verify_ssl = true
|
||||||
|
|
||||||
|
[sync]
|
||||||
1
data/repo/test/algodata.json
Normal file
1
data/repo/test/algodata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
3
data/repo/test/manifest.toml
Normal file
3
data/repo/test/manifest.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
title = "测试单元: 过秦论"
|
||||||
|
author = "__heurams__"
|
||||||
|
desc = "高考古诗文: 过秦论"
|
||||||
11
data/repo/test/payload.toml
Normal file
11
data/repo/test/payload.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
["秦孝公据崤函之固, 拥雍州之地,"]
|
||||||
|
note = []
|
||||||
|
content = "秦孝公/据/崤函/之固/, 拥/雍州/之地,/"
|
||||||
|
translation = "秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,"
|
||||||
|
keyword_note = {"据"="占据", "崤函"="崤山和函谷关", "雍州"="古代九州之一"}
|
||||||
|
|
||||||
|
["君臣固守以窥周室,"]
|
||||||
|
note = []
|
||||||
|
content = "君臣/固守/以窥/周室,/"
|
||||||
|
translation = "君臣牢固地守卫着,借以窥视周王室的权力,"
|
||||||
|
keyword_note = {"窥"="窥视"}
|
||||||
5
data/repo/test/schedule.toml
Normal file
5
data/repo/test/schedule.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
schedule = ["quick_review", "recognition", "final_review"]
|
||||||
|
[phases]
|
||||||
|
quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["Recognition", "1.0"]]
|
||||||
|
recognition = [["Recognition", "1.0"]]
|
||||||
|
final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["Recognition", "1.0"]]
|
||||||
17
data/repo/test/typedef.toml
Normal file
17
data/repo/test/typedef.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[annotation]
|
||||||
|
note = "笔记"
|
||||||
|
keyword_note = "关键词翻译"
|
||||||
|
translation = "语句翻译"
|
||||||
|
delimiter = "分隔符"
|
||||||
|
content = "内容"
|
||||||
|
tts_text = "文本转语音文本"
|
||||||
|
|
||||||
|
[common]
|
||||||
|
delimiter = "/"
|
||||||
|
tts_text = "eval:payload['content'].replace('/', '')"
|
||||||
|
|
||||||
|
[common.puzzles] # 谜题定义
|
||||||
|
# 我们称 "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 = "选择正确项: " }
|
||||||
|
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:payload['content']", delimiter = "eval:nucleon['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Nucleon 是 HeurAMS 软件项目使用的基于 TOML 的专有源文件格式, 版本 5
|
|
||||||
# 建议使用的 MIME 类型: application/vnd.xyz.imwangzhiyu.heurams-nucleon.v5+toml
|
|
||||||
|
|
||||||
[__metadata__]
|
|
||||||
[__metadata__.attribution] # 元信息
|
|
||||||
desc = "带有宏支持的空白模板"
|
|
||||||
|
|
||||||
[__metadata__.annotation] # 键批注
|
|
||||||
|
|
||||||
[__metadata__.formation] # 文件配置
|
|
||||||
#delimiter = "/"
|
|
||||||
#tts_text = "eval:nucleon['content'].replace('/', '')"
|
|
||||||
|
|
||||||
[__metadata__.orbital.puzzles] # 谜题定义
|
|
||||||
# 我们称 "Recognition" 为 recognition 谜题的 alia
|
|
||||||
#"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:nucleon['content']", secondary = ["eval:nucleon['keyword_note']", "eval:nucleon['note']"], top_dim = ["eval:nucleon['translation']"] }
|
|
||||||
#"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:nucleon['content']", mapping = "eval:nucleon['keyword_note']", jammer = "eval:nucleon['keyword_note']", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
|
|
||||||
#"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:nucleon['content']", delimiter = "eval:metadata['formation']['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
|
||||||
|
|
||||||
[__metadata__.orbital.schedule] # 内置的推荐学习方案
|
|
||||||
#quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["recognition", "1.0"]]
|
|
||||||
#recognition = [["Recognition", "1.0"]]
|
|
||||||
#final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["recognition", "1.0"]]
|
|
||||||
@@ -31,12 +31,7 @@ max_riddles_num = 2
|
|||||||
min_denominator = 3
|
min_denominator = 3
|
||||||
|
|
||||||
[paths] # 相对于配置文件的 ".." (即工作目录) 而言 或绝对路径
|
[paths] # 相对于配置文件的 ".." (即工作目录) 而言 或绝对路径
|
||||||
nucleon_dir = "./data/nucleon"
|
data = "./data"
|
||||||
electron_dir = "./data/electron"
|
|
||||||
global_dir = "./data/global" # 全局数据路径, SM-15 等算法需要
|
|
||||||
orbital_dir = "./data/orbital"
|
|
||||||
cache_dir = "./data/cache"
|
|
||||||
template_dir = "./data/template"
|
|
||||||
|
|
||||||
[services] # 定义服务到提供者的映射
|
[services] # 定义服务到提供者的映射
|
||||||
audio = "playsound" # 可选项: playsound(通用), termux(仅用于支持 Android Termux), mpg123(TODO)
|
audio = "playsound" # 可选项: playsound(通用), termux(仅用于支持 Android Termux), mpg123(TODO)
|
||||||
14
examples/jiebatest.py
Normal file
14
examples/jiebatest.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# encoding=utf-8
|
||||||
|
import jieba
|
||||||
|
|
||||||
|
# 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)))
|
||||||
|
|
||||||
|
seg_list = jieba.cut("秦孝公据崤函之固, 拥雍州之地", cut_all=False)
|
||||||
|
print("Default Mode: " + "/ ".join(seg_list)) # 精确模式
|
||||||
|
|
||||||
|
seg_list = jieba.cut("他来到了网易杭研大厦") # 默认是精确模式
|
||||||
|
print(", ".join(seg_list))
|
||||||
719
examples/repo.ipynb
Normal file
719
examples/repo.ipynb
Normal file
@@ -0,0 +1,719 @@
|
|||||||
|
{
|
||||||
|
"cells": [
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "51b89355",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# 演练场\n",
|
||||||
|
"此笔记本将带你了解 repomgr 与 particles 对象相关操作"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "f5c49014",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# 从一个例子开始\n",
|
||||||
|
"## 了解文件结构\n",
|
||||||
|
"了解一下文件结构"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 1,
|
||||||
|
"id": "a5ed9864",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[01;34m.\u001b[0m\n",
|
||||||
|
"├── \u001b[00mrepo.ipynb\u001b[0m\n",
|
||||||
|
"└── \u001b[01;34mtest_repo\u001b[0m\n",
|
||||||
|
" ├── \u001b[00malgodata.json\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mmanifest.toml\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mpayload.toml\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mschedule.toml\u001b[0m\n",
|
||||||
|
" └── \u001b[00mtypedef.toml\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"2 directories, 6 files\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!tree # 了解文件结构"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "4e10922b",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"如果你先前运行了单元格, 请运行下面一格清理."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 2,
|
||||||
|
"id": "9777730e",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"zsh:1: no matches found: heurams.log*\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"!rm -rf test_new_repo\n",
|
||||||
|
"!rm -rf heurams.log*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "058c098f",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 导入模块\n",
|
||||||
|
"导入所需模块, 你会看到欢迎信息, 标示了库所使用的配置. \n",
|
||||||
|
"HeurAMS 在基础设施也使用配置文件实现隐式的依赖注入. "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 3,
|
||||||
|
"id": "bf1b00c8",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"欢迎使用 HeurAMS 及其组件!\n",
|
||||||
|
"rootdir: /mnt/data/Devel/HeurAMS/HeurAMS/src/heurams\n",
|
||||||
|
"workdir: /mnt/data/Devel/HeurAMS/HeurAMS/examples\n",
|
||||||
|
"未能加载自定义用户配置\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"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 模块, 用于表示文件路径, 在整个项目中, 都使用此模块表示路径"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "ea1f68bb",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 运行时检查\n",
|
||||||
|
"如你所见, repo 在文件系统内存储为一个文件夹. \n",
|
||||||
|
"因此在载入之前, 首先要检查这是否是一个合乎标准的 repo 文件夹. "
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 4,
|
||||||
|
"id": "897b62d7",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"这是一个 合规 的 repo!\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"is_vaild = repolib.Repo.check_repodir(Path(\"./test_repo\"))\n",
|
||||||
|
"print(f\"这是一个 {'合规' if is_vaild else '不合规'} 的 repo!\")"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "24a19991",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 加载仓库\n",
|
||||||
|
"接下来, 正式加载 repo."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 5,
|
||||||
|
"id": "708ae7e4",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"test_repo = repolib.Repo.create_from_repodir(Path(\"./test_repo\"))"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "474f8eb7",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 导出为字典\n",
|
||||||
|
"作为一个数据容器, repo 相应地建立了导入和导出的功能. \n",
|
||||||
|
"我们刚刚从本地文件夹导入了一个 repo. \n",
|
||||||
|
"现在试试导出为一个字典."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 6,
|
||||||
|
"id": "a11115fb",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"{'algodata': [('君臣固守以窥周室,', {}), ('秦孝公据崤函之固, 拥雍州之地,', {})],\n",
|
||||||
|
" 'manifest': {'author': '__heurams__',\n",
|
||||||
|
" 'desc': '高考古诗文: 过秦论',\n",
|
||||||
|
" 'title': '测试单元: 过秦论'},\n",
|
||||||
|
" 'payload': [('君臣固守以窥周室,',\n",
|
||||||
|
" {'content': '君臣/固守/以窥/周室,/',\n",
|
||||||
|
" 'keyword_note': {'窥': '窥视'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '君臣牢固地守卫着,借以窥视周王室的权力,'}),\n",
|
||||||
|
" ('秦孝公据崤函之固, 拥雍州之地,',\n",
|
||||||
|
" {'content': '秦孝公/据/崤函/之固/, 拥/雍州/之地,/',\n",
|
||||||
|
" 'keyword_note': {'崤函': '崤山和函谷关', '据': '占据', '雍州': '古代九州之一'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,'})],\n",
|
||||||
|
" 'schedule': {'phases': {'final_review': [['FillBlank', '0.7'],\n",
|
||||||
|
" ['SelectMeaning', '0.7'],\n",
|
||||||
|
" ['Recognition', '1.0']],\n",
|
||||||
|
" 'quick_review': [['FillBlank', '1.0'],\n",
|
||||||
|
" ['SelectMeaning', '0.5'],\n",
|
||||||
|
" ['Recognition', '1.0']],\n",
|
||||||
|
" 'recognition': [['Recognition', '1.0']]},\n",
|
||||||
|
" 'schedule': ['quick_review', 'recognition', 'final_review']},\n",
|
||||||
|
" 'source': PosixPath('test_repo'),\n",
|
||||||
|
" 'typedef': {'annotation': {'content': '内容',\n",
|
||||||
|
" 'delimiter': '分隔符',\n",
|
||||||
|
" 'keyword_note': '关键词翻译',\n",
|
||||||
|
" 'note': '笔记',\n",
|
||||||
|
" 'translation': '语句翻译',\n",
|
||||||
|
" 'tts_text': '文本转语音文本'},\n",
|
||||||
|
" 'common': {'delimiter': '/',\n",
|
||||||
|
" 'tts_text': \"eval:payload['content'].replace('/', '')\"},\n",
|
||||||
|
" 'puzzles': {'FillBlank': {'__hint__': '',\n",
|
||||||
|
" '__origin__': 'cloze',\n",
|
||||||
|
" 'delimiter': \"eval:metadata['formation']['delimiter']\",\n",
|
||||||
|
" 'min_denominator': \"eval:default['cloze']['min_denominator']\",\n",
|
||||||
|
" 'text': \"eval:payload['content']\"},\n",
|
||||||
|
" 'Recognition': {'__hint__': '',\n",
|
||||||
|
" '__origin__': 'recognition',\n",
|
||||||
|
" 'primary': \"eval:payload['content']\",\n",
|
||||||
|
" 'secondary': [\"eval:payload['keyword_note']\",\n",
|
||||||
|
" \"eval:payload['note']\"],\n",
|
||||||
|
" 'top_dim': [\"eval:payload['translation']\"]},\n",
|
||||||
|
" 'SelectMeaning': {'__hint__': \"eval:payload['content']\",\n",
|
||||||
|
" '__origin__': 'mcq',\n",
|
||||||
|
" 'jammer': \"eval:list(payload['keyword_note'].values())\",\n",
|
||||||
|
" 'mapping': \"eval:payload['keyword_note']\",\n",
|
||||||
|
" 'max_riddles_num': \"eval:default['mcq']['max_riddles_num']\",\n",
|
||||||
|
" 'prefix': '选择正确项: ',\n",
|
||||||
|
" 'primary': \"eval:payload['content']\"}},\n",
|
||||||
|
" '古文句': {}}}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"test_repo_dic = test_repo.export_to_single_dict()\n",
|
||||||
|
"from pprint import pprint\n",
|
||||||
|
"\n",
|
||||||
|
"pprint(test_repo_dic)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "35a2e06f",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## 持久化与部分保存\n",
|
||||||
|
"如你所见, 所有内容被结构化地输出了! \n",
|
||||||
|
"\n",
|
||||||
|
"现在写回到文件夹! \n",
|
||||||
|
"\n",
|
||||||
|
"我们注意到, 并非所有的内容都要被修改. \n",
|
||||||
|
"我们可以只保存接受修改的一部分, 默认情况下, 是迭代的记忆数据(algodata). \n",
|
||||||
|
"这就是为什么我们一般不使用单个 json 或 toml 来存储 repo.\n",
|
||||||
|
"\n",
|
||||||
|
"persist_to_repodir 接受两个可选参数: \n",
|
||||||
|
"- save_list: 默认为 [\"algodata\"], 是要持久化的数据.\n",
|
||||||
|
"- source: 默认为原目录, 你也可以手动指定为其他文件夹(通过 Path)\n",
|
||||||
|
"\n",
|
||||||
|
"现在做一些演练, 我们将创建一个位于 test_new_repo 的\"克隆\", 此时我们!\n",
|
||||||
|
"除非文件夹已经存在, Repo 对象将会为你自动创建新文件夹."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 7,
|
||||||
|
"id": "05eeaacc",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"\u001b[01;34m.\u001b[0m\n",
|
||||||
|
"├── \u001b[00mheurams.log\u001b[0m\n",
|
||||||
|
"├── \u001b[00mrepo.ipynb\u001b[0m\n",
|
||||||
|
"├── \u001b[01;34mtest_new_repo\u001b[0m\n",
|
||||||
|
"│ ├── \u001b[00malgodata.json\u001b[0m\n",
|
||||||
|
"│ ├── \u001b[00mmanifest.toml\u001b[0m\n",
|
||||||
|
"│ ├── \u001b[00mpayload.toml\u001b[0m\n",
|
||||||
|
"│ ├── \u001b[00mschedule.toml\u001b[0m\n",
|
||||||
|
"│ └── \u001b[00mtypedef.toml\u001b[0m\n",
|
||||||
|
"└── \u001b[01;34mtest_repo\u001b[0m\n",
|
||||||
|
" ├── \u001b[00malgodata.json\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mmanifest.toml\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mpayload.toml\u001b[0m\n",
|
||||||
|
" ├── \u001b[00mschedule.toml\u001b[0m\n",
|
||||||
|
" └── \u001b[00mtypedef.toml\u001b[0m\n",
|
||||||
|
"\n",
|
||||||
|
"3 directories, 12 files\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"test_repo.persist_to_repodir(\n",
|
||||||
|
" save_list=[\"schedule\", \"payload\", \"manifest\", \"typedef\", \"algodata\"],\n",
|
||||||
|
" source=Path(\"test_new_repo\"),\n",
|
||||||
|
")\n",
|
||||||
|
"!tree"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "059d7bdf",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"如你所见, test_new_repo 已被生成!"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "4ef8925c",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"# 数据结构\n",
|
||||||
|
"现在讲解 repo 的数据结构"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "c19fed95",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"## Lict 对象\n",
|
||||||
|
"Lict 对象集成了部分列表和字典的功能, 数据在这两种风格的 API 间都可用, 且修改是同步的. \n",
|
||||||
|
"Lict 默认情况下不会保存序列顺序, 而是在列表形式下, 自动按索引字符序排布, 详情请参阅源代码. \n",
|
||||||
|
"现在导入并初始化一个 Lict 对象:"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 8,
|
||||||
|
"id": "7e88bd7c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('name', 'tom')]\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('name', 'tom')]\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"from heurams.utils.lict import Lict\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)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "4d760bf9",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### 输出形式\n",
|
||||||
|
"lct 的\"官方\"输出形式是列表形式\n",
|
||||||
|
"你也可以选择输出字典形式"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 9,
|
||||||
|
"id": "248f6cba",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"{'name': 'tom', 'age': 12, 'enemy': 'jerry'}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"print(lct.dicted_data)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "29dce184",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### dicted_data 属性与修改方式\n",
|
||||||
|
"dicted_data 属性是一个字典, 它自动同步来自 Lict 对象操作的修改.\n",
|
||||||
|
"一个注意事项: 不要直接修改 dicted_data, 这将不会触发同步 hook.\n",
|
||||||
|
"如果你一定要这样做, 请在完事后手动运行同步 hook.\n",
|
||||||
|
"推荐的修改方式是直接把 lct 当作一个字典"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 10,
|
||||||
|
"id": "a0eb07a7",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('name', 'tom')]\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('name', 'tom'), ('type', 'cat')]\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('is_human', False), ('name', 'tom'), ('type', 'cat')]\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# 由于 jupyter 的环境处理, 请不要重复运行此单元格, 如果想再看一遍, 请重启 jupyter 后再全部运行\n",
|
||||||
|
"\n",
|
||||||
|
"# 错误的方式\n",
|
||||||
|
"lct.dicted_data[\"type\"] = \"cat\"\n",
|
||||||
|
"print(lct) # 将不会同步修改\n",
|
||||||
|
"\n",
|
||||||
|
"# 不推荐, 但可用的方式\n",
|
||||||
|
"lct.dicted_data[\"type\"] = \"cat\"\n",
|
||||||
|
"lct._sync_based_on_dict()\n",
|
||||||
|
"print(lct)\n",
|
||||||
|
"\n",
|
||||||
|
"# 推荐方式\n",
|
||||||
|
"lct[\"is_human\"] = False\n",
|
||||||
|
"print(lct)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "2337d113",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### data 属性与修改方式\n",
|
||||||
|
"data 属性是一个列表, 它自动同步来自 Lict 对象操作的修改.\n",
|
||||||
|
"一个注意事项: 不要直接修改 data, 这将不会触发同步 hook, 并且可能破坏排序.\n",
|
||||||
|
"如果你一定要这样做, 请在完事后手动运行同步 hook 和 sort, 此处不演示.\n",
|
||||||
|
"推荐的修改方式是直接把 lct 当作一个列表, 且避免使用索引修改"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 11,
|
||||||
|
"id": "0ab442d4",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"{'age': 12, 'enemy': 'jerry', 'is_human': False, 'name': 'tom', 'type': 'cat', 'enemy_2': 'spike'}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"# 由于 jupyter 的环境处理, 请不要重复运行此单元格, 如果想再看一遍, 请重启 jupyter 后再全部运行\n",
|
||||||
|
"\n",
|
||||||
|
"# 唯一推荐方式\n",
|
||||||
|
"lct.append((\"enemy_2\", \"spike\"))\n",
|
||||||
|
"print(lct.dicted_data)"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "a3383f59",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"### 多面手\n",
|
||||||
|
"Lict 有一些很酷的功能\n",
|
||||||
|
"详情请看源文件\n",
|
||||||
|
"此处是一些例子"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 12,
|
||||||
|
"id": "f3ca752f",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('enemy_2', 'spike'), ('is_human', False), ('name', 'tom'), ('type', 'cat')]\n",
|
||||||
|
"{'age': 12, 'enemy': 'jerry', 'is_human': False, 'name': 'tom', 'type': 'cat', 'enemy_2': 'spike'}\n",
|
||||||
|
"------\n",
|
||||||
|
"('age', 12)\n",
|
||||||
|
"('enemy', 'jerry')\n",
|
||||||
|
"('enemy_2', 'spike')\n",
|
||||||
|
"('is_human', False)\n",
|
||||||
|
"('name', 'tom')\n",
|
||||||
|
"('type', 'cat')\n",
|
||||||
|
"6\n",
|
||||||
|
"('type', 'cat')\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('enemy_2', 'spike'), ('is_human', False), ('name', 'tom')]\n",
|
||||||
|
"('name', 'tom')\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('enemy_2', 'spike'), ('is_human', False)]\n",
|
||||||
|
"('is_human', False)\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry'), ('enemy_2', 'spike')]\n",
|
||||||
|
"('enemy_2', 'spike')\n",
|
||||||
|
"[('age', 12), ('enemy', 'jerry')]\n",
|
||||||
|
"('enemy', 'jerry')\n",
|
||||||
|
"[('age', 12)]\n",
|
||||||
|
"('age', 12)\n",
|
||||||
|
"[]\n"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"text/plain": [
|
||||||
|
"Ellipsis"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"execution_count": 12,
|
||||||
|
"metadata": {},
|
||||||
|
"output_type": "execute_result"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"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",
|
||||||
|
"for i in lct:\n",
|
||||||
|
" print(i)\n",
|
||||||
|
"print(len(lct))\n",
|
||||||
|
"while len(lct) > 0:\n",
|
||||||
|
" print(lct.pop())\n",
|
||||||
|
" print(lct)\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",
|
||||||
|
"..."
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "markdown",
|
||||||
|
"id": "2d6d3483",
|
||||||
|
"metadata": {},
|
||||||
|
"source": [
|
||||||
|
"关爱环境 从你我做起"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 13,
|
||||||
|
"id": "773bf99c",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [],
|
||||||
|
"source": [
|
||||||
|
"!rm -rf test_new_repo\n",
|
||||||
|
"!rm -rf heurams.log*"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cell_type": "code",
|
||||||
|
"execution_count": 14,
|
||||||
|
"id": "8645c5a2",
|
||||||
|
"metadata": {},
|
||||||
|
"outputs": [
|
||||||
|
{
|
||||||
|
"name": "stdout",
|
||||||
|
"output_type": "stream",
|
||||||
|
"text": [
|
||||||
|
"{ 'content': '君臣/固守/以窥/周室,/',\n",
|
||||||
|
" 'delimiter': '/',\n",
|
||||||
|
" 'keyword_note': {'窥': '窥视'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '君臣牢固地守卫着,借以窥视周王室的权力,',\n",
|
||||||
|
" 'tts_text': '君臣固守以窥周室,'}\n",
|
||||||
|
"{ 'SM-2': { 'efactor': 2.5,\n",
|
||||||
|
" 'interval': 1,\n",
|
||||||
|
" 'is_activated': 1,\n",
|
||||||
|
" 'last_date': 20454,\n",
|
||||||
|
" 'last_modify': 1767274438.752494,\n",
|
||||||
|
" 'next_date': 20455,\n",
|
||||||
|
" 'real_rept': 1,\n",
|
||||||
|
" 'rept': 0}}\n",
|
||||||
|
"{ 'content': '秦孝公/据/崤函/之固/, 拥/雍州/之地,/',\n",
|
||||||
|
" 'delimiter': '/',\n",
|
||||||
|
" 'keyword_note': {'崤函': '崤山和函谷关', '据': '占据', '雍州': '古代九州之一'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,',\n",
|
||||||
|
" 'tts_text': '秦孝公据崤函之固, 拥雍州之地,'}\n",
|
||||||
|
"{ 'SM-2': { 'efactor': 2.5,\n",
|
||||||
|
" 'interval': 1,\n",
|
||||||
|
" 'is_activated': 1,\n",
|
||||||
|
" 'last_date': 20454,\n",
|
||||||
|
" 'last_modify': 1767274438.7534873,\n",
|
||||||
|
" 'next_date': 20455,\n",
|
||||||
|
" 'real_rept': 1,\n",
|
||||||
|
" 'rept': 0}}\n",
|
||||||
|
"{ 'algodata': [ ( '君臣固守以窥周室,',\n",
|
||||||
|
" { 'SM-2': { 'efactor': 2.5,\n",
|
||||||
|
" 'interval': 1,\n",
|
||||||
|
" 'is_activated': 1,\n",
|
||||||
|
" 'last_date': 20454,\n",
|
||||||
|
" 'last_modify': 1767274438.752494,\n",
|
||||||
|
" 'next_date': 20455,\n",
|
||||||
|
" 'real_rept': 1,\n",
|
||||||
|
" 'rept': 0}}),\n",
|
||||||
|
" ( '秦孝公据崤函之固, 拥雍州之地,',\n",
|
||||||
|
" { 'SM-2': { 'efactor': 2.5,\n",
|
||||||
|
" 'interval': 1,\n",
|
||||||
|
" 'is_activated': 1,\n",
|
||||||
|
" 'last_date': 20454,\n",
|
||||||
|
" 'last_modify': 1767274438.7534873,\n",
|
||||||
|
" 'next_date': 20455,\n",
|
||||||
|
" 'real_rept': 1,\n",
|
||||||
|
" 'rept': 0}})],\n",
|
||||||
|
" 'manifest': { 'author': '__heurams__',\n",
|
||||||
|
" 'desc': '高考古诗文: 过秦论',\n",
|
||||||
|
" 'title': '测试单元: 过秦论'},\n",
|
||||||
|
" 'payload': [ ( '君臣固守以窥周室,',\n",
|
||||||
|
" { 'content': '君臣/固守/以窥/周室,/',\n",
|
||||||
|
" 'keyword_note': {'窥': '窥视'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '君臣牢固地守卫着,借以窥视周王室的权力,'}),\n",
|
||||||
|
" ( '秦孝公据崤函之固, 拥雍州之地,',\n",
|
||||||
|
" { 'content': '秦孝公/据/崤函/之固/, 拥/雍州/之地,/',\n",
|
||||||
|
" 'keyword_note': { '崤函': '崤山和函谷关',\n",
|
||||||
|
" '据': '占据',\n",
|
||||||
|
" '雍州': '古代九州之一'},\n",
|
||||||
|
" 'note': [],\n",
|
||||||
|
" 'translation': '秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,'})],\n",
|
||||||
|
" 'schedule': { 'phases': { 'final_review': [ ['FillBlank', '0.7'],\n",
|
||||||
|
" ['SelectMeaning', '0.7'],\n",
|
||||||
|
" ['Recognition', '1.0']],\n",
|
||||||
|
" 'quick_review': [ ['FillBlank', '1.0'],\n",
|
||||||
|
" ['SelectMeaning', '0.5'],\n",
|
||||||
|
" ['Recognition', '1.0']],\n",
|
||||||
|
" 'recognition': [['Recognition', '1.0']]},\n",
|
||||||
|
" 'schedule': [ 'quick_review',\n",
|
||||||
|
" 'recognition',\n",
|
||||||
|
" 'final_review']},\n",
|
||||||
|
" 'source': PosixPath('test_repo'),\n",
|
||||||
|
" 'typedef': { 'annotation': { 'content': '内容',\n",
|
||||||
|
" 'delimiter': '分隔符',\n",
|
||||||
|
" 'keyword_note': '关键词翻译',\n",
|
||||||
|
" 'note': '笔记',\n",
|
||||||
|
" 'translation': '语句翻译',\n",
|
||||||
|
" 'tts_text': '文本转语音文本'},\n",
|
||||||
|
" 'common': { 'delimiter': '/',\n",
|
||||||
|
" 'tts_text': \"eval:payload['content'].replace('/', \"\n",
|
||||||
|
" \"'')\"},\n",
|
||||||
|
" 'puzzles': { 'FillBlank': { '__hint__': '',\n",
|
||||||
|
" '__origin__': 'cloze',\n",
|
||||||
|
" 'delimiter': \"eval:metadata['formation']['delimiter']\",\n",
|
||||||
|
" 'min_denominator': \"eval:default['cloze']['min_denominator']\",\n",
|
||||||
|
" 'text': \"eval:payload['content']\"},\n",
|
||||||
|
" 'Recognition': { '__hint__': '',\n",
|
||||||
|
" '__origin__': 'recognition',\n",
|
||||||
|
" 'primary': \"eval:payload['content']\",\n",
|
||||||
|
" 'secondary': [ \"eval:payload['keyword_note']\",\n",
|
||||||
|
" \"eval:payload['note']\"],\n",
|
||||||
|
" 'top_dim': [ \"eval:payload['translation']\"]},\n",
|
||||||
|
" 'SelectMeaning': { '__hint__': \"eval:payload['content']\",\n",
|
||||||
|
" '__origin__': 'mcq',\n",
|
||||||
|
" 'jammer': \"eval:list(payload['keyword_note'].values())\",\n",
|
||||||
|
" 'mapping': \"eval:payload['keyword_note']\",\n",
|
||||||
|
" 'max_riddles_num': \"eval:default['mcq']['max_riddles_num']\",\n",
|
||||||
|
" 'prefix': '选择正确项: ',\n",
|
||||||
|
" 'primary': \"eval:payload['content']\"}},\n",
|
||||||
|
" '古文句': {}}}\n"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"source": [
|
||||||
|
"repo = repolib.Repo.create_from_repodir(Path(\"./test_repo\"))\n",
|
||||||
|
"for i in repo.ident_index:\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",
|
||||||
|
" print(repr(e))\n",
|
||||||
|
"print(repo)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"metadata": {
|
||||||
|
"kernelspec": {
|
||||||
|
"display_name": "Python 3",
|
||||||
|
"language": "python",
|
||||||
|
"name": "python3"
|
||||||
|
},
|
||||||
|
"language_info": {
|
||||||
|
"codemirror_mode": {
|
||||||
|
"name": "ipython",
|
||||||
|
"version": 3
|
||||||
|
},
|
||||||
|
"file_extension": ".py",
|
||||||
|
"mimetype": "text/x-python",
|
||||||
|
"name": "python",
|
||||||
|
"nbconvert_exporter": "python",
|
||||||
|
"pygments_lexer": "ipython3",
|
||||||
|
"version": "3.13.11"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nbformat": 4,
|
||||||
|
"nbformat_minor": 5
|
||||||
|
}
|
||||||
54
examples/simplemem.py
Normal file
54
examples/simplemem.py
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
e = pt.Electron.create_on_electonic_data(
|
||||||
|
electronic_data=repo.electronic_data_lict.get_itemic_unit(i)
|
||||||
|
)
|
||||||
|
print(n)
|
||||||
|
input()
|
||||||
|
a = pt.Atom(n, e, repo.orbitic_data)
|
||||||
|
alist.append(a)
|
||||||
|
#e.activate()
|
||||||
|
#e.revisor(5, True)
|
||||||
|
print(repr(a))
|
||||||
|
# print(repr(e))
|
||||||
|
print(repo)
|
||||||
|
input()
|
||||||
|
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)
|
||||||
1
examples/test_repo/algodata.json
Normal file
1
examples/test_repo/algodata.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{}
|
||||||
3
examples/test_repo/manifest.toml
Normal file
3
examples/test_repo/manifest.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
title = "测试单元: 过秦论"
|
||||||
|
author = "__heurams__"
|
||||||
|
desc = "高考古诗文: 过秦论"
|
||||||
11
examples/test_repo/payload.toml
Normal file
11
examples/test_repo/payload.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
["秦孝公据崤函之固, 拥雍州之地,"]
|
||||||
|
note = []
|
||||||
|
content = "秦孝公/据/崤函/之固/, 拥/雍州/之地,/"
|
||||||
|
translation = "秦孝公占据着崤山和函谷关的险固地势,拥有雍州的土地,"
|
||||||
|
keyword_note = {"据"="占据", "崤函"="崤山和函谷关", "雍州"="古代九州之一"}
|
||||||
|
|
||||||
|
["君臣固守以窥周室,"]
|
||||||
|
note = []
|
||||||
|
content = "君臣/固守/以窥/周室,/"
|
||||||
|
translation = "君臣牢固地守卫着,借以窥视周王室的权力,"
|
||||||
|
keyword_note = {"窥"="窥视"}
|
||||||
5
examples/test_repo/schedule.toml
Normal file
5
examples/test_repo/schedule.toml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
schedule = ["quick_review", "recognition", "final_review"]
|
||||||
|
[phases]
|
||||||
|
quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["Recognition", "1.0"]]
|
||||||
|
recognition = [["Recognition", "1.0"]]
|
||||||
|
final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["Recognition", "1.0"]]
|
||||||
17
examples/test_repo/typedef.toml
Normal file
17
examples/test_repo/typedef.toml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[annotation]
|
||||||
|
note = "笔记"
|
||||||
|
keyword_note = "关键词翻译"
|
||||||
|
translation = "语句翻译"
|
||||||
|
delimiter = "分隔符"
|
||||||
|
content = "内容"
|
||||||
|
tts_text = "文本转语音文本"
|
||||||
|
|
||||||
|
[common]
|
||||||
|
delimiter = "/"
|
||||||
|
tts_text = "eval:payload['content'].replace('/', '')"
|
||||||
|
|
||||||
|
[common.puzzles] # 谜题定义
|
||||||
|
# 我们称 "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 = "选择正确项: " }
|
||||||
|
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:payload['content']", delimiter = "eval:nucleon['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
||||||
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "heurams"
|
name = "heurams"
|
||||||
version = "0.4.3"
|
version = "0.5.0"
|
||||||
description = "Heuristic Assisted Memory Scheduler"
|
description = "Heuristic Assisted Memory Scheduler"
|
||||||
license = {file = "LICENSE"}
|
license = {file = "LICENSE"}
|
||||||
classifiers = [
|
classifiers = [
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
from contextvars import ContextVar
|
from contextvars import ContextVar
|
||||||
|
import shutil
|
||||||
|
|
||||||
from heurams.services.config import ConfigFile
|
from heurams.services.config import ConfigFile
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
@@ -14,31 +15,39 @@ from heurams.services.logger import get_logger
|
|||||||
# 数据文件路径规定: 以运行目录为准
|
# 数据文件路径规定: 以运行目录为准
|
||||||
|
|
||||||
rootdir = pathlib.Path(__file__).parent
|
rootdir = pathlib.Path(__file__).parent
|
||||||
print(f"rootdir: {rootdir}")
|
print(f"项目根目录: {rootdir}")
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
logger.debug(f"项目根目录: {rootdir}")
|
logger.debug(f"项目根目录: {rootdir}")
|
||||||
workdir = pathlib.Path.cwd()
|
workdir = pathlib.Path.cwd()
|
||||||
print(f"workdir: {workdir}")
|
print(f"工作目录: {workdir}")
|
||||||
logger.debug(f"工作目录: {workdir}")
|
logger.debug(f"工作目录: {workdir}")
|
||||||
config_var: ContextVar[ConfigFile] = ContextVar(
|
|
||||||
"config_var", default=ConfigFile(rootdir / "default" / "config" / "config.toml")
|
if pathlib.Path(workdir / "data" / "config" / "config_dev.toml").exists():
|
||||||
)
|
|
||||||
try:
|
|
||||||
config_var: ContextVar[ConfigFile] = ContextVar(
|
|
||||||
"config_var", default=ConfigFile(workdir / "config" / "config.toml")
|
|
||||||
) # 配置文件
|
|
||||||
print("已加载自定义用户配置")
|
|
||||||
logger.info("已加载自定义用户配置, 路径: %s", workdir / "config" / "config.toml")
|
|
||||||
except Exception as e:
|
|
||||||
print("未能加载自定义用户配置")
|
|
||||||
logger.warning("未能加载自定义用户配置, 错误: %s", e)
|
|
||||||
if pathlib.Path(workdir / "config" / "config_dev.toml").exists():
|
|
||||||
print("使用开发设置")
|
print("使用开发设置")
|
||||||
logger.debug("使用开发设置")
|
logger.debug("使用开发设置")
|
||||||
config_var: ContextVar[ConfigFile] = ContextVar(
|
config_var: ContextVar[ConfigFile] = ContextVar(
|
||||||
"config_var", default=ConfigFile(workdir / "config" / "config_dev.toml")
|
"config_var",
|
||||||
|
default=ConfigFile(workdir / "data" / "config" / "config_dev.toml"),
|
||||||
)
|
)
|
||||||
# runtime_var: ContextVar = ContextVar('runtime_var', default=dict()) # 运行时共享数据
|
else:
|
||||||
|
try:
|
||||||
|
config_var: ContextVar[ConfigFile] = ContextVar(
|
||||||
|
"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",
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
config_var: ContextVar[ConfigFile] = ContextVar(
|
||||||
|
"config_var",
|
||||||
|
default=ConfigFile(workdir / "data" / "config" / "config.toml"),
|
||||||
|
) # 配置文件
|
||||||
|
|
||||||
|
|
||||||
class ConfigContext:
|
class ConfigContext:
|
||||||
|
|||||||
@@ -31,12 +31,7 @@ max_riddles_num = 2
|
|||||||
min_denominator = 3
|
min_denominator = 3
|
||||||
|
|
||||||
[paths] # 相对于配置文件的 ".." (即工作目录) 而言 或绝对路径
|
[paths] # 相对于配置文件的 ".." (即工作目录) 而言 或绝对路径
|
||||||
nucleon_dir = "./data/nucleon"
|
data = "./data"
|
||||||
electron_dir = "./data/electron"
|
|
||||||
global_dir = "./data/global" # 全局数据路径, SM-15 等算法需要
|
|
||||||
orbital_dir = "./data/orbital"
|
|
||||||
cache_dir = "./data/cache"
|
|
||||||
template_dir = "./data/template"
|
|
||||||
|
|
||||||
[services] # 定义服务到提供者的映射
|
[services] # 定义服务到提供者的映射
|
||||||
audio = "playsound" # 可选项: playsound(通用), termux(仅用于支持 Android Termux), mpg123(TODO)
|
audio = "playsound" # 可选项: playsound(通用), termux(仅用于支持 Android Termux), mpg123(TODO)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ from heurams.services.logger import get_logger
|
|||||||
|
|
||||||
from .screens.about import AboutScreen
|
from .screens.about import AboutScreen
|
||||||
from .screens.dashboard import DashboardScreen
|
from .screens.dashboard import DashboardScreen
|
||||||
from .screens.nucreator import NucleonCreatorScreen
|
|
||||||
from .screens.precache import PrecachingScreen
|
from .screens.precache import PrecachingScreen
|
||||||
|
from .screens.repocreator import RepoCreatorScreen
|
||||||
from .screens.synctool import SyncScreen
|
from .screens.synctool import SyncScreen
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
@@ -17,9 +17,9 @@ def environment_check():
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
logger.debug("检查环境路径")
|
logger.debug("检查环境路径")
|
||||||
|
subdir = ['cache/voice', 'repo', 'global', 'config']
|
||||||
for i in config_var.get()["paths"].values():
|
for i in subdir:
|
||||||
i = Path(i)
|
i = Path(config_var.get()['paths']['data']) / i
|
||||||
if not i.exists():
|
if not i.exists():
|
||||||
logger.info("创建目录: %s", i)
|
logger.info("创建目录: %s", i)
|
||||||
print(f"创建 {i}")
|
print(f"创建 {i}")
|
||||||
@@ -39,13 +39,13 @@ class HeurAMSApp(App):
|
|||||||
("d", "toggle_dark", "切换色调"),
|
("d", "toggle_dark", "切换色调"),
|
||||||
("1", "app.push_screen('dashboard')", "仪表盘"),
|
("1", "app.push_screen('dashboard')", "仪表盘"),
|
||||||
("2", "app.push_screen('precache_all')", "缓存管理器"),
|
("2", "app.push_screen('precache_all')", "缓存管理器"),
|
||||||
("3", "app.push_screen('nucleon_creator')", "创建新单元"),
|
("3", "app.push_screen('repo_creator')", "创建新仓库"),
|
||||||
# ("4", "app.push_screen('synctool')", "同步工具"),
|
# ("4", "app.push_screen('synctool')", "同步工具"),
|
||||||
("0", "app.push_screen('about')", "版本信息"),
|
("0", "app.push_screen('about')", "版本信息"),
|
||||||
]
|
]
|
||||||
SCREENS = {
|
SCREENS = {
|
||||||
"dashboard": DashboardScreen,
|
"dashboard": DashboardScreen,
|
||||||
"nucleon_creator": NucleonCreatorScreen,
|
"repo_creator": RepoCreatorScreen,
|
||||||
"precache_all": PrecachingScreen,
|
"precache_all": PrecachingScreen,
|
||||||
"synctool": SyncScreen,
|
"synctool": SyncScreen,
|
||||||
"about": AboutScreen,
|
"about": AboutScreen,
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from heurams.services.logger import get_logger
|
|||||||
|
|
||||||
from .screens.about import AboutScreen
|
from .screens.about import AboutScreen
|
||||||
from .screens.dashboard import DashboardScreen
|
from .screens.dashboard import DashboardScreen
|
||||||
from .screens.nucreator import NucleonCreatorScreen
|
|
||||||
from .screens.precache import PrecachingScreen
|
from .screens.precache import PrecachingScreen
|
||||||
|
from .screens.repocreator import RepoCreatorScreen
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
"""关于界面"""
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
from textual.containers import ScrollableContainer
|
from textual.containers import ScrollableContainer
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
@@ -19,7 +20,7 @@ class AboutScreen(Screen):
|
|||||||
|
|
||||||
版本 {version.ver} {version.stage.capitalize()}
|
版本 {version.ver} {version.stage.capitalize()}
|
||||||
|
|
||||||
开发代号: {version.codename.capitalize()}
|
开发代号: {version.codename.capitalize()} {version.codename_cn}
|
||||||
|
|
||||||
一个基于启发式算法的开放源代码记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
|
一个基于启发式算法的开放源代码记忆调度器, 旨在帮助用户更高效地进行记忆工作与学习规划.
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
#!/usr/bin/env python3
|
"""仪表盘界面"""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
from textual.containers import ScrollableContainer
|
from textual.containers import ScrollableContainer
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import (Button, Footer, Header, Label, ListItem, ListView,
|
from textual.widgets import Button, Footer, Header, Label, ListItem, ListView, Static
|
||||||
Static)
|
|
||||||
|
|
||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
import heurams.services.version as version
|
import heurams.services.version as version
|
||||||
from heurams.context import *
|
from heurams.context import *
|
||||||
from heurams.kernel.particles import *
|
from heurams.kernel.particles import *
|
||||||
|
from heurams.kernel.repolib import *
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .about import AboutScreen
|
from .about import AboutScreen
|
||||||
@@ -31,130 +32,102 @@ class DashboardScreen(Screen):
|
|||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.nextdates = {}
|
self.repostat = {}
|
||||||
self.texts = {}
|
self.title2dirname = {}
|
||||||
self.stay_enabled = {}
|
self.title2repo = {}
|
||||||
|
self._load_data()
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
"""组合界面组件"""
|
"""组合界面组件"""
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
yield ScrollableContainer(
|
yield ScrollableContainer(
|
||||||
Label('欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
|
Label('欢迎使用 "潜进" 启发式辅助记忆调度器', classes="title-label"),
|
||||||
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()}"),
|
Label(f"当前 UNIX 日时间戳: {timer.get_daystamp()} (UTC+{config_var.get()["timezone_offset"] / 3600})"),
|
||||||
Label(f'时区修正: UTC+{config_var.get()["timezone_offset"] / 3600}'),
|
Label(f"全局算法设置: {config_var.get()['algorithm']['default']}"),
|
||||||
Label(f"使用算法: {config_var.get()['algorithm']['default']}"),
|
Label("选择待学习或待修改的项目:", classes="title-label"),
|
||||||
Label("选择待学习或待修改的记忆单元集:", classes="title-label"),
|
ListView(id="repo-list", classes="repo-list-view"),
|
||||||
ListView(id="union-list", classes="union-list-view"),
|
Label(f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} '),
|
||||||
Label(
|
|
||||||
f'"潜进" 启发式辅助记忆调度器 | 版本 {version.ver} '
|
|
||||||
f"{version.codename.capitalize()} 2025"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
def analyser(self, filename: str) -> dict:
|
def _load_data(self):
|
||||||
"""分析文件状态以生成显示文本
|
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)
|
||||||
|
|
||||||
Args:
|
def _analyse_repo(self, repo: Repo):
|
||||||
filename: 要分析的文件名
|
dirname = repo.source.name # type: ignore
|
||||||
|
title = repo.manifest["title"]
|
||||||
Returns:
|
|
||||||
dict: 包含显示文本的字典,键为行号
|
|
||||||
"""
|
|
||||||
from heurams.kernel.particles.loader import load_electron, load_nucleon
|
|
||||||
|
|
||||||
result = {}
|
|
||||||
filestem = pathlib.Path(filename).stem
|
|
||||||
|
|
||||||
# 构建电子文件路径
|
|
||||||
electron_dir = config_var.get()["paths"]["electron_dir"]
|
|
||||||
electron_file_path = pathlib.Path(electron_dir) / f"{filestem}.json"
|
|
||||||
|
|
||||||
logger.debug(f"电子文件路径: {electron_file_path}")
|
|
||||||
|
|
||||||
# 确保电子文件存在
|
|
||||||
if not electron_file_path.exists():
|
|
||||||
electron_file_path.touch()
|
|
||||||
electron_file_path.write_text("{}")
|
|
||||||
|
|
||||||
# 加载电子数据
|
|
||||||
electron_dict = load_electron(path=electron_file_path)
|
|
||||||
logger.debug(f"电子数据: {electron_dict}")
|
|
||||||
|
|
||||||
# 分析电子状态
|
|
||||||
is_due = 0
|
is_due = 0
|
||||||
is_activated = 0
|
unit_sum = len(repo)
|
||||||
|
activated_sum = 0
|
||||||
nextdate = 0x3F3F3F3F
|
nextdate = 0x3F3F3F3F
|
||||||
|
is_unfinished = unit_sum > activated_sum
|
||||||
for electron in electron_dict.values():
|
for i in repo.ident_index:
|
||||||
logger.debug(f"{electron}, 是否到期: {electron.is_due()}")
|
nucleon = pt.Nucleon.create_on_nucleonic_data(
|
||||||
|
nucleonic_data=repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||||
if electron.is_due():
|
)
|
||||||
is_due = 1
|
electron = pt.Electron.create_on_electonic_data(
|
||||||
|
electronic_data=repo.electronic_data_lict.get_itemic_unit(i)
|
||||||
|
)
|
||||||
if electron.is_activated():
|
if electron.is_activated():
|
||||||
is_activated = 1
|
activated_sum += 1
|
||||||
nextdate = min(nextdate, electron.nextdate())
|
if electron.is_due():
|
||||||
|
is_due = 1
|
||||||
# 检查是否需要更多复习
|
nextdate = min(nextdate, electron.nextdate())
|
||||||
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
|
if is_unfinished:
|
||||||
nucleon_path = pathlib.Path(nucleon_dir) / f"{filestem}.toml"
|
nextdate = min(nextdate, timer.get_daystamp())
|
||||||
nucleon_count = len(load_nucleon(nucleon_path))
|
need_to_study = is_due or is_unfinished
|
||||||
electron_count = len(electron_dict)
|
prompt = f"{title}\0\n 进度: {activated_sum}/{unit_sum}\n {"需要学习" if need_to_study else "无需操作"}"
|
||||||
is_more = not (electron_count >= nucleon_count)
|
stat = {
|
||||||
|
"is_due": is_due,
|
||||||
logger.debug(f"是否需要更多复习: {is_more}")
|
"unit_sum": unit_sum,
|
||||||
|
"title": title,
|
||||||
# 更新状态
|
"activated_sum": activated_sum,
|
||||||
self.nextdates[filename] = nextdate
|
"nextdate": nextdate,
|
||||||
self.stay_enabled[filename] = is_due or is_more
|
"is_unfinished": is_unfinished,
|
||||||
|
"need_to_study": need_to_study,
|
||||||
# 构建返回结果
|
"prompt": prompt,
|
||||||
result[0] = f"{filename}\0"
|
"dirname": dirname,
|
||||||
|
}
|
||||||
if not is_activated:
|
self.repostat[dirname] = stat
|
||||||
result[1] = " 尚未激活"
|
self.title2dirname[title] = dirname
|
||||||
else:
|
self.title2repo[title] = repo
|
||||||
status_text = "需要复习" if is_due else "当前无需复习"
|
|
||||||
result[1] = f"下一次复习: {nextdate}\n{status_text}"
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
"""挂载组件时初始化"""
|
"""挂载组件时初始化"""
|
||||||
union_list_widget = self.query_one("#union-list", ListView)
|
repo_list_widget = self.query_one("#repo-list", ListView)
|
||||||
probe = probe_all(0)
|
|
||||||
|
|
||||||
# 分析所有文件
|
|
||||||
for file in probe["nucleon"]:
|
|
||||||
self.texts[file] = self.analyser(file)
|
|
||||||
|
|
||||||
# 按下次复习时间排序
|
# 按下次复习时间排序
|
||||||
nucleon_files = sorted(
|
repodirs = sorted(
|
||||||
probe["nucleon"],
|
self.repo_dirs,
|
||||||
key=lambda f: self.nextdates[f],
|
key=lambda f: self.repostat[f.name]["nextdate"],
|
||||||
reverse=True,
|
reverse=True,
|
||||||
)
|
)
|
||||||
|
repotitles = map(lambda f: self.repostat[f.name]["title"], repodirs)
|
||||||
# 填充列表
|
# 填充列表
|
||||||
if not probe["nucleon"]:
|
if not repodirs:
|
||||||
union_list_widget.append(
|
repo_list_widget.append(
|
||||||
ListItem(
|
ListItem(
|
||||||
Static(
|
Static(
|
||||||
"在 ./nucleon/ 中未找到任何内容源数据文件。\n"
|
"在 ./data/repo/ 中未找到任何仓库.\n"
|
||||||
"请放置文件后重启应用,或者新建空的单元集。"
|
"请导入仓库后重启应用, 或者新建空的仓库."
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
union_list_widget.disabled = True
|
repo_list_widget.disabled = True
|
||||||
return
|
return
|
||||||
|
|
||||||
for file in nucleon_files:
|
for repotitle in repotitles:
|
||||||
text = self.texts[file]
|
prompt = self.repostat[self.title2dirname[repotitle]]["prompt"]
|
||||||
list_item = ListItem(Label(f"{text[0]}\n{text[1]}"))
|
list_item = ListItem(Label(prompt))
|
||||||
union_list_widget.append(list_item)
|
repo_list_widget.append(list_item)
|
||||||
|
|
||||||
if not self.stay_enabled[file]:
|
# if not self.stay_enabled[repodir]:
|
||||||
list_item.disabled = True
|
# list_item.disabled = True
|
||||||
|
|
||||||
def on_list_view_selected(self, event) -> None:
|
def on_list_view_selected(self, event) -> None:
|
||||||
"""处理列表项选择事件"""
|
"""处理列表项选择事件"""
|
||||||
@@ -164,43 +137,19 @@ class DashboardScreen(Screen):
|
|||||||
selected_label = event.item.query_one(Label)
|
selected_label = event.item.query_one(Label)
|
||||||
label_text = str(selected_label.renderable)
|
label_text = str(selected_label.renderable)
|
||||||
|
|
||||||
if "未找到任何 .toml 文件" in label_text:
|
if "未找到任何仓库" in label_text:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 提取文件名
|
# 提取文件名
|
||||||
selected_filename = pathlib.Path(label_text.partition("\0")[0].replace("*", ""))
|
selected_repotitle = label_text.partition("\0")[0].replace("*", "")
|
||||||
|
selected_repo = self.title2repo[label_text.partition("\0")[0].replace("*", "")]
|
||||||
# 构建文件路径
|
|
||||||
nucleon_dir = config_var.get()["paths"]["nucleon_dir"]
|
|
||||||
electron_dir = config_var.get()["paths"]["electron_dir"]
|
|
||||||
|
|
||||||
nucleon_file_path = pathlib.Path(nucleon_dir) / selected_filename
|
|
||||||
electron_file_path = (
|
|
||||||
pathlib.Path(electron_dir) / f"{selected_filename.stem}.json"
|
|
||||||
)
|
|
||||||
|
|
||||||
# 跳转到准备屏幕
|
# 跳转到准备屏幕
|
||||||
self.app.push_screen(PreparationScreen(nucleon_file_path, electron_file_path))
|
self.app.push_screen(
|
||||||
|
PreparationScreen(
|
||||||
def on_button_pressed(self, event) -> None:
|
selected_repo, self.repostat[self.title2dirname[selected_repotitle]]
|
||||||
"""处理按钮点击事件"""
|
)
|
||||||
button_id = event.button.id
|
)
|
||||||
|
|
||||||
if button_id == "new_nucleon_button":
|
|
||||||
from .nucreator import NucleonCreatorScreen
|
|
||||||
|
|
||||||
new_screen = NucleonCreatorScreen()
|
|
||||||
self.app.push_screen(new_screen)
|
|
||||||
|
|
||||||
elif button_id == "precache_all_button":
|
|
||||||
from .precache import PrecachingScreen
|
|
||||||
|
|
||||||
precache_screen = PrecachingScreen()
|
|
||||||
self.app.push_screen(precache_screen)
|
|
||||||
|
|
||||||
elif button_id == "about_button":
|
|
||||||
about_screen = AboutScreen()
|
|
||||||
self.app.push_screen(about_screen)
|
|
||||||
|
|
||||||
def action_quit_app(self) -> None:
|
def action_quit_app(self) -> None:
|
||||||
"""退出应用程序"""
|
"""退出应用程序"""
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
"""笔记界面"""
|
||||||
|
|||||||
1
src/heurams/interface/screens/memointegrity.py
Normal file
1
src/heurams/interface/screens/memointegrity.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
"""整体式记忆工作界面"""
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
"""队列式记忆工作界面"""
|
||||||
|
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
@@ -7,8 +8,8 @@ from textual.reactive import reactive
|
|||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Button, Footer, Header, Label, Static
|
from textual.widgets import Button, Footer, Header, Label, Static
|
||||||
|
|
||||||
|
import heurams.kernel.evaluators as pz
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as pz
|
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
from heurams.kernel.reactor import *
|
from heurams.kernel.reactor import *
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
@@ -27,7 +28,7 @@ logger = get_logger(__name__)
|
|||||||
class MemScreen(Screen):
|
class MemScreen(Screen):
|
||||||
BINDINGS = [
|
BINDINGS = [
|
||||||
("q", "pop_screen", "返回"),
|
("q", "pop_screen", "返回"),
|
||||||
# ("p", "prev", "复习上一个"),
|
("p", "prev", "查看上一个"),
|
||||||
("d", "toggle_dark", ""),
|
("d", "toggle_dark", ""),
|
||||||
("v", "play_voice", "朗读"),
|
("v", "play_voice", "朗读"),
|
||||||
("0,1,2,3", "app.push_screen('about')", ""),
|
("0,1,2,3", "app.push_screen('about')", ""),
|
||||||
@@ -39,67 +40,61 @@ class MemScreen(Screen):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
atoms: list,
|
phaser: Phaser,
|
||||||
name: str | None = None,
|
name = None,
|
||||||
id: str | None = None,
|
id = None,
|
||||||
classes: str | None = None,
|
classes = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.atoms = atoms
|
self.phaser = phaser
|
||||||
for i in self.atoms:
|
self.update_state()
|
||||||
i.do_eval()
|
|
||||||
self.phaser = Phaser(atoms)
|
|
||||||
# logger.debug(self.phaser.state)
|
|
||||||
self.procession: Procession = self.phaser.current_procession() # type: ignore
|
|
||||||
self.atom: pt.Atom = self.procession.current_atom
|
|
||||||
# logger.debug(self.phaser.state)
|
|
||||||
# self.procession.forward(1)
|
|
||||||
|
|
||||||
def on_mount(self):
|
|
||||||
self.load_puzzle()
|
|
||||||
pass
|
|
||||||
|
|
||||||
def puzzle_widget(self):
|
|
||||||
try:
|
|
||||||
logger.debug(self.phaser.state)
|
|
||||||
logger.debug(self.procession.cursor)
|
|
||||||
logger.debug(self.atom)
|
|
||||||
self.fission = Fission(self.atom, self.phaser.state)
|
|
||||||
puzzle_debug = next(self.fission.generate())
|
|
||||||
# logger.debug(puzzle_debug)
|
|
||||||
return shim.puzzle2widget[puzzle_debug["puzzle"]](
|
|
||||||
atom=self.atom, alia=puzzle_debug["alia"]
|
|
||||||
)
|
|
||||||
except (KeyError, StopIteration, AttributeError) as e:
|
|
||||||
logger.debug(f"调度展开出错: {e}")
|
|
||||||
return Static("无法生成谜题")
|
|
||||||
# logger.debug(shim.puzzle2widget[puzzle_debug["puzzle"]])
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
with ScrollableContainer():
|
with ScrollableContainer():
|
||||||
yield Label(self._get_progress_text(), id="progress")
|
yield Label(self._get_progress_text(), id="progress")
|
||||||
|
|
||||||
# self.mount(self.current_widget()) # type: ignore
|
|
||||||
yield ScrollableContainer(id="puzzle-container")
|
yield ScrollableContainer(id="puzzle-container")
|
||||||
# yield Button("重新学习此单元", id="re-recognize", variant="warning")
|
|
||||||
yield Footer()
|
yield Footer()
|
||||||
|
|
||||||
|
def update_state(self):
|
||||||
|
"""更新状态机"""
|
||||||
|
self.procession: Procession = self.phaser.current_procession() # type: ignore
|
||||||
|
self.atom: pt.Atom = self.procession.current_atom # type: ignore
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
self.mount_puzzle()
|
||||||
|
self.update_display()
|
||||||
|
|
||||||
|
def puzzle_widget(self):
|
||||||
|
try:
|
||||||
|
self.fission = self.procession.get_fission()
|
||||||
|
puzzle = self.fission.get_current_puzzle()
|
||||||
|
# logger.debug(puzzle_debug)
|
||||||
|
return shim.puzzle2widget[puzzle["puzzle"]]( # type: ignore
|
||||||
|
atom=self.atom, alia=puzzle["alia"] # type: ignore
|
||||||
|
)
|
||||||
|
except (KeyError, StopIteration, AttributeError) as e:
|
||||||
|
logger.debug(f"调度展开出错: {e}")
|
||||||
|
return Static(f"无法生成谜题 {e}")
|
||||||
|
# logger.debug(shim.puzzle2widget[puzzle_debug["puzzle"]])
|
||||||
|
|
||||||
def _get_progress_text(self):
|
def _get_progress_text(self):
|
||||||
return f"当前进度: {self.procession.process() + 1}/{self.procession.total_length()}"
|
return f"当前进度: {self.procession.process() + 1}/{self.procession.total_length()}"
|
||||||
|
|
||||||
def update_display(self):
|
def update_display(self):
|
||||||
|
"""更新进度显示"""
|
||||||
progress_widget = self.query_one("#progress")
|
progress_widget = self.query_one("#progress")
|
||||||
progress_widget.update(self._get_progress_text()) # type: ignore
|
progress_widget.update(self._get_progress_text()) # type: ignore
|
||||||
|
|
||||||
def load_puzzle(self):
|
def mount_puzzle(self):
|
||||||
self.atom: pt.Atom = self.procession.current_atom
|
"""挂载当前谜题组件"""
|
||||||
container = self.query_one("#puzzle-container")
|
container = self.query_one("#puzzle-container")
|
||||||
for i in container.children:
|
for i in container.children:
|
||||||
i.remove()
|
i.remove()
|
||||||
container.mount(self.puzzle_widget())
|
container.mount(self.puzzle_widget())
|
||||||
|
|
||||||
def load_finished_widget(self):
|
def mount_finished_widget(self):
|
||||||
|
"""挂载已完成组件"""
|
||||||
container = self.query_one("#puzzle-container")
|
container = self.query_one("#puzzle-container")
|
||||||
for i in container.children:
|
for i in container.children:
|
||||||
i.remove()
|
i.remove()
|
||||||
@@ -110,39 +105,6 @@ class MemScreen(Screen):
|
|||||||
def on_button_pressed(self, event):
|
def on_button_pressed(self, event):
|
||||||
event.stop()
|
event.stop()
|
||||||
|
|
||||||
def watch_rating(self, old_rating, new_rating) -> None:
|
|
||||||
if self.procession == 0:
|
|
||||||
return
|
|
||||||
if new_rating == -1:
|
|
||||||
return
|
|
||||||
forwards = 1 if new_rating >= 4 else 0
|
|
||||||
self.rating = -1
|
|
||||||
logger.debug(f"试图前进: {"允许" if forwards else "禁止"}")
|
|
||||||
if forwards:
|
|
||||||
ret = self.procession.forward(1)
|
|
||||||
if ret == 0: # 若结束了此次队列
|
|
||||||
self.procession = self.phaser.current_procession() # type: ignore
|
|
||||||
if self.procession == 0: # 若所有队列都结束了
|
|
||||||
logger.debug(f"记忆进程结束")
|
|
||||||
for i in self.atoms:
|
|
||||||
i: pt.Atom
|
|
||||||
i.revise()
|
|
||||||
i.persist("electron")
|
|
||||||
self.load_finished_widget()
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
logger.debug(f"建立新队列 {self.procession.phase}")
|
|
||||||
self.load_puzzle()
|
|
||||||
else: # 若不通过
|
|
||||||
self.procession.append()
|
|
||||||
self.update_display()
|
|
||||||
|
|
||||||
def action_quick_pass(self):
|
|
||||||
self.rating = 5
|
|
||||||
self.atom.minimize(5)
|
|
||||||
self.atom.registry["electron"].activate()
|
|
||||||
self.atom.lock(1)
|
|
||||||
|
|
||||||
def action_play_voice(self):
|
def action_play_voice(self):
|
||||||
self.run_worker(self.play_voice, exclusive=True, thread=True)
|
self.run_worker(self.play_voice, exclusive=True, thread=True)
|
||||||
|
|
||||||
@@ -153,23 +115,41 @@ class MemScreen(Screen):
|
|||||||
from heurams.services.audio_service import play_by_path
|
from heurams.services.audio_service import play_by_path
|
||||||
from heurams.services.hasher import get_md5
|
from heurams.services.hasher import get_md5
|
||||||
|
|
||||||
path = Path(config_var.get()["paths"]["cache_dir"])
|
path = Path(config_var.get()["paths"]['data']) / 'cache' / 'voice'
|
||||||
path = (
|
path = (
|
||||||
path
|
path
|
||||||
/ f"{get_md5(self.atom.registry['nucleon'].metadata["formation"]["tts_text"])}.wav"
|
/ f"{get_md5(self.atom.registry['nucleon']["tts_text"])}.wav"
|
||||||
)
|
)
|
||||||
if path.exists():
|
if path.exists():
|
||||||
play_by_path(path)
|
play_by_path(path)
|
||||||
else:
|
else:
|
||||||
from heurams.services.tts_service import convertor
|
from heurams.services.tts_service import convertor
|
||||||
|
|
||||||
convertor(
|
convertor(
|
||||||
self.atom.registry["nucleon"].metadata["formation"]["tts_text"], path
|
self.atom.registry["nucleon"]["tts_text"], path
|
||||||
)
|
)
|
||||||
play_by_path(path)
|
play_by_path(path)
|
||||||
|
|
||||||
def action_toggle_dark(self):
|
def watch_rating(self, old_rating, new_rating) -> None:
|
||||||
self.app.action_toggle_dark()
|
self.update_state() # 刷新状态
|
||||||
|
if self.procession == None: # 已经完成记忆
|
||||||
def action_pop_screen(self):
|
return
|
||||||
self.app.pop_screen()
|
if new_rating == -1: # 安全值
|
||||||
|
return
|
||||||
|
forwards = 1 if new_rating >= 4 else 0 # 准许前进
|
||||||
|
self.rating = -1
|
||||||
|
logger.debug(f"试图前进: {"允许" if forwards else "禁止"}")
|
||||||
|
if forwards:
|
||||||
|
ret = self.procession.forward(1)
|
||||||
|
if ret == 0: # 若结束了此次队列
|
||||||
|
self.update_state()
|
||||||
|
if self.procession == 0: # 若所有队列都结束了
|
||||||
|
logger.debug(f"记忆进程结束")
|
||||||
|
self.mount_finished_widget()
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
logger.debug(f"建立新队列 {self.procession.phase}")
|
||||||
|
self.update_state()
|
||||||
|
self.mount_puzzle()
|
||||||
|
else: # 若不通过
|
||||||
|
self.procession.append()
|
||||||
|
self.update_display()
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
"""缓存工具界面"""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
@@ -11,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):
|
||||||
"""预缓存音频文件屏幕
|
"""预缓存音频文件屏幕
|
||||||
@@ -37,12 +39,6 @@ class PrecachingScreen(Screen):
|
|||||||
self.precache_worker = None
|
self.precache_worker = None
|
||||||
self.cancel_flag = 0
|
self.cancel_flag = 0
|
||||||
self.desc = desc
|
self.desc = desc
|
||||||
for i in nucleons:
|
|
||||||
i: pt.Nucleon
|
|
||||||
atom = pt.Atom()
|
|
||||||
atom.link("nucleon", i)
|
|
||||||
atom.do_eval()
|
|
||||||
# print("完成 EVAL")
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
@@ -93,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"]["cache_dir"])
|
|
||||||
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():
|
||||||
@@ -109,10 +104,8 @@ class PrecachingScreen(Screen):
|
|||||||
|
|
||||||
def precache_by_nucleon(self, nucleon: pt.Nucleon):
|
def precache_by_nucleon(self, nucleon: pt.Nucleon):
|
||||||
"""依据 Nucleon 缓存"""
|
"""依据 Nucleon 缓存"""
|
||||||
# print(nucleon.metadata['formation']['tts_text'])
|
ret = self.precache_by_text(nucleon["tts_text"])
|
||||||
ret = self.precache_by_text(nucleon.metadata["formation"]["tts_text"])
|
|
||||||
return ret
|
return ret
|
||||||
# print(f"TTS 缓存: {nucleon.metadata['formation']['tts_text']}")
|
|
||||||
|
|
||||||
def precache_by_list(self, nucleons: list):
|
def precache_by_list(self, nucleons: list):
|
||||||
"""依据 Nucleons 列表缓存"""
|
"""依据 Nucleons 列表缓存"""
|
||||||
@@ -121,7 +114,7 @@ class PrecachingScreen(Screen):
|
|||||||
worker = get_current_worker()
|
worker = get_current_worker()
|
||||||
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
|
if worker and worker.is_cancelled: # 函数在worker中执行且已被取消
|
||||||
return False
|
return False
|
||||||
text = nucleon.metadata["formation"]["tts_text"]
|
text = nucleon["tts_text"]
|
||||||
# self.current_item = text[:30] + "..." if len(text) > 50 else text
|
# self.current_item = text[:30] + "..." if len(text) > 50 else text
|
||||||
# print(text)
|
# print(text)
|
||||||
self.processed += 1
|
self.processed += 1
|
||||||
@@ -151,38 +144,30 @@ class PrecachingScreen(Screen):
|
|||||||
# print(f"返回 {ret}")
|
# print(f"返回 {ret}")
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def precache_by_filepath(self, path: pathlib.Path):
|
|
||||||
"""预缓存单个文件的所有内容"""
|
|
||||||
lst = list()
|
|
||||||
for i in pt.load_nucleon(path):
|
|
||||||
lst.append(i[0])
|
|
||||||
return self.precache_by_list(lst)
|
|
||||||
|
|
||||||
def precache_all_files(self):
|
def precache_all_files(self):
|
||||||
"""预缓存所有文件"""
|
"""预缓存所有文件"""
|
||||||
from heurams.context import config_var, rootdir, workdir
|
from heurams.context import config_var, rootdir, workdir
|
||||||
|
from heurams.kernel.repolib import Repo
|
||||||
|
|
||||||
nucleon_path = pathlib.Path(config_var.get()["paths"]["nucleon_dir"])
|
repo_path = pathlib.Path(config_var.get()["paths"]["data"]) / "repo"
|
||||||
nucleon_files = [
|
repo_dirs = Repo.probe_vaild_repos_in_dir(repo_path)
|
||||||
f for f in nucleon_path.iterdir() if f.suffix == ".toml"
|
repos = map(Repo.create_from_repodir, repo_dirs)
|
||||||
] # TODO: 解耦合
|
|
||||||
|
|
||||||
# 计算总项目数
|
# 计算总项目数
|
||||||
self.total = 0
|
self.total = 0
|
||||||
nu = list()
|
nucleon_list = list()
|
||||||
for file in nucleon_files:
|
for repo in repos:
|
||||||
try:
|
try:
|
||||||
for i in pt.load_nucleon(file):
|
for i in repo.ident_index:
|
||||||
nu.append(i[0])
|
nucleon_list.append(
|
||||||
|
pt.Nucleon.create_on_nucleonic_data(
|
||||||
|
repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||||
|
)
|
||||||
|
)
|
||||||
except:
|
except:
|
||||||
continue
|
continue
|
||||||
self.total = len(nu)
|
self.total = len(nucleon_list)
|
||||||
for i in nu:
|
return self.precache_by_list(nucleon_list)
|
||||||
i: pt.Nucleon
|
|
||||||
atom = pt.Atom()
|
|
||||||
atom.link("nucleon", i)
|
|
||||||
atom.do_eval()
|
|
||||||
return self.precache_by_list(nu)
|
|
||||||
|
|
||||||
def on_button_pressed(self, event: Button.Pressed) -> None:
|
def on_button_pressed(self, event: Button.Pressed) -> None:
|
||||||
event.stop()
|
event.stop()
|
||||||
@@ -220,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"]["cache_dir"]}", ignore_errors=True
|
cache_dir, ignore_errors=True
|
||||||
)
|
)
|
||||||
self.update_status("已清空", "音频缓存已清空", 0)
|
self.update_status("已清空", "音频缓存已清空", 0)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
"""记忆准备界面"""
|
||||||
|
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
from textual.containers import ScrollableContainer
|
from textual.containers import ScrollableContainer
|
||||||
from textual.reactive import reactive
|
from textual.reactive import reactive
|
||||||
@@ -11,6 +12,7 @@ import heurams.services.hasher as hasher
|
|||||||
from heurams.context import *
|
from heurams.context import *
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
from heurams.kernel.repolib import *
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -28,24 +30,19 @@ class PreparationScreen(Screen):
|
|||||||
|
|
||||||
scheduled_num = reactive(config_var.get()["scheduled_num"])
|
scheduled_num = reactive(config_var.get()["scheduled_num"])
|
||||||
|
|
||||||
def __init__(self, nucleon_file: pathlib.Path, electron_file: pathlib.Path) -> None:
|
def __init__(self, repo: Repo, repostat: dict) -> None:
|
||||||
super().__init__(name=None, id=None, classes=None)
|
super().__init__(name=None, id=None, classes=None)
|
||||||
self.nucleon_file = nucleon_file
|
self.repo = repo
|
||||||
self.electron_file = electron_file
|
self.repostat = repostat
|
||||||
self.nucleons_with_orbital = pt.load_nucleon(self.nucleon_file)
|
|
||||||
self.electrons = pt.load_electron(self.electron_file)
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
yield Header(show_clock=True)
|
yield Header(show_clock=True)
|
||||||
with ScrollableContainer(id="vice_container"):
|
with ScrollableContainer(id="vice_container"):
|
||||||
yield Label(f"准备就绪: [b]{self.nucleon_file.stem}[/b]\n")
|
yield Label(f"准备就绪: [b]{self.repostat['title']}[/b]\n")
|
||||||
yield Label(
|
yield Label(
|
||||||
f"内容源文件: {config_var.get()['paths']['nucleon_dir']}/[b]{self.nucleon_file.name}[/b]"
|
f"仓库路径: {config_var.get()['paths']['data']}/repo/[b]{self.repostat['dirname']}[/b]"
|
||||||
)
|
)
|
||||||
yield Label(
|
yield Label(f"\n单元数量: {len(self.repo)}\n")
|
||||||
f"元数据文件: {config_var.get()['paths']['electron_dir']}/[b]{self.electron_file.name}[/b]"
|
|
||||||
)
|
|
||||||
yield Label(f"\n单元数量: {len(self.nucleons_with_orbital)}\n")
|
|
||||||
yield Label(f"单次记忆数量: {self.scheduled_num}", id="schnum_label")
|
yield Label(f"单次记忆数量: {self.scheduled_num}", id="schnum_label")
|
||||||
|
|
||||||
yield Button(
|
yield Button(
|
||||||
@@ -75,10 +72,11 @@ class PreparationScreen(Screen):
|
|||||||
|
|
||||||
def _get_full_content(self):
|
def _get_full_content(self):
|
||||||
content = ""
|
content = ""
|
||||||
for nucleon, orbital in self.nucleons_with_orbital:
|
for i in self.repo.ident_index:
|
||||||
nucleon: pt.Nucleon
|
n = pt.Nucleon.create_on_nucleonic_data(
|
||||||
# print(nucleon.payload)
|
nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||||
content += " - " + nucleon["content"] + " \n"
|
)
|
||||||
|
content += f"- {n['content']} \n"
|
||||||
return content
|
return content
|
||||||
|
|
||||||
def action_go_back(self):
|
def action_go_back(self):
|
||||||
@@ -88,9 +86,15 @@ class PreparationScreen(Screen):
|
|||||||
from ..screens.precache import PrecachingScreen
|
from ..screens.precache import PrecachingScreen
|
||||||
|
|
||||||
lst = list()
|
lst = list()
|
||||||
for i in self.nucleons_with_orbital:
|
for i in self.repo.ident_index:
|
||||||
lst.append(i[0])
|
lst.append(
|
||||||
precache_screen = PrecachingScreen(lst)
|
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)
|
self.app.push_screen(precache_screen)
|
||||||
|
|
||||||
def action_quit_app(self):
|
def action_quit_app(self):
|
||||||
@@ -101,38 +105,32 @@ class PreparationScreen(Screen):
|
|||||||
logger.debug("按下按钮")
|
logger.debug("按下按钮")
|
||||||
if event.button.id == "start_memorizing_button":
|
if event.button.id == "start_memorizing_button":
|
||||||
atoms = list()
|
atoms = list()
|
||||||
for nucleon, orbital in self.nucleons_with_orbital:
|
for i in self.repo.ident_index:
|
||||||
atom = pt.Atom(nucleon.ident)
|
n = pt.Nucleon.create_on_nucleonic_data(
|
||||||
atom.link("nucleon", nucleon)
|
nucleonic_data=self.repo.nucleonic_data_lict.get_itemic_unit(i)
|
||||||
try:
|
)
|
||||||
atom.link("electron", self.electrons[nucleon.ident])
|
e = pt.Electron.create_on_electonic_data(
|
||||||
except KeyError:
|
electronic_data=self.repo.electronic_data_lict.get_itemic_unit(i)
|
||||||
atom.link("electron", pt.Electron(nucleon.ident))
|
)
|
||||||
atom.link("orbital", orbital)
|
a = pt.Atom(n, e, self.repo.orbitic_data)
|
||||||
atom.link("nucleon_fmt", "toml")
|
atoms.append(a)
|
||||||
atom.link("electron_fmt", "json")
|
|
||||||
atom.link("orbital_fmt", "toml")
|
|
||||||
atom.link("nucleon_path", self.nucleon_file)
|
|
||||||
atom.link("electron_path", self.electron_file)
|
|
||||||
atom.link("orbital_path", None)
|
|
||||||
atoms.append(atom)
|
|
||||||
atoms_to_provide = list()
|
atoms_to_provide = list()
|
||||||
left_new = self.scheduled_num
|
left_new = self.scheduled_num
|
||||||
for i in atoms:
|
for i in atoms:
|
||||||
i: pt.Atom
|
i: pt.Atom
|
||||||
if i.registry["electron"].is_due():
|
if i.registry["electron"].is_activated():
|
||||||
atoms_to_provide.append(i)
|
if i.registry["electron"].is_due():
|
||||||
|
atoms_to_provide.append(i)
|
||||||
else:
|
else:
|
||||||
if i.registry["electron"].is_activated():
|
left_new -= 1
|
||||||
pass
|
if left_new >= 0:
|
||||||
else:
|
atoms_to_provide.append(i)
|
||||||
left_new -= 1
|
from .memoqueue import MemScreen
|
||||||
if left_new >= 0:
|
import heurams.kernel.reactor as rt
|
||||||
atoms_to_provide.append(i)
|
pheser = rt.Phaser(atoms_to_provide)
|
||||||
logger.debug(f"ATP: {atoms_to_provide}")
|
memscreen = MemScreen(pheser)
|
||||||
from .memorizor import MemScreen
|
|
||||||
|
|
||||||
memscreen = MemScreen(atoms_to_provide)
|
|
||||||
self.app.push_screen(memscreen)
|
self.app.push_screen(memscreen)
|
||||||
|
|
||||||
elif event.button.id == "precache_button":
|
elif event.button.id == "precache_button":
|
||||||
self.action_precache()
|
self.action_precache()
|
||||||
|
|||||||
1
src/heurams/interface/screens/radio.py
Normal file
1
src/heurams/interface/screens/radio.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
""" "前进电台" 界面"""
|
||||||
@@ -1,20 +1,20 @@
|
|||||||
#!/usr/bin/env python3
|
"""仓库创建向导界面"""
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import toml
|
import toml
|
||||||
from textual.app import ComposeResult
|
from textual.app import ComposeResult
|
||||||
from textual.containers import ScrollableContainer
|
from textual.containers import ScrollableContainer
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import (Button, Footer, Header, Input, Label, Markdown,
|
from textual.widgets import Button, Footer, Header, Input, Label, Markdown, Select
|
||||||
Select)
|
|
||||||
|
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
from heurams.services.version import ver
|
from heurams.services.version import ver
|
||||||
|
|
||||||
|
|
||||||
class NucleonCreatorScreen(Screen):
|
class RepoCreatorScreen(Screen):
|
||||||
BINDINGS = [("q", "go_back", "返回")]
|
BINDINGS = [("q", "go_back", "返回")]
|
||||||
SUB_TITLE = "单元集创建向导"
|
SUB_TITLE = "仓库创建向导"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__(name=None, id=None, classes=None)
|
super().__init__(name=None, id=None, classes=None)
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
"""同步工具界面"""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,10 @@
|
|||||||
"""Kernel 操作辅助函数库"""
|
"""Kernel 操作辅助函数库"""
|
||||||
|
|
||||||
import random
|
|
||||||
from typing import TypedDict
|
|
||||||
|
|
||||||
import heurams.interface.widgets as pzw
|
import heurams.interface.widgets as pzw
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.evaluators as pz
|
||||||
import heurams.kernel.puzzles as pz
|
|
||||||
|
|
||||||
staging = {} # 细粒度缓存区, 是 ident -> quality 的封装
|
|
||||||
|
|
||||||
|
|
||||||
def report_to_staging(atom: pt.Atom, quality):
|
|
||||||
staging[atom.ident] = min(quality, staging[atom.ident])
|
|
||||||
|
|
||||||
|
|
||||||
def clear():
|
|
||||||
staging = dict()
|
|
||||||
|
|
||||||
|
|
||||||
def deploy_to_electron():
|
|
||||||
for atom_ident, quality in staging.items():
|
|
||||||
if pt.atom_registry[atom_ident].registry["electron"].is_activated:
|
|
||||||
pt.atom_registry[atom_ident].registry["electron"].revisor(quality=quality)
|
|
||||||
else:
|
|
||||||
pt.atom_registry[atom_ident].registry["electron"].revisor(
|
|
||||||
quality=quality, is_new_activation=True
|
|
||||||
)
|
|
||||||
clear()
|
|
||||||
|
|
||||||
|
|
||||||
puzzle2widget = {
|
puzzle2widget = {
|
||||||
pz.RecognitionPuzzle: pzw.Recognition,
|
pz.RecognitionPuzzle: pzw.Recognition,
|
||||||
pz.ClozePuzzle: pzw.ClozePuzzle,
|
pz.ClozePuzzle: pzw.ClozePuzzle,
|
||||||
pz.MCQPuzzle: pzw.MCQPuzzle,
|
pz.MCQPuzzle: pzw.MCQPuzzle,
|
||||||
pz.BasePuzzle: pzw.BasePuzzleWidget,
|
pz.BaseEvaluator: pzw.BasePuzzleWidget,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from textual.message import Message
|
|||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Label
|
from textual.widgets import Button, Label
|
||||||
|
|
||||||
|
import heurams.kernel.evaluators as pz
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as pz
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .base_puzzle_widget import BasePuzzleWidget
|
from .base_puzzle_widget import BasePuzzleWidget
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ from textual.containers import Container, ScrollableContainer
|
|||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.widgets import Button, Label
|
from textual.widgets import Button, Label
|
||||||
|
|
||||||
|
import heurams.kernel.evaluators as pz
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as pz
|
|
||||||
from heurams.services.hasher import hash
|
from heurams.services.hasher import hash
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
|
|||||||
@@ -54,8 +54,8 @@ class Recognition(BasePuzzleWidget):
|
|||||||
autovoice = config_var.get()["interface"]["memorizor"]["autovoice"]
|
autovoice = config_var.get()["interface"]["memorizor"]["autovoice"]
|
||||||
if autovoice:
|
if autovoice:
|
||||||
self.screen.action_play_voice() # type: ignore
|
self.screen.action_play_voice() # type: ignore
|
||||||
cfg: RecognitionConfig = self.atom.registry["orbital"]["puzzles"][self.alia]
|
cfg: RecognitionConfig = self.atom.registry["nucleon"]["puzzles"][self.alia]
|
||||||
delim = self.atom.registry["nucleon"].metadata["formation"]["delimiter"]
|
delim = self.atom.registry["nucleon"]["delimiter"]
|
||||||
replace_dict = {
|
replace_dict = {
|
||||||
", ": ",",
|
", ": ",",
|
||||||
". ": ".",
|
". ": ".",
|
||||||
@@ -69,7 +69,7 @@ class Recognition(BasePuzzleWidget):
|
|||||||
}
|
}
|
||||||
|
|
||||||
nucleon = self.atom.registry["nucleon"]
|
nucleon = self.atom.registry["nucleon"]
|
||||||
metadata = self.atom.registry["nucleon"].metadata
|
metadata = self.atom.registry["nucleon"]
|
||||||
primary = cfg["primary"]
|
primary = cfg["primary"]
|
||||||
|
|
||||||
with Center():
|
with Center():
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
|
from .base import BaseAlgorithm
|
||||||
from .sm2 import SM2Algorithm
|
from .sm2 import SM2Algorithm
|
||||||
from .sm15m import SM15MAlgorithm
|
from .sm15m import SM15MAlgorithm
|
||||||
|
|
||||||
@@ -7,12 +8,14 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"SM2Algorithm",
|
"SM2Algorithm",
|
||||||
|
"BaseAlgorithm",
|
||||||
|
"SM15MAlgorithm",
|
||||||
]
|
]
|
||||||
|
|
||||||
algorithms = {
|
algorithms = {
|
||||||
"SM-2": SM2Algorithm,
|
"SM-2": SM2Algorithm,
|
||||||
"SM-15M": SM15MAlgorithm,
|
"SM-15M": SM15MAlgorithm,
|
||||||
# "SM-15M": SM15MAlgorithm,
|
"Base": BaseAlgorithm,
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("算法模块初始化完成, 注册的算法: %s", list(algorithms.keys()))
|
logger.debug("算法模块初始化完成, 注册的算法: %s", list(algorithms.keys()))
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ class BaseAlgorithm:
|
|||||||
algo_name = "BaseAlgorithm"
|
algo_name = "BaseAlgorithm"
|
||||||
|
|
||||||
class AlgodataDict(TypedDict):
|
class AlgodataDict(TypedDict):
|
||||||
efactor: float
|
|
||||||
real_rept: int
|
real_rept: int
|
||||||
rept: int
|
rept: int
|
||||||
interval: int
|
interval: int
|
||||||
@@ -52,7 +51,7 @@ class BaseAlgorithm:
|
|||||||
return 1
|
return 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rate(cls, algodata) -> str:
|
def get_rating(cls, algodata) -> str:
|
||||||
"""获取评分信息"""
|
"""获取评分信息"""
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"BaseAlgorithm.rate 被调用, algodata keys: %s",
|
"BaseAlgorithm.rate 被调用, algodata keys: %s",
|
||||||
@@ -68,3 +67,11 @@ class BaseAlgorithm:
|
|||||||
list(algodata.keys()) if algodata else [],
|
list(algodata.keys()) if algodata else [],
|
||||||
)
|
)
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_integrity(cls, algodata):
|
||||||
|
try:
|
||||||
|
cls.AlgodataDict(**algodata[cls.algo_name])
|
||||||
|
return 1
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|||||||
@@ -14,13 +14,22 @@ import pathlib
|
|||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
from heurams.context import config_var
|
from heurams.context import config_var
|
||||||
from heurams.kernel.algorithms.sm15m_calc import (MAX_AF, MIN_AF, NOTCH_AF,
|
from heurams.kernel.algorithms.sm15m_calc import (
|
||||||
RANGE_AF, RANGE_REPETITION,
|
MAX_AF,
|
||||||
SM, THRESHOLD_RECALL, Item)
|
MIN_AF,
|
||||||
|
NOTCH_AF,
|
||||||
|
RANGE_AF,
|
||||||
|
RANGE_REPETITION,
|
||||||
|
SM,
|
||||||
|
THRESHOLD_RECALL,
|
||||||
|
Item,
|
||||||
|
)
|
||||||
|
|
||||||
# 全局状态文件路径
|
# 全局状态文件路径
|
||||||
_GLOBAL_STATE_FILE = os.path.expanduser(
|
_GLOBAL_STATE_FILE = os.path.expanduser(
|
||||||
pathlib.Path(config_var.get()["paths"]["global_dir"]) / "sm15m_global_state.json"
|
pathlib.Path(config_var.get()["paths"]["data"])
|
||||||
|
/ "global"
|
||||||
|
/ "sm15m_global_state.json"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ class SM2Algorithm(BaseAlgorithm):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def rate(cls, algodata):
|
def get_rating(cls, algodata):
|
||||||
efactor = algodata[cls.algo_name]["efactor"]
|
efactor = algodata[cls.algo_name]["efactor"]
|
||||||
logger.debug("SM2.rate: efactor=%f", efactor)
|
logger.debug("SM2.rate: efactor=%f", efactor)
|
||||||
return str(efactor)
|
return str(efactor)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
"""
|
"""
|
||||||
Puzzle 模块 - 谜题生成系统
|
Evaluator 模块 - 生成评估模块
|
||||||
|
|
||||||
提供多种类型的谜题生成器, 支持从字符串、字典等数据源导入题目
|
提供多种类型的辅助评估生成器, 支持从字符串、字典等数据源导入题目
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
from .base import BasePuzzle
|
from .base import BaseEvaluator
|
||||||
from .cloze import ClozePuzzle
|
from .cloze import ClozePuzzle
|
||||||
from .mcq import MCQPuzzle
|
from .mcq import MCQPuzzle
|
||||||
from .recognition import RecognitionPuzzle
|
from .recognition import RecognitionPuzzle
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"BasePuzzle",
|
"BaseEvaluator",
|
||||||
"ClozePuzzle",
|
"ClozePuzzle",
|
||||||
"MCQPuzzle",
|
"MCQPuzzle",
|
||||||
"RecognitionPuzzle",
|
"RecognitionPuzzle",
|
||||||
@@ -24,12 +24,12 @@ puzzles = {
|
|||||||
"mcq": MCQPuzzle,
|
"mcq": MCQPuzzle,
|
||||||
"cloze": ClozePuzzle,
|
"cloze": ClozePuzzle,
|
||||||
"recognition": RecognitionPuzzle,
|
"recognition": RecognitionPuzzle,
|
||||||
"base": BasePuzzle,
|
"base": BaseEvaluator,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create_by_dict(config_dict: dict) -> BasePuzzle:
|
def create_by_dict(config_dict: dict) -> BaseEvaluator:
|
||||||
"""
|
"""
|
||||||
根据配置字典创建谜题
|
根据配置字典创建谜题
|
||||||
|
|
||||||
@@ -4,7 +4,7 @@ from heurams.services.logger import get_logger
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BasePuzzle:
|
class BaseEvaluator:
|
||||||
"""谜题基类"""
|
"""谜题基类"""
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
@@ -2,12 +2,12 @@ import random
|
|||||||
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .base import BasePuzzle
|
from .base import BaseEvaluator
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ClozePuzzle(BasePuzzle):
|
class ClozePuzzle(BaseEvaluator):
|
||||||
"""填空题谜题生成器
|
"""填空题谜题生成器
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
12
src/heurams/kernel/evaluators/guess.py
Normal file
12
src/heurams/kernel/evaluators/guess.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import random
|
||||||
|
|
||||||
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
|
from .base import BaseEvaluator
|
||||||
|
|
||||||
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class GuessEvaluator(BaseEvaluator):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
@@ -4,12 +4,12 @@ from typing import Dict, List, Optional, Union
|
|||||||
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .base import BasePuzzle
|
from .base import BaseEvaluator
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class MCQPuzzle(BasePuzzle):
|
class MCQPuzzle(BaseEvaluator):
|
||||||
"""选择题谜题生成器
|
"""选择题谜题生成器
|
||||||
|
|
||||||
该类用于生成和管理选择题谜题, 支持多个题目同时生成,
|
该类用于生成和管理选择题谜题, 支持多个题目同时生成,
|
||||||
@@ -3,12 +3,12 @@ import random
|
|||||||
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .base import BasePuzzle
|
from .base import BaseEvaluator
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class RecognitionPuzzle(BasePuzzle):
|
class RecognitionPuzzle(BaseEvaluator):
|
||||||
"""识别占位符"""
|
"""识别占位符"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@@ -1,29 +1,4 @@
|
|||||||
"""
|
from .atom import Atom
|
||||||
Particle 模块 - 粒子对象系统
|
|
||||||
|
|
||||||
提供闪卡所需对象, 使用物理学粒子的领域驱动设计
|
|
||||||
"""
|
|
||||||
|
|
||||||
from heurams.services.logger import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
logger.debug("粒子模块已加载")
|
|
||||||
|
|
||||||
from .atom import Atom, atom_registry
|
|
||||||
from .electron import Electron
|
from .electron import Electron
|
||||||
from .loader import load_electron, load_nucleon
|
|
||||||
from .nucleon import Nucleon
|
from .nucleon import Nucleon
|
||||||
from .orbital import Orbital
|
#from .orbital import Orbital
|
||||||
from .probe import probe_all, probe_by_filename
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"Electron",
|
|
||||||
"Nucleon",
|
|
||||||
"Orbital",
|
|
||||||
"Atom",
|
|
||||||
"probe_all",
|
|
||||||
"probe_by_filename",
|
|
||||||
"load_nucleon",
|
|
||||||
"load_electron",
|
|
||||||
"atom_registry",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -1,17 +1,10 @@
|
|||||||
import json
|
|
||||||
import pathlib
|
|
||||||
import typing
|
|
||||||
from typing import TypedDict
|
from typing import TypedDict
|
||||||
|
|
||||||
import bidict
|
|
||||||
import toml
|
|
||||||
|
|
||||||
from heurams.context import config_var
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .electron import Electron
|
from .electron import Electron
|
||||||
from .nucleon import Nucleon
|
from .nucleon import Nucleon
|
||||||
from .orbital import Orbital
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
@@ -19,19 +12,13 @@ logger = get_logger(__name__)
|
|||||||
class AtomRegister_runtime(TypedDict):
|
class AtomRegister_runtime(TypedDict):
|
||||||
locked: bool # 只读锁定标识符
|
locked: bool # 只读锁定标识符
|
||||||
min_rate: int # 最低评分
|
min_rate: int # 最低评分
|
||||||
newact: bool # 新激活
|
new_activation: bool # 新激活
|
||||||
|
|
||||||
|
|
||||||
class AtomRegister(TypedDict):
|
class AtomRegister(TypedDict):
|
||||||
nucleon: Nucleon
|
nucleon: Nucleon
|
||||||
nucleon_path: pathlib.Path
|
|
||||||
nucleon_fmt: str
|
|
||||||
electron: Electron
|
electron: Electron
|
||||||
electron_path: pathlib.Path
|
orbital: dict
|
||||||
electron_fmt: str
|
|
||||||
orbital: Orbital
|
|
||||||
orbital_path: pathlib.Path
|
|
||||||
orbital_fmt: str
|
|
||||||
runtime: AtomRegister_runtime
|
runtime: AtomRegister_runtime
|
||||||
|
|
||||||
|
|
||||||
@@ -44,88 +31,27 @@ class Atom:
|
|||||||
以及关联路径
|
以及关联路径
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ident=""):
|
default_runtime = {
|
||||||
logger.debug("创建 Atom 实例, ident: '%s'", ident)
|
"locked": False,
|
||||||
self.ident = ident
|
"min_rate": 0x3F3F3F3F,
|
||||||
atom_registry[ident] = self
|
"new_activation": False,
|
||||||
logger.debug("Atom 已注册到全局注册表, 当前注册表大小: %d", len(atom_registry))
|
}
|
||||||
# self.is_evaled = False
|
|
||||||
|
def __init__(self, nucleon_obj=None, electron_obj=None, orbital_obj=None):
|
||||||
|
self.ident = nucleon_obj["ident"] # type: ignore
|
||||||
self.registry: AtomRegister = { # type: ignore
|
self.registry: AtomRegister = { # type: ignore
|
||||||
"nucleon": None,
|
"ident": nucleon_obj["ident"], # type: ignore
|
||||||
"nucleon_path": None,
|
"nucleon": nucleon_obj,
|
||||||
"nucleon_fmt": "toml",
|
"electron": electron_obj,
|
||||||
"electron": None,
|
"orbital": orbital_obj,
|
||||||
"electron_path": None,
|
"runtime": dict(),
|
||||||
"electron_fmt": "json",
|
|
||||||
"orbital": None,
|
|
||||||
"orbital_path": None, # 允许设置为 None, 此时使用 nucleon 文件内的推荐配置
|
|
||||||
"orbital_fmt": "toml",
|
|
||||||
"runtime": {"locked": False, "min_rate": 0x3F3F3F3F, "newact": False},
|
|
||||||
}
|
}
|
||||||
logger.debug("Atom 初始化完成")
|
self.init_runtime()
|
||||||
|
if self.registry["electron"].is_activated() == 0:
|
||||||
|
self.registry["runtime"]["new_activation"] = True
|
||||||
|
|
||||||
def link(self, key, value):
|
def init_runtime(self):
|
||||||
logger.debug("Atom.link: key='%s', value type: %s", key, type(value).__name__)
|
self.registry["runtime"] = AtomRegister_runtime(**self.default_runtime)
|
||||||
if key in self.registry.keys():
|
|
||||||
self.registry[key] = value
|
|
||||||
logger.debug("键 '%s' 已链接, 触发 do_eval", key)
|
|
||||||
if key == "electron":
|
|
||||||
if self.registry["electron"].is_activated() == 0:
|
|
||||||
self.registry["runtime"]["newact"] = True
|
|
||||||
else:
|
|
||||||
logger.error("尝试链接不受支持的键: '%s'", key)
|
|
||||||
raise ValueError("不受支持的原子元数据链接操作")
|
|
||||||
|
|
||||||
def do_eval(self):
|
|
||||||
"""
|
|
||||||
执行并以结果替换当前单元的所有 eval 语句
|
|
||||||
TODO: 带有限制的 eval, 异步/多线程执行避免堵塞
|
|
||||||
"""
|
|
||||||
logger.debug("EVAL 开始")
|
|
||||||
|
|
||||||
# eval 环境设置
|
|
||||||
def eval_with_env(s: str):
|
|
||||||
default = config_var.get()["puzzles"]
|
|
||||||
payload = self.registry["nucleon"].payload
|
|
||||||
metadata = self.registry["nucleon"].metadata
|
|
||||||
eval_value = eval(s)
|
|
||||||
if isinstance(eval_value, (int, float)):
|
|
||||||
ret = str(eval_value)
|
|
||||||
else:
|
|
||||||
ret = eval_value
|
|
||||||
logger.debug(
|
|
||||||
"eval 执行成功: '%s' -> '%s'",
|
|
||||||
s,
|
|
||||||
str(ret)[:50] + "..." if len(ret) > 50 else ret,
|
|
||||||
)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def traverse(data, modifier):
|
|
||||||
if isinstance(data, dict):
|
|
||||||
for key, value in data.items():
|
|
||||||
data[key] = traverse(value, modifier)
|
|
||||||
return data
|
|
||||||
elif isinstance(data, list):
|
|
||||||
for i, item in enumerate(data):
|
|
||||||
data[i] = traverse(item, modifier)
|
|
||||||
return data
|
|
||||||
elif isinstance(data, tuple):
|
|
||||||
return tuple(traverse(item, modifier) for item in data)
|
|
||||||
else:
|
|
||||||
if isinstance(data, str):
|
|
||||||
if data.startswith("eval:"):
|
|
||||||
logger.debug("发现 eval 表达式: '%s'", data[5:])
|
|
||||||
return modifier(data[5:])
|
|
||||||
return data
|
|
||||||
|
|
||||||
try:
|
|
||||||
traverse(self.registry["nucleon"].payload, eval_with_env)
|
|
||||||
traverse(self.registry["nucleon"].metadata, eval_with_env)
|
|
||||||
traverse(self.registry["orbital"], eval_with_env)
|
|
||||||
except Exception as e:
|
|
||||||
ret = f"此 eval 实例发生错误: {e}"
|
|
||||||
logger.warning(ret)
|
|
||||||
logger.debug("EVAL 完成")
|
|
||||||
|
|
||||||
def minimize(self, rating):
|
def minimize(self, rating):
|
||||||
"""效果等同于 self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate'])
|
"""效果等同于 self.registry['runtime']['min_rate'] = min(rating, self.registry['runtime']['min_rate'])
|
||||||
@@ -158,62 +84,21 @@ class Atom:
|
|||||||
logger.debug(f"允许总评分: {self.registry['runtime']['min_rate']}")
|
logger.debug(f"允许总评分: {self.registry['runtime']['min_rate']}")
|
||||||
self.registry["electron"].revisor(
|
self.registry["electron"].revisor(
|
||||||
self.registry["runtime"]["min_rate"],
|
self.registry["runtime"]["min_rate"],
|
||||||
is_new_activation=self.registry["runtime"]["newact"],
|
is_new_activation=self.registry["runtime"]["new_activation"],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
logger.debug("禁止总评分")
|
logger.debug("禁止总评分")
|
||||||
|
|
||||||
def persist(self, key):
|
|
||||||
logger.debug("Atom.persist: key='%s'", key)
|
|
||||||
path: pathlib.Path | None = self.registry[key + "_path"]
|
|
||||||
if isinstance(path, pathlib.Path):
|
|
||||||
path = typing.cast(pathlib.Path, path)
|
|
||||||
logger.debug("持久化路径: %s, 格式: %s", path, self.registry[key + "_fmt"])
|
|
||||||
path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
if self.registry[key + "_fmt"] == "toml":
|
|
||||||
with open(path, "r+") as f:
|
|
||||||
f.seek(0)
|
|
||||||
f.truncate()
|
|
||||||
toml.dump(self.registry[key], f)
|
|
||||||
logger.debug("TOML 数据已保存到: %s", path)
|
|
||||||
elif self.registry[key + "_fmt"] == "json":
|
|
||||||
with open(path, "r+") as f:
|
|
||||||
origin = json.load(f)
|
|
||||||
f.seek(0)
|
|
||||||
f.truncate()
|
|
||||||
origin[self.ident] = self.registry[key].algodata
|
|
||||||
json.dump(origin, f, indent=2, ensure_ascii=False)
|
|
||||||
logger.debug("JSON 数据已保存到: %s", path)
|
|
||||||
else:
|
|
||||||
logger.error("不受支持的持久化格式: %s", self.registry[key + "_fmt"])
|
|
||||||
raise KeyError("不受支持的持久化格式")
|
|
||||||
else:
|
|
||||||
logger.error("路径未初始化: %s_path", key)
|
|
||||||
raise TypeError("对未初始化的路径对象操作")
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
logger.debug("Atom.__getitem__: key='%s'", key)
|
return self.registry[key]
|
||||||
if key in self.registry:
|
|
||||||
value = self.registry[key]
|
|
||||||
logger.debug("返回 value type: %s", type(value).__name__)
|
|
||||||
return value
|
|
||||||
logger.error("不支持的键: '%s'", key)
|
|
||||||
raise KeyError(f"不支持的键: {key}")
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
def __setitem__(self, key, value):
|
||||||
logger.debug(
|
if key == "ident":
|
||||||
"Atom.__setitem__: key='%s', value type: %s", key, type(value).__name__
|
raise AttributeError("应为只读")
|
||||||
)
|
self.registry[key] = value
|
||||||
if key in self.registry:
|
|
||||||
self.registry[key] = value
|
|
||||||
logger.debug("键 '%s' 已设置", key)
|
|
||||||
else:
|
|
||||||
logger.error("不支持的键: '%s'", key)
|
|
||||||
raise KeyError(f"不支持的键: {key}")
|
|
||||||
|
|
||||||
@staticmethod
|
def __repr__(self):
|
||||||
def placeholder():
|
from pprint import pformat
|
||||||
return (Electron.placeholder(), Nucleon.placeholder(), {})
|
|
||||||
|
|
||||||
|
s = pformat(self.registry, indent=4)
|
||||||
atom_registry: bidict.bidict[str, Atom] = bidict.bidict()
|
return s
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
import heurams.kernel.algorithms as algolib
|
||||||
import heurams.services.timer as timer
|
import heurams.services.timer as timer
|
||||||
from heurams.context import config_var
|
|
||||||
from heurams.kernel.algorithms import algorithms
|
from heurams.kernel.algorithms import algorithms
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
@@ -7,94 +10,62 @@ logger = get_logger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class Electron:
|
class Electron:
|
||||||
"""电子: 记忆分析元数据及算法"""
|
"""电子: 单算法支持的记忆数据包装"""
|
||||||
|
|
||||||
def __init__(self, ident: str, algodata: dict = {}, algo_name: str = ""):
|
def __init__(self, ident: str, algodata: dict = {}, algo_name: str = ""):
|
||||||
"""初始化电子对象 (记忆数据)
|
"""初始化电子对象 (记忆数据)
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ident: 算法的唯一标识符, 用于区分不同的算法实例, 使用 algodata[ident] 获取
|
ident: 算法的唯一标识符, 用于区分不同的算法实例, 使用 algodata[ident] 获取
|
||||||
algodata: 算法数据字典, 包含算法的各项参数和设置
|
algodata: 算法数据字典引用, 包含算法的各项参数和设置
|
||||||
algo: 使用的算法模块标识
|
algo_name: 使用的算法模块标识
|
||||||
"""
|
"""
|
||||||
if algo_name == "":
|
if algo_name == "":
|
||||||
algo_name = config_var.get()["algorithm"]["default"]
|
algo_name = "SM-2"
|
||||||
logger.debug(
|
|
||||||
"创建 Electron 实例, ident: '%s', algo_name: '%s', algodata: %s",
|
|
||||||
ident,
|
|
||||||
algo_name,
|
|
||||||
algodata,
|
|
||||||
)
|
|
||||||
self.algodata = algodata
|
self.algodata = algodata
|
||||||
self.ident = ident
|
self.ident = ident
|
||||||
self.algo = algorithms[algo_name]
|
self.algo: algolib.BaseAlgorithm = algorithms[algo_name]
|
||||||
logger.debug("使用的算法类: %s", self.algo.__name__)
|
|
||||||
|
|
||||||
if self.algo.algo_name not in self.algodata.keys():
|
if not self.algo.check_integrity(self.algodata):
|
||||||
self.algodata[self.algo.algo_name] = {}
|
self.algodata[self.algo.algo_name] = deepcopy(self.algo.defaults)
|
||||||
logger.debug("算法键 '%s' 不存在, 已创建空字典", self.algo)
|
|
||||||
if not self.algodata[self.algo.algo_name]:
|
|
||||||
logger.debug(
|
|
||||||
f"算法数据为空, 使用默认值初始化{self.algodata[self.algo.algo_name]}"
|
|
||||||
)
|
|
||||||
self._default_init(self.algo.defaults)
|
|
||||||
else:
|
|
||||||
logger.debug("算法数据已存在, 跳过默认初始化")
|
|
||||||
logger.debug(
|
|
||||||
"Electron 初始化完成, algodata keys: %s", list(self.algodata.keys())
|
|
||||||
)
|
|
||||||
|
|
||||||
def _default_init(self, defaults: dict):
|
def __repr__(self):
|
||||||
"""默认初始化包装"""
|
from pprint import pformat
|
||||||
logger.debug(
|
|
||||||
"Electron._default_init: 使用默认值, keys: %s", list(defaults.keys())
|
s = pformat(self.algodata, indent=4)
|
||||||
)
|
return s
|
||||||
self.algodata[self.algo.algo_name] = defaults.copy()
|
|
||||||
|
|
||||||
def activate(self):
|
def activate(self):
|
||||||
"""激活此电子"""
|
"""激活此电子"""
|
||||||
logger.debug("Electron.activate: 激活 ident='%s'", self.ident)
|
|
||||||
self.algodata[self.algo.algo_name]["is_activated"] = 1
|
self.algodata[self.algo.algo_name]["is_activated"] = 1
|
||||||
self.algodata[self.algo.algo_name]["last_modify"] = timer.get_timestamp()
|
self.algodata[self.algo.algo_name]["last_modify"] = timer.get_timestamp()
|
||||||
logger.debug("电子已激活, is_activated=1")
|
|
||||||
|
|
||||||
def modify(self, var: str, value):
|
def modify(self, key, value):
|
||||||
"""修改 algodata[algo] 中子字典数据"""
|
"""修改 algodata[algo] 中子字典数据"""
|
||||||
logger.debug("Electron.modify: var='%s', value=%s", var, value)
|
if key in self.algodata[self.algo.algo_name]:
|
||||||
if var in self.algodata[self.algo.algo_name]:
|
self.algodata[self.algo.algo_name][key] = value
|
||||||
self.algodata[self.algo.algo_name][var] = value
|
|
||||||
self.algodata[self.algo.algo_name]["last_modify"] = timer.get_timestamp()
|
self.algodata[self.algo.algo_name]["last_modify"] = timer.get_timestamp()
|
||||||
logger.debug("变量 '%s' 已修改, 更新 last_modify", var)
|
|
||||||
else:
|
else:
|
||||||
logger.warning("'%s' 非已知元数据字段", var)
|
raise AttributeError("不存在的子键")
|
||||||
print(f"警告: '{var}' 非已知元数据字段")
|
|
||||||
|
|
||||||
def is_due(self):
|
def is_due(self):
|
||||||
"""是否应该复习"""
|
"""是否应该复习"""
|
||||||
logger.debug("Electron.is_due: 检查 ident='%s'", self.ident)
|
|
||||||
result = self.algo.is_due(self.algodata)
|
result = self.algo.is_due(self.algodata)
|
||||||
logger.debug("is_due 结果: %s", result)
|
|
||||||
return result and self.is_activated()
|
return result and self.is_activated()
|
||||||
|
|
||||||
def is_activated(self):
|
def is_activated(self):
|
||||||
result = self.algodata[self.algo.algo_name]["is_activated"]
|
result = self.algodata[self.algo.algo_name]["is_activated"]
|
||||||
logger.debug("Electron.is_activated: ident='%s', 结果: %d", self.ident, result)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def get_rate(self):
|
def get_rating(self):
|
||||||
"评价"
|
|
||||||
try:
|
try:
|
||||||
logger.debug("Electron.rate: ident='%s'", self.ident)
|
result = self.algo.get_rating(self.algodata)
|
||||||
result = self.algo.rate(self.algodata)
|
|
||||||
logger.debug("rate 结果: %s", result)
|
|
||||||
return result
|
return result
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def nextdate(self) -> int:
|
def nextdate(self) -> int:
|
||||||
logger.debug("Electron.nextdate: ident='%s'", self.ident)
|
|
||||||
result = self.algo.nextdate(self.algodata)
|
result = self.algo.nextdate(self.algodata)
|
||||||
logger.debug("nextdate 结果: %d", result)
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def revisor(self, quality: int = 5, is_new_activation: bool = False):
|
def revisor(self, quality: int = 5, is_new_activation: bool = False):
|
||||||
@@ -104,32 +75,7 @@ class Electron:
|
|||||||
quality (int): 记忆保留率量化参数 (0-5)
|
quality (int): 记忆保留率量化参数 (0-5)
|
||||||
is_new_activation (bool): 是否为初次激活
|
is_new_activation (bool): 是否为初次激活
|
||||||
"""
|
"""
|
||||||
logger.debug(
|
|
||||||
"Electron.revisor: ident='%s', quality=%d, is_new_activation=%s",
|
|
||||||
self.ident,
|
|
||||||
quality,
|
|
||||||
is_new_activation,
|
|
||||||
)
|
|
||||||
self.algo.revisor(self.algodata, quality, is_new_activation)
|
self.algo.revisor(self.algodata, quality, is_new_activation)
|
||||||
logger.debug(
|
|
||||||
"revisor 完成, 更新后的 algodata: %s", self.algodata.get(self.algo, {})
|
|
||||||
)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return (
|
|
||||||
f"记忆单元预览 \n"
|
|
||||||
f"标识符: '{self.ident}' \n"
|
|
||||||
f"算法: {self.algo} \n"
|
|
||||||
f"易度系数: {self.algodata[self.algo.algo_name]['efactor']:.2f} \n"
|
|
||||||
f"已经重复的次数: {self.algodata[self.algo.algo_name]['rept']} \n"
|
|
||||||
f"下次间隔: {self.algodata[self.algo.algo_name]['interval']} 天 \n"
|
|
||||||
f"下次复习日期时间戳: {self.algodata[self.algo.algo_name]['next_date']}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if self.ident == other.ident:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def __hash__(self):
|
def __hash__(self):
|
||||||
return hash(self.ident)
|
return hash(self.ident)
|
||||||
@@ -153,6 +99,9 @@ class Electron:
|
|||||||
return len(self.algodata[self.algo.algo_name])
|
return len(self.algodata[self.algo.algo_name])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def placeholder():
|
def create_on_electonic_data(electronic_data: tuple, algo_name: str = ""):
|
||||||
"""生成一个电子占位符"""
|
_data = electronic_data
|
||||||
return Electron("电子对象样例内容", {})
|
ident = _data[0]
|
||||||
|
algodata = _data[1]
|
||||||
|
ident = ident
|
||||||
|
return Electron(ident, algodata, algo_name)
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
import json
|
|
||||||
import pathlib
|
|
||||||
from copy import deepcopy
|
|
||||||
|
|
||||||
import toml
|
|
||||||
|
|
||||||
import heurams.services.hasher as hasher
|
|
||||||
from heurams.services.logger import get_logger
|
|
||||||
|
|
||||||
from .electron import Electron
|
|
||||||
from .nucleon import Nucleon
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def load_nucleon(path: pathlib.Path, fmt="toml"):
|
|
||||||
logger.debug("load_nucleon: 加载文件 %s, 格式: %s", path, fmt)
|
|
||||||
with open(path, "r") as f:
|
|
||||||
dictdata = dict()
|
|
||||||
dictdata = toml.load(f) # type: ignore
|
|
||||||
logger.debug("TOML 解析成功, keys: %s", list(dictdata.keys()))
|
|
||||||
lst = list()
|
|
||||||
nested_data = dict()
|
|
||||||
# 修正 toml 解析器的不管嵌套行为
|
|
||||||
for key, value in dictdata.items():
|
|
||||||
if "__metadata__" in key: # 以免影响句号
|
|
||||||
if "." in key:
|
|
||||||
parts = key.split(".")
|
|
||||||
current = nested_data
|
|
||||||
for part in parts[:-1]:
|
|
||||||
if part not in current:
|
|
||||||
current[part] = {}
|
|
||||||
current = current[part]
|
|
||||||
current[parts[-1]] = value
|
|
||||||
logger.debug("处理元数据键: %s", key)
|
|
||||||
else:
|
|
||||||
nested_data[key] = value
|
|
||||||
logger.debug("嵌套数据处理完成, keys: %s", list(nested_data.keys()))
|
|
||||||
# print(nested_data)
|
|
||||||
for item, attr in nested_data.items():
|
|
||||||
if item == "__metadata__":
|
|
||||||
continue
|
|
||||||
logger.debug("处理项目: %s", item)
|
|
||||||
lst.append(
|
|
||||||
(
|
|
||||||
Nucleon(item, attr, deepcopy(nested_data["__metadata__"])),
|
|
||||||
deepcopy(nested_data["__metadata__"]["orbital"]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
logger.debug("load_nucleon 完成, 加载了 %d 个 Nucleon 对象", len(lst))
|
|
||||||
return lst
|
|
||||||
|
|
||||||
|
|
||||||
def load_electron(path: pathlib.Path, fmt="json") -> dict:
|
|
||||||
"""从文件路径加载电子对象
|
|
||||||
|
|
||||||
Args:
|
|
||||||
path (pathlib.Path): 路径
|
|
||||||
fmt (str): 文件格式(可选, 默认 json)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 键名是电子对象名称, 值是电子对象
|
|
||||||
"""
|
|
||||||
logger.debug("load_electron: 加载文件 %s, 格式: %s", path, fmt)
|
|
||||||
with open(path, "r") as f:
|
|
||||||
dictdata = dict()
|
|
||||||
dictdata = json.load(f) # type: ignore
|
|
||||||
logger.debug("JSON 解析成功, keys: %s", list(dictdata.keys()))
|
|
||||||
dic = dict()
|
|
||||||
for item, attr in dictdata.items():
|
|
||||||
logger.debug("处理电子项目: %s, %s", item, attr)
|
|
||||||
dic[item] = Electron(item, attr)
|
|
||||||
logger.debug("load_electron 完成, 加载了 %d 个 Electron 对象", len(dic))
|
|
||||||
return dic
|
|
||||||
@@ -1,56 +1,62 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
from logging import config
|
||||||
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
from heurams.utils.evalizor import Evalizer
|
||||||
|
from heurams.context import config_var
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Nucleon:
|
class Nucleon:
|
||||||
"""原子核: 材料元数据"""
|
"""原子核: 带有运行时隔离的模板化只读材料元数据容器"""
|
||||||
|
|
||||||
def __init__(self, ident: str, payload: dict, metadata: dict = {}):
|
def __init__(self, ident, payload, common):
|
||||||
"""初始化原子核 (记忆内容)
|
|
||||||
|
|
||||||
Args:
|
|
||||||
ident: 唯一标识符
|
|
||||||
payload: 记忆内容信息
|
|
||||||
metadata: 可选元数据信息
|
|
||||||
"""
|
|
||||||
logger.debug(
|
|
||||||
"创建 Nucleon 实例, ident: '%s', payload keys: %s, metadata keys: %s",
|
|
||||||
ident,
|
|
||||||
list(payload.keys()) if payload else [],
|
|
||||||
list(metadata.keys()) if metadata else [],
|
|
||||||
)
|
|
||||||
self.metadata = metadata
|
|
||||||
self.payload = payload
|
|
||||||
self.ident = ident
|
self.ident = ident
|
||||||
logger.debug("Nucleon 初始化完成")
|
env = {"payload": payload,
|
||||||
|
"default": config_var.get()['puzzles'],
|
||||||
|
"nucleon": (payload | common)}
|
||||||
|
self.evalizer = Evalizer(environment=env)
|
||||||
|
self.data: dict = self.evalizer(deepcopy((payload | common))) # type: ignore
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
logger.debug("Nucleon.__getitem__: key='%s'", key)
|
if isinstance(key, str):
|
||||||
if key == "ident":
|
if key == "ident":
|
||||||
logger.debug("返回 ident: '%s'", self.ident)
|
return self.ident
|
||||||
return self.ident
|
return self.data[key]
|
||||||
if key in self.payload:
|
|
||||||
value = self.payload[key]
|
|
||||||
logger.debug(
|
|
||||||
"返回 payload['%s'], value type: %s", key, type(value).__name__
|
|
||||||
)
|
|
||||||
return value
|
|
||||||
else:
|
else:
|
||||||
logger.error("键 '%s' 未在 payload 中找到", key)
|
raise AttributeError
|
||||||
raise KeyError(f"Key '{key}' not found in payload.")
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
raise AttributeError("应为只读")
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
raise AttributeError("应为只读")
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
yield from self.payload.keys()
|
return iter(self.data)
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key in (self.data)
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
if key in self:
|
||||||
|
return self[key]
|
||||||
|
return default
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self.payload)
|
return len(self.data)
|
||||||
|
|
||||||
def __hash__(self):
|
def __repr__(self):
|
||||||
return hash(self.ident)
|
from pprint import pformat
|
||||||
|
|
||||||
|
s = pformat(self.data, indent=4)
|
||||||
|
return s
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def placeholder():
|
def create_on_nucleonic_data(nucleonic_data: tuple):
|
||||||
"""生成一个占位原子核"""
|
_data = nucleonic_data
|
||||||
logger.debug("创建 Nucleon 占位符")
|
payload = _data[1][0]
|
||||||
return Nucleon("核子对象样例内容", {})
|
common = _data[1][1]
|
||||||
|
ident = _data[0] # TODO:实现eval
|
||||||
|
return Nucleon(ident, payload, common)
|
||||||
|
|||||||
@@ -1,30 +1,17 @@
|
|||||||
from typing import TypedDict
|
"""轨道对象"""
|
||||||
|
|
||||||
from heurams.services.logger import get_logger
|
# 似乎没有实现这个类的必要...
|
||||||
|
# 那不妨在这儿写点文档
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
"""
|
||||||
logger.debug("Orbital 类型定义模块已加载")
|
orbital, 即轨道, 是定义队列式复习阶段流程的数据结构, 其实就是个字典, 至于为何不用typeddict, 因为懒.
|
||||||
|
|
||||||
|
orbital_example = {
|
||||||
class OrbitalSchedule(TypedDict):
|
"schedule": [列表 存储阶段(phases)名称]
|
||||||
quick_review: list
|
"phases":{
|
||||||
recognition: list
|
阶段名称 = [["谜题(puzzle 现称 evaluator 评估器)名称", "概率系数 可大于1(整数部分为重复次数) 注意使用字符串包裹(toml 规范)"], ...],
|
||||||
final_review: list
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
class Orbital(TypedDict):
|
至于谜题定义 放在 nucleon['puzzles'], 这样设计是为了兼容多种不同谜题实现的记忆单元, 尽管如此, 你也可见其谜题调度方式必须是相同的.
|
||||||
schedule: OrbitalSchedule
|
|
||||||
puzzles: dict
|
|
||||||
|
|
||||||
|
|
||||||
"""一份示例
|
|
||||||
["__metadata__.orbital.puzzles"] # 谜题定义
|
|
||||||
"Recognition" = { __origin__ = "recognition", __hint__ = "", primary = "eval:nucleon['content']", secondery = ["eval:nucleon['keyword_note']", "eval:nucleon['note']"], top_dim = ["eval:nucleon['translation']"] }
|
|
||||||
"SelectMeaning" = { __origin__ = "mcq", __hint__ = "eval:nucleon['content']", jammer = "eval:nucleon['keyword_note']", max_riddles_num = "eval:default['mcq']['max_riddles_num']", prefix = "选择正确项: " }
|
|
||||||
"FillBlank" = { __origin__ = "cloze", __hint__ = "", text = "eval:nucleon['content']", delimiter = "eval:metadata['formation']['delimiter']", min_denominator = "eval:default['cloze']['min_denominator']"}
|
|
||||||
|
|
||||||
["__metadata__.orbital.schedule"] # 内置的推荐学习方案
|
|
||||||
quick_review = [["FillBlank", "1.0"], ["SelectMeaning", "0.5"], ["recognition", "1.0"]]
|
|
||||||
recognition = [["recognition", "1.0"]]
|
|
||||||
final_review = [["FillBlank", "0.7"], ["SelectMeaning", "0.7"], ["recognition", "1.0"]]
|
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
import pathlib
|
|
||||||
|
|
||||||
from heurams.context import config_var
|
|
||||||
from heurams.services.logger import get_logger
|
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def probe_by_filename(filename):
|
|
||||||
"""探测指定文件 (无扩展名) 的所有信息"""
|
|
||||||
logger.debug("probe_by_filename: 探测文件 '%s'", filename)
|
|
||||||
paths: dict = config_var.get().get("paths")
|
|
||||||
logger.debug("配置路径: %s", paths)
|
|
||||||
formats = ["toml", "json"]
|
|
||||||
result = {}
|
|
||||||
for item, attr in paths.items():
|
|
||||||
for i in formats:
|
|
||||||
attr: pathlib.Path = pathlib.Path(attr) / filename + "." + i
|
|
||||||
if attr.exists():
|
|
||||||
logger.debug("找到文件: %s", attr)
|
|
||||||
result[item.replace("_dir", "")] = str(attr)
|
|
||||||
else:
|
|
||||||
logger.debug("文件不存在: %s", attr)
|
|
||||||
logger.debug("probe_by_filename 结果: %s", result)
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def probe_all(is_stem=1):
|
|
||||||
"""依据目录探测所有信息
|
|
||||||
|
|
||||||
Args:
|
|
||||||
is_stem (boolean): 是否**删除**文件扩展名
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
dict: 有三项, 每一项的键名都是文件组类型, 值都是文件组列表, 只包含文件名
|
|
||||||
"""
|
|
||||||
logger.debug("probe_all: 开始探测, is_stem=%d", is_stem)
|
|
||||||
paths: dict = config_var.get().get("paths")
|
|
||||||
logger.debug("配置路径: %s", paths)
|
|
||||||
result = {}
|
|
||||||
for item, attr in paths.items():
|
|
||||||
attr: pathlib.Path = pathlib.Path(attr)
|
|
||||||
result[item.replace("_dir", "")] = list()
|
|
||||||
logger.debug("扫描目录: %s", attr)
|
|
||||||
file_count = 0
|
|
||||||
for i in attr.iterdir():
|
|
||||||
if not i.is_dir():
|
|
||||||
file_count += 1
|
|
||||||
if is_stem:
|
|
||||||
result[item.replace("_dir", "")].append(str(i.stem))
|
|
||||||
else:
|
|
||||||
result[item.replace("_dir", "")].append(str(i.name))
|
|
||||||
logger.debug("目录 %s 中找到 %d 个文件", attr, file_count)
|
|
||||||
logger.debug("probe_all 完成, 结果 keys: %s", list(result.keys()))
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import os
|
|
||||||
|
|
||||||
print(os.getcwd())
|
|
||||||
print(probe_all())
|
|
||||||
1
src/heurams/kernel/reactor/README.md
Normal file
1
src/heurams/kernel/reactor/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# Reactor - 记忆过程状态机模块
|
||||||
@@ -1,45 +1,70 @@
|
|||||||
import random
|
import random
|
||||||
|
|
||||||
|
import heurams.kernel.evaluators as puz
|
||||||
import heurams.kernel.particles as pt
|
import heurams.kernel.particles as pt
|
||||||
import heurams.kernel.puzzles as puz
|
|
||||||
from heurams.services.logger import get_logger
|
from heurams.services.logger import get_logger
|
||||||
|
|
||||||
from .states import PhaserState
|
from .states import PhaserState
|
||||||
|
|
||||||
|
|
||||||
class Fission:
|
class Fission:
|
||||||
"""裂变器: 单原子调度展开器"""
|
"""单原子调度展开器"""
|
||||||
|
|
||||||
def __init__(self, atom: pt.Atom, phase=PhaserState.RECOGNITION):
|
def __init__(self, atom: pt.Atom, phase_state=PhaserState.RECOGNITION):
|
||||||
|
self.cursor = 0
|
||||||
self.logger = get_logger(__name__)
|
self.logger = get_logger(__name__)
|
||||||
self.atom = atom
|
self.atom = atom
|
||||||
# print(f"{phase.value}")
|
|
||||||
self.orbital_schedule = atom.registry["orbital"]["schedule"][phase.value] # type: ignore
|
# NOTE: phase 为 PhaserState 枚举实例,需要获取其value
|
||||||
self.orbital_puzzles = atom.registry["orbital"]["puzzles"]
|
phase_value = (
|
||||||
# print(self.orbital_schedule)
|
phase_state.value if isinstance(phase_state, PhaserState) else phase_state
|
||||||
|
)
|
||||||
|
|
||||||
|
self.orbital_schedule = atom.registry['orbital']["phases"][phase_value] # type: ignore
|
||||||
|
self.orbital_puzzles = atom.registry["nucleon"]["puzzles"]
|
||||||
|
|
||||||
self.puzzles = list()
|
self.puzzles = list()
|
||||||
for item, possibility in self.orbital_schedule: # type: ignore
|
for item, possibility in self.orbital_schedule: # type: ignore
|
||||||
print(f"ad:{item}")
|
self.logger.debug(f"开始处理: {item}")
|
||||||
self.logger.debug(f"开始处理 orbital 项: {item}")
|
|
||||||
if not isinstance(possibility, float):
|
if not isinstance(possibility, float):
|
||||||
possibility = float(possibility)
|
possibility = float(possibility)
|
||||||
|
|
||||||
while possibility > 1:
|
while possibility > 1:
|
||||||
self.puzzles.append(
|
self.puzzles.append(
|
||||||
{
|
{
|
||||||
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
||||||
"alia": item,
|
"alia": item,
|
||||||
|
"finished": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
possibility -= 1
|
possibility -= 1
|
||||||
|
|
||||||
if random.random() <= possibility:
|
if random.random() <= possibility:
|
||||||
self.puzzles.append(
|
self.puzzles.append(
|
||||||
{
|
{
|
||||||
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
"puzzle": puz.puzzles[self.orbital_puzzles[item]["__origin__"]],
|
||||||
"alia": item,
|
"alia": item,
|
||||||
|
"finished": 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
print(f"ok:{item}")
|
|
||||||
self.logger.debug(f"orbital 项处理完成: {item}")
|
self.logger.debug(f"orbital 项处理完成: {item}")
|
||||||
|
|
||||||
def generate(self):
|
def get_puzzles(self):
|
||||||
yield from self.puzzles
|
return self.puzzles
|
||||||
|
|
||||||
|
def get_current_puzzle(self, forward = 0):
|
||||||
|
if forward:
|
||||||
|
if len(self.puzzles) <= self.cursor + 1:
|
||||||
|
return 0
|
||||||
|
self.cursor += 1
|
||||||
|
return self.puzzles[self.cursor]
|
||||||
|
else:
|
||||||
|
return self.puzzles[self.cursor]
|
||||||
|
|
||||||
|
|
||||||
|
def check_passed(self):
|
||||||
|
for i in self.puzzles:
|
||||||
|
if i["finished"] == 0:
|
||||||
|
return 0
|
||||||
|
return 1
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
# 移相器类定义
|
|
||||||
|
|
||||||
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 .procession import Procession
|
from .procession import Procession
|
||||||
from .states import PhaserState, ProcessionState
|
from .states import PhaserState, ProcessionState
|
||||||
@@ -9,43 +8,133 @@ from .states import PhaserState, ProcessionState
|
|||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Phaser:
|
class Phaser(Machine):
|
||||||
"""移相器: 全局调度阶段管理器"""
|
"""全局调度阶段管理器"""
|
||||||
|
|
||||||
def __init__(self, atoms: list[pt.Atom]) -> None:
|
def __init__(self, atoms: list[pt.Atom]) -> None:
|
||||||
logger.debug("Phaser.__init__: 原子数量=%d", len(atoms))
|
logger.debug("Phaser.__init__: 原子数量=%d", len(atoms))
|
||||||
|
|
||||||
new_atoms = list()
|
new_atoms = list()
|
||||||
old_atoms = list()
|
old_atoms = list()
|
||||||
self.state = PhaserState.UNSURE
|
|
||||||
for i in atoms:
|
for i in atoms:
|
||||||
if not i.registry["electron"].is_activated():
|
if not i.registry["electron"].is_activated():
|
||||||
new_atoms.append(i)
|
new_atoms.append(i)
|
||||||
else:
|
else:
|
||||||
old_atoms.append(i)
|
old_atoms.append(i)
|
||||||
|
|
||||||
logger.debug("新原子数量=%d, 旧原子数量=%d", len(new_atoms), len(old_atoms))
|
logger.debug("新原子数量=%d, 旧原子数量=%d", len(new_atoms), len(old_atoms))
|
||||||
|
|
||||||
self.processions = list()
|
self.processions = list()
|
||||||
|
# TODO: 改进为基于配置文件的可变复习阶段管理
|
||||||
if len(old_atoms):
|
if len(old_atoms):
|
||||||
self.processions.append(
|
self.processions.append(
|
||||||
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
|
Procession(old_atoms, PhaserState.QUICK_REVIEW, "初始复习")
|
||||||
)
|
)
|
||||||
logger.debug("创建初始复习 Procession")
|
logger.debug("创建初始复习 Procession")
|
||||||
|
|
||||||
if len(new_atoms):
|
if len(new_atoms):
|
||||||
self.processions.append(
|
self.processions.append(
|
||||||
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
|
Procession(new_atoms, PhaserState.RECOGNITION, "新记忆")
|
||||||
)
|
)
|
||||||
logger.debug("创建新记忆 Procession")
|
logger.debug("创建新记忆 Procession")
|
||||||
|
|
||||||
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
|
self.processions.append(Procession(atoms, PhaserState.FINAL_REVIEW, "总体复习"))
|
||||||
logger.debug("创建总体复习 Procession")
|
logger.debug("创建总体复习 Procession")
|
||||||
logger.debug("Phaser 初始化完成, processions 数量=%d", len(self.processions))
|
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"},
|
||||||
|
]
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
Machine.__init__(
|
||||||
|
self,
|
||||||
|
states=states,
|
||||||
|
transitions=transitions,
|
||||||
|
initial=PhaserState.UNSURE.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.to_unsure()
|
||||||
|
|
||||||
|
def on_unsure(self):
|
||||||
|
"""进入UNSURE状态时的回调"""
|
||||||
|
logger.debug("Phaser 进入 UNSURE 状态")
|
||||||
|
|
||||||
|
def on_quick_review(self):
|
||||||
|
"""进入QUICK_REVIEW状态时的回调"""
|
||||||
|
logger.debug("Phaser 进入 QUICK_REVIEW 状态")
|
||||||
|
|
||||||
|
def on_recognition(self):
|
||||||
|
"""进入RECOGNITION状态时的回调"""
|
||||||
|
logger.debug("Phaser 进入 RECOGNITION 状态")
|
||||||
|
|
||||||
|
def on_final_review(self):
|
||||||
|
"""进入FINAL_REVIEW状态时的回调"""
|
||||||
|
logger.debug("Phaser 进入 FINAL_REVIEW 状态")
|
||||||
|
|
||||||
|
def on_finished(self):
|
||||||
|
"""进入FINISHED状态时的回调"""
|
||||||
|
logger.debug("Phaser 进入 FINISHED 状态")
|
||||||
|
|
||||||
def current_procession(self):
|
def current_procession(self):
|
||||||
logger.debug("Phaser.current_procession 被调用")
|
logger.debug("Phaser.current_procession 被调用")
|
||||||
for i in self.processions:
|
for i in self.processions:
|
||||||
i: Procession
|
i: Procession
|
||||||
if not i.state == ProcessionState.FINISHED:
|
if i.state != ProcessionState.FINISHED.value:
|
||||||
self.state = i.phase
|
# 根据当前procession的phase更新Phaser状态
|
||||||
|
if i.phase == PhaserState.QUICK_REVIEW:
|
||||||
|
self.to_quick_review()
|
||||||
|
elif i.phase == PhaserState.RECOGNITION:
|
||||||
|
self.to_recognition()
|
||||||
|
elif i.phase == PhaserState.FINAL_REVIEW:
|
||||||
|
self.to_final_review()
|
||||||
|
|
||||||
logger.debug("找到未完成的 Procession: phase=%s", i.phase)
|
logger.debug("找到未完成的 Procession: phase=%s", i.phase)
|
||||||
return i
|
return i
|
||||||
self.state = PhaserState.FINISHED
|
|
||||||
|
# 所有Procession都已完成
|
||||||
|
self.to_finished()
|
||||||
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
|
logger.debug("所有 Procession 已完成, 状态设置为 FINISHED")
|
||||||
return 0
|
return None
|
||||||
|
|
||||||
|
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'
|
||||||
|
|||||||
@@ -1,60 +1,104 @@
|
|||||||
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 tabulate import tabulate as tabu
|
||||||
|
|
||||||
|
from .fission import Fission
|
||||||
from .states import PhaserState, ProcessionState
|
from .states import PhaserState, ProcessionState
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Procession:
|
class Procession(Machine):
|
||||||
"""队列: 标识单次记忆流程"""
|
"""队列: 标识单次记忆流程"""
|
||||||
|
|
||||||
def __init__(self, atoms: list, phase: 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.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]
|
self.current_atom = atoms[0] if atoms else None
|
||||||
self.cursor = 0
|
self.cursor = 0
|
||||||
self.name = name
|
self.name_ = name_
|
||||||
self.phase = phase
|
self.phase = phase_state
|
||||||
self.state: ProcessionState = ProcessionState.RUNNING
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
Machine.__init__(
|
||||||
|
self,
|
||||||
|
states=states,
|
||||||
|
transitions=transitions,
|
||||||
|
initial=ProcessionState.RUNNING.value,
|
||||||
|
)
|
||||||
|
|
||||||
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.queue))
|
logger.debug("Procession 初始化完成, 队列长度=%d", len(self.queue))
|
||||||
|
|
||||||
|
def on_running(self):
|
||||||
|
"""进入RUNNING状态时的回调"""
|
||||||
|
logger.debug("Procession 进入 RUNNING 状态")
|
||||||
|
|
||||||
|
def on_finished(self):
|
||||||
|
"""进入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):
|
||||||
self.state = ProcessionState.FINISHED
|
if self.state != ProcessionState.FINISHED.value:
|
||||||
|
self.finish() # 触发状态转换
|
||||||
logger.debug("Procession 已完成")
|
logger.debug("Procession 已完成")
|
||||||
else:
|
else:
|
||||||
self.state = ProcessionState.RUNNING
|
if self.state != ProcessionState.RUNNING.value:
|
||||||
try:
|
self.restart() # 确保在RUNNING状态
|
||||||
logger.debug("cursor 更新为: %d", self.cursor)
|
|
||||||
self.current_atom = self.queue[self.cursor]
|
self.current_atom = self.queue[self.cursor]
|
||||||
logger.debug("当前原子更新为: %s", self.current_atom.ident)
|
logger.debug("cursor 更新为: %d", self.cursor)
|
||||||
|
logger.debug(
|
||||||
|
"当前原子更新为: %s",
|
||||||
|
self.current_atom.ident if self.current_atom else "None",
|
||||||
|
)
|
||||||
return 1 # 成功
|
return 1 # 成功
|
||||||
except IndexError as e:
|
return 0
|
||||||
logger.debug("IndexError: %s", e)
|
|
||||||
self.state = ProcessionState.FINISHED
|
|
||||||
logger.debug("Procession 因索引错误而完成")
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def append(self, atom=None):
|
def append(self, atom=None):
|
||||||
if atom == 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")
|
||||||
if self.queue[len(self.queue) - 1] != atom or len(self) <= 1:
|
|
||||||
|
if not self.queue or self.queue[-1] != atom or len(self) <= 1:
|
||||||
self.queue.append(atom)
|
self.queue.append(atom)
|
||||||
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.queue))
|
logger.debug("原子已追加到队列, 新队列长度=%d", len(self.queue))
|
||||||
else:
|
else:
|
||||||
logger.debug("原子未追加(重复或队列长度<=1)")
|
logger.debug("原子未追加(重复或队列长度<=1)")
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
|
if not self.queue:
|
||||||
|
return 0
|
||||||
length = len(self.queue) - self.cursor
|
length = len(self.queue) - self.cursor
|
||||||
logger.debug("Procession.__len__: 剩余长度=%d", length)
|
logger.debug("Procession.__len__: 剩余长度=%d", length)
|
||||||
return length
|
return length
|
||||||
@@ -69,6 +113,23 @@ class Procession:
|
|||||||
return total
|
return total
|
||||||
|
|
||||||
def is_empty(self):
|
def is_empty(self):
|
||||||
empty = len(self.queue)
|
empty = len(self.queue) == 0
|
||||||
logger.debug("Procession.is_empty: %d", empty)
|
logger.debug("Procession.is_empty: %s", empty)
|
||||||
return empty
|
return empty
|
||||||
|
|
||||||
|
def get_fission(self):
|
||||||
|
return Fission(atom=self.current_atom, phase_state=self.phase) # type: ignore
|
||||||
|
|
||||||
|
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'
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ class PhaserState(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class ProcessionState(Enum):
|
class ProcessionState(Enum):
|
||||||
RUNNING = auto()
|
RUNNING = "running"
|
||||||
FINISHED = auto()
|
FINISHED = "finished"
|
||||||
|
|
||||||
|
|
||||||
logger.debug("状态枚举定义已加载")
|
logger.debug("状态枚举定义已加载")
|
||||||
|
|||||||
1
src/heurams/kernel/repolib/__init__.py
Normal file
1
src/heurams/kernel/repolib/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .repo import *
|
||||||
5
src/heurams/kernel/repolib/aux.py
Normal file
5
src/heurams/kernel/repolib/aux.py
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
from ...utils.lict import Lict
|
||||||
|
|
||||||
|
|
||||||
|
def merge(x: Lict, y: Lict):
|
||||||
|
return Lict(list(x.values()) + list(y.values()))
|
||||||
3
src/heurams/kernel/repolib/navi.py
Normal file
3
src/heurams/kernel/repolib/navi.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class Navi:
|
||||||
|
def __init__(self, init) -> None:
|
||||||
|
pass
|
||||||
176
src/heurams/kernel/repolib/repo.py
Normal file
176
src/heurams/kernel/repolib/repo.py
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
import json
|
||||||
|
from functools import reduce
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypedDict
|
||||||
|
|
||||||
|
import toml
|
||||||
|
|
||||||
|
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",
|
||||||
|
"payload": "payload.toml",
|
||||||
|
"algodata": "algodata.json",
|
||||||
|
"manifest": "manifest.toml",
|
||||||
|
"typedef": "typedef.toml",
|
||||||
|
}
|
||||||
|
|
||||||
|
type_mapping = {
|
||||||
|
"schedule": "dict",
|
||||||
|
"payload": "lict",
|
||||||
|
"algodata": "lict",
|
||||||
|
"manifest": "dict",
|
||||||
|
"typedef": "dict",
|
||||||
|
}
|
||||||
|
|
||||||
|
default_save_list = ["algodata"]
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
schedule: dict,
|
||||||
|
payload: Lict,
|
||||||
|
manifest: dict,
|
||||||
|
typedef: dict,
|
||||||
|
algodata: Lict,
|
||||||
|
source=None,
|
||||||
|
) -> None:
|
||||||
|
self.schedule: dict = schedule
|
||||||
|
self.manifest: RepoManifest = manifest # type: ignore
|
||||||
|
self.typedef: dict = typedef
|
||||||
|
self.payload: Lict = payload
|
||||||
|
self.algodata: Lict = algodata
|
||||||
|
self.source: Path | None = source # 若存在, 指向 repo 所在 dir
|
||||||
|
self.database = {
|
||||||
|
"schedule": self.schedule,
|
||||||
|
"payload": self.payload,
|
||||||
|
"manifest": self.manifest,
|
||||||
|
"typedef": self.typedef,
|
||||||
|
"algodata": self.algodata,
|
||||||
|
"source": self.source,
|
||||||
|
}
|
||||||
|
self.generate_particles_data()
|
||||||
|
|
||||||
|
def generate_particles_data(self):
|
||||||
|
|
||||||
|
self.nucleonic_data_lict = Lict(
|
||||||
|
initlist=list(map(self._nucleonic_proc, self.payload))
|
||||||
|
)
|
||||||
|
self.orbitic_data = self.schedule
|
||||||
|
self.ident_index = self.nucleonic_data_lict.keys()
|
||||||
|
for i in self.ident_index:
|
||||||
|
self.algodata.append_new((i, {}))
|
||||||
|
self.electronic_data_lict = self.algodata
|
||||||
|
|
||||||
|
def _nucleonic_proc(self, unit):
|
||||||
|
ident = unit[0]
|
||||||
|
common = self.typedef["common"]
|
||||||
|
return (ident, (unit[1], common))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _merge(value):
|
||||||
|
def inner(x):
|
||||||
|
return (x, value)
|
||||||
|
|
||||||
|
return inner
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.payload)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
from pprint import pformat
|
||||||
|
|
||||||
|
s = pformat(self.database, indent=4)
|
||||||
|
return s
|
||||||
|
|
||||||
|
def persist_to_repodir(
|
||||||
|
self, save_list: list | None = None, source: Path | None = None
|
||||||
|
):
|
||||||
|
if save_list == None:
|
||||||
|
save_list = self.default_save_list
|
||||||
|
if self.source != None and source == None:
|
||||||
|
source = self.source
|
||||||
|
if source == None:
|
||||||
|
raise FileNotFoundError("不存在仓库到文件的映射")
|
||||||
|
source.mkdir(parents=True, exist_ok=False)
|
||||||
|
for keyname in save_list:
|
||||||
|
filename = self.file_mapping[keyname]
|
||||||
|
with open(source / filename, "w") as f:
|
||||||
|
try:
|
||||||
|
dict_data = self.database[keyname].dicted_data
|
||||||
|
except:
|
||||||
|
dict_data = dict(self.database[keyname])
|
||||||
|
if filename.endswith("toml"):
|
||||||
|
toml.dump(dict_data, f)
|
||||||
|
elif filename.endswith("json"):
|
||||||
|
json.dump(dict_data, f)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的文件类型: {filename}")
|
||||||
|
|
||||||
|
def export_to_single_dict(self):
|
||||||
|
return self.database
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_new_repo(cls, source=None):
|
||||||
|
default_database = {
|
||||||
|
"schedule": {},
|
||||||
|
"payload": Lict([]),
|
||||||
|
"algodata": Lict([]),
|
||||||
|
"manifest": {},
|
||||||
|
"typedef": {},
|
||||||
|
"source": source,
|
||||||
|
}
|
||||||
|
return Repo(**default_database)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_repodir(cls, source: Path):
|
||||||
|
database = {}
|
||||||
|
for keyname, filename in cls.file_mapping.items():
|
||||||
|
with open(source / filename, "r") as f:
|
||||||
|
loaded: dict
|
||||||
|
if filename.endswith("toml"):
|
||||||
|
loaded = toml.load(f)
|
||||||
|
elif filename.endswith("json"):
|
||||||
|
loaded = json.load(f)
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的文件类型: {filename}")
|
||||||
|
if cls.type_mapping[keyname] == "lict":
|
||||||
|
database[keyname] = Lict(list(loaded.items()))
|
||||||
|
elif cls.type_mapping[keyname] == "dict":
|
||||||
|
database[keyname] = loaded
|
||||||
|
else:
|
||||||
|
raise ValueError(f"不支持的数据容器: {cls.type_mapping[keyname]}")
|
||||||
|
database["source"] = source
|
||||||
|
return Repo(**database)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create_from_single_dict(cls, dictdata, source: Path | None = None):
|
||||||
|
database = dictdata
|
||||||
|
database["source"] = source
|
||||||
|
return Repo(**database)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def check_repodir(cls, source: Path):
|
||||||
|
try:
|
||||||
|
cls.create_from_repodir(source)
|
||||||
|
return 1
|
||||||
|
except:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def probe_vaild_repos_in_dir(cls, folder: Path):
|
||||||
|
lst = list()
|
||||||
|
for i in folder.iterdir():
|
||||||
|
if i.is_dir():
|
||||||
|
if cls.check_repodir(i):
|
||||||
|
lst.append(i)
|
||||||
|
return lst
|
||||||
4
src/heurams/services/textproc.py
Normal file
4
src/heurams/services/textproc.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
def truncate(text):
|
||||||
|
if len(text) <= 3:
|
||||||
|
return text
|
||||||
|
return text[:3] + ">"
|
||||||
@@ -3,8 +3,9 @@ from heurams.services.logger import get_logger
|
|||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
|
|
||||||
ver = "0.4.3"
|
ver = "0.5.0"
|
||||||
stage = "prototype"
|
stage = "prototype"
|
||||||
codename = "fledge" # 雏鸟, 0.4.x 版本
|
codename = "fulcrom"
|
||||||
|
codename_cn = "支点"
|
||||||
|
|
||||||
logger.info("HeurAMS 版本: %s (%s), 阶段: %s", ver, codename, stage)
|
logger.info("HeurAMS 版本: %s (%s), 阶段: %s", ver, codename, stage)
|
||||||
|
|||||||
@@ -1,2 +0,0 @@
|
|||||||
# Utils - 实用工具
|
|
||||||
脚本与部分分离式工具函数
|
|
||||||
33
src/heurams/utils/evalizor.py
Normal file
33
src/heurams/utils/evalizor.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
class Evalizer:
|
||||||
|
"""几乎无副作用的模板系统
|
||||||
|
|
||||||
|
接受环境信息并创建一个模板解析工具, 工具传入参数支持list, dict及其嵌套
|
||||||
|
副作用问题: 仅存在于 eval 函数
|
||||||
|
"""
|
||||||
|
|
||||||
|
# TODO: 弃用风险极高的 eval
|
||||||
|
# TODO: 异步/多线程执行避免堵塞
|
||||||
|
def __init__(self, environment: dict) -> None:
|
||||||
|
self.env = environment
|
||||||
|
|
||||||
|
def __call__(self, anyobj):
|
||||||
|
return self.travel(anyobj)
|
||||||
|
|
||||||
|
def travel(self, anyobj):
|
||||||
|
if isinstance(anyobj, list):
|
||||||
|
return list(map(self.travel, anyobj))
|
||||||
|
elif isinstance(anyobj, dict):
|
||||||
|
return dict(map(self.travel, anyobj.items()))
|
||||||
|
elif isinstance(anyobj, tuple):
|
||||||
|
return tuple(map(self.travel, anyobj))
|
||||||
|
elif isinstance(anyobj, str):
|
||||||
|
if anyobj.startswith("eval:"):
|
||||||
|
return self.eval_with_env(anyobj[5:])
|
||||||
|
else:
|
||||||
|
return anyobj
|
||||||
|
else:
|
||||||
|
return anyobj
|
||||||
|
|
||||||
|
def eval_with_env(self, s: str):
|
||||||
|
ret = eval(s, globals(), self.env)
|
||||||
|
return ret
|
||||||
149
src/heurams/utils/lict.py
Normal file
149
src/heurams/utils/lict.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
from collections import UserList
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
|
||||||
|
class Lict(UserList): # TODO: 优化同步(惰性同步), 当前性能为 O(n)
|
||||||
|
""" "列典" 对象
|
||||||
|
|
||||||
|
同时兼容字典和列表大多数 API, 两边数据同步的容器
|
||||||
|
列表数据是 dictobj.items() 的格式
|
||||||
|
支持根据字典或列表初始化
|
||||||
|
限制要求:
|
||||||
|
- 键名一定唯一, 且仅能为字符串
|
||||||
|
- 值一定是引用对象
|
||||||
|
- 不使用并发
|
||||||
|
- 不在乎列表顺序语义(严格按键名字符序排列)和列表索引查找, 因此外部的 sort, index 等功能不可用
|
||||||
|
- append 的元组中, 表示键名的元素不能重复, 否则会导致覆盖行为
|
||||||
|
|
||||||
|
只有在 Python 3.7+ 中, forced_order 行为才能被取消.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
initlist: list | None = None,
|
||||||
|
initdict: dict | None = None,
|
||||||
|
forced_order=False,
|
||||||
|
):
|
||||||
|
self.dicted_data = {}
|
||||||
|
if initdict != None:
|
||||||
|
initlist = list(initdict.items())
|
||||||
|
super().__init__(initlist=initlist)
|
||||||
|
self.forced_order = forced_order
|
||||||
|
self._sync_based_on_list()
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def _sync_based_on_dict(self):
|
||||||
|
self.data = list(self.dicted_data.items())
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def _sync_based_on_list(self):
|
||||||
|
self.dicted_data = {}
|
||||||
|
for i in self.data:
|
||||||
|
self.dicted_data[i[0]] = i[1]
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator:
|
||||||
|
return self.data.__iter__()
|
||||||
|
|
||||||
|
def __getitem__(self, i):
|
||||||
|
if isinstance(i, str):
|
||||||
|
return self.dicted_data[i]
|
||||||
|
else:
|
||||||
|
return super().__getitem__(i)
|
||||||
|
|
||||||
|
def get_itemic_unit(self, ident):
|
||||||
|
return (ident, self.dicted_data[ident])
|
||||||
|
|
||||||
|
def __setitem__(self, i, item):
|
||||||
|
if isinstance(i, str):
|
||||||
|
self.dicted_data[i] = item
|
||||||
|
self._sync_based_on_dict()
|
||||||
|
else:
|
||||||
|
if item != (item[0], item[1]):
|
||||||
|
raise NotImplementedError
|
||||||
|
super().__setitem__(i, item)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
|
||||||
|
def __delitem__(self, i):
|
||||||
|
if isinstance(i, str):
|
||||||
|
del self.dicted_data[i]
|
||||||
|
self._sync_based_on_dict()
|
||||||
|
else:
|
||||||
|
super().__delitem__(i)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.data or item in self.keys() or item in self.values()
|
||||||
|
|
||||||
|
def append(self, item: Any) -> None:
|
||||||
|
if item != (item[0], item[1]):
|
||||||
|
raise NotImplementedError
|
||||||
|
super().append(item)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def append_new(self, item: Any):
|
||||||
|
if item != (item[0], item[1]):
|
||||||
|
raise NotImplementedError
|
||||||
|
if item[0] not in self:
|
||||||
|
super().append(item)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def insert(self, i: int, item: Any) -> None:
|
||||||
|
if item != (item[0], item[1]): # 确保 item 是遵从限制的元组
|
||||||
|
raise NotImplementedError
|
||||||
|
super().insert(i, item)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def pop(self, i: int = -1) -> Any:
|
||||||
|
res = super().pop(i)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def remove(self, item: Any) -> None:
|
||||||
|
if isinstance(item, str):
|
||||||
|
item = (item, self.dicted_data[item])
|
||||||
|
if item != (item[0], item[1]):
|
||||||
|
raise NotImplementedError
|
||||||
|
super().remove(item)
|
||||||
|
self._sync_based_on_list()
|
||||||
|
if self.forced_order:
|
||||||
|
self.data.sort()
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
super().clear()
|
||||||
|
self._sync_based_on_list()
|
||||||
|
|
||||||
|
def index(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def extend(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def sort(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def reverse(self):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
return self.dicted_data.keys()
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
return self.dicted_data.values()
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def keys_equal_with(self, other):
|
||||||
|
return self.key_equality(self, other)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def key_equality(cls, a, b):
|
||||||
|
return a.keys() == b.keys()
|
||||||
241
src/heurams/utils/refvar.py
Normal file
241
src/heurams/utils/refvar.py
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
class RefVar:
|
||||||
|
def __init__(self, initvalue) -> None:
|
||||||
|
self.data = initvalue
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
return f"RefVar({repr(self.data)})"
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return str(self.data)
|
||||||
|
|
||||||
|
def __add__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data + other.data)
|
||||||
|
return RefVar(self.data + other)
|
||||||
|
|
||||||
|
def __radd__(self, other):
|
||||||
|
return RefVar(other + self.data)
|
||||||
|
|
||||||
|
def __sub__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data - other.data)
|
||||||
|
return RefVar(self.data - other)
|
||||||
|
|
||||||
|
def __rsub__(self, other):
|
||||||
|
return RefVar(other - self.data)
|
||||||
|
|
||||||
|
def __mul__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data * other.data)
|
||||||
|
return RefVar(self.data * other)
|
||||||
|
|
||||||
|
def __rmul__(self, other):
|
||||||
|
return RefVar(other * self.data)
|
||||||
|
|
||||||
|
def __truediv__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data / other.data)
|
||||||
|
return RefVar(self.data / other)
|
||||||
|
|
||||||
|
def __rtruediv__(self, other):
|
||||||
|
return RefVar(other / self.data)
|
||||||
|
|
||||||
|
def __floordiv__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data // other.data)
|
||||||
|
return RefVar(self.data // other)
|
||||||
|
|
||||||
|
def __rfloordiv__(self, other):
|
||||||
|
return RefVar(other // self.data)
|
||||||
|
|
||||||
|
def __mod__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data % other.data)
|
||||||
|
return RefVar(self.data % other)
|
||||||
|
|
||||||
|
def __rmod__(self, other):
|
||||||
|
return RefVar(other % self.data)
|
||||||
|
|
||||||
|
def __pow__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data**other.data)
|
||||||
|
return RefVar(self.data**other)
|
||||||
|
|
||||||
|
def __rpow__(self, other):
|
||||||
|
return RefVar(other**self.data)
|
||||||
|
|
||||||
|
def __neg__(self):
|
||||||
|
return RefVar(-self.data)
|
||||||
|
|
||||||
|
def __pos__(self):
|
||||||
|
return RefVar(+self.data)
|
||||||
|
|
||||||
|
def __abs__(self):
|
||||||
|
return RefVar(abs(self.data))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return self.data == other.data
|
||||||
|
return self.data == other
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return self.data < other.data
|
||||||
|
return self.data < other
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return self.data <= other.data
|
||||||
|
return self.data <= other
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return self.data > other.data
|
||||||
|
return self.data > other
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return self.data >= other.data
|
||||||
|
return self.data >= other
|
||||||
|
|
||||||
|
# 位运算
|
||||||
|
def __and__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data & other.data)
|
||||||
|
return RefVar(self.data & other)
|
||||||
|
|
||||||
|
def __rand__(self, other):
|
||||||
|
return RefVar(other & self.data)
|
||||||
|
|
||||||
|
def __or__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data | other.data)
|
||||||
|
return RefVar(self.data | other)
|
||||||
|
|
||||||
|
def __ror__(self, other):
|
||||||
|
return RefVar(other | self.data)
|
||||||
|
|
||||||
|
def __xor__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data ^ other.data)
|
||||||
|
return RefVar(self.data ^ other)
|
||||||
|
|
||||||
|
def __rxor__(self, other):
|
||||||
|
return RefVar(other ^ self.data)
|
||||||
|
|
||||||
|
def __lshift__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data << other.data)
|
||||||
|
return RefVar(self.data << other)
|
||||||
|
|
||||||
|
def __rlshift__(self, other):
|
||||||
|
return RefVar(other << self.data)
|
||||||
|
|
||||||
|
def __rshift__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
return RefVar(self.data >> other.data)
|
||||||
|
return RefVar(self.data >> other)
|
||||||
|
|
||||||
|
def __rrshift__(self, other):
|
||||||
|
return RefVar(other >> self.data)
|
||||||
|
|
||||||
|
def __invert__(self):
|
||||||
|
return RefVar(~self.data)
|
||||||
|
|
||||||
|
# 类型转换
|
||||||
|
def __int__(self):
|
||||||
|
return int(self.data)
|
||||||
|
|
||||||
|
def __float__(self):
|
||||||
|
return float(self.data)
|
||||||
|
|
||||||
|
def __bool__(self):
|
||||||
|
return bool(self.data)
|
||||||
|
|
||||||
|
def __complex__(self):
|
||||||
|
return complex(self.data)
|
||||||
|
|
||||||
|
def __bytes__(self):
|
||||||
|
return bytes(self.data)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self.data)
|
||||||
|
|
||||||
|
# 容器操作(如果底层数据支持)
|
||||||
|
def __len__(self):
|
||||||
|
return len(self.data)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
return self.data[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self.data[key] = value
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self.data[key]
|
||||||
|
|
||||||
|
def __contains__(self, item):
|
||||||
|
return item in self.data
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter(self.data)
|
||||||
|
|
||||||
|
def __iadd__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data += other.data
|
||||||
|
else:
|
||||||
|
self.data += other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __isub__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data -= other.data
|
||||||
|
else:
|
||||||
|
self.data -= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __imul__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data *= other.data
|
||||||
|
else:
|
||||||
|
self.data *= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __itruediv__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data /= other.data
|
||||||
|
else:
|
||||||
|
self.data /= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ifloordiv__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data //= other.data
|
||||||
|
else:
|
||||||
|
self.data //= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __imod__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data %= other.data
|
||||||
|
else:
|
||||||
|
self.data %= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __ipow__(self, other):
|
||||||
|
if isinstance(other, RefVar):
|
||||||
|
self.data **= other.data
|
||||||
|
else:
|
||||||
|
self.data **= other
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
if callable(self.data):
|
||||||
|
return self.data(*args, **kwargs)
|
||||||
|
raise TypeError(f"'{type(self.data).__name__}' object is not callable")
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self.data, name)
|
||||||
@@ -10,8 +10,12 @@ from unittest.mock import MagicMock, Mock, patch
|
|||||||
|
|
||||||
from heurams.context import ConfigContext
|
from heurams.context import ConfigContext
|
||||||
from heurams.services.config import ConfigFile
|
from heurams.services.config import ConfigFile
|
||||||
from heurams.services.sync_service import (ConflictStrategy, SyncConfig,
|
from heurams.services.sync_service import (
|
||||||
SyncMode, SyncService)
|
ConflictStrategy,
|
||||||
|
SyncConfig,
|
||||||
|
SyncMode,
|
||||||
|
SyncService,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestSyncServiceUnit(unittest.TestCase):
|
class TestSyncServiceUnit(unittest.TestCase):
|
||||||
@@ -202,8 +206,7 @@ class TestSyncServiceUnit(unittest.TestCase):
|
|||||||
mock_config.data = config_data
|
mock_config.data = config_data
|
||||||
mock_config_var.get.return_value = mock_config
|
mock_config_var.get.return_value = mock_config
|
||||||
|
|
||||||
from heurams.services.sync_service import \
|
from heurams.services.sync_service import create_sync_service_from_config
|
||||||
create_sync_service_from_config
|
|
||||||
|
|
||||||
service = create_sync_service_from_config()
|
service = create_sync_service_from_config()
|
||||||
|
|
||||||
|
|||||||
@@ -174,7 +174,7 @@ class TestSM2Algorithm(unittest.TestCase):
|
|||||||
def test_rate(self):
|
def test_rate(self):
|
||||||
"""测试 rate 方法"""
|
"""测试 rate 方法"""
|
||||||
algodata = {SM2Algorithm.algo_name: {"efactor": 2.7}}
|
algodata = {SM2Algorithm.algo_name: {"efactor": 2.7}}
|
||||||
self.assertEqual(SM2Algorithm.rate(algodata), "2.7")
|
self.assertEqual(SM2Algorithm.get_rating(algodata), "2.7")
|
||||||
|
|
||||||
def test_nextdate(self):
|
def test_nextdate(self):
|
||||||
"""测试 nextdate 方法"""
|
"""测试 nextdate 方法"""
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class TestElectron(unittest.TestCase):
|
|||||||
electron = Electron("test_electron")
|
electron = Electron("test_electron")
|
||||||
with patch.object(electron.algo, "rate") as mock_rate:
|
with patch.object(electron.algo, "rate") as mock_rate:
|
||||||
mock_rate.return_value = "good"
|
mock_rate.return_value = "good"
|
||||||
result = electron.get_rate()
|
result = electron.get_rating()
|
||||||
mock_rate.assert_called_once_with(electron.algodata)
|
mock_rate.assert_called_once_with(electron.algodata)
|
||||||
self.assertEqual(result, "good")
|
self.assertEqual(result, "good")
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
from heurams.kernel.puzzles.base import BasePuzzle
|
from heurams.kernel.evaluators.base import BasePuzzle
|
||||||
|
|
||||||
|
|
||||||
class TestBasePuzzle(unittest.TestCase):
|
class TestBasePuzzle(unittest.TestCase):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, patch
|
from unittest.mock import MagicMock, patch
|
||||||
|
|
||||||
from heurams.kernel.puzzles.cloze import ClozePuzzle
|
from heurams.kernel.evaluators.cloze import ClozePuzzle
|
||||||
|
|
||||||
|
|
||||||
class TestClozePuzzle(unittest.TestCase):
|
class TestClozePuzzle(unittest.TestCase):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import MagicMock, call, patch
|
from unittest.mock import MagicMock, call, patch
|
||||||
|
|
||||||
from heurams.kernel.puzzles.mcq import MCQPuzzle
|
from heurams.kernel.evaluators.mcq import MCQPuzzle
|
||||||
|
|
||||||
|
|
||||||
class TestMCQPuzzle(unittest.TestCase):
|
class TestMCQPuzzle(unittest.TestCase):
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, Mock, patch
|
|||||||
|
|
||||||
from heurams.kernel.particles.atom import Atom
|
from heurams.kernel.particles.atom import Atom
|
||||||
from heurams.kernel.particles.electron import Electron
|
from heurams.kernel.particles.electron import Electron
|
||||||
from heurams.kernel.reactor.phaser import Phaser
|
from heurams.kernel.reactor.procession import Phaser
|
||||||
from heurams.kernel.reactor.states import PhaserState, ProcessionState
|
from heurams.kernel.reactor.states import PhaserState, ProcessionState
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user