Skip to content

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 libOpenPhase under ./lib/ from a Makefile build, or build/<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 / .pvts output).

Project layout

Every OpenPhase simulation has three files at minimum:

text
MyFirstSimulation/
├── ProjectInput.opi    # Text input read by OpenPhase at startup
├── NormalGG.cpp        # C++ program that drives the time loop
└── Makefile            # Builds the executable against libOpenPhase

The .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

text
$<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:

text
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           : Periodic

A few points worth noting, because they catch almost every new user:

  • Grid parameters live in @GridParameters, not in @Settings. Settings holds 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_0 is a grain boundary within phase 0; _0_1 is an interface between two different phases.
  • Boundary-condition values are Periodic, NoFlux, Free, Fixed, or Mirror. 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.

cpp
#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.SetDO.CalculatePhaseFieldIncrementsPhi.NormalizeIncrementsPhi.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.

makefile
# 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 TextData

Makefile.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:

bash
cd examples/NormalGG          # or your copy
make                          # picks up SETTINGS from the library build

Pass the same SETTINGS you used for the library if it was built with anything other than the defaults:

bash
make SETTINGS=mpi-parallel

Building 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:

bash
cmake --build build --target NormalGG

The 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:

bash
./NormalGG                    # reads ./ProjectInput.opi
./NormalGG MyCustomInput.opi  # reads an alternate input file

Console 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/):

FolderContentsWritten 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

  1. File → Open and select the VTK files. ParaView groups them automatically into one time series.
  2. Press Apply.
  3. For a plain grain-growth visualisation, colour by the ActivePhaseFields scalar 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_grains in the C++ source, or $dx, $dt, and $Sigma_0_0 / $Mu_0_0 in the input file, and see how the run responds.
  • Swap $EnergyModel_0_0: ISO for $EnergyModel_0_0: CUBIC and add $EpsilonE_0_0; follow the worked example on Interface Properties.
  • Add composition: pick a binary example such as examples/SolidificationFeC, which adds Composition, Temperature, and EquilibriumPartitionBinary to 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:

bash
export LD_LIBRARY_PATH="$PWD/../../lib:$LD_LIBRARY_PATH"
# or, permanently: {#or-permanently}
sudo cp ../../lib/libOpenPhase.so /usr/local/lib && sudo ldconfig

Exit 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.

Released under the GNU GPLv3 License.