@nm-lang/cli 0.5.0 → 0.7.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.
@@ -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