Dogtag
The content on this page was written by AI under human supervision.
Dogtag reads the file that defines a family of Feynman integrals and establishes which family it actually is, before any reduction or numerical evaluation is spent on it. The name is meant literally: a dog tag identifies its wearer, and Dogtag fixes an integral family's identity (the same family as a known one up to a relabeling of the loop momenta or not, which catalog topology, planar or crossed in the physical order of the legs, which cut signature) so that every later step can trust the label the file carries. The input is a Kira integralfamilies.yaml (with its kinematics.yaml), an AmflowFamily .jl file, an AMFlow JSON job file (its family block and target integrals), or a pySecDec LoopIntegralFromGraph script. The output is a short fingerprint of each family plus warnings when the family matches a known graph under a relabeling of the loop momenta, or when a file labeled nonplanar turns out to be planar. A second mode compares a family file with a drawing of its graph and reports whether the two are the same graph, line masses and external legs included. Another part of the package, the loomcheck module in the loomcheck/ subfolder, asks the matching question for conformal integrals in position space: whether the graph meets the conditions of the published Yangian-symmetry theorems for fishnet and loom graphs.
What it does
An integral family is a list of propagators $q^2 - m^2$, with $q$ a linear combination of loop momenta $k_i$ and external momenta $p_j$. Two files can list propagators that look different and still describe the same graph, because a propagator depends on $q$ only through $q^2 = (-q)^2$. The case it is built to catch is a file labeled as the nonplanar (crossed) two-loop double box whose propagators, after $k_2 \to -k_2$ and momentum conservation, are exactly those of the planar double box (hep-ph/9905323). Reading the list by eye does not reveal this, and every later step of a reduction trusts the label. The script is meant to be run once on a family file before a Kira reduction or an AMFlow evaluation is started on it.
For each family the script does three things. It applies every signed permutation of the loop momenta, $k_i \to \pm k_{\sigma(i)}$ (with --leg-perms, also permutations of the external legs), and compares the resulting propagator set with a catalog of nine reference families stored in the script. Among them are the massless planar double box (planar_smirnov_dbox) and the genuine crossed double box (crossed_tausk_dbox, hep-ph/9909506); more are added by editing CATALOG_SOURCES. When no signed permutation works it also tries loop redefinitions $k \to Uk + c\cdot p$ with $U$ an integer matrix of determinant $\pm 1$, which is how two routings of one graph differ. A match is reported with its relabeling and says whether the line masses, propagator powers and leg virtualities agree too. It then rebuilds the Feynman graph from the propagator momenta, placing vertices by momentum conservation, and tests planarity with the external legs joined by a cycle in a fixed cyclic order: the one the file declares, otherwise $p_1, \ldots, p_N$. The cycle is what separates the planar and crossed boxes, which are the same abstract graph and differ only in that order. planar=True is the planar box; planar=False means a crossing is forced. Last, for each channel ($s$, $t$, $u$) it counts the fewest internal lines a cut must sever to separate that pair of legs from the rest; the triple depends on the momentum routing, so planarity is the deciding check. Two hashes are printed: one of the routed propagator set and one of the rebuilt graph, unchanged under rerouting.
Physical propagators are picked from top_level_sectors in a Kira yaml, the *_INDICES vector in a .jl file, or one target integral's index vector in an AMFlow job file (--integral N; by default the integral with the most positive indices). Irreducible scalar productsdot products such as k1·p3 that a reduction keeps in its basis but that are not lines of the diagram and entries with no loop momentum are dropped. The external legs are read as data: for a Kira yaml from the kinematics.yaml passed with --kinematics or found beside the family file, or from external_momenta, momentum_conservation and leg_virtualities keys in the family block; the other formats declare their own legs. Only when a Kira yaml carries none of this are the legs inferred from the propagator symbols. The report then prints a LEG-SET-INFERRED warning and withholds the planarity verdict, because a family whose top sector never spells its dependent leg would be read with one leg too few.
The compare mode (--drawn) takes the family file and a drawn graph. The drawing is an edge list in JSON, a yaml drawn_graph: block, or a routed yaml whose header comments state the edges, the legs with their $p^2$, the cyclic order and the line multiplicities. It rebuilds both graphs, tests them for isomorphism with masses, propagator powers and leg virtualities as labels, and prints exactly one verdict. IDENTITY-PASS means the labeled graphs are isomorphic; FAMILY-MISMATCH names the first level that fails (bare graph, masses, legs); NON-GRAPH means one side's propagators do not close into a connected graph with one cycle per loop momentum; NOT-CHECKABLE names the side that cannot be read or leaves a leg's virtuality undeclared. Under the verdict come the leg map, the identification of symbolic mass labels across the two sides, the relabeling, the legs per vertex and the vertex and edge counts of both sides, and their planarity in the drawn order. A declared cyclic leg order that the leg map does not preserve is reported as CYCLIC-ORDER MISMATCH without changing the verdict.
The checks are exact graph computations with no numerical tolerance, within these limits. The vertex reconstruction is exact for two-loop four-point boxes and the related one- and two-loop boxes, triangles and ladders. On large or many-loop families it may not complete, and the script then reports planar=None with the cause and raises no mismatch warning. The planarity test discriminates only with four or more external legs; with fewer, the report says not discriminating (N < 4 legs) and a file labeled nonplanar that reads planar gets an "inconclusive" note instead of a warning. "Labeled nonplanar" means a keyword search of the family name, file name and the top of the file for words such as nonplanar, crossed or tausk.
The Yangian screen, the loomcheck module (formerly the separate Loomcheck tool, now the loomcheck/ subfolder of this package), tests whether a conformal Feynman integral in position space, $I = \int \prod_{v} d^D x_v \prod_{i \lt j} (x_{ij}^2)^{-a_{ij}}$ given as a dictionary {(i, j): a_ij} with numerator factors entered as negative powers, can fall under the published Yangianan infinite-dimensional symmetry algebra that extends conformal symmetry and signals integrability-symmetry theorems for fishnet and loom graphs (arXiv:1708.00007, arXiv:2304.04654, arXiv:2505.05550). It checks three conditions on the graph: the powers at every internal vertex sum to the dimension $D$, the full graph with numerator edges included is planar, and every $k$-sided face of the planar embedding has powers summing to $(k-2)D/2$. A failed condition is a proof that the graph lies outside those theorems as published; a pass says only that the conditions do not exclude it. The face count treats the outer face like any other, so a violation count of 0 to 2 is not to be trusted on its own, and the file parser reads only the four-loop basis format of the arXiv:2607.11645 ancillary; other integrals go to screen_graph directly.
Examples
Run the self-test. The self-test confirms the install and shows the report format, using the catalog families as controls.
python3 topology_audit.py --selftest
Abridged output:
[2] catalog planar Smirnov dbox (control, must read planar):
name=planar_dbox_control loops=2 genuine_props=7 isp=0 massive=0
planar=True (networkx.check_planarity with external-leg closure cycle in canonical order 1,2,3,4)
cut_signature={'s': 2, 't': 3, 'u': 4} multiset=[2, 3, 4] t/u asymmetric
...
[3] catalog crossed box entries (declared planar False, must read NONplanar):
name=crossed_tausk_dbox_control loops=2 genuine_props=7 isp=0 massive=0
planar=False (networkx.check_planarity with external-leg closure cycle in canonical order 1,2,3,4)
cut_signature={'s': 2, 't': 3, 'u': 3} multiset=[2, 3, 3] t<->u symmetric
...
SELF-TEST: 17 executed / 0 skipped (17 PASS, 0 FAIL) of 17 legs
SELF-TEST: ALL PASS (rc 0)
Seventeen groups of checks run in one to two minutes. They include the mislabeled double box, the catalog controls, a double box with a massive rung, a three-loop banana and the file readers; one group runs the compare mode over a test corpus of 30 published families, each paired with the graph drawn in its paper, and the last runs the loomcheck module's own checks. The exit code is 0 only when every check ran and passed, 1 if any failed, and 2 if any was skipped (networkx or a fixture file missing; the skipped check is named). The fixture files come with the package under tests/fixtures/; the environment variables DOGTAG_NPDBOX_FAMILY, DOGTAG_C3_FAMILY and DOGTAG_BANANA_FAMILY (the older TOPOLOGY_AUDIT_* names are still read) substitute your own family files for three of them.
Catch a mislabeled double box. A Kira family file whose name says nonplanar crossed box and whose last propagator, $(k_1+k_2)^2$, looks like the crossing rung:
integralfamilies:
- name: "NP2L_xbox"
loop_momenta: [k1, k2]
top_level_sectors: [127]
external_momenta: [p1, p2, p3, p4]
momentum_conservation: {p4: "-p1 - p2 - p3"}
leg_virtualities: {p1: "0", p2: "0", p3: "0", p4: "0"}
propagators:
- [ "k1^2", 0 ]
- [ "(k1 + p1)^2", 0 ]
- [ "(k1 + p1 + p2)^2", 0 ]
- [ "k2^2", 0 ]
- [ "(k2 + p3 + p4)^2", 0 ]
- [ "(k2 + p4)^2", 0 ]
- [ "(k1 + k2)^2", 0 ]
The external_momenta, momentum_conservation and leg_virtualities keys declare the legs; a Kira kinematics.yaml beside the file, or passed with --kinematics, does the same job.
python3 topology_audit.py NP2L_xbox.yaml
Output (one long line shortened):
name=NP2L_xbox loops=2 genuine_props=7 isp=0 massive=0
planar=True (networkx.check_planarity with external-leg closure cycle in canonical order 1,2,3,4)
cut_signature={'s': 2, 't': 3, 'u': 4} multiset=[2, 3, 4] t/u asymmetric
canonical_hash=b6577ea5d3bf (routed) canonical_hash_realized=b6d3659027de (realized graph, routing-independent)
>> ISO to 'planar_smirnov_dbox' under [k2 -> -k2]
!! graph-isomorphic to catalog family 'planar_smirnov_dbox' under [k2 -> -k2] (massless planar double box (Smirnov hep-ph/9905323)); labels carried: masses [...], multiplicities [...] and leg classes {...} match the entry under [k2 -> -k2]
!! LABEL/PROVENANCE MISMATCH: file is labeled nonplanar/crossed but the reconstructed graph is PLANAR in the canonical external-leg order (networkx). This is the NP-dbox-style mislabel. Cross-check the cut signature {'s': 2, 't': 3, 'u': 4}.
Lines starting >> are catalog matches; lines starting !! are warnings. The relabeling can be checked by hand: with $p_4 = -p_1-p_2-p_3$, the substitution $k_2 \to -k_2$ turns $(k_2+p_3+p_4)^2$ into $(k_2+p_1+p_2)^2$, $(k_2+p_4)^2$ into $(k_2+p_1+p_2+p_3)^2$ and $(k_1+k_2)^2$ into $(k_1-k_2)^2$, which is the planar routing propagator for propagator. A genuinely crossed family prints planar=False and no mismatch line. Without the leg keys and without a kinematics.yaml the same file prints planar=None and a LEG-SET-INFERRED warning. --json prints the report as a JSON list with one object per family holding the same fields, catalog_matches (each with family, relabeling, relabeling_class, note) and warnings; in a yaml with several families --name NAME selects one.
Compare a family with its drawn graph. Two of the included fixture files are a published double-box family and the graph drawn for it in the paper:
python3 topology_audit.py tests/fixtures/row02/family_of_record.jl \
--drawn tests/fixtures/row02/drawn_graph.yaml --verdict
Abridged output:
VERDICT: IDENTITY-PASS (leg map {'p4': 'p1', 'p3': 'p2', 'p1': 'p3', 'p2': 'p4'}; the propagator sets differ by a loop redefinition: affine map ['l1 -> k1 +k2 -p1 -p2 -p3', 'l2 -> k1 -p1 -p2 -p3'] with leg map {...})
family doublebox (family_of_record.jl): loops ['l1', 'l2'], legs ['p1', 'p2', 'p3', 'p4'], ...
realized: V=6 E=7 N=4 legs per vertex [1, 1, 1, 1, 0, 0]; distinct connected realizations 1
drawn drawn_row02 (drawn_graph.yaml): loops ['k1', 'k2'], legs ['p1', 'p2', 'p3', 'p4'], ...
isomorphism of the realized labeled graphs (VF2): bare True, with masses and multiplicities True, with leg classes True; by leg name False
...
The drawing's propagators come from a spanning-tree routing of its edge list, so no signed permutation relates the two files; the verdict rests on the isomorphism of the rebuilt graphs, and the affine map is the loop redefinition that carries one propagator set onto the other. by leg name False says the match needs the legs relabeled, as the leg map shows. Without --verdict the family's own audit follows the evidence; --json puts the compare under "verdict"; --kinematics-drawn supplies a kinematics.yaml for the drawn side. In a multi-family file --name picks the family compared, and a name the file does not carry stops with exit code 2 and the list of names it does carry.
Routines
Command line
topology_audit.py FAMILY_FILE [--name NAME] [--leg-perms] [--kinematics K.yaml] [--integral N] [--json]— audit every family in the file and print fingerprint, catalog matches and warnings (exit code 2 if no family is found).topology_audit.py FAMILY_FILE --drawn DRAWN_GRAPH [--name NAME] [--kinematics K.yaml] [--kinematics-drawn KD.yaml] [--integral N] [--verdict] [--json]— the compare mode: the verdict line, its evidence, then the family's audit unless--verdict.topology_audit.py --selftest(alias--self-test) — run the self-test suite.tests/census_battery.py— run the compare mode over the 30-family test corpus alone (about 20 seconds);python3 -m pytest testsruns every check case by case.tests/repin_fixtures.py [--check]— after an intentional edit of a fixture file, rewrite its checksums everywhere they are recorded;--checkonly reports stale ones (exit code 1 if any).
Python (import topology_audit), reading and auditing
Family(name, loops, exts, ext_subs, propagators, source, physical=None, nu=None, leg_virt=None, mass_values=None, kinematics_source=None, cyclic_leg_order=None, ...)— one family in memory: momentum names, the dependent-leg substitution,(expression, mass)pairs, the physical-line mask, propagator powers, leg virtualities and, for a drawn graph, the cyclic leg order.load_family(path, name=None, kinematics=None, integral_index=None)— read any of the input formats into a list ofFamilyobjects (viaload_kira_yaml,load_amflow_jl,load_amflow_json,load_pysecdec_graphorload_edge_list), completing the dependent leg;read_kinematics_yaml(path)reads a Kirakinematics.yamlon its own.audit(fam, try_leg_perms=False)— the full audit; returns the fingerprint dict extended withcatalog_matches,cut_symmetry,planarity_report,leg_setandwarnings.fingerprint(fam)— counts, mass and propagator-power multisets, cut signature, planarity, a 12-character hash of the sign-canonical propagator set and the routing-independent hash of the rebuilt graph (realized_canonical_hash(fam)).planarity(fam, leg_order=None)— dict withplanar(legs closed inclosure_order),discriminating,abstract_planar(leg order ignored),planar_legs_joined_at_infinity,planar_in_some_leg_order,method,n_vertices,n_edges.cut_signature(fam)— minimum internal cut per channel, keyeds,t,ufor legsp1..p4and by leg pair otherwise; aNoneentry carries its cause.build_graph(fam)— rebuild the graph by momentum conservation; returns(G, ok)withGa networkxMultiGraphcarrying the reconstruction report inG.graph["realization"], andok=False(empty graph, named cause) when no graph realizes the propagators.isomorphism_to(vecsA, vecsB, nloop, n_ext, try_leg_perms=False, affine=False, find_all=False, ...)— search the signed loop (and optionally leg) permutations, then withaffine=Truethe loop redefinitions, mapping one propagator set onto another; returns the relabeling string orNone.momentum_vectors(fam)supplies the vectors.CATALOG_SOURCES— the reference families; each entry hasloops,exts,ext_subs,propagators,note, the expectedplanarvalue and the leg virtualitieslegs.
Python, the compare mode
compare_drawn(famA, famB, kinA=None, kinB=None, integral_indexA=None, nameA=None)— the family of record against the drawn graph (Familyobjects or file paths); returns a dict withverdict,reason,line,first_failing_level,leg_map,mass_identification,relabeling,cyclic_order,planarity_in_drawn_order,legs_per_vertex,V,E,sides;verdict_lines(res)turns it into the printed lines.realized_graph_iso(famA, famB, ...)— isomorphism of the two rebuilt graphs (networkx VF2) at four levels,bare,with_masses,with_masses_and_legs,with_masses_and_leg_names, each with its leg map;affine_iso_search(famA, famB, ...)finds the leg bijection and loop redefinition that carry one labeled propagator set onto the other.self_test()— run the self-test suite from Python; returns(results, rc).
The Yangian screen (the loomcheck module)
python3 -m loomcheck [--basis FILE] [--targets LIST] [--D D] [--json OUT], run from the package directory (python3 loomcheck/loomcheck.py ...works from anywhere) — screen the listed 1-indexed entries of a basis file in the arXiv:2607.11645 format (nine default entries without--targets;LOOMCHECK_BASISsets the default file), one result line per entry and a summary count;python3 -m loomcheck --selftestruns ten hand-checkable cases and exits 0 only when all pass.screen_graph(powers, D=4, internal=None)(from loomcheck import screen_graph, with the package directory onsys.path) — screen one graph given as{(i, j): power}and returnconformal_internal_weight,planar_full_graph,numerator_edges,faces,phat_face_violationsandyangian_class(true only when all three conditions hold);load_entries(basis_path)andparse(entries, t)read the basis file.
Requirements and source
Python 3 with sympy and networkx (and pytest for the case-by-case suite); no YAML library, Kira or AMFlow is needed, and a single audit or compare takes about a second. Self-tests: python3 topology_audit.py --selftest from the package directory, which must end with SELF-TEST: ALL PASS (rc 0), and python3 -m loomcheck --selftest, which must end SELFTEST PASS: 10/10 checks passed; python3 -m pytest tests covers the audit checks case by case. Without networkx the tool still runs but prints None for every value that needs the rebuilt graph, and the self-test exits 2. The code is tools/dogtag/ (formerly topology-audit) in BootLoops' bootloops-dev repository under the GitHub organization BootLoops-ai (topology_audit.py, the loomcheck/ subfolder, README.md, GUIDE.md, tests/ with the fixture files), released under the MIT license.