-
Notifications
You must be signed in to change notification settings - Fork 8
Fix stride computation for dimensions with shape 0 in ndarray #68
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: main
Are you sure you want to change the base?
Changes from all commits
34f9a77
65d81f1
1c02d43
96de41c
3bda108
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 | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -785,9 +785,21 @@ function NDArray( | |||||||
| offset = 0 | ||||||||
| end | ||||||||
| if strides isa Nothing | ||||||||
| # Calculate byte strides in C order | ||||||||
| # Calculate byte strides in C order. Any dimension of length zero is treated as | ||||||||
| # length 1 within this product only (not in `shape` itself); this matches NumPy's | ||||||||
| # convention for computing default C-contiguous strides (`PyArray_NewFromDescr`), | ||||||||
| # relied on by the reference Python `asdf` package when constructing arrays via | ||||||||
| # `np.ndarray(shape, dtype, data, offset, None, order)`. Without the clamp, any | ||||||||
| # zero-length dimension collapses the strides of every outer dimension whose | ||||||||
| # product includes it down to zero, which then fails the `strides` positivity | ||||||||
| # check below even though no data is ever read from a zero-size array. Negative | ||||||||
| # entries are left unclamped so the `shape` negativity check below still reports | ||||||||
| # them with its own clear error message. STScI Roman L2 `.asdf` products contain | ||||||||
| # zero-shape arrays (e.g. `chisq`, `dumo`) with no explicit `strides` key, | ||||||||
| # triggering this exact failure prior to the fix. | ||||||||
| sz = sizeof(Type(datatype)) | ||||||||
| strides = reverse(cumprod([sz; reverse(shape[(begin + 1):end])])) | ||||||||
| clamped_shape = [s == 0 ? 1 : s for s in shape[(begin + 1):end]] | ||||||||
| strides = reverse(cumprod([sz; reverse(clamped_shape)])) | ||||||||
|
Comment on lines
+801
to
+802
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.
Suggested change
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 this is type-stable and requires fewer changes to the original line here
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 tried this first but the if any(shape .< 0)
throw(ArgumentError("`shape` cannot have negative elements."))
end
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'm not sure if I follow. Even when a negative julia> let
shape = Int64[-2, 3] # [2, -3], [-3], etc.
strides = nothing
ASDF.NDArray(ASDF.LazyBlockHeaders(), Int64(0), nothing, shape, ASDF.Datatype_float32, ASDF.host_byteorder, Int64(0), strides)
end
ERROR: ArgumentError: `shape` cannot have negative elements. |
||||||||
| end | ||||||||
| return NDArray( | ||||||||
| lazy_block_headers, source, data, Vector{Int64}(shape), datatype, byteorder, Int64(offset), Vector{Int64}(strides) | ||||||||
|
|
@@ -833,7 +845,7 @@ size(result) == Tuple(reverse(ndarray.shape)) | |||||||
| eltype(result) == ASDF.materialized_eltype(ndarray.datatype) | ||||||||
| ``` | ||||||||
|
|
||||||||
| For the `ucs4` and `ascii` string datatypes, [`materialized_eltype`](@ref) is a thin `AbstractString` view over the characters ([`UCS4String`](@ref) / [`AsciiString`](@ref); see [`stringify_data`](@ref)). For all other datatypes, `eltype(result) == Type(ndarray.datatype)` and additionally `sizeof(eltype) .* strides(result) == Tuple(reverse(ndarray.strides))`. | ||||||||
| For the `ucs4` and `ascii` string datatypes, [`materialized_eltype`](@ref) is a thin `AbstractString` view over the characters ([`UCS4String`](@ref) / [`AsciiString`](@ref); see [`stringify_data`](@ref)). For all other datatypes, `eltype(result) == Type(ndarray.datatype)` and additionally, when the array has at least one element, `sizeof(eltype) .* strides(result) == Tuple(reverse(ndarray.strides))` along every dimension with more than one element. (A dimension of length 1 has no well-defined stride, there is no pair of adjacent elements to space apart along it, and in a zero-element array no dimension does, so `reshape`/`reinterpret` are free to report any value there; such strides are excluded from this check.) | ||||||||
| """ | ||||||||
| function Base.getindex(ndarray::NDArray) | ||||||||
| if ndarray.data !== nothing | ||||||||
|
|
@@ -870,7 +882,14 @@ function Base.getindex(ndarray::NDArray) | |||||||
| # Check array layout | ||||||||
| @assert size(data) == Tuple(reverse(ndarray.shape)) # `data` conforms to specified `ndarray.shape` | ||||||||
| @assert eltype(data) == Type(ndarray.datatype) # `data` matches type specified by `ndarray.datatype` | ||||||||
| if sizeof(eltype(data)) .* Base.strides(data) != Tuple(reverse(ndarray.strides)) | ||||||||
| # A dimension of length 1 has no meaningful stride (there is no pair of adjacent elements | ||||||||
| # to space apart along it), and in an empty array no dimension does — Julia reports | ||||||||
| # stride 0 along any dimension whose faster-varying dimensions include a zero length. | ||||||||
| # Only compare strides where they are meaningful. | ||||||||
| computed_strides = sizeof(eltype(data)) .* Base.strides(data) | ||||||||
| expected_strides = Tuple(reverse(ndarray.strides)) | ||||||||
| data_shape = size(data) | ||||||||
| if !isempty(data) && any(data_shape[i] > 1 && computed_strides[i] != expected_strides[i] for i in eachindex(data_shape)) | ||||||||
| error("`data` has different stride from `ndarray.strides`") | ||||||||
| end | ||||||||
|
|
||||||||
|
|
||||||||
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.
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.
I would keep "Negative entries are left unclamped so the
shapenegativity check below still reports them with its own clear error message.", see comment below