commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Phil Steitz <phil.ste...@gmail.com>
Subject Re: [math] fluent API for the ODE integrators
Date Wed, 18 Sep 2013 00:10:30 GMT
On 9/17/13 2:07 PM, Thomas Neidhart wrote:
> On 09/17/2013 04:22 PM, Evan Ward wrote:
>> Hi Luc,
>>
>> On Mon 16 Sep 2013 12:04:21 PM EDT, Luc Maisonobe wrote:
>>> Hello,
>>>
>>> I have started (at last) to work on the fluent API for ODE integrators.
>>> It seems to be well suited for the needs, and would allow a better
>>> separation of the integrator "engine" and the integrator "state". The
>>> engine will hold all the coefficients for the linear combinations within
>>> the integration steps and some general settings like min and max step
>>> sizes, or absolute and relative error thresholds for adaptive stepsize
>>> integrators. These data don't change during one integration run, and
>>> user may want to share them among several successive or parallel
>>> integration runs. The state will hold more transient values for numerous
>>> internal variables, like step size, start of step, pending discrete
>>> events, number of evaluations ... These data are updated continuously
>>> during an integration run and can certainly not be shared.
>>>
>>> Users will only see and configure the engine. Each time they call the
>>> integrate method on an engine, it will create on the fly a state
>>> dedicated for this call and everything within the integrator will
>>> reference this state instance. Users may call the integrate method
>>> several time to run them in parallel in different threads. As each call
>>> will have its own state instance, and as the engine itself which is
>>> shared by all states is immutable, everything should work without problem.

This sounds reasonable to me.  Two things you might want to think about:

0) It might make sense to actually split config / state data into
three parts instead of two:
a) problem instance definition (the coefficients, for example go here)
b) engine configuration (step size, other engine config)
c) algorithm state (what you call integrator state)

1) Be careful with mutable == state.  Item c) above might logically
include some runtime immutable stuff.

