nativegate

Converting legacy Fortran and C++ to Python: the case for generation over translation

What "convert Fortran to Python" usually means

A rewrite. Someone reads the deck and produces a Python file: every loop, every COMMON block, every magic constant transcribed into numpy. The reason this is the expensive plan is that a validated numerical code is a record — what your organization relies on is not merely the text of the routine, but the artifact the auditors signed and the twenty years of production runs behind it. A rewrite makes every consumer rebuild that trust from zero, in a language where 1-based array origins, assumed-size arguments and COMMON-block state are each their own subtle-difference hazard. Re-hosting keeps the compiled numerics and converts the interface around them instead.

ApproachWhat it recoversWhat it costs
Hand-rewrite into Pythonnone of the validated numericsall regression work starts over; every translated loop is a new subtle-difference risk
Hand-written bindingsthe numerics keep runningargument intent, array shapes and session lifecycle re-implemented per routine
Subprocess wrapper + stdout parsingnumerics and a rough call surfaceno library, no schema, no tests; brittle at scale
nativegatenumerics unchanged plus generated bindings, tests, wheel, HTTP service, containersthe integration work you wanted anyway, generated once and pinned to source hashes

What nativegate does on day one

One generator handles a tree: you point ngate quickstart at a header or a Fortran deck, and in one command it produces a pip-installable wheel and its compile artifacts. The same source drives a FastAPI service (the wheel) and a Dockerfile; you edit nothing twice.

pip install "nativegate[clang,build]"
ngate quickstart legacy/sim.f --name sim_api
pip install services/sim_api/dist/*.whl

Three minutes later, the same library is also an HTTP service:

ngate serve sim_api
curl -X POST "http://localhost:8000/solve?a=1&b=2"

The case, feature by feature

1. Handles 1990s-era sources unmodified

Fixed-form columns 6 and 73, INCLUDE decks, IMPLICIT typing, COMMON blocks, netlib's CS/CD dual-dialect marking, statement labels in the marked copy — all tested against real third-party Fortran, with the dialect resolution and label handling that the netlib specfun contact produced, published as an independently wrapped library.

2. Equality you can pin a build to

Every generated service can record a numerical baseline (ngate golden record): the return values of each entry point, tolerance, and the SHA-256 of every native source byte. ngate golden verify fails a rebuild that moved a number, and CI fails when a hash moved. For the specfun showcase, all 14 pinned entry points came back unchanged when the bindings were regenerated from the pristine downloads — that is the whole argument for generation over translation, measured.

3. One workflow, two languages

The same CLI, yaml schema, test suite and golden harness cover C++ (pybind11 through libclang) and Fortran (f2py). The team learns one interface, not one per language.

4. It refuses rather than mis-types

What it cannot bind is recorded as a skip with the reason: derived types it cannot flatten, callbacks (EXTERNAL dummies), dialect files without a chosen half. The list is published (DEFECTS.md), and every defect found in the wild is regression-tested once fixed — the specfun and quadpack contacts alone produced six of them.

5. A service, not just a wheel

The generated service ships optional API-key auth, a schema, request logging, readiness/liveness probes with SIGTERM draining, Kubernetes manifests and a reproducible, hash-locked Docker build — the deployment half a "just load the .so" wrapper leaves to you.

The case against hand-rolled wrappers

It is not that hand-written bindings are impossible — many teams have one, and it works while the API stays small. The cost grows with every new endpoint: array edges, intent, error flags, session lifecycles, all redone per routine and re-verified by nobody in particular. The generated vs hand-written comparison states the trade in measured numbers rather than adjectives.

What nativegate does not do

Stated plainly, because the narrowing helps you plan:

  • It does not translate your Fortran to Python. The numerics compile from the original source — that is the point and also the limit.
  • Routines taking an external f callback are refused for now, with a reason (QUADPACK-style integrators). Scoped in the roadmap.
  • fparser2 refuses Hollerith-era syntax gfortran compiles with a warning; such decks use the regex reader backend.
  • Fortran COMMON-block state means one native call at a time per process — scale comes from more processes behind an ingress, and the generated Kubernetes manifests are shaped accordingly.

Where to look, without being asked to take our word for it

  • The generator: source, test suite, DEFECTS.md and ROADMAP.md published as-is.
  • pip install "nativegate[clang,build]" — the tool installs from PyPI today.
  • specfun-py: netlib specfun wrapped end to end from the unchanged Fortran, values pinned in CI.
  • The blog: what the tool does, what it refuses, and the defects the third-party contacts found — with the fix version listed for each.
Try it → The enterprise page