cours général du module OBI2
Transcription
cours général du module OBI2
Module 2: Algorithmique et Programmation : Notions de base pour les biologistes Objectif: Initiation à la programmation et à l'algorithmique. Faisabilité d'un algorithme : les problèmes simples et les problèmes complexes. Présentation des algorithmes classiques pour l'optimisation et le traitement des données. Contenu : Qu'est ce qu'un algorithme ? Analyse d'un problème. Représentation et structures de données. Liaisons entre les structures de données et les algorithmes. Complexité d'un algorithme. Résolutions exactes et heuristiques. La mise en oeuvre sera faite dans un langage de programmation interprété et souple (Python). Jour 1: cours: notions générales d'algorithmique et de programmation (variables, structure de contrôle, fonctions). Voyageur de commerce – NP complet (factorielle) Heuristique (planche à clous, savon – MonteCarlo) Programmation dynamique – Camions (tranches) Présentation du langage Python (Interprété/Interactif) Variables: type implicite liste, tuple et string sont des séquences. Indentation délimite les blocs d'instructions – instruction vide "pass" Listes: a = [] # déclaration d'une liste T-uples: a = (4, ‘titi’,3.14159) # déclaration d'un t-uple (un t-uple est non modifiable) Les listes et t-uples commencent à 0, coordonnées négatives == permutations circulaires –1 == dernière case (maximum négatif = -longueur, dernière case +1 : erreur,, types différents autorisés. Donc les coordonnées vont de –longueur à +(longueur –1) a.append("toto") a[3:10] # extrait éléments 3 à 9 for i in liste: # i prend successivement les valeurs de la liste for i in range(debut, fin, step): # i prend les valeurs de "debut à ("fin"-1) par pas de "step". while <test> : def foo(arguments): # indentation nécessaire Fonctions : attention, passage par valeur, sauf les listes, strings et tuples. Cela veut dire une variable est copiée dans la fonction : la fonction ne peut donc modifier la variable de l’appelant. Mais dans le cas des listes, strings et tuples, le nom de la variable est une référence, donc la référence n’est pas modifiable, mais l’objet référencé peut l’être (puisqu’on a la référence !). Références : Voir l’exemple suivant : >>> >>> [1, >>> >>> [1, >>> >>> [1, >>> [1, x=[1,2,3] x 2, 3] y=x y 2, 3] x[2]=-1 x 2, -1] y 2, -1] y est donc bien une référence à x, y et x désignent un même objet (une liste) Pour copier une liste, il faut l’expliciter, avec par exemple les index de début et de fin, comme ici : >>> >>> [1, >>> >>> [1, >>> [1, z=x[:] z 2, -1] x[2]=-2 x 2, -2] z 2, -1] Là, x a été copiée dans z, et si on touche à x, on ne touche pas à z. Si on ne veut pas qu’une liste soit touchée dans une fonction, il faudrait alors faire : >>> def foo(l): ... l[2]=-5 ... print l >>> [1, >>> [1, >>> [1, >>> [1, foo(x) 2, -5] x 2, -5] foo(z[:]) 2, -5] z 2, -1] # là, x est touchée # là, y n’est pas touchée # lister les méthodes liées a un objet range(4) liste [0, 1, 2, 3] liste=dir(liste) ['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__eq__', '__ge__', '__getattribute__', '__getitem__', '__getslice__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__repr__', '__rmul__', '__setattr__', '__setitem__', '__setslice__', '__str__', 'append', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort'] map, reduce, filter reduce(plus,liste) # f(f(f(liste[0],liste[1]),liste[2]), liste[3])..etc 6 map(carre,liste) [0, 1, 4, 9] map(plus, liste,liste) [0, 2, 4, 6] map(None, liste1,liste2): applique None aux doublets d'une liste constituée de chacune des paires des éléments de liste1 et liste2 filter(f,liste): retourne la sous liste de liste constituée de chacun des éléments pour lesquels f est vraie. TP: Utilisation des structures de données et des structures de contrôles de Python, fonctions. Boucle Tests Calcul moyenne moyenne cumulative selon formule: mi = mi-1 + vi*i/(i-1) mi: moyenne au "temps" i, vi ième valeur calcul variance Faire avec programmation impérative (boucle) et fonctionnelle (reduce). fichier python "fonctions" def somme(a, b): return a+b def carre(n): return n*n def sommeseq(seq): # somme d'une séquence s=0 for x in seq: s=s+x return s # somme par reduce (programmation fonctionnelle) def sum(liste): # somme d'une liste return reduce(somme,liste) def moyenne(liste): return float(sommeseq(liste))/len(liste) # variance def var(liste): return (moyenne(map(carre,liste))carre(moyenne(liste))*(float(len(liste))/(len(liste)-1))) def admis(s): return (s[-1]>=10) # retourne une liste de n-uplets qui sont les mots des lignes # du fichier f def readfile(f): t= [] fil = open(f,'r') tmp= fil.readlines() for x in tmp: t.append(x.split()) # fil.close() return t # retourne 0 si a==b, -1 si a<b, + 1 si a>b def compare(a,b): if a>b: return 1 elif a<b: return -1 else : return 0 ================= fichier "notes": Terrieur Alain 12 4 16 Potanpoche Jessica 0 12 4 Chemonfis Thierry 8.5 14 17 Bonnot Jean 18 12 Kiroul Pierre 10 4.5 15 ... #!/usr/local/bin/python2.1 import fonctions f=open('notes','r') ll=f.readlines() t=[] for x in ll: t.append(x.split()) et=[] etud=[] for x in t: et=x[0:2] et.append(fonctions.moyenne(map(float,x[2:]))) etud.append(et) w=open('moys','w') for x in etud: w.write(x[0]+" "+x[1]+" "+"%2.2f" % x[2]+"\n") # afficher à l'écra les admis: print "etudiants admis : listeadmis = filter(fonctions.admis,etud) for x in listeadmis : print x[0], x[1], '%2.2f' % x[2] w.close() Jour 2 : Cours: Récursivité (Cf document recursif.pdf) Algorithmes de tri. TP: tri (insertion, sélection, bulle, merge) Sur le net : applications graphiques en java : http://java.sun.com/applets/jdk/1.4/demo/applets/SortDemo/example1.html http://www.cs.ubc.ca/spider/harrison/Java/ http://lwh.free.fr/pages/algo/tri/tri.htm TP: Recherche séquentielle dans une liste triée. C’est l’algorithme de recherche le plus élémentaire qui parcourt la listejusqu’à ce qu’un objet soit trouvé ou bien que la fin du tableau soit atteinte. Nous allons appliquer cela à la liste obtenue après lecture du fichier notes en cherchant la ligne correspondant à un étudiant sur la base de son nom de famille. def rec_seq(tab, name): cpt = 0 for i in tab: test = compare(i[0], name) cpt = cpt+1 if test ==0 : return i, cpt print "pas trouve" Le nombre maximum de comparaisons d’objets effectuées par cette algorithme est N le nombre d’éléments de la liste, ce maximum étant atteint lorsque l’élément n’est pas trouvé. En moyenne il procède à N/2 comparaisons. Cela est très mauvais et on sait faire beaucoup mieux. Recherche dichotomique dans une liste triée. Un supplément d’information, permet souvent de réduire la complexité d’un problème. L’algorithme de recherche séquentielle n’utilisait que le test d’égalité avec deux réponses possibles (oui/non). Comme les éléments (les noms) sont triés par ordre croissant. On peut réaliser un test avec trois résultats : égalité, inférieur, supérieur. #!/prog/seq_analysis/python import fonctions # t est une liste de triplets (nom, prenom, notes) triée sur les noms # v est le nom recherché def dicho(t,v): g=0 d=len(t)-1 while g<=d: m=(g+d)/2 test = fonctions.compare(v,t[m][0]) if test == 1: g=m+1 elif test == -1: d=m-1 else: print 'trouve en position',m return print 'pas trouve' # 'notes' est le fichier du jour 1 note = fonctions.readfile('notes') dicho (note,'Grange') - Ouvrir un texte (nedit) pour le programme "tri.py" # "driver" pour les tris en interactif import tri # la première fois reload(tri) # les autres fois # création du fichier de nombres aléatoires import random f=open("random","w") i = 0 while i < 100: f.write("%f " % random.random()) i=i+1 f.close() f=open("random","r") # lecture du fichier de nombres aléatoires ll=f.readline() # ll est une string avec toutes les valeurs t=[] # création du tableau de valeurs for x in string.split(ll): t.append(float(x)) (ensuite trier...) ====== tri.py #!/usr/bin/python import sys from copy import deepcopy import random import pdb #---------------def Compare(x,y): p=0 if y < x : p = -1 elif x < y : p = 1 return p #---------------def Echange(Tr,x,y): #Copie en dur, par valeur pas par adresse T=deepcopy(Tr) temp=T[x] T[x]=T[y] T[y]=temp return T #---------------def RechSeq(T,x): if T[0] == x : return 0 else: L=len(T) i=0 while i < L and T[i] != x : i=i+1 if i < L : return i else: return -1 #---------------def RechDich(T,x): trouve = -1 i = 0 j=len(T) while i <= j and trouve == -1 : mid=int((i+j)/2) if x == T[mid] : trouve = mid else : if x > T[mid] : i = mid + 1 else: j = mid - 1 return trouve #---------------def PosMin(T,x,y): mini=x for i in xrange(x+1,y): if Compare(T[i],T[min]) == 1 : mini=i return mini #---------------def TriSeq(T): i=0 L = len(T) Trie=deepcopy(T) while i < L-1 : min = PosMin(i,L) if i != min : Trie=Echange(Trie,i,min) i=i+1 return Trie #---------------def TriBulle(Tr): permut=1 L = len(Tr) T=deepcopy(Tr) i=ntot=0 comp=ech=0 while permut > 0 : if i == L-1 : print "Bulle ",ntot if ntot == 0 : permut = 0 else: i = 0 ntot = 0 #pdb.set_trace() else: p=Compare(T[i],T[i+1]) comp+=1 if p == -1 : ntot+=1 Trie=Echange(T,i,i+1) ech+=1 i=i+1 return T,comp,ech #---------------def TriIns(Tr): i=1 L = len(Tr) T=deepcopy(Tr) while i < L : p=Compare(T[i-1],T[i]) if p == -1 : pos=0 j=0 while j < i and T[i] > T[j] : j=j+1 pos = j if pos == 0 : temp=T[0:i] temf=T[i+1:L] T=[T[i]]+temp+temf elif pos < i : temp=T[0:pos] temf=T[pos:i] tem2=T[i+1:L] T=temp+[T[i]]+temf+tem2 i=i+1 print "TriIns ",T return T #---------------def TriShell(Tr) : T=deepcopy(Tr) #Calcul du pas h=0 while h < L : h = 3*h + 1 print h while h > 0 : i=h while i < L : valeur=T[i] j=i-h while j >= 0 and T[j] > valeur : T=Echange(T,j,j+h) j=j-h i+=1 print h," ** ",T h = h/3 return T #---------------def Fusion(T1,T2) : L1=len(T1) L2=len(T2) Fus=[] i=j=0 while i < L1 and j < L2 : if T1[i] < T2[j] : Fus.append(T1[i]) i+=1 else : Fus.append(T2[j]) j+=1 if i == L1 : Fus.extend(T2[j:]) else : Fus.extend(T1[i:]) return Fus #---------------def TriFusion(T) : if len(T) == 1 : return T else: mid=int(len(T)/2) Tfus=Fusion( TriFusion(T[:mid]) , TriFusion(T[mid:]) ) return Tfus #---------------def TriRapide(Tr,deb,fin) : T=deepcopy(Tr) if deb < fin : #Definition du pivot au milieu pivot = (deb+fin)/2 #On place le pivot au debut du tableau pour faire une comparaison continue T=Echange(T,deb,pivot) #Comparaison des valeurs % pivot et range val inf au debut posinf=deb for k in xrange(deb+1,fin) : if Compare(T[k],T[deb]) == 1: posinf+=1 T=Echange(T,k,posinf) #Mettre le pivot √† sa position, donc √† la suite des elements qui lui sont inferieurs T=Echange(T,deb,posinf) #Tri sous tableau gauche T=TriRapide(T,deb,posinf) #Tri sous tableau droit T=TriRapide(T,posinf+1,fin) return T #---------------def main(): x=10 T=[] for i in range(x) : T.append(int(10*random.random())) #T=[2, 3, 4, 3, 5, 9, 3, 4, 1, 6] print T T=TriRapide(T,0,x) print T main() Jour 3 : Cours Cours optimisation (simplexe, monte carlo et méthodes dérivées (recuit simulé), gradients) Optimisation numérique: (Cf document Minimisation numérique.doc) - monte carlo: exemple du calcul de π Optimisation combinatoire (Pierre) (Cf document Optimisation_combinatoire.doc) TP: dénombrement des mono et dinucléotides (comparaison)–les dictionnaires - comptage dans les séquences des mots de longueur 1 et 2 - calcul du CG skew le long d’un génome séquence au format fasta: >NC_002162 Ureaplasma urealyticum, complete genome. atggctaataattatcaaactttatatgattcagcaataaaaaggattccatacgatctt atttctgatcaagcttatgcaattctacaaaatgctaaaactcataaagtttgcgatggt gttttatatataattgtagccaatgcctttgaaaaaagtattattaacggtaattttatt ... Programme pour compter les mononucléotides, les dinucléotides, et calculer le GC Skew sur une fenêtre de longueur w avec sortie dans un fichier (lignes : <position> <GCskew>) à plotter dans gnuplot (avec plot "plotgc.txt" ) #!/usr/bin/python # --------------------------------------------------------def readfasta(nf): f=open("seq.fst","r") nf="seq.fst" seq="" line=f.readline() while (line!=""): line=f.readline() seq=seq+line[:-1] return seq # --------------------------------------------------------def dico(nf): dico={} for x in nf: if dico.has_key(x): dico[x]=dico[x]+1 else: dico[x]=1 return dico # --------------------------------------------------------def dicodi(nf): dicodi={} for i in range(len(seq[:-2])): dicodi[seq[i:i+2]]=dicodi.get(seq[i:i+2],0)+1 for j in dicodi.keys(): dicodi[j]=float(dicodi[j])/float(len(seq)) return dicodi # --------------------------------------------------------def compare(dico_mono,dico_dinuc): for i in dico_dinuc.keys(): fmono=float(dico_mono[i[0]]*dico_mono[i[1]]) fdinuc=dico_dinuc[i] print "%s %f %f" %(i,fmono,fdinuc) # --------------------------------------------------------def calcgc(dicogc): return float((dicogc["G"]-dicogc["C"]))/(dicogc["G"]+dicogc["C"]) # --------------------------------------------------------def gc(seq,w): gc={} gc=dico(seq[0:w]) filout=open("plotgc.txt","w") filout.write("%d %.3f\n" %(0, calcgc(gc))) for i in range(1,len(seq)-w+1): gc[seq[i-1]]=gc[seq[i-1]]-1 gc[seq[i+w-1]]=gc[seq[i+w-1]]+1 filout.write("%d %.3f\n" %(i, calcgc(gc))) filout.close() return # --------- MAIN -----------------------------------------seq=readfasta("seq.fst") m=dico(seq) d=dicodi(seq) compare(m,d) print "nombre de bases total",len(seq) countbases=dico(seq) print countbases dinucleotides=dicodi(seq) print dinucleotides gc(seq,1000) TP: camion, programmation dynamique camions (programmation dynamique). liens avec les séquences prendre le fichier de villes sur le site de Joel #--------------------------------------------------------------#!/usr/local/bin/python #!/usr/bin/python import sys def lit_fichier(fname): """ A partir du nom de fichier contenant les distances entre les paires de villes sous le format: ville1 ville2 distance, cette fonction rend la ville de depart (premier champs de la premiere ligne ainsi qu'un dictionnaire de distance ou les cles sont les paires (ville1, ville2) et les valeurs sont les distances en reels. """ f=open(fname,'r') l=f.readlines() dico={} for x in l: t=x.split() dico[t[0],t[1]]=float(t[2]) deb=l[0].split()[0] return deb,dico #--------------------------------------------------------------def tranche(debut,dict): """ Cette fonction a partir de la ville de depart et des cles du dictionnaire dict rend une liste de listes ou le ieme element correspond aux villes de la ieme trance a traverser """ tv=[] dd=dict.keys() temp=[debut] while len(temp)!=0: tv.append(temp) temp=[] debut=tv[-1][0] for i in dict.keys(): if i[0]==debut: temp.append(i[1]) return tv #--------------------------------------------------------------def chemin(liste,dict, dep): """ Cette fonction determine le chemin minimum en passant par une ville et une seule de chaque tranche de villes contenues dans liste en partant de la ville dep et en fonction des distances contenues dans dict cour correspond au meilleur chemin courrant (donc il est remis a rien a chaque nouvelle tranche l etudiee). A chaque fois qu'une tranche est terminee, je mets dans chem1 le resultat de cour. Chem1 est donc le meilleur chemin pour les villes de la lieme tranche -1. """ depart = [(dep), 0] cour=[depart] # Remplissage de cour pour la tranche 0 for l in liste[1:]: # Pour chaque tranche de villes chem1 = cour cour=[] for i in l: # Pour chaque ville de la tranche l # on fixe arbitrairement le minimum # a la premiere distance mini=chem1[0][-1]+dict[(chem1[0][-2],i)] for j in chem1: # Pour chaque ville de la tranche l-1 v=j[-2],i distemp=j[-1]+dict[v] if distemp<=mini: mini = distemp cmini = j[:-1] cmini.append(i) cmini.append(mini) cour.append(cmini) return cour #--------------------------------------------------------------def main(): if len(sys.argv)!=2: print "entrez la commande sous la forme: camion.py fichier" else: fichier=sys.argv[1] dep,donnees=lit_fichier(fichier) tran=tranche(dep,donnees) #print tran print chemin(tran, donnees, dep) main() #--------------------------------------------------------------- Jour 4 : TP: optimisation suite (recuit simulé à partir du montecarlo, gradient). Taboo search: grille à 2 dimensions #!/usr/bin/python import random import math #fonction de rosenbroock def rosen((x,y)): return 100.0*(y-(x*x))*(y-(x*x))+(1-x)*(1-x) # calcul du paysage de la fonction # sauvergarde dans fichier "paysage" # pour affichage dans gnuplot (sans "line") # Dans gnuplot, il suffira d'entrer # splot "paysage","recuit.xyz","taboo.xyz" # pour voir en 3D les chemins differe # (note: on peut aussi le faire directement # dans gnuplot, donc facultatif) def paysage(fun,xmin,xmax,ymin,ymax,pas): f=open("paysage","w") x=xmin while x<=xmax: y=ymin while y>=ymax: f.write("%f %f%f\n" %(y,y,fun((x,y)))) y=y+pas #print x,y,fun((x,y)) x=x+pas f.close() return # # recuit simule (fichier "recuit.py") # impression des points dans fichier "recuit.xyz" a # a tracer dans gnuplot avec l'option "with line" # arguments: # f: fonction # pstart: point 2D de depart (x,y) # tempinit: Temperature de depart # Tf: Temperature finale # npas: nombre de pas a chaque temperature # pas: longueur du pas # def recuit(f,(x,y),ti,tf,pas,npas): t = ti r= open("recuit.xyz",'w') # xmin,ymin vont etre les minima courant xmin,ymin=(x,y) # X,Y va etre le minimum minimorum X,Y = (xmin ,ymin) while t>tf: for i in range(0,npas): x = xmin + (random.random()*2 - 1)*pas y = ymin + (random.random()*2 - 1)*pas d = f((x,y)) - f((xmin,ymin)) if (d < 0): xmin ,ymin = x,y r.write("%f %f %f\n" % (xmin,ymin,f((xmin,ymin)))) if f((xmin,ymin)) < f((X,Y)): X,Y = (xmin ,ymin) elif math.exp((-d)/t) > random.random(): xmin ,ymin = x,y r.write("%f %f %f\n" %(xmin,ymin,f((xmin,ymin)))) if f((xmin,ymin)) < f((X,Y)): X,Y = (xmin ,ymin) t=t*0.7 return (X,Y) # # # # # # # # taboo search NotInList: fonction verifiant que l'element el n'est pas dans la liste (retourne 1 si c'est vrai) def NotInList(el, liste): # liste est une liste de 2-uples d'entiers # qui sont les arrondis de 10 fois les coordonnees # de maniere a ne pas repasser par un point proche # puisque les reels ne retomberaient jamais juste # el est le tuple des coordonnees reelles du point # dont on veut savoir si il est dans la liste taboo eli = (int(10*el[0]+5),int(10*el[1]+5)) for i in liste: if eli == i: return 0 return 1 # # taboo search: routine principale # impression des points dans fichier "taboo.xyz" a # tracer dans gnuplot avec l'option "with line" # arguments: # f: fonction # pstart: point 2D de depart (x,y) # npas: nombre de pas total a ne pas depasser # pas: longueur du pas # lentaboo: longueur de la liste tabou # def taboo(f,(x,y),npas,pas,lentaboo): r= open("taboo.xyz",'w') n = 0 listetaboo = [None]*lentaboo listetaboo[0] = (int(10*x+5),int(10*y+5)) xmin,ymin = (x,y) solutions = 1 while n < npas: x = (random.random()*2 - 1)*pas + x y = (random.random()*2 - 1)*pas + y if NotInList((x,y),listetaboo) == 1 and f((x,y)) <f((xmin,ymin)) : xmin,ymin = (x,y) solutions = solutions + 1 r.write("%f %f %f\n" %(x,y,f((x,y)))) listetaboo[solutions % lentaboo] = (int(10*x+5),int(10*y+5)) n+=1 return (xmin,ymin) s = recuit(rosen,(-2,2),1000,1,1.0,100) print "recuit: meilleure solution: ",s, rosen(s) s = taboo(rosen,(-2,2),10000,1,30) print"taboo: meilleure solution: ", s, rosen(s) Si on met les données du paysage dans « paysage », celle du trajet (x,y,f(x,y)) dans « recuit.xyz » (ou dans « taboo.xyz »), il suffit pour tracer le chemin de minimisation dans le paysage de donner les ordres suivant dans gnuplot : splot "paysage","taboo.xyz" with line,"recuit.xyz" with line # ou mieux, on peut créer le « paysage » directement avec : set hidden3d set isosamples 30 set view 60,30,1 splot [-2.0:2] [-2:2] 100 * (y - (x*x))*(y - (x*x))+(1-x)*(1-x), "recuit.xyz" with line, "taboo.xyz" with line (* ceci figure la fontion de Rosenbrook en 3D avec les trajets par dessus *) ANNEXE Affectation: Auction algorithm: Personnes Voitures i -> A[i] u[i,j] pour chaque j appartenant a A[i] Si C = MAX{u[i,j]: (i,j) appartenant a A} A chaque pas, il existe un prix price [j] pour les voitures. Marginal utility de la personne i pour acheter j -> u[i,j] - price[j] A chaque itération, une personne "non assignée" choisit un j tel que u[i,j]-price[j] est maximum On associe a chaque personne i une value[i], plafond sur la plus haute "marginal utility", cad value[i] >= MAX{u[i,j]: (i,j) appartenant a A} On dit que: Bid[i,j] est admissible si value[i] = u[i,j] - price[j] Bid[i,j] est inadmissible si value[i] sinon Il faut pour l'algorithme que chaque Bid soit "admissible". Si la personne i est la prochaine à être traitée et qu'elle n'a pas de Bid "admissible", alors value[i] est trop haute, et on décroît cette valeur a: MAX{u[i,j]-price[j]: (i,j) appartenant a A} Donc, l'algorithme procède en personnes "préemptant" des voitures. Si une personne i préempte une voiture j, alors le prix de j augmente de 1F; ainsi les préemptions suivantes seront plus élevées, et i est assigné à j. La personne k qui avait une préemption sur j (si elle existe) devient "inassignée", elle devra préempter une autre voiture par la suite. Tant que le marché continue, le prix des voitures augmente et les valeurs marginales des personnes décroissent. L'algorithme s'arrête dès que chaque personne a une voiture. On atteint un assignement optimum. Depart: price[j] = 0 pour chaque voiture value[i] = MAX{u[i,j]: (i,j) appartenant a A} (c'est suffisant pour une version pseudo-polynomiale) exemple d'affectation: Min SOMME(i,j) (xiyj) y 6 12 15 15 x 4 8 9 11 10 5 7 8 12 10 6 9 Resultat: (x1y1),(x2y4),(x3y2),(x4y3), SOMME=28