001/* Check the input streams against a parameter value, ignoring absent values. 002 003 Copyright (c) 1998-2018 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.HashSet; 032import java.util.Iterator; 033import java.util.List; 034import java.util.Set; 035 036import ptolemy.actor.parameters.SharedParameter; 037import ptolemy.data.ArrayToken; 038import ptolemy.data.BooleanToken; 039import ptolemy.data.DoubleToken; 040import ptolemy.data.OrderedRecordToken; 041import ptolemy.data.RecordToken; 042import ptolemy.data.Token; 043import ptolemy.data.expr.Parameter; 044import ptolemy.data.type.ArrayType; 045import ptolemy.data.type.BaseType; 046import ptolemy.data.type.TypeConstant; 047import ptolemy.graph.Inequality; 048import ptolemy.kernel.CompositeEntity; 049import ptolemy.kernel.util.Attribute; 050import ptolemy.kernel.util.IllegalActionException; 051import ptolemy.kernel.util.NameDuplicationException; 052import ptolemy.kernel.util.Workspace; 053import ptolemy.util.MessageHandler; 054import ptolemy.util.StringUtilities; 055 056/////////////////////////////////////////////////////////////////// 057//// NonStrictTest 058 059/** 060 061 <p>This actor compares the inputs against the value specified by the 062 <i>correctValues</i> parameter. That parameter is an ArrayToken, 063 where each element of the array is of the same type as the input. 064 On each firing where the input is present, the value of the input 065 is compared against the next token in the <i>correctValues</i> 066 parameter. If it matches, the firing succeeds. If it doesn't 067 match, then an exception is thrown. After matching each of 068 the value in the <i>correctValues</i> parameter, subsequent iterations 069 always succeed, so the actor can be used as a "power-up" test for a model, 070 checking the first few iterations against some known results.</p> 071 <p> 072 Unlike the Test actor, NonStrictTest does not support a multiport 073 input, only single port inputs are supported. This also differs 074 from Test in that it ignores absent inputs, and it checks the inputs 075 in the postfire() method rather than the fire() method.</p> 076 <p> 077 This actor accepts any type of data on its input port, therefore it 078 doesn't declare a type, but lets the type resolution algorithm find 079 the least fixed point. If backward type inference is enabled, and 080 no input type has been declared, the input is constrained to be 081 equal to <code>BaseType.GENERAL</code>. This will result in upstream 082 ports resolving to the most general type rather than the most specific. 083 </p><p> 084 If the input is a DoubleToken or ComplexToken, then the comparison 085 passes if the value is close to what it should be, within the 086 specified <i>tolerance</i> (which defaults to 10<sup>-9</sup>). 087 During training, if a correct value is 088 greater than 10 orders of magnitude than the tolerance, then the 089 tolerance is changed to a value 9 orders of magnitude less than 090 the correct value. This helps avoid comparisons beyond the 091 precision of a Java double.</p> 092 <p> 093 If the parameter <i>trainingMode</i> is <i>true</i>, then instead 094 of performing the test, this actor collects the inputs into the 095 <i>correctValues</i> parameter. Thus, to use this actor, you can 096 place it in a model, set <i>trainingMode</i> to <i>true</i> to 097 collect the reference data, then set <i>trainingMode</i> to 098 <i>false</i>. Any subsequent run of the actor will throw an 099 exception if the input data does not match the training data. 100 The value of the reference token is set in the wrapup() method. 101 The <i>trainingMode</i> parameter is a shared parameter, 102 meaning that if you change it for any one instance of this 103 actor in the model, then it will be changed for all instances.</p> 104 105 @see Test 106 @author Paul Whitaker, Christopher Hylands, Edward A. Lee 107 @version $Id$ 108 @since Ptolemy II 2.0 109 @Pt.ProposedRating Yellow (cxh) 110 @Pt.AcceptedRating Yellow (cxh) 111 */ 112public class NonStrictTest extends Sink { 113 // The Test actor could be extended so that Strictness was a parameter, 114 // but that would require some slightly tricky code to handle 115 // multiports in a non-strict fashion. The problem is that if 116 // we have more than one input channel, and we want to handle 117 // non-strict inputs, then we need to keep track of number of 118 // tokens we have seen on each channel. Also, this actor does 119 // not read inputs until postfire(), which is too late to produce 120 // an output, as done by Test. 121 122 /** Construct an actor with an input multiport. 123 * @param container The container. 124 * @param name The name of this actor. 125 * @exception IllegalActionException If the entity cannot be contained 126 * by the proposed container. 127 * @exception NameDuplicationException If the container already has an 128 * actor with this name. 129 */ 130 public NonStrictTest(CompositeEntity container, String name) 131 throws NameDuplicationException, IllegalActionException { 132 super(container, name); 133 134 correctValues = new Parameter(this, "correctValues"); 135 correctValues.setExpression("{true}"); 136 correctValues.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 137 138 tolerance = new Parameter(this, "tolerance"); 139 tolerance.setExpression("1.0E-9"); 140 tolerance.setTypeEquals(BaseType.DOUBLE); 141 142 requireAllCorrectValues = new SharedParameter(this, 143 "requireAllCorrectValues", getClass(), "true"); 144 requireAllCorrectValues.setTypeEquals(BaseType.BOOLEAN); 145 146 requireOrderedValues = new Parameter(this, "requireOrderedValues"); 147 requireOrderedValues.setExpression("true"); 148 requireOrderedValues.setTypeEquals(BaseType.BOOLEAN); 149 150 trainingMode = new SharedParameter(this, "trainingMode", getClass(), 151 "false"); 152 trainingMode.setTypeEquals(BaseType.BOOLEAN); 153 154 input.setMultiport(false); 155 } 156 157 /////////////////////////////////////////////////////////////////// 158 //// ports and parameters //// 159 160 /** A matrix specifying what the input should be. 161 * This defaults to a one-by-one array containing a boolean true. 162 */ 163 public Parameter correctValues; 164 165 /** A double specifying how close the input has to be to the value 166 * given by <i>correctValues</i>. This is a DoubleToken, with default 167 * value 10<sup>-9</sup>. During training, if a correct value is 168 * greater than 10 orders of magnitude than the tolerance, then the 169 * tolerance is changed to a value 9 orders of magnitude less than 170 * the correct value. This helps avoid comparisons beyond the 171 * precision of a Java double. 172 */ 173 public Parameter tolerance; 174 175 /** If true, and the number of tokens seen in wrapup() is not 176 * equal to or greater than the number of elements in the 177 * <i>correctValues</i> array, then throw an exception. The 178 * default value is true. This parameter is a shared parameter, 179 * meaning that changing it for any one instance in a model will 180 * change it for all instances in the model. 181 */ 182 public Parameter requireAllCorrectValues; 183 184 /** If true, then require that inputs appear in the order 185 * recorded in the correctValues parameter. If false, then 186 * the inputs can appear in any order. The default value 187 * is true. 188 */ 189 public Parameter requireOrderedValues; 190 191 /** If true, then do not check inputs, but rather collect them into 192 * the <i>correctValues</i> array. This parameter is a boolean, 193 * and it defaults to false. It is a shared parameter, meaning 194 * that changing it for any one instance in a model will change 195 * it for all instances in the model. 196 */ 197 public SharedParameter trainingMode; 198 199 /////////////////////////////////////////////////////////////////// 200 //// public methods //// 201 202 /** Override the base class to set type constraints. 203 * @param workspace The workspace for the new object. 204 * @return A new instance of ArrayAverage. 205 * @exception CloneNotSupportedException If a derived class contains 206 * an attribute that cannot be cloned. 207 */ 208 @Override 209 public Object clone(Workspace workspace) throws CloneNotSupportedException { 210 NonStrictTest newObject = (NonStrictTest) super.clone(workspace); 211 newObject.correctValues.setTypeAtLeast(ArrayType.ARRAY_BOTTOM); 212 return newObject; 213 } 214 215 /** If the attribute being changed is <i>tolerance</i>, then check 216 * that it is increasing and nonnegative. 217 * @param attribute The attribute that changed. 218 * @exception IllegalActionException If the indexes vector is not 219 * increasing and nonnegative, or the indexes is not a row vector. 220 */ 221 @Override 222 public void attributeChanged(Attribute attribute) 223 throws IllegalActionException { 224 if (attribute == tolerance) { 225 _tolerance = ((DoubleToken) tolerance.getToken()).doubleValue(); 226 } else { 227 super.attributeChanged(attribute); 228 } 229 } 230 231 /** Call super.fire() and set _firedOnce to true. 232 * Derived classes should either call this fire() method 233 * or else set _firedOnce to true. 234 * @see #_firedOnce 235 * @exception IllegalActionException If thrown by the baseclass. 236 */ 237 @Override 238 public void fire() throws IllegalActionException { 239 super.fire(); 240 _firedOnce = true; 241 } 242 243 /** Override the base class to set the iteration counter to zero. 244 * @exception IllegalActionException If the base class throws it or 245 * if we are running under the test suite and the trainingMode 246 * parameter is set to true. 247 * @see ptolemy.util.MessageHandler#isNonInteractive() 248 */ 249 @Override 250 public void initialize() throws IllegalActionException { 251 super.initialize(); 252 _iteration = 0; 253 _initialized = true; 254 _firedOnce = false; 255 _numberOfInputTokensSeen = 0; 256 _matchedValues = new boolean[((ArrayToken) correctValues.getToken()) 257 .length()]; 258 _trainingTokens = null; 259 260 if (((BooleanToken) trainingMode.getToken()).booleanValue()) { 261 if (MessageHandler.isNonInteractive()) { 262 throw new IllegalActionException(this, 263 TRAINING_MODE_ERROR_MESSAGE); 264 } else { 265 System.err.println("Warning: '" + getFullName() 266 + "' is in training mode, set the trainingMode " 267 + "parameter to false before checking in"); 268 } 269 } 270 } 271 272 /** Read one token from each input channel and compare against 273 * the value specified in <i>correctValues</i>. If the token count 274 * is larger than the length of <i>correctValues</i>, then return 275 * immediately, indicating that the inputs correctly matched 276 * the values in <i>correctValues</i> and that the test succeeded. 277 * 278 * @exception IllegalActionException If an input does not match 279 * the required value or if the width of the input is not 1. 280 */ 281 @Override 282 public boolean postfire() throws IllegalActionException { 283 if (!super.postfire()) { 284 return false; 285 } 286 if (input.getWidth() != 1) { 287 throw new IllegalActionException(this, 288 "Width of input is " + input.getWidth() 289 + " but NonStrictTest only supports a width of 1."); 290 } 291 292 boolean training = ((BooleanToken) trainingMode.getToken()) 293 .booleanValue(); 294 295 if (training) { 296 if (_trainingTokens == null) { 297 _trainingTokens = new ArrayList(); 298 } 299 300 if (input.hasToken(0)) { 301 _trainingTokens.add(input.get(0)); 302 } 303 304 return true; 305 } 306 307 if (_numberOfInputTokensSeen >= ((ArrayToken) correctValues.getToken()) 308 .length()) { 309 // Consume and discard input values. We are beyond the end 310 // of the correctValues array. 311 if (input.hasToken(0)) { 312 input.get(0); 313 } 314 315 return true; 316 } 317 318 if (input.hasToken(0)) { 319 Token token = input.get(0); 320 Token referenceToken = null; 321 322 if (((BooleanToken) requireOrderedValues.getToken()) 323 .booleanValue()) { 324 referenceToken = ((ArrayToken) correctValues.getToken()) 325 .getElement(_numberOfInputTokensSeen); 326 327 if (!_isClose(token, referenceToken, _tolerance)) { 328 throw new IllegalActionException(this, 329 "Test fails in iteration " + _iteration + ".\n" 330 + "Value was: " + token 331 + ". Should have been: " + referenceToken); 332 } 333 } else { 334 // requireCorrectOrder is false. 335 boolean sawMatch = false; 336 for (int i = 0; i < ((ArrayToken) correctValues.getToken()) 337 .length(); i++) { 338 if (!_matchedValues[i]) { 339 referenceToken = ((ArrayToken) correctValues.getToken()) 340 .getElement(i); 341 342 if (_isClose(token, referenceToken, _tolerance)) { 343 _matchedValues[i] = true; 344 sawMatch = true; 345 break; 346 } 347 } 348 } 349 if (!sawMatch) { 350 throw new IllegalActionException(this, 351 "Test fails in iteration " + _iteration + ".\n" 352 + "Value was: " + token 353 + ". No matches were found in any of " 354 + "the as yet unmatched correct values."); 355 } 356 } 357 _numberOfInputTokensSeen++; 358 } 359 360 _iteration++; 361 return true; 362 } 363 364 /** If <i>trainingMode</i> is <i>true</i>, then take the collected 365 * training tokens and store them as an array in <i>correctValues</i>. 366 * @exception IllegalActionException If initialized() was called 367 * and fire() was not called or if the number of inputs tokens seen 368 * is not greater than or equal to the number of elements in the 369 * <i>correctValues</i> array. 370 */ 371 @Override 372 public void wrapup() throws IllegalActionException { 373 super.wrapup(); 374 375 boolean training = ((BooleanToken) trainingMode.getToken()) 376 .booleanValue(); 377 378 if (!training && _initialized) { 379 if (!_firedOnce) { 380 String errorMessage = "The fire() method of this actor was never called. " 381 + "Usually, this is an error indicating that " 382 + "starvation is occurring."; 383 384 String fireCompatProperty = "ptolemy.actor.lib.NonStrictTest.fire.compat"; 385 386 if (StringUtilities.getProperty(fireCompatProperty) 387 .length() > 0) { 388 System.err.println("Warning: '" + getFullName() + "' " 389 + errorMessage 390 + "\nThis error is being ignored because " + "the " 391 + fireCompatProperty + "property was set."); 392 } else { 393 _initialized = false; 394 throw new IllegalActionException(this, errorMessage); 395 } 396 } 397 398 if (_numberOfInputTokensSeen < ((ArrayToken) correctValues 399 .getToken()).length()) { 400 String errorMessage = "The test produced only " 401 + _numberOfInputTokensSeen 402 + " tokens, yet the correctValues parameter was " 403 + "expecting " 404 + ((ArrayToken) correctValues.getToken()).length() 405 + " tokens."; 406 if (((BooleanToken) requireAllCorrectValues.getToken()) 407 .booleanValue()) { 408 _initialized = false; 409 // FIXME: this produce a dialog for each failed test. 410 throw new IllegalActionException(this, errorMessage); 411 } 412 System.err.println( 413 "Warning: '" + getFullName() + "' " + errorMessage); 414 } 415 } 416 417 _initialized = false; 418 419 // Note that wrapup() might get called by the manager before 420 // we have any data... 421 if (training && _trainingTokens != null && _trainingTokens.size() > 0) { 422 Object[] newValues = _trainingTokens.toArray(); 423 424 // NOTE: Support input multiport for the benefit of derived classes. 425 int width = input.getWidth(); 426 Token[] newTokens = new Token[newValues.length]; 427 428 if (width == 1) { 429 for (int i = 0; i < newValues.length; i++) { 430 if (newValues[i] instanceof Token[]) { 431 // Handle width of 1, ArrayToken 432 newTokens[i] = new ArrayToken((Token[]) newValues[i]); 433 for (int j = 0; j < ((Token[]) newValues[i]).length; j++) { 434 _checkRangeOfTolerance(((Token[]) newValues[i])[j]); 435 } 436 } else { 437 newTokens[i] = (Token) newValues[i]; 438 _checkRangeOfTolerance((Token) newValues[i]); 439 } 440 } 441 } else { 442 for (int i = 0; i < newValues.length; i++) { 443 ArrayList entry = (ArrayList) newValues[i]; 444 445 // Entry may be an empty array, in which case, 446 // we cannot do the update, so we return. 447 if (entry.size() < 1) { 448 System.err.println("Warning: '" + getFullName() 449 + "': Unable to train. " 450 + "Zero tokens received in iteration " + i); 451 return; 452 } 453 454 Object[] entries = entry.toArray(); 455 Token[] newEntry = new Token[entries.length]; 456 457 for (int j = 0; j < entries.length; j++) { 458 newEntry[j] = (Token) entries[j]; 459 _checkRangeOfTolerance(newEntry[j]); 460 } 461 462 newTokens[i] = new ArrayToken(newEntry); 463 } 464 } 465 466 correctValues.setToken(new ArrayToken(newTokens)); 467 correctValues.setPersistent(true); 468 } 469 470 if (training 471 && (_trainingTokens == null || _trainingTokens.size() == 0)) { 472 System.err.println("Warning: '" + getFullName() 473 + "' The test produced 0 tokens."); 474 // If we get no data and we are training, set the expression 475 // to the empty string. 476 477 // Copernicus: Don't use setExpression() here, use setToken(NIL) 478 //correctValues.setExpression("{}"); 479 correctValues.setToken(ArrayToken.NIL); 480 } 481 } 482 483 /////////////////////////////////////////////////////////////////// 484 //// public variables //// 485 486 /** Exception message that is used if we are running under 487 * the nightly build and the trainingMode parameter is true. 488 */ 489 public static final String TRAINING_MODE_ERROR_MESSAGE = "Training Mode set for test actor and isRunningNightlyBuild()\n" 490 + " returned true, indicating that the\n" 491 + " ptolemy.ptII.isRunningNightlyBuild property is set.\n" 492 + " The trainingMode parameter should not be set in files\n" 493 + " that are checked into the nightly build!" 494 + " To run the tests in nightly build mode, use" 495 + " make nightly"; 496 497 /////////////////////////////////////////////////////////////////// 498 //// protected methods //// 499 500 /** Check that the difference in exponents between the 501 * input and the tolerance is not greater than the precision 502 * of a Double. If the exponent of newValue parameter is 503 * different by from the exponent of the <i>tolerance</i> 504 * parameter by more than 10, then adjust the <i>tolerance</i> 505 * parameter. This is useful for training large modesl 506 * that have many PublisherTests. 507 * @param newValue The token to be tested. DoubleTokens 508 * are tested, other tokens are ignored. 509 * @exception IllegalActionException If thrown while reading the 510 * <i>tolerance</i> parameter. 511 */ 512 protected void _checkRangeOfTolerance(Token newValue) 513 throws IllegalActionException { 514 if (newValue instanceof DoubleToken) { 515 Double value = ((DoubleToken) newValue).doubleValue(); 516 if (value == 0.0) { 517 // The exponent of 0.0 is -Infinity, so skip it 518 return; 519 } 520 double log = Math.log10(((DoubleToken) newValue).doubleValue()); 521 if (Math.abs(log - Math.log10(_tolerance)) > 10) { 522 // Set the tolerance to something closer to the input so that 523 // we don't set it many times. 524 double newTolerance = Math.pow(10, log - 9); 525 if (newTolerance > _tolerance) { 526 // Only set the tolerance if it is greater than the old tolerance. 527 tolerance.setToken(new DoubleToken(newTolerance)); 528 tolerance.setPersistent(true); 529 attributeChanged(tolerance); 530 System.out.println("NonStrictTest: " + getFullName() 531 + ": exponent of " + newValue + " is " + log 532 + ", which cannot be compared with the previous tolerance." 533 + " The new tolerance is " 534 + tolerance.getExpression() + "."); 535 } 536 } 537 } 538 } 539 540 /** Set the input port to be greater than or equal to 541 * <code>BaseType.GENERAL</code> in case backward type inference is 542 * enabled and the input port has no type declared. 543 * 544 * @return A set of inequalities. 545 */ 546 @Override 547 protected Set<Inequality> _customTypeConstraints() { 548 HashSet<Inequality> result = new HashSet<Inequality>(); 549 if (isBackwardTypeInferenceEnabled() 550 && input.getTypeTerm().isSettable()) { 551 result.add(new Inequality(new TypeConstant(BaseType.GENERAL), 552 input.getTypeTerm())); 553 } 554 return result; 555 } 556 557 /** Test whether the value of the first token is close to the 558 * the value of the second token. 559 * Arrays and Records are handled specially, see 560 * {@link #_isCloseToIfNilArrayElement(Token, Token, double)} and 561 * {@link #_isCloseToIfNilRecordElement(Token, Token, double)}. 562 * If the tokens do not support this comparison, then return false. 563 * 564 * @param token1 The first array token to compare. 565 * @param token2 The second array token to compare. 566 * @param epsilon The value that we use to determine whether two 567 * tokens are close. 568 * @return True if the first argument is close 569 * to this token. False 570 */ 571 protected static boolean _isClose(Token token1, Token token2, 572 double epsilon) { 573 574 // FIXME: If we get a nil token on the input, what should we do? 575 // Here, we require that the referenceToken also be nil. 576 // If the token is an ArrayToken and two corresponding elements 577 // are nil, then we consider them "close". 578 // return !(token1.isCloseTo(token2, epsilon).booleanValue() == false 579 // && !token2.isNil() 580 // && !_isCloseToIfNilArrayElement(token1, token2, 581 // epsilon) 582 // && !_isCloseToIfNilRecordElement(token1, token2, 583 // epsilon)); 584 585 try { 586 boolean isClose; 587 isClose = token1.isCloseTo(token2, epsilon).booleanValue() 588 || token1.isNil() && token2.isNil(); 589 // Additional guards makes things slightly easier for 590 // Copernicus. 591 if (token1 instanceof ArrayToken && token2 instanceof ArrayToken) { 592 isClose |= _isCloseToIfNilArrayElement(token1, token2, epsilon); 593 } 594 if (token1 instanceof RecordToken 595 && token2 instanceof RecordToken) { 596 isClose |= _isCloseToIfNilRecordElement(token1, token2, 597 epsilon); 598 } 599 return isClose; 600 } catch (IllegalActionException ex) { 601 return false; 602 } 603 } 604 605 /** Test whether the value of this token is close to the first argument, 606 * where "close" means that the distance between them is less than 607 * or equal to the second argument. This method only makes sense 608 * for tokens where the distance between them is reasonably 609 * represented as a double. It is assumed that the argument is 610 * an ArrayToken, and the isCloseTo() method of the array elements 611 * is used. 612 * This method differs from 613 * {@link ptolemy.data.ArrayToken#_isCloseTo(Token, double)} 614 * in that if corresponding elements are both nil tokens, then 615 * those two elements are considered "close", see 616 * {@link ptolemy.data.Token#NIL}. 617 * @param token1 The first array token to compare. 618 * @param token2 The second array token to compare. 619 * @param epsilon The value that we use to determine whether two 620 * tokens are close. 621 * @exception IllegalActionException If the elements do not support 622 * this comparison. 623 * @return True if the first argument is close 624 * to this token. False if the arguments are not ArrayTokens 625 */ 626 protected static boolean _isCloseToIfNilArrayElement(Token token1, 627 Token token2, double epsilon) throws IllegalActionException { 628 if (!(token1 instanceof ArrayToken) 629 || !(token2 instanceof ArrayToken)) { 630 return false; 631 } 632 633 ArrayToken array1 = (ArrayToken) token1; 634 ArrayToken array2 = (ArrayToken) token2; 635 if (array1.length() != array2.length()) { 636 return false; 637 } 638 639 for (int i = 0; i < array1.length(); i++) { 640 // Here is where isCloseTo() differs from isEqualTo(). 641 // Note that we return false the first time we hit an 642 // element token that is not close to our current element token. 643 BooleanToken result = array1.getElement(i) 644 .isCloseTo(array2.getElement(i), epsilon); 645 646 // If the tokens are not close and array1[i] and is not nil, then 647 // the arrays really aren't close. 648 if (result.booleanValue() == false) { 649 if (array1.getElement(i).isNil() 650 && array2.getElement(i).isNil()) { 651 // They are not close, but both are nil, so for 652 // our purposes, the are close. 653 } else { 654 return false; 655 } 656 } 657 } 658 return true; 659 } 660 661 /** Test whether the value of this token is close to the first argument, 662 * where "close" means that the distance between them is less than 663 * or equal to the second argument. This method only makes sense 664 * for tokens where the distance between them is reasonably 665 * represented as a double. It is assumed that the argument is 666 * a Record, and the isCloseTo() method of the record elements 667 * is used. 668 * This method differs from 669 * {@link ptolemy.data.RecordToken#_isCloseTo(Token, double)} 670 * in that if corresponding elements are both nil tokens, then 671 * those two elements are considered "close", see 672 * {@link ptolemy.data.Token#NIL}. 673 * @param token1 The first array token to compare. 674 * @param token2 The second array token to compare. 675 * @param epsilon The value that we use to determine whether two 676 * tokens are close. 677 * @exception IllegalActionException If the elements do not support 678 * this comparison. 679 * @return True if the first argument is close 680 * to this token. False if the arguments are not ArrayTokens 681 */ 682 protected static boolean _isCloseToIfNilRecordElement(Token token1, 683 Token token2, double epsilon) throws IllegalActionException { 684 if (!(token1 instanceof RecordToken) 685 || !(token2 instanceof RecordToken)) { 686 return false; 687 } 688 689 if (token1 instanceof OrderedRecordToken 690 && token2 instanceof OrderedRecordToken) { 691 return false; 692 } 693 694 RecordToken record1 = (RecordToken) token1; 695 RecordToken record2 = (RecordToken) token2; 696 697 Set myLabelSet = record1.labelSet(); 698 Set argLabelSet = record2.labelSet(); 699 700 if (!myLabelSet.equals(argLabelSet)) { 701 return false; 702 } 703 704 // Loop through all of the fields, checking each one for closeness. 705 Iterator iterator = myLabelSet.iterator(); 706 707 while (iterator.hasNext()) { 708 String label = (String) iterator.next(); 709 Token innerToken1 = record1.get(label); 710 Token innerToken2 = record2.get(label); 711 boolean result = false; 712 if (innerToken1 instanceof ArrayToken) { 713 result = _isCloseToIfNilArrayElement(innerToken1, innerToken2, 714 epsilon); 715 } else if (innerToken1 instanceof RecordToken) { 716 result = _isCloseToIfNilRecordElement(innerToken1, innerToken2, 717 epsilon); 718 } else { 719 result = innerToken1.isCloseTo(innerToken2, epsilon) 720 .booleanValue(); 721 } 722 723 if (!result) { 724 if (innerToken1.isNil() && innerToken2.isNil()) { 725 // They are not close, but both are nil, so for 726 // our purposes, the are close. 727 } else { 728 return false; 729 } 730 } 731 } 732 return true; 733 } 734 735 /////////////////////////////////////////////////////////////////// 736 //// protected variables //// 737 738 /** Set to true if fire() is called once. If fire() is not called at 739 * least once, then throw an exception in wrapup(). 740 */ 741 protected boolean _firedOnce = false; 742 743 /** Set to true when initialized() is called. 744 */ 745 protected boolean _initialized = false; 746 747 /** Count of iterations. */ 748 protected int _iteration; 749 750 /** Number of input tokens seen by this actor in the fire method.*/ 751 protected int _numberOfInputTokensSeen = 0; 752 753 /** An array of booleans where if an element is true, then the 754 * corresponding element in <i>correctValues</i> has been seen. 755 * This field is only used if the <i>requireCorrectOrder</i> 756 * parameter is false. 757 */ 758 protected boolean[] _matchedValues; 759 760 /** A double that is read from the <i>tolerance</i> parameter 761 * specifying how close the input has to be to the value 762 * given by <i>correctValues</i>. This is a double, with default 763 * value 10<sup>-9</sup>. 764 */ 765 protected double _tolerance; 766 767 /** List to store tokens for training mode. */ 768 protected List _trainingTokens; 769}