Recipe11.3.Tackling Coupled Equations


Recipe 11.3. Tackling Coupled Equations

Problem

You've seen how to apply both the Runge-Kutta method and Euler's basic method using VBA for first- and second-order differential equations, but now you need to solve a system of coupled equations . You're wondering how to extend what you've learned so far to deal with this more complicated problem.

Solution

You can apply the same VBA techniques discussed earlier with some modifications to deal with multiple equations.

Discussion

Consider this system of coupled nonlinear differential equations with initial conditions:

Pretty, huh? This problem originally consisted of two coupled second-order equations that were reduced to four first-order equations using the same technique discussed in Recipe 11.2. This is a common technique for reducing the order of differential equations, making them more amenable to solving. This particular problem was adapted from a problem that appears in An Introduction to Linear and Nonlinear Finite Element Analysis, by Prem K. Kythe and Dongming Wei (Birkhauser). The problem represents a finite element solution of the nonlinear vibrations of a metal rod. Since this is a nonlinear problem, it requires a numerical solution. In their book, Kythe and Wei present some Matlab code to solve this problem using Euler's method .

Euler's method

I implemented Euler's method in VBA to illustrate how to solve this same system in Excel. Example 11-4 shows my VBA code.

Example 11-4. DoEuler for nonlinear system

Public Sub DoEuler( )
    Dim y(1 To 4) As Double
    Dim t(1 To 4) As Double
    Dim n As Integer
    Dim dt As Double
    Dim time As Double
 
    y(1) = 29.8613
    y(2) = 0
    y(3) = 30.0616
    y(4) = 0
 
    n = 5000
    dt = 5 / n
    time = 0
 
    For i = 1 To n
        t(1) = y(1) + dt * dy1dt(y(1), y(2), y(3), y(4))
        t(2) = y(2) + dt * dy2dt(y(1), y(2), y(3), y(4))
        t(3) = y(3) + dt * dy3dt(y(1), y(2), y(3), y(4))
        t(4) = y(4) + dt * dy4dt(y(1), y(2), y(3), y(4))
 
        y(1) = t(1)
        y(2) = t(2)
        y(3) = t(3)
        y(4) = t(4)
 
        time = time + dt
        ActiveSheet.Cells(i + 1, 1) = time
        ActiveSheet.Cells(i + 1, 2) = y(1)
        ActiveSheet.Cells(i + 1, 3) = y(2)
        ActiveSheet.Cells(i + 1, 4) = y(3)
        ActiveSheet.Cells(i + 1, 5) = y(4)
    Next i
End Sub
 

As you can see, this is not a particularly complicated subroutine. It's very similar to the code in Example 11-2 that I showed you for solving a first-order equation using Euler's method. The major difference in this case is that we have four coupled equations instead of a single equation.

I'm using VBA array variables in this code for the y-values and temporary t-values. In VBA, you declare an array using Dim y(1 To 4) As Double. You can access the array using y(1) or y(3), and so on.


I have to confess that there's a little more to this code than is shown in Example 11-4. Notice the function callsdy1dt, dy2dt, dy3dt, dy4dtin the lines of code that implement Euler's integration formula (the lines just after the For statement). These functions compute the derivatives for each y. The derivative expressions are taken directly from the system of equations shown earlier. The corresponding VBA functions are shown in Example 11-5.

Example 11-5. Derivative functions

Public Function dy1dt(y1 As Double, y2 As Double, y3 As Double, y4 As Double) As Double
    dy1dt = y2
End Function
 
