| 1 | // =============================================================== // |
|---|
| 2 | // // |
|---|
| 3 | // File : NT_tree_cmp.cxx // |
|---|
| 4 | // Purpose : // |
|---|
| 5 | // // |
|---|
| 6 | // Institute of Microbiology (Technical University Munich) // |
|---|
| 7 | // http://www.arb-home.de/ // |
|---|
| 8 | // // |
|---|
| 9 | // =============================================================== // |
|---|
| 10 | |
|---|
| 11 | #include "NT_species_set.h" |
|---|
| 12 | #include <aw_msg.hxx> |
|---|
| 13 | #include <gb_aci.h> |
|---|
| 14 | #include <gb_aci_impl.h> |
|---|
| 15 | #include <arb_progress.h> |
|---|
| 16 | #include <arb_defs.h> |
|---|
| 17 | #include <arb_msg_nospam.h> |
|---|
| 18 | #include <string> |
|---|
| 19 | #include <climits> |
|---|
| 20 | |
|---|
| 21 | using namespace std; |
|---|
| 22 | |
|---|
| 23 | SpecSetRegistry::SpecSetRegistry(long nspecies_, arb_progress *progress_, const GroupMatchScorer& scorer_) : |
|---|
| 24 | species_counter(0), |
|---|
| 25 | nspecies(nspecies_), |
|---|
| 26 | nsets(0), |
|---|
| 27 | sets(ARB_calloc<RSpecSet*>(max_nsets())), |
|---|
| 28 | scorer(scorer_), |
|---|
| 29 | progress(progress_), |
|---|
| 30 | species_hash(GBS_create_hash(nspecies, GB_IGNORE_CASE)) |
|---|
| 31 | { |
|---|
| 32 | for (int i=0; i<256; i++) { |
|---|
| 33 | int j = i; |
|---|
| 34 | int count = 0; |
|---|
| 35 | while (j) { // count bits |
|---|
| 36 | if (j&1) count ++; |
|---|
| 37 | j = j>>1; |
|---|
| 38 | } |
|---|
| 39 | set_bits[i] = count; |
|---|
| 40 | } |
|---|
| 41 | tmp_bitstring = allocate_bitstring(); |
|---|
| 42 | } |
|---|
| 43 | |
|---|
| 44 | SpecSetRegistry::~SpecSetRegistry() { |
|---|
| 45 | for (int i=0; i<nsets; i++) delete sets[i]; |
|---|
| 46 | free(tmp_bitstring); |
|---|
| 47 | free(sets); |
|---|
| 48 | GBS_free_hash(species_hash); |
|---|
| 49 | } |
|---|
| 50 | |
|---|
| 51 | void SpecSetRegistry::add(const char *species_name) { |
|---|
| 52 | if (GBS_read_hash(species_hash, species_name)) { |
|---|
| 53 | aw_message(GBS_global_string("Warning: Species '%s' is found more than once in tree", species_name)); |
|---|
| 54 | } |
|---|
| 55 | else { |
|---|
| 56 | GBS_write_hash(species_hash, species_name, ++species_counter); |
|---|
| 57 | nt_assert(species_counter<=nspecies); // more species added than planned |
|---|
| 58 | nt_assert(species_counter<=nspecies); |
|---|
| 59 | } |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | void SpecSetRegistry::add(RSpecSet *rset) { |
|---|
| 63 | nt_assert(rset->size()>1); // do NOT register leafs (only inner nodes allowed) |
|---|
| 64 | nt_assert(nsets<max_nsets()); |
|---|
| 65 | sets[nsets++] = rset; |
|---|
| 66 | } |
|---|
| 67 | |
|---|
| 68 | void SpecSetRegistry::dump_bitstring(const char *tag, unsigned char *bs) { |
|---|
| 69 | fprintf(stderr, "%s: ", tag); |
|---|
| 70 | const long bbytes = bitstring_bytes(); |
|---|
| 71 | for (long i = 0; i<bbytes; ++i) { |
|---|
| 72 | int val = bs[i]; |
|---|
| 73 | for (int b = 0; b<8; ++b) { |
|---|
| 74 | fputc(val&128 ? 'X' : '-', stderr); |
|---|
| 75 | val<<=1; |
|---|
| 76 | } |
|---|
| 77 | } |
|---|
| 78 | fputc('\n', stderr); |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | GroupPenalty GroupMatchScorer::calcPenalty(long removed, long added, long commonSpecies) { |
|---|
| 82 | // - can be calculated from removed/added/commonSpecies |
|---|
| 83 | // - shall be reported (result param?) |
|---|
| 84 | |
|---|
| 85 | if (commonSpecies) { |
|---|
| 86 | long oldGroupSize = removed+commonSpecies; |
|---|
| 87 | long newGroupSize = added+commonSpecies; |
|---|
| 88 | double ingroupRatio = 1.0 - removed/double(oldGroupSize); // = percent of old members in new group |
|---|
| 89 | double outgroupRatio = added/double(newGroupSize); // = percent of non-group-members in new group |
|---|
| 90 | |
|---|
| 91 | if (insideLimits(ingroupRatio, outgroupRatio)) { |
|---|
| 92 | double penalty = |
|---|
| 93 | // abs. penalty: |
|---|
| 94 | removed*ingroupPep + |
|---|
| 95 | added*outgroupPep + |
|---|
| 96 | // rel. penalty: |
|---|
| 97 | (1-ingroupRatio)*ingroupInvRelPen + |
|---|
| 98 | outgroupRatio*outgroupRelPen; |
|---|
| 99 | |
|---|
| 100 | return GroupPenalty(penalty, ingroupRatio, outgroupRatio, oldGroupSize); |
|---|
| 101 | } |
|---|
| 102 | } |
|---|
| 103 | return GroupPenalty::NoMatch(); |
|---|
| 104 | } |
|---|
| 105 | |
|---|
| 106 | GroupPenalty GroupMatchScorer::matchGroups(const TSpecSet& sourceSet, const RSpecSet& targetSet, long commonSpecies, long overallSpecies) { |
|---|
| 107 | /*! calculate score of group match (tests normal and keeled insertion, reports better) |
|---|
| 108 | * @param sourceSet tested set (= named group in source tree (if moving groups)) |
|---|
| 109 | * @param targetSet registered set (= subtree of target tree (if moving groups)) |
|---|
| 110 | * @param commonSpecies number of species common in sourceSet and targetSet |
|---|
| 111 | * @param overallSpecies number of registered species (in SpecSetRegistry) |
|---|
| 112 | * @return better GroupPenalty |
|---|
| 113 | */ |
|---|
| 114 | |
|---|
| 115 | long removed = sourceSet.get_known_members() - commonSpecies; |
|---|
| 116 | long added = targetSet.get_known_members() - commonSpecies; |
|---|
| 117 | |
|---|
| 118 | GroupPenalty match = calcPenalty(removed, added, commonSpecies); |
|---|
| 119 | nt_assert(implicated(match.doesMatch(), (removed+added)<overallSpecies)); |
|---|
| 120 | |
|---|
| 121 | long targetSize_keeled = overallSpecies - targetSet.get_known_members(); |
|---|
| 122 | long commonSpecies_keeled = sourceSet.get_known_members() - commonSpecies; |
|---|
| 123 | long removed_keeled = commonSpecies; |
|---|
| 124 | long added_keeled = targetSize_keeled - commonSpecies_keeled; |
|---|
| 125 | |
|---|
| 126 | GroupPenalty match_keeled = calcPenalty(removed_keeled, added_keeled, commonSpecies_keeled); |
|---|
| 127 | match_keeled.mark_as_keeled(); |
|---|
| 128 | if (match_keeled.doesMatch()) { // do not add to NoMatch! |
|---|
| 129 | match_keeled.addPenalty(keelPenalty); |
|---|
| 130 | } |
|---|
| 131 | nt_assert(implicated(match_keeled.doesMatch(), (removed_keeled+added_keeled)<overallSpecies)); |
|---|
| 132 | |
|---|
| 133 | if (match_keeled.betterThan(match)) { |
|---|
| 134 | // @@@ if this happens, the match semantic is inverted: if moving a group -> group should be keeled (by caller)! |
|---|
| 135 | // related to #512 + #451 |
|---|
| 136 | return match_keeled; |
|---|
| 137 | } |
|---|
| 138 | |
|---|
| 139 | return match; |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | double GroupMatchScorer::calcUnknownMembersPenalty(const TSpecSet& sourceSet) const { |
|---|
| 143 | return sourceSet.get_unknown_members() * unfoundPep; // penalty for unknown members in source tree |
|---|
| 144 | } |
|---|
| 145 | |
|---|
| 146 | GB_ERROR GroupMatchScorer::check_validity() const { |
|---|
| 147 | if (ingroupPep == 0.0 && ingroupInvRelPen == 0.0) { |
|---|
| 148 | return "one ingroup penalty has to be different from zero"; |
|---|
| 149 | } |
|---|
| 150 | if (outgroupPep == 0.0 && outgroupRelPen == 0.0) { |
|---|
| 151 | return "one outgroup penalty has to be different from zero"; |
|---|
| 152 | } |
|---|
| 153 | |
|---|
| 154 | if (ingroupPep<0.0 || outgroupPep<0.0 || ingroupInvRelPen<0.0 || outgroupRelPen<0.0) { |
|---|
| 155 | return "invalid negative in/outgroup penalty"; |
|---|
| 156 | } |
|---|
| 157 | |
|---|
| 158 | if (!ingroup.isValid() || !outgroup.isValid()) { |
|---|
| 159 | return "invalid limits"; |
|---|
| 160 | } |
|---|
| 161 | |
|---|
| 162 | return NULp; |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | RSpecSet *SpecSetRegistry::search_best_match(const TSpecSet *tset, GroupPenalty& min_penalty) { |
|---|
| 166 | // returns best match for 'tset' (against all registered sets). |
|---|
| 167 | // NULp if no match found. |
|---|
| 168 | // sets 'min_penalty' to penalty found for returned set. |
|---|
| 169 | |
|---|
| 170 | RSpecSet *bset = NULp; |
|---|
| 171 | min_penalty = GroupPenalty::NoMatch(); |
|---|
| 172 | |
|---|
| 173 | const long bbytes = bitstring_bytes(); |
|---|
| 174 | const long blongs = bitstring_longs(); |
|---|
| 175 | |
|---|
| 176 | unsigned char * const tbs = tset->bitstring; |
|---|
| 177 | long * const tbl = (long*)tbs; |
|---|
| 178 | long * const tmp_bl = (long*)tmp_bitstring; |
|---|
| 179 | |
|---|
| 180 | for (long i=nsets-1; i>=0; i--) { |
|---|
| 181 | long same = 0; // amount of known members present in both sets |
|---|
| 182 | |
|---|
| 183 | RSpecSet *rset = sets[i]; |
|---|
| 184 | |
|---|
| 185 | unsigned char * const rbs = rset->bitstring; |
|---|
| 186 | long * const rbl = (long*)rbs; |
|---|
| 187 | |
|---|
| 188 | for (long j = 0; j<blongs; ++j) { // LOOP_VECTORIZED |
|---|
| 189 | tmp_bl[j] = tbl[j] & rbl[j]; |
|---|
| 190 | } |
|---|
| 191 | for (long j = 0; j<bbytes; ++j) { // (Note: failed to optimize: gcc wont vectorize double-indexed access) |
|---|
| 192 | same += set_bits[tmp_bitstring[j]]; |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | GroupPenalty penalty = scorer.matchGroups(*tset, *rset, same, nspecies); |
|---|
| 196 | if (penalty.betterThan(min_penalty)) { |
|---|
| 197 | min_penalty = penalty; |
|---|
| 198 | bset = rset; |
|---|
| 199 | } |
|---|
| 200 | progress->inc(); |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | return bset; |
|---|
| 204 | } |
|---|
| 205 | |
|---|
| 206 | void GroupPenalty::registerUnfound(const GroupMatchScorer& scorer, const TSpecSet& tset) { |
|---|
| 207 | addPenalty(scorer.calcUnknownMembersPenalty(tset)); |
|---|
| 208 | nt_assert(unknown == -1); // registerUnfound shall be called exactly once! |
|---|
| 209 | unknown = tset.get_unknown_members(); |
|---|
| 210 | } |
|---|
| 211 | |
|---|
| 212 | double SpecSetRegistry::search_and_remember_best_match_and_log_errors(const TSpecSet *tset, FILE *log) { |
|---|
| 213 | /*! search for best matching registered subtree. |
|---|
| 214 | * @param tset tested set |
|---|
| 215 | * @param log if != NULp -> log move information (not allowed when "comparing topo"!). |
|---|
| 216 | * @return the penalty for best matching subtree (plus a small penalty for unregistered species) |
|---|
| 217 | * or -1 if best matching subtree is "too bad" (i.e. if no matching subtree was found). |
|---|
| 218 | * |
|---|
| 219 | * if return value >= 0 -> stores best_match & best_node (inside best match for 'tset' found in other tree) |
|---|
| 220 | * |
|---|
| 221 | * When moving node info, 'tset' represents a (named!) subtree of the source tree. |
|---|
| 222 | * When comparing topologies, 'tset' represents a subtree of the destination tree (trees are swapped in .@NTREE_move_tree_info) |
|---|
| 223 | */ |
|---|
| 224 | |
|---|
| 225 | GroupPenalty match; |
|---|
| 226 | RSpecSet *bset = search_best_match(tset, match); |
|---|
| 227 | |
|---|
| 228 | if (!bset) { |
|---|
| 229 | // no good match found. |
|---|
| 230 | // atm this happens for small groups (e.g. with 2 species) + a special placement of these species in tree. |
|---|
| 231 | // in the future 'finding no match' will get normal behavior. |
|---|
| 232 | return -1; |
|---|
| 233 | } |
|---|
| 234 | |
|---|
| 235 | AP_tree *earlierFoundGroup = bset->matchedNode(); // group previously considered for same destination node |
|---|
| 236 | AP_tree *currentGroup = tset->set_node; |
|---|
| 237 | |
|---|
| 238 | match.registerUnfound(scorer, *tset); |
|---|
| 239 | if (match.betterThan(bset->bestMatch())) { |
|---|
| 240 | nt_assert(!bset->bestMatch().isPerfectMatch()); // shall never supersede optimally placed group! |
|---|
| 241 | if (earlierFoundGroup && log) { |
|---|
| 242 | nt_assert(earlierFoundGroup->name && currentGroup->name); |
|---|
| 243 | fprintf(log, "Group '%s' skipped (got superseded by group '%s'; same best target positions)\n", |
|---|
| 244 | earlierFoundGroup->name, |
|---|
| 245 | currentGroup->name); |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | bset->storeBetterMatch(match, currentGroup); |
|---|
| 249 | } |
|---|
| 250 | else { |
|---|
| 251 | nt_assert(!match.isPerfectMatch()); // found two groups with optimal placement. should be impossible by design! can be caused by invalid penalties |
|---|
| 252 | if (log) { |
|---|
| 253 | nt_assert(earlierFoundGroup->name && currentGroup->name); |
|---|
| 254 | fprintf(log, "Group '%s' skipped (does NOT supersede group '%s'; same best target positions)\n", |
|---|
| 255 | currentGroup->name, |
|---|
| 256 | earlierFoundGroup->name); |
|---|
| 257 | } |
|---|
| 258 | } |
|---|
| 259 | |
|---|
| 260 | return match.get_penalty(); |
|---|
| 261 | } |
|---|
| 262 | |
|---|
| 263 | void SpecSet::init(AP_tree *nodei, const SpecSetRegistry& ssr) { |
|---|
| 264 | bitstring = ssr.allocate_bitstring(); |
|---|
| 265 | known_members = 0; |
|---|
| 266 | set_node = nodei; |
|---|
| 267 | } |
|---|
| 268 | |
|---|
| 269 | SpecSet::SpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const char *species_name) { |
|---|
| 270 | init(nodei, ssr); |
|---|
| 271 | |
|---|
| 272 | long species_index = ssr.get_species_index(species_name); |
|---|
| 273 | if (species_index) { |
|---|
| 274 | nt_assert(species_index>0); |
|---|
| 275 | --species_index; // [1..N] -> [0..N-1] |
|---|
| 276 | nt_assert((species_index/8) < ssr.bitstring_bytes()); |
|---|
| 277 | bitstring[species_index/8] |= 1 << (species_index % 8); |
|---|
| 278 | known_members = 1; |
|---|
| 279 | } |
|---|
| 280 | } |
|---|
| 281 | |
|---|
| 282 | SpecSet::SpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const SpecSet *l, const SpecSet *r) { |
|---|
| 283 | init(nodei, ssr); |
|---|
| 284 | |
|---|
| 285 | const long *lbs = (const long *)l->bitstring; |
|---|
| 286 | const long *rbs = (const long *)r->bitstring; |
|---|
| 287 | long *dbs = (long *)bitstring; |
|---|
| 288 | |
|---|
| 289 | for (long j = ssr.bitstring_longs()-1; j>=0; j--) { // LOOP_VECTORIZED=2 |
|---|
| 290 | dbs[j] = lbs[j] | rbs[j]; |
|---|
| 291 | } |
|---|
| 292 | known_members = l->known_members + r->known_members; |
|---|
| 293 | } |
|---|
| 294 | |
|---|
| 295 | SpecSet::~SpecSet() { |
|---|
| 296 | free(bitstring); |
|---|
| 297 | } |
|---|
| 298 | |
|---|
| 299 | RSpecSet::RSpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const char *species_name) : |
|---|
| 300 | SpecSet(nodei, ssr, species_name), |
|---|
| 301 | best_node(NULp) |
|---|
| 302 | {} |
|---|
| 303 | RSpecSet::RSpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const RSpecSet *l, const RSpecSet *r) : |
|---|
| 304 | SpecSet(nodei, ssr, l, r), |
|---|
| 305 | best_node(NULp) |
|---|
| 306 | {} |
|---|
| 307 | TSpecSet::TSpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const char *species_name) : |
|---|
| 308 | SpecSet(nodei, ssr, species_name), |
|---|
| 309 | unfound_species_count(1-known_members) |
|---|
| 310 | { |
|---|
| 311 | } |
|---|
| 312 | TSpecSet::TSpecSet(AP_tree *nodei, const SpecSetRegistry& ssr, const TSpecSet *l, const TSpecSet *r) : |
|---|
| 313 | SpecSet(nodei, ssr, l, r), |
|---|
| 314 | unfound_species_count(l->unfound_species_count + r->unfound_species_count) |
|---|
| 315 | { |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | RSpecSet *SpecSetRegistry::registerTree(AP_tree *node) { |
|---|
| 319 | RSpecSet *ss; |
|---|
| 320 | // Warning: confusing resource handling: |
|---|
| 321 | // - leafs are returned "NOT owned by anybody" |
|---|
| 322 | // - inner nodes are added to and owned by this->sets |
|---|
| 323 | |
|---|
| 324 | if (node->is_leaf()) { |
|---|
| 325 | this->add(node->name); |
|---|
| 326 | ss = new RSpecSet(node, *this, node->name); |
|---|
| 327 | nt_assert(ss->is_leaf_set()); |
|---|
| 328 | } |
|---|
| 329 | else { |
|---|
| 330 | RSpecSet *ls = registerTree(node->get_leftson()); |
|---|
| 331 | RSpecSet *rs = registerTree(node->get_rightson()); |
|---|
| 332 | |
|---|
| 333 | ss = new RSpecSet(node, *this, ls, rs); |
|---|
| 334 | this->add(ss); |
|---|
| 335 | nt_assert(!ss->is_leaf_set()); |
|---|
| 336 | |
|---|
| 337 | if (ls->is_leaf_set()) delete ls; |
|---|
| 338 | if (rs->is_leaf_set()) delete rs; |
|---|
| 339 | } |
|---|
| 340 | return ss; |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | TSpecSet *SpecSetRegistry::find_best_matches_info(AP_tree *node, FILE *log, bool compare_node_info) { |
|---|
| 344 | /* Go through all nodes of subtree 'node' (part of the source tree if copying info) |
|---|
| 345 | * and search for the best matching registered node (in this->sets; which belong to dest tree if copying info). |
|---|
| 346 | * |
|---|
| 347 | * [Note: If comparing topologies, source- and dest-trees were exchanged by caller.] |
|---|
| 348 | * |
|---|
| 349 | * Named groups (if copying info) resp. all nodes (if comparing topologies) |
|---|
| 350 | * get compared versus all registered nodes and the best matching node is detected. |
|---|
| 351 | * The compared node is stored inside the best matching node. |
|---|
| 352 | */ |
|---|
| 353 | |
|---|
| 354 | nt_assert(contradicted(log, compare_node_info)); |
|---|
| 355 | |
|---|
| 356 | TSpecSet *ss = NULp; |
|---|
| 357 | if (node->is_leaf()) { |
|---|
| 358 | ss = new TSpecSet(node, *this, node->name); |
|---|
| 359 | } |
|---|
| 360 | else { |
|---|
| 361 | TSpecSet *ls = find_best_matches_info(node->get_leftson(), log, compare_node_info); |
|---|
| 362 | TSpecSet *rs = ls ? find_best_matches_info(node->get_rightson(), log, compare_node_info) : NULp; |
|---|
| 363 | |
|---|
| 364 | if (rs) { |
|---|
| 365 | ss = new TSpecSet(node, *this, ls, rs); |
|---|
| 366 | if (compare_node_info) { |
|---|
| 367 | nt_assert(!log); // does not work / not allowed |
|---|
| 368 | |
|---|
| 369 | double penalty = search_and_remember_best_match_and_log_errors(ss, log); |
|---|
| 370 | int ipenalty = int(penalty); |
|---|
| 371 | |
|---|
| 372 | // the #-sign is important (otherwise TREE_write_Newick will not work correctly; interference with bootstrap values; refer to #787 + #767) |
|---|
| 373 | if (ipenalty>0) node->use_as_remark(GBS_global_string_copy("# %i", ipenalty)); |
|---|
| 374 | else if (ipenalty<0) node->set_remark("# no match"); |
|---|
| 375 | else node->remove_remark(); |
|---|
| 376 | } |
|---|
| 377 | else { |
|---|
| 378 | if (node->name) { |
|---|
| 379 | double penalty = search_and_remember_best_match_and_log_errors(ss, log); |
|---|
| 380 | if (penalty<0) { |
|---|
| 381 | nt_assert(log); |
|---|
| 382 | fprintf(log, "Group '%s' doesn't fit to any destination subtree.\n", node->name); |
|---|
| 383 | } |
|---|
| 384 | } |
|---|
| 385 | } |
|---|
| 386 | } |
|---|
| 387 | delete rs; |
|---|
| 388 | delete ls; |
|---|
| 389 | } |
|---|
| 390 | |
|---|
| 391 | if (ss && progress->aborted()) { |
|---|
| 392 | delete ss; |
|---|
| 393 | ss = NULp; // abort recursion after user-abort |
|---|
| 394 | } |
|---|
| 395 | return ss; |
|---|
| 396 | } |
|---|
| 397 | |
|---|
| 398 | // ----------------------------- |
|---|
| 399 | // local ACI extension |
|---|
| 400 | |
|---|
| 401 | class GroupXfer_callenv : public GBL_call_env { |
|---|
| 402 | RefPtr<const char> name; |
|---|
| 403 | const GroupPenalty& match; |
|---|
| 404 | |
|---|
| 405 | int oldGroupSize; |
|---|
| 406 | int newGroupSize; |
|---|
| 407 | |
|---|
| 408 | public: |
|---|
| 409 | GroupXfer_callenv(const GBL_env& env_, const char *name_, const GroupPenalty& match_, int oldGroupSize_, int newGroupSize_) : |
|---|
| 410 | GBL_call_env(NULp, env_), |
|---|
| 411 | name(name_), |
|---|
| 412 | match(match_), |
|---|
| 413 | oldGroupSize(oldGroupSize_), |
|---|
| 414 | newGroupSize(newGroupSize_) |
|---|
| 415 | {} |
|---|
| 416 | |
|---|
| 417 | const char *get_group_name() const { return name; } |
|---|
| 418 | const GroupPenalty& get_match() const { return match; } |
|---|
| 419 | int get_old_group_size() const { return oldGroupSize; } |
|---|
| 420 | int get_new_group_size() const { return newGroupSize; } |
|---|
| 421 | }; |
|---|
| 422 | |
|---|
| 423 | inline const GroupXfer_callenv& custom_gx_env(GBL_command_arguments *args) { return DOWNCAST_REFERENCE(const GroupXfer_callenv, args->get_callEnv()); } |
|---|
| 424 | inline const GroupPenalty& custom_match(GBL_command_arguments *args) { return custom_gx_env(args).get_match(); } |
|---|
| 425 | |
|---|
| 426 | using namespace GBL_IMPL; |
|---|
| 427 | |
|---|
| 428 | #define IMPL_FORMATVALUE_CMD(args,fmt,value) \ |
|---|
| 429 | COMMAND_DROPS_INPUT_STREAMS(args); \ |
|---|
| 430 | GB_ERROR error = check_no_parameter(args); \ |
|---|
| 431 | if (!error) FORMAT_2_OUT(args, fmt, value); \ |
|---|
| 432 | return error |
|---|
| 433 | |
|---|
| 434 | static GB_ERROR gxl_groupname(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%s", custom_gx_env(args).get_group_name()); } |
|---|
| 435 | static GB_ERROR gxl_penalty(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%f", custom_match(args).get_penalty()); } |
|---|
| 436 | static GB_ERROR gxl_ingroup(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%.1f%%", custom_match(args).get_ingroup_ratio()*100.0); } |
|---|
| 437 | static GB_ERROR gxl_outgroup(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%.1f%%", custom_match(args).get_outgroup_ratio()*100.0); } |
|---|
| 438 | static GB_ERROR gxl_oldsize(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%i", custom_gx_env(args).get_old_group_size()); } |
|---|
| 439 | static GB_ERROR gxl_newsize(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%i", custom_gx_env(args).get_new_group_size()); } |
|---|
| 440 | static GB_ERROR gxl_unknown(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%i", custom_match(args).get_unknown()); } |
|---|
| 441 | static GB_ERROR gxl_keeled(GBL_command_arguments *args) { IMPL_FORMATVALUE_CMD(args, "%i", int(custom_match(args).shouldHaveBeenKeeled())); } |
|---|
| 442 | |
|---|
| 443 | static GBL_command_definition groupXfer_command_table[] = { |
|---|
| 444 | { "groupname", gxl_groupname }, |
|---|
| 445 | { "penalty", gxl_penalty }, |
|---|
| 446 | { "ingroup", gxl_ingroup }, |
|---|
| 447 | { "outgroup", gxl_outgroup }, |
|---|
| 448 | { "oldsize", gxl_oldsize }, |
|---|
| 449 | { "newsize", gxl_newsize }, |
|---|
| 450 | { "unknown", gxl_unknown }, |
|---|
| 451 | { "keeled", gxl_keeled }, |
|---|
| 452 | { NULp, NULp } |
|---|
| 453 | }; |
|---|
| 454 | |
|---|
| 455 | static const GBL_command_lookup_table& get_GroupXfer_customized_ACI_commands() { |
|---|
| 456 | static GBL_custom_command_lookup_table clt(groupXfer_command_table, |
|---|
| 457 | ARRAY_ELEMS(groupXfer_command_table)-1, |
|---|
| 458 | ACI_get_standard_commands()); |
|---|
| 459 | return clt; |
|---|
| 460 | } |
|---|
| 461 | |
|---|
| 462 | |
|---|
| 463 | |
|---|
| 464 | GB_ERROR SpecSetRegistry::write_node_information(FILE *log, bool delete_old_nodes, GroupsToTransfer what, const char *aci) { |
|---|
| 465 | GB_ERROR error = NULp; |
|---|
| 466 | MessageSpamFilter suppress("problematic group names"); |
|---|
| 467 | |
|---|
| 468 | if (log) fputs("\nDetailed group changes:\n\n", log); |
|---|
| 469 | |
|---|
| 470 | GBL_env env(NULp, // have no item here |
|---|
| 471 | NULp, // use no treename |
|---|
| 472 | get_GroupXfer_customized_ACI_commands()); |
|---|
| 473 | |
|---|
| 474 | for (long j=this->nsets-1; j>=0 && !error; j--) { |
|---|
| 475 | RSpecSet * const cset = this->sets[j]; |
|---|
| 476 | |
|---|
| 477 | char *old_group_name = NULp; // !=NULp -> old node has been deleted |
|---|
| 478 | AP_tree *sourceNode = cset->matchedNode(); |
|---|
| 479 | AP_tree *targetNode = cset->set_node; |
|---|
| 480 | bool insert_new_node = sourceNode && sourceNode->name; |
|---|
| 481 | |
|---|
| 482 | if (what == XFER_GROUPS_WITH_MARKED && insert_new_node) { |
|---|
| 483 | if (!targetNode->contains_marked_species()) insert_new_node = false; // @@@ this tests the wrong tree (the target tree)! (#792) |
|---|
| 484 | } |
|---|
| 485 | |
|---|
| 486 | if (targetNode->gb_node && (delete_old_nodes || insert_new_node)) { // There is already a node, delete old |
|---|
| 487 | if (!targetNode->name) { |
|---|
| 488 | GBDATA *gb_name = GB_entry(targetNode->gb_node, "group_name"); |
|---|
| 489 | if (gb_name) { |
|---|
| 490 | targetNode->name = GB_read_string(gb_name); |
|---|
| 491 | } |
|---|
| 492 | else { |
|---|
| 493 | targetNode->name = ARB_strdup("<gb_node w/o name>"); // @@@ happens whenever any other node info is present (e.g. linewidth or angles; node gets deleted when delete_old_nodes==true); #793 |
|---|
| 494 | } |
|---|
| 495 | } |
|---|
| 496 | |
|---|
| 497 | old_group_name = ARB_strdup(targetNode->name); // store old_group_name to rename new inserted node |
|---|
| 498 | |
|---|
| 499 | error = GB_delete(targetNode->gb_node); |
|---|
| 500 | if (!error) { |
|---|
| 501 | targetNode->gb_node = NULp; |
|---|
| 502 | freenull(targetNode->name); |
|---|
| 503 | } |
|---|
| 504 | } |
|---|
| 505 | |
|---|
| 506 | if (!error) { |
|---|
| 507 | if (insert_new_node) { |
|---|
| 508 | targetNode->gb_node = GB_create_container(targetNode->get_tree_root()->get_gb_tree(), "node"); |
|---|
| 509 | error = GB_copy_dropProtectMarksAndTempstate(targetNode->gb_node, sourceNode->gb_node); // @@@ this copies more info than group-name (e.g. linewidth or angles; unwanted!); #793 |
|---|
| 510 | // @@@ need a function which copies/moves/deletes group-related info (between two nodes). currently affected fields: 'group_name', 'grouped', 'keeled'; #793 |
|---|
| 511 | |
|---|
| 512 | if (!error) { |
|---|
| 513 | GBDATA *gb_name = GB_entry(targetNode->gb_node, "group_name"); |
|---|
| 514 | nt_assert(gb_name); |
|---|
| 515 | if (gb_name) { |
|---|
| 516 | const GroupPenalty& match = cset->bestMatch(); |
|---|
| 517 | |
|---|
| 518 | int oldGroupsize = match.get_groupsize(); |
|---|
| 519 | int newGroupsize = cset->size(); |
|---|
| 520 | |
|---|
| 521 | char *new_group_name = NULp; // new wanted groupname |
|---|
| 522 | { |
|---|
| 523 | char *source_group_name = GB_read_string(gb_name); // existing groupname (in group-container copied from source tree) |
|---|
| 524 | if (aci) { |
|---|
| 525 | GroupXfer_callenv callEnv(env, source_group_name, match, oldGroupsize, newGroupsize); |
|---|
| 526 | new_group_name = GB_command_interpreter_in_env("", aci, callEnv); |
|---|
| 527 | if (!new_group_name) error = GB_await_error(); |
|---|
| 528 | } |
|---|
| 529 | else { |
|---|
| 530 | reassign(new_group_name, source_group_name); |
|---|
| 531 | } |
|---|
| 532 | free(source_group_name); |
|---|
| 533 | } |
|---|
| 534 | |
|---|
| 535 | if (!error) { |
|---|
| 536 | nt_assert(new_group_name); |
|---|
| 537 | if (old_group_name) { // overwrite existing group |
|---|
| 538 | if (!delete_old_nodes) { |
|---|
| 539 | if (strcmp(old_group_name, new_group_name) != 0) { // old and new name differ |
|---|
| 540 | char *combined_name = GBS_global_string_copy("%s [was: %s]", new_group_name, old_group_name); |
|---|
| 541 | if (log) fprintf(log, "Destination group '%s' overwritten by '%s'", old_group_name, combined_name); |
|---|
| 542 | reassign(new_group_name, combined_name); |
|---|
| 543 | } |
|---|
| 544 | else { // name matches existing |
|---|
| 545 | if (log) fprintf(log, "Group '%s' remains unchanged", old_group_name); |
|---|
| 546 | } |
|---|
| 547 | } |
|---|
| 548 | else { |
|---|
| 549 | if (log) { |
|---|
| 550 | if (strcmp(old_group_name, new_group_name) == 0) { |
|---|
| 551 | fprintf(log, "Group '%s' remains unchanged", old_group_name); |
|---|
| 552 | } |
|---|
| 553 | else { |
|---|
| 554 | fprintf(log, "Destination group '%s' overwritten by '%s'", old_group_name, new_group_name); |
|---|
| 555 | } |
|---|
| 556 | } |
|---|
| 557 | } |
|---|
| 558 | } |
|---|
| 559 | else { // no group at destination -> insert |
|---|
| 560 | if (log) fprintf(log, "Group '%s' inserted", new_group_name); |
|---|
| 561 | } |
|---|
| 562 | } |
|---|
| 563 | |
|---|
| 564 | if (!error) error = GBT_write_group_name(gb_name, new_group_name, true); // always write wanted target name |
|---|
| 565 | |
|---|
| 566 | if (log) { |
|---|
| 567 | if (error) { |
|---|
| 568 | fprintf(log, " (Failed! Reason: %s)\n", error); |
|---|
| 569 | } |
|---|
| 570 | else if (match.isPerfectMatch()) { |
|---|
| 571 | fputc('\n', log); |
|---|
| 572 | } |
|---|
| 573 | else { |
|---|
| 574 | fprintf(log, " (not placed optimal; penalty=%f; in=%.1f%%/%i; out=%.1f%%/%i%s)\n", |
|---|
| 575 | match.get_penalty(), |
|---|
| 576 | match.get_ingroup_ratio() *100.0, oldGroupsize, |
|---|
| 577 | match.get_outgroup_ratio()*100.0, newGroupsize, |
|---|
| 578 | match.shouldHaveBeenKeeled() ? "; WARNING: matched inverse set, i.e. rest of tree!" : ""); |
|---|
| 579 | } |
|---|
| 580 | } |
|---|
| 581 | |
|---|
| 582 | free(new_group_name); |
|---|
| 583 | } |
|---|
| 584 | } |
|---|
| 585 | } |
|---|
| 586 | else { |
|---|
| 587 | nt_assert(implicated(old_group_name, delete_old_nodes)); |
|---|
| 588 | if (old_group_name && log) { |
|---|
| 589 | fprintf(log, "Destination group '%s' removed\n", old_group_name); |
|---|
| 590 | } |
|---|
| 591 | } |
|---|
| 592 | } |
|---|
| 593 | free(old_group_name); |
|---|
| 594 | } |
|---|
| 595 | return error; |
|---|
| 596 | } |
|---|
| 597 | |
|---|
| 598 | void SpecSetRegistry::finish(GB_ERROR& error) { |
|---|
| 599 | if (!error) error = progress->error_if_aborted(); |
|---|
| 600 | progress->done(); |
|---|
| 601 | } |
|---|
| 602 | |
|---|
| 603 | GB_ERROR NTREE_move_tree_info(GBDATA *gb_main, const char *tree_source, const char *tree_dest, const char *log_file, GroupTransferMode mode, GroupsToTransfer what, const GroupMatchScorer& scorer, const char *aci) { |
|---|
| 604 | GB_ERROR error = NULp; |
|---|
| 605 | FILE *log = NULp; |
|---|
| 606 | |
|---|
| 607 | if (!contradicted(mode == COMPARE_TOPOLOGY, log_file)) { |
|---|
| 608 | GBK_terminatef("invalid use of function NTREE_move_tree_info: mode=%i log_file=%s\n", int(mode), log_file); |
|---|
| 609 | } |
|---|
| 610 | |
|---|
| 611 | if (aci && !aci[0]) aci = NULp; // handle "empty ACI" like "no ACI" |
|---|
| 612 | |
|---|
| 613 | if (mode == COMPARE_TOPOLOGY) { |
|---|
| 614 | // info is written into 'tree_source' in .@find_best_matches_info |
|---|
| 615 | // (but we want to modify destination tree - like 'mode node info' does) |
|---|
| 616 | std::swap(tree_source, tree_dest); |
|---|
| 617 | } |
|---|
| 618 | |
|---|
| 619 | if (log_file) { |
|---|
| 620 | nt_assert(mode == REMOVE_EXISTING_GROUPS || mode == KEEP_OLD_NAMES); |
|---|
| 621 | log = fopen(log_file, "w"); |
|---|
| 622 | if (log) { |
|---|
| 623 | const char *transferredGroups = what == XFER_GROUPS_WITH_MARKED ? "groups containing marked" : "all"; |
|---|
| 624 | const char *overwriteMode = NULp; |
|---|
| 625 | |
|---|
| 626 | switch (mode) { |
|---|
| 627 | case COMPARE_TOPOLOGY: nt_assert(0); break; |
|---|
| 628 | case REMOVE_EXISTING_GROUPS: overwriteMode = "remove existing groups"; break; |
|---|
| 629 | case KEEP_OLD_NAMES: overwriteMode = "keep old names"; break; |
|---|
| 630 | } |
|---|
| 631 | |
|---|
| 632 | nt_assert(transferredGroups && overwriteMode); |
|---|
| 633 | |
|---|
| 634 | fprintf(log, // start of log |
|---|
| 635 | "LOGFILE: transfer group information\n" |
|---|
| 636 | "\n" |
|---|
| 637 | " Source tree '%s'\n" |
|---|
| 638 | "Destination tree '%s'\n" |
|---|
| 639 | "\n" |
|---|
| 640 | "transferred groups: %s\n" |
|---|
| 641 | " overwrite mode: %s\n" |
|---|
| 642 | "\n", |
|---|
| 643 | tree_source, |
|---|
| 644 | tree_dest, |
|---|
| 645 | transferredGroups, |
|---|
| 646 | overwriteMode); |
|---|
| 647 | } |
|---|
| 648 | else { |
|---|
| 649 | error = GB_IO_error("writing", log_file); |
|---|
| 650 | } |
|---|
| 651 | } |
|---|
| 652 | |
|---|
| 653 | GB_begin_transaction(gb_main); |
|---|
| 654 | |
|---|
| 655 | AP_tree_root rsource(new AliView(gb_main), NULp, false, NULp); |
|---|
| 656 | AP_tree_root rdest (new AliView(gb_main), NULp, false, NULp); |
|---|
| 657 | AP_tree_root& rsave = (mode == COMPARE_TOPOLOGY) ? rsource : rdest; |
|---|
| 658 | arb_progress progress(mode == COMPARE_TOPOLOGY ? "Compare topologies" : (mode == REMOVE_EXISTING_GROUPS ? "Copy group information" : "Add group information")); |
|---|
| 659 | |
|---|
| 660 | if (!error) error = scorer.check_validity(); |
|---|
| 661 | if (!error) error = rsource.loadFromDB(tree_source); |
|---|
| 662 | if (!error) error = rsource.linkToDB(NULp, NULp); |
|---|
| 663 | if (!error) { |
|---|
| 664 | error = rdest.loadFromDB(tree_dest); |
|---|
| 665 | if (!error) error = rdest.linkToDB(NULp, NULp); |
|---|
| 666 | if (!error) { |
|---|
| 667 | AP_tree *source = rsource.get_root_node(); |
|---|
| 668 | AP_tree *dest = rdest.get_root_node(); |
|---|
| 669 | |
|---|
| 670 | int dest_leafs = dest->count_leafs(); |
|---|
| 671 | int dest_nodes = leafs_2_nodes(dest_leafs, ROOTED); |
|---|
| 672 | int dest_inodes = dest_nodes-dest_leafs; // inner nodes |
|---|
| 673 | |
|---|
| 674 | int source_leafs = source->count_leafs(); |
|---|
| 675 | int source_nodes = leafs_2_nodes(source_leafs, ROOTED); |
|---|
| 676 | int source_inodes = source_nodes-source_leafs; // inner nodes |
|---|
| 677 | |
|---|
| 678 | size_t compared_nodes = mode == COMPARE_TOPOLOGY ? source_inodes : source->count_clades(); |
|---|
| 679 | size_t progress_steps = (compared_nodes + 2) * dest_inodes; // 2 for searching both sides of root |
|---|
| 680 | |
|---|
| 681 | arb_progress compare_progress(progress_steps); |
|---|
| 682 | compare_progress.subtitle("Register topology"); |
|---|
| 683 | |
|---|
| 684 | { |
|---|
| 685 | SpecSetRegistry ssr(dest_leafs, &compare_progress, scorer); |
|---|
| 686 | |
|---|
| 687 | ssr.registerTree(dest); |
|---|
| 688 | |
|---|
| 689 | if (source_leafs < 3) error = GB_export_errorf("tree '%s' has less than 3 species", tree_source); |
|---|
| 690 | else { |
|---|
| 691 | compare_progress.subtitle("Match subtrees"); |
|---|
| 692 | |
|---|
| 693 | TSpecSet *root_tsetl = ssr.find_best_matches_info(source->get_leftson(), log, mode == COMPARE_TOPOLOGY); |
|---|
| 694 | TSpecSet *root_tsetr = root_tsetl ? ssr.find_best_matches_info(source->get_rightson(), log, mode == COMPARE_TOPOLOGY) : NULp; |
|---|
| 695 | |
|---|
| 696 | if (root_tsetr) { |
|---|
| 697 | if (mode != COMPARE_TOPOLOGY) { |
|---|
| 698 | compare_progress.subtitle("Write group information"); |
|---|
| 699 | error = ssr.write_node_information(log, mode == REMOVE_EXISTING_GROUPS, what, aci); |
|---|
| 700 | } |
|---|
| 701 | |
|---|
| 702 | if (!error) { |
|---|
| 703 | GroupPenalty dummy; |
|---|
| 704 | |
|---|
| 705 | ssr.setScorer(GroupMatchScorer()); // otherwise may find no matches |
|---|
| 706 | RSpecSet *new_root_rsetl = ssr.search_best_match(root_tsetl, dummy); |
|---|
| 707 | RSpecSet *new_root_rsetr = ssr.search_best_match(root_tsetr, dummy); |
|---|
| 708 | |
|---|
| 709 | nt_assert(new_root_rsetl && new_root_rsetr); // should always report matches (even really bad ones!) |
|---|
| 710 | |
|---|
| 711 | AP_tree *new_rootl = new_root_rsetl->set_node; |
|---|
| 712 | AP_tree *new_rootr = new_root_rsetr->set_node; |
|---|
| 713 | |
|---|
| 714 | // naive attempt to set root of target tree |
|---|
| 715 | // @@@ setting root wont work very well.. |
|---|
| 716 | // - if these two nodes are NOT adjacent (esp. if 'new_rootr' is less "good" than 'new_rootl') |
|---|
| 717 | // - probably modifies topology even if not necessary; see ad_trees.cxx@NAIVE_ROOTING |
|---|
| 718 | new_rootl->set_root(); |
|---|
| 719 | new_rootr->set_root(); |
|---|
| 720 | |
|---|
| 721 | compare_progress.subtitle("Save trees"); |
|---|
| 722 | |
|---|
| 723 | AP_tree *saved_root = (mode == COMPARE_TOPOLOGY) ? source : new_rootr->get_root_node(); |
|---|
| 724 | error = GBT_overwrite_tree(rsave.get_gb_tree(), saved_root); |
|---|
| 725 | |
|---|
| 726 | if (!error) { |
|---|
| 727 | char *entry; |
|---|
| 728 | if (mode == COMPARE_TOPOLOGY) { |
|---|
| 729 | entry = GBS_global_string_copy("Compared topology with %s", tree_dest); // Note: the modified tree is 'tree_source'! |
|---|
| 730 | } |
|---|
| 731 | else { |
|---|
| 732 | const char *copiedOrAdded = mode == REMOVE_EXISTING_GROUPS ? "Copied" : "Added"; |
|---|
| 733 | |
|---|
| 734 | entry = GBS_global_string_copy("%s node info %sfrom %s", |
|---|
| 735 | copiedOrAdded, |
|---|
| 736 | what == XFER_GROUPS_WITH_MARKED ? "of marked " : "", |
|---|
| 737 | tree_source); |
|---|
| 738 | } |
|---|
| 739 | GBT_log_to_tree_remark(rsave.get_gb_tree(), entry, true); |
|---|
| 740 | free(entry); |
|---|
| 741 | } |
|---|
| 742 | } |
|---|
| 743 | } |
|---|
| 744 | |
|---|
| 745 | delete root_tsetl; |
|---|
| 746 | delete root_tsetr; |
|---|
| 747 | } |
|---|
| 748 | |
|---|
| 749 | ssr.finish(error); |
|---|
| 750 | } |
|---|
| 751 | if (error) compare_progress.done(); |
|---|
| 752 | } |
|---|
| 753 | } |
|---|
| 754 | |
|---|
| 755 | if (log) { |
|---|
| 756 | if (error) fprintf(log, "\nError: %s\n", error); // write error to log as well |
|---|
| 757 | else fputs("[done]\n", log); |
|---|
| 758 | fclose(log); |
|---|
| 759 | } |
|---|
| 760 | |
|---|
| 761 | return GB_end_transaction(gb_main, error); |
|---|
| 762 | } |
|---|
| 763 | |
|---|
| 764 | // -------------------------------------------------------------------------------- |
|---|
| 765 | |
|---|
| 766 | #ifdef UNIT_TESTS |
|---|
| 767 | #ifndef TEST_UNIT_H |
|---|
| 768 | #include <test_unit.h> |
|---|
| 769 | #endif |
|---|
| 770 | |
|---|
| 771 | void TEST_species_sets() { |
|---|
| 772 | // SpecSetRegistry ssr(0, NULp); // does fatal error - fine |
|---|
| 773 | |
|---|
| 774 | GroupMatchScorer defaultScorer; |
|---|
| 775 | { |
|---|
| 776 | SpecSetRegistry s1(1, NULp, defaultScorer); |
|---|
| 777 | TEST_EXPECT_EQUAL(s1.bitstring_bytes(), 1); |
|---|
| 778 | TEST_EXPECT_EQUAL(s1.bitstring_longs(), 1); |
|---|
| 779 | |
|---|
| 780 | s1.add("one"); |
|---|
| 781 | // s1.add("too much"); // fails assertion; fine |
|---|
| 782 | TSpecSet t1(NULp, s1, "one"); |
|---|
| 783 | } |
|---|
| 784 | { |
|---|
| 785 | SpecSetRegistry s8(8, NULp, defaultScorer); |
|---|
| 786 | TEST_EXPECT_EQUAL(s8.bitstring_bytes(), 1); |
|---|
| 787 | TEST_EXPECT_EQUAL(s8.bitstring_longs(), 1); |
|---|
| 788 | |
|---|
| 789 | for (int i = 1; i<=8; ++i) { |
|---|
| 790 | s8.add(GBS_global_string("%i", i)); |
|---|
| 791 | } |
|---|
| 792 | // s8.add("too much"); // fails assertion; fine |
|---|
| 793 | for (int i = 1; i<=8; ++i) { |
|---|
| 794 | TSpecSet t(NULp, s8, GBS_global_string("%i", i)); |
|---|
| 795 | } |
|---|
| 796 | } |
|---|
| 797 | { |
|---|
| 798 | SpecSetRegistry s9(9, NULp, defaultScorer); |
|---|
| 799 | TEST_EXPECT_EQUAL(s9.bitstring_bytes(), 2); |
|---|
| 800 | TEST_EXPECT_EQUAL(s9.bitstring_longs(), 1); |
|---|
| 801 | |
|---|
| 802 | for (int i = 1; i<=9; ++i) { |
|---|
| 803 | s9.add(GBS_global_string("%i", i)); |
|---|
| 804 | } |
|---|
| 805 | // s9.add("too much"); // fails assertion; fine |
|---|
| 806 | for (int i = 1; i<=9; ++i) { |
|---|
| 807 | TSpecSet t(NULp, s9, GBS_global_string("%i", i)); |
|---|
| 808 | } |
|---|
| 809 | } |
|---|
| 810 | |
|---|
| 811 | { |
|---|
| 812 | SpecSetRegistry s64(64, NULp, defaultScorer); |
|---|
| 813 | TEST_EXPECT_EQUAL(s64.bitstring_bytes(), 8); |
|---|
| 814 | TEST_EXPECT_EQUAL(s64.bitstring_longs(), 1); |
|---|
| 815 | |
|---|
| 816 | for (int i = 1; i<=64; ++i) { |
|---|
| 817 | s64.add(GBS_global_string("%i", i)); |
|---|
| 818 | } |
|---|
| 819 | // s64.add("too much"); // fails assertion; fine |
|---|
| 820 | for (int i = 1; i<=64; ++i) { |
|---|
| 821 | TSpecSet t(NULp, s64, GBS_global_string("%i", i)); |
|---|
| 822 | } |
|---|
| 823 | } |
|---|
| 824 | { |
|---|
| 825 | SpecSetRegistry s65(65, NULp, defaultScorer); |
|---|
| 826 | TEST_EXPECT_EQUAL(s65.bitstring_bytes(), 9); |
|---|
| 827 | TEST_EXPECT_EQUAL(s65.bitstring_longs(), 2); |
|---|
| 828 | |
|---|
| 829 | for (int i = 1; i<=65; ++i) { |
|---|
| 830 | s65.add(GBS_global_string("%i", i)); |
|---|
| 831 | } |
|---|
| 832 | // s65.add("too much"); // fails assertion; fine |
|---|
| 833 | for (int i = 1; i<=65; ++i) { |
|---|
| 834 | TSpecSet t(NULp, s65, GBS_global_string("%i", i)); |
|---|
| 835 | } |
|---|
| 836 | } |
|---|
| 837 | } |
|---|
| 838 | |
|---|
| 839 | #endif // UNIT_TESTS |
|---|
| 840 | |
|---|
| 841 | // -------------------------------------------------------------------------------- |
|---|