Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
58a7c3e
docs(spec): fix review findings in 001-low-memory-interpolator
lachlangrose Oct 27, 2026
d13a244
docs(spec): add 001-low-memory-interpolator spec, plan and tasks
lachlangrose Oct 27, 2026
a9e89ea
chore(loop-interpolation-core): scaffold core package, fixture and na…
lachlangrose Oct 27, 2026
2ba2cf9
feat(loop-interpolation-core): port loop-rs interpolation-fd and add …
lachlangrose Oct 27, 2026
d031e28
feat(loop-interpolation-core): remove grid rotation and serde; skip r…
lachlangrose Oct 27, 2026
bc86724
feat(loop-interpolation-core): make rayon optional with a one-thread …
lachlangrose Oct 27, 2026
cb8af11
feat(loop-interpolation-core): add counting allocator, baseline memor…
lachlangrose Oct 27, 2026
ab21fcb
feat(loop-interpolation-core): cut shell tables to u32; add failing i…
lachlangrose Oct 27, 2026
d3beebc
feat(loop-interpolation-core): validate flat input and add MemTracker
lachlangrose Oct 27, 2026
c54ef16
test(loop-interpolation-core): add failing memory estimate and CG ses…
lachlangrose Oct 27, 2026
0e251c0
feat(loop-interpolation-core): add memory estimate, budget check and …
lachlangrose Oct 27, 2026
73e8f56
feat(loop-interpolation-core): add CG state machine and solve session
lachlangrose Oct 27, 2026
2c9cd0c
test(loop-interpolation-core): add failing ADMM and constraint family…
lachlangrose Oct 27, 2026
db5bcaa
fix(loop-interpolation-core): match the matrix solver rows for curvat…
lachlangrose Oct 27, 2026
b118764
feat(loop-interpolation-core): add ADMM state machine with feasibilit…
lachlangrose Oct 27, 2026
74ee73e
feat(loop-interpolation-core): add Field evaluation and cut ADMM memory
lachlangrose Oct 27, 2026
568a02e
docs(spec): record build decisions in the plan answers
lachlangrose Oct 27, 2026
e720720
docs(spec): correct the column scaling answer in the plan
lachlangrose Oct 27, 2026
1ef045d
feat(loop-interpolation-core): add PyO3 bindings; add failing single …
lachlangrose Oct 27, 2026
8dc8d11
feat(loop-interpolation): add native solver adapter; make stencils ge…
lachlangrose Oct 27, 2026
dc21063
feat(loop-interpolation): route solver native through solve_system; m…
lachlangrose Oct 27, 2026
7193f24
feat(loop-interpolation-core): make data and inequality operators gen…
lachlangrose Oct 27, 2026
b9382a6
test(loop-interpolation): run native tests against the built wheel
lachlangrose Oct 27, 2026
9f6f12b
feat(loop-interpolation-core): add single precision sessions; add nat…
lachlangrose Oct 27, 2026
37f7548
test(loop-interpolation-core): add reference problem tests and fit th…
lachlangrose Oct 27, 2026
4e442a9
style(loop-interpolation-core): fix clippy findings in the core and o…
lachlangrose Oct 27, 2026
db774ca
feat(loop-interpolation-core): build the Python wheel with the parall…
lachlangrose Oct 28, 2026
c595b97
docs(spec): record the OP1 confirmation in the plan answers
lachlangrose Oct 28, 2026
ad67100
test(loop-interpolation-core): add the Playwright fixture test for th…
lachlangrose Oct 28, 2026
ec55631
chore: ignore Playwright output of the core package
lachlangrose Oct 28, 2026
90ab269
test(loop-interpolation-core): add the browser responsiveness, cancel…
lachlangrose Oct 28, 2026
6a0a81f
feat(loop-interpolation-core): add the wasm-bindgen classes for the b…
lachlangrose Oct 28, 2026
3d86111
style(loop-interpolation-core): format wasm.rs
lachlangrose Oct 28, 2026
a453fc1
feat(loop-interpolation-core): add the browser Worker and the wasm bu…
lachlangrose Oct 28, 2026
21e2e33
docs(loop-interpolation-core): add TypeScript declarations and the we…
lachlangrose Oct 28, 2026
f031406
docs(loop-interpolation-core): complete the package README and record…
lachlangrose Oct 28, 2026
eefa41e
ci: add the workflow for the native solver (Rust, wheel, WebAssembly,…
lachlangrose Oct 28, 2026
02cf99b
test(loop-interpolation-core): add the browser reference run of R1
lachlangrose Oct 28, 2026
a8f7647
chore(loop-interpolation-core): mark the final checks of spec 001 done
lachlangrose Oct 28, 2026
274ece1
chore(loop-interpolation-core): align the version to 0.1.0
lachlangrose Oct 28, 2026
e37f101
fix(loop-interpolation-core): make a failed finalize final in SolveSe…
lachlangrose Oct 28, 2026
2783dc5
fix(loop-interpolation): make the native solver path safe for unsuppo…
lachlangrose Oct 28, 2026
d0572f2
fix(loop-interpolation): filter bad rows and cap pair bounds in the n…
lachlangrose Oct 28, 2026
dd2ada0
feat(loop-interpolation): add per-solve ridge factor and warm start t…
lachlangrose Oct 28, 2026
9772043
feat(loop-interpolation-core): add ridge factor and start value to Pr…
lachlangrose Oct 28, 2026
3d36054
feat(loop-interpolation): send ridge factor and start value to the na…
lachlangrose Oct 28, 2026
e31c112
style(loop-interpolation-core): fix clippy 1.99 findings
lachlangrose Oct 28, 2026
a18685e
fix: give loop-interpolation-core a uv source so the workspace locks
lachlangrose Oct 28, 2026
862b279
test(loop-interpolation): allow 1e-5 value difference in the golden f…
lachlangrose Oct 28, 2026
1754e30
ci: skip the style auto-commit for pull requests from forks
lachlangrose Oct 28, 2026
de9d902
test(loop-interpolation): compare repeated native solves with a toler…
lachlangrose Oct 28, 2026
bac125a
perf(loop-interpolation-core): apply the regularisation with per-clas…
lachlangrose Oct 28, 2026
35e072b
test(loop-interpolation): use the SC-003 limit for repeated native AD…
lachlangrose Oct 28, 2026
8b02ff4
ci: build loop-interpolation-core wheels and publish them to PyPI
lachlangrose Oct 28, 2026
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
91 changes: 91 additions & 0 deletions .github/workflows/core-wheels.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
name: "Native wheels"

# Build the wheels and the sdist of loop-interpolation-core.
# pypi.yml calls this workflow and then publishes the files.
# A pull request that changes the crate also runs it, so a broken wheel
# build shows before a release.
#
# The crate uses abi3-py39, so one wheel for each platform works with all
# Python versions from 3.9.

on:
workflow_call:
pull_request:
branches:
- master
paths:
- 'packages/loop_interpolation_core/**'
- '.github/workflows/core-wheels.yml'
workflow_dispatch:

permissions:
contents: read

env:
MANIFEST: packages/loop_interpolation_core/Cargo.toml

jobs:
wheels:
name: Wheel ${{ matrix.platform.name }}
runs-on: ${{ matrix.platform.runner }}
strategy:
fail-fast: false
matrix:
platform:
- { name: linux-x86_64, runner: ubuntu-latest, target: x86_64, smoke: true }
- { name: linux-aarch64, runner: ubuntu-24.04-arm, target: aarch64, smoke: true }
- { name: macos-arm64, runner: macos-latest, target: aarch64, smoke: true }
# Cross-compiled on the arm64 runner, so it is not run here.
- { name: macos-x86_64, runner: macos-latest, target: x86_64, smoke: false }
- { name: windows-x86_64, runner: windows-latest, target: x64, smoke: true }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
# On Linux the build runs in a manylinux container.
- name: Build the wheel
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
manylinux: auto
args: --release --out dist --manifest-path ${{ env.MANIFEST }}
- name: Install the wheel and solve a small problem
if: matrix.platform.smoke
shell: bash
run: |
python -m pip install numpy
python -m pip install --no-index --find-links dist loop-interpolation-core
python - <<'EOF'
import numpy as np
import loop_interpolation_core as core

problem = core.Problem([0.0, 0.0, 0.0], [8, 8, 8], [1.0, 1.0, 1.0])
points = np.array([2.0, 2.0, 2.0, 5.0, 5.0, 5.0])
problem.add_value(points, np.array([0.0, 1.0]), np.ones(2))
field, report = problem.solve({})
assert report["converged"], report
assert field.shape == (512,), field.shape
print("solve converged in", report["iterations"], "iterations")
EOF
- uses: actions/upload-artifact@v4
with:
name: core-wheel-${{ matrix.platform.name }}
path: dist/*.whl
if-no-files-found: error

sdist:
name: Sdist
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build the sdist
uses: PyO3/maturin-action@v1
with:
command: sdist
args: --out dist --manifest-path ${{ env.MANIFEST }}
- uses: actions/upload-artifact@v4
with:
name: core-sdist
path: dist/*.tar.gz
if-no-files-found: error
4 changes: 4 additions & 0 deletions .github/workflows/linter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,16 @@ jobs:
- name: Lint with ruff
run: |
ruff check ${{ env.PROJECT_FOLDERS }} --fix --exit-zero
# A pull request from a fork gets a read-only GITHUB_TOKEN, so it
# cannot push the style branch. Skip the push and the PR for it.
- uses: stefanzweifel/git-auto-commit-action@v5
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
with:
commit_message: "style: style fixes by ruff and autoformatting by black"
branch: lint/style-fixes-${{ github.run_id }}
create_branch: true
- name: Create Pull Request
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
218 changes: 218 additions & 0 deletions .github/workflows/native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
name: "Native solver"

# This workflow checks the Rust crate loop_interpolation_core.
# It runs only when the crate, the Python adapter or this file changes.

on:
push:
branches:
- master
paths:
- 'packages/loop_interpolation_core/**'
- 'packages/loop_interpolation/src/loop_interpolation/_native.py'
- '.github/workflows/native.yml'
pull_request:
branches:
- master
paths:
- 'packages/loop_interpolation_core/**'
- 'packages/loop_interpolation/src/loop_interpolation/_native.py'
- '.github/workflows/native.yml'
workflow_dispatch:

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
MANIFEST: packages/loop_interpolation_core/Cargo.toml

jobs:
# Format, lint and test the crate with all feature sets.
rust:
name: Rust checks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install the Rust toolchain
run: |
rustup toolchain install stable --profile minimal --component rustfmt --component clippy --target wasm32-unknown-unknown
rustup default stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/loop_interpolation_core/target
key: rust-${{ runner.os }}-${{ hashFiles('packages/loop_interpolation_core/Cargo.lock') }}
restore-keys: rust-${{ runner.os }}-
- name: Format
run: cargo fmt --manifest-path "$MANIFEST" --check
- name: Clippy, no features
run: cargo clippy --manifest-path "$MANIFEST" --all-targets -- -D warnings
- name: Clippy, parallel
run: cargo clippy --manifest-path "$MANIFEST" --all-targets --features parallel -- -D warnings
- name: Clippy, python
run: cargo clippy --manifest-path "$MANIFEST" --all-targets --features python -- -D warnings
- name: Clippy, wasm
run: cargo clippy --manifest-path "$MANIFEST" --all-targets --features wasm --target wasm32-unknown-unknown -- -D warnings
# Use --release. Debug test runs take many minutes.
- name: Test, no features
run: cargo test --manifest-path "$MANIFEST" --release
- name: Test, parallel
run: cargo test --manifest-path "$MANIFEST" --release --features parallel

# Build the Python wheel and run the Python tests with the native core installed.
wheel:
name: Wheel ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/loop_interpolation_core/target
key: wheel-${{ runner.os }}-${{ hashFiles('packages/loop_interpolation_core/Cargo.lock') }}
restore-keys: wheel-${{ runner.os }}-
# maturin develop needs a virtual environment.
# The Linux and macOS folder is bin. The Windows folder is Scripts.
- name: Install, build and test
shell: bash
run: |
python -m venv .venv
if [ -f .venv/Scripts/activate ]; then source .venv/Scripts/activate; else source .venv/bin/activate; fi
python -m pip install --upgrade pip
python -m pip install maturin numpy scipy pytest
python -m pip install -e packages/loop_common -e packages/loop_interpolation
maturin develop --release -m packages/loop_interpolation_core/Cargo.toml
python -m pytest packages/loop_interpolation/tests -q

# Build the WebAssembly package, check its size and run the browser tests.
wasm:
name: WebAssembly
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install the Rust toolchain
run: |
rustup toolchain install stable --profile minimal --target wasm32-unknown-unknown
rustup default stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/loop_interpolation_core/target
key: wasm-${{ runner.os }}-${{ hashFiles('packages/loop_interpolation_core/Cargo.lock') }}
restore-keys: wasm-${{ runner.os }}-
# The wasm-bindgen CLI version must equal the wasm-bindgen version in Cargo.lock.
- name: Read the wasm-bindgen version
run: |
V=$(grep -A1 '^name = "wasm-bindgen"$' packages/loop_interpolation_core/Cargo.lock | sed -n 's/^version = "\(.*\)"$/\1/p')
echo "Version: $V"
test -n "$V"
echo "WASM_BINDGEN_VERSION=$V" >> "$GITHUB_ENV"
- name: Cache the wasm-bindgen CLI
id: bindgen-cache
uses: actions/cache@v4
with:
path: ~/.cargo/bin/wasm-bindgen
key: wasm-bindgen-cli-${{ runner.os }}-${{ env.WASM_BINDGEN_VERSION }}
- name: Install the wasm-bindgen CLI
if: steps.bindgen-cache.outputs.cache-hit != 'true'
run: cargo install wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --locked
- name: Build the WebAssembly package
run: bash packages/loop_interpolation_core/scripts/build_wasm.sh
- name: Check the size
run: python -I packages/loop_interpolation_core/scripts/check_wasm_size.py
- name: Install the browser test packages
working-directory: packages/loop_interpolation_core/tests_web
run: |
npm ci
npx playwright install --with-deps chromium
- name: Browser tests
working-directory: packages/loop_interpolation_core/tests_web
run: npm test

# Slow reference runs. Start these by hand only.
reference:
name: Reference problems
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install the Rust toolchain
run: |
rustup toolchain install stable --profile minimal --target wasm32-unknown-unknown
rustup default stable
- uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
packages/loop_interpolation_core/target
key: reference-${{ runner.os }}-${{ hashFiles('packages/loop_interpolation_core/Cargo.lock') }}
restore-keys: reference-${{ runner.os }}-
- name: Read the wasm-bindgen version
run: |
V=$(grep -A1 '^name = "wasm-bindgen"$' packages/loop_interpolation_core/Cargo.lock | sed -n 's/^version = "\(.*\)"$/\1/p')
test -n "$V"
echo "WASM_BINDGEN_VERSION=$V" >> "$GITHUB_ENV"
- name: Cache the wasm-bindgen CLI
id: bindgen-cache
uses: actions/cache@v4
with:
path: ~/.cargo/bin/wasm-bindgen
key: wasm-bindgen-cli-${{ runner.os }}-${{ env.WASM_BINDGEN_VERSION }}
- name: Install the wasm-bindgen CLI
if: steps.bindgen-cache.outputs.cache-hit != 'true'
run: cargo install wasm-bindgen-cli --version "$WASM_BINDGEN_VERSION" --locked
# The test f32_250_cubed_within_budget is too slow for CI. Skip it.
- name: Ignored Rust tests
run: |
set -o pipefail
cargo test --manifest-path "$MANIFEST" --release --test reference_problems -- --ignored --nocapture --test-threads=1 --skip f32_250_cubed_within_budget 2>&1 | tee rust_reference.log
- name: Write the reference field
run: cargo test --manifest-path "$MANIFEST" --release --test reference_problems r1_write_field -- --ignored
- name: Build the WebAssembly package
run: bash packages/loop_interpolation_core/scripts/build_wasm.sh
- name: Browser reference spec
working-directory: packages/loop_interpolation_core/tests_web
env:
RUN_REFERENCE: '1'
run: |
set -o pipefail
npm ci
npx playwright install --with-deps chromium
npm test -- reference 2>&1 | tee "$GITHUB_WORKSPACE/browser_reference.log"
- name: Write the job summary
if: always()
run: |
{
echo "## Rust reference problems"
echo '```'
cat rust_reference.log 2>/dev/null || echo "No output."
echo '```'
echo "## Browser reference spec"
echo '```'
cat browser_reference.log 2>/dev/null || echo "No output."
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
27 changes: 27 additions & 0 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,33 @@ permissions:
contents: read

jobs:
# loop-interpolation-core is the Rust core behind the optional extra
# loop-interpolation[native]. It has compiled wheels, so it is built by
# core-wheels.yml and not by the sdist matrix below. No other package
# waits for it: a failed core upload does not stop their release.
build_core:
name: Build loop-interpolation-core
uses: ./.github/workflows/core-wheels.yml

# Trusted publishing (OIDC): no password. PyPI must list this repository
# and the workflow file pypi.yml as a trusted publisher of the project.
upload_core_to_pypi:
name: Upload loop-interpolation-core to PyPI
needs: ["build_core"]
runs-on: ubuntu-latest
permissions:
id-token: write
steps:
- uses: actions/download-artifact@v4
with:
pattern: core-*
path: dist
merge-multiple: true
- uses: pypa/gh-action-pypi-publish@release/v1
with:
skip-existing: true
verbose: true

# loop_common/loop_interpolation/loopstructural_visualisation are
# workspace-local deps of LoopStructural (see [tool.uv.sources] in
# pyproject.toml) — loop_common/loop_interpolation are runtime deps,
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,9 @@ sandbox/*

# codebase-graph
.codebaseGraph/

# loop_interpolation_core build output (wasm pkg) and browser test dependencies
packages/loop_interpolation_core/pkg/
packages/loop_interpolation_core/tests_web/node_modules/
packages/loop_interpolation_core/tests_web/test-results/
packages/loop_interpolation_core/tests_web/playwright-report/
12 changes: 12 additions & 0 deletions .release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"exclude-paths": [
"packages/loop_common",
"packages/loop_interpolation",
"packages/loop_interpolation_core",
"packages/map2loop",
"packages/loopstructural_visualisation"
]
Expand All @@ -20,6 +21,17 @@
"release-type": "python",
"component": "loop-interpolation"
},
"packages/loop_interpolation_core": {
"release-type": "python",
"component": "loop-interpolation-core",
"extra-files": [
{
"type": "toml",
"path": "Cargo.toml",
"jsonpath": "$.package.version"
}
]
},
"packages/map2loop": {
"release-type": "python",
"component": "map2loop"
Expand Down
Loading
Loading