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

1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com> 

2# 

3# SPDX-License-Identifier: MIT 

4 

5from abc import ABC, abstractmethod 

6 

7 

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 

11 

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 """ 

20 

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 """ 

27 

28 message = "override in a derived class" 

29 raise NotImplementedError(message) 

30 

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 """ 

37 

38 message = "override in a derived class" 

39 raise NotImplementedError(message) 

40 

41 def negated(self, value: Vector) -> Vector: 

42 return self.scaled(value, self.multiplicative_negator) 

43 

44 @abstractmethod 

45 def added(self, addend1: Vector, addend2: Vector) -> Vector: 

46 """Return the vector sum of `addend1`, and `addend2`""" 

47 

48 message = "override in a derived class" 

49 raise NotImplementedError(message) 

50 

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) 

55 

56 return result 

57 

58 def subtracted(self, diminuend: Vector, subtrahend: Vector) -> Vector: 

59 return self.added(diminuend, self.negated(subtrahend)) 

60 

61 @abstractmethod 

62 def scaled(self, multiplicand: Vector, scalar: Scalar) -> Vector: 

63 """Returns `multiplicand` scaled (multiplied) by `scalar`""" 

64 

65 message = "override in a derived class" 

66 raise NotImplementedError(message) 

67 

68 @abstractmethod 

69 def inner_multiplied(self, multiplicand1: Vector, multiplicand2: Vector) -> Scalar: 

70 """Returns the inner product of `multiplicand1`, and `multiplicand2` 

71 

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 """ 

78 

79 message = "override in a derived class" 

80 raise NotImplementedError(message) 

81 

82 @abstractmethod 

83 def norm(self, value: Vector) -> Scalar: 

84 message = "override in a derived class" 

85 raise NotImplementedError(message) 

86 

87 @abstractmethod 

88 def normalized(self, value: Vector) -> Vector: 

89 message = "override in a derived class" 

90 raise NotImplementedError(message) 

91 

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* 

96 

97 We recommend always calling this method with keyword arguments, since 

98 *projected*, and *direction* can easily be confused when unnamed. 

99 """ 

100 

101 message = "override in a derived class" 

102 raise NotImplementedError(message)