Skip to content

Approximation of Laplacian zero modes¤

In this example notebook, the second in of a pair, we consider a complex manifold \(X\), and approximate the zero modes of the Laplacian \(\Delta_g\) on \(X\) by optimisation of the natural associated variational problem. One may regard this as an example of how to use the approximate metric to study the geometry of \(X\).

The manifold \(X\) is the same Calabi-Yau threefold defined in the previous example, where we found an approximation to the unique Ricci-flat metric tensor on \(X\). We will use the points sampled and optimsed parameters saved by the previous example.

import jax
from jax import random
import jax.numpy as jnp

import os, time
import numpy as np

from functools import partial

from cymyc import dataloading
from cymyc.utils import gen_utils as utils

Motivation¤

This will be a brief overview with technical details smoothed over for the ease of exposition - for the full story, please consult this article.

We are interested in differential forms on the Calabi-Yau, \(\eta \in \Omega^1(X)\), which are annihilated by the Laplacian on \(X\), $$ \Delta_g \eta = 0~.$$

Evaluation of the Laplacian requires the metric on \(X\) - which makes sense, as eigenmodes on manifolds should tell you about geometry! For physical reasons, in string compactification scenarios, we are interested in eigenmodes which are harmonic w.r.t. the Ricci-flat metric. These correspond to observable physical matter fields in string compactifications scenarios.

With harmonic forms in hand, we may predict the masses and strengths with which these particles interact at low energies. The end goal is to predict if a given 'string model' (of which there are exceptionally many) recovers a quantum field theory with properties close to our universe at low energies.

Load data and metric checkpoint¤

We load the points sampled from \(X\) in the previous notebook, as well as the parameters for the approximate Ricci-flat metric.

class args(object):
    # specify training config. For more options, see `src/approx/default_config`
    name = "X33_demo_harmonic"
    learning_rate = 1e-4
    n_epochs = 24
    dataset = "data/X33_demo/"
    metric_checkpoint = "experiments/X33_demo/X33_demo_epoch_FIN_2024_10_28_14:28_PARAMS.pkl" # replace this with your checkpoint
    batch_size = 1024
    n_units_harmonic = [64, 64, 128, 64, 42]

# Override default arguments from config file with provided command line arguments
from cymyc.approx.default_config import config
config = utils.override_default_args(args, config)
config = utils.read_metadata(config)  # load dataset metadata

np_rng = np.random.default_rng()
data_train, data_val, train_loader, val_loader, psi = dataloading.initialize_loaders_train(
    np_rng      = np_rng,
    data_path   = os.path.join(config.dataset, "dataset.npz"),
    batch_size  = config.batch_size)
Saving config file to experiments/X33_demo_harmonic/X33_demo_harmonic_METADATA.pkl
Dataset size: (400000, 12), kappa: 0.0393404
Vol[g]: 0.0277778, Vol[Ω]: 0.7060880

from cymyc.approx import models

metric_model_class = models.LearnedVector_spectral_nn_CICY
metric_model = metric_model_class(config.n_ambient_coords, config.ambient, config.n_units)

seed = int(time.time()) # 42
rng = random.PRNGKey(seed)
rng, init_rng = random.split(rng)

_params, init_rng = utils.random_params(init_rng, metric_model, data_dim=config.n_ambient_coords * 2)
metric_params = utils.load_params(_params, config.metric_checkpoint)  # parameters for trained metric NN
Compiling LearnedVector_spectral_nn.spectral_layer.

Harmonic model ansatz¤

Our variational ansatz \(\tilde{\eta}_{\lambda}\) is obtained as an \(\overline{\partial}\)-exact correction from some easily computable reference form \(\phi \in H^1(T_X)\), and the natural variational objective to minimise is the Laplacian itself,

\[\tilde{\eta}(\cdot; \lambda) = \phi + \bar{\partial} \mathfrak{s}(\cdot; \lambda), \quad \lambda = \textsf{argmin}_{\lambda' \in \Lambda} \Delta_g \tilde{\eta}(\cdot; \lambda').\]

Here \(\mathfrak{s}\) is a section of the (holomorphic) tangent bundle over \(X\). The approximation problem thus reduces to finding a way to model a section of the tangent bundle \(\mathfrak{s} \in \Gamma(T_X)\) using a parameterised function - this is far from obvious on a manifold with nontrivial topology!

The core idea is to construct a basis of sections \(\{ \mathbf{e}^i \}_i\) of \(T_X\), and to take \(\mathfrak{s}\) to be a linear combination of the basis elements, with the coefficients parameterised by a vector-valued globally defined function \(\psi\): $$ \mathfrak{s} = \sum_{\mu} \psi_{\mu} \mathbf{e}^{\mu} ~.$$ We again we leave the full story to this article, but note this is crucial to ensure that \(\tilde{\eta}\) is a bona-fide geometrical object globally defined over \(X\) - one gets nonsensical answers if the ansatz does not respect the topology of \(X\).

