molstar
Version:
A comprehensive macromolecular library.
242 lines (241 loc) • 13.2 kB
JavaScript
/**
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { Mat4 } from '../../mol-math/linear-algebra.js';
import { QueryContext, StructureSelection, StructureElement } from '../../mol-model/structure.js';
import { superpose, alignAndSuperpose } from '../../mol-model/structure/structure/util/superposition.js';
import { tmAlign } from '../../mol-model/structure/structure/util/tm-align.js';
import { MolScriptBuilder as MS } from '../../mol-script/language/builder.js';
import { compile } from '../../mol-script/runtime/query/compiler.js';
import { StateTransforms } from '../../mol-plugin-state/transforms.js';
import { Asset } from '../../mol-util/assets.js';
export function buildStaticSuperposition(plugin, src) {
return plugin.dataTransaction(async () => {
for (const s of src) {
const { structure } = await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`, 'mmcif');
await transform(plugin, structure, s.matrix);
const chain = await plugin.builders.structure.tryCreateComponentFromExpression(structure, chainSelection(s.auth_asym_id), `Chain ${s.auth_asym_id}`);
if (chain)
await plugin.builders.structure.representation.addRepresentation(chain, { type: 'cartoon' });
}
});
}
export const StaticSuperpositionTestData = [
{
pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity()
},
{
pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows([
[0.406, 0.879, 0.248, -200.633],
[0.693, -0.473, 0.544, 73.403],
[0.596, -0.049, -0.802, -14.209],
[0, 0, 0, 1]
])
},
{
pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows([
[-0.053, -0.077, 0.996, -45.633],
[-0.312, 0.949, 0.057, -12.255],
[-0.949, -0.307, -0.074, 53.562],
[0, 0, 0, 1]
])
}
];
export function dynamicSuperpositionTest(plugin, src, comp_id) {
return plugin.dataTransaction(async () => {
for (const s of src) {
await loadStructure(plugin, `https://www.ebi.ac.uk/pdbe/static/entry/${s}_updated.cif`, 'mmcif');
}
const pivot = MS.struct.filter.first([
MS.struct.generator.atomGroups({
'residue-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_comp_id(), comp_id]),
'group-by': MS.struct.atomProperty.macromolecular.residueKey()
})
]);
const rest = MS.struct.modifier.exceptBy({
0: MS.struct.modifier.includeSurroundings({
0: pivot,
radius: 5
}),
by: pivot
});
const query = compile(pivot);
const xs = plugin.managers.structure.hierarchy.current.structures;
const selections = xs.map(s => StructureSelection.toLociWithCurrentUnits(query(new QueryContext(s.cell.obj.data))));
const transforms = superpose(selections);
await siteVisual(plugin, xs[0].cell, pivot, rest);
for (let i = 1; i < selections.length; i++) {
await transform(plugin, xs[i].cell, transforms[i - 1].bTransform);
await siteVisual(plugin, xs[i].cell, pivot, rest);
}
});
}
async function siteVisual(plugin, s, pivot, rest) {
const center = await plugin.builders.structure.tryCreateComponentFromExpression(s, pivot, 'pivot');
if (center)
await plugin.builders.structure.representation.addRepresentation(center, { type: 'ball-and-stick', color: 'residue-name' });
const surr = await plugin.builders.structure.tryCreateComponentFromExpression(s, rest, 'rest');
if (surr)
await plugin.builders.structure.representation.addRepresentation(surr, { type: 'ball-and-stick', color: 'uniform', size: 'uniform', sizeParams: { value: 0.33 } });
}
async function loadStructure(plugin, url, format, assemblyId) {
const data = await plugin.builders.data.download({ url: Asset.Url(url) });
const trajectory = await plugin.builders.structure.parseTrajectory(data, format);
const model = await plugin.builders.structure.createModel(trajectory);
const structure = await plugin.builders.structure.createStructure(model, assemblyId ? { name: 'assembly', params: { id: assemblyId } } : void 0);
return { data, trajectory, model, structure };
}
function chainSelection(auth_asym_id) {
return MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
});
}
function transform(plugin, s, matrix) {
const b = plugin.state.data.build().to(s)
.insert(StateTransforms.Model.TransformStructureConformation, { transform: { name: 'matrix', params: { data: matrix, transpose: false } } });
return plugin.runTask(plugin.state.data.updateTree(b));
}
/**
* TM-align superposition: aligns two structures using TM-align algorithm
* @param plugin - Mol* plugin context
* @param pdbId1 - PDB ID of first structure (reference)
* @param chain1 - Chain ID of first structure
* @param pdbId2 - PDB ID of second structure (mobile)
* @param chain2 - Chain ID of second structure
* @param color1 - Optional color for first structure (hex, default blue)
* @param color2 - Optional color for second structure (hex, default red)
*/
export async function tmAlignStructures(plugin, pdbId1, chain1, pdbId2, chain2, color1 = 0x3498db, color2 = 0xe74c3c) {
var _a, _b, _c, _d;
await plugin.clear();
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
const label1 = `${pdbId1} Chain ${chain1}`;
const label2 = `${pdbId2} Chain ${chain2}`;
// Load structures
const struct1 = await loadStructure(plugin, url1, 'pdb');
const struct2 = await loadStructure(plugin, url2, 'pdb');
// Build query for C-alpha atoms from specified chains
const caQuery1 = compile(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain1]),
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
}));
const caQuery2 = compile(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain2]),
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
}));
const structure1Data = (_b = (_a = struct1.structure.cell) === null || _a === void 0 ? void 0 : _a.obj) === null || _b === void 0 ? void 0 : _b.data;
const structure2Data = (_d = (_c = struct2.structure.cell) === null || _c === void 0 ? void 0 : _c.obj) === null || _d === void 0 ? void 0 : _d.data;
if (!structure1Data || !structure2Data) {
console.error('Failed to load structures');
return undefined;
}
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery1(new QueryContext(structure1Data)));
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery2(new QueryContext(structure2Data)));
const loci1 = StructureElement.Loci.is(sel1) ? sel1 : StructureElement.Loci.none(structure1Data);
const loci2 = StructureElement.Loci.is(sel2) ? sel2 : StructureElement.Loci.none(structure2Data);
if (StructureElement.Loci.size(loci1) === 0 || StructureElement.Loci.size(loci2) === 0) {
console.error('Empty selection - cannot run TM-align');
// Still show the structures without alignment
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
return undefined;
}
// Run TM-align
const result = tmAlign(loci1, loci2);
console.log('TM-score (structure 1):', result.tmScoreA.toFixed(5));
console.log('TM-score (structure 2):', result.tmScoreB.toFixed(5));
console.log('RMSD:', result.rmsd.toFixed(2), 'A');
console.log('Aligned residues:', result.alignedLength);
// Apply the transformation to superimpose structure 2 onto structure 1
await transform(plugin, struct2.structure, result.bTransform);
// Add cartoon representations
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
return {
tmScoreA: result.tmScoreA,
tmScoreB: result.tmScoreB,
rmsd: result.rmsd,
alignedLength: result.alignedLength
};
}
async function addChainRepresentation(plugin, structure, chain, label, color) {
const component = await plugin.builders.structure.tryCreateComponentFromExpression(structure, chainSelection(chain), label);
if (component) {
await plugin.builders.structure.representation.addRepresentation(component, {
type: 'cartoon',
color: 'uniform',
colorParams: { value: color }
});
}
}
/**
* Load and display two structures without any alignment
* @param plugin - Mol* plugin context
* @param pdbId1 - PDB ID of first structure
* @param chain1 - Chain ID of first structure
* @param pdbId2 - PDB ID of second structure
* @param chain2 - Chain ID of second structure
* @param color1 - Optional color for first structure (hex, default blue)
* @param color2 - Optional color for second structure (hex, default red)
*/
export async function loadStructuresNoAlignment(plugin, pdbId1, chain1, pdbId2, chain2, color1 = 0x3498db, color2 = 0xe74c3c) {
await plugin.clear();
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
const label1 = `${pdbId1} Chain ${chain1}`;
const label2 = `${pdbId2} Chain ${chain2}`;
const struct1 = await loadStructure(plugin, url1, 'pdb');
const struct2 = await loadStructure(plugin, url2, 'pdb');
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
console.log('Loaded structures - NO ALIGNMENT');
}
/**
* Sequence-based superposition: aligns two structures using sequence alignment + RMSD minimization
* @param plugin - Mol* plugin context
* @param pdbId1 - PDB ID of first structure (reference)
* @param chain1 - Chain ID of first structure
* @param pdbId2 - PDB ID of second structure (mobile)
* @param chain2 - Chain ID of second structure
* @param color1 - Optional color for first structure (hex, default blue)
* @param color2 - Optional color for second structure (hex, default red)
*/
export async function sequenceAlignStructures(plugin, pdbId1, chain1, pdbId2, chain2, color1 = 0x3498db, color2 = 0xe74c3c) {
var _a, _b, _c, _d;
await plugin.clear();
const url1 = `https://files.rcsb.org/download/${pdbId1}.pdb`;
const url2 = `https://files.rcsb.org/download/${pdbId2}.pdb`;
const label1 = `${pdbId1} Chain ${chain1}`;
const label2 = `${pdbId2} Chain ${chain2}`;
const struct1 = await loadStructure(plugin, url1, 'pdb');
const struct2 = await loadStructure(plugin, url2, 'pdb');
// Build queries for C-alpha atoms from specified chains
const caQuery1 = compile(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain1]),
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
}));
const caQuery2 = compile(MS.struct.generator.atomGroups({
'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), chain2]),
'atom-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.label_atom_id(), 'CA'])
}));
const structure1Data = (_b = (_a = struct1.structure.cell) === null || _a === void 0 ? void 0 : _a.obj) === null || _b === void 0 ? void 0 : _b.data;
const structure2Data = (_d = (_c = struct2.structure.cell) === null || _c === void 0 ? void 0 : _c.obj) === null || _d === void 0 ? void 0 : _d.data;
if (!structure1Data || !structure2Data) {
console.error('Failed to load structures');
return { rmsd: 0 };
}
const sel1 = StructureSelection.toLociWithCurrentUnits(caQuery1(new QueryContext(structure1Data)));
const sel2 = StructureSelection.toLociWithCurrentUnits(caQuery2(new QueryContext(structure2Data)));
// Run sequence alignment + superposition
const transforms = alignAndSuperpose([sel1, sel2]);
// Apply the transformation to superimpose structure 2 onto structure 1
await transform(plugin, struct2.structure, transforms[0].bTransform);
// Add cartoon representations
await addChainRepresentation(plugin, struct1.structure, chain1, label1, color1);
await addChainRepresentation(plugin, struct2.structure, chain2, label2, color2);
console.log('RMSD:', transforms[0].rmsd.toFixed(2), 'A');
return { rmsd: transforms[0].rmsd };
}