Interpolate between array points - arrays

In the following code I built 2 arrays:
One contains a "degree data" (so basically an Integer array increasing one by one) representing a portion of degrees in a circumference.
The second one a "power data" (a Double, whose values keep more or less cubic increasing until a maximum corresponding to a 0 in the first array. Then they will decrease almost as they increased). This array goes for example from values that are more or less -4.0 to more or less -4.0.
I thought I could "simplify" the second one to a parabola (looks very much like one) and use its coefficients (calculated via LinEst) A, B, C and D to interpolate data between points.
What I do need is to find with a 0.1 degrees precision the 2 numbers which are nearest to -3.0 and then find their "distance" in degrees.
Problem is: I can't manage to do it. There is something I'm missing and the coefficients seems not to represent my set of data.
Dim i As Integer, j As Integer
Dim MaxVal As Double, MaxAngle As Integer, CyclicAngle As Double
Dim XValues() As Double, YValues() As Double, Coeff As Variant
Dim LeftAngle As Double, RightAngle As Double, LeftAngleValue As Double, RightAngleValue As Double
' Searches for the maximum value and its angle
MaxVal = -80
ReDim YValues(359 * 3 + 2)
For i = 3 To 362 ' This will fill arrays from a worksheet (defined as Public in
' another subroutine) in which the data starts from row 3. I need the data stored in
' the 2nd column)
For j = 0 To 2 ' since the array represents a circumference, i make it "cyclic"
YValues((i - 3) + (360 * j)) = TargetSheet.Cells(i, 2)
Next j
If TargetSheet.Cells(i, 2) > MaxVal Then
MaxVal = TargetSheet.Cells(i, 2)
MaxAngle = i - 3
End If
Next
' The following searches the "middle" maximum
i = 0
j = 0
Do Until j = 2
If YValues(i) = MaxVal Then
j = j + 1
CyclicAngle = i
End If
i = i + 1
Loop
' Searches in the middle for the <-3 (we name it "-4") values
i = CyclicAngle
Do Until YValues(i) < -3
i = i + 1
Loop
RightAngle = i + 1
i = CyclicAngle
Do Until YValues(i) < -3
i = i - 1
Loop
LeftAngle = i - 1
' Copying only the "-4" to "-4"
ReDim XValues(RightAngle - LeftAngle)
For i = 0 To RightAngle - LeftAngle
XValues(i) = YValues(LeftAngle + i)
Next i
' Now correctly store the data in a new ordered array
ReDim YValues(UBound(XValues))
For i = 0 To UBound(XValues)
YValues(i) = XValues(i)
XValues(i) = LeftAngle - 360 + i
Next i
Here is the critic point:
' Gets the coefficients of a 3rd degree curve representing the Y-Array
Coeff = Application.LinEst(Application.Transpose(YValues), Application.Power(Application.Transpose(XValues), Array(1, 2, 3)), True, False)
' Sets the arrays to have a point every 0.1°
LeftAngle = LeftAngle * 10
RightAngle = RightAngle * 10
MaxAngle = MaxAngle * 10
ReDim XValues(RightAngle - LeftAngle)
ReDim YValues(RightAngle - LeftAngle)
For i = LeftAngle To RightAngle
XValues(i - LeftAngle) = i / 10
YValues(i - LeftAngle) = Coeff(1) * (i / 10) ^ 3 + Coeff(2) * (i / 10) ^ 2 + Coeff(3) * (i / 10) + Coeff(4)
Next
Now if I look at the YValues array, the numbers stored inside don't look absolutely how they should be.
How do I interpolate to find those -3s then?

