Commit 94a8c7aa by jfregin

Added explicit SDC minimal example + toolbox

parent 73ce15d3
SDC_explicit.py 0 → 100644
 from functions import fcs as fcs import numpy as np import matplotlib.pyplot as plt from copy import deepcopy # ################################## Vars ################################### # l = -1j*(1) # lambda a = 0 # t_start b = 1 # t_end y0 = 1 # initial condition M = 4 # number of quadrature nodes K = 5 # number of iterations t_plot = np.linspace(a, b, 100) # array of times for plotting analytical solution # The size of y and t is M+2 because they include t_start + nodes + t_end y = np.zeros(M+2, dtype=complex) # allocate memory for solution vector t = np.zeros(M+2) # allocate memory for time vector # some control variables fin_u = 1 # calculate final update if fin_u = 1 radau = 0 # use radau IIA nodes, if 0 use Gauss-Legendre nodes check = 0 # use collocation solution to check for correct # # application of SDC # ################################## RHS #################################### # # Dahlquist Equation def f(y, ld=l): return l*y # ################################## SOL #################################### # def F(t, ld=l): return np.exp(l*t) # ################################## Init ################################### # # Calculate first guess y[0] = y0 # set initial condition t[0] = a # set initial time t[-1] = b # set final time # calculate quadrature nodes and weights for final update if radau: nodes, weights = fcs.rnw_r(M, a, b) else: nodes, weights = fcs.nodes_weights(M, a, b) # write nodes to time array t[1:-1] = nodes # calculate delta_tau d_tau = np.diff(t) # use euler explicit for initial guess for i in range(M): y[i+1] = y[i] + d_tau[i]*f(y[i]) #y[:] = y0 # set initial guess to y0 # ################################## SDC #################################### # # generate matrices Q and S needed for SDC iterations Q = fcs.Qmatrix(nodes, a) S = fcs.Smatrix(Q) for j in range(K): y_old = deepcopy(y) quad = S@(l*y_old[1:-1]) for i in range(M): y[i+1] = y[i] + d_tau[i]*(f(y[i]) - f(y_old[i])) + quad[i] # calculate residual (needs to go to 0 if k-> inf) residual = y[0] + Q@f(y[1:-1]) - y[1:-1] # ################################## Check ################################## # # This is not necessary for SDC it is just to check wether SDC and collocation # solutions match if check: # Use collocation solution to check if SDC works correctly # This is not needed for SDC, we only check for the correct solution Id = np.eye(len(Q)) A = (Id - l*Q) B = np.zeros(len(Q)) B[:] = np.exp(0) Sol = np.linalg.solve(A, B) # works and returns correct solution print('') print('Errors at collocation nodes are:') print(Sol - y[1:-1]) # if K is large difference should be ~1e-15 print('M='+str(M)+' iterations were used') # ############################## Final Update ############################### # # calcualte final update if fin_u: y[-1] = y0 + np.dot(weights, f(y[1:-1])) # ################################## Plot ################################### # # plot results (only real part) plt.plot(t_plot, F(t_plot), linestyle='--', color='C0', label=r'$F(x)$') plt.plot(t[0], y[0], linestyle='', marker='x', color='C1', label=r'$y(a)$') plt.plot(t[1:-1], y[1:-1], linestyle='', marker='o', color='C3', label=r'$y(\tau_m)$') if fin_u: plt.plot(t[-1], y[-1], linestyle='', color='C7', marker='D', markersize=10, label=r'$y(b)$') if check: plt.plot(t[1:-1], Sol, linestyle='', marker='.', color='C4', label='Collocation Solution') plt.legend()
