Using VBA to iteratively solve a system of equations - vba

I'm trying to solve the following system of equations in VBA:
I have looked for other similar questions and I can't find any with suitable solutions. I have already solved the equations in a worksheet by simply entering the equations as formulae is separate cells (which will initially create a circular reference warning) and the enabling iterative calculation) - given that I know DR will always be greater than PR, I thought an iterative approach of incrementally reducing PR from an initial value of DR would work in VBA. For reference, the worksheet used is shown below:
The formula in G9 is =(G8*B6)/(G10+273.15), i.e. equation 2 (see above), and the formula in G10 is =(B6+(B6*(-1+((G9*(B3-B35))/(B3-B35))^0.263)/B34))-B33*(B6+(B6*(-1+((G9*(B3-B35))/(B3-B35))^0.263)/B34)), i.e. equation 1 (see above).
When I try to do this programmatically in VBA by reducing PR incrementally from an initial value of DR, it doesn't work. My code is below:
Sub ChargeTempAndPressureCalculations()
Dim AP_hPa As Double
Dim AP_psi As Double
Dim TIn_C As Double
Dim TIn_K As Double
Dim PR As Double
Dim Ei As Double
Dim Et As Double
Dim Vci As Double
Dim DR As Double
Dim TOut_C As Double
AP_hPa = 1029 'Input
AP_psi = AP_hPa * 100 * 0.000145038
TIn_C = 15 'Input
TIn_K = TIn_C + 273.15
Et = 0.75 'Input
Ei = 0.75 'Input
Vci = 0.5 'Input
DR = 2.7103502887329 'Input
PR = DR
Do Until TOut_C = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et)) - Ei * (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et)) And PR = (DR * TIn_K) / (TOut_C + 273)
PR = PR - 0.00000000001
Loop
Debug.Print "Charge air temperature = " & TOut_C
Debug.Print "Pressure Ratio = " & PR
End Sub
It's clearly the loop that's the issue but what is it that I'm doing wrong?
Edit:
I've split equation 1 to prevent the 'Equation too complex' error observed by another user, mentioned in the comments. I've also added a control to prevent the number of steps going above 1000.
Sub ChargeTempAndPressureCalculations()
Dim AP_hPa As Double
Dim AP_psi As Double
Dim TIn_C As Double
Dim TIn_K As Double
Dim PR As Double
Dim Ei As Double
Dim Et As Double
Dim Vci As Double
Dim DR As Double
Dim TOut_C As Double
Dim A As Double
Dim B As Double
Dim i As Integer
AP_hPa = 1029 'Input
AP_psi = AP_hPa * 100 * 0.000145038
TIn_C = 15 'Input
TIn_K = TIn_C + 273.15
Et = 0.75 'Input
Ei = 0.75 'Input
Vci = 0.5 'Input
DR = 2.7103502887329 'Input
PR = DR
Do Until i > 1000 Or (TOut_C = A - Ei * B And PR = (DR * TIn_K) / (TOut_C + 273))
'Spliting equation for TOut_C to simplify the expression and prevent an error
A = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et))
B = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et))
PR = PR - 0.00000000001
i = i + 1
Loop
Debug.Print "Charge air temperature = " & TOut_C
Debug.Print "Pressure Ratio = " & PR
End Sub
Having read the answer provided, I'm still none the wiser as to how to resolve my issue.

I've gone over your calculation a bit more and I presume you want to stop if the increment of PR and TOut_C is very marginal...
The below code does exactly that. It calculates TOut_C at a given PR, it subsequently calculates the PR corresponding to that TOut_C, the loop then substitutes the newly calculated PR into the TOut_C calculation and so on.
It calculates the difference between the substitute calculation and the previous calculation and if there is no longer a 'large' offset between the two it stops the loop.
Sub ChargeTempAndPressureCalculations()
Dim AP_hPa As Double
Dim AP_psi As Double
Dim TIn_C As Double
Dim TIn_K As Double
Dim PR As Double
Dim Ei As Double
Dim Et As Double
Dim Vci As Double
Dim DR As Double
Dim TOut_C As Double
AP_hPa = 1029 'Input
AP_psi = AP_hPa * 100 * 0.000145038
TIn_C = 15 'Input
TIn_K = TIn_C + 273.15
Et = 0.75 'Input
Ei = 0.75 'Input
Vci = 0.5 'Input
DR = 2.7103502887329 'Input
PR = DR
dTOut_C = 1 'Set to arbitrary number to initialize the loop
dPR = 1 'Set to arbitrary number to initialize the loop
Do Until dPR < 0.0000000001 And dTOut_C < 0.0000000001
'Calculate the TOut_C and PR
TOut_C = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et)) - Ei * (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et))
PR = (DR * TIn_K) / (TOut_C + 273)
'Calculate difference relative to last calculation
dPR = PR - PR0
dTOut_C = TOut_C - TOut_C0
'Set the last calculation as previous calculation and re-do loop
PR0 = PR
TOut_C0 = TOut_C
Loop
Debug.Print "Charge air temperature = " & TOut_C
Debug.Print "Pressure Ratio = " & PR
End Sub
Given your initial input the output is:
Charge air temperature = 93.2076926574912
Pressure Ratio = 2.13263525413933
Was that what you were looking for ?
PS: Technically what you should do is mathematically rewrite the equation to solve for PR based on DR as that is in essence what you are doing...

