Problems implementing Runge Kutta to solve a Damped Pendulum - vb.net

I am a high school student working on a "home project" to animate a damped pendulum by solving differential equations using the Runge Kutta method.
(The equations can be seen here: http://www.maths.tcd.ie/~smurray/Pendulumwriteup.pdf)
I have been informed that in my code, my implementation of RK4 is not correct, and to be honest I have been struggling to understand it.
The program is written in VB.net 2010.
My code for solving the equations are as follows:
Public Sub RK4Solve(ByRef The As Decimal, ByRef Ome As Decimal, ByRef h As Decimal)
l1 = h * Ome
k1 = h * f(The, Ome, h)
l2 = h * (0.5 * l1) + Ome
k2 = f((The + (0.5 * h * k1)), (Ome + (0.5 * h * l1)), (t + (0.5 * h)))
l3 = h * (0.5 * l2) + Ome
k3 = f((The + (0.5 * h * k2)), (Ome + (0.5 * h * l2)), (t + (0.5 * h)))
l4 = h * l3 + Ome
k4 = f((The + (h * k3)), (Ome + (h * l3)), (t + h))
'Setting next step of variables
The = The + (h / 6 * (l1 + (2 * l2) + (2 * l3) + l4))
Ome = Ome + (h / 6 * (k1 + (2 * k2) + (2 * k3) + k4))
t += h
End Sub
I am aware that I am multiplying each step by too many h's - I am just lost on what is happening.
My full code:
Public Class Form1
Dim l As Decimal = 1 'Length of rod (1m)
Dim g As Decimal = 9.81 'Gravity
Dim w As Decimal = 0 ' Angular Velocity
Dim initheta As Decimal = -Math.PI / 2 'Initial Theta
Dim theta As Decimal = -Math.PI / 2 'Theta (This one changes for the simulation)
Dim t As Decimal = 0 'Current time of the simulation
Dim h As Decimal = 0.01 'Time step
Dim b As Decimal = Math.Sqrt(g / l) 'Constant used in the function for dw/dt
Dim k As Decimal = 0 'Coefficient of Damping
Dim initialx = l * Math.Sin(initheta) 'Initial Amplitude of the pendulum
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
End Sub
'Function for dw/dt
Public Function f(ByRef the As Decimal, ByRef omega As Decimal, ByRef time As Decimal)
Return ((-b ^ 2) * Math.Sin(the)) - (k * omega) + (initheta * Math.Cos(omega * time))
End Function
Dim k1, k2, k3, k4, l1, l2, l3, l4 As Decimal 'Initialising RK4 variables
Public Sub RK4Solve(ByRef The As Decimal, ByRef Ome As Decimal, ByRef h As Decimal)
l1 = h * Ome
k1 = h * f(The, Ome, h)
l2 = h * (0.5 * l1) + Ome
k2 = f((The + (0.5 * h * k1)), (Ome + (0.5 * h * l1)), (t + (0.5 * h)))
l3 = h * (0.5 * l2) + Ome
k3 = f((The + (0.5 * h * k2)), (Ome + (0.5 * h * l2)), (t + (0.5 * h)))
l4 = h * l3 + Ome
k4 = f((The + (h * k3)), (Ome + (h * l3)), (t + h))
'Setting next step of variables
The = The + (h / 6 * (l1 + (2 * l2) + (2 * l3) + l4))
Ome = Ome + (h / 6 * (k1 + (2 * k2) + (2 * k3) + k4))
t += h
End Sub
'Timer ticking every 0.1s
'Time step is 0.01s to increase accuracy of results for testing
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
ComboBox1.Items.Add(theta) 'Adding theta to a drop down box to test data
RK4Solve(theta, w, h)
End Sub
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Timer1.Enabled = False
End Sub
End Class
I have been trying to solve this for a while now, and I'm on my last legs so I am resorting to asking for help. Thanks to anyone that can!

It helps if you separate the differential equations from the RK4 implementation. Then you can implement RK4 as in the documentation
k1 = f1( y1, y2, x)
l1 = f2( y1, y2, x)
k2 = f1( y1 + 0.5*h*k1, y2 + 0.5*h*l1, x + 0.5*h)
l2 = f2( y1 + 0.5*h*k1, y2 + 0.5*h*l1, x + 0.5*h)
k3 = f1( y1 + 0.5*h*k2, y2 + 0.5*h*l2, x + 0.5*h)
l3 = f2( y1 + 0.5*h*k2, y2 + 0.5*h*l2, x + 0.5*h)
k4 = f1( y1 + h*k3, y2 + h*l3, x + h)
l4 = f2( y1 + h*k3, y2 + h*l3, x + h)
y1 = y1 + h/6*(k1+2*(k2+k3)+k4)
y2 = y2 + h/6*(l1+2*(l2+l3)+l4)
x = x + h
Not only does it help to avoid the duplication of the multiplication by h, but also the duplication of the addition of the base point values Ome and The.

Related

Block If without End If during the barrier option calculation

I am not able to compute the Barrier Option, because it shows me an error in the first line (where I wrote function). The code is as following. Thank you in advance.
Function UOBarrierOption(S As Double, q As Double, T As Double, X As Double, r As Double, _
sigma As Double, CallPutFlag As String, H As Double, K As Double, phi As Double, eta As Double)
Dim x1 As Double, x2 As Double
Dim y1 As Double, y2 As Double
Dim z As Double, mu As Double, lambda As Double
Dim AA As Double, BB As Double, CC As Double, DD As Double, EE As Double, FF As Double
mu = (r - q - sigma ^ 2 / 2) / (sigma ^ 2)
lambda = Sqr(mu ^ 2 + 2 * r / sigma ^ 2)
x1 = Log(S / X) / (sigma * Sqr(T)) + (1 + mu) * sigma * Sqr(T)
x2 = Log(S / H) / (sigma * Sqr(T)) + (1 + mu) * sigma * Sqr(T)
y1 = (Log(H ^ 2) / S / S) / (sigma * Sqr(T)) + (1 + mu) * sigma * Sqr(T)
y2 = (Log(H / S)) / (sigma * Sqr(T)) + (1 + mu) * sigma * Sqr(T)
z = Log(H / S) / (sigma * Sqr(T)) + lambda * sigma * Sqr(T)
AA = phi * S * Exp(-q * T) * Application.NormSDist(phi * x1) - phi * X * Exp(-r * T) * Application.NormSDist(phi * x1 - phi * sigma * Sqr(T))
BB = phi * S * Exp(-q * T) * Application.NormSDist(phi * x2) - phi * X * Exp(-r * T) * Application.NormSDist(phi * x2 - phi * sigma * Sqr(T))
CC = phi * S * Exp(-q * T) * (H / S) ^ (2 * (mu + 1)) * Application.NormSDist(eta * y1) - phi * X * Exp(-r * T) * (H / S) ^ (2 * mu) * Application.NormSDist(eta * y1 - eta * sigma * Sqr(T))
DD = phi * S * Exp(-q * T) * (H / S) ^ (2 * (mu + 1)) * Application.NormSDist(eta * y2) - phi * X * Exp(-r * T) * (H / S) ^ (2 * mu) * Application.NormSDist(eta * y2 - eta * sigma * Sqr(T))
EE = K * Exp(-r * T) * (Application.NormSDist(eta * x2 - eta * sigma * Sqr(T)) - (H / S) ^ (2 * mu) * Application.NormSDist(eta * y2 - eta * sigma * Sqr(T)))
FF = K * Exp(-r * T) * (Application.NormSDist(-eta * x2 + eta * sigma * Sqr(T)) + (H / S) ^ (2 * mu) * Application.NormSDist(eta * y2 - eta * sigma * Sqr(T)))
If CallPutFlag = "Cdi" Then
If X > H Then
UOBarrierOption = CC + EE
ElseIf X < H Then
UOBarrierOption = AA - BB + DD + EE
End Function
ElseIf CallPutFlag = "Cui" Then
If X > H Then
UOBarrierOption = AA + EE
ElseIf X < H Then
UOBarrierOption = BB - CC + DD + EE
End Function
ElseIf CallPutFlag = "Pdi" Then
If X > H Then
UOBarrierOption = BB - CC + DD + EE
ElseIf X < H Then
UOBarrierOption = AA + EE
End Function
ElseIf CallPutFlag = "Pui" Then
If X > H Then
UOBarrierOption = AA - BB + DD + EE
ElseIf X < H Then
UOBarrierOption = CC + EE
End Function
ElseIf CallPutFlag = "Cdo" Then
If X > H Then
UOBarrierOption = AA - CC + FF
ElseIf X < H Then
UOBarrierOption = BB - DD + FF
End Function
ElseIf CallPutFlag = "Cuo" Then
If X > H Then
UOBarrierOption = F
ElseIf X < H Then
UOBarrierOption = AA - BB + CC - DD + FF
End Function
ElseIf CallPutFlag = "Pdo" Then
If X > H Then
UOBarrierOption = AA - BB + CC - DD + FF
ElseIf X < H Then
UOBarrierOption = F
End Function
ElseIf CallPutFlag = "Puo" Then
If X > H Then
UOBarrierOption = BB - DD + FF
ElseIf X < H Then
UOBarrierOption = AA - CC + FF
End Function
End If
End Function
P.S. I have different "phi"s and "eta"s for different types of option barriers (cdi, pdi and etc.). Right now I am trying different combinations, but it also gives "end if function missing" type of error
If your function returns something you must declare the type returned in the function, and assign the returned value to the function, for exmaple:
Function CalculateSquareRoot(NumberArg As Double) As Double
If NumberArg < 0 Then ' Evaluate argument.
Exit Function ' Exit to calling procedure.
Else
CalculateSquareRoot = Sqr(NumberArg) ' Return square root.
End If
End Function
See the As Double and the CalculateSquareRoot = Sqr(NumberArg). That is what the function returns.
If it does not return anything, and its just a method, you should declare it with Sub().
Sub()
'your method
End Sub

How is a local variable in another function affecting a variable in my main function?

So I have a "main" function (SolveSixODES) that calls a secondary function (AllODEs). And when it does this, the x value in the main function gets modified. I don't understand how this can be possible, seeing as it is not a global variable.
Here is the code, my inputs I used are as follows:
x=0, xmax=3, y=0-6, h=0.1, error=0.1
Public Function SolveSixODE(x As Double, xmax As Double, Y As Range, h As Double, error As Double) 'Weird bug: You must leave the first y4 value blank
Dim i As Integer, k(7, 7) As Double, j As Integer, m As Integer 'k(Order #, equation #)
Dim Y5(7) As Double, Y4(7) As Double, Y4Old(7) As Double
Dim delta0(7) As Double, delta1(7) As Double, delRatio(7) As Double, Rmin As Double
For i = 1 To 6 'Moving the input data so it can acutally be used
Y4(i) = Y(i)
Next i
While x < xmax
If x + h < xmax Then
x = x + h
Else
h = xmax - x
x = xmax
End If
For j = 1 To 6 'j is the order i is equation number
For i = 1 To 6 'Calculating all of the k(1) values for eq 1 to 6
k(j, i) = AllODES(x, Y4, i, j, k, h) '!!!!!SOME HOW THIS LOOP MAKES X negative...!!!!!!!
Next i
Next j
For i = 1 To 6
Y4Old(i) = Y4(i) 'Saving old y4 value to calc delta0
Y4(i) = Y4(i) + h * (k(1, i) * (37 / 378) + k(3, i) * (250 / 621) + k(4, i) * (125 / 594) + k(6, i) * (512 / 1771))
Y5(i) = Y4(i) + h * (k(1, i) * (2825 / 27648) + k(3, i) * (18575 / 48384) + k(4, i) * (13525 / 55296) + k(5, i) * (277 / 14336) + k(6, i) * (0.25))
delta0(i) = error * (Abs(Y4Old(i)) + Abs(h * AllODES(x, Y4Old, i, 1, k, h))) 'First order because we don't want to use the k vals
delta1(i) = Abs(Y5(i) - Y4(i))
delRatio(i) = Abs(delta0(i) / delta1(i)) 'Ratio of errors
Next i
Rmin = delRatio(1)
For i = 2 To 6
If delRatio(i) < Rmin Then
Rmin = delRatio(i) 'Determine the smallest error ratio
End If
Next i
If Rmin < 1 Then 'If this is true then the step size was too big must repeat step
x = x - h 'Set x and y's back to previous values
For i = 1 To 6
Y4(i) = Y4Old(i)
Next i
h = 0.9 * h * Rmin ^ 0.25 'adjust h value; 0.9 is a safety factor
Else
h = 0.9 * h * Rmin ^ 0.2 'Otherwise, we march on
End If
m = m + 1
Wend
SolveSixODE = Y4
End Function
Public Function AllODES(x As Double, Y() As Double, EqNumber As Integer, order As Integer, k() As Double, h As Double) As Double
Dim conc(7) As Double, i As Integer, j As Integer
If order = 1 Then
x = x - h
For i = 1 To 6 'Movin the data so I can use it
conc(i) = Y(i) 'also adjusting the x and y values for RK4 (Cash Karp values)
Next i
ElseIf order = 2 Then
x = x - h + h * 0.2
For i = 1 To 6
conc(i) = Y(i) + h * k(1, i) * 0.2
Next i
ElseIf order = 3 Then
x = x - h + 0.3 * h
For i = 1 To 6
conc(i) = Y(i) + h * (0.075 * k(1, i) + 0.225 * k(2, i))
Next i
ElseIf order = 4 Then
x = x - h + 0.6 * h
For i = 1 To 6
conc(i) = Y(i) + h * (0.3 * k(1, i) - 0.9 * k(2, i) + 1.2 * k(3, i))
Next i
ElseIf order = 5 Then
x = x - h + h
For i = 1 To 6
conc(i) = Y(i) + h * ((-11 / 54) * k(1, i) + 2.5 * k(2, i) - (70 / 27) * k(3, i) + (35 / 27) * k(4, i))
Next i
ElseIf order = 6 Then
x = x - h + 0.875 * h
For i = 1 To 6
conc(i) = Y(i) + h * ((1631 / 55296) * k(1, i) + (175 / 512) * k(2, i) + (575 / 13824) * k(3, i) + (44275 / (110592) * k(4, i) + (253 / 4096) * k(5, i)))
Next i
Else
MsgBox ("error")
End If
If EqNumber = 1 Then 'These are the actual equations
AllODES = x + Y(1)
ElseIf EqNumber = 2 Then
AllODES = x
ElseIf EqNumber = 3 Then
AllODES = Y(3)
ElseIf EqNumber = 4 Then
AllODES = 2 * x
ElseIf EqNumber = 5 Then
AllODES = 2 * Y(2)
ElseIf EqNumber = 6 Then
AllODES = 3 * x
Else
MsgBox ("You entered an Eq Number that was dumb")
End If
End Function
It's possible that it is something really trivial that I missed but this seems to contradict my knowledge of how variables work. So if you understand how the function is able to manipulate a variable from another function in this case, I would appreciate any advice and/or explanation!
Thanks in advance!
the x value in the main function gets modified. I don't understand how this can be possible, seeing as it is not a global variable
This is normal because you are passing x by reference to the function AllODES and you do change it there. When the keyword ByVal is not explicitly specified in the function/sub prototype, the default passing mechanism is ByRef, that is, by reference.
Public Function AllODES(x As Double, ...
means
Public Function AllODES(ByRef x As Double, ....
We observe that x is manipulated in this function, so the change will appear in the caller. If you want that the change of x does not report back in the caller's scope, pass x by value:
Public Function AllODES(ByVal x As Double, ....
' ^^^^^
Only in this case the x of the caller and the x of the callee will be two different variables.

VB.NET spirograph program

I'm trying to create a a program that will draw hypotrochoids (spirograph). The program below compiles fine. But when I run it I only get a portion of the drawing.. I'm not sure what I'm doing wrong. I'm fairly new to VB.. Any help is appreciated. Thanks.
Here is the screenshot http://imgur.com/a/KxFWk
Public Class Form1
Private Sub Form1_Load(sender As System.Object, e As System.EventArgs) Handles MyBase.Load
End Sub
Private Sub PictureBox1_Paint(sender As Object, e As PaintEventArgs) Handles PictureBox1.Paint
Dim x As Integer
Dim y As Integer
Dim p As Integer
Dim x1 As Integer
Dim y1 As Integer
Dim x2 As Integer
Dim y2 As Integer
x = 75
y = 15
p = 15
x1 = (x + y) * Math.Cos(0) + p * Math.Cos(0)
y1 = (x + y) * Math.Sin(0) + p * Math.Sin(0)
For t = 0 To 500 Step 0.1
x2 = (x + y) * Math.Cos(t) + p * Math.Cos((x + y) * t / y)
y2 = (x + y) * Math.Sin(t) + p * Math.Sin((x + y) * t / y)
e.Graphics.DrawLine(Pens.Blue, x1, y1, x2, y2)
x1 = x2
y1 = y2
Next
End Sub
End Class
The results of the Sin and Cos calculations result in negative numbers where the parameter is greater than 90 for cos and greater than 180 for sin.
To see the whole image, you need to change the offset for x2 and y2 - see the code below. Alter the number 200 in each of the four lines to a value appropriate for your picturebox
x1 = 200 + CInt((x + y) * Math.Cos(0) + p * Math.Cos(0))
y1 = 200 + CInt((x + y) * Math.Sin(0) + p * Math.Sin(0))
For t As Double = 0 To 500 Step 0.1
x2 = 200 + CInt((x + y) * Math.Cos(t) + p * Math.Cos((x + y) * t / y))
y2 = 200 + CInt((x + y) * Math.Sin(t) + p * Math.Sin((x + y) * t / y))
e.Graphics.DrawLine(Pens.Blue, x1, y1, x2, y2)
x1 = x2
y1 = y2
Next

VBA root finding trough bisection

My vba code keeps returning a value of 0 when I know the roots of my function are not 0.
It's pretty simple code but I can't seem to debug it. Any idea where this error might be coming from??
Option Explicit
Public Function Bisect(ByVal xlow As Double, ByVal xhigh As Double) As Double
Dim i As Integer
Dim xmid As Double
xmid = (xlow + xhigh) / 2
For i = 1 To 100
If f(xlow) * f(xmid) < 0 Then
xhigh = xmid
xmid = (xlow + xhigh) / 2
Else
xlow = xmid
xmid = (xlow + xhigh) / 2
End If
Next i
Bisect = xmid
End Function
Function f(ByVal x As Double, Optional ByRef inputArray As Range) As Variant
Dim ca0 As Double
Dim v0 As Double
Dim k As Double
Dim e As Double
Dim ac As Double
Dim L As Double
inputArray(2, 2) = ca0
inputArray(3, 2) = v0
inputArray(4, 2) = k
inputArray(5, 2) = e
inputArray(6, 2) = ac
inputArray(7, 2) = L
f(x) = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L
End Function
' i Think you want to take those constant values from cells presentin the sheet
Function f(ByVal x As Double) As Variant
Dim inputArray As Range
Dim ca0 As Double
Dim v0 As Double
Dim k As Double
Dim e As Double
Dim ac As Double
Dim L As Double
' i Think you want to take values from cells in the sheet
ca0 = ActiveSheet.Cells(2, 2).Value
v0 = ActiveSheet.Cells(3, 2).Value
k = ActiveSheet.Cells(4, 2).Value
e = ActiveSheet.Cells(5, 2).Value
ac = ActiveSheet.Cells(6, 2).Value
L = ActiveSheet.Cells(7, 2).Value
Could it be that you try to assign the inputarray with empty variables?
In my mind it should be:
ca0 = inputArray(2, 2)
v0 = inputArray(3, 2)
And so on.
I'm guessing
f(x) = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L
Should be
f = (v0 / (k * ca0 * ac)) * ((2 * e * (1 + e) * Log(1 - x)) + (e ^ 2 * x) + (((1 + e) ^ 2 * x) / (1 - x))) - L

Excel VBA compile error: Expected Sub, Function or Property

I am getting a compile error in Excel VBA which says Expected Sub, Function or Property. The function I am using is given below which is trying to copy the rate function in Excel.
Thanks for your help.
Function rate_m(nper As Double, pmt As Double, pv As Double, fv As Double, types As Double, guess As Double) As Variant
Dim y, y0, y1, x0, x1, f, i As Double
Dim FINANCIAL_MAX_ITERATIONS As Double
Dim FINANCIAL_PRECISION As Double
If IsNull(guess) Then guess = 0.01
If IsNull(fv) Then fv = 0
If IsNull(types) Then types = 0
FINANCIAL_MAX_ITERATIONS = 128 'Bet accuracy with 128
FINANCIAL_PRECISION = 0.0000001 '1.0e-8
y , y0, y1, x0, x1, f, i = 0
rate_m = guess
If Abs(rate_m) < FINANCIAL_PRECISION Then
y = pv * (1 + nper * rate_m) + pmt * (1 + rate_m * types) * nper + fv
Else
f = Exp(nper * Log(1 + rate_m))
y = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv
y0 = pv + pmt * nper + fv
y1 = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv
End If
'find root by Newton secant method
i , x0 = 0
x1 = rate_m
While Abs(y0 - y1) > FINANCIAL_PRECISION & i < FINANCIAL_MAX_ITERATIONS
rate_m = (y1 * x0 - y0 * x1) / (y1 - y0)
x0 = x1
x1 = rate_m
If Abs(rate_m) < FINANCIAL_PRECISION Then
y = pv * (1 + nper * rate_m) + pmt * (1 + rate_m * types) * nper + fv
Else
f = Exp(nper * Log(1 + rate_m))
y = pv * f + pmt * (1 / rate_m + types) * (f - 1) + fv
End If
y0 = y1
y1 = y
i = i + 1
Wend
End Function
A couple things...
First, you have to assign each variable individually...like this:
y = 0
y0 = 0
y1 = 0
x0 = 0
x1 = 0
f = 0
i = 0
Second, you probably want to DIM your variables all as Double. Unfortunately, this line:
Dim y, y0, y1, x0, x1, f, i As Double
Only declares i as a Double, all the others will be a Variant. You need to declare each one individually, like this:
Dim y As Double
Dim y0 As Double
Dim y1 As Double
Dim x0 As Double
Dim x1 As Double
Dim f As Double
Dim i As Double
Every IF ends with a End If (unless in a single line) and While...loop. You might want to take a look at VBA's syntax:
http://msdn.microsoft.com/en-us/library/office/ee814737(v=office.14).aspx
EDIT: You have to declare variable individually, instead of:
y , y0, y1, x0, x1, f, i = 0
you could do:
y = 0
y0 = 0
y1 = 0
x0 = 0
x1 = 0
f = 0
i = 0