001/* Huffman code base class.
002
003 Copyright (c) 2004-2015 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.actor.lib.comm;
029
030import java.util.LinkedList;
031
032import ptolemy.actor.TypedIOPort;
033import ptolemy.actor.lib.Transformer;
034import ptolemy.data.ArrayToken;
035import ptolemy.data.DoubleToken;
036import ptolemy.data.StringToken;
037import ptolemy.data.expr.Parameter;
038import ptolemy.data.type.ArrayType;
039import ptolemy.data.type.BaseType;
040import ptolemy.kernel.CompositeEntity;
041import ptolemy.kernel.util.Attribute;
042import ptolemy.kernel.util.IllegalActionException;
043import ptolemy.kernel.util.NameDuplicationException;
044import ptolemy.kernel.util.Workspace;
045
046///////////////////////////////////////////////////////////////////
047//// HuffmanBasic
048
049/**
050 Given a probability distribution, generate the Huffman code book.
051 The probability distribution is given by the <i>pmf</i> parameter.
052 The corresponding alphabet is given by the <i>alphabet</i> parameter.
053 The code book is in a format of an array of strings, each string
054 consists of '0' and '1's. The code book is sent to the
055 <i>huffmanCodeBook</i> output port.
056
057 @author Ye Zhou
058 @version $Id$
059 @since Ptolemy II 4.1
060 @Pt.ProposedRating Red (zhouye)
061 @Pt.AcceptedRating Red (cxh)
062 */
063public class HuffmanBasic extends Transformer {
064    /** Construct an actor with the given container and name.
065     *  The output and trigger ports are also constructed.
066     *  @param container The container.
067     *  @param name The name of this actor.
068     *  @exception IllegalActionException If the entity cannot be contained
069     *   by the proposed container.
070     *  @exception NameDuplicationException If the container already has an
071     *   actor with this name.
072     */
073    public HuffmanBasic(CompositeEntity container, String name)
074            throws NameDuplicationException, IllegalActionException {
075        super(container, name);
076
077        pmf = new Parameter(this, "pmf");
078        pmf.setExpression("{0.5, 0.5}");
079        pmf.setTypeEquals(new ArrayType(BaseType.DOUBLE));
080
081        alphabet = new Parameter(this, "alphabet");
082        alphabet.setExpression("{0, 1}");
083        alphabet.setTypeAtLeast(ArrayType.ARRAY_BOTTOM);
084
085        // Declare port types.
086        huffmanCodeBook = new TypedIOPort(this, "huffmanCodeBook", false, true);
087        huffmanCodeBook.setTypeEquals(new ArrayType(BaseType.STRING));
088    }
089
090    ///////////////////////////////////////////////////////////////////
091    ////                     ports and parameters                  ////
092
093    /** The probability mass function. This parameter is an array
094     *  of doubles. Each element should be positive and the sum of
095     *  all elements should be 1.0. The default value is {0.5, 0.5}.
096     */
097    public Parameter pmf;
098
099    /** The alphabet of the input. This parameter is an ArrayToken.
100     *  Its default value is {0, 1}.
101     */
102    public Parameter alphabet;
103
104    /** A port that produces the Huffman code book generated
105     *  based on the probability mass function. It is an array
106     *  of strings.
107     */
108    public TypedIOPort huffmanCodeBook;
109
110    ///////////////////////////////////////////////////////////////////
111    ////                         public methods                    ////
112
113    /** Override the base class to set type constraints.
114     *  @param workspace The workspace for the new object.
115     *  @return A new instance of ArrayElement.
116     *  @exception CloneNotSupportedException If a derived class contains
117     *   an attribute that cannot be cloned.
118     */
119    @Override
120    public Object clone(Workspace workspace) throws CloneNotSupportedException {
121        HuffmanBasic newObject = (HuffmanBasic) super.clone(workspace);
122        newObject.alphabet.setTypeAtLeast(ArrayType.ARRAY_BOTTOM);
123
124        newObject._pmf = null;
125        return newObject;
126    }
127
128    ///////////////////////////////////////////////////////////////////
129    ////                  public inner classes                      ////
130
131    /** A class that defines the node in binary tree that is used
132     *  to construct the codebook of Huffman code.
133     */
134    public static class Node {
135
136        // FindBugs suggests making this class static so as to decrease
137        // the size of instances and avoid dangling references.
138
139        /** Construct the node with the given probability value
140         *  and its index in the <i>pmf</i> array.
141         * @param prob The given probability value.
142         * @param index The corresponding index in the pmf array.
143         */
144        public Node(double prob, int index) {
145            probability = prob;
146            indexInArray = index;
147            leftChild = null;
148            rightChild = null;
149            huffmanCode = "";
150        }
151
152        /** Construct the parent node given the left child
153         *  and the right child.
154         * @param left The left child.
155         * @param right The right child.
156         */
157        public Node(Node left, Node right) {
158            probability = left.probability + right.probability;
159            indexInArray = -1;
160            leftChild = left;
161            rightChild = right;
162            huffmanCode = "";
163        }
164
165        /** The probability of the node.
166         */
167        public double probability;
168
169        /** The corresponding index in the pmf array of this node.
170         *  If the value is -1, then this node is constructed by
171         *  combining at least two probabilities.
172         */
173        public int indexInArray;
174
175        /** The left child of the node.
176         */
177        public Node leftChild;
178
179        /** The right child of the node.
180         */
181        public Node rightChild;
182
183        /** The huffman code of this node.
184         */
185        public String huffmanCode;
186    }
187
188    ///////////////////////////////////////////////////////////////////
189    ////                         public methods                    ////
190
191    /** If the attribute being changed is <i>pmf</i>, then verify
192     *  all the elements are positive and their sum is 1.0.
193     *  @param attribute The attribute that changed.
194     *  @exception IllegalActionException If any element in <i>pmf</i>
195     *  is non-positive or the sum is not 1.0.
196     */
197    @Override
198    public void attributeChanged(Attribute attribute)
199            throws IllegalActionException {
200        _parametersInvalid = true;
201
202        if (attribute == pmf) {
203            ArrayToken pmfValue = (ArrayToken) pmf.getToken();
204            _pmf = new double[pmfValue.length()];
205
206            double sum = 0.0;
207
208            for (int i = 0; i < _pmf.length; i++) {
209                _pmf[i] = ((DoubleToken) pmfValue.getElement(i)).doubleValue();
210
211                if (_pmf[i] <= 0.0) {
212                    throw new IllegalActionException(this,
213                            "Probabilities must be positive!");
214                }
215
216                sum = sum + _pmf[i];
217            }
218
219            //if (!SignalProcessing.close(sum, 1.0))
220            //  throw new IllegalActionException(this,
221            //    "Parameter values is required to sum to one.");
222        } else {
223            super.attributeChanged(attribute);
224        }
225    }
226
227    /** Generate the Huffman codebook for the given <i>pmf</i>, and
228     *  encode the input into booleans and send them to the output port.
229     */
230    @Override
231    public void fire() throws IllegalActionException {
232        super.fire();
233        ArrayToken alphabetArrayToken = (ArrayToken) alphabet.getToken();
234
235        if (_pmf.length != alphabetArrayToken.length()) {
236            throw new IllegalActionException(this,
237                    "uncoded alphabet and pmf are required to be arrays"
238                            + "with same length.");
239        }
240
241        // Token[] alphabetTokens = new Token[_pmf.length];
242
243        // for (int i = 0; i < _pmf.length; i++) {
244        //     alphabetTokens[i] = alphabetArrayToken.getElement(i);
245        // }
246
247        if (_parametersInvalid) {
248            _parametersInvalid = false;
249            _codeBook = generateCodeBook(_pmf);
250
251            // FIXME: only produce the code book if the parameters
252            // have been updated.
253            StringToken[] codeBookTokens = new StringToken[_pmf.length];
254
255            for (int i = 0; i < _pmf.length; i++) {
256                codeBookTokens[i] = new StringToken(_codeBook[i]);
257            }
258
259            huffmanCodeBook.send(0,
260                    new ArrayToken(BaseType.STRING, codeBookTokens));
261        }
262    }
263
264    /** Generate the Huffman code book given the probability
265     *  mass function.
266     * @param pmf The probability mass function.
267     * @return The code book, where each codeword is a string
268     *  of '0' and '1'.
269     */
270    public String[] generateCodeBook(double[] pmf) {
271        String[] codeBook = new String[pmf.length];
272        LinkedList list = new LinkedList();
273
274        // Generate the huffman code book.
275        for (int i = 0; i < _pmf.length; i++) {
276            // Create a list of nodes;
277            Node node = new Node(_pmf[i], i);
278            list.add(node);
279        }
280
281        // Construct the binary tree.
282        while (list.size() > 1) {
283            Node node1 = _findMinNode(list);
284            list.remove(node1);
285
286            Node node2 = _findMinNode(list);
287            list.remove(node2);
288
289            // node2 has larger prob than node1.
290            Node newNode = new Node(node2, node1);
291            list.add(newNode);
292        }
293
294        // Now there is only one element in the list,
295        // and its probability should be 1.
296        Node root = (Node) list.get(0);
297        root.huffmanCode = "";
298        _setCode(root, codeBook);
299        return codeBook;
300    }
301
302    /** Initialize the actor by resetting the _parametersInvalid to true.
303     *  Creat a linked list to store the nodes for the binary tree.
304     *  @exception IllegalActionException If the parent class throws it.
305     */
306    @Override
307    public void initialize() throws IllegalActionException {
308        super.initialize();
309        _parametersInvalid = true;
310    }
311
312    ///////////////////////////////////////////////////////////////////
313    ////                         protected variables               ////
314
315    /** The huffman code book.
316     */
317    protected String[] _codeBook;
318
319    /** Flag that indicates if the parameters are invalid. If it is
320     *  true, then a new code book needs to be generated.
321     */
322    protected boolean _parametersInvalid;
323
324    /** The probability mass function.
325     */
326    protected double[] _pmf;
327
328    ///////////////////////////////////////////////////////////////////
329    ////                         private methods                   ////
330
331    /** Find the node with the minimum probability value in the
332     *  given linked list.
333     *  @param list The given linked list.
334     *  @return The node with the minimum probability value.
335     */
336    private Node _findMinNode(LinkedList list) {
337        double minProb = ((Node) list.get(0)).probability;
338        int index = 0;
339
340        for (int i = 1; i < list.size(); i++) {
341            if (((Node) list.get(i)).probability < minProb) {
342                index = i;
343                minProb = ((Node) list.get(i)).probability;
344            }
345        }
346
347        return (Node) list.get(index);
348    }
349
350    /** Set the Huffman codeword for the given node and all its children.
351     * @param node The given node.
352     * @param codeBook The code book to be generated.
353     */
354    private void _setCode(Node node, String[] codeBook) {
355        String parentCode = node.huffmanCode;
356        Node left;
357        Node right;
358
359        if ((left = node.leftChild) != null) {
360            String leftCode = parentCode + "0";
361            left.huffmanCode = leftCode;
362
363            if (left.indexInArray >= 0) {
364                codeBook[left.indexInArray] = leftCode;
365            } else {
366                _setCode(left, codeBook);
367            }
368        }
369
370        if ((right = node.rightChild) != null) {
371            String rightCode = parentCode + "1";
372            right.huffmanCode = rightCode;
373
374            if (right.indexInArray >= 0) {
375                codeBook[right.indexInArray] = rightCode;
376            } else {
377                _setCode(right, codeBook);
378            }
379        }
380    }
381}