qlat.wilson_flow — Wilson Flow and Stout Smearing Utilities

Source: qlat/qlat/wilson_flow.pyx

Note: Update this document when updating the source file.

Outline

  1. Overview

  2. Energy Density Functions

  3. Wilson Flow

  4. Stout Smearing

  5. Local Tree Gauge Fixing

  6. Examples


Overview

wilson_flow implements the Yang-Mills gradient flow (Wilson flow) and related gauge field operations used in lattice QCD simulations. The Wilson flow evolves a gauge field in a fictitious flow time t, smoothing short-distance fluctuations while preserving long-distance topology. This is essential for computing the running coupling, energy density, and topological charge.

The module also provides stout smearing and a local tree gauge fixing procedure that can be used for gauge fixing within spatial blocks.

Key references:


Energy Density Functions

gf_energy_density

gf_energy_density(gf: GaugeField) -> float

Compute the total plaquette energy density (Euclidean) of the gauge field. Returns the global sum of E(x) = -1/2 * Tr(F_{mu,nu} F_{mu,nu}) over all sites. The result is not normalized by volume.

gf_energy_density_field

gf_energy_density_field(gf: GaugeField) -> FieldRealD

Return a FieldRealD with multiplicity == 1 containing the local energy density at each lattice site. This is the per-site version of gf_energy_density.

gf_energy_density_dir_field

gf_energy_density_dir_field(gf: GaugeField) -> FieldRealD

Return a FieldRealD with multiplicity == 6 containing the energy density decomposed by the six Lorentz directions ((mu, nu) with mu < nu).

For a smeared gauge field the relation to the plaquette is approximately:

gf_energy_density_dir_field(gf)[:]  ~  6 * (1 - gf_plaq_field(gf)[:])

Summing over the last axis recovers gf_energy_density_field:

gf_energy_density_dir_field(gf)[:].sum(-1, keepdims=True)
    == gf_energy_density_field(gf)[:]

See arXiv:1006.4518 Eq. (2.1) and arXiv:1203.4469.


Wilson Flow

gf_wilson_flow_force

gf_wilson_flow_force(gf: GaugeField, c1: float = 0.0) -> GaugeMomentum

Compute the Wilson flow force (the Lie algebra valued derivative of the action) for gauge field gf. Parameter c1 controls the Symanzik improvement (c1 = 0 for the standard Wilson plaquette action). Returns the force as a GaugeMomentum.

gf_wilson_flow_step

gf_wilson_flow_step(
    gf: GaugeField,
    epsilon: float,
    *,
    c1: float = 0.0,
    wilson_flow_integrator_type: str = None,
)

Evolve the gauge field gf in place by one Wilson flow step of size epsilon. The default integrator is the 3rd-order Runge-Kutta scheme from arXiv:1006.4518. An Euler integrator is also available by passing wilson_flow_integrator_type="euler".

Parameter

Type

Default

Description

gf

GaugeField

Gauge field (modified in place)

epsilon

float

Flow step size

c1

float

0.0

Symanzik improvement parameter

wilson_flow_integrator_type

str or None

None

"runge-kutta" (default) or "euler"

gf_wilson_flow

gf_wilson_flow(
    gf: GaugeField,
    flow_time: float,
    steps: int,
    *,
    c1: float = 0.0,
    existing_flow_time: float = 0.0,
    wilson_flow_integrator_type: str = None,
) -> list[float]

Evolve the gauge field gf in place by a total Wilson flow time flow_time, divided into steps equal sub-steps. Returns a list of energy density values recorded after each sub-step. The printed t^2 E values are used to determine the scale t_0 (defined by t^2 E = 0.3).

Parameter

Type

Default

Description

gf

GaugeField

Gauge field (modified in place)

flow_time

float

Total flow time

steps

int

Number of sub-steps

c1

float

0.0

Symanzik improvement parameter

existing_flow_time

float

0.0

Offset for reporting (e.g., if field was already flowed)

wilson_flow_integrator_type

str or None

None

"runge-kutta" or "euler"

gf_energy_derivative_density_field

gf_energy_derivative_density_field(
    gf: GaugeField,
    *,
    epsilon: float = 0.0125,
    c1: float = 0.0,
    wilson_flow_integrator_type: str = None,
) -> FieldRealD

