Skip to content

Commit

Permalink
Add splitting methods + re-add removed creation methods (#72)
Browse files Browse the repository at this point in the history
  • Loading branch information
christopherzimmerman committed Nov 14, 2021
1 parent cd7ab5b commit c2b9804
Show file tree
Hide file tree
Showing 6 changed files with 356 additions and 0 deletions.
146 changes: 146 additions & 0 deletions src/tensor/allocation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,152 @@ class Tensor(T, S)
self.range(start, stop, T.new(1), device)
end

# Return evenly spaced numbers over a specified interval.
# Returns `num` evenly spaced samples, calculated over the
# interval [`start`, `stop`].
# The endpoint of the interval can optionally be excluded.
#
# ## Arguments
#
# * start : `T` - Start of interval
# * stop : `T` - End of interval
# * num : `Int` - Number of samples
# * endpoint : `Bool` - Indicates if endpoint of the interval should be
# included in the results
# * device : `Num::Storage` - Backend for the `Tensor`
#
# ## Examples
#
# ```
# Tensor.linspace(0_f32, 1_f32, 5) # => [0.0, 0.25, 0.5, 0.75, 1.0]
#
# Tensor.linspace(0_f32, 1_f32, 5, endpoint: false) # => [0.0, 0.2, 0.4, 0.6, 0.8]
# ```
def self.linear_space(
start : T,
stop : T,
num : Int = 50,
endpoint = true,
device = CPU
)
unless num > 0
raise Num::Exceptions::ValueError.new(
"Number of samples must be non-negative"
)
end
divisor = endpoint ? num - 1 : num
result = Tensor.range(T.new(num), device: device)
delta = stop - start

if num > 1
step = delta / divisor
if step == 0
raise Num::Exceptions::ValueError.new(
"Step cannot be 0"
)
end
else
step = delta
end

Num.multiply!(result, step)
Num.add!(result, start)

if endpoint && num > 1
result[-1] = stop
end

result
end

# Return numbers spaced evenly on a log scale.
# In linear space, the sequence starts at ``base ** start``
# (`base` to the power of `start`) and ends with ``base ** stop``
# (see `endpoint` below).
#
# ## Arguments
#
# * start : `T` - Start of interval
# * stop : `T` - End of interval
# * num : `Int` - Number of samples
# * endpoint : `Bool` - Indicates if endpoint should be included in the results
# * device : `Num::Storage` - Backend for the `Tensor`
#
# ## Examples
#
# ```
# Tensor.logarithmic_space(2.0, 3.0, num: 4)
#
# # [100 , 215.443, 464.159, 1000 ]
# ```
def self.logarithmic_space(
start : T,
stop : T,
num = 50,
endpoint = true,
base : T = T.new(10.0),
device = CPU
)
result = Tensor.linear_space(
start,
stop,
num: num,
endpoint: endpoint,
device: device
)
Num.power(base, result)
end

# Return numbers spaced evenly on a log scale (a geometric progression).
# This is similar to `logspace`, but with endpoints specified directly.
# Each output sample is a constant multiple of the previous.
#
# ## Arguments
#
# * start : `T` - Start of interval
# * stop : `T` - End of interval
# * num : `Int` - Number of samples
# * endpoint : `Bool` - Indicates if endpoint should be included in the results
# * device : `Num::Storage`
#
# ## Examples
#
# ```
# Tensor.geometric_space(1_f32, 1000_f32, 4) # => [1, 10, 100, 1000]
# ```
def self.geometric_space(
start : T,
stop : T,
num : Int = 50,
endpoint : Bool = true,
device = CPU
)
if start == 0 || stop == 0
raise Num::Exceptions::ValueError.new(
"Geometric sequence cannot include zero"
)
end

out_sign = T.new(1.0)

if start < 0 && stop < 0
start, stop = -start, -stop
out_sign = -out_sign
end

log_start = Math.log(start, T.new(10.0))
log_stop = Math.log(stop, T.new(10.0))

Tensor.logarithmic_space(
log_start,
log_stop,
num: num,
endpoint: endpoint,
base: T.new(10.0),
device: device
) * out_sign
end

# Return a two-dimensional `Tensor` with ones along the diagonal,
# and zeros elsewhere
#
Expand Down
129 changes: 129 additions & 0 deletions src/tensor/backends/cpu/impl_manipulate.cr
Original file line number Diff line number Diff line change
Expand Up @@ -393,4 +393,133 @@ module Num
end
a[s]
end

# Split a `Tensor` into multiple sub-`Tensor`s
#
# ## Arguments
#
# * a : `Tensor(U, CPU(U))` - `Tensor` to split`
# * ind : `Int` - Number of sections of resulting `Array`
# * axis : `Int` - Axis along which to split
#
# ## Examples
#
# ```
# a = Tensor.range(9)
# puts Num.array_split(a, 2) # => [[0, 1, 2, 3, 4], [5, 6, 7, 8]]
# ```
def array_split(
a : Tensor(U, CPU(U)),
ind : Int,
axis : Int = 0
) : Array(Tensor(U, CPU(U))) forall U
n = a.shape[axis]
e = n // ind
extra = n % ind
sizes = [0]
sizes += Array.new(extra, e + 1)
sizes += Array.new(ind - extra, e)
r = 0
sizes.each_with_index do |s, i|
tmp = r
r += s
sizes[i] = s + tmp
end
split_internal(a, axis, ind, sizes)
end

# Split a `Tensor` into multiple sub-`Tensor`s, using an explicit mapping
# of indices to split the `Tensor`
#
# ## Arguments
#
# * a : `Tensor(U, CPU(U))` - `Tensor` to split`
# * ind : `Int` - Array of indices to use when splitting the `Tensor`
# * axis : `Int` - Axis along which to split
#
# ## Examples
#
# ```
# a = Tensor.range(9)
# puts Num.array_split(a, [1, 3, 5]) # => [[0], [1, 2], [3, 4], [5, 6, 7, 8]]
# ```
def array_split(
a : Tensor(U, CPU(U)),
ind : Array(Int),
axis : Int = 0
) : Array(Tensor(U, CPU(U))) forall U
n = ind.size + 1
div_points = [0]
div_points += ind
div_points << a.shape[axis]
split_internal(a, axis, n, div_points)
end

# Split a `Tensor` into multiple sub-`Tensor`s. The number of sections
# must divide the `Tensor` equally.
#
# ## Arguments
#
# * a : `Tensor(U, CPU(U))` - `Tensor` to split`
# * ind : `Int` - Number of sections of resulting `Array`
# * axis : `Int` - Axis along which to split
#
# ## Examples
#
# ```
# a = Tensor.range(1, 9)
# puts Num.array_split(a, 2) # => [[1, 2, 3, 4], [5, 6, 7, 8]]
# ```
def split(
a : Tensor(U, CPU(U)),
ind : Int,
axis : Int = 0
) : Array(Tensor(U, CPU(U))) forall U
n = a.shape[axis]
if n % ind != 0
raise Num::Exceptions::ValueError.new(
"Split does not result in equal size sub-Tensors"
)
end
array_split(a, ind, axis)
end

# Split a `Tensor` into multiple sub-`Tensor`s, using an explicit mapping
# of indices to split the `Tensor`
#
# ## Arguments
#
# * a : `Tensor(U, CPU(U))` - `Tensor` to split`
# * ind : `Int` - Array of indices to use when splitting the `Tensor`
# * axis : `Int` - Axis along which to split
#
# ## Examples
#
# ```
# a = Tensor.range(9)
# puts Num.array_split(a, [1, 3, 5]) # => [[0], [1, 2], [3, 4], [5, 6, 7, 8]]
# ```
def split(
a : Tensor(U, CPU(U)),
ind : Array(Int),
axis : Int = 0
) : Array(Tensor(U, CPU(U))) forall U
array_split(a, ind, axis)
end

private def split_internal(
arr : Tensor(U, V),
axis : Int,
n : Int,
div_points : Array(Int)
) : Array(Tensor(U, V)) forall U, V
result = [] of Tensor(U, V)
swapped = arr.swap_axes(axis, 0)
n.times do |i|
s0 = div_points[i]
s1 = div_points[i + 1]
result << arr[s0...s1].swap_axes(axis, 0)
end
result
end
end
3 changes: 3 additions & 0 deletions src/tensor/backends/opencl/impl_allocation.cr
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@ class OCL(T) < Num::Backend::Storage(T)
end

private def metadata_to_buffer(arr : Array(Int32))
if arr == [] of Int32
arr = [1]
end
buffer = Cl.buffer(Num::ClContext.instance.context, arr.size.to_u64, dtype: Int32)
Cl.write(Num::ClContext.instance.queue, arr.to_unsafe, buffer, (arr.size * sizeof(Int32)).to_u64)
buffer
Expand Down
28 changes: 28 additions & 0 deletions src/tensor/backends/opencl/impl_math.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ module Num
@@name = "{{ fn }}Kernel"
end

class {{ dtype }}{{ fn.stringify.capitalize.id }}TensorScalarInplace < Num::ArithmeticTensorScalarInplaceKernel({{ dtype }})
@@operator = "{{ operator.id }}"
@@name = "{{ fn }}Kernel"
end

# :nodoc:
class {{ dtype }}{{ fn.stringify.capitalize.id }}ScalarTensor < Num::ArithmeticScalarTensorKernel({{ dtype }})
@@operator = "{{ operator.id }}"
Expand Down Expand Up @@ -119,6 +124,29 @@ module Num
)
end

