001/* A publisher that transparently tunnels messages to subscribers and saves its output for testing 002 003 Copyright (c) 2007-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.actor.lib; 029 030import java.util.ArrayList; 031import java.util.List; 032 033import ptolemy.actor.parameters.SharedParameter; 034import ptolemy.data.ArrayToken; 035import ptolemy.data.BooleanToken; 036import ptolemy.data.DoubleToken; 037import ptolemy.data.Token; 038import ptolemy.data.expr.Parameter; 039import ptolemy.data.type.ArrayType; 040import ptolemy.data.type.BaseType; 041import ptolemy.kernel.CompositeEntity; 042import ptolemy.kernel.util.Attribute; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.NameDuplicationException; 045import ptolemy.util.MessageHandler; 046import ptolemy.util.StringUtilities; 047 048/////////////////////////////////////////////////////////////////// 049//// PublisherNonStrictTest 050 051/** 052 This actor publishes input tokens on a named channel and compares 053 the inputs against the value specified by the <i>correctValues</i> parameter. 054 055 <p>This actor combines the {@link ptolemy.actor.lib.Publisher} actor 056 and the {@link ptolemy.actor.lib.NonStrictTest} actor. Thus, it has quite 057 a bit of duplicated code from the NonStrictTest actor. 058 059 <p> Note that in the superclass (Publisher), the input is a multiport, 060 whereas in this class it is a regular non-multiport. To use a multiport 061 input, use {@link ptolemy.actor.lib.PublisherTest} 062 063 @author Christopher Brooks, based on Test, which has Edward A. Lee and Jim Armstrong as authors 064 @version $Id$ 065 @since Ptolemy II 6.1 066 @Pt.ProposedRating Red (cxh) 067 @Pt.AcceptedRating Red (cxh) 068 */ 069public class PublisherNonStrictTest extends Publisher { 070 071 /** Construct a publisher with the specified container and name. 072 * @param container The container actor. 073 * @param name The name of the actor. 074 * @exception IllegalActionException If the actor is not of an acceptable 075 * class for the container. 076 * @exception NameDuplicationException If the name coincides with 077 * an actor already in the container. 078 */ 079 public PublisherNonStrictTest(CompositeEntity container, String name) 080 throws IllegalActionException, NameDuplicationException { 081 super(container, name); 082 083 correctValues = new Parameter(this, "correctValues"); 084 correctValues.setExpression("{true}"); 085 correctValues.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 086 087 tolerance = new Parameter(this, "tolerance"); 088 tolerance.setExpression("1.0"); 089 tolerance.setTypeEquals(BaseType.DOUBLE); 090 091 trainingMode = new SharedParameter(this, "trainingMode", getClass(), 092 "false"); 093 trainingMode.setTypeEquals(BaseType.BOOLEAN); 094 095 // Note that in Publisher, the input is a multiport. 096 input.setMultiport(false); 097 } 098 099 /////////////////////////////////////////////////////////////////// 100 //// ports and parameters //// 101 102 /** A matrix specifying what the input should be. 103 * This defaults to a one-by-one array containing a boolean true. 104 */ 105 public Parameter correctValues; 106 107 /** A double specifying how close the input has to be to the value 108 * given by <i>correctValues</i>. This is a DoubleToken, with default 109 * value 10<sup>-9</sup>. During training, if a correct value is 110 * greater than 10 orders of magnitude than the tolerance, then the 111 * tolerance is changed to a value 9 orders of magnitude less than 112 * the correct value. This helps avoid comparisons beyond the 113 * precision of a Java double. 114 */ 115 public Parameter tolerance; 116 117 /** If true, then do not check inputs, but rather collect them into 118 * the <i>correctValues</i> array. This parameter is a boolean, 119 * and it defaults to false. It is a shared parameter, meaning 120 * that changing it for any one instance in a model will change 121 * it for all instances in the model. 122 */ 123 public SharedParameter trainingMode; 124 125 /////////////////////////////////////////////////////////////////// 126 //// public methods //// 127 128 /** If the attribute is the channel, increment the workspace version 129 * to force cached receiver lists to be updated, and invalidate 130 * the schedule and resolved types of the director, if there is one. 131 * @param attribute The attribute that changed. 132 * @exception IllegalActionException If the change is not acceptable 133 * to this container. 134 */ 135 @Override 136 public void attributeChanged(Attribute attribute) 137 throws IllegalActionException { 138 if (attribute == tolerance) { 139 _tolerance = ((DoubleToken) tolerance.getToken()).doubleValue(); 140 } else { 141 super.attributeChanged(attribute); 142 } 143 } 144 145 /** Read at most one input token from each 146 * input channel and send it to the subscribers, 147 * if any. 148 * @exception IllegalActionException If there is no director. 149 */ 150 @Override 151 public void fire() throws IllegalActionException { 152 // We don't call the super class because we want to 153 // have token production occur in postfire. 154 155 //super.fire(); 156 if (_debugging) { 157 _debug("Called fire()"); 158 } 159 _firedOnce = true; 160 161 //int width = input.getWidth(); 162 163 // // If we are in training mode, read the inputs and add to the 164 // // training data. 165 // boolean training = ((BooleanToken) trainingMode.getToken()) 166 // .booleanValue(); 167 168 // if (training) { 169 // if (_trainingTokens == null) { 170 // _trainingTokens = new ArrayList(); 171 // } 172 173 // if (width == 1) { 174 // if (input.hasToken(0)) { 175 // Token token = input.get(0); 176 // output.send(0, token); 177 // if (token instanceof ArrayToken) { 178 // Token[] innerArrayToken = new Token[1]; 179 // innerArrayToken[0] = token; 180 // _trainingTokens.add(innerArrayToken); 181 // } else { 182 // _trainingTokens.add(token); 183 // } 184 // } 185 // } else { 186 // ArrayList arrayList = new ArrayList(); 187 188 // for (int i = 0; i < width; i++) { 189 // if (input.hasToken(i)) { 190 // Token token = input.get(i); 191 // arrayList.add(token); 192 // output.send(i, token); 193 // } 194 // } 195 196 // _trainingTokens.add(arrayList); 197 // } 198 199 // return; 200 // } 201 202 // for (int i = 0; i < width; i++) { 203 // if (input.hasToken(i)) { 204 // Token token = input.get(i); 205 // output.send(i, token); 206 // } 207 // } 208 } 209 210 /** Override the base class to set the iteration counter to zero. 211 * @exception IllegalActionException If the base class throws it or 212 * if we are running under the test suite and the trainingMode 213 * parameter is set to true. 214 * @see ptolemy.util.MessageHandler#isNonInteractive() 215 */ 216 @Override 217 public void initialize() throws IllegalActionException { 218 super.initialize(); 219 _numberOfInputTokensSeen = 0; 220 _iteration = 0; 221 _trainingTokens = null; 222 _firedOnce = false; 223 _initialized = true; 224 225 if (((BooleanToken) trainingMode.getToken()).booleanValue()) { 226 if (MessageHandler.isNonInteractive()) { 227 throw new IllegalActionException(this, 228 NonStrictTest.TRAINING_MODE_ERROR_MESSAGE); 229 } else { 230 System.err.println("Warning: '" + getFullName() 231 + "' is in training mode, set the trainingMode " 232 + "parameter to false before checking in"); 233 } 234 } 235 } 236 237 /** Override the base class to ensure that links to subscribers 238 * have been updated. 239 * @exception IllegalActionException If there is already a publisher 240 * publishing on the same channel. 241 */ 242 @Override 243 public void preinitialize() throws IllegalActionException { 244 super.preinitialize(); 245 } 246 247 /** Read one token from each input channel and compare against 248 * the value specified in <i>correctValues</i>. If the token count 249 * is larger than the length of <i>correctValues</i>, then return 250 * immediately, indicating that the inputs correctly matched 251 * the values in <i>correctValues</i> and that the test succeeded. 252 * 253 * @exception IllegalActionException If an input does not match 254 * the required value or if the width of the input is not 1. 255 */ 256 @Override 257 public boolean postfire() throws IllegalActionException { 258 boolean superReturnValue = super.postfire(); 259 if (input.getWidth() != 1) { 260 throw new IllegalActionException(this, "Width of input is " 261 + input.getWidth() 262 + " but PublisherNonStrictTest only supports a width of 1."); 263 } 264 265 boolean training = ((BooleanToken) trainingMode.getToken()) 266 .booleanValue(); 267 268 if (training) { 269 if (_trainingTokens == null) { 270 _trainingTokens = new ArrayList(); 271 } 272 273 if (input.hasToken(0)) { 274 Token token = input.get(0); 275 _trainingTokens.add(token); 276 output.send(0, token); 277 } 278 279 return true && superReturnValue; 280 } 281 282 // if (_numberOfInputTokensSeen >= ((ArrayToken) (correctValues.getToken())) 283 // .length()) { 284 // // Consume and discard input values. We are beyond the end 285 // // of the correctValues array. 286 // //if (input.hasToken(0)) { 287 // // input.get(0); 288 289 // //} 290 // return true; 291 // } 292 293 Token referenceToken = ((ArrayToken) correctValues.getToken()) 294 .getElement(_numberOfInputTokensSeen); 295 296 for (int i = 0; i < input.getWidth(); i++) { 297 if (input.hasToken(i)) { 298 Token token = input.get(i); 299 300 //if (input.hasToken(0)) { 301 //Token token = input.get(0); 302 // output.send(0, token); 303 _numberOfInputTokensSeen++; 304 305 // FIXME: If we get a nil token on the input, what should we do? 306 // Here, we require that the referenceToken also be nil. 307 // If the token is an ArrayToken and two corresponding elements 308 // are nil, then we consider them "close". 309 if (token.isCloseTo(referenceToken, _tolerance) 310 .booleanValue() == false 311 && !referenceToken.isNil() 312 && !NonStrictTest._isCloseToIfNilArrayElement(token, 313 referenceToken, _tolerance) 314 && !NonStrictTest._isCloseToIfNilRecordElement(token, 315 referenceToken, _tolerance)) { 316 throw new IllegalActionException(this, 317 "Test fails in iteration " + _iteration + ".\n" 318 + "Value was: " + token 319 + ". Should have been: " + referenceToken); 320 } 321 output.send(i, token); 322 } 323 } 324 325 _iteration++; 326 return true && superReturnValue; 327 } 328 329 /** If <i>trainingMode</i> is <i>true</i>, then take the collected 330 * training tokens and store them as an array in <i>correctValues</i>. 331 * @exception IllegalActionException If initialized() was called 332 * and fire() was not called or if the number of inputs tokens seen 333 * is not greater than or equal to the number of elements in the 334 * <i>correctValues</i> array. 335 */ 336 @Override 337 public void wrapup() throws IllegalActionException { 338 super.wrapup(); 339 340 boolean training = ((BooleanToken) trainingMode.getToken()) 341 .booleanValue(); 342 343 if (!training && _initialized) { 344 if (!_firedOnce) { 345 String errorMessage = "The fire() method of this actor was never called. " 346 + "Usually, this is an error indicating that " 347 + "starvation is occurring."; 348 String fireCompatProperty = "ptolemy.actor.lib.NonStrictTest.fire.compat"; 349 350 if (StringUtilities.getProperty(fireCompatProperty) 351 .length() > 0) { 352 System.err.println("Warning: '" + getFullName() + "' " 353 + errorMessage 354 + "\nThis error is being ignored because " + "the " 355 + fireCompatProperty + "property was set."); 356 } else { 357 throw new IllegalActionException(this, errorMessage); 358 } 359 } 360 361 if (_numberOfInputTokensSeen < ((ArrayToken) correctValues 362 .getToken()).length()) { 363 String errorMessage = "The test produced only " 364 + _numberOfInputTokensSeen 365 + " tokens, yet the correctValues parameter was " 366 + "expecting " 367 + ((ArrayToken) correctValues.getToken()).length() 368 + " tokens."; 369 370 System.err.println( 371 "Warning: '" + getFullName() + "' " + errorMessage); 372 } 373 } 374 375 _initialized = false; 376 377 // Note that wrapup() might get called by the manager before 378 // we have any data... 379 if (training && _trainingTokens != null && _trainingTokens.size() > 0) { 380 Object[] newValues = _trainingTokens.toArray(); 381 382 // NOTE: Support input multiport for the benefit of derived classes. 383 int width = input.getWidth(); 384 Token[] newTokens = new Token[newValues.length]; 385 386 if (width == 1) { 387 for (int i = 0; i < newValues.length; i++) { 388 if (newValues[i] instanceof Token[]) { 389 // Handle width of 1, ArrayToken 390 newTokens[i] = new ArrayToken((Token[]) newValues[i]); 391 for (int j = 0; j < ((Token[]) newValues[i]).length; i++) { 392 _checkRangeOfTolerance(((Token[]) newValues[i])[j]); 393 } 394 } else { 395 newTokens[i] = (Token) newValues[i]; 396 _checkRangeOfTolerance((Token) newValues[i]); 397 } 398 } 399 } else { 400 for (int i = 0; i < newValues.length; i++) { 401 ArrayList entry = (ArrayList) newValues[i]; 402 403 // Entry may be an empty array, in which case, 404 // we cannot do the update, so we return. 405 if (entry.size() < 1) { 406 System.err.println("Warning: '" + getFullName() 407 + "': Unable to train. " 408 + "Zero tokens received in iteration " + i); 409 return; 410 } 411 412 Object[] entries = entry.toArray(); 413 Token[] newEntry = new Token[entries.length]; 414 415 for (int j = 0; j < entries.length; j++) { 416 newEntry[j] = (Token) entries[j]; 417 _checkRangeOfTolerance(newEntry[i]); 418 } 419 420 newTokens[i] = new ArrayToken(newEntry); 421 } 422 } 423 424 correctValues.setToken(new ArrayToken(newTokens)); 425 correctValues.setPersistent(true); 426 } 427 428 if (training 429 && (_trainingTokens == null || _trainingTokens.size() == 0)) { 430 System.err.println("Warning: '" + getFullName() 431 + "' The test produced 0 tokens."); 432 } 433 } 434 435 /////////////////////////////////////////////////////////////////// 436 //// protected variables //// 437 438 /** Set to true if fire() is called once. If fire() is not called at 439 * least once, then throw an exception in wrapup(). 440 */ 441 protected boolean _firedOnce = false; 442 443 /** Set to true when initialized() is called. 444 */ 445 protected boolean _initialized = false; 446 447 /** Count of iterations. */ 448 protected int _iteration; 449 450 /** Number of input tokens seen by this actor in the fire method.*/ 451 protected int _numberOfInputTokensSeen = 0; 452 453 /** A double that is read from the <i>tolerance</i> parameter 454 * specifying how close the input has to be to the value 455 * given by <i>correctValues</i>. This is a double, with default 456 * value 10<sup>-9</sup>. 457 */ 458 protected double _tolerance; 459 460 /** List to store tokens for training mode. */ 461 protected List _trainingTokens; 462 463 /** Check that the difference in exponents between the 464 * input and the tolerance is not greater than the precision 465 * of a Double. If the exponent of newValue parameter is 466 * different by from the exponent of the <i>tolerance</i> 467 * parameter by more than 10, then adjust the <i>tolerance</i> 468 * parameter. This is useful for training large modesl 469 * that have many PublisherTests. 470 * @param newValue The token to be tested. DoubleTokens 471 * are tested, other tokens are ignored. 472 * @exception IllegalActionException If thrown while reading the 473 * <i>tolerance</i> parameter. 474 */ 475 private void _checkRangeOfTolerance(Token newValue) 476 throws IllegalActionException { 477 if (newValue instanceof DoubleToken) { 478 Double value = ((DoubleToken) newValue).doubleValue(); 479 if (value == 0.0) { 480 // The exponent of 0.0 is -Infinity, so skip it 481 return; 482 } 483 double log = Math.log10(((DoubleToken) newValue).doubleValue()); 484 if (Math.abs(log - Math.log10(_tolerance)) > 10) { 485 // Set the tolerance to something closer to the input so that 486 // we don't set it many times. 487 double newTolerance = Math.pow(10, log - 9); 488 if (newTolerance > _tolerance) { 489 // Only set the tolerance if it is greater than the old tolerance. 490 tolerance.setToken(new DoubleToken(newTolerance)); 491 tolerance.setPersistent(true); 492 attributeChanged(tolerance); 493 System.out.println("PublisherNonStrictTest: " 494 + getFullName() + ": exponent of " + newValue 495 + " is " + log 496 + ", which cannot be compared with the previous tolerance." 497 + " The new tolerance is " 498 + tolerance.getExpression() + "."); 499 } 500 } 501 } 502 } 503}