molstar
Version:
A comprehensive macromolecular library.
283 lines (282 loc) • 14.2 kB
JavaScript
"use strict";
/**
* Copyright (c) 2019-2024 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>
* @author Paul Pillot <paul.pillot@tandemai.com>
*
* based in part on NGL (https://github.com/arose/ngl)
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.WeakHydrogenBondsProvider = exports.HydrogenBondsProvider = exports.HydrogenAcceptorProvider = exports.WeakHydrogenDonorProvider = exports.HydrogenDonorProvider = void 0;
const param_definition_1 = require("../../../mol-util/param-definition");
const geometry_1 = require("../chemistry/geometry");
const features_1 = require("./features");
const util_1 = require("../chemistry/util");
const types_1 = 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 types_2 = require("../../../mol-model/structure/model/types");
const GeometryParams = {
distanceMax: param_definition_1.ParamDefinition.Numeric(3.5, { min: 1, max: 5, step: 0.1 }),
backbone: param_definition_1.ParamDefinition.Boolean(true, { description: 'Include backbone-to-backbone hydrogen bonds' }),
accAngleDevMax: param_definition_1.ParamDefinition.Numeric(45, { min: 0, max: 180, step: 1 }, { description: 'Max deviation from ideal acceptor angle' }),
ignoreHydrogens: param_definition_1.ParamDefinition.Boolean(false, { description: 'Ignore explicit hydrogens in geometric constraints' }),
donAngleDevMax: param_definition_1.ParamDefinition.Numeric(45, { min: 0, max: 180, step: 1 }, { description: 'Max deviation from ideal donor angle' }),
accOutOfPlaneAngleMax: param_definition_1.ParamDefinition.Numeric(90, { min: 0, max: 180, step: 1 }),
donOutOfPlaneAngleMax: param_definition_1.ParamDefinition.Numeric(45, { min: 0, max: 180, step: 1 }),
};
const HydrogenBondsParams = {
...GeometryParams,
water: param_definition_1.ParamDefinition.Boolean(false, { description: 'Include water-to-water hydrogen bonds' }),
sulfurDistanceMax: param_definition_1.ParamDefinition.Numeric(4.1, { min: 1, max: 5, step: 0.1 }),
};
const WeakHydrogenBondsParams = {
...GeometryParams,
};
//
// Geometric characteristics of hydrogen bonds involving sulfur atoms in proteins
// https://doi.org/10.1002/prot.22327
// Satisfying Hydrogen Bonding Potential in Proteins (HBPLUS)
// https://doi.org/10.1006/jmbi.1994.1334
// http://www.csb.yale.edu/userguides/datamanip/hbplus/hbplus_descrip.html
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;
}
/**
* Potential hydrogen donor
*/
function addUnitHydrogenDonors(structure, unit, builder) {
const { totalH } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
for (let i = 0, il = elements.length; i < il; ++i) {
const element = (0, util_1.typeSymbol)(unit, i);
if ((
// include both nitrogen atoms in histidine due to
// their often ambiguous protonation assignment
isHistidineNitrogen(unit, i)) || (totalH[i] > 0 &&
(element === types_1.Elements.N || element === types_1.Elements.O || element === types_1.Elements.S))) {
builder.add(common_1.FeatureType.HydrogenDonor, common_1.FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
}
}
}
/**
* Weak hydrogen donor.
*/
function addUnitWeakHydrogenDonors(structure, unit, builder) {
const { totalH } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
for (let i = 0, il = elements.length; i < il; ++i) {
if ((0, util_1.typeSymbol)(unit, i) === types_1.Elements.C &&
totalH[i] > 0 &&
((0, util_1.bondToElementCount)(structure, unit, i, types_1.Elements.N) > 0 ||
(0, util_1.bondToElementCount)(structure, unit, i, types_1.Elements.O) > 0 ||
inAromaticRingWithElectronNegativeElement(unit, i))) {
builder.add(common_1.FeatureType.WeakHydrogenDonor, common_1.FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
}
}
}
function inAromaticRingWithElectronNegativeElement(unit, index) {
const { elementAromaticRingIndices, all } = unit.rings;
const ringIndices = elementAromaticRingIndices.get(index);
if (ringIndices === undefined)
return false;
for (let i = 0, il = ringIndices.length; i < il; ++i) {
const ring = all[ringIndices[i]];
for (let j = 0, jl = ring.length; j < jl; ++j) {
const element = (0, util_1.typeSymbol)(unit, ring[j]);
if (element === types_1.Elements.N || element === types_1.Elements.O) {
return true;
}
}
}
return false;
}
/**
* Potential hydrogen acceptor
*/
function addUnitHydrogenAcceptors(structure, unit, builder) {
const { charge, implicitH, idealGeometry } = getUnitValenceModel(structure, unit);
const { elements } = unit;
const { x, y, z } = unit.model.atomicConformation;
const add = (i) => {
builder.add(common_1.FeatureType.HydrogenAcceptor, common_1.FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
};
for (let i = 0, il = elements.length; i < il; ++i) {
const element = (0, util_1.typeSymbol)(unit, i);
if (element === types_1.Elements.O) {
// Basically assume all oxygen atoms are acceptors!
add(i);
}
else if (element === types_1.Elements.N) {
if (isHistidineNitrogen(unit, i)) {
// include both nitrogen atoms in histidine due to
// their often ambiguous protonation assignment
add(i);
}
else if (charge[i] < 1) {
// Neutral nitrogen might be an acceptor
// It must have at least one lone pair not conjugated
const totalBonds = (0, util_1.bondCount)(structure, unit, i) + implicitH[i];
const ig = idealGeometry[i];
if ((ig === geometry_1.AtomGeometry.Tetrahedral && totalBonds < 4) ||
(ig === geometry_1.AtomGeometry.Trigonal && totalBonds < 3) ||
(ig === geometry_1.AtomGeometry.Linear && totalBonds < 2)) {
add(i);
}
}
}
else if (element === types_1.Elements.S) {
const resname = (0, util_1.compId)(unit, i);
if (resname === 'CYS' || resname === 'MET' || (0, util_1.formalCharge)(unit, i) === -1) {
add(i);
}
}
}
}
function isWater(unit, index) {
return unit.model.atomicHierarchy.derived.residue.moleculeType[unit.residueIndex[unit.elements[index]]] === types_2.MoleculeType.Water;
}
function isBackbone(unit, index) {
return types_2.ProteinBackboneAtoms.has((0, util_1.atomId)(unit, index));
}
function isRing(unit, index) {
return unit.rings.elementRingIndices.has(index);
}
function isHistidineNitrogen(unit, index) {
return (0, util_1.compId)(unit, index) === 'HIS' && (0, util_1.typeSymbol)(unit, index) === types_1.Elements.N && isRing(unit, index);
}
function isBackboneHydrogenBond(unitA, indexA, unitB, indexB) {
return isBackbone(unitA, indexA) && isBackbone(unitB, indexB);
}
function isWaterHydrogenBond(unitA, indexA, unitB, indexB) {
return isWater(unitA, indexA) && isWater(unitB, indexB);
}
function isHydrogenBond(ti, tj) {
return ((ti === common_1.FeatureType.HydrogenAcceptor && tj === common_1.FeatureType.HydrogenDonor) ||
(ti === common_1.FeatureType.HydrogenDonor && tj === common_1.FeatureType.HydrogenAcceptor));
}
function isWeakHydrogenBond(ti, tj) {
return ((ti === common_1.FeatureType.WeakHydrogenDonor && tj === common_1.FeatureType.HydrogenAcceptor) ||
(ti === common_1.FeatureType.HydrogenAcceptor && tj === common_1.FeatureType.WeakHydrogenDonor));
}
function getGeometryOptions(props) {
return {
ignoreHydrogens: props.ignoreHydrogens,
includeBackbone: props.backbone,
maxAccAngleDev: (0, misc_1.degToRad)(props.accAngleDevMax),
maxDonAngleDev: (0, misc_1.degToRad)(props.donAngleDevMax),
maxAccOutOfPlaneAngle: (0, misc_1.degToRad)(props.accOutOfPlaneAngleMax),
maxDonOutOfPlaneAngle: (0, misc_1.degToRad)(props.donOutOfPlaneAngleMax),
};
}
function getHydrogenBondsOptions(props) {
return {
...getGeometryOptions(props),
includeWater: props.water,
maxSulfurDistSq: props.sulfurDistanceMax * props.sulfurDistanceMax,
maxDistSq: props.distanceMax * props.distanceMax
};
}
const deg120InRad = (0, misc_1.degToRad)(120);
function checkGeometry(structure, don, acc, opts) {
const donIndex = don.members[don.offsets[don.feature]];
const accIndex = acc.members[acc.offsets[acc.feature]];
if (!opts.includeBackbone && isBackboneHydrogenBond(don.unit, donIndex, acc.unit, accIndex))
return;
const [donAngles, donHAngles] = (0, geometry_1.calcAngles)(structure, don.unit, donIndex, acc.unit, accIndex, opts.ignoreHydrogens);
const idealDonAngle = geometry_1.AtomGeometryAngles.get(don.idealGeometry[donIndex]) || deg120InRad;
if (donAngles.some(donAngle => Math.abs(idealDonAngle - donAngle) > opts.maxDonAngleDev))
return;
if (donHAngles.length && !donHAngles.some(donHAngles => donHAngles < opts.maxDonAngleDev))
return;
if (don.idealGeometry[donIndex] === geometry_1.AtomGeometry.Trigonal) {
const outOfPlane = (0, geometry_1.calcPlaneAngle)(structure, don.unit, donIndex, acc.unit, accIndex);
if (outOfPlane !== undefined && outOfPlane > opts.maxDonOutOfPlaneAngle)
return;
}
let donorIndex = donIndex;
if (!opts.ignoreHydrogens && donHAngles.length > 0) {
donorIndex = (0, geometry_1.closestHydrogenIndex)(structure, don.unit, donIndex, acc.unit, accIndex);
}
const [accAngles, accHAngles] = (0, geometry_1.calcAngles)(structure, acc.unit, accIndex, don.unit, donorIndex, opts.ignoreHydrogens);
const idealAccAngle = geometry_1.AtomGeometryAngles.get(acc.idealGeometry[accIndex]) || deg120InRad;
// Do not limit large acceptor angles
if (accAngles.some(accAngle => idealAccAngle - accAngle > opts.maxAccAngleDev))
return;
if (accHAngles.some(accHAngles => idealAccAngle - accHAngles > opts.maxAccAngleDev))
return;
if (acc.idealGeometry[accIndex] === geometry_1.AtomGeometry.Trigonal) {
const outOfPlane = (0, geometry_1.calcPlaneAngle)(structure, acc.unit, accIndex, don.unit, donIndex);
if (outOfPlane !== undefined && outOfPlane > opts.maxAccOutOfPlaneAngle)
return;
}
return true;
}
function testHydrogenBond(structure, infoA, infoB, distanceSq, opts) {
const typeA = infoA.types[infoA.feature];
const typeB = infoB.types[infoB.feature];
if (!isHydrogenBond(typeA, typeB))
return;
const [don, acc] = typeB === common_1.FeatureType.HydrogenAcceptor ? [infoA, infoB] : [infoB, infoA];
const donIndex = don.members[don.offsets[don.feature]];
const accIndex = acc.members[acc.offsets[acc.feature]];
// check if distance is ok depending on non-sulfur-containing hbond
const maxDistSq = (0, util_1.typeSymbol)(don.unit, donIndex) === types_1.Elements.S || (0, util_1.typeSymbol)(acc.unit, accIndex) === types_1.Elements.S ? opts.maxSulfurDistSq : opts.maxDistSq;
if (distanceSq > maxDistSq)
return;
if (!opts.includeWater && isWaterHydrogenBond(don.unit, donIndex, acc.unit, accIndex))
return;
if (!checkGeometry(structure, don, acc, opts))
return;
return common_1.InteractionType.HydrogenBond;
}
function testWeakHydrogenBond(structure, infoA, infoB, distanceSq, opts) {
const typeA = infoA.types[infoA.feature];
const typeB = infoB.types[infoB.feature];
if (!isWeakHydrogenBond(typeA, typeB))
return;
const [don, acc] = typeB === common_1.FeatureType.HydrogenAcceptor ? [infoA, infoB] : [infoB, infoA];
if (!checkGeometry(structure, don, acc, opts))
return;
return common_1.InteractionType.WeakHydrogenBond;
}
//
exports.HydrogenDonorProvider = features_1.Features.Provider([common_1.FeatureType.HydrogenDonor], addUnitHydrogenDonors);
exports.WeakHydrogenDonorProvider = features_1.Features.Provider([common_1.FeatureType.WeakHydrogenDonor], addUnitWeakHydrogenDonors);
exports.HydrogenAcceptorProvider = features_1.Features.Provider([common_1.FeatureType.HydrogenAcceptor], addUnitHydrogenAcceptors);
exports.HydrogenBondsProvider = {
name: 'hydrogen-bonds',
params: HydrogenBondsParams,
createTester: (props) => {
const maxDistance = Math.max(props.distanceMax, props.sulfurDistanceMax);
const opts = getHydrogenBondsOptions(props);
return {
maxDistance,
requiredFeatures: new Set([common_1.FeatureType.HydrogenDonor, common_1.FeatureType.HydrogenAcceptor]),
getType: (structure, infoA, infoB, distanceSq) => testHydrogenBond(structure, infoA, infoB, distanceSq, opts)
};
}
};
exports.WeakHydrogenBondsProvider = {
name: 'weak-hydrogen-bonds',
params: WeakHydrogenBondsParams,
createTester: (props) => {
const opts = getGeometryOptions(props);
return {
maxDistance: props.distanceMax,
requiredFeatures: new Set([common_1.FeatureType.WeakHydrogenDonor, common_1.FeatureType.HydrogenAcceptor]),
getType: (structure, infoA, infoB, distanceSq) => testWeakHydrogenBond(structure, infoA, infoB, distanceSq, opts)
};
}
};