Your First OpenPhase Simulation
This page walks through the smallest useful OpenPhase simulation — a 2D normal grain growth run — from directory layout to ParaView visualisation. It exactly mirrors the examples/NormalGG example that ships with the library, so if you get stuck at any step you can cross-check against the shipped copy.
Prerequisites
- OpenPhase built. Either
libOpenPhaseunder./lib/from a Makefile build, orbuild/<preset>/...from a CMake build (see Installation). - A C++17 toolchain reachable from the shell (GCC 9+, Clang, Intel DPC++ / icx).
- ParaView (optional, for visualising the
.vts/.pvtsoutput).
Project layout
Every OpenPhase simulation has three files at minimum:
MyFirstSimulation/
├── ProjectInput.opi # Text input read by OpenPhase at startup
├── NormalGG.cpp # C++ program that drives the time loop
└── Makefile # Builds the executable against libOpenPhaseThe .opi filename is free-form; the default, if no argument is passed to main(), is ProjectInput.opi. The C++ filename is free-form; by convention it matches the example directory name.
The input file — ProjectInput.opi
OpenPhase input files are plain text. Every line in a $ entry has the form
$<Keyword> Free-form comment : <value>with the : separating the comment from the value. Sections are introduced by @BlockName headings. The example below is the exact input for a 2D grain-growth run:
Standard Open Phase Input File
!!!All values in MKS (or properly scaled) units please!!!
@RunTimeControl
$SimTtl Simulation Title : Normal grain growth
$nSteps Number of Time Steps : 2000
$FTime Output to disk every (tSteps) : 100
$STime Output to screen every (tSteps) : 100
$LUnits Units of length : m
$TUnits Units of time : s
$MUnits Units of mass : kg
$EUnits Energy units : J
$dt Initial Time Step : 1e-5
$nOMP Number of OpenMP Threads : 1
$Restrt Restart switch (Yes/No) : No
$tStart Restart at time step : 0
$tRstrt Restart output every (tSteps) : 10000
@GridParameters
$Nx System Size in X Direction : 101
$Ny System Size in Y Direction : 101
$Nz System Size in Z Direction : 1
$dx Grid Spacing : 1e-6
$IWidth Interface Width (in grid points) : 5.0
@Settings
$Phase_0 Name of Phase 0 : Phase1
@InterfaceProperties
$EnergyModel_0_0 Interface energy model : ISO
$Sigma_0_0 Interface energy : 0.24
$MobilityModel_0_0 Interface mobility model : ISO
$Mu_0_0 Interface mobility : 1.0e-7
@BoundaryConditions
$BC0X X axis beginning boundary condition : Periodic
$BCNX X axis far end boundary condition : Periodic
$BC0Y Y axis beginning boundary condition : Periodic
$BCNY Y axis far end boundary condition : Periodic
$BC0Z Z axis beginning boundary condition : Periodic
$BCNZ Z axis far end boundary condition : PeriodicA few points worth noting, because they catch almost every new user:
- Grid parameters live in
@GridParameters, not in@Settings.Settingsholds the phase list and the output-directory paths; see Settings for the exact tokens. - Interface-model names are enum values, not free-form strings. For the isotropic model, write
ISO— not"Isotropic". See Interface Properties for the full set (ISO,CUBIC,CUBICFULL,HEXBOETTGER,HEXSUN,HEXYANG,FACETED,FACETEDFULL). - Phase pairs are
_α_β, zero-based._0_0is a grain boundary within phase 0;_0_1is an interface between two different phases. - Boundary-condition values are
Periodic,NoFlux,Free,Fixed, orMirror. See Boundary Conditions.
The C++ program — NormalGG.cpp
The program constructs each OpenPhase module, seeds a multi-grain initial microstructure with a Voronoi tessellation, and runs the phase-field time loop.
#include "Settings.h"
#include "RunTimeControl.h"
#include "DoubleObstacle.h"
#include "PhaseField.h"
#include "Initializations.h"
#include "BoundaryConditions.h"
#include "InterfaceProperties.h"
#include "Tools/TimeInfo.h"
#include "Tools/MicrostructureAnalysis.h"
using namespace std;
using namespace openphase;
int main(int argc, char *argv[])
{
std::string InputFile;
if (argc > 1)
{
InputFile = argv[1];
}
else
{
std::cerr << "No input file provided, using default ProjectInput.opi\n";
InputFile = "ProjectInput.opi";
}
Settings OPSettings;
OPSettings.ReadInput(InputFile);
RunTimeControl RTC(OPSettings, InputFile);
PhaseField Phi(OPSettings, InputFile);
DoubleObstacle DO(OPSettings, InputFile);
InterfaceProperties IP(OPSettings, InputFile);
BoundaryConditions BC(OPSettings, InputFile);
// Voronoi tessellation — 200 grains of phase 0.
int number_of_grains = 200;
size_t GrainsPhase = 0;
Initializations::VoronoiTessellation(Phi, BC, number_of_grains, GrainsPhase);
std::cout << "Entering the time loop!" << std::endl;
for (RTC.tStep = RTC.tStart; RTC.tStep <= RTC.nSteps; RTC.IncrementTimeStep())
{
IP.Set(Phi, BC);
DO.CalculatePhaseFieldIncrements(Phi, IP);
Phi.NormalizeIncrements(BC, RTC.dt);
Phi.MergeIncrements(BC, RTC.dt);
if (RTC.WriteVTK())
{
Phi.WriteVTK(OPSettings, RTC.tStep);
MicrostructureAnalysis::WriteGrainsStatistics(Phi, RTC.tStep);
}
if (RTC.WriteRawData())
{
Phi.Write(OPSettings, RTC.tStep);
}
if (RTC.WriteToScreen())
{
double I_En = DO.AverageEnergyDensity(Phi, IP);
std::string msg = ConsoleOutput::GetStandard("Interface energy density", I_En);
ConsoleOutput::WriteTimeStep(RTC, msg);
}
}
return EXIT_SUCCESS;
}The time loop is the same shape across almost every OpenPhase program: IP.Set → DO.CalculatePhaseFieldIncrements → Phi.NormalizeIncrements → Phi.MergeIncrements. Driving forces from diffusion, elasticity, or user code are added before CalculatePhaseFieldIncrements by accumulating into a shared DrivingForce.
Building with GNU Make
Place the project inside the distribution's examples/ directory so it picks up Makefile.defs automatically. The shipped examples/NormalGG/Makefile is the canonical template — copy and rename it.
# Standard Makefile for an OpenPhase example {#standard-makefile-for-an-openphase-example}
.PHONY: all clean cleanall
DEPTH = ../..
SRC := $(wildcard *.cpp)
PROGS := $(basename $(SRC))
include $(DEPTH)/Makefile.defs
INCLUDES += $(EXTERNAL_INCLUDES)
LDFLAGS = -L$(DEPTH)/lib $(EXTERNAL_LIBS)
LIBSRC = $(DEPTH)/lib/$(LIBNAME)
MAKE := make SETTINGS="$(SETTINGS)"
all: $(PROGS)
$(PROGS): %: %.cpp $(LIBSRC)
$(CXX) $(CXXFLAGS) $(INCLUDES) -o $@ $< $(LDFLAGS)
clean:
rm -f $(PROGS) *.o
cleanall: clean
rm -rf VTK RawData TextDataMakefile.defs supplies CXX, CXXFLAGS (with -std=c++17 -O3 -fopenmp), INCLUDES, and EXTERNAL_LIBS based on the SETTINGS used when the library itself was built. That keeps compiler flags in sync between the library and your executable — don't override them unless you know why.
Build:
cd examples/NormalGG # or your copy
make # picks up SETTINGS from the library buildPass the same SETTINGS you used for the library if it was built with anything other than the defaults:
make SETTINGS=mpi-parallelBuilding with CMake
If you built the library with CMake and -DENABLE_EXAMPLES=ON (the default), your program is already built under the build/ tree. To build only this example:
cmake --build build --target NormalGGThe built executable is at build/examples/NormalGG/NormalGG, with ProjectInput.opi copied next to it automatically (the build step handles that for every .opi in the example directory).
Running the simulation
From the example directory:
./NormalGG # reads ./ProjectInput.opi
./NormalGG MyCustomInput.opi # reads an alternate input fileConsole output every $STime steps reports the current time step, elapsed wall time, and the averaged interface energy density (falling steadily for a well-set-up grain-growth run). The run produces three output folders based on the paths in @Settings (defaults are VTK/, RawData/, TextData/):
| Folder | Contents | Written every |
|---|---|---|
VTK/ | .vts files (.pvts in MPI) — per-phase-field data for ParaView. | $FTime steps |
RawData/ | Binary restart snapshots readable by PhaseField::Read. | $tRstrt steps |
TextData/ | CSV statistics from MicrostructureAnalysis::WriteGrainsStatistics and related writers. | $FTime steps |
Visualising with ParaView
- File → Open and select the VTK files. ParaView groups them automatically into one time series.
- Press Apply.
- For a plain grain-growth visualisation, colour by the
ActivePhaseFieldsscalar and pick a categorical colour scheme.
For grain-boundary energy visualisations, also open the interface- energy VTK emitted by DoubleObstacle::WriteEnergyVTK.
Where to go next
- Change
number_of_grainsin the C++ source, or$dx,$dt, and$Sigma_0_0/$Mu_0_0in the input file, and see how the run responds. - Swap
$EnergyModel_0_0: ISOfor$EnergyModel_0_0: CUBICand add$EpsilonE_0_0; follow the worked example on Interface Properties. - Add composition: pick a binary example such as
examples/SolidificationFeC, which addsComposition,Temperature, andEquilibriumPartitionBinaryto the same time-loop pattern. - Understand the project layout: see Project Structure.
Troubleshooting
Runtime loader cannot find libOpenPhase.so
Your shared library is not on the runtime loader's search path:
export LD_LIBRARY_PATH="$PWD/../../lib:$LD_LIBRARY_PATH"
# or, permanently: {#or-permanently}
sudo cp ../../lib/libOpenPhase.so /usr/local/lib && sudo ldconfigExit after reading the input, Wrong state of matter is selected
The @Settings block requires one $State_<n> per active phase and only accepts SOLID, LIQUID, or GAS. See Settings.
Unknown EnergyModel / Unknown MobilityModel
You used a free-form string (e.g. "Isotropic") instead of an enum value. Valid values are listed under Interface energy models on Interface Properties.