From 75097c670e973e673749a3aae31445d0f4b45dc5 Mon Sep 17 00:00:00 2001
From: David Sehnal <david.sehnal@gmail.com>
Date: Wed, 29 May 2019 13:03:19 +0200
Subject: [PATCH] wip superposition

---
 src/apps/basic-wrapper/helpers.ts             | 67 +++++++++++++++++++
 src/apps/basic-wrapper/index.html             |  9 ++-
 src/apps/basic-wrapper/index.ts               | 15 ++++-
 src/apps/basic-wrapper/superposition.ts       | 55 +++++++++++++++
 src/mol-math/geometry/symmetry-operator.ts    |  6 +-
 .../structure/structure/structure.ts          |  2 +-
 src/mol-plugin/state/transforms/model.ts      | 23 ++++++-
 7 files changed, 167 insertions(+), 10 deletions(-)
 create mode 100644 src/apps/basic-wrapper/helpers.ts
 create mode 100644 src/apps/basic-wrapper/superposition.ts

diff --git a/src/apps/basic-wrapper/helpers.ts b/src/apps/basic-wrapper/helpers.ts
new file mode 100644
index 000000000..bbe9e92ab
--- /dev/null
+++ b/src/apps/basic-wrapper/helpers.ts
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Mat4, Vec3 } from 'mol-math/linear-algebra';
+import { PluginContext } from 'mol-plugin/context';
+import { PluginStateObject as PSO } from 'mol-plugin/state/objects';
+import { StateTransforms } from 'mol-plugin/state/transforms';
+import { StructureRepresentation3DHelpers } from 'mol-plugin/state/transforms/representation';
+import { MolScriptBuilder as MS } from 'mol-script/language/builder';
+import { StateBuilder } from 'mol-state';
+type SupportedFormats = 'cif' | 'pdb'
+
+export namespace StateHelper {
+    export function download(b: StateBuilder.To<PSO.Root>, url: string, ref?: string) {
+        return b.apply(StateTransforms.Data.Download, { url, isBinary: false }, { ref });
+    }
+
+    export function getModel(b: StateBuilder.To<PSO.Data.Binary | PSO.Data.String>, format: SupportedFormats, modelIndex = 0) {
+        const parsed = format === 'cif'
+            ? b.apply(StateTransforms.Data.ParseCif).apply(StateTransforms.Model.TrajectoryFromMmCif)
+            : b.apply(StateTransforms.Model.TrajectoryFromPDB);
+
+        return parsed.apply(StateTransforms.Model.ModelFromTrajectory, { modelIndex });
+    }
+
+    export function structure(b: StateBuilder.To<PSO.Molecule.Model>) {
+        return b.apply(StateTransforms.Model.StructureFromModel, { tags: 'struct' })
+    };
+
+    export function selectChain(b: StateBuilder.To<PSO.Molecule.Structure>, auth_asym_id: string) {
+        const query = MS.struct.generator.atomGroups({
+            'chain-test': MS.core.rel.eq([MS.struct.atomProperty.macromolecular.auth_asym_id(), auth_asym_id])
+        })
+        return b.apply(StateTransforms.Model.StructureSelection, { query, label: `Chain ${auth_asym_id}` });
+    }
+
+    export function identityTransform(b: StateBuilder.To<PSO.Molecule.Structure>, m: Mat4) {
+        return b.apply(StateTransforms.Model.TransformStructureConformation,
+            { axis: Vec3.create(1, 0, 0), angle: 0, translation: Vec3.zero() },
+            { tags: 'transform' });
+    }
+
+    export function transform(b: StateBuilder.To<PSO.Molecule.Structure>, matrix: Mat4) {
+        return b.apply(StateTransforms.Model.TransformStructureConformationByMatrix, { matrix }, { tags: 'transform' });
+    }
+
+    export function assemble(b: StateBuilder.To<PSO.Molecule.Model>, id?: string) {
+        return b.apply(StateTransforms.Model.StructureAssemblyFromModel, { id: id || 'deposited' }, { tags: 'asm' })
+    }
+
+    export function visual(ctx: PluginContext, visualRoot: StateBuilder.To<PSO.Molecule.Structure>) {
+        visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-sequence' })
+            .apply(StateTransforms.Representation.StructureRepresentation3D,
+                StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'cartoon'), { tags: 'seq-visual' });
+        visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'atomic-het' })
+            .apply(StateTransforms.Representation.StructureRepresentation3D,
+                StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick'), { tags: 'het-visual' });
+        // visualRoot.apply(StateTransforms.Model.StructureComplexElement, { type: 'water' })
+        //     .apply(StateTransforms.Representation.StructureRepresentation3D,
+        //         StructureRepresentation3DHelpers.getDefaultParamsStatic(ctx, 'ball-and-stick', { alpha: 0.51 }), { tags: 'water-visual' });
+        return visualRoot;
+    }
+
+}
\ No newline at end of file
diff --git a/src/apps/basic-wrapper/index.html b/src/apps/basic-wrapper/index.html
index efa0b8c9f..1d68db266 100644
--- a/src/apps/basic-wrapper/index.html
+++ b/src/apps/basic-wrapper/index.html
@@ -75,8 +75,9 @@
 
             BasicMolStarWrapper.init('app' /** or document.getElementById('app') */);
             BasicMolStarWrapper.setBackground(0xffffff);
