Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default Composition Diagrams #52

Merged
merged 7 commits into from
Jun 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/DiagrammaticEquations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ recursive_delete_parents, spacename, varname, unicode!, vec_to_dec!,
## collages
Collage, collate,
## composition
oapply, unique_by, unique_by!, OpenSummationDecapodeOb, OpenSummationDecapode, Open,
oapply, unique_by, unique_by!, OpenSummationDecapodeOb, OpenSummationDecapode, Open, default_composition_diagram,
## acset
SchDecapode, SchNamedDecapode, AbstractDecapode, AbstractNamedDecapode, NamedDecapode, SummationDecapode,
contract_operators!, contract_operators, add_constant!, add_parameter, fill_names!, dot_rename!, expand_operators, infer_state_names, recognize_types,
contract_operators!, contract_operators, add_constant!, add_parameter, fill_names!, dot_rename!, expand_operators, infer_state_names, infer_terminal_names, recognize_types,
resolve_overloads!, replace_names!,
apply_inference_rule_op1!, apply_inference_rule_op2!,
transfer_parents!, transfer_children!,
Expand Down
26 changes: 23 additions & 3 deletions src/acset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -202,10 +202,10 @@ end
""" function infer_states(d::SummationDecapode)

Find variables which have a time derivative or are not the source of a computation.
See also: [`infer_state_names`](@ref).
See also: [`infer_terminals`](@ref).
"""
function infer_states(d::SummationDecapode)
childless = filter(parts(d, :Var)) do v
parentless = filter(parts(d, :Var)) do v
length(incident(d, v, :tgt)) == 0 &&
length(incident(d, v, :res)) == 0 &&
length(incident(d, v, :sum)) == 0 &&
Expand All @@ -214,7 +214,7 @@ function infer_states(d::SummationDecapode)
parents_of_tvars =
union(d[incident(d,:∂ₜ, :op1), :src],
d[incident(d,:dt, :op1), :src])
union(childless, parents_of_tvars)
union(parentless, parents_of_tvars)
end

""" function infer_state_names(d)
Expand All @@ -224,6 +224,26 @@ See also: [`infer_states`](@ref).
"""
infer_state_names(d) = d[infer_states(d), :name]

""" function infer_terminals(d::SummationDecapode)
Find variables which have no children.
See also: [`infer_states`](@ref).
"""
function infer_terminals(d::SummationDecapode)
filter(parts(d, :Var)) do v
length(incident(d, v, :src)) == 0 &&
length(incident(d, v, :proj1)) == 0 &&
length(incident(d, v, :proj2)) == 0 &&
length(incident(d, v, :summand)) == 0
end
end

""" function infer_terminal_names(d)

Find names of variables which have no children.
See also: [`infer_terminals`](@ref).
"""
infer_terminal_names(d) = d[infer_terminals(d), :name]

""" function expand_operators(d::SummationDecapode)

Find operations that are compositions, and expand them with intermediate variables.
Expand Down
93 changes: 73 additions & 20 deletions src/composition.jl
Original file line number Diff line number Diff line change
@@ -1,26 +1,8 @@
## TODO: Decapodey

import Catlab.CategoricalAlgebra: apex, feet, legs
import Catlab.WiringDiagrams: oapply

OpenSummationDecapodeOb, OpenSummationDecapode = OpenACSetTypes(SummationDecapode, :Var)

#FIXME: why can't we just add a constructor for OpenSummationDecapode
""" Open(d::SummationDecapode{T,U,V}, names::AbstractVector{Symbol}) where {T,U,V}

creates an OpenSummationDecapode based on named variables rather than variable indices.
See AlgebraicPetri.jl's Open for the analogous verion for LabelledReactionNetworks.
"""
function Open(d::SummationDecapode{T,U,V}, names::AbstractVector{Symbol}) where {T,U,V}
legs = map(names) do name
FinFunction(incident(d, name, :name), nparts(d, :Var))
end
OpenSummationDecapode{T,U,V}(d, legs...)
end

apex(Decapode::OpenSummationDecapode) = apex(Decapode.cospan)
legs(Decapode::OpenSummationDecapode) = legs(Decapode.cospan)
feet(Decapode::OpenSummationDecapode) = Decapode.feet
# ACSet manipulation helper functions
# -----------------------------------

""" function unique_by!(acset, column_names::Vector{Symbol})

