Alignements Smith-Waterman distribués avec SOAP::Lite
et BioPerl
1. Problèmatiques
IBM et Genomining ont récemment lancé un projet nommé
Décrypthon, a l'occasion du Téléthon annuel. Il s'agit,
pour l'utilisateur lambda, de télécharger un économisateur
d'écran un peu particulier. Celui ci est doté d'un moteur de
comparaison de séquences protéiques basé sur la méthode
SW, et de modules permettant de récupérer des données
(séquences protéiques) sur un serveur distant, et de lui communiquer
les résultats des comparaisons. Le but ultime est modestement de comparer
toutes les protéines entre elles. Il s'agit d'un mode de fonctionnement
que l'on nomme P2P (Peer To Peer) : la puissance de calcul du système
ne se trouve pas sur une seule machine mais sur un ensemble de machines
plus ou moins puissantes, réparties sur le réseau local ou
Internet.
Le programme de calcul n'étant pas disponible sous Linux, j'ai donc
décidé d'en ré-écrire une version similaire, mais
ne fonctionnant que sous Unix. Il ne s'agit pas d'un gros travail, puisque
le client fait 150 lignes de code Perl, et le serveur 100. En revanche, on
tire profit des nombreux modules Perl disponibles et évitant au bioinformaticien
de réinventer la roue (ex : SOAP et l'alignement protéique).
Notons toutefois que cette implémentation n'est pas compatible
avec les softs du Décrypthon.
Exemple d'alignement Smith-Waterman :
A partir de il s'agit simplement d'une implementation minimale, qui néanmoins
pourrait etre pleinement utile sur un parc de machines Unix.
1.1 Problèmes et solutions
Les problémes à résoudre sont de plusieurs ordres :
- liées à l'utilisation d'un économisateur d'écran
- liées à au découpage du jeu de données initial
en morceaux
- liées au transfert de données entre client et serveur
- liées à la procedure d'alignement
- lié au stockage persistant des séquences et résultats
chez le client. il est bien évident qu'une base MySQL n'est pas utilisable.
D'abord parce que cela alourdit la procedure (il faut installer MySQL sur
chaque client, créer des bases, des tables, gerer les droits d'accès,
installer des modules d'accès pour Perl, etc).
1.1.1 liées à l'utilisation d'un économisateur d'écran
Il existe sous Linux un programme général d'économisateur
d'écran, nommé xscreensaver. Celui-ci fonctionne en tant que
"wrapper" du programme économisateur. Cela signifie que une fois xscreensaver
lancé, celui attends une période d'inactivité de la
part de l'utilisateur. Lorsque celle-ci survient, il lance l'un des programmes
externes spécifiés dans le fichier .xscreensaver, lequel peut
- mais ce n'est pas nécéssaire - afficher des animations à
l'écran.
Lorsque un événement utilisateur survient, xscreensaver envoie
le signal SIGTERM afin de le "tuer". Cette méthode de "terminaison"
étant quelque peu brutale, il peut être nécéssaire
d'intercepter ce signal au sein du programme SWC .
# XScreenSaver Preferences File
# Written by xscreensaver-demo 3.28 for olly on Thu Apr 4 13:35:55 2002.
# http://www.jwz.org/xscreensaver/
timeout: 0:10:00
cycle: 0:10:00
lock: False
lockTimeout: 0:00:00
passwdTimeout: 0:00:30
visualID: default
installColormap:True
verbose: True
timestamp: False
splash: True
splashDuration: 0:00:05
demoCommand: xscreensaver-demo
prefsCommand: xscreensaver-demo -prefs
helpURL: http://www.jwz.org/xscreensaver/man.html
loadURL: netscape -remote 'openURL(%s)' || netscape '%s'
nice: 10
fade: True
unfade: False
fadeSeconds: 0:00:03
fadeTicks: 20
captureStderr: True
font: *-medium-r-*-140-*-m-*
dpmsEnabled: False
dpmsStandby: 0:00:10
dpmsSuspend: 0:00:10
dpmsOff: 0:00:10
programs: /home/olly/swc/swc.pl
pointerPollTime:0:00:05
windowCreationTimeout:0:00:30
initialDelay: 0:00:00
sgiSaverExtension:True
mitSaverExtension:False
xidleExtension: True
procInterrupts: True
overlayStderr: True
1.1.1 liées à au découpage du jeu de données
initial en morceaux
Dans ce type d'architecture, il est nécéssaire d'y avoir au
niveau du serveur un ordonnancateur, c'est a dire un programme de répartition
des taches à effectuer par les clients.
Nous mettrons en place un ordonnancateur simple, basé sur la notion
de "job".
Un job est constitué d'une séquence cible, et d'un jeu de séquences
à comparer avec cette séquence cible. La taille de ce jeu de
séquences est égale à 40 au maximum. A chaque fois que
le client se connecte en demandant un nouveau jeu de données, un nouveau
job est créé afin de stocker. Les paramêtres du nouveau
job sont obtenus à partir du dernier job créé auparavant.
Une table dans MySQL va servir à stocker les jobs.
1.1.3 liées au transfert de données entre client et serveur
Le transfert de données entre client et serveur se fait grace au protocole
SOAP. Il s'agit d'un protocole de RPC (Remote Procedure Call), basé
sur XML pour le codage de l'information transmise. Sans rentrer dans les
détails, nous dirons qu'il s'agit d'un standard permettant d'appeler
une procédure distante (sur autre site internet, ou sur une autre
machine) de façon transparente, c'est-a-dire sans se soucier du transfert
physique de données via les réseaux.
Pour implémenter ces transfert, nous utilisons le module Perl SOAP::Lite
:
Les problèmatiques restant à résoudre :
- controle et ré-execution des jobs jamais arrivés
- optimisation des transferts et gestion du hors-connexion. En effet, dans
sa version actuelle, SWC est uniquement utilisable sur des machines connectées
au Net en permanence.
2. Pré-requis
Nous utiliserons les modules Perl suivant : SOAP::Lite to implement BioPerl
to DBI modules to access to MySQL database in Perl Nous utiliserons les
logiciels (open source) suivant : xscreensaver to launch the SWC client when
the user is idle MySQL database server, to store the results, the running
jobs, and the results. Bioperl XS extentions : le package bioperl-ext contient
des modules avec du code en langage C, interfacé au Perl via le mécanisme
XS. En particulier, le package bioperl-ext-06 contient le module Bio::Tools::pSW
permettant de réaliser des alignements 2 à 2 par la méthode
de Smith-Waterman. Ce package est disponible via CPAN, ou via le serveur
FTP dont l'URL est ftp://bioperl.org/pub/external/
Installer un module Perl peut se faire de manière quasi-automatisé
grace a un module Perl (souvent pré-installé) nommé
CPAN.pm. Par exemple, pour installer le module SOAP::Lite, il suffit de taper
:
3. Créer la partie serveur
3.1 Base de données
En tout premier lieu, créer une base de données.
mysql> create database SWC
-> ;
Query OK, 1 row affected (0.56 sec)
Créer ensuite la table amenée à stocker des séquences
drop table if exists SEQUENCES;
CREATE TABLE SEQUENCES (
ID int not null auto_increment primary key,
ACCNUM varchar(10),
SEQUENCE text
);
Puis la table de stockage de JOBS (petit jeux de données envoyés
au client)
drop table if exists JOBS;
CREATE TABLE JOBS (
ID int not null auto_increment primary key,
SEQID int,
SEQST int,
SEQEN int
);
Enfin, creer la table permettant de stocker les resultats.
drop table if exists RESULTS;
CREATE TABLE RESULTS (
ACCNUM1 varchar(10),
ACCNUM2 varchar(10),
ALNSEQ1 text,
ALNSEQ2 text,
SCORE float
);
Voici le code du serveur SOAP. Celui fonctionne en tant que "démon"
Unix, c'est a dire que l'on s'y connecte via un port (ici 7777) sur lequel
celui-ci écoute en permanence. Notons que le serveur SOAP peut (et
doit) gérer des accès simultanés.
#!/usr/bin/perl -w
use SOAP::Transport::HTTP;
use DBI();
SOAP::Transport::HTTP::Daemon
-> new (LocalAddr => 'localhost', LocalPort => 7777)
-> dispatch_to('Server')
-> handle;
package Server;
sub connect {
$dbh = DBI->connect( "DBI:mysql:database=SWC;host=localhost",
"",
"",
{'RaiseError' => 1}
);
return $dbh;
}
sub getSequences {
my ($class) = @_;
my $CHUNK = 40;
$dbh = $class->connect();
# get the total number of sequences in the DB
$query = "SELECT count(*) AS COUNT FROM SEQUENCES";
$sth = $dbh->prepare($query);
$sth->execute;
$count = $sth->fetchrow_hashref->{COUNT};
print "got $count sequences in DB ...\n";
# get the last job
$query = "SELECT * FROM JOBS ORDER BY ID DESC LIMIT 1";
$sth = $dbh->prepare($query);
$sth->execute or die ("pb ..\n");
$row = $sth->fetchrow_hashref;
print "Issuing query $query\n";
# if the END of the last job is equal to the number of seqs
if (!$row) {
$seqid = 1;
$seqst = 1;
$seqen = 1 + $CHUNK;
} elsif ($row->{SEQEN} >= $count) {
$seqid = $row->{SEQID} + 1;
$seqst = 1;
$seqen = 1 + $CHUNK;
} else {
$seqid = $row->{SEQID};
$seqst = $row->{SEQEN} + 1;
$seqen = ($row->{SEQEN} + $CHUNK > $count ? $count : $row->{SEQEN} + $CHUNK);
}
# create a new job
$query = "INSERT INTO JOBS VALUES ('null', '$seqid', '$seqst', '$seqen')";
print "Issuing query $query\n";
$dbh->do($query) or die("pb ...\n");
# get the target sequence
$query = "SELECT * FROM SEQUENCES WHERE ID = $seqid";
print "Issuing query $query\n";
$sth = $dbh->prepare($query);
$sth->execute;
@seqs = ();
$row = $sth->fetchrow_hashref;
push(@seqs, $row);
# get the sequences
$query = "SELECT * FROM SEQUENCES WHERE ID >= $seqst AND ID != $seqid LIMIT $CHUNK";
print "Issuing query $query\n";
$sth = $dbh->prepare($query);
$sth->execute;
while ($row = $sth->fetchrow_hashref) {
push(@seqs, $row);
}
return \@seqs;
}
sub addResults {
my ($class, $c) = @_;
$dbh = $class->connect();
print "results :\n";
foreach $aln (@$c) {
print $aln->{ACCNUM1} . "-" . $aln->{ACCNUM2} . "\n";
$query = "INSERT INTO RESULTS VALUES ('" . $aln->{ACCNUM1} . "', '" .
$aln->{ACCNUM2} . "', '" .
$aln->{ALNSEQ1} . "', '" .
$aln->{ALNSEQ2} . "', '" .
$aln->{SCORE} . "')";
#print "$query\n";
$dbh->do($query) or die "oops ...\n";
}
}
4. CREER LA PARTIE CLIENT
#!/usr/bin/perl
use Bio::Tools::pSW;
use Bio::Seq;
use Bio::SeqIO;
use Bio::AlignIO;
use GDBM_File;
use SOAP::Lite;
tie(my(%indexdb), 'GDBM_File',"sequences.db", &GDBM_WRCREAT, 0644);
tie(my(%varsdb), 'GDBM_File',"vars.db", &GDBM_WRCREAT, 0644);
tie(my(%resultsdb), 'GDBM_File',"results.db", &GDBM_WRCREAT, 0644);
sub handler{
local ($signal) = @_;
print "Got the $signal signal, aborting!\n";
$varsdb{"theid"} = $theid;
$varsdb{"j"} = $j;
exit();
}
$SIG{'TERM'} = 'handler';
RETURN:
# list the stored results
while (($k, $v) = each(%resultsdb)) {
print ">$k\n";
print "$v\n";
}
# in case the whole chunk has been computed
print scalar(keys(%resultsdb));
print scalar(keys(%indexdb));
if (scalar(keys(%resultsdb)) == scalar(keys(%indexdb)) - 1) {
# send back the results
@res = ();
while (($k, $v) = each(%resultsdb)) {
($a1, $a2) = $k =~ /^(.+)\|(.+)$/;
($score, $s1, $s2) = $v =~ /^(.+)\|(.+)\|(.+)$/;
my %hash = ("ACCNUM1" => $a1,
"ACCNUM2" => $a2,
"ALNSEQ1" => $s1,
"ALNSEQ2" => $s2,
"SCORE" => $score );
push(@res, \%hash);
}
$soap_response = SOAP::Lite
-> uri('http://localhost/Server')
-> proxy('http://localhost:7777')
-> addResults(\@res);
%indexdb = ();
%resultsdb = ();
%varsdb = ();
}
if (scalar(keys(%indexdb)) == 0) {
# get new sequences
$soap_response = SOAP::Lite
-> uri('http://localhost/Server')
-> proxy('http://localhost:7777')
-> getSequences();
print "Getting data from the soap server !\n";
$res = $soap_response->result;
$cnt = 0;
foreach $seq (@$res) {
$theid = $seq->{ACCNUM} if ($cnt++ == 0);
print $seq->{ACCNUM} . "\n";
$indexdb{$seq->{ACCNUM}} = $seq->{SEQUENCE};
}
$thej = 0;
} else {
# the sequence set is not empty, this could have been a SIGTERM
$thej = ($varsdb{"j"}?$varsdb{"j"}:0);
$theid = $varsdb{"theid"};
}
@keys1 = keys %indexdb;
$factory = new Bio::Tools::pSW( '-matrix' => 'blosum62.bla',
'-gap' => 12,
'-ext' => 2, );
LABEL: for ($j=$thej; $j<=$#keys1; $j++) {
next LABEL if ($theid eq $keys1[$j]);
print "Aligning " . $theid . " and " . $keys1[$j] . " ($j)\n";
$seq1 = Bio::Seq->new( -seq => $indexdb{$theid}, -id => $theid);
$seq2 = Bio::Seq->new( -seq => $indexdb{$keys1[$j]}, -id => $keys1[$j]);
$aln = $factory->pairwise_alignment($seq1, $seq2);
@seqs = $aln->each_seq;
$record = "";
$cle = "";
$record .= $aln->percentage_identity;
$record .= "|";
$record .= $seqs[0]->seq;
$record .= "|";
$record .= $seqs[1]->seq;
$cle .= $seqs[0]->id;
$cle .= "|";
$cle .= $seqs[1]->id;
$resultsdb{$cle} = $record;
}
goto RETURN;
untie %indexdb;
untie %varsdb;
untie %resultsdb;
The On the server side, both the sequences and the results are stored
in a MySQL database. This is necessary since concurrent access to the database
are likely to arise. create a new database create a new table to store
the sequences : mysql> create database SWC -> ; Query OK, 1 row
affected (0.56 sec) mysql> use SWC Database changed mysql> CREATE TABLE
SEQUENCES ( -> ID int not null auto_increment primary key,
-> ACCNUM varchar(10), -> SEQUENCE text -> ); Query
OK, 0 rows affected (0.18 sec) mysql> CREATE TABLE JOBS ( ) insert
sequences The first web service provides with an associative array of sequences
to work with. only one sequence to share with every other ...