Skip to content

Commit

Permalink
Migrate integration tests to test_eagle (Qiskit#1828)
Browse files Browse the repository at this point in the history
* migrate to test_eagle

* linter fixes

* least busty

* updated docstring

* updated test

* updated asserts

* trigger integration-test job

* updated tests

* updated tests

* updated tests

* style fixes

* uncomment

* updated tests

* check production

* remove tests

* fixed backend tests

* restore default settings

* added QISKIT_IBM_DEVICE env

* restore

* fixed @production_only tests

* replace constant device

* fix test

* style fixes

* linter fixes

* manually merge

* update assert

* delete test_options

* fixed pubs

* fix tests

* linter fixes

* remove print

* update status

* linter fixes

* linter

* linter

* update device to qpu

* remove tag

---------

Co-authored-by: Kevin Tian <[email protected]>
  • Loading branch information
ptristan3 and kt474 authored Oct 14, 2024
1 parent f8323dc commit dc03ff3
Show file tree
Hide file tree
Showing 15 changed files with 192 additions and 336 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ jobs:
QISKIT_IBM_TOKEN: ${{ secrets.QISKIT_IBM_TOKEN }}
QISKIT_IBM_URL: ${{ secrets.QISKIT_IBM_URL }}
QISKIT_IBM_INSTANCE: ${{ secrets.QISKIT_IBM_INSTANCE }}
QISKIT_IBM_QPU: ${{ secrets.QISKIT_IBM_QPU }}

LOG_LEVEL: DEBUG
STREAM_LOG: True
QISKIT_IN_PARALLEL: True
Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,13 +206,15 @@ Sample configuration for IBM Quantum
QISKIT_IBM_TOKEN=... # IBM Quantum API token
QISKIT_IBM_URL=https://auth.quantum-computing.ibm.com/api # IBM Quantum API URL
QISKIT_IBM_INSTANCE=ibm-q/open/main # IBM Quantum provider to use (hub/group/project)
QISKIT_IBM_QPU=... # IBM Quantum Processing Unit to use
```

Sample configuration for IBM Cloud
```bash
QISKIT_IBM_TOKEN=... # IBM Cloud API key
QISKIT_IBM_URL=https://cloud.ibm.com # Cloud URL
QISKIT_IBM_INSTANCE=crn:v1:bluemix:... # The CRN value of the Quantum service instance
QISKIT_IBM_QPU=... # The Quantum Processing Unit to use
```


Expand Down
15 changes: 9 additions & 6 deletions test/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,15 @@ def _wrapper(self, *args, **kwargs):


def _get_integration_test_config():
token, url, instance, channel_strategy = (
token, url, instance, qpu, channel_strategy = (
os.getenv("QISKIT_IBM_TOKEN"),
os.getenv("QISKIT_IBM_URL"),
os.getenv("QISKIT_IBM_INSTANCE"),
os.getenv("QISKIT_IBM_QPU"),
os.getenv("CHANNEL_STRATEGY"),
)
channel: Any = "ibm_quantum" if url.find("quantum-computing.ibm.com") >= 0 else "ibm_cloud"
return channel, token, url, instance, channel_strategy
return channel, token, url, instance, qpu, channel_strategy


def run_integration_test(func):
Expand Down Expand Up @@ -116,7 +117,7 @@ def _wrapper(self, *args, **kwargs):
["ibm_cloud", "ibm_quantum"] if supported_channel is None else supported_channel
)

channel, token, url, instance, channel_strategy = _get_integration_test_config()
channel, token, url, instance, qpu, channel_strategy = _get_integration_test_config()
if not all([channel, token, url]):
raise Exception("Configuration Issue") # pylint: disable=broad-exception-raised

Expand All @@ -139,6 +140,7 @@ def _wrapper(self, *args, **kwargs):
token=token,
url=url,
instance=instance,
qpu=qpu,
service=service,
channel_strategy=channel_strategy,
)
Expand All @@ -156,6 +158,7 @@ class IntegrationTestDependencies:

service: QiskitRuntimeService
instance: Optional[str]
qpu: str
token: str
channel: str
url: str
Expand Down Expand Up @@ -193,13 +196,13 @@ def _wrapper(self, *args, **kwargs):
if backend_name:
_backend = service.backend(name=backend_name)
else:
_backend = service.backend(name=self.dependencies.qpu)

if not _backend:
_backend = service.least_busy(
min_num_qubits=min_num_qubits,
simulator=simulator,
)
if not _backend:
# pylint: disable=broad-exception-raised
raise Exception("Unable to find a suitable backend.")

kwargs["backend"] = _backend
func(self, *args, **kwargs)
Expand Down
25 changes: 17 additions & 8 deletions test/ibm_test_case.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from collections import defaultdict
from typing import DefaultDict, Dict

from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager
from qiskit_ibm_runtime import QISKIT_IBM_RUNTIME_LOGGER_NAME
from qiskit_ibm_runtime import QiskitRuntimeService, SamplerV2

Expand Down Expand Up @@ -213,8 +214,14 @@ def tearDownClass(cls) -> None:

@classmethod
def _find_sim_backends(cls):
"""Find a simulator backend for each service."""
cls.sim_backends[cls.service.channel] = cls.service.backends(simulator=True)[0].name
"""Find a simulator or test backend for each service."""
backends = cls.service.backends()
# Simulators or tests backends can be not available
cls.sim_backends[cls.service.channel] = None
for backend in backends:
if backend.simulator or backend.name.startswith("test_"):
cls.sim_backends[cls.service.channel] = backend.name
break

def _run_program(
self,
Expand All @@ -233,31 +240,33 @@ def _run_program(
):
"""Run a program."""
self.log.debug("Running program on %s", service.channel)
pid = program_id or self.program_ids[service.channel]
backend_name = backend if backend is not None else self.sim_backends[service.channel]
backend = service.backend(backend_name)
pm = generate_preset_pass_manager(optimization_level=1, target=backend.target)
inputs = (
inputs
if inputs is not None
else {
"interim_results": interim_results or {},
"circuits": circuits or bell(),
"circuits": pm.run(circuits) if circuits else pm.run(bell()),
}
)
pid = program_id or self.program_ids[service.channel]
backend_name = backend if backend is not None else self.sim_backends[service.channel]

options = {
"backend": backend_name,
"log_level": log_level,
"job_tags": job_tags,
"max_execution_time": max_execution_time,
}
if pid == "sampler":
backend = service.backend(backend_name)
sampler = SamplerV2(mode=backend)
if job_tags:
sampler.options.environment.job_tags = job_tags
if circuits:
job = sampler.run([circuits])
job = sampler.run([pm.run(circuits) if circuits else pm.run(bell())])
else:
job = sampler.run([bell()])
job = sampler.run([pm.run(bell())])
else:
job = service._run(
program_id=pid,
Expand Down
14 changes: 8 additions & 6 deletions test/integration/test_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import copy

from qiskit.transpiler.target import Target
from qiskit import QuantumCircuit
from qiskit import QuantumCircuit, transpile
from qiskit.providers.exceptions import QiskitBackendNotFoundError
from qiskit.providers.backend import QubitProperties
from qiskit_ibm_runtime.exceptions import IBMInputValueError
Expand Down Expand Up @@ -216,7 +216,7 @@ def test_backend_pending_jobs(self):
if self.dependencies.channel == "ibm_cloud":
raise SkipTest("Cloud account does not have real backend.")
backends = self.service.backends()
self.assertTrue(any(backend.status().pending_jobs > 0 for backend in backends))
self.assertTrue(any(backend.status().pending_jobs >= 0 for backend in backends))

def test_backend_fetch_all_qubit_properties(self):
"""Check retrieving properties of all qubits"""
Expand All @@ -233,23 +233,25 @@ def test_backend_fetch_all_qubit_properties(self):

def test_sim_backend_options(self):
"""Test simulator backend options."""
backend = self.service.backend("ibmq_qasm_simulator")
backend = self.backend
backend.options.shots = 2048
backend.set_options(memory=True)
sampler = Sampler(mode=backend)
inputs = sampler.run([bell()], shots=1).inputs
isa_circuit = transpile(bell(), backend)
inputs = sampler.run([isa_circuit], shots=1).inputs
self.assertEqual(inputs["pubs"][0][2], 1)

@production_only
def test_paused_backend_warning(self):
"""Test that a warning is given when running jobs on a paused backend."""
backend = self.service.backend("ibmq_qasm_simulator")
backend = self.backend
paused_status = backend.status()
paused_status.status_msg = "internal"
backend.status = mock.MagicMock(return_value=paused_status)
isa_circuit = transpile(bell(), backend)
with self.assertWarns(Warning):
sampler = Sampler(mode=backend)
sampler.run([bell()])
sampler.run([isa_circuit])

def test_backend_wrong_instance(self):
"""Test that an error is raised when retrieving a backend not in the instance."""
Expand Down
32 changes: 15 additions & 17 deletions test/integration/test_estimator_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,12 @@ class TestEstimatorV2(IBMIntegrationTestCase):

def setUp(self) -> None:
super().setUp()
self.backend = "ibmq_qasm_simulator"
self._backend = self.service.backend(self.dependencies.qpu)

@run_integration_test
def test_estimator_v2_session(self, service):
"""Verify correct results are returned"""
backend = service.backend(self.backend)
pass_mgr = generate_preset_pass_manager(backend=backend, optimization_level=1)
pass_mgr = generate_preset_pass_manager(backend=self._backend, optimization_level=1)

psi1 = pass_mgr.run(RealAmplitudes(num_qubits=2, reps=2))
psi2 = pass_mgr.run(RealAmplitudes(num_qubits=2, reps=3))
Expand All @@ -51,7 +50,7 @@ def test_estimator_v2_session(self, service):
theta2 = [0, 1, 1, 2, 3, 5, 8, 13]
theta3 = [1, 2, 3, 4, 5, 6]

with Session(service, self.backend) as session:
with Session(service, self.dependencies.qpu) as session:
estimator = EstimatorV2(mode=session)

job = estimator.run([(psi1, H1, [theta1])])
Expand All @@ -66,15 +65,14 @@ def test_estimator_v2_session(self, service):
result3 = job3.result()
self._verify_result_type(result3, num_pubs=3, shapes=[(), (), ()])

@run_integration_test
def test_estimator_v2_options(self, service):
def test_estimator_v2_options(self):
"""Test V2 Estimator with different options."""
backend = service.backend(self.backend)
pass_mgr = generate_preset_pass_manager(backend=backend, optimization_level=1)
pass_mgr = generate_preset_pass_manager(backend=self._backend, optimization_level=1)

circuit = pass_mgr.run(IQP([[6, 5, 3], [5, 4, 5], [3, 5, 1]]))
observables = SparsePauliOp("X" * circuit.num_qubits).apply_layout(circuit.layout)
observable = SparsePauliOp("X" * circuit.num_qubits)

estimator = EstimatorV2(mode=backend)
estimator = EstimatorV2(mode=self._backend)
estimator.options.default_precision = 0.05
estimator.options.default_shots = 400
estimator.options.resilience_level = 1
Expand All @@ -98,26 +96,26 @@ def test_estimator_v2_options(self, service):
estimator.options.twirling.strategy = "active"
estimator.options.twirling.num_randomizations = 16
estimator.options.twirling.shots_per_randomization = 100
estimator.options.environment = {"job_tags": [".".join(self.id().split(".")[-2:])]}

job = estimator.run([(circuit, observables)])
job = estimator.run([(circuit, observable)])
result = job.result()
self._verify_result_type(result, num_pubs=1, shapes=[()])
self.assertEqual(result[0].metadata["shots"], 1600)

@skip("Skip until simulator options are accepted by server.")
@run_integration_test
def test_pec(self, service):
def test_pec(self):
"""Test running with PEC."""
backend = service.backend(self.backend)
pass_mgr = generate_preset_pass_manager(backend=backend, optimization_level=1)
pass_mgr = generate_preset_pass_manager(backend=self._backend, optimization_level=1)
circuit = pass_mgr.run(IQP([[6, 5, 3], [5, 4, 5], [3, 5, 1]]))
observables = SparsePauliOp("X" * circuit.num_qubits).apply_layout(circuit.layout)
observables = SparsePauliOp("X" * circuit.num_qubits)

estimator = EstimatorV2(mode=backend)
estimator = EstimatorV2(mode=self._backend)
estimator.options.resilience_level = 0
estimator.options.resilience.pec_mitigation = True
estimator.options.resilience.pec_max_overhead = 200
estimator.options.simulator.set_backend(FakeAuckland())
estimator.options.environment = {"job_tags": [".".join(self.id().split(".")[-2:])]}

job = estimator.run([(circuit, observables)])
result = job.result()
Expand Down
34 changes: 19 additions & 15 deletions test/integration/test_ibm_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister
from qiskit.compiler import transpile
from qiskit.providers.jobstatus import JobStatus
from qiskit.transpiler.preset_passmanagers import generate_preset_pass_manager

from qiskit_ibm_runtime import SamplerV2 as Sampler
from qiskit_ibm_runtime.exceptions import RuntimeJobTimeoutError, RuntimeJobNotFound
Expand All @@ -34,10 +35,13 @@ class TestIBMJob(IBMIntegrationTestCase):
def setUp(self):
"""Initial test setup."""
super().setUp()
self.sim_backend = self.service.backend("ibmq_qasm_simulator")
self.sim_backend = self.service.backend(self.dependencies.qpu)
self.bell = bell()
sampler = Sampler(mode=self.sim_backend)
self.sim_job = sampler.run([self.bell])

pass_mgr = generate_preset_pass_manager(backend=self.sim_backend, optimization_level=1)
self.isa_circuit = pass_mgr.run(self.bell)
self.sim_job = sampler.run([self.isa_circuit])
self.last_month = datetime.now() - timedelta(days=30)

def test_run_multiple_simulator(self):
Expand All @@ -52,7 +56,8 @@ def test_run_multiple_simulator(self):
num_jobs = 4
sampler = Sampler(mode=self.sim_backend)
job_array = [
sampler.run(transpile([quantum_circuit] * 20), shots=2048) for _ in range(num_jobs)
sampler.run([transpile(quantum_circuit, backend=self.sim_backend)] * 20, shots=2048)
for _ in range(num_jobs)
]
timeout = 30
start_time = time.time()
Expand Down Expand Up @@ -193,35 +198,34 @@ def test_retrieve_jobs_created_before(self):
limit=2,
created_before=past_month,
)
self.assertTrue(job_list)
self.assertIsInstance(job_list, list)
for job in job_list:
self.assertLessEqual(
job.creation_date,
past_month_tz_aware,
"job {} creation date {} not within range".format(job.job_id(), job.creation_date),
)

def test_retrieve_jobs_between_datetimes(self):
"""Test retrieving jobs created between two specified datetimes."""
def test_retrieve_jobs_between_datetime(self):
"""Test retrieving jobs created between two specified datetime."""
date_today = datetime.now()
past_month = date_today - timedelta(30)
past_two_month = date_today - timedelta(60)
past_one_month = date_today - timedelta(30)

# Add local tz in order to compare to `creation_date` which is tz aware.
past_month_tz_aware = past_month.replace(tzinfo=tz.tzlocal())
past_two_month_tz_aware = past_two_month.replace(tzinfo=tz.tzlocal())
today_tz_aware = date_today.replace(tzinfo=tz.tzlocal())
past_one_month_tz_aware = past_one_month.replace(tzinfo=tz.tzlocal())

with self.subTest():
job_list = self.service.jobs(
backend_name=self.sim_backend.name,
limit=2,
created_after=past_two_month,
created_before=past_month,
created_after=past_one_month,
created_before=date_today,
)
self.assertTrue(job_list)
for job in job_list:
self.assertTrue(
(past_two_month_tz_aware <= job.creation_date <= past_month_tz_aware),
(past_one_month_tz_aware <= job.creation_date <= today_tz_aware),
"job {} creation date {} not within range".format(
job.job_id(), job.creation_date
),
Expand All @@ -230,7 +234,7 @@ def test_retrieve_jobs_between_datetimes(self):
def test_retrieve_jobs_order(self):
"""Test retrieving jobs with different orders."""
sampler = Sampler(mode=self.sim_backend)
job = sampler.run([self.bell])
job = sampler.run([self.isa_circuit])
job.wait_for_final_state()
newest_jobs = self.service.jobs(
limit=20,
Expand Down Expand Up @@ -284,4 +288,4 @@ def test_wait_for_final_state_timeout(self):

def test_job_circuits(self):
"""Test job circuits."""
self.assertEqual(str(self.bell), str(self.sim_job.inputs["pubs"][0][0]))
self.assertEqual(self.isa_circuit, self.sim_job.inputs["pubs"][0][0])
2 changes: 1 addition & 1 deletion test/integration/test_ibm_job_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def setUpClass(cls, dependencies: IntegrationTestDependencies) -> None:
super().setUpClass()
cls.dependencies = dependencies
cls.service = dependencies.service
cls.sim_backend = dependencies.service.backend("ibmq_qasm_simulator")
cls.sim_backend = dependencies.service.backend(dependencies.qpu)
cls.bell = transpile(bell(), cls.sim_backend)
sampler = Sampler(mode=cls.sim_backend)
cls.sim_job = sampler.run([cls.bell])
Expand Down
Loading

0 comments on commit dc03ff3

Please sign in to comment.