def solve_eig(l, x_max, E0, x_num, tol): # Differential equation in standard form def f(t, y): return np.array( [y[1], (-2 / t + l * (l + 1) / t**2 - y[2]) * y[0], 0 * y[1]]) # Boundary conditions def bc(ya, yb): return np.array([ya[0], yb[0], ya[1] - 1]) # Create s interval x = np.linspace(tol, x_max, x_num) # Initial conditions from RK4 y0 = de.rku4(lambda y, t: f(t, y), [0, 1, E0], x) # Solve BVP with solve_bvp sol = itg.solve_bvp(f, bc, x, y0.T, tol=tol) # Solve again using RK4 #y_rk4 = de.rku4(lambda y, t:f(t, y), sol.y[:,0], s) #plt.plot(y_rk4[:,0], -y_rk4[:,1], label='Re:RK4') return sol
def eig_shoot(g, a, b, lmd1, lmd2, t, adot=1, tol=10**(-8), max_itr=100, inf=False): '''Implements the shooting method to solve second order BVPs USAGE: y = shoot(g, a, b, lmd1, lmd2, t, tol, max_iter, inf) INPUT: g - constructor function g(lmd) = f(y,t) which gives f for every lmd. f is the differential equation in the standard form dy/dt = f(y,t). Since we are solving a second-order boundary-value problem that has been transformed into a first order system, the function f should return a 1x2 array with the first entry equal to y and the second entry equal to y'. a - solution value at the left boundary: a = y(t[0]). b - solution value at the right boundary: b = y(t[n-1]). lmd1 - first initial estimate of eigenvalue. lmd2 - second initial estimate of eigenvalue. t - array of n time values to determine y at. adot - the derivative at the first boundary that you want to use. tol - allowable tolerance on right boundary: | b - y[n-1] | < tol. max_itr - maximum number of iterations. inf - set to True if we are working on a finite approximation of an infinite interval. This changes to method with which we update the eigenvalue approximations to binary search. OUTPUT: y - array of normalized solution values corresponding to the values in the supplied array t. lmd - solution eigenvalue corresponding to y NOTE: This function assumes that the second order BVP has been converted to a first order system of two equations. The secant method is used to refine the values of lmd used for the initial value problems. If inf=True the method is changed to binary search. ''' from diffeq import rku4 n = len(t) # Determine the size of the arrays we will generate # Compute solution to first initial value problem (IVP) with y'(a) = 1 and # lmd = lmd1. Because we are using the secant method to refine our estimates # of lmd, we don't really need all of the solution of the IVP, just the last # point of it -- this is saved in w1. w2 is, for now, an alias of w1 y1 = rku4(g(lmd1), [a, adot], t) w1 = y1[n - 1, 0] w2 = w1 print('%2d: lmd = %10.3e, error = %10.3e' % (0, lmd1, b - w1)) # Begin the main loop. We will compute the solution of a second IVP and # then use the both solutions to refine our estimate of lmd. This second # solution then replaces the first and a new 'second' solution is generated. # This process continues until we either solve the problem to within the # specified tolerance or we exceed the maximum number of allowable iterations. # Create iteration counter itr = 1 print('inf') if inf == True: print(inf) y2 = rku4(g(lmd2), [a, adot], t) w2 = y2[n - 1, 0] # Check whether interval can be bisected if numpy.sign(w1 - b) == numpy.sign(w2 - b): print('\a**** ERROR ****') print('Interval of eigenvalues is not appropriate for bisection') break while numpy.abs(w2 - w1) > tol and itr < max_iter: # Calculate midpoint lmdm = (lmd2 + lmd1) / 2 ym = rku4(g(lmdm), [a, adot], t) wm = ym[n - 1, 0] print('%2d: lmd = %10.3e, error = %10.3e' % (itr, lmdm, b - wm)) if numpy.sign(wm - b) * numpy.sign(w1 - b) < 0: y2 = ym else: y1 = ym itr += 1 else: while abs(b - w2) > tol and itr < max_itr: # Solve second initial value problem, using y'(a) = 1 and lmd = lmd2. We # need to retain the entire solution vector y since if y(t(n)) is close # enough to b for us to stop then the first column of y becomes our solution # vector. y2 = rku4(g(lmd2), [a, adot], t) w2 = y2[n - 1, 0] print('%2d: lmd = %10.3e, error = %10.3e' % (itr, lmd2, b - w2)) # Compute the new approximations to the eigenvalue lmd. We compute lmd2 # using a linear fit through (lmd1, w1) and (lmd2,w2) where w1 and w2 are # the estimates at t=b of the initial value problems solved above with. The # new value for lmd1 is the old value of lmd2. lmd1, lmd2 = (lmd2, lmd2 + (lmd2 - lmd1) / (w2 - w1) * (b - w2)) w1 = w2 # Update iteration counter itr += 1 # All done. Check to see if we really solved the problem, and then return # the solution. if abs(b - w2) >= tol: print('\a**** ERROR ****') print('Maximum number of iterations (%d) exceeded' % max_itr) print('Returned values may not have desired accuracy') print('Error estimate of solution at the second endpoint is %e' % (b - w2)) return y2[:, 0], lmd2
def shoot(f, a, b, z1, z2, t, tol): '''Implements the shooting method to solve second order BVPs USAGE: y = shoot(f, a, b, z1, z2, t, tol) INPUT: f - function dy/dt = f(y,t). Since we are solving a second- order boundary-value problem that has been transformed into a first order system, this function should return a 1x2 array with the first entry equal to y and the second entry equal to y'. a - solution value at the left boundary: a = y(t[0]). b - solution value at the right boundary: b = y(t[n-1]). z1 - first initial estimate of y'(t[0]). z2 - second initial estimate of y'(t[0]). t - array of n time values to determine y at. tol - allowable tolerance on right boundary: | b - y[n-1] | < tol OUTPUT: y - array of solution function values corresponding to the values in the supplied array t. NOTE: This function assumes that the second order BVP has been converted to a first order system of two equations. The secant method is used to refine the initial values of y' used for the initial value problems. ''' from diffeq import rku4 max_iter = 100 # Maximum number of shooting iterations n = len(t) # Determine the size of the arrays we will generate # Compute solution to first initial value problem (IVP) with y'(a) = z1. # Because we are using the secant method to refine our estimates of z = # y', we don't really need all the solution of the IVP, just the last # point of it -- this is saved in w1. y = rku4(f, [a, z1], t) w1 = y[n - 1, 0] print('%2d: z = %10.3e, error = %10.3e' % (0, z1, b - w1)) # Begin the main loop. We will compute the solution of a second IVP and # then use the both solutions to refine our estimate of y'(a). This # second solution then replaces the first and a new 'second' solution is # generated. This process continues until we either solve the problem to # within the specified tolerance or we exceed the maximum number of # allowable iterations. for i in range(max_iter): # Solve second initial value problem, using y'(a) = z2. We need to # retain the entire solution vector y since if y(t(n)) is close enough # to b for us to stop then the first column of y becomes our solution # vector. y = rku4(f, [a, z2], t) w2 = y[n - 1, 0] print('%2d: z = %10.3e, error = %10.3e' % (i + 1, z2, b - w2)) # Check to see if we are done... if abs(b - w2) < tol: break # Compute the new approximations to the initial value of the first # derivative. We compute z2 using a linear fit through (z1,w1) and # (z2,w2) where w1 and w2 are the estimates at t=b of the initial # value problems solved above with y1'(a) = z1 and y2'(a) = z2. The # new value for z1 is the old value of z2. #z1, z2 = ( z2, z1 + ( z2 - z1 ) / ( w2 - w1 ) * ( b - w1 ) ) z1, z2 = (z2, z2 + (z2 - z1) / (w2 - w1) * (b - w2)) w1 = w2 # All done. Check to see if we really solved the problem, and then return # the solution. if abs(b - w2) >= tol: print('\a**** ERROR ****') print('Maximum number of iterations (%d) exceeded' % max_iter) print('Returned values may not have desired accuracy') print('Error estimate of returned solution is %e' % (b - w2)) return y[:, 0]