Note that in:
Do Until TOut_C = ...
the = sign is a comparisson and not an assignment. As TOut_C has not been used yet, it is set to zero by VB and so you are comparing whether the right hand side is zero. This does not seem to be your intention, as you use TOut_C in the AND part as TOut_C + 273 which then would always be 273.
But if you want to compare with zero, then note that the RHS will probably never become zero in floating point arithmetic and you must compare with an "epsilon", a small value that is your precission threshold. For example:
Private Const eps = 0.00000000001 ' must be smaller than your step size
Do Until Abs(TOut_C - RHS) < eps
I leave fixing this to you. (I also get an error "Expression too complex" on my Excel version.)

There is primarily two problems...
In your Do..Loop you want the code to break on TOut_C = <something based on PR> AND PR = <something based on TOut_C>
Both equation however lead to a double datatype, this is almost imposibble to have that be an = comparison as the likelyhood of hitting that equal point are virtualy zero (as I explained here)
So, you would want to set a more flexible parameter such as TOut_C > 63 for example.
The second thing that I can find is that the equations are just that, equations. So they'll produce a number but as that number is the result of the equation, but what would define the endpoint ? TOut_C is never defined and subsequently compared as indicated, your code starts with PR being 2.71... but Tout_C is 0.
So could you elaborate more on the correlation between TOut_C and PR and what would be the solution values for either or both of them ? Or are you trying to solve a balance point i.e. for a which TOut_C do both functions achieve the same result ? (That would require mathematically re-writing either function to express the same output) So rewrite the function for PR to produce Tout_C...
The loop works (i.e. it loops) if you write it like so...
It doesn't solve it, but at least you know it loops...
Do Until TOut_C = 15 And PR = 22
TOut_C = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et)) - Ei * (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et))
PR = (DR * TIn_K) / (TOut_C + 273)
PR = PR - 0.00000000001
Loop

See http://www.decisionmodels.com/calcsecretsc.htm on how Excel processes circular references. Basically, it just calculates each cell, ignoring the circular reference and then it updates the values in each iteration.
Applying this to your VBA routine produces the following sub routine:
Sub ChargeTempAndPressureCalculations()
' input variables
Dim AP_hPa As Double
Dim AP_psi As Double
Dim TIn_C As Double
Dim TIn_K As Double
Dim Ei As Double
Dim Et As Double
Dim Vci As Double
Dim DR As Double
' temporary variables
Dim Td As Double
Dim Pd As Double
Dim A As Double
' output variables
Dim TOut_C As Double
Dim PR As Double
' iteration control
Dim eps As Double
Dim i As Integer
AP_hPa = 1029
AP_psi = AP_hPa * 100 * 0.000145038
TIn_C = 15
TIn_K = TIn_C + 273.15
Et = 0.75
Ei = 0.75
Vci = 0.5
DR = 2.7103502887329
PR = DR
eps = 0.00000000001
i = 0
Do
Td = TOut_C ' remember values from previous iteration ( 'd' means 'delta')
Pd = PR
A = (TIn_K + (TIn_K * (-1 + ((PR * (AP_psi - Vci)) / (AP_psi - Vci)) ^ 0.263) / Et))
TOut_C = A - Ei * A
PR = (DR * TIn_K) / (TOut_C + 273)
i = i + 1
Debug.Print TOut_C & ", " & PR & "(" & Abs(Td - TOut_C) & ", " & Abs(Pd - PR) & ")" ' show progression
' loop until the difference is less than eps or max iterations reached
Loop While (i < 100 And (Abs(Td - TOut_C) > eps And Abs(Pd - PR) > eps))
Debug.Print "Charge air temperature = " & TOut_C
Debug.Print "Pressure Ratio = " & PR
Debug.Print "number of iterations: " & i
End Sub
Output:
100.835921416446, 2.08911822261291(100.835921416446, 0.621232066119993)
92.5738330344343, 2.13633297880164(8.26208838201211, 4.72147561887288E-02)
93.2611120407512, 2.13232420812257(0.687279006316899, 4.00877067907057E-03)
93.2031960023579, 2.13266144103602(5.79160383933299E-02, 3.37232913455665E-04)
93.2080712078647, 2.13263304962791(4.87520550686327E-03, 2.83914081067316E-05)
93.2076607895546, 2.13263543972443(4.10418310181626E-04, 2.39009651137323E-06)
93.2076953402811, 2.13263523851592(3.45507265677725E-05, 2.01208508077144E-07)
93.2076924316548, 2.1326352554545(2.90862628560262E-06, 1.6938581648418E-08)
93.2076926765153, 2.13263525402854(2.44860444809092E-07, 1.42596112695514E-09)
93.2076926559019, 2.13263525414858(2.06133563551703E-08, 1.200430865822E-10)
93.2076926576372, 2.13263525413848(1.73530168012803E-09, 1.01052499701382E-11)
93.2076926574912, 2.13263525413933(1.46073375617561E-10, 8.5043083686287E-13)
Charge air temperature = 93.2076926574912
Pressure Ratio = 2.13263525413933
number of iterations: 12

