UNPKG

molstar

Version:

A comprehensive macromolecular library.

174 lines (173 loc) 7.77 kB
/** * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { OrderedSet } from '../../../mol-data/int'; import { Mat4, Vec3 } from '../../../mol-math/linear-algebra'; import { Unit } from '../../../mol-model/structure'; import { VdwRadius } from '../../../mol-model/structure/model/properties/atomic'; import { StructureLookup3DResultContext } from '../../../mol-model/structure/structure/util/lookup3d'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { altLoc, connectedTo, typeSymbol } from '../chemistry/util'; import { Features } from './features'; export const ContactsParams = { lineOfSightDistFactor: PD.Numeric(1.0, { min: 0, max: 3, step: 0.1 }), }; const MAX_LINE_OF_SIGHT_DISTANCE = 3; function validPair(structure, infoA, infoB) { const indexA = infoA.members[infoA.offsets[infoA.feature]]; const indexB = infoB.members[infoB.offsets[infoB.feature]]; if (indexA === indexB && infoA.unit === infoB.unit) return false; // no self interaction const altA = altLoc(infoA.unit, indexA); const altB = altLoc(infoB.unit, indexB); if (altA && altB && altA !== altB) return false; // incompatible alternate location id if (infoA.unit === infoB.unit && infoA.unit.model.atomicHierarchy.residueAtomSegments.count > 1 && infoA.unit.residueIndex[infoA.unit.elements[indexA]] === infoB.unit.residueIndex[infoB.unit.elements[indexB]]) return false; // same residue (and more than one residue) // e.g. no hbond if donor and acceptor are bonded if (connectedTo(structure, infoA.unit, indexA, infoB.unit, indexB)) return false; return true; } // function invalidAltLoc(unitA, indexA, unitB, indexB) { const altA = altLoc(unitA, indexA); const altB = altLoc(unitB, indexB); return altA && altB && altA !== altB; } function isMember(element, info) { const { feature, offsets, members } = info; for (let i = offsets[feature], il = offsets[feature + 1]; i < il; ++i) { if (members[i] === element) return true; } return false; } const tmpVec = Vec3(); const tmpVecA = Vec3(); const tmpVecB = Vec3(); // need to use a separate context for structure.lookup3d.find because of nested queries const lineOfSightLookupCtx = StructureLookup3DResultContext(); function checkLineOfSight(structure, infoA, infoB, distFactor) { const featureA = infoA.feature; const featureB = infoB.feature; const indexA = infoA.members[infoA.offsets[featureA]]; const indexB = infoB.members[infoB.offsets[featureB]]; Features.position(tmpVecA, infoA); Features.position(tmpVecB, infoB); Vec3.scale(tmpVec, Vec3.add(tmpVec, tmpVecA, tmpVecB), 0.5); const distMax = distFactor * MAX_LINE_OF_SIGHT_DISTANCE; const { count, indices, units, squaredDistances } = structure.lookup3d.find(tmpVec[0], tmpVec[1], tmpVec[2], distMax, lineOfSightLookupCtx); if (count === 0) return true; for (let r = 0; r < count; ++r) { const i = indices[r]; const unit = units[r]; if (!Unit.isAtomic(unit)) continue; const element = typeSymbol(unit, i); // allow hydrogens if (element === "H" /* Elements.H */) continue; const vdw = VdwRadius(element); // check distance if (vdw * vdw * distFactor * distFactor <= squaredDistances[r]) continue; // allow different altlocs if (invalidAltLoc(unit, i, infoA.unit, indexA) || invalidAltLoc(unit, i, infoB.unit, indexB)) continue; // allow member atoms if ((infoA.unit === unit && isMember(i, infoA)) || (infoB.unit === unit && isMember(i, infoB))) continue; unit.conformation.position(unit.elements[i], tmpVec); // allow atoms at the center of functional groups if (Vec3.squaredDistance(tmpVec, tmpVecA) < 1 || Vec3.squaredDistance(tmpVec, tmpVecB) < 1) continue; return false; } return true; } /** * Add all intra-unit contacts, i.e. pairs of features */ export function addUnitContacts(structure, unit, features, builder, testers, props) { for (const tester of testers) { _addUnitContacts(structure, unit, features, builder, tester, props); } } function _addUnitContacts(structure, unit, features, builder, tester, props) { const { x, y, z } = features; const { lookup3d, indices: subsetIndices } = features.subset(tester.requiredFeatures); const infoA = Features.Info(structure, unit, features); const infoB = { ...infoA }; const distFactor = props.lineOfSightDistFactor; for (let t = 0, tl = OrderedSet.size(subsetIndices); t < tl; ++t) { const i = OrderedSet.getAt(subsetIndices, t); const { count, indices, squaredDistances } = lookup3d.find(x[i], y[i], z[i], tester.maxDistance); if (count === 0) continue; infoA.feature = i; for (let r = 0; r < count; ++r) { const j = OrderedSet.getAt(subsetIndices, indices[r]); if (j <= i) continue; infoB.feature = j; if (!validPair(structure, infoA, infoB)) continue; const type = tester.getType(structure, infoA, infoB, squaredDistances[r]); if (type && checkLineOfSight(structure, infoA, infoB, distFactor)) { builder.add(i, j, type); } } } } const _imageTransform = Mat4(); /** * Add all inter-unit contacts, i.e. pairs of features */ export function addStructureContacts(structure, unitA, featuresA, unitB, featuresB, builder, testers, props) { const { count: countA, x: xA, y: yA, z: zA } = featuresA; const { lookup3d } = featuresB; // the lookup queries need to happen in the "unitB space". // that means imageA = inverseOperB(operA(i)) const imageTransform = Mat4.mul(_imageTransform, unitB.conformation.operator.inverse, unitA.conformation.operator.matrix); const isNotIdentity = !Mat4.isIdentity(imageTransform); const imageA = Vec3(); const maxDistance = Math.max(...testers.map(t => t.maxDistance)); const { center, radius } = lookup3d.boundary.sphere; const testDistanceSq = (radius + maxDistance) * (radius + maxDistance); const distFactor = props.lineOfSightDistFactor; const infoA = Features.Info(structure, unitA, featuresA); const infoB = Features.Info(structure, unitB, featuresB); builder.startUnitPair(unitA, unitB); for (let i = 0; i < countA; ++i) { Vec3.set(imageA, xA[i], yA[i], zA[i]); if (isNotIdentity) Vec3.transformMat4(imageA, imageA, imageTransform); if (Vec3.squaredDistance(imageA, center) > testDistanceSq) continue; const { indices, count, squaredDistances } = lookup3d.find(imageA[0], imageA[1], imageA[2], maxDistance); if (count === 0) continue; infoA.feature = i; for (let r = 0; r < count; ++r) { const j = indices[r]; infoB.feature = j; if (!validPair(structure, infoA, infoB)) continue; const distanceSq = squaredDistances[r]; for (const tester of testers) { if (distanceSq < tester.maxDistance * tester.maxDistance) { const type = tester.getType(structure, infoA, infoB, distanceSq); if (type && checkLineOfSight(structure, infoA, infoB, distFactor)) { builder.add(i, j, type); break; } } } } } builder.finishUnitPair(); }