ソースを参照

Merge commit '69b5202f95f96b5bbf3b3d6361f230e3712abd76' into ui-app

# Conflicts:
#	package-lock.json
#	package.json
#	src/apps/render-test/state.ts
#	src/apps/render-test/utils/index.ts
#	src/apps/render-test/utils/mcubes.ts
#	src/mol-model/structure/model/format.ts
#	src/mol-model/structure/model/formats/gro.ts
Alexander Rose 6 年 前
コミット
1f8dfc4caf
100 ファイル変更2260 行追加2438 行削除
  1. 4 0
      data/mmcif-field-names.csv
  2. 14 389
      package-lock.json
  3. 1 2
      src/apps/cif2bcif/converter.ts
  4. 2 2
      src/apps/combine-mmcif/index.ts
  5. 3 4
      src/apps/schema-generator/schema-from-mmcif-dic.ts
  6. 2 2
      src/apps/structure-info/helpers.ts
  7. 54 28
      src/apps/structure-info/model.ts
  8. 2 3
      src/apps/structure-info/volume.ts
  9. 8 8
      src/examples/task.ts
  10. 2 2
      src/mol-app/service/job.ts
  11. 2 1
      src/mol-data/generic/unique-array.ts
  12. 11 0
      src/mol-data/int/_spec/sorted-array.spec.ts
  13. 22 0
      src/mol-data/int/impl/sorted-array.ts
  14. 6 0
      src/mol-data/int/sorted-array.ts
  15. 4 3
      src/mol-data/util/equivalence-classes.ts
  16. 8 0
      src/mol-data/util/hash-functions.ts
  17. 3 3
      src/mol-data/util/sort.ts
  18. 13 43
      src/mol-geo/representation/structure/index.ts
  19. 19 17
      src/mol-geo/representation/structure/point.ts
  20. 15 16
      src/mol-geo/representation/structure/spacefill.ts
  21. 11 10
      src/mol-geo/representation/structure/utils.ts
  22. 1 2
      src/mol-geo/representation/volume/index.ts
  23. 3 3
      src/mol-geo/representation/volume/surface.ts
  24. 8 8
      src/mol-geo/theme/structure/color/chain-id.ts
  25. 2 3
      src/mol-geo/theme/structure/color/element-index.ts
  26. 3 4
      src/mol-geo/theme/structure/color/element-symbol.ts
  27. 2 3
      src/mol-geo/theme/structure/color/index.ts
  28. 1 1
      src/mol-geo/theme/structure/color/instance-index.ts
  29. 7 7
      src/mol-geo/theme/structure/size/element.ts
  30. 2 3
      src/mol-geo/theme/structure/size/index.ts
  31. 3 4
      src/mol-io/reader/_spec/csv.spec.ts
  32. 2 3
      src/mol-io/reader/_spec/gro.spec.ts
  33. 3 4
      src/mol-io/reader/_spec/mol2.spec.ts
  34. 5 0
      src/mol-io/reader/cif/schema/mmcif.ts
  35. 17 19
      src/mol-math/geometry/spacegroup/construction.ts
  36. 12 5
      src/mol-math/geometry/spacegroup/tables.ts
  37. 2 2
      src/mol-math/geometry/symmetry-operator.ts
  38. 5 1
      src/mol-math/linear-algebra/3d/common.ts
  39. 4 0
      src/mol-math/linear-algebra/3d/mat3.ts
  40. 3 3
      src/mol-math/linear-algebra/3d/mat4.ts
  41. 11 1
      src/mol-math/linear-algebra/tensor.ts
  42. 0 202
      src/mol-model/structure/_spec/atom-set.spec.ts
  43. 4 4
      src/mol-model/structure/export/mmcif.ts
  44. 1 1
      src/mol-model/structure/model.ts
  45. 3 3
      src/mol-model/structure/model/format.ts
  46. 154 153
      src/mol-model/structure/model/formats/gro.ts
  47. 86 55
      src/mol-model/structure/model/formats/mmcif.ts
  48. 1 1
      src/mol-model/structure/model/formats/mmcif/assembly.ts
  49. 6 6
      src/mol-model/structure/model/formats/mmcif/bonds.ts
  50. 59 28
      src/mol-model/structure/model/formats/mmcif/ihm.ts
  51. 154 0
      src/mol-model/structure/model/formats/mmcif/secondary-structure.ts
  52. 3 3
      src/mol-model/structure/model/formats/mmcif/sequence.ts
  53. 2 2
      src/mol-model/structure/model/formats/mmcif/util.ts
  54. 13 23
      src/mol-model/structure/model/model.ts
  55. 5 34
      src/mol-model/structure/model/properties/atomic.ts
  56. 2 4
      src/mol-model/structure/model/properties/atomic/conformation.ts
  57. 10 12
      src/mol-model/structure/model/properties/atomic/hierarchy.ts
  58. 38 0
      src/mol-model/structure/model/properties/atomic/measures.ts
  59. 0 53
      src/mol-model/structure/model/properties/coarse-grained.ts
  60. 8 0
      src/mol-model/structure/model/properties/coarse.ts
  61. 30 0
      src/mol-model/structure/model/properties/coarse/conformation.ts
  62. 49 0
      src/mol-model/structure/model/properties/coarse/hierarchy.ts
  63. 2 4
      src/mol-model/structure/model/properties/common.ts
  64. 9 6
      src/mol-model/structure/model/properties/seconday-structure.ts
  65. 2 2
      src/mol-model/structure/model/properties/sequence.ts
  66. 7 4
      src/mol-model/structure/model/properties/symmetry.ts
  67. 9 12
      src/mol-model/structure/model/properties/utils/atomic-keys.ts
  68. 84 0
      src/mol-model/structure/model/properties/utils/coarse-keys.ts
  69. 0 0
      src/mol-model/structure/model/properties/utils/guess-element.ts
  70. 1 0
      src/mol-model/structure/model/types.ts
  71. 2 0
      src/mol-model/structure/query.ts
  72. 47 95
      src/mol-model/structure/query/generators.ts
  73. 101 0
      src/mol-model/structure/query/modifiers.ts
  74. 42 42
      src/mol-model/structure/query/properties.ts
  75. 34 60
      src/mol-model/structure/query/selection.ts
  76. 82 0
      src/mol-model/structure/query/utils/builders.ts
  77. 104 0
      src/mol-model/structure/query/utils/structure.ts
  78. 2 4
      src/mol-model/structure/structure.ts
  79. 4 4
      src/mol-model/structure/structure/element.ts
  80. 0 77
      src/mol-model/structure/structure/element/group.ts
  81. 0 10
      src/mol-model/structure/structure/element/impl/properties.ts
  82. 0 65
      src/mol-model/structure/structure/element/impl/set-builder.ts
  83. 0 464
      src/mol-model/structure/structure/element/impl/set.ts
  84. 0 67
      src/mol-model/structure/structure/element/set.ts
  85. 170 58
      src/mol-model/structure/structure/structure.ts
  86. 84 25
      src/mol-model/structure/structure/symmetry.ts
  87. 131 82
      src/mol-model/structure/structure/unit.ts
  88. 8 0
      src/mol-model/structure/structure/unit/bonds.ts
  89. 16 17
      src/mol-model/structure/structure/unit/bonds/intra-compute.ts
  90. 5 5
      src/mol-model/structure/structure/unit/bonds/intra-data.ts
  91. 12 15
      src/mol-model/structure/structure/util/boundary.ts
  92. 93 81
      src/mol-model/structure/structure/util/lookup3d.ts
  93. 116 0
      src/mol-model/structure/structure/util/subset-builder.ts
  94. 98 0
      src/mol-model/structure/structure/util/unique-subset-builder.ts
  95. 21 10
      src/mol-task/execution/observable.ts
  96. 1 7
      src/mol-task/execution/runtime-context.ts
  97. 1 10
      src/mol-task/execution/synchronous.ts
  98. 1 12
      src/mol-task/index.ts
  99. 40 6
      src/mol-task/task.ts
  100. 1 1
      src/mol-util/bit-flags.ts

+ 4 - 0
data/mmcif-field-names.csv

@@ -1,3 +1,7 @@
+atom_sites.entry_id
+atom_sites.fract_transf_matrix
+atom_sites.fract_transf_vector
+
 atom_site.group_PDB
 atom_site.id
 atom_site.type_symbol

+ 14 - 389
package-lock.json

@@ -24,15 +24,6 @@
         "js-tokens": "3.0.2"
       }
     },
-    "@babel/runtime": {
-      "version": "7.0.0-beta.47",
-      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.0.0-beta.47.tgz",
-      "integrity": "sha512-3IaakAC5B4bHJ0aCUKVw0pt+GruavdgWDFbf7TfKh7ZJ8yQuUp7af7MNwf3e+jH8776cjqYmMO1JNDDAE9WfrA==",
-      "requires": {
-        "core-js": "2.5.3",
-        "regenerator-runtime": "0.11.1"
-      }
-    },
     "@mrmlnc/readdir-enhanced": {
       "version": "2.2.1",
       "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@@ -104,15 +95,6 @@
       "integrity": "sha512-e74sM9W/4qqWB6D4TWV9FQk0WoHtX1X4FJpbjxucMSVJHtFjbQOH3H6yp+xno4br0AKG0wz/kPtaN599GUOvAg==",
       "dev": true
     },
-    "@types/jss": {
-      "version": "9.5.3",
-      "resolved": "https://registry.npmjs.org/@types/jss/-/jss-9.5.3.tgz",
-      "integrity": "sha512-RQWhcpOVyIhGryKpnUyZARwsgmp+tB82O7c75lC4Tjbmr3hPiCnM1wc+pJipVEOsikYXW0IHgeiQzmxQXbnAIA==",
-      "requires": {
-        "csstype": "2.5.1",
-        "indefinite-observable": "1.0.1"
-      }
-    },
     "@types/mime": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.0.tgz",
@@ -138,6 +120,7 @@
       "version": "16.3.14",
       "resolved": "https://registry.npmjs.org/@types/react/-/react-16.3.14.tgz",
       "integrity": "sha512-wNUGm49fPl7eE2fnYdF0v5vSOrUMdKMQD/4NwtQRnb6mnPwtkhabmuFz37eq90+hhyfz0pWd38jkZHOcaZ6LGw==",
+      "dev": true,
       "requires": {
         "csstype": "2.5.1"
       }
@@ -152,14 +135,6 @@
         "@types/react": "16.3.14"
       }
     },
-    "@types/react-transition-group": {
-      "version": "2.0.9",
-      "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.9.tgz",
-      "integrity": "sha512-Id2MtQcmOgLymqqLqg1VjzNpN7O5vGoF47h3s7jxhzqWdMCtk2GwxFUqcKbGrRmHzzQGyRatfG8yahonIys74Q==",
-      "requires": {
-        "@types/react": "16.3.14"
-      }
-    },
     "@types/serve-static": {
       "version": "1.13.1",
       "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.1.tgz",
@@ -1599,6 +1574,7 @@
       "version": "6.26.0",
       "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
       "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
+      "dev": true,
       "requires": {
         "core-js": "2.5.3",
         "regenerator-runtime": "0.11.1"
@@ -1842,11 +1818,6 @@
         "repeat-element": "1.1.2"
       }
     },
-    "brcast": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/brcast/-/brcast-3.0.1.tgz",
-      "integrity": "sha512-eI3yqf9YEqyGl9PCNTR46MGvDylGtaHjalcz6Q3fAPnP/PhpKkkve52vFdfGpwp4VUvK6LUr4TQN+2stCrEwTg=="
-    },
     "brorand": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
@@ -2172,11 +2143,6 @@
         }
       }
     },
-    "change-emitter": {
-      "version": "0.1.6",
-      "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz",
-      "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU="
-    },
     "chardet": {
       "version": "0.4.2",
       "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz",
@@ -2358,11 +2324,6 @@
         }
       }
     },
-    "classnames": {
-      "version": "2.2.5",
-      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.2.5.tgz",
-      "integrity": "sha1-+zgB1FNGdknvNgPH1hoCvRKb3m0="
-    },
     "cli-cursor": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
@@ -2823,7 +2784,8 @@
     "core-js": {
       "version": "2.5.3",
       "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz",
-      "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4="
+      "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=",
+      "dev": true
     },
     "core-util-is": {
       "version": "1.0.2",
@@ -3009,14 +2971,6 @@
         }
       }
     },
-    "css-vendor": {
-      "version": "0.3.8",
-      "resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-0.3.8.tgz",
-      "integrity": "sha1-ZCHP0wNM5mT+dnOXL9ARn8KJQfo=",
-      "requires": {
-        "is-in-browser": "1.1.3"
-      }
-    },
     "cssesc": {
       "version": "0.1.0",
       "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-0.1.0.tgz",
@@ -3091,7 +3045,8 @@
     "csstype": {
       "version": "2.5.1",
       "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.5.1.tgz",
-      "integrity": "sha512-qfG5lXkiUKz3kAuABSlpRxL9QL/U8ViJiXC6hvk/7tEJaCj7a2ZOW2kVtSFGpETOfQR7MicXjf/q1bmO1iShiA=="
+      "integrity": "sha512-qfG5lXkiUKz3kAuABSlpRxL9QL/U8ViJiXC6hvk/7tEJaCj7a2ZOW2kVtSFGpETOfQR7MicXjf/q1bmO1iShiA==",
+      "dev": true
     },
     "currently-unhandled": {
       "version": "0.4.1",
@@ -3190,11 +3145,6 @@
       "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
       "dev": true
     },
-    "deepmerge": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.1.0.tgz",
-      "integrity": "sha512-Q89Z26KAfA3lpPGhbF6XMfYAm3jIV3avViy6KOJ2JLzFbeWHOvPQUu5aSJIWXap3gDZC2y1eF5HXEPI2wGqgvw=="
-    },
     "default-require-extensions": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz",
@@ -3338,16 +3288,6 @@
         }
       }
     },
-    "dom-helpers": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.3.1.tgz",
-      "integrity": "sha512-2Sm+JaYn74OiTM2wHvxJOo3roiq/h25Yi69Fqk269cNUwIXsCvATB6CRSFC9Am/20G2b28hGv/+7NiWydIrPvg=="
-    },
-    "dom-walk": {
-      "version": "0.1.1",
-      "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz",
-      "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg="
-    },
     "domain-browser": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -5668,15 +5608,6 @@
         "find-index": "0.1.1"
       }
     },
-    "global": {
-      "version": "4.3.2",
-      "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz",
-      "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=",
-      "requires": {
-        "min-document": "2.19.0",
-        "process": "0.5.2"
-      }
-    },
     "global-modules": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz",
@@ -6270,11 +6201,6 @@
       "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz",
       "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA=="
     },
-    "hoist-non-react-statics": {
-      "version": "2.5.0",
-      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.0.tgz",
-      "integrity": "sha512-6Bl6XsDT1ntE0lHbIhr4Kp2PGcleGZ66qu5Jqk8lc0Xc/IeG6gVLmwUGs/K0Us+L8VWoKgj0uWdPMataOsm31w=="
-    },
     "home-or-tmp": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz",
@@ -6348,11 +6274,6 @@
       "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=",
       "dev": true
     },
-    "hyphenate-style-name": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz",
-      "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es="
-    },
     "iconv-lite": {
       "version": "0.4.19",
       "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz",
@@ -6448,8 +6369,7 @@
     "immutable": {
       "version": "4.0.0-rc.9",
       "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.9.tgz",
-      "integrity": "sha512-uw4u9Jy3G2Y1qkIFtEGy9NgJxFJT1l3HKgeSFHfrvy91T8W54cJoQ+qK3fTwhil8XkEHuc2S+MI+fbD0vKObDA==",
-      "dev": true
+      "integrity": "sha512-uw4u9Jy3G2Y1qkIFtEGy9NgJxFJT1l3HKgeSFHfrvy91T8W54cJoQ+qK3fTwhil8XkEHuc2S+MI+fbD0vKObDA=="
     },
     "import-local": {
       "version": "1.0.0",
@@ -6473,14 +6393,6 @@
       "integrity": "sha1-4g/146KvwmkDILbcVSaCqcf631E=",
       "dev": true
     },
-    "indefinite-observable": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/indefinite-observable/-/indefinite-observable-1.0.1.tgz",
-      "integrity": "sha1-CZFUI8yNb36xy3iCrRNGM8mm7cM=",
-      "requires": {
-        "symbol-observable": "1.0.4"
-      }
-    },
     "indent-string": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz",
@@ -6747,11 +6659,6 @@
       "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
       "dev": true
     },
-    "is-function": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz",
-      "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU="
-    },
     "is-generator-fn": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz",
@@ -6767,11 +6674,6 @@
         "is-extglob": "1.0.0"
       }
     },
-    "is-in-browser": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
-      "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
-    },
     "is-my-ip-valid": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz",
@@ -6850,6 +6752,7 @@
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
       "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
+      "dev": true,
       "requires": {
         "isobject": "3.0.1"
       },
@@ -6857,7 +6760,8 @@
         "isobject": {
           "version": "3.0.1",
           "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
-          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
+          "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=",
+          "dev": true
         }
       }
     },
@@ -7673,113 +7577,6 @@
         "verror": "1.10.0"
       }
     },
-    "jss": {
-      "version": "9.8.1",
-      "resolved": "https://registry.npmjs.org/jss/-/jss-9.8.1.tgz",
-      "integrity": "sha512-a9dXInEPTRmdSmzw3LNhbAwdQVZgCRmFU7dFzrpLTMAcdolHXNamhxQ6J+PNIqUtWa9yRbZIzWX6aUlI55LZ/A==",
-      "requires": {
-        "is-in-browser": "1.1.3",
-        "symbol-observable": "1.2.0",
-        "warning": "3.0.0"
-      },
-      "dependencies": {
-        "symbol-observable": {
-          "version": "1.2.0",
-          "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
-          "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ=="
-        }
-      }
-    },
-    "jss-camel-case": {
-      "version": "6.1.0",
-      "resolved": "https://registry.npmjs.org/jss-camel-case/-/jss-camel-case-6.1.0.tgz",
-      "integrity": "sha512-HPF2Q7wmNW1t79mCqSeU2vdd/vFFGpkazwvfHMOhPlMgXrJDzdj9viA2SaHk9ZbD5pfL63a8ylp4++irYbbzMQ==",
-      "requires": {
-        "hyphenate-style-name": "1.0.2"
-      }
-    },
-    "jss-compose": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/jss-compose/-/jss-compose-5.0.0.tgz",
-      "integrity": "sha512-YofRYuiA0+VbeOw0VjgkyO380sA4+TWDrW52nSluD9n+1FWOlDzNbgpZ/Sb3Y46+DcAbOS21W5jo6SAqUEiuwA==",
-      "requires": {
-        "warning": "3.0.0"
-      }
-    },
-    "jss-default-unit": {
-      "version": "8.0.2",
-      "resolved": "https://registry.npmjs.org/jss-default-unit/-/jss-default-unit-8.0.2.tgz",
-      "integrity": "sha512-WxNHrF/18CdoAGw2H0FqOEvJdREXVXLazn7PQYU7V6/BWkCV0GkmWsppNiExdw8dP4TU1ma1dT9zBNJ95feLmg=="
-    },
-    "jss-expand": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/jss-expand/-/jss-expand-5.3.0.tgz",
-      "integrity": "sha512-NiM4TbDVE0ykXSAw6dfFmB1LIqXP/jdd0ZMnlvlGgEMkMt+weJIl8Ynq1DsuBY9WwkNyzWktdqcEW2VN0RAtQg=="
-    },
-    "jss-extend": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/jss-extend/-/jss-extend-6.2.0.tgz",
-      "integrity": "sha512-YszrmcB6o9HOsKPszK7NeDBNNjVyiW864jfoiHoMlgMIg2qlxKw70axZHqgczXHDcoyi/0/ikP1XaHDPRvYtEA==",
-      "requires": {
-        "warning": "3.0.0"
-      }
-    },
-    "jss-global": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/jss-global/-/jss-global-3.0.0.tgz",
-      "integrity": "sha512-wxYn7vL+TImyQYGAfdplg7yaxnPQ9RaXY/cIA8hawaVnmmWxDHzBK32u1y+RAvWboa3lW83ya3nVZ/C+jyjZ5Q=="
-    },
-    "jss-nested": {
-      "version": "6.0.1",
-      "resolved": "https://registry.npmjs.org/jss-nested/-/jss-nested-6.0.1.tgz",
-      "integrity": "sha512-rn964TralHOZxoyEgeq3hXY8hyuCElnvQoVrQwKHVmu55VRDd6IqExAx9be5HgK0yN/+hQdgAXQl/GUrBbbSTA==",
-      "requires": {
-        "warning": "3.0.0"
-      }
-    },
-    "jss-preset-default": {
-      "version": "4.5.0",
-      "resolved": "https://registry.npmjs.org/jss-preset-default/-/jss-preset-default-4.5.0.tgz",
-      "integrity": "sha512-qZbpRVtHT7hBPpZEBPFfafZKWmq3tA/An5RNqywDsZQGrlinIF/mGD9lmj6jGqu8GrED2SMHZ3pPKLmjCZoiaQ==",
-      "requires": {
-        "jss-camel-case": "6.1.0",
-        "jss-compose": "5.0.0",
-        "jss-default-unit": "8.0.2",
-        "jss-expand": "5.3.0",
-        "jss-extend": "6.2.0",
-        "jss-global": "3.0.0",
-        "jss-nested": "6.0.1",
-        "jss-props-sort": "6.0.0",
-        "jss-template": "1.0.1",
-        "jss-vendor-prefixer": "7.0.0"
-      }
-    },
-    "jss-props-sort": {
-      "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/jss-props-sort/-/jss-props-sort-6.0.0.tgz",
-      "integrity": "sha512-E89UDcrphmI0LzmvYk25Hp4aE5ZBsXqMWlkFXS0EtPkunJkRr+WXdCNYbXbksIPnKlBenGB9OxzQY+mVc70S+g=="
-    },
-    "jss-template": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/jss-template/-/jss-template-1.0.1.tgz",
-      "integrity": "sha512-m5BqEWha17fmIVXm1z8xbJhY6GFJxNB9H68GVnCWPyGYfxiAgY9WTQyvDAVj+pYRgrXSOfN5V1T4+SzN1sJTeg==",
-      "requires": {
-        "warning": "3.0.0"
-      }
-    },
-    "jss-vendor-prefixer": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/jss-vendor-prefixer/-/jss-vendor-prefixer-7.0.0.tgz",
-      "integrity": "sha512-Agd+FKmvsI0HLcYXkvy8GYOw3AAASBUpsmIRvVQheps+JWaN892uFOInTr0DRydwaD91vSSUCU4NssschvF7MA==",
-      "requires": {
-        "css-vendor": "0.3.8"
-      }
-    },
-    "keycode": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",
-      "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ="
-    },
     "keyv": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.0.0.tgz",
@@ -8146,7 +7943,8 @@
     "lodash": {
       "version": "4.17.4",
       "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
-      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
+      "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=",
+      "dev": true
     },
     "lodash._baseassign": {
       "version": "3.2.0",
@@ -8447,41 +8245,6 @@
         "object-visit": "1.0.1"
       }
     },
-    "material-ui": {
-      "version": "1.0.0-beta.47",
-      "resolved": "https://registry.npmjs.org/material-ui/-/material-ui-1.0.0-beta.47.tgz",
-      "integrity": "sha512-SLyYStYSGrrhBtDQS6j2lpKeP3Nfi4vn+PA24xwuL+qSLJODD1EiQpn7w13Ky7Ni3M3Q1svBmHNnZLIKeOL85w==",
-      "requires": {
-        "@babel/runtime": "7.0.0-beta.47",
-        "@types/jss": "9.5.3",
-        "@types/react-transition-group": "2.0.9",
-        "brcast": "3.0.1",
-        "classnames": "2.2.5",
-        "deepmerge": "2.1.0",
-        "dom-helpers": "3.3.1",
-        "hoist-non-react-statics": "2.5.0",
-        "jss": "9.8.1",
-        "jss-camel-case": "6.1.0",
-        "jss-default-unit": "8.0.2",
-        "jss-global": "3.0.0",
-        "jss-nested": "6.0.1",
-        "jss-props-sort": "6.0.0",
-        "jss-vendor-prefixer": "7.0.0",
-        "keycode": "2.2.0",
-        "lodash": "4.17.4",
-        "normalize-scroll-left": "0.1.2",
-        "prop-types": "15.6.1",
-        "react-event-listener": "0.5.3",
-        "react-jss": "8.4.0",
-        "react-lifecycles-compat": "3.0.4",
-        "react-popper": "0.10.4",
-        "react-scrollbar-size": "2.1.0",
-        "react-transition-group": "2.3.1",
-        "recompose": "0.27.0",
-        "scroll": "2.0.3",
-        "warning": "3.0.0"
-      }
-    },
     "math-expression-evaluator": {
       "version": "1.2.17",
       "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz",
@@ -8709,14 +8472,6 @@
       "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz",
       "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4="
     },
-    "min-document": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
-      "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
-      "requires": {
-        "dom-walk": "0.1.1"
-      }
-    },
     "minimalistic-assert": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -9342,11 +9097,6 @@
       "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",
       "dev": true
     },
-    "normalize-scroll-left": {
-      "version": "0.1.2",
-      "resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.1.2.tgz",
-      "integrity": "sha512-F9YMRls0zCF6BFIE2YnXDRpHPpfd91nOIaNdDgrx5YMoPLo8Wqj+6jNXHQsYBavJeXP4ww8HCt0xQAKc5qk2Fg=="
-    },
     "normalize-url": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-2.0.1.tgz",
@@ -9965,11 +9715,6 @@
       "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==",
       "dev": true
     },
-    "popper.js": {
-      "version": "1.14.3",
-      "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.14.3.tgz",
-      "integrity": "sha1-FDj5jQRqz3tNeM1QK/QYrGTU8JU="
-    },
     "posix-character-classes": {
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -10687,11 +10432,6 @@
       "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==",
       "dev": true
     },
-    "process": {
-      "version": "0.5.2",
-      "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz",
-      "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8="
-    },
     "process-nextick-args": {
       "version": "1.0.7",
       "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz",
@@ -10876,14 +10616,6 @@
         }
       }
     },
-    "rafl": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/rafl/-/rafl-1.2.2.tgz",
-      "integrity": "sha1-/pMPdYIRAg1H44gV9Rlqi+QVB0A=",
-      "requires": {
-        "global": "4.3.2"
-      }
-    },
     "randomatic": {
       "version": "1.1.7",
       "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz",
@@ -11029,64 +10761,6 @@
         "prop-types": "15.6.1"
       }
     },
-    "react-event-listener": {
-      "version": "0.5.3",
-      "resolved": "https://registry.npmjs.org/react-event-listener/-/react-event-listener-0.5.3.tgz",
-      "integrity": "sha512-fTGYvhe7eTsqq0m664Km0rxKQcqLIGZWZINmy1LU0fu312tay8Mt3Twq2P5Xj1dfDVvvzT1Ql3/FDkiMPJ1MOg==",
-      "requires": {
-        "babel-runtime": "6.26.0",
-        "fbjs": "0.8.16",
-        "prop-types": "15.6.1",
-        "warning": "3.0.0"
-      }
-    },
-    "react-jss": {
-      "version": "8.4.0",
-      "resolved": "https://registry.npmjs.org/react-jss/-/react-jss-8.4.0.tgz",
-      "integrity": "sha512-yIi4udcTIIh5u4KJ47wsL3UZYMuSrp5xR1YBvPeRNshpCdRoJxt5BWmCu1RA3LIa+//dnRsAtAQmMAYeg1W9oQ==",
-      "requires": {
-        "hoist-non-react-statics": "2.5.0",
-        "jss": "9.8.1",
-        "jss-preset-default": "4.5.0",
-        "prop-types": "15.6.1",
-        "theming": "1.3.0"
-      }
-    },
-    "react-lifecycles-compat": {
-      "version": "3.0.4",
-      "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
-      "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
-    },
-    "react-popper": {
-      "version": "0.10.4",
-      "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz",
-      "integrity": "sha1-rypBXqIike3VBGeNev2opu4ylao=",
-      "requires": {
-        "popper.js": "1.14.3",
-        "prop-types": "15.6.1"
-      }
-    },
-    "react-scrollbar-size": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/react-scrollbar-size/-/react-scrollbar-size-2.1.0.tgz",
-      "integrity": "sha512-9dDUJvk7S48r0TRKjlKJ9e/LkLLYgc9LdQR6W21I8ZqtSrEsedPOoMji4nU3DHy7fx2l8YMScJS/N7qiloYzXQ==",
-      "requires": {
-        "babel-runtime": "6.26.0",
-        "prop-types": "15.6.1",
-        "react-event-listener": "0.5.3",
-        "stifle": "1.0.4"
-      }
-    },
-    "react-transition-group": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.3.1.tgz",
-      "integrity": "sha512-hu4/LAOFSKjWt1+1hgnOv3ldxmt6lvZGTWz4KUkFrqzXrNDIVSu6txIcPszw7PNduR8en9YTN55JLRyd/L1ZiQ==",
-      "requires": {
-        "dom-helpers": "3.3.1",
-        "loose-envify": "1.3.1",
-        "prop-types": "15.6.1"
-      }
-    },
     "read-chunk": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz",
@@ -11211,19 +10885,6 @@
         "resolve": "1.1.7"
       }
     },
-    "recompose": {
-      "version": "0.27.0",
-      "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.27.0.tgz",
-      "integrity": "sha512-hivr1EopLhzjchhv2Y7VcLA2H5NGztwV/qfYqmIAhTkNowNQ9PyXdfq9Q8QCa0TMrPM1NtStlUyi5I/p8XfUNQ==",
-      "requires": {
-        "babel-runtime": "6.26.0",
-        "change-emitter": "0.1.6",
-        "fbjs": "0.8.16",
-        "hoist-non-react-statics": "2.5.0",
-        "react-lifecycles-compat": "3.0.4",
-        "symbol-observable": "1.0.4"
-      }
-    },
     "redent": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz",
@@ -11279,7 +10940,8 @@
     "regenerator-runtime": {
       "version": "0.11.1",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
-      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
+      "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==",
+      "dev": true
     },
     "regenerator-transform": {
       "version": "0.10.1",
@@ -12076,14 +11738,6 @@
       "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=",
       "dev": true
     },
-    "scroll": {
-      "version": "2.0.3",
-      "resolved": "https://registry.npmjs.org/scroll/-/scroll-2.0.3.tgz",
-      "integrity": "sha512-3ncZzf8gUW739h3LeS68nSssO60O+GGjT3SxzgofQmT8PIoyHzebql9HHPJopZX8iT6TKOdwaWFMqL6LzUN3DQ==",
-      "requires": {
-        "rafl": "1.2.2"
-      }
-    },
     "scss-tokenizer": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",
@@ -12821,11 +12475,6 @@
       "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=",
       "dev": true
     },
-    "stifle": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/stifle/-/stifle-1.0.4.tgz",
-      "integrity": "sha1-izvN9SQZsKnHnjWtrc5QEjwdjpk="
-    },
     "stream-browserify": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
@@ -13082,11 +12731,6 @@
         }
       }
     },
-    "symbol-observable": {
-      "version": "1.0.4",
-      "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.4.tgz",
-      "integrity": "sha1-Kb9hXUqnEhvdiYsi1LP5vE4qoD0="
-    },
     "symbol-tree": {
       "version": "3.2.2",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
@@ -13483,17 +13127,6 @@
       "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==",
       "dev": true
     },
-    "theming": {
-      "version": "1.3.0",
-      "resolved": "https://registry.npmjs.org/theming/-/theming-1.3.0.tgz",
-      "integrity": "sha512-ya5Ef7XDGbTPBv5ENTwrwkPUexrlPeiAg/EI9kdlUAZhNlRbCdhMKRgjNX1IcmsmiPcqDQZE6BpSaH+cr31FKw==",
-      "requires": {
-        "brcast": "3.0.1",
-        "is-function": "1.0.1",
-        "is-plain-object": "2.0.4",
-        "prop-types": "15.6.1"
-      }
-    },
     "throat": {
       "version": "4.1.0",
       "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz",
@@ -14308,14 +13941,6 @@
         "makeerror": "1.0.11"
       }
     },
