Browse Source

added StructureFromTrajectory, improved multi-model structures

Alexander Rose 5 years ago
parent
commit
13cd6e82ba

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

@@ -71,7 +71,7 @@ function getTransforms(results: Ingredient['results']) {
 }
 
 function getAssembly(transforms: Mat4[], structure: Structure) {
-    const builder = Structure.Builder(void 0, void 0)
+    const builder = Structure.Builder()
     const { units } = structure;
 
     for (let i = 0, il = transforms.length; i < il; ++i) {
@@ -115,7 +115,7 @@ export function createStructureFromCellPack(ingredients: Packing['ingredients'],
             if (s) structures.push(s)
         }
 
-        const builder = Structure.Builder(void 0, void 0)
+        const builder = Structure.Builder()
         let offsetInvariantId = 0
         for (const s of structures) {
             let maxInvariantId = 0

+ 1 - 1
src/mol-model/structure/query/queries/filters.ts

@@ -39,7 +39,7 @@ export function first(query: StructureQuery): StructureQuery {
         if (sel.kind === 'singletons') {
             if (sel.structure.elementCount > 0) {
                 const u = sel.structure.units[0];
-                const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], ctx.inputStructure);
+                const s = Structure.create([u.getChild(SortedArray.ofSingleton(u.elements[0]))], { parent: ctx.inputStructure });
                 ret.add(s);
             }
         } else {

+ 1 - 1
src/mol-model/structure/query/queries/generators.ts

@@ -220,7 +220,7 @@ function atomGroupsGrouped({ unitTest, entityTest, chainTest, residueTest, atomT
 function getRingStructure(unit: Unit.Atomic, ring: UnitRing, inputStructure: Structure) {
     const elements = new Int32Array(ring.length) as any as ElementIndex[];
     for (let i = 0, _i = ring.length; i < _i; i++) elements[i] = unit.elements[ring[i]];
-    return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], inputStructure);
+    return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))], { parent: inputStructure });
 }
 
 export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery {

+ 1 - 1
src/mol-model/structure/query/selection.ts

@@ -135,7 +135,7 @@ namespace StructureSelection {
                 const { elements } = unit;
                 for (let i = 0, _i = elements.length; i < _i; i++) {
                     // TODO: optimize this somehow???
-                    const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], sel.source);
+                    const s = Structure.create([unit.getChild(SortedArray.ofSingleton(elements[i]))], { parent: sel.source });
                     fn(s, idx++);
                 }
             }

+ 2 - 2
src/mol-model/structure/query/utils/structure-set.ts

@@ -80,7 +80,7 @@ export function structureIntersect(sA: Structure, sB: Structure): Structure {
         }
     }
 
-    return Structure.create(units, sA.parent || sB.parent);
+    return Structure.create(units, { parent: sA.parent || sB.parent });
 }
 
 export function structureSubtract(a: Structure, b: Structure): Structure {
@@ -103,5 +103,5 @@ export function structureSubtract(a: Structure, b: Structure): Structure {
         }
     }
 
-    return Structure.create(units, a.parent || b.parent);
+    return Structure.create(units, { parent: a.parent || b.parent });
 }

+ 1 - 1
src/mol-model/structure/structure/element.ts

@@ -651,7 +651,7 @@ namespace StructureElement {
                     }
                 }
             }
-            return Structure.create(units, parent)
+            return Structure.create(units, { parent })
         }
 
         export function areEqual(a: Query, b: Query) {

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2019 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>
@@ -44,6 +44,8 @@ class Structure {
         carbohydrates?: Carbohydrates,
         models?: ReadonlyArray<Model>,
         model?: Model,
+        masterModel?: Model,
+        representativeModel?: Model,
         uniqueResidueNames?: Set<string>,
         entityIndices?: ReadonlyArray<EntityIndex>,
         uniqueAtomicResidueIndices?: ReadonlyMap<UUID, ReadonlyArray<ResidueIndex>>,
@@ -205,15 +207,30 @@ class Structure {
             || (this._props.uniqueAtomicResidueIndices = getUniqueAtomicResidueIndices(this));
     }
 
-    /** If the structure is based on a single model, return it. Otherwise throw an exception. */
+    /**
+     * If the structure is based on a single model or has a master-/representative-model, return it.
+     * Otherwise throw an exception.
+     */
     get model(): Model {
         if (this._props.model) return this._props.model;
+        if (this._props.representativeModel) return this._props.representativeModel;
+        if (this._props.masterModel) return this._props.masterModel;
         const models = this.models;
-        if (models.length > 1) throw new Error('The structre is based on multiple models.');
+        if (models.length > 1) {
+            throw new Error('The structure is based on multiple models and has neither a master- nor a representative-model.');
+        }
         this._props.model = models[0];
         return this._props.model;
     }
 
+    get masterModel(): Model | undefined {
+        return this._props.masterModel
+    }
+
+    get representativeModel(): Model | undefined {
+        return this._props.representativeModel
+    }
+
     hasElement(e: StructureElement) {
         if (!this.unitMap.has(e.unit.id)) return false;
         return SortedArray.has(this.unitMap.get(e.unit.id).elements, e.element);
@@ -243,12 +260,19 @@ class Structure {
         return map;
     }
 
-    constructor(units: ArrayLike<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator) {
+    constructor(units: ArrayLike<Unit>, props: Structure.Props = {}) {
         this.unitMap = this.initUnits(units);
         this.units = units as ReadonlyArray<Unit>;
-        if (parent) this._props.parent = parent.parent || parent;
-        if (coordinateSystem) this._props.coordinateSystem = coordinateSystem;
-        else if (parent) this._props.coordinateSystem = parent.coordinateSystem;
+        if (props.parent) this._props.parent = props.parent.parent || props.parent;
+
+        if (props.coordinateSystem) this._props.coordinateSystem = props.coordinateSystem;
+        else if (props.parent) this._props.coordinateSystem = props.parent.coordinateSystem;
+
+        if (props.masterModel) this._props.masterModel = props.masterModel;
+        else if (props.parent) this._props.masterModel = props.parent.masterModel;
+
+        if (props.representativeModel) this._props.representativeModel = props.representativeModel;
+        else if (props.parent) this._props.representativeModel = props.parent.representativeModel;
     }
 }
 
@@ -339,7 +363,16 @@ function getUniqueAtomicResidueIndices(structure: Structure): ReadonlyMap<UUID,
 }
 
 namespace Structure {
-    export const Empty = new Structure([], void 0, void 0);
+    export const Empty = new Structure([]);
+
+    export interface Props {
+        parent?: Structure
+        coordinateSystem?: SymmetryOperator
+        /** Master model for structures of a protein model and multiple ligand models */
+        masterModel?: Model
+        /** Representative model for structures of a model trajectory */
+        representativeModel?: Model
+    }
 
     /** Represents a single structure */
     export interface Loci {
@@ -366,8 +399,28 @@ namespace Structure {
         return a.structure === b.structure
     }
 
-    export function create(units: ReadonlyArray<Unit>, parent: Structure | undefined, coordinateSystem?: SymmetryOperator): Structure {
-        return new Structure(units, parent, coordinateSystem);
+    export function create(units: ReadonlyArray<Unit>, props?: Props): Structure {
+        return new Structure(units, props);
+    }
+
+    export function ofTrajectory(trajectory: ReadonlyArray<Model>): Structure {
+        if (trajectory.length === 0) return Empty
+
+        const units: Unit[] = [];
+
+        let count = 0
+        for (let i = 0, il = trajectory.length; i < il; ++i) {
+            const structure = ofModel(trajectory[i])
+            for (let j = 0, jl = structure.units.length; j < jl; ++j) {
+                const u = structure.units[j]
+                const invariantId = u.invariantId + count
+                const newUnit = Unit.create(units.length, invariantId, u.kind, u.model, u.conformation.operator, u.elements)
+                units.push(newUnit)
+            }
+            count = units.length
+        }
+
+        return create(units, { representativeModel: trajectory[0] });
     }
 
     /**
@@ -378,7 +431,7 @@ namespace Structure {
      */
     export function ofModel(model: Model): Structure {
         const chains = model.atomicHierarchy.chainAtomSegments;
-        const builder = new StructureBuilder(void 0, void 0);
+        const builder = new StructureBuilder();
 
         for (let c = 0 as ChainIndex; c < chains.count; c++) {
             const start = chains.offsets[c];
@@ -496,7 +549,7 @@ namespace Structure {
 
         const cs = s.coordinateSystem;
         const newCS = SymmetryOperator.compose(SymmetryOperator.create(cs.name, transform, cs.assembly, cs.ncsId, cs.hkl), cs);
-        return new Structure(units, s, newCS);
+        return new Structure(units, { parent: s, coordinateSystem: newCS });
     }
 
     export class StructureBuilder {
@@ -517,20 +570,20 @@ namespace Structure {
         }
 
         getStructure(): Structure {
-            return create(this.units, this.parent, this.coordinateSystem);
+            return create(this.units, this.props);
         }
 
         get isEmpty() {
             return this.units.length === 0;
         }
 
-        constructor(private parent: Structure | undefined, private coordinateSystem: SymmetryOperator | undefined) {
+        constructor(private props: Props = {}) {
 
         }
     }
 
-    export function Builder(parent: Structure | undefined, coordinateSystem: SymmetryOperator | undefined) {
-        return new StructureBuilder(parent, coordinateSystem);
+    export function Builder(props: Props = {}) {
+        return new StructureBuilder(props);
     }
 
     export function hashCode(s: Structure) {

+ 4 - 3
src/mol-model/structure/structure/symmetry.ts

@@ -24,7 +24,8 @@ namespace StructureSymmetry {
             const assembly = ModelSymmetry.findAssembly(models[0], asmName);
             if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
-            const assembler = Structure.Builder(void 0, SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] }));
+            const coordinateSystem = SymmetryOperator.create(assembly.id, Mat4.identity(), { id: assembly.id, operList: [] })
+            const assembler = Structure.Builder({ coordinateSystem });
 
             const queryCtx = new QueryContext(structure);
 
@@ -137,7 +138,7 @@ function getOperatorsCached333(symmetry: ModelSymmetry) {
 }
 
 function assembleOperators(structure: Structure, operators: ReadonlyArray<SymmetryOperator>) {
-    const assembler = Structure.Builder(void 0, void 0);
+    const assembler = Structure.Builder();
     const { units } = structure;
     for (const oper of operators) {
         for (const unit of units) {
@@ -179,7 +180,7 @@ async function findMatesRadius(ctx: RuntimeContext, structure: Structure, radius
     const operators = getOperatorsCached333(symmetry);
     const lookup = structure.lookup3d;
 
-    const assembler = Structure.Builder(void 0, void 0);
+    const assembler = Structure.Builder();
 
     const { units } = structure;
     const center = Vec3.zero();

+ 20 - 4
src/mol-model/structure/structure/unit/links/inter-compute.ts

@@ -1,7 +1,8 @@
 /**
- * Copyright (c) 2017 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2019 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 { LinkType } from '../../../model/types';
@@ -152,10 +153,15 @@ function findPairLinks(unitA: Unit.Atomic, unitB: Unit.Atomic, params: LinkCompu
     return bondCount;
 }
 
-function findLinks(structure: Structure, params: LinkComputationParameters) {
+export interface InterLinkComputationParameters extends LinkComputationParameters {
+    validUnitPair: (unitA: Unit, unitB: Unit) => boolean
+}
+
+function findLinks(structure: Structure, params: InterLinkComputationParameters) {
     const map = new Map<number, InterUnitBonds.UnitPairBonds[]>();
     if (!structure.units.some(u => Unit.isAtomic(u))) return new InterUnitBonds(map);
 
+    const { validUnitPair } = params;
     const lookup = structure.lookup3d;
     const imageCenter = Vec3.zero();
 
@@ -167,7 +173,7 @@ function findLinks(structure: Structure, params: LinkComputationParameters) {
         const closeUnits = lookup.findUnitIndices(imageCenter[0], imageCenter[1], imageCenter[2], bs.radius + MAX_RADIUS);
         for (let i = 0; i < closeUnits.count; i++) {
             const other = structure.units[closeUnits.indices[i]];
-            if (!Unit.isAtomic(other) || unit.id >= other.id) continue;
+            if (!Unit.isAtomic(other) || unit.id >= other.id || !validUnitPair(unit, other)) continue;
 
             if (other.elements.length >= unit.elements.length) findPairLinks(unit, other, params, map);
             else findPairLinks(other, unit, params, map);
@@ -177,10 +183,20 @@ function findLinks(structure: Structure, params: LinkComputationParameters) {
     return new InterUnitBonds(map);
 }
 
-function computeInterUnitBonds(structure: Structure, params?: Partial<LinkComputationParameters>): InterUnitBonds {
+function ValidUnitPair(structure: Structure) {
+    const { masterModel } = structure
+    if (masterModel) {
+        return (a: Unit, b: Unit) => a.model === b.model || a.model === masterModel || b.model === masterModel
+    } else {
+        return (a: Unit, b: Unit) => a.model === b.model
+    }
+}
+
+function computeInterUnitBonds(structure: Structure, params?: Partial<InterLinkComputationParameters>): InterUnitBonds {
     return findLinks(structure, {
         maxHbondLength: (params && params.maxHbondLength) || 1.15,
         forceCompute: !!(params && params.forceCompute),
+        validUnitPair: (params && params.validUnitPair) || ValidUnitPair(structure),
     });
 }
 

+ 1 - 1
src/mol-model/structure/structure/util/subset-builder.ts

@@ -90,7 +90,7 @@ export class StructureSubsetBuilder {
             newUnits[newUnits.length] = child;
         }
 
-        return Structure.create(newUnits, this.parent);
+        return Structure.create(newUnits, { parent: this.parent });
     }
 
     getStructure() {

+ 1 - 1
src/mol-model/structure/structure/util/unique-subset-builder.ts

@@ -85,7 +85,7 @@ export class StructureUniqueSubsetBuilder {
             newUnits[newUnits.length] = child;
         }
 
-        return Structure.create(newUnits, this.parent, this.parent.coordinateSystem);
+        return Structure.create(newUnits, { parent: this.parent });
     }
 
     get isEmpty() {

+ 1 - 0
src/mol-plugin/index.ts

@@ -44,6 +44,7 @@ export const DefaultPluginSpec: PluginSpec = {
         PluginSpec.Action(StateTransforms.Model.StructureSymmetryFromModel),
         PluginSpec.Action(TransformStructureConformation),
         PluginSpec.Action(StateTransforms.Model.StructureFromModel),
+        PluginSpec.Action(StateTransforms.Model.StructureFromTrajectory),
         PluginSpec.Action(StateTransforms.Model.ModelFromTrajectory),
         PluginSpec.Action(StateTransforms.Model.UserStructureSelection),
         PluginSpec.Action(StateTransforms.Representation.StructureRepresentation3D),

+ 5 - 5
src/mol-plugin/state/actions/structure.ts

@@ -199,9 +199,9 @@ function createSingleTrajectoryModel(sources: StateTransformer.Params<Download>[
         .apply(StateTransforms.Data.DownloadBlob, {
             sources: sources.map((src, i) => ({ id: '' + i, url: src.url, isBinary: src.isBinary })),
             maxConcurrency: 6
-        }).apply(StateTransforms.Data.ParseBlob, {
+        }, { state: { isGhost: true } }).apply(StateTransforms.Data.ParseBlob, {
             formats: sources.map((_, i) => ({ id: '' + i, format: 'cif' as 'cif' }))
-        })
+        }, { state: { isGhost: true } })
         .apply(StateTransforms.Model.TrajectoryFromBlob)
         .apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex: 0 });
 }
@@ -211,13 +211,13 @@ export function createModelTree(b: StateBuilder.To<PluginStateObject.Data.Binary
     switch (format) {
         case 'cif':
             parsed = b.apply(StateTransforms.Data.ParseCif, void 0, { state: { isGhost: true } })
-                .apply(StateTransforms.Model.TrajectoryFromMmCif, void 0, { state: { isGhost: true } })
+                .apply(StateTransforms.Model.TrajectoryFromMmCif)
             break
         case 'pdb':
-            parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB, void 0, { state: { isGhost: true } });
+            parsed = b.apply(StateTransforms.Model.TrajectoryFromPDB);
             break
         case 'gro':
-            parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO, void 0, { state: { isGhost: true } });
+            parsed = b.apply(StateTransforms.Model.TrajectoryFromGRO);
             break
         default:
             throw new Error('unsupported format')

+ 1 - 1
src/mol-plugin/state/transforms/helpers.ts

@@ -42,7 +42,7 @@ export function getStructureTransparency(structure: Structure, script: Script, v
  * Attaches ComputedSecondaryStructure property when unavailable in sourceData
  */
 export async function ensureSecondaryStructure(s: Structure) {
-    if (s.model && s.model.sourceData.kind === 'mmCIF') {
+    if (s.models.length === 1 && s.model && s.model.sourceData.kind === 'mmCIF') {
         if (!s.model.sourceData.data.struct_conf.id.isDefined && !s.model.sourceData.data.struct_sheet_range.id.isDefined) {
             await ComputedSecondaryStructure.attachFromCifOrCompute(s)
         }

+ 17 - 0
src/mol-plugin/state/transforms/model.ts

@@ -33,6 +33,7 @@ export { TrajectoryFromMmCif };
 export { TrajectoryFromPDB };
 export { TrajectoryFromGRO };
 export { ModelFromTrajectory };
+export { StructureFromTrajectory };
 export { StructureFromModel };
 export { StructureAssemblyFromModel };
 export { StructureSymmetryFromModel };
@@ -161,6 +162,22 @@ const ModelFromTrajectory = PluginStateTransform.BuiltIn({
     }
 });
 
+type StructureFromTrajectory = typeof StructureFromTrajectory
+const StructureFromTrajectory = PluginStateTransform.BuiltIn({
+    name: 'structure-from-trajectory',
+    display: { name: 'Structure from Trajectory', description: 'Create a molecular structure from a trajectory.' },
+    from: SO.Molecule.Trajectory,
+    to: SO.Molecule.Structure
+})({
+    apply({ a }) {
+        return Task.create('Build Structure', async ctx => {
+            const s = Structure.ofTrajectory(a.data);
+            const props = { label: a.data[0].label, description: s.elementCount === 1 ? '1 element' : `${s.elementCount} elements` };
+            return new SO.Molecule.Structure(s, props);
+        })
+    }
+});
+
 type StructureFromModel = typeof StructureFromModel
 const StructureFromModel = PluginStateTransform.BuiltIn({
     name: 'structure-from-model',