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

src/parser/xtc-parser.js

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

import { Debug, Log, ParserRegistry } from '../globals.js'
import { ensureBuffer } from '../utils.js'
import TrajectoryParser from './trajectory-parser.js'

const MagicInts = new Uint32Array([
  0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 10, 12, 16, 20, 25, 32, 40, 50, 64,
  80, 101, 128, 161, 203, 256, 322, 406, 512, 645, 812, 1024, 1290,
  1625, 2048, 2580, 3250, 4096, 5060, 6501, 8192, 10321, 13003,
  16384, 20642, 26007, 32768, 41285, 52015, 65536, 82570, 104031,
  131072, 165140, 208063, 262144, 330280, 416127, 524287, 660561,
  832255, 1048576, 1321122, 1664510, 2097152, 2642245, 3329021,
  4194304, 5284491, 6658042, 8388607, 10568983, 13316085, 16777216
])
const FirstIdx = 9
// const LastIdx = MagicInts.length

function sizeOfInt (size) {
  let num = 1
  let numOfBits = 0
  while (size >= num && numOfBits < 32) {
    numOfBits++
    num <<= 1
  }
  return numOfBits
}

const _tmpBytes = new Uint8Array(32)

function sizeOfInts (numOfInts, sizes) {
  let numOfBytes = 1
  let numOfBits = 0
  _tmpBytes[0] = 1
  for (let i = 0; i < numOfInts; i++) {
    let bytecnt
    let tmp = 0
    for (bytecnt = 0; bytecnt < numOfBytes; bytecnt++) {
      tmp += _tmpBytes[bytecnt] * sizes[i]
      _tmpBytes[bytecnt] = tmp & 0xff
      tmp >>= 8
    }
    while (tmp !== 0) {
      _tmpBytes[bytecnt++] = tmp & 0xff
      tmp >>= 8
    }
    numOfBytes = bytecnt
  }
  let num = 1
  numOfBytes--
  while (_tmpBytes[numOfBytes] >= num) {
    numOfBits++
    num *= 2
  }
  return numOfBits + numOfBytes * 8
}

function decodeBits (buf, cbuf, numOfBits, buf2) {
  const mask = (1 << numOfBits) - 1
  let lastBB0 = buf2[1]
  let lastBB1 = buf2[2]
  let cnt = buf[0]
  let num = 0

  while (numOfBits >= 8) {
    lastBB1 = (lastBB1 << 8) | cbuf[cnt++]
    num |= (lastBB1 >> lastBB0) << (numOfBits - 8)
    numOfBits -= 8
  }

  if (numOfBits > 0) {
    if (lastBB0 < numOfBits) {
      lastBB0 += 8
      lastBB1 = (lastBB1 << 8) | cbuf[cnt++]
    }
    lastBB0 -= numOfBits
    num |= (lastBB1 >> lastBB0) & ((1 << numOfBits) - 1)
  }

  num &= mask
  buf[0] = cnt
  buf[1] = lastBB0
  buf[2] = lastBB1

  return num
}

const _tmpIntBytes = new Int32Array(32)

function decodeInts (buf, cbuf, numOfInts, numOfBits, sizes, nums, buf2) {
  let numOfBytes = 0
  _tmpIntBytes[1] = 0
  _tmpIntBytes[2] = 0
  _tmpIntBytes[3] = 0

  while (numOfBits > 8) {
    // this is inversed??? why??? because of the endiannness???
    _tmpIntBytes[numOfBytes++] = decodeBits(buf, cbuf, 8, buf2)
    numOfBits -= 8
  }

  if (numOfBits > 0) {
    _tmpIntBytes[numOfBytes++] = decodeBits(buf, cbuf, numOfBits, buf2)
  }

  for (let i = numOfInts - 1; i > 0; i--) {
    let num = 0
    for (let j = numOfBytes - 1; j >= 0; j--) {
      num = (num << 8) | _tmpIntBytes[j]
      const p = (num / sizes[i]) | 0
      _tmpIntBytes[j] = p
      num = num - p * sizes[i]
    }
    nums[i] = num
  }
  nums[0] = (
    _tmpIntBytes[0] |
    (_tmpIntBytes[1] << 8) |
    (_tmpIntBytes[2] << 16) |
    (_tmpIntBytes[3] << 24)
  )
}

class XtcParser extends TrajectoryParser {
  get type () { return 'xtc' }
  get isBinary () { return true }

  _parse () {
    // https://github.com/gromacs/gromacs/blob/master/src/gromacs/fileio/xtcio.cpp
    // https://github.com/gromacs/gromacs/blob/master/src/gromacs/fileio/libxdrf.cpp

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

    const bin = ensureBuffer(this.streamer.data)
    const dv = new DataView(bin)

    const f = this.frames
    const coordinates = f.coordinates
    const boxes = f.boxes
    const times = f.times

    const minMaxInt = new Int32Array(6)
    const sizeint = new Int32Array(3)
    const bitsizeint = new Int32Array(3)
    const sizesmall = new Uint32Array(3)
    const thiscoord = new Float32Array(3)
    const prevcoord = new Float32Array(3)

    let offset = 0
    const buf = new Int32Array(3)
    const buf2 = new Uint32Array(buf.buffer)

    while (true) {
      let frameCoords

      // const magicnum = dv.getInt32(offset)
      const natoms = dv.getInt32(offset + 4)
      // const step = dv.getInt32(offset + 8)
      offset += 12

      const natoms3 = natoms * 3

      times.push(dv.getFloat32(offset))
      offset += 4

      const box = new Float32Array(9)
      for (let i = 0; i < 9; ++i) {
        box[i] = dv.getFloat32(offset) * 10
        offset += 4
      }
      boxes.push(box)

      if (natoms <= 9) {  // no compression
        for (let i = 0; i < natoms; ++i) {
          frameCoords[i] = dv.getFloat32(offset)
          offset += 4
        }
      } else {
        buf[0] = buf[1] = buf[2] = 0.0
        sizeint[0] = sizeint[1] = sizeint[2] = 0
        sizesmall[0] = sizesmall[1] = sizesmall[2] = 0
        bitsizeint[0] = bitsizeint[1] = bitsizeint[2] = 0
        thiscoord[0] = thiscoord[1] = thiscoord[2] = 0
        prevcoord[0] = prevcoord[1] = prevcoord[2] = 0

        frameCoords = new Float32Array(natoms3)
        let lfp = 0

        const lsize = dv.getInt32(offset)
        offset += 4
        const precision = dv.getFloat32(offset)
        offset += 4

        minMaxInt[0] = dv.getInt32(offset)
        minMaxInt[1] = dv.getInt32(offset + 4)
        minMaxInt[2] = dv.getInt32(offset + 8)
        minMaxInt[3] = dv.getInt32(offset + 12)
        minMaxInt[4] = dv.getInt32(offset + 16)
        minMaxInt[5] = dv.getInt32(offset + 20)
        sizeint[0] = minMaxInt[3] - minMaxInt[0] + 1
        sizeint[1] = minMaxInt[4] - minMaxInt[1] + 1
        sizeint[2] = minMaxInt[5] - minMaxInt[2] + 1
        offset += 24

        let bitsize
        if ((sizeint[0] | sizeint[1] | sizeint[2]) > 0xffffff) {
          bitsizeint[0] = sizeOfInt(sizeint[0])
          bitsizeint[1] = sizeOfInt(sizeint[1])
          bitsizeint[2] = sizeOfInt(sizeint[2])
          bitsize = 0  // flag the use of large sizes
        } else {
          bitsize = sizeOfInts(3, sizeint)
        }

        let smallidx = dv.getInt32(offset)
        offset += 4
        // if (smallidx == 0) {alert("Undocumented error 1"); return;}

        // let tmpIdx = smallidx + 8
        // const maxidx = (LastIdx < tmpIdx) ? LastIdx : tmpIdx
        // const minidx = maxidx - 8  // often this equal smallidx
        let tmpIdx = smallidx - 1
        tmpIdx = (FirstIdx > tmpIdx) ? FirstIdx : tmpIdx
        let smaller = (MagicInts[tmpIdx] / 2) | 0
        let smallnum = (MagicInts[smallidx] / 2) | 0

        sizesmall[0] = sizesmall[1] = sizesmall[2] = MagicInts[smallidx]
        // larger = MagicInts[maxidx]

        let adz = Math.ceil(dv.getInt32(offset) / 4) * 4
        offset += 4
        // if (tmpIdx == 0) {alert("Undocumented error 2"); return;}

        // buf = new Int32Array(bin, offset);
        // buf8 = new Uint8Array(bin, offset);

        // tmpIdx += 3; rndup = tmpIdx%4;
        // for (i=tmpIdx+rndup-1; i>=tmpIdx; i--) buf8[i] = 0;

        // now unpack buf2...

        const invPrecision = 1.0 / precision
        let run = 0
        let i = 0

        const buf8 = new Uint8Array(bin, offset)  // 229...

        thiscoord[0] = thiscoord[1] = thiscoord[2] = 0

        while (i < lsize) {
          if (bitsize === 0) {
            thiscoord[0] = decodeBits(buf, buf8, bitsizeint[0], buf2)
            thiscoord[1] = decodeBits(buf, buf8, bitsizeint[1], buf2)
            thiscoord[2] = decodeBits(buf, buf8, bitsizeint[2], buf2)
          } else {
            decodeInts(buf, buf8, 3, bitsize, sizeint, thiscoord, buf2)
          }

          i++

          thiscoord[0] += minMaxInt[0]
          thiscoord[1] += minMaxInt[1]
          thiscoord[2] += minMaxInt[2]

          prevcoord[0] = thiscoord[0]
          prevcoord[1] = thiscoord[1]
          prevcoord[2] = thiscoord[2]

          const flag = decodeBits(buf, buf8, 1, buf2)
          let isSmaller = 0

          if (flag === 1) {
            run = decodeBits(buf, buf8, 5, buf2)
            isSmaller = run % 3
            run -= isSmaller
            isSmaller--
          }

          // if ((lfp-ptrstart)+run > size3){
          //   fprintf(stderr, "(xdrfile error) Buffer overrun during decompression.\n");
          //   return 0;
          // }

          if (run > 0) {
            thiscoord[0] = thiscoord[1] = thiscoord[2] = 0

            for (let k = 0; k < run; k += 3) {
              decodeInts(buf, buf8, 3, smallidx, sizesmall, thiscoord, buf2)
              i++

              thiscoord[0] += prevcoord[0] - smallnum
              thiscoord[1] += prevcoord[1] - smallnum
              thiscoord[2] += prevcoord[2] - smallnum

              if (k === 0) {
                // interchange first with second atom for
                // better compression of water molecules
                let tmpSwap = thiscoord[0]
                thiscoord[0] = prevcoord[0]
                prevcoord[0] = tmpSwap

                tmpSwap = thiscoord[1]
                thiscoord[1] = prevcoord[1]
                prevcoord[1] = tmpSwap

                tmpSwap = thiscoord[2]
                thiscoord[2] = prevcoord[2]
                prevcoord[2] = tmpSwap

                frameCoords[lfp++] = prevcoord[0] * invPrecision
                frameCoords[lfp++] = prevcoord[1] * invPrecision
                frameCoords[lfp++] = prevcoord[2] * invPrecision
              } else {
                prevcoord[0] = thiscoord[0]
                prevcoord[1] = thiscoord[1]
                prevcoord[2] = thiscoord[2]
              }
              frameCoords[lfp++] = thiscoord[0] * invPrecision
              frameCoords[lfp++] = thiscoord[1] * invPrecision
              frameCoords[lfp++] = thiscoord[2] * invPrecision
            }
          } else {
            frameCoords[lfp++] = thiscoord[0] * invPrecision
            frameCoords[lfp++] = thiscoord[1] * invPrecision
            frameCoords[lfp++] = thiscoord[2] * invPrecision
          }

          smallidx += isSmaller

          if (isSmaller < 0) {
            smallnum = smaller
            if (smallidx > FirstIdx) {
              smaller = (MagicInts[smallidx - 1] / 2) | 0
            } else {
              smaller = 0
            }
          } else if (isSmaller > 0) {
            smaller = smallnum
            smallnum = (MagicInts[smallidx] / 2) | 0
          }
          sizesmall[0] = sizesmall[1] = sizesmall[2] = MagicInts[smallidx]

          if (sizesmall[0] === 0 || sizesmall[1] === 0 || sizesmall[2] === 0) {
            console.error('(xdrfile error) Undefined error.')
            return
          }
        }
        offset += adz
      }

      for (let c = 0; c < natoms3; c++) {
        frameCoords[c] *= 10
      }

      coordinates.push(frameCoords)

      if (offset >= bin.byteLength) break
    }

    if (times.length >= 1) {
      f.timeOffset = times[0]
    }
    if (times.length >= 2) {
      f.deltaTime = times[1] - times[0]
    }

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

ParserRegistry.add('xtc', XtcParser)

export default XtcParser