Jekyll2022-02-24T05:35:34+00:00https://jmontalvo94.github.io/feed.xmlJorge MontalvoWebsite and blog of Jorge MontalvoJorge MontalvoPINNs I: About stability and convergence2020-12-03T00:00:00+00:002020-12-03T00:00:00+00:00https://jmontalvo94.github.io/blog/2020/pinns-i<blockquote> <p><strong>PINNs series</strong> - This is the first of a three-series blog posts about Physics-Informed Neural Networks (<a href="https://maziarraissi.github.io/PINNs/">PINNs</a>). <!-- If you're only interested in the implementation of the PINN, skip to [post 2](https://jmontalvo94.github.io/blog/2020/pinns-ii/)! --></p> </blockquote> <p class="notice--success"><strong>TL;DR.</strong> A generator will behave according to the swing equation, where its physical parameters and the magnitude of the disturbance will take it into a new state, possibly to a non-stable state too! We will solve this equation for a single machine system using PINNs $\rightarrow m \ddot{\delta}(t) + d \dot{\delta}(t) + B sin(\delta(t)) - P(t) = 0$</p> <h1 id="the-future-of-the-power-system">The future of the power system</h1> <p>Given the impact of the climate change crisis in our planet and the effort in the power sector to decarbonize the electricity supply, it is imperative to further increase the electricity produced by renewable resources.<sup><a href="#fn1">1</a></sup> However, with a rising penetration of renewables and a decrease in conventional units, the system stability becomes more tricky since some important parameters of the system (inertia, explained below) will tend to decrease. Hence, the analysis of the stability of the power system becomes an important challenge for the future decarbonized grid.<sup><a href="#fn2">2</a></sup></p> <p>In general, the dynamic stability of the grid is analyzed by running many simulations and experiments based on ordinary differential equation (ODE) solvers. These solvers take some time to run and require tuning and special handling, this is where machine learning applications might come to the rescue to speed-up the calculations and simplify the process. However, keep in mind that the power system community has spent entire decades on developing these specialized tools! A great solution would take into account the knowledge we currently have of the power system and combine it with ML algorithms to further push the boundary of science 🤩</p> <h1 id="the-swing-equation">The swing equation</h1> <p class="text-center"><img src="https://jmontalvo94.github.io/images/smib.PNG" alt="SMIB" /> <strong>Figure 1.</strong> Single machine infinite bus system</p> <p>First, let’s tap into the stability of power systems to study the physical dynamics of an electrical generator. The single machine infinite bus (SMIB) system helps us represent the behavior of a machine connected to the grid, as depicted in <strong>Figure 1.</strong><sup><a href="#fn3">3</a></sup> Here we have a generator $P_1$ connected to node 1, a transmission or distribution line represented by the susceptance $B_{12}$ between node 1 and node 2, and finally the power grid at node 2. Given some power demand and the difference in the voltages (driven by the voltage drop in the line) between both nodes, a power flows from the generator into the grid, represented by $P$. If we imagine this in the real world, $P_1$ could represent a wind turbine or gas turbine connected to some substation through a transmission line. This system can be represented by the <strong>swing equation</strong>, which directly originates from Newton’s second law for a rotational motion of a rigid body:</p> $T_a(t) = J \alpha_m(t)$ $T_a(t) = T_m(t) - T_e(t)$ <p>where</p> <p>$T \rightarrow$ (a)ccelerating, (m)echanical and (e)lectrical torque [$\text{N-m}$]</p> <p>$J \rightarrow$ moment of inertia or rotational inertia [$\text{kg}-m^2$]</p> <p>$\alpha \rightarrow$ rotor angular acceleration [$\text{rad}/s^2$]</p> <p>This equation describes how, given a difference between a mechanical torque (a force given by some driver like a turbine connected to the generator) and an electrical torque (a force requested by the power grid), the generator will respond with certain acceleration $\alpha_m$. The moment of inertia or rotational inertia can be interpreted as the proportional of the torque that needs to be given to the system to start accelerating (or decelerating). In simpler terms, it basically tells us that if we apply torque to a rotational body, then it will start to accelerate if we give it enough force (or the other way around, if the body is already rotating the moment of inertia opposes to change, so you need to give some more torque in the opposite direction to start decelerating). Notice that all terms depend on time except the inertia, which is a property of the body!</p> <p>Further, the angular acceleration of the rotor can be decomposed into its derivatives:</p> $\alpha_m(t) = \frac{dw_m(t)}{dt} = \frac{d^2\theta_m(t)}{dt^2}$ $w_m(t) = \frac{d\theta_m(t)}{dt}$ <p>where</p> <p>$w \rightarrow$ rotor angular velocity [$\text{rad}/s$]</p> <p>$\theta \rightarrow$ rotor angle [$\text{rad}$]</p> <p>In steady-state, $T_m = T_e$ which makes $T_a = 0$, i.e. the rotor acceleration is zero and there’s a constant rotor velocity. This means that in a <em>stable</em> state there’s no acceleration, just a constant rotor velocity. Imagine that if two people are pulling a string, one in each side, if they both exert the exact same force they will be in an equilibrium state, not accelerating at all! However, in a rotational body the system keeps a constant angular velocity, which is basically the velocity at which the rotor rotates (that’s why it is called rotor!).</p> <p>Now, if we define the synchronous speed as the reference axis, which is the speed at which the magnetic field rotates to keep some designed frequency (50 Hz in Europe, 60 Hz in North America). And if we magically had the ability to measure the rotor angular position at every time step with respect to this reference axis, then:</p> $\theta_m(t) = w_{syn}(t) + \delta_m(t)$ <p>where</p> <p>$w_{syn} \rightarrow$ synchronous angular velocity of rotor [$\text{rad}/s$]</p> <p>$\delta_m \rightarrow$ rotor angular position w.r.t. synchronous reference [$\text{rad}$]</p> <p>Then using these definitions in the swing equation:</p> $T_m(t) - T_e(t) = J\frac{d^2\theta_m(t)}{dt^2}$ <p>Measuring only from the reference axis:</p> $T_m(t) - T_e(t) = J \frac{d^2\delta_m(t)}{dt^2}$ <p>Multiplying both sides by $w_m$ (to obtain power instead of torque) and dividing by $\text{S}_{rated}$ (to obtain power in per unit):</p> $p_{m,}(t) - p_{e}(t) = \frac{Jw_m(t)}{\text{S}_{rated}}\frac{d^2\delta(t)}{dt^2}$ <p>Adding a damping term $d$, which is similar to the rotational inertia but depends on the angular velocity:</p> $p_{m}(t) - p_{e} = \frac{Jw_m(t)}{\text{S}_{rated}}\frac{d^2\delta(t)}{dt^2} + d\frac{d\delta(t)}{dt}$ <p>Defining an inertia constant as $m$:</p> $p_{m}(t) - p_{e}(t) = m\frac{d^2\delta(t)}{dt^2} + d\frac{d\delta(t)}{dt}$ <p>Finally, using Newton’s dot notation for derivatives:</p> $p_{m}(t) - p_{e}(t) = m\ddot{\delta}(t) + d\dot{\delta}(t)$ <p class="notice--warning"><strong>Notice.</strong> This formulation ignores transmission losses and bus voltage deviations!</p> <h2 id="ode-formulation">ODE Formulation</h2> <p>Now that we defined the basis of the swing equation, we can represent the SMIB system as an ordinary differential equation (ODE):</p> $m_k \ddot{\delta} + d_k \dot{\delta} + B_{kj} V_k V_j sin(\delta) - P_k = 0$ <p>Then, we can express this second-order differential equation into a system of ODEs by defining the state-vector:</p> $\begin{bmatrix}x_1 \\ x_2\end{bmatrix} = \begin{bmatrix} \delta\\ \dot{\delta} \end{bmatrix}$ <p>where</p> $\frac{d}{dt}\begin{bmatrix} \delta \\ \dot{\delta} \end{bmatrix} = \begin{bmatrix} \dot{\delta} \\ \ddot{\delta} \end{bmatrix}$ $\begin{bmatrix} \dot{\delta} \\ \ddot{\delta} \end{bmatrix} = \begin{bmatrix} \dot{\delta} \\ \frac{P - d \dot{\delta} - B\ \text{sin}\left( \delta_i - \delta_j \right)}{m} \end{bmatrix}$ <p>and, since $\delta_j$ is always zero in our case:</p> $\begin{bmatrix} \dot{\delta} \\ \ddot{\delta} \end{bmatrix} = \begin{bmatrix} \dot{\delta} \\ \frac{P - d \dot{\delta} - B\ \text{sin}\left( \delta \right)}{m} \end{bmatrix}$ <p>In code, it would look like this:</p> <figure class="highlight"><pre><code class="language-py" data-lang="py"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 </pre></td><td class="code"><pre><span class="k">def</span> <span class="nf">swing_equation</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">P</span><span class="p">):</span> <span class="sa">r</span><span class="s">""" Swing equation expressed as a system of ODEs. Given by the formula: .. math:: m_i \ddot{\delta} + d_i \dot{\delta} + B_{ij} V_i V_j sin(\delta) - P_i = 0 Args: x: State vector [delta_i, omega_i] m: Inertia of the machine d: Damping coefficient B: Bus susceptance matrix P: Power injection or retrieval Returns: x_new: Updated state vector [delta_i, omega_i] """</span> <span class="c1"># Split the state variable into delta and omega </span> <span class="n">state_delta</span> <span class="o">=</span> <span class="n">x</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="n">state_omega</span> <span class="o">=</span> <span class="n">x</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="c1"># Computing the non-linear term in the swing equation sum_j (B_ij sin(delta_i - delta_j)) </span> <span class="n">delta_i</span> <span class="o">=</span> <span class="n">state_delta</span> <span class="n">delta_j</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">delta_i</span><span class="p">)</span> <span class="n">delta_ij</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">sin</span><span class="p">(</span><span class="n">delta_i</span> <span class="o">-</span> <span class="n">delta_j</span><span class="p">)</span> <span class="n">connectivity_vector</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">np</span><span class="p">.</span><span class="n">multiply</span><span class="p">(</span><span class="n">B</span><span class="p">,</span> <span class="n">delta_ij</span><span class="p">))</span> <span class="c1"># Preallocate memory </span> <span class="n">state_delta_new</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">state_delta</span><span class="p">)</span> <span class="n">state_omega_new</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">zeros_like</span><span class="p">(</span><span class="n">state_omega</span><span class="p">)</span> <span class="c1"># Update states </span> <span class="n">state_delta_new</span> <span class="o">=</span> <span class="n">state_omega</span> <span class="n">state_omega_new</span> <span class="o">=</span> <span class="mi">1</span> <span class="o">/</span> <span class="n">m</span> <span class="o">*</span> <span class="p">(</span><span class="n">P</span> <span class="o">-</span> <span class="n">d</span> <span class="o">*</span> <span class="n">state_omega</span> <span class="o">-</span> <span class="n">connectivity_vector</span><span class="p">)</span> <span class="k">return</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="n">state_delta_new</span><span class="p">,</span> <span class="n">state_omega_new</span><span class="p">])</span> </pre></td></tr></tbody></table></code></pre></figure> <p>And to solve it we can use the <code class="language-plaintext highlighter-rouge">integrate</code> function from scipy, as follows:</p> <figure class="highlight"><pre><code class="language-py" data-lang="py"><table class="rouge-table"><tbody><tr><td class="gutter gl"><pre class="lineno">1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 </pre></td><td class="code"><pre><span class="k">def</span> <span class="nf">solve_swing_eq</span><span class="p">(</span><span class="n">params</span><span class="p">):</span> <span class="s">"""Solves the swing equation with pre-defined parameters. Args: params: Dictionary containing relevant parameters. Returns: states: Solution of the swing equation (angle) """</span> <span class="c1"># Unpack parameters </span> <span class="n">m</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'inertia'</span><span class="p">]</span> <span class="n">d</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'damping'</span><span class="p">]</span> <span class="n">B</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'susceptance'</span><span class="p">]</span> <span class="n">P</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'power'</span><span class="p">]</span> <span class="n">delta_0</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="n">params</span><span class="p">[</span><span class="s">'delta_0'</span><span class="p">]])</span> <span class="n">omega_0</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="n">params</span><span class="p">[</span><span class="s">'omega_0'</span><span class="p">]])</span> <span class="n">t_span</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'t_span'</span><span class="p">]</span> <span class="c1"># (t_min, t_max) </span> <span class="n">n_data</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s">'n_data'</span><span class="p">]</span> <span class="c1"># Define analysis time period </span> <span class="n">t_eval</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">linspace</span><span class="p">(</span><span class="n">t_span</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">t_span</span><span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="n">n_data</span><span class="p">)</span> <span class="c1"># Pack initial state </span> <span class="n">initial_states</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">concatenate</span><span class="p">([</span><span class="n">delta_0</span><span class="p">,</span> <span class="n">omega_0</span><span class="p">])</span> <span class="c1"># Solve ODE </span> <span class="n">ode_solution</span> <span class="o">=</span> <span class="n">integrate</span><span class="p">.</span><span class="n">solve_ivp</span><span class="p">(</span><span class="n">swing_equation</span><span class="p">,</span> <span class="n">t_span</span><span class="o">=</span><span class="n">t_span</span><span class="p">,</span> <span class="n">y0</span><span class="o">=</span><span class="n">initial_states</span><span class="p">,</span> <span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="n">m</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">B</span><span class="p">,</span> <span class="n">P</span><span class="p">],</span> <span class="n">t_eval</span><span class="o">=</span><span class="n">t_eval</span><span class="p">)</span> <span class="c1"># Extract solution only </span> <span class="n">states</span> <span class="o">=</span> <span class="n">ode_solution</span><span class="p">.</span><span class="n">y</span> <span class="k">return</span> <span class="n">states</span> </pre></td></tr></tbody></table></code></pre></figure> <p>These two code chunks work for a time span, but for a specific power disturbance $P$. We would need to solve it for as many power disturbances cases as we want.</p> <h2 id="about-stability-and-convergence">About stability and convergence</h2> <p>By solving the previous ODE over time, we can obtain the exact behavior of the machine given some power disturbance. Let’s analyze the case when $P = 0.01$, $P = 0.1$, $P = 0.2$, and $P = 0.3$.</p> <p class="text-center"><img src="https://jmontalvo94.github.io/images/rotor_angle.png" alt="Rotor angle" class="zoom" /> <strong>Figure 2.</strong> Rotor angle vs. Time</p> <p>On the rotor angle, in the first two cases it seems that the angle increases with the disturbance to reach an apex and a nadir in the first cycle, then stabilizes. Notice the y-axis, with a very small disturbance the angle doesn’t change much at 0.05 radians, while with a disturbance of 0.1 the angle increases by 0.5 radians. Now, the case with $P=0.3$ is quite interesting, in this case the ODE enters a state of unstability and diverges, i.e. increases the angle and doesn’t converge to a certain value.</p> <p class="text-center"><img src="https://jmontalvo94.github.io/images/rotor_speed.png" alt="Rotor speed" class="zoom" /> <strong>Figure 3.</strong> Rotor speed vs. Time</p> <p>We can see the same behavior on the rotational speed of the rotor, in the first two cases the value stabilizes but on the third case it follows a constant cyclical pattern. Remember that the derivative of the speed is the angle, that’s why the previous plot increased with small bumps, these are shown here as a cyclical pattern.</p> <p>Now, let’s take a look at the phase plots, same cases:</p> <p class="text-center"><img src="https://jmontalvo94.github.io/images/phase_plot.png" alt="Phase plot" class="zoom align-center" /> <strong>Figure 4.</strong> Phase plot - Rotor speed vs. Rotor angle</p> <p>Here we can see the derivatives as an arrow, given a specific point (at certain $\delta$ and $\omega$). In the first two cases, the gradient takes the system into a stable point, starting at 0. However, in the last case, the system follows the gradients and stays in this cyclical pattern we saw in the previous two plots.</p> <h2 id="final-remarks">Final remarks</h2> <p>In this first post, the swing equation case was proposed with an initial investigation on the convergence of the system. This will help us interpret the behavior of the PINNs given this stable/non-stable case of the equation.</p> <h2 id="references">References</h2> <p><a name="fn1">1</a>: D. Rolnick. et. al., Tackling Climate Change with Machine Learning. <a href="https://arxiv.org/pdf/1906.05433.pdf" class="btn btn--info">Paper</a></p> <p><a name="fn2">2</a>: P. Denholm, T. Mai, R. Wallace Kenyon, B. Kroposki, M. O’Malley. Inertia and the Power Grid: A Guide Without the Spin. National Renewable Energy Laboratory (NREL). <a href="https://www.nrel.gov/docs/fy20osti/73856.pdf" class="btn btn--info">Report</a></p> <p><a name="fn3">3</a>: G. S. Misyris, A. Venzke, S. Chatzivasileiadis, Physics-Informed Neural Networks for Power Systems, accepted at IEEE PES General Meeting 2020, Montreal, Canada. <a href="https://arxiv.org/pdf/1911.03737.pdf" class="btn btn--info">Paper</a></p> <!-- [^2] J. Stiasny, G. S. Misyris, S. Chatzivasileiadis, Physics-Informed Neural Networks for Non-linear System Identification applied to Power System Dynamics, submitted, 2020. [[paper](https://arxiv.org/abs/2004.04026)] -->Jorge MontalvoFirst part of the PINNs series where we talk about the swing equation and the stability of power systems.