@nm-lang/cli 0.4.0 → 0.6.0
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.
- package/README.md +229 -166
- package/bin/bridge-host.mjs +78 -0
- package/bin/bridge-worker.mjs +27 -0
- package/bin/nm.mjs +5 -1
- package/bin/project-create.d.mts +27 -0
- package/bin/project-create.mjs +122 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +120 -25
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +7 -2
- package/python/LICENSE.txt +57 -0
- package/python/NOTICE.txt +11 -0
- package/python/README.md +174 -0
- package/python/examples/magic.ipynb +98 -0
- package/python/examples/training.ipynb +78 -0
- package/python/nm_bridge.py +524 -0
- package/python/nm_jupyter/__init__.py +20 -0
- package/python/nm_jupyter/extension.py +135 -0
- package/python/pyproject.toml +21 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Explicit %%nm cells. This module is only needed with optional IPython.
|
|
2
|
+
|
|
3
|
+
Arguments are data, client names are direct namespace lookups, and the cell is
|
|
4
|
+
passed unchanged as N/M source. No Python evaluation, shell or grants are added.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import argparse
|
|
9
|
+
import math
|
|
10
|
+
import re
|
|
11
|
+
import shlex
|
|
12
|
+
|
|
13
|
+
from IPython.core.error import UsageError
|
|
14
|
+
from IPython.core.magic import no_var_expand
|
|
15
|
+
|
|
16
|
+
from nm_bridge import NMClient
|
|
17
|
+
|
|
18
|
+
_COPY = {
|
|
19
|
+
"en": {
|
|
20
|
+
"arguments": "Invalid %%nm arguments. Choose --client NAME and exactly one of --check, --run or --train.",
|
|
21
|
+
"client": "--client must name an explicitly configured NMClient in this notebook's Python namespace.",
|
|
22
|
+
"training": "Training options require --train and bounded, supported values. Steps: 1-100; learning rate: (0,5]; pilot shots: 2-32; absolute error: (0,1000000].",
|
|
23
|
+
"source": "The cell must contain non-empty N/M source of at most 32768 UTF-8 bytes.",
|
|
24
|
+
},
|
|
25
|
+
"tr": {
|
|
26
|
+
"arguments": "%%nm argümanları geçersiz. --client ADI ve --check, --run veya --train seçeneklerinden tam birini seçin.",
|
|
27
|
+
"client": "--client, bu notebook'un Python ad alanında açıkça yapılandırılmış bir NMClient adı olmalıdır.",
|
|
28
|
+
"training": "Eğitim seçenekleri --train ve sınırlı, desteklenen değerler gerektirir. Adımlar: 1-100; öğrenme oranı: (0,5]; pilot ölçümler: 2-32; mutlak hata: (0,1000000].",
|
|
29
|
+
"source": "Hücre en fazla 32768 UTF-8 baytlık, boş olmayan N/M kaynak içermelidir.",
|
|
30
|
+
},
|
|
31
|
+
}
|
|
32
|
+
_IDENTIFIER = re.compile(r"[A-Za-z_][A-Za-z_0-9]{0,127}\Z")
|
|
33
|
+
_TRAINING_FLAGS = {
|
|
34
|
+
"steps": "steps", "learning_rate": "learningRate", "optimizer": "optimizer",
|
|
35
|
+
"objective": "objective", "objective_target": "objectiveTarget", "gradient_method": "gradientMethod",
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class _ArgumentsError(ValueError):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class _Parser(argparse.ArgumentParser):
|
|
44
|
+
def error(self, message):
|
|
45
|
+
raise _ArgumentsError from None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _parser():
|
|
49
|
+
parser = _Parser(prog="%%nm", add_help=False, allow_abbrev=False)
|
|
50
|
+
parser.add_argument("--client", required=True)
|
|
51
|
+
mode = parser.add_mutually_exclusive_group(required=True)
|
|
52
|
+
for operation in ("check", "run", "train"):
|
|
53
|
+
mode.add_argument("--" + operation, dest="operation", action="store_const", const=operation)
|
|
54
|
+
parser.add_argument("--steps", type=int)
|
|
55
|
+
parser.add_argument("--learning-rate", type=float)
|
|
56
|
+
parser.add_argument("--optimizer", choices=("gd", "momentum", "adam", "spsa", "qng"))
|
|
57
|
+
parser.add_argument("--objective", choices=("minimize", "maximize"))
|
|
58
|
+
parser.add_argument("--objective-target")
|
|
59
|
+
parser.add_argument("--gradient-method", choices=("parameter-shift", "adjoint", "auto"))
|
|
60
|
+
parser.add_argument("--measurement-allocation", choices=("fixed", "adaptive"))
|
|
61
|
+
parser.add_argument("--pilot-shots", type=int)
|
|
62
|
+
parser.add_argument("--absolute-error", type=float)
|
|
63
|
+
return parser
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _fail(key, locale=None):
|
|
67
|
+
message = _COPY[locale][key] if locale in _COPY else _COPY["en"][key] + " / " + _COPY["tr"][key]
|
|
68
|
+
raise UsageError("NM-JUPYTER-001: " + message) from None
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _options(args, locale):
|
|
72
|
+
options = {key: getattr(args, flag) for flag, key in _TRAINING_FLAGS.items() if getattr(args, flag) is not None}
|
|
73
|
+
allocation, pilot, error = args.measurement_allocation, args.pilot_shots, args.absolute_error
|
|
74
|
+
if (args.operation != "train" and (options or allocation is not None or pilot is not None or error is not None)
|
|
75
|
+
or args.steps is not None and not 1 <= args.steps <= 100
|
|
76
|
+
or args.learning_rate is not None and not (math.isfinite(args.learning_rate) and 0 < args.learning_rate <= 5)
|
|
77
|
+
or args.objective_target is not None and not _IDENTIFIER.fullmatch(args.objective_target)
|
|
78
|
+
or pilot is not None and (allocation != "adaptive" or not 2 <= pilot <= 32)
|
|
79
|
+
or error is not None and not (math.isfinite(error) and 0 < error <= 1_000_000)):
|
|
80
|
+
_fail("training", locale)
|
|
81
|
+
if allocation is not None:
|
|
82
|
+
options["measurementAllocation"] = {"method": allocation}
|
|
83
|
+
if pilot is not None:
|
|
84
|
+
options["measurementAllocation"]["pilotShots"] = pilot
|
|
85
|
+
if error is not None:
|
|
86
|
+
options["targetAccuracy"] = {"absoluteError": error}
|
|
87
|
+
return options or None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class _NMCell:
|
|
91
|
+
def __init__(self, shell):
|
|
92
|
+
self.shell = shell
|
|
93
|
+
|
|
94
|
+
def __call__(self, line, cell):
|
|
95
|
+
try:
|
|
96
|
+
if not isinstance(line, str) or len(line.encode("utf-8")) > 4096:
|
|
97
|
+
raise _ArgumentsError
|
|
98
|
+
tokens = shlex.split(line, posix=True)
|
|
99
|
+
flags = [token.partition("=")[0] for token in tokens if token.startswith("--")]
|
|
100
|
+
if len(flags) != len(set(flags)):
|
|
101
|
+
raise _ArgumentsError
|
|
102
|
+
args = _parser().parse_args(tokens)
|
|
103
|
+
except (ValueError, UnicodeError):
|
|
104
|
+
_fail("arguments")
|
|
105
|
+
if not _IDENTIFIER.fullmatch(args.client):
|
|
106
|
+
_fail("client")
|
|
107
|
+
client = self.shell.user_ns.get(args.client)
|
|
108
|
+
if not isinstance(client, NMClient):
|
|
109
|
+
_fail("client")
|
|
110
|
+
options = _options(args, client.locale)
|
|
111
|
+
try:
|
|
112
|
+
if not isinstance(cell, str) or not cell.strip() or len(cell.encode("utf-8")) > 32768:
|
|
113
|
+
_fail("source", client.locale)
|
|
114
|
+
except UnicodeError:
|
|
115
|
+
_fail("source", client.locale)
|
|
116
|
+
if args.operation == "train":
|
|
117
|
+
return client.train(cell, options=options)
|
|
118
|
+
return getattr(client, args.operation)(cell)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def load_ipython_extension(ipython):
|
|
122
|
+
# Registration changes no authority and does not invoke any client.
|
|
123
|
+
current = ipython.find_cell_magic("nm")
|
|
124
|
+
if isinstance(current, _NMCell):
|
|
125
|
+
return
|
|
126
|
+
if current is not None:
|
|
127
|
+
_fail("arguments") # Never overwrite another extension's %%nm.
|
|
128
|
+
# IPython normally expands $names/{Python expressions} in magic arguments.
|
|
129
|
+
# Disable that before parsing so even the argument line remains plain data.
|
|
130
|
+
ipython.register_magic_function(no_var_expand(_NMCell(ipython)), magic_kind="cell", magic_name="nm")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def unload_ipython_extension(ipython):
|
|
134
|
+
if isinstance(ipython.find_cell_magic("nm"), _NMCell):
|
|
135
|
+
del ipython.magics_manager.magics["cell"]["nm"]
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools==80.9.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nm-jupyter-adapter"
|
|
7
|
+
version = "0.0.0.dev0"
|
|
8
|
+
description = "Local development Python/IPython adapter for the separately installed N/M CLI"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "LicenseRef-QuantumSoftware-NM-Free-Use"
|
|
12
|
+
license-files = ["LICENSE.txt", "NOTICE.txt"]
|
|
13
|
+
dependencies = []
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
notebook = ["ipython==9.17.1; python_version >= '3.11'"]
|
|
17
|
+
|
|
18
|
+
[tool.setuptools]
|
|
19
|
+
py-modules = ["nm_bridge"]
|
|
20
|
+
packages = ["nm_jupyter"]
|
|
21
|
+
include-package-data = false
|