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

Support arbitrary indices for interpolation & extrapolation #146

Merged
merged 2 commits into from
Apr 11, 2017

Conversation

timholy
Copy link
Member

@timholy timholy commented Apr 7, 2017

The master branch of Interpolations assumes that the input arrays start indexing at 1. Starting with julia-0.5, it's possible to support arrays that index over arbitrary AbstractUnitRanges. This PR appears to support such arrays. While not tested yet, I presume the first consumer of this functionality will be ImageTransformations.jl, where we use non-1 based indices to keep track of where pixels "really" are.

In theory, internally the most thorough way to do this would have been to rework how we apply padding: it might be somewhat more natural to pad a 1:n range so that the internal padded version has indices 0:n+1 rather than 1:n+2. (Among other things, that would ensure that x = 2.2 would mean the same "physical" location no matter whether one was referring to the original or padded array.) However, that would make Interpolations strictly dependent on a package like OffsetArrays, and I wasn't sure whether people wanted that. So I decided to keep our current schemes and modify Interpolations so that OffsetArrays would be supported but not required.

One consequence of this decision is that any user of this functionality needs to extend A_ldiv_B_md! for whatever non1-array types (s)he wants to use. This seems necessary because our linear algebra infrastructure all assumes 1-based indices.

The point of having added benchmarks was to prepare for this PR, so...here are the regressions and improvements:

julia> regs = regressions(judge(minimum(pr), minimum(master)))
1-element BenchmarkTools.BenchmarkGroup:
  tags: []
  "bsplines" => 4-element BenchmarkGroup([])

julia> leaves(regs)
30-element Array{Any,1}:
 (Any["bsplines","constant","1d_Constant()_OnGrid_construct"],TrialJudgement(-1.90% => invariant))       
 (Any["bsplines","constant","1d_Constant()_OnGrid_use"],TrialJudgement(+10.33% => regression))           
 (Any["bsplines","constant","3d_Constant()_OnCell_use"],TrialJudgement(+49.27% => regression))           
 (Any["bsplines","constant","2d_Constant()_OnGrid_construct"],TrialJudgement(-2.46% => invariant))       
 (Any["bsplines","constant","1d_Constant()_OnCell_use"],TrialJudgement(+9.89% => regression))            
 (Any["bsplines","constant","2d_Constant()_OnCell_use"],TrialJudgement(+9.29% => regression))            
 (Any["bsplines","constant","2d_Constant()_OnGrid_use"],TrialJudgement(+8.99% => regression))            
 (Any["bsplines","constant","1d_Constant()_OnCell_construct"],TrialJudgement(-1.63% => invariant))       
 (Any["bsplines","constant","3d_Constant()_OnGrid_use"],TrialJudgement(+48.81% => regression))           
 (Any["bsplines","constant","2d_Constant()_OnCell_construct"],TrialJudgement(-0.09% => invariant))       
 (Any["bsplines","cubic","2d_Quadratic(Flat())_OnCell_use"],TrialJudgement(+5.31% => regression))        
 (Any["bsplines","cubic","2d_Quadratic(Flat())_OnGrid_use"],TrialJudgement(+5.26% => regression))        
 (Any["bsplines","linear","2d_Linear()_OnGrid_construct"],TrialJudgement(-2.20% => invariant))           
 (Any["bsplines","linear","1d_Linear()_OnGrid_use"],TrialJudgement(+6.37% => regression))                
 (Any["bsplines","linear","1d_Linear()_OnGrid_construct"],TrialJudgement(-3.05% => invariant))           
 (Any["bsplines","linear","1d_Linear()_OnCell_construct"],TrialJudgement(-0.46% => invariant))           
 (Any["bsplines","linear","2d_Linear()_OnCell_use"],TrialJudgement(+5.81% => regression))                
 (Any["bsplines","linear","3d_Linear()_OnCell_use"],TrialJudgement(+15.79% => regression))               
 (Any["bsplines","linear","2d_Linear()_OnCell_construct"],TrialJudgement(-3.15% => invariant))           
 (Any["bsplines","linear","2d_Linear()_OnGrid_use"],TrialJudgement(+8.27% => regression))                
 (Any["bsplines","linear","3d_Linear()_OnGrid_use"],TrialJudgement(+14.46% => regression))               
 (Any["bsplines","quadratic","3d_Quadratic(Free())_OnCell_use"],TrialJudgement(+6.48% => regression))    
 (Any["bsplines","quadratic","3d_Quadratic(Free())_OnGrid_use"],TrialJudgement(+6.74% => regression))    
 (Any["bsplines","quadratic","3d_Quadratic(Periodic())_OnGrid_use"],TrialJudgement(+6.65% => regression))
 (Any["bsplines","quadratic","3d_Quadratic(Periodic())_OnCell_use"],TrialJudgement(+6.68% => regression))
 (Any["bsplines","quadratic","2d_Quadratic(InPlace())_OnCell_use"],TrialJudgement(+21.72% => regression))
 (Any["bsplines","quadratic","3d_Quadratic(InPlace())_OnCell_use"],TrialJudgement(+9.35% => regression)) 
 (Any["bsplines","quadratic","3d_Quadratic(Reflect())_OnGrid_use"],TrialJudgement(+6.81% => regression)) 
 (Any["bsplines","quadratic","3d_Quadratic(Line())_OnCell_use"],TrialJudgement(+6.32% => regression))    
 (Any["bsplines","quadratic","3d_Quadratic(Line())_OnGrid_use"],TrialJudgement(+30.08% => regression))   

