-- (almost) direct translation from rkc.f [http://www.netlib.org/ode/rkc.f] -- Implementation of Runge-Kutta-Chebyshev explicit ODE solver -- -- Plan: -- 1. basic stage: fixed timestep and s (order of Chebyshev polynomials) [DONE] -- 2. error estimation & control [in progress] -- 3. automatic time step adjustment [almost-DONE] -- 4. automatic s -- 5. spectral radius -- --require "profiler" require 'lib' module(..., package.seeall); function rkc_v1(t,h,s,obj) -- first version local c,mu,mut,nu,gammat = calc_coefs(s) local phase = obj.phase local dv = obj.derivatives local W0 = phase local F0 = obj:derivs(t,phase) local W1 = phase + mut[1]*h*F0 local Wm1,Wm2 = W1,W0 local W = W0+0 local thr = 1e3 for j = 2,s do if math.abs(absmax_phase(W)) > thr then io.stderr:write('j: ',j,', mu: ', mu[j],', nu: ', nu[j], ', mut: ', mut[j],', gammat: ', gammat[j],'\n') io.stderr:write(obj:pr_state(t,W)) error(string.format("the solution has ran away: %f", absmax_phase(W) )) end W = (1 - mu[j] - nu[j])*W0 + mu[j]*Wm1 + nu[j]*Wm2 W = W + mut[j]*h*(obj:derivs(t+c[j-1]*h,Wm1) + gammat[j]*F0) Wm2 = Wm1 -- wrap around Wm1 = W end --io.stderr:write('\n') return W end function calc_coefs(s) -- calculate coefficients for number of stages s. -- Return vectors. Used in rkc_v1 local coefs = {} local epsilon,w0,w1,tmp1,tmp2,arg local b = {} local mu = {} local nu = {} local mut = {} -- mu with tilda local c = {} -- step size adjustments local gammat = {} -- gamma with tilda local T,dT,d2T = {},{},{} -- Chebyshev polynomial and its derivatives epsilon = 2/13 w0 = 1 + epsilon/(s*s) tmp1 = w0*w0 - 1 -- temporary var tmp2 = math.sqrt(tmp1) -- temporary var arg = s*math.log(w0 + tmp2) -- w1 = T's(w0)/T''s(w0) w1 = math.sinh(arg)*tmp1 / (math.cosh(arg)*s*tmp2 - w0 *math.sinh(arg)) --?-- b[0] = .25/(w0*w0) b[1] = b[0] mut[1] = w1*b[1] T[0] = 1 -- todo: check! T[1] = w0 dT[0] = 0 dT[1] = 1 d2T[0] = 0 d2T[1] = 0 c[0]= 0 -- c[1] = mut[1] -- as in rkc.f for j = 2,s do T[j] = 2*w0*T[j-1] - T[j-2] dT[j] = 2*w0*dT[j-1] - dT[j-2] + 2*T[j-1] d2T[j] = 2*w0*d2T[j-1] - d2T[j-2] + 4*dT[j-1] b[j] = d2T[j] / (dT[j] * dT[j]) mu[j] = 2 * w0*b[j] / b[j-1] nu[j] = -b[j] / b[j-2] mut[j] = mu[j] * w1 / w0 gammat[j] = -1 + T[j-1]*b[j-1] c[j] = mu[j]*c[j-1] + nu[j]*c[j-2] + mut[j]*(1 + gammat[j]) -- as in the rkc.f code --c[j] = w1*d2T[j]/dT[j] -- as in the paper (B.P. Sommeijer et. al, 1997), not in the rkc.f code end --c[1] = c[2]/dT[2] --c[s] = 1 return c,mu,mut,nu,gammat end function rkc_v3(W0, t,h,s,obj) -- third version (speed optimisation) local eps,w0,w1,tmp1,arg eps = 2/13 w0 = 1+ eps/(s*s) tmp1 = w0*w0 - 1 arg = s*math.log(w0 + math.sqrt(tmp1)) w1 = math.sinh(arg)*tmp1 / (math.cosh(arg)*s*math.sqrt(tmp1) - w0 *math.sinh(arg)) local cfs_2 = { -- coefs_{j-2} b = .25/(w0*w0), T = 1, dT = 0, d2T = 0, c = 0, } local cfs_1 = { -- coefs_{j-1} b = cfs_2.b, T = w0, dT = 1, d2T = 0, mut = w1*cfs_2.b, c = w1*cfs_2.b, -- as in rkc.f } local dv local F0 = obj:derivs(t, W0) + 0 -- derivs with the original phase local Wm1 = W0 + F0*(cfs_1.mut*h) local Wm2 = W0 + 0 local W = W0 + 0 --setmetatable({}, lib.vecmt) local cfs = {} for j = 2,s do cfs.T = 2*w0*cfs_1.T - cfs_2.T cfs.dT = 2*w0*cfs_1.dT - cfs_2.dT + 2*cfs_1.T cfs.d2T = 2*w0*cfs_1.d2T - cfs_2.d2T + 4*cfs_1.dT cfs.b = cfs.d2T / (cfs.dT * cfs.dT) cfs.mu = 2*w0*cfs.b/cfs_1.b cfs.nu = -cfs.b / cfs_2.b cfs.mut = cfs.mu * w1 / w0 cfs.gat = -1 + cfs_1.T * cfs_1.b --as in the rkc.f --cfs.c = cfs.mu*cfs_1.c + cfs.nu*cfs_2.c + cfs.mut*(1 + cfs.gat) -- as in the paper (B.P. Sommeijer et. al, 1997), -- not as in the rkc.f code cfs.c = w1*cfs.d2T/cfs.dT dv = obj:derivs(t + cfs_1.c*h, Wm1) -- calc derivs with another phase for n,val in ipairs(W0) do W[n] = ((1 - cfs.mu - cfs.nu)*val + cfs.mu*Wm1[n] + cfs.nu*Wm2[n] + cfs.mut*h*(dv[n] + cfs.gat*F0[n])) ---------- Wm2[n] = Wm1[n] Wm1[n] = W[n] end --wrap around --TODO: redefine as a tail recursion :) --(cp_vector target source) [desctructive for target] --lib.cp_vector(Wm2,Wm1) --lib.cp_vector(Wm1,W) lib.cp_simp_table(cfs_2,cfs_1) lib.cp_simp_table(cfs_1,cfs) --io.stderr:write(string.format("::: %3.20e :::\n\n", -- lib.sum_sq_vect(W))) end return W, t+h, h end function Estnm1(U0, U1, dv0, dv1, h) local E = setmetatable({}, lib.vecmt) for i,val in ipairs(U0) do E[i] = ((U0[i] - U1[i])*2 + h*(dv0[i] + dv1[i]))*2/5 end return E end function Est2(a,b,h) return (a*12 + 6*h*b)/15 end local hprev = 0.0 function rkc_a(U0, t, h, s, obj) -- rkc with adjustable timestep [gives ca 3x speedup] --profiler.start() local recipr_atol = 1/1e-3 local nmax = 1e3 local j,n = 0,0 local dv0 = obj:derivs(t, U0) + 0 local dv1 local U1, E, err local rec = 0.85 -- h recalculating coefficient h = h/rec repeat U1 = rkc_v3(U0, t, h, s, obj) dv1 = obj:derivs(t+h, U1) E = Estnm1(U0, U1, dv0, dv1, h) * recipr_atol err,n = lib.sum_sq_vect(E) err = math.sqrt(err/n) h = h*rec j = j+1 until (err <= 1 or j > nmax) h = h/rec local tnext = t + h --profiler.stop() --io.stderr:write(string.format('-------------\n')) return U1, tnext, h end rkc = rkc_v3 -- pointer to the active verstion