Skip to content
Draft
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -178,5 +178,9 @@ benches/results/**
# devcontainer files
.devcontainer/devcontainer-lock.json

# nix build symlinks and direnv state
/result
/result-*
.direnv/
# Useful when running in clusters
logs/
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ uv run pytest # Run tests (monoprop's suite + the workspace members' suites)
just build-docs # Build documentation
```

Nix users get the same toolchain with `nix develop`. The flake (`flake.nix` plus
`nix/`) also exposes `packages.monoprop{,-mpi}` and a `nix run` app; the packaged
build disables the C++ unit tests and arch flags, and its `version` is pinned by
hand because setuptools-scm cannot read git metadata in the build sandbox.


### Template Metaprogramming

Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,19 @@ Without a DevContainer, install the prerequisites from the
[building guide](https://docs.monoprop.algorithmiq.tech/building#prerequisites)
by hand.

### Nix

The repository is a [Nix flake](https://wiki.nixos.org/wiki/Flakes), so on Nix or
NixOS none of the prerequisites have to be installed by hand:

```bash
nix develop # dev shell: C++ toolchain, hwloc, MPI, uv, just, node
nix build .#monoprop # build the package (`.#monoprop-mpi` for the MPI build)
nix run # Python interpreter with monoprop importable
```

Inside `nix develop` the usual `uv sync` and `just` workflows apply unchanged.

## Contributing

Please read [CONTRIBUTING.md](CONTRIBUTING.md) before opening a pull request.
Expand Down
27 changes: 27 additions & 0 deletions docs/content/docs/building.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,33 @@ The repository ships a [DevContainer](https://containers.dev/) with all of the
above pre-configured; opening the folder in VS Code and rebuilding the container is
the quickest route to a working environment.

## Building with Nix

The repository is also a [Nix flake](https://wiki.nixos.org/wiki/Flakes):

| Command | What it gives you |
| --- | --- |
| `nix develop` | a shell with every prerequisite above, plus `uv`, `just` and Node.js |
| `nix build .#monoprop` | the Python package, built without MPI |
| `nix build .#monoprop-mpi` | the same package with `monoprop_ENABLE_MPI=ON` |
| `nix run` | a Python interpreter with `monoprop` importable |

Inside `nix develop` the `uv sync` and `just` workflows below apply unchanged. The
shell sets two variables that only matter on NixOS: `UV_PYTHON_PREFERENCE=only-system`,
because uv's managed interpreters expect a loader NixOS does not provide, and
`LD_LIBRARY_PATH`, so that manylinux wheels can resolve `libstdc++`.

The packaged build deviates from the `uv` build in three places, all in
`nix/monoprop.nix`:

- the C++ unit tests are disabled, because they resolve msgpack-cxx through a git
fetch that the build sandbox denies — build them from the dev shell instead;
- `monoprop_ENABLE_ARCH_FLAGS` is off, since a store path may be substituted onto a
machine other than the one that built it; pass
`.override { enableArchFlags = true; }` for a native build;
- the version is pinned, because setuptools-scm cannot read git metadata inside the
sandbox; bump it alongside the release tag.

## Building the Python bindings

`uv` creates a virtual environment, installs the Python dependencies, and
Expand Down
61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

59 changes: 59 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
{
description = "monoprop: because your operators deserve to propagate at escape velocity.";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs =
{
nixpkgs,
flake-utils,
...
}:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
python = pkgs.python312;
scikit-build-core = python.pkgs.scikit-build-core.overridePythonAttrs (_: rec {
version = "1.0.3";
src = pkgs.fetchPypi {
pname = "scikit_build_core";
inherit version;
hash = "sha256-pNegWXjuN5dcN3Q1EMiZHi3rzn74OvsKB8DFdv1PFug=";
};
doCheck = false;
});

monoprop = python.pkgs.callPackage ./nix/monoprop.nix { inherit scikit-build-core; };
monoprop-mpi = monoprop.override { withMPI = true; };

replEnv = python.withPackages (ps: [
monoprop
ps.numpy
]);
in
{
packages = {
default = monoprop;
inherit monoprop monoprop-mpi;
};

apps.default = {
type = "app";
program = "${replEnv}/bin/python";
meta.description = "Python interpreter with monoprop importable";
};

devShells.default = pkgs.callPackage ./nix/devshell.nix { inherit python; };

checks = {
inherit monoprop;
};

formatter = pkgs.nixfmt-tree;
}
);
}
68 changes: 68 additions & 0 deletions nix/devshell.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
lib,
mkShell,
stdenv,
python,

# C++ toolchain
cmake,
ninja,
pkg-config,
clang-tools,
gdb,
lcov,
doxygen,

# C++ dependencies
boost,
hwloc,
openmpi,

# workflow tooling
git,
just,
nodejs,
uv,
zlib,
}:

mkShell {
nativeBuildInputs = [
cmake
ninja
pkg-config
clang-tools
gdb
lcov
doxygen
git
just
nodejs
python
uv
];

# Host inputs, so that CMake's setup hook puts them on NIXPKGS_CMAKE_PREFIX_PATH
# and PKG_CONFIG_PATH for the CMake run scikit-build-core drives.
buildInputs = [
boost
hwloc
openmpi
];

shellHook = ''
# `[tool.uv] python-preference = "only-managed"` pulls prebuilt interpreters
# that expect a loader NixOS does not provide; use this shell's CPython.
export UV_PYTHON_PREFERENCE=only-system
export UV_PYTHON="${python}/bin/python"

# manylinux wheels (numpy, qiskit, ...) resolve libstdc++ through the ambient
# loader path, which is empty on NixOS.
export LD_LIBRARY_PATH="${
lib.makeLibraryPath [
stdenv.cc.cc.lib
zlib
]
}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
'';
}
120 changes: 120 additions & 0 deletions nix/monoprop.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
{
lib,
buildPythonPackage,
python,

# build tooling
cmake,
ninja,
pkg-config,
nanobind,
scikit-build-core,
setuptools-scm,

# C++ dependencies
boost,
hwloc,

# runtime dependencies
msgpack,
numpy,

# optional MPI support
mpi,
mpi4py,
withMPI ? false,

# `-march=native` is the upstream default; it is off here because a store path
# may be built on one machine and substituted onto another.
enableArchFlags ? false,

# setuptools-scm derives the version from git metadata, which the build sandbox
# does not see. Bump alongside the release tag.
version ? "0.8.0",
}:

buildPythonPackage {
pname = "monoprop";
inherit version;
pyproject = true;

src = lib.fileset.toSource {
root = ../.;
fileset = lib.fileset.unions [
../CMakeLists.txt
../LICENSE
../README.md
../pyproject.toml
# `tools/generate-dispatch.py` stamps this header onto the generated files.
../.github/license-header.txt
../cmake
../cpp
../src
../tools
];
};

# scikit-build-core invokes CMake itself; the nixpkgs hook must not configure
# the tree first.
dontUseCmakeConfigure = true;

nativeBuildInputs = [
cmake
ninja
pkg-config
];

build-system = [
nanobind
scikit-build-core
setuptools-scm
# Upstream lists mpi4py in `[build-system] requires` unconditionally, even
# though the extension only links MPI when `withMPI` is set.
mpi4py
];

buildInputs = [
boost
hwloc
]
++ lib.optionals withMPI [ mpi ];

dependencies = [
msgpack
numpy
]
++ lib.optionals withMPI [ mpi4py ];

env = {
SETUPTOOLS_SCM_PRETEND_VERSION = version;
SKBUILD_CMAKE_DEFINE = lib.concatStringsSep ";" (
[
# The C++ test target resolves msgpack-cxx through CPM, i.e. a git fetch
# the sandbox denies; C++ tests belong to the dev shell anyway.
"monoprop_ENABLE_CXX_UNIT_TESTS=OFF"
"monoprop_ENABLE_ARCH_FLAGS=${if enableArchFlags then "ON" else "OFF"}"
# nanobind's CMake config lives inside its Python package, which is not
# on the interpreter's own site-packages path under nixpkgs.
"nanobind_DIR=${nanobind}/${python.sitePackages}/nanobind/cmake"
]
++ lib.optionals withMPI [ "monoprop_ENABLE_MPI=ON" ]
);
};

# Overrides the single-job default that `[tool.scikit-build]` sets for laptops.
preBuild = ''
export SKBUILD_BUILD_TOOL_ARGS="-j$NIX_BUILD_CORES"
'';

# The test suite lives outside the wheel and needs fixtures excluded from src.
doCheck = false;
pythonImportsCheck = [ "monoprop" ];

meta = {
description = "High-performance Majorana and Pauli propagation";
homepage = "https://github.com/Algorithmiq/monoprop";
changelog = "https://github.com/Algorithmiq/monoprop/releases";
license = lib.licenses.asl20;
platforms = lib.platforms.unix;
};
}