Feynman diagrams are introduced as drawings representing particle interactions, but each one also contains a particular graph structure. In this project, I explore these structures computationally by generating all possible Feynman-style diagrams in
3
ϕ
theory, meaning each interaction vertex has a degree of exactly 3. Given a number of interaction vertices and incoming/outgoing external legs, the program constructs all possible distributions of these external legs, generates internal edge relations, filters out only valid connected graphs, and then removes duplicates using graph isomorphism. The result is systematic enumerations of distinct
3
ϕ
graph topologies. This project focuses on creating the structures of the graphs rather than calculating actual physical amplitudes. As an extension, the graphs are reinterpreted in the context of large-
N
c
gauge theory, where planar diagrams dominate over subleading non-planar diagrams.

Introduction

Feynman diagrams provide a compact visual communication tool for quantum field theory. Instead of calculating particle interactions only through tedious algebraic expressions, physicists represent terms in a perturbative expansion as diagrams made of external legs, internal propagation, and interaction vertices. In perturbation theory, one of the first steps is usually to draw out all possible Feynman graphs for a given order. For example, many lecture notes on
3
ϕ
theory describe the first rule for
th
n
order perturbative contributions as drawing out all possible Feynman diagrams with
n
vertices before assigning their propagator and vertex factors.
​
This project focuses on that first step of generating the diagrams themselves. Rather than manually drawing possible graphs, I created a Wolfram Language program that enumerates distinct graph topologies (without tadpoles) for a scalar
3
ϕ
-style theory. In
3
ϕ
theory, each interaction vertex has exactly 3 edges connected to it, so the underlying graph problem simplifies to generating all connected graphs in which each internal vertex has degree three while each external leg is connected to only one vertex.
​
The purpose of this project is not to evaluate full Feynman amplitudes. I do not at all bring into play momenta or calculate propagator integrals and scattering probabilities. Rather, the project studies the combinatorial skeletons of the Feynman diagrams, including which structures are possible, how they can be generated efficiently, and how duplicate diagrams can be removed.

Representing external vertices and preparing for the computations

