JuliaMath / JuliaMath/MeasureTheory.jl
Redundant computation for Affine measures
- Dominant language
- Julia
- Stars
- 401
- Forks
- 31
- PR merge metrics
- No merged PRs in 30d
Description
Say we have a measure like
```julia
julia> d = MvNormal((σ=[3;4;;],))
Affine{(:σ,),PowerMeasure{typeof(identity), Normal{(), Tuple{}}, 1, Tuple{Base.OneTo{Int64}}},Tuple{Matrix{Int64}}}(AffineTransform{(:σ,),Tuple{Matrix{Int64}}}((σ = [3; 4;;],)), Normal() ^ 1)
```
Then this works just fine:
```julia
julia> x = rand(d)
2-element Vector{Float64}:
-3.212402995915957
-4.283203994554609
julia> logdensity(d,x)
-0.5733073893427674
```
But there's a problem. Along the way, this requires calculating
```julia
z = d.σ \ x
```
and the base measure is left as
```julia
julia> basemeasure(d)
0.398942 * Affine{(:σ,),PowerMeasure{typeof(identity), Lebesgue{ℝ}, 1, Tuple{Base.OneTo{Int64}}},Tuple{Matrix{Int64}}}(AffineTransform{(:σ,),Tuple{Matrix{Int64}}}((σ = [3; 4;;],)), Lebesgue(ℝ) ^ 1)
```
So computing the logdensity with respect to other measures will typically require more repetitions of the `z = d.σ \ x` code, which is very inefficient.
Adding some `@show` statements in `logpdf` makes this clear:
```julia
julia> logpdf(d, [0,0])
d = Affine{(:σ,),PowerMeasure{typeof(identity), Normal{(), Tuple{}}, 1, Tuple{Base.OneTo{Int64}}},Tuple{Matrix{Int64}}}(AffineTransform{(:σ,),Tuple{Matrix{Int64}}}((σ = [3; 4;;],)), Normal() ^ 1)
z = [-0.0]
d = 0.398942 * Affine{(:σ,),PowerMeasure{typeof(identity), Lebesgue{ℝ}, 1, Tuple{Base.OneTo{Int64}}},Tuple{Matrix{Int64}}}(AffineTransform{(:σ,),Tuple{Matrix{Int64}}}((σ = [3; 4;;],)), Lebesgue(ℝ) ^ 1)
z = [-0.0]
d = Affine{(:σ,),PowerMeasure{typeof(identity), Lebesgue{ℝ}, 1, Tuple{Base.OneTo{Int64}}},Tuple{Matrix{Int64}}}(AffineTransform{(:σ,),Tuple{Matrix{Int64}}}((σ = [3; 4;;],)), Lebesgue(ℝ) ^ 1)
z = [-0.0]
d = 0.2 * Lebesgue(ℝ) ^ 1
d = Lebesgue(ℝ) ^ 1
-2.528376445638773
```
Each `z = ...` is the result of solving the same linear system, three times.
I got a start on fixing this, by adding a `MapsTo` combinator:
```julia
struct MapsTo{X,Y}
x::X
y::Y
end
export ↦, mapsto
mapsto(x,y) = x ↦ y
↦(x::X, y::Y) where {X,Y} = MapsTo{X,Y}(x,y)
Base.first(t::MapsTo) = t.x
Base.last(t::MapsTo) = t.y
Base.Pair(t::MapsTo) = t.x => t.y
Base.show(io::IO, t::MapsTo) = print(t.x, " ↦ ", t.y)
```
It's pretty easy to make this work with `logpdf`, but for a more generic `logdensity` I think it will take some restructuring. We might need a `LogdensityComputation` struct that's just for internal use, and holds things we know so far about the result.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.