quapp-common 0.0.13.dev6__tar.gz → 0.0.13.dev7__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.
Files changed (90) hide show
  1. {quapp_common-0.0.13.dev6/quapp_common.egg-info → quapp_common-0.0.13.dev7}/PKG-INFO +1 -1
  2. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/pyproject.toml +41 -41
  3. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/circuit_adapter.py +286 -286
  4. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/dispatcher.py +4 -0
  5. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/language.py +1 -0
  6. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7/quapp_common.egg-info}/PKG-INFO +1 -1
  7. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common.egg-info/SOURCES.txt +1 -0
  8. quapp_common-0.0.13.dev7/tests/test_language_dispatcher.py +20 -0
  9. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/LICENSE +0 -0
  10. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/README.md +0 -0
  11. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/__init__.py +0 -0
  12. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/async_tasks/__init__.py +0 -0
  13. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/async_tasks/async_invocation_task.py +0 -0
  14. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/async_tasks/async_task.py +0 -0
  15. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/async_tasks/export_circuit_task.py +0 -0
  16. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/async_tasks/post_processing_task.py +0 -0
  17. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/__init__.py +0 -0
  18. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/backend/__init__.py +0 -0
  19. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/backend/invocation.py +0 -0
  20. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/backend/job_fetcher.py +0 -0
  21. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/backend/job_fetching.py +0 -0
  22. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/backend/job_manager.py +0 -0
  23. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/bridge.py +0 -0
  24. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/callback/__init__.py +0 -0
  25. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/callback/update_job_metadata.py +0 -0
  26. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/device/__init__.py +0 -0
  27. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/device/device_selection.py +0 -0
  28. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/component/result_serializer.py +0 -0
  29. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/config/__init__.py +0 -0
  30. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/config/logging_config.py +0 -0
  31. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/config/thread_config.py +0 -0
  32. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/__init__.py +0 -0
  33. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/async_task/__init__.py +0 -0
  34. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/async_task/circuit_export/__init__.py +0 -0
  35. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/async_task/circuit_export/backend_holder.py +0 -0
  36. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/async_task/circuit_export/circuit_holder.py +0 -0
  37. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/backend/__init__.py +0 -0
  38. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/backend/backend_information.py +0 -0
  39. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/callback/__init__.py +0 -0
  40. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/callback/callback_url.py +0 -0
  41. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/device/__init__.py +0 -0
  42. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/device/circuit_running_option.py +0 -0
  43. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/promise/__init__.py +0 -0
  44. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/promise/post_processing_promise.py +0 -0
  45. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/promise/promise.py +0 -0
  46. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/request/__init__.py +0 -0
  47. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/request/invocation_request.py +0 -0
  48. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/request/job_fetching_request.py +0 -0
  49. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/request/request.py +0 -0
  50. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/response/__init__.py +0 -0
  51. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/response/authentication.py +0 -0
  52. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/response/custom_header.py +0 -0
  53. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/data/response/job_response.py +0 -0
  54. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/__init__.py +0 -0
  55. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/base_enum.py +0 -0
  56. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/http_header.py +0 -0
  57. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/invocation_step.py +0 -0
  58. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/media_type.py +0 -0
  59. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/processing_unit.py +0 -0
  60. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/provider_tag.py +0 -0
  61. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/sdk.py +0 -0
  62. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/status/__init__.py +0 -0
  63. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/status/job_status.py +0 -0
  64. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/status/status_code.py +0 -0
  65. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/enum/token_type.py +0 -0
  66. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/factory/__init__.py +0 -0
  67. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/factory/device_factory.py +0 -0
  68. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/factory/handler_factory.py +0 -0
  69. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/factory/provider_factory.py +0 -0
  70. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/handler/__init__.py +0 -0
  71. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/handler/handler.py +0 -0
  72. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/__init__.py +0 -0
  73. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/device/__init__.py +0 -0
  74. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/device/custom_device.py +0 -0
  75. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/device/device.py +0 -0
  76. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/invocation.py +0 -0
  77. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/provider/__init__.py +0 -0
  78. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/model/provider/provider.py +0 -0
  79. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/util/__init__.py +0 -0
  80. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/util/file_utils.py +0 -0
  81. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/util/http_utils.py +0 -0
  82. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/util/json_parser_utils.py +0 -0
  83. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common/util/response_utils.py +0 -0
  84. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common.egg-info/dependency_links.txt +0 -0
  85. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common.egg-info/requires.txt +0 -0
  86. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/quapp_common.egg-info/top_level.txt +0 -0
  87. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/setup.cfg +0 -0
  88. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/tests/test_circuit_adapter.py +0 -0
  89. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/tests/test_json_parser_utils.py +0 -0
  90. {quapp_common-0.0.13.dev6 → quapp_common-0.0.13.dev7}/tests/test_result_serializer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.13.dev6
