Using Goal Seek - vba

I'm taking apart a very old spreadsheet I wrote and trying to put it back together using VBA. So far I've this, which seems to work:
Sub PipeData()
Dim FlowRate As Single
Dim Density As Single
Dim DynamicViscosity As Single
Dim PipeSize As Single
Dim Pi As Single
Dim ReynoldsNumber As Single
Dim Lamda As Single
Dim EquivalentRoughness As Single
Dim RelativeRoughness As Single
Dim Velocity As Single
Dim PressureDrop As Single
Density = 977.8
DynamicViscosity = 0.0004
PipeSize = 36.1
Pi = WorksheetFunction.Pi()
EquivalentRoughness = 0.046
RelativeRoughness = EquivalentRoughness / PipeSize
FlowRate = Cells(2, 7)
ReynoldsNumber = (4 * FlowRate) / (DynamicViscosity * Pi * (PipeSize / 1000))
If ReynoldsNumber < 2000 Then
Lamda = 64 / ReynoldsNumber
Else
Lamda = ((1 / (-1.8 * WorksheetFunction.Log((6.9 / ReynoldsNumber) + ((RelativeRoughness / 3.71) ^ 1.11)))) ^ 2)
End If
Velocity = ((4 * FlowRate) / (Pi * Density * ((PipeSize / 1000) ^ 2)))
PressureDrop = ((Lamda * Density) * (Velocity ^ 2)) / (2 * (PipeSize / 1000))
End Sub
Some of the constants listed here (for example density, pipe size, etc.) I eventually intend to read from a worksheet or automatically calculate but for now I'm proceeding one step at a time.
Now that I'm satisfied that this works, which I've checked by outputting the numbers generated, I want to use Goal Seek to find the flow rate value at a certain pre-defined flow rate.
So what I want to do is have VBA cycle through different flow rate values until the desired pressure drop value is reached. I will tell VBA the desired pressure drop in a cell in the Excel sheet. I want this calculation to exist entirely inside VBA without any worksheet formulas.
So I've got, in very simplified terms, the following:
(1) A starting flow rate (I guess this should be defined in the VBA code otherwise Goal Seek won't have a starting point)
(2) Some calculations
(3) A resulting pressure drop.
(4) If the resulting pressure drop is not equal to a pre-defined value (located in cell G3) the flow rate value in (1) should be adjusted and the calculations run again.
(5) When the resulting pressure drop equals the pre-defined value tell me what the flow rate value used to calculate this is.
Any ideas?

OK, I took a crack at this..there may be a better way and this assumes a direct relationship (not inverse)..i moved some of your variables into constants and put the pressure calc in a function, and changed data types to double. It is a UDF with you can use in the worksheet.
Const Density As Double = 977.8
Const DynamicViscosity As Double = 0.0004
Const PipeSize As Double = 36.1
Const Pi As Double = 3.14159265358979
Const EquivalentRoughness As Double = 0.046
Const RelativeRoughness As Double = EquivalentRoughness / PipeSize
Const Sig As Double = 0.0000000001 'this indicates how accurate you want your answer
Dim FlowRate As Double
Dim ReynoldsNumber As Double
Dim Lamda As Double
Dim Velocity As Double
Function PipeData(IdealPressureDrop As Long)
FlowRate = 1000 + Sig
Stepper = 100
If PressureDrop(FlowRate) > IdealPressureDrop Then
FlowRateGoal = GoalSeek(FlowRate, Stepper, -1, IdealPressureDrop)
Else
FlowRateGoal = GoalSeek(FlowRate, Stepper, 1, IdealPressureDrop)
End If
PipeData = FlowRateGoal
End Function
Function GoalSeek(FlowRate, Stepper, Direction, IdealPressureDrop)
calcagain:
Select Case Direction
Case 1
Do While PressureDrop(FlowRate) < IdealPressureDrop
oFR = FlowRate
FlowRate = FlowRate + Stepper
Loop
Case -1
Do While PressureDrop(FlowRate) > IdealPressureDrop
oFR = FlowRate
FlowRate = FlowRate - Stepper
Loop
End Select
Stepper = Stepper / 10
If Stepper < Sig Then GoTo getout
FlowRate = oFR
GoTo calcagain
getout:
GoalSeek = FlowRate
End Function
Function PressureDrop(FlowRate)
ReynoldsNumber = (4 * FlowRate) / (DynamicViscosity * Pi * (PipeSize / 1000))
If ReynoldsNumber < 2000 Then
Lamda = 64 / ReynoldsNumber
Else
Lamda = ((1 / (-1.8 * WorksheetFunction.Log((6.9 / ReynoldsNumber) + ((RelativeRoughness / 3.71) ^ 1.11)))) ^ 2)
End If
Velocity = ((4 * FlowRate) / (Pi * Density * ((PipeSize / 1000) ^ 2)))
PressureDrop = ((Lamda * Density) * (Velocity ^ 2)) / (2 * (PipeSize / 1000))
End Function
This can now be referenced in the worksheet with
=PipeData(A3)
Where "A3" is your ideal pressure drop number

Related

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

MS Excel. VBA function returns #value

It would be nice if someone could explain what causes function above return #value error.
Public Function papild(x)
Dim Sum As Double, A As Double, pi As Double,
Sum = 0.5 - (x - pi / 4)
A = -(x - pi / 4)
pi = Application.WorksheetFunction.pi()
Dim k As Integer, i As Integer
k = 2
i = 0
Do While Abs(A) > 0.0001
A = -A * 4 * A * A / (k + i) * (k + i + 1)
Sum = Sum + A
k = k + 1
i = i + 1
Loop
paplid = Sum
End Function
Function takes x value from MS Excel cell and it's equal = -1.5708 (=-PI()/2 #Formula Bar)
In lines 3 and 4 you work with variable pi before setting it in line 5...
Could there be some brackets missing in your formula. It basically says:
A = -4A^3 * (k+i+1)/(k+1)
This obviously drifts to +/- infinite so your loop cannot end.
Also there is a comma too much in the second line and a spelling error in the last line (paplid instead of papild).
Have you tried debugging the code?
When I run the code I get an overflow error # the 6th iteration of the while loop starting with x = -1.5708. Number gets to large to fit inside variable
.Other than that there are some minor things:
missing As Double
Public Function papild(x) As Double
and unnecessary comma at the end
Dim Sum As Double, A As Double, pi As Double,

Open TK determine sphere (quadstrips) normals?

Introduction
I am somewhat new to using Open GL / Open TK. I have learned how to draw basic shapes, use matrices, lighting, shadowing, etc. I have a function that draws a sphere:
Private Sub drawSphere(r As Double, lats As Integer, longs As Integer)
Dim i As Integer, j As Integer
For i = 0 To lats
Dim lat0 As Double = PI * (-0.5 + CDbl(i - 1) / lats)
Dim z0 As Double = Sin(lat0)
Dim zr0 As Double = Cos(lat0)
Dim lat1 As Double = PI * (-0.5 + CDbl(i) / lats)
Dim z1 As Double = Sin(lat1)
Dim zr1 As Double = Cos(lat1)
GL.Begin(PrimitiveType.QuadStrip)
For j = 0 To longs
Dim lng As Double = 2 * PI * CDbl(j - 1) / longs
Dim x As Double = Cos(lng)
Dim y As Double = Sin(lng)
GL.Normal3(x * zr0 * r, y * zr0 * r, z0 * r)
GL.Vertex3(x * zr0 * r, y * zr0 * r, z0 * r)
GL.Normal3(x * zr1 * r, y * zr1 * r, z1 * r)
GL.Vertex3(x * zr1 * r, y * zr1 * r, z1 * r)
Next
GL.End()
Next
End Sub
I have other code that sets up the lights. I know the other code works because I have a separate function for drawing an STL object:
Dim texture As UInteger() = New UInteger(0) {}
Dim i As Integer = 0
If stl_table.Items.Count > 0 Then
find_center_of_part()
GL.Begin(PrimitiveType.Triangles)
GL.Color3(part_color.R, part_color.G, part_color.B)
Do Until i + 4 >= stl_table.Items.Count
GL.Normal3(Convert.ToSingle(stl_table.Items.Item(i).SubItems(0).Text), Convert.ToSingle(stl_table.Items.Item(i).SubItems(1).Text), Convert.ToSingle(stl_table.Items.Item(i).SubItems(2).Text))
GL.Vertex3(stl_table.Items.Item(i + 1).SubItems(0).Text - avgx, stl_table.Items.Item(i + 1).SubItems(1).Text - avgy, stl_table.Items.Item(i + 1).SubItems(2).Text - avgz)
GL.Vertex3(stl_table.Items.Item(i + 2).SubItems(0).Text - avgx, stl_table.Items.Item(i + 2).SubItems(1).Text - avgy, stl_table.Items.Item(i + 2).SubItems(2).Text - avgz)
GL.Vertex3(stl_table.Items.Item(i + 3).SubItems(0).Text - avgx, stl_table.Items.Item(i + 3).SubItems(1).Text - avgy, stl_table.Items.Item(i + 3).SubItems(2).Text - avgz)
i = i + 4
Loop
GL.End()
End If
This second function basically imports a CAD STL file and draws it as triangles. The normal vectors are simply an input from the CAD file (so they are already computed). This method's lighting works perfectly fine which makes me know my lighting code is correct.
Problem
The problem is that my sphere is not getting light correctly. I know through testing that this is due to my normal vectors.
With the current code, my sphere looks like this:
There is a "spot" of light which makes me think that is simply one of the quadstrips having the normal correct.
Does anybody have any suggestions on setting up the normal vectors correctly inside my function? Also before anybody suggests it, I can't use GLU or GLUT for what I am trying to accomplish, which is why I need the sphere function.

Rotating Shapes when arranging them into circle ppt vba

I found a great script to arrange objects (shapes) into a circle here:
Aligning Shapes in a Circle using VBA, Microsoft Community
Sub Test()
Call AlignShapesInCircle(720 / 2, 540 / 2, 100, ActiveWindow.Selection.ShapeRange)
End Sub
Function AlignShapesInCircle(x As Single, y As Single, r As Single, shprng As ShapeRange)
'x,y = center point of the circle
'r = radius of the circle
'shprng = the shape selection that needs to be arranged
Dim angle As Single
Dim currentangle As Single
Dim x1 As Single
Dim y1 As Single
Dim i As Integer
currentangle = 0
angle = 360 / shprng.count
For currentangle = 0 To 359 Step angle
i = i + 1
x1 = r * Cos(D2R(currentangle))
y1 = r * Sin(D2R(currentangle))
shprng(i).Left = x + x1
shprng(i).Top = y + y1
Next
End Function
Function D2R(Degrees) As Double
D2R = Degrees / 57.2957795130823
End Function
Function R2D(Radians) As Double
R2D = 57.2957795130823 * Radians
End Function
Now I want the shapes to rotate so that if I use arrows the tip will always show towards the center.
I have to introduce a line here:
shprng(i).Left = x + x1
shprng(i).Top = y + y1
shprng(i).Rotation = ???
Any ideas where I could find the proper formula?
Silly - figured it out - it was easier than I thought. Don't need any SIN and COS which frightened me - just:
shprng(i).Rotation = (360 / (shprng.Count)) * (i - 1)

Calculating an exponentially weighted moving average

I am trying a fairly simple function to calculate an exponentially weighted moving average volatility in Excel VBA, following. However, I think there is some error in my function that I can't pinpoint, because I don't get the correct solution.
Function EWMA(numbers As Range, Lambda As Single) As Double
Dim mean As Double
Dim x As Double
Dim c As Range
Dim n As Integer
mean = WorksheetFunction.Average(numbers)
n = WorksheetFunction.Count(numbers)
For Each c In numbers
x = x + (Lambda ^ (n - c.Count)) * ((c.Value - mean) ^ 2)
Next c
EWMA = (1 - Lambda) * x
End Function
The values I am using and the target volatility (calculated using a spreadsheet EWMA) are here.
What am I doing wrong?
Update: using Ron Rosenfeld's solution:
Option Explicit
Function EWMA(Zero As Range, Lambda As Double) As Double
Dim vZero As Variant
Dim SumWtdRtn As Double
Dim I As Long
Dim vPrices As Variant
Dim LogRtn As Double, RtnSQ As Double, WT As Double, WtdRtn As Double
vZero = Zero
For I = 2 To UBound(vZero, 1)
vPrices = 1 / ((1 + vZero(I, 1)) ^ (3 / 12))
LogRtn = Log(vPrices(I - 1, 1) / vPrices(I, 1))
RtnSQ = LogRtn ^ 2
WT = (1 - Lambda) * Lambda ^ (I - 2)
WtdRtn = WT * RtnSQ
SumWtdRtn = SumWtdRtn + WtdRtn
Next I
EWMA = SumWtdRtn ^ (1 / 2)
End Function
Here's a little bit different way of doing it as a VBA function. The inputs are an array of prices, which is assumed to be in descending order as you show, and Lambda. Hopefully, the names of the variables will let you see the logic:
Option Explicit
Function EWMA2(Prices As Range, Lambda As Double) As Double
Dim vPrices As Variant
Dim dSumWtdRtn As Double
Dim I As Long
Dim dLogRtn As Double, dRtnSQ As Double, dWT As Double, dWtdRtn As Double
vPrices = Prices
For I = 2 To UBound(vPrices, 1)
dLogRtn = Log(vPrices(I - 1, 1) / vPrices(I, 1))
dRtnSQ = dLogRtn ^ 2
dWT = (1 - Lambda) * Lambda ^ (I - 2)
dWtdRtn = dWT * dRtnSQ
dSumWtdRtn = dSumWtdRtn + dWtdRtn
Next I
EWMA2 = dSumWtdRtn ^ (1 / 2)
End Function
With your data, it gives the same results as the spreadsheet calculation (within the limits of precision of the data types)
EDIT
If you want to input the 3M CAD Zero Rates as the range input, and not the pricing, then you could modify the above to compute the two relevant prices from the returns data. In this case, it would be:
Option Explicit
Function EWMAV(Zeros As Range, Lambda As Double) As Double
Dim vZeros() As Variant
Dim dPrice1 As Double, dPrice2 As Double
Dim dSumWtdRtn As Double
Dim I As Long
Dim dLogRtn As Double, dRtnSQ As Double, dWT As Double, dWtdRtn As Double
vZeros = Zeros
For I = 2 To UBound(vZeros, 1)
dPrice1 = 1 / ((1 + vZeros(I - 1, 1)) ^ (3 / 12))
dPrice2 = 1 / ((1 + vZeros(I, 1)) ^ (3 / 12))
dLogRtn = Log(dPrice1 / dPrice2)
dRtnSQ = dLogRtn ^ 2
dWT = (1 - Lambda) * Lambda ^ (I - 2)
dWtdRtn = dWT * dRtnSQ
dSumWtdRtn = dSumWtdRtn + dWtdRtn
Next I
EWMAV = dSumWtdRtn ^ (1 / 2)
End Function