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}