TensorPy is a lightweight, NumPy-inspired numerical computing library that offers a simplified and customizable array-processing framework. Built for prototyping and experimentation, TensorPy implements key features such as array creation, reshaping, linear algebra operations, mathematical functions, and broadcasting, while highlighting its internal logic and computational details.
Common Parameters:
shape: Tuple or integer specifying array dimensionsdtype: Data type (int or float), defaults to intobject: List/tuple to initialize arraya: Input array to copy
Functions:
-
.empty(shape, dtype=int):- Creates an uninitialized array of given shape
- Contents are arbitrary until explicitly set
-
.array(object, dtype=int):- Creates an array from a list/tuple
- Example:
.array([1,2,3]) → [1,2,3]
-
.zeros(shape, dtype=int):- Creates an array filled with 0
- Example:
.zeros((2,2)) → [[0,0],[0,0]]
-
.ones(shape, dtype = int):- Creates an array filled with ones
-
.copy(a):- Create a copy of the input array
-
.arange(start, stop, step, dtype=int):- Creates evenly spaced values within the interval
- Similar to Python's range() but returns array
- Example:
Tensor.arange(0, 5, 1) # → [0, 1, 2, 3, 4] Tensor.arange(0.5, 2.0, 0.5) # → [0.5, 1.0, 1.5] - Corresponding method:
.arange(args) - Challenge: float arithmetic (floating points are covered -> rounding up after addition)
-
.reshape(a, newshape):- Reshapes the array without changing the data
- Example:
Tensor.reshape([1,2,3,4], (2,2)) → [[1,2],[3,4]] - Corresponding method:
.reshape() - Challenge: check irregular/inconsistent input list
-
.ndarray.flat ~= .ndarray.flatten(order = 'C' | 'F'):- Flatten all the elements in the input matrix into a list
- Example:
t = Tensor([[1, 2], [3, 4]]) t.flatten(order='C') # → [1, 2, 3, 4] t.flatten(order='F') # → [1, 3, 2, 4] - Challenge: F-style flattening
-
.ndim():- One of the core methods of
Tensorclass, retrieving the number of dimensions (n)Tensor([[1, 2], [3, 4]]).ndim() # → 2 Tensor([1, 2, 3]).ndim() # → 1 - Corresponding methods:
.flatten(order = 'C', 'F):.multi_processing_flatten(order = 'C', 'F')
- One of the core methods of
-
.ndarray.T ~= .ndarray.transpose(axes : tuple = Optional):- Example:
Tensor([[1, 2], [3, 4]]).transpose() # → [[1, 3], [2, 4]] Tensor([[[1, 2], [3, 4]], [[5, 6], [7, 8]]]).transpose((1, 0, 2)) # -> [[[1, 2], [5, 6]], [[3, 4], [7, 8]]] - Corresponding methods:
.transpose():.multi_processing_transpose():
- Challenge: Flip the axes with the given order (especially with increasing count of array dimensions)
- Example:
.dot(A, B):-
Output the dot product of any two N-D arrays
A,B -
Example:
A = [[ [1, 2, 3], [4, 5, 6] ], [ [7, 8, 9], [10, 11, 12] ]] B = [[1, 2], [3, 4], [5, 6]] -> A . B = [[ [22, 28], [49, 64] ], [ [76, 100], [103, 136] ]] -
Generic Formula:
1.1: Condition:
dk(last dimension ofA) must matchel-1(second-to-last dimension ofB)1.2: Final shape:
(d1, ..., dk-1, e1, ..., el-2, el)(remove matching dimensiondk)1.3:
C_{i1,..., ik-1, j1,..., jl-2} = \sum_{m}A_{i1,..., ik-1, m} . B_{j1, ..., jl-2, m, jl}1.3.1: Example:
-
Corresponding method:
.iter_dot()
-
-
.prod(): -
.sum(): Similar to.prod()-
General Formula
R[i][j][k] = A[i][j][0][k] + A[i][j][1][k] +... + A[i][j][len(shape[axis]) - 1][k] -
Corresponding method:
.iter_sum()
-
-
.lcm(): -
.gcd(): -
.add(A, B):-
General Formula:
7.1: Case 1: Both A and B are scalar, return
A + B7.2: Case 2: Either A or B is scalar:
A(i, j, ..., m) = A(i, j, ..., m) + B, given:s = A's shape, 0 <= i < s[0], 0 <= j < s[1], etc7.3: Case 3: Both A and B are M-D array and N-D array:
A (d1, d2, ..., dm); B (e1, e2, ..., en)-
General Formula:
C_{i1, i2, ..., ik} = A_{j1, j2, ..., jn} + B_{l1, l2, ..., lm} -
7.3.1: Pad shapes with 1s to make A, B equally dimensional:
sA = (1,1,..,d1,...,dm); sB = (1,1,..., e1,..., en) -
7.3.2: Find the final shape: Rule 1: For each
d_iore_jthat is missing or equal to 1, we can treat it as 1 and select the higher-dimensional dimension of the other array, since in pure math, this aims to broadcast the final shape to get the higher dimension. For i.e:A (2,3) = [ [1,2,3] [4,5,6] ] B (3,) = [1,2,3]- The final shape is
(2,3)sinceBis broadcasted to shape (1,3) then (2,3) - After broadcasting, we have:
A (+) B = [ [1,2,3] [4,5,6] ] (+) [ [1,2,3] [1,2,3] ]Rule 2: For any pair of matching dimensions between
AandB, we select this dimension for our final shape. - The final shape is
-
7.3.3: Get the indices from the final shape:
Rule 3: For the dimension that is broadcast, add
0to the corresponding index of the coordinate.Rule 4: To find the actual coordinates in the original
AandB, remove the padded 1(s) out of the final coordinates (which are previously generated from the final shape of the result tensor).
-
-
Corresponding method:
.add()
-
-
.divide(): -
.pow(): -
.subtract(): -
.max(): -
.min(): -
.sqrt(): -
.positive(): -
.negative():
- General Formula:
A(i, j, k,..., n) = (-1) * A(i, j, k,..., n) - Override the magic (dunder) method
__neg__, which can be found by printing some built-in methods (as shown below) -
print(dir(int)) # Output: ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '_...] - Example:
A = 4 -> A1 = -A = -4 A = [[1, 2, 3]] -> A2 = -A = [[-1, -2, -3]]
.logical_and(A: cond, B: cond) -> list[bool]:
- General Form:
A(d1, d2, ..., dk,..., dm), B(e1, e2, ..., en-2, en)C[d1][d2]..[dk][ei+1]..[en] = A[d1][d2]..[0 -> dk]..[dm] & B[e1]..[0 -> ei]...[en](applying padding and broadcasting as.sum(*args),.dot(*arg)).
- Since the passing parameters can be 2 conditions, we need to construct and rewrite
__lt__, and__gt__for the basic operators of comparisons - 2 sub-methods:
__lt__(threshold: Union[int, float]) -> list[bool]__gt__(threshold: Union[int, float]) -> list[bool]
- Example:
A = [1, 2, 3, 4] B = [1, 2, 3, 4] tensor_a = Tensor(A) tensor_b = Tensor(B) gt_result = tensor_a > 1 # [False, True, True, True] lt_result = tensor_b < 4 # [True, True, True, False] combined = gt_result & lt_result # [False, True, True, False] expected = [False, True, True, False]
.logical_or():
.poly1d(c_or_r, r=False, variable=None)- Constructor of the polynomial with the input coefficient list
p = np.poly1d([1, 2, 3]) print(np.poly1d(p)) # x^2 + 2x + 3 - Evaluate the polynomial at a given value of
xp(0.5) # 4.25 - Show the coefficients
p.c # array([1, 2, 3]) - Show the co-efficient of the kth power in the polynomial
p[1] # 2 - Polynomials can be added, subtracted, multiplied, and divided
p * p # poly1d([ 1, 4, 10, 12, 9]) - Construct a polynomial from its roots
Polynomial.poly1d([1, 2], True) # poly1d([ 1., -3., 2.]) - Find the power of a polynomial with the given exponent
p ** 2 # poly1d([ 1, 4, 10, 12, 9])
- Constructor of the polynomial with the input coefficient list
.polyadd:p1 = np.poly1d([1, 2]) p2 = np.poly1d([9, 5, 4]) print(p1) print(p2) print(np.polyadd(p1, p2)) -> Return [9,6,6] # Polynomial Object using .get_coeffs().polymul:p1 = np.poly1d([1, 2, 3]) p2 = np.poly1d([9, 5, 1]) print(p1) print(p2) print(np.polymul(p1, p2)) # Return [ 9, 23, 38, 17, 3] # Polynomial Object using .get_coeffs()
- Try some approaches: buffer, I/O, mmap, etc.
- Learn the differences (benefits / trade-offs) among those approaches
- Ref: https://docs.python.org/3/library/mmap.html
.load().save()
-
.sort()- General Form:
A(i1, i2, ..., in) = sorted A(d1, d2, ..., dx, ..., dn) # x = input axis
- General Form:
-
.partition():- Ref: https://github.com/mathics/Mathics/blob/master/mathics/algorithm/introselect.py (for default introselect)
-
.where(condition: Union[list, bool], x, y)- General Form:
-
Case 1:
cond: list[bool]Z (i, j, ..., n) = if cond[i][j]...[n] then X[i][j]...[n] else Y[i][j]...[n] -
Case 2:
cond: boolZ = X if condition = True else Y
-
- General Form:

