Skip to content

Swampland Conjectures

Kay Lehnert edited this page Mar 31, 2026 · 26 revisions

To assess the swampland conjectures, we computed a few additional quantities and made those available through the Python wrapper classy. The swampland conjectures are explained in the thesis and in more detail in the Hitchhiker's Guide to the Swampland. Here, we focus on the technical implementation.

We added various new variables:

  • $\phi_{\max}$
  • $\phi_{\min}$
  • $\Delta\phi$
  • de Sitter Conjecture $s_1=\frac{\left|\nabla V\right|}{V}$ (minimal value reached)
  • de Sitter Conjecture $s_2=\frac{\text{min}\left(\Delta V\right)}{V}$ (maximal value reached)
  • $s_1$ @ maximum of $s_2$
  • $s_2$ @ minimum of $s_1$
  • a parameter to track the scalar weak gravity conjecture $\frac{1}{M_\textnormal{P}^2}\left(\frac{\mathrm{d}^2V}{\mathrm{d}\phi^2}\right)^2\leq2\left(\frac{\mathrm{d}^3V}{\mathrm{d}\phi^3}\right)^2-\frac{\mathrm{d}^2V}{\mathrm{d}\phi^2}\frac{\mathrm{d}^4V}{\mathrm{d}\phi^4}$
  • a parameter to track the strong scalar weak gravity conjecture $M^2m^2\partial_\phi^2\left(1/m^2\right)=2c^2\left(4\left(1+\tanh\left(c\phi\right)\right)-1/\cosh\left(c\phi\right)^2\right)\geq1$
  • a parameter to track the strong scalar Festina Lente bound $\frac{M_\textnormal{P}^2}{4}\left(\frac{3\left(V^\prime\right)^2}{V^2}-\frac{2V^{\prime\prime}}{V}\right)\geq1$
  • a lower bound on the dark matter mass $m_0$ by the anti–de Sitter conjecture with and without scale separation $m=\frac{m_0}{2}\left[1-\tanh\left(\mathfrak{c}\left(\phi-\phi_0\right)\right)\right]\lesssim\left|V\right|^{\mathfrak{d}} M_\textnormal{P}^{1-2\mathfrak{d}}$

that are implemented as follows:

background.h

    double phi_scf_min;                             /**< minimum \f$ \phi \f$ encountered over the background table */
    double phi_scf_max;                             /**< maximum \f$ \phi \f$ encountered over the background table */
    double phi_scf_range;                           /**< \f$ \phi_{max}-\phi_{min} \f$ over the background table */
    double dV_V_scf_min;                            /**< minimum \f$ |dV/d\phi|/V \f$ encountered over the background table (de Sitter Conjecture) */
    double ddV_V_scf_max;                           /**< maximum \f$ d^2V/d\phi^2/V \f$ encountered over the background table (second de Sitter Conjecture) */
    double ddV_V_at_dV_V_min;                       /**< \f$ d^2V/d\phi^2/V \f$ evaluated when \f$ |dV/d\phi|/V \f$ reaches its minimum */
    double dV_V_at_ddV_V_max;                       /**< \f$ |dV/d\phi|/V \f$ evaluated when \f$ d^2V/d\phi^2/V \f$ reaches its maximum */
    double swgc_expr_min;                           /**< minimum of \f$ 2(d^3V/d\phi^3)^2 - (d^2V/d\phi^2)(d^4V/d\phi^4) - (d^2V/d\phi^2)^2 \f$ (scalar weak gravity conjecture) */
    double sswgc_min;                               /**< minimum of the strong scalar weak gravity conjecture expression \f$ M_P^2 m^2 \partial_\phi^2(1/m^2) \f$ */
    double AdSDC2_max;                              /**< maximum of AdSDC2 boundary expression during evolution */
    double AdSDC4_max;                              /**< maximum of AdSDC4 boundary expression during evolution */
    double combined_dSC_min;                        /**< minimum of \f$ (3(dV_p)^2/V^2 - 2 d^2V/d\phi^2/V)/4 \f$ during evolution, a strong form of a combined de Sitter conjecture, stemming from the FLB and the SSWGC. */

cclassy.pxd

        double phi_scf_min
        double phi_scf_max
        double phi_scf_range
        double dV_V_scf_min
        double ddV_V_scf_max
        double ddV_V_at_dV_V_min
        double dV_V_at_ddV_V_max
        double swgc_expr_min
        double sswgc_min
        double AdSDC2_max
        double AdSDC4_max
        double combined_dSC_min

classy.pyx

            elif name == 'phi_scf_min':
                value = self.ba.phi_scf_min
            elif name == 'phi_scf_max':
                value = self.ba.phi_scf_max
            elif name == 'phi_scf_range':
                value = self.ba.phi_scf_range
            elif name == 'dV_V_scf_min':
                value = self.ba.dV_V_scf_min
            elif name == 'ddV_V_scf_max':
                value = self.ba.ddV_V_scf_max
            elif name == 'ddV_V_at_dV_V_min':
                value = self.ba.ddV_V_at_dV_V_min
            elif name == 'dV_V_at_ddV_V_max':
                value = self.ba.dV_V_at_ddV_V_max
            elif name == 'swgc_expr_min':
                value = self.ba.swgc_expr_min
            elif name == 'sswgc_min':
                value = self.ba.sswgc_min
            elif name == 'AdSDC2_max':
                value = self.ba.AdSDC2_max
            elif name == 'AdSDC4_max':
                value = self.ba.AdSDC4_max
            elif name == 'combined_dSC_min':
                value = self.ba.combined_dSC_min

The swampland constraints hold at all times. Therefore, we check for the minimum and maximum values of the various swampland parameters that are reached during the cosmological evolution. These extremal values are extracted at the very end, after the cosmological run, and extracted from the background table that stores the individual values at each time step. There are other quantities that are retrieved in this manner, e.g. the conformal age. To profit from synergies and allowing the compiler to unroll a single for-loop in a vectorised way using modern CPU instruction sets, we implemented the retrieval of the extremal swampland values as follows in background_solve() in background.c:

  if (pba->has_scf == _FALSE_)
  {
    pba->phi_scf_min = 0.0;
    pba->phi_scf_max = 0.0;
    pba->phi_scf_range = 0.0;
    pba->dV_V_scf_min = 0.0;
    pba->ddV_V_scf_max = 0.0;
    pba->ddV_V_at_dV_V_min = 0.0;
    pba->dV_V_at_ddV_V_max = 0.0;
    pba->swgc_expr_min = 0.0;
    pba->sswgc_min = 0.0;
    pba->AdSDC2_max = 0.0;
    pba->AdSDC4_max = 0.0;
    pba->combined_dSC_min = 0.0;
  }

. . .

  /** - In a single fused loop over lines, fill the rest of the
      background table for quantities that depend on "conformal_age"
      or "D_today" (computed just above), AND compute all scalar
      field swampland diagnostic quantities (min/max reductions).
      Fusing both passes into one loop improves cache locality
      since each table row is only loaded from memory once. */
  {
    /** Number of rows in the background table */
    int num_bg_rows = pba->bt_size;
    /** Number of columns per row (stride between consecutive rows) */
    int cols_per_row = pba->bg_size;
    /** Reciprocal of the growth factor today, used to normalise D(a) */
    double inv_D_today = 1.0 / D_today;
    /** Flag: whether scalar field is active and swampland diagnostics are needed */
    int has_scalar_field = (pba->has_scf == _TRUE_);

    /* Pre-cache curvature quantities for comoving radius computation.
       These avoid recomputing sqrt() on every row. */
    /** sqrt(K) for closed geometry (sgnK == +1), 0 otherwise */
    double sqrt_curv_pos = 0.0;
    /** sqrt(-K) for open geometry (sgnK == -1), 0 otherwise */
    double sqrt_curv_neg = 0.0;
    if (pba->sgnK == 1)
      sqrt_curv_pos = sqrt(pba->K);
    else if (pba->sgnK == -1)
      sqrt_curv_neg = sqrt(-pba->K);

    /* ---- SCF column-offset cache and swampland accumulators ----
       These are only meaningful when has_scalar_field is true.
       Initialised to safe sentinel values so they never produce
       spurious results if the scf branch is skipped. */

    /** Column offsets into the strided background table for scalar field quantities */
    int col_phi = 0;     /**< offset for scalar field value phi */
    int col_V = 0;       /**< offset for scalar field potential V */
    int col_dV_pure = 0; /**< offset for pure potential derivative V'_pure (no coupling) */
    int col_ddV = 0;     /**< offset for second derivative V'' */
    int col_d3V = 0;     /**< offset for third derivative V''' */
    int col_d4V = 0;     /**< offset for fourth derivative V'''' */

    /** Whether the dark matter model is interacting (model_cdm==2),
        which activates the SSWGC and AdSDC diagnostics */
    int is_interacting_dm = 0;
    /** Dark matter coupling constant c (from pba->cdm_c) */
    double dm_coupling_c = 0.0;

    /* Running extrema for the scalar field range */
    /** Running minimum of phi across all table rows */
    double phi_running_min = 0.0;
    /** Running maximum of phi across all table rows */
    double phi_running_max = 0.0;

    /* ---- de Sitter Conjecture (dSC) accumulators ---- */
    /** Running minimum of |V'_pure|/V (first de Sitter Conjecture parameter).
        Initialised to +sentinel so any real value wins. */
    double dV_over_V_min = 1.e100;
    /** Running maximum of V''/V (second de Sitter Conjecture parameter).
        Initialised to -sentinel so any real value wins. */
    double ddV_over_V_max = -1.e100;
    /** Value of V''/V at the row where |V'_pure|/V is minimised
        (cross-diagnostic: evaluates second dSC where first dSC is weakest) */
    double ddV_over_V_at_dV_min = -1.e100;
    /** Value of |V'_pure|/V at the row where V''/V is maximised
        (cross-diagnostic: evaluates first dSC where second dSC is strongest) */
    double dV_over_V_at_ddV_max = 1.e100;

    /* ---- Scalar Weak Gravity Conjecture (SWGC) accumulator ---- */
    /** Running minimum of 2(V''')^2 - V''*V'''' - (V'')^2.
        Positive values indicate the SWGC is satisfied.
        Initialised to +sentinel so any real value wins. */
    double swgc_running_min = 1.e100;

    /* ---- Combined de Sitter Conjecture accumulator ---- */
    /** Running minimum of (3*(V'_pure/V)^2 - 2*V''/V)/4.
        This combined bound comes from the FLB and SSWGC.
        Initialised to +sentinel so any real value wins. */
    double combined_dSC_running_min = 1.e100;

    /* ---- Strong Scalar WGC (SSWGC) accumulator ---- */
    /** Running minimum of the SSWGC expression: M_P^2 m^2 d^2(1/m^2)/dphi^2.
        Only meaningful for interacting DM (model_cdm==2). */
    double sswgc_running_min = 1.e100;

    /* ---- Anti-de Sitter Distance Conjecture (AdSDC) accumulators ---- */
    /** Running maximum of (1-tanh(c*phi))/|V|^{1/2}/2  (AdSDC with exponent 1/2) */
    double AdSDC2_running_max = 0.0;
    /** Running maximum of (1-tanh(c*phi))/|V|^{1/4}/2  (AdSDC with exponent 1/4) */
    double AdSDC4_running_max = 0.0;

    if (has_scalar_field)
    {
      /* Cache column offsets from the background struct */
      col_phi = pba->index_bg_phi_scf;
      col_V = pba->index_bg_V_scf;
      col_dV_pure = pba->index_bg_dV_p_scf;
      col_ddV = pba->index_bg_ddV_scf;
      col_d3V = pba->index_bg_d3V_scf;
      col_d4V = pba->index_bg_d4V_scf;

      is_interacting_dm = (pba->model_cdm == 2);
      dm_coupling_c = pba->cdm_c;

      /* Seed all accumulators from the first row (i==0) of the table,
         so the main loop can start comparisons from i==1. */
      double *first_row = pba->background_table;
      double phi_seed = first_row[col_phi];     /**< phi at earliest time in table */
      double V_seed = first_row[col_V];         /**< V(phi) at earliest time */
      double dVp_seed = first_row[col_dV_pure]; /**< V'_pure at earliest time */
      double ddV_seed = first_row[col_ddV];     /**< V'' at earliest time */
      double d3V_seed = first_row[col_d3V];     /**< V''' at earliest time */
      double d4V_seed = first_row[col_d4V];     /**< V'''' at earliest time */

      phi_running_min = phi_seed;
      phi_running_max = phi_seed;

      if (V_seed != 0.0)
      {
        double inv_V_seed = 1.0 / V_seed;
        dV_over_V_min = fabs(dVp_seed) * inv_V_seed;
        ddV_over_V_max = ddV_seed * inv_V_seed;
        ddV_over_V_at_dV_min = ddV_over_V_max;
        dV_over_V_at_ddV_max = dV_over_V_min;
        swgc_running_min = 2.0 * d3V_seed * d3V_seed - ddV_seed * d4V_seed - ddV_seed * ddV_seed;
        double dVp_over_V_seed = dVp_seed * inv_V_seed;
        combined_dSC_running_min = 0.25 * (3.0 * dVp_over_V_seed * dVp_over_V_seed - 2.0 * ddV_over_V_max);
      }

      if (is_interacting_dm)
      {
        double c_times_phi_seed = dm_coupling_c * phi_seed;
        double tanh_c_phi_seed = tanh(c_times_phi_seed);
        double cosh_c_phi_seed = cosh(c_times_phi_seed);
        /** SSWGC expression: 2*c^2 * [4*(1+tanh(c*phi)) - sech^2(c*phi)] */
        sswgc_running_min = 2.0 * dm_coupling_c * dm_coupling_c * (4.0 * (1.0 + tanh_c_phi_seed) - 1.0 / (cosh_c_phi_seed * cosh_c_phi_seed));
        double abs_V_seed = fabs(V_seed);
        double sqrt_abs_V_seed = sqrt(abs_V_seed);
        /* f_cdm(phi) = 1/(1+exp(2*c*phi)) == (1-tanh(c*phi))/2
         * (identity verified in Mathematica).
         * Using the exp form to avoid catastrophic cancellation for large c*phi. */
        double f_cdm_seed = 1.0 / (1.0 + exp(2.0 * c_times_phi_seed));
        AdSDC2_running_max = f_cdm_seed / sqrt_abs_V_seed;
        AdSDC4_running_max = f_cdm_seed / sqrt(sqrt_abs_V_seed);
      }
    }

    /* ---- Main fused loop over all rows of the background table ---- */
    for (int i = 0; i < num_bg_rows; i++)
    {
      double *row = pba->background_table + i * cols_per_row;

      /* --- Part 1: Growth factor normalisation and distance computation ---
         Normalise D(a) by D_today so that D(a_today)=1, then compute
         conformal distance, comoving radius and angular/luminosity distances. */
      row[pba->index_bg_D] *= inv_D_today;

      conformal_distance = pba->conformal_age - pba->tau_table[i];
      row[pba->index_bg_conf_distance] = conformal_distance;

      if (pba->sgnK == 0)
        comoving_radius = conformal_distance;
      else if (pba->sgnK == 1)
        comoving_radius = sin(sqrt_curv_pos * conformal_distance) / sqrt_curv_pos;
      else /* sgnK == -1 */
        comoving_radius = sinh(sqrt_curv_neg * conformal_distance) / sqrt_curv_neg;

      /** 1 + z for this row, used for angular/luminosity distance */
      double one_plus_z = 1.0 + pba->z_table[i];
      row[pba->index_bg_ang_distance] = comoving_radius / one_plus_z;
      row[pba->index_bg_lum_distance] = comoving_radius * one_plus_z;

      /* --- Part 2: Scalar field swampland diagnostics ---
         Row 0 was already used to seed the accumulators above,
         so comparisons start from row 1 onwards. */
      if (has_scalar_field && i > 0)
      {
        /** Scalar field value at this time step */
        double phi = row[col_phi];
        /** Scalar field potential V(phi) at this time step */
        double V = row[col_V];
        /** Pure potential derivative V'_pure(phi) (without coupling to DM) */
        double dV_pure = row[col_dV_pure];
        /** Second derivative V''(phi) */
        double V_second_deriv = row[col_ddV];
        /** Third derivative V'''(phi) */
        double V_third_deriv = row[col_d3V];
        /** Fourth derivative V''''(phi) */
        double V_fourth_deriv = row[col_d4V];

. . . (computation of the actual swampland parameters is explained in the subsections here in this Wiki chapter).

    /* Store swampland results into the background struct for the Python wrapper.
       Sentinel values (from rows where V==0 everywhere) are mapped to 0. */
    if (has_scalar_field)
    {
      pba->phi_scf_min = phi_running_min;
      pba->phi_scf_max = phi_running_max;
      pba->phi_scf_range = phi_running_max - phi_running_min;

      pba->dV_V_scf_min = (dV_over_V_min < 1.e99) ? dV_over_V_min : 0.0;
      pba->ddV_V_at_dV_V_min = (dV_over_V_min < 1.e99) ? ddV_over_V_at_dV_min : 0.0;
      pba->ddV_V_scf_max = (ddV_over_V_max > -1.e99) ? ddV_over_V_max : 0.0;
      pba->dV_V_at_ddV_V_max = (ddV_over_V_max > -1.e99) ? dV_over_V_at_ddV_max : 0.0;
      pba->swgc_expr_min = (swgc_running_min < 1.e99) ? swgc_running_min : 0.0;
      pba->sswgc_min = (sswgc_running_min < 1.e99) ? sswgc_running_min : 0.0;
      pba->AdSDC2_max = (AdSDC2_running_max > -1.e99) ? AdSDC2_running_max : 0.0;
      pba->AdSDC4_max = (AdSDC4_running_max > -1.e99) ? AdSDC4_running_max : 0.0;
      pba->combined_dSC_min = (combined_dSC_running_min < 1.e99) ? combined_dSC_running_min : 0.0;
    }
  }

The Anti–de Sitter Conjecture

$m=\frac{m_0}{2}\left[1-\tanh\left(\mathfrak{c}\left(\phi-\phi_0\right)\right)\right]\lesssim\left|V\right|^{\mathfrak{d}} M_\textnormal{P}^{1-2\mathfrak{d}}$ can give a lower mass bound on the unknown dark matter mass $m_0$. CLASS does not use an absolute mass scale for DM at any point, yet, with this routine we could at least obtain a lower bound on DM mass compatible with this model and observational data.

          double abs_V = fabs(V);
          double sqrt_abs_V = sqrt(abs_V);
          /* f_cdm(phi) = 1/(1+exp(2*c*phi)) == (1-tanh(c*phi))/2
           * (identity verified in Mathematica).
           * Using the exp form to avoid catastrophic cancellation for large c*phi. */
          double f_cdm = 1.0 / (1.0 + exp(2.0 * c_times_phi));

          /** AdSDC boundary with exponent 1/2: f_cdm(phi) / |V|^{1/2} */
          double AdSDC2_expr = f_cdm / sqrt_abs_V;
          if (AdSDC2_expr > AdSDC2_running_max)
            AdSDC2_running_max = AdSDC2_expr;

          /** AdSDC boundary with exponent 1/4: f_cdm(phi) / |V|^{1/4} */
          double AdSDC4_expr = f_cdm / sqrt(sqrt_abs_V);
          if (AdSDC4_expr > AdSDC4_running_max)
            AdSDC4_running_max = AdSDC4_expr;

This yields then an upper bound for $1/m_0$, i.e. the inverse is a lower bound on the dark matter mass $m_0$.

The de Sitter Conjecture

The de Sitter conjecture puts constraints on the scalar field dynamics by requesting that either $\left|\nabla V\right|/V\geq s_1$ or $\min\left(\Delta V\right)/V\leq -s_2$ at every point during the cosmic evolution. That means that one of the two conditions can be violated, yet not both of them at the same time. Therefore, we track the minimal value of $s_1$ and also store the value of $s_2$ at that point, as well as the maximal value of $s_2$ and the value of $s_1$ at the point $s_2$ reaches its maximum.

          double inv_V = 1.0 / V;

          /** |V'_pure|/V for the first de Sitter Conjecture */
          double dV_over_V = fabs(dV_pure) * inv_V;
          /** V''/V for the second de Sitter Conjecture */
          double ddV_over_V = V_second_deriv * inv_V;

          /* Track minimum of |V'|/V and record V''/V at that point */
          if (dV_over_V < dV_over_V_min)
          {
            dV_over_V_min = dV_over_V;
            ddV_over_V_at_dV_min = ddV_over_V;
          }
          /* Track maximum of V''/V and record |V'|/V at that point */
          if (ddV_over_V > ddV_over_V_max)
          {
            ddV_over_V_max = ddV_over_V;
            dV_over_V_at_ddV_max = dV_over_V;
          }

Distance Conjecture

The constraint $\frac{\Delta \phi}{M_\textnormal{P}}\lesssim10$ can be checked by accessing

      pba->phi_scf_min = phi_running_min;
      pba->phi_scf_max = phi_running_max;
      pba->phi_scf_range = phi_running_max - phi_running_min;

which are extracted from the background table

        /* Update running phi range */
        if (phi < phi_running_min)
          phi_running_min = phi;
        if (phi > phi_running_max)
          phi_running_max = phi;

Scalar Weak Gravity Conjecture

Requiring that gravity is always the weakest force, also weaker than scalar interactions, gives rise to the constraint $\frac{1}{M_\textnormal{P}^2}\left(\frac{\mathrm{d}^2V}{\mathrm{d}\phi^2}\right)^2\leq2\left(\frac{\mathrm{d}^3V}{\mathrm{d}\phi^3}\right)^2-\frac{\mathrm{d}^2V}{\mathrm{d}\phi^2}\frac{\mathrm{d}^4V}{\mathrm{d}\phi^4}$, which is implemented as

          /** SWGC expression: 2*(V''')^2 - V''*V'''' - (V'')^2 */
          double swgc_expr = 2.0 * V_third_deriv * V_third_deriv - V_second_deriv * V_fourth_deriv - V_second_deriv * V_second_deriv;
          if (swgc_expr < swgc_running_min)
            swgc_running_min = swgc_expr;

If swampland constraints are enforced, negative values can then be discarded, e.g. through a corresponding likelihood constraint in Cobaya.

The Strong Scalar Weak Gravity Conjecture

$M^2m^2\partial_\phi^2\left(1/m^2\right)=2c^2\left(4\left(1+\tanh\left(c\phi\right)\right)-1/\cosh\left(c\phi\right)^2\right)\geq1$ puts a constraint on the dark matter mass term dynamics, such that the minimum of the expression should be larger than 1 (not enforced in CLASS, but made available to the Python wrapper, such that the minimal value of the SSWGC that is reached during cosmological evolution can be used as likelihood constraint:

          double sswgc_expr = 2.0 * dm_coupling_c * dm_coupling_c * (4.0 * (1.0 + tanh_c_phi) - 1.0 / (cosh_c_phi * cosh_c_phi));
          if (sswgc_expr < sswgc_running_min)
            sswgc_running_min = sswgc_expr;

This bound assumes $m^2=V^{\prime\prime}$ (Dudas et al.).

The Strong Scalar Festina Lente Bound

Combining the Festina Lente bound with the strong scalar weak gravity conjecture leads to a constraint on the DE potential that is even more demanding than the de Sitter conjecture: $\frac{M_\textnormal{P}^2}{4}\left(\frac{3\left(V^\prime\right)^2}{V^2}-\frac{2V^{\prime\prime}}{V}\right)\geq1$

          /** V'_pure/V ratio for the combined dSC bound
              (read directly from table — no dV_p_scf() call needed) */
          double dVp_over_V = dV_pure * inv_V;
          /** Combined dSC: (3*(V'/V)^2 - 2*V''/V) / 4 */
          double combined_dSC_expr = 0.25 * (3.0 * dVp_over_V * dVp_over_V - 2.0 * ddV_over_V);
          if (combined_dSC_expr < combined_dSC_running_min)
            combined_dSC_running_min = combined_dSC_expr;

We use dV_pure, since in our definition of dV (not pure), the additional generalised coupling between DE and DM is present. We don't need this to evaluate the swampland conjecture, as it is a constraint on the scalar field dynamics. For the swampland constraints, we take the 'pure' scalar field potential without additional coupling.

Clone this wiki locally