Path: blob/main/latex-templates/templates/mathematics/differential_eq.tex
51 views
unlisted
\documentclass[a4paper, 11pt]{report}1\usepackage[utf8]{inputenc}2\usepackage[T1]{fontenc}3\usepackage{amsmath, amssymb}4\usepackage{graphicx}5\usepackage{siunitx}6\usepackage{booktabs}7\usepackage{xcolor}8\usepackage[makestderr]{pythontex}910\definecolor{stable}{RGB}{46, 204, 113}11\definecolor{unstable}{RGB}{231, 76, 60}12\definecolor{saddle}{RGB}{241, 196, 15}13\definecolor{limit}{RGB}{155, 89, 182}1415\title{Ordinary Differential Equations:\\16Phase Portraits, Stability Analysis, and Limit Cycles}17\author{Department of Applied Mathematics\\Technical Report AM-2024-002}18\date{\today}1920\begin{document}21\maketitle2223\begin{abstract}24This report provides a comprehensive computational analysis of ordinary differential equations (ODEs). We examine first and second-order ODEs, construct phase portraits for autonomous systems, perform stability analysis of equilibrium points, and investigate limit cycles in nonlinear oscillators. The van der Pol oscillator is analyzed in detail to demonstrate relaxation oscillations and Hopf bifurcations.25\end{abstract}2627\tableofcontents2829\chapter{Introduction}3031Ordinary differential equations describe the evolution of systems in terms of derivatives with respect to a single variable. This report focuses on qualitative analysis through phase portraits and stability theory.3233\section{Classification of ODEs}34\begin{itemize}35\item \textbf{First-order}: $\frac{dy}{dt} = f(t, y)$36\item \textbf{Second-order}: $\frac{d^2y}{dt^2} = f(t, y, \frac{dy}{dt})$37\item \textbf{Linear}: Coefficients are functions of $t$ only38\item \textbf{Autonomous}: No explicit time dependence39\end{itemize}4041\chapter{First-Order ODEs}4243\section{Analytical Solutions}44Consider the first-order linear ODE:45\begin{equation}46\frac{dy}{dt} + p(t)y = q(t)47\end{equation}4849Solution via integrating factor $\mu(t) = e^{\int p(t)dt}$:50\begin{equation}51y(t) = \frac{1}{\mu(t)}\left[\int \mu(t)q(t)dt + C\right]52\end{equation}5354\begin{pycode}55import numpy as np56import matplotlib.pyplot as plt57from scipy.integrate import solve_ivp, odeint58from scipy.optimize import fsolve59plt.rc('text', usetex=True)60plt.rc('font', family='serif')6162np.random.seed(42)6364# First-order examples65def exponential_decay(t, y, k=1.0):66return -k * y6768def logistic_growth(t, y, r=1.0, K=10.0):69return r * y * (1 - y/K)7071# Solve multiple first-order ODEs72t_span = (0, 10)73t_eval = np.linspace(0, 10, 200)7475# Exponential decay with different initial conditions76fig, axes = plt.subplots(2, 2, figsize=(12, 10))7778ax = axes[0, 0]79for y0 in [1, 2, 3, 4, 5]:80sol = solve_ivp(exponential_decay, t_span, [y0], t_eval=t_eval, args=(0.5,))81ax.plot(sol.t, sol.y[0], label=f'$y_0 = {y0}$')82ax.set_xlabel('Time $t$')83ax.set_ylabel('$y(t)$')84ax.set_title('Exponential Decay: $dy/dt = -ky$')85ax.legend()86ax.grid(True, alpha=0.3)8788# Logistic growth89ax = axes[0, 1]90for y0 in [0.5, 2, 5, 8, 12, 15]:91sol = solve_ivp(logistic_growth, t_span, [y0], t_eval=t_eval)92ax.plot(sol.t, sol.y[0], label=f'$y_0 = {y0}$')93ax.axhline(10, color='red', linestyle='--', alpha=0.5, label='$K = 10$')94ax.set_xlabel('Time $t$')95ax.set_ylabel('$y(t)$')96ax.set_title('Logistic Growth: $dy/dt = ry(1-y/K)$')97ax.legend(loc='right')98ax.grid(True, alpha=0.3)99100# Direction field for dy/dt = y - y^2101ax = axes[1, 0]102y_range = np.linspace(-0.5, 1.5, 20)103t_range = np.linspace(0, 5, 20)104T, Y = np.meshgrid(t_range, y_range)105dY = Y - Y**2106dT = np.ones_like(dY)107norm = np.sqrt(dT**2 + dY**2)108ax.quiver(T, Y, dT/norm, dY/norm, alpha=0.5)109110# Plot solutions111for y0 in [0.1, 0.5, 0.9, 1.1, 1.5]:112sol = solve_ivp(lambda t, y: y - y**2, (0, 5), [y0], t_eval=np.linspace(0, 5, 100))113ax.plot(sol.t, sol.y[0], linewidth=2)114ax.set_xlabel('Time $t$')115ax.set_ylabel('$y(t)$')116ax.set_title('Direction Field: $dy/dt = y - y^2$')117ax.set_xlim(0, 5)118ax.set_ylim(-0.5, 1.5)119ax.grid(True, alpha=0.3)120121# Bernoulli equation122ax = axes[1, 1]123# dy/dt = y - y^3 (bistable)124for y0 in np.linspace(-1.5, 1.5, 15):125sol = solve_ivp(lambda t, y: y - y**3, (0, 5), [y0], t_eval=np.linspace(0, 5, 100))126ax.plot(sol.t, sol.y[0], 'b-', linewidth=0.8, alpha=0.6)127ax.axhline(1, color='green', linestyle='--', label='Stable: $y=1$')128ax.axhline(-1, color='green', linestyle='--', label='Stable: $y=-1$')129ax.axhline(0, color='red', linestyle='--', label='Unstable: $y=0$')130ax.set_xlabel('Time $t$')131ax.set_ylabel('$y(t)$')132ax.set_title('Bistable System: $dy/dt = y - y^3$')133ax.legend()134ax.grid(True, alpha=0.3)135136plt.tight_layout()137plt.savefig('first_order_odes.pdf', dpi=150, bbox_inches='tight')138plt.close()139\end{pycode}140141\begin{figure}[htbp]142\centering143\includegraphics[width=0.95\textwidth]{first_order_odes.pdf}144\caption{First-order ODE solutions: (a) exponential decay, (b) logistic growth, (c) direction field, (d) bistable system.}145\end{figure}146147\chapter{Second-Order Linear ODEs}148149\section{Harmonic Oscillator}150The damped harmonic oscillator:151\begin{equation}152m\frac{d^2x}{dt^2} + c\frac{dx}{dt} + kx = 0153\end{equation}154155Characteristic equation: $mr^2 + cr + k = 0$156157\begin{pycode}158# Second-order ODE as system of first-order159def harmonic_oscillator(t, state, m=1.0, c=0.0, k=1.0):160x, v = state161return [v, -(c*v + k*x)/m]162163fig, axes = plt.subplots(2, 3, figsize=(14, 8))164165# Undamped166t_span = (0, 20)167t_eval = np.linspace(0, 20, 500)168169ax = axes[0, 0]170sol = solve_ivp(harmonic_oscillator, t_span, [1, 0], t_eval=t_eval, args=(1, 0, 1))171ax.plot(sol.t, sol.y[0], 'b-')172ax.set_xlabel('Time $t$')173ax.set_ylabel('$x(t)$')174ax.set_title('Undamped ($\\zeta = 0$)')175ax.grid(True, alpha=0.3)176177# Underdamped178ax = axes[0, 1]179sol = solve_ivp(harmonic_oscillator, t_span, [1, 0], t_eval=t_eval, args=(1, 0.3, 1))180ax.plot(sol.t, sol.y[0], 'b-')181ax.set_xlabel('Time $t$')182ax.set_ylabel('$x(t)$')183ax.set_title('Underdamped ($\\zeta = 0.15$)')184ax.grid(True, alpha=0.3)185186# Critically damped187ax = axes[0, 2]188sol = solve_ivp(harmonic_oscillator, t_span, [1, 0], t_eval=t_eval, args=(1, 2, 1))189ax.plot(sol.t, sol.y[0], 'b-')190ax.set_xlabel('Time $t$')191ax.set_ylabel('$x(t)$')192ax.set_title('Critically Damped ($\\zeta = 1$)')193ax.grid(True, alpha=0.3)194195# Overdamped196ax = axes[1, 0]197sol = solve_ivp(harmonic_oscillator, t_span, [1, 0], t_eval=t_eval, args=(1, 4, 1))198ax.plot(sol.t, sol.y[0], 'b-')199ax.set_xlabel('Time $t$')200ax.set_ylabel('$x(t)$')201ax.set_title('Overdamped ($\\zeta = 2$)')202ax.grid(True, alpha=0.3)203204# Phase portraits205ax = axes[1, 1]206for zeta in [0, 0.15, 0.5, 1.0]:207c = 2 * zeta208sol = solve_ivp(harmonic_oscillator, (0, 30), [1, 0], t_eval=np.linspace(0, 30, 1000), args=(1, c, 1))209ax.plot(sol.y[0], sol.y[1], label=f'$\\zeta = {zeta}$')210ax.set_xlabel('$x$')211ax.set_ylabel('$\\dot{x}$')212ax.set_title('Phase Portraits')213ax.legend()214ax.grid(True, alpha=0.3)215216# Energy decay217ax = axes[1, 2]218for zeta in [0.1, 0.3, 0.5]:219c = 2 * zeta220sol = solve_ivp(harmonic_oscillator, t_span, [1, 0], t_eval=t_eval, args=(1, c, 1))221energy = 0.5 * sol.y[0]**2 + 0.5 * sol.y[1]**2222ax.plot(sol.t, energy, label=f'$\\zeta = {zeta}$')223ax.set_xlabel('Time $t$')224ax.set_ylabel('Energy $E$')225ax.set_title('Energy Decay')226ax.legend()227ax.grid(True, alpha=0.3)228ax.set_yscale('log')229230plt.tight_layout()231plt.savefig('second_order_odes.pdf', dpi=150, bbox_inches='tight')232plt.close()233\end{pycode}234235\begin{figure}[htbp]236\centering237\includegraphics[width=0.95\textwidth]{second_order_odes.pdf}238\caption{Second-order ODE: damped harmonic oscillator with various damping ratios.}239\end{figure}240241\chapter{Phase Portrait Analysis}242243\section{2D Autonomous Systems}244Consider the system:245\begin{align}246\frac{dx}{dt} &= f(x, y) \\247\frac{dy}{dt} &= g(x, y)248\end{align}249250Equilibrium points satisfy $f(x^*, y^*) = g(x^*, y^*) = 0$.251252\section{Linear Stability Analysis}253Near an equilibrium, linearize using the Jacobian:254\begin{equation}255J = \begin{pmatrix}256\frac{\partial f}{\partial x} & \frac{\partial f}{\partial y} \\257\frac{\partial g}{\partial x} & \frac{\partial g}{\partial y}258\end{pmatrix}259\end{equation}260261Eigenvalues $\lambda$ determine stability.262263\begin{pycode}264# 2D systems with different equilibrium types265def saddle_system(t, state):266x, y = state267return [x, -y]268269def stable_node(t, state):270x, y = state271return [-x, -2*y]272273def unstable_spiral(t, state):274x, y = state275return [0.1*x - y, x + 0.1*y]276277def center(t, state):278x, y = state279return [-y, x]280281fig, axes = plt.subplots(2, 2, figsize=(12, 12))282systems = [283(saddle_system, 'Saddle Point'),284(stable_node, 'Stable Node'),285(unstable_spiral, 'Unstable Spiral'),286(center, 'Center')287]288289for ax, (system, title) in zip(axes.flatten(), systems):290# Vector field291x_range = np.linspace(-3, 3, 20)292y_range = np.linspace(-3, 3, 20)293X, Y = np.meshgrid(x_range, y_range)294295state = np.array([X, Y])296derivatives = system(0, state)297U, V = derivatives[0], derivatives[1]298299norm = np.sqrt(U**2 + V**2)300norm[norm == 0] = 1301ax.quiver(X, Y, U/norm, V/norm, alpha=0.4)302303# Trajectories304angles = np.linspace(0, 2*np.pi, 8, endpoint=False)305for angle in angles:306x0 = 2.5 * np.cos(angle)307y0 = 2.5 * np.sin(angle)308try:309sol = solve_ivp(system, (0, 10), [x0, y0], t_eval=np.linspace(0, 10, 500), max_step=0.1)310ax.plot(sol.y[0], sol.y[1], 'b-', linewidth=1)311except:312pass313314ax.plot(0, 0, 'ro', markersize=8)315ax.set_xlabel('$x$')316ax.set_ylabel('$y$')317ax.set_title(title)318ax.set_xlim(-3, 3)319ax.set_ylim(-3, 3)320ax.grid(True, alpha=0.3)321322plt.tight_layout()323plt.savefig('phase_portraits.pdf', dpi=150, bbox_inches='tight')324plt.close()325\end{pycode}326327\begin{figure}[htbp]328\centering329\includegraphics[width=0.95\textwidth]{phase_portraits.pdf}330\caption{Phase portraits showing different equilibrium types based on eigenvalue classification.}331\end{figure}332333\chapter{Limit Cycles}334335\section{Van der Pol Oscillator}336The van der Pol oscillator exhibits self-sustained oscillations:337\begin{equation}338\frac{d^2x}{dt^2} - \mu(1 - x^2)\frac{dx}{dt} + x = 0339\end{equation}340341\begin{pycode}342def van_der_pol(t, state, mu=1.0):343x, v = state344return [v, mu*(1 - x**2)*v - x]345346fig, axes = plt.subplots(2, 3, figsize=(14, 8))347348# Time series for different mu349mu_values = [0.1, 1.0, 5.0]350t_span = (0, 50)351t_eval = np.linspace(0, 50, 2000)352353for i, mu in enumerate(mu_values):354ax = axes[0, i]355sol = solve_ivp(van_der_pol, t_span, [0.1, 0], t_eval=t_eval, args=(mu,))356ax.plot(sol.t, sol.y[0], 'b-', linewidth=0.8)357ax.set_xlabel('Time $t$')358ax.set_ylabel('$x(t)$')359ax.set_title(f'Van der Pol: $\\mu = {mu}$')360ax.grid(True, alpha=0.3)361362# Phase portraits with limit cycle363for i, mu in enumerate(mu_values):364ax = axes[1, i]365366# Multiple initial conditions367for x0, v0 in [(0.1, 0), (4, 0), (0, 4), (-3, -3)]:368sol = solve_ivp(van_der_pol, (0, 100), [x0, v0], t_eval=np.linspace(0, 100, 3000), args=(mu,))369ax.plot(sol.y[0], sol.y[1], linewidth=0.5, alpha=0.7)370371ax.set_xlabel('$x$')372ax.set_ylabel('$\\dot{x}$')373ax.set_title(f'Phase Portrait: $\\mu = {mu}$')374ax.grid(True, alpha=0.3)375376plt.tight_layout()377plt.savefig('van_der_pol.pdf', dpi=150, bbox_inches='tight')378plt.close()379380# Calculate limit cycle amplitude381mu_test = 1.0382sol_lc = solve_ivp(van_der_pol, (0, 100), [0.1, 0], t_eval=np.linspace(0, 100, 5000), args=(mu_test,))383limit_cycle_amp = np.max(np.abs(sol_lc.y[0][-1000:]))384\end{pycode}385386\begin{figure}[htbp]387\centering388\includegraphics[width=0.95\textwidth]{van_der_pol.pdf}389\caption{Van der Pol oscillator: time series (top) and phase portraits (bottom) showing limit cycles.}390\end{figure}391392The limit cycle amplitude for $\mu = 1$ is approximately \py{f'{limit_cycle_amp:.3f}'}.393394\chapter{Predator-Prey Dynamics}395396\section{Lotka-Volterra Equations}397\begin{align}398\frac{dx}{dt} &= \alpha x - \beta xy \\399\frac{dy}{dt} &= \delta xy - \gamma y400\end{align}401402\begin{pycode}403def lotka_volterra(t, state, alpha=1.0, beta=0.1, delta=0.075, gamma=1.5):404x, y = state405return [alpha*x - beta*x*y, delta*x*y - gamma*y]406407fig, axes = plt.subplots(1, 3, figsize=(14, 4))408409t_span = (0, 50)410t_eval = np.linspace(0, 50, 1000)411sol = solve_ivp(lotka_volterra, t_span, [10, 5], t_eval=t_eval)412413# Time series414ax = axes[0]415ax.plot(sol.t, sol.y[0], 'b-', label='Prey $x$')416ax.plot(sol.t, sol.y[1], 'r-', label='Predator $y$')417ax.set_xlabel('Time $t$')418ax.set_ylabel('Population')419ax.set_title('Population Dynamics')420ax.legend()421ax.grid(True, alpha=0.3)422423# Phase portrait424ax = axes[1]425for x0, y0 in [(10, 5), (20, 10), (30, 5), (5, 2)]:426sol_pp = solve_ivp(lotka_volterra, t_span, [x0, y0], t_eval=t_eval)427ax.plot(sol_pp.y[0], sol_pp.y[1], linewidth=1)428ax.set_xlabel('Prey $x$')429ax.set_ylabel('Predator $y$')430ax.set_title('Phase Portrait')431ax.grid(True, alpha=0.3)432433# Equilibrium434x_eq = 1.5 / 0.075435y_eq = 1.0 / 0.1436ax.plot(x_eq, y_eq, 'ko', markersize=8)437438# Conservation quantity439ax = axes[2]440H = 0.075*sol.y[0] + 0.1*sol.y[1] - 1.5*np.log(sol.y[0]) - 1.0*np.log(sol.y[1])441ax.plot(sol.t, H, 'g-')442ax.set_xlabel('Time $t$')443ax.set_ylabel('$H(x, y)$')444ax.set_title('Conserved Quantity')445ax.grid(True, alpha=0.3)446447plt.tight_layout()448plt.savefig('lotka_volterra.pdf', dpi=150, bbox_inches='tight')449plt.close()450451period_peaks = []452for i in range(1, len(sol.y[0])-1):453if sol.y[0][i] > sol.y[0][i-1] and sol.y[0][i] > sol.y[0][i+1]:454period_peaks.append(sol.t[i])455if len(period_peaks) > 1:456lv_period = np.mean(np.diff(period_peaks))457else:458lv_period = 0459\end{pycode}460461\begin{figure}[htbp]462\centering463\includegraphics[width=0.95\textwidth]{lotka_volterra.pdf}464\caption{Lotka-Volterra predator-prey model: populations oscillate with period $T \approx \py{f'{lv_period:.2f}'}$.}465\end{figure}466467\chapter{Numerical Results}468469\begin{pycode}470# Summary of eigenvalue classifications471classifications = [472('Real, opposite signs', 'Saddle', 'Unstable'),473('Real, both negative', 'Stable node', 'Stable'),474('Real, both positive', 'Unstable node', 'Unstable'),475('Complex, $\\text{Re} < 0$', 'Stable spiral', 'Stable'),476('Complex, $\\text{Re} > 0$', 'Unstable spiral', 'Unstable'),477('Pure imaginary', 'Center', 'Neutral'),478]479\end{pycode}480481\begin{table}[htbp]482\centering483\caption{Equilibrium classification by eigenvalues}484\begin{tabular}{@{}lll@{}}485\toprule486Eigenvalues & Type & Stability \\487\midrule488\py{classifications[0][0]} & \py{classifications[0][1]} & \py{classifications[0][2]} \\489\py{classifications[1][0]} & \py{classifications[1][1]} & \py{classifications[1][2]} \\490\py{classifications[2][0]} & \py{classifications[2][1]} & \py{classifications[2][2]} \\491\py{classifications[3][0]} & \py{classifications[3][1]} & \py{classifications[3][2]} \\492\py{classifications[4][0]} & \py{classifications[4][1]} & \py{classifications[4][2]} \\493\py{classifications[5][0]} & \py{classifications[5][1]} & \py{classifications[5][2]} \\494\bottomrule495\end{tabular}496\end{table}497498\chapter{Conclusions}499500\begin{enumerate}501\item First-order ODEs: direction fields reveal solution behavior502\item Second-order ODEs: damping ratio determines oscillation character503\item Phase portraits: eigenvalues classify equilibrium types504\item Limit cycles: nonlinear systems can have isolated periodic orbits505\item Predator-prey: conservative systems show closed orbits506\end{enumerate}507508\end{document}509510511