Writing Physics Classes
[ This page is under development ]
This page tries to provide an overview for how to write Physics
classes and documents some quirks about existing classes.
All physics classes are subclasses of the Physics
class.
Unlike other classes in the Enzo-E layer that descend from the Cello class-hierarchy (Method
, Initial
, Prolong
, Refine
, etc.), the Cello-layer doesn’t really interact much with instances of the Physics
classes - beyond storing them.
In practice, Physics
classes are commonly used to store problem-specific configuration information that needs to be accessed by multiple different Method
classes and/or initializers.
Some functions that make use of this information are also sometimes introduced to these classes.
When to write a Physics
class
Other ways to store data
To understand when it may be useful to write a Physics
class, it’s first useful to discuss some of the ways one could access/store cross-cutting configuration data:
store this information in a global variable (DON’T DO THIS)
From a coding-style perspective, this is almost always the wrong way to store such information.
Moreover, Charm++ does not support global variables unless they are properly declared in the .CI (note: the code will still compile, but it should be avoided all the same).
access information stored in
EnzoConfig
, which is accessible through enzo::config() (TRY NOT TO DO THIS)this class already stores a lot of values parsed from parameter files.
while this approach can be convenient in simple cases, it generally leads to brittle code that is hard to refactor (challenges can come up if you want to alter the way that different options are stored in the parameter file).
This approach has been used a fair amount in the past, but we are actively moving away from it (and discourage this approach).
Cross-cutting configuration information is commonly only relevant when you are using a particular
Method
class. In this case, you can store the configuration information within thatMethod
class and define public instance-methods on that particular class that accesses the information.This approach is strongly prefered over the preceding approaches 1 and 2. Refactoring is generally easier in this approach. Moreover, this can be easier than defining a
Physics
class.For this approach, it’s important to understand how to access an instance of a particular kind of
Method
at an arbitrary point in the code (after allMethod
classes have been constructed). One can use enzo::problem()->method("<name>") to return a pointer to the instance of theMethod
class for whichMethod::name()
returns"<name>"
. If no such instance can be found, the expression returns anullptr
. You then need to cast that pointer to the appropriateMethod
subclass before you access the information.At the time of writing, this approach is commonly used to store information encoded within the
EnzoMethodGrackle
class. To access such information, one could writeconst EnzoMethodGrackle *ptr = static_cast<const EnzoMethodGrackle*> (enzo::problem()->method("grackle")); if (ptr != nullptr) { // maybe do stuff with ptr->try_get_chemistry() ... }
In practice, some convenience functions have been written to help with these sorts of operations like enzo::grackle_method() or enzo::grackle_chemistry()
In principle, one could do something analogous involving subclasses of
Initial
, but that could potentially introduce problems during a simulation restart.
Storing information in a Physics
class
It’s often most useful to encode configuration-information within a Physics
class when there isn’t an obvious single Method
class where it should be stored.
A particular scenario where this is relevant is when separate (somewhat-interchangable) Method
classes implement different algorithms to model the same set of physics.
For example, consider the storage/access of the dual-energy formalism configuration.
Since this is mostly relevant in the context of a hydro-solver it may make sense to store this information in the Method
class that encapsulates a hydro-solver.
However, because Enzo-E has Method
classes that implement different hydro-solvers (that use the dual-energy formalism), we instead encode this information in a Physics
class.
There are also scenarios where some configuration information isn’t really associated with any singular Method
.
For example the Equation-Of-State is important to a number of different methods.
Another example includes the Gravitational Constant - this is important in self-gravity solvers and external-potential solvers, which are implemented in different Method
classes.
General Tips
The general advice is to implement a Physics
class so that it is immutable (after construction the instance’s state doesn’t change).
This makes the behavior of Physics
classes much easier to reason about because a single PE (processing element)
only has one instance of a given
Physics
classAND is responsible for evolving one or more instances of
EnzoBlock
.
Quirky Implementations
EnzoPhysicsCosmology
currently tracks some mutable state (e.g. the current scale-factor, the current rate of expansions, current redshift).
This is just something to be mindful of.
It’s worth noting that the initialization of EnzoPhysicsFluidProps
and EnzoPhysicsGravity
are a little quirky.
These objects are ALWAYS initialized, regardless of whether a user specifies the names of these objects in the Physics:list
configuration-file parameter.
This choice was made for the sake of maintaining backwards compatability with older versions of parameter-files that were created before these classes were invented (since they encode some information that was previously stored elsewhere).
Note
This means that all simulations have an instance of EnzoPhysicsGravity
(regardless of whether or not gravity is actually in use).