Paper deep dive
A Fortran General-Purpose Transpiler: Proof of Concept
Shivamshan Sivanesan, Kazem Ardaneh
Intelligence
Status: succeeded | Model: Gemma-4-26B-A4B | Prompt: intel-v1 | Confidence: 92%
Last extracted: 8/4/2026, 3:56:18 AM
Summary
The paper introduces FGPT (Fortran General-Purpose Transpiler), a Python-based compiler framework that automatically transpiles legacy Fortran code into GPU-adapted Fortran, auto-differentiable Fortran, or NumPy/JAX modules. It addresses the expertise gap in high-performance computing by using a three-stage pipeline (frontend, middle-end, backend) with dependency-aware procedure isolation and AST-based semantic transformations to preserve numerical fidelity and program semantics, validated on climate modeling kernels.
Entities (11)
Relation Signals (8)
FGPT â transpiles â Fortran
confidence 95% ¡ FGPT ... transpiles Fortran into GPU-adapted Fortran, auto-differentiable Fortran via Tapenade, or NumPy and JAX modules.
FGPT â uses â AST
confidence 95% ¡ FGPT ... combines dependency-aware procedure isolation with Abstract Syntax Tree (AST) based transformations
F2NP â ispartof â FGPT
confidence 90% ¡ The F2NP stage lowers isolated Fortran procedures to equivalent NumPy implementations.
Transformer â ispartof â FGPT
confidence 90% ¡ The Transformer stage assembles these components into complete Python programs.
FGPT â targets â JAX
confidence 90% ¡ produce JAX modules ready for GPU acceleration and automatic differentiation.
FGPT â uses â fparser
confidence 90% ¡ FGPT begins by parsing Fortran source with fparser
FGPT â validateson â IPSL land-surface model
confidence 90% ¡ We evaluate FGPT on the largest procedures of the IPSL land-surface model
Tapenade â enables â auto-differentiable Fortran
confidence 85% ¡ auto-differentiable Fortran via Tapenade
Cypher Suggestions (0)
No Cypher suggestions yet.
Abstract
Abstract:Fortran has been the cornerstone of high-performance computing for decades and remains unmatched in many domains. Yet the language faces an expertise gap: a new generation of scientists is barely familiar with it, while many experienced Fortran developers are only now transitioning to modern ecosystems such as JAX. This gap often results in "Fython"--Python code written with a Fortran mindset-- that fails to leverage modern frameworks. We present FGPT, a Python-based compiler framework designed to bridge this divide. It provides a systematic pipeline that transpiles Fortran into GPU-adapted Fortran, auto-differentiable Fortran via Tapenade, or NumPy and JAX modules. Its architecture comprises three stages: (i) a frontend that parses Fortran and extracts target procedures along with their cross-module dependencies; (ii) a middle-end that lowers the code into an intermediate representation, then into GPU-adapted or auto-differentiable Fortran, or a NumPy class; and (iii) a backend that rewrites control-flow and expressions to produce JAX modules ready for GPU acceleration and automatic differentiation. While large language models hold promise for small snippets, they fail at the scale of community scientific codes--often spanning hundreds of thousands of lines--where consistent transformations, strict numerical fidelity, and validation against production tests are non-negotiable. FGPT addresses these challenges by preserving program semantics throughout the entire translation. We verified the framework on representative climate modeling kernels and demonstrated that it produces correct, differentiable Python implementations without requiring manual intervention. By combining rigorous compiler techniques with modern accelerator support, FGPT offers a scalable, trustworthy path for modernizing legacy Fortran code.
Tags
Links
- Source: https://arxiv.org/abs/2608.00130v1
- Canonical: https://arxiv.org/abs/2608.00130v1
Trouble viewing inline? Open PDF directly â
Full Text
58,131 characters extracted from source content.
Expand or collapse full text
A Fortran General-Purpose Transpiler: Proof of Concept Shivamshan Sivanesan kardaneh@ipsl.fr Modeling Center, University, CNRS, IPSL, , Abstract Fortran has been the cornerstone of high-performance computing for decades and remains unmatched in many domains. Yet the language faces an expertise gap: a new generation of scientists is barely familiar with it, while many experienced Fortran developers are only now transitioning to modern ecosystems such as JAX. This gap often results in "Fython"âPython code written with a Fortran mindsetâ that fails to leverage modern frameworks. We present FGPT, a Python-based compiler framework designed to bridge this divide. It provides a systematic pipeline that transpiles Fortran into GPU-adapted Fortran, auto-differentiable Fortran via Tapenade, or NumPy and JAX modules. Its architecture comprises three stages: (i) a frontend that parses Fortran and extracts target procedures along with their cross-module dependencies; (i) a middle-end that lowers the code into an intermediate representation, then into GPU-adapted or auto-differentiable Fortran, or a NumPy class; and (i) a backend that rewrites control-flow and expressions to produce JAX modules ready for GPU acceleration and automatic differentiation. While large language models hold promise for small snippets, they fail at the scale of community scientific codesâoften spanning hundreds of thousands of linesâwhere consistent transformations, strict numerical fidelity, and validation against production tests are non-negotiable. FGPT addresses these challenges by preserving program semantics throughout the entire translation. We verified the framework on representative climate modeling kernels and demonstrated that it produces correct, differentiable Python implementations without requiring manual intervention. By combining rigorous compiler techniques with modern accelerator support, FGPT offers a scalable, trustworthy path for modernizing legacy Fortran code. keywords: Fortran, Python, Transpiler, Source-to-source compiler, High performance computing, Automatic differentiation 2026 4 10.1017/eds.2020.x Frontmatter [1]Kazem Ardaneh 0000-0003-0473-6907 Sivanesan & Ardaneh 1 Introduction Fortran has long been the dominant language for high-performance computing (HPC), owing to its unrivaled efficiency in loop-oriented computations. Today, the majority of climate models, weather forecasting systems, computational fluid dynamics solvers, and other large-scale scientific codes are written in Fortran. Yet as these codes continue to grow in size and complexity, their compilation, maintenance, and evolution have become increasingly challenging. Modernizing legacy Fortran presents a major hurdle. Adapting existing applications to heterogeneous architecturesâincluding GPUsâoften demands extensive manual refactoring, a task hampered by a shrinking pool of Fortran experts. This challenge is compounded by the coexistence of multiple language standards, compiler implementations, and project-specific coding conventions. Moreover, compared with high-level ecosystems such as Jax, Fortran offers limited native access to automatic differentiation (AD), AI-driven frameworks, and accelerator programming models. These limitations increasingly isolate Fortran codes from the broader computational science innovation cycle. Modernization is therefore essentialânot only to extend the lifetime of established models, but also to unlock new computational paradigms such as differentiable programming and hybrid CPU-GPU execution. Developing scalable, automated approaches for translating legacy Fortran applications is thus critical for the sustainability and future evolution of scientific code. We note that existing interoperability tools, such as f2py (Peterson, 2009), allow calling compiled Fortran from Python but do not generate equivalent source codeâand thus do not meet the demand for fully translatable, maintainable Python implementations. The primary approach to porting Fortran to higher-level languages remains manual reimplementation. Häfner et al. (2018) demonstrated this with Veros, a JAX-based ocean circulation model developed through regular-expression preprocessing followed by extensive manual vectorizationâa process that reproduced the original modelâs numerics with competitive GPU performance but required roughly one year of expert effort and yielded a methodology specific to a single application. More recently, Zhou et al. (2024) explored LLM-based translation of land-surface routines into Python/JAX using GPT-4 with dependency-ordered decomposition and iterative check against unit tests. While this achieved GPU acceleration and enabled gradient-based parameter estimation, the authors concluded that whole-code translation remains impractical: legacy scientific applications exceed current context limits, and function-level translations frequently require manual correction. Agentic approaches have recently been applied to larger codes. Lahlou et al. (2026) translated the 19,000-line CLM-ml-v2 model into a numerically and gradient-verified JAX implementation using dependency analysis, persistent state documents, and a Fortran reference oracle. Similarly, Koldunov et al. (2026) reported a multi-week agent-assisted migration of the 74,000-line FESOM2 ocean model to C++/Kokkos. Despite their differing methodologies, both studies demonstrate a consistent conclusion: large, configurable applications demand incremental, dependency-aware decomposition over monolithic translation. Existing approaches remain limited either by the extensive human effort required for manual translation or by the probabilistic nature of LLM-based workflows, which often demand iterative check, debugging, and human interventionâundermining reproducibility and independent verification. Moreover, no prior approach has demonstrated a deterministic transformation pipeline capable of scaling to large scientific codes while preserving numerical fidelity. Compiler-based transformations, in contrast, are deterministic, reproducible, and independently verifiable; each stage can be validated in isolation while naturally scaling to large applications. These observations motivate the following question: Can large legacy Fortran codes be automatically modernized via a deterministic, dependency-aware compiler pipeline that preserves numerical fidelity while targeting modern hybrid architectures? We introduce FGPT (Fortran General-Purpose Transpiler), a source-to-source transpiler that combines dependency-aware procedure isolation with Abstract Syntax Tree (AST) based transformations to modernize legacy Fortran code. FGPT parses the original code, isolates target procedures along with their dependencies, generates reference inputâoutput pairs from the original implementation, and translates each isolated procedure into GPU-adapted Fortran, auto-differentiable Fortran (via Tapenade), or Python. For the Python target, FGPT first produces a NumPy implementation, then transforms it into JAX, with numerical consistency verified against the original Fortran after each stage. This work focuses on the Python translation pipeline. We evaluate FGPT on the largest procedures of the IPSL land-surface model, comprising interconnected modules with deeply nested procedure hierarchies exceeding 15,000 lines of Fortran code. The results demonstrate that FGPT correctly reconstructs procedure interfaces and dependencies while producing numerically consistent NumPy implementations and JAX-compatible programs. 2 FGPT in a nutshell FGPT begins by parsing Fortran source with fparser (STFC, 2026) and representing it as an AST, then applies a sequence of deterministic semantic transformations for generating the final code (Figure 1). For the transpilation, a line-by-line string translation strategy was rejected because Fortran and Python differ fundamentally in syntax, execution model, array indexing, loop semantics, memory layout, intrinsic procedures, and control-flow. Instead, FGPT adopts a compiler-inspired intermediate representation using the Python AST. Transformations operate on program constructs rather than source strings, enabling semantic-preserving rewrites independent of formatting or coding style. The AST is recursively traversed to identify subroutines, loops, conditionals, intrinsics, declarations, array operations, procedure calls, etc. These nodes drive construction of the corresponding Python AST using Pythonâs NodeTransformer framework, systematically rewriting Fortran-specific constructs into equivalent Python representations. Executable Python script is then regenerated via ast.unparse. Using the AST as the intermediate representation offers three principal advantages: modularity (individual passes can be introduced or extended independently), semantic transformations on program structure rather than strings, and a foundation for subsequent compiler passes, including NumPy-to-JAX lowering. The same methodology extends to the JAX backend: rather than translating Fortran directly to JAX, the validated NumPy AST serves as the intermediate representation from which JAX-compatible code is generated through additional semantic transformations. This staged design enables independent validation of each transformation phase while progressively extending the capabilities of the generated implementation. Fortran CodeOriginal source codeASTSemantic representationPython CodeGenerated Python syntaxCreate an intermediary representation using the ASTEncodes program intentLeverages Python libraries(e.g., NumPy)Preserves semantic meaningOne line of code: ast.unparse(tree) Figure 1: AST-based transpilation Scientific Fortran applications often comprise thousands of procedures across numerous modules with complex interdependencies, making whole-code transpilation impractical due to the difficulty of analysis, transformation, and validation. To address this, FGPT employs a dependency-aware procedure isolation stage that decomposes the code into independently executable units prior to transpilationâestablishing the foundation for all subsequent compiler transformations, as detailed below. 2.1 Isolation The objective of the isolation stage is to extract a single Fortran procedure together with all information required for its independent compilation, execution, verification, and subsequent transpilation. This requires reconstructing its complete execution contextâincluding variable declarations, array allocation semantics, intrinsic procedures, external calls, module variables, interface blocks, and other language constructs upon which the procedure depends. As each procedure is extracted, structural metadataâincluding declarations, dependencies, interfaces, and the call graphâis simultaneously collected and forwarded to the AST transformation pipeline. Thus, the isolation stage not only produces an independently executable compilation unit for numerical validation but also provides the semantic context required for subsequent transformations. The extracted dependencies (called procedures and global variables) are assembled into a generated global module, while the isolated target procedure is embedded within a generated main program. The isolation process consists of six components: ⢠Processor: Parses Fortran source, builds Fortran ASTs, and supplies parsing and code-generation utilities for the transpilation pipeline. ⢠Navigator: Resolves inter-module dependencies through breadth-first traversal, locating declarations, interfaces, external procedures, and module dependencies while prioritizing the shortest path to each search request. ⢠Extractor: Analyzes the AST to reconstruct the target procedureâs semantic contextâincluding interfaces, dummy arguments, declarations, array dimensions, loops, scopes, call graphs, and inter-procedural dependenciesâand produces metadata that underpins subsequent transformations. ⢠Shaper: Performs inter-procedural array shape inference, resolving implicit dimensions from declarations, interfaces, and argument propagation across call hierarchies. It reconstructs explicit array specificationsâincluding dimensions inferred via intrinsics such as SIZEâto supply complete shape information for transformations. ⢠Modifier: Implements the core OPENACC source-to-source transformation engine, applying deterministic, semantic-preserving rewrites to Fortran constructsâincluding array operations, implicit shapes, control-flow, procedure interfaces, and loopsâdirectly on the AST. ⢠Isolator: Reconstructs the target procedure and its dependency context into a standalone compilation unit for independent execution, validation, debugging, and transpilation. Table 1: Comparison between Fortran and Python programming languages. fntable Array Indexing n-based: arbitrary lower and upper bounds (e.g., a(-7:12)) 0-based: arrays start at index 0 Memory Layout Column-major order Row-major order; NumPy supports column-major via order=âFâ Array Slicing a(1:5) includes indices 1 through 5 (inclusive upper bound) a[0:5] includes indices 0 through 4 (exclusive upper bound) Performance Compiled for high-performance computing Interpreted by default; comparable performance via NumPy/JAX Mutability All arguments are mutable and updated in place NumPy arrays are mutable in place; scalars are immutableâmodified scalars must be explicitly returned Typing System Strongly, statically typed with explicit precision control Dynamically typed; precision must be set explicitly (e.g., np.float64) to match Fortran Argument Passing Pass-by-reference; subroutines modify arguments directly Pass-by-object-reference; mutable and immutable types must be handled separately to emulate in-place modification Execution Model Compiled Interpreted Variable Scope Implicit scoping rules; modules and CONTAINS define visibility Explicit scoping via functions, classes, and namespaces Array Operations Native array operations with elemental functions and array syntax Native operations via NumPy; Python built-ins do not support vectorized operations Data Types Native support for complex, character, logical, and user-defined types Native types include int, float, complex, bool, str; arrays via NumPy Parallelism Native support via Coarrays, MPI, OpenMP, OpenACC Via external libraries (e.g., MPI for Python, JAX, CuPy, Numba) 2.2 Transpilation Fortran and Python differ substantially in language semantics, execution model, memory representation, and variable management. As such, semantic equivalence cannot be achieved through direct syntactic translation alone; rather, each language difference is addressed through a corresponding compiler transformation, summarized in Table 1. The transpilation pipeline reconciles these differences through AST rewriting rules. Control-flow constructs are adapted to zero-based indexing; array allocation and slicing semantics are reconstructed while preserving Fortran memory ordering where needed, with numerical types mapped to NumPy equivalents (e.g., np.float64). Procedure interfaces are rewritten to convert mutable scalar arguments into explicit return values, aligning with Python calling conventions. 3 From isolated Fortran procedures to Python scripts The dependency-aware isolation stage produces standalone Fortran procedures with the metadata required for transpilation. Building on these units, the next stage generates executable Python scripts while preserving the separation between global state, procedure logic, and execution flow. The generated program comprises a global class encapsulating transpiled dependencies, global variables, module state, and initialization routines; and a main module orchestrating the execution. Encapsulating shared state within a classârather than using a moduleâimproves modularity, maintainability, and compatibility with subsequent transformations (Figure 2). The transpilation comprises two passes: FortranAST PythonPython Source-to-Python Translation PipelineEquinoxJax CompatibleAdjoint ModelTangent Linear Auto-differentiation Pipeline Figure 2: Workflow of the transpiler ⢠Transformer: Assembles the overall Python program by constructing the global class, and main module, generating initialization logic, integrating translated procedures, resolving dependencies, and producing a Python AST. ⢠F2NP: Performs the semantic lowering of isolated Fortran procedures into equivalent NumPy implementations. This stage rewrites language-specific constructsâincluding loop bounds, array indexing, intrinsic procedures, control-flow statements, and procedure interfacesâwhile preserving the execution logic of the original implementation. Subsequent NodeTransformer-based corrective passes perform additional semantic correctionsâincluding indexing semantics, attribute adjustments, etc âto produce executable Python code. Finally, standardized templates assemble the transformed AST into executable Python scripts. Fortran AST Node (Subroutine / Function) Send via recursive_ast visit_Subroutine / visit_Function Extract Signature Name, Arguments, Types Argument Normalization Intent handling, defaults Recursive Body Transformation Statements & Expressions Function or Subroutine? Generate Return Statement Generate Procedure Body Only Generate Python ast.FunctionDef Append to Module Body FunctionSubroutine Figure 3: Workflow of the F2NP transpiler 3.1 F2NP transpiler The F2NP stage lowers isolated Fortran procedures to equivalent NumPy implementations. For each procedure, it constructs a Python AST that preserves the original control-flow, procedure interfaces, and computational semantics. Figure 3 illustrates this process, from the parsed Fortran AST to the generated ast.FunctionDef. The transpiler uses Fortran AST, where each node includes parent and child relationshipsâenabling recursive traversal and coherent reconstruction of the equivalent Python AST. A principal challenge arises from the differing control-flow representations in Fortran and Python. Fortran explicitly terminates structures with END statements, whereas Python denotes block structure via indentation. The transpiler exploits the explicit termination markers in the Fortran AST to rebuild the Python hierarchy using two coordinated last-in-first-out (LIFO) stacks: ⢠A module stack that constructs the overall function body; ⢠A control stack that tracks the current nesting context of conditionals, loops, and other control-flow constructs. When a new control-flow construct is encountered, its corresponding Python AST node is created, attached to the current parentâs body, and pushed onto the control stack. Subsequent statements are appended to the top-of-stack node, allowing nested structures to be reconstructed incrementally during recursive traversal. Since the stack stores references to live AST nodes, modifications propagate immediately throughout the tree, obviating additional reconstruction passes. Conditional constructs require special handling, as some Fortran control-flow statements lack explicit terminators. An ELSEIF branch implicitly closes the preceding IF branch without an END statement; ELSEWHERE behaves similarly for WHERE constructs. To recover these implicit boundaries, the stack-based traversal is augmented with independent nesting counters for DO, IF, ELSEIF, WHERE, ELSEWHERE, and CASE constructs. These counters track the active nesting level of each type, enabling correct hierarchical reconstruction regardless of nesting complexity (Figure A2, Appendix A). With the control-flow hierarchy reconstructed, the F2NP transpiler applies semantic rewrite rules to resolve language-specific differences between Fortran and Python. These transformations operate locally on the AST and do not require surrounding program context: ⢠Loop-bound: Fortran loop bounds are adjusted for n-based to zero-based indexing in Python range expressions. ⢠Array-dimension: Fortran array bounds are transformed to NumPy dimensions via size=upperboundâlowerbound+1size=upperbound-lowerbound+1. ⢠Intrinsic-function: Fortran intrinsics (SUM, MIN, MAX, etc.) are mapped to NumPy equivalents, with DIM translated to axis regardless of explicitness. 3.2 Transformer The F2NP stage lowers isolated Fortran procedures to NumPy, while the Transformer stage assembles these components into complete Python programs. As the orchestration layer, it constructs the top-level AST, integrates transpiled procedures, generates initialization logic, and produces the final program structure for validation and evaluation (Figure A1). Using structural metadata from isolation, the Transformer constructs the NumPy organization into two components: a global class encapsulating shared variables, module state, initialization, and translated dependency procedures; and a main module handling execution, input, and validation. 3.2.1 Global class Construction of the global class proceeds in two stages: structural assembly and semantic assembly. First, global variables required by the isolated procedure are reconstructedâdeclarations, initial values, array dimensions, and input layouts are extracted from Fortran and translated to Python. Next, the transpiled procedures from F2NP are integrated. Dependencies are resolved via depth-first traversal of the call graph, ensuring lower-level routines are incorporated before their callersâpreserving execution semantics while producing a modular, object-oriented representation. The resulting global class provides a unified executable representation encapsulating global states and dependency procedures. 3.2.2 Main module The generated main module serves as the entry point for each isolated procedure, orchestrating environment initialization, procedure invocation, and numerical validation against the original Fortran implementation. Its construction proceeds in three stages: (i) analyzing the global class interface for correct access to procedures and state; (i) initializing local variables and input data using reference datasets from isolation; and (i) automatically generating validation routines to compare Python outputs against Fortran reference values. The main module also incorporates runtime measurements, enabling performance comparisons between the generated Python implementation and its Fortran counterpart. 3.3 Corrective passes Although F2NP preserves structural semantics, certain language-specific correctionsâarray lower bounds, class attribute mappings, and object-composition relationshipsârequire semantic information that only becomes available after program assembly. FGPT therefore applies two sequential corrective passes to the generated Python AST: AdjustIndices, which restores Fortran indexing semantics; and ReplaceGlobals, which reconstructs object-oriented attribute and method references. 3.3.1 Array index correction The first corrective pass, AdjustIndices (Figure 4), restores Fortran indexing semantics within Pythonâs zero-based framework. Driven by array metadata from isolation, each array is corrected according to its declared lower bound. Arrays with the default lower bound of one have index expressions shifted by one position; those with arbitrary bounds (e.g., 0:n or -3:n) require an explicit offset of (1 - lower bound), applied only to loop variables and identified index expressions. Beyond explicit indexing, functions like argmin and argmaxâwhich return zero-based indices in NumPy but one-based in Fortranâare adjusted by restoring the appropriate lower bound of the referenced array. For multidimensional arrays, the correction is derived from the axis argument to ensure dimensional correctness. To maintain consistency, variables with corrected indices are tracked; subsequent uses in assignments, loop bounds, conditionals, and comparisons reuse the existing correction rather than reapplying the offset. Shared Metadataarray_infoCONV_VARSadjusted_vars visit_Subscriptvisit_Assignvisit_Forvisit_Ifvisit_Call_adjust_index_apply_offset_if_convvar_adjust_assignment_rhs_handle_compare_handle_binop AST Visitor Methods Index Transformation Helpers AdjustIndices Python/NumPy AST input Adjusted Python AST Figure 4: AdjustIndices pass 3.3.2 Attribute resolution The second corrective pass, ReplaceGlobals (Figure 5), reconstructs the object-oriented structure introduced during program assembly. Because variables and procedure calls are translated before the class hierarchy is established, references to global state remain unattributed until the complete program structure is available. Once assembly is complete, structural metadata from isolation resolves these references: variables belonging to the global class are rewritten as self.attribute, and standalone function calls are converted to attributed method calls. This restores the shared stateâprocedure relationships established during reconstruction while conforming to Pythonâs object-oriented model. Attribute resolution applies uniformly across all expression contextsâassignments, loop bounds, conditionals, Boolean operations, comparisons, and formatted stringsâensuring consistent representation of shared state throughout the generated program. Metadatacls_info_local_scope visit_Namevisit_Attributevisit_Assignvisit_Callvisit_Forvisit_Ifvisit_BinOpget_attr_node_replace_compare AST Visitor Methods Name Resolution Helpers ReplaceGlobals Python/NumPy AST input Rewritten Python AST Figure 5: ReplaceGlobals pass 4 Numerical verification We verify that FGPT preserves the numerical precision of the original Fortran code when generating NumPy and JAX implementations by comparing outputs against the original Fortran. Since each procedure is isolated prior to transformation, reference inputs and outputs are generated for every translation unit, enabling translation errors to be localized to individual passes rather than only after full program generation. The same validation procedure is applied to both NumPy and JAX implementations. Numerical consistency is quantified using the maximum absolute error, maxâĄ(|TâTF|), ( |T-T_F | ), where T denotes the output of the transpiled implementation (NumPy or JAX) and TFT_F is the corresponding Fortran output. In practice, this criterion is evaluated using numpy.isclose (scalars and Booleans) and numpy.allclose (arrays), both applying |aâb|â¤atol+rtolâ |b|,|a-b| +rtol¡|b|, where a is the either NumPy or JAX implementation and b the Fortran reference. Default tolerances are rtol=1Ă10â5rtol=1Ă 10^-5 and atol=1Ă10â8atol=1Ă 10^-8. All benchmarks were performed on CPU unless otherwise stated. 4.1 Benchmark To evaluate the proposed pipeline, we consider the hydrology module from the IPSL land-surface model, comprising around 15,000 lines of Fortran code with the following procedures: hydrol_main/ |-- hydrol_soil |-- hydrol_vegupd |-- hydrol_alma |-- hydrol_canop |-- explicitsnow_main |-- hydrol_hydraulic_arch_tuzet_calc Several of these procedures call additional child procedures, which are recursively isolated and transpiled as part of the dependency analysis. Consequently, the entire hydrology workflow is translated into both NumPy and JAX. These procedures include multidimensional arrays, nested procedure hierarchies, conditional execution, iterative loops, intrinsic functions, and extensive module dependencies, providing a representative evaluation on a real-world scientific application. For each isolated procedure, numerical consistency is quantified by reporting the maximum absolute difference between the original Fortran outputs and the corresponding transpiled implementation. Complete results are presented in Figures 6(a)â6(d). (a) hydrol_main. (b) hydrol_soil. (c) explicitsnow_main. (d) hydrol_hydraulic_arch_tuzet_calc. Figure 6: Maximum absolute difference between Numpy and Fortran outputs Across all evaluated procedures, the maximum absolute difference is typically on the order of 10â910^-9. Differences of this magnitude are expected between the two implementations and arise primarily from two sources. First, floating-point arithmetic is not associative; mathematically equivalent implementations that perform operations in different orders may accumulate rounding errors differently. Second, although both implementations conform to the floating-point standard, they employ different execution strategies. Optimizing Fortran compilers perform instruction scheduling, vectorization, and architecture-specific optimizations, whereas NumPy delegates many operations to optimized BLAS and LAPACK libraries. These implementation differences can produce variations in floating-point rounding while remaining numerically equivalent. The performance is evaluated by comparing wall-clock execution time between the Fortran and NumPy implementations. Speedup is computed as Speedup=TFTN,Speedup= T_FT_N, where TFT_F and TNT_N denote the measured execution times of the Fortran and NumPy implementations, respectively. Figure 7(a) shows execution time and speedup for hydrology; child-procedure results are shown in Figures 7(b)â7(d). (a) hydrol_main. (b) hydrol_soil. (c) explicitsnow_main. (d) hydrol_hydraulic_arch_tuzet_calc. Figure 7: Runtime and speedup comparison between Fortran and Numpy implementations The NumPy implementation is order or magnitude slower than the original Fortran code, which is expected: Fortran is compiled by a compiler into efficient machine code, while the NumPy implementation executes through the Python interpreter, incurring additional overhead from interpretation, function calls, and object management. The performance gap is not uniform across procedures, however; where a procedureâs core computation maps cleanly onto NumPyâs vectorized primitives, the NumPy version approachesâand in some cases matchesâFortranâs performance. Conversely, procedures dominated by deeply nested loops or iterative updates favor Fortranâs compiled execution model. 5 Towards differentiable programming with JAX JAX (Bradbury et al., 2018) performs AD by tracing Python function execution to build a JAXPR, an intermediate representation, which can be transformed via forward- or reverse-mode AD and compiled by XLA for CPU, and GPU architectures. This execution model imposes stronger constraints than NumPy: JAX requires programs to be functional, so mutable state, in-place operations, and unsupported control-flow must be rewritten for tracing compatibility. Thus, the NumPy implementations from previous FGPT stages require an additional lowering step to adapt them to JAXâs model. FGPT employs a second transformation step that converts NumPy ASTs to JAX. The current implementation produces valid, XLA-compilable eqx.Module subclasses with JAX-traceable methods. However, explicit differentiation interfaces (jax.grad, jax.jvp, jax.vjp) are not yet generated, as the pipeline does not currently specify which variables are differentiation inputs or outputs. This work therefore focuses on validating the transformation pipeline itself rather than derivative correctness; user-configurable differentiation specifications will be addressed in future work. JAX transformations (jit, grad, jvp) operate on immutable pytrees, not arbitrary Python classesâa significant challenge for transpiled code. To preserve object-oriented structure while satisfying JAXâs functional model, FGPT uses Equinox (Kidger and Garcia, 2021), which represents Python classes as pytrees while remaining JAX-compatible. This allows the NumPy-derived class-based template to be retained without manual restructuring. Equinox is well-suited to automated transpilation because: ⢠Python classes inherit directly from eqx.Module, preserving the object-oriented organization of the translated program; ⢠Class attributes are automatically registered as pytree leaves, eliminating manual pytree construction; ⢠Static (non-differentiable) fields are identified explicitly using eqx.field(static=True), allowing metadata such as dimensions and parameters to remain outside the differentiation graph; ⢠Generated modules remain directly compatible with JAX transformations including jit, grad, jvp, vjp, and vmap; ⢠Forward and reverse-mode derivatives can be computed directly on class instances. Compared with higher-level frameworks such as Haiku or Flax, Equinox introduces minimal additional abstractionâa key advantage for automatically generated scientific code that must preserve the structural fidelity of the original Fortran implementation. 6 NumPy to JAX pipeline The JAX lowering step comprises two transformations. First, AutoDiff converts NumPy classes to Equinox modules, establishing the structure required for automatic differentiation. Second, JaxConverter rewrites computational kernels by replacing mutable array updates, adapting control-flow, and substituting NumPy operations with their JAX equivalents. 6.1 JaxConverter 6.1.1 Analysis-driven transformation JaxConverter is organized into specialized transformation modules (Figure 8) that implement the lowering operations described below. Unlike a direct statement-to-statement translation, this stage is analysis-driven and optimizing: loops and conditionals are first classified by supporting analysis classesâVectorizationAnalyser and Controlâwhich inspect the program structure without modifying the AST and produce metadata describing the appropriate transformation strategy. Specific handling is applied per construct (Table 2), as not all are equally traceable. Table 2: Summary of control-flow constructs compatible with jit compilation and automatic differentiation. fwd indicates forward-mode differentiation only; â denotes whether the loop may be unrolled. fntable jit grad if â â for ââ â while ââ â lax.while_loop â fwd lax.fori_loop â fwd lax.scan â â Control accumulates the metadata that later stages need to rewrite a given loop or conditional correctly. Conceptually similar to the control stack described in Section 3.1, it is designed to carry JAX-specific information: the kind of construct being tracked, its classification (as determined by VectorizationAnalyser), the axis it vectorizes along if applicable, and any additional context needed to preserve nested or masked conditions once the code is rewritten. These decisionsâwhether to vectorize, apply masked updates, or use indexed loopsâguide the JaxConverter in the AST rewrites. Python/NumPy AST input dispatch by node type (visit_*) classify_for / analyzer checks lower to lax primitive (scan/cond/vmap) Transformed JAX-traceable AST AST walknode kindloop/branch typerewriteJaxConverter(ast.NodeTransformer)core _ConditionalLowering visit_If dispatch, lax.cond synthesis _LoopLowering visit_For/While, lax.scan, vmap _DynamicLoop dynamic-bound loops, mask expansion control-flow lowering _ArrayUpdate visit_Assign, .at[âŚ] rewriting _Masking WHERE/ELSEWHERE, dynamic slice masks _Vectorization axis discovery, rank inference array & masking _BranchAnalysis assigned/read names, loop targets, logging _CallRewriting visit_Call/Expr, vmap wrap, helpers branch & call analysis _Scope scope stack, name-gen counters utilitycontrol-flowarray/maskingbranch/callJaxConverter: Transformation PipelineJaxConverter: Class Composition Figure 8: Workflow of the JaxConverter and its helper classes 6.1.2 Vectorization and loop handling Vectorization is applied along a user-specified loop variable provided as a pipeline input rather than inferred automatically. In the hydrology case study, this variable is kjpindexâthe dominant and most computationally expensive loop index across most kernels. Instances of this variable are broadcast across dimensions or handled via pairwise indexing to enable parallel execution. Remaining loops are rewritten using lax.scan, which preserves loop structure within the computational graph rather than unrolling it, improving tracing efficiency and reducing memory usage under jit. Because vectorization introduces additional broadcast dimensions, reduction operations must be handled carefully to recover the correct scalar or lower-dimensional output. VectorizationAnalyser classifies loops and conditionals by vectorization potential, while Control tracks control-flow metadata during transformation. A RemoveLogging pass removes logging and print statements, as these serve no purpose in a differentiable, high-performance context. 6.1.3 Outer-to-inner recursive strategy JAX traces symbolically, propagating abstract tracers (shape and dtype) rather than concrete values, so control-flow and array shapes must be statically known. Data-dependent loops must therefore be restructured into functional primitives such as lax.scan or lax.cond before tracing. This requirement shapes the converterâs recursive strategy: rather than a pure depth-first traversal, JaxConverter restructures outer control-flow constructs before recursing into their bodies. For example, converting a for loop into lax.scan first extracts and encapsulates the loop body into a new scan function, and only then applies a depth-first traversal within that encapsulated body via self.visit. This outer-to-inner ordering preserves higher-level semantic contextâtransforming nested constructs before their enclosing loop or conditional is resolved risks losing vectorization or scan-boundary information. 6.1.4 Helper-function construction Some JAX control-flow constructs, e.g., lax.scan and lax.cond, require their computation bodies to be represented as standalone helper functions. These are constructed incrementally using a worklist during transformation and finalized once the enclosing function is fully processed. Each helper is transformed within its own scoped context, ensuring isolation from sibling and parent scopes. 6.1.5 Dimensional consistency Promoting a variable from a scalar to an array indexed along the vectorization axis may introduce dimensional inconsistencies in expressions that combine it with variables of different shapes. MaybeAddIndexTransformer detects and corrects this situation: it compares the rank each expression is expected to have against the rank it has once vectorization has been applied, and where a lower-rank operand needs to be broadcast against a higher-rank one along the vectorized axis, it inserts the minimal indexing needed to align them. This lets vectorization be applied locally, one variable at a time, without requiring every expression in a subroutine to be manually re-derived for shape consistency; only the specific operands that fall out of alignment as a result of vectorization are touched, leaving the rest of the expression unchanged. 6.1.6 Reduction semantics Reduction operations are often written without an explicit axis in the original NumPy code, relying on an implicit "reduce over everything" default. This ambiguity is incompatible with JAX tracing and differentiation, where the shape of every intermediate value must be known statically. ReductionHandler resolves this by inferring, for each reduction call, which axis or axes it should act over, based on the structure of the expression being reduced and the known dimensionality of its operands, and then rewriting the call to specify that axis explicitly. Where a reductionâs operand carries a vectorized axis, that axis is excluded from the inferred reduction, so that vectorization introduced earlier in the pipeline is not silently collapsed by a later reduction operating over the wrong dimension. 6.1.7 Corrective passes Following the primary transformations, two corrective passes ensure semantic correctness. MaybeAddIndexTransformer resolves shape and broadcasting inconsistencies, while ReductionHandler inserts explicit reduction axes where required to preserve the intended reduction semantics. Like the main transformation modules, these passes operate directly on the AST. 6.2 AutoDiff The AutoDiff class orchestrates the transformation of NumPy modules into JAX- and Equinox-compatible programs, structurally preparing them for automatic differentiation in either forward (tangent-linear) or reverse (adjoint) mode. This stage follows the same orchestrated transformation pattern as the Fortran-to-NumPy pipeline, but must additionally satisfy the functional purity, immutability, and PyTree constraints imposed by JAX and Equinox. Main file + Class file: parsed and validated as ast.Modulecorrect_main: patch Main module(JAX imports, x64, input wrapping)_prepare_class: restructure class module (see right)Patch main with class_modif(insert timer, fix test calls, rename instances)Fix imports & write both modules to filesFinal Python files: *_jax.py / *_d.py / *_b.py1. Add eqx.Module base, rename class2. Define static / dynamic fields3. Strip numpy scalar casts from __init__4. Rewrite declaration_initializationfor JAX file reading5. Convert method bodies via JaxConverter(topological order, leaves first)6. Decorate outermost methodwith @eqx.filter_jit7. Build class_modif + timer_node Figure 9: Workflow of AutoDiff 6.2.1 Transformation ordering and module conversion Unlike the Fortran-to-NumPy transpilation, which proceeds sequentially from class definitions to calling scripts, the Equinox transformation begins with the main script. This ordering is necessary because main defines the concrete input arguments (e.g., dimensions and types) that seed shape propagation and guide subsequent transformationsâinformation required both for the NumPy-to-JAX conversion and for correct tracing downstream. The main function is rewritten to enable JAX double precision, replace NumPy arrays with jnp.asarray, explicitly assign JAX dtypes (jnp.int32, jnp.float64) to scalar inputs, and insert two calls to the transformed function: a warm-up call that triggers JAX tracing and compilation, and a second call that measures execution performance with compilation overhead excluded. Additional script-level changes insert the required JAX and Equinox imports, convert np operations to jnp, and integrate a timing utility. As with earlier stages, all modifications are performed on the AST and the scripts are regenerated via ast.unparse. Class definitions are then rewritten to inherit from eqx.Module, converting them into Equinox PyTree-compatible modules. Fields are partitioned into dynamic fields (differentiable arrays and runtime values) and static fields, marked via eqx.field(static=True) to exclude structural parameters from differentiation. This separation is required because Equinox needs explicit control over which attributes participate in PyTree traversal, and by extension in any future gradient computation. 6.2.2 Functionalizing mutations Beyond syntactic np-to-jnp substitution, the class-level read method requires further restructuring because JAX arrays are immutable and Equinox modules behave as PyTreesâin-place attribute updates of the form self.attribute = value are not permitted. Assignments are therefore rewritten into a functional update scheme: scalar values are wrapped with explicit JAX dtypes, array initializations (e.g., np.zeros) are adjusted to JAX-compatible constructors, and direct assignments or setattr calls are replaced with updates to an intermediate dictionary that accumulates modified attributes. Once all assignments are processed, the model instance is functionally reconstructed via eqx.tree_at, producing a new PyTree with the updated fields rather than mutating the original object. This makes the resulting module structurally compatible with JAX transformations such as jit, which require side-effect-free execution. Trivial control-flow branches that could introduce data-dependent execution paths, e.g., conditionals containing only a continue statement, are removed, as they could interfere with symbolic tracing. 6.2.3 Integration and output Once structural and functional transformations are complete, the classâs computational methods are processed by JaxConverter (Section 6.1), transformed in dependency order so that callees are rewritten before their callers. The parent function is decorated with eqx.filter_jit and inserted back into the class definition. The output of this stage is a fully functional Equinox module that preserves the semantics of the original NumPy implementation while satisfying JAXâs tracing requirements. Figure 9 illustrates this workflow, showing the transformation of the main module and class module, including functional updates, JAX conversion, and final code generation via ast.unparse. (a) hydrol_main. (b) hydrol_soil. (c) explicitsnow_main. (d) hydrol_hydraulic_arch_tuzet_calc. Figure 10: Maximum absolute deviation between JAX and Fortran outputs 6.3 Numerical verification This section extends the verification framework from Section 4 to the JAX implementations, assessing both numerical consistency and runtime performance. The same validation protocolâmaximum absolute difference and wall-clock timingâis applied throughout. Because the JAX transformation preserves the computational graph of the transpiled NumPy code, its forward output must be verified directly against the original Fortran implementation. As shown in Figure 10, numerical consistency is preserved throughout the complete translation pipeline, from Fortran to Python and subsequently to JAX. Most routines fall into one of two groups: exact matches, or exhibit only floating-point differences on the order of 10â1410^-14 to 10â1810^-18 for the highest-level parent routines (e.g., explicitsnow_main, and hydrol_soil, where small floating-point rounding differences accumulate along the call chain. All observed differences remain several orders of magnitude below the validation tolerances (rtol=1e-5, atol=1e-8). Performance is evaluated for all three implementations: original compiled Fortran, NumPy, and JIT-compiled JAX. Figure 11(a) reports execution time and speedup for hydrology, while Figures 11(b)â11(d) present the corresponding results for its child procedures. Figure 12 then summarizes all three configurations together, reporting both NumPy and JAX speedups relative to Fortran alongside the Fortran runtime for each high-level routine. Across nearly all procedures, the JAX implementation is faster than both the Fortran reference and the NumPy implementation. This speedup is consistent throughout the procedure hierarchyâit holds for both parent routines and their lower-level children, indicating that the benefits of vectorization and JIT compilation propagate across the call chain. As shown in Figure 12, the JIT-compiled JAX implementation achieves a 3.66Ă speedup over the Fortran implementation, owing to vectorization and JIT compilation. This improvement is particularly evident for routines such as hydrol_soil, which exhibits significantly faster execution (Table 3). (a) hydrol_main. (b) hydrol_soil. (c) explicitsnow_main. (d) hydrol_hydraulic_arch_tuzet_calc. Figure 11: Runtime comparison and speedup between Jax and Fortran Table 3: JAX, NumPy, and Fortran runtimes in seconds. fntable hydrol_alma hydrol_canop hydrol_flood hydrol_vegupd hydrol_soil explicitsnow_main hydrol_hydraulic_arch_tuzet_calc JAX(CPU) 0.000599 0.001840 0.000796 0.004448 0.257696 0.009416 13.007017 NumPy(CPU) 0.043769 0.308702 0.068819 0.649055 66.780735 1.222364 32.056070 Fortran(CPU) 0.000904 0.010225 0.001814 0.023951 1.960143 0.027354 4.606041 The only exception is hydrol_hydraulic_arch_tuzet_calc, whose Fortran implementation consists of an outer vectorization loop that repeatedly calls child routinesâa structure that cannot be vectorized by JAX, resulting in no performance gain over the Fortran version. 6.3.1 NumPy and JAX Numerical Consistency Although the Python and JAX implementations are generated from the same intermediate representation, they do not produce identical numerical differences with respect to the Fortran. This is expected: the two backends execute the same mathematical expressions through different execution stacks, causing floating-point rounding errors to accumulate independently. The JAX implementation is compiled through XLA, which may fuse, reorder, or vectorize arithmetic operations, producing a different sequence of floating-point operations and, consequently, slightly different rounding behavior. For example, in the routine explicitsnow_main, the mean maximum absolute difference is âź10â12 10^-12 for the NumPy implementation and âź10â16 10^-16 for the JAX implementation. For both backends, the largest numerical deviations occur in the same parent routines (e.g., explicitsnow_main), indicating that the dominant source of numerical discrepancy is the accumulation of rounding error along the call hierarchy, rather than any backend-specific implementation difference. 6.4 AD verification The current pipeline produces JAX and Equinox modules that are structurally ready for differentiation but does not yet produce jax.grad, jax.jvp, or jax.vjp call sites, since the pipeline does not yet specify which variables gradients should be taken with respect to. Therefore, gradient evaluation is outside the scope of the present work. Nevertheless, the generated modules are fully compatible with JAXâs transformation and are structurally prepared for AD. Once differentiation targets are introduced, gradient correctness will be validated by comparing JAX-derived gradients against those produced by Tapenade from the original Fortran implementation. Figure 12: Performance evaluation between Fortran, NumPy and JAX 7 Conclusions This work presented FGPT, a general-purpose source-to-source transpilation framework for modernizing legacy Fortran scientific code through automatic translation into NumPy and JAX implementations. FGPT combines dependency-aware subroutine isolation with an AST-based transformation pipeline that preserves the semantic structure of the original program while producing readable, maintainable Python code suitable for scientific computing. The framework is organized around two transformation stages. The F2NP component performs the semantic lowering of Fortran codes into equivalent NumPy implementations, resolving language-specific differences such as array indexing, intrinsic procedures, control-flow reconstruction, and procedure interfaces. The Transformer component assembles these translated procedures into complete executable Python modules by resolving dependencies, constructing the global program structure, generating initialization logic, and producing standalone programs for execution and verification. A subsequent stage corrects NumPy indexing semantics and resolves object-oriented attribute mappings. Building upon the NumPy representation, FGPT further generates JAX-compatible implementations by transforming the intermediate Python AST into executable eqx.Module classes and adapting language constructs to satisfy JAXâs tracing and functional programming model. Mutable array operations and incompatible control-flow constructs are rewritten using JAX primitives such as lax.scan, jax.less_than, and lax.cond, producing code that is compatible with XLA compilation and accelerator backends including GPUs and TPUs. Verification on representative modules from the IPSL land-surface model demonstrates that the framework preserves numerical consistency to within floating-point precision while successfully translating complex scientific code containing deeply nested procedure hierarchies, multidimensional arrays, and extensive module dependencies. These results demonstrate that a compiler-inspired, dependency-aware transpilation pipeline provides a practical and scalable alternative to manual rewriting and emerging LLM-assisted translation approaches by offering deterministic, reproducible transformations. The current implementation nevertheless has several limitations. Support for all modern Fortran language features has not yet been fully implemented; Table 4 summarizes the constructs currently handled by FGPT alongside those identified for future extension. Additional work is required to extend the translation rules to these more advanced language constructs and object-oriented features. Future releases will therefore expand the coverage of the transpiler while also introducing configurable AD interfaces capable of generating jax.grad, jax.jvp, and jax.vjp wrappers directly from user-specified differentiation targets. Together, these extensions will broaden the applicability of FGPT to increasingly complex scientific codes and further facilitate the modernization of legacy Earth system and high-performance computing applications. Table 4: Fortran language constructs in FGPT. fntable Fortran Construct Status DO loops Supported IF / ELSE IF / ELSE Supported WHERE / ELSEWHERE Supported CASE / SELECT CASE Supported Array declarations and slicing Supported Intrinsic procedures (SUM, MIN, MAX, etc.) Supported Module variables and shared state Supported External subroutine/function calls Supported Multidimensional array operations Supported Pointers Not yet supported Derived Types Not yet supported Interface Blocks Not yet supported Backmatter Acknowledgments We acknowledge the EuroHPC Joint Undertaking for awarding access to the EuroHPC supercomputer LEONARDO, hosted by CINECA (Italy) and the LEONARDO consortium, through the EuroHPC Development Access calls (EHPC-DEV-2024D10-070 and EHPC-DEV-2025D08-095). Funding Statement This work was supported by the Horizon Europe project AI4PEX (Grant No. 101137682). Competing Interests None Data Availability Statement The FGPT source code is publicly available at https://github.com/kardaneh/Fgpt under the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The benchmark data, including the isolated Fortran procedures, transpiled NumPy and JAX implementations, and reference inputs and outputs used for validation, are available on Zenodo at https://zenodo.org/records/21640037. The IPSL land-surface model used for evaluation is available to the research community under the terms of its own license. Ethical Standards The research meets all ethical guidelines, including adherence to the legal requirements of the study country. Author Contributions All authors contributed equally. Supplementary Material None References Häfner et al. (2018) Häfner D., Jacobsen R. L., Eden C., Kristensen M. R. B., Jochum M., Nuterman R., Vinter B. (2018) Veros v0.1 â a fast and versatile ocean simulator in pure Python, Geoscientific Model Development 11(8), 3299â3312. Bradbury et al. (2018) Bradbury J., Frostig R., Hawkins P., Johnson M. J., Katariya Y., Leary C., Maclaurin D., Necula G., Paszke A., VanderPlas J., Wanderman-Milne S., Zhang Q. (2018) JAX: composable transformations of Python+NumPy programs, http://github.com/jax-ml/jax. Kidger and Garcia (2021) Kidger P., Garcia C. (2021) Equinox: neural networks in JAX via callable PyTrees and filtered transformations, arXiv preprint arXiv:2111.00254. Koldunov et al. (2026) Koldunov N. V., Cheedela S. K., Danilov S., Sidorenko D., Beyer S., Jung T. (2026) An Ocean Model Ported by a Large Language Model: Experience and Lessons from FESOM2 (Fortran to C to C++/Kokkos), arXiv preprint arXiv:2606.11356. Lahlou et al. (2026) Lahlou A., Hawkins L., Gentine P. (2026) Systematic LLM Translation of Legacy Scientific Code to Differentiable Frameworks: Application to a Land Surface Model, arXiv preprint arXiv:2606.07681. STFC (2026) UKRI Science and Technology Facilities Council (2026) stfc/fparser, https://github.com/stfc/fparser. Peterson (2009) Peterson P. (2009) F2PY: a tool for connecting Fortran and Python programs, International Journal of Computational Science and Engineering 4(4), 296â305. Van Der Walt et al. (2011) Van Der Walt S., Colbert S. C., Varoquaux G. (2011) The NumPy array: a structure for efficient numerical computation, arXiv preprint arXiv:1102.1523. Zhou et al. (2024) Zhou A., Hawkins L., Gentine P. (2024) Proof-of-concept: Using ChatGPT to Translate and Modernize an Earth System Model from Fortran to Python/JAX, arXiv preprint arXiv:2405.00018. Appendix A Appendix A.1 Environment and reproducibility The Fortran codes were compiled using mpif90 with the NVIDIA HPC SDK nvfortran backend. CPU builds used the following flags: -Wall -g -O0 -Kieee -Ktrap=fp -Mbounds -traceback -r8 -i4 The -r8 flag promotes all default REAL variables to 8-byte precision, while -i4 sets default INTEGER to 4 bytes. GPU builds additionally used -acc -gpu=c80, though GPU execution was not used for the benchmarks reported here. The Fortran environment linked against NetCDF (C and Fortran), IOIPSL, XIOS, and Tapenade. All benchmarks were executed on a single core of an AMD EPYC 7302 16-Core Processor with 6 GiB of RAM, running Ubuntu 20.04.6 LTS. The Python environment consisted of Python 3.10.20, NumPy 2.2.6, and JAX 0.6.2. Fortran Source CodeIsolation and ExtractionParsing, normalization, and procedure-unit extraction. F2NP TransformationFortran Procedure â Python FunctionGlobal ProcedureExtractionMain ProcedureExtractionPython Class EmissionPython Script EmissionGenerated Python Source (AST)ReplaceGlobalsName and Attribute resolutionAdjustIndicesIndex NormalizationDependency OrderingFinal Normalized Python CodeFortran ASTPython ASTGlobal scopeMain routine Figure A1: End-to-end transpilation pipeline Start ParsingIf StatementCreate ast.If nodePush to control stackNext Statement?Else If StatementElse Statement Append to previous element of the control stack End If Statementelifno more brancheselseother stmtCreate new ast.If nodePush âorelseâ list of previous if to control stackElif counter >0?Append to orelse of previous ifPush to control stackPop from control stackPop IF from control stackDecrement elif counterDoneyesnoIncrement elif counter Figure A2: Internal diagram of ELSE/ELSE IF in Python AST with respective to Contorl Stack