Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Full Phugoid Model

In Lesson 1, we developed an idealized model of phugoid motion with no drag. In Lesson 2, we studied small perturbations around trimmed flight (straight-line phugoid), leading to simple harmonic motion. A useful pattern of re-writing a second-order differential equation as a system of two first-order equations allowed us to use Euler’s method to compute a two-component state. We learned about convergence and calculated the error of the numerical solution, comparing with an analytical solution. That is a good foundation!

We now return to the full dynamical model, include drag, and allow the aircraft speed and trajectory angle to vary together.

The numerical method stays the same, but the model and state variables change:

AspectLesson 2Lesson 3
ModelLinearized oscillationNonlinear, damped phugoid
Stateu=[z,b]u=[z,b]u=[v,θ,x,y]u=[v,\theta,x,y]
Vertical coordinatezz: depth, positive downwardyy: altitude, positive upward
Right-hand siderhs_linear_phugoid()rhs_full_phugoid()
Numerical methodForward EulerThe same Forward Euler step

The change from downward-positive depth to upward-positive altitude deserves particular attention. Here, yy is a spatial coordinate used to draw the aircraft’s trajectory in the sky, so increasing yy means climbing. We use θ\theta for the trajectory angle and take it as positive above the horizontal.

Lift, drag, and weight acting on a glider whose trajectory angle is positive above the horizontal

Figure 1:Forces on a glider with a positive trajectory angle.

In Figure 1, LL is lift, W=mgW=mg is weight, DD is drag, and θ\theta is the instantaneous trajectory angle. Resolving Newton’s second law parallel and perpendicular to the trajectory gives

mdvdt=−Wsin⁡θ−D,mvdθdt=−Wcos⁡θ+L.\begin{aligned} m\frac{dv}{dt} &= -W\sin\theta-D,\\ mv\frac{d\theta}{dt} &= -W\cos\theta+L. \end{aligned}

On the direction normal to the trajectory, we used that the aircraft travels a small distance ds=Rdθds=Rd\theta along the curved trajectory, at a speed v=ds/dt=R dθ/dtv=ds/dt=R \, d\theta/dt. This helps us write the centripetal acceleration v2/Rv^2/R as v dθ/dtv\, d\theta/dt.

Dividing Equation 1 by the weight and adopting primes for the time derivative gives

v′g=−sin⁡θ−DW,vgθ′=−cos⁡θ+LW.\begin{aligned} \frac{v'}{g} &= -\sin\theta-\frac{D}{W},\\ \frac{v}{g}\theta' &= -\cos\theta+\frac{L}{W}. \end{aligned}

From Lesson 1, the lift-to-weight ratio is written in terms of the trim speed, L/W=v2/vt2L/W=v^2/v_t^2. Lift and drag share the same dynamic-pressure factor:

L=CLS12ρv2,D=CDS12ρv2.L=C_LS\frac{1}{2}\rho v^2, \qquad D=C_DS\frac{1}{2}\rho v^2.

It follows from Equation 3 that D/L=CD/CLD/L=C_D/C_L. Substituting these relations into Equation 2 produces the nonlinear velocity-and-angle model:

v′=−gsin⁡θ−CDCLgvt2v2,θ′=−gvcos⁡θ+gvt2v.\begin{aligned} v' &= -g\sin\theta -\frac{C_D}{C_L}\frac{g}{v_t^2}v^2,\\ \theta' &= -\frac{g}{v}\cos\theta +\frac{g}{v_t^2}v. \end{aligned}

The factor CD/CLC_D/C_L is the inverse of the aerodynamic efficiency L/DL/D. It supplies damping: when the drag coefficient is zero, the damping term disappears. More aerodynamically efficient aircraft therefore have a more weakly damped phugoid mode.

The initial value problem

To draw the flight path, we also integrate the spatial coordinates. The horizontal position xx and upward-positive altitude yy satisfy

x′(t)=vcos⁡θ,y′(t)=vsin⁡θ.\begin{aligned} x'(t) &= v\cos\theta,\\ y'(t) &= v\sin\theta. \end{aligned}

Together, Equation 4 and Equation 5 form a system of four first-order differential equations. We need one initial value for every state variable:

v(0)=v0,θ(0)=θ0,x(0)=x0,y(0)=y0.v(0)=v_0,\qquad \theta(0)=\theta_0,\qquad x(0)=x_0,\qquad y(0)=y_0.

Solve with Forward Euler

Forward Euler replaces a time derivative by a forward difference. For the speed,

v′(tn)≈vn+1−vnΔt,v'(t_n)\approx\frac{v^{n+1}-v^n}{\Delta t},

where the superscript nn identifies the state at time tnt_n. Using the form of Equation 7 for each of the differential equations and solving for the next state gives

vn+1=vn+Δt(−gsin⁡θn−CDCLgvt2(vn)2),θn+1=θn+Δt(−gvncos⁡θn+gvt2vn),xn+1=xn+Δt vncos⁡θn,yn+1=yn+Δt vnsin⁡θn.\begin{aligned} v^{n+1} &= v^n+\Delta t\left( -g\sin\theta^n -\frac{C_D}{C_L}\frac{g}{v_t^2}(v^n)^2 \right),\\ \theta^{n+1} &= \theta^n+\Delta t\left( -\frac{g}{v^n}\cos\theta^n +\frac{g}{v_t^2}v^n \right),\\ x^{n+1} &= x^n+\Delta t\,v^n\cos\theta^n,\\ y^{n+1} &= y^n+\Delta t\,v^n\sin\theta^n. \end{aligned}

Note that Equation 8 evaluates the complete right-hand side from the current state before updating any state variable. In other words, after evaluating all four derivatives, we advance the complete state together.

As before, we collect the state and its derivatives into vectors:

u=[vθxy],u′=f(u)=[−gsin⁡θ−CDCLgvt2v2−gvcos⁡θ+gvt2vvcos⁡θvsin⁡θ].u= \begin{bmatrix} v\\ \theta\\ x\\ y \end{bmatrix}, \qquad u'=f(u)= \begin{bmatrix} -g\sin\theta-\dfrac{C_D}{C_L}\dfrac{g}{v_t^2}v^2\\ -\dfrac{g}{v}\cos\theta+\dfrac{g}{v_t^2}v\\ v\cos\theta\\ v\sin\theta \end{bmatrix}.

In vector form, one Forward Euler step is simply

un+1=un+Δt f(un).u^{n+1}=u^n+\Delta t\,f(u^n).

We will adopt the modular approach from Lesson 2 after the agent-driven refactoring, where we defined a Python function for the system dynamics and another for the time stepping. This is the key transition: rhs_full_phugoid() represents the model with drag and returns four derivatives, but euler_step() still implements Equation 10 without needing to know the length or meaning of the state vector. That is the power of array operations!

We begin by importing Python’s array and plotting libraries using the conventional aliases: np for NumPy and plt for Matplotlib.

Next, we need to choose representative model parameters and initial conditions. Angles in the state are measured in radians because np.sin() and np.cos() expect radians. The ratio CL/CD=40C_L/C_D=40 represents a reasonably efficient sailplane, and vt=30 m/sv_t=30\ \mathrm{m/s} is a representative trim speed. These are exploratory values rather than a model of a particular aircraft.

The function below translates Equation 9 directly into code. The name rhs_full_phugoid distinguishes this nonlinear four-state model from the linear two-state function in the previous lesson. We’re also providing a detailed description of the function parameters and return values in the docstring: the commented block under the function name. This block is shown to the user when appending a question mark (?) to the function name, or using the built-in help() functionality.

Compare each returned array entry with the corresponding row of Equation 9. The position values xx and yy do not appear on the right-hand side, but keeping them in the state lets the same integration loop advance both the flight dynamics and the trajectory.

The Forward Euler function has the same vector update as in the previous lesson. This version uses *args so it can forward the larger model-parameter list without naming those parameters.

Now choose the final time TT and step size Δt\Delta t. The variable num_steps counts updates, while the time grid contains num_steps + 1 points because it includes both endpoints. np.empty() allocates a two-dimensional state-history array with one row per time step and four columns ordered as [v,θ,x,y][v,\theta,x,y]. In the for-loop, the function euler_step() is called to get the solution at time step n+1.

Plot the trajectory

The state history contains everything we need to plot the trajectory of the glider. We extract the position coordinates using array indexing. A colon in the row position selects every time point, while columns 2 and 3 contain horizontal position and altitude, respectively.[1]

Grid convergence

In Lesson 2, when we studied the straight-line phugoid under a small perturbation, we looked at convergence by comparing the numerical solution with the exact solution. But we do not have an exact solution for this full nonlinear model. Instead, we compare solutions computed with different time-step sizes against the solution on a fine grid.

The finest-grid result is a numerical reference, not an exact solution. The resulting differences provide evidence of grid convergence only if the reference grid is sufficiently resolved and all compared grids represent the same initial-value problem over the same interval.

Compute a state history for each time-step size:

To compare arrays sampled on different grids, their time points must align. For nested grids, the refinement ratio r=Δtcoarse/Δtfiner=\Delta t_{\mathrm{coarse}}/\Delta t_{\mathrm{fine}} is an integer: every coarse-grid time is also a fine-grid time. A strided slice, which extracts elements from an array at regular intervals, can then select the fine-grid values that correspond to the coarse grid.

The function below calculates refinement_ratio from the two time-step sizes. round() accommodates their floating-point representation, after which np.isclose() verifies that Δtcoarse≈r Δtfine\Delta t_{\mathrm{coarse}}\approx r\,\Delta t_{\mathrm{fine}}. If this relation fails, strided slicing cannot make a valid time-by-time comparison; interpolation would be needed, and we deliberately avoid introducing that additional approximation here. raise ValueError(... means: “This input is unacceptable; stop this function and report an error.”

After slicing, the shape check confirms that the endpoints and sample counts also align. Because every grid in this lesson begins at zero and ends at the same TT, the spacing and shape checks together establish that corresponding entries represent the same times. The generic names q_coarse and q_fine make clear that the function compares one sampled quantity; below, that quantity will be horizontal position.

Now we can use the function to compute the differences between the xx positions that were computed with each different value of the time step Δt\Delta t and the reference values on the finest grid. These differences will be used to analyze convergence below.

The assignments before the loop select the finest-grid data as the numerical reference: the index -1 means the final item, and column 2 of the state history contains horizontal position. The slices [:-1] then exclude that reference from both sequences, so we do not compare the finest solution with itself.

Each entry in u_histories was generated from the time-step size at the same position in dt_values. The call to zip(..., strict=True) pairs those corresponding entries and verifies that the two sliced sequences have equal lengths. The loop can therefore be read almost as prose: for each trial time step and its corresponding state history do something (compute the differences).

Inside the loop, x_trial = u_trial[:, 2] extracts the horizontal-position history, discrete_l1_difference() compares it with x_reference, using both time-step sizes to align the grids, and .append() stores the resulting difference in the same order as the coarser entries of dt_values.

We’re ready to make a plot of our analysis. Like in Lesson 2, we use a log-log plot to visualize a power-law relationship.

As the time step decreases, the difference from the finest-grid reference also decreases. This is useful numerical evidence, but it does not yet prove convergence because the finest reference is not exact.

Observed order of convergence

Systematic grid-refinement studies and estimates of observed order are standard tools in numerical-solution verification Roache, 1998 Oberkampf & Roy, 2010. As you increasingly adopt agentic coding, having a strong foundation on these techniques will be ever more important.

Suppose three step sizes have a constant refinement ratio rr, with f1f_1 the finest-grid result and f3f_3 the coarsest. Let D21D_{21} be a norm of the difference between the medium and fine results, and D32D_{32} the corresponding difference between the coarse and medium results. The observed order of convergence is:

p=log⁡(D32/D21)log⁡r.p= \frac{\log(D_{32}/D_{21})}{\log r}.

For a first-order method in its asymptotic range, we expect D32/D21≈rD_{32}/D_{21}\approx r and therefore p≈1p\approx1. We use three nested grids with r=2r=2.

Verification study with an agent

Verification studies are necessary for computational credibility, but they often require repetitive integrations, grid alignment, comparisons, and reporting. An agent can reduce that workload and make these frequently neglected activities more practical. It cannot decide which claim matters, whether the experiment actually tests that claim, or whether the evidence is sufficient. Those responsibilities remain yours.

Complete the task specification

Create a Markdown cell in your notebook titled Agent observed-order task brief and complete the four headings below. The directions under each heading are requirements for your specification, not text to send unchanged. Your brief should be precise enough that another person could tell whether the requested experiment was carried out, without prescribing every line of code.

  • Goal and scope: State the numerical claim being investigated, identify horizontal position x(t)x(t) as the quantity to compare, and name your notebook as the artifact in which the agent will add the investigation. Keep physical-model validation, other integrators, and the paper-airplane challenge in Lesson 4 out of scope.

  • Non-negotiables: Record your three calculated step sizes in fine-to-coarse order; the definitions of D21D_{21} and D32D_{32}; the formula for pp; and the existing model, Euler step, initial-value problem, final time, and comparison function that must remain unchanged. Require column 2 of the state history and nested-grid comparisons without interpolation.

  • Allowed actions: Grant only the access needed to inspect your notebook, add new cells after this section, execute the required local cells, and revise the new cells if they fail. Explicitly address changes to existing cells, other files, package installation, and network access.

  • Done when: Require the agent to expose the three step sizes and step counts, D21D_{21}, D32D_{32}, their ratio, the calculated pp, execution status, and a short record of its changes. The result p≈1p\approx1 is the hypothesis under investigation, not a completion condition: an unexpected result must be reported rather than tuned away.

Compare the completed brief with the minimum sufficient specification. Resolve any consequential ambiguity before granting edit or execution access.

Invoke the agent

Save your notebook so the attached artifact contains your independent expectations and completed task brief. Configure the permissions recorded in the brief, attach the saved notebook, and send this short request:

Prompt

Read the completed section titled “Agent observed-order task brief” in the attached notebook. Carry out the specified numerical investigation, including the permitted notebook edits and execution. Return the requested change record and numerical evidence. Do not broaden the investigation or make the final credibility judgment.

If you do not have access to an agent that can edit and execute a notebook, carry out the completed specification yourself and use the same audit and verdict below. The instructor can provide the reference result after you have recorded your own evidence.

Audit the implementation and evidence

Inspect the cells the agent added before accepting them. Do not judge the work from the final value of pp alone. Check that:

  • the grids are ordered fine, medium, and coarse, with the constant refinement ratio you specified;

  • every integration uses the same initial state, parameters, final time, rhs_full_phugoid(), and euler_step();

  • column 2, horizontal position, is compared on aligned grids using discrete_l1_difference();

  • D21D_{21} is the medium--fine difference, D32D_{32} is the coarse--medium difference, and their ratio appears in the correct order in Equation 11;

  • the response exposes the raw differences, ratio, and pp, rather than only asserting that verification passed; and

  • no previous cells or out-of-scope artifacts were changed.

Run the accepted cells yourself. Independently divide the reported D32D_{32} by D21D_{21} and use that ratio to recalculate pp. If either value disagrees with the agent’s report, locate the discrepancy rather than changing the expected theory or suppressing the result.

Completion rubric

This is a Complete/Revise checkpoint. Your independent expectation, specification, audit, evidence, and judgment are what matters. Every row must meet the Complete description. In particular, obtaining p≈1p\approx1 does not compensate for an incorrect or unaudited experiment.

CriterionCompleteRevise
Independent expectationCorrectly orders the grids and predicts D32/D21≈2D_{32}/D_{21}\approx2 and p≈1p\approx1 before delegation.Expectations are absent, recorded after delegation, or use incorrect grid or difference ordering.
SpecificationDefines the claim, quantity, grids, difference mapping, formula, task boundary, and required evidence.Leaves consequential numerical choices implicit or merely asks the agent to “check convergence.”
Access and scopeGrants bounded notebook inspection, new-cell editing, and local execution while protecting existing work.Gives unbounded access or permits changes to the model, Euler step, problem data, or prior results.
Code auditChecks state indexing, common problem data, grid nesting, difference ordering, and the observed-order formula.Judges primarily from the final value or accepts the response without inspecting its implementation.
Reproducible evidenceRecords all step sizes and counts, both raw differences, their ratio, pp, and the execution result.Reports only that pp is close to one or repeats an unsupported assurance that checks passed.
Verdict and limitationsConnects the evidence to a bounded claim and identifies what remains unverified.Treats observed order as proof that the model and complete calculation are correct.
ProvenanceRecords the agent, artifact, request, permitted actions, accepted changes, and learner-run verification.Material agent work or subsequent human corrections are not recorded.

What’s next?

This lesson marched the full, damped phugoid model forward with Euler’s method and used grid refinement to assess convergence without an exact solution. In Lesson 4, a paper-airplane challenge turns method choice into an exercise in engineering judgment. You will introduce explicit midpoint RK2, build and verify a touchdown-range calculation, and use refinement evidence to decide when a predicted range is accurate enough. You will then compare Euler and RK2 by computational cost and supervise agent-authored search code while retaining responsibility for execution and the final verdict.

Footnotes
  1. For a quick review of Python indexing using simple strings, see this video by Prof. Barba.

References
  1. Roache, P. J. (1998). Verification and Validation in Computational Science and Engineering. Hermosa Publishers.
  2. Oberkampf, W. L., & Roy, C. J. (2010). Verification and Validation in Scientific Computing. Cambridge University Press.