That’s a different thing as it really means the time in the ODE definition !
You might have time on the right side of the equation (non-autonomous case).
This is independent from the implementational aspect I’m referring to. Maybe it’s possible to do the implementation differently, but this seemed to me as the most practical way when I designed it.
This reminds me to an ancient discussion with James on the list. I forgot the topic but James’ argument was that when you define the thing yourself, you know its properties. So, why would you ask for the size when you have defined the size yourself ? It’s very easy to keep that number stored in a variable before applying the ode solver.
The passed function is assumed to reflect the ODE and should not do something else – because of the way it is used. Maybe this is a bit similar to the case of UGenGraphFunction which is also used in a very specific way. All kinds of confusion come up when it is thought – as it often happens with new users – to also perform some language-only operation “as normal”.
Maybe that could be emphasized more clearly in the documentation.
This was the statement that you should please ignore after my fresh air break
As I pointed out in my follow-up, it is in fact used multiple times for the graph building – and purposely so !
You can snoop the file Fb1_ODEintdef.sc to compare the different implementations. The amount of applications of odeDef
in the Fb1_ODEintdefs gives a hint. For the default integration type \sym2
the helper function symplecticOp
is called with order 2, thus it uses odeDef
2 x 2 times, which explains the overall 32 = 8 (blockSize) * 4 calls in your example.
symplecticOp = { |order, odeDef, t, y, dt, size, args|
var newArgs = y.copy, yMid = y.copy, yNew = 0!size, or = 1/order;
or = 1/order;
div(order, 2).do { |k|
(k > 0).if {
newArgs = yNew.copy;
yMid = yNew.copy;
};
for(0, size-1, { |i|
(i > 0).if { newArgs[i-1] = yMid[i-1] };
yMid[i] = odeDef.(i, t, newArgs, *args) * dt * or + yMid[i];
});
for(size-1, 0, { |i|
(i + 1 < size).if { newArgs[i+1] = yNew[i+1] };
yNew[i] = odeDef.(i, t, newArgs, *args) * dt * or + yMid[i];
});
};
yNew
};
Fb1_ODEintdef(\sym2,
{ |odeDef, t, y, dt, size ... args|
symplecticOp.(2, odeDef, t, y, dt, size, args);
}, 1, 1);
Though, only 8 (blockSize) calls with \pec:
// variants of prediction-evaluation-correction
// using explicit Euler and trapezoidal rule
Fb1_ODEintdef(\pec,
{ |odeDef, t, y, dt, size ... args|
var tn = t + dt;
var y0 = y[0..size-1];
var y1 = y[size..2*size-1];
var p = y1 * dt + y0;
var pe = odeDef.(nil, tn, p, *args);
var pec = (y1 + pe) * dt * 0.5 + y0;
pec ++ pe
}, 1, 2);
But as said, the symplectic variants are far superior in terms of stability, a trade-off with CPU-usage that – if it is a concern – could be decided from case to case.
Cheers
Daniel