Note: The objective requires taking the third derivative of the neural network modelling the metric, so this example runs significantly faster on a GPU.

from flax import linen as nn
from functools import partial

from cymyc.approx import harmonic, eta_train
# initialize model
eta_model_class = models.CoeffNetwork_spectral_nn_CICY
eta_model = eta_model_class(
    dim         = config.n_ambient_coords,
    ambient     = config.ambient,
    n_units     = config.n_units_harmonic,
    activation  = nn.gelu)

t0 = time.time()
rng, init_rng = random.split(rng)
logger = utils.logger_setup('X33_demo_harmonic', filepath=os.path.abspath(''))
logger.info(eta_model.tabulate(init_rng, jnp.ones([1, config.n_ambient_coords * 2])))
14:30:40 INFO - logger_setup: /home/jt796/github/cymyc/docs/examples
14:30:40 INFO - <module>: 
                     CoeffNetwork_spectral_nn_CICY Summary                      
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ path           module         inputs         outputs        params       ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│               │ CoeffNetwork… │ float32[1,12] │ -             │              │
│               │               │               │ complex64[1,… │              │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_0      │ Dense         │ float32[36]   │ float32[64]   │ bias:        │
│               │               │               │               │ float32[64]  │
│               │               │               │               │ kernel:      │
│               │               │               │               │ float32[36,… │
│               │               │               │               │              │
│               │               │               │               │ 2,368 (9.5   │
│               │               │               │               │ KB)          │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_1      │ Dense         │ float32[64]   │ float32[64]   │ bias:        │
│               │               │               │               │ float32[64]  │
│               │               │               │               │ kernel:      │
│               │               │               │               │ float32[64,… │
│               │               │               │               │              │
│               │               │               │               │ 4,160 (16.6  │
│               │               │               │               │ KB)          │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_2      │ Dense         │ float32[64]   │ float32[128]  │ bias:        │
│               │               │               │               │ float32[128] │
│               │               │               │               │ kernel:      │
│               │               │               │               │ float32[64,… │
│               │               │               │               │              │
│               │               │               │               │ 8,320 (33.3  │
│               │               │               │               │ KB)          │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_3      │ Dense         │ float32[128]  │ float32[64]   │ bias:        │
│               │               │               │               │ float32[64]  │
│               │               │               │               │ kernel:      │
│               │               │               │               │ float32[128… │
│               │               │               │               │              │
│               │               │               │               │ 8,256 (33.0  │
│               │               │               │               │ KB)          │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_4      │ Dense         │ float32[64]   │ float32[42]   │ bias:        │
│               │               │               │               │ float32[42]  │
│               │               │               │               │ kernel:      │
│               │               │               │               │ float32[64,… │
│               │               │               │               │              │
│               │               │               │               │ 2,730 (10.9  │
│               │               │               │               │ KB)          │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│ layers_coeffs │ EinsumComplex │ float32[42]   │ complex64[1,… │ bias:        │
│               │               │               │               │ float32[1,1… │
│               │               │               │               │ im_kernel:   │
│               │               │               │               │ float32[42,… │
│               │               │               │               │ re_kernel:   │
│               │               │               │               │ float32[42,… │
│               │               │               │               │              │
│               │               │               │               │ 26,775       │
│               │               │               │               │ (107.1 KB)   │
├───────────────┼───────────────┼───────────────┼───────────────┼──────────────┤
│                                                      Total  52,609       │
│                                                             (210.4 KB)   │
└───────────────┴───────────────┴───────────────┴───────────────┴──────────────┘
                                                                                
                      Total Parameters: 52,609 (210.4 KB)                       



Compiling LearnedVector_spectral_nn.spectral_layer.
CoeffNetwork_spectral_nn_CICY.__call__, coeff shape, (1, 15, 21)

For the particular manifold we consider, there is one unique harmonic one-form, as \(h^{(2,1)} = 1\). Topological considerations mean the number of harmonic one-forms is equal to the number of independent ways we can deform the defining polynomials while remaining on the zero locus. From the definition of the manifold as the intersection of zero loci in \(\mathbb{P}^5\), there is only one way to do this,

\[ B_{\psi} = \left\{\begin{array}{c}Z_0^3 + Z_1^3 + Z_2^3 - 3 \psi Z_3 Z_4 Z_5 = 0\\ Z_3^3 + Z_4^3 + Z_5^3 - 3 \psi Z_0 Z_1 Z_2 = 0\end{array} \, : \, \psi \in \mathbb{C}\right\} \subset \mathbb{P}^5~. \]

The single complex structure moduli direction corresponds to the trilinear polynomial deformations above, and we can write down this deformation explicitly. We also need to calculate some complex structure data associated with the manifold.

There's too much information to comfortably carry around as arguments to functions used in optimisation. To remedy this, we wrap everything up into an appropriately filtered class - harmonic_wp, whose methods are compatible with Jax transformations.

from cymyc import alg_geo
from examples import poly_spec

def X33_deformation(p, precision=np.complex128):
    d1 = jnp.einsum("...a,aj-&gt;...j", jnp.expand_dims(p[3]*p[4]*p[5], axis=-1),
                      jnp.asarray([[-3.,0.]], precision))
    d2 = jnp.einsum("...a,aj-&gt;...j", jnp.expand_dims(p[0]*p[1]*p[2], axis=-1),
                      jnp.asarray([[0.,-3.]], precision))
    return d1 + d2

def _X33_coefficients(psi):
    coefficients = [jnp.append(jnp.ones(3), -3.0*psi), jnp.append(jnp.ones(3), -3.0*psi)]
    return coefficients

monomials, cy_dim, kmoduli, ambient = poly_spec.X33_spec()
coefficients = _X33_coefficients(psi)

dQdz_info = [alg_geo.dQdz_poly(config.n_ambient_coords, m, c) for (m,c) in zip(monomials, coefficients)]
dQdz_monomials, dQdz_coeffs = list(zip(*dQdz_info))
config.dQdz_monomials = dQdz_monomials
config.dQdz_coeffs = dQdz_coeffs
g_FS_fn, g_correction_fn, pb_fn = models.helper_fns(config)
# full transformation-compatible closure
metric_fn = jax.tree_util.Partial(models.ddbar_phi_model, params=metric_params, 
                                  g_ref_fn=g_FS_fn, g_correction_fn=g_correction_fn)
harmonic_wp = harmonic.HarmonicFull(cy_dim, monomials, ambient, [X33_deformation], dQdz_monomials,
                                dQdz_coeffs, metric_fn, pb_fn, _X33_coefficients, psi)

Optimisation loop for harmonic zero modes¤

This is again fairly standard - note Jax is more bare-metal than other libraries, so we write the looping logic ourselves.

jit compilation introduces a delay the first time the train_step function is called, but executes quickly when called subsequently. We pay an initial up-front cost for compilation of Python functions into a form efficiently executable by an accelerator, which will be repaid during the execution itself.

import time, logging
import optax

from tqdm import tqdm
from collections import defaultdict

optimizer = optax.chain(
        optax.clip_by_global_norm(1.0),
        optax.adamw(config.learning_rate))
params, opt_state, init_rng = eta_train.create_train_state(init_rng, eta_model, optimizer, data_dim=config.n_ambient_coords * 2)

storage = defaultdict(list)

try:
    device = jax.devices('gpu')[0]
except:
    device = jax.devices('cpu')[0]
Compiling LearnedVector_spectral_nn.spectral_layer.
CoeffNetwork_spectral_nn_CICY.__call__, coeff shape, (1, 15, 21)

with jax.default_device(device):
    logger.info(f"Running on {device}")

    for epoch in range(config.n_epochs):
        val_loader, val_data = dataloading.get_validation_data(val_loader, config.batch_size, data_val, np_rng)
        storage = eta_train.callback(harmonic_wp.loss_breakdown, epoch, t0, 0, val_data, params, config, storage, logger, mode='VAL')

        if epoch &gt; 0:
            train_loader = dataloading.data_loader(data_train, config.batch_size, np_rng)

        train_loader_it = tqdm(train_loader, desc=f"Epoch: {epoch}", total=data_train[0].shape[0]//config.batch_size,
                               colour='green', mininterval=0.1)
        for t, data in enumerate(train_loader_it):
            params, opt_state, loss = eta_train.train_step(data, params, opt_state, harmonic_wp.objective_function, optimizer)
            train_loader_it.set_postfix_str(f"loss: {loss:.5f}", refresh=False)

utils.basic_ckpt(params, opt_state, config.name, 'FIN')
utils.save_logs(storage, config.name, 'FIN')
14:30:49 INFO - <module>: Running on cuda:0

Compiling ddbar_phi_model
Compiling phi_head
Compiling LearnedVector_spectral_nn.spectral_layer.
Compiling HarmonicFull.objective_function
Compiling HarmonicFull.del_bar_zeta_complete

/tmp/ipykernel_900602/1695235459.py:6: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[-3.,0.]], precision))
/tmp/ipykernel_900602/1695235459.py:8: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[0.,-3.]], precision))

Compiling coeff_head
['layers_0', 'layers_1', 'layers_2', 'layers_3', 'layers_4', 'layers_coeffs']
Compiling LearnedVector_spectral_nn.spectral_layer.
CoeffNetwork_spectral_nn_CICY.__call__, coeff shape, (1, 15, 21)

/tmp/ipykernel_900602/1695235459.py:6: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[-3.,0.]], precision))
/tmp/ipykernel_900602/1695235459.py:8: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[0.,-3.]], precision))

