]> git.donarmstrong.com Git - biopieces.git/blob - code_c/Maasha/src/bipartite_scan.c
fixed 0 offset bug in remove_adaptor
[biopieces.git] / code_c / Maasha / src / bipartite_scan.c
1 #include "common.h"
2 #include "mem.h"
3 #include "filesys.h"
4 #include "seq.h"
5 #include "fasta.h"
6 #include "list.h"
7
8 #define BLOCK_SIZE_NT     4                         /* one block holds 4 nucleotides. */
9 #define BITS_IN_NT        2                         /* two bits holds 1 nucleotide. */
10 #define BITS_IN_BYTE      8                         /* number of bits in one byte. */
11 #define BLOCK_SPACE_MAX   2                         /* maximum space between two blocks. */
12 #define BLOCK_MASK        ( BLOCK_SPACE_MAX - 1 )   /* mask for printing block space. */
13 #define COUNT_ARRAY_NMEMB ( 1 << 30 )               /* number of objects in the unsigned int count array. */
14 #define CUTOFF            1                         /* minimum number of motifs in output. */
15  
16 #define add_A( c )              /* add 00 to the rightmost two bits of bin (i.e. do nothing). */
17 #define add_T( c ) ( c |= 3 )   /* add 11 on the rightmost two bits of c. */
18 #define add_C( c ) ( c |= 1 )   /* add 01 on the rightmost two bits of c. */
19 #define add_G( c ) ( c |= 2 )   /* add 10 on the rightmost two bits of c. */
20
21 /* Structure that will hold one tetra nucleotide block. */
22 struct _bitblock
23 {
24     uchar bin;   /* Tetra nucleotide binary encoded. */
25     bool hasN;   /* Flag indicating any N's in the block. */
26 };
27
28 typedef struct _bitblock bitblock;
29
30 /* Byte array for fast convertion of binary blocks back to DNA. */
31 char *bin2dna[256] = {
32     "AAAA", "AAAC", "AAAG", "AAAT", "AACA", "AACC", "AACG", "AACT",
33     "AAGA", "AAGC", "AAGG", "AAGT", "AATA", "AATC", "AATG", "AATT",
34     "ACAA", "ACAC", "ACAG", "ACAT", "ACCA", "ACCC", "ACCG", "ACCT",
35     "ACGA", "ACGC", "ACGG", "ACGT", "ACTA", "ACTC", "ACTG", "ACTT",
36     "AGAA", "AGAC", "AGAG", "AGAT", "AGCA", "AGCC", "AGCG", "AGCT",
37     "AGGA", "AGGC", "AGGG", "AGGT", "AGTA", "AGTC", "AGTG", "AGTT",
38     "ATAA", "ATAC", "ATAG", "ATAT", "ATCA", "ATCC", "ATCG", "ATCT",
39     "ATGA", "ATGC", "ATGG", "ATGT", "ATTA", "ATTC", "ATTG", "ATTT",
40     "CAAA", "CAAC", "CAAG", "CAAT", "CACA", "CACC", "CACG", "CACT",
41     "CAGA", "CAGC", "CAGG", "CAGT", "CATA", "CATC", "CATG", "CATT",
42     "CCAA", "CCAC", "CCAG", "CCAT", "CCCA", "CCCC", "CCCG", "CCCT",
43     "CCGA", "CCGC", "CCGG", "CCGT", "CCTA", "CCTC", "CCTG", "CCTT",
44     "CGAA", "CGAC", "CGAG", "CGAT", "CGCA", "CGCC", "CGCG", "CGCT",
45     "CGGA", "CGGC", "CGGG", "CGGT", "CGTA", "CGTC", "CGTG", "CGTT",
46     "CTAA", "CTAC", "CTAG", "CTAT", "CTCA", "CTCC", "CTCG", "CTCT",
47     "CTGA", "CTGC", "CTGG", "CTGT", "CTTA", "CTTC", "CTTG", "CTTT",
48     "GAAA", "GAAC", "GAAG", "GAAT", "GACA", "GACC", "GACG", "GACT",
49     "GAGA", "GAGC", "GAGG", "GAGT", "GATA", "GATC", "GATG", "GATT",
50     "GCAA", "GCAC", "GCAG", "GCAT", "GCCA", "GCCC", "GCCG", "GCCT",
51     "GCGA", "GCGC", "GCGG", "GCGT", "GCTA", "GCTC", "GCTG", "GCTT",
52     "GGAA", "GGAC", "GGAG", "GGAT", "GGCA", "GGCC", "GGCG", "GGCT",
53     "GGGA", "GGGC", "GGGG", "GGGT", "GGTA", "GGTC", "GGTG", "GGTT",
54     "GTAA", "GTAC", "GTAG", "GTAT", "GTCA", "GTCC", "GTCG", "GTCT",
55     "GTGA", "GTGC", "GTGG", "GTGT", "GTTA", "GTTC", "GTTG", "GTTT",
56     "TAAA", "TAAC", "TAAG", "TAAT", "TACA", "TACC", "TACG", "TACT",
57     "TAGA", "TAGC", "TAGG", "TAGT", "TATA", "TATC", "TATG", "TATT",
58     "TCAA", "TCAC", "TCAG", "TCAT", "TCCA", "TCCC", "TCCG", "TCCT",
59     "TCGA", "TCGC", "TCGG", "TCGT", "TCTA", "TCTC", "TCTG", "TCTT",
60     "TGAA", "TGAC", "TGAG", "TGAT", "TGCA", "TGCC", "TGCG", "TGCT",
61     "TGGA", "TGGC", "TGGG", "TGGT", "TGTA", "TGTC", "TGTG", "TGTT",
62     "TTAA", "TTAC", "TTAG", "TTAT", "TTCA", "TTCC", "TTCG", "TTCT",
63     "TTGA", "TTGC", "TTGG", "TTGT", "TTTA", "TTTC", "TTTG", "TTTT"
64 };
65
66 /* Function declarations. */
67 void      run_scan( int argc, char *argv[] );
68 void      print_usage();
69 void      scan_file( char *file, seq_entry *entry, uint *count_array );
70 uint     *count_array_new( size_t nmemb );
71 void      scan_seq( char *seq, size_t seq_len, uint *count_array );
72 void      scan_list( list_sl *list, uint *count_array );
73 bitblock *bitblock_new();
74 uint      blocks2motif( uchar bin1, uchar bin2, ushort dist );
75 size_t    filter_results( uint **array_ppt, size_t nmemb, uint cutoff );
76 int       cmp_uint_desc( const void *a, const void *b );
77 void      count_array_print( uint *count_array, size_t nmemb );
78 void      motif_print( uint motif, uint count );
79 void      bitblock_list_print( list_sl *list );
80 void      bitblock_print( bitblock *out );
81
82 /* Unit test declarations. */
83 static void run_tests();
84 static void test_count_array_new();
85 static void test_bitblock_new();
86 static void test_bitblock_print();
87 static void test_bitblock_list_print();
88 static void test_scan_seq();
89 static void test_filter_results();
90 static void test_array_sort();
91 static void test_blocks2motif();
92
93
94 /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> MAIN <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
95
96
97 int main( int argc, char *argv[] )
98 {
99     run_tests();
100
101     if ( argc == 1 ) {
102         print_usage();
103     }
104
105     run_scan( argc, argv );
106
107     return EXIT_SUCCESS;
108 }
109
110
111 /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> FUNCTIONS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
112
113
114 void print_usage()
115 {
116     /* Martin A. Hansen, September 2008 */
117
118     /* Print usage and exit if no files in argument. */
119
120     fprintf( stderr,
121         "Usage: bipartite_scam <FASTA file(s)> > result.csv\n"        
122     );
123
124     exit( EXIT_SUCCESS );
125 }
126
127
128 void run_scan( int argc, char *argv[] )
129 {
130     /* Martin A. Hansen, September 2008 */
131
132     /* For each file in argv scan the file for */
133     /* bipartite motifs and output the motifs */
134     /* and their count. */
135
136     char      *file        = NULL;
137     int        i           = 0;
138     seq_entry *entry       = NULL;
139     uint      *count_array = NULL;
140     size_t    new_nmemb    = 0;
141
142     count_array = count_array_new( COUNT_ARRAY_NMEMB );
143
144     entry = seq_new( MAX_SEQ_NAME, MAX_SEQ );
145
146     for ( i = 1; i < argc; i++ )
147     {
148         file = argv[ i ];
149
150         fprintf( stderr, "Scanning file: %s\n", file );
151         scan_file( file, entry, count_array );
152         fprintf( stderr, "done.\n" );
153     }
154
155     fprintf( stderr, "Filtering results (cutoff=%d) ... ", CUTOFF );
156     new_nmemb = filter_results( &count_array, COUNT_ARRAY_NMEMB, CUTOFF );
157     fprintf( stderr, "done.\n" );
158
159     fprintf( stderr, "Sorting motifs: ... " );
160     qsort( count_array, new_nmemb, sizeof( uint ), cmp_uint_desc );
161     fprintf( stderr, "done.\n" );
162
163     fprintf( stderr, "Printing motifs: ... " );
164     count_array_print( count_array, new_nmemb );
165     fprintf( stderr, "done.\n" );
166
167     seq_destroy( entry );
168
169     mem_free( &count_array );
170 }
171
172
173 uint *count_array_new( size_t nmemb )
174 {
175     /* Martin A. Hansen, September 2008 */
176
177     /* Initialize a new zeroed uint array of nmemb objects. */
178
179     uint *array = NULL;
180
181     assert( nmemb > 0 );
182
183     array = mem_get_zero( nmemb * sizeof( uint ) );
184
185     return array;
186 }
187
188
189 void scan_file( char *file, seq_entry *entry, uint *count_array )
190 {
191     /* Martin A. Hansen, September 2008 */
192     
193     /* Scan all FASTA entries of a file in both */
194     /* sense and anti-sense directions. */
195
196     FILE *fp = read_open( file );
197
198     while ( fasta_get_entry( fp, &entry ) == TRUE )
199     {
200         fprintf( stderr, "   Scanning: %s (%zu nt) ... ", entry->seq_name, entry->seq_len );
201     
202         scan_seq( entry->seq, entry->seq_len, count_array );
203
204         fprintf( stderr, "done.\n" );
205     }
206
207     close_stream( fp );
208 }
209
210
211 void scan_seq( char *seq, size_t seq_len, uint *count_array )
212 {
213     /* Martin A. Hansen, September 2008 */
214
215     /* Run a sliding window over a given sequence. The window */
216     /* consists of a list where new blocks of 4 nucleotides */
217     /* are pushed onto one end while at the same time old */
218     /* blocks are popped from the other end. The number of */
219     /* in the list is determined by the maximum seperator. */
220     /* Everytime we have a full window, the window is scanned */
221     /* for motifs. */
222  
223     bitblock *block      = NULL;
224     size_t    b_count    = 0;
225     ushort    n_count    = 0;
226     size_t    i          = 0;
227     uchar     bin        = 0;
228     bool      first_node = TRUE;
229     node_sl *new_node    = NULL;
230     node_sl *old_node    = NULL;
231     list_sl *list        = list_sl_new();
232
233     for ( i = 0; seq[ i ]; i++ )
234     {
235         bin <<= BITS_IN_NT;
236
237         switch( seq[ i ] )
238         {
239             case 'A': case 'a': add_A( bin ); break;
240             case 'T': case 't': add_T( bin ); break;
241             case 'C': case 'c': add_C( bin ); break;
242             case 'G': case 'g': add_G( bin ); break;
243             default: n_count = BLOCK_SIZE_NT; break;
244         }
245
246         if ( i > BLOCK_SIZE_NT - 2 )
247         {
248             b_count++;
249
250             block      = bitblock_new();
251             block->bin = bin;
252
253             if ( n_count > 0 )
254             {
255                  block->hasN = TRUE;
256                  n_count--;
257             }
258
259             new_node      = node_sl_new();
260             new_node->val = block;
261
262             if ( first_node ) {
263                 list_sl_add_beg( &list, &new_node );
264             } else {
265                 list_sl_add_after( &old_node, &new_node );
266             }
267
268             old_node = new_node;
269
270             first_node = FALSE;
271
272             if ( b_count > BLOCK_SPACE_MAX + BLOCK_SIZE_NT )
273             {
274                 // bitblock_list_print( list );   /* DEBUG */
275
276                 scan_list( list, count_array );
277
278                 list_sl_remove_beg( &list );
279             }
280         }
281     }
282
283     /* if the list is shorter than BLOCK_SPACE_MAX + BLOCK_SIZE_NT */
284     if ( b_count <= BLOCK_SPACE_MAX + BLOCK_SIZE_NT )
285     {
286         // bitblock_list_print( list );  /* DEBUG */
287
288         scan_list( list, count_array );
289     }
290
291     list_sl_destroy( &list );
292 }
293
294
295 void scan_list( list_sl *list, uint *count_array )
296 {
297     /* Martin A. Hansen, September 2008 */
298
299     /* Scan a list of blocks for biparite motifs by creating */
300     /* a binary motif consisting of two blocks of 4 nucleotides */
301     /* along with the distance separating them. Motifs containing */
302     /* N's are skipped. */
303
304     node_sl  *first_node = NULL;
305     node_sl  *next_node  = NULL;
306     bitblock *block1     = NULL;
307     bitblock *block2     = NULL;
308     int       i          = 0;
309     ushort    dist       = 0;
310     uint      motif_bin  = 0;
311
312 //    bitblock_list_print( list );
313
314     first_node = list->first;
315
316     block1 = ( bitblock * ) first_node->val;
317
318     if ( ! block1->hasN )
319     {
320         next_node = first_node->next;
321
322         for ( i = 0; i < BLOCK_SIZE_NT - 1; i++ ) {
323             next_node = next_node->next;
324         }
325
326         for ( next_node = next_node; next_node != NULL; next_node = next_node->next )
327         {
328             block2 = ( bitblock * ) next_node->val;
329         
330 //             printf( "block1: %s  block2: %s   dist: %d\n", bin2dna[ block1->bin ], bin2dna[ block2->bin ], dist ); /* DEBUG */
331
332             if ( ! block2->hasN )
333             {
334                 motif_bin = blocks2motif( block1->bin, block2->bin, dist );
335
336                 // motif_print( motif_bin, 0 ); /* DEBUG */
337                 // bitblock_list_print( list ); /* DEBUG */
338
339                 count_array[ motif_bin ]++;
340             }
341
342             dist++;
343         }
344     }
345 }
346
347
348 bitblock *bitblock_new()
349 {
350     /* Martin A. Hansen, September 2008 */
351
352     /* Initializes a new empty bitblock. */
353
354     bitblock *new_block = NULL;
355
356     new_block = mem_get( sizeof( bitblock ) );
357
358     new_block->bin  = 0;
359     new_block->hasN = FALSE;
360
361     return new_block;
362 }
363
364
365 uint blocks2motif( uchar bin1, uchar bin2, ushort dist )
366 {
367     /* Martin A. Hansen, September 2008 */
368
369     /* Given two binary encoded tetra nuceotide blocks, */
370     /* and the distance separating this, create a binary */
371     /* bipartite motif. */
372
373     uint motif = 0;
374
375     motif |= bin1;
376
377     motif <<= sizeof( uchar ) * BITS_IN_BYTE;
378
379     motif |= bin2;
380
381     motif <<= sizeof( uchar ) * BITS_IN_BYTE;
382
383     motif |= dist;
384
385     return motif;
386 }
387
388
389 size_t filter_results( uint **array_ppt, size_t nmemb, uint cutoff )
390 {
391     /* Martin A. Hansen, September 2008 */
392
393     /* Iterates an array of bipartite scan results */
394     /* determining how many elements are above the cutoff, */
395     /* then allocate memory for a new array, copy the */
396     /* values that were above the cutoff to the new array. */
397     /* The number of elements on the new array is returned, */
398     /* and the array is replaced. */
399
400     uint *array      = ( uint * ) *array_ppt;
401     uint *new_array  = NULL;
402     size_t i         = 0;
403     size_t j         = 0;
404     size_t new_nmemb = 0;
405
406     for ( i = 0; i < nmemb; i++ )
407     {
408         if ( array[ i ] >= cutoff ) {
409             new_nmemb++;
410         }
411     }
412
413     assert( new_nmemb > 0 );
414
415     new_array = count_array_new( new_nmemb );
416
417     for ( i = 0; i < nmemb; i++ )
418     {
419         if ( array[ i ] >= cutoff )
420         {
421             new_array[ j ] = array[ i ];
422
423             j++;
424         }
425     }
426
427     *array_ppt = new_array;
428
429     return new_nmemb;
430 }
431
432
433 int cmp_uint_desc( const void *a, const void *b )
434 {
435     /* Martin A. Hansen, September 2008 */
436
437     /* Compare function for qsort of an array of unsigned ints */
438     /* to be sorted in descending order. */
439
440     const uint *uint_a = ( const uint *) a;
441     const uint *uint_b = ( const uint *) b;
442
443     return *uint_b - *uint_a; 
444 }
445
446
447 void count_array_print( uint *count_array, size_t nmemb )
448 {
449     /* Martin A. Hansen, Seqptember 2008. */
450
451     /* Print all bipartite motifs in count_array as */
452     /* tabular output. */
453
454     uint i     = 0;
455     uint motif = 0;
456     uint count = 0;
457
458     for ( i = 0; i < nmemb; i++ )
459     {
460         motif = i;
461         count = count_array[ i ];
462
463         motif_print( motif, count );
464     }
465 }
466
467
468 void motif_print( uint motif, uint count )
469 {
470     /* Martin A. Hansen, September 2008 */
471
472     /* Converts a binary encoded bipartite motif */
473     /* into DNA and output the motif, distance and */
474     /* count seperated by tabs: */
475     /* BLOCK1 \t BLOCK2 \t DIST \t COUNT */
476
477     uchar  bin1 = 0;
478     uchar  bin2 = 0;
479     ushort dist = 0;
480
481     dist = ( ushort ) motif & BLOCK_MASK;
482
483     motif >>= sizeof( uchar ) * BITS_IN_BYTE;
484
485     bin2 = ( uchar ) motif;
486
487     motif >>= sizeof( uchar ) * BITS_IN_BYTE;
488
489     bin1 = ( uchar ) motif;
490
491     printf( "%s\t%s\t%d\t%d\n", bin2dna[ bin1 ], bin2dna[ bin2 ], dist, count );
492 }
493
494
495 void bitblock_list_print( list_sl *list )
496 {
497     /* Martin A. Hansen, September 2008 */
498
499     /* Debug function to print all blocks in a list. */
500
501     node_sl *node = NULL;
502
503     printf( "\nbitblock_list_print:\n" );
504
505     for ( node = list->first; node != NULL; node = node->next ) {
506         bitblock_print( ( bitblock * ) node->val );
507     }
508 }
509
510
511 void bitblock_print( bitblock *out )
512 {
513     /* Martin A. Hansen, September 2008 */
514
515     /* Debug function to print a given block. */
516
517     printf( "bin: %d  dna: %s   hasN: %d\n", out->bin, bin2dna[ ( int ) out->bin ], out->hasN );
518 }
519
520
521 /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UNIT TESTS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
522
523
524 void run_tests()
525 {
526     fprintf( stderr, "Running tests\n" );
527
528     test_count_array_new();
529     test_bitblock_new();
530     test_bitblock_print();
531     test_bitblock_list_print();
532     test_scan_seq();
533     test_filter_results();
534     test_array_sort();
535     test_blocks2motif();
536
537     fprintf( stderr, "All tests OK\n" );
538 }
539
540
541 void test_count_array_new()
542 {
543     fprintf( stderr, "   Running test_count_array_new ... " );
544
545     size_t  i     = 0;
546     size_t  nmemb = 128; 
547     uint   *array = NULL;
548
549     array = count_array_new( nmemb );
550
551     for ( i = 0; i < nmemb; i++ ) {
552         assert( array[ i ] == 0 );
553     }
554
555     mem_free( &array );
556
557     fprintf( stderr, "done.\n" );
558 }
559
560
561 void test_bitblock_new()
562 {
563     fprintf( stderr, "   Running test_bitblock_new ... " );
564
565     bitblock *new_block = bitblock_new();
566
567     assert( new_block->bin  == 0 );
568     assert( new_block->hasN == FALSE );
569
570     fprintf( stderr, "done.\n" );
571 }
572
573
574 void test_bitblock_print()
575 {
576     fprintf( stderr, "   Running test_bitblock_print ... " );
577
578     bitblock *new_block = bitblock_new();
579
580     new_block->bin  = 7;
581     new_block->hasN = TRUE;
582
583 //    bitblock_print( new_block );
584
585     fprintf( stderr, "done.\n");
586 }
587
588
589 void test_bitblock_list_print()
590 {
591     fprintf( stderr, "   Running test_bitblock_list_print ... " );
592
593     list_sl  *list   = list_sl_new();
594     node_sl  *node1  = node_sl_new();
595     node_sl  *node2  = node_sl_new();
596     node_sl  *node3  = node_sl_new();
597     bitblock *block1 = bitblock_new();
598     bitblock *block2 = bitblock_new();
599     bitblock *block3 = bitblock_new();
600
601     block1->bin  = 0;
602     block1->hasN = TRUE;
603     block2->bin  = 1;
604     block2->hasN = TRUE;
605     block3->bin  = 2;
606     block3->hasN = TRUE;
607
608     node1->val  = block1;
609     node2->val  = block2;
610     node3->val  = block3;
611
612     list_sl_add_beg( &list, &node1 );
613     list_sl_add_beg( &list, &node2 );
614     list_sl_add_beg( &list, &node3 );
615
616     // bitblock_list_print( list );
617
618     fprintf( stderr, "done.\n" );
619 }
620
621
622 void test_scan_seq()
623 {
624     fprintf( stderr, "   Running test_scan_seq ... " );
625
626     //char   *seq       = "AAAANTCGGCTNGGGG";
627     //char   *seq         = "AAAATCGGCTGGGG";
628     char   *seq         = "AAAAAAAAAAAAAAG";
629     size_t  seq_len     = strlen( seq );
630     size_t  nmemb       = 1 << 5;
631     
632     uint   *count_array = count_array_new( nmemb );
633
634     scan_seq( seq, seq_len, count_array );
635
636     count_array_print( count_array, nmemb );   /* DEBUG */
637
638     fprintf( stderr, "done.\n" );
639 }
640
641
642 void test_filter_results()
643 {
644     fprintf( stderr, "   Running test_filter_results ... " );
645
646     uint    array[]     = { 4, 234, 23, 43, 12, 23, 1, 34 };
647     uint   *array_pt    = array;
648     size_t  array_nmemb = sizeof( array ) / sizeof( uint );
649     uint    cutoff      = 20;
650     size_t  size        = 0;
651     uint    i           = 0;
652
653     size = filter_results( &array_pt, array_nmemb, cutoff );
654
655     assert( size == 5 ); /* 5 elements greather than 20. */
656
657     for ( i = 0; i < size; i ++ ) {
658 //        printf( "elem: %i\n", array_pt[ i ] );
659     }
660
661     fprintf( stderr, "done.\n" );
662 }
663
664
665 void test_array_sort()
666 {
667     fprintf( stderr, "   Running test_array_sort ... " );
668
669     uint array[]    = { 4, 234, 23, 43, 12, 23, 1, 34 };
670     int  array_size = sizeof( array ) / sizeof( uint );
671     int  i          = 0;
672
673     for ( i = 0; i < array_size; i ++ ) {
674 //        printf( "elem: %i\n", array[ i ] );
675     }
676
677     qsort( array, array_size, sizeof( uint ), cmp_uint_desc );
678
679     for ( i = 0; i < array_size; i ++ ) {
680 //        printf( "elem: %i\n", array[ i ] );
681     }
682
683     assert( array[ 0 ] == 234 );     /* 234 first in sorted array */
684     assert( array[ array_size - 1 ] == 1 );     /* 1 last in sorted array */
685
686     fprintf( stderr, "done.\n" );
687 }
688
689
690 static void test_blocks2motif()
691 {
692     fprintf( stderr, "   Running test_blocks2motif ... " );
693
694     uchar  bin1  = 4;
695     uchar  bin2  = 3;
696     ushort dist  = 256;
697     uint   motif = 0;
698
699     motif = blocks2motif( bin1, bin2, dist );
700
701 //    printf( "motif: %d\n", motif );
702
703     fprintf( stderr, "done.\n");
704 }
705
706
707 /* >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>><<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< */
708
709