Expand Down Expand Up @@ -67,6 +49,28 @@
unique_by!(acset_copy, table, columns)
end

# Operadic composition
# --------------------

OpenSummationDecapodeOb, OpenSummationDecapode = OpenACSetTypes(SummationDecapode, :Var)

#FIXME: why can't we just add a constructor for OpenSummationDecapode
""" Open(d::SummationDecapode{T,U,V}, names::AbstractVector{Symbol}) where {T,U,V}

creates an OpenSummationDecapode based on named variables rather than variable indices.
See AlgebraicPetri.jl's Open for the analogous verion for LabelledReactionNetworks.
"""
function Open(d::SummationDecapode{T,U,V}, names::AbstractVector{Symbol}) where {T,U,V}
legs = map(names) do name
FinFunction(incident(d, name, :name), nparts(d, :Var))
end
OpenSummationDecapode{T,U,V}(d, legs...)
end

apex(Decapode::OpenSummationDecapode) = apex(Decapode.cospan)
legs(Decapode::OpenSummationDecapode) = legs(Decapode.cospan)
feet(Decapode::OpenSummationDecapode) = Decapode.feet

""" function type_check_Decapodes_composition(relation::RelationDiagram, decs::Vector{OpenSummationDecapode})

Check that the types of all Vars connected by the same junction match.
Expand Down Expand Up @@ -212,3 +216,52 @@
# oapply(r, OpenPode(Heat, [:H]))

oapply(r::RelationDiagram, pode::OpenSummationDecapode) = oapply(r, [pode])

# Default composition
# -------------------

# This helper function finds elements which appear in an array more than once.
function find_duplicates(vs::Vector{T}) where T
once, twice = Set{T}(), Set{T}()
foreach(v -> v ∈ once ? push!(twice,v) : push!(once,v), vs)
twice

Check warning on line 227 in src/composition.jl

View check run for this annotation

Codecov / codecov/patch

src/composition.jl#L227

Added line #L227 was not covered by tests
end

# TODO: Upstream this to Catlab?
function construct_relation_diagram(boxes::Vector{Symbol}, junctions::Vector{Vector{Symbol}})
lukem12345 marked this conversation as resolved.
Show resolved Hide resolved
tables = map(boxes, junctions) do b, j
Expr(:call, b, j...)
lukem12345 marked this conversation as resolved.
Show resolved Hide resolved
end
quote @relation () begin $(tables...) end end |> eval
end

# TODO: Add a macro which provides names for boxes via the Symbol of the Decapode.
""" function default_composition_diagram(podes::Vector{D}, names::Vector{Symbol}) where {D<:SummationDecapode}

Given a list of Decapodes and their names, return a composition diagram which assumes that variables sharing the same name ought to be composed.

No Literals are exposed. Use [`unique_lits!`](@ref) after composing.

Throw an error if any individual Decapode already contains a repeated name (except for Literals).

If `only_states_terminals` is `true`, only expose state and terminal variables. Defaults to `false`.

