if not modules then modules = { } end modules ['meta-imp-functions'] = { version = 1.001, comment = "companion to meta-imp-functions.mkxl", author = "Hans Hagen, PRAGMA-ADE, Hasselt NL", copyright = "PRAGMA ADE / ConTeXt Development Team", license = "see context related readme files", } local formatters = string.formatters local sequenced = table.sequenced local noffunctions = 0 local version = 1 local function preparecache(p) noffunctions = noffunctions + 1 local action = p.action p.action = nil local hash = md5.HEX(sequenced(p)) local name = formatters["mkiv-%s-m-f-%03i.lua"](tex.jobname,noffunctions) p.action = action return name, hash end local function getcache(p) local cache = p.cache if cache then local name, hash = preparecache(p) local data = table.load(name) if data and data.hash == hash and data.version == version and data.data then return hash, name, data.data else return hash, name, false end else return false, false, false end end local function setcache(hash,name,data) local result = { version = version, hash = hash, data = data, } table.save(name,result) end local injectpath = mp.inject.path local getparameterset = metapost.getparameterset local report = logs.reporter("metapost","functions") local functions = { } local actions = { } function mp.registerfunction(specification) local name = specification.name functions[name] = specification end function mp.registeraction(specification) local name = specification.name actions[name] = specification end metapost.registerscript("processfunction", function() local specification = getparameterset("function") local name = specification.name local lua = specification.lua local fnction = functions[name] local action = lua and actions[lua] if fnction then if action then specification.action = action.action end -- statistics.starttiming(functions) fnction.action(specification) -- statistics.stoptiming(functions) end end) -- statistics.register("mp function time", function() -- return statistics.elapsedseconds(functions,"including feedback to metapost") -- end) -- Here comes the fancy stuff: local math = math local sqrt = math.sqrt local mathfunctions = math.functions or { } math.functions = mathfunctions -- Todo : reference where we got the factors from because those from -- -- This is Runge-Kutta-Merson 4("5") -- See Table 4.1. Merson 4("5") of Hairer, Nørsett, Wanner - Solving Ordinary Differential Equations I (Springer, 2008) -- -- function mathfunctions.rungekutta(specification) -- local f = specification.action or function(t,x,y) return x, y end -- local x = specification.x or 0 -- local y = specification.y or 0 -- local t = 0 -- local tmax = specification.tmax or 1 -- local dt = specification.dt or tmax/10 -- local eps = specification.eps or dt/10 -- local r = 1 -- -- local result = { { x, y, x, y, x, y } } -- local result = { { x, y } } -- while t < tmax do -- local k1x, k1y = f(t, x, -- y) -- k1x = dt * k1x -- k1y = dt * k1y -- local k2x, k2y = f(t + (1/3) * dt, x + (1/3) * k1x, -- y + (1/3) * k1y) -- k2x = dt * k2x -- k2y = dt * k2y -- local k3x, k3y = f(t + (1/3) * dt, x + (1/6) * k1x + (1/6) * k2x, -- y + (1/6) * k1y + (1/6) * k2y) -- k3x = dt * k3x -- k3y = dt * k3y -- local k4x, k4y = f(t + (1/2) * dt, x + (1/8) * k1x + (3/8) * k3x, -- y + (1/8) * k1y + (3/8) * k3y) -- k4x = dt * k4x -- k4y = dt * k4y -- local k5x, k5y = f(t + dt, x + (1/2) * k1x - (3/2) * k3x - (2) * k4x, -- y + (1/2) * k1y - (3/2) * k3y - (2) * k4y) -- k5x = dt * k5x -- k5y = dt * k5y -- -- -- local teps = sqrt(((1/10-1/6) * k1x + (3/10) * k3x + (2/5-2/3) * k4x + (1/5 -1/6) * k5x)^2 + -- ((1/10-1/6) * k1y + (3/10) * k3y + (2/5-2/3) * k4y + (1/5 -1/6) * k5y)^2 ) -- if teps < eps then -- dt = 0.9 * dt * (eps/teps)^(1/4) -- x = x + (1/10) * k1x + (3/10) * k3x + (2/5) * k4x + (1/5) * k5x -- y = y + (1/10) * k1y + (3/10) * k3y + (2/5) * k4y + (1/5) * k5y -- r = r + 1 -- -- result[r] = { x, y, x, y, x, y } -- result[r] = { x, y } -- t = t + dt -- else -- dt = 0.9 * dt * (eps/teps)^(1/3) -- end -- end -- return result -- end local function rungekutta(specification) local f = specification.action or function(t,x,y) return x, y end local x = specification.x or 0 local y = specification.y or 0 local tmin = specification.tmin or 0 local tmax = specification.tmax or 1 local t = tmin local rmax = specification.maxpath or 0 local stepsize = specification.stepsize or "adaptive" local dt = specification.dt or (tmax-tmin)/10 local eps = specification.eps or dt/10 local kind = specification.kind or specification.type -- xy x y local adaptive = stepsize == "adaptive" local r = 1 local result if kind ~= "tx" and kind ~= "ty" then kind = "xy" end if kind == "xy" then -- result = { { x, y, x, y, x, y } } result = { { x, y } } elseif kind == "tx" then -- result = { { x, x, t, x, t, x } } result = { { t, x } } else -- result = { { x, y, t, y, t, y } } result = { { t, y } } end local hash, name, data = getcache(specification) if data then -- print(hash,name,"REUSING") return data else -- print(hash,name,"GENERATING") end if rmax == 0 then rmax = 0xFFFF end while t < tmax do local k1x, k1y = f(t, x, y) k1x = dt * k1x k1y = dt * k1y local k2x, k2y = f(t + (1/3) * dt, x + (1/3) * k1x, y + (1/3) * k1y) k2x = dt * k2x k2y = dt * k2y local k3x, k3y = f(t + (1/3) * dt, x + (1/6) * k1x + (1/6) * k2x, y + (1/6) * k1y + (1/6) * k2y) k3x = dt * k3x k3y = dt * k3y local k4x, k4y = f(t + (1/2) * dt, x + (1/8) * k1x + (3/8) * k3x, y + (1/8) * k1y + (3/8) * k3y) k4x = dt * k4x k4y = dt * k4y local k5x, k5y = f(t + dt, x + (1/2) * k1x - (3/2) * k3x - (2) * k4x, y + (1/2) * k1y - (3/2) * k3y - (2) * k4y) k5x = dt * k5x k5y = dt * k5y -- if adaptive then local teps = sqrt(((1/10-1/6) * k1x + (3/10) * k3x + (2/5-2/3) * k4x + (1/5 -1/6) * k5x)^2 + ((1/10-1/6) * k1y + (3/10) * k3y + (2/5-2/3) * k4y + (1/5 -1/6) * k5y)^2 ) local step = eps/teps if teps < eps then step = step^(1/4) dt = 0.9 * dt * step else step = step^(1/3) dt = 0.9 * dt * step goto again end end ::append:: t = t + dt x = x + (1/10) * k1x + (3/10) * k3x + (2/5) * k4x + (1/5) * k5x y = y + (1/10) * k1y + (3/10) * k3y + (2/5) * k4y + (1/5) * k5y r = r + 1 if kind == "xy" then result[r] = { x, y } elseif kind == "tx" then result[r] = { t, x } else result[r] = { t, y } end if r >= rmax then -- report("pathmax is set to %i, quiting",rmax) break end ::again:: end if name and hash then setcache(hash,name,result) end return result end mathfunctions.rungekutta = rungekutta mp.registerfunction { name = "rungekutta", action = function(specification) local result = rungekutta(specification) if result then injectpath(result) else injectpath { { 0, 0 } } end end }