001/* The derivative in the continuous domain.
002
003 Copyright (c) 1998-2014 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.lib;
029
030import ptolemy.actor.TypedAtomicActor;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.actor.util.Time;
033import ptolemy.data.DoubleToken;
034import ptolemy.data.type.BaseType;
035import ptolemy.domains.continuous.kernel.ContinuousDirector;
036import ptolemy.kernel.CompositeEntity;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039import ptolemy.kernel.util.StringAttribute;
040
041///////////////////////////////////////////////////////////////////
042//// Derivative
043
044/**
045 A crude approximation to a derivative in the continuous domain.
046 In continuous-time modeling, one should generally avoid taking derivatives
047 directly. It is better to use an {@link Integrator} actor in a feedback loop.
048 The input to the Integrator is the derivative of its output.
049 The reason for avoiding taking derivatives directly
050 is that small amounts of noise on the input of
051 a derivative actor result in large output fluctuations.
052 Since continuous-time simulation involves choosing step sizes,
053 the choice of step size will strongly affect the resulting
054 output of the derivative. Derivatives tend to be very noisy,
055 with considerable fluctuations in value. Moreover, if the
056 input to this actor has discontinuities, the output will not
057 be piecewise continuous, and at the discontinuity, the results
058 could be difficult to control. If an Integrator is downstream,
059 then the solver will be forced to use its smallest step size.
060 <p>
061 That said, if you have read this far, you are probably determined
062 to compute a derivative. Hence, we provide this actor, which performs
063 a simple operation and provides a simple (partial) guarantee. Specifically,
064 a correctly connected Derivative followed by an Integrator is (almost)
065 an identity function. And an Integrator followed by a Derivative is
066 also (almost) an identity function. The reason for the "almost" is
067 that very first <i>derivative</i> output of the Derivative actor
068 is always zero. Determining a derivative without any past history
069 requires seeing the future. Although in principle it might be
070 possible for this actor to collaborate with the solver to
071 speculatively execute into the future to get the derivative,
072 we have not done that here.
073 <p>
074 Upon firing, this actor produces an output on the <i>derivative</i>
075 port, and may also produce an output on the <i>impulse</i> port.
076 The <i>derivative</i> output value is the difference between the
077 input at the current time and the previous input divided by the
078 time between these inputs, unless that time is zero. If the time
079 between this input and the previous one is zero, and the value
080 of the previous input and the current one is non-zero, then this
081 actor will be produce the value difference on the <i>impulse</i>
082 output and will produce whatever
083 it previously produced on the <i>derivative</i> output.
084 <p>
085 On the very first firing after being initialized,
086 this actor always produces zero
087 on the <i>derivative</i> output. If the input is
088 non-zero, then it will produce the value of the input on
089 the <i>impulse</i> output. This ensures that if the
090 <i>impulse</i> output is connected to the <i>impulse</i>
091 input of a downstream Integrator, that the Integrator will
092 be correctly initialized.
093 <p>
094 The <i>impulse</i> output should be interpreted as a Dirac
095 delta function. It is a discrete output. If it is connected to
096 the <i>impulse</i> input of the Integrator actor, and the
097 <i>derivative</i> output is connected to the <i>derivative</i>
098 input of the Integrator actor, then the cascade of two actors
099 will be an identity function for all input signals.
100 <p>
101 If upon any firing the input is absent, then both outputs
102 will be absent, and the actor will reinitialize. Hence, on
103 the next firing where the input is present, this actor will
104 behave as if that firing is a first firing.
105 <p>
106 Note that this actor exercises no control at all over step sizes.
107 It simply works with whatever step sizes are provided. Thus,
108 it is mathematically questionable to use it in any model except
109 where its input comes from an Integrator or its outputs go
110 to an Integrator. The Integrator actor will exercise control
111 over step sizes.
112
113 @author Edward A. Lee and Janette Cardoso
114 @version $Id$
115 @since Ptolemy II 10.0
116 @Pt.ProposedRating Yellow (eal)
117 @Pt.AcceptedRating Red (eal)
118 */
119public class Derivative extends TypedAtomicActor {
120
121    /** Construct a derivative actor.
122     *  @param container The container.
123     *  @param name The name.
124     *  @exception NameDuplicationException If the name is used by
125     *  another actor in the container.
126     *  @exception IllegalActionException If ports can not be created, or
127     *   thrown by the super class.
128     *  @see ptolemy.domains.continuous.kernel.ContinuousIntegrator
129     */
130    public Derivative(CompositeEntity container, String name)
131            throws NameDuplicationException, IllegalActionException {
132        super(container, name);
133
134        input = new TypedIOPort(this, "input", true, false);
135        input.setTypeEquals(BaseType.DOUBLE);
136
137        derivative = new TypedIOPort(this, "derivative", false, true);
138        derivative.setTypeEquals(BaseType.DOUBLE);
139
140        impulse = new TypedIOPort(this, "impulse", false, true);
141        impulse.setTypeEquals(BaseType.DOUBLE);
142        StringAttribute cardinality = new StringAttribute(impulse, "_cardinal");
143        cardinality.setExpression("SOUTH");
144
145        // icon
146        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" "
147                + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n"
148                + "<text x=\"-25\" y=\"0\" " + "style=\"font-size:14\">\n"
149                + "d/dt \n" + "</text>\n" + "style=\"fill:blue\"/>\n"
150                + "</svg>\n");
151    }
152
153    ///////////////////////////////////////////////////////////////////
154    ////                     ports and parameters                  ////
155
156    /** The derivative output port. This port has type double.
157     */
158    public TypedIOPort derivative;
159
160    /** The impulse output port. This port has type double.
161     */
162    public TypedIOPort impulse;
163
164    /** The input port. This port has type double.
165     */
166    public TypedIOPort input;
167
168    ///////////////////////////////////////////////////////////////////
169    ////                         public methods                    ////
170
171    /** Produce outputs as specified in the class comment.
172     *  @exception IllegalActionException If the superclass throws it.
173     */
174    @Override
175    public void fire() throws IllegalActionException {
176        super.fire();
177
178        if (!input.isKnown(0)) {
179            if (_debugging) {
180                _debug("fire: Input is not known.");
181            }
182            return;
183        }
184
185        if (!input.hasToken(0)) {
186            if (_debugging) {
187                _debug("fire: Input has no token.");
188            }
189            return;
190        }
191        // Have to use the time from the port, not the director,
192        // because if this domain is embedded, then the
193        // director returns global time.
194        Time currentTime = input.getModelTime(0);
195        DoubleToken currentInput = (DoubleToken) input.get(0);
196
197        if (_debugging) {
198            _debug("fire at time: " + currentTime + ", microstep "
199                    + ((ContinuousDirector) getDirector()).getIndex()
200                    + "\n-- current input: " + currentInput
201                    + "\n-- _previousOutput: " + _previousOutput
202                    + "\n-- _previousInput: " + _previousInput
203                    + "\n-- _previousTime: " + _previousTime);
204        }
205        if (_previousTime == null) {
206            // First firing.
207            derivative.send(0, DoubleToken.ZERO);
208
209            if (_debugging) {
210                _debug("fire: first firing. Sending zero.");
211            }
212
213            if (currentInput.doubleValue() != 0.0) {
214                impulse.send(0, currentInput);
215                if (_debugging) {
216                    _debug("fire: Initial value is not zero. Sending impulse: "
217                            + currentInput);
218                }
219            }
220        } else {
221            // Not the first firing.
222            if (currentTime.equals(_previousTime)) {
223                // No time has elapsed.
224                derivative.send(0, _previousOutput);
225                if (_debugging) {
226                    _debug("fire: No time has elapsed. Sending previous output: "
227                            + _previousOutput);
228                }
229                if (_previousInput != currentInput.doubleValue()) {
230                    impulse.send(0, new DoubleToken(
231                            currentInput.doubleValue() - _previousInput));
232                    if (_debugging) {
233                        _debug("fire: Discontinuity. Sending impulse: "
234                                + (currentInput.doubleValue()
235                                        - _previousInput));
236                    }
237                }
238            } else {
239                // Time has elapsed.
240                double timeGap = currentTime.subtract(_previousTime)
241                        .getDoubleValue();
242                double derivativeValue = (currentInput.doubleValue()
243                        - _previousInput) / timeGap;
244                derivative.send(0, new DoubleToken(derivativeValue));
245                if (_debugging) {
246                    _debug("fire: Time has elapsed. Sending output: "
247                            + derivativeValue);
248                }
249            }
250        }
251    }
252
253    /** Ensure that the next invocation of the fire() method is treated
254     *  as a first firing.
255     *  @exception IllegalActionException If the superclass throws it.
256     */
257    @Override
258    public void initialize() throws IllegalActionException {
259        super.initialize();
260        _previousTime = null;
261        _previousOutput = null;
262        _previousInput = 0.0;
263    }
264
265    /** Record the current input and time.
266     *  @exception IllegalActionException If the superclass throws it.
267     *  @return Whatever the superclass returns (true).
268     */
269    @Override
270    public boolean postfire() throws IllegalActionException {
271        boolean result = super.postfire();
272
273        if (!input.hasToken(0)) {
274            initialize();
275            if (_debugging) {
276                _debug("Postfire: Input has no token. Initializing.");
277            }
278            return result;
279        }
280        // Have to completely recalculate the current output
281        // because the last invocation of fire() is not
282        // necessarily at the current time.
283        // Have to use the time from the port, not the director,
284        // because if this domain is embedded, then the
285        // director returns global time.
286        Time currentTime = input.getModelTime(0);
287        DoubleToken currentInput = (DoubleToken) input.get(0);
288        if (_previousTime == null) {
289            // First firing.
290            _previousOutput = DoubleToken.ZERO;
291            if (_debugging) {
292                _debug("First postfire");
293            }
294        } else {
295            // Not the first firing.
296            if (!currentTime.equals(_previousTime)) {
297                // Time has elapsed.
298                double timeGap = currentTime.subtract(_previousTime)
299                        .getDoubleValue();
300                double derivativeValue = (currentInput.doubleValue()
301                        - _previousInput) / timeGap;
302                _previousOutput = new DoubleToken(derivativeValue);
303            }
304        }
305        _previousInput = ((DoubleToken) input.get(0)).doubleValue();
306        _previousTime = getDirector().getModelTime();
307        if (_debugging) {
308            _debug("postfire at time: " + currentTime + ", microstep "
309                    + ((ContinuousDirector) getDirector()).getIndex()
310                    + "\n-- current input: " + currentInput
311                    + "\n-- _previousOutput updated to: " + _previousOutput
312                    + "\n-- _previousInput updated to: " + _previousInput
313                    + "\n-- _previousTime updated to: " + _previousTime + "\n");
314        }
315        return result;
316    }
317
318    ///////////////////////////////////////////////////////////////////
319    ////                         private variables                 ////
320
321    /** The time of the previous input, or null on the first firing. */
322    private Time _previousTime;
323
324    /** The value of the previous output, or null on the first firing. */
325    private DoubleToken _previousOutput;
326
327    /** The value of the previous input, or 0.0 on the first firing. */
328    private double _previousInput;
329}