UNPKG

molstar

Version:

A comprehensive macromolecular library.

320 lines (319 loc) 13.8 kB
/** * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Fred Ludlow <Fred.Ludlow@astx.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { __assign, __awaiter, __generator } from "tslib"; import { Unit, Bond } from '../../../mol-model/structure'; import { isMetal } from '../../../mol-model/structure/model/properties/atomic/types'; import { assignGeometry } from './geometry'; import { bondCount, typeSymbol, formalCharge, bondToElementCount } from './util'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { isDebugMode } from '../../../mol-util/debug'; import { SortedArray } from '../../../mol-data/int'; import { BondType } from '../../../mol-model/structure/model/types'; /** * TODO: * Ensure proper treatment of disorder/models. e.g. V257 N in 5vim * Formal charge of 255 for SO4 anion (e.g. 5ghl) * Have removed a lot of explicit features (as I think they're more * generally captured by better VM). * Could we instead have a "delocalised negative/positive" charge * feature and flag these up? * */ var tmpConjBondItA = new Bond.ElementBondIterator(); var tmpConjBondItB = new Bond.ElementBondIterator(); /** * Are we involved in some kind of pi system. Either explicitly forming * double bond or N, O next to a double bond, except: * * N,O with degree 4 cannot be conjugated. * N,O adjacent to P=O or S=O do not qualify (keeps sulfonamide N sp3 geom) */ function isConjugated(structure, unit, index) { var element = typeSymbol(unit, index); var hetero = element === "O" /* Elements.O */ || element === "N" /* Elements.N */; if (hetero && bondCount(structure, unit, index) === 4) return false; tmpConjBondItA.setElement(structure, unit, index); while (tmpConjBondItA.hasNext) { var bA = tmpConjBondItA.move(); if (bA.order > 1) return true; if (hetero) { var elementB = typeSymbol(bA.otherUnit, bA.otherIndex); tmpConjBondItB.setElement(structure, bA.otherUnit, bA.otherIndex); while (tmpConjBondItB.hasNext) { var bB = tmpConjBondItB.move(); if (bB.order > 1) { if ((elementB === "P" /* Elements.P */ || elementB === "S" /* Elements.S */) && typeSymbol(bB.otherUnit, bB.otherIndex) === "O" /* Elements.O */) { continue; } return true; } } } } return false; } export function explicitValence(structure, unit, index) { var v = 0; // intra-unit bonds var _a = unit.bonds, offset = _a.offset, _b = _a.edgeProps, flags = _b.flags, order = _b.order; for (var i = offset[index], il = offset[index + 1]; i < il; ++i) { if (BondType.isCovalent(flags[i])) v += order[i]; } // inter-unit bonds structure.interUnitBonds.getEdgeIndices(index, unit.id).forEach(function (i) { var b = structure.interUnitBonds.edges[i]; if (BondType.isCovalent(b.props.flag)) v += b.props.order; }); return v; } var tmpChargeBondItA = new Bond.ElementBondIterator(); var tmpChargeBondItB = new Bond.ElementBondIterator(); /** * Attempts to produce a consistent charge and implicit * H-count for an atom. * * If both props.assignCharge and props.assignH, this * approximately follows the rules described in * https://docs.eyesopen.com/toolkits/python/oechemtk/valence.html#openeye-hydrogen-count-model * * If only charge or hydrogens are to be assigned it takes * a much simpler view and deduces one from the other */ export function calculateHydrogensCharge(structure, unit, index, props) { var hydrogenCount = bondToElementCount(structure, unit, index, "H" /* Elements.H */); var element = typeSymbol(unit, index); var charge = formalCharge(unit, index); var assignCharge = (props.assignCharge === 'always' || (props.assignCharge === 'auto' && charge === 0)); var assignH = (props.assignH === 'always' || (props.assignH === 'auto' && hydrogenCount === 0)); var degree = bondCount(structure, unit, index); var valence = explicitValence(structure, unit, index); var conjugated = isConjugated(structure, unit, index); var multiBond = (valence - degree > 0); var implicitHCount = 0; var geom = 8 /* AtomGeometry.Unknown */; switch (element) { case "H" /* Elements.H */: if (assignCharge) { if (degree === 0) { charge = 1; geom = 0 /* AtomGeometry.Spherical */; } else if (degree === 1) { charge = 0; geom = 1 /* AtomGeometry.Terminal */; } } break; case "C" /* Elements.C */: // TODO: Isocyanide? if (assignCharge) { charge = 0; // Assume carbon always neutral } if (assignH) { // Carbocation/carbanion are 3-valent implicitHCount = Math.max(0, 4 - valence - Math.abs(charge)); } // Carbocation is planar, carbanion is tetrahedral geom = assignGeometry(degree + implicitHCount + Math.max(0, -charge)); break; case "N" /* Elements.N */: if (assignCharge) { if (!assignH) { // Trust input H explicitly: charge = valence - 3; } else if (conjugated && valence < 4) { // Neutral unless amidine/guanidine double-bonded N: if (degree - hydrogenCount === 1 && valence - hydrogenCount === 2) { charge = 1; } else { charge = 0; } } else { // Sulfonamide nitrogen and classed as sp3 in conjugation model but // they won't be charged // Don't assign charge to nitrogens bound to metals tmpChargeBondItA.setElement(structure, unit, index); while (tmpChargeBondItA.hasNext) { var b = tmpChargeBondItA.move(); var elementB = typeSymbol(b.otherUnit, b.otherIndex); if (elementB === "S" /* Elements.S */ || isMetal(elementB)) { charge = 0; break; } else { charge = 1; } } // TODO: Planarity sanity check? } } if (assignH) { // NH4+ -> 4, 1' amide -> 2, nitro N/N+ depiction -> 0 implicitHCount = Math.max(0, 3 - valence + charge); } if (conjugated && !multiBond) { // Amide, anilinic N etc. cannot consider lone-pair for geometry purposes // Anilinic N geometry is depenent on ring electronics, for our purposes we // assume it's trigonal! geom = assignGeometry(degree + implicitHCount - charge); } else { // Everything else, pyridine, amine, nitrile, lp plays normal role: geom = assignGeometry(degree + implicitHCount + 1 - charge); } break; case "O" /* Elements.O */: if (assignCharge) { if (!assignH) { charge = valence - 2; } if (valence === 1) { tmpChargeBondItA.setElement(structure, unit, index); b1: while (tmpChargeBondItA.hasNext) { var bA = tmpChargeBondItA.move(); tmpChargeBondItB.setElement(structure, bA.otherUnit, bA.otherIndex); while (tmpChargeBondItB.hasNext) { var bB = tmpChargeBondItB.move(); if (!(bB.otherUnit === unit && bB.otherIndex === index) && typeSymbol(bB.otherUnit, bB.otherIndex) === "O" /* Elements.O */ && bB.order === 2) { charge = -1; break b1; } } } } } if (assignH) { // ethanol -> 1, carboxylate -> -1 implicitHCount = Math.max(0, 2 - valence + charge); } if (conjugated && !multiBond) { // carboxylate OH, phenol OH, one lone-pair taken up with conjugation geom = assignGeometry(degree + implicitHCount - charge + 1); } else { // Carbonyl (trigonal) geom = assignGeometry(degree + implicitHCount - charge + 2); } break; // Only handles thiols/thiolates/thioether/sulfonium. Sulfoxides and higher // oxidiation states are assumed neutral S (charge carried on O if required) case "S" /* Elements.S */: if (assignCharge) { if (!assignH) { if (valence <= 3 && bondToElementCount(structure, unit, index, "O" /* Elements.O */) === 0) { charge = valence - 2; // e.g. explicitly deprotonated thiol } else { charge = 0; } } } if (assignH) { if (valence < 2) { implicitHCount = Math.max(0, 2 - valence + charge); } } if (valence <= 3) { // Thiol, thiolate, tioether -> tetrahedral geom = assignGeometry(degree + implicitHCount - charge + 2); } break; case "F" /* Elements.F */: case "CL" /* Elements.CL */: case "BR" /* Elements.BR */: case "I" /* Elements.I */: case "AT" /* Elements.AT */: // Never implicitly protonate halides if (assignCharge) { charge = valence - 1; } break; case "LI" /* Elements.LI */: case "NA" /* Elements.NA */: case "K" /* Elements.K */: case "RB" /* Elements.RB */: case "CS" /* Elements.CS */: case "FR" /* Elements.FR */: if (assignCharge) { charge = 1 - valence; } break; case "BE" /* Elements.BE */: case "MG" /* Elements.MG */: case "CA" /* Elements.CA */: case "SR" /* Elements.SR */: case "BA" /* Elements.BA */: case "RA" /* Elements.RA */: if (assignCharge) { charge = 2 - valence; } break; default: if (isDebugMode) { console.warn('Requested charge, protonation for an unhandled element', element); } } return [charge, implicitHCount, implicitHCount + hydrogenCount, geom]; } function calcUnitValenceModel(structure, unit, props) { var n = unit.elements.length; var charge = new Int8Array(n); var implicitH = new Int8Array(n); var totalH = new Int8Array(n); var idealGeometry = new Int8Array(n); // always use root UnitIndex to take the topology of the whole structure in account var hasParent = !!structure.parent; var mapping; if (hasParent) { var rootUnit = structure.root.unitMap.get(unit.id); mapping = SortedArray.indicesOf(rootUnit.elements, unit.elements); if (mapping.length !== unit.elements.length) { throw new Error('expected to find an index for every element'); } unit = rootUnit; structure = structure.root; } for (var i = 0; i < n; ++i) { var j = (hasParent ? mapping[i] : i); var _a = calculateHydrogensCharge(structure, unit, j, props), chg = _a[0], implH = _a[1], totH = _a[2], geom = _a[3]; charge[i] = chg; implicitH[i] = implH; totalH[i] = totH; idealGeometry[i] = geom; } return { charge: charge, implicitH: implicitH, totalH: totalH, idealGeometry: idealGeometry }; } export var ValenceModelParams = { assignCharge: PD.Select('auto', [['always', 'always'], ['auto', 'auto'], ['never', 'never']]), assignH: PD.Select('auto', [['always', 'always'], ['auto', 'auto'], ['never', 'never']]), }; export function calcValenceModel(ctx, structure, props) { return __awaiter(this, void 0, void 0, function () { var p, map, i, il, u, valenceModel; return __generator(this, function (_a) { p = __assign(__assign({}, PD.getDefaultValues(ValenceModelParams)), props); map = new Map(); for (i = 0, il = structure.units.length; i < il; ++i) { u = structure.units[i]; if (Unit.isAtomic(u)) { valenceModel = calcUnitValenceModel(structure, u, p); map.set(u.id, valenceModel); } } return [2 /*return*/, map]; }); }); }