struct Phase::IndexRegion(T)

Overview

An IndexRegion represents the relationship between the coordinates in a source MultiIndexable and the coordinates of its slicing. For example:

# This is the "source MultiIndexable" referred to above
narr = NArray[['a', 'b', 'c'],
              ['d', 'e', 'f'],
              ['g', 'h', 'i']]

# And this is a possible slicing of it:
sliced = narr[0..2.., 1..]
puts sliced # => [['b', 'c'],
            #     ['h', 'i']]

# An `IndexRegion` is a function from a coordinate in the context of `sliced`
# to a coordinate in the original `NArray`. We can see this by creating an
# `IndexRegion` via the source shape and the slicing operation:
mapping = IndexRegion.new(region_literal: [0..2.., 1..], bound_shape: narr.shape)

# sliced[0, 0] has the same element as narr[0, 1]
puts mapping.get(0, 0) # => [0, 1]

# sliced[1, 1] has the same element as narr[2, 2]
puts mapping.get(1, 1) # => [2, 2]

# We can even print the whole `IndexRegion` to see where
# each element of sliced is coming from:
puts mapping.to_narr
# 2x2 Phase::NArray(Array(Int32))
# [[[0, 1], [0, 2]],
#  [[2, 1], [2, 2]]]

# Phase uses this internally to compute slicing!
narr[mapping] == sliced # => true

Included Modules

Defined in:

index_region.cr

Constant Summary

DROP_BY_DEFAULT = MultiIndexable::DROP_BY_DEFAULT

See MultiIndexable::DROP_BY_DEFAULT

Constructors

Class Method Summary

Instance Method Summary

Instance methods inherited from module Phase::MultiIndexable(Array(T))

%(other : MultiIndexable(U)) forall U
%(other)
%
, &(other : MultiIndexable(U)) forall U
&(other)
&
, &*(other : MultiIndexable(U)) forall U
&*(other)
&*
, &**(other : MultiIndexable(U)) forall U
&**(other)
&**
, &+(other : MultiIndexable(U)) forall U
&+(other)
&+
, &-(other : MultiIndexable(U)) forall U
&-(other)
&-
, *(other : MultiIndexable(U)) forall U
*(other)
*
, **(other : MultiIndexable(U)) forall U
**(other)
**
, +(other : MultiIndexable(U)) forall U
+(other)
+
+
, -(other : MultiIndexable(U)) forall U
-(other)
-
-
, /(other : MultiIndexable(U)) forall U
/(other)
/
, //(other : MultiIndexable(U)) forall U
//(other)
//
, <(other : MultiIndexable(U)) forall U
<(other)
<
, <=(other : MultiIndexable(U)) forall U
<=(other)
<=
, <=>(other : MultiIndexable(U)) forall U
<=>(other)
<=>
, ==(other : self) : Bool ==, =~(value) : MultiIndexable(Bool) =~, >(other : MultiIndexable(U)) forall U
>(other)
>
, >=(other : MultiIndexable(U)) forall U
>=(other)
>=
, [](region_literal : Indexable, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
[](region : IndexRegion)
[](*region_literal, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
[]
, []?(bool_mask : MultiIndexable(Bool)) : MultiIndexable(T(T) | Nil)
[]?(region : Indexable, drop : Bool = MultiIndexable::DROP_BY_DEFAULT) : MultiIndexable(T(T)) | Nil
[]?(region : IndexRegion) : MultiIndexable(T(T)) | Nil
[]?(*region_literal, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
[]?
, ^(other : MultiIndexable(U)) forall U
^(other)
^
, |(other : MultiIndexable(U)) forall U
|(other)
|
, ~ ~, apply : ApplyProxy apply, apply! : InPlaceApplyProxy apply!, colex_each : ElemIterator
colex_each(&) : Nil
colex_each
, colex_each_coord : ColexIterator
colex_each_coord(&) : Nil
colex_each_coord
, dimensions : Int dimensions, each : ElemIterator
each(&) : Nil
each
, each_coord : LexIterator
each_coord(&) : Nil
each_coord
, each_slice(axis = 0) : Iterator
each_slice(axis = 0, &)
each_slice
, each_with(*args, &) each_with, each_with_coord : ElemAndCoordIterator
each_with_coord(&) : Nil
each_with_coord
, empty? : Bool empty?, ensure_writable ensure_writable, eq(other : MultiIndexable(U)) : MultiIndexable(Bool) forall U
eq(value) : MultiIndexable(Bool)
eq
, equals?(other : MultiIndexable, &) : Bool equals?, fast_each : Iterator(T(T))
fast_each(&) : Nil
fast_each
, first : T(T) first, get(coord : Indexable) : T(T)
get(*coord : Int)
get
, get_available(region_literal : Indexable, drop : Bool = DROP_BY_DEFAULT)
get_available(region : IndexRegion, drop : Bool = DROP_BY_DEFAULT)
get_available(*region_literal, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
get_available
, get_chunk(coord : Indexable, region_shape : Indexable(I)) forall I
get_chunk(region_literal : Indexable, drop : Bool = DROP_BY_DEFAULT)
get_chunk(region : IndexRegion) : MultiIndexable(T(T))
get_chunk(*region_literal, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
get_chunk
, get_element(coord : Indexable) : T(T)
get_element(*coord : Int)
get_element
, has_coord?(coord : Indexable) : Bool
has_coord?(*coord : Int)
has_coord?
, has_region?(region_literal : Indexable, drop : Bool = DROP_BY_DEFAULT) : Bool
has_region?(region : IndexRegion) : Bool
has_region?(*region_literal, drop : Bool = MultiIndexable::DROP_BY_DEFAULT)
has_region?
, hash(hasher) hash, last : T(T) last, map(&block : T(T) -> R) : MultiIndexable(R) forall R map, map!(&block : T(T) -> T(T) | MultiIndexable(T(T))) : MultiIndexable(T(T)) map!, map_with(*args : *U, &) forall U
map_with(*args, &)
map_with
, map_with!(*args : *U, &) forall U map_with!, map_with_coord(&) map_with_coord, map_with_coord!(&block : T(T) -> MultiIndexable(T(T))) map_with_coord!, permute(*args) : MultiIndexable(T(T)) permute, process(&block : T(T) -> R) : ProcView(self, T(T), R) forall R
process(proc : Proc(T(T), R)) : ProcView(self, T(T), R) forall R
process
, reshape(*args) : MultiIndexable(T(T)) reshape, reverse(*args) : MultiIndexable(T(T)) reverse, sample(n : Int, random = Random::DEFAULT) : Enumerable(T(T))
sample(random = Random::DEFAULT) : T(T)
sample
, scalar? : Bool scalar?, shape : Array shape, size size, slices(axis = 0) : Indexable slices, tile(counts : Enumerable(Int)) : MultiIndexable
tile(*counts : Int)
tile
, to_f : Float to_f, to_literal_s(io : IO) : Nil to_literal_s, to_narr : NArray(T(T)) to_narr, to_s(io : IO, settings = Formatter::Settings.new) : Nil
to_s(settings = Formatter::Settings.new) : String
to_s
, to_scalar : T(T) to_scalar, to_scalar? : T(T) | Nil to_scalar?, unsafe_fetch_chunk(region : IndexRegion) : MultiIndexable(T(T)) unsafe_fetch_chunk, unsafe_fetch_element(coord : Indexable) : T(T) unsafe_fetch_element, view(region : Indexable | Nil | IndexRegion = nil) : View(self, T(T))
view(*region) : View(self, T(T))
view

Class methods inherited from module Phase::MultiIndexable(Array(T))

each_with(*args : *U, &) forall U each_with

Macros inherited from module Phase::MultiIndexable(Array(T))

coord_splat_overload(name) coord_splat_overload, def_elementwise_binary(name) def_elementwise_binary, def_elementwise_unary(name) def_elementwise_unary, region_splat_overload(name) region_splat_overload

Constructor Detail

def self.new(region : IndexRegion, bound_shape : Shape) #

Copy constructor that throws a ShapeError if region doesn't fit inside of bound_shape. (see IndexRegion#fits_in?)

src = IndexRegion.cover([3, 4]) # => IndexRegion[0..2, 0..3]
IndexRegion.new(src, [4, 4]) # => IndexRegion[0..2, 0..3]
IndexRegion.new(src, [2, 2]) # => ShapeError

[View source]
def self.new(region_literal : Enumerable, bound_shape : Indexable(T), drop : Bool = DROP_BY_DEFAULT) : IndexRegion(T) #

Creates an IndexRegion from a region_literal, using bound_shape for relative index handling. This is the most commonly used IndexRegion constructor. If the region literal has fewer dimensions than bound_shape, then the latter axes will be inferred as ...

# Normal usage
IndexRegion.new([1...5, ..-3], [5, 5]) # => IndexRegion[1..4, 0..2]

# If the region literal is shorter than the bound shape, it
# is filled with trailing ".."s
IndexRegion.new([1], [2, 3])     # => IndexRegion[1, 0..2]
IndexRegion.new([1, ..], [2, 3]) # => IndexRegion[1, 0..2]

# If the region literal is longer than the bound shape, a
# DimensionError is raised
IndexRegion.new([.., 3], [3]) # => DimensionError

# If the region literal is out of bounds, an IndexError
# is raised
IndexRegion.new([5..10], [2]) # => IndexError
IndexRegion.new([5..10], [-2]) # => IndexError

[View source]
def self.new(region_literal : RegionLiteral, drop : Bool = DROP_BY_DEFAULT) #

Creates an IndexRegion from an absolute (positive, bounded) region_literal. This allows you to bypass the usual requirement of passing a bound_shape, which is usually needed in order to process negative or nil indexes.

IndexRegion.new([1, 2...5]) # => IndexRegion[1, 2..4]
IndexRegion.new([..]) # => Exception (TODO: pick a better exception)
IndexRegion.new([-1]) # => Exception (TODO: pick a better exception)

[View source]
def self.new(region_literal : Enumerable, bound_shape : Indexable | Nil = nil, drop : Bool = DROP_BY_DEFAULT, *, trim_to : Shape(T)) #

Creates an IndexRegion by clipping the region_literal to fit inside of the shape trim_to. By default, only absolute (positive) ordinates can be used in the region literal - however, if a bound_shape is passed, relative (negative / unbounded) indexing can be used, and will refer to it.

# Using *trim_to* allows you to clip a region to a shape
IndexRegion.new([0..5, 1..2], trim_to: [2, 2]) # => IndexRegion[0..1, 1..1]

# A *bound_shape* lets you use relative indexes
IndexRegion.new([.., 2..-2], bound_shape: [3, 5], trim_to: [2, 3]) # => IndexRegion[0..1, 2..2]

# This method won't throw, but it *will* return an empty
# IndexRegion if the *region_literal* doesn't fit in *trim_to*.
IndexRegion.new([5..8], trim_to: [3]) # => IndexRegion[0..0..0]

[View source]

Class Method Detail

def self.cover(bound_shape : Shape(T), *, drop : Bool = DROP_BY_DEFAULT, degeneracy : Array(Bool) | Nil = nil) #

Creates an IndexRegion whose coordinates fully cover the given bound_shape.

IndexRegion.cover([2, 3]).to_narr # => 2x3 Phase::NArray(Array(Int32))
                                  #    [[[0, 0], [0, 1], [0, 2]],
                                  #     [[1, 0], [1, 1], [1, 2]]]

[View source]

Instance Method Detail

def ==(other : self) #
Description copied from module Phase::MultiIndexable(Array(T))

Returns true if both the shape and elements of self and other are equal.

NArray.new([1, 2]) == NArray.new([1, 2]) # => true
NArray.new([[1], [2]]) == NArray.new([1, 2]) # => false
NArray.new([8, 2]) == NArray.new([1, 2]) # => false

def absolute_to_local(coord) #

Maps an absolute (output) coordinate to its corresponding local (input) coordinate.

idx_r = IndexRegion(Int32).new([3..5, 2..1])
idx_r.absolute_to_local([3, 2]) # => [0, 0]
idx_r.absolute_to_local([4, 2]) # => [1, 0]
idx_r.absolute_to_local([5, 1]) # => [2, 1]

[View source]
def absolute_to_local_unsafe(coord) #

Unsafe version of #absolute_to_local that does not check if coord is in bounds for this IndexRegion.


[View source]
def clone #

Returns a copy of self with all instance variables cloned.


[View source]
def degeneracy : Array(Bool) #

Stores the user-hinted dimension dropping information from the region literal. For example: IndexRegion(Int32).new(1, 1..1, 1..2) has @degeneracy == [true, false, false] because axis 0 was an integer (droppable) whereas axis 1 and 2 were both ranges (and thus aren't reliably droppable). This array will be populated regardless of if dimension dropping is enabled. If this IndexRegion does not correspond to a region literal (e.g. IndexRegion.cover(shape)), @degeneracy should be populated with false.


[View source]
def degeneracy=(degeneracy : Array(Bool)) #

Stores the user-hinted dimension dropping information from the region literal. For example: IndexRegion(Int32).new(1, 1..1, 1..2) has @degeneracy == [true, false, false] because axis 0 was an integer (droppable) whereas axis 1 and 2 were both ranges (and thus aren't reliably droppable). This array will be populated regardless of if dimension dropping is enabled. If this IndexRegion does not correspond to a region literal (e.g. IndexRegion.cover(shape)), @degeneracy should be populated with false.


[View source]
def drop : Bool #

Whether or not dimensions should be dropped.


[View source]
def each : LexIterator(T) #

Unsafe version of #absolute_to_local that does not check if coord is in bounds for this IndexRegion.


[View source]
def first #

Returns a copy of the coordinate of the first "corner" in this IndexRegion. For example, if the region literal is [1..3, 5..-2..1], the "first corner" is [1, 5] - the first ordinate on the axis 0 range is 1, and the first ordinate on axis 1 is 5. Similarly, the #last coordinate is [3, 1]. Note that if and only if @step[i] == 0, then @first[i] and @last[i] will be meaningless, as an empty set of coordinates has no corners. See @step.


[View source]
def fits_in?(bound_shape : Shape) : Bool #

Returns true if this IndexRegion contains coordinates that all fit inside of the given bound_shape. For example:

idx_r = IndexRegion.new(region_literal: [1..2..], bound_shape: [5])
idx_r.to_narr # => [[1], [3]]

idx_r.fits_in?([3]) # => false
idx_r.fits_in?([4]) # => true
idx_r.fits_in?([4, 4]) # => DimensionError

[View source]
def hash(hasher) #
Description copied from struct Struct

See Object#hash(hasher)


def includes?(coord : InputCoord) #

Returns true if this IndexRegion points to the provided coord. For example:


idx_r = IndexRegion(Int32).new([0..3, 5..7])

# This IndexRegion maps the input coordinate [0, 0] to [0, 5]
idx_r.get(0, 0) # => [0, 5]

# And thus it includes [0, 5]
idx_r.includes? [0, 5] # => true

# On the other hand, no input coordinate will map to [10, 10]
idx_r.includes? [10, 10] # => false

# Don't confuse input and output coordinates, here! Like all
# `MultiIndexable`s, idx_r implements `#has_coord?`. `#includes?`
# refers to the values (output coordinates) of the `IndexRegion`,
# whereas `#has_coord?` refers to the input coordinates.
idx_r.includes? [0, 0] # => false
idx_r.has_coord? [0, 0] # => true

[View source]
def last #

Similar to IndexRegion#first. For example, if the region literal is [1..3, 5..-2..1], the "last corner" is [3, 1] - the last ordinate on the axis 0 range is 3, and the last ordinate on axis 1 is 1.


[View source]
def local_to_absolute(coord) #

Maps a local (input) coordinate to its corresponding absolute (output) coordinate. This is equivalent to using IndexRegion#[](coord), but it is aliased here in order to make IndexRegion code easier to reason about. For example:

idx_r = IndexRegion(Int32).new([3..5, 2..1])
idx_r.local_to_absolute([0, 0]) # => [3, 2]
idx_r[0, 0] # => [3, 2]

[View source]
def local_to_absolute_unsafe(coord : Coord) : Array(T) #

Unsafe version of #local_to_absolute that does not check if coord is in bounds for this IndexRegion.


[View source]
def proper_dimensions : Int32 #

Returns the number of dimensions of the space that this IndexRegion maps into. For example:

#                          region   proper shape
idx_r = IndexRegion.new([1, .., ..], [5, 5, 5])

# The IndexRegion above describes a 2D region (a matrix)
puts idx_r.dimensions # => 2

# But the matrix draws out of a 3D MultiIndexable:
puts idx_r.proper_dimensions # => 3

[View source]
def reverse : self #

Returns a reversed copy of this IndexRegion. See #reverse!.


[View source]
def reverse! : IndexRegion(T) #

Reverses the ordering of an IndexRegion in place. For example:

idx_r = IndexRegion(Int32).new([0..2..2])
narr = NArray['a', 'b', 'c']

idx_r.each { |coord| puts coord } # => [0], [2]
narr[idx_r] # => NArray['a', 'c']

idx_r.reverse!
idx_r.each { |coord| puts coord } # => [2], [0]
narr[idx_r] # => NArray['c', 'a']

[View source]
def shape_internal(drop = MultiIndexable::DROP_BY_DEFAULT) : Array(T) #

============= Methods required by MultiIndexable ===========================

TODO drop isn't being used here, why is it included?


[View source]
def stride #

Returns the spacing between elements along each axis. For example, if the region literal is [1..3, 5..-2..1], the stride on axis 0 is 1 (by default), and the stride on axis 1 is -2 (as written in the region literal). Thus, calling #stride on the corresponding IndexRegion would yield [1, -2].

idx_r = IndexRegion(Int32).new([1..3, 5..-2..1])
puts idx_r.stride # => [1, -2]

[View source]
def to_s(io : IO) #

Returns a translated copy of this IndexRegion. See #translate!.


[View source]
def translate(offset : Enumerable) : self #

Returns a translated copy of this IndexRegion. See #translate!.


[View source]
def translate!(offset : Enumerable) : self #

Translates this IndexRegion in place by adding an offset to each output coordinate. For example:

idx_r = IndexRegion(Int32).new([0, 5..-2..0])
puts idx_r # => IndexRegion[0, 5..-2..1]

idx_r.translate!([1, -1])
puts idx_r # => IndexRegion[1, 4..-2..0]

IndexRegions only output canonical coordinates. This
translation would produce negative ordinates in the output coords,
which is illegal.
idx_r.translate!([-10, -10]) # => IndexError

[View source]
def trim(bound_shape) : self #

Returns a trimmed copy of this IndexRegion. See #trim!.


[View source]
def trim!(bound_shape : Shape) : self #

Clips this IndexRegion in-place to fit the bound_shape provided.

a = IndexRegion(Int32).new([1..3, 10..-2..0])
puts a # => IndexRegion[1..3, 10..-2..0]

# Trimming to a shape that `a` fits in has no effect:
a.trim!([100, 100])
puts a # => IndexRegion[1..3, 10..-2..0]

a.trim!([2, 3])
puts a # => IndexRegion[1..1, 2..-2..0]

# It's possible to recieve an empty result after trimming
b = IndexRegion(Int32).new([5..6])
b.trim!([0])
puts b # => IndexRegion[0..0..0] (no elements)

# When using the wrong number of dimensions, a DimensionError is raised
c = IndexRegion(Int32).new([5..4])
c.trim!([4, 3]) # => DimensionError

[View source]
def unsafe_fetch_chunk(region : IndexRegion, drop : Bool) : IndexRegion(T) #

Returns the number of dimensions of the space that this IndexRegion maps into. For example:

#                          region   proper shape
idx_r = IndexRegion.new([1, .., ..], [5, 5, 5])

# The IndexRegion above describes a 2D region (a matrix)
puts idx_r.dimensions # => 2

# But the matrix draws out of a 3D MultiIndexable:
puts idx_r.proper_dimensions # => 3

[View source]
def unsafe_fetch_element(coord : Coord) : Array(T) #

Returns the number of dimensions of the space that this IndexRegion maps into. For example:

#                          region   proper shape
idx_r = IndexRegion.new([1, .., ..], [5, 5, 5])

# The IndexRegion above describes a 2D region (a matrix)
puts idx_r.dimensions # => 2

# But the matrix draws out of a 3D MultiIndexable:
puts idx_r.proper_dimensions # => 3

[View source]
def unsafe_translate!(offset : Enumerable) : self #

Translates an IndexRegion in place, without checking that the result is valid. See #translate!.

WARNING this allows for the creation of IndexRegions with negative ordinates, which may cause undocumented behaviour elsewhere in the code. The burden is on the user to ensure that negative ordinates are not created, or that they are appropriately handled.


[View source]