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}