-
Notifications
You must be signed in to change notification settings - Fork 37
VNT Part 2: Solving the issue of block elements #1180
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
base: mhauru/vnt-for-fastldf
Are you sure you want to change the base?
Changes from all commits
35c3e20
4253e9b
5cb3916
a96bb44
a8014e6
cfc6041
e198fbb
b77b0af
633e920
222334a
d22face
4cb49e1
420a6b2
d2f58d7
ce9da19
4eb33e9
51b399a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -206,13 +206,17 @@ an unlinked value. | |
|
|
||
| $(TYPEDFIELDS) | ||
| """ | ||
| struct RangeAndLinked | ||
| struct RangeAndLinked{T<:Tuple} | ||
| # indices that the variable corresponds to in the vectorised parameter | ||
| range::UnitRange{Int} | ||
| # whether it's linked | ||
| is_linked::Bool | ||
| # original size of the variable before vectorisation | ||
| original_size::T | ||
| end | ||
|
|
||
| Base.size(ral::RangeAndLinked) = ral.original_size | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It appears to me that this definition only exists to satisfy the VNT interface, and for this reason I think it is better to not rely on Using a separate function also avoids issues with structs imported from other libraries where julia> size(product_distribution((; a = Normal())))
ERROR: MethodError: no method matching size(::Distributions.ProductNamedTupleDistribution{(:a,), Tuple{Normal{Float64}}, Continuous, Float64})We could of course still have a fallback definition of vnt_size(x) = size(x)to avoid redefining size on everything.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is a very good idea. Will implement, not sure if I'll do it now or in January. |
||
|
|
||
| """ | ||
| VectorWithRanges{Tlink}( | ||
| varname_ranges::VarNamedTuple, | ||
|
|
@@ -247,7 +251,12 @@ struct VectorWithRanges{Tlink,VNT<:VarNamedTuple,T<:AbstractVector{<:Real}} | |
| end | ||
|
|
||
| function _get_range_and_linked(vr::VectorWithRanges, vn::VarName) | ||
| return vr.varname_ranges[vn] | ||
| # The type assertion does nothing if VectorWithRanges has concrete element types, as is | ||
| # the case for all type stable models. However, if the model is not type stable, | ||
| # vr.varname_ranges[vn] may infer to have type `Any`. In this case it is helpful to | ||
| # assert that it is a RangeAndLinked, because even though it remains non-concrete, | ||
| # it'll allow the compiler to infer the types of `range` and `is_linked`. | ||
| return vr.varname_ranges[vn]::RangeAndLinked | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without this assertion, this model: @model function demo_one_variable_multiple_constraints(
::Type{TV}=Vector{Float64}
) where {TV}
x = TV(undef, 5)
x[1] ~ Normal()
x[2] ~ InverseGamma(2, 3)
x[3] ~ truncated(Normal(), -5, 20)
x[4:5] ~ Dirichlet([1.0, 2.0])
return (x=x,)
endfails the test that checks that the return type of Now, I'm fine saying that a model that mixes Note that this problem is independent of the above comment of introducing a type parameter to
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think in principle we should be able to do better (but maybe not in practice). Once you remove the Tuple from RangeAndLinked, the definition of I'm inclined to say that if Julia is capable of inferring a return type of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This, too, I think is an excellent idea. The problem is indeed |
||
| end | ||
| function init( | ||
| ::Random.AbstractRNG, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's a bit unfortunate to have to create this field, because now you may end up with abstractly typed
RangeAndLinked, which obscures the fact that the two fields you really care about, namelyrangeandis_linked, are still concrete. The reason this is needed is that VNT needs to know how much "space" in aPartialArrayan instance ofRangeAndLinkedtakes. An alternative to this could be something like givingsetindex!!(::VarNamedTuple, ...)an extra kwarg of something likeignore_size_checks.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, either that -- or you could just pass the size itself as a kwarg and if the kwarg is not passed, it can fall back to calling
vnt_sizeon the value being set. not sure if that is bad for type stability as i've been bitten by julia kwargs many times but from an interface perspective, i think that is most flexible as it allows callers to specifically override the auto-size determination mechanism. my suspicion is type stability should be ok though since it will become Core.kwcall with NamedTuple{size::Tuple{Int,Int}} (or whatever the size is) which is all concretely typed.when constructing the VNT of RangeAndLinked (inside the LDF setup code) it should be trivial to determine the size separately from the range + linked status and pass it into setindex. right now we would just need to read from the Metadata dist field, otherwise if it's an accumulator then the info is passed through from accumulate_assume!!. after the VNT has been constructed we would only ever read from the VNT, so there would no longer be any need to know the size and it can be dropped from this struct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it makes this extra unfortunate that this is only needed when setting the value, by getindex-time it's not needed. I'm not necessarily opposed to something like the
kwargsolution, though I'm not a big fan of how it's different fromBangBang.setindex!!for everything else. I simultaneously hold these two conflicting opinions:fthat operates for many types, and it uses the (semantically) same arguments for almost all of them, then if to define your own method forfyou need to change the arguments, that means you shouldn't define the thing your defining as a method off, but rather as a different function.setindex!!is clearly the right function to define a method for for VNT, and anything else would be an unnecessary convolution of the interface that makes VNT less intuitive and more laborious to use.I don't think it's an unreasonable request to say that if you want to set a value of type
Xinto an array as a block, so that it covers some range like[1:3, 4:17, 100:101], then typeXmust have some suitable notion of size or otherwise it's a no-go. You could say that the notion of size something you provide ephemerally atsetindex!!time, but it feels a bit hacky, compared to saying thatXactually "owns" its notion of size.Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That makes sense. I think, the purist part of me (which is quite a large part of me...) much prefers your first argument over your second.
Why not both, though? We could have
BangBang.setindex!!for 99% of use cases, and something likeDynamicPPL.setindex!!_sizedthat allows you to pass the size (or skip the check and infer from the varname; I'm ok with either). The implementation would be mostly sharedso code duplication would not be too much; and presumably one can explain in the docstring that the latter should only be used when you the programmer promises that it's a sensible operation (maybe call it unsafe or anything, but it's not really unsafe since there's no real danger in setting a wrong size, as long as you are always consistent with the indices used for getting and setting, which the code enforces).
I guess it is a bit more complexity; personally I would be OK with making that tradeoff in return for the satisfaction of knowing that we have done Good Programming Practices.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another way of framing it is that we're going to have to introduce a hack somewhere to make this work, and I think I prefer the hack of controlling the size (or equivalently skipping the check and inferring it from the varname key; come to think of it, I'm starting to prefer that option) vs. the hack of bundling additional fields into a data structure that doesn't really need it. I don't think that RangeAndLinked intrinsically needs the size: if we were using a Dict{VarName} then there's no need for the size (although of course that comes with other drawbacks). So the way I see it, the reason for needing a hack is VNT, and it seems unfair to force RangeAndLinked to adjust itself for someone else.