Browse Source

added support to build custom assembly from symmetry operations and asym ids

Alexander Rose 5 years ago
parent
commit
131cc606f0

+ 69 - 17
src/mol-model/structure/structure/symmetry.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2020 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>
@@ -11,10 +11,11 @@ import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/
 import { Vec3, Mat4 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext, Task } from '../../../mol-task';
 import { Symmetry, Model } from '../model';
-import { QueryContext, StructureSelection } from '../query';
+import { QueryContext, StructureSelection, Queries as Q } from '../query';
 import Structure from './structure';
 import Unit from './unit';
 import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
+import StructureProperties from './properties';
 
 namespace StructureSymmetry {
     export function buildAssembly(structure: Structure, asmName: string) {
@@ -48,6 +49,40 @@ namespace StructureSymmetry {
         });
     }
 
+    export type Generators = { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
+
+    export function buildSymmetryAssembly(structure: Structure, generators: Generators, symmetry: Symmetry) {
+        return Task.create('Build Symmetry Assembly', async ctx => {
+            const models = structure.models;
+            if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.');
+
+            const modelCenter = Vec3() // Model.getCenter(models[0])
+            const assembler = Structure.Builder({ label: structure.label });
+
+            const queryCtx = new QueryContext(structure);
+
+            for (const g of generators) {
+                const selector = getSelector(g.asymIds);
+                const selection = selector(queryCtx);
+                if (StructureSelection.structureCount(selection) === 0) {
+                    continue;
+                }
+                const { units } = StructureSelection.unionStructure(selection);
+
+                for (const { index, shift: [i, j, k] } of g.operators) {
+                    const operators = getOperatorsForIndex(symmetry, index, i, j, k, modelCenter)
+                    for (const unit of units) {
+                        for (const op of operators) {
+                            assembler.addWithOperator(unit, op);
+                        }
+                    }
+                }
+            }
+
+            return assembler.getStructure();
+        });
+    }
+
     export function builderSymmetryMates(structure: Structure, radius: number) {
         return Task.create('Find Symmetry Mates', ctx => findMatesRadius(ctx, structure, radius));
     }
@@ -96,7 +131,35 @@ namespace StructureSymmetry {
     }
 }
 
-function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
+function getSelector(asymIds: string[]) {
+    return Q.generators.atoms({ chainTest: Q.pred.and(
+        Q.pred.eq(ctx => StructureProperties.unit.operator_name(ctx.element), SymmetryOperator.DefaultName),
+        Q.pred.inSet(ctx => StructureProperties.chain.label_asym_id(ctx.element), asymIds)
+    )});
+}
+
+function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: number, k: number, modelCenter: Vec3) {
+    const { spacegroup, ncsOperators } = symmetry;
+    const operators: SymmetryOperator[] = []
+
+    const { toFractional } = spacegroup.cell
+    const ref = Vec3.transformMat4(Vec3(), modelCenter, toFractional)
+
+    const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, index, i, j, k, ref)
+    if (ncsOperators && ncsOperators.length) {
+        for (let u = 0, ul = ncsOperators.length; u < ul; ++u) {
+            const ncsOp = ncsOperators![u]
+            const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix)
+            const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
+            operators.push(operator)
+        }
+    } else {
+        operators.push(symOp)
+    }
+    return operators
+}
+
+function getOperatorsForRange(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) {
     const { spacegroup, ncsOperators } = symmetry;
     const ncsCount = (ncsOperators && ncsOperators.length) || 0
     const operators: SymmetryOperator[] = [];
@@ -117,18 +180,7 @@ function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCente
                 for (let k = ijkMin[2]; k <= ijkMax[2]; k++) {
                     // check if we have added identity as the 1st operator.
                     if (!ncsCount && op === 0 && i === 0 && j === 0 && k === 0) continue;
-
-                    const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, op, i, j, k, ref)
-                    if (ncsCount) {
-                        for (let u = 0; u < ncsCount; ++u) {
-                            const ncsOp = ncsOperators![u]
-                            const matrix = Mat4.mul(Mat4.zero(), symOp.matrix, ncsOp.matrix)
-                            const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp);
-                            operators[operators.length] = operator;
-                        }
-                    } else {
-                        operators[operators.length] = symOp;
-                    }
+                    operators.push(...getOperatorsForIndex(symmetry, op, i, j, k, ref))
                 }
             }
         }