functions.py 0 → 100755
 #!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Thu Mar 26 15:19:24 2020 @author: jf TODO: -extract DC-implicit and SDC-implicit methods and add them to the fcs class -remove unnecessary functions (DC2 or DC) best would be to unify DC-EE and DC-IE into one function """ class fcs: import numpy as np def subinterval(a, b, num_int): """ Parameters ---------- a : real lower boundary b : real upper boundary num_int : integer number of grid points between a and b Returns ------- sub_int : new grid with num_int subintervals in between two points in the grid. Type:(array) """ import numpy as np n = num_int + 1 step = (b - a) / n sub_int = np.arange(a, b, step) return sub_int def refine_grid(grid1d, subintervals=4, subfunc=subinterval): """ Parameters ---------- grid1d : array one dimensional grid array subintervals : integer number of intervals in between grid points subfunc : fuction function that creates a grid with subintervals gridpoints between two integers a and b. b is not included in the new grid Returns ------- new_grid : array grid with equally spaced subintervals that includes the starting and end points a and b. """ import numpy as np si = subintervals g = grid1d new_grid = subfunc(g[0], g[1], si) for i in range(2, len(g)): new_grid = np.append(new_grid, subfunc(g[(i - 1)], g[i], si)) new_grid = np.append(new_grid, g[(-1)]) return new_grid def coord_trans(cin, a, b, A=-1, B=1): """ transforms coordinates cin within the interval [A,B] to coordinates cout within a new interval [a,b] Parameters ---------- cin : coordinates within interval [A,B] a : lower boundary of new interval b : upper boundary of new interval A : initial interval lower boundary, default is A=-1 B : initial interval upper boundary, default is B=1 Returns ------- cout : coordinates within new interval [a,b] """ cout = ((b - a) * cin + a * B - b * A) / (B - A) return cout def NewtonVM(t): """ t: array or list containing nodes. returns: array Newton Vandermode Matrix. Entries are in the lower triangle Polynomial can be created with scipy.linalg.solve_triangular(NewtonVM(t),y,lower=True) where y contains the points the polynomial need to pass through """ import numpy as np t = np.asarray(t) dim = len(t) VM = np.zeros([dim, dim]) VM[:, 0] = 1 for i in range(1, dim): VM[:, i] = (t[:] - t[(i - 1)]) * VM[:, i - 1] return VM def Horner_newton(weights, xi, x): """ Horner scheme to evaluate polynomials based on newton basis """ import numpy as np y = np.zeros_like(x) for i in range(len(weights)): y = y * (x - xi[(-i - 1)]) + weights[(-i - 1)] return y def GL(M, a, b, ct=coord_trans, A=-1, B=1): """ calculates Gauss-Legendre nodes for Gaussian quadrature at int(M) Points between float(a) and float(b). A and B shouldn't be changed """ # is a lot faster than GaussLegendre but slower than _getNodes import numpy as np # calculate nodes and weights on [-1,1] nodes, weights = np.polynomial.legendre.leggauss(M) nodes = ct(nodes, a, b) # transform back to interval [a,b] weights = (b-a)/(B-A)*weights # also transform weights to [a,b] return nodes, weights def eval(f, nodes, weights, pw=False): # performes gaussian quadrature given the function f import types import numpy as np if pw is False: if type(f) == types.FunctionType: return np.dot(weights, f(nodes)) else: return np.dot(weights, f) else: l = len(weights) summation = np.zeros(l) if type(f) == types.FunctionType: for i in range(0, l): summation[i] = np.dot(weights[0:i+1], f(nodes[0:i+1])) return summation else: for i in range(0, l): summation[i] = np.dot(weights[0:i+1], f[0:i+1]) return summation def GQ(f, a, b, M=10, coord_trans=coord_trans, GL=GL, eval=eval, pw=False): """ performs gaussian quadrature on function f based on the interval [a,b] """ nodes, weights = GL(M, a, b) # print('weights: ' +str(weights)) return eval(f, nodes, weights, pw=pw) def Newton_polynomial_specific(x, t='', y='', NVM=NewtonVM, HN=Horner_newton): """ input: see NewtonVM returns two arrays x: yy: Lagrange Polynomial passing through points(t_i,y_i) Note: maybe use *args to prevent t='' and y='' """ from scipy.linalg import solve_triangular VM = NVM(t) weights = solve_triangular(VM, y, lower=True) yy = HN(weights, t, x) return x, yy def Newton_polynomial(t, y, N_linspace=100, NVM=NewtonVM, HN=Horner_newton): """ input: see NewtonVM returns two arrays x: resolves t in N_linspace steps yy: Lagrange Polynomial passing through points(t_i,y_i) """ from scipy.linalg import solve_triangular import numpy as np VM = NVM(t) weights = solve_triangular(VM, y, lower=True) x = np.linspace(t[0], t[(-1)], N_linspace) yy = HN(weights, t, x) return x, yy def nodes_weights(n, a, b, A=-1, B=1): # nodes and weights for gauss legendre quadrature from scipy.special import legendre import numpy as np poly = legendre(n) polyd = poly.deriv() nodes= poly.roots nodes = np.sort(nodes) weights = 2/((1-nodes**2)*(np.polyval(polyd,nodes))**2) nodes = ((b - a) * nodes + a * B - b * A) / (B - A) weights=(b-a)/(B-A)*weights return nodes, weights def my_get_weights(n, a, b, nodes, nodes_weights=nodes_weights, NewtonVM=NewtonVM, Horner_newton=Horner_newton, eval=eval): # integrates lagrange polynomials to the points [nodes] import scipy import numpy as np nodes_m, weights_m=nodes_weights(np.ceil(n/2), a, b) # use gauss-legendre quadrature to integrate polynomials weights = np.zeros(n) for j in np.arange(n): coeff = np.zeros(n) coeff[j] = 1.0 # is unity because it needs to be scaled with y_j for interpolation we have sum y_j*l_j poly_coeffs = scipy.linalg.solve_triangular(NewtonVM(nodes), coeff, lower=True) eval_newt_poly = Horner_newton(poly_coeffs, nodes, nodes_m) weights[j] = eval(eval_newt_poly, nodes_m, weights_m) return weights def lnw(n, a, b, A=-1, B=1): import numpy as np from scipy.special import legendre # nodes and weights for gauss - lobatto quadrature # See Abramowitz & Stegun p 888 nodes = np.zeros(n) nodes[0] = A nodes[-1] = B poly = legendre(n-1) polyd = poly.deriv() subnodes = np.sort(polyd.roots) nodes[1:-1] = subnodes weights = 2/(n*(n-1)*(np.polyval(poly, nodes))**2) weights[0] = 2/(n*(n-1)) weights[-1] = 2/(n*(n-1)) nodes = ((b - a) * nodes + a * B - b * A) / (B - A) weights = (b-a)/(B-A)*weights return nodes, weights def rnw(n, a, b, A=-1, B=1): import numpy as np from scipy.special import legendre # nodes and weights for gauss - radau quadrature # See Abramowitz & Stegun p 888 nodes = np.zeros(n) nodes[0] = A p = np.poly1d([1, 1]) pn = legendre(n) pn1 = legendre(n-1) poly, remainder = (pn + pn1)/p # [1] returns remainder from polynomial division nodes[1:] = np.sort(poly.roots) weights = 1/n**2 * (1-nodes[1:])/(pn1(nodes[1:]))**2 weights = np.append(2/n**2,weights) nodes = ((b - a) * nodes + a * B - b * A) / (B - A) weights = (b-a)/(B-A)*weights return nodes, weights def rnw_r(n, a, b, A=-1, B=1): import numpy as np from scipy.special import legendre # nodes and weights for gauss - radau IIA quadrature # See Abramowitz & Stegun p 888 nodes = np.zeros(n) nodes[0] = A p = np.poly1d([1, 1]) pn = legendre(n) pn1 = legendre(n-1) poly, remainder = (pn + pn1)/p # [1] returns remainder from polynomial division nodes[1:] = np.sort(poly.roots) weights = 1/n**2 * (1-nodes[1:])/(pn1(nodes[1:]))**2 weights = np.append(2/n**2, weights) nodes = ((b - a) * nodes + a * B - b * A) / (B - A) weights = (b - a)/(B - A)*weights nodes = ((b + a) - nodes)[::-1] # reverse nodes weights = weights[::-1] # reverse weights return nodes, weights def Qmatrix(nodes, a, my_get_weights=my_get_weights): """ Integration Matrix """ import numpy as np M = len(nodes) Q = np.zeros([M, M]) # for all nodes, get weights for the interval [tleft,node] for m in np.arange(M): w = my_get_weights(M, a, nodes[m],nodes) Q[m, 0:] = w return Q def Smatrix(Q): """ Integration matrix based on Q: sum(S@vector) returns integration """ from copy import deepcopy import numpy as np M = len(Q) S = np.zeros([M, M]) S[0, :] = deepcopy(Q[0, :]) for m in np.arange(1, M): S[m, :] = Q[m, :] - Q[m - 1, :] return S def Q_fast(t): """ EQ. 3.6 SISDC Paper """ import numpy as np dim = len(t)-2 diff = np.diff(t[0:-1]) Q_f = np.zeros([dim,dim]) for i in range(0,dim): Q_f[i,0:i+1] = diff[0:i+1] delta_t = t[-1]-t[0] Q_f = 1/delta_t * Q_f return Q_f def Q_slow(t, Qf=Q_fast): # this one should be correct import numpy as np Q_f = Qf(t) Q_s = np.zeros(Q_f.shape) Q_s[1:, :-1] = Q_f[1:, 1:] return Q_s def IMEX_matrix(lf, ls, t): import numpy as np dim = len(t)-2 diff = np.diff(t[0:-1]) R = np.zeros([dim, dim], dtype=np.complex) terms = (1+1j*diff*ls)/(1-1j*diff*lf) diag = np.cumprod(terms) np.fill_diagonal(R, diag) return R def stabl(lf, ls, t, k=3, gw=my_get_weights, IMEX_matrix=IMEX_matrix): from numpy.linalg import matrix_power as mp import numpy as np dim = len(t)-2 a = t[0] b = t[-1] delta_t = b-a nodes = t[1:-1] Q_f = fcs.Q_fast(t) Q_s = fcs.Q_slow_alt(t) Q = fcs.Qmatrix(nodes, a, gw) Q_delta = 1/(b-a)*Q Id = np.identity(dim, dtype=np.complex) L = Id - delta_t*(1j*lf*Q_f + 1j*ls*Q_s) L_inv = np.linalg.inv(L) R = delta_t*((1j*(lf+ls)*Q_delta)-(1j*lf*Q_f + 1j*ls*Q_s)) garb, q = fcs.rnw(dim, a, b) q = q[::-1] R2 = IMEX_matrix(lf, ls, t) term = mp(L_inv@R, k) term2 = mp(L_inv@R, k)@R2 for j in range(0, k): term += mp(L_inv@R, j)@L_inv term2 += mp(L_inv@R, j)@L_inv ones = np.ones(len(Q)) return 1 + 1j*(lf+ls)*q.dot(term.dot(ones)), 1 + 1j*(lf+ls)*q.dot(term2.dot(ones))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment