packages/miew/src/chem/AutoBond.js

Summary

Maintainability
B
4 hrs
Test Coverage
import AtomPairs from './AtomPairs'
import Bond from './Bond'

const cProfileBondBuilder = false
const cEstBondsMultiplier = 4
const cSpaceCode = 32
const cBondTolerance = 0.45
const cVMDTolerance = 0.6
const cBondRadInJMOL = true
const cEpsilon = 0.001

/**
 * Get radius used for building bonds.
 *
 * @param {Atom} atom - Atom object.
 * @returns {number} special value for bonding radius for this atom
 */
function _getBondingRadius(atom) {
  const { element } = atom
  if (element) {
    return element.radiusBonding
  }
  throw new Error('_getBondingRadius: Logic error.')
}

function _isAtomEligible(atom) {
  // build for all non-hetatm and for hetatm without bonds
  return !atom.isHet() || (atom.bonds && atom.bonds.length === 0)
}

/**
 * Bond between atoms.
 *
 * @param {Complex} complex molecular complex

 * @exports AutoBond
 * @constructor
 */
class AutoBond {
  constructor(complex) {
    this._complex = complex
    this._maxRad = 1.8
    const bBox = this._complex.getDefaultBoundaries().boundingBox
    this._vBoxMin = bBox.min.clone()
    this._vBoxMax = bBox.max.clone()

    this._pairCollection = null
  }

  /**
   * Add existing pairs of connectors (from pdb file after its reading)
   * @returns {number} 0
   */
  _addExistingPairs() {
    const atoms = this._complex.getAtoms()
    const numAtoms = atoms.length
    let aInd = 0
    const collection = this._pairCollection

    for (; aInd < numAtoms; aInd++) {
      const { bonds } = atoms[aInd]
      const numBondsForAtom = bonds.length
      for (let bInd = 0; bInd < numBondsForAtom; bInd++) {
        const bond = bonds[bInd]
        const indTo = bond._left.index
        if (indTo === aInd) {
          collection.addPair(aInd, bond._right.index)
        }
      } // for (b) all bonds in atom
    } // for (a)
    return 0
  }

  _findPairs() {
    const vw = this._complex.getVoxelWorld()
    if (vw === null) {
      return
    }

    const atoms = this._complex._atoms
    const atomsNum = atoms.length
    const self = this

    let rA
    let isHydrogenA
    let posA
    let locationA
    let atomA

    const processAtom = function (atomB) {
      if (isHydrogenA && atomB.isHydrogen()) {
        return
      }

      const locationB = atomB.location
      if (
        locationA !== cSpaceCode &&
        locationB !== cSpaceCode &&
        locationA !== locationB
      ) {
        return
      }

      const dist2 = posA.distanceToSquared(atomB.position)
      const rB = atomB.element.radiusBonding
      const maxAcceptable = cBondRadInJMOL
        ? rA + rB + cBondTolerance
        : cVMDTolerance * (rA + rB)

      if (dist2 > maxAcceptable * maxAcceptable) {
        return
      }

      if (dist2 < cEpsilon) {
        return
      }

      self._pairCollection.addPair(atomA.index, atomB.index)
    }

    for (let i = 0; i < atomsNum; ++i) {
      atomA = atoms[i]
      if (!_isAtomEligible(atomA)) {
        continue
      }

      rA = atomA.element.radiusBonding
      isHydrogenA = atomA.isHydrogen()
      posA = atomA.position
      locationA = atomA.location

      vw.forEachAtomWithinRadius(
        posA,
        2 * this._maxRad + cBondTolerance,
        processAtom
      )
    }
  }

  _addPairs() {
    const atoms = this._complex._atoms

    for (let i = 0, k = 0; i < this._pairCollection.numPairs; i++, k += 4) {
      const iA = this._pairCollection.intBuffer[k]
      const iB = this._pairCollection.intBuffer[k + 1]
      this._addPair(atoms[iA], atoms[iB])
    }
  }

  _addPair(atomA, atomB) {
    const bondsA = atomA.bonds
    const indexA = atomA.index
    const indexB = atomB.index
    for (let j = 0, numBonds = bondsA.length; j < numBonds; ++j) {
      const bond = bondsA[j]
      if (bond._left.index === indexB || bond._right.index === indexB) {
        return
      }
    }
    const left = indexA < indexB ? atomA : atomB
    const right = indexA < indexB ? atomB : atomA
    const newBond = this._complex.addBond(
      left,
      right,
      0,
      Bond.BondType.UNKNOWN,
      false
    )
    bondsA.push(newBond)
    atomB.bonds.push(newBond)
  }

  build() {
    if (cProfileBondBuilder) {
      console.time('Bonds Builder')
    }
    this._buildInner()

    if (cProfileBondBuilder) {
      console.timeEnd('Bonds Builder')
    }
  }

  _buildInner() {
    const atoms = this._complex._atoms
    if (atoms.length < 2) {
      return
    }
    if (atoms[0].index < 0) {
      throw new Error('AutoBond: Atoms in complex were not indexed.')
    }

    this._calcBoundingBox()
    this._pairCollection = new AtomPairs(atoms.length * cEstBondsMultiplier)
    this._addExistingPairs()
    this._findPairs()
    this._addPairs()
  }

  _calcBoundingBox() {
    const atoms = this._complex._atoms
    const nAtoms = atoms.length
    let maxRad = _getBondingRadius(atoms[0])
    for (let i = 1; i < nAtoms; ++i) {
      maxRad = Math.max(maxRad, _getBondingRadius(atoms[i]))
    }
    this._vBoxMax.addScalar(maxRad)
    this._vBoxMin.addScalar(-maxRad)
    this._maxRad = maxRad * 1.2
  }

  destroy() {
    if (this._pairCollection) {
      this._pairCollection.destroy()
    }
  }
}

export default AutoBond