001/* Linear Difference Equation System.
002
003 @Copyright (c) 1998-2014 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION 2
026 COPYRIGHTENDKEY
027 */
028package ptolemy.actor.lib;
029
030import ptolemy.actor.TypedIOPort;
031import ptolemy.data.DoubleMatrixToken;
032import ptolemy.data.Token;
033import ptolemy.data.expr.Parameter;
034import ptolemy.data.type.BaseType;
035import ptolemy.kernel.CompositeEntity;
036import ptolemy.kernel.util.Attribute;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.NameDuplicationException;
039
040///////////////////////////////////////////////////////////////////
041//// LinearDifferenceEquationSystem
042
043/**
044 Linear Difference Equation System.
045
046 <p>The linear state-space model implements a system whose behavior
047 is defined by:
048
049 <pre>
050 x(k+1) = Ax(k) + Bu(k)
051 y(k) = Cx(k) + Du(k)
052 x(0) = x0
053 </pre>
054
055 where x is the state vector, u is the input vector, and y is the
056 output vector. (Note that in Ptolemy II, vectors are double matrices
057 with one column or one row.) The matrix coefficients must have the
058 following characteristics:
059
060 <pre>
061 A must be an n-by-n matrix, where n is the number of states.
062 B must be an n-by-m matrix, where m is the number of inputs.
063 C must be an r-by-n matrix, where r is the number of outputs.
064 D must be an r-by-m matrix.
065 </pre>
066
067 For each firing, the actor accepts one input DoubleMatrixToken of
068 dimension <i>m</i> x 1, and generates one output DoubleMatrixToken of
069 dimension <i>r</i> x 1.
070
071 <P>
072 In addition to producing the output <i>y</i> through port
073 <i>output</i>, the actor also produce the state values <i>x</i>
074 through port <i>state</i>.
075
076 @author Jie Liu and Elaine Cheong
077 @version $Id$
078 @since Ptolemy II 2.0
079 @Pt.ProposedRating Yellow (celaine)
080 @Pt.AcceptedRating Yellow (celaine)
081 */
082public class LinearDifferenceEquationSystem extends Transformer {
083    /** Construct an actor with the given container and name.
084     *  @param container The container.
085     *  @param name The name of this actor.
086     *  @exception IllegalActionException If the actor cannot be contained
087     *   by the proposed container.
088     *  @exception NameDuplicationException If the container already has an
089     *   actor with this name.
090     */
091    public LinearDifferenceEquationSystem(CompositeEntity container,
092            String name)
093            throws IllegalActionException, NameDuplicationException {
094        super(container, name);
095        input.setMultiport(false);
096        output.setMultiport(false);
097        state = new TypedIOPort(this, "state", false, true);
098
099        A = new Parameter(this, "A");
100        A.setExpression("[1.0]");
101        A.setTypeEquals(BaseType.DOUBLE_MATRIX);
102
103        B = new Parameter(this, "B");
104        B.setExpression("[1.0]");
105        B.setTypeEquals(BaseType.DOUBLE_MATRIX);
106
107        C = new Parameter(this, "C");
108        C.setExpression("[1.0]");
109        C.setTypeEquals(BaseType.DOUBLE_MATRIX);
110
111        D = new Parameter(this, "D");
112        D.setExpression("[0.0]");
113        D.setTypeEquals(BaseType.DOUBLE_MATRIX);
114
115        initialStates = new Parameter(this, "initialStates");
116        initialStates.setExpression("[0.0]");
117        initialStates.setTypeEquals(BaseType.DOUBLE_MATRIX);
118
119        double[][] zero = { { 0.0 } };
120        _x = new DoubleMatrixToken(zero);
121        _initialStateChanged = true;
122
123        // icon
124        _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-75\" y=\"-30\" "
125                + "width=\"150\" height=\"60\" " + "style=\"fill:white\"/>\n"
126                + "<text x=\"-70\" y=\"-10\" " + "style=\"font-size:14\">\n"
127                + "x(k+1) = Ax(k) + Bu(k) " + "</text>\n"
128                + "<text x=\"-70\" y=\"10\" " + "style=\"font-size:14\">\n"
129                + "    y(k) = Cx(k) + Du(k)" + "</text>\n" + "</svg>\n");
130    }
131
132    ///////////////////////////////////////////////////////////////////
133    ////                     ports and parameters                  ////
134
135    /** Output port that produces DoubleMatrixToken of dimension
136     *  <i>r</i> x 1 (see class comment).
137     */
138    public TypedIOPort state;
139
140    /** The A matrix in the state-space representation. It must be a
141     *  square matrix.
142     *  The default value is [[1.0]].
143     */
144    public Parameter A;
145
146    /** The B matrix in the state-space representation. The number of
147     *  rows must be equal to the number of rows of the A matrix. The
148     *  number of columns must be equal to the number of rows in the
149     *  input token.  The default value is [[1.0]].
150     */
151    public Parameter B;
152
153    /** The C matrix in the state-space representation. The number of
154     *  columns must be equal to the number of columns of the A
155     *  matrix.  The number of rows must be equal to the number of
156     *  columns in the output token. The default value is [[0.0]].
157     */
158    public Parameter C;
159
160    /** The D matrix in the state-space representation. The number of
161     *  columns must be equal to the number of rows in the input token
162     *  (a DoubleMatrixToken of dimension <i>m</i> x 1.  The number of
163     *  rows must be equal to the number of columns in the output
164     *  token (a DoubleMatrixToken of dimension <i>r</i> x 1.  The
165     *  default value is [[0.0]].
166     */
167    public Parameter D;
168
169    /** The initial condition for the state variables. This must be a
170     *  column vector (double matrix with only one column) whose
171     *  length is equal to the number of state variables.  The default
172     *  value is [0.0].
173     *
174     *  NOTE: Changes to this parameter will be * applied at the next
175     *  time when fire() is called.
176     */
177    public Parameter initialStates;
178
179    ///////////////////////////////////////////////////////////////////
180    ////                         public methods                    ////
181
182    /** If the argument is <i>A, B, C, D</i> or <i>initialStates</i>
183     *  parameters, check that they are indeed matrices and vectors,
184     *  and request initialization from the director if there is one.
185     *  Other sanity checks like the dimensions of the matrices will
186     *  be done in the preinitialize() method.
187     *  @param attribute The attribute that changed.
188     *  @exception IllegalActionException If the numerator and the
189     *   denominator matrix is not a row vector.
190     */
191    @Override
192    public void attributeChanged(Attribute attribute)
193            throws IllegalActionException {
194        if (attribute == A) {
195            // Check that it is a square matrix.
196            DoubleMatrixToken token = (DoubleMatrixToken) A.getToken();
197
198            if (token.getRowCount() == 0 || token.getColumnCount() == 0
199                    || token.getRowCount() != token.getColumnCount()) {
200                throw new IllegalActionException(this,
201                        "The A matrix must be a nonempty square matrix.");
202            }
203        } else if (attribute == B) {
204            // Check that B is a matrix.
205            DoubleMatrixToken token = (DoubleMatrixToken) B.getToken();
206
207            if (token.getRowCount() == 0 || token.getColumnCount() == 0) {
208                throw new IllegalActionException(this,
209                        "The B matrix must be a nonempty matrix.");
210            }
211        } else if (attribute == C) {
212            // Check that C is a matrix.
213            DoubleMatrixToken token = (DoubleMatrixToken) C.getToken();
214
215            if (token.getRowCount() == 0 || token.getColumnCount() == 0) {
216                throw new IllegalActionException(this,
217                        "The C matrix must be a nonempty matrix.");
218            }
219        } else if (attribute == D) {
220            DoubleMatrixToken token = (DoubleMatrixToken) D.getToken();
221
222            if (token.getRowCount() == 0 || token.getColumnCount() == 0) {
223                throw new IllegalActionException(this,
224                        "The D matrix must be a nonempty matrix.");
225            }
226        } else if (attribute == initialStates) {
227            // The initialStates parameter should be a row vector.
228            DoubleMatrixToken token = (DoubleMatrixToken) initialStates
229                    .getToken();
230
231            if (token.getColumnCount() != 1 || token.getRowCount() < 1) {
232                throw new IllegalActionException(this,
233                        "The initialStates must be a column vector.");
234            }
235
236            _initialStateChanged = true;
237        } else {
238            super.attributeChanged(attribute);
239        }
240    }
241
242    /** Consume the input token, compute the system response, and
243     *  produce outputs. Notice that the state is updated in
244     *  postfire. That is, if fire() is called multiple times before
245     *  postfire() is called, this actor will use the same internal
246     *  state to compute the outputs.
247     *  @exception IllegalActionException If the get() or send() methods
248     *   of the ports throw this exception.
249     */
250    @Override
251    public void fire() throws IllegalActionException {
252        super.fire();
253        if (input.hasToken(0)) {
254            Token u = input.get(0);
255            Token y = C.getToken().multiply(_x).add(D.getToken().multiply(u));
256            _xPrime = A.getToken().multiply(_x).add(B.getToken().multiply(u));
257
258            if (_singleOutput) {
259                output.send(0, ((DoubleMatrixToken) y).getElementAsToken(0, 0));
260            } else {
261                output.send(0, y);
262            }
263
264            if (_singleState) {
265                state.send(0, ((DoubleMatrixToken) _x).getElementAsToken(0, 0));
266            } else {
267                state.send(0, _x);
268            }
269        }
270    }
271
272    /** Update the internal state.
273     *  @exception IllegalActionException If thrown by the super class.
274     */
275    @Override
276    public boolean postfire() throws IllegalActionException {
277        if (super.postfire()) {
278            _x = _xPrime;
279            return true;
280        } else {
281            return false;
282        }
283    }
284
285    /** If the parameter <i>initialStates</i> has changed, then update
286     *  the internal state of this actor to be the value of the
287     *  <i>initialStates</i> parameter.
288     *  @exception IllegalActionException If <i>initialStates</i>
289     *  parameter is invalid, or if the base class throws it.
290     */
291    @Override
292    public boolean prefire() throws IllegalActionException {
293        super.prefire();
294
295        if (_initialStateChanged) {
296            _x = initialStates.getToken();
297            _initialStateChanged = false;
298        }
299
300        if (input.hasToken(0)) {
301            return true;
302        } else {
303            return false;
304        }
305    }
306
307    /** Check the dimension of all parameters. If the system needs
308     *  multiple inputs, then set the type of the <i>input</i> port to
309     *  be DoubleMatrix; otherwise set the type to Double.  Similarly,
310     *  for the output ports <i>output</i> and <i>state</i>, if the
311     *  system needs multiple outputs, then set the type of the port
312     *  to be DoubleMatrix; otherwise set the type to Double.
313     *  @exception IllegalActionException If the dimensions do not
314     *  match.
315     */
316    @Override
317    public void preinitialize() throws IllegalActionException {
318        super.preinitialize();
319
320        DoubleMatrixToken a = (DoubleMatrixToken) A.getToken();
321        int n = a.getRowCount();
322        DoubleMatrixToken b = (DoubleMatrixToken) B.getToken();
323
324        if (b.getRowCount() != n) {
325            throw new IllegalActionException(this,
326                    "The number of rows of the B matrix should equal to "
327                            + "the number of rows of the A matrix.");
328        }
329
330        if (n == 1) {
331            _singleState = true;
332            state.setTypeEquals(BaseType.DOUBLE);
333        } else {
334            _singleState = false;
335            state.setTypeEquals(BaseType.DOUBLE_MATRIX);
336        }
337
338        int m = b.getColumnCount();
339
340        if (m == 1) {
341            input.setTypeEquals(BaseType.DOUBLE);
342        } else {
343            input.setTypeEquals(BaseType.DOUBLE_MATRIX);
344        }
345
346        DoubleMatrixToken c = (DoubleMatrixToken) C.getToken();
347
348        if (c.getColumnCount() != n) {
349            throw new IllegalActionException(this,
350                    "The number of columns of the C matrix should equal to "
351                            + "the number of rows of the A matrix.");
352        }
353
354        int r = c.getRowCount();
355
356        if (r == 1) {
357            _singleOutput = true;
358            output.setTypeEquals(BaseType.DOUBLE);
359        } else {
360            _singleOutput = false;
361            output.setTypeEquals(BaseType.DOUBLE_MATRIX);
362        }
363
364        DoubleMatrixToken d = (DoubleMatrixToken) D.getToken();
365
366        if (c.getRowCount() != d.getRowCount()) {
367            throw new IllegalActionException(this,
368                    "The number of rows of the D matrix should equal to "
369                            + "the number of rows of the C matrix.");
370        }
371
372        DoubleMatrixToken x0 = (DoubleMatrixToken) initialStates.getToken();
373
374        if (x0.getRowCount() != n) {
375            throw new IllegalActionException(this,
376                    "The number of initial states should equal to "
377                            + "the number of columns of the A matrix.");
378        }
379
380        // reset initial state.
381        _initialStateChanged = true;
382    }
383
384    ///////////////////////////////////////////////////////////////////
385    ////                         private variables                 ////
386    // The internal state.
387    private Token _x;
388
389    // The next state.
390    private Token _xPrime;
391
392    // Indicate whether the initial state has beed set.
393    private boolean _initialStateChanged;
394
395    // Indicate whether the output is a scalar.
396    private boolean _singleOutput;
397
398    // Indicate whether the state variable is a scalar;
399    private boolean _singleState;
400}