algorithm.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. /**
  2. * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  5. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  6. */
  7. import { Structure, StructureElement, StructureProperties, Unit } from '../../mol-model/structure';
  8. import { Task, RuntimeContext } from '../../mol-task';
  9. import { CentroidHelper } from '../../mol-math/geometry/centroid-helper';
  10. import { AccessibleSurfaceAreaParams } from '../../mol-model-props/computed/accessible-surface-area';
  11. import { Vec3 } from '../../mol-math/linear-algebra';
  12. import { getElementMoleculeType } from '../../mol-model/structure/util';
  13. import { MoleculeType } from '../../mol-model/structure/model/types';
  14. import { AccessibleSurfaceArea } from '../../mol-model-props/computed/accessible-surface-area/shrake-rupley';
  15. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  16. import { MembraneOrientation } from './prop';
  17. const LARGE_CA_THRESHOLD = 5000;
  18. const UPDATE_INTERVAL = 10;
  19. interface ANVILContext {
  20. structure: Structure,
  21. numberOfSpherePoints: number,
  22. stepSize: number,
  23. minThickness: number,
  24. maxThickness: number,
  25. asaCutoff: number,
  26. adjust: number,
  27. offsets: ArrayLike<number>,
  28. exposed: ArrayLike<number>,
  29. hydrophobic: ArrayLike<boolean>,
  30. centroid: Vec3,
  31. extent: number
  32. };
  33. export const ANVILParams = {
  34. numberOfSpherePoints: PD.Numeric(140, { min: 35, max: 700, step: 1 }, { description: 'Number of spheres/directions to test for membrane placement. Original value is 350.' }),
  35. stepSize: PD.Numeric(1, { min: 0.25, max: 4, step: 0.25 }, { description: 'Thickness of membrane slices that will be tested' }),
  36. minThickness: PD.Numeric(20, { min: 10, max: 30, step: 1}, { description: 'Minimum membrane thickness used during refinement' }),
  37. maxThickness: PD.Numeric(40, { min: 30, max: 50, step: 1}, { description: 'Maximum membrane thickness used during refinement' }),
  38. asaCutoff: PD.Numeric(40, { min: 10, max: 100, step: 1 }, { description: 'Relative ASA cutoff above which residues will be considered' }),
  39. adjust: PD.Numeric(14, { min: 0, max: 30, step: 1 }, { description: 'Minimum length of membrane-spanning regions (original values: 14 for alpha-helices and 5 for beta sheets). Set to 0 to not optimize membrane thickness.' })
  40. };
  41. export type ANVILParams = typeof ANVILParams
  42. export type ANVILProps = PD.Values<ANVILParams>
  43. /**
  44. * Implements:
  45. * Membrane positioning for high- and low-resolution protein structures through a binary classification approach
  46. * Guillaume Postic, Yassine Ghouzam, Vincent Guiraud, and Jean-Christophe Gelly
  47. * Protein Engineering, Design & Selection, 2015, 1–5
  48. * doi: 10.1093/protein/gzv063
  49. */
  50. export function computeANVIL(structure: Structure, props: ANVILProps) {
  51. return Task.create('Compute Membrane Orientation', async runtime => {
  52. return await calculate(runtime, structure, props);
  53. });
  54. }
  55. // avoiding namespace lookup improved performance in Chrome (Aug 2020)
  56. const v3add = Vec3.add;
  57. const v3clone = Vec3.clone;
  58. const v3create = Vec3.create;
  59. const v3distance = Vec3.distance;
  60. const v3dot = Vec3.dot;
  61. const v3magnitude = Vec3.magnitude;
  62. const v3normalize = Vec3.normalize;
  63. const v3scale = Vec3.scale;
  64. const v3scaleAndAdd = Vec3.scaleAndAdd;
  65. const v3set = Vec3.set;
  66. const v3squaredDistance = Vec3.squaredDistance;
  67. const v3sub = Vec3.sub;
  68. const v3zero = Vec3.zero;
  69. const centroidHelper = new CentroidHelper();
  70. async function initialize(structure: Structure, props: ANVILProps, accessibleSurfaceArea: AccessibleSurfaceArea): Promise<ANVILContext> {
  71. const l = StructureElement.Location.create(structure);
  72. const { label_atom_id, label_comp_id, x, y, z } = StructureProperties.atom;
  73. const elementCount = structure.polymerResidueCount;
  74. const asaCutoff = props.asaCutoff / 100;
  75. centroidHelper.reset();
  76. let offsets = new Array<number>(elementCount);
  77. let exposed = new Array<number>(elementCount);
  78. let hydrophobic = new Array<boolean>(elementCount);
  79. const vec = v3zero();
  80. let m = 0;
  81. let n = 0;
  82. for (let i = 0, il = structure.units.length; i < il; ++i) {
  83. const unit = structure.units[i];
  84. const { elements } = unit;
  85. l.unit = unit;
  86. for (let j = 0, jl = elements.length; j < jl; ++j) {
  87. const eI = elements[j];
  88. l.element = eI;
  89. // consider only amino acids
  90. if (getElementMoleculeType(unit, eI) !== MoleculeType.Protein) {
  91. continue;
  92. }
  93. // only CA is considered for downstream operations
  94. if (label_atom_id(l) !== 'CA' && label_atom_id(l) !== 'BB') {
  95. continue;
  96. }
  97. // original ANVIL only considers canonical amino acids
  98. if (!MaxAsa[label_comp_id(l)]) {
  99. continue;
  100. }
  101. // while iterating use first pass to compute centroid
  102. v3set(vec, x(l), y(l), z(l));
  103. centroidHelper.includeStep(vec);
  104. // keep track of offsets and exposed state to reuse
  105. offsets[m++] = structure.serialMapping.getSerialIndex(l.unit, l.element);
  106. if (AccessibleSurfaceArea.getValue(l, accessibleSurfaceArea) / MaxAsa[label_comp_id(l)] > asaCutoff) {
  107. exposed[n] = structure.serialMapping.getSerialIndex(l.unit, l.element);
  108. hydrophobic[n] = isHydrophobic(label_comp_id(l));
  109. n++;
  110. }
  111. }
  112. }
  113. // omit potentially empty tail
  114. offsets = offsets.slice(0, m);
  115. exposed = exposed.slice(0, n);
  116. hydrophobic = hydrophobic.splice(0, n);
  117. // calculate centroid and extent
  118. centroidHelper.finishedIncludeStep();
  119. const centroid = v3clone(centroidHelper.center);
  120. for (let k = 0, kl = offsets.length; k < kl; k++) {
  121. setLocation(l, structure, offsets[k]);
  122. v3set(vec, x(l), y(l), z(l));
  123. centroidHelper.radiusStep(vec);
  124. }
  125. const extent = 1.2 * Math.sqrt(centroidHelper.radiusSq);
  126. return {
  127. ...props,
  128. structure,
  129. offsets,
  130. exposed,
  131. hydrophobic,
  132. centroid,
  133. extent
  134. };
  135. }
  136. export async function calculate(runtime: RuntimeContext, structure: Structure, params: ANVILProps): Promise<MembraneOrientation> {
  137. // can't get away with the default 92 points here
  138. const asaProps = { ...PD.getDefaultValues(AccessibleSurfaceAreaParams), probeSize: 4.0, traceOnly: true, numberOfSpherePoints: 184 };
  139. const accessibleSurfaceArea = await AccessibleSurfaceArea.compute(structure, asaProps).runInContext(runtime);
  140. const ctx = await initialize(structure, params, accessibleSurfaceArea);
  141. const initialHphobHphil = HphobHphil.filtered(ctx);
  142. const initialMembrane = (await findMembrane(runtime, 'Placing initial membrane...', ctx, generateSpherePoints(ctx, ctx.numberOfSpherePoints), initialHphobHphil))!;
  143. const refinedMembrane = (await findMembrane(runtime, 'Refining membrane placement...', ctx, findProximateAxes(ctx, initialMembrane), initialHphobHphil))!;
  144. let membrane = initialMembrane.qmax! > refinedMembrane.qmax! ? initialMembrane : refinedMembrane;
  145. if (ctx.adjust && ctx.offsets.length < LARGE_CA_THRESHOLD) {
  146. membrane = await adjustThickness(runtime, 'Adjusting membrane thickness...', ctx, membrane, initialHphobHphil);
  147. }
  148. const normalVector = v3zero();
  149. const center = v3zero();
  150. v3sub(normalVector, membrane.planePoint1, membrane.planePoint2);
  151. v3normalize(normalVector, normalVector);
  152. v3add(center, membrane.planePoint1, membrane.planePoint2);
  153. v3scale(center, center, 0.5);
  154. const extent = adjustExtent(ctx, membrane, center);
  155. return {
  156. planePoint1: membrane.planePoint1,
  157. planePoint2: membrane.planePoint2,
  158. normalVector,
  159. centroid: center,
  160. radius: extent
  161. };
  162. }
  163. interface MembraneCandidate {
  164. planePoint1: Vec3,
  165. planePoint2: Vec3,
  166. stats: HphobHphil,
  167. normalVector?: Vec3,
  168. spherePoint?: Vec3,
  169. qmax?: number
  170. }
  171. namespace MembraneCandidate {
  172. export function initial(c1: Vec3, c2: Vec3, stats: HphobHphil): MembraneCandidate {
  173. return {
  174. planePoint1: c1,
  175. planePoint2: c2,
  176. stats
  177. };
  178. }
  179. export function scored(spherePoint: Vec3, planePoint1: Vec3, planePoint2: Vec3, stats: HphobHphil, qmax: number, centroid: Vec3): MembraneCandidate {
  180. const normalVector = v3zero();
  181. v3sub(normalVector, centroid, spherePoint);
  182. return {
  183. planePoint1,
  184. planePoint2,
  185. stats,
  186. normalVector,
  187. spherePoint,
  188. qmax
  189. };
  190. }
  191. }
  192. async function findMembrane(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, spherePoints: Vec3[], initialStats: HphobHphil): Promise<MembraneCandidate | undefined> {
  193. const { centroid, stepSize, minThickness, maxThickness, exposed, structure } = ctx;
  194. const { units } = structure;
  195. const { elementIndices, unitIndices } = structure.serialMapping;
  196. // best performing membrane
  197. let membrane: MembraneCandidate | undefined;
  198. // score of the best performing membrane
  199. let qmax = 0;
  200. // construct slices of thickness 1.0 along the axis connecting the centroid and the spherePoint
  201. const diam = v3zero();
  202. const testPoint = v3zero();
  203. for (let n = 0, nl = spherePoints.length; n < nl; n++) {
  204. if (runtime?.shouldUpdate && message && (n + 1) % UPDATE_INTERVAL === 0) {
  205. await runtime.update({ message, current: (n + 1), max: nl });
  206. }
  207. const spherePoint = spherePoints[n];
  208. v3sub(diam, centroid, spherePoint);
  209. v3scale(diam, diam, 2);
  210. const diamNorm = v3magnitude(diam);
  211. const filter: Set<number>[] = [];
  212. for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
  213. filter[filter.length] = new Set<number>();
  214. }
  215. for (let i = 0, il = exposed.length; i < il; i++) {
  216. const unit = units[unitIndices[exposed[i]]];
  217. const elementIndex = elementIndices[exposed[i]];
  218. v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
  219. v3sub(testPoint, testPoint, spherePoint);
  220. filter[Math.floor(v3dot(testPoint, diam) / diamNorm / stepSize)].add(i);
  221. }
  222. const qvartemp = [];
  223. for (let i = 0, il = diamNorm - stepSize; i < il; i += stepSize) {
  224. const c1 = v3zero();
  225. const c2 = v3zero();
  226. v3scaleAndAdd(c1, spherePoint, diam, i / diamNorm);
  227. v3scaleAndAdd(c2, spherePoint, diam, (i + stepSize) / diamNorm);
  228. // evaluate how well this membrane slice embeddeds the peculiar residues
  229. const stats = HphobHphil.filtered(ctx, filter[Math.round(i / stepSize)]);
  230. qvartemp.push(MembraneCandidate.initial(c1, c2, stats));
  231. }
  232. let jmax = Math.floor((minThickness / stepSize) - 1);
  233. for (let width = 0, widthl = maxThickness; width <= widthl;) {
  234. for (let i = 0, il = qvartemp.length - 1 - jmax; i < il; i++) {
  235. let hphob = 0;
  236. let hphil = 0;
  237. for (let j = 0; j < jmax; j++) {
  238. const ij = qvartemp[i + j];
  239. if (j === 0 || j === jmax - 1) {
  240. hphob += Math.floor(0.5 * ij.stats.hphob);
  241. hphil += 0.5 * ij.stats.hphil;
  242. } else {
  243. hphob += ij.stats.hphob;
  244. hphil += ij.stats.hphil;
  245. }
  246. }
  247. if (hphob !== 0) {
  248. const stats = HphobHphil.of(hphob, hphil);
  249. const qvaltest = qValue(stats, initialStats);
  250. if (qvaltest >= qmax) {
  251. qmax = qvaltest;
  252. membrane = MembraneCandidate.scored(spherePoint, qvartemp[i].planePoint1, qvartemp[i + jmax].planePoint2, stats, qmax, centroid);
  253. }
  254. }
  255. }
  256. jmax++;
  257. width = (jmax + 1) * stepSize;
  258. }
  259. }
  260. return membrane;
  261. }
  262. /** Adjust membrane thickness by maximizing the number of membrane segments. */
  263. async function adjustThickness(runtime: RuntimeContext, message: string | undefined, ctx: ANVILContext, membrane: MembraneCandidate, initialHphobHphil: HphobHphil): Promise<MembraneCandidate> {
  264. const { minThickness } = ctx;
  265. const step = 0.3;
  266. let maxThickness = v3distance(membrane.planePoint1, membrane.planePoint2);
  267. let maxNos = membraneSegments(ctx, membrane).length;
  268. let optimalThickness = membrane;
  269. let n = 0;
  270. const nl = Math.ceil((maxThickness - minThickness) / step);
  271. while (maxThickness > minThickness) {
  272. n++;
  273. if (runtime?.shouldUpdate && message && n % UPDATE_INTERVAL === 0) {
  274. await runtime.update({ message, current: n, max: nl });
  275. }
  276. const p = {
  277. ...ctx,
  278. maxThickness,
  279. stepSize: step
  280. };
  281. const temp = await findMembrane(runtime, void 0, p, [membrane.spherePoint!], initialHphobHphil);
  282. if (temp) {
  283. const nos = membraneSegments(ctx, temp).length;
  284. if (nos > maxNos) {
  285. maxNos = nos;
  286. optimalThickness = temp;
  287. }
  288. }
  289. maxThickness -= step;
  290. }
  291. return optimalThickness;
  292. }
  293. /** Report auth_seq_ids for all transmembrane segments. Will reject segments that are shorter than the adjust parameter specifies. Missing residues are considered in-membrane. */
  294. function membraneSegments(ctx: ANVILContext, membrane: MembraneCandidate): ArrayLike<{ start: number, end: number }> {
  295. const { offsets, structure, adjust } = ctx;
  296. const { normalVector, planePoint1, planePoint2 } = membrane;
  297. const { units } = structure;
  298. const { elementIndices, unitIndices } = structure.serialMapping;
  299. const testPoint = v3zero();
  300. const { auth_seq_id } = StructureProperties.residue;
  301. const d1 = -v3dot(normalVector!, planePoint1);
  302. const d2 = -v3dot(normalVector!, planePoint2);
  303. const dMin = Math.min(d1, d2);
  304. const dMax = Math.max(d1, d2);
  305. const inMembrane: { [k: string]: Set<number> } = Object.create(null);
  306. const outMembrane: { [k: string]: Set<number> } = Object.create(null);
  307. const segments: Array<{ start: number, end: number }> = [];
  308. let authAsymId;
  309. let lastAuthAsymId = null;
  310. let authSeqId;
  311. let lastAuthSeqId = units[0].model.atomicHierarchy.residues.auth_seq_id.value((units[0] as Unit.Atomic).chainIndex[0]) - 1;
  312. let startOffset = 0;
  313. let endOffset = 0;
  314. // collect all residues in membrane layer
  315. for (let k = 0, kl = offsets.length; k < kl; k++) {
  316. const unit = units[unitIndices[offsets[k]]];
  317. if (!Unit.isAtomic(unit)) throw 'Property only available for atomic models.';
  318. const elementIndex = elementIndices[offsets[k]];
  319. authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
  320. if (authAsymId !== lastAuthAsymId) {
  321. if (!inMembrane[authAsymId]) inMembrane[authAsymId] = new Set<number>();
  322. if (!outMembrane[authAsymId]) outMembrane[authAsymId] = new Set<number>();
  323. lastAuthAsymId = authAsymId;
  324. }
  325. authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
  326. v3set(testPoint, unit.conformation.x(elementIndex), unit.conformation.y(elementIndex), unit.conformation.z(elementIndex));
  327. if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
  328. inMembrane[authAsymId].add(authSeqId);
  329. } else {
  330. outMembrane[authAsymId].add(authSeqId);
  331. }
  332. }
  333. for (let k = 0, kl = offsets.length; k < kl; k++) {
  334. const unit = units[unitIndices[offsets[k]]];
  335. if (!Unit.isAtomic(unit)) throw 'Property only available for atomic models.';
  336. const elementIndex = elementIndices[offsets[k]];
  337. authAsymId = unit.model.atomicHierarchy.chains.auth_asym_id.value(unit.chainIndex[elementIndex]);
  338. authSeqId = unit.model.atomicHierarchy.residues.auth_seq_id.value(unit.residueIndex[elementIndex]);
  339. if (inMembrane[authAsymId].has(authSeqId)) {
  340. // chain change
  341. if (authAsymId !== lastAuthAsymId) {
  342. segments.push({ start: startOffset, end: endOffset });
  343. lastAuthAsymId = authAsymId;
  344. startOffset = k;
  345. endOffset = k;
  346. }
  347. // sequence gaps
  348. if (authSeqId !== lastAuthSeqId + 1) {
  349. if (outMembrane[authAsymId].has(lastAuthSeqId + 1)) {
  350. segments.push({ start: startOffset, end: endOffset });
  351. startOffset = k;
  352. }
  353. lastAuthSeqId = authSeqId;
  354. endOffset = k;
  355. } else {
  356. lastAuthSeqId++;
  357. endOffset++;
  358. }
  359. }
  360. }
  361. segments.push({ start: startOffset, end: endOffset });
  362. const l = StructureElement.Location.create(structure);
  363. let startAuth;
  364. let endAuth;
  365. const refinedSegments: Array<{ start: number, end: number }> = [];
  366. for (let k = 0, kl = segments.length; k < kl; k++) {
  367. const { start, end } = segments[k];
  368. if (start === 0 || end === offsets.length - 1) continue;
  369. // evaluate residues 1 pos outside of membrane
  370. setLocation(l, structure, offsets[start - 1]);
  371. v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
  372. const d3 = -v3dot(normalVector!, testPoint);
  373. setLocation(l, structure, offsets[end + 1]);
  374. v3set(testPoint, l.unit.conformation.x(l.element), l.unit.conformation.y(l.element), l.unit.conformation.z(l.element));
  375. const d4 = -v3dot(normalVector!, testPoint);
  376. if (Math.min(d3, d4) < dMin && Math.max(d3, d4) > dMax) {
  377. // reject this refinement
  378. setLocation(l, structure, offsets[start]);
  379. startAuth = auth_seq_id(l);
  380. setLocation(l, structure, offsets[end]);
  381. endAuth = auth_seq_id(l);
  382. if (Math.abs(startAuth - endAuth) + 1 < adjust) {
  383. return [];
  384. }
  385. refinedSegments.push(segments[k]);
  386. }
  387. }
  388. return refinedSegments;
  389. }
  390. /** Filter for membrane residues and calculate the final extent of the membrane layer */
  391. function adjustExtent(ctx: ANVILContext, membrane: MembraneCandidate, centroid: Vec3): number {
  392. const { offsets, structure } = ctx;
  393. const { normalVector, planePoint1, planePoint2 } = membrane;
  394. const l = StructureElement.Location.create(structure);
  395. const testPoint = v3zero();
  396. const { x, y, z } = StructureProperties.atom;
  397. const d1 = -v3dot(normalVector!, planePoint1);
  398. const d2 = -v3dot(normalVector!, planePoint2);
  399. const dMin = Math.min(d1, d2);
  400. const dMax = Math.max(d1, d2);
  401. let extent = 0;
  402. for (let k = 0, kl = offsets.length; k < kl; k++) {
  403. setLocation(l, structure, offsets[k]);
  404. v3set(testPoint, x(l), y(l), z(l));
  405. if (_isInMembranePlane(testPoint, normalVector!, dMin, dMax)) {
  406. const dsq = v3squaredDistance(testPoint, centroid);
  407. if (dsq > extent) extent = dsq;
  408. }
  409. }
  410. return Math.sqrt(extent);
  411. }
  412. function qValue(currentStats: HphobHphil, initialStats: HphobHphil): number {
  413. if(initialStats.hphob < 1) {
  414. initialStats.hphob = 0.1;
  415. }
  416. if(initialStats.hphil < 1) {
  417. initialStats.hphil += 1;
  418. }
  419. const part_tot = currentStats.hphob + currentStats.hphil;
  420. return (currentStats.hphob * (initialStats.hphil - currentStats.hphil) - currentStats.hphil * (initialStats.hphob - currentStats.hphob)) /
  421. Math.sqrt(part_tot * initialStats.hphob * initialStats.hphil * (initialStats.hphob + initialStats.hphil - part_tot));
  422. }
  423. export function isInMembranePlane(testPoint: Vec3, normalVector: Vec3, planePoint1: Vec3, planePoint2: Vec3): boolean {
  424. const d1 = -v3dot(normalVector, planePoint1);
  425. const d2 = -v3dot(normalVector, planePoint2);
  426. return _isInMembranePlane(testPoint, normalVector, Math.min(d1, d2), Math.max(d1, d2));
  427. }
  428. function _isInMembranePlane(testPoint: Vec3, normalVector: Vec3, min: number, max: number): boolean {
  429. const d = -v3dot(normalVector, testPoint);
  430. return d > min && d < max;
  431. }
  432. /** Generates a defined number of points on a sphere with radius = extent around the specified centroid */
  433. function generateSpherePoints(ctx: ANVILContext, numberOfSpherePoints: number): Vec3[] {
  434. const { centroid, extent } = ctx;
  435. const points = [];
  436. let oldPhi = 0, h, theta, phi;
  437. for(let k = 1, kl = numberOfSpherePoints + 1; k < kl; k++) {
  438. h = -1 + 2 * (k - 1) / (numberOfSpherePoints - 1);
  439. theta = Math.acos(h);
  440. phi = (k === 1 || k === numberOfSpherePoints) ? 0 : (oldPhi + 3.6 / Math.sqrt(numberOfSpherePoints * (1 - h * h))) % (2 * Math.PI);
  441. const point = v3create(
  442. extent * Math.sin(phi) * Math.sin(theta) + centroid[0],
  443. extent * Math.cos(theta) + centroid[1],
  444. extent * Math.cos(phi) * Math.sin(theta) + centroid[2]
  445. );
  446. points[k - 1] = point;
  447. oldPhi = phi;
  448. }
  449. return points;
  450. }
  451. /** Generates sphere points close to that of the initial membrane */
  452. function findProximateAxes(ctx: ANVILContext, membrane: MembraneCandidate): Vec3[] {
  453. const { numberOfSpherePoints, extent } = ctx;
  454. const points = generateSpherePoints(ctx, 30000);
  455. let j = 4;
  456. let sphere_pts2: Vec3[] = [];
  457. const s = 2 * extent / numberOfSpherePoints;
  458. while (sphere_pts2.length < numberOfSpherePoints) {
  459. const dsq = (s + j) * (s + j);
  460. sphere_pts2 = [];
  461. for (let i = 0, il = points.length; i < il; i++) {
  462. if (v3squaredDistance(points[i], membrane.spherePoint!) < dsq) {
  463. sphere_pts2.push(points[i]);
  464. }
  465. }
  466. j += 0.2;
  467. }
  468. return sphere_pts2;
  469. }
  470. interface HphobHphil {
  471. hphob: number,
  472. hphil: number
  473. }
  474. namespace HphobHphil {
  475. export function of(hphob: number, hphil: number) {
  476. return {
  477. hphob: hphob,
  478. hphil: hphil
  479. };
  480. }
  481. export function filtered(ctx: ANVILContext, filter?: Set<number>): HphobHphil {
  482. const { exposed, hydrophobic } = ctx;
  483. let hphob = 0;
  484. let hphil = 0;
  485. for (let k = 0, kl = exposed.length; k < kl; k++) {
  486. // testPoints have to be in putative membrane layer
  487. if (filter && !filter.has(k)) {
  488. continue;
  489. }
  490. if (hydrophobic[k]) {
  491. hphob++;
  492. } else {
  493. hphil++;
  494. }
  495. }
  496. return of(hphob, hphil);
  497. }
  498. }
  499. /** ANVIL-specific (not general) definition of membrane-favoring amino acids */
  500. const HYDROPHOBIC_AMINO_ACIDS = new Set(['ALA', 'CYS', 'GLY', 'HIS', 'ILE', 'LEU', 'MET', 'PHE', 'SER', 'TRP', 'VAL']);
  501. /** Returns true if ANVIL considers this as amino acid that favors being embedded in a membrane */
  502. export function isHydrophobic(label_comp_id: string): boolean {
  503. return HYDROPHOBIC_AMINO_ACIDS.has(label_comp_id);
  504. }
  505. /** Accessible surface area used for normalization. ANVIL uses 'Total-Side REL' values from NACCESS, from: Hubbard, S. J., & Thornton, J. M. (1993). naccess. Computer Program, Department of Biochemistry and Molecular Biology, University College London, 2(1). */
  506. export const MaxAsa: { [k: string]: number } = {
  507. 'ALA': 69.41,
  508. 'ARG': 201.25,
  509. 'ASN': 106.24,
  510. 'ASP': 102.69,
  511. 'CYS': 96.75,
  512. 'GLU': 134.74,
  513. 'GLN': 140.99,
  514. 'GLY': 32.33,
  515. 'HIS': 147.08,
  516. 'ILE': 137.96,
  517. 'LEU': 141.12,
  518. 'LYS': 163.30,
  519. 'MET': 156.64,
  520. 'PHE': 164.11,
  521. 'PRO': 119.90,
  522. 'SER': 78.11,
  523. 'THR': 101.70,
  524. 'TRP': 211.26,
  525. 'TYR': 177.38,
  526. 'VAL': 114.28
  527. };
  528. function setLocation(l: StructureElement.Location, structure: Structure, serialIndex: number) {
  529. l.structure = structure;
  530. l.unit = structure.units[structure.serialMapping.unitIndices[serialIndex]];
  531. l.element = structure.serialMapping.elementIndices[serialIndex];
  532. return l;
  533. }