HarmonicFull.objective_function, codiff shape (1024, 1, 3)
Compiling HarmonicFull.harmonic_rep_breakdown (1, 3, 6) (1, 5, 6) (1, 3, 3)

/home/jt796/github/cymyc/cymyc/fubini_study.py:136: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in zeros is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  g_FS = jnp.zeros((n_coords, n_coords), dtype=cdtype)
/home/jt796/github/cymyc/cymyc/fubini_study.py:75: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in eye is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  delta_mn = jnp.eye(complex_dim, dtype=cdtype)
/home/jt796/dev/lib/python3.10/site-packages/jax/_src/numpy/array_methods.py:68: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in astype is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  return lax_numpy.astype(arr, dtype, copy=copy, device=device)
/tmp/ipykernel_900602/1695235459.py:6: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[-3.,0.]], precision))
/tmp/ipykernel_900602/1695235459.py:8: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[0.,-3.]], precision))

Compiling HarmonicFull.zeta_jacobian_complete
HarmonicFull.loss_breakdown: (1024, 1, 3, 3), (1024, 1, 3, 3), (1024, 1, 3, 3)
HarmonicFull.loss_breakdown: (1, 1), (1, 1)
HarmonicFull.loss_breakdown: (1, 1), (1, 1)
Harmonic.section_network_transformed, basis_form shape (6, 6, 3)
Harmonic.section_network_transformed, O2 shape (6, 6)
HarmonicFull.transition_loss: (4, 1, 3), (4, 1, 3)
HarmonicFull.transition_loss: (4, 3, 3)
HarmonicFull.transition_loss: (4, 1)

14:31:15 INFO - callback: [35.0s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62671+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.00045-0.00000j | G_WP_CY: 0.62649+0.00000j | G_WP_KS: 0.58940-0.00000j | G_WP_bundle: 0.62025-0.00000j | codiff_mean: 2.27620 | cup_product: 0.62649+0.00000j | loss: 1.65997 | polarisation: 0.01561 | ratio (cy/bundle): 1.01005+0.00000j | symmetry: 0.00149 | transition_loss: 0.00002 | σ_measure: 0.01715
Epoch: 0:   0%|                                                                                                                                                                                                                                       | 0/390 [00:00<?, ?it/s]
Compiling train_step
Compiling HarmonicFull.objective_function
HarmonicFull.objective_function, codiff shape (1024, 1, 3)

Epoch: 0: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:32<00:00, 11.99it/s, loss: 1.32860]
14:31:47 INFO - callback: [67.6s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62566-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02046-0.00000j | G_WP_CY: 0.60843-0.00000j | G_WP_KS: 0.58757-0.00000j | G_WP_bundle: 0.60056-0.00000j | codiff_mean: 1.77433 | cup_product: 0.60843-0.00000j | loss: 1.32885 | polarisation: 0.01100 | ratio (cy/bundle): 1.01310+0.00000j | symmetry: 0.00105 | transition_loss: 0.00005 | σ_measure: 0.01861
Epoch: 1: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.61it/s, loss: 1.33671]
14:32:02 INFO - callback: [82.3s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63023-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02211+0.00000j | G_WP_CY: 0.60896-0.00000j | G_WP_KS: 0.59290-0.00000j | G_WP_bundle: 0.59967-0.00000j | codiff_mean: 1.70031 | cup_product: 0.60896-0.00000j | loss: 1.27186 | polarisation: 0.00991 | ratio (cy/bundle): 1.01550+0.00000j | symmetry: 0.00094 | transition_loss: 0.00005 | σ_measure: 0.01998
Epoch: 2: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.55it/s, loss: 1.27861]
14:32:17 INFO - callback: [97.0s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.64637-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02328-0.00000j | G_WP_CY: 0.61311+0.00000j | G_WP_KS: 0.60882-0.00000j | G_WP_bundle: 0.60562+0.00000j | codiff_mean: 1.63999 | cup_product: 0.61311+0.00000j | loss: 1.22227 | polarisation: 0.00982 | ratio (cy/bundle): 1.01236-0.00000j | symmetry: 0.00095 | transition_loss: 0.00005 | σ_measure: 0.01776
Epoch: 3: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.41it/s, loss: 1.16261]
14:32:32 INFO - callback: [111.8s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.64294-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02476-0.00000j | G_WP_CY: 0.61118+0.00000j | G_WP_KS: 0.60449-0.00000j | G_WP_bundle: 0.60440-0.00000j | codiff_mean: 1.65331 | cup_product: 0.61118+0.00000j | loss: 1.23758 | polarisation: 0.01364 | ratio (cy/bundle): 1.01122+0.00000j | symmetry: 0.00132 | transition_loss: 0.00005 | σ_measure: 0.01849
Epoch: 4: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.39it/s, loss: 1.13474]
14:32:46 INFO - callback: [126.6s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63438+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02462+0.00000j | G_WP_CY: 0.60475-0.00000j | G_WP_KS: 0.60065-0.00000j | G_WP_bundle: 0.59778-0.00000j | codiff_mean: 1.51980 | cup_product: 0.60475-0.00000j | loss: 1.13862 | polarisation: 0.01556 | ratio (cy/bundle): 1.01166+0.00000j | symmetry: 0.00148 | transition_loss: 0.00005 | σ_measure: 0.01814
Epoch: 5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.40it/s, loss: 1.11696]
14:33:01 INFO - callback: [141.4s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63007+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02768+0.00000j | G_WP_CY: 0.59541+0.00000j | G_WP_KS: 0.59187+0.00000j | G_WP_bundle: 0.59020+0.00000j | codiff_mean: 1.50262 | cup_product: 0.59541+0.00000j | loss: 1.12339 | polarisation: 0.01581 | ratio (cy/bundle): 1.00882-0.00000j | symmetry: 0.00151 | transition_loss: 0.00005 | σ_measure: 0.01806
Epoch: 6: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.32it/s, loss: 1.16596]
14:33:16 INFO - callback: [156.2s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63916-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02702-0.00000j | G_WP_CY: 0.60619-0.00000j | G_WP_KS: 0.60372-0.00000j | G_WP_bundle: 0.59950-0.00000j | codiff_mean: 1.48826 | cup_product: 0.60619-0.00000j | loss: 1.11567 | polarisation: 0.01515 | ratio (cy/bundle): 1.01116+0.00000j | symmetry: 0.00145 | transition_loss: 0.00005 | σ_measure: 0.01789
Epoch: 7: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.25it/s, loss: 1.07817]
14:33:31 INFO - callback: [171.1s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62881+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02891-0.00000j | G_WP_CY: 0.59561-0.00000j | G_WP_KS: 0.59570-0.00000j | G_WP_bundle: 0.59026-0.00000j | codiff_mean: 1.39257 | cup_product: 0.59561-0.00000j | loss: 1.04637 | polarisation: 0.01338 | ratio (cy/bundle): 1.00907+0.00000j | symmetry: 0.00131 | transition_loss: 0.00006 | σ_measure: 0.01760
Epoch: 8: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.29it/s, loss: 1.04522]
14:33:46 INFO - callback: [186.0s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63322-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02955-0.00000j | G_WP_CY: 0.60660+0.00000j | G_WP_KS: 0.59505+0.00000j | G_WP_bundle: 0.59911+0.00000j | codiff_mean: 1.41148 | cup_product: 0.60660+0.00000j | loss: 1.07220 | polarisation: 0.01218 | ratio (cy/bundle): 1.01251-0.00000j | symmetry: 0.00117 | transition_loss: 0.00005 | σ_measure: 0.01906
Epoch: 9: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.33it/s, loss: 1.05662]
14:34:01 INFO - callback: [200.8s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62742-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02984-0.00000j | G_WP_CY: 0.60118+0.00000j | G_WP_KS: 0.59164-0.00000j | G_WP_bundle: 0.59447-0.00000j | codiff_mean: 1.40329 | cup_product: 0.60118+0.00000j | loss: 1.06624 | polarisation: 0.01098 | ratio (cy/bundle): 1.01128+0.00000j | symmetry: 0.00106 | transition_loss: 0.00006 | σ_measure: 0.01830
Epoch: 10: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.30it/s, loss: 1.06050]
14:34:16 INFO - callback: [215.7s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63662-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03060+0.00000j | G_WP_CY: 0.61175+0.00000j | G_WP_KS: 0.59872-0.00000j | G_WP_bundle: 0.60362+0.00000j | codiff_mean: 1.42114 | cup_product: 0.61175+0.00000j | loss: 1.08489 | polarisation: 0.01045 | ratio (cy/bundle): 1.01347-0.00000j | symmetry: 0.00101 | transition_loss: 0.00005 | σ_measure: 0.01845
Epoch: 11: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.32it/s, loss: 1.07115]
14:34:30 INFO - callback: [230.6s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62945+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03060-0.00000j | G_WP_CY: 0.59642+0.00000j | G_WP_KS: 0.59091+0.00000j | G_WP_bundle: 0.58964-0.00000j | codiff_mean: 1.37652 | cup_product: 0.59642+0.00000j | loss: 1.04193 | polarisation: 0.00997 | ratio (cy/bundle): 1.01151+0.00000j | symmetry: 0.00096 | transition_loss: 0.00005 | σ_measure: 0.01769
Epoch: 12: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.31it/s, loss: 1.03597]
14:34:45 INFO - callback: [245.4s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62905-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03046+0.00000j | G_WP_CY: 0.60431-0.00000j | G_WP_KS: 0.59016-0.00000j | G_WP_bundle: 0.59812-0.00000j | codiff_mean: 1.39410 | cup_product: 0.60431-0.00000j | loss: 1.06245 | polarisation: 0.00959 | ratio (cy/bundle): 1.01036+0.00000j | symmetry: 0.00093 | transition_loss: 0.00005 | σ_measure: 0.01745
Epoch: 13: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.31it/s, loss: 1.03161]
14:35:00 INFO - callback: [260.3s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.64733-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02906+0.00000j | G_WP_CY: 0.61880-0.00000j | G_WP_KS: 0.61145+0.00000j | G_WP_bundle: 0.61192+0.00000j | codiff_mean: 1.35653 | cup_product: 0.61880-0.00000j | loss: 1.03833 | polarisation: 0.00947 | ratio (cy/bundle): 1.01123-0.00000j | symmetry: 0.00092 | transition_loss: 0.00005 | σ_measure: 0.01875
Epoch: 14: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.29it/s, loss: 1.02154]
14:35:15 INFO - callback: [275.1s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62227-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03119+0.00000j | G_WP_CY: 0.59337+0.00000j | G_WP_KS: 0.58216+0.00000j | G_WP_bundle: 0.58690-0.00000j | codiff_mean: 1.35817 | cup_product: 0.59337+0.00000j | loss: 1.03111 | polarisation: 0.00933 | ratio (cy/bundle): 1.01102+0.00000j | symmetry: 0.00090 | transition_loss: 0.00006 | σ_measure: 0.01775
Epoch: 15: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.28it/s, loss: 1.00940]
14:35:30 INFO - callback: [290.0s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63946-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03053+0.00000j | G_WP_CY: 0.61106+0.00000j | G_WP_KS: 0.60210-0.00000j | G_WP_bundle: 0.60366+0.00000j | codiff_mean: 1.36192 | cup_product: 0.61106+0.00000j | loss: 1.04222 | polarisation: 0.00969 | ratio (cy/bundle): 1.01226-0.00000j | symmetry: 0.00093 | transition_loss: 0.00006 | σ_measure: 0.01847
Epoch: 16: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.29it/s, loss: 1.04527]
14:35:45 INFO - callback: [304.9s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.63897+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02938+0.00000j | G_WP_CY: 0.60864+0.00000j | G_WP_KS: 0.59739+0.00000j | G_WP_bundle: 0.60115+0.00000j | codiff_mean: 1.34631 | cup_product: 0.60864+0.00000j | loss: 1.03394 | polarisation: 0.00955 | ratio (cy/bundle): 1.01246-0.00000j | symmetry: 0.00092 | transition_loss: 0.00005 | σ_measure: 0.01851
Epoch: 17: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.31it/s, loss: 0.99576]
14:36:00 INFO - callback: [319.7s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.64299+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03033+0.00000j | G_WP_CY: 0.61084-0.00000j | G_WP_KS: 0.60372-0.00000j | G_WP_bundle: 0.60402-0.00000j | codiff_mean: 1.31469 | cup_product: 0.61084-0.00000j | loss: 1.01105 | polarisation: 0.00958 | ratio (cy/bundle): 1.01129+0.00001j | symmetry: 0.00093 | transition_loss: 0.00005 | σ_measure: 0.01810
Epoch: 18: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.29it/s, loss: 1.02552]
14:36:14 INFO - callback: [334.6s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.65684-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02964+0.00000j | G_WP_CY: 0.62522+0.00000j | G_WP_KS: 0.62042-0.00000j | G_WP_bundle: 0.61759+0.00000j | codiff_mean: 1.32823 | cup_product: 0.62522+0.00000j | loss: 1.03414 | polarisation: 0.00955 | ratio (cy/bundle): 1.01235-0.00000j | symmetry: 0.00092 | transition_loss: 0.00005 | σ_measure: 0.01852
Epoch: 19: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.25it/s, loss: 0.94967]
14:36:29 INFO - callback: [349.5s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.65011+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.03001-0.00000j | G_WP_CY: 0.61602-0.00000j | G_WP_KS: 0.61144+0.00000j | G_WP_bundle: 0.60789-0.00000j | codiff_mean: 1.29495 | cup_product: 0.61602-0.00000j | loss: 1.00682 | polarisation: 0.00964 | ratio (cy/bundle): 1.01336+0.00000j | symmetry: 0.00093 | transition_loss: 0.00005 | σ_measure: 0.01868
Epoch: 20: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.21it/s, loss: 0.99232]
14:36:44 INFO - callback: [364.4s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.62450-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02969+0.00000j | G_WP_CY: 0.59524-0.00000j | G_WP_KS: 0.58997-0.00000j | G_WP_bundle: 0.58911-0.00000j | codiff_mean: 1.27056 | cup_product: 0.59524-0.00000j | loss: 0.97583 | polarisation: 0.00978 | ratio (cy/bundle): 1.01040+0.00000j | symmetry: 0.00094 | transition_loss: 0.00005 | σ_measure: 0.01735
Epoch: 21: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.28it/s, loss: 1.00253]
14:36:59 INFO - callback: [379.3s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.65316+0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02926-0.00000j | G_WP_CY: 0.62194+0.00000j | G_WP_KS: 0.61513-0.00000j | G_WP_bundle: 0.61363+0.00000j | codiff_mean: 1.27800 | cup_product: 0.62194+0.00000j | loss: 0.99417 | polarisation: 0.00985 | ratio (cy/bundle): 1.01354-0.00000j | symmetry: 0.00095 | transition_loss: 0.00005 | σ_measure: 0.01898
Epoch: 22: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.26it/s, loss: 0.97764]
14:37:14 INFO - callback: [394.2s]: [VAL] | Iter: 0 | (ξ,ξ) (WP): 0.66280-0.00000j | (∂-bar θ, ∂-bar θ) (WP): 0.02955-0.00000j | G_WP_CY: 0.63757-0.00000j | G_WP_KS: 0.62257-0.00000j | G_WP_bundle: 0.62755-0.00000j | codiff_mean: 1.32277 | cup_product: 0.63757-0.00000j | loss: 1.03961 | polarisation: 0.00977 | ratio (cy/bundle): 1.01598+0.00000j | symmetry: 0.00094 | transition_loss: 0.00005 | σ_measure: 0.02021
Epoch: 23: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 390/390 [00:14<00:00, 26.27it/s, loss: 1.00871]

