sympy - differentiation over an abstract component - python

I'm working differentiating in sympy, thanks to this answer I'm almost done, but not quite.
I have the following code in variations:
From the answer
x = IndexedBase('x')
alpha, beta, gamma = symbols('alpha beta gamma', integer=True)
r = sqrt(x[alpha]**2 + x[beta]**2 + x[gamma]**2)
T0 = 1/r
i,j,k,l = symbols('i j k l')
T1 = diff(T0, x[i])
T1.subs(sqrt(x[alpha]**2 + x[beta]**2 + x[gamma]**2), 'r')
Using Vector class
V = CoordSys3D('V')
v = x[alpha]*R.i + x[beta]*R.j + x[gamma]*R.k
r = v.magnitude()
T0 = 1/r
T1 = diff(T0, x[i])
T1.subs(sqrt(x[alpha]**2 + x[beta]**2 + x[gamma]**2), 'r')
Both give the following answer:
However, this has a lot of unwanted delta functions, which only multiply in number after differentiating to higher orders.
Here, alpha beta gamma are just Cartesian components of a vector, and r is its length.
Knowing that, of course, those delta functions can never be simultaneously 1, I want to achieve this result:, where i is some of Cartesian components.
Is this possible?
Thanks!

Managed to do it!
That's the code sample:
x = IndexedBase('x')
alpha, beta, gamma, delta = symbols('alpha beta gamma delta', cls=Idx, range=3)
i = Idx('i', 3)
x_i = x[i]
r = sqrt(Sum(x_i**2, i))
T0 = 1/r
T1 = diff(T0, x[alpha])
T1.simplify().subs(sqrt(Sum(x_i**2, i).doit()), 'r')
Outputs:
-x[alpha]/r**3

Related

CFD simulation (with multiple for loops and matrix operations) is very slow to run. Looking to replace with faster numpy functions (or alternative)

As mentioned above, the function below works, however its very slow. I am very interested in using faster/optimised numpy (or other) vectorized alternatives. I have not posted the entire script here due to it being too large.
My specific question is - are there suitable numpy (or other) functions that I can use to 1) reduce run time and 2) reduce code volume of this function, specifically the for loop?
Edit: mass, temp, U and dpdh are functions that carry out simple algebraic calculations and return constants
def my_system(t, y, n, hIn, min, mAlumina, cpAlumina, sa, V):
dydt = np.zeros(3 * n) #setting up zeros array for solution (solving for [H0,Ts0,m0,H1,Ts1,m1,H2,Ts2,m2,..Hn,Tsn,mn])
# y = [h_0, Ts_0, m_0, ... h_n, Ts_n, m_n]
# y[0] = hin
# y[1] = Ts0
# y[2] = minL
i=0
## Using thermo
T = temp(y[i],P) #initial T
m = mass(y[i],P) #initial m
#initial values
dydt[i] = (min * (hIn - y[i]) + (U(hIn,P,min) * sa * (y[i + 1] - T))) / m # dH/dt (eq. 2)
dydt[i + 1] = -(U(hIn,P,min) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt from eq.3
dmdt = dydt[i] * dpdh(y[i], P) * V # dm/dt (holdup variation) eq. 4b
dydt[i + 2] = min - dmdt # mass flow out (eq.4a)
for i in range(3, 3 * n, 3): #starting at index 3, and incrementing by 3 because we are solving for 'triplets' [h,Ts,m] in each loop
## Using thermo
T = temp(y[i],P)
m = mass(y[i],P)
# [h, TS, mdot]
dydt[i] = (dydt[i-1] * (y[i - 3] - y[i]) + (U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T))) / m # dH/dt (eq.2), dydt[i-1] is the mass of the previous tank
dydt[i + 1] = -(U(y[i-3], P, dydt[i-1]) * sa * (y[i + 1] - T)) / (mAlumina * cpAlumina) # dTs/dt eq. (3)
dmdt = dydt[i] * dpdh(y[i], P) * V # Equation 4b
dydt[i + 2] = dydt[i-1] - dmdt # Equation 4a
return dydt
The functions mass, temp, U, and dpdh used inside the my_system function all take numbers as input, perform some simple algebraic operation and return a number (no need to optimise these I am just providing them for further context)
def temp(H,P):
# returns temperature given enthalpy (after processing function)
T = flasher.flash(H=H, P=P, zs=zs, retry=True).T
return T
def mass(H, P):
# returns mass holdup in mol
m = flasher.flash(H=H, P=P, zs=zs, retry=True).rho()*V
return m
def dpdh(H, P):
res = flasher.flash(H=H, P=P, zs=zs, retry=True)
if res.phase_count == 1:
if res.phase == 'L':
drho_dTf = res.liquid0.drho_dT()
else:
drho_dTf = res.gas.drho_dT()
else:
drho_dTf = res.bulk._equilibrium_derivative(of='rho', wrt='T', const='P')
dpdh = drho_dTf/res.dH_dT_P()
return dpdh
def U(H,P,m):
# Given T, P, m
air = Mixture(['nitrogen', 'oxygen'], Vfgs=[0.79, 0.21], H=H, P=P)
mu = air.mu*1000/mWAir #mol/m.s
cp = air.Cpm #J/mol.K
kg = air.k #W/m.K
g0 = m/areaBed #mol/m2.s
a = sa*n/vTotal #m^2/m^3 #QUESTIONABLE
psi = 1
beta = 10
pr = (mu*cp)/kg
re = (6*g0)/(a*mu*psi)
hfs = ((2.19*(re**1/3)) + (0.78*(re**0.619)))*(pr**1/3)*(kg)/diameterParticle
h = 1/((1/hfs) + ((diameterParticle/beta)/kAlumina))
return h
Reference Image:
enter image description here
For improving the speed, you can see Numba, which is useable if you use NumPy a lot but not every code can be used with Numba. Apart from that, the formulation of the equation system is confusing. You are solving 3 equations and adding the result to a single dydt list by 3 elements each. You can simply create three lists, solve each equation and add them to their respective list. For this, you need to re-write my_system as:
import numpy as np
def my_system(t, RHS, hIn, Ts0, minL, mAlumina, cpAlumina, sa, V):
# get initial boundary condition values
y1 = RHS[0]
y2 = RHS[1]
y3 = RHS[2]
## Using thermo
T = # calculate T
m = # calculate m
# [h, TS, mdot] solve dy1dt for h, dy2dt for TS and dy3dt for mdot
dy1dt = # dH/dt (eq.2), y1 corresponds to initial or previous value of dy1dt
dy2dt = # dTs/dt eq. (3), y2 corresponds to initial or previous value of dy2dt
dmdt = # Equation 4b
dy3dt = # Equation 4a, y3 corresponds to initial or previous value of dy3dt
# Left-hand side of ODE
LHS = np.zeros([3,])
LHS[0] = dy1dt
LHS[1] = dy2dt
LHS[2] = dy3dt
return LHS
In this function, you can pass RHS as a list with initial values ([dy1dt, dy2dt, dy3dt]) which will be unpacked as y1, y2, and y3 respectively and use them for respective differential equations. The solved equations (next values) will be saved to dy1dt, dy2dt, and dy3dt which will be returned as a list LHS.
Now you can solve this using scipy.integrate.odeint. Therefore, you can leave the for loop structure and solve the equations by using this method as follows:
hIn = #some val
Ts0 = #some val
minL = #some val
mAlumina = #some vaL
cpAlumina = #some val
sa = #some val
V = #some val
P = #some val
## Using thermo
T = temp(hIn,P) #initial T
m = mass(hIn,P) #initial m
#initial values
y01 = # calculate dH/dt (eq. 2)
y02 = # calculate dTs/dt from eq.3
dmdt = # calculate dm/dt (holdup variation) eq. 4b
y03 = # calculatemass flow out (eq.4a)
n = # time till where you want to solve the equation system
y0 = [y01, y02, y03]
step_size = 1
t = np.linspace(0, n, int(n/step_size)) # use that start time to which initial values corresponds
res = odeint(my_sytem, y0, t, args=(hIn, Ts0, minL, mAlumina, cpAlumina, sa, V,), tfirst=True)
print(res[:,0]) # print results for dH/dt
print(res[:,1]) # print results for dTs/dt
print(res[:,2]) # print results for Equation 4a
Here, I have passed all the initial values as y0 and chosen a step size of 1 which you can change as per your need.

Solve non-linear non homogeneous differential equation with python (Duffing oscillator)

I try to solve the Duffing equation using odeint:
def func(z, t):
q, p = z
return [p, F*np.cos(OMEGA * t) + Fm*np.cos(3*OMEGA * t) + 2*gamma*omega*p - omega**2 * q - beta * q**3]
OMEGA = 1.4
T = 1 / OMEGA
F = 0.2
gamma = 0.1
omega = -1.0
beta = 0.0
Fm = 0
z0 = [1, 0]
psi = 1 / ((omega**2 - OMEGA**2)**2 + 4*gamma**2*OMEGA**2)
wf = np.sqrt(omega**2 - gamma**2)
t = np.linspace(0, 100, 1000)
sol1 = odeintw(func, z0, t, atol=1e-13, rtol=1e-13, mxstep=1000)
When F = gamma = beta = 0 we have a system of two linear homogeneous equations. It's simple!
But when F not equal 0 the system becomes non homogeneous. The problem is that the numerical solution does not coincide with the analytical one:
Figure 1
the numerical solution does not take into account the inhomogeneous solution of the equation.
I have not been able to figure out if it is possible to use here solve_bvp function. Could you help me?
Inserting the constants, the equation becomes
x'' + 2*c*x' + x = F*cos(W*t)
The general solution form is
x(t)=A*cos(W*t)+B*sin(W*t)+exp(-c*t)*(C*cos(w*t)+D*sin(w*t))
w^2=1-c^2
for the particular solution one gets
-W^2*(A*cos(W*t)+B*sin(W*t))
+2*c*W*(B*cos(W*t)-A*sin(W*t))
+ (A*cos(W*t)+B*sin(W*t))
=F*cos(W*t)
(1-W^2)*A + 2*c*W*B = F
-2*c*W*A + (1-W^2)*B = 0
For the initial conditions it is needed that
A+C = 1
W*B-c*C+w*D=0
In python code thus
F=0.2; c=0.1; W=1.4
w=(1-c*c)**0.5
A,B = np.linalg.solve([[1-W*W, 2*c*W],[-2*c*W,1-W*W]], [F,0])
C = 1-A; D = (c*C-W*B)/w
print(f"w={w}, A={A}, B={B}, C={C}, D={D}")
with the output
w=0.99498743710662, A=-0.192, B=0.056, C=1.192, D=0.04100554286257586
continuing
t = np.linspace(0, 100, 1000)
u=odeint(lambda u,t:[u[1], F*np.cos(W*t)-2*c*u[1]-u[0]], [1,0],t, atol=1e-13, rtol=1e-13)
plt.plot(t,u[:,0],label="odeint", lw=3);
plt.plot(t,A*np.cos(W*t)+B*np.sin(W*t)+np.exp(-c*t)*(C*np.cos(w*t)+D*np.sin(w*t)), label="exact");
plt.legend(); plt.grid(); plt.show();
results in an exact fit

Different behaviour between MATLAB fmincon and scipy optimize minimize

I'm translating some code from MATLAB to python. This code simulate the behaviour of a model and I want to estimate parameters from it. The problem is that results obtained with python and with MATLAB are very different. I've tought it was related to the difference between the MATLAB's
fmincon and the python scipy.optimize.minimize function, but according to this tutorial that I've found on youtube (https://www.youtube.com/watch?v=SwogAa1719M) the results are almost the same,so problems must be in my code but I can't find them.
I report a minimum example of my code
def cost_function(parameters, measured, t, x0, total_active):
Tp = simulate(parameters, t, x0, total_active)
measured = np.squeeze(measured)
Tp = Tp[:,2]
return (np.sum(np.power((np.divide(np.diff(measured), measured[1:])-np.divide(np.diff(Tp),Tp[1:])),2)))
def SIR_model(x, t, params, total_active):
S0, _, R0 = x
v, tau, I0 = params
dSdt = - v * S0 * I0 / total_active(t)
dIdt = v * S0 * I0 / total_active(t) - g * I0 - tau * I0
dCdt = tau * I0
return [dSdt, dIdt, dCdt]
def simulate(p, t, x0, total_active):
T = np.zeros((len(t), 3))
T[0, :] = x0
for i in range(len(t) - 1):
ts = [t[i], t[i + 1]]
y = odeint(SIR_model, x0, ts, args=(p, total_active))
x0 = y[-1]
T[i + 1, :] = x0
return T
def identify_model(data, initial_guess, t, N, total_active):
# Set initial condition of the model
x0 = []
Q0 = data[0]
x0.append(N - initial_guess['It0'] - Q0)
x0.append(initial_guess['It0'])
x0.append(Q0)
It0 = initial_guess['It0']
v = initial_guess['v']
tau = initial_guess['tau']
lim_sup = [v * 10, tau * 1.5, It0 * 1.3]
lim_inf = [v * 0, tau * 0.9, It0 * 0.7]
bounds = Bounds(lim_inf, lim_sup)
options = {"maxiter": 1000,"ftol": 1e-08}
return minimize(cost_function, np.asarray([initial_guess['v'], initial_guess['tau'],initial_guess['It0']]),args=(data, t, x0, total_active), bounds=bounds,options=options, tol=1e-08,method="SLSQP")['x']
data=[275.5,317.,457.33333333,646.,888.66666667,1236.66666667,1619.33333333,2077.33333333,2542.33333333]
times = [i for i in range(len(data))]
total_active_data=[59999725.,59999684.33333334,59999558.66666666,59999385.33333334,59999158.33333333,59998823.,59998474.66666666,59998053.33333333,59997652.66666666]
total_active = interp1d([i for i in range(len(total_active_data))], total_active_data, fill_value="extrapolate")
initial_guess = {"v": 0.97, "tau": 0.066, "It0": 100}
print(identify_model(data,initial_guess,times,60e6,total_active))
This snippet gives, as result, [0.97099097,0.099,130.].
The (I hope so) equivalent MATLAB code is:
function [pars] = Identify_Model(data,initial_guess,lim_inf,lim_sup,times,N,total_active)
scalefac=100;
%Initialize the initial guess for each parameter
v=initial_guess.v;
It0=initial_guess.It0;
tau=initial_guess.tau;
g=0.07;
lim_inf(3)=lim_inf(3)/scalefac;
lim_sup(3)=lim_sup(3)/scalefac;
%Identify the model parameters
options=optimset('MaxIter',100,'TolFun',1e-6,'TolX',1e-6);
parmin=fmincon(#(pars) error_SIR(data,[pars(1),pars(2),g,scalefac*pars(3)],times,N,total_active),[v,tau,It0],[],[],[],[],lim_inf,lim_sup,[],options);
pars=num2cell([parmin(1:2),scalefac*parmin(3)]);
pars=[pars(1),pars(2),g,pars(3)];
end
function [costo]=error_SIR(data,pars,tempi,N,totale_attivi)
%Assign the parameters
pars=num2cell(pars);
[v,tau,g,I0]=pars{:};
%Simulate the model
Q0=data(1,1);
S0=N-I0-Q0;
[~,y]=ode45(#(t,x) SIR(t,x,v,tau,g,totale_attivi),tempi,[S0;I0;Q0]);
if length(tempi)==2
y=[y(1,:);y(end,:)];
end
%Identify on the normalized data (Data/mean data)
costo=sum(((diff(sum(data,1))./sum(data(:,2:end),1))-(diff(sum(y(:,3),2))./sum(y(2:end,3),2))').^2);
end
function y=SIR(t,x,v,tau,g,total_active)
y=zeros(3,1);
y(1)=-v*x(1)*x(2)/total_active(t); % S
y(2)=v*x(1)*x(2)/total_active(t)-(tau+g)*x(2); % I
y(3)=tau*x(2); % C
end
total_active_data=[59999725.,59999684.333,59999558.666,59999385.333,59999158.33,59998823.,59998474.66,59998053.333,59997652.66666666]
total_active = #(t) interp1(1:9,total_active_data,t);
initial_guess.It0=100;
initial_guess.v=0.97;
initial_guess.g=0.07;
initial_guess.tau=0.066;
g=initial_guess.g;
It0=initial_guess.It0;
v=initial_guess.v;
tau=initial_guess.tau;
N=60e6
%Define the constrints for the identification
lim_sup=[v*10, tau*1.5, It0*1.3];
lim_inf=[v*0, tau*0.9, It0*0.7];
data=[275.5,317.,457.33333333,646.,888.66666667,1236.66666667,1619.33333333,2077.33333333,2542.33333333]
times=1:9;
%identify the model parameters
pars=Identify_Model(data,initial_guess,lim_inf,lim_sup,times,N,total_active)
And the result is {[0.643004812025865]} {[0.0989999761533351]} {[0.07]} {[129.9999687237]} (don't consider the value 0.07, it's fixed). I thought that the problem could linked to the fact that I want to minimize a non-convex function, and maybe fmincon is more powerful than the scipy.optimize.minimize function?

Specify Initial Values for For Loop

I am currently working on coding a recursive algorithm. I created functions similar to the one below with a lot of for loops.
def create_m_(y, m, theta, mc, theta_mc):
for i in range (len(data[0])-1):
m = create_m(y)[i]
theta = theta_m[i]
mc = create_mc(y)[i-1]
create_theta_mc[i-1]
m_ = (m * theta_mc + mc * theta + m * mc)
return m_
The only thing is: the initial values of mc and theta_mc that will start the loop are not calculated using the formulas in the function (these initial values are specific cases). How do I got about setting the loop to specify the first values of mc and theta_mc?
EDIT:
Can I do this?
def create_m_(y, m, theta, mc, theta_mc):
for i in range (len(data[0])-1):
if i == 1:
m = m_2
theta = theta_m_2
else:
m = create_m(y)[i]
theta = theta_m[i]
mc = create_mc(y)[i-1]
create_theta_mc[i-1]
m_ = (m * theta_mc + mc * theta + m * mc)
return m_
Just change the function
def create_m_(y, m, theta, mc, theta_mc)
to:
def create_m_(y, m, theta, mc=initial_value, theta_mc=initial_value)
When you call this function for the first time without specifying the value of mc or theta-mc, they will default to the initial value.

Numpy/Scipy Solve simulataneous equations with integrals in them

I am trying to use numpy and scipy to solve the following two equations:
P(z) = sgn(-cos(np.pi*D1) + cos(5*z)) * sgn(-cos(np.pi*D2) + cos(6*z))
1. 0 = 2/2pi ∫ P(z,D1,D2) * cos(5z) dz + z/L
2. 0 = 2/2pi ∫ P(z,D1,D2) * cos(6z) dz - z/L
for D1 and D2 (integral limits are 0 -> 2pi).
My code is:
def equations(p, z):
D1, D2 = p
period = 2*np.pi
P1 = lambda zz, D1, D2: \
np.sign(-np.cos(np.pi*D1) + np.cos(6.*zz)) * \
np.sign(-np.cos(np.pi*D2) + np.cos(5.*zz)) * \
np.cos(6.*zz)
P2 = lambda zz, D1, D2: \
np.sign(-np.cos(np.pi*D1) + np.cos(6.*zz)) * \
np.sign(-np.cos(np.pi*D2) + np.cos(5.*zz)) * \
np.cos(5.*zz)
eq1 = 2./period * integrate.quad(P1, 0., period, args=(D1,D2), epsabs=0.01)[0] + z
eq2 = 2./period * integrate.quad(P2, 0., period, args=(D1,D2), epsabs=0.01)[0] - z
return (eq1, eq2)
z = np.arange(0., 1000., 0.01)
N = int(len(z))
D1 = np.empty([N])
D2 = np.empty([N])
for i in range(N):
D1[i], D2[i] = fsolve(equations, x0=(0.5, 0.5), args=z[i])
print D1, D2
Unfortunately, it does not seem to converge. I don't know much about numerical methods and was hoping someone could give me a hand.
Thank you.
P.S. I'm also trying the following which should be equivalent:
import numpy as np
from scipy.optimize import fsolve
from scipy import integrate
from scipy import signal
def equations(p, z):
D1, D2 = p
period = 2.*np.pi
K12 = 1./L * z
K32 = -1./L * z + 1.
P1 = lambda zz, D1, D2: \
signal.square(6.*zz, duty=D1) * \
signal.square(5.*zz, duty=D2) * \
np.cos(6.*zz)
P2 = lambda zz, D1, D2: \
signal.square(6.*zz, duty=D1) * \
signal.square(5.*zz, duty=D2) * \
np.cos(5.*zz)
eq1 = 2./period * integrate.quad(P1, 0., period, args=(D1,D2))[0] + K12
eq2 = 2./period * integrate.quad(P2, 0., period, args=(D1,D2))[0] - K32
return (eq1, eq2)
h = 0.01
L = 10.
z = np.arange(0., L, h)
N = int(len(z))
D1 = np.empty([N])
D2 = np.empty([N])
for i in range(N):
D1[i], D2[i] = fsolve(equations, x0=(0.5, 0.5), args=z[i])
print
print z[i]
print ("%0.8f,%0.8f" % (D1[i], D2[i]))
print
PSS:
I implemented what you wrote (I think I understand it!), very nicely done. Thank you. Unfortunately, I really don't have much skill in this field and don't really know how to make a suitable guess, so I just guess 0.5 (I also added a small amount of noise to the initial guess to try and improve it). The result I'm getting have numerical errors it seems, and I'm not sure why, I was hoping you could point me in the right direction. So essentially, I did an FFT sweep (did an FFT for each dutycycle variation and looked at the frequency component at 5, which is shown below in the graph) and found that the linear part (z/L) is slightly jagged.
PSSS:
Thank you for that, I've noted some of the techniques you've suggested. I tried replicated your second graph as it seems very useful. To do this, I kept D1 (D2) fixed and swept D2 (D1), and I did this for various z values. fmin did not always find the correct minimum (it was dependent on the initial guess) so I swept the initial guess of fmin until I found the correct answer. I get a similar answer to you. (I think it's correct?)
Also, I would just like to say that you might like to give me your contact details, as this solution as a step in finding the solution to a problem I have (I'm a student doing research), and I will most certainly acknowledge you in any papers in which this code is used.
#!/usr/bin/env python
import numpy as np
from scipy.optimize import fsolve
from scipy import integrate
from scipy import optimize
from scipy import signal
######################################################
######################################################
altsigns = np.ones(50)
altsigns[1::2] = -1
def get_breaks(x, y, a, b):
sa = np.arange(0, 2*a, 2)
sb = np.arange(0, 2*b, 2)
zx = (( x + sa) % (2*a))*np.pi/a
zx2 = ((-x + sa) % (2*a))*np.pi/a
zy = (( y + sb) % (2*b))*np.pi/b
zy2 = ((-y + sb) % (2*b))*np.pi/b
zi = np.r_[np.sort(np.hstack((zx, zx2, zy, zy2))), 2*np.pi]
if zi[0]:
zi = np.r_[0, zi]
return zi
def integrals(x, y, a, b):
zi = get_breaks(x % 1., y % 1., a, b)
sins = np.vstack((np.sin(b*zi), np.sin(a*zi)))
return (altsigns[:zi.size-1]*(sins[:,1:] - sins[:,:-1])).sum(1) / np.array((b, a))
def equation1(p, z, d2):
D2 = d2
D1 = p
I1, _ = integrals(D1, D2, deltaK1, deltaK2)
eq1 = 1. / np.pi * I1 + z
return abs(eq1)
def equation2(p, z, d1):
D1 = d1
D2 = p
_, I2 = integrals(D1, D2, deltaK1, deltaK2)
eq2 = 1. / np.pi * I2 - z + 1
return abs(eq2)
######################################################
######################################################
z = [0.2, 0.4, 0.6, 0.8, 1.0]#np.arange(0., 1., 0.1)
step = 0.05
deltaK1 = 5.
deltaK2 = 6.
f = open('data.dat', 'w')
D = np.arange(0.0, 1.0, step)
D1eq1 = np.empty([len(D)])
D2eq2 = np.empty([len(D)])
D1eq1Err = np.empty([len(D)])
D2eq2Err = np.empty([len(D)])
for n in z:
for i in range(len(D)):
# Fix D2 and solve for D1.
for guessD1 in np.arange(0.,1.,0.1):
D2 = D
tempD1 = optimize.fmin(equation1, guessD1, args=(n, D2[i]), disp=False, xtol=1e-8, ftol=1e-8, full_output=True)
if tempD1[1] < 1.e-6:
D1eq1Err[i] = tempD1[1]
D1eq1[i] = tempD1[0][0]
break
else:
D1eq1Err[i] = -1.
D1eq1[i] = -1.
# Fix D1 and solve for D2.
for guessD2 in np.arange(0.,1.,0.1):
D1 = D
tempD2 = optimize.fmin(equation2, guessD2, args=(n, D1[i]), disp=False, xtol=1e-8, ftol=1e-8, full_output=True)
if tempD2[1] < 1.e-6:
D2eq2Err[i] = tempD2[1]
D2eq2[i] = tempD2[0][0]
break
else:
D2eq2Err[i] = -2.
D2eq2[i] = -2.
for i in range(len(D)):
f.write('%0.8f,%0.8f,%0.8f,%0.8f,%0.8f\n' %(D[i], D1eq1[i], D2eq2[i], D1eq1Err[i], D2eq2Err[i]))
f.write('\n\n')
f.close()
This is a very ill-posed problem. Let's recap what you are trying to do:
You want to solve 100000 optimization problems
Each optimization problem is 2 dimensional, so you need O(10000) function evaluations (estimating O(100) function evaluations for a 1D optimization problem)
Each function evaluation depends on the evaluation of two numerical integrals
The integrands contain jumps, i.e. they are 0-times contiguously differentiable
The integrands are composed of periodic functions, so they have multiple minima and maxima
So you are off to a very hard time. In addition, even in the most optimistic estimate in which all factors in the integrand that are < 1 are replaced by 1, the integrals can only take values between -2*pi and 2*pi. Much less than that in reality. So you can already see that you only have a chance of a solution for
I1 - z = 0
I2 + z = 0
for very small numbers of z. So there is no point in trying up to z = 1000.
I am almost certain that this is not the problem you need to solve. (I cannot imagine a context in which such a problem would appear. It seems like a weird twist on Fourier coefficient computation...) But in case you insist, your best bet is to work on the inner loop first.
As you noted, the numerical evaluation of the integrals is subject to large errors. This is due to the jumps introduced by the sgn() function. Functions such as scipy.integrate.quad() tend to use higher order algorithms which assume that the integrands are smooth. If they are not, they perform very badly. You either need to hand-pick an algorithm that can deal with jumps or, much better in this case, do the integrals by hand:
The following algorithm calculates the jump points of the sgn() function and then evaluates the analytic integrals on all pieces:
altsigns = np.ones(50)
altsigns[1::2] = -1
def get_breaks(x, y, a, b):
sa = np.arange(0, 2*a, 2)
sb = np.arange(0, 2*b, 2)
zx = (( x + sa) % (2*a))*np.pi/a
zx2 = ((-x + sa) % (2*a))*np.pi/a
zy = (( y + sb) % (2*b))*np.pi/b
zy2 = ((-y + sb) % (2*b))*np.pi/b
zi = np.r_[np.sort(np.hstack((zx, zx2, zy, zy2))), 2*pi]
if zi[0]:
zi = np.r_[0, zi]
return zi
def integrals(x, y, a, b):
zi = get_breaks(x % 1., y % 1., a, b)
sins = np.vstack((np.sin(b*zi), np.sin(a*zi)))
return (altsigns[:zi.size-1]*(sins[:,1:] - sins[:,:-1])).sum(1) / np.array((b, a))
This gets rid of the problem of the numerical integration. It is very accurate and fast. However, even the integrals will not be perfectly contiguous for all parameters, so in order to solve your optimization problem, you are better off using an algorithm that doesn't rely on the existence of any derivatives. The only choice in scipy is scipy.optimize.fmin(), which you can use like:
def equations2(p, z):
x, y = p
I1, I2 = integrals(x, y, 6., 5.)
fact = 1. / pi
eq1 = fact * I1 + z
eq2 = fact * I2 - z
return eq1, eq2
def norm2(p, z):
eq1, eq2 = equations2(p, z)
return eq1**2 + eq2**2 # this has the minimum when eq1 == eq2 == 0
z = 0.25
res = fmin(norm2, (0.25, 0.25), args=(z,), xtol=1e-8, ftol=1e-8)
print res
# -> [ 0.3972 0.5988]
print equations2(res, z)
# -> (-2.7285737558280232e-09, -2.4748670890417657e-09)
You are still left with the problem of finding suitable starting values for all z, which is still a tricky business. Good Luck!
Edit
To check if you still have numerical errors, plug the result of the optimization back in the equations and see if they are satisfied to the required accuracy, which is what I did above. Note that I used (0.25, 0.25) as a starting value, since starting at (0.5, 0.5) didn't lead to convergence. This is normal for optimizations problems with local minima (such as yours). There is no better way to deal with this other than trying multiple starting values, rejecting non-converged results. In the case above, if equations2(res, z) returns anything higher than, say, (1e-6, 1e-6), I would reject the result and try again with a different starting value. A very useful technique for successive optimization problems is to use the result of the previous problem as the starting value for the next problem.
Note however that you have no guarantee of a smooth solution for D1(z) and D2(z). Just a tiny change in D1 could push one break point off the integration interval, resulting in a big change of the value of the integral. The algorithm may well adjust by using D2, leading to jumps in D1(z) and D2(z). Note also that you can take any result modulo 1, due to the symmetries of cos(pi*D1).
The bottom line: There shouldn't be any remaining numerical inaccuracies if you use the analytical formula for the integrals. If the residuals are less than the accuracy you specified, this is your solution. If they are not, you need to find better starting values. If you can't, a solution may not exist. If the solutions are not contiguous as a function of z, that is also expected, since your integrals are not contiguous. Good luck!
Edit 2
It appears your equations have two solutions in the interval z in [0, ~0.46], and no solutions for z > 0.46, see the first figure below. To prove this, see the good old graphical solution in the second figure below. The contours represent solutions of Eq. 1 (vertical) and Eq. 2 (horizontal), for different z. You can see that the contours cross twice for z < 0.46 (two solutions) and not at all for z > 0.46 (no solution that simultaneously satisfies both equations). If this is not what you expected, you need to write down different equations (which was my suspicion in the first place...)
Here is the final code I was using:
import numpy as np
from numpy import sin, cos, sign, pi, arange, sort, concatenate
from scipy.optimize import fmin
a = 6.0
b = 5.0
def P(z, x, y):
return sign((cos(a*z) - cos(pi*x)) * (cos(b*z) - cos(pi*y)))
def P1(z, x, y):
return P(z, x, y) * cos(b*z)
def P2(z, x, y):
return P(z, x, y) * cos(a*z)
altsigns = np.ones(50)
altsigns[1::2] = -1
twopi = 2*pi
pi_a = pi/a
da = 2*pi_a
pi_b = pi/b
db = 2*pi_b
lim = np.array([0., twopi])
def get_breaks(x, y):
zx = arange(x*pi_a, twopi, da)
zx2 = arange((2-x)*pi_a, twopi, da)
zy = arange(y*pi_b, twopi, db)
zy2 = arange((2-y)*pi_b, twopi, db)
zi = sort(concatenate((lim, zx, zx2, zy, zy2)))
return zi
ba = np.array((b, a))[:,None]
fact = np.array((1. / b, 1. / a))
def integrals(x, y):
zi = get_breaks(x % 1., y % 1.)
sins = sin(ba*zi)
return fact * (altsigns[:zi.size-1]*(sins[:,1:] - sins[:,:-1])).sum(1)
def equations2(p, z):
x, y = p
I1, I2 = integrals(x, y)
fact = 1. / pi
eq1 = fact * I1 + z
eq2 = fact * I2 - z
return eq1, eq2
def norm2(p, z):
eq1, eq2 = equations2(p, z)
return eq1**2 + eq2**2
def eval_integrals(Nx=100, Ny=101):
x = np.arange(Nx) / float(Nx)
y = np.arange(Ny) / float(Ny)
I = np.zeros((Nx, Ny, 2))
for i in xrange(Nx):
xi = x[i]
Ii = I[i]
for j in xrange(Ny):
Ii[j] = integrals(xi, y[j])
return x, y, I
def solve(z, start=(0.25, 0.25)):
N = len(z)
res = np.zeros((N, 2))
res.fill(np.nan)
for i in xrange(N):
if i < 100:
prev = start
prev = fmin(norm2, prev, args=(z[i],), xtol=1e-8, ftol=1e-8)
if norm2(prev, z[i]) < 1e-7:
res[i] = prev
else:
break
return res
#x, y, I = eval_integrals(Nx=1000, Ny=1001)
#zlvl = np.arange(0.2, 1.2, 0.2)
#contour(x, y, -I[:,:,0].T/pi, zlvl)
#contour(x, y, I[:,:,1].T/pi, zlvl)
N = 1000
z = np.linspace(0., 1., N)
res = np.zeros((N, 2, 2))
res[:,0,:] = solve(z, (0.25, 0.25))
res[:,1,:] = solve(z, (0.05, 0.95))

Categories

Resources