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}