The error was in this piece of code: I didn't think I could not "rotate" the XValues array
' Now correctly store the data in a new ordered array
ReDim YValues(UBound(XValues))
For i = 0 To UBound(XValues)
YValues(i) = XValues(i)
XValues(i) = LeftAngle + i ' Was LeftAngle - 360 + i
Next i
I should now be able to find my -3.0 with a simple loop iteration.
There are of course a few obstacles but since I have the LeftAngle and RightAngle references I should be able to solve all.
Full Solution
Please see my Question and correct it with the code above. From the "critic point" it goes:
' Gets the coefficients of a 3rd degree curve representing the Y-Array
' and build an interpolation array whose points are every 0.1°
Coeff = Application.LinEst(Application.Transpose(YValues), Application.Power(Application.Transpose(XValues), Array(1, 2, 3)), True, False)
'TargetSheet.Range("E1:H1") = Coeff
' Sets the arrays to be every 0.1°
LeftAngle = LeftAngle * 10
RightAngle = RightAngle * 10
j = CInt(LeftAngle)
k = CInt(RightAngle)
ReDim XValues(k - j)
ReDim YValues(k - j)
For i = j To k
XValues(i - LeftAngle) = i / 10
YValues(i - LeftAngle) = Coeff(1) * (i / 10) ^ 3 + Coeff(2) * (i / 10) ^ 2 + Coeff(3) * (i / 10) + Coeff(4)
Next
' Now searches for the -3dB angles with a better resolution
RightAngleValue = 10
LeftAngleValue = 10
CyclicAngle = CyclicAngle * 10
i = 0
Do Until XValues(i) > CyclicAngle / 10
If Abs(YValues(i) +3) < LeftAngleValue Then
LeftAngleValue = Abs(YValues(i) + 3)
LeftAngle = i * 0.1
End If
i = i + 1
Loop
Do Until i > UBound(XValues)
If Abs(YValues(i) +3) < RightAngleValue Then
RightAngleValue = Abs(YValues(i) + 3)
RightAngle = i * 0.1
End If
i = i + 1
Loop
myFunction = RightAngle - LeftAngle

Related

array of non-repeating random numbers in visual basic not working

I have the following code that receives a number and returns a random number between two ranges depending on the number passed by parameter.
Private Function GeneraNumero(ByVal columna)
Dim value As Integer
If columna = 0 Then
value = CInt((1 - 9) * Rnd() + 9)
ElseIf columna = 1 Then
value = CInt((10 - 19) * Rnd() + 19)
ElseIf columna = 2 Then
value = CInt((20 - 29) * Rnd() + 29)
ElseIf columna = 3 Then
value = CInt((30 - 39) * Rnd() + 39)
ElseIf columna = 4 Then
value = CInt((40 - 49) * Rnd() + 49)
ElseIf columna = 5 Then
value = CInt((50 - 59) * Rnd() + 59)
ElseIf columna = 6 Then
value = CInt((60 - 69) * Rnd() + 69)
ElseIf columna = 7 Then
value = CInt((70 - 79) * Rnd() + 79)
ElseIf columna = 8 Then
value = CInt((80 - 90) * Rnd() + 90)
End If
Return value
End Function
and this part is in charge of calling the previous code passing a different number as a parameter, after obtaining the random number I go through an array of numbers created to store them and if it is not found there then I save it if it is found I repeat the loop, then I create a button to which I assign that number as text and put the button in a TableLayoutPanel.
So far so good, the only thing that happens is that there is always some repeated number and I am not able to see the failure.
Private Sub GenerarCarton()
Dim repetido As Boolean = False
filas = 3
columnas = 9
Dim numeros(27) As Integer
Dim aleatorio As Integer
Dim numero As Integer
For i = 0 To filas - 1
For j = 0 To columnas - 1
Do
aleatorio = GeneraNumero(j)
For k = 0 To numeros.Length - 1
If numeros(k) <> aleatorio Then
numeros(k) = aleatorio
numero = aleatorio
repetido = True
Else
repetido = False
End If
Next
Loop While repetido = True
Dim miboton As New Button
With miboton
.Name = "boton" & i & j
.Width = 50
.Height = 50
.Text = numero
.Top = .Height * ((i - 1) Mod filas) + 100
.Left = .Width * (j) + 10
End With
TableLayoutPanel1.Controls.Add(miboton)
AddHandler miboton.MouseDown, AddressOf miclick
Next j
Next i
End Sub
There is no failure. Even if these are pseudo random numbers, a basic principle is, that every number is independent - it doesn't "know" about any previous number, it is random "on its own".
Thus, with so few possible numbers that you have, it would rather be unlikely, that dupes would never be present.
If you wish unique numbers, create a list of these for each range, then sort these randomly (by the native return value of Rnd, which is a Single having many decimals), and pick the top 3.
If you wish truly random numbers, study my project VBA.Random.

