001/* Linear state space model in the CT domain. 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 java.util.Iterator; 031 032import ptolemy.actor.Actor; 033import ptolemy.actor.Director; 034import ptolemy.actor.IORelation; 035import ptolemy.actor.TypedCompositeActor; 036import ptolemy.actor.TypedIOPort; 037import ptolemy.actor.TypedIORelation; 038import ptolemy.actor.lib.AddSubtract; 039import ptolemy.actor.lib.Scale; 040import ptolemy.data.DoubleMatrixToken; 041import ptolemy.data.expr.Parameter; 042import ptolemy.data.type.BaseType; 043import ptolemy.kernel.CompositeEntity; 044import ptolemy.kernel.util.Attribute; 045import ptolemy.kernel.util.IllegalActionException; 046import ptolemy.kernel.util.InternalErrorException; 047import ptolemy.kernel.util.NameDuplicationException; 048import ptolemy.kernel.util.Workspace; 049 050/////////////////////////////////////////////////////////////////// 051//// LinearStateSpace 052 053/** 054 Linear state space model in the CT domain. 055 056 <p>The State-Space model implements a system whose behavior is defined by: 057 <pre> 058 dx/dt = Ax + Bu 059 y = Cx + Du 060 x(0) = x0 061 </pre> 062 where x is the state vector, u is the input vector, and y is the output 063 vector. The matrix coefficients must have the following characteristics: 064 <pre> 065 A must be an n-by-n matrix, where n is the number of states. 066 B must be an n-by-m matrix, where m is the number of inputs. 067 C must be an r-by-n matrix, where r is the number of outputs. 068 D must be an r-by-m matrix. 069 </pre> 070 The actor accepts <i>m</i> inputs and generates <i>r</i> outputs 071 through a multi-input port and a multi-output port. The widths of the 072 ports must match the number of rows and columns in corresponding 073 matrices, otherwise, an exception will be thrown. 074 <P> 075 This actor works like a higher-order function. It is opaque after 076 construction or the change of parameters. Upon preinitialization, 077 the actor will create a subsystem using integrators, adders, and 078 scales. After that, the actor becomes transparent, and the director 079 takes over the control of the actors contained by this actor. 080 <P> 081 This actor is based on the 082 ptolemy.domains.ct.lib.LinearStateSpace actor by Jie Liu. 083 084 @author Edward A. Lee, Contributor: Jie Liu 085 @version $Id$ 086 @since Ptolemy II 8.0 087 @Pt.ProposedRating Red (liuj) 088 @Pt.AcceptedRating Red (cxh) 089 */ 090public class LinearStateSpace extends TypedCompositeActor { 091 /** Construct the composite actor with a name and a container. 092 * This constructor creates the ports, parameters, and the icon. 093 * @param container The container. 094 * @param name The name. 095 * @exception NameDuplicationException If another entity already had 096 * this name. 097 * @exception IllegalActionException If there was an internal problem. 098 */ 099 public LinearStateSpace(CompositeEntity container, String name) 100 throws NameDuplicationException, IllegalActionException { 101 super(container, name); 102 _init(); 103 } 104 105 /** Construct a LinearStateSpace in the specified 106 * workspace with no container and an empty string as a name. You 107 * can then change the name with setName(). If the workspace 108 * argument is null, then use the default workspace. 109 * @param workspace The workspace that will list the actor. 110 * @exception IllegalActionException If the name has a period in it, or 111 * the director is not compatible with the specified container. 112 * @exception NameDuplicationException If the container already contains 113 * an entity with the specified name. 114 */ 115 public LinearStateSpace(Workspace workspace) 116 throws IllegalActionException, NameDuplicationException { 117 super(workspace); 118 _init(); 119 } 120 121 /////////////////////////////////////////////////////////////////// 122 //// ports and parameters //// 123 124 /** Multi-input port. 125 */ 126 public TypedIOPort input; 127 128 /** Multi-output port. 129 */ 130 public TypedIOPort output; 131 132 /** State output multiport. 133 */ 134 public TypedIOPort stateOutput; 135 136 /** The A matrix in the state-space representation. It must be a 137 * square matrix. 138 * The default value is [[1.0]]. 139 */ 140 public Parameter A; 141 142 /** The B matrix in the state-space representation. The number of 143 * rows must equal to the number of rows of the A matrix. The number 144 * of columns must equal to the width of the input port. 145 * The default value is [[1.0]]. 146 */ 147 public Parameter B; 148 149 /** The C matrix in the state-space representation. The number of 150 * columns must equal to the number of columns of the A matrix. 151 * The number of rows must equal to the width of the output port. 152 * The default value is [[1.0]]. 153 */ 154 public Parameter C; 155 156 /** The D matrix in the state-space representation. The number of 157 * columns must equal to the width of the input port. 158 * The number of rows must equal to the width of the output port. 159 * The default value is [[0.0]]. 160 */ 161 public Parameter D; 162 163 /** The initial condition for the state variables. This must be 164 * a vector (double matrix with only one row) whose length equals 165 * to the number of state variables. 166 * The default value is [0.0]. 167 */ 168 public Parameter initialStates; 169 170 /////////////////////////////////////////////////////////////////// 171 //// public methods //// 172 173 /** If the argument is <i>A, B, C, D</i> or <i>initialState</i> 174 * parameters, check that they are indeed matrices and vectors, 175 * and request for initialization from the director if there is one. 176 * Other sanity checks like the dimensions of the matrices will 177 * be done in the preinitialize() method. 178 * @param attribute The attribute that changed. 179 * @exception IllegalActionException If the numerator and the 180 * denominator matrix is not a row vector. 181 */ 182 @Override 183 public void attributeChanged(Attribute attribute) 184 throws IllegalActionException { 185 if (attribute == A) { 186 // Check that it is a square matrix. 187 DoubleMatrixToken token = (DoubleMatrixToken) A.getToken(); 188 189 if (token.getRowCount() == 0 || token.getColumnCount() == 0 190 || token.getRowCount() != token.getColumnCount()) { 191 throw new IllegalActionException(this, 192 "The A matrix must be a nonempty square matrix."); 193 } 194 195 _requestInitialization = true; 196 } else if (attribute == B) { 197 // Check that B is a matrix. 198 DoubleMatrixToken token = (DoubleMatrixToken) B.getToken(); 199 200 if (token.getRowCount() == 0 || token.getColumnCount() == 0) { 201 throw new IllegalActionException(this, 202 "The B matrix must be a nonempty matrix."); 203 } 204 205 _requestInitialization = true; 206 } else if (attribute == C) { 207 // Check that C is a matrix. 208 DoubleMatrixToken token = (DoubleMatrixToken) C.getToken(); 209 210 if (token.getRowCount() == 0 || token.getColumnCount() == 0) { 211 throw new IllegalActionException(this, 212 "The C matrix must be a nonempty matrix."); 213 } 214 215 _requestInitialization = true; 216 } else if (attribute == D) { 217 DoubleMatrixToken token = (DoubleMatrixToken) D.getToken(); 218 219 if (token.getRowCount() == 0 || token.getColumnCount() == 0) { 220 throw new IllegalActionException(this, 221 "The D matrix must be a nonempty matrix."); 222 } 223 224 _requestInitialization = true; 225 } else if (attribute == initialStates) { 226 // The initialStates parameter should be a row vector. 227 DoubleMatrixToken token = (DoubleMatrixToken) initialStates 228 .getToken(); 229 230 if (token.getRowCount() != 1 || token.getColumnCount() < 1) { 231 throw new IllegalActionException(this, 232 "The initialStates must be a row vector."); 233 } 234 235 // Changes of the initialStates parameter are ignored after 236 // the execution. 237 } else { 238 super.attributeChanged(attribute); 239 } 240 } 241 242 /** Return the executive director, regardless what isOpaque() returns. 243 * //FIXME: this is not what this method does!!! 244 * @return the executive director. 245 */ 246 @Override 247 public Director getDirector() { 248 if (_opaque) { 249 return null; 250 } else { 251 return getExecutiveDirector(); 252 } 253 } 254 255 /** Return the opaqueness of this composite actor. This actor is 256 * opaque if it has not been preinitialized after creation or 257 * changes of parameters. Otherwise, it is not opaque. 258 */ 259 @Override 260 public boolean isOpaque() { 261 return _opaque; 262 } 263 264 /** Request the reinitialization. 265 * @return True if the super class returns true. 266 * @exception IllegalActionException If thrown by super class. 267 */ 268 @Override 269 public boolean postfire() throws IllegalActionException { 270 // FIXME: the parameter change does affect the workspace version. 271 if (_requestInitialization) { 272 _requestInitialization(); 273 } 274 275 return super.postfire(); 276 } 277 278 /** Sanity check the parameters; if the parameters are legal 279 * create a continuous-time subsystem that implement the model, 280 * preinitialize all the actors in the subsystem, 281 * and set the opaqueness of this actor to true. 282 * This method needs the write access on the workspace. 283 * @exception IllegalActionException If there is no CTDirector, 284 * or any contained actors throw it in its preinitialize() method. 285 */ 286 @Override 287 public void preinitialize() throws IllegalActionException { 288 // Check parameters. 289 _checkParameters(); 290 291 // We work at the token level with out copying the matrices. 292 DoubleMatrixToken a = (DoubleMatrixToken) A.getToken(); 293 int n = a.getRowCount(); 294 DoubleMatrixToken b = (DoubleMatrixToken) B.getToken(); 295 int m = b.getColumnCount(); 296 DoubleMatrixToken c = (DoubleMatrixToken) C.getToken(); 297 int r = c.getRowCount(); 298 299 DoubleMatrixToken d = (DoubleMatrixToken) D.getToken(); 300 301 /* DoubleMatrixToken x0 = (DoubleMatrixToken)*/initialStates.getToken(); 302 303 try { 304 _workspace.getWriteAccess(); 305 306 // remove the existing model 307 removeAllEntities(); 308 removeAllRelations(); 309 310 // Create the model 311 Integrator[] integrators = new Integrator[n]; 312 IORelation[] states = new IORelation[n]; 313 314 AddSubtract[] stateAdders = new AddSubtract[n]; 315 316 // Integrators 317 for (int i = 0; i < n; i++) { 318 integrators[i] = new Integrator(this, "state_" + i); 319 integrators[i].initialState 320 .setExpression("initialStates(0," + i + ")"); 321 states[i] = new TypedIORelation(this, "relation_state_" + i); 322 integrators[i].state.link(states[i]); 323 324 // One adder per integrator. 325 stateAdders[i] = new AddSubtract(this, "stateAdder_" + i); 326 connect(stateAdders[i].output, integrators[i].derivative); 327 stateOutput.link(states[i]); 328 } 329 330 // State feedback 331 Scale[][] feedback = new Scale[n][n]; 332 333 for (int i = 0; i < n; i++) { 334 for (int j = 0; j < n; j++) { 335 // We don't create the Scale if the corresponding element 336 // in the A matrix is 0. 337 if (a.getElementAt(i, j) == 0.0) { 338 continue; 339 } 340 feedback[i][j] = new Scale(this, "feedback_" + i + "_" + j); 341 feedback[i][j].factor 342 .setExpression("A(" + i + ", " + j + ")"); 343 feedback[i][j].input.link(states[j]); 344 connect(feedback[i][j].output, stateAdders[i].plus); 345 } 346 } 347 348 // Inputs 349 Scale[][] inputScales = new Scale[n][m]; 350 IORelation[] inputs = new IORelation[m]; 351 352 for (int j = 0; j < m; j++) { 353 inputs[j] = new TypedIORelation(this, "relation_input_" + j); 354 input.link(inputs[j]); 355 356 // Create input scales. 357 for (int i = 0; i < n; i++) { 358 // We create a scale for each input even if the 359 // corresponding element in B is 0. Otherwise, 360 // if the elements of A's in this row are also zero, 361 // then we will have an illegal topology. 362 inputScales[i][j] = new Scale(this, "b_" + i + "_" + j); 363 inputScales[i][j].factor 364 .setExpression("B(" + i + ", " + j + ")"); 365 inputScales[i][j].input.link(inputs[j]); 366 connect(inputScales[i][j].output, stateAdders[i].plus); 367 } 368 } 369 370 // Outputs 371 AddSubtract[] outputAdders = new AddSubtract[r]; 372 Scale[][] outputScales = new Scale[r][n]; 373 374 for (int l = 0; l < r; l++) { 375 outputAdders[l] = new AddSubtract(this, "outputAdder" + l); 376 connect(outputAdders[l].output, output); 377 378 for (int i = 0; i < n; i++) { 379 // Create the output scales only if the corresponding 380 // 'c' element is not 0. 381 if (c.getElementAt(l, i) == 0.0) { 382 continue; 383 } 384 outputScales[l][i] = new Scale(this, 385 "outputScale_" + l + "_" + i); 386 outputScales[l][i].factor 387 .setExpression("C(" + l + ", " + i + ")"); 388 outputScales[l][i].input.link(states[i]); 389 connect(outputScales[l][i].output, outputAdders[l].plus); 390 } 391 } 392 393 // Direct feed through. 394 Scale[][] feedThrough = new Scale[r][m]; 395 396 for (int l = 0; l < r; l++) { 397 for (int j = 0; j < m; j++) { 398 // Create the scale only if the element is not 0: 399 if (d.getElementAt(l, j) == 0.0) { 400 continue; 401 } 402 feedThrough[l][j] = new Scale(this, 403 "feedThrough_" + l + "_" + j); 404 feedThrough[l][j].factor 405 .setExpression("D(" + l + ", " + j + ")"); 406 feedThrough[l][j].input.link(inputs[j]); 407 connect(feedThrough[l][j].output, outputAdders[l].plus); 408 } 409 } 410 411 _opaque = false; 412 _workspace.incrVersion(); 413 } catch (NameDuplicationException ex) { 414 // Should never happen. 415 throw new InternalErrorException("Duplicated name when " 416 + "constructing the subsystem" + ex.getMessage()); 417 } finally { 418 _workspace.doneWriting(); 419 } 420 421 // NOTE: Cannot call super.preinitialize() because the actor is not 422 // opaque. 423 // super.preinitialize(); 424 425 // preinitialize all contained actors. 426 for (Iterator i = deepEntityList().iterator(); i.hasNext();) { 427 Actor actor = (Actor) i.next(); 428 actor.preinitialize(); 429 } 430 } 431 432 /** Stop the current firing. This method overrides the stopFire() 433 * method in TypedCompositeActor base class, so that it will not 434 * invoke the local director (since there is none). This method 435 * should not be called after initialization phase, i.e. when 436 * the actor is transparent. 437 */ 438 @Override 439 public void stopFire() { 440 return; 441 } 442 443 /** Set the opaqueness back to true and call the wrapup() method 444 * of the super class. 445 * @exception IllegalActionException If there is no director. 446 */ 447 @Override 448 public void wrapup() throws IllegalActionException { 449 _opaque = true; 450 super.wrapup(); 451 } 452 453 /////////////////////////////////////////////////////////////////// 454 //// private methods //// 455 456 /** Check the dimensions of all parameters and ports. 457 * @exception IllegalActionException If the dimensions are illegal. 458 */ 459 private void _checkParameters() throws IllegalActionException { 460 DoubleMatrixToken a = (DoubleMatrixToken) A.getToken(); 461 int n = a.getRowCount(); 462 DoubleMatrixToken b = (DoubleMatrixToken) B.getToken(); 463 464 if (b.getRowCount() != n) { 465 throw new IllegalActionException(this, 466 "The number of rows of the B matrix (" + b.getRowCount() 467 + ") should be equal to " 468 + "the number of rows of the A matrix (" + n 469 + ")."); 470 } 471 472 int m = b.getColumnCount(); 473 474 if (input.getWidth() != m) { 475 throw new IllegalActionException(this, 476 "The number of columns of the B matrix (" 477 + b.getColumnCount() + ") should be equal to " 478 + "the width of the input port (" + input.getWidth() 479 + ")."); 480 } 481 482 DoubleMatrixToken c = (DoubleMatrixToken) C.getToken(); 483 484 if (c.getColumnCount() != n) { 485 throw new IllegalActionException(this, 486 "The number of columns of the C matrix (" 487 + c.getColumnCount() + ") should be equal to " 488 + "the number of rows of the A matrix (" + n 489 + ")."); 490 } 491 492 // The output width is not checked, since we may only want 493 // to use some of the outputs 494 DoubleMatrixToken d = (DoubleMatrixToken) D.getToken(); 495 496 if (c.getRowCount() != d.getRowCount()) { 497 throw new IllegalActionException(this, 498 "The number of rows of the D matrix (" + d.getRowCount() 499 + ") should be equal to " 500 + "the number of rows of the C matrix (" 501 + c.getRowCount() + ")."); 502 } 503 504 if (d.getColumnCount() != input.getWidth()) { 505 throw new IllegalActionException(this, 506 "The number of columns of the D matrix (" 507 + d.getColumnCount() + ") should be equal to " 508 + "the width of the input port (" + input.getWidth() 509 + ")."); 510 } 511 512 DoubleMatrixToken x0 = (DoubleMatrixToken) initialStates.getToken(); 513 514 if (x0.getColumnCount() != n) { 515 throw new IllegalActionException(this, 516 "The number of initial states (" + x0.getColumnCount() 517 + ") should equal to " 518 + "the number of columns of the A matrix (" + n 519 + ")."); 520 } 521 } 522 523 /** Initialize the class. */ 524 private void _init() 525 throws IllegalActionException, NameDuplicationException { 526 input = new TypedIOPort(this, "input", true, false); 527 input.setMultiport(true); 528 output = new TypedIOPort(this, "output", false, true); 529 output.setMultiport(true); 530 stateOutput = new TypedIOPort(this, "stateOutput", false, true); 531 stateOutput.setMultiport(true); 532 _opaque = true; 533 _requestInitialization = true; 534 535 double[][] one = { { 1.0 } }; 536 double[][] zero = { { 0.0 } }; 537 538 A = new Parameter(this, "A", new DoubleMatrixToken(one)); 539 A.setTypeEquals(BaseType.DOUBLE_MATRIX); 540 541 B = new Parameter(this, "B", new DoubleMatrixToken(one)); 542 B.setTypeEquals(BaseType.DOUBLE_MATRIX); 543 544 C = new Parameter(this, "C", new DoubleMatrixToken(one)); 545 C.setTypeEquals(BaseType.DOUBLE_MATRIX); 546 547 D = new Parameter(this, "D", new DoubleMatrixToken(zero)); 548 D.setTypeEquals(BaseType.DOUBLE_MATRIX); 549 550 initialStates = new Parameter(this, "initialStates", 551 new DoubleMatrixToken(zero)); 552 initialStates.setTypeEquals(BaseType.DOUBLE_MATRIX); 553 setClassName("ptolemy.domains.ct.lib.LinearStateSpace"); 554 555 // icon 556 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-50\" y=\"-30\" " 557 + "width=\"100\" height=\"60\" " + "style=\"fill:white\"/>\n" 558 + "<text x=\"-45\" y=\"-10\" " + "style=\"font-size:14\">\n" 559 + "dx/dt=Ax+Bu " + "</text>\n" + "<text x=\"-45\" y=\"10\" " 560 + "style=\"font-size:14\">\n" + " y=Cx+Du" + "</text>\n" 561 + "</svg>\n"); 562 } 563 564 /** Set this composite actor to opaque and request for reinitialization 565 * from the director if there is one. 566 */ 567 private void _requestInitialization() { 568 // Request for initialization. 569 Director dir = getDirector(); 570 571 if (dir != null) { 572 dir.requestInitialization(this); 573 } 574 575 // Set this composite to opaque. 576 _opaque = true; 577 } 578 579 /////////////////////////////////////////////////////////////////// 580 //// private variables //// 581 // opaqueness. 582 private boolean _opaque; 583 584 // request for initialization. 585 private boolean _requestInitialization; 586}