diff --git a/src/mol-model/structure/query/context.ts b/src/mol-model/structure/query/context.ts index 188aac0e74467e0fe384765424ed0ee118fa1c09..772fae563744ef4243990e06bfa840aaafae1674 100644 --- a/src/mol-model/structure/query/context.ts +++ b/src/mol-model/structure/query/context.ts @@ -15,9 +15,11 @@ export interface QueryContextView { export class QueryContext implements QueryContextView { private currentElementStack: StructureElement[] = []; private currentStructureStack: Structure[] = []; + private inputStructureStack: Structure[] = []; + private timeCreated = now(); - private timeoutMs: number; + readonly timeoutMs: number; readonly inputStructure: Structure; /** Current element */ @@ -43,6 +45,16 @@ export class QueryContext implements QueryContextView { else (this.currentStructure as Structure) = void 0 as any; } + pushInputStructure(structure: Structure) { + this.inputStructureStack.push(this.inputStructure); + (this.inputStructure as any) = structure; + } + + popInputStructure() { + if (this.inputStructureStack.length === 0) throw new Error('Must push before pop.'); + (this.inputStructure as any) = this.inputStructureStack.pop(); + } + throwIfTimedOut() { if (this.timeoutMs === 0) return; if (now() - this.timeCreated > this.timeoutMs) { @@ -50,8 +62,6 @@ export class QueryContext implements QueryContextView { } } - // todo timeout - constructor(structure: Structure, timeoutMs = 0) { this.inputStructure = structure; this.timeoutMs = timeoutMs; diff --git a/src/mol-model/structure/query/queries/combinators.ts b/src/mol-model/structure/query/queries/combinators.ts index 05adcf3983414635adceb1f139cf3198d16c1268..7bd531bd341446c61b474621f8f44c94d17d9446 100644 --- a/src/mol-model/structure/query/queries/combinators.ts +++ b/src/mol-model/structure/query/queries/combinators.ts @@ -7,6 +7,8 @@ import { StructureQuery } from '../query'; import { StructureSelection } from '../selection'; import { none } from './generators'; +import { HashSet } from 'mol-data/generic'; +import { Structure } from '../../structure'; export function merge(queries: ArrayLike<StructureQuery>): StructureQuery { if (queries.length === 0) { @@ -26,4 +28,40 @@ export function merge(queries: ArrayLike<StructureQuery>): StructureQuery { } } -// TODO: intersect, distanceCluster \ No newline at end of file +export function intersect(queries: ArrayLike<StructureQuery>): StructureQuery { + if (queries.length === 0) { + return none; + } else if (queries.length === 1) { + return queries[0]; + } + return ctx => { + const selections: StructureSelection[] = []; + for (let i = 0; i < queries.length; i++) selections.push(queries[i](ctx)); + let pivotIndex = 0, pivotLength = StructureSelection.structureCount(selections[0]); + for (let i = 1; i < selections.length; i++) { + const len = StructureSelection.structureCount(selections[i]); + if (len < pivotLength) { + pivotIndex = i; + pivotLength = len; + } + } + + ctx.throwIfTimedOut(); + const pivotSet = HashSet<Structure>(s => s.hashCode, Structure.areEqual); + StructureSelection.forEach(selections[pivotIndex], s => pivotSet.add(s)); + + const ret = StructureSelection.UniqueBuilder(ctx.inputStructure); + + for (let pI = 0; pI < selections.length; pI++) { + if (pI === pivotIndex) continue; + StructureSelection.forEach(selections[pI], s => { + if (pivotSet.has(s)) ret.add(s); + }); + ctx.throwIfTimedOut(); + } + + return ret.getSelection(); + }; +} + +// TODO: distanceCluster \ No newline at end of file diff --git a/src/mol-model/structure/query/queries/filters.ts b/src/mol-model/structure/query/queries/filters.ts index 49ca5dc201f31af2bb1cf9575f7bde6453b10313..06f8ce8a4a84e65a3b68bb40841af3066f72589e 100644 --- a/src/mol-model/structure/query/queries/filters.ts +++ b/src/mol-model/structure/query/queries/filters.ts @@ -10,6 +10,7 @@ import { QueryContext, QueryFn, QueryPredicate } from '../context'; import { StructureQuery } from '../query'; import { StructureSelection } from '../selection'; import { structureAreIntersecting } from '../utils/structure'; +import { Vec3 } from 'mol-math/linear-algebra'; export function pick(query: StructureQuery, pred: QueryPredicate): StructureQuery { return ctx => { @@ -101,4 +102,93 @@ export function areIntersectedBy(query: StructureQuery, by: StructureQuery): Str }; } -// TODO: within, isConnectedTo \ No newline at end of file +export interface WithinParams { + query: StructureQuery, + target: StructureQuery, + minRadius?: number, + maxRadius: number, + atomRadius?: QueryFn<number>, + invert?: boolean +} + +function _zeroRadius(ctx: QueryContext) { return 0; } + +export function within(params: WithinParams): StructureQuery { + return queryCtx => { + const ctx: WithinContext = { + queryCtx, + selection: params.query(queryCtx), + target: params.target(queryCtx), + maxRadius: params.maxRadius, + minRadius: params.minRadius ? params.minRadius : 0, + atomRadius: params.atomRadius || _zeroRadius, + invert: !!params.invert, + } + + if (ctx.minRadius === 0 && ctx.atomRadius === _zeroRadius) { + return withinMaxRadius(ctx); + } else { + // TODO + throw 'not implemented'; + // return withinMinMaxRadius(ctx); + } + } +} + +interface WithinContext { + queryCtx: QueryContext, + selection: StructureSelection, + target: StructureSelection, + minRadius: number, + maxRadius: number, + invert: boolean, + atomRadius: QueryFn<number> +} +function withinMaxRadius({ queryCtx, selection, target, maxRadius, invert }: WithinContext) { + const targetLookup = StructureSelection.unionStructure(target).lookup3d; + const ret = StructureSelection.LinearBuilder(queryCtx.inputStructure); + + const pos = Vec3.zero(); + StructureSelection.forEach(selection, (s, sI) => { + const { units } = s; + + let withinRadius = false; + for (let i = 0, _i = units.length; i < _i; i++) { + const unit = units[i]; + const { elements, conformation } = unit; + + switch (unit.kind) { + case Unit.Kind.Atomic: + // TODO: assign radius to gaussian elements? + case Unit.Kind.Gaussians: + for (let i = 0, _i = elements.length; i < _i; i++) { + conformation.position(elements[i], pos); + if (targetLookup.check(pos[0], pos[1], pos[2], maxRadius)) { + withinRadius = true; + break; + } + } + break; + case Unit.Kind.Spheres: + const radius = unit.coarseConformation.radius; + for (let i = 0, _i = elements.length; i < _i; i++) { + conformation.position(elements[i], pos); + if (targetLookup.check(pos[0], pos[1], pos[2], maxRadius + radius[elements[i]])) { + withinRadius = true; + break; + } + } + break; + } + if (withinRadius) break; + } + if (invert) withinRadius = !withinRadius; + if (withinRadius) ret.add(s); + if (sI % 10 === 0) queryCtx.throwIfTimedOut(); + }); + + return ret.getSelection(); +} + + +// TODO: isConnectedTo \ No newline at end of file diff --git a/src/mol-model/structure/query/queries/generators.ts b/src/mol-model/structure/query/queries/generators.ts index f8ba1c17f37084ff7518e817ca40e4451d6cde35..d8a465e5c13b1efbb7502612cd76099621db4bc8 100644 --- a/src/mol-model/structure/query/queries/generators.ts +++ b/src/mol-model/structure/query/queries/generators.ts @@ -7,9 +7,14 @@ import { StructureQuery } from '../query' import { StructureSelection } from '../selection' import { Unit, StructureProperties as P } from '../../structure' -import { Segmentation } from 'mol-data/int' +import { Segmentation, SortedArray } from 'mol-data/int' import { LinearGroupingBuilder } from '../utils/builders'; -import { QueryPredicate, QueryFn, QueryContextView } from '../context'; +import { QueryPredicate, QueryFn, QueryContextView, QueryContext } from '../context'; +import { UnitRing } from '../../structure/unit/rings'; +import Structure from '../../structure/structure'; +import { ElementIndex } from '../../model'; +import { UniqueArray } from 'mol-data/generic'; +import { structureSubtract } from '../utils/structure'; export const none: StructureQuery = ctx => StructureSelection.Sequence(ctx.inputStructure, []); export const all: StructureQuery = ctx => StructureSelection.Singletons(ctx.inputStructure, ctx.inputStructure); @@ -157,4 +162,63 @@ function atomGroupsGrouped({ entityTest, chainTest, residueTest, atomTest, group ctx.popCurrentElement(); return builder.getSelection(); }; +} + +function getRingStructure(unit: Unit.Atomic, ring: UnitRing) { + const elements = new Int32Array(ring.length) as any as ElementIndex[]; + for (let i = 0, _i = ring.length; i < _i; i++) elements[i] = unit.elements[ring[i]]; + return Structure.create([unit.getChild(SortedArray.ofSortedArray(elements))]) +} + +export function rings(fingerprints?: ArrayLike<UnitRing.Fingerprint>): StructureQuery { + return ctx => { + const { units } = ctx.inputStructure; + let ret = StructureSelection.LinearBuilder(ctx.inputStructure); + + if (!fingerprints || fingerprints.length === 0) { + for (const u of units) { + if (!Unit.isAtomic(u)) continue; + + for (const r of u.rings.all) { + ret.add(getRingStructure(u, r)); + } + } + } else { + const uniqueFps = UniqueArray.create<UnitRing.Fingerprint, UnitRing.Fingerprint>(); + for (let i = 0; i < fingerprints.length; i++) UniqueArray.add(uniqueFps, fingerprints[i], fingerprints[i]); + + for (const u of units) { + if (!Unit.isAtomic(u)) continue; + + const rings = u.rings; + for (const fp of uniqueFps.array) { + if (!rings.byFingerprint.has(fp)) continue; + for (const r of rings.byFingerprint.get(fp)!) { + ret.add(getRingStructure(u, rings.all[r])); + } + } + } + } + + return ret.getSelection(); + } +} + +export function querySelection(selection: StructureQuery, query: StructureQuery, inComplement: boolean = false): StructureQuery { + return ctx => { + const targetSel = selection(ctx); + if (StructureSelection.structureCount(targetSel) === 0) return targetSel; + + const target = inComplement + ? structureSubtract(ctx.inputStructure, StructureSelection.unionStructure(targetSel)) + : StructureSelection.unionStructure(targetSel); + + if (target.elementCount === 0) return StructureSelection.Empty(ctx.inputStructure); + ctx.throwIfTimedOut(); + + ctx.pushInputStructure(target); + const result = query(ctx); + ctx.popInputStructure(); + return StructureSelection.withInputStructure(result, ctx.inputStructure); + } } \ No newline at end of file diff --git a/src/mol-model/structure/query/queries/modifiers.ts b/src/mol-model/structure/query/queries/modifiers.ts index f6add3bbec78f401ac461fec32b2ef36767e429b..94847da473058b66c4af92c78725c3ab5050f835 100644 --- a/src/mol-model/structure/query/queries/modifiers.ts +++ b/src/mol-model/structure/query/queries/modifiers.ts @@ -59,6 +59,7 @@ export function wholeResidues(query: StructureQuery, isFlat: boolean): Structure export interface IncludeSurroundingsParams { radius: number, + // TODO // atomRadius?: Element.Property<number>, wholeResidues?: boolean } @@ -96,4 +97,6 @@ export function includeSurroundings(query: StructureQuery, params: IncludeSurrou return builder.getSelection(); } }; -} \ No newline at end of file +} + +// TODO: queryEach, intersectBy, exceptBy, unionBy, union, cluster, includeConnected \ No newline at end of file diff --git a/src/mol-model/structure/query/selection.ts b/src/mol-model/structure/query/selection.ts index a92cf93aa422f6fd049e5ab57b57a1fdccfbaad5..c4c804faf09442cdc53c9b9bbf82da31a5c2f177 100644 --- a/src/mol-model/structure/query/selection.ts +++ b/src/mol-model/structure/query/selection.ts @@ -119,7 +119,12 @@ namespace StructureSelection { } } - // TODO: spatial lookup + export function withInputStructure(selection: StructureSelection, structure: Structure) { + if (isSingleton(selection)) return Singletons(structure, selection.structure); + return Sequence(structure, selection.structures); + } + + // TODO: spatial lookup? } export { StructureSelection } \ 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 edc6447f0a1447cd06ccd8f4a0e7a4870468f926..32fc551caafadeeca2b6c04f66fc33bb1a12a685 100644 --- a/src/mol-model/structure/structure/structure.ts +++ b/src/mol-model/structure/structure/structure.ts @@ -21,6 +21,7 @@ import StructureProperties from './properties'; import { ResidueIndex } from '../model/indexing'; import { Carbohydrates } from './carbohydrates/data'; import { computeCarbohydrates } from './carbohydrates/compute'; +import { Vec3 } from 'mol-math/linear-algebra'; class Structure { readonly unitMap: IntMap<Unit>; @@ -321,6 +322,81 @@ namespace Structure { sortArray(uniqueResidues.array); return uniqueResidues.array; } + + const distVec = Vec3.zero(); + function atomicOrGaussianDistance(u: Unit.Atomic | Unit.Gaussians, p: Vec3, r: number) { + const { elements, conformation } = u; + let minD = Number.MAX_VALUE; + for (let i = 0, _i = elements.length; i < _i; i++) { + const d = Vec3.distance(p, conformation.position(elements[i], distVec)) - r; + if (d < minD) minD = d; + } + return minD; + } + + function sphereDistance(u: Unit.Spheres, p: Vec3, r: number) { + const { elements, conformation } = u; + const radius = u.coarseConformation.radius; + let minD = Number.MAX_VALUE; + for (let i = 0, _i = elements.length; i < _i; i++) { + const d = Vec3.distance(p, conformation.position(elements[i], distVec)) - r - radius[elements[i]]; + if (d < minD) minD = d; + } + return minD; + } + + export function minDistanceToPoint(s: Structure, point: Vec3, radius: number) { + const { units } = s; + let minD = Number.MAX_VALUE, d = 0; + for (let i = 0, _i = units.length; i < _i; i++) { + const unit = units[i]; + switch (unit.kind) { + case Unit.Kind.Atomic: + // TODO: assign radius to gaussian elements? + case Unit.Kind.Gaussians: + d = atomicOrGaussianDistance(unit, point, radius); + break; + case Unit.Kind.Spheres: + d = sphereDistance(unit, point, radius); + break; + } + if (d < minD) minD = d; + } + return minD; + } + + const distPivot = Vec3.zero(); + export function distance(a: Structure, b: Structure) { + if (a.elementCount === 0 || b.elementCount === 0) return 0; + + const { units } = a; + let minD = Number.MAX_VALUE, d = 0; + + for (let i = 0, _i = units.length; i < _i; i++) { + const unit = units[i]; + const { elements, conformation } = unit; + + switch (unit.kind) { + case Unit.Kind.Atomic: + // TODO: assign radius to gaussian elements? + case Unit.Kind.Gaussians: + for (let i = 0, _i = elements.length; i < _i; i++) { + const d = minDistanceToPoint(b, conformation.position(elements[i], distPivot), 0); + if (d < minD) minD = d; + } + break; + case Unit.Kind.Spheres: + const radius = unit.coarseConformation.radius; + for (let i = 0, _i = elements.length; i < _i; i++) { + const d = minDistanceToPoint(b, conformation.position(elements[i], distPivot), radius[elements[i]]); + if (d < minD) minD = d; + } + break; + } + if (d < minD) minD = d; + } + return minD; + } } export default Structure \ No newline at end of file