source: branches/lib/NTREE/ad_trees.cxx

Last change on this file was 19549, checked in by westram, 5 weeks ago
  • reintegrates 'sync' into 'trunk'
    • fixes #869
      • introduces synchronisation of external commands via DB
      • macros now wait for these external commands to finish ⇒ macro can continue with results
  • adds: log:branches/sync@19535:19548
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 77.3 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : ad_trees.cxx                                      //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11#include "tree_position.h"
12#include "ad_trees.h"
13#include "NT_tree_cmp.h"
14
15#include <CT_ctree.hxx>
16
17#include <TreeAdmin.h>
18#include <TreeRead.h>
19#include <TreeWrite.h>
20#include <TreeCallbacks.hxx>
21
22#include <awt_sel_boxes.hxx>
23#include <awt_modules.hxx>
24#include <awt_TreeAwars.hxx>
25
26#include <aw_awars.hxx>
27#include <aw_edit.hxx>
28#include <aw_file.hxx>
29#include <aw_msg.hxx>
30#include <aw_root.hxx>
31#include <aw_select.hxx>
32
33#include <arb_strbuf.h>
34#include <arb_file.h>
35#include <arb_diff.h>
36#include <arb_defs.h>
37
38#include <cctype>
39#include <awt_config_manager.hxx>
40#include <xcmd.hxx>
41
42#define AWAR_TREE_SAV "ad_tree/"
43#define AWAR_TREE_TMP "tmp/ad_tree/"
44
45#define AWAR_TREE_SECURITY AWAR_TREE_TMP "tree_security"
46#define AWAR_TREE_REM      AWAR_TREE_TMP "tree_rem"
47#define AWAR_TREE_IMPORT   AWAR_TREE_TMP "import_tree"
48
49#define AWAR_GROUPXFER_SAV AWAR_TREE_SAV "groupxfer/"
50
51#define AWAR_GROUPXFER_SOURCE         AWAR_GROUPXFER_SAV "restrict"
52#define AWAR_GROUPXFER_OVERWRITE_MODE AWAR_GROUPXFER_SAV "overwrite"
53#define AWAR_GROUPXFER_INGROUP_ABS    AWAR_GROUPXFER_SAV "ingroup/abs"
54#define AWAR_GROUPXFER_INGROUP_REL    AWAR_GROUPXFER_SAV "ingroup/rel"
55#define AWAR_GROUPXFER_INGROUP_LIM    AWAR_GROUPXFER_SAV "ingroup/lim"
56#define AWAR_GROUPXFER_OUTGROUP_ABS   AWAR_GROUPXFER_SAV "outgroup/abs"
57#define AWAR_GROUPXFER_OUTGROUP_REL   AWAR_GROUPXFER_SAV "outgroup/rel"
58#define AWAR_GROUPXFER_OUTGROUP_LIM   AWAR_GROUPXFER_SAV "outgroup/lim"
59#define AWAR_GROUPXFER_UNKNOWN_ABS    AWAR_GROUPXFER_SAV "unknown"
60#define AWAR_GROUPXFER_KEELING        AWAR_GROUPXFER_SAV "keeling"
61#define AWAR_GROUPXFER_ACI            AWAR_GROUPXFER_SAV "aci"
62
63#define AWAR_TREE_EXPORT_FILEBASE AWAR_TREE_TMP "export_tree"
64#define AWAR_TREE_EXPORT_FILTER   AWAR_TREE_EXPORT_FILEBASE "/filter"
65#define AWAR_TREE_EXPORT_NAME     AWAR_TREE_EXPORT_FILEBASE "/file_name"
66
67#define AWAR_TREE_EXPORT_SAV AWAR_TREE_SAV "export_tree/"
68
69#define AWAR_TREE_EXPORT_FORMAT             AWAR_TREE_EXPORT_SAV "format"
70#define AWAR_TREE_EXPORT_NDS                AWAR_TREE_EXPORT_SAV "NDS"
71#define AWAR_TREE_EXPORT_INCLUDE_BOOTSTRAPS AWAR_TREE_EXPORT_SAV "bootstraps"
72#define AWAR_TREE_EXPORT_INCLUDE_BRANCHLENS AWAR_TREE_EXPORT_SAV "branchlens"
73#define AWAR_TREE_EXPORT_INCLUDE_GROUPNAMES AWAR_TREE_EXPORT_SAV "groupnames"
74#define AWAR_TREE_EXPORT_HIDE_FOLDED_GROUPS AWAR_TREE_EXPORT_SAV "hide_folded"
75#define AWAR_TREE_EXPORT_QUOTEMODE          AWAR_TREE_EXPORT_SAV "quote_mode"
76#define AWAR_TREE_EXPORT_NON_ASCII          AWAR_TREE_EXPORT_SAV "non_ASCII"
77#define AWAR_TREE_EXPORT_REPLACE            AWAR_TREE_EXPORT_SAV "replace"
78
79
80#define AWAR_TREE_CONSENSE_TMP AWAR_TREE_TMP "consense/"
81#define AWAR_TREE_CONSENSE_SAV AWAR_TREE_SAV "consense/"
82
83#define AWAR_TREE_CONSENSE_TREE     AWAR_TREE_CONSENSE_SAV "tree"
84#define AWAR_TREE_CONSENSE_SELECTED AWAR_TREE_CONSENSE_TMP "selected"
85
86static void tree_vars_callback(AW_root *aw_root) {
87    // map tree awars to display database entries (security+comment)
88
89    if (GLOBAL.gb_main) {
90        GB_transaction ta(GLOBAL.gb_main);
91
92        char   *treename = aw_root->awar(AWAR_TREE_NAME)->read_string();
93        GBDATA *gb_tree  = GBT_find_tree(GLOBAL.gb_main, treename);
94
95        if (!gb_tree) {
96            aw_root->awar(AWAR_TREE_SECURITY)->unmap();
97            aw_root->awar(AWAR_TREE_REM)->unmap();
98        }
99        else {
100            GBDATA *tree_prot = GB_search(gb_tree, "security", GB_FIND);
101            if (!tree_prot) GBT_readOrCreate_int(gb_tree, "security", GB_read_security_write(gb_tree));
102            tree_prot         = GB_search(gb_tree, "security", GB_INT);
103
104            GBDATA *tree_rem = GB_search(gb_tree, "remark",   GB_STRING);
105            aw_root->awar(AWAR_TREE_SECURITY)->map(tree_prot);
106            aw_root->awar(AWAR_TREE_REM)     ->map(tree_rem);
107        }
108
109        // create default filename from tree-name:
110        {
111            char *suffix = aw_root->awar(AWAR_TREE_EXPORT_FILTER)->read_string();
112            char *fname  = GBS_string_eval(treename, GBS_global_string("*=*1.%s:tree_*=*1", suffix));
113
114            aw_root->awar(AWAR_TREE_EXPORT_NAME)->write_string(fname); // create default file name
115
116            free(fname);
117            free(suffix);
118        }
119
120        free(treename);
121    }
122}
123
124static void update_default_treename_cb(AW_root *aw_root) {
125    // update import tree name depending on file name
126    GB_transaction ta(GLOBAL.gb_main);
127
128    char *treename        = aw_root->awar(AWAR_TREE_IMPORT "/file_name")->read_string();
129    char *treename_nopath = strrchr(treename, '/');
130
131    if (treename_nopath) {
132        ++treename_nopath;
133    }
134    else {
135        treename_nopath = treename;
136    }
137
138    char *fname = GBS_string_eval(treename_nopath, "*.tree=tree_*1:*.ntree=tree_*1:*.xml=tree_*1:.=:-=_: =_");
139    aw_root->awar(AWAR_TREE_IMPORT "/tree_name")->write_string(fname);
140
141    free(fname);
142    free(treename);
143}
144
145static void ad_tree_set_security(AW_root *aw_root) {
146    if (GLOBAL.gb_main) {
147        GB_transaction ta(GLOBAL.gb_main);
148
149        char   *treename = aw_root->awar(AWAR_TREE_NAME)->read_string();
150        GBDATA *gb_tree  = GBT_find_tree(GLOBAL.gb_main, treename);
151
152        if (gb_tree) {
153            long prot = aw_root->awar(AWAR_TREE_SECURITY)->read_int();
154            long old  = GB_read_security_delete(gb_tree);
155
156            GB_ERROR error = NULp;
157            if (old != prot) {
158                error             = GB_write_security_delete(gb_tree, prot);
159                if (!error) error = GB_write_security_write(gb_tree, prot);
160            }
161            aw_message_if(error);
162        }
163        free(treename);
164    }
165}
166
167enum ExportTreeType {
168    AD_TREE_EXPORT_FORMAT_NEWICK,
169    AD_TREE_EXPORT_FORMAT_XML,
170    AD_TREE_EXPORT_FORMAT_NEWICK_PRETTY,
171};
172
173enum ExportNodeType { // careful: value saved in awar!
174    AD_TREE_EXPORT_NODE_SPECIES_NAME,
175    AD_TREE_EXPORT_NODE_NDS,
176    AD_TREE_EXPORT_NODE_NDS_TRUNCATED
177};
178
179static void update_filter_cb(AW_root *root) {
180    const char *filter_type = NULp;
181
182    switch (ExportTreeType(root->awar(AWAR_TREE_EXPORT_FORMAT)->read_int())) {
183        case AD_TREE_EXPORT_FORMAT_XML:
184            filter_type = "xml";
185            break;
186
187        case AD_TREE_EXPORT_FORMAT_NEWICK:
188        case AD_TREE_EXPORT_FORMAT_NEWICK_PRETTY:
189            switch (ExportNodeType(root->awar(AWAR_TREE_EXPORT_NDS)->read_int())) {
190                case AD_TREE_EXPORT_NODE_SPECIES_NAME:  filter_type = "tree"; break;
191                case AD_TREE_EXPORT_NODE_NDS:
192                case AD_TREE_EXPORT_NODE_NDS_TRUNCATED: filter_type = "ntree"; break;
193            }
194            break;
195
196        default: nt_assert(0); break;
197    }
198
199    nt_assert(filter_type);
200    root->awar(AWAR_TREE_EXPORT_FILTER)->write_string(filter_type);
201}
202
203void create_trees_var(AW_root *aw_root, AW_default aw_def) {
204    AW_awar *awar_tree_name = aw_root->awar_string(AWAR_TREE_NAME, NULp, aw_def)->set_srt(SRT_AUTOCORRECT_TREENAME);
205
206    aw_root->awar_pointer(AWAR_GROUP, NULp, aw_def);
207
208    TreeAdmin::create_awars(aw_root, aw_def, true);
209
210    aw_root->awar_int   (AWAR_TREE_SECURITY, 0,    aw_def);
211    aw_root->awar_string(AWAR_TREE_REM,      NULp, aw_def);
212
213    AW_create_fileselection_awars(aw_root, AWAR_TREE_EXPORT_FILEBASE, "", ".tree", "treefile");
214    aw_root->awar_int(AWAR_TREE_EXPORT_FORMAT, AD_TREE_EXPORT_FORMAT_NEWICK, aw_def)-> add_callback(update_filter_cb);
215    aw_root->awar_int(AWAR_TREE_EXPORT_NDS,  AD_TREE_EXPORT_NODE_SPECIES_NAME, aw_def)-> add_callback(update_filter_cb);
216
217    aw_root->awar_int(AWAR_TREE_EXPORT_INCLUDE_BOOTSTRAPS, 0, aw_def);
218    aw_root->awar_int(AWAR_TREE_EXPORT_INCLUDE_BRANCHLENS, 1, aw_def);
219    aw_root->awar_int(AWAR_TREE_EXPORT_HIDE_FOLDED_GROUPS, 0, aw_def);
220    aw_root->awar_int(AWAR_TREE_EXPORT_INCLUDE_GROUPNAMES, 1, aw_def);
221
222    aw_root->awar_int(AWAR_TREE_EXPORT_QUOTEMODE, LABEL_SINGLE_QUOTES, aw_def); // old default behavior
223    aw_root->awar_int(AWAR_TREE_EXPORT_NON_ASCII, 0,                   aw_def); // changes default behavior (=reject non-ASCII)
224    aw_root->awar_int(AWAR_TREE_EXPORT_REPLACE,   0,                   aw_def); // old default behavior
225
226    AW_create_fileselection_awars(aw_root, AWAR_TREE_IMPORT, "", ".tree", "treefile");
227
228    aw_root->awar_string(AWAR_TREE_IMPORT "/tree_name", "tree_",    aw_def)->set_srt(SRT_AUTOCORRECT_TREENAME);
229
230    aw_root->awar(AWAR_TREE_IMPORT "/file_name")->add_callback(update_default_treename_cb);
231    awar_tree_name->add_callback(tree_vars_callback);
232    awar_tree_name->map(AWAR_TREE);
233    aw_root->awar(AWAR_TREE_SECURITY)->add_callback(ad_tree_set_security);
234
235    aw_root->awar_int(AWAR_GROUPXFER_SOURCE,         XFER_ALL_GROUPS,        aw_def);
236    aw_root->awar_int(AWAR_GROUPXFER_OVERWRITE_MODE, REMOVE_EXISTING_GROUPS, aw_def);
237
238    aw_root->awar_float(AWAR_GROUPXFER_INGROUP_ABS,  1.0,    aw_def);
239    aw_root->awar_float(AWAR_GROUPXFER_OUTGROUP_ABS, 1.0,    aw_def);
240    aw_root->awar_float(AWAR_GROUPXFER_INGROUP_REL,  0.0,    aw_def);
241    aw_root->awar_float(AWAR_GROUPXFER_OUTGROUP_REL, 0.0,    aw_def);
242    aw_root->awar_float(AWAR_GROUPXFER_INGROUP_LIM,  0.0,    aw_def);
243    aw_root->awar_float(AWAR_GROUPXFER_OUTGROUP_LIM, 100.0,  aw_def);
244    aw_root->awar_float(AWAR_GROUPXFER_UNKNOWN_ABS,  0.0001, aw_def);
245    aw_root->awar_float(AWAR_GROUPXFER_KEELING,      0.01,   aw_def);
246
247    aw_root->awar_string(AWAR_GROUPXFER_ACI, "", aw_def);
248
249    aw_root->awar_string(AWAR_TREE_CONSENSE_TREE, "tree_consensus", aw_def)->set_srt(SRT_AUTOCORRECT_TREENAME);
250    AW_awar *ctree_awar = aw_root->awar_string(AWAR_TREE_CONSENSE_SELECTED, "", aw_def);
251    AWT_registerTreeAwarSimple(ctree_awar);
252
253    update_filter_cb(aw_root);
254    tree_vars_callback(aw_root);
255}
256
257static void tree_save_cb(AW_window *aww) {
258    AW_root  *aw_root   = aww->get_root();
259    char     *tree_name = aw_root->awar(AWAR_TREE_NAME)->read_string();
260
261    GB_ERROR error = NULp;
262
263    if (!tree_name || !strlen(tree_name)) {
264        error = "Please select a tree first";
265    }
266    else {
267        char *fname   = aw_root->awar(AWAR_TREE_EXPORT_NAME)->read_string();
268        char *db_name = aw_root->awar(AWAR_DB_NAME)->read_string();
269
270        SmartPtr<TreeLabeler> labeler;
271        switch (ExportNodeType(aw_root->awar(AWAR_TREE_EXPORT_NDS)->read_int())) {
272            case AD_TREE_EXPORT_NODE_NDS:           labeler = new NDS_Labeler(NDS_OUTPUT_LEAFTEXT_UNLIMITED); break;
273            case AD_TREE_EXPORT_NODE_NDS_TRUNCATED: labeler = new NDS_Labeler(NDS_OUTPUT_LEAFTEXT);           break;
274            case AD_TREE_EXPORT_NODE_SPECIES_NAME:  labeler = new Node_ID_Labeler;                            break;
275        }
276
277        ExportTreeType exportType = static_cast<ExportTreeType>(aw_root->awar(AWAR_TREE_EXPORT_FORMAT)->read_int());
278        switch (exportType) {
279            case AD_TREE_EXPORT_FORMAT_XML:
280                error = TREE_write_XML(GLOBAL.gb_main, db_name, tree_name, *labeler,
281                                       aw_root->awar(AWAR_TREE_EXPORT_HIDE_FOLDED_GROUPS)->read_int(),
282                                       fname);
283                break;
284
285            case AD_TREE_EXPORT_FORMAT_NEWICK:
286            case AD_TREE_EXPORT_FORMAT_NEWICK_PRETTY:
287                LabelQuoting quoteMode = LabelQuoting(aw_root->awar(AWAR_TREE_EXPORT_QUOTEMODE)->read_int());
288
289                if (aw_root->awar(AWAR_TREE_EXPORT_REPLACE)  ->read_int()) quoteMode = LabelQuoting(quoteMode|LABEL_FORCE_REPLACE);
290                if (aw_root->awar(AWAR_TREE_EXPORT_NON_ASCII)->read_int()) quoteMode = LabelQuoting(quoteMode|LABEL_ACCEPT_NON_ASCII);
291
292                error = TREE_write_Newick(GLOBAL.gb_main, tree_name, *labeler,
293                                          aw_root->awar(AWAR_TREE_EXPORT_INCLUDE_BRANCHLENS)->read_int(),
294                                          aw_root->awar(AWAR_TREE_EXPORT_INCLUDE_BOOTSTRAPS)->read_int(),
295                                          aw_root->awar(AWAR_TREE_EXPORT_INCLUDE_GROUPNAMES)->read_int(),
296                                          exportType == AD_TREE_EXPORT_FORMAT_NEWICK_PRETTY,
297                                          quoteMode,
298                                          fname);
299                break;
300        }
301
302        free(db_name);
303        free(fname);
304    }
305
306    aww->hide_or_notify(error);
307    free(tree_name);
308
309    AW_refresh_fileselection(aw_root, AWAR_TREE_EXPORT_FILEBASE);
310}
311
312static AWT_config_mapping_def tree_export_config_mapping[] = {
313    { AWAR_TREE_EXPORT_FORMAT,             "format" },
314    { AWAR_TREE_EXPORT_NDS,                "nodetype" },
315    { AWAR_TREE_EXPORT_INCLUDE_BRANCHLENS, "lengths" },
316    { AWAR_TREE_EXPORT_INCLUDE_BOOTSTRAPS, "bootstraps" },
317    { AWAR_TREE_EXPORT_INCLUDE_GROUPNAMES, "groupnames" },
318    { AWAR_TREE_EXPORT_HIDE_FOLDED_GROUPS, "hidefolded" },
319    { AWAR_TREE_EXPORT_QUOTEMODE,          "quotemode" },
320    { AWAR_TREE_EXPORT_NON_ASCII,          "nonASCII" },
321    { AWAR_TREE_EXPORT_REPLACE,            "replacechars" },
322
323    { NULp, NULp }
324};
325
326static AW_window *create_tree_export_window(AW_root *root) {
327    AW_window_simple *aws = new AW_window_simple;
328    aws->init(root, "SAVETREE", "TREE SAVE");
329    aws->load_xfig("tree_export.fig");
330
331    aws->at("close");
332    aws->callback(AW_POPDOWN);
333    aws->create_button("CLOSE", "CLOSE", "C");
334
335    aws->at("help");
336    aws->callback(makeHelpCallback("tr_export.hlp"));
337    aws->create_button("HELP", "HELP", "H");
338
339    AW_create_standard_fileselection(aws, AWAR_TREE_EXPORT_FILEBASE);
340
341    aws->at("format");
342    aws->label("Tree format");
343    aws->create_option_menu(AWAR_TREE_EXPORT_FORMAT);
344    aws->insert_option("Newick tree",              "N", AD_TREE_EXPORT_FORMAT_NEWICK);
345    aws->insert_option("Newick (pretty, but big)", "P", AD_TREE_EXPORT_FORMAT_NEWICK_PRETTY);
346    aws->insert_option("XML tree (export only)",   "X", AD_TREE_EXPORT_FORMAT_XML);
347    aws->update_option_menu();
348
349    aws->at("labels");
350    aws->label("Tree labels");
351    aws->create_option_menu(AWAR_TREE_EXPORT_NDS);
352    aws->insert_default_option("Species/group ID ('name')", "S", AD_TREE_EXPORT_NODE_SPECIES_NAME);
353    aws->insert_option        ("NDS",                       "N", AD_TREE_EXPORT_NODE_NDS);
354    aws->insert_option        ("NDS (truncated)",           "t", AD_TREE_EXPORT_NODE_NDS_TRUNCATED);
355    aws->update_option_menu();
356
357    aws->at("quote");
358    aws->label("Label quoting (Newick only)");
359    aws->create_option_menu(AWAR_TREE_EXPORT_QUOTEMODE);
360    aws->insert_option("none",            "n", LABEL_DISALLOW_QUOTES);
361    aws->insert_option("single",          "s", LABEL_SINGLE_QUOTES);
362    aws->insert_option("double",          "d", LABEL_DOUBLE_QUOTES);
363    aws->insert_option("single (forced)", "i", LABEL_SINGLE_QUOTES | LABEL_FORCE_QUOTES);
364    aws->insert_option("double (forced)", "o", LABEL_DOUBLE_QUOTES | LABEL_FORCE_QUOTES);
365    aws->update_option_menu();
366
367    aws->at("ascii"); aws->label("Accept non-ASCII chars");        aws->create_toggle(AWAR_TREE_EXPORT_NON_ASCII);
368    aws->at("repl");  aws->label("Replace problem chars");         aws->create_toggle(AWAR_TREE_EXPORT_REPLACE);
369    aws->at("group"); aws->label("Save group names");              aws->create_toggle(AWAR_TREE_EXPORT_INCLUDE_GROUPNAMES);
370    aws->at("hide");  aws->label("Hide folded groups (XML only)"); aws->create_toggle(AWAR_TREE_EXPORT_HIDE_FOLDED_GROUPS);
371    aws->at("len");   aws->label("Save branch lengths");           aws->create_toggle(AWAR_TREE_EXPORT_INCLUDE_BRANCHLENS);
372    aws->at("boot");  aws->label("Save bootstrap values");         aws->create_toggle(AWAR_TREE_EXPORT_INCLUDE_BOOTSTRAPS);
373
374    aws->at("save");
375    aws->button_length(10);
376    aws->callback(tree_save_cb);
377    aws->create_button("SAVE", "SAVE", "o");
378
379    aws->at("cfg");
380    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "tree_export", tree_export_config_mapping);
381
382    update_filter_cb(root);
383
384    return aws;
385}
386
387static char *readXmlTree(char *fname) {
388    // create a temp file
389    char tempFile[]  = "newickXXXXXX";
390    int createTempFile = mkstemp(tempFile);
391
392    if (createTempFile) {
393        GBS_strstruct buf(strlen(fname));
394
395        // extract path from fname in order to place a copy of dtd file required to validate xml file
396        {
397            char *tmpFname = ARB_strdup(fname);
398            for (char *tok = strtok(tmpFname, "/"); tok;) {
399                char *tmp = tok;
400                tok = strtok(NULp, "/");
401                if (tok) {
402                    buf.put('/');
403                    buf.cat(tmp);
404                }
405            }
406            free(tmpFname);
407        }
408
409        // linking arb_tree.dtd file to the Path from where xml file is loaded
410        char *command = GBS_global_string_copy("ln -s %s/lib/dtd/arb_tree.dtd %s/.", GB_getenvARBHOME(), buf.get_data());
411        ARB_system(command, XCmdType(XCMD_SYNC_WAIT_ON_ERROR, GLOBAL.gb_main));
412
413        // execute xml2newick to convert xml format tree to newick format tree
414        command = GBS_global_string_copy("xml2newick %s %s", fname, tempFile);
415        ARB_system(command, XCmdType(XCMD_SYNC_WAIT_ON_ERROR, GLOBAL.gb_main));
416
417        free(command);
418
419        // return newick format tree file
420        return ARB_strdup(tempFile);
421    }
422    else {
423        printf("Failed to create Temporary File to Parse xml file!\n");
424        return NULp;
425    }
426}
427
428static void tree_load_cb(AW_window *aww) {
429    GB_ERROR  error     = NULp;
430    AW_root  *aw_root   = aww->get_root();
431    char     *tree_name = aw_root->awar(AWAR_TREE_IMPORT "/tree_name")->read_string();
432
433    {
434        char *pcTreeFormat = aw_root->awar(AWAR_TREE_IMPORT "/filter")->read_string();
435        char *fname        = aw_root->awar(AWAR_TREE_IMPORT "/file_name")->read_string();
436        if (strcmp(pcTreeFormat, "xml") == 0) {
437            char *tempFname = readXmlTree(fname);
438
439            error = TREE_load_to_db(GLOBAL.gb_main, tempFname, tree_name);
440
441            GB_unlink_or_warn(tempFname, NULp);
442            free(tempFname);
443        }
444        else {
445            error = TREE_load_to_db(GLOBAL.gb_main, fname, tree_name);
446        }
447
448        free(fname);
449        free(pcTreeFormat);
450    }
451
452    aww->hide_or_notify(error);
453    if (!error) aw_root->awar(AWAR_TREE)->write_string(tree_name); // show new tree
454
455    free(tree_name);
456}
457
458static AW_window *create_tree_import_window(AW_root *root) {
459    AW_window_simple *aws = new AW_window_simple;
460    aws->init(root, "LOAD_TREE", "TREE LOAD");
461    aws->load_xfig("sel_box_tree.fig");
462
463    aws->at("close");
464    aws->callback(AW_POPDOWN);
465    aws->create_button("CLOSE", "CLOSE", "C");
466
467    aws->at("help");
468    aws->callback(makeHelpCallback("tr_import.hlp"));
469    aws->create_button("HELP", "HELP", "H");
470
471    aws->at("format");
472    aws->create_option_menu(AWAR_TREE_IMPORT "/filter");
473    aws->insert_default_option("Newick", "t", "tree");
474    aws->insert_option("XML", "x", "xml");
475    aws->update_option_menu();
476
477    aws->at("user");
478    aws->label("Tree name");
479    aws->create_input_field(AWAR_TREE_IMPORT "/tree_name", 15);
480
481    AW_create_standard_fileselection(aws, AWAR_TREE_IMPORT);
482
483    aws->at("save2");
484    aws->callback(tree_load_cb);
485    aws->create_button("LOAD", "LOAD", "o");
486
487    aws->window_fit();
488
489    return aws;
490}
491
492static void ad_move_tree_info(AW_window *aww, bool transferGroups) {
493    AW_root *awr = aww->get_root();
494
495    char     *log_file = NULp;
496    GB_ERROR  error    = NULp;
497
498    GroupTransferMode mode = COMPARE_TOPOLOGY;
499    GroupsToTransfer  what = XFER_ALL_GROUPS;
500
501    if (transferGroups) {
502        // log file is only written if transferring groups!
503        char *log_name       = GB_unique_filename("arb_node", "log");
504        log_file             = GB_create_tempfile(log_name);
505        if (!log_file) error = GB_await_error();
506        free(log_name);
507
508        what = GroupsToTransfer(awr->awar(AWAR_GROUPXFER_SOURCE)->read_int());
509        mode = GroupTransferMode(awr->awar(AWAR_GROUPXFER_OVERWRITE_MODE)->read_int());
510    }
511
512    if (!error) {
513        char *src_tree = TreeAdmin::source_tree_awar(awr)->read_string();
514        char *dst_tree = TreeAdmin::dest_tree_awar(awr)->read_string();
515        char *aci      = awr->awar(AWAR_GROUPXFER_ACI)->read_string();
516
517        NT_deselect_group(awr); // avoid crash (if group selected in target tree)
518
519        GroupMatchScorer userScorer;
520        if (transferGroups) {
521            userScorer.setLimits(RatioLimits(awr->awar(AWAR_GROUPXFER_INGROUP_LIM)->read_float()/100, 1.0),
522                                 RatioLimits(0.0, awr->awar(AWAR_GROUPXFER_OUTGROUP_LIM)->read_float()/100));
523
524            userScorer.setPerErrorPenalties(awr->awar(AWAR_GROUPXFER_INGROUP_ABS)->read_float(),
525                                            awr->awar(AWAR_GROUPXFER_OUTGROUP_ABS)->read_float(),
526                                            awr->awar(AWAR_GROUPXFER_UNKNOWN_ABS)->read_float());
527
528            userScorer.setRelativePenalties(awr->awar(AWAR_GROUPXFER_INGROUP_REL)->read_float(),
529                                            awr->awar(AWAR_GROUPXFER_OUTGROUP_REL)->read_float());
530        }
531
532        error = NTREE_move_tree_info(GLOBAL.gb_main, src_tree, dst_tree, log_file, mode, what, userScorer, aci);
533
534        if (mode == COMPARE_TOPOLOGY && !error) {
535            // if tree is not shown -> provide hint
536            TREE_canvas *canvas_showing_dest = NT_get_canvas_showing_tree(dst_tree, false);
537            if (!canvas_showing_dest) {
538                aw_message(GBS_global_string("Note: annotations added to tree '%s'\n"
539                                             "Press the right 'Display' button to view that tree", dst_tree));
540            }
541        }
542
543        if (log_file) {
544            AW_edit(log_file);
545            GB_remove_on_exit(log_file);
546        }
547
548        free(aci);
549        free(dst_tree);
550        free(src_tree);
551    }
552
553    aw_message_if(error);
554
555    free(log_file);
556}
557
558static void swap_source_dest_cb(AW_window *aww) {
559    AW_root *root = aww->get_root();
560
561    AW_awar *s = TreeAdmin::source_tree_awar(root);
562    AW_awar *d = TreeAdmin::dest_tree_awar(root);
563
564    char *old_src = s->read_string();
565    s->write_string(d->read_char_pntr());
566    d->write_string(old_src);
567    free(old_src);
568}
569
570static void copy_tree_awar_cb(UNFIXED, AW_awar *aw_source, AW_awar *aw_dest) {
571    const char *tree = aw_source->read_char_pntr();
572    if (tree && tree[0]) aw_dest->write_string(tree);
573}
574
575void NT_create_twoTreeSelection(AW_window *aws) {
576    AW_root *root = aws->get_root();
577
578    aws->auto_space(10, 3);
579
580    aws->at("tree1");
581    awt_create_TREE_selection_list(GLOBAL.gb_main, aws, TreeAdmin::source_tree_awar(root)->awar_name);
582    aws->at("tree2");
583    awt_create_TREE_selection_list(GLOBAL.gb_main, aws, TreeAdmin::dest_tree_awar(root)->awar_name);
584
585    AW_awar *awar_displayed_tree = root->awar(AWAR_TREE_NAME);
586
587    { // let source tree default to currently displayed tree:
588        static bool firstCall = true;
589        if (firstCall) {
590            TreeAdmin::source_tree_awar(root)->write_string(awar_displayed_tree->read_char_pntr());
591            firstCall = false;
592        }
593    }
594
595
596    aws->at("select1");
597    aws->callback(makeWindowCallback(copy_tree_awar_cb, awar_displayed_tree, TreeAdmin::source_tree_awar(root)));  aws->create_autosize_button("SELECT_DISPLAYED1", "Use shown");
598    aws->callback(makeWindowCallback(copy_tree_awar_cb, TreeAdmin::source_tree_awar(root), awar_displayed_tree));  aws->create_autosize_button("DISPLAY_SELECTED1", "Display");
599
600    aws->callback(swap_source_dest_cb);
601    aws->create_autosize_button("SWAP", "Swap");
602
603    aws->at("select2");
604    aws->callback(makeWindowCallback(copy_tree_awar_cb, awar_displayed_tree, TreeAdmin::dest_tree_awar(root)));  aws->create_autosize_button("SELECT_DISPLAYED2", "Use shown");
605    aws->callback(makeWindowCallback(copy_tree_awar_cb, TreeAdmin::dest_tree_awar(root), awar_displayed_tree));  aws->create_autosize_button("DISPLAY_SELECTED2", "Display");
606}
607
608static AW_window_simple *create_select_other_tree_window(AW_root *root, const char *winId, const char *winTitle, const char *helpFile, AW_awar *awar_displayed_tree) {
609    AW_window_simple *aws = new AW_window_simple;
610    aws->init(root, winId, winTitle);
611    aws->load_xfig("ad_one_tree.fig");
612
613    aws->at("close");
614    aws->auto_space(10, 3);
615
616    aws->callback(AW_POPDOWN);
617    aws->create_button("CLOSE", "Close", "C");
618
619    aws->at("help");
620    aws->callback(makeHelpCallback(helpFile));
621    aws->create_button("HELP", "Help", "H");
622
623    AW_awar *awar_other_tree = TreeAdmin::dest_tree_awar(root);
624
625    aws->at("tree");
626    awt_create_TREE_selection_list(GLOBAL.gb_main, aws, awar_other_tree->awar_name);
627
628    aws->at("select");
629    aws->callback(makeWindowCallback(copy_tree_awar_cb, awar_displayed_tree, awar_other_tree));     aws->create_autosize_button("SELECT_DISPLAYED", "Use");
630    aws->callback(makeWindowCallback(copy_tree_awar_cb, awar_other_tree,     awar_displayed_tree)); aws->create_autosize_button("DISPLAY_SELECTED", "Display");
631
632    aws->at("user");
633
634    return aws;
635}
636
637AW_window *NT_create_compareTopologies_window(AW_root *root) {
638    AW_window_simple *aws = new AW_window_simple;
639    aws->init(root, "CMP_TOPOLOGY", "Compare tree topologies");
640    aws->load_xfig("compare_topo.fig");
641
642    aws->at("close");
643    aws->callback(AW_POPDOWN);
644    aws->create_button("CLOSE", "Close", "C");
645
646    aws->at("help");
647    aws->callback(makeHelpCallback("compare_topo.hlp"));
648    aws->create_button("HELP", "Help", "H");
649
650    NT_create_twoTreeSelection(aws);
651
652    aws->at("user");
653    aws->callback(makeWindowCallback(ad_move_tree_info, false));
654    aws->create_autosize_button("CMP_TOPOLOGY", "Compare topologies");
655
656    return aws;
657}
658
659static AWT_config_mapping_def moveGroupInfo_mapping[] = {
660    { AWAR_GROUPXFER_INGROUP_ABS,    "ingroup_abs" },
661    { AWAR_GROUPXFER_INGROUP_REL,    "ingroup_rel" },
662    { AWAR_GROUPXFER_OUTGROUP_ABS,   "outgroup_abs" },
663    { AWAR_GROUPXFER_OUTGROUP_REL,   "outgroup_rel" },
664
665    { AWAR_GROUPXFER_UNKNOWN_ABS,    "unknown_abs" },
666    { AWAR_GROUPXFER_KEELING,        "keeling" },
667
668    { AWAR_GROUPXFER_INGROUP_LIM,    "ingroup_lim" },
669    { AWAR_GROUPXFER_OUTGROUP_LIM,   "outgroup_lim" },
670
671    { AWAR_GROUPXFER_SOURCE,         "sourceGroups" },
672    { AWAR_GROUPXFER_OVERWRITE_MODE, "overwriteGroups" },
673
674    { AWAR_GROUPXFER_ACI,            "aci" },
675
676    { NULp, NULp },
677};
678
679static AWT_predefined_config moveGroupInfo_predef[] = {
680    { "*only_perfect_groups", "Only copy perfectly matching groups with\n * 100% ingroup ratio and\n * 0% outgroup ratio", "ingroup_lim='100';outgroup_lim='0'" },
681
682    { "*maximize_ingroup_ratio", "Maximize ingroup ratio \n   over outgroup ratio.\n(Note: diff of factor 10 is maybe too strong)", "ingroup_abs='0';ingroup_rel='100';outgroup_abs='0';outgroup_rel='10'" },
683    { "*minimize_outgroup_ratio", "Minimize outgroup ratio \n     over ingroup ratio.\n(Note: diff of factor 10 is maybe too strong)", "ingroup_abs='0';ingroup_rel='10';outgroup_abs='0';outgroup_rel='100'" },
684
685    { "*report2name", "custom target group name:\n * add prefix \"XFRD_\" (allows to distinguish newly transferred from existing groups)\n * add suffix reporting penalty", "aci='\"XFRD_\";groupname;\" {penalty = \";penalty;\"}\"'" },
686
687    { NULp, NULp, NULp },
688};
689
690
691
692AW_window *NT_create_moveGroupInfo_window(AW_root *root) {
693    AW_window_simple *aws = new AW_window_simple;
694    aws->init(root, "COPY_NODE_INFO_OF_TREE", "Move groups");
695    aws->load_xfig("move_groups.fig");
696
697    aws->button_length(11);
698
699    aws->at("close");
700    aws->callback(AW_POPDOWN);
701    aws->create_button("CLOSE", "Close", "C");
702
703    aws->at("help");
704    aws->callback(makeHelpCallback("move_groups.hlp"));
705    aws->create_button("HELP", "Help", "H");
706
707    const int FLOAT_COLUMNS   = 10;
708    const int PERCENT_COLUMNS = 5;
709
710    aws->at("ipep"); aws->create_input_field(AWAR_GROUPXFER_INGROUP_ABS, FLOAT_COLUMNS);
711    aws->at("irp");  aws->create_input_field(AWAR_GROUPXFER_INGROUP_REL, FLOAT_COLUMNS);
712    aws->at("ilim"); aws->create_input_field(AWAR_GROUPXFER_INGROUP_LIM, PERCENT_COLUMNS);
713
714    aws->at("opep"); aws->create_input_field(AWAR_GROUPXFER_OUTGROUP_ABS, FLOAT_COLUMNS);
715    aws->at("orp");  aws->create_input_field(AWAR_GROUPXFER_OUTGROUP_REL, FLOAT_COLUMNS);
716    aws->at("olim"); aws->create_input_field(AWAR_GROUPXFER_OUTGROUP_LIM, PERCENT_COLUMNS);
717
718    aws->at("upep"); aws->create_input_field(AWAR_GROUPXFER_UNKNOWN_ABS, FLOAT_COLUMNS);
719    aws->at("keel"); aws->create_input_field(AWAR_GROUPXFER_KEELING,     FLOAT_COLUMNS);
720
721    aws->at("srcGrps");
722    aws->create_option_menu(AWAR_GROUPXFER_SOURCE);
723    aws->insert_option("all groups",         "a", XFER_ALL_GROUPS);
724    aws->insert_option("groups with marked", "m", XFER_GROUPS_WITH_MARKED);
725    aws->update_option_menu();
726
727    aws->at("tgtGrps");
728    aws->create_option_menu(AWAR_GROUPXFER_OVERWRITE_MODE);
729    aws->insert_option("remove existing groups",          "r", REMOVE_EXISTING_GROUPS);
730    aws->insert_option("keep \"newname [was: oldname]\"", "k", KEEP_OLD_NAMES);
731    aws->update_option_menu();
732
733    aws->at("aci");
734    aws->create_input_field(AWAR_GROUPXFER_ACI);
735
736    NT_create_twoTreeSelection(aws);
737
738    aws->at("go");
739    aws->callback(makeWindowCallback(ad_move_tree_info, true));
740    aws->create_button("GO", "GO", "G");
741
742    aws->at("cfg");
743    AWT_insert_config_manager(aws, AW_ROOT_DEFAULT, "moveGroupInfo", moveGroupInfo_mapping, NULp, moveGroupInfo_predef);
744
745    return aws;
746}
747
748static void reorder_trees_cb(AW_window *aww, awt_reorder_mode dest) {
749    // moves the tree in the list of trees
750
751    char     *tree_name = aww->get_root()->awar(AWAR_TREE_NAME)->read_string();
752    GB_ERROR  error     = NULp;
753
754    GB_transaction ta(GLOBAL.gb_main);
755    GBDATA *gb_treedata   = GBT_get_tree_data(GLOBAL.gb_main);
756    GBDATA *gb_moved_tree = GB_entry(gb_treedata, tree_name);
757
758    if (!gb_moved_tree) {
759        error = "No tree selected";
760    }
761    else {
762        GBT_ORDER_MODE  move_mode;
763        GBDATA         *gb_target_tree = NULp;
764
765        switch (dest) {
766            case ARM_UP:
767                move_mode      = GBT_INFRONTOF;
768                gb_target_tree = GBT_tree_infrontof(gb_moved_tree);
769                if (gb_target_tree) break;
770                FALLTHROUGH; // move top-tree up = move to bottom
771            case ARM_BOTTOM:
772                move_mode      = GBT_BEHIND;
773                gb_target_tree = GBT_find_bottom_tree(GLOBAL.gb_main);
774                break;
775
776            case ARM_DOWN:
777                move_mode      = GBT_BEHIND;
778                gb_target_tree = GBT_tree_behind(gb_moved_tree);
779                if (gb_target_tree) break;
780                FALLTHROUGH; // move bottom-tree down = move to top
781            case ARM_TOP:
782                move_mode      = GBT_INFRONTOF;
783                gb_target_tree = GBT_find_top_tree(GLOBAL.gb_main);
784                break;
785        }
786
787        if (gb_target_tree && gb_target_tree != gb_moved_tree) {
788            error = GBT_move_tree(gb_moved_tree, move_mode, gb_target_tree);
789        }
790    }
791
792    if (error) aw_message(error);
793    free(tree_name);
794}
795
796void popup_tree_admin_window(AW_window *awp) {
797    static AW_window_simple *aws = NULp;
798
799    if (!aws) {
800        AW_root *aw_root = awp->get_root();
801
802        aws = new AW_window_simple;
803        aws->init(aw_root, "TREE_ADMIN", "TREE ADMIN");
804        aws->load_xfig("ad_tree.fig");
805
806        aws->callback(AW_POPDOWN);
807        aws->at("close");
808        aws->create_button("CLOSE", "CLOSE", "C");
809
810        aws->callback(makeHelpCallback("treeadm.hlp"));
811        aws->at("help");
812        aws->create_button("HELP", "HELP", "H");
813
814        aws->button_length(40);
815
816        aws->at("sel");
817        aws->create_button(NULp, AWAR_TREE_NAME, NULp, "+");
818
819        aws->at("security");
820        aws->create_option_menu(AWAR_TREE_SECURITY);
821        aws->insert_option("0", "0", 0);
822        aws->insert_option("1", "1", 1);
823        aws->insert_option("2", "2", 2);
824        aws->insert_option("3", "3", 3);
825        aws->insert_option("4", "4", 4);
826        aws->insert_option("5", "5", 5);
827        aws->insert_default_option("6", "6", 6);
828        aws->update_option_menu();
829
830        aws->at("rem");
831        aws->create_text_field(AWAR_TREE_REM);
832
833
834        aws->button_length(20);
835
836        static TreeAdmin::Spec spec(GLOBAL.gb_main, AWAR_TREE_NAME);
837
838        aws->at("delete");
839        aws->help_text("treeadm.hlp");
840        aws->callback(makeWindowCallback(TreeAdmin::delete_tree_cb, &spec));
841        aws->create_button("DELETE", "Delete", "D");
842
843        aws->at("rename");
844        aws->help_text("treeadm.hlp");
845        aws->callback(makeCreateWindowCallback(TreeAdmin::create_rename_window, &spec));
846        aws->create_button("RENAME", "Rename", "R");
847
848        aws->at("copy");
849        aws->help_text("treeadm.hlp");
850        aws->callback(makeCreateWindowCallback(TreeAdmin::create_copy_window, &spec));
851        aws->create_button("COPY", "Copy", "C");
852
853        aws->at("export");
854        aws->help_text("tr_export.hlp");
855        aws->callback(create_tree_export_window);
856        aws->create_button("EXPORT", "Export", "E");
857
858        aws->at("import");
859        aws->help_text("tr_import.hlp");
860        aws->callback(create_tree_import_window);
861        aws->create_button("IMPORT", "Import", "I");
862
863        aws->button_length(0);
864
865        aws->at("list");
866        awt_create_TREE_selection_list(GLOBAL.gb_main, aws, AWAR_TREE_NAME);
867
868        aws->at("sort");
869        awt_create_order_buttons(aws, reorder_trees_cb);
870    }
871
872    aws->activate();
873}
874
875// -----------------------
876//      consense tree
877
878
879static void create_consense_tree_cb(AW_window *aww, AW_selection *selected_trees) {
880    AW_root  *aw_root = aww->get_root();
881    GB_ERROR  error   = NULp;
882
883    const char *cons_tree_name = aw_root->awar(AWAR_TREE_CONSENSE_TREE)->read_char_pntr();
884    if (!cons_tree_name || !cons_tree_name[0]) {
885        error = "No name specified for the consensus tree";
886    }
887    else {
888        StrArray tree_names;
889        selected_trees->get_values(tree_names);
890
891        if (tree_names.size()<2) {
892            error = "Not enough trees selected (at least 2 needed)";
893        }
894        else {
895            GBDATA *gb_main = GLOBAL.gb_main;
896            GB_transaction ta(gb_main);
897
898            {
899                arb_progress progress("Building consensus tree");
900                ConsensusTreeBuilder tree_builder;
901
902                progress.subtitle("loading input trees");
903                for (size_t t = 0; t<tree_names.size() && !error; ++t) {
904                    TreeRoot      *root = new SizeAwareRoot; // will be deleted when tree gets deleted
905                    SizeAwareTree *tree = DOWNCAST(SizeAwareTree*, GBT_read_tree(gb_main, tree_names[t], root));
906                    if (!tree) {
907                        error = GB_await_error();
908                    }
909                    else {
910                        tree_builder.add(tree, tree_names[t], 1.0);
911                    }
912                }
913
914                if (!error) {
915                    size_t    species_count;
916                    TreeNode *cons_tree = tree_builder.get(species_count, error); // triggers 2 implicit progress increments
917
918                    if (!error && progress.aborted()) {
919                        error = "user abort";
920                    }
921
922                    nt_assert(contradicted(cons_tree, error));
923                    if (cons_tree) {
924                        char *comment = tree_builder.get_tree_remark();
925                        error         = GBT_write_tree_with_remark(gb_main, cons_tree_name, cons_tree, comment);
926                        free(comment);
927                        UNCOVERED();
928                        destroy(cons_tree);
929                    }
930                }
931                if (error) progress.done();
932            }
933            error = ta.close(error);
934        }
935    }
936
937    if (!error) {
938        aw_root->awar(AWAR_TREE_NAME)->write_string(cons_tree_name); // show in main window
939    }
940
941    aw_message_if(error);
942}
943
944static void use_selected_as_target_cb(AW_window *aww) {
945    AW_root *aw_root = aww->get_root();
946    aw_root->awar(AWAR_TREE_CONSENSE_TREE)->write_string(aw_root->awar(AWAR_TREE_CONSENSE_SELECTED)->read_char_pntr());
947}
948
949AW_window *NT_create_consense_window(AW_root *aw_root) {
950    static AW_window_simple *aws = NULp;
951    if (!aws) {
952        aws = new AW_window_simple;
953        aws->init(aw_root, "CONSENSE_TREE", "Consensus Tree");
954        aws->load_xfig("ad_cons_tree.fig");
955
956        aws->auto_space(10, 10);
957
958        aws->callback(AW_POPDOWN);
959        aws->at("close");
960        aws->create_button("CLOSE", "CLOSE", "C");
961
962        aws->callback(makeHelpCallback("consense_tree.hlp"));
963        aws->at("help");
964        aws->create_button("HELP", "HELP", "H");
965
966        aws->at("list");
967        AW_DB_selection *all_trees      = awt_create_TREE_selection_list(GLOBAL.gb_main, aws, AWAR_TREE_CONSENSE_SELECTED);
968        AW_selection    *selected_trees = awt_create_subset_selection_list(aws, all_trees->get_sellist(), "selected", "add", "sort");
969
970        aws->at("name");
971        aws->create_input_field(AWAR_TREE_CONSENSE_TREE);
972
973        aws->button_length(0);
974        aws->callback(use_selected_as_target_cb);
975        aws->create_button("USE_AS_TARGET", "#moveLeft.xpm");
976
977        aws->at("build");
978        aws->callback(makeWindowCallback(create_consense_tree_cb, selected_trees));
979        aws->create_autosize_button("BUILD", "Build consensus tree", "B");
980    }
981    return aws;
982}
983
984class CombinedPosInfo {
985    // combines relative positions of a subtree in 2 trees (source- and target-tree).
986    // provides compare operations for SortByTopo
987
988    TreeRelativePosition source; // in source tree ("ordering tree")
989    TreeRelativePosition target; // in target tree ("modified tree")
990
991public:
992
993    CombinedPosInfo(const TreeRelativePosition& s, const TreeRelativePosition& t)
994        : source(s),
995          target(t)
996    {
997        nt_assert(target.is_known());
998    }
999    CombinedPosInfo(const CombinedPosInfo& c1, const CombinedPosInfo& c2)
1000        : source(c1.source, c2.source),
1001          target(c1.target, c2.target)
1002    {}
1003
1004    int compare(const CombinedPosInfo &right) const {
1005        // result similar to strcmp(this, right)
1006        if (!source.is_known() || !right.source.is_known()) {
1007            // one subtree is completely unknown in source-tree
1008            // => keep target-tree order
1009            return target.compare(right.target);
1010        }
1011        return source.compare(right.source);
1012    }
1013};
1014
1015class SortByTopo : virtual Noncopyable {
1016    TreePositionLookup        source_pos; // in ordering topology
1017    const TreePositionLookup *target_pos; // in target topology (used where source_pos does not provide order)
1018
1019    CombinedPosInfo reorder_subtree_rec(TreeNode *node) { // similar to ../ARBDB/TreeNode.cxx@reorder_subtree
1020        static const char *smallest_leafname; // has to be set to the alphabetically smallest name (when function exits)
1021
1022        if (node->is_leaf()) {
1023            smallest_leafname = node->name;
1024            return CombinedPosInfo(source_pos.relative(node->name),
1025                                   target_pos->relative(node->name));
1026        }
1027
1028        CombinedPosInfo  leftInfo       = reorder_subtree_rec(node->get_leftson());
1029        const char      *smallest_left  = smallest_leafname;
1030        CombinedPosInfo  rightInfo      = reorder_subtree_rec(node->get_rightson());
1031        const char      *smallest_right = smallest_leafname;
1032
1033        bool left_leafname_bigger = strcmp(smallest_left, smallest_right)>0;
1034        smallest_leafname         = left_leafname_bigger ? smallest_right : smallest_left;
1035
1036        {
1037            int cmp = leftInfo.compare(rightInfo);
1038            if (cmp>0 || (cmp == 0 && left_leafname_bigger)) {
1039                node->swap_sons();
1040            }
1041        }
1042
1043        return CombinedPosInfo(leftInfo, rightInfo);
1044    }
1045public:
1046
1047    SortByTopo(const TreeNode *by)
1048        : source_pos(by),
1049          target_pos(NULp)
1050    {}
1051
1052#if defined(UNIT_TESTS)
1053    TreeRelativePosition sourcePos(const char *name) { return source_pos.relative(name); }
1054#endif
1055
1056    void reorder_subtree(TreeNode *tree) {
1057        TreePositionLookup tpos(tree);
1058        LocallyModify<const TreePositionLookup*> provide(target_pos, &tpos);
1059        reorder_subtree_rec(tree);
1060    }
1061};
1062
1063static GB_ERROR sort_tree_by_other_tree(GBDATA *gb_main, TreeNode *tree, const char *other_tree) {
1064    GB_ERROR       error = NULp;
1065    GB_transaction ta(gb_main);
1066
1067    TreeNode *otherTree   = GBT_read_tree(gb_main, other_tree, new SimpleRoot);
1068    if (!otherTree) error = GB_await_error();
1069    else {
1070        SortByTopo sorter(otherTree);
1071        destroy(otherTree);
1072        sorter.reorder_subtree(tree);
1073    }
1074    return error;
1075}
1076
1077static bool sort_dtree_by_other_tree_cb(TreeNode *tree, GB_ERROR& error) {
1078    const char *other_tree = TreeAdmin::dest_tree_awar(AW_root::SINGLETON)->read_char_pntr();
1079    error = sort_tree_by_other_tree(GLOBAL.gb_main, tree, other_tree);
1080    return !error;
1081}
1082
1083static void sort_tree_by_other_tree_cb(UNFIXED, TREE_canvas *ntw) {
1084    GB_ERROR error = NT_with_displayed_tree_do(ntw, sort_dtree_by_other_tree_cb);
1085    aw_message_if(error);
1086}
1087
1088AW_window *NT_create_sort_tree_by_other_tree_window(AW_root *aw_root, TREE_canvas *ntw) {
1089    AW_window_simple *aws = create_select_other_tree_window(aw_root, ntw->aww->local_id("SORT_BY_OTHER"), "Sort tree by other tree", "resortbyother.hlp", ntw->get_awar_tree());
1090
1091    aws->callback(makeWindowCallback(sort_tree_by_other_tree_cb, ntw));
1092    aws->create_autosize_button("RESORT", "Sort according to source tree");
1093
1094    return aws;
1095}
1096
1097// ---------------------------
1098//      multifurcate tree
1099
1100#define AWAR_MFURC                    "tree/multifurc/"
1101#define AWAR_MFURC_CONSIDER_BOOTSTRAP AWAR_MFURC "use_bs"
1102#define AWAR_MFURC_CONSIDER_LENGTH    AWAR_MFURC "use_len"
1103#define AWAR_MFURC_CONSIDER_TERMINALS AWAR_MFURC "terminals"
1104#define AWAR_MFURC_LENGTH_LIMIT       AWAR_MFURC "len"
1105#define AWAR_MFURC_BOOTSTRAP_LIMIT    AWAR_MFURC "bs"
1106
1107void NT_create_multifurcate_tree_awars(AW_root *aw_root, AW_default props) {
1108    aw_root->awar_int  (AWAR_MFURC_CONSIDER_BOOTSTRAP, 0,   props);
1109    aw_root->awar_int  (AWAR_MFURC_CONSIDER_LENGTH,    1,   props);
1110    aw_root->awar_int  (AWAR_MFURC_CONSIDER_TERMINALS, 0,   props);
1111    aw_root->awar_float(AWAR_MFURC_LENGTH_LIMIT,       0.1, props);
1112    aw_root->awar_float(AWAR_MFURC_BOOTSTRAP_LIMIT,    50,  props);
1113}
1114static void multifurcation_cb(UNFIXED, TREE_canvas *ntw) {
1115    AW_root *aw_root = ntw->aww->get_root();
1116
1117    float below_bootstrap = 101.0;
1118    float below_length    = 1000000.0;
1119    bool  applyAtLeafs    = aw_root->awar(AWAR_MFURC_CONSIDER_TERMINALS)->read_int();
1120
1121    if (aw_root->awar(AWAR_MFURC_CONSIDER_BOOTSTRAP)->read_int()) below_bootstrap = aw_root->awar(AWAR_MFURC_BOOTSTRAP_LIMIT)->read_float();
1122    if (aw_root->awar(AWAR_MFURC_CONSIDER_LENGTH)   ->read_int()) below_length    = aw_root->awar(AWAR_MFURC_LENGTH_LIMIT)   ->read_float();
1123
1124    NT_multifurcate_tree(ntw, TreeNode::multifurc_limits(below_bootstrap, below_length, applyAtLeafs));
1125}
1126AW_window *NT_create_multifurcate_tree_window(AW_root *aw_root, TREE_canvas *ntw) {
1127    AW_window_simple *aws = new AW_window_simple;
1128
1129    aws->init(aw_root, ntw->aww->local_id("multifurcate"), "Multifurcate tree");
1130    aws->at(10, 10);
1131    aws->auto_space(10, 10);
1132
1133    aws->callback(AW_POPDOWN);
1134    aws->create_button("CLOSE", "CLOSE", "C");
1135
1136    aws->callback(makeHelpCallback("multifurcate.hlp"));
1137    aws->create_button("HELP", "HELP", "H");
1138
1139    const int LABEL_LENGTH = 46;
1140    aws->label_length(LABEL_LENGTH);
1141
1142    aws->at_newline();
1143    aws->label("Multifurcate branches with branchlength below");
1144    aws->create_toggle(AWAR_MFURC_CONSIDER_LENGTH);
1145    aws->create_input_field(AWAR_MFURC_LENGTH_LIMIT, 10);
1146
1147    aws->at_newline();
1148    aws->label("                          AND bootstrap below");
1149    aws->create_toggle(AWAR_MFURC_CONSIDER_BOOTSTRAP);
1150    aws->create_input_field(AWAR_MFURC_BOOTSTRAP_LIMIT, 10);
1151
1152    aws->label_length(0);
1153    aws->at_newline();
1154    aws->label("Also apply to terminal branches");
1155    aws->create_toggle(AWAR_MFURC_CONSIDER_TERMINALS);
1156
1157    aws->at_newline();
1158    aws->callback(makeWindowCallback(multifurcation_cb, ntw));
1159    aws->create_autosize_button("MULTIFURCATE", "Multifurcate", "M");
1160
1161    return aws;
1162}
1163
1164// --------------------------------------------------------------------------------
1165
1166#ifdef UNIT_TESTS
1167#ifndef TEST_UNIT_H
1168#include <test_unit.h>
1169#endif
1170
1171static const char *getTreeComment(GBDATA *gb_main, const char *treeName) {
1172    GB_transaction ta(gb_main);
1173    return GBT_tree_info_string(gb_main, treeName, -1);
1174}
1175
1176#define TEST_EXPECT_TREE_COMMENT_CONTAINS(treeName,expected)               TEST_EXPECT_CONTAINS(getTreeComment(gb_main,treeName),expected)
1177#define TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN(treeName,expected)         TEST_EXPECT_DOESNT_CONTAIN(getTreeComment(gb_main,treeName),expected)
1178#define TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN__BROKEN(treeName,expected) TEST_EXPECT_DOESNT_CONTAIN__BROKEN(getTreeComment(gb_main,treeName),expected)
1179
1180static GB_ERROR sort_namedtree_by_other_tree(GBDATA *gb_main, const char *tree, const char *other_tree) {
1181    GB_ERROR        error = NULp;
1182    GB_transaction  ta(gb_main);
1183    SizeAwareTree  *Tree  = DOWNCAST(SizeAwareTree*, GBT_read_tree(gb_main, tree, new SizeAwareRoot));
1184    if (!Tree) error      = GB_await_error();
1185    else {
1186        Tree->compute_tree();
1187        error             = sort_tree_by_other_tree(gb_main, Tree, other_tree);
1188        if (!error) error = GBT_write_tree(gb_main, tree, Tree);
1189    }
1190    destroy(Tree);
1191    return error;
1192}
1193
1194void TEST_sort_tree_by_other_tree() {
1195    GB_shell  shell;
1196    GBDATA   *gb_main = GB_open("TEST_trees.arb", "rw");
1197    TEST_REJECT_NULL(gb_main);
1198
1199    const char *topo_test   = "(((((((CloTyro3:1.046,CloTyro4:0.061):0.026,CloTyro2:0.017):0.017,CloTyrob:0.009):0.274,CloInnoc:0.371):0.057,CloBifer:0.388):0.124,(((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120):0.010,CloPaste:0.179):0.131):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522):0.053,CelBiazo:0.059):0.207,CytAquat:0.711):0.081);";
1200    const char *topo_center = "(((CloPaste:0.179,((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120):0.010):0.131,((CloInnoc:0.371,((CloTyro2:0.017,(CloTyro3:1.046,CloTyro4:0.061):0.026):0.017,CloTyrob:0.009):0.274):0.057,CloBifer:0.388):0.124):0.081,((CelBiazo:0.059,((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522):0.053):0.207,CytAquat:0.711):0.081);";
1201    const char *topo_bottom = "((CytAquat:0.711,(CelBiazo:0.059,(CorGluta:0.522,(CorAquat:0.084,CurCitre:0.058):0.103):0.053):0.207):0.081,((CloPaste:0.179,(CloCarni:0.120,(CloButy2:0.009,CloButyr:0.000):0.564):0.010):0.131,(CloBifer:0.388,(CloInnoc:0.371,(CloTyrob:0.009,(CloTyro2:0.017,(CloTyro3:1.046,CloTyro4:0.061):0.026):0.017):0.274):0.057):0.124):0.081);";
1202
1203    const char *topo_vs_nj_bs = "(((((((CloTyro3:1.046,CloTyro4:0.061):0.026,CloTyro2:0.017):0.017,CloTyrob:0.009):0.274,CloInnoc:0.371):0.057,CloBifer:0.388):0.124,(((CloButyr:0.000,CloButy2:0.009):0.564,CloCarni:0.120):0.010,CloPaste:0.179):0.131):0.081,(((CorGluta:0.522,(CorAquat:0.084,CurCitre:0.058):0.103):0.053,CelBiazo:0.059):0.207,CytAquat:0.711):0.081);";
1204
1205    TEST_EXPECT_DIFFERENT(topo_test,   topo_center);
1206    TEST_EXPECT_DIFFERENT(topo_test,   topo_bottom);
1207    TEST_EXPECT_DIFFERENT(topo_center, topo_bottom);
1208
1209    // create sorted copies of tree_test
1210    {
1211        GB_transaction  ta(gb_main);
1212        SizeAwareTree  *tree = DOWNCAST(SizeAwareTree*, GBT_read_tree(gb_main, "tree_test", new SizeAwareRoot));
1213        TEST_REJECT_NULL(tree);
1214        TEST_EXPECT_NEWICK(nLENGTH, tree, topo_test);
1215
1216        tree->reorder_tree(BIG_BRANCHES_TO_CENTER); TEST_EXPECT_NO_ERROR(GBT_write_tree(gb_main, "tree_sorted_center", tree)); TEST_EXPECT_NEWICK(nLENGTH, tree, topo_center);
1217        tree->reorder_tree(BIG_BRANCHES_TO_BOTTOM); TEST_EXPECT_NO_ERROR(GBT_write_tree(gb_main, "tree_sorted_bottom", tree)); TEST_EXPECT_NEWICK(nLENGTH, tree, topo_bottom);
1218
1219        // test SortByTopo
1220        {
1221            SortByTopo   sbt(tree);
1222            const double EPSILON = 0.0001;
1223
1224            TEST_EXPECT_SIMILAR(sbt.sourcePos("CytAquat").value(), 0.0, EPSILON); // leftmost species (in topo_bottom)
1225            TEST_EXPECT_SIMILAR(sbt.sourcePos("CloTyro4").value(), 1.0, EPSILON); // rightmost species
1226
1227            TEST_EXPECT_SIMILAR(sbt.sourcePos("CurCitre").value(), 0.2857, EPSILON); // (5 of 15)
1228            TEST_EXPECT_SIMILAR(sbt.sourcePos("CloButy2").value(), 0.5,    EPSILON); // center species (8 of 15)
1229            TEST_EXPECT_SIMILAR(sbt.sourcePos("CloTyrob").value(), 0.7857, EPSILON); // (12 of 15)
1230
1231            TEST_REJECT(sbt.sourcePos("Un-Known").is_known()); // unknown species
1232        }
1233
1234        tree->reorder_tree(BIG_BRANCHES_TO_EDGE); TEST_EXPECT_NO_ERROR(GBT_write_tree(gb_main, "tree_work", tree));
1235
1236        destroy(tree);
1237    }
1238
1239
1240    TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, "tree_work", "tree_sorted_center")); TEST_EXPECT_SAVED_NEWICK(nLENGTH, gb_main, "tree_work", topo_center);
1241    TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, "tree_work", "tree_sorted_bottom")); TEST_EXPECT_SAVED_NEWICK(nLENGTH, gb_main, "tree_work", topo_bottom);
1242    TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, "tree_work", "tree_test"));          TEST_EXPECT_SAVED_NEWICK(nLENGTH, gb_main, "tree_work", topo_test);
1243    TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, "tree_work", "tree_nj_bs"));         TEST_EXPECT_SAVED_NEWICK(nLENGTH, gb_main, "tree_work", topo_vs_nj_bs);
1244
1245    // TEST_EXPECT_NO_ERROR(GB_save_as(gb_main, "TEST_trees_save.arb", "b")); // test-save db to examine saved trees (do not commit!)
1246
1247    // ----------------------------------------------------------------------------------------------------
1248    // test high-level function TREE_load_to_db (see #701; placed here by laziness, not related to sorting trees)
1249    TEST_EXPECT_NO_ERROR(TREE_load_to_db(gb_main, "trees/test.tree", "tree_loaded")); // ../UNIT_TESTER/run/trees/test.tree
1250    TEST_EXPECT_ERROR_CLEAR();
1251    TEST_EXPECT_SAVED_NEWICK(nALL, gb_main, "tree_loaded", "(((s1:0.200,s2:0.400):0.600,(s3:0.300,s 4:0.100):0.100):0.000,(s5:0.020,s-6:0.040):0.060);");
1252    TEST_EXPECT_TREE_COMMENT_CONTAINS("tree_loaded", "covering most of tree reader code"); // test comment
1253
1254    GB_close(gb_main);
1255}
1256
1257void TEST_move_node_info() {
1258    GB_shell  shell;
1259    GBDATA   *gb_main = GB_open("TEST_trees.arb", "r");
1260
1261    const char *treeTarget1 = "tree_removal";
1262    const char *treeTarget2 = "tree_test";
1263    const char *treeSortby1 = "tree_removal_copy";
1264    const char *treeSortby2 = "tree_test_copy";
1265
1266    const char *treeSource1 = treeSortby2;   // contains 1 group ("test")
1267    const char *treeSource2 = "tree_tree2";  // contains 2 groups ("g2" + "outer")
1268    const char *treeSource3 = "tree_groups"; // contains 5 groups
1269
1270#define GROUP_TEST         "(CloTyrob,(CloTyro2,(CloTyro3,CloTyro4)))"
1271#define GROUP_TEST_FLIPPED "(((CloTyro3,CloTyro4),CloTyro2),CloTyrob)"
1272
1273#define NAMED_GROUP_TEST       GROUP_TEST "'test'"
1274#define OVERWRITTEN_GROUP_TEST GROUP_TEST "'g2 [was: test]'"
1275
1276    const char *org_topo1 = "((CloInnoc," GROUP_TEST "),(CloBifer,((CloCarni,CurCitre),((CloPaste,(Zombie1,(CloButy2,CloButyr))),(CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2))))))));";
1277    const char *org_topo2 = "((((" GROUP_TEST_FLIPPED ",CloInnoc),CloBifer),(((CloButy2,CloButyr),CloCarni),CloPaste)),((((CorAquat,CurCitre),CorGluta),CelBiazo),CytAquat));";
1278
1279    // (index convention := source target)
1280    const char *unwanted_topo11 = "((CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2)))),((CloPaste,(Zombie1,(CloButy2,CloButyr))),((CloCarni,CurCitre),(CloBifer,(CloInnoc," NAMED_GROUP_TEST ")))));";
1281    const char *unwanted_topo21 = "((CloButy2,CloButyr),(Zombie1,(CloPaste,((((CloInnoc," OVERWRITTEN_GROUP_TEST "),CloBifer),(CloCarni,CurCitre)),(CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2)))))))'outer');";
1282
1283    const char *sorted_topo11 = "(((((CloInnoc," NAMED_GROUP_TEST "),CloBifer),(CloCarni,CurCitre)),(CloPaste,(Zombie1,(CloButy2,CloButyr)))),(CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2)))));";
1284    const char *sorted_topo21 = "(((((((CloInnoc," OVERWRITTEN_GROUP_TEST "),CloBifer),(CloCarni,CurCitre)),(CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2))))),CloPaste),Zombie1)'outer',(CloButy2,CloButyr));";
1285    const char *topo32        = "((CloButy2,CloButyr)'upper',(((((" GROUP_TEST_FLIPPED "'low2',CloInnoc),CloBifer),((((CorAquat,CurCitre),CorGluta),CelBiazo),CytAquat)'low1'),CloPaste),CloCarni));";
1286    const char *topo32_rc     = "((CloButy2,CloButyr)'upper',(((((" GROUP_TEST_FLIPPED "'low2',CloInnoc),CloBifer),((((CorAquat,CurCitre),CorGluta),CelBiazo),CytAquat)'low1'),CloPaste),CloCarni)'lower');"; // @@@ should be same as topo32 (see #451)
1287    const char *topo32_rel    = "((CloButy2,CloButyr)"     ",(((((" GROUP_TEST_FLIPPED "'low2 [p=0.250000;ir=100.0%;3->4]',CloInnoc),CloBifer)'low1 [p=0.232222;ir=100.0%;7->6]',((((CorAquat,CurCitre),CorGluta),CelBiazo),CytAquat)'upper [p=0.510000;ir=100.0%;5->5]'),CloPaste),CloCarni)'lower [p=0.230769;ir=100.0%;10->13]');"; // group 'upper' and 'low1' moved to different locations by relative scoring
1288    const char *topo32_li     = "((CloButy2,CloButyr)"     ",(((((" GROUP_TEST_FLIPPED "'low2',CloInnoc),CloBifer),((((CorAquat,CurCitre),CorGluta),CelBiazo),CytAquat)'low1'),CloPaste),CloCarni)'lower');"; // group 'upper' filtered by limits
1289
1290    const char *compared_topo = "(((((((CloInnoc,(CloTyrob,(CloTyro2,(CloTyro3,CloTyro4)))),CloBifer),(CloCarni,CurCitre)'# 2')'# 2',(CytAquat,(CelBiazo,(CorGluta,(CorAquat,Zombie2)'# 1')'# 1')'# 1')'# 1')'# 1',CloPaste),Zombie1),(CloButy2,CloButyr));";
1291
1292    const char *LOG = "move_node_info.log";
1293
1294// #define TEST_AUTO_UPDATE // uncomment to auto-update expected log-files
1295#if defined(TEST_AUTO_UPDATE)
1296# define TEST_LOGS_EXPECTED(expected) TEST_COPY_FILE(LOG, expected)
1297#else
1298# define TEST_LOGS_EXPECTED(expected) TEST_EXPECT_TEXTFILES_EQUAL(expected, LOG)
1299#endif // TEST_AUTO_UPDATE
1300
1301    // create copies of 'tree_removal' + 'tree_test'
1302    {
1303        GB_transaction ta(gb_main);
1304
1305        // remove existing comments from trees (already contains some log-entries tested below)
1306        {
1307            const char  *resetComment = "<comment reset>";
1308            TEST_EXPECT_NO_ERROR(GBT_write_tree_remark(gb_main, treeTarget1, resetComment));
1309            TEST_EXPECT_NO_ERROR(GBT_write_tree_remark(gb_main, treeTarget2, resetComment));
1310        }
1311
1312        TEST_EXPECT_NO_ERROR(GBT_copy_tree(gb_main, treeTarget1, treeSortby1));
1313        TEST_EXPECT_NO_ERROR(GBT_copy_tree(gb_main, treeTarget2, treeSortby2));
1314
1315        TEST_EXPECT_SAVED_NEWICK(nSIMPLE, gb_main, treeTarget1, org_topo1);
1316        TEST_EXPECT_SAVED_NEWICK(nSIMPLE, gb_main, treeTarget2, org_topo2);
1317    }
1318
1319    GroupMatchScorer defaultScorer;
1320
1321    // move node info
1322    {
1323        const char *comment_added = "Copied node info from tree_test_copy";
1324        TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN(treeTarget1, comment_added);
1325
1326        TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource1, treeTarget1, LOG, REMOVE_EXISTING_GROUPS, XFER_ALL_GROUPS, defaultScorer, NULp));
1327        TEST_LOGS_EXPECTED("group_xfer_11.log.expected");
1328
1329        TEST_EXPECT_SAVED_NEWICK__BROKEN(nSIMPLE, gb_main, treeTarget1, org_topo1); // @@@ moving node info modifies topology; caused by NT_tree_cmp.cxx@NAIVE_ROOTING
1330        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget1, unwanted_topo11);
1331        TEST_EXPECT_TREE_COMMENT_CONTAINS(treeTarget1, comment_added);
1332
1333        // @@@ when we have a function to set the root according to another tree (#449),
1334        // use that function here. sorting tree after that, should again result in 'org_topo1'!
1335
1336        TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, treeTarget1, treeSortby1));
1337        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget1, sorted_topo11);
1338    }
1339    {
1340        const char *comment_added = "Copied node info from tree_groups";
1341        TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN(treeTarget2, comment_added);
1342
1343        TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource3, treeTarget2, LOG, REMOVE_EXISTING_GROUPS, XFER_ALL_GROUPS, defaultScorer, NULp));
1344        TEST_LOGS_EXPECTED("group_xfer_32.log.expected");
1345
1346        TEST_EXPECT_SAVED_NEWICK__BROKEN(nSIMPLE, gb_main, treeTarget2, org_topo2); // @@@ moving node info modifies topology; caused by NT_tree_cmp.cxx@NAIVE_ROOTING
1347        TEST_EXPECT_TREE_COMMENT_CONTAINS(treeTarget2, comment_added);
1348        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget2, topo32);
1349
1350        // perform same group-xfer after 1st xfer changed root => inserts 4 instead of 3 groups (obviously caused by changed root position; see #451)
1351        TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource3, treeTarget2, LOG, REMOVE_EXISTING_GROUPS, XFER_ALL_GROUPS, defaultScorer, NULp));
1352        TEST_LOGS_EXPECTED("group_xfer_32_rc.log.expected");
1353        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget2, topo32_rc);
1354
1355        {
1356            GroupMatchScorer relativeScorer;
1357            relativeScorer.setPerErrorPenalties(0.0, 0.0, 0.0001); // remove absolute penalties for in-/outgroup
1358            relativeScorer.setRelativePenalties(1.0, 1.0);         // set relative penalties for in-/outgroup
1359
1360            TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource3, treeTarget2, LOG, REMOVE_EXISTING_GROUPS, XFER_ALL_GROUPS, relativeScorer,
1361                                                      "groupname;\" [p=\";penalty;\";ir=\";ingroup;\";\";oldsize;\"->\";newsize;\"]\""));
1362            TEST_LOGS_EXPECTED("group_xfer_32_rel.log.expected");
1363            TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget2, topo32_rel);
1364        }
1365
1366        // again perform same group-xfer using ingroup- and outgroup-limit
1367        {
1368            GroupMatchScorer limitedScorer;
1369            // limitedScorer.setLimits(RatioLimits(1.0, 1.0), RatioLimits(0.0, 0.0)); // filters all groups (upper, low2, low1 + lower)
1370            // limitedScorer.setLimits(RatioLimits(0.0, 1.0), RatioLimits(0.0, 1.0)); // filters no group (by definition)
1371            // limitedScorer.setLimits(RatioLimits(0.5, 1.0), RatioLimits(0.0, 1.0)); // group 'upper' placed at position with higher absolute penalty
1372            // limitedScorer.setLimits(RatioLimits(0.75, 1.0), RatioLimits(0.0, 1.0)); // weird (does keel groups)
1373            // limitedScorer.setLimits(RatioLimits(0.9, 1.0), RatioLimits(0.0, 1.0)); // removes group 'upper'
1374            // limitedScorer.setLimits(RatioLimits(0.0, 1.0), RatioLimits(0.0, 0.5)); // filters no group
1375            // limitedScorer.setLimits(RatioLimits(0.0, 1.0), RatioLimits(0.0, 0.1)); // filters groups 'twoleafs' + 'low2' (group 'lower' superseeded by 'low1')
1376            // limitedScorer.setLimits(RatioLimits(0.0, 1.0), RatioLimits(0.0, 0.3)); // filters group 'twoleafs'
1377            // limitedScorer.setLimits(RatioLimits(0.9, 1.0), RatioLimits(0.0, 0.1)); // filters all groups (weird)
1378            limitedScorer.setLimits(RatioLimits(0.7, 1.0), RatioLimits(0.0, 0.3)); // filters group 'upper' + group 'twoleafs'
1379            limitedScorer.setPerErrorPenalties(2.0, 2.0, 0.0002); // all values are 2*default -> result is same, scores are doubled!
1380
1381            TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource3, treeTarget2, LOG, REMOVE_EXISTING_GROUPS, XFER_ALL_GROUPS, limitedScorer, "")); // test empty ACI does same as passing NULp
1382            TEST_LOGS_EXPECTED("group_xfer_32_li.log.expected");
1383            TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget2, topo32_li);
1384        }
1385    }
1386
1387    // add node info
1388    {
1389        const char *comment_added = "Added node info from tree_tree2";
1390        TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN(treeTarget1, comment_added);
1391
1392        TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource2, treeTarget1, LOG, KEEP_OLD_NAMES, XFER_ALL_GROUPS, defaultScorer, NULp));
1393        TEST_LOGS_EXPECTED("group_xfer_21.log.expected");
1394
1395        TEST_EXPECT_SAVED_NEWICK__BROKEN(nSIMPLE, gb_main, treeTarget1, org_topo1); // @@@ moving node info modifies topology; caused by NT_tree_cmp.cxx@NAIVE_ROOTING
1396        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget1, unwanted_topo21);
1397        TEST_EXPECT_TREE_COMMENT_CONTAINS(treeTarget1, comment_added);
1398
1399        // @@@ when we have a function to set the root according to another tree (#449),
1400        // use that function here. sorting tree after that, should again result in 'org_topo1'!
1401
1402        TEST_EXPECT_NO_ERROR(sort_namedtree_by_other_tree(gb_main, treeTarget1, treeSortby1));
1403        TEST_EXPECT_SAVED_NEWICK(nGROUP, gb_main, treeTarget1, sorted_topo21);
1404    }
1405
1406    // compare node info
1407    {
1408        const char *comment_added = "Compared topology with tree_test";
1409        TEST_EXPECT_TREE_COMMENT_DOESNT_CONTAIN(treeTarget1, comment_added);
1410
1411        TEST_EXPECT_NO_ERROR(NTREE_move_tree_info(gb_main, treeSource1, treeTarget1, NULp, COMPARE_TOPOLOGY, XFER_ALL_GROUPS, defaultScorer, NULp));
1412        TEST_EXPECT_SAVED_NEWICK(nREMARK, gb_main, treeTarget1, compared_topo);
1413        TEST_EXPECT_TREE_COMMENT_CONTAINS(treeTarget1, comment_added);
1414    }
1415
1416    // test error cases:
1417    {
1418#define DOESNT_MATTER_ARGS gb_main,treeSource2,treeTarget1,LOG,REMOVE_EXISTING_GROUPS,XFER_ALL_GROUPS
1419
1420        GroupMatchScorer invalidScoring;
1421
1422        invalidScoring.setPerErrorPenalties(1.0, 0.0, 0.0001);
1423        TEST_EXPECT_ERROR_CONTAINS(NTREE_move_tree_info(DOESNT_MATTER_ARGS, invalidScoring, NULp), "one outgroup penalty has to be different from zero");
1424
1425        invalidScoring.setPerErrorPenalties(0.0, 1.0, 0.0001);
1426        TEST_EXPECT_ERROR_CONTAINS(NTREE_move_tree_info(DOESNT_MATTER_ARGS, invalidScoring, NULp), "one ingroup penalty has to be different from zero");
1427
1428        invalidScoring.setPerErrorPenalties(-1.0, 1.0, 0.0001);
1429        TEST_EXPECT_ERROR_CONTAINS(NTREE_move_tree_info(DOESNT_MATTER_ARGS, invalidScoring, NULp), "invalid negative in/outgroup penalty");
1430
1431        invalidScoring.setPerErrorPenalties(1.0, 1.0, 0.0001);
1432        invalidScoring.setRelativePenalties(100.0, -100.0);
1433        TEST_EXPECT_ERROR_CONTAINS(NTREE_move_tree_info(DOESNT_MATTER_ARGS, invalidScoring, NULp), "invalid negative in/outgroup penalty");
1434
1435#undef DOESNT_MATTER_ARGS
1436    }
1437
1438    GB_unlink(LOG);
1439    GB_close(gb_main);
1440}
1441
1442__ATTR__REDUCED_OPTIMIZE void TEST_edges() {
1443    GB_shell  shell;
1444    GBDATA   *gb_main = GB_open("TEST_trees.arb", "rw");
1445    TEST_REJECT_NULL(gb_main);
1446
1447    {
1448        GB_transaction  ta(gb_main);
1449        TreeNode       *tree = GBT_read_tree(gb_main, "tree_test", new SizeAwareRoot);
1450
1451        TreeNode *left  = tree->findLeafNamed("CloTyro3"); TEST_REJECT_NULL(left);
1452        TreeNode *node  = left->get_father();              TEST_REJECT_NULL(node);
1453        TreeNode *right = node->findLeafNamed("CloTyro4"); TEST_REJECT_NULL(right);
1454
1455        TEST_EXPECT(node == right->get_father());
1456        TEST_EXPECT(node->get_leftson()  == left);
1457        TEST_EXPECT(node->get_rightson() == right);
1458
1459        TreeNode *parent  = node->get_father();                TEST_REJECT_NULL(parent);
1460        TreeNode *brother = parent->findLeafNamed("CloTyro2"); TEST_REJECT_NULL(brother);
1461
1462        TEST_EXPECT(node->get_brother() == brother);
1463
1464        TreeNode *grandpa  = parent->get_father(); TEST_REJECT_NULL(grandpa);
1465
1466        // topology:
1467        //
1468        //            grandpa
1469        //              /
1470        //             /
1471        //            /
1472        //          parent
1473        //           /\              .
1474        //          /  \             .
1475        //         /    \            .
1476        //       node  brother
1477        //        /\                 .
1478        //       /  \                .
1479        //      /    \               .
1480        //    left right
1481
1482        // test next() and otherNext() for inner edge 'node->parent'
1483        {
1484            ARB_edge nodeUp = parentEdge(node);
1485
1486            TEST_EXPECT(node->is_leftson());                      // if child is left son..
1487            TEST_EXPECT(nodeUp.next().dest()         == grandpa); // .. next() continues rootwards
1488            TEST_EXPECT(nodeUp.counter_next().dest() == brother);
1489
1490            ARB_edge brotherUp = parentEdge(brother);
1491
1492            TEST_EXPECT(brother->is_rightson());                  // if child is right son..
1493            TEST_EXPECT(brotherUp.next().dest()         == node); // .. next() continues with other son
1494            TEST_EXPECT(brotherUp.counter_next().dest() == grandpa);
1495
1496            ARB_edge down = nodeUp.inverse();
1497
1498            TEST_EXPECT(down.next().dest()         == right); // next descends into right son
1499            TEST_EXPECT(down.counter_next().dest() == left);
1500
1501            TEST_EXPECT(nodeUp.previous().source()         == left);
1502            TEST_EXPECT(nodeUp.counter_previous().source() == right);
1503
1504            ARB_edge toLeaf(node, left);
1505            TEST_EXPECT(toLeaf.is_edge_to_leaf());
1506
1507            // all iterators should turn around at leaf:
1508            TEST_EXPECT(toLeaf.next().dest()         == node);
1509            TEST_EXPECT(toLeaf.counter_next().dest() == node);
1510
1511            ARB_edge fromLeaf(left, node);
1512            TEST_EXPECT(fromLeaf.previous().dest()         == left);
1513            TEST_EXPECT(fromLeaf.counter_previous().dest() == left);
1514
1515            ARB_edge rootEdge(tree->get_leftson(), tree->get_rightson());
1516
1517            TEST_EXPECT(rootEdge.get_type() == ROOT_EDGE);
1518            TEST_EXPECT(nodeUp.get_type()   == EDGE_TO_ROOT);
1519            TEST_EXPECT(fromLeaf.get_type() == EDGE_TO_ROOT);
1520            TEST_EXPECT(down.get_type()     == EDGE_TO_LEAF);
1521            TEST_EXPECT(toLeaf.get_type()   == EDGE_TO_LEAF);
1522
1523            // test iterators are inverse functions
1524            {
1525                ARB_edge e[] = { nodeUp, down, toLeaf, fromLeaf, rootEdge };
1526                const int EDGES = ARRAY_ELEMS(e);
1527                for (int i = 0; i<EDGES; ++i) {
1528                    TEST_ANNOTATE(GBS_global_string("i=%i", i));
1529
1530                    TEST_EXPECT(e[i].next().previous() == e[i]);
1531                    TEST_EXPECT(e[i].previous().next() == e[i]);
1532
1533                    TEST_EXPECT(e[i].counter_next().counter_previous() == e[i]);
1534                    TEST_EXPECT(e[i].counter_previous().counter_next() == e[i]);
1535
1536                    ARB_edge inv(e[i].inverse());
1537
1538                    TEST_EXPECT(e[i].counter_next().inverse().next()         == inv);
1539                    TEST_EXPECT(e[i].counter_previous().inverse().previous() == inv);
1540                    TEST_EXPECT(e[i].next().inverse().counter_next()         == inv);
1541                    TEST_EXPECT(e[i].previous().inverse().counter_previous() == inv);
1542                }
1543            }
1544
1545            // test adjacent_distance
1546            const double EPSILON = 0.000001;
1547
1548            const double NLEN = 0.025806;
1549            const double BLEN = 0.017316;
1550            const double PLEN = 0.017167;
1551            const double LLEN = 1.045690;
1552            const double RLEN = 0.060606;
1553
1554            TEST_EXPECT_SIMILAR(node->get_branchlength(),             NLEN, EPSILON);
1555            TEST_EXPECT_SIMILAR(nodeUp.length(),                      NLEN, EPSILON);
1556            TEST_EXPECT_SIMILAR(down.length(),                        NLEN, EPSILON);
1557            TEST_EXPECT_SIMILAR(nodeUp.length_or_adjacent_distance(), NLEN, EPSILON);
1558            TEST_EXPECT_SIMILAR(down.length_or_adjacent_distance(),   NLEN, EPSILON);
1559
1560            TEST_EXPECT_SIMILAR(brother->get_branchlength(), BLEN,      EPSILON);
1561            TEST_EXPECT_SIMILAR(parent ->get_branchlength(), PLEN,      EPSILON);
1562            TEST_EXPECT_SIMILAR(nodeUp.adjacent_distance(),  BLEN+PLEN, EPSILON);
1563
1564            TEST_EXPECT_SIMILAR(left ->get_branchlength(), LLEN,      EPSILON);
1565            TEST_EXPECT_SIMILAR(right->get_branchlength(), RLEN,      EPSILON);
1566            TEST_EXPECT_SIMILAR(down.adjacent_distance(),  LLEN+RLEN, EPSILON);
1567
1568            // modify lengths
1569            const double MOD_NLEN = 0.123456;
1570            const double MOD_LLEN = 0.246802;
1571
1572            toLeaf.set_length(MOD_LLEN);
1573            nodeUp.set_length(MOD_NLEN);
1574
1575            TEST_EXPECT_SIMILAR(toLeaf.length(), MOD_LLEN, EPSILON);
1576            TEST_EXPECT_SIMILAR(nodeUp.length(), MOD_NLEN, EPSILON);
1577            TEST_EXPECT_SIMILAR(down.length(),   MOD_NLEN, EPSILON);
1578        }
1579
1580        destroy(tree);
1581    }
1582
1583    GB_close(gb_main);
1584}
1585
1586void TEST_remove_bootstraps() {
1587    GB_shell  shell;
1588    GBDATA   *gb_main = GB_open("TEST_trees.arb", "rw");
1589    TEST_REJECT_NULL(gb_main);
1590
1591    {
1592        GB_transaction  ta(gb_main);
1593        TreeNode       *tree = GBT_read_tree(gb_main, "tree_test", new SizeAwareRoot);
1594        TEST_REJECT_NULL(tree);
1595
1596        const char *topo_org   = "(((((((CloTyro3,CloTyro4)'40%',CloTyro2)'0%',CloTyrob)'97%',CloInnoc)'0%',CloBifer)'53%',(((CloButy2,CloButyr),CloCarni)'33%',CloPaste)'97%'),((((CorAquat,CurCitre),CorGluta)'17%',CelBiazo)'40%',CytAquat));";
1597        const char *topo_rem   = "(((((((CloTyro3,CloTyro4)"   ",CloTyro2)"  ",CloTyrob)"   ",CloInnoc)"  ",CloBifer)"   ",(((CloButy2,CloButyr),CloCarni)"   ",CloPaste)"   "),((((CorAquat,CurCitre),CorGluta)"   ",CelBiazo)"   ",CytAquat));";
1598
1599        TEST_EXPECT_NEWICK(nREMARK, tree, topo_org);
1600
1601        tree->remove_bootstrap();
1602        TEST_EXPECT_NEWICK(nREMARK, tree, topo_rem);
1603
1604        destroy(tree);
1605    }
1606
1607    GB_close(gb_main);
1608}
1609
1610void TEST_multifurcate_tree() {
1611    GB_shell  shell;
1612    GBDATA   *gb_main = GB_open("TEST_trees.arb", "rw");
1613    TEST_REJECT_NULL(gb_main);
1614
1615    const char *topo_test               = "(((((((CloTyro3:1.046,CloTyro4:0.061)'40%':0.026,CloTyro2:0.017)'0%':0.017,CloTyrob:0.009)'97%:test':0.274,CloInnoc:0.371)'0%':0.057,CloBifer:0.388)'53%':0.124,(((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120)'33%':0.010,CloPaste:0.179)'97%':0.131):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522)'17%':0.053,CelBiazo:0.059)'40%':0.207,CytAquat:0.711):0.081);";
1616    // changes                          = "                                                                                                    +0.307         -0.371     +0.064 "
1617    const char *topo_single             = "(((((((CloTyro3:1.046,CloTyro4:0.061)'40%':0.026,CloTyro2:0.017)'0%':0.017,CloTyrob:0.009)'97%:test':0.581,CloInnoc:0.000)'0%':0.121,CloBifer:0.388)'53%':0.124,(((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120)'33%':0.010,CloPaste:0.179)'97%':0.131):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522)'17%':0.053,CelBiazo:0.059)'40%':0.207,CytAquat:0.711):0.081);";
1618    const char *topo_bs_less_101_005    = "(((((((CloTyro3:1.098,CloTyro4:0.064)"   ":0.000,CloTyro2:0.000)"  ":0.000,CloTyrob:0.000)'97%:test':0.287,CloInnoc:0.371)'0%':0.057,CloBifer:0.388)'53%':0.124,(((CloButy2:0.000,CloButyr:0.000):0.578,CloCarni:0.121)"   ":0.000,CloPaste:0.181)'97%':0.132):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522)'17%':0.053,CelBiazo:0.059)'40%':0.207,CytAquat:0.711):0.081);";
1619    const char *topo_bs_less_101_005_NT = "(((((((CloTyro3:1.078,CloTyro4:0.062)"   ":0.000,CloTyro2:0.018)"  ":0.000,CloTyrob:0.009)'97%:test':0.282,CloInnoc:0.371)'0%':0.057,CloBifer:0.388)'53%':0.124,(((CloButy2:0.009,CloButyr:0.000):0.570,CloCarni:0.121)"   ":0.000,CloPaste:0.181)'97%':0.132):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522)'17%':0.053,CelBiazo:0.059)'40%':0.207,CytAquat:0.711):0.081);";
1620    const char *topo_bs_less_30_005     = "(((((((CloTyro3:1.046,CloTyro4:0.061)'40%':0.027,CloTyro2:0.018)"  ":0.000,CloTyrob:0.009)'97%:test':0.288,CloInnoc:0.371)'0%':0.057,CloBifer:0.388)'53%':0.124,(((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120)'33%':0.010,CloPaste:0.179)'97%':0.131):0.081,((((CorAquat:0.084,CurCitre:0.058):0.103,CorGluta:0.522)'17%':0.053,CelBiazo:0.059)'40%':0.207,CytAquat:0.711):0.081);";
1621    const char *topo_bs_less_30         = "(((((((CloTyro3:1.046,CloTyro4:0.061)'40%':0.027,CloTyro2:0.018)"  ":0.000,CloTyrob:0.009)'97%:test':0.302,CloInnoc:0.390)"  ":0.000,CloBifer:0.407)'53%':0.131,(((CloButy2:0.009,CloButyr:0.000):0.564,CloCarni:0.120)'33%':0.010,CloPaste:0.179)'97%':0.131):0.081,((((CorAquat:0.084,CurCitre:0.058):0.109,CorGluta:0.554)"   ":0.000,CelBiazo:0.062)'40%':0.220,CytAquat:0.711):0.081);";
1622    const char *topo_all                = "(((((((CloTyro3:0.000,CloTyro4:0.000)"   ":0.000,CloTyro2:0.000)"  ":0.000,CloTyrob:0.000)'"  "test':0.000,CloInnoc:0.000)"  ":0.000,CloBifer:0.000)"   ":0.000,(((CloButy2:0.000,CloButyr:0.000):0.000,CloCarni:0.000)"   ":0.000,CloPaste:0.000)"   ":0.000):0.000,((((CorAquat:0.000,CurCitre:0.000):0.000,CorGluta:0.000)"   ":0.000,CelBiazo:0.000)"   ":0.000,CytAquat:0.000):0.000);";
1623
1624    const double STABLE_LENGTH = 5.362750;
1625    const double EPSILON       = 0.000001;
1626
1627    for (int test = 1; test<=6; ++test) {
1628        GB_transaction  ta(gb_main);
1629        TreeNode       *tree = GBT_read_tree(gb_main, "tree_test", new SizeAwareRoot);
1630
1631        TEST_REJECT_NULL(tree);
1632        if (test == 1) {
1633            TEST_EXPECT_NEWICK(nALL, tree, topo_test);
1634            TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1635        }
1636
1637        switch (test) {
1638            case 1:
1639                tree->multifurcate_whole_tree(TreeNode::multifurc_limits(101, 0.05, true));
1640                TEST_EXPECT_NEWICK(nALL, tree, topo_bs_less_101_005);
1641                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1642                break;
1643            case 6:
1644                tree->multifurcate_whole_tree(TreeNode::multifurc_limits(101, 0.05, false));
1645                TEST_EXPECT_NEWICK(nALL, tree, topo_bs_less_101_005_NT);
1646                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1647                break;
1648            case 2:
1649                tree->multifurcate_whole_tree(TreeNode::multifurc_limits(30, 0.05, true));
1650                TEST_EXPECT_NEWICK(nALL, tree, topo_bs_less_30_005);
1651                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1652                break;
1653            case 3:
1654                tree->multifurcate_whole_tree(TreeNode::multifurc_limits(30, 1000, true));
1655                TEST_EXPECT_NEWICK(nALL, tree, topo_bs_less_30);
1656                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1657                break;
1658            case 4:
1659                tree->multifurcate_whole_tree(TreeNode::multifurc_limits(101, 1000, true)); // multifurcate all
1660                TEST_EXPECT_NEWICK(nALL, tree, topo_all);
1661                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), 0.0, EPSILON);
1662                break;
1663            case 5: {
1664                TreeNode *CloInnoc = tree->findLeafNamed("CloInnoc");
1665                TEST_REJECT_NULL(CloInnoc);
1666
1667                parentEdge(CloInnoc).multifurcate();
1668                TEST_EXPECT_NEWICK(nALL, tree, topo_single);
1669
1670                TEST_EXPECT_SIMILAR(tree->sum_child_lengths(), STABLE_LENGTH, EPSILON);
1671                break;
1672            }
1673            default:
1674                nt_assert(0);
1675                break;
1676        }
1677
1678        destroy(tree);
1679    }
1680
1681    GB_close(gb_main);
1682}
1683
1684void TEST_TreeNode_attributes() {
1685    // -> ../UNIT_TESTER/run/trees/bg_exp_p__0.tree
1686    TreeNode *tree = TREE_load("trees/bg_exp_p__0.tree", new SimpleRoot, NULp, false, NULp);
1687
1688    TreeNode *MabPelag = tree->findLeafNamed("MabPelag");
1689    TreeNode *MabSalin = tree->findLeafNamed("MabSalin");
1690    TreeNode *PaoMaris = tree->findLeafNamed("PaoMaris");
1691
1692    TEST_EXPECT(MabPelag->father == MabSalin->father); // are brothers
1693
1694    TreeNode *Mabs    = MabPelag->father;
1695    TreeNode *PaoMabs = PaoMaris->father;
1696
1697    // ((MabPelag, MabSalin), PaoMaris)
1698    //  -------- Mabs ------
1699    // -------------- PaoMabs ---------
1700
1701    TEST_EXPECT(Mabs->father == PaoMabs);
1702
1703    // is_son_of
1704    TEST_EXPECT(MabPelag->is_son_of(Mabs));
1705    TEST_EXPECT(MabSalin->is_son_of(Mabs));
1706    TEST_EXPECT(Mabs->is_son_of(PaoMabs));
1707    TEST_EXPECT(PaoMaris->is_son_of(PaoMabs));
1708
1709    // is_inside
1710    TEST_EXPECT(MabPelag->is_inside(Mabs));     // leaf in father
1711    TEST_EXPECT(Mabs->is_inside(PaoMabs));      // node in father
1712    TEST_EXPECT(MabPelag->is_inside(PaoMabs));  // leaf in grandfather
1713    TEST_EXPECT(MabPelag->is_inside(MabPelag)); // self-containment
1714    TEST_REJECT(Mabs->is_inside(MabPelag));     // not: father in child
1715    TEST_REJECT(MabPelag->is_inside(MabSalin)); // not: node in brother
1716
1717    // is_ancestor_of
1718    TEST_EXPECT(Mabs->is_ancestor_of(MabPelag));
1719    TEST_EXPECT(Mabs->is_ancestor_of(MabSalin));
1720    TEST_REJECT(Mabs->is_ancestor_of(PaoMaris)); // brother is no ancestor
1721    TEST_REJECT(Mabs->is_ancestor_of(Mabs));     // node never is ancestor of itself
1722    TEST_REJECT(Mabs->is_ancestor_of(PaoMabs));  // child cannot be ancestor
1723
1724    TEST_EXPECT(PaoMabs->is_ancestor_of(MabPelag)); // root of subtree (PaoMabs) is ancestor of all members..
1725    TEST_EXPECT(PaoMabs->is_ancestor_of(MabSalin));
1726    TEST_EXPECT(PaoMabs->is_ancestor_of(PaoMaris));
1727    TEST_EXPECT(PaoMabs->is_ancestor_of(Mabs));
1728    TEST_REJECT(PaoMabs->is_ancestor_of(PaoMabs));  // .. despite itself
1729
1730    // in_same_branch_as / in_other_branch_than
1731    TEST_EXPECT(MabPelag->in_same_branch_as(MabPelag));
1732    TEST_EXPECT(MabPelag->in_other_branch_than(MabSalin));
1733    TEST_EXPECT(MabPelag->in_other_branch_than(PaoMaris));
1734    TEST_EXPECT(MabPelag->in_same_branch_as(Mabs));
1735    TEST_EXPECT(MabPelag->in_same_branch_as(PaoMabs));
1736
1737    TEST_EXPECT(PaoMabs->in_same_branch_as(MabPelag));
1738    TEST_EXPECT(PaoMabs->in_same_branch_as(MabSalin));
1739    TEST_EXPECT(PaoMabs->in_same_branch_as(PaoMaris));
1740    TEST_EXPECT(PaoMabs->in_same_branch_as(Mabs));
1741    TEST_EXPECT(PaoMabs->in_same_branch_as(PaoMabs));
1742
1743    TEST_EXPECT(Mabs->in_same_branch_as(MabPelag));
1744    TEST_EXPECT(Mabs->in_same_branch_as(MabSalin));
1745    TEST_EXPECT(Mabs->in_other_branch_than(PaoMaris));
1746    TEST_EXPECT(Mabs->in_same_branch_as(Mabs));
1747    TEST_EXPECT(Mabs->in_same_branch_as(PaoMabs));
1748
1749    // ancestor_common_with
1750    TEST_EXPECT(MabPelag->ancestor_common_with(MabSalin) == Mabs);
1751    TEST_EXPECT(MabSalin->ancestor_common_with(MabPelag) == Mabs);
1752    TEST_EXPECT(PaoMaris->ancestor_common_with(MabPelag) == PaoMabs);
1753    TEST_EXPECT(PaoMabs->ancestor_common_with(Mabs)      == PaoMabs);
1754    TEST_EXPECT(MabPelag->ancestor_common_with(PaoMabs)  == PaoMabs);
1755
1756    destroy(tree);
1757}
1758
1759#endif // UNIT_TESTS
1760
1761// --------------------------------------------------------------------------------
1762
Note: See TracBrowser for help on using the repository browser.