Diagrammatic Computation (DC) involves representing operations and their connections visually, much like diagrams in mathematics or engineering. This approach emphasizes modular building blocks that can be composed in various ways to model complex systems. To illustrate these concepts computationally, we start by installing and loading the paclet:
In[534]:=
PacletInstall["Wolfram/DiagrammaticComputation"]
Out[534]=
PacletObject
Name:Wolfram/DiagrammaticComputation
Version:1.0.4
In[535]:=
<<Wolfram`DiagrammaticComputation`
Core Ideas of Diagrammatic Thinking
At its essence, DC treats computations as abstract structures: boxes representing operations, with ports for inputs and outputs. These can be wired together to form larger diagrams, revealing patterns and relationships that might be less apparent in linear code. The focus is on composition, modularity, and visualization, rather than specific applications.
Constructing a Diagram
Consider representing addition with two inputs (x) and (y) and one output (z):
In[12]:=
Diagram
[Plus,{x,y},z]
Out[12]=
This creates a symbolic diagram: a box labeled "Plus" with input ports (x) and (y), and a single output port (z). It remains unevaluated, serving as a blueprint. By default, such diagrams format visually, showing wires connecting the ports to the operation.
There are a few custom shapes that will be useful for changing diagram default appearance:
Vertical composition connects diagrams end-to-end, with outputs from one feeding inputs to the next, mirroring function composition. Matching port labels enable automatic wiring. Consider numerical operations: one doubles a number, another increments it by 1:
In[42]:=
double=
Diagram
["[2×]",x,y]
Out[42]=
In[43]:=
increment=
Diagram
["[+1]",y,z]
Out[43]=
Vertical composition (increment after double):
In[44]:=
DiagramComposition
[increment,double]
Out[44]=
DiagramRightComposition composes vertically in reverse:
This connects (y) from double to (y) in increment. The rendering shows stacked boxes connected by a wire. Using standard sequential Composition (@*) this can be represented like this:
In[18]:=
("[+1]"@*"[2×]")@3
Out[18]=
[+1][[2×][3]]
Or using RightComposition (\*) producing the same expression:
In[543]:=
("[2×]"/*"[+1]")@3
Out[543]=
[+1][[2×][3]]
If abstract nodes are replaced by actual functions, this would produce the expected result. For input 3, double produces 6, increment yields 7:
In[544]:=
((y2y)/*(xx+1))@3
Out[544]=
7
We'll create function representations of diagrams like this and more complex ones automatically later. But for now, let's try to compose these functions in the opposite order:
In[545]:=
DiagramComposition
[double,increment]
Out[545]=
The result may be unexpected because port (z) is followed by port (x), which do not match, so the diagrams compose horizontally in parallel. To fix this, it is possible to reassign ports for a diagram. For example, a new double diagram with adjusted port names can be constructed like this:
In[45]:=
double2=
Diagram
[double,z,x]
Out[45]=
With the new input port name matching the output of the incrementing diagram, composition works correctly:
In[46]:=
DiagramComposition
[double2,increment]
Out[46]=
Which, after turning into a function, would produce a different result:
In[548]:=
((zz+1)/*(y2y))@3
Out[548]=
8
The parallel composition above can also be done directly using DiagramProduct in any order; this ensures that ports of horizontally composed diagrams never wire together:
In[47]:=
DiagramProduct
[increment,double]
Out[47]=
In[48]:=
DiagramProduct
[double,increment]
Out[48]=
These diagrams also show that, in principle, diagrams can have multiple inputs and multiple outputs:
This also includes zero inputs and/or zero outputs:
For example, the input to a computation can be represented as a diagram with no inputs:
And it can also be used in a composition to diagrammatically represent the whole computation:
Diagram functions
Building on simple compositions, we can create more intricate compositions by combining vertical and horizontal arrangements, introducing branching and merging. This allows modeling workflows with parallel paths that diverge and reconverge, such as processing a number through multiple operations before combining results.
To turn diagrams into functions, its symbolic label has to be annotated with functional code, for example:
Given this more verbose diagram, we can turn it into a function:
With such function annotations it is possible to build a more complex diagram, now using more convenient ColumnDiagram and RowDiagram constructors:
Input 3 doubles to 6, one copy is incremented to 7 and another squared to 36, which then adds to 43:
By default it is assumed that functions take a sequence and output a sequence, but both input and output can also be either a List or an Association.
The List output would be Indexed and Association would incorporate port expressions as keys:
Diagram networks
Rather than arranging diagrams in a grid-like fashion, there is a more flexible way to wire arbitrary ports using DiagramNetwork, which connects all equivalent ports independent of their position within or across all diagrams.
For example it is possible to create a loop by wiring input and output together:
Or a loop that includes more that one diagram:
It is always possible to arrange such networks into a grid by introducing special "u-turn" diagrams, conventionally named caps and cups:
Wire diagrams
String diagrams include special diagrams which have some string properties and act as a generalization of identity diagram weaving single wires:
In addition there are some diagrams that have a special role in process theories and behave like arbitrary input/output generalization of identity:
Diagram surgery
Diagrams have hierarchical structure and can be decomposed into their constituents in multiple ways.
Decompose into tree-like expression consisting of diagram nodes only:
Diagram tensors
Wire diagrams have special representations as tensors: