Skip to content

Commit

Permalink
Merge branch 'main' into issue_11038_draw_fix
Browse files Browse the repository at this point in the history
  • Loading branch information
SoranaAurelia committed Nov 5, 2023
2 parents 2351394 + 02cb814 commit 72da56a
Show file tree
Hide file tree
Showing 11 changed files with 381 additions and 27 deletions.
4 changes: 2 additions & 2 deletions qiskit/circuit/library/blueprintcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,10 @@ def qasm(self, formatted=False, filename=None, encoding=None):
self._build()
return super().qasm(formatted, filename, encoding)

def append(self, instruction, qargs=None, cargs=None):
def _append(self, instruction, _qargs=None, _cargs=None):
if not self._is_built:
self._build()
return super().append(instruction, qargs, cargs)
return super()._append(instruction, _qargs, _cargs)

def compose(self, other, qubits=None, clbits=None, front=False, inplace=False, wrap=False):
if not self._is_built:
Expand Down
4 changes: 3 additions & 1 deletion qiskit/passmanager/base_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
from collections.abc import Iterable, Callable, Generator
from typing import Any

from .compilation_status import RunState, PassManagerState
from .compilation_status import RunState, PassManagerState, PropertySet

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -62,6 +62,7 @@ class GenericPass(Task, ABC):
"""

def __init__(self):
self.property_set = PropertySet()
self.requires: Iterable[Task] = []

def name(self) -> str:
Expand All @@ -77,6 +78,7 @@ def execute(
# Overriding this method is not safe.
# Pass subclass must keep current implementation.
# Especially, task execution may break when method signature is modified.
self.property_set = state.property_set

if self.requires:
# pylint: disable=cyclic-import
Expand Down
2 changes: 2 additions & 0 deletions qiskit/passmanager/flow_controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ def iter_tasks(self, state: PassManagerState) -> Generator[Task, PassManagerStat
state = yield task
if not self.do_while(state.property_set):
return
# Remove stored tasks from the completed task collection for next loop
state.workflow_status.completed_passes.difference_update(self.tasks)
raise PassManagerError("Maximum iteration reached. max_iteration=%i" % max_iteration)


Expand Down
10 changes: 6 additions & 4 deletions qiskit/passmanager/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

import logging
from abc import ABC, abstractmethod
from collections.abc import Callable, Sequence, Iterable
from collections.abc import Callable, Iterable
from itertools import chain
from typing import Any

Expand Down Expand Up @@ -169,14 +169,16 @@ def _passmanager_backend(

def run(
self,
in_programs: Any,
in_programs: Any | list[Any],
callback: Callable = None,
**kwargs,
) -> Any:
"""Run all the passes on the specified ``circuits``.
"""Run all the passes on the specified ``in_programs``.
Args:
in_programs: Input programs to transform via all the registered passes.
A single input object cannot be a Python builtin list object.
A list object is considered as multiple input objects to optimize.
callback: A callback function that will be called after each pass execution. The
function will be called with 4 keyword arguments::
Expand Down Expand Up @@ -212,7 +214,7 @@ def callback_func(**kwargs):
return in_programs

is_list = True
if not isinstance(in_programs, Sequence):
if not isinstance(in_programs, list):
in_programs = [in_programs]
is_list = False

Expand Down
16 changes: 0 additions & 16 deletions qiskit/transpiler/basepasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ class BasePass(GenericPass, metaclass=MetaPass):
def __init__(self):
super().__init__()
self.preserves: Iterable[GenericPass] = []
self.property_set = PropertySet()
self._hash = hash(None)

def __hash__(self):
Expand Down Expand Up @@ -118,21 +117,6 @@ def is_analysis_pass(self):
"""
return isinstance(self, AnalysisPass)

def execute(
self,
passmanager_ir: PassManagerIR,
state: PassManagerState,
callback: Callable = None,
) -> tuple[PassManagerIR, PassManagerState]:
# For backward compatibility.
# Circuit passes access self.property_set.
self.property_set = state.property_set
return super().execute(
passmanager_ir=passmanager_ir,
state=state,
callback=callback,
)

