validation-report.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. */
  6. import { ParamDefinition as PD } from '../../mol-util/param-definition'
  7. import { CustomPropertyDescriptor, Structure, Unit } from '../../mol-model/structure';
  8. import { CustomProperty } from '../common/custom-property';
  9. import { CustomModelProperty } from '../common/custom-model-property';
  10. import { Model, ElementIndex, ResidueIndex } from '../../mol-model/structure/model';
  11. import { IntAdjacencyGraph } from '../../mol-math/graph';
  12. import { readFromFile } from '../../mol-util/data-source';
  13. import { CustomStructureProperty } from '../common/custom-structure-property';
  14. import { InterUnitGraph } from '../../mol-math/graph/inter-unit-graph';
  15. import { UnitIndex } from '../../mol-model/structure/structure/element/element';
  16. import { IntMap, SortedArray } from '../../mol-data/int';
  17. import { arrayMax } from '../../mol-util/array';
  18. import { equalEps } from '../../mol-math/linear-algebra/3d/common';
  19. import { Vec3 } from '../../mol-math/linear-algebra';
  20. import { MmcifFormat } from '../../mol-model-formats/structure/mmcif';
  21. import { QuerySymbolRuntime } from '../../mol-script/runtime/query/compiler';
  22. import { CustomPropSymbol } from '../../mol-script/language/symbol';
  23. import Type from '../../mol-script/language/type';
  24. export { ValidationReport }
  25. interface ValidationReport {
  26. /**
  27. * Real Space R (RSRZ) for residues,
  28. * defined for polymer residues in X-ray structures
  29. */
  30. rsrz: Map<ResidueIndex, number>
  31. /**
  32. * Real Space Correlation Coefficient (RSCC) for residues,
  33. * defined for each non-polymer residue in X-ray structures
  34. */
  35. rscc: Map<ResidueIndex, number>
  36. /**
  37. * Random Coil Index (RCI) for residues,
  38. * defined for polymer residues in NMR structures
  39. */
  40. rci: Map<ResidueIndex, number>
  41. /**
  42. * Set of geometry issues for residues
  43. */
  44. geometryIssues: Map<ResidueIndex, Set<string>>
  45. /**
  46. * Set of bond outliers
  47. */
  48. bondOutliers: {
  49. index: Map<ElementIndex, number[]>
  50. data: {
  51. tag: string, atomA: ElementIndex, atomB: ElementIndex
  52. z: number, mean: number, obs: number, stdev: number
  53. }[]
  54. }
  55. /**
  56. * Set of angle outliers
  57. */
  58. angleOutliers: {
  59. index: Map<ElementIndex, number[]>
  60. data: {
  61. tag: string, atomA: ElementIndex, atomB: ElementIndex, atomC: ElementIndex,
  62. z: number, mean: number, obs: number, stdev: number
  63. }[]
  64. }
  65. /**
  66. * Clashes between atoms, including id, magniture and distance
  67. */
  68. clashes: IntAdjacencyGraph<ElementIndex, {
  69. readonly id: ArrayLike<number>
  70. readonly magnitude: ArrayLike<number>
  71. readonly distance: ArrayLike<number>
  72. }>
  73. }
  74. namespace ValidationReport {
  75. export enum Tag {
  76. DensityFit = 'rcsb-density-fit',
  77. GeometryQuality = 'rcsb-geometry-quality',
  78. RandomCoilIndex = 'rcsb-random-coil-index',
  79. Clashes = 'rcsb-clashes',
  80. }
  81. export const DefaultBaseUrl = '//ftp.rcsb.org/pub/pdb/validation_reports'
  82. export function getEntryUrl(pdbId: string, baseUrl: string) {
  83. const id = pdbId.toLowerCase()
  84. return `${baseUrl}/${id.substr(1, 2)}/${id}/${id}_validation.xml.gz`
  85. }
  86. export function isApplicable(model?: Model): boolean {
  87. return (
  88. !!model &&
  89. MmcifFormat.is(model.sourceData) &&
  90. (model.sourceData.data.db.database_2.database_id.isDefined ||
  91. model.entryId.length === 4)
  92. )
  93. }
  94. export function fromXml(xml: XMLDocument, model: Model): ValidationReport {
  95. return parseValidationReportXml(xml, model)
  96. }
  97. export async function fetch(ctx: CustomProperty.Context, model: Model, props: ServerSourceProps): Promise<ValidationReport> {
  98. const url = getEntryUrl(model.entryId, props.baseUrl)
  99. const xml = await ctx.fetch({ url, type: 'xml' }).runInContext(ctx.runtime)
  100. return fromXml(xml, model)
  101. }
  102. export async function open(ctx: CustomProperty.Context, model: Model, props: FileSourceProps): Promise<ValidationReport> {
  103. const xml = await readFromFile(props.input, 'xml').runInContext(ctx.runtime)
  104. return fromXml(xml, model)
  105. }
  106. export async function obtain(ctx: CustomProperty.Context, model: Model, props: ValidationReportProps): Promise<ValidationReport> {
  107. switch(props.source.name) {
  108. case 'file': return open(ctx, model, props.source.params)
  109. case 'server': return fetch(ctx, model, props.source.params)
  110. }
  111. }
  112. export const symbols = {
  113. hasClash: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.has-clash', Type.Bool),
  114. ctx => {
  115. const { unit, element } = ctx.element
  116. if (!Unit.isAtomic(unit)) return 0
  117. const validationReport = ValidationReportProvider.get(unit.model).value
  118. return validationReport && validationReport.clashes.getVertexEdgeCount(element) > 0
  119. }
  120. ),
  121. issueCount: QuerySymbolRuntime.Dynamic(CustomPropSymbol('rcsb', 'validation-report.issue-count', Type.Num),
  122. ctx => {
  123. const { unit, element } = ctx.element
  124. if (!Unit.isAtomic(unit)) return 0
  125. const validationReport = ValidationReportProvider.get(unit.model).value
  126. return validationReport?.geometryIssues.get(unit.residueIndex[element])?.size || 0
  127. }
  128. ),
  129. }
  130. }
  131. const FileSourceParams = {
  132. input: PD.File({ accept: '.xml,.gz,.zip' })
  133. }
  134. type FileSourceProps = PD.Values<typeof FileSourceParams>
  135. const ServerSourceParams = {
  136. baseUrl: PD.Text(ValidationReport.DefaultBaseUrl, { description: 'Base URL to directory tree' })
  137. }
  138. type ServerSourceProps = PD.Values<typeof ServerSourceParams>
  139. export const ValidationReportParams = {
  140. source: PD.MappedStatic('server', {
  141. 'file': PD.Group(FileSourceParams, { label: 'File', isFlat: true }),
  142. 'server': PD.Group(ServerSourceParams, { label: 'Server', isFlat: true }),
  143. }, { options: [['file', 'File'], ['server', 'Server']] })
  144. }
  145. export type ValidationReportParams = typeof ValidationReportParams
  146. export type ValidationReportProps = PD.Values<ValidationReportParams>
  147. export const ValidationReportProvider: CustomModelProperty.Provider<ValidationReportParams, ValidationReport> = CustomModelProperty.createProvider({
  148. label: 'RCSB Validation Report',
  149. descriptor: CustomPropertyDescriptor({
  150. name: 'rcsb_validation_report',
  151. symbols: ValidationReport.symbols
  152. }),
  153. type: 'dynamic',
  154. defaultParams: ValidationReportParams,
  155. getParams: (data: Model) => ValidationReportParams,
  156. isApplicable: (data: Model) => ValidationReport.isApplicable(data),
  157. obtain: async (ctx: CustomProperty.Context, data: Model, props: Partial<ValidationReportProps>) => {
  158. const p = { ...PD.getDefaultValues(ValidationReportParams), ...props }
  159. return await ValidationReport.obtain(ctx, data, p)
  160. }
  161. })
  162. //
  163. type IntraUnitClashesProps = {
  164. readonly id: ArrayLike<number>
  165. readonly magnitude: ArrayLike<number>
  166. readonly distance: ArrayLike<number>
  167. }
  168. type InterUnitClashesProps = {
  169. readonly id: number
  170. readonly magnitude: number
  171. readonly distance: number
  172. }
  173. export type IntraUnitClashes = IntAdjacencyGraph<UnitIndex, IntraUnitClashesProps>
  174. export type InterUnitClashes = InterUnitGraph<Unit.Atomic, UnitIndex, InterUnitClashesProps>
  175. export interface Clashes {
  176. readonly interUnit: InterUnitClashes
  177. readonly intraUnit: IntMap<IntraUnitClashes>
  178. }
  179. function createInterUnitClashes(structure: Structure, clashes: ValidationReport['clashes']) {
  180. const builder = new InterUnitGraph.Builder<Unit.Atomic, UnitIndex, InterUnitClashesProps>()
  181. const { a, b, edgeProps: { id, magnitude, distance } } = clashes
  182. const pA = Vec3()
  183. const pB = Vec3()
  184. Structure.eachUnitPair(structure, (unitA: Unit, unitB: Unit) => {
  185. const elementsA = unitA.elements
  186. const elementsB = unitB.elements
  187. builder.startUnitPair(unitA as Unit.Atomic, unitB as Unit.Atomic)
  188. for (let i = 0, il = clashes.edgeCount * 2; i < il; ++i) {
  189. // TODO create lookup
  190. let indexA = SortedArray.indexOf(elementsA, a[i])
  191. let indexB = SortedArray.indexOf(elementsB, b[i])
  192. if (indexA !== -1 && indexB !== -1) {
  193. unitA.conformation.position(a[i], pA)
  194. unitB.conformation.position(b[i], pB)
  195. // check actual distance to avoid clashes between unrelated chain instances
  196. if (equalEps(distance[i], Vec3.distance(pA, pB), 0.1)) {
  197. builder.add(indexA as UnitIndex, indexB as UnitIndex, {
  198. id: id[i],
  199. magnitude: magnitude[i],
  200. distance: distance[i],
  201. })
  202. }
  203. }
  204. }
  205. builder.finishUnitPair()
  206. }, {
  207. maxRadius: arrayMax(clashes.edgeProps.distance),
  208. validUnit: (unit: Unit) => Unit.isAtomic(unit),
  209. validUnitPair: (unitA: Unit, unitB: Unit) => unitA.model === unitB.model
  210. })
  211. return new InterUnitGraph(builder.getMap())
  212. }
  213. function createIntraUnitClashes(unit: Unit.Atomic, clashes: ValidationReport['clashes']): IntraUnitClashes {
  214. const aIndices: UnitIndex[] = []
  215. const bIndices: UnitIndex[] = []
  216. const ids: number[] = []
  217. const magnitudes: number[] = []
  218. const distances: number[] = []
  219. const { elements } = unit
  220. const { a, b, edgeCount, edgeProps } = clashes
  221. for (let i = 0, il = edgeCount * 2; i < il; ++i) {
  222. // TODO create lookup
  223. let indexA = SortedArray.indexOf(elements, a[i])
  224. let indexB = SortedArray.indexOf(elements, b[i])
  225. if (indexA !== -1 && indexB !== -1) {
  226. aIndices.push(indexA as UnitIndex)
  227. bIndices.push(indexB as UnitIndex)
  228. ids.push(edgeProps.id[i])
  229. magnitudes.push(edgeProps.magnitude[i])
  230. distances.push(edgeProps.distance[i])
  231. }
  232. }
  233. const builder = new IntAdjacencyGraph.EdgeBuilder(elements.length, aIndices, bIndices)
  234. const id = new Int32Array(builder.slotCount)
  235. const magnitude = new Float32Array(builder.slotCount)
  236. const distance = new Float32Array(builder.slotCount)
  237. for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
  238. builder.addNextEdge()
  239. builder.assignProperty(id, ids[i])
  240. builder.assignProperty(magnitude, magnitudes[i])
  241. builder.assignProperty(distance, distances[i])
  242. }
  243. return builder.createGraph({ id, magnitude, distance })
  244. }
  245. function createClashes(structure: Structure, clashes: ValidationReport['clashes']): Clashes {
  246. const intraUnit = IntMap.Mutable<IntraUnitClashes>()
  247. for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
  248. const group = structure.unitSymmetryGroups[i]
  249. if (!Unit.isAtomic(group.units[0])) continue
  250. const intraClashes = createIntraUnitClashes(group.units[0], clashes)
  251. for (let j = 0, jl = group.units.length; j < jl; ++j) {
  252. intraUnit.set(group.units[j].id, intraClashes)
  253. }
  254. }
  255. return {
  256. interUnit: createInterUnitClashes(structure, clashes),
  257. intraUnit
  258. }
  259. }
  260. export const ClashesProvider: CustomStructureProperty.Provider<{}, Clashes> = CustomStructureProperty.createProvider({
  261. label: 'Clashes',
  262. descriptor: CustomPropertyDescriptor({
  263. name: 'rcsb_clashes',
  264. // TODO `cifExport` and `symbol`
  265. }),
  266. type: 'local',
  267. defaultParams: {},
  268. getParams: (data: Structure) => ({}),
  269. isApplicable: (data: Structure) => true,
  270. obtain: async (ctx: CustomProperty.Context, data: Structure) => {
  271. await ValidationReportProvider.attach(ctx, data.models[0])
  272. const validationReport = ValidationReportProvider.get(data.models[0]).value!
  273. return createClashes(data, validationReport.clashes)
  274. }
  275. })
  276. //
  277. function getItem(a: NamedNodeMap, name: string) {
  278. const item = a.getNamedItem(name)
  279. return item !== null ? item.value : ''
  280. }
  281. function hasAttr(a: NamedNodeMap, name: string, value: string) {
  282. const item = a.getNamedItem(name)
  283. return item !== null && item.value === value
  284. }
  285. function getMogInfo(a: NamedNodeMap) {
  286. return {
  287. mean: parseFloat(getItem(a, 'mean')),
  288. obs: parseFloat(getItem(a, 'obsval')),
  289. stdev: parseFloat(getItem(a, 'stdev')),
  290. z: parseFloat(getItem(a, 'Zscore')),
  291. }
  292. }
  293. function getMolInfo(a: NamedNodeMap) {
  294. return {
  295. mean: parseFloat(getItem(a, 'mean')),
  296. obs: parseFloat(getItem(a, 'obs')),
  297. stdev: parseFloat(getItem(a, 'stdev')),
  298. z: parseInt(getItem(a, 'z')),
  299. }
  300. }
  301. function addIndex(index: number, element: ElementIndex, map: Map<ElementIndex, number[]>) {
  302. if (map.has(element)) map.get(element)!.push(index)
  303. else map.set(element, [index])
  304. }
  305. function ClashesBuilder(elementsCount: number) {
  306. const aIndices: ElementIndex[] = []
  307. const bIndices: ElementIndex[] = []
  308. const ids: number[] = []
  309. const magnitudes: number[] = []
  310. const distances: number[] = []
  311. const seen = new Map<string, ElementIndex>()
  312. return {
  313. add(element: ElementIndex, id: number, magnitude: number, distance: number, isSymop: boolean) {
  314. const hash = `${id}|${isSymop ? 's' : ''}`
  315. const other = seen.get(hash)
  316. if (other !== undefined) {
  317. aIndices[aIndices.length] = element
  318. bIndices[bIndices.length] = other
  319. ids[ids.length] = id
  320. magnitudes[magnitudes.length] = magnitude
  321. distances[distances.length] = distance
  322. } else {
  323. seen.set(hash, element)
  324. }
  325. },
  326. get() {
  327. const builder = new IntAdjacencyGraph.EdgeBuilder(elementsCount, aIndices, bIndices)
  328. const id = new Int32Array(builder.slotCount)
  329. const magnitude = new Float32Array(builder.slotCount)
  330. const distance = new Float32Array(builder.slotCount)
  331. for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
  332. builder.addNextEdge()
  333. builder.assignProperty(id, ids[i])
  334. builder.assignProperty(magnitude, magnitudes[i])
  335. builder.assignProperty(distance, distances[i])
  336. }
  337. return builder.createGraph({ id, magnitude, distance })
  338. }
  339. }
  340. }
  341. function parseValidationReportXml(xml: XMLDocument, model: Model): ValidationReport {
  342. const rsrz = new Map<ResidueIndex, number>()
  343. const rscc = new Map<ResidueIndex, number>()
  344. const rci = new Map<ResidueIndex, number>()
  345. const geometryIssues = new Map<ResidueIndex, Set<string>>()
  346. const bondOutliers = {
  347. index: new Map<ElementIndex, number[]>(),
  348. data: [] as ValidationReport['bondOutliers']['data']
  349. }
  350. const angleOutliers = {
  351. index: new Map<ElementIndex, number[]>(),
  352. data: [] as ValidationReport['angleOutliers']['data']
  353. }
  354. const clashesBuilder = ClashesBuilder(model.atomicHierarchy.atoms._rowCount)
  355. const { index } = model.atomicHierarchy
  356. const entries = xml.getElementsByTagName('Entry')
  357. if (entries.length === 1) {
  358. const chemicalShiftLists = entries[0].getElementsByTagName('chemical_shift_list')
  359. if (chemicalShiftLists.length === 1) {
  360. const randomCoilIndices = chemicalShiftLists[0].getElementsByTagName('random_coil_index')
  361. for (let j = 0, jl = randomCoilIndices.length; j < jl; ++j) {
  362. const { attributes } = randomCoilIndices[j]
  363. const value = parseFloat(getItem(attributes, 'value'))
  364. const auth_asym_id = getItem(attributes, 'chain')
  365. const auth_comp_id = getItem(attributes, 'rescode')
  366. const auth_seq_id = parseInt(getItem(attributes, 'resnum'))
  367. const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id })
  368. if (rI !== -1) rci.set(rI, value)
  369. }
  370. }
  371. }
  372. const groups = xml.getElementsByTagName('ModelledSubgroup')
  373. for (let i = 0, il = groups.length; i < il; ++i) {
  374. const g = groups[ i ]
  375. const ga = g.attributes
  376. const pdbx_PDB_model_num = parseInt(getItem(ga, 'model'))
  377. if (model.modelNum !== pdbx_PDB_model_num) continue
  378. const auth_asym_id = getItem(ga, 'chain')
  379. const auth_comp_id = getItem(ga, 'resname')
  380. const auth_seq_id = parseInt(getItem(ga, 'resnum'))
  381. const pdbx_PDB_ins_code = getItem(ga, 'icode').trim() || undefined
  382. const label_alt_id = getItem(ga, 'altcode').trim() || undefined
  383. const rI = index.findResidueAuth({ auth_asym_id, auth_comp_id, auth_seq_id, pdbx_PDB_ins_code })
  384. // continue if no residue index is found
  385. if (rI === -1) continue
  386. if (ga.getNamedItem('rsrz') !== null) rsrz.set(rI, parseFloat(getItem(ga, 'rsrz')))
  387. if (ga.getNamedItem('rscc') !== null) rscc.set(rI, parseFloat(getItem(ga, 'rscc')))
  388. const isPolymer = getItem(ga, 'seq') !== '.'
  389. const issues = new Set<string>()
  390. if (isPolymer) {
  391. const molBondOutliers = g.getElementsByTagName('bond-outlier')
  392. if (molBondOutliers.length) issues.add('bond-outlier')
  393. for (let j = 0, jl = molBondOutliers.length; j < jl; ++j) {
  394. const bo = molBondOutliers[j].attributes
  395. const idx = bondOutliers.data.length
  396. const atomA = index.findAtomOnResidue(rI, getItem(bo, 'atom0'))
  397. const atomB = index.findAtomOnResidue(rI, getItem(bo, 'atom1'))
  398. addIndex(idx, atomA, bondOutliers.index)
  399. addIndex(idx, atomB, bondOutliers.index)
  400. bondOutliers.data.push({
  401. tag: 'bond-outlier', atomA, atomB, ...getMolInfo(bo)
  402. })
  403. }
  404. const molAngleOutliers = g.getElementsByTagName('angle-outlier')
  405. if (molAngleOutliers.length) issues.add('angle-outlier')
  406. for (let j = 0, jl = molAngleOutliers.length; j < jl; ++j) {
  407. const ao = molAngleOutliers[j].attributes
  408. const idx = bondOutliers.data.length
  409. const atomA = index.findAtomOnResidue(rI, getItem(ao, 'atom0'))
  410. const atomB = index.findAtomOnResidue(rI, getItem(ao, 'atom1'))
  411. const atomC = index.findAtomOnResidue(rI, getItem(ao, 'atom2'))
  412. addIndex(idx, atomA, angleOutliers.index)
  413. addIndex(idx, atomB, angleOutliers.index)
  414. addIndex(idx, atomC, angleOutliers.index)
  415. angleOutliers.data.push({
  416. tag: 'angle-outlier', atomA, atomB, atomC, ...getMolInfo(ao)
  417. })
  418. }
  419. const planeOutliers = g.getElementsByTagName('plane-outlier')
  420. if (planeOutliers.length) issues.add('plane-outlier')
  421. if (hasAttr(ga, 'rota', 'OUTLIER')) issues.add('rotamer-outlier')
  422. if (hasAttr(ga, 'rama', 'OUTLIER')) issues.add('ramachandran-outlier')
  423. if (hasAttr(ga, 'RNApucker', 'outlier')) issues.add('RNApucker-outlier')
  424. } else {
  425. const mogBondOutliers = g.getElementsByTagName('mog-bond-outlier')
  426. if (mogBondOutliers.length) issues.add('mog-bond-outlier')
  427. for (let j = 0, jl = mogBondOutliers.length; j < jl; ++j) {
  428. const mbo = mogBondOutliers[j].attributes
  429. const atoms = getItem(mbo, 'atoms').split(',')
  430. const idx = bondOutliers.data.length
  431. const atomA = index.findAtomOnResidue(rI, atoms[0])
  432. const atomB = index.findAtomOnResidue(rI, atoms[1])
  433. addIndex(idx, atomA, bondOutliers.index)
  434. addIndex(idx, atomB, bondOutliers.index)
  435. bondOutliers.data.push({
  436. tag: 'mog-bond-outlier', atomA, atomB, ...getMogInfo(mbo)
  437. })
  438. }
  439. const mogAngleOutliers = g.getElementsByTagName('mog-angle-outlier')
  440. if (mogAngleOutliers.length) issues.add('mog-angle-outlier')
  441. for (let j = 0, jl = mogAngleOutliers.length; j < jl; ++j) {
  442. const mao = mogAngleOutliers[j].attributes
  443. const atoms = getItem(mao, 'atoms').split(',')
  444. const idx = angleOutliers.data.length
  445. const atomA = index.findAtomOnResidue(rI, atoms[0])
  446. const atomB = index.findAtomOnResidue(rI, atoms[1])
  447. const atomC = index.findAtomOnResidue(rI, atoms[2])
  448. addIndex(idx, atomA, angleOutliers.index)
  449. addIndex(idx, atomB, angleOutliers.index)
  450. addIndex(idx, atomC, angleOutliers.index)
  451. angleOutliers.data.push({
  452. tag: 'mog-angle-outlier', atomA, atomB, atomC, ...getMogInfo(mao)
  453. })
  454. }
  455. }
  456. const clashes = g.getElementsByTagName('clash')
  457. if (clashes.length) issues.add('clash')
  458. for (let j = 0, jl = clashes.length; j < jl; ++j) {
  459. const ca = clashes[j].attributes
  460. const id = parseInt(getItem(ca, 'cid'))
  461. const magnitude = parseFloat(getItem(ca, 'clashmag'))
  462. const distance = parseFloat(getItem(ca, 'dist'))
  463. const label_atom_id = getItem(ca, 'atom')
  464. const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
  465. if (element !== -1) {
  466. clashesBuilder.add(element, id, magnitude, distance, false)
  467. }
  468. }
  469. const symmClashes = g.getElementsByTagName('symm-clash')
  470. if (symmClashes.length) issues.add('symm-clash')
  471. for (let j = 0, jl = symmClashes.length; j < jl; ++j) {
  472. const sca = symmClashes[j].attributes
  473. const id = parseInt(getItem(sca, 'scid'))
  474. const magnitude = parseFloat(getItem(sca, 'clashmag'))
  475. const distance = parseFloat(getItem(sca, 'dist'))
  476. const label_atom_id = getItem(sca, 'atom')
  477. const element = index.findAtomOnResidue(rI, label_atom_id, label_alt_id)
  478. if (element !== -1) {
  479. clashesBuilder.add(element, id, magnitude, distance, true)
  480. }
  481. }
  482. geometryIssues.set(rI, issues)
  483. }
  484. const clashes = clashesBuilder.get()
  485. const validationReport = {
  486. rsrz, rscc, rci, geometryIssues,
  487. bondOutliers, angleOutliers,
  488. clashes
  489. }
  490. return validationReport
  491. }