Planetary gaps

This notebook discusses ways to impose gaps on the surface density profiles. Since DustPy is one-dimensional it is difficult to self-consistently model gaps carved by planets into the protoplanetary disk. One way of doing this, would be to set the gas surface density profile to analytical gap profile taken from multi-dimensional hydrodynamical simulation. However, in this case the gas would be static and non-evolving.

Another way would be to utilize the fact that the product of viscosity and gas surface density is constant in steady state.

\(\nu \Sigma_\mathrm{gas} = \mathrm{const.}\)

Imposing the inverse gap profile onto the viscosity would therefore create the desired gap profile on the gas surface density. Since the viscosity is proportial to the \(\alpha\)-parameter, we therefore impose the inverse gap profile onto \(\alpha\).

This notebook adds gaps carved by the Solar System giant planet to DustPy simulations with different gap shapes. We therefore create a dictionary with the masses of the planets in g and their semi-major axis in cm.

[1]:
import dustpy.constants as c
[2]:
planets = {
    "jupiter": {
        "a": 5.2038 * c.au,
        "M": 317.8 * c.M_earth,
    },
    "saturn": {
        "a": 9.5826 * c.au,
        "M": 95.159 * c.M_earth,
    },
    "uranus": {
        "a": 19.19126 * c.au,
        "M": 14.536 * c.M_earth,
    },
    "neptune": {
        "a": 30.07 * c.au,
        "M": 17.147 * c.M_earth,
    },
}

Kanagawa et al. (2017)

DustPyLib contains the analytical function of Kanagawa et al. (2017) fitted to gap profiles obtained from hydrodynamical simulations.

We first create a DustPy simulation object.

[3]:
from dustpy import Simulation
[4]:
s = Simulation()

Before we initialize the simulation object, we refine the radial grid around the planet locations.

[5]:
from dustpylib.grid.refinement import refine_radial_local
import numpy as np
[6]:
ri = np.geomspace(s.ini.grid.rmin, s.ini.grid.rmax, s.ini.grid.Nr)
for planet in planets.values():
    ri = refine_radial_local(ri, planet["a"], num=3)
[7]:
s.grid.ri = ri

Now we can initialize the simulation object with the new grid.

[8]:
s.initialize()

In the next step we add a group for the planets and add their masses as fields.

[9]:
s.addgroup("planets", description="Planets")
for name, planet in planets.items():
    s.planets.addgroup(name, description="Planet {}".format(name.title()))
    s.planets.__dict__[name].addfield("M", planet["M"], description="Mass in g")
    s.planets.__dict__[name].addfield("a", planet["a"], description="Semi-major axis in cm")
[10]:
s.planets
[10]:
Group (Planets)
---------------
    jupiter      : Group (Planet Jupiter)
    neptune      : Group (Planet Neptune)
    saturn       : Group (Planet Saturn)
    uranus       : Group (Planet Uranus)
  -----
[11]:
s.planets.jupiter
[11]:
Group (Planet Jupiter)
----------------------
    a            : Field (Semi-major axis in cm)
    M            : Field (Mass in g)
  -----

Since we are going to impose the inverse gap profile on \(\alpha\), we need to copy and store the unperturbed \(\alpha\)-profile for later usage.

[12]:
alpha0 = s.gas.alpha.copy()

In the next step we need to define an updater function for \(\alpha\), which imposes the inverse gap profile. For this we can use the kanagawa2017() function of DustPyLib.

[13]:
from dustpylib.substructures.gaps import kanagawa2017
from scipy.interpolate import interp1d
[14]:
def alpha(s):
    # Unperturbed profile
    alpha = alpha0.copy()
    # Iteration over all planets
    for name, planet in s.planets.__dict__.items():
        # Skip hidden fields:
        if name.startswith("_"):
            continue
        # Dimensionless planet mass
        q = planet.M/s.star.M
        # Interpolation of aspect ratio and alpha0 onto planet position
        f_h = interp1d(s.grid.r, s.gas.Hp/s.grid.r)
        h = f_h(planet.a)
        f_alp = interp1d(s.grid.r, alpha0)
        alp = f_alp(planet.a)
        # Inverse alpha-profile
        alpha /= kanagawa2017(s.grid.r, planet.a, q, h, alp)
    return alpha

This function can now be added as updater of the \(\alpha\) field.

[15]:
s.gas.alpha.updater = alpha

After updating the simulation object, \(\alpha\) should have the desired profile.

[16]:
s.update()
[17]:
import matplotlib.pyplot as plt
plt.rcParams["figure.dpi"] = 150.
[18]:
fig, ax = plt.subplots()
ax.loglog(s.grid.r/c.au, s.gas.alpha)
ax.set_xlim(s.grid.r[0]/c.au, s.grid.r[-1]/c.au)
ax.set_xlabel("Distance from star [au]")
ax.set_ylabel(r"$\alpha$")
fig.tight_layout()
_images/planetary_gaps_29_0.png

The simulation would now be ready to go by executing s.run() and the gap profile would be imposed on the gas surface density on viscous time scales.

But we could also impose this profile on the gas and dust surface densities from the beginning.

[19]:
s.gas.Sigma[...] /= s.gas.alpha/alpha0
s.dust.Sigma[...] /= (s.gas.alpha/alpha0)[:, None]
[20]:
fig, ax = plt.subplots()
ax.loglog(s.grid.r/c.au, s.gas.Sigma, label="Gas")
ax.loglog(s.grid.r/c.au, s.dust.Sigma.sum(-1), label="Dust")
ax.set_xlim(s.grid.r[0]/c.au, s.grid.r[-1]/c.au)
ax.set_ylim(1.e-5, 1.e4)
ax.set_xlabel("Distance from star [au]")
ax.set_ylabel("Surface density [g/cm²]")
ax.legend()
fig.tight_layout()
_images/planetary_gaps_32_0.png

Duffell (2020)

DustPyLib furthermore contains the empirically derived gap shapes of Duffell (2020).

This example analogous to the example above and implements the Solar System giants to a DustPy simulation.

[21]:
from dustpy import Simulation
[22]:
s = Simulation()

Before we initialize the simulation object, we refine the radial grid around the planet locations.

[23]:
from dustpylib.grid.refinement import refine_radial_local
import numpy as np
[24]:
ri = np.geomspace(s.ini.grid.rmin, s.ini.grid.rmax, s.ini.grid.Nr)
for planet in planets.values():
    ri = refine_radial_local(ri, planet["a"], num=3)
[25]:
s.grid.ri = ri

Now we can initialize the simulation object with the new grid.

[26]:
s.initialize()

In the next step we add a group for the planets and add their masses as fields.

[27]:
s.addgroup("planets", description="Planets")
for name, planet in planets.items():
    s.planets.addgroup(name, description="Planet {}".format(name.title()))
    s.planets.__dict__[name].addfield("M", planet["M"], description="Mass in g")
    s.planets.__dict__[name].addfield("a", planet["a"], description="Semi-major axis in cm")
[28]:
s.planets
[28]:
Group (Planets)
---------------
    jupiter      : Group (Planet Jupiter)
    neptune      : Group (Planet Neptune)
    saturn       : Group (Planet Saturn)
    uranus       : Group (Planet Uranus)
  -----
[29]:
s.planets.jupiter
[29]:
Group (Planet Jupiter)
----------------------
    a            : Field (Semi-major axis in cm)
    M            : Field (Mass in g)
  -----

Since we are going to impose the inverse gap profile on \(\alpha\), we need to copy and store the unperturbed \(\alpha\)-profile for later usage.

[30]:
alpha0 = s.gas.alpha.copy()

In the next step we need to define an updater function for \(\alpha\), which imposes the inverse gap profile. For this we can use the duffell2020() function of DustPyLib.

[31]:
from dustpylib.substructures.gaps import duffell2020
from scipy.interpolate import interp1d
[32]:
def alpha(s):
    # Unperturbed profile
    alpha = alpha0.copy()
    # Iteration over all planets
    for name, p in s.planets.__dict__.items():
        # Skip hidden fields:
        if name.startswith("_"):
            continue
        # Dimensionless planet mass
        q = p.M/s.star.M
        # Interpolation of aspect ratio and alpha0 onto planet position
        f_h = interp1d(s.grid.r, s.gas.Hp/s.grid.r)
        h = f_h(p.a)
        f_alp = interp1d(s.grid.r, alpha0)
        alp = f_alp(p.a)
        # Inverse alpha-profile
        alpha /= duffell2020(s.grid.r, p.a, q, h, alp)
    return alpha

This function can now be added as updater of the \(\alpha\) field.

[33]:
s.gas.alpha.updater = alpha

After updating the simulation object, \(\alpha\) should have the desired profile.

[34]:
s.update()
[35]:
import matplotlib.pyplot as plt
plt.rcParams["figure.dpi"] = 150.
[36]:
fig, ax = plt.subplots()
ax.loglog(s.grid.r/c.au, s.gas.alpha)
ax.set_xlim(s.grid.r[0]/c.au, s.grid.r[-1]/c.au)
ax.set_xlabel("Distance from star [au]")
ax.set_ylabel(r"$\alpha$")
fig.tight_layout()
_images/planetary_gaps_57_0.png

The simulation would now be ready to go by executing s.run() and the gap profile would be imposed on the gas surface density on viscous time scales.

But we could also impose this profile on the gas and dust surface densities from the beginning.

[37]:
s.gas.Sigma[...] /= s.gas.alpha/alpha0
s.dust.Sigma[...] /= (s.gas.alpha/alpha0)[:, None]
[38]:
fig, ax = plt.subplots()
ax.loglog(s.grid.r/c.au, s.gas.Sigma, label="Gas")
ax.loglog(s.grid.r/c.au, s.dust.Sigma.sum(-1), label="Dust")
ax.set_xlim(s.grid.r[0]/c.au, s.grid.r[-1]/c.au)
ax.set_ylim(1.e-5, 1.e4)
ax.set_xlabel("Distance from star [au]")
ax.set_ylabel("Surface density [g/cm²]")
ax.legend()
fig.tight_layout()
_images/planetary_gaps_60_0.png