UNPKG

molstar

Version:

A comprehensive macromolecular library.

327 lines (326 loc) 15.9 kB
"use strict"; /** * 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) */ Object.defineProperty(exports, "__esModule", { value: true }); exports.CationPiProvider = exports.PiStackingProvider = exports.IonicProvider = exports.AromaticRingProvider = exports.PositiveChargeProvider = exports.NegativChargeProvider = void 0; const param_definition_1 = require("../../../mol-util/param-definition"); const features_1 = require("./features"); const types_1 = require("../../../mol-model/structure/model/types"); const util_1 = require("../chemistry/util"); const types_2 = require("../../../mol-model/structure/model/properties/atomic/types"); const valence_model_1 = require("../valence-model"); const misc_1 = require("../../../mol-math/misc"); const common_1 = require("./common"); const int_1 = require("../../../mol-data/int"); const functional_group_1 = require("../chemistry/functional-group"); const linear_algebra_1 = require("../../../mol-math/linear-algebra"); const IonicParams = { distanceMax: param_definition_1.ParamDefinition.Numeric(5.0, { min: 0, max: 8, step: 0.1 }), }; const PiStackingParams = { distanceMax: param_definition_1.ParamDefinition.Numeric(5.5, { min: 1, max: 8, step: 0.1 }), offsetMax: param_definition_1.ParamDefinition.Numeric(2.0, { min: 0, max: 4, step: 0.1 }), angleDevMax: param_definition_1.ParamDefinition.Numeric(30, { min: 0, max: 180, step: 1 }), }; const CationPiParams = { distanceMax: param_definition_1.ParamDefinition.Numeric(6.0, { min: 1, max: 8, step: 0.1 }), offsetMax: param_definition_1.ParamDefinition.Numeric(2.0, { min: 0, max: 4, step: 0.1 }), }; // const PositvelyCharged = ['ARG', 'HIS', 'LYS']; const NegativelyCharged = ['GLU', 'ASP']; function getUnitValenceModel(structure, unit) { const valenceModel = valence_model_1.ValenceModelProvider.get(structure).value; if (!valenceModel) throw Error('expected valence model to be available'); const unitValenceModel = valenceModel.get(unit.id); if (!unitValenceModel) throw Error('expected valence model for unit to be available'); return unitValenceModel; } function addUnitPositiveCharges(structure, unit, builder) { const { charge } = getUnitValenceModel(structure, unit); const { elements } = unit; const { x, y, z } = unit.model.atomicConformation; const addedElements = new Set(); const { label_comp_id } = unit.model.atomicHierarchy.atoms; const residueIt = int_1.Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (residueIt.hasNext) { const { index: residueIndex, start, end } = residueIt.move(); const compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]); if (PositvelyCharged.includes(compId)) { builder.startState(); for (let j = start; j < end; ++j) { if ((0, util_1.typeSymbol)(unit, j) === types_2.Elements.N && !types_1.ProteinBackboneAtoms.has((0, util_1.atomId)(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } } builder.finishState(common_1.FeatureType.PositiveCharge, common_1.FeatureGroup.None); } else if (!types_1.PolymerNames.has(compId)) { addedElements.clear(); for (let j = start; j < end; ++j) { let group = common_1.FeatureGroup.None; if ((0, functional_group_1.isGuanidine)(structure, unit, j)) { group = common_1.FeatureGroup.Guanidine; } else if ((0, functional_group_1.isAcetamidine)(structure, unit, j)) { group = common_1.FeatureGroup.Acetamidine; } if (group) { builder.startState(); (0, util_1.eachBondedAtom)(structure, unit, j, (_, k) => { if ((0, util_1.typeSymbol)(unit, k) === types_2.Elements.N) { addedElements.add(k); builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(common_1.FeatureType.PositiveCharge, group); } } for (let j = start; j < end; ++j) { if (charge[j] > 0 && !addedElements.has(j)) { builder.add(common_1.FeatureType.PositiveCharge, common_1.FeatureGroup.None, x[elements[j]], y[elements[j]], z[elements[j]], j); } } } } } function addUnitNegativeCharges(structure, unit, builder) { const { charge } = getUnitValenceModel(structure, unit); const { elements } = unit; const { x, y, z } = unit.model.atomicConformation; const addedElements = new Set(); const { label_comp_id } = unit.model.atomicHierarchy.atoms; const residueIt = int_1.Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, elements); while (residueIt.hasNext) { const { index: residueIndex, start, end } = residueIt.move(); const compId = label_comp_id.value(unit.model.atomicHierarchy.residueAtomSegments.offsets[residueIndex]); if (NegativelyCharged.includes(compId)) { builder.startState(); for (let j = start; j < end; ++j) { if ((0, util_1.typeSymbol)(unit, j) === types_2.Elements.O && !types_1.ProteinBackboneAtoms.has((0, util_1.atomId)(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } } builder.finishState(common_1.FeatureType.NegativeCharge, common_1.FeatureGroup.None); } else if (types_1.BaseNames.has(compId)) { for (let j = start; j < end; ++j) { if ((0, functional_group_1.isPhosphate)(structure, unit, j)) { builder.startState(); (0, util_1.eachBondedAtom)(structure, unit, j, (_, k) => { if ((0, util_1.typeSymbol)(unit, k) === types_2.Elements.O) { builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(common_1.FeatureType.NegativeCharge, common_1.FeatureGroup.Phosphate); } } } else if (!types_1.PolymerNames.has(compId)) { for (let j = start; j < end; ++j) { builder.startState(); if ((0, util_1.typeSymbol)(unit, j) === types_2.Elements.N && !types_1.ProteinBackboneAtoms.has((0, util_1.atomId)(unit, j))) { builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } builder.finishState(common_1.FeatureType.NegativeCharge, common_1.FeatureGroup.None); let group = common_1.FeatureGroup.None; if ((0, functional_group_1.isSulfonicAcid)(structure, unit, j)) { group = common_1.FeatureGroup.SulfonicAcid; } else if ((0, functional_group_1.isPhosphate)(structure, unit, j)) { group = common_1.FeatureGroup.Phosphate; } else if ((0, functional_group_1.isSulfate)(structure, unit, j)) { group = common_1.FeatureGroup.Sulfate; } else if ((0, functional_group_1.isCarboxylate)(structure, unit, j)) { group = common_1.FeatureGroup.Carboxylate; } if (group) { builder.startState(); (0, util_1.eachBondedAtom)(structure, unit, j, (_, k) => { if ((0, util_1.typeSymbol)(unit, k) === types_2.Elements.O) { addedElements.add(k); builder.pushMember(x[elements[k]], y[elements[k]], z[elements[k]], k); } }); builder.finishState(common_1.FeatureType.NegativeCharge, group); } } for (let j = start; j < end; ++j) { if (charge[j] < 0 && !addedElements.has(j)) { builder.add(common_1.FeatureType.NegativeCharge, common_1.FeatureGroup.None, x[elements[j]], y[elements[j]], z[elements[j]], j); } } } } } function addUnitAromaticRings(structure, unit, builder) { const { elements } = unit; const { x, y, z } = unit.model.atomicConformation; for (const ringIndex of unit.rings.aromaticRings) { const ring = unit.rings.all[ringIndex]; builder.startState(); for (let i = 0, il = ring.length; i < il; ++i) { const j = ring[i]; builder.pushMember(x[elements[j]], y[elements[j]], z[elements[j]], j); } builder.finishState(common_1.FeatureType.AromaticRing, common_1.FeatureGroup.None); } } function isIonic(ti, tj) { return ((ti === common_1.FeatureType.NegativeCharge && tj === common_1.FeatureType.PositiveCharge) || (ti === common_1.FeatureType.PositiveCharge && tj === common_1.FeatureType.NegativeCharge)); } function isPiStacking(ti, tj) { return ti === common_1.FeatureType.AromaticRing && tj === common_1.FeatureType.AromaticRing; } function isCationPi(ti, tj) { return ((ti === common_1.FeatureType.AromaticRing && tj === common_1.FeatureType.PositiveCharge) || (ti === common_1.FeatureType.PositiveCharge && tj === common_1.FeatureType.AromaticRing)); } const tmpPointA = (0, linear_algebra_1.Vec3)(); const tmpPointB = (0, linear_algebra_1.Vec3)(); function areFeaturesWithinDistanceSq(infoA, infoB, distanceSq) { const { feature: featureA, offsets: offsetsA, members: membersA } = infoA; const { feature: featureB, offsets: offsetsB, members: membersB } = infoB; for (let i = offsetsA[featureA], il = offsetsA[featureA + 1]; i < il; ++i) { const elementA = membersA[i]; infoA.unit.conformation.position(infoA.unit.elements[elementA], tmpPointA); for (let j = offsetsB[featureB], jl = offsetsB[featureB + 1]; j < jl; ++j) { const elementB = membersB[j]; infoB.unit.conformation.position(infoB.unit.elements[elementB], tmpPointB); if (linear_algebra_1.Vec3.squaredDistance(tmpPointA, tmpPointB) < distanceSq) return true; } } return false; } const tmpVecA = (0, linear_algebra_1.Vec3)(); const tmpVecB = (0, linear_algebra_1.Vec3)(); const tmpVecC = (0, linear_algebra_1.Vec3)(); const tmpVecD = (0, linear_algebra_1.Vec3)(); function getNormal(out, info) { const { unit, feature, offsets, members } = info; const { elements } = unit; const 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 linear_algebra_1.Vec3.triangleNormal(out, tmpVecA, tmpVecB, tmpVecC); } const getOffset = function (infoA, infoB, normal) { features_1.Features.position(tmpVecA, infoA); features_1.Features.position(tmpVecB, infoB); linear_algebra_1.Vec3.sub(tmpVecC, tmpVecA, tmpVecB); linear_algebra_1.Vec3.projectOnPlane(tmpVecD, tmpVecC, normal); linear_algebra_1.Vec3.add(tmpVecD, tmpVecD, tmpVecB); return linear_algebra_1.Vec3.distance(tmpVecD, tmpVecB); }; function getIonicOptions(props) { return { distanceMaxSq: props.distanceMax * props.distanceMax, }; } function getPiStackingOptions(props) { return { offsetMax: props.offsetMax, angleDevMax: (0, misc_1.degToRad)(props.angleDevMax), }; } function getCationPiOptions(props) { return { offsetMax: props.offsetMax }; } const deg180InRad = (0, misc_1.degToRad)(180); const deg90InRad = (0, misc_1.degToRad)(90); const tmpNormalA = (0, linear_algebra_1.Vec3)(); const tmpNormalB = (0, linear_algebra_1.Vec3)(); function testIonic(structure, infoA, infoB, distanceSq, opts) { const typeA = infoA.types[infoA.feature]; const typeB = infoB.types[infoB.feature]; if (isIonic(typeA, typeB)) { if (areFeaturesWithinDistanceSq(infoA, infoB, opts.distanceMaxSq)) { return common_1.InteractionType.Ionic; } } } function testPiStacking(structure, infoA, infoB, distanceSq, opts) { const typeA = infoA.types[infoA.feature]; const typeB = infoB.types[infoB.feature]; if (isPiStacking(typeA, typeB)) { getNormal(tmpNormalA, infoA); getNormal(tmpNormalB, infoB); const angle = linear_algebra_1.Vec3.angle(tmpNormalA, tmpNormalB); const offset = Math.min(getOffset(infoA, infoB, tmpNormalB), getOffset(infoB, infoA, tmpNormalA)); if (offset <= opts.offsetMax) { if (angle <= opts.angleDevMax || angle >= deg180InRad - opts.angleDevMax) { return common_1.InteractionType.PiStacking; // parallel } else if (angle <= opts.angleDevMax + deg90InRad && angle >= deg90InRad - opts.angleDevMax) { return common_1.InteractionType.PiStacking; // t-shaped } } } } function testCationPi(structure, infoA, infoB, distanceSq, opts) { const typeA = infoA.types[infoA.feature]; const typeB = infoB.types[infoB.feature]; if (isCationPi(typeA, typeB)) { const [infoR, infoC] = typeA === common_1.FeatureType.AromaticRing ? [infoA, infoB] : [infoB, infoA]; getNormal(tmpNormalA, infoR); const offset = getOffset(infoC, infoR, tmpNormalA); if (offset <= opts.offsetMax) { return common_1.InteractionType.CationPi; } } } // exports.NegativChargeProvider = features_1.Features.Provider([common_1.FeatureType.NegativeCharge], addUnitNegativeCharges); exports.PositiveChargeProvider = features_1.Features.Provider([common_1.FeatureType.PositiveCharge], addUnitPositiveCharges); exports.AromaticRingProvider = features_1.Features.Provider([common_1.FeatureType.AromaticRing], addUnitAromaticRings); exports.IonicProvider = { name: 'ionic', params: IonicParams, createTester: (props) => { const opts = getIonicOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([common_1.FeatureType.NegativeCharge, common_1.FeatureType.PositiveCharge]), getType: (structure, infoA, infoB, distanceSq) => testIonic(structure, infoA, infoB, distanceSq, opts) }; } }; exports.PiStackingProvider = { name: 'pi-stacking', params: PiStackingParams, createTester: (props) => { const opts = getPiStackingOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([common_1.FeatureType.AromaticRing]), getType: (structure, infoA, infoB, distanceSq) => testPiStacking(structure, infoA, infoB, distanceSq, opts) }; } }; exports.CationPiProvider = { name: 'cation-pi', params: CationPiParams, createTester: (props) => { const opts = getCationPiOptions(props); return { maxDistance: props.distanceMax, requiredFeatures: new Set([common_1.FeatureType.AromaticRing, common_1.FeatureType.PositiveCharge]), getType: (structure, infoA, infoB, distanceSq) => testCationPi(structure, infoA, infoB, distanceSq, opts) }; } };