-            BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
-            BasicMolStarWrapper.toggleSpin();
+            // BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId });
+            // BasicMolStarWrapper.toggleSpin();
+
 
             addControl('Load Asym Unit', () => BasicMolStarWrapper.load({ url: url, format: format }));
             addControl('Load Assembly', () => BasicMolStarWrapper.load({ url: url, format: format, assemblyId: assemblyId }));
@@ -104,6 +105,10 @@
 
             addControl('Apply Stripes', () => BasicMolStarWrapper.coloring.applyStripes());
 
+            addHeader('Tests');
+
+            addControl('Static Super', () => BasicMolStarWrapper.tests.staticSuperposition());
+
             ////////////////////////////////////////////////////////
 
             function addControl(label, action) {
diff --git a/src/apps/basic-wrapper/index.ts b/src/apps/basic-wrapper/index.ts
index 17da1682a..fb4392f3d 100644
--- a/src/apps/basic-wrapper/index.ts
+++ b/src/apps/basic-wrapper/index.ts
@@ -15,7 +15,8 @@ import { PluginStateObject as PSO } from 'mol-plugin/state/objects';
 import { AnimateModelIndex } from 'mol-plugin/state/animation/built-in';
 import { StateBuilder } from 'mol-state';
 import { StripedResidues } from './coloring';
-import { BasicWrapperControls } from './controls';
+// import { BasicWrapperControls } from './controls';
+import { StaticSuperpositionTestData, buildStaticSuperposition } from './superposition';
 require('mol-plugin/skin/light.scss')
 
 type SupportedFormats = 'cif' | 'pdb'
@@ -33,8 +34,8 @@ class BasicWrapper {
                     showControls: false
                 },
                 controls: {
-                    left: 'none',
-                    right: BasicWrapperControls
+                    // left: 'none',
+                    // right: BasicWrapperControls
                 }
             }
         });
@@ -140,6 +141,14 @@ class BasicWrapper {
             await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
         }
     }
+
+    tests = {
+        staticSuperposition: async () => {
+            const state = this.plugin.state.dataState;
+            const tree = buildStaticSuperposition(this.plugin, StaticSuperpositionTestData);
+            await PluginCommands.State.Update.dispatch(this.plugin, { state, tree });
+        }
+    }
 }
 
 (window as any).BasicMolStarWrapper = new BasicWrapper();
