Ver Fonte

add unit.multiChain; tweaks to unit.chainGroupId

Alexander Rose há 5 anos atrás
pai
commit
ac94f0659d

+ 1 - 1
src/apps/viewer/extensions/cellpack/model.ts

@@ -268,7 +268,7 @@ export function createStructureFromCellPack(packing: CellPacking, baseUrl: strin
             for (const u of s.units) {
                 const invariantId = u.invariantId + offsetInvariantId
                 if (u.invariantId > maxInvariantId) maxInvariantId = u.invariantId
-                builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, invariantId)
+                builder.addUnit(u.kind, u.model, u.conformation.operator, u.elements, false, invariantId)
             }
             offsetInvariantId += maxInvariantId
         }

+ 2 - 0
src/mol-model/structure/structure/properties.ts

@@ -164,6 +164,8 @@ const entity = {
 
 const unit = {
     id: p(l => l.unit.id),
+    chainGroupId: p(l => l.unit.chainGroupId),
+    multiChain: p(l => l.unit.multiChain),
     object_primitive: p(l => l.unit.objectPrimitive),
     operator_name: p(l => l.unit.conformation.operator.name),
     model_index: p(l => l.unit.model.modelNum),

+ 27 - 16
src/mol-model/structure/structure/structure.ts

@@ -541,7 +541,7 @@ namespace Structure {
                 const u = structure.units[j]
                 const invariantId = u.invariantId + count
                 const chainGroupId = u.chainGroupId + count
-                const newUnit = Unit.create(units.length, invariantId, chainGroupId, u.kind, u.model, u.conformation.operator, u.elements)
+                const newUnit = Unit.create(units.length, invariantId, chainGroupId, u.multiChain, u.kind, u.model, u.conformation.operator, u.elements)
                 units.push(newUnit)
             }
             count = units.length
@@ -554,10 +554,12 @@ namespace Structure {
      * Construct a Structure from a model.
      *
      * Generally, a single unit corresponds to a single chain, with the exception
-     * of consecutive "single atom chains" with same entity id.
+     * of consecutive "single atom chains" with same entity_id and same auth_asym_id.
      */
     export function ofModel(model: Model): Structure {
         const chains = model.atomicHierarchy.chainAtomSegments;
+        const { index } = model.atomicHierarchy
+        const { auth_asym_id } = model.atomicHierarchy.chains
         const builder = new StructureBuilder({ label: model.label });
 
         for (let c = 0 as ChainIndex; c < chains.count; c++) {
@@ -567,27 +569,34 @@ namespace Structure {
             // note that it assumes there are no "zero atom residues"
             let singleAtomResidues = AtomicHierarchy.chainResidueCount(model.atomicHierarchy, c) === chains.offsets[c + 1] - chains.offsets[c]
 
-            // merge all consecutive "single atom chains" with same entity id
+            // merge all consecutive "single atom chains" with same entity_id and same auth_asym_id
+            let multiChain = false
             while (c + 1 < chains.count
                 && chains.offsets[c + 1] - chains.offsets[c] === 1
                 && chains.offsets[c + 2] - chains.offsets[c + 1] === 1
             ) {
-                c++;
                 singleAtomResidues = true
-                const e1 = model.atomicHierarchy.index.getEntityFromChain(c);
-                const e2 = model.atomicHierarchy.index.getEntityFromChain(c + 1 as ChainIndex);
+                const e1 = index.getEntityFromChain(c);
+                const e2 = index.getEntityFromChain(c + 1 as ChainIndex);
                 if (e1 !== e2) break
+
+                const a1 = auth_asym_id.value(c);
+                const a2 = auth_asym_id.value(c + 1);
+                if (a1 !== a2) break
+
+                multiChain = true
+                c++;
             }
 
             const elements = SortedArray.ofBounds(start as ElementIndex, chains.offsets[c + 1] as ElementIndex);
 
             if (singleAtomResidues) {
-                partitionAtomicUnitByAtom(model, elements, builder);
+                partitionAtomicUnitByAtom(model, elements, builder, multiChain);
             } else if (elements.length > 200000 || isWaterChain(model, c)) {
                 // split up very large chains e.g. lipid bilayers, micelles or water with explicit H
-                partitionAtomicUnitByResidue(model, elements, builder);
+                partitionAtomicUnitByResidue(model, elements, builder, multiChain);
             } else {
-                builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
+                builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements, multiChain);
             }
         }
 
@@ -609,23 +618,25 @@ namespace Structure {
         return model.entities.data.type.value(e) === 'water';
     }
 
-    function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder) {
+    function partitionAtomicUnitByAtom(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
         const { x, y, z } = model.atomicConformation;
         const lookup = GridLookup3D({ x, y, z, indices }, 8192);
         const { offset, count, array } = lookup.buckets;
 
+        builder.beginChainGroup();
         for (let i = 0, _i = offset.length; i < _i; i++) {
             const start = offset[i];
             const set = new Int32Array(count[i]);
             for (let j = 0, _j = count[i]; j < _j; j++) {
                 set[j] = indices[array[start + j]];
             }
-            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set));
+            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(set), multiChain);
         }
+        builder.endChainGroup();
     }
 
     // keeps atoms of residues together
-    function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder) {
+    function partitionAtomicUnitByResidue(model: Model, indices: SortedArray, builder: StructureBuilder, multiChain: boolean) {
         const { residueAtomSegments } = model.atomicHierarchy
 
         const startIndices: number[] = []
@@ -655,7 +666,7 @@ namespace Structure {
                     set[set.length] = l;
                 }
             }
-            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)));
+            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, SortedArray.ofSortedArray(new Int32Array(set)), multiChain);
         }
         builder.endChainGroup();
     }
