Skip to content

Setting up a Simulation

This guide covers how to create and configure Kamayan simulations using either C++ or Python.

C++

int main(int argc, char *argv[]) {
  auto pman = kamayan::InitEnv(argc, argv);

  auto units = std::make_shared<kamayan::UnitCollection>(kamayan::ProcessUnits());

  auto simulation = std::make_shared<kamayan::KamayanUnit>("isentropic_vortex");
  simulation->SetupParams = kamayan::isentropic_vortex::Setup;
  simulation->InitializeData = kamayan::isentropic_vortex::Initialize;
  simulation->ProblemGeneratorMeshBlock = kamayan::isentropic_vortex::ProblemGenerator;
  units->Add(simulation);

  auto driver = kamayan::InitPackages(pman, units);
  auto driver_status = driver.Execute();

  pman->ParthenonFinalize();
}

Kamayan itself is more of a library of KamayanUnits and a driver that is steered by user code. Simulations must provide their own main function, which will initialize kamayan, build a driver and execute the evolution loop. The main work of creating a new simulation is in building the driver from a UnitCollection. Kamayan provides a kamayan::ProcessUnits() function that will build the default set of units, which can be modified however one wishes. Additionally, units may be added into the UnitCollection, which can hook into any of the interfaces described in Kamayan Infrastructure. Most importantly a unit that provides a ProblemGenerator should be added to set the initial conditions.

Finally the new problem can be added to the build with a provided cmake function.

problems/CMakeLists.txt:add
add_problem(isentropic_vortex.cpp isentropic_vortex)

Python

Introduction

The design of kamayan as a library of component units that can be used to build a standalone C++ program to run a simulation allows for a very similar program to be constructed using the provided Python bindings (pyKamayan).

API Reference

For detailed documentation of all Python classes and functions, see the API Reference.

Building a Simulation with pyKamayan

problems/sedov.py
@kamayan_app(description="Sedov blast wave simulation")
def sedov() -> KamayanManager:
    """Build the KamayanManager for Sedov."""
    units = kman.process_units(
        "sedov", setup_params=setup, initialize=initialize, pgen=pgen
    )
    km = KamayanManager("sedov", units)

    nxb = 32  # zones per block
    nblocks = int(128 / 32)  # number of blocks to get 128 zones at coarsest resolution
    km.grid = AdaptiveGrid(
        xbnd1=(-0.5, 0.5),  # xmin/max
        xbnd2=(-0.5, 0.5),  # ymin/max
        nxb1=nxb,  # zones per block along x
        nxb2=nxb,
        num_levels=3,  # 3 levels of refinement
        nblocks1=nblocks,  # number of root blocks in each direction
        nblocks2=nblocks,
    )
    km.grid.refinement_fields.add("pres")
    km.grid.boundary_conditions = gr.outflow_box()

    km.driver = driver.Driver(integrator="rk2", tlim=0.05)
    km.outputs.add("restarts", "rst", dt=0.01)
    km.physics.hydro = Hydro(reconstruction="wenoz", riemann="hllc")
    km.physics.eos = eos.GammaEos(gamma=5.0 / 3.0, mode_init="dens_pres")

    km.params["sedov"] = {"density": 1.0, "pressure": 1.0e-5, "energy": 1.0}
    return km

In complete analogy to the C++ approach, a UnitCollection is made using kamayan_manager.process_units to construct a "sedov" unit using the provided Python callbacks and taking the default unit collection provided by kamayan. This is used to build the KamayanManager object that is used to set input parameters and run the simulation. The KamayanManager owns a set of properties that can be set with the various objects provided by the kamayan.code_units module to set various input parameters in common combinations. Finally, any arbitrary input parameter can be set through the KamayanManager.params property.

See the Simulation Manager and Core Module in the API reference for detailed documentation.

KamayanManager

kamayan.kamayan_manager.KamayanManager

Manages the an instance of the kamayan simulation.

params cached property
params: KamayanParams

Get parameters interface for setting overrides.

write_input
write_input(file: None | Path = None)

Write out all the params owned by the unit data collection.

Format as a parthenon input file.

execute
execute(*args: str)

Initialize the kamayan environment and execute the simulation.

Parameters:

Name Type Description Default
*args str

Additional arguments to forward to Parthenon (e.g., parthenon/time/nlim=100)

()

process_units

kamayan.kamayan_manager.process_units

process_units(
    name: str,
    setup_params: SetupInterface | None = None,
    initialize: InitializeInterface | None = None,
    pgen: ProblemGeneratorInterface | None = None,
) -> pk.UnitCollection

Build a default UnitCollection with a user provided KamayanUnit.

Parameters:

Name Type Description Default
name str

Name for user generated KamayanUnit

required
setup_params SetupInterface | None

hook into parameter setup

None
initialize InitializeInterface | None

hook into package initialization

None
pgen ProblemGeneratorInterface | None

problem generator

None

For details on UnitData, callbacks, and component architecture, see Kamayan Infrastructure.

Adding the CLI Interface

The @kamayan_app decorator automatically generates a command-line interface with common commands for running, testing, and inspecting your simulation. See the CLI Interface module in the API reference for detailed documentation.

kamayan_app Decorator

kamayan.cli.app.kamayan_app

kamayan_app(
    name: Optional[str] = None,
    *,
    description: Optional[str] = None,
) -> Callable

Decorator to create a Kamayan simulation CLI.

Parameters:

Name Type Description Default
name Optional[str]

Optional name for the simulation

None
description Optional[str]

Optional description for the CLI help

None
Usage

@kamayan_app def my_simulation() -> KamayanManager: km = KamayanManager(...) # configure ... return km

if name == "main": my_simulation.app()

The decorated function can also be used directly

km = my_simulation()

Add the decorator and entry point to your simulation:

from kamayan import kamayan_app

@kamayan_app(description="Sedov blast wave simulation")
def sedov() -> KamayanManager:
    # ... (simulation setup as shown above)
    return km

if __name__ == "__main__":
    sedov.app()

Running Your Simulation

We provide an entry point script kamayan that will launch a simulation from a script that has a function decorated with the @kamayan_app decorator.

Single process:

uv run kamayan src/problems/sedov.py

Parallel (MPI):

mpirun -np 4 uv run kamayan src/problems/sedov.py

MPI Command Order

Always put mpirun before uv run:

mpirun -np 4 uv run kamayan ...
uv run mpirun -np 4 kamayan ... (broken pipe error)

Dry run (generate input only):

uv run kamayan src/problems/sedov.py run --dry-run

Available Commands

The @kamayan_app decorator provides these commands:

Command Description
run Execute simulation (default)
run --dry-run Generate input file only
generate-input Generate input file
info Show configuration
version Show Kamayan version

Parthenon Arguments

Additional arguments are forwarded to Parthenon's argument parser. Kamayan automatically generates its own input file, so the -i flag is not used.

Argument Description Example
-r <file> Restart from checkpoint -r sedov.out0.00500.rhdf
-a <file> Analyze/postprocess -a output.phdf
-d <dir> Run directory -d /scratch/run
-t hh:mm:ss Wall time limit -t 01:30:00
-n Parse and quit -n
-m <n> Output mesh structure -m 4
-c Show config -c
block/par=val Override parameters sedov/density=2.0

Example:

mpirun -np 8 uv run python src/problems/sedov.py -r sedov.out0.00500.rhdf

Typical Workflow

# Check configuration
uv run python src/problems/sedov.py info

# Test run
uv run python src/problems/sedov.py run --dry-run

# Production run
mpirun -np 16 uv run python src/problems/sedov.py

# Restart
mpirun -np 16 uv run python src/problems/sedov.py -r sedov.out0.00500.rhdf