Hulek-Verrill attractors with JAXVacua#

What’s in this notebook? This advanced notebook uses the Hulek–Verrill/AESZ34 rank-two attractor points of arXiv:1912.06146 to demonstrate JAXVacua’s custom-period interface. It feeds local period and tangent data from the paper into jaxvacua.FluxEFT, translates the black-hole charge and IIB flux conventions, and checks the resulting \(W=0\) and \(DW=0\) conditions with JAXVacua’s internal special-geometry machinery. The period map used here is only a local tangent model around the attractor points; it verifies conventions and F-term alignment, but it does not compute global Picard–Fuchs continuation or true attractor-flow curves.

Outline#

  1. Setup

  2. Flux vacua and black-hole attractors

  3. Paper input data

  4. Charges and fluxes

  5. Local JAXVacua model

  6. Figure-2-style local flow schematic

  7. All-attractor overview plot

  8. Compact check for all three attractors

  9. Take-aways

  10. Further reading

Flux vacua and black-hole attractors#

The connection used in this notebook is the special Kähler geometry of \(H^3(X)\). A four-dimensional BPS black hole in type IIB on a Calabi-Yau threefold carries one integral charge

\[ \Gamma \in H^3(X,\mathbb{Z}) \]

with central charge

\[ Z_\Gamma = e^{K_{\rm cs}/2}\int_X \Gamma \wedge \Omega . \]

At the horizon, the attractor equations fix the complex-structure moduli by \(D_i Z_\Gamma=0\). Equivalently, at the attractor the charge has only \((3,0)+(0,3)\) Hodge components, up to the usual convention-dependent normalisation. A rank-two attractor is the special case in which

\[ L=\left(H^{3,0}\oplus H^{0,3}\right)\cap H^3(X,\mathbb{Z}), \qquad \operatorname{rank} L=2 . \]

A type-IIB flux compactification instead chooses two integral three-form fluxes \(F_3\), \(H_3\) and the axio-dilaton \(\tau\), combines them as

\[ G_3=F_3-\tau H_3, \qquad W=\int_X G_3\wedge\Omega , \]

and solves the flux F-term equations. The equations \(D_i W=D_\tau W=0\) are Hodge-alignment conditions on \(G_3\): they remove the \((1,2)\) and \((3,0)\) pieces, leaving the imaginary-self-dual components \((2,1)+(0,3)\). If one additionally imposes \(W=0\), as in the examples below, the \((0,3)\) part is absent and \(G_3\) is of type \((2,1)\).

Thus flux vacua and black-hole attractors are closely parallel, but not identical. A black-hole attractor is specified by one real charge \(\Gamma\), while a flux vacuum is specified by the flux doublet \((F_3,H_3)\) together with \(\tau\). For the Hulek–Verrill rank-two points this distinction matters: the paper’s charge vectors span \(L\) and are the correct labels for black-hole attractor flows, whereas the JAXVacua \(DW=0\) check uses vectors from the complementary lattice \(L_\perp\), encoded in the paper’s \(D_\phi\Pi\) data, so that \(F-\tau H\) lies along the \((2,1)\) direction at the attractor.

Literature pointers:

Setup#

from math import sqrt, pi
import os
import warnings

os.environ.setdefault('MPLCONFIGDIR', '/tmp/jaxvacua_mpl_cache')
os.makedirs(os.environ['MPLCONFIGDIR'], exist_ok=True)

import numpy as np
import jax
jax.config.update('jax_enable_x64', True)
import jax.numpy as jnp

from scipy.optimize import root
import matplotlib.pyplot as plt

import jaxvacua

warnings.filterwarnings('ignore')

np.set_printoptions(precision=6, suppress=False)

Paper input data#

The only hard-coded ingredients below are the period values and the attractor tangent data from the paper. All Kähler potential, superpotential, and F-term evaluations below are done by jaxvacua.FluxEFT.

For the quotient parameter we use kappa=1, corresponding to the Z/10Z quotient. Set kappa=2 for the Z/5Z quotient.

# Critical L-values quoted in arXiv:1912.06146.
L14_4_1 = 0.67496319716994177129269568273091339919322842904407
L14_4_2 = 0.91930674266912115653914356907939249680895763199044

L34_4_1_re = 0.61300748403501690756896255581360559790853555213198
L34_4_2_re = 0.72053904959503349611018739597922735350251006854978

