nativegate
← Blog

Modernizing Legacy Petroleum Engineering Software

Petroleum software is a special case of legacy code: the numerics often date to the 1980s and 1990s — black-oil PVT correlation decks, rock-property routines, well-model correlations — and yet they are still trusted, still run, and still outlive every attempt to replace them. The people who can check the correlations are not necessarily the people who can rewrite the fixed-form F77 they live in.

We picked this domain for nativegate's flagship example precisely because it is the hard case. This post describes what the petroleum modernization problem looks like from the inside, and what worked on an example ~4,800-line codebase of exactly this shape.

Why petroleum code is the hard version of the problem

  • Deck structure. Code from this era is written as include decks: shared declarations spliced in by INCLUDE statements, not modules. The file you open is not the unit you compile. Any binding tool that parses the visible file only sees half the program.
  • COMMON blocks everywhere. State lives in named COMMON storage shared across dozens of routines. This makes every routine's behavior depend on what ran before it — and it makes naive concurrent access a correctness bug, not a performance problem.
  • Implicit typing and period conventions. IMPLICIT typing, REAL*8, CHARACTER*(*) accepted-length strings, typed FUNCTION returns like DOUBLE PRECISION FUNCTION PVTRS — a surprising fraction of published parsers miss routines in this dialect. On our example codebase, a first discovery pass found 25 of 49 routines and silently ignored the other 24. That gap was a defect, and now a fix with a test.
  • Validation is the asset. The correlations have been checked against lab data and field behavior for decades. The cost of modernization is not the code; it is the risk of touching answers that people plan wells around.

What worked, in order

The nativegate example — a 2003 F90 facade over seven fixed-form F77 decks of black-oil PVT and rock-property routines — went end-to-end from unmodified source to a working wheel with the following steps:

  1. Expand the decks before parsing. INCLUDE expansion produces a generated copy, never modifying the original files, so the parser sees the program the compiler sees.
  2. Infer intent. Old decks don't declare INTENT(IN). Inferring argument direction per routine is what makes scalar outputs come back at all — before the fix, Fortran scalar outputs were simply dropped.
  3. Resolve parameterized kinds. The modern real(dp) idiom breaks f2py builds when the PARAMETER doesn't travel with it; resolving it to real(8) on the generated copy turns a cryptic ~200-lines-later build failure into a clean build.
  4. Bind a chosen surface. 10 exposed routines out of 49 discovered. The rest stay compiled-in, reachable through the ones chosen.
  5. Record and verify the numbers. ngate golden recordngate golden verify, committed, run in CI.

Why the numerical check matters more here than anywhere

In most software, "it works after the refactor" is the acceptance test. In petroleum software, the acceptance test is "it returns the same numbers" — engineers have benchmark cases they have trusted for years, and any drift, however small, costs a re-validation exercise nobody budgeted. A committed golden record that replays known configurations and fails on any float movement turns that acceptance test into a checkbox instead of a research project.

What petroleum teams get at the end

Not a rewrite. The Fortran still runs, compiled. What changes is everything around it: the correlations become importable Python functions the data-science team can call from notebooks and workflows; the same routines can be exposed as a service for other tools; and the verification harness guards every future change. The interface finally catches up to the discipline.

See the worked example → View on GitHub