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}