Skip to content

Commit

Permalink
Merge pull request #77 from AlgebraicJulia/fix_attrvar_pc
Browse files Browse the repository at this point in the history
Remove support for Attribute change via DPO
  • Loading branch information
kris-brown authored Jul 19, 2024
2 parents 459429d + 56dddea commit 35fabeb
Show file tree
Hide file tree
Showing 16 changed files with 191 additions and 369 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name = "AlgebraicRewriting"
uuid = "725a01d3-f174-5bbd-84e1-b9417bad95d9"
license = "MIT"
authors = ["Kris Brown <[email protected]>"]
version = "0.3.6"
version = "0.3.7"

[deps]
ACSets = "227ef7b5-1206-438b-ac65-934d6da304b8"
Expand Down
13 changes: 4 additions & 9 deletions docs/literate/full_demo.jl
Original file line number Diff line number Diff line change
Expand Up @@ -354,20 +354,15 @@ end

l = homomorphism(I, L; initial=(V=1:2,))
r = homomorphism(I, R; initial=(V=1:2,))
rule = Rule(l, r; monic=[:E], expr=Dict(:Weight => [xs -> xs[1] + xs[2]]))
rule = Rule(l, r; monic=[:E], expr=Dict(:Weight => [((w₁,w₂),) -> w₁ + w₂]))

G = @acset WeightedGraph{Int} begin
V = 1
E = 3
src = 1
tgt = 1
weight = [10, 20, 100]
V = 1; E = 3; src = 1; tgt = 1; weight = [10, 20, 100]
end

m = get_matches(rule,G)[1]
m = homomorphism(L, G; initial=(E=1:2,))
@test rewrite_match(rule, m) == @acset WeightedGraph{Int} begin
V = 1; E = 2
src = [1]; tgt = [1]; weight = [30, 100]
V = 1; E = 2; src = [1]; tgt = [1]; weight = [30, 100]
end

# # 8. Graph processes
Expand Down
8 changes: 4 additions & 4 deletions docs/literate/game_of_life.jl
Original file line number Diff line number Diff line change
Expand Up @@ -315,9 +315,9 @@ view_sched(next_step)

life(n::Int) = for_schedule(update_next next_step, n) |> F

const L = life(1) # Game of life simulation that runs just one (global) timestep
const L1 = life(1) # Game of life simulation that runs just one (global) timestep

view_sched(L)
view_sched(L1)

# ## Running the simulation

Expand All @@ -331,11 +331,11 @@ view_life_graph(G)
view_life(G) |> println

# Run the simulation
res = interpret(L, G; maxstep=1000);
res = interpret(L1, G; maxstep=1000);

# Look at the end state

res[end][1] |> codom |> view_life |> println

# Visualize the results in the `traj` folder
view_traj(L, res[1:10], view_life; agent=true)
view_traj(L1, res[1:10], view_life; agent=true)
121 changes: 67 additions & 54 deletions docs/literate/lotka_volterra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ be oriented in a particular direction at any point in time.
(Sheep, Wolf)::Ob
sheep_loc::Hom(Sheep, V); wolf_loc::Hom(Wolf, V)

(Time, Eng)::AttrType
countdown::Attr(V, Time);
Time::Ob
countdown::Hom(Time, V);

Eng::AttrType
sheep_eng::Attr(Sheep, Eng); wolf_eng::Attr(Wolf, Eng)

Dir::AttrType
Expand All @@ -59,7 +61,7 @@ end
## efficient ABM rewriting uses BitSetParts rather than DenseParts to allow
## in-place pushout rewriting, rather than pure/non-mutating pushouts.)
@acset_type LV_Generic(SchLV, part_type=BitSetParts) <: HasGraph
const LV = LV_Generic{Int, Int, Symbol}
const LV = LV_Generic{Int, Symbol}

to_graphviz(SchLV; prog="dot")

Expand All @@ -77,7 +79,7 @@ unnecessary when doing the actual agent-based modeling. So what we will do is
end

