diff --git a/data/mmcif-field-names.csv b/data/mmcif-field-names.csv index 61649087a630b56eaacafc345338f2b8b37a9584..bd1f2d51bdfe8257abd257a27b09b9f4b6afc390 100644 --- a/data/mmcif-field-names.csv +++ b/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 diff --git a/package-lock.json b/package-lock.json index 7e574f630d3b78f7f2f79cd0b910a5f38116cbf4..f8ca913d1861b34ca68229a9b8a821c70985b36f 100644 Binary files a/package-lock.json and b/package-lock.json differ diff --git a/src/apps/cif2bcif/converter.ts b/src/apps/cif2bcif/converter.ts index 8a0530bc5a9acb1dc25e351ba017fbe066307a25..06a9ed517a6529752436d7a22ef9e4908abc80c0 100644 --- a/src/apps/cif2bcif/converter.ts +++ b/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()); } diff --git a/src/apps/combine-mmcif/index.ts b/src/apps/combine-mmcif/index.ts index d107f81132f4110c1c4d01152711ba7f8a9155c5..205c55c11c748d752dd12033405a524fd6102b22 100644 --- a/src/apps/combine-mmcif/index.ts +++ b/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 diff --git a/src/apps/schema-generator/schema-from-mmcif-dic.ts b/src/apps/schema-generator/schema-from-mmcif-dic.ts index eb1d7e74ba4a336e5fc70860a753d1bf8a4ec78e..3d6fc0e4bd3bff518e7d61b3c59032de2096eebd 100644 --- a/src/apps/schema-generator/schema-from-mmcif-dic.ts +++ b/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; diff --git a/src/apps/structure-info/helpers.ts b/src/apps/structure-info/helpers.ts index 94095a99055759cdbab8d9b2c677345632520848..f4a86d9a67ac824988e4fbe0fffd13ced8b2aac5 100644 --- a/src/apps/structure-info/helpers.ts +++ b/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; } diff --git a/src/apps/structure-info/model.ts b/src/apps/structure-info/model.ts index 8ab4ad1dbf361bdda44c7d36492d95e616ca126a..13c6ed4422ff664de86e1eb801a6e224be21cb0e 100644 --- a/src/apps/structure-info/model.ts +++ b/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) { diff --git a/src/apps/structure-info/volume.ts b/src/apps/structure-info/volume.ts index 325e268dd7779e311408fe7c72280b6a7acac48c..00dd391711eba424686f5825594056426ac04380 100644 --- a/src/apps/structure-info/volume.ts +++ b/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. diff --git a/src/examples/task.ts b/src/examples/task.ts index 41025f042c48eb1252f14483ba94e1f10c771470..64143cfb7da152845fa702b6854eb0c5f677c018 100644 --- a/src/examples/task.ts +++ b/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); diff --git a/src/mol-app/service/job.ts b/src/mol-app/service/job.ts index ed2ec6a5da0a65538d2890c587d706f12e901687..3bfd0263ef88655bd885c945267c182efce00183 100644 --- a/src/mol-app/service/job.ts +++ b/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)); } diff --git a/src/mol-data/generic/unique-array.ts b/src/mol-data/generic/unique-array.ts index 5a5caa501af2ba0ba4098c9cf0904ee8d9296859..dad6693959d1d47d7bf66a50f2456bbc0b341ee8 100644 --- a/src/mol-data/generic/unique-array.ts +++ b/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; } } diff --git a/src/mol-data/int/_spec/sorted-array.spec.ts b/src/mol-data/int/_spec/sorted-array.spec.ts index db8cd15d7c90b396bc1226635c34b3222982dcbb..2cd82af32ce9046bb8c2b7a3a71779ea68084071 100644 --- a/src/mol-data/int/_spec/sorted-array.spec.ts +++ b/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))) }); \ No newline at end of file diff --git a/src/mol-data/int/impl/sorted-array.ts b/src/mol-data/int/impl/sorted-array.ts index f3147c7086664c7900c78db8c89f1e87b15ff509..4a5476c16c46de1194639194442ba6b904339a81 100644 --- a/src/mol-data/int/impl/sorted-array.ts +++ b/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) { diff --git a/src/mol-data/int/sorted-array.ts b/src/mol-data/int/sorted-array.ts index 08235061224bfebfdf5f019281936e5c090bb65b..928e383ddf1a083f9a652428131074e4d5a8c735 100644 --- a/src/mol-data/int/sorted-array.ts +++ b/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' } diff --git a/src/mol-data/util/equivalence-classes.ts b/src/mol-data/util/equivalence-classes.ts index e40456499bcc6315d01d6ce17bca83ccd243323a..cfad84f041f3daeffaedb9b340dde3e3801a8d52 100644 --- a/src/mol-data/util/equivalence-classes.ts +++ b/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; } } diff --git a/src/mol-data/util/hash-functions.ts b/src/mol-data/util/hash-functions.ts index 00cae92c4d6f16ba47ed7281b5a773d432198164..8584baa5946e9a06a4ef1ca8e07b8e43607028b9 100644 --- a/src/mol-data/util/hash-functions.ts +++ b/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) diff --git a/src/mol-data/util/sort.ts b/src/mol-data/util/sort.ts index e6eb6571ea82387a9e121e364ecb6528847d7da9..8068a449124aeb9d2b6dac403e0a4ba810fa8e2a 100644 --- a/src/mol-data/util/sort.ts +++ b/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) { diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 274da6a077bbfdeedcf8449020d710e27647cfe0..363e1771ed7ff49ddb907967511e6123f4769410 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/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) } diff --git a/src/mol-geo/representation/structure/point.ts b/src/mol-geo/representation/structure/point.ts index 71f317e39c3a31e83cce6d0f63a3f25201406e29..5828e518c5fe5f325a0fcdf477c28ef83150eea9 100644 --- a/src/mol-geo/representation/structure/point.ts +++ b/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)) { diff --git a/src/mol-geo/representation/structure/spacefill.ts b/src/mol-geo/representation/structure/spacefill.ts index 689f2bfc9bfe0bfac81346a6294d5ae2330e2620..7f2b7ab722ffe1911b2cb70cc8de332bb98fb8b1 100644 --- a/src/mol-geo/representation/structure/spacefill.ts +++ b/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) diff --git a/src/mol-geo/representation/structure/utils.ts b/src/mol-geo/representation/structure/utils.ts index fba67c536e46e9447fd19b170299d25771b19996..c7cd81d17c60d684d5c7bb485dc91d25825c6bba 100644 --- a/src/mol-geo/representation/structure/utils.ts +++ b/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 }) } } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/index.ts b/src/mol-geo/representation/volume/index.ts index 84aee724fff635612391ba5d63db2f2df2e8f55e..779c05940b96e083a7e308479f76d010deaa7e79 100644 --- a/src/mol-geo/representation/volume/index.ts +++ b/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) }); }, diff --git a/src/mol-geo/representation/volume/surface.ts b/src/mol-geo/representation/volume/surface.ts index a2de1a61d10cef902ee848ee9efb6ddab6572ead..bc9531ead7527fed500969d8bf22aa1a966c2c9f 100644 --- a/src/mol-geo/representation/volume/surface.ts +++ b/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) } diff --git a/src/mol-geo/theme/structure/color/chain-id.ts b/src/mol-geo/theme/structure/color/chain-id.ts index 43dfc0db654e236eaf9af30e8ed49b16d033d5be..d521d6036c9f12edaa560e8ba87be14b9bc76fb6 100644 --- a/src/mol-geo/theme/structure/color/chain-id.ts +++ b/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 diff --git a/src/mol-geo/theme/structure/color/element-index.ts b/src/mol-geo/theme/structure/color/element-index.ts index b205043d46ffb5a1fee65485dc9f026644f7a2c6..f6540f95e3fff159b13f9f20908bba27152d83ec 100644 --- a/src/mol-geo/theme/structure/color/element-index.ts +++ b/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 }) diff --git a/src/mol-geo/theme/structure/color/element-symbol.ts b/src/mol-geo/theme/structure/color/element-symbol.ts index 432807d2bb4ee75947a70538afc9f93844591b97..affedfac08a2ca695894d76ff1177370a827b2e8 100644 --- a/src/mol-geo/theme/structure/color/element-symbol.ts +++ b/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 diff --git a/src/mol-geo/theme/structure/color/index.ts b/src/mol-geo/theme/structure/color/index.ts index 82d5b22a6c6535744d2c1000f5d0948bdace8311..9e0d0318dc439923c33f6f164e660357520e8b0a 100644 --- a/src/mol-geo/theme/structure/color/index.ts +++ b/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 } diff --git a/src/mol-geo/theme/structure/color/instance-index.ts b/src/mol-geo/theme/structure/color/instance-index.ts index bd7d6f5775d05bf415f9e4df822f054086c4ad1c..ffae3b0861016c0901dac0e591150e26f114ae4f 100644 --- a/src/mol-geo/theme/structure/color/instance-index.ts +++ b/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 ] diff --git a/src/mol-geo/theme/structure/size/element.ts b/src/mol-geo/theme/structure/size/element.ts index 699bc071a499ab303d3e40d48c3befb465248afe..741ba136f322274b9e72abee37168e56d481fb62 100644 --- a/src/mol-geo/theme/structure/size/element.ts +++ b/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 diff --git a/src/mol-geo/theme/structure/size/index.ts b/src/mol-geo/theme/structure/size/index.ts index 22caed502febf7ab0dd05a25201fe1c960335f07..0c18ca93ee04603b9334f682790ff56e64b21591 100644 --- a/src/mol-geo/theme/structure/size/index.ts +++ b/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 } diff --git a/src/mol-io/reader/_spec/csv.spec.ts b/src/mol-io/reader/_spec/csv.spec.ts index b074b5bc7d51a2df190180065852e4f7dec8c75d..0802bc7491fedc90c702ef6bc24d7fa653746895 100644 --- a/src/mol-io/reader/_spec/csv.spec.ts +++ b/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; diff --git a/src/mol-io/reader/_spec/gro.spec.ts b/src/mol-io/reader/_spec/gro.spec.ts index 6254be8f9d45d287f643e2d50993e8b62eb11bbe..055e4e61b227a58a86c5a1b4b6317c996558ec98 100644 --- a/src/mol-io/reader/_spec/gro.spec.ts +++ b/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) diff --git a/src/mol-io/reader/_spec/mol2.spec.ts b/src/mol-io/reader/_spec/mol2.spec.ts index 43244780636af6fbcd75010522ba9405b75ca2ff..dc88b16043caf6777ca6571656f2c3cfab6eb38f 100644 --- a/src/mol-io/reader/_spec/mol2.spec.ts +++ b/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); } diff --git a/src/mol-io/reader/cif/schema/mmcif.ts b/src/mol-io/reader/cif/schema/mmcif.ts index 373fa1ae8401ea1043707cebc355e0760cf321dc..8f67d33bfa2b3c7ff56c2358ba004197450eb8ca 100644 --- a/src/mol-io/reader/cif/schema/mmcif.ts +++ b/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, diff --git a/src/mol-math/geometry/spacegroup/construction.ts b/src/mol-math/geometry/spacegroup/construction.ts index 65aa22e849283499982fbf6fefdd71ade328105b..2acdb5287eb62b96c24ee3a4617a5f742b04bc63 100644 --- a/src/mol-math/geometry/spacegroup/construction.ts +++ b/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(); diff --git a/src/mol-math/geometry/spacegroup/tables.ts b/src/mol-math/geometry/spacegroup/tables.ts index de3ec97927b441f1fc80b0a8d665650b628bf331..91ae4bc0273ed99cc361e047c64684c0cacea90f 100644 --- a/src/mol-math/geometry/spacegroup/tables.ts +++ b/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; -}()); \ No newline at end of file +}()); + +// 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; +} \ No newline at end of file diff --git a/src/mol-math/geometry/symmetry-operator.ts b/src/mol-math/geometry/symmetry-operator.ts index 6b65fafd045d6bd31dcfbe1b2eb59308b44a37be..fe6cbc7aa04ca0deaa8869cb4afbe7998fc7669e 100644 --- a/src/mol-math/geometry/symmetry-operator.ts +++ b/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 { diff --git a/src/mol-math/linear-algebra/3d/common.ts b/src/mol-math/linear-algebra/3d/common.ts index eebaeb2901c60d1213d8e5963a5c44fb3fc2842f..4276dd8d60b1e254621bfb87e8dbd5302b023d0e 100644 --- a/src/mol-math/linear-algebra/3d/common.ts +++ b/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 } \ No newline at end of file +export const enum EPSILON { Value = 0.000001 } + +export function equalEps(a: number, b: number, eps: number) { + return Math.abs(a - b) <= eps; +} diff --git a/src/mol-math/linear-algebra/3d/mat3.ts b/src/mol-math/linear-algebra/3d/mat3.ts index cd577776b23b29e6abbd21049ae009ce42ce3a50..c72dc713dba2f2c51e76ae986e1f39474f09bdc5 100644 --- a/src/mol-math/linear-algebra/3d/mat3.ts +++ b/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 */ diff --git a/src/mol-math/linear-algebra/3d/mat4.ts b/src/mol-math/linear-algebra/3d/mat4.ts index 930eaf8518e425ff451b638830264510a8c5d97b..a23c5503950401af6c82307b0cbcbdce23bfbadd 100644 --- a/src/mol-math/linear-algebra/3d/mat4.ts +++ b/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; diff --git a/src/mol-math/linear-algebra/tensor.ts b/src/mol-math/linear-algebra/tensor.ts index 976e410e1b0fb96055c435cabd873cb868791be9..4bcae46f7bd0db8987d0036ed8590ba804cd1d9c 100644 --- a/src/mol-math/linear-algebra/tensor.ts +++ b/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(); diff --git a/src/mol-model/structure/_spec/atom-set.spec.ts b/src/mol-model/structure/_spec/atom-set.spec.ts deleted file mode 100644 index 60b208053c028b902523853e1a01d0587c0b027c..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/_spec/atom-set.spec.ts +++ /dev/null @@ -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)]); - }); -}); \ No newline at end of file diff --git a/src/mol-model/structure/export/mmcif.ts b/src/mol-model/structure/export/mmcif.ts index 3958a39faf09c85e7d07a2daca46530a396674df..d7f4491a64738c7009e719bd0672ce8d8741bb75 100644 --- a/src/mol-model/structure/export/mmcif.ts +++ b/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 } } diff --git a/src/mol-model/structure/model.ts b/src/mol-model/structure/model.ts index c53fd0986639873923cf30fbceba2d6010233741..1704ab09282eb09ec1e30a442532e52dc80def63 100644 --- a/src/mol-model/structure/model.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-model/structure/model/format.ts b/src/mol-model/structure/model/format.ts index 59f96c2a79fd06f442d5a439196d3fe54576a9f6..2fa1f8ae1880d87643f6923b8a266c6693a151f5 100644 --- a/src/mol-model/structure/model/format.ts +++ b/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 } } diff --git a/src/mol-model/structure/model/formats/gro.ts b/src/mol-model/structure/model/formats/gro.ts index 6e365bb3506446712b08ab3573ea92d8d66fc934..1e04fb1e7e065d2c62174e0301d1b693d4f61bca 100644 --- a/src/mol-model/structure/model/formats/gro.ts +++ b/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; \ No newline at end of file +// 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; diff --git a/src/mol-model/structure/model/formats/mmcif.ts b/src/mol-model/structure/model/formats/mmcif.ts index 33c09a598b84df5e8fb06d7ce4a4c2fca7c949bb..6f135c7209df51cbb0721bc969a8510205f0711e 100644 --- a/src/mol-model/structure/model/formats/mmcif.ts +++ b/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; \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/assembly.ts b/src/mol-model/structure/model/formats/mmcif/assembly.ts index 2315133d4cb83c7219207a9690f5b55ca148db9b..48720596c26741364f2acc6306ae47ba87ad3426 100644 --- a/src/mol-model/structure/model/formats/mmcif/assembly.ts +++ b/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 []; diff --git a/src/mol-model/structure/model/formats/mmcif/bonds.ts b/src/mol-model/structure/model/formats/mmcif/bonds.ts index 139a7a8117647b2e14ff66dd0c9c5ea9f04bcbaf..95ac32d4d36ffcebcc35064cd69e5d5b790f1ba9 100644 --- a/src/mol-model/structure/model/formats/mmcif/bonds.ts +++ b/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) { diff --git a/src/mol-model/structure/model/formats/mmcif/ihm.ts b/src/mol-model/structure/model/formats/mmcif/ihm.ts index d8590657156c97e3f38c1137792dcca2825a06aa..1dfdc1c1d36a15658b7c88da88f7741382596ad0 100644 --- a/src/mol-model/structure/model/formats/mmcif/ihm.ts +++ b/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 } \ No newline at end of file +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) }; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts b/src/mol-model/structure/model/formats/mmcif/secondary-structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..ed07db38bf79d75bc18c3382989e5a2ca14e8796 --- /dev/null +++ b/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); + } + } + } + } +} \ No newline at end of file diff --git a/src/mol-model/structure/model/formats/mmcif/sequence.ts b/src/mol-model/structure/model/formats/mmcif/sequence.ts index ccba6849b3719825cbe067a8d4eb5a7e45c2816e..3b30419696303332c6c91fec2fbc66b6de3f2451 100644 --- a/src/mol-model/structure/model/formats/mmcif/sequence.ts +++ b/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; diff --git a/src/mol-model/structure/model/formats/mmcif/util.ts b/src/mol-model/structure/model/formats/mmcif/util.ts index a03d511f54dd47643b4cc8b2189fdb532e994e7e..1817e1c22868410ce6529934aa279e3096654189 100644 --- a/src/mol-model/structure/model/formats/mmcif/util.ts +++ b/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; } diff --git a/src/mol-model/structure/model/model.ts b/src/mol-model/structure/model/model.ts index e685af863b4467eeb50f4668607cff258db6c3f2..1053bde91c2382c05f3a64445ae12facec884b62 100644 --- a/src/mol-model/structure/model/model.ts +++ b/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 \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/atomic.ts b/src/mol-model/structure/model/properties/atomic.ts index 38550af272dffcbb155f37312f24310f75ab095f..65b7bb2cbbf97f583911af6c54409be3f3e89718 100644 --- a/src/mol-model/structure/model/properties/atomic.ts +++ b/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 -} \ No newline at end of file +export * from './atomic/conformation' +export * from './atomic/hierarchy' +export * from './atomic/measures' \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/atom-site-conformation.ts b/src/mol-model/structure/model/properties/atomic/conformation.ts similarity index 93% rename from src/mol-model/structure/model/properties/atom-site-conformation.ts rename to src/mol-model/structure/model/properties/atomic/conformation.ts index ce94b8597d2d1920b469f86d3942898e403fa5de..fd99511a40461ec1ff316c5295cabb586382265f 100644 --- a/src/mol-model/structure/model/properties/atom-site-conformation.ts +++ b/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 \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/hierarchy.ts b/src/mol-model/structure/model/properties/atomic/hierarchy.ts similarity index 86% rename from src/mol-model/structure/model/properties/hierarchy.ts rename to src/mol-model/structure/model/properties/atomic/hierarchy.ts index d25903d95d0e4bd865b1969152902deea071c966..fe50e990b242ece24021f6f8eee514388e95d6da 100644 --- a/src/mol-model/structure/model/properties/hierarchy.ts +++ b/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 \ No newline at end of file +type _Hierarchy = AtomicData & AtomicSegments & AtomicKeys +export interface AtomicHierarchy extends _Hierarchy { } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/atomic/measures.ts b/src/mol-model/structure/model/properties/atomic/measures.ts new file mode 100644 index 0000000000000000000000000000000000000000..05aad28a9c16dbb67ffc6b1b10e00b86527ca574 --- /dev/null +++ b/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 +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse-grained.ts b/src/mol-model/structure/model/properties/coarse-grained.ts deleted file mode 100644 index 392f06a2ce24f82c7c59492d9597fa2a33f015ef..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/model/properties/coarse-grained.ts +++ /dev/null @@ -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; \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse.ts b/src/mol-model/structure/model/properties/coarse.ts new file mode 100644 index 0000000000000000000000000000000000000000..d1fc3decf25a10a9a54c5924d5c4cdfaa7ab556a --- /dev/null +++ b/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' \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse/conformation.ts b/src/mol-model/structure/model/properties/coarse/conformation.ts new file mode 100644 index 0000000000000000000000000000000000000000..4298b80bdb8fd043149520f2b5b50d56aae59212 --- /dev/null +++ b/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> +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/coarse/hierarchy.ts b/src/mol-model/structure/model/properties/coarse/hierarchy.ts new file mode 100644 index 0000000000000000000000000000000000000000..fedb463c9e7b015de3485ef2c2a90b2798df71ec --- /dev/null +++ b/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; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/common.ts b/src/mol-model/structure/model/properties/common.ts index aa5d93aaec136b39e51f2b22b1b5de3d6bde2c65..9dadbc95ac9d951e4d175949655012b0222a52a5 100644 --- a/src/mol-model/structure/model/properties/common.ts +++ b/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 } \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/seconday-structure.ts b/src/mol-model/structure/model/properties/seconday-structure.ts index 04eef7fb927781a87617b89a5d6b008e483ff527..dfde631a30ecb78ca0e854b5d9868b7861f953a0 100644 --- a/src/mol-model/structure/model/properties/seconday-structure.ts +++ b/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[] -} \ No newline at end of file + readonly key: ArrayLike<number> +} + +export { SecondaryStructure } \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/sequence.ts b/src/mol-model/structure/model/properties/sequence.ts index d7aa43951ea85c75921b0e4c9879870b73a56faa..b34b1ef61b624307682ef17e577b670444bbec36 100644 --- a/src/mol-model/structure/model/properties/sequence.ts +++ b/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'; diff --git a/src/mol-model/structure/model/properties/symmetry.ts b/src/mol-model/structure/model/properties/symmetry.ts index d459241b4a7ff1174933c8e410e4fa5e841eddad..3b2659e9e49541c16c1e47f8ae229905a0d1ca92 100644 --- a/src/mol-model/structure/model/properties/symmetry.ts +++ b/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 \ No newline at end of file +export { ModelSymmetry } \ No newline at end of file diff --git a/src/mol-model/structure/model/utils/hierarchy-keys.ts b/src/mol-model/structure/model/properties/utils/atomic-keys.ts similarity index 88% rename from src/mol-model/structure/model/utils/hierarchy-keys.ts rename to src/mol-model/structure/model/properties/utils/atomic-keys.ts index d70a26a94abdaeba5be60d137bccb7360d2b8820..1cfec2f0348e902f39787c3f2c0510687caef256 100644 --- a/src/mol-model/structure/model/utils/hierarchy-keys.ts +++ b/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; \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-model/structure/model/properties/utils/coarse-keys.ts b/src/mol-model/structure/model/properties/utils/coarse-keys.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ef72039a5f3fa35705364b38d24d2e4bef00553 --- /dev/null +++ b/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 + }; +} \ No newline at end of file diff --git a/src/mol-model/structure/model/utils/guess-element.ts b/src/mol-model/structure/model/properties/utils/guess-element.ts similarity index 100% rename from src/mol-model/structure/model/utils/guess-element.ts rename to src/mol-model/structure/model/properties/utils/guess-element.ts diff --git a/src/mol-model/structure/model/types.ts b/src/mol-model/structure/model/types.ts index caa1ee2c85af1c68e2bdca9a9e0fdd90de87b23b..36d0cb935f3631e5fd0057526443f753472090f6 100644 --- a/src/mol-model/structure/model/types.ts +++ b/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, diff --git a/src/mol-model/structure/query.ts b/src/mol-model/structure/query.ts index 55dff3a8e548d080534bb4f171d356689fbfc9b1..b5886cbda873b29ebfbf23534c64609b8f1197b2 100644 --- a/src/mol-model/structure/query.ts +++ b/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 } diff --git a/src/mol-model/structure/query/generators.ts b/src/mol-model/structure/query/generators.ts index 66c9f0415dd28f02c0f2ad74eac035ec09d305eb..0650a17ae0690e5147a54239991436c8eda6fde9 100644 --- a/src/mol-model/structure/query/generators.ts +++ b/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(); diff --git a/src/mol-model/structure/query/modifiers.ts b/src/mol-model/structure/query/modifiers.ts new file mode 100644 index 0000000000000000000000000000000000000000..3b332ff7fcfc9d142ae080968375a3c54af0eedb --- /dev/null +++ b/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(); + } + }; +} \ No newline at end of file diff --git a/src/mol-model/structure/query/properties.ts b/src/mol-model/structure/query/properties.ts index 98d22ce51ca05136bc70f0bf52c82399d8fbd400..3299acaaee21a426e2e3069845768e132b63aca3 100644 --- a/src/mol-model/structure/query/properties.ts +++ b/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 diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index f9f516710817ea7ae9d8c042457a7f81e2f53a2d..a1fc34f2a0e6f741a667aa97c2af2fcd7ec7a9c2 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/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) { } } diff --git a/src/mol-model/structure/query/utils/builders.ts b/src/mol-model/structure/query/utils/builders.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e1d6d7cfaea643c2a41942f3ff2bebc14b65cb3 --- /dev/null +++ b/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) { } +} \ No newline at end of file diff --git a/src/mol-model/structure/query/utils/structure.ts b/src/mol-model/structure/query/utils/structure.ts new file mode 100644 index 0000000000000000000000000000000000000000..f30fd20e67475592bf886154f0ceb6434e3854b5 --- /dev/null +++ b/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); +} \ No newline at end of file diff --git a/src/mol-model/structure/structure.ts b/src/mol-model/structure/structure.ts index 59f200b700ccd406f08a897a847e84b60acc7bee..c57bb2a47cb40b67ac59da9cd567439d728d49c7 100644 --- a/src/mol-model/structure/structure.ts +++ b/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 } \ No newline at end of file +export { Element, Structure, Unit, StructureSymmetry } \ No newline at end of file diff --git a/src/mol-model/structure/structure/element.ts b/src/mol-model/structure/structure/element.ts index f730ed988d5fe55d1ace4a16eac6cc82331243c0..e62dcf1333a3585005a1075391a2314152e4f21d 100644 --- a/src/mol-model/structure/structure/element.ts +++ b/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; } diff --git a/src/mol-model/structure/structure/element/group.ts b/src/mol-model/structure/structure/element/group.ts deleted file mode 100644 index e89b3d37e87b6611a0630fe4978fe1218ceaa76b..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/element/group.ts +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/impl/properties.ts b/src/mol-model/structure/structure/element/impl/properties.ts deleted file mode 100644 index 03ae27d163b402e0026bcfefd54bd706739187a0..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/element/impl/properties.ts +++ /dev/null @@ -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? \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/impl/set-builder.ts b/src/mol-model/structure/structure/element/impl/set-builder.ts deleted file mode 100644 index 28956aa832d6d267856f512e00984a1452b0bdcc..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/element/impl/set-builder.ts +++ /dev/null @@ -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); -} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/impl/set.ts b/src/mol-model/structure/structure/element/impl/set.ts deleted file mode 100644 index 2aaf3182e5f1ea62f0c313357d3b48d6b8a5dd4c..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/element/impl/set.ts +++ /dev/null @@ -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)); - } - } -} \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/set.ts b/src/mol-model/structure/structure/element/set.ts deleted file mode 100644 index dddebe8bfd28b73505e0c93d18840a68087d83d4..0000000000000000000000000000000000000000 --- a/src/mol-model/structure/structure/element/set.ts +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/mol-model/structure/structure/structure.ts b/src/mol-model/structure/structure/structure.ts index 6572e03e1ea8988ac436a44c2259fc1223a988d9..79b21a33945594527cc5b649acd99bd15787c30f 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/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 \ No newline at end of file diff --git a/src/mol-model/structure/structure/symmetry.ts b/src/mol-model/structure/structure/symmetry.ts index 7fe2fb2bb874e1402537df5ec312c3df5677e5ed..0a912a4ab1ab61f8d08cb533ca052e634a81e538 100644 --- a/src/mol-model/structure/structure/symmetry.ts +++ b/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(); - }); -} \ No newline at end of file + return ret; + } +} + +export default StructureSymmetry; \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit.ts b/src/mol-model/structure/structure/unit.ts index 596dd3580cddfa4d32aaee7cfaae507e1d6925dd..5c34fd555248e6b3c2cf752446930b7d47116f6e 100644 --- a/src/mol-model/structure/structure/unit.ts +++ b/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; \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/bonds.ts b/src/mol-model/structure/structure/unit/bonds.ts new file mode 100644 index 0000000000000000000000000000000000000000..6316c3eebbdd5a53cc1c3a9b8fd85c6a6cf4dd15 --- /dev/null +++ b/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' \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/properties/bonds/group-compute.ts b/src/mol-model/structure/structure/unit/bonds/intra-compute.ts similarity index 93% rename from src/mol-model/structure/structure/element/properties/bonds/group-compute.ts rename to src/mol-model/structure/structure/unit/bonds/intra-compute.ts index a75540509204cff433c98804911a2cb9bfe7961a..9cd4a9ed1839ec43517ce9e9c46ecb06eb8d31f7 100644 --- a/src/mol-model/structure/structure/element/properties/bonds/group-compute.ts +++ b/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 } \ No newline at end of file +export { computeIntraUnitBonds } \ No newline at end of file diff --git a/src/mol-model/structure/structure/element/properties/bonds/group-data.ts b/src/mol-model/structure/structure/unit/bonds/intra-data.ts similarity index 88% rename from src/mol-model/structure/structure/element/properties/bonds/group-data.ts rename to src/mol-model/structure/structure/unit/bonds/intra-data.ts index 0406f05d326db903c081be750001b069b4a89578..c897e6ca63ed8dff5071f1c52def66e41f3bf935 100644 --- a/src/mol-model/structure/structure/element/properties/bonds/group-data.ts +++ b/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 } \ No newline at end of file +export { IntraUnitBonds } \ No newline at end of file diff --git a/src/mol-model/structure/structure/util/boundary.ts b/src/mol-model/structure/structure/util/boundary.ts index 547d3828eb2f892bcdb9b8b620cebe36fc0a0610..2a595f8cd7551a060e19d12388e7ad222f55dfd7 100644 --- a/src/mol-model/structure/structure/util/boundary.ts +++ b/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; diff --git a/src/mol-model/structure/structure/util/lookup3d.ts b/src/mol-model/structure/structure/util/lookup3d.ts index bb7230600205a39c78beeff7ac044b2d99a95d8d..beb7c150a40cc72668c78134543d78c80342d41a 100644 --- a/src/mol-model/structure/structure/util/lookup3d.ts +++ b/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 } \ No newline at end of file + 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) }); + } +} \ No newline at end of file diff --git a/src/mol-model/structure/structure/util/subset-builder.ts b/src/mol-model/structure/structure/util/subset-builder.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c143774272e1e4350a90886289aa4c14e70adf0 --- /dev/null +++ b/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) { + + } +} diff --git a/src/mol-model/structure/structure/util/unique-subset-builder.ts b/src/mol-model/structure/structure/util/unique-subset-builder.ts new file mode 100644 index 0000000000000000000000000000000000000000..bc18e6519217570f2bb6e6b8097e5baa3e2d6f96 --- /dev/null +++ b/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) { + + } +} diff --git a/src/mol-task/execution/observable.ts b/src/mol-task/execution/observable.ts index 82d4e05a43917365d625115cdd1b13c072346d88..81fe04efb346a3ba3d125761e8487299826077cd 100644 --- a/src/mol-task/execution/observable.ts +++ b/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 } \ No newline at end of file +} \ No newline at end of file diff --git a/src/mol-task/execution/runtime-context.ts b/src/mol-task/execution/runtime-context.ts index d2529a3eb3599d9ece65c7183b7a36cdc0117b33..d0469abcfe65c408f321087e40170791c19c4176 100644 --- a/src/mol-task/execution/runtime-context.ts +++ b/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 { diff --git a/src/mol-task/execution/synchronous.ts b/src/mol-task/execution/synchronous.ts index 4ca40a04a5ccc91024b08ca3bcf6093ac8643e34..5c729a61097f58d47d00619901f00487dcc282df 100644 --- a/src/mol-task/execution/synchronous.ts +++ b/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 } \ No newline at end of file +export const SyncRuntimeContext = new SynchronousRuntimeContext(); \ No newline at end of file diff --git a/src/mol-task/index.ts b/src/mol-task/index.ts index 830153c1cfaa3cd6512b083dd988b90b825bdb08..2ef6489c9fffe85d67739274d1aab599dae14de9 100644 --- a/src/mol-task/index.ts +++ b/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 } \ No newline at end of file +export { Task, RuntimeContext, Progress, now, Scheduler, MultistepTask, chunkedSubtask } \ No newline at end of file diff --git a/src/mol-task/task.ts b/src/mol-task/task.ts index 0ce5ca40bf65ef59de655b3ecdf39d4a2b12abec..ee5474fa9175064d06a0ed97e0e4fa9230de8ca5 100644 --- a/src/mol-task/task.ts +++ b/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); } diff --git a/src/mol-util/bit-flags.ts b/src/mol-util/bit-flags.ts index 0cf3b761d5c4297081fab1eb9cd7c340e80fe99b..8143eb160bcbc0a1268cb3ddb9ef56d094d2d6e8 100644 --- a/src/mol-util/bit-flags.ts +++ b/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; } diff --git a/src/mol-view/state/transform.ts b/src/mol-view/state/transform.ts index fe02007907f7b1dbbf84790599ec9c25b7e5edaa..0389fc0646d088a592c9292fb36e813397373aa6 100644 --- a/src/mol-view/state/transform.ts +++ b/src/mol-view/state/transform.ts @@ -6,12 +6,12 @@ import CIF from 'mol-io/reader/cif' import { FileEntity, DataEntity, UrlEntity, CifEntity, MmcifEntity, ModelEntity, StructureEntity, SpacefillEntity, AnyEntity } from './entity'; -import { Run } from 'mol-task'; -import { Model, Structure, Symmetry } from 'mol-model/structure'; +import { Model, Structure } from 'mol-model/structure'; import { StateContext } from './context'; import Spacefill, { SpacefillProps } from 'mol-geo/representation/structure/spacefill'; import { StructureRepresentation } from 'mol-geo/representation/structure'; +import StructureSymmetry from 'mol-model/structure/structure/symmetry'; type transformer<I extends AnyEntity, O extends AnyEntity, P extends {}> = (ctx: StateContext, inputEntity: I, props?: P) => Promise<O> @@ -46,7 +46,7 @@ export type DataToCif = StateTransform<DataEntity, CifEntity, {}> export const DataToCif: DataToCif = StateTransform.create('data', 'cif', 'data-to-cif', async function (ctx: StateContext, dataEntity: DataEntity) { const comp = CIF.parse(dataEntity.value.data) - const parsed = await Run(comp, ctx.log) + const parsed = await comp.run(ctx.log) if (parsed.isError) throw parsed return CifEntity.ofCifFile(ctx, parsed.result) }) @@ -60,7 +60,8 @@ export const CifToMmcif: CifToMmcif = StateTransform.create('cif', 'mmcif', 'cif export type MmcifToModel = StateTransform<MmcifEntity, ModelEntity, {}> export const MmcifToModel: MmcifToModel = StateTransform.create('mmcif', 'model', 'mmcif-to-model', async function (ctx: StateContext, mmcifEntity: MmcifEntity) { - return ModelEntity.ofModels(ctx, Model.create({ kind: 'mmCIF', data: mmcifEntity.value })) + const models = await Model.create({ kind: 'mmCIF', data: mmcifEntity.value }).run(ctx.log) + return ModelEntity.ofModels(ctx, models) }) export interface StructureProps { @@ -75,7 +76,7 @@ export const ModelToStructure: ModelToStructure = StateTransform.create('model', let structure: Structure const assemblies = model.symmetry.assemblies if (assemblies.length) { - structure = await Run(Symmetry.buildAssembly(Structure.ofModel(model), assembly || '1'), ctx.log) + structure = await StructureSymmetry.buildAssembly(Structure.ofModel(model), assembly || '1').run(ctx.log) } else { structure = Structure.ofModel(model) } @@ -86,7 +87,7 @@ export type StructureToSpacefill = StateTransform<StructureEntity, SpacefillEnti export const StructureToSpacefill: StructureToSpacefill = StateTransform.create('structure', 'spacefill', 'structure-to-spacefill', async function (ctx: StateContext, structureEntity: StructureEntity, props: SpacefillProps = {}) { const spacefillRepr = StructureRepresentation(Spacefill) - await Run(spacefillRepr.create(structureEntity.value, props), ctx.log) + await spacefillRepr.create(structureEntity.value, props).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.requestDraw() console.log(ctx.viewer.stats) @@ -98,7 +99,7 @@ export const SpacefillUpdate: SpacefillUpdate = StateTransform.create('spacefill async function (ctx: StateContext, spacefillEntity: SpacefillEntity, props: SpacefillProps = {}) { console.log('fopbar') const spacefillRepr = spacefillEntity.value - await Run(spacefillRepr.update(props), ctx.log) + await spacefillRepr.update(props).run(ctx.log) ctx.viewer.add(spacefillRepr) ctx.viewer.update() ctx.viewer.requestDraw() diff --git a/src/mol-view/util.ts b/src/mol-view/util.ts index cf9343d2450dfca85f2045008d054f939240c0eb..0fa3e82b42a181d4d3dd9f77aabce3007c2c87b8 100644 --- a/src/mol-view/util.ts +++ b/src/mol-view/util.ts @@ -5,7 +5,7 @@ */ import CIF from 'mol-io/reader/cif' -import { Run, Progress } from 'mol-task' +import { Progress } from 'mol-task' import { VolumeData, parseDensityServerData } from 'mol-model/volume' import { DensityServer_Data_Database } from 'mol-io/reader/cif/schema/density-server'; @@ -16,7 +16,7 @@ export async function downloadCif(url: string, isBinary: boolean) { export async function parseCif(data: string|Uint8Array) { const comp = CIF.parse(data) - const parsed = await Run(comp, Progress.format); + const parsed = await comp.run(Progress.format); if (parsed.isError) throw parsed; return parsed.result } @@ -26,7 +26,7 @@ export type Volume = { source: DensityServer_Data_Database, volume: VolumeData } export async function getVolumeFromEmdId(emdid: string): Promise<Volume> { const cif = await downloadCif(`https://webchem.ncbr.muni.cz/DensityServer/em/emd-${emdid}/cell?detail=4`, 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() } } export function resizeCanvas (canvas: HTMLCanvasElement, container: Element) { diff --git a/src/perf-tests/lookup3d.ts b/src/perf-tests/lookup3d.ts index 3875a8c138aa79364204574cebb6b97d2c9a17b0..3b52eee9f0e027d48f6113ae3551a3d42e922038 100644 --- a/src/perf-tests/lookup3d.ts +++ b/src/perf-tests/lookup3d.ts @@ -4,7 +4,6 @@ import CIF from 'mol-io/reader/cif' import { Structure, Model } from 'mol-model/structure' -import { Run } from 'mol-task'; import { GridLookup3D } from 'mol-math/geometry'; // import { sortArray } from 'mol-data/util'; import { OrderedSet } from 'mol-data/int'; @@ -27,14 +26,14 @@ async function readData(path: string) { export async function readCIF(path: string) { const input = await readData(path) const comp = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input); - const parsed = await Run(comp); + const parsed = await comp.run(); if (parsed.isError) { throw parsed; } const data = parsed.result.blocks[0]; const mmcif = CIF.schema.mmCIF(data); - const models = Model.create({ kind: 'mmCIF', data: mmcif }); + const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run(); const structures = models.map(Structure.ofModel); return { mmcif, models, structures }; diff --git a/src/perf-tests/sets.ts b/src/perf-tests/sets.ts index fc582759279cf4bf2dd28705a5b9fc5663cf7b71..bd38cf542c8bd4b26050d3bf7d6b9a836af3574d 100644 --- a/src/perf-tests/sets.ts +++ b/src/perf-tests/sets.ts @@ -1,6 +1,6 @@ import * as B from 'benchmark' import { Tuple, Segmentation, OrderedSet as OrdSet } from 'mol-data/int' -import { ElementSet } from 'mol-model/structure' +//import { ElementSet } from 'mol-model/structure' // export namespace Iteration { // const U = 1000, V = 2500; @@ -198,39 +198,39 @@ export namespace Union { } } -export namespace Build { - function createSorted() { - const b = ElementSet.LinearBuilder(ElementSet.Empty); - for (let i = 0; i < 10; i++) { - for (let j = 0; j < 1000; j++) { - b.add(i, j); - } - } - return b.getSet(); - } +// export namespace Build { +// function createSorted() { +// const b = ElementSet.LinearBuilder(ElementSet.Empty); +// for (let i = 0; i < 10; i++) { +// for (let j = 0; j < 1000; j++) { +// b.add(i, j); +// } +// } +// return b.getSet(); +// } - function createByUnit() { - const b = ElementSet.LinearBuilder(ElementSet.Empty); - for (let i = 0; i < 10; i++) { - b.beginUnit(); - for (let j = 0; j < 1000; j++) { - b.addToUnit(j); - } - b.commitUnit(i); - } - return b.getSet(); - } +// function createByUnit() { +// const b = ElementSet.LinearBuilder(ElementSet.Empty); +// for (let i = 0; i < 10; i++) { +// b.beginUnit(); +// for (let j = 0; j < 1000; j++) { +// b.addToUnit(j); +// } +// b.commitUnit(i); +// } +// return b.getSet(); +// } - export function run() { - const suite = new B.Suite(); - suite - .add('create sorted', () => createSorted()) - .add('create by unit', () => createByUnit()) - .on('cycle', (e: any) => console.log(String(e.target))) - .run(); - } -} +// export function run() { +// const suite = new B.Suite(); +// suite +// .add('create sorted', () => createSorted()) +// .add('create by unit', () => createByUnit()) +// .on('cycle', (e: any) => console.log(String(e.target))) +// .run(); +// } +// } export namespace Tuples { function createData(n: number) { diff --git a/src/perf-tests/structure.ts b/src/perf-tests/structure.ts index 03f04a2333d8fcfcc8af2b31f9b2b7cc02036255..b2fea5b987fa7e53b58545ada38256da9a355dea 100644 --- a/src/perf-tests/structure.ts +++ b/src/perf-tests/structure.ts @@ -11,12 +11,13 @@ import * as fs from 'fs' import fetch from 'node-fetch' import CIF from 'mol-io/reader/cif' -import { Structure, Model, Queries as Q, Element, ElementGroup, ElementSet, Selection, Symmetry, Unit, Query } from 'mol-model/structure' -import { Segmentation, OrderedSet } from 'mol-data/int' +import { Structure, Model, Queries as Q, Element, Selection, StructureSymmetry, Query } from 'mol-model/structure' +//import { Segmentation, OrderedSet } from 'mol-data/int' import to_mmCIF from 'mol-model/structure/export/mmcif' -import { Run } from 'mol-task'; -import { EquivalenceClasses } from 'mol-data/util'; +import { Vec3 } from 'mol-math/linear-algebra'; +//import { printUnits } from 'apps/structure-info/model'; +//import { EquivalenceClasses } from 'mol-data/util'; require('util.promisify').shim(); const readFileAsync = util.promisify(fs.readFile); @@ -61,7 +62,7 @@ export async function readCIF(path: string) { console.time('parse'); const comp = typeof input === 'string' ? CIF.parseText(input) : CIF.parseBinary(input); - const parsed = await Run(comp); + const parsed = await comp.run(); console.timeEnd('parse'); if (parsed.isError) { throw parsed; @@ -73,7 +74,7 @@ export async function readCIF(path: string) { console.timeEnd('schema') console.time('buildModels') - const models = Model.create({ kind: 'mmCIF', data: mmcif }); + const models = await Model.create({ kind: 'mmCIF', data: mmcif }).run(); console.timeEnd('buildModels') const structures = models.map(Structure.ofModel); @@ -119,19 +120,14 @@ export namespace PropertyAccess { } function sumProperty(structure: Structure, p: Element.Property<number>) { - const { elements, units } = structure; - const unitIds = ElementSet.unitIndices(elements); const l = Element.Location(); - let s = 0; - for (let i = 0, _i = unitIds.length; i < _i; i++) { - l.unit = units[unitIds[i]]; - const set = ElementSet.groupAt(elements, i); - - - for (let j = 0, _j = ElementGroup.size(set); j < _j; j++) { - l.element= ElementGroup.getAt(set, j); + for (const unit of structure.units) { + l.unit = unit; + const elements = unit.elements; + for (let j = 0, _j = elements.length; j < _j; j++) { + l.element = elements[j]; s += p(l); } } @@ -139,44 +135,44 @@ export namespace PropertyAccess { return s; } - function sumPropertySegmented(structure: Structure, p: Element.Property<number>) { - const { elements, units } = structure; - const unitIds = ElementSet.unitIndices(elements); - const l = Element.Location(); + // function sumPropertySegmented(structure: Structure, p: Element.Property<number>) { + // const { elements, units } = structure; + // const unitIds = ElementSet.unitIndices(elements); + // const l = Element.Location(); - let s = 0; + // let s = 0; - let vA = 0, cC = 0, rC = 0; - for (let i = 0, _i = unitIds.length; i < _i; i++) { - const unit = units[unitIds[i]] as Unit.Atomic; - l.unit = unit; - const set = ElementSet.groupAt(elements, i); - - const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.elements); - const residues = unit.hierarchy.residueSegments; - while (chainsIt.hasNext) { - cC++; - - const chainSegment = chainsIt.move(); - const residuesIt = Segmentation.transientSegments(residues, set.elements, chainSegment); - while (residuesIt.hasNext) { - rC++; - const residueSegment = residuesIt.move(); - // l.element= OrdSet.getAt(set, residueSegment.start); - // console.log(unit.hierarchy.residues.auth_comp_id.value(unit.residueIndex[l.atom]), l.atom, OrdSet.getAt(set, residueSegment.end)) - for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { - l.element= ElementGroup.getAt(set, j); - vA++; - s += p(l); - } - } - } - } + // let vA = 0, cC = 0, rC = 0; + // for (let i = 0, _i = unitIds.length; i < _i; i++) { + // const unit = units[unitIds[i]] as Unit.Atomic; + // l.unit = unit; + // const set = ElementSet.groupAt(elements, i); - console.log('seg atom count', vA, cC, rC); + // const chainsIt = Segmentation.transientSegments(unit.hierarchy.chainSegments, set.elements); + // const residues = unit.hierarchy.residueSegments; + // while (chainsIt.hasNext) { + // cC++; - return s; - } + // const chainSegment = chainsIt.move(); + // const residuesIt = Segmentation.transientSegments(residues, set.elements, chainSegment); + // while (residuesIt.hasNext) { + // rC++; + // const residueSegment = residuesIt.move(); + // // l.element= OrdSet.getAt(set, residueSegment.start); + // // console.log(unit.hierarchy.residues.auth_comp_id.value(unit.residueIndex[l.atom]), l.atom, OrdSet.getAt(set, residueSegment.end)) + // for (let j = residueSegment.start, _j = residueSegment.end; j < _j; j++) { + // l.element= ElementGroup.getAt(set, j); + // vA++; + // s += p(l); + // } + // } + // } + // } + + // console.log('seg atom count', vA, cC, rC); + + // return s; + // } // function sumPropertyResidue(structure: Structure, p: Element.Property<number>) { // const { atoms, units } = structure; @@ -199,20 +195,20 @@ export namespace PropertyAccess { // return s; // } - function sumPropertyAtomSetIt(structure: Structure, p: Element.Property<number>) { - const { elements, units } = structure; + // function sumPropertyAtomSetIt(structure: Structure, p: Element.Property<number>) { + // const { elements, units } = structure; - let s = 0; - const atomsIt = ElementSet.elements(elements); - const l = Element.Location(); - while (atomsIt.hasNext) { - const a = atomsIt.move(); - l.unit = units[Element.unit(a)]; - l.element= Element.index(a); - s += p(l); - } - return s; - } + // let s = 0; + // const atomsIt = ElementSet.elements(elements); + // const l = Element.Location(); + // while (atomsIt.hasNext) { + // const a = atomsIt.move(); + // l.unit = units[Element.unit(a)]; + // l.element= Element.index(a); + // s += p(l); + // } + // return s; + // } // function sumPropertySegmentedMutable(structure: Structure, p: Property<number>) { // const { atoms, units } = structure; @@ -296,51 +292,107 @@ export namespace PropertyAccess { export async function testAssembly(id: string, s: Structure) { console.time('assembly') - const a = await Run(Symmetry.buildAssembly(s, '1')); + const a = await StructureSymmetry.buildAssembly(s, '1').run(); + //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('assembly') fs.writeFileSync(`${DATA_DIR}/${id}_assembly.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); - console.log('built asm'); - - const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>( - ({ unit, group }) => ElementGroup.hashCode(group), - (a, b) => a.unit.model.id === b.unit.model.id && (a.group.key === b.group.key && OrderedSet.areEqual(a.group.elements, b.group.elements)) - ); + export async function testSymmetry(id: string, s: Structure) { + console.time('symmetry') + const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-1, -1, -1), Vec3.create(1, 1, 1)).run(); + //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); - for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) { - const group = ElementSet.groupAt(elements, i); - const unitId = ElementSet.groupUnitIndex(elements, i); - uniqueGroups.add(unitId, { unit: units[unitId], group }); - } + 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'); + } - console.log('group count', uniqueGroups.groups.length); + export async function testIncludeSurroundings(id: string, s: Structure) { + //const a = s; + console.time('symmetry') + const a = await StructureSymmetry.buildSymmetryRange(s, Vec3.create(-2, -2, -2), Vec3.create(2, 2, 2)).run(); + //console.log(printUnits(a)); + + const auth_comp_id = Q.props.residue.auth_comp_id, op = Q.props.unit.operator_name; + //const q1 = Q.generators.atoms({ residueTest: l => auth_comp_id(l) === 'REA' }); + const q1 = Q.modifiers.includeSurroundings(Q.generators.atoms({ + chainTest: l => op(l) === '1_555', + residueTest: l => auth_comp_id(l) === 'REA' + }), { + radius: 5, + wholeResidues: true + }); + const surr = Selection.unionStructure(await query(Query(q1), a)); + console.timeEnd('symmetry') + + // for (const u of surr.units) { + // const { atomId } = u.model.atomicConformation; + // console.log(`${u.id}, ${u.conformation.operator.name}`); + // for (let i = 0; i < u.elements.length; i++) { + // console.log(` ${atomId.value(u.elements[i])}`); + // } + // } + + // const it = surr.elementLocations(); + // while (it.hasNext) { + // const e = it.move(); + // console.log(`${Q.props.unit.operator_name(e)} ${Q.props.atom.id(e)}`); + // } + //fs.writeFileSync(`${DATA_DIR}/${id}_surr.bcif`, to_mmCIF(id, a, true)); + fs.writeFileSync(`${DATA_DIR}/${id}_surr.cif`, to_mmCIF(id, surr, false)); + console.log('exported'); } + // export async function testGrouping(structure: Structure) { + // const { elements, units } = await Run(Symmetry.buildAssembly(structure, '1')); + // console.log('grouping', units.length); + // console.log('built asm'); + + // const uniqueGroups = EquivalenceClasses<number, { unit: Unit, group: ElementGroup }>( + // ({ unit, group }) => ElementGroup.hashCode(group), + // (a, b) => a.unit.model.id === b.unit.model.id && (a.group.key === b.group.key && OrderedSet.areEqual(a.group.elements, b.group.elements)) + // ); + + // for (let i = 0, _i = ElementSet.groupCount(elements); i < _i; i++) { + // const group = ElementSet.groupAt(elements, i); + // const unitId = ElementSet.groupUnitIndex(elements, i); + // uniqueGroups.add(unitId, { unit: units[unitId], group }); + // } + + // console.log('group count', uniqueGroups.groups.length); + // } + function query(q: Query, s: Structure) { - return Run((q(s))); + return q(s).run(); } export async function run() { //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: s1, /*, mmcif*/ } = await readCIF('e:/test/quick/1tqn_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]); - console.log('------'); - testGrouping(s1[0]); + // testGrouping(structures[0]); + // console.log('------'); + // testGrouping(s1[0]); //const { structures, models/*, mmcif*/ } = await readCIF('e:/test/quick/5j7v_updated.cif'); //console.log(mmcif.pdbx_struct_oper_list.matrix.toArray()); // console.log(mmcif.pdbx_struct_oper_list.vector.toArray()); - // testAssembly('5j7v', structures[0]); + //await testAssembly('1hrv', structures[0]); + //await testSymmetry('1cbs', structures[0]); + await testIncludeSurroundings('1cbs', structures[0]); // throw ''; // console.log(models[0].symmetry.assemblies); @@ -356,11 +408,11 @@ export namespace PropertyAccess { // return; console.log('bs', baseline(models[0])); - console.log('sp', sumProperty(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); - console.log(sumPropertySegmented(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); + console.log('sp', sumProperty(structures[0], l => l.unit.model.atomicConformation.atomId.value(l.element))); + //console.log(sumPropertySegmented(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); //console.log(sumPropertySegmentedMutable(structures[0], l => l.unit.model.conformation.atomId.value(l.element)); - console.log(sumPropertyAtomSetIt(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); + //console.log(sumPropertyAtomSetIt(structures[0], l => l.unit.model.atomSiteConformation.atomId.value(l.element))); //console.log(sumProperty(structures[0], Property.cachedAtomColumn(m => m.conformation.atomId))); //console.log(sumDirect(structures[0])); //console.log('r', sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom]))); @@ -403,14 +455,14 @@ export namespace PropertyAccess { console.log(Selection.structureCount(q2r)); //console.log(q1(structures[0])); - //const col = models[0].conformation.atomId.value; + const col = models[0].atomicConformation.atomId.value; const suite = new B.Suite(); suite //.add('test q', () => q1(structures[0])) //.add('test q', () => q(structures[0])) - .add('test q1', async () => await q1(structures[0])) - .add('test q3', async () => await q3(structures[0])) - //.add('test int', () => sumProperty(structures[0], l => col(l.element)) + .add('test int', () => sumProperty(structures[0], l => col(l.element))) + .add('test q1', async () => await query(q1, structures[0])) + .add('test q3', async () => await query(q3, structures[0])) // .add('sum residue', () => sumPropertyResidue(structures[0], l => l.unit.hierarchy.residues.auth_seq_id.value(l.unit.residueIndex[l.atom]))) // .add('baseline', () => baseline(models[0]))