Browse Source

improve IndexPairBonds

- add id field
- better distance-based assignment
Alexander Rose 3 years ago
parent
commit
78b5d505bd

+ 3 - 0
CHANGELOG.md

@@ -9,6 +9,9 @@ Note that since we don't clearly distinguish between a public and private interf
 - Add PDBj as a ``pdb-provider`` option
 - Move Viewer APP to a separate file to allow use without importing light theme & index.html
 - Add symmetry support for mol2 files (only spacegroup setting 1)
+- Improve bond assignment from ``IndexPairBonds``
+    - Add ``id`` for mapping to source data
+    - Fix assignment of bonds with unphysical length
 
 ## [v3.0.0-dev.8] - 2021-12-31
 

+ 2 - 1
src/mol-model-formats/structure/mol2.ts

@@ -77,6 +77,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
         if (_models.frameCount > 0) {
             const indexA = Column.ofIntArray(Column.mapToArray(bonds.origin_atom_id, x => x - 1, Int32Array));
             const indexB = Column.ofIntArray(Column.mapToArray(bonds.target_atom_id, x => x - 1, Int32Array));
+            const id = bonds.bond_id;
             const order = Column.ofIntArray(Column.mapToArray(bonds.bond_type, x => {
                 switch (x) {
                     case 'ar': // aromatic
@@ -103,7 +104,7 @@ async function getModels(mol2: Mol2File, ctx: RuntimeContext) {
                         return BondType.Flag.Covalent;
                 }
             }, Int8Array));
-            const pairBonds = IndexPairBonds.fromData({ pairs: { indexA, indexB, order, flag }, count: atoms.count });
+            const pairBonds = IndexPairBonds.fromData({ pairs: { id, indexA, indexB, order, flag }, count: atoms.count });
 
             const first = _models.representative;
             IndexPairBonds.Provider.set(first, pairBonds);

+ 23 - 6
src/mol-model-formats/structure/property/bonds/index-pair.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2022 Mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -10,9 +10,9 @@ import { Column } from '../../../../mol-data/db';
 import { FormatPropertyProvider } from '../../common/property';
 import { BondType } from '../../../../mol-model/structure/model/types';
 import { ElementIndex } from '../../../../mol-model/structure';
-import { DefaultBondMaxRadius } from '../../../../mol-model/structure/structure/unit/bonds/common';
 
 export type IndexPairsProps = {
+    readonly id: ArrayLike<number>
     readonly order: ArrayLike<number>
     readonly distance: ArrayLike<number>
     readonly flag: ArrayLike<BondType.Flag>
@@ -22,17 +22,19 @@ export type IndexPairBonds = { bonds: IndexPairs, maxDistance: number }
 
 function getGraph(indexA: ArrayLike<ElementIndex>, indexB: ArrayLike<ElementIndex>, props: Partial<IndexPairsProps>, count: number): IndexPairs {
     const builder = new IntAdjacencyGraph.EdgeBuilder(count, indexA, indexB);
+    const id = new Int32Array(builder.slotCount);
     const order = new Int8Array(builder.slotCount);
     const distance = new Array(builder.slotCount);
     const flag = new Array(builder.slotCount);
     for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
         builder.addNextEdge();
+        builder.assignProperty(id, props.id ? props.id[i] : -1);
         builder.assignProperty(order, props.order ? props.order[i] : 1);
         builder.assignProperty(distance, props.distance ? props.distance[i] : -1);
         builder.assignProperty(flag, props.flag ? props.flag[i] : BondType.Flag.Covalent);
     }
 
-    return builder.createGraph({ order, distance, flag });
+    return builder.createGraph({ id, order, distance, flag });
 }
 
 export namespace IndexPairBonds {
@@ -45,15 +47,29 @@ export namespace IndexPairBonds {
     export type Data = {
         pairs: {
             indexA: Column<number>,
-            indexB: Column<number>
+            indexB: Column<number>,
+            id?: Column<number>,
             order?: Column<number>,
+            /**
+             * Useful for bonds in periodic cells. That is, only bonds within the given
+             * distance are added. This allows for bond between periodic image but
+             * avoids unwanted bonds with wrong distances.
+             */
             distance?: Column<number>,
             flag?: Column<BondType.Flag>,
         },
         count: number
     }
 
-    export const DefaultProps = { maxDistance: DefaultBondMaxRadius };
+    export const DefaultProps = {
+        /**
+         * If -1, test using element-based threshold, otherwise distance in Angstrom.
+         *
+         * This option exists to handle bonds in periodic cells. For systems that are
+         * made from beads (as opposed to atomic elements), set to a spicific distance.
+         */
+        maxDistance: -1
+    };
     export type Props = typeof DefaultProps
 
     export function fromData(data: Data, props: Partial<Props> = {}): IndexPairBonds {
@@ -61,11 +77,12 @@ export namespace IndexPairBonds {
         const { pairs, count } = data;
         const indexA = pairs.indexA.toArray() as ArrayLike<ElementIndex>;
         const indexB = pairs.indexB.toArray() as ArrayLike<ElementIndex>;
+        const id = pairs.id && pairs.id.toArray();
         const order = pairs.order && pairs.order.toArray();
         const distance = pairs.distance && pairs.distance.toArray();
         const flag = pairs.flag && pairs.flag.toArray();
         return {
-            bonds: getGraph(indexA, indexB, { order, distance, flag }, count),
+            bonds: getGraph(indexA, indexB, { id, order, distance, flag }, count),
             maxDistance: p.maxDistance
         };
     }

+ 10 - 1
src/mol-model/structure/structure/unit/bonds/common.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 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>
@@ -110,6 +110,15 @@ export function getElementThreshold(i: number) {
     return r;
 }
 
+export function getPairingThreshold(elementIndexA: number, elementIndexB: number, thresholdA: number, thresholdB: number) {
+    const thresholdAB = getElementPairThreshold(elementIndexA, elementIndexB);
+    return thresholdAB > 0
+        ? thresholdAB
+        : elementIndexB < 0
+            ? thresholdA
+            : (thresholdA + thresholdB) / 1.95; // not sure if avg or min but max is too big
+}
+
 const H_ID = __ElementIndex['H']!;
 export function isHydrogen(i: number) {
     return i === H_ID;

+ 16 - 11
src/mol-model/structure/structure/unit/bonds/inter-compute.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 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>
@@ -8,7 +8,7 @@
 import { BondType, MoleculeType } from '../../../model/types';
 import { Structure } from '../../structure';
 import { Unit } from '../../unit';
-import { getElementIdx, getElementPairThreshold, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps } from './common';
+import { getElementIdx, getElementThreshold, isHydrogen, BondComputationProps, MetalsSet, DefaultBondComputationProps, getPairingThreshold } from './common';
 import { InterUnitBonds, InterUnitEdgeProps } from './data';
 import { SortedArray } from '../../../../../mol-data/int';
 import { Vec3, Mat4 } from '../../../../../mol-math/linear-algebra';
@@ -82,11 +82,22 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
 
                 const _bI = SortedArray.indexOf(unitB.elements, bI) as StructureElement.UnitIndex;
                 if (_bI < 0) continue;
-                if (type_symbolA.value(aI) === 'H' && type_symbolB.value(bI) === 'H') continue;
+
+                const aeI = getElementIdx(type_symbolA.value(aI));
+                const beI = getElementIdx(type_symbolA.value(bI));
+                // only element-based test when maxDistance !== -1
+                if (maxDistance !== -1 && isHydrogen(aeI) && isHydrogen(beI)) continue;
 
                 const d = distance[i];
                 const dist = getDistance(unitA, aI, unitB, bI);
-                if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
+                const pairingThreshold = getPairingThreshold(
+                    aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
+                );
+
+                if ((d !== -1 && equalEps(dist, d, 0.5)) ||
+                    (maxDistance !== -1 && dist < maxDistance) ||
+                    dist < pairingThreshold
+                ) {
                     builder.add(_aI, _bI, { order: order[i], flag: flag[i] });
                 }
             }
@@ -155,13 +166,7 @@ function findPairBonds(unitA: Unit.Atomic, unitB: Unit.Atomic, props: BondComput
             const dist = Math.sqrt(squaredDistances[ni]);
             if (dist === 0) continue;
 
-            const thresholdAB = getElementPairThreshold(aeI, beI);
-            const pairingThreshold = thresholdAB > 0
-                ? thresholdAB
-                : beI < 0
-                    ? thresholdA
-                    : (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
-
+            const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
             if (dist <= pairingThreshold) {
                 const atomIdB = label_atom_idB.value(bI);
                 const compIdB = label_comp_idB.value(residueIndexB[bI]);

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

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2017-2021 Mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017-2022 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>
@@ -9,7 +9,7 @@ import { BondType } from '../../../model/types';
 import { IntraUnitBonds } from './data';
 import { Unit } from '../../unit';
 import { IntAdjacencyGraph } from '../../../../../mol-math/graph';
-import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, getElementPairThreshold, DefaultBondComputationProps } from './common';
+import { BondComputationProps, getElementIdx, MetalsSet, getElementThreshold, isHydrogen, DefaultBondComputationProps, getPairingThreshold } from './common';
 import { SortedArray } from '../../../../../mol-data/int';
 import { getIntraBondOrderFromTable } from '../../../model/properties/atomic/bonds';
 import { StructureElement } from '../../element';
@@ -62,7 +62,8 @@ function findIndexPairBonds(unit: Unit.Atomic) {
 
     for (let _aI = 0 as StructureElement.UnitIndex; _aI < atomCount; _aI++) {
         const aI = atoms[_aI];
-        const isHa = type_symbol.value(aI) === 'H';
+        const aeI = getElementIdx(type_symbol.value(aI));
+        const isHa = isHydrogen(aeI);
 
         const srcA = sourceIndex.value(aI);
 
@@ -72,11 +73,21 @@ function findIndexPairBonds(unit: Unit.Atomic) {
 
             const _bI = SortedArray.indexOf(unit.elements, bI) as StructureElement.UnitIndex;
             if (_bI < 0) continue;
-            if (isHa && type_symbol.value(bI) === 'H') continue;
+
+            const beI = getElementIdx(type_symbol.value(bI));
+            // only element-based test when maxDistance !== -1
+            if (maxDistance !== -1 && isHa && isHydrogen(beI)) continue;
 
             const d = distance[i];
             const dist = getDistance(unit, aI, bI);
-            if ((d !== -1 && equalEps(dist, d, 0.5)) || dist < maxDistance) {
+            const pairingThreshold = getPairingThreshold(
+                aeI, beI, getElementThreshold(aeI), getElementThreshold(beI)
+            );
+
+            if ((d !== -1 && equalEps(dist, d, 0.5)) ||
+                (maxDistance !== -1 && dist < maxDistance) ||
+                dist < pairingThreshold
+            ) {
                 atomA[atomA.length] = _aI;
                 atomB[atomB.length] = _bI;
                 orders[orders.length] = order[i];
@@ -214,13 +225,7 @@ function findBonds(unit: Unit.Atomic, props: BondComputationProps): IntraUnitBon
             const dist = Math.sqrt(squaredDistances[ni]);
             if (dist === 0) continue;
 
-            const thresholdAB = getElementPairThreshold(aeI, beI);
-            const pairingThreshold = thresholdAB > 0
-                ? thresholdAB
-                : beI < 0
-                    ? thresholdA
-                    : (thresholdA + getElementThreshold(beI)) / 1.95; // not sure if avg or min but max is too big
-
+            const pairingThreshold = getPairingThreshold(aeI, beI, thresholdA, getElementThreshold(beI));
             if (dist <= pairingThreshold) {
                 atomA[atomA.length] = _aI;
                 atomB[atomB.length] = _bI;