Public Function dy2dt(y1 As Double, y2 As Double, y3 As Double, y4 As Double) As Double
    dy2dt = (-15600# * (2# * ((Abs(y1)) ^ -0.74) + 3# * ((Abs(y3 - y1))^-0.74))) _
             / 7# * y1 + (3# * 15600# * ((Abs(y3 - y1)) ^ -0.74)) / 7# * y3
End Function
 
Public Function dy3dt(y1 As Double, y2 As Double, y3 As Double, y4 As Double) As Double
    dy3dt = y4
End Function
 
Public Function dy4dt(y1 As Double, y2 As Double, y3 As Double, y4 As Double) As Double
    dy4dt = (-15600# * (-((Abs(y1)) ^ -0.74) - 5# * ((Abs(y3 - y1)) ^ -0.74))) _
             / 7# * y1 - (5# * 15600# * ((Abs(y3 - y1)) ^ -0.74)) / 7# * y3
End Function
 

This code runs just fine. The problem is that this is a particularly troublesome system of equations for Euler's method. The solution diverges dramatically as it marches along in time. Check out Figure 11-5 to see what I mean.

The chart shows y1 and y3the displacements of the rod at its center and free endover time. The results should look sinusoidal. However, you can clearly see that this solution is not a sinusoid. This is wonderful example of the sort of problem where Euler's method, though easy to implement, is a poor choice.

Runge-Kutta method

The Runge-Kutta method presents a better alternative to Euler's method for this problem. In Recipe 11.2, I showed you how to reduce a second-order equation to two first-order equations and then use the Runge-Kutta method to solve one and use a Euler step to solve the other. In this new example, I'll show you how to apply the Runge-Kutta method for all four equations at each time step. To this end, I prepared a new subroutine, DoRungeKutta, shown in Example 11-6. DoRungeKutta also makes calls to the derivative functions shown earlier in Example 11-5.

Figure 11-5. Poor results using Euler's method


Example 11-6. Runge-Kutta for nonlinear system

Public Sub DoRungeKutta( )
    ' Declar Local variables
    Dim y(1 To 4) As Double
    Dim k1(1 To 4) As Double
    Dim k2(1 To 4) As Double
    Dim k3(1 To 4) As Double
    Dim k4(1 To 4) As Double
    Dim n As Integer
    Dim dt As Double
    Dim time As Double
 
    Application.ScreenUpdating = False
 
    ' Initialize variables
    y(1) = 29.8613
    y(2) = 0
    y(3) = 30.0616
    y(4) = 0
    n = 5000
    dt = 5 / n:
    time = 0
 
    ' Perform iterations
    For i = 1 To n
        k1(1) = dt * dy1dt(y(1), y(2), y(3), y(4))
        k1(2) = dt * dy2dt(y(1), y(2), y(3), y(4))
        k1(3) = dt * dy3dt(y(1), y(2), y(3), y(4))
        k1(4) = dt * dy4dt(y(1), y(2), y(3), y(4))
 
        k2(1) = dt * dy1dt(y(1)+k1(1)/2#, y(2)+k1(2)/2#, y(3)+k1(3)/2#, _
                           y(4)+k1(4)/2#)
        k2(2) = dt * dy2dt(y(1)+k1(1)/2#, y(2)+k1(2)/2#, y(3)+k1(3)/2#, _
                           y(4)+k1(4)/2#)
        k2(3) = dt * dy3dt(y(1)+k1(1)/2#, y(2)+k1(2)/2#, y(3)+k1(3)/2#, _
                           y(4)+k1(4)/2#)
        k2(4) = dt * dy4dt(y(1)+k1(1)/2#, y(2)+k1(2)/2#, y(3)+k1(3)/2#, _
                           y(4)+k1(4)/2#)
 
        k3(1) = dt * dy1dt(y(1)+k2(1)/2#, y(2)+k2(2)/2#, y(3)+k2(3)/2#, _
                           y(4)+k2(4)/2#)
        k3(2) = dt * dy2dt(y(1)+k2(1)/2#, y(2)+k2(2)/2#, y(3)+k2(3)/2#, _
                           y(4)+k2(4)/2#)
        k3(3) = dt * dy3dt(y(1)+k2(1)/2#, y(2)+k2(2)/2#, y(3)+k2(3)/2#, _
                           y(4)+k2(4)/2#)
        k3(4) = dt * dy4dt(y(1)+k2(1)/2#, y(2)+k2(2)/2#, y(3)+k2(3)/2#, _
                           y(4)+k2(4)/2#)
 
        k4(1) = dt * dy1dt(y(1) + k3(1), y(2) + k3(2), y(3) + k3(3), y(4) + k3(4))
        k4(2) = dt * dy2dt(y(1) + k3(1), y(2) + k3(2), y(3) + k3(3), y(4) + k3(4))
        k4(3) = dt * dy3dt(y(1) + k3(1), y(2) + k3(2), y(3) + k3(3), y(4) + k3(4))
        k4(4) = dt * dy4dt(y(1) + k3(1), y(2) + k3(2), y(3) + k3(3), y(4) + k3(4))
 
        y(1) = y(1) + (k1(1) + 2 * k2(1) + 2 * k3(1) + k4(1)) / 6
        y(2) = y(2) + (k1(2) + 2 * k2(2) + 2 * k3(2) + k4(2)) / 6
        y(3) = y(3) + (k1(3) + 2 * k2(3) + 2 * k3(3) + k4(3)) / 6
        y(4) = y(4) + (k1(4) + 2 * k2(4) + 2 * k3(4) + k4(4)) / 6
 
        time = time + dt
        ActiveSheet.Cells(i + 1, 1) = time
        ActiveSheet.Cells(i + 1, 2) = y(1)
        ActiveSheet.Cells(i + 1, 3) = y(2)
        ActiveSheet.Cells(i + 1, 4) = y(3)
        ActiveSheet.Cells(i + 1, 5) = y(4)
    Next i
    Application.ScreenUpdating = True
    Application.ActiveWorkbook.Save
 
End Sub
 

The line Dim y(1 To 4) As Double declares a four-element array variable in which to store the computed ys at every time step. The next several declarations consist of array declarations for the Runge-Kutta k-values. Each of the four equations requires four empty k-values.

After setting the initial y conditions and initializing other utility variables, the subroutine enters a For loop. The guts of the For loop contain a straightforward application of the Runge-Kutta formulas for each equation in the system being solved. As in earlier examples, output is returned to the active spreadsheet at each time step.

Running DoRungeKutta gives much better results than the earlier attempt that used Euler's method. Figure 11-6 shows the new results.

Figure 11-6. Good results using the Runge-Kutta method


These results look far better than those obtained earlier. Clearly, the results shown in Figure 11-6 for y1 and y3 are sinusoidal as expected. The difference between these results and those obtained using Euler's method are dramatic and effectively illustrate the relative accuracy of higher-order methods, such as the Runge-Kutta method, over Euler's basic method.

Take note of the lines of code Application.ScreenUpdating = False and Application.ScreenUpdating = True at the beginning and end of this subroutine, respectively. These lines turn screen updating off and then back on. This subroutine generates a lot of results, so I turned off screen updating to speed up the calculations. At the end, I turned it back on so the final results are displayed. The line Application.ActiveWorkbook.Save saves the workbook just in case I forget to do so.