quafu-sdk 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quafu/__init__.py +60 -0
- quafu/__main__.py +7 -0
- quafu/_conflict.py +121 -0
- quafu/_i18n.py +154 -0
- quafu/_inputs.py +92 -0
- quafu/_messages.py +423 -0
- quafu/_qasm.py +59 -0
- quafu/bits.py +93 -0
- quafu/cli.py +193 -0
- quafu/client.py +563 -0
- quafu/config.py +67 -0
- quafu/credentials.py +260 -0
- quafu/devices.py +91 -0
- quafu/errors.py +350 -0
- quafu/jobs.py +323 -0
- quafu/oauth.py +394 -0
- quafu/qiskit/__init__.py +225 -0
- quafu/tables.py +117 -0
- quafu/transport.py +99 -0
- quafu_sdk-0.2.0.dist-info/METADATA +433 -0
- quafu_sdk-0.2.0.dist-info/RECORD +24 -0
- quafu_sdk-0.2.0.dist-info/WHEEL +4 -0
- quafu_sdk-0.2.0.dist-info/entry_points.txt +3 -0
- quafu_sdk-0.2.0.dist-info/licenses/LICENSE +21 -0
quafu/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""QUAFU 量子计算平台的 Python SDK(发行名 quafu-sdk)。
|
|
3
|
+
|
|
4
|
+
import quafu
|
|
5
|
+
job = quafu.submit(qasm, target='sim') # 第一次用时打印登录链接,在浏览器里登录即可
|
|
6
|
+
print(job.result().counts)
|
|
7
|
+
|
|
8
|
+
quafu.login() / quafu.logout() / quafu.whoami()
|
|
9
|
+
quafu.devices() # 能交到哪些目标
|
|
10
|
+
quafu.job('任务组号') # 接回之前交的任务组
|
|
11
|
+
quafu.sweep(template, parameters, points, target) # 扫参:平台按参数点代入模板
|
|
12
|
+
quafu.open_job(target, ...) # 迭代式任务组:append / wait / table / close
|
|
13
|
+
quafu.device('Dongling').calibration() # 设备详情与校准数据;quafu.calibration(id)
|
|
14
|
+
quafu.stats() # 本人用量与今日额度
|
|
15
|
+
quafu.Client(api_key='qf_…') # 显式身份
|
|
16
|
+
from quafu.qiskit import QuafuProvider, sweep # Qiskit 后端适配与参数化线路扫参(需要 qiskit)
|
|
17
|
+
|
|
18
|
+
运行时只用标准库;Qiskit 线路作为输入时需要 pip install "quafu-sdk[qiskit]"。"""
|
|
19
|
+
import os as _os
|
|
20
|
+
import sys as _sys
|
|
21
|
+
|
|
22
|
+
from ._conflict import ConflictError, check as _check_conflict
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
_check_conflict(_os.path.dirname(_os.path.abspath(__file__)))
|
|
26
|
+
except ConflictError as _e:
|
|
27
|
+
# 命令行 quafu 也要先 import 本包:那里报一句话、退出码 1,不甩 Python 栈
|
|
28
|
+
if _os.path.basename(_sys.argv[0] if _sys.argv else '').lower() in ('quafu', 'quafu.exe'):
|
|
29
|
+
from ._i18n import safe_streams as _safe_streams, t as _t
|
|
30
|
+
_safe_streams()
|
|
31
|
+
_sys.stderr.write(_t('cli.error', _e) + '\n')
|
|
32
|
+
raise SystemExit(1)
|
|
33
|
+
raise
|
|
34
|
+
|
|
35
|
+
__version__ = '0.2.0'
|
|
36
|
+
|
|
37
|
+
from ._i18n import get_language, set_language # noqa: E402
|
|
38
|
+
|
|
39
|
+
from .config import DEFAULT_BASE_URL # noqa: E402
|
|
40
|
+
from .errors import (QuafuError, AuthError, NotLoggedIn, LoginFailed, LoginExpired, LoginRevoked, # noqa: E402
|
|
41
|
+
CredentialsError, BadRequest, PrecheckError, QuotaError, DeviceUnavailable, JobFailed,
|
|
42
|
+
JobCancelled, JobNotFound, JobClosed, TaskNotFound, ServerUnavailable, SubmitUncertain,
|
|
43
|
+
ProtocolError, ClientOutdated, Timeout)
|
|
44
|
+
from .jobs import Job, Result # noqa: E402
|
|
45
|
+
from .tables import ResultTable # noqa: E402
|
|
46
|
+
from .devices import Device, Calibration # noqa: E402
|
|
47
|
+
from .bits import reorder_counts, counts_by_qubit, probabilities, expectation_z # noqa: E402
|
|
48
|
+
from .client import (Client, login, logout, whoami, submit, sweep, open_job, job, devices, device, # noqa: E402
|
|
49
|
+
calibration, stats)
|
|
50
|
+
|
|
51
|
+
__all__ = [
|
|
52
|
+
'__version__', 'DEFAULT_BASE_URL', 'set_language', 'get_language',
|
|
53
|
+
'login', 'logout', 'whoami', 'submit', 'sweep', 'open_job', 'job', 'devices', 'device', 'calibration', 'stats',
|
|
54
|
+
'Client', 'Job', 'Result', 'ResultTable', 'Device', 'Calibration',
|
|
55
|
+
'reorder_counts', 'counts_by_qubit', 'probabilities', 'expectation_z',
|
|
56
|
+
'QuafuError', 'AuthError', 'NotLoggedIn', 'LoginFailed', 'LoginExpired', 'LoginRevoked', 'CredentialsError',
|
|
57
|
+
'BadRequest', 'PrecheckError', 'QuotaError', 'DeviceUnavailable', 'JobFailed', 'JobCancelled', 'JobNotFound', 'JobClosed',
|
|
58
|
+
'TaskNotFound', 'ServerUnavailable', 'SubmitUncertain', 'ProtocolError', 'ClientOutdated', 'Timeout',
|
|
59
|
+
'ConflictError',
|
|
60
|
+
]
|
quafu/__main__.py
ADDED
quafu/_conflict.py
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""导入名冲突检查:import quafu 与命令行 quafu 的第一件事。
|
|
3
|
+
|
|
4
|
+
PyPI 上另有发行包也用导入名 quafu。pip 不检查两个发行包是否写同一个目录,两者装进同一个 Python 环境时,
|
|
5
|
+
文件会装进同一个 site-packages/quafu/、互相覆盖,后装的赢;卸掉其中一个还会删掉另一个的同名文件。
|
|
6
|
+
混合体的行为不可预测,所以一检测到就报错、说明怎么办,而不是带着警告跑下去。
|
|
7
|
+
|
|
8
|
+
两道检查,都只用标准库、只读本地元数据,不联网:
|
|
9
|
+
1. 当前环境里有没有别的发行包也提供顶层名 quafu(读各发行包的 RECORD / top_level.txt;
|
|
10
|
+
冲突包的名字从元数据动态读出,源码里不写死任何旧组件的名字)
|
|
11
|
+
2. 本包目录里有没有不属于本包的条目(别的发行包把文件装进了 quafu/,而它的元数据已经不在了)"""
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
|
|
15
|
+
from ._i18n import t
|
|
16
|
+
|
|
17
|
+
PACKAGE = 'quafu'
|
|
18
|
+
OWN_DISTRIBUTION = 'quafu-sdk'
|
|
19
|
+
# 本包目录下的全部顶层条目(模块去掉 .py)。新增模块时同步这里,tests/unit/test_conflict.py 会核对
|
|
20
|
+
OWN_ENTRIES = frozenset(('__init__', '__pycache__', '__main__', '_conflict', '_i18n', '_inputs', '_messages', '_qasm',
|
|
21
|
+
'bits', 'cli', 'client',
|
|
22
|
+
'config', 'credentials', 'devices', 'errors', 'jobs', 'oauth', 'qiskit', 'tables', 'transport',
|
|
23
|
+
'py.typed'))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ConflictError(ImportError):
|
|
27
|
+
"""当前 Python 环境里还装了另一个占用 quafu 名字的包。"""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def normalize(name):
|
|
31
|
+
return re.sub(r'[-_.]+', '-', name or '').lower()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _provides(dist, package):
|
|
35
|
+
"""这个发行包是否往顶层名 package 下装文件。"""
|
|
36
|
+
try:
|
|
37
|
+
record = dist.read_text('RECORD')
|
|
38
|
+
except Exception:
|
|
39
|
+
record = None
|
|
40
|
+
if record:
|
|
41
|
+
return record.startswith(package + '/') or ('\n%s/' % package) in record
|
|
42
|
+
try:
|
|
43
|
+
top = dist.read_text('top_level.txt')
|
|
44
|
+
except Exception:
|
|
45
|
+
top = None
|
|
46
|
+
return bool(top) and package in top.split()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def other_owners(package=PACKAGE):
|
|
50
|
+
"""除本包外、也提供顶层名 package 的发行包:[(名字, 版本)],按名字排序。"""
|
|
51
|
+
try:
|
|
52
|
+
from importlib import metadata
|
|
53
|
+
except ImportError: # pragma: no cover Python < 3.8
|
|
54
|
+
return []
|
|
55
|
+
found = {}
|
|
56
|
+
for dist in metadata.distributions():
|
|
57
|
+
try:
|
|
58
|
+
name = dist.metadata['Name'] or ''
|
|
59
|
+
except Exception:
|
|
60
|
+
continue
|
|
61
|
+
if normalize(name) == OWN_DISTRIBUTION or not name:
|
|
62
|
+
continue
|
|
63
|
+
if _provides(dist, package):
|
|
64
|
+
found[normalize(name)] = (name, dist.version or '?')
|
|
65
|
+
return [found[k] for k in sorted(found)]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def foreign_entries(package_dir):
|
|
69
|
+
"""本包目录里不属于本包的顶层条目(排好序)。"""
|
|
70
|
+
try:
|
|
71
|
+
names = os.listdir(package_dir)
|
|
72
|
+
except OSError:
|
|
73
|
+
return []
|
|
74
|
+
out = []
|
|
75
|
+
for n in names:
|
|
76
|
+
stem = n[:-3] if n.endswith('.py') else n
|
|
77
|
+
if n.endswith('.pyc') or stem in OWN_ENTRIES:
|
|
78
|
+
continue
|
|
79
|
+
out.append(n)
|
|
80
|
+
return sorted(out)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# 换干净环境的命令:与默认装法 uv 一致,每种终端一块、能整块照抄(#810:没进虚拟环境时 macOS / Ubuntu 24.04 没有
|
|
84
|
+
# python 命令,只用 uv 装 Python 的 Windows 上 python 返回 9009;一块里混写两种终端也照抄不了)。只给当前系统那几块。
|
|
85
|
+
# PowerShell 默认执行策略会拦 Activate.ps1,所以先放开本进程的策略(只对这个窗口有效)。
|
|
86
|
+
_POSIX_COMMANDS = (' uv venv quafu-env\n'
|
|
87
|
+
' source quafu-env/bin/activate\n'
|
|
88
|
+
' uv pip install quafu-sdk')
|
|
89
|
+
_WINDOWS_COMMANDS = (' PowerShell:\n'
|
|
90
|
+
' uv venv quafu-env\n'
|
|
91
|
+
' Set-ExecutionPolicy Bypass -Scope Process -Force\n'
|
|
92
|
+
' quafu-env\\Scripts\\Activate.ps1\n'
|
|
93
|
+
' uv pip install quafu-sdk\n'
|
|
94
|
+
' cmd:\n'
|
|
95
|
+
' uv venv quafu-env\n'
|
|
96
|
+
' quafu-env\\Scripts\\activate.bat\n'
|
|
97
|
+
' uv pip install quafu-sdk')
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def recovery_commands(windows=None):
|
|
101
|
+
"""当前系统上新建干净环境、装本包的命令块(缩进好,可直接接在说明后面)。"""
|
|
102
|
+
if windows is None:
|
|
103
|
+
windows = os.name == 'nt'
|
|
104
|
+
return _WINDOWS_COMMANDS if windows else _POSIX_COMMANDS
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def message(owners, entries, windows=None):
|
|
108
|
+
sep = t('conflict.list_sep')
|
|
109
|
+
if owners:
|
|
110
|
+
what = sep.join('%s %s' % kv for kv in owners)
|
|
111
|
+
else:
|
|
112
|
+
what = t('conflict.foreign_files', sep.join(entries[:5]))
|
|
113
|
+
return t('conflict.message', what, recovery_commands(windows))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def check(package_dir):
|
|
117
|
+
"""有冲突抛 ConflictError。"""
|
|
118
|
+
owners = other_owners()
|
|
119
|
+
entries = foreign_entries(package_dir)
|
|
120
|
+
if owners or entries:
|
|
121
|
+
raise ConflictError(message(owners, entries))
|
quafu/_i18n.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""Display language: English (default) or Chinese, for CLI output, login prompts and exception messages.
|
|
3
|
+
|
|
4
|
+
Which language, first match wins:
|
|
5
|
+
1. quafu.set_language('zh') this process only, not saved (an explicit call in code, like an argument)
|
|
6
|
+
2. environment variable QUAFU_LANG en / zh; also en_US, zh_CN, zh-CN, zh_CN.UTF-8 …; anything else is ignored
|
|
7
|
+
3. saved setting ~/.quafu-sdk/config.json (QUAFU_CONFIG_DIR moves the directory), written by
|
|
8
|
+
`quafu config set language zh` or by pressing Z / E during a browser login;
|
|
9
|
+
kept apart from credentials.json and not split by BASE_URL
|
|
10
|
+
4. default en. The system locale is never consulted: Neo 2026-10-08 wants English by default.
|
|
11
|
+
|
|
12
|
+
Standard library only, and no imports from the rest of the package: _conflict uses it before anything else loads.
|
|
13
|
+
Message text lives in _messages.py."""
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
|
|
17
|
+
from ._messages import MESSAGES
|
|
18
|
+
|
|
19
|
+
LANGUAGES = ('en', 'zh')
|
|
20
|
+
DEFAULT = 'en'
|
|
21
|
+
ENV_LANG = 'QUAFU_LANG'
|
|
22
|
+
ENV_CONFIG_DIR = 'QUAFU_CONFIG_DIR'
|
|
23
|
+
DIR_NAME = '.quafu-sdk'
|
|
24
|
+
SETTINGS_FILE = 'config.json'
|
|
25
|
+
SETTINGS_VERSION = 1
|
|
26
|
+
_INDEX = {'en': 0, 'zh': 1}
|
|
27
|
+
|
|
28
|
+
_override = None # set_language() in this process
|
|
29
|
+
_cache = [None, None, None] # [path, mtime_ns, language]: one stat per lookup instead of reading the file
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def normalize(value):
|
|
33
|
+
"""'zh' / 'zh_CN' / 'zh-CN' / 'zh_CN.UTF-8' / 'ZH' → 'zh'; same for en. Anything else → None."""
|
|
34
|
+
if not isinstance(value, str):
|
|
35
|
+
return None
|
|
36
|
+
v = value.strip().lower().split('.')[0].split('@')[0].replace('_', '-')
|
|
37
|
+
base = v.split('-')[0]
|
|
38
|
+
return base if base in LANGUAGES else None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def config_root():
|
|
42
|
+
"""The SDK's directory on this machine: QUAFU_CONFIG_DIR, else ~/.quafu-sdk. Credentials and settings both live here."""
|
|
43
|
+
d = os.environ.get(ENV_CONFIG_DIR)
|
|
44
|
+
if d:
|
|
45
|
+
return os.path.abspath(os.path.expanduser(d))
|
|
46
|
+
return os.path.join(os.path.abspath(os.path.expanduser('~')), DIR_NAME)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def settings_path():
|
|
50
|
+
return os.path.join(config_root(), SETTINGS_FILE)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def load_settings(path=None):
|
|
54
|
+
"""The settings file as a dict; missing, unreadable or malformed → {} (a broken settings file must not stop the SDK)."""
|
|
55
|
+
path = path or settings_path()
|
|
56
|
+
try:
|
|
57
|
+
with open(path, encoding='utf-8') as f:
|
|
58
|
+
data = json.load(f)
|
|
59
|
+
except (OSError, ValueError):
|
|
60
|
+
return {}
|
|
61
|
+
return data if isinstance(data, dict) else {}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def saved_language():
|
|
65
|
+
"""The language saved in the settings file, or None."""
|
|
66
|
+
path = settings_path()
|
|
67
|
+
try:
|
|
68
|
+
mtime = os.stat(path).st_mtime_ns
|
|
69
|
+
except OSError:
|
|
70
|
+
return None
|
|
71
|
+
if _cache[0] == path and _cache[1] == mtime:
|
|
72
|
+
return _cache[2]
|
|
73
|
+
lang = normalize(load_settings(path).get('language'))
|
|
74
|
+
_cache[:] = [path, mtime, lang]
|
|
75
|
+
return lang
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def save_language(lang):
|
|
79
|
+
"""Save the language in the settings file (other keys kept) and return the path. Raises ValueError for an
|
|
80
|
+
unsupported language and OSError if the file can't be written. Written atomically, like credentials."""
|
|
81
|
+
code = normalize(lang)
|
|
82
|
+
if code is None:
|
|
83
|
+
raise ValueError(t('lang.invalid', lang))
|
|
84
|
+
path = settings_path()
|
|
85
|
+
data = load_settings(path)
|
|
86
|
+
data.update(version=SETTINGS_VERSION, language=code)
|
|
87
|
+
os.makedirs(os.path.dirname(path), mode=0o700, exist_ok=True)
|
|
88
|
+
tmp = '%s.%d.tmp' % (path, os.getpid())
|
|
89
|
+
try:
|
|
90
|
+
with open(tmp, 'w', encoding='utf-8') as f:
|
|
91
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
92
|
+
f.write('\n')
|
|
93
|
+
os.replace(tmp, path)
|
|
94
|
+
finally:
|
|
95
|
+
if os.path.exists(tmp):
|
|
96
|
+
os.remove(tmp)
|
|
97
|
+
_cache[:] = [None, None, None]
|
|
98
|
+
return path
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def resolve():
|
|
102
|
+
"""(language, source); source is 'process', 'env', 'saved' or 'default'."""
|
|
103
|
+
if _override is not None:
|
|
104
|
+
return _override, 'process'
|
|
105
|
+
env = normalize(os.environ.get(ENV_LANG))
|
|
106
|
+
if env is not None:
|
|
107
|
+
return env, 'env'
|
|
108
|
+
saved = saved_language()
|
|
109
|
+
if saved is not None:
|
|
110
|
+
return saved, 'saved'
|
|
111
|
+
return DEFAULT, 'default'
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def get_language():
|
|
115
|
+
"""The display language in effect: 'en' or 'zh'."""
|
|
116
|
+
return resolve()[0]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def set_language(lang):
|
|
120
|
+
"""Set the display language for this process only ('en' or 'zh'; zh_CN-style forms are accepted). It takes
|
|
121
|
+
precedence over QUAFU_LANG and the saved setting and is not saved; set_language(None) removes it.
|
|
122
|
+
To save a choice for every run, use `quafu config set language zh` on the command line."""
|
|
123
|
+
global _override
|
|
124
|
+
if lang is None:
|
|
125
|
+
_override = None
|
|
126
|
+
return None
|
|
127
|
+
code = normalize(lang)
|
|
128
|
+
if code is None:
|
|
129
|
+
raise ValueError(t('lang.invalid', lang))
|
|
130
|
+
_override = code
|
|
131
|
+
return code
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def t(key, *args):
|
|
135
|
+
"""The message `key` in the current language, %-formatted with args."""
|
|
136
|
+
text = MESSAGES[key][_INDEX[get_language()]]
|
|
137
|
+
return text % args if args else text
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def safe_streams():
|
|
141
|
+
"""Command line only: make stdout / stderr replace characters they can't encode instead of raising.
|
|
142
|
+
|
|
143
|
+
Redirected to a file or a pipe, Python encodes output with the locale's code page; on English Windows that is
|
|
144
|
+
cp1252, which can't encode Chinese, so `quafu --help > f.txt` with the Chinese UI crashed with
|
|
145
|
+
UnicodeEncodeError and a traceback (#810). The terminal itself is unaffected (Windows writes the console in
|
|
146
|
+
UTF-16). Only the error handler changes, never the encoding: a file or pipe gets what its reader expects, with
|
|
147
|
+
'?' for what can't be written. Streams without reconfigure() (replaced by tests or an embedding host) are left
|
|
148
|
+
alone; this must never raise."""
|
|
149
|
+
import sys
|
|
150
|
+
for stream in (sys.stdout, sys.stderr):
|
|
151
|
+
try:
|
|
152
|
+
stream.reconfigure(errors='replace')
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
quafu/_inputs.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""交给平台之前的输入规整:参数点表、参数声明、线路项。只做形状与类型检查,取值合不合规由平台判。"""
|
|
3
|
+
import math
|
|
4
|
+
import operator
|
|
5
|
+
|
|
6
|
+
from ._i18n import t
|
|
7
|
+
from ._qasm import is_qiskit_circuit, to_qasm
|
|
8
|
+
|
|
9
|
+
CIRCUIT_KEYS = ('qasm', 'shots', 'name', 'parameter_values', 'iteration', 'evaluation_index', 'metadata')
|
|
10
|
+
PARAMETER_KEYS = ('name', 'unit', 'symbol')
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def number(v, where):
|
|
14
|
+
try:
|
|
15
|
+
x = float(v)
|
|
16
|
+
except (TypeError, ValueError):
|
|
17
|
+
raise TypeError(t('inputs.not_number', where, v)) from None
|
|
18
|
+
except OverflowError:
|
|
19
|
+
raise ValueError(t('inputs.overflow', where)) from None
|
|
20
|
+
if not math.isfinite(x):
|
|
21
|
+
raise ValueError(t('inputs.not_finite', where))
|
|
22
|
+
return x
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
INDEX_MAX = 2 ** 31 - 1 # iteration / evaluation_index 的取值范围 0..2^31−1(契约 D132)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def index(v, where):
|
|
29
|
+
x = operator.index(v)
|
|
30
|
+
if not 0 <= x <= INDEX_MAX:
|
|
31
|
+
raise ValueError(t('inputs.index_range', where, INDEX_MAX, x))
|
|
32
|
+
return x
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def normalize_points(points):
|
|
36
|
+
"""参数点表 → [[float, ...], ...]。收列表或 numpy 数组;某行是单个数时当作一个参数的点。"""
|
|
37
|
+
if hasattr(points, 'tolist'):
|
|
38
|
+
points = points.tolist()
|
|
39
|
+
rows = []
|
|
40
|
+
for i, row in enumerate(points):
|
|
41
|
+
if hasattr(row, 'tolist'):
|
|
42
|
+
row = row.tolist()
|
|
43
|
+
if not isinstance(row, (list, tuple)):
|
|
44
|
+
row = [row]
|
|
45
|
+
rows.append([number(v, 'points[%d]' % i) for v in row])
|
|
46
|
+
return rows
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def normalize_parameters(parameters):
|
|
50
|
+
"""参数声明 → [{name, unit[, symbol]}]。每项写 dict 或 (name, unit[, symbol]);unit 必填,无量纲写 "1"。"""
|
|
51
|
+
out = []
|
|
52
|
+
for p in parameters:
|
|
53
|
+
if isinstance(p, dict):
|
|
54
|
+
extra = set(p) - set(PARAMETER_KEYS)
|
|
55
|
+
if extra:
|
|
56
|
+
raise TypeError(t('inputs.param_unknown_fields', ', '.join(sorted(extra))))
|
|
57
|
+
item = {k: p[k] for k in PARAMETER_KEYS if p.get(k) is not None}
|
|
58
|
+
elif isinstance(p, (tuple, list)) and 2 <= len(p) <= 3:
|
|
59
|
+
item = dict(zip(PARAMETER_KEYS, p))
|
|
60
|
+
else:
|
|
61
|
+
raise TypeError(t('inputs.param_shape'))
|
|
62
|
+
if not item.get('name') or not item.get('unit'):
|
|
63
|
+
raise TypeError(t('inputs.param_required', p))
|
|
64
|
+
out.append(item)
|
|
65
|
+
return out
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def normalize_circuit(item, iteration=None, evaluation_index=None):
|
|
69
|
+
"""一条线路 → 契约的 circuits 项。收 OpenQASM 2 文本或 dict;iteration / evaluation_index 只补给没写的项。"""
|
|
70
|
+
if isinstance(item, str) or is_qiskit_circuit(item):
|
|
71
|
+
item = {'qasm': to_qasm(item)}
|
|
72
|
+
elif not isinstance(item, dict):
|
|
73
|
+
raise TypeError(t('inputs.circuit_type'))
|
|
74
|
+
extra = set(item) - set(CIRCUIT_KEYS)
|
|
75
|
+
if extra:
|
|
76
|
+
raise TypeError(t('inputs.circuit_unknown_fields', ', '.join(sorted(extra))))
|
|
77
|
+
if is_qiskit_circuit(item.get('qasm')):
|
|
78
|
+
item = dict(item, qasm=to_qasm(item['qasm']))
|
|
79
|
+
if not isinstance(item.get('qasm'), str):
|
|
80
|
+
raise TypeError(t('inputs.qasm_type'))
|
|
81
|
+
out = {k: v for k, v in item.items() if v is not None}
|
|
82
|
+
if 'shots' in out:
|
|
83
|
+
out['shots'] = operator.index(out['shots'])
|
|
84
|
+
if 'parameter_values' in out:
|
|
85
|
+
out['parameter_values'] = {str(k): number(v, 'parameter_values[%r]' % k)
|
|
86
|
+
for k, v in out['parameter_values'].items()}
|
|
87
|
+
for key, value in (('iteration', iteration), ('evaluation_index', evaluation_index)):
|
|
88
|
+
if value is not None:
|
|
89
|
+
out.setdefault(key, value)
|
|
90
|
+
if key in out:
|
|
91
|
+
out[key] = index(out[key], key)
|
|
92
|
+
return out
|