UNPKG

molstar

Version:

A comprehensive macromolecular library.

411 lines (410 loc) 17.9 kB
/** * Copyright (c) 2017-2024 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { UUID } from '../../../mol-util/uuid'; import { CustomProperties } from '../../custom-property'; import { calcModelCenter, getAsymIdCount } from './util'; import { Vec3 } from '../../../mol-math/linear-algebra'; import { Coordinates } from '../coordinates'; import { Task } from '../../../mol-task'; import { IndexPairBonds } from '../../../mol-model-formats/structure/property/bonds/index-pair'; import { createModels } from '../../../mol-model-formats/structure/basic/parser'; import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; import { Column } from '../../../mol-data/db'; import { CustomModelProperty } from '../../../mol-model-props/common/custom-model-property'; import { ArrayTrajectory } from '../trajectory'; import { ModelSecondaryStructure } from '../../../mol-model-formats/structure/property/secondary-structure'; { } export var Model; (function (Model) { function _trajectoryFromModelAndCoordinates(model, coordinates) { const trajectory = []; const { frames } = coordinates; const srcIndexArray = getSourceIndexArray(model); const coarseGrained = isCoarseGrained(model); const elementCount = model.atomicHierarchy.atoms._rowCount; for (let i = 0, il = frames.length; i < il; ++i) { const f = frames[i]; if (f.elementCount !== elementCount) { throw new Error(`Frame element count mismatch, got ${f.elementCount} but expected ${elementCount}.`); } const m = { ...model, id: UUID.create22(), modelNum: i, atomicConformation: getAtomicConformationFromFrame(model, f), // TODO: add support for supplying sphere and gaussian coordinates in addition to atomic coordinates? // coarseConformation: coarse.conformation, customProperties: new CustomProperties(), _staticPropertyData: Object.create(null), _dynamicPropertyData: Object.create(null) }; if (f.cell) { const symmetry = ModelSymmetry.fromCell(f.cell.size, f.cell.anglesInRadians); ModelSymmetry.Provider.set(m, symmetry); } Model.TrajectoryInfo.set(m, { index: i, size: frames.length }); Model.CoarseGrained.set(m, coarseGrained); trajectory.push(m); } return { trajectory, srcIndexArray }; } function getSourceIndexArray(model) { const srcIndex = model.atomicHierarchy.atomSourceIndex; let srcIndexArray = undefined; if ('__srcIndexArray__' in model._staticPropertyData) { srcIndexArray = model._dynamicPropertyData.__srcIndexArray__; } else { srcIndexArray = Column.isIdentity(srcIndex) ? void 0 : srcIndex.toArray({ array: Int32Array }); model._dynamicPropertyData.__srcIndexArray__ = srcIndexArray; } return srcIndexArray; } function trajectoryFromModelAndCoordinates(model, coordinates) { return new ArrayTrajectory(_trajectoryFromModelAndCoordinates(model, coordinates).trajectory); } Model.trajectoryFromModelAndCoordinates = trajectoryFromModelAndCoordinates; function trajectoryFromTopologyAndCoordinates(topology, coordinates) { return Task.create('Create Trajectory', async (ctx) => { const models = await createModels(topology.basic, topology.sourceData, ctx); if (models.frameCount === 0) throw new Error('found no model'); const model = models.representative; const { trajectory } = _trajectoryFromModelAndCoordinates(model, coordinates); const bondData = { pairs: topology.bonds, count: model.atomicHierarchy.atoms._rowCount }; const indexPairBonds = IndexPairBonds.fromData(bondData); const coarseGrained = isCoarseGrained(model); let index = 0; for (const m of trajectory) { IndexPairBonds.Provider.set(m, indexPairBonds); Model.TrajectoryInfo.set(m, { index: index++, size: trajectory.length }); Model.CoarseGrained.set(m, coarseGrained); } return new ArrayTrajectory(trajectory); }); } Model.trajectoryFromTopologyAndCoordinates = trajectoryFromTopologyAndCoordinates; function getAtomicConformationFromFrame(model, frame) { return Coordinates.getAtomicConformation(frame, { atomId: model.atomicConformation.atomId, occupancy: model.atomicConformation.occupancy, B_iso_or_equiv: model.atomicConformation.B_iso_or_equiv }, getSourceIndexArray(model)); } Model.getAtomicConformationFromFrame = getAtomicConformationFromFrame; const CenterProp = '__Center__'; function getCenter(model) { if (model._dynamicPropertyData[CenterProp]) return model._dynamicPropertyData[CenterProp]; const center = calcModelCenter(model.atomicConformation, model.coarseConformation); model._dynamicPropertyData[CenterProp] = center; return center; } Model.getCenter = getCenter; function invertIndex(xs) { const invertedIndex = new Int32Array(xs.rowCount); let isIdentity = false; for (let i = 0, _i = xs.rowCount; i < _i; i++) { const x = xs.value(i); if (x !== i) isIdentity = false; invertedIndex[x] = i; } return { isIdentity, invertedIndex: invertedIndex }; } const InvertedAtomSrcIndexProp = '__InvertedAtomSrcIndex__'; function getInvertedAtomSourceIndex(model) { if (model._staticPropertyData[InvertedAtomSrcIndexProp]) return model._staticPropertyData[InvertedAtomSrcIndexProp]; const index = invertIndex(model.atomicHierarchy.atomSourceIndex); model._staticPropertyData[InvertedAtomSrcIndexProp] = index; return index; } Model.getInvertedAtomSourceIndex = getInvertedAtomSourceIndex; const TrajectoryInfoProp = '__TrajectoryInfo__'; Model.TrajectoryInfo = { get(model) { return model._dynamicPropertyData[TrajectoryInfoProp] || { index: 0, size: 1 }; }, set(model, trajectoryInfo) { return model._dynamicPropertyData[TrajectoryInfoProp] = trajectoryInfo; } }; const AsymIdCountProp = '__AsymIdCount__'; Model.AsymIdCount = { get(model) { if (model._dynamicPropertyData[AsymIdCountProp]) return model._dynamicPropertyData[AsymIdCountProp]; const asymIdCount = getAsymIdCount(model); model._dynamicPropertyData[AsymIdCountProp] = asymIdCount; return asymIdCount; }, }; Model.AsymIdOffset = CustomModelProperty.createSimple('asym_id_offset', 'static'); Model.Index = CustomModelProperty.createSimple('index', 'static'); Model.MaxIndex = CustomModelProperty.createSimple('max_index', 'static'); function getRoot(model) { return model.parent || model; } Model.getRoot = getRoot; function areHierarchiesEqual(a, b) { return a.atomicHierarchy === b.atomicHierarchy && a.coarseHierarchy === b.coarseHierarchy; } Model.areHierarchiesEqual = areHierarchiesEqual; const CoordinatesHistoryProp = '__CoordinatesHistory__'; Model.CoordinatesHistory = { get(model) { return model._staticPropertyData[CoordinatesHistoryProp]; }, set(model, coordinatesHistory) { return model._staticPropertyData[CoordinatesHistoryProp] = coordinatesHistory; } }; const CoarseGrainedProp = '__CoarseGrained__'; Model.CoarseGrained = { get(model) { return model._staticPropertyData[CoarseGrainedProp]; }, set(model, coarseGrained) { return model._staticPropertyData[CoarseGrainedProp] = coarseGrained; } }; /** * Mark as coarse grained if any of the following conditions are met: * - has typical coarse grained atom names (BB, SC1) * - has less than three times as many atoms as polymer residues (C-alpha only models) * - has no standard sidechain atoms */ function isCoarseGrained(model) { let coarseGrained = Model.CoarseGrained.get(model); if (coarseGrained === undefined) { let polymerResidueCount = 0; let polymerDirectionCount = 0; const { polymerType, directionToElementIndex } = model.atomicHierarchy.derived.residue; for (let i = 0; i < polymerType.length; ++i) { if (polymerType[i] !== 0 /* PolymerType.NA */) { polymerResidueCount += 1; if (directionToElementIndex[i] !== -1) polymerDirectionCount += 1; } } // check for coarse grained atom names let hasBB = false, hasSC1 = false; const { label_atom_id, _rowCount: atomCount } = model.atomicHierarchy.atoms; for (let i = 0; i < atomCount; ++i) { const atomName = label_atom_id.value(i); if (!hasBB && atomName === 'BB') hasBB = true; if (!hasSC1 && atomName === 'SC1') hasSC1 = true; if (hasBB && hasSC1) break; } coarseGrained = false; if (atomCount > 0 && polymerResidueCount > 0) { if (hasBB && hasSC1) { coarseGrained = true; } else if (atomCount / polymerResidueCount < 3) { coarseGrained = true; } else if (polymerDirectionCount === 0) { coarseGrained = true; } } Model.CoarseGrained.set(model, coarseGrained); } return coarseGrained; } Model.isCoarseGrained = isCoarseGrained; // function hasCarbohydrate(model) { return model.properties.saccharideComponentMap.size > 0; } Model.hasCarbohydrate = hasCarbohydrate; function hasProtein(model) { const { subtype } = model.entities; for (let i = 0, il = subtype.rowCount; i < il; ++i) { if (subtype.value(i).startsWith('polypeptide')) return true; } return false; } Model.hasProtein = hasProtein; function hasNucleic(model) { const { subtype } = model.entities; for (let i = 0, il = subtype.rowCount; i < il; ++i) { const s = subtype.value(i); if (s.endsWith('ribonucleotide hybrid') || s.endsWith('ribonucleotide')) return true; } return false; } Model.hasNucleic = hasNucleic; function isFromPdbArchive(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0, il = db.database_2.database_id.rowCount; i < il; ++i) { if (db.database_2.database_id.value(i) === 'pdb') return true; } return false; } Model.isFromPdbArchive = isFromPdbArchive; function hasPdbId(model) { if (!MmcifFormat.is(model.sourceData)) return false; return ( // 4 character PDB id model.entryId.match(/^[1-9][a-z0-9]{3,3}$/i) !== null || // long PDB id model.entryId.match(/^pdb_[0-9]{4,4}[1-9][a-z0-9]{3,3}$/i) !== null); } Model.hasPdbId = hasPdbId; function hasSecondaryStructure(model) { if (MmcifFormat.is(model.sourceData)) { const { db } = model.sourceData.data; return (db.struct_conf.id.isDefined || db.struct_sheet_range.id.isDefined); } else { return ModelSecondaryStructure.Provider.isApplicable(model); } } Model.hasSecondaryStructure = hasSecondaryStructure; const tmpAngles90 = Vec3.create(1.5707963, 1.5707963, 1.5707963); // in radians const tmpLengths1 = Vec3.create(1, 1, 1); function hasCrystalSymmetry(model) { var _a; const spacegroup = (_a = ModelSymmetry.Provider.get(model)) === null || _a === void 0 ? void 0 : _a.spacegroup; return !!spacegroup && !(spacegroup.num === 1 && Vec3.equals(spacegroup.cell.anglesInRadians, tmpAngles90) && Vec3.equals(spacegroup.cell.size, tmpLengths1)); } Model.hasCrystalSymmetry = hasCrystalSymmetry; function isFromXray(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.exptl.method.rowCount; i++) { const v = db.exptl.method.value(i).toUpperCase(); if (v.indexOf('DIFFRACTION') >= 0) return true; } return false; } Model.isFromXray = isFromXray; function isFromEm(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.exptl.method.rowCount; i++) { const v = db.exptl.method.value(i).toUpperCase(); if (v.indexOf('MICROSCOPY') >= 0) return true; } return false; } Model.isFromEm = isFromEm; function isFromNmr(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.exptl.method.rowCount; i++) { const v = db.exptl.method.value(i).toUpperCase(); if (v.indexOf('NMR') >= 0) return true; } return false; } Model.isFromNmr = isFromNmr; function isExperimental(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.struct.pdbx_structure_determination_methodology.rowCount; i++) { if (db.struct.pdbx_structure_determination_methodology.value(i).toLowerCase() === 'experimental') return true; } return false; } Model.isExperimental = isExperimental; function isIntegrative(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.struct.pdbx_structure_determination_methodology.rowCount; i++) { if (db.struct.pdbx_structure_determination_methodology.value(i).toLowerCase() === 'integrative') return true; } return false; } Model.isIntegrative = isIntegrative; function isComputational(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; for (let i = 0; i < db.struct.pdbx_structure_determination_methodology.rowCount; i++) { if (db.struct.pdbx_structure_determination_methodology.value(i).toLowerCase() === 'computational') return true; } return false; } Model.isComputational = isComputational; function hasXrayMap(model) { if (!MmcifFormat.is(model.sourceData)) return false; // Check exprimental method to exclude models solved with // 'ELECTRON CRYSTALLOGRAPHY' which also have structure factors if (!isFromXray(model)) return false; const { db } = model.sourceData.data; const { status_code_sf } = db.pdbx_database_status; return status_code_sf.isDefined && status_code_sf.value(0) === 'REL'; } Model.hasXrayMap = hasXrayMap; /** * Also checks for `content_type` of 'associated EM volume' to exclude cases * like 6TEK which are solved with 'X-RAY DIFFRACTION' but have an related * EMDB entry of type 'other EM volume'. */ function hasEmMap(model) { if (!MmcifFormat.is(model.sourceData)) return false; const { db } = model.sourceData.data; const { db_name, content_type } = db.pdbx_database_related; for (let i = 0, il = db.pdbx_database_related._rowCount; i < il; ++i) { if (db_name.value(i).toUpperCase() === 'EMDB' && content_type.value(i) === 'associated EM volume') { return true; } } return false; } Model.hasEmMap = hasEmMap; function hasDensityMap(model) { if (!MmcifFormat.is(model.sourceData)) return false; return hasXrayMap(model) || hasEmMap(model); } Model.hasDensityMap = hasDensityMap; function probablyHasDensityMap(model) { if (!MmcifFormat.is(model.sourceData)) return false; if (Model.isIntegrative(model)) return false; const { db } = model.sourceData.data; return hasDensityMap(model) || ( // check if from pdb archive but missing relevant meta data hasPdbId(model) && (!db.exptl.method.isDefined || (isFromXray(model) && (!db.pdbx_database_status.status_code_sf.isDefined || db.pdbx_database_status.status_code_sf.valueKind(0) === 2 /* Column.ValueKinds.Unknown */)) || (isFromEm(model) && (!db.pdbx_database_related.db_name.isDefined)))); } Model.probablyHasDensityMap = probablyHasDensityMap; })(Model || (Model = {}));