https://github.com/emmt/cartesianboxes.jl
Flexible and efficicient multi-dimensional index boxes in Julia
https://github.com/emmt/cartesianboxes.jl
Last synced: 5 months ago
JSON representation
Flexible and efficicient multi-dimensional index boxes in Julia
- Host: GitHub
- URL: https://github.com/emmt/cartesianboxes.jl
- Owner: emmt
- License: other
- Created: 2018-02-14T14:53:03.000Z (over 8 years ago)
- Default Branch: master
- Last Pushed: 2024-06-18T08:49:05.000Z (almost 2 years ago)
- Last Synced: 2025-01-29T05:41:23.523Z (over 1 year ago)
- Language: Julia
- Homepage:
- Size: 83 KB
- Stars: 2
- Watchers: 3
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Flexible and efficient multi-dimensional index boxes in Julia
[](LICENSE.md)
[](https://github.com/emmt/CartesianBoxes.jl/actions/workflows/CI.yml?query=branch%3Amaster)
[](https://ci.appveyor.com/project/emmt/CartesianBoxes-jl/branch/master)
[](http://codecov.io/github/emmt/CartesianBoxes.jl?branch=master)
This module implements `CartesianBox{N}` to define rectangular regions of
`N`-dimensional indices in Julia arrays. Cartesian boxes are similar to
`CartesianIndices` but, being a different type, they can be used to
specifically extend methods without introducing unexpected behaviors in other
Julia modules.
For multi-dimensional loops, instances of `CartesianBox{N}` are as fast as
`CartesianIndices{N}`. They can thus be used as a *fast* and *portable*
replacement (see [*Rationale*](#rationale) below) of these different
representations of rectangular multi-dimensional regions.
## Usage
### Construction
A Cartesian box is created by calling the constructor `CartesianBox(args...)`
with a variety of arguments. For example:
```julia
CartesianBox(A)
```
yields the Cartesian box which contains all indices of array `A`. An arbitrary
region whose first and last multi-dimensional indices are `(imin,jmin,...)` and
`(imax,jmax,...)` can be defined by one of:
```julia
CartesianBox(CartesianIndex(imin,jmin,...), CartesianIndex(imax,jmax,...))
CartesianBox((imin:imax, jmin:jmax, ...))
CartesianBox((imin,jmin,...), (imax,jmax,...))
```
hence
```julia
CartesianBox(axes(A))
```
also defines a Cartesian box containing all indices of array `A`. For normal
arrays (with 1-based indices), it is sufficient to provide the dimensions of
the array:
```julia
CartesianBox(size(A))
CartesianBox((dim1, dim2, ...))
```
This is however not recommended, `CartesianBox(axes(A))` or, for short,
`CartesianBox(A)` are more likely to be correct for any kind of array `A`.
It is also possible to convert an instance `R` of `CartesianIndices` into a
`CartesianBox` by calling the constructor:
```julia
B = CartesianBox(R)
```
The reverse operation is also possible and is a lossless operation as shown by:
```julia
CartesianIndices(B) === R
```
which always holds.
To retrieve the `N`-tuple of ranges that constitute a Cartesian box `B`, call
`Tuple(B)`. This is not the same as `axes(B)` which yields the ranges to index
`B` itself.
```julia
B = CartesianBox(2:7, 3:5)
Tuple(B) -> (2:7, 3:5)
axes(B) -> (Base.OneTo(6),Base.OneTo(3))
```
### Fast (and safe) iterations
An instance of `CartesianBox` can be used in a loop as follows:
```julia
for i in CartesianBox(...)
A[i] = ...
end
```
where `i` will be set to a `CartesianIndex` with all the multi-dimensional
indices of the rectangular region defined by `B`. To benefit from faster loops
you may suppress bound checking and activate
[SIMD](https://fr.wikipedia.org/wiki/Single_instruction_multiple_data)
vectorization:
```julia
@inbounds @simd for i in CartesianBox(...)
A[i] = ...
end
```
When at least one of `A` or `B` is a Cartesian box, the expression `A ∩ B`, or
equivalently `intersect(A,B)`, yields the Cartesian box containing all indices
in `A` and `B`. This may be used to write safe loops like:
```julia
A = ... # some array
B = CartesianBox(...) # some region of interest
@inbounds for i in B ∩ A
A[i] = ...
end
```
to operate on the indices of the Cartesian box `B` that are valid for `A`.
When at least one of `A` or `B` is a Cartesian box, the expression `A ⊆ B`, or
equivalently `intersect(A,B)`, yields whether all Cartesian indices defined by
`A` are also indices of `B`. This may be used as:
```julia
A = ... # some array
B = CartesianBox(...) # some region of interest
if B ⊆ A
@inbounds for i in B
A[i] = ...
end
end
```
to only access the indices of the Cartesian box `B` if they are all valid for
`A`.
### Indexation and views
You may extract the region defined by a Cartesian box `B` from an array `A` by
calling:
```julia
C = A[B]
```
Setting values is also possible with
```julia
A[B] = C
```
where `C` is an array of same dimensions as the region defined by `B`. To fill
the region `B` of array `A` with a scalar `x`, just do:
```julia
fill!(A, B, x) -> A
```
A *view* can be created by:
```julia
V = view(A, B)
```
which yields a sub-array `V` sharing its elements with `A` in the region
defined by `B`.
### Arithmetic operations on a Cartesian box
If `B` is an instance of `CartesianBox{N}`, `-B` reverses the limits of `B`.
Expressions like `B + I` or `B - I` can be used to shift `B`, an instance of
`CartesianBox{N}`, by offset `I` specified as an instance of
`CartesianIndex{N}` or as an `N`-tuple of integers. Expression like `I - B`
shifts and negates the Cartesian box `B`.
### Exported or extended methods
The call:
```julia
intersect(CartesianBox, A, B)
```
yields the Cartesian box given by the intersection of the Cartesian regions
defined by `A` and `B`. In this context, a Cartesian region can specified by a
Cartesian box, a list of integer valued ranges, a list of dimensions, or an
instance of `CartesianIndices`. This method is equivalent to `intersect(A,B)`,
or `A ∩ B` for short, when at least one of `A` or `B` is a Cartesian box.
The call:
```julia
isnonemptypartof(A, B)
```
yields whether the region defined by `A` is nonempty and a valid part of the
region defined by `B` or of the contents of `B` if `B` is an array. This is
equivalent to:
```julia
!isempty(CartesianBox(A)) && (CartesianBox(A) ⊆ CartesianBox(B))
```
except that `A` may not be an array.
The call:
```julia
boundingbox([pred,] A [, B])
```
yields the bounding-box of values in array `A` for which the predicate function
`pred` is true. If the predicate function `pred` is omitted, the result is the
bounding-box of non-zero values in array `A` or of the `true` values in `A` if
its elements are of type `Bool`. Optional argument `B` is to only consider a
sub-region `B` of `A` (`B` can be a `CartesianBox`, a `CartesianIndices`, or a
tuple of integer valued unit ranges).
## Restrictions
* The algorithm for finding the bounding-box of valid values is pretty simple
and scales as `O(length(A))`.
* There is no way to define an *empty* Cartesian box when `N=0`.
## Installation
The easiest way to install `CartesianBoxes` is via Julia registry
[`EmmtRegistry`](https://github.com/emmt/EmmtRegistry):
```julia
using Pkg
pkg"registry add General" # if you have not yet any registries
pkg"registry add https://github.com/emmt/EmmtRegistry"
pkg"add CartesianBoxes"
```
## Rationale
For pre-0.7 Julia versions, rectangular regions of `N`-dimensional indices were
defined by instances of `CartesianRange{CardinalIndex{N}}` in Julia and have a
number of related methods which make coding [multi-dimensional
algorithms](https://julialang.org/blog/2016/02/iteration) not only *easy* but
also *very efficient*. More recent Julia versions (≥ 0.7) introduced a change
in the representation of such sets of multi-dimensional indices which are now
called
[`CartesianIndices{N}`](https://github.com/JuliaLang/julia/issues/20974). There
have been a few changes in the API but, in general, it is sufficient to replace
`CartesianRange{CardinalIndex{N}}` by `CartesianIndices{N}` in the code. For
backward compatibility, [`using
Compat`](https://github.com/JuliaLang/Compat.jl) let you use
`CartesianIndices{N}` with Julia ≤ 0.6. However, while the performances have
been maintained or even improved with `CartesianIndices{N}` in Julia ≥ 0.7
compared to `CartesianRange{CardinalIndex{N}}` in Julia ≤ 0.6, this is not true
if you are using `CartesianIndices{N}` in Julia 0.6 via the
[Compat](https://github.com/JuliaLang/Compat.jl) package. For instance, I
measured (with [BenchmarkTools](http://github.com/JuliaCI/BenchmarkTools.jl))
slowdowns worse than a factor of 30 for simple additions of arrays. My guess is
that this is because [Compat](https://github.com/JuliaLang/Compat.jl) does not
extend `simd_outer_range()`, `simd_inner_length()` nor `simd_index()` methods
for `CartesianIndices{N}`.
Another motivation for this module, was that I wanted to add some
functionalities in a such a way that is does not interfere with how
`CartesianIndices` or `CartesianRange` are used by others.