Skip to content

Commit 564716a

Browse files
Support more numpy functions (#2210)
* support linalg.diagonal, diag, linalg.eigvals * support linalg.eigvalsh, linalg.matrix_norm, linalg.vector_norm * support vander, linalg.matrix_transpose, tril, triu, linalg.lstsq * if numpy's submodule doesn't contain a certain function, ignore it * add tests * vector_norm and matrix_norm only supports numpy 2.0+ * CHANGES, don't support inv for now
1 parent bfb7fef commit 564716a

File tree

3 files changed

+100
-1
lines changed

3 files changed

+100
-1
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ Pint Changelog
77
- Fix raising exception in `Quantity.from_tuple` with invalid units (#2199)
88
- Add devcontainer.json to add GitHub Codespace support (#2208)
99
- Add support for `numpy.geomspace` (#2206)
10+
- Add support for `linalg.diagonal`, `linalg.matrix_transpose`, `diag`, `tril`, `triu`, `linalg.eigvals`, `linalg.eigvalsh`, `linalg.matrix_norm` and `linalg.vector_norm` (#2210)
1011

1112

1213
0.25.0 (2025-08-25)

pint/facets/numpy/numpy_func.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,9 @@ def implement_func(func_type, func_str, input_units=None, output_unit=None):
284284
if func is None:
285285
return
286286
for func_str_piece in func_str_split[1:]:
287-
func = getattr(func, func_str_piece)
287+
func = getattr(func, func_str_piece, None)
288+
if func is None:
289+
return
288290

289291
@implements(func_str, func_type)
290292
def implementation(*args, **kwargs):
@@ -885,6 +887,7 @@ def implementation(*args, **kwargs):
885887
("moveaxis", "a", True),
886888
("around", "a", True),
887889
("diagonal", "a", True),
890+
("linalg.diagonal", "x", True),
888891
("mean", "a", True),
889892
("ptp", "a", True),
890893
("ravel", "a", True),
@@ -894,6 +897,7 @@ def implementation(*args, **kwargs):
894897
("median", "a", True),
895898
("nanmedian", "a", True),
896899
("transpose", "a", True),
900+
("linalg.matrix_transpose", "x", True),
897901
("roll", "a", True),
898902
("copy", "a", True),
899903
("average", "a", True),
@@ -1050,10 +1054,17 @@ def implementation(a, *args, **kwargs):
10501054
# Handle functions with output unit defined by operation
10511055
for func_str in (
10521056
"sum",
1057+
"diag",
1058+
"tril",
1059+
"triu",
10531060
"nansum",
10541061
"cumsum",
10551062
"nancumsum",
10561063
"linalg.norm",
1064+
"linalg.eigvals",
1065+
"linalg.eigvalsh",
1066+
"linalg.matrix_norm",
1067+
"linalg.vector_norm",
10571068
):
10581069
implement_func("function", func_str, input_units=None, output_unit="sum")
10591070
for func_str in ("diff", "ediff1d", "std", "nanstd"):

pint/testsuite/test_numpy.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,13 @@ def test_transpose_numpy_func(self):
139139
np.transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m
140140
)
141141

142+
@helpers.requires_array_function_protocol()
143+
@helpers.requires_numpy_at_least("2.0")
144+
def test_linalg_matrix_transpose(self):
145+
helpers.assert_quantity_equal(
146+
np.linalg.matrix_transpose(self.q), [[1, 3], [2, 4]] * self.ureg.m
147+
)
148+
142149
@helpers.requires_array_function_protocol()
143150
def test_flip_numpy_func(self):
144151
helpers.assert_quantity_equal(
@@ -576,6 +583,25 @@ def test_exponentiation_array_exp_2(self):
576583
with pytest.raises(DimensionalityError):
577584
op.ipow(arr_cp, q_cp)
578585

586+
# Advanced matrix operations
587+
def test_eigvals(self):
588+
q = [[2, 1], [1, 2]] * self.ureg.m
589+
helpers.assert_quantity_equal(np.linalg.eigvals(q), [3, 1] * self.ureg.m)
590+
591+
def test_eigvals_offset(self):
592+
q = self.Q_([[2, 1], [1, 2]], self.ureg.degC)
593+
with pytest.raises(OffsetUnitCalculusError):
594+
np.linalg.eigvals(q)
595+
596+
def test_eigvalsh(self):
597+
q = [[5 + 2j, 9 - 2j], [0 + 2j, 2 - 1j]] * self.ureg.m
598+
helpers.assert_quantity_equal(np.linalg.eigvalsh(q), [1, 6] * self.ureg.m)
599+
600+
def test_eigvalsh_offset(self):
601+
q = self.Q_([[5 + 2j, 9 - 2j], [0 + 2j, 2 - 1j]], self.ureg.degC)
602+
with pytest.raises(OffsetUnitCalculusError):
603+
np.linalg.eigvalsh(q)
604+
579605

580606
class TestNumpyUnclassified(TestNumpyMethods):
581607
def test_tolist(self):
@@ -650,6 +676,53 @@ def test_diagonal_numpy_func(self):
650676
q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m
651677
helpers.assert_quantity_equal(np.diagonal(q, offset=-1), [1, 2] * self.ureg.m)
652678

679+
@helpers.requires_array_function_protocol()
680+
@helpers.requires_numpy_at_least("2.0")
681+
def test_linalg_diagonal(self):
682+
q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m
683+
helpers.assert_quantity_equal(
684+
np.linalg.diagonal(q, offset=-1), [1, 2] * self.ureg.m
685+
)
686+
687+
@helpers.requires_array_function_protocol()
688+
def test_tril(self):
689+
q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m
690+
helpers.assert_quantity_equal(
691+
np.tril(q), [[1, 0, 0], [1, 2, 0], [1, 2, 3]] * self.ureg.m
692+
)
693+
694+
@helpers.requires_array_function_protocol()
695+
def test_tril_offset(self):
696+
q = self.Q_([[1, 2, 3], [1, 2, 3], [1, 2, 3]], self.ureg.degC)
697+
with pytest.raises(OffsetUnitCalculusError):
698+
np.tril(q)
699+
700+
@helpers.requires_array_function_protocol()
701+
def test_triu(self):
702+
q = [[1, 2, 3], [1, 2, 3], [1, 2, 3]] * self.ureg.m
703+
helpers.assert_quantity_equal(
704+
np.triu(q), [[1, 2, 3], [0, 2, 3], [0, 0, 3]] * self.ureg.m
705+
)
706+
707+
@helpers.requires_array_function_protocol()
708+
def test_triu_offset(self):
709+
q = self.Q_([[1, 2, 3], [1, 2, 3], [1, 2, 3]], self.ureg.degC)
710+
with pytest.raises(OffsetUnitCalculusError):
711+
np.triu(q)
712+
713+
@helpers.requires_array_function_protocol()
714+
def test_diag(self):
715+
q = [1, 2, 3] * self.ureg.m
716+
helpers.assert_quantity_equal(
717+
np.diag(q), [[1, 0, 0], [0, 2, 0], [0, 0, 3]] * self.ureg.m
718+
)
719+
720+
@helpers.requires_array_function_protocol()
721+
def test_diag_offset(self):
722+
q = self.Q_([1, 2, 3], self.ureg.degC)
723+
with pytest.raises(OffsetUnitCalculusError):
724+
np.diag(q)
725+
653726
def test_compress(self):
654727
helpers.assert_quantity_equal(
655728
self.q.compress([False, True], axis=0), [[3, 4]] * self.ureg.m
@@ -1509,6 +1582,20 @@ def test_linalg_norm(self):
15091582
expected = [5, 13, 17] * self.ureg.m
15101583
helpers.assert_quantity_equal(np.linalg.norm(q, axis=0), expected)
15111584

1585+
@helpers.requires_array_function_protocol()
1586+
@helpers.requires_numpy_at_least("2.0")
1587+
def test_linalg_vector_norm(self):
1588+
q = np.array([[3, 5, 8], [4, 12, 15]]) * self.ureg.m
1589+
expected = [5, 13, 17] * self.ureg.m
1590+
helpers.assert_quantity_equal(np.linalg.vector_norm(q, axis=0), expected)
1591+
1592+
@helpers.requires_array_function_protocol()
1593+
@helpers.requires_numpy_at_least("2.0")
1594+
def test_linalg_matrix_norm(self):
1595+
helpers.assert_quantity_equal(
1596+
np.linalg.matrix_norm(self.q, ord=1), 6 * self.ureg.m
1597+
)
1598+
15121599
@helpers.requires_array_function_protocol()
15131600
def test_geomspace(self):
15141601
start = 1 * self.ureg.m

0 commit comments

Comments
 (0)