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:

  1. 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).

  2. 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).

  3. 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 that Method 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 all Method classes have been constructed). One can use enzo::problem()->method("<name>") to return a pointer to the instance of the Method class for which Method::name() returns "<name>". If no such instance can be found, the expression returns a nullptr. You then need to cast that pointer to the appropriate Method 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 write

      const 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 class

  • AND 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).