001/* A clock that keeps track of model time at a level of the model hierarchy.
002
003 Copyright (c) 2012-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 */
027package ptolemy.actor;
028
029import java.util.Collection;
030
031import ptolemy.actor.parameters.SharedParameter;
032import ptolemy.actor.util.Time;
033import ptolemy.data.DoubleToken;
034import ptolemy.data.expr.Parameter;
035import ptolemy.data.type.BaseType;
036import ptolemy.kernel.util.AbstractSettableAttribute;
037import ptolemy.kernel.util.Attribute;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.NamedObj;
041import ptolemy.kernel.util.Settable;
042import ptolemy.kernel.util.ValueListener;
043import ptolemy.kernel.util.Workspace;
044import ptolemy.math.ExtendedMath;
045
046/** A clock that keeps track of model time at a level of the model hierarchy
047 *  and relates it to the time of the enclosing model, if there is one. The time
048 *  of the enclosing model is referred to as the environment time. This
049 *  clock has a notion of local time and committed time. The committed time
050 *  is "simultaneous" with the environment time.
051 *
052 *  <p>The local time is
053 *  not allowed to move backwards past the committed time, but ahead
054 *  of that time, it can move around at will. </p>
055 *  <p>
056 *  There is no way of explicitly committing time, but
057 *  several methods have the side effect of committing the current
058 *  local time. For example, {@link #setClockDrift(double)} will commit
059 *  the current local time and change the clock drift.  So will
060 *  {@link #start()} and {@link #stop()} </p>
061 *
062 *  <p>
063 *  This class implements the AbstractSettableAttribute interface because
064 *  we want the localClock to be shown as a parameter in the editor
065 *  dialogue of a director. A better implementation would be to derive
066 *  LocalClock from Attribute and make changes to vergil such that
067 *  Attributes are displayed in the dialogue, however, for the moment,
068 *  the required changes are too complex.
069 *  The value of the clock is exposed as an attribute that, by default,
070 *  is non editable. The clock drift is a contained attribute that can
071 *  be modified. </p>
072 *
073 *  <p> This class also specifies a <i>globalTimeResolution</i>
074 *  parameter. This is a double with default 1E-10, which is
075 *  10<sup>-10</sup>.  All time values are rounded to the nearest
076 *  multiple of this value. If the value is changed during a run, an
077 *  exception is thrown.  This is a shared parameter, which means that
078 *  all instances of Director in the model will have the same value
079 *  for this parameter. Changing one of them changes all of them. </p>
080 *
081 *  <p>FIXME: Setting of clock drift must be controlled because it commits
082 *  time. </p>
083 *
084 * @author Ilge Akkaya, Patricia Derler, Edward A. Lee, Christos Stergiou, Michael Zimmer
085 * @version $Id$
086 * @since Ptolemy II 10.0
087 * @Pt.ProposedRating yellow (eal)
088 * @Pt.AcceptedRating red (eal)
089 */
090public class LocalClock extends AbstractSettableAttribute {
091
092    /** Construct an attribute with the given name contained by the specified
093     *  entity. The container argument must not be null, or a
094     *  NullPointerException will be thrown.  This attribute will use the
095     *  workspace of the container for synchronization and version counts.
096     *  If the name argument is null, then the name is set to the empty string.
097     *  Increment the version of the workspace.
098     *  @param container The container.
099     *  @param name The name of this attribute.
100     *  @exception IllegalActionException If the attribute is not of an
101     *   acceptable class for the container, or if the name contains a period.
102     *  @exception NameDuplicationException If the name coincides with
103     *   an attribute already in the container.
104     */
105    public LocalClock(NamedObj container, String name)
106            throws IllegalActionException, NameDuplicationException {
107        super(container, name);
108        globalTimeResolution = new SharedParameter(this, "globalTimeResolution",
109                null, "1E-10");
110
111        clockDrift = new Parameter(this, "clockRate");
112        clockDrift.setExpression("1.0");
113        clockDrift.setTypeEquals(BaseType.DOUBLE);
114
115        // Make sure getCurrentTime() never returns null.
116        _localTime = Time.NEGATIVE_INFINITY;
117        _drift = 1.0;
118        _visibility = Settable.NOT_EDITABLE;
119    }
120
121    ///////////////////////////////////////////////////////////////////
122    ////                         parameters                        ////
123
124    /** The time precision used by this director. All time values are
125     *  rounded to the nearest multiple of this number. This is a double
126     *  that defaults to "1E-10" which is 10<sup>-10</sup>.
127     *  This is a shared parameter, meaning that changing one instance
128     *  in a model results in all instances being changed.
129     */
130    public SharedParameter globalTimeResolution;
131
132    /** The drift of the local clock with respect to the environment
133     *  clock. If this is a top level director the clock drift has no
134     *  consequence. The value is a double that is initialized to
135     *  1.0 which means that the local clock drift matches the one
136     *  of the environment.
137     */
138    public Parameter clockDrift;
139
140    ///////////////////////////////////////////////////////////////////
141    ////                         public method                     ////
142
143    /** This method has to be implemented for the AbstractSettableAttribute
144     *  interface. This interface is only needed for the LocalClock to
145     *  show up in the configuration dialogue of the container (the director).
146     *  The method will not be used for this class so the implementation
147     *  is empty.
148     *  @param listener The listener to be added.
149     *  @see #removeValueListener(ValueListener)
150     */
151    @Override
152    public void addValueListener(ValueListener listener) {
153        // nothing to do.
154    }
155
156    /** Delegate the call to the director, which handles changes
157     *  to the parameters of the clock.
158     *  @param attribute The attribute that changed.
159     *  @exception IllegalActionException If the director throws it.
160     */
161    @Override
162    public void attributeChanged(Attribute attribute)
163            throws IllegalActionException {
164        if (attribute == clockDrift) {
165            double drift;
166            drift = ((DoubleToken) clockDrift.getToken()).doubleValue();
167            if (drift != getClockDrift()) {
168                setClockDrift(drift);
169            }
170        } else if (attribute == globalTimeResolution) {
171            // This is extremely frequently used, so cache the value.
172            // Prevent this from changing during a run!
173            double newResolution = ((DoubleToken) globalTimeResolution
174                    .getToken()).doubleValue();
175
176            // FindBugs reports this comparison as a problem, but it
177            // is not an issue because we usually don't calculate
178            // _timeResolution, we set it.
179            if (newResolution != getTimeResolution()) {
180                NamedObj container = getContainer().getContainer();
181
182                if (container instanceof Actor) {
183                    Manager manager = ((Actor) container).getManager();
184
185                    if (manager != null) {
186                        Manager.State state = manager.getState();
187
188                        if (state != Manager.IDLE
189                                && state != Manager.PREINITIALIZING) {
190                            throw new IllegalActionException(this,
191                                    "Cannot change timePrecision during a run.");
192                        }
193                    }
194                }
195
196                if (newResolution <= ExtendedMath.DOUBLE_PRECISION_SMALLEST_NORMALIZED_POSITIVE_DOUBLE) {
197                    throw new IllegalActionException(this,
198                            "Invalid timeResolution: " + newResolution
199                                    + "\n The value must be "
200                                    + "greater than the smallest, normalized, "
201                                    + "positive, double value with a double "
202                                    + "precision: "
203                                    + ExtendedMath.DOUBLE_PRECISION_SMALLEST_NORMALIZED_POSITIVE_DOUBLE);
204                }
205
206                setTimeResolution(newResolution);
207            }
208        }
209        super.attributeChanged(attribute);
210    }
211
212    /** Clone the object into the specified workspace.
213     *  @param workspace The workspace for the cloned object.
214     *  @return The cloned object.
215     *  @exception CloneNotSupportedException If thrown by super class.
216     */
217    @Override
218    public Object clone(Workspace workspace) throws CloneNotSupportedException {
219        LocalClock newObject = (LocalClock) super.clone(workspace);
220        newObject._localTime = Time.NEGATIVE_INFINITY;
221        newObject._offset = ((Director) getContainer())._zeroTime;
222        newObject._drift = 1.0;
223        return newObject;
224    }
225
226    /** Get clock drift.
227     *  @return The clock drift.
228     *  @see #setClockDrift(double)
229     */
230    public double getClockDrift() {
231        // FIXME: This returns a double, what does 1.0 mean?  0.0?
232        return _drift;
233    }
234
235    /** Get the environment time that corresponds to the given local time.
236     *  The given local time is required to be either equal to or
237     *  greater than the committed time when this method is called.
238     *  @param time The local Time.
239     *  @return The corresponding environment Time.
240     *  @exception IllegalActionException If the specified local time
241     *   is in the past, or if Time objects cannot be created.
242     */
243    public Time getEnvironmentTimeForLocalTime(Time time)
244            throws IllegalActionException {
245        if (time.compareTo(_lastCommitLocalTime) < 0) {
246            throw new IllegalActionException(
247                    "Cannot compute environment time for local time " + time
248                            + " because "
249                            + "the last commit of the local time occurred at "
250                            + "local time " + _lastCommitLocalTime);
251        }
252        Time localTimePassedSinceCommit = time.subtract(_lastCommitLocalTime);
253        Time environmentTimePassedSinceCommit = localTimePassedSinceCommit;
254        if (_drift != 1.0) {
255            double environmentTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommit
256                    .getDoubleValue();
257            environmentTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommitDoubleValue
258                    / _drift;
259            environmentTimePassedSinceCommit = new Time(
260                    (Director) getContainer(),
261                    environmentTimePassedSinceCommitDoubleValue);
262        }
263        Time environmentTime = _lastCommitEnvironmentTime
264                .add(environmentTimePassedSinceCommit);
265        return environmentTime;
266    }
267
268    /** Return the local time.
269     *  @return The local time as a string value.
270     */
271    @Override
272    public String getExpression() {
273        if (_localTime == null) {
274            return "";
275        } else {
276            return String.valueOf(_localTime);
277        }
278    }
279
280    /** Get current local time. If it has never been set, then this will return
281     *  Time.NEGATIVE_INFINITY. The returned value may have been set by
282     *  {@link #setLocalTime(Time)}.
283     *  @return The current local time.
284     *  @see #setLocalTime(Time)
285     */
286    public Time getLocalTime() {
287        return _localTime;
288    }
289
290    /** Get the local time that corresponds to the current environment time.
291     *  The current environment time is required to be greater than or equal
292     *  to the environment time corresponding to the last committed local time.
293     *  @return The corresponding local time.
294     *  @exception IllegalActionException If Time objects cannot be created, or
295     *   if the current environment time is less than the time
296     *   corresponding to the last committed local time.
297     */
298    public Time getLocalTimeForCurrentEnvironmentTime()
299            throws IllegalActionException {
300        return getLocalTimeForEnvironmentTime(
301                ((Director) getContainer()).getEnvironmentTime());
302    }
303
304    /** Get the local time that corresponds to the given environment time.
305     *  The given environment time is required to be greater than or equal
306     *  to the environment time corresponding to the last committed local time.
307     *  @param time The environment time.
308     *  @return The corresponding local time.
309     *  @exception IllegalActionException If the specified environment time
310     *   is less than the environment time corresponding to the last
311     *   committed local time, or if Time objects cannot be created.
312     */
313    public Time getLocalTimeForEnvironmentTime(Time time)
314            throws IllegalActionException {
315        if (_lastCommitEnvironmentTime == null
316                || time.compareTo(_lastCommitEnvironmentTime) < 0) {
317            throw new IllegalActionException(
318                    "Cannot compute local time for environment time " + time
319                            + " because "
320                            + "the last commit of the local time occurred at "
321                            + "local time " + _lastCommitLocalTime + " which "
322                            + "corresponds to environment time "
323                            + _lastCommitEnvironmentTime);
324        }
325
326        Time environmentTimePassedSinceCommit = time
327                .subtract(_lastCommitEnvironmentTime);
328        Time localTimePassedSinceCommit = environmentTimePassedSinceCommit;
329        if (_drift != 1.0) {
330            double localTimePassedSinceCommitDoubleValue = environmentTimePassedSinceCommit
331                    .getDoubleValue();
332            localTimePassedSinceCommitDoubleValue = localTimePassedSinceCommitDoubleValue
333                    * _drift;
334            localTimePassedSinceCommit = new Time((Director) getContainer(),
335                    localTimePassedSinceCommitDoubleValue);
336        }
337        Time localTime = _lastCommitEnvironmentTime.subtract(_offset)
338                .add(localTimePassedSinceCommit);
339        return localTime;
340    }
341
342    /** Get the time resolution of the model. The time resolution is
343     *  the value of the <i>timeResolution</i> parameter. This is the
344     *  smallest time unit for the model.
345     *  @return The time resolution of the model.
346     *  @see #setTimeResolution(double)
347     */
348    public final double getTimeResolution() {
349        // This method is final for performance reason.
350        return _timeResolution;
351    }
352
353    /** The LocalClock is not editable, thus visibility is
354     *  always set to NOT_EDITABLE.
355     *  @return NOT_EDITABLE.
356     *  @see #setVisibility(Visibility)
357     */
358    @Override
359    public Visibility getVisibility() {
360        return _visibility;
361    }
362
363    /** Initialize parameters that cannot be initialized in the
364     *  constructor. For instance, Time objects cannot be created
365     *  in the constructor because the time resolution might not be
366     *  known yet. Older models have the timeResolution parameter
367     *  specified in the director which will only be loaded by the
368     *  MOMLParser after the director is initialized.
369     */
370    public void initialize() {
371        _offset = ((Director) getContainer())._zeroTime;
372    }
373
374    /** This method has to be implemented for the AbstractSettableAttribute
375     *  interface. This interface is only needed for the LocalClock to
376     *  show up in the configuration dialogue of the container (the director).
377     *  The method will not be used for this class so the implementation
378     *  is empty.
379     *  @param listener The listener to be removed.
380     *  @see #addValueListener(ValueListener)
381     */
382    @Override
383    public void removeValueListener(ValueListener listener) {
384        // nothing to do.
385    }
386
387    /** Set local time and commit.
388     *  This is allowed to set time earlier than the
389     *  last committed local time.
390     *  @param time The new local time.
391     */
392    public void resetLocalTime(Time time) {
393        if (_debugging) {
394            _debug("reset local time to " + time);
395        }
396        _localTime = time;
397        _commit();
398    }
399
400    /** Set the new clock drift and commit it.
401     *  @param drift New clock drift.
402     *  @exception IllegalActionException If the specified drift is
403     *   non-positive.
404     *  @see #getClockDrift()
405     */
406    public void setClockDrift(double drift) throws IllegalActionException {
407        // FIXME: This returns a double, what does 1.0 mean?  0.0?
408        if (drift <= 0.0) {
409            throw new IllegalActionException(getContainer(),
410                    "Illegal clock drift: " + drift
411                            + ". Clock drift is required to be positive.");
412        }
413        _drift = drift;
414        _commit();
415    }
416
417    /** Set local time without committing.
418     *  This is not allowed to set
419     *  time earlier than the last committed local time.
420     *  @param time The new local time.
421     *  @exception IllegalActionException If the specified time is
422     *   earlier than the current time.
423     *  @see #getLocalTime()
424     */
425    public void setLocalTime(Time time) throws IllegalActionException {
426        if (_lastCommitLocalTime != null
427                && time.compareTo(_lastCommitLocalTime) < 0) {
428            throw new IllegalActionException(getContainer(),
429                    "Cannot set local time to " + time
430                            + ", which is earlier than the last committed current time "
431                            + _lastCommitLocalTime);
432        }
433        _localTime = time;
434    }
435
436    /** Set time resolution.
437     *  @param timeResolution The new time resolution.
438     *  @see #getTimeResolution()
439     */
440    public void setTimeResolution(double timeResolution) {
441        _timeResolution = timeResolution;
442    }
443
444    /** This method has to be implemented for the AbstractSettableAttribute
445     *  interface. This interface is only needed for the LocalClock to
446     *  show up in the configuration dialogue of the container (the director).
447     *  This method does not do anything because visibility is always
448     *  NOT_EDITABLE.
449     *  @param visibility The new visibility.
450     *  @see #getVisibility()
451     */
452    @Override
453    public void setVisibility(Visibility visibility) {
454        _visibility = visibility;
455    }
456
457    /** Start the clock with the current drift as specified by the
458     *  last call to {@link #setClockDrift(double)}.
459     *  If {@link #setClockDrift(double)} has never been called, then
460     *  the drift is 1.0.
461     *  This method commits current local time.
462     */
463    public void start() {
464        _commit();
465    }
466
467    /** Stop the clock. The current time will remain the
468     *  same as its current value until the next call to
469     *  {@link #start()}.
470     *  This method commits current local time.
471     */
472    public void stop() {
473        _commit();
474    }
475
476    /** This method has to be implemented for the AbstractSettableAttribute
477     *  interface. This interface is only needed for the LocalClock to
478     *  show up in the configuration dialogue of the container (the director).
479     *  The value of the LocalClock does not need validation, thus this method
480     *  does not do anything.
481     *  @return Null.
482     *  @exception IllegalActionException Not thrown in this base class.
483     */
484    @Override
485    public Collection validate() throws IllegalActionException {
486        return null;
487    }
488
489    ///////////////////////////////////////////////////////////////////
490    ////                         private methods                   ////
491
492    /** Commit the current local time.
493     */
494    private void _commit() {
495        if (_offset == null || _localTime == null) { // not initialized.
496            return;
497        }
498        // skip if local time has never been set.
499        if (_localTime != Time.NEGATIVE_INFINITY) {
500            Time environmentTime = ((Director) getContainer())
501                    .getEnvironmentTime();
502            if (environmentTime == null) {
503                _offset = ((Director) getContainer())._zeroTime;
504            } else {
505                _offset = environmentTime.subtract(_localTime);
506            }
507            _lastCommitEnvironmentTime = environmentTime;
508            _lastCommitLocalTime = _localTime;
509        }
510    }
511
512    ///////////////////////////////////////////////////////////////////
513    ////                         private variable                  ////
514
515    /** The current time of this clock. */
516    private Time _localTime;
517
518    /** The current clock drift.
519     *  The drift is initialized to 1.0 which means that the
520     *  local time matches to the environment time.
521     */
522    private double _drift;
523
524    /** The environment time at which a change to local time, drift,
525     *  or resumption occurred.
526     */
527    private Time _lastCommitEnvironmentTime;
528
529    /** The local time at which a change to local time, drift,
530     *  or resumption occurred.
531     */
532    private Time _lastCommitLocalTime;
533
534    /** The environment time minus the local time at the the point
535     *  at which a commit occurred.
536     *  By default, the offset is zero.
537     */
538    private Time _offset;
539
540    private Visibility _visibility;
541
542    /** Time resolution cache, with a reasonable default value. */
543    private double _timeResolution = 1E-10;
544
545}