Finding unknowns in function from known output using Scipy/Python - python

I have the following python function required inputs a and b.
def interimfunc(x,y,z):
#this is a dummy function - not a part of the actual question but included for completeness.
#the actual function involves some statistical treatment - but that is not a problem here.
sol = x*y+z
return sol
def finalfunc(a, b):
interimsol1 = interimfunc(0.4,a,b)
interimsol2 = interimfunc(0.8,a,b)
finalsol = interimsol1/interimsol2
return finalsol
if finalsol is a known value.
How do I find out the unknowns "a" and "b" by solving non-linear system of equations??
===
I got 4 down-votes after posting this. I am a mechanical engineer and learning computer science. I did try researching on the internet but need to refine my search - hence I asked a question to experts here.
===
In addition to above equations - there is one more information:
interimsol2 = interimfunc(0.8,a,b)
where interimsol2 = 10 #i.e. known value
How do we inclde this new information in our unknown finding?
===
Actual problem as requested below by #SergeyIvanov
def func(mu, sigma):
tenpercent = st.norm.ppf(2, mu, sigma)
ninetypercent = st.norm.ppf(2, mu, sigma)
rfs = tenpercent/ninetypercent
return rfs

I think you should solve a system of nonlinear equations. This code should solve your problem in case of two equations for two known solutions (of course you can extend it):
from scipy.optimize import fsolve
known_values = [3,5]
def interimfunc(x,y,z):
sol = x*y+z
return sol
def finalfunc(a, b):
interimsol1 = interimfunc(0.4,a,b)
interimsol2 = interimfunc(0.8,a,b)
finalsol = interimsol1/interimsol2
return finalsol
def equations(p):
a, b = p
return (finalfunc(a,b) - known_values[0], # finalfunc(a,b) == solution1
finalfunc(a,b) - known_values[1]) # finalfunc(a,b) == solution2
a, b = fsolve(equations, (1, 1))# solution
print(a,b)
# -6192.07497308 5779.26987919
print(equations((a, b)))
# (1.0000003476651482, -0.99999965233485177) <-- bad convergence beacause there is no free paremeter in finalfunc.
But it works only with equal known_values, that is pointless (solution will be a random combination of a and b). The problem is that you should have something to distinguish two equations finalfunc (e.g. additional parameter), because you can get diffrent solutions only with different arguments. So finally you should have something like this:
from scipy.optimize import fsolve
def interimfunc(x,y,z):
sol = x*y+z
return sol
def finalfunc(a, b, c ):
interimsol1 = interimfunc(0.4,a,b) + c
interimsol2 = interimfunc(0.8,a,b) + c
finalsol = interimsol1/interimsol2
return finalsol
known_values = [0.8260869565217391,0.8333333333333334]
def equations(p):
a, b = p
return (finalfunc(a,b,0) - known_values[0], # finalfunc(a,b,c) == solution1
finalfunc(a,b,1) - known_values[1]) # finalfunc(a,b,c) == solution2
a, b = fsolve(equations, (1, 1))# solution
print(a,b)
print(equations((a, b)))
# 10.0 15.0 <-- correct values
# (4.4408920985006262e-16, 2.2204460492503131e-16) <-- good convergence
For last example:
from scipy.optimize import fsolve
import scipy.stats as st
def equations(p):
mu, sigma = p
tenpercent = st.norm.ppf(2, mu, sigma)
ninetypercent = st.norm.ppf(2, mu, sigma)
return (ninetypercent - 500,
tenpercent / ninetypercent - 1.0)
mu, sigma = fsolve(equations,x0=(100, 10))# solution
print("mu, sigma:",mu, sigma)
print(equations((mu, sigma)))
The problem here is that ppf can generate nan and ruin an optimization process. So guess values should be proposed very carefully.

Related

Numerical Python: Solving a BVP with a boolean condition?

I am not sure about the best way to ask this question, but I am trying to find the long-term state of an ODE system with an arbitrary extra constraint that needs to be fulfilled.
Ex:
import numpy as np
from scipy.integrate import solve_ivp
import matplotlib.pyplot as plt
def f(t, X, a, b):
return a*X + b
N = 5
X0 = np.random.randn(N)
t_range = [0, 1]
a = 3
b = 4
sol = solve_ivp(f, t_range, X0, args=(a, b))
for i in range(N):
plt.plot(sol['t'], sol['y'][i, :])
In the code above, f is the right-hand-side of my ODE model. N is the dimension of my state vector X, t_range is the interval I want to view the values of X over, X0 is my initial condition, and a and b are just arbitrary model parameters.
The code written produces the following chart:
However, let's say I didn't want to evaluate X over some fixed t_range. Let's say I wanted to keep solving the ODE until some condition was satisfied:
def bc(X) -> bool:
""" True means we stop solving. """
return (X < 0).any()
In other words, I want to do something like this:
X = X0
while not bc(X):
X = new value from ODE solver
Is there an easy way to do this in scipy? If not, is there a straightforward way to implement this?
Please let me know if I can clarify anything better.
#Lutz Lehmann answered my question in the comments.
If I wanted the solver to stop when one entry in X became < 0:
event = lambda t, X, a, b : min(X) # account for when args are passed to event func
event.terminal # tells it to stop
sol = solve_ivp(f, t_range, X0, args=(a, b), events = event)

Double Direct Integration

I am trying to solve the set of coupled boundary value problems such that;
U'' +aB'+ b*(cosh(lambda z))^{-2}tanh(lambda*z) = 0,
B'' + c*U' = 0,
T'' = (gamma^{-1} - 1)*(d*(U')^2 + e*(B')^2)
subject to the boundary conditions U(+/- 1/2) = +/-U_0*tanh(lambda/2), B(+/- 1/2) = 0 and T(-1/2) = 1, T(1/2) = 4. I have decomposed this set of equations into a set of first order differential equations, and used the derivative array such that [U, U', B, B', T, T']. But bvp solve is returning the error that I have a single Jacobian. When I remove the last two equations, I get a solution for U and B and that works fine. However, I am unsure why adding the other two equations results in this issue.
import numpy as np
from scipy.integrate import solve_bvp
import matplotlib.pyplot as plt
%matplotlib inline
alpha = 1E-7
zeta = 8E-3
C_k = 0.01
sigma = 0.005
Q = 30
U_0 = 0.1
gamma = 5/3
theta = 3
def fun(x, y):
return y[1], -2*U_0*Q**2*(1/np.cosh(Q*x))**2*np.tanh(Q*x)-((alpha)/(C_k*sigma))*y[3], y[3],\
-(1/(C_k*zeta))*y[1], y[4], (1/gamma - 1)*(sigma*(y[1])**2 + zeta*alpha*(y[3])**2)
def bc(ya, yb):
return ya[0]+U_0*np.tanh(Q*0.5), yb[0]-U_0*np.tanh(Q*0.5), ya[2]-0, yb[2]-0, ya[4] - 1, yb[4] - 4
x = np.linspace(-0.5, 0.5, 500)
y = np.zeros((6, x.size))
sol = solve_bvp(fun, bc, x, y)
print(sol)
However, the error that I am getting is that 'setting an array with sequence'. The first function and boundary conditions solves two coupled equations, then I use these results to evaluate the equation I have given. I have tried writing all of my equations in one function, however this seems to be returning trivial solutions i.e an array full of zeros.
Any help would be appreciated.
When the expressions become larger it is often more helpful to keep the computations human readable instead of compact.
def fun(x, y):
U, dU, B, dB, T, dT = y;
d2U = -2*U_0*Q**2*(1/np.cosh(Q*x))**2*np.tanh(Q*x)-((alpha)/(C_k*sigma))*dB;
d2B = -(1/(C_k*zeta))*dU;
d2T = (1/gamma - 1)*(sigma*dU**2 + zeta*alpha*dB**2);
return dU, d2U, dB, d2B, dT, d2T
This avoids missing an index error as there are no indices in this computation, all has names close to the original formulas.
Then the solution components (using initialization with only 5 points, resulting in a refinement with 65 points) plots as

Improving accuracy in scipy.optimize.fsolve with equations involving integration

I'm trying to solve an integral equation using the following code (irrelevant parts removed):
def _pdf(self, a, b, c, t):
pdf = some_pdf(a,b,c,t)
return pdf
def _result(self, a, b, c, flag):
return fsolve(lambda t: flag - 1 + quad(lambda tau: self._pdf(a, b, c, tau), 0, t)[0], x0)[0]
Which takes a probability density function and finds a result tau such that the integral of pdf from tau to infinity is equal to flag. Note that x0 is a (float) estimate of the root defined elsewhere in the script. Also note that flag is an extremely small number, on the order of 1e-9.
In my application fsolve only successfully finds a root about 50% of the time. It often just returns x0, significantly biasing my results. There is no closed form for the integral of pdf, so I am forced to integrate numerically and feel that this might be introducing some inaccuracy?
EDIT:
This has since been solved using a method other than that described below, but I'd like to get quadpy to work and see if the results improve at all. The specific code I'm trying to get to work is as follows:
import quadpy
import numpy as np
from scipy.optimize import *
from scipy.special import gammaln, kv, gammaincinv, gamma
from scipy.integrate import quad, simps
l = 226.02453163
mu = 0.00212571582056
nu = 4.86569872444
flag = 2.5e-09
estimate = 3 * mu
def pdf(l, mu, nu, t):
return np.exp(np.log(2) + (l + nu - 1 + 1) / 2 * np.log(l * nu / mu) + (l + nu - 1 - 1) / 2 * np.log(t) + np.log(
kv(nu - l, 2 * np.sqrt(l * nu / mu * t))) - gammaln(l) - gammaln(nu))
def tail_cdf(l, mu, nu, tau):
i, error = quadpy.line_segment.adaptive_integrate(
lambda t: pdf(l, mu, nu, t), [tau, 10000], 1.0e-10
)
return i
result = fsolve(lambda tau: flag - tail_cdf(l, mu, nu, tau[0]), estimate)
When I run this I get an assertion error from assert all(lengths > minimum_interval_length). I'm not quite sure of how to remedy this; any help would be very much appreciated!
As an example, I tried 1 / x for the integration between 1 and alpha to retrieve the target integral 2.0. This
import quadpy
from scipy.optimize import fsolve
def f(alpha):
beta, _ = quadpy.quad(lambda x: 1.0/x, 1, alpha)
return beta
target = 2.0
res = fsolve(lambda alpha: target - f(alpha), x0=2.0)
print(res)
correctly returns 7.38905611.
The failing quadpy assertion
assert all(lengths > minimum_interval_length)
you're getting means that the adaptive integration hit its limit: Either relax your tolerance a bit, or decrease the minimum_interval_length (see here).

Fitting data to numerical solution of an ode in python

I have a system of two first order ODEs, which are nonlinear, and hence difficult to solve analytically in a closed form. I want to fit the numerical solution to this system of ODEs to a data set. My data set is for only one of the two variables that are part of the ODE system. How do I go about this?
This didn't help because there's only one variable there.
My code which is currently leading to an error is:
import numpy as np
from scipy.integrate import odeint
from scipy.optimize import curve_fit
def f(y, t, a, b, g):
S, I = y # S, I are supposed to be my variables
Sdot = -a * S * I
Idot = (a - b) * S * I + (b - g - b * I) * I
dydt = [Sdot, Idot]
return dydt
def y(t, a, b, g, y0):
y = odeint(f, y0, t, args=(a, b, g))
return y.ravel()
I_data =[] # I have data only for I, not for S
file = open('./ratings_showdown.csv')
for e_raw in file.read().split('\r\n'):
try:
e=float(e_raw); I_data.append(e)
except ValueError:
continue
data_t = range(len(I_data))
popt, cov = curve_fit(y, data_t, I_data, [.05, 0.02, 0.01, [0.99,0.01]])
#want to fit I part of solution to data for variable I
#ERROR here, ValueError: setting an array element with a sequence
a_opt, b_opt, g_opt, y0_opt = popt
print("a = %g" % a_opt)
print("b = %g" % b_opt)
print("g = %g" % g_opt)
print("y0 = %g" % y0_opt)
import matplotlib.pyplot as plt
t = np.linspace(0, len(data_y), 2000)
plt.plot(data_t, data_y, '.',
t, y(t, a_opt, b_opt, g_opt, y0_opt), '-')
plt.gcf().set_size_inches(6, 4)
#plt.savefig('out.png', dpi=96) #to save the fit result
plt.show()
This type of ODE fitting becomes a lot easier in symfit, which I wrote specifically as a user friendly wrapper to scipy. I think it will be very useful for your situation because the decreased amount of boiler-plate code simplifies things a lot.
From the docs and applied roughly to your problem:
from symfit import variables, parameters, Fit, D, ODEModel
S, I, t = variables('S, I, t')
a, b, g = parameters('a, b, g')
model_dict = {
D(S, t): -a * S * I,
D(I, t): (a - b) * S * I + (b - g - b * I) * I
}
ode_model = ODEModel(model_dict, initial={t: 0.0, S: 0.99, I: 0.01})
fit = Fit(ode_model, t=tdata, I=I_data, S=None)
fit_result = fit.execute()
Check out the docs for more :)
So I figured out the problem.
The curve_fit() function apparently returns a list as it's second return value. So, instead of passing the initial conditions as a list [0.99,0.01], I passed them separately as 0.99 and 0.01.

Using nquad for a double integral

Having a problem here. Here's my code so far:
from scipy import integrate
import math
import numpy as np
a = 0.250
s02 = 214.0
a_s = 0.0163
def integrand(r, R, s02, a_s, a):
return 2.0 * r * (r/a)**(-0.1) * (1.0 + (r**2/a**2))**(-2.45)\\
*(math.sqrt(r**2 - R**2))**(-1.0) * (a_s/(1 + (R-0.0283)**2/a_s**2 ))
def bounds_R(s02, a_s, a):
return [0, np.inf]
def bounds_r(R, s02, a_s, a):
return [R, np.inf]
result = integrate.nquad(integrand, [bounds_r(R, s02, a_s, a), bounds_R(s02, a_s, a)])
a, s02 and a_s are constants. I need to perform the first integral over r, and then the second integral over R. The problem I think is the fact that R appears in the limits for the first integral (something called an Abel transform). Tried a few things and every time getting an error that there are either too few arguments in the boundary functions or too little.
Please help!
If you write integrate.nquad(integrand, [bounds_r(R, s02, a_s, a), bounds_R(s02, a_s, a)]), python is expecting you to affect a value to R. But you didn't because integration is carried out over R.
This syntax should work :
result = integrate.nquad(integrand, [bounds_r, bounds_R], args=(s02,a_s,a))
Take a look to the second example in the documentation of integrate.nquad.

Categories

Resources