blob: 6e3d808d6afd476d800bd7a711ea8622e0060af1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// author Kevin Lang, Oath Research
#include "fm85Compression.h"
#include "fm85Util.h"
#include <stdexcept>
#include <new>
/*********************************/
// The following material is in a separate file because it is so big.
#include "compressionData.data"
/***************************************************************/
/***************************************************************/
// Intentionally uses malloc instead of the custom allocator
// since it is for global initialization, not for allocating instances.
U8 * makeInversePermutation (U8 * permu, int length) {
U8 * inverse = (U8 *) malloc (((size_t) length) * sizeof(U8));
if (inverse == NULL) throw std::bad_alloc();
int i;
for (i = 0; i < length; i++) {
inverse[permu[i]] = i;
}
for (i = 0; i < length; i++) {
if (permu[inverse[i]] != i) throw std::logic_error("inverse permutation error");
}
return inverse;
}
/***************************************************************/
/***************************************************************/
/* Given an encoding table that maps unsigned bytes to codewords
of length at most 12, this builds a size-4096 decoding table */
// The second argument is typically 256, but can be other values such as 65.
// Intentionally uses malloc instead of the custom allocator
// since it is for global initialization, not for allocating instances.
U16 * makeDecodingTable (U16 * encodingTable, int numByteValues) {
int byteValue;
U16 * decodingTable = (U16 *) malloc (((size_t) 4096) * sizeof(U16));
if (decodingTable == NULL) throw std::bad_alloc();
for (byteValue=0; byteValue < numByteValues; byteValue++) {
int encodingEntry = encodingTable [byteValue];
int codeValue = encodingEntry & 0xfff;
int codeLength = encodingEntry >> 12;
int decodingEntry = (codeLength << 8) | byteValue;
int garbageLength = 12 - codeLength;
int numCopies = 1 << garbageLength;
int garbageBits;
for (garbageBits = 0; garbageBits < numCopies; garbageBits++) {
int extendedCodeValue = codeValue | (garbageBits << codeLength);
decodingTable[extendedCodeValue & 0xfff] = decodingEntry;
}
}
return (decodingTable);
}
/***************************************************************/
/***************************************************************/
void validateDecodingTable (U16 * decodingTable, U16 * encodingTable) {
int decodeThis;
for (decodeThis = 0; decodeThis < 4096; decodeThis++) {
int tmpD = decodingTable[decodeThis];
int decodedByte = tmpD & 0xff;
int decodedLength = tmpD >> 8;
int tmpE = encodingTable[decodedByte];
int encodedBitpattern = tmpE & 0xfff;
int encodedLength = tmpE >> 12;
// encodedBitpattern++; // uncomment this line to test the test
// encodedLength++; // uncomment this line to test the test
if (decodedLength != encodedLength) throw std::logic_error("decoded length error");
if (encodedBitpattern != (decodeThis & ((1 << decodedLength) - 1))) throw std::logic_error("bit pattern error");
}
}
/***************************************************************/
/***************************************************************/
void makeTheDecodingTables (void) {
int i;
lengthLimitedUnaryDecodingTable65 = makeDecodingTable (lengthLimitedUnaryEncodingTable65, 65);
validateDecodingTable (lengthLimitedUnaryDecodingTable65, lengthLimitedUnaryEncodingTable65);
for (i = 0; i < (16 + 6); i++) {
decodingTablesForHighEntropyByte[i] = makeDecodingTable(encodingTablesForHighEntropyByte[i], 256);
validateDecodingTable (decodingTablesForHighEntropyByte[i], encodingTablesForHighEntropyByte[i]);
}
for (i = 0; i < 16; i++) {
columnPermutationsForDecoding[i] = makeInversePermutation(columnPermutationsForEncoding[i],56);
}
// fprintf (stderr, "tables okay\n"); fflush (stderr);
}
void freeTheDecodingTables (void) {
int i;
free(lengthLimitedUnaryDecodingTable65);
for (i = 0; i < (16 + 6); i++) {
free(decodingTablesForHighEntropyByte[i]);
}
for (i = 0; i < 16; i++) {
free(columnPermutationsForDecoding[i]);
}
}
/***************************************************************/
/***************************************************************/
static inline void writeUnary (U32 * compressedWords,
Long * nextWordIndexPtr,
U64 * bitbufPtr,
int * bufbitsPtr,
Long the_value)
{
// printf("%ld writing\n", the_value);
if (compressedWords == NULL) throw std::logic_error("compressedWords == NULL");
if (nextWordIndexPtr == NULL) throw std::logic_error("nextWordIndexPtr == NULL");
if (bitbufPtr == NULL) throw std::logic_error("bitbufPtr == NULL");
if (bufbitsPtr == NULL) throw std::logic_error("bufbitsPtr == NULL");
Long nextWordIndex = *nextWordIndexPtr;
U64 bitbuf = *bitbufPtr;
int bufbits = *bufbitsPtr;
if (bufbits < 0 || bufbits > 31) throw std::out_of_range("bufbits out of range");
Long remaining = the_value;
while (remaining >= 16) {
remaining -= 16;
// Here we output 16 zeros, but we don't need to physically write them into bitbuf
// because it already contains zeros in that region.
bufbits += 16; // Record the fact that 16 bits of output have occurred.
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
}
if (remaining < 0 || remaining > 15) throw std::out_of_range("remaining out of range");
U64 theUnaryCode = 1ULL << remaining;
bitbuf |= theUnaryCode << bufbits;
bufbits += (1 + remaining);
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
*nextWordIndexPtr = nextWordIndex;
*bitbufPtr = bitbuf;
*bufbitsPtr = bufbits;
}
/***************************************************************/
/***************************************************************/
static inline Long readUnary (U32 * compressedWords,
Long * nextWordIndexPtr,
U64 * bitbufPtr,
int * bufbitsPtr)
{
if (compressedWords == NULL) throw std::logic_error("compressedWords == NULL");
if (nextWordIndexPtr == NULL) throw std::logic_error("nextWordIndexPtr == NULL");
if (bitbufPtr == NULL) throw std::logic_error("bitbufPtr == NULL");
if (bufbitsPtr == NULL) throw std::logic_error("bufbitsPtr == NULL");
Long nextWordIndex = *nextWordIndexPtr;
U64 bitbuf = *bitbufPtr;
int bufbits = *bufbitsPtr;
Long subTotal = 0;
readUnaryLoop:
MAYBE_FILL_BITBUF(compressedWords,nextWordIndex,8); // ensure 8 bits in bit buffer
// if (bufbits < 8) { // Prepare for an 8-bit peek into the bitstream.
// bitbuf |= (((U64) compressedWords[nextWordIndex++]) << bufbits);
// bufbits += 32;
// }
int peek8 = bitbuf & 0xffULL; // These 8 bits include either all or part of the Unary codeword.
int trailingZeros = byteTrailingZerosTable[peek8];
if (trailingZeros < 0 || trailingZeros > 8) throw std::out_of_range("trailingZeros out of range");
if (trailingZeros == 8) { // The codeword was partial, so read some more.
subTotal += 8;
bufbits -= 8;
bitbuf >>= 8;
goto readUnaryLoop;
}
bufbits -= (1+trailingZeros);
bitbuf >>= (1+trailingZeros);
*nextWordIndexPtr = nextWordIndex;
*bitbufPtr = bitbuf;
*bufbitsPtr = bufbits;
// printf("%ld READING\n", subTotal+trailingZeros); fflush (stdout);
return (subTotal+trailingZeros);
}
/***************************************************************/
/***************************************************************/
// This returns the number of compressedWords that were actually used.
// It is the caller's responsibility to ensure that the compressedWords array is long enough.
Long lowLevelCompressBytes (U8 * byteArray, // input
Long numBytesToEncode, // input
U16 * encodingTable, // input
U32 * compressedWords) { // output
Long byteIndex = 0;
Long nextWordIndex = 0;
U64 bitbuf = 0; /* bits are packed into this first, then are flushed to compressedWords */
int bufbits = 0; /* number of bits currently in bitbuf; must be between 0 and 31 */
for (byteIndex = 0; byteIndex < numBytesToEncode; byteIndex++) {
U64 codeInfo = (U64) encodingTable[byteArray[byteIndex]];
U64 codeVal = codeInfo & 0xfff;
int codeLen = codeInfo >> 12;
bitbuf |= (codeVal << bufbits);
bufbits += codeLen;
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
}
// Pad the bitstream with 11 zero-bits so that the decompressor's 12-bit peek can't overrun its input.
bufbits += 11;
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
if (bufbits > 0) { // We are done encoding now, so we flush the bit buffer.
if (bufbits >= 32) throw std::logic_error("bufbits >= 32");
compressedWords[nextWordIndex++] = (U32) (bitbuf & 0xffffffff);
bitbuf = 0; bufbits = 0; // not really necessary
}
return nextWordIndex;
}
/***************************************************************/
/***************************************************************/
void lowLevelUncompressBytes (U8 * byteArray, // output
Long numBytesToDecode, // input (but refers to the output)
U16 * decodingTable, // input
U32 * compressedWords, // input
Long numCompressedWords) { // input
Long byteIndex = 0;
Long wordIndex = 0;
U64 bitbuf = 0;
int bufbits = 0;
// printf ("Y\n"); fflush (stdout);
if (byteArray == NULL) throw std::logic_error("byteArray == NULL");
if (decodingTable == NULL) throw std::logic_error("decodingTable == NULL");
if (compressedWords == NULL) throw std::logic_error("compressedWords == NULL");
for (byteIndex = 0; byteIndex < numBytesToDecode; byteIndex++) {
// if (bufbits < 12) { // Prepare for a 12-bit peek into the bitstream.
// bitbuf |= (((U64) compressedWords[wordIndex++]) << bufbits);
// bufbits += 32;
// }
MAYBE_FILL_BITBUF(compressedWords,wordIndex,12); // ensure 12 bits in bit buffer
int peek12 = bitbuf & 0xfffULL; // These 12 bits will include an entire Huffman codeword.
int lookup = decodingTable[peek12];
int codeWordLength = lookup >> 8;
U8 decodedByte = lookup & 0xff;
byteArray[byteIndex] = decodedByte;
bitbuf >>= codeWordLength;
bufbits -= codeWordLength;
}
// Buffer over-run should be impossible unless there is a bug.
// However, we might as well check here.
if (wordIndex > numCompressedWords) throw std::logic_error("wordIndex > numCompressedWords");
// printf ("X\n"); fflush (stdout);
return;
}
/***************************************************************/
/***************************************************************/
// Here "pairs" refers to row/column pairs that specify
// the positions of surprising values in the bit matrix.
// returns the number of compressedWords actually used
Long lowLevelCompressPairs (U32 * pairArray, // input
Long numPairsToEncode, // input
Long numBaseBits, // input
U32 * compressedWords) { // output
Long pairIndex = 0;
Long nextWordIndex = 0;
U64 bitbuf = 0;
int bufbits = 0;
Long golombLoMask = (1LL << numBaseBits) - 1;
Long predictedRowIndex = 0;
Short predictedColIndex = 0;
for (pairIndex = 0; pairIndex < numPairsToEncode; pairIndex++) {
U32 rowCol = pairArray[pairIndex];
Long rowIndex = (Long) (rowCol >> 6);
Short colIndex = (Short) (rowCol & 63);
if (rowIndex != predictedRowIndex) { predictedColIndex = 0; }
if (rowIndex < predictedRowIndex) throw std::logic_error("rowIndex < predictedRowIndex");
if (colIndex < predictedColIndex) throw std::logic_error("colIndex < predictedColIndex");
Long yDelta = rowIndex - predictedRowIndex;
Short xDelta = colIndex - predictedColIndex;
predictedRowIndex = rowIndex;
predictedColIndex = colIndex + 1;
U64 codeInfo = (U64) lengthLimitedUnaryEncodingTable65[xDelta];
U64 codeVal = codeInfo & 0xfff;
int codeLen = codeInfo >> 12;
bitbuf |= (codeVal << bufbits);
bufbits += codeLen;
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
Long golombLo = yDelta & golombLoMask;
Long golombHi = yDelta >> numBaseBits;
writeUnary (compressedWords, &nextWordIndex, &bitbuf, &bufbits, golombHi);
bitbuf |= golombLo << bufbits;
bufbits += numBaseBits;
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
}
// Pad the bitstream so that the decompressor's 12-bit peek can't overrun its input.
Long padding = 10LL - numBaseBits; // should be 10LL
if (padding < 0) padding = 0;
bufbits += padding;
MAYBE_FLUSH_BITBUF(compressedWords,nextWordIndex);
if (bufbits > 0) { // We are done encoding now, so we flush the bit buffer.
if (bufbits >= 32) throw std::logic_error("bufbits >= 32");
compressedWords[nextWordIndex++] = (U32) (bitbuf & 0xffffffff);
bitbuf = 0; bufbits = 0; // not really necessary
}
return nextWordIndex;
}
/***************************************************************/
/***************************************************************/
void lowLevelUncompressPairs (U32 * pairArray, // output
Long numPairsToDecode, // input (but refers to the output)
Long numBaseBits, // input
U32 * compressedWords, // input
Long numCompressedWords) { // input
Long pairIndex = 0;
Long wordIndex = 0;
U64 bitbuf = 0;
int bufbits = 0;
Long golombLoMask = (1LL << numBaseBits) - 1;
Long predictedRowIndex = 0;
Short predictedColIndex = 0;
// for each pair we need to read:
// xDelta (12-bit length-limited unary)
// yDeltaHi (unary)
// yDeltaLo (basebits)
for (pairIndex = 0; pairIndex < numPairsToDecode; pairIndex++) {
MAYBE_FILL_BITBUF(compressedWords,wordIndex,12); // ensure 12 bits in bit buffer
int peek12 = bitbuf & 0xfffULL;
int lookup = lengthLimitedUnaryDecodingTable65[peek12];
int codeWordLength = lookup >> 8;
Short xDelta = lookup & 0xff;
bitbuf >>= codeWordLength;
bufbits -= codeWordLength;
Long golombHi = readUnary (compressedWords, &wordIndex, &bitbuf, &bufbits);
MAYBE_FILL_BITBUF(compressedWords,wordIndex,numBaseBits); // ensure numBaseBits in bit buffer
Long golombLo = bitbuf & golombLoMask;
bitbuf >>= numBaseBits;
bufbits -= numBaseBits;
Long yDelta = (golombHi << numBaseBits) | golombLo;
// Now that we have yDelta and xDelta, we can compute the pair's row and column.
if (yDelta > 0) { predictedColIndex = 0; }
Long rowIndex = predictedRowIndex + yDelta;
Short colIndex = predictedColIndex + xDelta;
U32 rowCol = (rowIndex << 6) | colIndex;
pairArray[pairIndex] = rowCol;
predictedRowIndex = rowIndex;
predictedColIndex = colIndex + 1;
}
if (wordIndex > numCompressedWords) throw std::logic_error("wordIndex > numCompressedWords"); // check for buffer over-run
}
/***************************************************************/
/***************************************************************/
Long safeLengthForCompressedPairBuf (Long k, Long numPairs, Long numBaseBits) {
if (numPairs <= 0) throw std::invalid_argument("numPairs <= 0");
// Long ybits = k + numPairs; // simpler and safer UB
// The following tighter UB on ybits is based on page 198
// of the textbook "Managing Gigabytes" by Witten, Moffat, and Bell.
// Notice that if numBaseBits == 0 it coincides with (k + numPairs).
Long ybits = numPairs * (1LL + numBaseBits) + (k >> numBaseBits);
Long xbits = 12 * numPairs;
Long padding = 10LL - numBaseBits;
if (padding < 0) padding = 0;
Long bits = xbits + ybits + padding;
return (divideLongsRoundingUp(bits, 32));
}
// Explanation of padding: we write
// 1) xdelta (huffman, provides at least 1 bit, requires 12-bit lookahead)
// 2) ydeltaGolombHi (unary, provides at least 1 bit, requires 8-bit lookahead)
// 3) ydeltaGolombLo (straight B bits).
// So the 12-bit lookahead is the tight constraint, but there are at least (2 + B) bits emitted,
// so we would be safe with max (0, 10 - B) bits of padding at the end of the bitstream.
/***************************************************************/
Long safeLengthForCompressedWindowBuf (Long k) { // measured in 32-bit words
Long bits = 12 * k + 11; // 11 bits of padding, due to 12-bit lookahead, with 1 bit certainly present.
return (divideLongsRoundingUp(bits, 32));
}
/***************************************************************/
/***************************************************************/
Short determinePseudoPhase (Short lgK, Long c) {
Long k = (1LL << lgK);
// This midrange logic produces pseudo-phases. They are used to select encoding tables.
// The thresholds were chosen by hand after looking at plots of measured compression.
if (1000 * c < 2375 * k) {
if ( 4 * c < 3 * k) return ( 16 + 0 ); // midrange table
else if ( 10 * c < 11 * k) return ( 16 + 1 ); // midrange table
else if ( 100 * c < 132 * k) return ( 16 + 2 ); // midrange table
else if ( 3 * c < 5 * k) return ( 16 + 3 ); // midrange table
else if (1000 * c < 1965 * k) return ( 16 + 4 ); // midrange table
else if (1000 * c < 2275 * k) return ( 16 + 5 ); // midrange table
else return ( 6 ); // steady-state table employed before its actual phase
}
else { // This steady-state logic produces true phases. They are used to select
// encoding tables, and also column permutations for the "Sliding" flavor.
if (lgK < 4) throw std::logic_error("lgK < 4");
Long tmp = c >> (lgK - 4);
Long phase = tmp & 15;
if (phase < 0 || phase >= 16) throw std::out_of_range("wrong phase");
return ((Short) phase);
}
}
/***************************************************************/
/***************************************************************/
void compressTheWindow (FM85 * target, FM85 * source) {
Long k = (1LL << source->lgK);
Long windowBufLen = safeLengthForCompressedWindowBuf (k);
U32 * windowBuf = (U32 *) fm85alloc ((size_t) (windowBufLen * sizeof(U32)));
if (windowBuf == NULL) throw std::logic_error("windowBuf == NULL");
Short pseudoPhase = determinePseudoPhase (source->lgK, source->numCoupons);
target->cwLength = lowLevelCompressBytes (source->slidingWindow, k,
encodingTablesForHighEntropyByte[pseudoPhase],
windowBuf);
// At this point we free the unused portion of the compression output buffer.
// Note: realloc caused strange timing spikes for lgK = 11 and 12.
U32 * shorterBuf = (U32 *) fm85alloc (((size_t) target->cwLength) * sizeof(U32));
if (shorterBuf == NULL) throw std::bad_alloc();
memcpy ((void *) shorterBuf, (void *) windowBuf, ((size_t) target->cwLength) * sizeof(U32));
fm85free (windowBuf);
target->compressedWindow = shorterBuf;
return;
}
/***************************************************************/
/***************************************************************/
void uncompressTheWindow (FM85 * target, FM85 * source) {
Long k = (1LL << source->lgK);
U8 * window = (U8 *) fm85alloc ((size_t) (k * sizeof(U8)));
if (window == NULL) throw std::bad_alloc();
// zeroing not needed here (unlike the Hybrid Flavor)
if (target->slidingWindow != NULL) throw std::logic_error("target->slidingWindow != NULL");
target->slidingWindow = window;
Short pseudoPhase = determinePseudoPhase (source->lgK, source->numCoupons);
if (source->compressedWindow == NULL) throw std::logic_error("source->compressedWindow == NULL");
lowLevelUncompressBytes (target->slidingWindow, k,
decodingTablesForHighEntropyByte[pseudoPhase],
source->compressedWindow,
source->cwLength);
return;
}
/***************************************************************/
/***************************************************************/
void compressTheSurprisingValues (FM85 * target, FM85 * source, U32 * pairs, Long numPairs) {
if (numPairs <= 0) throw std::invalid_argument("numPairs <= 0");
target->numCompressedSurprisingValues = numPairs;
Long k = (1LL << source->lgK);
Long numBaseBits = golombChooseNumberOfBaseBits (k + numPairs, numPairs);
Long pairBufLen = safeLengthForCompressedPairBuf (k, numPairs, numBaseBits);
U32 * pairBuf = (U32 *) fm85alloc ((size_t) (pairBufLen * sizeof(U32)));
if (pairBuf == NULL) throw std::bad_alloc();
target->csvLength = lowLevelCompressPairs (pairs, numPairs, numBaseBits, pairBuf);
// At this point we free the unused portion of the compression output buffer.
// Note: realloc caused strange timing spikes for lgK = 11 and 12.
U32 * shorterBuf = (U32 *) fm85alloc (((size_t) target->csvLength) * sizeof(U32));
if (shorterBuf == NULL) throw std::bad_alloc();
memcpy ((void *) shorterBuf, (void *) pairBuf, ((size_t) target->csvLength) * sizeof(U32));
fm85free (pairBuf);
target->compressedSurprisingValues = shorterBuf;
}
/***************************************************************/
/***************************************************************/
// allocates and returns an array of uncompressed pairs.
// the length of this array is known to the source sketch.
U32 * uncompressTheSurprisingValues (FM85 * source) {
if (source->isCompressed != 1) throw std::logic_error("not compressed");
Long k = (1LL << source->lgK);
Long numPairs = source->numCompressedSurprisingValues;
if (numPairs <= 0) throw std::logic_error("numPairs <= 0");
U32 * pairs = (U32 *) fm85alloc ((size_t) numPairs * sizeof(U32));
if (pairs == NULL) throw std::bad_alloc();
Long numBaseBits = golombChooseNumberOfBaseBits (k + numPairs, numPairs);
lowLevelUncompressPairs(pairs, numPairs, numBaseBits,
source->compressedSurprisingValues, source->csvLength);
return (pairs);
}
/***************************************************************/
/***************************************************************/
void compressEmptyFlavor (FM85 * target, FM85 * source) {
return; // nothing to do, so just return
}
/***************************************************************/
void uncompressEmptyFlavor (FM85 * target, FM85 * source) {
return; // nothing to do, so just return
}
/***************************************************************/
/***************************************************************/
void compressSparseFlavor (FM85 * target, FM85 * source) {
if (source->slidingWindow != NULL) throw std::logic_error("source->slidingWindow != NULL"); // there is no window to compress
Long numPairs = 0;
U32 * pairs = u32TableUnwrappingGetItems (source->surprisingValueTable, &numPairs);
introspectiveInsertionSort(pairs, 0, numPairs-1);
compressTheSurprisingValues (target, source, pairs, numPairs);
if (pairs) fm85free (pairs);
return;
}
/***************************************************************/
void uncompressSparseFlavor (FM85 * target, FM85 * source) {
if (source->compressedWindow != NULL) throw std::logic_error("source->compressedWindow != NULL");
if (source->compressedSurprisingValues == NULL) throw std::logic_error("source->compressedSurprisingValues == NULL");
U32 * pairs = uncompressTheSurprisingValues (source);
Long numPairs = source->numCompressedSurprisingValues;
u32Table * table = makeU32TableFromPairsArray (pairs, numPairs, source->lgK);
target->surprisingValueTable = table;
fm85free (pairs);
return;
}
/***************************************************************/
/***************************************************************/
// The empty space that this leaves at the beginning of the output array
// will be filled in later by the caller.
U32 * trickyGetPairsFromWindow (U8 * window, Long k, Long numPairsToGet, Long emptySpace) {
Long outputLength = emptySpace + numPairsToGet;
U32 * pairs = (U32 *) fm85alloc ((size_t) (outputLength * sizeof(U32)));
if (pairs == NULL) throw std::bad_alloc();
Long rowIndex = 0;
Long pairIndex = emptySpace;
for (rowIndex = 0; rowIndex < k; rowIndex++) {
U8 byte = window[rowIndex];
while (byte != 0) {
Short colIndex = byteTrailingZerosTable[byte];
// assert (colIndex < 8);
byte = byte ^ (1 << colIndex); // erase the 1
pairs[pairIndex++] = (U32) ((rowIndex << 6) | colIndex);
}
}
if (pairIndex != outputLength) throw std::logic_error("pairIndex != outputLength");
return (pairs);
}
/***************************************************************/
// This is complicated because it effectively builds a Sparse version
// of a Pinned sketch before compressing it. Hence the name Hybrid.
void compressHybridFlavor (FM85 * target, FM85 * source) {
// Long i;
Long k = (1LL << source->lgK);
Long numPairsFromTable = 0;
U32 * pairsFromTable = u32TableUnwrappingGetItems (source->surprisingValueTable, &numPairsFromTable);
introspectiveInsertionSort(pairsFromTable, 0, numPairsFromTable-1);
if (source->slidingWindow == NULL) throw std::logic_error("source->slidingWindow == NULL");
if (source->windowOffset != 0) throw std::logic_error("source->windowOffset != 0");
Long numPairsFromArray = source->numCoupons - numPairsFromTable; // because the window offset is zero
U32 * allPairs = trickyGetPairsFromWindow (source->slidingWindow, k, numPairsFromArray, numPairsFromTable);
u32Merge (pairsFromTable, 0, numPairsFromTable,
allPairs, numPairsFromTable, numPairsFromArray,
allPairs, 0); // note the overlapping subarray trick
// for (i = 0; i < source->numCoupons-1; i++) { assert (allPairs[i] < allPairs[i+1]); }
compressTheSurprisingValues (target, source, allPairs, source->numCoupons);
if (pairsFromTable) fm85free (pairsFromTable);
fm85free (allPairs);
return;
}
/***************************************************************/
void uncompressHybridFlavor (FM85 * target, FM85 * source) {
if (source->compressedWindow != NULL) throw std::logic_error("source->compressedWindow != NULL");
if (source->compressedSurprisingValues == NULL) throw std::logic_error("source->compressedSurprisingValues == NULL");
U32 * pairs = uncompressTheSurprisingValues (source);
Long numPairs = source->numCompressedSurprisingValues;
// In the hybrid flavor, some of these pairs actually
// belong in the window, so we will separate them out,
// moving the "true" pairs to the bottom of the array.
Long k = (1LL << source->lgK);
U8 * window = (U8 *) fm85alloc ((size_t) (k * sizeof(U8)));
if (window == NULL) throw std::bad_alloc();
bzero ((void *) window, (size_t) k); // important: zero the memory
Long nextTruePair = 0;
Long i;
for (i = 0; i < numPairs; i++) {
U32 rowCol = pairs[i];
if (rowCol == ALL32BITS) throw std::logic_error("rowCol == ALL32BITS");
Short col = (Short) (rowCol & 63);
if (col < 8) {
Long row = (Long) (rowCol >> 6);
window[row] |= (1 << col); // set the window bit
}
else {
pairs[nextTruePair++] = rowCol; // move true pair down
}
}
if (source->windowOffset != 0) throw std::logic_error("source->windowOffset != 0");
target->windowOffset = 0;
u32Table * table = makeU32TableFromPairsArray (pairs,
nextTruePair,
source->lgK);
target->surprisingValueTable = table;
target->slidingWindow = window;
fm85free (pairs);
return;
}
/***************************************************************/
/***************************************************************/
void compressPinnedFlavor (FM85 * target, FM85 * source) {
compressTheWindow (target, source);
Long numPairs = source->surprisingValueTable->numItems;
if (numPairs > 0) {
Long chkNumPairs;
U32 * pairs = u32TableUnwrappingGetItems (source->surprisingValueTable, &chkNumPairs);
if (chkNumPairs != numPairs) throw std::logic_error("chkNumPairs != numPairs");
// Here we subtract 8 from the column indices. Because they are stored in the low 6 bits
// of each rowCol pair, and because no column index is less than 8 for a "Pinned" sketch,
// I believe we can simply subtract 8 from the pairs themselves.
Long i; // shift the columns over by 8 positions before compressing (because of the window)
for (i = 0; i < numPairs; i++) {
if ((pairs[i] & 63) < 8) throw std::logic_error("(pairs[i] & 63) < 8");
pairs[i] -= 8;
}
introspectiveInsertionSort(pairs, 0, numPairs-1);
compressTheSurprisingValues (target, source, pairs, numPairs);
if (pairs) fm85free (pairs);
}
return;
}
/***************************************************************/
void uncompressPinnedFlavor (FM85 * target, FM85 * source) {
if (source->compressedWindow == NULL) throw std::logic_error("source->compressedWindow == NULL");
uncompressTheWindow (target, source);
Long numPairs = source->numCompressedSurprisingValues;
if (numPairs == 0) {
target->surprisingValueTable = u32TableMake (2, 6 + source->lgK);
}
else {
if (numPairs <= 0) throw std::logic_error("numPairs <= 0");
if (source->compressedSurprisingValues == NULL) throw std::logic_error("source->compressedSurprisingValues == NULL");
U32 * pairs = uncompressTheSurprisingValues (source);
Long i; // undo the compressor's 8-column shift
for (i = 0; i < numPairs; i++) {
if ((pairs[i] & 63) >= 56) throw std::logic_error("(pairs[i] & 63) >= 56");
pairs[i] += 8;
}
u32Table * table = makeU32TableFromPairsArray (pairs, numPairs, source->lgK);
target->surprisingValueTable = table;
fm85free (pairs);
}
return;
}
/***************************************************************/
/***************************************************************/
// Complicated by the existence of both a left fringe and a right fringe.
void compressSlidingFlavor (FM85 * target, FM85 * source) {
compressTheWindow (target, source);
Long numPairs = source->surprisingValueTable->numItems;
if (numPairs > 0) {
Long chkNumPairs;
U32 * pairs = u32TableUnwrappingGetItems (source->surprisingValueTable, &chkNumPairs);
if (chkNumPairs != numPairs) throw std::logic_error("chkNumPairs != numPairs");
// Here we apply a complicated transformation to the column indices, which
// changes the implied ordering of the pairs, so we must do it before sorting.
Short pseudoPhase = determinePseudoPhase (source->lgK, source->numCoupons); // NB
if (pseudoPhase >= 16) throw std::logic_error("pseudoPhase >= 16");
U8 * permutation = columnPermutationsForEncoding[pseudoPhase];
Short offset = source->windowOffset;
if (offset <= 0 || offset > 56) throw std::out_of_range("offset out of range");
Long i;
for (i = 0; i < numPairs; i++) {
U32 rowCol = pairs[i];
Long row = (Long) (rowCol >> 6);
Short col = (Short) (rowCol & 63);
// first rotate the columns into a canonical configuration: new = ((old - (offset+8)) + 64) mod 64
col = (col + 56 - offset) & 63;
if (col < 0 || col >= 56) throw std::out_of_range("col out of range");
// then apply the permutation
col = permutation[col];
pairs[i] = (U32) ((row << 6) | col);
}
introspectiveInsertionSort(pairs, 0, numPairs-1);
compressTheSurprisingValues (target, source, pairs, numPairs);
if (pairs) fm85free (pairs);
}
return;
}
/***************************************************************/
void uncompressSlidingFlavor (FM85 * target, FM85 * source) {
if (source->compressedWindow == NULL) throw std::logic_error("source->compressedWindow == NULL");
uncompressTheWindow (target, source);
Long numPairs = source->numCompressedSurprisingValues;
if (numPairs == 0) {
target->surprisingValueTable = u32TableMake (2, 6 + source->lgK);
}
else {
if (numPairs <= 0) throw std::logic_error("numPairs <= 0");
if (source->compressedSurprisingValues == NULL) throw std::logic_error("source->compressedSurprisingValues == NULL");
U32 * pairs = uncompressTheSurprisingValues (source);
Short pseudoPhase = determinePseudoPhase (source->lgK, source->numCoupons); // NB
if (pseudoPhase >= 16) throw std::logic_error("pseudoPhase >= 16");
U8 * permutation = columnPermutationsForDecoding[pseudoPhase];
Short offset = source->windowOffset;
if (offset <= 0 || offset > 56) throw std::out_of_range("offset out of range");
Long i;
for (i = 0; i < numPairs; i++) {
U32 rowCol = pairs[i];
Long row = (Long) (rowCol >> 6);
Short col = (Short) (rowCol & 63);
// first undo the permutation
col = permutation[col];
// then undo the rotation: old = (new + (offset+8)) mod 64
col = (col + (offset+8)) & 63;
pairs[i] = (U32) ((row << 6) | col);
}
u32Table * table = makeU32TableFromPairsArray (pairs, numPairs, source->lgK);
target->surprisingValueTable = table;
fm85free (pairs);
}
return;
}
/***************************************************************/
/***************************************************************/
// Note: in the final system, compressed and uncompressed sketches will have different types
FM85 * fm85Compress (FM85 * source) {
if (source->isCompressed != 0) throw std::invalid_argument("already compressed");
FM85 * target = (FM85 *) fm85alloc (sizeof(FM85));
if (target == NULL) throw std::bad_alloc();
target->lgK = source->lgK;
target->numCoupons = source->numCoupons;
target->windowOffset = source->windowOffset;
target->firstInterestingColumn = source->firstInterestingColumn;
target->mergeFlag = source->mergeFlag;
target->kxp = source->kxp;
target->hipEstAccum = source->hipEstAccum;
target->hipErrAccum = source->hipErrAccum;
target->isCompressed = 1;
// initialize the variables that belong in a compressed sketch
target->numCompressedSurprisingValues = 0;
target->compressedSurprisingValues = (U32 *) NULL;
target->csvLength = 0;
target->compressedWindow = (U32 *) NULL;
target->cwLength = 0;
// clear the variables that don't belong in a compressed sketch
target->slidingWindow = NULL;
target->surprisingValueTable = NULL;
enum flavorType flavor = determineSketchFlavor(source);
switch (flavor) {
case EMPTY: compressEmptyFlavor (target, source); break;
case SPARSE:
compressSparseFlavor (target, source);
if (target->compressedWindow != NULL) throw std::logic_error("target->compressedWindow != NULL");
if (target->compressedSurprisingValues == NULL) throw std::logic_error("target->compressedSurprisingValues == NULL");
break;
case HYBRID:
compressHybridFlavor (target, source);
if (target->compressedWindow != NULL) throw std::logic_error("target->compressedWindow != NULL");
if (target->compressedSurprisingValues == NULL) throw std::logic_error("target->compressedSurprisingValues == NULL");
break;
case PINNED:
compressPinnedFlavor (target, source);
if (target->compressedWindow == NULL) throw std::logic_error("target->compressedWindow != NULL");
// assert (target->compressedSurprisingValues != NULL);
break;
case SLIDING:
compressSlidingFlavor(target, source);
if (target->compressedWindow == NULL) throw std::logic_error("target->compressedWindow == NULL");
// assert (target->compressedSurprisingValues != NULL);
break;
default: throw std::logic_error("Unknown sketch flavor");
}
return target;
}
/***************************************************************/
/***************************************************************/
// Note: in the final system, compressed and uncompressed sketches will have different types
FM85 * fm85Uncompress (FM85 * source) {
if (source->isCompressed != 1) throw std::invalid_argument("not compressed");
FM85 * target = (FM85 *) fm85alloc (sizeof(FM85));
if (target == NULL) throw std::bad_alloc();
target->lgK = source->lgK;
target->numCoupons = source->numCoupons;
target->windowOffset = source->windowOffset;
target->firstInterestingColumn = source->firstInterestingColumn;
target->mergeFlag = source->mergeFlag;
target->kxp = source->kxp;
target->hipEstAccum = source->hipEstAccum;
target->hipErrAccum = source->hipErrAccum;
target->isCompressed = 0;
// initialize the variables that belong in an updateable sketch
target->slidingWindow = (U8 *) NULL;
target->surprisingValueTable = (u32Table *) NULL;
// clear the variables that don't belong in an updateable sketch
target->numCompressedSurprisingValues = 0;
target->compressedSurprisingValues = (U32 *) NULL;
target->csvLength = 0;
target->compressedWindow = (U32 *) NULL;
target->cwLength = 0;
enum flavorType flavor = determineSketchFlavor(source);
switch (flavor) {
case EMPTY: uncompressEmptyFlavor (target, source); break;
case SPARSE:
if (source->compressedWindow != NULL) throw std::logic_error("source->compressedWindow != NULL");
uncompressSparseFlavor (target, source);
break;
case HYBRID:
uncompressHybridFlavor (target, source);
break;
case PINNED:
if (source->compressedWindow == NULL) throw std::logic_error("source->compressedWindow == NULL");
uncompressPinnedFlavor (target, source);
break;
case SLIDING: uncompressSlidingFlavor(target, source); break;
default: std::logic_error("Unknown sketch flavor");
}
return target;
}