Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/actions/setup-tools/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ runs:
shell: bash
env:
RESEARCH_REPO_TOOLS_HOME: ${{ runner.temp }}/rrt
run: uv run --locked --managed-python --only-group tooling research-repo-tools setup
run: uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup

- name: Export verified managed paths
shell: bash
Expand Down
183 changes: 120 additions & 63 deletions .github/workflows/release-benchmarks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Install trusted package runner
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: "0.13.1"
version: "0.13.6"
enable-cache: false
cache-python: false

Expand All @@ -54,114 +54,171 @@ jobs:
echo "::error::Dispatch with --ref $RELEASE_TAG and the matching tag input"
exit 1
fi
target="$(uvx --python 3.15 --from research-repo-tools==0.2.3 research-repo-tools \
target="$(uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
--root "$RUNNER_TEMP" performance release-draft "$GH_REPO" "$RELEASE_TAG" \
--commit "$GITHUB_SHA" --expected-title "$RELEASE_TAG" | jq -c .)"
echo "target=$target" >> "$GITHUB_OUTPUT"

release-baseline:
measure-baseline:
needs: validate-release
runs-on: ubuntu-latest
# 2 min checkout + 60 min shared setup + 140 min default + 130 min exact
# + 18 min for the tail and runner overhead; below the hosted 360 min limit.
timeout-minutes: 350
strategy:
fail-fast: false
matrix:
suite: [public-default, public-exact]
# Per runner: 2 min checkout + 60 min preparation + 2 min host capture
# + 240 min measurement + 26 min packaging, upload and overhead.
timeout-minutes: 330
env:
RELEASE_TAG: ${{ inputs.tag }}
BENCH_SUITE: ${{ matrix.suite }}
CRITERION_HOME: ${{ github.workspace }}/target/criterion
outputs:
release-asset: ${{ steps.package-baseline.outputs.asset }}
artifact-name: ${{ steps.package-baseline.outputs.artifact-name }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Use the workflow's own commit and tag cache scope, never a separate ref.
ref: ${{ github.sha }}
persist-credentials: false
timeout-minutes: 2

- name: Prepare release benchmarks
# One parent timeout bounds all nested installs, validation, and inventory.
timeout-minutes: 60
uses: ./.github/actions/prepare-release-benchmarks # zizmor: ignore[self-repository] actionlint 1.7.12 does not accept $/...
with:
validated-attempt: ${{ needs.validate-release.outputs.validated-attempt }}

- name: Save default-feature Criterion baseline
id: comparative
timeout-minutes: 140
run: |
set -euo pipefail
echo "[release-baseline] public-default started $(date -u +%FT%TZ); budget 140 min"
date +%s > target/public-default-started
just bench-save-baseline "$RELEASE_TAG" public-default
date +%s > target/public-default-finished

- name: Save exact Criterion baseline
id: exact
timeout-minutes: 130
- name: Capture runner host
timeout-minutes: 2
run: uv run --locked --no-sync research-repo-tools performance host --output target/release-host.json

- name: Save Criterion suite
id: measurement
timeout-minutes: 240
run: |
set -euo pipefail
echo "[release-baseline] public-exact started $(date -u +%FT%TZ); budget 130 min"
date +%s > target/public-exact-started
just bench-save-baseline "$RELEASE_TAG" public-exact
date +%s > target/public-exact-finished
echo "[release-baseline] $BENCH_SUITE started $(date -u +%FT%TZ); budget 240 min"
date +%s > "target/$BENCH_SUITE-started"
just bench-save-baseline "$RELEASE_TAG" "$BENCH_SUITE"
date +%s > "target/$BENCH_SUITE-finished"

- name: Validate complete release dataset
run: just bench-release-check "$RELEASE_TAG"

- name: Package release Criterion baseline
id: package-baseline
- name: Package Criterion suite
run: |
set -euo pipefail
output="target/release-suites/$BENCH_SUITE"
features=bench
if [[ "$BENCH_SUITE" == public-exact ]]; then
features=bench,exact
fi
uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools performance pack-suite \
tooling/criterion-saved.toml "$BENCH_SUITE" --saved-sample "$RELEASE_TAG" \
--output "$output/criterion.tar.gz" --revision "$GITHUB_SHA" \
--session "$GITHUB_REPOSITORY:$GITHUB_RUN_ID:$GITHUB_RUN_ATTEMPT" \
--host target/release-host.json \
--started-at "$(cat "target/$BENCH_SUITE-started")" \
--finished-at "$(cat "target/$BENCH_SUITE-finished")" \
--context "command=just bench-save-baseline $RELEASE_TAG $BENCH_SUITE" \
--context "features=$features" --context "runner=$RUNNER_NAME" \
--context "image=$ImageOS" --context "image_version=$ImageVersion" \
--context "rustc=$(rustc -vV | paste -sd ';' -)" \
--context "cargo=$(cargo --version)" \
--context "cargo_lock_sha256=$(sha256sum Cargo.lock | cut -d ' ' -f 1)"
# Transport the native inventory separately for the assembly plan.
# Both archives must match it and the checked-in required case policy.
if [[ "$BENCH_SUITE" == public-default ]]; then
cp target/release-benchmark-inventory.json "$output/inventory.json"
fi

asset="la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz"
cp target/release-benchmark-inventory.json target/criterion/release-benchmark-inventory.json
tar -C target -czf "$asset" criterion
echo "asset=$asset" >> "$GITHUB_OUTPUT"
echo "artifact-name=bench-baseline-${RELEASE_TAG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"

- name: Upload temporary baseline artifact
- name: Upload temporary suite artifact
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ steps.package-baseline.outputs.artifact-name }}
path: ${{ steps.package-baseline.outputs.asset }}
name: bench-suite-${{ inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}-${{ matrix.suite }}
path: target/release-suites/${{ matrix.suite }}
retention-days: 30
if-no-files-found: error

- name: Suite timing summary
if: ${{ always() }}
env:
COMPARATIVE_OUTCOME: ${{ steps.comparative.outcome }}
EXACT_OUTCOME: ${{ steps.exact.outcome }}
SUITE_OUTCOME: ${{ steps.measurement.outcome }}
run: |
set -euo pipefail
elapsed="not started"
if [[ -f "target/$BENCH_SUITE-started" ]]; then
end="$(date +%s)"
if [[ -f "target/$BENCH_SUITE-finished" ]]; then
end="$(cat "target/$BENCH_SUITE-finished")"
fi
elapsed="$((end - $(cat "target/$BENCH_SUITE-started")))"
fi
{
echo "### Release benchmark timing"
echo ""
echo "| Suite | Outcome | Elapsed seconds | Budget minutes |"
echo "| --- | --- | --- | --- |"
for suite in public-default public-exact; do
elapsed="not started"
if [[ -f "target/$suite-started" ]]; then
end="$(date +%s)"
if [[ -f "target/$suite-finished" ]]; then
end="$(cat "target/$suite-finished")"
fi
elapsed="$((end - $(cat "target/$suite-started")))"
fi
outcome="$COMPARATIVE_OUTCOME"
budget=140
if [[ "$suite" == public-exact ]]; then
outcome="$EXACT_OUTCOME"
budget=130
fi
echo "| $suite | $outcome | $elapsed | $budget |"
done
echo "| $BENCH_SUITE | $SUITE_OUTCOME | $elapsed | 240 |"
echo ""
echo "An unfinished suite reports elapsed time through this summary."
} | tee -a "$GITHUB_STEP_SUMMARY"

release-baseline:
needs: [validate-release, measure-baseline]
runs-on: ubuntu-latest
timeout-minutes: 30
env:
RELEASE_TAG: ${{ inputs.tag }}
outputs:
release-asset: ${{ steps.package-baseline.outputs.asset }}
artifact-name: ${{ steps.package-baseline.outputs.artifact-name }}

steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.sha }}
persist-credentials: false