Related

Using VBA to open Chrome and fill out a form

I constantly use the website below to track air miles round trip. Recently, the website stopped working in IE, so my code did as well. Since I use this on a work computer, I cannot download many of the other solutions that I have found in my searches and I cannot use another website without going through a lengthy process to get the site approved. Is there a way to perform the same task here in Chrome without any other downloads?
Dim ele As Object
Dim IE As New InternetExplorer
IE.Visible = True
IE.navigate "http://www.distancefromto.net"
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
'step 1
With IE
.document.getElementsByName("distance")(0).Value = Range("B2").Value
.document.getElementsByName("distance")(1).Value = Range("B3").Value & Range("E3").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim a As String
a = Trim(.document.getElementById("totaldistancemiles").Value)
Dim aa As Variant
aa = Split(a, " ")
Range("C2").Value = aa(0)
'step 2
.document.getElementsByName("distance")(0).Value = Range("B4").Value & Range("E4").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim b As String
b = Trim(.document.getElementById("totaldistancemiles").Value)
Dim bb As Variant
bb = Split(b, " ")
Range("C3").Value = bb(0)
'step 3
.document.getElementsByName("distance")(1).Value = Range("B2").Value
.document.getElementById("hae").Click
Do
DoEvents
Loop Until IE.readyState = READYSTATE_COMPLETE
Application.Wait (Now + TimeValue("0:00:02"))
Dim c As String
c = Trim(.document.getElementById("totaldistancemiles").Value)
Dim cc As Variant
cc = Split(c, " ")
Range("C4").Value = cc(0)
End With
IE.Quit
Any help, even a definitive "no, it's not possible" would be greatly appriciated
Thanks
Chrome:
To use Chrome - no. You would need to download selenium basic or use a different programming language e.g python.
Different site:
You could switch to using a different site (appreciate there may some minor differences on your prior figures due to the website though technically the distances shouldn’t have changed that much!). I note you say that this would be problematic. At the risk of sounding stalkerish, you have used freemaptools before so that might be an acceptable choice?
API:
If you find a site offering an API service you might be able to ditch all the above and issue an XMLHTTP request. I couldn't see your site offering an API service otherwise that would have been the obvious next choice.
#RahulChalwa mentions "[the site OP is using is itself using a wrapper around google maps API: https://maps.googleapis.com/maps/api/js/GeocodeService.Search. User can register for API and do a POST request]"; so that might be the way forward. Main documentation here.
E.g. API site: Personal and small scale use API - wheretocredit.com
Current set-up debug:
Ascertain the reason for IE no longer working in your current set-up would also be advisable, perhaps by contacting the site developers and raising your issue.
Perform the calculation (as the site does) using the Vincenty's formula or, as other sites do, using Haversine formula:
Haversine:
VBA haversine formula
Vicenty's (including sample code):
How to Calculate Distance in Excel
Vicenty's code from Contextures. I have attributed but if this should not be included here I will remove.
'*************************************************************
Private Const PI = 3.14159265358979
Private Const EPSILON As Double = 0.000000000001
Public Function distVincenty(ByVal lat1 As Double, ByVal lon1 As Double, _
ByVal lat2 As Double, ByVal lon2 As Double) As Double
'INPUTS: Latitude and Longitude of initial and
' destination points in decimal format.
'OUTPUT: Distance between the two points in Meters.
'
'======================================
' Calculate geodesic distance (in m) between two points specified by
' latitude/longitude (in numeric [decimal] degrees)
' using Vincenty inverse formula for ellipsoids
'======================================
' Code has been ported by lost_species from www.aliencoffee.co.uk to VBA
' from javascript published at:
' https://www.movable-type.co.uk/scripts/latlong-vincenty.html
' * from: Vincenty inverse formula - T Vincenty, "Direct and Inverse Solutions
' * of Geodesics on the Ellipsoid with application
' * of nested equations", Survey Review, vol XXII no 176, 1975
' * https://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf
'Additional Reference: https://en.wikipedia.org/wiki/Vincenty%27s_formulae
'======================================
' Copyright lost_species 2008 LGPL
' https://www.fsf.org/licensing/licenses/lgpl.html
'======================================
' Code modifications to prevent "Formula Too Complex" errors
' in Excel (2010) VBA implementation
' provided by Jerry Latham, Microsoft MVP Excel Group, 2005-2011
' July 23 2011
'======================================
Dim low_a As Double
Dim low_b As Double
Dim f As Double
Dim L As Double
Dim U1 As Double
Dim U2 As Double
Dim sinU1 As Double
Dim sinU2 As Double
Dim cosU1 As Double
Dim cosU2 As Double
Dim lambda As Double
Dim lambdaP As Double
Dim iterLimit As Integer
Dim sinLambda As Double
Dim cosLambda As Double
Dim sinSigma As Double
Dim cosSigma As Double
Dim sigma As Double
Dim sinAlpha As Double
Dim cosSqAlpha As Double
Dim cos2SigmaM As Double
Dim C As Double
Dim uSq As Double
Dim upper_A As Double
Dim upper_B As Double
Dim deltaSigma As Double
Dim s As Double ' final result, will be returned rounded to 3 decimals (mm).
'added by JLatham to break up "Too Complex" formulas
'into pieces to properly calculate those formulas as noted below
'and to prevent overflow errors when using
'Excel 2010 x64 on Windows 7 x64 systems
Dim P1 As Double ' used to calculate a portion of a complex formula
Dim P2 As Double ' used to calculate a portion of a complex formula
Dim P3 As Double ' used to calculate a portion of a complex formula
'See https://en.wikipedia.org/wiki/World_Geodetic_System
'for information on various Ellipsoid parameters for other standards.
'low_a and low_b in meters
' === GRS-80 ===
' low_a = 6378137
' low_b = 6356752.314245
' f = 1 / 298.257223563
'
' === Airy 1830 === Reported best accuracy for England and Northern Europe.
' low_a = 6377563.396
' low_b = 6356256.910
' f = 1 / 299.3249646
'
' === International 1924 ===
' low_a = 6378388
' low_b = 6356911.946
' f = 1 / 297
'
' === Clarke Model 1880 ===
' low_a = 6378249.145
' low_b = 6356514.86955
' f = 1 / 293.465
'
' === GRS-67 ===
' low_a = 6378160
' low_b = 6356774.719
' f = 1 / 298.247167
'=== WGS-84 Ellipsoid Parameters ===
low_a = 6378137 ' +/- 2m
low_b = 6356752.3142
f = 1 / 298.257223563
'====================================
L = toRad(lon2 - lon1)
U1 = Atn((1 - f) * Tan(toRad(lat1)))
U2 = Atn((1 - f) * Tan(toRad(lat2)))
sinU1 = Sin(U1)
cosU1 = Cos(U1)
sinU2 = Sin(U2)
cosU2 = Cos(U2)
lambda = L
lambdaP = 2 * PI
iterLimit = 100 ' can be set as low as 20 if desired.
While (Abs(lambda - lambdaP) > EPSILON) And (iterLimit > 0)
iterLimit = iterLimit - 1
sinLambda = Sin(lambda)
cosLambda = Cos(lambda)
sinSigma = Sqr(((cosU2 * sinLambda) ^ 2) + _
((cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ^ 2))
If sinSigma = 0 Then
distVincenty = 0 'co-incident points
Exit Function
End If
cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda
sigma = Atan2(cosSigma, sinSigma)
sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma
cosSqAlpha = 1 - sinAlpha * sinAlpha
If cosSqAlpha = 0 Then 'check for a divide by zero
cos2SigmaM = 0 '2 points on the equator
Else
cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha
End If
C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha))
lambdaP = lambda
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate lambda
'lambda = L + (1 - C) * f * sinAlpha * _
(sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * _
(-1 + 2 * (cos2SigmaM ^ 2))))
'calculate portions
P1 = -1 + 2 * (cos2SigmaM ^ 2)
P2 = (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * P1))
'complete the calculation
lambda = L + (1 - C) * f * sinAlpha * P2
Wend
If iterLimit < 1 Then
MsgBox "iteration limit has been reached, something didn't work."
Exit Function
End If
uSq = cosSqAlpha * (low_a ^ 2 - low_b ^ 2) / (low_b ^ 2)
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate upper_A
'upper_A = 1 + uSq / 16384 * (4096 + uSq * _
(-768 + uSq * (320 - 175 * uSq)))
'calculate one piece of the equation
P1 = (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)))
'complete the calculation
upper_A = 1 + uSq / 16384 * P1
'oddly enough, upper_B calculates without any issues - JLatham
upper_B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)))
'the original calculation is "Too Complex" for Excel VBA to deal with
'so it is broken into segments to calculate without that issue
'the original implementation to calculate deltaSigma
'deltaSigma = upper_B * sinSigma * (cos2SigmaM + upper_B / 4 * _
(cosSigma * (-1 + 2 * cos2SigmaM ^ 2) _
- upper_B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ^ 2) * _
(-3 + 4 * cos2SigmaM ^ 2)))
'calculate pieces of the deltaSigma formula
'broken into 3 pieces to prevent overflow error that may occur in
'Excel 2010 64-bit version.
P1 = (-3 + 4 * sinSigma ^ 2) * (-3 + 4 * cos2SigmaM ^ 2)
P2 = upper_B * sinSigma
P3 = (cos2SigmaM + upper_B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ^ 2) _
- upper_B / 6 * cos2SigmaM * P1))
'complete the deltaSigma calculation
deltaSigma = P2 * P3
'calculate the distance
s = low_b * upper_A * (sigma - deltaSigma)
'round distance to millimeters
distVincenty = Round(s, 3)
End Function
Function SignIt(Degree_Dec As String) As Double
'Input: a string representation of a lat or long in the
' format of 10° 27' 36" S/N or 10~ 27' 36" E/W
'OUTPUT: signed decimal value ready to convert to radians
'
Dim decimalValue As Double
Dim tempString As String
tempString = UCase(Trim(Degree_Dec))
decimalValue = Convert_Decimal(tempString)
If Right(tempString, 1) = "S" Or Right(tempString, 1) = "W" Then
decimalValue = decimalValue * -1
End If
SignIt = decimalValue
End Function
Function Convert_Degree(Decimal_Deg) As Variant
'source: https://support.microsoft.com/kb/213449
'
'converts a decimal degree representation to deg min sec
'as 10.46 returns 10° 27' 36"
'
Dim degrees As Variant
Dim minutes As Variant
Dim seconds As Variant
With Application
'Set degree to Integer of Argument Passed
degrees = Int(Decimal_Deg)
'Set minutes to 60 times the number to the right
'of the decimal for the variable Decimal_Deg
minutes = (Decimal_Deg - degrees) * 60
'Set seconds to 60 times the number to the right of the
'decimal for the variable Minute
seconds = Format(((minutes - Int(minutes)) * 60), "0")
'Returns the Result of degree conversion
'(for example, 10.46 = 10° 27' 36")
Convert_Degree = " " & degrees & "° " & Int(minutes) & "' " _
& seconds + Chr(34)
End With
End Function
Function Convert_Decimal(Degree_Deg As String) As Double
'source: https://support.microsoft.com/kb/213449
' Declare the variables to be double precision floating-point.
' Converts text angular entry to decimal equivalent, as:
' 10° 27' 36" returns 10.46
' alternative to ° is permitted: Use ~ instead, as:
' 10~ 27' 36" also returns 10.46
Dim degrees As Double
Dim minutes As Double
Dim seconds As Double
'
'modification by JLatham
'allow the user to use the ~ symbol instead of ° to denote degrees
'since ~ is available from the keyboard and ° has to be entered
'through [Alt] [0] [1] [7] [6] on the number pad.
Degree_Deg = Replace(Degree_Deg, "~", "°")
' Set degree to value before "°" of Argument Passed.
degrees = Val(Left(Degree_Deg, InStr(1, Degree_Deg, "°") - 1))
' Set minutes to the value between the "°" and the "'"
' of the text string for the variable Degree_Deg divided by
' 60. The Val function converts the text string to a number.
minutes = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "°") + 2, _
InStr(1, Degree_Deg, "'") - InStr(1, Degree_Deg, "°") - 2)) / 60
' Set seconds to the number to the right of "'" that is
' converted to a value and then divided by 3600.
seconds = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "'") + _
2, Len(Degree_Deg) - InStr(1, Degree_Deg, "'") - 2)) / 3600
Convert_Decimal = degrees + minutes + seconds
End Function
Private Function toRad(ByVal degrees As Double) As Double
toRad = degrees * (PI / 180)
End Function
Private Function Atan2(ByVal X As Double, ByVal Y As Double) As Double
' code nicked from:
' https://en.wikibooks.org/wiki/Programming:Visual_Basic_Classic
' /Simple_Arithmetic#Trigonometrical_Functions
' If you re-use this watch out: the x and y have been reversed from typical use.
If Y > 0 Then
If X >= Y Then
Atan2 = Atn(Y / X)
ElseIf X <= -Y Then
Atan2 = Atn(Y / X) + PI
Else
Atan2 = PI / 2 - Atn(X / Y)
End If
Else
If X >= -Y Then
Atan2 = Atn(Y / X)
ElseIf X <= Y Then
Atan2 = Atn(Y / X) - PI
Else
Atan2 = -Atn(X / Y) - PI / 2
End If
End If
End Function
'======================================

