|
@@ -2,22 +2,26 @@
|
|
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
* Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
|
|
*
|
|
*
|
|
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
* @author Alexander Rose <alexander.rose@weirdbyte.de>
|
|
|
|
+ * @author David Sehnal <david.sehnal@gmail.com>
|
|
*/
|
|
*/
|
|
|
|
|
|
import { OrderedSet } from '../../../../mol-data/int';
|
|
import { OrderedSet } from '../../../../mol-data/int';
|
|
import Unit from '../unit';
|
|
import Unit from '../unit';
|
|
import { Loci } from './loci';
|
|
import { Loci } from './loci';
|
|
import { Location } from './location';
|
|
import { Location } from './location';
|
|
|
|
+import { ChainIndex } from '../../model/indexing';
|
|
|
|
|
|
export interface Stats {
|
|
export interface Stats {
|
|
elementCount: number
|
|
elementCount: number
|
|
conformationCount: number
|
|
conformationCount: number
|
|
residueCount: number
|
|
residueCount: number
|
|
|
|
+ chainCount: number
|
|
unitCount: number
|
|
unitCount: number
|
|
|
|
|
|
firstElementLoc: Location
|
|
firstElementLoc: Location
|
|
firstConformationLoc: Location
|
|
firstConformationLoc: Location
|
|
firstResidueLoc: Location
|
|
firstResidueLoc: Location
|
|
|
|
+ firstChainLoc: Location
|
|
firstUnitLoc: Location
|
|
firstUnitLoc: Location
|
|
}
|
|
}
|
|
|
|
|
|
@@ -27,15 +31,22 @@ export namespace Stats {
|
|
elementCount: 0,
|
|
elementCount: 0,
|
|
conformationCount: 0,
|
|
conformationCount: 0,
|
|
residueCount: 0,
|
|
residueCount: 0,
|
|
|
|
+ chainCount: 0,
|
|
unitCount: 0,
|
|
unitCount: 0,
|
|
|
|
|
|
firstElementLoc: Location.create(),
|
|
firstElementLoc: Location.create(),
|
|
firstConformationLoc: Location.create(),
|
|
firstConformationLoc: Location.create(),
|
|
firstResidueLoc: Location.create(),
|
|
firstResidueLoc: Location.create(),
|
|
|
|
+ firstChainLoc: Location.create(),
|
|
firstUnitLoc: Location.create(),
|
|
firstUnitLoc: Location.create(),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function addCountHelper<K>(map: Map<K, number>, key: K, inc: number) {
|
|
|
|
+ const count = map.get(key) || 0
|
|
|
|
+ map.set(key, count + inc)
|
|
|
|
+ }
|
|
|
|
+
|
|
function handleElement(stats: Stats, element: Loci['elements'][0]) {
|
|
function handleElement(stats: Stats, element: Loci['elements'][0]) {
|
|
const { indices, unit } = element
|
|
const { indices, unit } = element
|
|
const { elements } = unit
|
|
const { elements } = unit
|
|
@@ -43,10 +54,6 @@ export namespace Stats {
|
|
|
|
|
|
const lociResidueAltIdCounts = new Map<string, number>()
|
|
const lociResidueAltIdCounts = new Map<string, number>()
|
|
const residueAltIdCounts = new Map<string, number>()
|
|
const residueAltIdCounts = new Map<string, number>()
|
|
- const addCount = (map: Map<string, number>, altId: string) => {
|
|
|
|
- const count = map.get(altId) || 0
|
|
|
|
- map.set(altId, count + 1)
|
|
|
|
- }
|
|
|
|
|
|
|
|
if (size > 0) {
|
|
if (size > 0) {
|
|
Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
|
|
Location.set(stats.firstElementLoc, unit, elements[OrderedSet.start(indices)])
|
|
@@ -72,13 +79,13 @@ export namespace Stats {
|
|
let j = 0
|
|
let j = 0
|
|
const eI = elements[OrderedSet.getAt(indices, i)]
|
|
const eI = elements[OrderedSet.getAt(indices, i)]
|
|
const rI = index[eI]
|
|
const rI = index[eI]
|
|
- addCount(lociResidueAltIdCounts, label_alt_id.value(eI))
|
|
|
|
|
|
+ addCountHelper(lociResidueAltIdCounts, label_alt_id.value(eI), 1)
|
|
++i
|
|
++i
|
|
++j
|
|
++j
|
|
while (i < size) {
|
|
while (i < size) {
|
|
const eI = elements[OrderedSet.getAt(indices, i)]
|
|
const eI = elements[OrderedSet.getAt(indices, i)]
|
|
if (index[eI] !== rI) break
|
|
if (index[eI] !== rI) break
|
|
- addCount(lociResidueAltIdCounts, label_alt_id.value(eI))
|
|
|
|
|
|
+ addCountHelper(lociResidueAltIdCounts, label_alt_id.value(eI), 1)
|
|
++i
|
|
++i
|
|
++j
|
|
++j
|
|
}
|
|
}
|
|
@@ -93,7 +100,7 @@ export namespace Stats {
|
|
// partial residue
|
|
// partial residue
|
|
residueAltIdCounts.clear()
|
|
residueAltIdCounts.clear()
|
|
for (let l = offsets[rI], _l = offsets[rI + 1]; l < _l; ++l) {
|
|
for (let l = offsets[rI], _l = offsets[rI + 1]; l < _l; ++l) {
|
|
- addCount(residueAltIdCounts, label_alt_id.value(l))
|
|
|
|
|
|
+ addCountHelper(residueAltIdCounts, label_alt_id.value(l), 1)
|
|
}
|
|
}
|
|
// check if shared atom count match
|
|
// check if shared atom count match
|
|
if (residueAltIdCounts.get('') === lociResidueAltIdCounts.get('')) {
|
|
if (residueAltIdCounts.get('') === lociResidueAltIdCounts.get('')) {
|
|
@@ -125,10 +132,180 @@ export namespace Stats {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ function handleUnitChainsSimple(stats: Stats, element: Loci['elements'][0]) {
|
|
|
|
+ const { indices, unit } = element;
|
|
|
|
+ const size = OrderedSet.size(indices);
|
|
|
|
+ if (size === 0) return;
|
|
|
|
+
|
|
|
|
+ const { elements } = unit;
|
|
|
|
+
|
|
|
|
+ if (!Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
|
|
|
|
+ if (size === elements.length) {
|
|
|
|
+ stats.chainCount += 1;
|
|
|
|
+ if (stats.chainCount === 1) {
|
|
|
|
+ Location.set(stats.firstChainLoc, unit, elements[OrderedSet.start(indices)]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const segments = Unit.isAtomic(unit)
|
|
|
|
+ ? unit.model.atomicHierarchy.chainAtomSegments
|
|
|
|
+ : Unit.isSpheres(unit)
|
|
|
|
+ ? unit.model.coarseHierarchy.spheres.chainElementSegments
|
|
|
|
+ : Unit.isGaussians(unit)
|
|
|
|
+ ? unit.model.coarseHierarchy.gaussians.chainElementSegments
|
|
|
|
+ : void 0;
|
|
|
|
+
|
|
|
|
+ if (!segments) {
|
|
|
|
+ console.warn('StructureElement loci stats: unknown unit type');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const { index, offsets } = segments;
|
|
|
|
+ let i = 0;
|
|
|
|
+ while (i < size) {
|
|
|
|
+ let j = 0
|
|
|
|
+ const eI = elements[OrderedSet.getAt(indices, i)];
|
|
|
|
+ const cI = index[eI];
|
|
|
|
+ ++i;
|
|
|
|
+ ++j;
|
|
|
|
+ while (i < size) {
|
|
|
|
+ const eI = elements[OrderedSet.getAt(indices, i)];
|
|
|
|
+ if (index[eI] !== cI) break;
|
|
|
|
+ ++i;
|
|
|
|
+ ++j;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (offsets[cI + 1] - offsets[cI] === j) {
|
|
|
|
+ // full chain
|
|
|
|
+ stats.chainCount += 1;
|
|
|
|
+ if (stats.chainCount === 1) {
|
|
|
|
+ Location.set(stats.firstChainLoc, unit, elements[offsets[cI]]);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ function handleUnitChainsPartitioned(stats: Stats, lociElements: Loci['elements'], start: number, end: number) {
|
|
|
|
+ let element = lociElements[start];
|
|
|
|
+
|
|
|
|
+ // all the elements have the same model since they are part of the same group so this is ok.
|
|
|
|
+ const segments = Unit.isAtomic(element.unit)
|
|
|
|
+ ? element.unit.model.atomicHierarchy.chainAtomSegments
|
|
|
|
+ : Unit.isSpheres(element.unit)
|
|
|
|
+ ? element.unit.model.coarseHierarchy.spheres.chainElementSegments
|
|
|
|
+ : Unit.isGaussians(element.unit)
|
|
|
|
+ ? element.unit.model.coarseHierarchy.gaussians.chainElementSegments
|
|
|
|
+ : void 0;
|
|
|
|
+
|
|
|
|
+ if (!segments) {
|
|
|
|
+ console.warn('StructureElement loci stats: unknown unit type');
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const { index, offsets } = segments;
|
|
|
|
+
|
|
|
|
+ const chainCounts = new Map<ChainIndex, number>();
|
|
|
|
+
|
|
|
|
+ for (let elIndex = start; elIndex < end; elIndex++) {
|
|
|
|
+ element = lociElements[elIndex];
|
|
|
|
+
|
|
|
|
+ const { indices, unit } = element
|
|
|
|
+ const size = OrderedSet.size(indices);
|
|
|
|
+ if (size === 0) continue;
|
|
|
|
+
|
|
|
|
+ const { elements } = unit;
|
|
|
|
+
|
|
|
|
+ if (!Unit.Traits.is(unit.traits, Unit.Trait.MultiChain)) {
|
|
|
|
+ const eI = elements[OrderedSet.start(indices)];
|
|
|
|
+ addCountHelper(chainCounts, index[eI], elements.length);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let i = 0;
|
|
|
|
+ while (i < size) {
|
|
|
|
+ let j = 0
|
|
|
|
+ const eI = elements[OrderedSet.getAt(indices, i)];
|
|
|
|
+ const cI = index[eI];
|
|
|
|
+ ++i;
|
|
|
|
+ ++j;
|
|
|
|
+ while (i < size) {
|
|
|
|
+ const eI = elements[OrderedSet.getAt(indices, i)];
|
|
|
|
+ if (index[eI] !== cI) break;
|
|
|
|
+ ++i;
|
|
|
|
+ ++j;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ addCountHelper(chainCounts, cI, j);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ let firstCI = -1;
|
|
|
|
+ chainCounts.forEach((count, cI) => {
|
|
|
|
+ if (offsets[cI + 1] - offsets[cI] === count) {
|
|
|
|
+ // full chain
|
|
|
|
+ stats.chainCount += 1
|
|
|
|
+ if (stats.chainCount === 1) {
|
|
|
|
+ firstCI = cI;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ });
|
|
|
|
+
|
|
|
|
+ if (firstCI < 0) return;
|
|
|
|
+
|
|
|
|
+ for (let elIndex = start; elIndex < end; elIndex++) {
|
|
|
|
+ element = lociElements[elIndex];
|
|
|
|
+
|
|
|
|
+ const { indices, unit } = element
|
|
|
|
+ const size = OrderedSet.size(indices);
|
|
|
|
+ if (size === 0) continue;
|
|
|
|
+
|
|
|
|
+ const { elements } = unit;
|
|
|
|
+
|
|
|
|
+ let i = 0;
|
|
|
|
+ while (i < size) {
|
|
|
|
+ const eI = elements[OrderedSet.getAt(indices, i)];
|
|
|
|
+ const cI = index[eI];
|
|
|
|
+ if (cI === firstCI) {
|
|
|
|
+ Location.set(stats.firstChainLoc, unit, elements[offsets[cI]]);
|
|
|
|
+ return;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
export function ofLoci(loci: Loci) {
|
|
export function ofLoci(loci: Loci) {
|
|
const stats = create()
|
|
const stats = create()
|
|
|
|
+ let hasPartitions = false;
|
|
if (!Loci.isEmpty(loci)) {
|
|
if (!Loci.isEmpty(loci)) {
|
|
- for (const e of loci.elements) handleElement(stats, e)
|
|
|
|
|
|
+ for (const e of loci.elements) {
|
|
|
|
+ handleElement(stats, e)
|
|
|
|
+ if (!Unit.Traits.is(e.unit.traits, Unit.Trait.Patitioned)) {
|
|
|
|
+ handleUnitChainsSimple(stats, e);
|
|
|
|
+ } else {
|
|
|
|
+ hasPartitions = true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (hasPartitions) {
|
|
|
|
+ for (let i = 0, len = loci.elements.length; i < len; i++) {
|
|
|
|
+ const e = loci.elements[i];
|
|
|
|
+ if (!Unit.Traits.is(e.unit.traits, Unit.Trait.Patitioned)) continue;
|
|
|
|
+
|
|
|
|
+ const start = i;
|
|
|
|
+ while (i < len && loci.elements[i].unit.chainGroupId === e.unit.chainGroupId) {
|
|
|
|
+ i++;
|
|
|
|
+ }
|
|
|
|
+ const end = i;
|
|
|
|
+ i--;
|
|
|
|
+ if (end - start === 1) {
|
|
|
|
+ handleUnitChainsSimple(stats, e);
|
|
|
|
+ } else {
|
|
|
|
+ handleUnitChainsPartitioned(stats, loci.elements, start, end);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
}
|
|
}
|
|
return stats
|
|
return stats
|
|
}
|
|
}
|