mirror of https://github.com/Nonannet/copapy.git
concat and sigmoid function added, moved relu and sigmoid to _nn.py, renamed min and max to minimum and maximum like in numpy and updated functions to work with tensors
This commit is contained in:
parent
d6ff9599f4
commit
4a71db0e38
|
|
@ -37,8 +37,9 @@ from ._target import Target, jit
|
|||
from ._basic_types import NumLike, value, generic_sdb, iif
|
||||
from ._vectors import vector, distance, scalar_projection, angle_between, rotate_vector, vector_projection
|
||||
from ._quaternion import quaternion
|
||||
from ._tensors import tensor, zeros, ones, arange, eye, identity, diagonal
|
||||
from ._math import sqrt, abs, sign, sin, cos, tan, asin, acos, atan, atan2, log, exp, pow, get_42, clamp, min, max, relu
|
||||
from ._tensors import tensor, zeros, ones, arange, eye, identity, diagonal, concat
|
||||
from ._math import sqrt, abs, sign, sin, cos, tan, asin, acos, atan, atan2, log, exp, pow, get_42, clamp, minimum, maximum
|
||||
from ._nn import relu, sigmoid
|
||||
from ._autograd import grad
|
||||
from ._tensors import tensor as matrix
|
||||
from ._version import __version__ # Run "pip install -e ." to generate _version.py
|
||||
|
|
@ -74,16 +75,18 @@ __all__ = [
|
|||
"pow",
|
||||
"get_42",
|
||||
"clamp",
|
||||
"min",
|
||||
"max",
|
||||
"minimum",
|
||||
"maximum",
|
||||
"relu",
|
||||
"distance",
|
||||
"scalar_projection",
|
||||
"angle_between",
|
||||
"rotate_vector",
|
||||
"vector_projection",
|
||||
"vector_projection",
|
||||
"quaternion",
|
||||
"grad",
|
||||
"eye",
|
||||
"concat",
|
||||
"sigmoid",
|
||||
"jit"
|
||||
]
|
||||
|
|
|
|||
|
|
@ -393,12 +393,20 @@ def clamp(x: U | value[U] | vector[U], min_value: U | value[U], max_value: U |
|
|||
|
||||
|
||||
@overload
|
||||
def min(x: value[U], y: U | value[U]) -> value[U]: ...
|
||||
def minimum(x: U, y: U) -> U: ...
|
||||
@overload
|
||||
def min(x: U | value[U], y: value[U]) -> value[U]: ...
|
||||
def minimum(x: value[U], y: U | value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def min(x: U, y: U) -> U: ...
|
||||
def min(x: U | value[U], y: U | value[U]) -> Any:
|
||||
def minimum(x: U | value[U], y: value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def minimum(x: vector[U], y: U | value[U] | vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def minimum(x: U | value[U] | vector[U], y: vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def minimum(x: tensor[U], y: U | value[U] | tensor[U]) -> tensor[U]: ...
|
||||
@overload
|
||||
def minimum(x: U | value[U] | tensor[U], y: tensor[U]) -> tensor[U]: ...
|
||||
def minimum(x: U | value[U] | vector[U] | tensor[U], y: U | value[U] | vector[U] | tensor[U]) -> Any:
|
||||
"""Minimum function to get the smaller of two values.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -408,22 +416,30 @@ def min(x: U | value[U], y: U | value[U]) -> Any:
|
|||
Returns:
|
||||
Minimum of x and y
|
||||
"""
|
||||
if isinstance(x, value):
|
||||
if isinstance(x, tensor) or isinstance(y, tensor):
|
||||
return _map2_tensor(x, y, minimum)
|
||||
if isinstance(x, vector) or isinstance(y, vector):
|
||||
return _map2_vector(x, y, minimum)
|
||||
if isinstance(x, value) or isinstance(y, value):
|
||||
return add_op('min', [x, y])
|
||||
if isinstance(x, tensor):
|
||||
return _map2_tensor(x, y, min)
|
||||
if isinstance(x, vector):
|
||||
return _map2_vector(x, y, min)
|
||||
return x if x < y else y
|
||||
|
||||
|
||||
@overload
|
||||
def max(x: value[U], y: U | value[U]) -> value[U]: ...
|
||||
def maximum(x: U, y: U) -> U: ...
|
||||
@overload
|
||||
def max(x: U | value[U], y: value[U]) -> value[U]: ...
|
||||
def maximum(x: value[U], y: U | value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def max(x: U, y: U) -> U: ...
|
||||
def max(x: U | value[U], y: U | value[U]) -> Any:
|
||||
def maximum(x: U | value[U], y: value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def maximum(x: vector[U], y: U | value[U] | vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def maximum(x: U | value[U] | vector[U], y: vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def maximum(x: tensor[U], y: U | value[U] | tensor[U]) -> tensor[U]: ...
|
||||
@overload
|
||||
def maximum(x: U | value[U] | tensor[U], y: tensor[U]) -> tensor[U]: ...
|
||||
def maximum(x: U | value[U] | vector[U] | tensor[U], y: U | value[U] | vector[U] | tensor[U]) -> Any:
|
||||
"""Maximum function to get the larger of two values.
|
||||
|
||||
Arguments:
|
||||
|
|
@ -433,12 +449,12 @@ def max(x: U | value[U], y: U | value[U]) -> Any:
|
|||
Returns:
|
||||
Maximum of x and y
|
||||
"""
|
||||
if isinstance(x, value):
|
||||
if isinstance(x, tensor) or isinstance(y, tensor):
|
||||
return _map2_tensor(x, y, maximum)
|
||||
if isinstance(x, vector) or isinstance(y, vector):
|
||||
return _map2_vector(x, y, maximum)
|
||||
if isinstance(x, value) or isinstance(y, value):
|
||||
return add_op('max', [x, y])
|
||||
if isinstance(x, tensor):
|
||||
return _map2_tensor(x, y, max)
|
||||
if isinstance(x, vector):
|
||||
return _map2_vector(x, y, max)
|
||||
return x if x > y else y
|
||||
|
||||
|
||||
|
|
@ -470,20 +486,6 @@ def lerp(v1: U | value[U] | vector[U], v2: U | value[U] | vector[U], t: unifloa
|
|||
return v1 * (1 - t) + v2 * t
|
||||
|
||||
|
||||
@overload
|
||||
def relu(x: U) -> U: ...
|
||||
@overload
|
||||
def relu(x: value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def relu(x: vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def relu(x: tensor[U]) -> tensor[U]: ...
|
||||
def relu(x: U | value[U] | vector[U] | tensor[U]) -> Any:
|
||||
"""Returns x for x > 0 and otherwise 0."""
|
||||
ret = x * (x > 0)
|
||||
return ret
|
||||
|
||||
|
||||
def _map2_vector(self: VecNumLike, other: VecNumLike, func: Callable[[Any, Any], value[U] | U]) -> vector[U]:
|
||||
"""Applies a function to each element of the vector and a second vector or scalar."""
|
||||
if isinstance(self, vector) and isinstance(other, vector):
|
||||
|
|
@ -499,9 +501,9 @@ def _map2_vector(self: VecNumLike, other: VecNumLike, func: Callable[[Any, Any],
|
|||
def _map2_tensor(self: TensorNumLike, other: TensorNumLike, func: Callable[[Any, Any], value[U] | U]) -> tensor[U]:
|
||||
"""Applies a function to each element of the vector and a second vector or scalar."""
|
||||
if isinstance(self, vector):
|
||||
self = tensor(self.values, (len(self.values),))
|
||||
self = tensor(self)
|
||||
if isinstance(other, vector):
|
||||
other = tensor(other.values, (len(other.values),))
|
||||
other = tensor(other)
|
||||
if isinstance(self, tensor) and isinstance(other, tensor):
|
||||
assert self.shape == other.shape, "Tensors must have the same shape"
|
||||
return tensor([func(x, y) for x, y in zip(self.values, other.values)], self.shape)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,34 @@
|
|||
from . import vector
|
||||
from . import tensor
|
||||
from . import value
|
||||
from typing import TypeVar, Any, overload
|
||||
import copapy as cp
|
||||
|
||||
U = TypeVar("U", int, float)
|
||||
|
||||
|
||||
@overload
|
||||
def relu(x: U) -> U: ...
|
||||
@overload
|
||||
def relu(x: value[U]) -> value[U]: ...
|
||||
@overload
|
||||
def relu(x: vector[U]) -> vector[U]: ...
|
||||
@overload
|
||||
def relu(x: tensor[U]) -> tensor[U]: ...
|
||||
def relu(x: U | value[U] | vector[U] | tensor[U]) -> Any:
|
||||
"""Returns x for x > 0 and otherwise 0."""
|
||||
ret = x * (x > 0)
|
||||
return ret
|
||||
|
||||
|
||||
@overload
|
||||
def sigmoid(x: U) -> float: ...
|
||||
@overload
|
||||
def sigmoid(x: value[U]) -> value[float]: ...
|
||||
@overload
|
||||
def sigmoid(x: vector[U]) -> vector[float]: ...
|
||||
@overload
|
||||
def sigmoid(x: tensor[U]) -> tensor[float]: ...
|
||||
def sigmoid(x: U | value[U] | vector[U] | tensor[U]) -> Any:
|
||||
"""Sigmoid function to map any value to the range (0, 1)."""
|
||||
return 1 / (1 + cp.exp(-x))
|
||||
|
|
@ -32,7 +32,7 @@ class tensor(ArrayType[TNum]):
|
|||
self.shape: tuple[int, ...] = tuple(shape)
|
||||
assert (isinstance(values, Sequence) and
|
||||
any(isinstance(v, (value, int, float)) for v in values)), \
|
||||
"Values must be a sequence of values if shape is provided"
|
||||
"Values must be a sequence of scalars if shape is provided"
|
||||
self.values: tuple[TNum | value[TNum], ...] = tuple(v for v in values if not isinstance(v, Sequence))
|
||||
self.ndim: int = len(shape)
|
||||
elif isinstance(values, (int, float)):
|
||||
|
|
@ -856,6 +856,84 @@ def ones(shape: Sequence[int] | int) -> tensor[int]:
|
|||
return tensor([1] * size, tuple(shape))
|
||||
|
||||
|
||||
@overload
|
||||
def concat(tensors: Sequence[vector[U]]) -> vector[U]: ...
|
||||
@overload
|
||||
def concat(tensors: Sequence[tensor[U]], axis: int = 0) -> tensor[U]: ...
|
||||
def concat(tensors: Sequence[tensor[U] | vector[U]], axis: int = 0) -> tensor[U] | vector[U]:
|
||||
"""Concatenate tensors or vectors along a specified axis.
|
||||
|
||||
Arguments:
|
||||
tensors: Tensors or vectors to concatenate. Must have the same shape except for the specified axis.
|
||||
axis: Axis along which to concatenate (default 0).
|
||||
|
||||
Returns:
|
||||
A new tensor or vector resulting from concatenation.
|
||||
"""
|
||||
assert tensors, "At least one tensor must be provided"
|
||||
|
||||
# Check if all inputs are vectors
|
||||
all_vectors = all(isinstance(item, vector) for item in tensors)
|
||||
|
||||
# Convert vectors to tensors for uniform processing
|
||||
tensor_list: list[tensor[U]] = []
|
||||
for item in tensors:
|
||||
if isinstance(item, vector):
|
||||
tensor_list.append(tensor(item.values, item.shape))
|
||||
else:
|
||||
tensor_list.append(item)
|
||||
|
||||
first_shape = tensor_list[0].shape
|
||||
ndim = len(first_shape)
|
||||
|
||||
if axis < 0:
|
||||
axis += ndim
|
||||
|
||||
assert 0 <= axis < ndim
|
||||
|
||||
# Shape checks
|
||||
for t in tensor_list:
|
||||
assert len(t.shape) == ndim
|
||||
for i in range(ndim):
|
||||
if i != axis:
|
||||
assert t.shape[i] == first_shape[i]
|
||||
|
||||
# Output shape
|
||||
new_shape = list(first_shape)
|
||||
new_shape[axis] = sum(t.shape[axis] for t in tensor_list)
|
||||
|
||||
# Compute block sizes
|
||||
inner_block: int = 1
|
||||
for s in first_shape[axis + 1:]:
|
||||
inner_block *= s
|
||||
|
||||
outer_block: int = 1
|
||||
for s in first_shape[:axis]:
|
||||
outer_block *= s
|
||||
|
||||
new_values: list[value[U] | U] = []
|
||||
|
||||
for outer in range(outer_block):
|
||||
for t in tensor_list:
|
||||
axis_size = t.shape[axis]
|
||||
start = outer * axis_size * inner_block
|
||||
end = start + axis_size * inner_block
|
||||
new_values.extend(t.values[start:end])
|
||||
|
||||
result_tensor = tensor(new_values, tuple(new_shape))
|
||||
|
||||
# If all inputs were vectors and result is 1D, return as vector
|
||||
if all_vectors and result_tensor.ndim == 1:
|
||||
return vector(result_tensor.values)
|
||||
|
||||
return result_tensor
|
||||
|
||||
|
||||
def flatten(t: tensor[U]) -> tensor[U]:
|
||||
"""Flatten a tensor to a 1D tensor."""
|
||||
return t.flatten()
|
||||
|
||||
|
||||
def arange(start: int | float, stop: int | float | None = None,
|
||||
step: int | float = 1) -> tensor[int] | tensor[float]:
|
||||
"""Create a tensor with evenly spaced values.
|
||||
|
|
@ -927,10 +1005,10 @@ def identity(size: int) -> tensor[int]:
|
|||
|
||||
|
||||
@overload
|
||||
def diagonal(vec: 'tensor[int] | vector[int]') -> tensor[int]: ...
|
||||
def diagonal(vec: tensor[int] | vector[int]) -> tensor[int]: ...
|
||||
@overload
|
||||
def diagonal(vec: 'tensor[float] | vector[float]') -> tensor[float]: ...
|
||||
def diagonal(vec: 'tensor[Any] | vector[Any]') -> 'tensor[Any]':
|
||||
def diagonal(vec: tensor[float] | vector[float]) -> tensor[float]: ...
|
||||
def diagonal(vec: tensor[Any] | vector[Any]) -> tensor[Any]:
|
||||
"""Create a diagonal tensor from a 1D tensor.
|
||||
|
||||
Arguments:
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ def test_fine():
|
|||
cp.abs(-c_f),
|
||||
cp.sign(c_i),
|
||||
cp.sign(-c_f),
|
||||
cp.min(c_i, 5),
|
||||
cp.max(c_f, 5))
|
||||
cp.minimum(c_i, 5),
|
||||
cp.maximum(c_f, 5))
|
||||
|
||||
re2_test = (a_f ** 2,
|
||||
a_i ** -1,
|
||||
|
|
@ -39,8 +39,8 @@ def test_fine():
|
|||
cp.abs(-a_f),
|
||||
cp.sign(a_i),
|
||||
cp.sign(-a_f),
|
||||
cp.min(a_i, 5),
|
||||
cp.max(a_f, 5))
|
||||
cp.minimum(a_i, 5),
|
||||
cp.maximum(a_f, 5))
|
||||
|
||||
ret_refe = (a_f ** 2,
|
||||
a_i ** -1,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import copapy as cp
|
||||
|
||||
|
||||
def test_tensor_basic():
|
||||
# Test 1: Create a scalar tensor
|
||||
print("Test 1: Scalar tensor")
|
||||
|
|
@ -225,6 +226,7 @@ def test_tensor_basic():
|
|||
assert t.ndim == 3
|
||||
print()
|
||||
|
||||
|
||||
def test_tensor_slicing():
|
||||
print("Test Numpy-style slicing")
|
||||
t = cp.tensor([[10, 20, 30], [40, 50, 60], [70, 80, 90]])
|
||||
|
|
@ -251,6 +253,25 @@ def test_tensor_slicing():
|
|||
assert slice4[2] == 90
|
||||
print()
|
||||
|
||||
|
||||
def test_tensor_concat():
|
||||
print("Test tensor concatenation")
|
||||
t1 = cp.tensor([[1, 2], [3, 4]])
|
||||
t2 = cp.tensor([[5, 6], [7, 8]])
|
||||
t3 = cp.tensor([[9, 10], [11, 12]])
|
||||
|
||||
concat_0 = cp.concat([t1, t2, t3], axis=0)
|
||||
print(f"Concatenate along axis 0: shape={concat_0.shape}")
|
||||
assert concat_0.shape == (6, 2)
|
||||
assert concat_0[4, 1] == 10
|
||||
|
||||
concat_1 = cp.concat([t1, t2, t3], axis=1)
|
||||
print(f"Concatenate along axis 1: shape={concat_1.shape}")
|
||||
assert concat_1.shape == (2, 6)
|
||||
assert concat_1[1, 4] == 11
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_tensor_basic()
|
||||
print("All tests completed!")
|
||||
|
|
|
|||
|
|
@ -114,6 +114,19 @@ def test_sort_vector():
|
|||
assert ref == result
|
||||
|
||||
|
||||
def test_vector_concat():
|
||||
print("Test vector concatenation")
|
||||
v1 = cp.vector([1, 2, 3])
|
||||
v2 = cp.vector([4, 5])
|
||||
v3 = cp.vector([6])
|
||||
|
||||
concat_vec = cp.concat([v1, v2, v3])
|
||||
print(f"Concatenate vectors: shape={concat_vec.shape}")
|
||||
assert concat_vec.shape == (6,)
|
||||
assert concat_vec[4] == 5
|
||||
print()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#test_vectors_init()
|
||||
#test_compiled_vectors()
|
||||
|
|
|
|||
Loading…
Reference in New Issue