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 +--------->| Integrator |---------+-----> 072 | | | | 073 | +----^-----^----+ | 074 | | 075 | |---------| | 076 +-------------| f(x, t) |<-----------+ 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}