VBA - Trying to get the root of a function - excel

I got this equation to solve in VBA:
Energy = y + (0.5) ^ 2 / ( 2 * 9.81 * y ^ 2 )
but I got the Energy value and want to solve for the y value.
In R is easy: uniroot. There is something like this?

The equation is a cubic polynomial with two positive roots and one negative. We can eliminate the negative one, and consider a direct evaluation of the two positive roots with the following VBA code in a Module.
Global Const g As Double = 9.81
Global Const PI As Double = 3.14159265358979
Public Function CalcE(ByVal y As Double) As Double
CalcE = y + (0.5) ^ 2 / (2# * g * y ^ 2)
End Function
Public Function SolveForY(ByVal E As Double, Optional ByVal second As Boolean = False) As Double
' Solve "E = y + (0.5) ^ 2 / ( 2 * g * y ^ 2 )" for "y"
If Not second Then
SolveForY = E / 3# - 2# * E * Sin(Asin((16# * E ^ 3# * g - 27#) / (16# * E ^ 3# * g)) / 3#) / 3#
Else
SolveForY = E / 3# + 2# * E * Sin(Asin((16# * E ^ 3 * g - 27#) / (16# * E ^ 3 * g)) / 3# + PI / 3#) / 3#
End If
End Function
and the supporting Asin() function defined in terms of Atn().
Public Function Asin(ByVal x) As Double
If Abs(x) < 1# Then
Asin = Atn(x / Sqr(1# - x * x))
Else
Asin = 2 * Atn(1) * Sgn(x)
End If
End Function
Some example results are
? SolveForY(1#)
0.12035578724076
? SolveForY(1#, True)
0.986917853921696
And checking the solutions
? CalcE(0.12035578724076)
0.999999999999997
? CalcE(0.986917853921696)
1

Consider using algebra:
Energy = y + (0.5)^2 / ( 2 * 9.81 * y^2 )
Energy = y + .25 / ( 19.62 * y^2 )
Energy * ( 19.62 * y^2 ) = y * ( 19.62 * y^2 ) + .25
Energy * 19.62 * y^2 = 19.62 * y^3 + .25
0 = 19.62 * y^3 - Energy * 19.62 * y^2 + .25
and apply standard techniques:
Previous Post

Here's a translation of John Alexiou's answer from VBA to Excel formulas. If energy in in cell B2 then the three solutions are given by:
=B2/3-2*B2*SIN((ASIN((16*B2^3*9.81-27)/(16*B2^3*9.81))+0*PI())/3)/3
=B2/3-2*B2*SIN((ASIN((16*B2^3*9.81-27)/(16*B2^3*9.81))+4*PI())/3)/3
=B2/3-2*B2*SIN((ASIN((16*B2^3*9.81-27)/(16*B2^3*9.81))+2*PI())/3)/3
The third equation gives negative y.

Related

UDF value error for complex itterative function

I want to create an UDF for my excel worksheet. However when I select my input for my UDF I get a value error. Can someone help me?
Code and images below:
code:
Public Function pipediameter(Pth As Double) As Double
Dim D As Double
p = 150
Tv = 30
T = 55
k = 0.000045
vi = 4
a = (1.729 * (10 ^ (-6))) / ((1 + (T / 25)) ^ 1.165)
rho = 988
Cp = 4180
di = 1
i = 1
While i <= 10
v = 4 * Pth / (3.14 * di ^ 2 * Cp * rho * Tv)
Re = v * di / vi
B1 = (0.774 * Log(Re) - 1.41) / (1 + 1.32 * Sqr(k / di))
B2 = ((k * Re) / (3.7 * di)) + (2.51 * B1)
Y = (B1 - ((B1 + (2 * (Log(B2 / Re) / Log(10)))) / (1 + (2.18 / B2)))) ^ (-2)
D = (((8 * Y) / (p * rho)) * ((Pth) / (3.14 * Cp * Tv)) ^ 2) ^ 0.2
di = D
i = i + 1
Wend
pipediameter = D
End Function

How do I use multithreading on this function for a np.meshgrid of values?

The following code generates numpy 2D lists of r and E values for the specified intervals.
r = np.linspace(3, 14, 10)
E = np.linspace(0.05, 0.75, 10)
r, E = np.meshgrid(r, E)
I am then using the following nested loop to generate output from the function ionisationGamma for each r and E interval value.
for ridx in trange(len(r)):
z = []
for cidx in range(len(r[ridx])):
z.append(ionisationGamma(r[ridx][cidx], E[ridx][cidx]))
Z.append(z)
Z = np.array(Z)
This loop gives me a 2D numpy array Z, which is my output and I am using it for a 3D graph. The problem with it is: it is taking ~6 hours to generate the output for all these intervals as there are so many values due to np.meshgrid. I have just discovered multi-threading in Python and wanted to know how I can implement this by using it. Any help is appreciated.
See below code for ionisationGamma
def ionisationGamma(r, E):
I = complex(0.1, 1.0)
a_soft = 1.0
omega = 0.057
beta = 0.0
dt = 0.1
steps = 10000
Nintervals = 60
N = 3000
xmin = float(-300)
xmax = -xmin
x = [0.0]*N
dx = (xmax - xmin) / (N - 1)
L = dx * N
dk = 2 * M_PI / L
propagator = None
in_, out_, psi0 = None, None, None
in_ = [complex(0.,0.)] * N
psi0 = [complex(0.,0.)] * N
out_ = [[complex(0.,0.)]*N for i in range(steps+1)]
overlap = exp(-r) * (1 + r + (1 / 3) * pow(r, 2))
normC = 1 / (sqrt(2 * (1 + overlap)))
gammai = 0.5
qi = 0.0 + (r / 2)
pi = 0.0
gammai1 = 0.5
gammai2 = 0.5
qi1 = 0.0 - (r / 2)
qi2 = 0.0 + (r / 2)
pi1 = 0.0
pi2 = 0.0
# split initial wavepacket
for i in range(N):
x[i] = xmin + i * dx
out_[0][i] = (normC) * ((pow(gammai1 / M_PI, 1. / 4.) * exp(complex(-(gammai1 / 2.) * pow(x[i] - qi1, 2.), pi1 * (x[i] - qi1)))) + (pow(gammai2 / M_PI, 1. / 4.) * exp(complex(-(gammai2 / 2.) * pow(x[i] - qi2, 2.), pi2 * (x[i] - qi2)))))
in_[i] = (normC) * ((pow(gammai1 / M_PI, 1. / 4.) * exp(complex(-(gammai1 / 2.) * pow(x[i] - qi1, 2.), pi1 * (x[i] - qi1)))) + (pow(gammai2 / M_PI, 1. / 4.) * exp(complex(-(gammai2 / 2.) * pow(x[i] - qi2, 2.), pi2 * (x[i] - qi2)))))
psi0[i] = in_[i]
for l in range(1, steps+1):
for i in range(N):
propagator = exp(complex(0, -potential(x[i], omega, beta, a_soft, r, E, dt, l) * dt / 2.))
in_[i] = propagator * in_[i];
in_ = np.fft.fft(in_, N)
for i in range(N):
k = dk * float(i if i < N / 2 else i - N)
propagator = exp(complex(0, -dt * pow(k, 2) / (2.)))
in_[i] = propagator * in_[i]
in_ = np.fft.ifft(in_, N)
for i in range(N):
propagator = exp(complex(0, -potential(x[i], omega, beta, a_soft, r, E, dt, l) * dt / 2.))
in_[i] = propagator * in_[i]
out_[l][i] = in_[i]
initialGammaCentre = 0.0
finalGammaCentre = 0.0
for i in range(500, 2500 +1):
initialGammaCentre += pow(abs(out_[0][i]), 2) * dx
finalGammaCentre += pow(abs(out_[steps][i]), 2) * dx
ionisationGamma = finalGammaCentre / initialGammaCentre
return ionisationGamma
def potential(x, omega, beta, a_soft, r, E, dt, l):
V = (-1. / sqrt((x - (r / 2)) * (x - (r / 2)) + a_soft * a_soft)) + ((-1. / sqrt((x + (r / 2)) * (x + (r / 2)) + a_soft * a_soft))) + E * x
return V
Since the question is about how to use multiprocessing, the following code will work:
import multiprocessing as mp
if __name__ == '__main__':
with mp.Pool(processes=16) as pool:
Z = pool.starmap(ionisationGamma, arguments)
Z = np.array(Z)
Where the arguments are:
arguments = list()
for ridx in range(len(r)):
for cidx in range(len(r[ridx])):
arguments.append((r[ridx][cidx], E[ridx][cidx]))
I am using starmap instead of map, since you have multiple arguments that you want to unpack. This will divide the arguments iterable over multiple cores, using the ionisationGamma function and the final result will be ordered.
However, I do feel the need to say that the main solution is not really the multiprocessing but the original function code. In ionisationGamma you are using several times the slow python for loops. And it would benefit your code a lot if you could vectorize those operations.
A second observation is that you are using many of those loops separately and it would be nice if you could separate that one big function into multiple smaller functions. Then you can time every function individually and speed up those that are too slow.

Code takes forever to come up with a solution

What I basically want, is comparing a timevalue (t1 and tuit)(in hours) to determine which method to use to calculate 'S' and 'k' in a function called 'stijghoogteverlaging'. Then a fitted curve can be made with those values.
I tried multiple things, like putting 'return s' underneath both s-methods.
if t1[i] < tuit:
s = Q / (4 * np.pi * k * D) * exp1(S * r**2 / (4 * k * D * t))
return s
else:
s = Q / (4 * np.pi * k * D) * ((exp1(S * r**2 / (4 * k * D * t))) - (exp1(S * r**2 / (4 * k * D * (t - tuit)))))
return s
But then I got a wrong fitted curve as can be seen in the image below.
Now I tried putting only one 'return s', but then it takes forever to calculate and I have to interrupt the kernel.
data = read_csv("pompproef_data.csv", sep = ';')
pb1 = data.iloc[1:,1].values-1.87
pb2 = data.iloc[1:,2].values-1.86
t1 = data.iloc[1:,0].values / (60*24)
volume = 10/1000 #m3
duur = [128,136, 150, 137, 143, 141] #seconden
totaal = np.sum(duur)
debiet = (((len(duur) * volume)/totaal)) * (60*60*24) #m3/d
print(debiet)
print(t1)
print(pb1)
tuit = 15/(24*60)
D = 2.0
Q = debiet
def stijghoogteverlaging(t, k, S):
for i in range(len(t1)):
if t1[i] < tuit:
s = Q / (4 * np.pi * k * D) * exp1(S * r**2 / (4 * k * D * t))
else:
s = Q / (4 * np.pi * k * D) * ((exp1(S * r**2 / (4 * k * D * t))) - (exp1(S * r**2 / (4 * k * D * (t - tuit)))))
return s
r = 4.0 #afstand peilbuis1 tot put
poptpb1, pcovpb1 = curve_fit(stijghoogteverlaging, t1, pb1, p0=[100, 1e-25], maxfev = 10000000)
print('optimale waarde van k voor peilbuis1:', poptpb1[0])
print('optimale waarde van S voor peilbuis1:', poptpb1[1])
tijd = data.iloc[1:,0].values
t = np.linspace(0.00069*(24*60), 0.021*(24*60), 1000)
s1 = stijghoogteverlaging(t, poptpb1[0], poptpb1[1])
plt.plot(tijd, pb1, 'r.', label = 'Gemeten bij 4 meter')
plt.plot(t, s1, 'b', label = 'fitted bij 4 m')
Does anyone have a solution?
Used values for t1 and pb1:
Plot with a wrong fitted curve(time in minutes).
The function stijghoogteverlaging is performing a nonsense operation over and over:
def stijghoogteverlaging(t, k, S):
for i in range(len(t1)):
if t1[i] < tuit:
s = Q / (4 * np.pi * k * D) * exp1(S * r**2 / (4 * k * D * t))
else:
s = Q / (4 * np.pi * k * D) * ((exp1(S * r**2 / (4 * k * D * t))) - (exp1(S * r**2 / (4 * k * D * (t - tuit)))))
return s
You are iterating len(t1) times, and at each iteration, you are computing the full vectorized value of s each and every time. That means that you are computing len(t)**2 values per call, and using a Python for loop as your outer loop to do it. As a minor point, you are accessing the x-data as the global variable t1 instead of the local value t, which gets passed in.
Your function should probably look more like this:
def stijghoogteverlaging(t, k, S):
return np.where(t < tuit,
Q / (4 * np.pi * k * D) * exp1(S * r**2 / (4 * k * D * t)),
Q / (4 * np.pi * k * D) * ((exp1(S * r**2 / (4 * k * D * t))) - (exp1(S * r**2 / (4 * k * D * (t - tuit)))))
)
This computes len(t) * 2 values per call, not len(t)**2, and selects a value from the appropriate result for each value of t.

Excel function sheet gives errors

My first attempt at user-defined functions in Excel (Mac) isn't going well. This gives "function or sub undefined":
Function CylArea(d0 As Double, theta As Double, y As Double) As Double
CylArea = Pi() * (d0 ^ 2 / 4 + d0 * Tan(Radians(theta)) * y + Tan(Radians(theta)) ^ 2 * y ^ 2)
End Function
Function CylVolume(d0 As Double, theta As Double, y As Double) As Double
CylVolume = Pi() * (d0 ^ 2 / 4 * y + 1 / 2 * d0 * Tan(Radians(theta)) * y ^ 2 + 1 / 3 * Tan(Radians(theta)) ^ 2 * y ^ 3)
End Function
Function CylAreaDeriv(d0 As Double, theta As Double, y As Double) As Double
CylAreaDeriv = Pi() * (d0 * Tan(Radians(theta)) + 2 * Tan(Radians(theta)) ^ 2 * y)
End Function
Curiously, the Pi in CylVolume is highlighted.
It looks like you are attempting to use Excel functions as you would in a worksheet within VBA code. This will likely not work.
Try changing all instances of Pi() to WorksheetFunction.Pi, all instances of Tan to Math.Tan and all instances of Radians to WorksheetFunction.Radians so that the code looks more like this:
Function CylVolume(d0 As Double, theta As Double, y As Double) As Double
CylVolume = WorksheetFunction.Pi * (d0 ^ 2 / 4 * y + 1 / 2 * d0 * Math.Tan(WorksheetFunction.Radians(theta)) * y ^ 2 + 1 / 3 * Math.Tan(WorksheetFunction.Radians(theta)) ^ 2 * y ^ 3)
End Function

Arithmetic Asian Option Pricing

I keep getting the invalid procedure call or argument error on the definition of sigma2d line.
Any idea how to avoid this code error?
Private Sub CommandButton4_Click()
Application.Range("E19").value = ""
Application.Range("F19").value = ""
S0 = Application.Range("C5").value 'arithmetic average of underlying 1
K = Application.Range("C6").value 'strike
T = Application.Range("C10").value 'maturity
sigma = Application.Range("C8").value 'volatility
r = Application.Range("C8").value 'risk free rate
nsteps = Application.Range("C12").value 'no of timesteps
nsimulations = Application.Range("C13").value ' no of mc simulations
div = Application.Range("C9").value 'dividends
Randomize
Dim M1 As Double, M2 As Double, sigma2d As Double
Dim d1 As Double, d2 As Double, Nd1 As Double, Nd2 As Double
M1 = (Exp((r - div) * T) - 1) / (r - div) * T
v = (2 * Exp((2 * r) - (2 * div) + (sigma * sigma) * T)) * S0 * S0
w = (r - div + (sigma * sigma)) * (2 * r - 2 * q + (sigma * sigma)) * T * T
Z = 2 * S0 * S0 / ((r - div) * T * T)
y = (1 / 2 * (r - div) + sigma * sigma)
h = Exp((r - div) * T) / (r - div + (sigma * sigma))
M2 = (v / w) + Z * (y - h)
M3 = M1 * M1
sigma2d = Log(M2 / M3)
d1 = (Log(M1 / K) + (sigma2d * T) / 2) / sigma * Sqr(T)
d2 = d1 - sigma * Sqr(T)
callArith = Exp(-r * T) * (M1 * Nd1 - K * Nd2)
Application.Range("E19").value = Application.Max(ExactCall, 0)
Are you trying to do the log of a negative number? Set a breakpoint and check variables before that line. Maybe you have an error before that generating a negative.
First check the argument to the Log function is positive.
Failing that, it could be due to a missing reference in the project. This manifests itself in this curious way. Have a look at "Tools", "References" and see if there is one missing.
You can write sigma2d = Vba.Log(M2 / M3) instead but that's only really a short fix since missing references will cause you headaches elsewhere.
One more thing, why not create a function instead, passing in all the variables as function parameters? Your spreadsheet will be more stable if you do that.
(Also, at the end of your code, d1 definition is incorrect. You need brackets around sigma * Sqr(T)).
I think you need a pair of () or do "/T" as you are multiplying by T here:
M1 = (Exp((r - div) * T) - 1) / (r - div) * T

Resources