\ No newline at end of file
diff --git a/src/apps/basic-wrapper/superposition.ts b/src/apps/basic-wrapper/superposition.ts
new file mode 100644
index 000000000..1961aab48
--- /dev/null
+++ b/src/apps/basic-wrapper/superposition.ts
@@ -0,0 +1,55 @@
+/**
+ * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+// TODO: move to an "example"
+
+import { PluginContext } from 'mol-plugin/context';
+import { Mat4 } from 'mol-math/linear-algebra';
+import { StateHelper } from './helpers';
+
+export type SuperpositionTestInput = {
+    pdbId: string,
+    auth_asym_id: string,
+    matrix: Mat4
+}[];
+
+// function getAxisAngleTranslation(m: Mat4) {
+//     const translation = Mat4.getTranslation(Vec3.zero(), m);
+//     const axis = Vec3.zero();
+//     const angle = 180 / Math.PI * Quat.getAxisAngle(axis, Mat4.getRotation(Quat.zero(), m));
+//     return { translation, axis, angle };
+// }
+
+export function buildStaticSuperposition(ctx: PluginContext, src: SuperpositionTestInput) {
+    const b = ctx.state.dataState.build().toRoot();
+    for (const s of src) {
+        StateHelper.visual(ctx,
+            StateHelper.transform(
+                StateHelper.selectChain(
+                    StateHelper.structure(
+                        StateHelper.getModel(StateHelper.download(b, `https://www.ebi.ac.uk/pdbe/static/entry/${s.pdbId}_updated.cif`), 'cif')),
+                    s.auth_asym_id
+                ),
+                s.matrix
+            )
+        );
+    }
+    return b;
+}
+
+export const StaticSuperpositionTestData: SuperpositionTestInput = [
+    { pdbId: '1aj5', auth_asym_id: 'A', matrix: Mat4.identity() },
+    { pdbId: '1df0', auth_asym_id: 'B', matrix: Mat4.ofRows(
+        [[0.406, 0.879, 0.248, -200.633],
+         [0.693, -0.473, 0.544, 73.403],
+         [0.596, -0.049, -0.802, -14.209],
+         [0, 0, 0, 1]] )},
+    { pdbId: '1dvi', auth_asym_id: 'A', matrix: Mat4.ofRows(
+        [[-0.053, -0.077, 0.996, -45.633],
+         [-0.312, 0.949, 0.057, -12.255],
+         [-0.949, -0.307, -0.074, 53.562],
+         [0, 0, 0, 1]] )}
+];
\ No newline at end of file
diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts
index 6f3a71fe5..847e967ce 100644
--- a/src/mol-math/geometry/symmetry-operator.ts
+++ b/src/mol-math/geometry/symmetry-operator.ts
@@ -33,13 +33,13 @@ namespace SymmetryOperator {
     export const DefaultName = '1_555'
     export const Default: SymmetryOperator = create(DefaultName, Mat4.identity(), { id: '', operList: [] });
 
-    const RotationEpsilon = 0.0001;
+    export const RotationTranslationEpsilon = 0.005;
 
     export function create(name: string, matrix: Mat4, assembly: SymmetryOperator['assembly'], ncsId?: string, hkl?: Vec3): SymmetryOperator {
         const _hkl = hkl ? Vec3.clone(hkl) : Vec3.zero();
         ncsId = ncsId || ''
         if (Mat4.isIdentity(matrix)) return { name, assembly, matrix, inverse: Mat4.identity(), isIdentity: true, hkl: _hkl, ncsId };
-        if (!Mat4.isRotationAndTranslation(matrix, RotationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
+        if (!Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon)) throw new Error(`Symmetry operator (${name}) must be a composition of rotation and translation.`);
         return { name, assembly, matrix, inverse: Mat4.invert(Mat4.zero(), matrix), isIdentity: false, hkl: _hkl, ncsId };
     }
 
@@ -51,7 +51,7 @@ namespace SymmetryOperator {
             }
         }
         Mat4.setTranslation(matrix, offset);
-        return Mat4.isRotationAndTranslation(matrix, RotationEpsilon);
+        return Mat4.isRotationAndTranslation(matrix, RotationTranslationEpsilon);
     }
 
     export function ofRotationAndOffset(name: string, rot: Mat3, offset: Vec3, ncsId?: string) {
diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts
index 5243b670f..d2e50fcee 100644
--- a/src/mol-model/structure/structure/structure.ts
+++ b/src/mol-model/structure/structure/structure.ts
@@ -428,7 +428,7 @@ namespace Structure {
 
     export function transform(s: Structure, transform: Mat4) {
         if (Mat4.isIdentity(transform)) return s;
-        if (!Mat4.isRotationAndTranslation(transform)) throw new Error('Only rotation/translation combination can be applied.');
+        if (!Mat4.isRotationAndTranslation(transform, SymmetryOperator.RotationTranslationEpsilon)) throw new Error('Only rotation/translation combination can be applied.');
 
         const units: Unit[] = [];
         for (const u of s.units) {
diff --git a/src/mol-plugin/state/transforms/model.ts b/src/mol-plugin/state/transforms/model.ts
index 0bc62ac0f..a114d8873 100644
--- a/src/mol-plugin/state/transforms/model.ts
+++ b/src/mol-plugin/state/transforms/model.ts
@@ -36,7 +36,8 @@ export { ModelFromTrajectory };
 export { StructureFromModel };
 export { StructureAssemblyFromModel };
 export { StructureSymmetryFromModel };
-export { TransformStructureConformation }
+export { TransformStructureConformation };
+export { TransformStructureConformationByMatrix };
 export { StructureSelection };
 export { UserStructureSelection };
 export { StructureComplexElement };
@@ -307,6 +308,26 @@ const TransformStructureConformation = PluginStateTransform.BuiltIn({
     }
 });
 
+type TransformStructureConformationByMatrix = typeof TransformStructureConformation
+const TransformStructureConformationByMatrix = PluginStateTransform.BuiltIn({
+    name: 'transform-structure-conformation-by-matrix',
+    display: { name: 'Transform Conformation' },
+    from: SO.Molecule.Structure,
+    to: SO.Molecule.Structure,
+    params: {
+        matrix: PD.Value<Mat4>(Mat4.identity(), { isHidden: true })
+    }
+})({
+    canAutoUpdate() {
+        return true;
+    },
+    apply({ a, params }) {
+        const s = Structure.transform(a.data, params.matrix);
+        const props = { label: `${a.label}`, description: `Transformed` };
+        return new SO.Molecule.Structure(s, props);
+    }
+});
+
 type StructureSelection = typeof StructureSelection
 const StructureSelection = PluginStateTransform.BuiltIn({
     name: 'structure-selection',
-- 
GitLab