Calculating distance in kilometers between coordinates

I'm trying to calculate distance in kilometers between two geographical coordinates using the haversine formula.
Code:
Dim dbl_dLat As Double
Dim dbl_dLon As Double
Dim dbl_a As Double
dbl_P = WorksheetFunction.Pi / 180
dbl_dLat = dbl_P * (dbl_Latitude2 - dbl_Latitude1)
dbl_dLon = dbl_P * (dbl_Longitude2 - dbl_Longitude1)
dbl_a = Sin(dbl_dLat / 2) * Sin(dbl_dLat / 2) + Cos(dbl_Latitude1 * dbl_P) * Cos(dbl_Latitude2 * dbl_P) * Sin(dbl_dLon / 2) * Sin(dbl_dLon / 2)
dbl_Distance_KM = 6371 * 2 * WorksheetFunction.Atan2(Sqr(dbl_a), Sqr(1 - dbl_a))
I'm testing with these coordinates:
dbl_Longitude1 = 55.629178
dbl_Longitude2 = 29.846686
dbl_Latitude1 = 37.659466
dbl_Latitude2 = 30.24441
And the code returns 20015.09, which is obviously wrong. It should be 642 km according to Yandex maps.
Where am I wrong? Are the longitude and latitude in wrong format?
As far as I can tell, the issue is that the order of arguments to atan2() varies by language. The following works* for me:
Option Explicit
Public Sub Distance()
Dim dbl_Longitude1 As Double, dbl_Longitude2 As Double, dbl_Latitude1 As Double, dbl_Latitude2 As Double
dbl_Longitude1 = 55.629178
dbl_Longitude2 = 29.846686
dbl_Latitude1 = 37.659466
dbl_Latitude2 = 30.24441
Dim dbl_dLat As Double
Dim dbl_dLon As Double
Dim dbl_a As Double
Dim dbl_P As Double
dbl_P = WorksheetFunction.Pi / 180
dbl_dLat = dbl_P * (dbl_Latitude2 - dbl_Latitude1) 'to radians
dbl_dLon = dbl_P * (dbl_Longitude2 - dbl_Longitude1) 'to radians
dbl_a = Sin(dbl_dLat / 2) * Sin(dbl_dLat / 2) + _
Cos(dbl_Latitude1 * dbl_P) * Cos(dbl_Latitude2 * dbl_P) * Sin(dbl_dLon / 2) * Sin(dbl_dLon / 2)
Dim c As Double
Dim dbl_Distance_KM As Double
c = 2 * WorksheetFunction.Atan2(Sqr(1 - dbl_a), Sqr(dbl_a)) ' *** swapped arguments to Atan2
dbl_Distance_KM = 6371 * c
Debug.Print dbl_Distance_KM
End Sub
*Output: 2507.26205401321, although gcmap.com says the answer is 2512 km. This might be a precision issue --- I think it's close enough to count as working. (Edit it might also be that gcmap uses local earth radii rather than the mean radius; I am not sure.)
Explanation
I found this description of the haversine formula for great-circle distance, which is what you are implementing. The JavaScript implementation on that page gives this computation for c:
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
In JavaScript, atan2() takes parameters y, x. However, in Excel VBA, WorksheetFunction.Atan2 takes parameters x, y. Your original code passed Sqr(dbl_a) as the first parameter, as it would be in JavaScript. However, Sqr(dbl_a) needs to be the second parameter in Excel VBA.
A comment on naming
Building on #JohnColeman's point, there are lots of ways to name variables. In this case, I would recommend using the prefixes for unit rather than for type: e.g., deg_Latitude1, RadPerDeg = Pi/180, and rad_dLat = RadPerDeg * (deg_Latitude2 - deg_Latitude1). I personally think that helps avoid unit-conversion mishaps.
My VBA code that returns the answer in feet; However 'd' is the answer in kilometers.
Imports System.Math
Module Haversine
Public Function GlobalAddressDistance(sLat1 As String, sLon1 As String, sLat2 As String, sLon2 As String) As String
Const R As Integer = 6371
Const cMetersToFeet As Single = 3.2808399
Const cKiloMetersToMeters As Integer = 1000
Dim a As Double = 0, c As Double = 0, d As Double = 0
'Convert strings to numberic double values
Dim dLat1 As Double = Val(sLat1)
Dim dLat2 As Double = Val(sLat2)
Dim dLatDiff As Double = DegreesToRadians(CDbl(sLat2) - CDbl(sLat1))
Dim dLonDiff As Double = DegreesToRadians(CDbl(sLon2) - CDbl(sLon1))
a = Pow(Sin(dLatDiff / 2), 2) + Cos(DegreesToRadians(dLat1)) * Cos(DegreesToRadians(dLat2)) * Pow(Sin(dLonDiff / 2), 2)
c = 2 * Atan2(Sqrt(a), Sqrt(1 - a))
d = R * c
'Convert kilometers to feet
Return Format((d * cKiloMetersToMeters * cMetersToFeet), "0.##").ToString
End Function
Private Function DegreesToRadians(ByVal dDegrees As Double) As Double
Return (dDegrees * PI) / 180
End Function
End Module

