Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion .license-tools-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
".*\\.profile",
"uv\\.lock",
"py\\.typed",
".*build.*"
".*build.*",
"ddsim_patterns.txt"
]
}
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**])
Expand Down Expand Up @@ -89,6 +89,7 @@ _📚 Refer to the [GitHub Release Notes] for previous changelogs._

<!-- PR links -->

[#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
Expand Down
67 changes: 46 additions & 21 deletions bindings/bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <nanobind/stl/list.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/map.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/optional.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/pair.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/string.h> // NOLINT(misc-include-cleaner)
#include <nanobind/stl/vector.h> // NOLINT(misc-include-cleaner)
#include <optional>
Expand Down Expand Up @@ -73,7 +74,6 @@ nb::class_<Sim> 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 =
Expand All @@ -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 =
Expand Down Expand Up @@ -166,7 +167,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_<HybridSchrodingerFeynmanSimulator::Mode>(m, "HybridSimulatorMode")
nb::enum_<HybridSchrodingerFeynmanSimulator::Mode>(
m, "HybridSimulatorMode",
R"pb(Enumeration of modes for the :class:`~HybridSimulator`.)pb")
.value("DD", HybridSchrodingerFeynmanSimulator::Mode::DD)
.value("amplitude", HybridSchrodingerFeynmanSimulator::Mode::Amplitude);

Expand Down Expand Up @@ -199,12 +202,16 @@ 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_<PathSimulator::Configuration::Mode>(m, "PathSimulatorMode")
nb::enum_<PathSimulator::Configuration::Mode>(
m, "PathSimulatorMode",
"Enumeration of modes for the :class:`~PathSimulator`.")
.value("sequential", PathSimulator::Configuration::Mode::Sequential)
.value("pairwise_recursive",
PathSimulator::Configuration::Mode::PairwiseRecursiveGrouping)
Expand All @@ -214,21 +221,28 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) {

nb::class_<PathSimulator::Configuration>(
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<nb::typed<nb::dict, nb::str, nb::any>>(dict);
},
"Get the configuration as a JSON-style dictionary.")
.def("__repr__", &PathSimulator::Configuration::toString);

auto pathSimulator = createSimulator<PathSimulator>(m, "PathSimulator");
Expand Down Expand Up @@ -257,10 +271,17 @@ NB_MODULE(MQT_DDSIM_MODULE_NAME, m) {
.def("set_simulation_path",
nb::overload_cast<const PathSimulator::SimulationPath::Components&,
bool>(&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_<UnitarySimulator::Mode>(m, "UnitarySimulatorMode")
nb::enum_<UnitarySimulator::Mode>(
m, "UnitarySimulatorMode",
R"pb(Enumeration of modes for the :class:`~UnitarySimulator`.)pb")
.value("recursive", UnitarySimulator::Mode::Recursive)
.value("sequential", UnitarySimulator::Mode::Sequential);

Expand Down Expand Up @@ -288,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.");
}
3 changes: 3 additions & 0 deletions bindings/ddsim_patterns.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
_hashable_values_:

_unhashable_values_map_:
49 changes: 49 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
package_root,
"--pattern-file",
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)
Comment thread
denialhaag marked this conversation as resolved.

# 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()
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading