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

[Merged by Bors] - Concretize IndexLens using to_indices #43

Closed
wants to merge 27 commits into from

Conversation

phipsgabler
Copy link
Member

@phipsgabler phipsgabler commented Nov 20, 2021

Should implement #35. The "problem" with this is that the resulting values are not exactly what you'd write by hand:

julia> AbstractPPL.concretize(@varname(x.a[1:end, end][:]), x)
x.a[1:2,2][Base.Slice(Base.OneTo(2))]

That wouldn't be so much of a problem besides printing, but unfortunately, lenses currenctly compare equality using strict types:

julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)).lens.indices == @varname(x[1:3, 1:10]).lens.indices
true

julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)).lens == @varname(x[1:3, 1:10]).lens
false

julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)) == @varname(x[1:3, 1:10])
false

Cf. jw3126/Setfield.jl#165; the equality comparison can hopefully be fixed there.

The remaining thing is that subsumption must still be able to work with Colon, since a user might index a trace/VarInfo using a non-concretized varname containing a colon. But at least we can then be sure that one side is always concrete.

@coveralls
Copy link

coveralls commented Nov 20, 2021

Pull Request Test Coverage Report for Build 1485214864

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 21 of 25 (84.0%) changed or added relevant lines in 1 file are covered.
  • 5 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.2%) to 77.982%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/varname.jl 21 25 84.0%
Files with Coverage Reduction New Missed Lines %
src/varname.jl 5 77.98%
Totals Coverage Status
Change from base Build 1429508741: 0.2%
Covered Lines: 85
Relevant Lines: 109

💛 - Coveralls

@codecov
Copy link

codecov bot commented Nov 20, 2021

Codecov Report

Base: 80.39% // Head: 80.00% // Decreases project coverage by -0.39% ⚠️

Coverage data is based on head (e854b17) compared to base (3e6f993).
Patch coverage: 100.00% of modified lines in pull request are covered.

❗ Current head e854b17 differs from pull request most recent head af291d3. Consider uploading reports for the commit af291d3 to get more accurate results

Additional details and impacted files
@@            Coverage Diff             @@
##             main      #43      +/-   ##
==========================================
- Coverage   80.39%   80.00%   -0.40%     
==========================================
  Files           3        2       -1     
  Lines         255      125     -130     
==========================================
- Hits          205      100     -105     
+ Misses         50       25      -25     
Impacted Files Coverage Δ
src/varname.jl 80.64% <100.00%> (+0.64%) ⬆️
src/graphinfo.jl

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

@phipsgabler
Copy link
Member Author

I added some normalization code to the index conversions, so that all ranges will become "normal ranges", as constructed by :.

This should mitigate possible confusions in printing and problems with equality. Also, at the time lenses are actually applied for getting, the indices are converted once more using to_indices, anyway, so the potential optimization of OneTo etc. is not lost.

@torfjelde
Copy link
Member

I'm generally okay with this change, but I'd like to think about it a tiny bit more. E.g.

The remaining thing is that subsumption must still be able to work with Colon, since a user might index a trace/VarInfo using a non-concretized varname containing a colon. But at least we can then be sure that one side is always concrete.

this is quite an annoying issue. Do you have any thoughts on how we'd approach this dealing with this?

Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

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

I guess this is quite breaking for DynamicPPL and Turing since the names of the parameters in the resulting chains etc. are based on this printed representation.

Probably even without this PR it would be good to test non-standard indices (e.g. OffsetArrays) - or is it already part of the test suite?

src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
@phipsgabler
Copy link
Member Author

The remaining thing is that subsumption must still be able to work with Colon, since a user might index a trace/VarInfo using a non-concretized varname containing a colon. But at least we can then be sure that one side is always concrete.

this is quite an annoying issue. Do you have any thoughts on how we'd approach this dealing with this?

Sure, lets think about it. In my intuition, : in a varname without concrete context is a bit like infinity. If you have only of the, it works quite fine, you only run into undefined situations when there's two.

But my solution would be to do the same thing as the to_index family of functions, and always pass along the value you're talking about. : only means somthing in the context of a given array. Then subsumption checks of : amount to subsumption of to_index(x, :).

Now that's the logical answer. But if we do it this way, you compute the to_index at every lookup. Hence the idea to normalize it right away. Now, as @devmotion noted corretly, this changes printing, on which chains unfortunately depend... OTOH, we already change printing:

prettify_index(::Colon) = ":"
. So one hacky solution would be to just continue with that. For example,

julia> to_indices([1,2], (:,))
(Base.Slice(Base.OneTo(2)),)

so we can dispatch on Base.Slice, which is used when a Colon is concretized.

@devmotion
Copy link
Member

So one hacky solution would be to just continue with that.

There's a major difference here though: : is an alias of the singleton Colon() (https://github.com/JuliaLang/julia/blob/8c9799530383d571d5beab4e530d86e40a289929/base/essentials.jl#L691), and hence both are identical and can't be distinguished. Whereas this is clearly not the case for Slice.

@phipsgabler
Copy link
Member Author

phipsgabler commented Nov 29, 2021

I found that the following is also a problem, and more severe:

julia> y = rand(10, 10);

julia> to_indices(x, (2:2:5,))
(2:2:4,)

EDIT: OK, this isn't as severe as it seems. The construction of this slice already performs this normalization:

julia> 2:2:5
2:2:4

Therefore this behaviour is already happening.

@phipsgabler
Copy link
Member Author

I've made some changes trying to find a balance here. Namely,

  • to_indices is applied to all indices, but values which were originally Colon are un-converted to just UnitRange(converted_index) (which is what you get when writing begin:end instead).
  • I reintroduced the explicit concretize argument for varname, since now there is an actual choice to be made for "non-dynamic" varnames. This behaves the same as previously and fails on begin/end, just with a different error message.

I thought it would be possible to wrap the un-converted index into Base.Slice, but Base doesn't like Slice{UnitRange}. So the only reasonable thing IMHO is to do it as I described. This does have the effect that stringification changes: y[:] becomes y[1:N].

@yebai
Copy link
Member

yebai commented Feb 3, 2022

@torfjelde can you take a look at this PR, see whether you are happy before I merge it?

@phipsgabler
Copy link
Member Author

I'd have to look at this one myself again; IIRC there were some unexpected subtleties.

@phipsgabler
Copy link
Member Author

phipsgabler commented Feb 5, 2022

Sooo... I think we need to think about this a bit more. I have now added a couple of proper tests for VarName, and fixed some edge cases. Setfield 0.8.1 is required to make lens equality in things like

@varname(A[1, Not(3)], true) == @varname(A[1, [1, 2, 4, 5, 6, 7, 8, 9, 10]])

work.

I have started to continue and refactor some other things (so currently many subsumption tests are broken).

@phipsgabler
Copy link
Member Author

phipsgabler commented Feb 5, 2022

I got quite something, and I think it's reasonable. Please review.

More test cases for non-standard indexing and general edge cases would probably be good.

This assumes that non-concrete colons can never be subsumed (you cannot reasonably decide that x[:] is subsumed by x[1]; the other way around it makes sense), and so I error in this case.

Note that this does not apply when you conretize first; then the colon will be resolved and the comparison is done on runtime indices.

Copy link
Member

@devmotion devmotion left a comment

Choose a reason for hiding this comment

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

Are the questions and possible issues in the discussion above resolved? If the PR is merged, I think it would be safer to put it into a breaking release due to the potential downstream breakages - or are they avoided in the latest version of the PR?

src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
@yebai
Copy link
Member

yebai commented Feb 6, 2022

This assumes that non-concrete colons can never be subsumed (you cannot reasonably decide that x[:] is subsumed by x[1]; the other way around it makes sense), and so I error in this case.

Thanks, @phipsgabler for looking further into it. I think it is sensible to throw an error on these corner cases. It is also fine to forbid cases where it causes inconsistency with downstream packages like MCMCChains, if that helps to keep the complexity of PR under control.

src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
src/varname.jl Outdated Show resolved Hide resolved
@phipsgabler
Copy link
Member Author

What remains to be discussed are the choices about colons. Is concretizing them into ranges OK, and do the subsumption rules involving them not cause troubles.