Defining an `n` length nested loop in VBA

Building a portfolio asset allocator, for assets A, B, C, D, and want to expand it to n assets. With four assets, can run four nested loops. The issue is to create n nested loops, and then apply the constraint, detailed below, and the middle nest, which allocates an array to the current asset distribution. The constraint is (A + B + C + ... + n) <> 100 i.e. 100% allocation across all assets.
The asset step sizes are arbitrary (5 or 1) but there are minimum and maximum bounds on each n assets.
The asset sum to 100 constraint knocks out many of the iterations.
What is interesting in this question, is the AllocArray requires the position of each {A, B, C, ... , nAsset} i.e. the location of each nested loop. I think this makes an exciting puzzle.
Below is nAssets = 4 i.e. {A,B,C,D}:
MinMaxStepParam = Range("A1:C4")
Min Max Step
A 5 100 5
B 5 100 5
C 5 100 5
D 5 100 5
Dim Sim As Long: Sim = 1
Dim AllocArray() As Variant
ReDim AllocArray(1 To nSim, 1 To nAsset + 1)
Dim A As Double
Dim B As Double
Dim C As Double
Dim D As Double
For A = AMin To AMax Step AStep
For B = BMin To BMax Step BStep
'If (A + B) > 100 Then GoTo endB ' not required as middle nest bound catches all
For C = CMin To CMax Step CStep
'If (A + B + C) > 100 Then GoTo endC ' ditto
For D = DMin To DMax Step DStep
'Constraints
If (A + B + C + D) <> 100 Then GoTo endD
Debug.Print Sim; A; B; C; D
AllocArray(Sim, 1) = Sim
AllocArray(Sim, 2) = A
AllocArray(Sim, 3) = B
AllocArray(Sim, 4) = C
AllocArray(Sim, 5) = D
Sim = Sim + 1
endD:
Next D
endC:
Next C
endB:
Next B
Next A
PrintArray AllocArray, ActiveWorkbook.Worksheets("Output").[A1]
Sub PrintArray(Data As Variant, Cl As Range)
Cl.Resize(UBound(Data, 1), UBound(Data, 2)) = Data
End Sub
I have produced this as a dynamic loop, but there is an error. Can VBA handle using Array locations for the Min, Max and Step of a loop? Is there an alternative?
Sub ConfigureArrayDylan()
Dim Param() As Variant: Param = Range("M3:O6") ' a 4x3 with...Min Max Step... {5 100 5; 5 100 5; 5 100 5; 5 100 5}.
Dim nAsset As Long: nAsset = UBound(Param) ' Count {A, B, ... , D } = 4
Debug.Print nAsset
Dim Asset As Double
Dim Value As Double
Dim Sim As Long: Sim = 1
Dim nSim As Long: nSim = 1
Dim nSimStep As Long
For i = 1 To nAsset Step 1
nSimStep = (1 + (Param(i, 2) - Param(i, 1)) / Param(i, 3)) '(1 + (AMax - AMin) / AStep) *
Debug.Print i; nSimStep
nSim = nSim * nSimStep
Next i
Debug.Print nSim
Dim AllocArray() As Variant
ReDim AllocArray(1 To nSim, 1 To nAsset + 1)
For Asset = 1 To nAssets Step 1
For Value = Param(Asset, 1) To Param(Asset, 2) Step Param(Asset, 3)
Debug.Print Value; ' FAILURE HERE
AllocArray(Sim, Asset) = Value
Sim = Sim + 1
Next Value
Next Asset
Debug.Print Sim;
PrintArray AllocArray, ActiveWorkbook.Worksheets("Test").[L18]
'Range("D30").Resize(NumRows, NumCols).Value = AllocArray
End Sub

