Skip to content

Commit

Permalink
Updating the artifact download path to fix the publish pipeline (micr…
Browse files Browse the repository at this point in the history
  • Loading branch information
masenol authored and Lawson Graham committed Aug 7, 2024
1 parent 73fe06d commit c5c8bd1
Show file tree
Hide file tree
Showing 52 changed files with 9,676 additions and 5,947 deletions.
54 changes: 49 additions & 5 deletions azure-quantum/azure/quantum/qiskit/backends/ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from azure.quantum.target.ionq import IonQ
from abc import abstractmethod

from qiskit import QuantumCircuit
from qiskit import QuantumCircuit, transpile

from .backend import (
AzureBackend,
Expand All @@ -22,6 +22,7 @@
from qiskit_ionq.helpers import (
ionq_basis_gates,
GATESET_MAP,
GATESET_MAP_QIR,
qiskit_circ_to_ionq_circ,
)

Expand Down Expand Up @@ -71,6 +72,9 @@ def _default_options(cls) -> Options:
},
targetCapability="BasicExecution",
)

def gateset(self):
return self.configuration().gateset

def _azure_config(self) -> Dict[str, str]:
config = super()._azure_config()
Expand All @@ -80,6 +84,42 @@ def _azure_config(self) -> Dict[str, str]:
}
)
return config

# TODO: decide if we want to allow for options passing differently
def estimate_cost(self, circuits, shots, options={}):
"""Estimate the cost for the given circuit."""
config = self.configuration()
input_params = self._get_input_params(options, shots=shots)

if not (isinstance(circuits, list)):
circuits = [circuits]

# TODO: evaluate proper means of use / fetching these values for transpile (could ignore, could use)
to_qir_kwargs = input_params.pop(
"to_qir_kwargs", config.azure.get("to_qir_kwargs", {"record_output": True})
)
targetCapability = input_params.pop(
"targetCapability",
self.options.get("targetCapability", "AdaptiveExecution"),
)

if not input_params.pop("skipTranspile", False):
# Set of gates supported by QIR targets.
circuits = transpile(
circuits, basis_gates=config.basis_gates, optimization_level=0
)

qir = self._get_qir_str(circuits, targetCapability, **to_qir_kwargs)
print (qir)


(module, _) = self._generate_qir(
circuits, targetCapability, **to_qir_kwargs
)

workspace = self.provider().get_workspace()
target = workspace.get_targets(self.name())
return target.estimate_cost(module, shots=shots)

def run(
self,
Expand All @@ -106,7 +146,7 @@ class IonQSimulatorQirBackend(IonQQirBackendBase):

def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"""Base class for interfacing with an IonQ QIR Simulator backend"""

gateset = kwargs.pop("gateset", "qis")
default_config = BackendConfiguration.from_dict(
{
"backend_name": name,
Expand All @@ -124,6 +164,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"open_pulse": False,
"gates": [{"name": "TODO", "parameters": [], "qasm_def": "TODO"}],
"azure": self._azure_config(),
"gateset": gateset
}
)
logger.info("Initializing IonQSimulatorQirBackend")
Expand All @@ -138,7 +179,7 @@ class IonQQPUQirBackend(IonQQirBackendBase):

def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"""Base class for interfacing with an IonQ QPU backend"""

gateset = kwargs.pop("gateset", "qis")
default_config = BackendConfiguration.from_dict(
{
"backend_name": name,
Expand All @@ -156,6 +197,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"open_pulse": False,
"gates": [{"name": "TODO", "parameters": [], "qasm_def": "TODO"}],
"azure": self._azure_config(),
"gateset": gateset
}
)
logger.info("Initializing IonQQPUQirBackend")
Expand All @@ -170,7 +212,7 @@ class IonQAriaQirBackend(IonQQirBackendBase):

def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"""Base class for interfacing with an IonQ Aria QPU backend"""

gateset = kwargs.pop("gateset", "qis")
default_config = BackendConfiguration.from_dict(
{
"backend_name": name,
Expand All @@ -188,6 +230,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"open_pulse": False,
"gates": [{"name": "TODO", "parameters": [], "qasm_def": "TODO"}],
"azure": self._azure_config(),
"gateset": gateset
}
)
logger.info("Initializing IonQAriaQirBackend")
Expand All @@ -202,7 +245,7 @@ class IonQForteQirBackend(IonQQirBackendBase):

def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"""Base class for interfacing with an IonQ Forte QPU backend"""

gateset = kwargs.pop("gateset", "qis")
default_config = BackendConfiguration.from_dict(
{
"backend_name": name,
Expand All @@ -220,6 +263,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"open_pulse": False,
"gates": [{"name": "TODO", "parameters": [], "qasm_def": "TODO"}],
"azure": self._azure_config(),
"gateset": gateset
}
)
logger.info("Initializing IonQForteQirBackend")
Expand Down
65 changes: 61 additions & 4 deletions azure-quantum/azure/quantum/qiskit/backends/quantinuum.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from .backend import AzureBackend, AzureQirBackend
from abc import abstractmethod
from qiskit import QuantumCircuit
from qiskit import QuantumCircuit, transpile
from qiskit.providers.models import BackendConfiguration
from qiskit.providers import Options
from qiskit.providers import Provider
Expand Down Expand Up @@ -50,6 +50,27 @@
"reset",
]

QUANTINUUM_BASIS_GATES_QIR = [
"x",
"y",
"z",
"rx",
"ry",
"rz",
"h",
"cx",
"cz",
"s",
"sdg",
"t",
"tdg",
"v",
"vdg",
"zz",
"measure",
"reset",
]

QUANTINUUM_PROVIDER_ID = "quantinuum"
QUANTINUUM_PROVIDER_NAME = "Quantinuum"

Expand Down Expand Up @@ -97,6 +118,42 @@ def _azure_config(self) -> Dict[str, str]:

def _get_n_qubits(self, name):
return _get_n_qubits(name)

# TODO: decide if we want to allow for options passing differently
def estimate_cost(self, circuits, shots, options={}):
"""Estimate the cost for the given circuit."""
config = self.configuration()
input_params = self._get_input_params(options, shots=shots)

if not (isinstance(circuits, list)):
circuits = [circuits]

# TODO: evaluate proper means of use / fetching these values for transpile (could ignore, could use)
to_qir_kwargs = input_params.pop(
"to_qir_kwargs", config.azure.get("to_qir_kwargs", {"record_output": True})
)
targetCapability = input_params.pop(
"targetCapability",
self.options.get("targetCapability", "AdaptiveExecution"),
)

if not input_params.pop("skipTranspile", False):
# Set of gates supported by QIR targets.
circuits = transpile(
circuits, basis_gates=config.basis_gates, optimization_level=0
)

qir = self._get_qir_str(circuits, targetCapability, **to_qir_kwargs)
print (qir)


(module, _) = self._generate_qir(
circuits, targetCapability, **to_qir_kwargs
)

workspace = self.provider().get_workspace()
target = workspace.get_targets(self.name())
return target.estimate_cost(module, shots=shots)


class QuantinuumSyntaxCheckerQirBackend(QuantinuumQirBackendBase):
Expand All @@ -118,7 +175,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"local": False,
"coupling_map": None,
"description": f"Quantinuum Syntax Checker on Azure Quantum",
"basis_gates": QUANTINUUM_BASIS_GATES,
"basis_gates": QUANTINUUM_BASIS_GATES_QIR,
"memory": True,
"n_qubits": self._get_n_qubits(name),
"conditional": False,
Expand Down Expand Up @@ -155,7 +212,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"local": False,
"coupling_map": None,
"description": f"Quantinuum emulator on Azure Quantum",
"basis_gates": QUANTINUUM_BASIS_GATES,
"basis_gates": QUANTINUUM_BASIS_GATES_QIR,
"memory": True,
"n_qubits": self._get_n_qubits(name),
"conditional": False,
Expand Down Expand Up @@ -192,7 +249,7 @@ def __init__(self, name: str, provider: "AzureQuantumProvider", **kwargs):
"local": False,
"coupling_map": None,
"description": f"Quantinuum QPU on Azure Quantum",
"basis_gates": QUANTINUUM_BASIS_GATES,
"basis_gates": QUANTINUUM_BASIS_GATES_QIR,
"memory": True,
"n_qubits": self._get_n_qubits(name),
"conditional": False,
Expand Down
26 changes: 21 additions & 5 deletions azure-quantum/azure/quantum/qiskit/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,20 @@ def _get_entry_point_names(self):
entry_point_names.append(entry_point["entryPoint"])
return entry_point_names if len(entry_point_names) > 0 else ["main"]

def _get_headers(self):
headers = self._azure_job.details.metadata
# TODO: when multi circuit jobs are possible, confirm metadata field will be a list of metadatas
if (not isinstance(headers, list)):
headers = [headers]

for header in headers:
del header['qiskit'] # we throw out the qiskit header as it is implied
for key in header.keys():
if isinstance(header[key], str) and header[key].startswith('{') and header[key].endswith('}'):
header[key] = json.loads(header[key])
return headers


def _format_microsoft_v2_results(self) -> List[Dict[str, Any]]:
success = self._azure_job.details.status == "Succeeded"

Expand All @@ -326,10 +340,14 @@ def _format_microsoft_v2_results(self) -> List[Dict[str, Any]]:
entry_point_names = self._get_entry_point_names()

results = self._translate_microsoft_v2_results()


headers = self._get_headers()
if len(results) != len(entry_point_names):
raise ValueError("The number of experiment results does not match the number of experiment names")

if len(results) != len(headers):
raise ValueError("The number of experiment results does not match the number of experiment names")

status = self.status()

return [{
Expand All @@ -338,7 +356,5 @@ def _format_microsoft_v2_results(self) -> List[Dict[str, Any]]:
"shots": total_count,
"name": name,
"status": status,
"header": {
"name": name
}
} for name, (total_count, result) in zip(entry_point_names, results)]
"header": header
} for name, (total_count, result), header in zip(entry_point_names, results, headers)]
43 changes: 24 additions & 19 deletions azure-quantum/azure/quantum/target/ionq.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from azure.quantum.job.job import Job
from azure.quantum.workspace import Workspace
from azure.quantum._client.models import CostEstimate, UsageEvent
from typing import Union

COST_1QUBIT_GATE_MAP = {
"ionq.simulator" : 0.0,
Expand Down Expand Up @@ -127,7 +128,7 @@ def submit(

def estimate_cost(
self,
circuit: Dict[str, Any],
circuit: Union[Dict[str, Any], Any],
num_shots: int = None,
price_1q: float = None,
price_2q: float = None,
Expand Down Expand Up @@ -182,20 +183,6 @@ def estimate_cost(
)
shots = num_shots

def is_1q_gate(gate: Dict[str, Any]):
return "controls" not in gate and "control" not in gate

def is_multi_q_gate(gate):
return "controls" in gate or "control" in gate

def num_2q_gates(gate):
controls = gate.get("controls")
if controls is None or len(controls) == 1:
# Only one control qubit
return 1
# Multiple control qubits
return 6 * (len(controls) - 2)

# Get the costs for the gates depending on the provider if not specified
if price_1q is None:
price_1q = COST_1QUBIT_GATE_MAP[self.name]
Expand All @@ -206,10 +193,28 @@ def num_2q_gates(gate):
if min_price is None:
min_price = MIN_PRICE_MAP[self.name]

gates = circuit.get("circuit", [])
N_1q = sum(map(is_1q_gate, gates))
N_2q = sum(map(num_2q_gates, filter(is_multi_q_gate, gates)))

if (isinstance(circuit, Dict)):
def is_1q_gate(gate: Dict[str, Any]):
return "controls" not in gate and "control" not in gate

def is_multi_q_gate(gate):
return "controls" in gate or "control" in gate

def num_2q_gates(gate):
controls = gate.get("controls")
if controls is None or len(controls) == 1:
# Only one control qubit
return 1
# Multiple control qubits
return 6 * (len(controls) - 2)

gates = circuit.get("circuit", [])
N_1q = sum(map(is_1q_gate, gates))
N_2q = sum(map(num_2q_gates, filter(is_multi_q_gate, gates)))

else:
N_1q, N_2q, _ = self._qir_module_to_gates(circuit)

price = (price_1q * N_1q + price_2q * N_2q) * shots
price = max(price, min_price)

Expand Down
Loading

0 comments on commit c5c8bd1

Please sign in to comment.