OGRe: An Object-Oriented General Relativity Package for Mathematica

We present OGRe, a modern Mathematica package for tensor calculus, designed to be both powerful and user-friendly. The package can be used in a variety of contexts where tensor calculations are needed, in both mathematics and physics, but it is especially suitable for general relativity. By implementing an object-oriented design paradigm, OGRe allows calculating arbitrarily complicated tensor formulas easily, and automatically transforms between index configurations and coordinate systems behind the scenes as needed, eliminating user errors by making it impossible for the user to combine tensors in inconsistent ways. Other features include displaying tensors in various forms, automatic calculation of curvature tensors and geodesic equations, easy importing and exporting of tensors between sessions, optimized algorithms and parallelization for improved performance, and more.


Introduction Summary
OGRe is a modern Mathematica package for differential geometry and tensor calculus. It can be used in a variety of contexts where tensor calculations are needed, in both mathematics and physics, but it is especially suitable for general relativity -the field of physics where tensors are most commonly and ubiquitously used. Whether the user is doing cutting-edge research in general relativity or just making first steps in learning the theory, the ability to manipulate tensors and 1 perform tensor calculations quickly, easily, and intuitively will greatly simplify and accelerate their work.
Tensors are abstract geometrical structures, which describe curved spaces and objects within these spaces. In principle, it is possible to perform calculations with the abstract tensors themselves, and this is often done in pure mathematics. However, in practice, one usually represents a tensor as a set of individual components -similarly to how an abstract vector is just an arrow, but concrete calculations usually involve representing the vector as a list of components. The mathematical details are given in the statement of need below.
Unfortunately, tensor calculations are notoriously complicated and prone to errors. Tensors have many individual components, and operations on tensors involve manipulating and combining the components of one or more tensors in convoluted ways. Furthermore, combining several tensors requires the representations of each of the tensors involved to be compatible with each other according to strict rules.
OGRe is designed to simplify the complexities of tensor calculations. This is done using an objectoriented programming approach, taking advantage of principles such as encapsulation and class invariants to eliminate the possibility of user error. A single tensor object in OGRe contains the components of the tensor in different representations, as well as metadata such as the type of the tensor and the symbol used to represent it in equations.
To construct a new object, the user only needs to enter the tensor's components -a multidimensional array of numbers, symbols, and/or functions -in one representation. Other representations will then be calculated automatically by OGRe as needed, by transforming the initial components behind the scenes using the appropriate rules.
Operations on tensors are performed by the user abstractly, without specifying which representations to use. OGRe's algorithm will automatically determine and use the correct combination of tensor representations needed for the specific operation, no matter how complicated the operation is. This ensures that the user cannot mistakenly perform "illegal" operations, that is, combine tensors of non-compatible representations. covector.
The most important use of tensors is in the context of curved spaces, notably in general relativity, where gravity is described using a curved 4-dimensional spacetime. The curvature is encoded in a special rank (0, 2) tensor called the metric. The metric can be used to raise and lower indices, that is, turn a lower index into an upper index or vice-versa. This means that for each non-negative integer k, all the spaces of rank ( p, q) tensors with p + q  k are isomorphic. Therefore, we can define a more general notion of abstract tensors of rank k, whose representations have k indices in total, but with a different number of upper vs. lower indices for each representation. One rank k tensor will thus have many different representations, depending both on the coordinate system and the index configuration.
Transforming a tensor representation from one coordinate system to another is done by taking complicated combinations of the tensor's components with the Jacobian of the coordinate transformation. Transforming from one index configuration to another is done similarly, by taking complicated combinations of the components with the metric. Given that tensor representations typically have dozens or even hundreds of individual components, this can be a very complicated task.
Operations on one or more tensors can be even more complicated, since the representations of the different tensors have to match. For example, addition of tensors may only be performed component-by-component if all tensors are in the exact same representation. On the other hand, contraction of an index of one tensor with an index of another tensor, which is a generalization of the notion of inner product, requires choosing the representations of the tensors such that one index being contracted is upper and the other is lower.
When doing such calculations by hand, it is quite easy to lose track and make mistakes -as every student of differentia geometry and general relativity inevitably discovers. Computer algebra systems, such as Mathematica [1], are thus indispensable for doing tensor calculations [2]. They save considerable time and effort that would have been spent performing the calculations by hand, but more importantly, they ensure that the final results are free of errors. user may only access an object's data using the methods defined by the class, and is unable to access the object's data directly.
Importantly, encapsulation allows for the preservation of class invariants. An invariant is a condition of validity that can always be assumed to be satisfied by the data stored in each object. If the methods make sure to preserve the invariant whenever they store or manipulate the data, and the user is prevented from changing the data manually and thus potentially violating the invariant, then the implementation of the class can be greatly simplified, and performance can be improved, because we will not need to verify that the data is valid every time we perform an operation.
The main idea behind OGRe is to simplify the use of tensors by encoding all the information about a tensor in a single, self-contained object. As we mentioned above, a tensor is an abstract object. We can find components which represent this abstract entity in a particular coordinate system and index configuration, but the tensor is not its components. In OGRe, a tensor object is initially defined (or constructed) by providing the components of the tensor in a particular representationbut once this is done, the user does not need to worry about coordinates or indices anymore, or even remember which coordinates and indices were initially used. The abstract tensor object will automatically transform the initial data to a different coordinate system or index configuration as needed, based on the context in which it was used.
As a tensor object holds the components of the same tensor in many different representations, the most important class invariant is the assumption that the different components indeed represent the same tensor. This is achieved using encapsulation; the object's data can only be modified by private methods that preserve the invariant, and thus the user cannot accidentally cause a violation of the invariant by assigning components to one representation that are not related to the components of all other representations by the appropriate coordinate and/or index transformation.
Unfortunately, Mathematica does not have built-in support for object-oriented programming. However, version 10.0 of Mathematica, released in 2014, introduced the Association symbol. An Association is an associative array; it is similar to a List, except that instead of being just an array of values, an Association is a list of keys with a value associated to each key. This allows us to easily implement a rudimentary form of object-oriented programming, storing the properties of each object in the keys of a corresponding Association.
Of course, as Mathematica is not truly object-oriented, there is no actual "tensor class" defined anywhere in the package. Instead, the tensor class exists only implicitly, as a design paradigm. Furthermore, the functions that process the data stored in the tensor objects are not methods of a class, they are simply modules that take tensor objects as input and/or produce tensor objects as outputs. (In earlier versions, I tried using a syntax that resembled method syntax in languages such as C++ or Python, but eventually decided against it, as it was too cumbersome.) Still, designing the package with object-oriented programming in mind allows us to reap many of this paradigm's benefits, as explained above -and it simply makes sense for tensors, due to their abstract and multifaceted nature.

Installing and loading the package
This package is compatible with Mathematica 12.0 or newer. It consists of only one file, OGRe.m. There are several different ways to load the package:  Run from local file with installation: This is the recommended option, as it allows you to permanently use the package offline from any Mathematica notebook. Download the file OGRe.m from https://github.com/bshoshany/OGRe and copy it to the directory given by  For the purposes of this documentation, I will use the "run from local file without installation" option, since you most likely downloaded both the documentation and the package together: • To list all available modules, type ?OGRe`*.
• To get help on a particular module, type ? followed by the module name.
• Clicking on the name of any module in this list will show its usage message. Notice that all OGRe modules start with the letter T, to help distinguish them from other modules, whether built-in or from other packages.
When the package loads, it will automatically check the GitHub repository for updates. This can also be done manually using the module TCheckForUpdates. If a new version of the package has been released, you will be given the option to download or install it in one click. If you are running the package directly from the web, you will always be using the latest version. Creating and displaying tensor objects