How (if possible) do I define and populate a global array variable from a worksheet in VBA Excel 2010?

*******Background*******
For reasons not up for debate I am using Excel 2010 and VBA to program a scientific model that involves a user inputting data to a worksheet from an online data bank for each species involved. The number of species may change from simulation to simulation. As the program iterates, it calls many functions multiple times (some in the tens of thousands of times). The execution speed of the program is becoming too long so I would like to try and speed it up. In my mind two easy things to increase the execution speed are to decrease the number of worksheet calls and to minimize the number of variants I have to use.
Also of note that several of the functions share the same constants.
I've looked elsewhere on stackexchange and other sites but I still haven't found what I'm looking for ♪. Maybe I just don't have the right search terms.
The question(s)
Because the number of species is not constant and because I would like to make a few arrays of constants available to multiple functions is there away that I can define say a global variable that is an double (or single) precision array that is dimensionalized when a sub is run, reads the constants from the excel sheet once, and then is destroyed when my "main" sub is finished executing?
I could create the array(s) in the main sub and pass it as an argument but several of my functions call other functions as arguments already and several of my lines are extremely long and hard to read. By making these constants arguments to pass to the functions only increases the length of these lines.
Second question if I can't create a global array variable is there away to call the worksheet once (like I have done) but to make the variable a double instead of a variant? I couldn't get the following to work because of the type mismatch error.
Dim C() As Double
redim c(1 to 7, 1 to n)
C = Application.Transpose(Worksheets("Viscosity2").Range("J10:p19"))
Function example: I have a function that I am running in VBA. This function is called tens of thousands of times in the course of my program. I would like to make the C(1 to 7, 1 to n) array and the mw(1 to n) array double precision arrays that call the worksheet once and then are available to multiple functions.
Below is that example function:
Function mumx(y, T)
'this function calculates the mixture viscosity using the Chapman Enskog Wilke method
'using the mol fraction vector, y, and the temperature T
n = UBound(y, 1) - LBound(y, 1) + 1 'number of species
'***********Get Equation Parameters from Worksheet**************
Dim C() As Variant
C = Application.Transpose(Worksheets("Viscosity2").Range("J10:p19"))
Dim mw As Variant
mw = Application.Transpose(Worksheets("Viscosity2").Range("g10:g19"))
'***************************************************************
Dim mu() As Double
ReDim mu(1 To n)
For i = 1 To n Step 1
mu(i) = (C(1, i) * (T ^ C(2, i))) / (1 + C(3, i) / T + (C(4, i) / (T ^ 2)))
Next i
Dim phi() As Double
ReDim phi(1 To n, 1 To n)
For i = 1 To n
For j = 1 To n
phi(i, j) = 1 / 8 ^ 0.5 * (1 + mw(i) / mw(j)) ^ -0.5 * (1 + (mu(i) / mu(j)) ^ 0.5 * (mw(j) / mw(i)) ^ 0.25) ^ 2
test = 1
Next j
Next i
Dim denom As Double
Dim mumix As Double
denom = 0
mumix = 0
For i = 1 To n
For j = 1 To n
denom = denom + y(j) * phi(i, j)
Next j
mumix = mumix + y(i) * mu(i) / denom
denom = 0
Next i
mumx = mumix
'where the units on mumx are in units of cP (which are 1 gm/(m*s))
End Function
'************Example constants are as follows********
'PS should someone stumble on this looking for say viscosity data
'the following constants just example constants
'
'
'C(1, 1) = 0.00018
'C(1, 2) = 0.000017
'C(1, 3) = 0.001113
'C(1, 4) = 0.00215
'C(1, 5) = 0.0005255
'C(1, 6) = 0.0011
'C(1, 7) = 0.0006559
'C(1, 8) = 0.00005
'C(1, 9) = 0.00026
'C(1, 10) = 0.002079
'
'C(2, 1) = 0.69
'C(2, 2) = 1.115
'C(2, 3) = 0.534
'C(2, 4) = 0.46
'C(2, 5) = 0.59
'C(2, 6) = 0.563
'C(2, 7) = 0.608
'C(2, 8) = 0.90
'C(2, 9) = 0.68
'C(2, 10) = 0.4163
'
'C(3, 1) = -0.59
'C(3, 2) = 0
'C(3, 3) = 94.7
'C(3, 4) = 290.
'C(3, 5) = 106.
'C(3, 6) = 96.3
'C(3, 7) = 54.7
'C(3, 8) = 0
'C(3, 9) = 98.9
'C(3, 10) = 353.
'
'C(4, 1) = 140.
'C(4, 2) = 0
'C(4, 3) = 0
'C(4, 4) = 0
'C(4, 5) = 0
'C(4, 6) = 0
'C(4, 7) = 0
'C(4, 8) = 0
'C(4, 9) = 0
'C(4, 10) = 0
'
'
'C(5, 1) = 0
'C(5, 2) = 0
'C(5, 3) = 0
'C(5, 4) = 0
'C(5, 5) = 0
'C(5, 6) = 0
'C(5, 7) = 0
'C(5, 8) = 0
'C(5, 9) = 0
'C(5, 10) = 0
'
'C(6, 1) = 300
'C(6, 2) = 300
'C(6, 3) = 300
'C(6, 4) = 300
'C(6, 5) = 300
'C(6, 6) = 300
'C(6, 7) = 300
'C(6, 8) = 300
'C(6, 9) = 300
'C(6, 10) = 300
'
'C(7, 1) = 1000
'C(7, 2) = 1000
'C(7, 3) = 1000
'C(7, 4) = 1000
'C(7, 5) = 1000
'C(7, 6) = 1000
'C(7, 7) = 1000
'C(7, 8) = 1000
'C(7, 9) = 1000
'C(7, 10) = 1000
'
'
'
'mw(1) = 2.0158
'mw(2) = 18.0148
'mw(3) = 28.01
'mw(4) = 44.009
'mw(5) = 16.0426
'mw(6) = 31.998
'mw(7) = 28.014
'mw(8) = 44.0962
'mw(9) = 30.0694
'mw(10) = 28.0536
'
''******************************
Yes, you can and should* use an array to store the constants entered by the user and yes, you can make it global so that it doesn't have to be passed to the other functions.
Here's an example; note that the data is read into a Variant first, then transferred to the array--this was your missing step that lead to the Type Mismatch error. While this may look like too much code for the effort, transferring the data into the Double array will be many times faster than reading the cells one-by-one.
Public C() As Double
Public Sub PopulateArrayC(n As Integer)
ReDim C(1 To 7, 1 To n)
Dim v As Variant
v = Application.Transpose(Worksheets("Viscosity2").Range("J10:P" & n + 10 - 1))
Dim i As Integer, j As Integer
For i = 1 To 7
For j = 1 To n
C(i, j) = v(i, j)
Next j
Next i
End Sub
*Reading from and writing to cells is very time consuming. Whenever possible, limit the number of reads and writes by*
storing frequently used values in variables, and
reading/writing whole ranges at a time.

Split an integer into digits (Hundreds, tens, units) and store in ARRAYS

I am working on some code that will compute very large factorial products that are much too large to fit into a Long-integer. I am to use 2 arrays, F() and C(). F is for the multiplication products and to store the units, C() is for the "carries", which will be added later. So the units are stored in F(I), for instance, C(I+1) holds the tens, C(I+2) holds the hundreds. Later you will add C(I) + F(I).
Dim F(0 To 30) As Single, C(0 To 30) As Single, sum As Single
Dim length As Single, split1 As Single, split2 As Single
Dim nexttwo As String, mult As String
Sub LargeFactCalc()
F(1) = 1
N = InputBox("Enter the number you would like to factorialize: ")
For J = 2 To N
For I = 1 To 30
mult = J * F(I)
Call split
Next I
sum = C(I) + F(I)
C(I + 1) = 0
C(I + 2) = 0
Next J
Print #1, F(I)
Close #1
End Sub
Sub split()
Select Case Len(mult)
Case 1
F(I) = mult 'UNITS
Case 2
C(I + 1) = Left(mult, 1) 'Tens
F(I) = Right(mult, 1) 'Units
Case 3
C(I + 2) = Left(mult, 1) 'Hundreds
C(I+1) = 'Tens should go here but I don't
'know how to seperate the middle number
F(I) = Right(mult, 1) 'Units
End Select
End Sub
What you need here is either the Mid function, which returns a number of character starting at a specific character, or a combination or Right and Left functions.
Try either :
C(I + 1) = Mid(mult, 2, 1) 'Returns 1 character starting from the 2nd character of mult
or :
C(I + 1) = Left(Right(mult, 2), 1) 'Returns the left-most character,
'of the 2 right-most characters
Format is Microsoft ACCESS 2010.
I have this in a form to split my AMOUNT field into hundreds, tens & units and enter these into individual fields on the same form called H, T & U.
I have made the hundreds H show all numbers if it larger that 999. i.e. if the result is 1234 it will show 12 in H 3 in T and 4 in U.
This is done by counting the length in the AMOUNT field. It is simple, short and sweet with no error catching yet.
Code is below.
Private Sub AMOUNT_AfterUpdate()
Dim LenAmt As String
LenAmt = Len(AMOUNT)
H = Mid(Right(AMOUNT, LenAmt), 1, LenAmt - 2)
T = Mid(Right(AMOUNT, 2), 1, 1)
U = Mid(Right(AMOUNT, 1), 1, 1)
Me.Refresh
End Sub

