On this page

physics module

The physics module provides reusable source/sink strokes, particle-line patterns, fermion-flow marks, momentum arrows, and label callbacks for draw.

Concepts

Physics Edge Styles

physics.style(..) returns source-style, sink-style, and edge-label callbacks for draw. The default source/sink strokes use darker/lighter halves to encode the graph source/sink split. Fermion map entries marked with fermion-flow receive one particle-flow arrow on the main edge using mark-orientation: "edge"; paired edges follow edge.orientation, dangling half edges place the arrow in the middle of the visible half edge, and undirected edges omit the arrow. This is independent of momentum arrows.

Set momentum-arrows: true to draw centered parallel arrow decorations while the main edge remains connected to the nodes. The arrows flow from source to sink independently of the DOT dir/orientation value; there is one momentum marker per edge, even though the base edge is internally styled as source and sink halves. On paired edges the marker lives on the sink half; on dangling edges it lives on the existing source or sink half-edge. The decoration defaults to a plain black 0.55pt stroke and a scaled CeTZ "barbed" arrowhead, so it does not inherit the base particle stroke. The arrow length is capped by both momentum-arrow-length and momentum-arrow-ratio, so the shorter one wins. When the layout provides an edge-label position, the arrow offset is signed so the momentum marker is drawn on the same side of the edge as that label. Use momentum-arrow-offset to set the normal displacement. momentum-arrow-mark: auto uses the default barbed marker, momentum-arrow-mark: none draws only the momentum line, and any other value overrides the CeTZ mark style, for example "stealth" or (end: "barbed",
scale: 1.6)
. Only the end mark is kept so source/sink splitting cannot create a second momentum head.

Optional labels can be built from edge metadata with show-edge-index, show-half-edge-index, show-particle, and, for explicit momentum fields, show-momentum. show-half-edge-index only emits a value for dangling half edges.

#import "../src/lib.typ": draw, graph, layout, physics
#import graph: build, dot, edge, edges, node, nodes, parse, sink, source

#let g = build({
  node(<a>)
  node(<c>)
  edge(
    source(<a>),
    <fermion-main>,
    sink(<c>),
    id: 7,
    particle: "fermion",
  )
  edge(
    source(<c>),
    <fermion-out>,
    id: 8,
    particle: "fermion",
  )
},
  name: "physics",
)
#let callbacks = physics.style(
  momentum-arrows: true,
  show-edge-index: true,
  show-particle: true,
)
#draw(
  layout(g),
  source-style: callbacks.source-style,
  sink-style: callbacks.sink-style,
  edge-label: callbacks.edge-label,
)

Set edge-offset on draw, or offset in a source-style/sink-style dictionary, to draw a fitted parallel path. Style callbacks may also return an array of dictionaries; the layers are drawn in order on the same graph edge, so parallel strokes do not require duplicate graph edges.

#let base-style(edge) = (
  stroke: (paint: gray, thickness: 0.7pt, cap: "round"),
)
#let offset-style(edge) = (
  offset: 0.18,
  length: 1.4,
  ratio: 0.5,
  resolve-length: "min",
  stroke: (paint: rgb("#2f6f4e"), thickness: 1pt, cap: "round"),
)
#let source-style(edge) = (base-style(edge), offset-style(edge))
#let sink-style(edge) = (base-style(edge), offset-style(edge) + (mark: (end: ">")))

The parallel path is applied to the base edge geometry before patterns and other decorations; node outsets then trim the shifted path, so shifted paths still start and end outside fitted node circles. Add edge-length or length to center-trim the shifted path to a fixed arc length, and add edge-ratio or ratio to cap it by a fraction of the base edge length. edge-resolve-length

resolve-length decides how to combine both limits
"min"/"shorter" (default), "max"/"longer",

"length"/"fixed", "ratio"/"relative", "none"/"full", or a function receiving (base-length, length, ratio). Set offset-side: "label" on an offset layer to choose the sign of offset so the layer is drawn on the same side of the curve as the edge label.

Data defaults are also the global styling hook for all sources, sinks, nodes, and edges. More specific data can be added with captured named arguments on node, edge, source, and sink items, or by running graph.map/graph.eval-fields after construction. This keeps evaluated Typst values in the native data channel instead of adding renderer-specific eval fields to the Rust topology spec:

