Arithmetic & Comparisons#
Arithmetic#
All tensor types with arithmetic support +, -, *, /, **,
unary -, and their in-place counterparts +=, -=, *=, /=,
**=. Numeric tensors (FloatTensor, IntTensor) additionally support
floor division // and //=.
Scalar arithmetic#
Every operator accepts a scalar on either side:
X = mpcf.FloatTensor(np.array([1.0, 2.0, 3.0]))
Y = X * 2.0 # [2.0, 4.0, 6.0]
Z = 10.0 + X # [11.0, 12.0, 13.0]
W = 10.0 / X # [10.0, 5.0, 3.33...]
X /= 5.0 # in-place: [0.2, 0.4, 0.6]
PCF tensors support all four operators with both Pcf operands (pointwise)
and numeric scalars. Scalar + and - shift the values, while * and
/ scale them:
X = mpcf.zeros((5,))
# ... fill X with PCFs ...
X * 3.0 # scale values
X + 10.0 # shift values up
1.0 / X # elementwise reciprocal
-X # negate
X + some_pcf # pointwise PCF addition
Power#
The ** operator raises every element to a given exponent. It works for both
numeric and PCF tensors:
X = mpcf.FloatTensor(np.array([4.0, 9.0, 16.0]))
Y = X ** 0.5 # [2.0, 3.0, 4.0]
X **= 2 # in-place: [16.0, 81.0, 256.0]
F = mpcf.zeros((5,))
# ... fill F with PCFs ...
G = F ** 2 # square every PCF's values
F **= 3 # cube in place
A RuntimeWarning is emitted if the result contains NaN or infinity (e.g.
raising a negative value to a fractional power).
Division#
For FloatTensor, / performs true division as expected:
X = mpcf.FloatTensor(np.array([10.0, 21.0, 35.0]))
X / 4.0 # [2.5, 5.25, 8.75]
For IntTensor, / returns a FloatTensor (float64), matching NumPy:
A = mpcf.IntTensor(np.array([10, 21, 35]))
A / 4 # FloatTensor: [2.5, 5.25, 8.75]
Floor division (//) rounds down to the nearest integer (e.g. 7 // 2 = 3
and -7 // 2 = -4). It is available on FloatTensor and IntTensor,
matching NumPy:
X = mpcf.FloatTensor(np.array([10.5, -7.3, 21.0]))
X // 3.0 # [3.0, -3.0, 7.0]
A = mpcf.IntTensor(np.array([10, -7, 21]))
A // 3 # IntTensor: [3, -3, 7]
Tensor-tensor arithmetic (broadcasting)#
When both operands are tensors of the same type, elementwise arithmetic is performed with NumPy-style broadcasting:
Shapes are compared dimension-by-dimension from the right.
Dimensions match if they are equal, or one of them is 1.
A missing leading dimension is treated as size 1.
import numpy as np
import masspcf as mpcf
A = mpcf.FloatTensor(np.array([[1.0, 2.0, 3.0],
[4.0, 5.0, 6.0]])) # shape (2, 3)
B = mpcf.FloatTensor(np.array([10.0, 20.0, 30.0])) # shape (3,)
C = A + B # shape (2, 3) — B is broadcast along dim 0
# C == [[11, 22, 33],
# [14, 25, 36]]
Both operands can be expanded at the same time:
col = mpcf.FloatTensor(np.array([[1.0], [2.0]])) # shape (2, 1)
row = mpcf.FloatTensor(np.array([[10.0, 20.0, 30.0]])) # shape (1, 3)
result = col + row # shape (2, 3)
# result == [[11, 21, 31],
# [12, 22, 32]]
In-place operators (+=, -=, *=, /=) broadcast the right-hand
side but never expand the left-hand side — the output shape must equal the
shape of the left operand, just like NumPy:
A += B # OK: (2,3) + (3,) -> (2,3) matches A
# B += A # ValueError: (3,) + (2,3) -> (2,3) != (3,)
Incompatible shapes raise ValueError:
X = mpcf.FloatTensor(np.array([1.0, 2.0, 3.0]))
Y = mpcf.FloatTensor(np.array([1.0, 2.0]))
# X + Y -> ValueError: shapes (3,) and (2,) are not broadcast-compatible
Broadcasting also works with PCF tensors:
F = mpcf.zeros((4, 10))
# ... fill F with PCFs ...
bias = mpcf.zeros((10,))
# ... fill bias ...
adjusted = F + bias # shape (4, 10) — bias broadcast along dim 0
broadcast_to#
For advanced use, broadcast_to() returns
a view of a tensor as if it had the given shape. Size-1 dimensions are
virtually repeated without copying data:
X = mpcf.FloatTensor(np.array([1.0, 2.0, 3.0])) # shape (3,)
view = X.broadcast_to((4, 3)) # shape (4, 3)
# Every row of view is [1, 2, 3]; view shares data with X
Comparisons#
Tensors support the comparison operators ==, !=, <, <=, >,
and >=. Each returns a BoolTensor containing the
element-wise result, just like NumPy:
import numpy as np
import masspcf as mpcf
A = mpcf.FloatTensor(np.array([1.0, 2.0, 3.0]))
B = mpcf.FloatTensor(np.array([1.0, 9.0, 3.0]))
result = A == B # BoolTensor: [True, False, True]
result = A < B # BoolTensor: [False, True, False]
Broadcasting#
Comparisons follow the same broadcasting rules as arithmetic. Shapes are compared dimension-by-dimension from the right, and size-1 or missing dimensions are expanded:
A = mpcf.FloatTensor(np.array([[1.0, 2.0],
[3.0, 4.0]])) # shape (2, 2)
B = mpcf.FloatTensor(np.array([1.0, 4.0])) # shape (2,)
result = A == B
# BoolTensor of shape (2, 2):
# [[True, False],
# [False, True]]
Column and scalar broadcasting also work:
col = mpcf.FloatTensor(np.array([[2.0], [3.0]])) # shape (2, 1)
result = A < col
# BoolTensor of shape (2, 2):
# [[True, False],
# [False, False]]
scalar = mpcf.FloatTensor(np.array([2.0])) # shape (1,)
result = A >= scalar
# BoolTensor of shape (2, 2):
# [[False, True],
# [True, True]]
Converting to Python bool#
Calling bool() on a single-element BoolTensor returns a Python bool.
For multi-element tensors, bool() raises ValueError, matching NumPy’s
behavior:
A = mpcf.FloatTensor(np.array([1.0]))
B = mpcf.FloatTensor(np.array([1.0]))
bool(A == B) # True
C = mpcf.FloatTensor(np.array([1.0, 2.0]))
D = mpcf.FloatTensor(np.array([1.0, 2.0]))
bool(C == D) # ValueError: more than one element
array_equal#
To check whether two tensors are entirely equal (as a single bool), use
array_equal():
A = mpcf.FloatTensor(np.array([1.0, 2.0, 3.0]))
B = A.copy()
A.array_equal(B) # True
A.array_equal(np.array([1.0, 2.0, 3.0])) # also accepts NumPy arrays
C = mpcf.FloatTensor(np.array([1.0, 9.0, 3.0]))
A.array_equal(C) # False
Tensors with different shapes always compare as not equal.
PCF comparisons#
Comparison operators also work on PCF tensors. Two PCFs are equal if they have the same breakpoints and values:
F = mpcf.random.noisy_sin((3,))
G = F.copy()
result = F == G # BoolTensor: [True, True, True]
F.array_equal(G) # True