Defining coordinates
To define tensors, we first need to define the manifold on which they reside. Since we are focusing on general relativity, we will use 4-dimensional spacetime manifolds in the following examples, but this package works equally well with manifolds that are purely spatial and/or have a different number of dimensions.
The first step is to define the coordinate system. In OGRe, coordinates are represented as a special tensor object: a vector x μ (a tensor of rank 1) representing a point. To define the coordinates, we use the module TNewCoordinates: coordinatesID is a string that will be used to identify the new object, and must be unique.
symbols are the coordinate symbols, e.g. {t, x, y, z}. They will automatically be cleared and protected against future changes using For example, to define the Cartesian coordinate system, we use the following syntax: The first argument is the new tensor object's unique ID. This is the string that we will use to refer to this tensor from now on, and it will also be displayed whenever we print the tensor. The ID string is case-sensitive, can include any characters, and can be of any length, but it is recommended to keep it short and simple. Once a tensor object is created, you cannot create another tensor object with the same ID unless you rename or delete it first (see below). Note that the ID string is also the return value of the module; generally, all modules that operate on a tensor object will return its ID string as output. This allows us to compose different modules together, as we will see below.
The second argument is the list of coordinates. Note that the order of coordinates matters, as it will determine the order of components in the tensors defined on this manifold. The symbols used also matter, as tensor components will usually be functions of these symbols. We can similarly define the spherical coordinate system: It is crucial that the coordinate symbols, in this case t, r, θ, ϕ, remain as undefined symbols throughout the calculation, otherwise errors may occur. For example, if our metric contains functions of r, and at some point in the notebook we set r = 1, then Mathematica will replace every instance of r with 1, which means those abstract functions will be replaced with their values evaluated at r = 1. Furthermore, if we, for example, want to take the derivative with respect to r (e.g. for the purpose of calculating various curvature tensors), this will not be possible, since one cannot take a derivative with respect to a number.
To prevent such errors, TNewCoordinates automatically clears any previous definitions of the given symbols, and also protects them against future changes. This is done using the Indeed, if we now try to give r the value 1, we will get an error: Set: Symbol r is Protected.
You can also use TSetReservedSymbols manually for any constants or parameters used in the tensor, as we will demonstrate in the next section. Note that it is always possible to replace reserved symbols with numbers or other expressions using /. or ReplaceAll, so giving a value 12 to a reserved symbol directly is not something you will ever need to do.

Defining metrics
To finish defining a manifold, we need to define its metric tensor. For this, we use the module metricID is a string that will be used to identify the new object, and must be unique.
coordinatesID is the unique ID of a tensor object representing a coordinate system, created using TNewCoordinates[].
components is a square, symmetric, and invertible matrix representing the metric with two lower indices in that coordinate system. symbol will be used to represent the metric in formulas. If not given, "g" will be used.
Again, we must input a unique ID for the new tensor object corresponding to the metric. We also input the unique ID of a coordinate system created using TNewCoordinates. This coordinate system is the one in which the components of the metric are initially given, but they will be automatically transformed to other coordinate systems later as needed. Note that the components are assumed to be the representation of the metric with two lower indices, since that is how metrics are usually defined; one upper and one lower index is just the identity matrix, and two upper indices is the inverse metric. Optionally, we can also specify a symbol to be used for representing the metric.
Let us use this module to create a tensor object for the Minkowski metric, specifying the components in Cartesian coordinates: As with TNewCoordinates, we received the ID string for the new tensor object as output.
Similarly, let us define the Schwarzschild metric, this time specifying the components in spherical coordinates. However, before we can safely do so, we should take one additional step. When we defined the coordinates using TNewCoordinates, the symbols used were automatically reserved, that is, cleared and protected from future changes, using TSetReservedSymbols. However, TNewMetric does not automatically reserve any symbols it finds in the metric, since you might not want to reserve some of them. Since the Schwarzschild metric has a free parameter M, the mass of the black hole, we must reserve this symbol manually: Note that since we do not specify a symbol, the symbol "g" will be used by default, as demonstrated below.

Displaying tensors
OGRe contains two modules for displaying the contents of tensors. The first one is TShow, which shows the ID, symbol, indices, coordinates, and components in those indices and coordinates, in vector or matrix form when applicable: We can also show the two metrics we created using these coordinates: The other module available in OGRe for displaying the contents of tensors is TList, which lists all of the unique (up to sign), non-zero components of the tensor. It is usually the best option for higher-rank tensors, which cannot be displayed in vector or matrix form, such as the Christoffel symbols or Riemann tensor (see below). Its syntax is: Minkowski: η tt = -η xx = -η yy = -η zz = -1

In[ ]:= TList["Schwarzschild"]
OGRe: Schwarzschild: Note that both TShow and TList display their outputs using the DisplayFormula Notebook style. It's up to the user to decide how to define this style; in this notebook, I used a font size of 20 and aligned to center. The style may be easily changed by clicking on the "Format" menu in Mathematica and selecting "Edit Stylesheet". Then, choose the DisplayFormula style, select that cell, and modify its format using the "Format" menu.
If, as in the examples above, no additional arguments are given to TShow and TList, they display the tensors in their default indices and default coordinates, which are the ones first used to define the tensor (unless you change them later). So, for example, the default indices of the Minkowski metric are two lower indices, and its default coordinates are Cartesian. We will show later how to change these defaults, and how to display any tensor in any index configuration and coordinate system.

Line and volume elements
In the case of metrics, we can also display them as a line element using the module f is a form function which is equal to 1 inside a "warp bubble" of finite radius and 0 outside it, and v is the velocity of the bubble, which can be faster than the speed of light (v > 1). Note that we reserved both v and f, since they are parameters used in the metric. Here we see another advantage of reserving symbols: since v and f are reserved symbols, and they are functions of the coordinates only, their arguments are not shown when using TShow and TList, for improved readability.
It is easy to see that the metric is flat where f = 0, that is, outside the bubble. Its line element is: We can simplify it by doing some clever factorization: In this form, it is immediately clear that the metric is flat outside the warp bubble (where f is 0), and inside the warp bubble (when f is 1) it is a flat metric translated by an amount v[t] t in the z direction: Another thing we can do with a metric is calculate its volume elements squared, which is simply the determinant of the metric: This means that the letter μ will be used for the first index, ν for the second, and so on. However, sometimes we want to use different letters. For example, let us change the indices to lowercase English letters:

In[ ]:= TSetIndexLetters["abcdefghijklmnopqrstuvwxyz"]
Out[]= abcdefghijklmnopqrstuvwxyz "Show" will now use these letters -in this particular order -when displaying tensors: Finally, let us reset the letters to the default:

