Num Methods: 1D FEM (higher-order, Newton)

30 days ago by solin

#Solve -(Lambda(u)*u')'+a0*u-f(x)=0 from scipy.integrate import fixed_quad from scipy.special.orthogonal import p_roots from scipy.sparse.linalg.dsolve import spsolve from scipy import sparse from numpy import arange, zeros, dot, array, exp, sqrt from pylab import plot, figure, savefig, grid, legend, clf, pcolor, spy, axis, matshow #finds the test function numbers on an element def functionIndices( elem_index, num_elems, order=1 ): #given the element index, the number of elements, and the order; # we will find the function indices which exist on the specific element if elem_index!=1 and elem_index!=num_elems: #give both hats on the interval index_list = [elem_index-1,elem_index] elif elem_index==1: #skip the known leftmost half-hat function \ index_list = [1] else: #elem_index == num_elems skip the known rightmost half-hat function / index_list = [num_elems-1] if order>1: #return higher order functions too for l_ in range(order-1): index_list.append( num_elems + (order-1)*(elem_index-1) + l_ ) return index_list #finds the values of u at x def u( x , Y, mesh, num_elems, elem_index, dir_bdy, order=1): index_list = functionIndices( elem_index, num_elems, order ) #get the functions on the current interval list_len = len(index_list) #find how many functions are on the interval result=0 #set to zero before summing a = mesh[elem_index-1] #find the left endpoint of the interval b = mesh[elem_index] #the right endpoint x = 2.*(x-a)/(b-a)-1. #change to ref map i.e. (-1.,1.) if list_len>order: #if the number of functions is greater than for i in range(list_len): # the order then we are no on the left or right result += Y[index_list[i]-1] * fn[i]( x ) # interval elif elem_index==1: #leftmost interval for i in range(list_len): result += Y[index_list[i]-1] * fn[i+1]( x ) result +=dir_bdy[0] * fn[0](x) #include left Dirichlet boundary else: #else we are on the right interval result+=Y[index_list[0]-1] * fn[0]( x ) for i in range(list_len-1): result += Y[index_list[i+1]-1] * fn[i+2]( x ) result += dir_bdy[1] * fn[1](x) #include right Dirichlet boundary return result # finds the value of du at x by first transforming to the -1 to 1 interval and then transforming back using a jacobian def du( x , Y, mesh, num_elems, elem_index, dir_bdy, order=1): index_list = functionIndices( elem_index, num_elems, order ) list_len = len(index_list) result=0 a = mesh[elem_index-1] b = mesh[elem_index] x = 2.*(x-a)/(b-a)-1. #change to ref map if list_len>order: for i in range(list_len): result += Y[index_list[i]-1] * dfn[i]( x ) elif elem_index==1: for i in range(list_len): result += Y[index_list[i]-1] * dfn[i+1]( x ) result +=dir_bdy[0] * dfn[0](x) else: result+=Y[index_list[0]-1] * dfn[0]( x ) for i in range(list_len-1): result += Y[index_list[i+1]-1] * dfn[i+2]( x ) result +=dir_bdy[1] * dfn[1](x) return result*2/(b-a) # Defining shape functions and derivatives def fn0(xi): return (1. - xi)/2. def dfn0(xi): return -1./2. def fn1(xi): return (1. + xi)/2. def dfn1(xi): return 1./2. def fn2(xi): return xi**2 - 1. def dfn2(xi): return 2.*xi def fn3(xi): return xi*(xi**2 - 1.) def dfn3(xi): return 3.*xi**2 - 1. def fn4(xi): return xi**4 - 2.*xi**2 + 1 def dfn4(xi): return 4*xi**3 - 4.*xi fn = [fn0, fn1, fn2, fn3, fn4] dfn = [dfn0, dfn1, dfn2, dfn3, dfn4] #Finds the i'th element of F (RHS of Newton's Method) def Fi( Y, u, dudx, f, Lambda, glob_i, order, num_elems, mesh, roots, weights, qpts_num, a0 ): #where elems is the list of elem intervals #find number of element elem_index = findElemNum( glob_i, num_elems, order ) #find test functions on element test_list = functionIndices( elem_index, num_elems, order ) #INTEGRATION a = mesh[elem_index-1] b = mesh[elem_index] jac = (b-a)/2. result = 0. vi_con = findCon( glob_i, num_elems, order, elem_index ) #integrate term lambda*u'*vi' on the element elem_index for k in range(qpts_num): result+= weights[k] * Lambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * du( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * dfn[vi_con](roots[k]) #integrate a0*u*vi for k in range(qpts_num): result+= weights[k] * a0*u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * fn[vi_con](roots[k]) * jac #integrate term -f(x)*vi for k in range(qpts_num): result+= -weights[k] * f( x_ref( roots[k], a, b ) ) * fn[vi_con](roots[k]) * jac if glob_i<num_elems: elem_index +=1 a = mesh[elem_index-1] b = mesh[elem_index] jac = (b-a)/2. vi_con = findCon( glob_i, num_elems, order, elem_index ) #integrate term lambda*u'*vi' on the element elem_index for k in range(qpts_num): result+= weights[k] * Lambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * du( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * dfn[vi_con](roots[k]) #integrate a0*u*vi for k in range(qpts_num): result+= weights[k] * a0*u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * fn[vi_con](roots[k]) * jac #integrate term -f(x)*vi for k in range(qpts_num): result+= -weights[k] * f( x_ref( roots[k], a, b ) ) * fn[vi_con](roots[k]) * jac return result #Finds the i,jth element of the Jacobian Matrix of the Newton Method def Jij( Y, u, dudx, f, Lambda, glob_i, glob_j, order, num_elems, mesh, roots, weights, qpts_num, a0 ): #integrate dLambda*vj*du*vi + Lambda*dvj*vi + vj*vi #find number of element for glob's elem_index_i = findElemNum( glob_i, num_elems, order ) elem_index_j = findElemNum( glob_j, num_elems, order ) #check if the functions will overlap overlapping_elems = 2 #first assume 2 overlaps elem_index = elem_index_i #specifies the leftmost overlap after tests are done if abs(elem_index_i-elem_index_j)>1: return 0. elif (abs(elem_index_i-elem_index_j)==1): if glob_i>=num_elems and glob_j>=num_elems:#check if both are bubbles return 0. elif glob_i>=num_elems or glob_j>=num_elems: #one is bubble if glob_i>=num_elems and (elem_index_i<elem_index_j): return 0. elif glob_j>=num_elems and (elem_index_j<elem_index_i): return 0. else: # a hat overlapping a bubble overlapping_elems=1 if glob_j>=num_elems: elem_index = elem_index_j else: elem_index = elem_index_i else: #both are hats but with only one overlapping element overlapping_elems = 1 if elem_index_i<elem_index_j: elem_index = elem_index_j else: #...j<...i elem_index = elem_index_i else: #same elem_index's returned if glob_j>=num_elems or glob_i>=num_elems: overlapping_elems=1 #a hat overlapping a bubble or bubble on bubble #if they do overlap #start on primary element specified by elem_index #find test functions on element test_list = functionIndices( elem_index, num_elems, order ) #INTEGRATION a = mesh[elem_index-1] b = mesh[elem_index] jac = (b-a)/2. result = 0 vi_con = findCon( glob_i, num_elems, order, elem_index ) vj_con = findCon( glob_j, num_elems, order, elem_index ) #integrate term dLambda*du*dvi*vj on the element elem_index for k in range(qpts_num): result+= weights[k] * dLambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * du( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * dfn[vi_con](roots[k]) * fn[vj_con](roots[k]) #integrate Lambda*dvi*dvj for k in range(qpts_num): result+= weights[k] * Lambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * dfn[vi_con](roots[k]) * dfn[vj_con](roots[k]) / jac #integrate term a0*vi*vj for k in range(qpts_num): result+= weights[k] * a0*fn[vi_con](roots[k]) * fn[vj_con](roots[k]) * jac if overlapping_elems==2: #INTEGRATION elem_index = elem_index+1 #move one element to the right a = mesh[elem_index-1] b = mesh[elem_index] jac = (b-a)/2. vi_con = findCon( glob_i, num_elems, order, elem_index ) vj_con = findCon( glob_j, num_elems, order, elem_index ) #integrate term dLambda*du*dvi*vj on the element elem_index for k in range(qpts_num): result+= weights[k] * dLambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * du( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order) * dfn[vi_con](roots[k]) * fn[vj_con](roots[k]) #integrate Lambda*dvi*dvj for k in range(qpts_num): result+= weights[k] * Lambda(u( x_ref( roots[k], a, b ) , Y, mesh, num_elems, elem_index, dir_bdy, order)) * dfn[vi_con](roots[k]) * dfn[vj_con](roots[k]) / jac #integrate term a0*vi*vj for k in range(qpts_num): result+= weights[k] * a0*fn[vi_con](roots[k]) * fn[vj_con](roots[k]) * jac return result #assembles all i,j components of the Jacobian matrix and returns a CSR matrix def assembleJ( Y, u, dudx, f, Lambda, order, num_elems, mesh, roots, weights, qpts_num, size, a0 ): I=[] J=[] V=[] for glob_i in range(1,size+1): for glob_j in range(1,size+1): I.append( glob_i-1 ) J.append( glob_j-1 ) V.append( Jij( Y, u, dudx, f, Lambda, glob_i, glob_j, order, num_elems, mesh, roots, weights, qpts_num, a0 ) ) # creating CSR matrix from arrays I, J, V Sn = sparse.coo_matrix((V,(I,J)),shape=(size,size)) Sn = Sn.tocsr() return Sn #assembles all the Fi elements of the RHS vector for the Newton Method def assembleF( Y, u, dudx, f, Lambda, order, num_elems, mesh, roots, weights, qpts_num, size, a0 ): F = zeros( size ) for glob_i in range(1,size+1): F[glob_i-1] = Fi( Y, u, dudx, f, Lambda, glob_i, order, num_elems, mesh, roots, weights, qpts_num, a0 ) return F #transformation from the reference map to the (a,b) interval of the problem def x_ref( xi, a, b ): return (b-a)*(xi+1)/2. + a #Higher order indices may be expressed in the form N+(p-1)*(k-1)+l where l runs from 0 to p-2 #Set this equal to the glob_i and solving for k (the element number) we find that we don't know l but the k must be an integer therefore l must satisfy a modulus formula given below. def findL( glob_i, num_elems, order ): if order==1 or glob_i<num_elems: return -1 for l_ in range(order-1): if ( glob_i - num_elems - l_ )%( order - 1)==0: return l_ #l_ is 0 for order 2 and 1 for order 3... print "ERROR!!! There is a bug in findL." return -10. #this is not a good return! ERROR! #Finds which test function to use given glob_i and the element we are on def findCon( glob_i, num_elems, order, elem_num ): if glob_i<num_elems: if glob_i==elem_num: return 1 else: return 0 return findL( glob_i, num_elems, order )+2 #After defining the findL formula above we are able to find the element index given glob_i. def findElemNum( glob_i, num_elems, order=1 ): #finds the element number given the data #find number of element for glob_i if glob_i < num_elems: #return the left elem's index elem_index_i = glob_i else: l_ = findL( glob_i, num_elems, order ) elem_index_i = 1 + ( glob_i - num_elems - l_ )/(order-1) return elem_index_i #makes a vector of equally space points from a to b. This is good for quick mesh's def makeVector( a, b, n_points ): vector_X = array( range(n_points) )/(n_points-1.)*(b-a) for i in range(n_points): vector_X[i] = vector_X[i]+a return vector_X #Find the L^2 norm of a vector Y def norm(Y): val = 0. for y in Y: val += y**2 return sqrt(val) #Solves the differential equation using Newton's Method. It terminates when F or dY are small. def solveEquation( mesh, Y, order, a0, f, Lambda, dLambda, qpts_num, dir_bdy, tol=1e-6 ): #roots and weights on -1 to 1 roots, weights = p_roots(qpts_num) num_elems = len(mesh)-1 size = order*num_elems-1 #Solving print "Solving." iter_count = 0 while 1: J = assembleJ( Y, u, du, f, Lambda, order, num_elems, mesh, roots, weights, qpts_num, size, a0 ) F = assembleF( Y, u, du, f, Lambda, order, num_elems, mesh, roots, weights, qpts_num, size, a0 ) norm_F = norm(F) print "Residual norm: ", norm_F if norm_F < tol: print "Residual in tolerance." break print "Solving matrix problem." dY = -spsolve(J, F) Y = Y + dY norm_dY = norm(dY) print "Increment norm: ", norm_dY iter_count += 1 if norm_dY < tol: print "Increment in tolerance." break # Plotting print "Plotting." elem_sub_div = 9 x_vector = [] sol = [] for i in range(num_elems): a = mesh[i] b = mesh[i+1] for j in range(elem_sub_div-1): x = j/(elem_sub_div-1.)*(b-a) +a x_vector.append( x ) u_val = u( x, Y, mesh, num_elems, i+1, dir_bdy, order) sol.append( u_val ) x_vector.append(b) sol.append( u( b, Y, mesh, num_elems, i+1, dir_bdy, order)) clf() axis('equal') label = "FEM solution" plot(x_vector, sol, "g-", label=label) legend() grid(True) savefig("a.png") return iter_count #INITIAL CONDITIONS DEFINED BY THE USER! num_elems = 3 a = -1. b = 1. mesh = makeVector( a, b, num_elems+1 ) order = 4 qpts_num = order+4 tol = 1e-5 Y = zeros(order*num_elems-1 ) #initial guess dir_bdy = [0.,0.] #Parameters for the differential equation: -(Lambda(u)*u')'+a0*u=f(x) a0 = 1. def f(x): return exp(-x) def Lambda( u ): return 1. + u**2 + u**6 def dLambda( u ): return 2.*u+6.*u**5 #Finally we will solve the equation iter_num = solveEquation( mesh, Y, order, a0, f, Lambda, dLambda, qpts_num, dir_bdy, tol ) print "Iterations: ", iter_num ################################################################################################## ################################################################################################## ################################################################################################## 
       
Solving.
Residual norm:  1.68910419612
Solving matrix problem.
Increment norm:  0.504363216616
Residual norm:  0.0758100500431
Solving matrix problem.
Increment norm:  0.0160598723432
Residual norm:  0.000463153485049
Solving matrix problem.
Increment norm:  7.05441288309e-05
Residual norm:  1.0733192717e-08
Residual in tolerance.
Plotting.
Iterations:  3