Skip to content

Commit

Permalink
Optimize vec_transform (#102)
Browse files Browse the repository at this point in the history
* optimize vec_transform

* bump version
  • Loading branch information
Korijn authored Feb 22, 2025
1 parent f162fcb commit ca09f0c
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 11 deletions.
28 changes: 19 additions & 9 deletions pylinalg/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ def vec_homogeneous(vectors, /, *, w=1, out=None, dtype=None) -> np.ndarray:
return out


def vec_transform(vectors, matrix, /, *, w=1, out=None, dtype=None) -> np.ndarray:
def vec_transform(
vectors, matrix, /, *, w=1, projection=True, out=None, dtype=None
) -> np.ndarray:
"""
Apply a transformation matrix to a vector.
Expand All @@ -86,6 +88,9 @@ def vec_transform(vectors, matrix, /, *, w=1, out=None, dtype=None) -> np.ndarra
The value of the scale component of the homogeneous coordinate. This
affects the result of translation transforms. use 0 (vectors) if the
translation component should not be applied, 1 (positions) otherwise.
projection : bool, optional
If False, the matrix is assumed to be purely affine
and the homogeneous component is not applied. Default is True.
out : ndarray, optional
A location into which the result is stored. If provided, it must have a
shape that the inputs broadcast to. If not provided or None, a
Expand All @@ -102,17 +107,22 @@ def vec_transform(vectors, matrix, /, *, w=1, out=None, dtype=None) -> np.ndarra

matrix = np.asarray(matrix)

vectors = vec_homogeneous(vectors, w=w, dtype=float)
result = matrix @ vectors[..., None]
result /= result[..., -1, :][..., None, :]
result = result[..., :-1, 0]

if out is not None:
out[:] = result
if projection:
vectors = vec_homogeneous(vectors, w=w, dtype=float)
vectors @= matrix.T
vectors[..., :-1] /= vectors[..., -1, None]
vectors = vectors[..., :-1]
else:
out = result
vectors = np.asarray(vectors, dtype=float, copy=True)
vectors @= matrix[:-1, :-1].T
vectors += matrix[:-1, -1]

if out is None:
out = vectors
if dtype is not None:
out = out.astype(dtype, copy=False)
else:
out[:] = vectors

return out

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

[project]
name = "pylinalg"
version = "0.6.4"
version = "0.6.5"
description = "Linear algebra utilities for Python"
readme = "README.md"
license = { file = "LICENSE" }
Expand Down
30 changes: 29 additions & 1 deletion tests/test_vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def test_vector_apply_translation():
vectors = np.array([[1, 0, 0]])
expected = np.array([[0, 2, 2]])
matrix = la.mat_from_translation([-1, 2, 2])
result = la.vec_transform(vectors, matrix)
result = la.vec_transform(vectors, matrix, projection=False)

npt.assert_array_almost_equal(
result,
Expand All @@ -92,6 +92,34 @@ def test_vec_transform_out():
assert result is out


def test_vec_transform_projection_flag():
vectors = np.array(
[
[1, 0, 0],
[1, 2, 3],
[1, 1, 1],
[0, 0, 0],
[7, 8, -9],
],
dtype="f4",
)
translation = np.array([-1, 2, 2], dtype=float)
expected = vectors + translation[None, :]

matrix = la.mat_from_translation(translation)

for projection in [True, False]:
for batch in [True, False]:
if batch:
vectors_in = vectors
expected_out = expected
else:
vectors_in = vectors[0]
expected_out = expected[0]
result = la.vec_transform(vectors_in, matrix, projection=projection)
npt.assert_array_equal(result, expected_out)


@given(ct.test_spherical, none())
@example((1, 0, np.pi / 2), (0, 0, 1))
@example((1, np.pi / 2, np.pi / 2), (1, 0, 0))
Expand Down

0 comments on commit ca09f0c

Please sign in to comment.