Added spacegroups to mol-model

This commit is contained in:
David Sehnal
2018-05-10 12:44:53 +02:00
parent b1ceb0c7d0
commit 059a09d63e
17 changed files with 165 additions and 63 deletions

View File

@@ -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
1 atom_site.group_PDB atom_sites.entry_id
1 atom_sites.entry_id
2 atom_sites.fract_transf_matrix
3 atom_sites.fract_transf_vector
4 atom_site.group_PDB
5 atom_site.group_PDB atom_site.id
6 atom_site.id atom_site.type_symbol
7 atom_site.type_symbol atom_site.label_atom_id

View File

@@ -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,

View File

@@ -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;
// P1 with [1, 1, 1] cell.
export const ZeroP1 = create(SpacegroupCell.Zero);
if (typeof num === 'undefined' || typeof name === 'undefined') {
throw new Error(`Spacegroup '${nameOrNumber}' is not defined.`);
}
const operators = GroupData[num].map(i => getOperatorMatrix(OperatorData[i]));
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();
@@ -89,6 +87,8 @@ namespace Spacegroup {
export function getSymmetryOperator(spacegroup: Spacegroup, index: number, i: number, j: number, k: number): SymmetryOperator {
const operator = updateOperatorMatrix(spacegroup, index, i, j, k, Mat4.zero());
console.log(Mat4.makeTable(operator));
console.log({ index, i, j, k });
return SymmetryOperator.create(`${index + 1}_${5 + i}${5 + j}${5 + k}`, operator, Vec3.create(i, j, k));
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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 }

View File

@@ -10,8 +10,7 @@ import { Interval, Segmentation } from 'mol-data/int'
import { Atoms } from 'mol-io/reader/gro/schema'
import Format from '../format'
import Model from '../model'
import * as AtomicHierarchy from '../properties/atomic/hierarchy'
import { AtomicConformation } from '../properties/atomic/conformation'
import { AtomicConformation, AtomicData, AtomsSchema, ResiduesSchema, ChainsSchema, AtomicSegments } from '../properties/atomic'
import { CoarseGrainedHierarchy } from '../properties/coarse-grained/hierarchy'
import findHierarchyKeys from '../utils/hierarchy-keys'
import { guessElement } from '../utils/guess-element'
@@ -21,6 +20,7 @@ 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';
import { ModelSymmetry } from '../properties/symmetry';
type HierarchyOffsets = { residues: ArrayLike<number>, chains: ArrayLike<number> }
@@ -44,9 +44,9 @@ function guessElementSymbol (value: string) {
return ElementSymbol(guessElement(value));
}
function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicHierarchy.AtomicData {
function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): AtomicData {
console.log(atomsData.atomName)
const atoms = Table.ofColumns(AtomicHierarchy.AtomsSchema, {
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,
@@ -54,14 +54,14 @@ function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Atomi
pdbx_formal_charge: Column.Undefined(atomsData.count, Column.Schema.int)
});
const residues = Table.view(Table.ofColumns(AtomicHierarchy.ResiduesSchema, {
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),
}), AtomicHierarchy.ResiduesSchema, offsets.residues);
}), ResiduesSchema, offsets.residues);
// Optimize the numeric columns
Table.columnToArray(residues, 'label_seq_id', Int32Array);
Table.columnToArray(residues, 'auth_seq_id', Int32Array);
@@ -72,7 +72,7 @@ function createHierarchyData(atomsData: Atoms, offsets: HierarchyOffsets): Atomi
// label_entity_id: Column.Undefined(atomsData.count, Column.Schema.str)
// });
const chains = Table.ofUndefinedColumns(AtomicHierarchy.ChainsSchema, 0);
const chains = Table.ofUndefinedColumns(ChainsSchema, 0);
return { atoms, residues, chains };
}
@@ -89,10 +89,10 @@ function getConformation(atoms: Atoms): AtomicConformation {
}
}
function isHierarchyDataEqual(a: AtomicHierarchy.AtomicData, b: AtomicHierarchy.AtomicData) {
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<AtomicHierarchy.ResiduesSchema>, b.residues as Table<AtomicHierarchy.ResiduesSchema>)
&& Table.areEqual(a.atoms as Table<AtomicHierarchy.AtomsSchema>, b.atoms as Table<AtomicHierarchy.AtomsSchema>)
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 {
@@ -109,7 +109,7 @@ function createModel(format: gro_Format, modelNum: number, previous?: Model): Mo
};
}
const hierarchySegments: AtomicHierarchy.AtomicSegments = {
const hierarchySegments: AtomicSegments = {
residueSegments: Segmentation.ofOffsets(hierarchyOffsets.residues, bounds),
chainSegments: Segmentation.ofOffsets(hierarchyOffsets.chains, bounds),
}
@@ -135,7 +135,7 @@ function createModel(format: gro_Format, modelNum: number, previous?: Model): Mo
sequence: Sequence.fromAtomicHierarchy(hierarchy),
atomSiteConformation: getConformation(structure.atoms),
coarseGrained: CoarseGrainedHierarchy.Empty,
symmetry: { assemblies: [] },
symmetry: ModelSymmetry.Default,
atomCount: structure.atoms.count
};
}

