Skip to content

Commit 3cba427

Browse files
authored
Merge pull request #91 from VEZY/fix-#90
Fix: issue when updating node id or merging trees
2 parents 519770d + a5ff6ab commit 3cba427

5 files changed

Lines changed: 87 additions & 2 deletions

File tree

src/compute_MTG/indexing.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,10 +79,23 @@ end
7979
attrs = node_attributes(node)
8080
store = attrs.ref.store
8181
store === nothing && return nothing
82+
if store !== plan.store
83+
throw(ArgumentError(
84+
"Incoherent columnar query plan for key `$(key)` on node id $(node_id(node)): " *
85+
"the node belongs to a different attribute store than the query root. " *
86+
"This usually happens after attaching subtrees from independent MTGs. " *
87+
"Rebuild a unified store with `MultiScaleTreeGraph.columnarize!(get_root(node))`."
88+
))
89+
end
8290
nodeid = node_id(node)
8391
nodeid > length(store.node_bucket) && return nothing
8492
bid = store.node_bucket[nodeid]
8593
bid == 0 && return nothing
94+
bid > length(plan.col_idx_by_bucket) && throw(ArgumentError(
95+
"Incoherent columnar query plan for key `$(key)` on node id $(nodeid): " *
96+
"bucket id $(bid) is outside query-plan bounds $(length(plan.col_idx_by_bucket)). " *
97+
"Rebuild a unified store with `MultiScaleTreeGraph.columnarize!(get_root(node))`."
98+
))
8699
col_idx = plan.col_idx_by_bucket[bid]
87100
col_idx == 0 && return nothing
88101
row = store.node_row[nodeid]

src/compute_MTG/node_funs.jl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,33 @@ function addchild!(p::Node, MTG::M) where {M<:AbstractNodeMTG}
7777
return child
7878
end
7979

80+
@inline function _columnar_store_or_nothing(node::Node)
81+
attrs = node_attributes(node)
82+
attrs isa ColumnarAttrs || return nothing
83+
return _store_for_node_attrs(attrs)
84+
end
85+
86+
@inline function _maybe_recolumnarize_after_attach!(p::Node, child::Node, child_was_root::Bool)
87+
child_was_root || return nothing
88+
p_store = _columnar_store_or_nothing(p)
89+
c_store = _columnar_store_or_nothing(child)
90+
if p_store !== nothing && c_store !== nothing && p_store !== c_store
91+
columnarize!(get_root(p))
92+
end
93+
return nothing
94+
end
95+
8096
function addchild!(p::Node{N,A}, child::Node; force=false) where {N<:AbstractNodeMTG,A}
81-
if parent(child) === nothing || force == true
97+
child_was_root = parent(child) === nothing
98+
99+
if child_was_root || force == true
82100
reparent!(child, p)
83101
elseif parent(child) != p && force == false
84102
error("The node already has a parent. Hint: use `force=true` if needed.")
85103
end
86104

87105
push!(children(p), child)
106+
_maybe_recolumnarize_after_attach!(p, child, child_was_root)
88107

89108
return child
90109
end

src/types/Attributes.jl

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ end
4545

4646
struct ColumnarQueryPlan
4747
key::Symbol
48+
store::MTGAttributeStore
4849
col_idx_by_bucket::Vector{Int}
4950
end
5051

@@ -110,7 +111,25 @@ function _rebuild_subtree_index!(store::MTGAttributeStore, root)
110111
pos = stack_pos[end]
111112

112113
if pos == 0
114+
current_attrs = node_attributes(current)
115+
current_attrs isa ColumnarAttrs || throw(ArgumentError(
116+
"Subtree index rebuild requires a columnar attribute backend."
117+
))
118+
current_store = _store_for_node_attrs(current_attrs)
119+
if current_store !== store
120+
throw(ArgumentError(
121+
"Incoherent MTG columnar stores detected while building descendants index: " *
122+
"node id $(node_id(current)) belongs to a different attribute store than the root. " *
123+
"Rebuild a unified store with `MultiScaleTreeGraph.columnarize!(get_root(root))`."
124+
))
125+
end
113126
nid = node_id(current)
127+
if nid > nmax || store.node_bucket[nid] == 0
128+
throw(ArgumentError(
129+
"Incoherent MTG columnar indexing state for node id $(nid). " *
130+
"Rebuild a unified store with `MultiScaleTreeGraph.columnarize!(get_root(root))`."
131+
))
132+
end
114133
t += 1
115134
tin[nid] = t
116135
push!(dfs_order, nid)
@@ -398,7 +417,7 @@ function build_columnar_query_plan(node, key::Symbol)
398417
@inbounds for i in eachindex(store.buckets)
399418
col_idx_by_bucket[i] = get(store.buckets[i].col_index, key, 0)
400419
end
401-
ColumnarQueryPlan(key, col_idx_by_bucket)
420+
ColumnarQueryPlan(key, store, col_idx_by_bucket)
402421
end
403422

404423
@inline function _symbol_bucket_ids(store::MTGAttributeStore, symbol_filter)

test/test-descendants.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,25 @@
6565
@test descendants!(out_nodes, get_node(mtg, 6), self=true) == [get_node(mtg, 6), get_node(mtg, 7)]
6666
end
6767

68+
@testset "descendants clear error on mixed columnar stores" begin
69+
mtg_a = read_mtg("files/simple_plant.mtg")
70+
mtg_b = read_mtg("files/simple_plant.mtg")
71+
# Bypass addchild! on purpose to build an incoherent tree (mixed stores).
72+
reparent!(mtg_b, mtg_a)
73+
push!(children(mtg_a), mtg_b)
74+
75+
err = try
76+
descendants(mtg_a, :Width)
77+
nothing
78+
catch e
79+
e
80+
end
81+
82+
@test err isa ArgumentError
83+
@test occursin("different attribute store", sprint(showerror, err))
84+
@test occursin("columnarize!", sprint(showerror, err))
85+
end
86+
6887
# using BenchmarkTools
6988
# @benchmark descendants($mtg, :Width) # 876 ns
7089
# @benchmark descendants!($mtg, :Width) # 5.6 μs

test/test-nodes.jl

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,3 +126,18 @@ end
126126
mtg = read_mtg(file, MutableNodeMTG)
127127
VERSION >= v"1.7" && @test_throws "The parent node has an MTG encoding of type `MutableNodeMTG`, but the MTG encoding you provide is of type `NodeMTG`, please make sure they are the same." Node(mtg, NodeMTG(:/, :Branch, 1, 2))
128128
end
129+
130+
@testset "addchild! re-columnarizes when attaching a root subtree" begin
131+
mtg_a = read_mtg(file)
132+
mtg_b = read_mtg(file)
133+
addchild!(mtg_a, mtg_b; force=true)
134+
@test_nowarn descendants(mtg_a, :Width)
135+
end
136+
137+
@testset "new child node attributes are in the columnar store" begin
138+
mtg = read_mtg(file)
139+
leaf = addchild!(mtg, MutableNodeMTG(:+, :Leaf, 999, 2), Dict{Symbol,Any}(:my_attr => 42))
140+
@test leaf[:my_attr] == 42
141+
vals = descendants(mtg, :my_attr; ignore_nothing=true)
142+
@test vals == [42]
143+
end

0 commit comments

Comments
 (0)