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}