算法模板指南
概述
unitarylab_algorithms/template.py 提供了在 UnitaryLab 算法框架中实现新算法的最小化脚手架。通过继承 BaseAlgorithm(unitarylab_algorithms.algo_base.BaseAlgorithm),你的算法将自动获得日志记录、结果格式化、文件保存和统一返回格式等能力。
本指南基于 template.py 与 algo_base.py 的真实源码逐条核对,介绍模板的完整结构、每个组成部分的作用,以及网页端参数注入的约定。
模板完整源码
# unitarylab_algorithms/template.py
import os
import time
from typing import Any, Dict
import numpy as np
from unitarylab.core import Circuit
try:
from .algo_base import BaseAlgorithm
except ImportError:
import sys
_algorithms_dir = os.path.dirname(os.path.abspath(__file__))
if _algorithms_dir not in sys.path:
sys.path.insert(0, _algorithms_dir)
from algo_base import BaseAlgorithm
class ExampleAlgorithm(BaseAlgorithm):
"""Minimal template for adding a new algorithm under algorithms/."""
def __init__(self, text_mode: str = "plain", algo_dir: str = None):
if algo_dir is None:
_this = os.path.abspath(__file__)
_directory = os.path.dirname(_this)
algo_dir = os.path.join(os.getcwd(), "results", os.path.basename(os.path.dirname(_directory)), os.path.basename(_directory))
os.makedirs(algo_dir, exist_ok=True)
# 设置算法名称、前缀
super().__init__(name="Example Algorithm", prefix="EXP", text_mode=text_mode, algo_dir=algo_dir)
def run(self, n: int = 2) -> Dict[str, Any]:
"""
Run the Example algorithm.
Parameters:
n: Number of qubits
Returns:
Dictionary containing algorithm results with fields:
- status: Execution status, 'ok' on success
- circuit_path: Local path to saved quantum circuit diagram (SVG)
- file_path: Local path to saved text file with results
"""
# 首先记录 input 参数信息,然后进行更新,会自动打印参数
input = {"Number of qubits (n)": n}
self.update_input(input)
# 正常的算法执行流程,期间可以通过 self.log 输出日志信息
self.log("Stage 1/4: Building circuit...")
qc = Circuit(n)
# 记录 output 信息,然后进行更新,会自动打印结果
output = {"Result": "Example result", "Elapsed time (s)": 0}
self.update_output(output)
# 记录算法执行状态和总结信息
self.status = "success"
self.summary = f"Execution successful. Example result is {output['Result']}."
# 保存线路图和结果文本
circuit_path = self.save_circuit(qc)
filename = self.save_txt()
# 如果有多个线路图,可以通过 self.save_circuit(qc, name="another") 来保存
circuit_path_1 = self.save_circuit(qc, name="example_1")
circuit_path_2 = self.save_circuit(qc, name="example_2")
circuit_path = [circuit_path_1, circuit_path_2]
# 最后构建返回字典,包含算法执行状态(True/False)、线路图路径、保存的文件路径、量子线路本身
return self._build_return_dict(True, circuit_path, filename, qc)
def test(n: int = 2) -> Dict[str, Any]:
# test 函数用于在本地测试算法,参数 n 需要设置默认值
# legacy 模式下使用富文本,plain 模式下使用纯文本
algo = ExampleAlgorithm(text_mode="legacy")
return algo.run(n=n)
if __name__ == "__main__":
# test 中输入的参数后加上 `# [PARAM]` 注释,和 parameters.json 中的参数名称一致,网页端运行时会自动替换为用户输入的参数值
# 不需要修改的参数就不用加 `# [PARAM]` 注释,保持默认值即可
n = 2 # [PARAM]
test(n=n)逐步指南:编写新算法
第一步——创建文件
在 unitarylab_algorithms/ 下创建目录和文件:
unitarylab_algorithms/
└── my_category/
└── my_algo/
├── __init__.py
├── algorithm.py
└── parameters.json第二步——导入 BaseAlgorithm 并实现 __init__
import os
from typing import Any, Dict
from unitarylab.core import Circuit
from unitarylab_algorithms.algo_base import BaseAlgorithm
class MyAlgorithm(BaseAlgorithm):
"""本算法功能的简要描述。"""
def __init__(self, text_mode: str = "plain", algo_dir: str = None):
if algo_dir is None:
_this = os.path.abspath(__file__)
_directory = os.path.dirname(_this)
algo_dir = os.path.join(
os.getcwd(), "results",
os.path.basename(os.path.dirname(_directory)),
os.path.basename(_directory),
)
os.makedirs(algo_dir, exist_ok=True)
super().__init__(name="My Algorithm", prefix="MYA", text_mode=text_mode, algo_dir=algo_dir)模板并未省略
__init__——每个算法都需要通过super().__init__(name=..., prefix=..., text_mode=..., algo_dir=...)显式设置算法名称与前缀(prefix会自动加上方括号,如未显式传入则取name前 3 个大写字符,如EXP)。algo_dir若不指定,则按results/<父目录名>/<当前目录名>的规则自动生成并创建。
第三步——定义 .run() 方法
def run(self, param_a: int, param_b: float = 1.0) -> Dict[str, Any]:
...第四步——在开头调用 update_input
始终记录输入参数,使其出现在格式化结果和保存的文本中(update_input 会自动打印 "Starting {name}" 与参数列表):
self.update_input({'param_a': param_a, 'param_b': param_b})第五步——构建并执行线路
qc = Circuit(param_a)
qc.h(0)
qc.cx(0, 1)
result = qc.execute()第六步——记录执行状态与摘要
self.status、self.summary 是需要手动赋值的普通属性,二者会分别出现在 format_result_ascii() 输出的 “Status” 与 “Summary” 小节中:
self.status = "success"
self.summary = f"Execution successful. Result is {some_value}."第七步——保存线路图与结果文本
circuit_path = self.save_circuit(qc, name='my_algo_circuit') # 返回完整文件路径(.svg)
filename = self.save_txt() # 返回仅文件名(不含目录!)若一次运行需要保存多张线路图,可多次调用 save_circuit 并将各路径收集为列表:
circuit_path_1 = self.save_circuit(qc, name="stage_1")
circuit_path_2 = self.save_circuit(qc, name="stage_2")
circuit_path = [circuit_path_1, circuit_path_2]
save_circuit返回完整路径({algo_dir}/{name}.svg),而save_txt只返回文件名(不含目录前缀),二者的返回值形态不同,混用时需注意。
第八步——记录输出并通过 _build_return_dict 构造返回值
self.update_output({'my_custom_field': some_value})
self.log(f"计算完成:{some_value}")
return self._build_return_dict(True, circuit_path, filename, qc)_build_return_dict(success, circuit_path, filepath, circuit=None) 的行为(源自 algo_base.py):
success: bool→ 转换为字符串'ok'(True)或'failed'(False),作为返回字典的status字段;与self.status属性是两个独立的值,并不会互相同步。filepath若为字符串会自动包装为单元素列表;随后为每个文件名构造{"format": filename[-3:], "filename": filename}。新增算法的结果文件建议使用 3 字符扩展名(如.txt、.svg、.npy)。circuit_path原样放入返回字典(可以是字符串,也可以是上一步的列表)。- 最终返回
{"status", "circuit_path", "plot", "circuit"}与self.output合并后的字典(dict.update()返回None,源码用result.update(self.output) or result这种写法返回合并后的result,等价于先update再返回,不是逻辑缺陷,只是写法特殊)。
第九步——添加 test() 函数
每个算法都应提供带合理默认值的模块级 test() 函数,约定使用 text_mode="legacy"(真实模板中的 test() 明确使用 legacy 富文本模式,而非默认的 plain):
def test(param_a=3, param_b=1.0):
algo = MyAlgorithm(text_mode="legacy")
return algo.run(param_a=param_a, param_b=param_b)第十步——__main__ 块与 # [PARAM] 注入约定
模板的 __main__ 块并非普通的本地测试入口,而是与网页端参数注入机制绑定:
if __name__ == "__main__":
param_a = 3 # [PARAM]
param_b = 1.0 # [PARAM]
test(param_a=param_a, param_b=param_b)- 需要允许用户在网页端调整的参数,其赋值行末尾需加上
# [PARAM]注释;该变量名必须与parameters.json中声明的参数名一致——网页端运行算法时,会将# [PARAM]标记的赋值语句替换为用户实际输入的参数值。 - 不需要用户调整(即无需暴露给网页端)的参数,保持默认值、不加
# [PARAM]注释即可。 - 该约定并非仅是模板中的示例写法:当前共有 31 个 Python 源文件实际使用
# [PARAM]标记(30 个算法模块及template.py;README 中对该标记的文字说明不计入),是各算法__main__块的通用约定,新增算法时应遵循。
BaseAlgorithm 方法参考
| 方法 | 使用时机 |
|---|---|
super().__init__(name, prefix="", text_mode="plain", algo_dir=None) | 子类 __init__ 中调用,设置算法名称/前缀/文本模式/结果目录;同时初始化 self.status=""、self.input={}、self.output={}、self.info=[]、self.summary="" |
self.update_input(dict) | 在 .run() 开头记录输入参数,合并进 self.input 并自动打印 |
self.update_output(dict) | 计算完成后记录输出值,合并进 self.output 并自动打印 |
self.log(message) | 传入字符串:打印并追加到 self.info;传入字典:逐条打印 - key = value(不追加到 self.info) |
self.status = "..." / self.summary = "..." | 直接属性赋值,用于 format_result_ascii() 中的 “Status”/“Summary” 小节,不会自动同步到 .run() 返回字典的 status 字段 |
self.save_circuit(circuit, name=None) | 将线路持久化为 SVG,返回完整文件路径;name 缺省时按算法名生成文件名 |
self.save_txt() | 将 format_result_ascii() 写入文本文件,返回仅文件名(不含目录) |
self.format_result_ascii() | 以字符串形式获取格式化结果(text_mode="plain" 为纯文本,"legacy" 带表情符号装饰) |
self._build_return_dict(success, circuit_path, filepath, circuit=None) | 构造并返回 .run() 的标准返回字典,见上文”第八步” |
完整最小示例
# unitarylab_algorithms/my_category/my_algo/algorithm.py
import os
from typing import Dict, Any
from unitarylab_algorithms.algo_base import BaseAlgorithm
from unitarylab.core import Circuit
class MyAlgorithm(BaseAlgorithm):
"""示例:对所有 n 个量子比特施加 H 门,并输出概率分布。"""
def __init__(self, text_mode: str = "plain", algo_dir: str = None):
if algo_dir is None:
_this = os.path.abspath(__file__)
_directory = os.path.dirname(_this)
algo_dir = os.path.join(
os.getcwd(), "results",
os.path.basename(os.path.dirname(_directory)),
os.path.basename(_directory),
)
os.makedirs(algo_dir, exist_ok=True)
super().__init__(name="My Algorithm", prefix="MYA", text_mode=text_mode, algo_dir=algo_dir)
def run(self, n: int = 3) -> Dict[str, Any]:
self.update_input({'n': n})
self.log("Stage 1/2: Building circuit...")
qc = Circuit(n)
for i in range(n):
qc.h(i)
sim_result = qc.execute()
probs = sim_result.probabilities
self.update_output({'probabilities': probs})
self.status = "success"
self.summary = f"对 {n} 个量子比特施加 H 门,均匀分布:{len(probs)} 个态。"
circuit_path = self.save_circuit(qc, name='h_all')
filename = self.save_txt()
return self._build_return_dict(True, circuit_path, filename, qc)
def test(n=3):
algo = MyAlgorithm(text_mode="legacy")
return algo.run(n=n)
if __name__ == "__main__":
n = 3 # [PARAM]
test(n=n)注册与使用
创建算法后,可直接导入使用,无需任何注册步骤:
from unitarylab_algorithms.my_category.my_algo.algorithm import MyAlgorithm
algo = MyAlgorithm()
result = algo.run(n=4)
print(result['status'])框架使用直接导入方式,无需集中注册。网页端参数面板依赖 parameters.json(参数名、类型、默认值、描述)与源码中的 # [PARAM] 标记配合工作,新增算法时两者的参数名必须保持一致。