001/* An actor that detects level crossings of its trigger input signal. 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.continuous.ContinuousStepSizeController; 033import ptolemy.data.DoubleToken; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.expr.StringParameter; 036import ptolemy.data.type.BaseType; 037import ptolemy.domains.continuous.kernel.ContinuousDirector; 038import ptolemy.kernel.CompositeEntity; 039import ptolemy.kernel.util.Attribute; 040import ptolemy.kernel.util.IllegalActionException; 041import ptolemy.kernel.util.NameDuplicationException; 042import ptolemy.kernel.util.Workspace; 043 044/////////////////////////////////////////////////////////////////// 045//// LevelCrossingDetector 046 047/** 048 An event detector that converts continuous signals to discrete events when 049 the input <i>trigger</i> signal crosses a threshold specified by the <i>level</i> 050 parameter. The <i>direction</i> parameter 051 can constrain the actor to detect only rising or falling transitions. 052 It has three possible values, "rising", "falling", and "both", where 053 "both" is the default. This actor will produce an output whether the 054 input is continuous or not. That is, if a discontinuity crosses the 055 threshold in the right direction, it produces an output at the time 056 of the discontinuity. If the input is continuous, 057 then the output is generated when the input is 058 within <i>errorTolerance</i> of the level. 059 The value of the output is given by the <i>value</i> parameter, 060 which by default has the value of the <i>level</i> parameter. 061 <p> 062 This actor has a one microstep delay before it will produce an 063 output. That is, when a level crossing is detected, the actor 064 requests a refiring in the next microstep at the current time, 065 and only in that refiring produces the output. 066 This ensures that the output satisfies the piecewise 067 continuity constraint. It is always absent at microstep 0. 068<p> 069 This actor will not produce an event at the time of the first firing 070 unless there is a level crossing discontinuity at that time. 071 072 @author Edward A. Lee, Haiyang Zheng 073 @version $Id$ 074 @since Ptolemy II 6.0 075 @Pt.ProposedRating Yellow (hyzheng) 076 @Pt.AcceptedRating Red (hyzheng) 077 */ 078public class LevelCrossingDetector extends TypedAtomicActor 079 implements ContinuousStepSizeController { 080 /** Construct an actor in the specified container with the specified 081 * name. The name must be unique within the container or an exception 082 * is thrown. The container argument must not be null, or a 083 * NullPointerException will be thrown. 084 * 085 * @param container The subsystem that this actor is lived in 086 * @param name The actor's name 087 * @exception IllegalActionException If the entity cannot be contained 088 * by the proposed container. 089 * @exception NameDuplicationException If name coincides with 090 * an entity already in the container. 091 */ 092 public LevelCrossingDetector(CompositeEntity container, String name) 093 throws IllegalActionException, NameDuplicationException { 094 super(container, name); 095 096 output = new TypedIOPort(this, "output", false, true); 097 098 trigger = new TypedIOPort(this, "trigger", true, false); 099 trigger.setMultiport(false); 100 trigger.setTypeEquals(BaseType.DOUBLE); 101 102 level = new Parameter(this, "level", new DoubleToken(0.0)); 103 level.setTypeEquals(BaseType.DOUBLE); 104 105 value = new Parameter(this, "value"); 106 value.setExpression("level"); 107 108 // By default, this director detects both directions of level crossings. 109 direction = new StringParameter(this, "direction"); 110 direction.setExpression("both"); 111 _detectRisingCrossing = true; 112 _detectFallingCrossing = true; 113 114 direction.addChoice("both"); 115 direction.addChoice("falling"); 116 direction.addChoice("rising"); 117 118 output.setTypeAtLeast(value); 119 120 _errorTolerance = 1e-4; 121 errorTolerance = new Parameter(this, "errorTolerance", 122 new DoubleToken(_errorTolerance)); 123 errorTolerance.setTypeEquals(BaseType.DOUBLE); 124 } 125 126 /////////////////////////////////////////////////////////////////// 127 //// public variables //// 128 129 /** A parameter that can be used to limit the detected level crossings 130 * to rising or falling. There are three choices: "falling", "rising", and 131 * "both". The default value is "both". 132 */ 133 public StringParameter direction; 134 135 /** The error tolerance specifying how close the value of a continuous 136 * input needs to be to the specified level to produce the output event. 137 * Note that this indirectly affects the accuracy of the time of the 138 * output since the output can be produced at any time after the 139 * level crossing occurs while it is still within the specified 140 * error tolerance of the level. This is a double with default 1e-4. 141 */ 142 public Parameter errorTolerance; 143 144 /** The parameter that specifies the level threshold. By default, it 145 * contains a double with value 0.0. Note, a change of this 146 * parameter at run time will not be applied until the next 147 * iteration. 148 */ 149 public Parameter level; 150 151 /** The output value to produce when a level-crossing is detected. 152 * This can be any data type. It defaults to the same value 153 * as the <i>level</i> parameter. 154 */ 155 public Parameter value; 156 157 /** The output port. The type is at least the type of the 158 * <i>value</i> parameter. 159 */ 160 public TypedIOPort output; 161 162 /** The trigger port. This is an input port with type double. 163 */ 164 public TypedIOPort trigger; 165 166 /////////////////////////////////////////////////////////////////// 167 //// public methods //// 168 169 /** Update the attribute if it has been changed. If the attribute 170 * is <i>errorTolerance</i> or <i>level</i>, then update the local cache. 171 * @param attribute The attribute that has changed. 172 * @exception IllegalActionException If the attribute change failed. 173 */ 174 @Override 175 public void attributeChanged(Attribute attribute) 176 throws IllegalActionException { 177 if (attribute == errorTolerance) { 178 double tolerance = ((DoubleToken) errorTolerance.getToken()) 179 .doubleValue(); 180 181 if (tolerance <= 0.0) { 182 throw new IllegalActionException(this, 183 "Error tolerance must be greater than 0."); 184 } 185 186 _errorTolerance = tolerance; 187 } else if (attribute == direction) { 188 String crossingDirections = direction.stringValue(); 189 190 if (crossingDirections.equalsIgnoreCase("falling")) { 191 _detectFallingCrossing = true; 192 _detectRisingCrossing = false; 193 } else if (crossingDirections.equalsIgnoreCase("rising")) { 194 _detectFallingCrossing = false; 195 _detectRisingCrossing = true; 196 } else if (crossingDirections.equalsIgnoreCase("both")) { 197 _detectFallingCrossing = true; 198 _detectRisingCrossing = true; 199 } else { 200 throw new IllegalActionException( 201 "Unknown direction: " + crossingDirections); 202 } 203 } else if (attribute == level) { 204 _level = ((DoubleToken) level.getToken()).doubleValue(); 205 } else { 206 super.attributeChanged(attribute); 207 } 208 } 209 210 /** Clone the actor into the specified workspace. 211 * @param workspace The workspace for the new object. 212 * @return A new actor. 213 * @exception CloneNotSupportedException If a derived class contains 214 * an attribute that cannot be cloned. 215 */ 216 @Override 217 public Object clone(Workspace workspace) throws CloneNotSupportedException { 218 LevelCrossingDetector newObject = (LevelCrossingDetector) super.clone( 219 workspace); 220 221 // Set the type constraints. 222 newObject.output.setTypeAtLeast(newObject.value); 223 return newObject; 224 } 225 226 /** Declare that the output does not depend on the input in a firing. 227 * @exception IllegalActionException If the causality interface 228 * cannot be computed. 229 * @see #getCausalityInterface() 230 */ 231 @Override 232 public void declareDelayDependency() throws IllegalActionException { 233 _declareDelayDependency(trigger, output, 0.0); 234 } 235 236 /** Detect whether the current input compared to the input 237 * on the last iteration indicates that a level crossing in the 238 * appropriate direction has occurred, if the time is within 239 * <i>errorTolerance</i> of the time at which the crossing occurs. 240 * If there is such a level crossing, then postfire will request 241 * a refiring at the current time, and the next invocation of fire() 242 * will produce the output event. 243 * @exception IllegalActionException If it cannot get a token from the trigger 244 * port or cannot send a token through the output port. 245 */ 246 @Override 247 public void fire() throws IllegalActionException { 248 ContinuousDirector dir = (ContinuousDirector) getDirector(); 249 double currentStepSize = dir.getCurrentStepSize(); 250 int microstep = dir.getIndex(); 251 _postponedOutputProduced = false; 252 253 if (_debugging) { 254 _debug("Called fire() at time " + dir.getModelTime() 255 + " with microstep " + microstep + " and step size " 256 + currentStepSize); 257 } 258 259 // If there is a postponed output, then produce it. 260 // Need to use <= rather than == here because a modal 261 // model may have been suspended when the microstep matched. 262 if (_postponed > 0 && _postponed <= microstep) { 263 if (_debugging) { 264 _debug("-- Produce postponed output."); 265 } 266 output.send(0, value.getToken()); 267 _postponedOutputProduced = true; 268 } else { 269 // There is no postponed output, so send clear. 270 if (_debugging) { 271 _debug("-- Output is absent."); 272 } 273 output.sendClear(0); 274 } 275 276 // If the trigger input is available, record it. 277 if (trigger.getWidth() > 0 && trigger.isKnown(0) 278 && trigger.hasToken(0)) { 279 _thisTrigger = ((DoubleToken) trigger.get(0)).doubleValue(); 280 if (_debugging) { 281 _debug("-- Consumed a trigger input: " + _thisTrigger); 282 _debug("-- Last trigger is: " + _lastTrigger); 283 } 284 285 // If first firing, do not look for a level crossing. 286 if (_lastTrigger == Double.NEGATIVE_INFINITY) { 287 return; 288 } 289 290 boolean inputIsIncreasing = _thisTrigger > _lastTrigger; 291 boolean inputIsDecreasing = _thisTrigger < _lastTrigger; 292 293 // If a crossing has occurred, and either the current step 294 // size is zero or the current input is within error tolerance 295 // of the level, then request a refiring. 296 // Check whether _lastTrigger and _thisTrigger are on opposite sides 297 // of the level. 298 // NOTE: The code below should not set _eventMissed = false because 299 // an event may be missed during any stage of speculative execution. 300 // This should be set to false only in postfire. 301 if ((_lastTrigger - _level) * (_thisTrigger - _level) < 0.0 302 || _thisTrigger == _level) { 303 // Crossing has occurred. Check whether the direction is right. 304 // Note that we do not produce an output is the input is neither 305 // increasing nor decreasing. Presumably, we already produced 306 // an output in that case. 307 if (_detectFallingCrossing && inputIsDecreasing 308 || _detectRisingCrossing && inputIsIncreasing) { 309 // If the step size is not 0.0, and the 310 // current input is not close enough, then we 311 // have missed an event and the step size will need 312 // to be adjusted. 313 if (currentStepSize != 0.0 && Math 314 .abs(_thisTrigger - _level) >= _errorTolerance) { 315 // Step size is nonzero and the current input is not 316 // close enough. We have missed an event. 317 if (_debugging) { 318 _debug("-- Missed an event. Step size will be adjusted."); 319 } 320 _eventMissed = true; 321 } else { 322 // Request a refiring. 323 _postponed = microstep + 1; 324 } 325 } 326 } 327 } 328 } 329 330 /** Initialize the execution. 331 * @exception IllegalActionException If thrown by the super class. 332 */ 333 @Override 334 public void initialize() throws IllegalActionException { 335 super.initialize(); 336 _eventMissed = false; 337 _level = ((DoubleToken) level.getToken()).doubleValue(); 338 _lastTrigger = Double.NEGATIVE_INFINITY; 339 _thisTrigger = _lastTrigger; 340 _postponed = 0; 341 _postponedOutputProduced = false; 342 } 343 344 /** Return false if with the current step size we miss a level crossing. 345 * @return False if the step size needs to be refined. 346 */ 347 @Override 348 public boolean isStepSizeAccurate() { 349 if (_debugging) { 350 _debug("Step size is accurate: " + !_eventMissed); 351 } 352 return !_eventMissed; 353 } 354 355 /** Return false. This actor can produce some outputs even the 356 * inputs are unknown. This actor is usable for breaking feedback 357 * loops. 358 * @return False. 359 */ 360 @Override 361 public boolean isStrict() { 362 return false; 363 } 364 365 /** Prepare for the next iteration, by making the current trigger 366 * token to be the history trigger token. 367 * @return True always. 368 * @exception IllegalActionException If thrown by the super class. 369 */ 370 @Override 371 public boolean postfire() throws IllegalActionException { 372 if (_debugging) { 373 _debug("Called postfire()."); 374 } 375 376 _lastTrigger = _thisTrigger; 377 _eventMissed = false; 378 if (_postponed > 0) { 379 if (_debugging) { 380 _debug("Requesting refiring at the current time."); 381 } 382 getDirector().fireAtCurrentTime(this); 383 } 384 if (_postponedOutputProduced) { 385 _postponedOutputProduced = false; 386 // There might be yet another postponed output requested. 387 // If the current microstep matches _postponed, then there 388 // there is not, and we can reset _postponed. 389 ContinuousDirector dir = (ContinuousDirector) getDirector(); 390 int microstep = dir.getIndex(); 391 if (microstep >= _postponed) { 392 _postponed = 0; 393 } 394 } 395 return super.postfire(); 396 } 397 398 /** Make sure the actor runs with a ContinuousDirector. 399 * @exception IllegalActionException If the director is not 400 * a ContinuousDirector or the parent class throws it. 401 */ 402 @Override 403 public void preinitialize() throws IllegalActionException { 404 if (!(getDirector() instanceof ContinuousDirector)) { 405 throw new IllegalActionException("LevelCrossingDetector can only" 406 + " be used inside Continuous domain."); 407 } 408 super.preinitialize(); 409 } 410 411 /** Return the refined step size if there is a missed event, 412 * otherwise return the current step size. 413 * @return The refined step size. 414 */ 415 @Override 416 public double refinedStepSize() { 417 ContinuousDirector dir = (ContinuousDirector) getDirector(); 418 double refinedStep = dir.getCurrentStepSize(); 419 420 if (_eventMissed) { 421 // The refined step size is a linear interpolation. 422 // NOTE: we always to get a little overshoot to make sure the 423 // level crossing happens. The little overshoot chosen here 424 // is half of the error tolerance. 425 refinedStep = (Math.abs(_lastTrigger - _level) 426 + _errorTolerance / 2) * dir.getCurrentStepSize() 427 / Math.abs(_thisTrigger - _lastTrigger); 428 429 if (_debugging) { 430 _debug(getFullName() + "-- Event Missed: refine step to " 431 + refinedStep); 432 } 433 // Reset this because the iteration will be repeated with a new step size. 434 // The new iteration may not miss the event. 435 _eventMissed = false; 436 } 437 return refinedStep; 438 } 439 440 /** Return the maximum Double value. This actor does not suggest 441 * or constrain the step size for the next iteration. 442 * @return java.Double.MAX_VALUE. 443 */ 444 @Override 445 public double suggestedStepSize() { 446 return java.lang.Double.MAX_VALUE; 447 } 448 449 /////////////////////////////////////////////////////////////////// 450 //// protected variables //// 451 452 /** The level threshold this actor detects. */ 453 protected double _level; 454 455 /////////////////////////////////////////////////////////////////// 456 //// private variables //// 457 458 // Flag indicating whether this actor detects the level crossing 459 // when the input value is rising. 460 private boolean _detectRisingCrossing; 461 462 // Flag indicating whether this actor detects the level crossing 463 // when the input value is falling. 464 private boolean _detectFallingCrossing; 465 466 // Cache of the value of errorTolerance. 467 private double _errorTolerance; 468 469 // Flag indicating a missed event. 470 private boolean _eventMissed = false; 471 472 // Last trigger input. 473 private double _lastTrigger; 474 475 // Indicator that the output is postponed to the specified microstep. 476 private int _postponed; 477 478 // Indicator that the postponed output was produced. 479 private boolean _postponedOutputProduced; 480 481 // This trigger input. 482 private double _thisTrigger; 483}