v_perp_14 = 0.37369955695472976699767292752499463211766555651682
v_perp_plus = 1.9696894453517505490479716982864516913834531417517
v_perp_minus = 1.0153884942216545916762729868825409864938877880731

sqrt17 = sqrt(17.0)
eps_plus = 4.0 + sqrt17
eps_minus = 4.0 - sqrt17
delta_plus = (3.0 + sqrt17) / 2.0
delta_minus = (3.0 - sqrt17) / 2.0


def paper_period(attractor, kappa=1):
    k = kappa
    if attractor == 'm17':
        return (
            1j * L14_4_1 / (4.0 * pi) * np.array([8*k, -30*k, 0, 5], complex)
            + 7.0 * L14_4_2 / (2.0 * pi**2) * np.array([0, 0, 2, 1], complex)
        )
    if attractor == 'plus':
        return (
            -1j * delta_minus**3 * L34_4_1_re / (2**5 * sqrt17 * pi)
            * np.array([-4*k, 30*k, 30, 5], complex)
            - sqrt17 * eps_minus**2 * delta_plus * L34_4_2_re / (2**3 * pi**2)
            * np.array([4*k, -9*k, 7, 4], complex)
        )
    if attractor == 'minus':
        return (
            1j * eps_plus**3 * delta_minus**3 * L34_4_1_re / (2**5 * sqrt17 * pi)
            * np.array([2*k, 0, 0, -5], complex)
            + sqrt17 * eps_plus * delta_plus * L34_4_2_re / (2**3 * pi**2)
            * np.array([0, 3*k, 1, 0], complex)
        )
    raise ValueError(attractor)


def attractor_phi(attractor):
    if attractor == 'm17':
        return -1.0 / 7.0
    if attractor == 'plus':
        return 33.0 + 8.0 * sqrt17
    if attractor == 'minus':
        return 33.0 - 8.0 * sqrt17
    raise ValueError(attractor)


def paper_K_phi(attractor):
    if attractor == 'm17':
        return -35.0 / 8.0
    if attractor == 'plus':
        return 5.0 / (2**3 * sqrt17) * eps_minus**2 * (2.0 + sqrt17)
    if attractor == 'minus':
        return -5.0 / (2**3 * sqrt17) * eps_plus**2 * (2.0 - sqrt17)
    raise ValueError(attractor)

Charges and fluxes#

The black-hole charge lattice L from the paper can be embedded as a pure RR flux [Q | 0], but that is not the natural supersymmetric IIB flux choice.

For DW=0, we use the orthogonal lattice L_perp, equivalently the paper’s D_phi Pi expressions. The following function returns integer F, H, the associated tau, and the JAXVacua flux vector [F | H].

def black_hole_charge_generators(attractor, kappa=1):
    k = int(kappa)
    if attractor == 'm17':
        return np.array([4*k, -15*k, -5, 0]), np.array([0, 0, 2, 1])
    if attractor == 'plus':
        return np.array([4*k, -9*k, 7, 4]), np.array([4*k, -30*k, -30, -5])
    if attractor == 'minus':
        return np.array([-2*k, 0, 0, 5]), np.array([0, 3*k, 1, 0])
    raise ValueError(attractor)


def flux_from_Lperp(attractor, kappa=1):
    k = int(kappa)
    if attractor == 'm17':
        F = np.array([-5*k, 10*k, -5, -3])
        H = np.array([-7*k, 14*k, -10, -5])
        tau = 0.5 + 1j * v_perp_14
    elif attractor == 'plus':
        # DPi is proportional to a - tau^{-1} b; multiply by -tau.
        a = np.array([9*k, -16*k, 20, 9])
        b = np.array([15*k, -36*k, 15, 11])
        F = b
        H = a
        tau = 1j * v_perp_plus
    elif attractor == 'minus':
        # DPi is proportional to a + tau b.
        a = np.array([0, -2*k, 5, 0])
        b = np.array([3*k, 0, 0, 1])
        F = a
        H = -b
        tau = 1j * v_perp_minus
    else:
        raise ValueError(attractor)

    fluxes = np.concatenate([F, H])
    
    return F, H, tau, fluxes


for name in ['m17', 'plus', 'minus']:
    e1, e2 = black_hole_charge_generators(name)
    F, H, tau, fluxes = flux_from_Lperp(name)
    print(name)
    print('  black-hole L generators:', e1, e2)
    print('  F =', F)
    print('  H =', H)
    print('  tau =', tau)
    print('  jaxvacua fluxes =', fluxes)

