001/* An integrator for the continuous domain.
002
003 Copyright (c) 1997-2015 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.domains.continuous.kernel;
029
030import java.util.Collection;
031import java.util.LinkedList;
032
033import ptolemy.actor.IOPort;
034import ptolemy.actor.NoTokenException;
035import ptolemy.actor.TypedAtomicActor;
036import ptolemy.actor.TypedIOPort;
037import ptolemy.actor.continuous.ContinuousStatefulComponent;
038import ptolemy.actor.continuous.ContinuousStepSizeController;
039import ptolemy.actor.parameters.ParameterPort;
040import ptolemy.actor.parameters.PortParameter;
041import ptolemy.actor.util.BooleanDependency;
042import ptolemy.actor.util.CausalityInterface;
043import ptolemy.actor.util.DefaultCausalityInterface;
044import ptolemy.actor.util.Dependency;
045import ptolemy.actor.util.Time;
046import ptolemy.data.DoubleToken;
047import ptolemy.data.type.BaseType;
048import ptolemy.kernel.CompositeEntity;
049import ptolemy.kernel.util.Attribute;
050import ptolemy.kernel.util.IllegalActionException;
051import ptolemy.kernel.util.InvalidStateException;
052import ptolemy.kernel.util.NameDuplicationException;
053import ptolemy.kernel.util.StringAttribute;
054import ptolemy.kernel.util.Workspace;
055
056///////////////////////////////////////////////////////////////////
057//// ContinuousIntegrator
058
059/**
060 The integrator in the continuous domain.
061
062 <p>The <i>derivative</i> port
063 receives the derivative of the state of the integrator with respect
064 to time. The <i>state</i> output port shows the state of the
065 integrator. So an ordinary differential equation (ODE), dx/dt = f(x,
066 t), can be built as follows:</p>
067
068 <pre>
069            +---------------+
070     dx/dt  |               |   x
071 +---------&gt;|   Integrator  |---------+-----&gt;
072 |          |               |         |
073 |          +----^-----^----+         |
074 |                                    |
075 |             |---------|            |
076 +-------------| f(x, t) |&lt;-----------+
077               |---------|
078 </pre>
079
080 <p> An integrator also has a port-parameter called
081 <i>initialState</i>. The parameter provides the initial state for
082 integration during the initialization stage of execution. If during
083 execution an input token is provided on the port, then the state of
084 the integrator will be reset at that time to the value of the
085 token. The default value of the parameter is 0.0.</p>
086
087 <p> An integrator also has an input port named <i>impulse</i>.  When
088 present, a token at the <i>impulse</i> input port is interpreted as
089 the weight of a Dirac delta function.  It causes an
090 increment or decrement to the state at the time of
091 the arrival of the value.  If both <i>impulse</i> and
092 <i>initialState</i> have data on the same microstep,
093 then <i>initialState</i> dominates.</p>
094
095 <p> Note that both <i>impulse</i> and <i>reset</i> expect to
096 receive discrete inputs. To preserve continuity, this means
097 that those inputs should be present only when the solver
098 step size is zero.
099 If this assumption is violated, then this actor will throw
100 an exception.</p>
101
102 <p> An integrator can generate an output (its current state) before
103 the derivative input is known, and hence can be used in feedback
104 loops like that above without creating a causality loop.  Since
105 <i>impulse</i> and <i>initialState</i> inputs affect the output
106 immediately, using them in feedback loops may require inclusion
107 of a TimeDelay actor.</p>
108
109 <p> For different ODE solving methods, the functionality of an
110 integrator may be different. The delegation and strategy design
111 patterns are used in this class, the abstract ODESolver class, and
112 the concrete ODE solver classes. Some solver-dependent methods of
113 integrators delegate to the concrete ODE solvers.</p>
114
115 <p> An integrator can possibly have several auxiliary variables for
116 the ODE solvers to use. The ODE solver class provides the number
117 of variables needed for that particular solver.  The auxiliary
118 variables can be set and get by setAuxVariables() and
119 getAuxVariables() methods.</p>
120
121 <p> This class is based on the CTBaseIntegrator by Jie Liu and
122 Haiyang Zheng, but it has more ports and provides more functionality.</p>
123
124 @author Haiyang Zheng and Edward A. Lee
125 @version $Id$
126 @since Ptolemy II 6.0
127 @Pt.ProposedRating Yellow (hyzheng)
128 @Pt.AcceptedRating Red (yuhong)
129 */
130public class ContinuousIntegrator extends TypedAtomicActor
131        implements ContinuousStatefulComponent, ContinuousStepSizeController {
132
133    /** Construct an integrator with the specified name and a container.
134     *  The integrator is in the same workspace as the container.
135     *  @param container The container.
136     *  @param name The name.
137     *  @exception NameDuplicationException If the name is used by
138     *  another actor in the container.
139     *  @exception IllegalActionException If ports can not be created, or
140     *   thrown by the super class.
141     */
142    public ContinuousIntegrator(CompositeEntity container, String name)
143            throws NameDuplicationException, IllegalActionException {
144        super(container, name);
145
146        impulse = new TypedIOPort(this, "impulse", true, false);
147        impulse.setTypeEquals(BaseType.DOUBLE);
148        StringAttribute cardinality = new StringAttribute(impulse, "_cardinal");
149        cardinality.setExpression("SOUTH");
150
151        derivative = new TypedIOPort(this, "derivative", true, false);
152        derivative.setTypeEquals(BaseType.DOUBLE);
153
154        state = new TypedIOPort(this, "state", false, true);
155        state.setTypeEquals(BaseType.DOUBLE);
156
157        initialState = new PortParameter(this, "initialState",
158                new DoubleToken(0.0));
159        initialState.setTypeEquals(BaseType.DOUBLE);
160        cardinality = new StringAttribute(initialState.getPort(), "_cardinal");
161        cardinality.setExpression("SOUTH");
162
163        _causalityInterface = new IntegratorCausalityInterface(this,
164                BooleanDependency.OTIMES_IDENTITY);
165    }
166
167    ///////////////////////////////////////////////////////////////////
168    ////                     ports and parameters                  ////
169
170    /** The impulse input port. This is a single port of type double.
171     */
172    public TypedIOPort impulse;
173
174    /** The derivative port. This is a single port of type double.
175     */
176    public TypedIOPort derivative;
177
178    /** The state port. This is a single port of type double.
179     */
180    public TypedIOPort state;
181
182    /** The initial state of type DoubleToken. The default value is 0.0.
183     */
184    public PortParameter initialState;
185
186    ///////////////////////////////////////////////////////////////////
187    ////                         public methods                    ////
188
189    /** If the specified attribute is <i>initialState</i>, then reset
190     *  the state of the integrator to its value.
191     *  @param attribute The attribute that has changed.
192     *  @exception IllegalActionException If the new parameter value
193     *  is not valid.
194     */
195    @Override
196    public void attributeChanged(Attribute attribute)
197            throws IllegalActionException {
198        if (attribute == initialState) {
199            _tentativeState = ((DoubleToken) initialState.getToken())
200                    .doubleValue();
201            _state = _tentativeState;
202            if (_debugging) {
203                _debug("initialState changed. Updating state to " + _state);
204            }
205        } else {
206            super.attributeChanged(attribute);
207        }
208    }
209
210    /** Clone this actor into the specified workspace. The new actor is
211     *  <i>not</i> added to the directory of that workspace (you must do this
212     *  yourself if you want it there).
213     *  The result is a new actor with the same ports as the original, but
214     *  no connections and no container.  A container must be set before
215     *  much can be done with this actor.
216     *  @param workspace The workspace for the cloned object.
217     *  @exception CloneNotSupportedException If cloned ports cannot have
218     *   as their container the cloned entity (this should not occur), or
219     *   if one of the attributes cannot be cloned.
220     *  @return A new ComponentEntity.
221     */
222    @Override
223    public Object clone(Workspace workspace) throws CloneNotSupportedException {
224        ContinuousIntegrator newObject = (ContinuousIntegrator) super.clone(
225                workspace);
226        newObject._auxVariables = null;
227        newObject._causalityInterface = new IntegratorCausalityInterface(
228                newObject, BooleanDependency.OTIMES_IDENTITY);
229        return newObject;
230    }
231
232    /** If the value at the <i>derivative</i> port is known, and the
233     *  current step size is bigger than 0, perform an integration.
234     *  If the <i>impulse</i> port is known and has data, then add the
235     *  value provided to the state; if the <i>initialState</i> port
236     *  is known and has data, then reset the state to the provided
237     *  value. If both <i>impulse</i> and <i>initialState</i> have
238     *  data, then <i>initialState</i> dominates. If either is
239     *  unknown, then simply return, leaving the output unknown. Note
240     *  that the signals provided at these two ports are required to
241     *  be purely discrete.  This is enforced by throwing an exception
242     *  if the current microstep is zero when they have
243     *  input data.
244     *  @exception IllegalActionException If the input is infinite or
245     *   not a number, or if thrown by the solver,
246     *   or if data is present at either <i>impulse</i>
247     *   or <i>initialState</i> and the step size is greater than zero.
248     */
249    @Override
250    public void fire() throws IllegalActionException {
251        super.fire();
252        ContinuousDirector dir = (ContinuousDirector) getDirector();
253        double stepSize = dir.getCurrentStepSize();
254        int microstep = dir.getIndex();
255
256        if (_debugging) {
257            Time currentTime = dir.getModelTime();
258            _debug("Fire at time " + currentTime + " and microstep " + microstep
259                    + " with step size " + stepSize);
260        }
261        // First handle the impulse input.
262        if (impulse.getWidth() > 0 && impulse.hasToken(0)) {
263            double impulseValue = ((DoubleToken) impulse.get(0)).doubleValue();
264            if (_debugging) {
265                _debug("-- impulse input received with value " + impulseValue);
266            }
267            if (impulseValue != 0.0) {
268                if (microstep == 0 && !_firstFiring) {
269                    throw new IllegalActionException(this,
270                            "Signal at the impulse port is not purely discrete.");
271                }
272                double currentState = getState() + impulseValue;
273                setTentativeState(currentState);
274                if (_debugging) {
275                    _debug("-- Due to impulse input, set state to "
276                            + currentState);
277                }
278            }
279        }
280        // Next handle the initialState port.
281        ParameterPort initialStatePort = initialState.getPort();
282        if (initialStatePort.getWidth() > 0 && initialStatePort.hasToken(0)) {
283            double initialValue = ((DoubleToken) initialStatePort.get(0))
284                    .doubleValue();
285            if (_debugging) {
286                _debug("-- initialState input received with value "
287                        + initialValue);
288            }
289            if (microstep == 0.0) {
290                throw new IllegalActionException(this,
291                        "Signal at the initialState port is not purely discrete.");
292            }
293            setTentativeState(initialValue);
294            if (_debugging) {
295                _debug("-- Due to initialState input, set state to "
296                        + initialValue);
297            }
298        }
299
300        // Produce the current _tentativeState as output, if it
301        // has not already been produced.
302        if (!state.isKnown()) {
303            double tentativeOutput = getTentativeState();
304            // If the round has not updated since the last output, then
305            // just produce the same output as last time.
306            int currentRound = dir._getODESolver()._getRound();
307            if (_lastRound == currentRound) {
308                tentativeOutput = _lastOutput;
309            }
310
311            if (_debugging) {
312                _debug("** Sending output " + tentativeOutput);
313            }
314            _lastOutput = tentativeOutput;
315            state.broadcast(new DoubleToken(tentativeOutput));
316        }
317
318        // The _tentativeSate is committed only in postfire(),
319        // but multiple rounds will occur before postfire() is called.
320        // At each round, this fire() method may be called multiple
321        // times, and we want to make sure that the integration step
322        // only runs once in the step.
323        if (derivative.isKnown() && derivative.hasToken(0)) {
324            int currentRound = dir._getODESolver()._getRound();
325            if (_lastRound < currentRound) {
326                // This is the first fire() in a new round
327                // where the derivative input is known and present.
328                // Update the tentative state. Note that we will
329                // have already produced an output, and so we
330                // will not read the updated _tentativeState
331                // again in subsequent invocations of fire()
332                // in this round. So it is safe to update
333                // _tentativeState.
334                _lastRound = currentRound;
335                double currentDerivative = getDerivative();
336                if (Double.isNaN(currentDerivative)
337                        || Double.isInfinite(currentDerivative)) {
338                    throw new IllegalActionException(this,
339                            "The provided derivative input is invalid: "
340                                    + currentDerivative);
341                }
342                if (stepSize > 0.0) {
343                    // The following method changes the tentative state.
344                    dir._getODESolver().integratorIntegrate(this);
345                }
346            }
347        }
348    }
349
350    /** Return the auxiliary variables in a double array.
351     *  The auxiliary variables are created in the prefire() method and
352     *  may be set during each firing of the actor. Return null if the
353     *  auxiliary variables have never been created.
354     *
355     *  @return The auxiliary variables in a double array.
356     *  @see #setAuxVariables
357     */
358    public double[] getAuxVariables() {
359        return _auxVariables;
360    }
361
362    /** Return a causality interface for this actor. This causality
363     *  interface expresses dependencies that are instances of
364     *  BooleanDependency that declare that the <i>state</i> output
365     *  port does not depend on any of the input ports at this
366     *  microstep. Moreover, the  <i>initialState</i> and <i>impulse</i> ports are
367     *  equivalent (to process inputs at either, you need to know
368     *  about inputs at the other).  You do not need to know about
369     *  inputs at <i>derivative</i>.
370     *  @return A representation of the dependencies between input ports
371     *   and output ports.
372     */
373    @Override
374    public CausalityInterface getCausalityInterface() {
375        return _causalityInterface;
376    }
377
378    /** Get the current value of the derivative input port.
379     *  @return The current value at the derivative input port.
380     *  @exception NoTokenException If reading the input throws it.
381     *  @exception IllegalActionException If thrown while reading
382     *  the input.
383     */
384    public double getDerivative()
385            throws NoTokenException, IllegalActionException {
386        double result = ((DoubleToken) derivative.get(0)).doubleValue();
387        if (_debugging) {
388            _debug("Read input: " + result);
389        }
390        return result;
391    }
392
393    /** Return the state of the integrator. The returned state is the
394     *  latest confirmed state.
395     *  @return The state of the integrator.
396     */
397    public final double getState() {
398        return _state;
399    }
400
401    /** Return the tentative state.
402     *  @return The tentative state.
403     *  @see #setTentativeState
404     */
405    public double getTentativeState() {
406        return _tentativeState;
407    }
408
409    /** Initialize the integrator. Check for the existence of a
410     *  director and an ODE solver. Set the state to the value given
411     *  by <i>initialState</i>.
412     *  @exception IllegalActionException If there is no director,
413     *   or the director has no ODE solver, or the initialState
414     *   parameter does not contain a valid token, or the superclass
415     *   throws it.
416     */
417    @Override
418    public void initialize() throws IllegalActionException {
419        if (!(getDirector() instanceof ContinuousDirector)) {
420            throw new IllegalActionException(this,
421                    "This actor can only be run in" + " a ContinuousDirector.");
422        }
423        ContinuousDirector dir = (ContinuousDirector) getDirector();
424
425        if (dir == null) {
426            throw new IllegalActionException(this, " no director available");
427        }
428
429        ContinuousODESolver solver = dir._getODESolver();
430
431        if (solver == null) {
432            throw new IllegalActionException(this, " no ODE solver available");
433        }
434
435        super.initialize();
436        _lastRound = -1;
437        _tentativeState = ((DoubleToken) initialState.getToken()).doubleValue();
438        _state = _tentativeState;
439        _firstFiring = true;
440
441        if (_debugging) {
442            _debug("Initialize: initial state = " + _tentativeState);
443        }
444
445        // The number of auxiliary variables that are used depends on
446        // the solver.
447        int n = solver.getIntegratorAuxVariableCount();
448        if (_auxVariables == null || _auxVariables.length != n) {
449            _auxVariables = new double[n];
450        }
451    }
452
453    /** Return true if the state is resolved successfully.
454     *  If the input is not available, or the input is a result of
455     *  divide by zero, a NumericalNonconvergeException is thrown.
456     *  @return True if the state is resolved successfully.
457     */
458    @Override
459    public boolean isStepSizeAccurate() {
460        ContinuousODESolver solver = ((ContinuousDirector) getDirector())
461                ._getODESolver();
462        _successful = solver.integratorIsAccurate(this);
463        return _successful;
464    }
465
466    /** Return false. This actor can produce some outputs even the
467     *  derivative input is unknown. This actor is crucial at breaking feedback
468     *  loops during simulation.
469     *  The impulse and initialState ports, have to be known for prefire to
470     *  return true (if they are connected).
471     *  @return False.
472     */
473    @Override
474    public boolean isStrict() {
475        return false;
476    }
477
478    /** Update the state. This commits the tentative state.
479     *  @return True always.
480     *  @exception IllegalActionException Not thrown in this base class.
481     */
482    @Override
483    public boolean postfire() throws IllegalActionException {
484        _lastRound = -1;
485        _firstFiring = false;
486
487        if (_debugging) {
488            _debug("Postfire called");
489        }
490        _state = _tentativeState;
491        if (_debugging) {
492            _debug("-- Committing the state: " + _state);
493        }
494        return super.postfire();
495    }
496
497    /** If either the <i>impulse</i> or <i>initialState</i> input is unknown,
498     *  then return false. Otherwise, return true.
499     *  @return True If the actor is ready to fire.
500     *  @exception IllegalActionException If the superclass throws it.
501     */
502    @Override
503    public boolean prefire() throws IllegalActionException {
504        boolean result = super.prefire();
505        if ((impulse.getWidth() == 0 || impulse.isKnown(0))
506                && (initialState.getPort().getWidth() == 0
507                        || initialState.getPort().isKnown(0))) {
508            return result;
509        }
510        return false;
511    }
512
513    /** Return the suggested next step size. This method delegates to
514     *  the integratorPredictedStepSize() method of the current ODESolver.
515     *  @return The suggested next step size.
516     */
517    @Override
518    public double suggestedStepSize() {
519        ContinuousODESolver solver = ((ContinuousDirector) getDirector())
520                ._getODESolver();
521        return solver.integratorSuggestedStepSize(this);
522    }
523
524    /** Return the estimation of the refined next step size.
525     *  If this integrator considers the current step to be accurate,
526     *  then return the current step size, otherwise return half of the
527     *  current step size.
528     *  @return The refined step size.
529     */
530    @Override
531    public double refinedStepSize() {
532        double step = ((ContinuousDirector) getDirector()).getCurrentStepSize();
533
534        if (_successful) {
535            return step;
536        } else {
537            return 0.5 * step;
538        }
539    }
540
541    /** Roll back to committed state. This resets the tentative state
542     *  to the current state.
543     */
544    @Override
545    public void rollBackToCommittedState() {
546        if (_debugging) {
547            _debug("Rolling back to state: " + _state);
548        }
549        _lastRound = -1;
550        _tentativeState = _state;
551    }
552
553    /** Set the value of an auxiliary variable. The index indicates
554     *  which auxiliary variable in the auxVariables array. If the
555     *  index is out of the bound of the auxiliary variable array, an
556     *  InvalidStateException is thrown to indicate an inconsistency
557     *  in the ODE solver.
558     *
559     *  @param index The index in the auxVariables array.
560     *  @param value The value to be set.
561     *  @exception InvalidStateException If the index is out of the range
562     *  of the auxiliary variable array.
563     *  @see #getAuxVariables
564     */
565    public void setAuxVariables(int index, double value)
566            throws InvalidStateException {
567        try {
568            _auxVariables[index] = value;
569        } catch (ArrayIndexOutOfBoundsException e) {
570            throw new InvalidStateException(this,
571                    "index out of the range of the auxVariables.");
572        }
573    }
574
575    /** Set the tentative state. Tentative state is the state that
576     *  the ODE solver resolved in one step. This may not
577     *  be the final state due to error control or event detection.
578     *  @param value The value to be set.
579     *  @see #getTentativeState()
580     */
581    public final void setTentativeState(double value) {
582        _tentativeState = value;
583    }
584
585    ///////////////////////////////////////////////////////////////////
586    ////                         private variables                 ////
587
588    /** Auxiliary variable array. This is used by the solver to
589     *  record intermediate values in a multi-step solver algorithm.
590     */
591    private double[] _auxVariables;
592
593    /** The custom causality interface. */
594    private CausalityInterface _causalityInterface;
595
596    /** Indicator that this is the first firing after initialize(). */
597    private boolean _firstFiring;
598
599    /** The last output produced in the same round. */
600    private double _lastOutput;
601
602    /** The last round this integrator is fired. */
603    private int _lastRound;
604
605    /** The state of the integrator. */
606    private double _state;
607
608    /** Indicate whether the latest step is successful from this
609     *  integrator's point of view.
610     */
611    private boolean _successful = false;
612
613    /** The tentative state. */
614    private double _tentativeState;
615
616    ///////////////////////////////////////////////////////////////////
617    ////                         inner classes                     ////
618
619    /** Custom causality interface that fine tunes the equivalent ports
620     *  and removes the dependence of the state output on the derivative
621     *  input. Ensure that only the impulse and initialState inputs are
622     *  equivalent (the base class will make all ports equivalent because
623     *  the initialState input is a ParameterPort).
624     */
625    private static class IntegratorCausalityInterface
626            extends DefaultCausalityInterface {
627        public IntegratorCausalityInterface(ContinuousIntegrator actor,
628                Dependency defaultDependency) {
629            super(actor, defaultDependency);
630            _actor = actor;
631            _derivativeEquivalents.add(actor.derivative);
632            _otherEquivalents.add(actor.impulse);
633            _otherEquivalents.add(actor.initialState.getPort());
634
635            removeDependency(actor.derivative, actor.state);
636        }
637
638        /** Override the base class to declare that the
639         *  <i>initialState</i> and <i>impulse</i> inputs are
640         *  equivalent, but not the <i>derivative</i> input port.
641         *  This is because to react to inputs at either
642         *  <i>initialState</i> or <i>impulse</i>, we have to know
643         *  what the input at the other is.  But the input at
644         *  <i>derivative</i> does not need to be known.  It will
645         *  affect the future only.
646         *  @param input The port to find the equivalence class of.
647         *  @exception IllegalArgumentException If the argument is not
648         *   contained by the associated actor.
649         */
650        @Override
651        public Collection<IOPort> equivalentPorts(IOPort input) {
652            if (input == _actor.derivative) {
653                return _derivativeEquivalents;
654            }
655            return _otherEquivalents;
656        }
657
658        private ContinuousIntegrator _actor;
659        private LinkedList<IOPort> _derivativeEquivalents = new LinkedList<IOPort>();
660        private LinkedList<IOPort> _otherEquivalents = new LinkedList<IOPort>();
661    }
662}