From fce2cd639ab39dec836e4f8d1cf19c46138c5c07 Mon Sep 17 00:00:00 2001 From: skushagra Date: Thu, 4 Jun 2026 16:48:38 +0530 Subject: [PATCH 1/5] feat: count circuit operation on circuit or given qubits --- src/braket/circuits/__init__.py | 1 + src/braket/circuits/circuit.py | 28 +++ src/braket/circuits/circuit_analysis.py | 94 ++++++++++ .../braket/circuits/test_circuit_analysis.py | 175 ++++++++++++++++++ 4 files changed, 298 insertions(+) create mode 100644 src/braket/circuits/circuit_analysis.py create mode 100644 test/unit_tests/braket/circuits/test_circuit_analysis.py diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index a2930ed79..9e0631591 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -29,6 +29,7 @@ DoubleAngledGate, # noqa: F401 ) from braket.circuits.circuit import Circuit # noqa: F401 +from braket.circuits.circuit_analysis import QubitMatch # noqa: F401 from braket.circuits.circuit_diagram import CircuitDiagram # noqa: F401 from braket.circuits.compiler_directive import CompilerDirective # noqa: F401 from braket.circuits.free_parameter import FreeParameter # noqa: F401 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index b656763c3..2f7d631d5 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -243,6 +243,34 @@ def parameters(self) -> set[FreeParameter]: """ return self._parameters + def count_instructions(self, operators=None, **kwargs) -> Counter[str]: + """ + Count instructions in the circuit with optional filtering. + + Args: + operators: Filter by operator name or type. + qubits: Filter by qubit. Accepts a single int or an iterable of ints. + qubit_match (QubitMatch): ANY = instruction touches any specified qubit; ALL = all. Default ANY. + include_types (Iterable[MomentType]): Moment types to count. Default: GATE only. + + Returns: + Counter[str]: Operator names mapped to occurrence counts. + + Examples: + >>> circ = Circuit().h(0).cnot(0, 1).rx(0, 0.5) + >>> circ.count_instructions() + Counter({'H': 1, 'CNot': 1, 'Rx': 1}) + >>> circ.count_instructions("h") + Counter({'H': 1}) + >>> circ.count_instructions(["H", "CNot"]) + Counter({'H': 1, 'CNot': 1}) + >>> circ.count_instructions(qubits=0) + Counter({'H': 1, 'CNot': 1, 'Rx': 1}) + """ + from braket.circuits.circuit_analysis import count_instructions as _count_instructions + + return _count_instructions(self, operators, **kwargs) + def with_euler_angles(self, observables: Sequence[Observable] | Sum) -> Circuit: """Returns a copy of the circuit with parametrized Euler angles on the observables' qubits diff --git a/src/braket/circuits/circuit_analysis.py b/src/braket/circuits/circuit_analysis.py new file mode 100644 index 000000000..5a221fd46 --- /dev/null +++ b/src/braket/circuits/circuit_analysis.py @@ -0,0 +1,94 @@ +from __future__ import annotations + +from collections import Counter +from collections.abc import Iterable +from enum import StrEnum +from typing import TYPE_CHECKING, Union + +from braket.circuits.moments import MomentType +from braket.circuits.operator import Operator +from braket.registers.qubit import QubitInput +from braket.registers.qubit_set import QubitSet + +if TYPE_CHECKING: + from braket.circuits.circuit import Circuit + + +class QubitMatch(StrEnum): + """Controls how multiple qubits are matched in count_instructions.""" + + ANY = "ANY" + ALL = "ALL" + + +OperatorIdentifier = Union[str, type[Operator], Operator] + + +def _normalize_operator_name(identifier: OperatorIdentifier) -> str: + if isinstance(identifier, type): + return identifier.__name__.upper() + if isinstance(identifier, str): + return identifier.upper() + return identifier.name.upper() + + +def _to_operator_names( + operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None, +) -> list[str]: + if operators is None: + return [] + if isinstance(operators, (str, type)) or isinstance(operators, Operator): + return [_normalize_operator_name(operators)] + return [_normalize_operator_name(op) for op in operators] + + +def count_instructions( + circuit: Circuit, + operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None = None, + qubits: QubitInput | Iterable[QubitInput] | None = None, + qubit_match: QubitMatch = QubitMatch.ANY, + include_types: Iterable[MomentType] = (MomentType.GATE,), +) -> Counter[str]: + """ + Count instructions in a circuit with optional filtering. + + When both ``operators`` and ``qubits`` are specified, an instruction must satisfy + both filters to be counted (AND semantics). + + Args: + circuit (Circuit): The circuit to analyse. + operators: Filter by operator name or type. Defaults to None (no filter). + qubits: Filter by qubit. Matched against the union of target and control qubits. + qubit_match (QubitMatch): How multiple qubits relate. ANY = instruction on + any specified qubit; ALL = instruction on all specified qubits. Default ANY. + include_types (Iterable[MomentType]): Moment types to count. Default: GATE only. + Pass additional MomentType values to include noise, measures, etc. + + Returns: + Counter[str]: Operator names mapped to occurrence counts. + """ + include_types_set = set(include_types) + operator_names_set = set(_to_operator_names(operators)) + _qs = QubitSet(qubits) if qubits is not None else None + filter_qubits = _qs if _qs else None # empty QubitSet treated as no filter + + result: Counter[str] = Counter() + + for key, instruction in circuit.moments.items(): + if key.moment_type not in include_types_set: + continue + + instr_qubits = instruction.target.union(instruction.control) + instr_name_upper = instruction.operator.name.upper() + + qubit_pass = filter_qubits is None or ( + any(q in instr_qubits for q in filter_qubits) + if qubit_match == QubitMatch.ANY + else all(q in instr_qubits for q in filter_qubits) + ) + operator_pass = not operator_names_set or instr_name_upper in operator_names_set + + if qubit_pass and operator_pass: + result[instruction.operator.name] += 1 + + return result diff --git a/test/unit_tests/braket/circuits/test_circuit_analysis.py b/test/unit_tests/braket/circuits/test_circuit_analysis.py new file mode 100644 index 000000000..5c2ece560 --- /dev/null +++ b/test/unit_tests/braket/circuits/test_circuit_analysis.py @@ -0,0 +1,175 @@ +from collections import Counter + +import pytest + +from braket.circuits import Circuit, gates +from braket.circuits.circuit_analysis import QubitMatch, count_instructions +from braket.circuits.moments import MomentType +from braket.circuits.noises import BitFlip + + +@pytest.fixture +def mixed_circuit(): + return Circuit().h(0).cnot(0, 1).rx(0, 0.5).h(1) + + +def test_qubit_match_values(): + assert QubitMatch.ANY == "ANY" + assert QubitMatch.ALL == "ALL" + + +def test_qubit_match_accepts_string_literal(): + assert "ANY" == QubitMatch.ANY + assert "ALL" == QubitMatch.ALL + + +def test_no_filters_returns_all_gates(mixed_circuit): + result = count_instructions(mixed_circuit) + assert result == Counter({"H": 2, "CNot": 1, "Rx": 1}) + + +def test_no_filters_empty_circuit(): + result = count_instructions(Circuit()) + assert result == Counter() + + +def test_default_include_types_excludes_gate_noise(): + circ = Circuit().h(0) + circ.apply_gate_noise(BitFlip(0.1)) + result = count_instructions(circ) + assert result == Counter({"H": 1}) + assert "BitFlip" not in result + + +def test_include_gate_noise_type(): + circ = Circuit().h(0) + circ.apply_gate_noise(BitFlip(0.1)) + result = count_instructions(circ, include_types=[MomentType.GATE, MomentType.GATE_NOISE]) + assert result["H"] == 1 + assert result["BitFlip"] == 1 + + +def test_operator_filter_uppercase_string(mixed_circuit): + result = count_instructions(mixed_circuit, operators="H") + assert result == Counter({"H": 2}) + + +def test_operator_filter_lowercase_string(mixed_circuit): + result = count_instructions(mixed_circuit, operators="h") + assert result == Counter({"H": 2}) + + +def test_operator_filter_mixed_case_string(mixed_circuit): + result = count_instructions(mixed_circuit, operators="cNoT") + assert result == Counter({"CNot": 1}) + + +def test_operator_filter_gate_class(mixed_circuit): + result = count_instructions(mixed_circuit, operators=gates.CNot) + assert result == Counter({"CNot": 1}) + + +def test_operator_filter_gate_instance(mixed_circuit): + result = count_instructions(mixed_circuit, operators=gates.CNot()) + assert result == Counter({"CNot": 1}) + + +def test_operator_filter_multiple_or(mixed_circuit): + result = count_instructions(mixed_circuit, operators=["H", "CNot"]) + assert result == Counter({"H": 2, "CNot": 1}) + + +def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): + result = count_instructions(mixed_circuit, operators=["h", gates.CNot]) + assert result == Counter({"H": 2, "CNot": 1}) + + +def test_operator_filter_unknown_name_returns_empty(mixed_circuit): + result = count_instructions(mixed_circuit, operators="ZZZ") + assert result == Counter() + + +def test_operators_empty_list_is_no_filter(mixed_circuit): + assert count_instructions(mixed_circuit, operators=[]) == count_instructions(mixed_circuit) + + +def test_qubits_empty_list_is_no_filter(mixed_circuit): + assert count_instructions(mixed_circuit, qubits=[]) == count_instructions(mixed_circuit) + + +def test_operator_empty_list_and_qubits_empty_list_is_no_filter(mixed_circuit): + assert count_instructions(mixed_circuit, operators=[], qubits=[]) == count_instructions( + mixed_circuit + ) + + +def test_qubit_filter_single_qubit(): + circ = Circuit().h(0).cnot(0, 1).rx(1, 0.5) + result = count_instructions(circ, qubits=0) + assert result == Counter({"H": 1, "CNot": 1}) + + +def test_qubit_filter_multiple_qubits_any(): + circ = Circuit().h(0).h(2).cnot(0, 1) + result = count_instructions(circ, qubits=[0, 2]) + assert result == Counter({"H": 2, "CNot": 1}) + + +def test_qubit_filter_multiple_qubits_all(): + circ = Circuit().h(0).h(1).cnot(0, 1) + result = count_instructions(circ, qubits=[0, 1], qubit_match=QubitMatch.ALL) + assert result == Counter({"CNot": 1}) + + +def test_qubit_filter_no_match(): + circ = Circuit().h(0).h(1) + result = count_instructions(circ, qubits=5) + assert result == Counter() + + +def test_both_filters_counts_intersection(): + circ = Circuit().h(0).rx(1, 0.5).cnot(0, 1) + result = count_instructions(circ, operators="CNot", qubits=[1]) + assert result == Counter({"CNot": 1}) + + +def test_both_filters_no_overlap(): + circ = Circuit().h(0).rx(1, 0.5) + result = count_instructions(circ, operators="H", qubits=[1]) + assert result == Counter() + + +def test_only_qubit_filter_no_operator_filter(): + circ = Circuit().h(0).cnot(0, 1) + result = count_instructions(circ, qubits=[0]) + assert result == Counter({"H": 1, "CNot": 1}) + + +def test_only_operator_filter_no_qubit_filter(): + circ = Circuit().h(0).h(1) + result = count_instructions(circ, operators="H") + assert result == Counter({"H": 2}) + + +def test_circuit_method_matches_standalone(mixed_circuit): + assert mixed_circuit.count_instructions() == count_instructions(mixed_circuit) + + +def test_circuit_method_positional_single_operator(mixed_circuit): + assert mixed_circuit.count_instructions("H") == Counter({"H": 2}) + + +def test_circuit_method_positional_list_of_operators(mixed_circuit): + assert mixed_circuit.count_instructions(["H", "CNot"]) == Counter({"H": 2, "CNot": 1}) + + +def test_circuit_method_passes_kwargs(mixed_circuit): + expected = count_instructions(mixed_circuit, operators="H", qubits=[0]) + actual = mixed_circuit.count_instructions(operators="H", qubits=[0]) + assert expected == actual + + +def test_qubit_match_importable_from_braket_circuits(): + from braket.circuits import QubitMatch as QM + + assert QM.ANY == "ANY" From 4d42c6788e2a0806b228a1dd18b78c2b1a1eb39f Mon Sep 17 00:00:00 2001 From: skushagra Date: Tue, 16 Jun 2026 23:37:40 +0530 Subject: [PATCH 2/5] fix: review comment updates: moved circuit_analysis logic to cirucit class --- src/braket/circuits/__init__.py | 3 +- src/braket/circuits/circuit.py | 85 ++++++++++++++--- src/braket/circuits/circuit_analysis.py | 94 ------------------- .../braket/circuits/test_circuit_analysis.py | 82 +++++++++------- 4 files changed, 123 insertions(+), 141 deletions(-) delete mode 100644 src/braket/circuits/circuit_analysis.py diff --git a/src/braket/circuits/__init__.py b/src/braket/circuits/__init__.py index 9e0631591..5f5bdde21 100644 --- a/src/braket/circuits/__init__.py +++ b/src/braket/circuits/__init__.py @@ -28,8 +28,7 @@ AngledGate, # noqa: F401 DoubleAngledGate, # noqa: F401 ) -from braket.circuits.circuit import Circuit # noqa: F401 -from braket.circuits.circuit_analysis import QubitMatch # noqa: F401 +from braket.circuits.circuit import Circuit, QubitMatch # noqa: F401 from braket.circuits.circuit_diagram import CircuitDiagram # noqa: F401 from braket.circuits.compiler_directive import CompilerDirective # noqa: F401 from braket.circuits.free_parameter import FreeParameter # noqa: F401 diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index 2f7d631d5..d08aa5efe 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -16,8 +16,9 @@ import warnings from collections import Counter from collections.abc import Callable, Iterable, Sequence +from enum import StrEnum from numbers import Number -from typing import Any, TypeVar +from typing import Any, TypeVar, Union import numpy as np import oqpy @@ -45,6 +46,7 @@ ) from braket.circuits.observable import Observable, euler_angle_parameter_names from braket.circuits.observables import Sum, TensorProduct +from braket.circuits.operator import Operator from braket.circuits.parameterizable import Parameterizable from braket.circuits.result_type import ( ObservableParameterResultType, @@ -73,6 +75,16 @@ AddableTypes = TypeVar("AddableTypes", SubroutineReturn, SubroutineCallable) +class QubitMatch(StrEnum): + """Controls how multiple qubits are matched in count.""" + + ANY = "ANY" + ALL = "ALL" + + +OperatorIdentifier = Union[str, type[Operator], Operator] + + class Circuit: """A representation of a quantum circuit that contains the instructions to be performed on a quantum device and the requested result types. @@ -243,33 +255,84 @@ def parameters(self) -> set[FreeParameter]: """ return self._parameters - def count_instructions(self, operators=None, **kwargs) -> Counter[str]: + @staticmethod + def _normalize_operator_name(identifier: OperatorIdentifier) -> str: + if isinstance(identifier, type): + return identifier.__name__.upper() + if isinstance(identifier, str): + return identifier.upper() + return identifier.name.upper() + + @staticmethod + def _to_operator_names( + operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None, + ) -> list[str]: + if operators is None: + return [] + if isinstance(operators, (str, type)) or isinstance(operators, Operator): + return [Circuit._normalize_operator_name(operators)] + return [Circuit._normalize_operator_name(op) for op in operators] + + def count( + self, + operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None = None, + qubits: QubitInput | Iterable[QubitInput] | None = None, + qubit_match: QubitMatch = QubitMatch.ANY, + include_types: Iterable[MomentType] = (MomentType.GATE,), + ) -> Counter[str]: """ Count instructions in the circuit with optional filtering. + When both ``operators`` and ``qubits`` are specified, an instruction must satisfy + both filters to be counted (AND semantics). + Args: - operators: Filter by operator name or type. - qubits: Filter by qubit. Accepts a single int or an iterable of ints. - qubit_match (QubitMatch): ANY = instruction touches any specified qubit; ALL = all. Default ANY. + operators: Filter by operator name or type. Defaults to None (no filter). + qubits: Filter by qubit. Matched against the union of target and control qubits. + qubit_match (QubitMatch): How multiple qubits relate. ANY = instruction on + any specified qubit; ALL = instruction on all specified qubits. Default ANY. include_types (Iterable[MomentType]): Moment types to count. Default: GATE only. + Pass additional MomentType values to include noise, measures, etc. Returns: Counter[str]: Operator names mapped to occurrence counts. Examples: >>> circ = Circuit().h(0).cnot(0, 1).rx(0, 0.5) - >>> circ.count_instructions() + >>> circ.count() Counter({'H': 1, 'CNot': 1, 'Rx': 1}) - >>> circ.count_instructions("h") + >>> circ.count("h") Counter({'H': 1}) - >>> circ.count_instructions(["H", "CNot"]) + >>> circ.count(["H", "CNot"]) Counter({'H': 1, 'CNot': 1}) - >>> circ.count_instructions(qubits=0) + >>> circ.count(qubits=0) Counter({'H': 1, 'CNot': 1, 'Rx': 1}) """ - from braket.circuits.circuit_analysis import count_instructions as _count_instructions + include_types_set = set(include_types) + operator_names_set = set(self._to_operator_names(operators)) + _qs = QubitSet(qubits) if qubits is not None else None + filter_qubits = _qs if _qs else None # empty QubitSet treated as no filter + + result: Counter[str] = Counter() + + for key, instruction in self.moments.items(): + if key.moment_type not in include_types_set: + continue + + instr_qubits = instruction.target.union(instruction.control) + instr_name_upper = instruction.operator.name.upper() + + qubit_pass = filter_qubits is None or ( + any(q in instr_qubits for q in filter_qubits) + if qubit_match == QubitMatch.ANY + else all(q in instr_qubits for q in filter_qubits) + ) + operator_pass = not operator_names_set or instr_name_upper in operator_names_set + + if qubit_pass and operator_pass: + result[instruction.operator.name] += 1 - return _count_instructions(self, operators, **kwargs) + return result def with_euler_angles(self, observables: Sequence[Observable] | Sum) -> Circuit: """Returns a copy of the circuit with parametrized Euler angles on the observables' qubits diff --git a/src/braket/circuits/circuit_analysis.py b/src/braket/circuits/circuit_analysis.py deleted file mode 100644 index 5a221fd46..000000000 --- a/src/braket/circuits/circuit_analysis.py +++ /dev/null @@ -1,94 +0,0 @@ -from __future__ import annotations - -from collections import Counter -from collections.abc import Iterable -from enum import StrEnum -from typing import TYPE_CHECKING, Union - -from braket.circuits.moments import MomentType -from braket.circuits.operator import Operator -from braket.registers.qubit import QubitInput -from braket.registers.qubit_set import QubitSet - -if TYPE_CHECKING: - from braket.circuits.circuit import Circuit - - -class QubitMatch(StrEnum): - """Controls how multiple qubits are matched in count_instructions.""" - - ANY = "ANY" - ALL = "ALL" - - -OperatorIdentifier = Union[str, type[Operator], Operator] - - -def _normalize_operator_name(identifier: OperatorIdentifier) -> str: - if isinstance(identifier, type): - return identifier.__name__.upper() - if isinstance(identifier, str): - return identifier.upper() - return identifier.name.upper() - - -def _to_operator_names( - operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None, -) -> list[str]: - if operators is None: - return [] - if isinstance(operators, (str, type)) or isinstance(operators, Operator): - return [_normalize_operator_name(operators)] - return [_normalize_operator_name(op) for op in operators] - - -def count_instructions( - circuit: Circuit, - operators: OperatorIdentifier | Iterable[OperatorIdentifier] | None = None, - qubits: QubitInput | Iterable[QubitInput] | None = None, - qubit_match: QubitMatch = QubitMatch.ANY, - include_types: Iterable[MomentType] = (MomentType.GATE,), -) -> Counter[str]: - """ - Count instructions in a circuit with optional filtering. - - When both ``operators`` and ``qubits`` are specified, an instruction must satisfy - both filters to be counted (AND semantics). - - Args: - circuit (Circuit): The circuit to analyse. - operators: Filter by operator name or type. Defaults to None (no filter). - qubits: Filter by qubit. Matched against the union of target and control qubits. - qubit_match (QubitMatch): How multiple qubits relate. ANY = instruction on - any specified qubit; ALL = instruction on all specified qubits. Default ANY. - include_types (Iterable[MomentType]): Moment types to count. Default: GATE only. - Pass additional MomentType values to include noise, measures, etc. - - Returns: - Counter[str]: Operator names mapped to occurrence counts. - """ - include_types_set = set(include_types) - operator_names_set = set(_to_operator_names(operators)) - _qs = QubitSet(qubits) if qubits is not None else None - filter_qubits = _qs if _qs else None # empty QubitSet treated as no filter - - result: Counter[str] = Counter() - - for key, instruction in circuit.moments.items(): - if key.moment_type not in include_types_set: - continue - - instr_qubits = instruction.target.union(instruction.control) - instr_name_upper = instruction.operator.name.upper() - - qubit_pass = filter_qubits is None or ( - any(q in instr_qubits for q in filter_qubits) - if qubit_match == QubitMatch.ANY - else all(q in instr_qubits for q in filter_qubits) - ) - operator_pass = not operator_names_set or instr_name_upper in operator_names_set - - if qubit_pass and operator_pass: - result[instruction.operator.name] += 1 - - return result diff --git a/test/unit_tests/braket/circuits/test_circuit_analysis.py b/test/unit_tests/braket/circuits/test_circuit_analysis.py index 5c2ece560..f2e3afd9e 100644 --- a/test/unit_tests/braket/circuits/test_circuit_analysis.py +++ b/test/unit_tests/braket/circuits/test_circuit_analysis.py @@ -3,7 +3,7 @@ import pytest from braket.circuits import Circuit, gates -from braket.circuits.circuit_analysis import QubitMatch, count_instructions +from braket.circuits.circuit import QubitMatch from braket.circuits.moments import MomentType from braket.circuits.noises import BitFlip @@ -24,19 +24,19 @@ def test_qubit_match_accepts_string_literal(): def test_no_filters_returns_all_gates(mixed_circuit): - result = count_instructions(mixed_circuit) + result = mixed_circuit.count() assert result == Counter({"H": 2, "CNot": 1, "Rx": 1}) def test_no_filters_empty_circuit(): - result = count_instructions(Circuit()) + result = Circuit().count() assert result == Counter() def test_default_include_types_excludes_gate_noise(): circ = Circuit().h(0) circ.apply_gate_noise(BitFlip(0.1)) - result = count_instructions(circ) + result = circ.count() assert result == Counter({"H": 1}) assert "BitFlip" not in result @@ -44,128 +44,142 @@ def test_default_include_types_excludes_gate_noise(): def test_include_gate_noise_type(): circ = Circuit().h(0) circ.apply_gate_noise(BitFlip(0.1)) - result = count_instructions(circ, include_types=[MomentType.GATE, MomentType.GATE_NOISE]) + result = circ.count(include_types=[MomentType.GATE, MomentType.GATE_NOISE]) assert result["H"] == 1 assert result["BitFlip"] == 1 def test_operator_filter_uppercase_string(mixed_circuit): - result = count_instructions(mixed_circuit, operators="H") + result = mixed_circuit.count(operators="H") assert result == Counter({"H": 2}) def test_operator_filter_lowercase_string(mixed_circuit): - result = count_instructions(mixed_circuit, operators="h") + result = mixed_circuit.count(operators="h") assert result == Counter({"H": 2}) def test_operator_filter_mixed_case_string(mixed_circuit): - result = count_instructions(mixed_circuit, operators="cNoT") + result = mixed_circuit.count(operators="cNoT") assert result == Counter({"CNot": 1}) def test_operator_filter_gate_class(mixed_circuit): - result = count_instructions(mixed_circuit, operators=gates.CNot) + result = mixed_circuit.count(operators=gates.CNot) assert result == Counter({"CNot": 1}) def test_operator_filter_gate_instance(mixed_circuit): - result = count_instructions(mixed_circuit, operators=gates.CNot()) + result = mixed_circuit.count(operators=gates.CNot()) assert result == Counter({"CNot": 1}) def test_operator_filter_multiple_or(mixed_circuit): - result = count_instructions(mixed_circuit, operators=["H", "CNot"]) + result = mixed_circuit.count(operators=["H", "CNot"]) assert result == Counter({"H": 2, "CNot": 1}) def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): - result = count_instructions(mixed_circuit, operators=["h", gates.CNot]) + result = mixed_circuit.count(operators=["h", gates.CNot]) assert result == Counter({"H": 2, "CNot": 1}) def test_operator_filter_unknown_name_returns_empty(mixed_circuit): - result = count_instructions(mixed_circuit, operators="ZZZ") + result = mixed_circuit.count(operators="ZZZ") assert result == Counter() def test_operators_empty_list_is_no_filter(mixed_circuit): - assert count_instructions(mixed_circuit, operators=[]) == count_instructions(mixed_circuit) + assert mixed_circuit.count(operators=[]) == mixed_circuit.count() def test_qubits_empty_list_is_no_filter(mixed_circuit): - assert count_instructions(mixed_circuit, qubits=[]) == count_instructions(mixed_circuit) + assert mixed_circuit.count(qubits=[]) == mixed_circuit.count() def test_operator_empty_list_and_qubits_empty_list_is_no_filter(mixed_circuit): - assert count_instructions(mixed_circuit, operators=[], qubits=[]) == count_instructions( - mixed_circuit - ) + assert mixed_circuit.count( + operators=[], qubits=[] + ) == mixed_circuit.count() def test_qubit_filter_single_qubit(): circ = Circuit().h(0).cnot(0, 1).rx(1, 0.5) - result = count_instructions(circ, qubits=0) + result = circ.count(qubits=0) assert result == Counter({"H": 1, "CNot": 1}) def test_qubit_filter_multiple_qubits_any(): circ = Circuit().h(0).h(2).cnot(0, 1) - result = count_instructions(circ, qubits=[0, 2]) + result = circ.count(qubits=[0, 2]) assert result == Counter({"H": 2, "CNot": 1}) def test_qubit_filter_multiple_qubits_all(): circ = Circuit().h(0).h(1).cnot(0, 1) - result = count_instructions(circ, qubits=[0, 1], qubit_match=QubitMatch.ALL) + result = circ.count(qubits=[0, 1], qubit_match=QubitMatch.ALL) assert result == Counter({"CNot": 1}) def test_qubit_filter_no_match(): circ = Circuit().h(0).h(1) - result = count_instructions(circ, qubits=5) + result = circ.count(qubits=5) + assert result == Counter() + + +def test_qubit_filter_qubit_not_in_circuit(): + circ = Circuit().h(0).cnot(0, 1) + result = circ.count(qubits=99) + assert result == Counter() + + +def test_qubit_filter_partial_qubits_not_in_circuit_any(): + circ = Circuit().h(0).cnot(0, 1) + result = circ.count(qubits=[0, 99]) + assert result == Counter({"H": 1, "CNot": 1}) + + +def test_qubit_filter_partial_qubits_not_in_circuit_all(): + circ = Circuit().h(0).cnot(0, 1) + result = circ.count(qubits=[0, 99], qubit_match=QubitMatch.ALL) assert result == Counter() def test_both_filters_counts_intersection(): circ = Circuit().h(0).rx(1, 0.5).cnot(0, 1) - result = count_instructions(circ, operators="CNot", qubits=[1]) + result = circ.count(operators="CNot", qubits=[1]) assert result == Counter({"CNot": 1}) def test_both_filters_no_overlap(): circ = Circuit().h(0).rx(1, 0.5) - result = count_instructions(circ, operators="H", qubits=[1]) + result = circ.count(operators="H", qubits=[1]) assert result == Counter() def test_only_qubit_filter_no_operator_filter(): circ = Circuit().h(0).cnot(0, 1) - result = count_instructions(circ, qubits=[0]) + result = circ.count(qubits=[0]) assert result == Counter({"H": 1, "CNot": 1}) def test_only_operator_filter_no_qubit_filter(): circ = Circuit().h(0).h(1) - result = count_instructions(circ, operators="H") + result = circ.count(operators="H") assert result == Counter({"H": 2}) -def test_circuit_method_matches_standalone(mixed_circuit): - assert mixed_circuit.count_instructions() == count_instructions(mixed_circuit) - - def test_circuit_method_positional_single_operator(mixed_circuit): - assert mixed_circuit.count_instructions("H") == Counter({"H": 2}) + assert mixed_circuit.count("H") == Counter({"H": 2}) def test_circuit_method_positional_list_of_operators(mixed_circuit): - assert mixed_circuit.count_instructions(["H", "CNot"]) == Counter({"H": 2, "CNot": 1}) + assert mixed_circuit.count(["H", "CNot"]) == Counter({"H": 2, "CNot": 1}) def test_circuit_method_passes_kwargs(mixed_circuit): - expected = count_instructions(mixed_circuit, operators="H", qubits=[0]) - actual = mixed_circuit.count_instructions(operators="H", qubits=[0]) + expected = mixed_circuit.count(operators="H", qubits=[0]) + actual = mixed_circuit.count(operators="H", qubits=[0]) assert expected == actual From d21202cd3d3d37da65ae81c1eebfdbaeadeffca3 Mon Sep 17 00:00:00 2001 From: skushagra Date: Fri, 19 Jun 2026 00:29:50 +0530 Subject: [PATCH 3/5] fix: updated tests to remove duplicate and unnecessary test methods --- .../braket/circuits/test_circuit_analysis.py | 177 ++---------------- 1 file changed, 19 insertions(+), 158 deletions(-) diff --git a/test/unit_tests/braket/circuits/test_circuit_analysis.py b/test/unit_tests/braket/circuits/test_circuit_analysis.py index f2e3afd9e..e75d9c8e9 100644 --- a/test/unit_tests/braket/circuits/test_circuit_analysis.py +++ b/test/unit_tests/braket/circuits/test_circuit_analysis.py @@ -12,178 +12,39 @@ def mixed_circuit(): return Circuit().h(0).cnot(0, 1).rx(0, 0.5).h(1) - -def test_qubit_match_values(): - assert QubitMatch.ANY == "ANY" - assert QubitMatch.ALL == "ALL" - - -def test_qubit_match_accepts_string_literal(): - assert "ANY" == QubitMatch.ANY - assert "ALL" == QubitMatch.ALL - - def test_no_filters_returns_all_gates(mixed_circuit): - result = mixed_circuit.count() - assert result == Counter({"H": 2, "CNot": 1, "Rx": 1}) - - -def test_no_filters_empty_circuit(): - result = Circuit().count() - assert result == Counter() - - -def test_default_include_types_excludes_gate_noise(): - circ = Circuit().h(0) - circ.apply_gate_noise(BitFlip(0.1)) - result = circ.count() - assert result == Counter({"H": 1}) - assert "BitFlip" not in result + assert mixed_circuit.count() == Counter({"H": 2, "CNot": 1, "Rx": 1}) +def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): + assert mixed_circuit.count(operators=["h", gates.CNot]) == Counter({"H": 2, "CNot": 1}) def test_include_gate_noise_type(): circ = Circuit().h(0) circ.apply_gate_noise(BitFlip(0.1)) result = circ.count(include_types=[MomentType.GATE, MomentType.GATE_NOISE]) - assert result["H"] == 1 - assert result["BitFlip"] == 1 - - -def test_operator_filter_uppercase_string(mixed_circuit): - result = mixed_circuit.count(operators="H") - assert result == Counter({"H": 2}) - - -def test_operator_filter_lowercase_string(mixed_circuit): - result = mixed_circuit.count(operators="h") - assert result == Counter({"H": 2}) - - -def test_operator_filter_mixed_case_string(mixed_circuit): - result = mixed_circuit.count(operators="cNoT") - assert result == Counter({"CNot": 1}) - - -def test_operator_filter_gate_class(mixed_circuit): - result = mixed_circuit.count(operators=gates.CNot) - assert result == Counter({"CNot": 1}) - - -def test_operator_filter_gate_instance(mixed_circuit): - result = mixed_circuit.count(operators=gates.CNot()) - assert result == Counter({"CNot": 1}) - - -def test_operator_filter_multiple_or(mixed_circuit): - result = mixed_circuit.count(operators=["H", "CNot"]) - assert result == Counter({"H": 2, "CNot": 1}) - - -def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): - result = mixed_circuit.count(operators=["h", gates.CNot]) - assert result == Counter({"H": 2, "CNot": 1}) - - -def test_operator_filter_unknown_name_returns_empty(mixed_circuit): - result = mixed_circuit.count(operators="ZZZ") - assert result == Counter() - - -def test_operators_empty_list_is_no_filter(mixed_circuit): - assert mixed_circuit.count(operators=[]) == mixed_circuit.count() - - -def test_qubits_empty_list_is_no_filter(mixed_circuit): - assert mixed_circuit.count(qubits=[]) == mixed_circuit.count() - - -def test_operator_empty_list_and_qubits_empty_list_is_no_filter(mixed_circuit): - assert mixed_circuit.count( - operators=[], qubits=[] - ) == mixed_circuit.count() - - -def test_qubit_filter_single_qubit(): - circ = Circuit().h(0).cnot(0, 1).rx(1, 0.5) - result = circ.count(qubits=0) - assert result == Counter({"H": 1, "CNot": 1}) + assert result == Counter({"H": 1, "BitFlip": 1}) +def test_qubit_filter_single_qubit(mixed_circuit): + assert mixed_circuit.count(qubits=0) == Counter({"H": 1, "CNot": 1, "Rx": 1}) def test_qubit_filter_multiple_qubits_any(): circ = Circuit().h(0).h(2).cnot(0, 1) - result = circ.count(qubits=[0, 2]) - assert result == Counter({"H": 2, "CNot": 1}) - - -def test_qubit_filter_multiple_qubits_all(): - circ = Circuit().h(0).h(1).cnot(0, 1) - result = circ.count(qubits=[0, 1], qubit_match=QubitMatch.ALL) - assert result == Counter({"CNot": 1}) - - -def test_qubit_filter_no_match(): - circ = Circuit().h(0).h(1) - result = circ.count(qubits=5) - assert result == Counter() - - -def test_qubit_filter_qubit_not_in_circuit(): - circ = Circuit().h(0).cnot(0, 1) - result = circ.count(qubits=99) - assert result == Counter() - - -def test_qubit_filter_partial_qubits_not_in_circuit_any(): - circ = Circuit().h(0).cnot(0, 1) - result = circ.count(qubits=[0, 99]) - assert result == Counter({"H": 1, "CNot": 1}) - - -def test_qubit_filter_partial_qubits_not_in_circuit_all(): - circ = Circuit().h(0).cnot(0, 1) - result = circ.count(qubits=[0, 99], qubit_match=QubitMatch.ALL) - assert result == Counter() - - -def test_both_filters_counts_intersection(): - circ = Circuit().h(0).rx(1, 0.5).cnot(0, 1) - result = circ.count(operators="CNot", qubits=[1]) - assert result == Counter({"CNot": 1}) - - -def test_both_filters_no_overlap(): - circ = Circuit().h(0).rx(1, 0.5) - result = circ.count(operators="H", qubits=[1]) - assert result == Counter() - - -def test_only_qubit_filter_no_operator_filter(): - circ = Circuit().h(0).cnot(0, 1) - result = circ.count(qubits=[0]) - assert result == Counter({"H": 1, "CNot": 1}) - - -def test_only_operator_filter_no_qubit_filter(): - circ = Circuit().h(0).h(1) - result = circ.count(operators="H") - assert result == Counter({"H": 2}) - - -def test_circuit_method_positional_single_operator(mixed_circuit): - assert mixed_circuit.count("H") == Counter({"H": 2}) - + assert circ.count(qubits=[0, 2]) == Counter({"H": 2, "CNot": 1}) -def test_circuit_method_positional_list_of_operators(mixed_circuit): - assert mixed_circuit.count(["H", "CNot"]) == Counter({"H": 2, "CNot": 1}) +def test_qubit_filter_multiple_qubits_all(mixed_circuit): + assert mixed_circuit.count(qubits=[0, 1], qubit_match=QubitMatch.ALL) == Counter({"CNot": 1}) +def test_operator_and_qubit_filters_intersect(mixed_circuit): + assert mixed_circuit.count(operators="CNot", qubits=[1]) == Counter({"CNot": 1}) -def test_circuit_method_passes_kwargs(mixed_circuit): - expected = mixed_circuit.count(operators="H", qubits=[0]) - actual = mixed_circuit.count(operators="H", qubits=[0]) - assert expected == actual +def test_unknown_operator_returns_empty(mixed_circuit): + assert mixed_circuit.count(operators="ZZZ") == Counter() +def test_qubit_not_in_circuit_returns_empty(mixed_circuit): + assert mixed_circuit.count(qubits=99) == Counter() -def test_qubit_match_importable_from_braket_circuits(): - from braket.circuits import QubitMatch as QM +def test_partial_qubits_not_in_circuit_any(mixed_circuit): + assert mixed_circuit.count(qubits=[0, 99]) == Counter({"H": 1, "CNot": 1, "Rx": 1}) - assert QM.ANY == "ANY" +def test_partial_qubits_not_in_circuit_all(mixed_circuit): + assert mixed_circuit.count(qubits=[0, 99], qubit_match=QubitMatch.ALL) == Counter() From 576007308f303bb983627f51b886dbed0303faba Mon Sep 17 00:00:00 2001 From: skushagra Date: Fri, 19 Jun 2026 01:34:48 +0530 Subject: [PATCH 4/5] fix: linter and formatter errors --- src/braket/circuits/circuit.py | 10 +++++----- .../braket/circuits/test_circuit_analysis.py | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/braket/circuits/circuit.py b/src/braket/circuits/circuit.py index d08aa5efe..b4ed94323 100644 --- a/src/braket/circuits/circuit.py +++ b/src/braket/circuits/circuit.py @@ -18,7 +18,7 @@ from collections.abc import Callable, Iterable, Sequence from enum import StrEnum from numbers import Number -from typing import Any, TypeVar, Union +from typing import Any, TypeVar import numpy as np import oqpy @@ -82,7 +82,7 @@ class QubitMatch(StrEnum): ALL = "ALL" -OperatorIdentifier = Union[str, type[Operator], Operator] +OperatorIdentifier = str | type[Operator] | Operator class Circuit: @@ -269,7 +269,7 @@ def _to_operator_names( ) -> list[str]: if operators is None: return [] - if isinstance(operators, (str, type)) or isinstance(operators, Operator): + if isinstance(operators, (str, type, Operator)): return [Circuit._normalize_operator_name(operators)] return [Circuit._normalize_operator_name(op) for op in operators] @@ -310,8 +310,8 @@ def count( """ include_types_set = set(include_types) operator_names_set = set(self._to_operator_names(operators)) - _qs = QubitSet(qubits) if qubits is not None else None - filter_qubits = _qs if _qs else None # empty QubitSet treated as no filter + qs = QubitSet(qubits) if qubits is not None else None + filter_qubits = qs or None # empty QubitSet treated as no filter result: Counter[str] = Counter() diff --git a/test/unit_tests/braket/circuits/test_circuit_analysis.py b/test/unit_tests/braket/circuits/test_circuit_analysis.py index e75d9c8e9..b6605f811 100644 --- a/test/unit_tests/braket/circuits/test_circuit_analysis.py +++ b/test/unit_tests/braket/circuits/test_circuit_analysis.py @@ -12,39 +12,50 @@ def mixed_circuit(): return Circuit().h(0).cnot(0, 1).rx(0, 0.5).h(1) + def test_no_filters_returns_all_gates(mixed_circuit): assert mixed_circuit.count() == Counter({"H": 2, "CNot": 1, "Rx": 1}) + def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): assert mixed_circuit.count(operators=["h", gates.CNot]) == Counter({"H": 2, "CNot": 1}) + def test_include_gate_noise_type(): circ = Circuit().h(0) circ.apply_gate_noise(BitFlip(0.1)) result = circ.count(include_types=[MomentType.GATE, MomentType.GATE_NOISE]) assert result == Counter({"H": 1, "BitFlip": 1}) + def test_qubit_filter_single_qubit(mixed_circuit): assert mixed_circuit.count(qubits=0) == Counter({"H": 1, "CNot": 1, "Rx": 1}) + def test_qubit_filter_multiple_qubits_any(): circ = Circuit().h(0).h(2).cnot(0, 1) assert circ.count(qubits=[0, 2]) == Counter({"H": 2, "CNot": 1}) + def test_qubit_filter_multiple_qubits_all(mixed_circuit): assert mixed_circuit.count(qubits=[0, 1], qubit_match=QubitMatch.ALL) == Counter({"CNot": 1}) + def test_operator_and_qubit_filters_intersect(mixed_circuit): assert mixed_circuit.count(operators="CNot", qubits=[1]) == Counter({"CNot": 1}) + def test_unknown_operator_returns_empty(mixed_circuit): assert mixed_circuit.count(operators="ZZZ") == Counter() + def test_qubit_not_in_circuit_returns_empty(mixed_circuit): assert mixed_circuit.count(qubits=99) == Counter() + def test_partial_qubits_not_in_circuit_any(mixed_circuit): assert mixed_circuit.count(qubits=[0, 99]) == Counter({"H": 1, "CNot": 1, "Rx": 1}) + def test_partial_qubits_not_in_circuit_all(mixed_circuit): assert mixed_circuit.count(qubits=[0, 99], qubit_match=QubitMatch.ALL) == Counter() From 4d5d9c875f5015e245a29745e662895cffa5dd38 Mon Sep 17 00:00:00 2001 From: skushagra Date: Fri, 19 Jun 2026 01:59:33 +0530 Subject: [PATCH 5/5] fix: add tests for code coverage --- .../braket/circuits/test_circuit_analysis.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/unit_tests/braket/circuits/test_circuit_analysis.py b/test/unit_tests/braket/circuits/test_circuit_analysis.py index b6605f811..6d025df17 100644 --- a/test/unit_tests/braket/circuits/test_circuit_analysis.py +++ b/test/unit_tests/braket/circuits/test_circuit_analysis.py @@ -21,6 +21,10 @@ def test_operator_filter_multiple_mixed_identifiers(mixed_circuit): assert mixed_circuit.count(operators=["h", gates.CNot]) == Counter({"H": 2, "CNot": 1}) +def test_operator_filter_gate_instance(mixed_circuit): + assert mixed_circuit.count(operators=gates.CNot()) == Counter({"CNot": 1}) + + def test_include_gate_noise_type(): circ = Circuit().h(0) circ.apply_gate_noise(BitFlip(0.1)) @@ -28,6 +32,12 @@ def test_include_gate_noise_type(): assert result == Counter({"H": 1, "BitFlip": 1}) +def test_gate_noise_excluded_by_default(): + circ = Circuit().h(0) + circ.apply_gate_noise(BitFlip(0.1)) + assert circ.count() == Counter({"H": 1}) + + def test_qubit_filter_single_qubit(mixed_circuit): assert mixed_circuit.count(qubits=0) == Counter({"H": 1, "CNot": 1, "Rx": 1})