diff --git a/src/mol-math/geometry/spacegroup/construction.ts b/src/mol-math/geometry/spacegroup/construction.ts index 2a49c7843fdefb24128d51425d5f5615980fada8..9bc4d9d6cee4cf8879c43ae978b66cbfa45a9028 100644 --- a/src/mol-math/geometry/spacegroup/construction.ts +++ b/src/mol-math/geometry/spacegroup/construction.ts @@ -6,11 +6,11 @@ */ import { Vec3, Mat4 } from '../../linear-algebra' -import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNames } from './tables' +import { SpacegroupName, TransformData, GroupData, getSpacegroupIndex, OperatorData, SpacegroupNumber } from './tables' import { SymmetryOperator } from '../../geometry'; interface SpacegroupCell { - /** Zero based spacegroup number */ + /** Index into spacegroup data table */ readonly index: number, readonly size: Vec3, readonly volume: number, @@ -22,7 +22,10 @@ interface SpacegroupCell { } interface Spacegroup { + /** Hermann-Mauguin spacegroup name */ readonly name: string, + /** Spacegroup number from International Tables for Crystallography */ + readonly num: number, readonly cell: SpacegroupCell, readonly operators: ReadonlyArray<Mat4> } @@ -72,14 +75,15 @@ namespace SpacegroupCell { } } - namespace Spacegroup { /** P1 with [1, 1, 1] cell */ export const ZeroP1 = create(SpacegroupCell.Zero); export function create(cell: SpacegroupCell): Spacegroup { const operators = GroupData[cell.index].map(i => getOperatorMatrix(OperatorData[i])); - return { name: SpacegroupNames[cell.index], cell, operators }; + const name = SpacegroupName[cell.index] + const num = SpacegroupNumber[cell.index] + return { name, num, cell, operators }; } const _ijkVec = Vec3(); diff --git a/src/mol-math/geometry/spacegroup/tables.ts b/src/mol-math/geometry/spacegroup/tables.ts index 4402cc436f5083f3ace22d78971359c0488cc209..996f4449fe70de22623b3ddc5c25e648c6bac1ed 100644 --- a/src/mol-math/geometry/spacegroup/tables.ts +++ b/src/mol-math/geometry/spacegroup/tables.ts @@ -1,7 +1,8 @@ /** - * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal <david.sehnal@gmail.com> + * @author Alexander Rose <alexander.rose@weirdbyte.de> */ export const TransformData = [ @@ -31,26 +32,26 @@ export const TransformData = [ [0.0, 0.0, -1.0, 0.75], [1.0, -1.0, 0.0, 0.0], [-1.0, 1.0, 0.0, 0.0], - [0.0, 0.0, 1.0, 0.333333333333333], - [0.0, 0.0, 1.0, 0.666666666666667], - [1.0, 0.0, 0.0, 0.666666666666667], - [0.0, 1.0, 0.0, 0.333333333333333], - [0.0, -1.0, 0.0, 0.666666666666667], - [1.0, -1.0, 0.0, 0.333333333333333], - [-1.0, 1.0, 0.0, 0.666666666666667], - [-1.0, 0.0, 0.0, 0.333333333333333], - [1.0, 0.0, 0.0, 0.333333333333333], - [0.0, 1.0, 0.0, 0.666666666666667], - [0.0, -1.0, 0.0, 0.333333333333333], - [1.0, -1.0, 0.0, 0.666666666666667], - [-1.0, 1.0, 0.0, 0.333333333333333], - [-1.0, 0.0, 0.0, 0.666666666666667], - [0.0, 0.0, -1.0, 0.333333333333333], - [0.0, 0.0, -1.0, 0.666666666666667], - [0.0, 0.0, 1.0, 0.833333333333333], - [0.0, 0.0, 1.0, 0.166666666666667], - [0.0, 0.0, -1.0, 0.833333333333333], - [0.0, 0.0, -1.0, 0.166666666666667], + [0.0, 0.0, 1.0, 1/3], + [0.0, 0.0, 1.0, 2/3], + [1.0, 0.0, 0.0, 2/3], + [0.0, 1.0, 0.0, 1/3], + [0.0, -1.0, 0.0, 2/3], + [1.0, -1.0, 0.0, 1/3], + [-1.0, 1.0, 0.0, 2/3], + [-1.0, 0.0, 0.0, 1/3], + [1.0, 0.0, 0.0, 1/3], + [0.0, 1.0, 0.0, 2/3], + [0.0, -1.0, 0.0, 1/3], + [1.0, -1.0, 0.0, 2/3], + [-1.0, 1.0, 0.0, 1/3], + [-1.0, 0.0, 0.0, 2/3], + [0.0, 0.0, -1.0, 1/3], + [0.0, 0.0, -1.0, 2/3], + [0.0, 0.0, 1.0, 5/6], + [0.0, 0.0, 1.0, 1/6], + [0.0, 0.0, -1.0, 5/6], + [0.0, 0.0, -1.0, 1/6], ]; export const OperatorData = [ @@ -970,6 +971,16 @@ export const GroupData = [ [0, 52, 16, 1, 26, 59, 20, 65], [0, 31, 1, 63], [0, 1, 24, 62], + [0, 15, 1, 9], // 'P 1 21/n 1' + // X,Y,Z + // -X+1/2,Y+1/2,-Z+1/2 + // -X,-Y,-Z + // X+1/2,-Y+1/2,Z+1/2 + [0, 5, 1, 8], // 'P 1 21/a 1' + // X,Y,Z + // -X+1/2,Y+1/2,-Z + // -X,-Y,-Z + // X+1/2,-Y+1/2,Z [0, 31, 1, 63, 26, 21, 65, 54], [0, 2, 57, 56], [0, 60, 3, 16], @@ -985,7 +996,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 ZeroBasedSpacegroupNumbers = { +export const SpacegroupNameToIndexMap = { 'P 1': 0, 'P -1': 1, 'P 1 2 1': 2, @@ -1318,44 +1329,164 @@ export const ZeroBasedSpacegroupNumbers = { 'B 1 1 2/m': 250, 'P 1 1 2/b': 251, 'P 1 1 21/b': 252, - 'B 1 1 2/b': 253, - 'P 21 2 2': 254, - 'P 2 21 2': 255, - 'P 21 21 2 (a)': 256, - 'P 21 2 21': 257, - 'P 2 21 21': 258, - 'C 2 2 21a)': 259, - 'C 2 2 2a': 260, - 'F 2 2 2a': 261, - 'I 2 2 2a': 262, - 'P 21/m 21/m 2/n a': 263, - 'P 42 21 2a': 264, - 'I 2 3a': 265, + 'P 1 21/n 1': 253, + 'P 1 21/a 1': 254, + 'B 1 1 2/b': 255, + 'P 21 2 2': 256, + 'P 2 21 2': 257, + 'P 21 21 2 (a)': 258, + 'P 21 2 21': 259, + 'P 2 21 21': 260, + 'C 2 2 21a)': 261, + 'C 2 2 2a': 262, + 'F 2 2 2a': 263, + 'I 2 2 2a': 264, + 'P 21/m 21/m 2/n a': 265, + 'P 42 21 2a': 266, + 'I 2 3a': 267, }; -export type SpacegroupName = keyof typeof ZeroBasedSpacegroupNumbers +export function getSpacegroupIndexFromNumber(num: number) { + // 38 spacegroup variants as given CCP4s symop.lib + switch (num) { + case 1146: return 146 + case 1148: return 149 + case 1155: return 157 + case 1160: return 163 + case 1161: return 165 + case 1166: return 171 + case 1167: return 173 -export const SpacegroupNames: { [num: number]: SpacegroupName } = (function () { + case 1003: return 237 // 'P 1 1 2' !(dyad along z) + case 1004: return 238 // 'P 1 1 21' !(unique axis c) + case 1005: return 239 // 'B 1 1 2' 'B 2' + case 2005: return 240 // 'A 1 2 1' + case 3005: return 241 // 'C 1 21 1' ! (Origin on screw at 1/4X) + case 4005: return 242 // 'I 1 2 1' 'I 2' !!! GJK @ 2003-06-02 + case 5005: return 243 // 'I 1 21 1' + case 1006: return 244 // 'P 1 1 m' + case 1007: return 245 // 'P 1 1 b' + case 1008: return 246 // 'B 1 1 m' + case 1009: return 247 // 'B 1 1 b' + case 1010: return 248 // 'P 1 1 2/m' + case 1011: return 249 // 'P 1 1 21/m' + case 1012: return 250 // 'B 1 1 2/m' + case 1013: return 251 // 'P 1 1 2/b' + case 1014: return 252 // 'P 1 1 21/b' + case 2014: return 253 // 'P 1 21/n 1' + case 3014: return 254 // 'P 1 21/a 1' + case 1015: return 255 // 'B 1 1 2/b' + case 1017: return 256 // 'P 21 2 2' !(unique axis a) + case 2017: return 257 // 'P 2 21 2' !(unique axis b) + case 1018: return 258 // 'P 21 21 2 (a)' ! origin on 21 21, shift (1/4,1/4,0) + case 2018: return 259 // 'P 21 2 21' !(unique axis b) + case 3018: return 260 // 'P 2 21 21' !(unique axis a) + case 1020: return 261 // 'C 2 2 21a)' ! P212121 with C centring, shift(1/4,0,0) + case 1021: return 262 // 'C 2 2 2a' ! C21212a origin on 21 21 + case 1022: return 263 // 'F 2 2 2a' ! same as 1018 with face centring shift (1/4,0,0) + case 1023: return 264 // 'I 2 2 2a' ! as 1018 with origin shift (1/4,1/4,1/4) + case 1059: return 265 // 'P 21/m 21/m 2/n a' + case 1094: return 266 // 'P 42 21 2a' ! (as P21212a) origin on 21 21 ie Shift 1/4,1/4,1/4 + case 1197: return 267 // 'I 2 3a' ! Expansion of 1023 which is an expansion of 1018 + } + + let offset = 0 + if (num > 146) ++offset + if (num > 148) ++offset + if (num > 155) ++offset + if (num > 160) ++offset + if (num > 161) ++offset + if (num > 166) ++offset + if (num > 167) ++offset + + return num - 1 + offset +} + +export function getSpacegroupNumberFromIndex(idx: number) { + if (idx < 146) return idx + 1 + if (idx === 146) return 1146 + + if (idx < 149) return idx + 1 - 1 + if (idx === 149) return 1148 + + if (idx < 157) return idx + 1 - 2 + if (idx === 157) return 1155 + + if (idx < 163) return idx + 1 - 3 + if (idx === 163) return 1160 + + if (idx < 165) return idx + 1 - 4 + if (idx === 165) return 1161 + + if (idx < 171) return idx + 1 - 5 + if (idx === 171) return 1166 + + if (idx < 173) return idx + 1 - 6 + if (idx === 173) return 1167 + + if (idx < 237) return idx + 1 - 7 + if (idx === 237) return 1003 + if (idx === 238) return 1004 + if (idx === 239) return 1005 + if (idx === 240) return 2005 + if (idx === 241) return 3005 + if (idx === 242) return 4005 + if (idx === 243) return 5005 + if (idx === 244) return 1006 + if (idx === 245) return 1007 + if (idx === 246) return 1008 + if (idx === 247) return 1009 + if (idx === 248) return 1010 + if (idx === 249) return 1011 + if (idx === 250) return 1012 + if (idx === 251) return 1013 + if (idx === 252) return 1014 + + if (idx === 253) return 2014 + if (idx === 254) return 3014 + if (idx === 255) return 1015 + if (idx === 256) return 1017 + if (idx === 257) return 2017 + if (idx === 258) return 1018 + if (idx === 259) return 2018 + if (idx === 260) return 3018 + if (idx === 261) return 1020 + if (idx === 262) return 1021 + if (idx === 263) return 1022 + if (idx === 264) return 1023 + if (idx === 265) return 1059 + if (idx === 266) return 1094 + if (idx === 267) return 1197 + + throw new Error(`unknown spacegroup index '${idx}'`) +} + +export type SpacegroupName = keyof typeof SpacegroupNameToIndexMap + +/** Maps spacegroup index to Hermann-Mauguin spacegroup name */ +export const SpacegroupName: { [idx: number]: SpacegroupName } = (function () { const names = Object.create(null); - for (const n of Object.keys(ZeroBasedSpacegroupNumbers)) { - names[(ZeroBasedSpacegroupNumbers as any)[n]] = n; + for (const n of Object.keys(SpacegroupNameToIndexMap)) { + names[(SpacegroupNameToIndexMap as any)[n]] = n; } return names; }()); -// return -1 if the spacegroup does not exist. -export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number { - let index: number - if (typeof nameOrNumber === 'number') { - // used by CCP4, see http://www.ccp4.ac.uk/html/pointless.html#setting - if (nameOrNumber === 1017) index = 254 - else if (nameOrNumber === 2017) index = 255 - else if (nameOrNumber === 2018) index = 257 - else if (nameOrNumber === 3018) index = 258 - else index = nameOrNumber - 1 - } else { - index = ZeroBasedSpacegroupNumbers[nameOrNumber as SpacegroupName]; +/** Maps spacegroup index to spacegroup number from International Tables for Crystallography */ +export const SpacegroupNumber: { [idx: number]: number } = (function () { + const numbers = Object.create(null); + for (const n of Object.keys(SpacegroupNameToIndexMap)) { + const idx = (SpacegroupNameToIndexMap as any)[n] + numbers[idx] = getSpacegroupNumberFromIndex(idx); } - if (typeof index === 'undefined' || typeof SpacegroupNames[index] === 'undefined') return -1; + return numbers; +}()); + +/** return -1 if the spacegroup does not exist */ +export function getSpacegroupIndex(nameOrNumber: number | string | SpacegroupName): number { + const index = typeof nameOrNumber === 'number' + ? getSpacegroupIndexFromNumber(nameOrNumber) + : SpacegroupNameToIndexMap[nameOrNumber as SpacegroupName]; + if (typeof index === 'undefined' || typeof SpacegroupName[index] === 'undefined') return -1; return index; } \ No newline at end of file diff --git a/src/mol-model-formats/structure/mmcif/parser.ts b/src/mol-model-formats/structure/mmcif/parser.ts index d4827cb4198352f86de1fa87db2e88007fca29aa..ff143a1ffb4f1750026f703bfec8fd816cf19613 100644 --- a/src/mol-model-formats/structure/mmcif/parser.ts +++ b/src/mol-model-formats/structure/mmcif/parser.ts @@ -54,11 +54,19 @@ function checkNonStandardCrystalFrame(format: mmCIF_Format, spacegroup: Spacegro return false; } +function getSpacegroupNameOrNumber(symmetry: mmCIF_Format['data']['symmetry']) { + const groupNumber = symmetry['Int_Tables_number'].value(0); + const groupName = symmetry['space_group_name_H-M'].value(0); + if (!symmetry['Int_Tables_number'].isDefined) return groupName + if (!symmetry['space_group_name_H-M'].isDefined) return groupNumber + return groupName +} + 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, + const nameOrNumber = getSpacegroupNameOrNumber(symmetry) + const spaceCell = SpacegroupCell.create(nameOrNumber, 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)); diff --git a/src/mol-plugin/util/model-unitcell.ts b/src/mol-plugin/util/model-unitcell.ts index fa3e7d8bd21b5e946df74124691a5b68ba3fdd6a..aa4d779e59b1ac4c9c32298de11e15f351efcf86 100644 --- a/src/mol-plugin/util/model-unitcell.ts +++ b/src/mol-plugin/util/model-unitcell.ts @@ -66,8 +66,8 @@ export async function getUnitcellRepresentation(ctx: RuntimeContext, model: Mode } function getUnitcellLabel(data: UnitcellData) { - const { cell, name } = data.symmetry.spacegroup - const { index, size, anglesInRadians } = cell + const { cell, name, num } = data.symmetry.spacegroup + const { size, anglesInRadians } = cell const a = size[0].toFixed(2) const b = size[1].toFixed(2) const c = size[2].toFixed(2) @@ -76,7 +76,7 @@ function getUnitcellLabel(data: UnitcellData) { const gamma = radToDeg(anglesInRadians[2]).toFixed(2) const label: string[] = [] // name - label.push(`${name} #${index + 1}`) + label.push(`${name} #${num}`) // sizes label.push(`${a}\u00D7${b}\u00D7${c} \u212B`) // angles