View File

@@ -9,9 +9,8 @@ import { Column, Table } from 'mol-data/db'
import { Interval, Segmentation } from 'mol-data/int'
import Format from '../format'
import Model from '../model'
import { AtomsSchema, ResiduesSchema, ChainsSchema, AtomicData, AtomicSegments } from '../properties/atomic/hierarchy'
import { AtomicConformation } from '../properties/atomic/conformation'
import Symmetry from '../properties/symmetry'
import { AtomsSchema, ResiduesSchema, ChainsSchema, AtomicData, AtomicSegments, AtomicConformation } from '../properties/atomic'
import { ModelSymmetry } from '../properties/symmetry'
import findHierarchyKeys from '../utils/hierarchy-keys'
import { ElementSymbol} from '../types'
import createAssemblies from './mmcif/assembly'
@@ -20,6 +19,8 @@ import mmCIF_Format = Format.mmCIF
import { getSequence } from './mmcif/sequence';
import { Entities } from '../properties/common';
import { coarseGrainedFromIHM } from './mmcif/ihm';
import { Spacegroup, SpacegroupCell } from 'mol-math/geometry';
import { Vec3 } from 'mol-math/linear-algebra';
function findModelBounds({ data }: mmCIF_Format, startIndex: number) {
const num = data.atom_site.pdbx_PDB_model_num;
@@ -83,8 +84,29 @@ function getConformation({ data }: mmCIF_Format, bounds: Interval): AtomicConfor
}
}
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 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) {

View File

@@ -7,7 +7,7 @@
import { mmCIF_Database as mmCIF } from 'mol-io/reader/cif/schema/mmcif'
import Sequence from '../../properties/sequence'
import { Column } from 'mol-data/db';
import { AtomicHierarchy } from '../../properties/atomic/hierarchy';
import { AtomicHierarchy } from '../../properties/atomic';
import { Entities } from '../../properties/common';
export function getSequence(cif: mmCIF, entities: Entities, hierarchy: AtomicHierarchy): Sequence {

View File

@@ -7,9 +7,8 @@
import UUID from 'mol-util/uuid'
import Format from './format'
import Sequence from './properties/sequence'
import { AtomicHierarchy } from './properties/atomic/hierarchy'
import { AtomicConformation } from './properties/atomic/conformation'
import Symmetry from './properties/symmetry'
import { AtomicHierarchy, AtomicConformation } from './properties/atomic'
import { ModelSymmetry } from './properties/symmetry'
import { CoarseGrainedHierarchy } from './properties/coarse-grained/hierarchy'
import { Entities } from './properties/common';
@@ -28,7 +27,7 @@ interface Model extends Readonly<{
sourceData: Format,
symmetry: Symmetry,
symmetry: ModelSymmetry,
entities: Entities,
sequence: Sequence,

View File

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

View File

@@ -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 }

View File

@@ -5,7 +5,7 @@
*/
import { Column } from 'mol-data/db'
import { AtomicData, AtomicSegments, AtomicKeys } from '../properties/atomic/hierarchy'
import { AtomicData, AtomicSegments, AtomicKeys } from '../properties/atomic'
import { Interval, Segmentation } from 'mol-data/int'
import { Entities } from '../properties/common';

View File

@@ -5,7 +5,7 @@
*/
import { Element, Unit } from '../structure'
import { VdwRadius } from '../model/properties/atomic/measures';
import { VdwRadius } from '../model/properties/atomic';
const constant = {
true: Element.property(l => true),

View File

@@ -11,15 +11,17 @@ 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 StructureSymmetry {
export function buildAssembly(structure: Structure, name: string) {
return Task.create('Build Symmetry', async ctx => {
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], name);
if (!assembly) throw new Error(`Assembly '${name}' is not defined.`);
const assembly = ModelSymmetry.findAssembly(models[0], asmName);
if (!assembly) throw new Error(`Assembly '${asmName}' is not defined.`);
const assembler = Structure.Builder();
@@ -41,6 +43,38 @@ namespace StructureSymmetry {
});
}
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 assembler = Structure.Builder();
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));
}

