En la realización de este cuaderno de actividades se pide el desarrollo de una función, CargarPDB, capaz de parsear la información estructural relevante de un archivo PDB y almacenarla en una estructura matricial. Esta función ha sido realizada durante el transcurso de la asignatura y es uno de los elementos claves del entorno de trabajo que hemos desarrollado durante el semestre. A continuación se procede a mostrar de forma detallada el funcionamiento de la misma. El código correspondiente a esta funcionalidad se puede encontrar en la librería biotools/src_biotools. Por último, aclarar que para la explicación no se seguirá el orden cronológico en que se programó (el cuál podría consultarse siguiendo los commits del repositorio) sino el orden lógico de este. Este apartado se corresponde a la 3ª actividad de la relación de ejercicios.
En primer lugar, se definieron una serie de records que nos permitieran almacenar la información de forma ordenada y de vectores dinámicos, dynamic arrays, para almacenar estos. Un record en pascal es un tipo de dato altamente estructurado y que consiste en la agrupación de elementos de distinto tipo. Un array es un tipo que agrupa variables del mismo tipo. Utilizamos vectores dinámicos cuando no es posible conocer el número exacto de elementos necesarios, por lo será necesario definir el tamaño de las mismas durante la ejecución del programa.
Se definió un record que contuviera la información relativa a un punto en el espacio, TPunto, y un array de puntos.
Se definió un record que contuviera la información relativa a un átomo de un archivo PDB en el espacio. La información que se consideró fue la siguiente como relevante fue la siguiente. Destacar que la información relativa a las coordenadas se almacena en un TPunto.
Se definió un record que contuviera la información relativa a un residuo de un archivo PDB, es decir, a una serie grupo de átomos (de longitud variable) sobre los cuáles se pueden definir nuevas propiedades como ángulos diedros.
Por último, se definieron unos record con la información relativa a una subunidad de un archivo PDB y a un archivo PDB completo. Destacar que, en este caso, se optó por almacenar información redundante con el fin de simplificar el acceso a esta luego. Estos records constan de una serie de vectores dinámicos que contienen TAtomPDB, TResiduoPDB, TSubunidadPDB y también de índices.
TAtomPDB=recordNumAtom:integer;//Número de átomoID:string;//Tipo de átomoresiduo:string;//Residuo al que pertenecesubunidad:char;//Subunidad a la que perteneceNumRes:integer;//Número de residuocoor:Tpunto;//Coordenadas espacialesR:real;//Factor tempend;
TResiduoPDB=record// con los residuosphi,psi:real;//Ángulos diedrosNumRes:integer;//Número de residuosubunidad:char;//Subunidad a la que perteneceID3:string;//Identificador residuo 3 letrasID1:char;//Identificador residuo 1 letraAtm1,AtmN:integer;//Número 1º y último átomoN,CA,C,O:integer;//Número átomos esenciales de residuoend;
TPDB=recordheader:string;atm:arrayofTAtomPDB;//array de records con información átomosres:arrayofTResiduoPDB;//array de records con información residuossub:arrayofTsubunidadPDB;//array de records con información subunidadesNumFichas,NumResiduos:integer;//Número de átomos y residuosNumSubunidades:integer;//Número de subunidadessubs,secuencia:string;//Strings caracteres subunidades y secuenciaend;
El cálculo de ángulos de torsión presentaba una mayor dificultad. En primer lugar, para facilitar el trabajo vectorial, se definieron nuevos operadores aritméticos y booleanos para el tipo TPunto. Por ejemplo, así definimos algunos de ellos:
La función CargarPDB acepta como único argumento un TStrings (texto contenido en un memo) y, de forma sistemática, recorre las líneas del archivo PDB, accediendo a la información relativa a cada átomo, residuo y subunidad, y guardándola en un record dentro de un array. Para hacer esto se tuvo en cuenta que los archivos .pdb son archivos que siguen unas pautas de formato muy específicas. Una vez recorrido el archivo .pdb, se recorre cada una de las subunidades y residuos del TPDB para definir los ángulos diedros \(\psi\) y \(\phi\). A continuación, se muestra la función CargarPDB:
functionCargarPDB(texto:TStrings):TPDB;overload;varp:TPDB;linea:string;j,k,F,R,S:integer;resno:integer;beginifisPDB(texto)=FalsethenShowMessage('No es un archivo PDB');p.secuencia:='';F:=0;R:=0;S:=0;setlength(p.atm,texto.count);setlength(p.res,texto.count);setlength(p.sub,texto.count);forj:=0totexto.count-1dobeginlinea:=texto[j];if(copy(linea,1,6)='ATOM ')thenbeginF:=F+1;p.atm[F].NumAtom:=strtoint(trim(copy(linea,7,5)));p.atm[F].ID:=trim(copy(linea,13,4));p.atm[F].Residuo:=copy(linea,18,3);p.atm[F].Subunidad:=linea[22];p.atm[F].NumRes:=strtoint(trim(copy(linea,23,4)));p.atm[F].coor.X:=strtofloat(trim(copy(linea,31,8)));p.atm[F].coor.Y:=strtofloat(trim(copy(linea,39,8)));p.atm[F].coor.Z:=strtofloat(trim(copy(linea,47,8)));p.atm[F].R:=strtofloat(trim(copy(linea,61,6)));//Residuoifp.atm[F].ID='N'thenbeginR:=R+1;p.res[R].Atm1:=F;p.res[R].ID3:=p.atm[F].Residuo;p.res[R].ID1:=AA3to1(p.res[R].ID3);p.res[R].N:=F;p.res[R].NumRes:=p.atm[F].NumRes;p.res[R].Subunidad:=p.atm[F].Subunidad;p.secuencia:=p.secuencia+p.res[R].ID1;//Subunidadifpos(p.atm[F].Subunidad,p.subs)=0thenbeginS:=S+1;p.subs:=p.subs+p.atm[F].Subunidad;p.sub[S].ID:=p.atm[F].Subunidad;p.sub[S].atm1:=F;p.sub[S].res1:=R;end;end;ifp.atm[F].ID='CA'thenp.res[R].CA:=F;ifp.atm[F].ID='C'thenp.res[R].C:=F;ifp.atm[F].ID='O'thenp.res[R].O:=F;p.res[R].AtmN:=F;p.sub[S].atmN:=F;p.sub[S].resN:=R;end;end;setlength(p.atm,F+1);setlength(p.res,R+1);setlength(p.sub,S+1);p.NumFichas:=F;p.NumResiduos:=R;p.NumSubunidades:=S;forj:=1top.NumSubunidadesdowithp.sub[j]dobeginAtomCount:=atmN-atm1+1;ResCount:=resN-res1+1;fork:=p.sub[j].res1+1top.sub[j].resn-1dobeginp.res[k].phi:=torsion(p.atm[p.res[k-1].C].coor,p.atm[p.res[k].N].coor,p.atm[p.res[k].CA].coor,p.atm[p.res[k].C].coor);p.res[k].psi:=torsion(p.atm[p.res[k].N].coor,p.atm[p.res[k].CA].coor,p.atm[p.res[k].C].coor,p.atm[p.res[k+1].N].coor);end;setlength(p.sub[j].resindex,p.NumResiduos+1);fork:=1top.sub[j].ResCountdobeginresno:=p.sub[j].res1+k-1;p.sub[j].resindex[p.res[resno].numres]:=resno;end;end;result:=p;end;
Además, se realizó una función CargarPDB sobrecargada que facilitara el uso de esta función dentro del entorno Lazarus y simplificara el código. Se decidió hacer esto porque, siendo el aspecto central de la asignatura el estudio de archivos PDB, iba a ser muy utilizada. La versión sobrecargada es la siguiente.
functioncargarPDB(varp:TPDB):string;vardialogo:TOpenDialog;textoPDB:TStrings;begindialogo:=TOpenDialog.create(application);textoPDB:=TStringlist.create;ifdialogo.executethenbegintextoPDB.loadfromfile(dialogo.filename);ifisPDB(textoPDB)=FalsethenShowMessage('No es un archivo PDB');p:=cargarPDB(textoPDB);result:=dialogo.filename;endelseresult:='';dialogo.free;textoPDB.free;end;