Skip to content

Move the moment equations into FORTRAN 77 and build with meson-python - #53

Merged
saudzahirr merged 1 commit into
masterfrom
feat/f77-moment-kernel
Aug 15, 2026
Merged

Move the moment equations into FORTRAN 77 and build with meson-python#53
saudzahirr merged 1 commit into
masterfrom
feat/f77-moment-kernel

Conversation

@saudzahirr

Copy link
Copy Markdown
Collaborator

The Cox (1979) moment equations are a quadruple loop over the input variables evaluated for every uncertain result, and in Python they dominated the cost of the library. They are now FORTRAN 77 subroutines in src/soerp/_soerp.f, compiled into an extension module by f2py, which returns the numerical core to the language the original SOERP was written in. Python keeps the automatic differentiation, the distribution moments and the public API.

Measured against the pure-Python implementation this replaces, on the same machine and inputs:

rawmoment(k=4), equation (A-9)     soerp_numeric(), the full call
n    python    fortran  speedup    python    fortran  speedup
2      59.9us    3.2us    18.8x     179.1us   12.5us    14.3x
3     203.8us    4.2us    48.8x     462.1us   13.8us    33.4x
5    1195.4us    4.4us   271.5x    2131.3us   16.9us   125.9x

10 18598.0us 13.0us 1434.9x 19636.0us 41.1us 477.3x

Cox's own three-part assembly now takes 16us end to end rather than 495us, so a result that used to cost half a millisecond is comfortably inside one. The whole test suite runs in half the time it did.

The kernel is verified, not assumed: it reproduces the exact expansion of E[y**k] for n = 1..5 across every combination of linear, pure-quadratic and cross-product terms, under both standardized and general input moments, and still reproduces Table 2 of the paper.

Build system, following the f2py-tpl template:

  • meson-python replaces hatchling, with meson.build at the root, one per package level, and the version derived from git by bin/get_version.py.
  • bin/build.py wraps install / wheel / clean.
  • Windows links the gfortran runtime statically so a wheel imports without MinGW present. Each flag is probed rather than assumed, since -static-libquadmath only exists from GCC 9 onwards and an unsupported flag is a hard link error.
  • VCCP is zeroed before use: it is intent(out) but only the i < j triangle is written, so the rest would otherwise be uninitialised memory.
  • VARCMP takes its scratch arrays from the caller via intent(cache,hide) rather than using automatic arrays, which keeps the source inside FORTRAN 77 and leaves the number of input variables unbounded.

The package moves under src/. Without that, a local soerp/ directory shadows the installed package, so pip install . followed by pytest from the repository root imports the source tree, which has no compiled extension, and every test errors on cannot import name '_soerp'.

CI has to change shape because the wheel is no longer pure Python. A single artifact can no longer be installed across the test matrix, so the test job builds from source on each runner and installs gfortran where the image lacks it; wheel building moves to cibuildwheel across Linux, Windows and macOS, with the results collected into the artifact the publish workflow already expects.

The Cox (1979) moment equations are a quadruple loop over the input
variables evaluated for every uncertain result, and in Python they
dominated the cost of the library. They are now FORTRAN 77 subroutines in
src/soerp/_soerp.f, compiled into an extension module by f2py, which
returns the numerical core to the language the original SOERP was written
in. Python keeps the automatic differentiation, the distribution moments
and the public API.

Measured against the pure-Python implementation this replaces, on the same
machine and inputs:

    rawmoment(k=4), equation (A-9)     soerp_numeric(), the full call
    n    python    fortran  speedup    python    fortran  speedup
    2      59.9us    3.2us    18.8x     179.1us   12.5us    14.3x
    3     203.8us    4.2us    48.8x     462.1us   13.8us    33.4x
    5    1195.4us    4.4us   271.5x    2131.3us   16.9us   125.9x
   10   18598.0us   13.0us  1434.9x   19636.0us   41.1us   477.3x

Cox's own three-part assembly now takes 16us end to end rather than 495us,
so a result that used to cost half a millisecond is comfortably inside one.
The whole test suite runs in half the time it did.

The kernel is verified, not assumed: it reproduces the exact expansion of
E[y**k] for n = 1..5 across every combination of linear, pure-quadratic and
cross-product terms, under both standardized and general input moments, and
still reproduces Table 2 of the paper.

Build system, following the f2py-tpl template:

- meson-python replaces hatchling, with meson.build at the root, one per
  package level, and the version derived from git by bin/get_version.py.
- bin/build.py wraps install / wheel / clean.
- Windows links the gfortran runtime statically so a wheel imports without
  MinGW present. Each flag is probed rather than assumed, since
  -static-libquadmath only exists from GCC 9 onwards and an unsupported
  flag is a hard link error.
- VCCP is zeroed before use: it is intent(out) but only the i < j triangle
  is written, so the rest would otherwise be uninitialised memory.
- VARCMP takes its scratch arrays from the caller via intent(cache,hide)
  rather than using automatic arrays, which keeps the source inside
  FORTRAN 77 and leaves the number of input variables unbounded.

The package moves under src/. Without that, a local soerp/ directory
shadows the installed package, so `pip install .` followed by `pytest` from
the repository root imports the source tree, which has no compiled
extension, and every test errors on `cannot import name '_soerp'`.

CI has to change shape because the wheel is no longer pure Python. A single
artifact can no longer be installed across the test matrix, so the test job
builds from source on each runner and installs gfortran where the image
lacks it; wheel building moves to cibuildwheel across Linux, Windows and
macOS, with the results collected into the artifact the publish workflow
already expects.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: codex <267193182+codex@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: gemini-code-assist <gemini-code-assist@users.noreply.github.com>
Co-authored-by: Junie <junie@jetbrains.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: kilocode-bot <github-actions@github.com>
@saudzahirr saudzahirr self-assigned this Aug 15, 2026
@sonarqubecloud

Copy link
Copy Markdown

@saudzahirr
saudzahirr merged commit e2a5c66 into master Aug 15, 2026
8 checks passed
@saudzahirr
saudzahirr deleted the feat/f77-moment-kernel branch August 15, 2026 11:15
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants