001/* An actor that outputs a random sequence with a Gaussian distribution. 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.kernel; 029 030import ptolemy.actor.Director; 031import ptolemy.actor.lib.Gaussian; 032import ptolemy.actor.util.Time; 033import ptolemy.data.BooleanToken; 034import ptolemy.data.expr.Parameter; 035import ptolemy.data.type.BaseType; 036import ptolemy.kernel.CompositeEntity; 037import ptolemy.kernel.util.IllegalActionException; 038import ptolemy.kernel.util.NameDuplicationException; 039 040/////////////////////////////////////////////////////////////////// 041//// Noise 042 043/** 044This actor generates continuous-time noise with a Gaussian distribution. 045It provides two approximations to a white noise process, depending 046on the value of the <i>linearlyInterpolate</i> parameter. Specifically, 047if this parameter is true (the default), then the output signal is 048a continuous signal that linearly interpolates between 049Gaussian random numbers generated at each sample time chosen by 050the solver. If the solver step size is constant or increasing, then these Gaussian 051random numbers will be independent. However, if the solver finds itself having 052to reduce the step size after performing a speculative execution into the 053future, then the random number produced at the end of the reduced 054integration step will be correlated with that produced at the beginning 055of the integration step. (FIXME: Need a figure to illustrate this.) 056<p> 057If <i>linearlyInterpolate</i> is set to false, then this actor will 058hold the value of its output constant for the duration of an integration 059step. Thus, the output signal is piecewise constant. At each time step 060chosen by the solver, the value is given by a new independent Gaussian 061random number. This method has the advantage that samples at all 062times chosen by the solver are uncorrelated. 063<p> 064In both cases, whether <i>linearlyInterpolate</i> is true or false, if the 065solver holds its step size constant, then the resulting signal is 066statistically equivalent to filtered white noise. If <i>linearlyInterpolate</i> 067is true, then the power spectrum has the shape of a sinc squared. 068If it is false, then it has the shape of the absolute value of a sinc 069function. In the latter case, the power is infinite, so the approximation 070is not physically realizable. In the former case, the power is finite. 071In both cases, sampling the process at the rate of one over the step 072size yields a discrete-time white noise process. 073<p> 074It is worth explaining why we must approximate white noise. 075In general, it is not possible in any discretized approximation of a continuous 076random process to exactly simulate a white noise process. By definition, a 077white noise process is one where any two values at distinct times are 078uncorrelated. A naive attempt to simulate this might simply generate 079a new random number at each sample time at which the solver chooses 080to fire the actor. However, this cannot work in general. 081Specifically, the semantics of the continuous domain assumes 082that signals are piecewise continuous. The signal resulting from 083the above strategy will not be piecewise continuous. If the solver 084refines a step size and chooses a point close to a previously calculated 085point, the new value produced by such an actor would not be close to the 086previously value previously produced. This can result in the solver 087assuming that its step size is too large and reducing it until it can 088reduce it no more. 089<p> 090To demonstrate this effect, try connecting a GaussianActor to a 091LevelCrossingDetector actor under a ContinuousDirector. An execution 092of the model will immediately trigger an exception with a message 093like "The refined step size is less than the time resolution, at time..." 094Conceptually, with a true white noise process, the level crossing 095occurs <i>at all times</i>, and therefore the exception is, 096in fact, the correct response. 097<p> 098If you modify the above example by sending the output of the 099Gaussian actor directly to an Integrator, and then the output 100of the Integrator to the LevelCrossingDetector, then the exception 101disappears. The Integrator ensures that the signal is piecewise 102continuous. This might seem like a reasonable approximation to a 103Weiner process, but in fact it is problematic. In particular, 104at the times that the LevelCrossingDetector triggers, the 105Gaussian actor will actually produce two distinct random numbers 106at the same time (at different microsteps). This changes the 107statistics of the output in a very subtle way. 108 109 @author Edward A. Lee 110 @version $Id$ 111 @since Ptolemy II 8.0 112 @Pt.ProposedRating Yellow (eal) 113 @Pt.AcceptedRating Red (eal) 114 */ 115public class Noise extends Gaussian { 116 /** Construct an actor with the given container and name. 117 * @param container The container. 118 * @param name The name of this actor. 119 * @exception IllegalActionException If the actor cannot be contained 120 * by the proposed container. 121 * @exception NameDuplicationException If the container already has an 122 * actor with this name. 123 */ 124 public Noise(CompositeEntity container, String name) 125 throws NameDuplicationException, IllegalActionException { 126 super(container, name); 127 128 linearlyInterpolate = new Parameter(this, "linearlyInterpolate"); 129 linearlyInterpolate.setTypeEquals(BaseType.BOOLEAN); 130 linearlyInterpolate.setExpression("true"); 131 } 132 133 /////////////////////////////////////////////////////////////////// 134 //// parameters //// 135 136 /** If true, linearly between random number for multistep solvers, 137 * and otherwise, perform zero-order hold. This is a boolean that 138 * defaults to true. 139 */ 140 public Parameter linearlyInterpolate; 141 142 /////////////////////////////////////////////////////////////////// 143 //// public methods //// 144 145 /** Produce a number that is linearly interpolated within the 146 * current integration step, if <i>linearlyInterpolate</i> is true, or 147 * the random number for the beginning of the integration 148 * step otherwise. 149 * @exception IllegalActionException If the superclass throws it. 150 */ 151 @Override 152 public void fire() throws IllegalActionException { 153 if (_needNewGenerator) { 154 _createGenerator(); 155 } 156 if (_needNew) { 157 // Generate a random number for the _end_ of the current 158 // integration period. 159 _generateRandomNumber(); 160 _needNew = false; 161 } 162 // Get required information from the director. 163 Director director = getDirector(); 164 double stepSize = ((ContinuousDirector) director)._getCurrentStepSize(); 165 boolean interpolating = ((BooleanToken) linearlyInterpolate.getToken()) 166 .booleanValue(); 167 if (!interpolating) { 168 // Doing zero-order hold. 169 // Output value should be the value at the beginning 170 // of the integration period. 171 _current = _valueAtStart; 172 } else if (stepSize == 0.0) { 173 // Interpolating and step size is zero. 174 // If the step size is zero then any newly 175 // generated random number is ignored. 176 _current = _valueAtStart; 177 } else { 178 // Interpolating and step size is greater than zero. 179 Time iterationBeginTime = ((ContinuousDirector) director)._iterationBeginTime; 180 Time currentTime = ((ContinuousDirector) director).getModelTime(); 181 double interval = currentTime.subtract(iterationBeginTime) 182 .getDoubleValue(); 183 if (interval == 0.0) { 184 _current = _valueAtStart; 185 } else { 186 double timeGapBetweenValues = _timeOfValueAtEnd 187 .subtract(iterationBeginTime).getDoubleValue(); 188 _current = _valueAtStart + (_valueAtEnd - _valueAtStart) 189 * interval / timeGapBetweenValues; 190 } 191 } 192 // The superclass produces on the output the _current value. 193 // Since above we set _needNew to false if it was true, 194 // the superclass will never produce a new random number. 195 super.fire(); 196 } 197 198 /** Initialize the random number generator with the seed, if it 199 * has been given. A seed of zero is interpreted to mean that no 200 * seed is specified. In such cases, a seed based on the current 201 * time and this instance of a RandomSource is used to be fairly 202 * sure that two identical sequences will not be returned. 203 * @exception IllegalActionException If the parent class throws it. 204 */ 205 @Override 206 public void initialize() throws IllegalActionException { 207 super.initialize(); 208 // Generate the first random number. 209 super._generateRandomNumber(); 210 // The first step size will be zero, so the value at 211 // the start and end of the integration period will be 212 // the same, namely the random number generated above. 213 _valueAtStart = _current; 214 _valueAtEnd = _current; 215 _timeOfValueAtEnd = getDirector().getModelTime(); 216 // Suppress generation of the first random number in the 217 // next invocation of fire(). 218 _needNew = false; 219 } 220 221 /** Set a flag to cause a new random number to be generated the next 222 * time fire() is called. 223 * @exception IllegalActionException If the base class throws it. 224 * @return True if it is OK to continue. 225 */ 226 @Override 227 public boolean postfire() throws IllegalActionException { 228 // The next line will set _needNew = true. 229 boolean result = super.postfire(); 230 231 Director director = getDirector(); 232 // The cast is safe because it was checked in fire(). 233 double stepSize = ((ContinuousDirector) director)._getCurrentStepSize(); 234 235 // If we are not interpolating, then request a refiring at the current time 236 // so that the new value is then produced at the current time. 237 boolean interpolating = ((BooleanToken) linearlyInterpolate.getToken()) 238 .booleanValue(); 239 if (!interpolating) { 240 // Request a refiring at the current time in order to get a zero-order 241 // hold effect, but only if the step size is non-zero. 242 // If the step size is zero, the new random number will be 243 // the same as the old so there is no need to refire. 244 if (stepSize != 0.0) { 245 // Request a refiring at the current time in order to get 246 // the zero-order hold effect. 247 director.fireAtCurrentTime(this); 248 } 249 } 250 return result; 251 } 252 253 /////////////////////////////////////////////////////////////////// 254 //// protected methods //// 255 256 /** Generate a new random number. This gets called in initialize() 257 * and in the first fire() method of an iteration. It produces a number 258 * that is to be the random number at the <i>end</i> of the current 259 * iteration. 260 * @exception IllegalActionException If parameter values are incorrect. 261 */ 262 @Override 263 protected void _generateRandomNumber() throws IllegalActionException { 264 Director director = getDirector(); 265 if (!(director instanceof ContinuousDirector)) { 266 throw new IllegalActionException(this, director, 267 "WhiteNoise actor is designed to work with ContinuousDirector," 268 + " but the director is " + director.getClass()); 269 } 270 double stepSize = ((ContinuousDirector) director)._getCurrentStepSize(); 271 boolean interpolating = ((BooleanToken) linearlyInterpolate.getToken()) 272 .booleanValue(); 273 if (!interpolating) { 274 // Doing zero-order hold. 275 _valueAtStart = _valueAtEnd; 276 _timeOfValueAtEnd = director.getModelTime(); 277 // Generate a new random number only if the step size is 278 // non-zero. 279 if (stepSize != 0.0) { 280 super._generateRandomNumber(); 281 _valueAtEnd = _current; 282 } 283 } else { 284 // Interpolating. 285 // The value at the start of the new period should be equal 286 // to the interpolated value at the current time. 287 // Presumably, this is the most recently produced output. 288 _valueAtStart = _current; 289 _timeOfValueAtEnd = director.getModelTime().add(stepSize); 290 // If the step size is non-zero, generate a new random number 291 // for the end of the period. Otherwise, use the current value. 292 if (stepSize != 0.0) { 293 super._generateRandomNumber(); 294 _valueAtEnd = _current; 295 } else { 296 _valueAtEnd = _valueAtStart; 297 } 298 } 299 } 300 301 /////////////////////////////////////////////////////////////////// 302 //// private variables //// 303 304 /** Time assigned to a new random number. This will be the integration end 305 * time for the first firing with a non-zero step size after the random 306 * number is generated. 307 */ 308 private Time _timeOfValueAtEnd; 309 310 /** The random number at the start of the current integration period. */ 311 private double _valueAtStart; 312 313 /** The random number at the end of the current integration period. */ 314 private double _valueAtEnd; 315}