|
@@ -8,280 +8,120 @@
|
|
import { ConfalPyramidsProvider } from './property';
|
|
import { ConfalPyramidsProvider } from './property';
|
|
import { ConfalPyramidsTypes as CPT } from './types';
|
|
import { ConfalPyramidsTypes as CPT } from './types';
|
|
import { Segmentation } from '../../../mol-data/int';
|
|
import { Segmentation } from '../../../mol-data/int';
|
|
-import { Vec3 } from '../../../mol-math/linear-algebra';
|
|
|
|
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
|
|
import { ChainIndex, ElementIndex, ResidueIndex, Structure, StructureElement, StructureProperties, Unit } from '../../../mol-model/structure';
|
|
|
|
|
|
-export namespace ConfalPyramidsUtil {
|
|
|
|
- type Residue = Segmentation.Segment<ResidueIndex>;
|
|
|
|
|
|
+type Residue = Segmentation.Segment<ResidueIndex>;
|
|
|
|
|
|
- export type AtomInfo = {
|
|
|
|
- pos: Vec3,
|
|
|
|
- index: ElementIndex,
|
|
|
|
- fakeAltId: string,
|
|
|
|
- };
|
|
|
|
|
|
+export type Pyramid = {
|
|
|
|
+ O3: ElementIndex,
|
|
|
|
+ P: ElementIndex,
|
|
|
|
+ OP1: ElementIndex,
|
|
|
|
+ OP2: ElementIndex,
|
|
|
|
+ O5: ElementIndex,
|
|
|
|
+ confalScore: number,
|
|
|
|
+ stepIdx: number,
|
|
|
|
+};
|
|
|
|
|
|
- export type FirstResidueAtoms = {
|
|
|
|
- O3: AtomInfo,
|
|
|
|
- };
|
|
|
|
|
|
+const EmptyStepIndices = new Array<number>();
|
|
|
|
|
|
- export type SecondResidueAtoms = {
|
|
|
|
- OP1: AtomInfo,
|
|
|
|
- OP2: AtomInfo,
|
|
|
|
- O5: AtomInfo,
|
|
|
|
- P: AtomInfo,
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- type ResidueInfo = {
|
|
|
|
- PDB_model_num: number,
|
|
|
|
- asym_id: string,
|
|
|
|
- auth_asym_id: string,
|
|
|
|
- seq_id: number,
|
|
|
|
- auth_seq_id: number,
|
|
|
|
- comp_id: string,
|
|
|
|
- alt_id: string,
|
|
|
|
- ins_code: string,
|
|
|
|
- };
|
|
|
|
|
|
+function copyResidue(r?: Residue) {
|
|
|
|
+ return r ? { index: r.index, start: r.start, end: r.end } : void 0;
|
|
|
|
+}
|
|
|
|
|
|
- export type Handler = (pyramid: CPT.Step, first: FirstResidueAtoms, second: SecondResidueAtoms, firstLocIndex: number, secondLocIndex: number) => void;
|
|
|
|
|
|
+function getAtomIndex(loc: StructureElement.Location, residue: Residue, names: string[], altId: string): ElementIndex {
|
|
|
|
+ for (let eI = residue.start; eI < residue.end; eI++) {
|
|
|
|
+ loc.element = loc.unit.elements[eI];
|
|
|
|
+ const elName = StructureProperties.atom.label_atom_id(loc);
|
|
|
|
+ const elAltId = StructureProperties.atom.label_alt_id(loc);
|
|
|
|
|
|
- function residueInfoFromLocation(loc: StructureElement.Location): ResidueInfo {
|
|
|
|
- return {
|
|
|
|
- PDB_model_num: StructureProperties.unit.model_num(loc),
|
|
|
|
- asym_id: StructureProperties.chain.label_asym_id(loc),
|
|
|
|
- auth_asym_id: StructureProperties.chain.auth_asym_id(loc),
|
|
|
|
- seq_id: StructureProperties.residue.label_seq_id(loc),
|
|
|
|
- auth_seq_id: StructureProperties.residue.auth_seq_id(loc),
|
|
|
|
- comp_id: StructureProperties.atom.label_comp_id(loc),
|
|
|
|
- alt_id: StructureProperties.atom.label_alt_id(loc),
|
|
|
|
- ins_code: StructureProperties.residue.pdbx_PDB_ins_code(loc)
|
|
|
|
- };
|
|
|
|
|
|
+ if (names.includes(elName) && (elAltId === altId || elAltId.length === 0))
|
|
|
|
+ return loc.element;
|
|
}
|
|
}
|
|
|
|
|
|
- export function hasMultipleModels(unit: Unit.Atomic): boolean {
|
|
|
|
- const prop = ConfalPyramidsProvider.get(unit.model).value;
|
|
|
|
- if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
|
|
|
|
- return prop.data.hasMultipleModels;
|
|
|
|
- }
|
|
|
|
|
|
+ return -1 as ElementIndex;
|
|
|
|
+}
|
|
|
|
|
|
- function getPossibleAltIds(residue: Residue, structure: Structure, unit: Unit.Atomic): string[] {
|
|
|
|
- const possibleAltIds: string[] = [];
|
|
|
|
|
|
+function getPyramid(loc: StructureElement.Location, one: Residue, two: Residue, altIdOne: string, altIdTwo: string, confalScore: number, stepIdx: number): Pyramid {
|
|
|
|
+ const O3 = getAtomIndex(loc, one, ['O3\'', 'O3*'], altIdOne);
|
|
|
|
+ const P = getAtomIndex(loc, two, ['P'], altIdTwo);
|
|
|
|
+ const OP1 = getAtomIndex(loc, two, ['OP1'], altIdTwo);
|
|
|
|
+ const OP2 = getAtomIndex(loc, two, ['OP2'], altIdTwo);
|
|
|
|
+ const O5 = getAtomIndex(loc, two, ['O5\'', 'O5*'], altIdTwo);
|
|
|
|
|
|
- const loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
|
|
|
- for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
|
|
|
- loc.element = unit.elements[rI];
|
|
|
|
- const altId = StructureProperties.atom.label_alt_id(loc);
|
|
|
|
- if (altId !== '' && !possibleAltIds.includes(altId)) possibleAltIds.push(altId);
|
|
|
|
- }
|
|
|
|
|
|
+ return { O3, P, OP1, OP2, O5, confalScore, stepIdx };
|
|
|
|
+}
|
|
|
|
|
|
- return possibleAltIds;
|
|
|
|
|
|
+export class ConfalPyramidsIterator {
|
|
|
|
+ private chainIt: Segmentation.SegmentIterator<ChainIndex>;
|
|
|
|
+ private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
|
|
|
|
+ private residueOne?: Residue;
|
|
|
|
+ private residueTwo: Residue;
|
|
|
|
+ private data?: CPT.Steps;
|
|
|
|
+ private loc: StructureElement.Location;
|
|
|
|
+
|
|
|
|
+ private getStepIndices(r: Residue) {
|
|
|
|
+ this.loc.element = this.loc.unit.elements[r.start];
|
|
|
|
+
|
|
|
|
+ const modelIdx = StructureProperties.unit.model_num(this.loc) - 1;
|
|
|
|
+ const chainId = StructureProperties.chain.auth_asym_id(this.loc);
|
|
|
|
+ const seqId = StructureProperties.residue.auth_seq_id(this.loc);
|
|
|
|
+
|
|
|
|
+ const chains = this.data!.mapping[modelIdx];
|
|
|
|
+ if (!chains) return EmptyStepIndices;
|
|
|
|
+ const residues = chains.get(chainId);
|
|
|
|
+ if (!residues) return EmptyStepIndices;
|
|
|
|
+ return residues.get(seqId) ?? EmptyStepIndices;
|
|
}
|
|
}
|
|
|
|
|
|
- class Utility {
|
|
|
|
- protected getPyramidByName(name: string): { pyramid: CPT.Step | undefined, index: number } {
|
|
|
|
- const index = this.data.names.get(name);
|
|
|
|
- if (index === undefined) return { pyramid: undefined, index: -1 };
|
|
|
|
-
|
|
|
|
- return { pyramid: this.data.steps[index], index };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- protected stepToName(entry_id: string, modelNum: number, locFirst: StructureElement.Location, locSecond: StructureElement.Location, fakeAltId_1: string, fakeAltId_2: string) {
|
|
|
|
- const first = residueInfoFromLocation(locFirst);
|
|
|
|
- const second = residueInfoFromLocation(locSecond);
|
|
|
|
- const model_id = this.hasMultipleModels ? `-m${modelNum}` : '';
|
|
|
|
- const alt_id_1 = fakeAltId_1 !== '' ? `.${fakeAltId_1}` : (first.alt_id.length ? `.${first.alt_id}` : '');
|
|
|
|
- const alt_id_2 = fakeAltId_2 !== '' ? `.${fakeAltId_2}` : (second.alt_id.length ? `.${second.alt_id}` : '');
|
|
|
|
- const ins_code_1 = first.ins_code.length ? `.${first.ins_code}` : '';
|
|
|
|
- const ins_code_2 = second.ins_code.length ? `.${second.ins_code}` : '';
|
|
|
|
-
|
|
|
|
- return `${entry_id}${model_id}_${first.auth_asym_id}_${first.comp_id}${alt_id_1}_${first.auth_seq_id}${ins_code_1}_${second.comp_id}${alt_id_2}_${second.auth_seq_id}${ins_code_2}`;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- constructor(unit: Unit.Atomic) {
|
|
|
|
- const prop = ConfalPyramidsProvider.get(unit.model).value;
|
|
|
|
- if (prop === undefined || prop.data === undefined) throw new Error('No custom properties data');
|
|
|
|
-
|
|
|
|
- this.data = prop.data;
|
|
|
|
- this.hasMultipleModels = hasMultipleModels(unit);
|
|
|
|
|
|
+ private moveStep() {
|
|
|
|
+ this.residueOne = copyResidue(this.residueTwo);
|
|
|
|
+ this.residueTwo = copyResidue(this.residueIt.move())!;
|
|
|
|
|
|
- this.entryId = unit.model.entryId.toLowerCase();
|
|
|
|
- this.modelNum = unit.model.modelNum;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- protected readonly data: CPT.StepsData;
|
|
|
|
- protected readonly hasMultipleModels: boolean;
|
|
|
|
- protected readonly entryId: string;
|
|
|
|
- protected readonly modelNum: number;
|
|
|
|
|
|
+ return this.toPyramids(this.residueOne!, this.residueTwo);
|
|
}
|
|
}
|
|
|
|
|
|
- export class UnitWalker extends Utility {
|
|
|
|
- private getAtomIndices(names: string[], residue: Residue): ElementIndex[] {
|
|
|
|
- const indices: ElementIndex[] = [];
|
|
|
|
-
|
|
|
|
- const loc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
|
|
|
|
- for (let rI = residue.start; rI <= residue.end - 1; rI++) {
|
|
|
|
- loc.element = this.unit.elements[rI];
|
|
|
|
- const thisName = StructureProperties.atom.label_atom_id(loc);
|
|
|
|
- if (names.includes(thisName)) indices.push(loc.element);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (indices.length === 0) {
|
|
|
|
- let namesStr = '';
|
|
|
|
- for (const n of names)
|
|
|
|
- namesStr += `${n} `;
|
|
|
|
|
|
+ private toPyramids(one: Residue, two: Residue) {
|
|
|
|
+ const indices = this.getStepIndices(one);
|
|
|
|
|
|
- throw new Error(`Element [${namesStr}] not found on residue ${residue.index}`);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return indices;
|
|
|
|
|
|
+ const points = [];
|
|
|
|
+ for (const idx of indices) {
|
|
|
|
+ const step = this.data!.steps[idx];
|
|
|
|
+ points.push(getPyramid(this.loc, one, two, step.label_alt_id_1, step.label_alt_id_2, step.confal_score, idx));
|
|
}
|
|
}
|
|
|
|
|
|
- private getAtomPositions(indices: ElementIndex[]): Vec3[] {
|
|
|
|
- const pos = this.unit.conformation.invariantPosition;
|
|
|
|
- const positions: Vec3[] = [];
|
|
|
|
-
|
|
|
|
- for (const eI of indices) {
|
|
|
|
- const v = Vec3.zero();
|
|
|
|
- pos(eI, v);
|
|
|
|
- positions.push(v);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return positions;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private handleStep(firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[]) {
|
|
|
|
- const modelNum = this.hasMultipleModels ? this.modelNum : -1;
|
|
|
|
- let ok = false;
|
|
|
|
-
|
|
|
|
- const firstLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
|
|
|
|
- const secondLoc = StructureElement.Location.create(this.structure, this.unit, -1 as ElementIndex);
|
|
|
|
- for (let i = 0; i < firstAtoms.length; i++) {
|
|
|
|
- const first = firstAtoms[i];
|
|
|
|
- for (let j = 0; j < secondAtoms.length; j++) {
|
|
|
|
- const second = secondAtoms[j];
|
|
|
|
- firstLoc.element = first.O3.index;
|
|
|
|
- secondLoc.element = second.OP1.index;
|
|
|
|
-
|
|
|
|
- const name = this.stepToName(this.entryId, modelNum, firstLoc, secondLoc, first.O3.fakeAltId, second.OP1.fakeAltId);
|
|
|
|
- const { pyramid, index } = this.getPyramidByName(name);
|
|
|
|
- if (pyramid !== undefined) {
|
|
|
|
- const locIndex = index * 2;
|
|
|
|
- this.handler(pyramid, first, second, locIndex, locIndex + 1);
|
|
|
|
- ok = true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (!ok) throw new Error('Bogus step');
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private processFirstResidue(residue: Residue, possibleAltIds: string[]) {
|
|
|
|
- const indO3 = this.getAtomIndices(['O3\'', 'O3*'], residue);
|
|
|
|
- const posO3 = this.getAtomPositions(indO3);
|
|
|
|
-
|
|
|
|
- const altPos: FirstResidueAtoms[] = [
|
|
|
|
- { O3: { pos: posO3[0], index: indO3[0], fakeAltId: '' } }
|
|
|
|
- ];
|
|
|
|
-
|
|
|
|
- for (let i = 1; i < indO3.length; i++) {
|
|
|
|
- altPos.push({ O3: { pos: posO3[i], index: indO3[i], fakeAltId: '' } });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (altPos.length === 1 && possibleAltIds.length > 1) {
|
|
|
|
- /* We have some alternate positions on the residue but O3 does not have any - fake them */
|
|
|
|
- altPos[0].O3.fakeAltId = possibleAltIds[0];
|
|
|
|
-
|
|
|
|
- for (let i = 1; i < possibleAltIds.length; i++)
|
|
|
|
- altPos.push({ O3: { pos: posO3[0], index: indO3[0], fakeAltId: possibleAltIds[i] } });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return altPos;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private processSecondResidue(residue: Residue, possibleAltIds: string[]) {
|
|
|
|
- const indOP1 = this.getAtomIndices(['OP1'], residue);
|
|
|
|
- const indOP2 = this.getAtomIndices(['OP2'], residue);
|
|
|
|
- const indO5 = this.getAtomIndices(['O5\'', 'O5*'], residue);
|
|
|
|
- const indP = this.getAtomIndices(['P'], residue);
|
|
|
|
-
|
|
|
|
- const posOP1 = this.getAtomPositions(indOP1);
|
|
|
|
- const posOP2 = this.getAtomPositions(indOP2);
|
|
|
|
- const posO5 = this.getAtomPositions(indO5);
|
|
|
|
- const posP = this.getAtomPositions(indP);
|
|
|
|
-
|
|
|
|
- const infoOP1: AtomInfo[] = [];
|
|
|
|
- /* We use OP1 as "pivotal" atom. There is no specific reason
|
|
|
|
- * to pick OP1, it is as good a choice as any other atom
|
|
|
|
- */
|
|
|
|
- if (indOP1.length === 1 && possibleAltIds.length > 1) {
|
|
|
|
- /* No altIds on OP1, fake them */
|
|
|
|
- for (const altId of possibleAltIds)
|
|
|
|
- infoOP1.push({ pos: posOP1[0], index: indOP1[0], fakeAltId: altId });
|
|
|
|
- } else {
|
|
|
|
- for (let i = 0; i < indOP1.length; i++)
|
|
|
|
- infoOP1.push({ pos: posOP1[i], index: indOP1[i], fakeAltId: '' });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- const mkInfo = (i: number, indices: ElementIndex[], positions: Vec3[], altId: string) => {
|
|
|
|
- if (i >= indices.length) {
|
|
|
|
- const last = indices.length - 1;
|
|
|
|
- return { pos: positions[last], index: indices[last], fakeAltId: altId };
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return { pos: positions[i], index: indices[i], fakeAltId: altId };
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- const altPos: SecondResidueAtoms[] = [];
|
|
|
|
- for (let i = 0; i < infoOP1.length; i++) {
|
|
|
|
- const altId = infoOP1[i].fakeAltId;
|
|
|
|
-
|
|
|
|
- const OP2 = mkInfo(i, indOP2, posOP2, altId);
|
|
|
|
- const O5 = mkInfo(i, indO5, posO5, altId);
|
|
|
|
- const P = mkInfo(i, indP, posP, altId);
|
|
|
|
-
|
|
|
|
- altPos.push({ OP1: infoOP1[i], OP2, O5, P });
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- return altPos;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- private step(residue: Residue): { firstAtoms: FirstResidueAtoms[], secondAtoms: SecondResidueAtoms[] } {
|
|
|
|
- const firstPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
|
|
|
- const firstAtoms = this.processFirstResidue(residue, firstPossibleAltIds);
|
|
|
|
|
|
+ return points;
|
|
|
|
+ }
|
|
|
|
|
|
- residue = this.residueIt.move();
|
|
|
|
|
|
+ constructor(structure: Structure, unit: Unit) {
|
|
|
|
+ this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
|
|
|
|
+ this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
|
|
|
|
|
- const secondPossibleAltIds = getPossibleAltIds(residue, this.structure, this.unit);
|
|
|
|
- const secondAtoms = this.processSecondResidue(residue, secondPossibleAltIds);
|
|
|
|
|
|
+ const prop = ConfalPyramidsProvider.get(unit.model).value;
|
|
|
|
+ this.data = prop?.data;
|
|
|
|
|
|
- return { firstAtoms, secondAtoms };
|
|
|
|
|
|
+ if (this.chainIt.hasNext) {
|
|
|
|
+ this.residueIt.setSegment(this.chainIt.move());
|
|
|
|
+ if (this.residueIt.hasNext)
|
|
|
|
+ this.residueTwo = this.residueIt.move();
|
|
}
|
|
}
|
|
|
|
|
|
- walk() {
|
|
|
|
- while (this.chainIt.hasNext) {
|
|
|
|
- this.residueIt.setSegment(this.chainIt.move());
|
|
|
|
-
|
|
|
|
- let residue = this.residueIt.move();
|
|
|
|
- while (this.residueIt.hasNext) {
|
|
|
|
- try {
|
|
|
|
- const { firstAtoms, secondAtoms } = this.step(residue);
|
|
|
|
-
|
|
|
|
- this.handleStep(firstAtoms, secondAtoms);
|
|
|
|
- } catch (error) {
|
|
|
|
- /* Skip and move along */
|
|
|
|
- residue = this.residueIt.move();
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ this.loc = StructureElement.Location.create(structure, unit, -1 as ElementIndex);
|
|
|
|
+ }
|
|
|
|
|
|
- constructor(private structure: Structure, private unit: Unit.Atomic, private handler: Handler) {
|
|
|
|
- super(unit);
|
|
|
|
|
|
+ get hasNext() {
|
|
|
|
+ if (!this.data)
|
|
|
|
+ return false;
|
|
|
|
+ return this.residueIt.hasNext
|
|
|
|
+ ? true
|
|
|
|
+ : this.chainIt.hasNext;
|
|
|
|
+ }
|
|
|
|
|
|
- this.chainIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainAtomSegments, unit.elements);
|
|
|
|
- this.residueIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueAtomSegments, unit.elements);
|
|
|
|
|
|
+ move() {
|
|
|
|
+ if (this.residueIt.hasNext) {
|
|
|
|
+ return this.moveStep();
|
|
|
|
+ } else {
|
|
|
|
+ this.residueIt.setSegment(this.chainIt.move());
|
|
|
|
+ return this.moveStep();
|
|
}
|
|
}
|
|
-
|
|
|
|
- private chainIt: Segmentation.SegmentIterator<ChainIndex>;
|
|
|
|
- private residueIt: Segmentation.SegmentIterator<ResidueIndex>;
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|