From e36dbd014051f59f540924d48759a91319e1ac14 Mon Sep 17 00:00:00 2001 From: Luke Morris <70283489+lukem12345@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:28:00 -0400 Subject: [PATCH] Apply collages to intermediate variables (#82) Co-authored-by: Matt --- src/collages.jl | 49 ++++++++++++------- test/collages.jl | 125 ++++++++++++++++++++++++++++++++++++----------- 2 files changed, 128 insertions(+), 46 deletions(-) diff --git a/src/collages.jl b/src/collages.jl index 07b528d..2761849 100644 --- a/src/collages.jl +++ b/src/collages.jl @@ -7,24 +7,17 @@ end collate(c::Collage) = collate(c.src, c.tgt, c.uwd, c.symbols) +# TODO: This is assuming only "restriction"-type morphisms. """ function collate(equations, boundaries, uwd, symbols) Create a collage of two Decapodes that simulates with boundary conditions. ``` """ function collate(equations, boundaries, uwd, symbols) - # TODO: This is assuming only "restriction"-type morphisms. - f = SummationDecapode{Any, Any, Symbol}() - # TODO: Double-check copy_parts!(f, equations, (:Var, :TVar, :Op1, :Op2, :Σ, :Summand)) - - # TODO: Throw an error if the user tries to use a boundary value differential - # form that is of a different type of the thing that we are applying the bound - # to. i.e. Form1 but target is a Form0. - - # TODO: This sets restrictions as Op1s. They are actually Op2s. i.e. Use `bv`. for b in boxes(uwd) + # Set up pointers. ps = incident(uwd, b, :box) ev = first(ps) bv = last(ps) @@ -33,15 +26,35 @@ function collate(equations, boundaries, uwd, symbols) en = symbols[en_key] bn = symbols[bn_key] var = only(incident(f, en, :name)) - b_var = add_part!(f, :Var, type=f[var, :type], name=f[var, :name]) - f[var, :name] = Symbol("r$(b)_" * string(f[var, :name])) - s_var = add_part!(f, :Var, type=boundaries[only(incident(boundaries, bn, :name)), :type], name=bn) - add_part!(f, :Op2, proj1=b_var, proj2=s_var, res=var, op2=uwd[b, :name]) - - # Update tangent variable pointers, if any. - tangent_op1s = filter(x -> f[x, :op1]==:∂ₜ, incident(f, var, :src)) - isempty(tangent_op1s) && continue - f[only(tangent_op1s), :src] = b_var + en_type = equations[only(incident(equations, en, :name)), :type] + bn_type = boundaries[only(incident(boundaries, bn, :name)), :type] + if en_type != bn_type + error("Cannot use $(string(bn)) of type $(string(bn_type)) to bound $(string(en)) of type $(string(en_type)).") + end + # Add a new variable and transfer the children of the original variable to it. + b_var = add_part!(f, :Var, type=f[var, :type], name=Symbol("r$(b)_" * string(f[var, :name]))) + transfer_children!(f, var, b_var) + # Transfer ∂ₜ morphisms back to the original variable, if any. + transferred_partials = filter(x -> f[x, :op1]==:∂ₜ, incident(f, b_var, :src)) + if !isempty(transferred_partials) + f[only(transferred_partials), :src] = var + end + # Transfer ∂ₜ morphisms to the "masked"/ bounded variable, if any. + untransferred_partials = filter(x -> f[x, :op1]==:∂ₜ, incident(f, var, :tgt)) + if !isempty(untransferred_partials) + f[untransferred_partials, :tgt] = b_var + end + # Insert the "masking" operation. + gettype(xs, n) = xs[only(incident(xs, n, :name)), :type] + newtype = @match (gettype(equations, en), gettype(boundaries, bn)) begin + (:infer, _) => :infer + (_, :infer) => :infer + (:Form0, :Constant) => :Form0 + (x, y) && if x == y end => x + (x, y) => error("Type mismatch between $x and $y") + end + s_var = add_part!(f, :Var, type=newtype, name=bn) + add_part!(f, :Op2, proj1=var, proj2=s_var, res=b_var, op2=uwd[b, :name]) end f diff --git a/test/collages.jl b/test/collages.jl index 90cbda8..f01a817 100644 --- a/test/collages.jl +++ b/test/collages.jl @@ -12,28 +12,36 @@ using Catlab # TODO: Add test with empty boundary Decapode. +# Note: Since the order does not matter in which rb1 and rb2 are applied, it +# seems informal to state that one goes before the other. +# It might be better to provide a semantics for incident edges a la: +#Diffusion = @Decapode begin +# C::Form0 +# ∂ₜ(C) == rb3(∘(d,⋆,d,⋆)(rb1(C))) +# ∂ₜ(C) == rb3(∘(d,⋆,d,⋆)(rb2(C))) +#end +# Such a technique would preserve the technical definition of "collage". + + # Test simple boundary masks. -DiffusionDynamics = @decapode begin +DiffusionDynamics = infer_types!(@decapode begin K::Form0 ∂ₜ(K) == ∘(d,⋆,d,⋆)(K) -end +end) DiffusionBoundaries = @decapode begin (Kb1, Kb2, Null)::Form0 end - DiffusionMorphism = @relation () begin rb1_leftwall(C, Cb1) rb2_rightwall(C, Cb2) rb3(Ċ, Zero) end - DiffusionSymbols = Dict( :C => :K, :Ċ => :K̇, :Cb1 => :Kb1, :Cb2 => :Kb2, :Zero => :Null) - DiffusionCollage = DiagrammaticEquations.collate( DiffusionDynamics, DiffusionBoundaries, @@ -45,30 +53,91 @@ DiffusionCollage = DiagrammaticEquations.collate( TVar = 1 Op1 = 2 Op2 = 3 - src = [5, 1] - tgt = [2, 2] - proj1 = [3, 5, 7] - proj2 = [4, 6, 8] - res = [1, 3, 2] - incl = [2] - op1 = Any[:∂ₜ, [:d, :⋆, :d, :⋆]] - op2 = [:rb1_leftwall, :rb2_rightwall, :rb3] - type = [:Form0, :infer, :Form0, :Form0, :Form0, :Form0, :infer, :Form0] - name = [:r1_K, :r3_K̇, :r2_K, :Kb1, :K, :Kb2, :K̇, :Null] + src = [1, 3] + tgt = [7, 2] + proj1 = [5, 1, 2] + proj2 = [4, 6, 8] + res = [3, 5, 7] + incl = [2] + op1 = Any[:∂ₜ, [:d, :⋆, :d, :⋆]] + op2 = [:rb1_leftwall, :rb2_rightwall, :rb3] + type = [:Form0, :Form0, :Form0, :Form0, :Form0, :Form0, :Form0, :Form0] + name = [:K, :K̇, :r1_K, :Kb1, :r2_K, :Kb2, :r3_K̇, :Null] end -# Note: Since the order does not matter in which rb1 and rb2 are applied, it -# seems informal to state that one goes before the other. -# It might be better to provide a semantics for incident edges a la: -#Diffusion = @Decapode begin -# C::Form0 -# ∂ₜ(C) == rb3(∘(d,⋆,d,⋆)(rb1(C))) -# ∂ₜ(C) == rb3(∘(d,⋆,d,⋆)(rb2(C))) -#end -# Such a technique would preserve the technical definition of "collage". +# Test boundary condition applications on intermediate variables. +IntermediateDynamics = infer_types!(@decapode begin + K::Form0 + J == Δ(K) + ∂ₜ(K) == Δ(J) +end) +IntermediateBoundaries = @decapode begin + (Jb1)::Form0 +end +IntermediateMorphism = @relation () begin + rb1_leftwall(C, Cb1) +end +IntermediateSymbols = Dict( + :C => :J, + :Cb1 => :Jb1) +IntermediateCollage = DiagrammaticEquations.collate( + IntermediateDynamics, + IntermediateBoundaries, + IntermediateMorphism, + IntermediateSymbols) +@test IntermediateCollage == @acset SummationDecapode{Any, Any, Symbol} begin + Var = 5 + TVar = 1 + Op1 = 3 + Op2 = 1 + src = [1, 1, 4] + tgt = [2, 3, 3] + proj1 = [2] + proj2 = [5] + res = [4] + incl = [3] + op1 = [:Δ, :∂ₜ, :Δ] + op2 = [:rb1_leftwall] + type = [:Form0, :Form0, :Form0, :Form0, :Form0] + name = [:K, :J, :K̇, :r1_J, :Jb1] +end -# Test gensim on a collage. -c = Collage(DiffusionDynamics, DiffusionBoundaries, - DiffusionMorphism, DiffusionSymbols) +# Test twice-applied boundary condition applications on intermediate variables. +TwiceDynamics = infer_types!(@decapode begin + K::Form0 + J == Δ(K) + ∂ₜ(K) == Δ(J) +end) +TwiceBoundaries = @decapode begin + (Jb1, Jb2)::Form0 +end +TwiceMorphism = @relation () begin + rb1_leftwall(C, Cb1) + rb2_rightwall(C, Cb2) +end +TwiceSymbols = Dict( + :C => :J, + :Cb1 => :Jb1, + :Cb2 => :Jb2) +TwiceCollage = DiagrammaticEquations.collate( + TwiceDynamics, + TwiceBoundaries, + TwiceMorphism, + TwiceSymbols) +@test TwiceCollage == @acset SummationDecapode{Any, Any, Symbol} begin + Var = 7 + TVar = 1 + Op1 = 3 + Op2 = 2 + src = [1, 1, 4] + tgt = [2, 3, 3] + proj1 = [6, 2] + proj2 = [5, 7] + res = [4, 6] + incl = [3] + op1 = [:Δ, :∂ₜ, :Δ] + op2 = [:rb1_leftwall, :rb2_rightwall] + type = [:Form0, :Form0, :Form0, :Form0, :Form0, :Form0, :Form0] + name = [:K, :J, :K̇, :r1_J, :Jb1, :r2_J, :Jb2] +end -# @test gensim(c) == gensim(DiffusionCollage)