Moduli-space limits and custom prepotentials#
What’s in this notebook? This notebook is the overview of moduli-space limits in JAXVacua. It explains the LCS limit, introduces what changes near conifold/LCS boundaries, and then shows how custom
prepotential_inputandperiod_inputlet users supply boundary descriptions that are not built into the catalogue.In this notebook, you will learn:
How the LCS prepotential is used in the standard model workflow.
What data enters a coni-LCS limit: a conifold curve, a basis change, and the number of shrinking cycles.
How to pass an analytic prepotential or period vector directly to JAXVacua.
How the custom-input mechanism relates to the built-in one-modulus hypergeometric models in NB11.
Non-goals: the detailed conifold approximation schemes, PFV examples,
z_cfreconstruction, and freezer interface are treated in NB10 — coni-LCS limit and PFV pipeline and NB12 — Freezer.Prerequisites: NB02 — JAXVacua overview and NB03 — CYTools interface for CYTools-derived examples.
Outline#
Setup#
import warnings, time, math
from functools import partial
import numpy as np
from scipy.optimize import root
import jax
from jax import jit
import jax.numpy as jnp
jax.config.update("jax_enable_x64", True)
import matplotlib.pyplot as plt
import seaborn as sn
cmap = sn.color_palette("viridis", as_cmap=True)
import jaxvacua as jvc
warnings.filterwarnings("ignore")
Section-specific fixtures. Each limit section uses the model that best illustrates the corresponding feature:
LCS — the degree-18 hypersurface in \(\mathbb{CP}^{1,1,1,6,9}\) (
h12=2, model_ID=1), the standard test geometry already used in NB05/NB07/NB08.coni-LCS overview — a CYTools-built polytope with conifold curve $C_{ m cf} = [-1,1,0]`. Here we identify the conifold data and explain what JAXVacua needs; the detailed approximation schemes are deferred to NB10.
Custom inputs — a minimal analytic prepotential and period-vector examples, plus a K-point boundary example that connects this interface to the built-in hypergeometric models in NB11.
The shared setup imports jaxvacua as jvc; each downstream section builds its own model because different limits require different input data.
LCS limits#
Introduction#
At Large Complex Structure (LCS), the prepotential can be expressed as
Here, the polynomial part \(F_{\mathrm{poly}}\) of the LCS prepotential \(F_{\mathrm{LCS}}\) can be expressed in terms of the periods \(X^I=(X^0,X^i)\) as
Here, \(\widetilde{\kappa}_{ijk}\) are the triple intersection numbers of the mirror dual Calabi-Yau threefold \(\widetilde{X}\). Here, we defined
The instanton part \(F_{\mathrm{inst}}\) of the LCS prepotential \(F_{\mathrm{LCS}}\) can be expressed in terms of the periods \(X^I=(X^0,X^i)\) as
Here the sum is performed over all effective curve classes \(q\in\mathcal{M}(\widetilde{X})\) in the Mori cone \(\mathcal{M}(\widetilde{X})\) of the mirror dual manifold \(\widetilde{X}\). Here, the \(n_q^{0}\) are the genus-0 Gopakumar-Vafa (GV) invariants which can be computed systematically using methods described in hep-th/9308122.
The infinite sum appearing in the polylogarithm \(\text{Li}_3\) can be rewritten to arrive at
in terms of genus-0 Gromov-Witten (GW) invariants \(N_q\). We typically work with the latter to simplify the calculation.
The above functions are implemented in the periods class and are selected automatically when we specify limit="LCS".
Example at \(h^{1,2} = 2\) - \(\mathbb{CP}[1,1,1,6,9]\)#
We load a pre-built LCS model using stored topological data. The key constructor parameters are:
h12: number of complex structure moduli \(h^{1,2}\)model_ID: selects among the pre-stored models of the given type andh12value (here the defaultmodel_type="KS"refers to models in the Kreuzer–Skarke database)limit="LCS": activates the LCS prepotential (implemented in theperiodsclass), which evaluates \(F = F_{\rm poly} + F_{\rm inst}\)maximum_degree: truncates the GV instanton sum at curves up to this total degree;maximum_degree=1keeps only the two leading worldsheet corrections, which is sufficient for a quick benchmark but shifts the minimum compared to the fully converged result
model = jvc.FluxVacuaFinder(h12=2,model_ID=1,maximum_degree=1, limit="LCS")
model
This is the \(\mathbb{CP}^4_{[1,1,1,6,9]}\) hypersurface discussed in 1912.10047. The topological data is accessible through the lcs_tree attribute. We can verify that the intersection numbers, the \(a\)-matrix \(a_{ij}\), and the \(b\)-vector \(b_i\) match the expected values:
model.lcs_tree.intnums,model.lcs_tree.a_matrix,model.lcs_tree.b_vector
JAXVacua uses the genus-0 Gromov–Witten (GW) invariants \(N_q\) to evaluate \(F_{\rm inst}\) (obtained from the GV invariants \(n_q^0\) via \(N_q = \sum_{d|q} n_{q/d}^0 / d^3\)). With maximum_degree=1, only the two simplest curve classes on the mirror contribute:
\(q = (1,0)\): GV invariant \(n_q^0 = 540\)
\(q = (0,1)\): GV invariant \(n_q^0 = 3\)
gv_invariants holds the values \(n_q^0\) and gv_charges holds the corresponding curve class vectors \(q_i\). Including higher-degree curves (e.g. maximum_degree=2 adds \(q = (1,1), (2,0), (0,2), \ldots\)) shifts the vacuum by a sub-leading correction — typically affecting only the 3rd significant digit for maximum_degree \(\geq 2\).
model.lcs_tree.gv_invariants,model.lcs_tree.gv_charges
As a concrete example, we target the small-\(|W_0|\) minimum from 2501.03984 (Eq. 4.11). The initial moduli and flux values are taken from the paper. Note that [2501.03984] used a higher GV truncation degree; with our maximum_degree=1 the Newton solver will converge to the nearby minimum of the truncated model, which differs slightly from the paper’s exact vacuum.
z0 = jnp.array([0.5+ 2.36817528j, 0.5+ 2.51175911j])
fluxes = jnp.array([4, 12, 2, -1, 0, -1, 36, -1, 0, 0, 1, -1.])
tau0 = 0.5+1j*1.48121567
At these starting values (taken from a higher-degree GV model), the \(F\)-term residuals are \(O(1)\) — the initial guess is far from the maximum_degree=1 vacuum:
model.DW(z0,jnp.conj(z0),tau0,jnp.conj(tau0),fluxes)
We refine numerically using newton_method_flux_vacua. Key parameters:
step_size_Newton=1: full Newton step; gives quadratic convergence near the minimum (reduce to ~0.1 for non-SUSY vacua where the basin of attraction may be small)tol=1e-12: terminates when \(\max_I |D_I W| < 10^{-12}\)mode="SUSY": solves the system \(D_I W = 0\) (supersymmetric vacuum); usemode="non-SUSY"to minimise \(|\partial_i V|^2\) insteadsolver_mode="complex": works in the complex moduli parameterisation (more efficient for SUSY vacua than the real-variable solver)
(Note: the first call triggers jax.jit compilation, which can take a few seconds.)
moduli,tau,residual = model.newton_method_flux_vacua(z0,tau0,fluxes,step_size_Newton = 1,tol=1e-12,max_iters=100,mode="SUSY",solver_mode="complex")
print(f"|z-z0|: {np.abs(z0-moduli)} |tau-tau0|: {np.abs(tau0-tau)} |DW|: {residual}")
Newton’s method has converged to the maximum_degree=1 minimum. The moduli are shifted from the paper’s starting values because the truncated GV sum changes the location of the minimum. The \(F\)-term conditions are now satisfied to machine precision:
model.DW(moduli,jnp.conj(moduli),tau,jnp.conj(tau),fluxes)
Newton convergence#
newton_method_flux_vacua internally works in the real representation: _convert_complex_to_real(z, z̄, τ, τ̄) packs the real and imaginary parts of \((z, \tau)\) into a flat real array x ∈ ℝ^{2(h^{1,2}+1)}, and DW_x(x, flux) evaluates all \(\{{\rm Re}(D_I W), {\rm Im}(D_I W)\}\) as a flat real vector. The Jacobian dDW_x is computed analytically via JAX. We can run the Newton step manually to visualise the (quadratic) convergence of \(\max_I|D_I W|\):
# Manual Newton iteration to track convergence
x = model._convert_complex_to_real(z0, jnp.conj(z0), tau0, jnp.conj(tau0))
n_iters = 12
residuals = []
for _ in range(n_iters):
dw = model.DW_x(x, fluxes)
residuals.append(float(jnp.max(jnp.abs(dw))))
J = model.dDW_x(x, fluxes)
x = x - jnp.linalg.solve(J, dw)
fig, ax = plt.subplots(figsize=(5.5, 3.5))
ax.semilogy(range(n_iters), residuals, "o-", color="steelblue", linewidth=2, markersize=5)
ax.set_xlabel("Newton iteration")
ax.set_ylabel(r"$\max_I\,|D_I W|$")
ax.set_title(r"Newton convergence — LCS example ($h^{1,2}=2$)")
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()
We extract the physical observables at the vacuum:
tadpole(fluxes)returns the D3-brane tadpole contribution \(N_{\rm flux} = \tfrac{1}{2}\int H_3 \wedge F_3\)superpotential_gauge_invariantcomputes \(W_0 = W/X^0\) in the gauge-invariant normalisation (divided by the period \(X^0\), making it independent of the overall period normalisation); this is the quantity directly compared to literature values
Nflux = model.tadpole(fluxes)
W = model.superpotential_gauge_invariant(moduli,tau,fluxes)
print(f"Nflux: {Nflux} |W0|: {np.abs(W)} ")
This matches the value \(|W_0|\approx 5.5\times 10^{-5}\) stated in Eq. (4.11) in 2501.03984. Note that small deviations are noticeable here because we are only using the leading order GVs. Sub-leading corrections shift the minimum and the value of \(W_0\) slightly, but affecting the 3rd significant digit.
coni-LCS limits#
Introduction#
To construct a Klebanov-Strassler throat region in a flux compactification, we must stabilise the complex structure moduli near a conifold locus. To do so, we work near a boundary of moduli space where a conifold curve shrinks following the approach of 2009.03312, see also 2009.03325 for similar results. Here we will just sketch the construction, referring the reader to the original work for more details.
A conifold singularity is a locus in the complex structure moduli space of a Calabi-Yau threefold \(X\) where a set of \(n_{\mathrm{cf}}\) three-cycles, all of which lie in the same homology class \([\mathcal{C}] \in H_3(X,\mathbb{Z})\), shrink to zero volume. In an LCS patch, we can identify the complex structure moduli space of \(X\) with the complexified K”ahler cone \(\mathcal{K}(\widetilde{X})\) of the mirror threefold \(\widetilde{X}\). In this picture, the conifold locus is identified with the facet of \(\mathcal{K}(\widetilde{X})\) where a fixed set of curves \(\mathcal{C}_{\mathrm{cf}}\) in some effective curve class \(\tilde{\mathbf{q}}^{\mathrm{cf}} \in \mathcal{M}(\widetilde{X})\cap H_2(\widetilde{X},\mathbb{Z})\), which we call the conifold class, shrink to zero size. We will defer discussion of how to actually find conifolds and how to compute \(n_{\mathrm{cf}}\) to 2406.13751, and for now just assume the existence of some conifold class \(\tilde{\mathbf{q}}^{\mathrm{cf}}\). The volume of the curves \(\mathcal{C}_{\mathrm{cf}}\) is measured by the absolute value of
which we will refer to as the conifold modulus. Denoting by \({\omega^a}_\alpha\) the generators of the lattice orthogonal to \(\tilde{\mathbf{q}}^{\mathrm{cf}}\), it will be useful to parameterise moduli space according to
with \(a \in 1,\ldots,h^{2,1}\) and \(\alpha \in 2,\ldots,h^{2,1}\), in terms of the conifold modulus \(z_{\mathrm{cf}}\) and the bulk moduli \(z^\alpha\). Here, \(\xi^a\) is an arbitrary constant vector satisfying \(\tilde{\mathbf{q}}^{\mathrm{cf}}_a\xi^a=1\).
In the following, it is convenient to work in a basis where \(z_{\text{cf}}=z^1\) and \(z_{\text{bulk}}^\alpha = z^\alpha\) for \(\alpha=2,\ldots,h^{1,2}\). The prepotential can then be written as an expansion in powers of \(z_{\text{cf}}\) as (see 2009.03312)
where the higher order terms are obtained from
in terms of the polynomial prepotential
The prepotential \(F_{\mathrm{coni-LCS}}(z)\) can be computed to any order in the expansion around \(|z_{\text{cf}}|\ll 1\). For reasonably large \(|z_{\text{cf}}| \gtrsim 10^{-5}\), it can be beneficial to check the full theory by taking the LCS prepotential without using Euler’s reflection formula for the 3rd polylogarithm.
Coni-LCS limits from CYTools#
We study the original example of 2009.03312 with \(h^{1,1}=99\) and \(h^{1,2}=3\). The mirror dual CY can be defined as follows
from cytools import Polytope
poly = Polytope([[-1,3,-2,-1],[1,-1,0,0],[-1,0,0,1],[-1,0,0,0],[-1,0,1,1],[-1,0,2,0],[-1,0,1,0]])
cy = poly.triangulate().get_cy()
cy
To find the conifold curve, we compute GV invariants to high degree and search for nilpotent rays: effective curve classes \(\mathbf{q}\) such that \(n_\mathbf{q}^0 \neq 0\) but \(n_{m\mathbf{q}}^0 = 0\) for all multiples \(m \geq 2\). A nilpotent ray signals a rational curve that does not have multi-cover contributions — physically, it shrinks to a point (a conifold singularity) as the corresponding Kähler modulus of the mirror \(\widetilde{X}\) is taken to zero. The number of shrinking three-cycles is \(n_{\rm cf} = n_\mathbf{q}^0 / 2\).
gvs = cy.compute_gvs(max_deg=20)
grading_vector = gvs.grading_vec
gvs = gvs.dok
keys = gvs.keys()
curve_charges = np.array(list(keys))
GV_invariants = np.array(list(gvs.values()))
The nilpotent curve search: for each degree-1 curve (innermost Mori cone generator), check that all integer multiples \(m\mathbf{q}\) for \(m = 2, \ldots, 19\) have vanishing GV invariant. We record both the curve class and the GV value \(n_\mathbf{q}^0\) (from which \(n_{\rm cf} = n_\mathbf{q}^0 / 2\) follows):
nilpotent_curves = []
for key in keys:
q = np.array(key)
degree = q@grading_vector
if degree==1:
flag=True
for i in range(2,20):
gv=gvs.get(tuple(i*q),0)
if gv!=0:
flag=False
break
if flag:
nilpotent_curves.append([q,gvs[key]])
nilpotent_curves
All three nilpotent curves coincide with the generators of the toric Mori cone (as expected for this model). The Mori cone generators are:
mori = cy.toric_mori_cone(in_basis=True)
mori.extremal_rays()
In the example of 2009.03312, the authors studied the conifold singularity arising when shrinking the curve associated with the class [-1,1,0].
Let us define this as our conifold curve:
conifold_curve = np.array([-1,1,0])
We can then find a suitable basis transformation in which this curve is represented by the class [1,0,0] by calling jvc.get_basis_change:
basis_matrix = jvc.get_basis_change(conifold_curve)
basis_matrix
Let us check that it leads to the expected result
conifold_curve@basis_matrix.T
The basis change matrix is not unique: any integer unimodular matrix \(B\) satisfying \(\tilde{\mathbf{q}}^{\rm cf} \cdot B^T = (1, 0, \ldots, 0)\) is valid. get_basis_change returns one canonical choice; another valid matrix is:
basis_matrix = np.array([[0, 1, 1], [1, 1, 0], [0, 0, 1]])
conifold_curve@basis_matrix.T
From overview to detailed coni-LCS pipelines#
At this point we have identified the data that distinguishes a coni-LCS model from an ordinary LCS model:
a conifold curve class
conifold_curve,an integer
basis_changethat puts the conifold modulus in the first coordinate,the multiplicity
ncf, anda conifold-aware period limit such as
limit="coniLCS".
The detailed approximation schemes are intentionally not expanded here. In practice there are several related workflows: the full coni-LCS prepotential, the bulk approximation in which \(z_{\rm cf}\) is integrated out, the coniLCS_series expansion, PFV initial data, and the ConifoldFreezer reduced-EFT interface. Those details make a coherent pipeline notebook of their own, so they live in NB10 — coni-LCS limit and PFV pipeline.
For the purposes of this overview, the important point is that all of these descriptions are period models: once the period/prepotential data has been supplied, the same FluxEFT and FluxVacuaFinder APIs compute \(K\), \(W\), \(D_IW\), Hessians and Newton refinements.
Custom periods and prepotentials#
This section is the most general entry point in the notebook: JAXVacua is not limited to the built-in Kreuzer-Skarke, CYTools, LCS, coni-LCS, or hypergeometric registries. If you can provide either a period vector \(\Pi(X)\) or a homogeneous prepotential \(F(X)\), the same JAXVacua machinery can evaluate \(K\), \(W\), \(D_I W\), Hessians, Newton solves, samplers and flux utilities.
This is useful in several common situations:
a local model or limiting prepotential is known analytically, but no catalogue entry exists;
a paper uses a period convention that is easier to reproduce directly than to translate into catalogue data;
one wants to compare two approximations to the same geometry, for example an LCS expansion and a local expansion near a K-point, C-point or conifold point;
periods have been computed by an external tool and should be passed to JAXVacua without adding a new model class.
The section proceeds in four stages:
Stage |
Purpose |
|---|---|
Minimal prepotential |
Show the smallest possible |
Minimal period vector |
Show the equivalent |
K-point \(X_{3,3}\) |
Demonstrate a non-LCS boundary prepotential from the literature. |
Vacuum search |
Show that custom periods can be used by |
Use prepotential_input when a homogeneous prepotential \(F(X)\) is the natural object. Use period_input when the period vector itself is known, or when no globally convenient prepotential exists in the chosen frame.
Calling conventions#
JAXVacua accepts two equivalent interfaces for custom geometries, via the constructor arguments prepotential_input and period_input.
Option A: Prepotential input — prepotential_input=F#
def F(X, conj=False):
...
return complex_scalar # JAX complex scalar
Argument |
Type |
Description |
|---|---|---|
|
|
Homogeneous coordinates \((X^0, X^1, \ldots, X^{h^{1,2}})\) |
|
|
When |
Return |
complex scalar |
The prepotential \(F(X)\), or its conjugate branch when |
conj flag guidance:
If your prepotential has only real-valued parameters, the same formula often works on conjugated inputs.
If it involves explicit complex constants, logarithm branches, or a distinguished boundary value such as a K-point rigid period \(\tau\), implement both branches explicitly. The K-point example below shows this.
From F, JAXVacua obtains the period vector by holomorphic autodiff,
Option B: Period vector input — period_input=Pi#
def Pi(X, conj=False):
...
return jnp.array([...]) # shape (2*(h12+1),)
Argument |
Type |
Description |
|---|---|---|
|
|
Homogeneous coordinates. |
|
|
Accept this keyword for compatibility with the special-geometry routines. For real coefficients it can usually be ignored, provided the function is evaluated on the already conjugated coordinates when |
Return |
|
Full period vector \(\Pi = (F_0,F_1,\ldots,X^0,X^1,\ldots)^T\). |
JAX compatibility#
Both F and Pi must be fully JAX-traceable: use jax.numpy rather than NumPy inside the callable, avoid Python branching on traced values, and keep static scalar model data in closures or explicit static arguments.
Symplectic normalisation#
The Kähler potential is
where model.periods.sigma is the symplectic matrix. A valid patch of moduli space requires \(i\Pi^\dagger\Sigma\Pi>0\). The overall period normalisation shifts \(K\) by a constant but does not change the supersymmetric equations \(D_IW=0\).
Minimal prepotential input#
We first use the toy one-modulus cubic prepotential
This is not meant as a realistic global model; it is the smallest example that exercises the prepotential_input pathway. Once the callable is supplied, FluxEFT can differentiate it with JAX and build the period vector, Kähler potential, superpotential and F-terms.
# The callable must accept homogeneous coordinates X and the optional conj flag.
def F(X, conj=False):
return X[1]**3 / X[0]
# Direct evaluation: useful as a quick consistency check before building FluxEFT.
F(jnp.ones(2) * 1j)
# No catalogue limit is passed here: the callable defines the geometry.
model = jvc.FluxEFT(h12=1, prepotential_input=F, limit=None)
model
# The affine wrapper model.F(z) evaluates the same prepotential at X=(1,z).
model.F(jnp.ones(1) * 1j)
# Because the model now has K, W and Pi, it can already evaluate F-terms.
flux = np.array([9, 10, -6, 7, -1, 1, 0, 6])
z0 = jnp.ones(1) * 1j
tau = 10 * 1j
model.DW(z0, jnp.conj(z0), tau, jnp.conj(tau), flux)
Minimal period-vector input#
The same toy geometry can be supplied directly as a period vector. This is the right interface when the periods come from a Picard-Fuchs computation, a local expansion, numerical analytic continuation, or an external code. The ordering used here is
For the cubic prepotential above, \(F_0=- (X^1)^3/(X^0)^2\) and \(F_1=3(X^1)^2/X^0\).
# Direct period-vector input in the same symplectic ordering used by JAXVacua.
def per(X, conj=False):
F0 = -X[1]**3 / X[0]**2
F1 = 3 * X[1]**2 / X[0]
return jnp.array([F0, F1, X[0], X[1]])
# Again, the callable defines the geometry; no catalogue limit is needed.
model = jvc.FluxEFT(h12=1, limit=None, model_type=None, period_input=per)
model
# The F-terms agree with the prepotential-input model because Pi came from the same F.
flux = np.array([9, 10, -6, 7, -1, 1, 0, 6])
z0 = jnp.ones(1) * 1j
tau = 10 * 1j
model.DW(z0, jnp.conj(z0), tau, jnp.conj(tau), flux)
The agreement with the previous prepotential_input example is the point of this comparison. Users can choose the representation that matches their source material without changing the downstream JAXVacua workflow.
Symplectic pairing validation#
A custom period vector should be checked before it is used in a scan. The basic diagnostic is
It should be real and positive in the physical patch. The following cell compares this direct symplectic pairing against periods.kahler_potential_per for the same period vector.
# Test at z = 1.5i (a point in the upper half-plane).
z_test = jnp.array([1.5j])
X_test = jnp.array([1.0 + 0.0j, z_test[0]])
Pi_eval = per(X_test)
sigma = model.periods.sigma
# The period-sector pairing equals exp(-K_cs).
pairing = 1j * (Pi_eval.conj() @ sigma @ Pi_eval)
K_periods = float(model.periods.kahler_potential_per(X_test, jnp.conj(X_test)).real)
K_from_pairing = -np.log(float(pairing.real))
print(f"i Π† Σ Π = {pairing:.6f}")
print(f" real part = {pairing.real:.6f} (> 0)")
print(f" imag part = {pairing.imag:.2e} (should be approximately 0)")
print()
print(f"-log(i Π† Σ Π) = {K_from_pairing:.6f}")
print(f"periods.kahler_potential = {K_periods:.6f}")
print(f"absolute difference = {abs(K_from_pairing - K_periods):.2e}")
print()
print("The full FluxEFT Kähler potential also includes the axio-dilaton term, so it requires tau and tau_c.")
Boundary prepotentials beyond LCS#
The custom interface is most useful when the relevant physics is not captured by a standard LCS expansion. A good example is a one-modulus Calabi-Yau near a non-LCS boundary point, where the period expansion contains logarithms, special constants and model-dependent boundary data.
The next cells reproduce the local K-point and C-point prepotential templates from Bastian, van de Heisteeg and Schlechter, Beyond Large Complex Structure: Quantized Periods and Boundary Data for One-Modulus Singularities, arXiv:2306.01059. NB11 wraps the same data in the higher-level HypergeometricModels registry; here we spell out the callable by hand to show exactly what prepotential_input can accept.
The helper functions below are direct JAX-callable versions of the local K-point and C-point formulae. They are intentionally written as ordinary functions: any user-derived prepotential with the same signature can be passed to FluxEFT.
# K-point and C-point local prepotential templates from arXiv:2306.01059.
# Static arguments are model constants; X remains the traced JAX input.
@partial(jit,static_argnums=(1,2,3,4,5,6,7,8,))
def prepot_Kpoint(X,delta=0,tau=0,gamma=0,c=0,B1=0,B2=0,B3=0,conj=False):
tau2 = tau.imag
if conj:
tau = jnp.conj(tau)
s = (X[1]-tau)/X[0]
F0 = delta/2+gamma*tau
if conj:
F1 = gamma-c*tau2/2/jnp.pi*(jnp.log(-1j*s/2/B1/tau2)-1)
F2 = -1j*c/4/jnp.pi**2*(jnp.log(-1j*s/2/B1/tau2)-2+B2/4/B1**2)
else:
F1 = gamma-c*tau2/2/jnp.pi*(jnp.log(1j*s/2/B1/tau2)-1)
F2 = 1j*c/4/jnp.pi**2*(jnp.log(1j*s/2/B1/tau2)-2+B2/4/B1**2)
F3 = c/16/jnp.pi/tau2*(1-B2**2/B1**4+2*B3/(3*B1**3))
return F0+s*F1+(s**2)*F2+(s**3)*F3
@partial(jit,static_argnums=(1,2,3,4,5,6,7,))
def prepot_Cpoint(X,delta=0,tau=0,gamma=0,k=0,A1=1,A2=1,conj=False):
tau2 = tau.imag
if conj:
tau = jnp.conj(tau)
s = (X[1]-tau)/X[0]
F0 = tau/2
F1 = delta-gamma*tau
if conj:
F2 = -1j*k*(3-2*jnp.log(s)+2*jnp.log(A1)) -gamma*F1
F3 = 1j*k*(3*A1**2*gamma-A2)/12/jnp.pi/A1**2
else:
F2 = 1j*k*(3-2*jnp.log(s)+2*jnp.log(A1)) -gamma*F1
F3 = -1j*k*(3*A1**2*gamma-A2)/12/jnp.pi/A1**2
return F0+s*F1+(s**2)*F2+(s**3)*F3
K-point example: \(X_{3,3}\)#
A K-point is a boundary point in a one-modulus complex-structure moduli space where the monodromy has a finite semisimple part together with a unipotent block. In the hypergeometric classification this occurs when the Picard-Fuchs exponents at \(z=\infty\) appear in two equal pairs,
The natural local coordinate is
where the rigid period \(\tau\in\mathbb{H}\) fixes the boundary value of \(X^1/X^0\).
The model \(X_{3,3}\) is the complete intersection of two cubic hypersurfaces in \(\mathbb{P}^5\). Its Picard-Fuchs exponents are \((1/3,1/3,2/3,2/3)\), so the point at \(z=\infty\) is of K-type. The rigid period is
and the instanton normalisation is fixed by the special value
of the associated weight-3 modular form. These conventions follow arXiv:2306.01059; see NB11 for the registry-based version via HypergeometricModels.build("X33", limit="Kpoint").
# Boundary data for X_{3,3} at the K-point.
tau = -1/2 + 1j * np.sqrt(3) / 2
gamma = 1/6
delta = 1/3
B1 = 1
B2 = 0
B3 = 0
c = 2
# arXiv:2306.01059 fixes the instanton normalisation through L(f,1).
import scipy
LF1 = scipy.special.gamma(1/3)**6 / (8 * np.sqrt(3) * np.pi**3)
coeff_norm = -1 / (3 * LF1**2)
# Wrap the boundary prepotential into the exact signature expected by FluxEFT.
@partial(jit, static_argnums=(1,))
def F(X, conj=False):
return prepot_Kpoint(
X,
delta=delta,
tau=tau,
gamma=gamma,
c=c,
B1=coeff_norm * B1,
B2=coeff_norm * B2,
B3=coeff_norm * B3,
conj=conj,
)
# Evaluate close to the K-point in the ordinary branch.
F(jnp.ones(2) * 1j)
# Check that the conjugate branch is available for Kähler-potential routines.
F(-jnp.ones(2) * 1j, conj=True)
The two evaluations above are not physical checks by themselves; they verify that both branches of the callable are defined and JAX-traceable. We now hand this K-point prepotential to FluxEFT. From this point onwards, the model exposes the same methods as a built-in geometry.
# Build a FluxEFT directly from the K-point prepotential.
model = jvc.FluxEFT(h12=1, limit=None, model_type=None, prepotential_input=F)
model
# Affine evaluation at X=(1,z).
model.F(jnp.ones(1) * 1j)
# The K-point model can immediately evaluate F-terms for arbitrary fluxes.
flux = np.array([9, 10, -6, 7, -1, 1, 0, 6])
z0 = jnp.ones(1) * 1j
tau = 10 * 1j
model.DW(z0, jnp.conj(z0), tau, jnp.conj(tau), flux)
Returning to the cubic toy model#
The K-point example demonstrates that genuinely non-LCS boundary data can be supplied by hand. The next two cells return to the simple cubic model to make the equivalence between period_input and prepotential_input completely explicit in a compact setting.
# Period-vector form of the cubic toy model.
def Pi(X, conj=False):
F_0 = -X[1]**3 / X[0]
F_1 = 3 * X[1]**2
return jnp.array([F_0, F_1, X[0], X[1]])
# For user-supplied periods we do not pass a catalogue limit label.
model = jvc.FluxEFT(h12=1, period_input=Pi)
model
# Prepotential form of the same cubic toy model.
def F(X, conj=False):
return -X[1]**3 / X[0]
# For user-supplied prepotentials we likewise omit a catalogue limit label.
model = jvc.FluxEFT(h12=1, prepotential_input=F)
model
Vacuum search with custom periods#
Once a FluxEFT model is built from prepotential_input or period_input, the same idea can be used in FluxVacuaFinder. The only extra ingredient needed by the sampler is minimal cone metadata: here the one-modulus physical region is simply \(\operatorname{Im} z>0\), encoded as a one-ray/one-hyperplane toy cone.
The following cells are a compact consistency check rather than a production scan. They show that a hand-written period vector can enter the FluxVacuaFinder pipeline, including sampling, linearised flux shifts and F-term checks. For a real ensemble one would tune the flux bounds, increase N, vmap_dim, max_batches, and use geometry-specific constraints.
from stringjax_tools import vmapping_func
# --- define the cubic period vector ---
def Pi_cubic(X, conj=False):
F_0 = -X[1]**3 / X[0]
F_1 = 3 * X[1]**2
return jnp.array([F_0, F_1, X[0], X[1]])
# --- build model (FluxVacuaFinder inherits FluxEFT and adds the sampler) ---
# The toy one-modulus cone is the half-line Im(z)>0. We label the
# metadata tree as LCS-like, while the Pi_cubic callable supplies the
# actual periods used below.
custom_tree = jvc.lcs_tree(
intnums=jnp.array([[[0.0]]]),
c2=jnp.array([0.0]),
hyperplanes=jnp.array([[1.0]]),
kahler_rays=jnp.array([[1.0]]),
limit="LCS",
)
model_vf = jvc.FluxVacuaFinder(
h12=1,
period_input=Pi_cubic,
lcs_tree_input=custom_tree,
limit=None,
Q=40,
)
sampler = jvc.data_sampler(model_vf,
flux_bounds=[-4, 4],
moduli_bounds=[1, 5],
axion_bounds=[-0.5, 0.5],
dilaton_bounds=[2, 10])
print(model_vf)
print(f"D3-tadpole bound Q = {model_vf.Q()}")
# --- define objective and optimiser ---
objective_fct = model_vf.DW
kwargs = {"Q": model_vf.Q(), "return_flag": True,
"constraints": None, "remove_NANs": True, "in_axes": (0, 0, 0)}
optimiser = vmapping_func(model_vf.linearised_shifts, mode="Fflux", **kwargs)
# --- run a small sampling consistency check ---
# For a production scan, increase N, vmap_dim, max_batches and
# max_resample_attempts after tuning the sampler and flux bounds.
seed = 42
rns_key = jvc.PRNGSequence(seed)
moduli, tau, fluxes, residuals = model_vf.sample_SUSY_flux_vacua(
N=1,
rns_key=rns_key,
sampler=sampler,
optimiser=optimiser,
objective_fct=objective_fct,
vmap_dim=20,
moduli_sampling_mode="box",
max_batches=1,
max_resample_attempts=5,
errors="warn",
print_progress=False,
)
print(f"\nFound {len(moduli)} SUSY vacua")
# --- inspect and validate vacua ---
if len(moduli) > 0:
W0 = model_vf.superpotential_gauge_invariant(moduli, tau, fluxes)
Nflux = model_vf.tadpole(fluxes)
DW_res = jnp.sum(jnp.abs(model_vf.DW(moduli, jnp.conj(moduli), tau, jnp.conj(tau), fluxes)), axis=1)
print(f"{'z':>25} {'τ':>22} {'|W₀|':>10} {'gₛ':>7} {'N_flux':>7} {'|DW|':>10}")
print("-" * 90)
for i in range(min(8, len(moduli))):
z_i = moduli[i][0]
t_i = tau[i]
print(f" {z_i.real:+.4f}{z_i.imag:+.4f}j "
f"{t_i.real:+.4f}{t_i.imag:+.4f}j "
f"{abs(W0[i]):.4e} "
f"{(1/t_i.imag):.4f} "
f"{int(Nflux[i]):>7} "
f"{float(DW_res[i]):.2e}")
if len(moduli) > 8:
print(f" ... ({len(moduli) - 8} more)")
else:
print("No vacua found in this bounded consistency check. For a real scan, increase N/vmap_dim/max_batches or widen sampler bounds.")
Take-aways#
LCS is the canonical Type IIB limit. The prepotential contains a cubic polynomial plus exponentially suppressed worldsheet instantons; the cubic dominates deep in the Kähler cone.
coni-LCS limits need extra geometric data. JAXVacua must know which curve shrinks, which basis isolates the conifold modulus, and the number of shrinking cycles
ncf.Detailed conifold approximations live in NB10. The full coni-LCS, bulk, series, PFV and freezer workflows are closely related and are easier to understand as one pipeline.
Custom periods and prepotentials are first-class inputs. Pass
prepotential_input=Fwhen \(F(X)\) is known analytically, orperiod_input=Piwhen periods come from another calculation.NB11 is the built-in one-modulus counterpart. The hypergeometric registry provides curated LCS, K-point and C-point models that use the same boundary-period philosophy without requiring users to write the functions by hand.
Validation matters. Check JAX traceability, conjugate branches and the symplectic pairing before running scans with custom period data.
Further reading#
NB10 — coniLCS pipeline — detailed conifold approximation, PFV and freezer workflows
NB11 — Hypergeometric models — registry-based K-point and C-point models
NB12 — Freezer — reduced EFTs after integrating out heavy moduli
Bastian, van de Heisteeg and Schlechter, Beyond Large Complex Structure: Quantized Periods and Boundary Data for One-Modulus Singularities, arXiv:2306.01059
JAXVacua framework paper, arXiv:2306.06160
arXiv:2404.12422 (Hulek-Verrill), arXiv:1903.00596 (PFV mechanism)