>>>
>>> These were the good news.
>>>
>>> The bad news are that it is difficult to separate the state from the
>>> engine as I have mixed everything right from the top level interface,
>>> which in this case is ODEIntegrator. The interface defines methods like
>>> getCurrentStepStart (which should refer to state) but also
>>> addStepHandler/addEventHandler (which should refer to engine). The next
>>> level interface (FirstOrederIntegrator) adds an integrate method which
>>> should belong to engine. The AbstractIntegrator class implements many of
>>> these methods and adds new protected methods which can be used by
>>> specific integrators.
>>>
>>> My current design is to have an internal IntegratorState class defined
>>> within AbstractIntegrator and providing all the state related fields and
>>> methods. For now, I don't think there is no need here for interfaces or
>>> abstract classes, the State is a simple thing. I may introduce a small
>>> hierarchy later on when I delve into the specific integrators, though.
>>> The integrate method in AbstractIntegrator would simply be something like:
>>>
>>> public void integrate(ExpandableStatefulODE equations, double t) {
>>> integrate(new IntegratorState(this), equations, t);
>>> }
>>>
>>> protected abstract integrate(IntegratorState state,
>>> ExpandableStatefulODE equations,
>>> double t);
>>>
>>> The protected integrate method with the state argument would be the same
>>> as the current integrate methods, just storing and updating variables in
>>> the State instance rather than in the integrator instance as done today.
>>>
>>> This should handle the engine part as desired, with a state instance
>>> created on the fly for each integration run.
>> +1 I like it. I think it will make the integrators easier to use and
>> more useful. :)
> I like the state idea too, a pattern I use a lot myself.
>
>>> Most of the IntegratorState class would be copied from the current
>>> AbstractIntegrator class, where many state related stuff is managed now.
>>> The problem is that many of these fields are protected fields
>>> (stepStart, stepSize, isLastStep, ...) and many methods are protected or
>>> even public methods (initIntegration, computeDerivatives). Of course,
>>> there are also the public methods inherited from the top level interface
>>> like getCurrentStepStart. I cannot simply remove the public method from
>>> the interface, nor remove the protected fields and methods from the
>>> AbstractIntegrator class, this would break compatibility...
>>>
>>> Most of these methods were never intended to be in the public API of the
>>> library, but due to Java limitations with cross packages visibility,
>>> some were put public. In the same spirit, the protected methods are not
>>> expected to be in the component API as it is not expected that people
>>> will provide their own implementations of ODE integrators: all the
>>> implementations and all the callers of the methods are within [math].
>>>
>>> So how can I handle these methods and fields? At least, I will deprecate
>>> them. I will also change all our own implementations to use the new
>>> IntegratorState class to store and update all these transient variables,
>>> so from [math] point of view, the deprecated protected methods and
>>> fields will not be used anymore. What should I do with the remnants and
>>> unused fields? As long as we are in the 3.X series, they should remain
>>> here, but how should they behave? Lets take one example: there is one
>>> method, intended to be called only by the specific integrators
>>> implementations:
>>>
>>> protected double acceptStep(AbstractStepInterpolator,
>>> double[], double[], double);
>>>
>>> It is called by AdamsBashforthIntegrator, AdamsMoultonIntegrator,
>>> EmbeddedRungeKuttaIntegrator, GraggBulirschStoerIntegrator and
>>> RungeKuttaIntegrator with lines like:
>>>
>>> stepStart = acceptStep(interpolator, y1, yDot1, t);
>>>
>>> All these calls will be changed to something like:
>>>
>>> state.setStepStart(state.acceptStep(interpolator, y1, yDot1, t));
>>>
>>> This means only the acceptStep of the new IntegratorState class will be
>>> called (and it will almost be a copy of the former method, moved to a
>>> new class). What should the acceptStep method in the AbstractIntegrator
>>> class do? It will not be called anymore by ourselves, and will not serve
>>> any purpose anymore. Still I cannot remove it.
>>>
>>> I certainly do not want to duplicate the entire package. We have seen
>>> users are lost when we do this and get questions on the list due to the
>>> confusion and the fact people either don't see the new package or see
>>> several packages and don't know which one to use.
>>>
>>> I have found one solution, but it is really ugly. I could temporarily
>>> add the IntegratorState instance as a field in AbstractIntegrator and
>>> have the integrator delegate to the State, as follows:
>>>
>>> public abstract class AbstractIntegrator
>>> implements FirstOrderIntegrator {
>>>
>>> /** Current step start time.
>>> * @deprecated as of 3.3 replaced by IntegratorState#stepStart
>>> */
>>> protected double stepStart;
>>>
>>> /** Last state instance.
>>> * Corresponds to last call to integrate().
>>> * @since 3.3
>>> * @deprecated temporary hack introduced in 3.3,
>>> to be removed in 4.0
>>> */
>>> @Deprecated
>>> private IntegratorState lastAllocatedState;
>>>
>>> /** Container for transient integration data.
>>> * @since 3.3
>>> */
>>> protected class IntegratorState {
>>>
>>> /** Current step start time. */
>>> private double stepStart;
>>>
>>> /** Get current step start time.
>>> * @return current step start time
>>> */
>>> public double getCurrentStepStart() {
>>> return stepStart;
>>> }
>>>
>>> /** Set current step start time.
>>> * @param current step start time
>>> */
>>> public void setCurrentStepStart(double stepStart) {
>>> this.stepStart = stepStart;
>>> // TODO: to be removed for 4.0
>>> AbstractIntegrator.this.stepStart = stepStart;
>>> }
>>>
>>> }
>>>
>>> /** {@inheritDoc}
>>> * @deprecated as of 3.3 replaced by
>>> * IntegratorState#getCurrentStepStart()
>>> */
>>> public double getCurrentStepStart() {
>>> return lastAllocatedState.getCurrentStepStart();
>>> }
>>>
>>> public void integrate(ExpandableStatefulODE equations, double t) {
>>>
>>> final IntegratorState localState = new IntegratorState(this);
>>>
>>> // TODO: to be removed for 4.0
>>> lastAllocatedState = localState;
>>>
>>> integrate(localState, equations, t);
>>>
>>> }
>>>
>>> protected abstract integrate(IntegratorState state,
>>> ExpandableStatefulODE equations,
>>> double t);
>>>
>>> }
>>>
>>> Here, we preserve the stepStart protected field, it will be updated
>>> automatically by the new code that uses IntegratorState.setStepStart,
>>> users may read it from dereived classes. We also preserve the
>>> getCurrentStepStart method, which delegates to the last allocated state.
>>> What will *not* work however is when people who would have developed
>>> their own integrator would try to update the field from their derived
>>> class. They will be able to do it, but won't have the desired effect
>>> since [math] doesn't use it anymore. Yes, I know, it was bad design
>>> right from the beginning to have a protected field, my bad.
>>>
>>> Of course, this would mean that each new integrate run would override
>>> the lastAllocatedState instance, but this is similar to the current
>>> behaviour. If people attempt to call integrate several time in parallel,
>>> lots of contingency problems will appear right now, so we don't
>>> introduce a new problem here. The field would be removed from
>>> AbstractIntegrator in 4.0 when the methods will be removed and all
>>> access will be limited at IntegratorState level.
>>>
>>> What do you think?
>> IMHO just make the change for 4.0. I think it would be easier for the
>> users to only have to learn the intricacies of one new API. Especially
>> since we know the deprecated 3.X API will be confusing and not
>> completely backward compatible. Just my $0.02.
> I agree with Evan, your proposed solution may work technically to
> preserve binary compatibility, but will break client code anyway.
>
> Such a change would only be acceptable for a major release imho. Otoh we
> have already quite some content for a 3.3 release. Let's try to release
> this in, let's say 1 month, and then work on a 4.0 release, cleaning up
> all the API drawbacks/flaws we have accumulated in the last years.
>
> As you already said, several users raised their concern about all the
> deprecated stuff in commons-math, I think we should not create more
> headaches (especially for ourselves ;-).

Big +1 here.  Lets get 3.3 out and get this right in 4.0 without
trying to preserve compat.

Phil
>
> Thomas
>
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
> For additional commands, e-mail: dev-help@commons.apache.org
>
>


---------------------------------------------------------------------
To unsubscribe, e-mail: dev-unsubscribe@commons.apache.org
For additional commands, e-mail: dev-help@commons.apache.org


Mime
View raw message