001/* Generate discrete events by periodically sampling a CT signal. 002 003 Copyright (c) 1998-2014 The Regents of the University of California. 004 All rights reserved. 005 Permission is hereby granted, without written agreement and without 006 license or royalty fees, to use, copy, modify, and distribute this 007 software and its documentation for any purpose, provided that the above 008 copyright notice and the following two paragraphs appear in all copies 009 of this software. 010 011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY 012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES 013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF 014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF 015 SUCH DAMAGE. 016 017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, 018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE 020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF 021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, 022 ENHANCEMENTS, OR MODIFICATIONS. 023 024 PT_COPYRIGHT_VERSION_2 025 COPYRIGHTENDKEY 026 027 */ 028package ptolemy.domains.continuous.lib; 029 030import ptolemy.actor.Director; 031import ptolemy.actor.SuperdenseTimeDirector; 032import ptolemy.actor.lib.Sampler; 033import ptolemy.actor.lib.Transformer; 034import ptolemy.actor.util.CausalityInterface; 035import ptolemy.actor.util.Time; 036import ptolemy.data.DoubleToken; 037import ptolemy.data.IntToken; 038import ptolemy.data.Token; 039import ptolemy.data.expr.Parameter; 040import ptolemy.data.type.BaseType; 041import ptolemy.kernel.CompositeEntity; 042import ptolemy.kernel.util.Attribute; 043import ptolemy.kernel.util.IllegalActionException; 044import ptolemy.kernel.util.NameDuplicationException; 045import ptolemy.kernel.util.Workspace; 046 047/////////////////////////////////////////////////////////////////// 048//// PeriodicSampler 049 050/** 051 This actor generates discrete events by periodically sampling the input signal. 052 The sampling rate is given by parameter <i>samplePeriod</i>, which has default value 053 0.1. Specifically, if the actor is initialized at time <i>t</i> and the sample 054 period is <i>T</i>, then the output will have the value of the input 055 at times <i>t</i> + <i>nT</i>, for all natural numbers <i>n</i>. 056 By default, this sampler will send to the output the initial value of 057 the input (the input value at microstep 0), but will send it one 058 microstep later (at microstep 1). 059 This ensures that the output at microstep 0 is always absent, thus 060 ensuring continuity from the left. That is, the input is absent prior 061 to the sample time, so continuity requires that it be absent at 062 microstep 0 at the sample time. 063 <p> 064 To get this sampler to record values other than the initial value, 065 set the <i>microstep</i> parameter to a value greater than 0. 066 Setting it to 1, for example, will record the input value after 067 a discontinuity rather than before the discontinuity. Note 068 that {@link Sampler} normally records its inputs at microstep 1 069 (because it is triggered by a discrete signal, which has events 070 at microstep 1), and therefore if you want this PeriodicSampler 071 to behave the same as Sampler, you should set <i>microstep</i> 072 to 1. 073 <p> 074 This actor has multiport inputs and outputs. Signals in 075 each input channel are sampled and produced to corresponding output 076 channel. 077 <p> 078 Note that this actor does not tolerate changing input or output 079 connections during execution. 080 081 @author Edward A. Lee 082 @version $Id$ 083 @since Ptolemy II 8.0 084 @Pt.ProposedRating Yellow (eal) 085 @Pt.AcceptedRating Red (cxh) 086 */ 087public class PeriodicSampler extends Transformer { 088 /** Construct an actor in the specified container with the specified 089 * name. The name must be unique within the container or an exception 090 * is thrown. The container argument must not be null, or a 091 * NullPointerException will be thrown. 092 * The actor can be either dynamic, or not. It must be set at the 093 * construction time and can't be changed thereafter. 094 * A dynamic actor will produce a token at its initialization phase. 095 * @param container The container of this actor. 096 * @param name The actor's name 097 * @exception IllegalActionException If the entity cannot be contained 098 * by the proposed container. 099 * @exception NameDuplicationException If name coincides with 100 * an entity already in the container. 101 */ 102 public PeriodicSampler(CompositeEntity container, String name) 103 throws IllegalActionException, NameDuplicationException { 104 super(container, name); 105 input.setMultiport(true); 106 output.setMultiport(true); 107 output.setWidthEquals(input, true); 108 109 samplePeriod = new Parameter(this, "samplePeriod"); 110 samplePeriod.setExpression("0.1"); 111 samplePeriod.setTypeEquals(BaseType.DOUBLE); 112 113 microstep = new Parameter(this, "microstep"); 114 microstep.setExpression("0"); 115 microstep.setTypeEquals(BaseType.INT); 116 117 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" " 118 + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n" 119 + "<polyline points=\"-30,0 -20,0 -10,0 10,-7\"/>\n" 120 + "<polyline points=\"10,0 30,0\"/>\n" + "</svg>\n"); 121 } 122 123 /////////////////////////////////////////////////////////////////// 124 //// public variables //// 125 126 /** The microstep at which to read the input. This is an 127 * whose default value is 0. 128 */ 129 public Parameter microstep; 130 131 /** The parameter for the sampling period. This is a double 132 * whose default value is 0.1. 133 */ 134 public Parameter samplePeriod; 135 136 /////////////////////////////////////////////////////////////////// 137 //// public methods //// 138 139 /** If the attribute is microstep, adjust the causality interface. 140 * @exception IllegalActionException If the superclass throws it. 141 */ 142 @Override 143 public void attributeChanged(Attribute attribute) 144 throws IllegalActionException { 145 if (attribute == microstep) { 146 int microstepValue = ((IntToken) microstep.getToken()).intValue(); 147 if (microstepValue != _microstep) { 148 CausalityInterface causalityInterface = getCausalityInterface(); 149 if (microstepValue == 0) { 150 // Output depends on the input after a microstep delay. 151 causalityInterface.declareDelayDependency(input, output, 152 0.0, 1); 153 } else { 154 // Output depends immediately on the input. 155 causalityInterface.declareDelayDependency(input, output, 156 0.0, 0); 157 } 158 _microstep = microstepValue; 159 Director director = getDirector(); 160 if (director != null) { 161 director.invalidateSchedule(); 162 } 163 } 164 } else { 165 super.attributeChanged(attribute); 166 } 167 } 168 169 /** Clone this actor into the specified workspace. The new actor is 170 * <i>not</i> added to the directory of that workspace (you must do this 171 * yourself if you want it there). 172 * The result is a new actor with the same ports as the original, but 173 * no connections and no container. A container must be set before 174 * much can be done with this actor. 175 * 176 * @param workspace The workspace for the cloned object. 177 * @exception CloneNotSupportedException If cloned ports cannot have 178 * as their container the cloned entity (this should not occur), or 179 * if one of the attributes cannot be cloned. 180 * @return A new ComponentEntity. 181 */ 182 @Override 183 public Object clone(Workspace workspace) throws CloneNotSupportedException { 184 PeriodicSampler newObject = (PeriodicSampler) super.clone(workspace); 185 newObject.output.setWidthEquals(newObject.input, true); 186 return newObject; 187 } 188 189 /** Generate an output if the current time is one of the sampling 190 * times and the microstep matches. 191 * In addition, if the microstep parameter has value 0, 192 * produce the output only if the current microstep is 1. 193 * The value of the event is the value of the input signal at the 194 * current time at the microstep specified by the microstep parameter. 195 * @exception IllegalActionException If the transfer of tokens failed. 196 */ 197 @Override 198 public void fire() throws IllegalActionException { 199 super.fire(); 200 SuperdenseTimeDirector director = (SuperdenseTimeDirector) getDirector(); 201 Time currentTime = getDirector().getModelTime(); 202 int microstep = director.getIndex(); 203 if (_debugging) { 204 _debug("Current time is " + currentTime + " with microstep " 205 + microstep); 206 } 207 int inputWidth = input.getWidth(); 208 int outputWidth = output.getWidth(); 209 if (currentTime.compareTo(_nextSamplingTime) == 0) { 210 // Time is right to produce an output. Check the microstep. 211 if (_microstep == 0 && microstep == 1) { 212 // In delay mode. Input should have been read in microstep 0. 213 for (int i = 0; i < outputWidth; i++) { 214 if (_pendingOutputs[i] != null) { 215 // There is a deferred output for this input channel. 216 output.send(i, _pendingOutputs[i]); 217 if (_debugging) { 218 _debug("Sending output value " + _pendingOutputs[i] 219 + " on channel " + i); 220 } 221 } else { 222 output.sendClear(i); 223 } 224 } 225 } else if (_microstep != 0 && _microstep == microstep) { 226 // Microstep matches. 227 // Read the input and produce an output. 228 for (int i = 0; i < inputWidth; i++) { 229 if (input.isKnown(i) && input.hasToken(i)) { 230 Token token = input.get(i); 231 if (i < outputWidth) { 232 output.send(i, token); 233 if (_debugging) { 234 _debug("Read input and sent to output: " + token 235 + " on channel " + i); 236 } 237 } 238 } 239 } 240 // If the output is wider than the input, send clear. 241 for (int i = inputWidth; i < outputWidth; i++) { 242 output.sendClear(i); 243 } 244 } else { 245 // Microstep does not match. 246 for (int i = 0; i < outputWidth; i++) { 247 output.sendClear(i); 248 } 249 } 250 } else { 251 // Not time to send an output. 252 for (int i = 0; i < outputWidth; i++) { 253 output.sendClear(i); 254 } 255 } 256 } 257 258 /** Set the next sampling time for each 259 * input as the start time (i.e. the current time). 260 * We do not register the start time as a breakpoint, since the 261 * director will fire at the start time any way. 262 * @exception IllegalActionException If thrown by the supper class. 263 */ 264 @Override 265 public void initialize() throws IllegalActionException { 266 super.initialize(); 267 int width = output.getWidth(); 268 if (_pendingOutputs == null || _pendingOutputs.length != width) { 269 _pendingOutputs = new Token[width]; 270 } 271 Time currentTime = getDirector().getModelTime(); 272 _nextSamplingTime = currentTime; 273 for (int i = 0; i < width; i++) { 274 _pendingOutputs[i] = null; 275 } 276 getDirector().fireAt(this, _nextSamplingTime, _microstep); 277 } 278 279 /** Return false if the microstep value 280 * is zero. In that case, this actor can produce some outputs even the 281 * inputs are unknown. This actor is usable for breaking feedback 282 * loops. It does not read inputs in the fire() method. 283 * @return False. 284 */ 285 @Override 286 public boolean isStrict() { 287 if (_microstep == 0) { 288 return false; 289 } else { 290 return true; 291 } 292 } 293 294 /** If the current microstep is zero, sample the inputs and request 295 * a refiring at the current time. If it is one, then request a refiring 296 * at the next sample time. 297 * @return True. 298 * @exception IllegalActionException If the superclass throws it. 299 */ 300 @Override 301 public boolean postfire() throws IllegalActionException { 302 Director director = getDirector(); 303 Time currentTime = director.getModelTime(); 304 int microstep = ((SuperdenseTimeDirector) director).getIndex(); 305 int inputWidth = input.getWidth(); 306 if (currentTime.compareTo(_nextSamplingTime) == 0) { 307 // Current time matches. Check microstep. 308 if (_microstep == 0) { 309 // In delay mode. Read the input if the microstep is 0. 310 if (microstep == 0) { 311 int outputWidth = output.getWidth(); 312 for (int i = 0; i < outputWidth; i++) { 313 if (i < inputWidth && input.hasToken(i)) { 314 _pendingOutputs[i] = input.get(i); 315 } else { 316 _pendingOutputs[i] = null; 317 } 318 if (_debugging) { 319 _debug("Read input: " + _pendingOutputs[i] 320 + " on channel " + i); 321 } 322 } 323 for (int i = outputWidth; i < inputWidth; i++) { 324 // Read and discard the input. 325 input.get(i); 326 } 327 director.fireAt(this, currentTime, 1); 328 } else { 329 if (microstep == 1) { 330 double samplePeriodValue = ((DoubleToken) samplePeriod 331 .getToken()).doubleValue(); 332 _nextSamplingTime = currentTime.add(samplePeriodValue); 333 director.fireAt(this, _nextSamplingTime, 0); 334 } 335 // Consume the inputs. 336 for (int i = 0; i < inputWidth; i++) { 337 if (input.hasToken(i)) { 338 input.get(i); 339 } 340 } 341 } 342 } else { 343 // Not in delay mode. If the microstep matches, 344 // request refiring. 345 if (_microstep == microstep) { 346 double samplePeriodValue = ((DoubleToken) samplePeriod 347 .getToken()).doubleValue(); 348 _nextSamplingTime = currentTime.add(samplePeriodValue); 349 director.fireAt(this, _nextSamplingTime, _microstep); 350 } else { 351 // Consume the inputs. 352 for (int i = 0; i < inputWidth; i++) { 353 if (input.hasToken(i)) { 354 input.get(i); 355 } 356 } 357 } 358 } 359 } else { 360 // Time does not match. 361 // Consume the inputs. 362 for (int i = 0; i < inputWidth; i++) { 363 if (input.hasToken(i)) { 364 input.get(i); 365 } 366 } 367 } 368 return super.postfire(); 369 } 370 371 /** Make sure the actor runs inside a domain that understands 372 * superdense time. 373 * @exception IllegalActionException If the director is not 374 * a SuperdenseTimeDirector or the parent class throws it. 375 */ 376 @Override 377 public void preinitialize() throws IllegalActionException { 378 if (!(getDirector() instanceof SuperdenseTimeDirector)) { 379 throw new IllegalActionException("PeriodicSampler can only" 380 + " be used inside a superdense time domain."); 381 } 382 super.preinitialize(); 383 } 384 385 /////////////////////////////////////////////////////////////////// 386 //// private variables //// 387 388 /** The next sampling time. */ 389 private Time _nextSamplingTime; 390 391 /** Record of pending output tokens (those that have been 392 * delayed because they appeared at the input when the 393 * the microstep was zero). 394 */ 395 private Token[] _pendingOutputs; 396 397 /** Value of the microstep. */ 398 private int _microstep = 1; 399}