source: tags/ms_r18q1/PROBE/PT_new_design.cxx

Last change on this file was 16766, checked in by westram, 6 years ago
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 54.2 KB
Line 
1// =============================================================== //
2//                                                                 //
3//   File      : PT_new_design.cxx                                 //
4//   Purpose   :                                                   //
5//                                                                 //
6//   Institute of Microbiology (Technical University Munich)       //
7//   http://www.arb-home.de/                                       //
8//                                                                 //
9// =============================================================== //
10
11
12#include "pt_split.h"
13#include <PT_server_prototypes.h>
14
15#include <struct_man.h>
16#include "probe_tree.h"
17#include "PT_prefixIter.h"
18
19#include <arb_str.h>
20#include <arb_defs.h>
21#include <arb_sort.h>
22
23#include <arb_strbuf.h>
24#include <arb_strarray.h>
25
26#include <climits>
27#include <algorithm>
28#include <map>
29#include <arb_progress.h>
30
31#if defined(DEBUG)
32// # define DUMP_DESIGNED_PROBES
33// # define DUMP_OLIGO_MATCHING
34#endif
35
36#define MIN_DESIGN_PROBE_LENGTH DOMAIN_MIN_LENGTH
37int Complement::calc_complement(int base) {
38    switch (base) {
39        case PT_A: return PT_T;
40        case PT_C: return PT_G;
41        case PT_G: return PT_C;
42        case PT_T: return PT_A;
43        default:   return base;
44    }
45}
46
47// overloaded functions to avoid problems with type-punning:
48inline void aisc_link(dll_public *dll, PT_tprobes *tprobe)   { aisc_link(reinterpret_cast<dllpublic_ext*>(dll), reinterpret_cast<dllheader_ext*>(tprobe)); }
49
50int pt_init_bond_matrix(PT_local *THIS) {
51    THIS->bond[0].val  = 0.0;
52    THIS->bond[1].val  = 0.0;
53    THIS->bond[2].val  = 0.5;
54    THIS->bond[3].val  = 1.1;
55    THIS->bond[4].val  = 0.0;
56    THIS->bond[5].val  = 0.0;
57    THIS->bond[6].val  = 1.5;
58    THIS->bond[7].val  = 0.0;
59    THIS->bond[8].val  = 0.5;
60    THIS->bond[9].val  = 1.5;
61    THIS->bond[10].val = 0.4;
62    THIS->bond[11].val = 0.9;
63    THIS->bond[12].val = 1.1;
64    THIS->bond[13].val = 0.0;
65    THIS->bond[14].val = 0.9;
66    THIS->bond[15].val = 0.0;
67
68    return 0;
69}
70
71struct dt_bondssum {
72    double dt;        // sum of mismatches
73    double sum_bonds; // sum of bonds of longest non mismatch string
74
75    dt_bondssum(double dt_, double sum_bonds_) : dt(dt_), sum_bonds(sum_bonds_) {}
76};
77
78class Oligo {
79    const char *data;
80    int         length;
81
82public:
83    Oligo() : data(NULp), length(0) {} // empty oligo
84    Oligo(const char *data_, int length_) : data(data_), length(length_) {}
85
86    char at(int offset) const {
87        pt_assert(offset >= 0 && offset<length);
88        return data[offset];
89    }
90    int size() const { return length; }
91
92    Oligo suffix(int skip) const {
93        return skip <= length ? Oligo(data+skip, length-skip) : Oligo();
94    }
95
96#if defined(DUMP_OLIGO_MATCHING)
97    void dump(FILE *out) const {
98        char *readable = readable_probe(data, length, 'T');
99        fputs(readable, out);
100        free(readable);
101    }
102#endif
103};
104
105class MatchingOligo {
106    Oligo oligo;
107    int   matched;       // how many positions matched
108    int   lastSplit;
109
110    bool   domain_found;
111    double maxDomainBondSum;
112
113    dt_bondssum linkage;
114
115    MatchingOligo(const MatchingOligo& other, double strength) // expand by 1 (with a split)
116        : oligo(other.oligo),
117          matched(other.matched+1),
118          domain_found(other.domain_found),
119          maxDomainBondSum(other.maxDomainBondSum),
120          linkage(other.linkage)
121    {
122        pt_assert(other.dangling());
123        pt_assert(strength<0.0);
124
125        lastSplit          = matched-1;
126        linkage.dt        -= strength;
127        linkage.sum_bonds  = 0.0;
128
129        pt_assert(domainLength() == 0);
130    }
131
132    MatchingOligo(const MatchingOligo& other, double strength, double max_bond) // expand by 1 (binding)
133        : oligo(other.oligo),
134          matched(other.matched+1),
135          domain_found(other.domain_found),
136          maxDomainBondSum(other.maxDomainBondSum),
137          linkage(other.linkage)
138    {
139        pt_assert(other.dangling());
140        pt_assert(strength >= 0.0);
141
142        lastSplit          = other.lastSplit;
143        linkage.dt        += strength;
144        linkage.sum_bonds += max_bond-strength;
145
146        pt_assert(linkage.sum_bonds >= 0.0);
147
148        if (domainLength() >= DOMAIN_MIN_LENGTH) {
149            domain_found     = true;
150            maxDomainBondSum = std::max(maxDomainBondSum, linkage.sum_bonds);
151        }
152    }
153
154    void accept_rest_dangling() {
155        pt_assert(dangling());
156        lastSplit = matched+1;
157        matched   = oligo.size();
158        pt_assert(!dangling());
159    }
160
161    void optimal_bind_rest(const Splits& splits) { // @@@ slow -> optimize
162        pt_assert(dangling());
163        while (dangling()) {
164            char   pc       = dangling_char();
165            double strength = splits.check(pc, pc); // @@@ always 0.0?
166            UNCOVERED();
167            pt_assert(strength >= 0.0);
168
169            double bondmax = splits.get_max_bond(pc);
170
171            matched++;
172            linkage.dt        += strength;
173            linkage.sum_bonds += bondmax-strength;
174        }
175        if (domainLength() >= DOMAIN_MIN_LENGTH) {
176            domain_found     = true;
177            maxDomainBondSum = std::max(maxDomainBondSum, linkage.sum_bonds);
178        }
179        pt_assert(!dangling());
180    }
181
182public:
183    explicit MatchingOligo(const Oligo& oligo_)
184        : oligo(oligo_),
185          matched(0),
186          lastSplit(-1),
187          domain_found(false),
188          maxDomainBondSum(-1),
189          linkage(0.0, 0.0)
190    {}
191
192    int dangling() const {
193        int dangle_count = oligo.size()-matched;
194        pt_assert(dangle_count >= 0);
195        return dangle_count;
196    }
197
198    char dangling_char() const {
199        pt_assert(dangling()>0);
200        return oligo.at(matched);
201    }
202
203    MatchingOligo bind_against(char c, const Splits& splits) const {
204        pt_assert(is_std_base_or_N(c));
205
206        char pc = dangling_char();
207        double strength;
208
209        if (c == PT_N) {
210            // we are checking outgroup-matches here => assume strongest possible bind versus N
211            strength = -100.0;
212            for (int base = PT_A; base < PT_BASES; ++base) {
213                strength = std::max(strength, splits.check(pc, base));
214            }
215            pt_assert(strength>-100.0);
216        }
217        else {
218            strength = splits.check(pc, c);
219        }
220
221        return strength<0.0
222            ? MatchingOligo(*this, strength)
223            : MatchingOligo(*this, strength, splits.get_max_bond(pc));
224    }
225
226    MatchingOligo dont_bind_rest() const {
227        MatchingOligo accepted(*this);
228        accepted.accept_rest_dangling();
229        return accepted;
230    }
231
232    int domainLength() const { return matched-(lastSplit+1); }
233
234    bool domainSeen() const { return domain_found; }
235    bool domainPossible() const {
236        pt_assert(!domainSeen()); // you are testing w/o need
237        return (domainLength()+dangling()) >= DOMAIN_MIN_LENGTH;
238    }
239
240    bool completely_bound() const { return !dangling(); }
241
242    int calc_centigrade_pos(const PT_tprobes *tprobe, const PT_pdc *const pdc) const {
243        pt_assert(completely_bound());
244        pt_assert(domainSeen());
245
246        double ndt = (linkage.dt * pdc->dt + (tprobe->sum_bonds - maxDomainBondSum)*pdc->dte) / tprobe->seq_len;
247        int    pos = (int)ndt;
248
249        pt_assert(pos >= 0);
250        return pos;
251    }
252
253    bool centigrade_pos_out_of_reach(const PT_tprobes *tprobe, const PT_pdc *const pdc, const Splits& splits) const {
254        MatchingOligo optimum(*this);
255        optimum.optimal_bind_rest(splits);
256
257        if (!optimum.domainSeen()) return true; // no domain -> no centigrade position
258
259        int centpos = optimum.calc_centigrade_pos(tprobe, pdc);
260        return centpos >= PERC_SIZE;
261    }
262
263    const Oligo& get_oligo() const { return oligo; }
264
265#if defined(DUMP_OLIGO_MATCHING)
266    void dump(FILE *out) const {
267        fputs("oligo='", out);
268        oligo.dump(out);
269
270        const char *domainState                = "impossible";
271        if (domainSeen()) domainState          = "seen";
272        else if (domainPossible()) domainState = "possible";
273
274        fprintf(out, "' matched=%2i lastSplit=%2i domainLength()=%2i dangling()=%2i domainState=%s maxDomainBondSum=%f dt=%f sum_bonds=%f\n",
275                matched, lastSplit, domainLength(), dangling(), domainState, maxDomainBondSum, linkage.dt, linkage.sum_bonds);
276    }
277#endif
278};
279
280static int ptnd_compare_quality(const void *vtp1, const void *vtp2, void *) {
281    const PT_tprobes *tp1 = (const PT_tprobes*)vtp1;
282    const PT_tprobes *tp2 = (const PT_tprobes*)vtp2;
283
284    int cmp = double_cmp(tp2->quality, tp1->quality);         // high quality first
285    if (!cmp) {
286        cmp           = tp1->apos - tp2->apos;                // low abs.pos first
287        if (!cmp) cmp = strcmp(tp1->sequence, tp2->sequence); // alphabetically by probe
288    }
289    return cmp;
290}
291
292static int ptnd_compare_sequence(const void *vtp1, const void *vtp2, void*) {
293    const PT_tprobes *tp1 = (const PT_tprobes*)vtp1;
294    const PT_tprobes *tp2 = (const PT_tprobes*)vtp2;
295
296    return strcmp(tp1->sequence, tp2->sequence);
297}
298
299enum ProbeSortMode {
300    PSM_QUALITY,
301    PSM_SEQUENCE,
302};
303
304static void sort_tprobes_by(PT_pdc *pdc, ProbeSortMode mode) {
305    if (pdc->tprobes) {
306        int list_len = pdc->tprobes->get_count();
307        if (list_len > 1) {
308            PT_tprobes **my_list = ARB_calloc<PT_tprobes*>(list_len);
309            {
310                PT_tprobes *tprobe;
311                int         i;
312
313                for (i = 0, tprobe = pdc->tprobes; tprobe; i++, tprobe = tprobe->next) {
314                    my_list[i] = tprobe;
315                }
316            }
317
318            switch (mode) {
319                case PSM_QUALITY:
320                    GB_sort((void **)my_list, 0, list_len, ptnd_compare_quality, NULp);
321                    break;
322
323                case PSM_SEQUENCE:
324                    GB_sort((void **)my_list, 0, list_len, ptnd_compare_sequence, NULp);
325                    break;
326            }
327
328            for (int i=0; i<list_len; i++) {
329                aisc_unlink(reinterpret_cast<dllheader_ext*>(my_list[i]));
330                aisc_link(&pdc->ptprobes, my_list[i]);
331            }
332
333            free(my_list);
334        }
335    }
336}
337static void clip_tprobes(PT_pdc *pdc, int count) {
338    PT_tprobes *tprobe;
339    int         i;
340
341    for (i=0, tprobe = pdc->tprobes;
342         tprobe && i< count;
343         i++, tprobe = tprobe->next) ;
344
345    if (tprobe) {
346        while (tprobe->next) {
347            destroy_PT_tprobes(tprobe->next);
348        }
349    }
350}
351static int pt_get_gc_content(char *probe) {
352    int gc = 0;
353    while (*probe) {
354        if (*probe == PT_G ||
355                *probe == PT_C) {
356            gc++;
357        }
358        probe ++;
359    }
360    return gc;
361}
362static double pt_get_temperature(const char *probe) {
363    int i;
364    double t = 0;
365    while ((i=*(probe++))) {
366        if (i == PT_G || i == PT_C) t+=4.0; else t += 2.0;
367    }
368    return t;
369}
370
371static void tprobes_sumup_perc_and_calc_quality(PT_pdc *pdc) {
372    for (PT_tprobes *tprobe = pdc->tprobes; tprobe; tprobe = tprobe->next) {
373        int sum = 0;
374        int i;
375        for (i=0; i<PERC_SIZE; ++i) {
376            sum             += tprobe->perc[i];
377            tprobe->perc[i]  = sum;
378        }
379
380        // pt_assert(tprobe->perc[0] == tprobe->mishits); // OutgroupMatcher and count_mishits_for_matched do not agree!
381        // @@@ found one case where it differs by 1 (6176 in perc, 6177 in mishits). Can this be caused somehow by N or IUPAC in seq?
382
383        int limit = 2*tprobe->mishits;
384        for (i=0; i<(PERC_SIZE-1); ++i) {
385            if (tprobe->perc[i]>limit) break;
386        }
387
388        tprobe->quality = ((double)tprobe->groupsize * i) + 1000.0/(1000.0 + tprobe->perc[i]);
389    }
390}
391
392static char hitgroup_idx2char(int idx) {
393    const int firstSmall = ALPHA_SIZE/2;
394    int c;
395    if (idx >= firstSmall) {
396        c = 'a'+(idx-firstSmall);
397    }
398    else {
399        c = 'A'+idx;
400    }
401    pt_assert(isalpha(c));
402    return c;
403}
404
405inline int get_max_probelen(const PT_pdc *pdc) {
406    return pdc->max_probelen == -1 ? pdc->min_probelen : pdc->max_probelen;
407}
408
409inline int shown_apos(const PT_tprobes *tprobe) { return info2bio(tprobe->apos); }
410inline int shown_ecoli(const PT_tprobes *tprobe) { return PT_abs_2_ecoli_rel(tprobe->apos+1); }
411inline int shown_qual(const PT_tprobes *tprobe) { return int(tprobe->quality+0.5); }
412
413class PD_formatter {
414    int width[PERC_SIZE]; // of centigrade list columns
415    int maxprobelen;
416
417    int apos_width;
418    int ecol_width;
419    int qual_width;
420    int grps_width;
421
422    static inline void track_max(int& tracked, int val) { tracked = std::max(tracked, val); }
423
424    void collect(const int *perc) { for (int i = 0; i<PERC_SIZE; ++i) width[i] = std::max(width[i], perc[i]); }
425    void collect(const PT_tprobes *tprobe) {
426        collect(tprobe->perc);
427        track_max(apos_width, shown_apos(tprobe));
428        track_max(ecol_width, shown_ecoli(tprobe));
429        track_max(qual_width, shown_qual(tprobe));
430        track_max(grps_width, shown_qual(tprobe));
431        track_max(maxprobelen, tprobe->seq_len);
432    }
433
434    void clear() {
435        for (int i = 0; i<PERC_SIZE; ++i) width[i] = 0;
436
437        apos_width = 0;
438        ecol_width = 0;
439        qual_width = 0;
440        grps_width = 0;
441
442        maxprobelen = 0;
443    }
444
445    static inline int width4num(int num) {
446        pt_assert(num >= 0);
447
448        int digits = 0;
449        while (num) {
450            ++digits;
451            num /= 10;
452        }
453        return digits ? digits : 1;
454    }
455
456public:
457    PD_formatter() { clear(); }
458    PD_formatter(const PT_pdc *pdc) {
459        clear();
460
461        // collect max values for output columns:
462        for (PT_tprobes *tprobe = pdc->tprobes; tprobe; tprobe = tprobe->next) collect(tprobe);
463
464        // convert max.values to needed print-width:
465        for (int i = 0; i<PERC_SIZE; ++i) width[i] = width4num(width[i]);
466
467        apos_width = std::max(width4num(apos_width), 2);
468        ecol_width = std::max(width4num(ecol_width), 4);
469        qual_width = std::max(width4num(qual_width), 4);
470        grps_width = std::max(width4num(grps_width), 4);
471    }
472
473    int sprint_centigrade_list(char *buffer, const int *perc) const {
474        // format centigrade-decrement-list
475        int printed = 0;
476        for (int i = 0; i<PERC_SIZE; ++i) {
477            buffer[printed++]  = ' ';
478            printed += perc[i]
479                ? sprintf(buffer+printed, "%*i", width[i], perc[i])
480                : sprintf(buffer+printed, "%*s", width[i], "-");
481        }
482        return printed;
483    }
484
485    int get_max_designed_len() const { return maxprobelen; }
486
487    int get_apos_width() const { return apos_width; }
488    int get_ecol_width() const { return ecol_width; }
489    int get_qual_width() const { return qual_width; }
490    int get_grps_width() const { return grps_width; }
491};
492
493typedef std::map<const PT_pdc*const, PD_formatter> PD_Formatter4design;
494static PD_Formatter4design format4design;
495
496inline const PD_formatter& get_formatter(const PT_pdc *pdc) {
497    PD_Formatter4design::iterator found = format4design.find(pdc);
498    if (found == format4design.end()) {
499        format4design[pdc] = PD_formatter(pdc);
500        found              = format4design.find(pdc);
501    }
502    pt_assert(found != format4design.end());
503
504    return found->second;
505}
506inline void erase_formatter(const PT_pdc *pdc) { format4design.erase(pdc); }
507
508char *get_design_info(const PT_tprobes *tprobe) {
509    pt_assert(tprobe);
510
511    const int            BUFFERSIZE = 2000;
512    char                *buffer     = (char *)GB_give_buffer(BUFFERSIZE);
513    PT_pdc              *pdc        = (PT_pdc *)tprobe->mh.parent->parent;
514    char                *p          = buffer;
515    const PD_formatter&  formatter  = get_formatter(pdc);
516
517    // target
518    {
519        int   len   = tprobe->seq_len;
520        char *probe = (char *)GB_give_buffer2(len+1);
521        strcpy(probe, tprobe->sequence);
522
523        probe_2_readable(probe, len); // convert probe to real ASCII
524        p += sprintf(p, "%-*s", formatter.get_max_designed_len()+1, probe);
525    }
526
527    {
528        int apos = shown_apos(tprobe);
529        int c;
530        int cs   = 0;
531
532        // search nearest previous hit
533        for (c=0; c<ALPHA_SIZE; c++) {
534            if (!pdc->pos_groups[c]) { // new group
535                pdc->pos_groups[c] = apos;
536                cs                 = '=';
537                break;
538            }
539            int dist = abs(apos - pdc->pos_groups[c]);
540            if (dist < pdc->min_probelen) {
541                apos = apos - pdc->pos_groups[c];
542                if (apos >= 0) {
543                    cs = '+';
544                    break;
545                }
546                cs = '-';
547                apos = -apos;
548                break;
549            }
550        }
551
552        if (cs) {
553            c  = hitgroup_idx2char(c);
554        }
555        else {
556            // ran out of pos_groups
557            c  = '?';
558            cs = ' ';
559        }
560
561        // le apos ecol
562        p += sprintf(p, "%2i ", tprobe->seq_len);
563        p += sprintf(p, "%c%c%*i ", c, cs, formatter.get_apos_width(), apos);
564        p += sprintf(p, "%*i ", formatter.get_ecol_width(), shown_ecoli(tprobe)); // ecoli-bases inclusive apos ("bases before apos+1")
565    }
566
567    p += sprintf(p, "%*i ", formatter.get_qual_width(), shown_qual(tprobe));
568    p += sprintf(p, "%*i ", formatter.get_grps_width(), tprobe->groupsize);
569    p += sprintf(p, "%5.1f", ((double)pt_get_gc_content(tprobe->sequence))/tprobe->seq_len*100.0); // G+C
570    p += sprintf(p, "%5.1f ", pt_get_temperature(tprobe->sequence));                               // temperature
571
572    // probe string
573    {
574        char *probe = create_reversed_probe(tprobe->sequence, tprobe->seq_len);
575        complement_probe(probe, tprobe->seq_len);
576        probe_2_readable(probe, tprobe->seq_len); // convert probe to real ASCII
577        p += sprintf(p, "%*s |", formatter.get_max_designed_len(), probe);
578        free(probe);
579    }
580
581    // non-group hits by temp. decrease
582    p += formatter.sprint_centigrade_list(p, tprobe->perc);
583    if (!tprobe->next) erase_formatter(pdc); // erase formatter when done with last probe
584
585    pt_assert((p-buffer)<BUFFERSIZE);
586    return buffer;
587}
588
589char *get_design_hinfo(const PT_pdc *pdc) {
590    const int  BUFFERSIZE = 2000;
591    char      *buffer     = (char *)GB_give_buffer(BUFFERSIZE);
592    char      *s          = buffer;
593
594    const PD_formatter& formatter = get_formatter(pdc);
595
596    {
597        char *ecolipos = NULp;
598        if (pdc->min_ecolipos == -1) {
599            if (pdc->max_ecolipos == -1) {
600                ecolipos = ARB_strdup("any");
601            }
602            else {
603                ecolipos = GBS_global_string_copy("<= %i", pdc->max_ecolipos);
604            }
605        }
606        else {
607            if (pdc->max_ecolipos == -1) {
608                ecolipos = GBS_global_string_copy(">= %i", pdc->min_ecolipos);
609            }
610            else {
611                ecolipos = GBS_global_string_copy("%4i -%5i", pdc->min_ecolipos, pdc->max_ecolipos);
612            }
613        }
614
615        char *mishit_annotation = NULp;
616        if (pdc->min_rj_mishits<INT_MAX) {
617            mishit_annotation = GBS_global_string_copy(" (lowest rejected nongroup hits: %i)", pdc->min_rj_mishits);
618        }
619
620        char *coverage_annotation = NULp;
621        if (pdc->max_rj_coverage>0.0) {
622            coverage_annotation = GBS_global_string_copy(" (max. rejected coverage: %.0f%%)", pdc->max_rj_coverage*100.0);
623        }
624
625        char *probelen_info;
626        if (get_max_probelen(pdc) == pdc->min_probelen) {
627            probelen_info = GBS_global_string_copy("%i", pdc->min_probelen);
628        }
629        else {
630            probelen_info = GBS_global_string_copy("%i-%i", pdc->min_probelen, get_max_probelen(pdc));
631        }
632
633        s += sprintf(s,
634                     "Probe design parameters:\n"
635                     "Length of probe    %s\n"
636                     "Temperature        [%4.1f -%5.1f]\n"
637                     "GC-content         [%4.1f -%5.1f]\n"
638                     "E.Coli position    [%s]\n"
639                     "Max. nongroup hits %i%s\n"
640                     "Min. group hits    %.0f%%%s\n",
641                     probelen_info,
642                     pdc->mintemp, pdc->maxtemp,
643                     pdc->min_gc*100.0, pdc->max_gc*100.0,
644                     ecolipos,
645                     pdc->max_mishits,        mishit_annotation   ? mishit_annotation   : "",
646                     pdc->min_coverage*100.0, coverage_annotation ? coverage_annotation : "");
647
648        free(probelen_info);
649        free(coverage_annotation);
650        free(mishit_annotation);
651
652        free(ecolipos);
653    }
654
655    if (pdc->tprobes) {
656        int maxprobelen = formatter.get_max_designed_len();
657
658        s += sprintf(s, "%-*s", maxprobelen+1, "Target");
659        s += sprintf(s, "le ");
660        s += sprintf(s, "%*s ", formatter.get_apos_width()+2, "apos");
661        s += sprintf(s, "%*s ", formatter.get_ecol_width(), "ecol");
662        s += sprintf(s, "%*s ", formatter.get_qual_width(), "qual");
663        s += sprintf(s, "%*s ", formatter.get_grps_width(), "grps");
664        s += sprintf(s, "  G+C temp ");
665        s += sprintf(s, "%*s | ", maxprobelen, maxprobelen<14 ? "Probe" : "Probe sequence");
666        s += sprintf(s, "Decrease T by n*.3C -> probe matches n non group species");
667    }
668    else {
669        s += sprintf(s, "No probes found!");
670        erase_formatter(pdc);
671    }
672
673    pt_assert((s-buffer)<BUFFERSIZE);
674
675    return buffer;
676}
677
678struct ptnd_chain_count_mishits {
679    int         mishits;
680    const char *probe;
681    int         height;
682
683    ptnd_chain_count_mishits() : mishits(0), probe(NULp), height(0) {}
684    ptnd_chain_count_mishits(const char *probe_, int height_) : mishits(0), probe(probe_), height(height_) {}
685
686    int operator()(const AbsLoc& probeLoc) {
687        // count all mishits for a probe
688
689        psg.abs_pos.announce(probeLoc.get_abs_pos());
690
691        if (probeLoc.get_pid().outside_group()) {
692            if (probe) {
693                pt_assert(probe[0]); // if this case occurs, avoid entering this branch
694
695                DataLoc         dataLoc(probeLoc);
696                ReadableDataLoc readableLoc(dataLoc);
697                for (int i = 0; probe[i] && readableLoc[height+i]; ++i) {
698                    if (probe[i] != readableLoc[height+i]) return 0;
699                }
700            }
701            mishits++;
702        }
703        return 0;
704    }
705};
706
707static int count_mishits_for_all(POS_TREE2 *pt) {
708    //! go down the tree to chains and leafs; count the species that are in the non member group
709    if (!pt)
710        return 0;
711
712    if (pt->is_leaf()) {
713        DataLoc loc(pt);
714        psg.abs_pos.announce(loc.get_abs_pos());
715        return loc.get_pid().outside_group();
716    }
717
718    if (pt->is_chain()) {
719        ptnd_chain_count_mishits counter;
720        PT_forwhole_chain(pt, counter); // @@@ expand
721        return counter.mishits;
722    }
723
724    int mishits = 0;
725    for (int base = PT_QU; base< PT_BASES; base++) {
726        mishits += count_mishits_for_all(PT_read_son(pt, (PT_base)base));
727    }
728    return mishits;
729}
730
731static int count_mishits_for_matched(char *probe, POS_TREE2 *pt, int height) {
732    //! search down the tree to find matching species for the given probe
733    if (!pt) return 0;
734    if (pt->is_node() && *probe) {
735        int mishits = 0;
736        for (int i=PT_A; i<PT_BASES; i++) {
737            if (i != *probe) continue;
738            POS_TREE2 *pthelp = PT_read_son(pt, (PT_base)i);
739            if (pthelp) mishits += count_mishits_for_matched(probe+1, pthelp, height+1);
740        }
741        return mishits;
742    }
743    if (*probe) {
744        if (pt->is_leaf()) {
745            const ReadableDataLoc loc(pt);
746
747            int pos = loc.get_rel_pos()+height;
748
749            if (pos + (int)(strlen(probe)) >= loc.get_pid().get_size()) // after end // @@@ wrong check ? better return from loop below when ref is PT_QU
750                return 0;
751
752            for (int i = 0; probe[i] && loc[height+i]; ++i) {
753                if (probe[i] != loc[height+i]) {
754                    return 0;
755                }
756            }
757        }
758        else {                // chain
759            ptnd_chain_count_mishits counter(probe, height);
760            PT_forwhole_chain(pt, counter); // @@@ expand
761            return counter.mishits;
762        }
763    }
764    return count_mishits_for_all(pt);
765}
766
767static void remove_tprobes_with_too_many_mishits(PT_pdc *pdc) {
768    //! Check for direct mishits
769    // tracks minimum rejected mishits in 'pdc->min_rj_mishits'
770    int min_rej_mishit_amount = INT_MAX;
771
772    for (PT_tprobes *tprobe = pdc->tprobes; tprobe; ) {
773        PT_tprobes *tprobe_next = tprobe->next;
774
775        psg.abs_pos.clear();
776        tprobe->mishits = count_mishits_for_matched(tprobe->sequence, psg.TREE_ROOT2(), 0);
777        tprobe->apos    = psg.abs_pos.get_most_used();
778        if (tprobe->mishits > pdc->max_mishits) {
779            min_rej_mishit_amount = std::min(min_rej_mishit_amount, tprobe->mishits);
780            destroy_PT_tprobes(tprobe);
781        }
782        tprobe = tprobe_next;
783    }
784    psg.abs_pos.clear();
785
786    pdc->min_rj_mishits = std::min(pdc->min_rj_mishits, min_rej_mishit_amount);
787}
788
789static void remove_tprobes_outside_ecoli_range(PT_pdc *pdc) {
790    //! Check the probes position.
791    // if (pdc->min_ecolipos == pdc->max_ecolipos) return; // @@@ wtf was this for? 
792
793    for (PT_tprobes *tprobe = pdc->tprobes; tprobe; ) {
794        PT_tprobes *tprobe_next = tprobe->next;
795        long relpos = PT_abs_2_ecoli_rel(tprobe->apos+1);
796        if (relpos < pdc->min_ecolipos || (relpos > pdc->max_ecolipos && pdc->max_ecolipos != -1)) {
797            destroy_PT_tprobes(tprobe);
798        }
799        tprobe = tprobe_next;
800    }
801}
802
803static size_t tprobes_calculate_bonds(PT_local *locs) {
804    /*! check the average bond size.
805     *  returns number of tprobes
806     *
807     * @TODO  checks probe hairpin bonds
808     */
809
810    PT_pdc *pdc   = locs->pdc;
811    size_t  count = 0;
812
813    MaxBond mbond(locs->bond);
814
815    for (PT_tprobes *tprobe = pdc->tprobes; tprobe; ) {
816        PT_tprobes *tprobe_next = tprobe->next;
817        tprobe->seq_len = strlen(tprobe->sequence);
818        double sbond = 0.0;
819        for (int i=0; i<tprobe->seq_len; i++) {
820            sbond += mbond.get_max_bond(tprobe->sequence[i]);
821        }
822        tprobe->sum_bonds = sbond;
823
824        ++count;
825        tprobe = tprobe_next;
826    }
827    return count;
828}
829
830class CentigradePos { // track minimum centigrade position for each species
831    typedef std::map<int, int> Pos4Name;
832
833    Pos4Name cpos; // key = outgroup-species-id; value = min. centigrade position for (partial) probe
834
835public:
836
837    static bool is_valid_pos(int pos) { return pos >= 0 && pos<PERC_SIZE; }
838
839    void set_pos_for(int name, int pos) {
840        pt_assert(is_valid_pos(pos)); // otherwise it should not be put into CentigradePos
841        Pos4Name::iterator found = cpos.find(name);
842        if (found == cpos.end()) {
843            cpos[name] = pos;
844        }
845        else if (pos<found->second) {
846            found->second = pos;
847        }
848    }
849
850    void summarize_centigrade_hits(int *centigrade_hits) const {
851        for (int i = 0; i<PERC_SIZE; ++i) centigrade_hits[i] = 0;
852        for (Pos4Name::const_iterator p = cpos.begin(); p != cpos.end(); ++p) {
853            int pos  = p->second;
854            pt_assert(is_valid_pos(pos)); // otherwise it should not be put into CentigradePos
855            centigrade_hits[pos]++;
856        }
857    }
858
859    bool empty() const { return cpos.empty(); }
860};
861
862class OutgroupMatcher : virtual Noncopyable {
863    const PT_pdc *const pdc;
864
865    Splits splits;
866
867    PT_tprobes    *currTprobe;
868    CentigradePos  result;
869
870    bool only_bind_behind_dot; // true for suffix-matching
871
872    void uncond_announce_match(int centPos, const AbsLoc& loc) {
873        pt_assert(loc.get_pid().outside_group());
874#if defined(DUMP_OLIGO_MATCHING)
875        fprintf(stderr, "announce_match centPos=%2i loc", centPos);
876        loc.dump(stderr);
877        fflush_all();
878#endif
879        result.set_pos_for(loc.get_name(), centPos);
880    }
881
882    bool location_follows_dot(const ReadableDataLoc& loc) const { return loc[-1] == PT_QU; }
883    bool location_follows_dot(const DataLoc& loc) const { return location_follows_dot(ReadableDataLoc(loc)); } // very expensive @@@ optimize using relpos
884    bool location_follows_dot(const AbsLoc& loc) const { return location_follows_dot(ReadableDataLoc(DataLoc(loc))); } // very very expensive
885
886    template<typename LOC>
887    bool acceptable_location(const LOC& loc) const {
888        return loc.get_pid().outside_group() &&
889            (only_bind_behind_dot ? location_follows_dot(loc) : true);
890    }
891
892    template<typename LOC>
893    void announce_match_if_acceptable(int centPos, const LOC& loc) {
894        if (acceptable_location(loc)) {
895            uncond_announce_match(centPos, loc);
896        }
897    }
898    void announce_match_if_acceptable(int centPos, POS_TREE2 *pt) {
899        switch (pt->get_type()) {
900            case PT2_NODE:
901                for (int i = PT_QU; i<PT_BASES; ++i) {
902                    POS_TREE2 *ptson = PT_read_son(pt, (PT_base)i);
903                    if (ptson) {
904                        announce_match_if_acceptable(centPos, ptson);
905                    }
906                }
907                break;
908
909            case PT2_LEAF:
910                announce_match_if_acceptable(centPos, DataLoc(pt));
911                break;
912
913            case PT2_CHAIN: {
914                ChainIteratorStage2 iter(pt);
915                while (iter) {
916                    announce_match_if_acceptable(centPos, iter.at());
917                    ++iter;
918                }
919                break;
920            }
921        }
922    }
923
924    template <typename PT_OR_LOC>
925    void announce_possible_match(const MatchingOligo& oligo, PT_OR_LOC pt_or_loc) {
926        pt_assert(oligo.domainSeen());
927        pt_assert(!oligo.dangling());
928
929        int centPos = oligo.calc_centigrade_pos(currTprobe, pdc);
930        if (centPos<PERC_SIZE) {
931            announce_match_if_acceptable(centPos, pt_or_loc);
932        }
933    }
934
935    bool might_reach_centigrade_pos(const MatchingOligo& oligo) const {
936        return !oligo.centigrade_pos_out_of_reach(currTprobe, pdc, splits);
937    }
938
939    void bind_rest(const MatchingOligo& oligo, const ReadableDataLoc& loc, const int height) {
940        // unconditionally bind rest of oligo versus sequence
941
942        pt_assert(oligo.domainSeen());                // entry-invariant for bind_rest()
943        pt_assert(oligo.dangling());                  // otherwise ReadableDataLoc is constructed w/o need (very expensive!)
944        pt_assert(might_reach_centigrade_pos(oligo)); // otherwise ReadableDataLoc is constructed w/o need (very expensive!)
945        pt_assert(loc.get_pid().outside_group());     // otherwise we are not interested in the result
946
947        if (loc[height]) {
948            MatchingOligo more = oligo.bind_against(loc[height], splits);
949            pt_assert(more.domainSeen()); // implied by oligo.domainSeen()
950            if (more.dangling()) {
951                if (might_reach_centigrade_pos(more)) {
952                    bind_rest(more, loc, height+1);
953                }
954            }
955            else {
956                announce_possible_match(more, loc);
957            }
958        }
959        else {
960            MatchingOligo all = oligo.dont_bind_rest();
961            announce_possible_match(all, loc);
962        }
963    }
964    void bind_rest_if_outside_group(const MatchingOligo& oligo, const DataLoc& loc, const int height) {
965        if (loc.get_pid().outside_group() && might_reach_centigrade_pos(oligo)) {
966            bind_rest(oligo, ReadableDataLoc(loc), height);
967        }
968    }
969    void bind_rest_if_outside_group(const MatchingOligo& oligo, const AbsLoc&  loc, const int height) {
970        if (loc.get_pid().outside_group() && might_reach_centigrade_pos(oligo)) {
971            bind_rest(oligo, ReadableDataLoc(DataLoc(loc)), height);
972        }
973    }
974
975    void bind_rest(const MatchingOligo& oligo, POS_TREE2 *pt, const int height) {
976        // unconditionally bind rest of oligo versus complete index-subtree
977        pt_assert(oligo.domainSeen()); // entry-invariant for bind_rest()
978
979        if (oligo.dangling()) {
980            if (might_reach_centigrade_pos(oligo)) {
981                switch (pt->get_type()) {
982                    case PT2_NODE: {
983                        POS_TREE2 *ptdotson = PT_read_son(pt, PT_QU);
984                        if (ptdotson) {
985                            MatchingOligo all = oligo.dont_bind_rest();
986                            announce_possible_match(all, ptdotson);
987                        }
988
989                        for (int i = PT_N; i<PT_BASES; ++i) {
990                            POS_TREE2 *ptson = PT_read_son(pt, (PT_base)i);
991                            if (ptson) {
992                                bind_rest(oligo.bind_against(i, splits), ptson, height+1);
993                            }
994                        }
995                        break;
996                    }
997                    case PT2_LEAF:
998                        bind_rest_if_outside_group(oligo, DataLoc(pt), height);
999                        break;
1000
1001                    case PT2_CHAIN:
1002                        ChainIteratorStage2 iter(pt);
1003                        while (iter) {
1004                            bind_rest_if_outside_group(oligo, iter.at(), height);
1005                            ++iter;
1006                        }
1007                        break;
1008                }
1009            }
1010        }
1011        else {
1012            announce_possible_match(oligo, pt);
1013        }
1014    }
1015
1016    void bind_till_domain(const MatchingOligo& oligo, const ReadableDataLoc& loc, const int height) {
1017        pt_assert(!oligo.domainSeen() && oligo.domainPossible()); // entry-invariant for bind_till_domain()
1018        pt_assert(oligo.dangling());                              // otherwise ReadableDataLoc is constructed w/o need (very expensive!)
1019        pt_assert(might_reach_centigrade_pos(oligo));             // otherwise ReadableDataLoc is constructed w/o need (very expensive!)
1020        pt_assert(loc.get_pid().outside_group());                 // otherwise we are not interested in the result
1021
1022        if (is_std_base_or_N(loc[height])) { // do not try to bind domain versus dot
1023            MatchingOligo more = oligo.bind_against(loc[height], splits);
1024            if (more.dangling()) {
1025                if (might_reach_centigrade_pos(more)) {
1026                    if      (more.domainSeen())     bind_rest       (more, loc, height+1);
1027                    else if (more.domainPossible()) bind_till_domain(more, loc, height+1);
1028                }
1029            }
1030            else if (more.domainSeen()) {
1031                announce_possible_match(more, loc);
1032            }
1033        }
1034    }
1035    void bind_till_domain_if_outside_group(const MatchingOligo& oligo, const DataLoc& loc, const int height) {
1036        if (loc.get_pid().outside_group() && might_reach_centigrade_pos(oligo)) {
1037            bind_till_domain(oligo, ReadableDataLoc(loc), height);
1038        }
1039    }
1040    void bind_till_domain_if_outside_group(const MatchingOligo& oligo, const AbsLoc&  loc, const int height) {
1041        if (loc.get_pid().outside_group() && might_reach_centigrade_pos(oligo)) {
1042            bind_till_domain(oligo, ReadableDataLoc(DataLoc(loc)), height);
1043        }
1044    }
1045
1046    void bind_till_domain(const MatchingOligo& oligo, POS_TREE2 *pt, const int height) {
1047        pt_assert(!oligo.domainSeen() && oligo.domainPossible()); // entry-invariant for bind_till_domain()
1048        pt_assert(oligo.dangling());
1049
1050        if (might_reach_centigrade_pos(oligo)) {
1051            switch (pt->get_type()) {
1052                case PT2_NODE:
1053                    for (int i = PT_A; i<PT_BASES; ++i) {
1054                        POS_TREE2 *ptson = PT_read_son(pt, (PT_base)i);
1055                        if (ptson) {
1056                            MatchingOligo sonOligo = oligo.bind_against(i, splits);
1057
1058                            if      (sonOligo.domainSeen())     bind_rest       (sonOligo, ptson, height+1);
1059                            else if (sonOligo.domainPossible()) bind_till_domain(sonOligo, ptson, height+1);
1060                        }
1061                    }
1062                    break;
1063
1064                case PT2_LEAF:
1065                    bind_till_domain_if_outside_group(oligo, DataLoc(pt), height);
1066                    break;
1067
1068                case PT2_CHAIN:
1069                    ChainIteratorStage2 iter(pt);
1070                    while (iter) {
1071                        bind_till_domain_if_outside_group(oligo, iter.at(), height);
1072                        ++iter;
1073                    }
1074                    break;
1075            }
1076        }
1077    }
1078
1079    void reset() { result = CentigradePos(); }
1080
1081public:
1082    OutgroupMatcher(PT_local *locs_, PT_pdc *pdc_)
1083        : pdc(pdc_),
1084          splits(locs_),
1085          currTprobe(NULp),
1086          only_bind_behind_dot(false)
1087    {}
1088
1089    void calculate_outgroup_matches(PT_tprobes& tprobe) {
1090        LocallyModify<PT_tprobes*> assign_tprobe(currTprobe, &tprobe);
1091        pt_assert(result.empty());
1092
1093        MatchingOligo  fullProbe(Oligo(tprobe.sequence, tprobe.seq_len));
1094        POS_TREE2     *ptroot = psg.TREE_ROOT2();
1095
1096        pt_assert(!fullProbe.domainSeen());
1097        pt_assert(fullProbe.domainPossible()); // probe length too short (probes shorter than DOMAIN_MIN_LENGTH always report 0 outgroup hits -> wrong result)
1098
1099        arb_progress progress(tprobe.seq_len);
1100
1101        only_bind_behind_dot = false;
1102        bind_till_domain(fullProbe, ptroot, 0);
1103        ++progress;
1104
1105        // match all suffixes of probe
1106        // - detect possible partial matches at start of alignment (and behind dots inside the sequence)
1107        only_bind_behind_dot = true;
1108        for (int off = 1; off<tprobe.seq_len; ++off) {
1109            MatchingOligo probeSuffix(fullProbe.get_oligo().suffix(off));
1110            if (!probeSuffix.domainPossible()) break; // abort - no smaller suffix may create a domain
1111            bind_till_domain(probeSuffix, ptroot, 0);
1112            ++progress;
1113        }
1114
1115        result.summarize_centigrade_hits(tprobe.perc);
1116        progress.done();
1117
1118        reset();
1119    }
1120};
1121
1122class ProbeOccurrence {
1123    const char *seq;
1124
1125public:
1126    ProbeOccurrence(const char *seq_) : seq(seq_) {}
1127
1128    const char *sequence() const { return seq; }
1129
1130    void forward() { ++seq; }
1131
1132    bool less(const ProbeOccurrence& other, size_t len) const {
1133        const char *mine = sequence();
1134        const char *his  = other.sequence();
1135
1136        int cmp = 0;
1137        for (size_t i = 0; i<len && !cmp; ++i) {
1138            cmp = mine[i]-his[i];
1139        }
1140        return cmp<0;
1141    }
1142};
1143
1144class ProbeIterator {
1145    ProbeOccurrence cursor;
1146
1147    size_t probelen;
1148    size_t rest;   // size of seqpart behind cursor
1149    size_t gc, at; // count G+C and A+T
1150
1151    bool has_only_std_bases() const { return (gc+at) == probelen; }
1152
1153    bool at_end() const { return !rest; }
1154
1155    PT_base base_at(size_t offset) const {
1156        pt_assert(offset < probelen);
1157        return PT_base(cursor.sequence()[offset]);
1158    }
1159
1160    void forget(PT_base b) {
1161        switch (b) {
1162            case PT_A: case PT_T: pt_assert(at>0); --at; break;
1163            case PT_C: case PT_G: pt_assert(gc>0); --gc; break;
1164            default : break;
1165        }
1166    }
1167    void count(PT_base b) {
1168        switch (b) {
1169            case PT_A: case PT_T: ++at; break;
1170            case PT_C: case PT_G: ++gc; break;
1171            default : break;
1172        }
1173    }
1174
1175    void init_counts() {
1176        at = gc = 0;
1177        for (size_t i = 0; i<probelen; ++i) {
1178            count(base_at(i));
1179        }
1180    }
1181
1182public:
1183    ProbeIterator(const char *seq, size_t seqlen, size_t probelen_)
1184        : cursor(seq),
1185          probelen(probelen_),
1186          rest(seqlen-probelen)
1187    {
1188        pt_assert(seqlen >= probelen);
1189        init_counts();
1190    }
1191
1192    bool next() {
1193        if (at_end()) return false;
1194        forget(base_at(0));
1195        cursor.forward();
1196        --rest;
1197        count(base_at(probelen-1));
1198        return true;
1199    }
1200
1201    int get_temperature() const { return 2*at + 4*gc; }
1202
1203    bool gc_in_wanted_range(PT_pdc *pdc) const {
1204        return pdc->min_gc*probelen <= gc && gc <= pdc->max_gc*probelen;
1205    }
1206    bool temperature_in_wanted_range(PT_pdc *pdc) const {
1207        int temp             = get_temperature();
1208        return pdc->mintemp <= temp && temp <= pdc->maxtemp;
1209    }
1210
1211    bool feasible(PT_pdc *pdc) const {
1212        return has_only_std_bases() &&
1213            gc_in_wanted_range(pdc) &&
1214            temperature_in_wanted_range(pdc);
1215    }
1216
1217    const ProbeOccurrence& occurrence() const { return cursor; }
1218
1219    void dump(FILE *out) const {
1220        char *probe = readable_probe(cursor.sequence(), probelen, 'T');
1221        fprintf(out, "probe='%s' probelen=%zu at=%zu gc=%zu\n", probe, probelen, at, gc);
1222        free(probe);
1223    }
1224};
1225
1226class PO_Less {
1227    size_t probelen;
1228public:
1229    PO_Less(size_t probelen_) : probelen(probelen_) {}
1230    bool operator()(const ProbeOccurrence& a, const ProbeOccurrence& b) const { return a.less(b, probelen); }
1231};
1232
1233struct SCP_Less {
1234    bool operator()(const SmartCharPtr& p1, const SmartCharPtr& p2) { return &*p1 < &*p2; } // simply compare addresses
1235};
1236
1237class ProbeCandidates {
1238    typedef std::set<ProbeOccurrence, PO_Less>      Candidates;
1239    typedef std::map<ProbeOccurrence, int, PO_Less> CandidateHits;
1240    typedef std::set<SmartCharPtr, SCP_Less>        SeqData;
1241
1242    size_t        probelen;
1243    CandidateHits candidateHits;
1244    SeqData       data; // locks SmartPtrs to input data (ProbeOccurrences point inside their data)
1245
1246public:
1247    ProbeCandidates(size_t probelen_)
1248        : probelen(probelen_),
1249          candidateHits(probelen)
1250    {}
1251
1252    void generate_for_sequence(PT_pdc *pdc, const SmartCharPtr& seq, size_t bp) {
1253        arb_progress base_progress(2*bp);
1254        if (bp >= probelen) {
1255            // collect all probe candidates for current sequence
1256            Candidates candidates(probelen);
1257            {
1258                data.insert(seq);
1259                ProbeIterator probe(&*seq, bp, probelen);
1260                do {
1261                    if (probe.feasible(pdc)) {
1262                        candidates.insert(probe.occurrence());
1263                        ++base_progress;
1264                    }
1265                } while (probe.next());
1266            }
1267
1268            // increment overall hitcount for each found candidate
1269            for (Candidates::iterator c = candidates.begin(); c != candidates.end(); ++c) {
1270                candidateHits[*c]++;
1271                ++base_progress;
1272            }
1273        }
1274        base_progress.done();
1275    }
1276
1277    void create_tprobes(PT_pdc *pdc, int ingroup_size) {
1278        // tracks maximum rejected target-group coverage
1279        int min_ingroup_hits     = (ingroup_size * pdc->min_coverage + .5);
1280        int max_rej_ingroup_hits = 0;
1281
1282        for (CandidateHits::iterator c = candidateHits.begin(); c != candidateHits.end(); ++c) {
1283            int ingroup_hits = c->second;
1284            if (ingroup_hits >= min_ingroup_hits) {
1285                const ProbeOccurrence& candi = c->first;
1286
1287                PT_tprobes *tprobe = create_PT_tprobes();
1288
1289                tprobe->sequence  = ARB_strndup(candi.sequence(), probelen);
1290                tprobe->temp      = pt_get_temperature(tprobe->sequence);
1291                tprobe->groupsize = ingroup_hits;
1292
1293                aisc_link(&pdc->ptprobes, tprobe);
1294            }
1295            else {
1296                max_rej_ingroup_hits = std::max(max_rej_ingroup_hits, ingroup_hits);
1297            }
1298        }
1299
1300        double max_rejected_coverage = max_rej_ingroup_hits/double(ingroup_size);
1301        pdc->max_rj_coverage         = std::max(pdc->max_rj_coverage, max_rejected_coverage);
1302    }
1303};
1304
1305class DesignTargets : virtual Noncopyable {
1306    PT_pdc *pdc;
1307
1308    int targets;       // overall target sequence count
1309    int known_targets; // known target sequences
1310    int added_targets; // added (=unknown) target sequences
1311
1312    long datasize;     // overall bp of target sequences
1313
1314    int min_probe_length; // min. designed probe length
1315
1316    int *known2id;
1317
1318    ARB_ERROR error;
1319
1320    void announce_seq_bp(long bp) {
1321        pt_assert(!error);
1322        if (bp<long(min_probe_length)) {
1323            error = GBS_global_string("Sequence contains only %lu bp. Impossible design request for", bp);
1324        }
1325        else {
1326            datasize                  += bp;
1327            if (datasize<bp) datasize  = ULONG_MAX;
1328        }
1329    }
1330
1331    void scan_added_targets() {
1332        added_targets = 0;
1333        for (PT_sequence *seq = pdc->sequences; seq && !error; seq = seq->next) {
1334            announce_seq_bp(seq->seq.size);
1335            ++added_targets;
1336        }
1337        if (error) error = GBS_global_string("%s one of the added sequences", error.deliver());
1338    }
1339    void scan_known_targets(int expected_known_targets) {
1340        known2id      = new int[expected_known_targets];
1341        known_targets = 0;
1342
1343        for (int id = 0; id < psg.data_count && !error; ++id) {
1344            const probe_input_data& pid = psg.data[id];
1345            if (pid.inside_group()) {
1346                announce_seq_bp(pid.get_size());
1347                known2id[known_targets++] = id;
1348                pt_assert(known_targets <= expected_known_targets);
1349                if (error) error = GBS_global_string("%s species '%s'", error.deliver(), pid.get_shortname());
1350            }
1351        }
1352    }
1353
1354public:
1355    DesignTargets(PT_pdc *pdc_, int expected_known_targets)
1356        : pdc(pdc_),
1357          targets(0),
1358          known_targets(0),
1359          datasize(0),
1360          min_probe_length(pdc->min_probelen),
1361          known2id(NULp)
1362    {
1363        scan_added_targets(); // calc added_targets
1364        if (!error) {
1365            scan_known_targets(expected_known_targets);
1366            targets = known_targets+added_targets;
1367        }
1368    }
1369    ~DesignTargets() {
1370        delete [] known2id;
1371    }
1372    ARB_ERROR& get_error() { return error; } // needs to be checked after construction!
1373
1374    long get_datasize() const { return datasize; }
1375    int get_count() const { return targets; }
1376    int get_known_count() const { return known_targets; }
1377    int get_added_count() const { return added_targets; }
1378
1379    void generate(ProbeCandidates& candidates) {
1380        arb_progress progress(get_count());
1381        for (int k = 0; k<known_targets; ++k) {
1382            const probe_input_data& pid = psg.data[known2id[k]];
1383            candidates.generate_for_sequence(pdc, pid.get_dataPtr(), pid.get_size());
1384            ++progress;
1385        }
1386        for (PT_sequence *seq = pdc->sequences; seq; seq = seq->next) {
1387            candidates.generate_for_sequence(pdc, ARB_strndup(seq->seq.data, seq->seq.size), seq->seq.size);
1388            ++progress;
1389        }
1390    }
1391};
1392
1393#if defined(DUMP_DESIGNED_PROBES)
1394static void dump_tprobe(PT_tprobes *tprobe, int idx) {
1395    char *readable = readable_probe(tprobe->sequence, tprobe->seq_len, 'T');
1396    fprintf(stderr, "tprobe='%s' idx=%i len=%i\n", readable, idx, tprobe->seq_len);
1397    free(readable);
1398}
1399static void DUMP_TPROBES(const char *where, PT_pdc *pdc) {
1400    int idx = 0;
1401    fprintf(stderr, "dumping tprobes %s:\n", where);
1402    for (PT_tprobes *tprobe = pdc->tprobes; tprobe; tprobe = tprobe->next) {
1403        dump_tprobe(tprobe, idx++);
1404    }
1405}
1406#else
1407#define DUMP_TPROBES(a,b)
1408#endif
1409
1410int PT_start_design(PT_pdc *pdc, int /* dummy */) {
1411    PT_local *locs = (PT_local*)pdc->mh.parent->parent;
1412
1413    erase_formatter(pdc);
1414    while (pdc->tprobes) destroy_PT_tprobes(pdc->tprobes);
1415
1416    for (PT_sequence *seq = pdc->sequences; seq; seq = seq->next) {              // Convert all external sequence to internal format
1417        seq->seq.size = probe_compress_sequence(seq->seq.data, seq->seq.size-1); // no longer convert final zero-byte (PT_QU gets auto-appended)
1418    }
1419
1420    ARB_ERROR error;
1421    int       unknown_species_count = 0;
1422    {
1423        char *unknown_names = ptpd_read_names(locs, pdc->names.data, pdc->checksums.data, error); // read the names
1424        if (unknown_names) {
1425            ConstStrArray names;
1426            GBT_splitNdestroy_string(names, unknown_names, "#", true);
1427            pt_assert(!unknown_names);
1428            unknown_species_count = names.size();
1429        }
1430    }
1431
1432    if (!error) {
1433        DesignTargets targets(pdc, locs->group_count); // here locs->group_count is amount of known marked species/genes
1434        error = targets.get_error();
1435
1436        if (!error) {
1437            locs->group_count = targets.get_count();
1438            if (locs->group_count <= 0) {
1439                error = GBS_global_string("No %s marked - no probes designed", gene_flag ? "genes" : "species");
1440            }
1441            else if (targets.get_added_count() != unknown_species_count) {
1442                if (gene_flag) { // cannot add genes
1443                    pt_assert(targets.get_added_count() == 0); // cannot, but did add?!
1444                    error = GBS_global_string("Cannot design probes for %i unknown marked genes", unknown_species_count);
1445                }
1446                else {
1447                    int added = targets.get_added_count();
1448                    error     = GBS_global_string("Got %i unknown marked species, but %i custom sequence%s added (has to match)",
1449                                                  unknown_species_count,
1450                                                  added,
1451                                                  added == 1 ? " was" : "s were");
1452                }
1453            }
1454            else if (pdc->min_probelen < MIN_DESIGN_PROBE_LENGTH) {
1455                error = GBS_global_string("Specified min. probe length %i is below the min. allowed probe length of %i", pdc->min_probelen, MIN_DESIGN_PROBE_LENGTH);
1456            }
1457            else if (get_max_probelen(pdc) < pdc->min_probelen) {
1458                error = GBS_global_string("Max. probe length %i is below the specified min. probe length of %i", get_max_probelen(pdc), pdc->min_probelen);
1459            }
1460            else {
1461                // search for possible probes
1462                if (!error) {
1463                    int min_probelen = pdc->min_probelen;
1464                    int max_probelen = get_max_probelen(pdc);
1465
1466                    arb_progress progress("Searching probe candidates", max_probelen-min_probelen+1);
1467                    for (int len = min_probelen; len <= max_probelen; ++len) {
1468                        ProbeCandidates candidates(len);
1469
1470                        targets.generate(candidates);
1471                        pt_assert(!targets.get_error());
1472
1473                        candidates.create_tprobes(pdc, targets.get_count());
1474                        ++progress;
1475                    }
1476                }
1477
1478                while (pdc->sequences) destroy_PT_sequence(pdc->sequences);
1479
1480                if (!error) {
1481                    sort_tprobes_by(pdc, PSM_SEQUENCE);
1482                    remove_tprobes_with_too_many_mishits(pdc);
1483                    remove_tprobes_outside_ecoli_range(pdc);
1484                    size_t tprobes = tprobes_calculate_bonds(locs);
1485                    DUMP_TPROBES("after tprobes_calculate_bonds", pdc);
1486
1487                    if (tprobes) {
1488                        arb_progress    progress(GBS_global_string("Calculating probe quality for %zu probe candidates", tprobes), tprobes);
1489                        OutgroupMatcher om(locs, pdc);
1490                        for (PT_tprobes *tprobe = pdc->tprobes; tprobe; tprobe = tprobe->next) {
1491                            om.calculate_outgroup_matches(*tprobe);
1492                            ++progress;
1493                        }
1494                    }
1495                    else {
1496                        fputs("No probe candidates found\n", stdout);
1497                    }
1498
1499                    tprobes_sumup_perc_and_calc_quality(pdc);
1500                    sort_tprobes_by(pdc, PSM_QUALITY);
1501                    clip_tprobes(pdc, pdc->clipresult);
1502                }
1503            }
1504        }
1505    }
1506
1507    pt_export_error_if(locs, error);
1508    return 0;
1509}
1510
1511// --------------------------------------------------------------------------------
1512
1513#ifdef UNIT_TESTS
1514#ifndef TEST_UNIT_H
1515#include <test_unit.h>
1516#endif
1517
1518static const char *concat_iteration(PrefixIterator& prefix) {
1519    static GBS_strstruct out(50);
1520
1521    out.erase();
1522
1523    while (!prefix.done()) {
1524        if (out.filled()) out.put(',');
1525
1526        char *readable = probe_2_readable(prefix.copy(), prefix.length());
1527        out.cat(readable);
1528        free(readable);
1529
1530        ++prefix;
1531    }
1532
1533    return out.get_data();
1534}
1535
1536void TEST_PrefixIterator() {
1537    // straight-forward permutation
1538    PrefixIterator p0(PT_A, PT_T, 0); TEST_EXPECT_EQUAL(p0.steps(), 1);
1539    PrefixIterator p1(PT_A, PT_T, 1); TEST_EXPECT_EQUAL(p1.steps(), 4);
1540    PrefixIterator p2(PT_A, PT_T, 2); TEST_EXPECT_EQUAL(p2.steps(), 16);
1541    PrefixIterator p3(PT_A, PT_T, 3); TEST_EXPECT_EQUAL(p3.steps(), 64);
1542
1543    TEST_EXPECT_EQUAL(p1.done(), false);
1544    TEST_EXPECT_EQUAL(p0.done(), false);
1545
1546    TEST_EXPECT_EQUAL(concat_iteration(p0), "");
1547    TEST_EXPECT_EQUAL(concat_iteration(p1), "A,C,G,U");
1548    TEST_EXPECT_EQUAL(concat_iteration(p2), "AA,AC,AG,AU,CA,CC,CG,CU,GA,GC,GG,GU,UA,UC,UG,UU");
1549
1550    // permutation truncated at PT_QU
1551    PrefixIterator q0(PT_QU, PT_T, 0); TEST_EXPECT_EQUAL(q0.steps(), 1);
1552    PrefixIterator q1(PT_QU, PT_T, 1); TEST_EXPECT_EQUAL(q1.steps(), 6);
1553    PrefixIterator q2(PT_QU, PT_T, 2); TEST_EXPECT_EQUAL(q2.steps(), 31);
1554    PrefixIterator q3(PT_QU, PT_T, 3); TEST_EXPECT_EQUAL(q3.steps(), 156);
1555    PrefixIterator q4(PT_QU, PT_T, 4); TEST_EXPECT_EQUAL(q4.steps(), 781);
1556
1557    TEST_EXPECT_EQUAL(concat_iteration(q0), "");
1558    TEST_EXPECT_EQUAL(concat_iteration(q1), ".,N,A,C,G,U");
1559    TEST_EXPECT_EQUAL(concat_iteration(q2),
1560                      ".,"
1561                      "N.,NN,NA,NC,NG,NU,"
1562                      "A.,AN,AA,AC,AG,AU,"
1563                      "C.,CN,CA,CC,CG,CU,"
1564                      "G.,GN,GA,GC,GG,GU,"
1565                      "U.,UN,UA,UC,UG,UU");
1566    TEST_EXPECT_EQUAL(concat_iteration(q3),
1567                      ".,"
1568                      "N.,"
1569                      "NN.,NNN,NNA,NNC,NNG,NNU,"
1570                      "NA.,NAN,NAA,NAC,NAG,NAU,"
1571                      "NC.,NCN,NCA,NCC,NCG,NCU,"
1572                      "NG.,NGN,NGA,NGC,NGG,NGU,"
1573                      "NU.,NUN,NUA,NUC,NUG,NUU,"
1574                      "A.,"
1575                      "AN.,ANN,ANA,ANC,ANG,ANU,"
1576                      "AA.,AAN,AAA,AAC,AAG,AAU,"
1577                      "AC.,ACN,ACA,ACC,ACG,ACU,"
1578                      "AG.,AGN,AGA,AGC,AGG,AGU,"
1579                      "AU.,AUN,AUA,AUC,AUG,AUU,"
1580                      "C.,"
1581                      "CN.,CNN,CNA,CNC,CNG,CNU,"
1582                      "CA.,CAN,CAA,CAC,CAG,CAU,"
1583                      "CC.,CCN,CCA,CCC,CCG,CCU,"
1584                      "CG.,CGN,CGA,CGC,CGG,CGU,"
1585                      "CU.,CUN,CUA,CUC,CUG,CUU,"
1586                      "G.,"
1587                      "GN.,GNN,GNA,GNC,GNG,GNU,"
1588                      "GA.,GAN,GAA,GAC,GAG,GAU,"
1589                      "GC.,GCN,GCA,GCC,GCG,GCU,"
1590                      "GG.,GGN,GGA,GGC,GGG,GGU,"
1591                      "GU.,GUN,GUA,GUC,GUG,GUU,"
1592                      "U.,"
1593                      "UN.,UNN,UNA,UNC,UNG,UNU,"
1594                      "UA.,UAN,UAA,UAC,UAG,UAU,"
1595                      "UC.,UCN,UCA,UCC,UCG,UCU,"
1596                      "UG.,UGN,UGA,UGC,UGG,UGU,"
1597                      "UU.,UUN,UUA,UUC,UUG,UUU");
1598}
1599
1600#endif // UNIT_TESTS
1601
1602// --------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.