Sanity check¤

We can plot the evolution of the Laplacian over the optimisation process to check if the resulting ansatz \(\tilde{\eta}\) is approaching something harmonic. Since the Laplacian is defined as $$\Delta_g = \frac{1}{2} \left( dd^{\dagger} + d^{\dagger}d\right)~, $$ we can study the norm of the codifferential, \(\left(d^{\dagger} \tilde{\eta}, d^{\dagger} \tilde{\eta}\right)_g\) to ensure \(\Delta_g \tilde{\eta} \approx 0\).

We can also study the degree to which the polarisation-preserving condition is violated, which follows from harmonicity of \(\eta\), and asserts \(\eta_{\overline{\alpha}\overline{\nu}} = g_{\mu \overline{\nu}} \eta^{\mu}_{\; \overline{\alpha}}\).

import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
plt.rcParams.update({'font.size': 14})

# remove if no local tex installation
plt.rcParams['text.usetex'] = True 
plt.rcParams['text.latex.preamble'] = r'\usepackage[cm]{sfmath} \usepackage{amssymb} \usepackage{mathrsfs} \usepackage{amsmath}'
plt.rcParams['font.sans-serif'] = 'cm'
fig = plt.figure(figsize=(17,7))
gs=GridSpec(1,2)
ax1=fig.add_subplot(gs[0,0])
ax2=fig.add_subplot(gs[0,1])

