Función CrearTPDB


Desarrollar una función de parsing de ficheros PDB (que denominaremos GetPDB) que permita representar adecuadamente los datos estructurales relevantes de una proteína en nuestro entorno de programación. Dicha función deberá ser capaz de leer los datos desde un fichero pdb y almacenarlos en una estructura matricial (cuyo tipo denominaremos TPDB) integrada por registros (de tipo TAtomPDB) que contendrán campos relativos a la identificación de cada átomo, residuo y subunidad a la que pertenece, coordenadas cristalográficas (a la que asignaremos un tipo TPunto), etc. La función se empleará en las sucesivas aplicaciones para interaccionar con los ficheros de estructura.


La función CargarProteina es una de las funciones de la librería biotools y es capaz de leer los datos de un fichero *.pdb y almacenarlos en una estructura matricial del tipo TPDB (explicado en la actividad 2), que permite a los sucesivos programas creados manejar esta información. Esta función solo requiere un único argumento de tipo TStrings. Por otra parte, la salida de esta función (y, por tanto, la función) es de tipo TPDB. Así, el descriptor de la función es el siguiente:

function CrearTPDB(lineas: TStrings): TPDB;

En la cláusula var se declaran las siguientes variables:

  p: TPDB;
  j, n, nr, nc: integer; 
  linea: string;

La variable p es de tipo TPDB y es donde se almacenarán los datos de la proteína. Las variables j, n, nr y nc son de tipo entero y son variables contadoras. La variable j es la variable contadora de un bucle for, mientras que las otras variables son más especiales y se explicarán más adelante en esta actividad. Por último, la variable linea se utiliza para ir guardando una a una todas las líneas contenidas en el TStrings lineas.
A continuación, comienza el bloque principal de la función CargarProteina. En primer lugar, aparecen 4 líneas de código con las que se fija el tamaño de distintos arrays que habían sido creados dinámicamente al crear los distintos tipos de records descritos en la actividad 2.

  setlength(p.atom, lineas.Count);
  setlength(p.indice, lineas.Count);
  setlength(p.res, lineas.Count);
  setlength(p.cad, lineas.Count);  

  • La matriz p.atom se trata de un array de datos de tipo TAtomPDB.
  • La matriz p.indice es un array de números enteros.
  • La matriz p.res consiste en un array con datos de tipo TResiduoPDB.
  • La matriz p.cad se trata de un array de datos de tipo TCadenaPDB.
Como la longitud de cada uno de estos campos del TPDB es distinta para cada proteína, el tamaño de cada una de estas matrices se fija en exceso, es decir, se crean más elementos de los que van a rellenarse. Esto se hace fijando el número de elementos al número de líneas que posee el TStrings lineas. Una vez que ya se han rellenado todos estos campos del record TPDB se ajusta su tamaño de nuevo al número de elementos que tengan en concreto para esa proteína.
Posteriormente se establece en 0 el valor de las variables n, nr y nc:

  n:= 0; nr:= 0; nc:= 0;

Una vez que se han hecho todos estos preparativos se rellenan los distintos campos del TPDB p. Cada línea del código se explica mediante comentarios.

  p.header:= copy(lineas[0],63,66); //Este campo contiene la ID de 4 dígitos del fichero *.pdb.
  for j:= 1 to lineas.Count-1 do //Este bucle permite recorrer todas las líneas del TStrings de entrada de la función.
  begin
    linea:= lineas[j]; //Cada línea del TString se guarda en la variable linea de tipo string.
    if (copy(linea,1,6) = 'ATOM  ') and ((linea[17] = ' ') or (linea[17] = 'A')) then //Con esta condición se seleccionan solo aquellas líneas del fichero *.pdb que contienen información sobre los átomos de la proteína, excluyendo aquellas que contienen átomos sobre los que se han propuesto otras coordenadas alternativas, lo cual va ser ignorado por los programas de esta asignatura.
    begin
      n:= n+1; //El contador n permite acceder a cada una de las posiciones del array atom, con datos de tipo TAtomPDB.
      p.atom[n].NumAtom:= strtoint(trim(copy(linea,7,5))); //Este campo contiene el serial number de cada átomo, es decir, el número con el que se identifica cada átomo en el fichero *.pdb. No tiene por qué ser un número consecutivo (ver p.indice).
      p.atom[n].ID:= trim(copy(linea,13,4)); //Este campo contiene la ID de cada átomo.
      p.atom[n].NumRes:= strtoint(trim(copy(linea,23,4))); //Este campo contiene el número de residuo al que pertenece cada átomo.
      p.atom[n].AltRes:= linea[17]; //Este campo indica si se trata de coordenadas alternativas del átomo o no.
      p.atom[n].Residuo:= trim(copy(linea,18,3)); //Este campo indica el residuo al que pertenece cada átomo en código de 3 letras.
      p.atom[n].Subunidad:= linea[22]; //Este campo hace referencia a la subunidad a la que pertenece cada átomo.
      p.atom[n].coor.X:= strtofloat(trim(copy(linea,31,8))); //Este campo contiene la coordenada X de cada átomo.
      p.atom[n].coor.Y:= strtofloat(trim(copy(linea,39,8))); //Este campo contiene la coordenada Y de cada átomo.
      p.atom[n].coor.Z:= strtofloat(trim(copy(linea,47,8))); //Este campo contiene la coordenada Z de cada átomo.
      p.indice[n]:= n; //Este campo permite asignar un número entero consecutivo a cada átomo de la proteína, ya que el serial number que aparece en el fichero *.pdb puede no ser consecutivo (ver p.atom.NumAtom).
      if p.atom[n].ID = 'N' then //La línea del primer átomo de cada residuo que aparece en el fichero *.pdb siempre coincide con el la línea del átomo de nitrógeno que forma parte del esqueleto covalente de la proteína. Por tanto, este condicional permite rellenar los campos del TPDB relacionados con residuos y no con átomos.
      begin
        nr:= nr+1; //El contador nr permite acceder a cada una de las posiciones del array res, con datos de tipo TResiduoPDB.
        p.res[nr].NumRes:= p.atom[n].NumRes; //Este campo permite asignar a cada residuo un número entero consecutivo.
        p.res[nr].ID:= p.atom[n].Residuo; //Este campo permite asignar a cada residuo su ID en código de 3 letras.
        p.res[nr].Subunidad:= p.atom[n].Subunidad;  //Este campo indica la subunidad a la que pertenece cada residuo.
        p.res[nr].N:= n; //Este campo contiene el serial number de cada átomo de nitrógeno del esqueleto covalente de la proteína.
      end;
      if p.atom[n].ID = 'CA' then p.res[nr].CA:= n; //Este campo contiene el serial number de cada carbono α del esqueleto covalente de la proteína.
      if p.atom[n].ID = 'C' then p.res[nr].C:= n; //Este campo contiene el serial number de cada carbono carbonílico del esqueleto covalente de la proteína.
      if p.atom[n].ID = 'O' then p.res[nr].O:= n; //Este campo contiene el serial number de cada átomo de oxígeno del esqueleto covalente de la proteína.
      if pos(p.atom[n].subunidad, p.cadenas) = 0 then //Este condicional se cumplirá cuando la subunidad a la que pertenezca un átomo determinado no esté presente en el campo p.cadenas. Esto se traduce en que ha aparecido una nueva subunidad y se rellenarán los campos necesarios relacionados con esta nueva subunidad.
      begin
        p.cadenas:= p.cadenas + p.atom[n].Subunidad; //Esto permite elongar el campo p.cadenas (de tipo string) con la ID de la nueva subunidad.
        nc:= nc + 1; //El contador nc permite acceder a cada una de las posiciones del array candenas, con datos de tipo TCadenaPDB.
        p.cad[nc].ID:= p.atom[n].Subunidad; //Este campo contiene la ID de cada subunidad.
        p.cad[nc].AtomIni:= n; //Este campo contiene el serial number del primer átomo de cada subunidad.
        p.cad[nc].AtomFin:= n; //Este campo contiene el serial number del último átomo de cada subunidad.
        p.cad[nc].ResIni:= nr; //Este campo contiene el serial number del primer residuo de cada subunidad.
        p.cad[nc].ResFin:= nr; //Este campo contiene el serial number del último residuo de cada subunidad.
      end else //Si no se está iniciando una subunidad, que es lo que ocurre en la mayoría de los casos:
      begin
        p.cad[nc].AtomFin:= n; //Este campo contiene el serial number del último átomo de cada subunidad.
        p.cad[nc].ResFin:= nr; //Este campo contiene el serial number del último residuo de cada subunidad.
      end;
    end;
  end;
  p.secuencia:= ''; //Con esta línea y la siguiente se consigue crear un string con la secuencia primaria de la proteína en código de una letra.
  for j:= 1 to nr do p.secuencia:= p.secuencia + aa3to1(p.res[j].ID);
  p.NumFichas:= n; //Este campo contiene el número de átomos total de la proteína.
  p.NumRes:= nr; //Este campo contiene el número de residuos total de la proteína.
  p.NumCad:= nc; //Este campo contiene el número de cadenas total de la proteína.

Las últimas líneas de esta función permiten ajustar el tamaño de los distintos arrays declarados dinámicamente al principio al número de elementos que posean para cada proteína dada y asignar la salida de la función a la variable local de tipo TPDB p:

  setlength(p.atom, n+1);
  setlength(p.indice, n+1);
  setlength(p.res, nr+1);
  setlength(p.cad, nc+1);
  result:= p;

Bibliografía

  1. Apuntes de clase de la asignatura “Ingeniería de Proteínas” del Grado en Bioquímica.
  2. https://wiki.freepascal.org/. Fecha de acceso: 19 de junio de 2019.