diff --git a/.semgrepignore b/.semgrepignore
new file mode 100644
index 0000000..93e119f
--- /dev/null
+++ b/.semgrepignore
@@ -0,0 +1,28 @@
+# Preserve Semgrep 1.180.0 defaults except test/, tests/, and testsuite/ so
+# ordinary Rust and Python tests remain in production scans.
+# https://github.com/semgrep/semgrep/blob/v1.180.0/src/targeting/default.semgrepignore
+.git
+.svn
+.hg
+_darcs
+CVS
+
+build/
+vendor/
+dist/
+*.min.js
+.env/
+.tox/
+
+node_modules/
+.npm/
+.yarn/
+.venv/
+_opam/
+_build/
+_cargo/
+
+*_test.go
+
+# Deliberately invalid rule fixtures are checked separately by just semgrep-test.
+/tests/semgrep/
diff --git a/Cargo.lock b/Cargo.lock
index b970439..77655e8 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -148,9 +148,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.8.0"
+version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6651c9ed80effdc7db0ff72512157f901af5e3549e341e24b1dd4887d836d838"
+checksum = "877bf67ac8e52449eb2f9e98a344ea163beb70dd1f9eebe3cc5357dcfeaee4df"
dependencies = [
"find-msvc-tools",
"shlex",
@@ -449,9 +449,9 @@ checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
[[package]]
name = "find-msvc-tools"
-version = "0.1.14"
+version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
+checksum = "1c66915cc5391097de7a2b0b972e8505aa33c0df401fd3a6811dbdb32881ba48"
[[package]]
name = "fnv"
diff --git a/REFERENCES.md b/REFERENCES.md
index fedd910..f1c02e8 100644
--- a/REFERENCES.md
+++ b/REFERENCES.md
@@ -79,8 +79,10 @@ original coordinates rather than an already-rounded difference vector.
`det_sign_exact()` uses a Shewchuk-style f64 error-bound filter \[[8]\] (the same bound exposed
by `det_errbound()` above) backed by exact `BigInt` arithmetic. Each f64 entry is decomposed
-into `mantissa × 2^exponent` and scaled to a common integer base. Dimensions 0–4 use direct
-integer determinant expansions; D ≥ 5 uses integer-only Bareiss elimination \[[7]\]. Neither
+into `mantissa × 2^exponent`. Each row uses its own minimum nonzero exponent `e_i`
+to form an integer row; the determinant is reconstructed as `det_int × 2^(Σ e_i)`.
+Zero rows use exponent zero. Dimensions 0–4 use direct integer determinant
+expansions; D ≥ 5 uses integer-only Bareiss elimination \[[7]\]. Neither
path constructs `BigRational` values or performs GCD normalization.
See `src/exact.rs` for the full architecture description.
@@ -136,8 +138,9 @@ Both the determinant and solve paths convert their finite-by-construction entrie
`decompose_proven_finite_f64`, which extracts the IEEE 754 binary64 sign, unbiased exponent,
and significand \[[9]\]. For nonzero `x`, it strips trailing zeros from the
significand so `|x| = m · 2^e` with `m` odd; signed zeros use a separate zero
-component. The integer matrix is then assembled by shifting each mantissa left by
-`exp − e_min`, giving a GCD-free exact-integer starting point. Solves and D ≥ 5 determinants
+component. Shifting each mantissa left by `exp − e_min` gives a GCD-free
+exact-integer starting point. Determinants choose `e_min` separately for each row;
+solves use the matrix and RHS scales described above. Solves and D ≥ 5 determinants
then apply Bareiss elimination; D ≤ 4 determinants use direct expansions. Tests verify
non-finite input rejection at the `Matrix` and `Vector` constructors. The test-only
`f64_to_big_rational` helper uses `BigRational::from_f64` as an independent oracle for
diff --git a/docs/BENCHMARKING.md b/docs/BENCHMARKING.md
index 15ed8e1..0401b90 100644
--- a/docs/BENCHMARKING.md
+++ b/docs/BENCHMARKING.md
@@ -845,8 +845,10 @@ measured 304 comparative benchmarks in about 55m 34s, plus 2m 9s compilation
that run provides no complete exact-suite runtime. The old 60-minute outer
limit therefore could not accommodate even that smaller harness.
-Inventory on 2026-09-07 found the following current workloads. The planning
-estimate uses 12 seconds per benchmark, allowing analysis/report overhead
+The comparative inventory on 2026-09-07 contained 533 cases. The current exact
+suite contains 354 cases: 264 original cases plus 90 diagnostic cases retained
+by the [row-scaling study](archive/performance/studies/exact-row-scaling.md).
+The planning estimate uses 12 seconds per benchmark, allowing analysis/report overhead
above the configured 3-second warmup and 5-second measurement target. This is
a capacity estimate, not a measured runtime or a guaranteed upper bound;
Criterion can extend measurement for expensive iterations.
@@ -854,7 +856,7 @@ Criterion can extend measurement for expensive iterations.
| Suite | Benchmarks | Nominal warmup + measurement | Planning estimate | Step limit | Headroom above estimate |
| --- | --- | --- | --- | --- | --- |
| `vs_linalg` | 533 | 71.1 min | 106.6 min | 150 min | 43.4 min (41%) |
-| `exact` | 264 | 35.2 min | 52.8 min | 90 min | 37.2 min (70%) |
+| `exact` | 354 | 47.2 min | 70.8 min | 90 min | 19.2 min (27%) |
Both commands retain Criterion's release defaults: 100 samples, 100,000
resamples, 95% confidence, 3-second warmup, and 5-second measurement target.
diff --git a/docs/MEASURING_COVERAGE.md b/docs/MEASURING_COVERAGE.md
index 3328e1d..d7f1d7d 100644
--- a/docs/MEASURING_COVERAGE.md
+++ b/docs/MEASURING_COVERAGE.md
@@ -45,9 +45,10 @@ Nextest also writes JUnit test results to:
target/nextest/coverage/test-results/junit.xml
```
-The Codecov workflow reads the `cargo-llvm-cov` and `cargo-nextest` versions
-from the `justfile`, installs those exact versions plus Rust's
-`llvm-tools-preview` component, and runs `just coverage-ci`. It verifies both
+The Codecov workflow uses the shared tool setup to install the `cargo-llvm-cov`
+and `cargo-nextest` versions declared in `pyproject.toml` under
+`[tool.research-repo-tools.toolchain.cargo]`, plus the `llvm-tools-preview`
+component declared in `rust-toolchain.toml`, and runs `just coverage-ci`. It verifies both
reports, uploads the Cobertura and JUnit files to Codecov, and archives each
report directory as a workflow artifact. Local setup via `just setup-tools`
installs the same Rust component and cargo subcommands.
diff --git a/docs/code_organization.md b/docs/code_organization.md
index c539f3a..e1a9d02 100644
--- a/docs/code_organization.md
+++ b/docs/code_organization.md
@@ -211,6 +211,10 @@ dispositions, Python 3.15 review and native validation gates.
`tooling/semgrep/` owns a locked Python 3.14 environment for Semgrep 1.180.0
while its wheels exclude Python 3.15. Local scans, shared fixture validation
and hosted SARIF use it; ordinary support scripts use Python 3.15.
+The root `.semgrepignore` keeps ordinary Rust and Python tests in those scans
+while excluding deliberate `tests/semgrep/` fixtures, which `just semgrep-test`
+validates separately. Git-ignored dependencies, environments, caches, and build
+outputs remain outside production scans.
`.github/actions/prepare-release-benchmarks/action.yml` groups tool installation,
input validation, and inventory under the release workflow's shared setup timeout.
diff --git a/docs/roadmap.md b/docs/roadmap.md
index 256cf10..10b0e21 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -10,9 +10,10 @@ Until v1.0, API breaks are acceptable when they improve correctness,
performance, or orthogonality. The `v0.4.x` line stays on stable Rust and includes
focused robustness APIs, downstream ergonomics, API-contract cleanup,
documentation, validation, invariant audits, benchmark coverage, and
-release/tooling hardening. The current release is `v0.4.6`.
-The next planned release is `v0.5.0`, consolidating the work previously targeted
-at `v0.4.7`. It remains on stable Rust and makes the Rust 1.99.1 minimum an
+release/tooling hardening. The current release is
+[`v0.5.0`](https://github.com/acgetchell/la-stack/releases/tag/v0.5.0), published
+on 2026-10-17, consolidating the work previously targeted at `v0.4.7`.
+It remains on stable Rust and makes the Rust 1.99.1 minimum an
explicit downstream upgrade boundary.
Expanded const-generic work is tracked separately without an assigned release;
it depends on the specific language capabilities required by the proposed API.
@@ -237,13 +238,13 @@ storage remains available through `as_array()` and `into_array()`.
Further stable-Rust maintenance can remain in `v0.4.x`; it does not depend on
expanded const-generic support.
-### v0.5.0 Stable Rust APIs and Performance (planned)
+### v0.5.0 Stable Rust APIs and Performance (released)
The [v0.5.0 milestone](https://github.com/acgetchell/la-stack/milestone/3)
-replaces the planned `v0.4.7` release. It combines the completed Rust 1.99
+replaced the planned `v0.4.7` release. It combined the completed Rust 1.99
migration and stable vector-angle API with correctness-preserving performance
improvements and release-tooling adoption. Expanded const-generics stabilization
-does not gate this release.
+did not gate this release.
Completed work included in this release:
@@ -279,28 +280,23 @@ API or feature is planned. The
decision, executed checks, and unperformed native Linux/Windows runs; the
[study](archive/performance/studies/algebraic-float.md) retains the raw evidence.
-Remaining release sequence:
+The release sequence is complete:
-1. Review and land the triangular rational-solve optimization
+1. The triangular rational-solve optimization
([#246](https://github.com/acgetchell/la-stack/issues/246),
- [draft PR #278](https://github.com/acgetchell/la-stack/pull/278)), retaining the
+ [PR #278](https://github.com/acgetchell/la-stack/pull/278)) landed with the
exact general-system fallback and independent regression evidence.
-2. Adopt the published research-repo-tools v0.1.9 capabilities
- ([#276](https://github.com/acgetchell/la-stack/issues/276)) after the upstream
- [publication gate](https://github.com/acgetchell/research-repo-tools/issues/99)
- verifies the registry release. Preserve the dimension plot, scientific
- eligibility rules, and historical artifacts.
-3. Complete the release validation and `v0.4.6` versus `v0.5.0` performance record
- through the validated adopted workflow. Configure and verify the crates.io
- publishing workflow and publisher settings
- ([#259](https://github.com/acgetchell/la-stack/issues/259)) before publication.
-4. Publish the reviewed `v0.5.0` release and verify crates.io availability and a
- clean downstream dependency resolution. Close #259 only after actual
- publication succeeds and its evidence is recorded.
-
-Consumers using a `0.4` Cargo requirement must explicitly adopt `0.5` after the
-release is available. Delaunay's adoption issues remain scheduled for `v0.8.3`
-and retain their independent correctness and performance gates. Package versions
+2. The published shared-tooling capabilities were adopted
+ ([#276](https://github.com/acgetchell/la-stack/issues/276)), preserving the
+ dimension plot, scientific eligibility rules, and historical artifacts.
+3. Release validation and the `v0.4.6` versus `v0.5.0` performance record completed
+ through the adopted workflow. The crates.io publishing workflow and publisher
+ settings were verified ([#259](https://github.com/acgetchell/la-stack/issues/259)).
+4. The reviewed `v0.5.0` release was published and registry availability verified;
+ #259 closed after publication on 2026-10-17.
+
+Consumers using a `0.4` Cargo requirement must explicitly adopt `0.5`.
+Downstream adoption retains independent correctness and performance gates. Package versions
and generated release artifacts are updated during dedicated release preparation.
### v0.5.1 Follow-Up Work (planned)
diff --git a/justfile b/justfile
index 2accb5b..9407319 100644
--- a/justfile
+++ b/justfile
@@ -496,7 +496,7 @@ security-secrets:
# Repository-owned Semgrep rules for project-specific diagnostics.
semgrep: _ensure-uv
- uv run --locked --project tooling/semgrep semgrep --metrics off --error --strict --timeout 30 --exclude tests/semgrep/src/project_rules/algebraic_float.rs --config semgrep.yaml .
+ uv run --locked --project tooling/semgrep semgrep --metrics off --error --strict --timeout 30 --config semgrep.yaml .
# Fixture tests for repository-owned Semgrep rules.
semgrep-test: python-sync
diff --git a/pyproject.toml b/pyproject.toml
index bd46a81..d6234ea 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -88,7 +88,7 @@ line-length = 160
[tool.uv]
package = false
-required-version = "==0.13.1"
+required-version = "==0.13.2"
# Semgrep has its own locked Python 3.14 environment under tooling/semgrep.
[dependency-groups]
diff --git a/scripts/tests/test_maintenance_integration.py b/scripts/tests/test_maintenance_integration.py
index c791424..257872b 100644
--- a/scripts/tests/test_maintenance_integration.py
+++ b/scripts/tests/test_maintenance_integration.py
@@ -139,3 +139,76 @@ def test_semgrep_keeps_its_current_version_outside_the_python_315_environment()
assert scanner["project"]["requires-python"] == ">=3.14,<3.15"
result = run_command("uv", ["run", "--locked", "--project", "tooling/semgrep", "semgrep", "--version"], cwd=REPO_ROOT)
assert result.stdout.strip() == "1.180.0"
+
+
+@pytest.mark.parametrize("output_args", [pytest.param((), id="local"), pytest.param(("--sarif",), id="sarif")])
+def test_semgrep_production_targets_include_tests_and_exclude_rule_fixtures(tmp_path: Path, output_args: tuple[str, ...]) -> None:
+ """Exercise both scan formats without Git ignores masking Semgrep exclusions."""
+ shutil.copyfile(REPO_ROOT / ".semgrepignore", tmp_path / ".semgrepignore")
+ included = {
+ "tests/proptest_exact.rs",
+ "benches/comparison/tests/vs_linalg_inputs.rs",
+ "scripts/tests/test_release_baseline.py",
+ ".github/workflows/semgrep-sarif.yml",
+ "test/control.rs",
+ "testsuite/control.py",
+ "src/control.go",
+ "assets/control.js",
+ }
+ excluded = {
+ f"{directory}/ignored.py"
+ for directory in (
+ ".svn",
+ ".hg",
+ "_darcs",
+ "CVS",
+ "build",
+ "vendor",
+ "dist",
+ ".env",
+ ".tox",
+ "node_modules",
+ ".npm",
+ ".yarn",
+ ".venv",
+ "_opam",
+ "_build",
+ "_cargo",
+ "tests/semgrep",
+ "nested/dist",
+ "nested/.env",
+ "nested/_build",
+ )
+ } | {"assets/bundle.min.js", "src/example_test.go", "nested/example_test.go"}
+ for name in included | excluded:
+ path = tmp_path / name
+ path.parent.mkdir(parents=True, exist_ok=True)
+ comment = "#" if path.suffix in {".py", ".yml"} else "//"
+ path.write_text(f"{comment} Target selection fixture.\n", encoding="utf-8")
+ result = run_command(
+ "uv",
+ [
+ "run",
+ "--locked",
+ "--project",
+ str(REPO_ROOT / "tooling/semgrep"),
+ "semgrep",
+ "scan",
+ "--metrics",
+ "off",
+ "--disable-version-check",
+ "--error",
+ "--strict",
+ "--timeout",
+ "30",
+ "--config",
+ str(REPO_ROOT / "semgrep.yaml"),
+ *output_args,
+ "--x-ls-long",
+ ".",
+ ],
+ cwd=tmp_path,
+ )
+ selected = {Path(line.removeprefix("selected ")).as_posix() for line in result.stdout.splitlines() if line.startswith("selected ")}
+ assert included <= selected
+ assert not (excluded & selected)
diff --git a/scripts/tests/test_performance_workflow.py b/scripts/tests/test_performance_workflow.py
index a374f51..e12b385 100644
--- a/scripts/tests/test_performance_workflow.py
+++ b/scripts/tests/test_performance_workflow.py
@@ -309,7 +309,8 @@ def assert_readme_presentation(readme: str, statistic: str) -> None:
assert f"Statistic: {statistic}. Unit: ns." in table
current, peer, ratio = ("12
[10.8, 13.2]", "20
[18, 22]", "1.66667") if statistic == "mean" else ("8
[7.2, 8.8]", "10
[9, 11]", "1.25")
faer, faer_ratio = ("32
[28.8, 35.2]", "2.66667") if statistic == "mean" else ("16
[14.4, 17.6]", "2")
- assert f"| 2 | {current} | {peer} | {faer} | {ratio}\N{MULTIPLICATION SIGN} | {faer_ratio}\N{MULTIPLICATION SIGN} |" in table
+ multiplication_sign = "\N{MULTIPLICATION SIGN}"
+ assert f"| 2 | {current} | {peer} | {faer} | {ratio}{multiplication_sign} | {faer_ratio}{multiplication_sign} |" in table
assert "ratios are point estimates, with no ratio intervals or significance claims" in table
for name in ("nalgebra", "faer"):
assert f"Speedup vs {name}: ratio; numerator = {name} (" in table
@@ -620,7 +621,8 @@ def test_native_command_policy_and_compatibility_adapter() -> None:
def test_native_inventory_and_phase_commands_use_the_measured_checkout(tmp_path: Path, monkeypatch: pytest.MonkeyPatch, baseline_tag: str, scope: str) -> None:
"""Exercise the configured Justfile, stubbing only the expensive Cargo boundary."""
root = tmp_path / "checkout with spaces"
- shutil.copytree(ROOT / "tooling", root / "tooling")
+ # Native benchmark policy needs the root configs, not the scanner environment.
+ shutil.copytree(ROOT / "tooling", root / "tooling", ignore=shutil.ignore_patterns("semgrep"))
shutil.copyfile(ROOT / "rust-toolchain.toml", root / "rust-toolchain.toml")
binary = tmp_path / "bin"
binary.mkdir()
diff --git a/scripts/tests/test_release_baseline.py b/scripts/tests/test_release_baseline.py
index fd5d605..2a10a65 100644
--- a/scripts/tests/test_release_baseline.py
+++ b/scripts/tests/test_release_baseline.py
@@ -11,6 +11,7 @@
import textwrap
import tomllib
from pathlib import Path
+from urllib.parse import urlsplit
import pytest
from research_repo_tools.criterion_inventory import CriterionInventory, load_discovery, serialize_inventory
@@ -274,6 +275,7 @@ def test_preflight_rejects_dispatch_outside_release_tag(tmp_path: Path, ref: str
GH_FIXTURE = r"""import hashlib, json, os, sys
from pathlib import Path
+from urllib.parse import urlsplit
args = sys.argv[1:]
root = Path(os.environ["RELEASE_FIXTURE"])
tag = os.environ["RELEASE_TAG"]
@@ -289,7 +291,7 @@ def test_preflight_rejects_dispatch_outside_release_tag(tmp_path: Path, ref: str
value = [[release]]
elif args[0] == "release":
value = {"databaseId": 42, "tagName": tag}
-elif any("uploads.github.com" in item for item in args):
+elif any(urlsplit(item).hostname == "uploads.github.com" for item in args):
(root / "uploaded").touch()
value = {}
elif "PATCH" in args:
@@ -351,7 +353,10 @@ def test_installed_cli_binds_trusted_target_and_leaves_draft_for_review(tmp_path
assert json.loads(target)["expected_title"] == BASELINE
stale = run_shell(runner + shell_step("Attach baseline for review"), fixture, {**env, "RELEASE_TARGET": target, "REMOTE_COMMIT": "b" * 40})
assert stale.returncode != 0
+ assert not (fixture / "uploaded").exists()
assert not (fixture / "published").exists()
+ stale_calls = [json.loads(line) for line in (fixture / "api-calls").read_text(encoding="utf-8").splitlines()]
+ assert not any(urlsplit(item).hostname == "uploads.github.com" for call in stale_calls for item in call)
result = run_shell(runner + shell_step("Attach baseline for review"), fixture, {**env, "RELEASE_TARGET": target})
assert result.returncode == 0, result.stderr
assert (fixture / "uploaded").exists()
diff --git a/semgrep.yaml b/semgrep.yaml
index 1873485..b0d3f6f 100644
--- a/semgrep.yaml
+++ b/semgrep.yaml
@@ -101,7 +101,7 @@ rules:
- "/examples/**/*.rs"
- "/benches/**/*.rs"
# Includes integration/property tests and the deliberate violation
- # fixture. `just semgrep` excludes that fixture, while
+ # fixture. The root .semgrepignore excludes rule fixtures, while
# `just semgrep-test` scans it directly.
- "/tests/**/*.rs"
pattern-either:
@@ -376,6 +376,9 @@ rules:
- "/benches/**/*.rs"
- "/examples/**/*.rs"
- "/tests/semgrep/src/project_rules/bench_example_usage.rs"
+ exclude:
+ # The comparison crate's integration tests use ordinary test assertions.
+ - "/benches/comparison/tests/**"
pattern-either:
- pattern: $VALUE.unwrap()
- pattern: $VALUE.expect(...)
diff --git a/src/exact.rs b/src/exact.rs
index d5a1e22..bbd031d 100644
--- a/src/exact.rs
+++ b/src/exact.rs
@@ -500,21 +500,11 @@ impl ExactF64Conversion for RationalVector {
}
}
-/// Convert a `BigInt × 2^exp` pair to an exactly represented finite `f64`.
+/// Extract the low 64 bits of `|value| >> shift` without allocating.
///
-/// This avoids allocating a [`BigRational`] when determinant and solve paths
-/// already have an integer significand plus binary exponent. The optional
-/// `index` is forwarded to [`LaError::Unrepresentable`] for vector-valued solve
-/// components; determinant callers pass `None`.
-///
-/// # Errors
-/// Returns [`LaError::Unrepresentable`] with
-/// [`UnrepresentableReason::RequiresRounding`] when the exact nonzero value
-/// would need rounding or underflows below the smallest positive subnormal.
-///
-/// Returns [`LaError::Unrepresentable`] with
-/// [`UnrepresentableReason::NotFinite`] when the exact value cannot be
-/// represented by any finite `f64`.
+/// Discards the sign and bits outside this window; callers must establish that
+/// the shifted magnitude fits when they need the whole value. Returns `None`
+/// when the starting word is absent or its index does not fit in `usize`.
fn shifted_magnitude_to_u64(value: &BigInt, shift: u64) -> Option {
let word_bits = u64::from(u64::BITS);
let word_index = usize::try_from(shift / word_bits).ok()?;
diff --git a/src/lib.rs b/src/lib.rs
index 5137950..1d22063 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -561,7 +561,7 @@ pub mod guide {
//!
//! The error coefficients (`ERR_COEFF_2`, `ERR_COEFF_3`, `ERR_COEFF_4`) are
//! conservative, dimension-specific constants, not caller-tunable tolerances. The
- //! [mathematical basis](https://github.com/acgetchell/la-stack/blob/v0.4.5/docs/mathematical_basis.md#determinants-and-certified-sign-filtering)
+ //! [mathematical basis](https://github.com/acgetchell/la-stack/blob/main/docs/mathematical_basis.md#determinants-and-certified-sign-filtering)
//! documents the bound and states its range preconditions. The constants are explicit
//! crate-root exports for advanced users who want to compose the same bound:
//! `use la_stack::{ERR_COEFF_2, ERR_COEFF_3, ERR_COEFF_4};`. They intentionally stay
diff --git a/tests/proptest_exact.rs b/tests/proptest_exact.rs
index 10120e9..83a479d 100644
--- a/tests/proptest_exact.rs
+++ b/tests/proptest_exact.rs
@@ -900,22 +900,39 @@ macro_rules! gen_extreme_exponent_det_filter_proptests {
prop_assert_eq!(matrix.det_sign_exact(), expected_sign);
- if let Ok(Some(bound)) = matrix.det_errbound() {
- let direct = matrix
- .det_direct()
- .unwrap()
- .expect("D<=4 has closed-form det_direct");
- let direct_exact = BigRational::from_f64(direct)
- .expect("det_direct returned finite f64");
- let bound_exact = BigRational::from_f64(bound)
- .expect("det_errbound returned finite f64");
- let error = (direct_exact - exact).abs();
-
- prop_assert!(
- error <= bound_exact,
- "mixed-exponent determinant error exceeded bound for D={}: error={error}, bound={bound_exact}",
+ match matrix.det_errbound() {
+ Ok(Some(bound)) => {
+ let direct = matrix
+ .det_direct()
+ .unwrap()
+ .expect("D<=4 has closed-form det_direct");
+ let direct_exact = BigRational::from_f64(direct)
+ .expect("det_direct returned finite f64");
+ let bound_exact = BigRational::from_f64(bound)
+ .expect("det_errbound returned finite f64");
+ let error = (direct_exact - exact).abs();
+
+ prop_assert!(
+ error <= bound_exact,
+ "mixed-exponent determinant error exceeded bound for D={}: error={error}, bound={bound_exact}",
+ $d,
+ );
+ }
+ Ok(None) => {}
+ Err(LaError::NonFinite {
+ origin: NonFiniteOrigin::Computation {
+ operation: ArithmeticOperation::Determinant
+ | ArithmeticOperation::DeterminantErrorBound,
+ ..
+ },
+ location: NonFiniteLocation::Scalar,
+ ..
+ }) => {}
+ Err(error) => prop_assert!(
+ false,
+ "unexpected determinant filter error for D={}: {error:?}, entries={entries:?}",
$d,
- );
+ ),
}
}
}
diff --git a/tests/rational_allocations.rs b/tests/rational_allocations.rs
index df57291..67fa60b 100644
--- a/tests/rational_allocations.rs
+++ b/tests/rational_allocations.rs
@@ -71,7 +71,7 @@ fn rational_allocation_evidence() {
let _ = black_box(black_box(matrix).det_sign());
});
record::(kind, "solve", || {
- let _ = black_box(black_box(matrix).solve(black_box(rhs)).unwrap());
+ drop(black_box(black_box(matrix).solve(black_box(rhs)).unwrap()));
});
for augmented in [false, true] {