Wolfram Computation Meets Knowledge

Hidden Figures: Modern Approaches to Orbit and Reentry Calculations

The movie Hidden Figures was released in theaters recently and has been getting good reviews. It also deals with an important time in US history, touching on a number of topics, including civil rights and the Space Race. The movie details the hidden story of Katherine Johnson and her coworkers (Dorothy Vaughan and Mary Jackson) at NASA during the Mercury missions and the United States’ early explorations into manned space flight. The movie focuses heavily on the dramatic civil rights struggle of African American women in NASA at the time, and these struggles are set against the number-crunching ability of Johnson and her coworkers. Computers were in their early days at this time, so Johnson and her team’s ability to perform complicated navigational orbital mechanics problems without the use of a computer provided an important sanity check against the early computer results.

Row[{Show[    Entity["Movie", "HiddenFigures::k39bj"][     EntityProperty["Movie", "Image"]], ImageSize -> 101], "  ",    Show[Entity["PopularCurve", "KatherineJohnsonCurve"][     EntityProperty["PopularCurve", "Image"]], Axes -> False,     Background -> LightBlue, ImageSize -> 120]}]

I will touch on two aspects of her scientific work that were mentioned in the film: orbit calculations and reentry calculations. For the orbit calculation, I will first exactly follow what Johnson did and then compare with a more modern, direct approach utilizing an array of tools made available with the Wolfram Language. Where the movie mentions the solving of differential equations using Euler’s method, I will compare this method with more modern ones in an important problem of rocketry: computing a reentry trajectory from the rocket equation and drag terms (derived using atmospheric model data obtained directly from within the Wolfram Language).

The movie doesn’t focus much on the math details of the types of problems Johnson and her team dealt with, but for the purposes of this blog, I hope to provide at least a flavor of the approaches one might have used in Johnson’s day compared to the present.

Placing a Satellite over a Selected Position

One of the earliest papers that Johnson coauthored, “Determination of Azimuth Angle at Burnout for Placing a Satellite over a Selected Earth Position,” deals with the problem of making sure that a satellite can be placed over a specific Earth location after a specified number of orbits, given a certain starting position (e.g. Cape Canaveral, Florida) and orbital trajectory. The approach that Johnson’s team used was to determine the azimuthal angle (the angle formed by the spacecraft’s velocity vector at the time of engine shutoff with a fixed reference direction, say north) to fire the rocket in, based on other orbital parameters. This is an important step in making sure that an astronaut is in the correct location for reentry to Earth.

NASA Technical Note

Constants and Initial Processing

In the paper, Johnson defines a number of constants and input parameters needed to solve the problem at hand. One detail to explain is the term “burnout,” which refers to the shutoff of the rocket engine. After burnout, orbital parameters are essentially “frozen,” and the spacecraft moves solely under the Earth’s gravity (as determined, of course, through Newton’s laws). In this section, I follow the paper’s unit conventions as closely as possible.

v1 = 25761.345 60; (* satellite velocity in ft/min *) r1 = 21637933.;(* circular orbit radius in feet *) \[Gamma]1 =    0.5 ; (* elevation angle between local horizon and velocity vector \ in degrees *) vc = 25506.28 60; (* circular orbit velocity in ft/min *) poverr1 =    1.020022269; (* p is the semilatus rectum of the orbit ellipse *) a = 22081775.57; (* semimajor axis of orbit in feet *) t\[Theta]1 =    5.842 ;(* t[\[Theta]1] is the time from perigee for \[Theta]1 in \ min, where \[Theta]1 is angle in orbit plane between perigee and \ burnout *) g0 = 115991.595; (* acceleration due to gravity ft/min^2 *) R = 2.090226 10^7; (* Earth radius in feet *) (* launch coordinates *)  \[Phi]1 = 28.50 ; (* launch latitude  *)  \[Lambda]1 = 279.45 ; (* launch longitude *)   \[Phi]2 = 34.00 ;(* intended pass over latitude *) \[Lambda]2 = 241.00 ; (* intended pass over longitude *) n = 3; (* number of orbits *) \[Omega]Capitale =    0.25068  (* angular velocity of Earth in degrees/min *) ;

For convenience, some functions are defined to deal with angles in degrees instead of radians. This allows for smoothly handling time in angle calculations:

SinDegree[x_] := Sin[x Degree] CosDegree[x_] := Cos[x Degree] TanDegree[x_] := Tan[x Degree] SecDegree[x_] := Sec[x Degree]  ArcSinDegree[x_] := ArcSin[x]/Degree ArcCosDegree[x_] := ArcCos[x]/Degree ArcTanDegree[x_] := ArcTan[x]/Degree

ArcTanDegree[x_, y_] := ArcTan[x, y]/Degree

Johnson goes on to describe several other derived parameters, though it’s interesting to note that she sometimes adopted values for these rather than using the values returned by her formulas. Her adopted values were often close to the values obtained by the formulas. For simplicity, the values from the formulas are used here.

Semilatus rectum of the orbit ellipse:

p = r1*(v1/vc)^2 CosDegree[\[Gamma]1];

Angle in orbit plane between perigee and burnout point:

\[Theta]1 = ArcTanDegree[TanDegree[\[Gamma]1] ((p/r1)/(p/r1 - 1))];

Orbit eccentricity:

e = (1/CosDegree[\[Theta]1]) (p/r1 - 1);

Orbit period:

T = 2 Pi Sqrt[R/g0] Sqrt[(a/R)^3];

Eccentric anomaly:

Eanomaly[\[Theta]_] := 2 ArcTan[Tan[\[Theta]/2] Sqrt[(1 - e)/(1 + e)]]

To describe the next parameter, it’s easiest to quote the original paper: “The requirement that a satellite with burnout position φ1, λ1 pass over a selected position φ2, λ2 after the completion of n orbits is equivalent to the requirement that, during the first orbit, the satellite pass over an equivalent position with latitude φ2 the same as that of the selected position but with longitude λ2e displaced eastward from λ2 by an amount sufficient to compensate for the rotation of the Earth during the n complete orbits, that is, by the polar hour angle n ωE T. The longitude of this equivalent position is thus given by the relation”:

\[Lambda]2e = \[Lambda]2 + n \[Omega]Capitale T;

Time from perigee for angle θ:

t[\[Theta]_] :=   T/(2 Pi) (Eanomaly[\[Theta] Degree] -      e Sin[Eanomaly[\[Theta] Degree]])

Computation

Part of the final solution is to determine values for intermediate parameters δλ1-2e and θ2e. This can be done in a couple of ways. First, I can use ContourPlot to obtain a graphical solution via equations 19 and 20 from the paper:

Clear[\[CapitalDelta]\[Lambda]1minus2e, \[Theta]2e]; ContourPlot[  Evaluate[{\[CapitalDelta]\[Lambda]1minus2e  == \[Lambda]2e - \ \[Lambda]1 + \[Omega]Capitale (t[\[Theta]2e ] - t[\[Theta]1]),     CosDegree[\[Theta]2e - \[Theta]1] ==      SinDegree[\[Phi]2] SinDegree[\[Phi]1] +       CosDegree[\[Phi]2] CosDegree[\[Phi]1] CosDegree[\[CapitalDelta]\ \[Lambda]1minus2e ] }],                                               {\[CapitalDelta]\ \[Lambda]1minus2e, 0, 60 }  , {\[Theta]2e, 0, 60 },   Prolog -> {Red, PointSize[Large],     Point[{31.948028324349036`, 51.7127788640602`}]}]

FindRoot can be used to find the solutions numerically:

Clear[\[CapitalDelta]\[Lambda]1minus2e, \[Theta]2e]; FindRoot[Evaluate[{\[CapitalDelta]\[Lambda]1minus2e  == \[Lambda]2e - \ \[Lambda]1 + \[Omega]Capitale (t[\[Theta]2e ] - t[\[Theta]1]),     CosDegree[\[Theta]2e - \[Theta]1] ==      SinDegree[\[Phi]2] SinDegree[\[Phi]1] +       CosDegree[\[Phi]2] CosDegree[\[Phi]1] CosDegree[\[CapitalDelta]\ \[Lambda]1minus2e ] }],                                          { \ {\[CapitalDelta]\[Lambda]1minus2e, 30 }, {\[Theta]2e, 55 }}]

{\[CapitalDelta]\[Lambda]1minus2e -> 32.1445, \[Theta]2e -> 51.8351}

Of course, Johnson didn’t have access to ContourPlot or FindRoot, so her paper describes an iterative technique. I translated the technique described in the paper into the Wolfram Language, and also solved for a number of other parameters via her iterative method. Because the base computations are for a spherical Earth, corrections for oblateness are included in her method:

\[CapitalDelta]\[Omega] = 0; \[CapitalDelta]\[Phi]2 = 0; \[CapitalDelta]\[CapitalOmega] = 0; \[CapitalDelta]\[Lambda]2 = 0; iO = 2;  Table[   \[Lambda]2e = \[Lambda]2 - \[CapitalDelta]\[Lambda]2 +      n \[Omega]Capitale T;   \[CapitalDelta]\[Lambda]1minus2e = \[Lambda]2e - \[Lambda]1 + \ \[Omega]Capitale*      If[iter == 1,        T/360 (\[Lambda]2e - \[Lambda]1) +         t[\[Theta]1], (tOf\[Theta]2e - t[\[Theta]1])];   \[Theta]2e =     ArcCosDegree[      SinDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2] SinDegree[\[Phi]1] +       CosDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2] CosDegree[\[Phi]1] \ CosDegree[\[CapitalDelta]\[Lambda]1minus2e]] + \[Theta]1;    \[Psi]1 =     ArcSinDegree[(SinDegree[\[CapitalDelta]\[Lambda]1minus2e] \ CosDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2])/      SinDegree[\[Theta]2e - \[Theta]1]];   i = ArcCosDegree[     Piecewise[{{CosDegree[\[Phi]1] SinDegree[\[Psi]1],         0 < \[Psi]1 < 180}, {-CosDegree[\[Phi]1] SinDegree[\[Psi]1],         180 <= \[Psi]1 < 360}}]];   \[Omega] = ArcSinDegree[SinDegree[\[Phi]1]/SinDegree[i]] - \[Theta]1;   (* after the first iteration,         correct \[Lambda]2 and \[Phi]2 for oblateness and     then keep this correction| page 18, 19 *)   If[iter == iO, \[CapitalDelta]\[Omega] =       3.4722*^-3 (R/p)^2 (R/a)^(3/2) (5 CosDegree[i]^2 - 1) (n T +         t[\[Theta]2e] - t[\[Theta]1])];   If[iter ==      iO, \[CapitalDelta]\[Phi]2 = \[CapitalDelta]\[Omega] SinDegree[       i] CosDegree[\[Omega] + \[Theta]2e]/CosDegree[\[Phi]2]];    If[iter ==      iO, \[CapitalDelta]\[CapitalOmega] = -6.9444*^-3 (R/p)^2 (R/a)^(3/         2) CosDegree[i] (n T + t[\[Theta]2e] - t[\[Theta]1])];   If[iter ==      iO, \[CapitalDelta]\[Lambda]2 = \[CapitalDelta]\[Omega] CosDegree[        i] SecDegree[\[Omega] + \[Theta]2e]^2/        (1 +           CosDegree[             i]^2 TanDegree[\[Omega] + \[Theta]2e]^2) + \ \[CapitalDelta]\[CapitalOmega]];   \[CapitalDelta]\[Lambda]N1 =     ArcTanDegree[SinDegree[\[Phi]1] TanDegree[\[Psi]1]];   \[Lambda]Nref = \[Lambda]1 - \[CapitalDelta]\[Lambda]N1;   tOf\[Theta]2e = t[\[Theta]2e];   {\[Theta]2e, tOf\[Theta]2e}, {iter, 1, 8, 1}];

Graphing the value of θ2e for the various iterations shows a quick convergence:

ListPlot[%[[All, 2]], PlotRange -> {10, 15}, Filling -> Axis,                    AxesLabel -> {"iteration", "\[Theta]2e"},   ImageSize -> 360]

ListPlot[%[[All, 2]], PlotRange -> {10, 15}, Filling -> Axis,                    AxesLabel -> {"iteration", "\[Theta]2e"},   ImageSize -> 360]

ksol = {   "\[CapitalDelta]\[Lambda]1minus2e" -> \ \[CapitalDelta]\[Lambda]1minus2e,    "\[CapitalDelta]\[Lambda]2" -> \[CapitalDelta]\[Lambda]2,    "\[Theta]2e" -> \[Theta]2e , "\[Psi]1" -> \[Psi]1,    "\[Omega]" -> \[Omega],    "\[CapitalDelta]\[Omega]" -> \[CapitalDelta]\[Omega],    "\[CapitalDelta]\[CapitalOmega]" -> \[CapitalDelta]\[CapitalOmega],    "i" -> N@i, "\[CapitalDelta]\[Phi]2" -> \[CapitalDelta]\[Phi]2 }

{"\[CapitalDelta]\[Lambda]1minus2e" -> 31.0612,   "\[CapitalDelta]\[Lambda]2" -> 1.02742, "\[Theta]2e" -> 50.9335,   "\[Psi]1" -> 70.5551, "\[Omega]" -> 34.5578,   "\[CapitalDelta]\[Omega]" -> 1.96276,   "\[CapitalDelta]\[CapitalOmega]" -> -1.33792, "i" -> 34.0355,   "\[CapitalDelta]\[Phi]2" -> 0.0841712}

I can convert the method in a FindRoot command as follows (this takes the oblateness effects into account in a fully self-consistent manner and calculates values for all nine variables involved in the equations):

fr := FindRoot[Evaluate[    {\[CapitalDelta]\[Lambda]1minus2e  == (\[Lambda]2 - \ \[CapitalDelta]\[Lambda]2 +          n \[Omega]Capitale T) - \[Lambda]1 + \[Omega]Capitale*(t[\ \[Theta]2e] - t[\[Theta]1]),      CosDegree[\[Theta]2e - \[Theta]1] ==       SinDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2] SinDegree[\[Phi]1] +        CosDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2] CosDegree[\[Phi]1] \ CosDegree[\[CapitalDelta]\[Lambda]1minus2e ],     SinDegree[\[Psi]1] == \ (SinDegree[\[CapitalDelta]\[Lambda]1minus2e] CosDegree[\[Phi]2 - \ \[CapitalDelta]\[Phi]2])/SinDegree[\[Theta]2e - \[Theta]1],     CosDegree[i] == CosDegree[\[Phi]1] SinDegree[\[Psi]1],     SinDegree[\[Omega] + \[Theta]1] == SinDegree[\[Phi]1]/SinDegree[i],     \[CapitalDelta]\[Omega] ==        3.4722*^-3 (R/p)^2 (R/a)^(3/2) (5 CosDegree[i]^2 - 1) (n T +          t[\[Theta]2e] - t[\[Theta]1]),     \[CapitalDelta]\[Phi]2 == \[CapitalDelta]\[Omega] SinDegree[        i] CosDegree[\[Omega] + \[Theta]2e]/        CosDegree[\[Phi]2 - \[CapitalDelta]\[Phi]2],     \[CapitalDelta]\[CapitalOmega] == -6.9444*^-3 (R/p)^2 (R/a)^(3/          2) CosDegree[i] (n T + t[\[Theta]2e] - t[\[Theta]1]),     \[CapitalDelta]\[Lambda]2 == \[CapitalDelta]\[Omega] CosDegree[         i] SecDegree[\[Omega] + \[Theta]2e]^2/(1 +            CosDegree[              i]^2 TanDegree[\[Omega] + \[Theta]2e]^2) + \ \[CapitalDelta]\[CapitalOmega]}],   {{\[CapitalDelta]\[Lambda]1minus2e, 31}, {\[CapitalDelta]\[Lambda]2,      1}, {\[Theta]2e, 51}, {\[Psi]1, 70}, {\[Omega],      34}, {\[CapitalDelta]\[Omega],      2}, {\[CapitalDelta]\[CapitalOmega], -1.3}, {i,      34}, {\[CapitalDelta]\[Phi]2, 0.1}}]

Clear[\[CapitalDelta]\[Lambda]1minus2e, \[CapitalDelta]\[Lambda]2, \ \[Theta]2e, \[Psi]1, \[Omega], \[CapitalDelta]\[Omega], \ \[CapitalDelta]\[CapitalOmega], i, \[CapitalDelta]\[Phi]2]; wlsol = fr

{\[CapitalDelta]\[Lambda]1minus2e ->    31.062, \[CapitalDelta]\[Lambda]2 -> 1.02664, \[Theta]2e ->    50.9332, \[Psi]1 -> 70.5964, \[Omega] ->    34.61, \[CapitalDelta]\[Omega] ->    1.96527, \[CapitalDelta]\[CapitalOmega] -> -1.33778,   i -> 34.0139, \[CapitalDelta]\[Phi]2 -> 0.102921}

Interestingly, even the iterative root-finding steps of this more complicated system converge quite quickly:

(OwnValues[fr] /.      HoldPattern[FindRoot[args__]] :>       Reap[FindRoot[args, StepMonitor :> Sow[          {\[CapitalDelta]\[Lambda]1minus2e, \ \[CapitalDelta]\[Lambda]2, \[Theta]2e, \[Psi]1, \[Omega], \ \[CapitalDelta]\[Omega], \[CapitalDelta]\[CapitalOmega],            i, \[CapitalDelta]\[Phi]2}]]])[[1, 2]][[2]]

{{{31.0619, 1.02671, 50.9332, 70.5861, 34.6025, 1.96521, -1.33777,     34.0149, 0.102885}, {31.062, 1.02664, 50.9332, 70.5964, 34.61,     1.96527, -1.33778, 34.0139, 0.102922}, {31.062, 1.02664, 50.9332,     70.5964, 34.61, 1.96527, -1.33778, 34.0139, 0.102921}, {31.062,     1.02664, 50.9332, 70.5964, 34.61, 1.96527, -1.33778, 34.0139,     0.102921}}}

Plotting

With the orbital parameters determined, it is desirable to visualize the solution. First, some critical parameters from the previous solutions need to be extracted:

{\[Theta]2e, \[Omega], i} = {"\[Theta]2e", "\[Omega]", "i"} /. ksol;

Next, the latitude and longitude of the satellite as a function of azimuth angle need to be derived:

\[CapitalDelta]\[Lambda]Ns[\[Theta]s_] :=   ArcTanDegree[CosDegree[\[Omega] + \[Theta]s],    CosDegree[-i] SinDegree[\[Omega] + \[Theta]s]]

\[Lambda]N[\[Theta]s_] := \[Lambda]Nref - \[Omega]Capitale \ (t[\[Theta]s] - t[\[Theta]1]) + \[CapitalDelta]\[Lambda]Ns[\[Theta]s]

φs and λs are the latitudes and longitudes as a function of θs:

\[Phi]s[\[Theta]s_] :=   ArcSinDegree[SinDegree[i] SinDegree[\[Omega] + \[Theta]s]]

satpts[n_] :=    Table[{\[Phi]s[\[Theta]s], \[Lambda]s[\[Theta]s, n]}, {\[Theta]s, 0,      360, .01}];

The satellite ground track can be constructed by creating a table of points:

points = {Table[{\[Lambda]s[\[Theta]s,        0], \[Phi]s[\[Theta]s]}, {\[Theta]s, \[Theta]1, 180, .1}],       Table[{\[Lambda]s[\[Theta]s,        1], \[Phi]s[\[Theta]s]}, {\[Theta]s, 0, 360, .1}],       Table[{\[Lambda]s[\[Theta]s,        2], \[Phi]s[\[Theta]s]}, {\[Theta]s, 0, 360, .1}],       Table[{\[Lambda]s[\[Theta]s,        3], \[Phi]s[\[Theta]s]}, {\[Theta]s, -180, \[Theta]2e, .1}]};

Johnson’s paper presents a sketch of the orbital solution including markers showing the burnout, selected and equivalent positions. It’s easy to reproduce a similar plain diagram here:

ListPlot[points,  PlotStyle -> Gray, PlotRange -> {{0, 360}, {-60, 60}},  Epilog -> {Black, Text[Style[#1, 32, Bold], #2] & @@@                {{"\[EmptySmallCircle]", {\[Lambda]1, \[Phi]1}}, {"\ \[EmptyDiamond]", {\[Lambda]2, \[Phi]2}, {"\[EmptySmallSquare]", {\ \[Lambda]2e, \[Phi]2}}} }} ,  Prolog -> {Arrow[{{333, 33}, {342, 31}}],     Arrow[{{212, 29}, {215, 30} }],                          Text[Style[      "Burnout\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(1\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(1\)]\)", 10], {280, 18}],                          Text[Style[      "Selected\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2\)]\)", 10], {240, 48}],                          Text[Style[      "Equivalent\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2  e\)]\)", 10], {312,       46}]},  GridLines -> {Table[\[Lambda], {\[Lambda], 0, 360, 40}],     Table[\[Phi], {\[Phi], -60, 60, 20}]},  Axes -> False, Frame -> True, ImageSize -> 600, AspectRatio -> 0.7,  FrameLabel -> {"Longitude, \[Lambda], deg", "Latitude, \[Phi], deg"},  FrameTicks -> {{Table[{\[Phi], \[Phi]}, {\[Phi], -60, 60, 20}],      None}, {Table[{\[Lambda], \[Lambda]}, {\[Lambda], 0, 360, 40}],      None}}]

ListPlot[points,  PlotStyle -> Gray, PlotRange -> {{0, 360}, {-60, 60}},  Epilog -> {Black, Text[Style[#1, 32, Bold], #2] & @@@                {{"\[EmptySmallCircle]", {\[Lambda]1, \[Phi]1}}, {"\ \[EmptyDiamond]", {\[Lambda]2, \[Phi]2}, {"\[EmptySmallSquare]", {\ \[Lambda]2e, \[Phi]2}}} }} ,  Prolog -> {Arrow[{{333, 33}, {342, 31}}],     Arrow[{{212, 29}, {215, 30} }],                          Text[Style[      "Burnout\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(1\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(1\)]\)", 10], {280, 18}],                          Text[Style[      "Selected\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2\)]\)", 10], {240, 48}],                          Text[Style[      "Equivalent\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2  e\)]\)", 10], {312,       46}]},  GridLines -> {Table[\[Lambda], {\[Lambda], 0, 360, 40}],     Table[\[Phi], {\[Phi], -60, 60, 20}]},  Axes -> False, Frame -> True, ImageSize -> 600, AspectRatio -> 0.7,  FrameLabel -> {"Longitude, \[Lambda], deg", "Latitude, \[Phi], deg"},  FrameTicks -> {{Table[{\[Phi], \[Phi]}, {\[Phi], -60, 60, 20}],      None}, {Table[{\[Lambda], \[Lambda]}, {\[Lambda], 0, 360, 40}],      None}}]

ListPlot[points,  PlotStyle -> Gray, PlotRange -> {{0, 360}, {-60, 60}},  Epilog -> {Black, Text[Style[#1, 32, Bold], #2] & @@@                {{"\[EmptySmallCircle]", {\[Lambda]1, \[Phi]1}}, {"\ \[EmptyDiamond]", {\[Lambda]2, \[Phi]2}, {"\[EmptySmallSquare]", {\ \[Lambda]2e, \[Phi]2}}} }} ,  Prolog -> {Arrow[{{333, 33}, {342, 31}}],     Arrow[{{212, 29}, {215, 30} }],                          Text[Style[      "Burnout\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(1\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(1\)]\)", 10], {280, 18}],                          Text[Style[      "Selected\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2\)]\)", 10], {240, 48}],                          Text[Style[      "Equivalent\nposition\n\!\(\*SubscriptBox[\(\[Phi]\), \ \(2\)]\),\!\(\*SubscriptBox[\(\[Lambda]\), \(2  e\)]\)", 10], {312,       46}]},  GridLines -> {Table[\[Lambda], {\[Lambda], 0, 360, 40}],     Table[\[Phi], {\[Phi], -60, 60, 20}]},  Axes -> False, Frame -> True, ImageSize -> 600, AspectRatio -> 0.7,  FrameLabel -> {"Longitude, \[Lambda], deg", "Latitude, \[Phi], deg"},  FrameTicks -> {{Table[{\[Phi], \[Phi]}, {\[Phi], -60, 60, 20}],      None}, {Table[{\[Lambda], \[Lambda]}, {\[Lambda], 0, 360, 40}],      None}}]

For comparison, here is her original diagram:

Original diagram

Katherine Johnson's Diagram

A more visually useful version can be constructed using GeoGraphics, taking care to convert the geocentric coordinates into geodetic coordinates:

path = {#1,  QuantityMagnitude[GeodesyData["Krassovsky",        {"FromGeocentricLatitude", #2}]]} & @@@ Flatten[points, 1];

GeoGraphics[{Blue, PointSize[0.015],    Point[GeoPosition[{\[Phi]1, \[Lambda]1}]], Red,    Point[GeoPosition[{\[Phi]2, \[Lambda]2}]],   Black, PointSize[0.002], Point[GeoPosition[Reverse /@ path]]},   GeoRange -> "World", GeoZoomLevel -> 3]

GeoGraphics[{Blue, PointSize[0.015],    Point[GeoPosition[{\[Phi]1, \[Lambda]1}]], Red,    Point[GeoPosition[{\[Phi]2, \[Lambda]2}]],   Black, PointSize[0.002], Point[GeoPosition[Reverse /@ path]]},   GeoRange -> "World", GeoZoomLevel -> 3]

How to Calculate Orbits Today

Today, virtually every one of us has, within immediate reach, access to computational resources far more powerful than those available to the entirety of NASA in the 1960s. Now, using only a desktop computer and the Wolfram Language, you can easily find direct numerical solutions to problems of orbital mechanics such as those posed to Katherine Johnson and her team. While perhaps less taxing of our ingenuity than older methods, the results one can get from these explorations are no less interesting or useful.

To solve for the azimuthal angle ψ using more modern methods, let’s set up parameters for a simple circular orbit beginning after burnout over Florida, assuming a spherically symmetric Earth (I’ll not bother trying to match the orbit of the Johnson paper precisely, and I’ll redefine certain quantities from above using the modern SI system of units). Starting from the same low-Earth orbit altitude used by Johnson, and using a little spherical trigonometry, it is straightforward to derive the initial conditions for our orbit:

\[Phi]1 = 28.50 Degree (* latitude of burnout point*); \[Lambda]1 = (279.45 - 360) Degree (* longitude of burnout point *);

(* assuming a circular orbit, and leaving azimuth angle \[Psi] \ undetermined *)  r0 = QuantityMagnitude[Quantity[21637933., "Feet"],     "Meters"] (* radius of the orbit *); v0 = Sqrt[G M/r0] (* initial velocity of the circular orbit *); \[ScriptCapitalR] =    RotationTransform[\[Lambda]1, {0, 0,      1}] (* used to rotate initial conditions *); {x0, y0, z0} = \[ScriptCapitalR][{r0 Cos[\[Phi]1], 0,      r0 Sin[\[Phi]1]}] (* initial x,y,z at burnout point *); {vx0, vy0, vz0} = \[ScriptCapitalR][    v0 {-Cos[\[Phi]1] Sin[\[Gamma]] -        Cos[\[Gamma]] Cos[\[Psi]] Sin[\[Phi]1],      Cos[\[Gamma]] Sin[\[Psi]],       Cos[\[Gamma]] Cos[\[Phi]1] Cos[\[Psi]] -        Sin[\[Gamma]] Sin[\[Phi]1]}] (* initial velocity at the burnout \ point *);

initCond[\[Psi]_, \[Gamma]_] = {x[0] == x0, y[0] == y0,     z[0] == z0,                                                           x'[0] == vx0, y'[0] == vy0,     z'[0] == vz0};

The relevant physical parameters can be obtained directly from within the Wolfram Language:

M, R, G} = QuantityMagnitude[Flatten[{Entity["Planet",                 "Earth"][{"Mass", "Radius"}],       Quantity[1, "GravitationalConstant"]}], "SIBase"];

Next, I obtain a differential equation for the motion of our spacecraft, given the gravitational field of the Earth. There are several ways you can model the gravitational potential near the Earth. Assuming a spherically symmetric planet and utilizing a Cartesian coordinate system throughout, the potential is merely:

pot[{x_, y_, z_}] := -G M /Sqrt[x^2 + y^2 + z^2];

Alternatively, you can use a more realistic model of Earth’s gravity, where the planet’s shape is taken to be an oblate ellipsoid of revolution. The exact form of the potential from such an ellipsoid (assuming constant mass-density over ellipsoidal shells), though complicated (containing multiple elliptic integrals), is available through EntityValue:

evg = EntityValue[    Entity["PhysicalSystem", "MassiveTriaxialEllipsoid"],     "GravitationalPotential"];

For a general homogeneous triaxial ellipsoid, the potential contains piecewise functions:

(pw = Cases[evg, _ _Piecewise, \[Infinity]][[1]]) // TraditionalForm //   Style[#, 6] &

Traditional form output

Here, κ is the largest root of x2/(a2+κ)+y2/(b2+κ)+z2/(c2+κ)=1. In the case of an oblate ellipsoid, the previous formula can be simplified to contain only elementary functions…

Limit[pw[[1, -1]],    QuantityVariable["b","Radius"] -> QuantityVariable[    "a","Radius"]] // FullSimplify

Traditional form output

… where κ=((2 z2 (a2c2+x2+y2)+(-a2+c2+x2+y2)2+z4)1/2a2c2+x2+y2+z2)/2.

A simpler form that is widely used in the geographic and space science community, and that I will use here, is given by the so-called International Gravity Formula (IGF). The IGF takes into account differences from a spherically symmetric potential up to second order in spherical harmonics, and gives numerically indistinguishable results from the exact potential referenced previously. In terms of four measured geodetic parameters, the IGF potential can be defined as follows:

{a, b} = GeodesyData["ITRF00", #] & /@ {"SemimajorAxis",      "SemiminorAxis"} ;  gPole = 9.8321849378(* g at Earth's pole in m/s^2*); gEquator = 9.78903267715(* g at Earth's equator in m/s^2*); With[{k = b gPole/(a gEquator) - 1, e = Sqrt[1 - b^2/a^2]},  potIGF[{x_, y_, z_}] :=   With[{(* latitude *) \[Phi] = ArcTan[Sqrt[x^2 + y^2], z]},    -G M/Sqrt[x^2 + y^2 + z^2] (1 + k Sin[\[Phi]]^2)/      Sqrt[1 - e^2 Sin[\[Phi]]^2]]]

I could easily use even better values for the gravitational force through GeogravityModelData. For the starting position, the IGF potential deviates only 0.06% from a high-order approximation:

{potIGF[{x0, y0, z0}],  GeogravityModelData[GeoPosition[GeoPositionXYZ[{x0, y0, z0}]],     "Potential"] // QuantityMagnitude[#, "SIBase"] &}

{-6.04963*10^7, -6.05363*10^7}

With these functional forms for the potential, finding the orbital path amounts to taking a gradient of the potential to get the gravitational field vector and then applying Newton’s third law. Doing so, I obtain the orbital equations of motion for the two gravity models:

grad = -Grad[pot[{x[t], y[t], z[t]}], {x[t], y[t], z[t]}]; gradIGF = -Grad[potIGF[{x[t], y[t], z[t]}], {x[t], y[t], z[t]}];

Fma = {{x''[t], y''[t], z''[t]} == grad}; FmaIGF = {{x''[t], y''[t], z''[t]} == gradIGF};

I am now ready to use the power of NDSolve to compute orbital trajectories. Before doing this, however, it will be nice to display the orbital path as a curve in three-dimensional space. To give these curves context, I will plot them over a texture map of the Earth’s surface, projected onto a sphere. Here I construct the desired graphics objects:

(*define orbit burnoutDot and burnoutArrow graphics*) burnoutCoords = {x0, y0, z0}; burnoutDirection = {vx0, vy0, vz0}/v0; burnoutDot = {Red, Sphere[burnoutCoords, R/30]}; burnoutArrow = {Red, Arrow[{burnoutCoords,       burnoutCoords + (R/3) burnoutDirection}]}; burnoutLabels[\[Psi]_, \[Gamma]_] =    Graphics3D[{burnoutDot, burnoutArrow}];

(*define globe texture and markings*) earthTexture = Lighter[#, 0.75] &@ ImageReflect[PlanetData["Earth",       "CylindricalEquidistantTexture"], Bottom]; globe = ParametricPlot3D[   R {-Cos[p] Sin[t], -Sin[p] Sin[t], Cos[t]}, {p, 0, 2 Pi}, {t, 0,     Pi}, Mesh -> None, PlotStyle -> Texture[earthTexture],    TextureCoordinateFunction -> Automatic]

(*define globe texture and markings*) earthTexture = Lighter[#, 0.75] &@ ImageReflect[PlanetData["Earth",       "CylindricalEquidistantTexture"], Bottom]; globe = ParametricPlot3D[   R {-Cos[p] Sin[t], -Sin[p] Sin[t], Cos[t]}, {p, 0, 2 Pi}, {t, 0,     Pi}, Mesh -> None, PlotStyle -> Texture[earthTexture],    TextureCoordinateFunction -> Automatic]

pole = Graphics3D[{Thick, Red, Line[{{0, 0, -1.3 R}, {0, 0, 1.3 R}}]}]; equator =    ParametricPlot3D[R {Cos[t], Sin[t], 0}, {t, 0, 2 Pi},     PlotStyle -> Yellow]; globeMarkings = {pole, equator};

While the orbital path computed in an inertial frame forms a periodic closed curve, when you account for the rotation of the Earth, it will cause the spacecraft to pass over different points on the Earth’s surface during each subsequent revolution. I can visualize this effect by adding an additional rotation term to the solutions I obtain from NDSolve. Taking the number of orbital periods to be three (similar to John Glenn’s flight) for visualization purposes, I construct the following Manipulate to see how the orbital path is affected by the azimuthal launch angle ψ, similar to the study in Johnson’s paper. I’ll plot both a path assuming a spherical Earth (in white) and another path using the IGF (in green) to get a sense of the size of the oblateness effect (note that the divergence of the two paths increases with each orbit):

With[{\[Omega] = \[Omega]Capitale/(360/(2 Pi ) 60), T = 3 2 Pi r0/v0,             \[Rho] = Sqrt[x[t]^2 + y[t]^2], \[Phi] =     ArcTan[x[t], y[t]],   \[Gamma] = 0.5 Degree},  Manipulate[   Block[{\[Psi] = \[Psi]s},     sol = NDSolve[      Join[Fma, initCond[\[Psi]s, \[Gamma]]], {x, y, z}, {t,        0, \[Tau] T}];    solIGF =      NDSolve[Join[FmaIGF, initCond[\[Psi]s, \[Gamma]]], {x, y, z}, {t,        0, \[Tau] T}];    Show[ParametricPlot3D[       Evaluate[{\[Rho] Cos[\[Phi] - \[Omega] t], \[Rho] Sin[\[Phi] - \ \[Omega] t],          z[t]} /. {sol[[1]], solIGF[[1]]}], {t, 0, \[Tau] T},        PlotStyle -> {White, Darker[Green]}] /.       l_Line :> Tube[l, 35000],      globe, globeMarkings, burnoutLabels[\[Psi]s, \[Gamma]],     Frame -> None, Ticks -> None,      PlotRange -> {All, All, {-1.2 R, 1.2 R}},      RotationAction -> "Clip", ViewPoint -> {0, -2, 0.4}]],   {{\[Psi]s, 70 Degree, "start angle"}, -Pi, Pi},   {{\[Tau], 1.025, "flight time"}, 0.001, 3},    SaveDefinitions -> True]]

globe manipulate

In the notebook attached to this blog, you can see this Manipulate in action, and note the speed at which each new solution is obtained. You would hope that Katherine Johnson and her colleagues at NASA would be impressed!

Now, varying the angle ψ at burnout time, it is straightforward to calculate the position of the spacecraft after, say, three revolutions:

posis = Block[{\[Gamma] =       0.5 Degree, \[Omega] = \[Omega]Capitale/(360/(2 Pi ) 60),      T = 3.05 2 Pi r0/v0},    Table[Map[(Most[#] - {0, \[Omega] T 360/(2 Pi)}) &,       GeoPosition[GeoPositionXYZ[{x[T], y[T], z[T]} /.         NDSolve[           Join[FmaIGF, initCond[\[Psi], \[Gamma]]], {x, y, z}, {t, 0,             T}][[1]]]]],     {\[Psi], 50 Degree, 90 Degree, 1 Degree}]];

GeoGraphics[{Gray, GeoPath[{"Parallel", 28, {-135, -120}}],   Yellow, Point[posis]}, GeoRange -> Quantity[450, "Miles"],  GeoBackground -> "StreetMap", ImageSize -> 400]

GeoGraphics[{Gray, GeoPath[{"Parallel", 28, {-135, -120}}],   Yellow, Point[posis]}, GeoRange -> Quantity[450, "Miles"],  GeoBackground -> "StreetMap", ImageSize -> 400]

Modeling the Reentry of a Satellite

The movie also mentions Euler’s method in connection with the reentry phase. After the initial problem of finding the azimuthal angle has been solved, as done in the previous sections, it’s time to come back to Earth. Rockets are fired to slow down the orbiting body, and a complex set of events happens as the craft transitions from the vacuum of space to an atmospheric environment. Changing atmospheric density, rapid deceleration and frictional heating all become important factors that must be taken into account in order to safely return the astronaut to Earth. Height, speed and acceleration as a function of time are all problems that need to be solved. This set of problems can be solved with Euler’s method, as done by Katherine Johnson, or by using the differential equation-solving functionality in the Wolfram Language.

For simple differential equations, one can get a detailed step-by-step solution with a specified quadrature method. An equivalent of Newton’s famous F = m a for a time-dependent mass m(t) is the so-called ideal rocket equation (in one dimension)…

DSolve[{(m0 - \[Beta] t) v'[t] == Subscript[\[ScriptV], ex] \[Beta],    v[0] == vi}, v[t], t]

… where m(t) is the rocket mass, ve the engine exhaust velocity and mp(t) the time derivative of the propellant mass. Assuming a constant mp(t), the structure of the equation is relatively simple and easily solvable in closed form:

{{v[t] ->     vi + Log[m0] Subscript[\[ScriptV], ex] -      Log[m0 - t \[Beta]] Subscript[\[ScriptV], ex]}}

With initial and final conditions for the mass, I get the celebrated rocket equation (Tsiolkovsky 1903):

FormulaLookup["rocket equation"]

{"RocketEquation"}

FormulaData["RocketEquation"]

QuantityVariable[ \!\(\*SubscriptBox[\("v"\), \("f"\)]\),"Speed"] ==   Log[QuantityVariable[ \!\(\*SubscriptBox[\("m"\), \("i"\)]\),"Mass"]/QuantityVariable[ \!\(\*SubscriptBox[\("m"\), \("f"\)]\),"Mass"]] QuantityVariable[ \!\(\*SubscriptBox[\("v"\), \("e"\)]\),"Speed"] + QuantityVariable[ \!\(\*SubscriptBox[\("v"\), \("i"\)]\),"Speed"]

FormulaData["RocketEquation", "QuantityVariableTable"]

FormulaData["RocketEquation", "QuantityVariableTable"]

FormulaData@FormulaLookup["rocket equation"][[1]]

FormulaData@FormulaLookup["rocket equation"][[1]]

The details of solving this equation with concrete parameter values and e.g. with the classical Euler method I can get from Wolfram|Alpha. Here are those details together with a detailed comparison with the exact solution, as well as with other numerical integration methods:

WolframAlpha["use Euler method (2-t)v'(t)=4, v(0)=0, from t=0 to \ 0.95"]

WolframAlpha["use Euler method (2-t)v'(t)=4, v(0)=0, from t=0 to \ 0.95"]

Following the movie plot, I will now implement a minimalistic ODE model of the reentry process. I start by defining parameters that mimic Glenn’s flight:

(* Glenn's flight *) mCapsule =    QuantityMagnitude[    Entity["MannedSpaceMission", "MercuryAtlas6"][     EntityProperty["MannedSpaceMission", "Mass"]],     "SIBase"]; (* mass of Mercury Atlas 6  *) \[Phi]0 = 34.00; \[Lambda]0 =   241.00 Degree;  (* initial latitude and longitude *) h = QuantityMagnitude[    Entity["Satellite", "00240"][     EntityProperty["Satellite", "AverageElevationWGS84"]], "Meters"];

X0 = (R + h) {Cos[\[Lambda]0] Cos[\[Phi]0],      Sin[\[Lambda]0] Cos[\[Phi]0],      Sin[\[Phi]0]}; (* position at start of reentry *) V0 = Sqrt[G M/(R + h)] {-Sin[\[Lambda]0], Cos[\[Lambda]0],      0};(* velocity at start of reentry *)

I assume that the braking process uses 1% of the thrust of the stage-one engine and runs, say, for 60 seconds. The equation of motion is:

Traditional output

Here, Fgrav is the gravitational force, Fexhaust(t) the explicitly time-dependent engine force and Ffriction(x(t),v(t)) the friction force. The latter depends via the air density explicitly on the position x(t) and via the friction law on v(t).

For the height-dependent air density, I can conveniently use the StandardAtmosphereData function. I also account for a height-dependent area because of the parachute that opened about 8.5 km above ground:

Cd = 1 (* drag coefficient *);  airDensity[X : {_Real, __}] :=   QuantityMagnitude[   StandardAtmosphereData[Quantity[Norm[X] - R, "Meters"], "Density"],    "SIBase"] airFriction[X_List,    V_List] := -1/2 area[Norm[X - R]] V.V Cd airDensity[X] V/Sqrt[V.V] brake[V_List,    t_, {\[Alpha]_, T_}] := -If[t < T, \[Alpha] Normalize[V], 0]

This gives the following set of coupled nonlinear differential equations to be solved. The last WhenEvent[...] specifies to end the integration when the capsule reaches the surface of the Earth. I use vector-valued position and velocity variables X and V:

area[height_] := If[h > 8500, 10, (* parachute *)2000];

With these definitions for the weight, exhaust and air friction force terms…

Fgrav[X_, V_, t_] := -G mCapsule M X/Sqrt[X.X]^3  Fexhaust[X_, V_, t_] := brake[V, t, {3000, 60}] Fairfraction[X_, V_, t_] := airFriction[X , V]

… total force can be found via:

Ftotal[X_, V_, t_]  :=   Fgrav[X, V, t] + Fexhaust[X, V, t] + Fairfraction[X, V, t]

odeSystem = { X'[t] == V[t],                          mCapsule V'[t] == Ftotal[X[t] , V[t], t],                              WhenEvent[Norm[X[t]] - R == 0, "StopIntegration"]};

In this simple model, I neglected the Earth’s rotation, intrinsic rotations of the capsule, active flight angle changes, supersonic effects on the friction force and more. The explicit form of the differential equations in coordinate components is the following. The equations that Katherine Johnson solved would have been quite similar to these:

(odeSystem /. {X -> ({x[#], y[#], z[#]} &),       V -> ({vx[#], vy[#], vz[#]} &)} /.                              w_WhenEvent :> Evaluate //@ w) //   Column[#, Dividers -> Center] &

Traditional output

Supplemented by the initial position and velocity, it is straightforward to solve this system of equations numerically. Today, this is just a simple call to NDSolve. I don’t have to worry about the method to use, step size control, error control and more because the Wolfram Language automatically chooses values that guarantee meaningful results:

tMax = 5000; inits = {X[0] == X0, V[0] == V0}; nds = NDSolve[Join[odeSystem, inits], {X, V}, {t, 0, 60, tMax}]

Output

Here is a plot of the height, speed and acceleration as a function of time:

plotXVAT[nds_] :=   With[{T = nds[[1, 1, 2, 1, 1, 2]]/60,     opts = Sequence[ImageSize -> 220, PlotRange -> All]},   GraphicsRow[{     Plot[(Norm[X[60 t]] - R)/1000 /. nds[[1]], {t, 0, T}, opts,       AxesLabel -> { "time (min)", "height (km)"}],     Plot[Norm[V[60 t]]/1000 /. nds[[1]], {t, 0, T}, opts,       AxesLabel -> { "time (min)", "speed (km/s)"}],      Plot[      Norm[Ftotal[X[60 t], V[60 t], 60 t]]/(9.81 mCapsule) /.        nds[[1]], {t, 0, T}, opts,       AxesLabel -> { "time (min)", "acceleration (g)"}]},    Spacings -> 0]]

plotXVAT[nds]

plotXVAT[nds]

Plotting as a function of height instead of time shows that the exponential increase of air density is responsible for the high deceleration. This is not due to the parachute, which happens at a relatively low altitude. The peak deceleration happens at a very high altitude as the capsule goes from a vacuum to an atmospheric environment very quickly:

With[{T = nds[[1, 1, 2, 1, 1, 2]]/60,    opts = Sequence[ImageSize -> 220, PlotRange -> All]},   ParametricPlot[   Evaluate[{(Norm[X[60 t]] - R)/1000,       Norm[Ftotal[X[60 t], V[60 t], 60 t]]/(9.81 mCapsule)} /.      nds[[1]]], {t, 0, T}, opts,    AxesLabel -> {"height (km)", "acceleration (g)"},    AspectRatio -> 0.6]]

With[{T = nds[[1, 1, 2, 1, 1, 2]]/60,    opts = Sequence[ImageSize -> 220, PlotRange -> All]},   ParametricPlot[   Evaluate[{(Norm[X[60 t]] - R)/1000,       Norm[Ftotal[X[60 t], V[60 t], 60 t]]/(9.81 mCapsule)} /.      nds[[1]]], {t, 0, T}, opts,    AxesLabel -> {"height (km)", "acceleration (g)"},    AspectRatio -> 0.6]]

And here is a plot of the vertical and tangential speed of the capsule in the reentry process:

With[{T = nds[[1, 1, 2, 1, 1, 2]],    opts = Sequence[ImageSize -> 300, PlotRange -> All]},   {Plot[V[t].Normalize[X[t]]/1000 /. nds[[1]], {t, 0, T}, opts,     AxesLabel -> { "time (s)", "vertical speed (km/s)"}],   Plot[(Norm[V[t] - V[t].Normalize[X[t]]  Normalize[V[t]]])/1000 /.      nds[[1]], {t, 0, T}, opts,     AxesLabel -> { "time (s)", "tangential speed (km/s)"}]}]

With[{T = nds[[1, 1, 2, 1, 1, 2]],    opts = Sequence[ImageSize -> 300, PlotRange -> All]},   {Plot[V[t].Normalize[X[t]]/1000 /. nds[[1]], {t, 0, T}, opts,     AxesLabel -> { "time (s)", "vertical speed (km/s)"}],   Plot[(Norm[V[t] - V[t].Normalize[X[t]]  Normalize[V[t]]])/1000 /.      nds[[1]], {t, 0, T}, opts,     AxesLabel -> { "time (s)", "tangential speed (km/s)"}]}]

Now I repeat the numerical solution with a fixed-step Euler method:

ndsEuler =   NDSolve[Join[odeSystem, inits], {X, V}, {t, 0, tMax},    Method -> {"FixedStep", Method -> "ExplicitEuler"},    StartingStepSize -> 0.05]

ndsEuler =   NDSolve[Join[odeSystem, inits], {X, V}, {t, 0, tMax},    Method -> {"FixedStep", Method -> "ExplicitEuler"},    StartingStepSize -> 0.05]

Qualitatively, the solution looks the same as the previous one:

plotXVAT[ndsEuler]

plotXVAT[ndsEuler]

For the used step size of the time integration, the accumulated error is on the order of a few percent. Smaller step sizes would reduce the error (see the previous Wolfram|Alpha output):

With[{T = nds[[1, 1, 2, 1, 1, 2]]},  Plot[100 ((Norm[X[t]] - R /. nds[[1]])/       (Norm[X[t]] - R /. ndsEuler[[1]]) - 1), {t, 0, T},                AxesLabel -> { "time (s)", "height error (%)"}]]

With[{T = nds[[1, 1, 2, 1, 1, 2]]},  Plot[100 ((Norm[X[t]] - R /. nds[[1]])/       (Norm[X[t]] - R /. ndsEuler[[1]]) - 1), {t, 0, T},                AxesLabel -> { "time (s)", "height error (%)"}]]

Note that the landing time predicted by the Euler method deviates only 0.11% from the previous time. (For comparison, if I were to solve the equation with two modern methods, say "BDF" vs. "Adams", the error would be smaller by a few orders of magnitude.)

Now, the reentry process generates a lot of heat. This is where the heat shield is needed. At which height is the most heat per area q generated? Without a detailed derivation, I can, from purely dimensional grounds, conjecture Overscript[q, .]~\[Rho] v^3:

DimensionalCombinations[{"Speed", "MassDensity"}, "HeatFlux"]

{QuantityVariable["MassDensity","MassDensity"] QuantityVariable[   "Speed","Speed"]^3}

With[{T = nds[[1, 1, 2, 1, 1, 2]]},  ParametricPlot[   Evaluate[{(Norm[X[t]] - R)/1000, airDensity[X[t]] Norm[V[t]]^3} /.      nds[[1]]], {t, 0, T}, PlotRange -> All, Ticks -> {True, False},               AxesLabel -> { "height (km)", "heat generated (a.u.)"},    AspectRatio -> 0.6]]

With[{T = nds[[1, 1, 2, 1, 1, 2]]},  ParametricPlot[   Evaluate[{(Norm[X[t]] - R)/1000, airDensity[X[t]] Norm[V[t]]^3} /.      nds[[1]]], {t, 0, T}, PlotRange -> All, Ticks -> {True, False},               AxesLabel -> { "height (km)", "heat generated (a.u.)"},    AspectRatio -> 0.6]]

Many more interesting things could be calculated (Hicks 2009), but just like the movie had to fit everything into two hours and seven minutes, I will now end my blog for the sake of time. I hope I can be pardoned for the statement that, with the Wolfram Language, the sky’s the limit.

To download this post as a Computable Document Format (CDF) file, click here. New to CDF? Get your copy for free with this one-time download.

Comments

Join the discussion

!Please enter your comment (at least 5 characters).

!Please enter your name.

!Please enter a valid email address.

14 comments

  1. The cdf link is broken!

    Reply
  2. The download link seems not working.

    Reply
  3. Thanks Jeffrey, Paco, and Michael for a most appropriate blog. I saw this film in Australia several weeks ago, and found it very inspiring. Most mathematical scientists would agree that there are only a few good films in which mathematics and mathematicians play a central role. This is one such, and I have to say the title is a clever one. Perhaps you guys could respond with a (short?) list of your favourite “mathematics” films, not counting the TV series “Numbers”, of course. :-) Would you include, for example, “Travelling Salesman”?

    Cheers

    Barrie

    Reply
  4. Whew! I’m sure glad this was “straightforward” and not complicated like I feared.

    Reply
  5. I’m excited to uncover this site. I need to thank you for some time for this fantastic read !! I definitely loved every part of it and I have you saved as a favorite to see new stuff in your blog.

    Reply
  6. This is really an amazing work from this site.

    Reply
  7. I have some questions regarding your code to reproduce Johnson’s graph of the orbit solution. I have copied your code into Mathematica and have successful results through the plot of the iteration convergence, but the code for johnson’s graph does not seem to be working.

    Reply
  8. Good day.It’s a creally incredible demonstration.and a fantastic utility.Bravo.

    Reply
  9. Thank you so much Wolfram! My mom is a math nerd and was asking me for something like this. I was able to find it on my first Google search and give it to her. She is delighted!!

    Reply