Skip to content
Open
Show file tree
Hide file tree
Changes from 7 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)
46 changes: 46 additions & 0 deletions include/aspect/gravity_model/radial.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,52 @@ namespace aspect
double magnitude_at_bottom;

};



template <int dim>
Copy link
Member

Choose a reason for hiding this comment

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

As I explain in #6681 we usually associate one class/plugin with one file. The radial gravity models were an exception from the earliest days of ASPECT's life. Can you introduce your gravity profile in a new file instead? For reasons I will explain below I would name the new file (and class and everything) RadialWithTidalPotential and radial_with_tidal_potential.h/cc respectively.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will make it into a different plugin file then!

Copy link
Member

Choose a reason for hiding this comment

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

Please add doxygen documentation above the class name about what this class does. You can use the text you wrote at the bottom of the source file, but bring it into the doxygen format (/** ... */). This will be used in the online documentation (here the example for RadialLinear: https://aspect.geodynamics.org/doc/doxygen/classaspect_1_1GravityModel_1_1RadialLinear.html.

class RadialLinearWithTidalPotential : public RadialLinear<dim>
Copy link
Member

Choose a reason for hiding this comment

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

While it works to derive a plugin from another plugin if you want to use the behavior of the parent class in the child class, I have noted over the years that it is rarely a good idea in ASPECT. It creates complicated class structures, and sometimes it straight out prevents making full use of the parent class (in particular if the parent has protected member variables or is itself derived from another class like here). I would recommend the following approach instead:

Suggested change
class RadialLinearWithTidalPotential : public RadialLinear<dim>
class RadialLinearWithTidalPotential : public Interface<dim>, public SimulatorAccess<dim>

Then give this class a private member variable of type RadialLinear<dim> as a "base_gravity_model" and use its functionality by calling functions of this member variable. You can see how this works in the class initial_temperature/patch_on_S40RTS.h and its member variable S40RTSPerturbation<dim> s40rts;. Note in particular the lines 166-168 in initial_temperature/patch_on_S40RTS.cc where your class would initialize the member variable and let it read its own input parameters and line 76 where it uses its function get_Vs.

For now this is just a way to achieve the same functionality as your current approach (just with a bit more code, but easier to understand concept). But in the future this allows to expand the plugin to work with more than just the radial linear gravity model. For example the material model prescribed_viscosity can use an arbitrary material model as base model, and then modify it. In the same way your plugin could use any of the radial gravity profiles and apply a tidal potential on top.

{
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:
/**
* Mass of hosting planet
Copy link
Member

Choose a reason for hiding this comment

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

what is a "hosting" planet? Do you mean the mass of the planet/star that generates the tidal potential? That would be worth spelling out.

*/
double M_p;

/**
* Semimajor axis of satellite's orbit
Copy link
Member

Choose a reason for hiding this comment

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

I ask this below already, but by "satellite" you mean the object ASPECT is modelling? Please spell that out.

*/
double a_s;

/**
* Rate of 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.

Please use SI units wherever possible. You can still allow m/yr as input parameter if the global parameter Use years instead of seconds is set, and then convert into m/s when reading and storing the parameter.

And why m as length unit? Wouldnt it be much easier to ask the user to compute the angular velocity themselves beforehand and use that as input? Then you dont need to input the radius of the satellite either, or do you need that for anything else?

*/
double b_NSR;

/**
* Radius of modelling satellite
Copy link
Member

Choose a reason for hiding this comment

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

By satellite you mean the moon/planet this gravity is computed for? This would be worth spelling out, since it may not be obvious for geoscientists focused on Earth.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Honestly, I haven't thought about the planet's perspective. My idea was about effect of gravitation by planet on satellite, because I wanted to implement gravity variation on Europa. That's why I used host planet, which affects the gravity of satellite.
Would it be better to differentiate as modeling object and gravitationally interacting object?
I also think I should add few more comments on parameters for cases other than icy moons.

*/
double R1;
};
}
}

Expand Down
170 changes: 170 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 Expand Up @@ -178,6 +183,159 @@ namespace aspect
"do not have either a spherical or ellipsoidal natural coordinate system."));

}



// ----------------------------- RadialLinearWithTidalPotential ----------------------