@@ -142,7 +194,7 @@ function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) {
     }
     symmetry._operators_333 = {
         ref: Vec3.clone(ref),
-        operators: getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref)
+        operators: getOperatorsForRange(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref)
     };
     return symmetry._operators_333.operators;
 }
@@ -181,7 +233,7 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM
     if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
 
     const modelCenter = Model.getCenter(models[0])
-    const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter);
+    const operators = getOperatorsForRange(symmetry, ijkMin, ijkMax, modelCenter);
     return assembleOperators(structure, operators);
 }
 

+ 49 - 4
src/mol-plugin/state/representation/model.ts

@@ -1,12 +1,13 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 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 { Model, Structure, StructureSymmetry } from '../../../mol-model/structure';
 import { stringToWords } from '../../../mol-util/string';
-import { SpacegroupCell } from '../../../mol-math/geometry';
+import { SpacegroupCell, Spacegroup } from '../../../mol-math/geometry';
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
 import { Vec3 } from '../../../mol-math/linear-algebra';
 import { RuntimeContext } from '../../../mol-task';
@@ -14,14 +15,33 @@ import { PluginContext } from '../../context';
 import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry';
 import { PluginStateObject as SO } from '../objects';
 import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry';
+import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif';
 
 export namespace ModelStructureRepresentation {
-    export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') {
+    export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') {
         const symmetry = model && ModelSymmetry.Provider.get(model)
 
         const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : [];
         const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell);
 
+        const operatorOptions: [number, string][] = []
+        if (symmetry) {
+            const { operators } = symmetry.spacegroup
+            for (let i = 0, il = operators.length; i < il; i++) {
+                operatorOptions.push([i, `${i + 1}: ${Spacegroup.getOperatorXyz(operators[i])}`])
+            }
+        }
+
+        const asymIdsOptions: [string, string][] = []
+        if (model && MmcifFormat.is(model?.sourceData)) {
+            // TODO make generally available for models, also include auth_asym_id
+            const { struct_asym } = model.sourceData.data.db
+            for (let i = 0, il = struct_asym._rowCount; i < il; ++i) {
+                const id = struct_asym.id.value(i)
+                asymIdsOptions.push([id, id])
+            }
+        }
+
         const modes = {
             deposited: PD.EmptyGroup(),
             assembly: PD.Group({
@@ -35,6 +55,19 @@ export namespace ModelStructureRepresentation {
             'symmetry': PD.Group({
                 ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }),
                 ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
+            }, { isFlat: true }),
+            'symmetry-assembly': PD.Group({
+                generators: PD.ObjectList({
+                    operators: PD.ObjectList({
+                        index: PD.Select(0, operatorOptions),
+                        shift: PD.Vec3(Vec3(), { label: 'IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } })
+                    }, e => `${e.index + 1}_${e.shift.map(a => a + 5).join('')}`, {
+                        defaultValue: [] as { index: number, shift: Vec3 }[]
+                    }),
+                    asymIds: PD.MultiSelect([] as string[], asymIdsOptions)
+                }, e => `${e.asymIds.length} asym ids, ${e.operators.length} operators`, {
+                    defaultValue: [] as { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[]
+                })
             }, { isFlat: true })
         };
 
@@ -49,6 +82,7 @@ export namespace ModelStructureRepresentation {
         if (showSymm) {
             options.push(['symmetry-mates', 'Symmetry Mates']);
             options.push(['symmetry', 'Symmetry (indices)']);
+            options.push(['symmetry-assembly', 'Symmetry (assembly)']);
         }
 
         return {
@@ -105,8 +139,16 @@ export namespace ModelStructureRepresentation {
         return new SO.Molecule.Structure(s, props);
     }
 
+    async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry) {
+        const base = Structure.ofModel(model);
+        const s = await StructureSymmetry.buildSymmetryAssembly(base, generators, symmetry).runInContext(ctx);
+        const props = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) };
+        return new SO.Molecule.Structure(s, props);
+    }
+
     export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> {
-        if (!params || params.name === 'deposited') {
+        const symmetry = ModelSymmetry.Provider.get(model)
+        if (!symmetry || !params || params.name === 'deposited') {
             const s = Structure.ofModel(model);
             return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) });
         }
@@ -119,6 +161,9 @@ export namespace ModelStructureRepresentation {
         if (params.name === 'symmetry-mates') {
             return buildSymmetryMates(ctx, model, params.params.radius)
         }
+        if (params.name === 'symmetry-assembly') {
+            return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry)
+        }
 
         throw new Error(`Unknown represetation type: ${(params as any).name}`);
     }