001/* An actor that produces a copy of the most recent input each time 002 the trigger input receives an event. 003 004 Copyright (c) 1998-2018 The Regents of the University of California. 005 All rights reserved. 006 Permission is hereby granted, without written agreement and without 007 license or royalty fees, to use, copy, modify, and distribute this 008 software and its documentation for any purpose, provided that the above 009 copyright notice and the following two paragraphs appear in all copies 010 of this software. 011 012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 016 SUCH DAMAGE. 017 018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 023 ENHANCEMENTS, OR MODIFICATIONS. 024 025 PT_COPYRIGHT_VERSION_2 026 COPYRIGHTENDKEY 027 028 */ 029package ptolemy.domains.de.lib; 030 031import java.util.Set; 032 033import ptolemy.actor.TypedIOPort; 034import ptolemy.actor.lib.Sampler; 035import ptolemy.actor.lib.Transformer; 036import ptolemy.data.Token; 037import ptolemy.data.expr.Parameter; 038import ptolemy.graph.Inequality; 039import ptolemy.kernel.CompositeEntity; 040import ptolemy.kernel.util.Attribute; 041import ptolemy.kernel.util.IllegalActionException; 042import ptolemy.kernel.util.InternalErrorException; 043import ptolemy.kernel.util.NameDuplicationException; 044import ptolemy.kernel.util.StringAttribute; 045import ptolemy.kernel.util.Workspace; 046 047/////////////////////////////////////////////////////////////////// 048//// MostRecent 049 050/** 051 Output the most recent input token when the <i>trigger</i> port 052 receives a token. If no token has been received on the <i>input</i> 053 port when a token is received on the <i>trigger</i> port, then the 054 value of the <i>initialValue</i> parameter is produced. If, however, 055 the <i>initialValue</i> parameter contains no value, then no output is 056 produced. The inputs can be of any token type, but the <i>output</i> 057 port is constrained to be of a type at least that of the <i>input</i> 058 port and the <i>initialValue</i> parameter (if it has a value). 059 060 <p> Both the <i>input</i> port and the <i>output</i> port are multiports. 061 Generally, their widths should match. Otherwise, if the width of the 062 <i>input</i> is greater than the width of the <i>output</i>, the extra 063 input tokens will not appear on any output, although they will be 064 consumed from the input port. If the width of the <i>output</i> is 065 greater than that of the <i>input</i>, then the last few channels of 066 the <i>output</i> will never emit tokens. 067 068 <p> The <i>trigger</i> port is a multiport. Whenever a trigger is received 069 on any channel the actor fires and produces an output. Multiple triggers 070 with the same timestamp are considered as one trigger. 071 072 <p> Note: If the width of the input changes during execution, then the 073 most recent inputs are forgotten, as if the execution of the model 074 were starting over. 075 076 <p> This actor is similar to the Inhibit actor in that it modifies a 077 stream of events based on the presence or absence of events from 078 another input. This actor reacts to the presence of the other event, 079 whereas Inhibit reacts to the absence of it. 080 081 <p> This actor is different from the Register actor in that the input 082 tokens are consumed from the input ports before the outputs are generated. 083 Note that this actor is also different from the 084 {@link Sampler} actor, which produces the <i>current</i> input on the 085 output when a <i>trigger</i> input is present, rather than the most 086 recently received input signal. 087 088 @author Jie Liu, Edward A. Lee, Steve Neuendorffer, Elaine Cheong 089 @version $Id$ 090 @since Ptolemy II 10.0 091 @Pt.ProposedRating Yellow (eal) 092 @Pt.AcceptedRating Yellow (eal) 093 @see ptolemy.domains.de.lib.Inhibit 094 @see ptolemy.domains.de.lib.Register 095 */ 096public class MostRecent extends Transformer { 097 /** Construct an actor with the given container and name. 098 * @param container The container. 099 * @param name The name of this actor. 100 * @exception IllegalActionException If the actor cannot be contained 101 * by the proposed container. 102 * @exception NameDuplicationException If the container already has an 103 * actor with this name. 104 */ 105 public MostRecent(CompositeEntity container, String name) 106 throws NameDuplicationException, IllegalActionException { 107 super(container, name); 108 input.setMultiport(true); 109 output.setMultiport(true); 110 output.setTypeAtLeast(input); 111 trigger = new TypedIOPort(this, "trigger", true, false); 112 trigger.setMultiport(true); 113 114 // Width constraint. Not bidirectional to not break any existing models. 115 output.setWidthEquals(input, false); 116 117 // Leave type undeclared. 118 initialValue = new Parameter(this, "initialValue"); 119 120 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" " 121 + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n" 122 + "<polyline points=\"0,20 0,0\"/>\n" 123 + "<polyline points=\"-30,-0 -10,0 10,-7\"/>\n" 124 + "<polyline points=\"10,0 30,0\"/>\n" + "</svg>\n"); 125 126 StringAttribute cardinality = new StringAttribute(trigger, "_cardinal"); 127 cardinality.setExpression("SOUTH"); 128 } 129 130 /////////////////////////////////////////////////////////////////// 131 //// ports and parameters //// 132 133 /** The trigger port, which has undeclared type. If this port 134 * receives a token, then the most recent token from the 135 * <i>input</i> port will be emitted on the <i>output</i> port. 136 */ 137 public TypedIOPort trigger; 138 139 /** The value that is output when no input has yet been received. 140 * If this is changed during execution, then the output will match 141 * the new value until another input is received. 142 * The type should be the same as the input port. 143 * @see #typeConstraints() 144 */ 145 public Parameter initialValue; 146 147 /////////////////////////////////////////////////////////////////// 148 //// public methods //// 149 150 /** If the <i>initialValue</i> parameter is the argument, then 151 * reset the current output to match the new value. 152 * @param attribute The attribute that changed. 153 * @exception IllegalActionException If the change is not acceptable 154 * to this container (not thrown in this base class). 155 */ 156 @Override 157 public void attributeChanged(Attribute attribute) 158 throws IllegalActionException { 159 160 if (attribute == initialValue) { 161 if (initialValue.getToken() != null) { 162 int width = 1; 163 // Calling input.getWidth() when the model is not being 164 // executed can cause Exceptions because the width cannot 165 // be resolved. This method is called also, for instance, when a 166 // class containing a MostRecent actor is saved. In this case, 167 // the model is not executed and the width is irrelevant. 168 if (_initializeDone) { 169 width = input.getWidth(); 170 } 171 if (width < 1) { 172 width = 1; 173 } 174 _lastInputs = new Token[width]; 175 for (int i = 0; i < width; i++) { 176 _lastInputs[i] = initialValue.getToken(); 177 } 178 179 } else { 180 _lastInputs = null; 181 } 182 } else { 183 super.attributeChanged(attribute); 184 } 185 } 186 187 /** Clone the actor into the specified workspace. This calls the 188 * base class and then sets the ports. 189 * @param workspace The workspace for the new object. 190 * @return A new actor. 191 * @exception CloneNotSupportedException If a derived class has 192 * has an attribute that cannot be cloned. 193 */ 194 @Override 195 public Object clone(Workspace workspace) throws CloneNotSupportedException { 196 MostRecent newObject = (MostRecent) super.clone(workspace); 197 newObject.output.setTypeAtLeast(newObject.input); 198 199 // Width constraint. Not bidirectional to not break any existing models. 200 newObject.output.setWidthEquals(newObject.input, false); 201 202 // This is not strictly needed (since it is always recreated 203 // in preinitialize) but it is safer. 204 newObject._lastInputs = null; 205 206 return newObject; 207 } 208 209 /** Consume all the tokens in the input ports and record them. 210 * If there is a token in the <i>trigger</i> port, emit the most 211 * recent token from the <i>input</i> port. If there has been no 212 * input token, but the <i>initialValue</i> parameter has been 213 * set, emit the value of the <i>initialValue</i> parameter. 214 * Otherwise, emit nothing. 215 * @exception IllegalActionException If there is no director. 216 */ 217 @Override 218 public void fire() throws IllegalActionException { 219 super.fire(); 220 221 int inputWidth = input.getWidth(); 222 int outputWidth = output.getWidth(); 223 int commonWidth = Math.min(inputWidth, outputWidth); 224 225 // If the <i>initialValue</i> parameter was not set, or if the 226 // width of the input has changed. 227 if (_lastInputs == null || _lastInputs.length != inputWidth) { 228 _lastInputs = new Token[inputWidth]; 229 } 230 231 readInputs(commonWidth, inputWidth); 232 233 sendOutputIfTriggered(commonWidth); 234 235 } 236 237 /** If there is no input on the <i>trigger</i> port, return 238 * false, indicating that this actor does not want to fire. 239 * This has the effect of leaving input values in the input 240 * ports, if there are any. 241 * @exception IllegalActionException If there is no director. 242 */ 243 @Override 244 public boolean prefire() throws IllegalActionException { 245 super.prefire(); 246 247 // If the trigger input is not connected, never fire. 248 if (trigger.isOutsideConnected()) { 249 boolean hasToken = false; 250 for (int j = 0; j < trigger.getWidth(); j++) { 251 if (trigger.hasToken(j)) { 252 hasToken = true; 253 break; 254 } 255 } 256 return hasToken; 257 } else { 258 return false; 259 } 260 } 261 262 /** Clear the cached input tokens. 263 * @exception IllegalActionException If there is no director. 264 */ 265 @Override 266 public void initialize() throws IllegalActionException { 267 if (initialValue.getToken() != null) { 268 _lastInputs = new Token[input.getWidth()]; 269 270 for (int i = 0; i < input.getWidth(); i++) { 271 _lastInputs[i] = initialValue.getToken(); 272 } 273 } else { 274 _lastInputs = null; 275 } 276 _initializeDone = true; 277 super.initialize(); 278 } 279 280 /** Wrapup and reset variables. 281 */ 282 @Override 283 public void wrapup() throws IllegalActionException { 284 super.wrapup(); 285 _initializeDone = false; 286 } 287 288 /** Override the method in the base class so that the type 289 * constraint for the <i>initialValue</i> parameter will be set 290 * if it contains a value. 291 * @return a list of Inequality objects. 292 * @see ptolemy.graph.Inequality 293 */ 294 /* public Set<Inequality> typeConstraints() { 295 Set<Inequality> typeConstraints = super.typeConstraints(); 296 297 try { 298 if (initialValue.getToken() != null) { 299 // Set type of initialValue to be equal to input type 300 Inequality ineq = new Inequality(initialValue.getTypeTerm(), 301 input.getTypeTerm()); 302 typeConstraints.add(ineq); 303 ineq = new Inequality(input.getTypeTerm(), 304 initialValue.getTypeTerm()); 305 306 typeConstraints.add(ineq); 307 } 308 } catch (IllegalActionException ex) { 309 // Errors in the initialValue parameter should already 310 // have been caught in getAttribute() method of the base 311 // class. 312 throw new InternalErrorException("Bad initialValue value!"); 313 } 314 315 return typeConstraints; 316 } 317 */ 318 /** 319 * Adds two inequalities to the set returned by the overridden method that 320 * together constrain the input to be equal to the type of the initial 321 * value. 322 */ 323 @Override 324 public Set<Inequality> _containedTypeConstraints() { 325 Set<Inequality> result = super._containedTypeConstraints(); 326 try { 327 // Set type of initialValue to be equal to input type 328 if (initialValue.getToken() != null) { 329 result.add(new Inequality(initialValue.getTypeTerm(), 330 input.getTypeTerm())); 331 result.add(new Inequality(input.getTypeTerm(), 332 initialValue.getTypeTerm())); 333 } 334 } catch (IllegalActionException ex) { 335 // Errors in the initialValue parameter should already 336 // have been caught in getAttribute() method of the base 337 // class. 338 throw new InternalErrorException("Bad initialValue value!"); 339 } 340 341 return result; 342 } 343 344 /////////////////////////////////////////////////////////////////// 345 //// private variables //// 346 347 /** The recorded inputs last seen. */ 348 protected Token[] _lastInputs; 349 350 /** Consume inputs and save them. Discard inputs on input channels 351 * that do not have corresponding output channels. 352 * @param commonWidth The minimum of the input and the output width. 353 * @param inputWidth The width of the input port. 354 * @exception IllegalActionException Thrown if port tokens cannot be accessed. 355 */ 356 protected void readInputs(int commonWidth, int inputWidth) 357 throws IllegalActionException { 358 // Consume the inputs we save. 359 for (int i = 0; i < commonWidth; i++) { 360 while (input.hasNewToken(i)) { 361 _lastInputs[i] = input.get(i); 362 } 363 } 364 365 // Consume the inputs we don't save. 366 for (int i = commonWidth; i < inputWidth; i++) { 367 while (input.hasNewToken(i)) { 368 input.get(i); 369 } 370 } 371 } 372 373 /** Send output tokens if any input on the trigger port has a token. 374 * All trigger tokens are consumed. 375 * @param commonWidth The minimum of the input and the output port width. 376 * @exception IllegalActionException Thrown if the width or the token of 377 * the trigger port cannot be accessed or if tokens cannot be sent on 378 * the output port. 379 */ 380 protected void sendOutputIfTriggered(int commonWidth) 381 throws IllegalActionException { 382 // If the trigger input is not known, return without 383 // doing anything. 384 if (!trigger.isKnown()) { 385 return; 386 } 387 // If we have a trigger... 388 boolean triggered = false; 389 for (int j = 0; j < trigger.getWidth(); j++) { 390 if (trigger.hasToken(j)) { 391 // Consume the trigger token. 392 trigger.get(j); 393 triggered = true; 394 } 395 } 396 397 for (int i = 0; i < commonWidth; i++) { 398 if (triggered) { 399 // Do not output anything if the <i>initialValue</i> 400 // parameter was not set and this actor has not 401 // received any inputs. 402 if (_lastInputs[i] != null) { 403 // Output the most recent token, assuming the 404 // receiver has a FIFO behavior. 405 output.send(i, _lastInputs[i]); 406 } 407 } else { 408 // Indicate that the output is absent so that this 409 // be used in an SR or Continuous feedback loop. 410 output.sendClear(i); 411 } 412 } 413 } 414 415 private boolean _initializeDone = false; 416}