Diagrammatic Computation 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 loading the paclet:
PacletInstall["Wolfram/DiagrammaticComputation"]
In[1]:=
<<Wolfram`DiagrammaticComputation`
Core Ideas of Diagrammatic Thinking
At its essence, diagrammatic computation 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.
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:
If abstract nodes are replaced by actual functions, this would produce the expected result. For input 3, double produces 6, increment yields 7:
In[10]:=
((y2y)/*(xx+1))@3
Out[10]=
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 diagrams in the opposite order:
In[11]:=
DiagramComposition
[double,increment]
Out[11]=
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[12]:=
double2=
Diagram
[double,z,x]
Out[12]=
With the new input port name matching the output of the incrementing diagram, composition works correctly:
In[13]:=
DiagramComposition
[double2,increment]
Out[13]=
Which, after turning into a function, would produce a different result:
In[14]:=
((zz+1)/*(y2y))@3
Out[14]=
8
The parallel composition above can also be done directly using
in any order; this ensures that ports of horizontally composed diagrams never wire together:
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, the symbolic label has to be annotated with functional code, for example:
Given this more verbose diagram, we can turn it into a function:
Input 3 doubles to 6, one copy is incremented to 7 and another squared to 36, which then adds to 43:
Diagram Networks
For example it is possible to create a loop by wiring input and output together:
Or a loop that includes more than 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 a tree-like expression consisting of diagram nodes only:
Diagram Tensors
Wire diagrams have special representations as tensors: