source: trunk/DIST/DI_clusters.cxx

Last change on this file was 19283, checked in by westram, 2 years 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: 39.6 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : DI_clusters.cxx                                   //
4//   Purpose   : Detect clusters of homologous sequences in tree   //
5//                                                                 //
6//   Coded by Ralf Westram (coder@reallysoft.de) in October 2009   //
7//   Institute of Microbiology (Technical University Munich)       //
8//   http://www.arb-home.de/                                       //
9//                                                                 //
10// =============================================================== //
11
12#include "di_clusters.hxx"
13#include "di_clustertree.hxx"
14#include "di_foundclusters.hxx"
15#include "di_awars.hxx"
16
17#include <AP_seq_protein.hxx>
18#include <AP_seq_dna.hxx>
19
20#include <awt_sel_boxes.hxx>
21
22#include <aw_awars.hxx>
23#include <aw_msg.hxx>
24#include <arb_progress.h>
25#include <arb_msg_nospam.h>
26#include <aw_root.hxx>
27
28#include <list>
29
30using namespace std;
31
32#define di_assert(cond) arb_assert(cond)
33
34// --------------
35//      awars
36
37#define AWAR_CLUSTER_PREFIX      AWAR_DIST_PREFIX "cluster/"
38#define AWAR_CLUSTER_PREFIX_TEMP "/tmp/" AWAR_DIST_PREFIX
39
40#define AWAR_CLUSTER_MAXDIST   AWAR_CLUSTER_PREFIX "maxdist"
41#define AWAR_CLUSTER_MINSIZE   AWAR_CLUSTER_PREFIX "minsize"
42#define AWAR_CLUSTER_AUTOMARK  AWAR_CLUSTER_PREFIX "automark"
43#define AWAR_CLUSTER_MARKREP   AWAR_CLUSTER_PREFIX "markrep"
44#define AWAR_CLUSTER_SELECTREP AWAR_CLUSTER_PREFIX "selrep"
45#define AWAR_CLUSTER_ORDER     AWAR_CLUSTER_PREFIX "order"
46
47#define AWAR_CLUSTER_GRP_PREFIX     AWAR_CLUSTER_PREFIX "group/"
48#define AWAR_CLUSTER_GRP_PREFIX_TMP "/tmp/" AWAR_CLUSTER_GRP_PREFIX
49
50#define AWAR_CLUSTER_GROUP_WHAT         AWAR_CLUSTER_GRP_PREFIX "all"
51#define AWAR_CLUSTER_GROUP_EXISTING     AWAR_CLUSTER_GRP_PREFIX "existing"
52#define AWAR_CLUSTER_GROUP_NOTFOUND     AWAR_CLUSTER_GRP_PREFIX "notfound"
53#define AWAR_CLUSTER_GROUP_IDENTITY     AWAR_CLUSTER_GRP_PREFIX "identity"
54#define AWAR_CLUSTER_GROUP_PREFIX       AWAR_CLUSTER_GRP_PREFIX "prefix"
55#define AWAR_CLUSTER_GROUP_PREFIX_MATCH AWAR_CLUSTER_GRP_PREFIX "prefix_match"
56#define AWAR_CLUSTER_GROUP_SUFFIX       AWAR_CLUSTER_GRP_PREFIX "suffix"
57#define AWAR_CLUSTER_GROUP_PARTSEP      AWAR_CLUSTER_GRP_PREFIX "separator"
58#define AWAR_CLUSTER_GROUP_EXAMPLE      AWAR_CLUSTER_GRP_PREFIX_TMP "example"
59
60#define AWAR_CLUSTER_SELECTED      AWAR_CLUSTER_PREFIX_TEMP "selected" // ID of currently selected cluster (or zero)
61#define AWAR_CLUSTER_RESTORE_LABEL AWAR_CLUSTER_PREFIX_TEMP "rlabel" // label of restore button
62
63enum Group_What {
64    GROUP_SELECTED,
65    GROUP_LISTED,
66};
67
68enum Group_Action {
69    GROUP_CREATE,
70    GROUP_DELETE,
71};
72
73enum Group_NotFound {
74    NOTFOUND_ABORT,
75    NOTFOUND_WARN,
76    NOTFOUND_IGNORE,
77};
78
79enum Group_Existing {
80    EXISTING_GROUP_ABORT,
81    EXISTING_GROUP_SKIP,
82    EXISTING_GROUP_OVERWRITE,
83    EXISTING_GROUP_APPEND_ORG,
84};
85
86// ----------------------------------------
87
88enum AffectedClusters { ALL_CLUSTERS, SEL_CLUSTER };
89
90static ClustersData *global_data = NULp;
91
92// ------------------------
93
94static void di_forget_global_data(AW_window *) {
95    di_assert(global_data);
96    global_data->free();
97    // do not delete 'global_data' itself, it will be reused when window is opened again
98}
99
100// ------------------------
101//      Update contents
102
103static void update_cluster_sellist() {
104    global_data->update_cluster_selection_list();
105    // @@@ update result info line
106}
107static void update_restore_label(AW_window *aww) {
108    AW_root    *aw_root = aww->get_root();
109    AW_awar    *awar    = aw_root->awar(AWAR_CLUSTER_RESTORE_LABEL);
110    unsigned    size    = global_data->count(STORED_CLUSTERS);
111    const char *label   = size ? GBS_global_string("Stored: %u", size) : "None stored";
112
113    awar->write_string(label);
114}
115static void update_all(AW_window *aww) {
116    update_cluster_sellist();
117    update_restore_label(aww);
118}
119
120static void save_results_recursive(ClusterTree *subtree) {
121    if (subtree->get_state() == CS_IS_CLUSTER) {
122        global_data->add(new Cluster(subtree), SHOWN_CLUSTERS);
123    }
124    if (!subtree->is_leaf()) {
125        save_results_recursive(subtree->get_leftson());
126        save_results_recursive(subtree->get_rightson());
127    }
128}
129static void save_results(ClusterTreeRoot *tree) {
130    global_data->clear(SHOWN_CLUSTERS);
131    save_results_recursive(tree->get_root_node());
132    update_cluster_sellist();
133}
134
135static void calculate_clusters(AW_window *aww) {
136    GBDATA   *gb_main = global_data->get_gb_main();
137    GB_ERROR  error   = NULp;
138
139    arb_progress progress("Detecting clusters");
140
141    // calculate ClusterTree
142    ClusterTreeRoot *tree = NULp;
143    {
144        GB_transaction  ta(gb_main);
145        AW_root        *aw_root = aww->get_root();
146
147        {
148            char    *use     = aw_root->awar(AWAR_DIST_ALIGNMENT)->read_string();
149            AliView *aliview = global_data->weighted_filter.create_aliview(use, error);
150
151            if (aliview) {
152                AP_sequence *seq = GBT_is_alignment_protein(gb_main, use)
153                    ? (AP_sequence*)new AP_sequence_protein(aliview)
154                    : new AP_sequence_parsimony(aliview);
155
156                float    maxDistance    = aw_root->awar(AWAR_CLUSTER_MAXDIST)->read_float();
157                unsigned minClusterSize = aw_root->awar(AWAR_CLUSTER_MINSIZE)->read_int();
158
159                tree = new ClusterTreeRoot(aliview, seq, maxDistance/100, minClusterSize);
160
161                delete seq;
162            }
163            free(use);
164        }
165
166        if (!error) {
167            progress.subtitle("Loading tree");
168            {
169                char *tree_name = aw_root->awar(AWAR_DIST_TREE_CURR_NAME)->read_string();
170                error           = tree->loadFromDB(tree_name);
171                free(tree_name);
172            }
173
174            if (!error) error = tree->linkToDB(NULp, NULp);
175        }
176    }
177
178    if (!error) {
179        error = tree->find_clusters();
180        if (!error) save_results(tree);
181    }
182
183    delete tree;
184
185    if (error) aw_message(error);
186}
187
188DECLARE_CBTYPE_FVV_AND_BUILDERS(ClusterCallback, void, ClusterPtr); // generates makeClusterCallback
189
190inline bool is_cluster(ID id) {
191    // 0 is 'no cluster' and -1 is used for list header
192    return id>0;
193}
194
195static int with_affected_clusters_do(AW_root *aw_root, AffectedClusters affected, bool warn_if_none_affected, ClusterCallback cb) {
196    // returns number of affected clusters
197    int affCount = 0;
198    if (affected == SEL_CLUSTER) {
199        AW_awar *awar  = aw_root->awar(AWAR_CLUSTER_SELECTED);
200        ID       selID = awar->read_int();
201
202        if (is_cluster(selID)) {
203            ClusterPtr selCluster = global_data->clusterWithID(selID);
204            cl_assert(!selCluster.isNull());
205            cb(selCluster);
206            affCount++;
207        }
208        else if (warn_if_none_affected) {
209            aw_message("No cluster is selected");
210        }
211    }
212    else {
213        unsigned shown = global_data->count(SHOWN_CLUSTERS);
214        if (shown) {
215            ClusterIDs     clusters(global_data->get_clusterIDs(SHOWN_CLUSTERS)); // intended copy!
216            ClusterIDsIter cli_end = clusters.end();
217            for (ClusterIDsIter cli = clusters.begin(); cli != cli_end; ++cli) {
218                ClusterPtr cluster = global_data->clusterWithID(*cli);
219                cb(cluster);
220                affCount++;
221            }
222        }
223        else if (warn_if_none_affected) {
224            aw_message("There are no clusters in the list");
225        }
226    }
227    return affCount;
228}
229
230// -------------
231//      mark
232
233void Cluster::mark_all_members(ClusterMarkMode mmode) const {
234    DBItemSetIter sp_end = members.end();
235    for (DBItemSetIter sp = members.begin(); sp != sp_end; ++sp) {
236        bool is_rep = *sp == representative;
237        bool mark   = false;
238
239        switch (mmode) {
240            case CMM_ALL:         mark = true;    break;
241            case CMM_ONLY_REP:    mark = is_rep;  break;
242            case CMM_ALL_BUT_REP: mark = !is_rep; break;
243        }
244
245        if (mark) GB_write_flag(*sp, 1);
246    }
247}
248
249static void mark_cluster(ClusterPtr cluster, ClusterMarkMode markRep) {
250    cluster->mark_all_members(markRep);
251}
252static void select_representative(ClusterPtr cluster) {
253    GBDATA         *gb_species = cluster->get_representative();
254    GB_transaction  ta(gb_species);
255    global_data->get_aw_root()->awar(AWAR_SPECIES_NAME)->write_string(null2empty(GBT_get_name(gb_species)));
256}
257
258static void mark_clusters(AW_window *, AffectedClusters affected, bool warn_if_none_affected) {
259    AW_root *aw_root = global_data->get_aw_root();
260    GBDATA  *gb_main = global_data->get_gb_main();
261
262    GB_transaction  ta(gb_main);
263    ClusterMarkMode markRep = (ClusterMarkMode)aw_root->awar(AWAR_CLUSTER_MARKREP)->read_int();
264
265    GBT_mark_all(gb_main, 0); // unmark all
266    with_affected_clusters_do(aw_root, affected, warn_if_none_affected, makeClusterCallback(mark_cluster, markRep));
267    aw_root->awar(AWAR_TREE_REFRESH)->touch();
268}
269
270static void select_cluster_cb(AW_root *aw_root) {
271    GB_transaction ta(global_data->get_gb_main());
272
273    bool auto_mark = aw_root->awar(AWAR_CLUSTER_AUTOMARK)->read_int();
274    if (auto_mark) mark_clusters(NULp, SEL_CLUSTER, false);
275
276    bool selRep = aw_root->awar(AWAR_CLUSTER_SELECTREP)->read_int();
277    if (selRep) {
278        int selected = with_affected_clusters_do(aw_root, SEL_CLUSTER, false, makeClusterCallback(select_representative));
279        if (!selected) aw_root->awar(AWAR_SPECIES_NAME)->write_string(""); // deselect species
280    }
281}
282
283static void select_cluster(ID id) {
284    global_data->get_aw_root()->awar(AWAR_CLUSTER_SELECTED)->write_int(id);
285}
286
287
288// -------------------
289//      Sort order
290
291
292static void sort_order_changed_cb(AW_root *aw_root) {
293    ClusterOrder order = (ClusterOrder)aw_root->awar(AWAR_CLUSTER_ORDER)->read_int();
294    global_data->changeSortOrder(order);
295    update_cluster_sellist();
296}
297
298// --------------
299//      group
300
301class GroupTree;
302typedef map<string, GroupTree*> Species2Tip;
303
304struct GroupTreeRoot FINAL_TYPE : public ARB_seqtree_root {
305    GroupTreeRoot(AliView *aliView, AP_sequence *seqTempl, bool add_delete_callbacks);
306    ~GroupTreeRoot() OVERRIDE { predelete(); }
307    inline TreeNode *makeNode() const OVERRIDE;
308    inline void destroyNode(TreeNode *node) const OVERRIDE;
309};
310
311class GroupTree FINAL_TYPE : public ARB_countedTree {
312    unsigned leaf_count;   // total number of leafs in subtree
313    unsigned tagged_count; // tagged leafs
314
315    void update_tag_counters();
316    unsigned get_leaf_count() const OVERRIDE { return leaf_count; }
317protected:
318    ~GroupTree() OVERRIDE {}
319    friend class GroupTreeRoot;
320public:
321
322    explicit GroupTree(GroupTreeRoot *root)
323        : ARB_countedTree(root),
324          leaf_count(0),
325          tagged_count(0)
326    {}
327
328    // ARB_countedTree interface
329    void init_tree() OVERRIDE { update_leaf_counters(); }
330    // ARB_countedTree interface end
331
332    DEFINE_TREE_RELATIVES_ACCESSORS(GroupTree);
333
334    void map_species2tip(Species2Tip& mapping);
335
336    unsigned update_leaf_counters();
337
338    void tag_leaf() {
339        di_assert(is_leaf());
340        tagged_count = 1;
341        get_father()->update_tag_counters();
342    }
343    unsigned get_tagged_count() const { return tagged_count; }
344    void clear_tags();
345
346    float tagged_rate() const { return float(get_tagged_count())/get_leaf_count(); }
347};
348
349GroupTreeRoot::GroupTreeRoot(AliView *aliView, AP_sequence *seqTempl, bool add_delete_callbacks)
350    : ARB_seqtree_root(aliView, seqTempl, add_delete_callbacks)
351{}
352inline TreeNode *GroupTreeRoot::makeNode() const { return new GroupTree(const_cast<GroupTreeRoot*>(this)); }
353inline void GroupTreeRoot::destroyNode(TreeNode *node) const { delete DOWNCAST(GroupTree*,node); }
354
355
356unsigned GroupTree::update_leaf_counters() {
357    if (is_leaf()) leaf_count = 1;
358    else leaf_count = get_leftson()->update_leaf_counters() + get_rightson()->update_leaf_counters();
359    return leaf_count;
360}
361
362void GroupTree::clear_tags() {
363    if (!is_leaf() && tagged_count) {
364        get_leftson()->clear_tags();
365        get_rightson()->clear_tags();
366    }
367    tagged_count = 0;
368}
369
370void GroupTree::map_species2tip(Species2Tip& mapping) {
371    if (is_leaf()) {
372        if (name) mapping[name] = this;
373    }
374    else {
375        get_leftson()->map_species2tip(mapping);
376        get_rightson()->map_species2tip(mapping);
377    }
378}
379
380void GroupTree::update_tag_counters() {
381    di_assert(!is_leaf());
382    GroupTree *node = this;
383    while (node) {
384        node->tagged_count = node->get_leftson()->get_tagged_count() + node->get_rightson()->get_tagged_count();
385        node               = node->get_father();
386    }
387}
388
389struct GroupChanges {
390    unsigned created;
391    unsigned skipped;
392    unsigned overwritten;
393    unsigned deleted;
394    unsigned restored;
395
396    void clear() { created = skipped = overwritten = deleted = restored = 0; }
397    bool exist() const { return created||overwritten||deleted||restored; }
398
399    void show_message() {
400        string msg;
401
402        if (created)     msg += GBS_global_string("%u created  ",     created);
403        if (overwritten) msg += GBS_global_string("%u overwritten  ", overwritten);
404        if (skipped)     msg += GBS_global_string("%u skipped  ",     skipped);
405        if (deleted)     msg += GBS_global_string("%u deleted  ",     deleted);
406        if (restored)    msg += GBS_global_string("%u restored  ",    restored);
407
408        if (!msg.empty()) {
409            msg = string("Group changes: ")+msg;
410            aw_message(msg.c_str());
411        }
412    }
413
414    GroupChanges() { clear(); }
415};
416
417
418// ---------------------
419//      GroupBuilder
420
421class GroupBuilder : virtual Noncopyable {
422    GBDATA         *gb_main;
423    string          tree_name;
424    GroupTreeRoot  *tree_root;
425    Group_Action    action;              // create or delete ?
426    Species2Tip     species2tip;         // map speciesName -> leaf
427    ARB_ERROR       error;
428    ClusterPtr      bad_cluster;         // error occurred here (is set)
429    Group_Existing  existing;
430    unsigned        existing_count;      // counts existing groups
431    Group_NotFound  notfound;
432    float           matchRatio;          // needed identity of subtree and cluster
433    float           maxDist;             // max. Distance used for calculation
434    string          cluster_prefix;      // prefix for cluster name
435    string          cluster_suffix_def;  // suffix-definition for cluster name
436    string          separator;           // separator for parts of cluster name
437    GroupChanges    changes;             // count tree modifications
438    bool            del_match_prefixes;  // only delete groups, where prefix matches
439
440    GroupTree *find_group_position(GroupTree *subtree, unsigned cluster_size);
441    float get_max_distance() const { return maxDist; }
442    void load_tree();
443
444    DEFINE_DOWNCAST_ACCESSORS(GroupTree, get_root_node, tree_root->get_root_node());
445
446    bool shall_delete_group(const char *name) const {
447        return !del_match_prefixes || matches_current_prefix(name);
448    }
449
450public:
451    GroupBuilder(GBDATA *gb_main_, Group_Action action_)
452        : gb_main(gb_main_),
453          tree_root(NULp),
454          action(action_),
455          error(NULp),
456          existing_count(0)
457    {
458        AW_root *awr = global_data->get_aw_root();
459
460        tree_name          = awr->awar(AWAR_DIST_TREE_CURR_NAME)->read_char_pntr();
461        existing           = (Group_Existing)awr->awar(AWAR_CLUSTER_GROUP_EXISTING)->read_int();
462        notfound           = (Group_NotFound)awr->awar(AWAR_CLUSTER_GROUP_NOTFOUND)->read_int();
463        del_match_prefixes = awr->awar(AWAR_CLUSTER_GROUP_PREFIX_MATCH)->read_int();
464        matchRatio         = awr->awar(AWAR_CLUSTER_GROUP_IDENTITY)->read_int()/100.0;
465        maxDist            = awr->awar(AWAR_CLUSTER_MAXDIST)->read_float();
466        cluster_prefix     = awr->awar(AWAR_CLUSTER_GROUP_PREFIX)->read_char_pntr();
467        cluster_suffix_def = awr->awar(AWAR_CLUSTER_GROUP_SUFFIX)->read_char_pntr();
468        separator          = awr->awar(AWAR_CLUSTER_GROUP_PARTSEP)->read_char_pntr();
469    }
470    ~GroupBuilder() { delete tree_root; }
471
472    ARB_ERROR get_error() const { return error; }
473    ClusterPtr get_bad_cluster() const { return bad_cluster; }
474
475    ARB_ERROR save_modified_tree();
476
477    GroupTree *find_best_matching_subtree(ClusterPtr cluster);
478    void update_group(ClusterPtr cluster); // create or delete group for cluster
479    string generate_group_name(ClusterPtr cluster, const GroupTree *group_node);
480
481    bool matches_current_prefix(const char *groupname) const {
482        return strstr(groupname, cluster_prefix.c_str()) == groupname;
483    }
484};
485
486void GroupBuilder::load_tree() {
487    di_assert(!tree_root);
488
489    tree_root = new GroupTreeRoot(new AliView(gb_main), NULp, false);
490    error     = tree_root->loadFromDB(tree_name.c_str());
491
492    if (error) {
493        delete tree_root;
494        tree_root = NULp;
495    }
496    else {
497        changes.clear();
498
499        GroupTree *tree = get_root_node();
500        tree->update_leaf_counters();
501        tree->map_species2tip(species2tip);
502    }
503}
504ARB_ERROR GroupBuilder::save_modified_tree() {
505    di_assert(!error);
506    if (changes.exist()) {
507        di_assert(tree_root);
508        error = tree_root->saveToDB();
509
510        AW_root *awr = global_data->get_aw_root();
511        awr->awar(AWAR_TREE_REFRESH)->touch();
512
513        if (!error) {
514            changes.show_message();
515            changes.clear();
516        }
517    }
518    return error;
519}
520
521GroupTree *GroupBuilder::find_group_position(GroupTree *subtree, unsigned cluster_size) {
522    // searches for best group in subtree matching the cluster
523
524    GroupTree *groupPos = NULp;
525    if (subtree->get_tagged_count() == cluster_size) {
526        groupPos                = find_group_position(subtree->get_leftson(), cluster_size);
527        if (!groupPos) groupPos = find_group_position(subtree->get_rightson(), cluster_size);
528
529        if (!groupPos) {                            // consider 'subtree'
530            if (subtree->tagged_rate() >= matchRatio) {
531                groupPos = subtree;
532            }
533        }
534    }
535    return groupPos;
536}
537
538class HasntCurrentClusterPrefix : public ARB_tree_predicate {
539    const GroupBuilder& builder;
540    bool selects(const ARB_seqtree& tree) const OVERRIDE {
541        const char *groupname        = tree.get_group_name();
542        bool        hasClusterPrefix = groupname && builder.matches_current_prefix(groupname);
543        return !hasClusterPrefix;
544    }
545public:
546    HasntCurrentClusterPrefix(const GroupBuilder& builder_) : builder(builder_) {}
547};
548
549string concatenate_name_parts(const list<string>& namepart, const string& separator) {
550    string concat;
551    for (list<string>::const_iterator p = namepart.begin(); p != namepart.end(); ++p) {
552        if (!p->empty()) {
553            if (!concat.empty()) concat += separator;
554            concat += *p;
555        }
556    }
557    return concat;
558}
559
560class UseTreeRoot : public ARB_tree_predicate {
561    bool selects(const ARB_seqtree& tree) const OVERRIDE { return tree.is_root_node(); }
562};
563
564string GroupBuilder::generate_group_name(ClusterPtr cluster, const GroupTree *group_node) {
565    list<string> namepart;
566    namepart.push_back(cluster_prefix);
567
568    string  orgname_suffix;
569    if (existing == EXISTING_GROUP_APPEND_ORG) {
570        char *old_name = group_node->name;
571        if (old_name) {
572            char *original = originalGroupName(old_name);
573            if (!original && !matches_current_prefix(old_name)) {
574                original = ARB_strdup(old_name); // use existing name as original name
575            }
576            if (original) {
577                orgname_suffix = string(" {was:")+original+"}";
578                free(original);
579            }
580        }
581    }
582
583    string text;
584    for (int i = 0; cluster_suffix_def[i]; ++i) {
585        if (cluster_suffix_def[i] == '%') {
586            ++i;
587            if (cluster_suffix_def[i] == '%') {
588                text += '%';
589            }
590            else {
591                string expanded;
592                switch(cluster_suffix_def[i]) {
593                    case 'p': expanded = cluster->get_upgroup_info(group_node, HasntCurrentClusterPrefix(*this), separator); break;
594                    case 'P': expanded = cluster->get_upgroup_info(group_node, UseAnyTree(), separator); break;
595                    case 't': expanded = cluster->get_upgroup_info(group_node, UseTreeRoot(), separator); break;
596                    case 'd': expanded = GBS_global_string("~%.3f", cluster->get_mean_distance()); break;
597                    case 'D': expanded = GBS_global_string("<=%.1f%%", get_max_distance()); break;
598                    case 'e': expanded = null2empty(group_node->name); break;
599                    case 'o': {
600                        int matchRate = int(group_node->tagged_rate()*100+0.5);
601                        if (matchRate<100) expanded = string(GBS_global_string("%i%%", matchRate))+separator+"of";
602                        break;
603                    }
604                    default:
605                        text += '%';
606                        text += cluster_suffix_def[i];
607                        break;
608                }
609
610                if (!expanded.empty()) {
611                    namepart.push_back(text);
612                    text = "";
613                    namepart.push_back(expanded);
614                }
615            }
616        }
617        else {
618            text += cluster_suffix_def[i];
619        }
620    }
621    namepart.push_back(text);
622
623    return concatenate_name_parts(namepart, separator) + orgname_suffix;
624}
625
626GroupTree *GroupBuilder::find_best_matching_subtree(ClusterPtr cluster) {
627    GroupTree *group_node = NULp;
628    if (!error) {
629        if (!tree_root) load_tree();
630        if (!error) {
631            const DBItemSet& members = cluster->get_members();
632
633            // mark cluster members in tree
634            {
635                GB_transaction ta(gb_main);
636                DBItemSetIter sp_end = members.end();
637                for (DBItemSetIter sp = members.begin(); sp != sp_end && !error; ++sp) {
638                    const char *name = GBT_get_name(*sp);
639                    di_assert(name);
640                    if (name) {
641                        Species2Tip::const_iterator found = species2tip.find(name);
642                        if (found == species2tip.end()) {
643                            error = GBS_global_string("Species '%s' is not in '%s'", name, tree_name.c_str());
644                        }
645                        else {
646                            GroupTree *leaf = found->second;
647                            leaf->tag_leaf();
648                        }
649                    }
650                }
651            }
652
653            if (!error) {
654                // top-down search for best matching node
655                group_node = find_group_position(get_root_node(), cluster->get_member_count());
656            }
657        }
658    }
659    return group_node;
660}
661
662void GroupBuilder::update_group(ClusterPtr cluster) {
663    if (!error) {
664        GroupTree *group_node = find_best_matching_subtree(cluster);
665        if (!error) {
666            if (!group_node) { // no matching subtree found
667                switch (notfound) {
668                    case NOTFOUND_WARN:
669                    case NOTFOUND_ABORT: {
670                        const char *msg = GBS_global_string("Could not find matching subtree for cluster '%s'", cluster->get_list_display(NULp));
671                        if (notfound == NOTFOUND_ABORT) error = msg;
672                        else aw_message(msg);
673                        break;
674                    }
675                    case NOTFOUND_IGNORE: break; // silently ignore
676                }
677            }
678            else { // found subtree for group
679                switch (action) {
680                    case GROUP_CREATE: {
681                        char *old_name = group_node->name;
682
683                        if (old_name && existing == EXISTING_GROUP_ABORT) {
684                            error = GBS_global_string("Existing group '%s' is in the way", old_name);
685                        }
686                        else {
687                            if (old_name && existing == EXISTING_GROUP_SKIP) {
688                                changes.skipped++;
689                            }
690                            else {
691                                string new_name = generate_group_name(cluster, group_node);
692
693                                if (old_name) changes.overwritten++; else changes.created++;
694
695                                free(old_name);
696                                group_node->name = ARB_strdup(new_name.c_str());
697
698                                // @@@ DRY that.. it's spread everywhere through libs :(
699                                if (!group_node->gb_node) {
700                                    GBDATA *gb_tree = group_node->get_tree_root()->get_gb_tree();
701                                    GB_transaction ta(gb_tree);
702                                    GBDATA *gb_node = GB_create_container(gb_tree, "node");
703                                    if (!gb_node) {
704                                        error = GB_await_error();
705                                    }
706                                    else {
707                                        error = GBT_write_int(gb_node, "id", 0);
708                                    }
709
710                                    if (!error) {
711                                        group_node->gb_node = gb_node;
712                                    }
713                                }
714                                if (group_node->gb_node && !error) {
715                                    GB_transaction ta(group_node->gb_node);
716                                    error = GBT_write_name_to_groupData(group_node->gb_node, true, group_node->name, true);
717                                    // @@@ if INT-field "keeled" exists -> set to 0
718                                }
719
720                                cluster->update_description(group_node); // change list display
721                            }
722                        }
723                        break;
724                    }
725                    case GROUP_DELETE: {
726                        if (group_node->name && shall_delete_group(group_node->name)) {
727                            char *original = originalGroupName(group_node->name);
728
729                            if (original) {
730                                freeset(group_node->name, original); // restore original name
731                                if (group_node->gb_node) {
732                                    error = GBT_write_name_to_groupData(group_node->gb_node, true, group_node->name, true);
733                                    // @@@ original keeled state is not restored here
734                                }
735                                changes.restored++;
736                            }
737                            else {
738                                freenull(group_node->name);
739                                group_node->gb_node = NULp; // forget ref to group data (@@@ need to delete group data from DB?)
740                                changes.deleted++;
741                            }
742
743                            cluster->update_description(group_node); // change list display
744                        }
745                        break;
746                    }
747                }
748            }
749        }
750
751        if (error) bad_cluster = cluster;
752        get_root_node()->clear_tags();
753    }
754}
755
756static void update_example(AW_root *aw_root) {
757    ID     selID = aw_root->awar(AWAR_CLUSTER_SELECTED)->read_int();
758    string value;
759
760    if (is_cluster(selID)) {
761        ClusterPtr selCluster = global_data->clusterWithID(selID);
762        cl_assert(!selCluster.isNull());
763
764        GroupBuilder  builder(global_data->get_gb_main(), GROUP_CREATE);
765        GroupTree    *group_node = builder.find_best_matching_subtree(selCluster);
766
767        GB_ERROR error = builder.get_error().deliver();
768
769        if (error)           value = GBS_global_string("<error: %s>", error);
770        else if (group_node) value = builder.generate_group_name(selCluster, group_node);
771        else                 value = "<no matching subtree found>";
772    }
773    else value = "<no cluster selected>";
774    aw_root->awar(AWAR_CLUSTER_GROUP_EXAMPLE)->write_string(value.c_str());
775}
776
777static void update_cluster_group(ClusterPtr cluster, GroupBuilder *groupBuilder) {
778    if (!groupBuilder->get_error()) {
779        groupBuilder->update_group(cluster);
780    }
781}
782
783static void accept_proposed_names(ClusterPtr cluster, bool accept) {
784    cluster->accept_proposed(accept);
785}
786
787static void group_clusters(AW_window *, Group_Action action) {
788    AW_root          *aw_root  = global_data->get_aw_root();
789    Group_What        what     = (Group_What)aw_root->awar(AWAR_CLUSTER_GROUP_WHAT)->read_int();
790    AffectedClusters  affected = what == GROUP_LISTED ? ALL_CLUSTERS : SEL_CLUSTER;
791
792    GroupBuilder groupBuilder(global_data->get_gb_main(), action);
793
794    GB_transaction ta(global_data->get_gb_main());
795
796    {
797        MessageSpamFilter suppress("problematic group names");
798        with_affected_clusters_do(aw_root, affected, true, makeClusterCallback(update_cluster_group, &groupBuilder));
799    }
800
801    ARB_ERROR error = groupBuilder.get_error();
802    if (error) {
803        ClusterPtr bad = groupBuilder.get_bad_cluster();
804        if (!bad.isNull()) {
805            select_cluster(bad->get_ID());
806            aw_message("Problematic cluster has been highlighted");
807        }
808    }
809    else {
810        error = groupBuilder.save_modified_tree();
811    }
812
813    error = ta.close(error);
814
815    bool accept = !error;
816    aw_message_if(error);
817    // careful! the following code will invalidate error, so don't use below
818
819    with_affected_clusters_do(aw_root, affected, false, makeClusterCallback(accept_proposed_names, accept)); // just affects display
820    global_data->update_cluster_selection_list();
821}
822
823static void popup_group_clusters_window(AW_window *aw_clusterList) {
824    static AW_window_simple *aws = NULp;
825
826    if (!aws) {
827        AW_root *aw_root = aw_clusterList->get_root();
828
829        aws = new AW_window_simple;
830        aws->init(aw_root, "cluster_groups", "Cluster groups");
831
832        aws->auto_space(10, 10);
833
834        aws->callback(AW_POPDOWN);
835        aws->create_button("CLOSE", "CLOSE", "C");
836
837        aws->callback(makeHelpCallback("cluster_group.hlp"));
838        aws->create_button("HELP", "HELP");
839
840        aws->at_newline();
841
842        aws->label("For");
843        aws->create_option_menu(AWAR_CLUSTER_GROUP_WHAT);
844        aws->insert_option        ("selected cluster", "s", GROUP_SELECTED);
845        aws->insert_default_option("listed clusters",  "l", GROUP_LISTED);
846        aws->update_option_menu();
847
848        aws->at_newline();
849
850        aws->label("with a min. cluster/subtree identity (%) of");
851        aws->create_input_field(AWAR_CLUSTER_GROUP_IDENTITY, 4);
852
853        aws->at_newline();
854
855        aws->label("-> if no matching subtree found");
856        aws->create_option_menu(AWAR_CLUSTER_GROUP_NOTFOUND);
857        aws->insert_default_option("abort",          "a", NOTFOUND_ABORT);
858        aws->insert_option        ("warn",           "w", NOTFOUND_WARN);
859        aws->insert_option        ("ignore",         "i", NOTFOUND_IGNORE);
860        aws->update_option_menu();
861
862        aws->at_newline();
863
864        aws->callback(makeWindowCallback(group_clusters, GROUP_CREATE));
865        aws->create_autosize_button("CREATE_GROUPS", "create groups!");
866
867        aws->label("If group exists");
868        aws->create_option_menu(AWAR_CLUSTER_GROUP_EXISTING);
869        aws->insert_default_option("abort",                "a", EXISTING_GROUP_ABORT);
870        aws->insert_option        ("skip",                 "s", EXISTING_GROUP_SKIP);
871        aws->insert_option        ("overwrite (caution!)", "o", EXISTING_GROUP_OVERWRITE);
872        aws->insert_option        ("append original",      "p", EXISTING_GROUP_APPEND_ORG);
873        aws->update_option_menu();
874
875        aws->at_newline();
876
877        aws->callback(makeWindowCallback(group_clusters, GROUP_DELETE));
878        aws->create_autosize_button("DELETE_GROUPS", "delete groups!");
879
880        aws->create_text_toggle(AWAR_CLUSTER_GROUP_PREFIX_MATCH, "(all)", "(where prefix matches)", 30);
881
882        aws->at_newline(); aws->label("Name prefix:"); aws->create_input_field(AWAR_CLUSTER_GROUP_PREFIX, 20);
883        aws->at_newline(); aws->label("Name suffix:"); aws->create_input_field(AWAR_CLUSTER_GROUP_SUFFIX, 20);
884        aws->at_newline(); aws->label("Separator:");   aws->create_input_field(AWAR_CLUSTER_GROUP_PARTSEP, 3);
885
886        aws->at_newline();
887        aws->button_length(60);
888        aws->label("=>");
889        aws->create_button(NULp, AWAR_CLUSTER_GROUP_EXAMPLE);
890
891        aws->window_fit();
892    }
893
894    aws->activate();
895}
896
897// ---------------
898//      delete
899
900static void delete_selected_cluster(ClusterPtr cluster) {
901    int pos    = global_data->get_pos(cluster, SHOWN_CLUSTERS);
902    int nextId = global_data->idAtPos(pos+1, SHOWN_CLUSTERS);
903    select_cluster(nextId);
904    global_data->remove(cluster, SHOWN_CLUSTERS);
905}
906static void delete_clusters(AW_window *aww, AffectedClusters affected) {
907    switch (affected) {
908        case SEL_CLUSTER:
909            with_affected_clusters_do(aww->get_root(), affected, true, makeClusterCallback(delete_selected_cluster));
910            break;
911        case ALL_CLUSTERS:
912            select_cluster(0);
913            global_data->clear(SHOWN_CLUSTERS);
914            break;
915    }
916
917    update_cluster_sellist();
918}
919
920// ----------------------
921//      store/restore
922
923static void store_selected_cluster(ClusterPtr cluster) {
924    int pos    = global_data->get_pos(cluster, SHOWN_CLUSTERS);
925    int nextId = global_data->idAtPos(pos+1, SHOWN_CLUSTERS);
926
927    select_cluster(nextId);
928    global_data->store(cluster->get_ID());
929}
930static void store_clusters(AW_window *aww, AffectedClusters affected) {
931    switch (affected) {
932        case SEL_CLUSTER:
933            with_affected_clusters_do(aww->get_root(), affected, true, makeClusterCallback(store_selected_cluster));
934            break;
935        case ALL_CLUSTERS:
936            select_cluster(0);
937            global_data->store_all();
938            break;
939    }
940
941    update_all(aww);
942}
943
944
945static void restore_clusters(AW_window *aww) {
946    global_data->restore_all();
947    update_all(aww);
948}
949static void swap_clusters(AW_window *aww) {
950    global_data->swap_all();
951    select_cluster(0);
952    update_all(aww);
953}
954
955// ---------------------------------
956//      cluster detection window
957
958AW_window *DI_create_cluster_detection_window(AW_root *aw_root, WeightedFilter *weightedFilter) {
959    static AW_window_simple *aws = NULp;
960    if (!aws) {
961        cl_assert(!global_data);
962        global_data = new ClustersData(*weightedFilter);
963
964        aws = new AW_window_simple;
965        aws->init(aw_root, "DETECT_CLUSTERS", "Detect clusters in tree");
966        aws->load_xfig("di_clusters.fig");
967
968        aws->on_hide(di_forget_global_data);
969
970        // -------------------
971        //      upper area
972
973        aws->at("close");
974        aws->callback(AW_POPDOWN);
975        aws->create_button("CLOSE", "CLOSE");
976
977        aws->at("help");
978        aws->callback(makeHelpCallback("di_clusters.hlp"));
979        aws->create_button("HELP", "HELP");
980
981        aws->at("max_dist");
982        aws->d_callback(makeWindowCallback(calculate_clusters));
983        aws->create_input_field(AWAR_CLUSTER_MAXDIST, 12);
984
985        aws->at("min_size");
986        aws->d_callback(makeWindowCallback(calculate_clusters));
987        aws->create_input_field(AWAR_CLUSTER_MINSIZE, 5);
988
989        aws->at("calculate");
990        aws->callback(calculate_clusters);
991        aws->create_autosize_button("CALC", "Detect clusters");
992
993        aws->button_length(20);
994        aws->at("tree_name");
995        aws->create_button(NULp, AWAR_DIST_TREE_CURR_NAME);
996
997        // -------------------
998        //      lower area
999
1000        aws->button_length(10);
1001
1002        // column 1
1003
1004        aws->at("select_rep"); aws->create_toggle(AWAR_CLUSTER_SELECTREP);
1005        aws->at("mark"); aws->callback(makeWindowCallback(mark_clusters, SEL_CLUSTER, true)); aws->create_button("MARK", "Mark");
1006        aws->at("auto_mark"); aws->create_toggle(AWAR_CLUSTER_AUTOMARK);
1007
1008        aws->at("mark_what");
1009        aws->create_option_menu(AWAR_CLUSTER_MARKREP);
1010        aws->insert_default_option("cluster w/o repr.",   "d", CMM_ALL_BUT_REP);
1011        aws->insert_option        ("whole cluster",       "b", CMM_ALL);
1012        aws->insert_option        ("only representative", "s", CMM_ONLY_REP);
1013        aws->update_option_menu();
1014
1015        aws->at("mark_all"); aws->callback(makeWindowCallback(mark_clusters, ALL_CLUSTERS, true)); aws->create_button("MARKALL", "Mark all");
1016
1017        // column 2
1018
1019        aws->button_length(18);
1020
1021        aws->at("group"); aws->callback(popup_group_clusters_window); aws->create_button("GROUP", "Cluster groups..");
1022
1023        aws->at("sort");
1024        aws->create_option_menu(AWAR_CLUSTER_ORDER);
1025        aws->insert_default_option("by mean distance",  "d", SORT_BY_MEANDIST);
1026        aws->insert_option        ("by min bases used", "b", SORT_BY_MIN_BASES);
1027        aws->insert_option        ("by size",           "s", SORT_BY_CLUSTERSIZE);
1028        aws->insert_option        ("by tree position",  "p", SORT_BY_TREEPOSITION);
1029        aws->insert_option        ("by min distance",   "i", SORT_BY_MIN_DIST);
1030        aws->insert_option        ("by max distance",   "x", SORT_BY_MAX_DIST);
1031        aws->insert_option        ("reverse",           "r", SORT_REVERSE);
1032        aws->update_option_menu();
1033
1034        // store/restore
1035
1036        aws->at("store_all"); aws->callback(makeWindowCallback(store_clusters, ALL_CLUSTERS)); aws->create_button("STOREALL", "Store all");
1037        aws->at("store");     aws->callback(makeWindowCallback(store_clusters, SEL_CLUSTER));  aws->create_button("STORESEL", "Store selected");
1038        aws->at("restore");   aws->callback(restore_clusters);                                 aws->create_button("RESTORE",  AWAR_CLUSTER_RESTORE_LABEL);
1039        aws->at("swap");      aws->callback(swap_clusters);                                    aws->create_button("Swap",     "Swap stored");
1040
1041        // column 4
1042
1043        aws->at("clear");  aws->callback(makeWindowCallback(delete_clusters, ALL_CLUSTERS)); aws->create_button("CLEAR", "Clear list");
1044        aws->at("delete"); aws->callback(makeWindowCallback(delete_clusters, SEL_CLUSTER));  aws->create_button("DEL",   "Delete selected");
1045
1046        // --------------------
1047        //      clusterlist
1048
1049        aws->at("cluster_list");
1050        global_data->clusterList = aws->create_selection_list(AWAR_CLUSTER_SELECTED);
1051        update_cluster_sellist();
1052
1053        aw_root->awar(AWAR_CLUSTER_SELECTED)->add_callback(select_cluster_cb);
1054        aw_root->awar(AWAR_CLUSTER_ORDER)->add_callback(sort_order_changed_cb);
1055        sort_order_changed_cb(aw_root);
1056    }
1057
1058    return aws;
1059}
1060
1061void DI_create_cluster_awars(AW_root *aw_root, AW_default def, AW_default db) {
1062    aw_root->awar_float(AWAR_CLUSTER_MAXDIST,   3.0,             def)->set_minmax(0.0, 100.0);
1063    aw_root->awar_int  (AWAR_CLUSTER_MINSIZE,   7,               def)->set_minmax(2, INT_MAX);
1064    aw_root->awar_int  (AWAR_CLUSTER_AUTOMARK,  1,               def);
1065    aw_root->awar_int  (AWAR_CLUSTER_MARKREP,   CMM_ALL_BUT_REP, def);
1066    aw_root->awar_int  (AWAR_CLUSTER_SELECTREP, 1,               def);
1067
1068    aw_root->awar_int   (AWAR_CLUSTER_ORDER,         SORT_BY_MEANDIST, def);
1069    aw_root->awar_string(AWAR_CLUSTER_RESTORE_LABEL, "None stored",    def);
1070
1071    aw_root->awar_int(AWAR_CLUSTER_GROUP_WHAT,     GROUP_LISTED,   def);
1072    aw_root->awar_int(AWAR_CLUSTER_GROUP_NOTFOUND, NOTFOUND_ABORT, def);
1073
1074    aw_root->awar_int   (AWAR_CLUSTER_GROUP_IDENTITY,     100, def)->set_minmax(1, 100);
1075    aw_root->awar_int   (AWAR_CLUSTER_GROUP_PREFIX_MATCH, 1,   def);
1076    aw_root->awar_string(AWAR_CLUSTER_GROUP_EXAMPLE,      "",  def);
1077
1078    aw_root->awar_int   (AWAR_CLUSTER_SELECTED,       0,                    def)->add_callback(update_example);
1079    aw_root->awar_int   (AWAR_CLUSTER_GROUP_EXISTING, EXISTING_GROUP_ABORT, def)->add_callback(update_example);
1080    aw_root->awar_string(AWAR_CLUSTER_GROUP_PREFIX,   "cluster",            def)->add_callback(update_example);
1081    aw_root->awar_string(AWAR_CLUSTER_GROUP_SUFFIX,   "%o%p",               def)->add_callback(update_example);
1082    aw_root->awar_string(AWAR_CLUSTER_GROUP_PARTSEP,  "-",                  def)->add_callback(update_example);
1083
1084    aw_root->awar_int(AWAR_TREE_REFRESH, 0, db);
1085
1086    update_example(aw_root);
1087}
1088
Note: See TracBrowser for help on using the repository browser.