algorithm.ts 23 KB

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