The first step is to represent external vertices separately from internal interaction vertices. I chose to use ordinary numbers to represent the internal vertices and use expressions of the form
e[1]
,
e[2]
, etc. to represent the external vertices. This makes the graph much easier to style later on as external components can now be represented separate from internal components.
Create labels for the external vertices:
In[]:=
externalVertices[externalCounts_]:=​​e/@Range[Total[externalCounts]]
Now load the graph-isomorphism package (https://szhorvat.net/pelican/igraphm-a-mathematica-interface-for-igraph.html). I used this instead of the built-in IsomorphicGraphQ as the built-in function creates issues with graphs with loops.
Load the IGraph/M package:
In[]:=
<<IGraphM`;
As this program is computationally demanding, I also used the Wolfram Compute Services.
Enabling the Compute Services:
In[]:=
RemoteBatchSubmissionEnvironment["WolframBatch"];

Distributing external legs

Before generating the internal edges, the program needs to decide the interaction vertices the external legs are attached to.
​
For example, if we have six interaction vertices and two external legs, the two legs may both be attached to one vertex, or may attach to two different internal vertices. This is represented by a list of external counts. For example, {1, 1, 0, 0, 0, 0} means that each of the first two internal vertices receive one external leg.
​
In my code, I also restrict each internal vertex to receive a maximum of two external legs to ensure a vertex is not completely used up by external legs and cannot connect to any other vertex.
Generate the possible distributions of external legs across internal vertices:
In[]:=
externalCountDistributions[vertexCount_,externalLegCount_:4]:=​​PadRight[#,vertexCount]&/@IntegerPartitions[externalLegCount,{1,vertexCount},{1,2}]
We can test this definition for the example of six vertices and two external legs.
Generate the external counts for six vertices and two external legs:
In[]:=
externalCountDistributions[6,2]
Out[]=
{{2,0,0,0,0,0},{1,1,0,0,0,0}}

Calculating internal degrees

Once the number of external legs attached to each vertex is known, the remaining internal degree can be determined using the
3
ϕ
rule. Since each interaction vertex must have a total degree of three, we get
internal degree3-external degree
.
​
So, if a vertex already has one external leg, it still needs two more internal edges. If it has no external legs, it need three internal edges.
Compute how many internal edges each interaction vertex needs:
In[]:=
internalDegrees[externalCounts_]:=​​3-externalCounts
Compute internal degrees for {1, 1, 0, 0, 0, 0}:
In[]:=
internalDegrees[{1,1,0,0,0,0}]
Out[]=
{2,2,3,3,3,3}
This output means that the first two vertices each need two internal edges, while the rest of the vertices each need three internal edges.

Generating bounded compositions

The internal-edge generation step needs to be able to decide how many edges are assigned from one vertex to the other possible vertices. For that, I used bounded compositions.
​
A bounded composition is something that lists the ways to split a total into different parts where each part has an upper bound. Here, I take the total as the remaining degree of the current vertex and the bounds as the remaining degrees of the possible neighbouring vertices.
Generate all ways to split a total among bounded choices:
In[]:=
boundedCompositions[total_,bounds_]:=​​Select[Tuples[Range[0,#]&/@bounds],​​Total[#]==total&]
Generate the bounded compositions of a simple example:
In[]:=
boundedCompositions[3,{1,2,3}]
Out[]=
{{0,0,3},{0,1,2},{0,2,1},{1,0,2},{1,1,1},{1,2,0}}
This example gives all the ways of assigning three edges among three possible neighbouring vertices without exceeding the remaining degrees of any neighbour.

Recursively generating internal edge lists

The main part of the program is the function internalEdgeList. This function takes a list of remaining internal degrees as input and builds possible internal edge sets. It first finds a vertex with a positive degree (degree
≠0
), identifies the possible vertices (with larger label values than the current vertex) that can connect to it, generates the possible edge allocations, updates the degree list, and then repeats.
​
The later vertices are extremely important as it prevents the repeated generation of the same internal structure in different orders.
Generate all possible internal edge lists for a given internal degree list:
In[]:=
internalEdgeList[degrees_]:=​​internalEdgeList[degrees,{}]​​internalEdgeList[remainingDegrees_,currentEdges_]:=Module[​​{​​vertexPosition,vertex,possibleNeighbours,possibleAllocations​​},​​​​vertexPosition=FirstPosition[remainingDegrees,_?Positive,Missing["Done"]];​​​​If[​​vertexPosition===Missing["Done"],​​{currentEdges},​​vertex=First[vertexPosition];​​​​possibleNeighbours=Select[​​Range[vertex+1,Length[remainingDegrees]],​​remainingDegrees[[#]]>0&​​];​​​​possibleAllocations=boundedCompositions[remainingDegrees[[vertex]],remainingDegrees[[possibleNeighbours]]];​​​​Join@@Map[​​Function[​​allocation,​​internalEdgeList[​​ReplacePart[​​remainingDegrees,Join[{vertex->0},Thread[possibleNeighbours->(remainingDegrees[[possibleNeighbours]]-allocation)]]​​],​​Join[currentEdges,Join@@MapThread[ConstantArray[vertex<->#1,#2]&,{possibleNeighbours,allocation}]]​​]​​],​​possibleAllocations​​]​​]​​]

Constructing the external edges

After generating the internal edge candidates, the program needs to connect the external vertices. The function externalEdges uses the external count list to create edges between the internal vertices and the external labels like e[1].
Construct the external edges attached to each internal vertex:
In[]:=
externalEdges[externalCounts_]:=​​Join@@MapThread[​​Function[​​{vertex,firstExternalNumber,externalCount},​​Thread[​​vertex<->(e/@Range[firstExternalNumber+1,firstExternalNumber+externalCount])​​]​​],​​{​​Range[Length[externalCounts]],​​Most[Accumulate[Prepend[externalCounts,0]]],​​externalCounts​​}​​]
Example output for {1, 1, 0, 0, 0, 0}:
In[]:=
externalEdges[{1,1,0,0,0,0}]
Out[]=
{1e[1],2e[2]}
This output shows that the first internal vertex is connected to e[1] and the second internal vertex is connected to e[2]. It also does not have any other terms as no other vertices are connected to the external labels.

Building the full
3
ϕ
graph

The function phi3Graph combines the internal and external edges into one graph and also styles the graph so that external vertices and edges are shown in red. This brings the visual output closer to the convention for Feynman diagrams where the external legs are distinguishable from the internal structure.
Build and style the full
3
ϕ
graph from external counts and internal edges:
In[]:=
phi3Graph[externalCounts_,internalEdges_]:=​​With[{externalEdgeList=externalEdges[externalCounts]},​​Graph[​​Join[​​internalEdges,​​externalEdgeList​​],​​VertexLabels->None,​​VertexStyle->Join[Thread[Range[Length[externalCounts]]->Automatic],Thread[externalVertices[externalCounts]->Red]],​​VertexSize->Join[Thread[Range[Length[externalCounts]]->Small],Thread[externalVertices[externalCounts]->Tiny]],​​EdgeStyle->Join[Thread[internalEdges->Directive[Automatic,Thick]],Thread[externalEdgeList->Directive[Red,Thick]]],​​ImageSize->Small​​]​​]

Testing the validity of the graph

Creating records for candidate graphs

Instead of only storing the graph images, I chose to store each graph as an association. This makes the resultant output much easier to pick apart and analyze because each record contains the external count distribution, the internal degrees, the internal edges, and the final graphs.
Create graph records for one external count distribution:
We can see this in action through an example. The following output shows the structure of each graph record. Storing the data like this with the final graph makes it easier to extend the project later.
Keys for the graph record of {1, 1, 0, 0, 0, 0}:

Removing invalid and duplicate graphs

The function phi3GraphRecords combines all the external count distributions, creates candidate records, filters out invalid graphs, and deletes duplicate diagrams using graph isomorphism.
​
The final isomorphism step is necessary because two graphs may have different vertex labels but the same topological structure. Since the goal is to enumerate distinct Feynman-style graph topologies, these duplicates should only be counted once.

Extracting and displaying the graphs

The function phi3Graphs (not to be confused with the previously mentioned phi3Graph) extracts only the graph from the record. Then, displayGraphs displays the graphs in the grid and also gives the total count.
Extract only the graph objects:
Display the generated graphs in a grid:
We can now look at how some graph examples look.
Display graph examples with number of vertices, external legs, and number of columns. As long as the sum of the number of internal vertices (V) and external legs (E) is even, there will be an output graph. V = 3, E = 2:
V = 4, E = 2:
V = 4, E = 4:
V = 5, E = 5:
V = 6, E = 4:

Results

Remove the external legs and keep only the internal graph structure:
This function takes one graph record and reconstructs only the graph formed by the internal interaction vertices and removes the external legs. These external legs are ignored as planarity is being tested only on the internal skeleton of the diagram.
Test whether the internal skeleton is planar:
Classify each graph as either a leading or subleading diagram:

Comparing planar and non-planar contributions

Compute the graph count and effective weighted share of each sector:
Example output for six vertices and two external legs:
This table separates the graphs into the planar and non-planar graphs. The “GraphCount” column gives the number of graphs in each sector while “EffectiveShare” gives the weight contribution.

Larger computation using Wolfram Compute Services

Limitations

Future directions

Conclusion

References

Birdtracks. (n.d.). Group theory: Birdtracks. https://birdtracks.eu/
Dmitrašinović, V. (2005). D(s0)+(2317)-D0(2308) mass difference as evidence for tetraquarks [Figure: Weight diagrams of SU(3) irreducible representations]. ResearchGate. https://www.researchgate.net/figure/Weight-diagrams-of-SU3-irreducible-representations-appearing-in-the-charmed-tetraquark_fig1_262064769
Ford, N. (n.d.). Quantum field theory III: Perturbation theory and Feynman diagrams. https://nicf.net/static/articles/qft-feynman.pdf
GeeksforGeeks. (2023). Count of all cycles without any inner cycle in a given graph. https://www.geeksforgeeks.org/dsa/count-of-all-cycles-without-any-inner-cycle-in-a-given-graph/
Morais, A. P. (2016). Feynman diagrams for beginners. arXiv. https://arxiv.org/pdf/1602.04182
Physics Stack Exchange. (2020). How to calculate the Feynman diagram under SU(Nc) group? https://physics.stackexchange.com/questions/555337/how-to-calculate-the-feynman-diagram-under-sun-c-group
Ross, D. (n.d.). Quantum field theory 3: Higher order corrections in scalar field theory. University of Southampton. https://www.southampton.ac.uk/~doug/qft/aqft1.pdf
Tong, D. (n.d.). Gauge theory: 6. Large N. University of Cambridge. https://davidtong.org/pdfs/teaching/gauge-theory/gauge6.pdf
Tong, D. (n.d.). Quantum field theory. University of Cambridge. https://davidtong.org/pdfs/teaching/quantum-field-theory/qft.pdf
Wikipedia contributors. (n.d.). Feynman diagram. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Feynman_diagram
Wikipedia contributors. (n.d.). Gerard ’t Hooft. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Gerard_%27t_Hooft
Wikipedia contributors. (n.d.). Lie algebra. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Lie_algebra
Wikipedia contributors. (n.d.). Quantum chromodynamics. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Quantum_chromodynamics
Wikipedia contributors. (n.d.). Special unitary group. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Special_unitary_group
Wikipedia contributors. (n.d.). Tadpole (physics). In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Tadpole_(physics)
Wikipedia contributors. (n.d.). Tuple. In Wikipedia. Retrieved July 8, 2026, from https://en.wikipedia.org/wiki/Tuple
Zee, A. (2018). On gravity: A brief tour of a weighty subject [Excerpt]. Princeton University Press. https://assets.cambridge.org/97811084/86217/excerpt/9781108486217_excerpt.pdf

Acknowledgements

I would like to thank my mentor Bruno Tenorio for guiding the development of this project and helping me refine both the computational and conceptual direction of this work. I would also like to thank my TA Qianheng (Hendry) Xu for his support throughout the project. I am thankful to Stephen Wolfram and the directors for helping select this project and for indicating the direction I should take it. I am also grateful to all the TAs and mentors who helped me along the way, even with small details like finding a replacement for the IsomorphicGraphQ function. Finally, I would like to thank my TA group, my advocate group, and my mentor group for making the whole journey enjoyable and memorable.

CITE THIS NOTEBOOK

Computational enumeration of Phi cubed Feynman diagrams​
by Ekagra Fuloria​
Wolfram Community, STAFF PICKS, July 9, 2026
​https://community.wolfram.com/groups/-/m/t/3749798