/* ************************************************************************* *
* This file is part of the `grup` package. *
* *
* Copyright 2015 Maciej Bartoszuk, Anna Cena, Marek Gagolewski, *
* *
* 'grup' is free software: you can redistribute it and/or *
* modify it under the terms of the GNU Lesser General Public License *
* as published by the Free Software Foundation, either version 3 *
* of the License, or (at your option) any later version. *
* *
* 'grup' is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with 'grup'. *
* If not, see . *
* ************************************************************************* */
#include "hclust2_mstbased_gini.h"
#include "hclust2_vptree_single.h"
using namespace grup;
// constructor (OK, we all know what this is, but I label it for faster in-code search)
HClustMSTbasedGini::HClustMSTbasedGini(Distance* dist, HClustOptions* opts) :
opts(opts),
n(dist->getObjectCount()),
#ifdef GENERATE_STATS
stats(),
#endif
distance(dist)
{
}
HClustMSTbasedGini::~HClustMSTbasedGini()
{
/* pass */
}
HclustPriorityQueue HClustMSTbasedGini::getMST()
{
MESSAGE_2("[%010.3f] determing the MST\n", clock()/(float)CLOCKS_PER_SEC);
HclustPriorityQueue out(n);
vector todo(n-1); // elements which are still not in the spanning tree
for (size_t k=0; k Adist(n, INFINITY);
std::vector Afrom(n, SIZE_MAX);
size_t lastj = 0; // a randomly chosen element :)
for (size_t i=0; i Adist[j]) {
bestj = j;
bestjpos = k;
}
#endif
}
#ifdef _OPENMP
// to avoid establishing a (slow!) critical section in
// the above loop, this fast part is done single-threadedly
for (size_t k=0; k Adist[todo[k]]) {
bestj = todo[k];
bestjpos = k;
}
}
#endif
out.push(HeapHierarchicalItem(Afrom[bestj], bestj, Adist[bestj]));
todo.erase(todo.begin()+bestjpos); // the algorithm is O(n^2) anyway + we need to iterate thru todo sequentially
lastj = bestj;
if (i % 512 == 0) MESSAGE_7("\r get MST: %d / %d", i, n-1);
Rcpp::checkUserInterrupt(); // may throw an exception, fast op, not thread safe
}
MESSAGE_7("\r get MST: %d / %d \n", n-1, n-1);
return out;
}
void HClustMSTbasedGini::linkAndRecomputeGini(PhatDisjointSets& ds, double& lastGini, size_t s1, size_t s2)
{
// if opts.thresholdGini == 1.0, there's no need to compute the Gini index
if (opts->thresholdGini < 1.0) {
double size1 = ds.getClusterSize(s1);
double size2 = ds.getClusterSize(s2);
lastGini *= (n)*(double)(ds.getClusterCount()-1);
std::size_t curi = s1;
do {
double curs = ds.getClusterSize(curi);
lastGini -= std::fabs(curs-size1);
lastGini -= std::fabs(curs-size2);
lastGini += std::fabs(curs-size1-size2);
curi = ds.getClusterNext(curi);
} while (curi != s1);
lastGini += std::fabs(size2-size1);
lastGini -= std::fabs(size2-size1-size2);
lastGini -= std::fabs(size1-size1-size2);
}
s1 = ds.link(s1, s2);
if (opts->thresholdGini < 1.0) {
lastGini /= (n)*(double)(ds.getClusterCount()-1);
lastGini = std::min(1.0, std::max(0.0, lastGini)); // avoid numeric inaccuracies
}
}
HClustResult HClustMSTbasedGini::compute()
{
HclustPriorityQueue pq;
if (opts->useVpTree) {
HClustVpTreeSingle hclust(distance, opts);
HClustResult res = hclust.compute(/*merge,order not needed*/opts->thresholdGini < 1.0);
if (opts->thresholdGini >= 1.0) return res;
Rcpp::NumericMatrix links = res.getLinks();
Rcpp::NumericVector dist = res.getHeight();
STOPIFNOT((size_t)dist.size() == n-1);
STOPIFNOT((size_t)links.nrow() == n-1);
pq = HclustPriorityQueue(n);
for (size_t i=0; i pq_cache;
while (true)
{
STOPIFNOT(!pq.empty())
HeapHierarchicalItem hhi = pq.top();
pq.pop();
size_t s1 = ds.find_set(hhi.index1);
size_t s2 = ds.find_set(hhi.index2);
STOPIFNOT(s1 != s2);
if (lastGini > opts->thresholdGini &&
ds.getClusterSize(s1) > minsize &&
ds.getClusterSize(s2) > minsize) {
// the writelock is still in ON
pq_cache.push_back(hhi);
continue;
}
std::size_t lastminsize = minsize;
res.link(hhi.index1, hhi.index2,
(lastGini <= opts->thresholdGini)?hhi.dist:-hhi.dist);
linkAndRecomputeGini(ds, lastGini, s1, s2);
if (opts->thresholdGini < 1.0)
minsize = ds.getMinClusterSize();
if (++i == n-1) break;
if (pq_cache.size() > 0 && (pq.empty() || lastGini <= opts->thresholdGini || minsize != lastminsize))
{
if (pq_cache.size() > 5) pq.reset(); // will call make_heap on next top()
while (!pq_cache.empty()) {
pq.push(pq_cache.back());
pq_cache.pop_back();
}
}
if (i % 512 == 0) MESSAGE_7("\r merge clusters: %d / %d", i+1, n-1);
Rcpp::checkUserInterrupt(); // may throw an exception, fast op, not thread safe
} // END WHILE
MESSAGE_7("\r merge clusters: %d / %d \n", n-1, n-1);
Rcpp::checkUserInterrupt();
return res;
}