Installation#

ergmx is a Python package with a core written in Rust. How you install it decides whether you need Rust:

You install

You need

Rust compiler

A release from PyPI

uv (or pip)

No: the compiled core is inside the wheel

From GitHub, or on a platform without a wheel

uv (or pip) and a C linker

Yes, but maturin downloads it if you don’t have it

For development

uv, rustup and a C linker

Yes, the version pinned in rust-toolchain.toml

ergmx needs Python 3.11 or newer. uv installs Python for you if needed. For what Rust, cargo and maturin are, and why ergmx uses them, see Under the hood.

Install uv#

uv manages Python versions, environments and packages. On macOS and Linux:

curl -LsSf https://astral.sh/uv/install.sh | sh

On Windows, in PowerShell:

powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Homebrew (brew install uv), pipx and other options are in uv’s installation guide.

Install ergmx from PyPI#

Released versions of ergmx come as wheels: one per platform, with the Rust core already compiled, for Linux (x86-64 and ARM, glibc and musl), macOS (Intel and Apple Silicon) and Windows (x86-64). Each wheel serves every Python from 3.11 on. Installing one needs no compiler.

In a uv project (a folder with a pyproject.toml, created with uv init):

uv add "ergmx[igraph,plot]"

In any environment:

uv pip install "ergmx[igraph,plot]"     # or: pip install "ergmx[igraph,plot]"

To run a script without creating an environment:

uv run --with "ergmx[igraph]" python my_analysis.py

Optional dependencies#

ergmx itself only needs NumPy and SciPy. Networks come from a graph library, and plots need matplotlib. Pick the extras you use:

Extra

Installs

For

igraph

python-igraph

igraph.Graph networks and ergmx.datasets

networkx

networkx

networkx.Graph / DiGraph networks

plot

matplotlib

gof(...).plot() and mcmc_diagnostics().plot()

pandas

pandas

.to_frame() of fits, predictions and tables of results

From GitHub#

uv add "ergmx[igraph,plot] @ git+https://github.com/neylsoncrepalde/ergmx"
# or, in any environment:
uv pip install "ergmx[igraph,plot] @ git+https://github.com/neylsoncrepalde/ergmx"

Add @v0.2.0 (a tag), @main or a commit after the URL to pick a version. This builds ergmx from source, as does installing on a platform without a wheel: uv runs maturin, the build tool of the Rust core, which compiles it with optimizations. The first build takes under a minute on a recent computer; uv caches the result. It needs a Rust compiler, which maturin downloads if you don’t have one (see The Rust toolchain).

The Rust toolchain#

Building ergmx from source needs two things:

  1. A C linker, which Rust uses to produce the compiled library. Neither uv nor maturin installs it:

    • macOS: the Xcode Command Line Tools, xcode-select --install;

    • Linux: a C compiler, such as sudo apt install build-essential (Debian, Ubuntu) or sudo dnf install gcc (Fedora);

    • Windows: the Visual Studio Build Tools, with the “Desktop development with C++” workload.

  2. Rust 1.88 or newer, installed by you or downloaded by maturin.

Rust installed by maturin#

If no cargo command is found, maturin downloads a private Rust toolchain before building, with puccinialin. It takes about half a minute and 500 MB, in your user cache folder (~/Library/Caches/puccinialin on macOS, ~/.cache/puccinialin on Linux, under %LOCALAPPDATA% on Windows), and is reused by later builds. It is not added to your PATH, so it doesn’t interfere with anything else. Delete the folder to remove it.

To forbid the download, so that a build without Rust fails instead, set MATURIN_NO_INSTALL_RUST=1.

Rust installed with rustup#

For development, or to keep a single Rust installation, install rustup, Rust’s official installer. On macOS and Linux:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

On Windows, download and run rustup-init.exe, after the Visual Studio Build Tools. Then open a new terminal, so that cargo is on your PATH.

The repository pins the Rust version in rust-toolchain.toml (1.98.1, with clippy and rustfmt), so that every build, and every lint, is the same. In the repository, install that version with:

rustup toolchain install

rustup then uses it automatically inside the repository, and your default Rust everywhere else. The oldest Rust that builds ergmx is 1.88, set as rust-version in Cargo.toml.

Development#

Clone the repository and let uv set up everything else:

git clone https://github.com/neylsoncrepalde/ergmx.git
cd ergmx
rustup toolchain install    # the pinned Rust
uv sync                     # Python, dependencies, and ergmx built from source
uv run pytest               # the Python tests

uv sync:

  • installs the Python of .python-version (3.14) if needed, and creates .venv;

  • installs the exact versions in uv.lock, including the dev dependency group (pytest, igraph, networkx, matplotlib, pandas, maturin);

  • builds the Rust core with optimizations and installs ergmx in editable mode: changes to the Python code apply immediately.

After changing Rust code, nothing else is needed: uv run notices the change (through cache-keys in pyproject.toml) and rebuilds the core, in a few seconds, before running the command. Use uv run for everything, or run uv sync before using .venv directly.

The Rust checks run with cargo:

cargo clippy --release --all-targets -- -D warnings
cargo test --release

cargo test links the Rust unit tests to the Python library of the python3 on your PATH (set PYO3_PYTHON to choose another); CI uses the Python of actions/setup-python.

To test another Python in a temporary environment, leaving .venv alone, or the oldest dependencies that ergmx allows:

uv run --isolated --python 3.11 pytest
uv venv --python 3.11 /tmp/lowest
uv pip install --python /tmp/lowest --resolution lowest-direct . \
    "pytest>=8" "igraph>=0.11.5" "networkx>=3.2" "matplotlib>=3.9" "pandas>=2.0"
/tmp/lowest/bin/python -m pytest

Building the documentation#

uv sync --group docs
uv run sphinx-build -W --keep-going -d docs/_build/doctrees docs docs/_build/html

Then open docs/_build/html/index.html. Every example runs during the build, so it takes under a minute.

Comparing with R#

The reference results in tests/data/ and the benchmark come from R scripts. To regenerate them, install R with the ergm, igraph and jsonlite packages, and run from the repository:

Rscript scripts/r_reference.R && Rscript scripts/r_gof_reference.R
Rscript benchmarks/benchmark.R && uv run python benchmarks/benchmark.py

How the Rust is bundled#

The Rust code in src/ is a library built with PyO3, which exposes it to Python as the module ergmx._core. maturin compiles it into a single file, _core.abi3.so (_core.pyd on Windows), and places it inside the ergmx package, next to the Python code in python/ergmx/. A wheel is that package, zipped: installing it copies the compiled file, and Python imports it like any module. Nothing is compiled on the user’s machine.

The core uses Python’s stable ABI (abi3): it only calls the part of Python’s C interface that doesn’t change between versions, so one compiled file works on Python 3.11, 3.12, 3.13, 3.14 and later.

To build the wheel for your platform and the source distribution:

uv build

They are written to dist/. The source distribution has the Rust sources and rust-toolchain.toml; installing it compiles them, as installing from GitHub does.

The Release workflow (.github/workflows/release.yml) builds the wheels of every platform on GitHub’s machines, installs each one it can run (Linux x86-64, macOS and Windows) on Python 3.11 and 3.14 in an environment without Rust or the source code, and fits a model with it.

Publishing a release#

When a GitHub release is published, the Release workflow uploads the wheels and the source distribution to PyPI with trusted publishing: PyPI trusts this repository’s workflow, so no password or token is stored. It needs a one-time setup:

  1. On PyPI, under Your account > Publishing, add a pending publisher: project name ergmx, owner neylsoncrepalde, repository ergmx, workflow release.yml, environment pypi.

  2. On GitHub, optionally, protect the pypi environment (Settings > Environments) so that publishing needs your approval.

Then, for each release: update the version in pyproject.toml and Cargo.toml and the changelog, commit, and create a release on GitHub with a tag such as v0.2.0. To build and test the wheels without publishing, run the workflow by hand from the Actions tab.

Troubleshooting#

Rust not found, installing into a temporary directory

maturin is downloading Rust, as above. Install rustup to use your own, or set MATURIN_NO_INSTALL_RUST=1 to fail instead.

linker 'cc' not found, xcrun: error, link.exe not found

The C linker is missing: see The Rust toolchain.

rustup says the toolchain of rust-toolchain.toml is not installed

Run rustup toolchain install in the repository.

Changes to the Rust code don’t show up

The core is rebuilt by uv run and uv sync, not by running .venv’s Python directly. Run uv sync, or force a rebuild with uv sync --reinstall-package ergmx.

Fits are much slower than the benchmarks

The core was built without optimizations, for example by maturin develop without --release. Rebuild it with uv sync --reinstall-package ergmx.