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

Remove support for Attribute change via DPO #77

Merged
merged 2 commits into from
Jul 19, 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
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(G⊕E, 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(G⊕D, S; monic=true)
GS_Dir30 = hom(G⊕D, 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(G⊕D, w_eat_l; initial=(Dir=[AttrVar(2)],))
GW_Dir = hom(G⊕D, 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
Loading