Skip to content
Closed
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
30 changes: 30 additions & 0 deletions src/varnamedtuple/map.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,36 @@ function DynamicPPL.subset(parent_vnt::VarNamedTuple, vns)
init=VarNamedTuple(),
)
end
"""
delete!!(vnt::VarNamedTuple, vn::VarName)

Return a new `VarNamedTuple` with the variable `vn` removed.

# Examples
```jldoctest
julia> using DynamicPPL, BangBang

julia> vnt = VarNamedTuple()
VarNamedTuple()

julia> vnt = setindex!!(vnt, 1.0, @varname(a))
VarNamedTuple
└─ a => 1.0

julia> vnt = setindex!!(vnt, 2.0, @varname(b))
VarNamedTuple
├─ a => 1.0
└─ b => 2.0

julia> delete!!(vnt, @varname(a))
VarNamedTuple
└─ b => 2.0
```
"""
function BangBang.delete!!(vnt::VarNamedTuple, vn::VarName)
remaining_vns = filter(k -> !subsumes(vn, k), keys(vnt))
return DynamicPPL.subset(vnt, remaining_vns)
end
Comment on lines +76 to +79
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR!

While this implementation is correct in most cases, there are some cases where it will fail.

  1. The subsumes check is a bit conservative and will fail to detect some things, which are described in http://turinglang.org/AbstractPPL.jl/stable/varname/#AbstractPPL.subsumes.

  2. Secondly, it doesn't allow you to delete part of a variable. Consider this:

julia> v = @vnt begin
           x := [1, 2]
       end
VarNamedTuple
└─ x => [1, 2]

julia> delete!!(v, @varname(x[1])) # what should this return?

Right now, this implementation of delete!! will do nothing, because we have that

julia> keys(v)
1-element Vector{VarName}:
 x

and x[1] doesn't subsume x. I think it would be very reasonable to expect the call to delete!! to return something like this, where the vector has been converted into a PartialArray:

julia> expected_result = @vnt begin
           @template x = zeros(2)
           x[2] := 2
       end
VarNamedTuple
└─ x => PartialArray size=(2,) data::Vector{Int64}
        └─ (2,) => 2

Apart from this, it is also pretty inefficient, because it has to traverse the entire VarNamedTuple to figure out which keys should remain, and then rebuild a complete VarNamedTuple that doesn't contain it. (Look at the implementation of subset: it's a loop over the keys which calls templated_setindex!!.)

This means that if you have a VNT that has 26 keys, and you remove one of them, you have to call templated_setindex!! 25 times. And this is probably unnecessary. VNT has a tree-like structure, which should allow you to identify exactly which branches can be pruned when you call delete!!, and not have to touch any of the remaining ones. For example, look at the way setindex!! is implemented. It uses the VarName to recurse into the correct part that needs to be changed, and doesn't touch anything else. The implementation here should be more surgical like that.

It's not going to be easy to write correctly, and I can only apologise, but it's unlikely that I can review it.


"""
apply!!(func, vnt::VarNamedTuple, name::VarName)
Expand Down
22 changes: 21 additions & 1 deletion test/varnamedtuple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ using DynamicPPL.VarNamedTuples:
NoTemplate,
templated_setindex_no_overwrite!!
using AbstractPPL: AbstractPPL, VarName, concretize, prefix, @opticof
using BangBang: setindex!!, empty!!
using BangBang: setindex!!, empty!!, delete!!
using DimensionalData: DimensionalData as DD
using InvertedIndices: InvertedIndices as II
using OffsetArrays: OffsetArrays as OA
Expand Down Expand Up @@ -2094,7 +2094,27 @@ Base.size(st::SizedThing) = st.size
end
@test densify!!(vnt) == VarNamedTuple(; x=CA.ComponentArray(; a=1.0, b=2.0))
end
@testset "delete!!" begin
vnt = VarNamedTuple()
vnt = setindex!!(vnt, 1.0, @varname(a))
vnt = setindex!!(vnt, 2.0, @varname(b))
vnt = setindex!!(vnt, 3.0, @varname(c))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests are a good starting point, but they are still too simple and only investigate the happy path where things are easy to make work. Notice how thorough the tests for get/set are above. Deletion is less important than those operations, so maybe the tests for deletion don't have to live up to that standard, but it needs to be much closer to them.


# Delete a single variable
vnt2 = delete!!(vnt, @varname(a))
@test !haskey(vnt2, @varname(a))
@test haskey(vnt2, @varname(b))
@test haskey(vnt2, @varname(c))

# Delete another variable
vnt3 = delete!!(vnt2, @varname(b))
@test !haskey(vnt3, @varname(b))
@test haskey(vnt3, @varname(c))

# Original is unchanged
@test haskey(vnt, @varname(a))
@test length(keys(vnt)) == 3
end
@testset "skeleton" begin
function test_skeleton(orig_vnt, expected_skeleton)
@test (@inferred skeleton(orig_vnt)) == expected_skeleton
Expand Down
Loading