S = np.abs(storage['codiff_mean'])
n = 32
ax1.plot(np.arange(len(S))[:n], np.abs(S)[:n], c='royalblue')
ax1.set_xlabel(f'Epochs')
ax1.set_ylabel(r'$\int_X d\mu_{\Omega} \, \Delta_g \tilde{\eta}$')
ax1.grid(True, 'both')
# ax1.set_yscale('log')

R = storage['polarisation']
ax2.plot(np.arange(len(R))[:n], np.abs(R)[:n], c='royalblue')
ax2.set_xlabel(f'Epochs')
ax2.set_ylabel(r'Polarisation violation')
ax2.grid(True, 'both')
# ax2.set_yscale('log')
No description has been provided for this image

Yukawa coupling computation¤

For the particular class of string models we consider, we can calculate physical observables ('Yukawa couplings') via two ways:

  • A computation from deformation theory, exact up to integration error.
  • Using the approximate harmonic forms we have just optimised for.

The purpose of this experiment is really a sanity check to ensure that our proposed method can generalise to other string models where there is no equivalent of deformation theory applicable, and the approximate harmonic forms are the only avenue for calculation of certain physical observables. We compare above methods below via an evaluation on the validation set.

val_data_batched, psi = dataloading._batch_aux(
    "data/X33_demo/dataset.npz", 1024, 'x_val',
    metadata_key='psi', aux_keys=('y_val',), precision = np.float32)
