molstar
Version:
A comprehensive macromolecular library.
220 lines (219 loc) • 10.5 kB
JavaScript
/**
* Copyright (c) 2019-2025 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author Alexander Rose <alexander.rose@weirdbyte.de>
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Structure, Unit, Bond } from '../../../mol-model/structure';
import { Features, FeaturesBuilder } from './features';
import { ValenceModelProvider } from '../valence-model';
import { interactionTypeLabel } from './common';
import { IntraContactsBuilder, InterContactsBuilder } from './contacts-builder';
import { IntMap } from '../../../mol-data/int';
import { addUnitContacts, addStructureContacts, ContactsParams } from './contacts';
import { HalogenDonorProvider, HalogenAcceptorProvider, HalogenBondsProvider } from './halogen-bonds';
import { HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider, HydrogenBondsProvider, WeakHydrogenBondsProvider } from './hydrogen-bonds';
import { NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider, IonicProvider, PiStackingProvider, CationPiProvider } from './charged';
import { HydrophobicAtomProvider, HydrophobicProvider } from './hydrophobic';
import { SetUtils } from '../../../mol-util/set';
import { MetalCoordinationProvider, MetalProvider, MetalBindingProvider } from './metal';
import { refineInteractions } from './refine';
import { DataLocation } from '../../../mol-model/location';
import { CentroidHelper } from '../../../mol-math/geometry/centroid-helper';
import { DataLoci } from '../../../mol-model/loci';
import { bondLabel } from '../../../mol-theme/label';
import { ObjectKeys } from '../../../mol-util/type-helpers';
export { Interactions };
var Interactions;
(function (Interactions) {
function Location(interactions, structure, unitA, indexA, unitB, indexB) {
return DataLocation('interactions', { structure, interactions }, { unitA: unitA, indexA: indexA, unitB: unitB, indexB: indexB });
}
Interactions.Location = Location;
function isLocation(x) {
return !!x && x.kind === 'data-location' && x.tag === 'interactions';
}
Interactions.isLocation = isLocation;
function areLocationsEqual(locA, locB) {
return (locA.data.structure === locB.data.structure &&
locA.data.interactions === locB.data.interactions &&
locA.element.indexA === locB.element.indexA &&
locA.element.indexB === locB.element.indexB &&
locA.element.unitA === locB.element.unitA &&
locA.element.unitB === locB.element.unitB);
}
Interactions.areLocationsEqual = areLocationsEqual;
function _label(interactions, element) {
const { unitA, indexA, unitB, indexB } = element;
const { contacts, unitsContacts } = interactions;
if (unitA === unitB) {
const contacts = unitsContacts.get(unitA.id);
const idx = contacts.getDirectedEdgeIndex(indexA, indexB);
return interactionTypeLabel(contacts.edgeProps.type[idx]);
}
else {
const idx = contacts.getEdgeIndex(indexA, unitA.id, indexB, unitB.id);
return interactionTypeLabel(contacts.edges[idx].props.type);
}
}
function locationLabel(location) {
return _label(location.data.interactions, location.element);
}
Interactions.locationLabel = locationLabel;
function Loci(structure, interactions, elements) {
return DataLoci('interactions', { structure, interactions }, elements, (boundingSphere) => getBoundingSphere(interactions, elements, boundingSphere), () => getLabel(structure, interactions, elements));
}
Interactions.Loci = Loci;
function isLoci(x) {
return !!x && x.kind === 'data-loci' && x.tag === 'interactions';
}
Interactions.isLoci = isLoci;
function getBoundingSphere(interactions, elements, boundingSphere) {
const { unitsFeatures } = interactions;
return CentroidHelper.fromPairProvider(elements.length, (i, pA, pB) => {
const e = elements[i];
Features.setPosition(pA, e.unitA, e.indexA, unitsFeatures.get(e.unitA.id));
Features.setPosition(pB, e.unitB, e.indexB, unitsFeatures.get(e.unitB.id));
}, boundingSphere);
}
Interactions.getBoundingSphere = getBoundingSphere;
function getLabel(structure, interactions, elements) {
const element = elements[0];
if (element === undefined)
return '';
const { unitA, indexA, unitB, indexB } = element;
const { unitsFeatures } = interactions;
const { members: mA, offsets: oA } = unitsFeatures.get(unitA.id);
const { members: mB, offsets: oB } = unitsFeatures.get(unitB.id);
const options = { granularity: 'element' };
if (oA[indexA + 1] - oA[indexA] > 1 || oB[indexB + 1] - oB[indexB] > 1) {
options.granularity = 'residue';
}
return [
_label(interactions, element),
bondLabel(Bond.Location(structure, unitA, mA[oA[indexA]], structure, unitB, mB[oB[indexB]]), options)
].join('</br>');
}
Interactions.getLabel = getLabel;
})(Interactions || (Interactions = {}));
const FeatureProviders = [
HydrogenDonorProvider, WeakHydrogenDonorProvider, HydrogenAcceptorProvider,
NegativChargeProvider, PositiveChargeProvider, AromaticRingProvider,
HalogenDonorProvider, HalogenAcceptorProvider,
HydrophobicAtomProvider,
MetalProvider, MetalBindingProvider,
];
const ContactProviders = {
'ionic': IonicProvider,
'pi-stacking': PiStackingProvider,
'cation-pi': CationPiProvider,
'halogen-bonds': HalogenBondsProvider,
'hydrogen-bonds': HydrogenBondsProvider,
'weak-hydrogen-bonds': WeakHydrogenBondsProvider,
'hydrophobic': HydrophobicProvider,
'metal-coordination': MetalCoordinationProvider,
};
function getProvidersParams(defaultOn = []) {
const params = Object.create(null);
Object.keys(ContactProviders).forEach(k => {
params[k] = PD.MappedStatic(defaultOn.includes(k) ? 'on' : 'off', {
on: PD.Group(ContactProviders[k].params),
off: PD.Group({})
}, { cycle: true });
});
return params;
}
export const ContactProviderParams = getProvidersParams([
// 'ionic',
'cation-pi',
'pi-stacking',
'hydrogen-bonds',
'halogen-bonds',
// 'hydrophobic',
'metal-coordination',
// 'weak-hydrogen-bonds',
]);
export const InteractionsParams = {
providers: PD.Group(ContactProviderParams, { isFlat: true }),
contacts: PD.Group(ContactsParams, { label: 'Advanced Options' }),
};
export async function computeInteractions(ctx, structure, props, options) {
const p = { ...PD.getDefaultValues(InteractionsParams), ...props };
const cacheKey = JSON.stringify(p);
await ValenceModelProvider.attach(ctx, structure);
const contactTesters = [];
ObjectKeys(ContactProviders).forEach(k => {
const { name, params } = p.providers[k];
if (name === 'on') {
contactTesters.push(ContactProviders[k].createTester(params));
}
});
const requiredFeatures = new Set();
contactTesters.forEach(l => SetUtils.add(requiredFeatures, l.requiredFeatures));
const featureProviders = FeatureProviders.filter(f => SetUtils.areIntersecting(requiredFeatures, f.types));
const unitsFeatures = IntMap.Mutable();
const unitsContacts = IntMap.Mutable();
for (let i = 0, il = structure.unitSymmetryGroups.length; i < il; ++i) {
const group = structure.unitSymmetryGroups[i];
if (ctx.runtime.shouldUpdate) {
await ctx.runtime.update({ message: 'computing interactions', current: i, max: il });
}
const features = findUnitFeatures(structure, group.units[0], featureProviders, cacheKey);
for (let j = 0, jl = group.units.length; j < jl; ++j) {
const u = group.units[j];
unitsFeatures.set(u.id, features);
}
if (options === null || options === void 0 ? void 0 : options.skipIntraContacts)
continue;
const intraUnitContacts = findIntraUnitContacts(structure, group.units[0], features, contactTesters, p.contacts);
for (let j = 0, jl = group.units.length; j < jl; ++j) {
const u = group.units[j];
unitsContacts.set(u.id, intraUnitContacts);
}
}
const contacts = findInterUnitContacts(structure, unitsFeatures, contactTesters, p.contacts, options);
const interactions = { unitsFeatures, unitsContacts, contacts };
refineInteractions(structure, interactions);
return interactions;
}
function findUnitFeatures(structure, unit, featureProviders, cacheKey) {
const key = `features-${cacheKey}`;
if (unit.transientCache.has(key)) {
return unit.transientCache.get(key);
}
const count = unit.elements.length;
const featuresBuilder = FeaturesBuilder.create(count, count / 2);
if (Unit.isAtomic(unit)) {
for (const fp of featureProviders) {
fp.add(structure, unit, featuresBuilder);
}
}
const features = featuresBuilder.getFeatures(count);
unit.transientCache.set(key, features);
return features;
}
function findIntraUnitContacts(structure, unit, features, contactTesters, props) {
const builder = IntraContactsBuilder.create(features, unit.elements.length);
if (Unit.isAtomic(unit)) {
addUnitContacts(structure, unit, features, builder, contactTesters, props);
}
return builder.getContacts();
}
function findInterUnitContacts(structure, unitsFeatures, contactTesters, props, options) {
const builder = InterContactsBuilder.create();
Structure.eachUnitPair(structure, (unitA, unitB) => {
const featuresA = unitsFeatures.get(unitA.id);
const featuresB = unitsFeatures.get(unitB.id);
addStructureContacts(structure, unitA, featuresA, unitB, featuresB, builder, contactTesters, props);
}, {
maxRadius: Math.max(...contactTesters.map(t => t.maxDistance)),
validUnit: (unit) => Unit.isAtomic(unit),
validUnitPair: (unitA, unitB) => {
if (options === null || options === void 0 ? void 0 : options.unitPairTest)
return options.unitPairTest(unitA, unitB);
return Structure.validUnitPair(structure, unitA, unitB);
}
});
return builder.getContacts(unitsFeatures);
}