catalyst-q 0.1.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.
- catalyst_hmk/__init__.py +18 -0
- catalyst_hmk/client.py +440 -0
- catalyst_hmk/cloudflare.py +56 -0
- catalyst_hmk/hmk.py +223 -0
- catalyst_hmk/identity.py +57 -0
- catalyst_hmk/licensing.py +157 -0
- catalyst_q/__init__.py +41 -0
- catalyst_q/benchmark_cli.py +50 -0
- catalyst_q/benchmarks.py +466 -0
- catalyst_q/circuit.py +125 -0
- catalyst_q/solvers.py +83 -0
- catalyst_q-0.1.0.dist-info/METADATA +87 -0
- catalyst_q-0.1.0.dist-info/RECORD +16 -0
- catalyst_q-0.1.0.dist-info/WHEEL +5 -0
- catalyst_q-0.1.0.dist-info/entry_points.txt +2 -0
- catalyst_q-0.1.0.dist-info/top_level.txt +2 -0
catalyst_hmk/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .client import CatalystClient, CatalystQClient, PreparedRequest, execute_prepared_request
|
|
2
|
+
from .cloudflare import CloudflareDeploymentSpec, RenderedDeployment
|
|
3
|
+
from .hmk import HypervectorMemoryKey
|
|
4
|
+
from .identity import InstallIdentity
|
|
5
|
+
from .licensing import FreemiumLimitError, UsagePolicy
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"CatalystClient",
|
|
9
|
+
"CatalystQClient",
|
|
10
|
+
"CloudflareDeploymentSpec",
|
|
11
|
+
"FreemiumLimitError",
|
|
12
|
+
"HypervectorMemoryKey",
|
|
13
|
+
"InstallIdentity",
|
|
14
|
+
"PreparedRequest",
|
|
15
|
+
"RenderedDeployment",
|
|
16
|
+
"UsagePolicy",
|
|
17
|
+
"execute_prepared_request",
|
|
18
|
+
]
|
catalyst_hmk/client.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
|
|
9
|
+
from urllib import error, request
|
|
10
|
+
|
|
11
|
+
from .hmk import HypervectorMemoryKey
|
|
12
|
+
from .identity import InstallIdentity
|
|
13
|
+
from .licensing import UsagePolicy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(frozen=True)
|
|
17
|
+
class PreparedRequest:
|
|
18
|
+
method: str
|
|
19
|
+
url: str
|
|
20
|
+
headers: Dict[str, str]
|
|
21
|
+
json: Dict[str, Any]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
Transport = Callable[[PreparedRequest, float], Tuple[int, Dict[str, str], bytes]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class CatalystClient:
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
api_key: Optional[str] = None,
|
|
31
|
+
base_url: str = "https://api.strategic-innovations.ai/v3turbo",
|
|
32
|
+
control_plane_url: str = "https://catalyst-q-sdk.strategic-innovations.workers.dev",
|
|
33
|
+
policy: Optional[UsagePolicy] = None,
|
|
34
|
+
install_identity: Optional[InstallIdentity] = None,
|
|
35
|
+
) -> None:
|
|
36
|
+
self.api_key = api_key
|
|
37
|
+
self.base_url = base_url.rstrip("/")
|
|
38
|
+
self.control_plane_url = control_plane_url.rstrip("/")
|
|
39
|
+
self.policy = policy or UsagePolicy.free()
|
|
40
|
+
self.install_identity = install_identity
|
|
41
|
+
|
|
42
|
+
def prepare_execute_request(
|
|
43
|
+
self,
|
|
44
|
+
circuit: List[Dict[str, Any]],
|
|
45
|
+
hmk: HypervectorMemoryKey,
|
|
46
|
+
calls_this_month: int,
|
|
47
|
+
) -> PreparedRequest:
|
|
48
|
+
return self.prepare_execute(circuit, hmk=hmk, calls_this_month=calls_this_month)
|
|
49
|
+
|
|
50
|
+
def prepare_execute(
|
|
51
|
+
self,
|
|
52
|
+
circuit: Union[List[Dict[str, Any]], Any],
|
|
53
|
+
hmk: HypervectorMemoryKey,
|
|
54
|
+
calls_this_month: int,
|
|
55
|
+
qubits: Optional[int] = None,
|
|
56
|
+
shots: Optional[int] = None,
|
|
57
|
+
compute_units_this_month: int = 0,
|
|
58
|
+
production: bool = False,
|
|
59
|
+
) -> PreparedRequest:
|
|
60
|
+
circuit_payload = _circuit_payload(circuit)
|
|
61
|
+
resolved_qubits = qubits or circuit_payload.get("qubits") or _infer_qubits(circuit_payload["circuit"])
|
|
62
|
+
gate_count = max(1, len(circuit_payload["circuit"]))
|
|
63
|
+
requested_compute_units = _circuit_compute_units(resolved_qubits, gate_count, shots)
|
|
64
|
+
body: Dict[str, Any] = {
|
|
65
|
+
"qubits": resolved_qubits,
|
|
66
|
+
"circuit": circuit_payload["circuit"],
|
|
67
|
+
"hmk": hmk.to_wire(),
|
|
68
|
+
"state_mode": "hmk_state_in_payload",
|
|
69
|
+
"registration": self._identity().to_wire(),
|
|
70
|
+
"billing_estimate": {
|
|
71
|
+
"circuit_runs": 1,
|
|
72
|
+
"qubits": resolved_qubits,
|
|
73
|
+
"gate_count": gate_count,
|
|
74
|
+
"shots": shots or 1,
|
|
75
|
+
"compute_units": requested_compute_units,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
if shots is not None:
|
|
79
|
+
body["shots"] = shots
|
|
80
|
+
self.policy.check_circuit_run(
|
|
81
|
+
"execute",
|
|
82
|
+
circuit_runs_this_month=calls_this_month,
|
|
83
|
+
compute_units_this_month=compute_units_this_month,
|
|
84
|
+
qubits=resolved_qubits,
|
|
85
|
+
requested_compute_units=requested_compute_units,
|
|
86
|
+
production=production,
|
|
87
|
+
)
|
|
88
|
+
return self._request("/execute", body)
|
|
89
|
+
|
|
90
|
+
def prepare_qasm(
|
|
91
|
+
self,
|
|
92
|
+
qasm: str,
|
|
93
|
+
hmk: HypervectorMemoryKey,
|
|
94
|
+
calls_this_month: int,
|
|
95
|
+
qubits: Optional[int] = None,
|
|
96
|
+
shots: Optional[int] = None,
|
|
97
|
+
output_format: Optional[str] = None,
|
|
98
|
+
compute_units_this_month: int = 0,
|
|
99
|
+
production: bool = False,
|
|
100
|
+
) -> PreparedRequest:
|
|
101
|
+
resolved_qubits = qubits or _infer_qasm_qubits(qasm)
|
|
102
|
+
gate_count = max(1, _infer_qasm_gate_count(qasm))
|
|
103
|
+
requested_compute_units = _circuit_compute_units(resolved_qubits, gate_count, shots)
|
|
104
|
+
body: Dict[str, Any] = {
|
|
105
|
+
"qasm": qasm,
|
|
106
|
+
"hmk": hmk.to_wire(),
|
|
107
|
+
"state_mode": "hmk_state_in_payload",
|
|
108
|
+
"registration": self._identity().to_wire(),
|
|
109
|
+
"billing_estimate": {
|
|
110
|
+
"circuit_runs": 1,
|
|
111
|
+
"qubits": resolved_qubits,
|
|
112
|
+
"gate_count": gate_count,
|
|
113
|
+
"shots": shots or 1,
|
|
114
|
+
"compute_units": requested_compute_units,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
if shots is not None:
|
|
118
|
+
body["shots"] = shots
|
|
119
|
+
if output_format is not None:
|
|
120
|
+
body["output_format"] = output_format
|
|
121
|
+
self.policy.check_circuit_run(
|
|
122
|
+
"execute_qasm",
|
|
123
|
+
circuit_runs_this_month=calls_this_month,
|
|
124
|
+
compute_units_this_month=compute_units_this_month,
|
|
125
|
+
qubits=resolved_qubits,
|
|
126
|
+
requested_compute_units=requested_compute_units,
|
|
127
|
+
production=production,
|
|
128
|
+
)
|
|
129
|
+
return self._request("/execute/qasm", body)
|
|
130
|
+
|
|
131
|
+
def prepare_solver(
|
|
132
|
+
self,
|
|
133
|
+
kind: str,
|
|
134
|
+
payload: Dict[str, Any],
|
|
135
|
+
hmk: HypervectorMemoryKey,
|
|
136
|
+
solver_runs_this_month: int,
|
|
137
|
+
compute_units_this_month: int = 0,
|
|
138
|
+
problem_size: Optional[int] = None,
|
|
139
|
+
requested_compute_units: Optional[int] = None,
|
|
140
|
+
production: bool = False,
|
|
141
|
+
) -> PreparedRequest:
|
|
142
|
+
allowed = {"sat", "tsp", "knapsack", "portfolio"}
|
|
143
|
+
if kind not in allowed:
|
|
144
|
+
raise ValueError(f"solver kind must be one of {sorted(allowed)}")
|
|
145
|
+
resolved_problem_size = problem_size or _infer_problem_size(kind, payload)
|
|
146
|
+
resolved_compute_units = requested_compute_units or _infer_solver_compute_units(
|
|
147
|
+
kind, payload, resolved_problem_size
|
|
148
|
+
)
|
|
149
|
+
body = {
|
|
150
|
+
**payload,
|
|
151
|
+
"hmk": hmk.to_wire(),
|
|
152
|
+
"state_mode": "hmk_state_in_payload",
|
|
153
|
+
"registration": self._identity().to_wire(),
|
|
154
|
+
"billing_estimate": {
|
|
155
|
+
"solver_runs": 1,
|
|
156
|
+
"problem_size": resolved_problem_size,
|
|
157
|
+
"compute_units": resolved_compute_units,
|
|
158
|
+
},
|
|
159
|
+
}
|
|
160
|
+
self.policy.check_solver_run(
|
|
161
|
+
f"solve_{kind}",
|
|
162
|
+
solver_runs_this_month=solver_runs_this_month,
|
|
163
|
+
compute_units_this_month=compute_units_this_month,
|
|
164
|
+
problem_size=resolved_problem_size,
|
|
165
|
+
requested_compute_units=resolved_compute_units,
|
|
166
|
+
production=production,
|
|
167
|
+
)
|
|
168
|
+
return self._request(f"/solve/{kind}", body)
|
|
169
|
+
|
|
170
|
+
def prepare_activation_request(self) -> PreparedRequest:
|
|
171
|
+
identity = self._identity()
|
|
172
|
+
return self._control_request(
|
|
173
|
+
"/v1/installs/activate",
|
|
174
|
+
{
|
|
175
|
+
"install_id": identity.install_id,
|
|
176
|
+
"tier": identity.tier,
|
|
177
|
+
"mode": identity.mode,
|
|
178
|
+
"register_on": identity.register_on,
|
|
179
|
+
"sdk_version": "0.1.0",
|
|
180
|
+
"package": "catalyst-q",
|
|
181
|
+
},
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
def prepare_usage_check_request(
|
|
185
|
+
self,
|
|
186
|
+
operation: str,
|
|
187
|
+
route: str,
|
|
188
|
+
billing_estimate: Dict[str, Any],
|
|
189
|
+
production: bool = False,
|
|
190
|
+
) -> PreparedRequest:
|
|
191
|
+
identity = self._identity()
|
|
192
|
+
return self._control_request(
|
|
193
|
+
"/v1/usage/check",
|
|
194
|
+
{
|
|
195
|
+
"install_id": identity.install_id,
|
|
196
|
+
"operation": operation,
|
|
197
|
+
"route": route,
|
|
198
|
+
"billing_estimate": billing_estimate,
|
|
199
|
+
"production": production,
|
|
200
|
+
"sdk_version": "0.1.0",
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def prepare_sat(
|
|
205
|
+
self,
|
|
206
|
+
problem: Any,
|
|
207
|
+
hmk: HypervectorMemoryKey,
|
|
208
|
+
solver_runs_this_month: int,
|
|
209
|
+
compute_units_this_month: int = 0,
|
|
210
|
+
production: bool = False,
|
|
211
|
+
) -> PreparedRequest:
|
|
212
|
+
return self._prepare_problem("sat", problem, hmk, solver_runs_this_month, compute_units_this_month, production)
|
|
213
|
+
|
|
214
|
+
def prepare_tsp(
|
|
215
|
+
self,
|
|
216
|
+
problem: Any,
|
|
217
|
+
hmk: HypervectorMemoryKey,
|
|
218
|
+
solver_runs_this_month: int,
|
|
219
|
+
compute_units_this_month: int = 0,
|
|
220
|
+
production: bool = False,
|
|
221
|
+
) -> PreparedRequest:
|
|
222
|
+
return self._prepare_problem("tsp", problem, hmk, solver_runs_this_month, compute_units_this_month, production)
|
|
223
|
+
|
|
224
|
+
def prepare_knapsack(
|
|
225
|
+
self,
|
|
226
|
+
problem: Any,
|
|
227
|
+
hmk: HypervectorMemoryKey,
|
|
228
|
+
solver_runs_this_month: int,
|
|
229
|
+
compute_units_this_month: int = 0,
|
|
230
|
+
production: bool = False,
|
|
231
|
+
) -> PreparedRequest:
|
|
232
|
+
return self._prepare_problem(
|
|
233
|
+
"knapsack", problem, hmk, solver_runs_this_month, compute_units_this_month, production
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
def prepare_portfolio(
|
|
237
|
+
self,
|
|
238
|
+
problem: Any,
|
|
239
|
+
hmk: HypervectorMemoryKey,
|
|
240
|
+
solver_runs_this_month: int,
|
|
241
|
+
compute_units_this_month: int = 0,
|
|
242
|
+
production: bool = False,
|
|
243
|
+
) -> PreparedRequest:
|
|
244
|
+
return self._prepare_problem(
|
|
245
|
+
"portfolio", problem, hmk, solver_runs_this_month, compute_units_this_month, production
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def _prepare_problem(
|
|
249
|
+
self,
|
|
250
|
+
kind: str,
|
|
251
|
+
problem: Any,
|
|
252
|
+
hmk: HypervectorMemoryKey,
|
|
253
|
+
solver_runs_this_month: int,
|
|
254
|
+
compute_units_this_month: int,
|
|
255
|
+
production: bool,
|
|
256
|
+
) -> PreparedRequest:
|
|
257
|
+
payload = _problem_payload(problem)
|
|
258
|
+
return self.prepare_solver(
|
|
259
|
+
kind,
|
|
260
|
+
payload,
|
|
261
|
+
hmk=hmk,
|
|
262
|
+
solver_runs_this_month=solver_runs_this_month,
|
|
263
|
+
compute_units_this_month=compute_units_this_month,
|
|
264
|
+
problem_size=getattr(problem, "problem_size", None),
|
|
265
|
+
requested_compute_units=getattr(problem, "compute_units", None),
|
|
266
|
+
production=production,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
def _request(self, path: str, body: Dict[str, Any]) -> PreparedRequest:
|
|
270
|
+
identity = self._identity()
|
|
271
|
+
return PreparedRequest(
|
|
272
|
+
method="POST",
|
|
273
|
+
url=f"{self.base_url}{path}",
|
|
274
|
+
headers={
|
|
275
|
+
"Authorization": f"Bearer {self.api_key or 'catalyst-free-anonymous'}",
|
|
276
|
+
"Content-Type": "application/json",
|
|
277
|
+
"User-Agent": "catalyst-q/0.1.0",
|
|
278
|
+
"X-Catalyst-SDK": "catalyst-q/0.1.0",
|
|
279
|
+
"X-Catalyst-API": "catalyst-q",
|
|
280
|
+
"X-Catalyst-Install-ID": identity.install_id,
|
|
281
|
+
"X-Catalyst-Install-Tier": identity.tier,
|
|
282
|
+
"X-Catalyst-Account-Mode": identity.mode,
|
|
283
|
+
},
|
|
284
|
+
json=body,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def _control_request(self, path: str, body: Dict[str, Any]) -> PreparedRequest:
|
|
288
|
+
identity = self._identity()
|
|
289
|
+
return PreparedRequest(
|
|
290
|
+
method="POST",
|
|
291
|
+
url=f"{self.control_plane_url}{path}",
|
|
292
|
+
headers={
|
|
293
|
+
"Authorization": f"Bearer {self.api_key or 'catalyst-free-anonymous'}",
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
"User-Agent": "catalyst-q/0.1.0",
|
|
296
|
+
"X-Catalyst-SDK": "catalyst-q/0.1.0",
|
|
297
|
+
"X-Catalyst-API": "catalyst-q",
|
|
298
|
+
"X-Catalyst-Install-ID": identity.install_id,
|
|
299
|
+
"X-Catalyst-Install-Tier": identity.tier,
|
|
300
|
+
"X-Catalyst-Account-Mode": identity.mode,
|
|
301
|
+
},
|
|
302
|
+
json=body,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def _identity(self) -> InstallIdentity:
|
|
306
|
+
if self.install_identity is None:
|
|
307
|
+
self.install_identity = InstallIdentity.load()
|
|
308
|
+
return self.install_identity
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
CatalystQClient = CatalystClient
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def execute_prepared_request(
|
|
315
|
+
prepared: PreparedRequest,
|
|
316
|
+
timeout: float = 30.0,
|
|
317
|
+
transport: Optional[Transport] = None,
|
|
318
|
+
) -> Dict[str, Any]:
|
|
319
|
+
started = time.perf_counter()
|
|
320
|
+
try:
|
|
321
|
+
status_code, headers, body = (transport or _urllib_transport)(prepared, timeout)
|
|
322
|
+
except Exception as exc: # noqa: BLE001 - benchmark artifacts should capture transport failures.
|
|
323
|
+
latency_ms = (time.perf_counter() - started) * 1000.0
|
|
324
|
+
return {
|
|
325
|
+
"ok": False,
|
|
326
|
+
"status_code": 0,
|
|
327
|
+
"latency_ms": round(latency_ms, 3),
|
|
328
|
+
"response_bytes": 0,
|
|
329
|
+
"response_sha256": hashlib.sha256(b"").hexdigest(),
|
|
330
|
+
"headers": {},
|
|
331
|
+
"error_type": exc.__class__.__name__,
|
|
332
|
+
"error": str(exc),
|
|
333
|
+
}
|
|
334
|
+
latency_ms = (time.perf_counter() - started) * 1000.0
|
|
335
|
+
result: Dict[str, Any] = {
|
|
336
|
+
"ok": 200 <= status_code < 300,
|
|
337
|
+
"status_code": status_code,
|
|
338
|
+
"latency_ms": round(latency_ms, 3),
|
|
339
|
+
"response_bytes": len(body),
|
|
340
|
+
"response_sha256": hashlib.sha256(body).hexdigest(),
|
|
341
|
+
"headers": dict(headers),
|
|
342
|
+
}
|
|
343
|
+
parsed = _try_parse_json(body)
|
|
344
|
+
if parsed is not None:
|
|
345
|
+
result["json"] = parsed
|
|
346
|
+
else:
|
|
347
|
+
result["text_preview"] = body[:512].decode("utf-8", errors="replace")
|
|
348
|
+
return result
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def _urllib_transport(prepared: PreparedRequest, timeout: float) -> Tuple[int, Dict[str, str], bytes]:
|
|
352
|
+
encoded = json.dumps(prepared.json, separators=(",", ":")).encode("utf-8")
|
|
353
|
+
req = request.Request(prepared.url, data=encoded, method=prepared.method, headers=prepared.headers)
|
|
354
|
+
try:
|
|
355
|
+
with request.urlopen(req, timeout=timeout) as response:
|
|
356
|
+
return response.status, dict(response.headers.items()), response.read()
|
|
357
|
+
except error.HTTPError as exc:
|
|
358
|
+
return exc.code, dict(exc.headers.items()), exc.read()
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def _try_parse_json(body: bytes) -> Optional[Any]:
|
|
362
|
+
try:
|
|
363
|
+
return json.loads(body.decode("utf-8"))
|
|
364
|
+
except (UnicodeDecodeError, json.JSONDecodeError):
|
|
365
|
+
return None
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def _circuit_payload(circuit: Union[List[Dict[str, Any]], Any]) -> Dict[str, Any]:
|
|
369
|
+
if hasattr(circuit, "to_payload"):
|
|
370
|
+
payload = circuit.to_payload()
|
|
371
|
+
if not isinstance(payload, dict) or "circuit" not in payload:
|
|
372
|
+
raise ValueError("circuit.to_payload() must return a dict with a circuit key")
|
|
373
|
+
return payload
|
|
374
|
+
return {"circuit": circuit}
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def _problem_payload(problem: Any) -> Dict[str, Any]:
|
|
378
|
+
if hasattr(problem, "to_payload"):
|
|
379
|
+
payload = problem.to_payload()
|
|
380
|
+
if not isinstance(payload, dict):
|
|
381
|
+
raise ValueError("problem.to_payload() must return a dict")
|
|
382
|
+
return payload
|
|
383
|
+
if not isinstance(problem, dict):
|
|
384
|
+
raise ValueError("solver problem must be a problem dataclass or payload dict")
|
|
385
|
+
return problem
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _infer_qubits(circuit: List[Dict[str, Any]]) -> int:
|
|
389
|
+
maximum = 0
|
|
390
|
+
for gate in circuit:
|
|
391
|
+
for key in ("target", "control"):
|
|
392
|
+
if key in gate and isinstance(gate[key], int):
|
|
393
|
+
maximum = max(maximum, gate[key] + 1)
|
|
394
|
+
for key in ("targets", "controls"):
|
|
395
|
+
values = gate.get(key)
|
|
396
|
+
if isinstance(values, list) and values:
|
|
397
|
+
maximum = max(maximum, *[int(value) + 1 for value in values])
|
|
398
|
+
return max(1, maximum)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _infer_qasm_qubits(qasm: str) -> int:
|
|
402
|
+
matches = [int(match.group(1)) for match in re.finditer(r"\bqreg\s+\w+\[(\d+)\]", qasm)]
|
|
403
|
+
return max(matches) if matches else 1
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _infer_qasm_gate_count(qasm: str) -> int:
|
|
407
|
+
ignored = ("OPENQASM", "include", "qreg", "creg", "//")
|
|
408
|
+
count = 0
|
|
409
|
+
for raw_line in qasm.splitlines():
|
|
410
|
+
line = raw_line.strip()
|
|
411
|
+
if not line or line.startswith(ignored):
|
|
412
|
+
continue
|
|
413
|
+
count += 1
|
|
414
|
+
return count
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _circuit_compute_units(qubits: int, gate_count: int, shots: Optional[int]) -> int:
|
|
418
|
+
return max(1, qubits) * max(1, gate_count) * max(1, shots or 1)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def _infer_problem_size(kind: str, payload: Dict[str, Any]) -> int:
|
|
422
|
+
if kind == "sat":
|
|
423
|
+
return int(payload.get("variables", 1))
|
|
424
|
+
if kind == "tsp":
|
|
425
|
+
return len(payload.get("distances", [])) or len(payload.get("cities", [])) or 1
|
|
426
|
+
if kind == "knapsack":
|
|
427
|
+
return len(payload.get("weights", [])) or 1
|
|
428
|
+
if kind == "portfolio":
|
|
429
|
+
return len(payload.get("returns", [])) or 1
|
|
430
|
+
return 1
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def _infer_solver_compute_units(kind: str, payload: Dict[str, Any], problem_size: int) -> int:
|
|
434
|
+
if kind == "sat":
|
|
435
|
+
return max(1, problem_size * len(payload.get("clauses", [])))
|
|
436
|
+
if kind in {"tsp", "portfolio"}:
|
|
437
|
+
return max(1, problem_size * problem_size)
|
|
438
|
+
if kind == "knapsack":
|
|
439
|
+
return max(1, problem_size * max(1, int(payload.get("capacity", 1))))
|
|
440
|
+
return max(1, problem_size)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Dict, List
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class RenderedDeployment:
|
|
9
|
+
files: Dict[str, str]
|
|
10
|
+
deploy_command: List[str]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass(frozen=True)
|
|
14
|
+
class CloudflareDeploymentSpec:
|
|
15
|
+
worker_name: str
|
|
16
|
+
route_prefix: str = "/v3turbo"
|
|
17
|
+
compatibility_date: str = "2026-05-11"
|
|
18
|
+
|
|
19
|
+
def render(self) -> RenderedDeployment:
|
|
20
|
+
wrangler = "\n".join(
|
|
21
|
+
[
|
|
22
|
+
f'name = "{self.worker_name}"',
|
|
23
|
+
'main = "src/worker.ts"',
|
|
24
|
+
f'compatibility_date = "{self.compatibility_date}"',
|
|
25
|
+
"",
|
|
26
|
+
"[vars]",
|
|
27
|
+
'CATALYST_STATE_MODE = "hmk_state_in_payload"',
|
|
28
|
+
'CATALYST_SHARDING = "nonlinear"',
|
|
29
|
+
"",
|
|
30
|
+
]
|
|
31
|
+
)
|
|
32
|
+
worker = "\n".join(
|
|
33
|
+
[
|
|
34
|
+
"export default {",
|
|
35
|
+
" async fetch(request: Request): Promise<Response> {",
|
|
36
|
+
" const url = new URL(request.url);",
|
|
37
|
+
f' if (!url.pathname.startsWith("{self.route_prefix}")) {{',
|
|
38
|
+
' return new Response("not found", { status: 404 });',
|
|
39
|
+
" }",
|
|
40
|
+
" return Response.json({",
|
|
41
|
+
' status: "ready",',
|
|
42
|
+
' state_mode: "hmk_state_in_payload",',
|
|
43
|
+
' sharding: "nonlinear",',
|
|
44
|
+
" });",
|
|
45
|
+
" },",
|
|
46
|
+
"};",
|
|
47
|
+
"",
|
|
48
|
+
]
|
|
49
|
+
)
|
|
50
|
+
return RenderedDeployment(
|
|
51
|
+
files={
|
|
52
|
+
"wrangler.toml": wrangler,
|
|
53
|
+
"src/worker.ts": worker,
|
|
54
|
+
},
|
|
55
|
+
deploy_command=["npx", "wrangler", "deploy"],
|
|
56
|
+
)
|