UNPKG

molstar

Version:

A comprehensive macromolecular library.

323 lines 14.6 kB
/** * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> * @author Fred Ludlow <Fred.Ludlow@astx.com> * * based in part on NGL (https://github.com/arose/ngl) */ import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Features } from './features'; import { ProteinBackboneAtoms, PolymerNames, BaseNames } from '../../../mol-model/structure/model/types'; import { typeSymbol, atomId, eachBondedAtom } from '../chemistry/util'; import { ValenceModelProvider } from '../valence-model'; import { degToRad } from '../../../mol-math/misc'; import { Segmentation } from '../../../mol-data/int'; import { isGuanidine, isAcetamidine, isPhosphate, isSulfonicAcid, isSulfate, isCarboxylate } from '../chemistry/functional-group'; import { Vec3 } from '../../../mol-math/linear-algebra'; var IonicParams = { distanceMax: PD.Numeric(5.0, { min: 0, max: 8, step: 0.1 }), }; var PiStackingParams = { distanceMax: PD.Numeric(5.5, { min: 1, max: 8, step: 0.1 }), offsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }), angleDevMax: PD.Numeric(30, { min: 0, max: 180, step: 1 }), }; var CationPiParams = { distanceMax: PD.Numeric(6.0, { min: 1, max: 8, step: 0.1 }), offsetMax: PD.Numeric(2.0, { min: 0, max: 4, step: 0.1 }), }; // var PositvelyCharged = ['ARG', 'HIS', 'LYS']; var NegativelyCharged = ['GLU', 'ASP']; function getUnitValenceModel(structure, unit) { var valenceModel = ValenceModelProvider.get(structure).value; if (!valenceModel) throw Error('expected valence model to be available'); var unitValenceModel = valenceModel.get(unit.id); if (!unitValenceModel) throw Error('expected valence model for unit to be available'); return unitValenceModel; } function addUnitPositiveCharges(structure, unit, builder) { var charge = getUnitValenceModel(structure, unit).charge; var elements = unit.elements; var _a = unit.model.atomicConformation, x = _a.x, y = _a.y, z = _a.z; var addedElements = new Set(); var label_comp_id = unit.model.atomicHierarchy.atoms.label_comp_id; var residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (residueIt.hasNext) { var _b = residueIt.move(), residueIndex = _b.index, start = _b.start, end = _b.end; var compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]); if (PositvelyCharged.includes(compId)) { builder.startState(); for (var j = start; j < end; ++j) { if (typeSymbol(unit, j) === "N" /* N */ && !ProteinBackboneAtoms.has(atomId(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } } builder.finishState(1 /* PositiveCharge */, 0 /* None */); } else if (!PolymerNames.has(compId)) { addedElements.clear(); for (var j = start; j < end; ++j) { var group = 0 /* None */; if (isGuanidine(structure, unit, j)) { group = 8 /* Guanidine */; } else if (isAcetamidine(structure, unit, j)) { group = 9 /* Acetamidine */; } if (group) { builder.startState(); eachBondedAtom(structure, unit, j, function (_, k) { if (typeSymbol(unit, k) === "N" /* N */) { addedElements.add(k); builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(1 /* PositiveCharge */, group); } } for (var j = start; j < end; ++j) { if (charge[j] > 0 && !addedElements.has(j)) { builder.add(1 /* PositiveCharge */, 0 /* None */, x[elements[j]], y[elements[j]], z[elements[j]], j); } } } } } function addUnitNegativeCharges(structure, unit, builder) { var charge = getUnitValenceModel(structure, unit).charge; var elements = unit.elements; var _a = unit.model.atomicConformation, x = _a.x, y = _a.y, z = _a.z; var addedElements = new Set(); var label_comp_id = unit.model.atomicHierarchy.atoms.label_comp_id; var residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (residueIt.hasNext) { var _b = residueIt.move(), residueIndex = _b.index, start = _b.start, end = _b.end; var compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]); if (NegativelyCharged.includes(compId)) { builder.startState(); for (var j = start; j < end; ++j) { if (typeSymbol(unit, j) === "O" /* O */ && !ProteinBackboneAtoms.has(atomId(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } } builder.finishState(2 /* NegativeCharge */, 0 /* None */); } else if (BaseNames.has(compId)) { for (var j = start; j < end; ++j) { if (isPhosphate(structure, unit, j)) { builder.startState(); eachBondedAtom(structure, unit, j, function (_, k) { if (typeSymbol(unit, k) === "O" /* O */) { builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(2 /* NegativeCharge */, 6 /* Phosphate */); } } } else if (!PolymerNames.has(compId)) { for (var j = start; j < end; ++j) { builder.startState(); if (typeSymbol(unit, j) === "N" /* N */ && !ProteinBackboneAtoms.has(atomId(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } builder.finishState(2 /* NegativeCharge */, 0 /* None */); var group = 0 /* None */; if (isSulfonicAcid(structure, unit, j)) { group = 4 /* SulfonicAcid */; } else if (isPhosphate(structure, unit, j)) { group = 6 /* Phosphate */; } else if (isSulfate(structure, unit, j)) { group = 5 /* Sulfate */; } else if (isCarboxylate(structure, unit, j)) { group = 10 /* Carboxylate */; } if (group) { builder.startState(); eachBondedAtom(structure, unit, j, function (_, k) { if (typeSymbol(unit, k) === "O" /* O */) { addedElements.add(k); builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(2 /* NegativeCharge */, group); } } for (var j = start; j < end; ++j) { if (charge[j] < 0 && !addedElements.has(j)) { builder.add(2 /* NegativeCharge */, 0 /* None */, x[elements[j]], y[elements[j]], z[elements[j]], j); } } } } } function addUnitAromaticRings(structure, unit, builder) { var elements = unit.elements; var _a = unit.model.atomicConformation, x = _a.x, y = _a.y, z = _a.z; for (var _i = 0, _b = unit.rings.aromaticRings; _i < _b.length; _i++) { var ringIndex = _b[_i]; var ring = unit.rings.all[ringIndex]; builder.startState(); for (var i = 0, il = ring.length; i < il; ++i) { var j = ring[i]; builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } builder.finishState(3 /* AromaticRing */, 0 /* None */); } } function isIonic(ti, tj) { return ((ti === 2 /* NegativeCharge */ && tj === 1 /* PositiveCharge */) || (ti === 1 /* PositiveCharge */ && tj === 2 /* NegativeCharge */)); } function isPiStacking(ti, tj) { return ti === 3 /* AromaticRing */ && tj === 3 /* AromaticRing */; } function isCationPi(ti, tj) { return ((ti === 3 /* AromaticRing */ && tj === 1 /* PositiveCharge */) || (ti === 1 /* PositiveCharge */ && tj === 3 /* AromaticRing */)); } var tmpPointA = Vec3(); var tmpPointB = Vec3(); function areFeaturesWithinDistanceSq(infoA, infoB, distanceSq) { var featureA = infoA.feature, offsetsA = infoA.offsets, membersA = infoA.members; var featureB = infoB.feature, offsetsB = infoB.offsets, membersB = infoB.members; for (var i = offsetsA[featureA], il = offsetsA[featureA + 1]; i < il; ++i) { var elementA = membersA[i]; infoA.unit.conformation.position(infoA.unit.elements[elementA], tmpPointA); for (var j = offsetsB[featureB], jl = offsetsB[featureB + 1]; j < jl; ++j) { var elementB = membersB[j]; infoB.unit.conformation.position(infoB.unit.elements[elementB], tmpPointB); if (Vec3.squaredDistance(tmpPointA, tmpPointB) < distanceSq) return true; } } return false; } var tmpVecA = Vec3(); var tmpVecB = Vec3(); var tmpVecC = Vec3(); var tmpVecD = Vec3(); function getNormal(out, info) { var unit = info.unit, feature = info.feature, offsets = info.offsets, members = info.members; var elements = unit.elements; var i = offsets[feature]; info.unit.conformation.position(elements[members[i]], tmpVecA); info.unit.conformation.position(elements[members[i + 1]], tmpVecB); info.unit.conformation.position(elements[members[i + 2]], tmpVecC); return Vec3.triangleNormal(out, tmpVecA, tmpVecB, tmpVecC); } var getOffset = function (infoA, infoB, normal) { Features.position(tmpVecA, infoA); Features.position(tmpVecB, infoB); Vec3.sub(tmpVecC, tmpVecA, tmpVecB); Vec3.projectOnPlane(tmpVecD, tmpVecC, normal); Vec3.add(tmpVecD, tmpVecD, tmpVecB); return Vec3.distance(tmpVecD, tmpVecB); }; function getIonicOptions(props) { return { distanceMaxSq: props.distanceMax * props.distanceMax, }; } function getPiStackingOptions(props) { return { offsetMax: props.offsetMax, angleDevMax: degToRad(props.angleDevMax), }; } function getCationPiOptions(props) { return { offsetMax: props.offsetMax }; } var deg180InRad = degToRad(180); var deg90InRad = degToRad(90); var tmpNormalA = Vec3(); var tmpNormalB = Vec3(); function testIonic(structure, infoA, infoB, distanceSq, opts) { var typeA = infoA.types[infoA.feature]; var typeB = infoB.types[infoB.feature]; if (isIonic(typeA, typeB)) { if (areFeaturesWithinDistanceSq(infoA, infoB, opts.distanceMaxSq)) { return 1 /* Ionic */; } } } function testPiStacking(structure, infoA, infoB, distanceSq, opts) { var typeA = infoA.types[infoA.feature]; var typeB = infoB.types[infoB.feature]; if (isPiStacking(typeA, typeB)) { getNormal(tmpNormalA, infoA); getNormal(tmpNormalB, infoB); var angle = Vec3.angle(tmpNormalA, tmpNormalB); var offset = Math.min(getOffset(infoA, infoB, tmpNormalB), getOffset(infoB, infoA, tmpNormalA)); if (offset <= opts.offsetMax) { if (angle <= opts.angleDevMax || angle >= deg180InRad - opts.angleDevMax) { return 3 /* PiStacking */; // parallel } else if (angle <= opts.angleDevMax + deg90InRad && angle >= deg90InRad - opts.angleDevMax) { return 3 /* PiStacking */; // t-shaped } } } } function testCationPi(structure, infoA, infoB, distanceSq, opts) { var typeA = infoA.types[infoA.feature]; var typeB = infoB.types[infoB.feature]; if (isCationPi(typeA, typeB)) { var _a = typeA === 3 /* AromaticRing */ ? [infoA, infoB] : [infoB, infoA], infoR = _a[0], infoC = _a[1]; getNormal(tmpNormalA, infoR); var offset = getOffset(infoC, infoR, tmpNormalA); if (offset <= opts.offsetMax) { return 2 /* CationPi */; } } } // export var NegativChargeProvider = Features.Provider([2 /* NegativeCharge */], addUnitNegativeCharges); export var PositiveChargeProvider = Features.Provider([1 /* PositiveCharge */], addUnitPositiveCharges); export var AromaticRingProvider = Features.Provider([3 /* AromaticRing */], addUnitAromaticRings); export var IonicProvider = { name: 'ionic', params: IonicParams, createTester: function (props) { var opts = getIonicOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([2 /* NegativeCharge */, 1 /* PositiveCharge */]), getType: function (structure, infoA, infoB, distanceSq) { return testIonic(structure, infoA, infoB, distanceSq, opts); } }; } }; export var PiStackingProvider = { name: 'pi-stacking', params: PiStackingParams, createTester: function (props) { var opts = getPiStackingOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([3 /* AromaticRing */]), getType: function (structure, infoA, infoB, distanceSq) { return testPiStacking(structure, infoA, infoB, distanceSq, opts); } }; } }; export var CationPiProvider = { name: 'cation-pi', params: CationPiParams, createTester: function (props) { var opts = getCationPiOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([3 /* AromaticRing */, 1 /* PositiveCharge */]), getType: function (structure, infoA, infoB, distanceSq) { return testCationPi(structure, infoA, infoB, distanceSq, opts); } }; } }; //# sourceMappingURL=charged.js.map