Out[]= μνρσκλαβγδεζϵθιξπτϕχψω
Note that TList always uses the coordinate symbols themselves for the indices (e.g. η tt , η xx , etc.), so it is not affected by TSetIndexLetters.

Creating tensors in a given manifold
Any tensors other than coordinates and metrics are created using the module TNewTensor:
tensorID is a string that will be used to identify the new object, and must be unique.
metricID is the unique ID of a tensor object representing a metric, created using TNewMetric[]. The metric will be used to raise and lower indices for the new tensor.
coordinatesID is the unique ID of a tensor object representing a coordinate system, created using TNewCoordinates[]. This coordinate system will be used to specify the components of the new tensor. If omitted, the default coordinate system of the metric metricID will be used.
indices must be a list of the form {±1, ±1, ...}, where +1 corresponds to an upper index and -1 corresponds to a lower index.
components is a list specifying the representation of the tensor with the index configuration indices and in the coordinate system coordinatesID.
symbol will be used to represent the tensor in formulas. If not given, the placeholder  will be used.
In OGRe, all tensor objects must have an associated metric -except coordinate objects, and the metric tensors themselves. This is because OGRe automatically raises and lowers indices as appropriate for various operations such as adding and contracting tensors, and it cannot do so without knowing which metric to use. Even scalars, which have no indices, should still be associated to a specific metric -since they can multiply other tensors, and you don't want to multiply tensors from different manifolds together.
The index configuration of the tensor is a 1-dimensional List. The number of indices is the rank of the tensor. Each element in the List corresponds to one index, with +1 specifying an upper index and -1 specifying a lower index. For example, {-1,-1} corresponds to a tensor such as the metric g μν , which has two lower indices, while {1,-1,-1,-1} corresponds to a tensor such as the Riemann tensor R σμν ρ , which has one upper index followed by three lower indices.
The components of the tensor must also be a List. The components are the representation of the new tensor in the given index configuration and coordinate system. If a coordinate system is not specified, the default coordinate system of the associated metric will be used -but it is recommended to always specify the coordinate system explicitly, to avoid accidentally defining the tensor with the wrong components. The components will be automatically converted to different indices or coordinates later as needed, as we will demonstrate below.
To create a scalar, or a tensor of rank 0 (with no indices), we must input an empty list {} for the indices, and a list with one item for the components. For example, let us define the Kretschmann scalar in the Schwarzschild spacetime (below we will show how to calculate it directly from the metric): In[ ]:= TNewTensor"Kretschmann", "Schwarzschild", "Spherical", Again, the output is the unique ID of the tensor object that was created. Let us show the tensor:

In[ ]:= TShow["Kretschmann"]
OGRe: Kretschmann: Notice that the output of TNewTensor is also the input of TShow, so in fact, we could compose them together using @. We will do so from now on.
Similarly, we can create a vector, or a tensor of rank 1 (with one index). For example, let us create a vector for the 4-velocity of a particle moving at 3-velocity v along the x direction in Minkowski space. (We do not need to reserve the symbol v, since we already reserved it for the Alcubierre metric above.) Since the 4-velocity has an upper index by definition, we make sure to define the components in the representation of the tensor with an upper index by specifying the index configuration as {1}:

24
In[ ]:= TShow@TNewTensor"FourVelocity", "Minkowski", "Cartesian", Again, the output of TNewTensor was the ID of the tensor, "FourVelocity", but that is also the input we want to pass to TShow, so we composed the two modules together. Note also that since we did not specify a symbol for this tensor, its symbol is just a placeholder . We will give it a proper symbol below.
Finally, as an example of a tensor of rank 2 (with two indices), let us define the stress-energy tensor T μν for a perfect fluid. First, let us reserve the symbols ρ (for the energy density) and p (for the pressure):

{1, 1}, DiagonalMatrix[{ρ, p, p, p}], "T"]
OGRe: PerfectFluid: In a similar manner, we could also define tensors of rank 3 and above. However, such tensors are most often derived by operating on lower-rank tensors, rather than defined manually via their components. We will see an example of such a derivation when we derive the Christoffel symbols and Riemann tensor from the metric below.

Operations on single tensors Changing the symbol or ID of a tensor
If we ever want to change the symbol used to display a tensor, we can simply use the module

TChangeSymbol:
In For example, let us give the symbol u to the four-velocity, and then show it:

newID.
If the tensor is a metric or a coordinate system, all currently defined tensors will be scanned, and any references to oldID will be replaced with newID.
For example, let us change the ID of the 4-velocity tensor from "FourVelocity" to "4-Velocity":

Out[]= 4-Velocity
The old ID no longer represents any tensor object, so we get an error if we try using it:

Out[]= $Aborted
Note that this error is associated with the symbol TMessage. Any message not associated with a specific OGRe module will be associated with this symbol.
We can access the tensor using the new ID:

In[ ]:= TShow["4-Velocity"]
OGRe: 4-Velocity: Note that when we define a tensor using a metric and a coordinate system, OGRe doesn't store the actual metric components or coordinates inside the tensor object -it only stores references to the relevant objects, using their IDs. This both improves performance and allows us to modify the metric or coordinates later without having to redefine all the tensors derived from them. For this reason, if the tensor to be renamed represents a metric or a coordinate system, OGRe will automatically update the references in the definitions of all of the tensors that have been defined so far in the session using that metric or coordinate system. This guarantees that there are never any broken references.

Deleting and overwriting tensors
If we want to delete a tensor we have created, we can use the TDelete module: To prevent breaking references, TDelete will not delete a tensor object representing a metric or coordinate system if it is referred to by any other tensor. For example, if we try to delete the coordinate system "Cartesian", we will get an error message, since it is used as the default coordinate system for "Minkowski" (among others):

In[ ]:= TDelete["Cartesian"]
TDelete: The coordinate system "Cartesian" cannot be deleted, as it is the default coordinate system of the tensor "Minkowski". To delete the coordinate system, first change the default coordinate system of "Minkowski" and any other relevant tensors.

Out[]= $Aborted
Similarly, we cannot delete the metric "Minkowski" since it was used to define the tensor "PerfectFluid" (among others):

In[ ]:= TDelete["Minkowski"]
TDelete: The metric "Minkowski" cannot be deleted, as it has been used to define the tensor "PerfectFluid". To delete the metric, first delete "PerfectFluid" and any other tensors defined using this metric.

Out[]= $Aborted
There is no module to change the components of a tensor after it has already been defined, as this may break class invariants (in other words, introduce inconsistencies in the data). Instead, you must create a new tensor with the same ID using TNewTensor. By default, OGRe does not allow overwriting tensors, to prevent loss of data:

Out[]= 4-Velocity
Users who want to be able to create new tensors with the same ID as an existing tensor without deleting the old tensor first, and are confident that they will not accidentally lose any data by doing so, may enable overwriting tensors using TSetAllowOverwrite: Note that this setting is persistent between sessions -if you turn overwriting on, then it will remain on permanently, even in other Mathematica sessions, until you turn it back off.

Raising and lowering indices
Raising and lowering indices is one of the most basic tensor operations. For example, if we have a vector represented with one upper index, v ν , we can turn it into a covector, which is represented with one lower index, by contracting it with the metric: This is called "lowering an index". Here and in the rest of this documentation, we will be using the Einstein summation convention, where the same index repeated exactly twice, once as an upper index and once as a lower index, implies summation over that index. In this case, the implied summation is over ν ∈ {0, 1, 2, 3}: Such a sum over an index is called a contraction, and it is a generalization of the inner product, as we will describe in more details below. Conversely, if we have a covector w μ , we can raise its index by contracting it with the inverse metric: This works the same for indices of higher-rank tensors. For example, if we have a tensor of rank 2 represented with two upper indices, T μλ , we can lower either one or both of its indices: In OGRe, since tensor objects are abstract tensors, independent of any specific index configuration, there is no notion of raising or lowering the indices of a tensor object. Instead, one simply request to display the components of the tensor with the desired index configuration. This works with both the TShow and TList modules, by simply adding as a second argument the list of indices in the format {±1,±1,...}, as when we created a new tensor.
As an example, let us show the vector "4-Velocity" with a lower index, that is, with index configuration {-1}: OGRe automatically knows to use the Minkowski metric to lower the index, which means that a minus sign has been added to the first component, as expected. Similarly, let us lower just the second index on PerfectFluid: In PerfectFluid: The components of the representation of the metric with two upper indices are the components of the inverse metric, since Therefore, a quick way to show the components of the inverse metric is: For the same reason, the metric with one upper and one lower index is just the identity matrix: Schwarzschild: As explained above, if the modules TShow or TList are called without any arguments, the tensor is displayed in its default index configuration, which is the one first used to define the tensor. So the 4-velocity has one upper index by default, and the stress tensor has two upper indices by default, because that is how we initially defined them. However, the default indices can be changed using the module TChangeDefaultIndices: Now, when we display the tensor using TShow without any arguments, this is the index configuration that will be used: 32

Coordinate transformations
The components of any tensor may be transformed from one coordinate system x μ to another coordinate system x μ ′ using the following prescription:  For every lower index μ, add a factor of ∂x μ /∂x μ ′ (i.e. the derivative of the old coordinates with respect to the new, or the Jacobian).
 For every upper index μ, add a factor of ∂x μ ′ /∂x μ (i.e. the derivative of the new coordinates with respect to the old, or the inverse of the Jacobian).
For example, given a tensor with components T αβ in a coordinate system x μ , we can transform to components T α ′ β ′ in another coordinate system x μ ′ as follows: For a general rank ( p, q) tensor with p upper indices α 1 , …, α p and q lower indices β 1 , ..., β q , the transformation takes the form As a mnemonic for this formula, recall that two indices may only be contracted if one of them is an upper index and the other is a lower index. If an index is in the denominator of a derivative, then its role is reversed (upper ↔ lower). Thus the old (non-primed) and new (primed) indices can only be in places that allow properly contracting the Jacobian or inverse Jacobian with the tensor. For example, α 1 is an upper index in T and therefore must be contracted with a lower index. Thus, ∂ x α 1 must be in the denominator, to lower its index and allow it to be contracted with the tensor.
As we saw above, OGRe automatically knows how to raise or lower indices as needed using the appropriate metric. Similarly, any operation that requires transforming to another coordinate system will preform the transformation automatically behind the scenes. However, for this to happen, OGRe needs to know the appropriate transformation rules. These are defined between the tensor objects representing the coordinates, which were generated using the module TNewCoordinates. The rules for transforming from a source coordinate system to a target 33 coordinate system are stored within the tensor object representing the source. This is done using the module TAddCoordTransformation: These will be stored in the data of the object "Cartesian". Note that we did not have to input a rule for t, since in this case, it stays the same. Conversely, let us add the rules to transform from spherical to Cartesian coordinates: In[ ]:= TAddCoordTransformation"Spherical"  "Cartesian", r  x 2 + y 2 + z 2 , θ  ArcCos z x 2 + y 2 + z 2

, ϕ  ArcTan[x, y];
These will be stored in the data of the object "Spherical". Now OGRe knows how to convert back and forth between these two coordinate systems -and this will happen automatically whenever required. We just needed to provide these rules once and for all, and any tensor initially defined in one coordinate system can now be automatically converted to the other.
As in the case of raising and lowering indices, displaying a tensor in a different coordinate system is a simple matter of calling the modules TShow or TList with an additional argument specifying the coordinate system to use. For example, let us show the Minkowski metric in spherical coordinates:

In[ ]:= TShow["Minkowski", "Spherical"]
OGRe: Minkowski: We can also ask to see a tensor in a specific index configuration and a specific coordinate system: The module TList works in exactly the same way, for example:

In[ ]:= TList["Kretschmann", "Cartesian"]
OGRe: Kretschmann: Just as with default indices, every tensor has a default coordinate system, which is, initially, the one we used to create it. We can change it using the module TChangeDefaultCoords, and then whenever we display the tensor, it will be displayed in that coordinate system if no other coordinate system is specified: Now, when we display the tensor using TList without any arguments (or with just indices), this is the coordinate system that will be used:

Setting simplification assumptions
Often, coordinate transformations are only invertible for a specific range of coordinates. For example, let us define a new scalar in Minkowski space, which is equal to the spatial distance from the origin: In[ ]:= TShow@TNewTensor"SpatialDistance", "Minkowski", "Cartesian", {},  x 2 + y 2 + z 2 , "d" OGRe: SpatialDistance: d (t, x, y, z) = x 2 + y 2 + z 2 When we convert this scalar to spherical coordinates, we expect to get r, but instead we get the absolute value of r: The output of this module is always an Association indicating whether variables are assumed to be real and listing the user-defined assumptions.
Note that these assumptions will be globally applied to all tensor calculations, which is usually the desired behavior, since for example the assumption r ≥ 0 should apply to all tensors that use spherical coordinates. Let us set this assumption now: In fact, it is good practice to set any assumptions regarding the coordinates as soon as they are defined, so we should have set this assumption already when we defined the spherical coordinates in the beginning of this documentation. From now on, this assumption will automatically be used by modules that perform any kind of calculations on tensors. However, if we now try to show the scalar again using TShow, we still get the same (non-simplified) result:

In[ ]:= TShow["SpatialDistance", "Spherical"]
OGRe: SpatialDistance: The reason is that when OGRe calculates the components of a tensor in a particular representation, it calculates them once and for all, and then saves them in the object's data to be reused later. This is done to improve performance, so that the components don't have to be recalculated every time they are needed. In this case, since we already calculated the spatial distance in spherical coordinates when we showed it above -before we set the new simplification assumptions -that value has been saved, and will not be recalculated automatically, even though we now have new assumptions.
However, we can force the simplification of the stored components with the new assumptions using the module TSimplify:

Importing and exporting tensors
In a single Mathematica session, one can spend considerable time and effort defining tensors and doing various operations on them. However, as the tensors are only stored in memory, once the session is over and the kernel is stopped, all that information will be lost. Due to the non-linear nature of Mathematica notebooks, even if you saved the entire notebook, it can be hard or even impossible to retrace your steps and get the exact same tensors again from the information in the notebook.
Instead of defining all the tensors from scratch, OGRe allows the user to export tensors and then import them in another session to continue working with them later. The tensors are stored internally as an Association, and exporting a tensor essentially amounts to outputting the corresponding Association. Warning: Do not change the exported data manually, as that might break the class invariants and cause errors after importing it back!
To export a single tensor, use the TExport module: OGReVersion  v1.6 (2021-08-07) This is a nested Association. The upper level has just one key: "4-Velocity", which is the ID of the tensor. Its value is another Association, which has the following keys:  "Components": An Association containing the components of the tensor in different representations, each with a specific index configuration and coordinate system. The components are only generated when a particular combination of indices and coordinates is requested for the first time, so for example, here the components with both an upper and a lower index in Minkowski coordinates have been stored, but no components in spherical coordinates, since we have not tried to access them so far.
 "DefaultCoords": The default coordinate system to use when displaying the tensor.
 "DefaultIndices": The default index configuration to use when displaying the tensor.
 "Metric": The unique ID of the metric that will be used to raise and lower the tensor's indices. Note that this is only a reference, so a tensor object with this ID must exist. If a tensor is exported, its metric must be exported separately as well for raising and lowering of indices to work.
 "Role": The role of the tensor, which depends on the module that created it. Will be "Coordinates" if the tensor was created using TNewCoordinates, "Metric" if the tensor was created using TNewMetric, or "Tensor" if the tensor was created using TNewTensor. Other modules that we will discuss below, such as TCalc, TCalcChristoffel, and TCalcRiemannTensor, have corresponding roles as well. Additional roles are only used internally by OGRe, such as "Temporary" for a temporary tensor created as an intermediate step in a calculation.
 "Symbol": The symbol used to represent the tensor when displaying it.
 "OGReVersion": The version of the package used to create the tensor. (Note that this key is not stored internally, it is added by TExport.) Other keys also exist in special cases. For example, for tensor objects representing coordinate systems, the keys "CoordTransformations" and "Jacobians" are used to store the details of coordinate transformations defined using TAddCoordTransformation, as can be seen by exporting "Cartesian":  The output of TExport is a raw Association, and is not intended to be read by humans. If you wish to display the information encoded in the object in human-readable form, use TInfo:

Symbol
TInfo[] lists all the tensors created so far in this session: coordinate systems, metrics, and the tensors associated with each metric.
TInfo [ID] displays information about the tensor object ID, including its symbol, role, associated metric, and default coordinates and indices, in human-readable form.
If ID represents a coordinate system, displays a list of all tensors using it as their default coordinate system.
If ID represents a metric, displays a list of all tensors using it as their associated metric.
TInfo will also tell you which other tensor objects use this tensor as their metric or default coordinate system, if applicable. For example:

Schwarzschild  Kretschmann
We see that we created 9 tensors in total so far: 2 coordinate systems, 3 metrics, 3 tensors associated with the Minkowski metric, and 1 tensor associated with the Schwarzschild metric.
To import a tensor back after it has been exported, use the TImport module:

Symbol TImport[data] imports a tensor that has been exported using TExport[].
To export all of the tensors defined in the current session, we may use the TExportAll module: The output will be an Association as above, where the keys are the names of all the tensors defined so far in the current session, and the value of each key is the data of that tensor. We will not show the complete output here, since it is very long, but let us just demonstrate that the keys of the Association are all of the tensors we defined so far in this session: TExportAll exports not only the tensors, but also a special key called Options, which contains the current version of the package (for compatibility purposes, in case the storage format changes in future versions) and any options set by the user during the session. Note that keys associated with tensor objects, such as "Cartesian", "Spherical", and so on, are always strings, but Options is not a string; this is to ensure it doesn't get accidentally interpreted as a tensor object.
We can see the options configured in this session by reading the value of the Options key:
The output of TExportAll can be saved in a Mathematica notebook, and imported using the module TImportAll: WARNING: This will irreversibly delete ALL of the tensors already defined in the current session.
Note that TImportAll will delete any tensors already defined in the current session, whether or not they have the same ID as an imported tensor. To keep them, first export them into an Association, Join it with the Association you wish the import, and then use TImportAll on the result -or, alternatively, import the tensors one by one using TImport. Similarly, any settings configured during the session will be replaced with the imported settings.
If a file name is given to TExportAll, the output will be saved to that file. If only the name of the file is given, and not a full path -e.g. TExportAll["OGReTensors.m"] -then the file will be saved in the current working directory, as given by Directory[]. To change the working directory, use SetDirectory[] before exporting the file. To import from the file, pass the file name to TImportAll, e.g. TImportAll["OGReTensors.m"].
Note that if for some reason you would like to delete all the tensors defined so far in the current session, you can simply import an empty Association as follows: TImportAll[<||>]. Be careful, as this action is irreversible! Finally, sometimes you may want to extract the components of a tensor in a specific representation as a List so you can use them outside of this package, as regular Mathematica expressions rather than tensor objects. This is done using TGetComponents:


We can now treat InverseSchwarzschild as any other List in Mathematica -for example, extract the element at a particular position: If the desired index configuration and/or coordinate system are not specified, the default ones will be used. However, it is important to always know exactly which representation the components are in, to avoid confusion. Thus, you will be notified which representation was used:

In[ ]:= TGetComponents["Schwarzschild"]
TGetComponents : Using the default index configuration {-1, -1} and the default coordinate system "Spherical". targetID specifies the ID of the tensor object in which to store the result.
If omitted, the ID "Result" will be used.
targetIndices specifies the order of indices of the resulting tensor. The indices must be a permutation of the free indices of formula. If omitted, the indices are assumed to be in the same order as they appear in formula.
symbol specifies the symbol to use for the resulting tensor. If omitted, the placeholder symbol  will be used.
Any use of TCalc should be thought of as invoking a tensor equation of the form where both the left-hand side and the right-hand side are tensors of the same rank and with the same free indices (that is, indices that are not being contracted). L β 1 ⋯β q α 1 ⋯α p is the tensor that will be used to store the result, while R β 1 ⋯β q α 1 ⋯α p is (the final result of) a general tensor calculation which contains any combination of addition, multiplication by scalar, trace, contraction, partial derivative, and covariant derivative. Let us now go over these operations one by one, and give some examples.

Addition of tensors
Addition of tensors in OGRe is represented by a sum of the form "ID1"["indices1"] + "ID2"["indices2"], where "ID1" and "ID2" are the IDs of the tensor objects to be added, and "indices1" and "indices2" are the index specifications for each tensor, given as a string of letters. Note that you do not specify the position (upper or lower) of the indices. Furthermore, just like in any tensor equation, the index letters themselves have no meaning; they are just placeholders. Therefore, "αβγ", "abc", and "ABC" are all completely equivalent. The only requirement is that the indices are consistent; in the case of addition, this means that both tensors being added must have the same indices up to permutation.
The following constraints apply to addition of tensors:  You may not add a tensor representing a coordinate system to any other tensor, since coordinates do not transform like tensors.
 You may not add two tensors associated with different metrics, since their sum would have undefined transformation properties.
 You may not add two tensors with different ranks, since that is not a well-defined operation.  As stated above, both tensors must have the same indices up to permutation. A μν + B μν and A μν + B νμ are both okay, but A μν + B αβ doesn't make sense, as it has more free indices than the rank of the result.
As an example, let us add the Minkowski metric η μν and the perfect fluid stress tensor T μν :

In[ ]:= TShow@TCalc["Minkowski"["μν"] + "PerfectFluid"["μν"]]
OGRe: Result: Notice that the result was stored in a tensor with ID "Result", and has no symbol. We can add a symbol to use as an additional argument: With this symbol, the tensor equation we are calculating becomes: We can also use a different ID for the result by giving it as the first argument, with or without a symbol: In Sometimes it is also helpful to specify indices for the result. To give an example, let us define the following non-symmetric tensor: However, if we flip its index string from "μν" to "νμ", then we instead get: Since the order of indices now matters, we can also define an index string for the left-hand side, to indicate the order of indices we want in the result. If that string is also "νμ", then we get back to the original result: We see that explicitly specifying the indices in TCalc allows it to have a 1-to-1 correspondence with any tensor equation. Importantly, note that there is no difference between "NonSymmetric"["μν"] and "NonSymmetric"["νμ"] on its own, as the index labels themselves are meaningless unless there is some context in which they obtain meaning -as is always the case for tensor expressions. However, there is a big difference between, for example, "Minkowski"["μν"] + "NonSymmetric"["μν"] and "Minkowski"["μν"] + "NonSymmetric"["νμ"], as the indices have a different order, and thus the two expressions refer to adding different components.
Of course, any number of tensors can be added, not just two -and the same tensor can be added with different index configurations. For example, we can calculate:

Multiplication of tensor by scalar
Multiplication of tensor by scalar in OGRe is represented by a product of the form scalar * "ID"["indices"], where "ID" is the ID of the tensor object to be multiplied, "indices" is an index specification as for addition, and scalar is the scalar to multiply by. Note that scalar should be a normal Mathematica symbol, such as a number or a variable, and not a tensor object of rank 0. To multiply a tensor by a tensor of rank 0, use contraction instead, as detailed in the next section.
As an example, let us multiply the Minkowski metric η μν by 2. The tensor equation we will be calculating is:

Taking traces and contracting tensors: theoretical review
The most complicated tensor operation is contraction, a generalization of the vector inner product. This is done by summing over one or more disjoint pairs of indices, with each pair containing exactly one upper index and one lower index. Raising and lowering indices is one example of contraction: the metric (or its inverse) is contracted with a tensor. Coordinate transformations are another example, where we contract the Jacobian (or its inverse) with a tensor.
The simplest example of contraction is the vector inner product, which is defined as the contraction of a vector (one upper index) with a covector (one lower index): The middle part of this equality comes from the fact that, as explained above, when we lower an index on w ν , we use the metric: This, in turn, justifies the notation g(v, w) on the right-hand side, as this is, in fact, an inner product of two vectors using the metric g (in index-free notation).
Contraction of indices in higher-rank tensors is simply a generalization of the inner product, for example: We can also contract more than one index: This simply amount to the fact that lowering both indices of B αβ involves contracting each index with the metric. We can even contract two indices of the same tensor: This is also called taking the trace. Furthermore, it is also possible to contract pairs of indices 54 from more than two tensors at the same time: However, such operations can always be broken down into individual contractions of pairs of tensors. For example, in this case, one could first contract B νρ with C ρσ and then contract the result with A μν -which is indeed how this kind of contraction will be performed in OGRe in practice: In a contraction, there are two types of indices: contracted indices, which are summed upon, and free indices, which are not summed upon. The rank of the tensor that results from the contraction is the number of free indices. So for example, in the expression A μα B αν we have one contracted index, α, and two free indices, μ and ν. Therefore, the resulting tensor is of rank two: T ν μ = A μα B αν .

Taking traces and contracting tensors: OGRe syntax
Contraction of tensors in OGRe is represented by an expression of the form "ID1"["indices1"] . "ID2"["indices2"], where "ID1" and "ID2" are the IDs of the tensor objects to be contracted, and "indices1" and "indices2" are the index strings for each tensor. Any matching indices in both index strings will be contracted. This means that, for example, v μ w μ is calculated using "v"["μ"] . "w"["μ"] and A μν B νρ C ρσ is calculated using "A"["μν"] . "B"["νρ"] . "C"["ρσ"]. Note that the user doesn't need to worry about the contracted indices being one upper and one lower, which is a common source of errors when contracting tensors by hand; the order of the indices, and whether the same index repeats twice, is all that matters.
As a first example, let us create the stress-energy tensor for a perfect fluid with a 4-velocity u μ . This is defined as follows: Even though this does not involve any contractions, it still counts as a "trivial" contraction, since two tensors (the 4-velocities) are juxtaposed next to each other to create another tensor. This is also known as an outer product. Therefore, it uses the same dot product syntax as any other contraction, except that there are no matching indices. Note that this expression involves not just contraction (in the first term), but also multiplication by scalar (in both terms), and addition of the two terms together. Again, OGRe takes care of everything behind the scene, so this just works:

In[ ]:= TShow@TCalc["PerfectFluidFromVelocity"["μμ"]]
OGRe: Result: Of course, this also works for tensors with more than two indices, as we will see below. Any combination of indices can be used, with no limit on the number of traces taken for each tensor.

Derivatives and curvature tensors
The partial derivative ∂ μ is represented in OGRe using the symbol TPartialD. It can be contracted with other tensors using the usual OGRe contraction notation -including an appropriate index string -to calculate gradients and divergences. The gradient of a tensor is the partial derivative ∂ μ acting on the tensor with a free index, e.g. ∂ μ ϕ, ∂ μ v ν , or ∂ μ T νλ , resulting in a tensor of one rank higher. For example, we can calculate the gradient ∂ μ K of the Kretschmann scalar as follows:

In[ ]:= TShow@TCalc[TPartialD["μ"]."Spherical"["μ"]]
OGRe: Result:  (t, r, θ, ϕ) = 4 As you can see, the syntax for both the gradient and divergence is the same; if the index specification of TPartialD["μ"] matches one of the indices of the tensor to its right, then the divergence will be calculated, otherwise the gradient will be calculated.

WARNING: When applying partial derivatives to tensors, the result generally does not transform like a tensor under a coordinate transformation.
For this reason, in general relativity we normally use the covariant derivative instead of a partial derivative. However, there are three important exceptions, where partial derivatives must be used: in the covariant derivative itself, the Levi-Civita connection, and the Riemann tensor, all of which will be discussed below.
Of these three special cases, the covariant derivative and the Riemann tensor turn out to nonetheless transform like tensors under coordinate transformations, due to cancellations. However, the Levi-Civita connection, whose components are called the Christoffel symbols, has a special transformation rule, which is used automatically by OGRe, as we will show.
In all other cases, if the user creates an arbitrary tensor using partial derivatives, the result will generally transform incorrectly under a coordinate transformation in OGRe. Therefore, it is highly recommended to avoid using partial derivatives with TCalc unless you really know what you're doing.

