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#
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
with central charge
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
A type-IIB flux compactification instead chooses two integral three-form fluxes \(F_3\), \(H_3\) and the axio-dilaton \(\tau\), combines them as
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:
Ferrara, Kallosh and Strominger, N=2 Extremal Black Holes, for the original four-dimensional BPS attractor mechanism.
Moore, Arithmetic and Attractors, for attractor varieties and arithmetic aspects of Calabi-Yau attractors.
Gukov, Vafa and Witten, CFT’s From Calabi-Yau Four-folds, for the flux superpotential used here.
Denef and Douglas, Distributions of flux vacua, for flux-vacuum counting and its comparison with attractor-point counting.
Kallosh, Flux vacua as supersymmetric attractors, for algebraic attractor equations for type-IIB flux vacua in terms of the flux doublet \((F,H)\).
Candelas, de la Ossa, Elmi and van Straten, A One Parameter Family of Calabi-Yau Manifolds with Attractor Points of Rank Two, for the Hulek–Verrill periods, charge lattices and rank-two attractor points used in this notebook.
Kachru, Nally and Yang Supersymmetric Flux Compactifications and Calabi-Yau Modularity for evidence of the modular properties of supersymmetric flux vacua, and their relationship to the attractor mechanism.
Candelas, de la Ossa, Flux Vacua and Modularity for Z2 Symmetric Calabi-Yau Manifolds for new families of flux vacua and their modular properties, including the Hulek–Verrill family.
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:
build the attractor period vector \(\Pi_*\) from the paper;
translate the \(L_\perp\) vectors into JAXVacua fluxes \([F\mid H]\);
construct a local period map whose covariant derivative satisfies \(D_\phi\Pi_*=F-\tau H\);
instantiate
jaxvacua.FluxEFTand evaluate \(A\), \(W\), and \(DW\);recover the minimum by solving
model.DW_x = 0from 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
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:
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
then we choose the local derivative
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
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#
NB09 — Moduli-space limits, for custom period and prepotential input in the broader moduli-space context.
NB10 — coni-LCS pipeline, for a detailed local-period treatment near conifold limits.
NB11 — Hypergeometric models, for built-in one-modulus period models.
Candelas, de la Ossa, Elmi and van Straten, arXiv:1912.06146, for the Hulek–Verrill period and attractor data used here.