Metadata-Version: 2.4
Name: foampilot-csc
Version: 1.0.2
Summary: Python orchestration for OpenFOAM, SmartSim, and machine-learning workflows on CSC systems
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: foamlib<2,>=1.7
Requires-Dist: jinja2
Requires-Dist: numpy

# FoamPilot CSC

FoamPilot CSC is a Python interface for coordinating OpenFOAM simulations with SmartSim and SmartRedis.

It provides a compact API for:

* inspecting and preparing OpenFOAM cases,
* generating meshes and decomposition files,
* configuring OpenFOAM-to-SmartRedis field transfer,
* sending modified fields back to OpenFOAM,
* running OpenFOAM through SmartSim,
* aggregating and scattering fields across MPI ranks,
* and implementing Python-driven LES closure models.

FoamPilot CSC is developed as part of the SmartSim-CSC stack and is primarily intended for CSC HPC environments.

## Installation

Install the Python package from PyPI:

```bash
python -m pip install foampilot-csc
```

FoamPilot can be imported without SmartSim or SmartRedis installed. Running an integrated OpenFOAM workflow, however, requires:

* SmartSim,
* SmartRedis,
* the SmartSim-enabled OpenFOAM libraries,
* and a compatible OpenFOAM runtime.

On the CSC SmartSim-CSC installation, load the environment before starting Python or Jupyter:

```bash
source /scratch/<project>/<user>/Utilities/Python4SmartSim.sh
```

## Basic usage

Create a `FoamCase` from an existing OpenFOAM case directory:

```python
from pathlib import Path

import foampilot as fp

case = fp.FoamCase(
    path=Path("/path/to/openfoam/case"),
    poll_interval=0.05,
    poll_timeout=60.0,
)
```

Prepare and inspect the case:

```python
report = case.initialize(
    clean=True,
    block_mesh=True,
    check_mesh=True,
    check_solver=True,
)

print(report.available_objects)
print(report.available_fields)

print(case.execution.exe)
print(case.execution.exe_args)
```

`initialize()` can perform the common OpenFOAM preparation steps before the case is launched.

For MPI execution, provide the number of subdomains:

```python
report = case.initialize(
    clean=True,
    block_mesh=True,
    check_mesh=True,
    check_solver=True,
    decompose=8,
)
```

FoamPilot then configures the returned execution command for parallel OpenFOAM execution.

## Field transfer

Use `case.configure()` to define data flowing between OpenFOAM and SmartRedis.

### OpenFOAM to SmartRedis

```python
outbound = case.configure(
    fields=["U", "p"],
    direction="of -> db",
    transfer_mode="write",
)
```

### SmartRedis to OpenFOAM

```python
inbound = case.configure(
    fields=["U"],
    direction="db -> of",
    transfer_mode="write",
)
```

Supported transfer modes are:

* `"write"`: transfer at OpenFOAM write times,
* `"all"`: transfer every timestep,
* a positive integer: transfer every specified number of timesteps.

Receive all configured outbound fields:

```python
fields = outbound.receive_fields(
    client,
    time_index=10,
)

U = fields["U"]
p = fields["p"]

x = fields["x"]
y = fields["y"]
z = fields["z"]
t = fields["t"]
```

For standard field transfer, FoamPilot also returns:

* `x`, `y`, and `z`: cell or patch coordinates,
* `t`: the OpenFOAM physical time as a Python `float`.

Send a field back to OpenFOAM:

```python
inbound.send(
    client,
    field="U",
    values=modified_U,
    time_index=10,
)
```

When the OpenFOAM case runs with MPI, FoamPilot transparently combines rank-local arrays when receiving fields and splits global arrays back into their original rank layout when sending fields.

## SmartSim execution

FoamPilot provides the OpenFOAM executable and arguments, while SmartSim controls the actual process launch.