Dataset size: (200000, 12), meta: 0.5000000

# clunky but gets the job done
def normalised_cubic_yukawas_batched(harmonic_wp, params, data_loader):
    from cymyc.utils import math_utils

    wp, kappa = 0., 0.
    vol_Omega, int_a, int_aa_p_bb = 0., 0., 0.
    dQdz_monomials, dQdz_coeffs = harmonic_wp.dQdz_monomials, harmonic_wp.dQdz_coeffs
    metric_fn, deformation_fn, pb_fn = harmonic_wp.metric_fn, harmonic_wp.deformation, harmonic_wp.pb_fn
    n = 0
    for _data in tqdm(zip(*data_loader), total=len(data_loader[0])):
        p, aux = _data
        p_c = math_utils.to_complex(p)
        weights, dVol_Omega = aux[:,0], aux[:,1]
        pullbacks = jax.vmap(pb_fn)(math_utils.to_complex(p))
        B = p.shape[0]
        _data = (p, weights, dVol_Omega)

        _vol_Omega, _int_a, _int_aa_p_bb = harmonic_wp._compute_wp_metric_diagonal_batch_i(
                                                p_c, weights, pullbacks, dQdz_monomials,
                                                dQdz_coeffs, deformation_fn)

        vol_Omega = math_utils.online_update(vol_Omega, _vol_Omega, n, B)
        int_a = math_utils.online_update(int_a, _int_a, n, B)
        int_aa_p_bb = math_utils.online_update(int_aa_p_bb, _int_aa_p_bb, n, B)

        # Trilinear coupling
        _kappa = harmonic_wp.yukawas(
            p_c, dQdz_monomials, dQdz_coeffs, deformation_fn, deformation_fn,
            deformation_fn, weights, pullbacks)
        kappa = math_utils.online_update(kappa, _kappa, n, B)

        _g_pred = jax.vmap(metric_fn)(p)
        _eta, *_ = jax.vmap(harmonic_wp.harmonic_rep_breakdown, in_axes=(0,None))(p, params)
        _wp = harmonic_wp.inner_product_Hodge(_data, _eta, _g_pred)
        wp = math_utils.online_update(wp, _wp, n, B)
        n += B

    yuk_normalised = jnp.abs(kappa) / (vol_Omega) * wp**(-3/2)
    g_wp_KS = -int_aa_p_bb / vol_Omega + (jnp.conjugate(int_a) * int_a) / vol_Omega**2

    return {
        "yuk_normalised":   yuk_normalised,
        "kappa":            kappa,
        "wp":               wp,
        "vol_Omega":        vol_Omega,
        "wp_KS":            g_wp_KS}
