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}