Local JAXVacua model#

We now make the construction explicit for the attractor point \(\phi=-1/7\). This section shows the steps that are easy to get wrong when translating the paper data into the JAXVacua convention:

  1. build the attractor period vector \(\Pi_*\) from the paper;

  2. translate the \(L_\perp\) vectors into JAXVacua fluxes \([F\mid H]\);

  3. construct a local period map whose covariant derivative satisfies \(D_\phi\Pi_*=F-\tau H\);

  4. instantiate jaxvacua.FluxEFT and evaluate \(A\), \(W\), and \(DW\);

  5. recover the minimum by solving model.DW_x = 0 from a nearby starting point.

The local period map is only a tangent model around the attractor. It is enough for the F-term check at \(\phi_*\), but it is not a replacement for a global Picard–Fuchs continuation routine.

def make_local_period_input(phi_star, Pi_star, dPi_star):
    """Local holomorphic period map Pi(phi) = Pi_* + (phi - phi_*) dPi_*."""
    phi_star = jnp.asarray(phi_star, dtype=jnp.complex128)
    Pi_star = jnp.asarray(Pi_star, dtype=jnp.complex128)
    dPi_star = jnp.asarray(dPi_star, dtype=jnp.complex128)

    def period_input(X, conj=False):
        # JAXVacua calls custom period_input with homogeneous coordinates X=(X^0, X^1).
        # We use X^1 as the local family coordinate phi and keep X^0 as the projective gauge.
        phi = X[1]
        if conj:
            return jnp.conj(Pi_star) + (phi - jnp.conj(phi_star)) * jnp.conj(dPi_star)
        return Pi_star + (phi - phi_star) * dPi_star

    return period_input

Step 1: construct the period vector#

For \(\kappa=1\), the paper gives

\[ \Pi\!\left(-\frac{1}{7}\right) =\frac{iL_4(1)}{4\pi}\,(8,-30,0,5) +\frac{7L_4(2)}{2\pi^2}\,(0,0,2,1) . \]

The entries are already ordered as JAXVacua expects: \((F_0,F_1,X^0,X^1)\).

kappa = 1
phi_star = attractor_phi('m17')
Pi_star = paper_period('m17', kappa=kappa)

print('phi_* =', phi_star)
print('Pi_*  =', Pi_star)
print('special coordinate X^1/X^0 =', Pi_star[3] / Pi_star[2])

Step 2: translate the fluxes#

The black-hole charges in the paper span the attractor lattice \(L\). For a supersymmetric IIB flux check we instead use the orthogonal \(L_\perp\) data from the paper’s \(D\Pi\) expression:

\[ F=(-5,10,-5,-3),\qquad H=(-7,14,-10,-5),\qquad \tau=\frac{1}{2}+i v_\perp . \]

These are placed into JAXVacua as \(\mathrm{fluxes}=[F\mid H]\).

F_star, H_star, tau_star, fluxes_star_np = flux_from_Lperp('m17', kappa=kappa)
G_star = F_star - tau_star * H_star

print('F      =', F_star)
print('H      =', H_star)
print('tau    =', tau_star)
print('[F|H]  =', fluxes_star_np)
print('G=F-tau H =', G_star)

Step 3: adapt the local period map to JAXVacua#

JAXVacua differentiates the user-supplied period map. The paper gives the covariant derivative direction. If

\[ D_\phi\Pi=\partial_\phi\Pi+K_\phi\Pi, \]

then we choose the local derivative

\[ \partial_\phi\Pi_*=(F-\tau H)-K_\phi\Pi_* . \]

With this convention, JAXVacua sees exactly \(D_\phi\Pi_*=F-\tau H\) at the attractor.

K_phi_star = paper_K_phi('m17')
dPi_star = G_star - K_phi_star * Pi_star

period_input_m17 = make_local_period_input(phi_star, Pi_star, dPi_star)
model_m17 = jaxvacua.FluxEFT(h12=1, limit=None, period_input=period_input_m17)

z_star = jnp.array([phi_star + 0.0j], dtype=jnp.complex128)
zc_star = jnp.conj(z_star)
fluxes_star = jnp.asarray(fluxes_star_np, dtype=jnp.float64)

print('K_phi =', K_phi_star)
print('partial_phi Pi_* =', dPi_star)

Step 4: evaluate the attractor with JAXVacua#

The quantities in the next cell are computed by JAXVacua methods, not by manual symplectic products in the notebook.

A_star = model_m17.A(z_star, zc_star)
W_star = model_m17.W(z_star, tau_star, fluxes_star)
DW_star = model_m17.DW(z_star, zc_star, tau_star, jnp.conj(tau_star), fluxes_star)

print('e^-K_cs =', complex(A_star))
print('W       =', complex(W_star), 'abs =', float(jnp.abs(W_star)))
print('DW      =', np.asarray(DW_star))
print('max|DW| =', float(jnp.max(jnp.abs(DW_star))))

Step 5: recover the minimum from DW_x#

DW_x is JAXVacua’s real F-term interface. For one complex-structure modulus the real vector is

\[ x=(\operatorname{Re}\phi,\operatorname{Im}\phi,\operatorname{Re}\tau,\operatorname{Im}\tau) . \]

Starting near the attractor, scipy.optimize.root converges back to the paper’s value.

x_exact = np.array([phi_star, 0.0, tau_star.real, tau_star.imag], dtype=float)
x0 = x_exact + np.array([0.02, 0.01, 0.03, -0.02])

res = root(model_m17.DW_x, x0, args=(fluxes_star,), jac=model_m17.dDW_x, method='hybr', tol=1e-10)

print('success:', res.success)
print('initial x0:', x0)
print('solution:', res.x)
print('target:  ', x_exact)
print('residual norm:', np.linalg.norm(model_m17.DW_x(res.x, fluxes_star)))

Figure-2-style local flow schematic#

Figure 2 of the paper shows attractor-flow curves in the phi plane for black-hole charge generators leading to rank-two attractor points. The plot below reinstates that earlier notebook view: the endpoints and charge labels use the paper data, while the curves are schematic paths drawn on the cut plane.

The labels refer to the black-hole charge generators spanning the paper’s lattice L. They are not the same objects as the L_perp fluxes used above for the IIB DW=0 check. To compute the actual flow trajectories we would need a global Hulek–Verrill period evaluator and the attractor-flow ODE; the local JAXVacua model is only a tangent model at the endpoint.

def _format_charge(q):
    return '[' + ', '.join(str(int(x)) for x in q) + ']'


def _schematic_flow(start, end, bend=0.0, n=180):
    t = np.linspace(0.0, 1.0, n)
    start = complex(start)
    end = complex(end)
    curve = (1.0 - t) * start + t * end
    curve += 1j * bend * np.sin(np.pi * t)
    return curve


def plot_phi_plane_schematic():
    phi_m17 = attractor_phi('m17')
    phi_minus = attractor_phi('minus')
    conifolds = np.array([1/25, 1/9, 1.0])
    m17_e1, m17_e2 = black_hole_charge_generators('m17')
    minus_e1, minus_e2 = black_hole_charge_generators('minus')

    flows = [
        (0.26 + 0.18j, phi_m17, 0.035, 'tab:blue', 'Q(-1/7,1) = ' + _format_charge(m17_e1)),
        (0.58 - 0.16j, phi_m17, -0.030, 'tab:orange', 'Q(-1/7,2) = ' + _format_charge(m17_e2)),
        (0.22 + 0.11j, phi_minus, 0.025, 'tab:green', 'Q($\phi_-$,1) = ' + _format_charge(minus_e1)),
        (0.42 - 0.05j, phi_minus, -0.020, 'tab:purple', 'Q($\phi_-$,2) = ' + _format_charge(minus_e2)),
    ]

    fig, ax = plt.subplots(figsize=(8.4, 6.2), constrained_layout=True, dpi=150)
    ax.plot([-0.32, 0.0], [0.0, 0.0], color='0.72', lw=3, solid_capstyle='round', label='branch cuts')
    ax.plot([1/25, 1.12], [0.0, 0.0], color='0.72', lw=3, solid_capstyle='round')
    ax.scatter([0.0], [0.0], s=82, facecolors='white', edgecolors='black', zorder=5, label='MUM')
    ax.scatter(conifolds, np.zeros_like(conifolds), s=48, color='black', zorder=5, label='conifolds')
    ax.scatter([phi_m17], [0.0], s=95, color='crimson', zorder=6, label='$\phi$=-1/7')
    ax.scatter([phi_minus], [0.0], s=95, color='tab:green', zorder=6, label='$\phi_-$')

    for start, end, bend, color, label in flows:
        curve = _schematic_flow(start, end, bend=bend)
        ax.plot(curve.real, curve.imag, color=color, lw=2.0, label=label)
        ax.annotate(
            '',
            xy=(curve.real[-1], curve.imag[-1]),
            xytext=(curve.real[-7], curve.imag[-7]),
            arrowprops=dict(arrowstyle='->', color=color, lw=2.0),
        )

    ax.annotate('-1/7', xy=(phi_m17, 0.0), xytext=(-0.19, -0.025), color="tab:red",fontsize=12)
    ax.annotate(r'$\phi_-$', xy=(phi_minus, 0.0), xytext=(-.01, -0.025), color="tab:green", fontsize=12)
    ax.annotate('1/25', xy=(1/25, 0.0), xytext=(0.03, 0.015), fontsize=12)
    ax.annotate('1/9', xy=(1/9, 0.0), xytext=(0.095, -0.015), fontsize=12)
    ax.annotate('1', xy=(1.0, 0.0), xytext=(0.99, -0.025), fontsize=12)

    ax.axhline(0.0, color='0.9', lw=0.8, zorder=0)
    ax.axvline(0.0, color='0.92', lw=0.8, zorder=0)
    ax.set_xlim(-0.32, 1.12)
    ax.set_ylim(-0.24, 0.24)
    ax.set_xlabel(r'Re$(\phi)$')
    ax.set_ylabel(r'Im$(\phi)$')
    ax.set_title('Local schematic flows with black-hole charges')
    ax.legend(loc='upper right', fontsize=12, title='schematic labels')
    return fig, ax