template <int dim>
Tensor<1,dim>
RadialLinearWithTidalPotential<dim>::gravity_vector (const Point<dim> &p) const
{
/**
* This gravity model is a plugin for a case where the
* tidal potential by flattening and non-synchronnous rotation changes gravity with position and time.
*
* The equation implemented in this heating 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)).
* 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
Copy link
Member

Choose a reason for hiding this comment

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

This part of the explanation needs to go to the header file where I ask for documentation about this class. It is a high level summary of what this plugin is for.

The part below my comment (about conversion to Cartesian coordinates) can stay here, it is only relevent for the implementation, but not for understanding what the purpose of this plugin is.

*
* 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);

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];

const double C1 = std::cos( 2. * b_NSR / R1 / year_in_seconds * t);
const double C2 = std::sin( 2. * b_NSR / R1 / year_in_seconds * 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;

const double G = aspect::constants::big_g;
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);

return RadialLinear<dim>::gravity_vector(p) + tidal_gravity;
}


template <int dim>
void
RadialLinearWithTidalPotential<dim>::declare_parameters (ParameterHandler &prm)
{
RadialLinear<dim>::declare_parameters (prm);

prm.enter_subsection("Gravity model");
{
prm.enter_subsection("Radial linear with tidal potnetial");
{
prm.declare_entry ("Mass of planet", "1.898e27",
Patterns::Double (),
"Mass of satellte's host planet. "
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
"Mass of satellte's host planet. "
"Mass of satellite's host planet. "

"Default value is for Europa, therefore, mass of Jupiter. "
"Units is $kg$.");
prm.declare_entry ("Semimajor axis of satellite", "670900000",
Patterns::Double (),
"Length of semimajor axis of satellite. "
Copy link
Member

Choose a reason for hiding this comment

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

Better to be more verbose:

Suggested change
"Length of semimajor axis of satellite. "
"Length of the semimajor axis of the orbit of the modeled planet. "

"Default value is for Europa's semimajor axis"
"Units is $m$.");
prm.declare_entry ("Rate of nonsynchronous rotation", "1000",
Patterns::Double (),
"Rate of nonsynchronous rotation (NSR). "
"This will be converted to angular rate. "
"Units is $m/year$");
}
prm.leave_subsection ();
}
prm.leave_subsection ();
}


template <int dim>
void
RadialLinearWithTidalPotential<dim>::parse_parameters (ParameterHandler &prm)
{
AssertThrow (dim==3, ExcMessage ("The 'radial linear 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.

update name

"can only be used in 3D."));
this->RadialLinear<dim>::parse_parameters (prm);
Copy link
Member

Choose a reason for hiding this comment

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

this line will change with my comments about using a member object of type RadialLinear.


prm.enter_subsection("Gravity model");
{
prm.enter_subsection("Radial linear with tidal potnetial");
Copy link
Member

Choose a reason for hiding this comment

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

typo in the section name. Also just use "Radial with tidal potential". That allows extending the plugin later.

{
M_p = prm.get_double ("Mass of planet");
a_s = prm.get_double ("Semimajor axis of satellite");
Copy link
Member

Choose a reason for hiding this comment

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

The current spelling is easy to confuse, if you model an elliptical body on an elliptical orbit.

Suggested change
a_s = prm.get_double ("Semimajor axis of satellite");
a_s = prm.get_double ("Semimajor axis of orbit");

b_NSR = prm.get_double ("Rate of nonsynchronous rotation");
Copy link
Member

Choose a reason for hiding this comment

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

here you can do the following to allow for different time units in the input file, but still use SI units in the code:

Suggested change
b_NSR = prm.get_double ("Rate of nonsynchronous rotation");
const double time_scale = this->get_parameters().convert_to_years ? constants::year_in_seconds : 1.0;
b_NSR = prm.get_double ("Rate of nonsynchronous rotation") / time_scale;

}
prm.leave_subsection ();
}
prm.leave_subsection ();
// 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 "
"is a sphere, a spherical shell, a chunk or an "
"ellipsoidal chunk."));
R1 = numbers::signaling_nan<double>();
}
}
}
}

Expand Down Expand Up @@ -207,5 +365,17 @@ namespace aspect
"is positive) and the magnitude changes linearly with depth. The "
"magnitude of gravity at the surface and bottom is read from the "
"input file in a section ``Gravity model/Radial linear''.")

ASPECT_REGISTER_GRAVITY_MODEL(RadialLinearWithTidalPotential,
"radial linear with tidal potential",
"A gravity model that is the sum of the `radial linear' model "
"(which is radial, pointing inward if the gravity "
"is positive, and a magnitude that changes linearly with depth), "
"and a term that results from a tidal gravity potential and that "
"leads to a gravity field that varies with latitude and longitude."
"The magnitude of gravity for the linear radial part is read from the "
"input file in a section `Gravity model/Radial linear'; the "
"parameters that describe the tidal potential contribution are read "
"from a section ``Gravity model/Radial linear with tidal potential''.")
}
}
100 changes: 100 additions & 0 deletions tests/gravity_tidal_potential.prm
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# This parameter file tests the gravity model plugin for a case where the
# tidal potential by flattening and non-synchronnous rotation changes gravity with position and time.
#
# The equation implemented in this heating 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 ).
# potential = 3 G M_p R_s^2 / 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, R_s: radius of satellite, 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.
#
# Model shows the Europa's icy shell without conduction in simpler model.
# Visualization 'gravity' shows gravity distribution.

set Dimension = 3
set Use years instead of seconds = true
set End time = 1e4

set Output directory = gravity_tidal_potential

set Maximum first time step = 1e3
set CFL number = 0.8
set Maximum time step = 1e3


set Pressure normalization = surface
set Surface pressure = 0


subsection Geometry model
set Model name = spherical shell
subsection Spherical shell
set Outer radius = 1560800
set Inner radius = 1460800
set Opening angle = 360
end
end


subsection Initial temperature model
set Model name = function
subsection Function
set Coordinate system = spherical
set Variable names = r, phi,theta
set Function expression = 100
end
end


subsection Boundary velocity model
set Zero velocity boundary indicators = top, bottom
end


subsection Gravity model
set Model name = radial linear with tidal potential
subsection Radial linear
set Magnitude at surface = 1.3
set Magnitude at bottom = 1.3
end
subsection Radial linear with tidal potnetial
end
end


subsection Material model
set Model name = simpler
subsection Simpler model
set Reference density = 917
set Reference specific heat = 2110
set Reference temperature = 100
set Thermal conductivity = 0 #1.93
set Thermal expansion coefficient = 0 #1.6e-4
set Viscosity = 1e20
end
end


subsection Formulation
set Formulation = Boussinesq approximation
end


subsection Mesh refinement
set Initial global refinement = 0
set Initial adaptive refinement = 0
set Time steps between mesh refinement = 0
end


subsection Postprocess
set List of postprocessors = velocity statistics, temperature statistics, visualization, basic statistics, \
pressure statistics, material statistics

subsection Visualization
set Time between graphical output = 1e3
set Output format = vtu
set List of output variables = material properties, strain rate, shear stress, stress, nonadiabatic pressure, gravity
end
end
Loading