001/* A differential system in the Continuous 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; 031import java.util.Set; 032 033import ptolemy.actor.Director; 034import ptolemy.actor.IORelation; 035import ptolemy.actor.TypedCompositeActor; 036import ptolemy.actor.TypedIOPort; 037import ptolemy.actor.TypedIORelation; 038import ptolemy.actor.lib.Expression; 039import ptolemy.data.ArrayToken; 040import ptolemy.data.DoubleToken; 041import ptolemy.data.StringToken; 042import ptolemy.data.expr.Parameter; 043import ptolemy.data.type.BaseType; 044import ptolemy.domains.continuous.kernel.ContinuousDirector; 045import ptolemy.kernel.CompositeEntity; 046import ptolemy.kernel.Port; 047import ptolemy.kernel.util.Attribute; 048import ptolemy.kernel.util.IllegalActionException; 049import ptolemy.kernel.util.InternalErrorException; 050import ptolemy.kernel.util.NameDuplicationException; 051import ptolemy.kernel.util.NamedObj; 052import ptolemy.kernel.util.Settable; 053import ptolemy.kernel.util.Workspace; 054 055/////////////////////////////////////////////////////////////////// 056//// DifferentialSystem 057 058/** 059 A differential system in the Continuous domain. 060 061 <p>The differential system model implements a system whose behavior 062 is defined by: 063 <pre> 064 dx/dt = f(x, u, t) 065 y = g(x, u, t) 066 x(0) = x0 067 </pre> 068 where x is the state vector, u is the input vector, and y is the output 069 vector, t is the time. To use this actor, proceed through the following 070 steps: 071 <ul> 072 <li> For each input in <i>u</i>, create an input port. 073 Each input may have any name, since you will refer to it by 074 name rather than by the symbol <i>u</i>. This actor will 075 automatically create a parameter with the same name as the 076 input port. That parameter will have its value set during 077 execution to match the value of the input. 078 Note that at this time, multiport inputs are not supported. 079 080 <li> Fill in the <i>stateVariableNames</i> parameter, which is 081 an array of strings, with the names of the state variables in <i>x</i>. 082 These names can be anything you like, since you will refer them to 083 by name rather than by the symbol <i>x</i>. 084 085 <li> For each state variable name in <i>stateVariableNames</i>, 086 create a parameter with a value equal to the initial value of that 087 particular state variable. 088 089 <li> Specify an update function (part of <i>f</i> above) for each 090 state variable by creating a parameter named <i>name</i>_dot, where 091 <i>name</i> is the name of the state variable. The value of this 092 parameter should be an expression giving the rate of change of 093 this state variable as a function of any of the state variables, 094 any input, any other actor parameter, and (possibly), the variable 095 <i>t</i>, representing current time. 096 097 <li> For each output in <i>y</i>, create an output port. 098 The output may have any name. This actor will automatically 099 create a parameter with the same name as the output port. 100 101 <li> For each parameter matching an output port, set its 102 value to be an expression giving the output 103 value as a function of the state variables, the inputs, any other 104 actor parameter, and (possibly), the variable 105 <i>t</i>, representing current time. 106 107 </ul> 108 <P> 109 This actor is a higher-order component. Upon preinitialization, 110 the actor will create a subsystem using integrators and expressions. 111 These are not persistent (they are not exported in the MoML file), 112 and will instead by created each time the actor is preinitialized. 113 <p> 114 This actor is based on the ptolemy.domain.ct.lib.DifferentialSystem 115 actor by Jie Liu. 116 117 @author Jie Liu and Edward A. Lee 118 @version $Id$ 119 @since Ptolemy II 7.0 120 @Pt.ProposedRating Red (liuj) 121 @Pt.AcceptedRating Red (cxh) 122 @see ptolemy.domains.continuous.lib.Integrator 123 */ 124public class DifferentialSystem extends TypedCompositeActor { 125 /** Construct the composite actor with a name and a container. 126 * This constructor creates the ports, parameters, and the icon. 127 * @param container The container. 128 * @param name The name. 129 * @exception NameDuplicationException If another entity already had 130 * this name. 131 * @exception IllegalActionException If there was an internal problem. 132 */ 133 public DifferentialSystem(CompositeEntity container, String name) 134 throws NameDuplicationException, IllegalActionException { 135 super(container, name); 136 _init(); 137 } 138 139 /** Construct a DifferentialSystem in the specified 140 * workspace with no container and an empty string as a name. You 141 * can then change the name with setName(). If the workspace 142 * argument is null, then use the default workspace. 143 * @param workspace The workspace that will list the actor. 144 * @exception IllegalActionException If the name has a period in it, or 145 * the director is not compatible with the specified container. 146 * @exception NameDuplicationException If the container already contains 147 * an entity with the specified name. 148 */ 149 public DifferentialSystem(Workspace workspace) 150 throws IllegalActionException, NameDuplicationException { 151 super(workspace); 152 _init(); 153 } 154 155 /////////////////////////////////////////////////////////////////// 156 //// parameters //// 157 158 /** The names of the state variables, in an array of strings. 159 * The default is an ArrayToken of an empty String. 160 */ 161 public Parameter stateVariableNames; 162 163 /** The value of current time. This parameter is not visible in 164 * the expression screen except in expert mode. Its value initially 165 * is just 0.0, a double, but upon each firing, it is given a 166 * value equal to the current time as reported by the director. 167 */ 168 public Parameter t; 169 170 /////////////////////////////////////////////////////////////////// 171 //// public methods //// 172 173 /** If the argument is any parameter other than <i>stateVariableNames</i> 174 * <i>t</i>, or any parameter matching an input port, 175 * then request reinitialization. 176 * @param attribute The attribute that changed. 177 * @exception IllegalActionException If the numerator and the 178 * denominator matrix is not a row vector. 179 */ 180 @Override 181 public void attributeChanged(Attribute attribute) 182 throws IllegalActionException { 183 super.attributeChanged(attribute); 184 if (attribute instanceof Parameter && attribute != t 185 && attribute != stateVariableNames) { 186 // If the attribute name matches an input port name, 187 // do not reinitialize. 188 TypedIOPort port = (TypedIOPort) getPort(attribute.getName()); 189 if (port == null || !port.isInput()) { 190 // Change of any parameter triggers reinitialization. 191 _requestInitialization(); 192 } 193 } 194 // If any parameter changes, then the next preinitialize() 195 // will recreate the contents. 196 _upToDate = false; 197 } 198 199 /** Override the base class to first set the value of the 200 * parameter <i>t</i> to match current time, then to set 201 * the local parameters that mirror input values, 202 * and then to fire the contained actors. 203 */ 204 @Override 205 public void fire() throws IllegalActionException { 206 // Set the time variable. 207 double currentTime = getDirector().getModelTime().getDoubleValue(); 208 t.setToken(new DoubleToken(currentTime)); 209 210 // Set the input parameters. 211 /* NOTE: There is no need to set the values of these shadow 212 * variables. They are not used. 213 List<TypedIOPort> inputs = inputPortList(); 214 for (TypedIOPort input : inputs) { 215 String name = input.getName(); 216 if (input.getWidth() > 0 && input.isKnown(0) && input.hasToken(0)) { 217 Parameter parameter = (Parameter)getAttribute(name); 218 parameter.setToken(input.get(0)); 219 } 220 } 221 */ 222 223 super.fire(); 224 } 225 226 /** Create the model inside from the parameter values. 227 * This method gets write access on the workspace. 228 * @exception IllegalActionException If there is no director, 229 * or if any contained actors throws it in its preinitialize() method. 230 * 231 */ 232 @Override 233 public void preinitialize() throws IllegalActionException { 234 if (_upToDate) { 235 super.preinitialize(); 236 return; 237 } 238 // Check parameters. 239 _checkParameters(); 240 241 ArrayToken stateNames = (ArrayToken) stateVariableNames.getToken(); 242 int n = stateNames.length(); 243 int m = inputPortList().size(); 244 int r = outputPortList().size(); 245 246 try { 247 _workspace.getWriteAccess(); 248 removeAllEntities(); 249 removeAllRelations(); 250 251 // Create the model 252 Integrator[] integrators = new Integrator[n]; 253 String[] states = new String[n]; 254 IORelation[] stateRelations = new IORelation[n]; 255 Expression[] equations = new Expression[n]; 256 257 // Integrators and feedback expressions 258 for (int i = 0; i < n; i++) { 259 states[i] = ((StringToken) stateNames.getElement(i)) 260 .stringValue().trim(); 261 integrators[i] = new Integrator(this, states[i]); 262 integrators[i].setPersistent(false); 263 integrators[i].initialState.setExpression(states[i]); 264 stateRelations[i] = new TypedIORelation(this, 265 "relation_" + states[i]); 266 stateRelations[i].setPersistent(false); 267 268 integrators[i].state.link(stateRelations[i]); 269 270 // One Expression actor per integrator. 271 equations[i] = new Expression(this, states[i] + "_dot"); 272 equations[i].setPersistent(false); 273 equations[i].expression.setExpression( 274 ((Parameter) getAttribute(states[i] + "_dot")) 275 .getExpression()); 276 277 connect(equations[i].output, integrators[i].derivative); 278 } 279 280 // Inputs 281 String[] inputs = new String[m]; 282 IORelation[] inputRelations = new IORelation[m]; 283 Iterator inputPorts = inputPortList().iterator(); 284 int inputIndex = 0; 285 286 while (inputPorts.hasNext()) { 287 inputs[inputIndex] = ((NamedObj) inputPorts.next()).getName(); 288 inputRelations[inputIndex] = new TypedIORelation(this, 289 "relation_" + inputs[inputIndex]); 290 inputRelations[inputIndex].setPersistent(false); 291 getPort(inputs[inputIndex]).link(inputRelations[inputIndex]); 292 inputIndex++; 293 } 294 295 // Outputs and output expressions. 296 String[] outputs = new String[r]; 297 Expression[] maps = new Expression[r]; 298 int outIndex = 0; 299 Iterator outputPorts = outputPortList().iterator(); 300 301 while (outputPorts.hasNext()) { 302 outputs[outIndex] = ((NamedObj) outputPorts.next()).getName(); 303 maps[outIndex] = new Expression(this, 304 "output_" + outputs[outIndex]); 305 maps[outIndex].setPersistent(false); 306 307 maps[outIndex].expression.setExpression( 308 ((Parameter) getAttribute(outputs[outIndex])) 309 .getExpression()); 310 maps[outIndex].output.setTypeEquals(BaseType.DOUBLE); 311 connect(maps[outIndex].output, 312 (TypedIOPort) getPort(outputs[outIndex])); 313 outIndex++; 314 } 315 316 // Connect state feedback expressions. 317 for (int i = 0; i < n; i++) { 318 // Create ports for each state update Expression actor 319 // and connect them. 320 // One port for each state variable. 321 for (int k = 0; k < n; k++) { 322 TypedIOPort port = new TypedIOPort(equations[i], states[k], 323 true, false); 324 port.setTypeEquals(BaseType.DOUBLE); 325 port.link(stateRelations[k]); 326 } 327 328 // One port for each input variable. 329 // Create and connect the port only if the input 330 // is used. 331 for (int k = 0; k < m; k++) { 332 Parameter stateUpdateSpec = (Parameter) getAttribute( 333 states[i] + "_dot"); 334 Set<String> freeIdentifiers = stateUpdateSpec 335 .getFreeIdentifiers(); 336 // Create an output port only if the expression references the input. 337 if (freeIdentifiers.contains(inputs[k])) { 338 TypedIOPort port = new TypedIOPort(equations[i], 339 inputs[k], true, false); 340 port.setTypeEquals(BaseType.DOUBLE); 341 port.link(inputRelations[k]); 342 } 343 } 344 } 345 346 // Connect output expressions. 347 // For each output expression/port: 348 for (int l = 0; l < r; l++) { 349 // Create ports for each state update Expression actor 350 // and connect them. 351 // One port for each state variable. 352 for (int k = 0; k < n; k++) { 353 TypedIOPort port = new TypedIOPort(maps[l], states[k], true, 354 false); 355 port.setTypeEquals(BaseType.DOUBLE); 356 port.link(stateRelations[k]); 357 } 358 359 // One port for each input variable. 360 // NOTE: Do not reference input ports 361 // in the expression for an output port 362 // if you want that output port in a feedback loop. 363 for (int k = 0; k < m; k++) { 364 Parameter outputSpec = (Parameter) getAttribute(outputs[l]); 365 Set<String> freeIdentifiers = outputSpec 366 .getFreeIdentifiers(); 367 // Create an output port only if the expression references the input. 368 if (freeIdentifiers.contains(inputs[k])) { 369 TypedIOPort port = new TypedIOPort(maps[l], inputs[k], 370 true, false); 371 port.setTypeEquals(BaseType.DOUBLE); 372 port.link(inputRelations[k]); 373 } 374 } 375 } 376 _upToDate = true; 377 } catch (NameDuplicationException ex) { 378 // Should never happen. 379 throw new InternalErrorException("Duplicated name when " 380 + "constructing the subsystem" + ex.getMessage()); 381 } finally { 382 _workspace.doneWriting(); 383 } 384 385 // Preinitialize the contained model. 386 super.preinitialize(); 387 } 388 389 /////////////////////////////////////////////////////////////////// 390 //// protected methods //// 391 392 /** Add a port to this actor. This overrides the base class to 393 * add a parameter with the same name as the port. This parameter 394 * is not persistent and is visible only in expert mode. It will 395 * be used to mirror the values of the inputs. 396 * @param port The TypedIOPort to add to this actor. 397 * @exception IllegalActionException If the port class is not 398 * acceptable to this actor, or the port has no name. 399 * @exception NameDuplicationException If the port name collides with a 400 * name already in the actor. 401 */ 402 @Override 403 protected void _addPort(Port port) 404 throws IllegalActionException, NameDuplicationException { 405 super._addPort(port); 406 407 // Add the parameter, if it does not already exist. 408 String name = port.getName(); 409 if (getAttribute(name) == null) { 410 Parameter parameter = new Parameter(this, name); 411 parameter.setExpression("0.0"); 412 } 413 } 414 415 /////////////////////////////////////////////////////////////////// 416 //// private methods //// 417 418 /** Check the dimensions of all parameters and ports. 419 * @exception IllegalActionException If the dimensions are illegal. 420 */ 421 private void _checkParameters() throws IllegalActionException { 422 // Check state variable names. 423 ArrayToken stateNames = (ArrayToken) stateVariableNames.getToken(); 424 int n = stateNames.length(); 425 426 if (n < 1) { 427 throw new IllegalActionException(this, "There must be at " 428 + "least one state variable for a differential system."); 429 } 430 431 // Check if any of the state variable names is an empty string. 432 for (int i = 0; i < n; i++) { 433 String name = ((StringToken) stateNames.getElement(i)).stringValue() 434 .trim(); 435 436 if (name.equals("")) { 437 throw new IllegalActionException(this, "A state variable " 438 + "name should not be an empty string."); 439 } 440 441 // Check state equations. 442 String equation = name + "_dot"; 443 444 if (getAttribute(equation) == null) { 445 throw new IllegalActionException(this, 446 "Please add a " + "parameter with name \"" + equation 447 + "\" that gives the state update expression."); 448 } 449 } 450 451 // Check output names. 452 Iterator outputPorts = outputPortList().iterator(); 453 454 // Note there could be no output. If there are outputs, 455 // check if any of the output variable names is an empty string, 456 // and also that there is an output port with the same name. 457 while (outputPorts.hasNext()) { 458 TypedIOPort output = (TypedIOPort) outputPorts.next(); 459 String name = output.getName().trim(); 460 461 if (name.equals("")) { 462 throw new IllegalActionException(this, "A output variable " 463 + "name should not be an empty string."); 464 } 465 466 // Check output maps. 467 if (getAttribute(name) == null) { 468 throw new IllegalActionException(this, 469 "Please add a " + "parameter with name \"" + name 470 + "\" to specify the output map."); 471 } 472 } 473 } 474 475 /** Initialize the class. */ 476 private void _init() 477 throws IllegalActionException, NameDuplicationException { 478 StringToken[] empty = new StringToken[1]; 479 stateVariableNames = new Parameter(this, "stateVariableNames"); 480 empty[0] = new StringToken(""); 481 stateVariableNames.setToken(new ArrayToken(BaseType.STRING, empty)); 482 483 setClassName("ptolemy.domains.ct.lib.DifferentialSystem"); 484 485 t = new Parameter(this, "t"); 486 t.setTypeEquals(BaseType.DOUBLE); 487 t.setVisibility(Settable.EXPERT); 488 t.setExpression("0.0"); 489 490 // This actor contains a ContinuousDirector. 491 // This director is not persistent, however. 492 // There is no need to store it in the MoML file, since 493 // it is created here in the constructor. 494 new ContinuousDirector(this, "ContinuousDirector").setPersistent(false); 495 496 // icon 497 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-50\" y=\"-30\" " 498 + "width=\"100\" height=\"60\" " + "style=\"fill:white\"/>\n" 499 + "<text x=\"-45\" y=\"-10\" " + "style=\"font-size:14\">\n" 500 + "dx/dt=f(x, u, t)" + "</text>\n" + "<text x=\"-45\" y=\"10\" " 501 + "style=\"font-size:14\">\n" + " y=g(x, u, t)" 502 + "</text>\n" + "style=\"fill:blue\"/>\n" + "</svg>\n"); 503 } 504 505 /** Set this composite actor to opaque and request for reinitialization 506 * from the director if there is one. 507 */ 508 private void _requestInitialization() { 509 // Request for initialization. 510 Director dir = getExecutiveDirector(); 511 512 if (dir != null) { 513 dir.requestInitialization(this); 514 } 515 } 516 517 /////////////////////////////////////////////////////////////////// 518 //// private members //// 519 520 /** Flag indicating whether the contained model is up to date. */ 521 private boolean _upToDate = false; 522}