Avec Rust : scores de similitude d’une séquence biologique contre une banque
Article mis en ligne le 25 octobre 2021
dernière modification le 27 octobre 2021

par Laurent Bloch

L’article précédent nous avait permis de lire une banque de séquences biologiques au format FASTA, en introduisant à cette occasion l’instruction match et quelques subtilités de la possession, du prêt et de l’emprunt de valeurs. Celui d’avant nous avait donné l’algorithme de Needleman et Wunsch pour aligner deux séquences et calculer leur score de similitude. Il ne nous reste plus qu’à combiner les deux pour obtenir tous les scores de la séquence d’intérêt contre celles de la banque. Ce travail de construction se fait aisément grâce aux caractéristiques de Rust : on s’est donné du mal pour avoir un programme qui compile, mais une fois que le compilateur a accepté notre code, il n’y a pas de mauvaises surprises à l’exécution.

Je retiens comme score final la valeur de la dernière case de la matrice (« en bas à droite ») : je n’ignore pas que d’autres choix sont loisibles.

Certaines lignes du programme sont placées en commentaire pour améliorer la concision et la lisibilité des résultats, mais il suffit de les « décommenter » pour obtenir le détail des matrices d’alignement.

Voici :

  1. // src/sequences_matrix/build_sequences_matrix.rs :
  2.  
  3. // This module was inspired by Vincent Esche's Seal crate,
  4. // but simplified and much more basic, without mmap and so on.
  5. // For pedagogic use.
  6.  
  7. pub mod build_sequences_matrix {
  8.  
  9.     use simple_matrix::Matrix;
  10.     use std::str;
  11.     use std::char;
  12.    
  13.     pub fn build_matrix(sequence1: &(String, Vec<u8>),
  14.                         sequence2: &(String, Vec<u8>),
  15.                         match_bonus: f32,
  16.                         gap_penalty: f32) {
  17. //                      -> Matrix<f32> {
  18. //      print_seq(&sequence1);
  19. //      print_seq(&sequence2);
  20.  
  21.         let l_seq1: usize = (sequence1.1).len();
  22.         let l_seq2: usize = (sequence2.1).len();
  23.  
  24.         println!("Longueur première séquence : {} ", l_seq1);
  25.         println!("Longueur seconde séquence : {} ", l_seq2);
  26.        
  27.         let mut the_mat: Matrix::<f32> = Matrix::new(l_seq2+1, l_seq1+1);
  28.  
  29.         init_matrix(&mut the_mat, l_seq2+1, l_seq1+1, 0.0);
  30.  
  31.         nw_matrix(&mut the_mat, l_seq2+1, l_seq1+1, match_bonus, gap_penalty, &sequence1.1, &sequence2.1);
  32.  
  33.         print_ident(&sequence1);
  34.         print_ident(&sequence2);
  35.  
  36. //      print_matrix(&the_mat, &sequence2.1, l_seq2+1, l_seq1+1);
  37.  
  38.         print_score(&the_mat, l_seq2+1, l_seq1+1);
  39.            
  40.     }
  41.  
  42.     fn nw_matrix(the_mat: &mut Matrix::<f32>,
  43.                      lin: usize,
  44.                      col: usize,
  45.                      match_bonus: f32,
  46.                      gap_penalty: f32,
  47.                      seq1: &Vec<u8>,
  48.                      seq2: &Vec<u8>) {
  49.         for j in 1..col {
  50.             the_mat.set(0, j, gap_penalty * j as f32) ;
  51.         }
  52.         let mut score: f32 = 0.0;
  53.         for i in 1..lin {
  54.             the_mat.set(i, 0, gap_penalty * i as f32) ;
  55.             for j in 1..col {
  56.                 if seq1[j-1] == seq2[i-1] {
  57.                     score = match_bonus} else {
  58.                     score = 0.0}
  59.                 the_mat.set(i, j, max3(the_mat.get(i-1,j-1).unwrap()
  60.                                        + score,
  61.                                        the_mat.get(i-1,j).unwrap()
  62.                                        + gap_penalty,
  63.                                        the_mat.get(i,j-1).unwrap()
  64.                                        + gap_penalty));
  65.             }
  66.         }
  67.     }
  68.  
  69.     fn max3(v1: f32, v2: f32, v3: f32) -> f32 {
  70.         let tmp = f32::max(v2, v3);
  71.         if v1 > tmp {
  72.             return v1 } else {
  73.             return tmp };
  74.     }
  75.    
  76.     fn init_matrix(the_mat: &mut Matrix::<f32>, lin: usize, col: usize, val: f32) {
  77.         for i in 0..lin {
  78.             for j in 0..col {
  79.                 the_mat.set(i, j, val) ;
  80.             }
  81.         }
  82.     }
  83.  
  84. // "print_seq" affiche une séquence selon différents formats.
  85.     pub fn print_seq(sequence: &(String, Vec<u8>)) {
  86.                 println!("Ident : {:?}", sequence.0);
  87. //              println!("Séquence : {:?}", sequence.1);
  88.                 let sequence_str = str::from_utf8(&sequence.1).unwrap().to_string();
  89.                 println!("Séquence : {}", &sequence_str);
  90.     }
  91.  
  92.     fn print_vector(the_vec: &Vec<u8>) {
  93.         let vec_str = str::from_utf8(the_vec).unwrap().to_string();
  94.         print!("{} ", "   ");
  95.         for c in vec_str.chars() {
  96.             print!("{} ", c);
  97.         }
  98.         print!("{}", "   \n");
  99.     }
  100.    
  101.     fn print_matrix(the_mat: &Matrix::<f32>, seq2: &Vec<u8>, lin: usize, col: usize) {
  102.         for i in 0..lin {
  103.             if i > 0 {print!("{} ", char::from(seq2[i-1]))} else
  104.             {print!("{} ", " ")};
  105.             for j in 0..col {
  106.                 print!("{} ", the_mat.get(i, j).unwrap());
  107.             }
  108.             print!("{}", "\n")
  109.         }
  110.     }
  111.  
  112.     fn print_score(the_mat: &Matrix::<f32>, lin: usize, col: usize) {
  113.         println!("Score de similarité : {} ", the_mat.get(lin-1, col-1).unwrap());
  114.         print!("{}", "\n")
  115.     }
  116.  
  117.     fn print_ident(sequence: &(String, Vec<u8>)) {
  118.         println!("Ident : {:?}", sequence.0);
  119.     }
  120. }

Télécharger

Et le programme de lecture des séquences, adapté de l’article précédent :

  1. // src/fasta_files_mgt/fasta_multiple_cmp.rs :
  2.  
  3. // https://linuxfr.org/forums/programmationautre/posts/rust-lire-des-donnees-de-type-i8-depuis-un-fichier
  4. // https://www.it-swarm-fr.com/fr/file-io/quelle-est-la-maniere-de-facto-de-lire-et-decrire-des-fichiers-dans-rust-1.x/1054845808/
  5. // https://docs.rs/simple-matrix/0.1.2/simple_matrix/
  6.  
  7. pub mod fasta_multiple_cmp {
  8.  
  9.     use std::env;
  10.     use std::fs;
  11.     use std::fs::File;
  12.     use std::io;
  13.     use std::io::Read;
  14.     use std::io::{prelude::*, BufReader};
  15.     use std::io::Lines;
  16.     use std::fs::Metadata;
  17.     use std::str;
  18.  
  19.     use crate::sequences_matrix::build_sequences_matrix::build_sequences_matrix::print_seq;
  20.     use crate::sequences_matrix::build_sequences_matrix::build_sequences_matrix::build_matrix;
  21.    
  22.     pub struct Config {
  23.         pub query_filename: String,
  24.         pub bank_filename: String,
  25.         pub match_bonus: f32,
  26.         pub gap_penalty: f32
  27.     }
  28.    
  29.     impl Config {
  30.         pub fn new(args: &[String]) -> Config {
  31.             if args.len() < 5 {
  32.                 panic!("pas assez d'arguments");
  33.             }
  34.             let query_filename = args[1].clone();
  35.             let bank_filename = args[2].clone();
  36.             let match_bonus: f32 = args[3].parse()
  37.                 .expect("Ce n'est pas un nombre !");
  38.             let gap_penalty: f32 = args[4].parse()
  39.                 .expect("Ce n'est pas un nombre !");
  40.                
  41.             Config {query_filename, bank_filename, match_bonus, gap_penalty}
  42.         }
  43.     }
  44.        
  45.     pub fn get_filenames() {
  46.         let args: Vec<String> = env::args().collect();
  47.         let config = Config::new(&args);
  48.        
  49.         println!("Alignement de {} avec {} \n", config.query_filename, config.bank_filename);
  50.        
  51.         let f_query = fasta_open_file(config.query_filename);
  52.         let f_bank = fasta_open_file(config.bank_filename);
  53.  
  54.         read_sequences(f_query,
  55.                        f_bank,
  56.                        config.match_bonus,
  57.                        config.gap_penalty);
  58.  
  59.     }
  60.  
  61.     fn fasta_open_file(filename: String) -> (File) {
  62.         let f = File::open(filename).expect("Fichier non trouvé !");
  63.         f
  64.     }
  65.  
  66.     fn get_sequence<B: BufRead>(count: &mut u8, ident: &mut String, lines: &mut Lines<B>)
  67.                                    -> (String, Vec<u8>) {
  68.         let mut sequence: (String, Vec<u8>) = (String::new(), vec![]);
  69.         let mut sequence_nuc: Vec<u8> = vec![];
  70.        
  71.         for line in lines {
  72.             let the_line = line.unwrap();
  73.             if the_line.len() > 0 {
  74.                 let first = &the_line[0..1];
  75.                 match first {
  76.                     first if first == ">" => {
  77.                         if *count == 0 {
  78.                             *ident = the_line.clone();
  79.                             *count += 1;
  80.                         } else {
  81.                             sequence = (ident.to_string(), sequence_nuc.clone());
  82.                             println!("Numéro : {}", count);
  83.                             *ident = the_line.clone();
  84.                             sequence_nuc = vec![];
  85.                             *count += 1;
  86.                             return sequence;
  87.                         }
  88.                     }
  89.                     first if first != ">" => {
  90.                         sequence_nuc.extend(the_line.as_bytes())}
  91.                     &_ => {}
  92.                 }
  93.             }
  94.         }
  95.         sequence = (ident.to_string(), sequence_nuc.clone());
  96.         println!("Numéro : {}", count);
  97.         sequence
  98.     }
  99.  
  100.  
  101. //    fn read_sequences(f: File) {
  102. //      let fb = BufReader::new(&f);
  103. //      let mut lines = fb.lines();
  104. //      let mut count: u8 = 0;
  105. //      let mut ident = String::new();
  106. //      loop {
  107. //          let sequence = get_sequence(&mut count, &mut ident, &mut lines);
  108. //          if sequence.1.len() == 0 {
  109. //              break} else {
  110. //              print_seq(&sequence);
  111. //          }
  112. //      }
  113. //  }
  114.  
  115.     fn read_sequences(f_query: File,
  116.                       f_bank: File,
  117.                       match_bonus: f32,
  118.                       gap_penalty: f32) {
  119.         let fq = BufReader::new(&f_query);
  120.         let mut fq_iter = fq.lines();
  121.         let mut count: u8 = 0;
  122.         let mut ident = String::new();
  123.         let query_sequence = get_sequence(&mut count, &mut ident, &mut fq_iter);
  124.         print_seq(&query_sequence);
  125.  
  126.         let fb = BufReader::new(&f_bank);
  127.         let mut fb_iter = fb.lines();
  128.         count = 0;
  129.         loop {
  130.             let bank_sequence = get_sequence(&mut count, &mut ident, &mut fb_iter);
  131.             if bank_sequence.1.len() == 0 {
  132.                 break} else {
  133. //              print_seq(&bank_sequence);
  134.                 build_matrix(&query_sequence,
  135.                              &bank_sequence,
  136.                              match_bonus,
  137.                              gap_penalty);
  138.             }
  139.         }
  140.     }
  141. }

Télécharger