Type mismatch error when generating random numbers

When I generate random numbers, I sometimes get (not always) the following error:
Run-time error '13': type mismatch.
on line Z = Sqr(time) * Application.NormSInv(Rnd()) (and the end of the second for loop).
Why do I get this error?
I think it has something to do with the fact that it contains Rnd().
Sub asiancall()
'defining variables
Dim spot As Double
Dim phi As Integer
Dim rd_cont As Double
Dim rf_cont As Double
Dim lambda As Double
Dim muY As Double
Dim sigmaY As Double
Dim vol As Double
Dim implied_vol As Double
Dim spotnext As Double
Dim time As Double
Dim sum As Double
Dim i As Long
Dim mean As Double
Dim payoff_mean As Double
Dim StDev As Double
Dim K As Double
Dim Egamma0 As Double
Dim mulTv As Double
Dim prod As Double
Dim U As Double
Dim Pois As Double
Dim Q As Double
Dim Z As Long
Dim gamma As Double
Dim payoff As Double
Dim payoff_sum As Double
Dim secondmoment As Double
Dim j As Long
Dim N As Long
Dim mu As Double
Dim sum1 As Double
'read input data
spot = Range("B3")
rd_cont = Range("C5")
rf_cont = Range("C4")
muY = Range("B17")
sigmaY = Range("B18")
lambda = Range("B16")
K = Range("F33")
implied_vol = Range("F35")
N = Range("F34")
vol = Range("B6")
'calculations
sum_BS = 0
payoff_BS = 0
mean_BS = 0
secondmoment_BS = 0
For j = 1 To N
spotnext = spot
spotnext_BS = spot
time = 0
sum1 = 0
time = 184 / (360 * 6)
For i = 1 To 6
' 'Merton uitvoeren
Egamma0 = Exp(muY + sigmaY * sigmaY * 0.5) - 1
mu = rd_cont - rf_cont
mulTv = (mu - lambda * Egamma0 - implied_vol * implied_vol * 0.5) * time
sum = 0
prod = 1
Do While sum <= time
U = Rnd()
Pois = -Log(U) / lambda
sum = sum + Pois
Q = Application.NormInv(Rnd(), muY, sigmaY)
gamma = Exp(Q) - 1
prod = prod * (1 + gamma)
Loop
prod = prod / (1 + gamma)
Z = Sqr(time) * Application.NormSInv(Rnd())
spotnext = spotnext * Exp(mulTv + implied_vol * Z) * prod
sum1 = sum1 + spotnext
Next i
mean = sum1 / 6
payoff = Application.Max(mean - K, 0)
payoff_sum = payoff_sum + payoff
secondmoment = secondmoment + payoff * payoff
Next j
Following up on the community wiki answer I posted, a possible solution is this:
Function RndExcludingZero()
Do
RndExcludingZero = Rnd()
Loop While RndExcludingZero = 0
End Function
Usage:
Z = Sqr(time) * Application.NormSInv(RndExcludingZero())
Rnd() returns values >=0 and <1.
At some point it is bound to return 0. When given 0 as input in Excel, NormSInv returns the #NUM!
Excel error.* When called in VBA via Application.NormSInv(0), it returns a Variant of subtype Error with value "Error 2036" (equivalent to the #NUM! Excel error).
Such Variant/Errors cannot be implicitly coerced to a numerical value (which is what the * operator expects) and thus in this case, you will get the type mismatch error.
You will only get this error when Rnd() happens to return 0, which is consistent with your observation that the error occurs only sometimes.
* This was first remarked by user3964075 in a now defunct comment to the question.

VB.Net issue with double data range while performing a linear regression

I am performing linear regression using this data in VB.Net
1411478155,71.9700012207031
1411478150,72.9700012207031
1411478145,73.9700012207031
1411478140,74.9700012207031
1411478135,76.9700012207031
1411478130,78.9700012207031
1411478125,80.9700012207031
1411478120,81.9700012207031
1411478115,82.9700012207031
1411478110,84.9700012207031
1411478105,85.9700012207031
1411478100,88.9700012207031
The formula that I am using is this,
where x = UTC Seconds, y = Values
In the denominator, I am getting a zero value because both expressions in the denominator equal to a value of 2.8688695263517E+20.
I defined my series as,
Dim xs(12) As [Double]
Dim ys(12) As [Double]
I am not sure if the square brackets matter.
For now, I am not able to get results due to zero denominator. What data type should I use?
I expect more rows of data in future.
Edit:
Given below is the sub
`
Public Sub GetLinearRegressionParams(ByVal xs() As Double, ByVal ys() As Double, ByRef a As Double, ByRef b As Double)
Dim sumX As Double = 0
Dim sumY As Double = 0
Dim sumXY As Double = 0
Dim sumX2 As Double = 0
Dim n As Integer
n = 0
For index = 0 To xs.Length - 1
If xs(index) = Nothing Then
Else
sumX = sumX + xs(index)
sumY = sumY + ys(index)
sumXY = sumXY + xs(index) * ys(index)
sumX2 = sumX2 + xs(index) * xs(index)
n = n + 1
End If
Next
a = (sumY * sumX2 - sumX * sumXY) / (n * sumX2 - sumX * sumX)
b = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX)
End Sub
`

Ignore overflow error when multiplication result is bigger than what a double can hold

During some iterative optimization, the following VBA code for the computation of the bivariate normal CDF sometimes throws an Overflow error on the line with z = hx * hy * c inside the while loop of the upper function.
I debugged the code and the overflow occurs when the numbers being multiplied result in a number bigger than what a double can hold.
Can you show me how to handle the problem by ignoring the iterations of the loop with such high values - I guess that's the only feasible solution (?). I tried myself with a On Error Goto nextiteration line before the multiplication and placing the nextiteration jump point before the Wend, but the error persists.
Function tetrachoric(x As Double, y As Double, rho As Double) As Double
Const FACCURACY As Double = 0.0000000000001
Const MinStopK As Integer = 20
Dim k As Integer
Dim c As Double
Dim z As Double
Dim s As Double
Dim hx As Double
Dim hx1 As Double
Dim hx2 As Double
Dim hy As Double
Dim hy1 As Double
Dim hy2 As Double
Dim CheckPass As Integer
hx = 1
hy = 1
hx1 = 0
hy1 = 0
k = 0
c = rho
z = c
s = z
CheckPass = 0
While CheckPass < MinStopK
k = k + 1
hx2 = hx1
hy2 = hy1
hx1 = hx
hy1 = hy
hx = x * hx1 - (k - 1) * hx2
hy = y * hy1 - (k - 1) * hy2
c = c * rho / (k + 1)
z = hx * hy * c
s = s + z
If Abs(z / s) < FACCURACY Then
CheckPass = CheckPass + 1
Else
CheckPass = 0
End If
Wend
tetrachoric = s
End Function
Public Function bivnor(x As Double, y As Double, rho As Double) As Double
'
' bivnor function
' Calculates bivariat normal CDF F(x,y,rho) for a pair of standard normal
' random variables with correlation RHO
'
If rho = 0 Then
bivnor = Application.WorksheetFunction.NormSDist(x) * _
Application.WorksheetFunction.NormSDist(y)
Else
bivnor = Application.WorksheetFunction.NormSDist(x) * _
Application.WorksheetFunction.NormSDist(y) + _
Application.WorksheetFunction.NormDist(x, 0, 1, False) * _
Application.WorksheetFunction.NormDist(y, 0, 1, False) * _
tetrachoric(x, y, rho)
End If
End Function
Source: Available for download at http://michael.marginalq.com/
you're hitting on the limits of the computer architecture. Many complex algorithms can't be implemented 1:1 with their mathematical representation because of performance reasons and/or erroneous behavior when overflowing. There's an exceptionally good blog about these issues - John D. Cook.
Please take a look here for a better implementation.
You can also try binding an external library, that gives you arbitrary precision number handling, of course implemented using very expensive (in terms of CPU time) software algorithms. More can be found here.
Updated code using On Error Resume Next instead of On Error Goto:
While CheckPass < MinStopK
k = k + 1
hx2 = hx1
hy2 = hy1
hx1 = hx
hy1 = hy
hx = x * hx1 - (k - 1) * hx2
hy = y * hy1 - (k - 1) * hy2
c = c * rho / (k + 1)
On Error Resume Next
z = hx * hy * c
If Err.Number = 0 Then
s = s + z
If Abs(z / s) < FACCURACY Then
CheckPass = CheckPass + 1
Else
CheckPass = 0
End If
Else
Err.Clear
End If
Wend
http://www.codeproject.com/KB/recipes/float_point.aspx treats how to "Use Logarithms to Avoid Overflow and Underflow", which is a simple but quite effective way of working around overflow problems. In fact, it's so simple yet logical, why haven't we thought of that solution ourselves? ;)