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}