source: trunk/DIST/DI_foundclusters.cxx

Last change on this file was 19283, checked in by westram, 18 months ago
  • allow to define a separator used to generate clusternames
    • old hardcoded default was '_' (which interferes with groupname conventions)
    • new customisable default is '-'
    • use custom separator
      • in get_upgroup_info().
      • for matchrate.
    • correctly handle empty separator.
  • change other hardcoded characters
    • avoid '_' in WHOLE_TREE (now uses <WHOLE TREE>).
    • neighter use '/' nor '-' when inserting relative notation (old: 3-4/7 new 3.4[7]).
  • part of #832
File size: 15.4 KB
Line 
1// ================================================================ //
2//                                                                  //
3//   File      : DI_foundclusters.cxx                               //
4//   Purpose   : Store results of cluster detection                 //
5//                                                                  //
6//   Coded by Ralf Westram (coder@reallysoft.de) in November 2009   //
7//   Institute of Microbiology (Technical University Munich)        //
8//   http://www.arb-home.de/                                        //
9//                                                                  //
10// ================================================================ //
11
12#include "di_foundclusters.hxx"
13#include "di_clustertree.hxx"
14
15#include <aw_window.hxx>
16#include <aw_select.hxx>
17
18#include <cmath>
19
20using namespace std;
21
22ID Cluster::unused_id = 1;
23
24static AP_FLOAT calc_mean_dist_to(const ClusterTree *distance_to, const LeafRelations *distances) {
25    size_t   count = 0;
26    AP_FLOAT sum   = 0.0;
27
28    LeafRelationCIter dist_end = distances->end();
29    for (LeafRelationCIter dist = distances->begin(); dist != dist_end; ++dist) {
30        const TwoLeafs& leafs = dist->first;
31
32        if (leafs.first() == distance_to || leafs.second() == distance_to) {
33            count++;
34            sum += dist->second;
35        }
36    }
37
38    cl_assert(count);                               // 'distance_to' does not occur in 'distances'
39
40    return sum/count;
41}
42
43Cluster::Cluster(ClusterTree *ct)
44    : next_desc(NULp)
45{
46    cl_assert(ct->get_state() == CS_IS_CLUSTER);
47
48    id = unused_id++;
49
50    const LeafRelations *distances = ct->get_sequence_dists();
51    cl_assert(distances);
52
53    LeafRelationCIter dist     = distances->begin();
54    LeafRelationCIter dist_end = distances->end();
55
56    cl_assert(dist != dist_end); // oops - empty cluster
57
58    min_dist  = max_dist = dist->second;
59    mean_dist = 0.0;
60
61    AP_FLOAT min_mean_dist = 9999999999.0;
62    representative         = NULp;
63
64    for (; dist != dist_end; ++dist) {
65        const TwoLeafs& leafs = dist->first;
66        AP_FLOAT        di    = dist->second;
67
68        if (di<min_dist) min_dist = di;
69        if (di>max_dist) max_dist = di;
70
71        mean_dist += di;
72
73        for (int i = 0; i<2; ++i) {
74            const ClusterTree *member     = i ? leafs.first() : leafs.second();
75            GBDATA            *gb_species = member->gb_node;
76
77            cl_assert(gb_species);
78            if (members.find(gb_species) == members.end()) { // not in set
79                members.insert(gb_species);
80                AP_FLOAT mean_dist_to_member = calc_mean_dist_to(member, distances);
81
82                if (mean_dist_to_member<min_mean_dist) {
83                    representative = gb_species;
84                    min_mean_dist  = mean_dist_to_member;
85                }
86            }
87        }
88
89    }
90
91    cl_assert(members.size() == ct->get_leaf_count());
92
93    min_bases = ct->get_min_bases();
94    {
95        ClusterTree *root    = ct->get_root_node();
96        size_t       allSize = root->get_leaf_count();
97        size_t       size    = ct->get_leaf_count();
98        size_t       first   = ct->relative_position_in(root);
99        size_t       last    = first+size-1;
100
101        rel_tree_pos = ((double(first)+last)/2) / allSize;
102    }
103    mean_dist /= distances->size();
104    cl_assert(representative);
105    desc = create_description(ct);
106}
107
108static string *get_downgroups(const ARB_countedTree *ct, const ARB_tree_predicate& keep_group_name, size_t& group_members) {
109    string *result = NULp;
110    if (ct->is_leaf()) {
111        group_members = 0;
112    }
113    else {
114        const char *group_name = ct->get_group_name();
115
116        if (group_name && keep_group_name.selects(*ct)) {
117            group_members = ct->get_leaf_count();
118            if (ct->is_keeled_group()) result = new string(GBS_global_string("%c%s", KEELED_INDICATOR, group_name));
119            else                       result = new string(group_name);
120        }
121        else {
122            string *leftgroups  = get_downgroups(ct->get_leftson(), keep_group_name, group_members);
123            size_t  right_members;
124            string *rightgroups = get_downgroups(ct->get_rightson(), keep_group_name, right_members);
125
126            group_members += right_members;
127
128            if (leftgroups || rightgroups) {
129                if (!leftgroups)       { result = rightgroups; rightgroups = NULp; }
130                else if (!rightgroups) { result = leftgroups; leftgroups = NULp; }
131                else                     result = new string(*leftgroups+"+"+*rightgroups);
132
133                delete leftgroups;
134                delete rightgroups;
135            }
136        }
137    }
138    return result;
139}
140static const char *get_upgroup(const ARB_countedTree *ct, const ARB_tree_predicate& keep_group_name, const ARB_countedTree*& upgroupPtr, bool reuse_original_name) {
141    const char *group_name = ct->get_group_name();
142    char       *original   = (reuse_original_name && group_name) ? originalGroupName(group_name) : NULp;
143
144    if (original) {
145        static SmartCharPtr result;
146        result = original;
147        group_name = &*result;
148        upgroupPtr = ct;
149    }
150    else if (group_name && keep_group_name.selects(*ct)) {
151        upgroupPtr = ct;
152    }
153    else {
154        const ARB_countedTree *father = ct->get_father();
155        if (father) {
156            group_name = get_upgroup(father, keep_group_name, upgroupPtr, reuse_original_name);
157        }
158        else {
159            upgroupPtr = ct;
160            group_name = "<WHOLE TREE>";
161        }
162    }
163
164    cl_assert(!upgroupPtr || ct->is_inside(upgroupPtr));
165    cl_assert(group_name);
166    return group_name;
167}
168
169string Cluster::create_description(const ARB_countedTree *ct) {
170    // creates cluster description (using up- and down-groups, percentages, existing groups, ...)
171    cl_assert(!ct->is_leaf());
172
173    string name;
174
175    UseAnyTree use_any_upgroup;
176
177    const ARB_countedTree *upgroup         = NULp;
178    const char            *upgroup_name    = get_upgroup(ct, use_any_upgroup, upgroup, false);
179    size_t                 upgroup_members = upgroup->get_leaf_count();
180
181    size_t cluster_members = ct->get_leaf_count();
182
183    cl_assert(upgroup_members>0); // if no group, whole tree should have been returned
184
185    if (upgroup_members == cluster_members) { // use original or existing name
186        name = string("[G] ")+upgroup_name;
187    }
188    else {
189        size_t  downgroup_members;
190        string *downgroup_names = get_downgroups(ct, use_any_upgroup, downgroup_members);
191
192        AP_FLOAT up_rel   = (AP_FLOAT)cluster_members/upgroup_members;   // used for: xx% of upgroup (big is good)
193        AP_FLOAT down_rel = (AP_FLOAT)downgroup_members/cluster_members; // used for: downgroup(s) + (1-xx)% outgroup (big is good)
194
195        if (up_rel>down_rel) { // prefer up_percent
196            name = string(GBS_global_string("%4.1f%% of %s", up_rel*100.0, upgroup_name));
197        }
198        else {
199            if (downgroup_members == cluster_members) {
200                name = downgroup_names->c_str();
201            }
202            else {
203                name = string(GBS_global_string("%s + %4.1f%% outgroup", downgroup_names->c_str(), (1-down_rel)*100.0));
204            }
205        }
206
207        delete downgroup_names;
208    }
209    return name;
210}
211
212string Cluster::get_upgroup_info(const ARB_countedTree *ct, const ARB_tree_predicate& keep_group_name, const string& separator) {
213    // generates a string indicating relative position in up-group
214    cl_assert(!ct->is_leaf());
215
216    const ARB_countedTree *upgroup      = NULp;
217    const char            *upgroup_name = get_upgroup(ct, keep_group_name, upgroup, true);
218
219    size_t upgroup_members = upgroup->get_leaf_count();
220    size_t cluster_members = ct->get_leaf_count();
221
222    const char *upgroup_info;
223    if (upgroup_members == cluster_members) {
224        upgroup_info = upgroup_name;
225    }
226    else {
227        size_t cluster_pos1 = ct->relative_position_in(upgroup)+1; // range [1..upgroup_members]
228        size_t cluster_posN = cluster_pos1+cluster_members-1;
229
230        upgroup_info = GBS_global_string("%zu.%zu[%zu]%s%s", cluster_pos1, cluster_posN, upgroup_members, separator.c_str(), upgroup_name);
231    }
232    return upgroup_info;
233}
234
235// --------------------------
236//      DisplayFormat
237
238class DisplayFormat : virtual Noncopyable {
239    size_t   max_count;
240    AP_FLOAT max_dist;
241    AP_FLOAT max_minBases;
242
243    mutable char *format_string;
244
245    static char *make_format(size_t val) {
246        int digits = calc_digits(val);
247        return GBS_global_string_copy("%%%izu", digits);
248    }
249
250    static void calc_float_format(AP_FLOAT val, int& all, int& afterdot) {
251        int digits   = calc_signed_digits(long(val));
252        afterdot = digits <=3 ? 5-digits-1 : 0;
253        cl_assert(afterdot >= 0);
254        all = digits+(afterdot ? (afterdot+1) : 0);
255    }
256
257    static char *make_format(AP_FLOAT val) {
258        int all, afterdot;
259        calc_float_format(val, all, afterdot);
260        return GBS_global_string_copy("%%%i.%if", all, afterdot);
261    }
262
263public:
264
265    DisplayFormat()
266        : max_count(0),
267          max_dist(0.0),
268          max_minBases(0.0),
269          format_string(NULp)
270    {}
271
272    ~DisplayFormat() {
273        free(format_string);
274    }
275
276    void announce(size_t count, AP_FLOAT maxDist, AP_FLOAT minBases) {
277        max_count    = std::max(max_count, count);
278        max_dist     = std::max(max_dist, maxDist);
279        max_minBases = std::max(max_minBases, minBases);
280
281        freenull(format_string);
282    }
283
284    const char *get_header() const {
285        int countSize = calc_digits(max_count) + 2; // allow to use two of the spaces behind count
286        int distSize, baseSize;
287        {
288            int afterdot;
289            calc_float_format(max_dist,     distSize, afterdot);
290            calc_float_format(max_minBases, baseSize, afterdot);
291        }
292
293        return GBS_global_string("%-*s%*s  %*s  Groupname",
294                                 countSize, countSize<5 ? "#" : "Count",
295                                 distSize, "Mean [min-max] dist",
296                                 baseSize, "Bases");
297    }
298
299    const char *get_format() const {
300        if (!format_string) {
301            // create format string for description
302            char *count_part = make_format(max_count);
303            char *dist_part  = make_format(max_dist);
304            char *bases_part = make_format(max_minBases);
305
306            format_string = GBS_global_string_copy("%s  %s [%s-%s]  %s  %%s",
307                                                   count_part,
308                                                   dist_part, dist_part, dist_part,
309                                                   bases_part);
310
311            free(bases_part);
312            free(dist_part);
313            free(count_part);
314        }
315        return format_string;
316    }
317};
318
319
320
321void Cluster::scan_display_widths(DisplayFormat& format) const {
322    AP_FLOAT max_of_all_dists = std::max(max_dist, std::max(min_dist, mean_dist));
323    format.announce(get_member_count(), max_of_all_dists*100.0, min_bases);
324}
325
326const char *Cluster::get_list_display(const DisplayFormat *format) const {
327    const char *format_string;
328    if (format) format_string = format->get_format();
329    else        format_string = "%zu  %.3f [%.3f-%.3f]  %.1f  %s";
330
331    return GBS_global_string(format_string,
332                             get_member_count(),
333                             mean_dist*100.0,
334                             min_dist*100.0,
335                             max_dist*100.0,
336                             min_bases,
337                             desc.c_str());
338}
339
340
341// ---------------------
342//      ClustersData
343
344void ClustersData::add(ClusterPtr clus, ClusterSubset subset) {
345    known_clusters[clus->get_ID()] = clus;
346    get_subset(subset).push_back(clus->get_ID());
347}
348
349void ClustersData::remove(ClusterPtr clus, ClusterSubset subset) {
350    int pos = get_pos(clus, subset);
351    if (pos != -1) {
352        ClusterIDs&           ids      = get_subset(subset);
353        ClusterIDs::iterator  toRemove = ids.begin();
354        advance(toRemove, pos);
355        ids.erase(toRemove);
356        known_clusters.erase(clus->get_ID());
357    }
358}
359
360void ClustersData::clear(ClusterSubset subset) {
361    ClusterIDs&     ids    = get_subset(subset);
362    ClusterIDsIter  id_end = ids.end();
363    for (ClusterIDsIter  id = ids.begin(); id != id_end; ++id) {
364        known_clusters.erase(*id);
365    }
366    ids.clear();
367}
368
369void ClustersData::store(ID id) {
370    ClusterPtr toStore = clusterWithID(id);
371    remove(toStore, SHOWN_CLUSTERS);
372    add(toStore, STORED_CLUSTERS);
373}
374
375void ClustersData::store_all() {
376    copy(shown.begin(), shown.end(), back_insert_iterator<ClusterIDs>(stored));
377    shown.clear();
378    sort_needed = false;
379}
380
381void ClustersData::restore_all() {
382    copy(stored.begin(), stored.end(), back_insert_iterator<ClusterIDs>(shown));
383    stored.clear();
384    sort_needed = true;
385}
386
387void ClustersData::swap_all() {
388    swap(stored, shown);
389    sort_needed = true;
390}
391
392
393class SortClusterIDsBy {
394    const KnownClusters& known;
395    ClusterOrder         primary_order;
396    ClusterOrder         secondary_order;
397public:
398    SortClusterIDsBy(const KnownClusters& known_, ClusterOrder primary, ClusterOrder secondary) :
399        known(known_),
400        primary_order(primary),
401        secondary_order(secondary)
402    {}
403
404     bool operator()(ID id1, ID id2) const {
405         ClusterPtr cl1 = known.find(id1)->second;
406         ClusterPtr cl2 = known.find(id2)->second;
407
408         cl_assert(!cl1.isNull() && !cl2.isNull());
409
410         bool less = cl1->lessByOrder(*cl2, primary_order);
411         if (!less) {
412             bool equal = !cl2->lessByOrder(*cl1, primary_order);
413             if (equal) {
414                 less = cl1->lessByOrder(*cl2, secondary_order);
415             }
416         }
417         return less;
418     }
419};
420
421void ClustersData::update_cluster_selection_list() {
422    cl_assert(clusterList);
423    clusterList->clear();
424
425    if (shown.empty()) {
426        clusterList->insert_default("<No clusters detected>", ID(0));
427    }
428    else {
429        {
430            SortClusterIDsBy sortBy(known_clusters, criteria[0], criteria[1]);
431            sort(shown.begin(), shown.end(), sortBy);
432        }
433        {
434            ClusterIDsIter    cl_end = shown.end();
435            DisplayFormat format;
436
437            for (int pass = 1; pass <= 2; ++pass) {
438                if (pass == 2) {
439                    clusterList->insert(format.get_header(), ID(-1));
440                }
441                for (ClusterIDsIter cl = shown.begin(); cl != cl_end; ++cl) {
442                    ID         id      = *cl;
443                    ClusterPtr cluster = clusterWithID(id);
444
445                    if (pass == 1) {
446                        cluster->scan_display_widths(format);
447                    }
448                    else {
449                        clusterList->insert(cluster->get_list_display(&format), cluster->get_ID());
450                    }
451                }
452            }
453            clusterList->insert_default("<select no cluster>", ID(0));
454        }
455    }
456    clusterList->update();
457}
458
459void ClustersData::free() {
460    // delete calculated/stored data
461    shown.clear();
462    stored.clear();
463    update_cluster_selection_list();
464    known_clusters.clear();
465}
466
467char *originalGroupName(const char *groupname) {
468    char *original = NULp;
469    const char *was = strstr(groupname, " {was:");
470    const char *closing = strchr(groupname, '}');
471    if (was && closing) {
472        original = ARB_strpartdup(was+6, closing-1);
473        if (original[0]) {
474            char *sub = originalGroupName(original);
475            if (sub) freeset(original, sub);
476        }
477        else {
478            freenull(original); // empty -> invalid
479        }
480    }
481    return original;
482}
483
484
485
Note: See TracBrowser for help on using the repository browser.