View File

@@ -16,6 +16,7 @@ import { Structure, Model, Queries as Q, Element, Selection, StructureSymmetry,
import to_mmCIF from 'mol-model/structure/export/mmcif'
import { Run } from 'mol-task';
import { Vec3 } from 'mol-math/linear-algebra';
//import { EquivalenceClasses } from 'mol-data/util';
require('util.promisify').shim();
@@ -302,6 +303,19 @@ export namespace PropertyAccess {
console.log('exported');
}
export async function testSymmetry(id: string, s: Structure) {
console.time('symmetry')
const a = await Run(StructureSymmetry.buildSymmetryRange(s, Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)));
//const auth_comp_id = Q.props.residue.auth_comp_id;
//const q1 = Query(Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'ALA' }));
//const alas = await query(q1, a);
console.timeEnd('symmetry')
fs.writeFileSync(`${DATA_DIR}/${id}_symm.bcif`, to_mmCIF(id, a, true));
//fs.writeFileSync(`${DATA_DIR}/${id}_assembly.bcif`, to_mmCIF(id, Selection.unionStructure(alas), true));
console.log('exported');
}
// export async function testGrouping(structure: Structure) {
// const { elements, units } = await Run(Symmetry.buildAssembly(structure, '1'));
// console.log('grouping', units.length);
@@ -329,7 +343,7 @@ export namespace PropertyAccess {
//const { structures, models/*, mmcif*/ } = await getBcif('1cbs');
// const { structures, models } = await getBcif('3j3q');
const { structures, models /*, mmcif*/ } = await readCIF('e:/test/quick/1hrv_updated.cif');
const { structures, models /*, mmcif*/ } = await readCIF('e:/test/quick/1cbs_updated.cif');
//const { structures: s1, /*, mmcif*/ } = await readCIF('e:/test/quick/1tqn_updated.cif');
// testGrouping(structures[0]);
@@ -340,7 +354,8 @@ export namespace PropertyAccess {
//console.log(mmcif.pdbx_struct_oper_list.matrix.toArray());
// console.log(mmcif.pdbx_struct_oper_list.vector.toArray());
await testAssembly('1hrv', structures[0]);
//await testAssembly('1hrv', structures[0]);
await testSymmetry('1cbs', structures[0]);
// throw '';
// console.log(models[0].symmetry.assemblies);