Build a minimal config-driven application¶
This tutorial creates an OpenPFC executable in a separate CMake project. The
application uses pfc::ui::App<MyModel> so a JSON or TOML file drives the
domain, time integration, FFT setup, modifiers, and result writers.
Use this path when the simulation belongs in its own repository. Do not fork
OpenPFC merely to add an application main.
Prerequisites¶
You need:
an installed OpenPFC package;
the same compatible compiler, MPI, and HeFFTe stack used to build OpenPFC;
CMAKE_PREFIX_PATHorOpenPFC_DIRpointing to the installation;nlohmann-json available to the downstream project when frontend JSON headers are used.
Complete installation guidance is in INSTALL.md.
Project layout¶
Create a new directory with this layout:
my-openpfc-app/
├── CMakeLists.txt
├── main.cpp
├── my_model.cpp
├── my_model.hpp
└── settings.json
Configure the CMake project¶
The target and enabled languages below match the installed-package consumer exercised by OpenPFC CI:
cmake_minimum_required(VERSION 3.21)
project(my_openpfc_app LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(OpenPFC REQUIRED)
find_package(nlohmann_json REQUIRED)
add_executable(my_app main.cpp my_model.cpp)
target_link_libraries(
my_app
PRIVATE
OpenPFC::openpfc
nlohmann_json::nlohmann_json
)
The C language is enabled because the installed package resolves MPI components
that include an MPI C target. OpenPFC::openpfc is the supported installed
target; un-namespaced in-tree aliases are not a downstream contract.
Define the model seam¶
MyModel owns the application-specific fields and physics. Keep the subclass
thin and place reusable mechanics in ordinary functions and data types.
// my_model.hpp
#pragma once
#include <mpi.h>
#include <openpfc/kernel/simulation/model.hpp>
class MyModel : public pfc::Model {
public:
explicit MyModel(pfc::FFT &fft, const pfc::Domain &domain,
MPI_Comm comm = MPI_COMM_WORLD)
: pfc::Model(fft, domain, comm) {}
void initialize(double dt) override;
void step(double time) override;
};
// my_model.cpp
#include "my_model.hpp"
void MyModel::initialize(double dt) {
(void)dt;
// Allocate fields and precompute operators here.
}
void MyModel::step(double time) {
(void)time;
// Advance the model by one step here.
}
The empty bodies deliberately isolate the integration shell from the physics.
For spectral model implementations, read
Spectral examples sequence and inspect
examples/04_diffusion_model.cpp and examples/12_cahn_hilliard.cpp.
Hand control to App¶
// main.cpp
#include "my_model.hpp"
#include <exception>
#include <iostream>
#include <openpfc/frontend/ui/app.hpp>
int main(int argc, char **argv) {
try {
pfc::ui::App<MyModel> app(argc, argv);
return app.main();
} catch (const std::exception &error) {
std::cerr << error.what() << '\n';
return 1;
}
}
App reads the configuration path from argv[1], initializes MPI-facing
runtime state, builds the spectral stack and simulator, and executes the time
loop.
Add a configuration¶
Start from a shipped input that uses the same frontend path, then reduce it to the fields required by your model. The exact supported keys and nesting belong in the Spectral App configuration reference. The conversion from configuration to runtime objects is described in Application pipeline.
A model that consumes model.params can provide an ADL-visible conversion
function:
void from_json(const pfc::ui::json &input, MyModel &model);
Add that function only when the model has parameters to read. Parameter ranges, required keys, units, and diagnostic reports are covered in Parameter validation.
Build and run¶
cmake -S . -B build \
-DCMAKE_PREFIX_PATH=/path/to/openpfc/install
cmake --build build -j"$(nproc)"
mpirun -n 1 ./build/my_app ./settings.json
Begin with one MPI rank. Increase the rank count only after the application works and the launcher has the required slots or scheduler allocation.
Optional: custom field modifiers¶
A configuration-selectable initial or boundary condition uses a
FieldModifier implementation registered in a catalog before the application
runs. The smallest example is examples/10_ui_register_ic.cpp; the extension
choices and catalog lifetime guidance are in
Extending OpenPFC.
Prefer an explicit local catalog in tests and reusable libraries. Process-wide registration is appropriate only when shared mutable registration state is acceptable for the executable.
Verify the integration¶
Before adding substantial physics, verify that:
CMake finds the intended OpenPFC installation;
the consumer and OpenPFC use compatible MPI and HeFFTe installations;
the executable starts with one rank and reads
settings.json;invalid or missing configuration values fail before time integration;
a configured writer creates the expected output artifact.
For build and runtime failures, use Troubleshooting.
For the roles of Domain, Model, Simulator, and App, use the
Tour of main types.