diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index e6c5bae4673f2379bfaeb1b2296f905b74613b70..a0fe47488e993364905d6837ee90853bad757c86 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/src/mol-model/structure/structure/symmetry.ts @@ -1,5 +1,5 @@ /** - * Copyright (c) 2017-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2017-2020 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> @@ -11,10 +11,11 @@ import { Spacegroup, SpacegroupCell, SymmetryOperator } from '../../../mol-math/ import { Vec3, Mat4 } from '../../../mol-math/linear-algebra'; import { RuntimeContext, Task } from '../../../mol-task'; import { Symmetry, Model } from '../model'; -import { QueryContext, StructureSelection } from '../query'; +import { QueryContext, StructureSelection, Queries as Q } from '../query'; import Structure from './structure'; import Unit from './unit'; import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; +import StructureProperties from './properties'; namespace StructureSymmetry { export function buildAssembly(structure: Structure, asmName: string) { @@ -48,6 +49,40 @@ namespace StructureSymmetry { }); } + export type Generators = { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[] + + export function buildSymmetryAssembly(structure: Structure, generators: Generators, symmetry: Symmetry) { + return Task.create('Build Symmetry Assembly', async ctx => { + const models = structure.models; + if (models.length !== 1) throw new Error('Can only build symmetry assemblies from structures based on 1 model.'); + + const modelCenter = Vec3() // Model.getCenter(models[0]) + const assembler = Structure.Builder({ label: structure.label }); + + const queryCtx = new QueryContext(structure); + + for (const g of generators) { + const selector = getSelector(g.asymIds); + const selection = selector(queryCtx); + if (StructureSelection.structureCount(selection) === 0) { + continue; + } + const { units } = StructureSelection.unionStructure(selection); + + for (const { index, shift: [i, j, k] } of g.operators) { + const operators = getOperatorsForIndex(symmetry, index, i, j, k, modelCenter) + for (const unit of units) { + for (const op of operators) { + assembler.addWithOperator(unit, op); + } + } + } + } + + return assembler.getStructure(); + }); + } + export function builderSymmetryMates(structure: Structure, radius: number) { return Task.create('Find Symmetry Mates', ctx => findMatesRadius(ctx, structure, radius)); } @@ -96,7 +131,35 @@ namespace StructureSymmetry { } } -function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) { +function getSelector(asymIds: string[]) { + return Q.generators.atoms({ chainTest: Q.pred.and( + Q.pred.eq(ctx => StructureProperties.unit.operator_name(ctx.element), SymmetryOperator.DefaultName), + Q.pred.inSet(ctx => StructureProperties.chain.label_asym_id(ctx.element), asymIds) + )}); +} + +function getOperatorsForIndex(symmetry: Symmetry, index: number, i: number, j: number, k: number, modelCenter: Vec3) { + const { spacegroup, ncsOperators } = symmetry; + const operators: SymmetryOperator[] = [] + + const { toFractional } = spacegroup.cell + const ref = Vec3.transformMat4(Vec3(), modelCenter, toFractional) + + const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, index, i, j, k, ref) + if (ncsOperators && ncsOperators.length) { + for (let u = 0, ul = ncsOperators.length; u < ul; ++u) { + const ncsOp = ncsOperators![u] + const matrix = Mat4.mul(Mat4(), symOp.matrix, ncsOp.matrix) + const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp); + operators.push(operator) + } + } else { + operators.push(symOp) + } + return operators +} + +function getOperatorsForRange(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCenter: Vec3) { const { spacegroup, ncsOperators } = symmetry; const ncsCount = (ncsOperators && ncsOperators.length) || 0 const operators: SymmetryOperator[] = []; @@ -117,18 +180,7 @@ function getOperators(symmetry: Symmetry, ijkMin: Vec3, ijkMax: Vec3, modelCente for (let k = ijkMin[2]; k <= ijkMax[2]; k++) { // check if we have added identity as the 1st operator. if (!ncsCount && op === 0 && i === 0 && j === 0 && k === 0) continue; - - const symOp = Spacegroup.getSymmetryOperatorRef(spacegroup, op, i, j, k, ref) - if (ncsCount) { - for (let u = 0; u < ncsCount; ++u) { - const ncsOp = ncsOperators![u] - const matrix = Mat4.mul(Mat4.zero(), symOp.matrix, ncsOp.matrix) - const operator = SymmetryOperator.create(`${symOp.name} ${ncsOp.name}`, matrix, symOp.assembly, ncsOp.ncsId, symOp.hkl, symOp.spgrOp); - operators[operators.length] = operator; - } - } else { - operators[operators.length] = symOp; - } + operators.push(...getOperatorsForIndex(symmetry, op, i, j, k, ref)) } } } @@ -142,7 +194,7 @@ function getOperatorsCached333(symmetry: Symmetry, ref: Vec3) { } symmetry._operators_333 = { ref: Vec3.clone(ref), - operators: getOperators(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref) + operators: getOperatorsForRange(symmetry, Vec3.create(-3, -3, -3), Vec3.create(3, 3, 3), ref) }; return symmetry._operators_333.operators; } @@ -181,7 +233,7 @@ async function findSymmetryRange(ctx: RuntimeContext, structure: Structure, ijkM if (SpacegroupCell.isZero(spacegroup.cell)) return structure; const modelCenter = Model.getCenter(models[0]) - const operators = getOperators(symmetry, ijkMin, ijkMax, modelCenter); + const operators = getOperatorsForRange(symmetry, ijkMin, ijkMax, modelCenter); return assembleOperators(structure, operators); } diff --git a/src/mol-plugin/state/representation/model.ts b/src/mol-plugin/state/representation/model.ts index 47abb3649f33ac19334f3edca6fddc52675692c9..0c25c5313f8e0408f885d1471688781bd76d7fa3 100644 --- a/src/mol-plugin/state/representation/model.ts +++ b/src/mol-plugin/state/representation/model.ts @@ -1,12 +1,13 @@ /** - * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2019-2020 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> */ import { Model, Structure, StructureSymmetry } from '../../../mol-model/structure'; import { stringToWords } from '../../../mol-util/string'; -import { SpacegroupCell } from '../../../mol-math/geometry'; +import { SpacegroupCell, Spacegroup } from '../../../mol-math/geometry'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Vec3 } from '../../../mol-math/linear-algebra'; import { RuntimeContext } from '../../../mol-task'; @@ -14,14 +15,33 @@ import { PluginContext } from '../../context'; import { Assembly, Symmetry } from '../../../mol-model/structure/model/properties/symmetry'; import { PluginStateObject as SO } from '../objects'; import { ModelSymmetry } from '../../../mol-model-formats/structure/property/symmetry'; +import { MmcifFormat } from '../../../mol-model-formats/structure/mmcif'; export namespace ModelStructureRepresentation { - export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates') { + export function getParams(model?: Model, defaultValue?: 'deposited' | 'assembly' | 'symmetry' | 'symmetry-mates' | 'symmetry-assembly') { const symmetry = model && ModelSymmetry.Provider.get(model) const assemblyIds = symmetry ? symmetry.assemblies.map(a => [a.id, `${a.id}: ${stringToWords(a.details)}`] as [string, string]) : []; const showSymm = !symmetry ? true : !SpacegroupCell.isZero(symmetry.spacegroup.cell); + const operatorOptions: [number, string][] = [] + if (symmetry) { + const { operators } = symmetry.spacegroup + for (let i = 0, il = operators.length; i < il; i++) { + operatorOptions.push([i, `${i + 1}: ${Spacegroup.getOperatorXyz(operators[i])}`]) + } + } + + const asymIdsOptions: [string, string][] = [] + if (model && MmcifFormat.is(model?.sourceData)) { + // TODO make generally available for models, also include auth_asym_id + const { struct_asym } = model.sourceData.data.db + for (let i = 0, il = struct_asym._rowCount; i < il; ++i) { + const id = struct_asym.id.value(i) + asymIdsOptions.push([id, id]) + } + } + const modes = { deposited: PD.EmptyGroup(), assembly: PD.Group({ @@ -35,6 +55,19 @@ export namespace ModelStructureRepresentation { 'symmetry': PD.Group({ ijkMin: PD.Vec3(Vec3.create(-1, -1, -1), { label: 'Min IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }), ijkMax: PD.Vec3(Vec3.create(1, 1, 1), { label: 'Max IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }) + }, { isFlat: true }), + 'symmetry-assembly': PD.Group({ + generators: PD.ObjectList({ + operators: PD.ObjectList({ + index: PD.Select(0, operatorOptions), + shift: PD.Vec3(Vec3(), { label: 'IJK', fieldLabels: { x: 'I', y: 'J', z: 'K' } }) + }, e => `${e.index + 1}_${e.shift.map(a => a + 5).join('')}`, { + defaultValue: [] as { index: number, shift: Vec3 }[] + }), + asymIds: PD.MultiSelect([] as string[], asymIdsOptions) + }, e => `${e.asymIds.length} asym ids, ${e.operators.length} operators`, { + defaultValue: [] as { operators: { index: number, shift: Vec3 }[], asymIds: string[] }[] + }) }, { isFlat: true }) }; @@ -49,6 +82,7 @@ export namespace ModelStructureRepresentation { if (showSymm) { options.push(['symmetry-mates', 'Symmetry Mates']); options.push(['symmetry', 'Symmetry (indices)']); + options.push(['symmetry-assembly', 'Symmetry (assembly)']); } return { @@ -105,8 +139,16 @@ export namespace ModelStructureRepresentation { return new SO.Molecule.Structure(s, props); } + async function buildSymmetryAssembly(ctx: RuntimeContext, model: Model, generators: StructureSymmetry.Generators, symmetry: Symmetry) { + const base = Structure.ofModel(model); + const s = await StructureSymmetry.buildSymmetryAssembly(base, generators, symmetry).runInContext(ctx); + const props = { label: `Symmetry Assembly`, description: Structure.elementDescription(s) }; + return new SO.Molecule.Structure(s, props); + } + export async function create(plugin: PluginContext, ctx: RuntimeContext, model: Model, params?: Params): Promise<SO.Molecule.Structure> { - if (!params || params.name === 'deposited') { + const symmetry = ModelSymmetry.Provider.get(model) + if (!symmetry || !params || params.name === 'deposited') { const s = Structure.ofModel(model); return new SO.Molecule.Structure(s, { label: 'Deposited', description: Structure.elementDescription(s) }); } @@ -119,6 +161,9 @@ export namespace ModelStructureRepresentation { if (params.name === 'symmetry-mates') { return buildSymmetryMates(ctx, model, params.params.radius) } + if (params.name === 'symmetry-assembly') { + return buildSymmetryAssembly(ctx, model, params.params.generators, symmetry) + } throw new Error(`Unknown represetation type: ${(params as any).name}`); }