Logo Search packages:      
Sourcecode: ugene version File versions

MuscleAdapter.cpp

/*****************************************************************
* Unipro UGENE - Integrated Bioinformatics Suite
* Copyright (C) 2008 Unipro, Russia (http://ugene.unipro.ru)
* All Rights Reserved
* 
*     This source code is distributed under the terms of the
*     GNU General Public License. See the files COPYING and LICENSE
*     for details.
*****************************************************************/

#include "MuscleAdapter.h"

#include "muscle/muscle.h"
#include "muscle/seqvect.h"
#include "muscle/distfunc.h"
#include "muscle/msa.h"
#include "muscle/tree.h"
#include "muscle/profile.h"
#include "muscle/muscle_context.h"

#include <core_api/DNAAlphabet.h>
#include <core_api/Task.h>
#include <util_algorithm/GAutoDeleteList.h>
#include <datatype/MAlignment.h>

#include <algorithm>
#include <QtCore/QVector>

namespace GB2 {

//////////////////////////////////////////////////////////////////////////
/// various helper methods and structures

00034 struct FILEStub : public FILE {
public:
    FILEStub(TaskStateInfo& _tsi) : tsi(_tsi){}
    TaskStateInfo& tsi;
};

static int ugene_printf(FILE *f, const char *format, ...) {
    if (format[0] <= 31 || strlen(format) == 1) {
        return 0;
    }
    char str[4096];
    va_list ArgList;
    va_start(ArgList, format);
    vsprintf(str, format, ArgList);

    FILEStub* s = (FILEStub*)f;
    s->tsi.stateDesc = (const char*)str;
    return 0;
}

class MuscleParamsHelper {
public:
    MuscleParamsHelper(TaskStateInfo& ti) : ugeneFileStub(ti) {
        MuscleContext *ctx = getMuscleContext();
        SetParams();

        //override some params. TODO: recheck possible conflict with SetPPScore() !
        ctx->progressPercent = &ti.progress;
        ctx->cancelFlag = &ti.cancelFlag;
        ctx->progress.g_fProgress = &ugeneFileStub; //log context
        ctx->progress.pr_printf = ugene_printf; //overriding logging
        ctx->params.g_uMaxMB = 0; //unlimited memory
        
        SetMaxIters(ctx->params.g_uMaxIters);
        SetStartTime();
    }
    ~MuscleParamsHelper() {
        MuscleContext *ctx = getMuscleContext();
        ctx->cancelFlag = &ctx->cancelStub;
        ctx->progressPercent = &ctx->progressStub;
        ctx->progress.pr_printf = fprintf;
        ctx->progress.g_fProgress = NULL;
    }

    FILEStub ugeneFileStub;
};

static ALPHA convertAlpha(DNAAlphabet* al) {
    if (al->isAmino()) {
        return ALPHA_Amino;
    }
    const QString& id = al->getId();
    if (id == BaseDNAAlphabetIds::NUCL_DNA_DEFAULT || id == BaseDNAAlphabetIds::NUCL_DNA_EXTENDED) {
        return ALPHA_DNA;
    }
    if (id == BaseDNAAlphabetIds::NUCL_RNA_DEFAULT || id == BaseDNAAlphabetIds::NUCL_RNA_EXTENDED) {
        return ALPHA_RNA;
    }
    return ALPHA_Undefined;
}

static void setupAlphaAndScore(DNAAlphabet* al, TaskStateInfo& ti) {
    ALPHA Alpha = convertAlpha(al);
    if (Alpha == ALPHA_Undefined) {
        ti.error = MuscleAdapter::tr("unsupported_alphabet_%1").arg(al->getName());
        return;
    }
    SetAlpha(Alpha);
    SetPPScore();
    if (ALPHA_DNA == Alpha || ALPHA_RNA == Alpha) {
        SetPPScore(PPSCORE_SPN);
    }
}

static void convertMAlignment2MSA(MSA& muscleMSA, const MAlignment& ma, bool fixAlpha) {
    assert(ma.isNormalized());
    for (int i=0, n = ma.getNumSequences(); i<n; i++) {
        const MAlignmentItem& aseq = ma.alignedSeqs[i];

        char* seq  = new char[aseq.sequence.length()+1];
        memcpy(seq, aseq.sequence.data(), aseq.sequence.length());
        seq[aseq.sequence.length()] = '\0';

        char* name = new char[aseq.name.length() + 1];
        memcpy(name, aseq.name.toLocal8Bit().data(), aseq.name.length());
        name[aseq.name.length()] = '\0';
        muscleMSA.AppendSeq(seq, aseq.sequence.length(), name);
    }
    if (fixAlpha) {
        muscleMSA.FixAlpha();
    }
}

static void convertMAlignment2SecVect(SeqVect& sv, const MAlignment& ma, bool fixAlpha) {
    sv.Clear();
    foreach(const MAlignmentItem& item, ma.alignedSeqs) {
        Seq *ptrSeq = new Seq();
        QByteArray name =  item.name.toAscii();
        ptrSeq->FromString(item.sequence.constData(), name.constData());
        //stripping gaps, original Seq::StripGaps fails on MSVC9
        Seq::iterator newEnd = std::remove(ptrSeq->begin(), ptrSeq->end(), MAlignment_GapChar);
        ptrSeq->erase(newEnd, ptrSeq->end());
        sv.push_back(ptrSeq);
    }
    if (fixAlpha) {
        sv.FixAlpha();
    }
}

static void convertMSA2MAlignment(MSA& msa, DNAAlphabet* al, MAlignment& res) {
    res.alphabet = al;
    for(int i=0, n = msa.GetSeqCount(); i < n; i++) {
        QString name = msa.GetSeqName(i);
        QByteArray seq;
        seq.reserve(msa.GetColCount());
        for (int j = 0, m = msa.GetColCount(); j < m ; j++) {
            char c = msa.GetChar(i, j);
            seq.append(c);
        }

        MAlignmentItem item(name, seq);
        res.alignedSeqs.append(item);        
    }
    assert(res.isNormalized());
}


//////////////////////////////////////////////////////////////////////////
/// align single MSA

00164 static void prepareAlignResults(MSA& msa, DNAAlphabet* al, MAlignment& ma, bool mhack) {
    if (mhack) {
        MHackEnd(msa);
    }
    MuscleContext* ctx = getMuscleContext();
    if (ctx->params.g_bStable) {
        MSA msaStable;
        Stabilize(msa, msaStable);
        msa.Clear();
        convertMSA2MAlignment(msaStable, al, ma);
    } else {
        convertMSA2MAlignment(msa, al, ma);
    }
}

void MuscleAdapter::align(const MAlignment& ma, MAlignment& res, TaskStateInfo& ti, bool mhack) {
    if(ti.cancelFlag)  {
        return;
    }
    try { 
        alignUnsafe(ma, res, ti, mhack);
    } catch (MuscleException e) {
        if (!ti.cancelFlag) {
            ti.error = tr("Internal MUSCLE error: %1").arg(e.str);
        }
    }
}

void MuscleAdapter::alignUnsafe(const MAlignment& ma, MAlignment& res, TaskStateInfo& ti, bool mhack) {
    ti.progress = 0;
    
    MuscleParamsHelper ph(ti);

    MuscleContext* ctx = getMuscleContext();
    SetSeqWeightMethod(ctx->params.g_SeqWeight1);

    setupAlphaAndScore(ma.alphabet, ti);
    if (ti.hasErrors()) {
        return;
    }

    SeqVect v;
    convertMAlignment2SecVect(v, ma, true);
    const unsigned uSeqCount = v.Length();
    if (0 == uSeqCount) {
        ti.error = tr("No sequences in input file");
        return;
    }

    unsigned uMaxL = 0;
    unsigned uTotL = 0;
    for (unsigned uSeqIndex = 0; uSeqIndex < uSeqCount; ++uSeqIndex) {
        unsigned L = v.GetSeq(uSeqIndex).Length();
        uTotL += L;
        uMaxL = qMax(uMaxL, L);
    }

    SetIter(1);
    ctx->params.g_bDiags = ctx->params.g_bDiags1;
    SetSeqStats(uSeqCount, uMaxL, uTotL/uSeqCount);

    SetMuscleSeqVect(v);

    MSA::SetIdCount(uSeqCount);

    // Initialize sequence ids.
    // From this point on, ids must somehow propagate from here.
    for (unsigned uSeqIndex = 0; uSeqIndex < uSeqCount; ++uSeqIndex) {
        v.SetSeqId(uSeqIndex, uSeqIndex);
    }

    if (0 == uSeqCount) {
        ti.error = tr("alignment_is_empty");
        return;
    }

    if (1 == uSeqCount) {
        res = ma;
        return;
    }

    if (uSeqCount > 1 && mhack) {
        MHackStart(v);
    }
    Tree GuideTree;
    TreeFromSeqVect(v, GuideTree, ctx->params.g_Cluster1, ctx->params.g_Distance1, ctx->params.g_Root1, ctx->params.g_pstrDistMxFileName1);
    
    SetMuscleTree(GuideTree);
    ValidateMuscleIds(GuideTree);

    MSA msa;
    gauto_array<ProgNode> ProgNodes;
    if (ctx->params.g_bLow) {
        ProgNodes.data = ProgressiveAlignE(v, GuideTree, msa);
    } else {
        ProgressiveAlign(v, GuideTree, msa);
    }

    if (ti.cancelFlag) {
        return;
    }

    SetCurrentAlignment(msa);
    
    ValidateMuscleIds(msa);

    if (1 == ctx->params.g_uMaxIters || 2 == uSeqCount) {
        assert(int(msa.GetSeqCount()) == ma.getNumSequences());
        prepareAlignResults(msa, ma.alphabet, res, mhack);
        return;
    }

    ti.progress = 25;
    
    if(ti.cancelFlag) {
        return;
    }

    if (0 == ctx->params.g_pstrUseTreeFileName)  {
        ctx->params.g_bDiags = ctx->params.g_bDiags2;
        SetIter(2);

        if (ctx->params.g_bLow) {
            if (0 != ctx->params.g_uMaxTreeRefineIters)
                RefineTreeE(msa, v, GuideTree, ProgNodes.get());
        } else {
            RefineTree(msa, GuideTree);
        }
    }
    if (ti.cancelFlag) {
        return;
    }

    SetSeqWeightMethod(ctx->params.g_SeqWeight2);
    SetMuscleTree(GuideTree);
    
    ti.progress = 45;
    
    if (ctx->params.g_bAnchors) {
        RefineVert(msa, GuideTree, ctx->params.g_uMaxIters - 2);
    } else {
        RefineHoriz(msa, GuideTree, ctx->params.g_uMaxIters - 2, false, false);
    }

    if (ti.cancelFlag) {
        return;
    }

    ValidateMuscleIds(msa);
    ValidateMuscleIds(GuideTree);

    assert(int(msa.GetSeqCount()) == ma.getNumSequences());
    
    prepareAlignResults(msa, ma.alphabet, res, mhack);
}

//////////////////////////////////////////////////////////////////////////
// refine single MSA

void MuscleAdapter::refine(const MAlignment& ma, MAlignment& res, TaskStateInfo& ti) {
    if(ti.cancelFlag)  {
        return;
    }
    try { 
        refineUnsafe(ma, res, ti);
    } catch (MuscleException e) {
        if (!ti.cancelFlag) {
            ti.error = tr("Internal MUSCLE error: %1").arg(e.str);
        }
    }
}

void MuscleAdapter::refineUnsafe(const MAlignment& ma, MAlignment& res, TaskStateInfo& ti) {
    assert(ma.isNormalized());
    
    ti.progress = 0;

    MuscleParamsHelper ph(ti);

    MuscleContext *ctx = getMuscleContext();
    SetSeqWeightMethod(ctx->params.g_SeqWeight1);

    setupAlphaAndScore(ma.alphabet, ti);
    if (ti.hasErrors()) {
        return;
    }
    MSA msa;
    convertMAlignment2MSA(msa, ma, true);
    unsigned uSeqCount = msa.GetSeqCount();
    MSA::SetIdCount(uSeqCount);

    // Initialize sequence ids.
    // From this point on, ids must somehow propogate from here.
    for (unsigned uSeqIndex = 0; uSeqIndex < uSeqCount; ++uSeqIndex) {
        msa.SetSeqId(uSeqIndex, uSeqIndex);
    }
    SetMuscleInputMSA(msa);

    Tree GuideTree;
    TreeFromMSA(msa, GuideTree, ctx->params.g_Cluster2, ctx->params.g_Distance2, ctx->params.g_Root2);
    SetMuscleTree(GuideTree);

    if (ctx->params.g_bAnchors) {
        RefineVert(msa, GuideTree, ctx->params.g_uMaxIters);
    } else {
        RefineHoriz(msa, GuideTree, ctx->params.g_uMaxIters, false, false);
    }

    ValidateMuscleIds(msa);
    ValidateMuscleIds(GuideTree);

    prepareAlignResults(msa, ma.alphabet, res, false);
}

//////////////////////////////////////////////////////////////////////////
/// align 2 profiles

//from profile.cpp
00382 static bool TreeNeededForWeighting(SEQWEIGHT s) {
    switch (s) {
        case SEQWEIGHT_ClustalW:
        case SEQWEIGHT_ThreeWay:
            return true;
        default:
            return false;
    }
}

static ProfPos *ProfileFromMSALocal_ProfileCPP(MSA &msa, Tree &tree) {
    MuscleContext *ctx = getMuscleContext();
    const unsigned uSeqCount = msa.GetSeqCount();
    for (unsigned uSeqIndex = 0; uSeqIndex < uSeqCount; ++uSeqIndex)
        msa.SetSeqId(uSeqIndex, uSeqIndex);

    if (TreeNeededForWeighting(ctx->params.g_SeqWeight2)) {
        TreeFromMSA(msa, tree, ctx->params.g_Cluster2, ctx->params.g_Distance2, ctx->params.g_Root1);
        SetMuscleTree(tree);
    }
    return ProfileFromMSA(msa);
}

void MuscleAdapter::align2Profiles(const MAlignment& ma1, const MAlignment& ma2, MAlignment& res, TaskStateInfo& ti) {
    if(ti.cancelFlag)  {
        return;
    }
    if (!ma1.isNormalized() || !ma2.isNormalized()) {
        ti.error = tr("Invalid input alignment");
        return;
    }
    try { 
        align2ProfilesUnsafe(ma1, ma2, res, ti);
    } catch (MuscleException e) {
        if (!ti.cancelFlag) {
            ti.error = tr("Internal MUSCLE error: %1").arg(e.str);
        }
    }
}

void MuscleAdapter::align2ProfilesUnsafe(const MAlignment& ma1, const MAlignment& ma2, MAlignment& res, TaskStateInfo& ti) {
    assert(ma1.isNormalized() && ma2.isNormalized());
    DNAAlphabet* al = DNAAlphabet::deriveCommonAlphabet(ma1.alphabet, ma2.alphabet);
    if (al == NULL) {
        ti.error = tr("Incompatible alphabets");
        return;
    }
    MuscleParamsHelper ph(ti);
    
    MuscleContext* ctx = getMuscleContext();    
    SetSeqWeightMethod(ctx->params.g_SeqWeight1);

    setupAlphaAndScore(al, ti);
    if (ti.hasErrors()) {
        return;
    }
    
    MSA msa1;
    convertMAlignment2MSA(msa1, ma1, true);
    MSA msa2;
    convertMAlignment2MSA(msa2, ma2, true);

    MSA::SetIdCount(ma1.getNumSequences() + ma2.getNumSequences());

    unsigned uLength1 = msa1.GetColCount();
    unsigned uLength2 = msa2.GetColCount();

    Tree tree1, tree2;
    gauto_array<ProfPos> Prof1(ProfileFromMSALocal_ProfileCPP(msa1, tree1));
    gauto_array<ProfPos> Prof2(ProfileFromMSALocal_ProfileCPP(msa2, tree2));
    gauto_array<ProfPos> ProfOut;

    PWPath Path; unsigned uLengthOut;
    ti.stateDesc = tr("Aligning profiles");
    AlignTwoProfs(Prof1.data, uLength1, 1.0, Prof2.data, uLength2, 1.0, Path, &ProfOut.data, &uLengthOut);
    
    ti.stateDesc = tr("Building output");
    MSA msaOut;
    AlignTwoMSAsGivenPath(Path, msa1, msa2, msaOut);
    msa1.Clear();//save memory
    msa2.Clear();

    //todo: stablize? -> original muscle fails if stablize is called
    convertMSA2MAlignment(msaOut, al, res);
}



//////////////////////////////////////////////////////////////////////////
// add unaligned sequences to profile

class AlignedSeq {
public:
    QString     name;
    QByteArray  seq;
    QByteArray  pathToMSA;
};


static QByteArray path2Str(const PWPath& path) {
    QByteArray res(path.GetEdgeCount(), '\0');
    for (int i = 0, n = path.GetEdgeCount(); i<n; i++) {
        const PWEdge &edge = path.GetEdge(i);
        res[i] = edge.cType;
    }
    return res;
}

//alters origPath according insertions made in msa (adjPath)
static void originalMSAToCurrent(const QByteArray& adjPath, const QByteArray& origPath, QByteArray& resPath) {
    assert(resPath.isEmpty());
    int aLen = adjPath.length();
    int oLen = origPath.length();
    int oPos = 0;
    int aPos = 0;
    for (;oPos < oLen || aPos < aLen; aPos++) {
        char oc = oPos < oLen ? origPath[oPos] : 'D';
        char ac = aPos < aLen ? adjPath[aPos]  : 'M';
        assert(ac != 'D'); //TODO: report error or check before the algorithm that MSA has no gap-cols
        if (ac == 'I' && oc == 'I') {
            resPath.append('M');
            oPos++;
            continue;
        }
        if (ac == 'I') {
            resPath.append('D');
            continue;
        }
        resPath.append(oc);
        oPos++;
    }
}

static void addSequenceToMSA(MAlignment& ma, const QByteArray& path, QByteArray& msaPathChanges, const QByteArray& seq, const QString& name) {
    assert(msaPathChanges.length() == ma.getLength());
    
    QVector<int> insCoords; //coords of gaps to be added to model
    QByteArray alignedSeq; //a sequence to be added to model
    int pathLen = path.size();
    int seqPos = 0;
    int seqLen = seq.length(); Q_UNUSED(seqLen);
    for(int pathPos = 0; pathPos < pathLen; pathPos++) {
        char c = path[pathPos];
        if (c == 'D') { //gap in seq
            alignedSeq.append((char)MAlignment_GapChar);
            continue;
        }
        //for 'M' or 'I' insert original char to seq        
        char sc = seq[seqPos];
        alignedSeq.append(sc);
        seqPos++;
        if (c =='I') { //insert gap to MSA
            insCoords.append(pathPos);
        } 
    }
    assert(seqPos == seqLen); //all seq symbols used
    int aseqLen = alignedSeq.length(); Q_UNUSED(aseqLen);
    int numIns = insCoords.size();
    int ma1Len = ma.getLength(); Q_UNUSED(ma1Len);
    assert(aseqLen == ma1Len + numIns);
    if (numIns != 0) {
        int prevLen = ma.getLength();
        int newLen = prevLen + numIns;
        QByteArray msaPathChangesNew;
        for (int i=0, n = ma.getNumSequences(); i < n; i++) {
            MAlignmentItem& mai = ma.alignedSeqs[i];
            const char* seq = mai.sequence.data();
            QByteArray newSeq;
            newSeq.reserve(newLen);
            int insCoordsPos = insCoords[0];
            int prevInsCoordsPos = -1;
            int insCoordsIdx = 0;
            for (int seqPos = 0; seqPos < prevLen; seqPos++) {
                if (seqPos == insCoordsPos) {
                    do { //add all sequential insertions
                        insCoordsIdx++;
                        prevInsCoordsPos = insCoordsPos;
                        insCoordsPos = insCoordsIdx < numIns ? insCoords[insCoordsIdx] : -1;
                        newSeq.append((char)MAlignment_GapChar);
                        if (i == 0) {
                            msaPathChangesNew.append('I');
                        }
                    } while (insCoordsPos == prevInsCoordsPos+1);
                } 
                newSeq.append(seq[seqPos]);
                if (i == 0) {
                    msaPathChangesNew.append(msaPathChanges[seqPos]);
                }
            }
            while (insCoordsIdx!=numIns) {
                insCoordsIdx++;
                newSeq.append((char)MAlignment_GapChar);
                if (i == 0) {
                    msaPathChangesNew.append('I');
                }
            }
            assert(newSeq.length() == newLen);
            mai.sequence = newSeq;
        }
        msaPathChanges.clear();
        msaPathChanges+=msaPathChangesNew;
        assert(ma.isNormalized());
        assert(msaPathChanges.length() == ma.getLength());
    }

    int ma2Len = ma.getLength(); Q_UNUSED(ma2Len);
    assert(aseqLen == ma2Len);  // gapped sequence has the same length as alignment

    ma.alignedSeqs.append(MAlignmentItem(name, alignedSeq));
    assert(ma.isNormalized());
}

void MuscleAdapter::addUnalignedSequencesToProfile(const MAlignment& ma, const MAlignment& unalignedSeqs, MAlignment& res, TaskStateInfo& ti) {
    if(ti.cancelFlag)  {
        return;
    }
    if (!ma.isNormalized()) {
        ti.error = tr("Profile is not aligned");
        return;
    }
    try { 
        addUnalignedSequencesToProfileUnsafe(ma, unalignedSeqs, res, ti);
    } catch (MuscleException e) {
        if (!ti.cancelFlag) {
            ti.error = tr("Internal MUSCLE error: %1").arg(e.str);
        }
    }
}


void MuscleAdapter::addUnalignedSequencesToProfileUnsafe(const MAlignment& ma, const MAlignment& unalignedSeqs, MAlignment& res, TaskStateInfo& ti) {
    assert(ma.isNormalized());

    DNAAlphabet* al = DNAAlphabet::deriveCommonAlphabet(ma.alphabet, unalignedSeqs.alphabet);
    if (al == NULL) {
        ti.error = tr("Incompatible alphabets");
        return;
    }
    
    // init muscle
    MuscleParamsHelper ph(ti);
    
    MuscleContext* ctx = getMuscleContext();
    SetSeqWeightMethod(ctx->params.g_SeqWeight1);
    
    setupAlphaAndScore(al, ti);
    if (ti.hasErrors()) {
        return;
    }
    
    MSA::SetIdCount(ma.getNumSequences() + 1);

    //prepare original MSA
    MSA profileMSA;
    convertMAlignment2MSA(profileMSA, ma, true);

    res = ma;
    
    //align with input sequences one by one
    Tree tree1;
    gauto_array<ProfPos> prof1(ProfileFromMSALocal_ProfileCPP(profileMSA, tree1));
    QVector<AlignedSeq> alignedSeqs;
    int dp = ti.progress;
    for (int i=0, n = unalignedSeqs.getNumSequences(); i < n; i++) {
        ti.stateDesc = tr("Aligning sequence %1 of %2").arg(QString::number(i+1)).arg(QString::number(n));
        ti.progress = dp + i*(95-dp)/n;
        const MAlignmentItem& useq = unalignedSeqs.alignedSeqs[i];
        Seq seq; seq.FromString(useq.sequence.data(), useq.name.toLocal8Bit().data());
        seq.SetId(0);
        seq.StripGaps();
        seq.FixAlpha();
        MSA  tmpMSA; tmpMSA.FromSeq(seq);

        Tree tree2;
        gauto_array<ProfPos> prof2(ProfileFromMSALocal_ProfileCPP(tmpMSA, tree2));
        gauto_array<ProfPos> profOut;

        PWPath path; unsigned uLengthOut;
        AlignTwoProfs(prof1.data, profileMSA.GetColCount(), 1.0, prof2.get(), tmpMSA.GetColCount(), 1.0, path, &profOut.data, &uLengthOut);
        
        AlignedSeq aseq;
        aseq.name = useq.name;
        aseq.seq = QByteArray((const char*)&seq.front(), seq.Length());//without gaps
        aseq.pathToMSA = path2Str(path);
        alignedSeqs.append(aseq);

        if (ti.cancelFlag) {
            return;
        }
    }

    //now add all sequences to the original MSA
    QByteArray changesToOriginalMSA(res.getLength(), 'M');  //path from original MSA to MSA with i seqs aligned
    QByteArray seqPath2CurrentMSA;
    for (int i=0, n = alignedSeqs.size(); i < n; i++) {
        if (i%10 == 9) {
            ti.stateDesc = tr("Merging results: %1 of %2").arg(QString::number(i+1)).arg(QString::number(n));
        }
        seqPath2CurrentMSA.clear();
        const AlignedSeq& aseq = alignedSeqs[i];
        originalMSAToCurrent(changesToOriginalMSA, aseq.pathToMSA, seqPath2CurrentMSA);
        addSequenceToMSA(res, seqPath2CurrentMSA, changesToOriginalMSA, aseq.seq, aseq.name);
        if (ti.cancelFlag) {
            res.clear();
            return;
        }
    }
    res.alphabet = al;
    assert(res.isNormalized());
}

} //namespace

Generated by  Doxygen 1.6.0   Back to index