@@ -664,7 +675,7 @@ namespace Structure {
         const { chainElementSegments } = elements;
         for (let cI = 0; cI < chainElementSegments.count; cI++) {
             const elements = SortedArray.ofBounds<ElementIndex>(chainElementSegments.offsets[cI], chainElementSegments.offsets[cI + 1]);
-            builder.addUnit(kind, model, SymmetryOperator.Default, elements);
+            builder.addUnit(kind, model, SymmetryOperator.Default, elements, false);
         }
     }
 
@@ -700,10 +711,10 @@ namespace Structure {
             this.inChainGroup = false;
         }
 
-        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, invariantId?: number): Unit {
+        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set, multiChain: boolean, invariantId?: number): Unit {
             if (invariantId === undefined) invariantId = this.invariantId()
             const chainGroupId = this.inChainGroup ? this.chainGroupId : ++this.chainGroupId;
-            const unit = Unit.create(this.units.length, invariantId, chainGroupId, kind, model, operator, elements);
+            const unit = Unit.create(this.units.length, invariantId, chainGroupId, multiChain, kind, model, operator, elements);
             this.units.push(unit);
             return unit;
         }

+ 17 - 12
src/mol-model/structure/structure/unit.ts

@@ -33,11 +33,11 @@ namespace Unit {
     export function isSpheres(u: Unit): u is Spheres { return u.kind === Kind.Spheres; }
     export function isGaussians(u: Unit): u is Gaussians { return u.kind === Kind.Gaussians; }
 
-    export function create(id: number, invariantId: number, chainGroupId: number, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
+    export function create(id: number, invariantId: number, chainGroupId: number, multiChain: boolean, kind: Kind, model: Model, operator: SymmetryOperator, elements: StructureElement.Set): Unit {
         switch (kind) {
-            case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, void 0), AtomicProperties());
-            case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), CoarseProperties());
-            case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), CoarseProperties());
+            case Kind.Atomic: return new Atomic(id, invariantId, chainGroupId, multiChain, model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation, void 0), AtomicProperties());
+            case Kind.Spheres: return createCoarse(id, invariantId, chainGroupId, multiChain, model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres, getSphereRadiusFunc(model)), CoarseProperties());
+            case Kind.Gaussians: return createCoarse(id, invariantId, chainGroupId, multiChain, model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians, getGaussianRadiusFunc(model)), CoarseProperties());
         }
     }
 
@@ -99,6 +99,7 @@ namespace Unit {
         /** invariant ID stays the same even if the Operator/conformation changes. */
         readonly invariantId: number,
         readonly chainGroupId: number,
+        readonly multiChain: boolean,
         readonly elements: StructureElement.Set,
         readonly model: Model,
         readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>,
@@ -142,6 +143,7 @@ namespace Unit {
         readonly invariantId: number;
         /** Used to identify a single chain split into multiple units. */
         readonly chainGroupId: number;
+        readonly multiChain: boolean;
         readonly elements: StructureElement.Set;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
@@ -155,12 +157,12 @@ namespace Unit {
 
         getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this;
-            return new Atomic(this.id, this.invariantId, this.chainGroupId, this.model, elements, this.conformation, AtomicProperties());
+            return new Atomic(this.id, this.invariantId, this.chainGroupId, this.multiChain, this.model, elements, this.conformation, AtomicProperties());
         }
 
         applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
             const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
-            return new Atomic(id, this.invariantId, this.chainGroupId, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
+            return new Atomic(id, this.invariantId, this.chainGroupId, this.multiChain, this.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation, this.conformation.r), this.props);
         }
 
         get lookup3d() {
@@ -224,10 +226,11 @@ namespace Unit {
             return this.model.atomicHierarchy.residueAtomSegments.index[this.elements[elementIndex]];
         }
 
-        constructor(id: number, invariantId: number, chainGroupId: number, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: AtomicProperties) {
+        constructor(id: number, invariantId: number, chainGroupId: number, multiChain: boolean, model: Model, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: AtomicProperties) {
             this.id = id;
             this.invariantId = invariantId;
             this.chainGroupId = chainGroupId;
+            this.multiChain = multiChain;
             this.model = model;
             this.elements = elements;
             this.conformation = conformation;
@@ -269,6 +272,7 @@ namespace Unit {
         readonly id: number;
         readonly invariantId: number;
         readonly chainGroupId: number;
+        readonly multiChain: boolean;
         readonly elements: StructureElement.Set;
         readonly model: Model;
         readonly conformation: SymmetryOperator.ArrayMapping<ElementIndex>;
@@ -280,12 +284,12 @@ namespace Unit {
 
         getChild(elements: StructureElement.Set): Unit {
             if (elements.length === this.elements.length) return this as any as Unit /** lets call this an ugly temporary hack */;
-            return createCoarse(this.id, this.invariantId, this.chainGroupId, this.model, this.kind, elements, this.conformation, CoarseProperties());
+            return createCoarse(this.id, this.invariantId, this.chainGroupId, this.multiChain, this.model, this.kind, elements, this.conformation, CoarseProperties());
         }
 
         applyOperator(id: number, operator: SymmetryOperator, dontCompose = false): Unit {
             const op = dontCompose ? operator : SymmetryOperator.compose(this.conformation.operator, operator);
-            const ret = createCoarse(id, this.invariantId, this.chainGroupId, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseConformation(), this.conformation.r), this.props);
+            const ret = createCoarse(id, this.invariantId, this.chainGroupId, this.multiChain, this.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseConformation(), this.conformation.r), this.props);
             // (ret as Coarse<K, C>)._lookup3d = this._lookup3d;
             return ret;
         }
@@ -314,12 +318,13 @@ namespace Unit {
             return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
         }
 
-        constructor(id: number, invariantId: number, chainGroupId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
+        constructor(id: number, invariantId: number, chainGroupId: number, multiChain: boolean, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties) {
             this.kind = kind;
             this.objectPrimitive = kind === Kind.Spheres ? 'sphere' : 'gaussian'
             this.id = id;
             this.invariantId = invariantId;
             this.chainGroupId = chainGroupId;
+            this.multiChain = multiChain;
             this.model = model;
             this.elements = elements;
             this.conformation = conformation;
@@ -343,8 +348,8 @@ namespace Unit {
         };
     }
 
-    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, chainGroupId: number, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): Unit {
-        return new Coarse(id, invariantId, chainGroupId, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */;
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, chainGroupId: number, multiChain: boolean, model: Model, kind: K, elements: StructureElement.Set, conformation: SymmetryOperator.ArrayMapping<ElementIndex>, props: CoarseProperties): Unit {
+        return new Coarse(id, invariantId, chainGroupId, multiChain, model, kind, elements, conformation, props) as any as Unit /** lets call this an ugly temporary hack */;
     }
 
     export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { }

+ 5 - 1
src/mol-theme/label.ts

@@ -171,7 +171,11 @@ function _atomicElementLabel(location: StructureElement.Location<Unit.Atomic>, g
             if (label_asym_id === auth_asym_id) {
                 label.push(`<b>${label_asym_id}</b>`)
             } else {
-                label.push(`<b>${label_asym_id}</b> <small>[auth</small> <b>${auth_asym_id}</b><small>]</small>`)
+                if (granularity === 'chain' && location.unit.multiChain) {
+                    label.push(`<small>[auth</small> <b>${auth_asym_id}</b><small>]</small>`)
+                } else {
+                    label.push(`<b>${label_asym_id}</b> <small>[auth</small> <b>${auth_asym_id}</b><small>]</small>`)
+                }
             }
     }