Skip to Content

算法模板指南

概述

unitarylab_algorithms/template.py 提供了在 UnitaryLab 算法框架中实现新算法的最小化脚手架。通过继承 BaseAlgorithmunitarylab_algorithms.algo_base.BaseAlgorithm),你的算法将自动获得日志记录、结果格式化、文件保存和统一返回格式等能力。

本指南基于 template.pyalgo_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.statusself.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] 标记配合工作,新增算法时两者的参数名必须保持一致。

最后更新于