Skip to content

Add option to use ForwardDiff.jl to compute Christoffel symbols exactly and separate covariant form containers #78

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

tristanmontoya
Copy link
Member

@tristanmontoya tristanmontoya commented Mar 7, 2025

Previously, the collocation derivative was applied to the metric tensor components to evaluate the Christoffel symbols of the second kind approximately, using $\partial_k G_{ij} \approx \partial_k (I^N G_{ij})$ to compute

$$\Gamma_{jk}^i := \frac{1}{2}G^{il}\big(\partial_j G_{kl} + \partial_k G_{jl} - \partial_l G_{jk} \big).$$

Since the rest of the geometric information for the covariant solver is computed analytically for the spherical shell, I would like the Christoffel symbols to also be exact. Because the expressions get quite messy when differentiating by hand, an easy way to compute them is to use automatic differentiation. Since reverse-mode automatic differentiation is not needed here and Trixi.jl already has ForwardDiff.jl as a dependency, I've added the option to use ForwardDiff.jl to compute the Christoffel symbols with forward-mode automatic differentiation.

If the Christoffel symbols needed to be recomputed frequently (e.g. in an adaptive simulation, or for a time-varying metric), perhaps there would be an argument for coding them by hand for efficiency purposes, but, since for now they are only calculated once per simulation and stored at each node, using automatic differentiation makes the most sense to me.

As with the Cartesian solver, options for computing metric terms are specified using the keyword argument metric_terms in the SemidiscretizationHyperbolic constructor. Currently, the options are MetricTermsCovariantSphere(christoffel_symbols=ChristoffelSymbolsAutodiff()) (the new default) and MetricTermsCovariantSphere(christoffel_symbols=ChristoffelSymbolsCollocationDerivative()) (the old way). Other approaches could be added similarly if we so choose.

Adding this as one of several options for computing the metric terms in the covariant solver led to some restructuring of the code, particularly the containers. Previously, the same P4estElementContainerPtrArray was used for both the covariant and Cartesian formulations' cache.elements container, despite most of that information not being used for the covariant form (the covariant solver stores the metric information in cache.auxiliary_node_variables so that the nodal values can be passed into the flux, which cannot access cache.elements). Now, we have a new container type P4estElementContainerCovariant, which stores only what is needed for the covariant solver and specializes the metric terms separately from the Cartesian solver. This made Trixi.calc_mortar_flux! no longer work with covariant-form containers, but since we haven't implemented mortars yet for the covariant solver anyway, I could safely remove the Trixi.calc_mortar_flux! from the specialized Trixi.rhs!. That call will be added back if/when an adaptive covariant solver is implemented, in which case we can go back to using the standard Trixi.rhs! method for P4estMesh{2}.

To make these changes, several methods which dispatched on types like Union{AbstractEquations{3}, AbstractCovariantEquations{2,3}} were separated into the two cases to handle the different containers used by the different solvers.

The structs for specifying metric terms were also moved from semidiscretization/semidiscretization.jl to the more aptly named semidiscretization/metric_terms.jl.

When computing the Christoffel symbols exactly, the sensitivity of the covariant basis vector components described in #49 can be seen from the fact that ForwardDiff returns NaNs when using GlobalSphericalCoordinates and an even number of cells per dimension on each face of the cubed sphere, presumably due to this pole problem. The default option GlobalCartesianCoordinates avoids this problem, and should therefore always be used instead of GlobalSphericalCoordinates.

Copy link

codecov bot commented Mar 7, 2025

Codecov Report

Attention: Patch coverage is 99.17355% with 1 line in your changes missing coverage. Please review.

Project coverage is 90.99%. Comparing base (dcc9c9a) to head (bb9e921).

Files with missing lines Patch % Lines
...vers/dgsem_p4est/dg_2d_manifold_in_3d_covariant.jl 97.82% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #78      +/-   ##
==========================================
+ Coverage   90.68%   90.99%   +0.31%     
==========================================
  Files          22       22              
  Lines        2189     2277      +88     
==========================================
+ Hits         1985     2072      +87     
- Misses        204      205       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@tristanmontoya tristanmontoya marked this pull request as ready for review March 10, 2025 23:17
@tristanmontoya tristanmontoya changed the title Use ForwardDiff.jl to compute Christoffel symbols exactly for a spherical shell Use ForwardDiff.jl to compute Christoffel symbols exactly Mar 10, 2025
@tristanmontoya tristanmontoya requested a review from benegee April 15, 2025 21:14
@tristanmontoya tristanmontoya enabled auto-merge (squash) April 15, 2025 21:14
@tristanmontoya tristanmontoya disabled auto-merge April 20, 2025 04:35
@tristanmontoya tristanmontoya requested a review from amrueda April 22, 2025 01:41
@tristanmontoya tristanmontoya changed the title Use ForwardDiff.jl to compute Christoffel symbols exactly Add option to use ForwardDiff.jl to compute Christoffel symbols exactly and separate covariant form containers Apr 24, 2025
Copy link
Collaborator

@amrueda amrueda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @tristanmontoya!
Some minor comments below.

@@ -39,6 +39,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e