def __call__(
self,
circuit: QuantumCircuit,
Expand Down
4 changes: 2 additions & 2 deletions qiskit/transpiler/passmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,15 @@ def _passmanager_frontend(
input_program: QuantumCircuit,
**kwargs,
) -> DAGCircuit:
return circuit_to_dag(input_program, copy_operations=False)
return circuit_to_dag(input_program, copy_operations=True)

def _passmanager_backend(
self,
passmanager_ir: DAGCircuit,
in_program: QuantumCircuit,
**kwargs,
) -> QuantumCircuit:
out_program = dag_to_circuit(passmanager_ir)
out_program = dag_to_circuit(passmanager_ir, copy_operations=False)

out_name = kwargs.get("output_name", None)
if out_name is not None:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
fixes:
- |
:class:`.BlueprintCircuit` subclasses will now behave correctly when the semi-public method
:meth:`.QuantumCircuit._append` is used with the blueprint in an unbuilt state, *i.e.* the
circuit will be built before attempting the append.
39 changes: 37 additions & 2 deletions test/python/circuit/library/test_blueprintcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@
from ddt import ddt, data

from qiskit.test.base import QiskitTestCase
from qiskit.circuit import QuantumRegister, Parameter, QuantumCircuit, Gate, Instruction
from qiskit.circuit.library import BlueprintCircuit
from qiskit.circuit import (
QuantumRegister,
Parameter,
QuantumCircuit,
Gate,
Instruction,
CircuitInstruction,
)
from qiskit.circuit.library import BlueprintCircuit, XGate


class MockBlueprint(BlueprintCircuit):
Expand Down Expand Up @@ -139,6 +146,34 @@ def test_to_gate_and_instruction(self, method):
gate = circuit.to_instruction()
self.assertIsInstance(gate, Instruction)

def test_build_before_appends(self):
"""Test that both forms of direct append (public and semi-public) function correctly."""

class DummyBlueprint(BlueprintCircuit):
"""Dummy circuit."""

def _check_configuration(self, raise_on_failure=True):
return True

def _build(self):
super()._build()
self.z(0)

expected = QuantumCircuit(2)
expected.z(0)
expected.x(0)

qr = QuantumRegister(2, "q")
mock = DummyBlueprint()
mock.add_register(qr)
mock.append(XGate(), [qr[0]], [])
self.assertEqual(expected, mock)

mock = DummyBlueprint()
mock.add_register(qr)
mock._append(CircuitInstruction(XGate(), (qr[0],), ()))
self.assertEqual(expected, mock)


if __name__ == "__main__":
unittest.main()
54 changes: 54 additions & 0 deletions test/python/passmanager/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.

"""Pass manager test cases."""

import contextlib
import logging
import re
from itertools import zip_longest
from logging import getLogger

from qiskit.test import QiskitTestCase


class PassManagerTestCase(QiskitTestCase):
"""Test case for the pass manager module."""

@contextlib.contextmanager
def assertLogContains(self, expected_lines):
"""A context manager that capture pass manager log.
Args:
expected_lines (List[str]): Expected logs. Each element can be regular expression.
"""
try:
logger = getLogger()
with self.assertLogs(logger=logger, level=logging.DEBUG) as cm:
yield cm
finally:
recorded_lines = cm.output
for i, (expected, recorded) in enumerate(zip_longest(expected_lines, recorded_lines)):
expected = expected or ""
recorded = recorded or ""
if not re.search(expected, recorded):
raise AssertionError(
f"Log didn't match. Mismatch found at line #{i}.\n\n"
f"Expected:\n{self._format_log(expected_lines)}\n"
f"Recorded:\n{self._format_log(recorded_lines)}"
)

def _format_log(self, lines):
out = ""
for i, line in enumerate(lines):
out += f"#{i:02d}: {line}\n"
return out
143 changes: 143 additions & 0 deletions test/python/passmanager/test_generic_pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2023
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
# pylint: disable=missing-class-docstring

"""Pass manager test cases."""

from test.python.passmanager import PassManagerTestCase

from logging import getLogger

from qiskit.passmanager import GenericPass
from qiskit.passmanager import PassManagerState, WorkflowStatus, PropertySet
from qiskit.passmanager.compilation_status import RunState


class TestGenericPass(PassManagerTestCase):
"""Tests for the GenericPass subclass."""

def setUp(self):
super().setUp()

self.state = PassManagerState(
workflow_status=WorkflowStatus(),
property_set=PropertySet(),
)

def test_run_task(self):
"""Test case: Simple successful task execution."""

class Task(GenericPass):
def run(self, passmanager_ir):
return passmanager_ir

task = Task()
data = "test_data"
expected = [r"Pass: Task - (\d*\.)?\d+ \(ms\)"]

with self.assertLogContains(expected):
task.execute(passmanager_ir=data, state=self.state)
self.assertEqual(self.state.workflow_status.count, 1)
self.assertIn(task, self.state.workflow_status.completed_passes)
self.assertEqual(self.state.workflow_status.previous_run, RunState.SUCCESS)

def test_failure_task(self):
"""Test case: Log is created regardless of success."""

class TestError(Exception):
pass

class RaiseError(GenericPass):
def run(self, passmanager_ir):
raise TestError()

task = RaiseError()
data = "test_data"
expected = [r"Pass: RaiseError - (\d*\.)?\d+ \(ms\)"]

with self.assertLogContains(expected):
with self.assertRaises(TestError):
task.execute(passmanager_ir=data, state=self.state)
self.assertEqual(self.state.workflow_status.count, 0)
self.assertNotIn(task, self.state.workflow_status.completed_passes)
self.assertEqual(self.state.workflow_status.previous_run, RunState.FAIL)

def test_requires(self):
"""Test case: Dependency tasks are run in advance to user provided task."""

class TaskA(GenericPass):
def run(self, passmanager_ir):
return passmanager_ir

class TaskB(GenericPass):
def __init__(self):
super().__init__()
self.requires = [TaskA()]

def run(self, passmanager_ir):
return passmanager_ir

task = TaskB()
data = "test_data"
expected = [
r"Pass: TaskA - (\d*\.)?\d+ \(ms\)",
r"Pass: TaskB - (\d*\.)?\d+ \(ms\)",
]
with self.assertLogContains(expected):
task.execute(passmanager_ir=data, state=self.state)
self.assertEqual(self.state.workflow_status.count, 2)

def test_requires_in_list(self):
"""Test case: Dependency tasks are not executed multiple times."""

class TaskA(GenericPass):
def run(self, passmanager_ir):
return passmanager_ir

class TaskB(GenericPass):
def __init__(self):
super().__init__()
self.requires = [TaskA()]

def run(self, passmanager_ir):
return passmanager_ir

task = TaskB()
data = "test_data"
expected = [
r"Pass: TaskB - (\d*\.)?\d+ \(ms\)",
]
self.state.workflow_status.completed_passes.add(task.requires[0]) # already done
with self.assertLogContains(expected):
task.execute(passmanager_ir=data, state=self.state)
self.assertEqual(self.state.workflow_status.count, 1)

def test_run_with_callable(self):
"""Test case: Callable is called after generic pass is run."""

# pylint: disable=unused-argument
def test_callable(task, passmanager_ir, property_set, running_time, count):
logger = getLogger()
logger.info("%s is running on %s", task.name(), passmanager_ir)

class Task(GenericPass):
def run(self, passmanager_ir):
return passmanager_ir

task = Task()
data = "test_data"
expected = [
r"Pass: Task - (\d*\.)?\d+ \(ms\)",
r"Task is running on test_data",
]
with self.assertLogContains(expected):
task.execute(passmanager_ir=data, state=self.state, callback=test_callable)
Loading

0 comments on commit 72da56a

Please sign in to comment.