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