Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/modules/changes/20250716_hyunseong96
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added: ASPECT now has a new gravity model plugin called 'Radial linear with tidal potnetial' for tidal forces.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Added: ASPECT now has a new gravity model plugin called 'Radial linear with tidal potnetial' for tidal forces.
Added: ASPECT now has a new gravity model plugin called 'Radial with tidal potential' for tidal forces.

This plugin is useful for modeling long-term interior and surface evolution in moons orbiting a large planet.
<br>
(Hyunseong Kim, Antoniette Greta Grima, Wolfgang Bangerth 2025/07/16)
2 changes: 2 additions & 0 deletions include/aspect/gravity_model/radial.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ namespace aspect
* Magnitude of the gravity vector.
*/
double magnitude;

template <int dim2> friend class RadialWithTidalPotential;
Copy link
Member

Choose a reason for hiding this comment

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

Remove this

};


Expand Down
96 changes: 96 additions & 0 deletions include/aspect/gravity_model/radial_with_tidal_potential.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
Copyright (C) 2014 - 2019 by the authors of the ASPECT code.
This file is part of ASPECT.
ASPECT is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
ASPECT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ASPECT; see the file LICENSE. If not see
<http://www.gnu.org/licenses/>.
*/


#ifndef _aspect_gravity_model_radial_with_tidal_potential_h
#define _aspect_gravity_model_radial_with_tidal_potential_h

#include <aspect/simulator_access.h>
#include <aspect/gravity_model/interface.h>
#include <aspect/gravity_model/radial.h>

namespace aspect
{
namespace GravityModel
{
/**
* A class that describes gravity as a radial vector of linearly
* changing magnitude by tidal potential from flattening and non-synchronnous rotation with respect to position and time.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* changing magnitude by tidal potential from flattening and non-synchronnous rotation with respect to position and time.
* changing magnitude, which is modified by a tidal potential from flattening and non-synchronous rotation.

*
* The equation implemented in this gravity model is from Tobie et al. (2025) (https://doi.org/10.1007/s11214-025-01136-y),
* which is defined as:
* g = -magnitude - gradient (-(tidal potential)).
* Tidal potential is positive because it is from geodesy research, where potential is taken as positive.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* Tidal potential is positive because it is from geodesy research, where potential is taken as positive.
* Tidal potential is positive because the formula follows conventions from geodesy research, where potential is taken as positive.

* (tidal potential) = (3 G M_p) / (2 a_s^3) * r^2 * (Tstar + T0)
* Tstar = 1/6 *(1-3*cos(theta)^2) and T0=1/2sin(theta)^2*cos(2*lambda + 2*b*t)
* where G = gravitational constant, M_p = mass of planet, a_s = semimajor axis of satellite's orbit, b = angular rate of nonsynchronous rotation.
* r, theta and lambda are radial distance, polar angle and azimuthal angle, respectively.
* b [1/s] = b_NSR [m/yr] / (circumference of satellite [m]) / year_to_seconds [s/yr] * 2 * pi
*
* @ingroup GravityModels
*/
template <int dim>
class RadialWithTidalPotential : public Interface<dim>, public SimulatorAccess<dim>
{
public:
/**
* Return the gravity vector as a function of position.
*/
Tensor<1,dim> gravity_vector (const Point<dim> &position) const override;

/**
* Declare the parameters this class takes through input files.
*/
static
void
declare_parameters (ParameterHandler &prm);

/**
* Read the parameters this class declares from the parameter file.
*/
void
parse_parameters (ParameterHandler &prm) override;

private:
/**
* Declare a member variable of type RadialConstant that allows us to call
* functions from radial_constant.cc.
*/
RadialConstant<dim> radialconstant;

/**
* Mass of the perturbing body
*/
double M_p;

/**
* Semimajor axis of the modeled body
Copy link
Member

Choose a reason for hiding this comment

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

Semimajor axis of the orbit or of the shape of the modeled body? Please add.

Copy link
Contributor Author

@hyunseong96 hyunseong96 Oct 6, 2025

Choose a reason for hiding this comment

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

I think this parameter explanation seems quite tricky. For me, I make model of Europa, so I use semimajor axis of Europa's orbit (modeled body). However, for Earth, if moon is the perturbing body, semimajor axis of moon should be used. If sun is the perturbing body, semimajor of Earth should be used.
My idea is to leave as 'Semimajor axis of the orbit' on header file and to make more explanation on declare_entry on plugin file. What do you think?

Copy link
Member

Choose a reason for hiding this comment

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

What do you think about: Semimajor axis of the orbit that causes the tidal perturbation? More explanation in the parameter description is of course also welcome.

*/
double a_s;

/**
* Angular rate of the non-synchronous rotation in m/year
Copy link
Member

Choose a reason for hiding this comment

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

Is this comment still up to date? If it is an angular rate, why does it have the unit of m/year? Shouldnt it be 1/s?

*/
double b_NSR;
};
}
}

#endif
5 changes: 5 additions & 0 deletions source/gravity_model/radial.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@

#include <deal.II/base/tensor.h>

#include <aspect/geometry_model/spherical_shell.h>
#include <aspect/geometry_model/sphere.h>
#include <aspect/geometry_model/chunk.h>
#include <aspect/geometry_model/ellipsoidal_chunk.h>

Copy link
Member

Choose a reason for hiding this comment

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

These includes are no longer necessary in this file, correct? I do not see any geometry models being referenced in the file.

Copy link
Contributor Author

@hyunseong96 hyunseong96 Oct 6, 2025

Choose a reason for hiding this comment

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

I think you are right. My initial thought was my plugin should return error when spherical geometry is not used, but Radial constant would already do this. I will change it!

namespace aspect
{
namespace GravityModel
Expand Down
199 changes: 199 additions & 0 deletions source/gravity_model/radial_with_tidal_potential.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
Copyright (C) 2011 - 2020 by the authors of the ASPECT code.
This file is part of ASPECT.
ASPECT is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
ASPECT is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with ASPECT; see the file LICENSE. If not see
<http://www.gnu.org/licenses/>.
*/


#include <aspect/gravity_model/radial_with_tidal_potential.h>
#include <aspect/geometry_model/interface.h>
#include <aspect/utilities.h>

#include <deal.II/base/tensor.h>

#include <aspect/gravity_model/radial.h>
#include <aspect/geometry_model/spherical_shell.h>
#include <aspect/geometry_model/sphere.h>
#include <aspect/geometry_model/chunk.h>
#include <aspect/geometry_model/ellipsoidal_chunk.h>

namespace aspect
{
namespace GravityModel
{
template <int dim>
Tensor<1,dim>
RadialWithTidalPotential<dim>::gravity_vector (const Point<dim> &p) const
{
Comment on lines 34 to 37
Copy link
Member

Choose a reason for hiding this comment

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

Since you want this to only work in 3D models, but ASPECT is compiled for 2D and 3D models, you will (or maybe already have) run into trouble with using types like Tensor<1,dim>. When ASPECT is compiled for dim==2 the compiler will notice you try to access elements that dont exist in 2D and likely complain. Do the following to work around this (this is taken from here):

Suggested change
template <int dim>
Tensor<1,dim>
RadialWithTidalPotential<dim>::gravity_vector (const Point<dim> &p) const
{
template <int dim>
Tensor<1,dim>
RadialWithTidalPotential<dim>::gravity_vector (const Point<dim> &p) const
{
// This plugin is not implemented for 2D models
AssertThrow(false, ExcNotImplemented());
return Tensor<1,dim>();
}
template <>
Tensor<1,3>
RadialWithTidalPotential<dim>::gravity_vector (const Point<3> &p) const
{
const unsigned int dim = 3;

This construct is called a template specialization (more information here: https://www.learncpp.com/cpp-tutorial/function-template-specialization/), and it provides different functions for your class for dim==2 (which will crash) and dim==3 (which will work as you expect it to).

Copy link
Contributor Author

@hyunseong96 hyunseong96 Oct 7, 2025

Choose a reason for hiding this comment

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

Thank you! but I had to change a bit to make this code work. I hope the code below is the correct modification.

template <>
Tensor<1,3>
RadialWithTidalPotential<3>::gravity_vector (const Point<3> &p) const
{

I will use this later for Tensor<1,2> when Antoniette and I decide how to implement 2D case.
So far, we had discussion how to implement this plugin in 2D but we will make it as different PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @gassmoeller , I have a bit of trouble for upper part of suggestion, which is template

Tensor<1,dim>
RadialWithTidalPotential<dim>::gravity_vector (const Point<dim> &p) const
{
// This plugin is not implemented for 2D models
AssertThrow(false, ExcNotImplemented());
return Tensor<1,dim>();
}

While in make, I see the warning that warning:

unused parameter 'p' [-Wunused-parameter]

Because tests considers warnings as errors, I fail tests. Is there a simple but reasonable way I can use Point 'p'? or the way to avoid this warning?

Copy link
Member

Choose a reason for hiding this comment

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

Yes, our usual way to avoid this warning is to comment the name (but not the type) of the parameter argument. This tells the compiler that this parameter exists (for some reasons or other), but that we are intentionally not using it in the function:

RadialWithTidalPotential<dim>::gravity_vector (const Point<dim> &/*p*/) const

/**
* Notation of this potential equation is converted from spherical coordinates to cartesian coordinates.
* Therefore, gradient of potential is (3 G M_p) / (2 a_s^3) * ( 1 / 6 * ( x^2 + y^2 - 2 * z^2) + 1 / 2 * (C1*(x^2 + y^2) - 2 * C2 * x * y)))
* where C1 = cos(2*b*t) and C2 = sin(2*b*t)
*/
const Tensor<1,dim> e_x = Point<dim>::unit_vector(0);
const Tensor<1,dim> e_y = Point<dim>::unit_vector(1);
const Tensor<1,dim> e_z = Point<dim>::unit_vector(2);
Copy link
Member

Choose a reason for hiding this comment

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

I dont think you need these, I will explain below why not.


const double t = (this->simulator_is_past_initialization()) ? this->get_time() : 0.0;
const double x = p[0];
const double y = p[1];
const double z = p[2];
Copy link
Member

Choose a reason for hiding this comment

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

What should happen if someone uses this gravity model for a 2D model? Then there is no p[2]? Is there a version of this formula that makes sense in 2D as well? Or do you want the model to crash in 2D and not allow this case?

Copy link
Member

Choose a reason for hiding this comment

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

This renaming doesnt really add much, x is not that much shorter than p[0]. I would just use p in the formulas below.


const double C1 = std::cos( 2. * b_NSR * t);
const double C2 = std::sin( 2. * b_NSR * t);

const double dTstar_over_dx = 1. / 6. * ( 2. * x );
const double dT0_over_dx = 1. / 2. * ( 2. * C1 * x - 2. * C2 * y );

const double dTstar_over_dy = 1. / 6. * ( 2. * y );
const double dT0_over_dy = 1. / 2. * ( -2. * C1 * y - 2. * C2 * x );

const double dTstar_over_dz = 1. / 6. * ( -4. * z );
const double dT0_over_dz = 0;
Copy link
Member

Choose a reason for hiding this comment

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

I would write this part the following way (assuming we stick to purely 3D models):

const Tensor<1,dim> dTstar_gradient (1./3. * p[0],
1./3. * p[1],
-2./3. * p[2]);

const Tensor<1,dim> dT0_gradient (C1*p[0] - C2*p[1],
-C1*p[1] - C2*p[0],
0);

Then you can significantly simplify the formula below.


const double G = aspect::constants::big_g;
const double T_factor = 3. * G * M_p / 2. / a_s / a_s / a_s;
Copy link
Member

Choose a reason for hiding this comment

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

Just a notational difference, but I find this easier to read:

Suggested change
const double T_factor = 3. * G * M_p / 2. / a_s / a_s / a_s;
const double T_factor = 3. * G * M_p / (2. * a_s * a_s * a_s);


const Tensor<1,dim> tidal_gravity = T_factor *
( (dTstar_over_dx + dT0_over_dx) * e_x
+ (dTstar_over_dy + dT0_over_dy) * e_y
+ (dTstar_over_dz + dT0_over_dz) * e_z);
Copy link
Member

Choose a reason for hiding this comment

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

If you simplify above as I suggest, this simply becomes:

Suggested change
const Tensor<1,dim> tidal_gravity = T_factor *
( (dTstar_over_dx + dT0_over_dx) * e_x
+ (dTstar_over_dy + dT0_over_dy) * e_y
+ (dTstar_over_dz + dT0_over_dz) * e_z);
const Tensor<1,dim> tidal_gravity = T_factor *
(dTstar_gradient + dT0_gradient);


if (p.norm() == 0.0)
return Tensor<1,dim>();

const double r = p.norm();
return -radialconstant.magnitude * p/r + tidal_gravity;
Copy link
Member

Choose a reason for hiding this comment

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

Ah, here is where you access the private member variable magnitude of the object radialconstant which is of the class type RadialConstant<dim>. The way to work around that is to call the public member function gravity_vector like so:

return radialconstant.gravity_vector(p) + tidal_gravity;

Then you do no longer access the private member variable magnitude, and instead call the public member function gravity_vector.

}


template <int dim>
void
RadialWithTidalPotential<dim>::declare_parameters (ParameterHandler &prm)
{
prm.enter_subsection("Gravity model");
{
prm.enter_subsection("Radial with tidal potential");
{
prm.declare_entry ("Mass of perterbing body", "1.898e27",
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
prm.declare_entry ("Mass of perterbing body", "1.898e27",
prm.declare_entry ("Mass of perturbing body", "1.898e27",

Patterns::Double (),
"Mass of body that perturbs gravity of modeled body. "
"Default value is for modeling Europa, therefore, mass of Jupiter. "
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Default value is for modeling Europa, therefore, mass of Jupiter. "
"The default value is chosen for modeling Europa, therefore, it is the mass of Jupiter. "

"Units is $kg$.");
prm.declare_entry ("Semimajor axis of orbit", "670900000",
Patterns::Double (),
"Length of semimajor axis of orbit between modeled body and perturbing body. "
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"Length of semimajor axis of orbit between modeled body and perturbing body. "
"The length of the semimajor axis of the orbit between the modeled body and the perturbing body. "

"Default value is for Europa's semimajor axis"
"Units is $m$.");
prm.declare_entry ("Angular rate of nonsynchronous rotation", "1000",
Patterns::Double (),
"Angular rate of nonsynchronous rotation (NSR). "
"This works for the modeled body having decoupled rotation between interior layers. "
Copy link
Member

Choose a reason for hiding this comment

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

This sentence needs some explanation. Why is this relevant? And what is this? I.e. does this whole plugin only work for cases like Europa with a swimming ice shell, but e.g. not Io with a rocky shell? Then this should be documented in the plugin documentation at the very end of the file, not in this parameter.

"Default value is for Europa's icy shell. "
"Units is $degrees/year$");
Copy link
Member

Choose a reason for hiding this comment

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

Please see my comment here: #6614 (comment)

We want this input parameter to be given in 'degrees/year' if the input parameter 'Use years instead of seconds' is set to 'true', and 'degrees/s' if not.

}
prm.leave_subsection ();
}
prm.leave_subsection ();
}
Copy link
Member

Choose a reason for hiding this comment

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

Before you end this function add the following:

RadialLinear<dim>::declare_parameters(prm);

This will let the RadialLinear class also declare its parameters. Technically, this is not necessary, because of course that other class is used as its own plugin, so ASPECT will call its declare_parameters function anyway. But calling this function multiple times is not wrong, and adding this line will make sure these parameters will always be declared, even if we decide in the future to remove that other class as its own plugin (and for example move it into the current class).



template <int dim>
void
RadialWithTidalPotential<dim>::parse_parameters (ParameterHandler &prm)
{
AssertThrow (dim==3, ExcMessage ("The 'radial with tidal potential' gravity model "
Copy link
Member

Choose a reason for hiding this comment

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

Ah, so you only allow this for 3D models. Please add this to the documentation at the bottom of this file. I will leave a comment above for how to handle this in the gravity_vector function.

"can only be used in 3D."));

prm.enter_subsection("Gravity model");
{
prm.enter_subsection("Radial with tidal potential");
{
M_p = prm.get_double ("Mass of perturbing body");
a_s = prm.get_double ("Semimajor axis of orbit");
const double time_scale = this->get_parameters().convert_to_years ? constants::year_in_seconds : 1.0;
Copy link
Member

Choose a reason for hiding this comment

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

Ah, so you do the conversion already. Then disregard my earlier comment, and just fix the documentation of this parameter to say that it is given in degrees/year or degrees/second depending on the input parameter 'Use years instead of seconds'.

b_NSR = prm.get_double ("Angular rate of nonsynchronous rotation") * constants::degree_to_radians / time_scale;
Copy link
Member

Choose a reason for hiding this comment

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

but according to your formulas, shouldnt this parameter now be b and not b_NSR?

}
prm.leave_subsection ();
}
prm.leave_subsection ();

radialconstant.parse_parameters(prm);

// This effect of tidal potential only works if the geometry is derived from
// a spherical model (i.e. a sphere, spherical shell or chunk)
if (Plugins::plugin_type_matches<const GeometryModel::Sphere<dim>>(this->get_geometry_model()))
{
R1 = Plugins::get_plugin_as_type<const GeometryModel::Sphere<dim>>
(this->get_geometry_model()).radius();
}
else if (Plugins::plugin_type_matches<const GeometryModel::SphericalShell<dim>>(this->get_geometry_model()))
{
R1 = Plugins::get_plugin_as_type<const GeometryModel::SphericalShell<dim>>
(this->get_geometry_model()).outer_radius();
}
else if (Plugins::plugin_type_matches<const GeometryModel::Chunk<dim>>(this->get_geometry_model()))
{
R1 = Plugins::get_plugin_as_type<const GeometryModel::Chunk<dim>>
(this->get_geometry_model()).outer_radius();
}
else if (Plugins::plugin_type_matches<const GeometryModel::EllipsoidalChunk<dim>>(this->get_geometry_model()))
{
const auto &gm = Plugins::get_plugin_as_type<const GeometryModel::EllipsoidalChunk<dim>>
(this->get_geometry_model());
// TODO
// If the eccentricity of the EllipsoidalChunk is non-zero, the radius can vary along a boundary,
// but the maximal depth is the same everywhere and we could calculate a representative pressure
// profile. However, it requires some extra logic with ellipsoidal
// coordinates, so for now we only allow eccentricity zero.
// Using the EllipsoidalChunk with eccentricity zero can still be useful,
// because the domain can be non-coordinate parallel.

AssertThrow(gm.get_eccentricity() == 0.0,
ExcNotImplemented("This plugin cannot be used with a non-zero eccentricity. "));

R1 = gm.get_semi_major_axis_a();
}
else
{
Assert (false, ExcMessage ("This initial condition can only be used if the geometry "
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
Assert (false, ExcMessage ("This initial condition can only be used if the geometry "
Assert (false, ExcMessage ("This gravity model can only be used if the geometry "

"is a sphere, a spherical shell, a chunk or an "
"ellipsoidal chunk."));
R1 = numbers::signaling_nan<double>();
}
}
}
}

// explicit instantiations
namespace aspect
{
namespace GravityModel
{
ASPECT_REGISTER_GRAVITY_MODEL(RadialWithTidalPotential,
"radial with tidal potential",
"A gravity model that is the sum of the `radial constant' model "
Copy link
Member

Choose a reason for hiding this comment

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

Document at the end of this text that this plugin only works in 3D (I had commented on that here).

"(which is radial, pointing inward if the gravity "
"is positive), "
"and a term that results from a tidal potential and that "
"leads to a gravity field that varies with latitude and longitude."
"The magnitude of gravity for the radial constant part is read from the "
"input file in a section `Gravity model/Radial constant'; the "
"parameters that describe the tidal potential contribution are read "
"from a section ``Gravity model/Radial with tidal potential''.")
}
}
Loading
Loading