The Christoffel symbols
The Christoffel symbols are a very important tensor-like objects in differential geometry. They are the components of the Levi-Civita connection, which is the unique torsion-free connection that preserves the metric. The Christoffel symbols are defined as follows: Each of the terms inside the parentheses is a gradient of the metric, with different indices. For example, the first term ∂ μ g νσ is represented in OGRe as TPartialD["μ"]."Metric"["νσ"] where "Metric" is the tensor object representing the metric. Since contraction, addition, and multiplication by scalar can be combined arbitrarily when using TCalc, we can calculate the Christoffel symbols in a straightforward way as follows: Result: However, there is a problem; as we mentioned above, the Christoffel symbols are not the components of a tensor, meaning that the Levi-Civita connection does not transform as a tensor does under a coordinate transformation. Indeed, by transforming the metric in the definition, one can show that The first term is the familiar transformation rule for a tensor, with one factor of the Jacobian per index as usual. However, there is also an extra second term, meaning that the Christoffel symbols do not transform like a tensor.
(Actually, you are also not supposed to raise or lower indices in the Christoffel symbols, but in practice, you can do that as long as you make it clear that it's just an abuse of notation -you are only adding factors of the metric, not creating a new tensor representation with different transformation properties.) Due to the extra transformation term, the tensor object we calculated manually above using TCalc must not be used. Instead, we should use the built-in module TCalcChristoffel, which not only performs the calculation automatically for us, but also marks the result as a special object with special transformation properties: Note that the Christoffel symbols are not the components of a tensor, but this tensor object will know to transform according to the correct rules under change of coordinates.
Let us, then, calculate the Christoffel symbols for the Schwarzschild metric properly, using TCalcChristoffel:

In[ ]:= TList@TCalcChristoffel["Schwarzschild"]
OGRe: SchwarzschildChristoffel: These are the same components we got before, but now they will transform properly. Note that the name of the tensor object created by TCalcChristoffel is always the name of metric with the word Christoffel appended to it (no spaces). SimpleMetricManualChristoffel: Then, with the built-in module TCalcChristoffel:

In[ ]:= TList@TCalcChristoffel["SimpleMetric"]
OGRe: SimpleMetricChristoffel: The two results have the same components, as expected. But now, let us now transform them to spherical coordinates. First, we transform the tensor object obtained using TCalcChristoffel:

In[ ]:= TList["SimpleMetricChristoffel", "Spherical"]
OGRe: SimpleMetricChristoffel: This is the correct representation of the Christoffel symbols in spherical coordinates, as the extra term in the transformation was taken into account. However, if we transform the Christoffel symbols we obtained manually using TCalc, we get:

In[ ]:= TList["SimpleMetricManualChristoffel", "Spherical"]
OGRe: SimpleMetricManualChristoffel: Now, when we calculate the Christoffel symbols manually from this metric, we will get their correct representation in spherical coordinates. This is because TCalc always performs the calculations internally in the default coordinates of the first tensor, so the result was calculated from scratch in spherical coordinates, instead of being calculated first in Cartesian coordinates and then transformed: SimpleMetricManualChristoffelSpherical: Here, a (t) is the scale factor and k is the curvature of the spatial surfaces, with k = +1, 0, -1 corresponding to positively curved, flat, or negatively curved respectively. Its Christoffel symbols can be easily calculated using TCalcChristoffel:

In[ ]:= TList@TCalcChristoffel["FLRW"]
OGRe: FLRWChristoffel: Notice how the derivatives of the function a are shown by TList in the notation ∂ t a instead of a'[t]. This may not seem like much of an improvement in this case, but in the case of partial derivatives of functions of many arguments, it can greatly improve readability. Here is a simple example:

The Riemann tensor
The Riemann curvature tensor R σμν ρ can be calculated from the Christoffel symbols using the definition: Notice that even though it contains partial derivatives, it nonetheless transforms like a tensor under a change of coordinates, because the extra transformation terms exactly cancel each other. To calculate this tensor, we can simply write down the formula in TCalc with the correct indices contracted. Note that this time we specified the LHS indices explicitly, since they are not in the same order as the RHS indices in our definition: The Riemann tensor with all its indices down satisfies the following symmetry and antisymmetry relations: We can verify this for the Schwarzschild Riemann tensor using TList, as it automatically detects components that are the same up to sign:

In[ ]:= TList@TCalcRiemannTensor["FLRW"]
OGRe: FLRWRiemann: Using TCalcRiemannTensor also has the advantage that it takes a metric as an input, and will automatically calculate the Christoffel symbols of the metric using TCalcChristoffel if they have not already been calculated. The same principle also applies to the other built-in modules for calculating curvature tensors, which we will present below; they always take a metric as input, and will calculate any intermediate tensors in their definitions automatically as needed.
Finally, recall that above, we gave the Kretschmann scalar for the Schwarzschild metric as an example of a scalar. Now that we have the Riemann tensor, and the ability to contract tensors, we can actually calculate the Kretschmann scalar from scratch. The formula is: so it can be easily calculated in OGRe as follows: The Ricci tensor and scalar The Ricci tensor R μν is the trace of the first and third indices of the Riemann tensor: Therefore, we can calculate it by taking the trace, with the usual TCalc syntax. For the Schwarzschild metric, the Ricci tensor vanishes: Result: No non-zero elements.
We can also use the shorthand module TCalcRicciTensor:

Symbol
TCalcRicciTensor[metricID] calculates the Ricci tensor from the metric metricID and stores the result in a new tensor object with ID "metricIDRicciTensor". If a tensor with ID "metricIDRiemann" exists, it will be assumed to be the Riemann tensor of the metric, and will be used in the calculation. Otherwise, "metricIDRiemann" will be created using Here is the Ricci tensor for the FLRW metric:

In[ ]:= TList@TCalcRicciTensor["FLRW"]
OGRe: FLRWRicciTensor: The Ricci scalar is the trace of the Ricci tensor: We can calculate it from the Ricci tensor by taking the trace:

In[ ]:= TShow@TCalc["FLRWRicciTensor"["μμ"], "R"]
OGRe: Result: Or, as usual, we can simply use the shorthand module TCalcRicciScalar to calculate it directly from the metric: "metricIDRicciScalar". If a tensor with ID "metricIDRicciTensor" exists, it will be assumed to be the Ricci tensor of the metric, and will be used in the calculation. Otherwise, "metricIDRicciTensor" will be created using 72

In[ ]:= TList@TCalcRicciScalar["FLRW"]
OGRe: FLRWRicciScalar: The Einstein tensor The Einstein tensor G μν is given by: As with all other curvature tensors, we can calculate it by combining the previously calculated tensors with the usual syntax: Result: Or we can use the built-in module TCalcEinsteinTensor: "metricIDEinstein". If a tensor with ID "metricIDRicciTensor" exists, it will be assumed to be the Ricci tensor of the metric, and will be used in the calculation. Otherwise, "metricIDRicciTensor" will be created using TCalcRicciTensor[].

Covariant derivatives
The partial derivative has limited use in general relativity, as it does not transform like a tensor. Therefore, it is only used in special cases, such as calculating the Christoffel symbols and the Riemann tensor. The covariant derivative ∇ μ is a generalization of the partial derivative, which does transform like a tensor (as long as it acts on a proper tensor). It is defined as follows:  On a scalar Φ, the covariant derivative acts as ∇ μ Φ ≡ ∂ μ Φ.
More generally, on a rank ( p, q) tensor with components T σ 1 ⋯σ q ν 1 ⋯ν p , the covariant derivative where κ = 1 or κ = 8 π depending on your preferred units. However, unlike ∇ μ G μν = 0, the relation ∇ μ T μν = 0 is not an identity; it is an energy-momentum conservation equation. To derive the equation for the FLRW metric, let us first define the rest-frame fluid 4-velocity in this spacetime: FLRWConservation: From demanding that the t component vanishes, we get the following equation: We see that in an expanding universe, energy is not conserved, but rather, the energy density changes with time in a way that depends on the scale factor. If the universe is not expanding, that is, a  = 0, then energy will be conserved.

Curves and geodesics
The Curve Lagrangian Consider a curve, which is a function x μ (λ) on the manifold where λ is called the curve parameter. The curve Lagrangian of a metric is defined as the norm-squared of the tangent to the curve: where x  μ is the first derivative of x μ with respect to the curve parameter (in Newton dot notation).
We can calculate it using the module TCalcLagrangian: If coordinatesID is not specified, the default coordinate system of the metric will be used.
For example:

In[ ]:= TList@TCalcLagrangian["Alcubierre"]
OGRe: AlcubierreLagrangian: Notice how TList (and TShow) use Newton dot notation for the derivatives of the coordinate functions, for improved readability. To get the full expressions with the explicit derivatives, we can use TGetComponents. For example:

Geodesic equations from the Lagrangian
By applying the Euler-Lagrange equations to the curve Lagrangian: we can obtain the geodesic equations for our spacetime. This is done using the module

TCalcGeodesicFromLagrangian:
In If coordinatesID is not specified, the default coordinate system of the metric will be used.
In the Minkowski metric, the geodesic equations are:

In[ ]:= TList@TCalcGeodesicFromLagrangian["Minkowski"]
OGRe: MinkowskiGeodesicFromLagrangian: Note that this module only calculates the left-hand side of the Euler-Lagrange equations; if we equate the result to zero, we will get the actual geodesics equations. This is hinted at visually by setting the resulting tensor's symbol to 0, so that you actually see the equations when using TList.
It is trivial to see that the solution to these equations is simply a curve with a constant velocity; in a flat Minkowski spacetime, particles experience no gravitational force, and thus no acceleration (unless some other force acts on them, of course).
The derivatives with respect to the curve parameter λ are kept unevaluated, using the Mathematica function Inactive. This simplifies the equations, and can sometimes help solve them by inspection. If we want to activate the derivatives, we simply need to use Activate. Recall that TList and TShow can apply a function to the tensor's components before displaying them, so we just need to pass Activate as the last argument: As with the Lagrangian itself, the geodesic equations are displayed in compact notation when using TList. If we want the full expressions with the explicit derivatives, for example in order to pass them to DSolve and actually solve the equations, we can use TGetComponents:

Changing the curve parameter
By default, the curve parameter is λ. However, sometimes we want to use another parameter -for example τ, the proper time. To change the parameter, we use TSetCurveParameter: Let us change it to τ:

In[ ]:= TSetCurveParameter[τ];
This changes the curve parameter retroactively, so that if we get the components of any Lagrangian or geodesic equation we previously calculated, the parameter will now be τ instead of λ:
When exporting tensors using TExportAll, the choice of curve parameter is exported as well, so that when you import the tensors later using TImportAll, OGRe will automatically know which 87 curve parameter was used when calculating the imported tensors.

Overwriting metrics
If overwriting tensors has been allowed using TSetAllowOverwrite, and we overwrite a metric tensor, then all of the curvature tensors calculated from the metric being overwritten will be automatically deleted, since the curvature tensors of the new metric will generally be different. For example, let us overwrite the FLRW metric with a similar metric where k is equal to 0: In[ ]:= TSetAllowOverwrite[True]; TShow@TNewMetric"FLRW", "Spherical",
TNewMetric: All curvature tensors previously calculated from the metric being overwritten will be deleted. If we now try to access, for example, "FLRWChristoffel", we will discover that it has been deleted:

Out[]= $Aborted
We must recalculate this curvature tensor, and all others, since they will be different from the ones we calculated from the previous metric.
Finally, let us set TSetAllowOverwrite back to its default setting, since this setting is persistent between sessions:

Parallelization
The calculations we have demonstrated so far in this documentation have been quite simple, and should not take more than a second to perform on a decent computer. However, when doing research, calculations can be much more involved, and thus also take more time to complete. Typically, the most time-consuming part of any tensor calculation is not the tensor operations themselves, but rather the simplification of the final result using FullSimplify.
If simplification is taking more than a few seconds, it is highly recommended to turn on the parallelization feature, which simplifies the components of the tensors in parallel instead of one after the other. This can provide a significant performance boost, proportional to the number of parallel kernels that can be launched. Note that this number is determined by your Mathematica license, and it may be less than the number of cores in your CPU.
To demonstrate the benefits of parallelization, let us consider the following somewhat complicated non-diagonal metric, which depends on an abstract function f: OGRe: ParallelizationTest: g μν (t, x, y, z ) = ft 2  f2 t 2  f3 t 2  f4 t 2  f2 t 2  f4 t 2  f6 t 2  f8 t 2  f3 t 2  f6 t 2  f9 t 2  f12 t 2  f4 t 2  f8 t 2  f12 t 2  f16 t 2  We first calculate the Christoffel symbols for this metric without parallelization, and use AbsoluteTiming to measure how long it takes:

In[ ]:= AbsoluteTiming[TCalcChristoffel["ParallelizationTest"]]
Out[]= {10.021, ParallelizationTestChristoffel} As you can see, on my computer the calculation takes about 10 seconds. On your computer this duration may be shorter or longer, but the calculation will invariably take a considerable amount of time on any personal computer. The vast majority of that time is spent not on calculating the tensor, but on simplifying the result. This can be seen, if you run this in Mathematica, from the fact that the first progress bar, for the calculation itself, fills up almost instantly, while the second progress bar, for the simplification, fills up slowly. (The progress bars disappear when the calculation is done, so they cannot be seen in the pre-calculated notebook.)

89
To turn on parallelization, we use the module TSetParallelization: it has a small overhead and may actually impede performance. However, if simplifications are taking more than a few seconds, then it is highly recommended to enable parallelization for a significant performance boost.
Let us turn it on now:
When parallelization is first turned on, all available parallel kernels are automatically launched. As you can see, my system has a 12-core CPU, but only 8 parallel kernels can be launched, since that is what my Mathematica license allows.
We will now repeat the calculation of the Christoffel symbols, in order to see how its performance improves with parallelization. However, before we can do that, we must use the command ClearSystemCache to clear Mathematica's cache -since otherwise it will remember the result of the simplification from before, and use that to artificially speed up the calculation:

In[ ]:= ClearSystemCache[]
(If you don't trust that ClearSystemCache is enough to make a reliable benchmark, you can exit the kernel using Quit, and then reload the package and redefine the Cartesian coordinates and the test metric.) 90 We will also delete the tensor that we calculated previously, so that we can calculate it again: We see that the calculation now only took about 3.56 seconds -an improvement by roughly a factor of 3. With longer calculations, the improvement will be even more significant. Increasing the number of kernels (if it was allowed by my license) would provide an additional speedup.
As a rule of thumb, if simplifications are taking less than a few seconds, then you should leave parallelization off, as it has a small overhead and may actually impede performance in that case. However, if simplifications are taking more than a few seconds, then it is highly recommended to enable parallelization for a significant performance boost.