gridoxide 0.1.0

AC power flow and state estimation solver with a sparse Newton-Raphson core
Documentation
name: Rust build and test

on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]

env:
  CARGO_TERM_COLOR: always

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v4
    # The OPF tests and tests/injection_hessian_test.rs read the pglib-opf
    # cases from benchmark-grids and convert them with numpy
    # (tests/data/pglib_opf.py). CGMES-Test-Configurations stays unfetched.
    - name: Fetch benchmark-grids
      run: git submodule update --init --depth 1 tests/data/benchmark-grids
    - uses: actions/setup-python@v5
      with:
        python-version: "3.12"
    - name: Install numpy
      run: pip install numpy
    # Dependencies and the previous run's build, keyed on Cargo.lock. Without
    # it every run compiles every crate from scratch, once per profile.
    - uses: Swatinem/rust-cache@v2

    # **Each feature set CI covers is compiled on its own, and tested once,
    # together.** Testing them one at a time ran the default suite eight times
    # over and relinked every test binary for each feature set; what the
    # separate builds actually guard is a missing or wrong `#[cfg]`, which a
    # compile check — `--all-targets`, so tests and examples included — finds
    # just as well, with no codegen and no link.
    #
    # Not here at all: `klu` (a C compiler and libclang), `pardiso` (a
    # separately-licensed Intel oneMKL, `MKLROOT` set — not on hosted runners;
    # see docs/src/solvers/backends.md), `opf-highs` and `opf-ipopt` (system
    # libraries no runner has, so `tests/opf_cross_test.rs`, which holds HiGHS
    # against the in-house solver, runs where HiGHS is installed), and
    # `smallsignal-arpack`. Everything below is pure Rust or fetched by cargo:
    #
    # - `opf`: the interior-point QP solver in `src/opf/ipm.rs`, and `opf::bnb`
    #   branching over it for the mixed-integer path.
    # - `ucte`: fixed-column text, no dependency. Its suite checks that each
    #   network solves to pypowsybl's answer, committed as JSON under
    #   `tests/data/ucte/`, so the runner needs no pypowsybl.
    # - `iidm`: one crate (quick-xml). Checked with `ucte` too, because its
    #   strongest test reads the same network through both parsers.
    # - `dynamics`: pure Rust on this crate's sparse backends; small-signal
    #   analysis rides along on it.
    # - `rao`: serde, which the crate already has. Its external gate,
    #   `tests/rao_cucumber_test.rs`, reads powsybl-open-rao's own Cucumber
    #   suite from benchmark-grids and needs no Java.
    # - `capi`: pure Rust plus cbindgen.
    # - `matpower`: plain text, no dependency. Its suite reads the cases in
    #   benchmark-grids and checks each solution against the case's own
    #   equations.
    - name: Check each feature set on its own
      run: |
        for features in "" opf ucte iidm iidm,ucte dynamics dynamics,iidm \
                        rao rao,ucte,iidm capi matpower; do
          echo "::group::cargo check --features '$features'"
          cargo check --all-targets --features "$features"
          echo "::endgroup::"
        done
    # Feature *unification* is its own failure mode, which the checks above
    # cannot see: `iidm` and `cgmes` share quick-xml, and cimmodel enables its
    # `encoding` feature — which changes the shape of a public type. That
    # combination compiled in no single-feature step and broke in the one
    # nobody ran. This is that step.
    - name: Check every CI-covered feature together
      run: cargo check --all-targets --features cgmes,ucte,iidm,opf,rao,dynamics,capi,matpower
    # Tested on the union, *less `cgmes`*: its tests read
    # CGMES-Test-Configurations (158 MB), which CI does not fetch, and nine of
    # them fail rather than skip without it. CI has never run them; fetching
    # the submodule and adding `cgmes` here is what would change that.
    #
    # The integration tests are one binary (`tests/all.rs`), so this links once.
    - name: Test every CI-covered feature together
      run: cargo test --features ucte,iidm,opf,rao,dynamics,capi,matpower
    # The library is split into crates (see crates/), and a plain `cargo test`
    # at the root tests only the root package. The others' own unit tests —
    # the KLU port's among them — run here.
    - name: Test the workspace's other crates
      run: cargo test --workspace --exclude gridoxide
    - name: Build the dynamics scaling examples
      run: |
        cargo build --release --features dynamics --example dynamics_scale
        cargo build --release --features dynamics --example smallsignal_scale
    # The two example programs are the part `cargo test` cannot cover. They are
    # what proves the generated header actually parses as C11 and C++17, that
    # the `#[repr(C)]` layouts agree with what a real compiler computes, and
    # that the exported symbols link — none of which a Rust test can check,
    # since it never sees the header at all.
    - name: Compile and run the C and C++ examples
      run: |
        cargo build --release --features capi
        cc  -std=c11   -Wall -Wextra -Werror -Iinclude \
            capi/examples/powerflow.c   target/release/libgridoxide.a \
            -o /tmp/powerflow_c   -lpthread -ldl -lm
        c++ -std=c++17 -Wall -Wextra -Werror -Iinclude \
            capi/examples/powerflow.cpp target/release/libgridoxide.a \
            -o /tmp/powerflow_cpp -lpthread -ldl -lm
        python tests/data/pglib_opf.py /tmp/pglib-opf
        /tmp/powerflow_c   /tmp/pglib-opf/pglib_opf_case14_ieee.json
        /tmp/powerflow_cpp /tmp/pglib-opf/pglib_opf_case14_ieee.json
    # The header is committed so a prebuilt-artifact consumer needs no Rust
    # toolchain. Nothing keeps it in step with the code unless something checks,
    # and building with `capi` regenerates it — so a diff here means a
    # contributor changed the ABI without committing the regenerated header.
    - name: Check the committed header is not stale
      run: git diff --exit-code -- include/gridoxide.h