## mnhistory.rb: internal history list for a body of all the computed ## derivatives of the body's position: pos, vel, acc, etc. ## ## Piet Hut, 2004/04 ## ## A history object h is an array containing particle state information s, ## for successive times t, as follows: ## ## h = [ [t0, s0], [t1, s1], . . . , [ti, si], . . . ] ## ## si = [ pos, vel, acc, . . . ] ## ## expressed explicitly in components, the h values are: ## ## h[0] = oldest {time, state} pair ## h[0][0] = oldest time t0 ## h[0][1] = state at t0: an array of {0th, 1th, ...} derivatives of position ## h[0][1][0] = value of position vector at t0 ## h[0][1][0][k] = value of kth component of position vector at t0 ## h[0][1][1] = value of velocity vector at t0 ## h[0][1][2] = value of acceleration vector at t0 ## h[1] = second {time, state} pair ## h[1][0] = second time t1 ## h[1][1] = state at t1 ## h[1][1][0] = value of position vector at t1 ## etc. ## ## Upon creation, a history list h contains at least one value, the oldest ## time t0. In addition, a state array is created of zero length, ready to ## be extended automatically when values are assigned to pos, vel, acc, etc. ## The initial structure is thus: ## ## h = [[0.0, []]] ## ## After creation, h can only grow. Before doing anything dynamic, one has ## to provide the particle with an initial position and velocity. At any ## given time after that, h can take on the following four forms: ## ## -- predicting: ## the particle has a `latest sales date', h.last[0], up to which time ## it can predict its state, even though its actual state h.last[1] has ## not yet been computed: h.last[1] = [], an empty array. Note that its ## penultimate state includes at least an acceleration h[h.size-2][1][2] ## and possibly higher derivatives, such as jerk h[h.size-2][1][3], etc. ## ## -- arrived: ## the particle position and velocity have been updated to its latest time ## h.last[0], and so h has no future `shelf life': it can only postdict its ## state, since it has not yet received values for acceleration, jerk, etc.: ## h.last[1] = [pos, vel]. ## ## -- incomplete: ## while moving between the two stable states `predicting' and `arrived', ## the particle may call other particles that in turn may call the particle ## itself, and find it in an incomplete state; I'm not yet sure whether and ## if so, how often and how this may happend, but it would be prudent to ## allow for this possibility in designing the History class. ## ## After reading in the initial position and velocity, h takes on the form of ## an `arrived' structure. After calculating the higher derivates and the ## time step deduced from that, h takes the `predicted' form. The particle ## can then use the prescribed integration algorithm to step forwards, to reach ## again at an `arrived' structure, and so on. ## ## Note: there is one extra twist: we need to give all particles an acc before ## any body can determine a time step (which asks other particles for ## their acc); this is done with an "acc_initializing" flag in Body.new require "mnvector.rb" class History def initialize(time = 0) @history = Array.new @history[0] = Array.new @history[0][0] = time.to_f # first time @history[0][1] = Array.new # array of derivatives of position end def set_first_time(t) @history[0][0] = t end def extend(t, history_depth) new_index = @history.size @history[new_index] = Array.new @history[new_index][0] = t @history[new_index][1] = Array.new while @history.size > history_depth # prune @history[0] = nil @history.compact! end end def set_last_rndot(n, rndot) # rndot = d^n r / dt^n @history.last[1][n] = rndot end def set_penultimate_rndot(n, rndot) @history[@history.size-2][1][n] = rndot end def arrived? @history.last[1][0] and @history.last[1][1] end def predicting? (not @history.last[1][0]) and @history[@history.size-2][1][2] end def ndim @history[0][1][0].size end def earliest if @history[1] and @history[1][0] 2*@history[0][0] - @history[1][0] # allow backwards extrapolation else @history[0][0] end end def latest if @history.last[0] @history.last[0] else @history[(@history.size)-2][0] end end def before_latest if @history.last[0] @history[(@history.size)-2][0] else @history[(@history.size)-3][0] end end def last_index (@history.size) - 1 end def index_before(t) if t < earliest nil elsif t < @history[0][0] -1 # allow backwards extrapolation else i = 1 while @history[i] and @history[i][0] and @history[i][0] <= t i += 1 end i - 1 end end def index_after(t) if t > latest nil else if @history[(@history.size) -1][0] i = (@history.size) - 2 else i = (@history.size) - 3 end while i >= 0 and @history[i][0] >= t i -= 1 end i + 1 end end def extrapolate(n, i, dt) # for now, uses only derivatives at starting point if not @history[i][1][n] nil else dn = 0 while @history[i][1][n+dn+1] dn += 1 end # evaluate Taylor series, starting with the highest derivative # example: r + v.dt + a.dt^2/2 + j.dt^3/6 = # r + (v + (a + (j + 0) * dt / 3) * dt /2) * dt / 1 increment = Vector.new(ndim, 0.0) while (dn > 0) increment += @history[i][1][n+dn] increment *= dt/dn dn -= 1 end @history[i][1][n] + increment end end def improved_extrapolate(n, i, dt) # extend Taylor series by one term. if i == 0 # if no past information: increment = Vector.new(ndim, 0.0) # return zero else # if there is past information: dn = 0 # use previous time to estimate while @history[i][1][n+dn+1] # one higher order Taylor term dn += 1 # (first value of increment) end # increment = (@history[i][1][n+dn] - @history[i-1][1][n+dn]) * (1.0/ (@history[i][0] - @history[i-1][0]) ) dn += 1 while (dn > 0) increment *= dt/dn # compute Taylor coefficient dn -= 1 end end extrapolate(n, i, dt) + increment end def rndot(n,t) if t < earliest or t > latest nil else i = index_before(t) j = index_after(t) if i == j if @history[i][1][n] @history[i][1][n] elsif @history[i-1][1][n] # extrapolate(n, i-1, t - @history[i-1][0]) improved_extrapolate(n, i-1, t - @history[i-1][0]) else nil end else i = 0 if i == -1 # allow backwards extrapolation # extrapolate(n, i, t - @history[i][0]) improved_extrapolate(n, i, t - @history[i][0]) end end end def pp # pretty print print " [" init_flag = true @history.each do |x| if init_flag print "[" init_flag = false else print " [" end p x[0] x[1].each do |y| print " " p y end print " ]\n" end print " ]\n" end end