plot_phi_plane_schematic();

All-attractor overview plot#

The previous plot keeps the local flow picture close to the paper. The overview below has a different purpose: it places all three attractor points, the MUM point, and the conifold points in one visual frame. Since phi_+ = 33 + 8 sqrt(17) is far from the other two attractors, the right panel uses a symmetric logarithmic real axis.

This is still a schematic, not an integration of the attractor-flow equations. The local model above only knows tangent data at the attractor endpoints; a true Figure-2 reproduction would require the global period map and attractor-flow ODE on the cut phi plane.

def plot_attractor_overview():
    phi_m17 = attractor_phi('m17')
    phi_minus = attractor_phi('minus')
    phi_plus = attractor_phi('plus')
    conifolds = np.array([1/25, 1/9, 1.0])

    fig, (ax_local, ax_global) = plt.subplots(
        1, 2, figsize=(11.0, 3.7), constrained_layout=True
    )

    # Local cut-plane schematic: shows the MUM point, finite conifolds,
    # branch cuts, and the two attractors close to the origin.
    ax_local.plot([-0.35, 0], [0, 0], color='0.72', lw=3, solid_capstyle='round', label='branch cuts')
    ax_local.plot([1/25, 1.15], [0, 0], color='0.72', lw=3, solid_capstyle='round')
    ax_local.scatter([0], [0], s=80, facecolors='white', edgecolors='black', zorder=5, label='MUM')
    ax_local.scatter(conifolds, np.zeros_like(conifolds), s=48, color='black', zorder=5, label='conifolds')
    ax_local.scatter([phi_m17], [0], s=95, color='crimson', zorder=6, label='phi=-1/7')
    ax_local.scatter([phi_minus], [0], s=95, color='tab:green', zorder=6, label='phi-')

    # Illustrative paths ending at the nearby attractors. These are not the
    # true attractor-flow ODE solutions; they just mark the geometric idea.
    t = np.linspace(0, 1, 180)
    for start, endpoint, color in [
        (0.26 + 0.18j, phi_m17 + 0j, 'tab:blue'),
        (0.58 - 0.16j, phi_m17 + 0j, 'tab:orange'),
        (0.22 + 0.11j, phi_minus + 0j, 'tab:green'),
    ]:
        curve = (1 - t)**1.25 * start + (1 - (1 - t)**1.25) * endpoint
        curve += 0.030j * np.sin(np.pi * t) * (1 if start.imag >= 0 else -1)
        ax_local.plot(curve.real, curve.imag, color=color, lw=2.0)
        ax_local.annotate(
            '', xy=(curve.real[-1], curve.imag[-1]), xytext=(curve.real[-12], curve.imag[-12]),
            arrowprops=dict(arrowstyle='->', color=color, lw=2.0)
        )

    ax_local.set_title('Local cut-plane view')
    ax_local.set_xlabel('Re(phi)')
    ax_local.set_ylabel('Im(phi)')
    ax_local.set_xlim(-0.32, 1.18)
    ax_local.set_ylim(-0.25, 0.25)
    ax_local.axhline(0, color='0.9', lw=0.8, zorder=0)
    ax_local.legend(loc='upper right', fontsize=8)

    # Global real-axis overview: symlog keeps phi+ visible without crushing
    # the local structure completely.
    ax_global.axhline(0, color='0.9', lw=0.8, zorder=0)
    ax_global.plot([-0.35, 0], [0, 0], color='0.72', lw=3, solid_capstyle='round')
    ax_global.plot([1/25, 80], [0, 0], color='0.72', lw=3, solid_capstyle='round')
    ax_global.scatter([0], [0], s=80, facecolors='white', edgecolors='black', zorder=5, label='MUM')
    ax_global.scatter(conifolds, np.zeros_like(conifolds), s=48, color='black', zorder=5, label='conifolds')
    ax_global.scatter([phi_m17], [0], s=95, color='crimson', zorder=6, label='phi=-1/7')
    ax_global.scatter([phi_minus], [0], s=95, color='tab:green', zorder=6, label='phi-')
    ax_global.scatter([phi_plus], [0], s=95, color='tab:purple', zorder=6, label='phi+')

    for x, txt, yoff in [
        (phi_m17, '-1/7', 0.08),
        (phi_minus, 'phi-', -0.09),
        (1/25, '1/25', 0.08),
        (1/9, '1/9', -0.09),
        (1.0, '1', 0.08),
        (phi_plus, 'phi+', 0.08),
    ]:
        ax_global.annotate(txt, xy=(x, 0), xytext=(x, yoff), ha='center', fontsize=8,
                           arrowprops=dict(arrowstyle='-', color='0.5', lw=0.8))

    ax_global.set_xscale('symlog', linthresh=0.05)
    ax_global.set_xticks([-0.1, 0.0, 0.1, 1.0, 10.0])
    ax_global.set_xticklabels(['-0.1', '0', '0.1', '1', '10'])
    ax_global.set_xlim(-0.4, 90)
    ax_global.set_ylim(-0.22, 0.22)
    ax_global.set_yticks([])
    ax_global.set_title('Compressed real-axis overview')
    ax_global.set_xlabel('phi (symlog scale)')
    ax_global.legend(loc='upper left', fontsize=8, ncols=2)

    return fig, (ax_local, ax_global)


