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.lib; 029 030import ptolemy.actor.Director; 031import ptolemy.actor.lib.Gaussian; 032import ptolemy.actor.util.Time; 033import ptolemy.data.BooleanToken; 034import ptolemy.data.DoubleToken; 035import ptolemy.data.expr.Parameter; 036import ptolemy.data.type.BaseType; 037import ptolemy.domains.continuous.kernel.ContinuousDirector; 038import ptolemy.kernel.CompositeEntity; 039import ptolemy.kernel.util.IllegalActionException; 040import ptolemy.kernel.util.NameDuplicationException; 041 042/////////////////////////////////////////////////////////////////// 043//// BandlimitedNoise 044 045/** 046This actor generates continuous-time noise with a Gaussian distribution 047and controlled bandwidth. The power spectrum of the noise produced 048is given by 049<pre> 050 S(f) = T s^2 sinc^4(pi f T) 051</pre> 052where f is frequency, s is the standard deviation, 053and the sinc function is given by 054<pre> 055 sinc(a) = sin(a)/a 056</pre> 057Here, T = 1/b, where b is the value of the <i>bandwidth</i> parameter. 058Notice that the power declines as the fourth power 059of one over the frequency. The <i>bandwidth</i> parameter specifies 060the frequency (in Hertz) at which the first zero occurs, or, 061equivalently, roughly the width of the main lobe. 062<p> 063This actor may affect the step size taken by the solver. Specifically, 064it ensures that the solver provides executions at least as frequently 065as twice the specified bandwidth. This is nominally the Nyquist frequency 066of an ideally bandlimited noise frequency, but since this noise process 067is not ideally bandlimited, the solver samples will typically have 068aliasing distortion. If you need to control that aliasing distortion, 069then you can set the <i>maxStepSize</i> parameter to something smaller 070than 1/2b, where b is the <i>bandwidth</i>. 071<p> 072For some uses, the effect that this actor has on the step size may 073be undesirable because it increases the cost of simulation. 074If a less rigorous form of noise is desired (for rough models or 075simple demonstrations), you can use the {@link Noise} actor. 076 077 @author Edward A. Lee 078 @version $Id$ 079 @since Ptolemy II 8.0 080 @Pt.ProposedRating Yellow (eal) 081 @Pt.AcceptedRating Red (eal) 082 */ 083public class BandlimitedNoise extends Gaussian { 084 /** Construct an actor with the given container and name. 085 * @param container The container. 086 * @param name The name of this actor. 087 * @exception IllegalActionException If the actor cannot be contained 088 * by the proposed container. 089 * @exception NameDuplicationException If the container already has an 090 * actor with this name. 091 */ 092 public BandlimitedNoise(CompositeEntity container, String name) 093 throws NameDuplicationException, IllegalActionException { 094 super(container, name); 095 096 output.setTypeEquals(BaseType.DOUBLE); 097 098 bandwidth = new Parameter(this, "bandwidth"); 099 bandwidth.setTypeEquals(BaseType.DOUBLE); 100 bandwidth.setExpression("10.0"); 101 102 // Move the mean port to first and hide the trigger port. 103 // It makes no sense to trigger this actor. 104 mean.getPort().moveToFirst(); 105 new Parameter(trigger, "_hide", BooleanToken.TRUE); 106 } 107 108 /////////////////////////////////////////////////////////////////// 109 //// parameters //// 110 111 /** The bandwidth of the noise random process in Hertz. 112 * The bandwidth is the frequency where the power spectral 113 * density first hits zero. This is a double that defaults to 114 * 10.0 Hertz. 115 */ 116 public Parameter bandwidth; 117 118 /////////////////////////////////////////////////////////////////// 119 //// public methods //// 120 121 /** Produce a number that is linearly interpolated within the 122 * current integration step, if <i>linearlyInterpolate</i> is true, or 123 * the random number for the beginning of the integration 124 * step otherwise. 125 * @exception IllegalActionException If the superclass throws it. 126 */ 127 @Override 128 public void fire() throws IllegalActionException { 129 // NOTE: The superclass fire() doesn't do what we want, 130 // so we have to replicate the parts of its superclass 131 // that we do want, which isn't much. 132 if (_debugging) { 133 _debug("Called fire()"); 134 } 135 136 standardDeviation.update(); 137 mean.update(); 138 139 if (_needNewGenerator) { 140 _createGenerator(); 141 } 142 Director director = getDirector(); 143 Time currentTime = director.getModelTime(); 144 if (_timeOfValueAtStart != null) { 145 // Not the first firing after initialize. 146 // Use the quantized version of the interval. 147 // Note that this will never be zero. 148 double timeGapBetweenValues = _timeOfValueAtEnd 149 .subtract(_timeOfValueAtStart).getDoubleValue(); 150 // The time interval, however, may be zero. 151 double interval = currentTime.subtract(_timeOfValueAtStart) 152 .getDoubleValue(); 153 // FIXME: Linear iterpolation is not a good choice here. 154 // Should be doing filtering. 155 _current = _valueAtStart + (_valueAtEnd - _valueAtStart) * interval 156 / timeGapBetweenValues; 157 } 158 output.send(0, new DoubleToken(_current)); 159 } 160 161 /** Initialize the random number generator with the seed, if it 162 * has been given. A seed of zero is interpreted to mean that no 163 * seed is specified. In such cases, a seed based on the current 164 * time and this instance of a RandomSource is used to be fairly 165 * sure that two identical sequences will not be returned. 166 * @exception IllegalActionException If the parent class throws it. 167 */ 168 @Override 169 public void initialize() throws IllegalActionException { 170 super.initialize(); 171 172 // Generate the first random number. 173 super._generateRandomNumber(); 174 _valueAtEnd = _current; 175 _timeOfValueAtEnd = getDirector().getModelTime(); 176 _timeOfValueAtStart = null; 177 178 // Generate the second random number. 179 _generateRandomNumber(); 180 getDirector().fireAt(this, _timeOfValueAtEnd); 181 } 182 183 /** If we are at the end of the current interval, then generate 184 * a new random number for the new interval, and request a 185 * refiring at the end of that interval. 186 * @exception IllegalActionException If the base class throws it. 187 * @return True if it is OK to continue. 188 */ 189 @Override 190 public boolean postfire() throws IllegalActionException { 191 boolean result = super.postfire(); 192 193 Director director = getDirector(); 194 Time currentTime = director.getModelTime(); 195 196 // On the first firing at the time matching the _timeValueOfEnd, 197 // generate a new random number for the end of the current interval. 198 // FIXME: NO! This might be a speculative execution at that time!!! 199 if (currentTime.equals(_timeOfValueAtEnd)) { 200 // Generate a random number for the _end_ of the current 201 // integration period. This will update _timeOfValueAtEnd. 202 _generateRandomNumber(); 203 director.fireAt(this, _timeOfValueAtEnd); 204 } 205 return result; 206 } 207 208 /////////////////////////////////////////////////////////////////// 209 //// protected methods //// 210 211 /** Generate a new random number. 212 * @return A random number. 213 * @exception IllegalActionException If parameter values are incorrect. 214 */ 215 protected double _generateGaussian() throws IllegalActionException { 216 double standardDeviationValue = ((DoubleToken) standardDeviation 217 .getToken()).doubleValue(); 218 double rawNum = _random.nextGaussian(); 219 return rawNum * standardDeviationValue; 220 } 221 222 /** Generate a new random number. This gets called in initialize() 223 * and in the first fire() method of an iteration. It produces a number 224 * that is to be the random number at the <i>end</i> of the current 225 * iteration. 226 * @exception IllegalActionException If parameter values are incorrect. 227 */ 228 @Override 229 protected void _generateRandomNumber() throws IllegalActionException { 230 Director director = getDirector(); 231 if (!(director instanceof ContinuousDirector)) { 232 throw new IllegalActionException(this, director, 233 "WhiteNoise actor is designed to work with ContinuousDirector," 234 + " but the director is " + director.getClass()); 235 } 236 _valueAtStart = _valueAtEnd; 237 _timeOfValueAtStart = _timeOfValueAtEnd; 238 super._generateRandomNumber(); 239 _valueAtEnd = _current; 240 241 double period = 1.0 242 / ((DoubleToken) bandwidth.getToken()).doubleValue(); 243 _timeOfValueAtEnd = _timeOfValueAtStart.add(period); 244 } 245 246 /////////////////////////////////////////////////////////////////// 247 //// private variables //// 248 249 /** Time associated with the second random number of the current interval. */ 250 private Time _timeOfValueAtEnd; 251 252 /** Time associated with the first random number of the current interval. */ 253 private Time _timeOfValueAtStart; 254 255 /** The random number at the start of the current interval. */ 256 private double _valueAtStart; 257 258 /** The random number at the end of the end of the current interval. */ 259 private double _valueAtEnd; 260}