openquantum-sdk 0.3.3__tar.gz

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,4 @@
1
+ include README.md
2
+ include requirements*.txt
3
+ recursive-include tests *.py
4
+ global-exclude *.pyc
@@ -0,0 +1,435 @@
1
+ Metadata-Version: 2.4
2
+ Name: openquantum-sdk
3
+ Version: 0.3.3
4
+ Summary: Python SDK for Open Quantum
5
+ Classifier: Programming Language :: Python :: 3
6
+ Classifier: License :: OSI Approved :: Apache Software License
7
+ Classifier: Operating System :: OS Independent
8
+ Requires-Python: >=3.8
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: requests>=2.25.0
11
+
12
+ # Open Quantum Python SDK
13
+
14
+ A lightweight, modern Python SDK for the **Open Quantum Platform**.
15
+
16
+ **Features:**
17
+
18
+ * **Management API** — list organizations, backend classes, and providers
19
+ * **Scheduler API** — job categories, upload, prepare, submit, poll, and **one-call `submit_job()`**
20
+ * **Smart defaults** — auto-select execution plan & queue priority from quote
21
+ * **Thread-safe auth** — Keycloak client-credentials with auto-refresh
22
+ * **Automatic output download** — `download_job_output()` returns parsed JSON
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install -U openquantum-sdk
28
+ # or local dev:
29
+ pip install -e .
30
+ ```
31
+
32
+ Python 3.9+ required.
33
+
34
+ ## Authentication
35
+
36
+ ### Option 1: SDK Key File (Recommended)
37
+
38
+ Download from [Open Quantum SDK Keys](https://openquantum.com/keys):
39
+
40
+ ```json
41
+ {
42
+ "client_id": "s_abc123...",
43
+ "client_secret": "e460c8..."
44
+ }
45
+ ```
46
+
47
+ ```bash
48
+ export OPENQUANTUM_SDK_KEY=/path/to/sdk-key.json
49
+ ```
50
+
51
+ ### Option 2: Direct in Code
52
+
53
+ ```python
54
+ from openquantum_sdk.auth import ClientCredentials, ClientCredentialsAuth
55
+
56
+ auth = ClientCredentialsAuth(
57
+ creds=ClientCredentials(client_id="s_...", client_secret="e460c8...")
58
+ )
59
+ ```
60
+
61
+ ## Quickstart: One-Call Submission (Recommended)
62
+
63
+ ```python
64
+ from openquantum_sdk.clients import SchedulerClient, ManagementClient, JobSubmissionConfig
65
+ from openquantum_sdk.enums import ExecutionPlanType, QueuePriorityType
66
+ import json
67
+
68
+ # 1. Instantiate clients (auto-loads auth from env)
69
+ scheduler = SchedulerClient()
70
+ management = ManagementClient()
71
+
72
+ try:
73
+ # 2. Discover your Organization ID
74
+ print("Discovering organization...")
75
+ orgs = management.list_user_organizations()
76
+ org_id = orgs.organizations[0].id
77
+ print(f" > Using Org: {orgs.organizations[0].name}")
78
+
79
+ # 2.5 Discover available backend providers
80
+ print("\nAvailable providers:")
81
+ providers = management.list_providers()
82
+ for p in providers.providers:
83
+ print(f" > {p.name} ({p.short_code or 'no short code'}) — {p.description}")
84
+
85
+ # 2.6 Discover backend classes
86
+ print("\nAvailable backend classes:")
87
+ classes = management.list_backend_classes()
88
+ for bc in classes.backend_classes:
89
+ print(f" > {bc.name} ({bc.short_code or 'no short code'}) — {bc.type}")
90
+ print(f" ├ Status: {bc.status or 'Unknown'} | Queue depth: {bc.queue_depth or 'N/A'}")
91
+ print(f" └ Use: '{bc.short_code or bc.id}' in backend_class_id")
92
+
93
+ # 3. Define config
94
+ config = JobSubmissionConfig(
95
+ organization_id=org_id,
96
+ backend_class_id="rigetti:ankaa-3",
97
+ name="Bell State SDK Quickstart",
98
+ job_subcategory_id="fin:port", # Finance Portfolio Optimization
99
+ shots=100,
100
+ execution_plan="auto",
101
+ queue_priority="auto",
102
+ auto_approve_quote=True,
103
+ verbose=True
104
+ )
105
+
106
+ # 4. Submit + wait + get result
107
+ dummy_qasm = b"OPENQASM 2.0; include 'qelib1.inc'; qreg q[2]; creg c[2]; h q[0]; cx q[0],q[1]; measure q -> c;"
108
+ job = scheduler.submit_job(config, file_content=dummy_qasm)
109
+
110
+ # 5. Download and print parsed JSON output
111
+ print("\n--- JOB SUCCEEDED ---")
112
+ print(f"Job ID: {job.id}")
113
+ print(f"Status: {job.status}")
114
+
115
+ if job.output_data_url:
116
+ output_json = scheduler.download_job_output(job)
117
+ print("\n--- JOB OUTPUT (JSON) ---")
118
+ print(json.dumps(output_json, indent=2))
119
+ else:
120
+ print("No output available.")
121
+
122
+ except Exception as e:
123
+ print(f"\n--- JOB FAILED ---")
124
+ print(f"Error: {e}")
125
+
126
+ finally:
127
+ scheduler.close()
128
+ management.close()
129
+ ```
130
+
131
+ > Handles **everything** in **one call** — including **automatic JSON download**.
132
+
133
+ ## Quickstart: Low-Level (Advanced)
134
+
135
+ ```python
136
+ from openquantum_sdk.clients import SchedulerClient, ManagementClient
137
+ from openquantum_sdk.models import JobPreparationCreate, JobCreate
138
+ from openquantum_sdk.utils import poll_for_status
139
+ import json
140
+
141
+ scheduler = SchedulerClient()
142
+ management = ManagementClient()
143
+
144
+ try:
145
+ # 1. Get org
146
+ org = management.list_user_organizations().organizations[0]
147
+
148
+ # 1.5 List providers & backend classes
149
+ print("Providers:")
150
+ for p in management.list_providers().providers:
151
+ print(f" - {p.name} ({p.short_code})")
152
+
153
+ print("\nBackend classes:")
154
+ for bc in management.list_backend_classes().backend_classes:
155
+ print(f" - {bc.name} [{bc.type}] — {bc.short_code or bc.id}")
156
+
157
+ # 2. Choose IDs
158
+ MY_BACKEND = "rigetti:ankaa-3"
159
+ MY_SUBCATEGORY = "fin:port"
160
+
161
+ # 3. Upload
162
+ upload_id = scheduler.upload_job_input(file_path="circuit.qasm")
163
+
164
+ # 4. Prepare
165
+ prep = JobPreparationCreate(
166
+ organization_id=org.id,
167
+ backend_class_id=MY_BACKEND,
168
+ name="Low-Level Test",
169
+ upload_endpoint_id=upload_id,
170
+ job_subcategory_id=MY_SUBCATEGORY,
171
+ shots=100,
172
+ configuration_data={}
173
+ )
174
+ prep_resp = scheduler.prepare_job(prep)
175
+
176
+ # 5. Poll preparation
177
+ def get_prep_status(prep_id: str):
178
+ result = scheduler.get_preparation_result(prep_id)
179
+ done = result.status in ("Completed", "Failed")
180
+ return done, result
181
+
182
+ result = poll_for_status(
183
+ get_status_fn=get_prep_status,
184
+ resource_id=prep_resp.id,
185
+ interval=2.0,
186
+ timeout=300
187
+ )
188
+
189
+ if result.status == "Failed":
190
+ raise RuntimeError(f"Preparation failed: {result.message or 'Unknown error'}")
191
+
192
+ # Quote inspection
193
+ print("\nQuote received:")
194
+ for plan in result.quote:
195
+ print(f" • {plan.name} — Base: {plan.price} credit{'s' if plan.price != 1 else ''}")
196
+ for qp in plan.queue_priorities:
197
+ total = plan.price + qp.price_increase
198
+ print(f" ├ {qp.name}: +{qp.price_increase} → Total: {total}")
199
+ print(f" └ Plan ID: {plan.execution_plan_id}")
200
+
201
+ # 6. Select cheapest
202
+ cheapest_plan = min(result.quote, key=lambda p: p.price)
203
+ cheapest_prio = min(cheapest_plan.queue_priorities, key=lambda q: q.price_increase)
204
+
205
+ # 7. Create job
206
+ job = JobCreate(
207
+ organization_id=org.id,
208
+ job_preparation_id=prep_resp.id,
209
+ execution_plan_id=cheapest_plan.execution_plan_id,
210
+ queue_priority_id=cheapest_prio.queue_priority_id,
211
+ )
212
+
213
+ job_resp = scheduler.create_job(job)
214
+
215
+ # 8. Poll completion
216
+ def get_job_status(job_id: str):
217
+ job_obj = scheduler.get_job(job_id)
218
+ done = job_obj.status in ("Completed", "Failed", "Canceled")
219
+ return done, job_obj
220
+
221
+ final_job = poll_for_status(
222
+ get_status_fn=get_job_status,
223
+ resource_id=job_resp.id,
224
+ interval=5.0,
225
+ timeout=86_400,
226
+ )
227
+
228
+ print(f"\nJob finished: {final_job.status}")
229
+
230
+ # 9. Download and print JSON output
231
+ if final_job.output_data_url:
232
+ output_json = scheduler.download_job_output(final_job)
233
+ print("\n--- JOB OUTPUT (JSON) ---")
234
+ print(json.dumps(output_json, indent=2))
235
+ else:
236
+ print("No output available.")
237
+
238
+ finally:
239
+ scheduler.close()
240
+ management.close()
241
+ ```
242
+
243
+ ## High-Level API: `submit_job()`
244
+
245
+ | Parameter | Type | Default | Description |
246
+ |---------|------|---------|-------------|
247
+ | `config` | `JobSubmissionConfig` | — | All job metadata |
248
+ | `file_path` | `str` | — | Input file |
249
+ | `file_content` | `bytes` | — | Or pass bytes |
250
+
251
+ ### `JobSubmissionConfig`
252
+
253
+ | Field | Type | Default | Notes |
254
+ |------|------|---------|-------|
255
+ | `organization_id` | `str` | — | **Required** |
256
+ | `backend_class_id` | `str` | — | **Required** (short code or UUID) |
257
+ | `job_subcategory_id` | `str` | — | **Required** |
258
+ | `name` | `str` | — | **Required** |
259
+ | `shots` | `int` | — | **Required** |
260
+ | `configuration_data` | `Dict[str, Any]` |`None` | |
261
+ | `execution_plan` | `ExecutionPlanType \| "auto"` | `"auto"` | |
262
+ | `queue_priority` | `QueuePriorityType \| "auto"` | `"auto"` | |
263
+ | `auto_approve_quote` | `bool` | `True` | Set `False` in notebooks |
264
+ | `job_timeout_seconds` | `int` | `86400` | 1 day |
265
+
266
+ ## API Reference
267
+
268
+ ### `SchedulerClient`
269
+
270
+ | Method | Returns | Description |
271
+ |--------|---------|-------------|
272
+ | `__init__(auth[ClientCredentialsAuth])` | | |
273
+ | `get_job_categories(limit=20, cursor=...)` | `PaginatedJobCategories` | List top-level categories |
274
+ | `get_job_subcategories(category_id, limit=20, cursor=...)` | `PaginatedJobCategories` | List subcategories |
275
+ | `upload_job_input(file_path=... or file_content=...)` | `str` | Returns upload ID |
276
+ | `prepare_job(JobPreparationCreate)` | `JobPreparationResponse` | Starts preparation |
277
+ | `get_preparation_result(preparation_id)` | `JobPreparationResultResponse` | Includes `.quote` |
278
+ | `create_job(JobCreate)` | `JobRead` | Submits job |
279
+ | `get_job(job_id)` | `JobRead` | Fetch job status |
280
+ | `cancel_job(job_id)` | `None` | Cancel running job |
281
+ | `list_jobs(organization_id, limit=20, cursor=..., status=...)` | `PaginatedJobs` | List your jobs |
282
+ | **`download_job_output(job)`** | `Any` | **Downloads + parses JSON output** |
283
+ | `submit_job(config, file_path=... or file_content=...)` | `JobRead` | **One-call submission + polling** |
284
+
285
+ ### `ManagementClient`
286
+
287
+ | Method | Returns | Description |
288
+ |--------|---------|-------------|
289
+ | `__init__(auth[ClientCredentialsAuth])` | | |
290
+ | `list_user_organizations(limit=20, cursor=...)` | `PaginatedOrganizations` | Your orgs |
291
+ | `list_providers(limit=20, cursor=...)` | `PaginatedProviders` | All backend providers |
292
+ | `list_backend_classes(provider_id=None, limit=20, cursor=...)` | `PaginatedBackendClasses` | Available backends |
293
+
294
+ ## Data Models
295
+
296
+ | Model | Key Fields |
297
+ |-------|------------|
298
+ | `BackendClassRead` | `id`, `name`, `description`, `type`, `provider_id`, `short_code`, `queue_depth`, `accepting_jobs`, `status` |
299
+ | `ErrorResponse` | `status_code`, `message: List[str]`, `error_code` |
300
+ | `JobCategoryRead` | `id`, `name`, `short_code` |
301
+ | `JobCreate` | `organization_id`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id` |
302
+ | `JobList` | `id`, `backend_class_id`, `name`, `status`, `backend_name`, `output_data_url`, `credits_used`, `submitted_at` |
303
+ | `JobPreparationCreate` | `organization_id`, `backend_class_id`, `name`, `upload_endpoint_id`, `job_subcategory_id`, `shots`, `configuration_data` |
304
+ | `JobPreparationResponse` | `id` |
305
+ | `JobPreparationResultResponse` | `organization_id`, `backend_class_id`, `status`, `name`, `input_data_url`, `job_category_id`, `job_subcategory_id`, `shots`, `quote: List[QuotePlan]`, `message`, `configuration_data` |
306
+ | `JobPreparationUploadResponse` | `id`, `url` |
307
+ | `JobRead` | `id`, `status`, `input_data_url`, `job_preparation_id`, `execution_plan_id`, `queue_priority_id`, `message`, `output_data_url`, `transaction_id`, `submitted_at` |
308
+ | `OrganizationRead` | `id`, `name` |
309
+ | `PaginatedBackendClasses` | `backend_classes: List[BackendClassRead]`, `pagination: PaginationInfo` |
310
+ | `PaginatedJobCategories` | `categories: List[JobCategoryRead]`, `pagination: PaginationInfo` |
311
+ | `PaginatedJobs` | `jobs: List[JobList]`, `pagination: PaginationInfo` |
312
+ | `PaginatedOrganizations` | `organizations: List[OrganizationRead]`, `pagination: PaginationInfo` |
313
+ | `PaginatedProviders` | `providers: List[ProviderRead]`, `pagination: PaginationInfo` |
314
+ | `PaginationInfo` | `next_cursor` |
315
+ | `ProviderRead` | `id`, `name`, `description`, `short_code` |
316
+ | `QueuePriority` | `name`, `description`, `price_increase`, `queue_priority_id` |
317
+ | `QuotePlan` | `name`, `price`, `description`, `execution_plan_id`, `queue_priorities: List[QueuePriority]` |
318
+
319
+ ## Utils: `poll_for_status()`
320
+
321
+ ```python
322
+ from openquantum_sdk.utils import poll_for_status
323
+ ```
324
+ | Parameter | Type | Default | Description |
325
+ | ------ | ------ | ------ | ------ |
326
+ | `get_status_fn` | `Callable[[str], Tuple[bool, Any]]` | | Function to be polled |
327
+ | `resource_id` | `str` | | id to be passed to status function: `get_status_fn(resource_id)` |
328
+ | `interval` | `int` | `5` | Number of seconds to wait before next call of status function |
329
+ | `timeout` | `int` | `300` | Number of seconds before timeout |
330
+
331
+
332
+ ## Enums
333
+
334
+ ```python
335
+ from openquantum_sdk.enums import ExecutionPlanType, QueuePriorityType
336
+ ```
337
+
338
+ | Enum | Values |
339
+ |------|--------|
340
+ | `ExecutionPlanType` | `PUBLIC`, `PRIVATE` |
341
+ | `QueuePriorityType` | `STANDARD`, `PRIORITY`, `INSTANT` |
342
+
343
+ ## CLI (Experimental)
344
+
345
+ ```bash
346
+ python -m openquantum_sdk --sdk-key ./key.json \
347
+ --input bell.qasm \
348
+ --backend "ionq:aria-1" \
349
+ --name "CLI Circuit" \
350
+ --subcategory "mathematics:linear-systems" \
351
+ --shots 100 \
352
+ --auto-approve
353
+ ```
354
+
355
+ Automatically **downloads and pretty-prints JSON output**.
356
+
357
+ ## Short Codes
358
+
359
+ ### Backends
360
+ | Provider | Backend | Short Code |
361
+ | ------ | ------ | ------ |
362
+ | IonQ | Aria-1 | `ionq:aria-1` |
363
+ | IonQ | Forte-1 | `ionq:forte-1` |
364
+ | IQM | Emerald | `iqm:emerald` |
365
+ | IQM | Garnet | `iqm:garnet` |
366
+ | Rigetti | Ankaa-3 | `rigetti:ankaa-3` |
367
+
368
+ *Note: This list may be modified as backends are added or removed from the platform. For the most up-to-date list, run the following code:*
369
+
370
+ ```python
371
+ backends = management.list_backend_classes().backend_classes
372
+
373
+ print('| Name | Short Code | Online |')
374
+ for b in backends:
375
+ print(f"| {b.name} | {b.short_code} | {b.accepting_jobs} |")
376
+ ```
377
+
378
+ ### Subcategories
379
+ | Category | Subcategory | Short Code |
380
+ | ------- | ------ | ------ |
381
+ | Chemistry | Molecular Ground State Estimation | `chem:mgse` |
382
+ | Chemistry | Other (Specify) | `chem:oth` |
383
+ | Chemistry | Quantum System Simulation | `chem:qss` |
384
+ | Chemistry | Variational Quantum Eigensolver (VQE) | `chem:vqe` |
385
+ | Cryptography | Discrete Logarithms | `crypto:dlog` |
386
+ | Cryptography | Hash Function Attacks | `crypto:hash` |
387
+ | Cryptography | Other (Specify) | `crypto:oth` |
388
+ | Cryptography | Shor's Factoring (RSA Cryptanalysis) | `crypto:shor` |
389
+ | Finance | Option Pricing | `fin:opt` |
390
+ | Finance | Other (Specify) | `fin:oth` |
391
+ | Finance | Portfolio Optimization | `fin:port` |
392
+ | Finance | Risk Analysis | `fin:risk` |
393
+ | Graph and Network Analysis | Graph Connectivity | `gna:con` |
394
+ | Graph and Network Analysis | Other (Specify) | `gna:oth` |
395
+ | Graph and Network Analysis | Pattern Matching | `gna:pat` |
396
+ | Graph and Network Analysis | Quantum Walks on Graphs | `gna:qwg` |
397
+ | Machine Learning | Other (Specify) | `ml:oth` |
398
+ | Machine Learning | Quantum Classification | `ml:qcl` |
399
+ | Machine Learning | Quantum Clustering | `ml:qcu` |
400
+ | Machine Learning | Quantum Recommendation Systems | `ml:qrs` |
401
+ | Mathematics | Differential Equations | `math:deq` |
402
+ | Mathematics | Hidden Subgroup Problems | `math:hsp` |
403
+ | Mathematics | Linear Systems (HHL Algorithm) | `math:hhl` |
404
+ | Mathematics | Other (Specify) | `math:oth` |
405
+ | Optimization | Adiabatic Optimization | `opt:adi` |
406
+ | Optimization | Constraint Satisfaction | `opt:csp` |
407
+ | Optimization | Other (Specify) | `opt:oth` |
408
+ | Optimization | Quantum Approximate Optimization Algorithm (QAOA) | `opt:qaoa` |
409
+ | Other | Other (Specify) | `oth:oth` |
410
+ | Physics | Hamiltonian Dynamics Simulation | `phys:hds` |
411
+ | Physics | Other (Specify) | `phys:oth` |
412
+ | Physics | Partition Functions | `phys:pfn` |
413
+ | Physics | Topological Invariants (e.g., Jones Polynomials) | `phys:tin` |
414
+
415
+
416
+ *Note: This list may be modified as categories and subcategories are added or removed. For the most up-to-date list, run the following code:*
417
+
418
+ ```python
419
+ cats = scheduler.get_job_categories().categories
420
+
421
+ print('| Category | Subcategory | Short Code |')
422
+ for cat in cats:
423
+ subcats = scheduler.get_job_subcategories(cat.short_code).categories
424
+ for subcat in subcats:
425
+ print(f'| {cat.name} | {subcat.name} | {subcat.short_code} |')
426
+ ```
427
+
428
+
429
+ ## FAQ
430
+
431
+ **Q: How do I get results as JSON?**
432
+ **A:** Use `scheduler.download_job_output(job)` — returns parsed object.
433
+
434
+ **Q: Why does auto mode fail on some QPUs?**
435
+ **A:** `auto` only allows `PUBLIC` + `STANDARD`. Use explicit enums for private/instant access.