Full development (Python + Rust, recommended):
- Python 3.12+
- Rust (stable toolchain)
- uv - Python package manager
- just - Command runner
- pecos - PECOS dev tools CLI
- ripgrep - Required by the dependency integrity checks run by
just lintandjust security-check. Install it withpecos install ripgrep, or manually withcargo install ripgrep --locked,brew install ripgrep,apt install ripgrep, orwinget install BurntSushi.ripgrep. - Windows: Git for Windows (provides Git Bash, required by Justfile recipes) or WSL
Pure Rust development (Rust crates only):
If you're only working on Rust crates (e.g., pecos-core, pecos-engines), you can use cargo directly without Python:
cargo build -p pecos-core
cargo test -p pecos-core
cargo clippy -p pecos-core
cargo clean # Clean Rust artifacts onlyFor developers who want to contribute or modify PECOS:
-
Make sure you have Python and Rust installed for your system.
-
Install the pre-clone dev tools from crates.io:
cargo install --locked uv just
This installs:
uv- Python package managerjust- Command runner for build tasks
-
Clone the repository:
git clone https://github.com/PECOS-packages/PECOS.git cd PECOS -
Install the PECOS developer CLI from the repo:
cargo install --path crates/pecos-cli
This installs the
pecosbinary (llvm, cuda, cuquantum, rust, python, deps commands). -
Create the development environment:
uv sync
uv syncinstalls the defaultdevandtestgroups (lint, build, docs, and pytest tooling). Optional groups you may want to add:Group When to enable Command examplesRunning notebooks under examples/or DataFrame benchmarksuv sync --group examplesnumpy-compatVerifying older NumPy/SciPy minimums uv sync --group numpy-compatcuda13GPU simulators on CUDA 13 (Turing/CC 7.5+; requires CUDA toolkit) uv sync --group cuda13cuda12GPU simulators on CUDA 12 (incl. V100/Volta) uv sync --group cuda12Combine groups with multiple
--groupflags (e.g.uv sync --group examples --group cuda13). Pick one CUDA major --cuda12andcuda13are mutually exclusive. -
LLVM 21.1 Setup (Required for LLVM IR/QIS Support)
PECOS Rust builds require LLVM version 21.1 for LLVM IR features. Python HUGR-to-QIS lowering uses the
selene-hugr-qis-compilerpackage throughpecos_rslib.hugr_lowering.compile_hugr_to_qis(re-exported bypecos.compilation_pipeline), followed by PECOS helper-symbol normalization. It does not require a local LLVM installation. Thepecos-rslib-llvmwheel remains available for QIR generation and its Rust compiler binding.Quick setup:
pecos install llvm cargo build
pecos install llvmis the managed shared-LLVM path on supported Debian/Ubuntu-compatible Linux systems. For macOS, Windows, and other Linux distributions, see the LLVM Setup Guide. -
You may wish to explicitly activate the environment for development. To do so:
=== "Linux/Mac"
sh source .venv/bin/activate=== "Windows"
sh .\.venv\Scripts\activate -
Build the project in editable mode
just build
Other build options:
just build-release(optimized),just build-native(optimized for your CPU). -
Run all Python and Rust tests:
just testNote: Make sure you have run a build command before running tests.
-
Run linters using pre-commit (after installing it) to make sure all everything is properly linted/formated
just lint
-
Run dependency and security policy checks when touching dependency manifests, lockfiles, GitHub Actions workflows, or security policy:
just security-check
For Rust-only dependency changes,
just cargo-denyruns the samecargo-denychecks that CI applies to the root workspace and the standalone native benchmark crate. -
To deactivate your development venv:
deactivate
Before pull requests are merged, they must pass linting, tests, and dependency/security checks. The local pre-PR gate is:
just check-allNote: For the Rust side of the project, you can use cargo to run tests, benchmarks, formatting, etc.
Runtime code imports numeric primitives from the public pecos surface. NumPy remains in tests as an oracle.
from pecos import Array, array, asarray, dtypes, sum as array_sum, zerosMap the dtype used by the migrated code as follows:
| NumPy dtype | PECOS dtype |
|---|---|
np.uint8 |
dtypes.uint8 |
np.float64 or float |
dtypes.float64 |
Use Array in annotations and enter the native layer with asarray() when an external API returns an array-like
object. Constructors and casts take PECOS dtypes, for example zeros(size, dtype=dtypes.uint8) and
array(values, dtype=dtypes.uint8).
Array.flatten() preserves row-major order and returns an independent copy:
from pecos import array, dtypes
values = array([[1, 0], [0, 1]], dtype=dtypes.uint8)
flat = values.flatten().tolist()
assert flat == [1, 0, 0, 1]Array deliberately differs from NumPy at three boundaries:
ravel()andreshape()always return copies. NumPy may return views, butArrayowns its buffer and has no view representation, so an honest copy is preferred over a silently-copying "view".reshape()infers a dimension only from the literal-1. Other negative dimensions are rejected, even though NumPy treats any single negative dimension as inferred.fill()does not coerce values by truthiness or parse numeric strings. Convert values explicitly instead of relying on behavior such as a non-empty string becomingTrue.
Two more migration boundaries currently need local workarounds:
- Elementwise
Array ^ Arrayis not available yet (#458). For arrays already known to contain only binary values, elementwise inequality has the same result; do not use that substitution for general integers. - Bit shifts, unsigned arithmetic, and bitwise boolean operators are not available on
Array(#458). For binary values, cast toint64and double instead of shifting; usewhere()for boolean selection.Array * Arrayis matrix multiplication, so useelemwise_mul()when the intended operation is elementwise. - Spell NumPy's
dtype=floatasdtype=dtypes.float64, especially withasarray(), to preserve NumPy's 64-bit width explicitly.
The decoder-side bit packing and boolean-selection patterns remain explicit and exact:
from pecos import any as array_any, array, dtypes, sum as array_sum, where
low = array([1, 0], dtype=dtypes.uint8).astype(dtypes.int64)
high = array([0, 1], dtype=dtypes.uint8).astype(dtypes.int64)
packed = (low + high * 2).astype(dtypes.uint8)
assert packed.tolist() == [1, 2]
observable_bits = array([[1, 0], [1, 1]], dtype=dtypes.int64)
weights = array([1, 2], dtype=dtypes.int64)
observable_masks = array_sum(observable_bits.elemwise_mul(weights), axis=1)
assert list(observable_masks) == [1, 3]
active = array([True, False], dtype=dtypes.bool_)
values = array([0, 2], dtype=dtypes.uint8)
assert array_any(where(active, False, values != 0))Elementwise comparison returns a boolean Array, and array_sum() accepts it directly -- counting
mismatches needs no cast:
from pecos import array, dtypes, sum as array_sum
predicted = array([1, 0, 1], dtype=dtypes.uint8)
expected = array([1, 1, 1], dtype=dtypes.uint8)
logical_errors = int(array_sum(predicted != expected))
assert logical_errors == 1Seed pecos.random immediately before a reproducible draw. Its stream deliberately differs from
NumPy's for the same seed, so migrate statistical tests by re-baselining pinned samples and retaining
distributional invariants instead of asserting cross-library sample equality:
from pecos import random
random.seed(458)
first = random.binomial(20, 0.25, size=8)
random.seed(458)
repeated = random.binomial(20, 0.25, size=8)
assert first.tolist() == repeated.tolist()Use the Justfile recipes below so local checks match CI:
| Command | When to run | What it checks |
|---|---|---|
just security-check |
Dependency, lockfile, GitHub Actions, cache, or security-policy changes | Runs the dependency integrity script and both cargo-deny checks |
just cargo-deny |
Rust dependency or Cargo lockfile changes | Checks advisories, banned dependency patterns, and allowed dependency sources |
just cargo-deny-workspace |
Root workspace Rust dependency changes | Runs cargo-deny on the root Rust workspace |
just cargo-deny-native-bench |
Native benchmark crate dependency changes | Runs cargo-deny on scripts/native_bench/bench_pecos/Cargo.toml |
just dependency-integrity-check |
CI workflow, lockfile policy, action pinning, or cache posture changes | Checks lock discipline, action pinning, cache write posture, dependency review coverage, and package-worm indicators |
just check-all |
Before opening or updating a PR with broad changes | Runs clean, release build, release tests, lint, and dependency/security checks |
cargo-deny is not installed by uv sync. To run the Rust dependency policy checks locally, install the same version used by CI:
cargo install --locked --version 0.19.6 cargo-denyThe first cargo-deny run may update the local advisory database under ~/.cargo. CI runs these checks on every relevant Cargo manifest, lockfile, deny.toml, or cargo-deny workflow change, and also on the scheduled security lane.
Clean commands are cross-platform (Windows, macOS, Linux):
just clean # Clean project build artifacts (includes selene)
just clean cache # Clean ~/.pecos/cache/ and ~/.pecos/tmp/
just clean deps # Clean ~/.pecos/deps/ (LLVM, CUDA, cuQuantum)
just clean all # Everything above
just clean dry-run # Preview what would be cleanedYou can also run the cleaning script directly:
uv run python scripts/clean.py --help
uv run python scripts/clean.py --dry-run # Preview what would be deletedFor day-to-day Rust development, cargo clean handles the target/ directory. The ~/.pecos/ directory (LLVM, CUDA, C++ dependencies) rarely needs cleaning - it contains installed dependencies rather than build artifacts.
PECOS uses ~/.pecos/ to store external dependencies and build artifacts that cannot be managed through Cargo.toml:
~/.pecos/
├── deps/llvm-21.1/ # LLVM 21.1 installation (for QIR/LLVM IR execution)
├── deps/ # Downloaded C++ dependencies (Stim, etc.)
└── cache/ # Build artifacts and intermediate files
| Variable | Purpose | Default |
|---|---|---|
PECOS_HOME |
Override entire home directory | ~/.pecos/ |
PECOS_DEPS_DIR |
Override deps location | $PECOS_HOME/deps/ |
PECOS_CACHE_DIR |
Override cache location | $PECOS_HOME/cache/ |
These can be set via shell environment or in .cargo/config.toml:
[env]
PECOS_HOME = { value = "/custom/path", force = true }For specific development topics, see:
- Parallel Blocks and Optimization - Guide to using and extending the Parallel block construct and optimizer
- Documentation Code Testing - Guide to testing code examples in documentation