Skip to content

Commit c0ed29e

Browse files
For #526, experiment: vectorized light travel time
1 parent 1ab46eb commit c0ed29e

File tree

1 file changed

+194
-0
lines changed

1 file changed

+194
-0
lines changed

design/broadcasting.py

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
from numpy import array, max
2+
from skyfield.api import load
3+
from skyfield.constants import C_AUDAY
4+
from skyfield.functions import length_of, _reconcile
5+
6+
def main():
7+
ts = load.timescale()
8+
9+
cfltt = _correct_for_light_travel_time # the original
10+
#cfltt = _correct_for_light_travel_time2 # possible replacement
11+
12+
print('==== One time, one observer, one target ====')
13+
14+
t = ts.utc(2021, 2, 19, 13, 47)
15+
planets = load('de421.bsp')
16+
observer = planets['earth'].at(t)
17+
target = planets['mars']
18+
r, v, t2, light_time = cfltt(observer, target)
19+
print(t.shape, observer.position.km.shape, r.shape)
20+
21+
print('==== N times, one observer, one target ====')
22+
23+
t = ts.utc(2021, 2, 19, 13, [46, 47, 48, 49])
24+
planets = load('de421.bsp')
25+
observer = planets['earth'].at(t)
26+
target = planets['mars']
27+
r, v, t2, light_time = cfltt(observer, target)
28+
print(t.shape, observer.position.km.shape, '->', r.shape)
29+
print('Here is where the planet wound up:')
30+
print(r)
31+
32+
# The above maneuvers work fine even with the old version of the
33+
# routine. But to proceed from here, we need to switch.
34+
35+
print('==== N times, one observers, M targets ====')
36+
37+
t = ts.utc(2021, 2, 19, 13, [46, 47, 48, 49])
38+
planets = load('de421.bsp')
39+
40+
earth = planets['earth']
41+
if 0:
42+
# Turn Earth into two observation positions.
43+
earth = planets['earth']
44+
earth._at = build_multi_at(earth._at)
45+
observer = earth.at(t)
46+
print('observer', observer.position.au.shape)
47+
48+
target = planets['mars']
49+
target._at = build_multi_at(target._at) # Turn Mars into 2 planets.
50+
print('target', target.at(t).position.au.shape)
51+
52+
t = ts.tt(t.tt[:,None]) # What if we add a dimension to t?
53+
print('t', t.shape)
54+
55+
r, v, t2, light_time = _correct_for_light_travel_time2(observer, target)
56+
print(t.shape, observer.position.km.shape, '->', r.shape)
57+
58+
print('Does it look like a second planet 1 AU away at the same 4 times?')
59+
print('First planet:')
60+
print(r[:,:,0])
61+
print('Second planet:')
62+
print(r[:,:,1])
63+
64+
offset = array([0, 1])[None,None,:] # Dimensions: [xyz, time, offset]
65+
66+
def build_multi_at(_at):
67+
# Take a single planet position, and pretend that really there are
68+
# two targets returning their positions (like comets or asteroids).
69+
70+
def wrapper(t):
71+
tposition, tvelocity, gcrs_position, message = _at(t)
72+
print('_at() t.shape', t.shape)
73+
if len(t.shape) < 2:
74+
# If the time lacks a second dimension, then let's expand
75+
# position and velocity by adding a new dimension (...,2) at
76+
# the bottom, as though there were two planets.
77+
tposition = tposition[:,:,None] + offset
78+
tvelocity = tvelocity[:,:,None] + offset
79+
else:
80+
# Otherwise, the time's extra dimension will already have us
81+
# producing what looks like two objects in the bottom
82+
# dimension! We can simply apply our position offset.
83+
assert tposition.shape[-1] == tvelocity.shape[-1] == 2
84+
tposition = tposition + offset
85+
tvelocity = tvelocity + offset
86+
return tposition, tvelocity, gcrs_position, message
87+
return wrapper
88+
89+
# The original.
90+
91+
def _correct_for_light_travel_time(observer, target):
92+
"""Return a light-time corrected astrometric position and velocity.
93+
94+
Given an `observer` that is a `Barycentric` position somewhere in
95+
the solar system, compute where in the sky they will see the body
96+
`target`, by computing the light-time between them and figuring out
97+
where `target` was back when the light was leaving it that is now
98+
reaching the eyes or instruments of the `observer`.
99+
100+
"""
101+
t = observer.t
102+
ts = t.ts
103+
whole = t.whole
104+
tdb_fraction = t.tdb_fraction
105+
106+
cposition = observer.position.au
107+
cvelocity = observer.velocity.au_per_d
108+
109+
tposition, tvelocity, gcrs_position, message = target._at(t)
110+
111+
distance = length_of(tposition - cposition)
112+
light_time0 = 0.0
113+
for i in range(10):
114+
light_time = distance / C_AUDAY
115+
delta = light_time - light_time0
116+
if abs(max(delta)) < 1e-12:
117+
break
118+
119+
# We assume a light travel time of at most a couple of days. A
120+
# longer light travel time would best be split into a whole and
121+
# fraction, for adding to the whole and fraction of TDB.
122+
t2 = ts.tdb_jd(whole, tdb_fraction - light_time)
123+
124+
tposition, tvelocity, gcrs_position, message = target._at(t2)
125+
distance = length_of(tposition - cposition)
126+
light_time0 = light_time
127+
else:
128+
raise ValueError('light-travel time failed to converge')
129+
return tposition - cposition, tvelocity - cvelocity, t, light_time
130+
131+
# Try allowing vectors while keeping things "right side up" with x,y,z
132+
# at the top dimension.
133+
134+
def sub(a, b):
135+
return (a.T - b.T).T
136+
137+
def _correct_for_light_travel_time2(observer, target):
138+
"""Return a light-time corrected astrometric position and velocity.
139+
140+
Given an `observer` that is a `Barycentric` position somewhere in
141+
the solar system, compute where in the sky they will see the body
142+
`target`, by computing the light-time between them and figuring out
143+
where `target` was back when the light was leaving it that is now
144+
reaching the eyes or instruments of the `observer`.
145+
146+
"""
147+
t = observer.t
148+
ts = t.ts
149+
whole = t.whole
150+
tdb_fraction = t.tdb_fraction
151+
152+
cposition = observer.position.au
153+
cvelocity = observer.velocity.au_per_d
154+
155+
tposition, tvelocity, gcrs_position, message = target._at(t)
156+
157+
distance = length_of(sub(tposition, cposition))
158+
159+
print('distance', distance.shape) # (t, targets)
160+
161+
light_time0 = 0.0
162+
for i in range(10):
163+
light_time = distance / C_AUDAY # GOOD: scalar
164+
delta = light_time - light_time0 # GOOD first time: scalar; 2nd: ?
165+
if abs(max(delta)) < 1e-12:
166+
break
167+
168+
# We assume a light travel time of at most a couple of days. A
169+
# longer light travel time would best be split into a whole and
170+
# fraction, for adding to the whole and fraction of TDB.
171+
print('tdb_fraction', tdb_fraction.shape, '[ORIGINAL]')
172+
print('light_time', light_time.shape)
173+
diff = sub(tdb_fraction, light_time)
174+
print('sub()', diff.shape) # (4,2)? YES!!!
175+
print('whole', whole.shape) # (4,)? YES!!!
176+
# whole, diff = _reconcile(whole, diff) # Winds up not needed?
177+
# print('sub()', diff.shape) # (4,2)
178+
# print('whole', whole.shape) # (4,1)
179+
t2 = ts.tdb_jd(whole, diff)
180+
print('t2', t2.shape) # (4, 1)? Why not (4, 2)? Because it prints top.
181+
182+
tposition, tvelocity, gcrs_position, message = target._at(t2)
183+
print('tposition', tposition.shape) # Needs to be 3,4,2
184+
distance = length_of(sub(tposition, cposition))
185+
light_time0 = light_time
186+
#exit()
187+
else:
188+
raise ValueError('light-travel time failed to converge')
189+
return sub(tposition, cposition), sub(tvelocity, cvelocity), t, light_time
190+
191+
_reconcile # So CI will think we used it, whether use above is commented or not
192+
193+
if __name__ == '__main__':
194+
main()

0 commit comments

Comments
 (0)