-
Notifications
You must be signed in to change notification settings - Fork 69
/
Copy pathapsides_body.hpp
203 lines (182 loc) · 7.59 KB
/
apsides_body.hpp
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
#pragma once
#include "physics/apsides.hpp"
#include <optional>
#include <vector>
#include "base/array.hpp"
#include "numerics/root_finders.hpp"
namespace principia {
namespace physics {
namespace internal_apsides {
using base::BoundedArray;
using geometry::Barycentre;
using geometry::Instant;
using geometry::Position;
using geometry::Sign;
using numerics::Bisect;
using numerics::Hermite3;
using quantities::Length;
using quantities::Speed;
using quantities::Square;
using quantities::Variation;
template<typename Frame>
void ComputeApsides(Trajectory<Frame> const& reference,
typename DiscreteTrajectory<Frame>::Iterator const begin,
typename DiscreteTrajectory<Frame>::Iterator const end,
int const max_points,
DiscreteTrajectory<Frame>& apoapsides,
DiscreteTrajectory<Frame>& periapsides) {
std::optional<Instant> previous_time;
std::optional<DegreesOfFreedom<Frame>> previous_degrees_of_freedom;
std::optional<Square<Length>> previous_squared_distance;
std::optional<Variation<Square<Length>>>
previous_squared_distance_derivative;
Instant const t_min = reference.t_min();
Instant const t_max = reference.t_max();
for (auto it = begin; it != end; ++it) {
auto const& [time, degrees_of_freedom] = *it;
if (time < t_min) {
continue;
}
if (time > t_max) {
break;
}
DegreesOfFreedom<Frame> const body_degrees_of_freedom =
reference.EvaluateDegreesOfFreedom(time);
RelativeDegreesOfFreedom<Frame> const relative =
degrees_of_freedom - body_degrees_of_freedom;
Square<Length> const squared_distance = relative.displacement().Norm²();
// This is the derivative of |squared_distance|.
Variation<Square<Length>> const squared_distance_derivative =
2.0 * InnerProduct(relative.displacement(), relative.velocity());
if (previous_squared_distance_derivative &&
Sign(squared_distance_derivative) !=
Sign(*previous_squared_distance_derivative)) {
CHECK(previous_time &&
previous_degrees_of_freedom &&
previous_squared_distance);
// The derivative of |squared_distance| changed sign. Construct a Hermite
// approximation of |squared_distance| and find its extrema.
Hermite3<Instant, Square<Length>> const
squared_distance_approximation(
{*previous_time, time},
{*previous_squared_distance, squared_distance},
{*previous_squared_distance_derivative,
squared_distance_derivative});
BoundedArray<Instant, 2> const extrema =
squared_distance_approximation.FindExtrema();
// Now look at the extrema and check that exactly one is in the required
// time interval. This is normally the case, but it can fail due to
// ill-conditioning.
Instant apsis_time;
int valid_extrema = 0;
for (auto const& extremum : extrema) {
if (extremum >= *previous_time && extremum <= time) {
apsis_time = extremum;
++valid_extrema;
}
}
if (valid_extrema != 1) {
// Something went wrong when finding the extrema of
// |squared_distance_approximation|. Use a linear interpolation of
// |squared_distance_derivative| instead.
apsis_time = Barycentre<Instant, Variation<Square<Length>>>(
{time, *previous_time},
{*previous_squared_distance_derivative,
-squared_distance_derivative});
}
// Now that we know the time of the apsis, use a Hermite approximation to
// derive its degrees of freedom. Note that an extremum of
// |squared_distance_approximation| is in general not an extremum for
// |position_approximation|: the distance computed using the latter is a
// 6th-degree polynomial. However, approximating this polynomial using a
// 3rd-degree polynomial would yield |squared_distance_approximation|, so
// we shouldn't be far from the truth.
DegreesOfFreedom<Frame> const apsis_degrees_of_freedom =
begin.trajectory()->EvaluateDegreesOfFreedom(apsis_time);
if (Sign(squared_distance_derivative).is_negative()) {
apoapsides.Append(apsis_time, apsis_degrees_of_freedom);
} else {
periapsides.Append(apsis_time, apsis_degrees_of_freedom);
}
if (apoapsides.Size() >= max_points && periapsides.Size() >= max_points) {
break;
}
}
previous_time = time;
previous_degrees_of_freedom = degrees_of_freedom;
previous_squared_distance = squared_distance;
previous_squared_distance_derivative = squared_distance_derivative;
}
}
template<typename Frame, typename Predicate>
void ComputeNodes(typename DiscreteTrajectory<Frame>::Iterator begin,
typename DiscreteTrajectory<Frame>::Iterator end,
Vector<double, Frame> const& north,
int const max_points,
DiscreteTrajectory<Frame>& ascending,
DiscreteTrajectory<Frame>& descending,
Predicate predicate) {
static_assert(
std::is_convertible<decltype(predicate(
std::declval<DegreesOfFreedom<Frame>>())),
bool>::value,
"|predicate| must be a predicate on |DegreesOfFreedom<Frame>|");
std::optional<Instant> previous_time;
std::optional<Length> previous_z;
std::optional<Speed> previous_z_speed;
for (auto it = begin; it != end; ++it) {
auto const& [time, degrees_of_freedom] = *it;
Length const z =
(degrees_of_freedom.position() - Frame::origin).coordinates().z;
Speed const z_speed = degrees_of_freedom.velocity().coordinates().z;
if (previous_z && Sign(z) != Sign(*previous_z)) {
CHECK(previous_time && previous_z_speed);
// |z| changed sign. Construct a Hermite approximation of |z| and find
// its zeros.
Hermite3<Instant, Length> const z_approximation(
{*previous_time, time},
{*previous_z, z},
{*previous_z_speed, z_speed});
Instant node_time;
if (Sign(z_approximation.Evaluate(*previous_time)) ==
Sign(z_approximation.Evaluate(time))) {
// The Hermite approximation is poorly conditioned, let's use a linear
// approximation
node_time = Barycentre<Instant, Length>({*previous_time, time},
{z, -*previous_z});
} else {
// The normal case, find the intersection with z = 0 using bisection.
// TODO(egg): Bisection on a polynomial seems daft; we should have
// Newton's method.
node_time = Bisect(
[&z_approximation](Instant const& t) {
return z_approximation.Evaluate(t);
},
*previous_time,
time);
}
DegreesOfFreedom<Frame> const node_degrees_of_freedom =
begin.trajectory()->EvaluateDegreesOfFreedom(node_time);
if (predicate(node_degrees_of_freedom)) {
if (Sign(InnerProduct(north, Vector<double, Frame>({0, 0, 1}))) ==
Sign(z_speed)) {
// |north| is up and we are going up, or |north| is down and we are
// going down.
ascending.Append(node_time, node_degrees_of_freedom);
} else {
descending.Append(node_time, node_degrees_of_freedom);
}
if (ascending.Size() >= max_points && descending.Size() >= max_points) {
break;
}
}
}
previous_time = time;
previous_z = z;
previous_z_speed = z_speed;
}
}
} // namespace internal_apsides
} // namespace physics
} // namespace principia