```python
from smartsim import Experiment
from smartredis import Client

exp = Experiment(
    name="openfoam-example",
    exp_path="/path/to/experiment",
    launcher="local",
)

db = exp.create_database(
    port=6780,
    interface="lo",
)

exp.generate(db, overwrite=True)
exp.start(db, block=False)

client = Client(
    address=db.get_address()[0],
    cluster=False,
)

execution = case.execution

run_settings = exp.create_run_settings(
    exe=execution.exe,
    exe_args=execution.exe_args,
)

of_model = exp.create_model(
    name=case.name,
    run_settings=run_settings,
)

exp.start(
    of_model,
    block=False,
)
```

Wait for the OpenFOAM process to reach a terminal state:

```python
status = fp.wait_for_model(
    exp,
    of_model,
)

print(status)
```

The same FoamPilot API can be used with a SmartSim `local` or `slurm` launcher.

## Python-driven LES closure

FoamPilot supports an online LES workflow using the bundled `smartSimLES` OpenFOAM model.

Create an LES case:

```python
case = fp.FoamCase(
    path="/path/to/les/case",
    simulation_type="les",
    poll_interval=0.05,
    poll_timeout=60.0,
)
```

Configure the SmartRedis-driven closure model:

```python
case.configure_closure(
    model="smartSimLES",
)
```

Configure closure inputs and outputs:

```python
outbound = case.configure(
    fields=["grad(U)"],
    direction="of -> db",
    transfer_mode="all",
)

inbound = case.configure(
    fields=["nut"],
    direction="db -> of",
    transfer_mode="all",
)
```

Process closure requests as OpenFOAM advances:

```python
for time_index in outbound.stream(
    client,
    experiment=exp,
    model=of_model,
    start_time_index=0,
):
    features = outbound.receive_fields(
        client,
        time_index=time_index,
    )

    grad_U = features["grad(U)"]
    V = features["V"]
    t = features["t"]

    nut = closure_model(
        grad_U,
        V,
    )

    inbound.send(
        client,
        field="nut",
        values=nut,
        time_index=time_index,
    )

    print(
        f"time_index={time_index}, "
        f"physical_time={t:.6e}, "
        f"nut_mean={nut.mean():.6e}"
    )
```

For LES closure exchange:

* `grad(U)` is returned when explicitly requested,
* `V` is automatically included as cell-volume metadata,
* `t` is automatically included as physical-time metadata,
* and `nut` is scattered back to the corresponding MPI ranks.

`outbound.stream()` monitors the OpenFOAM model and yields each available closure timestep. It stops when the model reaches a terminal state and no unprocessed closure data remain.

## API overview

### `FoamCase`

```python
fp.FoamCase(
    path,
    simulation_type="laminar",
    poll_interval=0.01,
    poll_timeout=10.0,
    handshake_mode="blocking",
    optional_timeout=0.005,
)
```

Important methods and properties:

```python
case.initialize(...)
case.configure(...)
case.configure_closure(...)
case.execution
case.name
case.field_names
case.boundary_names
```

### Outbound relay

```python
outbound.receive_fields(...)
outbound.stream(...)
```

### Inbound relay

```python
inbound.send(...)
```

### Process monitoring

```python
fp.wait_for_model(...)
```

## Runtime notes

FoamPilot configures OpenFOAM cases but does not provide OpenFOAM, SmartSim, SmartRedis, or the required compiled OpenFOAM libraries.

The OpenFOAM runtime must contain the SmartSim-CSC integration libraries expected by the generated case configuration.

For CSC installations, the recommended approach is to install the complete SmartSim-CSC stack and load its generated environment script before using FoamPilot.

The package version and the installed SmartSim-CSC commit should remain compatible, particularly when using online LES closure functionality.

## Project status

FoamPilot CSC is an actively developed interface focused on SmartSim-driven OpenFOAM workflows on CSC systems.

The current public API centres on:

```python
FoamCase
FoamCase.initialize
FoamCase.configure
FoamCase.configure_closure
FoamFieldRelay.receive_fields
FoamFieldRelay.stream
FoamClosureRelay.receive_fields
FoamClosureRelay.stream
FoamFieldRelay.send
FoamClosureRelay.send
wait_for_model
load_environment
```

Legacy API aliases and compatibility wrappers are not guaranteed to remain available between releases.

## Licence

FoamPilot CSC is distributed under the licence included with the SmartSim-CSC source distribution.
