Skip to content

Abstract the external-code execution layer #671

Description

@HongSik-Yun-Fusion

Background

The external-code adapters under vaft.code increasingly follow a common scientific workflow:

prepare inputs
    ↓
run code
    ↓
collect outputs

The Config / Inputs / Result and prepare_* / run_* / collect_* APIs are becoming consistent, but the actual execution logic is still implemented separately inside each adapter.

For example, EFIT, CHEASE, TES, and GPEC each invoke external processes directly and handle runtime concerns independently, including:

  • executable resolution
  • working directory handling
  • environment variables
  • command-line arguments
  • timeout handling
  • stdout / stderr capture
  • return-code handling
  • future MPI or scheduler-based execution

At the same time, not every solver is an external executable. TokaMaker is currently driven in-process through its Python API.

This suggests that the scientific adapter boundary and the execution mechanism should be separated explicitly.

Goal

Allow each physics-code adapter to focus on scientific input/output translation while delegating process execution to a common execution layer.

Conceptually:

ODS / scientific configuration
            ↓
       code adapter
   prepare / run / collect
            ↓
      execution layer
            ↓
   local / MPI / scheduler
            ↓
       physics code

The goal is not to unify all solver implementations, but to make the distinction explicit:

scientific adapter != execution mechanism

Proposed direction

Introduce a small execution backend protocol, for example:

class ExecutionBackend(Protocol):
    def run(
        self,
        command,
        *,
        workdir,
        env=None,
        timeout=None,
        nproc=1,
    ) -> ExecutionResult:
        ...

ExecutionResult should initially provide at least:

returncode
stdout
stderr

Additional execution metadata could be added later if useful, such as:

launcher command
process / job identifier
elapsed time
execution metadata

The first implementation only needs to support local subprocess execution:

ExecutionBackend
    └── LocalBackend

Future backends could then be added without changing code-specific scientific APIs:

LocalBackend
MPIBackend
SlurmBackend
...

Relationship to CodeConfig / CodeInputs / CodeResult

vaft.code.base already defines:

CodeConfig
CodeInputs
CodeResult
CodeRunner

The execution abstraction should therefore be designed as an extension of the existing structure rather than as a separate framework.

In particular, the responsibility boundary should be clarified for runtime fields that are currently repeated across code-specific configs:

executable
workdir
args
env
timeout

Scientific and numerical configuration should remain owned by each adapter. The purpose of the execution layer is to remove duplicated execution behavior, not to eliminate code-specific configuration.

Python-native solvers

The abstraction must not assume that every solver is launched as a subprocess.

TokaMaker is currently executed in-process through a Python API. For the initial implementation, subprocess-based adapters can adopt the common execution backend while TokaMaker keeps its current execution path.

A later design can decide whether Python-native execution should also implement the same backend protocol.

Proposed scope

1. Define a common execution result and backend interface

Introduce the minimum types needed to describe a local code execution.

2. Implement a local subprocess backend

The backend should cover the common behavior currently duplicated across external-code adapters.

3. Migrate one representative adapter

Apply the backend to one relatively simple subprocess-based adapter first and use that migration to validate the interface.

There is no need to migrate every adapter in the first change.

4. Migrate remaining subprocess adapters incrementally

Once the interface is stable, move direct process-launch logic from EFIT, CHEASE, TES, GPEC, and similar adapters into the shared execution layer.

Out of scope

This issue does not require:

  • changes to the Snakemake workflow structure
  • changes to the ODS / OMAS data model
  • unifying scientific configuration across physics codes
  • replacing all adapters with one generic solver API
  • changing the TokaMaker execution model
  • implementing Slurm/PBS support in the first iteration

MPI and scheduler support can be added later after the abstraction is validated.

Expected benefits

  • reduce duplicated process-execution code across adapters
  • make timeout, environment, logging, and error-handling conventions consistent
  • allow MPI or scheduler support without reimplementing it per physics code
  • make adapter behavior easier to test with fake/mock execution backends
  • separate scientific adapter logic from machine/runtime-specific behavior
  • reduce the cost of integrating additional external physics codes

Acceptance criteria

  • A common execution backend interface and result type are defined.
  • A local subprocess backend is implemented.
  • At least one existing external-code adapter uses the backend.
  • Existing public scientific APIs and result semantics for that adapter are preserved.
  • The execution layer can be unit-tested without requiring the real external physics executable.
  • The design leaves room for future MPI / scheduler backends without requiring changes to adapter-facing scientific APIs.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions