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

src/structure/validation.js

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

import { Vector3, Color } from '../../lib/three.es6.js'

import { Debug, Log } from '../globals.js'
import { defaults } from '../utils.js'
import { ClashPicker } from '../utils/picker.js'
import { uniformArray3 } from '../math/array-utils.js'
import { guessElement } from '../structure/structure-utils.js'

function getSele (a, atomname, useAltcode) {
  const icode = a.icode.value
  const chain = a.chain.value
  const altcode = a.altcode.value
  let sele = a.resnum.value
  if (icode.trim()) sele += '^' + icode
  if (chain.trim()) sele += ':' + chain
  if (atomname) sele += '.' + atomname
  if (useAltcode && altcode.trim()) sele += '%' + altcode
  sele += '/' + (parseInt(a.model.value) - 1)
  return sele
}

function setBitDict (dict, key, bit) {
  if (dict[ key ] === undefined) {
    dict[ key ] = bit
  } else {
    dict[ key ] |= bit
  }
}

function hasAttrValue (attr, value) {
  return attr !== undefined && attr.value === value
}

function getAtomSele (ap) {
  const icode = ap.inscode
  const chain = ap.chainname
  const atomname = ap.atomname
  const altcode = ap.altloc
  let sele = ap.resno
  if (icode) sele += '^' + icode
  if (chain) sele += ':' + chain
  if (atomname) sele += '.' + atomname
  if (altcode) sele += '%' + altcode
  sele += '/' + ap.modelIndex
  return sele
}

function getProblemCount (clashDict, g, ga) {
  let geoProblemCount = 0

  const clashes = g.getElementsByTagName('clash')
  for (let j = 0, jl = clashes.length; j < jl; ++j) {
    if (clashDict[ clashes[ j ].attributes.cid.value ]) {
      geoProblemCount += 1
      break
    }
  }

  const angleOutliers = g.getElementsByTagName('angle-outlier')
  if (angleOutliers.length > 0) {
    geoProblemCount += 1
  }

  const bondOutliers = g.getElementsByTagName('bond-outlier')
  if (bondOutliers.length > 0) {
    geoProblemCount += 1
  }

  const planeOutliers = g.getElementsByTagName('plane-outlier')
  if (planeOutliers.length > 0) {
    geoProblemCount += 1
  }

  if (hasAttrValue(ga.rota, 'OUTLIER')) {
    geoProblemCount += 1
  }

  if (hasAttrValue(ga.rama, 'OUTLIER')) {
    geoProblemCount += 1
  }

  if (hasAttrValue(ga.RNApucker, 'outlier')) {
    geoProblemCount += 1
  }

  return geoProblemCount
}

class Validation {
  constructor (name, path) {
    this.name = name
    this.path = path

    this.rsrzDict = {}
    this.rsccDict = {}
    this.clashDict = {}
    this.clashArray = []
    this.geoDict = {}
    this.geoAtomDict = {}
    this.atomDict = {}
    this.clashSele = 'NONE'
  }

  get type () { return 'validation' }

  fromXml (xml) {
    if (Debug) Log.time('Validation.fromXml')

    const rsrzDict = this.rsrzDict
    const rsccDict = this.rsccDict
    const clashDict = this.clashDict
    const clashArray = this.clashArray
    const geoDict = this.geoDict
    const geoAtomDict = this.geoAtomDict
    const atomDict = this.atomDict

    const groups = xml.getElementsByTagName('ModelledSubgroup')

    const _clashDict = {}
    const clashList = []

    if (Debug) Log.time('Validation.fromXml#clashDict')

    for (let i = 0, il = groups.length; i < il; ++i) {
      const g = groups[ i ]
      const ga = g.attributes

      const sele = getSele(ga)
      if (ga.rsrz !== undefined) {
        rsrzDict[ sele ] = parseFloat(ga.rsrz.value)
      }
      if (ga.rscc !== undefined) {
        rsccDict[ sele ] = parseFloat(ga.rscc.value)
      }
      ga.sele = sele

      const clashes = g.getElementsByTagName('clash')

      for (let j = 0, jl = clashes.length; j < jl; ++j) {
        const ca = clashes[ j ].attributes
        const atom = ca.atom.value

        if (guessElement(atom) !== 'H') {
          const cid = ca.cid.value
          const atomSele = getSele(ga, atom, true)
          atomDict[ atomSele ] = true

          if (_clashDict[ cid ] === undefined) {
            _clashDict[ cid ] = {
              sele1: atomSele,
              res1: sele
            }
          } else {
            const c = _clashDict[ cid ]
            if (c.res1 !== sele) {
              c.sele2 = atomSele
              c.res2 = sele
              clashList.push(c.res1, sele)
              clashDict[ cid ] = c
              clashArray.push(c)
            }
          }
        }
      }
    }

    if (Debug) Log.timeEnd('Validation.fromXml#clashDict')

    for (let i = 0, il = groups.length; i < il; ++i) {
      const g = groups[ i ]
      const ga = g.attributes

      const sele = ga.sele
      const isPolymer = ga.seq.value !== '.'

      if (isPolymer) {
        const geoProblemCount = getProblemCount(clashDict, g, ga)
        if (geoProblemCount > 0) {
          geoDict[ sele ] = geoProblemCount
        }
      } else {
        const clashes = g.getElementsByTagName('clash')
        const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier')
        const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier')

        if (mogBondOutliers.length > 0 || mogAngleOutliers.length > 0 || clashes.length > 0) {
          const atomDict = {}
          geoAtomDict[ sele ] = atomDict

          for (let j = 0, jl = clashes.length; j < jl; ++j) {
            const ca = clashes[ j ].attributes
            if (clashDict[ ca.cid.value ]) {
              setBitDict(atomDict, ca.atom.value, 1)
            }
          }

          for (let j = 0, jl = mogBondOutliers.length; j < jl; ++j) {
            const mbo = mogBondOutliers[ j ].attributes
            mbo.atoms.value.split(',').forEach(function (atomname) {
              setBitDict(atomDict, atomname, 2)
            })
          }

          for (let j = 0, jl = mogAngleOutliers.length; j < jl; ++j) {
            const mao = mogAngleOutliers[ j ].attributes
            mao.atoms.value.split(',').forEach(function (atomname) {
              setBitDict(atomDict, atomname, 4)
            })
          }
        }
      }
    }

    this.clashSele = clashList.length ? clashList.join(' OR ') : 'NONE'

    if (Debug) Log.timeEnd('Validation.fromXml')
  }

  getClashData (params) {
    if (Debug) Log.time('Validation.getClashData')

    const p = params || {}

    const s = p.structure
    const atomSet = s.atomSet
    const c = new Color(defaults(p.color, '#f0027f'))

    const ap1 = s.getAtomProxy()
    const ap2 = s.getAtomProxy()
    const vDir = new Vector3()
    const vPos1 = new Vector3()
    const vPos2 = new Vector3()

    const clashArray = this.clashArray
    const n = clashArray.length

    const position1 = new Float32Array(n * 3)
    const position2 = new Float32Array(n * 3)
    const color = uniformArray3(n, c.r, c.g, c.b)
    const radius = new Float32Array(n)
    const picking = new Float32Array(n)

    if (Debug) Log.time('Validation.getClashData#atomDict')

    const atomDict = this.atomDict

    s.eachAtom(function (ap) {
      const sele = getAtomSele(ap)
      if (atomDict[ sele ] === true) {
        atomDict[ sele ] = ap.index
      }
    })

    if (Debug) Log.timeEnd('Validation.getClashData#atomDict')

    let i = 0

    clashArray.forEach(function (c, idx) {
      ap1.index = atomDict[ c.sele1 ]
      ap2.index = atomDict[ c.sele2 ]

      if (ap1.index === undefined || ap2.index === undefined ||
                !atomSet.isSet(ap1.index, ap2.index)) return

      vDir.subVectors(ap2, ap1).setLength(ap1.vdw)
      vPos1.copy(ap1).add(vDir)

      vDir.subVectors(ap1, ap2).setLength(ap2.vdw)
      vPos2.copy(ap2).add(vDir)

      const dHalf = ap1.distanceTo(ap2) / 2
      const r1 = Math.sqrt(ap1.vdw * ap1.vdw - dHalf * dHalf)
      const r2 = Math.sqrt(ap2.vdw * ap2.vdw - dHalf * dHalf)

      vPos1.toArray(position1, i * 3)
      vPos2.toArray(position2, i * 3)
      radius[ i ] = (r1 + r2) / 2
      picking[ i ] = idx

      ++i
    })

    if (Debug) Log.timeEnd('Validation.getClashData')

    return {
      position1: position1.subarray(0, i * 3),
      position2: position2.subarray(0, i * 3),
      color: color.subarray(0, i * 3),
      color2: color.subarray(0, i * 3),
      radius: radius.subarray(0, i),
      picking: new ClashPicker(picking.subarray(0, i), this, s)
    }
  }
}

export default Validation