- name: Install trusted package runner
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: "0.13.6"
enable-cache: false
cache-python: false

- name: Download default suite
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: bench-suite-${{ inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}-public-default
path: target/release-suites/public-default

- name: Download exact suite
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
name: bench-suite-${{ inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}-public-exact
path: target/release-suites/public-exact

- name: Assemble release Criterion baseline
id: package-baseline
run: |
set -euo pipefail

asset="la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz"
cp target/release-suites/public-default/inventory.json target/release-benchmark-inventory.json
uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
--root "$GITHUB_WORKSPACE" performance assemble-suites tooling/criterion-saved.toml \
target/release-suites/public-default/criterion.tar.gz \
target/release-suites/public-exact/criterion.tar.gz \
--saved-sample "$RELEASE_TAG" --output "$asset" --revision "$GITHUB_SHA" \
--session "$GITHUB_REPOSITORY:$GITHUB_RUN_ID:$GITHUB_RUN_ATTEMPT"
echo "asset=$asset" >> "$GITHUB_OUTPUT"
echo "artifact-name=bench-baseline-${RELEASE_TAG}-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}" >> "$GITHUB_OUTPUT"

- name: Upload temporary baseline artifact
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: ${{ steps.package-baseline.outputs.artifact-name }}
path: ${{ steps.package-baseline.outputs.asset }}
retention-days: 30
if-no-files-found: error

publish-baseline:
needs: [validate-release, release-baseline]
permissions:
Expand All @@ -173,7 +230,7 @@ jobs:
- name: Install trusted package runner
uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
version: "0.13.1"
version: "0.13.6"
enable-cache: false
cache-python: false

Expand All @@ -194,7 +251,7 @@ jobs:

[[ "$RELEASE_ASSET" == "la-stack-${RELEASE_TAG}-criterion-baseline.tar.gz" ]] || exit 1
printf '%s\n' "$RELEASE_TARGET" > "$RUNNER_TEMP/release-target.json"
uvx --python 3.15 --from research-repo-tools==0.2.3 research-repo-tools \
uvx --python 3.15 --from research-repo-tools==0.2.4 research-repo-tools \
--root "$RUNNER_TEMP" performance release-upload "$GH_REPO" "$RELEASE_TAG" \
"$GITHUB_WORKSPACE/$RELEASE_ASSET" --target "$RUNNER_TEMP/release-target.json"

Expand Down
10 changes: 6 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ exact version declared in `pyproject.toml`, and `jq`. Authenticate the GitHub CL
the declared Python, Rust, Cargo tools, and Just through the locked PyPI package:

```bash
uv run --locked --managed-python --only-group tooling research-repo-tools setup
uv run --locked --managed-python --only-group tooling python tooling/shared_tooling.py setup
```

Set up the remaining development tools and validate the checkout:
Expand All @@ -39,7 +39,7 @@ change files. Run bare `just` for the complete generated command list, including
arguments and descriptions. The [command policy](docs/dev/docs.md#command-discovery-and-placement)
owns recipe naming and documentation placement.

Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.2.3` package,
Setup, tool checks, updates, changelog, and opt-in CodeRabbit review use the published `research-repo-tools==0.2.4` package,
locked in the `tooling` dependency group and included by `dev`. Its `python-tools`
extra owns the pytest, Ruff and Ty versions.
`toolchain.inherit-python-tools` checks those versions against the lock and
Expand Down Expand Up @@ -71,12 +71,14 @@ Then `just update-dependencies` upgrades Cargo requirements and refreshes Cargo'
lock, resolves exact direct Python `dev` pins, refreshes the complete `uv.lock`,
and explicitly synchronizes `dev` with managed Rust available for native builds.
Included groups retain their constraints, including the exact shared-package pin.
Semgrep shares the root Python 3.15 environment and lockfile. Preserve its exact
Semgrep scans and fixture checks share the root Python 3.15 environment and lockfile. Preserve its exact
pin, native-wheel requirement and compatibility overrides for MCP, PyJWT,
OpenTelemetry and Python 3.15 Windows wheels. Scanner commands retain the Linux
stack-limit workaround required by Semgrep on Python 3.15. The hosted dependency
audit checks the root Python lockfile with the shared OSV adapter; its existing
Cargo audit retains the native Rust advisory policy.
Cargo audit retains the native Rust advisory policy. The shared fixture checker
uses that same installed scanner; see the
[tooling guide](tooling/README.md#setup-and-validation).
`just update-cargo-dependencies` excludes `num-bigint` and `num-rational` from
incompatible requirement upgrades because their public types must advance
together; Cargo can still refresh locked versions within those requirements.
Expand Down
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ required-features = [ "exact" ]
name = "rational_input_5x5"
required-features = [ "exact" ]

[[example]]
name = "polynomial_determinant"
required-features = [ "exact" ]

[[bench]]
name = "public_api"
harness = false
Expand Down Expand Up @@ -121,6 +125,11 @@ name = "rational_dot"
harness = false
required-features = [ "bench", "exact" ]

[[bench]]
name = "polynomial"
harness = false
required-features = [ "bench", "exact" ]

[profile.release]
lto = "fat"
codegen-units = 1
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ encloses exact-real values during a small set of outward-rounded operations and
feeds `IntervalMatrix<D>` determinant proofs; it does not make `Matrix` generic
over alternate scalars or provide a general interval package.

With `exact`, `SparsePolynomial` supplies rational coefficients keyed by `u128`
exponents for bounded polynomial determinants through order eight. Its first
nonzero term uses increasing exponent order; callers own perturbation encoding
and work limits. This focused kernel retains cancellation exactly.

This is not a generic scalar-parameterized API. Exact support intentionally
covers the robustness-sensitive operations that require it: determinant sign,
determinant value, and linear solve, followed by explicit strict or rounded
Expand All @@ -198,6 +203,7 @@ to combine operations.
| Certified dot, affine-difference, and determinant estimates | [`ScalarWithErrorBound`][api-scalar-bound], [`DeterminantWithErrorBound`][api-det-bound] |
| Exact rational dot products¹ | [`RationalVector::dot`][api-rational-dot], [`rational_dot`][api-rational-dot-slice] |
| Exact signs, determinants, solves, and output conversion¹ | [Exact arithmetic examples][api-exact] |
| Exact sparse polynomial determinants¹ | [`polynomial_determinant`][api-polynomial], [`SparsePolynomial`][api-sparse-polynomial] |
| Floating-point determinants and solves | [`Matrix<D>`][api-matrix], [`Lu<D>`][api-lu], [`Ldlt<D>`][api-ldlt] |
| Gram matrix construction | [`gram_matrix`][api-gram] |
| Interval expressions, determinant signs, and solution enclosures | [`Interval`][api-interval], [`IntervalMatrix<D>`][api-interval-matrix] |
Expand Down Expand Up @@ -431,6 +437,7 @@ The `examples/` directory contains small, runnable programs:
- **[`exact_sign_3x3`][example-exact-sign]** — exact determinant sign of a near-singular 3×3 matrix (requires `exact` feature)
- **[`exact_solve_3x3`][example-exact-solve]** — exact solve of a near-singular 3×3 system vs f64 LU (requires `exact` feature)
- **[`ldlt_solve_3x3`][example-ldlt-solve]** — solve a 3×3 symmetric positive definite system via LDLT
- **[`polynomial_determinant`][example-polynomial]** — exact cancellation, leading coefficients, and explicit work limits (requires `exact` feature)
- **[`rational_input_5x5`][example-rational-input]** — exact rational solve of a 5×5 system that becomes singular as f64 (requires `exact` feature)
- **[`solve_5x5`][example-solve]** — solve a 5×5 system via LU with partial pivoting
- **[`vector_angles`][example-vector-angles]** — unsigned angles for fixed vectors and borrowed slices, including extreme magnitudes and subnormal separations
Expand All @@ -441,6 +448,7 @@ The `examples/` directory contains small, runnable programs:
[example-exact-sign]: https://github.com/acgetchell/la-stack/blob/main/examples/exact_sign_3x3.rs
[example-exact-solve]: https://github.com/acgetchell/la-stack/blob/main/examples/exact_solve_3x3.rs
[example-ldlt-solve]: https://github.com/acgetchell/la-stack/blob/main/examples/ldlt_solve_3x3.rs
[example-polynomial]: https://github.com/acgetchell/la-stack/blob/main/examples/polynomial_determinant.rs
[example-rational-input]: https://github.com/acgetchell/la-stack/blob/main/examples/rational_input_5x5.rs
[example-solve]: https://github.com/acgetchell/la-stack/blob/main/examples/solve_5x5.rs
[example-vector-angles]: https://github.com/acgetchell/la-stack/blob/main/examples/vector_angles.rs
Expand Down Expand Up @@ -572,6 +580,8 @@ BSD 3-Clause License. See [LICENSE](https://github.com/acgetchell/la-stack/blob/

[audit-badge]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml/badge.svg
[api-column-basis]: https://docs.rs/la-stack/latest/la_stack/fn.column_basis.html
[api-polynomial]: https://docs.rs/la-stack/latest/la_stack/fn.polynomial_determinant.html
[api-sparse-polynomial]: https://docs.rs/la-stack/latest/la_stack/struct.SparsePolynomial.html
[api-rational-dot]: https://docs.rs/la-stack/latest/la_stack/struct.RationalVector.html#method.dot
[api-rational-dot-slice]: https://docs.rs/la-stack/latest/la_stack/fn.rational_dot.html
[audit-workflow]: https://github.com/acgetchell/la-stack/actions/workflows/audit.yml
Expand Down
Loading
Loading