#
# Copyright © 2022-2024 University of Strasbourg. All Rights Reserved.
# Copyright © 2023-2025 QPerfect. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
"""Utilities for generating operation lists and trees."""
import inspect
from collections import defaultdict
from mimiqcircuits import *
class InheritanceTree:
"""Class to generate and display an inheritance tree for classes that inherit from a base class.
>>> from mimiqcircuits import *
>>> import mimiqcircuits as mc
>>> inheritance_tree = InheritanceTree(Operation)
>>> inheritance_tree.extract_classes(mc)
>>> inheritance_tree.extract_gate_functions(mc)
>>> inheritance_tree.print_tree()
:class:`Operation`
:class:`AbstractAnnotation`
:class:`Detector`
:class:`ObservableInclude`
:class:`QubitCoordinates`
:class:`ShiftCoordinates`
:class:`Tick`
:class:`AbstractClassical`
:class:`And`
:class:`Not`
:class:`Or`
:class:`ParityCheck`
:class:`SetBit0`
:class:`SetBit1`
:class:`Xor`
:class:`AbstractMeasurement`
:class:`Measure`
:class:`MeasureReset`
:class:`MeasureResetX`
:class:`MeasureResetY`
:class:`MeasureResetZ`
:class:`MeasureX`
:class:`MeasureXX`
:class:`MeasureY`
:class:`MeasureYY`
:class:`MeasureZ`
:class:`MeasureZZ`
:class:`AbstractOperator`
:class:`DiagonalOp`
:class:`Gate`
:class:`Control`
:class:`GateC3X`
:class:`GateCCX`
:class:`GateCH`
:class:`GateCP`
:class:`GateCRX`
:class:`GateCRY`
:class:`GateCRZ`
:class:`GateCS`
:class:`GateCSDG`
:class:`GateCSWAP`
:class:`GateCSX`
:class:`GateCSXDG`
:class:`GateCU`
:class:`GateCX`
:class:`GateCY`
:class:`GateCZ`
:class:`Delay`
:class:`Diffusion`
:class:`GateCall`
:class:`GateCustom`
:class:`GateDCX`
:class:`GateECR`
:class:`GateH`
:class:`GateHXY`
:class:`GateHXZ`
:class:`GateHYZ`
:class:`GateID`
:class:`GateISWAP`
:class:`GateP`
:class:`GateR`
:class:`GateRNZ`
:class:`GateRX`
:class:`GateRXX`
:class:`GateRY`
:class:`GateRYY`
:class:`GateRZ`
:class:`GateRZX`
:class:`GateRZZ`
:class:`GateSWAP`
:class:`GateU`
:class:`GateU1`
:class:`GateU2`
:class:`GateU3`
:class:`GateX`
:class:`GateXXminusYY`
:class:`GateXXplusYY`
:class:`GateY`
:class:`GateZ`
:class:`Inverse`
:class:`GateISWAPDG`
:class:`GateSDG`
:class:`GateSXDG`
:class:`GateSYDG`
:class:`GateTDG`
:class:`Parallel`
:class:`PauliString`
:class:`PhaseGradient`
:class:`PolynomialOracle`
:class:`Power`
:class:`GateS`
:class:`GateSX`
:class:`GateSY`
:class:`GateT`
:class:`QFT`
:class:`RPauli`
:class:`Operator`
:class:`Projector0`
:class:`Projector00`
:class:`Projector01`
:class:`Projector1`
:class:`Projector10`
:class:`Projector11`
:class:`ProjectorX0`
:class:`ProjectorX1`
:class:`ProjectorY0`
:class:`ProjectorY1`
:class:`ProjectorZ0`
:class:`ProjectorZ1`
:class:`RescaledGate`
:class:`SigmaMinus`
:class:`SigmaPlus`
:class:`Add`
:class:`Amplitude`
:class:`Barrier`
:class:`Block`
:class:`BondDim`
:class:`ExpectationValue`
:class:`IfStatement`
:class:`Multiply`
:class:`Pow`
:class:`ReadoutErr`
:class:`Repeat`
:class:`SchmidtRank`
:class:`VonNeumannEntropy`
:class:`krauschannel`
:class:`AmplitudeDamping`
:class:`Depolarizing`
:class:`Depolarizing1`
:class:`Depolarizing2`
:class:`GeneralizedAmplitudeDamping`
:class:`Kraus`
:class:`MixedUnitary`
:class:`PauliNoise`
:class:`PauliX`
:class:`PauliY`
:class:`PauliZ`
:class:`PhaseAmplitudeDamping`
:class:`ProjectiveNoise`
:class:`ProjectiveNoiseX`
:class:`ProjectiveNoiseY`
:class:`ProjectiveNoiseZ`
:class:`Reset`
:class:`ResetX`
:class:`ResetY`
:class:`ResetZ`
:class:`ThermalNoise`
<BLANKLINE>
"""
def __init__(self, base_class):
"""Initialize with the base class (e.g., Operation)."""
self.base_class = base_class
self.class_tree = defaultdict(list)
self.function_registry = defaultdict(list) # <-- added
def extract_classes(self, module):
"""Extract all classes from the module that inherit from the base class."""
classes = inspect.getmembers(module, inspect.isclass)
for name, cls in classes:
if issubclass(cls, self.base_class) and cls != self.base_class:
parent = cls.__bases__[0].__name__
self.class_tree[parent].append(name)
def extract_gate_functions(self, module):
"""Automatically extract gate-like functions that return subclasses of the base class."""
functions = inspect.getmembers(module, inspect.isfunction)
for name, func in functions:
try:
if name.startswith("_"):
continue
sig = inspect.signature(func)
if any(
param.default == inspect.Parameter.empty
and param.kind
in (
inspect.Parameter.POSITIONAL_ONLY,
inspect.Parameter.POSITIONAL_OR_KEYWORD,
)
for param in sig.parameters.values()
):
continue
result = func()
if isinstance(result, self.base_class):
parent_name = type(result).__name__
self.function_registry[parent_name].append(name)
except Exception:
continue
def generate_tree_output(self, class_name=None, level=0):
"""Recursively generate the class hierarchy tree as a string."""
if class_name is None:
class_name = self.base_class.__name__
result = " " * level + f":class:`{class_name}`\n"
for func in sorted(self.function_registry.get(class_name, [])): # <-- added
result += " " * (level + 1) + f":func:`{func}`\n" # <-- added
for child_class in sorted(self.class_tree.get(class_name, [])):
result += self.generate_tree_output(child_class, level + 1)
return result
def print_tree(self):
"""Print the generated tree."""
print(self.generate_tree_output())
def generate_ascii_tree(self, class_name=None, prefix="", is_last=True):
"""
Generate a clean ASCII tree showing class/function hierarchy.
"""
if class_name is None:
class_name = self.base_class.__name__
branch = "└── " if is_last else "├── "
line = f"{prefix}{branch}{class_name}" if prefix else class_name
lines = [line]
# Combine class children and function children
class_children = sorted(self.class_tree.get(class_name, []))
func_children = sorted(self.function_registry.get(class_name, []))
all_children = class_children + func_children
for idx, child in enumerate(all_children):
child_is_last = idx == len(all_children) - 1
next_prefix = prefix + (" " if is_last else "│ ")
if child in self.class_tree or child in self.function_registry:
# Recursive call for subclasses
lines.append(
self.generate_ascii_tree(child, next_prefix, child_is_last)
)
else:
# Function node
branch_func = "└── " if child_is_last else "├── "
lines.append(f"{next_prefix}{branch_func}{child}")
return "\n".join(lines)
def attach_inheritance_tree_to_docstring(
base_class,
module,
target_object=None,
title="Hierarchy of Operations",
start_from=None,
):
tree = InheritanceTree(base_class)
tree.extract_classes(module)
tree.extract_gate_functions(module)
tree_str = tree.generate_ascii_tree(class_name=start_from)
target = target_object if target_object is not None else base_class
if target.__doc__ is None:
target.__doc__ = ""
target.__doc__ += f"\n\n{title}:\n{tree_str}"
[docs]
class OPERATIONS:
"""All supported quantum operations. Use `help(OPERATIONS)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError(
"Use `help(OPERATIONS)` to view available operations."
)
class ANNOTATIONS:
"""Annotation-related operations. Use `help(ANNOTATIONS)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError(
"Use `help(ANNOTATIONS)` to view available annotations."
)
[docs]
class GATES:
"""Gate operations. Use `help(GATES)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError("Use `help(GATES)` to view available gates.")
[docs]
class KRAUSCHANNELS:
"""Kraus channel operations. Use `help(KRAUSCHANNELS)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError(
"Use `help(KRAUSCHANNELS)` to view available channels."
)
[docs]
class MEASUREMENTS:
"""Measurement operations. Use `help(MEASUREMENTS)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError(
"Use `help(MEASUREMENTS)` to view available measurements."
)
[docs]
class SIMPLEGATES:
"""Simple gates. Use `help(SIMPLEGATES)` to view the full list."""
def __new__(cls):
raise NotImplementedError(
"Use `help(SIMPLEGATES)` to view available simple gates."
)
class CLASSICALOPERATIONS:
"""Classical operations. Use `help(CLASSICALOPERATIONS)` to view the hierarchy."""
def __new__(cls):
raise NotImplementedError(
"Use `help(CLASSICALOPERATIONS)` to view available classical operations."
)
@staticmethod
def list():
return [
GateCustom,
GateU,
GateID,
GateX,
GateY,
GateZ,
GateH,
GateHXY,
GateHXZ,
GateHYZ,
GateS,
GateSDG,
GateT,
GateTDG,
GateSX,
GateSXDG,
GateSY,
GateSYDG,
GateRX,
GateRY,
GateRZ,
GateR,
GateU1,
GateU2,
GateU3,
GateCX,
GateCY,
GateCZ,
GateCH,
GateSWAP,
GateISWAP,
GateCS,
GateCSDG,
GateCSX,
GateCSXDG,
GateECR,
GateDCX,
GateCP,
GateCU,
GateCRX,
GateCRY,
GateCRZ,
GateRXX,
GateRYY,
GateRZZ,
GateRZX,
GateXXplusYY,
GateXXminusYY,
GateCCX,
GateC3X,
GateCCP,
GateCSWAP,
GateP,
]
SIMPLEGATES_GROUPS = {
"Single qubit gates": [
GateX,
GateY,
GateZ,
GateH,
GateS,
GateSDG,
GateT,
GateTDG,
GateSX,
GateSXDG,
GateSY,
GateSYDG,
GateID,
],
"Single qubit gates (parametric)": [
GateU,
GateP,
GateRX,
GateRY,
GateRZ,
GateR,
GateU1,
GateU2,
GateU3,
],
"Two qubit gates": [
GateCX,
GateCY,
GateCZ,
GateCH,
GateSWAP,
GateISWAP,
GateCS,
GateCSDG,
GateCSX,
GateCSXDG,
GateECR,
GateDCX,
],
"Two qubit gates (parametric)": [
GateCU,
GateCP,
GateCRX,
GateCRY,
GateCRZ,
GateRXX,
GateRYY,
GateRZZ,
GateRZX,
GateXXplusYY,
GateXXminusYY,
],
"Multi-qubit gates (special)": [
GateCCX,
GateC3X,
GateCCP,
GateCSWAP,
GateHXY,
GateHXZ,
GateHYZ,
GateCustom,
],
}
def generate_ascii_gate_tree_grouped(groups):
lines = ["Available quantum gates:\n"]
for section, gates in groups.items():
lines.append(f"*{section}*")
gate_names = sorted(g.__name__ for g in gates)
for i, name in enumerate(gate_names):
branch = "└── " if i == len(gate_names) - 1 else "├── "
lines.append(branch + name)
lines.append("") # empty line between groups
return "\n".join(lines)
# RST-safe fenced code block for Sphinx docstrings
def _rst_fenced(text: str) -> str:
"""Wrap text in a RST fenced code block for Sphinx docstrings."""
indented = "\n".join(" " + line for line in text.splitlines())
return f"\n.. code-block::\n\n{indented}\n"
def attach_inheritance_tree_to_docstring(
base, module, target_object, title, start_from
):
"""
Attach an ASCII inheritance tree to a class or module docstring,
wrapped in a proper Sphinx RST code-block so formatting is preserved.
"""
tree = InheritanceTree(base)
tree.extract_classes(module)
tree.extract_gate_functions(module)
ascii_tree = tree.generate_ascii_tree(class_name=start_from)
block = _rst_fenced(ascii_tree)
existing = target_object.__doc__ or ""
target_object.__doc__ = existing + f"\n\n{title}:\n" + block
__all__ = ["OPERATIONS", "GATES", "KRAUSCHANNELS", "MEASUREMENTS", "SIMPLEGATES"]