Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
A direct command skips the review prompt. Inspect the source before running it.
ACSets (Attributed C-Sets): Algebraic databases with Specter-style bidirectional
version
1.0.0
ACSets: Algebraic Databases Skill
"The category of simple graphs does not even have a terminal object!"
โ AlgebraicJulia Blog, with characteristic ironic detachment
bmorphism Contributions
"Parametrised optics model cybernetic systems, namely dynamical systems steered by one or more agents. Then โ represents agency being exerted on systems"
โ @bmorphism, GitHub bio
"universal topos construction for social cognition and democratization of mathematical approach to problem-solving to all"
โ Plurigrid: the story thus far
ACSets ("attributed C-sets") are a family of data structures generalizing both graphs and data frames. They are an efficient in-memory implementation of a category-theoretic formalism for relational databases.
C-set = Functor X: C โ Set where C is a small category (schema)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Schema (Small Category C) โ
โ โโโโโโโ src โโโโโโโ โ
โ โ E โโโโโโโโโถโ V โ โ
โ โ โ tgt โ โ โ
โ โโโโฌโโโโโโโโโโโถโโโโโโโ โ
โ โ โ
โ โ A C-set X assigns: โ
โ โ X(V) = set of vertices โ
โ โ X(E) = set of edges โ
โ โ X(src): X(E) โ X(V) โ
โ โ X(tgt): X(E) โ X(V) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Core Concepts
1. Schema Definition
using Catlab.CategoricalAlgebra
@present SchGraph(FreeSchema) begin
V::Ob
E::Ob
src::Hom(E,V)
tgt::Hom(E,V)
end
@acset_type Graph(SchGraph, index=[:src,:tgt])
2. Symmetric Graphs (Undirected)
@present SchSymmetricGraph <: SchGraph begin
inv::Hom(E,E)
compose(inv,src) == tgt
compose(inv,tgt) == src
compose(inv,inv) == id(E)
end
@acset_type SymmetricGraph(SchSymmetricGraph, index=[:src])
3. Attributed ACSets (with Data)
@present SchWeightedGraph <: SchGraph begin
Weight::AttrType
weight::Attr(E, Weight)
end
@acset_type WeightedGraph(SchWeightedGraph, index=[:src,:tgt]){Float64}
GF(3) Conservation for ACSets
Integrate with Music Topos 3-coloring:
# Map ACSet parts to trits for GF(3) conservation
function acset_to_trits(g::Graph, seed::UInt64)
rng = SplitMix64(seed)
trits = Int[]
for e in parts(g, :E)
h = next_u64!(rng)
hue = (h >> 16 & 0xffff) / 65535.0 * 360
trit = hue < 60 || hue >= 300 ? 1 :
hue < 180 ? 0 : -1
push!(trits, trit)
end
trits
end
# Verify conservation: sum(trits) โก 0 (mod 3)
function gf3_conserved(trits)
sum(trits) % 3 == 0
end
Gay.jl Color Bindings for @acset_colim (PR #990)
Since Catlab PR #990, @acset_colim exposes nameโpart bindings. Combine with Gay.jl for:
Named Part Coloring
using Gay
# Build ACSet with named parts
result, bindings = @acset_colim SchGraph begin
e::E
v1::V; v2::V
src(e) == v1
tgt(e) == v2
end
# Color each named part deterministically
seed = 0x114514
colors = Dict{Symbol, String}()
for (name, (ob, idx)) in bindings
colors[name] = Gay.color_at(seed, idx) # deterministic hex
end
# => Dict(:e => "#A855F7", :v1 => "#3B82F6", :v2 => "#10B981")
Two Modalities for XOR Validation
Modality 1: Different seeds (parallel verification)
seeds = [0x1, 0x2, 0x3] # Three independent streams
colors_per_seed = [Gay.palette(s, length(bindings)) for s in seeds]
# XOR guarantee: if all three agree on structure, computation is stable
# Divergence โ indicates floating-point or algorithmic instability
Modality 2: Same seed, staggered indices (convergence test)
seed = 0x114514
# Run same computation 3 times, color at indices 1, 2, 3
c1 = compute_and_color(data, seed, index=1)
c2 = compute_and_color(data, seed, index=2)
c3 = compute_and_color(data, seed, index=3)
# Convergence: c1 == c2 == c3 within bounded iterations โ stable
# Divergence: colors differ โ numerical instability detected automatically
Empty Block = Initial Object = Neutral Color
# Empty block produces initial object (fixed in PR #990)
init, _ = @acset_colim SchGraph begin end # โ
# Initial object gets neutral/zero color (the "0" in GF(3))
neutral_color = Gay.color_at(seed, 0) # or special "initial" marker
Bidirectional Index with Color Tags
struct ColoredACSet{T}
acset::T
bindings::Dict{Symbol, Tuple{Symbol, Int}}
colors::Dict{Symbol, String}
seed::UInt64
end
function colored_acset_colim(schema, seed, block)
acset, bindings = @acset_colim schema block
colors = Dict(name => Gay.color_at(seed, idx)
for (name, (_, idx)) in bindings)
ColoredACSet(acset, bindings, colors, seed)
end
# Lookup by name โ part index โ color (all directions)
# name โ color: ca.colors[:v1]
# color โ name: findfirst(==(hex), ca.colors)
# name โ part: ca.bindings[:v1][2]
Instability Detection Pattern
function detect_instability(f, input, seed; tolerance=3)
"""
Run f three times with same seed at staggered indices.
If colors diverge beyond tolerance, flag instability.
"""
results = [f(input) for _ in 1:3]
colors = [Gay.color_at(seed, i) for i in 1:3]
# Compare results - if they should be identical but aren't,
# the divergent colors make the instability visually obvious
for i in 1:3, j in i+1:3
if results[i] โ results[j]
@warn "Instability detected" color_i=colors[i] color_j=colors[j]
return false
end
end
true
end
This integrates the semantic naming from PR #990 with Gay.jl's deterministic coloring to create self-validating, visually debuggable ACSet constructions.
Specter-Style Bidirectional Navigation
Inspired by Nathan Marz's Specter library, navigate ACSets with paths that work for both select AND transform.
The Key Insight: comp-navs = alloc + field sets
From Marz: "comp-navs is fast because it's just object allocation + field sets"
# What comp_navs actually does:
comp_navs(a, b, c) = ComposedNav([a, b, c]) # That's it!
# No compilation, no interpretation, no optimization
# Just: allocate struct, set field, done
# All work happens at traversal via CPS:
nav_select(nav1, data,
r1 -> nav_select(nav2, r1,
r2 -> nav_select(nav3, r2, identity)))
using SpecterACSet
# Navigate morphism values
acset_field(:E, :src) # All source vertex IDs
acset_field(:E, :tgt) # All target vertex IDs
# Filter parts by predicate
acset_where(:E, :src, ==(1)) # Edges where src == 1
# Navigate all parts of an object
acset_parts(:V) # All vertex IDs
acset_parts(:E) # All edge IDs
Bidirectional Example
g = @acset Graph begin V=4; E=3; src=[1,2,3]; tgt=[2,3,4] end
# Select: get all source vertices
select([acset_field(:E, :src)], g) # โ [1, 2, 3]
# Transform: shift all targets (same path!)
g2 = transform([acset_field(:E, :tgt)], t -> mod1(t+1, 4), g)
select([acset_field(:E, :tgt)], g2) # โ [3, 4, 1]
Cross-Domain Bridge (Sexp โ ACSet)
# ACSet โ Sexp โ Navigate โ Transform โ Sexp โ ACSet
sexp = sexp_of_acset(g)
# Navigate sexp to find all morphism names
morphism_names = select([SEXP_CHILDREN, sexp_nth(1), ATOM_VALUE], sexp)
# Roundtrip back to ACSet
g2 = acset_of_sexp(Graph, sexp)
Higher-Order Functions on ACSets
From Issue #7, implement functional patterns:
Function
Description
Example
map
Transform parts
map(g, :E) do e; ... end
filter
Select parts by predicate
`filter(g, :V) {
fold
Aggregate over parts
fold(+, g, :E, :weight)
Open ACSets (Composable Interfaces)
# From Issue #89: Open versions of InterType ACSets
using ACSets.OpenACSetTypes
# Create open ACSet with exposed ports
@open_acset_type OpenGraph(SchGraph, [:V])
# Compose via pushout
g1 = OpenGraph(...) # ports: v1, v2
g2 = OpenGraph(...) # ports: v3, v4
g_composed = compose(g1, g2, [:v2 => :v3])
Why Simple Graphs Are Badly Behaved
The category of simple graphs does not even have a terminal object. Under the standard definition (symmetric, irreflexive edge relation), there's no "universal" graph that every other graph maps to uniquely. This reveals hidden assumptions in the simple graph model.
Simple graph: G = (V, E) where E is a binary relation on V that is:
Symmetric: E(v,u) whenever E(u,v)
Irreflexive: E(v,v) for no vertex v
Category theorist's graph: G consists of:
Vertex set G(V)
Edge set G(E)
Functions G(src), G(tgt): G(E) โ G(V)
This allows:
Multiple edges between vertices (multigraph)
Self-loops
Edges as first-class citizens with identity
C-Sets: The Mathematical Foundation
A C-set is a functor X: C โ Set where C is a small category (schema).
Schema C (small category) C-set X (functor C โ Set)
โโโโโโโโโโโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโโโโโโโ
Objects c โ C โโโโโถ Sets X(c)
Morphisms f: c โ d โโโโโถ Functions X(f): X(c) โ X(d)
Terminology
Term
Definition
C-set
Functor C โ Set (copresheaf)
Presheaf
Functor C^op โ Set (contravariant)
Category action
C-set generalizes G-set (group action)
The Schema for Graphs
The schema Sch(Graph) is the category with:
Two objects: E, V
Two non-identity morphisms: src: E โ V, tgt: E โ V
โโโโโ src โโโโโ
โ E โโโโโโโโโถโ V โ
โ โ tgt โ โ
โโโโโโโโโโโโโถโโโโโ
A graph G is a Sch(Graph)-set, meaning:
G(V) = set of vertices
G(E) = set of edges
G(src): G(E) โ G(V) assigns source vertex to each edge
G(tgt): G(E) โ G(V) assigns target vertex to each edge
The index=[:src,:tgt] parameter creates inverse lookups:
@acset_type Graph(SchGraph, index=[:src,:tgt])
# Without index: O(|E|) to find edges incident to vertex
# With index: O(k) where k = number of incident edges
Symmetric Graphs (Undirected)
The schema for symmetric graphs extends the graph schema with an involution:
@present SchHalfEdgeGraph(FreeSchema) begin
V::Ob
H::Ob
vertex::Hom(H,V)
inv::Hom(H,H)
compose(inv, inv) == id(H)
end
@acset_type HalfEdgeGraph(SchHalfEdgeGraph, index=[:vertex])
Dangling Edges
Fixed points hยทinv = h are dangling edges โ half-edges not paired with others, left at the boundary. Distinct half-edges h โ h' with hยทinv = h' and same vertex are self-loops.
V โฆ V
E โฆ H
src โฆ vertex
tgt โฆ inv โจ vertex
inv โฆ inv
This is an isomorphism (invertible). The two perspectives are mathematically equivalent but have different interpretations.
Rotation Systems (Topological Graph Theory)
A rotation system adds a permutation ฯ on half-edges where cycles = vertices:
@present SchRotationGraph <: SchHalfEdgeGraph begin
ฯ::Hom(H,H)
compose(ฯ, vertex) == vertex # cycles stay at same vertex
end
The schema Sch(RotSys) with just H, ฮฑ (involution), ฯ (permutation) is a group โ making rotation systems group actions.
Key theorem: Rotation systems โ cellular embeddings in oriented surfaces (1-1 correspondence).
Reflexive Graphs (Blog III)
A reflexive graph has a distinguished self-loop at each vertex:
โโโโโ src โโโโโ refl
โ E โโโโโโโโโถโ V โโโโโโโโโถโ E โ
โ โ tgt โ โ
โโโโโโโโโโโโโถโโโโโ
Equations:
refl โจ src = id_V
refl โจ tgt = id_V
@present SchReflexiveGraph <: SchGraph begin
refl::Hom(V,E)
compose(refl, src) == id(V)
compose(refl, tgt) == id(V)
end
C-Set Homomorphisms
A homomorphism ฮฑ: X โ Y of C-sets is a natural transformation:
For each object c โ C: function ฮฑ_c: X(c) โ Y(c)
For each morphism f: c โ d: naturality square commutes
X(c) โโฮฑ_cโโโถ Y(c)
โ โ
X(f) Y(f)
โผ โผ
X(d) โโฮฑ_dโโโถ Y(d)
ยฌ vs ~ notation collision: Conflicts with standard pseudocomplement usage
No connection to ACSets.jl code: Gap between categorical exposition and macros
+1 Appreciation (Positive)
Schema isomorphism reveals deep identity: Source-target graphs = half-edge graphs in different clothes
Rotation systems as groups: Invertibility + algebraic topology without coordinates
Dangling edges model open systems: Boundary conditions, interfaces solved structurally
Reflexive homomorphisms = quotients done right: Contraction/abstraction as natural transformations
Two negations unify constructive/classical: Failure of excluded middle is topology through logic
โA = A โง ~A: Boundary operator derived from pure logic (Stokes theorem intuition)
Product proliferation is a feature: Each schema choice gives different tensor product
0 Neutral Integration
The skill presents the neutral synthesis: rigorous definitions with practical Catlab code, acknowledging both the elegant mathematical structure and the implementation gaps.
Key References
Reyes, Reyes & Zolfaghari (2004): Generic Figures and Their Glueings โ C-sets as category actions
Spivak (2009): Higher-Dimensional Models of Networks โ C-sets for scientific modeling
Lando & Zvonkin (2004): Graphs on Surfaces โ Rotation systems, ฯ/ฮฑ notation
# Two views must agree on pooled representation
function verify_sheaf_condition(patches; threshold=0.99)
pooled1 = patches[view1, :pooled_repr]
pooled2 = patches[view2, :pooled_repr]
cosine_sim(pooled1, pooled2) >= threshold
end
Gay.jl Label Ontology for ACSets
Gay.jl uses a categorical label system on GitHub issues. These map directly to ACSet concepts:
# H^1 = 0 โ proper sheaf (sections glue)
# H^1 โ 0 โ obstruction class exists
function cech_h1_obstruction(seed, depth=7)
fingerprints = [xor_fingerprint(gay_split(seed, i)) for i in 1:depth]
# Check if local fingerprints glue globally
failures = count(i -> fingerprints[i] โป fingerprints[i+1] โ expected[i], 1:depth-1)
failures > 0 # true = obstruction detected
end
5. Coherence Violation Detection (Issue #214)
Mac Lane's pentagon and hexagon for monoidal categories:
# Pentagon identity for 4-way associativity
# ((a โ b) โ c) โ d must equal a โ (b โ (c โ d))
# via ANY path through the pentagon
function verify_pentagon(split_fn, seed)
paths = enumerate_pentagon_paths(seed)
all(p1 โ p2 for (p1, p2) in pairs(paths))
end
# Hexagon identity for braiding
function verify_hexagon(split_fn, seed)
# ฯ_{a,bโc} = (1 โ ฯ_{a,c}) โ (ฯ_{a,b} โ 1)
left_path = braid_then_tensor(seed)
right_path = tensor_then_braid(seed)
left_path โ right_path
end
using AlgebraicRewriting, Gay
# Rule: merge two vertices
rule = @rule SchGraph begin
L = @acset begin v1::V; v2::V end
R = @acset begin v::V end
# L โ R collapses v1, v2 โ v
end
# Color the rewrite
seed = 1069
L_colors = Gay.palette(seed, nparts(L, :V))
R_colors = Gay.palette(seed, nparts(R, :V))
# Verify: rewrite preserves GF(3) trit sum
@assert gf3_conserved(L_colors) == gf3_conserved(R_colors)
8. Structured Decomposition Adhesions
From StructuredDecompositions.jl + Gay.jl:
# Adhesion = shared boundary between decomposition pieces
# Color adhesions to track gluing
struct ColoredAdhesion
left_piece::ACSet
right_piece::ACSet
adhesion::ACSet # shared sub-ACSet
color::String # Gay.jl deterministic color
end
function color_decomposition(decomp, seed)
[ColoredAdhesion(
piece.left, piece.right, piece.adhesion,
Gay.color_at(seed, i)
) for (i, piece) in enumerate(decomp.pieces)]
end
Related Packages
Catlab.jl: Full categorical algebra (homomorphisms, limits, colimits)
Gay.jl: Deterministic colors with SPI + sheaf obstruction detection
Xenomodern Integration
The ironic detachment comes from recognizing that:
Category theory isn't about abstraction for its own sake โ it's about finding the right abstractions that compose
Simple graphs are actually badly behaved โ the terminal object problem reveals hidden assumptions
Functors are data structures โ this reframes databases as applied category theory
xenomodernity
โ
โโโโโโโโโโโดโโโโโโโโโโ
โ โ
ironic sincere
detachment engagement
โ โ
โโโโโโโโโโโฌโโโโโโโโโโ
โ
C-sets as functors
(both ironic AND sincere)
Ramanujan Spectral Integration (NEW 2025-12-22)
ACSet-based edge growth with spectral constraints:
Ramanujan-Preserving Growth
@present SchRamanujanGraph <: SchGraph begin
SpectralData::AttrType
lambda2::Attr(V, SpectralData) # Track ฮปโ per growth step
end
function grow_edge_ramanujan!(G::ACSet, u, v)
"""
Add edge preserving Ramanujan property.
Uses Alon-Boppana bound: ฮปโ โฅ 2โ(d-1).
"""
d = degree(G)
bound = 2 * sqrt(d - 1)
# Tentatively add edge
add_part!(G, :E, src=u, tgt=v)
# Check spectral constraint
ฮปโ = second_eigenvalue(adjacency_matrix(G))
if ฮปโ > bound + 0.01 # Tolerance
# Rollback: remove edge
rem_part!(G, :E, nparts(G, :E))
return false
end
return true
end
Non-Backtracking Edge Schema (Ihara Zeta)
@present SchNonBacktracking(FreeSchema) begin
V::Ob; E::Ob; DE::Ob # DE = directed edges
src::Hom(E,V); tgt::Hom(E,V)
forward::Hom(E, DE); backward::Hom(E, DE)
de_src::Hom(DE, V); de_tgt::Hom(DE, V)
# Non-backtracking constraint: head(e) = tail(f) โง e โ fโปยน
nonbacktrack::Hom(DE, DE) # B matrix as morphism
end
# Ihara zeta via ACSet homomorphisms
function prime_cycles(G::ACSet, max_length)
cycles = []
for k in 1:max_length
if moebius(k) != 0 # Only squarefree lengths
push!(cycles, find_cycles(G, k))
end
end
return cycles
end
Centrality via Mรถbius Inversion
function alternating_centrality(G::ACSet)
"""
Centrality via Mรถbius-weighted path counts.
c(v) = ฮฃ_{k} ฮผ(k) ร paths_k(v) / k
"""
n = nparts(G, :V)
A = adjacency_matrix(G)
c = zeros(n)
for k in 1:diameter(G)
ฮผ_k = moebius(k)
if ฮผ_k != 0
paths_k = diag(A^k)
c .+= ฮผ_k .* paths_k ./ k
end
end
return c ./ sum(abs.(c))
end
StructACSet Internals (DeepWiki 2025-12-22)
Schema and attribute types known at compile time, enabling performance optimizations.
Type Parameters
StructACSet{S, Ts, PT}
# S = TypeLevelSchema{Symbol} - schema at compile time
# Ts = Tuple of Julia types for attributes (e.g., Float64 for Weight)
# PT = PartsType strategy (IntParts or BitSetParts)
Column Storage
struct StructACSet{S, Ts, PT}
parts::NamedTuple # {:V => IntParts, :E => IntParts, ...}
subparts::NamedTuple # {:src => Column, :tgt => Column, :weight => Column, ...}
end
# Column types:
# - Homs: Vector{Int} mapping parts to parts
# - Attrs: Vector{Union{AttrVar,T}} for attribute values
Index Configuration
@acset_type Graph(SchGraph,
index=[:src, :tgt], # Preimage cache: O(1) incident queries
unique_index=[:inv] # Injective cache: even faster
)
# Index types:
# - NoIndex: Linear scan O(n)
# - Index (StoredPreimageCache): O(1) average via hash
# - UniqueIndex (InjectiveCache): O(1) guaranteed, injective morphisms only
Part ID Allocation Strategies
Strategy
Type
Deletion
Use Case
IntParts
DenseParts
Pop-and-swap (renumbers)
Fast, compact storage
BitSetParts
MarkAsDeleted
Preserves IDs, requires gc!()
Stable references
# IntParts (default): contiguous IDs 1..n
add_parts!(G, :V, 3) # IDs: 1, 2, 3
rem_part!(G, :V, 2) # ID 3 โ 2, ID 2 gone
# BitSetParts: sparse IDs with gaps
rem_part!(G, :V, 2) # ID 2 marked deleted, ID 3 unchanged
gc!(G) # Compact: removes gaps
Julia Scientific Package Integration
From julia-scientific skill - the full Julia package ecosystem that builds on ACSets:
Package
Category
ACSet Integration
Catlab.jl
Core
Schema definitions, morphisms
AlgebraicRewriting.jl
Rewriting
Double-pushout rewriting
StructuredDecompositions.jl
Sheaves
Tree decomposition sheaves
AlgebraicDynamics.jl
Dynamics
Compositional ODEs
DataFrames.jl
Data
Tabular data (special case)
Graphs.jl
Networks
Graph ACSet instances
MolecularGraph.jl
Chemistry
Molecular graphs
BioStructures.jl
Bioinformatics
Protein structure graphs
GraphNeuralNetworks.jl
ML
GNN on ACSet graphs
The Homoiconic Bridge: Scheme โ SMILES โ ACSet
Deep structural insight: S-expressions (Scheme), SMILES strings (chemistry), and ACSets share the same foundation โ trees/graphs with recursive self-reference:
S-expression: (+ (* 2 3) (- 4 1)) โ AST tree
SMILES: CC(=O)Oc1ccccc1C(=O)O โ Molecular graph
ACSet: Graph{V,E,src,tgt} โ Typed graph functor
All three: linearized representations of graph structure
# The bridge in code
using LispSyntax, MolecularGraph, Catlab
# Scheme โ AST graph
sexp = @lisp (defun factorial (n) (if (<= n 1) 1 (* n (factorial (- n 1)))))
ast_graph = ast_to_acset(sexp)
# SMILES โ Molecular graph
mol = smilestomol("CC(=O)Oc1ccccc1C(=O)O") # Aspirin
mol_graph = mol_to_acset(mol)
# Both are ACSets! Same navigation works:
select([ALL, pred(is_branch_node)], ast_graph)
select([ALL, pred(is_ring_atom)], mol_graph)
What Comes After SMILES: Learnable Chemical Structure
The evolution: 7 parallel streams colored via Gay.jl (seed=137):
Gen
Color
Representation
Julia Package
Learnable?
1
#43D9E1
SMILES
MolecularGraph.jl
No
2
#18CDEF
SELFIES
PyCall+selfies
No
3
#18D6D0
Fingerprints
MolecularGraph.jl
Partially
4
#C70D22
Graph features
ChemistryFeaturization.jl
Partially
5
#E44ABB
GNN (MPNN/GAT/SchNet)
GraphNeuralNetworks.jl
Yes
6
#58A021
3D coordinates
Chemfiles.jl, DFTK.jl
Yes
7
#BDB223
Foundation
GraphNeuralNetworks.jl
Fully
Parallel streams evolve independently โ each generation has its own color trajectory:
just acset-demo # Run ACSet demonstration
just acset-graph # Create and visualize graph
just acset-symmetric # Symmetric graph example
just acset-gf3 # Check GF(3) conservation
Integration with โซG (Category of Elements)
Navigate the category of elements using paths:
# โซG objects: (Ob, part_id) pairs
# Navigate to all elements
select([elements_of(:V)], g) # โ [(V,1), (V,2), (V,3), (V,4)]
# Navigate morphism structure
select([elements_of(:E), incident_to(:src, 1)], g) # Edges from vertex 1
SDF Interleaving
This skill connects to Software Design for Flexibility (Hanson & Sussman, 2021):