def backward_difference_formula(k): r""" Construct the k-step backward differentiation method. The methods are implicit and have order k. They have the form: `\sum_{j=0}^{k} \alpha_j y_{n+k-j+1} = h \beta_j f(y_{n+1})` They are generated using equation (1.22') from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> bdf4=lm.backward_difference_formula(4) >>> bdf4.A_alpha_stability() 73 **Reference**: :cite:`hairer1993` pp. 364-365 """ alpha=snp.zeros(k+1) beta=snp.zeros(k+1) beta[k]=1 gamma=snp.zeros(k+1) gamma[0]=1 alphaj=snp.zeros(k+1) for j in range(1,k+1): gamma[j]= sympy.Rational(1,j) for i in range(0,j+1): alphaj[k-i]=(-1)**i*combinatorial.factorials.binomial(j,i)*gamma[j] alpha=alpha+alphaj name=str(k)+'-step BDF' return LinearMultistepMethod(alpha,beta,name=name,shortname='BDF'+str(k))
def Nystrom(k): r""" Construct the k-step explicit Nystrom linear multistep method. The methods are explicit and have order k. They have the form: `y_{n+1} = y_{n-1} + h \sum_{j=0}^{k-1} \beta_j f(y_n-k+j+1)` They are generated using equations (1.13) and (1.7) from [hairer1993]_ III.1, along with the binomial expansion and the relation in exercise 4 on p. 367. Note that the term "Nystrom method" is also commonly used to refer to a class of methods for second-order ODEs; those are NOT the methods generated by this function. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> nys3=lm.Nystrom(6) >>> nys3.order() 6 References: #. [hairer1993]_ """ import sympy from sympy import Rational one = Rational(1, 1) alpha = snp.zeros(k + 1) alpha[k] = one alpha[k - 2] = -one beta = snp.zeros(k + 1) kappa = snp.zeros(k) gamma = snp.zeros(k) gamma[0] = one kappa[0] = 2 * one beta[k - 1] = 2 * one betaj = snp.zeros(k + 1) for j in range(1, k): gamma[j] = one - sum(gamma[:j] / snp.arange(j + 1, 1, -1)) kappa[j] = 2 * gamma[j] - gamma[j - 1] for i in range(0, j + 1): betaj[k - i - 1] = (-one)**i * sympy.combinatorial.factorials.binomial( j, i) * kappa[j] beta = beta + betaj name = str(k) + '-step Nystrom' return LinearMultistepMethod(alpha, beta, name=name, shortname='Nys' + str(k))
def Nystrom(k): r""" Construct the k-step explicit Nystrom linear multistep method. The methods are explicit and have order k. They have the form: `y_{n+1} = y_{n-1} + h \sum_{j=0}^{k-1} \beta_j f(y_n-k+j+1)` They are generated using equations (1.13) and (1.7) from [hairer1993]_ III.1, along with the binomial expansion and the relation in exercise 4 on p. 367. Note that the term "Nystrom method" is also commonly used to refer to a class of methods for second-order ODEs; those are NOT the methods generated by this function. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> nys3=lm.Nystrom(6) >>> nys3.order() 6 References: #. [hairer1993]_ """ import sympy from sympy import Rational one = Rational(1,1) alpha = snp.zeros(k+1) alpha[k] = one alpha[k-2] = -one beta = snp.zeros(k+1) kappa = snp.zeros(k) gamma = snp.zeros(k) gamma[0] = one kappa[0] = 2*one beta[k-1] = 2*one betaj = snp.zeros(k+1) for j in range(1,k): gamma[j] = one-sum(gamma[:j]/snp.arange(j+1,1,-1)) kappa[j] = 2 * gamma[j] - gamma[j-1] for i in range(0,j+1): betaj[k-i-1] = (-one)**i*sympy.combinatorial.factorials.binomial(j,i)*kappa[j] beta = beta+betaj name = str(k)+'-step Nystrom' return LinearMultistepMethod(alpha,beta,name=name,shortname='Nys'+str(k))
def Milne_Simpson(k): r""" Construct the k-step, Milne-Simpson method. The methods are implicit and (for k>=3) have order k+1. They have the form: `y_{n+1} = y_{n-1} + h \sum_{j=0}^{k} \beta_j f(y_n-k+j+1)` They are generated using equation (1.15), the equation in Exercise 3, and the relation in exercise 4, all from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> ms3=lm.Milne_Simpson(3) >>> ms3.order() 4 References: [hairer1993]_ """ import sympy alpha = snp.zeros(k + 1) beta = snp.zeros(k + 1) alpha[k] = 1 alpha[k - 2] = -1 gamma = snp.zeros(k + 1) kappa = snp.zeros(k + 1) gamma[0] = 1 kappa[0] = 2 beta[k] = 2 betaj = snp.zeros(k + 1) for j in range(1, k + 1): gamma[j] = -sum(gamma[:j] / snp.arange(j + 1, 1, -1)) kappa[j] = 2 * gamma[j] - gamma[j - 1] for i in range(0, j + 1): betaj[k - i] = (-1)**i * sympy.combinatorial.factorials.binomial( j, i) * kappa[j] beta = beta + betaj name = str(k) + '-step Milne-Simpson' return LinearMultistepMethod(alpha, beta, name=name, shortname='MS' + str(k))
def Adams_Bashforth(k): r""" Construct the k-step, Adams-Bashforth method. The methods are explicit and have order k. They have the form: `y_{n+1} = y_n + h \sum_{j=0}^{k-1} \beta_j f(y_n-k+j+1)` They are generated using equations (1.5) and (1.7) from [hairer1993]_ III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> ab3=lm.Adams_Bashforth(3) >>> ab3.order() 3 References: #. [hairer1993]_ """ import sympy from sympy import Rational one = Rational(1, 1) alpha = snp.zeros(k + 1) beta = snp.zeros(k + 1) alpha[k] = one alpha[k - 1] = -one gamma = snp.zeros(k) gamma[0] = one beta[k - 1] = one betaj = snp.zeros(k + 1) for j in range(1, k): gamma[j] = one - sum(gamma[:j] / snp.arange(j + 1, 1, -1)) for i in range(0, j + 1): betaj[k - i - 1] = (-one)**i * sympy.combinatorial.factorials.binomial( j, i) * gamma[j] beta = beta + betaj name = str(k) + '-step Adams-Bashforth' return LinearMultistepMethod(alpha, beta, name=name, shortname='AB' + str(k))
def Adams_Moulton(k): r""" Construct the k-step, Adams-Moulton method. The methods are implicit and have order k+1. They have the form: `y_{n+1} = y_n + h \sum_{j=0}^{k} \beta_j f(y_n-k+j+1)` They are generated using equation (1.9) and the equation in Exercise 3 from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> am3=lm.Adams_Moulton(3) >>> am3.order() 4 References: [hairer1993]_ """ import sympy alpha = snp.zeros(k + 1) beta = snp.zeros(k + 1) alpha[k] = 1 alpha[k - 1] = -1 gamma = snp.zeros(k + 1) gamma[0] = 1 beta[k] = 1 betaj = snp.zeros(k + 1) for j in range(1, k + 1): gamma[j] = -sum(gamma[:j] / snp.arange(j + 1, 1, -1)) for i in range(0, j + 1): betaj[k - i] = (-1)**i * sympy.combinatorial.factorials.binomial( j, i) * gamma[j] beta = beta + betaj name = str(k) + '-step Adams-Moulton' return LinearMultistepMethod(alpha, beta, name=name, shortname='AM' + str(k))
def elm_ssp2(k): r""" Returns the optimal SSP k-step linear multistep method of order 2. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> lm10=lm.elm_ssp2(10) >>> lm10.ssp_coefficient() 8/9 """ alpha = snp.zeros(k + 1) beta = snp.zeros(k + 1) alpha[-1] = sympy.Rational(1, 1) alpha[0] = sympy.Rational(-1, (k - 1)**2) alpha[k - 1] = sympy.Rational(-(k - 1)**2 + 1, (k - 1)**2) beta[k - 1] = sympy.Rational(k, k - 1) name = 'Optimal ' + str(k) + '-step, 2nd order SSP method.' return LinearMultistepMethod(alpha, beta, name=name)
def Milne_Simpson(k): r""" Construct the k-step, Milne-Simpson method. The methods are implicit and (for k>=3) have order k+1. They have the form: `y_{n+1} = y_{n-1} + h \sum_{j=0}^{k} \beta_j f(y_n-k+j+1)` They are generated using equation (1.15), the equation in Exercise 3, and the relation in exercise 4, all from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> ms3=lm.Milne_Simpson(3) >>> ms3.order() 4 References: [hairer1993]_ """ import sympy alpha = snp.zeros(k+1) beta = snp.zeros(k+1) alpha[k] = 1 alpha[k-2] = -1 gamma = snp.zeros(k+1) kappa = snp.zeros(k+1) gamma[0] = 1 kappa[0] = 2 beta[k] = 2 betaj = snp.zeros(k+1) for j in range(1,k+1): gamma[j] = -sum(gamma[:j]/snp.arange(j+1,1,-1)) kappa[j] = 2 * gamma[j] - gamma[j-1] for i in range(0,j+1): betaj[k-i] = (-1)**i*sympy.combinatorial.factorials.binomial(j,i)*kappa[j] beta = beta+betaj name = str(k)+'-step Milne-Simpson' return LinearMultistepMethod(alpha,beta,name=name,shortname='MS'+str(k))
def sand_cc(s): r""" Construct Sand's circle-contractive method of order `p=2(s+1)` that uses `2^s + 1` steps. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> cc4 = lm.sand_cc(4) >>> cc4.order() 10 >>> cc4.ssp_coefficient() 1/8 **References**: #. [sand1986]_ """ import sympy one = sympy.Rational(1) zero = sympy.Rational(0) k = 2**s + 1 p = 2*(s+1) Jn = [k,k-1] for i in range(1,s+1): Jn.append(k-1-2**i) alpha = snp.zeros(k+1) beta = snp.zeros(k+1) # This is inefficient for j in Jn: tau_product = one tau_sum = zero tau = [one/(j-i) for i in Jn if i!=j] tau_product = np.prod(tau) tau_sum = np.sum(tau) beta[j] = tau_product**2 alpha[j] = 2*beta[j]*tau_sum return LinearMultistepMethod(alpha,beta,'Sand circle-contractive')
def Adams_Bashforth(k): r""" Construct the k-step, Adams-Bashforth method. The methods are explicit and have order k. They have the form: `y_{n+1} = y_n + h \sum_{j=0}^{k-1} \beta_j f(y_n-k+j+1)` They are generated using equations (1.5) and (1.7) from [hairer1993]_ III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> ab3=lm.Adams_Bashforth(3) >>> ab3.order() 3 References: #. [hairer1993]_ """ import sympy from sympy import Rational one = Rational(1,1) alpha=snp.zeros(k+1) beta=snp.zeros(k+1) alpha[k]=one alpha[k-1]=-one gamma=snp.zeros(k) gamma[0]=one beta[k-1]=one betaj=snp.zeros(k+1) for j in range(1,k): gamma[j]=one-sum(gamma[:j]/snp.arange(j+1,1,-1)) for i in range(0,j+1): betaj[k-i-1]=(-one)**i*sympy.combinatorial.factorials.binomial(j,i)*gamma[j] beta=beta+betaj name=str(k)+'-step Adams-Bashforth' return LinearMultistepMethod(alpha,beta,name=name,shortname='AB'+str(k))
def sand_cc(s): r""" Construct Sand's circle-contractive method of order `p=2(s+1)` that uses `2^s + 1` steps. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> cc4 = lm.sand_cc(4) >>> cc4.order() 10 >>> cc4.ssp_coefficient() 1/8 **References**: #. [sand1986]_ """ import sympy one = sympy.Rational(1) zero = sympy.Rational(0) k = 2**s + 1 p = 2 * (s + 1) Jn = [k, k - 1] for i in range(1, s + 1): Jn.append(k - 1 - 2**i) alpha = snp.zeros(k + 1) beta = snp.zeros(k + 1) # This is inefficient for j in Jn: tau_product = one tau_sum = zero tau = [one / (j - i) for i in Jn if i != j] tau_product = np.prod(tau) tau_sum = np.sum(tau) beta[j] = tau_product**2 alpha[j] = 2 * beta[j] * tau_sum return LinearMultistepMethod(alpha, beta, 'Sand circle-contractive')
def elm_ssp2(k): r""" Returns the optimal SSP k-step linear multistep method of order 2. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> lm10=lm.elm_ssp2(10) >>> lm10.ssp_coefficient() 8/9 """ import sympy alpha=snp.zeros(k+1) beta=snp.zeros(k+1) alpha[-1]=sympy.Rational(1,1) alpha[0]=sympy.Rational(-1,(k-1)**2) alpha[k-1]=sympy.Rational(-(k-1)**2+1,(k-1)**2) beta[k-1]=sympy.Rational(k,k-1) name='Optimal '+str(k)+'-step, 2nd order SSP method.' return LinearMultistepMethod(alpha,beta,name=name)
def Adams_Moulton(k): r""" Construct the k-step, Adams-Moulton method. The methods are implicit and have order k+1. They have the form: `y_{n+1} = y_n + h \sum_{j=0}^{k} \beta_j f(y_n-k+j+1)` They are generated using equation (1.9) and the equation in Exercise 3 from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> am3=lm.Adams_Moulton(3) >>> am3.order() 4 References: [hairer1993]_ """ import sympy alpha=snp.zeros(k+1) beta=snp.zeros(k+1) alpha[k]=1 alpha[k-1]=-1 gamma=snp.zeros(k+1) gamma[0]=1 beta[k]=1 betaj=snp.zeros(k+1) for j in range(1,k+1): gamma[j]= -sum(gamma[:j]/snp.arange(j+1,1,-1)) for i in range(0,j+1): betaj[k-i]=(-1)**i*sympy.combinatorial.factorials.binomial(j,i)*gamma[j] beta=beta+betaj name=str(k)+'-step Adams-Moulton' return LinearMultistepMethod(alpha,beta,name=name,shortname='AM'+str(k))
def backward_difference_formula(k): r""" Construct the k-step backward differentiation method. The methods are implicit and have order k. They have the form: `\sum_{j=0}^{k} \alpha_j y_{n+k-j+1} = h \beta_j f(y_{n+1})` They are generated using equation (1.22') from Hairer & Wanner III.1, along with the binomial expansion. **Examples**:: >>> import nodepy.linear_multistep_method as lm >>> bdf4=lm.backward_difference_formula(4) >>> bdf4.A_alpha_stability() 73 **References**: #.[hairer1993]_ pp. 364-365 """ import sympy alpha=snp.zeros(k+1) beta=snp.zeros(k+1) beta[k]=1 gamma=snp.zeros(k+1) gamma[0]=1 alphaj=snp.zeros(k+1) for j in range(1,k+1): gamma[j]= sympy.Rational(1,j) for i in range(0,j+1): alphaj[k-i]=(-1)**i*sympy.combinatorial.factorials.binomial(j,i)*gamma[j] alpha=alpha+alphaj name=str(k)+'-step BDF' return LinearMultistepMethod(alpha,beta,name=name,shortname='BDF'+str(k))
def stability_matrix(self, z): r""" Constructs the stability matrix of a two-step Runge-Kutta method. Right now just for a specific value of z. We ought to use Sage to do it symbolically. **Output**: M -- stability matrix evaluated at z WARNING: This only works for Type I & Type II methods right now!!! """ s = self.Ahat.shape[1] if self.type == 'General': # J Y^n = K Y^{n-1} K1 = np.column_stack((z * self.Ahat, self.d, 1 - self.d)) K2 = snp.zeros(s + 2) K2[-1] = 1 K3 = np.concatenate( (z * self.bhat, np.array((self.theta, 1 - self.theta)))) K = np.vstack((K1, K2, K3)) J = snp.eye(s + 2) J[:s, :s] = J[:s, :s] - z * self.A J[-1, :s] = z * self.b M = snp.solve(J.astype('complex64'), K.astype('complex64')) #M = snp.solve(J, K) # This version is slower else: D = np.hstack([1. - self.d, self.d]) thet = np.hstack([1. - self.theta, self.theta]) A, b = self.A, self.b if self.type == 'Type II': ahat = np.zeros([self.s, 1]) ahat[:, 0] = self.Ahat[:, 0] bh = np.zeros([1, 1]) bh[0, 0] = self.bhat[0] A = np.hstack([ahat, self.A]) A = np.vstack([np.zeros([1, self.s + 1]), A]) b = np.vstack([bh, self.b]) M1 = np.linalg.solve(np.eye(self.s) - z * self.A, D) L1 = thet + z * np.dot(self.b.T, M1) M = np.vstack([L1, [1., 0.]]) return M
def stability_matrix(self,z): r""" Constructs the stability matrix of a two-step Runge-Kutta method. Right now just for a specific value of z. We ought to use Sage to do it symbolically. **Output**: M -- stability matrix evaluated at z WARNING: This only works for Type I & Type II methods right now!!! """ s = self.Ahat.shape[1] if self.type == 'General': # J Y^n = K Y^{n-1} K1 = np.column_stack((z*self.Ahat,self.d,1-self.d)) K2 = snp.zeros(s+2); K2[-1] = 1 K3 = np.concatenate((z*self.bhat,np.array((self.theta,1-self.theta)))) K = np.vstack((K1,K2,K3)) J = snp.eye(s+2) J[:s,:s] = J[:s,:s] - z*self.A J[-1,:s] = z*self.b M = snp.solve(J.astype('complex64'),K.astype('complex64')) #M = snp.solve(J, K) # This version is slower else: D=np.hstack([1.-self.d,self.d]) thet=np.hstack([1.-self.theta,self.theta]) A,b=self.A,self.b if self.type=='Type II': ahat = np.zeros([self.s,1]); ahat[:,0] = self.Ahat[:,0] bh = np.zeros([1,1]); bh[0,0]=self.bhat[0] A = np.hstack([ahat,self.A]) A = np.vstack([np.zeros([1,self.s+1]),A]) b = np.vstack([bh,self.b]) M1=np.linalg.solve(np.eye(self.s)-z*self.A,D) L1=thet+z*np.dot(self.b.T,M1) M=np.vstack([L1,[1.,0.]]) return M