Repository: PlasmaPy/PlasmaPy

Issue:
Create classes to represent normalizations in simulation subpackage
Numerical simulations of plasma phenomena are frequently normalized to characteristic values so that the corresponding equations and fields become dimensionless.  A strategy for keeping track of normalizations is therefore a necessary building block for a simulation framework.  At present, there is no standard method keeping track of normalizations.

Moreover, the normalizations for fluid and particle-in-cell simulations are often done differently.  For example, time in a particle-in-cell simulation is often normalized to the ion cyclotron timescale and length is often normalized to the ion inertial length.  Length and time in a fluid simulation are often normalized to arbitrary values, but typically related such that the Alfvén velocity is one.

It would be helpful to have a standard way of saving normalizations outside of the package.  This could be done for example through a YAML, TOML, or HDF5 file, though there should be an open metadata standard for it.  

One headache when dealing with normalizations is figuring out the normalizations for things like diffusion coefficients or viscosities, which are derived from the primary normalizations (e.g., magnetic field, etc.).  It would be helpful for the normalization class (or classes) to have a method that takes a set of units that is derived from the base units, and then return the normalization for quantities measured in those units.  For example, if we want to return a value that is in units of `kg * m ** 2 / s ** 2`, we would pass in that set of units, and get a `Quantity` with those units that corresponds to the normalization.  We'd have to be careful for this though, and make sure there aren't any extra factors of two that we would be missing.  This wouldn't necessarily need to be created in the first pull request, and could instead be raised as its own issue if we decide to go in that direction.

There are some straightforward cases that could be put in analytically, like the typical normalizations for the MHD equations.  


Discussion:
Cross-reference: #603 
Oh!  And then we could also use dunder methods like `__mul__` to define operations like...
```Python
normalizations = Normalizations(B=5 * u.T, ...)
dimensionless_particle = DimensionlessParticle(mass=1, charge=-2)
custom_particle = dimensionless_particle * normalizations
```
We could do things like this for other dimensionless objects that we create, like...
```Python
normalization = Normalizations(...)
dimensionless_initial_conditions = DimensionlessInitialConditions()
dimensional_initial_conditions = dimensionless_initial_conditions * normalizations
```

Resolving patch:
diff --git a/plasmapy/simulation/__init__.py b/plasmapy/simulation/__init__.py
--- a/plasmapy/simulation/__init__.py
+++ b/plasmapy/simulation/__init__.py
@@ -6,6 +6,7 @@
 ]
 
 from plasmapy.simulation.abstractions import (
+    AbstractNormalizations,
     AbstractSimulation,
     AbstractTimeDependentSimulation,
 )
diff --git a/plasmapy/simulation/abstractions.py b/plasmapy/simulation/abstractions.py
--- a/plasmapy/simulation/abstractions.py
+++ b/plasmapy/simulation/abstractions.py
@@ -1,6 +1,12 @@
 """Abstract classes for numerical simulations."""
 
-__all__ = ["AbstractSimulation", "AbstractTimeDependentSimulation"]
+__all__ = [
+    "AbstractNormalizations",
+    "AbstractSimulation",
+    "AbstractTimeDependentSimulation",
+]
+
+import astropy.units as u
 
 from abc import ABC, abstractmethod
 from typing import NoReturn
@@ -21,26 +27,22 @@ def summarize(self):
         """
         Print out a summary of the simulation parameters and status.
         """
-
-        pass
+        ...
 
     @abstractmethod
     def initialize(self) -> NoReturn:
         """Prepare the simulation to be run."""
-
-        pass
+        ...
 
     @abstractmethod
     def simulate(self):
         """Perform the actual simulation."""
-
-        pass
+        ...
 
     @abstractmethod
     def finalize(self) -> NoReturn:
         """Perform the steps to close the simulation and output data."""
-
-        pass
+        ...
 
 
 class AbstractTimeDependentSimulation(AbstractSimulation):
@@ -53,4 +55,133 @@ class AbstractTimeDependentSimulation(AbstractSimulation):
     change at any time.
     """
 
-    pass
+    ...
+
+
+class AbstractNormalizations(ABC):
+    """
+    An abstract base class to represent the normalization constants for
+    systems of equations describing plasmas.
+
+    .. warning::
+       This interface is unstable and is subject to change.
+    """
+
+    @property
+    @abstractmethod
+    def current_density(self) -> u.A / u.m ** 2:
+        """The current density normalization."""
+        ...
+
+    @property
+    @abstractmethod
+    def diffusivity(self) -> u.m ** 2 / u.s:
+        """The normalization for diffusivity."""
+        ...
+
+    @property
+    @abstractmethod
+    def dynamic_viscosity(self) -> u.Pa * u.s:
+        """The normalization for dynamic viscosity."""
+        ...
+
+    @property
+    @abstractmethod
+    def electric_field(self) -> u.V / u.m:
+        """The electric field normalization."""
+        ...
+
+    @property
+    @abstractmethod
+    def heat_flux(self) -> u.J * u.m ** -2 * u.s ** -1:
+        """The normalization for heat flux."""
+        ...
+
+    @property
+    @abstractmethod
+    def length(self) -> u.m:
+        """The normalization for length."""
+        ...
+
+    @property
+    @abstractmethod
+    def magnetic_field(self) -> u.T:
+        """The magnetic field normalization."""
+        ...
+
+    @property
+    @abstractmethod
+    def magnetic_flux(self) -> u.T * u.m:
+        """The normalization for the magnetic flux or vector potential."""
+        ...
+
+    @property
+    @abstractmethod
+    def mass(self) -> u.kg:
+        """The normalization for mass."""
+        ...
+
+    @property
+    @abstractmethod
+    def mass_density(self) -> u.kg / u.m ** 3:
+        """The normalization for mass density."""
+        ...
+
+    @property
+    @abstractmethod
+    def number_density(self) -> u.m ** -3:
+        """The normalization for number density."""
+        ...
+
+    @property
+    @abstractmethod
+    def pressure(self) -> u.Pa:
+        """The normalization for pressure."""
+        ...
+
+    @property
+    @abstractmethod
+    def temperature(self) -> u.K:
+        """The normalization for temperature."""
+        ...
+
+    @property
+    @abstractmethod
+    def thermal_conductivity(self) -> u.W / (u.K * u.m):
+        """The normalization for thermal conductivity."""
+        ...
+
+    @property
+    @abstractmethod
+    def time(self) -> u.s:
+        """The normalization for time."""
+        ...
+
+    @property
+    @abstractmethod
+    def wavenumber(self) -> u.m ** -1:
+        """The normalization for inverse length."""
+        ...
+
+    @property
+    @abstractmethod
+    def velocity(self) -> u.m / u.s:
+        """The normalization for velocity."""
+        ...
+
+    @property
+    @abstractmethod
+    def volumetric_heating_rate(self) -> u.J * u.m ** -3 * u.s ** -1:
+        """The normalization for the volumetric heating rate."""
+        ...
+
+    @property
+    @abstractmethod
+    def volumetric_rate(self) -> u.m ** -3 * u.s ** -1:
+        """
+        The normalization for a volumetric rate.
+
+        This normalization is applicable to, for example, the number
+        of collisions per unit volume per unit time.
+        """
+        ...


Test patch: