Commit 06d738f2 authored by Mikaël Salson's avatar Mikaël Salson

Merge branch 'feature-a/4722-filter-kmerstringaffect' into 'dev'

Filtering >127 genes, welcoming back KmerStringAffect

Closes #4722

See merge request !954
parents 602b53c8 c6e07a4b
Pipeline #242776 failed with stages
in 15 minutes and 44 seconds
......@@ -17,8 +17,7 @@ FilterWithACAutomaton::~FilterWithACAutomaton(){
}
void FilterWithACAutomaton::buildACAutomatonToFilterBioReader(string seed, float keys_compress){
char asciiChar;
int asciiNumber;
unsigned int asciiNumber;
string currentLabel;
string previousLabel;
......@@ -27,13 +26,13 @@ void FilterWithACAutomaton::buildACAutomatonToFilterBioReader(string seed, float
indexes = nullptr;
return;
}
automaton = new PointerACAutomaton<KmerAffect>(seed, false, true);
automaton = new PointerACAutomaton<KmerStringAffect>(seed, false, true);
indexes = new vector<int>();
asciiNumber = SPECIFIC_KMERS_NUMBER;
automaton->insert(originalBioReader.sequence(0),std::string("") + char(asciiNumber), true, 0, seed);
automaton->insert(originalBioReader.sequence(0), std::to_string(asciiNumber), true, 0, seed);
indexes->push_back(0);
int previousAsciiNumber = asciiNumber;
unsigned int previousAsciiNumber = asciiNumber;
int rawNumber = 0;
previousLabel = extractGeneName(originalBioReader.label(0));
......@@ -49,15 +48,7 @@ void FilterWithACAutomaton::buildACAutomatonToFilterBioReader(string seed, float
indexes->push_back(i);
previousAsciiNumber = asciiNumber;
}
if(asciiNumber > 127){
cerr << WARNING_STRING << "Pre-filtering disabled" << endl;
delete automaton; delete indexes;
automaton = nullptr;
indexes = nullptr;
return;
}
asciiChar = char(asciiNumber);
automaton->insert(originalBioReader.sequence(i),std::string("") + asciiChar, true, 0, seed);
automaton->insert(originalBioReader.sequence(i),std::to_string(asciiNumber), true, 0, seed);
previousLabel = currentLabel;
}
indexes->push_back(originalBioReader.size());
......@@ -72,7 +63,7 @@ BioReader FilterWithACAutomaton::filterBioReaderWithACAutomaton(
seqtype &seq, int kmer_threshold, int pvalue){
BioReader result;
map<KmerAffect, int> mapAho;
map<KmerStringAffect, int> mapAho;
this->filtered_sequences_calls += 1;
if(!automaton || !indexes || kmer_threshold < 0){
this->filtered_sequences_nb += originalBioReader.size();
......@@ -110,17 +101,17 @@ BioReader FilterWithACAutomaton::filterBioReaderWithACAutomaton(
sorted map */
}else{
/* sort map */
using Comparator = bool (*) (pair<KmerAffect, int>, pair<KmerAffect, int>);
Comparator compFunctor = [](pair<KmerAffect, int> elem1 ,pair<KmerAffect, int> elem2){
using Comparator = bool (*) (pair<KmerStringAffect, int>, pair<KmerStringAffect, int>);
Comparator compFunctor = [](pair<KmerStringAffect, int> elem1 ,pair<KmerStringAffect, int> elem2){
return (elem1.second == elem2.second) ? elem1.first > elem2.first : elem1.second > elem2.second;
};
// Use a set to use the comparator and sort function
set<pair<KmerAffect, int>, Comparator> setOfWords(mapAho.begin(), mapAho.end(), compFunctor);
set<pair<KmerStringAffect, int>, Comparator> setOfWords(mapAho.begin(), mapAho.end(), compFunctor);
// Iterate over the pair and not the map
int nbKmers = 0;
int nb_kmers_limit = -1; // Limit number of kmers, defined when the last gene of interest is reached
for(pair<KmerAffect, int> element : setOfWords){
for(pair<KmerStringAffect, int> element : setOfWords){
// Add corresponding sequences to the BioReader
if(!element.first.isGeneric()){
continue;
......@@ -149,9 +140,8 @@ BioReader FilterWithACAutomaton::filterBioReaderWithACAutomaton(
return (result.size() == 0) ? originalBioReader : result;
}
void FilterWithACAutomaton::transferBioReaderSequences(const BioReader &src, BioReader &dst, KmerAffect k) const{
char asciiChar = k.getLabel().at(0);
unsigned int asciiNum = int(asciiChar);
void FilterWithACAutomaton::transferBioReaderSequences(const BioReader &src, BioReader &dst, KmerStringAffect k) const{
unsigned int asciiNum = stoi(k.getLabel());
if(asciiNum > indexes->size() || !k.isGeneric()){
throw invalid_argument("Incorrect K-mer transmitted.");
......@@ -161,9 +151,8 @@ void FilterWithACAutomaton::transferBioReaderSequences(const BioReader &src, Bio
}
}
int FilterWithACAutomaton::getSizeLongestTransferredSequence(const BioReader &reader, KmerAffect k) const{
char asciiChar = k.getLabel().at(0);
unsigned int asciiNum = int(asciiChar);
int FilterWithACAutomaton::getSizeLongestTransferredSequence(const BioReader &reader, KmerStringAffect k) const{
unsigned int asciiNum = stoi(k.getLabel());
if(asciiNum > indexes->size() || !k.isGeneric()){
throw invalid_argument("Incorrect K-mer transmitted.");
......@@ -181,7 +170,7 @@ vector<int>* FilterWithACAutomaton::getIndexes() const{
return this->indexes;
}
AbstractACAutomaton<KmerAffect>* FilterWithACAutomaton::getAutomaton() const{
AbstractACAutomaton<KmerStringAffect>* FilterWithACAutomaton::getAutomaton() const{
return this->automaton;
}
......
......@@ -8,7 +8,7 @@ class FilterWithACAutomaton {
private:
vector<int>* indexes;
AbstractACAutomaton<KmerAffect>* automaton;
AbstractACAutomaton<KmerStringAffect>* automaton;
public:
BioReader &originalBioReader;
......@@ -26,9 +26,9 @@ class FilterWithACAutomaton {
/**
This function will filter a BioReader
@param idxAho: A pointer to a pair containing an int vector pointer and
an AbstractACAutomaton pointer parametrized by KmerAffect.
an AbstractACAutomaton pointer parametrized by KmerStringAffect.
The int vector represents indexes of a BioReader and the
automaton is build with single char labels put in KmerAffect.
automaton is build with single char labels put in KmerStringAffect.
To know more about them, read doc of
buildACAutomatonToFilterBioReader function.
......@@ -103,7 +103,7 @@ class FilterWithACAutomaton {
/**
* Return the automaton stored.
*/
AbstractACAutomaton<KmerAffect>* getAutomaton() const;
AbstractACAutomaton<KmerStringAffect>* getAutomaton() const;
/**
......@@ -114,7 +114,7 @@ class FilterWithACAutomaton {
* The label stored in the K-mer is used to select sequences. For more informations
* about how the label is used, see buildACAutomatonToFilterBioReader's doc.
*/
void transferBioReaderSequences(const BioReader &src, BioReader &dst, const KmerAffect k) const;
void transferBioReaderSequences(const BioReader &src, BioReader &dst, const KmerStringAffect k) const;
friend ostream &operator<<(ostream&, const FilterWithACAutomaton&);
......@@ -123,6 +123,6 @@ class FilterWithACAutomaton {
* Get the size of the longest sequence among the sequences that were just
* transferred to the BioReader reader.
*/
int getSizeLongestTransferredSequence(const BioReader &reader, KmerAffect k) const;
int getSizeLongestTransferredSequence(const BioReader &reader, KmerStringAffect k) const;
};
#endif
......@@ -26,7 +26,7 @@ void Germline::init(string _code, char _shortcut,
affect_5 = string(1, toupper(shortcut)) + "-" + code + "V";
affect_4 = string(1, 14 + shortcut) + "-" + code + "D";
affect_3 = string(1, tolower(shortcut)) + "-" + code + "J";
filter_5 = build_automaton ? new FilterWithACAutomaton(rep_5, this->seed_5, KEYS_COMPRESS) : nullptr;
filter_5 = build_automaton ? new FilterWithACAutomaton(rep_5, this->seed_5) : nullptr;
}
......
......@@ -33,8 +33,6 @@ enum SEGMENTATION_METHODS {
#define PSEUDO_NOT_ANALYZED "not analyzed"
#define PSEUDO_NOT_ANALYZED_CODE 'z'
#define KEYS_COMPRESS 1.65 // enough for ~208 *01 genes (191 IGHV + ...) / 127
using namespace std;
using json = nlohmann::json;
......
......@@ -220,3 +220,125 @@ ostream &operator<<(ostream &os, const KmerAffect &kmer) {
return os;
}
//////////////////////////////////////////////////
KmerStringAffect::KmerStringAffect() {
label = "";
strand = 0;
length = 0;
}
KmerStringAffect::KmerStringAffect(const KmerStringAffect &ksa):
label(ksa.label),strand(ksa.strand),length(ksa.length){}
KmerStringAffect::KmerStringAffect(const string &label,
int strand,
unsigned char length) {
this->label = label;
this->strand = strand;
this->length = length;
}
KmerStringAffect &KmerStringAffect::operator+=(const KmerStringAffect &kmer) {
if (*this != kmer) {
if (*this == KSA_UNKNOWN)
// Not defined yet
*this = kmer;
else if (*this != KSA_AMBIGUOUS) {
if (this->label == kmer.label)
// Different strand but same label, put ambiguous
*this = KSA_AMBIGUOUS;
else
// Ambiguous: different labels
*this = KSA_AMBIGUOUS;
} // else we are already ambiguous, stay as is.
}
return *this;
}
KmerStringAffect &KmerStringAffect::operator=(const KmerStringAffect &ka) {
label = ka.label;
strand = ka.strand;
length = ka.length;
return *this;
}
KmerStringAffect KmerStringAffect::getAmbiguous() {
return KSA_AMBIGUOUS;
}
int KmerStringAffect::getStrand() const {
return (isUnknown() || isAmbiguous()) ? 0 : strand;
}
string KmerStringAffect::getLabel() const {
return label;
}
unsigned char KmerStringAffect::getLength() const {
return length;
}
KmerStringAffect KmerStringAffect::getUnknown() {
return KSA_UNKNOWN;
}
bool KmerStringAffect::isAmbiguous() const {
return *this == KSA_AMBIGUOUS;
}
bool KmerStringAffect::isUnknown() const {
return *this == KSA_UNKNOWN;
}
bool KmerStringAffect::isNull() const {
return isUnknown();
}
bool KmerStringAffect::isGeneric() const {
return !(isUnknown() || isAmbiguous());
}
string KmerStringAffect::toString() const {
if (isUnknown()) {
return " _";
}
switch(strand) {
case 1:
return "+"+label;
case -1:
return "-"+label;
default:
return " ?";
}
}
bool operator==(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return k1.strand == k2.strand && k1.label == k2.label;
}
bool operator!=(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return ! (k1 == k2);
}
bool operator<(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return k1.label < k2.label || (k1.label == k2.label && k1.strand < k2.strand);
}
bool operator>(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return k1.label > k2.label || (k1.label == k2.label && k1.strand > k2.strand);
}
bool operator<=(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return ! (k1 > k2);
}
bool operator>=(const KmerStringAffect &k1, const KmerStringAffect &k2) {
return ! (k1 < k2);
}
ostream &operator<<(ostream &os, const KmerStringAffect &kmer) {
os << kmer.toString();
return os;
}
bool KmerStringAffect::hasRevcompSymetry() {
return false;
}
......@@ -227,4 +227,109 @@ const KmerAffect AFFECT_J_BWD = KmerAffect("J", -1);
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
* This class represents the affectation with a string for the label. Two
* constants are defined representing affectation that are either unknown
* (KSA_UNKNOWN) or ambiguous (KSA_AMBIGUOUS).
*/
class KmerStringAffect {
public:
string label;
int strand;
unsigned char length;
/**
* Construct an unknown affectation.
* @post isUnknown()
*/
KmerStringAffect();
KmerStringAffect(const KmerStringAffect &);
/**
* Construct an affectation as stated by the parameters
*/
KmerStringAffect(const string &label, int strand=1, unsigned char length=0);
/**
* Add another affectation to the current one.
* @post The current affectation is not modified if the parameter is the same
* affectation as the current one.
* If the current affectation is unknown, the affectation is set to the
* parameter.
* If the label is the same but the strand is different, the strand is
* arbitrarily put to forward.
* In the other cases, the affectation is set to ambiguous.
*/
KmerStringAffect &operator+=(const KmerStringAffect &);
/**
* Affectation
*/
KmerStringAffect &operator=(const KmerStringAffect &ka);
/**
* @return the ambiguous affectation
*/
static KmerStringAffect getAmbiguous();
/**
* @return the strand of the affectation
* -1 for backward
* 1 for forward
* 0 for unknown or ambiguous
*/
int getStrand() const;
/**
* @return the label of the affectation.
*/
string getLabel() const;
/**
* @return the length of such an affectation
*/
unsigned char getLength() const;
/**
* @return the unknown affectation
*/
static KmerStringAffect getUnknown();
/**
* @return true iff the class does not take care of the strand
* (false in our case).
*/
static bool hasRevcompSymetry();
/**
* @return true iff the affectation should be considered as ambiguous.
*/
bool isAmbiguous() const;
/**
* @return true iff the value is the same as the one given by default constructor
*/
bool isNull() const;
/**
* @return true iff the affectation is unkwown yet.
*/
bool isUnknown() const;
/**
* @return true if the K-mer is not odd (ambiguous or unknown)
*/
bool isGeneric() const;
string toString() const;
};
bool operator!=(const KmerStringAffect &k1, const KmerStringAffect &k2);
bool operator==(const KmerStringAffect &k1, const KmerStringAffect &k2);
bool operator<(const KmerStringAffect &k1, const KmerStringAffect &k2);
bool operator>(const KmerStringAffect &k1, const KmerStringAffect &k2);
bool operator<=(const KmerStringAffect &k1, const KmerStringAffect &k2);
bool operator>=(const KmerStringAffect &k1, const KmerStringAffect &k2);
ostream &operator<<(ostream &os, const KmerStringAffect &kmer);
const KmerStringAffect KSA_UNKNOWN = KmerStringAffect();
const KmerStringAffect KSA_AMBIGUOUS = KmerStringAffect("", 2);
#endif
......@@ -6,4 +6,4 @@ $ Clone 13 is correctly analyzed
$ Statistics on --analysis-filter
1:Statistics on filtered genes for clone analysis
rb1: IGH 70[0-9]{2}/ 16[0-9]{3} 43..%
rb1: IGH 37[0-9]{2}/ 16[0-9]{3} 22..%
......@@ -3,6 +3,7 @@
#include "core/germline.h"
#include "core/tools.h"
#include <algorithm>
#include <string>
/*
Create an artificial BioReader to experiment tests.
......@@ -148,13 +149,13 @@ vector<int> getDebugIndexes3(){
Check the integrity of the automatonBuilderFilteringBioReader
function. This test is separe in two parts. The first one
will check if the vector of indexes received is accurate
while the second part will check that every KmerAffect inside
while the second part will check that every KmerStringAffect inside
the revceivedAutomaton wear the good label.
*/
void testAutomatonBuilderFilteringBioReader(){
vector<int> *v1, *v2, *v3;
AbstractACAutomaton<KmerAffect> *a1, *a2, *a3;
KmerAffect tmpKmer;
AbstractACAutomaton<KmerStringAffect> *a1, *a2, *a3;
KmerStringAffect tmpKmer;
seqtype tmpSeq;
BioReader testedBioReader1;
BioReader testedBioReader2;
......@@ -164,8 +165,7 @@ void testAutomatonBuilderFilteringBioReader(){
vector<int> expectedIndexes3;
FilterWithACAutomaton *f1, *f2, *f3;
seqtype seq;
KmerAffect k;
char asciiChar;
KmerStringAffect k;
unsigned int asciiNum;
const string TEST_SIZE_ERROR =
......@@ -173,7 +173,7 @@ void testAutomatonBuilderFilteringBioReader(){
const string TEST_CONTENT_ERROR =
"The expected vector doesn't have the good content";
const string TEST_LABEL_ERROR =
"The KmerAffect doesn't have the good label";
"The KmerStringAffect doesn't have the good label";
testedBioReader1 = getDebugBioReader1();
testedBioReader2 = getDebugBioReader2();
......@@ -221,13 +221,12 @@ void testAutomatonBuilderFilteringBioReader(){
TEST_CONTENT_ERROR);
}
/* test automaton KmerAffect label */
/* test automaton KmerStringAffect label */
for(unsigned int i = 0, l = SPECIFIC_KMERS_NUMBER;i < expectedIndexes1.size() - 1; ++i, ++l){
for(int j = expectedIndexes1[i]; j < expectedIndexes1[i + 1]; ++j){
seq = testedBioReader1.sequence(j);
k = a1->get(seq);
asciiChar = k.getLabel().at(0);
asciiNum = int(asciiChar);
asciiNum = std::stoi(k.getLabel());
TAP_TEST_EQUAL(asciiNum, l, TEST_AUTOMATON_BUILDER_TO_FILTER_BIOREADER,
TEST_LABEL_ERROR);
}
......@@ -236,8 +235,7 @@ void testAutomatonBuilderFilteringBioReader(){
for(int j = expectedIndexes2[i]; j < expectedIndexes2[i + 1]; ++j){
seq = testedBioReader2.sequence(j);
k = a2->get(seq);
asciiChar = k.getLabel().at(0);
asciiNum = int(asciiChar);
asciiNum = std::stoi(k.getLabel());
TAP_TEST_EQUAL(asciiNum, l, TEST_AUTOMATON_BUILDER_TO_FILTER_BIOREADER,
TEST_LABEL_ERROR);
}
......@@ -246,8 +244,7 @@ void testAutomatonBuilderFilteringBioReader(){
for(int j = expectedIndexes3[i]; j < expectedIndexes3[i + 1]; ++j){
seq = testedBioReader3.sequence(j);
k = a3->get(seq);
asciiChar = k.getLabel().at(0);
asciiNum = int(asciiChar);
asciiNum = std::stoi(k.getLabel());
TAP_TEST_EQUAL(asciiNum, l, TEST_AUTOMATON_BUILDER_TO_FILTER_BIOREADER,
TEST_LABEL_ERROR);
}
......@@ -257,8 +254,8 @@ void testAutomatonBuilderFilteringBioReader(){
void testFilterBioReaderWithACAutomaton(){
vector<int> *v1, *v2, *v3;
AbstractACAutomaton<KmerAffect> *a1, *a2, *a3;
KmerAffect tmpKmer;
AbstractACAutomaton<KmerStringAffect> *a1, *a2, *a3;
KmerStringAffect tmpKmer;
seqtype sequence1, sequence2, sequence3;
BioReader testedBioReader1, testedBioReader2, testedBioReader3;
BioReader filteredBioReader1, filteredBioReader2, filteredBioReader3;
......@@ -302,40 +299,40 @@ void testFilterBioReaderWithACAutomaton(){
TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, SIZE_ERROR);
//check filtered BioReaders content
map<KmerAffect, int> m1 = a1->getMultiResults(sequence1);
map<KmerStringAffect, int> m1 = a1->getMultiResults(sequence1);
list<Sequence> l1 = filteredBioReader1.getAll();
for(auto const m : m1){
KmerAffect tmpKmer = m.first;
KmerStringAffect tmpKmer = m.first;
if(!tmpKmer.isGeneric()){
continue;
}
unsigned int asciiNumber = int(tmpKmer.getLabel().at(0));
unsigned int asciiNumber = std::stoi(tmpKmer.getLabel());
for(int i = v1->at(asciiNumber-SPECIFIC_KMERS_NUMBER); i < v1->at(asciiNumber-SPECIFIC_KMERS_NUMBER + 1); ++i){
TAP_TEST(find(l1.begin(), l1.end(), testedBioReader1.read(i)) != l1.end(),
TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, GENES_ERROR);
}
}
map<KmerAffect, int> m2 = a2->getMultiResults(sequence2);
map<KmerStringAffect, int> m2 = a2->getMultiResults(sequence2);
list<Sequence> l2 = filteredBioReader2.getAll();
for(auto const m : m2){
KmerAffect tmpKmer = m.first;
KmerStringAffect tmpKmer = m.first;
if(!tmpKmer.isGeneric()){
continue;
}
unsigned int asciiNumber = int(tmpKmer.getLabel().at(0));
unsigned int asciiNumber = std::stoi(tmpKmer.getLabel());
for(int i = v2->at(asciiNumber-SPECIFIC_KMERS_NUMBER); i < v2->at(asciiNumber-SPECIFIC_KMERS_NUMBER + 1); ++i){
TAP_TEST(find(l2.begin(), l2.end(), testedBioReader2.read(i)) != l2.end(),
TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, GENES_ERROR);
}
}
map<KmerAffect, int> m3 = a3->getMultiResults(sequence3);
map<KmerStringAffect, int> m3 = a3->getMultiResults(sequence3);
list<Sequence> l3 = filteredBioReader3.getAll();
for(auto const m : m3){
KmerAffect tmpKmer = m.first;
KmerStringAffect tmpKmer = m.first;
if(!tmpKmer.isGeneric()){
continue;
}
unsigned int asciiNumber = int(tmpKmer.getLabel().at(0));
unsigned int asciiNumber = std::stoi(tmpKmer.getLabel());
for(int i = v3->at(asciiNumber-SPECIFIC_KMERS_NUMBER); i < v3->at(asciiNumber-SPECIFIC_KMERS_NUMBER + 1); ++i){
TAP_TEST(find(l3.begin(), l3.end(), testedBioReader3.read(i)) != l3.end(),
TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, GENES_ERROR);
......@@ -465,23 +462,11 @@ void testExAequoKmersWhenSignificantParameter(){
delete f;
}
void testBehaviourWhenHugeBioReader(){
BioReader hugeBioReader;
FilterWithACAutomaton *f;
hugeBioReader.add("../../germline/homo-sapiens/IGHV.fa");
hugeBioReader.add("../../germline/homo-sapiens/IGLV.fa");
AbstractACAutomaton<KmerAffect>* automaton;
f = new FilterWithACAutomaton(hugeBioReader, "#########");
automaton = f->getAutomaton();
TAP_TEST(!automaton, TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON,
"Automaton should not be constructed on a BioReader containing more than 127 sequences.");
delete f;
}
/* Test the good behaviour of Filter's transferBioReaderSequences function. */
void testTransferBioReaderSequences(){
affect_t affect;
KmerAffect *kmer;
KmerStringAffect *kmer;
BioReader res, testedBioReader1;
FilterWithACAutomaton *f;
bool caught = false;
......@@ -490,23 +475,10 @@ void testTransferBioReaderSequences(){
const string ERROR_INCORRECT_BIOREADER = "The BioReader doesn't have the correct number of sequences.";
testedBioReader1 = getDebugBioReader1();
f = new FilterWithACAutomaton(testedBioReader1, "####");
affect.length = 1;
/* When k-mer's label has a n°ascii over 127, the transfer should not operate. */
affect.c = char(128);
kmer = new KmerAffect(affect);
try{
f->transferBioReaderSequences(testedBioReader1, res, *kmer);
}catch(...){
caught = true;
}
TAP_TEST(caught, TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, ERROR_NO_EXCEPTION_THROWN);
TAP_TEST_EQUAL(res.size(), 0, TEST_FILTER_BIOREADER_WITH_AC_AUTOMATON, ERROR_NON_EMPTY_BIOREADER);
delete kmer;
/* When k-mer's label has a n°ascii above the number of genes contained in the BioReader, the transfer should not operate. */
affect.c = char(8);
kmer = new KmerAffect(affect);
kmer = new KmerStringAffect("8", 0, 0);
try{
f->transferBioReaderSequences(testedBioReader1, res, *kmer);
caught = false;
......@@ -518,34 +490,27 @@ void testTransferBioReaderSequences(){
delete kmer;
/* When k-mer's label has the n°ascii 0, the transfer should not operate since it's an ambiguous k-mer. */
affect.c = AFFECT_AMBIGUOUS_CHAR;