Repository: JuliaArrays/OffsetArrays.jl Branch: master Commit: b0d729d61518 Files: 26 Total size: 204.0 KB Directory structure: gitextract_gvhhoopx/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── CompatHelper.yml │ ├── TagBot.yml │ ├── UnitTest.yml │ ├── docs.yml │ └── invalidations.yml ├── .gitignore ├── LICENSE.md ├── Project.toml ├── README.md ├── benchmark/ │ └── benchmarks.jl ├── docs/ │ ├── .gitignore │ ├── Project.toml │ ├── make.jl │ └── src/ │ ├── index.md │ ├── internals.md │ └── reference.md ├── ext/ │ └── OffsetArraysAdaptExt.jl ├── src/ │ ├── OffsetArrays.jl │ ├── axes.jl │ ├── origin.jl │ ├── precompile.jl │ └── utils.jl └── test/ ├── customranges.jl ├── origin.jl └── runtests.jl ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "github-actions" directory: "/" # Location of package manifests schedule: interval: "weekly" ================================================ FILE: .github/workflows/CompatHelper.yml ================================================ name: CompatHelper on: schedule: - cron: 0 0 * * * workflow_dispatch: permissions: contents: write pull-requests: write jobs: CompatHelper: runs-on: ubuntu-latest steps: - name: Check if Julia is already available in the PATH id: julia_in_path run: which julia continue-on-error: true - name: Install Julia, but only if it is not already available in the PATH uses: julia-actions/setup-julia@v2 with: version: '1' arch: ${{ runner.arch }} if: steps.julia_in_path.outcome != 'success' - name: "Add the General registry via Git" run: | import Pkg ENV["JULIA_PKG_SERVER"] = "" Pkg.Registry.add("General") shell: julia --color=yes {0} - name: "Install CompatHelper" run: | import Pkg name = "CompatHelper" uuid = "aa819f21-2bde-4658-8897-bab36330d9b7" version = "3" Pkg.add(; name, uuid, version) shell: julia --color=yes {0} - name: "Run CompatHelper" run: | import CompatHelper CompatHelper.main() shell: julia --color=yes {0} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COMPATHELPER_PRIV: ${{ secrets.DOCUMENTER_KEY }} # COMPATHELPER_PRIV: ${{ secrets.COMPATHELPER_PRIV }} ================================================ FILE: .github/workflows/TagBot.yml ================================================ name: TagBot on: issue_comment: types: - created workflow_dispatch: jobs: TagBot: if: github.event_name == 'workflow_dispatch' || github.actor == 'JuliaTagBot' runs-on: ubuntu-latest steps: - uses: JuliaRegistries/TagBot@v1 with: token: ${{ secrets.GITHUB_TOKEN }} ssh: ${{ secrets.TAGBOT }} ================================================ FILE: .github/workflows/UnitTest.yml ================================================ name: Unit test on: push: tags: - 'v*' branches: - master paths-ignore: - 'LICENSE.md' - 'README.md' - '.github/workflows/TagBot.yml' pull_request: paths-ignore: - 'LICENSE.md' - 'README.md' - '.github/workflows/TagBot.yml' schedule: - cron: '20 00 1 * *' concurrency: group: build-${{ github.event.pull_request.number || github.ref }}-${{ github.workflow }} cancel-in-progress: true jobs: test: runs-on: ${{ matrix.os }} strategy: # allow-failure is not supported yet # https://github.com/actions/toolkit/issues/399 fail-fast: false matrix: julia-version: ['1.0', 'lts', '1', 'pre'] os: [ubuntu-latest, windows-latest, macOS-latest] julia-arch: [x64] # only test one 32-bit job include: - os: ubuntu-latest julia-version: '1' julia-arch: x86 steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@v2 with: version: ${{ matrix.julia-version }} arch: ${{ matrix.julia-arch }} - uses: julia-actions/cache@v3 - name: "Unit Test" uses: julia-actions/julia-runtest@v1 - uses: julia-actions/julia-processcoverage@v1 - uses: codecov/codecov-action@v6 with: token: ${{ secrets.CODECOV_TOKEN }} files: lcov.info ================================================ FILE: .github/workflows/docs.yml ================================================ name: Documentation on: pull_request: push: branches: - 'master' - 'release-' tags: '*' release: types: [published] jobs: build: runs-on: ${{ matrix.os }} strategy: matrix: julia-version: [1] os: [ubuntu-latest] steps: - uses: actions/checkout@v6 - uses: julia-actions/setup-julia@latest with: version: ${{ matrix.julia-version }} - name: Cache artifacts uses: actions/cache@v5 env: cache-name: cache-artifacts with: path: ~/.julia/artifacts key: ${{ runner.os }}-test-${{ env.cache-name }}-${{ hashFiles('**/Project.toml') }} restore-keys: | ${{ runner.os }}-test-${{ env.cache-name }}- ${{ runner.os }}-test- ${{ runner.os }}- - name: Install dependencies run: julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); Pkg.instantiate()' - name: Build and deploy env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: julia --project=docs/ docs/make.jl ================================================ FILE: .github/workflows/invalidations.yml ================================================ name: Invalidations on: pull_request jobs: evaluate: runs-on: ubuntu-latest steps: - uses: julia-actions/setup-julia@v2 with: version: '1.10' - uses: actions/checkout@v6 - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-invalidations@v1 id: invs_pr - uses: actions/checkout@v6 with: ref: 'master' - uses: julia-actions/julia-buildpkg@latest - uses: julia-actions/julia-invalidations@v1 id: invs_master - name: Report invalidation counts run: | echo "Invalidations on master: ${{ steps.invs_master.outputs.total }} (${{ steps.invs_master.outputs.deps }} via deps)" echo "This branch: ${{ steps.invs_pr.outputs.total }} (${{ steps.invs_pr.outputs.deps }} via deps)" shell: bash - name: PR doesn't increase number of invalidations run: | if (( ${{ steps.invs_pr.outputs.total }} > ${{ steps.invs_master.outputs.total }} )); then exit 1 fi shell: bash ================================================ FILE: .gitignore ================================================ Manifest.toml Manifest-v*.*.toml docs/build LocalPreferences.toml .vscode ================================================ FILE: LICENSE.md ================================================ The FArrayMod.jl package is licensed under the MIT "Expat" License: > Copyright (c) 2014: > * Alexander Samoilov > > Permission is hereby granted, free of charge, to any person obtaining > a copy of this software and associated documentation files (the > "Software"), to deal in the Software without restriction, including > without limitation the rights to use, copy, modify, merge, publish, > distribute, sublicense, and/or sell copies of the Software, and to > permit persons to whom the Software is furnished to do so, subject to > the following conditions: > > The above copyright notice and this permission notice shall be > included in all copies or substantial portions of the Software. > > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, > EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF > MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. > IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY > CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, > TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE > SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Project.toml ================================================ name = "OffsetArrays" uuid = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" version = "1.17.0" [deps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" [weakdeps] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" [extensions] OffsetArraysAdaptExt = "Adapt" [compat] Adapt = "2, 3, 4" Aqua = "0.8" CatIndices = "0.2" DelimitedFiles = "<0.0.1, 1" DistributedArrays = "0.6" Documenter = "0.27, 1" EllipsisNotation = "1" FillArrays = "0.11, 0.13, 1" LinearAlgebra = "<0.0.1, 1" StaticArrays = "1" Test = "<0.0.1, 1" julia = "0.7, 1" [extras] Adapt = "79e6a3ab-5dfb-504d-930d-738a2a938a0e" Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" CatIndices = "aafaddc9-749c-510e-ac4f-586e18779b91" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" DistributedArrays = "aaf54ef3-cdf8-58ed-94cc-d582ad619b94" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" EllipsisNotation = "da5c29d0-fa7d-589e-88eb-ea29b0a81949" FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] test = ["Adapt", "Aqua", "CatIndices", "DistributedArrays", "DelimitedFiles", "Documenter", "EllipsisNotation", "FillArrays", "LinearAlgebra", "StaticArrays", "Test"] ================================================ FILE: README.md ================================================ # OffsetArrays.jl | Documentation | Build Status | Code coverage | Version | | :-: | :-: | :-: | :-: | | [![][docs-stable-img]][docs-stable-url] | [![][action-img]][action-url] | [![][codecov-img]][codecov-url] | [![][ver-img]][ver-url] | | [![][docs-dev-img]][docs-dev-url] | [![][pkgeval-img]][pkgeval-url] | | [![][deps-img]][deps-url] | ## Introduction OffsetArrays provides Julia users with arrays that have arbitrary indices, similar to those found in some other programming languages like Fortran. An `OffsetArray` is a lightweight wrapper around an `AbstractArray` that shifts its indices. Generally, indexing into an `OffsetArray` should be as performant as the parent array. ## Usage There are two ways to construct `OffsetArray`s: by specifying the axes of the array, or by specifying its origin. The first way to construct an `OffsetArray` by specifying its axes is: ```julia OA = OffsetArray(A, axis1, axis2, ...) ``` where you want `OA` to have axes `(axis1, axis2, ...)` and be indexed by values that fall within these axis ranges. Example: ```julia julia> using OffsetArrays julia> A = Float64.(reshape(1:15, 3, 5)) 3×5 Matrix{Float64}: 1.0 4.0 7.0 10.0 13.0 2.0 5.0 8.0 11.0 14.0 3.0 6.0 9.0 12.0 15.0 julia> axes(A) # indices of a Matrix start from 1 along each axis (Base.OneTo(3), Base.OneTo(5)) julia> OA = OffsetArray(A, -1:1, 0:4) # OA will have the axes (-1:1, 0:4) 3×5 OffsetArray(::Matrix{Float64}, -1:1, 0:4) with eltype Float64 with indices -1:1×0:4: 1.0 4.0 7.0 10.0 13.0 2.0 5.0 8.0 11.0 14.0 3.0 6.0 9.0 12.0 15.0 julia> OA[-1, 0] 1.0 julia> OA[1, 4] 15.0 ``` The second way to construct an `OffsetArray` is by specifying the origin, that is, the first index along each axis. This is particularly useful if one wants, eg., arrays that are 0-indexed as opposed to 1-indexed. A convenient way to construct an `OffsetArray` this way is by using `OffsetArrays.Origin`: ```julia julia> using OffsetArrays: Origin julia> Origin(0)(A) # indices begin at 0 along all axes 3×5 OffsetArray(::Matrix{Float64}, 0:2, 0:4) with eltype Float64 with indices 0:2×0:4: 1.0 4.0 7.0 10.0 13.0 2.0 5.0 8.0 11.0 14.0 3.0 6.0 9.0 12.0 15.0 julia> Origin(2, 3)(A) # indices begin at 2 along the first axis and 3 along the second 3×5 OffsetArray(::Matrix{Float64}, 2:4, 3:7) with eltype Float64 with indices 2:4×3:7: 1.0 4.0 7.0 10.0 13.0 2.0 5.0 8.0 11.0 14.0 3.0 6.0 9.0 12.0 15.0 ``` While the examples here refer to the common case where the parent arrays have indices starting at 1, this is not necessary. An `OffsetArray` may wrap any array that has integer indices, irrespective of where the indices begin. ## How to go back to 1-indexed arrays Certain libraries, such as `LinearAlgebra`, require arrays to be indexed from 1. Passing an `OffsetArray` with shifted indices would lead to an error here. ```julia julia> A = Float64.(reshape(1:16, 4, 4)); julia> AO = Origin(0)(A); julia> using LinearAlgebra julia> Diagonal(AO) ERROR: ArgumentError: offset arrays are not supported but got an array with index other than 1 ``` The way to obtain a `1`-indexed array from an `OffsetArray` is by using `OffsetArrays.no_offset_view`. An example of this is: ```julia julia> OffsetArrays.no_offset_view(AO) 4×4 Matrix{Float64}: 1.0 5.0 9.0 13.0 2.0 6.0 10.0 14.0 3.0 7.0 11.0 15.0 4.0 8.0 12.0 16.0 ``` This may now be passed to `LinearAlgebra`: ```julia julia> D = Diagonal(OffsetArrays.no_offset_view(AO)) 4×4 Diagonal{Float64, Vector{Float64}}: 1.0 ⋅ ⋅ ⋅ ⋅ 6.0 ⋅ ⋅ ⋅ ⋅ 11.0 ⋅ ⋅ ⋅ ⋅ 16.0 ``` If we want to restore the original indices of `AO`, we may wrap an `OffsetArray` around the `Diagonal` as: ```julia julia> Origin(AO)(D) 4×4 OffsetArray(::Diagonal{Float64, Vector{Float64}}, 0:3, 0:3) with eltype Float64 with indices 0:3×0:3: 1.0 ⋅ ⋅ ⋅ ⋅ 6.0 ⋅ ⋅ ⋅ ⋅ 11.0 ⋅ ⋅ ⋅ ⋅ 16.0 ``` Here, `Origin(AO)` is able to automatically infer and use the indices of `AO`. ## Best practice on adopting OffsetArrays For some applications, OffsetArrays give users an easy-to-understand interface. However, handling the non-conventional axes of OffsetArrays requires extra care. Otherwise, the code might error, crash, or return incorrect results. You can read [the Julialang documentation on offset](https://docs.julialang.org/en/v1/devdocs/offset-arrays/) for more information. Here we briefly summarize some of the best practices for users and package authors. ### There is no need to support OffsetArrays for every function You don't need to support offset arrays for _internal functions_ that only consume standard 1-based arrays -- it doesn't change or improve anything. You don't need to support offset arrays for functions that _have no well-defined behavior on custom axes_. For instance, many linear algebra functions such as matrix multiplication `A * B` does not have an agreed behavior for offset arrays. In this case, it is a better practice to let users do the conversion. The helper function `Base.require_one_based_indexing` can be used to early check the axes and throw a meaningful error. If your interface functions do not intend to support offset arrays, we recommend you add this check before starting the real computation. ### use `axes` instead of `size`/`length` Many implementations assume the array axes start at 1 by writing loops such as `for i in 1:length(x)` or `for i in 1:size(x, 1)`. A better practice is to use `for i in eachindex(x)` or `for i in axes(x, 1)` -- `axes` provides more information than `size` with no performance overhead. Also, if you know what indices type you want to use, [`LinearIndices`][doc_LinearIndices] and [`CartesianIndices`][doc_CartesianIndices] allow you to loop multidimensional arrays efficiently without worrying about the axes. ### test against OffsetArrays For package authors that declare support for `AbstractArray`, we recommend having a few test cases against `OffsetArray` to ensure the function works well for arrays with custom axes. This gives you more confidence that users don't run into strange situations. For package users that want to use offset arrays, many numerical correctness issues come from the fact that `@inbounds` is used inappropriately with the 1-based indexing assumption. Thus for debug purposes, it is not a bad idea to start Julia with `--check-bounds=yes`, which turns all `@inbounds` into a no-op and uncover potential out-of-bound errors. [pkgeval-img]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/O/OffsetArrays.svg [pkgeval-url]: https://juliaci.github.io/NanosoldierReports/pkgeval_badges/report.html [action-img]: https://github.com/JuliaArrays/OffsetArrays.jl/workflows/Unit%20test/badge.svg [action-url]: https://github.com/JuliaArrays/OffsetArrays.jl/actions [codecov-img]: https://codecov.io/github/JuliaArrays/OffsetArrays.jl/coverage.svg?branch=master [codecov-url]: https://codecov.io/gh/JuliaArrays/OffsetArrays.jl [docs-stable-img]: https://img.shields.io/badge/docs-stable-blue.svg [docs-stable-url]: https://juliaarrays.github.io/OffsetArrays.jl/stable/ [docs-dev-img]: https://img.shields.io/badge/docs-dev-blue.svg [docs-dev-url]: https://juliaarrays.github.io/OffsetArrays.jl/dev/ [ver-img]: https://juliahub.com/docs/OffsetArrays/version.svg [ver-url]: https://juliahub.com/ui/Packages/OffsetArrays/UDEDl [deps-img]: https://juliahub.com/docs/OffsetArrays/deps.svg [deps-url]: https://juliahub.com/ui/Packages/OffsetArrays/UDEDl [doc_LinearIndices]: https://docs.julialang.org/en/v1/base/arrays/#Base.LinearIndices [doc_CartesianIndices]: https://docs.julialang.org/en/v1/base/arrays/#Base.IteratorsMD.CartesianIndices ================================================ FILE: benchmark/benchmarks.jl ================================================ using BenchmarkTools using OffsetArrays const dim = 1000 x = Array{Float64}(undef, 2*dim); y = OffsetArray{Float64}(undef, -dim + 1 : dim); x2d = Array{Float64}(undef, 2*dim, 2*dim); y2d = OffsetArray{Float64}(undef, -dim + 1 : dim, -dim + 1 : dim); s = OffsetVector(1:dim, 0); sur = 1:dim; sior = OffsetArrays.IdOffsetRange(parent(s)); fill1d(x) = for i in axes(x,1); x[i] = i; end fill2d(x) = for j in axes(x,2); for i in axes(x,1); x[i,j] = i + j; end; end update(x) = for i in axes(x,1); x[i] = x[i] + i; end update2d(x) = for j in axes(x,2); for i in axes(x,1); x[i,j] = x[i,j] + i + j; end; end update_eachindex(x) = for i in eachindex(x); x[i] = x[i] + i; end unsafe_fill(x) = @inbounds(for i in axes(x,1); x[i] = i; end) unsafe_fill2d(x) = @inbounds(for j in axes(x,2); for i in axes(x,1); x[i,j] = i + j; end; end) unsafe_update(x) = @inbounds(for i in axes(x,1); x[i] = x[i] + i; end) unsafe_update2d(x) = @inbounds(for j in axes(x,2); for i in axes(x,1); x[i,j] = x[i,j] + i + j; end; end) unsafe_update_eachindex(x) = @inbounds(for i in eachindex(x); x[i] = x[i] + i; end) vectorlinearindexing(a, ax) = a[ax] vectorCartesianindexing(a, ax1, ax2) = a[ax1, ax2] nestedvectorlinearindexing(a, ax1, ax2) = a[ax1[ax2]] macro showbenchmark(ex) print(ex, " : ") quote println(@benchmark esc($ex)) end end @showbenchmark fill1d(x) @showbenchmark fill1d(y) @showbenchmark fill2d(x2d) @showbenchmark fill2d(y2d) @showbenchmark update(x) @showbenchmark update(y) @showbenchmark update2d(x2d) @showbenchmark update2d(y2d) @showbenchmark update_eachindex(x) @showbenchmark update_eachindex(y) @showbenchmark unsafe_fill(x) @showbenchmark unsafe_fill(y) @showbenchmark unsafe_fill2d(x2d) @showbenchmark unsafe_fill2d(y2d) @showbenchmark unsafe_update(x) @showbenchmark unsafe_update(y) @showbenchmark unsafe_update2d(x2d) @showbenchmark unsafe_update2d(y2d) @showbenchmark unsafe_update_eachindex(x) @showbenchmark unsafe_update_eachindex(y) # Benchmarks of vector indexing using OffsetRanges as axes @showbenchmark vectorlinearindexing(x, s) @showbenchmark vectorlinearindexing(x, sur) @showbenchmark vectorlinearindexing(x, sior) @showbenchmark vectorlinearindexing(y, s) @showbenchmark vectorlinearindexing(y, sur) @showbenchmark vectorlinearindexing(y, sior) @showbenchmark vectorlinearindexing(sur, s) @showbenchmark vectorlinearindexing(sur, sur) @showbenchmark vectorlinearindexing(sur, sior) @showbenchmark vectorCartesianindexing(x2d, s, s) @showbenchmark vectorCartesianindexing(x2d, sur, sur) @showbenchmark vectorCartesianindexing(x2d, sior, sior) @showbenchmark nestedvectorlinearindexing(x, s, s) @showbenchmark nestedvectorlinearindexing(x, sur, sur) @showbenchmark nestedvectorlinearindexing(x, s, sior) @showbenchmark nestedvectorlinearindexing(x, sur, sior) @showbenchmark nestedvectorlinearindexing(x, sior, sior) @showbenchmark vectorlinearindexing(x, sior[sior]) @showbenchmark nestedvectorlinearindexing(x, sur, sior) @showbenchmark vectorlinearindexing(x, sur[sior]) @showbenchmark nestedvectorlinearindexing(x, sior, sur) @showbenchmark vectorlinearindexing(x, sior[sur]) @showbenchmark nestedvectorlinearindexing(x, sur, sur) ================================================ FILE: docs/.gitignore ================================================ build/ site/ ================================================ FILE: docs/Project.toml ================================================ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" OffsetArrays = "6fe1bfb0-de20-5000-8ca7-80f57d26f881" [compat] Documenter = "0.27" JSON = "0.21" ================================================ FILE: docs/make.jl ================================================ using Documenter, JSON using OffsetArrays DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true) makedocs( sitename = "OffsetArrays", format = Documenter.HTML(prettyurls = get(ENV, "CI", nothing) == "true"), pages = ["index.md", "internals.md", "reference.md"], modules = [OffsetArrays], doctestfilters = [r"at \./.*", r"at /home.*", r"top-level scope.*", r"\[\d*\]\s*$"], # for backtraces ) # a workdaround to github action that only push preview when PR has "push_preview" labels # issue: https://github.com/JuliaDocs/Documenter.jl/issues/1225 function should_push_preview(event_path = get(ENV, "GITHUB_EVENT_PATH", nothing)) event_path === nothing && return false event = JSON.parsefile(event_path) haskey(event, "pull_request") || return false labels = [x["name"] for x in event["pull_request"]["labels"]] return "push_preview" in labels end deploydocs( repo = "github.com:JuliaArrays/OffsetArrays.jl.git", push_preview = should_push_preview() ) ================================================ FILE: docs/src/index.md ================================================ # OffsetArrays.jl OffsetArrays provides Julia users with arrays that have arbitrary indices, similar to those found in some other programming languages like Fortran. Below is the basic usage found in the README, followed by a couple of short examples illustrating circumstances in which OffsetArrays can be useful. For a lengthier discussion, see [this blog post](https://julialang.org/blog/2017/04/offset-arrays/). ## Usage You can construct such arrays as follows: ```julia OA = OffsetArray(A, axis1, axis2, ...) ``` where you want `OA` to have axes `(axis1, axis2, ...)` and be indexed by values that fall within these axis ranges. ```@repl index using OffsetArrays A = Float64.(reshape(1:15, 3, 5)) OA = OffsetArray(A, -1:1, 0:4) # OA will have axes (-1:1, 0:4) OA = OffsetArray(A, CartesianIndex(-1, 0):CartesianIndex(1, 4)) OA[-1,0], OA[1,4] ``` You could also pass integers as offsets, where `0` means no offsets are applied: ```@repl index OA = OffsetArray(A, -2, -1) ``` When you create a new `OffsetArray` on the top of another `OffsetArray`, the offsets are accumulated: ```@repl index OOA = OffsetArray(OA, 2, 1) ``` For the special cases that you want to compensate the offset back to the ordinary 1-based array, you can use [`OffsetArrays.no_offset_view(A)`](@ref). Furthermore, you could use `Base.require_one_based_indexing` if you want to ensure the array does not have offsets. ```@repl index OffsetArrays.no_offset_view(OA) Base.require_one_based_indexing(ans) Base.require_one_based_indexing(OA) ``` [`OffsetArrays.Origin`](@ref) can be convenient if you want to directly specify the origin of the output OffsetArray, it will automatically compute the corresponding offsets. For example: ```@repl index OffsetArray(A, OffsetArrays.Origin(-1, -1)) OffsetArray(OA, OffsetArrays.Origin(-1, -1)) ``` An equivalent — but possibly more convenient — way to specify the origin of an array is ```@repl index OffsetArrays.Origin(-1, -1)(A) ``` Sometimes, it will be convenient to shift the center coordinate of the given array to `(0, 0, ...)`, `OffsetArrays.centered` is a helper for this very purpose: ```@repl index Ao = OffsetArrays.centered(A) Ao[0, 0] == 8.0 ``` and `OffsetArrays.center` tells you the center coordinate of given array: ```@repl index c = OffsetArrays.center(A) A[c...] == 8.0 ``` ## Example: Relativistic Notation Suppose we have a position vector `r = [:x, :y, :z]` which is naturally one-based, ie. `r[1] == :x`, `r[2] == :y`, `r[3] == :z` and we also want to construct a relativistic position vector which includes time as the 0th component. This can be done with OffsetArrays like ```jldoctest; setup = :(using OffsetArrays) julia> r = [:x, :y, :z]; julia> x = OffsetVector([:t, r...], 0:3) 4-element OffsetArray(::Vector{Symbol}, 0:3) with eltype Symbol with indices 0:3: :t :x :y :z julia> x[0] :t julia> x[1:3] 3-element Vector{Symbol}: :x :y :z ``` ## Example: Polynomials Suppose one wants to represent the Laurent polynomial ```math 6/x + 5 - 2*x + 3*x^2 + x^3 ``` The coefficients of this polynomial are a naturally `-1` based list, since the `n`th element of the list (counting from `-1`) `6, 5, -2, 3, 1` is the coefficient corresponding to the `n`th power of `x`. This Laurent polynomial can be evaluated at say `x = 2` as follows. ```jldoctest; setup = :(using OffsetArrays) julia> coeffs = OffsetVector([6, 5, -2, 3, 1], -1:3) 5-element OffsetArray(::Vector{Int64}, -1:3) with eltype Int64 with indices -1:3: 6 5 -2 3 1 julia> polynomial(x, coeffs) = sum(coeffs[n]*x^n for n in eachindex(coeffs)) polynomial (generic function with 1 method) julia> polynomial(2.0, coeffs) 24.0 ``` Notice our use of the `eachindex` function which does not assume that the given array starts at `1`. ================================================ FILE: docs/src/internals.md ================================================ # For developers Writing code that supports OffsetArrays is generally fairly straightforward. The majority of cases can be handled with these tips: - replace many uses of `size` with `axes` - replace `1:length(A)` with `eachindex(A)`, or if you need an integer index with `LinearIndices(A)` - replace explicit allocations like `Array{Int}(undef, size(B))` with `similar(Array{Int}, axes(B))` More information can be found in [Julia's developer documentation](https://docs.julialang.org/en/v1/devdocs/offset-arrays/). The most subtle issues tend to arise around the axes, and further detail specific to OffsetArrays.jl follows below. ## Internals How does OffsetArrays work? The fundamental principle is very simple: an `OffsetArray` is just a wrapper around a "parent" array, together with an index offset: ```jldoctest oa; setup=:(using OffsetArrays) julia> oa = OffsetArray([1 2; 3 4], 0:1, 5:6) 2×2 OffsetArray(::Matrix{Int64}, 0:1, 5:6) with eltype Int64 with indices 0:1×5:6: 1 2 3 4 julia> parent(oa) 2×2 Matrix{Int64}: 1 2 3 4 julia> oa.offsets (-1, 4) ``` So `parent(oa)` is the original array we constructed it with, and `oa.offsets` is a tuple, each entry encoding the index-shift to be applied along the corresponding axis. When you index `oa[i,j]`, it "translates" the `i,j` indexes back to the parent array's indexes and then returns the value in the parent. ## The axes of OffsetArrays The internal of offset computing is achieved by [`IdOffsetRange`](@ref OffsetArrays.IdOffsetRange) type: ```jldoctest oa julia> ax = axes(oa, 2) OffsetArrays.IdOffsetRange(values=5:6, indices=5:6) ``` This has a similar design to `Base.IdentityUnitRange` that `ax[x] == x` always holds. ```jldoctest oa julia> ax[5] 5 julia> ax[1] ERROR: BoundsError: attempt to access 2-element OffsetArrays.IdOffsetRange{Int64, Base.OneTo{Int64}} with indices 5:6 at index [1] [...] ``` This property makes sure that they tend to be their own axes: ```jldoctest oa julia> axes(ax) (OffsetArrays.IdOffsetRange(values=5:6, indices=5:6),) julia> axes(ax[ax]) (OffsetArrays.IdOffsetRange(values=5:6, indices=5:6),) ``` This example of indexing is [idempotent](https://en.wikipedia.org/wiki/Idempotence). This is a useful characteristic for ensuring the "fundamental axiom" of generalized indexing, that `a[ax][i] == a[ax[i]]`: ```jldoctest; setup=:(using OffsetArrays) julia> oa2 = OffsetArray([5, 10, 15, 20], 0:3) 4-element OffsetArray(::Vector{Int64}, 0:3) with eltype Int64 with indices 0:3: 5 10 15 20 julia> ax2 = axes(oa2, 1) OffsetArrays.IdOffsetRange(values=0:3, indices=0:3) julia> oa2[2] 15 julia> oa2[ax2][2] 15 julia> oa2[ax2[2]] 15 ``` `IdOffsetRange`s apply the offset both to the values and the indices of the range, and otherwise preserve the parent range. !!! warning There are circumstances where constructing a specific type of `IdOffsetRange` cannot be supported without changing the axes of the range (see [`OffsetArrays.IdOffsetRange`](@ref).) In the future, this package will distinguish between *construction* and *conversion*: - construction (aka, *coercion*) will always succeed, even if it has to change the axes of the result (Examples: `RangeType(rng)`, `typeof(rng1)(rng2)`) - conversion will succeed only if it can preserve both the values and the axes (Examples: `convert(RangeType, rng)`, `oftype(rng1, rng2)`) While these behave equivalently now (conversion currently performs coercion), developers are encouraged to "future-proof" their code by choosing the behavior appropriate for each usage. ## Wrapping other offset array types An `OffsetArray` may wrap any subtype of `AbstractArray`, including ones that do not use `1`-based indexing. Such arrays however need to satisfy the fundamental axiom of idempotent indexing for things to work correctly. In other words, an axis of an offset array needs to have the same values as its own axis. This property is built into `OffsetArray`s if the parent uses 1-based indexing, but it's up to the user to ensure the correctness in case a type is to be wrapped that uses offset indices. We demonstrate this through an example by creating a custom 0-based range type that we wrap in an `OffsetArray`: ```jldoctest zerobasedrange; setup=:(using OffsetArrays) julia> struct ZeroBasedRange{T,A<:AbstractRange{T}} <: AbstractRange{T} a :: A function ZeroBasedRange(a::AbstractRange{T}) where {T} @assert !Base.has_offset_axes(a) new{T, typeof(a)}(a) end end; julia> Base.parent(A::ZeroBasedRange) = A.a; julia> Base.first(A::ZeroBasedRange) = first(A.a); julia> Base.length(A::ZeroBasedRange) = length(A.a); julia> Base.last(A::ZeroBasedRange) = last(A.a); julia> Base.size(A::ZeroBasedRange) = size(A.a); julia> Base.axes(A::ZeroBasedRange) = map(x -> 0:x-1, size(A.a)); julia> Base.getindex(A::ZeroBasedRange, i::Int) = A.a[i + 1]; julia> Base.step(A::ZeroBasedRange) = step(A.a); julia> function Base.show(io::IO, A::ZeroBasedRange) show(io, A.a) print(io, " with indices $(axes(A,1))") end; ``` This definition of a `ZeroBasedRange` appears to have the correct indices, for example: ```jldoctest zerobasedrange julia> z = ZeroBasedRange(1:4) 1:4 with indices 0:3 julia> z[0] 1 julia> z[3] 4 ``` However this does not use idempotent indexing, as the axis of a `ZeroBasedRange` is not its own axis. ```jldoctest zerobasedrange julia> axes(z, 1) 0:3 julia> axes(axes(z, 1), 1) Base.OneTo(4) ``` This will lead to complications in certain functions --- for example `LinearIndices` --- that tends to implicitly assume idempotent indexing. In this case the `LinearIndices` of `z` will not match its axis. ```jldoctest zerobasedrange julia> LinearIndices(z) 4-element LinearIndices{1, Tuple{UnitRange{Int64}}}: 1 2 3 4 ``` Wrapping such a type in an `OffsetArray` might lead to unexpected bugs. ```jldoctest zerobasedrange julia> zo = OffsetArray(z, 1); julia> axes(zo, 1) OffsetArrays.IdOffsetRange(values=1:4, indices=2:5) julia> Array(zo) ERROR: BoundsError: attempt to access 4-element UnitRange{Int64} at index [5] [...] ``` The `Array` conversion errors despite `zo` having 1-based indices. The function `axes(zo, 1)` hints at the underlying problem --- the values and the indices of the axis are different. We may check that the axis of `zo` is not its own axis: ```jldoctest zerobasedrange julia> axes(zo, 1) OffsetArrays.IdOffsetRange(values=1:4, indices=2:5) julia> axes(axes(zo, 1), 1) OffsetArrays.IdOffsetRange(values=2:5, indices=2:5) ``` In this case the bug may be fixed by defining the `axes` of a `ZeroBasedRange` to be idempotent, for example using the `OffsetArrays.IdentityUnitRange` wrapper: ```jldoctest zerobasedrange julia> Base.axes(A::ZeroBasedRange) = map(x -> OffsetArrays.IdentityUnitRange(0:x-1), size(A.a)) julia> axes(zo, 1) OffsetArrays.IdOffsetRange(values=1:4, indices=1:4) ``` With this new definition, the values and indices of the axis are identical, which makes indexing idempotent. The conversion to an `Array` works as expected now: ```jldoctest zerobasedrange julia> Array(zo) 4-element Vector{Int64}: 1 2 3 4 ``` ## Caveats Because `IdOffsetRange` behaves quite differently to the normal `UnitRange` type, there are some cases that you should be aware of, especially when you are working with multi-dimensional arrays. One such cases is `getindex`: ```jldoctest getindex; setup = :(using OffsetArrays) julia> Ao = zeros(-3:3, -3:3); Ao[:] .= 1:49; julia> Ao[-3:0, :] |> axes # the first dimension does not preserve offsets (OffsetArrays.IdOffsetRange(values=1:4, indices=1:4), OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3)) julia> Ao[-3:0, -3:3] |> axes # neither dimensions preserve offsets (Base.OneTo(4), Base.OneTo(7)) julia> Ao[axes(Ao)...] |> axes # offsets are preserved (OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3), OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3)) julia> Ao[:] |> axes # This is linear indexing (Base.OneTo(49),) ``` Note that if you pass a `UnitRange`, the offsets in corresponding dimension will not be preserved. This might look weird at first, but since it follows the `a[ax][i] == a[ax[i]]` rule, it is not a bug. ```jldoctest getindex julia> I = -3:0; # UnitRange always starts at index 1 julia> Ao[I, 0][1] == Ao[I[1], 0] true julia> ax = axes(Ao, 1) # ax starts at index -3 OffsetArrays.IdOffsetRange(values=-3:3, indices=-3:3) julia> Ao[ax, 0][1] == Ao[ax[1], 0] true ``` ## Using custom axis types While a wide variety of `AbstractUnitRange`s provided by `Base` may be used as indices to construct an `OffsetArray`, at times it might be convenient to define custom types. The `OffsetArray` constructor accepts any type that may be converted to an `AbstractUnitRange`. This proceeds through a two-step process. Let's assume that the constructor called is `OffsetArray(A, indstup)`, where `indstup` is a `Tuple` of indices. 1. At the first step, the constructor calls `to_indices(A, axes(A), indstup)` to lower `indstup` to a `Tuple` of `AbstractUnitRange`s. This step converts --- among other things --- `Colon`s to axis ranges. Custom types may extend `Base.to_indices(A, axes(A), indstup)` with the desired conversion of `indstup` to `Tuple{Vararg{AbstractUnitRange{Int}}}` if this is feasible. 2. At the second step, the result obtained from the previous step treated again to convert it to a `Tuple` of `AbstractUnitRange`s to handle cases where the first step doesn't achieve this. An additional customization option may be specified at this stage: a type may be converted either to a single `AbstractUnitRange{Int}`, or to a `Tuple` of them. A type might specify which of these two behaviours is desired by extending [`OffsetArrays.AxisConversionStyle`](@ref). An example of a type that is acted upon at this stage is `CartesianIndices`, which is converted to a `Tuple` of `AbstractUnitRange`s. For example, here are a couple of custom type that facilitate zero-based indexing: ```jldoctest; setup = :(using OffsetArrays) julia> struct ZeroBasedIndexing end julia> Base.to_indices(A, inds, ::Tuple{ZeroBasedIndexing}) = map(x -> 0:length(x)-1, inds) julia> a = zeros(3, 3); julia> oa = OffsetArray(a, ZeroBasedIndexing()); julia> axes(oa) (OffsetArrays.IdOffsetRange(values=0:2, indices=0:2), OffsetArrays.IdOffsetRange(values=0:2, indices=0:2)) ``` In this example we had to define the action of `to_indices` as the type `ZeroBasedIndexing` did not have a familiar hierarchy. Things are even simpler if we subtype `AbstractUnitRange`, in which case we need to define `first` and `length` for the custom range to be able to use it as an axis: ```jldoctest; setup = :(using OffsetArrays) julia> struct ZeroTo <: AbstractUnitRange{Int} n :: Int ZeroTo(n) = new(n < 0 ? -1 : n) end julia> Base.first(::ZeroTo) = 0 julia> Base.length(r::ZeroTo) = r.n + 1 julia> oa = OffsetArray(zeros(2,2), ZeroTo(1), ZeroTo(1)); julia> axes(oa) (OffsetArrays.IdOffsetRange(values=0:1, indices=0:1), OffsetArrays.IdOffsetRange(values=0:1, indices=0:1)) ``` Note that zero-based indexing may also be achieved using the pre-defined type [`OffsetArrays.Origin`](@ref). ================================================ FILE: docs/src/reference.md ================================================ # Reference ```@docs OffsetArray OffsetVector OffsetMatrix OffsetArrays.Origin OffsetArrays.IdOffsetRange OffsetArrays.no_offset_view OffsetArrays.AxisConversionStyle OffsetArrays.center OffsetArrays.centered ``` ================================================ FILE: ext/OffsetArraysAdaptExt.jl ================================================ module OffsetArraysAdaptExt using OffsetArrays, Adapt ## # Adapt allows for automatic conversion of CPU OffsetArrays to GPU OffsetArrays ## import Adapt Adapt.adapt_structure(to, O::OffsetArray) = OffsetArrays.parent_call(x -> Adapt.adapt(to, x), O) @static if isdefined(Adapt, :parent_type) # To support Adapt 3.0 which doesn't have parent_type defined Adapt.parent_type(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA Adapt.unwrap_type(W::Type{<:OffsetArray}) = unwrap_type(parent_type(W)) end end ================================================ FILE: src/OffsetArrays.jl ================================================ module OffsetArrays using Base: tail, @propagate_inbounds @static if !isdefined(Base, :IdentityUnitRange) const IdentityUnitRange = Base.Slice else using Base: IdentityUnitRange end export OffsetArray, OffsetMatrix, OffsetVector const IIUR = IdentityUnitRange{<:AbstractUnitRange{<:Integer}} include("axes.jl") include("utils.jl") include("origin.jl") # Technically we know the length of CartesianIndices but we need to convert it first, so here we # don't put it in OffsetAxisKnownLength. const OffsetAxisKnownLength = Union{Integer, AbstractUnitRange} const OffsetAxis = Union{OffsetAxisKnownLength, Colon} const ArrayInitializer = Union{UndefInitializer, Missing, Nothing} ## OffsetArray """ OffsetArray(A, indices...) Return an `AbstractArray` that shares element type and size with the first argument but uses the supplied `indices` to infer its axes. If all the indices are `AbstractUnitRange`s then these are directly used as the axis span along each dimension. Refer to the examples below for other permissible types. Alternatively it's possible to specify the coordinates of one corner of the array and have the axes be computed automatically from the size of `A`. This constructor makes it convenient to shift to an arbitrary starting index along each axis, for example to a zero-based indexing scheme followed by arrays in languages such as C and Python. See [`Origin`](@ref) and the examples below for this usage. # Example: offsets There are two types of `indices`: integers and ranges-like types. Integers are recognized as offsets, where `0` means no offsets are applied: ```jldoctest; setup=:(using OffsetArrays) julia> A = OffsetArray(reshape(1:6, 2, 3), -1, -2) 2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1: 1 3 5 2 4 6 julia> A[0, 1] 5 ``` Examples of range-like types are: `UnitRange` (e.g, `-1:2`), `CartesianIndices`, and `Colon()` (or concisely `:`). A `UnitRange` specifies the axis span along one particular dimension, `CartesianIndices` specify the axis spans along multiple dimensions, and a `Colon` is a placeholder that specifies that the `OffsetArray` shares its axis with its parent along that dimension. ```jldoctest; setup=:(using OffsetArrays) julia> OffsetArray(reshape(1:6, 2, 3), 0:1, -1:1) 2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1: 1 3 5 2 4 6 julia> OffsetArray(reshape(1:6, 2, 3), :, -1:1) # : as a placeholder to indicate that no offset is to be applied to the first dimension 2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 1:2, -1:1) with eltype $Int with indices 1:2×-1:1: 1 3 5 2 4 6 ``` Use `CartesianIndices` to specify the coordinates of two diagonally opposite corners: ```jldoctest; setup=:(using OffsetArrays) julia> OffsetArray(reshape(1:6, 2, 3), CartesianIndex(0, -1):CartesianIndex(1, 1)) 2×3 OffsetArray(reshape(::UnitRange{$Int}, 2, 3), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1: 1 3 5 2 4 6 ``` Integers and range-like types may not be combined in the same call: ```julia julia> OffsetArray(reshape(1:6, 2, 3), 0, -1:1) ERROR: [...] ``` # Example: origin [`OffsetArrays.Origin`](@ref) may be used to specify the origin of the OffsetArray. The term origin here refers to the corner with the lowest values of coordinates, such as the left edge for an `AbstractVector`, the bottom left corner for an `AbstractMatrix` and so on. The coordinates of the origin sets the starting index of the array along each dimension. ```jldoctest; setup=:(using OffsetArrays) julia> a = [1 2; 3 4]; julia> OffsetArray(a, OffsetArrays.Origin(0, 1)) 2×2 OffsetArray(::$(Array{Int,2}), 0:1, 1:2) with eltype $Int with indices 0:1×1:2: 1 2 3 4 julia> OffsetArray(a, OffsetArrays.Origin(0)) # set the origin to zero along each dimension 2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1: 1 2 3 4 ``` """ struct OffsetArray{T,N,AA<:AbstractArray{T,N}} <: AbstractArray{T,N} parent::AA offsets::NTuple{N,Int} @inline function OffsetArray{T, N, AA}(parent::AA, offsets::NTuple{N, Int}; checkoverflow = true) where {T, N, AA<:AbstractArray{T,N}} # allocation of `map` on tuple is optimized away checkoverflow && map(overflow_check, axes(parent), offsets) new{T, N, AA}(parent, offsets) end end """ OffsetVector(v, index) Type alias and convenience constructor for one-dimensional [`OffsetArray`](@ref)s. """ const OffsetVector{T,AA<:AbstractVector{T}} = OffsetArray{T,1,AA} """ OffsetMatrix(A, index1, index2) Type alias and convenience constructor for two-dimensional [`OffsetArray`](@ref)s. """ const OffsetMatrix{T,AA<:AbstractMatrix{T}} = OffsetArray{T,2,AA} # checks if the offset may be added to the range without overflowing function overflow_check(r::AbstractUnitRange, offset::Integer) Base.hastypemax(eltype(r)) || return nothing # This gives some performance boost https://github.com/JuliaLang/julia/issues/33273 throw_upper_overflow_error(val) = throw(OverflowError("offset should be <= $(typemax(Int) - val) corresponding to the axis $r, received an offset $offset")) throw_lower_overflow_error(val) = throw(OverflowError("offset should be >= $(typemin(Int) - val) corresponding to the axis $r, received an offset $offset")) # With ranges in the picture, first(r) might not necessarily be < last(r) # we therefore use the min and max of first(r) and last(r) to check for overflow firstlast_min, firstlast_max = minmax(first(r), last(r)) if offset > 0 && firstlast_max > typemax(Int) - offset throw_upper_overflow_error(firstlast_max) elseif offset < 0 && firstlast_min < typemin(Int) - offset throw_lower_overflow_error(firstlast_min) end return nothing end # Tuples of integers are treated as offsets # Empty Tuples are handled here @inline function OffsetArray(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) _checkindices(A, offsets, "offsets") OffsetArray{eltype(A), ndims(A), typeof(A)}(A, offsets; kw...) end # These methods are necessary to disallow incompatible dimensions for # the OffsetVector and the OffsetMatrix constructors for (FT, ND) in ((:OffsetVector, :1), (:OffsetMatrix, :2)) @eval @inline function $FT(A::AbstractArray{<:Any,$ND}, offsets::Tuple{Vararg{Integer}}; kw...) _checkindices(A, offsets, "offsets") OffsetArray{eltype(A), $ND, typeof(A)}(A, offsets; kw...) end FTstr = string(FT) @eval @inline function $FT(A::AbstractArray, offsets::Tuple{Vararg{Integer}}; kw...) throw(ArgumentError($FTstr*" requires a "*string($ND)*"D array")) end end ## OffsetArray constructors for FT in (:OffsetArray, :OffsetVector, :OffsetMatrix) # Nested OffsetArrays may strip off the wrapper and collate the offsets # empty tuples are handled here @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Vararg{Int}}; checkoverflow = true) _checkindices(A, offsets, "offsets") # ensure that the offsets may be added together without an overflow checkoverflow && map(overflow_check, axes(A), offsets) I = map(+, _offsets(A, parent(A)), offsets) $FT(parent(A), I, checkoverflow = false) end @eval @inline function $FT(A::OffsetArray, offsets::Tuple{Integer,Vararg{Integer}}; kw...) $FT(A, map(Int, offsets); kw...) end # In general, indices get converted to AbstractUnitRanges. # CartesianIndices{N} get converted to N ranges @eval @inline function $FT(A::AbstractArray, inds::Tuple{Any,Vararg{Any}}; kw...) $FT(A, _toAbstractUnitRanges(to_indices(A, axes(A), inds)); kw...) end # convert ranges to offsets @eval @inline function $FT(A::AbstractArray, inds::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) _checkindices(A, inds, "indices") # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) lA = size(A) lI = map(length, inds) lA == lI || throw_dimerr(lA, lI) $FT(A, map(_offset, axes(A), inds); kw...) end @eval @inline $FT(A::AbstractArray, inds...; kw...) = $FT(A, inds; kw...) @eval @inline $FT(A::AbstractArray; checkoverflow = false) = $FT(A, ntuple(zero, Val(ndims(A))), checkoverflow = checkoverflow) @eval @inline $FT(A::AbstractArray, origin::Origin; checkoverflow = true) = $FT(A, origin.index .- first.(axes(A)); checkoverflow = checkoverflow) end (o::Origin)(A::AbstractArray) = OffsetArray(no_offset_view(A), o) Origin(A::AbstractArray) = Origin(first.(axes(A))) # conversion-related methods @inline OffsetArray{T}(M::AbstractArray, I...; kw...) where {T} = OffsetArray{T,ndims(M)}(M, I...; kw...) @inline function OffsetArray{T,N}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N} M2 = _of_eltype(T, M) OffsetArray{T,N}(M2, I...; kw...) end @inline OffsetArray{T,N}(M::OffsetArray{T,N}, I...; kw...) where {T,N} = OffsetArray(M, I...; kw...) @inline OffsetArray{T,N}(M::AbstractArray{T,N}, I...; kw...) where {T,N} = OffsetArray{T,N,typeof(M)}(M, I...; kw...) @inline OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I...; kw...) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, I; kw...) @inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::NTuple{N,Int}; checkoverflow = true) where {T,N,A<:AbstractArray{T,N}} checkoverflow && map(overflow_check, axes(M), I) Mv = no_offset_view(M) MvA = convert(A, Mv)::A Iof = map(+, _offsets(M), I) OffsetArray{T,N,A}(MvA, Iof, checkoverflow = false) end @inline function OffsetArray{T, N, AA}(parent::AbstractArray{<:Any,N}, offsets::NTuple{N, Integer}; kw...) where {T, N, AA<:AbstractArray{T,N}} OffsetArray{T, N, AA}(parent, map(Int, offsets)::NTuple{N,Int}; kw...) end @inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple{AbstractUnitRange,Vararg{AbstractUnitRange}}; kw...) where {T,N,A<:AbstractArray{T,N}} _checkindices(M, I, "indices") # Performance gain by wrapping the error in a function: see https://github.com/JuliaLang/julia/issues/37558 throw_dimerr(lA, lI) = throw(DimensionMismatch("supplied axes do not agree with the size of the array (got size $lA for the array and $lI for the indices")) lM = size(M) lI = map(length, I) lM == lI || throw_dimerr(lM, lI) OffsetArray{T,N,A}(M, map(_offset, axes(M), I); kw...) end @inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}, I::Tuple; kw...) where {T,N,A<:AbstractArray{T,N}} OffsetArray{T,N,A}(M, _toAbstractUnitRanges(to_indices(M, axes(M), I)); kw...) end @inline function OffsetArray{T,N,A}(M::AbstractArray{<:Any,N}; kw...) where {T,N,A<:AbstractArray{T,N}} Mv = no_offset_view(M) MvA = convert(A, Mv)::A OffsetArray{T,N,A}(MvA, _offsets(M); kw...) end @inline OffsetArray{T,N,A}(M::A; checkoverflow = false) where {T,N,A<:AbstractArray{T,N}} = OffsetArray{T,N,A}(M, ntuple(zero, Val(N)); checkoverflow = checkoverflow) Base.convert(::Type{T}, M::AbstractArray) where {T<:OffsetArray} = M isa T ? M : T(M) @inline AbstractArray{T,N}(M::OffsetArray{S,N}) where {T,S,N} = OffsetArray{T}(M) # array initialization @inline function OffsetArray{T,N}(init::ArrayInitializer, inds::Tuple{Vararg{OffsetAxisKnownLength}}; kw...) where {T,N} _checkindices(N, inds, "indices") AA = Array{T,N}(init, map(_indexlength, inds)) OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...) end @inline function OffsetArray{T, N}(init::ArrayInitializer, inds::Tuple; kw...) where {T, N} OffsetArray{T, N}(init, _toAbstractUnitRanges(inds); kw...) end @inline OffsetArray{T,N}(init::ArrayInitializer, inds...; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) @inline OffsetArray{T}(init::ArrayInitializer, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} = OffsetArray{T,N}(init, inds; kw...) @inline function OffsetArray{T}(init::ArrayInitializer, inds::Tuple; kw...) where {T} OffsetArray{T}(init, _toAbstractUnitRanges(inds); kw...) end @inline OffsetArray{T}(init::ArrayInitializer, inds...; kw...) where {T} = OffsetArray{T}(init, inds; kw...) Base.IndexStyle(::Type{OA}) where {OA<:OffsetArray} = IndexStyle(parenttype(OA)) parenttype(::Type{OffsetArray{T,N,AA}}) where {T,N,AA} = AA parenttype(A::OffsetArray) = parenttype(typeof(A)) Base.parent(A::OffsetArray) = A.parent # TODO: Ideally we would delegate to the parent's broadcasting implementation, but that # is currently broken in sufficiently many implementation, namely RecursiveArrayTools, DistributedArrays # and StaticArrays, that it will take concentrated effort to get this working across the ecosystem. # The goal would be to have `OffsetArray(CuArray) .+ 1 == OffsetArray{CuArray}`. # Base.Broadcast.BroadcastStyle(::Type{<:OffsetArray{<:Any, <:Any, AA}}) where AA = Base.Broadcast.BroadcastStyle(AA) @inline Base.size(A::OffsetArray) = size(parent(A)) # specializing length isn't necessary, as length(A) = prod(size(A)), # but specializing length enables constant-propagation for statically sized arrays # see https://github.com/JuliaArrays/OffsetArrays.jl/pull/304 @inline Base.length(A::OffsetArray) = length(parent(A)) @inline Base.axes(A::OffsetArray) = map(IdOffsetRange, axes(parent(A)), A.offsets) @inline Base.axes(A::OffsetArray, d) = d <= ndims(A) ? IdOffsetRange(axes(parent(A), d), A.offsets[d]) : IdOffsetRange(axes(parent(A), d)) @inline Base.axes1(A::OffsetArray{T,0}) where {T} = IdOffsetRange(axes(parent(A), 1)) # we only need to specialize this one # Issue 128 # See https://github.com/JuliaLang/julia/issues/37274 for the issue reported in Base # The fix https://github.com/JuliaLang/julia/pull/39404 should be available on v1.6 # The following method is added on older Julia versions to ensure correct behavior for OffsetVectors if VERSION < v"1.6" @inline function Base.compute_linindex(A::OffsetVector, I::NTuple{N,Any}) where N IP = Base.fill_to_length(axes(A), Base.OneTo(1), Val(N)) Base.compute_linindex(first(LinearIndices(A)), 1, IP, I) end end # Utils to translate a function to the parent while preserving offsets unwrap(x) = x, identity unwrap(x::OffsetArray) = parent(x), data -> OffsetArray(data, x.offsets, checkoverflow = false) function parent_call(f, x) parent, wrap_offset = unwrap(x) wrap_offset(f(parent)) end Base.similar(A::OffsetArray, ::Type{T}, dims::Dims) where T = similar(parent(A), T, dims) function Base.similar(A::AbstractArray, ::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where T # strip IdOffsetRanges to extract the parent range and use it to generate the array new_shape = map(_strip_IdOffsetRange, shape) # route through _similar_axes_or_length to avoid a stack overflow if map(_strip_IdOffsetRange, shape) === shape # This tries to use new_shape directly in similar if similar(A, T, ::typeof(new_shape)) is defined # If this fails, it calls similar(A, T, map(_indexlength, new_shape)) to use the size along each axis # to generate the new array P = _similar_axes_or_length(A, T, new_shape, shape) return OffsetArray(P, map(_offset, axes(P), shape)) end Base.similar(::Type{A}, sz::Tuple{Vararg{Int}}) where {A<:OffsetArray} = similar(Array{eltype(A)}, sz) function Base.similar(::Type{T}, shape::Tuple{OffsetAxisKnownLength,Vararg{OffsetAxisKnownLength}}) where {T<:AbstractArray} new_shape = map(_strip_IdOffsetRange, shape) P = _similar_axes_or_length(T, new_shape, shape) OffsetArray(P, map(_offset, axes(P), shape)) end # Try to use the axes to generate the parent array type # This is useful if the axes have special meanings, such as with static arrays # This method is hit if at least one axis provided to similar(A, T, axes) is an IdOffsetRange # For example this is hit when similar(A::OffsetArray) is called, # which expands to similar(A, eltype(A), axes(A)) _similar_axes_or_length(A, T, ax, ::Any) = similar(A, T, ax) _similar_axes_or_length(AT, ax, ::Any) = similar(AT, ax) # Handle the general case by resorting to lengths along each axis # This is hit if none of the axes provided to similar(A, T, axes) are IdOffsetRanges, # and if similar(A, T, axes::AX) is not defined for the type AX. # In this case the best that we can do is to create a mutable array of the correct size _similar_axes_or_length(A, T, ax::I, ::I) where {I} = similar(A, T, map(_indexlength, ax)) _similar_axes_or_length(AT, ax::I, ::I) where {I} = similar(AT, map(_indexlength, ax)) # reshape accepts a single colon # this method is limited to AbstractUnitRange{<:Integer} to avoid method overwritten errors if Base defines the same, # see https://github.com/JuliaLang/julia/pull/56850 Base.reshape(A::AbstractArray, inds::Union{Integer, Colon, AbstractUnitRange{<:Integer}}...) = reshape(A, inds) function Base.reshape(A::AbstractArray, inds::Tuple{Vararg{OffsetAxis}}) AR = reshape(no_offset_view(A), map(_indexlength, inds)) O = OffsetArray(AR, map(_offset, axes(AR), inds)) return _popreshape(O, axes(AR), _filterreshapeinds(inds)) end # Reshaping OffsetArrays can "pop" the original OffsetArray wrapper and return # an OffsetArray(reshape(...)) instead of an OffsetArray(reshape(OffsetArray(...))) # Short-circuit for AbstractVectors if the axes are compatible to get around the Base restriction # to 1-based vectors function _reshape(A::AbstractVector, inds::Tuple{OffsetAxis}) @noinline throw_dimerr(ind::Integer) = throw( DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with length $ind")) @noinline throw_dimerr(ind) = throw( DimensionMismatch("parent has $(size(A,1)) elements, which is incompatible with indices $ind")) _checksize(first(inds), size(A,1)) || throw_dimerr(first(inds)) A end _reshape(A, inds) = _reshape2(A, inds) _reshape2(A, inds) = reshape(A, inds) # avoid a stackoverflow by relegating to the parent if no_offset_view returns an offsetarray _reshape2(A::OffsetArray, inds) = reshape(parent(A), inds) _reshape_nov(A, inds) = _reshape(no_offset_view(A), inds) # And for non-offset axes, we can just return a reshape of the parent directly Base.reshape(A::OffsetArray, inds::Tuple{Integer,Vararg{Integer}}) = _reshape_nov(A, inds) Base.reshape(A::OffsetArray, inds::Dims) = _reshape_nov(A, inds) if VERSION < v"1.10.7" # the specialized reshape(parent::AbstractVector, ::Tuple{Colon}) is available in Base at least on this version Base.reshape(A::OffsetVector, ::Tuple{Colon}) = A Base.reshape(A::OffsetArray, inds::Tuple{Vararg{Union{Int,Colon}}}) = _reshape_nov(A, inds) end # permutedims in Base does not preserve axes, and can not be fixed in a non-breaking way # This is a stopgap solution Base.permutedims(v::OffsetVector) = reshape(v, (1, axes(v, 1))) if VERSION < v"1.12.0-DEV.343" # available in Base beyond this version Base.fill(v, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(similar(Array{typeof(v)}, inds), v) Base.zeros(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} = fill!(similar(Array{T}, inds), zero(T)) Base.ones(::Type{T}, inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {T, N} = fill!(similar(Array{T}, inds), one(T)) Base.trues(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(similar(BitArray, inds), true) Base.falses(inds::NTuple{N, Union{Integer, AbstractUnitRange}}) where {N} = fill!(similar(BitArray, inds), false) end Base.zero(A::OffsetArray) = parent_call(zero, A) Base.fill!(A::OffsetArray, x) = parent_call(Ap -> fill!(Ap, x), A) ## Indexing # Note this gets the index of the parent *array*, not the index of the parent *range* # Here's how one can think about this: # Δi = i - first(r) # i′ = first(r.parent) + Δi # and one obtains the result below. parentindex(r::IdOffsetRange, i) = i - r.offset @propagate_inbounds Base.getindex(A::OffsetArray{<:Any,0}) = A.parent[] @inline function Base.getindex(A::OffsetArray{<:Any,N}, I::Vararg{Int,N}) where N @boundscheck checkbounds(A, I...) J = map(parentindex, axes(A), I) @inbounds parent(A)[J...] end @propagate_inbounds Base.getindex(A::OffsetArray{<:Any,N}, c::Vararg{Colon,N}) where N = parent_call(x -> getindex(x, c...), A) # With one Colon we use linear indexing. # In this case we may forward the index to the parent, as the information about the axes is lost # The exception to this is with OffsetVectors where the axis information is preserved, # but that case is handled by getindex(::OffsetArray{<:Any,N}, ::Vararg{Colon,N}) @propagate_inbounds Base.getindex(A::OffsetArray, c::Colon) = A.parent[:] @inline function Base.getindex(A::OffsetVector, i::Int) @boundscheck checkbounds(A, i) @inbounds parent(A)[parentindex(Base.axes1(A), i)] end @propagate_inbounds Base.getindex(A::OffsetArray, i::Int) = parent(A)[i] @inline function Base.setindex!(A::OffsetArray{T,N}, val, I::Vararg{Int,N}) where {T,N} @boundscheck checkbounds(A, I...) J = map(parentindex, axes(A), I) @inbounds parent(A)[J...] = val A end @inline function Base.setindex!(A::OffsetVector, val, i::Int) @boundscheck checkbounds(A, i) @inbounds parent(A)[parentindex(Base.axes1(A), i)] = val A end @propagate_inbounds function Base.setindex!(A::OffsetArray, val, i::Int) parent(A)[i] = val A end @inline Base.iterate(a::OffsetArray, i...) = iterate(parent(a), i...) Base.in(x, A::OffsetArray) = in(x, parent(A)) Base.copy(A::OffsetArray) = parent_call(copy, A) Base.strides(A::OffsetArray) = strides(parent(A)) Base.elsize(::Type{OffsetArray{T,N,A}}) where {T,N,A} = Base.elsize(A) Base.cconvert(P::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.cconvert(P, parent(A)) if VERSION < v"1.11-" @inline Base.unsafe_convert(::Type{Ptr{T}}, A::OffsetArray{T}) where {T} = Base.unsafe_convert(Ptr{T}, parent(A)) end # For fast broadcasting: ref https://discourse.julialang.org/t/why-is-there-a-performance-hit-on-broadcasting-with-offsetarrays/32194 Base.dataids(A::OffsetArray) = Base.dataids(parent(A)) Broadcast.broadcast_unalias(dest::OffsetArray, src::OffsetArray) = parent(dest) === parent(src) ? src : Broadcast.unalias(dest, src) ### Special handling for AbstractRange const OffsetRange{T} = OffsetVector{T,<:AbstractRange{T}} const OffsetUnitRange{T} = OffsetVector{T,<:AbstractUnitRange{T}} Base.step(a::OffsetRange) = step(parent(a)) Base.checkindex(::Type{Bool}, inds::AbstractUnitRange, or::OffsetRange) = Base.checkindex(Bool, inds, parent(or)) # Certain special methods for linear indexing with integer ranges (or OffsetRanges) # These may bypass the default getindex(A, I...) pathway if the parent types permit this # For example AbstractUnitRanges and Arrays have special linear indexing behavior defined # If both the arguments are offset, we may unwrap the indices to call (::OffsetArray)[::AbstractRange{Int}] @propagate_inbounds function Base.getindex(A::OffsetArray, r::OffsetRange{Int}) _indexedby(A[parent(r)], axes(r)) end # If the indices are offset, we may unwrap them and pass the parent to getindex @propagate_inbounds function Base.getindex(A::AbstractRange, r::OffsetRange{Int}) _indexedby(A[parent(r)], axes(r)) end # An OffsetUnitRange might use the rapid getindex(::Array, ::AbstractUnitRange{Int}) for contiguous indexing @propagate_inbounds function Base.getindex(A::Array, r::OffsetUnitRange{Int}) B = A[_contiguousindexingtype(parent(r))] OffsetArray(B, axes(r), checkoverflow = false) end # avoid hitting the slow method getindex(::Array, ::AbstractRange{Int}) # instead use the faster getindex(::Array, ::UnitRange{Int}) if VERSION <= v"1.7.0-DEV.1039" @propagate_inbounds function Base.getindex(A::Array, r::Union{IdOffsetRange, IIUR}) B = A[_contiguousindexingtype(r)] _indexedby(B, axes(r)) end end # Linear Indexing of OffsetArrays with AbstractUnitRanges may use the faster contiguous indexing methods @inline function Base.getindex(A::OffsetArray, r::AbstractUnitRange{Int}) @boundscheck checkbounds(A, r) # nD OffsetArrays do not have their linear indices shifted, so we may forward the indices provided to the parent @inbounds B = parent(A)[_contiguousindexingtype(r)] _indexedby(B, axes(r)) end @inline function Base.getindex(A::OffsetVector, r::AbstractUnitRange{Int}) @boundscheck checkbounds(A, r) # OffsetVectors may have their linear indices shifted, so we subtract the offset from the indices provided @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] _indexedby(B, axes(r)) end # This method added mainly to index an OffsetRange with another range @inline function Base.getindex(A::OffsetVector, r::AbstractRange{Int}) @boundscheck checkbounds(A, r) @inbounds B = parent(A)[_subtractoffset(r, A.offsets[1])] _indexedby(B, axes(r)) end # In general we would pass through getindex(A, I...) which calls to_indices(A, I) and finally to_index(I) # An OffsetUnitRange{Int} has an equivalent IdOffsetRange with the same values and axes, # something similar also holds for OffsetUnitRange{BigInt} # We may replace the former with the latter in an indexing operation to obtain a performance boost @inline function Base.to_index(r::OffsetUnitRange{<:Union{Int,BigInt}}) of = first(axes(r,1)) - 1 IdOffsetRange(_subtractoffset(parent(r), of), of) end @inline function _boundscheck_index_retaining_axes(r, s) @boundscheck checkbounds(r, s) @inbounds pr = r[UnitRange(s)] _indexedby(pr, axes(s)) end @inline _boundscheck_return(r, s) = (@boundscheck checkbounds(r, s); s) for OR in [:IIUR, :IdOffsetRange] for R in [:StepRange, :StepRangeLen, :LinRange, :UnitRange] @eval @inline Base.getindex(r::$R, s::$OR) = _boundscheck_index_retaining_axes(r, s) end # this method is needed for ambiguity resolution @eval @inline function Base.getindex(r::StepRangeLen{T,<:Base.TwicePrecision,<:Base.TwicePrecision}, s::$OR) where T _boundscheck_index_retaining_axes(r, s) end end Base.getindex(r::Base.OneTo, s::IdOffsetRange) = _boundscheck_index_retaining_axes(r, s) if VERSION < v"1.7.0-beta2" Base.getindex(r::Base.OneTo, s::IIUR) = _boundscheck_index_retaining_axes(r, s) end # These methods are added to avoid ambiguities with Base. # The ones involving Base types should be ported to Base and version-limited here @inline Base.getindex(r::IdentityUnitRange, s::IIUR) = _boundscheck_return(r, s) @inline Base.getindex(r::IdentityUnitRange, s::IdOffsetRange) = _boundscheck_return(r, s) if IdentityUnitRange !== Base.Slice @inline Base.getindex(r::Base.Slice, s::IIUR) = _boundscheck_return(r, s) @inline Base.getindex(r::Base.Slice, s::IdOffsetRange) = _boundscheck_return(r, s) end # eltype conversion # This may use specialized map methods for the parent Base.map(::Type{T}, O::OffsetArray) where {T} = parent_call(x -> map(T, x), O) Base.map(::Type{T}, r::IdOffsetRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) if eltype(IIUR) === Int # This is type-piracy, but there is no way to convert an IdentityUnitRange to a non-Int type in Base Base.map(::Type{T}, r::IdentityUnitRange) where {T<:Real} = _indexedby(map(T, UnitRange(r)), axes(r)) end if VERSION < v"1.7.2" # mapreduce is faster with an IdOffsetRange than with an OffsetUnitRange on Julia 1.6 # We therefore convert OffsetUnitRanges to IdOffsetRanges with the same values and axes function Base.mapreduce(f, op, A1::OffsetUnitRange{<:Integer}, As::OffsetUnitRange{<:Integer}...; kw...) As = (A1, As...) ofs = map(A -> first(axes(A,1)) - 1, As) AIds = map((A, of) -> IdOffsetRange(_subtractoffset(parent(A), of), of), As, ofs) mapreduce(f, op, AIds...; kw...) end end # Optimize certain reductions that treat an OffsetVector as a list for f in [:minimum, :maximum, :extrema, :sum] @eval Base.$f(r::OffsetRange) = $f(parent(r)) end function Base.show(io::IO, r::OffsetRange) show(io, r.parent) print(io, " with indices ", UnitRange(axes(r, 1))) end Base.show(io::IO, ::MIME"text/plain", r::OffsetRange) = show(io, r) ### Some mutating functions defined only for OffsetVector ### Base.resize!(A::OffsetVector, nl::Integer) = (resize!(A.parent, nl); A) Base.push!(A::OffsetVector, x...) = (push!(A.parent, x...); A) Base.pop!(A::OffsetVector) = pop!(A.parent) Base.append!(A::OffsetVector, items) = (append!(A.parent, items); A) Base.empty!(A::OffsetVector) = (empty!(A.parent); A) # These functions keep the summary compact const OffsetIndices = Union{IdOffsetRange, IdentityUnitRange{<:IdOffsetRange}} function Base.inds2string(inds::Tuple{OffsetIndices, Vararg{OffsetIndices}}) Base.inds2string(map(UnitRange, inds)) end Base.showindices(io::IO, ind1::IdOffsetRange, inds::IdOffsetRange...) = Base.showindices(io, map(UnitRange, (ind1, inds...))...) function Base.showarg(io::IO, @nospecialize(a::OffsetArray), toplevel) print(io, "OffsetArray(") Base.showarg(io, parent(a), false) Base.showindices(io, axes(a)...) print(io, ')') if toplevel print(io, " with eltype ", eltype(a)) end end function Base.replace_in_print_matrix(A::OffsetArray{<:Any,2}, i::Integer, j::Integer, s::AbstractString) J = map(parentindex, axes(A), (i,j)) Base.replace_in_print_matrix(parent(A), J..., s) end function Base.replace_in_print_matrix(A::OffsetArray{<:Any,1}, i::Integer, j::Integer, s::AbstractString) ip = parentindex(axes(A,1), i) Base.replace_in_print_matrix(parent(A), ip, j, s) end # Actual unsafe_wrap implementation @inline function _unsafe_wrap(pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; own = false, kw...) where {T,N} _checkindices(N, inds, "indices") AA = Base.unsafe_wrap(Array, pointer, map(_indexlength, inds); own=own) OffsetArray{T, N, typeof(AA)}(AA, map(_indexoffset, inds); kw...) end const OffsetArrayUnion{T,N} = Union{Type{OffsetArray}, Type{OffsetArray{T}}, Type{OffsetArray{T,N}}, Type{OffsetArray{T1, N} where T1}} where {T,N} """ Base.unsafe_wrap(OffsetArray, pointer::Ptr{T}, inds...; own=false, kw...) Base.unsafe_wrap(OffsetArray, pointer::Ptr{T}, inds::Tuple; own=false, kw...) Construct an `OffsetArray` around a pointer with axes defined by `inds` which may be Integers or AbstractUnitRanges. If the keyword `own` is true, then Julia will free the pointer when the array is garbage collected. Other keywords are forwarded to the `OffsetArray` constructor. """ @inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, OffsetAxisKnownLength}; kw...) where {T,N} _unsafe_wrap(pointer, inds; kw...) end # Avoid ambiguity @inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::NTuple{N, <:Integer}; kw...) where {T,N} _unsafe_wrap(pointer, inds; kw...) end @inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{OffsetAxisKnownLength,N}; kw...) where {T,N} _unsafe_wrap(pointer, inds; kw...) end # Avoid ambiguity @inline function Base.unsafe_wrap(::OffsetArrayUnion{T,N}, pointer::Ptr{T}, inds::Vararg{Integer,N}; kw...) where {T,N} _unsafe_wrap(pointer, inds; kw...) end """ no_offset_view(A) Return an `AbstractArray` that shares structure and underlying data with the argument, but uses 1-based indexing. May just return the argument when applicable. Not exported. The default implementation uses `OffsetArrays`, but other types should use something more specific to remove a level of indirection when applicable. ```jldoctest; setup=:(using OffsetArrays) julia> A = [1 3 5; 2 4 6]; julia> O = OffsetArray(A, 0:1, -1:1) 2×3 OffsetArray(::$(Matrix{Int}), 0:1, -1:1) with eltype $Int with indices 0:1×-1:1: 1 3 5 2 4 6 julia> OffsetArrays.no_offset_view(O)[1,1] = -9 -9 julia> A 2×3 $(Matrix{Int}): -9 3 5 2 4 6 ``` """ no_offset_view(A::OffsetArray) = no_offset_view(parent(A)) if isdefined(Base, :IdentityUnitRange) # valid only if Slice is distinguished from IdentityUnitRange _onebasedslice(S::Base.Slice) = Base.Slice(Base.OneTo(length(S))) _onebasedslice(S::Base.Slice{<:Base.OneTo}) = S _onebasedslice(S) = S _isoffsetslice(::Any) = false _isoffsetslice(::Base.Slice) = true _isoffsetslice(::Base.Slice{<:Base.OneTo}) = false function no_offset_view(S::SubArray) #= If a view contains an offset Slice axis, i.e. it is a view of an offset array along the offset axis, we shift the axis to a 1-based one. E.g. Slice(2:3) -> Slice(Base.OneTo(2)) We transform the `parent` as well as the `parentindices`, so that the view still points to the same elements, even though the indices have changed. This way, we retain the axis of the view as a `Slice` =# P = parent(S) pinds = parentindices(S) #= Check if all the axes are `Slice`s and the parent has `OneTo` axes, in which case we may unwrap the `OffsetArray` and forward the view to the parent. =# may_pop_parent = all(_isoffsetslice, pinds) && P isa OffsetArray && all(x -> x isa Base.OneTo, axes(parent(P))) if may_pop_parent return no_offset_view(P) end #= we convert offset `Slice`s to 1-based ones using `_onebasedslice`. The next call, `no_offset_view`, is a no-op on a `Slice{<:OneTo}`, while it converts the offset axes to 1-based ones. Eventually, we end up with a `Tuple` comprising `Slice{<:OneTo}`s and other 1-based axes. The difference between `_onebasedslice` and `no_offset_view` is that the latter does not change the value of the range, while the former does. =# newviewinds = map(no_offset_view ∘ _onebasedslice, pinds) needs_shifting = any(_isoffsetslice, pinds) P_maybeshiftedinds = if needs_shifting t = Origin(parent(S)).index neworigin = ntuple(i -> _isoffsetslice(pinds[i]) ? 1 : t[i], length(t)) Origin(neworigin)(P) else P end view(P_maybeshiftedinds, newviewinds...) end end no_offset_view(a::Array) = a no_offset_view(i::Number) = i no_offset_view(A::AbstractArray) = _no_offset_view(axes(A), A) _no_offset_view(::Tuple{}, A::AbstractArray{T,0}) where T = A _no_offset_view(::Tuple{Base.OneTo, Vararg{Base.OneTo}}, A::AbstractArray) = A _no_offset_view(::Any, A::AbstractArray) = _no_offset_view(A) _no_offset_view(A::AbstractArray) = OffsetArray(A, Origin(1)) _no_offset_view(A::AbstractUnitRange) = UnitRange(A) ##### # center/centered # These two helpers are deliberately not exported; their meaning can be very different in # other scenarios and will be very likely to cause name conflicts if exported. ##### if VERSION < v"1.4" _halfroundInt(v, r::RoundingMode) = round(Int, v/2, r) else _halfroundInt(v, r::RoundingMode) = div(v, 2, r) end """ center(A, [r::RoundingMode=RoundDown])::Dims Return the center coordinate of given array `A`. If `size(A, k)` is even, a rounding procedure will be applied with mode `r`. !!! compat "OffsetArrays 1.9" This method requires at least OffsetArrays 1.9. # Examples ```jldoctest; setup=:(using OffsetArrays) julia> A = reshape(collect(1:9), 3, 3) 3×3 $(Matrix{Int}): 1 4 7 2 5 8 3 6 9 julia> c = OffsetArrays.center(A) (2, 2) julia> A[c...] 5 julia> Ao = OffsetArray(A, -2, -2); # axes (-1:1, -1:1) julia> c = OffsetArrays.center(Ao) (0, 0) julia> Ao[c...] 5 ``` To shift the center coordinate of the given array to `(0, 0, ...)`, you can use [`centered`](@ref OffsetArrays.centered). """ function center(A::AbstractArray, r::RoundingMode=RoundDown) map(axes(A)) do inds _halfroundInt(length(inds)-1, r) + first(inds) end end """ centered(A, cp=center(A)) -> Ao Shift the center coordinate/point `cp` of array `A` to `(0, 0, ..., 0)`. Internally, this is equivalent to `OffsetArray(A, .-cp)`. !!! compat "OffsetArrays 1.9" This method requires at least OffsetArrays 1.9. # Examples ```jldoctest; setup=:(using OffsetArrays) julia> A = reshape(collect(1:9), 3, 3) 3×3 $(Matrix{Int}): 1 4 7 2 5 8 3 6 9 julia> Ao = OffsetArrays.centered(A); # axes (-1:1, -1:1) julia> Ao[0, 0] 5 julia> Ao = OffsetArray(A, OffsetArrays.Origin(0)); # axes (0:2, 0:2) julia> Aoo = OffsetArrays.centered(Ao); # axes (-1:1, -1:1) julia> Aoo[0, 0] 5 ``` Users are allowed to pass `cp` to change how "center point" is interpreted, but the meaning of the output array should be reinterpreted as well. For instance, if `cp = map(last, axes(A))` then this function no longer shifts the center point but instead the bottom-right point to `(0, 0, ..., 0)`. A commonly usage of `cp` is to change the rounding behavior when the array is of even size at some dimension: ```jldoctest; setup=:(using OffsetArrays) julia> A = reshape(collect(1:4), 2, 2) # Ideally the center should be (1.5, 1.5) but OffsetArrays only support integer offsets 2×2 $(Matrix{Int}): 1 3 2 4 julia> OffsetArrays.centered(A, OffsetArrays.center(A, RoundUp)) # set (2, 2) as the center point 2×2 OffsetArray(::$(Matrix{Int}), -1:0, -1:0) with eltype $(Int) with indices -1:0×-1:0: 1 3 2 4 julia> OffsetArrays.centered(A, OffsetArrays.center(A, RoundDown)) # set (1, 1) as the center point 2×2 OffsetArray(::$(Matrix{Int}), 0:1, 0:1) with eltype $(Int) with indices 0:1×0:1: 1 3 2 4 ``` See also [`center`](@ref OffsetArrays.center). """ centered(A::AbstractArray, cp::Dims=center(A)) = OffsetArray(A, .-cp) centered(A::AbstractArray, i::CartesianIndex) = centered(A, Tuple(i)) if VERSION < v"1.12.0-DEV.1713" # The Base implementations are fixed in https://github.com/JuliaLang/julia/pull/56464 and https://github.com/JuliaLang/julia/pull/56474 # we therefore limit these specializations to older versions of julia # we may pass the searchsorted* functions to the parent, and wrap the offset for f in [:searchsortedfirst, :searchsortedlast, :searchsorted] _safe_f = Symbol("_safe_" * String(f)) @eval function $_safe_f(v::OffsetVector, x, ilo, ihi, o::Base.Ordering) offset = v.offsets[1] $f(parent(v), x, ilo - offset, ihi - offset, o) .+ offset end @eval Base.$f(v::OffsetVector, x, ilo::T, ihi::T, o::Base.Ordering) where T<:Integer = $_safe_f(v, x, ilo, ihi, o) end if VERSION <= v"1.2" # ambiguity warnings in earlier versions for f in [:searchsortedfirst, :searchsortedlast, :searchsorted] _safe_f = Symbol("_safe_" * String(f)) @eval Base.$f(v::OffsetVector, x, ilo::Int, ihi::Int, o::Base.Ordering) = $_safe_f(v, x, ilo, ihi, o) end end end if VERSION < v"1.1.0-DEV.783" Base.copyfirst!(dest::OffsetArray, src::OffsetArray) = (maximum!(parent(dest), parent(src)); return dest) end if VERSION <= v"1.7.0-DEV.400" # https://github.com/JuliaLang/julia/pull/39393 # index for zero-argument getindex should be first linear index instead of 1 (#194) Base._to_linear_index(A::OffsetArray) = first(LinearIndices(A)) end if !isdefined(Base, :get_extension) include("../ext/OffsetArraysAdaptExt.jl") end if Base.VERSION >= v"1.4.2" include("precompile.jl") _precompile_() end ## # Deprecations ## # This is a bad API design as it introduces counter intuitive results (#250) @deprecate centered(A::AbstractArray, r::RoundingMode) OffsetArray(A, .-center(A, r)) false end # module ================================================ FILE: src/axes.jl ================================================ """ ro = IdOffsetRange(r::AbstractUnitRange, offset=0) Construct an "identity offset range". Numerically, `collect(ro) == collect(r) .+ offset`, with the additional property that `axes(ro, 1) = axes(r, 1) .+ offset`. When `r` starts at 1, then `ro[i] == i` and even `ro[ro] == ro`, i.e., it's the "identity," which is the origin of the "Id" in `IdOffsetRange`. # Examples The most common case is shifting a range that starts at 1 (either `1:n` or `Base.OneTo(n)`): ```jldoctest ior julia> using OffsetArrays: IdOffsetRange julia> ro = IdOffsetRange(1:3, -2) IdOffsetRange(values=-1:1, indices=-1:1) julia> axes(ro, 1) IdOffsetRange(values=-1:1, indices=-1:1) julia> ro[-1] -1 julia> ro[3] ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int, UnitRange{Int}}) with indices -1:1 at index [3] ``` If the range doesn't start at 1, the values may be different from the indices: ```jldoctest ior julia> ro = IdOffsetRange(11:13, -2) IdOffsetRange(values=9:11, indices=-1:1) julia> axes(ro, 1) # 11:13 is indexed by 1:3, and the offset is also applied to the axes IdOffsetRange(values=-1:1, indices=-1:1) julia> ro[-1] 9 julia> ro[3] ERROR: BoundsError: attempt to access 3-element $(IdOffsetRange{Int, UnitRange{Int}}) with indices -1:1 at index [3] ``` # Extended help Construction/coercion preserves the (shifted) values of the input range, but may modify the indices if required by the specified types. For example, r = OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}(3:4) has `r[1] == 3` and `r[2] == 4`, whereas r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4) has `r[3] == 3` and `r[4] == 4`, and `r[1]` would throw a `BoundsError`. In this latter case, a shift in the axes was needed because `Base.OneTo` ranges must start with value 1. !!! warning In the future, *conversion* will preserve both the values and the indices, throwing an error when this is not achievable. For instance, r = convert(OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}, 3:4) has `r[1] == 3` and `r[2] == 4` and would satisfy `r == 3:4`, whereas ```julia julia> convert(OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}, 3:4) # future behavior, not present behavior ERROR: ArgumentError: first element must be 1, got 3 ``` where the error will arise because the result could not have the same axes as the input. An important corollary is that `typeof(r1)(r2)` and `oftype(r1, r2)` will behave differently: the first coerces `r2` to be of the type of `r1`, whereas the second converts. Developers are urged to future-proof their code by choosing the behavior appropriate for each usage. """ struct IdOffsetRange{T<:Integer,I<:AbstractUnitRange{T}} <: AbstractUnitRange{T} parent::I offset::T function IdOffsetRange{T,I}(r::I, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} _bool_check(T, r, offset) new{T,I}(r, offset) end #= This method is necessary to avoid a StackOverflowError in IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer). The type signature in that method is more specific than IdOffsetRange{T,I}(r::I, offset::T), so it ends up calling itself if I <: IdOffsetRange. =# function IdOffsetRange{T,IdOffsetRange{T,I}}(r::IdOffsetRange{T,I}, offset::T) where {T<:Integer,I<:AbstractUnitRange{T}} _bool_check(T, r, offset) new{T,IdOffsetRange{T,I}}(r, offset) end end function _bool_check(::Type{Bool}, r, offset) # disallow the construction of IdOffsetRange{Bool, UnitRange{Bool}}(true:true, true) if offset && (first(r) || last(r)) throw(ArgumentError("values = $r and offset = $offset can not produce a boolean range")) end return nothing end _bool_check(::Type, r, offset) = nothing # Construction/coercion from arbitrary AbstractUnitRanges function IdOffsetRange{T,I}(r::AbstractUnitRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} rc, o = offset_coerce(I, r) return IdOffsetRange{T,I}(rc, convert(T, o+offset)::T) end function IdOffsetRange{T}(r::AbstractUnitRange, offset::Integer = 0) where T<:Integer rc = convert(AbstractUnitRange{T}, r)::AbstractUnitRange{T} return IdOffsetRange{T,typeof(rc)}(rc, convert(T, offset)::T) end IdOffsetRange(r::AbstractUnitRange{T}, offset::Integer = 0) where T<:Integer = IdOffsetRange{T,typeof(r)}(r, convert(T, offset)::T) # Coercion from other IdOffsetRanges IdOffsetRange{T,I}(r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r function IdOffsetRange{T,I}(r::IdOffsetRange, offset::Integer = 0) where {T<:Integer,I<:AbstractUnitRange{T}} rc, offset_rc = offset_coerce(I, r.parent) return IdOffsetRange{T,I}(rc, convert(T, r.offset + offset + offset_rc)::T) end IdOffsetRange{T}(r::IdOffsetRange{T}) where {T<:Integer} = r function IdOffsetRange{T}(r::IdOffsetRange, offset::Integer = 0) where T<:Integer return IdOffsetRange{T}(r.parent, r.offset + offset) end IdOffsetRange(r::IdOffsetRange) = r # Constructor to make `show` round-trippable # try to preserve typeof(values) if the indices are known to be 1-based _subtractindexoffset(values, indices::Union{Base.OneTo, IdentityUnitRange{<:Base.OneTo}}, offset) = values _subtractindexoffset(values, indices, offset) = _subtractoffset(values, offset) function IdOffsetRange(; values::AbstractUnitRange{<:Integer}, indices::AbstractUnitRange{<:Integer}) length(values) == length(indices) || throw(ArgumentError("values and indices must have the same length")) values_nooffset = no_offset_view(values) offset = first(indices) - 1 values_minus_offset = _subtractindexoffset(values_nooffset, indices, offset) return IdOffsetRange(values_minus_offset, offset) end # Conversions to an AbstractUnitRange{Int} (and to an OrdinalRange{Int,Int} on Julia v"1.6") are necessary # to evaluate CartesianIndices for BigInt ranges, as their axes are also BigInt ranges Base.AbstractUnitRange{T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r) # https://github.com/JuliaLang/julia/pull/40038 if v"1.6" <= VERSION < v"1.9.0-DEV.642" Base.OrdinalRange{T,T}(r::IdOffsetRange) where {T<:Integer} = IdOffsetRange{T}(r) end # TODO: uncomment these when Julia is ready # # Conversion preserves both the values and the indices, throwing an InexactError if this # # is not possible. # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange{T,I}) where {T<:Integer,I<:AbstractUnitRange{T}} = r # Base.convert(::Type{IdOffsetRange{T,I}}, r::IdOffsetRange) where {T<:Integer,I<:AbstractUnitRange{T}} = # IdOffsetRange{T,I}(convert(I, r.parent), r.offset) # Base.convert(::Type{IdOffsetRange{T,I}}, r::AbstractUnitRange) where {T<:Integer,I<:AbstractUnitRange{T}} = # IdOffsetRange{T,I}(convert(I, r), 0) offset_coerce(::Type{Base.OneTo{T}}, r::Base.OneTo) where T<:Integer = convert(Base.OneTo{T}, r), 0 function offset_coerce(::Type{Base.OneTo{T}}, r::AbstractUnitRange) where T<:Integer o = first(r) - 1 return Base.OneTo{T}(last(r) - o), o end # function offset_coerce(::Type{Base.OneTo{T}}, r::IdOffsetRange) where T<:Integer # rc, o = offset_coerce(Base.OneTo{T}, r.parent) # Fallback, specialze this method if `convert(I, r)` doesn't do what you need offset_coerce(::Type{I}, r::AbstractUnitRange) where I<:AbstractUnitRange = convert(I, r)::I, 0 @inline Base.parent(r::IdOffsetRange) = r.parent @inline Base.axes(r::IdOffsetRange) = (axes1(r),) @inline axes1(r::IdOffsetRange) = IdOffsetRange(Base.axes1(r.parent), r.offset) if VERSION < v"1.8.2" Base.axes1(r::IdOffsetRange) = axes1(r) end @inline Base.unsafe_indices(r::IdOffsetRange) = (axes1(r),) @inline Base.length(r::IdOffsetRange) = length(r.parent) @inline Base.isempty(r::IdOffsetRange) = isempty(r.parent) #= We specialize on reduced_indices to work around cases where the parent axis type doesn't support reduced_index, but the axes do support reduced_indices The difference is that reduced_index expects the axis type to remain unchanged, which may not always be possible, eg. for statically sized axes See https://github.com/JuliaArrays/OffsetArrays.jl/issues/204 =# function Base.reduced_indices(inds::Tuple{IdOffsetRange, Vararg{IdOffsetRange}}, d::Int) parents_reduced = Base.reduced_indices(map(parent, inds), d) ntuple(i -> IdOffsetRange(parents_reduced[i], inds[i].offset), Val(length(inds))) end Base.reduced_index(i::IdOffsetRange) = typeof(i)(first(i):first(i)) # Workaround for #92 on Julia < 1.4 Base.reduced_index(i::IdentityUnitRange{<:IdOffsetRange}) = typeof(i)(first(i):first(i)) if VERSION < v"1.8.2" for f in [:firstindex, :lastindex] @eval @inline Base.$f(r::IdOffsetRange) = $f(r.parent) + r.offset end end for f in [:first, :last] # coerce the type to deal with values that get promoted on addition (eg. Bool) @eval @inline Base.$f(r::IdOffsetRange) = eltype(r)($f(r.parent) + r.offset) end # Iteration for an IdOffsetRange @inline Base.iterate(r::IdOffsetRange, i...) = _iterate(r, i...) # In general we iterate over the parent term by term and add the offset. # This might have some performance degradation when coupled with bounds-checking # See https://github.com/JuliaArrays/OffsetArrays.jl/issues/214 @inline function _iterate(r::IdOffsetRange, i...) ret = iterate(r.parent, i...) ret === nothing && return nothing return (eltype(r)(ret[1] + r.offset), ret[2]) end # Base.OneTo(n) is known to be exactly equivalent to the range 1:n, # and has no specialized iteration defined for it, # so we may add the offset to the range directly and iterate over the result # This gets around the performance issue described in issue #214 # We use the helper function _addoffset to evaluate the range instead of broadcasting # just in case this makes it easy for the compiler. @inline _iterate(r::IdOffsetRange{<:Integer, <:Base.OneTo}, i...) = iterate(_addoffset(r.parent, r.offset), i...) @inline function Base.getindex(r::IdOffsetRange, i::Integer) i isa Bool && throw(ArgumentError("invalid index: $i of type Bool")) @boundscheck checkbounds(r, i) @inbounds eltype(r)(r.parent[i - r.offset] + r.offset) end # Logical indexing following https://github.com/JuliaLang/julia/pull/31829 #= Helper function to perform logical indxeing for boolean ranges The code implemented is a branch-free version of the following: range(first(s) ? first(r) : last(r), length=Int(last(s))) See https://github.com/JuliaArrays/OffsetArrays.jl/pull/224#discussion_r595635143 Logical indexing does not preserve indices, unlike other forms of vector indexing =# @inline function _getindex(r, s::AbstractUnitRange{Bool}) range(first(r) * first(s) + last(r) * !first(s), length=Int(last(s))) end @inline function _getindex(r, s::StepRange{Bool}) range(first(r) * first(s) + last(r) * !first(s), step = oneunit(step(s)), length=Int(last(s))) end @inline function _getindex(r, s::AbstractUnitRange) @inbounds rs = r.parent[_subtractoffset(s, r.offset)] .+ r.offset _indexedby(rs, axes(s)) end @inline function _getindex(r, s::StepRange) rs = @inbounds r.parent[s .- r.offset] .+ r.offset _indexedby(rs, axes(s)) end for T in [:AbstractUnitRange, :StepRange] @eval @inline function Base.getindex(r::IdOffsetRange, s::$T{<:Integer}) @boundscheck checkbounds(r, s) return _getindex(r, s) end end # These methods are necessary to avoid ambiguity for R in [:IIUR, :IdOffsetRange] @eval @inline function Base.getindex(r::IdOffsetRange, s::$R) @boundscheck checkbounds(r, s) return _getindex(r, s) end end # offset-preserve broadcasting Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(-), r::IdOffsetRange, x::Integer) = IdOffsetRange(r.parent .- x, r.offset) Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), r::IdOffsetRange, x::Integer) = IdOffsetRange(r.parent .+ x, r.offset) Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(+), x::Integer, r::IdOffsetRange) = IdOffsetRange(x .+ r.parent, r.offset) Broadcast.broadcasted(::Base.Broadcast.DefaultArrayStyle{1}, ::typeof(big), r::IdOffsetRange) = IdOffsetRange(big.(r.parent), r.offset) Base.show(io::IO, r::IdOffsetRange) = print(io, IdOffsetRange, "(values=",first(r), ':', last(r),", indices=",first(eachindex(r)),':',last(eachindex(r)), ")") # Optimizations @inline Base.checkindex(::Type{Bool}, inds::IdOffsetRange, i::Real) = Base.checkindex(Bool, inds.parent, i - inds.offset) if VERSION < v"1.5.2" # issue 100, 133: IdOffsetRange as another index-preserving case shouldn't comtribute offsets # fixed by https://github.com/JuliaLang/julia/pull/37204 @inline Base.compute_offset1(parent, stride1::Integer, dims::Tuple{Int}, inds::Tuple{IdOffsetRange}, I::Tuple) = Base.compute_linindex(parent, I) - stride1*first(Base.axes1(inds[1])) end # This was deemed "too private" to extend: see issue #184 # # Fixes an inference failure in Base.mapfirst! # # Test: A = OffsetArray(rand(4,4), (-3,5)); R = similar(A, (1:1, 6:9)); maximum!(R, A) # if isdefined(Base, :_firstslice) # Base._firstslice(i::IdOffsetRange) = IdOffsetRange(Base._firstslice(i.parent), i.offset) # end ================================================ FILE: src/origin.jl ================================================ """ Origin(indices...) Origin(origin::Tuple) Origin(origin::CartesianIndex) A helper type to construct OffsetArray with a given origin. This is not exported. The `origin` of an array is defined as the tuple of the first index along each axis, i.e., `first.(axes(A))`. # Example ```jldoctest origin; setup=:(using OffsetArrays) julia> a = [1 2; 3 4]; julia> using OffsetArrays: Origin julia> OffsetArray(a, Origin(0, 1)) 2×2 OffsetArray(::$(Array{Int,2}), 0:1, 1:2) with eltype $Int with indices 0:1×1:2: 1 2 3 4 julia> OffsetArray(a, Origin(0)) # short notation for `Origin(0, 0)` 2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1: 1 2 3 4 ``` An `Origin` object is callable, and it may shift the origin of an array to the specified point. ```jldoctest origin julia> b = Origin(0)(a) # shift the origin of the array to (0,0) 2×2 OffsetArray(::$(Array{Int, 2}), 0:1, 0:1) with eltype $Int with indices 0:1×0:1: 1 2 3 4 ``` The type `Origin`, when called with an `AbstractArray` as the argument, will return an instance corresponding ot the origin of the array. ```jldoctest origin julia> origin_b = Origin(b) # retrieve the origin of the array as an Origin instance Origin(0, 0) julia> origin_b(ones(2,2)) # shift the origin of another array to that of b, in this case to (0,0) 2×2 OffsetArray(::$(Array{Float64, 2}), 0:1, 0:1) with eltype Float64 with indices 0:1×0:1: 1.0 1.0 1.0 1.0 ``` !!! tip One may broadcast an `Origin` instance over multiple arrays to shift them all to the same origin. ```jldoctest julia> using OffsetArrays: Origin julia> a = [1 2; 3 4]; # origin at (1,1) julia> b = Origin(2,3)(a); # origin at (2,3) julia> c = Origin(4)(a); # origin at (4,4) julia> ao, bo, co = Origin(0).((a, b, c)); # shift all origins to (0,0) julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (0,0) true julia> ao, bo, co = Origin(b).((a, b, c)); # shift all origins to that of b julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) true julia> ao, bo, co = OffsetArray.((a, b, c), Origin(b)); # another way to do the same julia> first.(axes(ao)) == first.(axes(bo)) == first.(axes(co)) == (2,3) true ``` """ struct Origin{T<:Union{Tuple{Vararg{Int}}, Int}} index::T end Origin(I::Tuple{Vararg{Int}}) = Origin{typeof(I)}(I) Origin(I::Tuple{Vararg{Number}}) = Origin(map(Int, I)) Origin(I::CartesianIndex) = Origin(Tuple(I)) Origin(I::Number...) = Origin(I) # Origin(0) != Origin((0, )) but they work the same with broadcasting Origin(n::Number) = Origin{Int}(Int(n)) Base.Broadcast.broadcastable(o::Origin) = Ref(o) _showidx(index::Integer) = "(" * string(index) * ")" _showidx(index::Tuple) = string(index) Base.show(io::IO, o::Origin) = print(io, "Origin", _showidx(o.index)) ================================================ FILE: src/precompile.jl ================================================ function _precompile_() ccall(:jl_generating_output, Cint, ()) == 1 || return nothing Base.precompile(Tuple{typeof(Base.showarg),IOBuffer,OffsetArray{Int, 0, Array{Int, 0}},Bool}) # time: 0.037824474 Base.precompile(Tuple{Type{IdOffsetRange{Int, Base.OneTo{Int}}},UnitRange{Int}}) # time: 0.009825722 Base.precompile(Tuple{typeof(Base.inds2string),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}, IdOffsetRange{Int, Base.OneTo{Int}}}}) # time: 0.0080779 Base.precompile(Tuple{typeof(zeros),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}}}) # time: 0.007713056 Base.precompile(Tuple{typeof(ones),Tuple{IdOffsetRange{Int, Base.OneTo{Int}}}}) # time: 0.007713056 Base.precompile(Tuple{typeof(trues),Tuple{UnitRange{Int}, UnitRange{Int}}}) # time: 0.005478372 Base.precompile(Tuple{typeof(falses),Tuple{UnitRange{Int}, UnitRange{Int}}}) # time: 0.005478372 Base.precompile(Tuple{typeof(firstindex),IdOffsetRange{Int, Base.OneTo{Int}}}) # time: 0.004100289 end ================================================ FILE: src/utils.jl ================================================ ### Low-level utilities ### _indexoffset(r::AbstractRange) = first(r) - 1 _indexoffset(i::Integer) = 0 _indexlength(r::AbstractRange) = length(r) _indexlength(i::Integer) = Int(i) _indexlength(i::Colon) = Colon() # utility methods used in reshape # we don't use _indexlength in this to avoid converting the arguments to Int _checksize(ind::Integer, s) = ind == s _checksize(ind::AbstractUnitRange, s) = length(ind) == s _toaxis(i::Integer) = Base.OneTo(i) _toaxis(i) = i _strip_IdOffsetRange(r::IdOffsetRange) = parent(r) _strip_IdOffsetRange(r) = r _offset(axparent::AbstractUnitRange, ax::AbstractUnitRange) = first(ax) - first(axparent) _offset(axparent::AbstractUnitRange, ::Union{Integer, Colon}) = 1 - first(axparent) _offsets(A::AbstractArray) = map(ax -> first(ax) - 1, axes(A)) _offsets(A::AbstractArray, B::AbstractArray) = map(_offset, axes(B), axes(A)) """ OffsetArrays.AxisConversionStyle(typeof(indices)) `AxisConversionStyle` declares if `indices` should be converted to a single `AbstractUnitRange{Int}` or to a `Tuple{Vararg{AbstractUnitRange{Int}}}` while flattening custom types into indices. This method is called after `to_indices(A::Array, axes(A), indices)` to provide further information in case `to_indices` does not return a `Tuple` of `AbstractUnitRange{Int}`. Custom index types should extend `AxisConversionStyle` and return either `OffsetArray.SingleRange()`, which is the default, or `OffsetArray.TupleOfRanges()`. In the former case, the type `T` should define `Base.convert(::Type{AbstractUnitRange{Int}}, ::T)`, whereas in the latter it should define `Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, ::T)`. An example of the latter is `CartesianIndices`, which is converted to a `Tuple` of `AbstractUnitRange{Int}` while flattening the indices. # Example ```jldoctest; setup=:(using OffsetArrays) julia> struct NTupleOfUnitRanges{N} x ::NTuple{N, UnitRange{Int}} end julia> Base.to_indices(A, inds, t::Tuple{NTupleOfUnitRanges{N}}) where {N} = t; julia> OffsetArrays.AxisConversionStyle(::Type{NTupleOfUnitRanges{N}}) where {N} = OffsetArrays.TupleOfRanges(); julia> Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, t::NTupleOfUnitRanges) = t.x; julia> a = zeros(3, 3); julia> inds = NTupleOfUnitRanges((3:5, 2:4)); julia> oa = OffsetArray(a, inds); julia> axes(oa, 1) == 3:5 true julia> axes(oa, 2) == 2:4 true ``` """ abstract type AxisConversionStyle end struct SingleRange <: AxisConversionStyle end struct TupleOfRanges <: AxisConversionStyle end AxisConversionStyle(::Type) = SingleRange() AxisConversionStyle(::Type{<:CartesianIndices}) = TupleOfRanges() _convertTupleAbstractUnitRange(x) = _convertTupleAbstractUnitRange(AxisConversionStyle(typeof(x)), x) _convertTupleAbstractUnitRange(::SingleRange, x) = (convert(AbstractUnitRange{Int}, x),) _convertTupleAbstractUnitRange(::TupleOfRanges, x) = convert(Tuple{Vararg{AbstractUnitRange{Int}}}, x) _toAbstractUnitRanges(t::Tuple) = (_convertTupleAbstractUnitRange(first(t))..., _toAbstractUnitRanges(tail(t))...) _toAbstractUnitRanges(::Tuple{}) = () # ensure that the indices are consistent in the constructor _checkindices(A::AbstractArray, indices, label) = _checkindices(ndims(A), indices, label) function _checkindices(N::Integer, indices, label) throw_argumenterror(N, indices, label) = throw(ArgumentError(label*" $indices are not compatible with a $(N)D array")) N == length(indices) || throw_argumenterror(N, indices, label) end @inline _indexedby(r::AbstractVector, ax::Tuple{Any}) = _indexedby(r, ax[1]) @inline _indexedby(r::AbstractUnitRange{<:Integer}, ::Base.OneTo) = no_offset_view(r) @inline _indexedby(r::AbstractUnitRange{Bool}, ::Base.OneTo) = no_offset_view(r) @inline _indexedby(r::AbstractVector, ::Base.OneTo) = no_offset_view(r) @inline function _indexedby(r::AbstractUnitRange{<:Integer}, ax::AbstractUnitRange) of = convert(eltype(r), first(ax) - 1) IdOffsetRange(_subtractoffset(r, of), of) end @inline _indexedby(r::AbstractUnitRange{Bool}, ax::AbstractUnitRange) = OffsetArray(r, ax) @inline _indexedby(r::AbstractVector, ax::AbstractUnitRange) = OffsetArray(r, ax) # These functions are equivalent to the broadcasted operation r .- of # However these ensure that the result is an AbstractRange even if a specific # broadcasting behavior is not defined for a custom type @inline _subtractoffset(r::AbstractUnitRange, of) = UnitRange(first(r) - of, last(r) - of) @inline _subtractoffset(r::AbstractRange, of) = range(first(r) - of, stop = last(r) - of, step = step(r)) # similar to _subtractoffset, except these evaluate r .+ of @inline _addoffset(r::AbstractUnitRange, of) = UnitRange(first(r) + of, last(r) + of) @inline _addoffset(r::AbstractRange, of) = range(first(r) + of, stop = last(r) + of, step = step(r)) if VERSION <= v"1.7.0-DEV.1039" _contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = UnitRange{Int}(r) else _contiguousindexingtype(r::AbstractUnitRange{<:Integer}) = r end _of_eltype(::Type{T}, M::AbstractArray{T}) where {T} = M _of_eltype(T, M::AbstractArray) = map(T, M) # filter the arguments to reshape to check if there are any ranges # If not, we may pop the parent array _filterreshapeinds(t::Tuple{AbstractUnitRange, Vararg{Any}}) = t _filterreshapeinds(t::Tuple) = _filterreshapeinds(tail(t)) _filterreshapeinds(t::Tuple{}) = t _popreshape(A::AbstractArray, ax::Tuple{Vararg{Base.OneTo}}, inds::Tuple{}) = no_offset_view(A) _popreshape(A::AbstractArray, ax, inds) = A ================================================ FILE: test/customranges.jl ================================================ # Useful for testing indexing struct ZeroBasedRange{T,A<:AbstractRange{T}} <: AbstractRange{T} a :: A function ZeroBasedRange(a::AbstractRange{T}) where {T} @assert !Base.has_offset_axes(a) new{T, typeof(a)}(a) end end struct ZeroBasedUnitRange{T,A<:AbstractUnitRange{T}} <: AbstractUnitRange{T} a :: A function ZeroBasedUnitRange(a::AbstractUnitRange{T}) where {T} @assert !Base.has_offset_axes(a) new{T, typeof(a)}(a) end end for Z in [:ZeroBasedRange, :ZeroBasedUnitRange] @eval Base.parent(A::$Z) = A.a @eval Base.first(A::$Z) = first(A.a) @eval Base.length(A::$Z) = length(A.a) @eval Base.last(A::$Z) = last(A.a) @eval Base.size(A::$Z) = size(A.a) @eval Base.axes(A::$Z) = map(x -> IdentityUnitRange(0:x-1), size(A.a)) @eval Base.getindex(A::$Z, i::Int) = A.a[i + 1] @eval Base.getindex(A::$Z, i::Integer) = A.a[i + 1] @eval Base.firstindex(A::$Z) = 0 @eval Base.step(A::$Z) = step(A.a) @eval OffsetArrays.no_offset_view(A::$Z) = A.a @eval function Base.show(io::IO, A::$Z) show(io, A.a) print(io, " with indices $(axes(A,1))") end end for Z in [:ZeroBasedRange, :ZeroBasedUnitRange] for R in [:AbstractRange, :AbstractUnitRange, :StepRange] @eval @inline function Base.getindex(A::$Z, r::$R{<:Integer}) @boundscheck checkbounds(A, r) OffsetArrays._indexedby(A.a[r .+ 1], axes(r)) end end for R in [:ZeroBasedUnitRange, :ZeroBasedRange] @eval @inline function Base.getindex(A::$Z, r::$R{<:Integer}) @boundscheck checkbounds(A, r) OffsetArrays._indexedby(A.a[r.a .+ 1], axes(r)) end end for R in [:IIUR, :IdOffsetRange] @eval @inline function Base.getindex(A::$Z, r::$R) @boundscheck checkbounds(A, r) OffsetArrays._indexedby(A.a[r .+ 1], axes(r)) end end for R in [:AbstractUnitRange, :IdOffsetRange, :IdentityUnitRange, :SliceIntUR, :StepRange, :StepRangeLen, :LinRange] @eval @inline function Base.getindex(A::$R, r::$Z) @boundscheck checkbounds(A, r) OffsetArrays._indexedby(A[r.a], axes(r)) end end @eval @inline function Base.getindex(A::StepRangeLen{<:Any,<:Base.TwicePrecision,<:Base.TwicePrecision}, r::$Z) @boundscheck checkbounds(A, r) OffsetArrays._indexedby(A[r.a], axes(r)) end @eval Base.reshape(z::$Z, inds::Tuple{}) = reshape(parent(z), inds) @eval Base.reshape(z::$Z, inds::Tuple{Int, Vararg{Int}}) = reshape(parent(z), inds) @eval Base.reshape(z::$Z, inds::Tuple{Union{Int, AbstractUnitRange{<:Integer}}, Vararg{Union{Int, AbstractUnitRange{<:Integer}}}}) = reshape(parent(z), inds) end # A basic range that does not have specialized vector indexing methods defined # In this case the best that we may do is to return an OffsetArray # Despite this, an indexing operation involving this type should preserve the axes of the indices struct CustomRange{T,A<:AbstractRange{T}} <: AbstractRange{T} a :: A end Base.parent(r::CustomRange) = r.a Base.size(r::CustomRange) = size(parent(r)) Base.length(r::CustomRange) = length(parent(r)) Base.axes(r::CustomRange) = axes(parent(r)) Base.first(r::CustomRange) = first(parent(r)) Base.last(r::CustomRange) = last(parent(r)) Base.step(r::CustomRange) = step(parent(r)) Base.getindex(r::CustomRange, i::Int) = getindex(parent(r), i) ================================================ FILE: test/origin.jl ================================================ using OffsetArrays: Origin @testset "Origin" begin get_origin(A::AbstractArray) = first.(axes(A)) @test Origin(0) != Origin((0, )) @test Origin(CartesianIndex(1, 2)) === Origin((1, 2)) === Origin(1, 2) @test Origin(Int32.((1,2))) == Origin(Int64.((1,2))) @test Origin(Int32.((1,2))...) == Origin(Int64.((1,2))...) == Origin((1.0, 2.0)) @test Origin(Int32(1)) == Origin(Int64(1)) == Origin(1.0) @test_throws Exception Origin(1.5) # 0d A = OffsetArray(zeros()) B = OffsetArray(zeros(), Origin()) @test axes(A) == axes(B) # 1d v = [1, 2] @test get_origin(OffsetArray(v, Origin(2))) == (2, ) ov = OffsetArray(v, -3) @test get_origin(OffsetArray(ov, Origin(2))) == (2, ) @test get_origin(OffsetVector(ov, Origin(2))) == (2, ) @test get_origin(OffsetArray(ov, Origin((2, )))) == (2, ) # 2d a = [1 2;3 4] @test get_origin(OffsetArray(a, Origin(0))) == (0, 0) oa = OffsetArray(a, -3, -3) @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0) @test get_origin(OffsetMatrix(oa, Origin(0))) == (0, 0) @test get_origin(OffsetArray(oa, Origin(1, 2))) == (1, 2) # 3d a = ones(3, 3, 3) @test get_origin(OffsetArray(a, Origin(0))) == (0, 0, 0) oa = OffsetArray(a, -3, -3, -3) @test get_origin(OffsetArray(oa, Origin(0))) == (0, 0, 0) @test get_origin(OffsetArray(oa, Origin(1, 2, 3))) == (1, 2, 3) # Scalar broadcasting let a = [ [1,2,3], [4,5,6] ] oa = OffsetVector.(a, Origin(0)) @test get_origin.(oa) == [ (0,), (0,) ] a = [ [1 2; 3 4], [5 6 7; 8 9 10] ] oa = OffsetArray.(a, Origin(0, -1)) @test get_origin.(oa) == [ (0,-1), (0,-1) ] end @testset "as a callable" begin a = [1 2; 3 4]; @test OffsetArray(a, Origin(2)) == Origin(2)(a) for (index, firstinds) in Any[(1, (1,1)), ((2,3), (2,3))] b = Origin(index)(a) @test first.(axes(b)) == firstinds @test Origin(b) == Origin(firstinds) @test Origin(OffsetArrays.no_offset_view(b)) == Origin(ntuple(_ -> 1, Val(ndims(b)))) end # compatibility with other array types @test Origin(Ones(2,2)) == Origin(1,1) @test Origin(SMatrix{2,2,Int,4}(1,2,3,4)) == Origin(1,1) end @testset "display" begin io = IOBuffer() show(io, Origin(1)) @test String(take!(io)) == "Origin(1)" show(io, Origin(1, 1)) @test String(take!(io)) == "Origin(1, 1)" end @testset "avoid overflow (issue #279)" begin A = Origin(typemin(Int)+1)(rand(3,3)) B = Origin(typemax(Int)-4)(A) @test first.(axes(B)) == ntuple(_ -> typemax(Int)-4, Val(ndims(B))) end end ================================================ FILE: test/runtests.jl ================================================ using Adapt using Aqua using Base: Slice using CatIndices: BidirectionalVector using DelimitedFiles using DistributedArrays using Documenter using EllipsisNotation using FillArrays using LinearAlgebra using OffsetArrays using OffsetArrays: IdentityUnitRange, no_offset_view, IIUR, Origin, IdOffsetRange using StaticArrays using Test const SliceIntUR = Slice{<:AbstractUnitRange{<:Integer}} DocMeta.setdocmeta!(OffsetArrays, :DocTestSetup, :(using OffsetArrays); recursive=true) no_offset_axes(x, d) = no_offset_view(axes(x, d)) no_offset_axes(x) = map(no_offset_view, axes(x)) # https://github.com/JuliaLang/julia/pull/29440 if VERSION < v"1.1.0-DEV.389" Base.:(:)(I::CartesianIndex{N}, J::CartesianIndex{N}) where N = CartesianIndices(map((i,j) -> i:j, Tuple(I), Tuple(J))) end # Custom index types struct ZeroBasedIndexing end struct NewColon end struct TupleOfRanges{N} x ::NTuple{N, UnitRange{Int}} end include("customranges.jl") function same_value(r1, r2) length(r1) == length(r2) || return false for (v1, v2) in zip(r1, r2) v1 == v2 || return false end return true end @testset "Project meta quality checks" begin Aqua.test_all(OffsetArrays, piracies=false) if VERSION >= v"1.2" doctest(OffsetArrays, manual = false) end end @testset "IdOffsetRange" begin function check_indexed_by(r, rindx) for i in rindx r[i] end @test_throws BoundsError r[minimum(rindx)-1] @test_throws BoundsError r[maximum(rindx)+1] return nothing end ro = OffsetArrays.IdOffsetRange(Base.OneTo(3)) rs = OffsetArrays.IdOffsetRange(3:5, -2) @test typeof(ro) !== typeof(rs) @test same_value(ro, 1:3) check_indexed_by(ro, 1:3) @test same_value(rs, 1:3) check_indexed_by(rs, -1:1) @test firstindex(ro) == 1 @test lastindex(ro) == 3 @test firstindex(rs) == -1 @test lastindex(rs) == 1 @test @inferred(typeof(ro)(ro)) === ro @test @inferred(OffsetArrays.IdOffsetRange{Int}(ro)) === ro @test @inferred(OffsetArrays.IdOffsetRange{Int16}(ro)) === OffsetArrays.IdOffsetRange(Base.OneTo(Int16(3))) @test @inferred(OffsetArrays.IdOffsetRange(ro)) === ro @test parent(ro) === ro.parent @test parent(rs) === rs.parent # construction/coercion preserves the values, altering the axes if needed r2 = @inferred(typeof(rs)(ro)) @test typeof(r2) === typeof(rs) @test same_value(ro, 1:3) check_indexed_by(ro, 1:3) r2 = @inferred(typeof(ro)(rs)) @test typeof(r2) === typeof(ro) @test same_value(r2, 1:3) check_indexed_by(r2, 1:3) # check the example in the comments r = OffsetArrays.IdOffsetRange{Int,UnitRange{Int}}(3:4) @test same_value(r, 3:4) check_indexed_by(r, 1:2) r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4) @test same_value(r, 3:4) check_indexed_by(r, 3:4) r = OffsetArrays.IdOffsetRange{Int,Base.OneTo{Int}}(3:4, -2) @test same_value(r, 1:2) check_indexed_by(r, 1:2) r = OffsetArrays.IdOffsetRange{Int32, Base.OneTo{Int32}}(Base.OneTo(Int64(2)), 3) @test same_value(r, 4:5) check_indexed_by(r, 4:5) r = IdOffsetRange{Int, UnitRange{Int}}(IdOffsetRange(3:5, 2), 2) @test typeof(r) == IdOffsetRange{Int, UnitRange{Int}} @test same_value(r, 7:9) check_indexed_by(r, 5:7) r = IdOffsetRange{Int, Base.OneTo{Int}}(IdOffsetRange(Base.OneTo(3), 1), 1) @test typeof(r) == IdOffsetRange{Int,Base.OneTo{Int}} @test same_value(r, 3:5) check_indexed_by(r, 3:5) rp = Base.OneTo(3) r = IdOffsetRange(rp) r2 = IdOffsetRange{Int,typeof(r)}(r, 1) @test same_value(r2, 2:4) check_indexed_by(r2, 2:4) r2 = IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}}(r, 1) @test typeof(r2) == IdOffsetRange{Int32,IdOffsetRange{Int32,Base.OneTo{Int32}}} @test same_value(r2, 2:4) check_indexed_by(r2, 2:4) # eltype coercion through the AbstractUnitRange constructor ro = OffsetArrays.IdOffsetRange(Base.OneTo(3)) @test @inferred(AbstractUnitRange{Int}(ro)) === ro rb = IdOffsetRange(Base.OneTo(big(3))) @test @inferred(AbstractUnitRange{Int}(rb)) === IdOffsetRange(Base.OneTo(3)) # Constructor that's round-trippable with `show` rrt = IdOffsetRange(values=7:9, indices=-1:1) @test same_value(rrt, 7:9) check_indexed_by(rrt, -1:1) @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:2) @test_throws ArgumentError IdOffsetRange(values=7:9, indices=-1:0) @test_throws TypeError IdOffsetRange(values=7:9, indices=-1) @test_throws UndefKeywordError IdOffsetRange(values=7:9) @test_throws UndefKeywordError IdOffsetRange(indices=-1:1) @test_throws MethodError IdOffsetRange(7:9, indices=-1:1) @test_throws MethodError IdOffsetRange(-1:1, values=7:9) p = IdOffsetRange(1:3, 2) q = IdOffsetRange(values = p .- 2, indices = p) @test same_value(q, 1:3) check_indexed_by(q, p) @testset for indices in Any[Base.OneTo(3), IdentityUnitRange(Base.OneTo(3))] p = IdOffsetRange(values = IdOffsetRange(1:3, 2), indices = indices) @test same_value(p, 3:5) check_indexed_by(p, 1:3) q = IdOffsetRange(values = Base.OneTo(3), indices = indices) @test same_value(q, 1:3) @test q isa IdOffsetRange{Int, Base.OneTo{Int}} end # conversion preserves both the values and the axes, throwing an error if this is not possible @test @inferred(oftype(ro, ro)) === ro @test @inferred(convert(OffsetArrays.IdOffsetRange{Int}, ro)) === ro @test @inferred(convert(OffsetArrays.IdOffsetRange{Int}, rs)) === rs @test @inferred(convert(OffsetArrays.IdOffsetRange{Int16}, ro)) === OffsetArrays.IdOffsetRange(Base.OneTo(Int16(3))) r2 = @inferred(oftype(rs, ro)) @test typeof(r2) === typeof(rs) @test same_value(r2, 1:3) check_indexed_by(r2, 1:3) # These two broken tests can be fixed by uncommenting the `convert` definitions # in axes.jl, but unfortunately Julia may not quite be ready for this. (E.g. `reinterpretarray.jl`) @test_broken try oftype(ro, rs); false catch err true end # replace with line below # @test_throws ArgumentError oftype(ro, rs) @test @inferred(oftype(ro, Base.OneTo(2))) === OffsetArrays.IdOffsetRange(Base.OneTo(2)) @test @inferred(oftype(ro, 1:2)) === OffsetArrays.IdOffsetRange(Base.OneTo(2)) @test_broken try oftype(ro, 3:4); false catch err true end # @test_throws ArgumentError oftype(ro, 3:4) # broadcasting behavior with scalars (issue #104) r3 = (1 .+ OffsetArrays.IdOffsetRange(3:5, -1) .+ 1) .- 1 @test r3 isa OffsetArrays.IdOffsetRange @test same_value(r3, 3:5) check_indexed_by(r3, axes(r3,1)) r = OffsetArrays.IdOffsetRange(3:5, -1) rc = copyto!(similar(r), r) n = big(typemax(Int)) @test @inferred(broadcast(+, r, n)) == @inferred(broadcast(+, n, r)) == rc .+ n @test @inferred(broadcast(-, r, n)) == rc .- n @test @inferred(broadcast(big, r)) == big.(rc) for n in Any[2, big(typemax(Int))] @test @inferred(broadcast(+, r, n)) == @inferred(broadcast(+, n, r)) == rc .+ n end @testset "Idempotent indexing" begin @testset "Indexing into an IdOffsetRange" begin r = OffsetArrays.IdOffsetRange(3:5, -1) # Indexing with IdentityUnitRange s = IdentityUnitRange(0:2) @test axes(r[s]) == axes(s) for i in eachindex(s) @test r[s[i]] == r[s][i] end # Indexing with IdOffsetRange s = OffsetArrays.IdOffsetRange(-4:-2, 4) @test axes(r[s]) == axes(s) for i in eachindex(s) @test r[s[i]] == r[s][i] end # Indexing with UnitRange s = 0:2 @test axes(r[s]) == axes(s) for i in eachindex(s) @test r[s[i]] == r[s][i] end end @testset "Indexing using an IdOffsetRange" begin r = OffsetArrays.IdOffsetRange(3:5, -1) # Indexing into an IdentityUnitRange s = IdentityUnitRange(-1:5) @test axes(s[r]) == axes(r) for i in eachindex(r) @test s[r[i]] == s[r][i] end # Indexing into an UnitRange s = -3:6 @test axes(s[r]) == axes(r) for i in eachindex(r) @test s[r[i]] == s[r][i] end end end # Test reduced index rred = Base.reduced_index(r) @test typeof(rred) == typeof(r) @test length(rred) == 1 @test first(rred) == first(r) @testset "reduced_indices" begin a = reshape(1:24, 2, 3, 4) sa = OffsetArray(a, (2, 3, 4)); @testset for dim in 1:ndims(sa) sasum = sum(sa, dims = dim) @test parent(sasum) == sum(a, dims = dim) find = firstindex(sa, dim) @test no_offset_axes(sasum, dim) == find:find end end @testset "conversion to AbstractUnitRange" begin r = IdOffsetRange(1:2) @test AbstractUnitRange{Int}(r) === r r2 = IdOffsetRange(big(1):big(2)) @test AbstractUnitRange{Int}(r2) === r @test AbstractUnitRange{BigInt}(r2) === r2 if v"1.5" < VERSION @test OrdinalRange{Int,Int}(r2) === r @test OrdinalRange{BigInt,BigInt}(r2) === r2 end end @testset "Bool IdOffsetRange (issue #223)" begin for b1 in [false, true], b2 in [false, true] r = IdOffsetRange(b1:b2) @test first(r) === b1 @test last(r) === b2 end @test_throws ArgumentError IdOffsetRange(true:true, true) @test_throws ArgumentError IdOffsetRange{Bool,UnitRange{Bool}}(true:true, true) @test_throws ArgumentError IdOffsetRange{Bool,IdOffsetRange{Bool,UnitRange{Bool}}}(IdOffsetRange(true:true), true) end @testset "Logical indexing" begin @testset "indexing with a single bool" begin r = IdOffsetRange(1:2) @test_throws ArgumentError r[true] @test_throws ArgumentError r[false] end @testset "indexing with a Bool UnitRange" begin r = IdOffsetRange(1:0) @test r[true:false] == 1:0 @test r[true:false] == collect(r)[true:false] @test_throws BoundsError r[true:true] @test_throws BoundsError r[false:false] @test_throws BoundsError r[false:true] r = IdOffsetRange(1:1) @test r[true:true] == 1:1 @test r[true:true] == collect(r)[true:true] @test r[false:false] == 1:0 @test r[false:false] == collect(r)[false:false] @test_throws BoundsError r[true:false] @test_throws BoundsError r[false:true] r = IdOffsetRange(1:2) @test r[false:true] == 2:2 @test r[false:true] == collect(r)[false:true] @test_throws BoundsError r[true:true] @test_throws BoundsError r[true:false] @test_throws BoundsError r[false:false] end @testset "indexing with a Bool IdOffsetRange" begin # bounds-checking requires the axes of the indices to match that of the array function testlogicalindexing(r, r2) r3 = r[r2]; @test no_offset_view(r3) == collect(r)[collect(r2)] end r = IdOffsetRange(10:9) r2 = IdOffsetRange(true:false) testlogicalindexing(r, r2) r = IdOffsetRange(10:10) r2 = IdOffsetRange(false:false) testlogicalindexing(r, r2) r2 = IdOffsetRange(true:true) testlogicalindexing(r, r2) r = IdOffsetRange(10:10, 1) r2 = IdOffsetRange(false:false, 1) # effectively true:true with indices 2:2 testlogicalindexing(r, r2) r = IdOffsetRange(10:11) r2 = IdOffsetRange(false:true) testlogicalindexing(r, r2) end @testset "indexing with a Bool StepRange" begin r = IdOffsetRange(1:0) @test r[true:true:false] == 1:1:0 @test_throws BoundsError r[true:true:true] @test_throws BoundsError r[false:true:false] @test_throws BoundsError r[false:true:true] r = IdOffsetRange(1:1) @test r[true:true:true] == 1:1:1 @test r[true:true:true] == collect(r)[true:true:true] @test axes(r[true:true:true], 1) == 1:1 @test r[false:true:false] == 1:1:0 @test r[false:true:false] == collect(r)[false:true:false] # StepRange{Bool,Int} s = StepRange(true, 1, true) @test r[s] == 1:1:1 @test r[s] == collect(r)[s] s = StepRange(true, 2, true) @test r[s] == 1:1:1 @test r[s] == collect(r)[s] s = StepRange(false, 1, false) @test r[s] == 1:1:0 @test r[s] == collect(r)[s] s = StepRange(false, 2, false) @test r[s] == 1:1:0 @test r[s] == collect(r)[s] @test_throws BoundsError r[true:true:false] @test_throws BoundsError r[false:true:true] r = IdOffsetRange(1:2) @test r[false:true:true] == 2:1:2 @test r[false:true:true] == collect(r)[false:true:true] # StepRange{Bool,Int} s = StepRange(false, 1, true) @test r[s] == 2:1:2 @test r[s] == collect(r)[s] @test_throws BoundsError r[true:true:true] @test_throws BoundsError r[true:true:false] @test_throws BoundsError r[false:true:false] end end @testset "iteration" begin # parent has Base.OneTo axes A = ones(4:10) ax = axes(A, 1) ind, st = iterate(ax) @test A[ind] == A[4] ind, st = iterate(ax, st) @test A[ind] == A[5] # parent doesn't have Base.OneTo axes B = @view A[:] C = OffsetArray(B, 0) ax = axes(C, 1) ind, st = iterate(ax) @test C[ind] == C[4] ind, st = iterate(ax, st) @test C[ind] == C[5] end end # used in testing the constructor struct WeirdInteger{T} <: Integer x :: T end # assume that it doesn't behave as expected Base.Int(a::WeirdInteger) = a @testset "Constructors" begin @testset "Single-entry arrays in dims 0:5" begin for n = 0:5 for z in (OffsetArray(ones(Int,ntuple(d->1,n)), ntuple(x->x-1,n)), fill!(OffsetArray{Float64}(undef, ntuple(x->x:x, n)), 1), fill!(OffsetArray{Float64}(undef, ntuple(x->x:x, n)...), 1), fill!(OffsetArray{Float64,n}(undef, ntuple(x->x:x, n)), 1), fill!(OffsetArray{Float64,n}(undef, ntuple(x->x:x, n)...), 1)) @test length(LinearIndices(z)) == 1 @test no_offset_axes(z) == ntuple(x->x:x, n) @test z[1] == 1 end end a0 = reshape([3]) a = OffsetArray(a0) @test axes(a) == () @test ndims(a) == 0 @test a[] == 3 @test a === OffsetArray(a, ()) @test_throws ArgumentError OffsetArray(a, 0) @test_throws ArgumentError OffsetArray(a0, 0) end @testset "OffsetVector" begin # initialization one_based_axes = Any[ (Base.OneTo(4), ), (1:4, ), (big(1):big(4), ), (CartesianIndex(1):CartesianIndex(4), ), (IdentityUnitRange(1:4), ), (IdOffsetRange(1:4),), (IdOffsetRange(3:6, -2),) ] offset_axes = Any[ (-1:2, ), (big(-1):big(2), ), (CartesianIndex(-1):CartesianIndex(2), ), (IdentityUnitRange(-1:2), ), (IdOffsetRange(-1:2),), (IdOffsetRange(3:6, -4),) ] offsets = size.(one_based_axes[1], 1) offsets_big = map(big, offsets) for inds in Any[offsets, offsets_big, one_based_axes...] # test indices API a = OffsetVector{Float64}(undef, inds) @test eltype(a) === Float64 @test axes(a) === axes(OffsetVector{Float64}(undef, inds...)) === axes(OffsetArray{Float64, 1}(undef, inds)) === axes(OffsetArray{Float64}(undef, inds)) @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), ) @test a.offsets === (0, ) @test axes(a.parent) == (Base.OneTo(4), ) a = OffsetVector{Nothing}(nothing, inds) @test eltype(a) === Nothing @test axes(a) === axes(OffsetVector{Nothing}(nothing, inds...)) === axes(OffsetArray{Nothing, 1}(nothing, inds)) @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), ) a = OffsetVector{Missing}(missing, inds) @test eltype(a) === Missing @test axes(a) === axes(OffsetVector{Missing}(missing, inds...)) === axes(OffsetArray{Missing, 1}(missing, inds)) @test axes(a) === (IdOffsetRange(Base.OneTo(4), 0), ) end # nested OffsetVectors for inds in Any[offsets, offsets_big] a = OffsetVector{Float64}(undef, inds) b = OffsetVector(a, inds); b2 = OffsetVector(a, inds...); @test eltype(b) === eltype(b2) === Float64 @test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4) end # offset indexing for inds in offset_axes # test offsets a = OffsetVector{Float64}(undef, inds) ax = (IdOffsetRange(Base.OneTo(4), -2), ) @test a.offsets === (-2, ) @test axes(a.parent) == (Base.OneTo(4), ) @test axes(a) === ax a = OffsetVector{Nothing}(nothing, inds) @test axes(a) === ax a = OffsetVector{Missing}(missing, inds) @test axes(a) === ax for (T, t) in Any[(Nothing, nothing), (Missing, missing)] a = OffsetVector{Union{T, Vector{Int}}}(undef, inds) @test !isassigned(a, -1) @test eltype(a) === Union{T, Vector{Int}} @test axes(a) === ax a = OffsetVector{Union{T, Vector{Int}}}(t, inds) @test a[-1] === t end end @test_throws Union{ArgumentError, ErrorException} OffsetVector{Float64}(undef, -2) # only positive number works # convenient constructors a = rand(4) for inds in offset_axes oa1 = OffsetVector(a, inds...) oa2 = OffsetVector(a, inds) oa3 = OffsetArray(a, inds...) oa4 = OffsetArray(a, inds) @test oa1 === oa2 === oa3 === oa4 @test axes(oa1) === (IdOffsetRange(Base.OneTo(4), -2), ) @test parent(oa1) === a @test oa1.offsets === (-2, ) end oa = OffsetArray(a, :) @test oa === OffsetArray(a, (:, )) === OffsetArray(a, axes(a)) === OffsetVector(a, :) === OffsetVector(a, axes(a)) @test oa == a @test axes(oa) == axes(a) @test axes(oa) !== axes(a) # nested offset array a = rand(4) oa = OffsetArray(a, -1) for inds in Any[.-oa.offsets, one_based_axes...] ooa = OffsetArray(oa, inds) @test typeof(parent(ooa)) <: Vector @test ooa === OffsetArray(oa, inds...) === OffsetVector(oa, inds) === OffsetVector(oa, inds...) @test ooa == a @test axes(ooa) == axes(a) @test axes(ooa) !== axes(a) end # overflow bounds check v = rand(5) @test axes(OffsetVector(v, typemax(Int)-length(v))) == (IdOffsetRange(axes(v)[1], typemax(Int)-length(v)), ) @test_throws OverflowError OffsetVector(v, typemax(Int)-length(v)+1) ao = OffsetArray(v, typemin(Int)) ao2 = OffsetArray{Float64, 1, typeof(ao)}(ao, (-1, )) @test no_offset_axes(ao2, 1) == typemin(Int) .+ (0:length(v)-1) ao2 = OffsetArray(ao, (-1,)) @test no_offset_axes(ao2, 1) == typemin(Int) .+ (0:length(v)-1) @test_throws OverflowError OffsetArray{Float64, 1, typeof(ao)}(ao, (-2, )) # inner Constructor @test_throws OverflowError OffsetArray(ao, (-2, )) # convenient constructor accumulate offsets @test_throws OverflowError OffsetVector(1:0, typemax(Int)) @test_throws OverflowError OffsetVector(OffsetVector(1:0, 0), typemax(Int)) @test_throws OverflowError OffsetArray(zeros(Int, typemax(Int):typemax(Int)), 2) @test_throws OverflowError OffsetArray(v, OffsetArrays.Origin(typemax(Int))) b = OffsetArray(OffsetArray(big(1):2, 1), typemax(Int)-1) @test no_offset_axes(b, 1) == big(typemax(Int)) .+ (1:2) @testset "OffsetRange" begin for r in Any[1:100, big(1):big(2)] a = OffsetVector(r, 4) @test first(r) in a @test !(last(r) + 1 in a) end @testset "BigInt axes" begin r = OffsetArray(1:big(2)^65, 4000) @test eltype(r) === BigInt @test no_offset_axes(r, 1) == (big(1):big(2)^65) .+ 4000 end end # disallow OffsetVector(::Array{<:Any, N}, offsets) where N != 1 @test_throws ArgumentError OffsetVector(zeros(2,2), (2, 2)) @test_throws ArgumentError OffsetVector(zeros(2,2), 2, 2) @test_throws ArgumentError OffsetVector(zeros(2,2), (1:2, 1:2)) @test_throws ArgumentError OffsetVector(zeros(2,2), 1:2, 1:2) @test_throws ArgumentError OffsetVector(zeros(), ()) @test_throws ArgumentError OffsetVector(zeros()) @test_throws ArgumentError OffsetVector(zeros(2,2), ()) @test_throws ArgumentError OffsetVector(zeros(2,2)) @test_throws ArgumentError OffsetVector(zeros(2,2), 2) @test_throws ArgumentError OffsetVector(zeros(2,2), (2,)) @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), 2, 3) @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), (2, 4)) @test_throws ArgumentError OffsetVector(zeros(2:3,2:3), ()) @test_throws ArgumentError OffsetVector(zeros(2:3,2:3)) # eltype of an OffsetArray should match that of the parent (issue #162) @test_throws TypeError OffsetVector{Float64,Vector{ComplexF64}} # ndim of an OffsetArray should match that of the parent @test_throws TypeError OffsetVector{Float64,Matrix{Float64}} end @testset "OffsetMatrix" begin # initialization one_based_axes = Any[ (Base.OneTo(4), Base.OneTo(3)), (1:4, 1:3), (big(1):big(4), big(1):big(3)), (CartesianIndex(1, 1):CartesianIndex(4, 3), ), (CartesianIndex(1):CartesianIndex(4), CartesianIndex(1):CartesianIndex(3)), (CartesianIndex(1):CartesianIndex(4), 1:3), (IdentityUnitRange(1:4), IdentityUnitRange(1:3)), (IdOffsetRange(1:4), IdOffsetRange(1:3)), (IdOffsetRange(3:6, -2), IdOffsetRange(3:5, -2)), (IdOffsetRange(3:6, -2), IdentityUnitRange(1:3)), (IdOffsetRange(3:6, -2), 1:3), ] offset_axes = Any[ (-1:2, 0:2), (big(-1):big(2), big(0):big(2)), (CartesianIndex(-1, 0):CartesianIndex(2, 2), ), (-1:2, CartesianIndex(0):CartesianIndex(2)), (CartesianIndex(-1):CartesianIndex(2), CartesianIndex(0):CartesianIndex(2)), (CartesianIndex(-1):CartesianIndex(2), 0:2), (IdentityUnitRange(-1:2), 0:2), (IdOffsetRange(-1:2), IdOffsetRange(0:2)), (IdOffsetRange(3:6, -4), IdOffsetRange(2:4, -2)), (IdOffsetRange(3:6, -4), IdentityUnitRange(0:2)), (IdOffsetRange(-1:2), 0:2), ] offsets = size.(one_based_axes[1], 1) offsets_big = map(big, offsets) for inds in Any[offsets, offsets_big, one_based_axes...] # test API a = OffsetMatrix{Float64}(undef, inds) ax = (IdOffsetRange(Base.OneTo(4), 0), IdOffsetRange(Base.OneTo(3), 0)) @test eltype(a) === Float64 @test axes(a) === axes(OffsetMatrix{Float64}(undef, inds...)) === axes(OffsetArray{Float64, 2}(undef, inds)) === axes(OffsetArray{Float64, 2}(undef, inds...)) === axes(OffsetArray{Float64}(undef, inds)) @test axes(a) === ax @test a.offsets === (0, 0) @test axes(a.parent) == (Base.OneTo(4), Base.OneTo(3)) a = OffsetMatrix{Nothing}(nothing, inds) @test eltype(a) === Nothing @test axes(a) === axes(OffsetMatrix{Nothing}(nothing, inds...)) === axes(OffsetArray{Nothing, 2}(nothing, inds)) === axes(OffsetArray{Nothing, 2}(nothing, inds...)) @test axes(a) === ax a = OffsetMatrix{Missing}(missing, inds) @test eltype(a) === Missing @test axes(a) === axes(OffsetMatrix{Missing}(missing, inds...)) === axes(OffsetArray{Missing, 2}(missing, inds)) === axes(OffsetArray{Missing, 2}(missing, inds...)) @test axes(a) === ax end @test_throws Union{ArgumentError, ErrorException} OffsetMatrix{Float64}(undef, 2, -2) # only positive numbers works # nested OffsetMatrices for inds in Any[offsets, offsets_big] a = OffsetMatrix{Float64}(undef, inds) b = OffsetMatrix(a, inds); b2 = OffsetMatrix(a, inds...); @test eltype(b) === eltype(b2) === Float64 @test axes(b, 1) === axes(b2, 1) === IdOffsetRange(Base.OneTo(4), 4) @test axes(b, 2) === axes(b2, 2) === IdOffsetRange(Base.OneTo(3), 3) end for inds in offset_axes # test offsets a = OffsetMatrix{Float64}(undef, inds) ax = (IdOffsetRange(Base.OneTo(4), -2), IdOffsetRange(Base.OneTo(3), -1)) @test a.offsets === (-2, -1) @test axes(a.parent) == (Base.OneTo(4), Base.OneTo(3)) @test axes(a) === ax a = OffsetMatrix{Nothing}(nothing, inds) @test axes(a) === ax a = OffsetMatrix{Missing}(missing, inds) @test axes(a) === ax for (T, t) in Any[(Nothing, nothing), (Missing, missing)] a = OffsetMatrix{Union{T, Vector{Int}}}(undef, inds) @test !isassigned(a, -1, 0) @test eltype(a) === Union{T, Vector{Int}} @test axes(a) === ax a = OffsetMatrix{Union{T, Vector{Int}}}(t, inds) @test a[-1, 0] === t end end # convenient constructors a = rand(4, 3) for inds in offset_axes ax = (IdOffsetRange(Base.OneTo(4), -2), IdOffsetRange(Base.OneTo(3), -1)) oa1 = OffsetMatrix(a, inds...) oa2 = OffsetMatrix(a, inds) oa3 = OffsetArray(a, inds...) oa4 = OffsetArray(a, inds) @test oa1 === oa2 === oa3 === oa4 @test axes(oa1) === ax @test parent(oa1) === a @test oa1.offsets === (-2, -1) end oa = OffsetArray(a, :, axes(a, 2)) @test oa === OffsetArray(a, (axes(oa, 1), :)) === OffsetArray(a, axes(a)) === OffsetMatrix(a, (axes(oa, 1), :)) === OffsetMatrix(a, axes(a)) @test oa == a @test axes(oa) == axes(a) @test axes(oa) !== axes(a) oa = OffsetMatrix(a, :, 2:4) @test oa === OffsetMatrix(a, axes(a, 1), 2:4) === OffsetMatrix(a, (axes(oa, 1), 2:4)) # nested offset array a = rand(4, 3) oa = OffsetArray(a, -1, -2) for inds in Any[.-oa.offsets, one_based_axes...] ooa = OffsetArray(oa, inds) @test ooa === OffsetArray(oa, inds...) === OffsetMatrix(oa, inds) === OffsetMatrix(oa, inds...) @test typeof(parent(ooa)) <: Matrix @test ooa == a @test axes(ooa) == axes(a) @test axes(ooa) !== axes(a) end # overflow bounds check a = rand(4, 3) @test axes(OffsetMatrix(a, typemax(Int)-size(a, 1), 0)) == (IdOffsetRange(axes(a)[1], typemax(Int)-size(a, 1)), axes(a, 2)) @test_throws OverflowError OffsetMatrix(a, typemax(Int)-size(a,1)+1, 0) @test_throws OverflowError OffsetMatrix(a, 0, typemax(Int)-size(a, 2)+1) # disallow OffsetMatrix(::Array{<:Any, N}, offsets) where N != 2 @test_throws ArgumentError OffsetMatrix(zeros(2), (2,)) @test_throws ArgumentError OffsetMatrix(zeros(2), 2) @test_throws ArgumentError OffsetMatrix(zeros(2), (1:2,)) @test_throws ArgumentError OffsetMatrix(zeros(2), 1:2) @test_throws ArgumentError OffsetMatrix(zeros(), ()) @test_throws ArgumentError OffsetMatrix(zeros()) @test_throws ArgumentError OffsetMatrix(zeros(2), ()) @test_throws ArgumentError OffsetMatrix(zeros(2)) @test_throws ArgumentError OffsetMatrix(zeros(2), (1, 2)) @test_throws ArgumentError OffsetMatrix(zeros(2), 1, 2) @test_throws ArgumentError OffsetMatrix(zeros(2:3), (2,)) @test_throws ArgumentError OffsetMatrix(zeros(2:3), 2) @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), (2,0,0)) @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), 2,0,0) @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2), ()) @test_throws ArgumentError OffsetMatrix(zeros(2:3, 1:2, 1:2)) # eltype of an OffsetArray should match that of the parent (issue #162) @test_throws TypeError OffsetMatrix{Float64,Matrix{ComplexF64}} # ndim of an OffsetArray should match that of the parent @test_throws TypeError OffsetMatrix{Float64,Vector{Float64}} end # no need to duplicate the 2D case here, # only add some special test cases @testset "OffsetArray" begin a = rand(2, 2, 2) oa = OffsetArray(a, 0:1, 3:4, 2:3) @test OffsetArray(a, CartesianIndices(axes(oa))) == oa @test no_offset_axes(OffsetArray(a, :, CartesianIndices((3:4, 2:3)))) == (1:2, 3:4, 2:3) @test no_offset_axes(OffsetArray(a, 10:11, CartesianIndices((3:4, 2:3)) )) == (10:11, 3:4, 2:3) @test no_offset_axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), :)) == (3:4, 2:3, 1:2) @test no_offset_axes(OffsetArray(a, CartesianIndices((3:4, 2:3)), 10:11)) == (3:4, 2:3, 10:11) @test no_offset_axes(OffsetArray(a, :, :, CartesianIndices((3:4,)) )) == (1:2, 1:2, 3:4) @test no_offset_axes(OffsetArray(a, 10:11, :, CartesianIndices((3:4,)) )) == (10:11, 1:2, 3:4) @test no_offset_axes(OffsetArray(a, 10:11, 2:3, CartesianIndices((3:4,)) )) == (10:11, 2:3, 3:4) # ignore empty CartesianIndices @test OffsetArray(a, CartesianIndices(()), 0:1, :, 2:3) == OffsetArray(a, 0:1, :, 2:3) @test OffsetArray(a, 0:1, CartesianIndices(()), :, 2:3) == OffsetArray(a, 0:1, :, 2:3) @test OffsetArray(a, 0:1, :, CartesianIndices(()), 2:3) == OffsetArray(a, 0:1, :, 2:3) @test OffsetArray(a, 0:1, :, 2:3, CartesianIndices(())) == OffsetArray(a, 0:1, :, 2:3) # nested OffsetArrays for offsets in [(1,1,1), big.((1,1,1))] ob = OffsetArray(oa, offsets); ob2 = OffsetArray(oa, offsets...); @test eltype(ob) === eltype(ob2) === Float64 @test axes(ob, 1) === axes(ob2, 1) === IdOffsetRange(Base.OneTo(2), 0) @test axes(ob, 2) === axes(ob2, 2) === IdOffsetRange(Base.OneTo(2), 3) @test axes(ob, 3) === axes(ob2, 3) === IdOffsetRange(Base.OneTo(2), 2) end indices = (-1:1, -7:7, -1:2, -5:5, -1:1, -3:3, -2:2, -1:1) y = OffsetArray{Float64}(undef, indices...); @test axes(y) === axes(OffsetArray{Float64}(undef, indices)) @test axes(y) === axes(OffsetArray{Float64, length(indices)}(undef, indices...)) @test no_offset_axes(y) == (-1:1, -7:7, -1:2, -5:5, -1:1, -3:3, -2:2, -1:1) @test eltype(y) === Float64 @test_throws ArgumentError OffsetArray{Float64, 2}(undef, indices) @test_throws ArgumentError OffsetArray(y, indices[1:2]) @test ndims(OffsetArray(zeros(), ())) == 0 @test Base.axes1(OffsetArray(zeros(), ())) === OffsetArrays.IdOffsetRange(Base.OneTo(1)) @testset "convenience constructors" begin ax = (2:3, 4:5) for f in (zeros, ones) a = f(Float64, ax) @test no_offset_axes(a) == ax @test eltype(a) == Float64 end for f in (trues, falses) a = f(ax) @test no_offset_axes(a) == ax @test eltype(a) == Bool end end # eltype of an OffsetArray should match that of the parent (issue #162) @test_throws TypeError OffsetArray{Float64,2,Matrix{ComplexF64}} # ndim of an OffsetArray should match that of the parent @test_throws TypeError OffsetArray{Float64,3,Matrix{Float64}} # should throw a TypeError if the offsets can not be converted to Ints @test_throws TypeError OffsetVector{Int,Vector{Int}}(zeros(Int,2), (WeirdInteger(1),)) end @testset "custom range types" begin @testset "EllipsisNotation" begin @testset "Vector" begin v = rand(5) @test axes(OffsetArray(v, ..)) == axes(v) @test OffsetArray(v, ..) == OffsetArray(v, :) @test axes(OffsetVector(v, ..)) == axes(v) @test OffsetVector(v, ..) == OffsetVector(v, :) @test no_offset_axes(OffsetArray(v, .., 2:6)) == (2:6, ) @test OffsetArray(v, .., 2:6) == OffsetArray(v, 2:6) @test no_offset_axes(OffsetVector(v, .., 2:6)) == (2:6, ) @test OffsetVector(v, .., 2:6) == OffsetVector(v, 2:6) end @testset "Matrix" begin m = rand(2, 2) @test axes(OffsetArray(m, ..)) == axes(m) @test OffsetArray(m, ..) == OffsetArray(m, :, :) @test axes(OffsetMatrix(m, ..)) == axes(m) @test OffsetMatrix(m, ..) == OffsetMatrix(m, :, :) @test no_offset_axes(OffsetArray(m, .., 2:3)) == (axes(m, 1), 2:3) @test OffsetArray(m, .., 2:3) == OffsetArray(m, :, 2:3) @test no_offset_axes(OffsetMatrix(m, .., 2:3)) == (axes(m, 1), 2:3) @test OffsetMatrix(m, .., 2:3) == OffsetMatrix(m, :, 2:3) @test no_offset_axes(OffsetArray(m, .., 2:3, 3:4)) == (2:3, 3:4) @test OffsetArray(m, .., 2:3, 3:4) == OffsetArray(m, 2:3, 3:4) @test no_offset_axes(OffsetMatrix(m, .., 2:3, 3:4)) == (2:3, 3:4) @test OffsetMatrix(m, .., 2:3, 3:4) == OffsetMatrix(m, 2:3, 3:4) end @testset "3D Array" begin a = rand(2, 2, 2) @test axes(OffsetArray(a, ..)) == axes(a) @test OffsetArray(a, ..) == OffsetArray(a, :, :, :) @test no_offset_axes(OffsetArray(a, .., 2:3)) == (axes(a)[1:2]..., 2:3) @test OffsetArray(a, .., 2:3) == OffsetArray(a, :, :, 2:3) @test no_offset_axes(OffsetArray(a, .., 2:3, 3:4)) == (axes(a, 1), 2:3, 3:4) @test OffsetArray(a, .., 2:3, 3:4) == OffsetArray(a, :, 2:3, 3:4) @test no_offset_axes(OffsetArray(a, 2:3, .., 3:4)) == (2:3, axes(a, 2), 3:4) @test OffsetArray(a, 2:3, .., 3:4) == OffsetArray(a, 2:3, :, 3:4) @test no_offset_axes(OffsetArray(a, .., 4:5, 2:3, 3:4)) == (4:5, 2:3, 3:4) @test OffsetArray(a, .., 4:5, 2:3, 3:4) == OffsetArray(a, 4:5, 2:3, 3:4) end end @testset "ZeroBasedIndexing" begin Base.to_indices(A, inds, ::Tuple{ZeroBasedIndexing}) = map(x -> 0:length(x) - 1, inds) a = zeros(3,3) oa = OffsetArray(a, ZeroBasedIndexing()) @test no_offset_axes(oa) == (0:2, 0:2) end @testset "TupleOfRanges" begin Base.to_indices(A, inds, t::Tuple{TupleOfRanges{N}}) where {N} = t OffsetArrays.AxisConversionStyle(::Type{TupleOfRanges{N}}) where {N} = OffsetArrays.TupleOfRanges() Base.convert(::Type{Tuple{Vararg{AbstractUnitRange{Int}}}}, t::TupleOfRanges) = t.x a = zeros(3,3) inds = TupleOfRanges((3:5, 2:4)) oa = OffsetArray(a, inds) @test no_offset_axes(oa) == inds.x end @testset "NewColon" begin Base.to_indices(A, inds, t::Tuple{NewColon,Vararg{Any}}) = (_uncolon(inds, t), to_indices(A, Base.tail(inds), Base.tail(t))...) _uncolon(inds::Tuple{}, I::Tuple{NewColon, Vararg{Any}}) = OneTo(1) _uncolon(inds::Tuple, I::Tuple{NewColon, Vararg{Any}}) = inds[1] a = zeros(3, 3) oa = OffsetArray(a, (NewColon(), 2:4)) @test no_offset_axes(oa) == (axes(a,1), 2:4) end end @testset "Offset range construction" begin r = -2:5 for AT in Any[OffsetArray, OffsetVector] y = AT(r, r) @test no_offset_axes(y) == (r,) @test step(y) == step(r) y = AT(r, (r,)) @test no_offset_axes(y) == (r,) y = AT(r, CartesianIndices((r, ))) @test no_offset_axes(y) == (r, ) end end @testset "size/length" begin for p in Any[SA[1,2,3,4], 1:4, [1:4;]] for A in Any[OffsetArray(p, 4), OffsetArray(reshape(p, 2, 2), 3, 4), OffsetArray(reshape(p, 2, 1, 2), 3, 0, 4), OffsetArray(reshape(p, Val(1)), 2)] @test size(A) == size(parent(A)) @test length(A) == length(parent(A)) end end end end @testset "Axes supplied to constructor correspond to final result" begin # Ref https://github.com/JuliaArrays/OffsetArrays.jl/pull/65#issuecomment-457181268 B = BidirectionalVector([1, 2, 3], -2) A = OffsetArray(B, -1:1) @test no_offset_axes(A) == (-1:1,) end @testset "unwrap" begin for A in [ones(2, 2), ones(2:3, 2:3), ZeroBasedRange(1:4)] p, f = OffsetArrays.unwrap(A) @test f(map(y -> y^2, p)) == A.^2 end end @testset "Traits" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) # IndexLinear S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) # IndexCartesian @test axes(A) === axes(S) @test no_offset_axes(A) == no_offset_axes(S) == (0:1, 3:4) @test axes(A, 1) === OffsetArrays.IdOffsetRange(Base.OneTo(2), -1) @test size(A) == size(A0) @test size(A, 1) == size(A0, 1) @test length(A) == length(A0) @test A == OffsetArray(A0, 0:1, 3:4) @test_throws DimensionMismatch OffsetArray(A0, 0:2, 3:4) @test_throws DimensionMismatch OffsetArray(A0, 0:1, 2:4) @test eachindex(IndexLinear(), A) == eachindex(IndexLinear(), parent(A)) @test eachindex(IndexCartesian(), A) == CartesianIndices(A) == CartesianIndices(axes(A)) @test eachindex(S) == eachindex(IndexCartesian(), S) == CartesianIndices(S) @test eachindex(IndexLinear(), S) == eachindex(IndexLinear(), A0) A = ones(5:6) @test eachindex(IndexLinear(), A) === axes(A, 1) A = OffsetArray(big(1):big(2), 1) B = OffsetArray(1:2, 1) @test CartesianIndices(A) == CartesianIndices(B) @test LinearIndices(A) == LinearIndices(B) @test eachindex(A) == eachindex(B) end @testset "Scalar indexing" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) @test @inferred(A[0,3]) == @inferred(A[0,3,1]) == @inferred(A[1]) == @inferred(S[0,3]) == @inferred(S[0,3,1]) == @inferred(S[1]) == 1 @test A[1,3] == A[1,3,1] == A[2] == S[1,3] == S[1,3,1] == S[2] == 2 @test A[0,4] == A[0,4,1] == A[3] == S[0,4] == S[0,4,1] == S[3] == 3 @test A[1,4] == A[1,4,1] == A[4] == S[1,4] == S[1,4,1] == S[4] == 4 @test @inbounds(A[0,3]) == @inbounds(A[0,3,1]) == @inbounds(A[1]) == @inbounds(S[0,3]) == @inbounds(S[0,3,1]) == @inbounds(S[1]) == 1 @test @inbounds(A[1,3]) == @inbounds(A[1,3,1]) == @inbounds(A[2]) == @inbounds(S[1,3]) == @inbounds(S[1,3,1]) == @inbounds(S[2]) == 2 @test @inbounds(A[0,4]) == @inbounds(A[0,4,1]) == @inbounds(A[3]) == @inbounds(S[0,4]) == @inbounds(S[0,4,1]) == @inbounds(S[3]) == 3 @test @inbounds(A[1,4]) == @inbounds(A[1,4,1]) == @inbounds(A[4]) == @inbounds(S[1,4]) == @inbounds(S[1,4,1]) == @inbounds(S[4]) == 4 @test_throws BoundsError(A, (1,1)) A[1,1] @test_throws BoundsError(A, (1,1)) A[1,1] = 4 @test_throws BoundsError(S, (1,1)) S[1,1] @test_throws BoundsError(S, (1,1)) S[1,1] = 4 @test_throws BoundsError(A, (0,3,2)) A[0,3,2] @test_throws BoundsError(A, (0,3,2)) A[0,3,2] = 4 @test_throws BoundsError(A, (0,3,0)) A[0,3,0] @test_throws BoundsError(A, (0,3,0)) A[0,3,0] = 4 Ac = copy(A) Ac[0,3] = 10 @test Ac[0,3] == 10 Ac[0,3,1] = 11 @test Ac[0,3] == 11 @inbounds Ac[0,3,1] = 12 @test Ac[0,3] == 12 y = OffsetArray{Float64}(undef, -1:1, -7:7, -3:-1, -5:5, -1:1, -3:3, -2:2, -1:1) y[-1,-7,-3,-5,-1,-3,-2,-1] = 14 y[-1,-7,-3,-5,-1,-3,-2,-1] += 5 @test y[-1,-7,-3,-5,-1,-3,-2,-1] == 19 @testset "setindex!" begin A = OffsetArray(ones(2,2), 1:2, 1:2) @test setindex!(A, 2, 1, 1) === A @test A[1,1] == 2 @test setindex!(A, 2, 1) === A @test A[1] == 2 v = OffsetArray(ones(3), 4:6) @test setindex!(A, 2, 4) === A @test A[4] == 2 end @testset "Zero-index indexing (#194)" begin @test OffsetArray([6], 2:2)[] == 6 @test OffsetArray(fill(6, 1, 1), 2:2, 3:3)[] == 6 @test OffsetArray(fill(6))[] == 6 @test_throws BoundsError OffsetArray([6,7], 2:3)[] @test_throws BoundsError OffsetArray([6 7], 2:2, 2:3)[] @test_throws BoundsError OffsetArray([], 2:1)[] end end _comp(x::Integer, y::Integer) = x == y _comp(x::Any, y::Any) = isapprox(Real(x), Real(y), atol = 1e-14, rtol = 1e-8) function test_indexing_axes_and_vals(r1, r2) r12 = r1[r2] if axes(r12, 1) != axes(r2, 1) @show r1 r2 r12 axes(r12, 1) axes(r2, 1) end @test axes(r12, 1) == axes(r2, 1) if axes(r12, 1) == axes(r2, 1) res1 = try _comp(first(r12), r1[first(r2)]) catch @show r1 r2 rethrow() end res2 = try _comp(last(r12), r1[last(r2)]) catch @show r1 r2 rethrow() end if !(res1 & res2) @show r1 r2 end @test res1 @test res2 for i in eachindex(r2) @test _comp(r12[i], r1[r2[i]]) end end end @testset "Vector indexing" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) @test A[:, 3] == S[:, 3] == OffsetArray([1,2], (A.offsets[1],)) @test A[:, 4] == S[:, 4] == OffsetArray([3,4], (A.offsets[1],)) @test_throws BoundsError A[:, 1] @test_throws BoundsError S[:, 1] @test A[0, :] == S[0, :] == OffsetArray([1,3], (A.offsets[2],)) @test A[1, :] == S[1, :] == OffsetArray([2,4], (A.offsets[2],)) @test_throws BoundsError A[2, :] @test_throws BoundsError S[2, :] @test A[0:1, 3] == S[0:1, 3] == [1,2] @test A[[1,0], 3] == S[[1,0], 3] == [2,1] @test A[0, 3:4] == S[0, 3:4] == [1,3] @test A[1, [4,3]] == S[1, [4,3]] == [4,2] @test A[:, :] == S[:, :] == A # Indexing a nD OffsetArray with n colons preserves the type r1 = OffsetArray(IdentityUnitRange(100:1000), 3) @test r1[:] === r1 # In general with more colons than dimensions, # the type might not be preserved but the values and the leading axes should be r2 = r1[:,:] @test axes(r2, 1) == axes(r1, 1) @test same_value(r1, r2) s = @SVector[i for i in 1:3] so = OffsetArray(s, 3) @test so[:] === so a = Ones(3, 2, 1) ao = OffsetArray(a, axes(a)) @test ao[:,:,:] === ao @test same_value(ao[:], ao) @test same_value(ao[:,:], ao) # Indexing an nD OffsetArray with one Colon preserves only the values. # This uses linear indexing a = ones(2:3, 2:3) b = a[:] @test same_value(a, b) vals = (1,2,3,4,5,6,7,8) s = SArray{Tuple{2,2,2},Int,3,8}(vals) so = OffsetArray(s, axes(s)); so2 = so[:] @test same_value(so2, s) # Test r1[inds] for various combinations of types # AbstractArrays with 1-based indices indslist1 = Any[ OffsetArray(5:8, 0), # This currently errors for IdentityUnitRange # see https://github.com/JuliaLang/julia/issues/39997 # OffsetArray(big(5):big(80), 0), OffsetArray(5:2:9, 0), OffsetArray(9:-2:5, 0), OffsetArray(IdentityUnitRange(5:8), -4), OffsetArray(IdOffsetRange(5:8), 0), ] # AbstractRanges with 1-based indices indslist2 = Any[ 5:8, # This currently errors for IdentityUnitRange # see https://github.com/JuliaLang/julia/issues/39997 # big(5):big(80), 5:2:9, 9:-2:5, IdOffsetRange(5:8), IdOffsetRange(ZeroBasedUnitRange(4:7), 1), ] for r1 in Any[ # AbstractArrays collect(1:100), reshape(collect(-1:100), -1:100), collect(reshape(1:400, 20, 20)), reshape(collect(1:21^2), -10:10, -10:10), # OffsetRanges OffsetArray(10:1000, 0), # 1-based index OffsetArray(UnitRange(10.0, 1000.0), 0), # 1-based index OffsetArray(10:3:1000, 3), # offset index OffsetArray(10.0:3:1000.0, 0), # 1-based index OffsetArray(10.0:3:1000.0, 3), # offset index OffsetArray(IdOffsetRange(10:1000, 1), -1), # 1-based index OffsetArray(IdOffsetRange(10:1000, 1), 3), # offset index OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -4), 1), 3), # 1-based index OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -1), 1), 3), # offset index # AbstractRanges Base.OneTo(1000), CustomRange(Base.OneTo(1000)), Slice(Base.OneTo(1000)), 1:1000, UnitRange(1.0, 1000.0), 1:3:1000, 1000:-3:1, 1.0:3.0:1000.0, StepRangeLen(Float64(1), Float64(1000), 1000), LinRange(1, 1000, 1000), Base.Slice(Base.OneTo(1000)), # 1-based index IdentityUnitRange(Base.OneTo(1000)), # 1-based index IdOffsetRange(Base.OneTo(1000)), # 1-based index IdentityUnitRange(2:1000), # offset index IdOffsetRange(ZeroBasedUnitRange(1:1000), 1), # 1-based index IdOffsetRange(ZeroBasedUnitRange(1:1000), 2), # offset index ZeroBasedUnitRange(1:1000), # offset range ZeroBasedRange(1:1000), # offset range ZeroBasedRange(1:1:1000), # offset range CustomRange(ZeroBasedRange(1:1:1000)), # offset range ] # AbstractArrays with 1-based indices for r2 in indslist1 test_indexing_axes_and_vals(r1, r2) test_indexing_axes_and_vals(r1, collect(r2)) end # AbstractRanges with 1-based indices for r2 in indslist2 test_indexing_axes_and_vals(r1, r2) test_indexing_axes_and_vals(r1, collect(r2)) if r1 isa AbstractRange && !(r1 isa CustomRange) && axes(r2, 1) isa Base.OneTo @test r1[r2] isa AbstractRange end end end # Indexing with IdentityUnitRange(::Base.OneTo) or Base.Slice(::OneTo) is special. # This is because axes(::IdentityUnitRange{<:Base.OneTo}, 1) isa Base.OneTo, and not an IdentityUnitRange. # These therefore may pass through no_offset_view unchanged. # This had led to a stack-overflow in indexing, as getindex was using no_offset_view. # Issue 209 for r1 in Any[ # This set of tests is for ranges r1 that have 1-based indices UnitRange(1.0, 99.0), 1:99, Base.OneTo(99), 1:1:99, 99:-1:1, 1.0:1.0:99.0, StepRangeLen(Float64(1), Float64(99), 99), LinRange(1, 99, 99), Base.Slice(Base.OneTo(99)), IdentityUnitRange(Base.OneTo(99)), IdOffsetRange(Base.OneTo(99)), ] for r2 in Any[ IdentityUnitRange(Base.OneTo(3)), Base.Slice(Base.OneTo(3)), IdOffsetRange(Base.OneTo(3)), ] test_indexing_axes_and_vals(r1, r2) test_indexing_axes_and_vals(r1, collect(r2)) if axes(r2, 1) isa Base.OneTo @test r1[r2] isa AbstractRange end end end end @testset "Vector indexing with offset ranges" begin r = OffsetArray(8:10, -1:1) r1 = r[0:1] @test r1 === 9:10 r1 = (8:10)[OffsetArray(1:2, -5:-4)] @test no_offset_axes(r1) == (-5:-4,) @test no_offset_view(r1) == 8:9 r1 = OffsetArray(8:10, -1:1)[OffsetArray(0:1, -5:-4)] @test no_offset_axes(r1) == (-5:-4,) @test no_offset_view(r1) == 9:10 a = OffsetVector(3:4, 10:11) ax = OffsetArrays.IdOffsetRange(5:6, 5) @test axes(a[ax]) == axes(ax) for i in axes(ax,1) @test a[ax[i]] == a[ax][i] end ax = IdentityUnitRange(10:11) @test axes(a[ax]) == axes(ax) for i in axes(ax,1) @test a[ax[i]] == a[ax][i] end # AbstractArrays with offset axes indslist1 = Any[OffsetArray(5:9, 40), OffsetArray(5:2:9, 40), OffsetArray(9:-2:5, 40), OffsetArray(IdentityUnitRange(5:8), 2), OffsetArray(IdOffsetRange(5:8, 1), 3), OffsetArray(IdOffsetRange(IdOffsetRange(5:8, 4), 1), 3), OffsetArray(IdOffsetRange(IdentityUnitRange(5:8), 1), 3), OffsetArray(IdentityUnitRange(IdOffsetRange(5:8, 1)), 3), ] # AbstractRanges with offset axes indslist2 = Any[IdOffsetRange(5:8, 1), IdentityUnitRange(5:8), IdOffsetRange(Base.OneTo(3), 4), IdOffsetRange(IdOffsetRange(5:8, 2), 1), IdOffsetRange(IdOffsetRange(IdOffsetRange(5:8, -1), 2), 1), IdentityUnitRange(IdOffsetRange(1:4, 5)), IdOffsetRange(IdentityUnitRange(15:20), -2), ZeroBasedUnitRange(5:8), ZeroBasedRange(5:8), ZeroBasedRange(5:2:9), ZeroBasedRange(9:-2:5), ] for r1 in Any[ # AbstractArrays collect(1:100), reshape(collect(-1:100), -1:100), collect(reshape(1:400, 20, 20)), reshape(collect(1:21^2), -10:10, -10:10), # OffsetRanges OffsetArray(10:1000, 0), # 1-based index OffsetArray(UnitRange(10.0, 1000.0), 0), # 1-based index OffsetArray(10:1000, 3), # offset index OffsetArray(10:3:1000, 0), # 1-based index OffsetArray(10:3:1000, 3), # offset index OffsetArray(10.0:3:1000.0, 0), # 1-based index OffsetArray(10.0:3:1000.0, 3), # offset index OffsetArray(IdOffsetRange(10:1000, -3), 3), # 1-based index OffsetArray(IdOffsetRange(10:1000, 1), 3), # offset index OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -4), 1), 3), # 1-based index OffsetArray(IdOffsetRange(IdOffsetRange(10:1000, -1), 1), 3), # offset index # AbstractRanges Base.OneTo(1000), Slice(Base.OneTo(1000)), CustomRange(Base.OneTo(1000)), 1:1000, UnitRange(1.0, 1000.0), 1:2:2000, 2000:-1:1, 1.0:2.0:2000.0, StepRangeLen(Float64(1), Float64(1000), 1000), LinRange(1.0, 2000.0, 2000), Base.Slice(Base.OneTo(1000)), # 1-based index IdOffsetRange(Base.OneTo(1000)), # 1-based index IdOffsetRange(1:1000, 0), # 1-based index IdOffsetRange(Base.OneTo(1000), 4), # offset index IdOffsetRange(1:1000, 4), # offset index IdOffsetRange(ZeroBasedUnitRange(1:1000), 1), # 1-based index IdOffsetRange(ZeroBasedUnitRange(1:1000), 2), # offset index IdentityUnitRange(ZeroBasedUnitRange(1:1000)), # 1-based index IdentityUnitRange(5:1000), # offset index ZeroBasedUnitRange(1:1000), # offset index ZeroBasedRange(1:1000), # offset index ZeroBasedRange(1:1:1000), # offset index ZeroBasedUnitRange(IdentityUnitRange(1:1000)), # offset index CustomRange(ZeroBasedUnitRange(IdentityUnitRange(1:1000))), # offset index ] # AbstractArrays with offset axes for r2 in indslist1 test_indexing_axes_and_vals(r1, r2) r2_dense = OffsetArray(collect(r2), axes(r2)) test_indexing_axes_and_vals(r1, r2_dense) end # AbstractRanges with offset axes for r2 in indslist2 test_indexing_axes_and_vals(r1, r2) r2_dense = OffsetArray(collect(r2), axes(r2)) test_indexing_axes_and_vals(r1, r2_dense) # This might not hold for all ranges, but holds for the known ones being tested here if r1 isa AbstractUnitRange{<:Integer} && r2 isa AbstractUnitRange{<:Integer} @test r1[r2] isa AbstractUnitRange{<:Integer} end end end end @testset "LinearIndexing" begin r = OffsetArray(ZeroBasedRange(3:4), 1); @test LinearIndices(r) == axes(r,1) r = OffsetArray(ZeroBasedRange(3:4), 2); @test LinearIndices(r) == axes(r,1) end @testset "CartesianIndexing" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) @test A[CartesianIndex((0,3))] == S[CartesianIndex((0,3))] == 1 @test A[CartesianIndex((0,3)),1] == S[CartesianIndex((0,3)),1] == 1 @test @inbounds(A[CartesianIndex((0,3))]) == @inbounds(S[CartesianIndex((0,3))]) == 1 @test @inbounds(A[CartesianIndex((0,3)),1]) == @inbounds(S[CartesianIndex((0,3)),1]) == 1 @test_throws BoundsError A[CartesianIndex(1,1)] @test_throws BoundsError A[CartesianIndex(1,1),0] @test_throws BoundsError A[CartesianIndex(1,1),2] @test_throws BoundsError S[CartesianIndex(1,1)] @test_throws BoundsError S[CartesianIndex(1,1),0] @test_throws BoundsError S[CartesianIndex(1,1),2] @test eachindex(A) == 1:4 @test eachindex(S) == CartesianIndices(IdentityUnitRange.((0:1,3:4))) end @testset "IdentityUnitRange indexing" begin # 155 a = OffsetVector(3:4, 2:3) ax = IdentityUnitRange(2:3) @test a[ax[2]] == a[ax][2] s = -2:2:4 r = 5:8 y = OffsetArray(s, r) @test no_offset_axes(y) == (r,) @test step(y) == step(s) a = OffsetVector(3:4, 10:11) ax = OffsetArrays.IdOffsetRange(5:6, 5) @test axes(a[ax]) == axes(ax) for i in axes(ax,1) @test a[ax[i]] == a[ax][i] end ax = IdentityUnitRange(10:11) @test axes(a[ax]) == axes(ax) for i in axes(ax,1) @test a[ax[i]] == a[ax][i] end end @testset "view" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) S = view(A, :, 3) @test S == OffsetArray([1,2], (A.offsets[1],)) @test S[0] == 1 @test S[1] == 2 @test_throws BoundsError S[2] @test axes(S) == (IdentityUnitRange(0:1),) S = view(A, 0, :) @test S == OffsetArray([1,3], (A.offsets[2],)) @test S[3] == 1 @test S[4] == 3 @test_throws BoundsError S[1] @test axes(S) == (IdentityUnitRange(3:4),) S = view(A, 0:0, 4) @test S == [3] @test S[1] == 3 @test_throws BoundsError S[0] @test axes(S) === (Base.OneTo(1),) S = view(A, 1, 3:4) @test S == [2,4] @test S[1] == 2 @test S[2] == 4 @test_throws BoundsError S[3] @test axes(S) === (Base.OneTo(2),) S = view(A, :, :) @test S == A @test S[0,3] == S[1] == 1 @test S[1,3] == S[2] == 2 @test S[0,4] == S[3] == 3 @test S[1,4] == S[4] == 4 @test_throws BoundsError S[1,1] @test axes(S) == IdentityUnitRange.((0:1, 3:4)) S = view(A, axes(A)...) @test S == A @test S[0,3] == S[1] == 1 @test S[1,3] == S[2] == 2 @test S[0,4] == S[3] == 3 @test S[1,4] == S[4] == 4 @test_throws BoundsError S[1,1] @test no_offset_axes(S) == (0:1, 3:4) # issue 100 S = view(A, axes(A, 1), 3) @test S == A[:, 3] @test S[0] == 1 @test S[1] == 2 @test_throws BoundsError S[length(S)] @test no_offset_axes(S) == (0:1, ) # issue 100 S = view(A, 1, axes(A, 2)) @test S == A[1, :] @test S[3] == 2 @test S[4] == 4 @test_throws BoundsError S[1] @test no_offset_axes(S) == (3:4, ) # issue 133 r = OffsetArrays.IdOffsetRange(1:2, -1) v1 = view(A, r, 3) @test v1[0] == 1 @test v1[1] == 2 @test axes(v1, 1) == axes(r, 1) v2 = view(A, UnitRange(r), 3) for (indflat, indoffset) in enumerate(r) @test v1[indoffset] == v2[indflat] end # issue 133 r = OffsetArrays.IdOffsetRange(1:2, 2) v1 = view(A, 1, r) @test v1[3] == 2 @test v1[4] == 4 @test axes(v1, 1) == axes(r, 1) v2 = view(A, 1, UnitRange(r)) for (indflat, indoffset) in enumerate(r) @test v1[indoffset] == v2[indflat] end # issue 133 a12 = zeros(3:8, 3:4) r = OffsetArrays.IdOffsetRange(Base.OneTo(3), 5) a12[r, 4] .= 3 @test all(a12[r, 4] .== 3) @test all(a12[UnitRange(r), 4] .== 3) A0 = collect(reshape(1:24, 2, 3, 4)) A = OffsetArray(A0, (-1,2,1)) S = view(A, axes(A, 1), 3:4, axes(A, 3)) @test S == A[:, 3:4, :] @test S[0, 1, 2] == A[0, 3, 2] @test S[0, 2, 2] == A[0, 4, 2] @test S[1, 1, 2] == A[1, 3, 2] @test no_offset_axes(S) == (0:1, Base.OneTo(2), 2:5) # issue #186 a = reshape(1:12, 3, 4) r = OffsetArrays.IdOffsetRange(3:4) av = view(a, :, r) @test av == a[:, 3:4] @test axes(av) == (axes(a,1), axes(r,1)) r = OffsetArrays.IdOffsetRange(1:2,2) av = view(a, :, r) @test no_offset_view(av) == a[:, 3:4] @test axes(av) == (axes(a,1), axes(r,1)) r = OffsetArrays.IdOffsetRange(2:3) av1d = view(a, r, 3) @test av1d == a[2:3, 3] @test axes(av1d) == (axes(r,1),) r = OffsetArrays.IdOffsetRange(Base.OneTo(2), 1) av1d = view(a, r, 3) @test no_offset_view(av1d) == a[2:3, 3] @test axes(av1d) == (axes(r,1),) # fix IdOffsetRange(::IdOffsetRange, offset) nesting from #178 b = 1:20 bov = OffsetArray(view(b, 3:4), 3:4) c = @view b[bov] @test same_value(c, 3:4) @test no_offset_axes(c,1) == 3:4 d = OffsetArray(c, 1:2) @test same_value(d, c) @test axes(d,1) == 1:2 # Issue 128 a = OffsetArray(1:3, 0:2); b = @view a[0] @test b[] == b[1] == 1 a = reshape(1:16, 4, 4); for ax1 in (:, axes(a,1), UnitRange(axes(a,1))), ax2 in (:, axes(a,2), UnitRange(axes(a,2))) av = @view a[ax1, ax2] av_nooffset = OffsetArrays.no_offset_view(av) @test axes(av_nooffset) === axes(av) end end @testset "iteration" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) let a for (a,d) in zip(A, A0) @test a == d end end v = ones(10) for r in Any[1:1:10, 1:10], s in Any[r, collect(r)] so = OffsetArray(s) @test Float64[v[i] for i in s] == Float64[v[i] for i in so] end end @testset "show/summary" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) S = OffsetArray(view(A0, 1:2, 1:2), (-1,2)) @test sprint(show, A) == "[1 3; 2 4]" @test sprint(show, S) == "[1 3; 2 4]" strs = split(strip(sprint(show, MIME("text/plain"), A)), '\n') @test strs[2] == " 1 3" @test strs[3] == " 2 4" v = OffsetArray(rand(3), (-2,)) @test sprint(show, v) == sprint(show, parent(v)) io = IOBuffer() function cmp_showf(printfunc, io, A) ioc = IOContext(io, :limit=>true, :compact=>true) printfunc(ioc, A) str1 = String(take!(io)) printfunc(ioc, parent(A)) str2 = String(take!(io)) @test str1 == str2 end cmp_showf(Base.print_matrix, io, OffsetArray(rand(5,5), (10,-9))) # rows&cols fit cmp_showf(Base.print_matrix, io, OffsetArray(rand(10^3,5), (10,-9))) # columns fit cmp_showf(Base.print_matrix, io, OffsetArray(rand(5,10^3), (10,-9))) # rows fit cmp_showf(Base.print_matrix, io, OffsetArray(rand(10^3,10^3), (10,-9))) # neither fits a = OffsetArray([1 2; 3 4], -1:0, 5:6) shownsz = VERSION >= v"1.2.0-DEV.229" ? Base.dims2string(size(a))*' ' : "" @test summary(a) == "$(shownsz)OffsetArray(::$(typeof(parent(a))), -1:0, 5:6) with eltype $(Int) with indices -1:0×5:6" shownsz = VERSION >= v"1.2.0-DEV.229" ? Base.dims2string(size(view(a, :, 5)))*' ' : "" @test summary(view(a, :, 5)) == "$(shownsz)view(OffsetArray(::$(typeof(parent(a))), -1:0, 5:6), :, 5) with eltype $(Int) with indices -1:0" a = OffsetArray(reshape([1])) @test summary(a) == "0-dimensional OffsetArray(::$(typeof(parent(a)))) with eltype $(Int)" a = OffsetArray([1 2; 3 4], -1:0, 5:6) io = IOBuffer() show(io, axes(a, 1)) @test String(take!(io)) == "IdOffsetRange(values=-1:0, indices=-1:0)" # not qualified because of the using OffsetArrays: IdOffsetRange at top show(io, axes(a, 2)) @test String(take!(io)) == "IdOffsetRange(values=5:6, indices=5:6)" rrtable = IdOffsetRange(values=7:9, indices=-1:1) rrted = eval(Meta.parse(string(rrtable))) @test pairs(rrtable) == pairs(rrted) @test Base.inds2string(axes(a)) == Base.inds2string(map(UnitRange, axes(a))) @test Base.inds2string((IdOffsetRange(3:4),)) == "3:4" @test Base.inds2string((IdentityUnitRange(IdOffsetRange(3:4)),)) == "3:4" # check that the following doesn't throw @test Base.inds2string(()) isa Any show(io, OffsetArray(3:5, 0:2)) @test String(take!(io)) == "3:5 with indices 0:2" show(io, MIME"text/plain"(), OffsetArray(3:5, 0:2)) @test String(take!(io)) == "3:5 with indices 0:2" # issue #198 for r in Any[axes(OffsetVector(1:10, -5), 1), 1:1:2, 1.0:1.0:2.0, 1:-1:-5] a = OffsetVector(r, 5) show(io, a) @test String(take!(io)) == "$r with indices $(UnitRange(axes(a,1)))" end d = Diagonal([1,2,3]) Base.print_array(io, d) s1 = String(take!(io)) od = OffsetArray(d, -1:1, 3:5) Base.print_array(io, od) s2 = String(take!(io)) @test s1 == s2 @test Base.replace_in_print_matrix(od, -1, 3, " ") == Base.replace_in_print_matrix(d, 1, 1, " ") @test Base.replace_in_print_matrix(od, -1, 4, " ") == Base.replace_in_print_matrix(d, 1, 2, " ") v = rand(3) ov = OffsetArray(v, (-2,)) @test Base.replace_in_print_matrix(ov, -1, 1, " ") == Base.replace_in_print_matrix(v, 1, 1, " ") # Avoid returning the value of toplevel if it is false # showarg should only print values, it shouldn't return anything @test Base.showarg(io, a, false) === nothing # check the other case too for good measure @test Base.showarg(io, a, true) === nothing end @testset "readdlm/writedlm" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) io = IOBuffer() writedlm(io, A) seek(io, 0) @test readdlm(io, eltype(A)) == parent(A) end @testset "similar" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) B = similar(A, Float32) @test isa(B, OffsetArray{Float32,2}) @test axes(B) === axes(A) B = similar(A, (3,4)) @test isa(B, Array{Int,2}) @test size(B) == (3,4) @test axes(B) === (Base.OneTo(3), Base.OneTo(4)) B = similar(A, (-3:3,1:4)) @test isa(B, OffsetArray{Int,2}) @test axes(B) == IdentityUnitRange.((-3:3, 1:4)) B = similar(parent(A), (-3:3,1:4)) @test isa(B, OffsetArray{Int,2}) @test axes(B) == IdentityUnitRange.((-3:3, 1:4)) @test isa([x for x in [1,2,3]], Vector{Int}) @test similar(Array{Int}, (0:0, 0:0)) isa OffsetArray{Int, 2} @test similar(Array{Int}, (1, 1)) isa Matrix{Int} @test similar(Array{Int}, (Base.OneTo(1), Base.OneTo(1))) isa Matrix{Int} B = similar(Array{Int}, (0:0, 3)) @test isa(B, OffsetArray{Int, 2}) @test no_offset_axes(B) == (0:0, 1:3) s = @SVector[i for i in 1:10] so = OffsetArray(s, 4); @test typeof(parent(similar(so))) == typeof(similar(s)) @test typeof(parent(similar(so, eltype(so), axes(so)))) == typeof(similar(s)) s = SArray{Tuple{2,2,2},Int,3,8}((1,2,3,4,5,6,7,8)) so = OffsetArray(s, 0, 0, 0) A = similar(so) @test A isa OffsetArray @test parent(A) isa StaticArray for ax in Any[(axes(s,1), axes(so)[2:3]...), (axes(so,1), axes(s)[2:3]...)] A = similar(so, Int, ax) @test A isa OffsetArray @test parent(A) isa StaticArray @test axes(A) == ax end # check with an unseen axis type A = similar(ones(1), Int, ZeroBasedUnitRange(3:4), 4:5) @test eltype(A) === Int @test no_offset_axes(A) == (3:4, 4:5) A = similar(ones(1), Int, IdOffsetRange(ZeroBasedUnitRange(3:4), 2), 4:5) @test eltype(A) === Int @test no_offset_axes(A) == (5:6, 4:5) A = similar(ones(1), Int, IdOffsetRange(ZeroBasedUnitRange(3:4), 2), 4) @test eltype(A) === Int @test no_offset_axes(A) == (5:6, 1:4) # test for similar(::Type, ax) indsoffset = (IdOffsetRange(SOneTo(2), 2),) A = similar(Array{Int}, indsoffset) @test parent(A) isa StaticArray @test no_offset_axes(A) == (3:4,) A = similar(Array{Int}, (indsoffset..., SOneTo(3))) @test parent(A) isa StaticArray @test no_offset_axes(A) == (3:4, 1:3) s = SArray{Tuple{2,2},Int,2,4}((1,2,3,4)); so = OffsetArray(s, 2, 2); so2 = so .+ 1; @test parent(so2) isa StaticArray @test_throws MethodError similar(A, (:,)) @test_throws MethodError similar(A, (: ,:)) @test_throws MethodError similar(A, (: ,2)) @test_throws MethodError similar(A, Float64, (: ,:)) @test_throws MethodError similar(A, Float64, (: ,2)) function testsimilar(args...) try similar(args...) catch e @test e isa MethodError io = IOBuffer() showerror(io, e) s = split(String(take!(io)),'\n')[1] @test occursin(repr(similar), s) end end testsimilar(typeof(A), (:, :)) testsimilar(typeof(A), (:, 2)) testsimilar(typeof(A), (:, 1:3)) @testset "similar with OffsetArray type (issue #263)" begin for i in Any[[1,2,3], 1:3, SVector{2,Int}(1,2), reshape(1:4, 2, 2)] k = OffsetArray(i, map(x -> -2, size(i))) j = similar(typeof(k), axes(k)) @test axes(j) == axes(k) @test eltype(j) == eltype(k) j = similar(typeof(k), size(k)) @test eltype(j) == eltype(k) @test size(j) == size(k) @test all(==(1), first.(axes(j))) end end end # custom FillArray with BigInt axes, used to test `reshape` struct MyBigFill{T,N} <: AbstractArray{T,N} val :: T axes :: NTuple{N,Base.OneTo{BigInt}} end MyBigFill(val, sz::Tuple{}) = MyBigFill{typeof(val),0}(val, sz) MyBigFill(val, sz::NTuple{N,BigInt}) where {N} = MyBigFill(val, map(Base.OneTo, sz)) MyBigFill(val, sz::Tuple{Vararg{Integer}}) = MyBigFill(val, map(BigInt, sz)) Base.size(M::MyBigFill) = map(length, M.axes) Base.axes(M::MyBigFill) = M.axes function Base.getindex(M::MyBigFill{<:Any,N}, ind::Vararg{Integer,N}) where {N} checkbounds(M, ind...) M.val end function Base.isassigned(M::MyBigFill{<:Any,N}, ind::Vararg{BigInt,N}) where {N} checkbounds(M, ind...) true end function Base.reshape(M::MyBigFill, ind::NTuple{N,BigInt}) where {N} length(M) == prod(ind) || throw(ArgumentError("length mismatch in reshape")) MyBigFill(M.val, ind) end Base.reshape(M::MyBigFill, ind::Tuple{}) = MyBigFill(M.val, ind) @testset "reshape" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) B = reshape(A0, -10:-9, 9:10) @test isa(B, OffsetArray{Int,2}) @test parent(B) == A0 @test axes(B) == IdentityUnitRange.((-10:-9, 9:10)) B = reshape(A, -10:-9, 9:10) @test isa(B, OffsetArray{Int,2}) @test pointer(parent(B)) === pointer(A0) @test axes(B) == IdentityUnitRange.((-10:-9, 9:10)) b = reshape(A, -7:-4) @test axes(b) == (IdentityUnitRange(-7:-4),) @test isa(parent(b), Vector{Int}) @test pointer(parent(b)) === pointer(parent(A)) @test parent(b) == A0[:] a = OffsetArray(rand(3,3,3), -1:1, 0:2, 3:5) # Offset axes are required for reshape(::OffsetArray, ::Val) support b = reshape(a, Val(2)) @test isa(b, OffsetArray{Float64,2}) @test pointer(parent(b)) === pointer(parent(a)) @test axes(b) == IdentityUnitRange.((-1:1, 1:9)) b = reshape(a, Val(4)) @test isa(b, OffsetArray{Float64,4}) @test pointer(parent(b)) === pointer(parent(a)) @test axes(b) == (axes(a)..., IdentityUnitRange(1:1)) @test reshape(OffsetArray(-1:0, -1:0), :, 1) == reshape(-1:0, 2, 1) @test reshape(OffsetArray(-1:2, -1:2), -2:-1, :) == reshape(-1:2, -2:-1, 2) @test reshape(OffsetArray(-1:0, -1:0), :) == OffsetArray(-1:0, -1:0) @test reshape(A, :) == reshape(A0, :) # reshape with one Colon for AbstractArrays B = reshape(A0, -10:-9, :) @test B isa OffsetArray{Int,2} @test parent(B) == A0 @test no_offset_axes(B, 1) == -10:-9 @test axes(B, 2) == axes(A0, 2) B = reshape(A0, -10:-9, 3:3, :) @test B isa OffsetArray{Int,3} @test same_value(A0, B) @test no_offset_axes(B, 1) == -10:-9 @test no_offset_axes(B, 2) == 3:3 @test axes(B, 3) == 1:2 B = reshape(A0, -10:-9, 3:4, :) @test B isa OffsetArray{Int,3} @test same_value(A0, B) @test no_offset_axes(B, 1) == -10:-9 @test no_offset_axes(B, 2) == 3:4 @test axes(B, 3) == 1:1 # pop the parent B = reshape(A, size(A)) @test B == A0 B = reshape(A, (Base.OneTo(2), 2)) @test B == A0 B = reshape(A, (2,:)) @test B == A0 # julialang/julia #33614 A = OffsetArray(-1:0, (-2,)) @test reshape(A, :) == A Arsc = reshape(A, :, 1) Arss = reshape(A, 2, 1) @test Arsc[1,1] == Arss[1,1] == -1 @test Arsc[2,1] == Arss[2,1] == 0 @test_throws BoundsError Arsc[0,1] @test_throws BoundsError Arss[0,1] A = OffsetArray([-1,0], (-2,)) Arsc = reshape(A, :, 1) Arsc[1,1] = 5 @test first(A) == 5 @testset "issue #235" begin Vec64 = zeros(6) ind_a_64 = 3 ind_a_32 =Int32.(ind_a_64) @test reshape(Vec64, ind_a_32, :) == reshape(Vec64, ind_a_64, :) end R = reshape(zeros(6), 2, :) @test R isa Matrix @test axes(R) == (1:2, 1:3) R = reshape(zeros(6,1), 2, :) @test R isa Matrix @test axes(R) == (1:2, 1:3) R = reshape(zeros(6), 1:2, :) @test axes(R) == (1:2, 1:3) R = reshape(zeros(6,1), 1:2, :) @test axes(R) == (1:2, 1:3) r = OffsetArray(ZeroBasedRange(3:4), 1); @test reshape(r, 2) == reshape(r, big(2)) == 3:4 @test reshape(r, (2,)) == reshape(r, (big(2),)) == 3:4 @test reshape(r, :) == 3:4 @test reshape(r, (:,)) == 3:4 @test reshape(r, big(2), 1) == reshape(3:4, 2, 1) # getindex for a reshaped array that wraps an offset array is broken on 1.0 if VERSION >= v"1.1" @test reshape(r, (2,:,4:4)) == OffsetArray(reshape(3:4, 2, 1, 1), 1:2, 1:1, 4:4) end # reshape works even if the parent doesn't have 1-based indices # this works even if the parent doesn't support the reshape r = OffsetArray(IdentityUnitRange(0:1), -1) @test reshape(r, 2) == 0:1 @test reshape(r, (2,)) == 0:1 @test reshape(r, :) == OffsetArray(0:1, -1:0) @test reshape(r, (:,)) == OffsetArray(0:1, -1:0) @test reshape(ones(2:3, 4:5), (2, :)) == ones(2,2) # more than one colon is not allowed @test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, :, 2) @test_throws Exception reshape(ones(3:4, 4:5, 1:2), :, 2, :) A = OffsetArray(rand(4, 4), -1, -1); B = reshape(A, (2, :)) @test axes(B, 1) == 1:2 @test axes(B, 2) == 1:8 # some more exotic vector types r = OffsetVector(CustomRange(ZeroBasedRange(0:2)), -2) r2 = reshape(r, :) @test r2 == r r2 = reshape(r, 3) @test axes(r2, 1) == 1:3 @test r2 == no_offset_view(r) @test_throws Exception reshape(r, length(r) + 1) @test_throws Exception reshape(r, 1:length(r) + 1) rp = parent(r) @test no_offset_axes(reshape(rp, 4:6), 1) == 4:6 @test axes(reshape(r, (3,1))) == (1:3, 1:1) # reshape with one single colon becomes a `vec` A = OffsetArray(rand(4, 4), -1, -1) @test reshape(A, (:, )) == vec(A) @test reshape(A, :) == vec(A) # ensure that there's no ambiguity using AbstractArray and Tuple{Vararg{OffsetAxis}} @test reshape(Fill(0), ()) === Fill(0) # This test is broken currently on julia v"1.12.0-DEV.780" @test try reshape(Fill(2,6), big(2), :) == Fill(2, 2, 3) catch e e isa TypeError || rethrow() end @testset "Tuple{Vararg{Integer}}" begin M = MyBigFill(4, (2, 3)) O = OffsetArray(M) @test vec(O) isa MyBigFill @test vec(O) == vec(M) M = MyBigFill(4, (1,1)) O = OffsetArray(M) @test reshape(O) == reshape(M) @test reshape(O) isa MyBigFill end end @testset "permutedims" begin a = OffsetArray(1:2, 2:3) @test permutedims(a) == reshape(1:2, 1, 2:3) a = OffsetArray([10,11], Base.OneTo(2)) @test permutedims(a) == reshape(10:11, 1, 1:2) a = OffsetArray(SVector{2}(1,2), 3:4) @test permutedims(a) == reshape(1:2, 1, 3:4) # check that the 2D case is unaffected a = OffsetArray(reshape(1:2, 1, 2), 2:2, 4:5) b = permutedims(a) @test a[2,:] == b[:,2] end @testset "Indexing with OffsetArray axes" begin A0 = [1 3; 2 4] i1 = OffsetArray([2,1], (-5,)) i1 = OffsetArray([2,1], -5) b = A0[i1, 1] @test axes(b) == (IdentityUnitRange(-4:-3),) @test b[-4] == 2 @test b[-3] == 1 b = A0[1,i1] @test axes(b) == (IdentityUnitRange(-4:-3),) @test b[-4] == 3 @test b[-3] == 1 v = view(A0, i1, 1) @test axes(v) == (IdentityUnitRange(-4:-3),) v = view(A0, 1:1, i1) @test axes(v) == (Base.OneTo(1), IdentityUnitRange(-4:-3)) for r in (1:10, 1:1:10, StepRangeLen(1, 1, 10), LinRange(1, 10, 10), 0.1:0.2:0.9) for s in (IdentityUnitRange(2:3), OffsetArray(2:3, 2:3)) @test axes(r[s]) == axes(s) end end end @testset "logical indexing" begin A0 = [1 3; 2 4] A = OffsetArray(A0, (-1,2)) @test A[A .> 2] == [3,4] end @testset "copyto!" begin a = OffsetArray{Int}(undef, (-3:-1,)) fill!(a, -1) copyto!(a, (1,2)) # non-array iterables @test a[-3] == 1 @test a[-2] == 2 @test a[-1] == -1 fill!(a, -1) copyto!(a, -2, (1,2)) @test a[-3] == -1 @test a[-2] == 1 @test a[-1] == 2 @test_throws BoundsError copyto!(a, 1, (1,2)) fill!(a, -1) copyto!(a, -2, (1,2,3), 2) @test a[-3] == -1 @test a[-2] == 2 @test a[-1] == 3 @test_throws BoundsError copyto!(a, -2, (1,2,3), 1) fill!(a, -1) copyto!(a, -2, (1,2,3), 1, 2) @test a[-3] == -1 @test a[-2] == 1 @test a[-1] == 2 b = 1:2 # copy between AbstractArrays bo = OffsetArray(1:2, (-3,)) if VERSION < v"1.5-" @test_throws BoundsError copyto!(a, b) fill!(a, -1) copyto!(a, bo) @test a[-3] == -1 @test a[-2] == 1 @test a[-1] == 2 else # the behavior of copyto! is corrected as the documentation says "first n element" # https://github.com/JuliaLang/julia/pull/34049 fill!(a, -1) copyto!(a, bo) @test a[-3] == 1 @test a[-2] == 2 @test a[-1] == -1 end fill!(a, -1) copyto!(a, -2, bo) @test a[-3] == -1 @test a[-2] == 1 @test a[-1] == 2 @test_throws BoundsError copyto!(a, -4, bo) @test_throws BoundsError copyto!(a, -1, bo) fill!(a, -1) copyto!(a, -3, b, 2) @test a[-3] == 2 @test a[-2] == a[-1] == -1 @test_throws BoundsError copyto!(a, -3, b, 1, 4) am = OffsetArray{Int}(undef, (1:1, 7:9)) # for testing linear indexing fill!(am, -1) copyto!(am, b) @test am[1] == 1 @test am[2] == 2 @test am[3] == -1 @test am[1,7] == 1 @test am[1,8] == 2 @test am[1,9] == -1 end @testset "map" begin am = OffsetArray{Int}(undef, (1:1, 7:9)) # for testing linear indexing fill!(am, -1) copyto!(am, 1:2) dest = similar(am) map!(+, dest, am, am) @test dest[1,7] == 2 @test dest[1,8] == 4 @test dest[1,9] == -2 @testset "eltype conversion" begin a = OffsetArray(1:2, 1) b = map(BigInt, a) @test eltype(b) == BigInt @test b == a @test parent(b) isa AbstractRange for ri in Any[2:3, Base.OneTo(2)] for r in Any[IdentityUnitRange(ri), IdOffsetRange(ri), IdOffsetRange(ri, 1), OffsetArray(ri), OffsetArray(ri, 2)] for T in [Int8, Int16, Int32, Int64, Int128, BigInt, Float32, Float64, BigFloat] r2 = map(T, r) @test eltype(r2) == T @test axes(r2) == axes(r) @test all(((x,y),) -> isequal(x,y), zip(r, r2)) end end end @testset "Bool" begin for ri in Any[0:0, 0:1, 1:0, 1:1, Base.OneTo(0), Base.OneTo(1)] for r = Any[IdentityUnitRange(ri), IdOffsetRange(ri), IdOffsetRange(ri .- 1, 1), OffsetVector(ri)] r2 = map(Bool, r) @test eltype(r2) == Bool @test axes(r2) == axes(r) @test all(((x,y),) -> isequal(x,y), zip(r, r2)) end end end end end @testset "reductions" begin A = OffsetArray(rand(Int,4,4), (-3,5)) @test maximum(A) == maximum(parent(A)) @test minimum(A) == minimum(parent(A)) @test extrema(A) == extrema(parent(A)) @test sum(A) == sum(parent(A)) @test sum(A, dims=1) == OffsetArray(sum(parent(A), dims=1), A.offsets) @test sum(A, dims=2) == OffsetArray(sum(parent(A), dims=2), A.offsets) @test sum(A, dims=(1,2)) == OffsetArray(sum(parent(A), dims=(1,2)), A.offsets) @test sum(view(OffsetArray(reshape(1:27, 3, 3, 3), 0, 0, 0), :, :, 1:2), dims=(2,3)) == reshape([51,57,63], 3, 1, 1) C = similar(A) cumsum!(C, A, dims = 1) @test parent(C) == cumsum(parent(A), dims = 1) @test parent(cumsum(A, dims = 1)) == cumsum(parent(A), dims = 1) cumsum!(C, A, dims = 2) @test parent(C) == cumsum(parent(A), dims = 2) R = similar(A, (1:1, 6:9)) maximum!(R, A) @test parent(R) == maximum(parent(A), dims = 1) R = similar(A, (-2:1, 1:1)) maximum!(R, A) @test parent(R) == maximum(parent(A), dims = 2) amin, iamin = findmin(A) pmin, ipmin = findmin(parent(A)) @test amin == pmin @test A[iamin] == amin @test amin == parent(A)[ipmin] amax, iamax = findmax(A) pmax, ipmax = findmax(parent(A)) @test amax == pmax @test A[iamax] == amax @test amax == parent(A)[ipmax] amin, amax = extrema(parent(A)) @test clamp.(A, (amax+amin)/2, amax) == OffsetArray(clamp.(parent(A), (amax+amin)/2, amax), axes(A)) @testset "mapreduce for OffsetRange" begin rangelist = Any[ # AbstractUnitRanges 5:100, UnitRange(5.0, 20.0), false:true, IdOffsetRange(4:5), IdentityUnitRange(4:5), IdOffsetRange(1:10, 4), # AbstractRanges 2:4:14, 1.5:1.0:10.5, ] for r in rangelist a = OffsetVector(r, 2); @test mapreduce(identity, +, a) == mapreduce(identity, +, r) @test mapreduce(x -> x^2, (x,y) -> x, a) == mapreduce(x -> x^2, (x,y) -> x, r) b = mapreduce(identity, +, a, dims = 1) br = mapreduce(identity, +, r, dims = 1) @test no_offset_view(b) == no_offset_view(br) @test no_offset_axes(b, 1) == first(axes(a,1)):first(axes(a,1)) @test mapreduce(identity, +, a, init = 3) == mapreduce(identity, +, r, init = 3) if VERSION >= v"1.2" @test mapreduce((x,y) -> x*y, +, a, a) == mapreduce((x,y) -> x*y, +, r, r) @test mapreduce((x,y) -> x*y, +, a, a, init = 10) == mapreduce((x,y) -> x*y, +, r, r, init = 10) end for f in [sum, minimum, maximum] @test f(a) == f(r) b = f(a, dims = 1); br = f(r, dims = 1) @test no_offset_view(b) == no_offset_view(br) @test no_offset_axes(b, 1) == first(axes(a,1)):first(axes(a,1)) b = f(a, dims = 2); br = f(r, dims = 2) @test no_offset_view(b) == no_offset_view(br) @test axes(b, 1) == axes(a,1) end @test extrema(a) == extrema(r) end end end # v = OffsetArray([1,1e100,1,-1e100], (-3,))*1000 # v2 = OffsetArray([1,-1e100,1,1e100], (5,))*1000 # @test isa(v, OffsetArray) # cv = OffsetArray([1,1e100,1e100,2], (-3,))*1000 # cv2 = OffsetArray([1,-1e100,-1e100,2], (5,))*1000 # @test isequal(cumsum_kbn(v), cv) # @test isequal(cumsum_kbn(v2), cv2) # @test isequal(sum_kbn(v), sum_kbn(parent(v))) @testset "Collections" begin A = OffsetArray(rand(4,4), (-3,5)) @test unique(A, dims=1) == OffsetArray(parent(A), 0, first(axes(A, 2)) - 1) @test unique(A, dims=2) == OffsetArray(parent(A), first(axes(A, 1)) - 1, 0) v = OffsetArray(rand(8), (-2,)) @test sort(v) == OffsetArray(sort(parent(v)), v.offsets) @test sortslices(A; dims=1) == OffsetArray(sortslices(parent(A); dims=1), A.offsets) @test sortslices(A; dims=2) == OffsetArray(sortslices(parent(A); dims=2), A.offsets) @test sort(A, dims = 1) == OffsetArray(sort(parent(A), dims = 1), A.offsets) @test sort(A, dims = 2) == OffsetArray(sort(parent(A), dims = 2), A.offsets) @test mapslices(sort, A, dims = 1) == OffsetArray(mapslices(sort, parent(A), dims = 1), A.offsets) @test mapslices(sort, A, dims = 2) == OffsetArray(mapslices(sort, parent(A), dims = 2), A.offsets) end @testset "rot/reverse" begin A = OffsetArray(rand(4,4), (-3,5)) @test rotl90(A) == OffsetArray(rotl90(parent(A)), A.offsets[[2,1]]) @test rotr90(A) == OffsetArray(rotr90(parent(A)), A.offsets[[2,1]]) @test reverse(A, dims = 1) == OffsetArray(reverse(parent(A), dims = 1), A.offsets) @test reverse(A, dims = 2) == OffsetArray(reverse(parent(A), dims = 2), A.offsets) end @testset "fill" begin B = fill(5, 1:3, -1:1) @test no_offset_axes(B) == (1:3,-1:1) @test all(B.==5) B = fill(5, (1:3, -1:1)) @test no_offset_axes(B) == (1:3,-1:1) @test all(B.==5) B = fill(5, 3, -1:1) @test no_offset_axes(B) == (1:3,-1:1) @test all(B.==5) @testset "fill!" begin D = dzeros((2,2)) DO = OffsetArray(D, 2, 2) fill!(DO, 1) @test all(isequal(1), DO) @test all(iszero, zero(DO)) S = SVector{2,Int}(1,1) SO = OffsetVector(S, -1) @test zero(SO) isa typeof(SO) end end @testset "broadcasting" begin A = OffsetArray(rand(4,4), (-3,5)) @test A.+1 == OffsetArray(parent(A).+1, A.offsets) @test 2*A == OffsetArray(2*parent(A), A.offsets) @test A+A == OffsetArray(parent(A)+parent(A), A.offsets) @test A.*A == OffsetArray(parent(A).*parent(A), A.offsets) end @testset "@inbounds" begin a = OffsetArray(zeros(7), -3:3) unsafe_fill!(x) = @inbounds(for i in axes(x,1); x[i] = i; end) function unsafe_sum(x) s = zero(eltype(x)) @inbounds for i in axes(x,1) s += x[i] end s end unsafe_fill!(a) for i = -3:3 @test a[i] == i end @test unsafe_sum(a) == 0 end @testset "Resizing OffsetVectors" begin local a = OffsetVector(rand(5),-3) axes(a,1) == -2:2 length(a) == 5 resize!(a,3) length(a) == 3 axes(a,1) == -2:0 @test_throws ArgumentError resize!(a,-3) end #### #### type defined for testing no_offset_view #### struct NegativeArray{T,N,S <: AbstractArray{T,N}} <: AbstractArray{T,N} parent::S end # Note: this defines the axes-of-the-axes to be OneTo. # In general this isn't recommended, because # positionof(A, i, j, ...) == map(getindex, axes(A), (i, j, ...)) # is quite desirable, and this requires that the axes be "identity" ranges, i.e., # `r[i] == i`. # Nevertheless it's useful to test this on a "broken" implementation # to make sure we still get the right answer. Base.axes(A::NegativeArray) = map(n -> (-n):(-1), size(A.parent)) Base.size(A::NegativeArray) = size(A.parent) function Base.getindex(A::NegativeArray{T,N}, I::Vararg{Int,N}) where {T,N} getindex(A.parent, (I .+ size(A.parent) .+ 1)...) end struct PointlessWrapper{T,N, A <: AbstractArray{T,N}} <: AbstractArray{T,N} parent :: A end Base.parent(x::PointlessWrapper) = x.parent Base.size(x::PointlessWrapper) = size(parent(x)) Base.axes(x::PointlessWrapper) = axes(parent(x)) Base.getindex(x::PointlessWrapper, i...) = x.parent[i...] @testset "no offset view" begin # OffsetArray fallback A = randn(3, 3) @inferred no_offset_view(A) O1 = OffsetArray(A, -1:1, 0:2) O2 = OffsetArray(O1, -2:0, -3:(-1)) @test no_offset_view(O2) ≡ A @inferred no_offset_view(O1) @inferred no_offset_view(O2) P = PointlessWrapper(A) @test @inferred(no_offset_view(P)) === P @test @inferred(no_offset_view(A)) === A a0 = reshape([1]) @test @inferred(no_offset_view(a0)) === a0 a0v = view(a0) @test @inferred(no_offset_view(a0v)) === a0v # generic fallback A = collect(reshape(1:12, 3, 4)) N = NegativeArray(A) @test N[-3, -4] == 1 V = no_offset_view(N) @test collect(V) == A A = reshape(view([5], 1, 1)) @test no_offset_view(A) == A # bidirectional B = BidirectionalVector([1, 2, 3]) pushfirst!(B, 0) OB = OffsetArrays.no_offset_view(B) @test no_offset_axes(OB, 1) == 1:4 @test collect(OB) == 0:3 # issue #198 offax = axes(OffsetVector(1:10, -5), 1) noffax = OffsetArrays.no_offset_view(offax) @test noffax == -4:5 @test axes(noffax, 1) == 1:10 # ideally covered by the above, but current it isn't @test isa(noffax, AbstractUnitRange) r = Base.OneTo(4) @test OffsetArrays.no_offset_view(r) isa typeof(r) # SubArrays A = reshape(1:12, 3, 4) V = view(A, OffsetArrays.IdentityUnitRange(2:3), OffsetArrays.IdentityUnitRange(2:3)) if collect(V) == [5 8; 6 9] # julia 1.0 has a bug here @test OffsetArrays.no_offset_view(V) == [5 8; 6 9] end V = view(A, OffsetArrays.IdentityUnitRange(2:3), 2) @test V != [5;6] if collect(V) == [5;6] @test OffsetArrays.no_offset_view(V) == [5;6] end O = OffsetArray(A, -1:1, 0:3) V = view(O, 0:1, 1:2) @test V == OffsetArrays.no_offset_view(V) == [5 8; 6 9] r1, r2 = OffsetArrays.IdOffsetRange(1:3, -2), OffsetArrays.IdentityUnitRange(2:3) V = view(O, r1, r2) @test V != collect(V) @test OffsetArrays.no_offset_view(V) == collect(V) V = @view O[:,:] @test IndexStyle(A) == IndexStyle(O) == IndexStyle(V) == IndexStyle(OffsetArrays.no_offset_view(V)) == IndexLinear() @testset "issue #375" begin arr = OffsetArray(reshape(1:15, 3, 5), 2, 3) arr_no_offset = OffsetArrays.no_offset_view(@view arr[:, 4]) @test all(!Base.has_offset_axes, axes(arr_no_offset)) end end @testset "no nesting" begin A = randn(2, 3) x = A[2, 2] O1 = OffsetArray(A, -1:0, -1:1) O2 = OffsetArray(O1, 0:1, 0:2) @test parent(O1) ≡ parent(O2) @test eltype(O1) ≡ eltype(O2) O2[1, 1] = x + 1 # just a sanity check @test A[2, 2] == x + 1 end @testset "mutating functions for OffsetVector" begin # push! o = OffsetVector(Int[], -1) @test push!(o) === o @test no_offset_axes(o, 1) == 0:-1 @test push!(o, 1) === o @test no_offset_axes(o, 1) == 0:0 @test o[end] == 1 @test push!(o, 2, 3) === o @test no_offset_axes(o, 1) == 0:2 @test o[end-1:end] == [2, 3] # pop! o = OffsetVector([1, 2, 3], -1) @test pop!(o) == 3 @test no_offset_axes(o, 1) == 0:1 # append! o = OffsetVector([1, 2, 3], -1) append!(o, [4, 5]) @test no_offset_axes(o, 1) == 0:4 # empty! o = OffsetVector([1, 2, 3], -1) @test empty!(o) === o @test no_offset_axes(o, 1) == 0:-1 end @testset "searchsorted (#85)" begin o = OffsetVector([1,3,4,5],-2) @test searchsortedfirst(o,-2) == -1 @test searchsortedfirst(o, 1) == -1 @test searchsortedfirst(o, 2) == 0 @test searchsortedfirst(o, 5) == 2 @test searchsortedfirst(o, 6) == 3 @test searchsortedlast(o, -2) == -2 @test searchsortedlast(o, 1) == -1 @test searchsortedlast(o, 2) == -1 @test searchsortedlast(o, 5) == 2 @test searchsortedlast(o, 6) == 2 @test searchsorted(o, -2) == -1:-2 @test searchsorted(o, 1) == -1:-1 @test searchsorted(o, 2) == 0:-1 @test searchsorted(o, 5) == 2:2 @test searchsorted(o, 6) == 3:2 if VERSION > v"1.2" # OffsetVector of another offset vector v = OffsetVector(Base.IdentityUnitRange(4:10),-2) @test searchsortedfirst(v, first(v)-1) == firstindex(v) for i in axes(v,1) @test searchsortedfirst(v, v[i]) == i end @test searchsortedfirst(v, last(v)+1) == lastindex(v)+1 @test searchsortedlast(v, first(v)-1) == firstindex(v)-1 for i in axes(v,1) @test searchsortedlast(v, v[i]) == i end @test searchsortedlast(v, last(v)+1) == lastindex(v) @test searchsorted(v, first(v)-1) === firstindex(v) .+ (0:-1) for i in axes(v,1) @test searchsorted(v, v[i]) == i:i end @test searchsorted(v, last(v)+1) === lastindex(v) .+ (1:0) end v = OffsetVector{Float64, OffsetVector{Float64, Vector{Float64}}}(OffsetVector([2,2,3,3,3,4], 3), 4) @test searchsortedfirst(v, minimum(v)-1) == firstindex(v) for el in unique(v) @test searchsortedfirst(v, el) == findfirst(isequal(el), v) end @test searchsortedfirst(v, maximum(v)+1) == lastindex(v)+1 @test searchsortedlast(v, minimum(v)-1) == firstindex(v)-1 for el in unique(v) @test searchsortedlast(v, el) == findlast(isequal(el), v) end @test searchsortedlast(v, maximum(v)+1) == lastindex(v) @test searchsorted(v, minimum(v)-1) === firstindex(v) .+ (0:-1) for el in unique(v) @test searchsorted(v, el) == findfirst(isequal(el), v):findlast(isequal(el), v) end @test searchsorted(v, maximum(v)+1) === lastindex(v) .+ (1:0) soa = OffsetArray([2,2,3], typemax(Int)-3) @test searchsortedfirst(soa, 1) == firstindex(soa) == typemax(Int)-2 @test searchsortedfirst(soa, 2) == firstindex(soa) == typemax(Int)-2 @test searchsortedfirst(soa, 3) == lastindex(soa) == typemax(Int) soa = OffsetArray([2,2,3], typemin(Int)) @test searchsortedlast(soa, 2) == firstindex(soa) + 1 == typemin(Int) + 2 @test searchsortedlast(soa, 3) == lastindex(soa) == typemin(Int) + 3 @test searchsortedlast(soa, 1) == typemin(Int) soa = OffsetArray([2,2,3], typemax(Int)-4) @test searchsorted(soa, 1) === firstindex(soa) .+ (0:-1) @test searchsorted(soa, 2) == firstindex(soa) .+ (0:1) == typemax(Int) .+ (-3:-2) @test searchsorted(soa, 3) == lastindex(soa) .+ (0:0) == typemax(Int) .+ (-1:-1) @test searchsorted(soa, 4) === lastindex(soa) .+ (1:0) soa = OffsetArray([2,2,3], typemax(Int)-3) @test searchsorted(soa, 1) === firstindex(soa) .+ (0:-1) @test searchsorted(soa, 2) == firstindex(soa) .+ (0:1) == typemax(Int) .+ (-2:-1) @test searchsorted(soa, 3) == lastindex(soa) .+ (0:0) == typemax(Int) .+ (0:0) @test searchsorted(soa, 4) === lastindex(soa) .+ (1:0) soa = OffsetArray([2,2,3], typemin(Int)) @test searchsorted(soa, 1) === firstindex(soa) .+ (0:-1) @test searchsorted(soa, 2) == firstindex(soa) .+ (0:1) == typemin(Int) .+ (1:2) @test searchsorted(soa, 3) == lastindex(soa) .+ (0:0) == typemin(Int) .+ (3:3) @test searchsorted(soa, 4) === lastindex(soa) .+ (1:0) end @testset "Adapt" begin # We need another storage type, CUDA.jl defines one but we can't use that for CI # let's define an appropriate method for SArrays Adapt.adapt_storage(::Type{SA}, xs::Array) where SA<:SArray = convert(SA, xs) # ambiguity Adapt.adapt_storage(::Type{SA}, xs::AbstractArray) where SA<:SArray = convert(SA, xs) arr = OffsetArray(rand(3, 3), -1:1, -1:1) s_arr = adapt(SMatrix{3,3}, arr) @test parent(s_arr) isa SArray @test arr == adapt(Array, s_arr) arr2 = OffsetArray(view(rand(5, 5), 2:4, 2:4), -1:1, -1:1) if isdefined(Adapt, :parent_type) @test Adapt.parent_type(typeof(arr)) == typeof(arr.parent) @test Adapt.unwrap_type(typeof(arr)) == typeof(arr.parent) @test Adapt.unwrap_type(typeof(arr2)) == typeof(arr.parent) end end @testset "Pointer" begin a = OffsetVector(collect(10:20), 9); @test 12 == a[12] == unsafe_load(pointer(a), 12 + (1 - firstindex(a))) == unsafe_load(pointer(a, 12)) A = OffsetArray(reshape(collect(10:130), (11,11)), 9, 9); @test 21 == A[12] == unsafe_load(pointer(A), 12) == unsafe_load(pointer(A, 12)) @test 61 == A[52] == unsafe_load(pointer(A), 52) == unsafe_load(pointer(A, 52)) @test pointer(a) === pointer(parent(a)) @test pointer(A) === pointer(parent(A)) @test pointer(a, 12) === pointer(parent(a), 12 + (1 - firstindex(a))) @test pointer(A, 12) === pointer(parent(A), 12) @test pointer(a) === pointer(a, firstindex(a)) @test pointer(A) === pointer(A, firstindex(A)) if VERSION ≥ v"1.5" @test pointer(a') === pointer(parent(a)) @test pointer(A') === pointer(parent(A)) @test pointer(a', 5) === pointer(parent(a), 5) @test pointer(A', 15) === pointer(parent(A)', 15) end @test Base.cconvert(Ptr{eltype(A)}, A) == Base.cconvert(Ptr{eltype(A)}, parent(A)) end # issue 171 struct Foo2 o::OffsetArray{Float64,1,Array{Float64,1}} end @testset "convert" begin d = Diagonal([1,1,1]) M = convert(Matrix{Float64}, d) od = OffsetArray(d, 1, 1) oM = convert(OffsetMatrix{Float64, Matrix{Float64}}, od) @test eltype(oM) == Float64 @test typeof(parent(oM)) == Matrix{Float64} @test oM == od oM2 = convert(OffsetMatrix{Float64, Matrix{Float64}}, d) @test eltype(oM2) == Float64 @test typeof(parent(oM2)) == Matrix{Float64} @test oM2 == d # issue 171 O = zeros(Int, 0:2) F = Foo2(O) @test F.o == O a = [MMatrix{2,2}(1:4) for i = 1:2] oa = [OffsetArray(ai, 0, 0) for ai in a] b = ones(2,2) @test b * a == b * oa for a = [1:4, ones(1:5)] for T in [OffsetArray, OffsetVector, OffsetArray{eltype(a)}, OffsetArray{Float32}, OffsetVector{eltype(a)}, OffsetVector{Float32}, OffsetVector{Float32, Vector{Float32}}, OffsetVector{Float32, OffsetVector{Float32, Vector{Float32}}}, OffsetVector{eltype(a), typeof(a)}, ] @test convert(T, a) isa T @test convert(T, a) == a b = T(a) @test b isa T @test b == a b = T(a, 0) @test b isa T @test b == a b = T(a, axes(a)) @test b isa T @test b == a end a2 = reshape(a, :, 1) for T in [OffsetArray{Float32}, OffsetMatrix{Float32}, OffsetArray{Float32, 2, Matrix{Float32}}] b = T(a2, 0, 0) @test b isa T @test b == a2 b = T(a2, axes(a2)) @test b isa T @test b == a2 b = T(a2, 1, 1) @test no_offset_axes(b) == map((x,y) -> x .+ y, axes(a2), (1,1)) b = T(a2) @test b isa T @test b == a2 end a3 = reshape(a, :, 1, 1) for T in [OffsetArray{Float32}, OffsetArray{Float32, 3}, OffsetArray{Float32, 3, Array{Float32,3}}] b = T(a3, 0, 0, 0) @test b isa T @test b == a3 b = T(a3, axes(a3)) @test b isa T @test b == a3 b = T(a3, 1, 1, 1) @test no_offset_axes(b) == map((x,y) -> x .+ y, axes(a3), (1,1,1)) b = T(a3) @test b isa T @test b == a3 end end a = ones(2:3) b = convert(OffsetArray, a) @test a === b b = convert(OffsetVector, a) @test a === b # test that non-Int offsets work correctly a = 1:4 b1 = OffsetVector{Float64,Vector{Float64}}(a, 2) b2 = OffsetVector{Float64,Vector{Float64}}(a, big(2)) @test b1 == b2 a = ones(2:3) b1 = OffsetArray{Float64, 1, typeof(a)}(a, (-1,)) b2 = OffsetArray{Float64, 1, typeof(a)}(a, (-big(1),)) @test b1 == b2 # test for custom offset arrays a = ZeroBasedRange(1:3) for T in [OffsetVector{Float64, UnitRange{Float64}}, OffsetVector{Int, Vector{Int}}, OffsetVector{Float64,OffsetVector{Float64,UnitRange{Float64}}}, OffsetArray{Int,1,OffsetArray{Int,1,UnitRange{Int}}}, ] b = T(a) @test b isa T @test b == a b = T(a, 2:4) @test b isa T @test no_offset_axes(b, 1) == 2:4 @test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a) b = T(a, 1) @test b isa T @test axes(b, 1) == 1:3 @test OffsetArrays.no_offset_view(b) == OffsetArrays.no_offset_view(a) c = convert(T, a) @test c isa T @test c == a end # test using custom indices a = ones(2,2) for T in [OffsetMatrix{Int}, OffsetMatrix{Float64}, OffsetMatrix{Float64, Matrix{Float64}}, OffsetMatrix{Int, Matrix{Int}}] b = T(a, ZeroBasedIndexing()) @test b isa T @test no_offset_axes(b) == (0:1, 0:1) end # changing the number of dimensions is not permitted A = rand(2,2) @test_throws MethodError convert(OffsetArray{Float64, 3}, A) @test_throws MethodError convert(OffsetArray{Float64, 3, Array{Float64,3}}, A) end @testset "center/centered" begin @testset "center" begin A = reshape(collect(1:9), 3, 3) c = OffsetArrays.center(A) @test c == (2, 2) @test A[c...] == 5 @test OffsetArrays.center(A, RoundDown) == OffsetArrays.center(A, RoundUp) A = reshape(collect(1:6), 2, 3) c = OffsetArrays.center(A) @test OffsetArrays.center(A, RoundDown) == c @test c == (1, 2) @test A[c...] == 3 c = OffsetArrays.center(A, RoundUp) @test c == (2, 2) @test A[c...] == 4 end @testset "centered" begin A = reshape(collect(1:9), 3, 3) Ao = OffsetArrays.centered(A) @test OffsetArrays.centered(Ao) === Ao @test OffsetArrays.centered(Ao, OffsetArrays.center(Ao)) === Ao @test typeof(Ao) <: OffsetArray @test parent(Ao) === A @test Ao.offsets == (-2, -2) @test Ao[0, 0] == 5 A = reshape(collect(1:6), 2, 3) Ao = OffsetArrays.centered(A) @test OffsetArrays.centered(A, OffsetArrays.center(A, RoundDown)) == Ao @test typeof(Ao) <: OffsetArray @test parent(Ao) === A @test Ao.offsets == (-1, -2) @test Ao[0, 0] == 3 Ao = OffsetArrays.centered(A, OffsetArrays.center(A, RoundUp)) @test typeof(Ao) <: OffsetArray @test parent(Ao) === A @test Ao.offsets == (-2, -2) @test Ao[0, 0] == 4 A = reshape(collect(1:9), 3, 3) Ao = OffsetArray(A, -1, -1) Aoo = OffsetArrays.centered(Ao) @test parent(Aoo) === A # there will be only one OffsetArray wrapper @test Aoo.offsets == (-2, -2) @test Aoo[0, 0] == 5 A = reshape(collect(1:9), 3, 3) Aoo = OffsetArrays.centered(A, CartesianIndex(2,2)) c = (0,0) i = CartesianIndex(c...) @test Aoo[i] == Aoo[c...] end end @testset "Conversion to AbstractArray{T}" begin r = 1:4 T = Float64 V = typeof(map(T, r)) v = OffsetVector(r) @test OffsetArray{T}(v) isa OffsetVector{T,V} @test AbstractArray{T}(v) isa OffsetVector{T,V} @test AbstractVector{T}(v) isa OffsetVector{T,V} @test convert(AbstractVector{T}, v) isa OffsetVector{T,V} @test convert(AbstractArray{T}, v) isa OffsetVector{T,V} @test axes(OffsetArray{T}(v)) === axes(v) @test axes(AbstractArray{T}(v)) === axes(v) @test axes(AbstractVector{T}(v)) === axes(v) @test axes(convert(AbstractVector{T}, v)) === axes(v) @test axes(convert(AbstractArray{T}, v)) == axes(v) A = SMatrix{2,2}(1, 0, 0, 1) TA = typeof(map(T, A)) OA = OffsetMatrix(A, 3:4, 5:6) @test OffsetArray{T}(OA) isa OffsetMatrix{T,TA} @test AbstractArray{T}(OA) isa OffsetMatrix{T,TA} @test AbstractMatrix{T}(OA) isa OffsetMatrix{T,TA} @test convert(AbstractMatrix{T}, OA) isa OffsetMatrix{T,TA} @test convert(AbstractArray{T}, OA) isa OffsetMatrix{T,TA} @test axes(OffsetArray{T}(OA)) === axes(OA) @test axes(AbstractArray{T}(OA)) === axes(OA) @test axes(AbstractMatrix{T}(OA)) === axes(OA) @test axes(convert(AbstractMatrix{T}, OA)) === axes(OA) @test axes(convert(AbstractArray{T}, OA)) === axes(OA) end include("origin.jl") @testset "misc" begin @test OffsetArrays._subtractoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int} @test OffsetArrays._subtractoffset(Base.OneTo(2), 1) == 0:1 @test OffsetArrays._subtractoffset(3:2:9, 1) isa AbstractRange{Int} @test OffsetArrays._subtractoffset(3:2:9, 1) == 2:2:8 @test OffsetArrays._addoffset(Base.OneTo(2), 1) isa AbstractUnitRange{Int} @test OffsetArrays._addoffset(Base.OneTo(2), 1) == 2:3 @test OffsetArrays._addoffset(3:2:9, 1) isa AbstractRange{Int} @test OffsetArrays._addoffset(3:2:9, 1) == 4:2:10 end @testset "unsafe_wrap" begin p = Ptr{UInt16}(Libc.malloc(2*3*4*2)) @test unsafe_wrap(OffsetArray, p, 2, 3, 4) isa OffsetArray{UInt16, 3} @test unsafe_wrap(OffsetArray, p, (2, 3, 4)) isa OffsetArray{UInt16, 3} @test unsafe_wrap(OffsetVector, p, 2*3*4) isa OffsetVector{UInt16} @test unsafe_wrap(OffsetMatrix, p, 2*3, 4) isa OffsetMatrix{UInt16} @test unsafe_wrap(OffsetArray{UInt16}, p, 2, 3, 4) isa OffsetArray{UInt16, 3} @test unsafe_wrap(OffsetArray{UInt16}, p, (2, 3, 4)) isa OffsetArray{UInt16, 3} @test unsafe_wrap(OffsetVector{UInt16}, p, 2*3*4) isa OffsetVector{UInt16} @test unsafe_wrap(OffsetMatrix{UInt16}, p, 2*3, 4) isa OffsetMatrix{UInt16} p = Ptr{UInt8}(p) @test unsafe_wrap(OffsetArray, p, 2:3, 3:5, 4:7) isa OffsetArray{UInt8, 3} @test unsafe_wrap(OffsetArray, p, (2:3, 3:5, 4:7)) isa OffsetArray{UInt8, 3} @test unsafe_wrap(OffsetVector, p, 1:(2*3*4) .- 1) isa OffsetVector{UInt8} @test unsafe_wrap(OffsetMatrix, p, 1:(2*3) .+ 6, 4:7) isa OffsetMatrix{UInt8} @test unsafe_wrap(OffsetMatrix, p, -5:5, Base.OneTo(3); own = true) isa OffsetMatrix{UInt8} end @info "Following deprecations are expected" @testset "deprecations" begin A = reshape(collect(1:9), 3, 3) @test OffsetArrays.centered(A, RoundDown) == OffsetArrays.centered(A, RoundUp) end