From 08fd05b008e60c7908dbe14d00276ce35abb39cc Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 26 Jan 2026 00:03:44 +0100 Subject: [PATCH 1/5] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Auto-generate=20stub?= =?UTF-8?q?=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 3 +- .license-tools-config.json | 3 +- bindings/bindings.cpp | 37 ++-- bindings/ddsim_patterns.txt | 3 + noxfile.py | 49 +++++ python/mqt/ddsim/pyddsim.pyi | 345 +++++++++++++++++++++++------------ 6 files changed, 307 insertions(+), 133 deletions(-) create mode 100644 bindings/ddsim_patterns.txt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9aa2d462..a1c187336 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -194,8 +194,9 @@ jobs: if: fromJSON(needs.change-detection.outputs.run-python-tests) uses: munich-quantum-toolkit/workflows/.github/workflows/reusable-python-linter.yml@d6314c45667c131055a0389afc110e8dedc6da3f # v1.17.11 with: - enable-ty: true + check-stubs: true enable-mypy: false + enable-ty: true build-sdist: name: 🚀 CD diff --git a/.license-tools-config.json b/.license-tools-config.json index 3c95e3966..d147613be 100644 --- a/.license-tools-config.json +++ b/.license-tools-config.json @@ -36,6 +36,7 @@ ".*\\.profile", "uv\\.lock", "py\\.typed", - ".*build.*" + ".*build.*", + "ddsim_patterns.txt" ] } diff --git a/bindings/bindings.cpp b/bindings/bindings.cpp index b9a408842..ad0422359 100644 --- a/bindings/bindings.cpp +++ b/bindings/bindings.cpp @@ -25,6 +25,7 @@ #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) +#include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include // NOLINT(misc-include-cleaner) #include @@ -73,7 +74,6 @@ nb::class_ createSimulator(nb::module_ m, const std::string& name) { // NOLINTNEXTLINE(performance-unnecessary-value-param) NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { nb::module_::import_("mqt.core.dd"); - m.doc() = "Python interface for the MQT DDSIM quantum circuit simulator"; // Circuit Simulator auto circuitSimulator = @@ -166,7 +166,9 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { "amp_damping_probability"_a = 0.02, "multi_qubit_gate_factor"_a = 2); // Hybrid Schrödinger-Feynman Simulator - nb::enum_(m, "HybridSimulatorMode") + nb::enum_( + m, "HybridSimulatorMode", + R"pb(Enumeration of modes for the :class:`~HybridSimulator`.)pb") .value("DD", HybridSchrodingerFeynmanSimulator::Mode::DD) .value("amplitude", HybridSchrodingerFeynmanSimulator::Mode::Amplitude); @@ -204,7 +206,9 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { &HybridSchrodingerFeynmanSimulator::getVectorFromHybridSimulation); // Path Simulator - nb::enum_(m, "PathSimulatorMode") + nb::enum_( + m, "PathSimulatorMode", + "Enumeration of modes for the :class:`~PathSimulator`.") .value("sequential", PathSimulator::Configuration::Mode::Sequential) .value("pairwise_recursive", PathSimulator::Configuration::Mode::PairwiseRecursiveGrouping) @@ -214,21 +218,26 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { nb::class_( m, "PathSimulatorConfiguration", - "Configuration options for the :class:`~.PathSimulator`.") + R"pb(Configuration options for the :class:`~.PathSimulator`.)pb") .def(nb::init()) - .def_rw( - "mode", &PathSimulator::Configuration::mode, - R"pbdoc(Setting the mode used for determining a simulation path)pbdoc") + .def_rw("mode", &PathSimulator::Configuration::mode, + R"pb(The mode used for determining a simulation path.)pb") .def_rw("bracket_size", &PathSimulator::Configuration::bracketSize, - R"pbdoc(Size of the brackets one wants to combine)pbdoc") + R"pb(Size of the brackets one wants to combine.)pb") .def_rw("starting_point", &PathSimulator::Configuration::startingPoint, - R"pbdoc(Start of the alternating or gate_cost strategy)pbdoc") + R"pb(Start of the alternating or gate_cost strategy.)pb") .def_rw( "gate_cost", &PathSimulator::Configuration::gateCost, - R"pbdoc(A list that contains the number of gates which are considered in each step)pbdoc") + R"pb(A list that contains the number of gates which are considered in each step.)pb") .def_rw("seed", &PathSimulator::Configuration::seed, - R"pbdoc(Seed for the simulator)pbdoc") - .def("json", &PathSimulator::Configuration::json) + R"pb(Seed for the simulator.)pb") + .def("json", + [](const PathSimulator::Configuration& config) { + const auto json = nb::module_::import_("json"); + const auto loads = json.attr("loads"); + const auto dict = loads(config.json().dump()); + return nb::cast>(dict); + }) .def("__repr__", &PathSimulator::Configuration::toString); auto pathSimulator = createSimulator(m, "PathSimulator"); @@ -260,7 +269,9 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { "path"_a, "assume_correct_order"_a = false); // Unitary Simulator - nb::enum_(m, "UnitarySimulatorMode") + nb::enum_( + m, "UnitarySimulatorMode", + R"pb(Enumeration of modes for the :class:`~UnitarySimulator`.)pb") .value("recursive", UnitarySimulator::Mode::Recursive) .value("sequential", UnitarySimulator::Mode::Sequential); diff --git a/bindings/ddsim_patterns.txt b/bindings/ddsim_patterns.txt new file mode 100644 index 000000000..55bcfafb2 --- /dev/null +++ b/bindings/ddsim_patterns.txt @@ -0,0 +1,3 @@ +_hashable_values_: + +_unhashable_values_map_: diff --git a/noxfile.py b/noxfile.py index 1115a8a2e..e0cc5a8c0 100755 --- a/noxfile.py +++ b/noxfile.py @@ -204,5 +204,54 @@ def docs(session: nox.Session) -> None: ) +@nox.session(reuse_venv=True, venv_backend="uv") +def stubs(session: nox.Session) -> None: + """Generate type stubs for Python bindings using nanobind.""" + env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} + session.run( + "uv", + "sync", + "--no-dev", + "--group", + "build", + env=env, + ) + + package_root = Path(__file__).parent / "python" / "mqt" / "ddsim" + pattern_file = Path(__file__).parent / "bindings" / "ddsim_patterns.txt" + + session.run( + "python", + "-m", + "nanobind.stubgen", + "--recursive", + "--include-private", + "--output-dir", + str(package_root), + "--pattern-file", + str(pattern_file), + "--module", + "mqt.ddsim.pyddsim", + ) + + pyi_files = list(package_root.glob("**/*.pyi")) + + if not pyi_files: + session.warn("No .pyi files found") + return + + if shutil.which("prek") is None: + session.install("prek") + + # Allow both 0 (no issues) and 1 as success codes for fixing up stubs + success_codes = [0, 1] + session.run("prek", "run", "license-tools", "--files", *pyi_files, external=True, success_codes=success_codes) + session.run("prek", "run", "ruff-check", "--files", *pyi_files, external=True, success_codes=success_codes) + session.run("prek", "run", "ruff-format", "--files", *pyi_files, external=True, success_codes=success_codes) + + # Run ruff-check again to ensure everything is clean + session.run("prek", "run", "ruff-check", "--files", *pyi_files, external=True) + + if __name__ == "__main__": nox.main() diff --git a/python/mqt/ddsim/pyddsim.pyi b/python/mqt/ddsim/pyddsim.pyi index 7d940d360..3ecfa8acf 100644 --- a/python/mqt/ddsim/pyddsim.pyi +++ b/python/mqt/ddsim/pyddsim.pyi @@ -6,101 +6,142 @@ # # Licensed under the MIT License -from enum import Enum +import enum +from collections.abc import Sequence from typing import Any, overload -from mqt.core.dd import MatrixDD, VectorDD -from mqt.core.ir import QuantumComputation - -__all__ = [ - "CircuitSimulator", - "DeterministicNoiseSimulator", - "HybridSimulator", - "HybridSimulatorMode", - "PathSimulator", - "PathSimulatorConfiguration", - "PathSimulatorMode", - "StochasticNoiseSimulator", - "UnitarySimulator", - "UnitarySimulatorMode", -] +import mqt.core.dd +import mqt.core.ir class CircuitSimulator: def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, approximation_step_fidelity: float = 1.0, approximation_steps: int = 1, approximation_strategy: str = "fidelity", seed: int = -1, ) -> None: ... - def expectation_value(self, observable: QuantumComputation) -> float: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def get_constructed_dd(self) -> VectorDD: ... - def set_tolerance(self, tol: float) -> None: ... - def simulate(self, shots: int) -> dict[str, int]: ... - def statistics(self) -> dict[str, str]: ... + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def simulate(self, shots: int) -> dict[str, int]: + """Simulate the circuit and return the result as a dictionary of counts.""" + + def get_constructed_dd(self) -> mqt.core.dd.VectorDD: + """Get the vector DD resulting from the simulation.""" + + def expectation_value(self, observable: mqt.core.ir.QuantumComputation) -> float: ... class StochasticNoiseSimulator: def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, approximation_step_fidelity: float = 1.0, approximation_steps: int = 1, approximation_strategy: str = "fidelity", seed: int = -1, noise_effects: str = "APD", noise_probability: float = 0.01, - amp_damping_probability: float | None = 0.02, + amp_damping_probability: float = 0.02, multi_qubit_gate_factor: float = 2, ) -> None: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def get_constructed_dd(self) -> VectorDD: ... - def set_tolerance(self, tol: float) -> None: ... - def simulate(self, shots: int) -> dict[str, int]: ... - def statistics(self) -> dict[str, str]: ... + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def simulate(self, shots: int) -> dict[str, int]: + """Simulate the circuit and return the result as a dictionary of counts.""" + + def get_constructed_dd(self) -> mqt.core.dd.VectorDD: + """Get the vector DD resulting from the simulation.""" class DeterministicNoiseSimulator: def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, approximation_step_fidelity: float = 1.0, approximation_steps: int = 1, approximation_strategy: str = "fidelity", seed: int = -1, noise_effects: str = "APD", noise_probability: float = 0.01, - amp_damping_probability: float | None = 0.02, + amp_damping_probability: float = 0.02, multi_qubit_gate_factor: float = 2, ) -> None: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def get_constructed_dd(self) -> VectorDD: ... - def set_tolerance(self, tol: float) -> None: ... - def simulate(self, shots: int) -> dict[str, int]: ... - def statistics(self) -> dict[str, str]: ... - -class HybridSimulatorMode(Enum): + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def simulate(self, shots: int) -> dict[str, int]: + """Simulate the circuit and return the result as a dictionary of counts.""" + + def get_constructed_dd(self) -> mqt.core.dd.VectorDD: + """Get the vector DD resulting from the simulation.""" + +class HybridSimulatorMode(enum.Enum): """Enumeration of modes for the :class:`~HybridSimulator`.""" - DD = ... - amplitude = ... + DD = 0 + + amplitude = 1 class HybridSimulator: def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, approximation_step_fidelity: float = 1.0, approximation_steps: int = 1, approximation_strategy: str = "fidelity", @@ -108,101 +149,169 @@ class HybridSimulator: mode: HybridSimulatorMode = ..., nthreads: int = 2, ) -> None: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... - def get_final_amplitudes(self) -> list[complex]: ... + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def simulate(self, shots: int) -> dict[str, int]: + """Simulate the circuit and return the result as a dictionary of counts.""" + + def get_constructed_dd(self) -> mqt.core.dd.VectorDD: + """Get the vector DD resulting from the simulation.""" + def get_mode(self) -> HybridSimulatorMode: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def get_constructed_dd(self) -> VectorDD: ... - def set_tolerance(self, tol: float) -> None: ... - def simulate(self, shots: int) -> dict[str, int]: ... - def statistics(self) -> dict[str, str]: ... - -class PathSimulatorMode(Enum): - """Enumeration of modes for the :class:`~PathCSimulator`.""" - - alternating = ... - bracket = ... - gate_cost = ... - pairwise_recursive = ... - sequential = ... + def get_final_amplitudes(self) -> list[complex]: ... + +class PathSimulatorMode(enum.Enum): + """Enumeration of modes for the :class:`~PathSimulator`.""" + + sequential = 0 + + pairwise_recursive = 1 + + bracket = 2 + + alternating = 3 + + gate_cost = 4 class PathSimulatorConfiguration: + """Configuration options for the :class:`~.PathSimulator`.""" + def __init__(self) -> None: ... - def json(self) -> dict[str, Any]: ... @property - def bracket_size(self) -> int: ... + def mode(self) -> PathSimulatorMode: + """The mode used for determining a simulation path.""" + + @mode.setter + def mode(self, arg: PathSimulatorMode, /) -> None: ... + @property + def bracket_size(self) -> int: + """Size of the brackets one wants to combine.""" + @bracket_size.setter - def bracket_size(self, arg0: int) -> None: ... + def bracket_size(self, arg: int, /) -> None: ... @property - def gate_cost(self) -> list[int]: ... - @gate_cost.setter - def gate_cost(self, arg0: list[int]) -> None: ... + def starting_point(self) -> int: + """Start of the alternating or gate_cost strategy.""" + + @starting_point.setter + def starting_point(self, arg: int, /) -> None: ... @property - def mode(self) -> PathSimulatorMode: ... - @mode.setter - def mode(self, arg0: PathSimulatorMode) -> None: ... + def gate_cost(self) -> list[int]: + """A list that contains the number of gates which are considered in each step.""" + + @gate_cost.setter + def gate_cost(self, arg: Sequence[int], /) -> None: ... @property - def seed(self) -> int: ... + def seed(self) -> int: + """Seed for the simulator.""" + @seed.setter - def seed(self, arg0: int) -> None: ... - @property - def starting_point(self) -> int: ... - @starting_point.setter - def starting_point(self, arg0: int) -> None: ... + def seed(self, arg: int, /) -> None: ... + def json(self) -> dict[str, Any]: ... class PathSimulator: @overload - def __init__(self, circ: QuantumComputation, config: PathSimulatorConfiguration = ...) -> None: ... + def __init__(self, circ: mqt.core.ir.QuantumComputation, config: PathSimulatorConfiguration = ...) -> None: ... @overload def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, mode: PathSimulatorMode = ..., bracket_size: int = 2, starting_point: int = 0, - gate_cost: list[int] = ..., - seed: int = ..., + gate_cost: Sequence[int] = [], + seed: int = 0, ) -> None: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def get_constructed_dd(self) -> VectorDD: ... - def set_simulation_path(self, path: list[tuple[int, int]], assume_correct_order: bool = False) -> None: ... - def set_tolerance(self, tol: float) -> None: ... - def simulate(self, shots: int) -> dict[str, int]: ... - def statistics(self) -> dict[str, str]: ... - -class UnitarySimulatorMode(Enum): + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def simulate(self, shots: int) -> dict[str, int]: + """Simulate the circuit and return the result as a dictionary of counts.""" + + def get_constructed_dd(self) -> mqt.core.dd.VectorDD: + """Get the vector DD resulting from the simulation.""" + + def set_simulation_path(self, path: Sequence[tuple[int, int]], assume_correct_order: bool = False) -> None: ... + +class UnitarySimulatorMode(enum.Enum): """Enumeration of modes for the :class:`~UnitarySimulator`.""" - recursive = ... - sequential = ... + recursive = 1 + + sequential = 0 class UnitarySimulator: def __init__( self, - circ: QuantumComputation, + circ: mqt.core.ir.QuantumComputation, approximation_step_fidelity: float = 1.0, approximation_steps: int = 1, approximation_strategy: str = "fidelity", seed: int = -1, mode: UnitarySimulatorMode = ..., ) -> None: ... - def construct(self) -> None: ... - def get_active_matrix_node_count(self) -> int: ... - def get_active_vector_node_count(self) -> int: ... + def get_number_of_qubits(self) -> int: + """Get the number of qubits.""" + + def get_name(self) -> str: + """Get the name of the simulator.""" + + def statistics(self) -> dict[str, str]: + """Get additional statistics provided by the simulator.""" + + def get_active_vector_node_count(self) -> int: + """Get the number of active vector nodes, i.e., the number of vector DD nodes in the unique table with a non-zero reference count.""" + + def get_active_matrix_node_count(self) -> int: + """Get the number of active matrix nodes, i.e., the number of matrix DD nodes in the unique table with a non-zero reference count.""" + + def get_tolerance(self) -> float: + """Get the tolerance for the DD package.""" + + def set_tolerance(self, tol: float) -> None: + """Set the tolerance for the DD package.""" + + def construct(self) -> None: + """Construct the DD representing the unitary matrix of the circuit.""" + + def get_mode(self) -> UnitarySimulatorMode: ... def get_construction_time(self) -> float: ... def get_final_node_count(self) -> int: ... - def get_max_node_count(self) -> int: ... - def get_mode(self) -> UnitarySimulatorMode: ... - def get_name(self) -> str: ... - def get_number_of_qubits(self) -> int: ... - def get_tolerance(self) -> float: ... - def set_tolerance(self, tol: float) -> None: ... - def statistics(self) -> dict[str, str]: ... - def get_constructed_dd(self) -> MatrixDD: ... + def get_constructed_dd(self) -> mqt.core.dd.MatrixDD: ... From 9f03b2446111bef685f5fdf183a2caa8f9aa172a Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 26 Jan 2026 00:07:00 +0100 Subject: [PATCH 2/5] Align ruff config --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 3aa1e6eda..c55dbd71b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -237,7 +237,7 @@ known-first-party = ["mqt.ddsim"] "test/python/**" = ["T20", "ANN", "D10"] "docs/**" = ["T20"] "noxfile.py" = ["T20", "TID251"] -"*.pyi" = ["D418", "PYI021"] # pydocstyle +"*.pyi" = ["D418", "E501", "PYI021"] "*.ipynb" = [ "D", # pydocstyle "E402", # Allow imports to appear anywhere in Jupyter notebooks From bd98e5f96801610fe65ee2a1d239fd0bc42fe410 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 26 Jan 2026 01:04:03 +0100 Subject: [PATCH 3/5] Update changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b5412daf2..76bb29043 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel ### Changed - 🔧 Replace `mypy` with `ty` ([#770]) ([**@denialhaag**]) -- ♻️ Migrate Python bindings from `pybind11` to `nanobind` ([#766]) ([**@denialhaag**]) +- ♻️ Migrate Python bindings from `pybind11` to `nanobind` ([#766], [#773]) ([**@denialhaag**]) - 📦️ Provide Stable ABI wheels for Python 3.12+ ([#766]) ([**@denialhaag**]) - 👷 Stop testing on `ubuntu-22.04` and `ubuntu-22.04-arm` runners ([#740]) ([**@denialhaag**]) - 👷 Stop testing with `clang-19` and start testing with `clang-21` ([#740]) ([**@denialhaag**]) @@ -89,6 +89,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._ +[#773]: https://github.com/munich-quantum-toolkit/ddsim/pull/773 [#770]: https://github.com/munich-quantum-toolkit/ddsim/pull/770 [#766]: https://github.com/munich-quantum-toolkit/ddsim/pull/766 [#740]: https://github.com/munich-quantum-toolkit/ddsim/pull/740 From 04f9e58f2d20db0f344fb732034bf81178b07ab4 Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 26 Jan 2026 01:06:24 +0100 Subject: [PATCH 4/5] Do not cast Path to str --- noxfile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/noxfile.py b/noxfile.py index e0cc5a8c0..494f3acb1 100755 --- a/noxfile.py +++ b/noxfile.py @@ -227,9 +227,9 @@ def stubs(session: nox.Session) -> None: "--recursive", "--include-private", "--output-dir", - str(package_root), + package_root, "--pattern-file", - str(pattern_file), + pattern_file, "--module", "mqt.ddsim.pyddsim", ) From d8177857b73fa3ca30910d726653f91ba18d8dcb Mon Sep 17 00:00:00 2001 From: Daniel Haag <121057143+denialhaag@users.noreply.github.com> Date: Mon, 26 Jan 2026 14:18:30 +0100 Subject: [PATCH 5/5] Add missing docstrings --- bindings/bindings.cpp | 44 ++++++++++++++++++++++++------------ python/mqt/ddsim/pyddsim.pyi | 36 +++++++++++++++++++++-------- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/bindings/bindings.cpp b/bindings/bindings.cpp index ad0422359..64d6a56e8 100644 --- a/bindings/bindings.cpp +++ b/bindings/bindings.cpp @@ -100,7 +100,8 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { "approximation_steps"_a = 1, "approximation_strategy"_a = "fidelity", "seed"_a = -1) .def("expectation_value", &CircuitSimulator::expectationValue, - "observable"_a); + "observable"_a, + "Compute the expectation value for the given observable."); // Stoch simulator auto stochasticNoiseSimulator = @@ -201,9 +202,11 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { "seed"_a = -1, "mode"_a = HybridSchrodingerFeynmanSimulator::Mode::Amplitude, "nthreads"_a = 2) - .def("get_mode", &HybridSchrodingerFeynmanSimulator::getMode) + .def("get_mode", &HybridSchrodingerFeynmanSimulator::getMode, + "Get the mode of the hybrid simulator.") .def("get_final_amplitudes", - &HybridSchrodingerFeynmanSimulator::getVectorFromHybridSimulation); + &HybridSchrodingerFeynmanSimulator::getVectorFromHybridSimulation, + "Get the final amplitudes from the hybrid simulation."); // Path Simulator nb::enum_( @@ -231,13 +234,15 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { R"pb(A list that contains the number of gates which are considered in each step.)pb") .def_rw("seed", &PathSimulator::Configuration::seed, R"pb(Seed for the simulator.)pb") - .def("json", - [](const PathSimulator::Configuration& config) { - const auto json = nb::module_::import_("json"); - const auto loads = json.attr("loads"); - const auto dict = loads(config.json().dump()); - return nb::cast>(dict); - }) + .def( + "json", + [](const PathSimulator::Configuration& config) { + const auto json = nb::module_::import_("json"); + const auto loads = json.attr("loads"); + const auto dict = loads(config.json().dump()); + return nb::cast>(dict); + }, + "Get the configuration as a JSON-style dictionary.") .def("__repr__", &PathSimulator::Configuration::toString); auto pathSimulator = createSimulator(m, "PathSimulator"); @@ -266,7 +271,12 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { .def("set_simulation_path", nb::overload_cast(&PathSimulator::setSimulationPath), - "path"_a, "assume_correct_order"_a = false); + "path"_a, "assume_correct_order"_a = false, + R"pb(Set the simulation path. + +Args: + path: The components of the simulation path. + assume_correct_order: Whether the provided path is assumed to be in the correct order. Defaults to False.)pb"); // Unitary Simulator nb::enum_( @@ -299,8 +309,12 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) { "circ"_a, "approximation_step_fidelity"_a = 1., "approximation_steps"_a = 1, "approximation_strategy"_a = "fidelity", "seed"_a = -1, "mode"_a = UnitarySimulator::Mode::Recursive) - .def("get_mode", &UnitarySimulator::getMode) - .def("get_construction_time", &UnitarySimulator::getConstructionTime) - .def("get_final_node_count", &UnitarySimulator::getFinalNodeCount) - .def("get_constructed_dd", &UnitarySimulator::getConstructedDD); + .def("get_mode", &UnitarySimulator::getMode, + "Get the mode of the unitary simulator.") + .def("get_construction_time", &UnitarySimulator::getConstructionTime, + "Get the time taken to construct the DD.") + .def("get_final_node_count", &UnitarySimulator::getFinalNodeCount, + "Get the final node count of the constructed DD.") + .def("get_constructed_dd", &UnitarySimulator::getConstructedDD, + "Get the constructed DD."); } diff --git a/python/mqt/ddsim/pyddsim.pyi b/python/mqt/ddsim/pyddsim.pyi index 3ecfa8acf..7fc8aa380 100644 --- a/python/mqt/ddsim/pyddsim.pyi +++ b/python/mqt/ddsim/pyddsim.pyi @@ -49,7 +49,8 @@ class CircuitSimulator: def get_constructed_dd(self) -> mqt.core.dd.VectorDD: """Get the vector DD resulting from the simulation.""" - def expectation_value(self, observable: mqt.core.ir.QuantumComputation) -> float: ... + def expectation_value(self, observable: mqt.core.ir.QuantumComputation) -> float: + """Compute the expectation value for the given observable.""" class StochasticNoiseSimulator: def __init__( @@ -176,8 +177,11 @@ class HybridSimulator: def get_constructed_dd(self) -> mqt.core.dd.VectorDD: """Get the vector DD resulting from the simulation.""" - def get_mode(self) -> HybridSimulatorMode: ... - def get_final_amplitudes(self) -> list[complex]: ... + def get_mode(self) -> HybridSimulatorMode: + """Get the mode of the hybrid simulator.""" + + def get_final_amplitudes(self) -> list[complex]: + """Get the final amplitudes from the hybrid simulation.""" class PathSimulatorMode(enum.Enum): """Enumeration of modes for the :class:`~PathSimulator`.""" @@ -226,7 +230,8 @@ class PathSimulatorConfiguration: @seed.setter def seed(self, arg: int, /) -> None: ... - def json(self) -> dict[str, Any]: ... + def json(self) -> dict[str, Any]: + """Get the configuration as a JSON-style dictionary.""" class PathSimulator: @overload @@ -268,7 +273,13 @@ class PathSimulator: def get_constructed_dd(self) -> mqt.core.dd.VectorDD: """Get the vector DD resulting from the simulation.""" - def set_simulation_path(self, path: Sequence[tuple[int, int]], assume_correct_order: bool = False) -> None: ... + def set_simulation_path(self, path: Sequence[tuple[int, int]], assume_correct_order: bool = False) -> None: + """Set the simulation path. + + Args: + path: The components of the simulation path. + assume_correct_order: Whether the provided path is assumed to be in the correct order. Defaults to False. + """ class UnitarySimulatorMode(enum.Enum): """Enumeration of modes for the :class:`~UnitarySimulator`.""" @@ -311,7 +322,14 @@ class UnitarySimulator: def construct(self) -> None: """Construct the DD representing the unitary matrix of the circuit.""" - def get_mode(self) -> UnitarySimulatorMode: ... - def get_construction_time(self) -> float: ... - def get_final_node_count(self) -> int: ... - def get_constructed_dd(self) -> mqt.core.dd.MatrixDD: ... + def get_mode(self) -> UnitarySimulatorMode: + """Get the mode of the unitary simulator.""" + + def get_construction_time(self) -> float: + """Get the time taken to construct the DD.""" + + def get_final_node_count(self) -> int: + """Get the final node count of the constructed DD.""" + + def get_constructed_dd(self) -> mqt.core.dd.MatrixDD: + """Get the constructed DD."""