# C++ mutants that survive because no behaviour distinguishes them.
#
# The counterpart of equivalent_mutants.txt for the native core. Same purpose,
# same two failure modes, one extra field: mutation of C++ is lexical, so a
# claim is keyed by column as well as line. A line can carry two candidates that
# read the same and are equivalent for different reasons -- in the fsum
# correction one `<` compares the remainder against zero and the other compares
# a partial -- and one argument does not cover both.
#
# Format, one claim per line:
#
#     <header path>  <line>:<column>  <before> -> <after>  # src=<8 hex> why
#
# The reason is required. An entry without one is a suppression, not a finding.
# `src=` is a whitespace-normalised digest of the line the claim is about, so
# reindenting does not invalidate an argument while changing the expression does.
#
# Two ways this file fails the build, both deliberate:
#
#   * a survivor that is not listed here -- a real gap, or a new equivalent that
#     has not been justified yet;
#   * an entry here that matched no survivor -- the mutant is killed now, or the
#     line moved, and the claim must be re-read rather than left to rot.
#
# Three kinds of argument appear below, and they are not equally strong:
#
#   equivalent   the mutated expression computes the same answer by another
#                route, or is unreachable. These are proofs.
#   undefined    the mutation reads out of bounds -- `n >= 0` on a size_t, then
#                indexing at SIZE_MAX. Not equivalent; simply not observable in
#                a way any test could depend on. Listed as such rather than
#                dressed up as equivalence.
#   capacity     a `reserve` hint, which cannot change a result.
#
cpp/include/tfidf/core/reduction.hpp  57:14  < -> <=  # src=e1484609 magnitude is read only by the >= in Neumaier::add, where -0.0 and 0.0 compare equal; the function's own note already records that -0.0 is its only difference from std::abs
cpp/include/tfidf/core/reduction.hpp  74:28  >= -> >  # src=64fa110b at equal magnitudes both branches compute the same compensation: with sum == x the subtraction is exact either way, and with sum == -x the total is zero and both recover the same remainder
cpp/include/tfidf/core/reduction.hpp  103:26  0 -> 1  # src=d66c51c8 starting at level 1 leaves partials[0] at its value-initialised 0.0 and shifts every real partial up one slot; value() folds that 0.0 in last, and adding 0.0 is the identity for a finite total
cpp/include/tfidf/core/reduction.hpp  134:23  > -> >=  # src=7410e9bd levels is size_t so levels >= 0 is always true, and counts[levels - 1] then indexes counts[SIZE_MAX]; undefined rather than equivalent, and no input makes it deterministically observable
cpp/include/tfidf/core/reduction.hpp  137:23  2 -> 3  # src=26d66a27 the weight is only compared for equality, so what it must do is give equal-size subtrees equal counts rather than record the size; 3^k is injective in k exactly as 2^k is, so the merges land at the same points and the tree is identical
cpp/include/tfidf/core/reduction.hpp  139:20  < -> <=  # src=c5eb3d72 writing partials[64] needs 64 completed levels, which is 2^64 blocks of 128 values
cpp/include/tfidf/core/reduction.hpp  171:27  0 -> 1  # src=adca7709 resize(1) on the first add creates a leading 0.0 that every later add preserves, shifting the array and the index together so partials[n - 1] resolves to the same element; a 0.0 partial is inert in the descent and false in both arms of the sign test, and 600000 random expansions found no separating input
cpp/include/tfidf/core/reduction.hpp  174:29  < -> <=  # src=7f6c11d4 the swap only matters when the magnitudes differ; at |x| == |y| the two-sum is exact in either order, giving the same hi and a remainder of zero
cpp/include/tfidf/core/reduction.hpp  218:18  > -> >=  # src=24ccd7e3 n is size_t, so n >= 0 is always true and the next decrement wraps into partials[SIZE_MAX]; undefined rather than equivalent
cpp/include/tfidf/core/reduction.hpp  228:27  < -> <=  # src=e41ae70b the descent leaves lo non-zero wherever it breaks, and where it does not break n is zero and the first conjunct already fails, so lo is never compared against zero as an equal
cpp/include/tfidf/core/reduction.hpp  228:52  < -> <=  # src=e41ae70b add pushes a partial only when it is non-zero, so no partial is ever exactly 0.0 and <= cannot differ from <
cpp/include/tfidf/core/reduction.hpp  229:27  > -> >=  # src=9d5e6008 the mirror of the lo comparison on the line above: lo is non-zero wherever this is reached
cpp/include/tfidf/core/reduction.hpp  229:52  > -> >=  # src=9d5e6008 the mirror of the partial comparison on the line above: no partial is ever exactly 0.0
cpp/include/tfidf/similarity/scoring.hpp  54:58  / -> *  # src=44f2eb01 reserve is a capacity hint; touched grows to whatever it needs either way and no scored result can depend on it
cpp/include/tfidf/vectorisation/sparse.hpp  81:13  + -> -  # src=bb1ac0d7 indices are strictly ascending, so dropping this increment still gives the same sum: the next iteration compares the index that did not move against its successor and advances it through the a < b branch instead
cpp/include/tfidf/vectorisation/sparse.hpp  81:14  + -> -  # src=bb1ac0d7 the second token of the same increment, equivalent for the reason above
cpp/include/tfidf/vectorisation/sparse.hpp  82:13  + -> -  # src=08b428dd the mirror of the increment above, on the other list
cpp/include/tfidf/vectorisation/sparse.hpp  82:14  + -> -  # src=08b428dd the second token of the same increment, equivalent for the reason above
cpp/include/tfidf/vectorisation/sparse.hpp  83:22  < -> <=  # src=4d0d6b34 the branch above has already taken a == b, so this comparison never sees two equal indices
cpp/include/tfidf/vectorisation/sparse.hpp  184:30  0 -> 1  # src=47635e97 starting the monotonicity scan at row 1 is observable only when indptr decreases at the first pair, which needs indptr[1] < indptr[0] == 0 and hence a negative offset; row() then casts that to size_t, so the behaviour is undefined rather than merely wrong
cpp/include/tfidf/vectorisation/sparse.hpp  184:37  1 -> 0  # src=47635e97 undefined: i then reaches indptr.size() - 1 and the body reads indptr[i + 1], one past the end of the span
cpp/include/tfidf/vectorisation/sparse.hpp  184:39  < -> <=  # src=47635e97 undefined: the same last iteration reached by the other route, reading indptr[indptr.size()]
cpp/include/tfidf/vectorisation/sparse.hpp  241:26  0 -> 1  # src=42af1cef the skipped iteration adds colptr[0], which assign set to zero and the counting loop never writes because it writes t + 1; the prefix sum is unchanged
cpp/include/tfidf/vectorisation/sparse.hpp  247:69  - -> +  # src=b583f8a4 cursor is only ever indexed below n_cols, so sizing it one entry longer changes nothing the transpose reads
cpp/include/tfidf/vectorisation/sparse.hpp  247:71  1 -> 0  # src=b583f8a4 the same: end() - 0 sizes cursor one entry longer than needed and every read stays below n_cols
cpp/include/tfidf/ranking/attributes.hpp  54:23  || -> &&  # src=bb9642f0 the second disjunct binds tighter once && is substituted, so the guard collapses to seen[r] alone and an out-of-range r indexes seen out of bounds; undefined rather than wrong, and no test can depend on what it reads
cpp/include/tfidf/ranking/attributes.hpp  54:28  >= -> >  # src=bb9642f0 r == n_docs then passes the range check and indexes seen one past the end, an out-of-bounds read followed by an out-of-bounds write; undefined rather than wrong, and a test that provoked it would fail the sanitiser job instead
cpp/include/tfidf/ranking/attributes.hpp  88:18  * -> /  # src=2bdceffa inside the #else of ratio_less, compiled only where __SIZEOF_INT128__ is absent. GCC and clang define it, so this body is unreachable in every configuration the mutation campaign builds; MSVC does compile it, and the Windows CI job covers it by running the tests rather than by mutating them
cpp/include/tfidf/ranking/attributes.hpp  88:26  < -> <=  # src=2bdceffa the relational operator of the same #else body, unreachable in the campaign's configuration for the same reason
cpp/include/tfidf/ranking/attributes.hpp  88:34  * -> /  # src=2bdceffa inside the #else of ratio_less, compiled only where __SIZEOF_INT128__ is absent. GCC and clang define it, so this body is unreachable in every configuration the mutation campaign builds; MSVC does compile it, and the Windows CI job covers it by running the tests rather than by mutating them
cpp/include/tfidf/ranking/distances.hpp  122:51  0 -> 1  # src=191062f0 the merge buffer's fill value, which is never read: every call past the hi - lo <= 1 base case writes buffer[lo, hi) in full before copying that range back, so no element is read that this call did not write
cpp/include/tfidf/ranking/distances.hpp  198:13  1 -> 0  # src=46651258 k = 0 is the only argument the guard stops covering, and the closed form returns the same +0.0 there: the square is 0 and penalty * 0.0 * -1.0 is -0.0, whose sum with +0.0 is +0.0. That holds for every admissible penalty and only for those; at p = inf the term is NaN, which is why the argument is complete only now that checked_penalty fixes the domain at [0, 1]
cpp/include/tfidf/ranking/distances.hpp  231:27  + -> -  # src=1cc9bd0e capacity: a bucket-count hint for a set used only through insert(...).second, so no bucket count changes the union or the distance. Where b is the longer list the count underflows to an impossible one, which reserve may refuse by throwing rather than by returning a different answer
cpp/include/tfidf/ranking/distances.hpp  247:37  < -> <=  # src=05b5d422 both_x and both_y are the positions of two distinct documents in one list, and positions gives each document a distinct index, so the comparison never sees equal operands
cpp/include/tfidf/ranking/distances.hpp  247:55  < -> <=  # src=05b5d422 the same argument for the other arm of the conditional, comparing the same two distinct positions in the other order
cpp/include/tfidf/ranking/distances.hpp  251:33  1 -> 0  # src=7ad7798e the outer loop runs one further iteration, at i = uni.size() - 1, whose inner loop starts at j = i + 1 = uni.size() and is therefore empty; nothing is read and nothing is added
cpp/include/tfidf/ranking/distances.hpp  251:35  < -> <=  # src=7ad7798e the same extra iteration by the other spelling: i + 1 <= uni.size() admits i = uni.size() - 1, whose inner loop starts at j = uni.size() and runs no body
cpp/include/tfidf/ranking/distances.hpp  265:33  < -> <=  # src=d4220859 case 1 compares the positions of two distinct documents in one list: uni holds each document once, guarded by seen.insert(item).second, and detail::positions gives each document a distinct index, so the operands are never equal
cpp/include/tfidf/ranking/distances.hpp  265:62  < -> <=  # src=d4220859 the same argument for the other list, comparing the same two distinct documents in b
cpp/include/tfidf/ranking/distances.hpp  326:22  0 -> 1  # src=0dd38575 a default member initialiser that every construction overwrites: compare_top_k holds the only default construction of a TopKComparison and assigns k before returning
cpp/include/tfidf/ranking/distances.hpp  328:24  false -> true  # src=e7efa8c6 the same initialiser argument for sets_differ, assigned from sa != sb on every path
cpp/include/tfidf/ranking/distances.hpp  335:38  0 -> 1  # src=699f4e36 the same initialiser argument for intersection_size, assigned from shared.size() on every path
cpp/include/tfidf/ranking/distances.hpp  342:28  0 -> 1  # src=601b96b4 the same initialiser argument for swapped, assigned on every path. It does not extend to kendall_intersection: that field keeps its NaN default whenever fewer than two documents are shared, so its initialiser is read
cpp/include/tfidf/ranking/margins.hpp  30:22  0 -> 1  # src=0dd38575 a default member initialiser that every construction overwrites: boundary_margin and min_adjacent_margin_top both assign m.k before returning, and nothing else constructs a Margin
cpp/include/tfidf/ranking/margins.hpp  49:39  - -> +  # src=f68167c3 reserve is a capacity hint; it changes the allocation and nothing a caller can observe, since the loop below pushes exactly size() - 1 elements either way
cpp/include/tfidf/ranking/margins.hpp  49:41  1 -> 0  # src=f68167c3 the same hint, reserving one slot more than the loop fills
cpp/include/tfidf/ranking/margins.hpp  87:11  <= -> <  # src=8f8da993 at k = 0 the mutant falls through, k_effective becomes min(0, n) = 0, and the k_effective < 2 clause below returns the same undefined margin carrying the same k_effective; at k < 0 both forms take the guard. The clause decides nothing the next one does not. The 0 -> 1 mutant on this line is NOT equivalent and is killed: it returns k_effective = 0 at k = 1, where the guard as written gives 1
cpp/include/tfidf/ranking/ranker.hpp  93:19  < -> <=  # src=18ee9b44 line 79 clamps m to min(m, keys.size()), so the two differ only at m == keys.size(), where the mutant calls nth_element(begin, end, end) -- a well-formed no-op, since nth may equal last
cpp/include/tfidf/ranking/ranker.hpp  116:11  >= -> >  # src=bca09351 the mutation removes only the m == keys.size() case from the early return, and the loops below are then empty (j runs from m to keys.size()), so it returns true by the long route instead of the short one
cpp/include/tfidf/ranking/ranker.hpp  120:35  < -> <=  # src=7a2f5238 the loop then evaluates keys[keys.size()], one past the end of the span; undefined rather than wrong
cpp/include/tfidf/ranking/sort_keys.hpp  35:28  0 -> 1  # src=de0f3b8e a default member initialiser that build_keys overwrites for every key it produces, and no SortKey reaches a comparison without passing through build_keys
cpp/include/tfidf/ranking/sort_keys.hpp  36:17  0 -> 1  # src=142dcd04 the same, for the member whose value is read back out: build_keys assigns k.doc = i unconditionally
cpp/include/tfidf/ranking/sort_keys.hpp  50:28  < -> <=  # src=e04a720e guarded by if (a.neg_score != b.neg_score), so the comparison never sees equal operands and < and <= agree on every value that reaches it
cpp/include/tfidf/ranking/sort_keys.hpp  52:31  < -> <=  # src=b1ae1833 the loop then reads a.ranks[kMaxAttributes], one past the end of a fixed-size std::array; undefined rather than wrong
cpp/include/tfidf/ranking/sort_keys.hpp  54:31  < -> <=  # src=32ebc9a0 guarded by if (a.ranks[i] != b.ranks[i]) on the line above, so the comparison never sees equal operands
cpp/include/tfidf/ranking/sort_keys.hpp  72:22  0 -> 1  # src=a661864b every key is filled with the same constant and the loop below overwrites the leading slots of all of them alike, so the trailing slots compare equal to each other whatever the fill was
cpp/include/tfidf/ranking/sort_keys.hpp  73:58  < -> <=  # src=328a66fb the priority bound: a <= priority.size() would read priority one past its end. Undefined rather than wrong. The other < on this line, against kMaxAttributes, is killed
cpp/include/tfidf/ranking/sort_keys.hpp  113:31  < -> <=  # src=10880e50 the loop then reads copy[copy.size()], one past the end of the vector; undefined rather than wrong
cpp/include/tfidf/ranking/tie_groups.hpp  76:58  2 -> 3  # src=368ac68a the pivot of a boundary search. The invariant needs only lo <= mid < hi_search, which lo + (hi_search - lo) / 3 satisfies for every non-empty interval, so the search still converges on the same boundary by a different sequence of steps
cpp/include/tfidf/ranking/tie_groups.hpp  88:65  2 -> 3  # src=f8ec30b2 the same pivot argument, applied to the upper boundary search
cpp/include/tfidf/ranking/tie_groups.hpp  112:27  1 -> 0  # src=e144525a starting the scan at 0 reads sorted_scores[-1]; undefined rather than wrong
cpp/include/tfidf/ranking/tie_groups.hpp  168:24  || -> &&  # src=8dab6fe1 the two operands are the same predicate: chains and cliques are both empty exactly when the score span is, verified over 140000 combinations of input and tau, so the second disjunct never decides
cpp/include/tfidf/ranking/tie_groups.hpp  171:33  0 -> 1  # src=13093504 a lower bound on a max fold over widths that are all at least 1, since a group spans at least one score; the seed is dominated on every path that reaches it
cpp/include/tfidf/ranking/tie_groups.hpp  175:34  0 -> 1  # src=341fe9b3 the same fold-seed argument, and the guard above guarantees cliques is non-empty before the fold runs
