NGL@1.0.0-beta.7 Home Manual Reference Source Gallery

src/parser/prmtop-parser.js

/**
 * @file Prmtop Parser
 * @author Alexander Rose <alexander.rose@weirdbyte.de>
 * @private
 */

import { Debug, Log, ParserRegistry } from '../globals.js'
import StructureParser from './structure-parser.js'
import {
    assignResidueTypeBonds, calculateBondsBetween,
    calculateBondsWithin, calculateChainnames
} from '../structure/structure-utils.js'

const amberChargeUnitFactor = 18.2223

const TitleMode = 1
const PointersMode = 2
const AtomNameMode = 3
const ChargeMode = 4
const MassMode = 5
const ResidueLabelMode = 6
const ResiduePointerMode = 7
const BondsIncHydrogenMode = 8
const BondsWithoutHydrogenMode = 9
const RadiiMode = 10

function parseIntSubstr (line, start, length) {
  return parseInt(line.substr(start, length).trim())
}

class PrmtopParser extends StructureParser {
  get type () { return 'prmtop' }

  _parse () {
    // http://ambermd.org/prmtop.pdf
    // http://ambermd.org/formats.html#topology

    if (Debug) Log.time('PrmtopParser._parse ' + this.name)

    const s = this.structure
    const sb = this.structureBuilder

    //

    const atomMap = s.atomMap
    const atomStore = s.atomStore
    atomStore.addField('partialCharge', 1, 'float32')
    atomStore.addField('radius', 1, 'float32')

    const title = []
    const pointersDict = {}
    const pointers = [
      'NATOM', 'NTYPES', 'NBONH', 'MBONA', 'NTHETH', 'MTHETA',
      'NPHIH', 'MPHIA', 'NHPARM', 'NPARM', 'NNB', 'NRES',
      'NBONA', 'NTHETA', 'NPHIA', 'NUMBND', 'NUMANG', 'NPTRA',
      'NATYP', 'NPHB', 'IFPERT', 'NBPER', 'NGPER', 'NDPER',
      'MBPER', 'MGPER', 'MDPER', 'IFBOX', 'NMXRS', 'IFCAP',
      'NUMEXTRA', 'NCOPY'
    ]
    pointers.forEach(name => { pointersDict[ name ] = 0 })

    let atomNames
    let charges
    let radii
    let bAtomIndex1
    let bAtomIndex2
    let bBondOrder
    let residueLabels
    let residuePointers

    let mode
    // let currentFormat
    let curIdx
    let bondIdx

    function _parseChunkOfLines (_i, _n, lines) {
      for (let i = _i; i < _n; ++i) {
        const line = lines[ i ]
        const lt = line.trim()

        if (!lt) {
          continue
        } else if (line.startsWith('%FORMAT')) {
          // currentFormat = lt.substring(8, lt.length - 1)
        } else if (line.startsWith('%FLAG')) {
          const flag = line.substr(5).trim()
          curIdx = 0

          if (flag === 'TITLE') {
            mode = TitleMode
          } else if (flag === 'POINTERS') {
            mode = PointersMode
          } else if (flag === 'ATOM_NAME') {
            mode = AtomNameMode
          } else if (flag === 'CHARGE') {
            mode = ChargeMode
          } else if (flag === 'MASS') {
            mode = MassMode
          } else if (flag === 'RESIDUE_LABEL') {
            mode = ResidueLabelMode
          } else if (flag === 'RESIDUE_POINTER') {
            mode = ResiduePointerMode
          } else if (flag === 'BONDS_INC_HYDROGEN') {
            bondIdx = 0
            mode = BondsIncHydrogenMode
          } else if (flag === 'BONDS_WITHOUT_HYDROGEN') {
            bondIdx = pointersDict['NBONH']
            mode = BondsWithoutHydrogenMode
          } else if (flag === 'RADII') {
            mode = RadiiMode
          } else {
            mode = undefined
          }
        } else if (mode === TitleMode) {
          title.push(lt)
        } else if (mode === PointersMode) {
          const n = Math.min(curIdx + 10, 32)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            pointersDict[pointers[curIdx]] = parseInt(
              line.substr(i * 8, 8).trim()
            )
          }
          atomNames = new Array(pointersDict.NATOM)
          charges = new Float32Array(pointersDict.NATOM)
          radii = new Float32Array(pointersDict.NATOM)
          atomStore.resize(pointersDict.NATOM)
          const bondCount = pointersDict.NBONH + pointersDict.MBONA
          bAtomIndex1 = new Uint32Array(bondCount)
          bAtomIndex2 = new Uint32Array(bondCount)
          bBondOrder = new Uint8Array(bondCount)
          residueLabels = new Array(pointersDict.NRES)
          residuePointers = new Uint32Array(pointersDict.NRES)
        } else if (mode === AtomNameMode) {
          const n = Math.min(curIdx + 20, pointersDict.NATOM)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            atomNames[curIdx] = line.substr(i * 4, 4).trim()
          }
        } else if (mode === ChargeMode) {
          const n = Math.min(curIdx + 5, pointersDict.NATOM)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            charges[curIdx] = parseFloat(line.substr(i * 16, 16)) / amberChargeUnitFactor
          }
        } else if (mode === MassMode) {

          // not currently used

        } else if (mode === ResidueLabelMode) {
          const n = Math.min(curIdx + 20, pointersDict.NRES)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            residueLabels[curIdx] = line.substr(i * 4, 4).trim()
          }
        } else if (mode === ResiduePointerMode) {
          const n = Math.min(curIdx + 10, pointersDict.NRES)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            residuePointers[curIdx] = parseIntSubstr(line, i * 8, 8)
          }
        } else if (mode === BondsIncHydrogenMode) {
          const n = Math.min(curIdx + 10, pointersDict.NBONH * 3)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            const r = curIdx % 3
            if (r === 0) {
              bAtomIndex1[bondIdx] = parseIntSubstr(line, i * 8, 8) / 3
            } if (r === 1) {
              bAtomIndex2[bondIdx] = parseIntSubstr(line, i * 8, 8) / 3
              bBondOrder[bondIdx] = 1
              ++bondIdx
            }
          }
        } else if (mode === BondsWithoutHydrogenMode) {
          const n = Math.min(curIdx + 10, pointersDict.MBONA * 3)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            const r = curIdx % 3
            if (r === 0) {
              bAtomIndex1[bondIdx] = parseIntSubstr(line, i * 8, 8) / 3
            } if (r === 1) {
              bAtomIndex2[bondIdx] = parseIntSubstr(line, i * 8, 8) / 3
              bBondOrder[bondIdx] = 1
              ++bondIdx
            }
          }
        } else if (mode === RadiiMode) {
          const n = Math.min(curIdx + 5, pointersDict.NATOM)
          for (let i = 0; curIdx < n; ++i, ++curIdx) {
            radii[curIdx] = parseFloat(line.substr(i * 16, 16))
          }
        }
      }
    }

    this.streamer.eachChunkOfLines(function (lines/*, chunkNo, chunkCount */) {
      _parseChunkOfLines(0, lines.length, lines)
    })

    s.title = title.join(' ')

    const atomCount = pointersDict.NATOM
    let curResIdx = 0
    let curResname = residueLabels[0]
    let curResno = 1
    for (let i = 0; i < atomCount; ++i) {
      if (i + 1 === residuePointers[curResIdx + 1]) {
        ++curResIdx
        curResname = residueLabels[curResIdx]
        curResno = curResIdx + 1
      }
      atomStore.atomTypeId[i] = atomMap.add(atomNames[i])
      atomStore.serial[i] = i + 1
      sb.addAtom(0, '', '', curResname, curResno)
    }

    atomStore.partialCharge.set(charges)
    atomStore.radius.set(radii)

    s.bondStore.length = bBondOrder.length
    s.bondStore.count = bBondOrder.length
    s.bondStore.atomIndex1 = bAtomIndex1
    s.bondStore.atomIndex2 = bAtomIndex2
    s.bondStore.bondOrder = bBondOrder

    sb.finalize()
    s.finalizeAtoms()
    s.finalizeBonds()
    calculateBondsWithin(s, true)
    calculateBondsBetween(s, true, true)
    calculateChainnames(s, true)
    assignResidueTypeBonds(s)

    if (Debug) Log.timeEnd('PrmtopParser._parse ' + this.name)
  }
}

ParserRegistry.add('prmtop', PrmtopParser)
ParserRegistry.add('parm7', PrmtopParser)

export default PrmtopParser