001/* An actor that computes the dot product of two arrays.
002
003 Copyright (c) 1997-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.sdf.lib;
029
030import ptolemy.actor.TypedAtomicActor;
031import ptolemy.actor.TypedIOPort;
032import ptolemy.data.ArrayToken;
033import ptolemy.data.MatrixToken;
034import ptolemy.data.ScalarToken;
035import ptolemy.data.Token;
036import ptolemy.data.type.ArrayType;
037import ptolemy.data.type.BaseType;
038import ptolemy.data.type.MatrixType;
039import ptolemy.data.type.MonotonicFunction;
040import ptolemy.data.type.Type;
041import ptolemy.data.type.TypeLattice;
042import ptolemy.graph.CPO;
043import ptolemy.graph.InequalityTerm;
044import ptolemy.kernel.CompositeEntity;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047import ptolemy.kernel.util.Workspace;
048
049///////////////////////////////////////////////////////////////////
050//// DotProduct
051
052/**
053 <p>Compute the dot product of two arrays or matrices. This actor has two
054 input ports, from which it receives two ArrayTokens or two Matrix
055 Tokens. The elements of the ArrayTokens or MatrixTokens must be of
056 type ScalarToken. The output is the dot product of the two arrays or
057 matrices.</p>
058
059 <p> This actor requires that each input port have a token upon
060 firing. On each firing, it produces exactly one token, which is of
061 type ScalarToken.</p>
062
063 @author Jeff Tsay, Paul Whitaker, Adam Cataldo
064 @version $Id$
065 @since Ptolemy II 1.0
066 @Pt.ProposedRating Yellow (pwhitake)
067 @Pt.AcceptedRating Red (acataldo)
068 */
069public class DotProduct extends TypedAtomicActor {
070    /** Construct an actor in the specified container with the specified
071     *  name.
072     *  @param container The container.
073     *  @param name The name of this actor within the container.
074     *  @exception IllegalActionException If the actor cannot be contained
075     *   by the proposed container.
076     *  @exception NameDuplicationException If the name coincides with
077     *   an actor already in the container.
078     */
079    public DotProduct(CompositeEntity container, String name)
080            throws IllegalActionException, NameDuplicationException {
081        super(container, name);
082
083        input1 = new TypedIOPort(this, "input1", true, false);
084        input2 = new TypedIOPort(this, "input2", true, false);
085        output = new TypedIOPort(this, "output", false, true);
086
087        // Set the type constraints.
088        output.setTypeAtLeast(new PortFunction(input1, input2));
089    }
090
091    ///////////////////////////////////////////////////////////////////
092    ////                     ports and parameters                  ////
093
094    /** The first input port. This has type ArrayToken. The elements of
095     *  the ArrayToken must be of type ScalarToken.
096     */
097    public TypedIOPort input1 = null;
098
099    /** The second input port. This has type ArrayToken. The elements of
100     *  the ArrayToken must be of type ScalarToken.
101     */
102    public TypedIOPort input2 = null;
103
104    /** The output port, which has type ScalarToken.
105     */
106    public TypedIOPort output = null;
107
108    ///////////////////////////////////////////////////////////////////
109    ////                         public methods                    ////
110
111    /** Clone the actor into the specified workspace. This calls the
112     *  base class and then sets the type constraints.
113     *  @param workspace The workspace for the new object.
114     *  @return A new actor.
115     *  @exception CloneNotSupportedException If a derived class has
116     *   an attribute that cannot be cloned.
117     */
118    @Override
119    public Object clone(Workspace workspace) throws CloneNotSupportedException {
120        DotProduct newObject = (DotProduct) super.clone(workspace);
121        PortFunction function = new PortFunction(newObject.input1,
122                newObject.input2);
123        newObject.output.setTypeAtLeast(function);
124        return newObject;
125    }
126
127    /** Read a Token from each of the input ports, and output the
128     *  dot product.
129     *  @exception IllegalActionException If there is no director, if
130     *  the input arrays have unequal widths, or if the input arrays
131     *  have no elements, or if the input types.
132     */
133    @Override
134    public void fire() throws IllegalActionException {
135        super.fire();
136
137        if (input1.getType() instanceof ArrayType
138                && input2.getType() instanceof ArrayType) {
139            try {
140                _arrayFire();
141            } catch (IllegalActionException e) {
142                throw e;
143            }
144        } else if (input1.getType() instanceof MatrixType
145                && input2.getType() instanceof MatrixType) {
146            try {
147                _matrixFire();
148            } catch (IllegalActionException e) {
149                throw e;
150            }
151        } else {
152            throw new IllegalActionException("Invalid types. "
153                    + "Input1 and input2 must both of "
154                    + "ArrayType or both be of MatrixType."
155                    + "The type of input1 was \"" + input1.getType().getClass()
156                    + "\". The type of input2 was \""
157                    + input2.getType().getClass() + "\".");
158        }
159    }
160
161    /** If both of the input ports have at least one token, return
162     *  what the superclass returns (presumably true).  Otherwise return
163     *  false.
164     *  @exception IllegalActionException If there is no director.
165     */
166    @Override
167    public boolean prefire() throws IllegalActionException {
168        if (!input1.hasToken(0) || !input2.hasToken(0)) {
169            if (_debugging) {
170                _debug("Called prefire(), which returns false.");
171            }
172
173            return false;
174        } else {
175            return super.prefire();
176        }
177    }
178
179    ///////////////////////////////////////////////////////////////////
180    ////                         private methods                   ////
181
182    /*  Read an ArrayToken from each of the input ports, and output the
183     *  dot product.
184     *  @exception IllegalActionException If the input arrays have
185     *  unequal widths or if the input arrays have no elements..
186     */
187    private void _arrayFire() throws IllegalActionException {
188        ArrayToken token1 = (ArrayToken) input1.get(0);
189        ArrayToken token2 = (ArrayToken) input2.get(0);
190
191        Token[] array1 = token1.arrayValue();
192        Token[] array2 = token2.arrayValue();
193
194        if (array1.length != array2.length) {
195            throw new IllegalActionException(
196                    "Inputs to DotProduct have " + "unequal lengths: "
197                            + array1.length + " and " + array2.length + ".");
198        }
199
200        if (array1.length < 1) {
201            throw new IllegalActionException(
202                    "Inputs to DotProduct have " + "no elements.");
203        }
204
205        Token dotProd = null;
206        ScalarToken currentTerm;
207
208        for (int i = 0; i < array1.length; i++) {
209            currentTerm = (ScalarToken) array1[i].multiply(array2[i]);
210
211            if (dotProd == null) {
212                dotProd = currentTerm;
213            } else {
214                dotProd = dotProd.add(currentTerm);
215            }
216        }
217
218        output.broadcast(dotProd);
219    }
220
221    /*  Read a MatrixToken from each of the input ports, and output
222     *  the dot product.
223     *  @exception IllegalActionException If the input matrices have
224     *  unequal sizes.
225     */
226    private void _matrixFire() throws IllegalActionException {
227        MatrixToken token1 = (MatrixToken) input1.get(0);
228        MatrixToken token2 = (MatrixToken) input2.get(0);
229
230        int columnCount1 = token1.getColumnCount();
231        int rowCount1 = token1.getRowCount();
232        int columnCount2 = token2.getColumnCount();
233        int rowCount2 = token2.getRowCount();
234
235        Token element1;
236        Token element2;
237        Token sum;
238
239        if (columnCount1 == columnCount2 && rowCount1 == rowCount2) {
240            sum = token1.getElementAsToken(0, 0).zero();
241
242            for (int i = 0; i < rowCount1; i += 1) {
243                for (int j = 0; j < columnCount1; j += 1) {
244                    element1 = token1.getElementAsToken(i, j);
245                    element2 = token2.getElementAsToken(i, j);
246                    sum = sum.add(element1.multiply(element2));
247                }
248            }
249
250            output.send(0, sum);
251        } else {
252            String matrix1 = rowCount1 + " by " + columnCount1;
253            String matrix2 = rowCount2 + " by " + columnCount2;
254            throw new IllegalActionException("Tried to multiply a " + matrix1
255                    + " matrix with a " + matrix2 + " matrix");
256        }
257    }
258
259    ///////////////////////////////////////////////////////////////////
260    ////                         inner classes                     ////
261
262    /** This class implements a function on the two input port types.
263     *  f(port1, port2) ==
264     *     LUB(elementType(port1), elementType(port2)) if both are matrices
265     *     LUB(elementType(port1), elementType(port2)) if both are arrays
266     *     UNKNOWN                                     otherwise
267     *  This function's value determines the output port type.
268     */
269    private static class PortFunction extends MonotonicFunction {
270        private PortFunction(TypedIOPort port1, TypedIOPort port2) {
271            _port1 = port1;
272            _port2 = port2;
273        }
274
275        ///////////////////////////////////////////////////////////////
276        ////                       public inner methods            ////
277
278        /** Return the function result.
279         *  @return A Type.
280         */
281        @Override
282        public Object getValue() {
283            Type type1 = _port1.getType();
284            Type type2 = _port2.getType();
285
286            if (type1 == BaseType.UNKNOWN || type2 == BaseType.UNKNOWN) {
287                return BaseType.UNKNOWN;
288            } else if (type1 instanceof ArrayType
289                    && type2 instanceof ArrayType) {
290                Type elType1 = ((ArrayType) type1).getElementType();
291                Type elType2 = ((ArrayType) type2).getElementType();
292                CPO lattice = TypeLattice.lattice();
293                return lattice.leastUpperBound(elType1, elType2);
294            } else if (type1 instanceof MatrixType
295                    && type2 instanceof MatrixType) {
296                Type elType1 = ((MatrixType) type1).getElementType();
297                Type elType2 = ((MatrixType) type2).getElementType();
298                CPO lattice = TypeLattice.lattice();
299                return lattice.leastUpperBound(elType1, elType2);
300            } else {
301                return BaseType.UNKNOWN;
302            }
303        }
304
305        /** Return the type variables in this InequalityTerm.  If
306         *  neither type variable has been set, return an array of
307         *  size two, containing the InequalityTerms corresponding to
308         *  the port.  If exactly one is set, return the
309         *  InequalityTerm corresponding to the other port (in a size
310         *  one array).  If both have been set, return an empty
311         *  InequalityTerm array.
312         *  @return An array of InequalityTerm.
313         */
314        @Override
315        public InequalityTerm[] getVariables() {
316            InequalityTerm term1 = _port1.getTypeTerm();
317            InequalityTerm term2 = _port2.getTypeTerm();
318
319            if (term1.isSettable() && term2.isSettable()) {
320                InequalityTerm[] array = { term1, term2 };
321                return array;
322            } else if (term1.isSettable()) {
323                InequalityTerm[] array = { term1 };
324                return array;
325            } else if (term2.isSettable()) {
326                InequalityTerm[] array = { term2 };
327                return array;
328            }
329
330            return new InequalityTerm[0];
331        }
332
333        ///////////////////////////////////////////////////////////////
334        ////                       private inner variable          ////
335        private TypedIOPort _port1;
336
337        private TypedIOPort _port2;
338    }
339}