vg¶
vg is a very good vector-geometry toolbelt for dealing with 3D points and vectors.
Motivation¶
Linear algebra provides a powerful toolset for a wide range of problems, including geometric problems, which are the focus of this library. Though some programmers need to know the math that powers these solutions, often understanding the abstractions is enough. Furthermore, the abstractions express these operations in highly readable forms which clearly communicate the programmer’s intention, regardless of the reader’s math background.
The goal of vg is to help Python programmers leverage the power of linear algebra to carry out vector-geometry operations. It produces code which is easy to write, understand, and maintain.
NumPy is powerful – and also worth learning! However it’s easy to get
slowed down by technical concerns like broadcasting and indexing, even
when working on basic geometric operations. vg is friendlier: it’s
NumPy for humans. It checks that your inputs are structured correctly for the
narrower use case of 3D geometry, and then it “just works.”
For example, vg.euclidean_distance()
is invoked the same for two stacks
of points, a stack and a point, or a simple pair of points.
In the name of readability, efficiency is not compromised. You’ll find these operations as suited to production code as one-off scripts. If you find anything is dramatically slower than a lower-level equivalent, please let us know so we can fix it!
Functions¶
All functions are optionally vectorized, meaning they accept single inputs and stacks of inputs interchangeably. They return The Right Thing – a single result or a stack of results – without the need to reshape inputs or outputs. With the power of NumPy, the vectorized functions are fast.
- vg.aligned_with(vector, along, reverse=False)¶
Given two vectors, flip the first if necessary, so that it points (approximately) along the second vector rather than (approximately) opposite it.
- Parameters
vector (np.arraylike) – A vector in R^3.
along (np.arraylike) – A second vector in R^3.
reverse (bool) – When True, reverse the logic, returning a vector that points against along.
- Returns
Either vector or -vector.
- Return type
np.arraylike
- vg.almost_collinear(v1, v2, atol=1e-08)¶
Test if v1 and v2 are almost collinear.
This will return true if either v1 or v2 is the zero vector, because mathematically speaking, the zero vector is collinear to everything.
Geometrically that doesn’t necessarily make sense, so if you want to handle zero vectors specially, you can test your inputs with vg.almost_zero().
- vg.almost_equal(v1, v2, atol=1e-08)¶
Test if v1 and v2 are equal within the given absolute tolerance.
- vg.almost_unit_length(vector, atol=1e-08)¶
Test if the vector has almost unit length. For a stacked input, test each one.
- Parameters
vector (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
- Returns
For a (3,) input, a bool. For a kx3 input, a (k,) array.
- Return type
object
- vg.almost_zero(v, atol=1e-08)¶
Test if v is almost the zero vector.
- vg.angle(v1, v2, look=None, assume_normalized=False, units='deg')¶
Compute the unsigned angle between two vectors. For a stacked input, the angle is computed pairwise.
When look is provided, the angle is computed in that viewing plane (look is the normal). Otherwise the angle is computed in 3-space.
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A vector or stack of vectors with the same shape as v1.
look (np.arraylike) – A (3,) vector specifying the normal of a viewing plane, or None to compute the angle in 3-space.
assume_normalized (bool) – When True, assume the input vectors are unit length. This improves performance, however when the inputs are not normalized, setting this will cause an incorrect results.
units (str) – ‘deg’ to return degrees or ‘rad’ to return radians.
- Returns
For a (3,) input, a float with the angle. For a kx3 input, a (k,) array.
- Return type
object
- vg.apex(points, along)¶
Find the most extreme point in the direction provided.
- Parameters
points (np.arraylike) – A kx3 stack of points in R^3.
along (np.arraylike) – A (3,) vector specifying the direction of interest.
- Returns
A copy of a point taken from points.
- Return type
np.ndarray
- vg.apex_and_opposite(points, along)¶
Find the most extreme point in the direction provided and the most extreme point in the opposite direction.
- Parameters
points (np.arraylike) – A kx3 stack of points in R^3.
along (np.arraylike) – A (3,) vector specifying the direction of interest.
- Returns
A 2x3 vector containing the apex and opposite points.
- Return type
np.ndarray
- vg.argapex(points, along)¶
Find the index of the most extreme point in the direction provided.
- Parameters
points (np.arraylike) – A kx3 stack of points in R^3.
along (np.arraylike) – A (3,) vector specifying the direction of interest.
- Returns
The index of the most extreme point.
- Return type
int
- vg.average(values, weights=None, ret_sum_of_weights=False)¶
Compute a weighted or unweighted average of the 3D input values. The inputs could be points or vectors.
- Parameters
values (np.arraylike) – A kx3 stack of vectors.
weights (array-convertible) – An optional k array of weights.
ret_sum_of_weights (bool) – When True, the sum of the weights is returned. When weights is None, this is the number of elements over which the average is taken.
- Returns
A (3,) vector with the weighted or unweighted average.
- Return type
np.ndarray
- vg.cross(v1, v2)¶
Compute individual or pairwise cross products.
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A (3,) vector or a kx3 stack of vectors. If stacks are provided for both v1 and v2 they must have the same shape.
- vg.dot(v1, v2)¶
Compute individual or pairwise dot products.
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A (3,) vector or a kx3 stack of vectors. If stacks are provided for both v1 and v2 they must have the same shape.
- vg.euclidean_distance(v1, v2)¶
Compute Euclidean distance, which is the distance between two points in a straight line. This can be done individually by passing in single point for either or both arguments, or pairwise by passing in stacks of points.
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A (3,) vector or a kx3 stack of vectors. If stacks are provided for both v1 and v2 they must have the same shape.
- Returns
When both inputs are (3,), a float with the distance. Otherwise a (k,) array.
- Return type
object
- vg.farthest(from_points, to_point, ret_index=False)¶
Find the point farthest from the given point.
- Parameters
from_points (np.arraylike) – A kx3 stack of points in R^3.
to_point (np.arraylike) – A (3,) point of interest.
ret_index (bool) – When True, return both the point and its index.
- Returns
A (3,) vector taken from from_points.
- Return type
np.ndarray
- vg.magnitude(vector)¶
Compute the magnitude of vector. For a stacked input, compute the magnitude of each one.
- Parameters
vector (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
- Returns
- For a (3,) input, a float with the magnitude. For a kx3
input, a (k,) array.
- Return type
object
- vg.major_axis(coords)¶
Compute the first principal component of the input coordinates. This is the vector which best describes the multidimensional data using a single dimension.
- Parameters
coords (np.arraylike) – A nxk stack of coordinates.
- Returns
A (k,) vector.
- Return type
np.ndarray
- vg.nearest(from_points, to_point, ret_index=False)¶
Find the point nearest to the given point.
- Parameters
from_points (np.arraylike) – A kx3 stack of points in R^3.
to_point (np.arraylike) – A (3,) point of interest.
ret_index (bool) – When True, return both the point and its index.
- Returns
A (3,) vector taken from from_points.
- Return type
np.ndarray
- vg.normalize(vector)¶
Return the vector, normalized.
If vector is 2d, treats it as stacked vectors, and normalizes each one.
- vg.perpendicular(v1, v2, normalized=True)¶
Given two noncollinear vectors, return a vector perpendicular to both.
Result vectors follow the right-hand rule. When the right index finger points along v1 and the right middle finger along v2, the right thumb points along the result.
When one or both sets of inputs is stacked, compute the perpendicular vectors elementwise, returning a stacked result. (e.g. when v1 and v2 are both stacked, result[k] is perpendicular to v1[k] and v2[k].)
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A (3,) vector or a kx3 stack of vectors. If stacked, the shape must be the same as v1.
normalized (bool) – When True, the result vector is guaranteed to be unit length.
- Returns
An array with the same shape as v1 and v2.
- Return type
np.arraylike
- vg.principal_components(coords)¶
Compute the principal components of the input coordinates. These are useful for dimensionality reduction and feature modeling.
- Parameters
coords (np.arraylike) – A nxk stack of coordinates.
- Returns
A kxk stack of vectors.
- Return type
np.ndarray
- vg.project(vector, onto)¶
Compute the vector projection of vector onto the vector onto.
onto need not be normalized.
- vg.reject(vector, from_v)¶
Compute the vector rejection of vector from from_v – i.e. the vector component of vector perpendicular to from_v.
from_v need not be normalized.
- vg.reject_axis(vector, axis, squash=False)¶
Compute the vector component of vector perpendicular to the basis vector specified by axis. 0 means x, 1 means y, 2 means z.
In other words, return a copy of vector that zeros the axis component.
When squash is True, instead of zeroing the component, it drops it, so an input vector (in R3) is mapped to a point in R2.
(N.B. Don’t be misled: this meaning of axis is pretty different from the typical meaning in numpy.)
- vg.rotate(vector, around_axis, angle, units='deg', assume_normalized=False)¶
Rotate a point or vector around a given axis. The direction of rotation around around_axis is determined by the right-hand rule.
- Parameters
vector (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
around_axis (np.arraylike) – A (3,) vector specifying the axis of rotation.
assume_normalized (bool) – When True, assume around_axis is unit length. This improves performance marginally, however when the inputs are not normalized, setting this will cause an incorrect results.
units (str) – ‘deg’ to specify angle in degrees or ‘rad’ to specify radians.
- Returns
- The transformed point or points. This has the same shape as
vector.
- Return type
np.arraylike
- vg.scalar_projection(vector, onto)¶
Compute the scalar projection of vector onto the vector onto.
onto need not be normalized.
- vg.scale_factor(v1, v2)¶
Given two parallel vectors, compute the scale factor k such that k * v1 is approximately equal to v2.
- Parameters
v1 (np.arraylike) – A vector in R^3 or a kx3 stack of vectors.
v2 (np.arraylike) – A second vector in R^3 or a kx3 stack of vectors. If v1 and v2 are both stacked, they must be the same shape.
- Returns
A float containing the scale factor k, or nan if v1 is the zero vector. If either input is stacked, the result will also be stacked.
- Return type
object
- vg.signed_angle(v1, v2, look, units='deg')¶
Compute the signed angle between two vectors. For a stacked input, the angle is computed pairwise.
Results are in the range -180 and 180 (or -math.pi and math.pi). A positive number indicates a clockwise sweep from v1 to v2. A negative number is counterclockwise.
- Parameters
v1 (np.arraylike) – A (3,) vector or a kx3 stack of vectors.
v2 (np.arraylike) – A vector or stack of vectors with the same shape as v1.
look (np.arraylike) – A (3,) vector specifying the normal of the viewing plane.
units (str) – ‘deg’ to return degrees or ‘rad’ to return radians.
- Returns
For a (3,) input, a float with the angle. For a kx3 input, a (k,) array.
- Return type
object
- vg.within(points, radius, of_point, atol=1e-08, ret_indices=False)¶
Select points within a given radius of a point.
- Parameters
points (np.arraylike) – A kx3 stack of points in R^3.
radius (float) – The radius of the sphere of interest centered on of_point.
of_point (np.arraylike) – The (3,) point of interest.
atol (float) – The distance tolerance. Points within radius + atol of of_point are selected.
ret_indexes (bool) – When True, return both the points and their indices.
- Returns
A (3,) vector taken from points.
- Return type
np.ndarray
- vg.shape.check(locals_namespace, name, shape)¶
Convenience function for invoking vg.shape.check_value() with a locals() dict.
- Parameters
namespace (dict) – A subscriptable object, typically locals().
name (str) – Key to pull from namespace.
shape (list) – Shape to validate. To require 3 by 1, pass (3,). To require n by 3, pass (-1, 3).
- Returns
The wildcard dimension (if one) or a tuple of wildcard dimensions (if more than one).
- Return type
object
Example
>>> def my_fun_function(points): ... vg.shape.check(locals(), 'points', (-1, 3)) ... # Proceed with confidence that `points` is a k x 3 array.
Example
>>> def my_fun_function(points): ... k = vg.shape.check(locals(), 'points', (-1, 3)) ... print(f"my_fun_function invoked with {k} points")
- vg.shape.check_value(arr, shape, name=None)¶
Check that the given argument has the expected shape. Shape dimensions can be ints or -1 for a wildcard. The wildcard dimensions are returned, which allows them to be used for subsequent validation or elsewhere in the function.
- Parameters
arr (np.arraylike) – An array-like input.
shape (list) – Shape to validate. To require an array with 3 elements, pass (3,). To require n by 3, pass (-1, 3).
name (str) – Variable name to embed in the error message.
- Returns
The wildcard dimension (if one) or a tuple of wildcard dimensions (if more than one).
- Return type
object
Example
>>> vg.shape.check_value(np.zeros((4, 3)), (-1, 3)) >>> # Proceed with confidence that `points` is a k x 3 array.
Example
>>> k = vg.shape.check_value(np.zeros((4, 3)), (-1, 3)) >>> k 4
- vg.shape.check_value_any(arr, *shapes, name=None)¶
Check that the given argument has any of the expected shapes. Shape dimensons can be ints or -1 for a wildcard.
- Parameters
arr (np.arraylike) – An array-like input.
shape (list) – Shape candidates to validate. To require an array with 3 elements, pass (3,). To require n by 3, pass (-1, 3).
name (str) – Variable name to embed in the error message.
- Returns
The wildcard dimension of the matched shape (if one) or a tuple of wildcard dimensions (if more than one). If the matched shape has no wildcard dimensions, returns None.
- Return type
object
Example
>>> k = check_shape_any(points, (3,), (-1, 3), name="points") >>> check_shape_any( reference_points_of_lines, (3,), (-1 if k is None else k, 3), name="reference_points_of_lines", )
- vg.shape.columnize(arr, shape=(- 1, 3), name=None)¶
Helper for functions which may accept a stack of points (kx3) returning a stack of results, or a single set of three points (3,) returning a single result.
For either kind of input, it returns the points as kx3, a boolean is_columnized, and a maybe_decolumnized function which can be applied to the result before returning it. For a columnized input this function does nothing, and for a non-columnized input, it decolumnizes it, producing the desired return value.
This is not limited to kx3. It can be used for different dimensional shapes like kx4, and even higher dimensional shapes like kx3x3.
Constants¶
r```{eval-rst} .. py:currentmodule:: vg
.. autodata:: basis :annotation:
.. py:currentmodule:: vg.basis
.. autodata:: x .. autodata:: neg_x .. autodata:: y .. autodata:: neg_y .. autodata:: z .. autodata:: neg_z
Style guide
-----------
Use the named secondary arguments. They tend to make the code more readable:
import vg
result = vg.proj(v1, onto=v2)
Design principles
-----------------
Linear algebra is useful and it doesn't have to be dificult to use. With the
power of abstractions, simple operations can be made simple, without poring
through lecture slides, textbooks, inscrutable Stack Overflow answers, or
dense NumPy docs. Code that uses linear algebra and geometric transformation
should be readable like English, without compromising efficiency.
These common operations should be abstracted for a few reasons:
1. If a developer is not programming linalg every day, they might forget the
underlying formula. These forms are easier to remember and more easily
referenced.
2. These forms tend to be self-documenting in a way that the NumPy forms are
not. If a developer is not programming linalg every day, this will again
come in handy.
3. These implementations are more robust. They automatically inspect `ndim`
on their arguments, so they work equally well if the argument is a vector
or a stack of vectors. They are more careful about checking edge cases
like a zero norm or zero cross product and returning a correct result
or raising an appropriate error.
Future-proofing your application or library
-------------------------------------------
This library adheres to [Semantic Versioning][semver].
[semver]: https://semver.org/
Since Python can accommodate only one installation of a package, using a
toolbelt like `vg` as a transitive dependency can be a particular challenge, as
various dependencies in the tree may rely on different versions of vg.
One option would be to avoid making breaking changes forevever. However this is
antithetical to one of the goals of the project, which is to make a friendly
interface for doing linear algebra. Experience has shown that over the years,
we get clearer about what does and doesn't belong in this library, and what ways
of exposing this functionality are easiest to learn. We want to continue to
improve the interface over time, even if it means small breaking changes.
As a result, we provide a forward compatibility layer, which all libraries
depending on `vg` are encouraged to use. Replace `import vg` with
`from vg.compat import v2 as vg` and use `>=2.0` as your dependency specifier.
You can also replace 2.0 with a later version which includes a feature you
need. The important thing is not to use `>=2.0,<3`. Since this project
guarantees that `from vg.compat import v2 as vg` will continue to work the same
in 3.0+, the `<3` constraint provides no stability value – and it makes
things unnecessarily difficult for consumers who use multiple dependencies with
`vg`.
Applications have two options:
1. Follow the recommendation for libraries: specify `>=2.0` and import using
`from vg.compat import v2 as vg`. This option provides better code stability
and makes upgrades seamless.
2. Specify `>=2,<3` and use `import vg` directly, and when upgrading to
`>=3,<4`, review the changelog and modify the calling code if necessary.
This option ensures you stay up to date with the recommended, friendliest
interface for calling into `vg`.
### Breaking changes
The project's goal is to limit breaking changes to the API to every one to two
years. This means breaking changes must be batched. Typically such features are
first made available under the `vg.experimental` module, and then moved into
`vg` upon the next major version release. Such experimental features may change
in any subsequent minor release.
### Deprecations
Deprecated features will emit deprecation warnings in a minor version and cause
errors or incorrect behavior in the next major version. No maintenance is
provided on deprecated APIs.
If you like vg you might also like …
-------------------------------------------
### [polliwog][]
Polliwog is a 2D and 3D computational geometry library. Like vg, it's designed
to scale from prototyping to production. It includes vectorized geometric
operations, transforms, and primitives like planes, polygonal chains, and
axis-aligned bounding boxes. Implemented in pure Python/NumPy. It is depends on
vg and is lightweight and fast.
*Note:* vg is limited in scope to dealing with 3D points and vectors. Almost
anything more complicated is considered general computational geometry, and goes
in polliwog instead.
### [ounce][]
Fast, simple, non-fancy, and non-magical package for manipulating units of
measure. A faster and less fancy counterpart to [Pint][].
[polliwog]: https://polliwog.readthedocs.io/en/latest/
[ounce]: https://ounce.readthedocs.io/en/latest/
[pint]: https://pint.readthedocs.io/en/stable/