I think I can argue for both (and think that some breaking changes are acceptable if it makes the overall implementation better and more consistent), but this needs to be cleared by people who can judge the downstream consequences better than me.

I'll add more test cases and README documentation to lay out the behaviour later this week.

@devmotion
Copy link
Member

It seems the PR breaks https://github.com/TuringLang/DynamicPPL.jl/blob/aeb5e03fdfe6958e4f79d9ccc5b01b84b93371ff/test/compiler.jl#L220. The model seems reasonable though, doesn't it?

Project.toml Outdated Show resolved Hide resolved
Co-authored-by: David Widmann <[email protected]>
@phipsgabler
Copy link
Member Author

It seems the PR breaks TuringLang/DynamicPPL.jl@aeb5e03/test/compiler.jl#L220. The model seems reasonable though, doesn't it?

It does indeed. I need to take a look at that.

@yebai
Copy link
Member

yebai commented Jun 8, 2022

@phipsgabler a gentle reminder

@phipsgabler
Copy link
Member Author

bors try

@bors
Copy link

bors bot commented Oct 5, 2022

try

Merge conflict.

@phipsgabler
Copy link
Member Author

bors try

bors bot added a commit that referenced this pull request Oct 5, 2022
@bors
Copy link

bors bot commented Oct 5, 2022

try

Build failed:

Project.toml Outdated Show resolved Hide resolved
Project.toml Outdated Show resolved Hide resolved
bors.toml Outdated Show resolved Hide resolved
@phipsgabler
Copy link
Member Author

The remaining DPPL errors are, I think, simply caused by the fact that varname should now be called with concretize = true in the compiler context.

@devmotion
Copy link
Member

devmotion commented Oct 5, 2022

If it breaks DynamicPPL, I guess it should be put into a breaking release?

Edit: Then also downstream tests would pass (they are canceled and pass if the downstream package is not compatible yet).

@phipsgabler phipsgabler dismissed torfjelde’s stale review October 5, 2022 12:53

I don't see why this is still here... am I missing something?

@yebai
Copy link
Member

yebai commented Nov 6, 2022

bors r+

bors bot pushed a commit that referenced this pull request Nov 6, 2022
Should implement #35.  The "problem" with this is that the resulting values are not exactly what you'd write by hand:

```julia
julia> AbstractPPL.concretize(@varname(x.a[1:end, end][:]), x)
x.a[1:2,2][Base.Slice(Base.OneTo(2))]
```

That wouldn't be so much of a problem besides printing, but unfortunately, lenses currenctly compare equality using strict types:

```julia
julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)).lens.indices == @varname(x[1:3, 1:10]).lens.indices
true

julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)).lens == @varname(x[1:3, 1:10]).lens
false

julia> AbstractPPL.concretize(@varname(x[1:3, :]), rand(10, 10)) == @varname(x[1:3, 1:10])
false
```

Cf. jw3126/Setfield.jl#165; the equality comparison can hopefully be fixed there.

The remaining thing is that subsumption must still be able to work with `Colon`, since a user might index a trace/VarInfo using a non-concretized varname containing a colon.  But at least we can then be sure that one side is always concrete.

Co-authored-by: Hong Ge <[email protected]>
Co-authored-by: Hong Ge <[email protected]>
@bors
Copy link

bors bot commented Nov 6, 2022

@bors bors bot changed the title Concretize IndexLens using to_indices [Merged by Bors] - Concretize IndexLens using to_indices Nov 6, 2022
@bors bors bot closed this Nov 6, 2022
@bors bors bot deleted the phg/concretization branch November 6, 2022 22:29
if Meta.isexpr(expr, :ref) || Meta.isexpr(expr, :.)
# Split into object/base symbol and lens.
sym_escaped, lens = Setfield.parse_obj_lens(expr)
# Setfield.jl escapes the return symbol, so we need to unescape
# to call `QuoteNode` on it.
sym = drop_escape(sym_escaped)

return if Setfield.need_dynamic_lens(expr)
:(
if concretize
Copy link
Member

Choose a reason for hiding this comment

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

@phipsgabler Do you remember why this change was made?

Copy link
Member

Choose a reason for hiding this comment

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

In particular, why it was made false by default.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants