NCBI C++ ToolKit
nw_formatter.cpp
Go to the documentation of this file.

Go to the SVN repository for this file.

1 /* $Id: nw_formatter.cpp 100425 2023-07-31 13:44:51Z mozese2 $
2  * ===========================================================================
3  *
4  * PUBLIC DOMAIN NOTICE
5  * National Center for Biotechnology Information
6  *
7  * This software/database is a "United States Government Work" under the
8  * terms of the United States Copyright Act. It was written as part of
9  * the author's official duties as a United States Government employee and
10  * thus cannot be copyrighted. This software/database is freely available
11  * to the public for use. The National Library of Medicine and the U.S.
12  * Government have not placed any restriction on its use or reproduction.
13  *
14  * Although all reasonable efforts have been taken to ensure the accuracy
15  * and reliability of the software and data, the NLM and the U.S.
16  * Government do not and cannot warrant the performance or results that
17  * may be obtained by using this software or data. The NLM and the U.S.
18  * Government disclaim all warranties, express or implied, including
19  * warranties of performance, merchantability or fitness for any particular
20  * purpose.
21  *
22  * Please cite the author in any work or product based on this material.
23  *
24  * ===========================================================================
25  *
26  * Author: Yuri Kapustin, Boris Kiryutin
27  *
28  * ===========================================================================
29  *
30  */
31 
32 #include <ncbi_pch.hpp>
33 #include "messages.hpp"
36 
42 #include <serial/objostrasn.hpp>
43 #include <serial/serial.hpp>
44 
45 #include <iterator>
46 
49 
50 
52  m_aligner(&aligner)
53 {
54  const char id_not_set[] = "ID_not_set";
55  CRef<CSeq_id> seqid (new CSeq_id);
56  seqid->SetLocal().SetStr(id_not_set);
57  m_Seq1Id = m_Seq2Id = seqid;
58 }
59 
60 
62 {
63  m_Seq1Id = id1;
64  m_Seq2Id = id2;
65 }
66 
68  TSeqPos query_start, ENa_strand query_strand,
69  TSeqPos subj_start, ENa_strand subj_strand,
70  int flags) const
71 {
72 
73  bool trim_end_gaps = ( flags & eSAFF_TrimEndGaps ) || m_aligner->IsSmithWaterman();
74 
75  CRef<CSeq_align> seqalign (new CSeq_align);
76 
77  // the alignment is pairwise
78  seqalign->SetDim(2);
79 
80  // NW is a global alignment; SW is local
81  if(m_aligner->IsSmithWaterman()) {
83  }
84  else {
86  }
87 
88  // add dynprog score
90  CRef<CScore> score (new CScore);
91  score->SetId().SetStr("global_score");
92  score->SetValue().SetInt(m_aligner->GetScore());
93  seqalign->SetScore().push_back(score);
94  }
95 
96  // add identity score
97  if(flags & eSAFF_Identity) {
98  const string transcript = m_aligner->GetTranscriptString();
99  TSeqPos matches = 0;
100  ITERATE(string, ii, transcript) {
101  if(*ii == CNWAligner::eTS_Match) {
102  ++matches;
103  }
104  }
105 
106  size_t length=0;
107  if(trim_end_gaps) {
108 
109  Int8 endi = transcript.size() - 1;
110  while( endi>=0 && ( transcript[endi] != CNWAligner::eTS_Match && transcript[endi] != CNWAligner::eTS_Replace ) ) --endi;
111 
112  Int8 ind = 0;
113  for( ;ind < endi; ++ind) {
114  if(transcript[ind] == CNWAligner::eTS_Match || transcript[ind] == CNWAligner::eTS_Replace ) break;
115  }
116 
117  if( ind <= endi ) {
118  length = endi + 1 - ind;
119  }
120 
121  } else {
122  length = transcript.size();
123  }
124 
125 
126 
127 
128  double idty = 0;
129  if( length > 0 ) {
130  idty = double(matches) / length;
131  }
132  CRef<CScore> score (new CScore);
133  score->SetId().SetStr("identity");
134  score->SetValue().SetReal(idty);
135  seqalign->SetScore().push_back(score);
136  }
137 
138  CRef<CDense_seg> rds= AsDenseSeg(query_start, query_strand,
139  subj_start, subj_strand, flags);
140 
141  if(rds) {
142  seqalign->SetSegs().SetDenseg(*rds);
143  } else {
144  seqalign->SetSegs().SetDendiag();
145  }
146 
147  return seqalign;
148 }
149 
151  TSeqPos query_start, ENa_strand query_strand,
152  TSeqPos subj_start, ENa_strand subj_strand,
153  int flags) const
154 {
155 
156  bool trim_end_gaps = ( flags & eSAFF_TrimEndGaps ) || m_aligner->IsSmithWaterman();
157 
158  const string transcript = m_aligner->GetTranscriptString();
159 
160  if(transcript.size() == 0) {
162  }
163 
164  CRef<CDense_seg> rds(new CDense_seg);
165  CDense_seg& ds = *rds;
166 
167  if( m_aligner->IsSmithWaterman() ) {// check if alignment is empty
168 
169  const CNWAligner::TTranscript stranscript = m_aligner->GetTranscript();
170 
171  vector<CNWAligner::ETranscriptSymbol>::const_iterator
172  ib = stranscript.begin(),
173  ie = stranscript.end();
174  while( ( ie != ib ) &&
177  ++ib;
178  }
179  if( ib == ie ) {
180  rds.Reset();
181  return rds;
182  }
183  }
184 
185 
186  ds.FromTranscript(query_start, query_strand,
187  subj_start, subj_strand,
188  transcript);
189 
190  CDense_seg::TIds& ids = ds.SetIds();
191  if( m_Seq1Id && m_Seq2Id ) {
192 
193  CRef<CSeq_id> id_query (new CSeq_id);
194  id_query->Assign(*m_Seq1Id);
195  ids.push_back(id_query);
196 
197  CRef<CSeq_id> id_subj (new CSeq_id);
198  id_subj->Assign(*m_Seq2Id);
199  ids.push_back(id_subj);
200  }
201 
202  if(trim_end_gaps) {
203  ds.TrimEndGaps();
204  }
205 
206  return rds;
207 }
208 
209 static const char s_kGap [] = "<GAP>";
210 
212 {
213  m_exon = false;
214  m_idty = 0;
215  m_len = m_box[1] - m_box[0] + 1;
216  m_annot = s_kGap;
217  m_details.resize(0);
218  m_score = 0; // no score for <Gap>s
219 }
220 
221 // try improving the segment by cutting it from the left
222 void CNWFormatter::SSegment::ImproveFromLeft1(const char* seq1, const char* seq2,
224 {
225 
226  //legacy check
227  const size_t min_query_size = 4;
228  if( int(m_box[1] - m_box[0] + 1) < int(min_query_size)) {
229  SetToGap();
230  return;
231  }
232 
233  //compute length and number of matches
234  int len_total = (int)m_details.size();
235  int match_total = 0;
236  string::iterator irs0 = m_details.begin(),
237  irs1 = m_details.end(), irs;
238 
239  for(irs = irs0; irs != irs1; ++irs) {
240  if(*irs == 'M') {
241  ++match_total;
242  }
243  }
244 
245  //count identity at the right end
246  string::reverse_iterator rirs0 = m_details.rbegin(),
247  rirs1 = m_details.rend(), rirs = rirs0;
248  int cnt = 0, max_cnt = 20;
249  int len = 0, match = 0;
250 
251  for( ; ( rirs != rirs1 ) && (cnt != max_cnt) ; ++rirs, ++cnt) {
252  ++len;
253  if(*rirs == 'M') {
254  ++match;
255  }
256  }
257  double ident = match/(double)len;
258 
259  //trimming point
260  int i0_max = 0, i1_max = 0;
261  string::iterator irs_max;
262 
263  //find the trimming point
264  int i0 = 0, i1 = 0;
265  len = 0;
266  match = 0;
267  double epsilon = 1e-10;
268  const double dropoff_diff = .19;
269 
270  --irs1;
271  for(irs = irs0; irs != irs1; ++irs) {
272 
273  switch(*irs) {
274 
275  case 'M': {
276  ++match;
277  ++i0;
278  ++i1;
279  }
280  break;
281 
282  case 'R': {
283  ++i0;
284  ++i1;
285  }
286  break;
287 
288  case 'I': {
289  ++i1;
290  }
291  break;
292 
293  case 'D': {
294  ++i0;
295  }
296  }
297  ++len;
298 
299  //trim here if
300  if( max( ident, (match_total - match)/(double)(len_total-len) ) - match/(double)len - dropoff_diff> epsilon ){
301  i0_max = i0;
302  i1_max = i1;
303  irs_max = irs;
304  //do not count trimmed part, adjust values
305  match_total -= match;
306  len_total -= len;
307  match = 0;
308  len = 0;
309  }
310  }
311 
312  // work around a weird case of equally optimal
313  // but detrimental for our purposes alignment
314  // -check the actual sequence chars
315  size_t head = 0;
316  while(i0_max > 0 && i1_max > 0) {
317  if( toupper(seq1[m_box[0]+i0_max-1]) != 'N' && seq1[m_box[0]+i0_max-1] == seq2[m_box[2]+i1_max-1] ) {
318  --i0_max; --i1_max;
319  ++head;
320  }
321  else {
322  break;
323  }
324  }
325 
326  //trim
327 
328  if(i0_max == 0 && i1_max == 0) return;//no changes
329 
330  // if the resulting segment is still long enough
331  if(m_box[1] - m_box[0] + 1 - i0_max >= min_query_size )
332  {
333  // resize
334  m_box[0] += i0_max;
335  m_box[2] += i1_max;
336  const size_t L = irs_max - irs0 + 1;
337  m_details.erase(0, L);
338  m_details.insert(m_details.begin(), head, 'M');
339  Update(aligner.GetNonNullPointer());
340 
341  // update the first two annotation symbols
342  if(m_annot.size() > 2 && m_annot[2] == '<') {
343  int j1 = int(m_box[2]) - 2;
344  char c1 = j1 >= 0? seq2[j1]: ' ';
345  m_annot[0] = c1;
346  int j2 = int(m_box[2]) - 1;
347  char c2 = j2 >= 0? seq2[j2]: ' ';
348  m_annot[1] = c2;
349  }
350  } else {
351  SetToGap();//just drop it
352  }
353 }
354 
355 
356 // try improving the segment by cutting it from the left
357 void CNWFormatter::SSegment::ImproveFromLeft(const char* seq1, const char* seq2,
359 {
360  const size_t min_query_size = 4;
361 
362  int i0 = int(m_box[1] - m_box[0] + 1), i0_max = i0;
363  if(i0 < int(min_query_size)) {
364  SetToGap();
365  return;
366  }
367 
368  // find the top score suffix
369  int i1 = int(m_box[3] - m_box[2] + 1), i1_max = i1;
370 
371  CNWAligner::TScore score_max = 0, s = 0;
372 
373  const CNWAligner::TScore wm = 1;
374  const CNWAligner::TScore wms = -1;
375  const CNWAligner::TScore wg = 0;
376  const CNWAligner::TScore ws = -1;
377 
378  string::reverse_iterator irs0 = m_details.rbegin(),
379  irs1 = m_details.rend(), irs = irs0, irs_max = irs0;
380 
381  for( ; irs != irs1; ++irs) {
382 
383  switch(*irs) {
384 
385  case 'M': {
386  s += wm;
387  --i0;
388  --i1;
389  }
390  break;
391 
392  case 'R': {
393  s += wms;
394  --i0;
395  --i1;
396  }
397  break;
398 
399  case 'I': {
400  s += ws;
401  if(irs > irs0 && *(irs-1)!='I') s += wg;
402  --i1;
403  }
404  break;
405 
406  case 'D': {
407  s += ws;
408  if(irs > irs0 && *(irs-1)!='D') s += wg;
409  --i0;
410  }
411  }
412 
413  if(s >= score_max) {
414  score_max = s;
415  i0_max = i0;
416  i1_max = i1;
417  irs_max = irs;
418  }
419  }
420 
421  // work around a weird case of equally optimal
422  // but detrimental for our purposes alignment
423  // -check the actual sequence chars
424  size_t head = 0;
425  while(i0_max > 0 && i1_max > 0) {
426  if( toupper (seq1[m_box[0]+i0_max-1]) != 'N' && seq1[m_box[0]+i0_max-1] == seq2[m_box[2]+i1_max-1]) {
427  --i0_max; --i1_max;
428  ++head;
429  }
430  else {
431  break;
432  }
433  }
434 
435  if(i0_max == 0 && i1_max == 0) return;//no chages
436 
437  // if the resulting segment is still long enough
438  if(m_box[1] - m_box[0] + 1 - i0_max >= min_query_size )
439  {
440  // resize
441  m_box[0] += i0_max;
442  m_box[2] += i1_max;
443  const size_t L = m_details.size() - (irs_max - irs0 + 1);
444  m_details.erase(0, L);
445  m_details.insert(m_details.begin(), head, 'M');
446  Update(aligner.GetNonNullPointer());
447 
448  // update the first two annotation symbols
449  if(m_annot.size() > 2 && m_annot[2] == '<') {
450  int j1 = int(m_box[2]) - 2;
451  char c1 = j1 >= 0? seq2[j1]: ' ';
452  m_annot[0] = c1;
453  int j2 = int(m_box[2]) - 1;
454  char c2 = j2 >= 0? seq2[j2]: ' ';
455  m_annot[1] = c2;
456  }
457  } else {
458  SetToGap();//just drop it
459  }
460 }
461 
463 {
464  size_t gap_count = 0;
465  ITERATE(string, irs, m_details) {
466  switch(*irs) {
467  case 'I':
468  case 'D':
469  ++gap_count;
470  break;
471  default:
472  break;
473  }
474  }
475  return gap_count;
476 }
477 
478 
480 {
481  map<char, size_t> count;
482  for(size_t i = m_box[0]; i<=m_box[1]; ++i) {
483  ++count[rna_seq[i]];
484  }
485  size_t gap_len = GapLength();
486  for(map<char, size_t>::iterator i = count.begin(); i != count.end(); ++i) {
487  if( m_len * 70 <= 100 * (i->second + gap_len) ) {
488  return true;
489  }
490  }
491  return false;
492 }
493 
494 
495 // try improving the segment by cutting it from the right
496 void CNWFormatter::SSegment::ImproveFromRight1(const char* seq1, const char* seq2,
498 {
499  const size_t min_query_size = 4;
500  //legacy check
501  if(m_box[1] - m_box[0] + 1 < min_query_size) {
502  SetToGap();
503  return;
504  }
505 
506  //identity total
507  int len_total = (int)m_details.size();
508  int match_total = 0;
509  string::iterator irs0 = m_details.begin(),
510  irs1 = m_details.end(), irs;
511 
512  for(irs = irs0; irs != irs1; ++irs) {
513  if(*irs == 'M') {
514  ++match_total;
515  }
516  }
517 
518  //count identity at the left end
519  int cnt = 0, max_cnt = 20;
520  int len = 0, match = 0;
521  for( irs = irs0; ( irs != irs1 ) && (cnt != max_cnt) ; ++irs, ++cnt) {
522  ++len;
523  if(*irs == 'M') {
524  ++match;
525  }
526  }
527  double ident = match/(double)len;
528 
529  double epsilon = 1e-10;
530  const double dropoff_diff = .19;
531 
532  int i0 = int(m_box[1] - m_box[0] + 1), i0_max = i0;
533  int i1 = int(m_box[3] - m_box[2] + 1), i1_max = i1;
534  match = 0;
535  len = 0;
536  string::reverse_iterator rirs0 = m_details.rbegin(),
537  rirs1 = m_details.rend(), rirs = rirs0, rirs_max;
538 
539  --rirs1;
540  for( ; rirs != rirs1; ++rirs) {
541 
542  switch(*rirs) {
543 
544  case 'M': {
545  ++match;
546  --i0;
547  --i1;
548  }
549  break;
550 
551  case 'R': {
552  --i0;
553  --i1;
554  }
555  break;
556 
557  case 'I': {
558  --i1;
559  }
560  break;
561 
562  case 'D': {
563  --i0;
564  }
565  }
566  ++len;
567 
568  //trim here if
569  if( max( ident, (match_total - match)/(double)(len_total-len) ) - match/(double)len - dropoff_diff > epsilon ) {
570  i0_max = i0;
571  i1_max = i1;
572  rirs_max = rirs;
573  //do not count trimmed part, adjust values
574  match_total -= match;
575  len_total -= len;
576  match = 0;
577  len = 0;
578  }
579  }
580 
581  int dimq = int(m_box[1] - m_box[0] + 1);
582  int dims = int(m_box[3] - m_box[2] + 1);
583 
584  // work around a weird case of equally optimal
585  // but detrimental for our purposes alignment
586  // -check the actual sequences
587  size_t tail = 0;
588  while(i0_max < dimq && i1_max < dims ) {
589  if( toupper(seq1[m_box[0]+i0_max]) != 'N' && seq1[m_box[0]+i0_max] == seq2[m_box[2]+i1_max]) {
590  ++i0_max; ++i1_max;
591  ++tail;
592  }
593  else {
594  break;
595  }
596  }
597 
598  if( i0_max >= dimq && i1_max >= dims ) return;//no changes
599 
600  // if the resulting segment is still long enough
601  if(i0_max - 1 >= int(min_query_size) ) {
602 
603  m_box[1] = m_box[0] + i0_max - 1;
604  m_box[3] = m_box[2] + i1_max - 1;
605 
606  m_details.resize(m_details.size() - (rirs_max - rirs0 + 1));
607  m_details.insert(m_details.end(), tail, 'M');
608  Update(aligner.GetNonNullPointer());
609 
610  // update the last two annotation chars
611  const size_t adim = m_annot.size();
612  if(adim > 2 && m_annot[adim - 3] == '>') {
613 
614  const size_t len2 (aligner->GetSeqLen2());
615  const char c3 (m_box[3] + 1 < len2? seq2[m_box[3] + 1]: ' ');
616  const char c4 (m_box[3] + 2 < len2? seq2[m_box[3] + 2]: ' ');
617  m_annot[adim-2] = c3;
618  m_annot[adim-1] = c4;
619  }
620  } else {
621  SetToGap();//just drop it
622  }
623 }
624 
625 
626 
627 // try improving the segment by cutting it from the right
628 void CNWFormatter::SSegment::ImproveFromRight(const char* seq1, const char* seq2,
630 {
631  const size_t min_query_size = 4;
632 
633  if(m_box[1] - m_box[0] + 1 < min_query_size) {
634  SetToGap();
635  return;
636  }
637 
638  // find the top score prefix
639  int i0 = -1, i0_max = i0;
640  int i1 = -1, i1_max = i1;
641 
642  CNWAligner::TScore score_max = 0, s = 0;
643 
644  const CNWAligner::TScore wm = 1;
645  const CNWAligner::TScore wms = -1;
646  const CNWAligner::TScore wg = 0;
647  const CNWAligner::TScore ws = -1;
648 
649  string::iterator irs0 = m_details.begin(),
650  irs1 = m_details.end(), irs = irs0, irs_max = irs0;
651 
652  for( ; irs != irs1; ++irs) {
653 
654  switch(*irs) {
655 
656  case 'M': {
657  s += wm;
658  ++i0;
659  ++i1;
660  }
661  break;
662 
663  case 'R': {
664  s += wms;
665  ++i0;
666  ++i1;
667  }
668  break;
669 
670  case 'I': {
671  s += ws;
672  if(irs > irs0 && *(irs-1) != 'I') s += wg;
673  ++i1;
674  }
675  break;
676 
677  case 'D': {
678  s += ws;
679  if(irs > irs0 && *(irs-1) != 'D') s += wg;
680  ++i0;
681  }
682  }
683 
684  if(s >= score_max) {
685  score_max = s;
686  i0_max = i0;
687  i1_max = i1;
688  irs_max = irs;
689  }
690  }
691 
692  int dimq = int(m_box[1] - m_box[0] + 1);
693  int dims = int(m_box[3] - m_box[2] + 1);
694 
695  // work around a weird case of equally optimal
696  // but detrimental for our purposes alignment
697  // -check the actual sequences
698  size_t tail = 0;
699  while(i0_max < dimq - 1 && i1_max < dims - 1) {
700  if( toupper(seq1[m_box[0]+i0_max+1]) != 'N' && seq1[m_box[0]+i0_max+1] == seq2[m_box[2]+i1_max+1] ) {
701  ++i0_max; ++i1_max;
702  ++tail;
703  }
704  else {
705  break;
706  }
707  }
708 
709  dimq += tail;
710  dims += tail;
711 
712  if(i0_max >= dimq - 1 && i1_max >= dims - 1) return;//no changes
713 
714  // if the resulting segment is still long enough
715  if(i0_max >= int(min_query_size) ) {
716 
717  m_box[1] = m_box[0] + i0_max;
718  m_box[3] = m_box[2] + i1_max;
719 
720  m_details.resize(irs_max - irs0 + 1);
721  m_details.insert(m_details.end(), tail, 'M');
722  Update(aligner.GetNonNullPointer());
723 
724  // update the last two annotation chars
725  const size_t adim = m_annot.size();
726  if(adim > 2 && m_annot[adim - 3] == '>') {
727 
728  const size_t len2 (aligner->GetSeqLen2());
729  const char c3 (m_box[3] + 1 < len2? seq2[m_box[3] + 1]: ' ');
730  const char c4 (m_box[3] + 2 < len2? seq2[m_box[3] + 2]: ' ');
731  m_annot[adim-2] = c3;
732  m_annot[adim-1] = c4;
733  }
734  } else {
735  SetToGap();//just drop it
736  }
737 }
738 
739 //check if 100% extension is possible, returns the length of possible extension
740 int CNWFormatter::SSegment::CanExtendRight(const vector<char>& mrna, const vector<char>& genomic) const
741 {
742  Int8 mind0 = m_box[1] + 1;
743  Int8 mind = mind0;
744  Int8 gind = m_box[3] + 1;
745  for(; mind < (int)mrna.size() && gind < (int)genomic.size(); ++gind, ++mind) {
746  if( toupper(mrna[mind]) == 'N' || mrna[mind] != genomic[gind] ) break;
747  }
748  return mind - mind0;
749 }
750 
751 
752 //check if 100% extension is possible, returns the length of possible extension
753 int CNWFormatter::SSegment::CanExtendLeft(const vector<char>& mrna, const vector<char>& genomic) const
754 {
755  int mind0 = (int)m_box[0] - 1;
756  int mind = mind0;
757  int gind = (int)m_box[2] - 1;
758  for(; mind >= 0 && gind >= 0; --mind, --gind) {
759  if( toupper(mrna[mind]) == 'N' || mrna[mind] != genomic[gind] ) break;
760  }
761  return mind0 - mind;
762 }
763 
764 //do extend, 100% identity in extension is implied
765 void CNWFormatter::SSegment::ExtendRight(const vector<char>& mrna, const vector<char>& genomic, Int8 ext_len, const CNWAligner* aligner)
766 {
767  if(ext_len > 0) {
768  m_box[1] += ext_len;
769  m_box[3] += ext_len;
770  m_details.append(ext_len, 'M');
771  Update(aligner);
772  // fix annotation
773  const size_t ann_dim = m_annot.size();
774  if(ann_dim > 2 && m_annot[ann_dim - 3] == '>') {
775  m_annot[ann_dim - 2] = (m_box[3] + 1) < genomic.size() ? genomic[m_box[3] + 1] : ' ';
776  m_annot[ann_dim - 1] = (m_box[3] + 2) < genomic.size() ? genomic[m_box[3] + 2] : ' ';
777  }
778  }
779 }
780 
781 //do extend, 100% identity in extension is implied
782 void CNWFormatter::SSegment::ExtendLeft(const vector<char>& mrna, const vector<char>& genomic, Int8 ext_len, const CNWAligner* aligner)
783 {
784  if(ext_len > 0) {
785  m_box[0] -= ext_len;
786  m_box[2] -= ext_len;
787  m_details.insert(m_details.begin(), ext_len, 'M');
788  Update(aligner);
789  //fix annotation
790  if( ( m_annot.size() > 2 ) && ( m_annot[2] == '<' ) ) {
791  m_annot[1] = m_box[2] >= 1 ? genomic[m_box[2] - 1] : ' ';
792  m_annot[0] = m_box[2] >= 2 ? genomic[m_box[2] - 2] : ' ';
793  }
794  }
795 }
796 
797 
799 {
800  // restore length and identity
801  m_len = m_details.size();
802 
803  string::const_iterator ib = m_details.begin(), ie = m_details.end();
804  size_t count (0); // std::count() not supported on some platforms
805  for(string::const_iterator ii = ib; ii != ie; ++ii) {
806  if(*ii == 'M') ++count;
807  }
808  m_idty = double(count) / m_len;
809 
810  const size_t xcript_dim (m_details.size());
811  CNWAligner::TTranscript transcript (xcript_dim);
812  for(size_t i (0); i < xcript_dim; ++i) {
813  transcript[i] = CNWAligner::ETranscriptSymbol(m_details[i]);
814  }
815 
816  m_score = float(paligner->CNWAligner::ScoreFromTranscript(transcript)) /
817  paligner->GetWm();
818 }
819 
820 
822 {
823  const size_t adim = m_annot.size();
824  return
825  (adim > 2 && m_annot[adim - 3] == '>')? (m_annot.c_str() + adim - 2): 0;
826 }
827 
828 
830 {
831  const size_t adim = m_annot.size();
832  return (adim > 3 && m_annot[2] == '<')? m_annot.c_str(): 0;
833 }
834 
835 
837  const char* acceptor,
838  bool semi_as_cons)
839 {
840  if(!donor || !acceptor) return false;
841 
842  bool rv;
843  if(semi_as_cons) {
844 
845  if(acceptor[0] == 'A') {
846  if(donor[0] == 'G' && acceptor[1] == 'G') {
847  rv = donor[1] == 'T' || donor[1] == 'C';
848  }
849  else {
850  rv = donor[0] == 'A' && donor[1] == 'T' && acceptor[1] == 'C';
851  }
852  }
853  else {
854  rv = false;
855  }
856  }
857  else {
858  rv = donor[0] == 'G' && donor[1] == 'T'
859  && acceptor[0] == 'A' && acceptor[1] == 'G';
860  }
861 
862  return rv;
863 }
864 void CNWFormatter::MakeSegments(deque<SSegment>* psegments) const
865 {
866  vector<SSegment> v;
867  MakeSegments(&v);
868  psegments->clear();
869  copy(v.begin(), v.end(), psegments->begin());
870 }
871 
872 void CNWFormatter::MakeSegments(vector<SSegment>* psegments) const
873 {
874  const CNWAligner::TTranscript transcript (m_aligner->GetTranscript());
875  if(transcript.size() == 0) {
877  }
878 
879  vector<SSegment>& segments(*psegments);
880  segments.resize(0);
881 
882  bool esfL1, esfR1, esfL2, esfR2;
883  m_aligner->GetEndSpaceFree(&esfL1, &esfR1, &esfL2, &esfR2);
884  const size_t len2 (m_aligner->GetSeqLen2());
885  const char* start1 (m_aligner->GetSeq1());
886  const char* start2 (m_aligner->GetSeq2());
887  const char* p1 (start1);
888  const char* p2 (start2);
889  Int8 tr_idx_hi0 (transcript.size() - 1), tr_idx_hi (tr_idx_hi0);
890  Int8 tr_idx_lo0 (0), tr_idx_lo (tr_idx_lo0);
891 
892  while(transcript[tr_idx_hi] == CNWAligner::eTS_SlackInsert
893  || transcript[tr_idx_hi] == CNWAligner::eTS_SlackDelete)
894  {
895  if(transcript[tr_idx_hi] == CNWAligner::eTS_SlackInsert) {
896  ++p2;
897  }
898  else {
899  ++p1;
900  }
901  --tr_idx_hi;
902  }
903 
904  if(esfL1 && transcript[tr_idx_hi0] == CNWAligner::eTS_Insert) {
905  while(esfL1 && transcript[tr_idx_hi] == CNWAligner::eTS_Insert) {
906  --tr_idx_hi;
907  ++p2;
908  }
909  }
910 
911  if(esfL2 && transcript[tr_idx_hi0] == CNWAligner::eTS_Delete) {
912  while(esfL2 && transcript[tr_idx_hi] == CNWAligner::eTS_Delete) {
913  --tr_idx_hi;
914  ++p1;
915  }
916  }
917 
918  if(esfR1 && transcript[tr_idx_lo0] == CNWAligner::eTS_Insert) {
919  while(esfR1 && transcript[tr_idx_lo] == CNWAligner::eTS_Insert) {
920  ++tr_idx_lo;
921  }
922  }
923 
924  if(esfR2 && transcript[tr_idx_lo0] == CNWAligner::eTS_Delete) {
925  while(esfR2 && transcript[tr_idx_lo] == CNWAligner::eTS_Delete) {
926  ++tr_idx_lo;
927  }
928  }
929 
930  vector<char> trans_ex (tr_idx_hi - tr_idx_lo + 1);
931 
932  for(int tr_idx (tr_idx_hi); tr_idx >= tr_idx_lo; ) {
933 
934  const char * p1_beg (p1), * p1_x (0);
935  const char * p2_beg (p2);
936  size_t matches (0), exon_aln_size (0), exon_aln_size_x(0);
937  int tr_idx_x (-1);
938 
939  vector<char>::iterator ii_ex (trans_ex.begin()), ii_ex_x;
940  size_t cons_dels (0);
941  const size_t max_cons_dels (25);
942  while(tr_idx >= tr_idx_lo && transcript[tr_idx] < CNWAligner::eTS_Intron) {
943 
944  bool noins (transcript[tr_idx] != CNWAligner::eTS_Insert);
945  bool nodel (transcript[tr_idx] != CNWAligner::eTS_Delete);
946  if(noins && nodel) {
947 
948  if(cons_dels > max_cons_dels) {
949  break;
950  }
951 
952  cons_dels = 0;
953 
954  if(toupper(*p1) != 'N' && *p1 == *p2) {
955  ++matches;
956  *ii_ex++ = 'M';
957  }
958  else {
959  *ii_ex++ = 'R';
960  }
961  ++p1;
962  ++p2;
963  } else if(noins) {
964 
965  if(cons_dels == 0) {
966  p1_x = p1;
967  ii_ex_x = ii_ex;
968  exon_aln_size_x = exon_aln_size;
969  tr_idx_x = tr_idx;
970  }
971 
972  ++p1;
973  *ii_ex++ = 'D';
974  ++cons_dels;
975  } else {
976 
977  ++p2;
978  *ii_ex++ = 'I';
979  cons_dels = 0;
980  }
981  --tr_idx;
982  ++exon_aln_size;
983  }
984 
985  if(cons_dels > max_cons_dels) {
986  swap(p1, p1_x);
987  swap(ii_ex, ii_ex_x);
988  swap(exon_aln_size, exon_aln_size_x);
989  swap(tr_idx, tr_idx_x);
990  }
991 
992  if(exon_aln_size > 0) {
993 
994  segments.push_back(SSegment());
995  SSegment& s = segments.back();
996 
997  s.m_exon = true;
998  s.m_idty = float(matches) / exon_aln_size;
999  s.m_len = exon_aln_size;
1000 
1001  size_t beg1 (p1_beg - start1), end1 (p1 - start1 - 1);
1002  size_t beg2 (p2_beg - start2), end2 (p2 - start2 - 1);
1003 
1004  s.m_box[0] = beg1;
1005  s.m_box[1] = end1;
1006  s.m_box[2] = beg2;
1007  s.m_box[3] = end2;
1008 
1009  char c1 ((p2_beg >= start2 + 2)? *(p2_beg - 2): ' ');
1010  char c2 ((p2_beg >= start2 + 1)? *(p2_beg - 1): ' ');
1011  char c3 ((p2 < start2 + len2)? *(p2): ' ');
1012  char c4 ((p2 < start2 + len2 - 1)? *(p2+1): ' ');
1013 
1014  s.m_annot.resize(10);
1015  s.m_annot[0] = c1;
1016  s.m_annot[1] = c2;
1017  const string s_exontag ("<exon>");
1018  copy(s_exontag.begin(), s_exontag.end(), s.m_annot.begin() + 2);
1019  s.m_annot[8] = c3;
1020  s.m_annot[9] = c4;
1021  s.m_details.resize(ii_ex - trans_ex.begin());
1022  copy(trans_ex.begin(), ii_ex, s.m_details.begin());
1023  s.Update(m_aligner);
1024  }
1025 
1026  if(cons_dels > max_cons_dels) {
1027 
1028  segments.push_back(SSegment());
1029  SSegment& s (segments.back());
1030 
1031  s.m_exon = false;
1032  s.m_idty = 0;
1033  s.m_len = exon_aln_size_x - exon_aln_size;
1034 
1035  size_t beg1 (p1 - start1), end1 (p1_x - start1 - 1);
1036  size_t beg2 (0), end2 (0);
1037 
1038  s.m_box[0] = beg1;
1039  s.m_box[1] = end1;
1040  s.m_box[2] = beg2;
1041  s.m_box[3] = end2;
1042 
1043  s.m_annot = "<gap>";
1044  s.m_details.resize(ii_ex_x - ii_ex);
1045  copy(ii_ex, ii_ex_x, s.m_details.begin());
1046 
1047  swap(p1, p1_x);
1048  swap(ii_ex, ii_ex_x);
1049  swap(exon_aln_size, exon_aln_size_x);
1050  swap(tr_idx, tr_idx_x);
1051  }
1052 
1053  if(tr_idx<tr_idx_lo || transcript[tr_idx] == CNWAligner::eTS_SlackInsert
1054  || transcript[tr_idx] == CNWAligner::eTS_SlackDelete)
1055  {
1056  break;
1057  }
1058 
1059  // find next exon
1060  while(tr_idx >= tr_idx_lo && (transcript[tr_idx] == CNWAligner::eTS_Intron)) {
1061  --tr_idx;
1062  ++p2;
1063  }
1064  }
1065 }
1066 
1067 
1068 void CNWFormatter::AsText(string* output, ETextFormatType type, size_t line_width)
1069  const
1070 {
1071  CNcbiOstrstream ss;
1072 
1073  const CNWAligner::TTranscript transcript = m_aligner->GetTranscript();
1074  if(transcript.size() == 0) {
1076  eNoSeqData,
1078  }
1079 
1080  const string strid_query = m_Seq1Id->GetSeqIdString(true);
1081  const string strid_subj = m_Seq2Id->GetSeqIdString(true);
1082 
1083  switch (type) {
1084 
1085  case eFormatType1: {
1086 
1087  ss << '>' << strid_query << '\t' << strid_subj << endl;
1088 
1089  vector<char> v1, v2;
1090  unsigned i1 (0), i2 (0);
1091  size_t aln_size (x_ApplyTranscript(&v1, &v2));
1092  for (size_t i = 0; i < aln_size; ) {
1093 
1094  ss << i << '\t' << i1 << ':' << i2 << endl;
1095  Int8 i0 = i;
1096  for (size_t jPos = 0; i < aln_size && jPos < line_width; ++i, ++jPos) {
1097  char c1 (v1[i0 + jPos]);
1098  ss << c1;
1099  if(c1 != '-' && c1 != 'x' && c1 != '+') ++i1;
1100  }
1101  ss << endl;
1102 
1103  string marker_line(line_width, ' ');
1104  i = i0;
1105  for (size_t jPos = 0; i < aln_size && jPos < line_width; ++i, ++jPos) {
1106  char c1 (v1[i0 + jPos]);
1107  char c2 (v2[i0 + jPos]);
1108  ss << c2;
1109  if(c2 != '-' && c2 != '+' && c2 != 'x')
1110  i2++;
1111  if( c2 != '-' && c1 != '-' && c1 != '+' && c1 != 'x' && ( toupper(c2) != toupper(c1) || m_aligner->GetScoreMatrix().s[(size_t)c1][(size_t)c2] <= 0 ))
1112  marker_line[jPos] = '^';
1113  }
1114  ss << endl << marker_line << endl;
1115  }
1116  }
1117  break;
1118 
1119  case eFormatType2: {
1120 
1121  ss << '>' << strid_query << '\t' << strid_subj << endl;
1122 
1123  vector<char> v1, v2;
1124  unsigned i1 (0), i2 (0);
1125  size_t aln_size (x_ApplyTranscript(&v1, &v2));
1126  for (size_t i = 0; i < aln_size; ) {
1127  ss << i << '\t' << i1 << ':' << i2 << endl;
1128  Int8 i0 = i;
1129  for (size_t jPos = 0; i < aln_size && jPos < line_width; ++i, ++jPos) {
1130  char c (v1[i0 + jPos]);
1131  ss << c;
1132  if(c != '-' && c != '+' && c != 'x') ++i1;
1133  }
1134  ss << endl;
1135 
1136  string line2 (line_width, ' ');
1137  string line3 (line_width, ' ');
1138  i = i0;
1139  for (size_t jPos = 0; i < aln_size && jPos < line_width; ++i, ++jPos) {
1140  char c1 (v1[i0 + jPos]);
1141  char c2 (v2[i0 + jPos]);
1142  if(c2 != '-' && c2 != '+' && c2 != 'x') i2++;
1143  if( toupper(c2) == toupper(c1) && m_aligner-> GetScoreMatrix().s[(size_t)c1][(size_t)c2] > 0 ) line2[jPos] = '|';
1144  line3[jPos] = c2;
1145  }
1146  ss << line2 << endl << line3 << endl << endl;
1147  }
1148  }
1149  break;
1150 
1151  case eFormatAsn: {
1152 
1153  CRef<CSeq_align> sa = AsSeqAlign();
1154  CObjectOStreamAsn asn_stream (ss);
1155  asn_stream << *sa;
1156  asn_stream << Separator;
1157  }
1158  break;
1159 
1160  case eFormatDenseSeg: {
1161 
1162  CRef<CDense_seg> ds = AsDenseSeg();
1163  CObjectOStreamAsn asn_stream (ss);
1164  asn_stream << *ds;
1165  asn_stream << Separator;
1166  }
1167  break;
1168 
1169  case eFormatFastA: {
1170  vector<char> v1, v2;
1171  size_t aln_size (x_ApplyTranscript(&v1, &v2));
1172 
1173  ss << '>' << strid_query << endl;
1174  const vector<char>* pv = &v1;
1175  for(size_t i = 0; i < aln_size; ) {
1176  for(size_t j = 0; j < line_width && i < aln_size; ++j, ++i) {
1177  ss << (*pv)[i];
1178  }
1179  ss << endl;
1180  }
1181 
1182  ss << '>' << strid_subj << endl;
1183  pv = &v2;
1184  for(size_t i = 0; i < aln_size; ) {
1185  for(size_t j = 0; j < line_width && i < aln_size; ++j, ++i) {
1186  ss << (*pv)[i];
1187  }
1188  ss << endl;
1189  }
1190  }
1191  break;
1192 
1193  case eFormatExonTable:
1194  case eFormatExonTableEx: {
1195 
1196  ss.precision(3);
1197 
1198  typedef deque<SSegment> TSegments;
1199  TSegments segments;
1200  MakeSegments(&segments);
1201  ITERATE(TSegments, ii, segments) {
1202 
1203  ss << strid_query << '\t' << strid_subj << '\t';
1204  ss << ii->m_idty << '\t' << ii->m_len << '\t';
1205  copy(ii->m_box, ii->m_box + 4,
1206  ostream_iterator<size_t>(ss,"\t"));
1207  ss << '\t' << ii->m_annot;
1208  if(type == eFormatExonTableEx) {
1209  ss << '\t' << ii->m_details;
1210  }
1211  ss << endl;
1212  }
1213  }
1214  break;
1215 
1216  default:
1217  NCBI_THROW(CAlgoAlignException, eBadParameter, "Incorrect format specified");
1218  }
1219 
1220  *output = CNcbiOstrstreamToString(ss);
1221 }
1222 
1223 
1224 
1225 // Transform source sequences according to the transcript.
1226 // cut flank gaps for Smith-Waterman
1227 // Write the results to v1 and v2 leaving source sequences intact.
1228 // Return alignment size.
1229 size_t CNWFormatter::x_ApplyTranscript(vector<char>* pv1, vector<char>* pv2)
1230  const
1231 {
1232  const CNWAligner::TTranscript transcript = m_aligner->GetTranscript();
1233 
1234  vector<char>& v1 (*pv1);
1235  vector<char>& v2 (*pv2);
1236 
1237  v1.clear();
1238  v2.clear();
1239 
1240  if(transcript.size() == 0) {
1241  return 0;
1242  }
1243 
1244 
1245  vector<CNWAligner::ETranscriptSymbol>::const_reverse_iterator
1246  ib = transcript.rbegin(),
1247  ie = transcript.rend(),
1248  ii;
1249 
1250  if( m_aligner->IsSmithWaterman() ) {
1251  --ie;
1252  while( ( ie != ib ) &&
1253  ( *ie == CNWAligner::eTS_Insert || *ie == CNWAligner::eTS_Delete || *ie == CNWAligner::eTS_Intron ||
1254  *ie == CNWAligner::eTS_SlackInsert || *ie == CNWAligner::eTS_SlackDelete ) ) {
1255  --ie;
1256  }
1257  }
1258 
1259  const char* iv1 (m_aligner->GetSeq1());
1260  const char* iv2 (m_aligner->GetSeq2());
1261 
1262  bool sw_ini_gap = false;
1263  if( m_aligner->IsSmithWaterman() ) {
1264  sw_ini_gap = true;
1265  }
1266 
1267  for (ii = ib; ii != ie; ii++) {
1268 
1269  CNWAligner::ETranscriptSymbol ts (*ii);
1270  char c1, c2;
1271  switch ( ts ) {
1272 
1273  case CNWAligner::eTS_Insert:
1274  c1 = '-';
1275  c2 = *iv2++;
1276  break;
1277 
1278  case CNWAligner::eTS_SlackInsert:
1279  c1 = 'x';
1280  c2 = *iv2++;
1281  break;
1282 
1283  case CNWAligner::eTS_Delete:
1284  c2 = '-';
1285  c1 = *iv1++;
1286  break;
1287 
1288 
1289  case CNWAligner::eTS_SlackDelete:
1290  c2 = 'x';
1291  c1 = *iv1++;
1292  break;
1293 
1294  case CNWAligner::eTS_Match:
1295  case CNWAligner::eTS_Replace:
1296  sw_ini_gap = false;
1297  c1 = *iv1++;
1298  c2 = *iv2++;
1299  break;
1300 
1301  case CNWAligner::eTS_Intron:
1302  c1 = '+';
1303  c2 = *iv2++;
1304  break;
1305 
1306  default:
1307  sw_ini_gap = false;
1308  c1 = c2 = '?';
1309  break;
1310  }
1311  if( !sw_ini_gap ) {
1312  v1.push_back(c1);
1313  v2.push_back(c2);
1314  }
1315  }
1316 
1317  return v1.size();
1318 }
1319 
1320 
1321 END_NCBI_SCOPE
User-defined methods of the data storage class.
void TrimEndGaps()
Trim leading/training gaps if possible.
Definition: Dense_seg.cpp:344
void FromTranscript(TSeqPos query_start, ENa_strand query_strand, TSeqPos subj_start, ENa_strand subj_strand, const string &transcript)
Initialize from pairwise alignment transcript (a string representation produced by CNWAligner)
Definition: Dense_seg.cpp:1273
Definition: Score.hpp:57
container_type::iterator iterator
Definition: map.hpp:54
const_iterator begin() const
Definition: map.hpp:151
const_iterator end() const
Definition: map.hpp:152
Definition: map.hpp:338
#define head
Definition: ct_nlmzip_i.h:138
static uch flags
void ImproveFromLeft(const char *seq1, const char *seq2, CConstRef< CSplicedAligner > aligner)
void ExtendRight(const vector< char > &mrna, const vector< char > &genomic, Int8 ext_len, const CNWAligner *aligner)
CRef< objects::CDense_seg > AsDenseSeg(TSeqPos query_start=0, objects::ENa_strand query_strand=objects::eNa_strand_plus, TSeqPos subj_start=0, objects::ENa_strand subj_strand=objects::eNa_strand_plus, int SAFF_flags=eSAFF_None) const
void ImproveFromRight(const char *seq1, const char *seq2, CConstRef< CSplicedAligner > aligner)
CNWFormatter(const CNWAligner &aligner)
void AsText(string *output, ETextFormatType type, size_t line_width=100) const
void MakeSegments(vector< SSegment > *psegments) const
const CNWAligner * m_aligner
int CanExtendRight(const vector< char > &mrna, const vector< char > &genomic) const
const char * GetDonor(void) const
void ImproveFromLeft1(const char *seq1, const char *seq2, CConstRef< CSplicedAligner > aligner)
CRef< objects::CSeq_align > AsSeqAlign(TSeqPos query_start=0, objects::ENa_strand query_strand=objects::eNa_strand_plus, TSeqPos subj_start=0, objects::ENa_strand subj_strand=objects::eNa_strand_plus, int SAFF_flags=eSAFF_None) const
void ImproveFromRight1(const char *seq1, const char *seq2, CConstRef< CSplicedAligner > aligner)
CConstRef< objects::CSeq_id > m_Seq2Id
static bool s_IsConsensusSplice(const char *donor, const char *acceptor, bool semi_as_cons=false)
const char * GetAcceptor(void) const
int CanExtendLeft(const vector< char > &mrna, const vector< char > &genomic) const
CConstRef< objects::CSeq_id > m_Seq1Id
void SetSeqIds(CConstRef< objects::CSeq_id > id1, CConstRef< objects::CSeq_id > id2)
void Update(const CNWAligner *aligner)
void ExtendLeft(const vector< char > &mrna, const vector< char > &genomic, Int8 ext_len, const CNWAligner *aligner)
bool IsLowComplexityExon(const char *rna_seq)
TScore GetWm(void) const
Definition: nw_aligner.hpp:164
const char * GetSeq2(void) const
Definition: nw_aligner.hpp:171
TTranscript GetTranscript(bool reversed=true) const
Definition: nw_aligner.cpp:909
const char * GetSeq1(void) const
Definition: nw_aligner.hpp:169
bool IsSmithWaterman() const
Definition: nw_aligner.cpp:898
size_t GetSeqLen2(void) const
Definition: nw_aligner.hpp:172
TScore GetScore(void) const
string GetTranscriptString(void) const
Definition: nw_aligner.cpp:931
vector< ETranscriptSymbol > TTranscript
Definition: nw_aligner.hpp:199
void GetEndSpaceFree(bool *L1, bool *R1, bool *L2, bool *R2) const
Definition: nw_aligner.cpp:890
unsigned int TSeqPos
Type for sequence locations and lengths.
Definition: ncbimisc.hpp:875
#define ITERATE(Type, Var, Cont)
ITERATE macro to sequence through container elements.
Definition: ncbimisc.hpp:815
void swap(NCBI_NS_NCBI::pair_base_member< T1, T2 > &pair1, NCBI_NS_NCBI::pair_base_member< T1, T2 > &pair2)
Definition: ncbimisc.hpp:1508
#define NCBI_THROW(exception_class, err_code, message)
Generic macro to throw an exception, given the exception class, error code and message string.
Definition: ncbiexpt.hpp:704
const float epsilon
Definition: math.hpp:61
virtual void Assign(const CSerialObject &source, ESerialRecursionMode how=eRecursive)
Optimized implementation of CSerialObject::Assign, which is not so efficient.
Definition: Seq_id.cpp:318
void Reset(void)
Reset reference object.
Definition: ncbiobj.hpp:773
TObjectType * GetNonNullPointer(void) const
Get pointer value and throw a null pointer exception if pointer is null.
Definition: ncbiobj.hpp:1654
int64_t Int8
8-byte (64-bit) signed integer
Definition: ncbitype.h:104
#define BEGIN_NCBI_SCOPE
Define ncbi namespace.
Definition: ncbistl.hpp:100
TStr & SetStr(void)
Select the variant.
Definition: Object_id_.hpp:304
TScore & SetScore(void)
Assign a value to Score data member.
Definition: Seq_align_.hpp:902
void SetSegs(TSegs &value)
Assign a value to Segs data member.
Definition: Seq_align_.cpp:310
void SetDim(TDim value)
Assign a value to Dim data member.
Definition: Seq_align_.hpp:865
void SetType(TType value)
Assign a value to Type data member.
Definition: Seq_align_.hpp:818
vector< CRef< CSeq_id > > TIds
Definition: Dense_seg_.hpp:106
TIds & SetIds(void)
Assign a value to Ids data member.
Definition: Dense_seg_.hpp:511
@ eType_partial
mapping pieces together
Definition: Seq_align_.hpp:103
ENa_strand
strand of nucleic acid
Definition: Na_strand_.hpp:64
TLocal & SetLocal(void)
Select the variant.
Definition: Seq_id_.cpp:199
unsigned int
A callback function used to compare two keys in a database.
Definition: types.hpp:1210
int i
int len
EIPRangeType t
Definition: ncbi_localip.c:101
int toupper(Uchar c)
Definition: ncbictype.hpp:73
T max(T x_, T y_)
void copy(Njn::Matrix< S > *matrix_, const Njn::Matrix< T > &matrix0_)
Definition: njn_matrix.hpp:613
USING_SCOPE(objects)
static const char s_kGap[]
static unsigned cnt[256]
static int match(register const pcre_uchar *eptr, register const pcre_uchar *ecode, const pcre_uchar *mstart, int offset_top, match_data *md, eptrblock *eptrb, unsigned int rdepth)
Definition: pcre_exec.c:513
static SQLCHAR output[256]
Definition: print.c:5
const char g_msg_NoAlignment[]
Definition: messages.hpp:38
Definition: type.c:6
Modified on Fri Dec 08 08:22:16 2023 by modify_doxy.py rev. 669887