Related
The question body:
A skydiver of mass m in a vertical free fall experiences an aerodynamic drag force F=cy'² ('c times y prime square') where y is measured downward from the start of the fall, and y is a function of time (y' denotes the derivative of y w.r.t time). The differential equation describing the fall is:
y''=g-(c/m)y'²
(where g = 9.80665 m/s^2; c = 0.2028 kg/m; m = 80 kg). And y(0)=y'(0)=0 as this is a free fall.
Task: The function must return the time of a fall of x meters, where x is the parameter of the function. The values of g, c and m are given below.
The Runge-Kutta function is defined as follows:
from numpy import *
def runge_kutta_4(F, x0, y0, x, h):
'''
Return y(x) given the following initial value problem:
y' = F(x, y)
y(x0) = y0 # initial conditions
h is the increment of x used in integration
F = [y'[0], y'[1], ..., y'[n-1]]
y = [y[0], y[1], ..., y[n-1]]
'''
X = []
Y = []
X.append(x0)
Y.append(y0)
while x0 < x:
k0 = F(x0, y0)
k1 = F(x0 + h / 2.0, y0 + h / 2.0 * k0)
k2 = F(x0 + h / 2.0, y0 + h / 2 * k1)
k3 = F(x0 + h, y0 + h * k2)
y0 = y0 + h / 6.0 * (k0 + 2 * k1 + 2.0 * k2 + k3)
x0 += h
X.append(x0)
Y.append(y0)
return array(X), array(Y)
And this is what I've done so far:
def prob_1_8(x)
g = 9.80665 # m/s**2
c = 0.2028 # kg/m
m = 80 # kg
def F(x, y):
return array([
y[1],
g - (c / m) * ((y[1]) ** 2)
])
X, Y = runge_kutta_4(F, 0, array([0, 0]), 5000, 1000)
for i in range(len(X)):
if X[i] == 5000:
return Y[i]
However, when I tried to print prob_1_8(5000), the number looks ridiculous and it displayed:
RuntimeWarning: overflow encountered in double_scalars.
According to the answer provided, I should get a value close to 84.8 when x=5000. Can someone help me with this? I don't know what's the problem and how to fix it.
Please contemplate the function call of X, Y = runge_kutta_4(F, 0, array([0, 0]), 5000, 1000). You are integrating over a time span of 5000 sec > 1 hour in steps of 1000 sec > 16 min. It is intuitively clear that this will be imprecise, as most of the acceleration will happen in the first 10 sec.
Then the question is what exactly you are trying to filter out with the loop. Is it the speed after this time?
The limit speed is where the right side is zero, at vmax=sqrt(g*m/c) = 62.1972 = 223.91 km/h, the claimed value of 84.8 can not be reached as a speed starting from rest. The fall time to a distance of x will be a little more than x/vmax, so you could use tmax = 100+x/vmax in T, Y = runge_kutta_4(F, t0, y0, tmax, 1).
Integrating in 1 sec time steps and looking for the speed after 5000 meters fall distance gives a result of 85 sec, distance 5013.33465614 m, speed 62.1972 m/s which is as to be expected close to the limit speed.
You can get a more precise time value by using (reverse) linear interpolation, then at time about 84.786 sec you reach distance 5000 m with a speed 62.1972 m/s. This again is compatible with the claimed result value, which now is a time, not a velocity.
I have been doing a lot of graphing of slope fields and ODE solutions recently, and I decided to try my hand at making a little function that automatically graphs solutions with a vector field overlay.
This function takes a set of initial conditions and plots that many solutions. It works pretty well, but for some initial values I get the error in the title:
invalid value encountered in double_scalars
def f(t, x): return np.power(x, 2) - x
Here is the code for the function:
def grapher(fn, t_0, t_n, dt, y_0):
"""
Takes a first order ODE and solves it for initial conditions
provided by y_0
:param fn: y' = f(t,y)
:param t_0: start time
:param t_n: end time
:param dt: step size
:param y_0: iterable containing initial conditions
:return:
"""
t = np.arange(t_0, t_n, dt)
y_min = .0
y_max = .0
for iv in np.asarray(y_0):
soln = rk4(dt, t, fn, iv)
plt.plot(t, soln, '-r')
if y_min > np.min(soln):
y_min = np.min(soln)
if y_max < np.max(soln):
y_max = np.max(soln)
x = np.linspace(t_0, t_n + dt, 11)
y = np.linspace(y_min, y_max, 11)
X, Y = np.meshgrid(x, y)
theta = np.arctan(f(X, Y))
U = np.cos(theta)
V = np.sin(theta)
plt.quiver(X, Y, U, V, angles='xy')
plt.xlim((t_0, t_n - dt))
plt.ylim((y_min - .1*y_min, y_max + .1*y_max))
plt.show()
And here is the application that fails:
def f(t, x): return x**2 - x
grapher(f,0,4,0.1, (-0.9, 0.9, 1.1))
It produces this graph, which is missing the solution associated with the initial condition 1.1:
However, if I choose a value less than or equal to 1, I get the correct graph:
I don't see an opportunity for divide by zero here, so I'm a bit confused. Also, the qualitative characteristics of the ODE are not fully on display unless I can choose an initial condition higher than 1.
I'd like to also note, that when I did not have a function to automate this process, the defined function f(x) = x^2 - x gave me no troubles at all. Any clue on why this might be?
If it helps, here is the rk4 algorithm I wrote in a different module:
def rk4(dt, t, field, y_0):
"""
:param dt: float - the timestep
:param t: array - the time mesh
:param field: method - the vector field y' = f(t, y)
:param y_0: array - contains initial conditions
:return: ndarray - solution
"""
# Initialize solution matrix. Each row is the solution to the system
# for a given time step. Each column is the full solution for a single
# equation.
y = np.asarray(len(t) * [y_0])
for i in np.arange(len(t) - 1):
k1 = dt * field(t[i], y[i])
k2 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k1)
k3 = dt * field(t[i] + 0.5 * dt, y[i] + 0.5 * k2)
k4 = dt * field(t[i] + dt, y[i] + k3)
y[i + 1] = y[i] + (k1 + 2 * k2 + 2 * k3 + k4) / 6
return y
I think that there is no error in the code, just the solution gets too large.
If you called grapher with
grapher(f, 0, 4, 0.1, (-0.9, 0.9, 1.01))
you would get:
With:
grapher(f, 0, 4, 0.1, (-0.9, 0.9, 1.02))
and when y_0 gets to be 1.1 the value for soln are not reported because np.pow(), upon detecting overflow, is just returning nan which then matplotlib does not know how to plot.
If you changed
def f(t, x):
return x**2 - x
to:
def f(t, x):
return x * (x - 1)
you would get a (ugly, but "correct") plot also of the solution for y_0 == 1.1, because instead of overflowing defaulting to nan you are now getting infs as maximum values, which of course matplotlib does not know how to handle in the process of generating the axes:
I want to generate x and y having a uniform distribution and limited by [xmin,xmax] and [ymin,ymax]
The points (x,y) should be inside a triangle.
How can I solve such a problem?
Here's some code that generates points uniformly on an arbitrary triangle in the plane.
import random
def point_on_triangle(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = sorted([random.random(), random.random()])
s, t, u = x, y - x, 1 - y
return (s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1])
The idea is to compute a weighted average of the three vertices, with the weights given by a random break of the unit interval [0, 1] into three pieces (uniformly over all such breaks). Here x and y represent the places at which we break the unit interval, and s, t and u are the length of the pieces following that break. We then use s, t and u as the barycentric coordinates of the point in the triangle.
Here's a variant of the above that avoids the need to sort, instead making use of an absolute value call:
def point_on_triangle2(pt1, pt2, pt3):
"""
Random point on the triangle with vertices pt1, pt2 and pt3.
"""
x, y = random.random(), random.random()
q = abs(x - y)
s, t, u = q, 0.5 * (x + y - q), 1 - 0.5 * (q + x + y)
return (
s * pt1[0] + t * pt2[0] + u * pt3[0],
s * pt1[1] + t * pt2[1] + u * pt3[1],
)
Here's an example usage that generates 10000 points in a triangle:
pt1 = (1, 1)
pt2 = (2, 4)
pt3 = (5, 2)
points = [point_on_triangle(pt1, pt2, pt3) for _ in range(10000)]
And a plot obtained from the above, demonstrating the uniformity. The plot was generated by this code:
import matplotlib.pyplot as plt
x, y = zip(*points)
plt.scatter(x, y, s=0.1)
plt.show()
Here's the image:
And since you tagged the question with the "numpy" tag, here's a NumPy version that generates multiple samples at once. Note that it uses the matrix multiplication operator #, introduced in Python 3.5 and supported in NumPy >= 1.10. You'll need to replace that with a call to np.dot on older Python or NumPy versions.
import numpy as np
def points_on_triangle(v, n):
"""
Give n random points uniformly on a triangle.
The vertices of the triangle are given by the shape
(2, 3) array *v*: one vertex per row.
"""
x = np.sort(np.random.rand(2, n), axis=0)
return np.column_stack([x[0], x[1]-x[0], 1.0-x[1]]) # v
# Example usage
v = np.array([(1, 1), (2, 4), (5, 2)])
points = points_on_triangle(v, 10000)
Ok, time to add another version, I guess. There is known algorithm to sample uniformly in triangle, see paper, chapter 4.2 for details.
Python code:
import math
import random
import matplotlib.pyplot as plt
def trisample(A, B, C):
"""
Given three vertices A, B, C,
sample point uniformly in the triangle
"""
r1 = random.random()
r2 = random.random()
s1 = math.sqrt(r1)
x = A[0] * (1.0 - s1) + B[0] * (1.0 - r2) * s1 + C[0] * r2 * s1
y = A[1] * (1.0 - s1) + B[1] * (1.0 - r2) * s1 + C[1] * r2 * s1
return (x, y)
random.seed(312345)
A = (1, 1)
B = (2, 4)
C = (5, 2)
points = [trisample(A, B, C) for _ in range(10000)]
xx, yy = zip(*points)
plt.scatter(xx, yy, s=0.2)
plt.show()
And result looks like
Uniform on the triangle?
import numpy as np
N = 10 # number of points to create in one go
rvs = np.random.random((N, 2)) # uniform on the unit square
# Now use the fact that the unit square is tiled by the two triangles
# 0 <= y <= x <= 1 and 0 <= x < y <= 1
# which are mapped onto each other (except for the diagonal which has
# probability 0) by swapping x and y.
# We use this map to send all points of the square to the same of the
# two triangles. Because the map preserves areas this will yield
# uniformly distributed points.
rvs = np.where(rvs[:, 0, None]>rvs[:, 1, None], rvs, rvs[:, ::-1])
Finally, transform the coordinates
xmin, ymin, xmax, ymax = -0.1, 1.1, 2.0, 3.3
rvs = np.array((ymin, xmin)) + rvs*(ymax-ymin, xmax-xmin)
Uniform marginals? The simplest solution would be to uniformly concentrate the mass on the line (ymin, xmin) - (ymax, xmax)
rvs = np.random.random((N,))
rvs = np.c_[ymin + (ymax-ymin)*rvs, xmin + (xmax-xmin)*rvs]
but that is not very interesting, is it?
I try to implement the Fourier series function according to the following formulas:
...where...
...and...
Here is my approach to the problem:
import numpy as np
import pylab as py
# Define "x" range.
x = np.linspace(0, 10, 1000)
# Define "T", i.e functions' period.
T = 2
L = T / 2
# "f(x)" function definition.
def f(x):
return np.sin(np.pi * 1000 * x)
# "a" coefficient calculation.
def a(n, L, accuracy = 1000):
a, b = -L, L
dx = (b - a) / accuracy
integration = 0
for i in np.linspace(a, b, accuracy):
x = a + i * dx
integration += f(x) * np.cos((n * np.pi * x) / L)
integration *= dx
return (1 / L) * integration
# "b" coefficient calculation.
def b(n, L, accuracy = 1000):
a, b = -L, L
dx = (b - a) / accuracy
integration = 0
for i in np.linspace(a, b, accuracy):
x = a + i * dx
integration += f(x) * np.sin((n * np.pi * x) / L)
integration *= dx
return (1 / L) * integration
# Fourier series.
def Sf(x, L, n = 10):
a0 = a(0, L)
sum = 0
for i in np.arange(1, n + 1):
sum += ((a(i, L) * np.cos(n * np.pi * x)) + (b(i, L) * np.sin(n * np.pi * x)))
return (a0 / 2) + sum
# x axis.
py.plot(x, np.zeros(np.size(x)), color = 'black')
# y axis.
py.plot(np.zeros(np.size(x)), x, color = 'black')
# Original signal.
py.plot(x, f(x), linewidth = 1.5, label = 'Signal')
# Approximation signal (Fourier series coefficients).
py.plot(x, Sf(x, L), color = 'red', linewidth = 1.5, label = 'Fourier series')
# Specify x and y axes limits.
py.xlim([0, 10])
py.ylim([-2, 2])
py.legend(loc = 'upper right', fontsize = '10')
py.show()
...and here is what I get after plotting the result:
I've read the How to calculate a Fourier series in Numpy? and I've implemented this approach already. It works great, but it use the expotential method, where I want to focus on trigonometry functions and the rectangular method in case of calculating the integraions for a_{n} and b_{n} coefficients.
Thank you in advance.
UPDATE (SOLVED)
Finally, here is a working example of the code. However, I'll spend more time on it, so if there is anything that can be improved, it will be done.
from __future__ import division
import numpy as np
import pylab as py
# Define "x" range.
x = np.linspace(0, 10, 1000)
# Define "T", i.e functions' period.
T = 2
L = T / 2
# "f(x)" function definition.
def f(x):
return np.sin((np.pi) * x) + np.sin((2 * np.pi) * x) + np.sin((5 * np.pi) * x)
# "a" coefficient calculation.
def a(n, L, accuracy = 1000):
a, b = -L, L
dx = (b - a) / accuracy
integration = 0
for x in np.linspace(a, b, accuracy):
integration += f(x) * np.cos((n * np.pi * x) / L)
integration *= dx
return (1 / L) * integration
# "b" coefficient calculation.
def b(n, L, accuracy = 1000):
a, b = -L, L
dx = (b - a) / accuracy
integration = 0
for x in np.linspace(a, b, accuracy):
integration += f(x) * np.sin((n * np.pi * x) / L)
integration *= dx
return (1 / L) * integration
# Fourier series.
def Sf(x, L, n = 10):
a0 = a(0, L)
sum = np.zeros(np.size(x))
for i in np.arange(1, n + 1):
sum += ((a(i, L) * np.cos((i * np.pi * x) / L)) + (b(i, L) * np.sin((i * np.pi * x) / L)))
return (a0 / 2) + sum
# x axis.
py.plot(x, np.zeros(np.size(x)), color = 'black')
# y axis.
py.plot(np.zeros(np.size(x)), x, color = 'black')
# Original signal.
py.plot(x, f(x), linewidth = 1.5, label = 'Signal')
# Approximation signal (Fourier series coefficients).
py.plot(x, Sf(x, L), '.', color = 'red', linewidth = 1.5, label = 'Fourier series')
# Specify x and y axes limits.
py.xlim([0, 5])
py.ylim([-2.2, 2.2])
py.legend(loc = 'upper right', fontsize = '10')
py.show()
Consider developing your code in a different way, block by block. You should be surprised if a code like this would work at the first try. Debugging is one option, as #tom10 said. The other option is rapid prototyping the code step by step in the interpreter, even better with ipython.
Above, you are expecting that b_1000 is non-zero, since the input f(x) is a sinusoid with a 1000 in it. You're also expecting that all other coefficients are zero right?
Then you should focus on the function b(n, L, accuracy = 1000) only. Looking at it, 3 things are going wrong. Here are some hints.
the multiplication of dx is within the loop. Sure about that?
in the loop, i is supposed to be an integer right? Is it really an integer? by prototyping or debugging you would discover this
be careful whenever you write (1/L) or a similar expression. If you're using python2.7, you're doing likely wrong. If not, at least use a from __future__ import division at the top of your source. Read this PEP if you don't know what I am talking about.
If you address these 3 points, b() will work. Then think of a in a similar fashion.
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))