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}