#let g = build({
  node(<a>)
  node(<c>)
  edge(
    source(<a>),
    <a-c>,
    sink(<c>),
    label: [a-c],
    kind: "highlight",
  )
},
  name: "demo",
  default-source-data: (style: (stroke: red + 0.5pt)),
  default-sink-data: (style: (stroke: blue + 0.5pt)),
)
#let callbacks = physics.style()
#draw(layout(g), source-style: callbacks.source-style, sink-style: callbacks.sink-style)

graph.build also accepts comma-separated items:

#let g = build(
  node(<a>),
  node(<b>),
  edge(
    source(<a>, compass: "e"),
    <ab>,
    sink(<b>, compass: "w"),
    label: [ab],
    statements: (color: "0055ff", label: "ab"),
  ),
  name: "demo",
  statements: (full_num: "x + y"),
  default-node-statements: (shape: "circle"),
  default-edge-statements: (
    color: "000000",
    display-label: "$#label$",
  ),
)

physics

  • stroke-style()
  • source-stroke()
  • sink-stroke()
  • particle-name()
  • edge-entry()
  • text-value()
  • eval-expression()
  • label-content()
  • style-dict()
  • momentum-value()
  • edge-index()
  • dangling-half-edge-index()
  • source-style()
  • sink-style()
  • edge-label()
  • style()

Variables

  • massive
  • massless
  • dashed
  • dotted
  • fermion-arrow-mark
  • fermion-flow
  • wave
  • coil
  • zigzag
  • default-edge
  • default-map
  • momentum-arrow-defaults

stroke-style

Build a rounded CeTZ stroke dictionary accepted by linnest.draw.

Parameters

source-stroke

Source-half stroke helper.

Parameters

sink-stroke

Sink-half stroke helper. The default lightening makes the two halves encode the graph’s source/sink split without requiring arrowheads.

Parameters

particle-name

Return an edge’s particle name, stripping the quotes often present in DOT statement values.

Parameters

edge-entry

Return the style-map entry for an edge.

Parameters

text-value

Convert DOT-ish values to plain text.

Parameters

eval-expression

Evaluate a string-valued style expression in the edge scope.

Parameters

label-content

Convert a style label value to content. In mode: "eval", string values are evaluated in the edge scope.

Parameters

style-dict

Convert a style value to a dictionary. In mode: "eval", string values are evaluated in the edge scope.

Parameters

momentum-value

Return the momentum field used by optional edge labels.

Parameters

edge-index

Return the edge index used by optional edge labels. The DOT id statement wins over the renderer-local eid.

Parameters

dangling-half-edge-index

Return the exposed half-edge index for dangling edges only.

Parameters

source-style

Style callback for the source half edge.

momentum-arrows: true adds one centered black CeTZ-mark decoration while the main edge remains drawn with its normal particle style.

Parameters

sink-style

Style callback for the sink half edge.

momentum-arrows: true adds one centered black CeTZ-mark decoration toward the sink node, so momentum arrows always flow from source to sink independently of edge.orientation.

Parameters

edge-label

Edge-label callback.

By default this preserves data display-label or label, then explicit display-label, label, and particle-map labels. Set any show-* option to build a label from selected metadata instead:

#let callbacks = physics.style(
  momentum-arrows: true,
  show-edge-index: true,
  show-particle: true,
)

Parameters

style

