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

src/structure/validation.js

  1. /**
  2. * @file Validation
  3. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  4. * @private
  5. */
  6.  
  7. import { Vector3, Color } from '../../lib/three.es6.js'
  8.  
  9. import { Debug, Log } from '../globals.js'
  10. import { defaults } from '../utils.js'
  11. import { ClashPicker } from '../utils/picker.js'
  12. import { uniformArray3 } from '../math/array-utils.js'
  13. import { guessElement } from '../structure/structure-utils.js'
  14.  
  15. function getSele (a, atomname, useAltcode) {
  16. const icode = a.icode.value
  17. const chain = a.chain.value
  18. const altcode = a.altcode.value
  19. let sele = a.resnum.value
  20. if (icode.trim()) sele += '^' + icode
  21. if (chain.trim()) sele += ':' + chain
  22. if (atomname) sele += '.' + atomname
  23. if (useAltcode && altcode.trim()) sele += '%' + altcode
  24. sele += '/' + (parseInt(a.model.value) - 1)
  25. return sele
  26. }
  27.  
  28. function setBitDict (dict, key, bit) {
  29. if (dict[ key ] === undefined) {
  30. dict[ key ] = bit
  31. } else {
  32. dict[ key ] |= bit
  33. }
  34. }
  35.  
  36. function hasAttrValue (attr, value) {
  37. return attr !== undefined && attr.value === value
  38. }
  39.  
  40. function getAtomSele (ap) {
  41. const icode = ap.inscode
  42. const chain = ap.chainname
  43. const atomname = ap.atomname
  44. const altcode = ap.altloc
  45. let sele = ap.resno
  46. if (icode) sele += '^' + icode
  47. if (chain) sele += ':' + chain
  48. if (atomname) sele += '.' + atomname
  49. if (altcode) sele += '%' + altcode
  50. sele += '/' + ap.modelIndex
  51. return sele
  52. }
  53.  
  54. function getProblemCount (clashDict, g, ga) {
  55. let geoProblemCount = 0
  56.  
  57. const clashes = g.getElementsByTagName('clash')
  58. for (let j = 0, jl = clashes.length; j < jl; ++j) {
  59. if (clashDict[ clashes[ j ].attributes.cid.value ]) {
  60. geoProblemCount += 1
  61. break
  62. }
  63. }
  64.  
  65. const angleOutliers = g.getElementsByTagName('angle-outlier')
  66. if (angleOutliers.length > 0) {
  67. geoProblemCount += 1
  68. }
  69.  
  70. const bondOutliers = g.getElementsByTagName('bond-outlier')
  71. if (bondOutliers.length > 0) {
  72. geoProblemCount += 1
  73. }
  74.  
  75. const planeOutliers = g.getElementsByTagName('plane-outlier')
  76. if (planeOutliers.length > 0) {
  77. geoProblemCount += 1
  78. }
  79.  
  80. if (hasAttrValue(ga.rota, 'OUTLIER')) {
  81. geoProblemCount += 1
  82. }
  83.  
  84. if (hasAttrValue(ga.rama, 'OUTLIER')) {
  85. geoProblemCount += 1
  86. }
  87.  
  88. if (hasAttrValue(ga.RNApucker, 'outlier')) {
  89. geoProblemCount += 1
  90. }
  91.  
  92. return geoProblemCount
  93. }
  94.  
  95. class Validation {
  96. constructor (name, path) {
  97. this.name = name
  98. this.path = path
  99.  
  100. this.rsrzDict = {}
  101. this.rsccDict = {}
  102. this.clashDict = {}
  103. this.clashArray = []
  104. this.geoDict = {}
  105. this.geoAtomDict = {}
  106. this.atomDict = {}
  107. this.clashSele = 'NONE'
  108. }
  109.  
  110. get type () { return 'validation' }
  111.  
  112. fromXml (xml) {
  113. if (Debug) Log.time('Validation.fromXml')
  114.  
  115. const rsrzDict = this.rsrzDict
  116. const rsccDict = this.rsccDict
  117. const clashDict = this.clashDict
  118. const clashArray = this.clashArray
  119. const geoDict = this.geoDict
  120. const geoAtomDict = this.geoAtomDict
  121. const atomDict = this.atomDict
  122.  
  123. const groups = xml.getElementsByTagName('ModelledSubgroup')
  124.  
  125. const _clashDict = {}
  126. const clashList = []
  127.  
  128. if (Debug) Log.time('Validation.fromXml#clashDict')
  129.  
  130. for (let i = 0, il = groups.length; i < il; ++i) {
  131. const g = groups[ i ]
  132. const ga = g.attributes
  133.  
  134. const sele = getSele(ga)
  135. if (ga.rsrz !== undefined) {
  136. rsrzDict[ sele ] = parseFloat(ga.rsrz.value)
  137. }
  138. if (ga.rscc !== undefined) {
  139. rsccDict[ sele ] = parseFloat(ga.rscc.value)
  140. }
  141. ga.sele = sele
  142.  
  143. const clashes = g.getElementsByTagName('clash')
  144.  
  145. for (let j = 0, jl = clashes.length; j < jl; ++j) {
  146. const ca = clashes[ j ].attributes
  147. const atom = ca.atom.value
  148.  
  149. if (guessElement(atom) !== 'H') {
  150. const cid = ca.cid.value
  151. const atomSele = getSele(ga, atom, true)
  152. atomDict[ atomSele ] = true
  153.  
  154. if (_clashDict[ cid ] === undefined) {
  155. _clashDict[ cid ] = {
  156. sele1: atomSele,
  157. res1: sele
  158. }
  159. } else {
  160. const c = _clashDict[ cid ]
  161. if (c.res1 !== sele) {
  162. c.sele2 = atomSele
  163. c.res2 = sele
  164. clashList.push(c.res1, sele)
  165. clashDict[ cid ] = c
  166. clashArray.push(c)
  167. }
  168. }
  169. }
  170. }
  171. }
  172.  
  173. if (Debug) Log.timeEnd('Validation.fromXml#clashDict')
  174.  
  175. for (let i = 0, il = groups.length; i < il; ++i) {
  176. const g = groups[ i ]
  177. const ga = g.attributes
  178.  
  179. const sele = ga.sele
  180. const isPolymer = ga.seq.value !== '.'
  181.  
  182. if (isPolymer) {
  183. const geoProblemCount = getProblemCount(clashDict, g, ga)
  184. if (geoProblemCount > 0) {
  185. geoDict[ sele ] = geoProblemCount
  186. }
  187. } else {
  188. const clashes = g.getElementsByTagName('clash')
  189. const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier')
  190. const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier')
  191.  
  192. if (mogBondOutliers.length > 0 || mogAngleOutliers.length > 0 || clashes.length > 0) {
  193. const atomDict = {}
  194. geoAtomDict[ sele ] = atomDict
  195.  
  196. for (let j = 0, jl = clashes.length; j < jl; ++j) {
  197. const ca = clashes[ j ].attributes
  198. if (clashDict[ ca.cid.value ]) {
  199. setBitDict(atomDict, ca.atom.value, 1)
  200. }
  201. }
  202.  
  203. for (let j = 0, jl = mogBondOutliers.length; j < jl; ++j) {
  204. const mbo = mogBondOutliers[ j ].attributes
  205. mbo.atoms.value.split(',').forEach(function (atomname) {
  206. setBitDict(atomDict, atomname, 2)
  207. })
  208. }
  209.  
  210. for (let j = 0, jl = mogAngleOutliers.length; j < jl; ++j) {
  211. const mao = mogAngleOutliers[ j ].attributes
  212. mao.atoms.value.split(',').forEach(function (atomname) {
  213. setBitDict(atomDict, atomname, 4)
  214. })
  215. }
  216. }
  217. }
  218. }
  219.  
  220. this.clashSele = clashList.length ? clashList.join(' OR ') : 'NONE'
  221.  
  222. if (Debug) Log.timeEnd('Validation.fromXml')
  223. }
  224.  
  225. getClashData (params) {
  226. if (Debug) Log.time('Validation.getClashData')
  227.  
  228. const p = params || {}
  229.  
  230. const s = p.structure
  231. const atomSet = s.atomSet
  232. const c = new Color(defaults(p.color, '#f0027f'))
  233.  
  234. const ap1 = s.getAtomProxy()
  235. const ap2 = s.getAtomProxy()
  236. const vDir = new Vector3()
  237. const vPos1 = new Vector3()
  238. const vPos2 = new Vector3()
  239.  
  240. const clashArray = this.clashArray
  241. const n = clashArray.length
  242.  
  243. const position1 = new Float32Array(n * 3)
  244. const position2 = new Float32Array(n * 3)
  245. const color = uniformArray3(n, c.r, c.g, c.b)
  246. const radius = new Float32Array(n)
  247. const picking = new Float32Array(n)
  248.  
  249. if (Debug) Log.time('Validation.getClashData#atomDict')
  250.  
  251. const atomDict = this.atomDict
  252.  
  253. s.eachAtom(function (ap) {
  254. const sele = getAtomSele(ap)
  255. if (atomDict[ sele ] === true) {
  256. atomDict[ sele ] = ap.index
  257. }
  258. })
  259.  
  260. if (Debug) Log.timeEnd('Validation.getClashData#atomDict')
  261.  
  262. let i = 0
  263.  
  264. clashArray.forEach(function (c, idx) {
  265. ap1.index = atomDict[ c.sele1 ]
  266. ap2.index = atomDict[ c.sele2 ]
  267.  
  268. if (ap1.index === undefined || ap2.index === undefined ||
  269. !atomSet.isSet(ap1.index, ap2.index)) return
  270.  
  271. vDir.subVectors(ap2, ap1).setLength(ap1.vdw)
  272. vPos1.copy(ap1).add(vDir)
  273.  
  274. vDir.subVectors(ap1, ap2).setLength(ap2.vdw)
  275. vPos2.copy(ap2).add(vDir)
  276.  
  277. const dHalf = ap1.distanceTo(ap2) / 2
  278. const r1 = Math.sqrt(ap1.vdw * ap1.vdw - dHalf * dHalf)
  279. const r2 = Math.sqrt(ap2.vdw * ap2.vdw - dHalf * dHalf)
  280.  
  281. vPos1.toArray(position1, i * 3)
  282. vPos2.toArray(position2, i * 3)
  283. radius[ i ] = (r1 + r2) / 2
  284. picking[ i ] = idx
  285.  
  286. ++i
  287. })
  288.  
  289. if (Debug) Log.timeEnd('Validation.getClashData')
  290.  
  291. return {
  292. position1: position1.subarray(0, i * 3),
  293. position2: position2.subarray(0, i * 3),
  294. color: color.subarray(0, i * 3),
  295. color2: color.subarray(0, i * 3),
  296. radius: radius.subarray(0, i),
  297. picking: new ClashPicker(picking.subarray(0, i), this, s)
  298. }
  299. }
  300. }
  301.  
  302. export default Validation