julia> imps = improvements(judge(minimum(pr), minimum(master)))
1-element BenchmarkTools.BenchmarkGroup:
  tags: []
  "bsplines" => 2-element BenchmarkGroup([])

julia> leaves(imps)
32-element Array{Any,1}:
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnGrid_construct"],TrialJudgement(-2.93% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-3.61% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-3.16% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Flat())_OnGrid_construct"],TrialJudgement(-5.22% => improvement))        
 (Any["bsplines","cubic","1d_Quadratic(Periodic())_OnCell_construct"],TrialJudgement(-2.30% => invariant))      
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnCell_use"],TrialJudgement(-10.98% => improvement))             
 (Any["bsplines","cubic","2d_Quadratic(Periodic())_OnGrid_use"],TrialJudgement(-5.14% => improvement))          
 (Any["bsplines","cubic","2d_Quadratic(Free())_OnCell_use"],TrialJudgement(-5.82% => improvement))              
 (Any["bsplines","cubic","1d_Quadratic(Free())_OnCell_construct"],TrialJudgement(-2.52% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Free())_OnGrid_construct"],TrialJudgement(-1.42% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Periodic())_OnGrid_construct"],TrialJudgement(-1.76% => invariant))      
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnGrid_construct"],TrialJudgement(-1.91% => invariant))      
 (Any["bsplines","quadratic","3d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-14.48% => improvement))   
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-4.32% => invariant))      
 (Any["bsplines","quadratic","2d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-1.22% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-13.18% => improvement))   
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnGrid_use"],TrialJudgement(-9.00% => improvement))      
 (Any["bsplines","quadratic","1d_Quadratic(Reflect())_OnGrid_construct"],TrialJudgement(-2.64% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Flat())_OnGrid_construct"],TrialJudgement(-2.44% => invariant))      
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnCell_construct"],TrialJudgement(-2.71% => invariant))  
 (Any["bsplines","quadratic","3d_Quadratic(InPlaceQ())_OnCell_construct"],TrialJudgement(-5.24% => improvement))
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnCell_use"],TrialJudgement(-9.15% => improvement))          
 (Any["bsplines","quadratic","3d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-5.84% => improvement))    
 (Any["bsplines","quadratic","2d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-1.72% => invariant))      
 (Any["bsplines","quadratic","1d_Quadratic(InPlace())_OnCell_construct"],TrialJudgement(-11.11% => improvement))
 (Any["bsplines","quadratic","1d_Quadratic(Free())_OnCell_construct"],TrialJudgement(-4.40% => invariant))      
 (Any["bsplines","quadratic","2d_Quadratic(InPlaceQ())_OnCell_use"],TrialJudgement(-10.85% => improvement))     
 (Any["bsplines","quadratic","3d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-1.90% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Free())_OnGrid_construct"],TrialJudgement(-5.78% => improvement))    
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnGrid_construct"],TrialJudgement(-3.06% => invariant))  
 (Any["bsplines","quadratic","3d_Quadratic(Flat())_OnCell_use"],TrialJudgement(-5.06% => improvement))          
 (Any["bsplines","quadratic","1d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-3.70% => invariant))   

Most of this is probably noise, but I suspect the regressions on Constant (esp. for 3d) are real. If anyone is concerned about that, I suspect the next step would be to turn map_repeat into a @generated function. I'd be interested in knowing how folks feel about the "cleanliness" vs "performance" tradeoff in this case.

ubound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d) = size(itp, d)
lbound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d) = 0.5
ubound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnCell}, d) = size(itp, d)+0.5
lbound{T,N,TCoefs,IT}(itp::BSplineInterpolation{T,N,TCoefs,IT,OnGrid}, d) =
Copy link
Member

Choose a reason for hiding this comment

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

Could it potentially help here to have something like firstindex instead of first(indices?

I suppose since this will usually be constructing Base.OneTo or a <:Range it probably isn't going to make a performance difference, but I'd be curious to see if special casing first and last indices would help us overcome the performance regression with 3d Constant BSplines

@sglyon
Copy link
Member

sglyon commented Apr 11, 2017

Hey @timholy this looks really cool, thanks for putting it together!

@sglyon
Copy link
Member

sglyon commented Apr 11, 2017

Also RE clearness and performance

My take is that code clarity is always a plus, but given the level of performance-driven metaprogramming we are doing here that ship might have already sailed...

What helped me most to wrap my head around how this library works was helpful comments and docstrings.

Prefiltering currently requires extending A_ldiv_B_md! for non1-array types.
This seems necessary because our linear algebra infrastructure all assumes 1-based indices.
@timholy
Copy link
Member Author

timholy commented Apr 11, 2017

OK, I made map_repeat a @generated function (or stated more precisely, I ensured it got inlined), and that largely fixes the performance issues:

julia> regs = regressions(judge(minimum(pr), minimum(master)))
1-element BenchmarkTools.BenchmarkGroup:
  tags: []
  "bsplines" => 3-element BenchmarkGroup([])

julia> leaves(regs)
15-element Array{Any,1}:
 (Any["bsplines","constant","1d_Constant()_OnGrid_construct"],TrialJudgement(-0.86% => invariant))   
 (Any["bsplines","constant","1d_Constant()_OnGrid_use"],TrialJudgement(+9.85% => regression))        
 (Any["bsplines","constant","3d_Constant()_OnCell_use"],TrialJudgement(+15.51% => regression))       
 (Any["bsplines","constant","2d_Constant()_OnGrid_construct"],TrialJudgement(-1.48% => invariant))   
 (Any["bsplines","constant","1d_Constant()_OnCell_use"],TrialJudgement(+9.84% => regression))        
 (Any["bsplines","constant","2d_Constant()_OnCell_use"],TrialJudgement(+9.19% => regression))        
 (Any["bsplines","constant","2d_Constant()_OnGrid_use"],TrialJudgement(+8.91% => regression))        
 (Any["bsplines","constant","1d_Constant()_OnCell_construct"],TrialJudgement(-0.85% => invariant))   
 (Any["bsplines","constant","3d_Constant()_OnGrid_use"],TrialJudgement(+8.58% => regression))        
 (Any["bsplines","constant","2d_Constant()_OnCell_construct"],TrialJudgement(-0.27% => invariant))   
 (Any["bsplines","cubic","3d_Quadratic(Periodic())_OnGrid_use"],TrialJudgement(+5.29% => regression))
 (Any["bsplines","linear","1d_Linear()_OnGrid_construct"],TrialJudgement(-1.05% => invariant))       
 (Any["bsplines","linear","1d_Linear()_OnCell_construct"],TrialJudgement(-0.01% => invariant))       
 (Any["bsplines","linear","2d_Linear()_OnCell_construct"],TrialJudgement(-1.99% => invariant))       
 (Any["bsplines","linear","2d_Linear()_OnGrid_construct"],TrialJudgement(-1.67% => invariant))       

julia> imps = improvements(judge(minimum(pr), minimum(master)))
1-element BenchmarkTools.BenchmarkGroup:
  tags: []
  "bsplines" => 2-element BenchmarkGroup([])

julia> leaves(imps)
32-element Array{Any,1}:
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnGrid_construct"],TrialJudgement(-3.22% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Flat())_OnGrid_construct"],TrialJudgement(-5.76% => improvement))        
 (Any["bsplines","cubic","2d_Quadratic(Free())_OnCell_use"],TrialJudgement(-6.13% => improvement))              
 (Any["bsplines","cubic","1d_Quadratic(Periodic())_OnCell_construct"],TrialJudgement(-2.10% => invariant))      
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnCell_use"],TrialJudgement(-10.77% => improvement))             
 (Any["bsplines","cubic","1d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-2.26% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Free())_OnCell_construct"],TrialJudgement(-2.28% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Free())_OnGrid_construct"],TrialJudgement(-2.11% => invariant))          
 (Any["bsplines","cubic","1d_Quadratic(Periodic())_OnGrid_construct"],TrialJudgement(-1.48% => invariant))      
 (Any["bsplines","cubic","1d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-1.61% => invariant))          
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnGrid_construct"],TrialJudgement(-3.17% => invariant))      
 (Any["bsplines","quadratic","3d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-13.75% => improvement))   
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnCell_construct"],TrialJudgement(-4.16% => invariant))      
 (Any["bsplines","quadratic","2d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-1.51% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-13.07% => improvement))   
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnGrid_use"],TrialJudgement(-9.20% => improvement))      
 (Any["bsplines","quadratic","1d_Quadratic(Reflect())_OnGrid_construct"],TrialJudgement(-3.19% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Flat())_OnGrid_construct"],TrialJudgement(-2.93% => invariant))      
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnCell_construct"],TrialJudgement(-2.79% => invariant))  
 (Any["bsplines","quadratic","1d_Quadratic(Line())_OnCell_use"],TrialJudgement(-9.12% => improvement))          
 (Any["bsplines","quadratic","3d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-6.30% => improvement))    
 (Any["bsplines","quadratic","2d_Quadratic(Flat())_OnCell_construct"],TrialJudgement(-1.43% => invariant))      
 (Any["bsplines","quadratic","3d_Quadratic(InPlaceQ())_OnCell_use"],TrialJudgement(-8.45% => improvement))      
 (Any["bsplines","quadratic","1d_Quadratic(InPlace())_OnCell_construct"],TrialJudgement(-10.25% => improvement))
 (Any["bsplines","quadratic","1d_Quadratic(Free())_OnCell_construct"],TrialJudgement(-3.67% => invariant))      
 (Any["bsplines","quadratic","2d_Quadratic(InPlaceQ())_OnCell_use"],TrialJudgement(-10.74% => improvement))     
 (Any["bsplines","quadratic","3d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-2.25% => invariant))   
 (Any["bsplines","quadratic","1d_Quadratic(Free())_OnGrid_construct"],TrialJudgement(-5.14% => improvement))    
 (Any["bsplines","quadratic","1d_Quadratic(Periodic())_OnGrid_construct"],TrialJudgement(-3.01% => invariant))  
 (Any["bsplines","quadratic","3d_Quadratic(Flat())_OnCell_use"],TrialJudgement(-8.46% => improvement))          
 (Any["bsplines","quadratic","3d_Quadratic(Reflect())_OnCell_use"],TrialJudgement(-7.23% => improvement))       
 (Any["bsplines","quadratic","1d_Quadratic(Reflect())_OnCell_construct"],TrialJudgement(-3.35% => invariant))   

Since on average this is faster than master, I'll merge it once it gets through CI.

@timholy timholy merged commit 0ecd53b into master Apr 11, 2017
@timholy timholy deleted the teh/indices2 branch April 11, 2017 10:26
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.

2 participants