1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
"""
Utility functions for orbital mechanics calculations.
This module provides various helper functions for working with orbits,
angles, time ranges, and other common operations.
"""
"""
Generate a range of astronomical times.
This function creates an array of Time objects, useful for propagating
orbits or generating ephemerides over a time span.
Parameters
----------
start : Time or str
Start time. If str, format and scale parameters apply.
periods : int, optional
Number of time points to generate (default: 50).
Ignored if both spacing and end are provided.
spacing : TimeDelta or Quantity, optional
Time spacing between consecutive points.
If Quantity, must have time units (e.g., u.hour, u.day).
If not provided and end is given, spacing is computed automatically.
end : Time or str, optional
End time. If str, format and scale parameters apply.
If provided with spacing, periods is ignored.
format : str, optional
Format string for parsing start/end if they are strings
(e.g., 'iso', 'jd', 'mjd'). See astropy.time.Time formats.
scale : str, optional
Time scale (e.g., 'utc', 'tt', 'tdb'). Default: 'utc'.
Returns
-------
Time
Array of Time objects spanning the requested range
Raises
------
ValueError
If neither spacing nor end is provided
Examples
--------
>>> from astrora.util import time_range
>>> from astropy.time import Time
>>> from astropy import units as u
>>>
>>> # Generate 100 time points over 1 day
>>> start = Time('2024-01-01 00:00:00', scale='utc')
>>> times = time_range(start, end=Time('2024-01-02 00:00:00'), periods=100)
>>> len(times)
100
>>>
>>> # Generate times with 1-hour spacing for 24 hours
>>> times = time_range(start, spacing=1*u.hour, periods=24)
>>> len(times)
24
>>>
>>> # Automatically determine spacing from start and end
>>> times = time_range(
... Time('2024-01-01'),
... end=Time('2024-01-10'),
... periods=10
... )
Notes
-----
This function is compatible with poliastro/hapsira's time_range API.
If both spacing and end are provided, the function generates times
from start to end with the given spacing, and periods is ignored.
The default number of periods (50) matches poliastro's default.
"""
# Parse start time
=
# Parse end time if provided
=
# Convert spacing to TimeDelta if it's a Quantity
=
# Case 1: spacing and end both provided -> ignore periods
# Generate times from start to end with given spacing
= . # Total duration in seconds
=
= + 1
= *
return +
# Case 2: only end provided -> compute spacing from periods
# Generate evenly spaced times from start to end
= .
=
return +
# Case 3: only spacing provided -> use periods
=
= *
return +
# Case 4: neither spacing nor end provided -> error
"""
Calculate the norm (magnitude) of a vector, respecting units.
This function computes the Euclidean norm of a vector and preserves
astropy units if present.
Parameters
----------
vector : array_like or Quantity
Input vector. Can be 1D or 2D (for multiple vectors).
If Quantity, units are preserved in the output.
Returns
-------
float or Quantity
Norm of the vector. If input has units, output has the same units.
For 2D arrays, returns norm of each row.
Examples
--------
>>> from astrora.util import norm
>>> from astropy import units as u
>>> import numpy as np
>>>
>>> # Simple vector norm
>>> v = np.array([3, 4, 0])
>>> norm(v)
5.0
>>>
>>> # Vector with units
>>> r = [6378, 0, 0] << u.km
>>> norm(r)
<Quantity 6378. km>
>>>
>>> # Multiple vectors (2D array)
>>> vectors = np.array([[3, 4, 0], [0, 5, 12]])
>>> norm(vectors)
array([5., 13.])
Notes
-----
This function is equivalent to np.linalg.norm but preserves astropy units.
It's compatible with poliastro/hapsira's util.norm function.
"""
# Extract value, compute norm, reattach units
=
=
# For 2D arrays, compute norm along last axis (each row)
=
return *
# No units, just compute norm
return
return
"""
Wrap an angle to a specified range.
This function wraps angles to the range [-limit, +limit). By default,
wraps to [-180°, +180°) or [-π, +π) depending on input units.
Parameters
----------
angle : float or Quantity
Input angle. If float, assumed to be in degrees.
If Quantity, must have angle units.
limit : float or Quantity, optional
Upper limit for wrapping (default: 180 degrees).
The angle will be wrapped to [-limit, +limit).
If float, assumed to be in degrees.
If Quantity, must have angle units.
Returns
-------
float or Quantity
Wrapped angle in the range [-limit, +limit).
If input is Quantity, output preserves the input units.
Examples
--------
>>> from astrora.util import wrap_angle
>>> from astropy import units as u
>>> import numpy as np
>>>
>>> # Wrap 370° to [-180°, +180°)
>>> wrap_angle(370)
10.0
>>>
>>> # Wrap -190° to [-180°, +180°)
>>> wrap_angle(-190)
170.0
>>>
>>> # Wrap with units (radians)
>>> wrap_angle(7*u.rad, limit=np.pi*u.rad)
<Quantity 0.71681469 rad>
>>>
>>> # Wrap to custom range [-90°, +90°)
>>> wrap_angle(100*u.deg, limit=90*u.deg)
<Quantity -80. deg>
Notes
-----
This function is compatible with poliastro/hapsira's util.wrap_angle.
The wrapping formula is: ((angle + limit) % (2 * limit)) - limit
"""
# Both angle and limit should be Quantities with angle units
# Assume limit is in same units as angle if not specified
= *
# Ensure both are in same units
= .
=
# Wrap using modulo arithmetic
= -
return *
# No units, assume degrees
= .
= -
return
"""
Return evenly spaced angular values over a specified interval.
This function is similar to np.linspace but handles angular wrapping
correctly, allowing for ranges that span more than 2π radians.
Parameters
----------
start : float or Quantity
Starting angle. If float, assumed to be in radians.
If Quantity, must have angle units.
stop : float or Quantity
Ending angle. If float, assumed to be in radians.
If Quantity, must have angle units (converted to match start).
num : int, optional
Number of samples to generate (default: 50).
Must be >= 2.
Returns
-------
ndarray or Quantity
Array of evenly spaced angles from start to stop (inclusive).
If inputs are Quantity, output preserves units.
Examples
--------
>>> from astrora.util import alinspace
>>> from astropy import units as u
>>> import numpy as np
>>>
>>> # Generate 5 angles from 0 to π radians
>>> angles = alinspace(0, np.pi, num=5)
>>> angles
array([0. , 0.78539816, 1.57079633, 2.35619449, 3.14159265])
>>>
>>> # With units (degrees)
>>> angles = alinspace(0*u.deg, 90*u.deg, num=4)
>>> angles
<Quantity [ 0., 30., 60., 90.] deg>
>>>
>>> # Across multiple revolutions (0 to 720 degrees)
>>> angles = alinspace(0*u.deg, 720*u.deg, num=9)
>>> len(angles)
9
Notes
-----
This function is compatible with poliastro/hapsira's util.alinspace.
Unlike wrap_angle, this function does NOT wrap the output angles.
It generates linearly spaced values that can exceed 2π if the range
between start and stop is large.
"""
# Work with Quantities
# Assume stop has same units as start
= *
# Convert stop to same units as start
=
# Generate linearly spaced values
=
return *
# No units, work with floats
# Convert to radians if stop has units but start doesn't
= .
return
"""
Find the index of the closest value in an array.
Parameters
----------
value : float or Quantity
Target value to find
values : array_like or Quantity
Array of values to search
Returns
-------
int
Index of the closest value in the array
Examples
--------
>>> from astrora.util import find_closest_value
>>> from astropy import units as u
>>> import numpy as np
>>>
>>> # Find closest value in array
>>> arr = np.array([1.0, 2.0, 3.0, 4.0, 5.0])
>>> find_closest_value(3.7, arr)
3
>>>
>>> # With units
>>> times = np.array([0, 100, 200, 300]) << u.s
>>> find_closest_value(250*u.s, times)
2
Notes
-----
This function is compatible with poliastro/hapsira's util.find_closest_value.
"""
# Convert to same units
= .
=
# value is float, values is Quantity - assume same units
=
=
# value is Quantity, values is array
=
=
# Both are regular arrays
=
=
# Find index of minimum distance
=
return
# Export all utility functions
=