3
+ Version: 0.0.13.dev7
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -1,41 +1,41 @@
1
- [build-system]
2
- requires = ["setuptools>=61.0.0", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [project]
6
- name = "quapp-common"
7
- version = "0.0.13.dev6"
8
- description = "Quapp common library supporting Quapp Platform for Quantum Computing"
9
- readme = "README.md"
10
- authors = [{ name = "CITYNOW Co. Ltd. ", email = "corp@citynow.vn" }]
11
- license = { file = "LICENSE" }
12
- classifiers = [
13
- "License :: OSI Approved :: MIT License",
14
- "Programming Language :: Python",
15
- "Programming Language :: Python :: 3",
16
- ]
17
- keywords = ["quapp", "quapp-common", "quantum"]
18
- dependencies = [
19
- "loguru",
20
- "requests",
21
- "numpy",
22
- "matplotlib",
23
- "pylatexenc",
24
- "starlette",
25
- "qibo",
26
- "dimod",
27
- "qulacs",
28
- "qiskit",
29
- "pytket",
30
- "pennylane"
31
- ]
32
- requires-python = ">=3.7"
33
-
34
- [project.optional-dependencies]
35
- dev = ["black", "bumpver", "isort", "pip-tools", "pytest"]
36
-
37
- [project.urls]
38
- Homepage = "https://citynow.asia/"
39
-
40
- [tool.pylint.main]
41
- source-roots = ["."]
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "quapp-common"
7
+ version = "0.0.13.dev7"
8
+ description = "Quapp common library supporting Quapp Platform for Quantum Computing"
9
+ readme = "README.md"
10
+ authors = [{ name = "CITYNOW Co. Ltd. ", email = "corp@citynow.vn" }]
11
+ license = { file = "LICENSE" }
12
+ classifiers = [
13
+ "License :: OSI Approved :: MIT License",
14
+ "Programming Language :: Python",
15
+ "Programming Language :: Python :: 3",
16
+ ]
17
+ keywords = ["quapp", "quapp-common", "quantum"]
18
+ dependencies = [
19
+ "loguru",
20
+ "requests",
21
+ "numpy",
22
+ "matplotlib",
23
+ "pylatexenc",
24
+ "starlette",
25
+ "qibo",
26
+ "dimod",
27
+ "qulacs",
28
+ "qiskit",
29
+ "pytket",
30
+ "pennylane"
31
+ ]
32
+ requires-python = ">=3.7"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["black", "bumpver", "isort", "pip-tools", "pytest"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://citynow.asia/"
39
+
40
+ [tool.pylint.main]
41
+ source-roots = ["."]
@@ -1,286 +1,286 @@
1
- # Quapp Platform Project
2
- # circuit_adapter.py
3
- # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
-
5
- import importlib
6
- import re
7
- from typing import Any, Dict
8
-
9
- import numpy as np
10
- import pennylane as qml
11
- import pennylane.measurements as _plm
12
- try:
13
- import qiskit.qasm2 as _qasm2
14
- except ImportError:
15
- _qasm2 = None
16
- from dimod import BinaryQuadraticModel
17
- from pytket.qasm import circuit_from_qasm_str
18
- from qibo.models import Circuit
19
- from qiskit import QuantumCircuit as QiskitCircuit
20
- from qulacs import QuantumCircuit as QulacsCircuit
21
- from qulacs.gate import DenseMatrix as DenseMatrixGate
22
-
23
- from ..config.logging_config import logger
24
- from ..enum.sdk import Sdk
25
-
26
- logger = logger.bind(context=__name__)
27
-
28
-
29
- def _load_qasm_circuit(qasm_str: str) -> QiskitCircuit:
30
- """Parse OpenQASM 2.0 string into a Qiskit QuantumCircuit.
31
-
32
- Compatible with Qiskit >= 1.0 (qiskit.qasm2.loads) and < 1.0 (from_qasm_str).
33
- """
34
- if _qasm2 is not None:
35
- return _qasm2.loads(qasm_str)
36
- return QiskitCircuit.from_qasm_str(qasm_str)
37
-
38
-
39
- def adapt(sdk: Sdk, result: dict):
40
- """Route to the appropriate adapter based on the 'format' field."""
41
- fmt = result.get("format")
42
- logger.debug(f"adapt() called | sdk={sdk} | format={fmt}")
43
- if fmt == "openqasm":
44
- circuit = result.get("circuit")
45
- if circuit is None:
46
- raise ValueError("Missing 'circuit' field in openqasm response")
47
- return _adapt_openqasm(sdk, circuit)
48
- if fmt == "qubo":
49
- qubo = result.get("qubo")
50
- if qubo is None:
51
- raise ValueError("Missing 'qubo' field in qubo response")
52
- return _adapt_qubo(sdk, qubo)
53
- if fmt == "json":
54
- logger.debug("Returning raw JSON data pass-through")
55
- return result.get("data")
56
- raise ValueError(f"Unsupported circuit interchange format: {fmt}")
57
-
58
-
59
- def _adapt_openqasm(sdk, qasm_str):
60
- """Convert an OpenQASM 2.0 string to a native SDK circuit object."""
61
- logger.debug(f"Adapting OpenQASM circuit for SDK: {sdk} | qasm_length={len(qasm_str)}")
62
- _handlers = {
63
- Sdk.QISKIT: _openqasm_to_qiskit,
64
- Sdk.BRAKET: _openqasm_to_braket,
65
- Sdk.PENNYLANE: _openqasm_to_pennylane,
66
- Sdk.PYQUIL: _openqasm_to_pyquil,
67
- Sdk.QIBO: _openqasm_to_qibo,
68
- Sdk.QULACS: _openqasm_to_qulacs,
69
- Sdk.CUDA_QUANTUM: _openqasm_to_cuda_quantum,
70
- }
71
- handler = _handlers.get(sdk)
72
- if handler is None:
73
- raise ValueError(f"OpenQASM adaptation not supported for SDK: {sdk}")
74
- return handler(qasm_str)
75
-
76
-
77
- def _adapt_qubo(sdk, qubo_raw: Dict[str, Any]):
78
- """Convert a QUBO dict with string keys ('x0,x1') to a BinaryQuadraticModel.
79
-
80
- JS returns keys like "x0,x1" as strings. We parse them into tuple keys
81
- for dimod's from_qubo() method.
82
- """
83
- logger.debug(f"Adapting QUBO for SDK: {sdk} | num_entries={len(qubo_raw)}")
84
-
85
- qubo_dict = {}
86
- for key, val in qubo_raw.items():
87
- if isinstance(key, str) and "," in key:
88
- parts = tuple(k.strip() for k in key.split(",", 1))
89
- qubo_dict[parts] = val
90
- else:
91
- qubo_dict[(key, key)] = val
92
-
93
- logger.debug(f"QUBO parsed: {len(qubo_dict)} entries -> BinaryQuadraticModel")
94
- return BinaryQuadraticModel.from_qubo(qubo_dict)
95
-
96
-
97
- def _count_qasm_qubits(qasm_str):
98
- """Sum qubit counts across all `qreg <name>[<n>];` declarations."""
99
- return sum(int(m) for m in re.findall(r'qreg\s+\w+\[(\d+)]', qasm_str))
100
-
101
-
102
- def _openqasm_to_braket(qasm_str):
103
- logger.debug("Converting OpenQASM via pytket -> tk_to_braket")
104
- tk_to_braket = importlib.import_module("pytket.extensions.braket").tk_to_braket
105
- result = tk_to_braket(circuit_from_qasm_str(qasm_str))
106
- # tk_to_braket returns (Circuit, n_shots) in pytket-braket >= 0.35
107
- is_tuple = isinstance(result, tuple)
108
- logger.debug(
109
- f"tk_to_braket returned tuple={is_tuple}; unpacking index 0" if is_tuple else "tk_to_braket returned Circuit directly")
110
- circuit = result[0] if is_tuple else result
111
- if not circuit.result_types:
112
- circuit.probability(target=list(range(circuit.qubit_count)))
113
- logger.debug(f"No result types found; added Probability over {circuit.qubit_count} qubits")
114
- return circuit
115
-
116
-
117
- def _openqasm_to_cuda_quantum(qasm_str):
118
- logger.debug("CUDA-Q: passing OpenQASM string through directly")
119
- # CUDA-Q accepts OpenQASM directly via kernel builder
120
- return qasm_str
121
-
122
-
123
- def _openqasm_to_pennylane(qasm_str):
124
- logger.debug("Converting OpenQASM via PennyLane qml.from_qasm wrapper")
125
- for _compat_name in ("Expectation", "Variance", "Sample", "Probability",
126
- "State", "MidMeasure"):
127
- if not hasattr(_plm, _compat_name):
128
- setattr(_plm, _compat_name, _compat_name)
129
-
130
- num_wires = _count_qasm_qubits(qasm_str)
131
- logger.debug(f"PennyLane: detected {num_wires} qubits; wrapping with qml.probs over all wires")
132
- try:
133
- base_fn = qml.from_qasm(qasm_str, measurements=[])
134
-
135
- def _circuit():
136
- base_fn()
137
- if num_wires > 0:
138
- return qml.probs(wires=list(range(num_wires)))
139
- return None
140
-
141
- return _circuit
142
- except Exception as _e:
143
- if "qasm plugin" not in str(_e) and "pennylane-qiskit" not in str(_e):
144
- raise
145
- logger.warning(
146
- f"qml.from_qasm unavailable ({_e}); falling back to Qiskit bridge for PennyLane QASM conversion"
147
- )
148
- return _qiskit_to_pennylane(qasm_str, num_wires)
149
-
150
-
151
- def _openqasm_to_pyquil(qasm_str):
152
- logger.debug("Converting OpenQASM via pytket -> tk_to_pyquil")
153
- tk_to_pyquil = importlib.import_module("pytket.extensions.pyquil").tk_to_pyquil
154
- return tk_to_pyquil(circuit_from_qasm_str(qasm_str))
155
-
156
-
157
- def _openqasm_to_qibo(qasm_str):
158
- logger.debug("Converting OpenQASM via Qibo Circuit.from_qasm")
159
- return Circuit.from_qasm(qasm_str)
160
-
161
-
162
- def _openqasm_to_qiskit(qasm_str):
163
- logger.debug("Converting OpenQASM via Qiskit QuantumCircuit")
164
- return _load_qasm_circuit(qasm_str)
165
-
166
-
167
- def _openqasm_to_qulacs(qasm_str):
168
- logger.debug("Converting OpenQASM for Qulacs via Qiskit bridge")
169
- # Try the Qiskit bridge first; fall back to raw pass-through if
170
- # qiskit is not installed or the conversion fails.
171
- try:
172
- qiskit_circ = _load_qasm_circuit(qasm_str)
173
- logger.debug(
174
- f"Qiskit parsed circuit: num_qubits={qiskit_circ.num_qubits} num_gates={len(qiskit_circ.data)}")
175
- return _qiskit_to_qulacs(qiskit_circ)
176
- except Exception as e:
177
- logger.warning(f"Qiskit bridge failed for Qulacs ({e}); falling back to raw OpenQASM pass-through")
178
- return qasm_str
179
-
180
-
181
- def _qiskit_to_pennylane(qasm_str, num_wires):
182
- """Parse an OpenQASM 2.0 string via Qiskit and return a PennyLane callable.
183
-
184
- Fallback for environments where qml.from_qasm is unavailable because
185
- pennylane-qiskit is not installed (PennyLane < 0.36).
186
- """
187
- qiskit_circ = _load_qasm_circuit(qasm_str)
188
- qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
189
- logger.debug(
190
- f"_qiskit_to_pennylane: num_qubits={qiskit_circ.num_qubits} "
191
- f"num_instructions={len(qiskit_circ.data)}"
192
- )
193
-
194
- instructions = [
195
- (inst.operation.name,
196
- [qubit_map[q] for q in inst.qubits],
197
- [float(p) for p in inst.operation.params])
198
- for inst in qiskit_circ.data
199
- ]
200
-
201
- _skip = frozenset({'measure', 'barrier', 'reset', 'id'})
202
- _gate_map = {
203
- 'h': lambda q, p: qml.Hadamard(wires=q[0]),
204
- 'x': lambda q, p: qml.PauliX(wires=q[0]),
205
- 'y': lambda q, p: qml.PauliY(wires=q[0]),
206
- 'z': lambda q, p: qml.PauliZ(wires=q[0]),
207
- 's': lambda q, p: qml.S(wires=q[0]),
208
- 'sdg': lambda q, p: qml.PhaseShift(-np.pi / 2, wires=q[0]),
209
- 't': lambda q, p: qml.T(wires=q[0]),
210
- 'tdg': lambda q, p: qml.PhaseShift(-np.pi / 4, wires=q[0]),
211
- 'cx': lambda q, p: qml.CNOT(wires=q),
212
- 'cz': lambda q, p: qml.CZ(wires=q),
213
- 'swap': lambda q, p: qml.SWAP(wires=q),
214
- 'rx': lambda q, p: qml.RX(p[0], wires=q[0]),
215
- 'ry': lambda q, p: qml.RY(p[0], wires=q[0]),
216
- 'rz': lambda q, p: qml.RZ(p[0], wires=q[0]),
217
- 'p': lambda q, p: qml.PhaseShift(p[0], wires=q[0]),
218
- 'u1': lambda q, p: qml.PhaseShift(p[0], wires=q[0]),
219
- 'u2': lambda q, p: qml.U2(p[0], p[1], wires=q[0]),
220
- 'u3': lambda q, p: qml.U3(p[0], p[1], p[2], wires=q[0]),
221
- 'u': lambda q, p: qml.U3(p[0], p[1], p[2], wires=q[0]),
222
- }
223
-
224
- def _circuit():
225
- for name, qubits, params in instructions:
226
- if name in _skip:
227
- continue
228
- gate_fn = _gate_map.get(name)
229
- if gate_fn is not None:
230
- gate_fn(qubits, params)
231
- else:
232
- logger.warning(f"PennyLane Qiskit bridge: unrecognized gate '{name}'; skipping")
233
- if num_wires > 0:
234
- return qml.probs(wires=list(range(num_wires)))
235
- return None
236
-
237
- return _circuit
238
-
239
-
240
- def _qiskit_to_qulacs(qiskit_circ):
241
- """Convert a Qiskit QuantumCircuit to a qulacs QuantumCircuit.
242
-
243
- Fixed gates are mapped via named qulacs methods; parametric and
244
- unrecognized gates fall back to DenseMatrix (unitary passthrough).
245
- Measurement, barrier, reset, and identity instructions are skipped.
246
- """
247
- n = qiskit_circ.num_qubits
248
- logger.debug(f"_qiskit_to_qulacs: num_qubits={n} num_instructions={len(qiskit_circ.data)}")
249
- qulacs_circ = QulacsCircuit(n)
250
- qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
251
-
252
- _SINGLE = {
253
- 'h': 'add_H_gate', 'x': 'add_X_gate',
254
- 'y': 'add_Y_gate', 'z': 'add_Z_gate',
255
- 's': 'add_S_gate', 'sdg': 'add_Sdag_gate',
256
- 't': 'add_T_gate', 'tdg': 'add_Tdag_gate',
257
- }
258
- _TWO = {
259
- 'cx': 'add_CNOT_gate',
260
- 'cz': 'add_CZ_gate',
261
- 'swap': 'add_SWAP_gate',
262
- }
263
- _SKIP = {'measure', 'barrier', 'reset', 'id'}
264
-
265
- for inst in qiskit_circ.data:
266
- gate = inst.operation
267
- qubits = [qubit_map[q] for q in inst.qubits]
268
- name = gate.name
269
-
270
- if name in _SKIP:
271
- continue
272
- elif name in _SINGLE:
273
- getattr(qulacs_circ, _SINGLE[name])(qubits[0])
274
- elif name in _TWO:
275
- getattr(qulacs_circ, _TWO[name])(qubits[0], qubits[1])
276
- else:
277
- # Parametric gates (rx, ry, rz, u*, p, ...) and any
278
- # unrecognized gate: extract the unitary and apply it
279
- # as a DenseMatrix. Single-qubit gates have no ordering
280
- # ambiguity; multi-qubit gates are passed with qulacs'
281
- # index order matching Qiskit's instruction qubit order.
282
- logger.debug(f"Gate '{name}' not in named map; converting via DenseMatrix on qubits={qubits}")
283
- qulacs_circ.add_gate(DenseMatrixGate(qubits, gate.to_matrix()))
284
-
285
- logger.debug(f"_qiskit_to_qulacs: conversion complete")
286
- return qulacs_circ
1
+ # Quapp Platform Project
2
+ # circuit_adapter.py
3
+ # Copyright © CITYNOW Co. Ltd. All rights reserved.
4
+
5
+ import importlib
6
+ import re
7
+ from typing import Any, Dict
8
+
9
+ import numpy as np
10
+ import pennylane as qml
11
+ import pennylane.measurements as _plm
12
+ try:
13
+ import qiskit.qasm2 as _qasm2
14
+ except ImportError:
15
+ _qasm2 = None
16
+ from dimod import BinaryQuadraticModel
17
+ from pytket.qasm import circuit_from_qasm_str
18
+ from qibo.models import Circuit
19
+ from qiskit import QuantumCircuit as QiskitCircuit
20
+ from qulacs import QuantumCircuit as QulacsCircuit
21
+ from qulacs.gate import DenseMatrix as DenseMatrixGate
22
+
23
+ from ..config.logging_config import logger
24
+ from ..enum.sdk import Sdk
25
+
26
+ logger = logger.bind(context=__name__)
27
+
28
+
29
+ def _load_qasm_circuit(qasm_str: str) -> QiskitCircuit:
30
+ """Parse OpenQASM 2.0 string into a Qiskit QuantumCircuit.
31
+
32
+ Compatible with Qiskit >= 1.0 (qiskit.qasm2.loads) and < 1.0 (from_qasm_str).
33
+ """
34
+ if _qasm2 is not None:
35
+ return _qasm2.loads(qasm_str)
36
+ return QiskitCircuit.from_qasm_str(qasm_str)
37
+
38
+
39
+ def adapt(sdk: Sdk, result: dict):
40
+ """Route to the appropriate adapter based on the 'format' field."""
41
+ fmt = result.get("format")
42
+ logger.debug(f"adapt() called | sdk={sdk} | format={fmt}")
43
+ if fmt == "openqasm":
44
+ circuit = result.get("circuit")
45
+ if circuit is None:
46
+ raise ValueError("Missing 'circuit' field in openqasm response")
47
+ return _adapt_openqasm(sdk, circuit)
48
+ if fmt == "qubo":
49
+ qubo = result.get("qubo")
50
+ if qubo is None:
51
+ raise ValueError("Missing 'qubo' field in qubo response")
52
+ return _adapt_qubo(sdk, qubo)
53
+ if fmt == "json":
54
+ logger.debug("Returning raw JSON data pass-through")
55
+ return result.get("data")
56
+ raise ValueError(f"Unsupported circuit interchange format: {fmt}")
57
+
58
+
59
+ def _adapt_openqasm(sdk, qasm_str):
60
+ """Convert an OpenQASM 2.0 string to a native SDK circuit object."""
61
+ logger.debug(f"Adapting OpenQASM circuit for SDK: {sdk} | qasm_length={len(qasm_str)}")
62
+ _handlers = {
63
+ Sdk.QISKIT: _openqasm_to_qiskit,
64
+ Sdk.BRAKET: _openqasm_to_braket,
65
+ Sdk.PENNYLANE: _openqasm_to_pennylane,
66
+ Sdk.PYQUIL: _openqasm_to_pyquil,
67
+ Sdk.QIBO: _openqasm_to_qibo,
68
+ Sdk.QULACS: _openqasm_to_qulacs,
69
+ Sdk.CUDA_QUANTUM: _openqasm_to_cuda_quantum,
70
+ }
71
+ handler = _handlers.get(sdk)
72
+ if handler is None:
73
+ raise ValueError(f"OpenQASM adaptation not supported for SDK: {sdk}")
74
+ return handler(qasm_str)
75
+
76
+
77
+ def _adapt_qubo(sdk, qubo_raw: Dict[str, Any]):
78
+ """Convert a QUBO dict with string keys ('x0,x1') to a BinaryQuadraticModel.
79
+
80
+ JS returns keys like "x0,x1" as strings. We parse them into tuple keys
81
+ for dimod's from_qubo() method.
82
+ """
83
+ logger.debug(f"Adapting QUBO for SDK: {sdk} | num_entries={len(qubo_raw)}")
84
+
85
+ qubo_dict = {}
86
+ for key, val in qubo_raw.items():
87
+ if isinstance(key, str) and "," in key:
88
+ parts = tuple(k.strip() for k in key.split(",", 1))
89
+ qubo_dict[parts] = val
90
+ else:
91
+ qubo_dict[(key, key)] = val
92
+
93
+ logger.debug(f"QUBO parsed: {len(qubo_dict)} entries -> BinaryQuadraticModel")
94
+ return BinaryQuadraticModel.from_qubo(qubo_dict)
95
+
96
+
97
+ def _count_qasm_qubits(qasm_str):
98
+ """Sum qubit counts across all `qreg <name>[<n>];` declarations."""
99
+ return sum(int(m) for m in re.findall(r'qreg\s+\w+\[(\d+)]', qasm_str))
100
+
101
+
102
+ def _openqasm_to_braket(qasm_str):
103
+ logger.debug("Converting OpenQASM via pytket -> tk_to_braket")
104
+ tk_to_braket = importlib.import_module("pytket.extensions.braket").tk_to_braket
105
+ result = tk_to_braket(circuit_from_qasm_str(qasm_str))
106
+ # tk_to_braket returns (Circuit, n_shots) in pytket-braket >= 0.35
107
+ is_tuple = isinstance(result, tuple)
108
+ logger.debug(
109
+ f"tk_to_braket returned tuple={is_tuple}; unpacking index 0" if is_tuple else "tk_to_braket returned Circuit directly")
110
+ circuit = result[0] if is_tuple else result
111
+ if not circuit.result_types:
112
+ circuit.probability(target=list(range(circuit.qubit_count)))
113
+ logger.debug(f"No result types found; added Probability over {circuit.qubit_count} qubits")
114
+ return circuit
115
+
116
+
117
+ def _openqasm_to_cuda_quantum(qasm_str):
118
+ logger.debug("CUDA-Q: passing OpenQASM string through directly")
119
+ # CUDA-Q accepts OpenQASM directly via kernel builder
120
+ return qasm_str
121
+
122
+
123
+ def _openqasm_to_pennylane(qasm_str):
124
+ logger.debug("Converting OpenQASM via PennyLane qml.from_qasm wrapper")
125
+ for _compat_name in ("Expectation", "Variance", "Sample", "Probability",
126
+ "State", "MidMeasure"):
127
+ if not hasattr(_plm, _compat_name):
128
+ setattr(_plm, _compat_name, _compat_name)
129
+
130
+ num_wires = _count_qasm_qubits(qasm_str)
131
+ logger.debug(f"PennyLane: detected {num_wires} qubits; wrapping with qml.probs over all wires")
132
+ try:
133
+ base_fn = qml.from_qasm(qasm_str, measurements=[])
134
+
135
+ def _circuit():
136
+ base_fn()
137
+ if num_wires > 0:
138
+ return qml.probs(wires=list(range(num_wires)))
139
+ return None
140
+
141
+ return _circuit
142
+ except Exception as _e:
143
+ if "qasm plugin" not in str(_e) and "pennylane-qiskit" not in str(_e):
144
+ raise
145
+ logger.warning(
146
+ f"qml.from_qasm unavailable ({_e}); falling back to Qiskit bridge for PennyLane QASM conversion"
147
+ )
148
+ return _qiskit_to_pennylane(qasm_str, num_wires)
149
+
150
+
151
+ def _openqasm_to_pyquil(qasm_str):
152
+ logger.debug("Converting OpenQASM via pytket -> tk_to_pyquil")
153
+ tk_to_pyquil = importlib.import_module("pytket.extensions.pyquil").tk_to_pyquil
154
+ return tk_to_pyquil(circuit_from_qasm_str(qasm_str))
155
+
156
+
157
+ def _openqasm_to_qibo(qasm_str):
158
+ logger.debug("Converting OpenQASM via Qibo Circuit.from_qasm")
159
+ return Circuit.from_qasm(qasm_str)
160
+
161
+
162
+ def _openqasm_to_qiskit(qasm_str):
163
+ logger.debug("Converting OpenQASM via Qiskit QuantumCircuit")
164
+ return _load_qasm_circuit(qasm_str)
165
+
166
+
167
+ def _openqasm_to_qulacs(qasm_str):
168
+ logger.debug("Converting OpenQASM for Qulacs via Qiskit bridge")
169
+ # Try the Qiskit bridge first; fall back to raw pass-through if
170
+ # qiskit is not installed or the conversion fails.
171
+ try:
172
+ qiskit_circ = _load_qasm_circuit(qasm_str)
173
+ logger.debug(
174
+ f"Qiskit parsed circuit: num_qubits={qiskit_circ.num_qubits} num_gates={len(qiskit_circ.data)}")
175
+ return _qiskit_to_qulacs(qiskit_circ)
176
+ except Exception as e:
177
+ logger.warning(f"Qiskit bridge failed for Qulacs ({e}); falling back to raw OpenQASM pass-through")
178
+ return qasm_str
179
+
180
+
181
+ def _qiskit_to_pennylane(qasm_str, num_wires):
182
+ """Parse an OpenQASM 2.0 string via Qiskit and return a PennyLane callable.
183
+
184
+ Fallback for environments where qml.from_qasm is unavailable because
185
+ pennylane-qiskit is not installed (PennyLane < 0.36).
186
+ """
187
+ qiskit_circ = _load_qasm_circuit(qasm_str)
188
+ qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
189
+ logger.debug(
190
+ f"_qiskit_to_pennylane: num_qubits={qiskit_circ.num_qubits} "
191
+ f"num_instructions={len(qiskit_circ.data)}"
192
+ )
193
+
194
+ instructions = [
195
+ (inst.operation.name,
196
+ [qubit_map[q] for q in inst.qubits],
197
+ [float(p) for p in inst.operation.params])
198
+ for inst in qiskit_circ.data
199
+ ]
200
+
201
+ _skip = frozenset({'measure', 'barrier', 'reset', 'id'})
202
+ _gate_map = {
203
+ 'h': lambda q, p: qml.Hadamard(wires=q[0]),
204
+ 'x': lambda q, p: qml.PauliX(wires=q[0]),
205
+ 'y': lambda q, p: qml.PauliY(wires=q[0]),
206
+ 'z': lambda q, p: qml.PauliZ(wires=q[0]),
207
+ 's': lambda q, p: qml.S(wires=q[0]),
208
+ 'sdg': lambda q, p: qml.PhaseShift(-np.pi / 2, wires=q[0]),
209
+ 't': lambda q, p: qml.T(wires=q[0]),
210
+ 'tdg': lambda q, p: qml.PhaseShift(-np.pi / 4, wires=q[0]),
211
+ 'cx': lambda q, p: qml.CNOT(wires=q),
212
+ 'cz': lambda q, p: qml.CZ(wires=q),
213
+ 'swap': lambda q, p: qml.SWAP(wires=q),
214
+ 'rx': lambda q, p: qml.RX(p[0], wires=q[0]),
215
+ 'ry': lambda q, p: qml.RY(p[0], wires=q[0]),
216
+ 'rz': lambda q, p: qml.RZ(p[0], wires=q[0]),
217
+ 'p': lambda q, p: qml.PhaseShift(p[0], wires=q[0]),
218
+ 'u1': lambda q, p: qml.PhaseShift(p[0], wires=q[0]),
219
+ 'u2': lambda q, p: qml.U2(p[0], p[1], wires=q[0]),
220
+ 'u3': lambda q, p: qml.U3(p[0], p[1], p[2], wires=q[0]),
221
+ 'u': lambda q, p: qml.U3(p[0], p[1], p[2], wires=q[0]),
222
+ }
223
+
224
+ def _circuit():
225
+ for name, qubits, params in instructions:
226
+ if name in _skip:
227
+ continue
228
+ gate_fn = _gate_map.get(name)
229
+ if gate_fn is not None:
230
+ gate_fn(qubits, params)
231
+ else:
232
+ logger.warning(f"PennyLane Qiskit bridge: unrecognized gate '{name}'; skipping")
233
+ if num_wires > 0:
234
+ return qml.probs(wires=list(range(num_wires)))
235
+ return None
236
+
237
+ return _circuit
238
+
239
+
240
+ def _qiskit_to_qulacs(qiskit_circ):
241
+ """Convert a Qiskit QuantumCircuit to a qulacs QuantumCircuit.
242
+
243
+ Fixed gates are mapped via named qulacs methods; parametric and
244
+ unrecognized gates fall back to DenseMatrix (unitary passthrough).
245
+ Measurement, barrier, reset, and identity instructions are skipped.
246
+ """
247
+ n = qiskit_circ.num_qubits
248
+ logger.debug(f"_qiskit_to_qulacs: num_qubits={n} num_instructions={len(qiskit_circ.data)}")
249
+ qulacs_circ = QulacsCircuit(n)
250
+ qubit_map = {bit: idx for idx, bit in enumerate(qiskit_circ.qubits)}
251
+
252
+ _SINGLE = {
253
+ 'h': 'add_H_gate', 'x': 'add_X_gate',
254
+ 'y': 'add_Y_gate', 'z': 'add_Z_gate',
255
+ 's': 'add_S_gate', 'sdg': 'add_Sdag_gate',
256
+ 't': 'add_T_gate', 'tdg': 'add_Tdag_gate',
257
+ }
258
+ _TWO = {
259
+ 'cx': 'add_CNOT_gate',
260
+ 'cz': 'add_CZ_gate',
261
+ 'swap': 'add_SWAP_gate',
262
+ }
263
+ _SKIP = {'measure', 'barrier', 'reset', 'id'}
264
+
265
+ for inst in qiskit_circ.data:
266
+ gate = inst.operation
267
+ qubits = [qubit_map[q] for q in inst.qubits]
268
+ name = gate.name
269
+
270
+ if name in _SKIP:
271
+ continue
272
+ elif name in _SINGLE:
273
+ getattr(qulacs_circ, _SINGLE[name])(qubits[0])
274
+ elif name in _TWO:
275
+ getattr(qulacs_circ, _TWO[name])(qubits[0], qubits[1])
276
+ else:
277
+ # Parametric gates (rx, ry, rz, u*, p, ...) and any
278
+ # unrecognized gate: extract the unitary and apply it
279
+ # as a DenseMatrix. Single-qubit gates have no ordering
280
+ # ambiguity; multi-qubit gates are passed with qulacs'
281
+ # index order matching Qiskit's instruction qubit order.
282
+ logger.debug(f"Gate '{name}' not in named map; converting via DenseMatrix on qubits={qubits}")
283
+ qulacs_circ.add_gate(DenseMatrixGate(qubits, gate.to_matrix()))
284
+
285
+ logger.debug(f"_qiskit_to_qulacs: conversion complete")
286
+ return qulacs_circ
@@ -25,6 +25,10 @@ RUNNER_MAP = {
25
25
  'command': [_QSHARP_BINARY],
26
26
  'timeout': 300,
27
27
  },
28
+ Language.C: {
29
+ 'command': ['./handler_compiled'],
30
+ 'timeout': 300,
31
+ },
28
32
  }
29
33
 
30
34
 
@@ -13,6 +13,7 @@ class Language(BaseEnum):
13
13
  PYTHON = 'python'
14
14
  JAVASCRIPT = 'javascript'
15
15
  QSHARP = 'qsharp'
16
+ C = 'c'
16
17
 
17
18
  @staticmethod
18
19
  def resolve(language: str):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quapp-common
3
- Version: 0.0.13.dev6
3
+ Version: 0.0.13.dev7
4
4
  Summary: Quapp common library supporting Quapp Platform for Quantum Computing
5
5
  Author-email: "CITYNOW Co. Ltd. " <corp@citynow.vn>
6
6
  License: The MIT License (MIT)
@@ -84,4 +84,5 @@ quapp_common/util/json_parser_utils.py
84
84
  quapp_common/util/response_utils.py
85
85
  tests/test_circuit_adapter.py
86
86
  tests/test_json_parser_utils.py
87
+ tests/test_language_dispatcher.py
87
88
  tests/test_result_serializer.py
@@ -0,0 +1,20 @@
1
+ import unittest
2
+
3
+ from quapp_common.component.dispatcher import RUNNER_MAP
4
+ from quapp_common.enum.language import Language
5
+
6
+
7
+ class TestLanguageCEnum(unittest.TestCase):
8
+ def test_value_is_c(self):
9
+ self.assertEqual(Language.C.value, 'c')
10
+
11
+
12
+ class TestRunnerMapLanguageC(unittest.TestCase):
13
+ def test_language_c_registered(self):
14
+ self.assertIn(Language.C, RUNNER_MAP)
15
+
16
+ def test_command_is_compiled_binary(self):
17
+ self.assertEqual(RUNNER_MAP[Language.C]['command'], ['./handler_compiled'])
18
+
19
+ def test_timeout(self):
20
+ self.assertEqual(RUNNER_MAP[Language.C]['timeout'], 300)