modifiers.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432
  1. /**
  2. * Copyright (c) 2017 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { Segmentation, SortedArray } from '../../../../mol-data/int';
  7. import { Structure, Unit } from '../../structure';
  8. import { StructureQuery } from '../query';
  9. import { StructureSelection } from '../selection';
  10. import { UniqueStructuresBuilder } from '../utils/builders';
  11. import { StructureUniqueSubsetBuilder } from '../../structure/util/unique-subset-builder';
  12. import { QueryContext, QueryFn } from '../context';
  13. import { structureIntersect, structureSubtract } from '../utils/structure-set';
  14. import { UniqueArray } from '../../../../mol-data/generic';
  15. import { StructureSubsetBuilder } from '../../structure/util/subset-builder';
  16. import StructureElement from '../../structure/element';
  17. function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
  18. const builder = source.subsetBuilder(true);
  19. for (const unit of structure.units) {
  20. if (unit.kind !== Unit.Kind.Atomic) {
  21. // just copy non-atomic units.
  22. builder.setUnit(unit.id, unit.elements);
  23. continue;
  24. }
  25. const { residueAtomSegments } = unit.model.atomicHierarchy;
  26. const elements = unit.elements;
  27. builder.beginUnit(unit.id);
  28. const residuesIt = Segmentation.transientSegments(residueAtomSegments, elements);
  29. while (residuesIt.hasNext) {
  30. const rI = residuesIt.move().index;
  31. for (let j = residueAtomSegments.offsets[rI], _j = residueAtomSegments.offsets[rI + 1]; j < _j; j++) {
  32. builder.addElement(j);
  33. }
  34. }
  35. builder.commitUnit();
  36. ctx.throwIfTimedOut();
  37. }
  38. return builder.getStructure();
  39. }
  40. export function wholeResidues(query: StructureQuery): StructureQuery {
  41. return function query_wholeResidues(ctx) {
  42. const inner = query(ctx);
  43. if (StructureSelection.isSingleton(inner)) {
  44. return StructureSelection.Singletons(ctx.inputStructure, getWholeResidues(ctx, ctx.inputStructure, inner.structure));
  45. } else {
  46. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  47. for (const s of inner.structures) {
  48. builder.add(getWholeResidues(ctx, ctx.inputStructure, s));
  49. }
  50. return builder.getSelection();
  51. }
  52. };
  53. }
  54. export interface IncludeSurroundingsParams {
  55. radius: number,
  56. elementRadius?: QueryFn<number>,
  57. wholeResidues?: boolean
  58. }
  59. function getIncludeSurroundings(ctx: QueryContext, source: Structure, structure: Structure, params: IncludeSurroundingsParams) {
  60. const builder = new StructureUniqueSubsetBuilder(source);
  61. const lookup = source.lookup3d;
  62. const r = params.radius;
  63. for (const unit of structure.units) {
  64. const { x, y, z } = unit.conformation;
  65. const elements = unit.elements;
  66. for (let i = 0, _i = elements.length; i < _i; i++) {
  67. const e = elements[i];
  68. lookup.findIntoBuilder(x(e), y(e), z(e), r, builder);
  69. }
  70. ctx.throwIfTimedOut();
  71. }
  72. return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure();
  73. }
  74. interface IncludeSurroundingsParamsWithRadius extends IncludeSurroundingsParams {
  75. elementRadius: QueryFn<number>,
  76. elementRadiusClosure: StructureElement.Property<number>,
  77. sourceMaxRadius: number
  78. }
  79. function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure, structure: Structure, params: IncludeSurroundingsParamsWithRadius) {
  80. const builder = new StructureUniqueSubsetBuilder(source);
  81. const lookup = source.lookup3d;
  82. const { elementRadius, elementRadiusClosure, sourceMaxRadius, radius } = params;
  83. ctx.pushCurrentElement();
  84. ctx.element.structure = structure;
  85. for (const unit of structure.units) {
  86. ctx.element.unit = unit;
  87. const { x, y, z } = unit.conformation;
  88. const elements = unit.elements;
  89. for (let i = 0, _i = elements.length; i < _i; i++) {
  90. const e = elements[i];
  91. ctx.element.element = e;
  92. const eRadius = elementRadius(ctx);
  93. lookup.findIntoBuilderWithRadius(x(e), y(e), z(e), eRadius, sourceMaxRadius, radius, elementRadiusClosure, builder);
  94. }
  95. ctx.throwIfTimedOut();
  96. }
  97. ctx.popCurrentElement();
  98. return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure();
  99. }
  100. function createElementRadiusFn(ctx: QueryContext, eRadius: QueryFn<number>): StructureElement.Property<number> {
  101. return e => {
  102. ctx.element.structure = e.structure;
  103. ctx.element.unit = e.unit;
  104. ctx.element.element = e.element;
  105. return eRadius(ctx);
  106. }
  107. }
  108. function findStructureRadius(ctx: QueryContext, eRadius: QueryFn<number>) {
  109. let r = 0;
  110. ctx.element.structure = ctx.inputStructure;
  111. for (const unit of ctx.inputStructure.units) {
  112. ctx.element.unit = unit;
  113. const elements = unit.elements;
  114. for (let i = 0, _i = elements.length; i < _i; i++) {
  115. const e = elements[i];
  116. ctx.element.element = e;
  117. const eR = eRadius(ctx);
  118. if (eR > r) r = eR;
  119. }
  120. }
  121. ctx.throwIfTimedOut();
  122. return r;
  123. }
  124. export function includeSurroundings(query: StructureQuery, params: IncludeSurroundingsParams): StructureQuery {
  125. return function query_includeSurroundings(ctx) {
  126. const inner = query(ctx);
  127. if (params.elementRadius) {
  128. const prms: IncludeSurroundingsParamsWithRadius = {
  129. ...params,
  130. elementRadius: params.elementRadius,
  131. elementRadiusClosure: createElementRadiusFn(ctx, params.elementRadius),
  132. sourceMaxRadius: findStructureRadius(ctx, params.elementRadius)
  133. };
  134. if (StructureSelection.isSingleton(inner)) {
  135. const surr = getIncludeSurroundingsWithRadius(ctx, ctx.inputStructure, inner.structure, prms);
  136. const ret = StructureSelection.Singletons(ctx.inputStructure, surr);
  137. return ret;
  138. } else {
  139. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  140. for (const s of inner.structures) {
  141. builder.add(getIncludeSurroundingsWithRadius(ctx, ctx.inputStructure, s, prms));
  142. }
  143. return builder.getSelection();
  144. }
  145. }
  146. if (StructureSelection.isSingleton(inner)) {
  147. const surr = getIncludeSurroundings(ctx, ctx.inputStructure, inner.structure, params);
  148. const ret = StructureSelection.Singletons(ctx.inputStructure, surr);
  149. return ret;
  150. } else {
  151. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  152. for (const s of inner.structures) {
  153. builder.add(getIncludeSurroundings(ctx, ctx.inputStructure, s, params));
  154. }
  155. return builder.getSelection();
  156. }
  157. };
  158. }
  159. export function querySelection(selection: StructureQuery, query: StructureQuery): StructureQuery {
  160. return function query_querySelection(ctx) {
  161. const targetSel = selection(ctx);
  162. if (StructureSelection.structureCount(targetSel) === 0) return targetSel;
  163. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  164. const add = (s: Structure) => ret.add(s);
  165. StructureSelection.forEach(targetSel, (s, sI) => {
  166. ctx.pushInputStructure(s);
  167. StructureSelection.forEach(query(ctx), add);
  168. ctx.popInputStructure();
  169. if (sI % 10 === 0) ctx.throwIfTimedOut();
  170. });
  171. return ret.getSelection();
  172. }
  173. }
  174. export function intersectBy(query: StructureQuery, by: StructureQuery): StructureQuery {
  175. return function query_intersectBy(ctx) {
  176. const selection = query(ctx);
  177. if (StructureSelection.structureCount(selection) === 0) return selection;
  178. const bySel = by(ctx);
  179. if (StructureSelection.structureCount(bySel) === 0) return StructureSelection.Empty(ctx.inputStructure);
  180. const unionBy = StructureSelection.unionStructure(bySel);
  181. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  182. StructureSelection.forEach(selection, (s, sI) => {
  183. const ii = structureIntersect(unionBy, s);
  184. if (ii.elementCount !== 0) ret.add(ii);
  185. if (sI % 50 === 0) ctx.throwIfTimedOut();
  186. });
  187. return ret.getSelection();
  188. };
  189. }
  190. export function exceptBy(query: StructureQuery, by: StructureQuery): StructureQuery {
  191. return function query_exceptBy(ctx) {
  192. const selection = query(ctx);
  193. if (StructureSelection.structureCount(selection) === 0) return selection;
  194. const bySel = by(ctx);
  195. if (StructureSelection.structureCount(bySel) === 0) return selection;
  196. const subtractBy = StructureSelection.unionStructure(bySel);
  197. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  198. StructureSelection.forEach(selection, (s, sI) => {
  199. const diff = structureSubtract(s, subtractBy);
  200. if (diff.elementCount !== 0) ret.add(diff);
  201. if (sI % 50 === 0) ctx.throwIfTimedOut();
  202. });
  203. return ret.getSelection();
  204. };
  205. }
  206. export function union(query: StructureQuery): StructureQuery {
  207. return function query_union(ctx) {
  208. const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
  209. ret.add(StructureSelection.unionStructure(query(ctx)));
  210. return ret.getSelection();
  211. };
  212. }
  213. export function expandProperty(query: StructureQuery, property: QueryFn): StructureQuery {
  214. return function query_expandProperty(ctx) {
  215. const src = query(ctx);
  216. const propertyToStructureIndexMap = new Map<any, UniqueArray<number>>();
  217. const builders: StructureSubsetBuilder[] = [];
  218. ctx.pushCurrentElement();
  219. StructureSelection.forEach(src, (s, sI) => {
  220. ctx.element.structure = s;
  221. for (const unit of s.units) {
  222. ctx.element.unit = unit;
  223. const elements = unit.elements;
  224. for (let i = 0, _i = elements.length; i < _i; i++) {
  225. ctx.element.element = elements[i];
  226. const p = property(ctx);
  227. let arr: UniqueArray<number>;
  228. if (propertyToStructureIndexMap.has(p)) arr = propertyToStructureIndexMap.get(p)!;
  229. else {
  230. arr = UniqueArray.create<number>();
  231. propertyToStructureIndexMap.set(p, arr);
  232. }
  233. UniqueArray.add(arr, sI, sI);
  234. }
  235. }
  236. builders[sI] = ctx.inputStructure.subsetBuilder(true);
  237. if (sI % 10 === 0) ctx.throwIfTimedOut();
  238. });
  239. ctx.element.structure = ctx.inputStructure;
  240. for (const unit of ctx.inputStructure.units) {
  241. ctx.element.unit = unit;
  242. const elements = unit.elements;
  243. for (let i = 0, _i = elements.length; i < _i; i++) {
  244. ctx.element.element = elements[i];
  245. const p = property(ctx);
  246. if (!propertyToStructureIndexMap.has(p)) continue;
  247. const indices = propertyToStructureIndexMap.get(p)!.array;
  248. for (let _sI = 0, __sI = indices.length; _sI < __sI; _sI++) {
  249. builders[indices[_sI]].addToUnit(unit.id, elements[i]);
  250. }
  251. }
  252. }
  253. ctx.popCurrentElement();
  254. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  255. for (const b of builders) ret.add(b.getStructure());
  256. return ret.getSelection();
  257. };
  258. }
  259. export interface IncludeConnectedParams {
  260. query: StructureQuery,
  261. bondTest?: QueryFn<boolean>,
  262. layerCount: number,
  263. wholeResidues: boolean
  264. }
  265. export function includeConnected({ query, layerCount, wholeResidues, bondTest }: IncludeConnectedParams): StructureQuery {
  266. const lc = Math.max(layerCount, 0);
  267. return function query_includeConnected(ctx) {
  268. const builder = StructureSelection.UniqueBuilder(ctx.inputStructure);
  269. const src = query(ctx);
  270. ctx.pushCurrentBond();
  271. ctx.atomicBond.setTestFn(bondTest);
  272. StructureSelection.forEach(src, (s, sI) => {
  273. let incl = s;
  274. for (let i = 0; i < lc; i++) {
  275. incl = includeConnectedStep(ctx, wholeResidues, incl);
  276. }
  277. builder.add(incl);
  278. if (sI % 10 === 0) ctx.throwIfTimedOut();
  279. });
  280. ctx.popCurrentBond();
  281. return builder.getSelection();
  282. }
  283. }
  284. function includeConnectedStep(ctx: QueryContext, wholeResidues: boolean, structure: Structure) {
  285. const expanded = expandConnected(ctx, structure);
  286. if (wholeResidues) return getWholeResidues(ctx, ctx.inputStructure, expanded);
  287. return expanded;
  288. }
  289. function expandConnected(ctx: QueryContext, structure: Structure) {
  290. const inputStructure = ctx.inputStructure;
  291. const interBonds = inputStructure.interUnitBonds;
  292. const builder = new StructureUniqueSubsetBuilder(inputStructure);
  293. // Note: each bond is visited twice so that bond.atom-a and bond.atom-b both get the "swapped values"
  294. const visitedSourceUnits = new Set<number>();
  295. const atomicBond = ctx.atomicBond;
  296. // Process intra unit bonds
  297. for (const unit of structure.units) {
  298. if (unit.kind !== Unit.Kind.Atomic) {
  299. // add the whole unit
  300. builder.beginUnit(unit.id);
  301. for (let i = 0, _i = unit.elements.length; i < _i; i++) {
  302. builder.addElement(unit.elements[i]);
  303. }
  304. builder.commitUnit();
  305. continue;
  306. }
  307. const inputUnitA = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
  308. const { offset: intraBondOffset, b: intraBondB, edgeProps: { flags, order } } = inputUnitA.bonds;
  309. atomicBond.setStructure(inputStructure);
  310. // Process intra unit bonds
  311. atomicBond.a.unit = inputUnitA;
  312. atomicBond.b.unit = inputUnitA;
  313. for (let i = 0, _i = unit.elements.length; i < _i; i++) {
  314. // add the current element
  315. builder.addToUnit(unit.id, unit.elements[i]);
  316. const aIndex = SortedArray.indexOf(inputUnitA.elements, unit.elements[i]) as StructureElement.UnitIndex;
  317. // check intra unit bonds
  318. for (let lI = intraBondOffset[aIndex], _lI = intraBondOffset[aIndex + 1]; lI < _lI; lI++) {
  319. const bIndex = intraBondB[lI] as StructureElement.UnitIndex;
  320. const bElement = inputUnitA.elements[bIndex];
  321. // Check if the element is already present:
  322. if (SortedArray.has(unit.elements, bElement) || builder.has(unit.id, bElement)) continue;
  323. atomicBond.aIndex = aIndex;
  324. atomicBond.a.element = unit.elements[i];
  325. atomicBond.bIndex = bIndex;
  326. atomicBond.b.element = bElement;
  327. atomicBond.type = flags[lI];
  328. atomicBond.order = order[lI];
  329. if (atomicBond.test(ctx, true)) {
  330. builder.addToUnit(unit.id, bElement);
  331. }
  332. }
  333. }
  334. // Process inter unit bonds
  335. for (const bondedUnit of interBonds.getConnectedUnits(inputUnitA)) {
  336. if (visitedSourceUnits.has(bondedUnit.unitB.id)) continue;
  337. const currentUnitB = structure.unitMap.get(bondedUnit.unitB.id);
  338. for (const aI of bondedUnit.connectedIndices) {
  339. // check if the element is in the expanded structure
  340. if (!SortedArray.has(unit.elements, inputUnitA.elements[aI])) continue;
  341. for (const bond of bondedUnit.getEdges(aI)) {
  342. const bElement = bondedUnit.unitB.elements[bond.indexB];
  343. // Check if the element is already present:
  344. if ((currentUnitB && SortedArray.has(currentUnitB.elements, bElement)) || builder.has(bondedUnit.unitB.id, bElement)) continue;
  345. atomicBond.a.unit = inputUnitA;
  346. atomicBond.aIndex = aI;
  347. atomicBond.a.element = inputUnitA.elements[aI];
  348. atomicBond.b.unit = bondedUnit.unitB;
  349. atomicBond.bIndex = bond.indexB;
  350. atomicBond.b.element = bElement;
  351. atomicBond.type = bond.props.flag;
  352. atomicBond.order = bond.props.order;
  353. if (atomicBond.test(ctx, true)) {
  354. builder.addToUnit(bondedUnit.unitB.id, bElement);
  355. }
  356. }
  357. }
  358. }
  359. visitedSourceUnits.add(inputUnitA.id);
  360. }
  361. return builder.getStructure();
  362. }
  363. // TODO: unionBy (skip this one?), cluster