molstar
Version:
A comprehensive macromolecular library.
323 lines • 14.6 kB
JavaScript
/**
* 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