modifiers.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  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. import { defaultLinkTest } from './internal';
  18. function getWholeResidues(ctx: QueryContext, source: Structure, structure: Structure) {
  19. const builder = source.subsetBuilder(true);
  20. for (const unit of structure.units) {
  21. if (unit.kind !== Unit.Kind.Atomic) {
  22. // just copy non-atomic units.
  23. builder.setUnit(unit.id, unit.elements);
  24. continue;
  25. }
  26. const { residueAtomSegments } = unit.model.atomicHierarchy;
  27. const elements = unit.elements;
  28. builder.beginUnit(unit.id);
  29. const residuesIt = Segmentation.transientSegments(residueAtomSegments, elements);
  30. while (residuesIt.hasNext) {
  31. const rI = residuesIt.move().index;
  32. for (let j = residueAtomSegments.offsets[rI], _j = residueAtomSegments.offsets[rI + 1]; j < _j; j++) {
  33. builder.addElement(j);
  34. }
  35. }
  36. builder.commitUnit();
  37. ctx.throwIfTimedOut();
  38. }
  39. return builder.getStructure();
  40. }
  41. export function wholeResidues(query: StructureQuery): StructureQuery {
  42. return function query_wholeResidues(ctx) {
  43. const inner = query(ctx);
  44. if (StructureSelection.isSingleton(inner)) {
  45. return StructureSelection.Singletons(ctx.inputStructure, getWholeResidues(ctx, ctx.inputStructure, inner.structure));
  46. } else {
  47. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  48. for (const s of inner.structures) {
  49. builder.add(getWholeResidues(ctx, ctx.inputStructure, s));
  50. }
  51. return builder.getSelection();
  52. }
  53. };
  54. }
  55. export interface IncludeSurroundingsParams {
  56. radius: number,
  57. elementRadius?: QueryFn<number>,
  58. wholeResidues?: boolean
  59. }
  60. function getIncludeSurroundings(ctx: QueryContext, source: Structure, structure: Structure, params: IncludeSurroundingsParams) {
  61. const builder = new StructureUniqueSubsetBuilder(source);
  62. const lookup = source.lookup3d;
  63. const r = params.radius;
  64. for (const unit of structure.units) {
  65. const { x, y, z } = unit.conformation;
  66. const elements = unit.elements;
  67. for (let i = 0, _i = elements.length; i < _i; i++) {
  68. const e = elements[i];
  69. lookup.findIntoBuilder(x(e), y(e), z(e), r, builder);
  70. }
  71. ctx.throwIfTimedOut();
  72. }
  73. return !!params.wholeResidues ? getWholeResidues(ctx, source, builder.getStructure()) : builder.getStructure();
  74. }
  75. interface IncludeSurroundingsParamsWithRadius extends IncludeSurroundingsParams {
  76. elementRadius: QueryFn<number>,
  77. elementRadiusClosure: StructureElement.Property<number>,
  78. sourceMaxRadius: number
  79. }
  80. function getIncludeSurroundingsWithRadius(ctx: QueryContext, source: Structure, structure: Structure, params: IncludeSurroundingsParamsWithRadius) {
  81. const builder = new StructureUniqueSubsetBuilder(source);
  82. const lookup = source.lookup3d;
  83. const { elementRadius, elementRadiusClosure, sourceMaxRadius, radius } = params;
  84. ctx.pushCurrentElement();
  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.unit = e.unit;
  103. ctx.element.element = e.element;
  104. return eRadius(ctx);
  105. }
  106. }
  107. function findStructureRadius(ctx: QueryContext, eRadius: QueryFn<number>) {
  108. let r = 0;
  109. for (const unit of ctx.inputStructure.units) {
  110. ctx.element.unit = unit;
  111. const elements = unit.elements;
  112. for (let i = 0, _i = elements.length; i < _i; i++) {
  113. const e = elements[i];
  114. ctx.element.element = e;
  115. const eR = eRadius(ctx);
  116. if (eR > r) r = eR;
  117. }
  118. }
  119. ctx.throwIfTimedOut();
  120. return r;
  121. }
  122. export function includeSurroundings(query: StructureQuery, params: IncludeSurroundingsParams): StructureQuery {
  123. return function query_includeSurroundings(ctx) {
  124. const inner = query(ctx);
  125. if (params.elementRadius) {
  126. const prms: IncludeSurroundingsParamsWithRadius = {
  127. ...params,
  128. elementRadius: params.elementRadius,
  129. elementRadiusClosure: createElementRadiusFn(ctx, params.elementRadius),
  130. sourceMaxRadius: findStructureRadius(ctx, params.elementRadius)
  131. };
  132. if (StructureSelection.isSingleton(inner)) {
  133. const surr = getIncludeSurroundingsWithRadius(ctx, ctx.inputStructure, inner.structure, prms);
  134. const ret = StructureSelection.Singletons(ctx.inputStructure, surr);
  135. return ret;
  136. } else {
  137. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  138. for (const s of inner.structures) {
  139. builder.add(getIncludeSurroundingsWithRadius(ctx, ctx.inputStructure, s, prms));
  140. }
  141. return builder.getSelection();
  142. }
  143. }
  144. if (StructureSelection.isSingleton(inner)) {
  145. const surr = getIncludeSurroundings(ctx, ctx.inputStructure, inner.structure, params);
  146. const ret = StructureSelection.Singletons(ctx.inputStructure, surr);
  147. return ret;
  148. } else {
  149. const builder = new UniqueStructuresBuilder(ctx.inputStructure);
  150. for (const s of inner.structures) {
  151. builder.add(getIncludeSurroundings(ctx, ctx.inputStructure, s, params));
  152. }
  153. return builder.getSelection();
  154. }
  155. };
  156. }
  157. export function querySelection(selection: StructureQuery, query: StructureQuery): StructureQuery {
  158. return function query_querySelection(ctx) {
  159. const targetSel = selection(ctx);
  160. if (StructureSelection.structureCount(targetSel) === 0) return targetSel;
  161. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  162. const add = (s: Structure) => ret.add(s);
  163. StructureSelection.forEach(targetSel, (s, sI) => {
  164. ctx.pushInputStructure(s);
  165. StructureSelection.forEach(query(ctx), add);
  166. ctx.popInputStructure();
  167. if (sI % 10 === 0) ctx.throwIfTimedOut();
  168. });
  169. return ret.getSelection();
  170. }
  171. }
  172. export function intersectBy(query: StructureQuery, by: StructureQuery): StructureQuery {
  173. return function query_intersectBy(ctx) {
  174. const selection = query(ctx);
  175. if (StructureSelection.structureCount(selection) === 0) return selection;
  176. const bySel = by(ctx);
  177. if (StructureSelection.structureCount(bySel) === 0) return StructureSelection.Empty(ctx.inputStructure);
  178. const unionBy = StructureSelection.unionStructure(bySel);
  179. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  180. StructureSelection.forEach(selection, (s, sI) => {
  181. const ii = structureIntersect(unionBy, s);
  182. if (ii.elementCount !== 0) ret.add(ii);
  183. if (sI % 50 === 0) ctx.throwIfTimedOut();
  184. });
  185. return ret.getSelection();
  186. };
  187. }
  188. export function exceptBy(query: StructureQuery, by: StructureQuery): StructureQuery {
  189. return function query_exceptBy(ctx) {
  190. const selection = query(ctx);
  191. if (StructureSelection.structureCount(selection) === 0) return selection;
  192. const bySel = by(ctx);
  193. if (StructureSelection.structureCount(bySel) === 0) return selection;
  194. const subtractBy = StructureSelection.unionStructure(bySel);
  195. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  196. StructureSelection.forEach(selection, (s, sI) => {
  197. const diff = structureSubtract(s, subtractBy);
  198. if (diff.elementCount !== 0) ret.add(diff);
  199. if (sI % 50 === 0) ctx.throwIfTimedOut();
  200. });
  201. return ret.getSelection();
  202. };
  203. }
  204. export function union(query: StructureQuery): StructureQuery {
  205. return function query_union(ctx) {
  206. const ret = StructureSelection.LinearBuilder(ctx.inputStructure);
  207. ret.add(StructureSelection.unionStructure(query(ctx)));
  208. return ret.getSelection();
  209. };
  210. }
  211. export function expandProperty(query: StructureQuery, property: QueryFn): StructureQuery {
  212. return function query_expandProperty(ctx) {
  213. const src = query(ctx);
  214. const propertyToStructureIndexMap = new Map<any, UniqueArray<number>>();
  215. const builders: StructureSubsetBuilder[] = [];
  216. ctx.pushCurrentElement();
  217. StructureSelection.forEach(src, (s, sI) => {
  218. for (const unit of s.units) {
  219. ctx.element.unit = unit;
  220. const elements = unit.elements;
  221. for (let i = 0, _i = elements.length; i < _i; i++) {
  222. ctx.element.element = elements[i];
  223. const p = property(ctx);
  224. let arr: UniqueArray<number>;
  225. if (propertyToStructureIndexMap.has(p)) arr = propertyToStructureIndexMap.get(p)!;
  226. else {
  227. arr = UniqueArray.create<number>();
  228. propertyToStructureIndexMap.set(p, arr);
  229. }
  230. UniqueArray.add(arr, sI, sI);
  231. }
  232. }
  233. builders[sI] = ctx.inputStructure.subsetBuilder(true);
  234. if (sI % 10 === 0) ctx.throwIfTimedOut();
  235. });
  236. for (const unit of ctx.inputStructure.units) {
  237. ctx.element.unit = unit;
  238. const elements = unit.elements;
  239. for (let i = 0, _i = elements.length; i < _i; i++) {
  240. ctx.element.element = elements[i];
  241. const p = property(ctx);
  242. if (!propertyToStructureIndexMap.has(p)) continue;
  243. const indices = propertyToStructureIndexMap.get(p)!.array;
  244. for (let _sI = 0, __sI = indices.length; _sI < __sI; _sI++) {
  245. builders[indices[_sI]].addToUnit(unit.id, elements[i]);
  246. }
  247. }
  248. }
  249. ctx.popCurrentElement();
  250. const ret = StructureSelection.UniqueBuilder(ctx.inputStructure);
  251. for (const b of builders) ret.add(b.getStructure());
  252. return ret.getSelection();
  253. };
  254. }
  255. export interface IncludeConnectedParams {
  256. query: StructureQuery,
  257. linkTest?: QueryFn<boolean>,
  258. layerCount: number,
  259. wholeResidues: boolean
  260. }
  261. export function includeConnected({ query, layerCount, wholeResidues, linkTest }: IncludeConnectedParams): StructureQuery {
  262. const bt = linkTest || defaultLinkTest;
  263. const lc = Math.max(layerCount, 0);
  264. return function query_includeConnected(ctx) {
  265. const builder = StructureSelection.UniqueBuilder(ctx.inputStructure);
  266. const src = query(ctx);
  267. ctx.pushCurrentLink();
  268. StructureSelection.forEach(src, (s, sI) => {
  269. let incl = s;
  270. for (let i = 0; i < lc; i++) {
  271. incl = includeConnectedStep(ctx, bt, wholeResidues, incl);
  272. }
  273. builder.add(incl);
  274. if (sI % 10 === 0) ctx.throwIfTimedOut();
  275. });
  276. ctx.popCurrentLink();
  277. return builder.getSelection();
  278. }
  279. }
  280. function includeConnectedStep(ctx: QueryContext, linkTest: QueryFn<boolean>, wholeResidues: boolean, structure: Structure) {
  281. const expanded = expandConnected(ctx, structure, linkTest);
  282. if (wholeResidues) return getWholeResidues(ctx, ctx.inputStructure, expanded);
  283. return expanded;
  284. }
  285. function expandConnected(ctx: QueryContext, structure: Structure, linkTest: QueryFn<boolean>) {
  286. const inputStructure = ctx.inputStructure;
  287. const interLinks = inputStructure.links;
  288. const builder = new StructureUniqueSubsetBuilder(inputStructure);
  289. const processedUnits = new Set<number>();
  290. const atomicLink = ctx.atomicLink;
  291. // Process intra unit links
  292. for (const unit of structure.units) {
  293. if (unit.kind !== Unit.Kind.Atomic) {
  294. // add the whole unit
  295. builder.beginUnit(unit.id);
  296. for (let i = 0, _i = unit.elements.length; i < _i; i++) {
  297. builder.addElement(unit.elements[i]);
  298. }
  299. builder.commitUnit();
  300. continue;
  301. }
  302. const inputUnit = inputStructure.unitMap.get(unit.id) as Unit.Atomic;
  303. const { offset: intraLinkOffset, b: intraLinkB, edgeProps: { flags, order } } = inputUnit.links;
  304. // Process intra unit links
  305. atomicLink.link.aUnit = inputUnit;
  306. atomicLink.link.bUnit = inputUnit;
  307. for (let i = 0, _i = unit.elements.length; i < _i; i++) {
  308. // add the current element
  309. builder.addToUnit(unit.id, unit.elements[i]);
  310. const srcIndex = SortedArray.indexOf(inputUnit.elements, unit.elements[i]);
  311. atomicLink.link.aIndex = srcIndex as StructureElement.UnitIndex;
  312. // check intra unit links
  313. for (let lI = intraLinkOffset[srcIndex], _lI = intraLinkOffset[srcIndex + 1]; lI < _lI; lI++) {
  314. atomicLink.link.bIndex = intraLinkB[lI] as StructureElement.UnitIndex;
  315. atomicLink.type = flags[lI];
  316. atomicLink.order = order[lI];
  317. if (linkTest(ctx)) {
  318. builder.addToUnit(unit.id, inputUnit.elements[intraLinkB[lI]]);
  319. }
  320. }
  321. }
  322. // Process inter unit links
  323. for (const linkedUnit of interLinks.getLinkedUnits(inputUnit)) {
  324. if (processedUnits.has(linkedUnit.unitA.id)) continue;
  325. atomicLink.link.bUnit = linkedUnit.unitB;
  326. for (const aI of linkedUnit.linkedElementIndices) {
  327. // check if the element is in the expanded structure
  328. if (!SortedArray.has(unit.elements, inputUnit.elements[aI])) continue;
  329. atomicLink.link.aIndex = aI;
  330. for (const bond of linkedUnit.getBonds(aI)) {
  331. atomicLink.link.bIndex = bond.indexB;
  332. atomicLink.type = bond.flag;
  333. atomicLink.order = bond.order;
  334. if (linkTest(ctx)) {
  335. builder.addToUnit(linkedUnit.unitB.id, linkedUnit.unitB.elements[bond.indexB]);
  336. }
  337. }
  338. }
  339. }
  340. processedUnits.add(unit.id);
  341. }
  342. return builder.getStructure();
  343. }
  344. // TODO: unionBy (skip this one?), cluster