Build, test and document

Run from the repository root with CPython 3.12.x and a C++20 compiler. Install the local test tools before using Make:

python3.12 -m venv .venv
.venv/bin/python -m pip install --cache-dir .cache/pip -r requirements/test.lock
make test
make test PRESET=cpu-openmp
make gtest PRESET=cpu-asan
make lint
make typecheck
make test-native
make demo

make test runs GoogleTest and pytest through CTest. make gtest and make pytest select their respective layers. The sanitizer preset runs native address and undefined-behavior checks. Python tests live under tests/python/; examples live under examples/. make lint checks Python lint and formatting plus C++ formatting. Build directories are isolated by preset under build/. JOBS controls build parallelism; PYTHON, BUILD_DIR and CMAKE_ARGS allow local toolchain overrides.

make typecheck checks representative public CPU and CUDA calls with the pinned mypy development tool. It copies the staged package into an isolated consumer directory, verifies that importing the CUDA façade does not load the optional extension, and checks return types and rejected API usage. No GPU or Torch/CuPy installation is needed. The checks live under tests/typing/; the benchmark package is outside their scope.

make test-native builds the selected preset with Python and internal tests disabled in build/<preset>-native. It installs the development component, moves the installation prefix, and configures an independent C++ consumer outside the checkout. The consumer checks dense/CSR CG and PCG, reusable workspaces, true residuals and basic operation counts. Native, OpenMP and BLAS CI configurations run these checks. Consumer compile settings are checked for leaked library-only macros, OpenMP flags and CUDA SDK include directories. Small syntax-only probes verify the installed SolveInfo discard diagnostic and accepted explicit-discard/status-use forms. Only these probe commands use -Werror; project and imported-target warning settings remain unchanged. CUDA CI also compiles/links the optional consumer and reports device-dependent skips; the GPU job uses NATIVE_ARGS=--require-cuda. NATIVE_BUILD_DIR overrides its build path. See Installation for direct CMake commands and imported targets.

No command above upgrades system packages, installs with pip --user, or changes shell configuration. CMake downloads pinned GoogleTest into its local build dependency directory. The optional BLAS dependency also belongs in the project’s local dependency directory.

Optional dense BLAS

The BLAS kernel replaces dense matrix-vector products. Vector updates and reductions retain the selected fused/separate CPU policy. dense_kernel is independent of algorithm, fused and Jacobi preconditioning. The first implementation accepts only num_threads=1; native OpenMP remains available for multiple threads. CSR operators require dense_kernel="native".

Build the optional mode explicitly:

make test PRESET=cpu-blas
make docs PRESET=cpu-blas
make doctest PRESET=cpu-blas

The Linux native build needs GNU or LLVM nm and objcopy in the existing toolchain. CMake downloads pinned OpenBLAS 0.3.30 and builds a private sequential LP64 double-precision BLAS provider under build/_deps. It needs no Fortran compiler or LAPACK build. Initial dependency downloads require network access. Wheel builds statically include the provider and use private symbol names, so installed wheels do not depend on that source/build directory.

On x86-64, a BLAS-enabled binary or wheel requires a CPU supporting SSE3 and SSSE3 (the CORE2 kernel baseline). The provider dispatches between CORE2 and Haswell kernels on supported CPUs. This requirement applies to the entire BLAS-enabled extension, including import; selecting the native kernel does not remove it. Other Linux architectures use the provider’s native build target and require a compatible CPU when relocating the wheel. These wheels are local platform builds; no manylinux portability is claimed.

Inspect cgpy.blas_enabled() and cgpy.build_info() in the process that will run the solve. See Python API for the runtime contract.

>>> A = np.array([[4.0, 1.0], [1.0, 3.0]])
>>> b = np.array([1.0, 2.0])
>>> result = cgpy.solve(A, b, dense_kernel="blas", num_threads=1)
>>> result.converged
True
>>> np.testing.assert_allclose(result.x, [1 / 11, 7 / 11])

A native-only build rejects the unavailable choice explicitly:

>>> try:
...     cgpy.solve(np.eye(2), np.ones(2), dense_kernel="blas")
... except ValueError:
...     rejected = True
... else:
...     rejected = False
>>> rejected
True

Wheel and source distributions

make wheel
make sdist
make dist
.venv/bin/python -m pip wheel --cache-dir .cache/pip --wheel-dir build/sdist-wheel dist/cgpy-*.tar.gz
make test-dist DIST_BLAS=OFF DIST_DIR=dist/native
make test-dist DIST_BLAS=ON DIST_DIR=dist/blas

make wheel builds a wheel with isolated backend dependencies, make sdist builds a source distribution, and make dist builds both through the source-distribution path. The pip command builds an sdist with isolation. make test-dist additionally installs that wheel into a separate temporary environment outside the source tree and checks dense/CSR APIs, native imports and public types against that installed environment. Both native stubs and the py.typed marker must survive the sdist-to-wheel path. The type checker runs in the development environment; it is not a runtime dependency of the installed wheel. Native and BLAS wheels have the same package version and wheel tag; use separate output directories to retain both artifacts. Runtime-only CPU installs need NumPy; Python 3.12 is required for every package mode.

CUDA builds use PRESET=cuda-release and DIST_CUDA=ON explicitly, with their own compatible local toolkit and runtime dependencies. See CUDA for complete commands and architecture/host-compiler overrides. CPU wheels retain CG_ENABLE_CUDA=OFF. Distribution checks require CUDA sources in the sdist, inspect installed ELF dependencies/RPATH, and execute GPU smoke solves when a usable device exists; GPUless installation checks report that skip.

Sphinx documentation

.venv/bin/python -m pip install --cache-dir .cache/pip -r requirements/docs.lock
make docs
make doctest

The HTML entry point is build/docs/html/index.html. The doctest builder executes native dense, CSR and prepared-solver examples; a BLAS build also executes the BLAS example above. Availability-specific groups use explicit runtime skip conditions; native-only builds test unavailable-mode rejection. Both builders treat warnings as errors. Autodoc imports the selected real extension through the same PYTHONPATH as the other Make targets, and Napoleon renders NumPy-style docstrings. No imports are mocked. Once dependencies and the extension are available, Sphinx requires no network access: the configuration uses no remote theme assets or intersphinx inventories.

GitHub Pages publishes the HTML built from main at the documentation site. The Documentation workflow builds the real CPU extension, checks Sphinx warnings and executes doctests on every pull request. After a merge to main, it uploads the HTML and deploys it to the github-pages environment. The repository’s Pages source is GitHub Actions; generated HTML stays out of Git. BLAS and CUDA documentation examples are additionally checked in their respective solver CI jobs.

Python version and dependency policy

Python support is intentionally limited to CPython 3.12.x. CI varies the execution/build configuration rather than testing unrelated interpreter versions. Use a current 3.12 patch release. Native-only builds use CG_BUILD_PYTHON=OFF and have no Python requirement.

The requirement files are explicit pinned pip inputs, maintained by review; there is no automatic lock generator. See dependency maintenance for clean-environment installation and update checks.