diff --git a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts index 6ff36dd809382fb496132b8a7e5b9c2e6cf81b71..68ce664400f7cc82f57173ec336244781f63c714 100644 --- a/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts +++ b/src/mol-geo/representation/structure/visual/carbohydrate-symbol-mesh.ts @@ -28,7 +28,7 @@ async function createCarbohydrateSymbolMesh(ctx: RuntimeContext, structure: Stru const t = Mat4.identity() const p = Vec3.zero() - const { carbohydrates } = structure + const carbohydrates = structure.carbohydrates const linkParams = { radiusTop: 0.2, radiusBottom: 0.2 } diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index 1492b2e05ada7a7fe0ab10b648719305e48736d7..159f2fe0b3c44b29f0193ad11ce058984404c155 100644 --- a/src/mol-model/structure/model/types.ts +++ b/src/mol-model/structure/model/types.ts @@ -147,6 +147,10 @@ export const WaterNames = [ 'SOL', 'WAT', 'HOH', 'H2O', 'W', 'DOD', 'D3O', 'TIP3', 'TIP4', 'SPC' ] +export const ExtraSaccharideNames = [ + 'MLR' +] + export const RnaBaseNames = [ 'A', 'C', 'T', 'G', 'I', 'U' ] export const DnaBaseNames = [ 'DA', 'DC', 'DT', 'DG', 'DI', 'DU' ] export const PurinBaseNames = [ 'A', 'G', 'DA', 'DG', 'DI' ] @@ -166,7 +170,7 @@ export function getMoleculeType(compType: string, compId: string) { return MoleculeType.RNA } else if (DNAComponentTypeNames.includes(compType)) { return MoleculeType.DNA - } else if (SaccharideComponentTypeNames.includes(compType)) { + } else if (SaccharideComponentTypeNames.includes(compType) || ExtraSaccharideNames.includes(compId)) { return MoleculeType.saccharide } else if (WaterNames.includes(compId)) { return MoleculeType.water diff --git a/src/mol-model/structure/structure/carbohydrates/compute.ts b/src/mol-model/structure/structure/carbohydrates/compute.ts index 1b9ad695d50117af9442eaade7cda6396d7cb0bc..94705c6ede10279b8e84df4f2554f28e7aa1c483 100644 --- a/src/mol-model/structure/structure/carbohydrates/compute.ts +++ b/src/mol-model/structure/structure/carbohydrates/compute.ts @@ -9,9 +9,13 @@ import { ResidueIndex } from '../../model'; import { Interval, Segmentation } from 'mol-data/int'; import Structure from '../structure'; import { Carbohydrates, CarbohydrateLink, CarbohydrateTerminalLink, CarbohydrateElement } from './data'; -import { SaccharideNameMap } from './constants'; +import { SaccharideNameMap, UnknownSaccharideComponent } from './constants'; import { Vec3 } from 'mol-math/linear-algebra'; -import { getCenterAndRadius } from '../../util'; +import { getCenterAndRadius, getMoleculeType } from '../../util'; +import { MoleculeType } from '../../model/types'; +import { areConnected } from 'mol-math/graph'; +import { combinations } from 'mol-data/util/combination'; +import { fillSerial } from 'mol-util/array'; function getResidueIndex(elementIndex: number, unit: Unit.Atomic) { return unit.model.atomicHierarchy.residueAtomSegments.index[unit.elements[elementIndex]] @@ -21,17 +25,19 @@ function getRingIndices(unit: Unit.Atomic, rI: ResidueIndex) { const { offsets } = unit.model.atomicHierarchy.residueAtomSegments const { elements } = unit const interval = Interval.ofBounds(offsets[rI], offsets[rI + 1]) - const rings = unit.rings.byFingerprint.get('C-C-C-C-C-O') || unit.rings.byFingerprint.get('C-C-C-C-O') - if (rings) { - for (let i = 0, il = rings.length; i < il; ++i) { - let withinIntervalCount = 0 - const ring = unit.rings.all[rings[i]] - for (let j = 0, jl = ring.length; j < jl; ++j) { - if (Interval.has(interval, elements[ring[j]])) ++withinIntervalCount - } - if (withinIntervalCount === ring.length) return ring + const rings: number[] = [] + rings.push(...unit.rings.byFingerprint.get('C-C-C-C-C-O') || []) + rings.push(...unit.rings.byFingerprint.get('C-C-C-C-O') || []) + const sugarRings: ReadonlyArray<number>[] = [] + for (let i = 0, il = rings.length; i < il; ++i) { + let withinIntervalCount = 0 + const ring = unit.rings.all[rings[i]] + for (let j = 0, jl = ring.length; j < jl; ++j) { + if (Interval.has(interval, elements[ring[j]])) ++withinIntervalCount } + if (withinIntervalCount === ring.length) sugarRings.push(ring) } + return sugarRings } export function computeCarbohydrates(structure: Structure): Carbohydrates { @@ -45,6 +51,7 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { return `${residueIndex}|${unitId}` } + // get carbohydrate elements and carbohydrate links induced by intra-residue bonds for (let i = 0, il = structure.units.length; i < il; ++i) { const unit = structure.units[i] if (!Unit.isAtomic(unit)) continue @@ -62,66 +69,87 @@ export function computeCarbohydrates(structure: Structure): Carbohydrates { while (residueIt.hasNext) { const { index: residueIndex } = residueIt.move(); - const saccharideComp = SaccharideNameMap.get(label_comp_id.value(residueIndex)) - if (!saccharideComp) continue + const saccharideComp = SaccharideNameMap.get(label_comp_id.value(residueIndex)) || UnknownSaccharideComponent + if (saccharideComp === UnknownSaccharideComponent) { + if (getMoleculeType(unit.model, residueIndex) !== MoleculeType.saccharide) continue + } + + const sugarRings = getRingIndices(unit, residueIndex) + const ringElements: number[] = [] + console.log('sugarRings', sugarRings) - const ringIndices = getRingIndices(unit, residueIndex) - if (ringIndices) { + for (let j = 0, jl = sugarRings.length; j < jl; ++j) { const center = Vec3.zero() const normal = Vec3.zero() const direction = Vec3.zero() - const ringRadius = getCenterAndRadius(center, unit, ringIndices) - console.log(ringRadius, center) + const elementIndex = elements.length + getCenterAndRadius(center, unit, sugarRings[j]) - elementsMap.set(elementKey(residueIndex, unit.id), elements.length) + ringElements.push(elementIndex) + elementsMap.set(elementKey(residueIndex, unit.id), elementIndex) elements.push({ center, normal, direction, unit, residueIndex, component: saccharideComp }) - } else { + } + + // add carbohydrate links induced by intra-residue bonds + const ringCombinations = combinations(fillSerial(new Array(sugarRings.length)), 2) + for (let j = 0, jl = ringCombinations.length; j < jl; ++j) { + const rc = ringCombinations[j] + if (areConnected(sugarRings[rc[0]], sugarRings[rc[1]], unit.links, 2)) { + links.push({ + carbohydrateIndexA: ringElements[rc[0]], + carbohydrateIndexB: ringElements[rc[1]] + }) + links.push({ + carbohydrateIndexA: ringElements[rc[1]], + carbohydrateIndexB: ringElements[rc[0]] + }) + } + } + + if (!sugarRings.length) { console.warn('No ring found for carbohydrate') } } } } - elementsMap.forEach((elementIndex, key) => { - const unit = elements[elementIndex].unit + // get carbohydrate links induced by inter-unit bonds + for (let i = 0, il = structure.units.length; i < il; ++i) { + const unit = structure.units[i] + if (!Unit.isAtomic(unit)) continue + structure.links.getLinkedUnits(unit).forEach(pairBonds => { pairBonds.linkedElementIndices.forEach(indexA => { pairBonds.getBonds(indexA).forEach(bondInfo => { - let { unitA, unitB } = pairBonds - let indexB = bondInfo.indexB - let residueIndexA = getResidueIndex(indexA, unitA) - let residueIndexB = getResidueIndex(indexB, unitB) - let keyA = elementKey(residueIndexA, unitA.id) - let keyB = elementKey(residueIndexB, unitB.id) - if (key === keyB) { - [keyB, keyA] = [keyA, keyB]; - [indexB, indexA] = [indexA, indexB]; - [unitB, unitA] = [unitA, unitB]; - } - const elementIndexB = elementsMap.get(keyB) - if (elementIndexB !== undefined) { + const { unitA, unitB } = pairBonds + const indexB = bondInfo.indexB + const elementIndexA = elementsMap.get(elementKey(getResidueIndex(indexA, unitA), unitA.id)) + const elementIndexB = elementsMap.get(elementKey(getResidueIndex(indexB, unitB), unitB.id)) + + if (elementIndexA !== undefined && elementIndexB !== undefined) { links.push({ - carbohydrateIndexA: elementIndex, + carbohydrateIndexA: elementIndexA, carbohydrateIndexB: elementIndexB }) - } else { + } else if (elementIndexA !== undefined) { terminalLinks.push({ - carbohydrateIndex: elementIndex, + carbohydrateIndex: elementIndexA, elementIndex: indexB, elementUnit: unitB, fromCarbohydrate: true }) + } else if (elementIndexB !== undefined) { terminalLinks.push({ - carbohydrateIndex: elementIndex, - elementIndex: indexB, - elementUnit: unitB, + carbohydrateIndex: elementIndexB, + elementIndex: indexA, + elementUnit: unitA, fromCarbohydrate: false }) } }) }) }) - }) + } return { links, terminalLinks, elements } } \ No newline at end of file diff --git a/src/mol-model/structure/structure/carbohydrates/constants.ts b/src/mol-model/structure/structure/carbohydrates/constants.ts index 8c71acb0f1333ff4d14904b24e9fd288e0f04ed4..b5b617a2ea17d41aabc321f8cf84e82e10566c6a 100644 --- a/src/mol-model/structure/structure/carbohydrates/constants.ts +++ b/src/mol-model/structure/structure/carbohydrates/constants.ts @@ -77,6 +77,13 @@ export type SaccharideComponent = { type: SaccharideType } +export const UnknownSaccharideComponent = { + abbr: 'Unk', + name: 'Unknown', + color: SaccharideColors.Secondary, + type: SaccharideType.Unknown +} + const Monosaccharides: SaccharideComponent[] = [ { abbr: 'Glc', name: 'Glucose', color: SaccharideColors.Blue, type: SaccharideType.Hexose }, { abbr: 'Man', name: 'Mannose', color: SaccharideColors.Green, type: SaccharideType.Hexose }, diff --git a/src/mol-view/stage.ts b/src/mol-view/stage.ts index a886525f57931616070ec6e17c4c17c03b158604..78d1ef9d6247c1b4af0419ef086501e0ee90f184 100644 --- a/src/mol-view/stage.ts +++ b/src/mol-view/stage.ts @@ -78,7 +78,7 @@ export class Stage { // this.loadPdbid('1hrv') // viral assembly // this.loadPdbid('1rb8') // virus // this.loadPdbid('1blu') // metal coordination - this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein + // this.loadPdbid('3pqr') // inter unit bonds, two polymer chains, ligands, water, carbohydrates linked to protein // this.loadPdbid('4v5a') // ribosome // this.loadPdbid('3j3q') // ... // this.loadPdbid('2np2') // dna @@ -91,10 +91,15 @@ export class Stage { // this.loadPdbid('1y26') // rna // this.loadPdbid('1xv6') // rna, modified nucleotides // this.loadPdbid('3bbm') // rna with linker + // this.loadPdbid('1euq') // t-rna + // this.loadPdbid('2e2i') // rna, dna, protein // this.loadPdbid('1gfl') // GFP, flourophore has carbonyl oxygen removed // this.loadPdbid('1sfi') // contains cyclic peptid // this.loadPdbid('3sn6') // discontinuous chains // this.loadPdbid('2zex') // small, contains carbohydrate polymer + // this.loadPdbid('2gdu') // contains sucrose + // this.loadPdbid('2fnc') // contains maltotriose + this.loadPdbid('4zs9') // contains raffinose // this.loadPdbid('2b5t') // contains large carbohydrate polymer // this.loadMmcifUrl(`../../examples/1cbs_full.bcif`) // this.loadMmcifUrl(`../../examples/1cbs_updated.cif`)