Compute the derivative dE/dt of the energy density with respect to flow time at t = 0 using a symmetric finite difference of step 2 * epsilon. Returns a FieldRealD with multiplicity == 1. This is related to the topological charge via the Yang-Mills gradient flow renormalization.

gf_plaq_flow_force

gf_plaq_flow_force(
    gf: GaugeField,
    plaq_factor: FieldRealD,
) -> GaugeMomentum

Compute the flow force with a plaquette-dependent beta factor, allowing spatially varying couplings. plaq_factor must have multiplicity == 6 (one factor per plaquette direction). The ordering of plaquettes matches gf_plaq_field.


Stout Smearing

gf_stout_smear

gf_stout_smear(
    gf: GaugeField,
    step_size: float,
    num_step: int = 1,
    *,
    method: str = None,
)

Apply stout smearing to gf in place for num_step steps. method selects the implementation: None or "force" calls gf_wilson_flow_force + gf_evolve directly (default), "stout" uses gf_block_stout_smear, "wilson-flow" uses gf_wilson_flow_step with the Euler integrator and c1=0. All three are mathematically equivalent.

Parameter

Type

Default

Description

gf

GaugeField

Gauge field (modified in place)

step_size

float

Smearing strength ρ (typical: 0.1–0.125)

num_step

int

1

Number of smearing iterations

method

str

None

None/"stout", "wilson-flow", or "force"

gf_block_stout_smear

gf_block_stout_smear(
    gf: GaugeField,
    block_site: Coordinate,
    step_size: float,
)

Apply stout smearing to gf in place. If block_site is an empty Coordinate(), smearing is applied globally. Otherwise, each spatial block of size block_site is smeared independently (useful for local gauge fixing workflows).

gf_local_stout_smear

gf_local_stout_smear(
    gf: GaugeField,
    block_site: Coordinate,
    step_size: float,
)

Apply stout smearing locally (no MPI communication). If block_site is an empty Coordinate(), no blocking is applied. Otherwise, each block is smeared independently.

gf_local_avg_plaq

gf_local_avg_plaq(
    gf: GaugeField,
    block_site: Coordinate,
) -> float

Compute the local (no global sum) average plaquette within blocks of size block_site. Used as a diagnostic in the gt_local_tree_gauge workflow. If block_site == Coordinate(), defaults to geo.node_site.


Local Tree Gauge Fixing

mk_local_tree_gauge_f_dir

mk_local_tree_gauge_f_dir(
    geo: Geometry,
    block_site: Coordinate,
    is_uniform: bool,
    rs: RngState,
) -> FieldInt

Generate a FieldInt (multiplicity 1) specifying the tree direction at each lattice site. If f_dir[x] == 5 for a site, that site is a tree root (the gauge transformation is the identity). Used as input to gt_local_tree_gauge.

gt_local_tree_gauge

gt_local_tree_gauge(
    gf: GaugeField,
    f_dir: FieldInt,
    num_step: int,
) -> GaugeTransform

Compute a gauge transformation gt_inv using a local tree gauge fixing procedure. The fixed gauge field is obtained as:

gf_fixed = gt_inv.inv() * gf

Note: this convention is the inverse of the usual gt * gf convention used elsewhere.

gt_block_tree_gauge

gt_block_tree_gauge(
    gf: GaugeField,
    *,
    block_site: Coordinate = None,
    new_size_node: Coordinate = None,
    is_uniform: bool = True,
    stout_smear_step_size: float = 0.125,
    num_smear_step: int = 4,
    f_dir_list: list = None,
    rs_f_dir: RngState = None,
) -> tuple[GaugeTransform, list[FieldInt]]

High-level block tree gauge fixing. Returns (gt_inv, f_dir_list).

Steps performed:

  1. Optionally reshuffle the field to match new_size_node.

  2. Apply num_smear_step rounds of local stout smearing to each block.

  3. Compute local tree gauge fixing for each block.

Parameter

Type

Default

Description

gf

GaugeField

Input gauge field (not modified)

block_site

Coordinate

[4,4,4,4]

Block dimensions

new_size_node

Coordinate

Derived from total_site / block_site

MPI node layout

is_uniform

bool

True

Whether tree directions are uniform

stout_smear_step_size

float

0.125

Stout smearing step size

num_smear_step

int

4

Number of smearing steps

f_dir_list

list or None

None

Pre-computed tree directions; if None, generated from rs_f_dir

rs_f_dir

RngState or None

None

RNG state for generating f_dir_list


Examples

Wilson Flow and Scale Setting

import qlat as q

size_node_list = [
    [1, 1, 1, 1],
    [1, 1, 1, 2],
    [1, 1, 1, 4],
    [1, 1, 1, 8],
]

q.begin_with_mpi(size_node_list)

total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
gf = q.GaugeField(geo)
rng = q.RngState("seed")
gf.set_rand(rng)

flow_time = 1.0
steps = 100
energy_list = q.gf_wilson_flow(gf, flow_time, steps)

for i, e in enumerate(energy_list):
    t = (i + 1) * (flow_time / steps)
    print(f"t={t:.4f}  E={e:.6f}  t^2*E={t*t*e:.6f}")

q.end_with_mpi()

Single Wilson Flow Step

import qlat as q

size_node_list = [
    [1, 1, 1, 1],
    [1, 1, 1, 2],
    [1, 1, 1, 4],
    [1, 1, 1, 8],
]

q.begin_with_mpi(size_node_list)

total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
gf = q.GaugeField(geo)
rng = q.RngState("seed")
gf.set_rand(rng)

e_before = q.gf_energy_density(gf)
q.gf_wilson_flow_step(gf, epsilon=0.01)
e_after = q.gf_energy_density(gf)

print(f"E before flow: {e_before}")
print(f"E after flow:  {e_after}")

q.end_with_mpi()

Energy Density Direction Field

import qlat as q

size_node_list = [
    [1, 1, 1, 1],
    [1, 1, 1, 2],
    [1, 1, 1, 4],
    [1, 1, 1, 8],
]

q.begin_with_mpi(size_node_list)

total_site = q.Coordinate([4, 4, 4, 8])
geo = q.Geometry(total_site)
gf = q.GaugeField(geo)
rng = q.RngState("seed")
gf.set_rand(rng)

ed_dir = q.gf_energy_density_dir_field(gf)
ed_total = q.gf_energy_density_field(gf)

import numpy as np
np.testing.assert_allclose(
    np.asarray(ed_dir).sum(axis=-1, keepdims=True),
    np.asarray(ed_total),
    atol=1e-12,
)
print("Direction decomposition matches total energy density.")

q.end_with_mpi()

Block Stout Smearing

import qlat as q

size_node_list = [
    [1, 1, 1, 1],
    [1, 1, 1, 2],
    [1, 1, 1, 4],
    [1, 1, 1, 8],
]

q.begin_with_mpi(size_node_list)

total_site = q.Coordinate([8, 8, 8, 8])
geo = q.Geometry(total_site)
gf = q.GaugeField(geo)
rng = q.RngState("seed")
gf.set_rand(rng)

block_site = q.Coordinate([4, 4, 4, 4])
step_size = 0.1

print(f"Plaq before smear: {q.gf_avg_plaq(gf)}")
q.gf_block_stout_smear(gf, block_site, step_size)
print(f"Plaq after smear:  {q.gf_avg_plaq(gf)}")

q.end_with_mpi()

Block Tree Gauge Fixing

import qlat as q

size_node_list = [
    [1, 1, 1, 1],
    [1, 1, 1, 2],
    [1, 1, 1, 4],
    [1, 1, 1, 8],
]

q.begin_with_mpi(size_node_list)

total_site = q.Coordinate([8, 8, 8, 8])
geo = q.Geometry(total_site)
gf = q.GaugeField(geo)
rng = q.RngState("seed")
gf.set_rand(rng)

block_site = q.Coordinate([4, 4, 4, 4])
rs = q.RngState("seed-gt")

gt_inv, f_dir_list = q.gt_block_tree_gauge(
    gf,
    block_site=block_site,
    is_uniform=True,
    rs_f_dir=rs,
)

gf_fixed = gt_inv.inv() * gf
print(f"Plaq before fix: {q.gf_avg_plaq(gf)}")
print(f"Plaq after fix:  {q.gf_avg_plaq(gf_fixed)}")

q.end_with_mpi()