-    "warning": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/warning/-/warning-3.0.0.tgz",
-      "integrity": "sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w=",
-      "requires": {
-        "loose-envify": "1.3.1"
-      }
-    },
     "watch": {
       "version": "0.18.0",
       "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz",

+ 1 - 2
src/apps/cif2bcif/converter.ts

@@ -9,11 +9,10 @@ import CIF, { CifCategory } from 'mol-io/reader/cif'
 import * as Encoder from 'mol-io/writer/cif'
 import * as fs from 'fs'
 import classify from './field-classifier'
-import { Run } from 'mol-task'
 
 async function getCIF(path: string) {
     const str = fs.readFileSync(path, 'utf8');
-    const parsed = await Run(CIF.parseText(str));
+    const parsed = await CIF.parseText(str).run();
     if (parsed.isError) {
         throw new Error(parsed.toString());
     }

+ 2 - 2
src/apps/combine-mmcif/index.ts

@@ -13,7 +13,7 @@ require('util.promisify').shim();
 const readFile = util.promisify(fs.readFile);
 const writeFile = util.promisify(fs.writeFile);
 
-import { Run, Progress } from 'mol-task'
+import { Progress } from 'mol-task'
 import { Database, Table, DatabaseCollection } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
 // import { CCD_Schema } from 'mol-io/reader/cif/schema/ccd'
@@ -78,7 +78,7 @@ export async function getBIRD() {
 async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data);
     console.time('parse cif');
-    const parsed = await Run(comp, p => console.log(Progress.format(p)), 250);
+    const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
     console.timeEnd('parse cif');
     if (parsed.isError) throw parsed;
     return parsed

+ 3 - 4
src/apps/schema-generator/schema-from-mmcif-dic.ts

@@ -14,15 +14,14 @@ import CIF, { CifFrame } from 'mol-io/reader/cif'
 import { generateSchema } from './util/cif-dic'
 import { generate } from './util/generate'
 import { Filter } from './util/json-schema'
-import { Run } from 'mol-task'
 
 async function runGenerateSchema(name: string, fieldNamesPath?: string, typescript = false, out?: string) {
     await ensureMmcifDicAvailable()
-    const mmcifDic = await Run(CIF.parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')));
+    const mmcifDic = await CIF.parseText(fs.readFileSync(MMCIF_DIC_PATH, 'utf8')).run();
     if (mmcifDic.isError) throw mmcifDic
 
     await ensureIhmDicAvailable()
-    const ihmDic = await Run(CIF.parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')));
+    const ihmDic = await CIF.parseText(fs.readFileSync(IHM_DIC_PATH, 'utf8')).run();
     if (ihmDic.isError) throw ihmDic
 
     const mmcifDicVersion = CIF.schema.dic(mmcifDic.result.blocks[0]).dictionary.version.value(0)
@@ -44,7 +43,7 @@ async function runGenerateSchema(name: string, fieldNamesPath?: string, typescri
 
 async function getFieldNamesFilter(fieldNamesPath: string): Promise<Filter> {
     const fieldNamesStr = fs.readFileSync(fieldNamesPath, 'utf8')
-    const parsed = await Run(Csv(fieldNamesStr, { noColumnNames: true }));
+    const parsed = await Csv(fieldNamesStr, { noColumnNames: true }).run();
     if (parsed.isError) throw parser.error
     const csvFile = parsed.result;
 

+ 2 - 2
src/apps/structure-info/helpers.ts

@@ -10,7 +10,7 @@ import fetch from 'node-fetch'
 require('util.promisify').shim();
 
 import CIF from 'mol-io/reader/cif'
-import { Run, Progress } from 'mol-task'
+import { Progress } from 'mol-task'
 
 const readFileAsync = util.promisify(fs.readFile);
 
@@ -27,7 +27,7 @@ async function readFile(path: string) {
 
 async function parseCif(data: string|Uint8Array) {
     const comp = CIF.parse(data);
-    const parsed = await Run(comp, p => console.log(Progress.format(p)), 250);
+    const parsed = await comp.run(p => console.log(Progress.format(p)), 250);
     if (parsed.isError) throw parsed;
     return parsed.result;
 }

+ 54 - 28
src/apps/structure-info/model.ts

@@ -10,13 +10,14 @@ require('util.promisify').shim();
 
 // import { Table } from 'mol-data/db'
 import CIF from 'mol-io/reader/cif'
-import { Model, Structure, Element, ElementSet, Unit, ElementGroup, Queries } from 'mol-model/structure'
+import { Model, Structure, Element, Unit, Queries } from 'mol-model/structure'
 // import { Run, Progress } from 'mol-task'
 import { OrderedSet } from 'mol-data/int';
 import { Table } from 'mol-data/db';
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
 import { openCif, downloadCif } from './helpers';
+import { BitFlags } from 'mol-util';
+import { SecondaryStructureType } from 'mol-model/structure/model/types';
 
 
 async function downloadFromPdb(pdb: string) {
@@ -31,7 +32,7 @@ async function readPdbFile(path: string) {
 }
 
 export function atomLabel(model: Model, aI: number) {
-    const { atoms, residues, chains, residueSegments, chainSegments } = model.hierarchy
+    const { atoms, residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
     const { label_atom_id } = atoms
     const { label_comp_id, label_seq_id } = residues
     const { label_asym_id } = chains
@@ -40,16 +41,42 @@ export function atomLabel(model: Model, aI: number) {
     return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)} ${label_atom_id.value(aI)}`
 }
 
+export function residueLabel(model: Model, rI: number) {
+    const { residues, chains, residueSegments, chainSegments } = model.atomicHierarchy
+    const { label_comp_id, label_seq_id } = residues
+    const { label_asym_id } = chains
+    const cI = chainSegments.segmentMap[residueSegments.segments[rI]]
+    return `${label_asym_id.value(cI)} ${label_comp_id.value(rI)} ${label_seq_id.value(rI)}`
+}
 
-export function printBonds(structure: Structure) {
-    const { units, elements } = structure;
-    const unitIds = ElementSet.unitIndices(elements);
+export function printSecStructure(model: Model) {
+    console.log('Secondary Structure\n=============');
+    const { residues } = model.atomicHierarchy;
+    const { type, key } = model.properties.secondaryStructure;
+
+    const count = residues._rowCount;
+    let rI = 0;
+    while (rI < count) {
+        let start = rI;
+        while (rI < count && key[start] === key[rI]) rI++;
+        rI--;
+
+        if (BitFlags.has(type[start], SecondaryStructureType.Flag.Beta)) {
+            console.log(`Sheet: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
+        } else if (BitFlags.has(type[start], SecondaryStructureType.Flag.Helix)) {
+            console.log(`Helix: ${residueLabel(model, start)} - ${residueLabel(model, rI)} (key ${key[start]})`);
+        }
 
-    for (let i = 0, _i = OrderedSet.size(unitIds); i < _i; i++) {
-        const unit = units[OrderedSet.getAt(unitIds, i)];
-        const group = ElementSet.groupFromUnitIndex(elements, OrderedSet.getAt(unitIds, i));
+        rI++;
+    }
+}
+
+export function printBonds(structure: Structure) {
+    for (const unit of structure.units) {
+        if (!Unit.isAtomic(unit)) continue;
 
-        const { count, offset, neighbor } = Unit.getGroupBonds(unit, group);
+        const elements = unit.elements;
+        const { count, offset, neighbor } = unit.bonds;
         const { model }  = unit;
 
         if (!count) continue;
@@ -60,9 +87,9 @@ export function printBonds(structure: Structure) {
 
             if (end <= start) continue;
 
-            const aI = ElementGroup.getAt(group, j);
+            const aI = elements[j];
             for (let _bI = start; _bI < end; _bI++) {
-                const bI = ElementGroup.getAt(group, neighbor[_bI])
+                const bI = elements[neighbor[_bI]];
                 console.log(`${atomLabel(model, aI)} -- ${atomLabel(model, bI)}`);
             }
         }
@@ -84,26 +111,23 @@ export function printSequence(model: Model) {
 
 export function printUnits(structure: Structure) {
     console.log('Units\n=============');
-    const { elements, units } = structure;
-    const unitIds = ElementSet.unitIndices(elements);
     const l = Element.Location();
 
-    for (let i = 0, _i = unitIds.length; i < _i; i++) {
-        const unitId = unitIds[i];
-        l.unit = units[unitId];
-        const set = ElementSet.groupAt(elements, i).elements;
-        const size = OrderedSet.size(set);
+    for (const unit of structure.units) {
+        l.unit = unit;
+        const elements = unit.elements;
+        const size = OrderedSet.size(elements);
 
         if (Unit.isAtomic(l.unit)) {
-            console.log(`Atomic unit ${unitId}: ${size} elements`);
+            console.log(`Atomic unit ${unit.id} ${unit.conformation.operator.name}: ${size} elements`);
         } else if (Unit.isCoarse(l.unit)) {
-            console.log(`Coarse unit ${unitId} (${l.unit.elementType === CoarseGrained.ElementType.Sphere ? 'spheres' : 'gaussians'}): ${size} elements.`);
+            console.log(`Coarse unit ${unit.id} ${unit.conformation.operator.name} (${Unit.isSpheres(l.unit) ? 'spheres' : 'gaussians'}): ${size} elements.`);
 
-            const props = Queries.props.coarse_grained;
+            const props = Queries.props.coarse;
             const seq = l.unit.model.sequence;
 
-            for (let j = 0, _j = Math.min(size, 10); j < _j; j++) {
-                l.element = OrderedSet.getAt(set, j);
+            for (let j = 0, _j = Math.min(size, 3); j < _j; j++) {
+                l.element = OrderedSet.getAt(elements, j);
 
                 const residues: string[] = [];
                 const start = props.seq_id_begin(l), end = props.seq_id_end(l);
@@ -111,24 +135,26 @@ export function printUnits(structure: Structure) {
                 for (let e = start; e <= end; e++) residues.push(compId(e));
                 console.log(`${props.asym_id(l)}:${start}-${end} (${residues.join('-')}) ${props.asym_id(l)} [${props.x(l).toFixed(2)}, ${props.y(l).toFixed(2)}, ${props.z(l).toFixed(2)}]`);
             }
-            if (size > 10) console.log(`...`);
+            if (size > 3) console.log(`...`);
         }
     }
 }
 
 
 export function printIHMModels(model: Model) {
-    if (!model.coarseGrained.isDefined) return false;
+    if (!model.coarseHierarchy.isDefined) return false;
     console.log('IHM Models\n=============');
-    console.log(Table.formatToString(model.coarseGrained.modelList));
+    console.log(Table.formatToString(model.coarseHierarchy.models));
 }
 
 async function run(mmcif: mmCIF_Database) {
-    const models = Model.create({ kind: 'mmCIF', data: mmcif });
+    const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run();
     const structure = Structure.ofModel(models[0]);
     printSequence(models[0]);
     printIHMModels(models[0]);
     printUnits(structure);
+    // printBonds(structure);
+    printSecStructure(models[0]);
 }
 
 async function runDL(pdb: string) {

+ 2 - 3
src/apps/structure-info/volume.ts

@@ -12,7 +12,6 @@ import { VolumeData, parseDensityServerData, VolumeIsoValue } from 'mol-model/vo
 import { downloadCif } from './helpers'
 import CIF from 'mol-io/reader/cif'
 import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server';
-import { Run } from 'mol-task';
 import { Table } from 'mol-data/db';
 import { computeVolumeSurface } from 'mol-geo/representation/volume/surface';
 import { StringBuilder } from 'mol-util';
@@ -25,7 +24,7 @@ type Volume = { source: DensityServer_Data_Database, volume: VolumeData }
 async function getVolume(url: string): Promise<Volume> {
     const cif = await downloadCif(url, true);
     const data = CIF.schema.densityServer(cif.blocks[1]);
-    return { source: data, volume: await Run(parseDensityServerData(data)) };
+    return { source: data, volume: await parseDensityServerData(data).run() };
 }
 
 function print(data: Volume) {
@@ -38,7 +37,7 @@ function print(data: Volume) {
 }
 
 async function doMesh(data: Volume, filename: string) {
-    const mesh = await Run(computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)));
+    const mesh = await computeVolumeSurface(data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5)).run();
     console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount });
 
     // Export the mesh in OBJ format.

+ 8 - 8
src/examples/task.ts

@@ -4,11 +4,11 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task, Run, Progress, Scheduler, now, MultistepTask, chunkedSubtask } from 'mol-task'
+import { Task, Progress, Scheduler, now, MultistepTask, chunkedSubtask } from 'mol-task'
 
 export async function test1() {
     const t = Task.create('test', async () => 1);
-    const r = await Run(t);
+    const r = await t.run();
     console.log(r);
 }
 
@@ -53,9 +53,9 @@ export function testTree() {
         if (ctx.shouldUpdate) await ctx.update('hi! 3');
 
         // ctx.update('Running children...', true);
-        const c1 = ctx.runChild(createTask(250, 1));
-        const c2 = ctx.runChild(createTask(500, 2));
-        const c3 = ctx.runChild(createTask(750, 3));
+        const c1 = createTask(250, 1).runAsChild(ctx);
+        const c2 = createTask(500, 2).runAsChild(ctx);
+        const c3 = createTask(750, 3).runAsChild(ctx);
 
         //await ctx.runChild(abortAfter(350));
 
@@ -87,8 +87,8 @@ export const ms = MultistepTask('ms-task', ['step 1', 'step 2', 'step 3'], async
         const s = await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 125 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
         return s.i;
     });
-    
-    await ctx.runChild(child);
+
+    await child.runAsChild(ctx);
     await Scheduler.delay(250);
     await step(1);
     await chunkedSubtask(ctx, 25, { i: 0, current: 0, total: 80 }, processChunk, (ctx, s, p) => ctx.update('chunk test ' + p))
@@ -114,7 +114,7 @@ async function test() {
         //const r = await Run(testTree(), abortingObserver, 250);
         //console.log(r);
 
-        const m = await Run(ms({ i: 10 }), logP);
+        const m = await ms({ i: 10 }).run(logP);
         console.log(m);
     } catch (e) {
         console.error(e);

+ 2 - 2
src/mol-app/service/job.ts

@@ -9,7 +9,7 @@ import { Context } from '../context/context'
 import { JobEvents } from '../event/basic';
 import { PerformanceMonitor } from 'mol-util/performance-monitor';
 import { formatProgress } from 'mol-util';
-import { Progress, Task, Run } from 'mol-task';
+import { Progress, Task } from 'mol-task';
 
 export class Job<T> {
     private info: Job.Info;
@@ -109,7 +109,7 @@ export namespace Job {
             JobEvents.Started.dispatch(this.context, this.info);
             this.context.performance.start('job' + this.info.id);
 
-            this.result = Run(this.task, (p: Progress) => this.progressUpdated(p), 250)
+            this.result = this.task.run((p: Progress) => this.progressUpdated(p), 250)
             this.result.then(() => this.resolved()).catch(e => this.rejected(e));
         }
 

+ 2 - 1
src/mol-data/generic/unique-array.ts

@@ -15,9 +15,10 @@ namespace UniqueArray {
     }
 
     export function add<K, T>({ keys, array }: UniqueArray<K, T>, key: K, value: T) {
-        if (keys.has(key)) return;
+        if (keys.has(key)) return false;
         keys.add(key);
         array[array.length] = value;
+        return true;
     }
 }
 

+ 11 - 0
src/mol-data/int/_spec/sorted-array.spec.ts

@@ -16,6 +16,11 @@ describe('sortedArray', () => {
         it(name, () => expect(a).toEqual(b));
     }
 
+    function compareArrays(a: ArrayLike<number>, b: ArrayLike<number>) {
+        expect(a.length).toBe(b.length);
+        for (let i = 0; i < a.length; i++) expect(a[i]).toBe(b[i]);
+    }
+
     const a1234 = SortedArray.ofSortedArray([1, 2, 3, 4]);
     const a2468 = SortedArray.ofSortedArray([2, 4, 6, 8]);
 
@@ -48,6 +53,12 @@ describe('sortedArray', () => {
 
     testI('findRange', SortedArray.findRange(a2468, 2, 4), Interval.ofRange(0, 1));
 
+    it('deduplicate', () => {
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 1, 1])), [1]);
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 1, 2, 2, 3, 4])), [1, 2, 3, 4]);
+        compareArrays(SortedArray.deduplicate(SortedArray.ofSortedArray([1, 2, 3])), [1, 2, 3]);
+    });
+
     // console.log(Interval.findPredecessorIndexInInterval(Interval.ofBounds(0, 3), 2, Interval.ofBounds(0, 3)))
     // console.log(SortedArray.findPredecessorIndexInInterval(SortedArray.ofSortedArray([0, 1, 2]), 2, Interval.ofBounds(0, 3)))
 });

+ 22 - 0
src/mol-data/int/impl/sorted-array.ts

@@ -15,6 +15,12 @@ export const Empty: Nums = []
 export function ofSingleton(v: number) { return [v]; }
 export function ofSortedArray(xs: Nums) { return xs; }
 export function ofUnsortedArray(xs: Nums) { sortArray(xs); return xs; }
+export function ofRange(min: number, max: number) {
+    if (max < min) return [];
+    const ret = new Int32Array(max - min + 1);
+    for (let i = min; i <= max; i++) ret[i - min] = i;
+    return ret;
+}
 export function is(xs: any): xs is Nums { return xs && (Array.isArray(xs) || !!xs.buffer); }
 
 export function start(xs: Nums) { return xs[0]; }
@@ -267,6 +273,22 @@ export function subtract(a: Nums, b: Nums) {
     return ofSortedArray(indices);
 }
 
+export function deduplicate(xs: Nums) {
+    if (xs.length < 2) return xs;
+    let count = 1;
+    for (let i = 0, _i = xs.length - 1; i < _i; i++) {
+        if (xs[i] !== xs[i + 1]) count++;
+    }
+    if (count === xs.length) return xs;
+    const ret = new Int32Array(count);
+    let o = 0;
+    for (let i = 0, _i = xs.length - 1; i < _i; i++) {
+        if (xs[i] !== xs[i + 1]) ret[o++] = xs[i];
+    }
+    ret[o] = xs[xs.length - 1];
+    return ret;
+}
+
 const _maxIntRangeRet = { startI: 0, startJ: 0, endI: 0, endJ: 0 };
 // for small sets, just gets the whole range, for large sets does a bunch of binary searches
 function getSuitableIntersectionRange(a: Nums, b: Nums) {

+ 6 - 0
src/mol-data/int/sorted-array.ts

@@ -12,6 +12,10 @@ namespace SortedArray {
     export const ofUnsortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofUnsortedArray as any;
     export const ofSingleton: (v: number) => SortedArray = Impl.ofSingleton as any;
     export const ofSortedArray: (xs: ArrayLike<number>) => SortedArray = Impl.ofSortedArray as any;
+    // create sorted array [min, max] (it DOES contain the max value)
+    export const ofRange: (min: number, max: number) => SortedArray = Impl.ofRange as any;
+    // create sorted array [min, max) (it DOES not contain the max value)
+    export const ofBounds: (min: number, max: number) => SortedArray = (min, max) => Impl.ofRange(min, max - 1) as any;
     export const is: (v: any) => v is Interval = Impl.is as any;
 
     export const has: (array: SortedArray, x: number) => boolean = Impl.has as any;
@@ -36,6 +40,8 @@ namespace SortedArray {
     export const findPredecessorIndex: (array: SortedArray, x: number) => number = Impl.findPredecessorIndex as any;
     export const findPredecessorIndexInInterval: (array: SortedArray, x: number, bounds: Interval) => number = Impl.findPredecessorIndexInInterval as any;
     export const findRange: (array: SortedArray, min: number, max: number) => Interval = Impl.findRange as any;
+
+    export const deduplicate: (arrat: SortedArray) => SortedArray = Impl.deduplicate as any;
 }
 
 interface SortedArray extends ArrayLike<number> { '@type': 'int-sorted-array' }

+ 4 - 3
src/mol-data/util/equivalence-classes.ts

@@ -17,6 +17,7 @@ class EquivalenceClassesImpl<K, V> {
         return { id, keys, value };
     }
 
+    // Return the group representative.
     add(key: K, a: V) {
         const hash = this.getHash(a);
         if (this.byHash.has(hash)) {
@@ -25,16 +26,16 @@ class EquivalenceClassesImpl<K, V> {
                 const group = groups[i];
                 if (this.areEqual(a, group.value)) {
                     group.keys[group.keys.length] = key;
-                    return group.id;
+                    return group.value;
                 }
             }
             const group = this.createGroup(key, a);
             groups[groups.length] = group;
-            return group.id;
+            return group.value;
         } else {
             const group = this.createGroup(key, a);
             this.byHash.set(hash, [group]);
-            return group.id;
+            return group.value;
         }
     }
 

+ 8 - 0
src/mol-data/util/hash-functions.ts

@@ -45,6 +45,14 @@ export function hash4(i: number, j: number, k: number, l: number) {
     return a;
 }
 
+export function hashString(s: string) {
+    let h = 0;
+    for (let i = 0, l = s.length; i < l; i++) {
+        h = (h << 5) - h + s.charCodeAt(i++) | 0;
+    }
+    return h;
+}
+
 /**
  * A unique number for each pair of integers
  * Biggest representable pair is (67108863, 67108863) (limit imposed by Number.MAX_SAFE_INTEGER)

+ 3 - 3
src/mol-data/util/sort.ts

@@ -13,10 +13,10 @@ export function arrayLess(arr: ArrayLike<number>, i: number, j: number) {
     return arr[i] - arr[j];
 }
 
-export function arraySwap(arr: any[], i: number, j: number) {
+export function arraySwap(arr: ArrayLike<any>, i: number, j: number) {
     const temp = arr[i];
-    arr[i] = arr[j];
-    arr[j] = temp;
+    (arr as any[])[i] = arr[j];
+    (arr as any[])[j] = temp;
 }
 
 function medianPivotIndex(data: any, cmp: Comparer, l: number, r: number) {

+ 13 - 43
src/mol-geo/representation/structure/index.ts

@@ -2,11 +2,10 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ElementGroup, ElementSet, Structure, Unit } from 'mol-model/structure';
-import { EquivalenceClasses } from 'mol-data/util';
-import { OrderedSet } from 'mol-data/int'
+import { Structure, StructureSymmetry, Unit } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { RenderObject } from 'mol-gl/scene';
 import { Representation, RepresentationProps } from '..';
@@ -15,7 +14,7 @@ import { Representation, RepresentationProps } from '..';
 
 export interface UnitsRepresentation<P> {
     renderObjects: ReadonlyArray<RenderObject>
-    create: (units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: P) => Task<void>
+    create: (group: Unit.SymmetryGroup, props: P) => Task<void>
     update: (props: P) => Task<boolean>
 }
 
@@ -27,8 +26,7 @@ export interface StructureRepresentation<P extends RepresentationProps = {}> ext
 
 interface GroupRepresentation<T> {
     repr: UnitsRepresentation<T>
-    units: Unit[]
-    elementGroup: ElementGroup
+    group: Unit.SymmetryGroup
 }
 
 export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P>): StructureRepresentation<P> {
@@ -39,40 +37,12 @@ export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P
         renderObjects,
         create(structure: Structure, props: P = {} as P) {
             return Task.create('StructureRepresentation.create', async ctx => {
-                const { elements, units } = structure;
-                const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
-                    ({ unit, group }) => ElementGroup.hashCode(group),
-                    (a, b) => a.unit.model.id === b.unit.model.id && OrderedSet.areEqual(a.group.elements, b.group.elements)
-                );
-
-                // const uniqueTransformations = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>(
-                //     ({ unit, group }) => unit.operator.matrix.join(','),
-                //     (a, b) => Mat4.areEqual(a.unit.operator.matrix, b.unit.operator.matrix, EPSILON.Value)
-                // );
-
-                const unitIndices = ElementSet.unitIndices(elements);
-                for (let i = 0, _i = unitIndices.length; i < _i; i++) {
-                    const unitIndex = unitIndices[i];
-                    const group = ElementSet.groupFromUnitIndex(elements, unitIndex);
-                    const unit = units[unitIndex]
-                    uniqueGroups.add(unitIndex, { unit, group });
-                    // uniqueTransformations.add(unitIndex, { unit, group });
-                }
-
-                // console.log({ uniqueGroups, uniqueTransformations })
-
-                for (let i = 0, il = uniqueGroups.groups.length; i < il; i++) {
-                    const groupUnits: Unit[] = []
-                    const group = uniqueGroups.groups[i]
-                    // console.log('group', i)
-                    for (let j = 0, jl = group.length; j < jl; j++) {
-                        groupUnits.push(units[group[j]])
-                    }
-                    const elementGroup = ElementSet.groupFromUnitIndex(elements, group[0])
+                const groups = StructureSymmetry.getTransformGroups(structure);
+                for (let i = 0; i < groups.length; i++) {
+                    const group = groups[i];
                     const repr = reprCtor()
-                    groupReprs.push({ repr, units: groupUnits, elementGroup })
-                    await ctx.update({ message: 'Building structure unit representations...', current: i, max: il });
-                    await ctx.runChild(repr.create(groupUnits, elementGroup, props));
+                    groupReprs.push({ repr, group })
+                    await repr.create(group, props).runAsChild(ctx, { message: 'Building structure unit representations...', current: i, max: groups.length });
                     renderObjects.push(...repr.renderObjects)
                 }
             });
@@ -83,10 +53,10 @@ export function StructureRepresentation<P>(reprCtor: () => UnitsRepresentation<P
                 renderObjects.length = 0 // clear
                 for (let i = 0, il = groupReprs.length; i < il; ++i) {
                     const groupRepr = groupReprs[i]
-                    const { repr, units, elementGroup } = groupRepr
-                    await ctx.update({ message: 'Updating structure unit representations...', current: i, max: il });
-                    if (!await ctx.runChild(repr.update(props))) {
-                        await ctx.runChild(repr.create(units, elementGroup, props))
+                    const { repr, group } = groupRepr
+                    const state = { message: 'Updating structure unit representations...', current: i, max: il };
+                    if (!await repr.update(props).runAsChild(ctx, state)) {
+                        await repr.create(group, props).runAsChild(ctx, state)
                     }
                     renderObjects.push(...repr.renderObjects)
                 }

+ 19 - 17
src/mol-geo/representation/structure/point.ts

@@ -2,12 +2,12 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { ValueCell } from 'mol-util/value-cell'
 import { createPointRenderObject, RenderObject, PointRenderObject } from 'mol-gl/scene'
-import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup, Element } from 'mol-model/structure';
+import { Unit, Element } from 'mol-model/structure';
 import { Task } from 'mol-task'
 import { fillSerial } from 'mol-gl/renderable/util';
 
@@ -16,6 +16,7 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { createTransforms, createColors, createSizes } from './utils';
 import { deepEqual } from 'mol-util';
+import { SortedArray } from 'mol-data/int';
 
 export const DefaultPointProps = {
     colorTheme: { name: 'instance-index' } as ColorTheme,
@@ -25,16 +26,17 @@ export const DefaultPointProps = {
 }
 export type PointProps = Partial<typeof DefaultPointProps>
 
-export function createPointVertices(unit: Unit, elementGroup: ElementGroup) {
-    const elementCount = OrderedSet.size(elementGroup.elements)
+export function createPointVertices(unit: Unit) {
+    const elements = unit.elements
+    const elementCount = elements.length
     const vertices = new Float32Array(elementCount * 3)
 
-    const { x, y, z } = unit
+    const { x, y, z } = unit.conformation
     const l = Element.Location()
     l.unit = unit
 
     for (let i = 0; i < elementCount; i++) {
-        l.element = ElementGroup.getAt(elementGroup, i)
+        l.element = elements[i];
         const i3 = i * 3
         vertices[i3] = x(l.element)
         vertices[i3 + 1] = y(l.element)
@@ -49,21 +51,21 @@ export default function Point(): UnitsRepresentation<PointProps> {
     let curProps = DefaultPointProps
 
     let _units: ReadonlyArray<Unit>
-    let _elementGroup: ElementGroup
+    let _elements: SortedArray
 
     return {
         renderObjects,
-        create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: PointProps = {}) {
+        create(group: Unit.SymmetryGroup, props: PointProps = {}) {
             return Task.create('Point.create', async ctx => {
                 renderObjects.length = 0 // clear
                 curProps = { ...DefaultPointProps, ...props }
 
-                _units = units
-                _elementGroup = elementGroup
+                _units = group.units
+                _elements = group.elements;
 
                 const { colorTheme, sizeTheme, alpha, visible } = curProps
-                const elementCount = OrderedSet.size(elementGroup.elements)
-                const unitCount = units.length
+                const elementCount = _elements.length
+                const unitCount = _units.length
 
                 const vertexMap = VertexMap.create(
                     elementCount,
@@ -73,16 +75,16 @@ export default function Point(): UnitsRepresentation<PointProps> {
                 )
 
                 await ctx.update('Computing point vertices');
-                const vertices = createPointVertices(units[0], elementGroup)
+                const vertices = createPointVertices(_units[0])
 
                 await ctx.update('Computing point transforms');
-                const transforms = createTransforms(units)
+                const transforms = createTransforms(group)
 
                 await ctx.update('Computing point colors');
-                const color = createColors(units, elementGroup, vertexMap, colorTheme)
+                const color = createColors(group, vertexMap, colorTheme)
 
                 await ctx.update('Computing point sizes');
-                const size = createSizes(units, elementGroup, vertexMap, sizeTheme)
+                const size = createSizes(group, vertexMap, sizeTheme)
 
                 points = createPointRenderObject({
                     objectId: 0,
@@ -106,7 +108,7 @@ export default function Point(): UnitsRepresentation<PointProps> {
         },
         update(props: PointProps) {
             return Task.create('Point.update', async ctx => {
-                if (!points || !_units || !_elementGroup) return false
+                if (!points || !_units || !_elements) return false
 
                 const newProps = { ...curProps, ...props }
                 if (deepEqual(curProps, newProps)) {

+ 15 - 16
src/mol-geo/representation/structure/spacefill.ts

@@ -2,6 +2,7 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
 import { ValueCell } from 'mol-util/value-cell'
@@ -9,15 +10,13 @@ import { ValueCell } from 'mol-util/value-cell'
 import { RenderObject, createMeshRenderObject, MeshRenderObject } from 'mol-gl/scene'
 // import { createColorTexture } from 'mol-gl/util';
 import { Vec3, Mat4 } from 'mol-math/linear-algebra'
-import { OrderedSet } from 'mol-data/int'
-import { Unit, ElementGroup, Element, Queries } from 'mol-model/structure';
+import { Unit, Element, Queries } from 'mol-model/structure';
 import { UnitsRepresentation } from './index';
 import { Task } from 'mol-task'
 import { MeshBuilder } from '../../shape/mesh-builder';
 import { createTransforms, createColors } from './utils';
 import { ColorTheme } from '../../theme';
 import VertexMap from '../../shape/vertex-map';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
 import { icosahedronVertexCount } from '../../primitive/icosahedron';
 
 export const DefaultSpacefillProps = {
@@ -29,17 +28,18 @@ export const DefaultSpacefillProps = {
 }
 export type SpacefillProps = Partial<typeof DefaultSpacefillProps>
 
-function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: number) {
+function createSpacefillMesh(unit: Unit, detail: number) {
     return Task.create('Sphere mesh', async ctx => {
-        const elementCount = OrderedSet.size(elementGroup.elements)
+        const { elements } = unit;
+        const elementCount = elements.length;
         const vertexCount = elementCount * icosahedronVertexCount(detail)
         const meshBuilder = MeshBuilder.create(vertexCount)
 
         let radius: Element.Property<number>
         if (Unit.isAtomic(unit)) {
             radius = Queries.props.atom.vdw_radius
-        } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
-            radius = Queries.props.coarse_grained.sphere_radius
+        } else if (Unit.isSpheres(unit)) {
+            radius = Queries.props.coarse.sphere_radius
         } else {
             console.warn('Unsupported unit type')
             return meshBuilder.getMesh()
@@ -48,12 +48,12 @@ function createSpacefillMesh(unit: Unit, elementGroup: ElementGroup, detail: num
         const v = Vec3.zero()
         const m = Mat4.identity()
 
-        const { x, y, z } = unit
+        const { x, y, z } = unit.conformation
         const l = Element.Location()
         l.unit = unit
 
         for (let i = 0; i < elementCount; i++) {
-            l.element = ElementGroup.getAt(elementGroup, i)
+            l.element = elements[i]
             v[0] = x(l.element)
             v[1] = y(l.element)
             v[2] = z(l.element)
@@ -77,23 +77,22 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
 
     return {
         renderObjects,
-        create(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, props: SpacefillProps = {}) {
+        create(group: Unit.SymmetryGroup, props: SpacefillProps = {}) {
             return Task.create('Spacefill.create', async ctx => {
                 renderObjects.length = 0 // clear
 
                 const { detail, colorTheme, alpha, visible, doubleSided } = { ...DefaultSpacefillProps, ...props }
 
-                await ctx.update('Computing spacefill mesh');
-                const mesh = await ctx.runChild(createSpacefillMesh(units[0], elementGroup, detail))
+                const mesh = await createSpacefillMesh(group.units[0], detail).runAsChild(ctx, 'Computing spacefill mesh')
                 // console.log(mesh)
 
                 const vertexMap = VertexMap.fromMesh(mesh)
 
                 await ctx.update('Computing spacefill transforms');
-                const transforms = createTransforms(units)
+                const transforms = createTransforms(group)
 
                 await ctx.update('Computing spacefill colors');
-                const color = createColors(units, elementGroup, vertexMap, colorTheme)
+                const color = createColors(group, vertexMap, colorTheme)
 
                 spheres = createMeshRenderObject({
                     objectId: 0,
@@ -108,9 +107,9 @@ export default function Spacefill(): UnitsRepresentation<SpacefillProps> {
                     transform: ValueCell.create(transforms),
                     index: mesh.indexBuffer,
 
-                    instanceCount: units.length,
+                    instanceCount: group.units.length,
                     indexCount: mesh.triangleCount,
-                    elementCount: OrderedSet.size(elementGroup.elements),
+                    elementCount: group.elements.length,
                     positionCount: mesh.vertexCount
                 })
                 renderObjects.push(spheres)

+ 11 - 10
src/mol-geo/representation/structure/utils.ts

@@ -2,9 +2,10 @@
  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Unit, ElementGroup } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import { Mat4 } from 'mol-math/linear-algebra'
 
 import { createUniformColor } from '../../util/color-data';
@@ -14,35 +15,35 @@ import VertexMap from '../../shape/vertex-map';
 import { ColorTheme, SizeTheme } from '../../theme';
 import { elementIndexColorData, elementSymbolColorData, instanceIndexColorData, chainIdColorData } from '../../theme/structure/color';
 
-export function createTransforms(units: ReadonlyArray<Unit>) {
+export function createTransforms({ units }: Unit.SymmetryGroup) {
     const unitCount = units.length
     const transforms = new Float32Array(unitCount * 16)
     for (let i = 0; i < unitCount; i++) {
-        Mat4.toArray(units[i].operator.matrix, transforms, i * 16)
+        Mat4.toArray(units[i].conformation.operator.matrix, transforms, i * 16)
     }
     return transforms
 }
 
-export function createColors(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: ColorTheme) {
+export function createColors(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: ColorTheme) {
     switch (props.name) {
         case 'atom-index':
-            return elementIndexColorData({ units, elementGroup, vertexMap })
+            return elementIndexColorData({ group, vertexMap })
         case 'chain-id':
-            return chainIdColorData({ units, elementGroup, vertexMap })
+            return chainIdColorData({ group, vertexMap })
         case 'element-symbol':
-            return elementSymbolColorData({ units, elementGroup, vertexMap })
+            return elementSymbolColorData({ group, vertexMap })
         case 'instance-index':
-            return instanceIndexColorData({ units, elementGroup, vertexMap })
+            return instanceIndexColorData({ group, vertexMap })
         case 'uniform':
             return createUniformColor(props)
     }
 }
 
-export function createSizes(units: ReadonlyArray<Unit>, elementGroup: ElementGroup, vertexMap: VertexMap, props: SizeTheme) {
+export function createSizes(group: Unit.SymmetryGroup, vertexMap: VertexMap, props: SizeTheme) {
     switch (props.name) {
         case 'uniform':
             return createUniformSize(props)
         case 'vdw':
-            return elementSizeData({ units, elementGroup, vertexMap })
+            return elementSizeData({ group, vertexMap })
     }
 }

+ 1 - 2
src/mol-geo/representation/volume/index.ts

@@ -29,8 +29,7 @@ export function VolumeRepresentation<P>(reprCtor: () => VolumeElementRepresentat
         create(volumeData: VolumeData, props: P = {} as P) {
             return Task.create('VolumeRepresentation.create', async ctx => {
                 const repr = reprCtor()
-                await ctx.update({ message: 'Building volume representation...', current: 0, max: 1 });
-                await ctx.runChild(repr.create(volumeData, props));
+                await repr.create(volumeData, props).runAsChild(ctx, { message: 'Building volume representation...', current: 0, max: 1 });
                 renderObjects.push(...repr.renderObjects)
             });
         },

+ 3 - 3
src/mol-geo/representation/volume/surface.ts

@@ -20,10 +20,10 @@ export function computeVolumeSurface(volume: VolumeData, isoValue: VolumeIsoValu
     return Task.create<Mesh>('Volume Surface', async ctx => {
         ctx.update({ message: 'Marching cubes...' });
 
-        const mesh = await ctx.runChild(computeMarchingCubes({
+        const mesh = await computeMarchingCubes({
             isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue,
             scalarField: volume.data
-        }));
+        }).runAsChild(ctx);
 
         const transform = VolumeData.getGridToCartesianTransform(volume);
         ctx.update({ message: 'Transforming mesh...' });
@@ -56,7 +56,7 @@ export default function Surface(): VolumeElementRepresentation<SurfaceProps> {
                 curProps = { ...DefaultSurfaceProps, ...props }
                 const { alpha, visible, flatShaded, flipSided, doubleSided } = curProps
 
-                const mesh = await ctx.runChild(computeVolumeSurface(volume, curProps.isoValue))
+                const mesh = await computeVolumeSurface(volume, curProps.isoValue).runAsChild(ctx)
                 if (!flatShaded) {
                     Mesh.computeNormalsImmediate(mesh)
                 }

+ 8 - 8
src/mol-geo/theme/structure/color/chain-id.ts

@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit, Queries, Element } from 'mol-model/structure';
+import { Unit, Queries, Element } from 'mol-model/structure';
 
 import { StructureColorDataProps } from '.';
 import { createAttributeOrElementColor } from '../../../util/color-data';
@@ -18,11 +18,11 @@ function createChainIdMap(unit: Unit) {
     let count: number
     let asym_id: Column<string>
     if (Unit.isAtomic(unit)) {
-        asym_id = unit.hierarchy.chains.label_asym_id
-        count = unit.hierarchy.chains._rowCount
+        asym_id = unit.model.atomicHierarchy.chains.label_asym_id
+        count = unit.model.atomicHierarchy.chains._rowCount
     } else if (Unit.isCoarse(unit)) {
-        asym_id = unit.siteBases.asym_id
-        count = unit.siteBases.count
+        asym_id = unit.coarseElements.asym_id
+        count = unit.coarseElements.count
     } else {
         console.warn('Unknown unit type')
         return { map, count: index }
@@ -39,7 +39,7 @@ function createChainIdMap(unit: Unit) {
 }
 
 export function chainIdColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
+    const { group: { units, elements }, vertexMap } = props
     const unit = units[0]
 
     const { map, count } = createChainIdMap(unit)
@@ -51,7 +51,7 @@ export function chainIdColorData(props: StructureColorDataProps) {
     if (Unit.isAtomic(unit)) {
         asym_id = Queries.props.chain.label_asym_id
     } else if (Unit.isCoarse(unit)) {
-        asym_id = Queries.props.coarse_grained.asym_id
+        asym_id = Queries.props.coarse.asym_id
     }
 
     const l = Element.Location()
@@ -59,7 +59,7 @@ export function chainIdColorData(props: StructureColorDataProps) {
 
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
-            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            l.element = elements[elementIdx]
             return scale.color(map.get(asym_id(l)) || 0)
         },
         vertexMap

+ 2 - 3
src/mol-geo/theme/structure/color/element-index.ts

@@ -6,13 +6,12 @@
 
 import { ColorScale } from 'mol-util/color';
 import { StructureColorDataProps } from '.';
-import { OrderedSet } from 'mol-data/int';
 import { createElementInstanceColor } from '../../../util/color-data';
 
 export function elementIndexColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
+    const { group: { units, elements }, vertexMap } = props
     const instanceCount = units.length
-    const elementCount = OrderedSet.size(elementGroup.elements)
+    const elementCount = elements.length
 
     const domain = [ 0, instanceCount * elementCount - 1 ]
     const scale = ColorScale.create({ domain })

+ 3 - 4
src/mol-geo/theme/structure/color/element-symbol.ts

@@ -7,7 +7,6 @@
 import { ElementSymbol } from 'mol-model/structure/model/types';
 import { Color } from 'mol-util/color';
 import { StructureColorDataProps } from '.';
-import { OrderedSet } from 'mol-data/int';
 import { createAttributeOrElementColor } from '../../../util/color-data';
 
 // from Jmol http://jmol.sourceforge.net/jscolors/ (or 0xFFFFFF)
@@ -23,11 +22,11 @@ export function elementSymbolColor(element: ElementSymbol): Color {
 }
 
 export function elementSymbolColorData(props: StructureColorDataProps) {
-    const { units, elementGroup, vertexMap } = props
-    const { type_symbol } = units[0].model.hierarchy.atoms
+    const { group: { units, elements }, vertexMap } = props
+    const { type_symbol } = units[0].model.atomicHierarchy.atoms
     return createAttributeOrElementColor(vertexMap, {
         colorFn: (elementIdx: number) => {
-            const e = OrderedSet.getAt(elementGroup.elements, elementIdx)
+            const e = elements[elementIdx]
             return elementSymbolColor(type_symbol.value(e))
         },
         vertexMap

+ 2 - 3
src/mol-geo/theme/structure/color/index.ts

@@ -4,12 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureColorDataProps {
-    units: ReadonlyArray<Unit>,
-    elementGroup: ElementGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 

+ 1 - 1
src/mol-geo/theme/structure/color/instance-index.ts

@@ -9,7 +9,7 @@ import { StructureColorDataProps } from '.';
 import { createInstanceColor } from '../../../util/color-data';
 
 export function instanceIndexColorData(props: StructureColorDataProps) {
-    const { units } = props
+    const { group: { units } } = props
     const instanceCount = units.length
 
     const domain = [ 0, instanceCount - 1 ]

+ 7 - 7
src/mol-geo/theme/structure/size/element.ts

@@ -4,26 +4,26 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Element, Unit, Queries } from 'mol-model/structure';
-import CoarseGrained from 'mol-model/structure/model/properties/coarse-grained';
+import { Element, Unit, Queries } from 'mol-model/structure';
 import { StructureSizeDataProps } from '.';
 import { createAttributeSize } from '../../../util/size-data';
 
 /** Create attribute data with the size of an element, i.e. vdw for atoms and radius for coarse spheres */
 export function elementSizeData(props: StructureSizeDataProps) {
-    const { units, elementGroup, vertexMap } = props
-    const unit = units[0]
+    const { group, vertexMap } = props
+    const unit = group.units[0]
+    const elements = group.elements;
     let radius: Element.Property<number>
     if (Unit.isAtomic(unit)) {
         radius = Queries.props.atom.vdw_radius
-    } else if (Unit.isCoarse(unit) && unit.elementType === CoarseGrained.ElementType.Sphere) {
-        radius = Queries.props.coarse_grained.sphere_radius
+    } else if (Unit.isSpheres(unit)) {
+        radius = Queries.props.coarse.sphere_radius
     }
     const l = Element.Location()
     l.unit = unit
     return createAttributeSize({
         sizeFn: (elementIdx: number) => {
-            l.element = ElementGroup.getAt(elementGroup, elementIdx)
+            l.element = elements[elementIdx]
             return radius(l)
         },
         vertexMap

+ 2 - 3
src/mol-geo/theme/structure/size/index.ts

@@ -4,12 +4,11 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { ElementGroup, Unit } from 'mol-model/structure';
+import { Unit } from 'mol-model/structure';
 import VertexMap from '../../../shape/vertex-map';
 
 export interface StructureSizeDataProps {
-    units: ReadonlyArray<Unit>,
-    elementGroup: ElementGroup,
+    group: Unit.SymmetryGroup,
     vertexMap: VertexMap
 }
 

+ 3 - 4
src/mol-io/reader/_spec/csv.spec.ts

@@ -5,7 +5,6 @@
  */
 
 import Csv from '../csv/parser'
-import { Run } from 'mol-task';
 
 const csvStringBasic = `StrCol,IntCol,FloatCol
 # comment
@@ -24,7 +23,7 @@ string2\t42\t2.44`
 
 describe('csv reader', () => {
     it('basic', async () => {
-        const parsed = await Run(Csv(csvStringBasic));
+        const parsed = await Csv(csvStringBasic).run();
         if (parsed.isError) return;
         const csvFile = parsed.result;
 
@@ -46,7 +45,7 @@ describe('csv reader', () => {
     });
 
     it('advanced', async () => {
-        const parsed = await Run(Csv(csvStringAdvanced));
+        const parsed = await Csv(csvStringAdvanced).run();
         if (parsed.isError) return;
         const csvFile = parsed.result;
 
@@ -63,7 +62,7 @@ describe('csv reader', () => {
     });
 
     it('tabs', async () => {
-        const parsed = await Run(Csv(tabString, { delimiter: '\t' }));
+        const parsed = await Csv(tabString, { delimiter: '\t' }).run();;
         if (parsed.isError) return;
         const csvFile = parsed.result;
 

+ 2 - 3
src/mol-io/reader/_spec/gro.spec.ts

@@ -6,7 +6,6 @@
  */
 
 import Gro from '../gro/parser'
-import { Run } from 'mol-task';
 
 const groString = `MD of 2 waters, t= 4.2
     6
@@ -27,7 +26,7 @@ const groStringHighPrecision = `Generated by trjconv : 2168 system t=  15.00000
 
 describe('gro reader', () => {
     it('basic', async () => {
-        const parsed = await Run(Gro(groString));
+        const parsed = await Gro(groString).run();
 
         if (parsed.isError) {
             console.log(parsed)
@@ -58,7 +57,7 @@ describe('gro reader', () => {
     });
 
     it('high precision', async () => {
-        const parsed = await Run(Gro(groStringHighPrecision));
+        const parsed = await Gro(groStringHighPrecision).run();
 
         if (parsed.isError) {
             console.log(parsed)

+ 3 - 4
src/mol-io/reader/_spec/mol2.spec.ts

@@ -1,6 +1,5 @@
 
 import Mol2 from '../mol2/parser'
-import { Run } from 'mol-task';
 
 const Mol2String = `@<TRIPOS>MOLECULE
 5816
@@ -247,7 +246,7 @@ GASTEIGER
 
 describe('mol2 reader', () => {
     it('basic', async () => {
-        const parsed =  await Run(Mol2(Mol2String));
+        const parsed =  await Mol2(Mol2String).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }
@@ -298,7 +297,7 @@ describe('mol2 reader', () => {
     });
 
     it('multiblocks', async () => {
-        const parsed =  await Run(Mol2(Mol2StringMultiBlocks));
+        const parsed =  await Mol2(Mol2StringMultiBlocks).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }
@@ -349,7 +348,7 @@ describe('mol2 reader', () => {
     });
 
     it('minimal', async () => {
-        const parsed =  await Run(Mol2(Mol2StringMinimal));
+        const parsed =  await Mol2(Mol2StringMinimal).run();
         if (parsed.isError) {
             throw new Error(parsed.message);
         }

+ 5 - 0
src/mol-io/reader/cif/schema/mmcif.ts

@@ -21,6 +21,11 @@ const Vector = Schema.Vector;
 const List = Schema.List;
 
 export const mmCIF_Schema = {
+    atom_sites: {
+        entry_id: str,
+        fract_transf_matrix: Matrix(3, 3),
+        fract_transf_vector: Vector(3)
+    },
     atom_site: {
         auth_asym_id: str,
         auth_atom_id: str,

+ 17 - 19
src/mol-math/geometry/spacegroup/construction.ts

@@ -5,11 +5,12 @@
  */
 
 import { Vec3, Mat4 } from '../../linear-algebra'
-import { SpacegroupName, TransformData, GroupData, SpacegroupNumbers, SpacegroupNames, OperatorData } from './tables'
+import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNames } from './tables'
 import { SymmetryOperator } from '../../geometry';
 
 interface SpacegroupCell {
-    readonly number: number,
+    // zero based spacegroup number
+    readonly index: number,
     readonly size: Vec3,
     readonly anglesInRadians: Vec3,
     /** Transfrom cartesian -> fractional coordinates within the cell */
@@ -26,16 +27,18 @@ interface Spacegroup {
 
 namespace SpacegroupCell {
     // Create a 'P 1' with cellsize [1, 1, 1]
-    export function zero() {
-        return create(0, Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
-    }
+    export const Zero: SpacegroupCell = create('P 1', Vec3.create(1, 1, 1), Vec3.create(Math.PI / 2, Math.PI / 2, Math.PI / 2));
 
     // True if 'P 1' with cellsize [1, 1, 1]
     export function isZero(cell: SpacegroupCell) {
-        return cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
+        return cell.index === 0 && cell.size[0] === 1 && cell.size[1] === 1 && cell.size[1] === 1;
     }
 
-    export function create(nameOrNumber: number | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell {
+    // returns Zero cell if the spacegroup does not exist
+    export function create(nameOrNumber: number | string | SpacegroupName, size: Vec3, anglesInRadians: Vec3): SpacegroupCell {
+        const index = getSpacegroupIndex(nameOrNumber);
+        if (index < 0) return Zero;
+
         const alpha = anglesInRadians[0];
         const beta = anglesInRadians[1];
         const gamma = anglesInRadians[2];
@@ -58,23 +61,18 @@ namespace SpacegroupCell {
         ]);
         const toFractional = Mat4.invert(Mat4.zero(), fromFractional)!;
 
-        const num = typeof nameOrNumber === 'number' ? nameOrNumber : SpacegroupNumbers[nameOrNumber];
-        return { number: num, size, anglesInRadians, toFractional, fromFractional };
+        return { index, size, anglesInRadians, toFractional, fromFractional };
     }
 }
 
-namespace Spacegroup {
-    export function create(nameOrNumber: number | SpacegroupName, cell: SpacegroupCell): Spacegroup {
-        const num = typeof nameOrNumber === 'number' ? nameOrNumber : SpacegroupNumbers[nameOrNumber];
-        const name = typeof nameOrNumber === 'number' ? SpacegroupNames[nameOrNumber] : nameOrNumber;
 
-        if (typeof num === 'undefined' || typeof name === 'undefined') {
-            throw new Error(`Spacegroup '${nameOrNumber}' is not defined.`);
-        }
-
-        const operators = GroupData[num].map(i => getOperatorMatrix(OperatorData[i]));
+namespace Spacegroup {
+    // P1 with [1, 1, 1] cell.
+    export const ZeroP1 = create(SpacegroupCell.Zero);
 
-        return { name, cell, operators };
+    export function create(cell: SpacegroupCell): Spacegroup {
+        const operators = GroupData[cell.index].map(i => getOperatorMatrix(OperatorData[i]));
+        return { name: SpacegroupNames[cell.index], cell, operators };
     }
 
     const _tempVec = Vec3.zero(), _tempMat = Mat4.zero();

+ 12 - 5
src/mol-math/geometry/spacegroup/tables.ts

@@ -985,7 +985,7 @@ export const GroupData = [
     [0, 22, 57, 3, 159, 279, 654, 655, 158, 274, 656, 657, 29, 18, 25, 27, 284, 658, 262, 269, 280, 659, 257, 267],
 ];
 
-export const SpacegroupNumbers = {
+export const ZeroBasedSpacegroupNumbers = {
     'P 1': 0,
     'P -1': 1,
     'P 1 2 1': 2,
@@ -1333,12 +1333,19 @@ export const SpacegroupNumbers = {
     'I 2 3a': 265,
 };
 
-export type SpacegroupName = keyof typeof SpacegroupNumbers
+export type SpacegroupName = keyof typeof ZeroBasedSpacegroupNumbers
 
 export const SpacegroupNames: { [num: number]: SpacegroupName } = (function () {
     const names = Object.create(null);
-    for (const n of Object.keys(SpacegroupNumbers)) {
-        names[(SpacegroupNumbers as any)[n]] = n;
+    for (const n of Object.keys(ZeroBasedSpacegroupNumbers)) {
+        names[(ZeroBasedSpacegroupNumbers as any)[n]] = n;
     }
     return names;
-}());
+}());
+
+// return -1 if the spacegroup does not exist.
+export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number {
+    const index = typeof nameOrNumber === 'number' ? nameOrNumber - 1 : ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName];
+    if (typeof index === 'undefined' || typeof SpacegroupNames[index] === 'undefined') return -1;
+    return index;
+}

+ 2 - 2
src/mol-math/geometry/symmetry-operator.ts

@@ -48,11 +48,11 @@ namespace SymmetryOperator {
 
     export interface Coordinates { x: ArrayLike<number>, y: ArrayLike<number>, z: ArrayLike<number> }
 
-    export function createMapping(operator: SymmetryOperator, coords: Coordinates) {
+    export function createMapping(operator: SymmetryOperator, coords: Coordinates): ArrayMapping {
         const invariantPosition = SymmetryOperator.createCoordinateMapper(SymmetryOperator.Default, coords);
         const position = operator.isIdentity ? invariantPosition : SymmetryOperator.createCoordinateMapper(operator, coords);
         const { x, y, z } = createProjections(operator, coords);
-        return { invariantPosition, position, x, y, z };
+        return { operator, invariantPosition, position, x, y, z };
     }
 
     export function createCoordinateMapper(t: SymmetryOperator, coords: Coordinates): CoordinateMapper {

+ 5 - 1
src/mol-math/linear-algebra/3d/common.ts

@@ -17,4 +17,8 @@
  * furnished to do so, subject to the following conditions:
  */
 
-export const enum EPSILON { Value = 0.000001 }
+export const enum EPSILON { Value = 0.000001 }
+
+export function equalEps(a: number, b: number, eps: number) {
+    return Math.abs(a - b) <= eps;
+}

+ 4 - 0
src/mol-math/linear-algebra/3d/mat3.ts

@@ -104,6 +104,10 @@ namespace Mat3 {
         return Mat3.copy(Mat3.zero(), a);
     }
 
+    export function setValue(a: Mat3, i: number, j: number, value: number) {
+        a[3 * j + i] = value;
+    }
+
     /**
      * Copy the values from one Mat3 to another
      */

+ 3 - 3
src/mol-math/linear-algebra/3d/mat4.ts

@@ -17,7 +17,7 @@
  * furnished to do so, subject to the following conditions:
  */
 
-import { EPSILON } from './common'
+import { EPSILON, equalEps } from './common'
 import Vec3 from './vec3';
 import Quat from './quat';
 
@@ -539,11 +539,11 @@ namespace Mat4 {
             a20 = a[8], a21 = a[9], a22 = a[10], a23 = a[11],
             /* a30 = a[12], a31 = a[13], a32 = a[14],*/ a33 = a[15];
 
-        if (a33 !== 1 || a03 !== 0 || a13 !== 0 || a23 !== 0) {
+        if (!equalEps(a33, 1, eps) || !equalEps(a03, 0, eps) || !equalEps(a13, 0, eps) || !equalEps(a23, 0, eps)) {
             return false;
         }
         const det3x3 = a00 * (a11 * a22 - a12 * a21) - a01 * (a10 * a22 - a12 * a20) + a02 * (a10 * a21 - a11 * a20);
-        if (det3x3 < 1 - eps || det3x3 > 1 + eps) {
+        if (!equalEps(det3x3, 1, eps)) {
             return false;
         }
         return true;

+ 11 - 1
src/mol-math/linear-algebra/tensor.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Mat4, Vec3, Vec4 } from './3d'
+import { Mat4, Vec3, Vec4, Mat3 } from './3d'
 
 export interface Tensor { data: Tensor.Data, space: Tensor.Space }
 
@@ -65,6 +65,16 @@ export namespace Tensor {
         return mat;
     }
 
+    export function toMat3(space: Space, data: Tensor.Data): Mat3 {
+        if (space.rank !== 2) throw new Error('Invalid tensor rank');
+        const mat = Mat3.zero();
+        const d0 = Math.min(3, space.dimensions[0]), d1 = Math.min(3, space.dimensions[1]);
+        for (let i = 0; i < d0; i++) {
+            for (let j = 0; j < d1; j++) Mat3.setValue(mat, i, j, space.get(data, i, j));
+        }
+        return mat;
+    }
+
     export function toVec3(space: Space, data: Tensor.Data): Vec3 {
         if (space.rank !== 1) throw new Error('Invalid tensor rank');
         const vec = Vec3.zero();

+ 0 - 202
src/mol-model/structure/_spec/atom-set.spec.ts

@@ -1,202 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { OrderedSet } from 'mol-data/int'
-import ElementSet from '../structure/element/set'
-import Element from '../structure/element'
-import ElementGroup from '../structure/element/group'
-
-describe('atom set', () => {
-    const p = (i: number, j: number) => Element.create(i, j);
-
-    function setToPairs(set: ElementSet): ArrayLike<Element> {
-        const ret: Element[] = [];
-        const it = ElementSet.elements(set);
-        while (it.hasNext) {
-            ret[ret.length] = it.move();
-        }
-        return ret;
-    }
-
-    it('singleton pair', () => {
-        const set = ElementSet.ofAtoms([p(10, 11)], ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(true);
-        expect(ElementSet.elementHas(set, p(11, 11))).toBe(false);
-        expect(ElementSet.elementAt(set, 0)).toBe(p(10, 11));
-        expect(ElementSet.elementCount(set)).toBe(1);
-    });
-
-    it('singleton atom', () => {
-        const set = ElementSet.singleton(p(10, 11), ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(10, 11)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(true);
-        expect(ElementSet.elementHas(set, p(11, 11))).toBe(false);
-        expect(ElementSet.elementAt(set, 0)).toBe(p(10, 11));
-        expect(ElementSet.elementCount(set)).toBe(1);
-    });
-
-    it('multi', () => {
-        const gen = ElementSet.Generator();
-        gen.add(1, ElementGroup.createNew(OrderedSet.ofSortedArray([4, 6, 7])));
-        gen.add(3, ElementGroup.createNew(OrderedSet.ofRange(0, 1)));
-        const set = gen.getSet();
-        const ret = [p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)];
-        expect(ElementSet.elementCount(set)).toBe(ret.length);
-        expect(setToPairs(set)).toEqual([p(1, 4), p(1, 6), p(1, 7), p(3, 0), p(3, 1)]);
-        expect(ElementSet.elementHas(set, p(10, 11))).toBe(false);
-        expect(ElementSet.elementHas(set, p(3, 0))).toBe(true);
-        expect(ElementSet.elementHas(set, p(1, 7))).toBe(true);
-        for (let i = 0; i < ElementSet.elementCount(set); i++) {
-            expect(Element.areEqual(ElementSet.elementAt(set, i), ret[i])).toBe(true);
-        }
-    });
-
-    it('template', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-        const gen = ElementSet.TemplateGenerator(template);
-        gen.add(0, OrderedSet.ofSortedArray([1, 2, 6]));
-        gen.add(1, OrderedSet.ofSingleton(3));
-        const set = gen.getSet();
-
-        expect(ElementSet.groupFromUnitIndex(set, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(set, 1)).toBe(ElementSet.groupFromUnitIndex(template, 1));
-        expect(set).toBe(template);
-    });
-
-    it('template 1', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-        const gen = ElementSet.TemplateGenerator(template);
-        gen.add(0, OrderedSet.ofSortedArray([1, 2, 6]));
-        gen.add(1, OrderedSet.ofSingleton(4));
-        const set = gen.getSet();
-
-        expect(ElementSet.groupFromUnitIndex(set, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(set, 1) === ElementSet.groupFromUnitIndex(template, 1)).toBe(false);
-        expect(set === template).toBe(false);
-    });
-
-    it('template union', () => {
-        const template = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty)
-
-        const p13 = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const p01 = ElementSet.ofAtoms([p(0, 1)], ElementSet.Empty);
-        const p02 = ElementSet.ofAtoms([p(0, 2)], ElementSet.Empty);
-        const p06 = ElementSet.ofAtoms([p(0, 6)], ElementSet.Empty);
-
-        const u0 = ElementSet.union([p01, p02, p06], template);
-        const u1 = ElementSet.union([p01, p02, p06, p13], template);
-        expect(ElementSet.groupFromUnitIndex(u0, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(u1, 0)).toBe(ElementSet.groupFromUnitIndex(template, 0));
-        expect(ElementSet.groupFromUnitIndex(u1, 1)).toBe(ElementSet.groupFromUnitIndex(template, 1));
-        expect(u1).toBe(template);
-    });
-
-    it('element at / index of', () => {
-        const control: Element[] = [];
-        const gen = ElementSet.Generator();
-        for (let i = 1; i < 10; i++) {
-            const set = [];
-            for (let j = 1; j < 7; j++) {
-                control[control.length] = p(i * i, j * j + 1);
-                set[set.length] = j * j + 1;
-            }
-            gen.add(i * i, ElementGroup.createNew(OrderedSet.ofSortedArray(set)));
-        }
-        const ms = gen.getSet();
-        for (let i = 0; i < control.length; i++) {
-            expect(Element.areEqual(ElementSet.elementAt(ms, i), control[i])).toBe(true);
-        }
-
-        for (let i = 0; i < control.length; i++) {
-            expect(ElementSet.elementIndexOf(ms, control[i])).toBe(i);
-        }
-    });
-
-    it('packed pairs', () => {
-        const set = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        expect(setToPairs(set)).toEqual([p(0, 1), p(0, 2), p(0, 6), p(1, 3)]);
-    });
-
-    it('equality', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const f = ElementSet.ofAtoms([p(3, 3)], ElementSet.Empty);
-
-        expect(ElementSet.areEqual(a, a)).toBe(true);
-        expect(ElementSet.areEqual(a, b)).toBe(true);
-        expect(ElementSet.areEqual(a, c)).toBe(false);
-        expect(ElementSet.areEqual(a, d)).toBe(false);
-        expect(ElementSet.areEqual(d, d)).toBe(true);
-        expect(ElementSet.areEqual(d, e)).toBe(true);
-        expect(ElementSet.areEqual(d, f)).toBe(false);
-    });
-
-    it('are intersecting', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3), p(0, 4), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const f = ElementSet.ofAtoms([p(3, 3)], ElementSet.Empty);
-        const g = ElementSet.ofAtoms([p(10, 3), p(8, 1), p(7, 6), p(3, 2)], ElementSet.Empty);
-
-        expect(ElementSet.areIntersecting(a, a)).toBe(true);
-        expect(ElementSet.areIntersecting(a, b)).toBe(true);
-        expect(ElementSet.areIntersecting(a, c)).toBe(true);
-        expect(ElementSet.areIntersecting(a, d)).toBe(true);
-        expect(ElementSet.areIntersecting(a, g)).toBe(false);
-        expect(ElementSet.areIntersecting(d, d)).toBe(true);
-        expect(ElementSet.areIntersecting(d, e)).toBe(true);
-        expect(ElementSet.areIntersecting(d, f)).toBe(false);
-    });
-
-    it('intersection', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        expect(ElementSet.intersect(a, a)).toBe(a);
-        expect(setToPairs(ElementSet.intersect(a, b))).toEqual([p(0, 1), p(0, 6)]);
-        expect(setToPairs(ElementSet.intersect(a, c))).toEqual([p(1, 3)]);
-        expect(setToPairs(ElementSet.intersect(c, d))).toEqual([]);
-    });
-
-    it('subtract', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const a1 = ElementSet.ofAtoms([p(1, 3), p(0, 1), p(0, 6), p(0, 2)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1), p(0, 6), p(4, 2)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        const e = ElementSet.ofAtoms([p(0, 2)], ElementSet.Empty);
-        expect(setToPairs(ElementSet.subtract(a, a))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(a, a1))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(a, b))).toEqual([p(0, 2), p(1, 3)]);
-        expect(setToPairs(ElementSet.subtract(c, d))).toEqual([p(1, 3)]);
-        expect(setToPairs(ElementSet.subtract(a, c))).toEqual([p(0, 1), p(0, 2), p(0, 6)]);
-        expect(setToPairs(ElementSet.subtract(c, a))).toEqual([]);
-        expect(setToPairs(ElementSet.subtract(d, a))).toEqual([p(2, 3)]);
-        expect(setToPairs(ElementSet.subtract(a, e))).toEqual([p(0, 1), p(0, 6), p(1, 3)]);
-    });
-
-    it('union', () => {
-        const a = ElementSet.ofAtoms([p(1, 3), p(0, 1)], ElementSet.Empty);
-        const a1 = ElementSet.ofAtoms([p(1, 3), p(0, 1)], ElementSet.Empty);
-        const b = ElementSet.ofAtoms([p(10, 3), p(0, 1)], ElementSet.Empty);
-        const c = ElementSet.ofAtoms([p(1, 3)], ElementSet.Empty);
-        const d = ElementSet.ofAtoms([p(2, 3)], ElementSet.Empty);
-        expect(ElementSet.union([a], ElementSet.Empty)).toBe(a);
-        expect(ElementSet.union([a, a], ElementSet.Empty)).toBe(a);
-        expect(setToPairs(ElementSet.union([a, a], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(ElementSet.union([a, a1], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3)]);
-        expect(setToPairs(ElementSet.union([a, b], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3), p(10, 3)]);
-        expect(setToPairs(ElementSet.union([c, d], ElementSet.Empty))).toEqual([p(1, 3), p(2, 3)]);
-        expect(setToPairs(ElementSet.union([a, b, c, d], ElementSet.Empty))).toEqual([p(0, 1), p(1, 3), p(2, 3), p(10, 3)]);
-    });
-});

+ 4 - 4
src/mol-model/structure/export/mmcif.ts

@@ -9,7 +9,7 @@ import { Column } from 'mol-data/db'
 import Iterator from 'mol-data/iterator'
 import * as Encoder from 'mol-io/writer/cif'
 // import { mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
-import { Structure, Element, ElementSet } from '../structure'
+import { Structure, Element } from '../structure'
 import { Model } from '../model'
 import P from '../query/properties'
 
@@ -96,7 +96,7 @@ const atom_site: Encoder.CategoryDefinition<Element.Location> = {
         str('auth_asym_id', P.chain.auth_asym_id),
 
         int('pdbx_PDB_model_num', P.unit.model_num),
-        str('pdbx_operator_name', P.unit.operator_name)
+        str('operator_name', P.unit.operator_name)
     ]
 };
 
@@ -113,8 +113,8 @@ function atomSiteProvider({ structure }: Context): Encoder.CategoryInstance {
     return {
         data: void 0,
         definition: atom_site,
-        keys: () => Structure.elementLocationsTransient(structure),
-        rowCount: ElementSet.elementCount(structure.elements)
+        keys: () => structure.elementLocations(),
+        rowCount: structure.elementCount
     }
 }
 

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

@@ -7,6 +7,6 @@
 import Model from './model/model'
 import * as Types from './model/types'
 import Format from './model/format'
-import ModelSymmetry from './model/properties/symmetry'
+import { ModelSymmetry } from './model/properties/symmetry'
 
 export { Model, Types, Format, ModelSymmetry }

+ 3 - 3
src/mol-model/structure/model/format.ts

@@ -4,15 +4,15 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { GroFile as GroFile } from 'mol-io/reader/gro/schema'
+// import { File as GroFile } from 'mol-io/reader/gro/schema'
 import { mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
 
 type Format =
-    | Format.gro
+    // | Format.gro
     | Format.mmCIF
 
 namespace Format {
-    export interface gro { kind: 'gro', data: GroFile }
+    // export interface gro { kind: 'gro', data: GroFile }
     export interface mmCIF { kind: 'mmCIF', data: mmCIF_Database }
 }
 

+ 154 - 153
src/mol-model/structure/model/formats/gro.ts

@@ -1,153 +1,154 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import UUID from 'mol-util/uuid'
-import { Column, Table } from 'mol-data/db'
-import { Interval, Segmentation } from 'mol-data/int'
-import { GroAtoms } from 'mol-io/reader/gro/schema'
-import Format from '../format'
-import Model from '../model'
-import * as Hierarchy from '../properties/hierarchy'
-import AtomSiteConformation from '../properties/atom-site-conformation'
-import CoarseGrained from '../properties/coarse-grained'
-import findHierarchyKeys from '../utils/hierarchy-keys'
-import { guessElement } from '../utils/guess-element'
-import { ElementSymbol} from '../types'
-import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-
-import gro_Format = Format.gro
-import Sequence from '../properties/sequence';
-import { Entities } from '../properties/common';
-
-type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
-
-function findHierarchyOffsets(atomsData: GroAtoms, bounds: Interval) {
-    const start = Interval.start(bounds), end = Interval.end(bounds);
-    const residues = [start], chains = [start];
-
-    const { residueName, residueNumber } = atomsData;
-
-    for (let i = start + 1; i < end; i++) {
-        const newResidue = !residueNumber.areValuesEqual(i - 1, i)
-            || !residueName.areValuesEqual(i - 1, i);
-        console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue)
-        if (newResidue) residues[residues.length] = i;
-    }
-    console.log(residues, residues.length)
-    return { residues, chains };
-}
-
-function guessElementSymbol (value: string) {
-    return ElementSymbol(guessElement(value));
-}
-
-function createHierarchyData(atomsData: GroAtoms, offsets: HierarchyOffsets): Hierarchy.Data {
-    console.log(atomsData.atomName)
-    const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
-        type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
-        label_atom_id: atomsData.atomName,
-        auth_atom_id: atomsData.atomName,
-        label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str),
-        pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
-    });
-
-    const residues = Table.view(Table.ofColumns(Hierarchy.ResiduesSchema, {
-        group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)),
-        label_comp_id: atomsData.residueName,
-        auth_comp_id: atomsData.residueName,
-        label_seq_id: atomsData.residueNumber,
-        auth_seq_id: atomsData.residueNumber,
-        pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str),
-    }), Hierarchy.ResiduesSchema, offsets.residues);
-    // Optimize the numeric columns
-    Table.columnToArray(residues, 'label_seq_id', Int32Array);
-    Table.columnToArray(residues, 'auth_seq_id', Int32Array);
-
-    // const chains = Table.ofColumns(Hierarchy.ChainsSchema, {
-    //     label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
-    //     auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
-    //     label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
-    // });
-
-    const chains = Table.ofUndefinedColumns(Hierarchy.ChainsSchema, 0);
-
-    return { atoms, residues, chains };
-}
-
-function getConformation(atoms: GroAtoms): AtomSiteConformation {
-    return {
-        id: UUID.create(),
-        atomId: atoms.atomNumber,
-        occupancy: Column.Undefined(atoms.count, Column.Schema.int),
-        B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float),
-        x: Column.mapToArray(atoms.x, x => x * 10, Float32Array),
-        y: Column.mapToArray(atoms.y, y => y * 10, Float32Array),
-        z: Column.mapToArray(atoms.z, z => z * 10, Float32Array)
-    }
-}
-
-function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) {
-    // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
-    return Table.areEqual(a.residues as Table<Hierarchy.ResiduesSchema>, b.residues as Table<Hierarchy.ResiduesSchema>)
-        && Table.areEqual(a.atoms as Table<Hierarchy.AtomsSchema>, b.atoms as Table<Hierarchy.AtomsSchema>)
-}
-
-function createModel(format: gro_Format, modelNum: number, previous?: Model): Model {
-    const structure = format.data.structures[modelNum];
-    const bounds = Interval.ofBounds(0, structure.atoms.count);
-
-    const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds);
-    const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets);
-
-    if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
-        return {
-            ...previous,
-            atomSiteConformation: getConformation(structure.atoms)
-        };
-    }
-
-    const hierarchySegments: Hierarchy.Segments = {
-        residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
-        chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
-    }
-
-    // TODO: create a better mock entity
-    const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{
-        id: '0',
-        src_method: 'syn',
-        type: 'polymer',
-        pdbx_number_of_molecules: 1
-    }]);
-
-    const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) };
-
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
-    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
-    return {
-        id: UUID.create(),
-        sourceData: format,
-        modelNum,
-        hierarchy,
-        entities,
-        sequence: Sequence.fromHierarchy(hierarchy),
-        atomSiteConformation: getConformation(structure.atoms),
-        coarseGrained: CoarseGrained.Empty,
-        symmetry: { assemblies: [] },
-        atomCount: structure.atoms.count
-    };
-}
-
-function buildModels(format: gro_Format): ReadonlyArray<Model> {
-    const models: Model[] = [];
-
-    format.data.structures.forEach((_, i) => {
-        const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0);
-        models.push(model);
-    });
-    return models;
-}
-
-export default buildModels;
+// TODO: make this work when the time comes.
+// /**
+//  * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+//  *
+//  * @author Alexander Rose <alexander.rose@weirdbyte.de>
+//  */
+
+// import { Column, Table } from 'mol-data/db';
+// import { Interval, Segmentation } from 'mol-data/int';
+// import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif';
+// import { Atoms } from 'mol-io/reader/gro/schema';
+// import UUID from 'mol-util/uuid';
+// import Format from '../format';
+// import Model from '../model';
+// import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic';
+// import { CoarseHierarchy } from '../properties/coarse';
+// import { Entities } from '../properties/common';
+// import Sequence from '../properties/sequence';
+// import { ModelSymmetry } from '../properties/symmetry';
+// import { guessElement } from '../properties/utils/guess-element';
+// import { getAtomicKeys } from '../properties/utils/keys';
+// import { ElementSymbol } from '../types';
+
+// import gro_Format = Format.gro
+
+// type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
+
+// function findHierarchyOffsets(atomsData: Atoms, bounds: Interval) {
+//     const start = Interval.start(bounds), end = Interval.end(bounds);
+//     const residues = [start], chains = [start];
+
+//     const { residueName, residueNumber } = atomsData;
+
+//     for (let i = start + 1; i < end; i++) {
+//         const newResidue = !residueNumber.areValuesEqual(i - 1, i)
+//             || !residueName.areValuesEqual(i - 1, i);
+//         console.log(residueName.value(i - 1), residueName.value(i), residueNumber.value(i - 1), residueNumber.value(i), newResidue)
+//         if (newResidue) residues[residues.length] = i;
+//     }
+//     console.log(residues, residues.length)
+//     return { residues, chains };
+// }
+
+// function guessElementSymbol (value: string) {
+//     return ElementSymbol(guessElement(value));
+// }
+
+// function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicData {
+//     console.log(atomsData.atomName)
+//     const atoms = Table.ofColumns(AtomsSchema, {
+//         type_symbol: Column.ofArray({ array: Column.mapToArray(atomsData.atomName, guessElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
+//         label_atom_id: atomsData.atomName,
+//         auth_atom_id: atomsData.atomName,
+//         label_alt_id: Column.Undefined(atomsData.count, Column.Schema.str),
+//         pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
+//     });
+
+//     const residues = Table.view(Table.ofColumns(ResiduesSchema, {
+//         group_PDB: Column.Undefined(atomsData.count, Column.Schema.Aliased<'ATOM' | 'HETATM'>(Column.Schema.str)),
+//         label_comp_id: atomsData.residueName,
+//         auth_comp_id: atomsData.residueName,
+//         label_seq_id: atomsData.residueNumber,
+//         auth_seq_id: atomsData.residueNumber,
+//         pdbx_PDB_ins_code: Column.Undefined(atomsData.count, Column.Schema.str),
+//     }), ResiduesSchema, offsets.residues);
+//     // Optimize the numeric columns
+//     Table.columnToArray(residues, 'label_seq_id', Int32Array);
+//     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
+
+//     // const chains = Table.ofColumns(Hierarchy.ChainsSchema, {
+//     //     label_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+//     //     auth_asym_id: Column.ofConst('A', atomsData.count, Column.Schema.str),
+//     //     label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
+//     // });
+
+//     const chains = Table.ofUndefinedColumns(ChainsSchema, 0);
+
+//     return { atoms, residues, chains };
+// }
+
+// function getConformation(atoms: Atoms): AtomicConformation {
+//     return {
+//         id: UUID.create(),
+//         atomId: atoms.atomNumber,
+//         occupancy: Column.Undefined(atoms.count, Column.Schema.int),
+//         B_iso_or_equiv: Column.Undefined(atoms.count, Column.Schema.float),
+//         x: Column.mapToArray(atoms.x, x => x * 10, Float32Array),
+//         y: Column.mapToArray(atoms.y, y => y * 10, Float32Array),
+//         z: Column.mapToArray(atoms.z, z => z * 10, Float32Array)
+//     }
+// }
+
+// function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
+//     // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
+//     return Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
+//         && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
+// }
+
+// function createModel(format: gro_Format, modelNum: number, previous?: Model): Model {
+//     const structure = format.data.structures[modelNum];
+//     const bounds = Interval.ofBounds(0, structure.atoms.count);
+
+//     const hierarchyOffsets = findHierarchyOffsets(structure.atoms, bounds);
+//     const hierarchyData = createHierarchyData(structure.atoms, hierarchyOffsets);
+
+//     if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
+//         return {
+//             ...previous,
+//             atomicConformation: getConformation(structure.atoms)
+//         };
+//     }
+
+//     const hierarchySegments: AtomicSegments = {
+//         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
+//         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
+//     }
+
+//     // TODO: create a better mock entity
+//     const entityTable = Table.ofRows<mmCIF['entity']>(mmCIF.entity, [{
+//         id: '0',
+//         src_method: 'syn',
+//         type: 'polymer',
+//         pdbx_number_of_molecules: 1
+//     }]);
+
+//     const entities: Entities = { data: entityTable, getEntityIndex: Column.createIndexer(entityTable.id) };
+
+//     const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);
+//     const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
+//     return {
+//         id: UUID.create(),
+//         sourceData: format,
+//         modelNum,
+//         atomicHierarchy,
+//         entities,
+//         sequence: Sequence.fromAtomicHierarchy(atomicHierarchy),
+//         atomicConformation: getConformation(structure.atoms),
+//         coarseHierarchy: CoarseHierarchy.Empty,
+//         coarseConformation: void 0 as any,
+//         symmetry: ModelSymmetry.Default
+//     };
+// }
+
+// function buildModels(format: gro_Format): ReadonlyArray<Model> {
+//     const models: Model[] = [];
+
+//     format.data.structures.forEach((_, i) => {
+//         const model = createModel(format, i, models.length > 0 ? models[models.length - 1] : void 0);
+//         models.push(model);
+//     });
+//     return models;
+// }
+
+// export default buildModels;

+ 86 - 55
src/mol-model/structure/model/formats/mmcif.ts

@@ -4,22 +4,25 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import UUID from 'mol-util/uuid'
-import { Column, Table } from 'mol-data/db'
-import { Interval, Segmentation } from 'mol-data/int'
-import Format from '../format'
-import Model from '../model'
-import * as Hierarchy from '../properties/hierarchy'
-import AtomSiteConformation from '../properties/atom-site-conformation'
-import Symmetry from '../properties/symmetry'
-import findHierarchyKeys from '../utils/hierarchy-keys'
-import { ElementSymbol} from '../types'
-import createAssemblies from './mmcif/assembly'
+import { Column, Table } from 'mol-data/db';
+import { Interval, Segmentation } from 'mol-data/int';
+import { Spacegroup, SpacegroupCell } from 'mol-math/geometry';
+import { Vec3 } from 'mol-math/linear-algebra';
+import UUID from 'mol-util/uuid';
+import Format from '../format';
+import Model from '../model';
+import { AtomicConformation, AtomicData, AtomicSegments, AtomsSchema, ChainsSchema, ResiduesSchema } from '../properties/atomic';
+import { Entities } from '../properties/common';
+import { ModelSymmetry } from '../properties/symmetry';
+import { getAtomicKeys } from '../properties/utils/atomic-keys';
+import { ElementSymbol } from '../types';
+import { createAssemblies } from './mmcif/assembly';
+import { getIHMCoarse } from './mmcif/ihm';
+import { getSequence } from './mmcif/sequence';
 
 import mmCIF_Format = Format.mmCIF
-import { getSequence } from './mmcif/sequence';
-import { Entities } from '../properties/common';
-import { coarseGrainedFromIHM } from './mmcif/ihm';
+import { Task } from 'mol-task';
+import { getSecondaryStructureMmCif } from './mmcif/secondary-structure';
 
 function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
     const num = data.atom_site.pdbx_PDB_model_num;
@@ -51,25 +54,25 @@ function findHierarchyOffsets({ data }: mmCIF_Format, bounds: Interval) {
     return { residues, chains };
 }
 
-function createHierarchyData({ data }: mmCIF_Format, bounds: Interval, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): Hierarchy.Data {
+function createHierarchyData({ data }: mmCIF_Format, bounds: Interval, offsets: { residues: ArrayLike<number>, chains: ArrayLike<number> }): AtomicData {
     const { atom_site } = data;
     const start = Interval.start(bounds), end = Interval.end(bounds);
-    const atoms = Table.ofColumns(Hierarchy.AtomsSchema, {
+    const atoms = Table.ofColumns(AtomsSchema, {
         type_symbol: Column.ofArray({ array: Column.mapToArray(Column.window(atom_site.type_symbol, start, end), ElementSymbol), schema: Column.Schema.Aliased<ElementSymbol>(Column.Schema.str) }),
         label_atom_id: Column.window(atom_site.label_atom_id, start, end),
         auth_atom_id: Column.window(atom_site.auth_atom_id, start, end),
         label_alt_id: Column.window(atom_site.label_alt_id, start, end),
         pdbx_formal_charge: Column.window(atom_site.pdbx_formal_charge, start, end)
     });
-    const residues = Table.view(atom_site, Hierarchy.ResiduesSchema, offsets.residues);
+    const residues = Table.view(atom_site, ResiduesSchema, offsets.residues);
     // Optimize the numeric columns
     Table.columnToArray(residues, 'label_seq_id', Int32Array);
     Table.columnToArray(residues, 'auth_seq_id', Int32Array);
-    const chains = Table.view(atom_site, Hierarchy.ChainsSchema, offsets.chains);
+    const chains = Table.view(atom_site, ChainsSchema, offsets.chains);
     return { atoms, residues, chains };
 }
 
-function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomSiteConformation {
+function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomicConformation {
     const start = Interval.start(bounds), end = Interval.end(bounds);
     const { atom_site } = data;
     return {
@@ -83,72 +86,100 @@ function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomSiteConf
     }
 }
 
-function getSymmetry(format: mmCIF_Format): Symmetry {
-    return { assemblies: createAssemblies(format) };
+function getSymmetry(format: mmCIF_Format): ModelSymmetry {
+    const assemblies = createAssemblies(format);
+    const spacegroup = getSpacegroup(format);
+    const isNonStandardCrytalFrame = checkNonStandardCrystalFrame(format, spacegroup);
+    return { assemblies, spacegroup, isNonStandardCrytalFrame };
 }
 
-function isHierarchyDataEqual(a: Hierarchy.Hierarchy, b: Hierarchy.Data) {
+function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegroup) {
+    const { atom_sites } = format.data;
+    if (atom_sites._rowCount === 0) return false;
+    // TODO: parse atom_sites transform and check if it corresponds to the toFractional matrix
+    return false;
+}
+
+function getSpacegroup(format: mmCIF_Format): Spacegroup {
+    const { symmetry, cell } = format.data;
+    if (symmetry._rowCount === 0 || cell._rowCount === 0) return Spacegroup.ZeroP1;
+    const groupName = symmetry['space_group_name_H-M'].value(0);
+    const spaceCell = SpacegroupCell.create(groupName,
+        Vec3.create(cell.length_a.value(0), cell.length_b.value(0), cell.length_c.value(0)),
+        Vec3.scale(Vec3.zero(), Vec3.create(cell.angle_alpha.value(0), cell.angle_beta.value(0), cell.angle_gamma.value(0)), Math.PI / 180));
+
+    return Spacegroup.create(spaceCell);
+}
+
+function isHierarchyDataEqual(a: AtomicData, b: AtomicData) {
     // need to cast because of how TS handles type resolution for interfaces https://github.com/Microsoft/TypeScript/issues/15300
-    return Table.areEqual(a.chains as Table<Hierarchy.ChainsSchema>, b.chains as Table<Hierarchy.ChainsSchema>)
-        && Table.areEqual(a.residues as Table<Hierarchy.ResiduesSchema>, b.residues as Table<Hierarchy.ResiduesSchema>)
-        && Table.areEqual(a.atoms as Table<Hierarchy.AtomsSchema>, b.atoms as Table<Hierarchy.AtomsSchema>)
+    return Table.areEqual(a.chains as Table<ChainsSchema>, b.chains as Table<ChainsSchema>)
+        && Table.areEqual(a.residues as Table<ResiduesSchema>, b.residues as Table<ResiduesSchema>)
+        && Table.areEqual(a.atoms as Table<AtomsSchema>, b.atoms as Table<AtomsSchema>)
 }
 
 function createModel(format: mmCIF_Format, bounds: Interval, previous?: Model): Model {
     const hierarchyOffsets = findHierarchyOffsets(format, bounds);
     const hierarchyData = createHierarchyData(format, bounds, hierarchyOffsets);
 
-    if (previous && isHierarchyDataEqual(previous.hierarchy, hierarchyData)) {
+    if (previous && isHierarchyDataEqual(previous.atomicHierarchy, hierarchyData)) {
         return {
             ...previous,
-            atomSiteConformation: getConformation(format, bounds)
+            atomicConformation: getConformation(format, bounds)
         };
     }
 
-    const hierarchySegments: Hierarchy.Segments = {
+    const hierarchySegments: AtomicSegments = {
         residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
         chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
     }
 
     const entities: Entities = { data: format.data.entity, getEntityIndex: Column.createIndexer(format.data.entity.id) };
 
-    const hierarchyKeys = findHierarchyKeys(hierarchyData, entities, hierarchySegments);
+    const hierarchyKeys = getAtomicKeys(hierarchyData, entities, hierarchySegments);
+
+    const atomicHierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
 
-    const hierarchy = { ...hierarchyData, ...hierarchyKeys, ...hierarchySegments };
+    const coarse = getIHMCoarse(format.data, entities);
 
     return {
         id: UUID.create(),
         sourceData: format,
         modelNum: format.data.atom_site.pdbx_PDB_model_num.value(Interval.start(bounds)),
         entities,
-        hierarchy,
-        sequence: getSequence(format.data, entities, hierarchy),
-        atomSiteConformation: getConformation(format, bounds),
-        coarseGrained: coarseGrainedFromIHM(format.data, entities),
-        symmetry: getSymmetry(format),
-        atomCount: Interval.size(bounds)
+        atomicHierarchy,
+        sequence: getSequence(format.data, entities, atomicHierarchy),
+        atomicConformation: getConformation(format, bounds),
+        coarseHierarchy: coarse.hierarchy,
+        coarseConformation: coarse.conformation,
+        properties: {
+            secondaryStructure: getSecondaryStructureMmCif(format.data, atomicHierarchy)
+        },
+        symmetry: getSymmetry(format)
     };
 }
 
-function buildModels(format: mmCIF_Format): ReadonlyArray<Model> {
-    const atomCount = format.data.atom_site._rowCount;
-    const isIHM = format.data.ihm_model_list._rowCount > 0;
-
-    if (atomCount === 0) {
-        return isIHM
-            ? [createModel(format, Interval.Empty, void 0)]
-            : [];
-    }
-
-    const models: Model[] = [];
-    let modelStart = 0;
-    while (modelStart < atomCount) {
-        const bounds = findModelBounds(format, modelStart);
-        const model = createModel(format, bounds, models.length > 0 ? models[models.length - 1] : void 0);
-        models.push(model);
-        modelStart = Interval.end(bounds);
-    }
-    return models;
+function buildModels(format: mmCIF_Format): Task<ReadonlyArray<Model>> {
+    return Task.create('Create mmCIF Model', async ctx => {
+        const atomCount = format.data.atom_site._rowCount;
+        const isIHM = format.data.ihm_model_list._rowCount > 0;
+
+        if (atomCount === 0) {
+            return isIHM
+                ? [createModel(format, Interval.Empty, void 0)]
+                : [];
+        }
+
+        const models: Model[] = [];
+        let modelStart = 0;
+        while (modelStart < atomCount) {
+            const bounds = findModelBounds(format, modelStart);
+            const model = createModel(format, bounds, models.length > 0 ? models[models.length - 1] : void 0);
+            models.push(model);
+            modelStart = Interval.end(bounds);
+        }
+        return models;
+    });
 }
 
 export default buildModels;

+ 1 - 1
src/mol-model/structure/model/formats/mmcif/assembly.ts

@@ -12,7 +12,7 @@ import { Queries as Q, Query } from '../../../query'
 
 import mmCIF_Format = Format.mmCIF
 
-export default function create(format: mmCIF_Format): ReadonlyArray<Assembly> {
+export function createAssemblies(format: mmCIF_Format): ReadonlyArray<Assembly> {
     const { pdbx_struct_assembly } = format.data;
     if (!pdbx_struct_assembly._rowCount) return [];
 

+ 6 - 6
src/mol-model/structure/model/formats/mmcif/bonds.ts

@@ -9,9 +9,9 @@ import Model from '../../model'
 import { BondType } from '../../types'
 import { findEntityIdByAsymId, findAtomIndexByLabelName } from './util'
 import { Column } from 'mol-data/db'
-import { GroupBonds } from '../../../structure/element/properties/bonds/group-data';
+import { IntraUnitBonds } from '../../../structure/unit/bonds';
 
-export class StructConn implements GroupBonds.StructConn {
+export class StructConn implements IntraUnitBonds.StructConn {
     private _residuePairIndex: Map<string, StructConn.Entry[]> | undefined = void 0;
     private _atomIndex: Map<number, StructConn.Entry[]> | undefined = void 0;
 
@@ -71,7 +71,7 @@ export class StructConn implements GroupBonds.StructConn {
 }
 
 export namespace StructConn {
-    export interface Entry extends GroupBonds.StructConnEntry {
+    export interface Entry extends IntraUnitBonds.StructConnEntry {
         distance: number,
         order: number,
         flags: number,
@@ -118,7 +118,7 @@ export namespace StructConn {
         const _p = (row: number, ps: typeof p1) => {
             if (ps.label_asym_id.valueKind(row) !== Column.ValueKind.Present) return void 0;
             const asymId = ps.label_asym_id.value(row)
-            const residueIndex = model.hierarchy.findResidueKey(
+            const residueIndex = model.atomicHierarchy.findResidueKey(
                 findEntityIdByAsymId(model, asymId),
                 ps.label_comp_id.value(row),
                 asymId,
@@ -181,7 +181,7 @@ export namespace StructConn {
     }
 }
 
-export class ComponentBondInfo implements GroupBonds.ComponentBondInfo {
+export class ComponentBondInfo implements IntraUnitBonds.ComponentBondInfo {
     entries: Map<string, ComponentBondInfo.Entry> = new Map();
 
     newEntry(id: string) {
@@ -192,7 +192,7 @@ export class ComponentBondInfo implements GroupBonds.ComponentBondInfo {
 }
 
 export namespace ComponentBondInfo {
-    export class Entry implements GroupBonds.ComponentBondInfoEntry {
+    export class Entry implements IntraUnitBonds.ComponentBondInfoEntry {
         map: Map<string, Map<string, { order: number, flags: number }>> = new Map();
 
         add(a: string, b: string, order: number, flags: number, swap = true) {

+ 59 - 28
src/mol-model/structure/model/formats/mmcif/ihm.ts

@@ -5,50 +5,81 @@
  */
 
 import { mmCIF_Database as mmCIF, mmCIF_Schema } from 'mol-io/reader/cif/schema/mmcif'
-import CoarseGrained from '../../properties/coarse-grained'
+import { CoarseHierarchy, CoarseConformation, CoarseElementData, CoarseSphereConformation, CoarseGaussianConformation } from '../../properties/coarse'
 import { Entities } from '../../properties/common';
 import { Column } from 'mol-data/db';
+import { getCoarseKeys } from '../../properties/utils/coarse-keys';
+import { UUID } from 'mol-util';
+import { Segmentation, Interval } from 'mol-data/int';
+import { Mat3, Tensor } from 'mol-math/linear-algebra';
 
-function coarseGrainedFromIHM(data: mmCIF, entities: Entities): CoarseGrained {
-    if (data.ihm_model_list._rowCount === 0) return CoarseGrained.Empty;
+export function getIHMCoarse(data: mmCIF, entities: Entities): { hierarchy: CoarseHierarchy, conformation: CoarseConformation } {
+    if (data.ihm_model_list._rowCount === 0) return { hierarchy: CoarseHierarchy.Empty, conformation: void 0 as any };
 
     const { ihm_model_list, ihm_sphere_obj_site, ihm_gaussian_obj_site } = data;
     const modelIndex = Column.createIndexer(ihm_model_list.model_id);
 
+    const sphereData = getData(ihm_sphere_obj_site);
+    const sphereConformation = getSphereConformation(ihm_sphere_obj_site);
+    const sphereKeys = getCoarseKeys(sphereData, modelIndex, entities);
+
+    const gaussianData = getData(ihm_gaussian_obj_site);
+    const gaussianConformation = getGaussianConformation(ihm_gaussian_obj_site);
+    const gaussianKeys = getCoarseKeys(gaussianData, modelIndex, entities);
+
     return {
-        isDefined: true,
-        modelList: ihm_model_list,
-        spheres: getSpheres(ihm_sphere_obj_site, entities, modelIndex),
-        gaussians: getGaussians(ihm_gaussian_obj_site, entities, modelIndex)
+        hierarchy: {
+            isDefined: true,
+            models: ihm_model_list,
+            spheres: { ...sphereData, ...sphereKeys },
+            gaussians: { ...gaussianData, ...gaussianKeys },
+        },
+        conformation: {
+            id: UUID.create(),
+            spheres: sphereConformation,
+            gaussians: gaussianConformation
+        }
     };
 }
 
-function getSpheres(data: mmCIF['ihm_sphere_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrained.Spheres {
-    const { Cartn_x, Cartn_y, Cartn_z, object_radius: radius, rmsf } = data;
-    const x = Cartn_x.toArray({ array: Float32Array });
-    const y = Cartn_y.toArray({ array: Float32Array });
-    const z = Cartn_z.toArray({ array: Float32Array });
-    return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, radius, rmsf };
+function getSphereConformation(data: mmCIF['ihm_sphere_obj_site']): CoarseSphereConformation {
+    return {
+        x: data.Cartn_x.toArray({ array: Float32Array }),
+        y: data.Cartn_y.toArray({ array: Float32Array }),
+        z: data.Cartn_z.toArray({ array: Float32Array }),
+        radius: data.object_radius.toArray({ array: Float32Array }),
+        rmsf: data.rmsf.toArray({ array: Float32Array })
+    };
 }
 
-function getGaussians(data: mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number): CoarseGrained.Gaussians {
-    const { mean_Cartn_x, mean_Cartn_y, mean_Cartn_z, weight, covariance_matrix  } = data;
-    const x = mean_Cartn_x.toArray({ array: Float32Array });
-    const y = mean_Cartn_y.toArray({ array: Float32Array });
-    const z = mean_Cartn_z.toArray({ array: Float32Array });
-    return { count: x.length, ...getCommonColumns(data, entities, modelIndex), x, y, z, weight, covariance_matrix, matrix_space: mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space };
-}
+function getGaussianConformation(data: mmCIF['ihm_gaussian_obj_site']): CoarseGaussianConformation {
+    const matrix_space = mmCIF_Schema.ihm_gaussian_obj_site.covariance_matrix.space;
+    const covariance_matrix: Mat3[] = [];
+    const { covariance_matrix: cm } = data;
 
-function getCommonColumns(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site'], entities: Entities, modelIndex: (id: number) => number) {
-    const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data;
+    for (let i = 0, _i = cm.rowCount; i < _i; i++) {
+        covariance_matrix[i] = Tensor.toMat3(matrix_space, cm.value(i));
+    }
 
     return {
-        entityKey: Column.mapToArray(entity_id, id => entities.getEntityIndex(id), Int32Array),
-        modelKey: Column.mapToArray(model_id, modelIndex, Int32Array),
-        asym_id,
-        seq_id_begin,
-        seq_id_end
+        x: data.mean_Cartn_x.toArray({ array: Float32Array }),
+        y: data.mean_Cartn_y.toArray({ array: Float32Array }),
+        z: data.mean_Cartn_z.toArray({ array: Float32Array }),
+        weight: data.weight.toArray({ array: Float32Array }),
+        covariance_matrix
     };
 }
 
-export { coarseGrainedFromIHM }
+function getChainSegments(asym_id: Column<string>) {
+    const offsets = [0];
+    for (let i = 1, _i = asym_id.rowCount; i < _i; i++) {
+        if (!asym_id.areValuesEqual(i - 1, i)) offsets[offsets.length] = i;
+    }
+
+    return Segmentation.ofOffsets(offsets, Interval.ofBounds(0, asym_id.rowCount));
+}
+
+function getData(data: mmCIF['ihm_sphere_obj_site'] | mmCIF['ihm_gaussian_obj_site']): CoarseElementData {
+    const { model_id, entity_id, seq_id_begin, seq_id_end, asym_id } = data;
+    return { count: model_id.rowCount, entity_id, model_id, asym_id, seq_id_begin, seq_id_end, chainSegments: getChainSegments(asym_id) };
+}

+ 154 - 0
src/mol-model/structure/model/formats/mmcif/secondary-structure.ts

@@ -0,0 +1,154 @@
+
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { mmCIF_Database as mmCIF, mmCIF_Database } from 'mol-io/reader/cif/schema/mmcif'
+import { SecondaryStructureType } from '../../types';
+import { AtomicHierarchy } from '../../properties/atomic';
+import { SecondaryStructure } from '../../properties/seconday-structure';
+import { Column } from 'mol-data/db';
+
+export function getSecondaryStructureMmCif(data: mmCIF_Database, hierarchy: AtomicHierarchy): SecondaryStructure {
+    const map: SecondaryStructureMap = new Map();
+    addHelices(data.struct_conf, map);
+    // must add Helices 1st because of 'key' value assignment.
+    addSheets(data.struct_sheet_range, map, data.struct_conf._rowCount);
+
+    const secStruct: SecondaryStructureData = {
+        type: new Int32Array(hierarchy.residues._rowCount) as any,
+        key: new Int32Array(hierarchy.residues._rowCount) as any
+    };
+
+    if (map.size > 0) assignSecondaryStructureRanges(hierarchy, map, secStruct);
+    return secStruct;
+}
+
+type SecondaryStructureEntry = {
+    startSeqNumber: number,
+    startInsCode: string | null,
+    endSeqNumber: number,
+    endInsCode: string | null,
+    type: SecondaryStructureType,
+    key: number
+}
+type SecondaryStructureMap = Map<string, Map<number, SecondaryStructureEntry>>
+type SecondaryStructureData = { type: SecondaryStructureType[], key: number[] }
+
+function addHelices(cat: mmCIF['struct_conf'], map: SecondaryStructureMap) {
+    if (!cat._rowCount) return;
+
+    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { pdbx_PDB_helix_class, conf_type_id } = cat;
+
+    for (let i = 0, _i = cat._rowCount; i < _i; i++) {
+        const type =  pdbx_PDB_helix_class.valueKind(i) === Column.ValueKind.Present
+            ? SecondaryStructureType.SecondaryStructurePdb[pdbx_PDB_helix_class.value(i)]
+            : conf_type_id.valueKind(i) === Column.ValueKind.Present
+            ? SecondaryStructureType.SecondaryStructureMmcif[conf_type_id.value(i)]
+            : SecondaryStructureType.Flag.NA
+
+        const entry: SecondaryStructureEntry = {
+            startSeqNumber: beg_label_seq_id.value(i),
+            startInsCode: pdbx_beg_PDB_ins_code.value(i),
+            endSeqNumber: end_label_seq_id.value(i),
+            endInsCode: pdbx_end_PDB_ins_code.value(i),
+            type: SecondaryStructureType.create(type),
+            key: i + 1
+        };
+
+        const asymId = beg_label_asym_id.value(i)!;
+        if (map.has(asymId)) {
+            map.get(asymId)!.set(entry.startSeqNumber, entry);
+        } else {
+            map.set(asymId, new Map([[entry.startSeqNumber, entry]]));
+        }
+    }
+}
+
+function addSheets(cat: mmCIF['struct_sheet_range'], map: SecondaryStructureMap, sheetCount: number) {
+    if (!cat._rowCount) return;
+
+    const { beg_label_asym_id, beg_label_seq_id, pdbx_beg_PDB_ins_code } = cat;
+    const { end_label_seq_id, pdbx_end_PDB_ins_code } = cat;
+    const { sheet_id } = cat;
+
+    const sheet_id_key = new Map<string, number>();
+    let currentKey = sheetCount + 1;
+
+    for (let i = 0, _i = cat._rowCount; i < _i; i++) {
+        const id = sheet_id.value(i);
+        let key: number;
+        if (sheet_id_key.has(id)) key = sheet_id_key.get(id)!;
+        else {
+            key = currentKey++;
+            sheet_id_key.set(id, key);
+        }
+
+        const entry: SecondaryStructureEntry = {
+            startSeqNumber: beg_label_seq_id.value(i),
+            startInsCode: pdbx_beg_PDB_ins_code.value(i),
+            endSeqNumber: end_label_seq_id.value(i),
+            endInsCode: pdbx_end_PDB_ins_code.value(i),
+            type: SecondaryStructureType.create(SecondaryStructureType.Flag.Beta | SecondaryStructureType.Flag.BetaSheet),
+            key
+        };
+
+        const asymId = beg_label_asym_id.value(i)!;
+        if (map.has(asymId)) {
+            map.get(asymId)!.set(entry.startSeqNumber, entry);
+        } else {
+            map.set(asymId, new Map([[entry.startSeqNumber, entry]]));
+        }
+    }
+
+    return;
+}
+
+function assignSecondaryStructureEntry(hierarchy: AtomicHierarchy, entry: SecondaryStructureEntry, resStart: number, resEnd: number, data: SecondaryStructureData) {
+    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+    const { endSeqNumber, endInsCode, type, key } = entry;
+
+    let rI = resStart;
+    while (rI < resEnd) {
+        const seqNumber = label_seq_id.value(rI);
+
+        data.type[rI] = type;
+        data.key[rI] = key;
+
+        if ((seqNumber > endSeqNumber) ||
+            (seqNumber === endSeqNumber && pdbx_PDB_ins_code.value(rI) === endInsCode)) {
+            break;
+        }
+
+        rI++;
+    }
+}
+
+function assignSecondaryStructureRanges(hierarchy: AtomicHierarchy, map: SecondaryStructureMap, data: SecondaryStructureData) {
+    const { segments: chainSegments, count: chainCount } = hierarchy.chainSegments;
+    const { label_asym_id } = hierarchy.chains;
+    const { label_seq_id, pdbx_PDB_ins_code } = hierarchy.residues;
+
+    for (let cI = 0; cI < chainCount; cI++) {
+        const resStart = chainSegments[cI], resEnd = chainSegments[cI + 1];
+        const asymId = label_asym_id.value(cI);
+
+        if (map.has(asymId)) {
+            const entries = map.get(asymId)!;
+
+            for (let rI = resStart; rI < resEnd; rI++) {
+                const seqNumber = label_seq_id.value(rI);
+                if (entries.has(seqNumber)) {
+                    const entry = entries.get(seqNumber)!;
+                    const insCode = pdbx_PDB_ins_code.value(rI);
+                    if (entry.startInsCode !== insCode) continue;
+                    assignSecondaryStructureEntry(hierarchy, entry, rI, resEnd, data);
+                }
+            }
+        }
+    }
+}

+ 3 - 3
src/mol-model/structure/model/formats/mmcif/sequence.ts

@@ -7,11 +7,11 @@
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 import Sequence from '../../properties/sequence'
 import { Column } from 'mol-data/db';
-import { Hierarchy } from '../../properties/hierarchy';
+import { AtomicHierarchy } from '../../properties/atomic';
 import { Entities } from '../../properties/common';
 
-export function getSequence(cif: mmCIF, entities: Entities, hierarchy: Hierarchy): Sequence {
-    if (!cif.entity_poly_seq._rowCount) return Sequence.fromHierarchy(hierarchy);
+export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): Sequence {
+    if (!cif.entity_poly_seq._rowCount) return Sequence.fromAtomicHierarchy(hierarchy);
 
     const { entity_id, num, mon_id } = cif.entity_poly_seq;
 

+ 2 - 2
src/mol-model/structure/model/formats/mmcif/util.ts

@@ -16,9 +16,9 @@ export function findEntityIdByAsymId(model: Model, asymId: string) {
 }
 
 export function findAtomIndexByLabelName(model: Model, residueIndex: number, atomName: string, altLoc: string | null) {
-    const { segmentMap, segments } = model.hierarchy.residueSegments
+    const { segmentMap, segments } = model.atomicHierarchy.residueSegments
     const idx = segmentMap[residueIndex]
-    const { label_atom_id, label_alt_id } = model.hierarchy.atoms;
+    const { label_atom_id, label_alt_id } = model.atomicHierarchy.atoms;
     for (let i = segments[idx], n = segments[idx + 1]; i <= n; ++i) {
         if (label_atom_id.value(i) === atomName && (!altLoc || label_alt_id.value(i) === altLoc)) return i;
     }

+ 13 - 23
src/mol-model/structure/model/model.ts

@@ -7,13 +7,13 @@
 import UUID from 'mol-util/uuid'
 import Format from './format'
 import Sequence from './properties/sequence'
-import Hierarchy from './properties/hierarchy'
-import AtomSiteConformation from './properties/atom-site-conformation'
-import Symmetry from './properties/symmetry'
-import CoarseGrained from './properties/coarse-grained'
+import { AtomicHierarchy, AtomicConformation } from './properties/atomic'
+import { ModelSymmetry } from './properties/symmetry'
+import { CoarseHierarchy, CoarseConformation } from './properties/coarse'
 import { Entities } from './properties/common';
+import { SecondaryStructure } from './properties/seconday-structure';
 
-import from_gro from './formats/gro'
+//import from_gro from './formats/gro'
 import from_mmCIF from './formats/mmcif'
 
 /**
@@ -28,15 +28,17 @@ interface Model extends Readonly<{
 
     sourceData: Format,
 
+    symmetry: ModelSymmetry,
     entities: Entities,
     sequence: Sequence,
 
-    hierarchy: Hierarchy,
-    atomSiteConformation: AtomSiteConformation,
-    symmetry: Symmetry,
-    coarseGrained: CoarseGrained,
+    atomicHierarchy: AtomicHierarchy,
+    atomicConformation: AtomicConformation,
 
-    atomCount: number,
+    properties: { secondaryStructure: SecondaryStructure },
+
+    coarseHierarchy: CoarseHierarchy,
+    coarseConformation: CoarseConformation
 }> {
 
 } { }
@@ -44,22 +46,10 @@ interface Model extends Readonly<{
 namespace Model {
     export function create(format: Format) {
         switch (format.kind) {
-            case 'gro': return from_gro(format);
+            //case 'gro': return from_gro(format);
             case 'mmCIF': return from_mmCIF(format);
         }
     }
-    // export function spatialLookup(model: Model): GridLookup {
-    //     if (model['@spatialLookup']) return model['@spatialLookup']!;
-    //     const lookup = GridLookup(model.conformation);
-    //     model['@spatialLookup'] = lookup;
-    //     return lookup;
-    // }
-    // export function bonds(model: Model): Bonds {
-    //     if (model['@bonds']) return model['@bonds']!;
-    //     const bonds = computeBonds(model);
-    //     model['@bonds'] = bonds;
-    //     return bonds;
-    // }
 }
 
 export default Model

+ 5 - 34
src/mol-model/structure/model/properties/atomic.ts

@@ -1,38 +1,9 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { ElementSymbol } from '../types';
-
-export const AtomicNumbers: { [e: string]: number | undefined } = { 'H': 1, 'D': 1, 'T': 1, 'HE': 2, 'LI': 3, 'BE': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'NE': 10, 'NA': 11, 'MG': 12, 'AL': 13, 'SI': 14, 'P': 15, 'S': 16, 'CL': 17, 'AR': 18, 'K': 19, 'CA': 20, 'SC': 21, 'TI': 22, 'V': 23, 'CR': 24, 'MN': 25, 'FE': 26, 'CO': 27, 'NI': 28, 'CU': 29, 'ZN': 30, 'GA': 31, 'GE': 32, 'AS': 33, 'SE': 34, 'BR': 35, 'KR': 36, 'RB': 37, 'SR': 38, 'Y': 39, 'ZR': 40, 'NB': 41, 'MO': 42, 'TC': 43, 'RU': 44, 'RH': 45, 'PD': 46, 'AG': 47, 'CD': 48, 'IN': 49, 'SN': 50, 'SB': 51, 'TE': 52, 'I': 53, 'XE': 54, 'CS': 55, 'BA': 56, 'LA': 57, 'CE': 58, 'PR': 59, 'ND': 60, 'PM': 61, 'SM': 62, 'EU': 63, 'GD': 64, 'TB': 65, 'DY': 66, 'HO': 67, 'ER': 68, 'TM': 69, 'YB': 70, 'LU': 71, 'HF': 72, 'TA': 73, 'W': 74, 'RE': 75, 'OS': 76, 'IR': 77, 'PT': 78, 'AU': 79, 'HG': 80, 'TL': 81, 'PB': 82, 'BI': 83, 'PO': 84, 'AT': 85, 'RN': 86, 'FR': 87, 'RA': 88, 'AC': 89, 'TH': 90, 'PA': 91, 'U': 92, 'NP': 93, 'PU': 94, 'AM': 95, 'CM': 96, 'BK': 97, 'CF': 98, 'ES': 99, 'FM': 100, 'MD': 101, 'NO': 102, 'LR': 103, 'RF': 104, 'DB': 105, 'SG': 106, 'BH': 107, 'HS': 108, 'MT': 109 };
-
-// http://dx.doi.org/10.1021/jp8111556 (or 2.0)
-export const ElementVdwRadii: { [e: number]: number | undefined } = {
-  1: 1.1, 2: 1.4, 3: 1.81, 4: 1.53, 5: 1.92, 6: 1.7, 7: 1.55, 8: 1.52, 9: 1.47, 10: 1.54, 11: 2.27, 12: 1.73, 13: 1.84, 14: 2.1, 15: 1.8, 16: 1.8, 17: 1.75, 18: 1.88, 19: 2.75, 20: 2.31, 21: 2.3, 22: 2.15, 23: 2.05, 24: 2.05, 25: 2.05, 26: 2.05, 27: 2.0, 28: 2.0, 29: 2.0, 30: 2.1, 31: 1.87, 32: 2.11, 33: 1.85, 34: 1.9, 35: 1.83, 36: 2.02, 37: 3.03, 38: 2.49, 39: 2.4, 40: 2.3, 41: 2.15, 42: 2.1, 43: 2.05, 44: 2.05, 45: 2.0, 46: 2.05, 47: 2.1, 48: 2.2, 49: 2.2, 50: 1.93, 51: 2.17, 52: 2.06, 53: 1.98, 54: 2.16, 55: 3.43, 56: 2.68, 57: 2.5, 58: 2.48, 59: 2.47, 60: 2.45, 61: 2.43, 62: 2.42, 63: 2.4, 64: 2.38, 65: 2.37, 66: 2.35, 67: 2.33, 68: 2.32, 69: 2.3, 70: 2.28, 71: 2.27, 72: 2.25, 73: 2.2, 74: 2.1, 75: 2.05, 76: 2.0, 77: 2.0, 78: 2.05, 79: 2.1, 80: 2.05, 81: 1.96, 82: 2.02, 83: 2.07, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 2.4, 91: 2.0, 92: 2.3, 93: 2.0, 94: 2.0, 95: 2.0, 96: 2.0, 97: 2.0, 98: 2.0, 99: 2.0, 100: 2.0, 101: 2.0, 102: 2.0, 103: 2.0, 104: 2.0, 105: 2.0, 106: 2.0, 107: 2.0, 108: 2.0, 109: 2.0
-}
-
-// https://doi.org/10.1515/pac-2015-0305 (table 2, 3, and 4)
-export const ElementAtomWeights: { [e: number]: number | undefined } = {
-  1: 1.008, 2: 4.0026, 3: 6.94, 4: 9.0122, 5: 10.81, 6: 10.81, 7: 14.007, 8: 15.999, 9: 18.998, 10: 20.180, 11: 22.990, 12: 24.305, 13: 26.982, 14: 28.085, 15: 30.974, 16: 32.06, 17: 35.45, 18: 39.948, 19: 39.098, 20: 40.078, 21: 44.956, 22: 47.867, 23: 50.942, 24: 51.996, 25: 54.938, 26: 55.845, 27: 58.933, 28: 58.693, 29: 63.546, 30: 65.38, 31: 69.723, 32: 72.630, 33: 74.922, 34: 78.971, 35: 79.904, 36: 83.798, 37: 85.468, 38: 87.62, 39: 88.906, 40: 91.224, 41: 92.906, 42: 95.95, 43: 96.906, 44: 101.07, 45: 102.91, 46: 106.42, 47: 107.87, 48: 112.41, 49: 114.82, 50: 118.71, 51: 121.76, 52: 127.60, 53: 127.60, 54: 131.29, 55: 132.91, 56: 137.33, 57: 138.91, 58: 140.12, 59: 140.91, 60: 144.24, 61: 144.912, 62: 150.36, 63: 151.96, 64: 157.25, 65: 158.93, 66: 162.50, 67: 164.93, 68: 167.26, 69: 168.93, 70: 173.05, 71: 174.97, 72: 178.49, 73: 180.95, 74: 183.84, 75: 186.21, 76: 190.23, 77: 192.22, 78: 195.08, 79: 196.97, 80: 200.59, 81: 204.38, 82: 207.2, 83: 208.98, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 232.04, 91: 231.04, 92: 238.03, 93: 237.048, 94: 244.064, 95: 243.061, 96: 247.070, 97: 247.070, 98: 251.079, 99: 252.083, 100: 257.095, 101: 258.098, 102: 259.101, 103: 262.110, 104: 267.122, 105: 270.131, 106: 271.134, 107: 270.133, 108: 270.134, 109: 278.156
-}
-
-export const DefaultVdwRadius = 1.7;  // C
-export const DefaultAtomWeight = 10.81;  // C
-export const DefaultAtomNumber = 0;
-
-export function VdwRadius(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultVdwRadius : ElementVdwRadii[i]!
-}
-
-export function AtomWeight(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultAtomWeight : ElementAtomWeights[i]!
-}
-
-export function AtomNumber(element: ElementSymbol): number {
-    const i = AtomicNumbers[element as any as string];
-    return i === void 0 ? DefaultAtomNumber : i
-}
+export * from './atomic/conformation'
+export * from './atomic/hierarchy'
+export * from './atomic/measures'

+ 2 - 4
src/mol-model/structure/model/properties/atom-site-conformation.ts → src/mol-model/structure/model/properties/atomic/conformation.ts

@@ -7,7 +7,7 @@
 import { Column } from 'mol-data/db'
 import UUID from 'mol-util/uuid'
 
-interface Conformation {
+export interface AtomicConformation {
     id: UUID,
 
     // ID is part of conformation because mmCIF is a leaky abstraction
@@ -23,6 +23,4 @@ interface Conformation {
     x: ArrayLike<number>,
     y: ArrayLike<number>,
     z: ArrayLike<number>
-}
-
-export default Conformation
+}

+ 10 - 12
src/mol-model/structure/model/properties/hierarchy.ts → src/mol-model/structure/model/properties/atomic/hierarchy.ts

@@ -7,7 +7,7 @@
 import { Column, Table } from 'mol-data/db'
 import { Segmentation } from 'mol-data/int'
 import { mmCIF_Schema as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-import { ElementSymbol} from '../types'
+import { ElementSymbol} from '../../types'
 
 export const AtomsSchema = {
     type_symbol: Column.Schema.Aliased<ElementSymbol>(mmCIF.atom_site.type_symbol),
@@ -40,36 +40,34 @@ export const ChainsSchema = {
 export type ChainsSchema = typeof ChainsSchema
 export interface Chains extends Table<ChainsSchema> { }
 
-export interface Data {
+export interface AtomicData {
     atoms: Atoms,
     residues: Residues,
     chains: Chains
 }
 
-export interface Segments {
+export interface AtomicSegments {
     residueSegments: Segmentation,
     chainSegments: Segmentation
 }
 
-export interface Keys {
+export interface AtomicKeys {
     // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
     // are in increasing order.
     // monotonous sequences enable for example faster secondary structure assignment.
     isMonotonous: boolean,
 
     // assign a key to each residue index.
-    residueKey: Column<number>,
+    residueKey: ArrayLike<number>,
     // assign a key to each chain index
-    chainKey: Column<number>,
+    chainKey: ArrayLike<number>,
     // assigne a key to each chain index
     // also index to the Entities table.
-    entityKey: Column<number>,
+    entityKey: ArrayLike<number>,
 
-    findChainKey(entityId: string, label_asym_id: string): number,
+    findChainKey(entityId: string, label_asym_id: string): number
     findResidueKey(entityId: string, label_asym_id: string, label_comp_id: string, auth_seq_id: number, pdbx_PDB_ins_code: string): number
 }
 
-type _Hierarchy = Data & Segments & Keys
-export interface Hierarchy extends _Hierarchy { }
-
-export default Hierarchy
+type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys
+export interface AtomicHierarchy extends _Hierarchy { }

+ 38 - 0
src/mol-model/structure/model/properties/atomic/measures.ts

@@ -0,0 +1,38 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
+import { ElementSymbol } from '../../types';
+
+export const AtomicNumbers: { [e: string]: number | undefined } = { 'H': 1, 'D': 1, 'T': 1, 'HE': 2, 'LI': 3, 'BE': 4, 'B': 5, 'C': 6, 'N': 7, 'O': 8, 'F': 9, 'NE': 10, 'NA': 11, 'MG': 12, 'AL': 13, 'SI': 14, 'P': 15, 'S': 16, 'CL': 17, 'AR': 18, 'K': 19, 'CA': 20, 'SC': 21, 'TI': 22, 'V': 23, 'CR': 24, 'MN': 25, 'FE': 26, 'CO': 27, 'NI': 28, 'CU': 29, 'ZN': 30, 'GA': 31, 'GE': 32, 'AS': 33, 'SE': 34, 'BR': 35, 'KR': 36, 'RB': 37, 'SR': 38, 'Y': 39, 'ZR': 40, 'NB': 41, 'MO': 42, 'TC': 43, 'RU': 44, 'RH': 45, 'PD': 46, 'AG': 47, 'CD': 48, 'IN': 49, 'SN': 50, 'SB': 51, 'TE': 52, 'I': 53, 'XE': 54, 'CS': 55, 'BA': 56, 'LA': 57, 'CE': 58, 'PR': 59, 'ND': 60, 'PM': 61, 'SM': 62, 'EU': 63, 'GD': 64, 'TB': 65, 'DY': 66, 'HO': 67, 'ER': 68, 'TM': 69, 'YB': 70, 'LU': 71, 'HF': 72, 'TA': 73, 'W': 74, 'RE': 75, 'OS': 76, 'IR': 77, 'PT': 78, 'AU': 79, 'HG': 80, 'TL': 81, 'PB': 82, 'BI': 83, 'PO': 84, 'AT': 85, 'RN': 86, 'FR': 87, 'RA': 88, 'AC': 89, 'TH': 90, 'PA': 91, 'U': 92, 'NP': 93, 'PU': 94, 'AM': 95, 'CM': 96, 'BK': 97, 'CF': 98, 'ES': 99, 'FM': 100, 'MD': 101, 'NO': 102, 'LR': 103, 'RF': 104, 'DB': 105, 'SG': 106, 'BH': 107, 'HS': 108, 'MT': 109 };
+
+// http://dx.doi.org/10.1021/jp8111556 (or 2.0)
+export const ElementVdwRadii: { [e: number]: number | undefined } = {
+  1: 1.1, 2: 1.4, 3: 1.81, 4: 1.53, 5: 1.92, 6: 1.7, 7: 1.55, 8: 1.52, 9: 1.47, 10: 1.54, 11: 2.27, 12: 1.73, 13: 1.84, 14: 2.1, 15: 1.8, 16: 1.8, 17: 1.75, 18: 1.88, 19: 2.75, 20: 2.31, 21: 2.3, 22: 2.15, 23: 2.05, 24: 2.05, 25: 2.05, 26: 2.05, 27: 2.0, 28: 2.0, 29: 2.0, 30: 2.1, 31: 1.87, 32: 2.11, 33: 1.85, 34: 1.9, 35: 1.83, 36: 2.02, 37: 3.03, 38: 2.49, 39: 2.4, 40: 2.3, 41: 2.15, 42: 2.1, 43: 2.05, 44: 2.05, 45: 2.0, 46: 2.05, 47: 2.1, 48: 2.2, 49: 2.2, 50: 1.93, 51: 2.17, 52: 2.06, 53: 1.98, 54: 2.16, 55: 3.43, 56: 2.68, 57: 2.5, 58: 2.48, 59: 2.47, 60: 2.45, 61: 2.43, 62: 2.42, 63: 2.4, 64: 2.38, 65: 2.37, 66: 2.35, 67: 2.33, 68: 2.32, 69: 2.3, 70: 2.28, 71: 2.27, 72: 2.25, 73: 2.2, 74: 2.1, 75: 2.05, 76: 2.0, 77: 2.0, 78: 2.05, 79: 2.1, 80: 2.05, 81: 1.96, 82: 2.02, 83: 2.07, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 2.4, 91: 2.0, 92: 2.3, 93: 2.0, 94: 2.0, 95: 2.0, 96: 2.0, 97: 2.0, 98: 2.0, 99: 2.0, 100: 2.0, 101: 2.0, 102: 2.0, 103: 2.0, 104: 2.0, 105: 2.0, 106: 2.0, 107: 2.0, 108: 2.0, 109: 2.0
+}
+
+// https://doi.org/10.1515/pac-2015-0305 (table 2, 3, and 4)
+export const ElementAtomWeights: { [e: number]: number | undefined } = {
+  1: 1.008, 2: 4.0026, 3: 6.94, 4: 9.0122, 5: 10.81, 6: 10.81, 7: 14.007, 8: 15.999, 9: 18.998, 10: 20.180, 11: 22.990, 12: 24.305, 13: 26.982, 14: 28.085, 15: 30.974, 16: 32.06, 17: 35.45, 18: 39.948, 19: 39.098, 20: 40.078, 21: 44.956, 22: 47.867, 23: 50.942, 24: 51.996, 25: 54.938, 26: 55.845, 27: 58.933, 28: 58.693, 29: 63.546, 30: 65.38, 31: 69.723, 32: 72.630, 33: 74.922, 34: 78.971, 35: 79.904, 36: 83.798, 37: 85.468, 38: 87.62, 39: 88.906, 40: 91.224, 41: 92.906, 42: 95.95, 43: 96.906, 44: 101.07, 45: 102.91, 46: 106.42, 47: 107.87, 48: 112.41, 49: 114.82, 50: 118.71, 51: 121.76, 52: 127.60, 53: 127.60, 54: 131.29, 55: 132.91, 56: 137.33, 57: 138.91, 58: 140.12, 59: 140.91, 60: 144.24, 61: 144.912, 62: 150.36, 63: 151.96, 64: 157.25, 65: 158.93, 66: 162.50, 67: 164.93, 68: 167.26, 69: 168.93, 70: 173.05, 71: 174.97, 72: 178.49, 73: 180.95, 74: 183.84, 75: 186.21, 76: 190.23, 77: 192.22, 78: 195.08, 79: 196.97, 80: 200.59, 81: 204.38, 82: 207.2, 83: 208.98, 84: 1.97, 85: 2.02, 86: 2.2, 87: 3.48, 88: 2.83, 89: 2.0, 90: 232.04, 91: 231.04, 92: 238.03, 93: 237.048, 94: 244.064, 95: 243.061, 96: 247.070, 97: 247.070, 98: 251.079, 99: 252.083, 100: 257.095, 101: 258.098, 102: 259.101, 103: 262.110, 104: 267.122, 105: 270.131, 106: 271.134, 107: 270.133, 108: 270.134, 109: 278.156
+}
+
+export const DefaultVdwRadius = 1.7;  // C
+export const DefaultAtomWeight = 10.81;  // C
+export const DefaultAtomNumber = 0;
+
+export function VdwRadius(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultVdwRadius : ElementVdwRadii[i]!
+}
+
+export function AtomWeight(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultAtomWeight : ElementAtomWeights[i]!
+}
+
+export function AtomNumber(element: ElementSymbol): number {
+    const i = AtomicNumbers[element as any as string];
+    return i === void 0 ? DefaultAtomNumber : i
+}

+ 0 - 53
src/mol-model/structure/model/properties/coarse-grained.ts

@@ -1,53 +0,0 @@
-/**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
-import { Tensor } from 'mol-math/linear-algebra';
-import { Column } from 'mol-data/db';
-
-interface CoarseGrained {
-    isDefined: boolean,
-    modelList: mmCIF['ihm_model_list'],
-    spheres: CoarseGrained.Spheres,
-    gaussians: CoarseGrained.Gaussians
-}
-
-namespace CoarseGrained {
-    export const Empty: CoarseGrained = { isDefined: false } as any;
-
-    export const enum ElementType { Sphere, Gaussian }
-
-    export interface SiteBase {
-        asym_id: string,
-        seq_id_begin: number,
-        seq_id_end: number
-    }
-
-    export interface Sphere extends SiteBase {
-        radius: number,
-        rmsf: number
-    }
-
-    export interface Gaussian extends SiteBase {
-        weight: number,
-        covariance_matrix: Tensor.Data
-    }
-
-    type Common = {
-        count: number,
-        x: ArrayLike<number>,
-        y: ArrayLike<number>,
-        z: ArrayLike<number>,
-        modelKey: ArrayLike<number>,
-        entityKey: ArrayLike<number>
-    }
-
-    export type SiteBases =  Common & { [P in keyof SiteBase]: Column<SiteBase[P]> }
-    export type Spheres =  Common & { [P in keyof Sphere]: Column<Sphere[P]> }
-    export type Gaussians = Common & { matrix_space: Tensor.Space } & { [P in keyof Gaussian]: Column<Gaussian[P]> }
-}
-
-export default CoarseGrained;

+ 8 - 0
src/mol-model/structure/model/properties/coarse.ts

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export * from './coarse/conformation'
+export * from './coarse/hierarchy'

+ 30 - 0
src/mol-model/structure/model/properties/coarse/conformation.ts

@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import UUID from 'mol-util/uuid'
+import { Mat3 } from 'mol-math/linear-algebra';
+
+export interface CoarseConformation {
+    id: UUID,
+    spheres: CoarseSphereConformation,
+    gaussians: CoarseGaussianConformation
+}
+
+export interface CoarseSphereConformation {
+    x: ArrayLike<number>,
+    y: ArrayLike<number>,
+    z: ArrayLike<number>,
+    radius: ArrayLike<number>,
+    rmsf: ArrayLike<number>
+}
+
+export interface CoarseGaussianConformation {
+    x: ArrayLike<number>,
+    y: ArrayLike<number>,
+    z: ArrayLike<number>,
+    weight: ArrayLike<number>,
+    covariance_matrix: ArrayLike<Mat3>
+}

+ 49 - 0
src/mol-model/structure/model/properties/coarse/hierarchy.ts

@@ -0,0 +1,49 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
+import { Column } from 'mol-data/db'
+import { Segmentation } from 'mol-data/int';
+
+export interface CoarsedElementKeys {
+    // indicate whether the keys form an increasing sequence and within each chain, sequence numbers
+    // are in increasing order.
+    // monotonous sequences enable for example faster secondary structure assignment.
+    isMonotonous: boolean,
+
+    // assign a key to each element
+    chainKey: ArrayLike<number>,
+    // assign a key to each element, index to the Model.entities.data table
+    entityKey: ArrayLike<number>,
+    // assign a key to each element, index to the CoarseHierarchy.models table
+    modelKey: ArrayLike<number>,
+
+    findChainKey(entityId: string, asym_id: string): number
+}
+
+export interface CoarseElementData {
+    count: number,
+    entity_id: Column<string>,
+    model_id: Column<number>,
+    asym_id: Column<string>,
+    seq_id_begin: Column<number>,
+    seq_id_end: Column<number>,
+
+    chainSegments: Segmentation
+}
+
+export type CoarseElements = CoarsedElementKeys & CoarseElementData
+
+export interface CoarseHierarchy {
+    isDefined: boolean,
+    models: mmCIF['ihm_model_list'],
+    spheres: CoarseElements,
+    gaussians: CoarseElements
+}
+
+export namespace CoarseHierarchy {
+    export const Empty: CoarseHierarchy = { isDefined: false } as any;
+}

+ 2 - 4
src/mol-model/structure/model/properties/common.ts

@@ -6,9 +6,7 @@
 
 import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
 
-interface Entities {
+export interface Entities {
     data: mmCIF['entity'],
     getEntityIndex(id: string): number
-}
-
-export { Entities }
+}

+ 9 - 6
src/mol-model/structure/model/properties/seconday-structure.ts

@@ -4,11 +4,14 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
+import { SecondaryStructureType } from '../types';
+
 /** Secondary structure "indexed" by residues. */
-export interface SecondaryStructure {
-    type: number[],
-    index: number[],
-    flags: number[],
+interface SecondaryStructure {
+    // assign flags to each residue
+    readonly type: ArrayLike<SecondaryStructureType>,
     /** unique value for each "element". This is because single sheet is speficied by multiple records. */
-    key: number[]
-}
+    readonly key: ArrayLike<number>
+}
+
+export { SecondaryStructure }

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

@@ -5,7 +5,7 @@
  */
 
 import { Column } from 'mol-data/db'
-import { Hierarchy } from './hierarchy';
+import { AtomicHierarchy } from './atomic/hierarchy';
 
 interface Sequence {
     readonly byEntityKey: { [key: number]: Sequence.Entity }
@@ -19,7 +19,7 @@ namespace Sequence {
         readonly compId: Column<string>
     }
 
-    export function fromHierarchy(hierarchy: Hierarchy): Sequence {
+    export function fromAtomicHierarchy(hierarchy: AtomicHierarchy): Sequence {
         // const { label_comp_id } = hierarchy.residues;
 
         throw 'not implemented';

+ 7 - 4
src/mol-model/structure/model/properties/symmetry.ts

@@ -8,6 +8,7 @@ import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
 import { arrayFind } from 'mol-data/util'
 import { Query } from '../../query'
 import { Model } from '../../model'
+import { Spacegroup } from 'mol-math/geometry';
 
 /** Determine an atom set and a list of operators that should be applied to that set  */
 export interface OperatorGroup {
@@ -40,12 +41,14 @@ export namespace Assembly {
     }
 }
 
-interface Symmetry {
+interface ModelSymmetry {
     readonly assemblies: ReadonlyArray<Assembly>,
+    readonly spacegroup: Spacegroup,
+    readonly isNonStandardCrytalFrame: boolean
 }
 
-namespace Symmetry {
-    export const Empty: Symmetry = { assemblies: [] };
+namespace ModelSymmetry {
+    export const Default: ModelSymmetry = { assemblies: [], spacegroup: Spacegroup.ZeroP1, isNonStandardCrytalFrame: false };
 
     export function findAssembly(model: Model, id: string): Assembly | undefined {
         const _id = id.toLocaleLowerCase();
@@ -53,4 +56,4 @@ namespace Symmetry {
     }
 }
 
-export default Symmetry
+export { ModelSymmetry }

+ 9 - 12
src/mol-model/structure/model/utils/hierarchy-keys.ts → src/mol-model/structure/model/properties/utils/atomic-keys.ts

@@ -4,10 +4,9 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Column } from 'mol-data/db'
-import { Data, Segments, Keys } from '../properties/hierarchy'
+import { AtomicData, AtomicSegments, AtomicKeys } from '../atomic'
 import { Interval, Segmentation } from 'mol-data/int'
-import { Entities } from '../properties/common';
+import { Entities } from '../common'
 
 function getResidueId(comp_id: string, seq_id: number, ins_code: string) {
     return `${comp_id} ${seq_id} ${ins_code}`;
@@ -29,14 +28,14 @@ function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key
 
 function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>, residue: Map<number, Map<string, number>>) {
     const getEntKey = entities.getEntityIndex;
-    const findChainKey: Keys['findChainKey'] = (e, c) => {
+    const findChainKey: AtomicKeys['findChainKey'] = (e, c) => {
         let eKey = getEntKey(e);
         if (eKey < 0) return -1;
         const cm = chain.get(eKey)!;
         if (!cm.has(c)) return -1;
         return cm.get(c)!;
     }
-    const findResidueKey: Keys['findResidueKey'] = (e, c, name, seq, ins) => {
+    const findResidueKey: AtomicKeys['findResidueKey'] = (e, c, name, seq, ins) => {
         let eKey = getEntKey(e);
         if (eKey < 0) return -1;
         const cm = chain.get(eKey)!;
@@ -62,7 +61,7 @@ function missingEntity(k: string) {
     throw new Error(`Missing entity entry for entity id '${k}'.`);
 }
 
-function create(data: Data, entities: Entities, segments: Segments): Keys {
+export function getAtomicKeys(data: AtomicData, entities: Entities, segments: AtomicSegments): AtomicKeys {
     const { chains, residues } = data;
 
     const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
@@ -110,12 +109,10 @@ function create(data: Data, entities: Entities, segments: Segments): Keys {
 
     return {
         isMonotonous: isMonotonous && checkMonotonous(entityKey) && checkMonotonous(chainKey) && checkMonotonous(residueKey),
-        residueKey: Column.ofIntArray(residueKey),
-        chainKey: Column.ofIntArray(chainKey),
-        entityKey: Column.ofIntArray(entityKey),
+        residueKey: residueKey,
+        chainKey: chainKey,
+        entityKey: entityKey,
         findChainKey,
         findResidueKey
     };
-}
-
-export default create;
+}

+ 84 - 0
src/mol-model/structure/model/properties/utils/coarse-keys.ts

@@ -0,0 +1,84 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Entities } from '../common';
+import { CoarseElementData, CoarsedElementKeys } from '../coarse';
+
+function getElementKey(map: Map<string, number>, key: string, counter: { index: number }) {
+    if (map.has(key)) return map.get(key)!;
+    const ret = counter.index++;
+    map.set(key, ret);
+    return ret;
+}
+
+function getElementSubstructureKeyMap(map: Map<number, Map<string, number>>, key: number) {
+    if (map.has(key)) return map.get(key)!;
+    const ret = new Map<string, number>();
+    map.set(key, ret);
+    return ret;
+}
+
+function createLookUp(entities: Entities, chain: Map<number, Map<string, number>>) {
+    const getEntKey = entities.getEntityIndex;
+    const findChainKey: CoarsedElementKeys['findChainKey'] = (e, c) => {
+        let eKey = getEntKey(e);
+        if (eKey < 0) return -1;
+        const cm = chain.get(eKey)!;
+        if (!cm.has(c)) return -1;
+        return cm.get(c)!;
+    }
+    return { findChainKey };
+}
+
+function checkMonotonous(xs: ArrayLike<number>) {
+    for (let i = 1, _i = xs.length; i < _i; i++) {
+        if (xs[i] < xs[i - 1]) {
+            return false;
+        }
+    }
+    return true;
+}
+
+function missingEntity(k: string) {
+    throw new Error(`Missing entity entry for entity id '${k}'.`);
+}
+
+function missingModel(k: string) {
+    throw new Error(`Missing entity entry for model id '${k}'.`);
+}
+
+export function getCoarseKeys(data: CoarseElementData, modelIndex: (id: number) => number, entities: Entities): CoarsedElementKeys {
+    const { model_id, entity_id, asym_id, count, chainSegments } = data;
+
+    const chainMaps = new Map<number, Map<string, number>>(), chainCounter = { index: 0 };
+    const chainKey = new Int32Array(count);
+    const entityKey = new Int32Array(count);
+    const modelKey = new Int32Array(count);
+
+    for (let i = 0; i < count; i++) {
+        entityKey[i] = entities.getEntityIndex(entity_id.value(i));
+        if (entityKey[i] < 0) missingEntity(entity_id.value(i));
+        modelKey[i] = modelIndex(model_id.value(i));
+        if (modelKey[i] < 0) missingModel('' + model_id.value(i));
+    }
+
+    for (let cI = 0; cI < chainSegments.count; cI++) {
+        const start = chainSegments.segments[cI], end = chainSegments.segments[cI + 1];
+        const map = getElementSubstructureKeyMap(chainMaps, entityKey[start]);
+        const key = getElementKey(map, asym_id.value(start), chainCounter);
+        for (let i = start; i < end; i++) chainKey[i] = key;
+    }
+
+    const { findChainKey } = createLookUp(entities, chainMaps);
+
+    return {
+        isMonotonous: checkMonotonous(entityKey) && checkMonotonous(chainKey),
+        chainKey: chainKey,
+        entityKey: entityKey,
+        modelKey: modelKey,
+        findChainKey
+    };
+}

+ 0 - 0
src/mol-model/structure/model/utils/guess-element.ts → src/mol-model/structure/model/properties/utils/guess-element.ts


+ 1 - 0
src/mol-model/structure/model/types.ts

@@ -94,6 +94,7 @@ export namespace SecondaryStructureType {
     export const Turn = ['s', 't', 'l', '']
 
     export const is: (ss: SecondaryStructureType, f: Flag) => boolean = BitFlags.has
+    export const create: (fs: Flag) => SecondaryStructureType = BitFlags.create
 
     export const enum Flag {
         None = 0x0,

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

@@ -7,11 +7,13 @@
 import Selection from './query/selection'
 import Query from './query/query'
 import * as generators from './query/generators'
+import * as modifiers from './query/modifiers'
 import props from './query/properties'
 import pred from './query/predicates'
 
 export const Queries = {
     generators,
+    modifiers,
     props,
     pred
 }

+ 47 - 95
src/mol-model/structure/query/generators.ts

@@ -7,10 +7,11 @@
 import Query from './query'
 import Selection from './selection'
 import P from './properties'
-import { Structure, ElementSet, Element, Unit } from '../structure'
+import { Element, Unit } from '../structure'
 import { OrderedSet, Segmentation } from 'mol-data/int'
+import { LinearGroupingBuilder } from './utils/builders';
 
-export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s.elements);
+export const all: Query.Provider = async (s, ctx) => Selection.Singletons(s, s);
 
 export interface AtomQueryParams {
     entityTest: Element.Predicate,
@@ -45,165 +46,116 @@ export function atoms(params?: Partial<AtomGroupsQueryParams>): Query.Provider {
 
 function atomGroupsLinear(atomTest: Element.Predicate): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
-        const builder = ElementSet.LinearBuilder(elements);
+        const builder = structure.subsetBuilder(true);
 
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            l.unit = units[unitId];
-            const set = ElementSet.groupAt(elements, i).elements;
+        let progress = 0;
+        for (const unit of units) {
+            l.unit = unit;
+            const elements = unit.elements;
 
-            builder.beginUnit();
-            for (let j = 0, _j = OrderedSet.size(set); j < _j; j++) {
-                l.element = OrderedSet.getAt(set, j);
-                if (atomTest(l)) builder.addToUnit(l.element);
+            builder.beginUnit(unit.id);
+            for (let j = 0, _j = elements.length; j < _j; j++) {
+                l.element = elements[j];
+                if (atomTest(l)) builder.addElement(l.element);
             }
-            builder.commitUnit(unitId);
+            builder.commitUnit();
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, max: units.length });
         }
 
-        return Selection.Singletons(structure, builder.getSet());
+        return Selection.Singletons(structure, builder.getStructure());
     };
 }
 
 function atomGroupsSegmented({ entityTest, chainTest, residueTest, atomTest }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
-        const builder = ElementSet.LinearBuilder(elements);
-
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            const unit = units[unitId];
+        const builder = structure.subsetBuilder(true);
 
+        let progress = 0;
+        for (const unit of units) {
             if (unit.kind !== Unit.Kind.Atomic) continue;
 
             l.unit = unit;
-            const set = ElementSet.groupAt(elements, i).elements;
+            const elements = unit.elements;
 
-            builder.beginUnit();
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
-            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);
+            builder.beginUnit(unit.id);
+            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(set, chainSegment.start);
+                l.element = OrderedSet.getAt(elements, chainSegment.start);
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(set, residueSegment.start);
+                    l.element = OrderedSet.getAt(elements, residueSegment.start);
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(set, j);
-                        if (atomTest(l)) builder.addToUnit(l.element);
+                        l.element = OrderedSet.getAt(elements, j);
+                        if (atomTest(l)) {
+                            builder.addElement(l.element);
+                        }
                     }
                 }
             }
-            builder.commitUnit(unitId);
+            builder.commitUnit();
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, max: units.length });
         }
 
-        return Selection.Singletons(structure, builder.getSet());
+        return Selection.Singletons(structure, builder.getStructure());
     };
 }
 
-class LinearGroupingBuilder {
-    private builders: ElementSet.Builder[] = [];
-    private builderMap = new Map<string, ElementSet.Builder>();
-
-    add(key: any, unit: number, atom: number) {
-        let b = this.builderMap.get(key);
-        if (!b) {
-            b = ElementSet.LinearBuilder(this.structure.elements);
-            this.builders[this.builders.length] = b;
-            this.builderMap.set(key, b);
-        }
-        b.add(unit, atom);
-    }
-
-    private allSingletons() {
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            if (this.builders[i].elementCount > 1) return false;
-        }
-        return true;
-    }
-
-    private singletonSelection(): Selection {
-        const atoms: Element[] = Element.createEmptyArray(this.builders.length);
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            atoms[i] = this.builders[i].singleton();
-        }
-        return Selection.Singletons(this.structure, ElementSet.ofAtoms(atoms, this.structure.elements));
-    }
-
-    private fullSelection() {
-        const sets: ElementSet[] = new Array(this.builders.length);
-        for (let i = 0, _i = this.builders.length; i < _i; i++) {
-            sets[i] = this.builders[i].getSet();
-        }
-        return Selection.Sequence(this.structure, sets);
-    }
-
-    getSelection(): Selection {
-        const len = this.builders.length;
-        if (len === 0) return Selection.Empty(this.structure);
-        if (this.allSingletons()) return this.singletonSelection();
-        return this.fullSelection();
-    }
-
-    constructor(private structure: Structure) { }
-}
-
 function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, groupBy }: AtomGroupsQueryParams): Query.Provider {
     return async (structure, ctx) => {
-        const { elements, units } = structure;
-        const unitIds = ElementSet.unitIndices(elements);
+        const { units } = structure;
         const l = Element.Location();
         const builder = new LinearGroupingBuilder(structure);
 
-        for (let i = 0, _i = unitIds.length; i < _i; i++) {
-            const unitId = unitIds[i];
-            const unit = units[unitId];
-
+        let progress = 0;
+        for (const unit of units) {
             if (unit.kind !== Unit.Kind.Atomic) continue;
 
             l.unit = unit;
-            const set = ElementSet.groupAt(elements, i).elements;
+            const elements = unit.elements;
 
-            const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set);
-            const residuesIt = Segmentation.transientSegments(unit.hierarchy.residueSegments, set);
+            const chainsIt = Segmentation.transientSegments(unit.model.atomicHierarchy.chainSegments, elements);
+            const residuesIt = Segmentation.transientSegments(unit.model.atomicHierarchy.residueSegments, elements);
             while (chainsIt.hasNext) {
                 const chainSegment = chainsIt.move();
-                l.element = OrderedSet.getAt(set, chainSegment.start);
+                l.element = OrderedSet.getAt(elements, chainSegment.start);
                 // test entity and chain
                 if (!entityTest(l) || !chainTest(l)) continue;
 
                 residuesIt.setSegment(chainSegment);
                 while (residuesIt.hasNext) {
                     const residueSegment = residuesIt.move();
-                    l.element = OrderedSet.getAt(set, residueSegment.start);
+                    l.element = OrderedSet.getAt(elements, residueSegment.start);
 
                     // test residue
                     if (!residueTest(l)) continue;
 
                     for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) {
-                        l.element = OrderedSet.getAt(set, j);
-                        if (atomTest(l)) builder.add(groupBy(l), unitId, l.element);
+                        l.element = OrderedSet.getAt(elements, j);
+                        if (atomTest(l)) builder.add(groupBy(l), unit.id, l.element);
                     }
                 }
             }
 
-            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: 0, max: unitIds.length });
+            progress++;
+            if (ctx.shouldUpdate) await ctx.update({ message: 'Atom Groups', current: progress, max: units.length });
         }
 
         return builder.getSelection();

+ 101 - 0
src/mol-model/structure/query/modifiers.ts

@@ -0,0 +1,101 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Segmentation } from 'mol-data/int';
+import { RuntimeContext } from 'mol-task';
+import { Structure, Unit } from '../structure';
+import Query from './query';
+import Selection from './selection';
+import { UniqueStructuresBuilder } from './utils/builders';
+import { StructureUniqueSubsetBuilder } from '../structure/util/unique-subset-builder';
+
+function getWholeResidues(ctx: RuntimeContext, source: Structure, structure: Structure) {
+    const builder = source.subsetBuilder(true);
+    for (const unit of structure.units) {
+        if (unit.kind !== Unit.Kind.Atomic) {
+            // just copy non-atomic units.
+            builder.setUnit(unit.id, unit.elements);
+            continue;
+        }
+
+        const { residueSegments } = unit.model.atomicHierarchy;
+
+        const elements = unit.elements;
+        builder.beginUnit(unit.id);
+        const residuesIt = Segmentation.transientSegments(residueSegments, elements);
+        while (residuesIt.hasNext) {
+            const rI = residuesIt.move().index;
+            for (let j = residueSegments.segments[rI], _j = residueSegments.segments[rI + 1]; j < _j; j++) {
+                builder.addElement(j);
+            }
+        }
+        builder.commitUnit();
+    }
+    return builder.getStructure();
+}
+
+export function wholeResidues(query: Query.Provider, isFlat: boolean): Query.Provider {
+    return async (structure, ctx) => {
+        const inner = await query(structure, ctx);
+        if (Selection.isSingleton(inner)) {
+            return Selection.Singletons(structure, getWholeResidues(ctx, structure, inner.structure));
+        } else {
+            const builder = new UniqueStructuresBuilder(structure);
+            let progress = 0;
+            for (const s of inner.structures) {
+                builder.add(getWholeResidues(ctx, structure, s));
+                progress++;
+                if (ctx.shouldUpdate) await ctx.update({ message: 'Whole Residues', current: progress, max: inner.structures.length });
+            }
+            return builder.getSelection();
+        }
+    };
+}
+
+
+// export function groupBy()  ...
+
+export interface IncludeSurroundingsParams {
+    radius: number,
+    // atomRadius?: Element.Property<number>,
+    wholeResidues?: boolean
+}
+
+async function getIncludeSurroundings(ctx: RuntimeContext, source: Structure, structure: Structure, params: IncludeSurroundingsParams) {
+    const builder = new StructureUniqueSubsetBuilder(source);
+    const lookup = source.lookup3d;
+    const r = params.radius;
+
+    let progress = 0;
+    for (const unit of structure.units) {
+        const { x, y, z } = unit.conformation;
+        const elements = unit.elements;
+        for (let i = 0, _i = elements.length; i < _i; i++) {
+            const e = elements[i];
+            lookup.findIntoBuilder(x(e), y(e), z(e), r, builder);
+        }
+        progress++;
+        if (progress % 2500 === 0 && ctx.shouldUpdate) await ctx.update({ message: 'Include Surroudnings', isIndeterminate: true });
+    }
+    return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure();
+}
+
+export function includeSurroundings(query: Query.Provider, params: IncludeSurroundingsParams): Query.Provider {
+    return async (structure, ctx) => {
+        const inner = await query(structure, ctx);
+        if (Selection.isSingleton(inner)) {
+            const surr = await getIncludeSurroundings(ctx, structure, inner.structure, params);
+            const ret = Selection.Singletons(structure, surr);
+            return ret;
+        } else {
+            const builder = new UniqueStructuresBuilder(structure);
+            for (const s of inner.structures) {
+                builder.add(await getIncludeSurroundings(ctx, structure, s, params));
+            }
+            return builder.getSelection();
+        }
+    };
+}

+ 42 - 42
src/mol-model/structure/query/properties.ts

@@ -5,7 +5,6 @@
  */
 
 import { Element, Unit } from '../structure'
-import CoarseGrained from '../model/properties/coarse-grained';
 import { VdwRadius } from '../model/properties/atomic';
 
 const constant = {
@@ -27,67 +26,68 @@ const atom = {
     key: Element.property(l => l.element),
 
     // Conformation
-    x: Element.property(l => l.unit.x(l.element)),
-    y: Element.property(l => l.unit.y(l.element)),
-    z: Element.property(l => l.unit.z(l.element)),
-    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.conformation.atomId.value(l.element)),
-    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.conformation.occupancy.value(l.element)),
-    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.conformation.B_iso_or_equiv.value(l.element)),
+    x: Element.property(l => l.unit.conformation.x(l.element)),
+    y: Element.property(l => l.unit.conformation.y(l.element)),
+    z: Element.property(l => l.unit.conformation.z(l.element)),
+    id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicConformation.atomId.value(l.element)),
+    occupancy: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.occupancy.value(l.element)),
+    B_iso_or_equiv: Element.property(l => !Unit.isAtomic(l.unit) ?  notAtomic() : l.unit.model.atomicConformation.B_iso_or_equiv.value(l.element)),
 
     // Hierarchy
-    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.type_symbol.value(l.element)),
-    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_atom_id.value(l.element)),
-    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.auth_atom_id.value(l.element)),
-    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.label_alt_id.value(l.element)),
-    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.atoms.pdbx_formal_charge.value(l.element)),
+    type_symbol: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element)),
+    label_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_atom_id.value(l.element)),
+    auth_atom_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.auth_atom_id.value(l.element)),
+    label_alt_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.label_alt_id.value(l.element)),
+    pdbx_formal_charge: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.atoms.pdbx_formal_charge.value(l.element)),
 
     // Derived
-    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.hierarchy.atoms.type_symbol.value(l.element))),
+    vdw_radius: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : VdwRadius(l.unit.model.atomicHierarchy.atoms.type_symbol.value(l.element))),
 }
 
 const residue = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residueKey.value(l.unit.residueIndex[l.element])),
-
-    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
-    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
-    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
-    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
-    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
-    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element]))
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residueKey[l.unit.residueIndex[l.element]]),
+
+    group_PDB: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.group_PDB.value(l.unit.residueIndex[l.element])),
+    label_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_comp_id.value(l.unit.residueIndex[l.element])),
+    auth_comp_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_comp_id.value(l.unit.residueIndex[l.element])),
+    label_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.label_seq_id.value(l.unit.residueIndex[l.element])),
+    auth_seq_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.element])),
+    pdbx_PDB_ins_code: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.residues.pdbx_PDB_ins_code.value(l.unit.residueIndex[l.element])),
+
+    // Properties
+    secondary_structure_type: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.type[l.unit.residueIndex[l.element]]),
+    secondary_structure_key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.properties.secondaryStructure.key[l.unit.residueIndex[l.element]]),
 }
 
 const chain = {
-    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chainKey.value(l.unit.chainIndex[l.element])),
+    key: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chainKey[l.unit.chainIndex[l.element]]),
 
-    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
-    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
-    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
+    label_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_asym_id.value(l.unit.chainIndex[l.element])),
+    auth_asym_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.auth_asym_id.value(l.unit.chainIndex[l.element])),
+    label_entity_id: Element.property(l => !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.chains.label_entity_id.value(l.unit.chainIndex[l.element]))
 }
 
-const coarse_grained = {
-    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.modelKey[l.element]),
-    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.entityKey[l.element]),
+const coarse = {
+    key: atom.key,
+    modelKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.modelKey[l.element]),
+    entityKey: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.entityKey[l.element]),
 
     x: atom.x,
     y: atom.y,
     z: atom.z,
 
-    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.asym_id.value(l.element)),
-    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.seq_id_begin.value(l.element)),
-    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.siteBases.seq_id_end.value(l.element)),
+    asym_id: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.asym_id.value(l.element)),
+    seq_id_begin: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_begin.value(l.element)),
+    seq_id_end: Element.property(l => !Unit.isCoarse(l.unit) ? notCoarse() : l.unit.coarseElements.seq_id_end.value(l.element)),
 
-    sphere_radius: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Sphere
-        ? notCoarse('spheres') : l.unit.spheres.radius.value(l.element)),
-    sphere_rmsf: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Sphere
-        ? notCoarse('spheres') : l.unit.spheres.rmsf.value(l.element)),
+    sphere_radius: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.radius[l.element]),
+    sphere_rmsf: Element.property(l => !Unit.isSpheres(l.unit) ? notCoarse('spheres') : l.unit.coarseConformation.rmsf[l.element]),
 
-    gaussian_weight: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Gaussian
-        ? notCoarse('gaussians') : l.unit.gaussians.weight.value(l.element)),
-    gaussian_covariance_matrix: Element.property(l => !Unit.isCoarse(l.unit) || l.unit.elementType !== CoarseGrained.ElementType.Gaussian
-        ? notCoarse('gaussians') : l.unit.gaussians.covariance_matrix.value(l.element)),
+    gaussian_weight: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.weight[l.element]),
+    gaussian_covariance_matrix: Element.property(l => !Unit.isGaussians(l.unit) ? notCoarse('gaussians') : l.unit.coarseConformation.covariance_matrix[l.element])
 }
 
-function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.hierarchy.entityKey.value(l.unit.chainIndex[l.element]); }
+function eK(l: Element.Location) { return !Unit.isAtomic(l.unit) ? notAtomic() : l.unit.model.atomicHierarchy.entityKey[l.unit.chainIndex[l.element]]; }
 
 const entity = {
     key: eK,
@@ -105,7 +105,7 @@ const entity = {
 }
 
 const unit = {
-    operator_name: Element.property(l => l.unit.operator.name),
+    operator_name: Element.property(l => l.unit.conformation.operator.name),
     model_num: Element.property(l => l.unit.model.modelNum)
 }
 
@@ -116,7 +116,7 @@ const Properties = {
     chain,
     entity,
     unit,
-    coarse_grained
+    coarse
 }
 
 type Properties = typeof Properties

+ 34 - 60
src/mol-model/structure/query/selection.ts

@@ -5,102 +5,76 @@
  */
 
 import { HashSet } from 'mol-data/generic'
-import { Structure, ElementSet } from '../structure'
+import { Structure } from '../structure'
+import { structureUnion } from './utils/structure';
 
 // A selection is a pair of a Structure and a sequence of unique AtomSets
 type Selection = Selection.Singletons | Selection.Sequence
 
 namespace Selection {
     // If each element of the selection is a singleton, we can use a more efficient representation.
-    export interface Singletons { readonly kind: 'singletons', readonly structure: Structure, readonly set: ElementSet }
-    export interface Sequence { readonly kind: 'sequence', readonly structure: Structure, readonly sets: ReadonlyArray<ElementSet> }
+    export interface Singletons { readonly kind: 'singletons', readonly source: Structure, readonly structure: Structure }
+    export interface Sequence { readonly kind: 'sequence', readonly source: Structure, readonly structures: Structure[] }
 
-    export function Singletons(structure: Structure, set: ElementSet): Singletons { return { kind: 'singletons', structure, set } }
-    export function Sequence(structure: Structure, sets: ElementSet[]): Sequence { return { kind: 'sequence', structure, sets } }
-    export function Empty(structure: Structure): Selection { return Sequence(structure, []); };
+    export function Singletons(source: Structure, structure: Structure): Singletons { return { kind: 'singletons', source, structure } }
+    export function Sequence(source: Structure, structures: Structure[]): Sequence { return { kind: 'sequence', source, structures } }
+    export function Empty(source: Structure): Selection { return Singletons(source, Structure.Empty); };
 
     export function isSingleton(s: Selection): s is Singletons { return s.kind === 'singletons'; }
-    export function isEmpty(s: Selection) { return isSingleton(s) ? ElementSet.elementCount(s.set) === 0 : s.sets.length === 0; }
+    export function isEmpty(s: Selection) { return isSingleton(s) ? s.structure.units.length === 0 : s.structures.length === 0; }
 
     export function structureCount(sel: Selection) {
-        if (isSingleton(sel)) return ElementSet.elementCount(sel.set);
-        return sel.sets.length;
+        if (isSingleton(sel)) return sel.structure.elementCount;
+        return sel.structures.length;
     }
 
     export function unionStructure(sel: Selection): Structure {
-        if (isEmpty(sel)) return Structure.Empty(sel.structure.units);
-        if (isSingleton(sel)) return Structure.create(sel.structure.units, sel.set);
-        return Structure.create(sel.structure.units, ElementSet.union(sel.sets, sel.structure.elements));
-    }
-
-    export function getAt(sel: Selection, i: number): Structure {
-        if (isSingleton(sel)) {
-            const atom = ElementSet.elementAt(sel.set, i);
-            return Structure.create(sel.structure.units, ElementSet.singleton(atom, sel.structure.elements));
-        }
-        return Structure.create(sel.structure.units, sel.sets[i]);
-    }
-
-    export function toStructures(sel: Selection): Structure[] {
-        const { units } = sel.structure;
-        if (isSingleton(sel)) {
-            const ret: Structure[] = new Array(ElementSet.elementCount(sel.set));
-            const atoms = ElementSet.elements(sel.set);
-            let offset = 0;
-            while (atoms.hasNext) {
-                const atom = atoms.move();
-                ret[offset++] = Structure.create(units, ElementSet.singleton(atom, sel.structure.elements))
-            }
-            return ret;
-        } else {
-            const { sets } = sel;
-            const ret: Structure[] = new Array(sets.length);
-            for (let i = 0, _i = sets.length; i < _i; i++) ret[i] = Structure.create(units, sets[i]);
-            return ret;
-        }
+        if (isEmpty(sel)) return Structure.Empty;
+        if (isSingleton(sel)) return sel.structure;
+        return structureUnion(sel.source, sel.structures);
     }
 
     export interface Builder {
-        add(set: ElementSet): void,
+        add(structure: Structure): void,
         getSelection(): Selection
     }
 
-    function getSelection(structure: Structure, sets: ElementSet[], allSingletons: boolean) {
-        const len = sets.length;
-        if (len === 0) return Empty(structure);
-        if (allSingletons) return Singletons(structure, ElementSet.union(sets, structure.elements));
-        return Sequence(structure, sets);
+    function getSelection(source: Structure, structures: Structure[], allSingletons: boolean) {
+        const len = structures.length;
+        if (len === 0) return Empty(source);
+        if (allSingletons) return Singletons(source, structureUnion(source, structures));
+        return Sequence(source, structures);
     }
 
     class LinearBuilderImpl implements Builder {
-        private sets: ElementSet[] = [];
+        private structures: Structure[] = [];
         private allSingletons = true;
 
-        add(atoms: ElementSet) {
-            const atomCount = ElementSet.elementCount(atoms);
-            if (atomCount === 0) return;
-            this.sets[this.sets.length] = atoms;
-            if (atomCount !== 1) this.allSingletons = false;
+        add(structure: Structure) {
+            const elementCount = structure.elementCount;
+            if (elementCount === 0) return;
+            this.structures[this.structures.length] = structure;
+            if (elementCount !== 1) this.allSingletons = false;
         }
 
-        getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); }
+        getSelection() { return getSelection(this.source, this.structures, this.allSingletons); }
 
-        constructor(private structure: Structure) { }
+        constructor(private source: Structure) { }
     }
 
     class HashBuilderImpl implements Builder {
-        private sets: ElementSet[] = [];
+        private structures: Structure[] = [];
         private allSingletons = true;
-        private uniqueSets = HashSet(ElementSet.hashCode, ElementSet.areEqual);
+        private uniqueSets = HashSet(Structure.hashCode, Structure.areEqual);
 
-        add(atoms: ElementSet) {
-            const atomCount = ElementSet.elementCount(atoms);
-            if (atomCount === 0 || !this.uniqueSets.add(atoms)) return;
-            this.sets[this.sets.length] = atoms;
+        add(structure: Structure) {
+            const atomCount = structure.elementCount;
+            if (atomCount === 0 || !this.uniqueSets.add(structure)) return;
+            this.structures[this.structures.length] = structure;
             if (atomCount !== 1) this.allSingletons = false;
         }
 
-        getSelection() { return getSelection(this.structure, this.sets, this.allSingletons); }
+        getSelection() { return getSelection(this.structure, this.structures, this.allSingletons); }
 
         constructor(private structure: Structure) { }
     }

+ 82 - 0
src/mol-model/structure/query/utils/builders.ts

@@ -0,0 +1,82 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Element, Structure } from '../../structure';
+import Selection from '../selection';
+import { HashSet } from 'mol-data/generic';
+import { structureUnion } from './structure';
+import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
+
+export class UniqueStructuresBuilder {
+    private set = HashSet(Structure.hashCode, Structure.areEqual);
+    private structures: Structure[] = [];
+    private allSingletons = true;
+
+    add(s: Structure) {
+        if (!s.elementCount) return;
+        if (s.elementCount !== 1) this.allSingletons = false;
+        if (this.set.add(s)) {
+            this.structures[this.structures.length] = s;
+        }
+    }
+
+    getSelection() {
+        if (this.allSingletons) return Selection.Singletons(this.source, structureUnion(this.source, this.structures));
+        return Selection.Sequence(this.source, this.structures);
+    }
+
+    constructor(private source: Structure) {
+    }
+}
+
+export class LinearGroupingBuilder {
+    private builders: StructureSubsetBuilder[] = [];
+    private builderMap = new Map<string, StructureSubsetBuilder>();
+
+    add(key: any, unit: number, element: number) {
+        let b = this.builderMap.get(key);
+        if (!b) {
+            b = this.source.subsetBuilder(true);
+            this.builders[this.builders.length] = b;
+            this.builderMap.set(key, b);
+        }
+        b.addToUnit(unit, element);
+    }
+
+    private allSingletons() {
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            if (this.builders[i].elementCount > 1) return false;
+        }
+        return true;
+    }
+
+    private singletonSelection(): Selection {
+        const builder = this.source.subsetBuilder(true);
+        const loc = Element.Location();
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            this.builders[i].setSingletonLocation(loc);
+            builder.addToUnit(loc.unit.id, loc.element);
+        }
+        return Selection.Singletons(this.source, builder.getStructure());
+    }
+
+    private fullSelection() {
+        const structures: Structure[] = new Array(this.builders.length);
+        for (let i = 0, _i = this.builders.length; i < _i; i++) {
+            structures[i] = this.builders[i].getStructure();
+        }
+        return Selection.Sequence(this.source, structures);
+    }
+
+    getSelection(): Selection {
+        const len = this.builders.length;
+        if (len === 0) return Selection.Empty(this.source);
+        if (this.allSingletons()) return this.singletonSelection();
+        return this.fullSelection();
+    }
+
+    constructor(private source: Structure) { }
+}

+ 104 - 0
src/mol-model/structure/query/utils/structure.ts

@@ -0,0 +1,104 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { Structure, Unit } from '../../structure'
+import { SortedArray } from 'mol-data/int';
+import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
+
+export function structureUnion(source: Structure, structures: Structure[]) {
+    if (structures.length === 0) return Structure.Empty;
+    if (structures.length === 1) return structures[0];
+
+    const unitMap = new Map<number, SortedArray>();
+    const fullUnits = new Set<number>();
+
+    for (const { units } of structures) {
+        for (let i = 0, _i = units.length; i < _i; i++) {
+            const u = units[i];
+            if (unitMap.has(u.id)) {
+                // check if there is anything more to union in this particual unit.
+                if (fullUnits.has(u.id)) continue;
+                const merged = SortedArray.union(unitMap.get(u.id)!, u.elements);
+                unitMap.set(u.id, merged);
+                if (merged.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
+            } else {
+                unitMap.set(u.id, u.elements);
+                if (u.elements.length === source.unitMap.get(u.id).elements.length) fullUnits.add(u.id);
+            }
+        }
+    }
+
+    const builder = source.subsetBuilder(true);
+    unitMap.forEach(buildUnion, builder);
+    return builder.getStructure();
+}
+
+function buildUnion(this: StructureSubsetBuilder, elements: SortedArray, id: number) {
+    this.setUnit(id, elements);
+}
+
+export function structureAreIntersecting(sA: Structure, sB: Structure): boolean {
+    if (sA === sB) return true;
+
+    let a, b;
+    if (sA.units.length < sB.units.length) { a = sA; b = sB; }
+    else { a = sB; b = sA; }
+
+    const aU = a.units, bU = b.unitMap;
+
+    for (let i = 0, _i = aU.length; i < _i; i++) {
+        const u = aU[i];
+        if (!bU.has(u.id)) continue;
+        const v = bU.get(u.id);
+        if (SortedArray.areIntersecting(u.elements, v.elements)) return true;
+    }
+
+    return false;
+}
+
+export function structureIntersect(sA: Structure, sB: Structure): Structure {
+    if (sA === sB) return sA;
+    if (!structureAreIntersecting(sA, sB)) return Structure.Empty;
+
+    let a, b;
+    if (sA.units.length < sB.units.length) { a = sA; b = sB; }
+    else { a = sB; b = sA; }
+
+    const aU = a.units, bU = b.unitMap;
+    const units: Unit[] = [];
+
+    for (let i = 0, _i = aU.length; i < _i; i++) {
+        const u = aU[i];
+        if (!bU.has(u.id)) continue;
+        const v = bU.get(u.id);
+        if (SortedArray.areIntersecting(u.elements, v.elements)) {
+            const int = SortedArray.intersect(u.elements, v.elements);
+            units[units.length] = u.getChild(int);
+        }
+    }
+
+    return Structure.create(units);
+}
+
+export function structureSubtract(a: Structure, b: Structure): Structure {
+    if (a === b) return Structure.Empty;
+    if (!structureAreIntersecting(a, b)) return a;
+
+    const aU = a.units, bU = b.unitMap;
+    const units: Unit[] = [];
+
+    for (let i = 0, _i = aU.length; i < _i; i++) {
+        const u = aU[i];
+        if (!bU.has(u.id)) continue;
+        const v = bU.get(u.id);
+        const sub = SortedArray.intersect(u.elements, v.elements);
+        if (sub.length > 0) {
+            units[units.length] = u.getChild(sub);
+        }
+    }
+
+    return Structure.create(units);
+}

+ 2 - 4
src/mol-model/structure/structure.ts

@@ -5,10 +5,8 @@
  */
 
 import Element from './structure/element'
-import ElementSet from './structure/element/set'
-import ElementGroup from './structure/element/group'
 import Structure from './structure/structure'
 import Unit from './structure/unit'
-import Symmetry from './structure/symmetry'
+import StructureSymmetry from './structure/symmetry'
 
-export { Element, ElementSet, ElementGroup, Structure, Unit, Symmetry }
+export { Element, Structure, Unit, StructureSymmetry }

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

@@ -15,8 +15,8 @@ namespace Element {
     export const Zero: Element = Tuple.Zero;
     export const create: (unit: number, index: number) => Element = Tuple.create;
     export const is: (x: any) => x is Element = Tuple.is;
-    export const unit: (e: Element) => number = Tuple.fst;
-    export const index: (e: Element) => number = Tuple.snd;
+    export const unitId: (e: Element) => number = Tuple.fst;
+    export const elementIndex: (e: Element) => number = Tuple.snd;
     export const areEqual: (e: Element, b: Element) => boolean = Tuple.areEqual;
     export const hashCode: (e: Element) => number = Tuple.hashCode;
 
@@ -29,8 +29,8 @@ namespace Element {
     export interface Predicate extends Property<boolean> { }
 
     export function updateLocation(structure: Structure, l: Location, element: Element) {
-        l.unit = structure.units[unit(element)];
-        l.element = index(element);
+        l.unit = structure.units[unitId(element)];
+        l.element = elementIndex(element);
         return l;
     }
 

+ 0 - 77
src/mol-model/structure/structure/element/group.ts

@@ -1,77 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { OrderedSet } from 'mol-data/int'
-import { Lookup3D } from 'mol-math/geometry'
-import Unit from '../unit'
-import { GroupBonds } from './properties/bonds/group-data';
-
-interface ElementGroup {
-    elements: OrderedSet,
-    // Unique identifier of the group, usable as partial key for various "caches".
-    key: number,
-
-    __lookup3d__?: Lookup3D,
-    __bonds__?: GroupBonds
-}
-
-namespace ElementGroup {
-    export const Empty = createNew(OrderedSet.Empty)
-
-    export function singleton(idx: number) {
-        return createNew(OrderedSet.ofSingleton(idx));
-    }
-
-    export function createNew(elements: OrderedSet): ElementGroup {
-        return { key: nextKey(), elements };
-    }
-
-    export function create(unit: Unit, elements: OrderedSet): ElementGroup {
-        if (OrderedSet.areEqual(elements, unit.fullGroup.elements)) return unit.fullGroup;
-        return createNew(elements);
-    }
-
-    export function createChild(parent: ElementGroup, elements: OrderedSet): ElementGroup {
-        if (OrderedSet.areEqual(elements, parent.elements)) return parent;
-        return createNew(elements);
-    }
-
-    export function size(group: ElementGroup) { return OrderedSet.size(group.elements); }
-    export function has(group: ElementGroup, element: number) { return OrderedSet.has(group.elements, element); }
-    export function getAt(group: ElementGroup, i: number) { return OrderedSet.getAt(group.elements, i); }
-    export function indexOf(group: ElementGroup, element: number) { return OrderedSet.indexOf(group.elements, element); }
-    export function hashCode(group: ElementGroup) { return OrderedSet.hashCode(group.elements); }
-    export function areEqual(a: ElementGroup, b: ElementGroup) { return OrderedSet.areEqual(a.elements, b.elements); }
-
-    export function intersect(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.intersect(a.elements, b.elements);
-        if (set === a.elements) return a;
-        if (set === b.elements) return b;
-        return createNew(set);
-    }
-
-    export function union(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.union(a.elements, b.elements);
-        if (set === a.elements) return a;
-        if (set === b.elements) return b;
-        return createNew(set);
-    }
-
-    export function subtract(a: ElementGroup, b: ElementGroup) {
-        const set = OrderedSet.subtract(a.elements, b.elements);
-        if (set === a.elements) return a;
-        return createNew(set);
-    }
-
-    let _id = 0;
-    function nextKey() {
-        const ret = _id;
-        _id = (_id + 1) % 0x3fffffff;
-        return ret;
-    }
-}
-
-export default ElementGroup

+ 0 - 10
src/mol-model/structure/structure/element/impl/properties.ts

@@ -1,10 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-// TODO: bounding sphere
-// TODO: distance, areWithIn?
-// TODO: check connected
-// TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?

+ 0 - 65
src/mol-model/structure/structure/element/impl/set-builder.ts

@@ -1,65 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import ElementSet from '../set'
-import Element from '../../element'
-import { OrderedSet, IntMap } from 'mol-data/int'
-import { sortArray } from 'mol-data/util/sort'
-
-export class Builder {
-    private keys: number[] = [];
-    private units = IntMap.Mutable<number[]>();
-    private currentUnit: number[] = [];
-
-    elementCount = 0;
-
-    add(u: number, e: number) {
-        const unit = this.units.get(u);
-        if (!!unit) { unit[unit.length] = e; }
-        else {
-            this.units.set(u, [e]);
-            this.keys[this.keys.length] = u;
-        }
-        this.elementCount++;
-    }
-
-    beginUnit() { this.currentUnit = this.currentUnit.length > 0 ? [] : this.currentUnit; }
-    addToUnit(a: number) { this.currentUnit[this.currentUnit.length] = a; this.elementCount++; }
-    commitUnit(u: number) {
-        if (this.currentUnit.length === 0) return;
-        this.keys[this.keys.length] = u;
-        this.units.set(u, this.currentUnit);
-    }
-
-    getSet(): ElementSet {
-        const generator = ElementSet.TemplateGenerator(this.parent);
-
-        for (let i = 0, _i = this.keys.length; i < _i; i++) {
-            const k = this.keys[i];
-            const unit = this.units.get(k);
-            const l = unit.length;
-            if (!this.sorted && l > 1) sortArray(unit);
-            generator.add(k, OrderedSet.ofSortedArray(unit));
-        }
-
-        return generator.getSet();
-    }
-
-    singleton(): Element {
-        const u = this.keys[0];
-        return Element.create(u, this.units.get(u)[0]);
-    }
-
-    constructor(private parent: ElementSet, private sorted: boolean) { }
-}
-
-export function LinearBuilder(parent: ElementSet) {
-    return new Builder(parent, true);
-}
-
-export function UnsortedBuilder(parent: ElementSet) {
-    return new Builder(parent, false);
-}

+ 0 - 464
src/mol-model/structure/structure/element/impl/set.ts

@@ -1,464 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { SortedArray, Interval, Iterator, OrderedSet as OS, IntMap } from 'mol-data/int'
-import { sortArray } from 'mol-data/util/sort'
-import { hash1 } from 'mol-data/util/hash-functions'
-import Element from '../../element'
-import ElementGroup from '../group'
-import { ElementSetLookup3D } from '../../util/lookup3d'
-import Structure from '../../structure';
-
-/** Long and painful implementation starts here */
-
-export type ElementSetImpl = {
-    groups: IntMap<ElementGroup>,
-    offsets: Int32Array,
-    hashCode: number,
-    keys: SortedArray,
-
-    __lookup3d__?: ElementSetLookup3D
-}
-
-export const Empty: ElementSetImpl = { groups: IntMap.Empty, offsets: new Int32Array(1), hashCode: 0, keys: SortedArray.Empty };
-
-export function ofElements(elements: ArrayLike<Element>, template: ElementSetImpl): ElementSetImpl {
-    return ofElementsImpl(elements, template);
-}
-
-export function singleton(element: Element, template: ElementSetImpl) {
-    return singletonImpl(element, template);
-}
-
-export function getKeys(set: ElementSetImpl): SortedArray {
-    return set.keys;
-}
-
-export function keyCount(set: ElementSetImpl): number {
-    return set.keys.length;
-}
-
-export function hasKey(set: ElementSetImpl, key: number): boolean {
-    return set.groups.has(key);
-}
-
-export function getKey(set: ElementSetImpl, index: number): number {
-    return set.keys[index];
-}
-
-export function hasAtom(set: ElementSetImpl, t: Element): boolean {
-    const os = set.groups.get(Element.unit(t));
-    return !!os && ElementGroup.has(os, Element.index(t));
-}
-
-export function getByKey(set: ElementSetImpl, key: number): ElementGroup {
-    return set.groups.get(key) || ElementGroup.Empty;
-}
-
-export function getByIndex(set: ElementSetImpl, index: number): ElementGroup {
-    const key = set.keys[index];
-    return set.groups.get(key) || ElementGroup.Empty;
-}
-
-export function getAt(set: ElementSetImpl, i: number): Element {
-    const { offsets, keys } = set;
-    const o = getOffsetIndex(offsets, i);
-    if (o >= offsets.length - 1) return Element.Zero;
-    const k = keys[o];
-    const e = ElementGroup.getAt(set.groups.get(k), i - offsets[o]);
-    return Element.create(k, e);
-}
-
-export function indexOf(set: ElementSetImpl, t: Element) {
-    const { keys } = set;
-    const u = Element.unit(t);
-    const setIdx = SortedArray.indexOf(keys, u);
-    if (setIdx < 0) return -1;
-    const o = ElementGroup.indexOf(set.groups.get(u), Element.index(t));
-    if (o < 0) return -1;
-    return set.offsets[setIdx] + o;
-}
-
-/** Number elements in the "child" sets */
-export function size(set: ElementSetImpl) {
-    return set.offsets[set.offsets.length - 1];
-}
-
-export function hashCode(set: ElementSetImpl) {
-    if (set.hashCode !== -1) return set.hashCode;
-    return computeHash(set);
-}
-
-export function areEqual(a: ElementSetImpl, b: ElementSetImpl) {
-    if (a === b) return true;
-    if (size(a) !== size(a)) return false;
-
-    const keys = a.keys;
-    if (!SortedArray.areEqual(keys, b.keys)) return false;
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        if (!ElementGroup.areEqual(aG.get(k), bG.get(k))) return false;
-    }
-    return true;
-}
-
-export function areIntersecting(a: ElementSetImpl, b: ElementSetImpl) {
-    if (a === b) return true;
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(a.keys, b.keys)) return false;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k), bk = bG.get(k);
-        if (!!ak && !!bk && OS.areIntersecting(ak.elements, bk.elements)) return true;
-    }
-    return false;
-}
-
-export function intersect(a: ElementSetImpl, b: ElementSetImpl) {
-    if (a === b) return a;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(a.keys, b.keys)) return Empty;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    const generator = new ChildGenerator(a, b);
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const bk = bG.get(k);
-        if (!bk) continue;
-        const ak = aG.get(k);
-        generator.add(k, ElementGroup.intersect(aG.get(k), bk), ak, bk);
-    }
-    return generator.getSet();
-}
-
-export function subtract(a: ElementSetImpl, b: ElementSetImpl) {
-    if (a === b) return Empty;
-
-    const keysA = a.keys, keysB = b.keys;
-    if (!SortedArray.areIntersecting(keysA, keysB)) return a;
-    const r = SortedArray.findRange(keysA, SortedArray.min(keysB), SortedArray.max(keysB));
-    const start = Interval.start(r), end = Interval.end(r);
-
-    const generator = new ChildGenerator(a, b);
-    const { groups: aG } = a;
-    const { groups: bG } = b;
-    for (let i = 0; i < start; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k);
-        generator.addA(k, ak, ak);
-    }
-    for (let i = start; i < end; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k), bk = bG.get(k);
-        if (!!bk) {
-            const subtraction = ElementGroup.subtract(ak, bk);
-            generator.addA(k, subtraction, ak);
-        } else {
-            generator.addA(k, ak, ak);
-        }
-    }
-    for (let i = end, _i = keysA.length; i < _i; i++) {
-        const k = keysA[i];
-        const ak = aG.get(k);
-        generator.addA(k, ak, ak);
-    }
-    return generator.getSet();
-}
-
-export function unionMany(sets: ArrayLike<ElementSetImpl>, template: ElementSetImpl) {
-    return findUnion(sets, template);
-}
-
-class ElementsIterator implements Iterator<Element> {
-    private unit: number = 0;
-    private keyCount: number;
-    private setIndex = -1;
-    private currentIndex = 0;
-    private currentSize = 0;
-    private currentSet: OS = OS.Empty;
-
-    hasNext: boolean = false;
-
-    move() {
-        if (!this.hasNext) return Element.Zero;
-        const ret = Element.create(this.unit, OS.getAt(this.currentSet, this.currentIndex++));
-        if (this.currentIndex >= this.currentSize) this.advance();
-        return ret;
-    }
-
-    private advance() {
-        if (++this.setIndex >= this.keyCount) {
-            this.hasNext = false;
-            return false;
-        }
-        this.unit = this.elements.keys[this.setIndex];
-        this.currentSet = this.elements.groups.get(this.unit).elements;
-        this.currentIndex = 0;
-        this.currentSize = OS.size(this.currentSet);
-        return true;
-    }
-
-    constructor(private elements: ElementSetImpl) {
-        this.keyCount = elements.keys.length;
-        this.hasNext = this.keyCount > 0;
-        this.advance();
-    }
-}
-
-export function values(set: ElementSetImpl): Iterator<Element> {
-    return new ElementsIterator(set);
-}
-
-export class TemplateAtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<ElementGroup>();
-    private templateGroups: IntMap<ElementGroup>;
-    private equalGroups = 0;
-
-    add(unit: number, set: OS) {
-        if (OS.size(set) === 0) return;
-        this.keys[this.keys.length] = unit;
-        if (this.templateGroups.has(unit)) {
-            const t = this.templateGroups.get(unit);
-            if (OS.areEqual(t.elements, set)) {
-                this.groups.set(unit, t);
-                this.equalGroups++;
-            } else {
-                this.groups.set(unit, ElementGroup.createNew(set));
-            }
-        } else {
-            this.groups.set(unit, ElementGroup.createNew(set));
-        }
-    }
-
-    getSet(): ElementSetImpl {
-        if (this.equalGroups === this.template.keys.length && this.equalGroups === this.keys.length) {
-            return this.template;
-        }
-        return create(this.keys, this.groups);
-    }
-
-    constructor(private template: ElementSetImpl) {
-        this.templateGroups = template.groups;
-    }
-}
-
-export function TemplateGenerator(template: ElementSetImpl) {
-    return new TemplateAtomSetGenerator(template);
-}
-
-export class AtomSetGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<ElementGroup>();
-
-    add(unit: number, group: ElementGroup) {
-        if (ElementGroup.size(group) === 0) return;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    getSet(): ElementSetImpl {
-        return create(this.keys, this.groups);
-    }
-}
-
-export function Generator() {
-    return new AtomSetGenerator();
-}
-
-export function getLookup3d(s: Structure) {
-    const set = s.elements as any as ElementSetImpl;
-    if (set.__lookup3d__) return set.__lookup3d__;
-    set.__lookup3d__ = ElementSetLookup3D.create(s);
-    return set.__lookup3d__;
-}
-
-/** When adding groups, compare them to existing ones. If they all match, return the whole original set. */
-class ChildGenerator {
-    private keys: number[] = [];
-    private groups = IntMap.Mutable<ElementGroup>();
-    private aEqual = 0;
-    private bEqual = 0;
-
-    add(unit: number, group: ElementGroup, a: ElementGroup, b: ElementGroup) {
-        if (ElementGroup.size(group) === 0) return;
-        if (a === group) this.aEqual++;
-        if (b === group) this.bEqual++;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    addA(unit: number, group: ElementGroup, a: ElementGroup) {
-        if (ElementGroup.size(group) === 0) return;
-
-        if (a === group) this.aEqual++;
-        this.keys[this.keys.length] = unit;
-        this.groups.set(unit, group);
-    }
-
-    constructor(private a: ElementSetImpl, private b: ElementSetImpl) {
-    }
-
-    getSet(): ElementSetImpl {
-        if (this.aEqual === this.a.keys.length) return this.a;
-        if (this.bEqual === this.b.keys.length) return this.b;
-        return create(this.keys, this.groups);
-    }
-}
-
-function create(keys: number[], groups: IntMap<ElementGroup>): ElementSetImpl {
-    sortArray(keys);
-    let runningSize = 0;
-    const offsets = new Int32Array(keys.length + 1);
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        runningSize += ElementGroup.size(groups.get(keys[i]));
-        offsets[i + 1] = runningSize;
-    }
-    return { keys: SortedArray.ofSortedArray(keys), groups: IntMap.asImmutable(groups), offsets, hashCode: -1 };
-}
-
-function getUniqueElements(xs: number[]): number[] {
-    let count = 1;
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] !== xs[i]) count++;
-    }
-    const ret = new (xs as any).constructor(count);
-    ret[0] = xs[0];
-    let offset = 1;
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] !== xs[i]) ret[offset++] = xs[i];
-    }
-    return ret;
-}
-
-function normalizeArray(xs: number[]): number[] {
-    sortArray(xs);
-    for (let i = 1, _i = xs.length; i < _i; i++) {
-        if (xs[i - 1] === xs[i]) return getUniqueElements(xs);
-    }
-    return xs;
-}
-
-function ofElementsImpl(xs: ArrayLike<Element>, template: ElementSetImpl) {
-    if (xs.length === 0) return Empty;
-
-    const elements = IntMap.Mutable<number[]>();
-    const keys: number[] = [];
-    for (let i = 0, _i = xs.length; i < _i; i++) {
-        const x = xs[i];
-        const u = Element.unit(x), v = Element.index(x);
-        if (elements.has(u)) {
-            const set = elements.get(u);
-            set[set.length] = v;
-        } else {
-            keys[keys.length] = u;
-            elements.set(u, [v]);
-        }
-    }
-
-    const generator = TemplateGenerator(template);
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        generator.add(k, OS.ofSortedArray(normalizeArray(elements.get(k))));
-    }
-
-    return generator.getSet();
-}
-
-function singletonImpl(element: Element, template: ElementSetImpl) {
-    const k = Element.unit(element), i = Element.index(element);
-    const { groups } = template;
-    const gs = IntMap.Mutable<ElementGroup>();
-    if (groups.has(k)) {
-        const g = groups.get(k);
-        if (ElementGroup.size(g) === 1 && ElementGroup.getAt(g, 0) === i) {
-            gs.set(k, g);
-            return create([k], gs);
-        }
-    }
-    gs.set(k, ElementGroup.createNew(OS.ofSingleton(i)));
-    return create([k], gs);
-}
-
-function getOffsetIndex(xs: ArrayLike<number>, value: number) {
-    let min = 0, max = xs.length - 1;
-    while (min < max) {
-        const mid = (min + max) >> 1;
-        const v = xs[mid];
-        if (value < v) max = mid - 1;
-        else if (value > v) min = mid + 1;
-        else return mid;
-    }
-    if (min > max) {
-        return max;
-    }
-    return value < xs[min] ? min - 1 : min;
-}
-
-function computeHash(set: ElementSetImpl) {
-    const { keys, groups } = set;
-    let hash = 23;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        hash = (31 * hash + k) | 0;
-        hash = (31 * hash + ElementGroup.hashCode(groups.get(k))) | 0;
-    }
-    hash = (31 * hash + size(set)) | 0;
-    hash = hash1(hash);
-    set.hashCode = hash;
-    return hash;
-}
-
-function findUnion(sets: ArrayLike<ElementSetImpl>, template: ElementSetImpl) {
-    if (!sets.length) return Empty;
-    if (sets.length === 1) return sets[0];
-    if (sets.length === 2 && sets[0] === sets[1]) return sets[0];
-
-    const keys: number[] = [];
-    const groups = IntMap.Mutable<ElementGroup>();
-    for (let i = 0, _i = sets.length; i < _i; i++) {
-        unionInto(keys, groups, sets[i]);
-    }
-
-    return normalizeUnion(keys, groups, template);
-}
-
-function normalizeUnion(keys: number[], groups: IntMap.Mutable<ElementGroup>, template: ElementSetImpl) {
-    let equalCount = 0;
-    let tg = template.groups, a: ElementGroup, t: ElementGroup;
-    for (let i = 0, _i = keys.length; i < _i; i++) {
-        const k = keys[i];
-        if (tg.has(k) && ElementGroup.areEqual(a = groups.get(k), t = tg.get(k))) {
-            groups.set(k, t);
-            equalCount++;
-        }
-    }
-    return equalCount === template.keys.length && equalCount === keys.length ? template : create(keys, groups);
-}
-
-function unionInto(keys: number[], groups: IntMap.Mutable<ElementGroup>, a: ElementSetImpl) {
-    const setKeys = a.keys;
-    const { groups: aG } = a;
-    for (let i = 0, _i = setKeys.length; i < _i; i++) {
-        const k = setKeys[i];
-        if (groups.has(k)) {
-            groups.set(k, ElementGroup.union(aG.get(k), groups.get(k)))
-        } else {
-            keys[keys.length] = k;
-            groups.set(k, aG.get(k));
-        }
-    }
-}

+ 0 - 67
src/mol-model/structure/structure/element/set.ts

@@ -1,67 +0,0 @@
-/**
- * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author David Sehnal <david.sehnal@gmail.com>
- */
-
-import { SortedArray, Iterator, OrderedSet } from 'mol-data/int'
-import Element from '../element'
-import ElementGroup from './group'
-import * as Impl from './impl/set'
-import * as Builders from './impl/set-builder'
-import { ElementSetLookup3D } from '../util/lookup3d';
-import Structure from '../structure';
-
-/**
- * A map-like representation of grouped atom set
- *
- * Essentially corresponds to the type { [unitId: number]: ElementGroup }.
- */
-namespace ElementSet {
-    export const Empty: ElementSet = Impl.Empty as any;
-
-    export const ofAtoms: (elements: ArrayLike<Element>, template: ElementSet) => ElementSet = Impl.ofElements as any;
-    export const singleton: (element: Element, template: ElementSet) => ElementSet = Impl.singleton as any;
-
-    export const unitIndices: (set: ElementSet) => SortedArray = Impl.getKeys as any;
-    export const unitHas: (set: ElementSet, index: number) => boolean = Impl.hasKey as any;
-
-    export const groupCount: (set: ElementSet) => number = Impl.keyCount as any;
-    export const groupUnitIndex: (set: ElementSet, index: number) => number = Impl.getKey as any;
-    export const groupFromUnitIndex: (set: ElementSet, unitId: number) => ElementGroup = Impl.getByKey as any;
-    export const groupAt: (set: ElementSet, index: number) => ElementGroup = Impl.getByIndex as any;
-
-    export const elementCount: (set: ElementSet) => number = Impl.size as any;
-    export const elementHas: (set: ElementSet, x: Element) => boolean = Impl.hasAtom as any;
-    export const elementIndexOf: (set: ElementSet, x: Element) => number = Impl.indexOf as any;
-    export const elementAt: (set: ElementSet, i: number) => Element = Impl.getAt as any;
-    export const elements: (set: ElementSet) => Iterator<Element> = Impl.values as any;
-
-    export const hashCode: (set: ElementSet) => number = Impl.hashCode as any;
-    export const areEqual: (a: ElementSet, b: ElementSet) => boolean = Impl.areEqual as any;
-    export const areIntersecting: (a: ElementSet, b: ElementSet) => boolean = Impl.areIntersecting as any;
-
-    export const union: (sets: ArrayLike<ElementSet>, template: ElementSet) => ElementSet = Impl.unionMany as any;
-    export const intersect: (a: ElementSet, b: ElementSet) => ElementSet = Impl.intersect as any;
-    export const subtract: (a: ElementSet, b: ElementSet) => ElementSet = Impl.subtract as any;
-
-    export type Builder = Builders.Builder
-    export const LinearBuilder = Builders.LinearBuilder
-    export const UnsortedBuilder = Builders.UnsortedBuilder
-
-    export interface Generator { add(unit: number, set: ElementGroup): void, getSet(): ElementSet }
-    export const Generator: () => Generator = Impl.Generator as any
-
-    export interface TemplateGenerator { add(unit: number, set: OrderedSet): void, getSet(): ElementSet }
-    export const TemplateGenerator: (template: ElementSet) => TemplateGenerator = Impl.TemplateGenerator as any
-
-    export const getLookup3d: (s: Structure) => ElementSetLookup3D = Impl.getLookup3d;
-
-    // TODO: distance, areWithIn?
-    // TODO: check connected
-    // TODO: add "parent" property? how to avoid using too much memory? Transitive parents? Parent unlinking?
-}
-
-interface ElementSet { '@type': 'element-set' }
-
-export default ElementSet

+ 170 - 58
src/mol-model/structure/structure/structure.ts

@@ -4,109 +4,221 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { OrderedSet, Iterator } from 'mol-data/int'
+import { IntMap, SortedArray, Iterator } from 'mol-data/int'
 import { UniqueArray } from 'mol-data/generic'
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
-import { Model, Format } from '../model'
-import Unit from './unit'
-import ElementSet from './element/set'
-import ElementGroup from './element/group'
+import { Model } from '../model'
+import { sort, arraySwap, hash1 } from 'mol-data/util';
 import Element from './element'
-import CoarseGrained from '../model/properties/coarse-grained';
+import Unit from './unit'
+import { StructureLookup3D } from './util/lookup3d';
+import { CoarseElements } from '../model/properties/coarse';
+import { StructureSubsetBuilder } from './util/subset-builder';
+
+class Structure {
+    readonly unitMap: IntMap<Unit>;
+    readonly units: ReadonlyArray<Unit>;
+    readonly elementCount: number;
+
+    private _hashCode = 0;
+
+    subsetBuilder(isSorted: boolean) {
+        return new StructureSubsetBuilder(this, isSorted);
+    }
+
+    get hashCode() {
+        if (this._hashCode !== 0) return this._hashCode;
+        return this.computeHash();
+    }
+
+    private computeHash() {
+        let hash = 23;
+        for (let i = 0, _i = this.units.length; i < _i; i++) {
+            const u = this.units[i];
+            hash = (31 * hash + u.id) | 0;
+            hash = (31 * hash + SortedArray.hashCode(u.elements)) | 0;
+        }
+        hash = (31 * hash + this.elementCount) | 0;
+        hash = hash1(hash);
+        this._hashCode = hash;
+        return hash;
+    }
+
+    elementLocations(): Iterator<Element.Location> {
+        return new Structure.ElementLocationIterator(this);
+    }
 
-// A structure is a pair of "units" and an element set.
-// Each unit contains the data and transformation of its corresponding elements.
-interface Structure {
-    readonly units: ReadonlyArray<Unit>,
-    readonly elements: ElementSet
+    get boundary() {
+        return this.lookup3d.boundary;
+    }
+
+    private _lookup3d?: StructureLookup3D = void 0;
+    get lookup3d() {
+        if (this._lookup3d) return this._lookup3d;
+        this._lookup3d = new StructureLookup3D(this);
+        return this._lookup3d;
+    }
+
+    constructor(units: ArrayLike<Unit>) {
+        const map = IntMap.Mutable<Unit>();
+        let elementCount = 0;
+        let isSorted = true;
+        let lastId = units.length > 0 ? units[0].id : 0;
+        for (let i = 0, _i = units.length; i < _i; i++) {
+            const u = units[i];
+            map.set(u.id, u);
+            elementCount += u.elements.length;
+            if (u.id < lastId) isSorted = false;
+            lastId = u.id;
+        }
+        if (!isSorted) sort(units, 0, units.length, cmpUnits, arraySwap)
+        this.unitMap = map;
+        this.units = units as ReadonlyArray<Unit>;
+        this.elementCount = elementCount;
+    }
 }
 
+function cmpUnits(units: ArrayLike<Unit>, i: number, j: number) { return units[i].id - units[j].id; }
+
 namespace Structure {
-    export function create(units: ReadonlyArray<Unit>, elements: ElementSet): Structure { return { units, elements }; }
-    export function Empty(units: ReadonlyArray<Unit>): Structure { return create(units, ElementSet.Empty); };
+    export const Empty = new Structure([]);
 
-    export function ofData(format: Format) {
-        const models = Model.create(format);
-        return models.map(ofModel);
-    }
+    export function create(units: ReadonlyArray<Unit>): Structure { return new Structure(units); }
 
     export function ofModel(model: Model): Structure {
-        const chains = model.hierarchy.chainSegments;
-        const builder = Builder();
+        const chains = model.atomicHierarchy.chainSegments;
+        const builder = new StructureBuilder();
 
         for (let c = 0; c < chains.count; c++) {
-            const group = ElementGroup.createNew(OrderedSet.ofBounds(chains.segments[c], chains.segments[c + 1]));
-            const unit = Unit.createAtomic(model, SymmetryOperator.Default, group);
-            builder.add(unit, unit.fullGroup);
+            const elements = SortedArray.ofBounds(chains.segments[c], chains.segments[c + 1]);
+            builder.addUnit(Unit.Kind.Atomic, model, SymmetryOperator.Default, elements);
         }
 
-        const cs = model.coarseGrained;
+        const cs = model.coarseHierarchy;
         if (cs.isDefined) {
             if (cs.spheres.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.spheres.count));
-                const unit = Unit.createCoarse(model, SymmetryOperator.Default, group, CoarseGrained.ElementType.Sphere);
-                builder.add(unit, unit.fullGroup);
+                addCoarseUnits(builder, model, model.coarseHierarchy.spheres, Unit.Kind.Spheres);
             }
             if (cs.gaussians.count > 0) {
-                const group = ElementGroup.createNew(OrderedSet.ofBounds(0, cs.gaussians.count));
-                const unit = Unit.createCoarse(model, SymmetryOperator.Default, group, CoarseGrained.ElementType.Gaussian);
-                builder.add(unit, unit.fullGroup);
+                addCoarseUnits(builder, model, model.coarseHierarchy.gaussians, Unit.Kind.Gaussians);
             }
         }
 
         return builder.getStructure();
     }
 
-    export interface Builder {
-        add(unit: Unit, elements: ElementGroup): void,
-        addUnit(unit: Unit): void,
-        setElements(unitId: number, elements: ElementGroup): void,
-        getStructure(): Structure,
-        readonly elementCount: number
+    function addCoarseUnits(builder: StructureBuilder, model: Model, elements: CoarseElements, kind: Unit.Kind) {
+        const { chainSegments } = elements;
+        for (let cI = 0; cI < chainSegments.count; cI++) {
+            const elements = SortedArray.ofBounds(chainSegments.segments[cI], chainSegments.segments[cI + 1]);
+            builder.addUnit(kind, model, SymmetryOperator.Default, elements);
+        }
     }
 
-    class BuilderImpl implements Builder {
-        private _unitId = 0;
+    export class StructureBuilder {
         private units: Unit[] = [];
-        private elements = ElementSet.Generator();
-        elementCount = 0;
 
-        add(unit: Unit, elements: ElementGroup) { const id = this.addUnit(unit); this.setElements(id, elements); }
-        addUnit(unit: Unit) { const id = this._unitId++; this.units[id] = unit; return id; }
-        setElements(unitId: number, elements: ElementGroup) { this.elements.add(unitId, elements); this.elementCount += ElementGroup.size(elements); }
-        getStructure(): Structure { return this.elementCount > 0 ? Structure.create(this.units, this.elements.getSet()) : Empty(this.units); }
-    }
+        addUnit(kind: Unit.Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+            const unit = Unit.create(this.units.length, kind, model, operator, elements);
+            this.units.push(unit);
+            return unit;
+        }
 
-    export function Builder(): Builder { return new BuilderImpl(); }
+        addWithOperator(unit: Unit, operator: SymmetryOperator): Unit {
+            const newUnit = unit.applyOperator(this.units.length, operator);
+            this.units.push(newUnit);
+            return newUnit;
+        }
 
-    /** Transient = location gets overwritten when move() is called. */
-    export function elementLocationsTransient(s: Structure): Iterator<Element.Location> {
-        const l = Element.Location();
-        const update = Element.updateLocation;
-        return Iterator.map(ElementSet.elements(s.elements), a => update(s, l, a));
+        getStructure(): Structure {
+            return create(this.units);
+        }
+
+        get isEmpty() {
+            return this.units.length === 0;
+        }
     }
 
+    export function Builder() { return new StructureBuilder(); }
+
     export function getModels(s: Structure) {
-        const { units, elements } = s;
+        const { units } = s;
         const arr = UniqueArray.create<Model['id'], Model>();
-        const ids = ElementSet.unitIndices(elements);
-        for (let i = 0; i < ids.length; i++) {
-            const u = units[ids[i]];
+        for (const u of units) {
             UniqueArray.add(arr, u.model.id, u.model);
         }
         return arr.array;
     }
 
-    export function getLookup3d(s: Structure) {
-        return ElementSet.getLookup3d(s);
+    export function getLookup3d(s: Structure): StructureLookup3D {
+        return 0 as any;
     }
 
     export function getBoundary(s: Structure) {
         return getLookup3d(s).boundary;
     }
 
-    // TODO: "lift" atom set operators?
-    // TODO: "diff"
+    export function hashCode(s: Structure) {
+        return s.hashCode;
+    }
+
+    export function areEqual(a: Structure, b: Structure) {
+        if (a.elementCount !== b.elementCount) return false;
+        const len = a.units.length;
+        if (len !== b.units.length) return false;
+
+        for (let i = 0; i < len; i++) {
+            if (a.units[i].id !== b.units[i].id) return false;
+        }
+
+        for (let i = 0; i < len; i++) {
+            if (!SortedArray.areEqual(a.units[i].elements, b.units[i].elements)) return false;
+        }
+
+        return true;
+    }
+
+    export class ElementLocationIterator implements Iterator<Element.Location> {
+        private current = Element.Location();
+        private unitIndex = 0;
+        private elements: SortedArray;
+        private maxIdx = 0;
+        private idx = -1;
+
+        hasNext: boolean;
+        move(): Element.Location {
+            this.advance();
+            this.current.element = this.elements[this.idx];
+            return this.current;
+        }
+
+        private advance() {
+            if (this.idx < this.maxIdx) {
+                this.idx++;
+                return;
+            }
+
+            this.idx = 0;
+            this.unitIndex++;
+            if (this.unitIndex >= this.structure.units.length) {
+                this.hasNext = false;
+                return;
+            }
+
+            this.current.unit = this.structure.units[this.unitIndex];
+            this.elements = this.current.unit.elements;
+            this.maxIdx = this.elements.length - 1;
+        }
+
+        constructor(private structure: Structure) {
+            this.hasNext = structure.elementCount > 0;
+            if (this.hasNext) {
+                this.elements = structure.units[0].elements;
+                this.maxIdx = this.elements.length - 1;
+                this.current.unit = structure.units[0];
+            }
+        }
+    }
 }
 
 export default Structure

+ 84 - 25
src/mol-model/structure/structure/symmetry.ts

@@ -5,43 +5,102 @@
  */
 
 import Structure from './structure'
-import ElementSet from './element/set'
-import Unit from './unit'
 import { Selection } from '../query'
 import { ModelSymmetry } from '../model'
 import { Task } from 'mol-task';
+import { SortedArray } from 'mol-data/int';
+import Unit from './unit';
+import { EquivalenceClasses, hash2 } from 'mol-data/util';
+import { Vec3 } from 'mol-math/linear-algebra';
+import { SymmetryOperator, Spacegroup, SpacegroupCell } from 'mol-math/geometry';
 
-namespace Symmetry {
-    export const buildAssembly = buildAssemblyImpl;
-}
+namespace StructureSymmetry {
+    export function buildAssembly(structure: Structure, asmName: string) {
+        return Task.create('Build Assembly', async ctx => {
+            const models = Structure.getModels(structure);
+            if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
+
+            const assembly = ModelSymmetry.findAssembly(models[0], asmName);
+            if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
 
-export default Symmetry;
+            const assembler = Structure.Builder();
 
-function buildAssemblyImpl(structure: Structure, name: string) {
-    return Task.create('Build Symmetry', async ctx => {
-        const models = Structure.getModels(structure);
-        if (models.length !== 1) throw new Error('Can only build assemblies from structures based on 1 model.');
+            for (const g of assembly.operatorGroups) {
+                const selection = await g.selector(structure).runAsChild(ctx);
+                if (Selection.structureCount(selection) === 0) {
+                    continue;
+                }
+                const { units } = Selection.unionStructure(selection);
 
-        const assembly = ModelSymmetry.findAssembly(models[0], name);
-        if (!assembly) throw new Error(`Assembly '${name}' is not defined.`);
+                for (const oper of g.operators) {
+                    for (const unit of units) {
+                        assembler.addWithOperator(unit, oper);
+                    }
+                }
+            }
 
-        const assembler = Structure.Builder();
+            return assembler.getStructure();
+        });
+    }
 
-        for (const g of assembly.operatorGroups) {
-            const selection = await ctx.runChild(g.selector(structure));
-            if (Selection.structureCount(selection) === 0) continue;
-            const { units, elements } = Selection.unionStructure(selection);
+    // TODO: build symmetry mates within radius
+
+    export function buildSymmetryRange(structure: Structure, ijkMin: Vec3, ijkMax: Vec3) {
+        return Task.create('Build Assembly', async ctx => {
+            const models = Structure.getModels(structure);
+            if (models.length !== 1) throw new Error('Can only build symmetries from structures based on 1 model.');
+
+            const { spacegroup } = models[0].symmetry;
+            if (SpacegroupCell.isZero(spacegroup.cell)) return structure;
+
+            const operators: SymmetryOperator[] = [];
+            for (let op = 0; op < spacegroup.operators.length; op++) {
+                for (let i = ijkMin[0]; i < ijkMax[0]; i++) {
+                    for (let j = ijkMin[1]; j < ijkMax[1]; j++) {
+                        for (let k = ijkMin[2]; k < ijkMax[2]; k++) {
+                            operators[operators.length] = Spacegroup.getSymmetryOperator(spacegroup, op, i, j, k);
+                        }
+                    }
+                }
+            }
 
-            const unitIds = ElementSet.unitIndices(elements);
+            const assembler = Structure.Builder();
 
-            for (const oper of g.operators) {
-                for (let uI = 0, _uI = unitIds.length; uI < _uI; uI++) {
-                    const unit = units[unitIds[uI]];
-                    assembler.add(Unit.withOperator(unit, oper), ElementSet.groupAt(elements, uI));
+            const { units } = structure;
+            for (const oper of operators) {
+                for (const unit of units) {
+                    assembler.addWithOperator(unit, oper);
                 }
             }
+
+            return assembler.getStructure();
+        });
+    }
+
+    function hashUnit(u: Unit) {
+        return hash2(u.invariantId, SortedArray.hashCode(u.elements));
+    }
+
+    function areUnitsEquivalent(a: Unit, b: Unit) {
+        return a.invariantId === b.invariantId && a.model.id === b.model.id && SortedArray.areEqual(a.elements, b.elements);
+    }
+
+    export function UnitEquivalenceBuilder() {
+        return EquivalenceClasses<number, Unit>(hashUnit, areUnitsEquivalent);
+    }
+
+    export function getTransformGroups(s: Structure): ReadonlyArray<Unit.SymmetryGroup> {
+        const groups = UnitEquivalenceBuilder();
+        for (const u of s.units) groups.add(u.id, u);
+
+        const ret: Unit.SymmetryGroup[] = [];
+        for (const eqUnits of groups.groups) {
+            const first = s.unitMap.get(eqUnits[0]);
+            ret.push({ elements: first.elements, units: eqUnits.map(id => s.unitMap.get(id)) });
         }
 
-        return assembler.getStructure();
-    });
-}
+        return ret;
+    }
+}
+
+export default StructureSymmetry;

+ 131 - 82
src/mol-model/structure/structure/unit.ts

@@ -5,34 +5,53 @@
  */
 
 import { SymmetryOperator } from 'mol-math/geometry/symmetry-operator'
-import ElementGroup from './element/group'
 import { Model } from '../model'
-import { GridLookup3D } from 'mol-math/geometry'
-import { computeUnitBonds } from './element/properties/bonds/group-compute';
-import CoarseGrained from '../model/properties/coarse-grained';
+import { GridLookup3D, Lookup3D } from 'mol-math/geometry'
+import { SortedArray } from 'mol-data/int';
+import { idFactory } from 'mol-util/id-factory';
+import { IntraUnitBonds, computeIntraUnitBonds } from './unit/bonds'
+import { CoarseElements, CoarseSphereConformation, CoarseGaussianConformation } from '../model/properties/coarse';
+import { ValueRef } from 'mol-util';
 
 // A building block of a structure that corresponds to an atomic or a coarse grained representation
 // 'conveniently grouped together'.
-type Unit = Unit.Atomic | Unit.Coarse
+type Unit = Unit.Atomic | Unit.Spheres | Unit.Gaussians
 
 namespace Unit {
-    export const enum Kind { Atomic, Coarse }
+    export const enum Kind { Atomic, Spheres, Gaussians }
 
     export function isAtomic(u: Unit): u is Atomic { return u.kind === Kind.Atomic; }
-    export function isCoarse(u: Unit): u is Coarse { return u.kind === Kind.Coarse; }
+    export function isCoarse(u: Unit): u is Spheres | Gaussians { return u.kind === Kind.Spheres || u.kind === Kind.Gaussians; }
+    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, kind: Kind, model: Model, operator: SymmetryOperator, elements: SortedArray): Unit {
+        switch (kind) {
+            case Kind.Atomic: return new Atomic(id, unitIdFactory(), model, elements, SymmetryOperator.createMapping(operator, model.atomicConformation), AtomicProperties());
+            case Kind.Spheres: return createCoarse(id, unitIdFactory(), model, Kind.Spheres, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.spheres));
+            case Kind.Gaussians: return createCoarse(id, unitIdFactory(), model, Kind.Gaussians, elements, SymmetryOperator.createMapping(operator, model.coarseConformation.gaussians));
+        }
+    }
 
-    export interface Base extends SymmetryOperator.ArrayMapping {
-        // Provides access to the underlying data.
+    // A group of units that differ only by symmetry operators.
+    export type SymmetryGroup = { readonly elements: SortedArray, readonly units: ReadonlyArray<Unit> }
+
+    export interface Base {
+        readonly id: number,
+        // invariant ID stays the same even if the Operator/conformation changes.
+        readonly invariantId: number,
+        readonly elements: SortedArray,
         readonly model: Model,
+        readonly conformation: SymmetryOperator.ArrayMapping,
+
+        getChild(elements: SortedArray): Unit,
+        applyOperator(id: number, operator: SymmetryOperator, dontCompose?: boolean /* = false */): Unit,
 
-        // The "full" atom group corresponding to this unit.
-        // Every selection is a subset of this atoms group.
-        // Things like inter-unit bonds or spatial lookups
-        // can be be implemented efficiently as "views" of the
-        // full group.
-        readonly fullGroup: ElementGroup
+        readonly lookup3d: Lookup3D
     }
 
+    const unitIdFactory = idFactory();
+
     // A bulding block of a structure that corresponds
     // to a "natural group of atoms" (most often a "chain")
     // together with a tranformation (rotation and translation)
@@ -40,91 +59,121 @@ namespace Unit {
     //
     // An atom set can be referenced by multiple diffrent units which
     // makes construction of assemblies and spacegroups very efficient.
-    export interface Atomic extends Base {
-        readonly kind: Unit.Kind.Atomic,
+    export class Atomic implements Base {
+        readonly kind = Kind.Atomic;
+
+        readonly id: number;
+        readonly invariantId: number;
+        readonly elements: SortedArray;
+        readonly model: Model;
+        readonly conformation: SymmetryOperator.ArrayMapping;
 
         // Reference some commonly accessed things for faster access.
-        readonly residueIndex: ArrayLike<number>,
-        readonly chainIndex: ArrayLike<number>,
-        readonly conformation: Model['atomSiteConformation'],
-        readonly hierarchy: Model['hierarchy']
-    }
+        readonly residueIndex: ArrayLike<number>;
+        readonly chainIndex: ArrayLike<number>;
 
-    // Coarse grained representations.
-    export interface Coarse extends Base  {
-        readonly kind: Unit.Kind.Coarse,
-        readonly elementType: CoarseGrained.ElementType,
+        private props: AtomicProperties;
 
-        readonly siteBases: CoarseGrained.SiteBases,
-        readonly spheres: CoarseGrained.Spheres,
-        readonly gaussians: CoarseGrained.Gaussians
+        getChild(elements: SortedArray): Unit {
+            if (elements.length === this.elements.length) return this;
+            return new Atomic(this.id, this.invariantId, 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.model, this.elements, SymmetryOperator.createMapping(op, this.model.atomicConformation), this.props);
+        }
+
+        get lookup3d() {
+            if (this.props.lookup3d.ref) return this.props.lookup3d.ref;
+            const { x, y, z } = this.model.atomicConformation;
+            this.props.lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
+            return this.props.lookup3d.ref;
+        }
+
+        get bonds() {
+            if (this.props.bonds.ref) return this.props.bonds.ref;
+            this.props.bonds.ref = computeIntraUnitBonds(this);
+            return this.props.bonds.ref;
+        }
+
+        constructor(id: number, invariantId: number, model: Model, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping, props: AtomicProperties) {
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+
+            this.residueIndex = model.atomicHierarchy.residueSegments.segmentMap;
+            this.chainIndex = model.atomicHierarchy.chainSegments.segmentMap;
+            this.props = props;
+        }
     }
 
-    export function createAtomic(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup): Unit.Atomic {
-        const h = model.hierarchy;
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, model.atomSiteConformation);
-
-        return {
-            model,
-            kind: Kind.Atomic,
-            operator,
-            fullGroup,
-            residueIndex: h.residueSegments.segmentMap,
-            chainIndex: h.chainSegments.segmentMap,
-            hierarchy: model.hierarchy,
-            conformation: model.atomSiteConformation,
-            invariantPosition,
-            position,
-            x, y, z
-        };
+    interface AtomicProperties {
+        lookup3d: ValueRef<Lookup3D | undefined>,
+        bonds: ValueRef<IntraUnitBonds | undefined>,
     }
 
-    export function createCoarse(model: Model, operator: SymmetryOperator, fullGroup: ElementGroup, elementType: CoarseGrained.ElementType): Unit.Coarse {
-        const siteBases = elementType === CoarseGrained.ElementType.Sphere ? model.coarseGrained.spheres : model.coarseGrained.gaussians;
-        const { invariantPosition, position, x, y, z } = SymmetryOperator.createMapping(operator, siteBases);
-
-        return {
-            model,
-            kind: Kind.Coarse,
-            elementType,
-            operator,
-            fullGroup,
-            siteBases,
-            spheres: model.coarseGrained.spheres,
-            gaussians: model.coarseGrained.gaussians,
-            invariantPosition,
-            position,
-            x, y, z
-        };
+    function AtomicProperties() {
+        return { lookup3d: ValueRef.create(void 0), bonds: ValueRef.create(void 0) };
     }
 
-    export function withOperator(unit: Unit, operator: SymmetryOperator): Unit {
-        switch (unit.kind) {
-            case Kind.Atomic: return createAtomic(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup);
-            case Kind.Coarse: return createCoarse(unit.model, SymmetryOperator.compose(unit.operator, operator), unit.fullGroup, unit.elementType);
+    class Coarse<K extends Kind.Gaussians | Kind.Spheres, C extends CoarseSphereConformation | CoarseGaussianConformation> implements Base {
+        readonly kind: K;
+
+        readonly id: number;
+        readonly invariantId: number;
+        readonly elements: SortedArray;
+        readonly model: Model;
+        readonly conformation: SymmetryOperator.ArrayMapping;
+
+        readonly coarseElements: CoarseElements;
+        readonly coarseConformation: C;
+
+        getChild(elements: SortedArray): 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.model, this.kind, elements, this.conformation);
         }
-    }
 
-    export function getLookup3d(unit: Unit, group: ElementGroup) {
-        if (group.__lookup3d__)  return group.__lookup3d__;
-        if (Unit.isAtomic(unit)) {
-            const { x, y, z } = unit.model.atomSiteConformation;
-            group.__lookup3d__ = GridLookup3D({ x, y, z, indices: group.elements });
-            return group.__lookup3d__;
+        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.model, this.kind, this.elements, SymmetryOperator.createMapping(op, this.getCoarseElements()));
+            (ret as Coarse<K, C>)._lookup3d = this._lookup3d;
+            return ret;
         }
 
-        throw 'not implemented';
-    }
+        private _lookup3d: ValueRef<Lookup3D | undefined> = ValueRef.create(void 0);
+        get lookup3d() {
+            if (this._lookup3d.ref) return this._lookup3d.ref;
+            // TODO: support sphere radius?
+            const { x, y, z } = this.getCoarseElements();
+            this._lookup3d.ref = GridLookup3D({ x, y, z, indices: this.elements });
+            return this._lookup3d.ref;
+        }
+
+        private getCoarseElements() {
+            return this.kind === Kind.Spheres ? this.model.coarseConformation.spheres : this.model.coarseConformation.gaussians;
+        }
 
-    export function getGroupBonds(unit: Unit, group: ElementGroup) {
-        if (group.__bonds__) return group.__bonds__;
-        if (Unit.isAtomic(unit)) {
-            group.__bonds__ = computeUnitBonds(unit, group);
-            return group.__bonds__;
+        constructor(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping) {
+            this.kind = kind;
+            this.id = id;
+            this.invariantId = invariantId;
+            this.model = model;
+            this.elements = elements;
+            this.conformation = conformation;
+            this.coarseElements = kind === Kind.Spheres ? model.coarseHierarchy.spheres : model.coarseHierarchy.gaussians;
+            this.coarseConformation = (kind === Kind.Spheres ? model.coarseConformation.spheres : model.coarseConformation.gaussians) as C;
         }
+    }
 
-        throw 'not implemented';
+    function createCoarse<K extends Kind.Gaussians | Kind.Spheres>(id: number, invariantId: number, model: Model, kind: K, elements: SortedArray, conformation: SymmetryOperator.ArrayMapping): Unit {
+        return new Coarse(id, invariantId, model, kind, elements, conformation) as any as Unit /** lets call this an ugly temporary hack */;
     }
+
+    export class Spheres extends Coarse<Kind.Spheres, CoarseSphereConformation> { }
+    export class Gaussians extends Coarse<Kind.Gaussians, CoarseGaussianConformation> { }
 }
 
 export default Unit;

+ 8 - 0
src/mol-model/structure/structure/unit/bonds.ts

@@ -0,0 +1,8 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+export * from './bonds/intra-data'
+export * from './bonds/intra-compute'

+ 16 - 17
src/mol-model/structure/structure/element/properties/bonds/group-compute.ts → src/mol-model/structure/structure/unit/bonds/intra-compute.ts

@@ -4,11 +4,10 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { BondType, ElementSymbol } from '../../../../model/types'
-import { GroupBonds } from './group-data'
-import { StructConn, ComponentBondInfo } from '../../../../model/formats/mmcif/bonds'
-import Unit from '../../../unit';
-import ElementGroup from '../../group';
+import { BondType, ElementSymbol } from '../../../model/types'
+import { IntraUnitBonds } from './intra-data'
+import { StructConn, ComponentBondInfo } from '../../../model/formats/mmcif/bonds'
+import Unit from '../../unit'
 
 export interface BondComputationParameters {
     maxHbondLength: number,
@@ -107,15 +106,15 @@ function computePerAtomBonds(atomA: number[], atomB: number[], _order: number[],
     };
 }
 
-function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondComputationParameters): GroupBonds {
+function _computeBonds(unit: Unit.Atomic, params: BondComputationParameters): IntraUnitBonds {
     const MAX_RADIUS = 3;
 
-    const { x, y, z } = unit.model.atomSiteConformation;
-    const atomCount = ElementGroup.size(atoms);
-    const { residueIndex } = unit;
-    const { type_symbol, label_atom_id, label_alt_id } = unit.model.hierarchy.atoms;
-    const { label_comp_id } = unit.model.hierarchy.residues;
-    const query3d = Unit.getLookup3d(unit, atoms);
+    const { x, y, z } = unit.model.atomicConformation;
+    const atomCount = unit.elements.length;
+    const { elements: atoms, residueIndex } = unit;
+    const { type_symbol, label_atom_id, label_alt_id } = unit.model.atomicHierarchy.atoms;
+    const { label_comp_id } = unit.model.atomicHierarchy.residues;
+    const query3d = unit.lookup3d;
 
     const structConn = unit.model.sourceData.kind === 'mmCIF' ? StructConn.create(unit.model) : void 0
     const component = unit.model.sourceData.kind === 'mmCIF' ? ComponentBondInfo.create(unit.model) : void 0
@@ -129,7 +128,7 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
     let componentMap: Map<string, Map<string, { flags: number, order: number }>> | undefined = void 0;
 
     for (let _aI = 0; _aI < atomCount; _aI++) {
-        const aI = ElementGroup.getAt(atoms, _aI);
+        const aI =  atoms[_aI];
         const raI = residueIndex[aI];
 
         if (!params.forceCompute && raI !== lastResidue) {
@@ -155,7 +154,7 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
 
         for (let ni = 0; ni < count; ni++) {
             const _bI = indices[ni];
-            const bI = ElementGroup.getAt(atoms, _bI);
+            const bI = atoms[_bI];
             if (bI <= aI) continue;
 
             const altB = label_alt_id.value(bI);
@@ -243,11 +242,11 @@ function _computeBonds(unit: Unit.Atomic, atoms: ElementGroup, params: BondCompu
     };
 }
 
-function computeUnitBonds(unit: Unit.Atomic, atoms: ElementGroup, params?: Partial<BondComputationParameters>) {
-    return _computeBonds(unit, atoms, {
+function computeIntraUnitBonds(unit: Unit.Atomic, params?: Partial<BondComputationParameters>) {
+    return _computeBonds(unit, {
         maxHbondLength: (params && params.maxHbondLength) || 1.15,
         forceCompute: !!(params && params.forceCompute),
     });
 }
 
-export { computeUnitBonds }
+export { computeIntraUnitBonds }

+ 5 - 5
src/mol-model/structure/structure/element/properties/bonds/group-data.ts → src/mol-model/structure/structure/unit/bonds/intra-data.ts

@@ -5,9 +5,9 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { BondType } from '../../../../model/types'
+import { BondType } from '../../../model/types'
 
-interface GroupBonds {
+interface IntraUnitBonds {
     /**
      * Where bonds for atom A start and end.
      * Start offset at idx, end at idx + 1
@@ -21,8 +21,8 @@ interface GroupBonds {
     count: number
 }
 
-namespace GroupBonds {
-    export function createEmpty(): GroupBonds {
+namespace IntraUnitBonds {
+    export function createEmpty(): IntraUnitBonds {
         return { offset: [], neighbor: [], order: [], flags: [], count: 0 }
     }
     export function isCovalent(flags: number) {
@@ -46,4 +46,4 @@ namespace GroupBonds {
     }
 }
 
-export { GroupBonds }
+export { IntraUnitBonds }

+ 12 - 15
src/mol-model/structure/structure/util/boundary.ts

@@ -5,8 +5,6 @@
  */
 
 import Structure from '../structure'
-import ElementSet from '../element/set'
-import { ElementGroup } from '../../structure'
 import { Box3D, Sphere3D } from 'mol-math/geometry';
 import { Vec3 } from 'mol-math/linear-algebra';
 
@@ -14,19 +12,19 @@ function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D
     const min = [Number.MAX_VALUE, Number.MAX_VALUE, Number.MAX_VALUE];
     const max = [-Number.MAX_VALUE, -Number.MAX_VALUE, -Number.MAX_VALUE];
 
-    const { units, elements } = s;
+    const { units } = s;
 
     let cx = 0, cy = 0, cz = 0;
     let radiusSq = 0;
     let size = 0;
 
-    for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
-        const group = ElementSet.groupAt(elements, i);
-        const { x, y, z } = units[ElementSet.groupUnitIndex(elements, i)];
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        const { x, y, z } = units[i].conformation;
 
-        size += ElementGroup.size(group);
-        for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
-            const e = ElementGroup.getAt(group, j);
+        const elements = units[i].elements;
+        size += elements.length;
+        for (let j = 0, _j = elements.length; j < _j; j++) {
+            const e = elements[j];
             const xx = x(e), yy = y(e), zz = z(e);
 
             min[0] = Math.min(xx, min[0]);
@@ -48,13 +46,12 @@ function computeStructureBoundary(s: Structure): { box: Box3D, sphere: Sphere3D
         cz /= size;
     }
 
-    for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) {
-        const group = ElementSet.groupAt(elements, i);
-        const { x, y, z } = units[ElementSet.groupUnitIndex(elements, i)];
+    for (let i = 0, _i = units.length; i < _i; i++) {
+        const { x, y, z } = units[i].conformation;
 
-        size += ElementGroup.size(group);
-        for (let j = 0, _j = ElementGroup.size(group); j < _j; j++) {
-            const e = ElementGroup.getAt(group, j);
+        const elements = units[i].elements;
+        for (let j = 0, _j = elements.length; j < _j; j++) {
+            const e = elements[j];
             const dx = x(e) - cx, dy = y(e) - cy, dz = z(e) - cz;
             const d = dx * dx + dy * dy + dz * dz;
             if (d > radiusSq) radiusSq = d;

+ 93 - 81
src/mol-model/structure/structure/util/lookup3d.ts

@@ -7,98 +7,110 @@
 import Structure from '../structure'
 import Element from '../element'
 import { Lookup3D, GridLookup3D, Result, Box3D, Sphere3D } from 'mol-math/geometry';
-import { ElementSet, Unit } from '../../structure';
 import { Vec3 } from 'mol-math/linear-algebra';
-import { OrderedSet } from 'mol-data/int';
 import { computeStructureBoundary } from './boundary';
-
-interface ElementSetLookup3D extends Lookup3D<Element> {}
-
-namespace ElementSetLookup3D {
-    class Impl implements ElementSetLookup3D {
-        private unitLookup: Lookup3D;
-        private result = Result.create<Element>();
-        private pivot = Vec3.zero();
-
-        find(x: number, y: number, z: number, radius: number): Result<Element> {
-            Result.reset(this.result);
-            const { units, elements } = this.structure;
-            const closeUnits = this.unitLookup.find(x, y, z, radius);
-            if (closeUnits.count === 0) return this.result;
-
-            for (let t = 0, _t = closeUnits.count; t < _t; t++) {
-                const i = closeUnits.indices[t];
-                const unitId = ElementSet.groupUnitIndex(elements, i);
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[unitId];
-                Vec3.set(this.pivot, x, y, z);
-                if (!unit.operator.isIdentity) {
-                    Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
-                }
-                const groupLookup = Unit.getLookup3d(unit, group);
-                const groupResult = groupLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
-                for (let j = 0, _j = groupResult.count; j < _j; j++) {
-                    Result.add(this.result, Element.create(unitId, groupResult.indices[j]), groupResult.squaredDistances[j]);
-                }
+import { OrderedSet } from 'mol-data/int';
+import { StructureUniqueSubsetBuilder } from './unique-subset-builder';
+
+export class StructureLookup3D implements Lookup3D<Element> {
+    private unitLookup: Lookup3D;
+    private result = Result.create<Element>();
+    private pivot = Vec3.zero();
+
+    find(x: number, y: number, z: number, radius: number): Result<Element> {
+        Result.reset(this.result);
+        const { units } = this.structure;
+        const closeUnits = this.unitLookup.find(x, y, z, radius);
+        if (closeUnits.count === 0) return this.result;
+
+        for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            const unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const unitLookup = unit.lookup3d;
+            const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
+            for (let j = 0, _j = groupResult.count; j < _j; j++) {
+                Result.add(this.result, Element.create(unit.id, groupResult.indices[j]), groupResult.squaredDistances[j]);
             }
-
-            return this.result;
         }
 
-        check(x: number, y: number, z: number, radius: number): boolean {
-            const { units, elements } = this.structure;
-            const closeUnits = this.unitLookup.find(x, y, z, radius);
-            if (closeUnits.count === 0) return false;
-
-            for (let t = 0, _t = closeUnits.count; t < _t; t++) {
-                const i = closeUnits.indices[t];
-                const unitId = ElementSet.groupUnitIndex(elements, i);
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[unitId];
-                Vec3.set(this.pivot, x, y, z);
-                if (!unit.operator.isIdentity) {
-                    Vec3.transformMat4(this.pivot, this.pivot, unit.operator.inverse);
-                }
-                const groupLookup = Unit.getLookup3d(unit, group);
-                if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
-            }
+        return this.result;
+    }
 
-            return false;
-        }
+    findIntoBuilder(x: number, y: number, z: number, radius: number, builder: StructureUniqueSubsetBuilder) {
+        const { units } = this.structure;
+        const closeUnits = this.unitLookup.find(x, y, z, radius);
+        if (closeUnits.count === 0) return;
 
-        boundary: { box: Box3D; sphere: Sphere3D; };
-
-        constructor(private structure: Structure) {
-            const { units, elements } = structure;
-            const unitCount = ElementSet.groupCount(elements);
-            const xs = new Float32Array(unitCount);
-            const ys = new Float32Array(unitCount);
-            const zs = new Float32Array(unitCount);
-            const radius = new Float32Array(unitCount);
-
-            const center = Vec3.zero();
-            for (let i = 0; i < unitCount; i++) {
-                const group = ElementSet.groupAt(elements, i);
-                const unit = units[ElementSet.groupUnitIndex(elements, i)];
-                const lookup = Unit.getLookup3d(unit, group);
-                const s = lookup.boundary.sphere;
-
-                Vec3.transformMat4(center, s.center, unit.operator.matrix);
-
-                xs[i] = center[0];
-                ys[i] = center[1];
-                zs[i] = center[2];
-                radius[i] = s.radius;
+        for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            const unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const unitLookup = unit.lookup3d;
+            const groupResult = unitLookup.find(this.pivot[0], this.pivot[1], this.pivot[2], radius);
+            if (groupResult.count === 0) continue;
+
+            const elements = unit.elements;
+            builder.beginUnit(unit.id);
+            for (let j = 0, _j = groupResult.count; j < _j; j++) {
+                builder.addElement(elements[groupResult.indices[j]]);
             }
+            builder.commitUnit();
+        }
+    }
+
+    check(x: number, y: number, z: number, radius: number): boolean {
+        const { units } = this.structure;
+        const closeUnits = this.unitLookup.find(x, y, z, radius);
+        if (closeUnits.count === 0) return false;
 
-            this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
-            this.boundary = computeStructureBoundary(structure);
+        for (let t = 0, _t = closeUnits.count; t < _t; t++) {
+            const unit = units[closeUnits.indices[t]];
+            Vec3.set(this.pivot, x, y, z);
+            if (!unit.conformation.operator.isIdentity) {
+                Vec3.transformMat4(this.pivot, this.pivot, unit.conformation.operator.inverse);
+            }
+            const groupLookup = unit.lookup3d;
+            if (groupLookup.check(this.pivot[0], this.pivot[1], this.pivot[2], radius)) return true;
         }
+
+        return false;
     }
 
-    export function create(s: Structure): ElementSetLookup3D {
-        return new Impl(s);
+    _boundary: { box: Box3D; sphere: Sphere3D; } | undefined = void 0;
+
+    get boundary() {
+        if (this.boundary) return this._boundary!;
+        this._boundary = computeStructureBoundary(this.structure);
+        return this._boundary!;
     }
-}
 
-export { ElementSetLookup3D }
+    constructor(private structure: Structure) {
+        const { units } = structure;
+        const unitCount = units.length;
+        const xs = new Float32Array(unitCount);
+        const ys = new Float32Array(unitCount);
+        const zs = new Float32Array(unitCount);
+        const radius = new Float32Array(unitCount);
+
+        const center = Vec3.zero();
+        for (let i = 0; i < unitCount; i++) {
+            const unit = units[i];
+            const lookup = unit.lookup3d;
+            const s = lookup.boundary.sphere;
+
+            Vec3.transformMat4(center, s.center, unit.conformation.operator.matrix);
+
+            xs[i] = center[0];
+            ys[i] = center[1];
+            zs[i] = center[2];
+            radius[i] = s.radius;
+        }
+
+        this.unitLookup = GridLookup3D({ x: xs, y: ys, z: zs, radius, indices: OrderedSet.ofBounds(0, unitCount) });
+    }
+}

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

@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { IntMap, SortedArray } from 'mol-data/int';
+import { sortArray } from 'mol-data/util';
+import Element from '../element';
+import StructureSymmetry from '../symmetry';
+import Unit from '../unit';
+import Structure from '../structure';
+
+export class StructureSubsetBuilder {
+    private ids: number[] = [];
+    private unitMap = IntMap.Mutable<number[]>();
+    private parentId = -1;
+    private currentUnit: number[] = [];
+    elementCount = 0;
+
+    addToUnit(parentId: number, e: number) {
+        const unit = this.unitMap.get(parentId);
+        if (!!unit) { unit[unit.length] = e; }
+        else {
+            this.unitMap.set(parentId, [e]);
+            this.ids[this.ids.length] = parentId;
+        }
+        this.elementCount++;
+    }
+
+    beginUnit(parentId: number) {
+        this.parentId = parentId;
+        this.currentUnit = this.currentUnit.length > 0 ? [] : this.currentUnit;
+    }
+
+    addElement(e: number) {
+        this.currentUnit[this.currentUnit.length] = e;
+        this.elementCount++;
+    }
+
+    commitUnit() {
+        if (this.currentUnit.length === 0) return;
+        this.ids[this.ids.length] = this.parentId;
+        this.unitMap.set(this.parentId, this.currentUnit);
+        this.parentId = -1;
+    }
+
+    setUnit(parentId: number, elements: ArrayLike<number>) {
+        this.ids[this.ids.length] = parentId;
+        this.unitMap.set(parentId, elements as number[]);
+        this.elementCount += elements.length;
+    }
+
+    private _getStructure(deduplicateElements: boolean): Structure {
+        if (this.isEmpty) return Structure.Empty;
+
+        const newUnits: Unit[] = [];
+        sortArray(this.ids);
+
+        const symmGroups = StructureSymmetry.UnitEquivalenceBuilder();
+
+        for (let i = 0, _i = this.ids.length; i < _i; i++) {
+            const id = this.ids[i];
+            const parent = this.parent.unitMap.get(id);
+
+            let unit: ArrayLike<number> = this.unitMap.get(id);
+            let sorted = false;
+
+            if (deduplicateElements) {
+                if (!this.isSorted) sortArray(unit);
+                unit = SortedArray.deduplicate(SortedArray.ofSortedArray(this.currentUnit));
+                sorted = true;
+            }
+
+            const l = unit.length;
+
+            // if the length is the same, just copy the old unit.
+            if (unit.length === parent.elements.length) {
+                newUnits[newUnits.length] = parent;
+                symmGroups.add(parent.id, parent);
+                continue;
+            }
+
+            if (!this.isSorted && !sorted && l > 1) sortArray(unit);
+
+            let child = parent.getChild(SortedArray.ofSortedArray(unit));
+            const pivot = symmGroups.add(child.id, child);
+            if (child !== pivot) child = pivot.applyOperator(child.id, child.conformation.operator, true);
+            newUnits[newUnits.length] = child;
+        }
+
+        return Structure.create(newUnits);
+    }
+
+    getStructure() {
+        return this._getStructure(false);
+    }
+
+    getStructureDeduplicate() {
+        return this._getStructure(true);
+    }
+
+    setSingletonLocation(location: Element.Location) {
+        const id = this.ids[0];
+        location.unit = this.parent.unitMap.get(id);
+        location.element = this.unitMap.get(id)[0];
+    }
+
+    get isEmpty() {
+        return this.elementCount === 0;
+    }
+
+    constructor(private parent: Structure, private isSorted: boolean) {
+
+    }
+}

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

@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author David Sehnal <david.sehnal@gmail.com>
+ */
+
+import { IntMap, SortedArray } from 'mol-data/int';
+import { sortArray } from 'mol-data/util';
+import StructureSymmetry from '../symmetry';
+import Unit from '../unit';
+import Structure from '../structure';
+import { UniqueArray } from 'mol-data/generic';
+
+type UArray = UniqueArray<number, number>
+
+export class StructureUniqueSubsetBuilder {
+    private ids: number[] = [];
+    private unitMap = IntMap.Mutable<UArray>();
+    private parentId = -1;
+    private currentUnit: UArray = UniqueArray.create();
+    elementCount = 0;
+
+    addToUnit(parentId: number, e: number) {
+        const unit = this.unitMap.get(parentId);
+        if (!!unit) {
+            if (UniqueArray.add(unit, e, e)) this.elementCount++;
+        }
+        else {
+            const arr: UArray = UniqueArray.create();
+            UniqueArray.add(arr, e, e);
+            this.unitMap.set(parentId, arr);
+            this.ids[this.ids.length] = parentId;
+            this.elementCount++;
+        }
+    }
+
+    beginUnit(parentId: number) {
+        this.parentId = parentId;
+        if (this.unitMap.has(parentId)) {
+            this.currentUnit = this.unitMap.get(parentId);
+        } else {
+            this.currentUnit = this.currentUnit.array.length > 0 ? UniqueArray.create() : this.currentUnit;
+        }
+    }
+
+    addElement(e: number) {
+        if (UniqueArray.add(this.currentUnit, e, e)) this.elementCount++;
+    }
+
+    commitUnit() {
+        if (this.currentUnit.array.length === 0 || this.unitMap.has(this.parentId)) return;
+        this.ids[this.ids.length] = this.parentId;
+        this.unitMap.set(this.parentId, this.currentUnit);
+        this.parentId = -1;
+    }
+
+    getStructure(): Structure {
+        if (this.isEmpty) return Structure.Empty;
+
+        const newUnits: Unit[] = [];
+        sortArray(this.ids);
+
+        const symmGroups = StructureSymmetry.UnitEquivalenceBuilder();
+
+        for (let i = 0, _i = this.ids.length; i < _i; i++) {
+            const id = this.ids[i];
+            const parent = this.parent.unitMap.get(id);
+
+            let unit: ArrayLike<number> = this.unitMap.get(id).array;
+
+            const l = unit.length;
+
+            // if the length is the same, just copy the old unit.
+            if (unit.length === parent.elements.length) {
+                newUnits[newUnits.length] = parent;
+                symmGroups.add(parent.id, parent);
+                continue;
+            }
+
+            if (l > 1) sortArray(unit);
+
+            let child = parent.getChild(SortedArray.ofSortedArray(unit));
+            const pivot = symmGroups.add(child.id, child);
+            if (child !== pivot) child = pivot.applyOperator(child.id, child.conformation.operator, true);
+            newUnits[newUnits.length] = child;
+        }
+
+        return Structure.create(newUnits);
+    }
+
+    get isEmpty() {
+        return this.elementCount === 0;
+    }
+
+    constructor(private parent: Structure) {
+
+    }
+}

+ 21 - 10
src/mol-task/execution/observable.ts

@@ -10,13 +10,26 @@ import { Progress } from './progress'
 import { now } from '../util/now'
 import { Scheduler } from '../util/scheduler'
 
-function ExecuteObservable<T>(task: Task<T>, observer: Progress.Observer, updateRateMs = 250) {
+interface ExposedTask<T> extends Task<T> {
+    f: (ctx: RuntimeContext) => Promise<T>,
+    onAbort?: () => void
+}
+
+export function ExecuteObservable<T>(task: Task<T>, observer: Progress.Observer, updateRateMs = 250) {
     const info = ProgressInfo(task, observer, updateRateMs);
     const ctx = new ObservableRuntimeContext(info, info.root);
-    return execute(task, ctx);
+    return execute(task as ExposedTask<T>, ctx);
+}
+
+export function ExecuteInContext<T>(ctx: RuntimeContext, task: Task<T>) {
+    return execute(task as ExposedTask<T>, ctx as ObservableRuntimeContext);
 }
 
-namespace ExecuteObservable {
+export function ExecuteObservableChild<T>(ctx: RuntimeContext, task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>) {
+    return (ctx as ObservableRuntimeContext).runChild(task, progress);
+}
+
+export namespace ExecuteObservable {
     export let PRINT_ERRORS_TO_STD_ERR = false;
 }
 
@@ -77,10 +90,10 @@ function snapshotProgress(info: ProgressInfo): Progress {
     return { root: cloneTree(info.root), canAbort: canAbort(info.root), requestAbort: info.tryAbort };
 }
 
-async function execute<T>(task: Task<T>, ctx: ObservableRuntimeContext) {
+async function execute<T>(task: ExposedTask<T>, ctx: ObservableRuntimeContext) {
     ctx.node.progress.startedTime = now();
     try {
-        const ret = await task.__f(ctx);
+        const ret = await task.f(ctx);
         if (ctx.info.abortToken.abortRequested) {
             abort(ctx.info, ctx.node);
         }
@@ -91,7 +104,7 @@ async function execute<T>(task: Task<T>, ctx: ObservableRuntimeContext) {
             if (ctx.node.children.length > 0) {
                 await new Promise(res => { ctx.onChildrenFinished = res; });
             }
-            if (task.__onAbort) task.__onAbort();
+            if (task.onAbort) task.onAbort();
         }
         if (ExecuteObservable.PRINT_ERRORS_TO_STD_ERR) console.error(e);
         throw e;
@@ -197,7 +210,7 @@ class ObservableRuntimeContext implements RuntimeContext {
         children.push(node);
         const ctx = new ObservableRuntimeContext(this.info, node);
         try {
-            return await execute(task, ctx);
+            return await execute(task as ExposedTask<T>, ctx);
         } catch (e) {
             if (Task.isAbort(e)) {
                 // need to catch the error here because otherwise
@@ -223,6 +236,4 @@ class ObservableRuntimeContext implements RuntimeContext {
         this.node = node;
         this.info = info;
     }
-}
-
-export { ExecuteObservable }
+}

+ 1 - 7
src/mol-task/execution/runtime-context.ts

@@ -4,8 +4,6 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task } from '../task'
-
 interface RuntimeContext {
     readonly shouldUpdate: boolean,
     readonly isSynchronous: boolean,
@@ -15,11 +13,7 @@ interface RuntimeContext {
     //
     // Alternatively, progress can be updated without notifying (and yielding) using update(progress, true).
     // This is useful for nested tasks.
-    update(progress?: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void,
-
-    // Run a child task that adds a new node to the progress tree.
-    // Allow to pass the progress so that the progress tree can be kept in a "good state" without having to separately call update.
-    runChild<T>(task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T>
+    update(progress?: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void
 }
 
 namespace RuntimeContext {

+ 1 - 10
src/mol-task/execution/synchronous.ts

@@ -4,21 +4,12 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { Task } from '../task'
 import { RuntimeContext } from './runtime-context'
 
 class SynchronousRuntimeContext implements RuntimeContext {
     shouldUpdate = false;
     isSynchronous = true;
-
     update(progress: string | Partial<RuntimeContext.ProgressUpdate>, dontNotify?: boolean): Promise<void> | void { }
-    runChild<T>(task: Task<T>, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T> { return ExecuteSynchronous(task); }
-}
-
-const SyncRuntimeInstance = new SynchronousRuntimeContext();
-
-function ExecuteSynchronous<T>(task: Task<T>) {
-    return task.__f(SyncRuntimeInstance);
 }
 
-export { ExecuteSynchronous }
+export const SyncRuntimeContext = new SynchronousRuntimeContext();

+ 1 - 12
src/mol-task/index.ts

@@ -6,21 +6,10 @@
 
 import { Task } from './task'
 import { RuntimeContext } from './execution/runtime-context'
-import { ExecuteSynchronous } from './execution/synchronous'
-import { ExecuteObservable } from './execution/observable'
 import { Progress } from './execution/progress'
 import { now } from './util/now'
 import { Scheduler } from './util/scheduler'
 import { MultistepTask } from './util/multistep'
 import { chunkedSubtask } from './util/chunked'
 
-// Run the task without the ability to observe its progress.
-function Run<T>(task: Task<T>): Promise<T>;
-// Run the task with the ability to observe its progress and request cancellation.
-function Run<T>(task: Task<T>, observer: Progress.Observer, updateRateMs?: number): Promise<T>;
-function Run<T>(task: Task<T>, observer?: Progress.Observer, updateRateMs?: number): Promise<T> {
-    if (observer) return ExecuteObservable(task, observer, updateRateMs || 250);
-    return ExecuteSynchronous(task);
-}
-
-export { Task, RuntimeContext, Progress, Run, now, Scheduler, MultistepTask, chunkedSubtask }
+export { Task, RuntimeContext, Progress, now, Scheduler, MultistepTask, chunkedSubtask }

+ 40 - 6
src/mol-task/task.ts

@@ -5,25 +5,59 @@
  */
 
 import { RuntimeContext } from './execution/runtime-context'
+import { Progress } from './execution/progress'
+import { ExecuteObservable, ExecuteObservableChild, ExecuteInContext } from './execution/observable';
+import { SyncRuntimeContext } from 'mol-task/execution/synchronous';
 
 // A "named function wrapper" with built in "computation tree progress tracking".
 // Use Run(t, ?observer, ?updateRate) to execute
 interface Task<T> {
+    // run the task without observation
+    run(): Promise<T>,
+    // run the task with the specified observer, default updateRate is 250ms
+    run(observer: Progress.Observer, updateRateMs?: number): Promise<T>,
+
+    // Run a child task that adds a new node to the progress tree.
+    // Allow to pass the progress so that the progress tree can be kept in a "good state" without having to separately call update.
+    runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T>
+
+    // Run the task on the specified context.
+    runInContext(ctx: RuntimeContext): Promise<T>
+
     readonly id: number,
-    readonly name: string,
-    // Do not call this directly, use Run.
-    readonly __f: (ctx: RuntimeContext) => Promise<T>,
-    // Do not call this directly, use Run.
-    readonly __onAbort: (() => void) | undefined
+    readonly name: string
 }
 
 namespace Task {
+    class Impl<T> implements Task<T> {
+        readonly id: number;
+
+        run(observer?: Progress.Observer, updateRateMs?: number): Promise<T> {
+            if (observer) return ExecuteObservable(this, observer, updateRateMs as number || 250);
+            return this.f(SyncRuntimeContext);
+        }
+
+        runAsChild(ctx: RuntimeContext, progress?: string | Partial<RuntimeContext.ProgressUpdate>): Promise<T> {
+            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
+            return ExecuteObservableChild(ctx, this, progress as string | Partial<RuntimeContext.ProgressUpdate>);
+        }
+
+        runInContext(ctx: RuntimeContext): Promise<T> {
+            if (ctx.isSynchronous) return this.f(SyncRuntimeContext);
+            return ExecuteInContext(ctx, this);
+        }
+
+        constructor(public name: string, public f: (ctx: RuntimeContext) => Promise<T>, public onAbort?: () => void) {
+            this.id = nextId();
+        }
+    }
+
     export interface Aborted { isAborted: true, reason: string }
     export function isAbort(e: any): e is Aborted { return !!e && !!e.isAborted; }
     export function Aborted(reason: string): Aborted { return { isAborted: true, reason }; }
 
     export function create<T>(name: string, f: (ctx: RuntimeContext) => Promise<T>, onAbort?: () => void): Task<T> {
-        return { id: nextId(), name, __f: f, __onAbort: onAbort };
+        return new Impl(name, f, onAbort);
     }
 
     export function constant<T>(name: string, value: T): Task<T> { return create(name, async ctx => value); }

+ 1 - 1
src/mol-util/bit-flags.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-interface BitFlags<Flags> { '@type': Flags }
+interface BitFlags<Flags> extends Number { '@type': Flags }
 
 namespace BitFlags {
     export function create<F>(flags: F): BitFlags<F> { return flags as any; }

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません