hackport-0.6.6: cabal/cabal-testsuite/PackageTests/Regression/T5309/memoized-tcm/costMatrix.h
/** costMatrix object to provide for a memoizable cost lookup table. Table is indexed by two
* dcElement values, and returns an int, for the cost. In addition, an additional dcElement
* is passed in by reference, and the median value of the two input elements is placed there.
* The getCost function is designed to interface directly with C.
*
* The key lookup is an ordered pair, so when looking up transition a -> b, a must go in as
* first in pair
*
* WARNING: In the interest of speed this code does no "type checking" to make sure that the
* two passed deElements are of the same type, i.e. that they have the same alphabet length.
* Any such checks should be done exterior to this library.
*/
#ifndef _COSTMATRIX_H
#define _COSTMATRIX_H
#define DEBUG 0
#include <climits>
#include <cstdlib>
#include <unordered_map>
#ifdef __cplusplus
extern "C" {
#endif
#include "dynamicCharacterOperations.h"
/** Next three fns defined here to use on C side. */
costMatrix_p construct_CostMatrix_C (size_t alphSize, int* tcm);
void destruct_CostMatrix_C (costMatrix_p mytype);
int call_getSetCost_C (costMatrix_p untyped_self, dcElement_t* left, dcElement_t* right, dcElement_t* retMedian);
#ifdef __cplusplus
}
#endif
typedef std::pair<dcElement_t, dcElement_t> keys_t;
typedef std::pair<int, packedChar*> costMedian_t;
typedef std::pair<keys_t, costMedian_t> mapAccessPair_t;
typedef void* costMatrix_p;
/** Allocate room for a costMedian_t. Assumes alphabetSize is already initialized. */
costMedian_t* allocCostMedian_t (size_t alphabetSize);
/** dealloc costMedian_t. */
void freeCostMedian_t (costMedian_t* toFree);
/** Allocate room for a keys_t. */
keys_t* allocKeys_t (size_t alphSize);
/** dealloc keys_t. Calls various other free fns. */
void freeKeys_t (const keys_t* toFree);
/** Allocate space for Pair<keys_t, costMedian_t>, calling allocators for both types. */
mapAccessPair_t* allocateMapAccessPair (size_t alphSize);
/** Hashes two `dcElement`s, and returns an order-dependent hash value. In this case
* "order dependent" means that the order of the arrays within the `dcElement`s matter,
* and the order that the `dcElement`s are sent in also matters, as is necessary for a
* non-symmetric tcm.
*
* First loops through each `dcElement` and combines all of the element values (recall that a
* `dcElement` has two fields, the second of which is the element, and is an array of `uint64_t`s)
* using two different seeds, then combines the two resulting values.
*/
struct KeyHash {
/** Following hash_combine code modified from here (seems to be based on Boost):
* http://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x
*/
std::size_t hash_combine (const dcElement_t lhs, const dcElement_t rhs) const {
std::size_t left_seed = 3141592653; // PI used as arbitrarily random seed
std::size_t right_seed = 2718281828; // E used as arbitrarily random seed
std::hash<uint64_t> hasher;
size_t elemArrCount = dcElemSize(lhs.alphSize);
for (size_t i = 0; i < elemArrCount; i++) {
left_seed ^= hasher(lhs.element[i]) + 0x9e3779b9 + (left_seed << 6) + (left_seed >> 2);
right_seed ^= hasher(rhs.element[i]) + 0x9e3779b9 + (right_seed << 6) + (right_seed >> 2);
}
left_seed ^= hasher(right_seed) + 0x9e3779b9 + (left_seed << 6) + (left_seed >> 2);
return left_seed;
}
std::size_t operator()(const keys_t& k) const
{
return hash_combine (k.first, k.second);
}
};
struct KeyEqual {
// Return true if every `uint64_t` in lhs->element and rhs->element is equal, else false.
bool operator()(const keys_t& lhs, const keys_t& rhs) const
{
// Assert that all key components share the same alphSize value
if ( lhs.first.alphSize != rhs.first.alphSize
|| lhs.first.alphSize != lhs.second.alphSize
|| lhs.second.alphSize != rhs.second.alphSize) {
return false;
}
//Assert that the left key elements match the right key elements
size_t elemArrWidth = dcElemSize(lhs.first.alphSize);
for (size_t i = 0; i < elemArrWidth; i++) {
if (lhs.first.element[i] != rhs.first.element[i]) {
return false;
}
if (lhs.second.element[i] != rhs.second.element[i]) {
return false;
}
}
return true;
}
};
typedef std::unordered_map<keys_t, costMedian_t, KeyHash, KeyEqual>::const_iterator mapIterator;
class CostMatrix
{
public:
// CostMatrix();
CostMatrix(size_t alphSize, int* tcm);
~CostMatrix();
/** Getter only for cost. Necessary for testing, to insure that particular
* key pair has, in fact, already been inserted into lookup table.
*/
int getCostMedian(dcElement_t* left, dcElement_t* right, dcElement_t* retMedian);
/** Acts as both a setter and getter, mutating myMap.
*
* Receives two dcElements and computes the transformation cost as well as
* the median for the two. Puts the median and alphabet size into retMedian,
* which must therefore by necessity be allocated elsewhere.
*
* This function allocates _if necessary_. So freeing inputs after a call will not
* cause invalid reads from the cost matrix.
*/
int getSetCostMedian(dcElement_t* left, dcElement_t* right, dcElement_t* retMedian);
private:
std::unordered_map <keys_t, costMedian_t, KeyHash, KeyEqual> myMatrix;
std::unordered_map <keys_t, costMedian_t, KeyHash, KeyEqual> hasher;
size_t alphabetSize;
/** Stored unambiguous tcm, necessary to do first calls to findDistance() without having to rewrite findDistance()
* and computeCostMedian()
*/
int *tcm;
/** Takes in a `keys_t` and a `costMedian_t` and updates myMap to store the new values,
* with @key as a key, and @median as the value.
*/
void setValue(keys_t* key, costMedian_t* median);
/** Takes in a pair of keys_t (each of which is a single `dcElement`) and computes their lowest-cost median.
* Uses a Sankoff-like algorithm, where all bases are considered, and the lowest cost bases are included in the
* cost and median calculations. That means a base might appear in the median that is not present in either of
* the two elements being compared.
*/
costMedian_t* computeCostMedian(keys_t key);
/** Find distance between an ambiguous nucleotide and an unambiguous ambElem. Return that value and the median.
* @param ambElem is ambiguous input.
* @param nucleotide is unambiguous.
* @param median is used to return the calculated median value.
*
* This fn is necessary because there isn't yet a cost matrix set up, so it's not possible to
* look up ambElems, therefore we must loop over possible values of the ambElem
* and find the lowest cost median.
*
* Nota bene: Requires symmetric, if not metric, matrix. TODO: Is this true? If so fix it?
*/
int findDistance (keys_t* searchKey, dcElement_t* ambElem);
/** Takes in an initial TCM, which is actually just a row-major array, creates hash table of costs
* where cost is least cost between two elements, and medians, where median is union of characters.
*
* Nota bene:
* Can only be called once this.alphabetSize has been set.
*/
void initializeMatrix ();
// DEPRECATED!!!
/** Takes in a pair of keys_t (each of which is a single `dcElement`) and computes their lowest-cost median.
* Contrast with computeCostMedian(). In this algorithm only bases which are present in at least one of
* the two elements being compared are considered.
*/
/* costMedian_t* computeCostMedianFitchy(keys_t keys); */
};
#endif // COSTMATRIX_H