Skip to content

Remove static dimension type parameter#42

Open
projekter wants to merge 4 commits intoJuliaMath:masterfrom
projekter:master
Open

Remove static dimension type parameter#42
projekter wants to merge 4 commits intoJuliaMath:masterfrom
projekter:master

Conversation

@projekter
Copy link

Currently, the "dimension" of the Sobol sequence is implemented as a static type parameter. However, the whole code never makes any use that the value is known at compile time. Instead, all it does is iterating over 1:ndims (which will typically be not so small that loop unrolling would be favorable in any way, and the loop bodies are too long for this anyway) and creating vectors of length ndims, which turns the static parameter into a runtime variable.

I would say that the whole point why for example Array uses a static dimension parameter instead of dynamic ones is:

  • code for efficient striding will be different for each dimension count and can then be efficiently compiler-generated and inlined
  • the "size" property can be a dimension-sized tuple with a size for each dimension, leading to type stability
  • and a very minor point is that dispatch will already do type checking when using arrays of wrong dimension, though since the design decision was made long before JET or PackageCompiler, this is not really be relevant here

For multiple SobolSeq of different dimension, instead, the compiler has to regenerate the code for every single method with the only difference between them being an integer constant in the function body, which is very inefficient. On top on this, any package that uses this one and needs to generate a SobolSeq with only runtime-known length (actually, I'm here because of MultistartOptimization.jl), unavoidably now has type instabilities and uninferrability (or it follows the same approach and "staticizes" the dynamical value, leading to dynamical dispatch and compiler overhead by method generation).
Given that there is no technical reason or benefit for the static type parameter, I propose to change it to a field of the struct instead, which solves all the mentioned problems. SobolSeq is now unparametrized and ScaledSobolSeq only contains the type of the boundaries as a type parameter.
This is a breaking change if people explicitly wrote SobolSeq{N} before, therefore this would be a major version increment.
Note that I increased the Julia version to 1.8 since I declared the ndims field const; however, this is the only reason for the compatibility reduction and can of course also be removed if you'd rather like to keep compatibility.

Closes #18 as obsolete (see also my comment there).

@codecov
Copy link

codecov bot commented Mar 16, 2026

Codecov Report

❌ Patch coverage is 75.00000% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.95%. Comparing base (3b404f2) to head (bca0038).

Files with missing lines Patch % Lines
src/Sobol.jl 75.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master      #42      +/-   ##
==========================================
+ Coverage   63.88%   66.95%   +3.06%     
==========================================
  Files           1        1              
  Lines         108      115       +7     
==========================================
+ Hits           69       77       +8     
+ Misses         39       38       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@projekter
Copy link
Author

Ok, how about this? The dimension is now grabbed from the vector as you suggested, and it can be any AbstractVector{UInt32}. I don't quite understand why StaticVector would be needed to make next! allocation free, for if you use the variant where you explicitly pass the output vector, it is always allocation-free (calling the one that allocates a new next! is a bit misleading, although of course the n field of the SobolSeq is mutated). But with this approach, it should also be possible to use MVectors. No SVectors however, mutability of the x field is required in next! (and therefore I also felt comfortable using it in the constructor).

@stevengj
Copy link
Member

I'm thinking of StaticArray for an interface that does not call the in-place variant next!, e.g. the iteration interface.

src/Sobol.jl Outdated
new(SobolSeq(N), lb, ub)
function ScaledSobolSeq{T,X}(lb::Vector{T}, ub::Vector{T}) where {T,X<:AbstractVector{UInt32}}
length(lb)==length(ub) || throw(DimensionMismatch("lb and ub do not have same length"))
new{T,X}(SobolSeq{X}(length(lb)), lb, ub)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe

Suggested change
new{T,X}(SobolSeq{X}(length(lb)), lb, ub)
x = similar(lb, T) # determine the type of array from lb
new{T,typeof{x}}(x, lb, ub)

Copy link
Author

Choose a reason for hiding this comment

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

But if you want to have a much greater level of flexibility, then this is overly restrictive - the bounds could very well be immutable SVectors, but x would have to be its mutable complement.

@projekter
Copy link
Author

Now you can explicitly pass any kind of vector to SobolSeq, and I also adjusted this for the scaled sequence. The bounds can now also be any kind of AbstractVector, in agreement with your previous comment.
Note that I also changed the sum used to determine the element type to a more type stable approach. However, lb and ub should now be of the same type - previously, any type was allowed, though I cannot imagine anyone ever used this.

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.

use StaticArrays

3 participants