elli-gf2 0.1.0

Machine-first GF(2) elimination library with exact multi-backend reduction and auto-selection.
Documentation
from __future__ import annotations

import ctypes
from ctypes import c_uint32, c_uint64, c_double, c_int32, c_char_p
from dataclasses import dataclass
from pathlib import Path
from os import fspath


@dataclass(frozen=True)
class Result:
    rank: int
    strategy: str
    ok: bool


class _ElliGf2Result(ctypes.Structure):
    _fields_ = [
        ("rank", c_uint64),
        ("strategy", c_uint32),
        ("ok", c_int32),
    ]


def _candidate_lib_paths() -> list[Path]:
    here = Path(__file__).resolve()
    repo_root = here.parents[2]

    names = [
        "libelli_gf2.dylib",  # macOS
        "libelli_gf2.so",     # Linux
        "elli_gf2.dll",       # Windows
    ]

    paths = []
    for name in names:
        paths.append(repo_root / "target" / "release" / name)
        paths.append(repo_root / "target" / "debug" / name)
    return paths


def _load_lib() -> ctypes.CDLL:
    for path in _candidate_lib_paths():
        if path.exists():
            return ctypes.CDLL(str(path))
    searched = "\n".join(str(p) for p in _candidate_lib_paths())
    raise FileNotFoundError(
        "Could not find compiled elli-gf2 shared library.\n"
        "Build it first with: cargo build --release\n"
        f"Searched:\n{searched}"
    )


_lib = _load_lib()

_lib.elli_gf2_strategy_name.argtypes = [c_uint32]
_lib.elli_gf2_strategy_name.restype = c_char_p

_lib.elli_gf2_reduce_er_auto.argtypes = [c_uint64, c_uint64, c_double, c_uint64]
_lib.elli_gf2_reduce_er_auto.restype = _ElliGf2Result

_lib.elli_gf2_reduce_ldpc_auto.argtypes = [c_uint64, c_uint64, c_uint64, c_uint64]
_lib.elli_gf2_reduce_ldpc_auto.restype = _ElliGf2Result

_lib.elli_gf2_reduce_banded_auto.argtypes = [c_uint64, c_uint64, c_uint64, c_uint64, c_uint64]
_lib.elli_gf2_reduce_banded_auto.restype = _ElliGf2Result

_lib.elli_gf2_reduce_file_auto.argtypes = [c_char_p]
_lib.elli_gf2_reduce_file_auto.restype = _ElliGf2Result


def _decode_result(raw: _ElliGf2Result) -> Result:
    strategy = _lib.elli_gf2_strategy_name(raw.strategy)
    strategy_str = strategy.decode("utf-8") if strategy else "Unknown"
    return Result(
        rank=int(raw.rank),
        strategy=strategy_str,
        ok=bool(raw.ok),
    )


def reduce_er_auto(rows: int, cols: int, density: float, seed: int = 1337) -> Result:
    raw = _lib.elli_gf2_reduce_er_auto(rows, cols, density, seed)
    return _decode_result(raw)


def reduce_ldpc_auto(rows: int, cols: int, col_weight: int, seed: int = 1337) -> Result:
    raw = _lib.elli_gf2_reduce_ldpc_auto(rows, cols, col_weight, seed)
    return _decode_result(raw)


def reduce_banded_auto(
    rows: int,
    cols: int,
    bandwidth: int,
    weight: int,
    seed: int = 1337,
) -> Result:
    raw = _lib.elli_gf2_reduce_banded_auto(rows, cols, bandwidth, weight, seed)
    return _decode_result(raw)


def reduce_file_auto(path: str | Path) -> Result:
    raw = _lib.elli_gf2_reduce_file_auto(fspath(path).encode("utf-8"))
    return _decode_result(raw)