yukawa_data = normalised_cubic_yukawas_batched(harmonic_wp, params, val_data_batched)
yukawa_data
  0%|                                                                                                                                                                                                                                                 | 0/195 [00:00<?, ?it/s]/tmp/ipykernel_900602/1695235459.py:6: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[-3.,0.]], precision))
/tmp/ipykernel_900602/1695235459.py:8: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[0.,-3.]], precision))

Compiling ddbar_phi_model
Compiling phi_head
Compiling LearnedVector_spectral_nn.spectral_layer.
Compiling HarmonicFull.del_bar_zeta_complete
Compiling coeff_head
['layers_0', 'layers_1', 'layers_2', 'layers_3', 'layers_4', 'layers_coeffs']
Compiling LearnedVector_spectral_nn.spectral_layer.
CoeffNetwork_spectral_nn_CICY.__call__, coeff shape, (1, 15, 21)

/tmp/ipykernel_900602/1695235459.py:6: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[-3.,0.]], precision))
/tmp/ipykernel_900602/1695235459.py:8: UserWarning: Explicitly requested dtype <class 'numpy.complex128'> requested in asarray is not available, and will be truncated to dtype complex64. To enable more dtypes, set the jax_enable_x64 configuration option or the JAX_ENABLE_X64 shell environment variable. See https://github.com/google/jax#current-gotchas for more.
  jnp.asarray([[0.,-3.]], precision))

Compiling HarmonicFull.harmonic_rep_breakdown (1, 3, 6) (1, 5, 6) (1, 3, 3)

100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 195/195 [00:21<00:00,  9.18it/s]

{'yuk_normalised': Array([[1.0534348+2.6020035e-07j]], dtype=complex64),
 'kappa': Array(-0.3489358+0.00036438j, dtype=complex64),
 'wp': Array([[0.60370165-9.94103e-08j]], dtype=complex64),
 'vol_Omega': Array(0.7061624, dtype=float32),
 'wp_KS': Array(0.6035413+7.4317977e-13j, dtype=complex64)}
yukawa_data = jax.tree_util.tree_map(lambda x: x.item(), yukawa_data)
print(f"Normalized Yukawa coupling λ @ (ψ={psi}) = {yukawa_data['yuk_normalised']:.07f}")
print(f"Harmonic calculation: WP @ (ψ={psi}) = {yukawa_data['wp']:.07f}")
print(f"Kodaira-Spencer calculation: WP @ (ψ={psi}) = {yukawa_data['wp_KS']:.07f}")
print(f"Relative error in Weil-Petersson metric = {np.abs((yukawa_data['wp'] - yukawa_data['wp_KS'])/yukawa_data['wp_KS'])*100:.04f}%")
Normalized Yukawa coupling λ @ (ψ=0.5) = 1.0534348+0.0000003j
Harmonic calculation: WP @ (ψ=0.5) = 0.6037017-0.0000001j
Kodaira-Spencer calculation: WP @ (ψ=0.5) = 0.6035413+0.0000000j
Relative error in Weil-Petersson metric = 0.0266%