• Stars
    star
    193
  • Rank 200,827 (Top 4 %)
  • Language
    Julia
  • License
    Other
  • Created over 10 years ago
  • Updated 4 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Fortran-like arrays with arbitrary, zero or negative starting indices.

OffsetArrays.jl

Documentation Build Status Code coverage Version

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 OffsetArrays: by specifying the axes of the array, or by specifying its origin.

The first way to construct an OffsetArray by specifying its axes is:

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> 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> 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> 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> 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> 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> 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 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 and 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.

More Repositories

1

StaticArrays.jl

Statically sized arrays for Julia
Julia
765
star
2

StructArrays.jl

Efficient implementation of struct arrays in Julia
Julia
307
star
3

LazyArrays.jl

Lazy arrays and linear algebra in Julia
Julia
278
star
4

AxisArrays.jl

Performant arrays where each dimension can have a named axis with values
Julia
200
star
5

BlockArrays.jl

BlockArrays for Julia
Julia
193
star
6

FillArrays.jl

Julia package for lazily representing matrices filled with a single entry
Julia
177
star
7

ArrayInterface.jl

Designs for new Base array interface primitives, used widely through scientific machine learning (SciML) and other organizations
Julia
134
star
8

MappedArrays.jl

Lazy in-place transformations of arrays
Julia
82
star
9

TiledIteration.jl

Julia package to facilitate writing mulithreaded, multidimensional, cache-efficient code
Julia
79
star
10

InfiniteArrays.jl

A Julia package for representing infinite-dimensional arrays
Julia
72
star
11

ElasticArrays.jl

Resizeable multi-dimensional arrays for Julia
Julia
59
star
12

HybridArrays.jl

Arrays with both statically and dynamically sized axes in Julia
Julia
55
star
13

ShiftedArrays.jl

Lazy shifted arrays for data analysis in Julia
Julia
49
star
14

PaddedViews.jl

Add virtual padding to the edges of an array
Julia
48
star
15

StructsOfArrays.jl

Structures of Arrays that behave like Arrays of Structures
Julia
48
star
16

BlockDiagonals.jl

Functionality for working efficiently with block diagonal matrices.
Julia
47
star
17

ArraysOfArrays.jl

Efficient storage and handling of nested arrays in Julia
Julia
41
star
18

UnsafeArrays.jl

Stack-allocated pointer-based array views
Julia
39
star
19

MosaicViews.jl

Julia package for lazily viewing a 3D or 4D array as an expanded 2D array in the form of a mosaic of matrix slices
Julia
23
star
20

EndpointRanges.jl

Julia package for doing arithmetic on endpoints in array indexing
Julia
23
star
21

FFTViews.jl

Julia package for fast fourier transforms and periodic views
Julia
20
star
22

StackViews.jl

no more 🐱🐱
Julia
19
star
23

IndirectArrays.jl

Julia implementation of indexed or "lookup" arrays
Julia
19
star
24

ArrayViews.jl

A Julia package to explore a new system of array views
Julia
19
star
25

StaticArraysCore.jl

Interface package for StaticArrays.jl
Julia
16
star
26

StaticArrayInterface.jl

Interface designs for enforcing static computations in array functions with Julia
Julia
14
star
27

FixedSizeArrays.jl

Fixed-size multidimensional arrays. An Array-like type with less indirection at the cost of resizing capability.
Julia
14
star
28

LazyGrids.jl

A Julia package for representing multi-dimensional grids
Julia
13
star
29

ShowItLikeYouBuildIt.jl

Compact display of type information by leveraging constructor syntax
Julia
11
star
30

MetadataArrays.jl

Julia
10
star
31

CustomUnitRanges.jl

Package-specific AbstractUnitRange types for julia
Julia
9
star
32

GetindexArrays.jl

Lazy arrays with arbitrary user-defined transformations
Julia
8
star
33

SpatioTemporalTraits.jl

Traits for arrays that live in space and time
Julia
7
star
34

UnalignedVectors.jl

Create arrays from memory buffers that lack appropriate alignment
Julia
5
star
35

CatIndices.jl

Julia package for indices-aware array concatenation and growth
Julia
4
star
36

AbstractArraysOfArrays.jl

[WIP] Abstract types and interface for nested arrays
4
star
37

RangeArrays.jl

Efficient and convenient array data structures where the columns of the arrays are generated (on the fly) by Ranges.
Julia
4
star
38

IdentityRanges.jl

Ranges that preserve indices of views
Julia
3
star
39

Ranges.jl

Support for LinSpace in Julia 0.5
Julia
3
star
40

OneTwoMany.jl

Tools for element access in Julia
Julia
2
star