Return array in Spreadsheet Function

The code below returns an array. I would like to use it in a spread sheet as an excel formula to return the array. However, when I do, it only returns the first value to the cell. Is there anyway to return the array in a range of equal size as the array?
Function LoadNumbers(Low As Long, High As Long) As Long()
'''''''''''''''''''''''''''''''''''''''
' Returns an array of Longs, containing
' the numbers from Low to High. The
' number of elements in the returned
' array will vary depending on the
' values of Low and High.
''''''''''''''''''''''''''''''''''''''''
'''''''''''''''''''''''''''''''''''''''''
' Declare ResultArray as a dynamic array
' to be resized based on the values of
' Low and High.
'''''''''''''''''''''''''''''''''''''''''
Dim ResultArray() As Long
Dim Ndx As Long
Dim Val As Long
'''''''''''''''''''''''''''''''''''''''''
' Ensure Low <= High
'''''''''''''''''''''''''''''''''''''''''
If Low > High Then
Exit Function
End If
'''''''''''''''''''''''''''''''''''''''''
' Resize the array
'''''''''''''''''''''''''''''''''''''''''
ReDim ResultArray(1 To (High - Low + 1))
''''''''''''''''''''''''''''''''''''''''
' Fill the array with values.
''''''''''''''''''''''''''''''''''''''''
Val = Low
For Ndx = LBound(ResultArray) To UBound(ResultArray)
ResultArray(Ndx) = Val
Val = Val + 1
Next Ndx
''''''''''''''''''''''''''''''''''''''''
' Return the array.
''''''''''''''''''''''''''''''''''''''''
LoadNumbers = ResultArray()
End Function
A UDF can certainly return an array, and your function works fine. Just select, e.g., range B2:D2, put =LoadNumbers(1, 3) into the formula bar, and hit Ctrl+Shift+Enter to tell Excel it's an array function.
Now, you can't have the UDF auto-resize the range it was called from according to its inputs (at least not without some ugly Application.OnTime hack), but you don't need to do that anyways. Just put the function in a 1000-cell-wide range to begin with, and have the UDF fill in the unused space with blank cells, like this:
Function LoadNumbers(ByVal Low As Long, ByVal High As Long) As Variant()
Dim ResultArray() As Variant
Dim Ndx As Long
Dim Val As Long
Dim SourceCols As Long
SourceCols = Application.Caller.Columns.Count
If Low > High Then
Exit Function
End If
If High - Low + 1 > SourceCols Then High = Low + SourceCols - 1
ReDim ResultArray(1 To SourceCols)
Val = Low
For Ndx = LBound(ResultArray) To (High - Low + 1)
ResultArray(Ndx) = Val
Val = Val + 1
Next Ndx
For Ndx = (High - Low + 2) To UBound(ResultArray)
ResultArray(Ndx) = vbNullString
Next Ndx
LoadNumbers = ResultArray()
End Function
A worksheet formula can only output a value to the same cell the formula was written in. As it stands, the code already produces an array. If you want the values to be shown as you copy the formula down, use a formula like this (in any cell you want) and then copy down:
=INDEX(LoadNumbers(1,10),ROWS($A$1:$A1))
If you copy down too far, you'll get a #REF! error because the LoadNumbers ran out of numbers.
I was looking for something similar (create a function in a macro, take inputs from a sheet, output an multi-dim array), and I hope my use-case below helps to answer. If not, my apologies:
Use-case:
Create and apply well-known numerical option valuation function, and output the stock price, valuation, and payoff as a 3-D array (3 columns) of #rows as specified in the function (20 in this case, as NAS variable). The code is copied - but the idea is to get the output into the sheet....
a) These inputs are static in the sheet.
b) I called the macro formula 'optval' via the 'fx' function list from an output cell I wanted to start in, and put the starting inputs into the formula.
b) The output will propagate to the cells as per the code using the NAS bound of 20 rows. Trivial, but it works.
c) you can automate the execution of this and output to the sheet - but anyhow, I hope this way helps anyway.
The module function is below (copied!) - but just put the starter inputs in i.e.
Vol=.2, Int rate = 0.05, Strike=120, Exp = 1, P type = C (or P), US?= N, i.e. european, , NAS=20 (or however many rows you want to see, and it affects the granularity of the numerical method)
Function optval(Vol, Intrate, Strike, Expn, Ptype, Etype, NAS)
ReDim S(0 To NAS) As Double
ReDim VOld(0 To NAS) As Double
ReDim VNew(0 To NAS) As Double
ReDim dummy(0 To NAS, 1 To 3)
dS = 2 * Strike / NAS
dt = 0.9 / NAS / NAS / Vol / Vol
NTS = Int(Expn / dt) + 1
dt = Expn / NTS
q = 1
If Ptype = "P" Then q = -1
For i = 0 To NAS
S(i) = i * dS
VOld(i) = Application.Max(q * (S(i) - Strike), 0)
dummy(i, 1) = S(i)
dummy(i, 2) = VOld(i) 'Payoff
Next i
For k = 1 To NTS
For i = 1 To NAS - 1
Delta = (VOld(i + 1) - VOld(i - 1)) / 2 / dS
Gamma = (VOld(i + 1) - 2 * VOld(i) + VOld(i - 1)) / dS / dS
Theta = -0.5 * Vol * Vol * S(i) * S(i) * Gamma - _
Intrate * S(i) * Delta + Intrate * VOld(i)
VNew(i) = VOld(i) - Theta * dt 'BSE
Next i
VNew(0) = VOld(0) * (1 - Intrate * dt) 'S=0
VNew(NAS) = 2 * VNew(NAS - 1) - VNew(NAS - 2) 'Infty
For i = 0 To NAS
VOld(i) = VNew(i)
Next i
If Etype = "Y" Then
For i = 0 To NAS
VOld(i) = Application.Max(VOld(i), dummy(i, 2))
Next i
End If
Next k
For i = 0 To NAS
dummy(i, 3) = VOld(i)
Next i
optval = dummy
End Function
=INDEX(LoadNumbers(1,10),ROWS($A$1:$A1),COLUMNS($B$1,B$1))

Resources