Bundle source-style, sink-style, and edge-label callbacks with shared options for linnest.draw.

 #let g = build({
   node(<a>,pos:pos(y:0))
   node(<b>,pos:pos(y:0))
   edge(source(<a>), <g-edge>, sink(<b>), particle: "g")
   edge(source(<a>), <g-edge2>, sink(<b>), particle: "g")
   edge(source(<a>), <g-edge3>, sink(<b>), particle: "g")
 },
   name: "physics",
 )
 #let a = ```dot
 digraph {
 0 [dod="-100" num="1"];
  1 [dod="-100" num="1"];
  2 [dod="-100" num="1"];
  3 [dod="-100" num="1"];
  4 [dod="-100" num="1"];
  5 [dod="-100" num="1"];
  6 [dod="-100" num="1"];
  exte0  [style=invis];
  exte0 -> 2:0   [id=0  dod="-100" lmb_rep="P(0,a___)" num="1" particle="d" pin="x:@-left"];
  exte1  [style=invis];
  exte1 -> 1:1   [id=1 dir=back  dod="-100" lmb_rep="P(1,a___)" num="1" particle="d~" pin="x:@-left"];
  exte2  [style=invis];
  exte2 -> 1:2   [id=2 dir=none  dod="-100" lmb_rep="P(2,a___)" mass="0" num="1" particle="H" pin="x:@-left"];
  exte3  [style=invis];
  exte3 -> 2:3   [id=3 dir=none  dod="-100" is_dummy="true" lmb_rep="0" mass="0" num="1" particle="H" pin="x:@-left"];
  exte4  [style=invis];
  6:4 -> exte4   [id=4 dir=none  dod="-100" lmb_rep="P(3,a___)" mass="0" num="1" particle="H" pin="x:@+right"];
  exte5  [style=invis];
  1:5 -> exte5   [id=5 dir=none  dod="-100" is_dummy="true" lmb_rep="0" mass="0" num="1" particle="H" pin="x:@+right"];
  exte6  [style=invis];
  5:6 -> exte6   [id=6 dir=none  dod="-100" lmb_rep="P(4,a___)" num="1" particle="H" pin="x:@+right"];
  exte7  [style=invis];
  4:7 -> exte7   [id=7 dir=none  dod="-100" lmb_rep="P(5,a___)" num="1" particle="H" pin="x:@+right"];
  exte8  [style=invis];
  3:8 -> exte8   [id=8 dir=none  dod="-100" lmb_rep="-1*P(3,a___)+-1*P(4,a___)+-1*P(5,a___)+P(0,a___)+P(1,a___)+P(2,a___)" num="1" particle="H" pin="x:@+right"];
  0:9 -> 1:10  [id=9  dod="-100" lmb_id="1" lmb_rep="K(1,a___)" num="1" particle="t"];
  0:11  -> 2:12  [id=10 dir=none  dod="-100" lmb_rep="-1*P(0,a___)+K(0,a___)" num="1" particle="g"];
  5:13  -> 0:14  [id=11  dod="-100" lmb_rep="-1*P(0,a___)+K(0,a___)+K(1,a___)" num="1" particle="t"];
  6:24  -> 1:18  [id=12 dir=none  dod="-100" lmb_rep="-1*P(3,a___)+K(0,a___)" mass="((Q(2,spenso::cind(0)))^2+(Q(2,spenso::cind(1)))^2*-1+(Q(2,spenso::cind(2)))^2*-1+(Q(2,spenso::cind(3)))^2*-1)^(1/2)" num="1" particle="g"];
  1:15  -> 4:16  [id=13  dod="-100" lmb_rep="-1*P(3,a___)+K(0,a___)+K(1,a___)+P(1,a___)+P(2,a___)" num="1" particle="t"];
  2:17  -> 6:23  [id=14  dod="-100" lmb_id="0" lmb_rep="K(0,a___)" num="1" particle="d"];
  4:19  -> 3:20  [id=15  dod="-100" lmb_rep="-1*P(3,a___)+-1*P(5,a___)+K(0,a___)+K(1,a___)+P(1,a___)+P(2,a___)" num="1" particle="t"];
  3:21  -> 5:22  [id=16  dod="-100" lmb_rep="-1*P(0,a___)+K(0,a___)+K(1,a___)+P(4,a___)" num="1" particle="t"];
}

 ```
 #let (source-style, sink-style, edge-label, ..callbacks) = physics.style(momentum-arrows: true, show-edge-index: true)
 #draw(
   layout(g,beta: 100),
   source-style: source-style,
   sink-style:   sink-style,
   edge-label: edge-label,
 )
 #let g = parse(a.text).at(0)
 #draw(
   layout(layout(g,beta: 100,epochs:300,steps:200),subgraph: subgraph.bits(g,(false,false,false,false,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true))),
   source-style: source-style,
   sink-style:   sink-style,
   edge-label: edge-label,
 )

Parameters

massive

Conventional massive-particle stroke thickness.

massless

Conventional massless-particle stroke thickness.

dashed

Dash pattern used for scalar-style lines.

dotted

Dotted dash style used for ghost-style lines.

fermion-arrow-mark

CeTZ marker used for fermion particle-flow arrows on the main edge.

fermion-flow

Mark an edge-map entry as a fermion so the main edge receives one particle-flow arrow that follows edge.orientation. This is separate from optional momentum arrows.

wave

Photon-style wave pattern.

coil

Gluon-style coil pattern.

zigzag

Weak-boson-style zigzag pattern.

default-edge

Fallback style entry used when an edge has no known particle type.

default-map

Small built-in particle map for standalone physics diagrams. GammaLoop generated edge-style.typ files pass their model-specific map to style.

momentum-arrow-defaults

Default style for source-to-sink momentum arrow layers.