<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Numerical Methods on 四方喫茶舘</title><link>http://blog.cedard.top/categories/numerical-methods/</link><description>Recent content in Numerical Methods on 四方喫茶舘</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 01 Oct 2026 21:24:34 -0700</lastBuildDate><atom:link href="http://blog.cedard.top/categories/numerical-methods/index.xml" rel="self" type="application/rss+xml"/><item><title>Numerical Methods: Periodic Steady State Simulation</title><link>http://blog.cedard.top/post/profession/numerical_methods/bvp/</link><pubDate>Thu, 01 Oct 2026 21:24:34 -0700</pubDate><guid>http://blog.cedard.top/post/profession/numerical_methods/bvp/</guid><description>&lt;p&gt;We explained some commonly used numerical methods for solving IVP problems in transient simulations in the last post. However, there are sometimes situations where IVP problem doesn&amp;rsquo;t characterize the system&amp;rsquo;s behavior adequately.&lt;/p&gt;&#10;&lt;p&gt;One such situation is when we are interested in the system&amp;rsquo;s periodic steady-state behavior rather than its transient response. In these cases, we are more interested in what&amp;rsquo;s called the Boundary Value Problem (BVP), where the solution is sought over a period with specified boundary conditions rather than initial conditions.&lt;/p&gt;&#10;&lt;h2 id="boundary-value-problem-bvp"&gt;&lt;a href="#boundary-value-problem-bvp" class="header-anchor"&gt;&lt;/a&gt;Boundary Value Problem (BVP)&#10;&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s say you have a switched-capacitor filter, or simply our RC circuit from last time, driven by a 1 GHz clock, and you want to know its steady-state output ripple. With a transient simulation, you would have to start from some initial condition and wait for hundreds of cycles until all the start-up transients die out, and then throw most of the waveform away. What we really want is the waveform of one period &lt;em&gt;after&lt;/em&gt; the circuit has settled, and the condition that tells us we&amp;rsquo;ve settled is that the state at the end of the period comes back to where it started. You have just created a boundary value problem (BVP): instead of specifying the state at one point in time and marching forward, we specify conditions that the solution must satisfy at both ends of an interval.&lt;/p&gt;&#10;&lt;p&gt;Mathematically, a two-point BVP can be expressed as:&lt;/p&gt;&#10;$$&#10;\frac{d\mathbf{x}(t)}{dt} = \mathbf{f}(\mathbf{x}(t), t), \quad f(t_0) = A, \quad f(t_0 + T) = B&#10;$$&lt;p&gt;where $\mathbf{x}(t)$ and $\mathbf{f}(\mathbf{x}(t), t)$ are the same as in the IVP, $T$ is the length of the interval. In the special case of periodic steady-state analysis, the boundary conditions are simply:&lt;/p&gt;&#10;$$&#10;f(t_0) = f(t_0 + T)&#10;$$&lt;p&gt;meaning that the solution repeats itself after one period.&lt;/p&gt;&#10;&lt;p&gt;BVPs are generally more challenging to solve than IVPs because the solution must satisfy conditions at multiple points rather than just an initial condition. We will see how we are going to convert a BVP into an IVP, and use whatever we have learned about IVPs to tackle the problem. However, before that we must understand what are the scenarios that BVP might not have a solution.&lt;/p&gt;&#10;&lt;h2 id="preventing-the-butterfly-effect"&gt;&lt;a href="#preventing-the-butterfly-effect" class="header-anchor"&gt;&lt;/a&gt;Preventing the Butterfly Effect&#10;&lt;/h2&gt;&lt;p&gt;You probably have heard of the &amp;ldquo;butterfly effect,&amp;rdquo; which refers to the sensitive dependence on initial conditions in chaotic systems. Sometimes this can create problems when we would like to find the exact solution.&lt;/p&gt;&#10;&lt;h3 id="the-lorenz-attractor-example"&gt;&lt;a href="#the-lorenz-attractor-example" class="header-anchor"&gt;&lt;/a&gt;The Lorenz Attractor Example&#10;&lt;/h3&gt;&lt;p&gt;One famous butterfly effect example is the Lorenz attractor.&lt;/p&gt;&#10;&lt;p&gt;In 1963, Edward Lorenz simplified a model of atmospheric convection down to just three state variables, and arrived at the following system of ODEs:&lt;/p&gt;&#10;$$&#10;\begin{align*}&#10;\begin{cases}&#10;\dot{x} &amp;= \sigma (y - x) \\&#10;\dot{y} &amp;= x (\rho - z) - y \\&#10;\dot{z} &amp;= xy - \beta z&#10;\end{cases}&#10;\end{align*}&#10;$$&lt;p&gt;where $\sigma$, $\rho$ and $\beta$ are positive constants. Lorenz used $\sigma = 10$, $\rho = 28$ and $\beta = 8/3$, which are still the standard values today. In our notation, the state vector is $\mathbf{x} = (x, y, z)^T$, and $\mathbf{f}(\mathbf{x})$ is the right-hand side above.&lt;/p&gt;&#10;&lt;p&gt;The system looks innocent: it&amp;rsquo;s only three equations, the only nonlinear terms are the products $xz$ and $xy$, and there is no randomness anywhere. Yet for these parameter values, every trajectory gets pulled onto a bounded, butterfly-shaped set, called the &lt;strong&gt;attractor&lt;/strong&gt;, and keeps looping around its two &amp;ldquo;wings&amp;rdquo; forever, switching between them in a pattern that never repeats.&lt;/p&gt;&#10;&lt;p&gt;The butterfly effect shows up when we start two simulations from almost the same point. Let&amp;rsquo;s integrate the system with the RK4 method from the last post, with $h = 0.005$, from $\mathbf{x}_0 = (1, 1, 1)$ and from a second initial state that differs only by $10^{-8}$ in $x$:&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Lorenz attractor, two trajectories starting 1e-8 apart, and their separation growing exponentially" class="gallery-image" data-flex-basis="778px" data-flex-grow="324" height="900" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/bvp/lorenz-attractor.png" width="2921"&gt;&lt;/p&gt;&#10;&lt;p&gt;For the first 25 time units, the two waveforms are indistinguishable. Then they suddenly go their separate ways, and after $t \approx 33$ they have nothing to do with each other. On the right, we see why: after an initial settling phase, the separation $\delta(t)$ between the two trajectories grows exponentially,&lt;/p&gt;&#10;$$&#10;\delta(t) \approx \delta_0 \, e^{\lambda t}&#10;$$&lt;p&gt;until it saturates at the size of the attractor itself. The growth rate $\lambda$ is called the &lt;strong&gt;largest Lyapunov exponent&lt;/strong&gt;. Fitting the growth phase gives a slope of 0.95, and averaging over 1000 time units gives $\lambda \approx 0.90$, i.e. any initial error grows by about $e^{0.9} \approx 2.5\times$ per unit time. A $10^{-8}$ difference, far below anything we could ever measure, reaches order one at $t \approx 28$: about 13 time units of settling, then roughly 20 of exponential growth.&lt;/p&gt;&#10;&lt;p&gt;Bear in mind that this is not a numerical artifact: a smaller time step only makes RK4 follow the &lt;em&gt;exact&lt;/em&gt; trajectory more closely, and the exact trajectories themselves diverge like this. This is a property of the system, not of the solver.&lt;/p&gt;&#10;&lt;h3 id="continuous-dependence-on-initial-conditions"&gt;&lt;a href="#continuous-dependence-on-initial-conditions" class="header-anchor"&gt;&lt;/a&gt;Continuous Dependence on Initial Conditions&#10;&lt;/h3&gt;&lt;p&gt;So, is there anything that prevents the butterfly effect? To answer this, let&amp;rsquo;s go back to the mapping $\psi$ from the IVP, and be a bit more precise about where it lands. For a fixed final time $T$, $\psi$ maps an initial state to a whole trajectory on $[t_0, T]$:&lt;/p&gt;&#10;$$&#10;\psi: \mathbb{R}^n \to \mathcal{C}_n[t_0, T], \quad \psi(\mathbf{x}_0)(t) := \mathbf{x}(t, t_0, \mathbf{x}_0)&#10;$$&lt;p&gt;where $\mathcal{C}_n[t_0, T]$ is the space of continuous functions from $[t_0, T]$ to $\mathbb{R}^n$. We say $\psi$ is &lt;strong&gt;continuous&lt;/strong&gt; at $\mathbf{x}_0$ if:&lt;/p&gt;&#10;$$&#10;\forall \varepsilon &gt; 0, \ \exists \delta &gt; 0 \ \text{ s.t. } \ \|\mathbf{x}_0 - \mathbf{z}_0\| &lt; \delta \Rightarrow \|\psi(\mathbf{x}_0) - \psi(\mathbf{z}_0)\|_\infty &lt; \varepsilon&#10;$$&lt;p&gt;On the left, \( \|\cdot\| \) can be any norm on $\mathbb{R}^n$. On the right, the norm is the infinity norm over the whole trajectory:&lt;/p&gt;&#10;$$&#10;\|\psi(\mathbf{x}_0) - \psi(\mathbf{z}_0)\|_\infty = \sup_{t_0 \le t \le T} \|\mathbf{x}(t) - \mathbf{z}(t)\|&#10;$$&lt;p&gt;Or, in terms of balls:&lt;/p&gt;&#10;$$&#10;\mathbf{z}_0 \in B_\delta(\mathbf{x}_0) \Rightarrow \psi(\mathbf{z}_0) \in B_\varepsilon(\psi(\mathbf{x}_0))&#10;$$&lt;p&gt;In plain words: if we start close enough, the worst-case difference between the two trajectories over the entire interval stays bounded by $\varepsilon$. This is exactly what &amp;ldquo;preventing the butterfly effect&amp;rdquo; means.&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;&lt;strong&gt;Theorem (Continuous Dependence on Initial Conditions)&lt;/strong&gt;: Consider $\dot{\mathbf{x}} = \mathbf{f}(t, \mathbf{x})$, $\mathbf{x} \in \mathbb{R}^n$, where $\mathbf{f}(t, \mathbf{x})$ satisfies the conditions of the global existence and uniqueness theorem. Fix $T \in [t_0, +\infty)$, and suppose $\mathbf{x}(\cdot)$ and $\mathbf{z}(\cdot)$ are two solutions satisfying&lt;/p&gt;&#10;$$ \begin{align*} \dot{\mathbf{x}} &amp;= \mathbf{f}(t, \mathbf{x}), \quad \mathbf{x}(t_0) = \mathbf{x}_0 \\ \dot{\mathbf{z}} &amp;= \mathbf{f}(t, \mathbf{z}), \quad \mathbf{z}(t_0) = \mathbf{z}_0 \end{align*} $$&lt;p&gt;Then $\forall \varepsilon &amp;gt; 0, \ \exists \delta &amp;gt; 0$ such that&lt;/p&gt;&#10;$$ \|\mathbf{x}_0 - \mathbf{z}_0\| &lt; \delta \Rightarrow \sup_{t_0 \le t \le T} \|\mathbf{x}(t) - \mathbf{z}(t)\| &lt; \varepsilon $$&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;Wait, doesn&amp;rsquo;t the Lorenz attractor contradict this? Not really. The key word is &lt;strong&gt;fix&lt;/strong&gt; $T$: the theorem promises a $\delta$ for every &lt;em&gt;finite&lt;/em&gt; interval, but says nothing about how small that $\delta$ has to be. For the Lorenz system, the separation grows like $e^{\lambda t}$, so keeping the trajectories within $\varepsilon$ up to time $T$ requires roughly \( \delta \sim \varepsilon \, e^{-\lambda (T - t_0)} \). Continuity still holds, but $\delta$ shrinks exponentially as the interval gets longer.&lt;/p&gt;&#10;&lt;p&gt;Here is why this matters for BVPs. Recall that the periodic steady state is a root of $F(\mathbf{x}_0) = \psi(\mathbf{x}_0)(t_0 + T) - \mathbf{x}_0$. To solve it with Newton&amp;rsquo;s method, we need the sensitivity of the final state with respect to the initial state, $\partial \psi / \partial \mathbf{x}_0$, which grows like $e^{\lambda T}$ for a chaotic system. Over a long interval, a tiny change in $\mathbf{x}_0$ changes $F$ by an enormous amount, and the problem becomes hopelessly ill-conditioned.&lt;/p&gt;&#10;&lt;h3 id="continuous-dependence-on-initial-conditions-1"&gt;&lt;a href="#continuous-dependence-on-initial-conditions-1" class="header-anchor"&gt;&lt;/a&gt;Continuous Dependence on Initial Conditions&#10;&lt;/h3&gt;&lt;p&gt;We defined function $\psi$ in the last post. As a reminder:&lt;/p&gt;&#10;$$&#10;\psi(\mathbf{x}_0)(t) = \mathbf{x}(t)&#10;$$&lt;p&gt;where $\mathbf{x}(t)$ is the solution of the initial value problem&lt;/p&gt;&#10;$$&#10;\begin{cases}&#10;\dot{\mathbf{x}} = \mathbf{f}(\mathbf{x}), \\&#10;\mathbf{x}(t_0) = \mathbf{x}_0.&#10;\end{cases}&#10;$$&lt;p&gt;To prevent small changes in the initial state from causing large deviations in the solution, we require the system to have &lt;strong&gt;continuous dependence on initial conditions&lt;/strong&gt;. Formally, this means that for any $\epsilon &amp;gt; 0$, there exists a $\delta &amp;gt; 0$ such that if \( \|\mathbf{x}_0 - \mathbf{y}_0\| &lt; \delta \), then&#10;&lt;/p&gt;&#10;$$&#10;\|\psi(\mathbf{x}_0)(t) - \psi(\mathbf{y}_0)(t)\| &lt; \epsilon \quad \text{for all } t \in [t_0, t_0 + T].&#10;$$&lt;h2 id="shooting-method"&gt;&lt;a href="#shooting-method" class="header-anchor"&gt;&lt;/a&gt;Shooting Method&#10;&lt;/h2&gt;&lt;p&gt;Now we are ready to solve a BVP. Let&amp;rsquo;s use the following problem as our running example:&lt;/p&gt;&#10;$$&#10;\ddot{x} + x = 0, \qquad x(0) = 1, \qquad x(\pi/2) = 2&#10;$$&lt;p&gt;This one has a closed-form solution, so we can check our answers. Every solution of the ODE has the form $x(t) = A\cos t + B\sin t$. From $x(0) = 1$ we get $A = 1$, and from $x(\pi/2) = 2$ we get $B = 2$:&lt;/p&gt;&#10;$$&#10;x(t) = \cos t + 2\sin t, \qquad \dot{x}(0) = 2&#10;$$&lt;blockquote class="alert alert-note"&gt;&#10; &lt;div class="alert-header"&gt;&#10; &lt;span class="alert-icon"&gt;📝&lt;/span&gt;&#10; &lt;span class="alert-title"&gt;Note&lt;/span&gt;&#10; &lt;/div&gt;&#10; &lt;div class="alert-body"&gt;&#10; &lt;p&gt;A second-order ODE needs two conditions, but not every choice works. Don&amp;rsquo;t use $x(\pi)$ as the second condition: every solution with $x(0) = 1$ has $x(\pi) = -1$, so that condition gives either no solution or infinitely many.&lt;/p&gt;&#10; &lt;/div&gt;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="idea"&gt;&lt;a href="#idea" class="header-anchor"&gt;&lt;/a&gt;Idea&#10;&lt;/h3&gt;&lt;p&gt;Remember that we promised to convert a BVP into an IVP. The shooting method does exactly that. If we knew the initial slope $s = \dot{x}(0)$, this would just be an IVP, and we know how to solve those. So let&amp;rsquo;s &lt;em&gt;guess&lt;/em&gt; $s$, integrate forward, and see by how much we miss the far boundary:&lt;/p&gt;&#10;$$&#10;F(s) = x(\pi/2; s) - 2&#10;$$&lt;p&gt;where $x(\pi/2; s)$ is the solution at $t = \pi/2$ when we start with slope $s$. The BVP is now a root-finding problem: find $s$ such that $F(s) = 0$. Just like aiming a cannon: fire, see where the ball lands, adjust the angle, fire again.&lt;/p&gt;&#10;&lt;p&gt;For periodic steady state, the idea is the same, except that the unknown is the whole initial state, and the miss is measured against the initial state itself:&lt;/p&gt;&#10;$$&#10;F(\mathbf{x}_0) = \psi(\mathbf{x}_0)(t_0 + T) - \mathbf{x}_0 = 0&#10;$$&lt;h3 id="steps"&gt;&lt;a href="#steps" class="header-anchor"&gt;&lt;/a&gt;Steps&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Rewrite the problem as a first-order system with $y_1 = x$ and $y_2 = \dot{x}$:&lt;/p&gt;&#10;$$&#10; \dot{y}_1 = y_2, \qquad \dot{y}_2 = -y_1, \qquad \mathbf{y}(0) = [1, s]^T&#10; $$&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Guess $s_0 = 0$. Integrating gives $x(\pi/2) = 0$, so $F_0 = -2$.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Guess $s_1 = 1$. Integrating gives $x(\pi/2) = 1$, so $F_1 = -1$.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Secant update:&lt;/p&gt;&#10;$$&#10; s_2 = s_1 - F_1 \frac{s_1 - s_0}{F_1 - F_0} = 1 - (-1)\frac{1}{1} = 2&#10; $$&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;$F(2) \approx 0$, so we have converged to $\dot{x}(0) = 2$.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Because the ODE is linear, $F(s)$ is linear in $s$, and the secant method converges in &lt;strong&gt;one step&lt;/strong&gt;. A nonlinear ODE would take a few iterations.&lt;/p&gt;&#10;&lt;h3 id="code"&gt;&lt;a href="#code" class="header-anchor"&gt;&lt;/a&gt;Code&#10;&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s reuse the RK4 integrator from the last post:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;X0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt; &lt;span class="c1"&gt;# x(0) = 1, x(T) = 2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="c1"&gt;# y = [x, x&amp;#39;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Integrate with x(0) = 1, x&amp;#39;(0) = s; return the miss at the far boundary.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;integrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rk4_step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;X0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;XT&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;F1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;s0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;F0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# secant step&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;F1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;F1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running it, the three guesses give:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Guess&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;$s$&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;$F(s)$&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;−2.000&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;−1.000&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.999999999951&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$-1.8 \times 10^{-15}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The remaining $5 \times 10^{-11}$ error in $s$ comes from RK4&amp;rsquo;s truncation error with $h = T/200$, not from the shooting itself.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Shooting method: three guesses of the initial slope, with the third landing on the target boundary value" class="gallery-image" data-flex-basis="403px" data-flex-grow="167" height="823" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/bvp/shooting.png" width="1382"&gt;&lt;/p&gt;&#10;&lt;p&gt;Bear in mind that for a nonlinear system with many states, we&amp;rsquo;d use Newton&amp;rsquo;s method instead of the secant method, which requires the Jacobian $\partial \psi / \partial \mathbf{x}_0$, the sensitivity of the final state to the initial state. This is exactly where the butterfly effect bites: for a chaotic (or just very sensitive) system, this Jacobian grows exponentially with the interval length.&lt;/p&gt;&#10;&lt;h2 id="harmonic-balance"&gt;&lt;a href="#harmonic-balance" class="header-anchor"&gt;&lt;/a&gt;Harmonic Balance&#10;&lt;/h2&gt;&lt;h3 id="idea-1"&gt;&lt;a href="#idea-1" class="header-anchor"&gt;&lt;/a&gt;Idea&#10;&lt;/h3&gt;&lt;p&gt;The shooting method works in the time domain. Harmonic balance goes the other way: assume the solution is a truncated Fourier series, substitute it into the ODE, and require the coefficient of &lt;strong&gt;each harmonic&lt;/strong&gt; in the residual to vanish. This turns the ODE into a set of algebraic equations for the Fourier coefficients, plus the frequency $\omega$ if it is unknown.&lt;/p&gt;&#10;&lt;h3 id="steps-1"&gt;&lt;a href="#steps-1" class="header-anchor"&gt;&lt;/a&gt;Steps&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Ansatz&lt;/strong&gt; with $N$ harmonics:&lt;/p&gt;&#10;$$&#10; x(t) = a_0 + \sum_{k=1}^{N} \left[ a_k \cos(k\omega t) + b_k \sin(k\omega t) \right]&#10; $$&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Substitute.&lt;/strong&gt; Since $\ddot{x} = -\sum_k (k\omega)^2 [a_k\cos(k\omega t) + b_k\sin(k\omega t)]$, we have&lt;/p&gt;&#10;$$&#10; \ddot{x} + x = a_0 + \sum_{k=1}^{N} (1 - k^2\omega^2)\left[a_k\cos(k\omega t) + b_k\sin(k\omega t)\right]&#10; $$&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Balance each harmonic:&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;DC: $a_0 = 0$&lt;/li&gt;&#10;&lt;li&gt;$k$-th harmonic: $(1 - k^2\omega^2) a_k = 0$ and $(1 - k^2\omega^2) b_k = 0$&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A nonzero solution needs $k\omega = 1$ for some $k$. Taking $k = 1$ gives &lt;strong&gt;$\omega = 1$&lt;/strong&gt;, and every other harmonic vanishes:&lt;/p&gt;&#10;$$&#10; x(t) = A\cos t + B\sin t&#10; $$&lt;p&gt;Note that the balance fixes the frequency, but not the amplitudes, because the ODE is linear and unforced.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Apply the boundary conditions:&lt;/strong&gt; $x(0) = A = 1$ and $x(\pi/2) = B = 2$, so&lt;/p&gt;&#10;$$&#10; x(t) = \cos t + 2\sin t&#10; $$&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The result is exact because the true solution happens to be a single harmonic. In a nonlinear ODE, e.g. the Duffing equation $\ddot{x} + x + \varepsilon x^3 = 0$, the harmonics couple to each other ($\cos^3 t$ produces $\cos 3t$), so the balance equations become nonlinear, and $\omega$ depends on the amplitude.&lt;/p&gt;&#10;&lt;h3 id="code-1"&gt;&lt;a href="#code-1" class="header-anchor"&gt;&lt;/a&gt;Code&#10;&lt;/h3&gt;&lt;p&gt;Numerically, we stack the unknowns $[\omega, a_0, \ldots, a_N, b_1, \ldots, b_N]$ into one vector, and solve the harmonic balance equations together with the two boundary conditions. There are more equations than unknowns, so we solve it in the least-squares sense with Gauss-Newton:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="c1"&gt;# number of harmonics&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# p = [w, a0..aN, b1..bN]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;x_hb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;residual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;unpack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;hb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r_&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]]&lt;/span&gt; &lt;span class="c1"&gt;# each harmonic of x&amp;#39;&amp;#39; + x&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;bc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x_hb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;X0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# boundary conditions&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x_hb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;XT&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;hb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bc&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;gauss_newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Least-squares Newton: p &amp;lt;- p - lstsq(J, r(p)), finite-difference Jacobian.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;23&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;24&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-7&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;25&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;26&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;step&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstsq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rcond&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;27&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;28&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;29&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;30&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;31&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;32&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;r_&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ones&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="c1"&gt;# rough initial guess&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;33&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;iters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gauss_newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;residual&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting from a rough guess of $\omega = 0.9$ and all coefficients at 0.5, it converges in 8 iterations to $\omega = 1$, $a = [0, 1, 0, 0]$ and $b = [0, 2, 0, 0]$. The 2nd and 3rd harmonics come out as zero, and the reconstructed waveform matches $\cos t + 2\sin t$ to machine precision.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Harmonic balance: the waveform rebuilt from the solved coefficients matches the exact solution, and only the first harmonic is nonzero" class="gallery-image" data-flex-basis="694px" data-flex-grow="289" height="823" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/bvp/harmonic-balance.png" width="2382"&gt;&lt;/p&gt;&#10;&lt;h2 id="from-bvp-to-periodic-steady-state-two-practical-scenarios"&gt;&lt;a href="#from-bvp-to-periodic-steady-state-two-practical-scenarios" class="header-anchor"&gt;&lt;/a&gt;From BVP to Periodic Steady State: Two Practical Scenarios&#10;&lt;/h2&gt;&lt;p&gt;So far, our example came with two nice boundary conditions handed to us. Real PSS problems are not that kind. When we ask the simulator for the periodic steady state of a circuit, we usually don&amp;rsquo;t know the state at the start of the period, and for an oscillator we don&amp;rsquo;t even know how long the period is. Let&amp;rsquo;s look at how each of these situations is turned into something the shooting method and harmonic balance can solve.&lt;/p&gt;&#10;&lt;h3 id="scenario-1-driven-circuit-unknown-initial-state"&gt;&lt;a href="#scenario-1-driven-circuit-unknown-initial-state" class="header-anchor"&gt;&lt;/a&gt;Scenario 1: Driven Circuit, Unknown Initial State&#10;&lt;/h3&gt;&lt;p&gt;This is the typical case: a circuit driven by a periodic source with a &lt;strong&gt;known&lt;/strong&gt; period $T$, like a clock or an LO. We want the steady state, but we have no idea what the state looks like at the start of a period; that&amp;rsquo;s exactly what we&amp;rsquo;re trying to find.&lt;/p&gt;&#10;&lt;p&gt;The trick is that periodicity itself replaces the missing initial condition:&lt;/p&gt;&#10;$$&#10;\mathbf{x}(t_0 + T) = \mathbf{x}(t_0)&#10;$$&lt;p&gt;That&amp;rsquo;s $n$ equations for the $n$ unknown components of $\mathbf{x}_0 = \mathbf{x}(t_0)$, which is a well-posed problem.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Shooting:&lt;/strong&gt; we solve $F(\mathbf{x}_0) = \psi(\mathbf{x}_0)(t_0 + T) - \mathbf{x}_0 = 0$ with Newton&amp;rsquo;s method. Each Newton iteration integrates one period of transient, and the Jacobian is&lt;/p&gt;&#10;$$&#10; \frac{\partial F}{\partial \mathbf{x}_0} = \frac{\partial \psi(\mathbf{x}_0)(t_0 + T)}{\partial \mathbf{x}_0} - I&#10; $$&lt;p&gt;The first term is the sensitivity of the end-of-period state to the start-of-period state, also known as the &lt;strong&gt;monodromy matrix&lt;/strong&gt;. For a linear circuit, $F$ is linear, and Newton converges in one step.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Harmonic balance:&lt;/strong&gt; here periodicity comes for free. Every term of a Fourier series with fundamental $\omega = 2\pi/T$ is already $T$-periodic, so there is no initial condition to find at all; we only solve for the coefficients. For a linear circuit, the harmonics don&amp;rsquo;t even talk to each other, and harmonic balance reduces to an AC analysis at each harmonic: $V_k = Z(jk\omega) I_k$.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;Example.&lt;/strong&gt; Let&amp;rsquo;s go back to the RC circuit from the last post (\( R = 10\,\text{k}\Omega \), \( C = 1\,\text{pF} \), $\tau = 10$ ns), and drive it with a square-wave current that switches between 0 and 100 µA at 1 GHz. This is the &amp;ldquo;RC driven by a clock&amp;rdquo; from the beginning of this post. The capacitor charges during the first half of the period and discharges during the second half, and in steady state the two exponential segments must connect. Solving for the end points gives the closed-form answer:&lt;/p&gt;&#10;$$&#10;v_{min} = IR\frac{e^{-a}}{1 + e^{-a}}, \qquad v_{max} = IR\frac{1}{1 + e^{-a}}, \qquad a = \frac{T}{2\tau}&#10;$$&lt;p&gt;i.e. $v_{min} = 487.503$ mV, $v_{max} = 512.497$ mV, a ripple of 24.99 mV around 500 mV.&lt;/p&gt;&#10;&lt;p&gt;The numbers:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Plain transient:&lt;/strong&gt; starting from 0 V, it takes &lt;strong&gt;131 periods&lt;/strong&gt; before the waveform settles within 1 µV of the steady state, since the settling is governed by $\tau = 10T$.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Shooting:&lt;/strong&gt; Newton converges after integrating only &lt;strong&gt;6 periods&lt;/strong&gt; in total (including the extra integrations for the finite-difference Jacobian), and lands on the exact $v_{min}$ to within $2 \times 10^{-15}$ V.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Harmonic balance:&lt;/strong&gt; the square wave has sharp edges, so the waveform has a corner at every edge, and the Fourier coefficients decay slowly. The worst-case error over the period is 2.37 mV with 1 harmonic, 0.84 mV with 5, 0.195 mV with 25, and still 0.050 mV with 101 harmonics.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;img alt="Driven RC: the square-wave source current, the triangle-shaped capacitor voltage in steady state, and a zoom on the corner where harmonic balance rounds off the edge" class="gallery-image" data-flex-basis="469px" data-flex-grow="195" height="1223" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/bvp/pss-driven.png" width="2390"&gt;&lt;/p&gt;&#10;&lt;p&gt;Note that the capacitor voltage itself is not a square wave: with $\tau = 10T$, the RC integrates the square-wave current into a triangle with 25 mV of ripple. The trouble for harmonic balance is the sharp corner at every source edge, which the zoom on the right shows: with 1 harmonic the corner is badly rounded off, with 5 it&amp;rsquo;s better, and only around 25 harmonics does it follow the exact solution closely.&lt;/p&gt;&#10;&lt;blockquote class="alert alert-warning"&gt;&#10; &lt;div class="alert-header"&gt;&#10; &lt;span class="alert-icon"&gt;⚠️&lt;/span&gt;&#10; &lt;span class="alert-title"&gt;Warning&lt;/span&gt;&#10; &lt;/div&gt;&#10; &lt;div class="alert-body"&gt;&#10; &lt;p&gt;The source edge at $T/2$ is a discontinuity, and an RK4 step that straddles it applies the wrong current for part of the step. In my first attempt, this alone left an error of about 0.5 mV in the steady state. The fix is what every circuit simulator does with &lt;strong&gt;breakpoints&lt;/strong&gt;: end a time step exactly at the edge, and restart the integration from there.&lt;/p&gt;&#10; &lt;/div&gt;&#10; &lt;/blockquote&gt;&#10;&lt;h3 id="scenario-2-autonomous-circuit-unknown-period"&gt;&lt;a href="#scenario-2-autonomous-circuit-unknown-period" class="header-anchor"&gt;&lt;/a&gt;Scenario 2: Autonomous Circuit, Unknown Period&#10;&lt;/h3&gt;&lt;p&gt;Now consider an oscillator: a ring oscillator, an LC VCO, or anything without a periodic input. Two new problems show up:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;The period $T$ is unknown.&lt;/strong&gt; It&amp;rsquo;s set by the circuit itself, so $T$ becomes an extra unknown, and we now have $n + 1$ unknowns but only $n$ periodicity equations.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The phase is arbitrary.&lt;/strong&gt; Since nothing drives the circuit, any time-shifted copy of a periodic solution is also a periodic solution, so there are infinitely many of them.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Both problems are solved together by adding one &lt;strong&gt;phase condition&lt;/strong&gt;, which pins down where the period starts. That gives $n + 1$ equations for $n + 1$ unknowns. On top of that, the DC operating point $\mathbf{x}(t) = \mathbf{x}_{DC}$ trivially satisfies periodicity for &lt;em&gt;any&lt;/em&gt; $T$. This is why oscillator PSS needs a good initial guess, and why simulators typically run a short transient first to get the oscillation going.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Shooting:&lt;/strong&gt; the unknowns are $(\mathbf{x}_0, T)$. A convenient phase condition is to start the period at a peak of one state variable, i.e. $\dot{x}_j(t_0) = 0$.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Harmonic balance:&lt;/strong&gt; the unknowns are the Fourier coefficients plus $\omega$. A convenient phase condition is to set the sine coefficient of the fundamental of one variable to zero, $b_1 = 0$, which lines up its fundamental with a cosine.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;strong&gt;Example.&lt;/strong&gt; The classic model of an oscillator is the &lt;strong&gt;Van der Pol oscillator&lt;/strong&gt;:&lt;/p&gt;&#10;$$&#10;\ddot{x} - \mu (1 - x^2) \dot{x} + x = 0&#10;$$&lt;p&gt;which is (in normalized units) an LC tank in parallel with a nonlinear conductance $-g_1 v + g_3 v^3$, a crude model of a cross-coupled pair: negative resistance at small amplitude to start the oscillation, and compression at large amplitude to limit it. We take $\mu = 1$, where there is no closed-form solution.&lt;/p&gt;&#10;&lt;p&gt;For shooting, the phase condition $\dot{x}(0) = 0$ means $x(0) = A$ is the amplitude, and the unknowns are just $[A, T]$:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shoot_vdp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pi&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Unknowns [A, T]; phase condition x&amp;#39;(0) = 0, i.e. start the period at a peak x(0) = A.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;F&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;y_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;integrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rk4_step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f_vdp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;N_STEPS&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y_end&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;F&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;array&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;A0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T0&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Starting from a guess of $A = 1.5$ and $T = 2\pi$, it converges to $A = 2.0086$ and $T = 6.6633$, which matches the known period for $\mu = 1$.&lt;/p&gt;&#10;&lt;p&gt;For harmonic balance, the nonlinear term $x^2 \dot{x}$ is awkward to expand by hand. Instead, we evaluate the residual at sample points in the time domain, and project it back onto the harmonics with a Fourier transform. This time-domain/frequency-domain round trip is also how harmonic balance simulators handle nonlinear devices. The results:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Harmonics $N$&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Period $T$&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Error vs. shooting&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6.283185&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$3.8 \times 10^{-1}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6.665495&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$2.2 \times 10^{-3}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6.663382&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$9.5 \times 10^{-5}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6.663287&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1.8 \times 10^{-7}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;6.663287&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1.1 \times 10^{-9}$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;With a single harmonic, harmonic balance gives exactly $T = 2\pi$ and an amplitude of 2. This is the &lt;strong&gt;describing function&lt;/strong&gt; result that you may remember from analyzing oscillators by hand: it assumes the waveform is a pure sinusoid, and therefore misses the frequency shift caused by the harmonics. With more harmonics, the error drops very quickly, because the waveform is smooth.&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="Van der Pol oscillator: one period from shooting and harmonic balance with 1 and 7 harmonics, and the harmonic balance period error vs. number of harmonics" class="gallery-image" data-flex-basis="694px" data-flex-grow="289" height="823" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/bvp/pss-autonomous.png" width="2381"&gt;&lt;/p&gt;&#10;&lt;p&gt;Note that the two methods use different phase conditions, so I aligned the HB waveforms to the peak at $t = 0$ in the left plot.&lt;/p&gt;&#10;&lt;h3 id="summary"&gt;&lt;a href="#summary" class="header-anchor"&gt;&lt;/a&gt;Summary&#10;&lt;/h3&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Driven circuit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Autonomous circuit&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Known&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Period $T$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nothing&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Unknowns&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\mathbf{x}_0$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\mathbf{x}_0$ and $T$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Equations&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Periodicity ($n$)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Periodicity ($n$) + phase condition ($1$)&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Shooting&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Newton on $\psi(\mathbf{x}_0)(T) - \mathbf{x}_0$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Same, with $T$ as an unknown and e.g. $\dot{x}_j(0) = 0$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Harmonic balance&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Coefficients at known $\omega$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Coefficients and $\omega$, with e.g. $b_1 = 0$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The two examples also show the trade-off between the two methods nicely. The switching waveform of the driven RC is exact with shooting, but with harmonic balance the error only falls like $1/N$: going from 25 to 101 harmonics buys just a 4× improvement. The smooth waveform of the Van der Pol oscillator is captured to $10^{-9}$ with 11 harmonics.&lt;/p&gt;&#10;</description></item><item><title>Numerical Methods: Transient Simulation</title><link>http://blog.cedard.top/post/profession/numerical_methods/ivp/</link><pubDate>Thu, 01 Oct 2026 20:58:32 -0700</pubDate><guid>http://blog.cedard.top/post/profession/numerical_methods/ivp/</guid><description>&lt;p&gt;Although I&amp;rsquo;m an IC designer, it&amp;rsquo;s sometimes necessary to understand what my EDA tool is doing under the hood, especially when the simulation takes so long for extracted simulation. You might wonder why the extracted simulation takes so much longer than the schematic simulation by just adding a bunch of parasitic elements. The reason lies in the numerical methods used to solve the underlying differential equations, which become more complex and computationally intensive as the circuit model becomes more detailed.&lt;/p&gt;&#10;&lt;p&gt;I will discuss some of the commonly used numerical methods used in transient simulation of circuits in this post.&lt;/p&gt;&#10;&lt;h2 id="initial-value-problem-ivp"&gt;&lt;a href="#initial-value-problem-ivp" class="header-anchor"&gt;&lt;/a&gt;Initial Value Problem (IVP)&#10;&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s say you want to measure an RC circuit&amp;rsquo;s settling time after it&amp;rsquo;s subjected to a step current input, and you ask the simulator to simulate for 50ns. You have just created an initial value problem (IVP), where the initial condition is the circuit&amp;rsquo;s state at the beginning of the simulation, and the goal is to find the circuit&amp;rsquo;s state at subsequent time points.&lt;/p&gt;&#10;&lt;p&gt;Mathematically, an IVP can be expressed as:&lt;/p&gt;&#10;$$&#10;\frac{d\mathbf{x}(t)}{dt} = \mathbf{f}(\mathbf{x}(t), t), \quad \mathbf{x}(t_0) = \mathbf{x}_0&#10;$$&lt;p&gt;where $\mathbf{x}(t)$ represents the state vector of the circuit at time $t$, $\mathbf{f}(\mathbf{x}(t), t)$ represents the system of differential equations governing the circuit, $t_0$ is the initial time, and $\mathbf{x}_0$ is the initial state of the circuit.&lt;/p&gt;&#10;&lt;p&gt;The IVP problem can also be interpreted as we performing a mapping from the initial state $\mathbf{x}_0$ at time $t_0$ to the solution vector $\mathbf{x}(t)$ at subsequent time points. In this case, people usually define it in following form:&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;Given the system&lt;/p&gt;&#10;$$ \dot{x} = f(x, t), \quad x(t_0) = x_0 \in \mathbb{R} $$&lt;p&gt;&#10;Define mapping $ \psi: \mathbb{R}^n \to \mathbb{C}^n $ such that&lt;/p&gt;&#10;$$ \psi(x_0)(t) = x(t, t_0, x_0) \quad \text{for} \quad t \ge t_0 $$&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;A system must be Lipschitz continuous with respect to the state vector $\mathbf{x}$ to guarantee the existence and uniqueness of the solution to the IVP. This can be either local or global, and whether a solution exists globally or only locally also depends on the Lipschitz condition. This is also known as the &lt;a class="link" href="https://en.wikipedia.org/wiki/Picard%E2%80%93Lindel%C3%B6f_theorem" target="_blank" rel="noopener"&#10; &gt;Picard-Lindelöf theorem&lt;/a&gt;, also known as the uniqueness and existence theorem for ordinary differential equations.&lt;/p&gt;&#10;&lt;h2 id="forward-euler-method"&gt;&lt;a href="#forward-euler-method" class="header-anchor"&gt;&lt;/a&gt;Forward Euler Method&#10;&lt;/h2&gt;&lt;p&gt;The Forward Euler Method is one of the simplest and most widely used numerical methods for solving IVPs. It is an explicit method, meaning that the state at the next time step is computed directly from the state at the current time step. The method can be expressed as:&lt;/p&gt;&#10;$$&#10;x_{k+1} = x_k + h f(x_k, t_k)&#10;$$&lt;p&gt;where $x_k$ is the state at time $t_k$, $h$ is the time step size, and $f(x_k, t_k)$ is the derivative of the state with respect to time evaluated at the current state and time.&lt;/p&gt;&#10;&lt;p&gt;The Forward Euler Method is conditionally stable, meaning that the choice of time step $h$ can affect the accuracy and stability of the solution. For stiff problems, where the system exhibits rapid changes in some components, the Forward Euler Method may require very small time steps to maintain stability, making it computationally expensive.&lt;/p&gt;&#10;&lt;p&gt;Here is a python code snippet demonstrating the Forward Euler Method:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;np&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward_euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Solve dx/dt = f(x, t), x(t0) = x0 with x_{k+1} = x_k + h * f(x_k, t_k).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# RC circuit driven by a step current: C dv/dt = I - v/R&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;10e3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;100e-6&lt;/span&gt; &lt;span class="c1"&gt;# 10 kOhm, 1 pF, 100 uA -&amp;gt; tau = RC = 10 ns&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;tau&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;C&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forward_euler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;50e-9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;20&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;v_exact&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;I&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;tau&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;21&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;final value: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; V (exact &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;v_exact&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.4f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; V), &amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;22&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;max error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;v_exact&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;1e3&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;.2f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; mV&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With $h = 0.1\tau$, the final value is 0.9948 V against the exact 0.9933 V, and the worst-case error along the waveform is about 19 mV. Not bad for three lines of math.&lt;/p&gt;&#10;&lt;h3 id="stability-the-test-equation"&gt;&lt;a href="#stability-the-test-equation" class="header-anchor"&gt;&lt;/a&gt;Stability: the Test Equation&#10;&lt;/h3&gt;&lt;p&gt;What does &amp;ldquo;conditionally stable&amp;rdquo; actually mean? The standard way to answer this is to feed the method the simplest possible linear system, also known as the &lt;strong&gt;test equation&lt;/strong&gt;:&lt;/p&gt;&#10;$$&#10;\dot{x} = \lambda x, \quad \lambda \in \mathbb{C}&#10;$$&lt;p&gt;Most linear circuits can be diagonalized into a bunch of these, with $\lambda$ being the poles of the circuit. Applying Forward Euler:&lt;/p&gt;&#10;$$&#10;x_{k+1} = x_k + h \lambda x_k = (1 + h\lambda) x_k&#10;$$&lt;p&gt;so after $k$ steps we have $x_k = (1 + h\lambda)^k x_0$. Let&amp;rsquo;s define $z = h\lambda$, and call $R(z) = 1 + z$ the &lt;strong&gt;stability function&lt;/strong&gt; of the method. The numerical solution only decays (like a stable circuit should) if $|R(z)| &amp;lt; 1$, i.e. $z$ has to sit inside a unit circle centered at $-1$.&lt;/p&gt;&#10;&lt;p&gt;For our RC circuit, $\lambda = -1/\tau$, therefore:&lt;/p&gt;&#10;$$&#10;|1 - h/\tau| &lt; 1 \Rightarrow h &lt; 2\tau&#10;$$&lt;p&gt;If we pick a time step larger than $2\tau$, the simulation blows up, even though the circuit itself is perfectly stable. Bear in mind that this limit is set by the &lt;strong&gt;fastest&lt;/strong&gt; pole in the system, not the one we care about.&lt;/p&gt;&#10;&lt;h2 id="backward-euler-method"&gt;&lt;a href="#backward-euler-method" class="header-anchor"&gt;&lt;/a&gt;Backward Euler Method&#10;&lt;/h2&gt;&lt;p&gt;What if we evaluate the derivative at the &lt;em&gt;end&lt;/em&gt; of the step instead of the beginning?&lt;/p&gt;&#10;$$&#10;x_{k+1} = x_k + h f(x_{k+1}, t_{k+1})&#10;$$&lt;p&gt;We realize that $x_{k+1}$ now shows up on both sides of the equation, which makes Backward Euler an &lt;strong&gt;implicit&lt;/strong&gt; method: we can&amp;rsquo;t just compute the next state, we have to &lt;em&gt;solve&lt;/em&gt; for it. For a nonlinear $f$, this is done using Newton-Raphson iteration at every time step, which is exactly what your circuit simulator is doing.&lt;/p&gt;&#10;&lt;p&gt;Applying the test equation:&lt;/p&gt;&#10;$$&#10;x_{k+1} = x_k + h\lambda x_{k+1} \Rightarrow R(z) = \frac{1}{1 - z}&#10;$$&lt;p&gt;$|R(z)| &amp;lt; 1$ whenever $\Re(z) &amp;lt; 0$, meaning that Backward Euler is stable for &lt;em&gt;any&lt;/em&gt; step size, as long as the circuit itself is stable. This property is called &lt;strong&gt;A-stability&lt;/strong&gt;. On top of that, $R(z) \to 0$ as $z \to -\infty$, so very fast poles are damped out immediately instead of lingering around; this stronger property is called &lt;strong&gt;L-stability&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;p&gt;Since the time-marching loop is the same for every method, from now on let&amp;rsquo;s split it out, and only write down how one step is taken:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;integrate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t_end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;March x_{k+1} = step(f, x_k, t_k, h) from t0 to t_end.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;t_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n_steps&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;forward_euler_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For the implicit methods, we need a small Newton-Raphson solver. Here I use a finite-difference Jacobian to keep it generic:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Solve g(x) = 0 with Newton-Raphson and a finite-difference Jacobian.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atleast_1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_iter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;gx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atleast_1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;J&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 7&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 8&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zeros_like&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt; 9&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1e-8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;10&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;[:,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;atleast_1d&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;gx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;11&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;linalg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;solve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;J&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;gx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;12&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;13&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;tol&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;14&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;15&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;16&lt;/span&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;17&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;backward_euler_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;18&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# x_{k+1} appears on both sides&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;19&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The price we pay: every time step now requires building a Jacobian and solving a linear system, instead of a single function evaluation. And Backward Euler is still only first-order accurate, just like Forward Euler.&lt;/p&gt;&#10;&lt;h2 id="trapezoidal-method"&gt;&lt;a href="#trapezoidal-method" class="header-anchor"&gt;&lt;/a&gt;Trapezoidal Method&#10;&lt;/h2&gt;&lt;p&gt;Forward Euler uses the slope at the beginning of the step, Backward Euler uses the slope at the end. A natural idea is to take the average of the two:&lt;/p&gt;&#10;$$&#10;x_{k+1} = x_k + \frac{h}{2} \left[ f(x_k, t_k) + f(x_{k+1}, t_{k+1}) \right]&#10;$$&lt;p&gt;This is the trapezoidal rule of integration applied to the IVP, and it&amp;rsquo;s still implicit. The stability function is:&lt;/p&gt;&#10;$$&#10;R(z) = \frac{1 + z/2}{1 - z/2}&#10;$$&lt;p&gt;Here is the interesting part: for $\Re(z) &amp;lt; 0$, the numerator is always smaller than the denominator in magnitude, so the trapezoidal method is also A-stable. On the imaginary axis, $|R(j\omega h)| = 1$ &lt;em&gt;exactly&lt;/em&gt;, which means a lossless oscillator stays lossless. And it&amp;rsquo;s second-order accurate, which is a big upgrade over both Euler methods at almost the same cost.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;trapezoidal_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;fk&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fk&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;newton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote class="alert alert-warning"&gt;&#10; &lt;div class="alert-header"&gt;&#10; &lt;span class="alert-icon"&gt;⚠️&lt;/span&gt;&#10; &lt;span class="alert-title"&gt;Warning&lt;/span&gt;&#10; &lt;/div&gt;&#10; &lt;div class="alert-body"&gt;&#10; &lt;p&gt;The trapezoidal method is A-stable but &lt;strong&gt;not&lt;/strong&gt; L-stable: as $z \to -\infty$, $R(z) \to -1$. A very fast pole (for example, a tiny parasitic RC) is therefore not damped out; instead its component flips sign every time step. This is the infamous &amp;ldquo;trapezoidal ringing&amp;rdquo; you may have seen in SPICE waveforms, typically right after a sharp edge. This is also why simulators commonly mix in Backward Euler or Gear (BDF2) steps around discontinuities.&lt;/p&gt;&#10; &lt;/div&gt;&#10; &lt;/blockquote&gt;&#10;&lt;h2 id="heuns-method-improved-euler"&gt;&lt;a href="#heuns-method-improved-euler" class="header-anchor"&gt;&lt;/a&gt;Heun&amp;rsquo;s Method (Improved Euler)&#10;&lt;/h2&gt;&lt;p&gt;What if we want the averaging idea of the trapezoidal method, but don&amp;rsquo;t want to solve an implicit equation? We can simply &lt;em&gt;predict&lt;/em&gt; $x_{k+1}$ using Forward Euler first, then use the prediction to evaluate the slope at the end of the step:&lt;/p&gt;&#10;$$&#10;\begin{align*}&#10;\tilde{x}_{k+1} &amp;= x_k + h f(x_k, t_k) \\&#10;x_{k+1} &amp;= x_k + \frac{h}{2} \left[ f(x_k, t_k) + f(\tilde{x}_{k+1}, t_{k+1}) \right]&#10;\end{align*}&#10;$$&lt;p&gt;This is called &lt;strong&gt;Heun&amp;rsquo;s method&lt;/strong&gt;, or the &lt;strong&gt;improved Euler method&lt;/strong&gt;. It&amp;rsquo;s a predictor-corrector scheme: explicit, two function evaluations per step, and second-order accurate.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;heun_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;x_pred&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="c1"&gt;# predictor: one Forward Euler step&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x_pred&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# corrector: average the two slopes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, being explicit, there is no free lunch on stability. The stability function is:&lt;/p&gt;&#10;$$&#10;R(z) = 1 + z + \frac{z^2}{2}&#10;$$&lt;p&gt;which, just like Forward Euler, requires $h &amp;lt; 2\tau$ on the negative real axis. Worse, on the imaginary axis $|R(j\omega h)|^2 = 1 + (\omega h)^4 / 4 &amp;gt; 1$, so an LC tank slowly gains energy.&lt;/p&gt;&#10;&lt;h2 id="runge-kutta-methods"&gt;&lt;a href="#runge-kutta-methods" class="header-anchor"&gt;&lt;/a&gt;Runge-Kutta Methods&#10;&lt;/h2&gt;&lt;p&gt;Heun&amp;rsquo;s method is in fact the simplest member of a larger family: the &lt;strong&gt;Runge-Kutta methods&lt;/strong&gt;. The idea is to sample the slope at several intermediate points within one step, and combine them with carefully chosen weights so that the error terms cancel up to a certain order. The most famous one is the classical fourth-order Runge-Kutta, usually just called &lt;strong&gt;RK4&lt;/strong&gt;:&lt;/p&gt;&#10;$$&#10;\begin{align*}&#10;k_1 &amp;= f(x_k, t_k) \\&#10;k_2 &amp;= f(x_k + \tfrac{h}{2} k_1, t_k + \tfrac{h}{2}) \\&#10;k_3 &amp;= f(x_k + \tfrac{h}{2} k_2, t_k + \tfrac{h}{2}) \\&#10;k_4 &amp;= f(x_k + h k_3, t_k + h) \\&#10;x_{k+1} &amp;= x_k + \frac{h}{6} (k_1 + 2k_2 + 2k_3 + k_4)&#10;\end{align*}&#10;$$&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;rk4_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;5&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;k4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;6&lt;/span&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;k3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;k4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Its stability function is the Taylor series of $e^z$ truncated at the fourth order:&lt;/p&gt;&#10;$$&#10;R(z) = 1 + z + \frac{z^2}{2} + \frac{z^3}{6} + \frac{z^4}{24}&#10;$$&lt;p&gt;The stability region is noticeably larger than Forward Euler&amp;rsquo;s (about $h &amp;lt; 2.79\tau$ on the negative real axis), and the accuracy is fourth order. But it&amp;rsquo;s still bounded: RK4 is explicit, and no explicit method can be A-stable.&lt;/p&gt;&#10;&lt;h2 id="comparison"&gt;&lt;a href="#comparison" class="header-anchor"&gt;&lt;/a&gt;Comparison&#10;&lt;/h2&gt;&lt;p&gt;Putting everything together:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Method&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Type&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Order&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Stability function $R(z)$&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;A-stable&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Cost per step&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Forward Euler&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Explicit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1 + z$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;No&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1 evaluation of $f$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Backward Euler&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Implicit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\frac{1}{1 - z}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Yes (also L-stable)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Newton solve&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Trapezoidal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Implicit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$\frac{1 + z/2}{1 - z/2}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Yes (not L-stable)&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Newton solve&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Heun&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Explicit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1 + z + \frac{z^2}{2}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;No&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2 evaluations of $f$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RK4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Explicit&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;$1 + z + \ldots + \frac{z^4}{24}$&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;No&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4 evaluations of $f$&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;And the pros and cons:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Forward Euler&lt;/strong&gt;: dead simple and cheap per step. However it&amp;rsquo;s only first order, and the step size is limited by the fastest pole. It also adds energy to oscillators.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Backward Euler&lt;/strong&gt;: unconditionally stable and kills fast parasitic modes immediately, which makes it extremely robust. The downside is that it&amp;rsquo;s only first order, and it adds artificial damping: an oscillator rings down even if it&amp;rsquo;s lossless.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Trapezoidal&lt;/strong&gt;: second order, A-stable, and preserves the amplitude of lossless oscillations. But it is not L-stable, which leads to the numerical ringing on stiff circuits, and it still needs a Newton solve every step.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Heun&lt;/strong&gt;: second order without any implicit solve, so it&amp;rsquo;s a cheap accuracy upgrade over Forward Euler. Stability is just as limited, though, and it slowly pumps energy into oscillators.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;RK4&lt;/strong&gt;: very accurate per step for smooth problems, and still explicit. But four function evaluations per step, and the stability region is bounded, so a stiff circuit will force tiny steps anyway.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="an-example-with-a-closed-form-solution"&gt;&lt;a href="#an-example-with-a-closed-form-solution" class="header-anchor"&gt;&lt;/a&gt;An Example with a Closed-Form Solution&#10;&lt;/h2&gt;&lt;p&gt;Talk is cheap. Let&amp;rsquo;s put all five methods side by side on circuits where we know the exact answer.&lt;/p&gt;&#10;&lt;h3 id="lc-tank"&gt;&lt;a href="#lc-tank" class="header-anchor"&gt;&lt;/a&gt;LC Tank&#10;&lt;/h3&gt;&lt;p&gt;Consider an ideal LC tank with \( L = 1\,\text{nH} \), \( C = 1\,\text{pF} \), initially charged to 1 V:&lt;/p&gt;&#10;$$&#10;\begin{align*}&#10;C \frac{dv}{dt} &amp;= -i \\&#10;L \frac{di}{dt} &amp;= v&#10;\end{align*}&#10;$$&lt;p&gt;with the closed-form solution $v(t) = \cos(\omega_0 t)$, $\omega_0 = 1/\sqrt{LC}$. The poles sit right on the imaginary axis, so this is the perfect test for whether a method adds or removes energy. We simulate five periods with $h = T_0/16$:&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="LC tank simulated with all five methods at h = T0/16, and the stored energy over time" class="gallery-image" data-flex-basis="694px" data-flex-grow="289" height="823" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/ivp/lc-tank-methods.png" width="2381"&gt;&lt;/p&gt;&#10;&lt;p&gt;After five periods ($v(5T_0) = 1$ V exactly):&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Method&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;$v(5T_0)$&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Energy / $E_0$&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Forward Euler&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+28.05 V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;96131&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Backward Euler&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+0.0003 V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;≈ 0&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Trapezoidal&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+0.923 V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.0000&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Heun&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+0.912 V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.61&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;RK4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;+0.998 V&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.996&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;This matches what the stability functions told us. Forward Euler explodes, because $|1 + j\omega_0 h| &amp;gt; 1$ and the energy grows by a fixed factor every step. Backward Euler damps the tank to nothing within five periods. The trapezoidal method keeps the energy &lt;em&gt;exactly&lt;/em&gt;, but the waveform slowly drifts in phase, which is why $v(5T_0)$ is off. Heun slowly gains energy, and RK4 is the closest to the exact solution with a tiny bit of damping.&lt;/p&gt;&#10;&lt;h3 id="rc-step-response-with-a-large-step"&gt;&lt;a href="#rc-step-response-with-a-large-step" class="header-anchor"&gt;&lt;/a&gt;RC Step Response with a Large Step&#10;&lt;/h3&gt;&lt;p&gt;Now back to our RC circuit from the beginning ($\tau = 10$ ns), but this time we deliberately use a step size $h = 2.5\tau$, which is larger than what Forward Euler can handle:&lt;/p&gt;&#10;&lt;p&gt;&lt;img alt="RC step response with h = 2.5 tau for all five methods, and the global error vs. step size for the LC tank" class="gallery-image" data-flex-basis="696px" data-flex-grow="290" height="821" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://images.blog.cedard.top/post/profession/numerical_methods/ivp/rc-stiff-convergence.png" width="2381"&gt;&lt;/p&gt;&#10;&lt;p&gt;On the left:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Forward Euler and Heun both diverge, since $h &amp;gt; 2\tau$. After 250 ns, they are at −56.7 V and −127.4 V respectively.&lt;/li&gt;&#10;&lt;li&gt;RK4 is still stable ($2.5 &amp;lt; 2.79$), but it&amp;rsquo;s inaccurate: after one step it&amp;rsquo;s at 0.352 V while the exact answer is 0.918 V. Stable doesn&amp;rsquo;t mean correct.&lt;/li&gt;&#10;&lt;li&gt;Backward Euler settles without any overshoot, just a bit slower than the real circuit.&lt;/li&gt;&#10;&lt;li&gt;Trapezoidal overshoots to 1.111 V on the first step and then rings around the final value: exactly the &amp;ldquo;trapezoidal ringing&amp;rdquo; from earlier, since $R(-2.5) = -0.11$ is negative.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;On the right, we sweep the step size on the LC tank and plot the maximum error over one period. The slopes on the log-log plot give the order of each method: 1.0 for both Euler methods, 2.0 for trapezoidal and Heun, and 4.0 for RK4, exactly as advertised. Notice also that trapezoidal and Heun share the same slope, but trapezoidal has the smaller error constant.&lt;/p&gt;&#10;&lt;h2 id="back-to-the-question-why-is-my-extracted-simulation-so-slow"&gt;&lt;a href="#back-to-the-question-why-is-my-extracted-simulation-so-slow" class="header-anchor"&gt;&lt;/a&gt;Back to the Question: Why Is My Extracted Simulation So Slow?&#10;&lt;/h2&gt;&lt;p&gt;Now we are able to answer the question from the beginning. Circuits are &lt;strong&gt;stiff&lt;/strong&gt;: the time constants of a post-layout netlist span many orders of magnitude, from the slow settling we care about down to the femtosecond-level RC of a tiny parasitic. With an explicit method, the step size would be dictated by that fastest parasitic pole, so explicit methods like RK4 are rarely used in circuit simulators.&lt;/p&gt;&#10;&lt;p&gt;Instead, SPICE-family simulators use implicit methods, typically trapezoidal and Gear (BDF2), together with Backward Euler around discontinuities. In Spectre, you can choose among them using the &lt;code&gt;method&lt;/code&gt; option of the transient analysis. Implicit methods remove the stability limit, but every time step now requires several Newton-Raphson iterations, and every iteration requires factorizing a matrix whose size grows with the number of nodes. Adding thousands of parasitic nodes therefore makes each time step much more expensive, and the fast parasitic dynamics still force the simulator to take smaller steps to keep the accuracy within tolerance.&lt;/p&gt;&#10;</description></item></channel></rss>