# {{ fn.stringify.capitalize.id }} a `Tensor` and a `Number` elementwise,
# modifying the `Tensor` inplace.
#
# ## Arguments
#
# * a : `Tensor(U, OCL(U))` - LHS argument to {{ fn }}
# * b : `U` - RHS argument to {{ fn }}
#
# ## Examples
#
# ```
# a = [1.5, 2.2, 3.2].to_tensor(OCL)
# Num.{{ fn }}(a, 3.5)
# ```
def {{ fn.id }}!(a : Tensor(U, OCL(U)), b : U) : Nil forall U
call_opencl_kernel(
U,
{{ fn.stringify.capitalize.id }}TensorScalarInplace,
[Int32, UInt32, Float32, Float64],
a, b
)
end

# {{ fn.stringify.capitalize.id }} a `Number` and a `Tensor` elementwise
#
# ## Arguments
Expand Down
45 changes: 45 additions & 0 deletions src/tensor/backends/opencl/kernels/operators.cr
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,51 @@ abstract class Num::ArithmeticTensorScalarKernel(T) < Num::Kernel(T)
end
end

# :nodoc:
abstract class Num::ArithmeticTensorScalarInplaceKernel(T) < Num::Kernel(T)
@@operator : String = ""

def get_program(dtype)
"
#{super}
#pragma OPENCL EXTENSION cl_khr_fp64 : enable
__kernel void #{@@name}
(const int rank,
const int len,
__global const int * restrict dst_shape,
__global const int * restrict dst_strides,
const int dst_offset,
__global #{dtype} * restrict const dst_data,
const #{dtype} B_data)
{
for (int elemID = get_global_id(0);
elemID < len;
elemID += get_global_size(0)) {
const int dst_real_idx = opencl_getIndexOfElementID(rank, dst_shape, dst_strides, dst_offset, elemID);
dst_data[dst_real_idx] = dst_data[dst_real_idx] #{@@operator} B_data;
}
}
"
end

def call(a : Tensor(T, OCL(T)), b : T)
Cl.args(
@kernel,
a.rank,
a.size,
a.data.shape,
a.data.strides,
a.offset,
a.to_unsafe,
b,
)
Cl.run(Num::ClContext.instance.queue, @kernel, a.size)
nil
end
end

# :nodoc:
abstract class Num::ArithmeticScalarTensorKernel(T) < Num::Kernel(T)
@@operator : String = ""
Expand Down
Loading

0 comments on commit c2b9804

Please sign in to comment.