azure-quantum 2.1.1__py3-none-any.whl → 2.2.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.
@@ -11,26 +11,7 @@ from .backend import AzureQirBackend
11
11
 
12
12
  from qiskit.providers.models import BackendConfiguration
13
13
  from qiskit.providers import Options, Provider
14
-
15
- QIR_BASIS_GATES = [
16
- "measure",
17
- "m",
18
- "cx",
19
- "cz",
20
- "h",
21
- "reset",
22
- "rx",
23
- "ry",
24
- "rz",
25
- "s",
26
- "sdg,"
27
- "t",
28
- "tdg",
29
- "x",
30
- "y",
31
- "z",
32
- "id",
33
- ]
14
+ from qsharp import TargetProfile
34
15
 
35
16
  if TYPE_CHECKING:
36
17
  from azure.quantum.qiskit import AzureQuantumProvider
@@ -60,7 +41,7 @@ class RigettiBackend(AzureQirBackend):
60
41
  other_options = {
61
42
  cls._SHOTS_PARAM_NAME: _DEFAULT_SHOTS_COUNT,
62
43
  }
63
- return Options(targetCapability="BasicExecution", **other_options)
44
+ return Options(target_profile=TargetProfile.Base, **other_options)
64
45
 
65
46
  def _azure_config(self) -> Dict[str, str]:
66
47
  config = super()._azure_config()
@@ -85,7 +66,7 @@ class RigettiSimulatorBackend(RigettiBackend):
85
66
  "local": False,
86
67
  "coupling_map": None,
87
68
  "description": "Rigetti simulator on Azure Quantum",
88
- "basis_gates": QIR_BASIS_GATES,
69
+ "basis_gates": self._basis_gates(),
89
70
  "memory": True,
90
71
  "n_qubits": RigettiTarget.num_qubits(name),
91
72
  "conditional": False,
@@ -117,7 +98,7 @@ class RigettiQPUBackend(RigettiBackend):
117
98
  "local": False,
118
99
  "coupling_map": None,
119
100
  "description": "Rigetti QPU on Azure Quantum",
120
- "basis_gates": QIR_BASIS_GATES,
101
+ "basis_gates": self._basis_gates(),
121
102
  "memory": True,
122
103
  "n_qubits": RigettiTarget.num_qubits(name),
123
104
  "conditional": False,
@@ -212,15 +212,19 @@ class AzureQuantumJob(JobV1):
212
212
 
213
213
  @staticmethod
214
214
  def _qir_to_qiskit_bitstring(obj):
215
- """Convert the data structure from Azure into the "schema" used by Qiskit """
215
+ """Convert the data structure from Azure into the "schema" used by Qiskit"""
216
216
  if isinstance(obj, str) and not re.match(r"[\d\s]+$", obj):
217
217
  obj = ast.literal_eval(obj)
218
218
 
219
219
  if isinstance(obj, tuple):
220
220
  # the outermost implied container is a tuple, and each item is
221
- # associated with a classical register. Azure and Qiskit order the
222
- # registers in opposite directions, so reverse here to match.
223
- return " ".join([AzureQuantumJob._qir_to_qiskit_bitstring(term) for term in reversed(obj)])
221
+ # associated with a classical register.
222
+ return " ".join(
223
+ [
224
+ AzureQuantumJob._qir_to_qiskit_bitstring(term)
225
+ for term in obj
226
+ ]
227
+ )
224
228
  elif isinstance(obj, list):
225
229
  # a list is for an individual classical register
226
230
  return "".join([str(bit) for bit in obj])
@@ -312,6 +316,26 @@ class AzureQuantumJob(JobV1):
312
316
  entry_point_names.append(entry_point["entryPoint"])
313
317
  return entry_point_names if len(entry_point_names) > 0 else ["main"]
314
318
 
319
+ def _get_headers(self):
320
+ headers = self._azure_job.details.metadata
321
+ if (not isinstance(headers, list)):
322
+ headers = [headers]
323
+
324
+ # This function will attempt to parse the header into a JSON object, and if the header is not a JSON object, we return the header itself
325
+ def tryParseJSON(header):
326
+ try:
327
+ json_object = json.loads(header)
328
+ except ValueError as e:
329
+ return header
330
+ return json_object
331
+
332
+ for header in headers:
333
+ del header['qiskit'] # we throw out the qiskit header as it is implied
334
+ for key in header.keys():
335
+ header[key] = tryParseJSON(header[key])
336
+ return headers
337
+
338
+
315
339
  def _format_microsoft_v2_results(self) -> List[Dict[str, Any]]:
316
340
  success = self._azure_job.details.status == "Succeeded"
317
341
 
@@ -326,10 +350,15 @@ class AzureQuantumJob(JobV1):
326
350
  entry_point_names = self._get_entry_point_names()
327
351
 
328
352
  results = self._translate_microsoft_v2_results()
329
-
353
+
330
354
  if len(results) != len(entry_point_names):
331
- raise ValueError("The number of experiment results does not match the number of experiment names")
355
+ raise ValueError("The number of experiment results does not match the number of entry point names")
356
+
357
+ headers = self._get_headers()
332
358
 
359
+ if len(results) != len(headers):
360
+ raise ValueError("The number of experiment results does not match the number of headers")
361
+
333
362
  status = self.status()
334
363
 
335
364
  return [{
@@ -338,7 +367,5 @@ class AzureQuantumJob(JobV1):
338
367
  "shots": total_count,
339
368
  "name": name,
340
369
  "status": status,
341
- "header": {
342
- "name": name
343
- }
344
- } for name, (total_count, result) in zip(entry_point_names, results)]
370
+ "header": header
371
+ } for name, (total_count, result), header in zip(entry_point_names, results, headers)]
@@ -129,9 +129,9 @@ see https://aka.ms/AQ/Docs/AddProvider"
129
129
  filtered_backends,
130
130
  )
131
131
  )
132
- # If default backends were found - return them, otherwise return the filtered_backends collection.
133
- # The latter case could happen where there's no default backend defined for the specified target.
134
- if len(default_backends) > 0:
132
+ # If default backends were found - return them, otherwise return the filtered_backends collection.
133
+ # The latter case could happen where there's no default backend defined for the specified target.
134
+ if len(default_backends) > 0:
135
135
  return default_backends
136
136
 
137
137
  return filtered_backends
@@ -277,7 +277,7 @@ see https://aka.ms/AQ/Docs/AddProvider"
277
277
  warnings.warn(
278
278
  f"Specified filters {unknown_filters} are not supported by the available backends."
279
279
  )
280
-
280
+
281
281
  backends = list(filter(filters, backends))
282
282
 
283
283
  return backends
@@ -12,6 +12,7 @@ from azure.quantum.target.target import (
12
12
  from azure.quantum.job.job import Job
13
13
  from azure.quantum.workspace import Workspace
14
14
  from azure.quantum._client.models import CostEstimate, UsageEvent
15
+ from typing import Union
15
16
 
16
17
  COST_1QUBIT_GATE_MAP = {
17
18
  "ionq.simulator" : 0.0,
@@ -58,12 +59,16 @@ class IonQ(Target):
58
59
  name: str = "ionq.simulator",
59
60
  input_data_format: str = "ionq.circuit.v1",
60
61
  output_data_format: str = "ionq.quantum-results.v1",
61
- capability: str = "BasicExecution",
62
+ capability: str = "",
62
63
  provider_id: str = "IonQ",
63
64
  content_type: str = "application/json",
64
65
  encoding: str = "",
65
- **kwargs
66
+ target_profile: Union[str, "TargetProfile"] = "Base",
67
+ **kwargs,
66
68
  ):
69
+ if capability:
70
+ msg = "The 'capability' parameter is not used for the Quantinuum target."
71
+ warn(msg, DeprecationWarning)
67
72
  super().__init__(
68
73
  workspace=workspace,
69
74
  name=name,
@@ -73,7 +78,8 @@ class IonQ(Target):
73
78
  provider_id=provider_id,
74
79
  content_type=content_type,
75
80
  encoding=encoding,
76
- **kwargs
81
+ target_profile=target_profile,
82
+ **kwargs,
77
83
  )
78
84
 
79
85
  def submit(
@@ -123,7 +129,7 @@ class IonQ(Target):
123
129
 
124
130
  def estimate_cost(
125
131
  self,
126
- circuit: Dict[str, Any],
132
+ circuit: Union[Dict[str, Any], Any],
127
133
  num_shots: int = None,
128
134
  price_1q: float = None,
129
135
  price_2q: float = None,
@@ -174,20 +180,6 @@ class IonQ(Target):
174
180
  )
175
181
  shots = num_shots
176
182
 
177
- def is_1q_gate(gate: Dict[str, Any]):
178
- return "controls" not in gate and "control" not in gate
179
-
180
- def is_multi_q_gate(gate):
181
- return "controls" in gate or "control" in gate
182
-
183
- def num_2q_gates(gate):
184
- controls = gate.get("controls")
185
- if controls is None or len(controls) == 1:
186
- # Only one control qubit
187
- return 1
188
- # Multiple control qubits
189
- return 6 * (len(controls) - 2)
190
-
191
183
  # Get the costs for the gates depending on the provider if not specified
192
184
  if price_1q is None:
193
185
  price_1q = COST_1QUBIT_GATE_MAP[self.name]
@@ -198,10 +190,28 @@ class IonQ(Target):
198
190
  if min_price is None:
199
191
  min_price = MIN_PRICE_MAP[self.name]
200
192
 
201
- gates = circuit.get("circuit", [])
202
- N_1q = sum(map(is_1q_gate, gates))
203
- N_2q = sum(map(num_2q_gates, filter(is_multi_q_gate, gates)))
204
-
193
+ if (isinstance(circuit, Dict)):
194
+ def is_1q_gate(gate: Dict[str, Any]):
195
+ return "controls" not in gate and "control" not in gate
196
+
197
+ def is_multi_q_gate(gate):
198
+ return "controls" in gate or "control" in gate
199
+
200
+ def num_2q_gates(gate):
201
+ controls = gate.get("controls")
202
+ if controls is None or len(controls) == 1:
203
+ # Only one control qubit
204
+ return 1
205
+ # Multiple control qubits
206
+ return 6 * (len(controls) - 2)
207
+
208
+ gates = circuit.get("circuit", [])
209
+ N_1q = sum(map(is_1q_gate, gates))
210
+ N_2q = sum(map(num_2q_gates, filter(is_multi_q_gate, gates)))
211
+
212
+ else:
213
+ N_1q, N_2q, _ = Target._calculate_qir_module_gate_stats(circuit)
214
+
205
215
  price = (price_1q * N_1q + price_2q * N_2q) * shots
206
216
  price = max(price, min_price)
207
217
 
@@ -392,6 +392,7 @@ class MicrosoftEstimator(Target):
392
392
  output_data_format="microsoft.resource-estimates.v1",
393
393
  provider_id="microsoft-qc",
394
394
  content_type=ContentType.json,
395
+ target_profile="Adaptive_RI",
395
396
  **kwargs
396
397
  )
397
398
 
@@ -422,14 +423,21 @@ class MicrosoftEstimator(Target):
422
423
  warnings.warn("The 'shots' parameter is ignored in resource estimation job.")
423
424
 
424
425
  try:
425
- from qiskit import QuantumCircuit, transpile
426
- from qiskit_qir import to_qir_module
427
- from qiskit_qir.visitor import SUPPORTED_INSTRUCTIONS
426
+ from qiskit import QuantumCircuit
427
+ from qsharp import TargetProfile
428
+ from qsharp.interop.qiskit import ResourceEstimatorBackend
429
+ from pyqir import Context, Module
430
+
428
431
  if isinstance(input_data, QuantumCircuit):
429
- input_data = transpile(input_data,
430
- basis_gates=SUPPORTED_INSTRUCTIONS,
431
- optimization_level=0)
432
- (module, _) = to_qir_module(input_data, record_output=False)
432
+ backend = ResourceEstimatorBackend()
433
+ target_profile = TargetProfile.from_str(self.target_profile)
434
+ qir_str = backend.qir(input_data, target_profile=target_profile)
435
+ context = Context()
436
+ module = Module.from_ir(context, qir_str)
437
+
438
+ err = module.verify()
439
+ if err is not None:
440
+ raise Exception(err)
433
441
  input_data = module.bitcode
434
442
  finally:
435
443
  return super().submit(
@@ -80,9 +80,10 @@ class Pasqal(Target):
80
80
  name: Union[PasqalTarget, str] = PasqalTarget.SIM_EMU_TN,
81
81
  input_data_format: str = "pasqal.pulser.v1",
82
82
  output_data_format: str = "pasqal.pulser-results.v1",
83
- capability: str = "BasicExecution",
83
+ capability: str = "",
84
84
  provider_id: str = "pasqal",
85
85
  encoding: str = "",
86
+ target_profile: Union[str, "TargetProfile"] = "Base",
86
87
  **kwargs,
87
88
  ):
88
89
  """
@@ -102,7 +103,12 @@ class Pasqal(Target):
102
103
  :type provider_id: str
103
104
  :param encoding: "Content-Encoding" attribute value to set on input blob (ex. "gzip")
104
105
  :type encoding: str
106
+ :param target_profile: Target QIR profile.
107
+ :type target_profile: str | TargetProfile
105
108
  """
109
+ if capability:
110
+ msg = "The 'capability' parameter is not used for the Quantinuum target."
111
+ warn(msg, DeprecationWarning)
106
112
 
107
113
  super().__init__(
108
114
  workspace=workspace,
@@ -2,7 +2,7 @@
2
2
  # Copyright (c) Microsoft Corporation.
3
3
  # Licensed under the MIT License.
4
4
  ##
5
- from typing import Any, Dict
5
+ from typing import Any, Dict, Union
6
6
  from warnings import warn
7
7
 
8
8
  from azure.quantum.target.target import (
@@ -34,12 +34,16 @@ class Quantinuum(Target):
34
34
  name: str = "quantinuum.sim.h1-1sc",
35
35
  input_data_format: str = "honeywell.openqasm.v1",
36
36
  output_data_format: str = "honeywell.quantum-results.v1",
37
- capability: str = "AdaptiveExecution",
37
+ capability: str = "",
38
38
  provider_id: str = "quantinuum",
39
39
  content_type: str = "application/qasm",
40
40
  encoding: str = "",
41
+ target_profile: Union[str, "TargetProfile"] = "Adaptive_RI",
41
42
  **kwargs
42
43
  ):
44
+ if capability:
45
+ msg = "The 'capability' parameter is not used for the Quantinuum target."
46
+ warn(msg, DeprecationWarning)
43
47
  super().__init__(
44
48
  workspace=workspace,
45
49
  name=name,
@@ -49,6 +53,7 @@ class Quantinuum(Target):
49
53
  provider_id=provider_id,
50
54
  content_type=content_type,
51
55
  encoding=encoding,
56
+ target_profile=target_profile,
52
57
  **kwargs
53
58
  )
54
59
 
@@ -98,7 +103,7 @@ class Quantinuum(Target):
98
103
 
99
104
  def estimate_cost(
100
105
  self,
101
- circuit: str = None,
106
+ circuit: Union[str, Any] = None,
102
107
  num_shots: int = None,
103
108
  N_1q: int = None,
104
109
  N_2q: int = None,
@@ -144,30 +149,35 @@ class Quantinuum(Target):
144
149
  )
145
150
  shots = num_shots
146
151
 
147
- if circuit is not None and (N_1q is None or N_2q is None or N_m is None):
148
- try:
149
- from qiskit.qasm2 import loads
150
- from qiskit.converters.circuit_to_dag import circuit_to_dag
151
-
152
- except ImportError:
153
- raise ImportError(
154
- "Missing dependency qiskit. Please run `pip install azure-quantum[qiskit]` " \
155
- "to estimate the circuit cost. Alternatively, specify the number of one-qubit and two-qubit " \
156
- "gates in the method input arguments.")
157
-
158
- else:
159
- from qiskit.dagcircuit.dagnode import DAGOpNode
160
- circuit_obj = loads(string=circuit)
161
- dag = circuit_to_dag(circuit=circuit_obj)
162
- N_1q, N_2q, N_m = 0, 0, 0
163
- for node in dag._multi_graph.nodes():
164
- if isinstance(node, DAGOpNode):
165
- if node.op.name in ["measure", "reset"]:
166
- N_m += 1
167
- elif node.op.num_qubits == 1:
168
- N_1q += 1
169
- else:
170
- N_2q += 1
152
+ # If we use passthrough, else assume QIR
153
+ if (isinstance(circuit, str)):
154
+ if circuit is not None and (N_1q is None or N_2q is None or N_m is None):
155
+ try:
156
+ from qiskit.qasm2 import loads
157
+ from qiskit.converters.circuit_to_dag import circuit_to_dag
158
+
159
+ except ImportError:
160
+ raise ImportError(
161
+ "Missing dependency qiskit. Please run `pip install azure-quantum[qiskit]` " \
162
+ "to estimate the circuit cost. Alternatively, specify the number of one-qubit and two-qubit " \
163
+ "gates in the method input arguments.")
164
+
165
+ else:
166
+ from qiskit.dagcircuit.dagnode import DAGOpNode
167
+ circuit_obj = loads(string=circuit)
168
+ dag = circuit_to_dag(circuit=circuit_obj)
169
+ N_1q, N_2q, N_m = 0, 0, 0
170
+ for node in dag._multi_graph.nodes():
171
+ if isinstance(node, DAGOpNode):
172
+ if node.op.name in ["measure", "reset"]:
173
+ N_m += 1
174
+ elif node.op.num_qubits == 1:
175
+ N_1q += 1
176
+ else:
177
+ N_2q += 1
178
+ else:
179
+ N_1q, N_2q, N_m = Target._calculate_qir_module_gate_stats(circuit)
180
+
171
181
 
172
182
  import re
173
183
  is_emulator_regex = re.compile("^.*(-sim|-[0-9]*e)$")
@@ -14,6 +14,7 @@ __all__ = [
14
14
  from dataclasses import dataclass
15
15
  from enum import Enum
16
16
  from typing import Union, Any, Dict, List, Optional
17
+ from warnings import warn
17
18
 
18
19
  from ..target import Target
19
20
  from ... import Job
@@ -29,7 +30,7 @@ class RigettiTarget(str, Enum):
29
30
  QVM = "rigetti.sim.qvm"
30
31
  """A simulator target for Quil. See https://github.com/quil-lang/qvm for more info."""
31
32
 
32
- ANKAA_2 = "rigetti.qpu.ankaa-2"
33
+ ANKAA_9Q_3 = "rigetti.qpu.ankaa-9q-3"
33
34
 
34
35
  def simulators() -> List[str]:
35
36
  """Returns a list of simulator targets"""
@@ -40,7 +41,7 @@ class RigettiTarget(str, Enum):
40
41
  def qpus() -> List[str]:
41
42
  """Returns a list of QPU targets"""
42
43
  return [
43
- RigettiTarget.ANKAA_2.value,
44
+ RigettiTarget.ANKAA_9Q_3.value,
44
45
  ]
45
46
 
46
47
  def num_qubits(target_name) -> int:
@@ -48,8 +49,8 @@ class RigettiTarget(str, Enum):
48
49
 
49
50
  if target_name == RigettiTarget.QVM.value:
50
51
  return 20
51
- elif target_name == RigettiTarget.ANKAA_2.value:
52
- return 84
52
+ elif target_name == RigettiTarget.ANKAA_9Q_3.value:
53
+ return 9
53
54
  else:
54
55
  raise ValueError(f"Unknown target {target_name}")
55
56
 
@@ -137,9 +138,10 @@ class Rigetti(Target):
137
138
  name: Union[RigettiTarget, str] = RigettiTarget.QVM,
138
139
  input_data_format: str = "rigetti.quil.v1",
139
140
  output_data_format: str = "rigetti.quil-results.v1",
140
- capability: str = "BasicExecution",
141
+ capability: str = "",
141
142
  provider_id: str = "rigetti",
142
143
  encoding: str = "",
144
+ target_profile: Union[str, "TargetProfile"] = "Base",
143
145
  **kwargs,
144
146
  ):
145
147
  """
@@ -159,8 +161,12 @@ class Rigetti(Target):
159
161
  :type provider_id: str
160
162
  :param encoding: "Content-Encoding" attribute value to set on input blob (ex. "gzip")
161
163
  :type encoding: str
164
+ :param target_profile: Target QIR profile.
165
+ :type target_profile: str | TargetProfile
162
166
  """
163
-
167
+ if capability:
168
+ msg = "The 'capability' parameter is not used for the Quantinuum target."
169
+ warn(msg, DeprecationWarning)
164
170
  super().__init__(
165
171
  workspace=workspace,
166
172
  name=name,
@@ -170,6 +176,7 @@ class Rigetti(Target):
170
176
  provider_id=provider_id,
171
177
  content_type="text/plain",
172
178
  encoding=encoding,
179
+ target_profile=target_profile,
173
180
  **kwargs,
174
181
  )
175
182
 
@@ -2,7 +2,8 @@
2
2
  # Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  # Licensed under the MIT License.
4
4
  ##
5
- from typing import TYPE_CHECKING, Any, Dict, Optional, Union, Type, Protocol, runtime_checkable
5
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, Type, Protocol, runtime_checkable
6
+ from dataclasses import dataclass
6
7
  import io
7
8
  import json
8
9
  import abc
@@ -26,6 +27,14 @@ class QirRepresentable(Protocol):
26
27
  def _repr_qir_(self, **kwargs: Any) -> bytes:
27
28
  raise NotImplementedError
28
29
 
30
+ @dataclass
31
+ class GateStats:
32
+ one_qubit_gates: int
33
+ multi_qubit_gates: int
34
+ measurement_gates: int
35
+
36
+ def __iter__(self):
37
+ return iter((self.one_qubit_gates, self.multi_qubit_gates, self.measurement_gates))
29
38
 
30
39
  class Target(abc.ABC, SessionHost):
31
40
 
@@ -59,7 +68,8 @@ class Target(abc.ABC, SessionHost):
59
68
  content_type: ContentType = ContentType.json,
60
69
  encoding: str = "",
61
70
  average_queue_time: Union[float, None] = None,
62
- current_availability: str = ""
71
+ current_availability: str = "",
72
+ target_profile: Union[str, "TargetProfile"] = "Base",
63
73
  ):
64
74
  """
65
75
  Initializes a new target.
@@ -72,7 +82,7 @@ class Target(abc.ABC, SessionHost):
72
82
  :type input_data_format: str
73
83
  :param output_data_format: Format of output data (ex. "microsoft.resource-estimates.v1")
74
84
  :type output_data_format: str
75
- :param capability: QIR capability
85
+ :param capability: QIR capability. Deprecated, use `target_profile`
76
86
  :type capability: str
77
87
  :param provider_id: Id of provider (ex. "microsoft-qc")
78
88
  :type provider_id: str
@@ -84,6 +94,8 @@ class Target(abc.ABC, SessionHost):
84
94
  :type average_queue_time: float
85
95
  :param current_availability: Set current availability (for internal use)
86
96
  :type current_availability: str
97
+ :param target_profile: Target QIR profile.
98
+ :type target_profile: str | TargetProfile
87
99
  """
88
100
  if not provider_id and "." in name:
89
101
  provider_id = name.split(".")[0]
@@ -97,6 +109,7 @@ class Target(abc.ABC, SessionHost):
97
109
  self.encoding = encoding
98
110
  self._average_queue_time = average_queue_time
99
111
  self._current_availability = current_availability
112
+ self.target_profile = target_profile
100
113
 
101
114
  def __repr__(self):
102
115
  return f"<Target name=\"{self.name}\", \
@@ -246,8 +259,17 @@ target '{self.name}' of provider '{self.provider_id}' not found."
246
259
  input_params["arguments"] = input_params.get("arguments", [])
247
260
  targetCapability = input_params.get("targetCapability", kwargs.pop("target_capability", self.capability))
248
261
  if targetCapability:
262
+ warnings.warn(
263
+ "The 'targetCapability' parameter is deprecated and will be ignored in the future. "
264
+ "Please, use 'target_profile' parameter instead.",
265
+ category=DeprecationWarning,
266
+ )
249
267
  input_params["targetCapability"] = targetCapability
250
- input_data = input_data._repr_qir_(target=self.name, target_capability=targetCapability)
268
+ if target_profile := input_params.get(
269
+ "target_profile", kwargs.pop("target_profile", self.target_profile)
270
+ ):
271
+ input_params["target_profile"] = target_profile
272
+ input_data = input_data._repr_qir_(target=self.name)
251
273
  else:
252
274
  input_data_format = kwargs.pop("input_data_format", self.input_data_format)
253
275
  output_data_format = kwargs.pop("output_data_format", self.output_data_format)
@@ -327,6 +349,57 @@ target '{self.name}' of provider '{self.provider_id}' not found."
327
349
  def _get_azure_provider_id(self) -> str:
328
350
  return self.provider_id
329
351
 
352
+ @classmethod
353
+ def _calculate_qir_module_gate_stats(self, qir_module) -> GateStats:
354
+ try:
355
+ from pyqir import Module, is_qubit_type, is_result_type, entry_point, is_entry_point, Function
356
+
357
+ except ImportError:
358
+ raise ImportError(
359
+ "Missing optional 'qiskit' dependencies. \
360
+ To install run: pip install azure-quantum[qiskit]"
361
+ )
362
+
363
+ module: Module = qir_module
364
+
365
+ one_qubit_gates = 0
366
+ multi_qubit_gates = 0
367
+ measurement_gates = 0
368
+
369
+ function_entry_points: list[Function] = filter(is_entry_point, module.functions)
370
+
371
+ # Iterate over the blocks and their instructions
372
+ for function in function_entry_points:
373
+ for block in function.basic_blocks:
374
+ for instruction in block.instructions:
375
+ qubit_count = 0
376
+ result_count = 0
377
+
378
+ # If the instruction is of type quantum rt, do not include this is the price calculation
379
+ if len(instruction.operands) > 0 and "__quantum__rt" not in instruction.operands[-1].name:
380
+ # Check each operand in the instruction
381
+ for operand in instruction.operands:
382
+ value_type = operand.type
383
+
384
+ if is_qubit_type(value_type):
385
+ qubit_count += 1
386
+ elif is_result_type(value_type):
387
+ result_count += 1
388
+
389
+ # Determine the type of gate based on the counts
390
+ if qubit_count == 1 and result_count == 0:
391
+ one_qubit_gates += 1
392
+ if qubit_count >= 2 and result_count == 0:
393
+ multi_qubit_gates += 1
394
+ if result_count > 0:
395
+ measurement_gates += 1
396
+
397
+ return GateStats (
398
+ one_qubit_gates,
399
+ multi_qubit_gates,
400
+ measurement_gates
401
+ )
402
+
330
403
 
331
404
  def _determine_shots_or_deprecated_num_shots(
332
405
  shots: int = None,
azure/quantum/version.py CHANGED
@@ -5,4 +5,4 @@
5
5
  # Copyright (c) Microsoft Corporation. All rights reserved.
6
6
  # Licensed under the MIT License.
7
7
  ##
8
- __version__ = "2.1.1"
8
+ __version__ = "2.2.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: azure-quantum
3
- Version: 2.1.1
3
+ Version: 2.2.0
4
4
  Summary: Python client for Azure Quantum
5
5
  Home-page: https://github.com/microsoft/azure-quantum-python
6
6
  Author: Microsoft
@@ -26,8 +26,8 @@ Requires-Dist: azure-devtools<2.0,>=1.2.0; extra == "all"
26
26
  Requires-Dist: graphviz>=0.20.1; extra == "all"
27
27
  Requires-Dist: pulser<0.19,>=0.18; extra == "all"
28
28
  Requires-Dist: qiskit-ionq<0.6,>=0.5; extra == "all"
29
- Requires-Dist: qiskit-qir<0.6,>=0.5; extra == "all"
30
- Requires-Dist: qiskit<2.0,>=1.0; extra == "all"
29
+ Requires-Dist: qsharp[qiskit]<2.0,>=1.9.0; extra == "all"
30
+ Requires-Dist: pyqir<0.11,>=0.10.6; extra == "all"
31
31
  Requires-Dist: Markdown<4.0,>=3.4.1; extra == "all"
32
32
  Requires-Dist: python-markdown-math<1.0,>=0.8.0; extra == "all"
33
33
  Requires-Dist: qsharp<2.0,>=1.0.33; extra == "all"
@@ -43,8 +43,8 @@ Provides-Extra: pulser
43
43
  Requires-Dist: pulser<0.19,>=0.18; extra == "pulser"
44
44
  Provides-Extra: qiskit
45
45
  Requires-Dist: qiskit-ionq<0.6,>=0.5; extra == "qiskit"
46
- Requires-Dist: qiskit-qir<0.6,>=0.5; extra == "qiskit"
47
- Requires-Dist: qiskit<2.0,>=1.0; extra == "qiskit"
46
+ Requires-Dist: qsharp[qiskit]<2.0,>=1.9.0; extra == "qiskit"
47
+ Requires-Dist: pyqir<0.11,>=0.10.6; extra == "qiskit"
48
48
  Requires-Dist: Markdown<4.0,>=3.4.1; extra == "qiskit"
49
49
  Requires-Dist: python-markdown-math<1.0,>=0.8.0; extra == "qiskit"
50
50
  Provides-Extra: qsharp