@@ -16,7 +16,7 @@ class Directions:
1616 Supports uniform, random, or custom sampling of directions.
1717
1818 **Examples:**
19-
19+
2020 .. code-block:: python
2121
2222 # Uniform sampling in 2D (default)
@@ -57,15 +57,15 @@ def __init__(
5757 - For 2D, directions are represented as angles; for higher dimensions, as unit vectors.
5858 - Use factory methods :meth:`uniform`, :meth:`random`, :meth:`from_angles`, or :meth:`from_vectors` for convenience.
5959 """
60-
60+
6161 self .num_dirs = num_dirs
6262 self .sampling = sampling
6363 self .dim = dim
6464 self .endpoint = endpoint
6565
6666 self ._rng = np .random .RandomState (seed )
67- self ._thetas = None
68- self ._vectors = None
67+ self ._thetas : Optional [ np . ndarray ] = None
68+ self ._vectors : Optional [ np . ndarray ] = None
6969 self ._initialize_directions ()
7070
7171 def _initialize_directions (self ):
@@ -82,16 +82,14 @@ def _initialize_directions(self):
8282 else :
8383 # generate random normal samples and normalize to lie on the unit sphere
8484 self ._vectors = self ._rng .randn (self .num_dirs , self .dim )
85- self ._vectors /= np .linalg .norm (self ._vectors ,
86- axis = 1 , keepdims = True )
85+ self ._vectors /= np .linalg .norm (self ._vectors , axis = 1 , keepdims = True )
8786 elif self .sampling == Sampling .RANDOM :
8887 if self .dim == 2 :
8988 self ._thetas = self ._rng .uniform (0 , 2 * np .pi , self .num_dirs )
9089 self ._thetas .sort ()
9190 else :
9291 self ._vectors = self ._rng .randn (self .num_dirs , self .dim )
93- self ._vectors /= np .linalg .norm (self ._vectors ,
94- axis = 1 , keepdims = True )
92+ self ._vectors /= np .linalg .norm (self ._vectors , axis = 1 , keepdims = True )
9593
9694 @classmethod
9795 def uniform (
@@ -142,11 +140,11 @@ def from_angles(cls, angles: Sequence[float]) -> "Directions":
142140 - Angles are stored in :attr:`thetas` and unit vectors are computed as needed.
143141 """
144142 instance = cls (len (angles ), Sampling .CUSTOM , dim = 2 )
145- instance ._thetas = np .array (angles )
143+ instance ._thetas = np .array (angles , dtype = float )
146144 return instance
147145
148146 @classmethod
149- def from_vectors (cls , vectors : Sequence [tuple ]) -> "Directions" :
147+ def from_vectors (cls , vectors : Sequence [Sequence [ float ] ]) -> "Directions" :
150148 """
151149 Create a Directions instance from custom direction vectors in any dimension.
152150
@@ -163,12 +161,12 @@ def from_vectors(cls, vectors: Sequence[tuple]) -> "Directions":
163161 - Vectors are normalized to unit length.
164162 - For 2D, angles are computed from the vectors and available via :attr:`thetas`.
165163 """
166- vectors = np .array (vectors , dtype = float )
167- norms = np .linalg .norm (vectors , axis = 1 , keepdims = True )
164+ vectors_arr = np .array (vectors , dtype = float )
165+ norms = np .linalg .norm (vectors_arr , axis = 1 , keepdims = True )
168166 if np .any (norms == 0 ):
169167 raise ValueError ("Zero-magnitude vectors are not allowed" )
170- normalized = vectors / norms
171- instance = cls (len (vectors ), Sampling .CUSTOM , dim = vectors .shape [1 ])
168+ normalized = vectors_arr / norms
169+ instance = cls (len (vectors_arr ), Sampling .CUSTOM , dim = vectors_arr .shape [1 ])
172170 instance ._vectors = normalized
173171 if instance .dim == 2 :
174172 instance ._thetas = np .arctan2 (normalized [:, 1 ], normalized [:, 0 ])
@@ -196,6 +194,7 @@ def thetas(self) -> np.ndarray:
196194 if self ._thetas is None :
197195 # Compute the angles from the vectors.
198196 self ._thetas = np .arctan2 (self .vectors [:, 1 ], self .vectors [:, 0 ])
197+ assert self ._thetas is not None
199198 return self ._thetas
200199
201200 @property
@@ -215,13 +214,15 @@ def vectors(self) -> np.ndarray:
215214 """
216215 if self ._vectors is None :
217216 if self .dim == 2 :
218- self ._vectors = np .column_stack (
219- (np .cos (self ._thetas ), np .sin (self ._thetas ))
220- )
217+ thetas = self ._thetas
218+ if thetas is None :
219+ raise ValueError ("2D direction angles are not initialized." )
220+ self ._vectors = np .column_stack ((np .cos (thetas ), np .sin (thetas )))
221221 else :
222222 raise ValueError (
223223 "Direction vectors for dimensions >2 should be generated during initialization."
224224 )
225+ assert self ._vectors is not None
225226 return self ._vectors
226227
227228 def __len__ (self ) -> int :
0 commit comments