# A semidiscretization collects data structures and functions for the spatial discretization
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand that this line can be removed because MetricTermsCovariantSphere() is the default value of metric_terms for this constructor. Isn't that the case? Any reason why we would like to keep it here and in all other elixirs?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to pass metric_terms as a parameter to trixi_include otherwise?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it is. However, for the testing, it is only needed to specify metric_terms explicitly in elixir_shallowwater_covariant_rossby_haurwitz.jl. I added a second review with code suggestions to improve the readability of the elixirs. What do you think of this variant?

Comment on lines +384 to +395
# Use ForwardDiff.jl to automatically differentiate the covariant metric tensor components
function calc_metric_derivatives(v1, v2, v3, v4, xi1, xi2, radius, equations)
dGdxi1 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, x, xi2, radius,
equations), xi1)
dGdxi2 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, xi1, x, radius,
equations), xi2)
return dGdxi1, dGdxi2
end

# Use the collocation derivative operator to numerically differentiate the covariant
# metric tensor components
function calc_metric_derivatives(aux_node_vars, equations, dg, i, j, element)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest using more verbose names for these functions

Suggested change
# Use ForwardDiff.jl to automatically differentiate the covariant metric tensor components
function calc_metric_derivatives(v1, v2, v3, v4, xi1, xi2, radius, equations)
dGdxi1 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, x, xi2, radius,
equations), xi1)
dGdxi2 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, xi1, x, radius,
equations), xi2)
return dGdxi1, dGdxi2
end
# Use the collocation derivative operator to numerically differentiate the covariant
# metric tensor components
function calc_metric_derivatives(aux_node_vars, equations, dg, i, j, element)
# Use ForwardDiff.jl to automatically differentiate the covariant metric tensor components
function calc_metric_derivatives_ad(v1, v2, v3, v4, xi1, xi2, radius, equations)
dGdxi1 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, x, xi2, radius,
equations), xi1)
dGdxi2 = derivative(x -> calc_metric_covariant(v1, v2, v3, v4, xi1, x, radius,
equations), xi2)
return dGdxi1, dGdxi2
end
# Use the collocation derivative operator to numerically differentiate the covariant
# metric tensor components
function calc_metric_derivatives_collocation(aux_node_vars, equations, dg, i, j, element)

Comment on lines +460 to +463
dGdxi1, dGdxi2 = calc_metric_derivatives(v1, v2, v3, v4,
dg.basis.nodes[i], dg.basis.nodes[j],
radius, equations)
# Compute Christoffel symbols of the first kind
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above

Suggested change
dGdxi1, dGdxi2 = calc_metric_derivatives(v1, v2, v3, v4,
dg.basis.nodes[i], dg.basis.nodes[j],
radius, equations)
# Compute Christoffel symbols of the first kind
dGdxi1, dGdxi2 = calc_metric_derivatives_ad(v1, v2, v3, v4,
dg.basis.nodes[i], dg.basis.nodes[j],
radius, equations)
# Compute Christoffel symbols of the first kind

Comment on lines +466 to +467
aux_node_vars[21:26, i, j, element] = calc_christoffel_symbols(dGdxi1, dGdxi2,
Gcon)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
aux_node_vars[21:26, i, j, element] = calc_christoffel_symbols(dGdxi1, dGdxi2,
Gcon)
aux_node_vars[21:26, i, j, element] = calc_christoffel_symbols_collocation(dGdxi1,
dGdxi2,
Gcon)

Copy link
Collaborator

@amrueda amrueda left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Following the discussion in #78 (comment), I add these suggestions to improve the readability of the elixirs.

@@ -39,6 +39,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e

# A semidiscretization collects data structures and functions for the spatial discretization
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metric_terms = MetricTermsCovariantSphere(),

@@ -33,6 +33,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e

# A semidiscretization collects data structures and functions for the spatial discretization
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metric_terms = MetricTermsCovariantSphere(),

@@ -43,6 +43,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e
# specify the bottom topography.
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
source_terms = source_terms_geometric_coriolis,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metric_terms = MetricTermsCovariantSphere(),

Comment on lines 40 to 43
# A semidiscretization collects data structures and functions for the spatial discretization
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
source_terms = source_terms_geometric_coriolis)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# A semidiscretization collects data structures and functions for the spatial discretization
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
source_terms = source_terms_geometric_coriolis)
# A semidiscretization collects data structures and functions for the spatial discretization.
# Even though `metric_terms = MetricTermsCovariantSphere()` is default, we pass it here
# explicitly, such that `metric_terms` can be adjusted from the `trixi_include()` call in the tests
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
source_terms = source_terms_geometric_coriolis)

@@ -41,6 +41,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e
# discretization. Here, we pass in the additional keyword argument "auxiliary_field" to
# specify the bottom topography.
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metric_terms = MetricTermsCovariantSphere(),

@@ -44,6 +44,7 @@ initial_condition_transformed = transform_initial_condition(initial_condition, e
# specify the bottom topography, which is the same as for the standard isolated mountain
# case.
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere(),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
metric_terms = MetricTermsCovariantSphere(),

Comment on lines +32 to +33
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere())
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver)

Comment on lines +31 to +32
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere())
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver,
metric_terms = MetricTermsCovariantSphere())
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_transformed, solver)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants