07 - RMK Context#
Up until now, all tutorials have covered individual building blocks of ReMKiT1D runs. In order to assemble runs these elements must be put together in a global context, where they will interact.
In this tutorial we finish the basic concepts of ReMKiT1D by covering various parts and features of RMKContext:
The
MPIContextandIOContextcomponents for setting MPI/JSON/HDF5 optionsBuilt-in normalisation,
Speciesobjects, and PETSc settingsThe standard workflow
Grid->Variables->Models->IntegrationSchemespecifying a simulationManipulatorobjects and using them inRMKContextconfig.jsonand LaTeX summary features
For the workflow we will use parts of the Gaussian advection example from the examples directory. The reader is encouraged to explore that other examples for complete workflows.
[1]:
import RMK_support as rmk
from RMK_support.stencils import StaggeredDivStencil as Div, StaggeredGradStencil as Grad
import numpy as np
ReMKiT1D supports parallelism in the spatial and harmonic directions, and these options can be set using MPIContext
The IO directory for HDF5 files as well as the name of the JSON config files are set using IOContext. The IOContext also contains information on using data from an input HDF5 as well as restart checkpoint saving/loading options. These aren’t covered in this tutorial, but used in examples.
[2]:
rk = rmk.RMKContext() # The main context object
rk.mpiContext = rmk.MPIContext(numProcsX = 4, # Number of MPI processes in the spatial direction
numProcsH = 1 # Number of MPI processes in the harmonic direction
)
rk.IOContext = rmk.IOContext(jsonFilepath="./config_test.json", # Path of the json config
HDF5Dir="./dummy_dir") # IO HDF5 directory path
With the above settings, one would need to create a dummy_dir directory in the directory with the config_test.json file and use mpirun -np 4 [ReMKiT1D executable path] -with_config_file=./config_test.json to run ReMKiT1D once the config file is generated at the end of this tutorial.
We can set a Grid object (needed for the normalisation below) in the context
[3]:
xGridWidths = 0.025*np.ones(512)
rk.grid = rmk.Grid(xGridWidths, interpretXGridAsWidths=True)
Normalisation, Species, and PETSc options#
ReMKiT1D offers a default normalisation scheme (see code paper) used whenever units are required by built-in functions (for example when setting the Grid to interpret the spatial grid in metres).
We can set and inspect the base normalisation quantities (density, temperature in eV, and reference ion charge) using the RMKContext, and we can retrieve any derived normalisation quantities.
[4]:
rk.normDensity = 1e19
rk.normTemperature = 10
rk.normZ = 1.0
print(rk.norms) # Normalisation in SI units (temperature in eV)
{'eVTemperature': 10, 'density': 1e+19, 'referenceIonZ': 1.0, 'time': 7.220495388899917e-08, 'velGrid': 1875539.6133072434, 'speed': 1875539.6133072434, 'EField': 147.6851265098392, 'heatFlux': 30049520.576485995, 'crossSection': 7.384256325491959e-19, 'length': 0.13542325129584085}
The above has used the default Textbook object. If we wanted to set our own Textbook object we’d assign it to rk.textbook. A common case where this might be useful is when requesting built-in derivations that require some Species information. In the example in this tutorial this is not used, but we demonstrate how one would set species and use their information to ask the Textbook to generate a derivation for the corresponding species temperature (for which the species mass
is needed).
For more use cases of Species see examples, especially those regarding CRMs.
[5]:
ionSpecies = rmk.Species("D+",
speciesID=-1, # Unique integer ID for this species (convention for ions is to use negative IDs)
atomicA=2.0, # Atomic mass in amus,
charge=1.0 # Charge in e
)
# to add the species to the context
rk.species.add(ionSpecies)
# to set a textbook that would generate a built-in derivation for the temperature
rk.textbook = rmk.Textbook(rk.grid,
tempDerivSpeciesIDs=[ionSpecies.speciesID]
)
#this would then let us use the following derivation in the Fortran code
print(rk.textbook["tempFromEnergyD+"].name) # See Textbook documentation for more info and examples for use cases
tempFromEnergyD+
As noted in the previous tutorial, the default Backwards Euler integrator uses PETSc, and RMKContext can be used to set the used PETSc options
[6]:
rk.setPETScOptions(kspSolverType="gmres", # PETSc KSP solver
cliOpts="-pc_type bjacobi -sub_pc_factor_shift_type nonzero -sub_pc_factor_levels 1" # Non-default PETSc CLI options (see PETSc documentation)
)
Default workflow with RMKContext#
After setting the Grid one can continue with the following workflow:
Define and add
VariablesDefine and add
Models/TermsSet the
IntegrationScheme
Note that there is no need to add all Variables at the same time or all Models/Terms at the same time, either. The above is only the suggested workflow, and users should adapt it according to their use case and preference.
[7]:
nInit = 1 + np.exp(-(rk.grid.xGrid-np.mean(rk.grid.xGrid))**2) # A Gaussian perturbation
TInit = np.ones(len(rk.grid.xGrid)) # Constant temperature
n,n_dual = rmk.varAndDual("n",rk.grid,data=nInit)
T = rmk.Variable("T",rk.grid,data=TInit,isDerived=True,isCommunicated=False)
G_dual,G = rmk.varAndDual("G",rk.grid,primaryOnDualGrid=True)
rk.variables.add(n,T,G) # rk.variables is a VariableContainer
model = rmk.Model(name="adv")
massRatio = 1/1836
model.ddt[n] += - Div()(G_dual).rename("div_G") # dn/dt = - div(G_dual)
model.ddt[G_dual] += -massRatio/2 * Grad()(T * n).rename("grad_p") # dG_dual/dt = -m_e/(2m_i) grad(n*T)
rk.models.add(model) # rk.models is a ModelCollection
In this example, we use a simple Backwards Euler solver with a fixed number of steps.
[8]:
# the implicit BDE integrator that checks convergence based on the variables n and G_dual
integrator = rmk.BDEIntegrator("BDE",nonlinTol=1e-12,absTol=10.0,convergenceVars=[n,G_dual])
integrationStep = rmk.IntegrationStep("BE",integrator)
integrationStep.add(rk.models) # Add all models in context
rk.integrationScheme = rmk.IntegrationScheme(dt=0.1,steps=integrationStep) #Create a scheme with our single step and a constant integration timestep 0.1
rk.integrationScheme.setFixedNumTimesteps(10000,200) # Run for 10000 steps outputting every 200
Manipulators#
Manipulators allow for non-standard data manipulation, and are mostly used for data access and diagnostics.
Here we demonstrate a TermEvaluator manipulator to extract the div_G term into its own variable
[9]:
divG = rmk.Variable("divG",rk.grid,isDerived=True) # We set the variable to be derived but do not add a derivation
rk.variables.add(divG) # Register divG in the context
divGEvaluator = rmk.TermEvaluator("divGEval",
modelTermTags=[("adv","div_G")], # Evaluate term "div_G" in model "adv"
resultVar=divG # and store in divG
)
rk.manipulators.add(divGEvaluator) # Register the manipulator
RMKContext also offers an automatic addition of manipulators for the diagnosis of terms evolving any Variable
[10]:
rk.addTermDiagnostics(G_dual) # Will add term diagnosis for term grad_p in model adv which evolves G_dual
Generating config file and LaTeX summary#
To generate a ReMKiT1D config file from a context simply run the following
NOTE: This tutorial produces a non-default name config file. To run, add the flag -with_config_path=./config_test.json
[11]:
rk.writeConfigFile()
Checking terms in model adv:
Checking term div_G
Checking term grad_p
ReMKiT1D’s Python interface offers the ability to generate LaTeX summaries of contexts and save them as PDF files.
Furthermore, remapping of variable names is supported
[12]:
latexRemap = {"n":"n", # This will remove the \text{} wrapper which is default around variable names
"n_dual":"n_{dual}",
"G":"\\vec{G}", # Note escape character
"G_dual":"\\vec{G}_{dual}"
}
rk.generatePDF(latexFilename="Tutorial 07", # Underscores will be inserted into the name
latexRemap=latexRemap,
cleanTex=True # Set to false to keep the intermediate tex files
)
Checking terms in model adv:
Checking term div_G
Checking term grad_p