001/* Transfer function 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; 031 032import ptolemy.actor.Actor; 033import ptolemy.actor.Director; 034import ptolemy.actor.IORelation; 035import ptolemy.actor.TypedCompositeActor; 036import ptolemy.actor.TypedIOPort; 037import ptolemy.actor.lib.AddSubtract; 038import ptolemy.actor.lib.Scale; 039import ptolemy.data.ArrayToken; 040import ptolemy.data.DoubleToken; 041import ptolemy.data.expr.Parameter; 042import ptolemy.data.type.ArrayType; 043import ptolemy.data.type.BaseType; 044import ptolemy.kernel.CompositeEntity; 045import ptolemy.kernel.util.Attribute; 046import ptolemy.kernel.util.IllegalActionException; 047import ptolemy.kernel.util.InternalErrorException; 048import ptolemy.kernel.util.NameDuplicationException; 049import ptolemy.kernel.util.Workspace; 050 051/////////////////////////////////////////////////////////////////// 052//// ContinuousTransferFunction 053 054/** 055 A transfer function in the continuous time domain. 056 This actor implements a transfer function where the single input (u) and 057 single output (y) can be expressed in (Laplace) transfer function 058 form as the following equation: 059 <pre> 060 y(s) b(1)*s^(m-1) + b(2)*s^(m-2) + ... + b(m) 061 ----- = ------------------------------------------- 062 u(s) a(1)*s^(n-1) + a(2)*s^(n-2) + ... + a(n) 063 </pre> 064 where m and n are the number of numerator and denominator coefficients, 065 respectively. This actors has two parameters -- numerator and denominator -- 066 containing the coefficients of the numerator and denominator in 067 descending powers of s. These coefficients are double numbers. 068 The order of the denominator (n) must be greater 069 than or equal to the order of the numerator (m). 070 <p> 071 This actor extends TypedCompositeActor and works as a higher-order function. 072 Whenever the parameters are changed, the actor will build a transparent 073 subsystem inside it using integrators, adders, and scales. This is called 074 a realization of the transfer function. Notice that there are infinite 075 number of realizations of a transfer function, and they are equivalent if and 076 only if the initial conditions are all zero. Here we choose the controllable 077 canonical form and preset all initial states of the integrators to zero. 078 If you need to set arbitrary initial 079 conditions you have to use the state-space representation, for example, 080 use the LinearStateSpace actor. 081 082 @author Christopher Brooks, based on the CT ContinuousTransferFunction by Jie Liu 083 @version $Id$ 084 @since Ptolemy II 8.0 085 @Pt.ProposedRating Red (liuj) 086 @Pt.AcceptedRating Red (cxh) 087 */ 088public class ContinuousTransferFunction extends TypedCompositeActor { 089 /** Construct the composite actor with a name and a container. 090 * @param container The container. 091 * @param name The name. 092 * @exception NameDuplicationException If another entity already had 093 * this name. 094 * @exception IllegalActionException If there was an internal problem. 095 */ 096 public ContinuousTransferFunction(CompositeEntity container, String name) 097 throws NameDuplicationException, IllegalActionException { 098 super(container, name); 099 _init(); 100 } 101 102 /** Construct a ContinuousTransferFunction in the specified 103 * workspace with no container and an empty string as a name. You 104 * can then change the name with setName(). If the workspace 105 * argument is null, then use the default workspace. 106 * @param workspace The workspace that will list the actor. 107 * @exception IllegalActionException If the name has a period in it, or 108 * the director is not compatible with the specified container. 109 * @exception NameDuplicationException If the container already contains 110 * an entity with the specified name. 111 */ 112 public ContinuousTransferFunction(Workspace workspace) 113 throws IllegalActionException, NameDuplicationException { 114 super(workspace); 115 _init(); 116 } 117 118 /////////////////////////////////////////////////////////////////// 119 //// ports and parameters //// 120 121 /** Single input port. 122 */ 123 public TypedIOPort input; 124 125 /** Single output port. 126 */ 127 public TypedIOPort output; 128 129 /** The coefficients of the numerator, containing an array of 130 * DoubleTokens. 131 * The default value is {1.0}. 132 */ 133 public Parameter numerator; 134 135 /** The coefficients of the denominator, containing an array 136 * of DoubleTokens. 137 * The array must have a length greater 138 * than or equal to the length of the numerator. 139 * The default value is {1.0}. 140 */ 141 public Parameter denominator; 142 143 /////////////////////////////////////////////////////////////////// 144 //// public methods //// 145 146 /** If the argument is the <i>numerator</i> or the <i>denominator</i> 147 * parameters, request for initialization from the director if 148 * there is one. Also check that the <i>denominator</i> vector 149 * cannot start with 0. 150 * Other sanity checks, like that the denominator must have a higher 151 * order than that of the numerator, and that the first element of the 152 * denominator should not be zero, are done in the preinitialize() 153 * method. 154 * @param attribute The attribute that changed. 155 * @exception IllegalActionException If the numerator and the 156 * denominator matrix is not a row vector. 157 */ 158 @Override 159 public void attributeChanged(Attribute attribute) 160 throws IllegalActionException { 161 if (attribute == numerator) { 162 // Set this composite to opaque. 163 _opaque = true; 164 165 // Request for initialization. 166 Director dir = getDirector(); 167 168 if (dir != null) { 169 dir.requestInitialization(this); 170 } 171 } else if (attribute == denominator) { 172 // Check that a_0 is not 0.0 173 ArrayToken aToken = (ArrayToken) denominator.getToken(); 174 175 if (((DoubleToken) aToken.getElement(0)).doubleValue() == 0.0) { 176 throw new IllegalActionException(this, 177 "The denominator coefficient cannot start with 0."); 178 } 179 180 // Set this composite to opaque. 181 _opaque = true; 182 183 // Request for initialization. 184 Director dir = getDirector(); 185 186 if (dir != null) { 187 dir.requestInitialization(this); 188 } 189 } else { 190 super.attributeChanged(attribute); 191 } 192 } 193 194 /** Return the executive director, regardless what isOpaque returns. 195 */ 196 @Override 197 public Director getDirector() { 198 if (_opaque) { 199 return null; 200 } else { 201 return getExecutiveDirector(); 202 } 203 } 204 205 /** Return the opaqueness of this composite actor. This actor is 206 * opaque if it has not been preinitialized after creation or 207 * changes of parameters. Otherwise, it is not opaque. 208 */ 209 @Override 210 public boolean isOpaque() { 211 return _opaque; 212 } 213 214 /** Sanity check the parameters; if the parameters are legal 215 * create a continuous-time subsystem that implement the transfer 216 * function, preinitialize all the actors in the subsystem, 217 * and set the opaqueness of this actor to true. 218 * This method need the write access on the workspace. 219 * @exception IllegalActionException If there is no ContinuousDirector, 220 * or any contained actors throw it in its preinitialize() method 221 * . 222 */ 223 @Override 224 public void preinitialize() throws IllegalActionException { 225 // Construct local double[] and Check dimensions. 226 ArrayToken bToken = (ArrayToken) numerator.getToken(); 227 int m = bToken.length(); 228 double[] bRow = new double[m]; 229 230 for (int i = 0; i < m; i++) { 231 bRow[i] = ((DoubleToken) bToken.getElement(i)).doubleValue(); 232 } 233 234 ArrayToken aToken = (ArrayToken) denominator.getToken(); 235 int n = aToken.length(); 236 double[] a = new double[n]; 237 238 for (int i = 0; i < n; i++) { 239 a[i] = ((DoubleToken) aToken.getElement(i)).doubleValue(); 240 } 241 242 if (m > n) { 243 throw new IllegalActionException(this, 244 "The order of the denominator must be greater than or " 245 + "equal to the order of the numerator."); 246 } 247 248 // Add leading zeros to bRow such that b has the same length as a. 249 double[] b = new double[n]; 250 251 // Note that b is initialized to contain all zeros. 252 for (int i = 1; i <= m; i++) { 253 b[n - i] = bRow[m - i]; 254 } 255 256 try { 257 _workspace.getWriteAccess(); 258 removeAllEntities(); 259 removeAllRelations(); 260 261 if (n == 1) { 262 // Algebraic system 263 if (a[0] == b[0]) { 264 connect(input, output); 265 } else { 266 Scale scaleD = new Scale(this, "ScaleD"); 267 scaleD.factor.setToken(new DoubleToken(b[0] / a[0])); 268 connect(input, scaleD.input); 269 connect(output, scaleD.output); 270 } 271 } else { 272 double d = b[0] / a[0]; 273 int order = n - 1; 274 AddSubtract inputAdder = new AddSubtract(this, "InputAdder"); 275 AddSubtract outputAdder = new AddSubtract(this, "OutputAdder"); 276 Integrator[] integrators = new Integrator[order]; 277 IORelation[] nodes = new IORelation[order]; 278 Scale[] feedback = new Scale[order]; 279 Scale[] feedforward = new Scale[order]; 280 281 for (int i = 0; i < order; i++) { 282 // The integrator names are d0x, d1x, etc. 283 integrators[i] = new Integrator(this, "Integrator" + i); 284 feedback[i] = new Scale(this, "Feedback" + i); 285 feedback[i].factor 286 .setToken(new DoubleToken(-a[i + 1] / a[0])); 287 feedforward[i] = new Scale(this, "Feedforward" + i); 288 feedforward[i].factor.setToken( 289 new DoubleToken((b[i + 1] - d * a[i + 1]) / a[0])); 290 291 // connections 292 nodes[i] = (IORelation) connect(integrators[i].state, 293 feedforward[i].input, "node" + i); 294 feedback[i].input.link(nodes[i]); 295 connect(feedback[i].output, inputAdder.plus); 296 connect(feedforward[i].output, outputAdder.plus); 297 298 if (i >= 1) { 299 integrators[i].derivative.link(nodes[i - 1]); 300 } 301 } 302 303 connect(inputAdder.output, integrators[0].derivative); 304 305 IORelation inputRelation = (IORelation) connect(input, 306 inputAdder.plus, "inputRelation"); 307 connect(output, outputAdder.output, "outputRelation"); 308 309 if (d != 0) { 310 Scale scaleD = new Scale(this, "ScaleD"); 311 scaleD.factor.setToken(new DoubleToken(d)); 312 scaleD.input.link(inputRelation); 313 connect(scaleD.output, outputAdder.plus); 314 } 315 } 316 317 _opaque = false; 318 _workspace.incrVersion(); 319 } catch (NameDuplicationException ex) { 320 // Should never happen. 321 throw new InternalErrorException("Duplicated name when " 322 + "constructing the subsystem" + ex.getMessage()); 323 } finally { 324 _workspace.doneWriting(); 325 } 326 327 // NOTE: Cannot call super.preinitialize() because the actor is not 328 // opaque. 329 // super.preinitialize(); 330 331 // preinitialize all contained actors. 332 for (Iterator i = deepEntityList().iterator(); i.hasNext();) { 333 Actor actor = (Actor) i.next(); 334 actor.preinitialize(); 335 } 336 } 337 338 /** Set the opaqueness to true and wrapup. 339 * @exception IllegalActionException If there is no director. 340 */ 341 @Override 342 public void wrapup() throws IllegalActionException { 343 _opaque = true; 344 super.wrapup(); 345 } 346 347 /////////////////////////////////////////////////////////////////// 348 //// private methods //// 349 350 /** Initialize the class. */ 351 private void _init() 352 throws IllegalActionException, NameDuplicationException { 353 354 input = new TypedIOPort(this, "input", true, false); 355 output = new TypedIOPort(this, "output", false, true); 356 _opaque = true; 357 358 numerator = new Parameter(this, "numerator"); 359 numerator.setExpression("{1.0}"); 360 numerator.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 361 362 denominator = new Parameter(this, "denominator"); 363 denominator.setExpression("{1.0}"); 364 denominator.setTypeEquals(new ArrayType(BaseType.DOUBLE)); 365 366 // Do not use TypedCompositeActor as the MoML name for this actor. 367 setClassName("ptolemy.domains.ct.lib.ContinuousTransferFunction"); 368 369 // icon 370 _attachText("_iconDescription", "<svg>\n" + "<rect x=\"-30\" y=\"-20\" " 371 + "width=\"60\" height=\"40\" " + "style=\"fill:white\"/>\n" 372 + "<text x=\"-25\" y=\"0\" " + "style=\"font-size:14\">\n" 373 + "b(s)/a(s) \n" + "</text>\n" + "style=\"fill:blue\"/>\n" 374 + "</svg>\n"); 375 } 376 377 /////////////////////////////////////////////////////////////////// 378 //// private variables //// 379 /** Opaqueness. */ 380 private boolean _opaque; 381}