Coverage for src \ pavpen \ geometry_calculator \ vector_field_operations.py: 100%
45 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com>
2#
3# SPDX-License-Identifier: MIT
5from abc import ABC, abstractmethod
8class VectorFieldOperations[Scalar, Vector](ABC):
9 """Provides an implementation of vector operations for a given data type
10 which can be interpreted as storing vectors
12 Having this as a separate object allows decoupling the data type storing
13 vectors from an implementation of vector operations (such as addition,
14 scaling, norm). This, in turn, allows us to treat existing data objects
15 (e.g., tuples) as vectors without having to patch their data type's
16 algebraic operations (e.g., a tuple multiplied by a scalar is already
17 defined), which may break other code relying on them, or adding a wrapper
18 type, which may introduce the need for marshalling.
19 """
21 @property
22 @abstractmethod
23 def additive_identity(self) -> Vector:
24 """The 'zero' vector, i.e. `x`, such that for any vector `v`:
25 `x + v = v`
26 """
28 message = "override in a derived class"
29 raise NotImplementedError(message)
31 @property
32 @abstractmethod
33 def multiplicative_negator(self) -> Scalar:
34 """The '-1' scalar, i.e. `x`, such that for any vector (or scalar) `v`:
35 `v * x + v = 0`
36 """
38 message = "override in a derived class"
39 raise NotImplementedError(message)
41 def negated(self, value: Vector) -> Vector:
42 return self.scaled(value, self.multiplicative_negator)
44 @abstractmethod
45 def added(self, addend1: Vector, addend2: Vector) -> Vector:
46 """Return the vector sum of `addend1`, and `addend2`"""
48 message = "override in a derived class"
49 raise NotImplementedError(message)
51 def summed(self, addend1: Vector, *rest_addends: Vector) -> Vector:
52 result = addend1
53 for addend in rest_addends:
54 result = self.added(result, addend)
56 return result
58 def subtracted(self, diminuend: Vector, subtrahend: Vector) -> Vector:
59 return self.added(diminuend, self.negated(subtrahend))
61 @abstractmethod
62 def scaled(self, multiplicand: Vector, scalar: Scalar) -> Vector:
63 """Returns `multiplicand` scaled (multiplied) by `scalar`"""
65 message = "override in a derived class"
66 raise NotImplementedError(message)
68 @abstractmethod
69 def inner_multiplied(self, multiplicand1: Vector, multiplicand2: Vector) -> Scalar:
70 """Returns the inner product of `multiplicand1`, and `multiplicand2`
72 In an orthonormal basis, this is the same as the dot (component-wise)
73 product. In either case, the result of the inner product must be the
74 product of the norms of each multiplicand, and of the orthogonal
75 projection of one multiplicand on the other (the result being negative,
76 if the projeciton endpoint is in the opposite direction of the vector).
77 """
79 message = "override in a derived class"
80 raise NotImplementedError(message)
82 @abstractmethod
83 def norm(self, value: Vector) -> Scalar:
84 message = "override in a derived class"
85 raise NotImplementedError(message)
87 @abstractmethod
88 def normalized(self, value: Vector) -> Vector:
89 message = "override in a derived class"
90 raise NotImplementedError(message)
92 @abstractmethod
93 def projection_length_along(self, projected: Vector, direction: Vector) -> Scalar:
94 """The norm of the component of the projection of *projected* on
95 *direction*
97 We recommend always calling this method with keyword arguments, since
98 *projected*, and *direction* can easily be confused when unnamed.
99 """
101 message = "override in a derived class"
102 raise NotImplementedError(message)