Note that composing immediately with [`oapply`](@ref) will fail if types do not match (e.g. (:infer, :Form0) or (:Form0, :Form1)).
"""
function default_composition_diagram(podes::Vector{D}, names::Vector{Symbol}, only_states_terminals=false) where {D<:SummationDecapode}
lukem12345 marked this conversation as resolved.
Show resolved Hide resolved
length(podes) == length(names) || error("$(length(podes)) models given, but $(length(names)) names provided.")
non_lit_names = map(podes) do pode
pode[findall(!=(:Literal), pode[:type]), :name]
end
for (nln, name) in zip(non_lit_names, names)
allunique(nln) || error("Decapode $name contains repeated variable names: $(find_duplicates(nln)).")
end
if only_states_terminals
foreach(non_lit_names, podes) do nln, pode
outers = infer_state_names(pode) ∪ infer_terminal_names(pode)
filter!(x -> x ∈ outers, nln)
end
end
construct_relation_diagram(names, non_lit_names)
end

105 changes: 105 additions & 0 deletions test/composition.jl
Original file line number Diff line number Diff line change
Expand Up @@ -195,4 +195,109 @@ end
# Test none of the decapodes were mutated
@test isequal(adv_adv, deep_copies)

@testset "Default Composition Diagrams" begin
lukem12345 marked this conversation as resolved.
Show resolved Hide resolved
# Compose Diffusion, Advection, and Superposition.
# Observe that we have pre-named ϕ to ϕ₁ or ϕ₂.
# Note that only one Decapode contains TVars.
Diffusion = @decapode begin
C::Form0
ϕ₁::Form1
ϕ₁ == ∘(k, d₀)(C)
end
Advection = @decapode begin
C::Form0
(V, ϕ₂)::Form1
ϕ₂ == ∧₀₁(C,V)
end
Superposition = @decapode begin
(C, Ċ)::Form0
(ϕ, ϕ₁, ϕ₂)::Form1
ϕ == ϕ₁ + ϕ₂
Ċ == ∘(⋆₀⁻¹, dual_d₁, ⋆₁)(ϕ)
∂ₜ(C) == Ċ
end
expected =
@relation () begin
Diffusion(C,ϕ₁)
Advection(C,V,ϕ₂)
Superposition(C,ϕ₁,ϕ₂,Ċ,ϕ)
end
@test is_isomorphic(expected, default_composition_diagram(
[Diffusion, Advection, Superposition],
[:Diffusion, :Advection, :Superposition]))
lukem12345 marked this conversation as resolved.
Show resolved Hide resolved

# Compose Halfar's equation with Glen's law.
GlensLaw = @decapode begin
Γ::Form1
(A,ρ,g,n)::Constant

Γ == (2/(n+2))*A*(ρ*g)^n
end
HalfarsEquation = @decapode begin
h::Form0
Γ::Form1
n::Constant

∂ₜ(h) == ∘(⋆, d, ⋆)(Γ * d(h) ∧ (mag(♯(d(h)))^(n-1)) ∧ (h^(n+2)))
end
expected =
@relation () begin
GlensLaw(Γ,A,ρ,g,n)
HalfarsEquation(h,Γ,n,ḣ)
end
@test is_isomorphic(expected, default_composition_diagram(
[GlensLaw, HalfarsEquation],
[:GlensLaw, :HalfarsEquation],
true))

# Test an error is thrown when # of models ≠ # of names.
Diffusion = @decapode begin
C::Form0
ϕ₁::Form1
ϕ₁ == ∘(k, d₀)(C)
end
Advection = @decapode begin
C::Form0
(V, ϕ₂)::Form1
ϕ₂ == ∧₀₁(C,V)
end
Superposition = @decapode begin
(C, Ċ)::Form0
(ϕ, ϕ₁, ϕ₂)::Form1
ϕ == ϕ₁ + ϕ₂
Ċ == ∘(⋆₀⁻¹, dual_d₁, ⋆₁)(ϕ)
∂ₜ(C) == Ċ
end
@test_throws "3 models given, but 2 names provided." default_composition_diagram(
[Diffusion, Advection, Superposition],
[:Diffusion, :Advection])

# Test an error is thrown when a single model contains duplicate names.
Diffusion = @decapode begin
C::Form0
ϕ₁::Form1
ϕ₁ == ∘(k, d₀)(C)
end
AdvectionDuplicates = @acset SummationDecapode{Any, Any, Symbol} begin
Var = 4
type = [:Form0, :Form1, :Form1, :Form1]
name = [:C, :V, :ϕ₂, :ϕ₂]
Op2 = 1
proj1 = [1]
proj2 = [2]
res = [3]
op2 = [:∧₀₁]
end
Superposition = @decapode begin
(C, Ċ)::Form0
(ϕ, ϕ₁, ϕ₂)::Form1
ϕ == ϕ₁ + ϕ₂
Ċ == ∘(⋆₀⁻¹, dual_d₁, ⋆₁)(ϕ)
∂ₜ(C) == Ċ
end
@test_throws "Decapode AdvectionDuplicates contains repeated variable names: Set([:ϕ₂])." default_composition_diagram(
[Diffusion, AdvectionDuplicates, Superposition],
[:Diffusion, :AdvectionDuplicates, :Superposition])
end

# end
Loading