Skip to content

Commit 99ede56

Browse files
committed
Remove batch plumbling
1 parent bdd1140 commit 99ede56

File tree

4 files changed

+42
-148
lines changed

4 files changed

+42
-148
lines changed

azure-quantum/azure/quantum/qiskit/backends/backend.py

Lines changed: 38 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
logger = logging.getLogger(__name__)
1212

13-
from typing import Any, Dict, Tuple, Union, List, Optional
13+
from typing import Any, Dict, Union, List, Optional
1414
from azure.quantum.version import __version__
1515
from azure.quantum.qiskit.job import (
1616
MICROSOFT_OUTPUT_DATA_FORMAT,
@@ -27,7 +27,6 @@
2727
from qiskit.providers import Provider
2828
from qiskit.providers.models import BackendConfiguration
2929
from qiskit.qobj import QasmQobj, PulseQobj
30-
import pyqir as pyqir
3130
from qsharp.interop.qiskit import QSharpBackend
3231
from qsharp import TargetProfile
3332

@@ -101,7 +100,7 @@ def run(
101100
102101
Args:
103102
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
104-
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
103+
list of one :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
105104
shots (int, optional): Number of shots, defaults to None.
106105
options: Any kwarg options to pass to the backend for running the
107106
config. If a key is also present in the options
@@ -334,7 +333,7 @@ def run(
334333
335334
Args:
336335
run_input (QuantumCircuit or List[QuantumCircuit]): An individual or a
337-
list of :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
336+
list of one :class:`~qiskit.circuits.QuantumCircuit` to run on the backend.
338337
shots (int, optional): Number of shots, defaults to None.
339338
options: Any kwarg options to pass to the backend for running the
340339
config. If a key is also present in the options
@@ -349,17 +348,16 @@ def run(
349348
options.pop("run_input", None)
350349
options.pop("circuit", None)
351350

352-
circuits = list([])
353-
if isinstance(run_input, QuantumCircuit):
354-
circuits = [run_input]
355-
else:
356-
circuits = run_input
357-
358-
max_circuits_per_job = self.configuration().max_experiments
359-
if len(circuits) > max_circuits_per_job:
360-
raise NotImplementedError(
361-
f"This backend only supports running a maximum of {max_circuits_per_job} circuits per job."
362-
)
351+
circuit = run_input
352+
if isinstance(run_input, list):
353+
# just in case they passed a list, we only support single-experiment jobs
354+
if len(run_input) != 1:
355+
raise NotImplementedError(
356+
f"This backend only supports running a single circuit per job."
357+
)
358+
circuit = run_input[0]
359+
if not isinstance(circuit, QuantumCircuit):
360+
raise ValueError("Invalid input: expected a QuantumCircuit.")
363361

364362
# config normalization
365363
input_params = self._get_input_params(options, shots=shots)
@@ -369,18 +367,11 @@ def run(
369367
if self._can_send_shots_input_param():
370368
shots_count = input_params.get(self.__class__._SHOTS_PARAM_NAME)
371369

372-
job_name = ""
373-
if len(circuits) > 1:
374-
job_name = f"batch-{len(circuits)}"
375-
if shots_count is not None:
376-
job_name = f"{job_name}-{shots_count}"
377-
else:
378-
job_name = circuits[0].name
379-
job_name = options.pop("job_name", job_name)
370+
job_name = options.pop("job_name", circuit.name)
380371

381-
metadata = options.pop("metadata", self._prepare_job_metadata(circuits))
372+
metadata = options.pop("metadata", self._prepare_job_metadata(circuit))
382373

383-
input_data = self._translate_input(circuits, input_params)
374+
input_data = self._translate_input(circuit, input_params)
384375

385376
job = super()._run(job_name, input_data, input_params, metadata, **options)
386377
logger.info(
@@ -389,28 +380,19 @@ def run(
389380

390381
return job
391382

392-
def _prepare_job_metadata(self, circuits: List[QuantumCircuit]) -> Dict[str, str]:
383+
def _prepare_job_metadata(self, circuit: QuantumCircuit) -> Dict[str, str]:
393384
"""Returns the metadata relative to the given circuits that will be attached to the Job"""
394-
if len(circuits) == 1:
395-
circuit: QuantumCircuit = circuits[0]
396-
return {
397-
"qiskit": str(True),
398-
"name": circuit.name,
399-
"num_qubits": circuit.num_qubits,
400-
"metadata": json.dumps(circuit.metadata),
401-
}
402-
# for batch jobs, we don't want to store the metadata of each circuit
403-
# we fill out the result header in output processing.
404-
# These headers don't matter for execution are are only used for
405-
# result processing.
406-
return {}
407-
408-
def _generate_qir(
409-
self, circuits: List[QuantumCircuit], target_profile: TargetProfile, **kwargs
410-
) -> pyqir.Module:
411-
412-
if len(circuits) == 0:
413-
raise ValueError("No QuantumCircuits provided")
385+
return {
386+
"qiskit": str(True),
387+
"name": circuit.name,
388+
"num_qubits": circuit.num_qubits,
389+
"metadata": json.dumps(circuit.metadata),
390+
}
391+
392+
393+
def _get_qir_str(
394+
self, circuit: QuantumCircuit, target_profile: TargetProfile, **kwargs
395+
) -> str:
414396

415397
config = self.configuration()
416398
# Barriers aren't removed by transpilation and must be explicitly removed in the Qiskit to QIR translation.
@@ -424,72 +406,36 @@ def _generate_qir(
424406
**kwargs,
425407
)
426408

427-
name = "batch"
428-
if len(circuits) == 1:
429-
name = circuits[0].name
430-
431-
if isinstance(circuits, list):
432-
for value in circuits:
433-
if not isinstance(value, QuantumCircuit):
434-
raise ValueError("Input must be List[QuantumCircuit]")
435-
else:
436-
raise ValueError("Input must be List[QuantumCircuit]")
437-
438-
context = pyqir.Context()
439-
llvm_module = pyqir.qir_module(context, name)
440-
for circuit in circuits:
441-
qir_str = backend.qir(circuit)
442-
module = pyqir.Module.from_ir(context, qir_str)
443-
entry_point = next(filter(pyqir.is_entry_point, module.functions))
444-
entry_point.name = circuit.name
445-
llvm_module.link(module)
446-
err = llvm_module.verify()
447-
if err is not None:
448-
raise Exception(err)
449-
450-
return llvm_module
409+
qir_str = backend.qir(circuit)
410+
411+
return qir_str
451412

452-
def _get_qir_str(
453-
self,
454-
circuits: List[QuantumCircuit],
455-
target_profile: TargetProfile,
456-
**to_qir_kwargs,
457-
) -> str:
458-
module = self._generate_qir(circuits, target_profile, **to_qir_kwargs)
459-
return str(module)
460413

461414
def _translate_input(
462-
self, circuits: Union[QuantumCircuit, List[QuantumCircuit]], input_params: Dict[str, Any]
415+
self, circuit: QuantumCircuit, input_params: Dict[str, Any]
463416
) -> bytes:
464417
"""Translates the input values to the QIR expected by the Backend."""
465418
logger.info(f"Using QIR as the job's payload format.")
466-
if not (isinstance(circuits, list)):
467-
circuits = [circuits]
468419

469420
target_profile = self._get_target_profile(input_params)
470421

471422
if logger.isEnabledFor(logging.DEBUG):
472-
qir = self._get_qir_str(circuits, target_profile, skip_transpilation=True)
423+
qir = self._get_qir_str(circuit, target_profile, skip_transpilation=True)
473424
logger.debug(f"QIR:\n{qir}")
474425

475426
# We'll transpile automatically to the supported gates in QIR unless explicitly skipped.
476427
skip_transpilation = input_params.pop("skipTranspile", False)
477428

478-
module = self._generate_qir(
479-
circuits, target_profile, skip_transpilation=skip_transpilation
429+
qir_str = self._get_qir_str(
430+
circuit, target_profile, skip_transpilation=skip_transpilation
480431
)
481432

482-
def get_func_name(func: pyqir.Function) -> str:
483-
return func.name
484-
485-
entry_points = list(
486-
map(get_func_name, filter(pyqir.is_entry_point, module.functions))
487-
)
433+
entry_points = ["ENTTRYPOINT_main"]
488434

489435
if not skip_transpilation:
490436
# We'll only log the QIR again if we performed a transpilation.
491437
if logger.isEnabledFor(logging.DEBUG):
492-
qir = str(module)
438+
qir = str(qir_str)
493439
logger.debug(f"QIR (Post-transpilation):\n{qir}")
494440

495441
if "items" not in input_params:
@@ -498,7 +444,7 @@ def get_func_name(func: pyqir.Function) -> str:
498444
{"entryPoint": name, "arguments": arguments} for name in entry_points
499445
]
500446

501-
return str(module).encode("utf-8")
447+
return qir_str.encode("utf-8")
502448

503449
def _get_target_profile(self, input_params) -> TargetProfile:
504450
# Default to Adaptive_RI if not specified on the backend

azure-quantum/azure/quantum/target/target.py

Lines changed: 1 addition & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
# Copyright (c) Microsoft Corporation. All rights reserved.
33
# Licensed under the MIT License.
44
##
5-
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union, Type, Protocol, runtime_checkable
5+
from typing import TYPE_CHECKING, Any, Dict, Union, Type, Protocol, runtime_checkable
66
from dataclasses import dataclass
77
import io
88
import json
@@ -339,54 +339,3 @@ def _get_azure_target_id(self) -> str:
339339

340340
def _get_azure_provider_id(self) -> str:
341341
return self.provider_id
342-
343-
@classmethod
344-
def _calculate_qir_module_gate_stats(self, qir_module) -> GateStats:
345-
try:
346-
from pyqir import Module, is_qubit_type, is_result_type, entry_point, is_entry_point, Function
347-
348-
except ImportError:
349-
raise ImportError(
350-
"Missing optional 'qiskit' dependencies. \
351-
To install run: pip install azure-quantum[qiskit]"
352-
)
353-
354-
module: Module = qir_module
355-
356-
one_qubit_gates = 0
357-
multi_qubit_gates = 0
358-
measurement_gates = 0
359-
360-
function_entry_points: list[Function] = filter(is_entry_point, module.functions)
361-
362-
# Iterate over the blocks and their instructions
363-
for function in function_entry_points:
364-
for block in function.basic_blocks:
365-
for instruction in block.instructions:
366-
qubit_count = 0
367-
result_count = 0
368-
369-
# If the instruction is of type quantum rt, do not include this is the price calculation
370-
if len(instruction.operands) > 0 and "__quantum__rt" not in instruction.operands[-1].name:
371-
# Check each operand in the instruction
372-
for operand in instruction.operands:
373-
value_type = operand.type
374-
375-
if is_qubit_type(value_type):
376-
qubit_count += 1
377-
elif is_result_type(value_type):
378-
result_count += 1
379-
380-
# Determine the type of gate based on the counts
381-
if qubit_count == 1 and result_count == 0:
382-
one_qubit_gates += 1
383-
if qubit_count >= 2 and result_count == 0:
384-
multi_qubit_gates += 1
385-
if result_count > 0:
386-
measurement_gates += 1
387-
388-
return GateStats (
389-
one_qubit_gates,
390-
multi_qubit_gates,
391-
measurement_gates
392-
)
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
qiskit-ionq>=0.5,<0.6
22
qsharp[qiskit]>=1.9.0,<2.0
3-
pyqir>=0.10.6,<0.11
43
Markdown>=3.4.1,<4.0
54
python-markdown-math>=0.8.0,<1.0

azure-quantum/tests/unit/test_qiskit.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def _default_options(cls):
127127
return None
128128

129129
def _translate_input(
130-
self, circuits: List[QuantumCircuit], input_params: Dict[str, Any]
130+
self, circuit: QuantumCircuit, input_params: Dict[str, Any]
131131
) -> bytes:
132132
return None
133133

@@ -436,7 +436,7 @@ def test_plugins_submit_qiskit_multi_circuit_experiment_to_ionq(self):
436436

437437
with pytest.raises(NotImplementedError) as exc:
438438
backend.run(circuit=[circuit, circuit], shots=500)
439-
self.assertEqual(str(exc.value), "This backend only supports running a maximum of 1 circuits per job.")
439+
self.assertEqual(str(exc.value), "This backend only supports running a single circuit per job.")
440440

441441
@pytest.mark.ionq
442442
@pytest.mark.live_test
@@ -1111,7 +1111,7 @@ def test_plugins_submit_qiskit_multi_circuit_experiment_to_quantinuum(self):
11111111

11121112
with self.assertRaises(NotImplementedError) as context:
11131113
backend.run(circuit=[circuit, circuit], shots=None)
1114-
self.assertEqual(str(context.exception), "This backend only supports running a maximum of 1 circuits per job.")
1114+
self.assertEqual(str(context.exception), "This backend only supports running a single circuit per job.")
11151115

11161116
@pytest.mark.quantinuum
11171117
@pytest.mark.live_test

0 commit comments

Comments
 (0)