Here we simply declare the relationship between the two schemas and say how we want to interpret it (the name of action which pushes data forward along the map is called sigma
and the one which moves data the opposite direction is called delta
).
Replacing code with data
Theme: code vs declaring relationships between abstractions.
Problem | Julia solution | AlgebraicJulia solution |
---|---|---|
Different pieces of a model need to be glued together. | Write a script which does the gluing or modifies how pieces are constructed. | Declare how overlap relates to the building blocks. (colimits) |
Different aspects of a model need to be combined / a distinction is needed. | Write a script which creates copies of one aspect for every part of the other aspect. | Declare how the different aspects interact with each other. (limits) |
We want to integrate systems at different levels of granularity. | Refactor the original code to incorporate the more detailed subsystem. | Separate syntax/semantics. Declare how the part relates to the whole at syntax level. (operads) |
We make a new assumption and want to migrate old knowledge into our new understanding. | Write a script to convert old data into updated data. | Declare how the new way of seeing the world (i.e. schema) is related to the old way. (data migration) |
So to summarize these past toy examples, there are some very general kinds of ways that we recognize a problem has shifted and we wish to reuse our old abstractions towards the new problem. In any specific scenario, one can always write an ad hoc function or script to handle this, but once you do this time after time, these one-off solutions become unmaintainable spaghetti. This is in contrast to the eat-your-cake-and-have-it-too solution on the right, where we have general solutions which do not need to be rewritten for every new scenario that pops up. And the way these compose together is transparent.
Of course, it’s not magic, and this strategy isn’t immediately available whenever we dream up of a new domain. Structural mathematics shows us what structure to look for in a new domain that allows us to do these things, though it’s still a case-by-case process to take domain-expert’s knowledge and find this structure. In the next section I’m going to give a high level picture of how this works.
Why does it work?
Informal definition: a category is a bunch of things that are related to each other
. . .
Key intuition: category theory is concerned with universal properties.
- These change something that we once thought of as a property of an object into a kind of relationship that object has towards related objects.
Goal of talk is not to communicate how category theory works at a technical level. This is why this is the only definition which will be featured in the talk. (…)
But it’s important to get an intuition for why it works to know it’s not magic. I think the philosophy of category theory, which encourages working with universal properties, is a good explanation. I’ll try to describe one example of the simplest universal property just to give you some intuition for how it works.
Example universal property: emptiness
Consider mathematical sets which are related to each other via functions.
The empty set is the unique set which has no elements in it.
But if we we look at how the empty set relates to all the other sets, we’ll eventually notice something about these relations.
. . .
The empty set is the unique set which has exactly one function into every other set.
So if you were to tell a high schooler what an empty set is, this is a perfectly good definition. However, it doesn’t automatically generalize to telling us what an empty matrix is, or what an empty dynamical system or graph is. (…)
Example universal property: emptiness
Consider colored graphs related to each other via vertex mappings which preserve color and edges.
The empty graph uniquely has no vertices nor edges in it.
But if we we look at how it relates to all the other graphs, we’ll eventually notice something characteristic.
The empty graph is the unique graph which has exactly one graph mapping into every other graph.
We can see that the yellow vertex lives within the triangle in one way but in the big triangle in two ways, etc. If I wanted to say why this is the empty graph, of course my first instinct would be to say “Just look at it! There are no vertices, no edges!”. But look that our same definition from earlier which says to look for the object which is related to every other object exactly once also applies here.
Universal properties and generalizable abstractions
Category theory enforces good conceptual hygeine - one isn’t allowed to depend on “implementation details” of the things which feature in its definitions.
This underlies the ability of models built in AlgebraicJulia to be extended and generalized without requiring messy code refactor.
When you structure your code around these definitions, you inherit this kind of generalizability.
Takeaways
CT is useful for the same reason interfaces are generally useful. In particular, CT provides generalized1 notions of
- multiplication / multidimensionality
- adding things side-by-side
- gluing things along a common boundary
- looking for a pattern
- find-and-replace a pattern
- parallel vs sequential processes
- Mad Libs style filling in of wildcards
- Zero and One
- A point
- “Open” systems
- Subsystems
- Enforcing equations
- Symmetry
. . .
These abstractions all fit very nicely with each other:
- conceptually built out of basic ideas of limits, colimits, and morphisms.
We can use them to replace a large amount of our code with high level, conceptual data.
I hope to have shown that just a few basic concepts in category theory, in particular morphisms, limits, and colimits, are a very versatile vocabulary of good abstractions. Various combinations of these are sufficient to capture all of the diverse kinds of activities you see up here in a very generic way that can be specialized to your domain once you view it categorically.
About the Topos Institute
- Vision: topos.institute
- Research: topos.site
- Blog: topos.site/blog
Mission: to shape technology for public benefit by advancing sciences of connection and integration.
Three pillars of our work, from theory to practice to social impact:
- Collaborative modeling in science and engineering
- Collective intelligence, including theories of systems and interaction
- Research ethics
- Physics simulations (PDEs) with Decapodes.jl
- Reaction networks with AlgebraicPetri.jl
- Epidemiological modeling with StockFlow.jl
- Agent-based modeling with AlgebraicRewriting.jl
- Interactive GUIs with Semagrams
Decapodes.jl: multiphysics modeling
"""Define the multiphysics"""
= @decapode DiffusionQuantities begin
Diffusion ::Form0{X}
C::Form1{X}
ϕ== k(d₀{X}(C)) # Fick's first law
ϕ end
= @decapode DiffusionQuantities begin
Advection ::Form0{X}
C::Form1{X}
(V, ϕ)== ∧₀₁{X}(C,V)
ϕ end
= @decapode DiffusionQuantities begin
Superposition ::Form0{X}
(C, Ċ)::Form1{X}
(ϕ, ϕ₁, ϕ₂)== ϕ₁ + ϕ₂
ϕ == ⋆₀⁻¹{X}(dual_d₁{X}(⋆₁{X}(ϕ)))
Ċ ∂ₜ{Form0{X}}(C) == Ċ
end
= @relation (C, V) begin
compose_diff_adv diffusion(C, ϕ₁)
advection(C, ϕ₂, V)
superposition(ϕ₁, ϕ₂, ϕ, C)
end
"""Geometry"""
= loadmesh(Torus_30x10()) mesh
"""Assign semantics to operators"""
= sym2func(mesh)
funcs :k] = Dict(:operator => 0.05 * I(ne(mesh)),
funcs[:type => MatrixFunc())
:⋆₁] = Dict(:operator => ⋆(Val{1}, mesh,
funcs[=DiagonalHodge()), :type => MatrixFunc());
hodge:∧₀₁] = Dict(:operator => (r, c,v)->r .=
funcs[∧(Tuple{0,1}, mesh, c, v), :type => InPlaceFunc())
Extending what I’ve said so far to the design of simulators amounts to the following:
- simulations should be compiled to code rather than written in code
- This is because we can do high level conceptual programming (e.g. limits and colimits) when we are working with data, whereas representing our simulation via code will forever condemn us to having every conceptual update require a painstaking manual code update that could be very challenging.
When we work at the low level of code, introducing a simple mathematical idea (for example adding a gravitation force) forces us to remind ourself of all the implementation details - there could be a cascade of changes required.
I can only briefly gesture at the work done by some of my colleagues in a compositional language for multiphysics. The idea is that there is a graphical language for specifying equations which rigorously corresponds to things like Fick’s law of diffusion and conservation of mass. This is just like how our Petri Nets were a nice graphical language which could rigorously be interpreted as Chemical Reaction Networks and how spans of models can be rigorously interpreted as models glued along an overlap.
There is some interesting math behind how this works which I won’t be able to get into, involving something called the “discrete exterior calculus”, but from a user perspective you can see us declaring each of these small diagrams and then composing them together along shared variables into a multi-physics. We then need to give a computational semantics by associating linear operators with some of the primitive building blocks, such as “multiplication by k” being 0.05 times the identity matrix.
We decoupled the high level physics from the geometry and the implementation.
Decapodes.jl: simulation
Resources
- Papers and talks: algebraicjulia.org
- Blog posts: algebraicjulia.org/blog and topos.site/blog
- Code: github.com/AlgebraicJulia
If you want to follow up on any the ideas in this talk, we have lots of papers, a couple of which I point out here to be potentially of interest to scientists. We also write blog posts intended to be more accessible and conversational, and I highlight a few here. Our code is always open source and we’re delighted to see it getting used. Lastly the talk itself is available online on my website, in case you want to click on links or see things in more detail (You can also see my extensive speaker notes there).
Footnotes
Defined by universal properties.↩︎