struct
Phase::IndexRegion(T)
- Phase::IndexRegion(T)
- Struct
- Value
- Object
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.crConstant Summary
-
DROP_BY_DEFAULT =
MultiIndexable::DROP_BY_DEFAULT -
See
MultiIndexable::DROP_BY_DEFAULT
Constructors
-
.new(region : IndexRegion, bound_shape : Shape)
Copy constructor that throws a
ShapeErrorif region doesn't fit inside of bound_shape. -
.new(region_literal : Enumerable, bound_shape : Indexable(T), drop : Bool = DROP_BY_DEFAULT) : IndexRegion(T)
Creates an
IndexRegionfrom a region_literal, using bound_shape for relative index handling. -
.new(region_literal : RegionLiteral, drop : Bool = DROP_BY_DEFAULT)
Creates an
IndexRegionfrom an absolute (positive, bounded) region_literal. -
.new(region_literal : Enumerable, bound_shape : Indexable | Nil = nil, drop : Bool = DROP_BY_DEFAULT, *, trim_to : Shape(T))
Creates an
IndexRegionby clipping the region_literal to fit inside of the shape trim_to.
Class Method Summary
-
.cover(bound_shape : Shape(T), *, drop : Bool = DROP_BY_DEFAULT, degeneracy : Array(Bool) | Nil = nil)
Creates an
IndexRegionwhose coordinates fully cover the given bound_shape.
Instance Method Summary
-
#==(other : self)
Returns true if both the shape and elements of
selfand other are equal. -
#absolute_to_local(coord)
Maps an absolute (output) coordinate to its corresponding local (input) coordinate.
-
#absolute_to_local_unsafe(coord)
Unsafe version of
#absolute_to_localthat does not check if coord is in bounds for thisIndexRegion. -
#clone
Returns a copy of
selfwith all instance variables cloned. -
#degeneracy : Array(Bool)
Stores the user-hinted dimension dropping information from the region literal.
-
#degeneracy=(degeneracy : Array(Bool))
Stores the user-hinted dimension dropping information from the region literal.
-
#drop : Bool
Whether or not dimensions should be dropped.
-
#each : LexIterator(T)
Unsafe version of
#absolute_to_localthat does not check if coord is in bounds for thisIndexRegion. -
#first
Returns a copy of the coordinate of the first "corner" in this
IndexRegion. -
#fits_in?(bound_shape : Shape) : Bool
Returns true if this
IndexRegioncontains coordinates that all fit inside of the given bound_shape. -
#hash(hasher)
See
Object#hash(hasher) -
#includes?(coord : InputCoord)
Returns true if this
IndexRegionpoints to the provided coord. -
#last
Similar to
IndexRegion#first. -
#local_to_absolute(coord)
Maps a local (input) coordinate to its corresponding absolute (output) coordinate.
-
#local_to_absolute_unsafe(coord : Coord) : Array(T)
Unsafe version of
#local_to_absolutethat does not check if coord is in bounds for thisIndexRegion. -
#proper_dimensions : Int32
Returns the number of dimensions of the space that this
IndexRegionmaps into. -
#reverse : self
Returns a reversed copy of this
IndexRegion. -
#reverse! : IndexRegion(T)
Reverses the ordering of an
IndexRegionin place. -
#shape_internal(drop = MultiIndexable::DROP_BY_DEFAULT) : Array(T)
============= Methods required by MultiIndexable =========================== TODO: drop isn't being used here, why is it included?
-
#stride
Returns the spacing between elements along each axis.
-
#to_s(io : IO)
Returns a translated copy of this
IndexRegion. -
#translate(offset : Enumerable) : self
Returns a translated copy of this
IndexRegion. -
#translate!(offset : Enumerable) : self
Translates this
IndexRegionin place by adding an offset to each output coordinate. -
#trim(bound_shape) : self
Returns a trimmed copy of this
IndexRegion. -
#trim!(bound_shape : Shape) : self
Clips this
IndexRegionin-place to fit the bound_shape provided. -
#unsafe_fetch_chunk(region : IndexRegion, drop : Bool) : IndexRegion(T)
Returns the number of dimensions of the space that this
IndexRegionmaps into. -
#unsafe_fetch_element(coord : Coord) : Array(T)
Returns the number of dimensions of the space that this
IndexRegionmaps into. -
#unsafe_translate!(offset : Enumerable) : self
Translates an
IndexRegionin place, without checking that the result is valid.
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
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
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
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)
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]
Class Method Detail
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]]]
Instance Method Detail
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
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]
Unsafe version of #absolute_to_local that does not check if coord is in bounds for this IndexRegion.
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.
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.
Unsafe version of #absolute_to_local that does not check if coord is in bounds for this IndexRegion.
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.
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
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
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.
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]
Unsafe version of #local_to_absolute that does not check if coord is in bounds for this IndexRegion.
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
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']
============= Methods required by MultiIndexable ===========================
TODO drop isn't being used here, why is it included?
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]
Returns a translated copy of this IndexRegion. See #translate!.
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
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
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
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
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.