001/* Check the input streams against a parameter value and outputs
002 a boolean true if result is correct.
003
004 Copyright (c) 1998-2016 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 review output port.
029 */
030package ptolemy.actor.lib;
031
032import java.util.ArrayList;
033import java.util.Iterator;
034import java.util.LinkedList;
035import java.util.List;
036
037import ptolemy.actor.TypedIOPort;
038import ptolemy.data.ArrayToken;
039import ptolemy.data.BooleanToken;
040import ptolemy.data.Token;
041import ptolemy.data.type.BaseType;
042import ptolemy.kernel.CompositeEntity;
043import ptolemy.kernel.util.IllegalActionException;
044import ptolemy.kernel.util.NameDuplicationException;
045
046///////////////////////////////////////////////////////////////////
047//// Test
048
049/**
050
051 <p>This actor compares the inputs against the value specified by the
052 <i>correctValues</i> parameter.  That parameter is an ArrayToken,
053 where each element of the array should have the same type as the
054 input.  The length of this array is the number of iterations of this
055 actor that are tested.  Subsequent iterations always succeed, so the
056 actor can be used as a "power-up" test for a model, checking the first
057 few iterations against some known results.
058 </p><p>
059 The input is a multiport.  If there is more than one channel connected
060 to it, then each element of <i>correctValues</i> must itself be an
061 ArrayToken, with length matching the number of channels.
062 Suppose for example that the width of the input is one,
063 and the first three inputs should be 1, 2, and 3.  Then you can
064 set <i>correctValues</i> to</p>
065 <pre>
066 {1, 2, 3}
067 </pre>
068 <p>Suppose instead that the input has width two, and the correct values
069 in the first iteration are 1 on the first channel and 2 on the second.
070 Then on the second iteration, the correct values are 3 on the first
071 channel and 4 on the second.  Then you can set <i>correctValues</i> to</p>
072 <pre>
073 {{1, 2}, {3, 4}}
074 </pre>
075 <p> With this setting, no tests are performed after the first two iterations
076 of this actor.
077 </p><p>
078 The input values are checked in the fire() method, which checks to
079 make sure that each input channel has a token.  If an input value is
080 missing or differs from what it should be, then fire() throws an
081 exception. Thus, the test passes if no exception is thrown.
082 If you need to check the input value in postfire() (say, after
083 a fixed-point iteration has converged), then use NonStrictTest.
084 </p><p>
085 If the input is a DoubleToken or ComplexToken,
086 then the comparison passes if the value is close to what it should
087 be, within the specified <i>tolerance</i> (which defaults to
088 10<sup>-9</sup>.  The input data type is undeclared, so it can
089 resolve to anything.
090 </p><p>
091 On each firing, this actor produces the output <i>false</i> until
092 it reaches the end of the <i>correctValues</i> array, at which point
093 it outputs <i>true</i>.  This can be fed, for example, to an instance
094 of the Stop actor to stop the test upon successfully matching the
095 test data. In training mode, the output is always false.
096 </p>
097 @see NonStrictTest
098 @author Edward A. Lee, Christopher Hylands, Jim Armstrong
099 @version $Id$
100 @since Ptolemy II 1.0
101 @Pt.ProposedRating Yellow (eal)
102 @Pt.AcceptedRating Yellow (cxh)
103 */
104public class Test extends NonStrictTest {
105    /** Construct an actor with an input multiport.
106     *  @param container The container.
107     *  @param name The name of this actor.
108     *  @exception IllegalActionException If the entity cannot be contained
109     *   by the proposed container.
110     *  @exception NameDuplicationException If the container already has an
111     *   actor with this name.
112     */
113    public Test(CompositeEntity container, String name)
114            throws NameDuplicationException, IllegalActionException {
115        super(container, name);
116
117        // Note that the parent class (NonStrictTest) does not have a multiport
118        // input port.
119        input.setMultiport(true);
120        output = new TypedIOPort(this, "output", false, true);
121        output.setTypeEquals(BaseType.BOOLEAN);
122    }
123
124    ///////////////////////////////////////////////////////////////////
125    ////                         ports                             ////
126
127    /** Boolean output that is false as long as there is data to
128     *  compare against the input, but becomes true on the first
129     *  firing after such data has been exhausted.
130     */
131    public TypedIOPort output;
132
133    ///////////////////////////////////////////////////////////////////
134    ////                         public methods                    ////
135
136    /** Read one token from each input channel and compare against
137     *  the value specified in <i>correctValues</i>. If the value
138     *  does not match, then throw an exception. If the value
139     *  matches, then output false if additional inputs are
140     *  expected (to indicate that the test is not
141     *  complete yet), and output true if this is the last expected
142     *  input.
143     *  <p>If the iteration count is larger than the length of
144     *  <i>correctValues</i>, then output <i>true</i> and return,
145     *  indicating that the test is complete, i.e. that all
146     *  values in <i>correctValues</i> have been matched.</p>
147     *  <p>If the <i>requireOrderedValues</i> parameter is false,
148     *  then the input can match any as yet unmatched value
149     *  in <i>correctValues</i>.
150     *
151     *  @exception IllegalActionException If an input is missing,
152     *   or if its value does not match the required value.
153     */
154    @Override
155    public void fire() throws IllegalActionException {
156        super.fire();
157
158        int width = input.getWidth();
159
160        // If we are in training mode, read the inputs and add to the
161        // training data.
162        boolean training = ((BooleanToken) trainingMode.getToken())
163                .booleanValue();
164
165        if (training) {
166            if (_debugging) {
167                _debug("Debug mode is on.");
168            }
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                    if (_debugging) {
177                        _debug("-- Read training input: " + token);
178                    }
179                    if (token instanceof ArrayToken) {
180                        Token[] innerArrayToken = new Token[1];
181                        innerArrayToken[0] = token;
182                        _trainingTokens.add(innerArrayToken);
183                    } else {
184                        _trainingTokens.add(token);
185                    }
186                }
187            } else {
188                ArrayList arrayList = new ArrayList();
189
190                for (int i = 0; i < width; i++) {
191                    Token token = input.get(i);
192                    if (_debugging) {
193                        _debug("-- Read training inputs: " + token);
194                    }
195                    arrayList.add(token);
196                }
197                _trainingTokens.add(arrayList);
198            }
199            output.broadcast(BooleanToken.FALSE);
200            return;
201        }
202
203        // If we are past the end of the expected inputs, then read
204        // and discard all inputs and output true
205        if (_numberOfInputTokensSeen >= ((ArrayToken) correctValues.getToken())
206                .length()) {
207            if (_debugging) {
208                _debug("Past the end of training data. Read and discard all inputs.");
209            }
210            // Consume and discard input values.  We are beyond the end
211            // of the correctValues array.
212            for (int i = 0; i < width; i++) {
213                if (input.hasToken(i)) {
214                    input.get(i);
215                }
216            }
217
218            // Indicate that the test has passed if the output is connected.
219            output.broadcast(BooleanToken.TRUE);
220            return;
221        }
222
223        Token[] reference = null;
224        // _matchedValues[x] is true if the x'th element of
225        // correctValues has been used.  _matchedValues is shared across
226        // multiple firings.
227
228        // possibleMatches is a list of indexes to possible
229        // correctValues for this firing.  Each element of the list is
230        // an index into the correctValues parameter that refers to a
231        // possibly correct value that has matched all the inputs thus
232        // far.
233        List<Integer> possibleMatches = null;
234
235        if (((BooleanToken) requireOrderedValues.getToken()).booleanValue()) {
236            reference = _getReference(_numberOfInputTokensSeen, width);
237        } else {
238            // While handling multiport inputs, we use the possibleMatches
239            // list to keep track of which of the elements in
240            // correctValues are possible correct values for this
241            // firing.
242
243            possibleMatches = new LinkedList();
244            for (int j = 0; j < _matchedValues.length; j++) {
245                if (!_matchedValues[j]) {
246                    possibleMatches.add(j);
247                }
248            }
249        }
250
251        for (int i = 0; i < width; i++) {
252            if (!input.hasToken(i)) {
253                throw new IllegalActionException(this,
254                        "Test fails in iteration " + _numberOfInputTokensSeen
255                                + ".\n" + "Empty input on channel " + i);
256            }
257
258            Token token = input.get(i);
259
260            if (((BooleanToken) requireOrderedValues.getToken())
261                    .booleanValue()) {
262                if (_debugging) {
263                    _debug("-- Read input: " + token
264                            + ", which is expected to match: " + reference[i]);
265                }
266
267                if (!_isClose(token, reference[i], _tolerance)) {
268                    throw new IllegalActionException(this,
269                            "Test fails in iteration "
270                                    + _numberOfInputTokensSeen + ".\n"
271                                    + "Value was: " + token
272                                    + ". Should have been: " + reference[i]);
273                }
274            } else {
275                // requireCorrectOrder is false.
276
277                // Loop through all the as yet unmatched correct values.
278                //
279                // If a channel does not match, then remove it from the
280                // list of possible matches
281                //
282                // If the value on the channel does not match any of
283                // the possible correct values, then throw an
284                // exception.  If all the channels match an as yet
285                // unmatched correct value, then mark that correct
286                // value as matched.
287                boolean sawMatch = false;
288                Iterator<Integer> possibles = possibleMatches.iterator();
289                while (possibles.hasNext()) {
290                    int possibleIndex = possibles.next().intValue();
291                    try {
292                        reference = _getReference(possibleIndex, width);
293                        if (_debugging) {
294                            _debug("Test: token #: " + _numberOfInputTokensSeen
295                                    + " channel: " + i + " possible: "
296                                    + possibleIndex + " token: " + token
297                                    + "ref[" + i + "]: " + reference[i]);
298                        }
299
300                        if (_isClose(token, reference[i], _tolerance)) {
301                            if (!sawMatch) {
302                                sawMatch = true;
303                                _matchedValues[possibleIndex] = true;
304                            }
305                        } else {
306                            if (_debugging) {
307                                _debug("Test: removing " + possibleIndex);
308                            }
309                            possibles.remove();
310                        }
311                    } catch (IllegalActionException ex) {
312                        // Chain the exceptions together so we know which test
313                        // actor failed if there was more than one...
314                        throw new IllegalActionException(this, ex,
315                                "Test fails in iteration "
316                                        + _numberOfInputTokensSeen + ".\n"
317                                        + "Value was: " + token);
318
319                    }
320                }
321                if (!sawMatch) {
322                    throw new IllegalActionException(this,
323                            "Test fails in iteration " + _iteration + ".\n"
324                                    + "Value was: " + token
325                                    + ". No matches were found in any of "
326                                    + "the as yet unmatched correct values.");
327                }
328            }
329        }
330
331        _numberOfInputTokensSeen++;
332
333        if (output.numberOfSinks() > 0) {
334            if (_numberOfInputTokensSeen >= ((ArrayToken) correctValues
335                    .getToken()).length()) {
336                // Seen all expected inputs.
337                output.send(0, BooleanToken.TRUE);
338            } else {
339                // More inputs expected.
340                output.send(0, BooleanToken.FALSE);
341            }
342        }
343    }
344
345    /** Override the base class to do nothing and return true.
346     *  @return True.
347     */
348    @Override
349    public boolean postfire() {
350        return true;
351    }
352
353    ///////////////////////////////////////////////////////////////////
354    ////                         private methods                   ////
355
356    /** Get the reference from the correctValues parameter.
357     *  @param index The index of the element in correctValues to get.
358     *  @param width The width of the input port
359     *  @return an array of tokens.
360     */
361    private Token[] _getReference(int index, int width)
362            throws IllegalActionException {
363        Token[] reference;
364        Token referenceToken = ((ArrayToken) correctValues.getToken())
365                .getElement(index);
366
367        if (width == 1 && !(referenceToken instanceof ArrayToken)) {
368            reference = new Token[1];
369            reference[0] = referenceToken;
370        } else {
371            try {
372                reference = ((ArrayToken) referenceToken).arrayValue();
373            } catch (ClassCastException ex) {
374                throw new IllegalActionException(this,
375                        "Test fails in iteration " + _numberOfInputTokensSeen
376                                + ".\n" + "Width of input is " + width
377                                + ", but correctValues parameter "
378                                + "is not an array " + "of arrays.");
379            }
380
381            if (width != reference.length) {
382                throw new IllegalActionException(this,
383                        "Test fails in iteration " + _numberOfInputTokensSeen
384                                + ".\n" + "Width of input is " + width
385                                + ", which does not match "
386                                + "the  width of the "
387                                + _numberOfInputTokensSeen + "-th element of"
388                                + " correctValues, " + reference.length);
389            }
390        }
391        return reference;
392    }
393
394    ///////////////////////////////////////////////////////////////////
395    ////                         protected methods                 ////
396
397    // /** Set the input port greater than or equal to the type of elements
398    //  *  in the correctValues array in case backward type inference is
399    //  *  enabled and the input port has no type declared.
400    //  *
401    //  *  @return A set of inequalities.
402    //  */
403    // @Override
404    // protected Set<Inequality> _customTypeConstraints() {
405    //     HashSet<Inequality> result = new HashSet<Inequality>();
406    //     if (isBackwardTypeInferenceEnabled()
407    //             && input.getTypeTerm().isSettable()) {
408    //         try {
409    //             TypeConstant typeConstant = null;
410    //             if (((BooleanToken) trainingMode.getToken())
411    //                     .booleanValue()) {
412    //                 typeConstant = new TypeConstant(BaseType.GENERAL);
413    //             } else {
414    //                 ArrayToken correctValuesToken = (ArrayToken) correctValues.getToken();
415    //                 if (input.getWidth() == 1) {
416    //                 // result.add(
417    //                 //         new Inequality(
418    //                 //                 new TypeConstant(
419    //                 //                         ((ArrayToken) correctValues.getToken())
420    //                 //                         .getElementType()),
421    //                 //                 input.getTypeTerm()));
422
423    //                 typeConstant = new TypeConstant(
424    //                         correctValuesToken.getElementType());
425    //                 } else {
426    //                     typeConstant = new TypeConstant(
427    //                             ((ArrayToken) correctValuesToken.getElement(0)).getElementType());
428    //                 }
429    //             }
430    //             result.add( new Inequality(typeConstant,
431    //                             input.getTypeTerm()));
432    //         } catch (IllegalActionException ex) {
433    //             throw new InternalErrorException(this, ex, "Failed to set the type constraint.");
434    //         }
435    //     }
436    //     return result;
437    // }
438
439}