plot_attractor_overview();

Compact check for all three attractors#

The helper below applies the same construction to phi=-1/7 and to the two quadratic attractors phi=33 +/- 8 sqrt(17).

def build_attractor_model(attractor, kappa=1):
    phi = attractor_phi(attractor)
    Pi = paper_period(attractor, kappa=kappa)
    K_phi = paper_K_phi(attractor)
    F, H, tau, fluxes = flux_from_Lperp(attractor, kappa=kappa)

    G = F - tau * H
    dPi = G - K_phi * Pi
    period_input = make_local_period_input(phi, Pi, dPi)

    model = jaxvacua.FluxEFT(h12=1, limit=None, period_input=period_input)
    return model, phi, tau, jnp.asarray(fluxes, dtype=jnp.float64)


for name in ['m17', 'plus', 'minus']:
    model, phi, tau, fluxes = build_attractor_model(name, kappa=1)
    z = jnp.array([phi + 0.0j], dtype=jnp.complex128)
    zc = jnp.conj(z)

    A = model.A(z, zc)
    W = model.W(z, tau, fluxes)
    DW = model.DW(z, zc, tau, jnp.conj(tau), fluxes)

    print(name)
    print('  phi =', phi)
    print('  e^-K_cs =', complex(A))
    print('  W       =', complex(W), 'abs =', float(jnp.abs(W)))
    print('  max|DW| =', float(jnp.max(jnp.abs(DW))))

Take-aways#

  • JAXVacua can evaluate a flux EFT from a user-supplied local period function, so custom period data can still use the package’s Kähler potential, superpotential and F-term machinery.

  • The black-hole charge lattice \(L\) in the Hulek–Verrill paper and the \(L_\perp\) data used for the IIB \(DW=0\) check are related but not interchangeable. The notebook keeps these conventions separate.

  • The examples reproduce the attractor F-term checks for all three rank-two attractor points and show a deterministic root solve returning to \(\phi=-1/7\).

  • The flow figures are schematic. Computing actual attractor-flow curves requires a global or patchwise Hulek–Verrill period evaluator, not only the local tangent period map used here.

Further reading#