Interaction Vertices and Actions¶
Vertices define the nonlinear terms in the interaction action
\(S_{\mathrm{int}}\). An Action collects
one or more vertices and handles the combinatorial expansion
\(S_{\mathrm{int}}^n\) during perturbative calculations.
Defining a Vertex¶
A Vertex is a frozen dataclass with three
attributes:
fieldsSequence of
Fieldobjects that participate in the vertex.couplingName of the coupling constant (e.g.
'F','g','K').localWhether all fields share the same spatial argument (default
True).
Local Vertices¶
In a local vertex every field is evaluated at the same spatial point and a single spatial integration wraps the term:
from sft_wick import Field, Vertex
phi = Field('phi', 'physical', n_components=3)
psi = Field('psi', 'response', n_components=3)
v = Vertex(fields=[phi, phi, psi], coupling='F')
# v.local is True by default
Non-Local Vertices¶
In a non-local vertex each field gets its own spatial argument and multiple integrations appear:
v_nl = Vertex(fields=[psi, psi], coupling='K', local=False)
Non-local vertex flags¶
A non-local Vertex accepts two opt-in
specialisation flags that shape the integration topology produced
downstream by compute_moment() /
integrate_moment(). Both are exposed at the
L0 surface (you can construct them directly here, without going
through the L1 NonLocalVertex wrapper),
and they propagate through the diagram pipeline via
VertexInstance →
DiagramTerm →
SpatialStructure.
Flag |
Effect on the diagram |
When to use |
|---|---|---|
|
Collapses the |
Equal-shell / Limber-like cumulants (e.g. cosmological
equal-time bispectrum |
|
Each ψ leg’s R-propagator (with its Wick partner φ) is absorbed:
it stays in the diagram graph for direction grouping but
contributes a factor of 1; the leg’s time/direction is aliased
to its partner’s, dropping the m leg-time integrations entirely.
The coupling callable is expected to return the
R-contracted form
|
Narrow-kernel |
The two flags are mutually exclusive and rejected at construction
time. Both are accepted on local vertices only as False
(local=True already shares a spacetime point, so neither
collapse is meaningful):
# Equal-time (single-shell) non-local cumulant.
v_K_equal = Vertex(
fields=[psi, psi, psi], coupling='K',
local=False, equal_time=True,
)
# Already-R-contracted non-local cumulant.
v_K_abs = Vertex(
fields=[psi, psi, psi], coupling='K',
local=False, already_R_contracted=True,
)
The L1 NonLocalVertex wrapper and the L2
YAML config both expose the same flags by name. See
Workflow API (L1 + L2) → Equal-time (single-shell) cumulants and Already-R-contracted
vertices for the L1/L2 surface and the math / validation details, and
docs/notes/R_contracted_nonlocal_vertex.md for the design note
behind the R-absorption dispatch.
Building an Action¶
An Action is simply a list of vertex
templates:
from sft_wick import Action
action = Action(vertices=[v, v_nl])
You can also pass an empty list when you only need zeroth-order (free-propagator) results:
free_action = Action(vertices=[])
Multinomial Expansion¶
When computing order n, the expansion
\(S_{\mathrm{int}}^n = (v_0 + v_1 + \cdots)^n\) is generated
by all_vertex_combinations(). For each
combination of n vertices (with repetition), it yields the
corresponding multinomial coefficient:
where \(n_k\) is the number of times vertex \(k\) appears.
Vertex Instantiation¶
During expansion, each vertex template is turned into a
VertexInstance with fresh, non-overlapping
internal indices. An IndexContext tracks
the counters to ensure no collisions between copies:
Component indices:
i_0,i_1,i_2, …Spatial variables:
y_0,y_1,y_2, …
This is handled automatically by
compute_moment(); you only need to interact
with VertexInstance directly if you are using the low-level API.