@acset_type LV′_Generic(SchLV′, part_type=BitSetParts) <: HasGraph
const LV′ = LV′_Generic{Int, Int, Symbol, Tuple{Int,Int}};
const LV′ = LV′_Generic{Int, Symbol, Tuple{Int,Int}};

#=
We will be representing directions as `Symbol`s and encode the geometry via
Expand Down Expand Up @@ -123,7 +125,7 @@ obtain the analogous actions for wolves.
=#

F = Migrate(
Dict(:Sheep => :Wolf, :Wolf => :Sheep),
Dict(:Sheep => :Wolf, :Wolf => :Sheep, :Time=>:Time),
Dict([:sheep_loc => :wolf_loc, :wolf_loc => :sheep_loc,
:sheep_eng => :wolf_eng, :wolf_eng => :sheep_eng, :countdown => :countdown,
:sheep_dir => :wolf_dir, :wolf_dir => :sheep_dir,]), SchLV, LV);
Expand All @@ -148,7 +150,8 @@ function create_grid(n::Int)
coords = Dict()
for i in 0:n-1 # Initialize grass 50% green, 50% uniformly between 0-30
for j in 0:n-1
coords[i=>j] = add_part!(lv, :V; countdown=max(0, rand(-30:30)), coord=(i, j))
coords[i=>j] = add_part!(lv, :V; coord=(i, j))
add_parts!(lv, :Time, max(0, rand(-30:30)); countdown=coords[i=>j])
end
end
for i in 0:n-1
Expand Down Expand Up @@ -210,7 +213,7 @@ function view_LV(p::LV′, pth=tempname(); name="G", title="", star=nothing)
stmts = Statement[]
for s in 1:nv(p)
st = (star == (:V => s)) ? "*" : ""
gv = p[s, :countdown]
gv = length(incident(p, s, :countdown))
col = gv == 0 ? "lightgreen" : "tan"
push!(stmts, Node("v$s", Attributes(
:label => gv == 0 ? "" : string(gv) * st,
Expand Down Expand Up @@ -266,8 +269,8 @@ obtain a generic LV′ sheep. We use this as the domain of a hom that
assigns the sheep to Sheep #2 of the world state `init` from above.
=#

S = @acset LV begin V=1; Sheep=1; Dir=1; Eng=1; Time=1;
sheep_loc=1; sheep_dir=[AttrVar(1)]; sheep_eng=[AttrVar(1)]; countdown=[AttrVar(1)]
S = @acset LV begin V=1; Sheep=1; Dir=1; Eng=1;
sheep_loc=1; sheep_dir=[AttrVar(1)]; sheep_eng=[AttrVar(1)];
end

view_LV(hom(F2(S), init; initial=(Sheep=[2],)))
Expand All @@ -285,6 +288,9 @@ yLV = yoneda_cache(LV; clear=false); # cache=false means reuse cached results
I = LV() # Empty agent type
S = @acset_colim yLV begin s::Sheep end # Generic sheep agent
W = F(S) # Generic wolf agent, obtained via the swapping `F` data migration
E = @acset_colim yLV begin e::Eng end
D = @acset_colim yLV begin d::Dir end
T = @acset_colim yLV begin t::Time end
G = @acset_colim yLV begin v::V end # Generic grass agent
N = Names(Dict("W" => W, "S" => S, "G" => G, "" => I)); # give these ACSets names

Expand Down Expand Up @@ -319,11 +325,12 @@ single ACSet along with an `expr` dictionary which states how attributes
change.
=#

rl = Rule(S; expr=(Dir=[xs -> left(only(xs))],));
rr = Rule(S; expr=(Dir=[xs -> right(only(xs))],));
GS_Eng = hom(GE, S; monic=true)
rl = Rule(GS_Eng,GS_Eng; expr=(Dir=[((s,),) -> left(s)],));
rr = Rule(GS_Eng,GS_Eng; expr=(Dir=[((s,),) -> right(s)],));

sheep_rotate_l = tryrule(RuleApp(:turn_left, rl, S));
sheep_rotate_r = tryrule(RuleApp(:turn_right, rr, S));
sheep_rotate_l = tryrule(RuleApp(:turn_left, rl, id(S), id(S)));
sheep_rotate_r = tryrule(RuleApp(:turn_right, rr, id(S), id(S)));

# We can imagine executing these rules in sequence
seq_sched = (sheep_rotate_l sheep_rotate_r);
Expand All @@ -336,8 +343,8 @@ view_sched(par_sched; names=N)
# #### Test rotation
begin
ex = @acset LV begin
E=1; Sheep=1; V=2
src=1; tgt=2; dir=:W; countdown = [0, 0]
E=1; Sheep=1; V=2; Time=2
src=1; tgt=2; dir=:W; countdown = [1, 2]
sheep_loc=1; sheep_eng=100; sheep_dir=:N
end;

Expand Down Expand Up @@ -382,8 +389,8 @@ sheep_fwd = tryrule(RuleApp(:move_fwd, sheep_fwd_rule,

begin
ex = @acset LV begin
V=3; E=2; Sheep=1;
countdown=[0,0,0]
V=3; E=2; Sheep=1; Time=4
countdown=[1,2,2,3]
src=[1,2]; tgt=[2,3]; dir=[:N,:W]
sheep_loc=1; sheep_dir=:N; sheep_eng = 10
end
Expand All @@ -397,24 +404,38 @@ end;

# ### Sheep eat grass

s_eat_pac = @acset_colim yLV begin s::Sheep; countdown(sheep_loc(s)) == 0 end;
function add_time(lv::LV, n::Int)
res = deepcopy(lv)
add_parts!(res, :Time, n; countdown=1)
res
end

s_eat_nac = @acset_colim yLV begin
s::Sheep; t::Time; countdown(t) == sheep_loc(s)
end;

se_rule = Rule(S; expr=(Eng=[vs -> only(vs) + 4], Time=[vs -> 30],),
ac=[AppCond(hom(S, s_eat_pac))]);
GS_Dir = hom(GD, S; monic=true)
GS_Dir30 = hom(GD, add_time(S, 30); monic=true)

sheep_eat = tryrule(RuleApp(:Sheep_eat, se_rule, S));
se_rule = Rule(GS_Dir,GS_Dir30; expr=(Eng=[vs -> only(vs) + 4],),
ac=[AppCond(hom(S, s_eat_nac), false)]);

S_to_S30 = hom(S, add_time(S, 30))
sheep_eat = tryrule(RuleApp(:Sheep_eat, se_rule, id(S), S_to_S30));

# #### Sheep eating test
begin
ex = @acset LV begin
E=1; V=2; Sheep=1;
src=1; tgt=2; dir=:S; countdown=[10, 0]
E=1; V=2; Sheep=1; Time=2
src=1; tgt=2; dir=:S; countdown=[1,1]
sheep_loc = 2; sheep_eng = 3; sheep_dir=:W
end

expected = copy(ex)
expected[2,:countdown] = 30
expected[1,:sheep_eng] = 7
expected = @acset LV begin
E=1; V=2; Sheep=1; Time=32
src=1; tgt=2; dir=:S; countdown=[1,1, fill(2, 30)...]
sheep_loc = 2; sheep_eng = 7; sheep_dir=:W
end

@test is_isomorphic(expected, rewrite(se_rule, ex))
rewrite!(se_rule, ex)
Expand All @@ -428,15 +449,17 @@ w_eat_l = @acset_colim yLV begin
sheep_loc(s) == wolf_loc(w)
end;

we_rule = Rule(hom(W, w_eat_l), id(W); expr=(Eng=[vs -> vs[2] + 20],));
GWS_Dir = hom(GD, w_eat_l; initial=(Dir=[AttrVar(2)],))
GW_Dir = hom(GD, W; monic=true,)
we_rule = Rule(GWS_Dir, GW_Dir, expr=(Eng=[vs -> vs[2] + 20],));

wolf_eat = tryrule(RuleApp(:Wolf_eat, we_rule, W));
wolf_eat = tryrule(RuleApp(:Wolf_eat, we_rule, hom(W, w_eat_l), id(W)));

# #### Wolf eating test
begin
ex = @acset LV begin
Sheep=1; Wolf=1; V=3; E=2;
src=[1,2]; tgt=[2,3]; countdown=[9,10,11]; dir=[:N,:N];
Sheep=1; Wolf=1; V=3; E=2; Time=3;
src=[1,2]; tgt=[2,3]; countdown=[1,2,3]; dir=[:N,:N];
sheep_loc=2; sheep_eng=[3]; sheep_dir=[:N]
wolf_loc=[2]; wolf_eng=[16]; wolf_dir=[:S]
end
Expand All @@ -463,8 +486,8 @@ sheep_starve = (RuleApp(:starve, sheep_die_rule,

begin
ex = @acset LV begin
V=1; Sheep=1; Wolf=1
countdown=20;
V=1; Sheep=1; Wolf=1; Time=1
countdown=[1];
sheep_loc=1; sheep_eng=0; sheep_dir=:W
wolf_loc=1; wolf_eng=10; wolf_dir=:S
end
Expand All @@ -486,8 +509,8 @@ end;
sheep_reprod_rule = Rule(
hom(G, S),
hom(G, s_reprod_r);
expr=(Dir=fill(vs->only(vs) ,2),
Eng=fill(vs -> round(Int, vs[1] / 2, RoundUp), 2),)
expr=(Dir=fill(((dₛ,),)->dₛ ,2),
Eng=fill(((eₛ,),) -> round(Int, eₛ / 2, RoundUp), 2),)
);

sheep_reprod = RuleApp(:reproduce, sheep_reprod_rule,
Expand All @@ -497,8 +520,8 @@ sheep_reprod = RuleApp(:reproduce, sheep_reprod_rule,

begin # test
ex = @acset LV begin
Sheep=1; Wolf=1; V=2;
countdown=[20,30]
Sheep=1; Wolf=1; V=2; Time=2
countdown=[1,2]
sheep_loc=1; sheep_eng=10; sheep_dir=:W
wolf_loc=2; wolf_eng=5; wolf_dir=:N
end
Expand All @@ -509,41 +532,31 @@ begin # test
expected[:sheep_loc] = [1, 1]
expected[:sheep_dir] = [:W, :W]

rewrite(sheep_reprod_rule,ex)
@test is_isomorphic(rewrite(sheep_reprod_rule,ex),expected)
rewrite!(sheep_reprod_rule,ex)
@test is_isomorphic(ex, expected)
end;

# ### Grass increments

g_inc_n = deepcopy(G)
set_subpart!(g_inc_n, 1, :countdown, 0)
rem_part!(g_inc_n, :Time, 1);

g_inc_rule = Rule(id(G), id(G);
ac=[AppCond(hom(G, g_inc_n), false)],
expr=(Time=[vs -> only(vs) - 1],));
g_inc_rule = Rule(hom(G, T), id(G));

g_inc = RuleApp(:GrassIncrements, g_inc_rule, G) |> tryrule;

# #### Grass incrementing test

begin
ex = @acset LV begin
Sheep = 1; V = 3; E = 2
src = [1, 2]; tgt = [2, 3]
sheep_loc = 2; sheep_eng = [3]; sheep_dir = [:N]
countdown = [1, 10, 2]; dir = fill(:N, 2)
end
expected = @acset LV begin
Sheep = 1; V = 3; E = 2
Sheep = 1; V = 3; E = 2; Time=3
src = [1, 2]; tgt = [2, 3]
sheep_loc = 2; sheep_eng = [3]; sheep_dir = [:N]
countdown = [0, 10, 2]; dir = fill(:N, 2)
countdown = [2,2,2]; dir = fill(:N, 2)
end
m = get_matches(g_inc_rule, ex)[1]
@test is_isomorphic(rewrite_match(g_inc_rule, m), expected)
rewrite_match!(g_inc_rule, m)
expected = deepcopy(ex);
rem_part!(expected, :Time, 1)
@test is_isomorphic(rewrite(g_inc_rule, ex), expected)
rewrite!(g_inc_rule, ex)
@test is_isomorphic(ex, expected)
end;

Expand Down
49 changes: 9 additions & 40 deletions src/categorical_algebra/CSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,58 +151,27 @@ finite sets. If any of the pointwise identification conditions fail (in FinSet),
this method will raise an error. If the dangling condition fails, the resulting
C-set will be only partially defined. To check all these conditions in advance,
use the function [`can_pushout_complement`](@ref).
In the absence of AttrVars, K is a subobject of G. But we want to be able to
change the value of attributes. So any variables in I are not concretized by
the I->K map. However, AttrVars may be merged together if `m: L -> G` merges
parts together.
"""
function pushout_complement(pair::ComposablePair{<:ACSet, <:TightACSetTransformation})
l, m = pair
I, G = dom(l), codom(m)
S = acset_schema(I)
all(at->nparts(G, at)==0, attrtypes(S)) || error("Cannot rewrite with AttrVars in G")
# Compute pushout complements pointwise in FinSet.
comps = NamedTuple(Dict(map(ob(S)) do o
comps = NamedTuple(Dict(map(types(S)) do o
o => pushout_complement(ComposablePair(l[o], m[o]))
end))
k_components = Dict{Symbol,Any}(pairs(map(first, comps)))
g_components = Dict{Symbol,Any}(pairs(map(last, comps)))

# Reassemble components into natural transformations.
g = hom(Subobject(G, NamedTuple(Dict(o => g_components[o] for o in ob(S)))))
K = dom(g)

var_eq = var_eqs(l, m) # equivalence class of attrvars
for at in attrtypes(S)
eq = var_eq[at]
roots = unique(find_root!.(Ref(eq), 1:length(eq)))
add_parts!(K, at, length(roots))
k_components[at] = map(parts(I, at)) do pᵢ
attrvar = AttrVar(findfirst(==(find_root!(eq, pᵢ)), roots))
for (a, d, _) in attrs(S; to=at)
for p in incident(I, AttrVar(pᵢ), a)
K[k_components[d](p), a] = attrvar
end
end
attrvar
end
T = Union{AttrVar, attrtype_type(G, at)}
g_components[at] = Vector{T}(map(parts(K, at)) do v
try
f, o, val = var_reference(K, at, v)
G[g_components[o](val), f]
catch e
vᵢ = findfirst(==(AttrVar(v)), k_components[at])
m[at](l[at](AttrVar(vᵢ)))
end
end)
end
k_components = Dict{Symbol,Any}(pairs(map(first, comps)))
g_components = Dict{Symbol,Any}(k => k ob(S) ? v : FinFunction(v)
for (k,v) in pairs(map(last, comps)))

k = ACSetTransformation(I, K; k_components...)
g = ACSetTransformation(K, G; g_components...)
# # Reassemble components into natural transformations.
g = hom(Subobject(G, NamedTuple(g_components)))
k = ACSetTransformation(I, dom(g); k_components...)
kg, lm = force.(compose.([k,l], [g, m]))
kg == lm || error("Square doesn't commute: \n\t$(components(kg)) \n!= \n\t$(components(lm))")
kg == lm || error(
"Square doesn't commute: \n\t$(components(kg)) \n!= \n\t$(components(lm))")
is_natural(k) || error("k unnatural $k")
is_natural(g) || error("g unnatural")
return ComposablePair(k, g)
Expand Down
Loading

0 comments on commit 35fabeb

Please sign in to comment.