secondary-structure.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Alexander Rose <alexander.rose@weirdbyte.de>
  5. * @author Sebastian Bittrich <sebastian.bittrich@rcsb.org>
  6. */
  7. import { SecondaryStructure } from 'mol-model/structure/model/properties/seconday-structure';
  8. import { ResidueIndex } from 'mol-model/structure';
  9. import { MoleculeType, SecondaryStructureType } from 'mol-model/structure/model/types';
  10. import { Vec3 } from 'mol-math/linear-algebra';
  11. import { GridLookup3D } from 'mol-math/geometry';
  12. import { SortedArray } from 'mol-data/int';
  13. import { IntAdjacencyGraph } from 'mol-math/graph';
  14. import { BitFlags } from 'mol-util';
  15. import { ElementIndex } from 'mol-model/structure/model/indexing';
  16. import { AtomicHierarchy, AtomicConformation } from '../atomic';
  17. import { ParamDefinition as PD } from 'mol-util/param-definition'
  18. /**
  19. * TODO bugs to fix:
  20. * - some turns are not detected correctly: see e.g. pdb:1acj - maybe more than 2 hbonds require some residue to donate electrons
  21. * - some sheets are not extended correctly: see e.g. pdb:1acj
  22. * - validate new helix definition
  23. * - validate new ordering of secondary structure elements
  24. */
  25. /** max distance between two C-alpha atoms to check for hbond */
  26. const caMaxDist = 9.0;
  27. /**
  28. * Constant for electrostatic energy in kcal/mol
  29. * f * q1 * q2
  30. * Q = -332 * 0.42 * 0.20
  31. *
  32. * f is the dimensional factor
  33. *
  34. * q1 and q2 are partial charges which are placed on the C,O
  35. * (+q1,-q1) and N,H (-q2,+q2)
  36. */
  37. const Q = -27.888
  38. /** cutoff for hbonds in kcal/mol, must be lower to be consider as an hbond */
  39. const hbondEnergyCutoff = -0.5
  40. /** prevent extremely low hbond energies */
  41. const hbondEnergyMinimal = -9.9
  42. interface DSSPContext {
  43. params: Partial<PD.Values<SecondaryStructureComputationParams>>,
  44. getResidueFlag: (f: DSSPType) => SecondaryStructureType,
  45. getFlagName: (f: DSSPType) => String,
  46. hierarchy: AtomicHierarchy
  47. proteinResidues: SortedArray<ResidueIndex>
  48. /** flags for each residue */
  49. flags: Uint32Array
  50. hbonds: DsspHbonds,
  51. torsionAngles: { phi: Float32Array, psi: Float32Array },
  52. backboneIndices: BackboneAtomIndices,
  53. conformation: AtomicConformation,
  54. ladders: Ladder[],
  55. bridges: Bridge[]
  56. }
  57. interface Ladder {
  58. previousLadder: number,
  59. nextLadder: number,
  60. firstStart: number,
  61. secondStart: number,
  62. secondEnd: number,
  63. firstEnd: number,
  64. type: BridgeType
  65. }
  66. const enum BridgeType {
  67. PARALLEL = 0x0,
  68. ANTI_PARALLEL = 0x1
  69. }
  70. class Bridge {
  71. partner1: number;
  72. partner2: number;
  73. type: BridgeType;
  74. constructor(p1: number, p2: number, type: BridgeType) {
  75. this.partner1 = Math.min(p1, p2)
  76. this.partner2 = Math.max(p1, p2)
  77. this.type = type
  78. }
  79. }
  80. type DSSPType = BitFlags<DSSPType.Flag>
  81. namespace DSSPType {
  82. export const is: (t: DSSPType, f: Flag) => boolean = BitFlags.has
  83. export const create: (f: Flag) => DSSPType = BitFlags.create
  84. export const enum Flag {
  85. _ = 0x0,
  86. H = 0x1,
  87. B = 0x2,
  88. E = 0x4,
  89. G = 0x8,
  90. I = 0x10,
  91. S = 0x20,
  92. T = 0x40,
  93. T3 = 0x80,
  94. T4 = 0x100,
  95. T5 = 0x200,
  96. T3S = 0x400, // marks 3-turn start
  97. T4S = 0x800,
  98. T5S = 0x1000
  99. }
  100. }
  101. export const SecondaryStructureComputationParams = {
  102. oldDefinition: PD.Boolean(true, { description: 'Whether to use the old DSSP convention for the annotation of turns and helices, causes them to be two residues shorter' }),
  103. oldOrdering: PD.Boolean(true, { description: 'Alpha-helices are preferred over 3-10 helices' })
  104. }
  105. export type SecondaryStructureComputationParams = typeof SecondaryStructureComputationParams
  106. export function computeSecondaryStructure(hierarchy: AtomicHierarchy,
  107. conformation: AtomicConformation) {
  108. // TODO use Zhang-Skolnik for CA alpha only parts or for coarse parts with per-residue elements
  109. return computeModelDSSP(hierarchy, conformation)
  110. }
  111. export function computeModelDSSP(hierarchy: AtomicHierarchy,
  112. conformation: AtomicConformation,
  113. params: Partial<PD.Values<SecondaryStructureComputationParams>> = {}): SecondaryStructure {
  114. params = { ...PD.getDefaultValues(SecondaryStructureComputationParams), ...params };
  115. const { lookup3d, proteinResidues } = calcAtomicTraceLookup3D(hierarchy, conformation)
  116. const backboneIndices = calcBackboneAtomIndices(hierarchy, proteinResidues)
  117. const hbonds = calcBackboneHbonds(hierarchy, conformation, proteinResidues, backboneIndices, lookup3d)
  118. const residueCount = proteinResidues.length
  119. const flags = new Uint32Array(residueCount)
  120. // console.log(`calculating secondary structure elements using ${ params.oldDefinition ? 'old' : 'revised'} definition and ${ params.oldOrdering ? 'old' : 'revised'} ordering of secondary structure elements`)
  121. const torsionAngles = calculateDihedralAngles(hierarchy, conformation, proteinResidues, backboneIndices)
  122. const ladders: Ladder[] = []
  123. const bridges: Bridge[] = []
  124. const getResidueFlag = params.oldDefinition ? getOriginalResidueFlag : getUpdatedResidueFlag
  125. const getFlagName = params.oldOrdering ? getOriginalFlagName : getUpdatedFlagName
  126. const ctx: DSSPContext = {
  127. params,
  128. getResidueFlag,
  129. getFlagName,
  130. hierarchy,
  131. proteinResidues,
  132. flags,
  133. hbonds,
  134. torsionAngles,
  135. backboneIndices,
  136. conformation,
  137. ladders,
  138. bridges
  139. }
  140. assignTurns(ctx)
  141. assignHelices(ctx)
  142. assignBends(ctx)
  143. assignBridges(ctx)
  144. assignLadders(ctx)
  145. assignSheets(ctx)
  146. const assignment = getDSSPAssignment(flags, getResidueFlag)
  147. const type = new Uint32Array(hierarchy.residues._rowCount) as unknown as SecondaryStructureType[]
  148. const keys: number[] = []
  149. const elements: SecondaryStructure.Element[] = []
  150. for (let i = 0, il = proteinResidues.length; i < il; ++i) {
  151. const assign = assignment[i]
  152. type[proteinResidues[i]] = assign
  153. const flag = getResidueFlag(flags[i])
  154. // TODO is this expected behavior? elements will be strictly split depending on 'winning' flag
  155. if (elements.length === 0 /* would fail at very start */ || flag !== (elements[elements.length - 1] as SecondaryStructure.Helix | SecondaryStructure.Sheet | SecondaryStructure.Turn).flags /* flag changed */) {
  156. elements[elements.length] = createElement(mapToKind(assign), flags[i], getResidueFlag)
  157. }
  158. keys[i] = elements.length - 1
  159. }
  160. const secondaryStructure: SecondaryStructure = {
  161. type,
  162. key: keys,
  163. elements: elements
  164. }
  165. return secondaryStructure
  166. }
  167. function createElement(kind: string, flag: DSSPType.Flag, getResidueFlag: (f: DSSPType) => SecondaryStructureType): SecondaryStructure.Element {
  168. // TODO would be nice to add more detailed information
  169. if (kind === 'helix') {
  170. return {
  171. kind: 'helix',
  172. flags: getResidueFlag(flag)
  173. } as SecondaryStructure.Helix
  174. } else if (kind === 'sheet') {
  175. return {
  176. kind: 'sheet',
  177. flags: getResidueFlag(flag)
  178. } as SecondaryStructure.Sheet
  179. } else if (kind === 'turn' || kind === 'bend') {
  180. return {
  181. kind: 'turn',
  182. flags: getResidueFlag(flag)
  183. }
  184. } else {
  185. return {
  186. kind: 'none'
  187. }
  188. }
  189. }
  190. function mapToKind(assignment: SecondaryStructureType.Flag) {
  191. if (assignment === SecondaryStructureType.SecondaryStructureDssp.H || assignment === SecondaryStructureType.SecondaryStructureDssp.G || assignment === SecondaryStructureType.SecondaryStructureDssp.I) {
  192. return 'helix'
  193. } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.B || assignment === SecondaryStructureType.SecondaryStructureDssp.E) {
  194. return 'sheet'
  195. } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.T) {
  196. return 'turn'
  197. } else if (assignment === SecondaryStructureType.SecondaryStructureDssp.S) {
  198. return 'bend'
  199. } else {
  200. return 'none'
  201. }
  202. }
  203. function calcAtomicTraceLookup3D(hierarchy: AtomicHierarchy, conformation: AtomicConformation) {
  204. const { x, y, z } = conformation;
  205. const { moleculeType, traceElementIndex } = hierarchy.derived.residue
  206. const indices: number[] = []
  207. const _proteinResidues: number[] = []
  208. for (let i = 0, il = moleculeType.length; i < il; ++i) {
  209. if (moleculeType[i] === MoleculeType.protein) {
  210. indices[indices.length] = traceElementIndex[i]
  211. _proteinResidues[_proteinResidues.length] = i
  212. }
  213. }
  214. const lookup3d = GridLookup3D({ x, y, z, indices: SortedArray.ofSortedArray(indices) }, 4);
  215. const proteinResidues = SortedArray.ofSortedArray<ResidueIndex>(_proteinResidues)
  216. return { lookup3d, proteinResidues }
  217. }
  218. interface BackboneAtomIndices {
  219. cIndices: ArrayLike<ElementIndex | -1>
  220. hIndices: ArrayLike<ElementIndex | -1>
  221. oIndices: ArrayLike<ElementIndex | -1>
  222. nIndices: ArrayLike<ElementIndex | -1>
  223. }
  224. function calcBackboneAtomIndices(hierarchy: AtomicHierarchy, proteinResidues: SortedArray<ResidueIndex>): BackboneAtomIndices {
  225. const residueCount = proteinResidues.length
  226. const { index } = hierarchy
  227. const c = new Int32Array(residueCount)
  228. const h = new Int32Array(residueCount)
  229. const o = new Int32Array(residueCount)
  230. const n = new Int32Array(residueCount)
  231. for (let i = 0, il = residueCount; i < il; ++i) {
  232. const rI = proteinResidues[i]
  233. c[i] = index.findAtomOnResidue(rI, 'C')
  234. h[i] = index.findAtomOnResidue(rI, 'H')
  235. o[i] = index.findAtomOnResidue(rI, 'O')
  236. n[i] = index.findAtomOnResidue(rI, 'N')
  237. }
  238. return {
  239. cIndices: c as unknown as ArrayLike<ElementIndex | -1>,
  240. hIndices: h as unknown as ArrayLike<ElementIndex | -1>,
  241. oIndices: o as unknown as ArrayLike<ElementIndex | -1>,
  242. nIndices: n as unknown as ArrayLike<ElementIndex | -1>,
  243. }
  244. }
  245. type DsspHbonds = IntAdjacencyGraph<{ readonly energies: ArrayLike<number> }>
  246. /**
  247. * Bend(i) =: [angle ((CW - Ca(i - 2)),(C"(i + 2) - C"(i))) > 70"]
  248. *
  249. * Type: S
  250. */
  251. function assignBends(ctx: DSSPContext) {
  252. const flags = ctx.flags
  253. const { x, y, z } = ctx.conformation
  254. const { traceElementIndex } = ctx.hierarchy.derived.residue
  255. const proteinResidues = ctx.proteinResidues
  256. const residueCount = proteinResidues.length
  257. const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i])
  258. const caPosPrev2 = Vec3()
  259. const caPos = Vec3()
  260. const caPosNext2 = Vec3()
  261. const nIndices = ctx.backboneIndices.nIndices
  262. const cPos = Vec3()
  263. const nPosNext = Vec3()
  264. const caMinus2 = Vec3()
  265. const caPlus2 = Vec3()
  266. f1: for (let i = 2; i < residueCount - 2; i++) {
  267. // check for peptide bond
  268. for (let k = 0; k < 4; k++) {
  269. let index = i + k - 2
  270. position(traceElementIndex[index], cPos)
  271. position(nIndices[index + 1], nPosNext)
  272. if (Vec3.squaredDistance(cPos, nPosNext) > 6.25 /* max squared peptide bond distance allowed */) {
  273. continue f1
  274. }
  275. }
  276. const oRIprev2 = proteinResidues[i - 2]
  277. const oRI = proteinResidues[i]
  278. const oRInext2 = proteinResidues[i + 2]
  279. const caAtomPrev2 = traceElementIndex[oRIprev2]
  280. const caAtom = traceElementIndex[oRI]
  281. const caAtomNext2 = traceElementIndex[oRInext2]
  282. position(caAtomPrev2, caPosPrev2)
  283. position(caAtom, caPos)
  284. position(caAtomNext2, caPosNext2)
  285. Vec3.sub(caMinus2, caPosPrev2, caPos)
  286. Vec3.sub(caPlus2, caPos, caPosNext2)
  287. const angle = Vec3.angle(caMinus2, caPlus2) * 360 / (2 * Math.PI)
  288. if (angle && angle > 70.00) {
  289. flags[i] |= DSSPType.Flag.S
  290. }
  291. }
  292. }
  293. function calculateDihedralAngles(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices): { phi: Float32Array, psi: Float32Array } {
  294. const { cIndices, nIndices } = backboneIndices
  295. const { index } = hierarchy
  296. const { x, y, z } = conformation
  297. const { traceElementIndex } = hierarchy.derived.residue
  298. const residueCount = proteinResidues.length
  299. const position = (i: number, v: Vec3) => i === -1 ? Vec3.setNaN(v) : Vec3.set(v, x[i], y[i], z[i])
  300. let cPosPrev = Vec3(), caPosPrev = Vec3(), nPosPrev = Vec3()
  301. let cPos = Vec3(), caPos = Vec3(), nPos = Vec3()
  302. let cPosNext = Vec3(), caPosNext = Vec3(), nPosNext = Vec3()
  303. if (residueCount === 0) return { phi: new Float32Array(0), psi: new Float32Array(0) }
  304. const phi: Float32Array = new Float32Array(residueCount - 1)
  305. const psi: Float32Array = new Float32Array(residueCount - 1)
  306. position(-1, cPosPrev)
  307. position(-1, caPosPrev)
  308. position(-1, nPosPrev)
  309. position(cIndices[0], cPos)
  310. position(traceElementIndex[proteinResidues[0]], caPos)
  311. position(nIndices[0], nPos)
  312. position(cIndices[1], cPosNext)
  313. position(traceElementIndex[proteinResidues[1]], caPosNext)
  314. position(nIndices[1], nPosNext)
  315. for (let i = 0; i < residueCount - 1; ++i) {
  316. // ignore C-terminal residue as acceptor
  317. if (index.findAtomOnResidue(proteinResidues[i], 'OXT') !== -1) continue
  318. // returns NaN for missing atoms
  319. phi[i] = Vec3.dihedralAngle(cPosPrev, nPos, caPos, cPos)
  320. psi[i] = Vec3.dihedralAngle(nPos, caPos, cPos, nPosNext)
  321. cPosPrev = cPos, caPosPrev = caPos, nPosPrev = nPos
  322. cPos = cPosNext, caPos = caPosNext, nPos = nPosNext
  323. position(cIndices[i + 1], cPosNext)
  324. position(traceElementIndex[proteinResidues[i + 1]], caPosNext)
  325. position(nIndices[i + 1], nPosNext)
  326. }
  327. return { phi, psi };
  328. }
  329. function calcBackboneHbonds(hierarchy: AtomicHierarchy, conformation: AtomicConformation, proteinResidues: SortedArray<ResidueIndex>, backboneIndices: BackboneAtomIndices, lookup3d: GridLookup3D): DsspHbonds {
  330. const { cIndices, hIndices, nIndices, oIndices } = backboneIndices
  331. const { index } = hierarchy
  332. const { x, y, z } = conformation
  333. const { traceElementIndex } = hierarchy.derived.residue
  334. const residueCount = proteinResidues.length
  335. const position = (i: number, v: Vec3) => Vec3.set(v, x[i], y[i], z[i])
  336. const oAtomResidues: number[] = [];
  337. const nAtomResidues: number[] = [];
  338. const energies: number[] = [];
  339. const oPos = Vec3()
  340. const cPos = Vec3()
  341. const caPos = Vec3()
  342. const nPos = Vec3()
  343. const hPos = Vec3()
  344. const cPosPrev = Vec3()
  345. const oPosPrev = Vec3()
  346. for (let i = 0, il = proteinResidues.length; i < il; ++i) {
  347. const oPI = i
  348. const oRI = proteinResidues[i]
  349. const oAtom = oIndices[oPI]
  350. const cAtom = cIndices[oPI]
  351. const caAtom = traceElementIndex[oRI]
  352. // continue if residue is missing O or C atom
  353. if (oAtom === -1 || cAtom === -1) continue
  354. // ignore C-terminal residue as acceptor
  355. if (index.findAtomOnResidue(oRI, 'OXT') !== -1) continue
  356. position(oAtom, oPos)
  357. position(cAtom, cPos)
  358. position(caAtom, caPos)
  359. const { indices, count } = lookup3d.find(caPos[0], caPos[1], caPos[2], caMaxDist)
  360. for (let j = 0; j < count; ++j) {
  361. const nPI = indices[j]
  362. // ignore bonds within a residue or to prev or next residue, TODO take chain border into account
  363. if (nPI === oPI || nPI - 1 === oPI || nPI + 1 === oPI) continue
  364. const nAtom = nIndices[nPI]
  365. if (nAtom === -1) continue
  366. position(nAtom, nPos)
  367. const hAtom = hIndices[nPI]
  368. if (hAtom === -1) {
  369. // approximate calculation of H position, TODO factor out
  370. if (nPI === 0) continue
  371. const nPIprev = nPI - 1
  372. const oAtomPrev = oIndices[nPIprev]
  373. const cAtomPrev = cIndices[nPIprev]
  374. if (oAtomPrev === -1 || cAtomPrev === -1) continue
  375. position(oAtomPrev, oPosPrev)
  376. position(cAtomPrev, cPosPrev)
  377. Vec3.sub(hPos, cPosPrev, oPosPrev)
  378. const dist = Vec3.distance(oPosPrev, cPosPrev)
  379. Vec3.scaleAndAdd(hPos, nPos, hPos, 1 / dist)
  380. } else {
  381. position(hAtom, hPos)
  382. }
  383. const e = calcHbondEnergy(oPos, cPos, nPos, hPos)
  384. if (e > hbondEnergyCutoff) continue
  385. oAtomResidues[oAtomResidues.length] = oPI
  386. nAtomResidues[nAtomResidues.length] = nPI
  387. energies[energies.length] = e
  388. }
  389. }
  390. return buildHbondGraph(residueCount, oAtomResidues, nAtomResidues, energies);
  391. }
  392. function buildHbondGraph(residueCount: number, oAtomResidues: number[], nAtomResidues: number[], energies: number[]) {
  393. const builder = new IntAdjacencyGraph.DirectedEdgeBuilder(residueCount, oAtomResidues, nAtomResidues);
  394. const _energies = new Float32Array(builder.slotCount);
  395. for (let i = 0, _i = builder.edgeCount; i < _i; i++) {
  396. builder.addNextEdge();
  397. builder.assignProperty(_energies, energies[i]);
  398. }
  399. return builder.createGraph({ energies });
  400. }
  401. /** Original priority: H,B,E,G,I,T,S */
  402. function getOriginalResidueFlag(f: DSSPType) {
  403. if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
  404. if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
  405. if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
  406. if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
  407. if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
  408. if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
  409. if (DSSPType.is(f, DSSPType.Flag.S)) return SecondaryStructureType.SecondaryStructureDssp.S
  410. return SecondaryStructureType.Flag.None
  411. }
  412. function getOriginalFlagName(f: DSSPType) {
  413. if (DSSPType.is(f, DSSPType.Flag.H)) return 'H'
  414. if (DSSPType.is(f, DSSPType.Flag.E)) return 'E'
  415. if (DSSPType.is(f, DSSPType.Flag.B)) return 'B'
  416. if (DSSPType.is(f, DSSPType.Flag.G)) return 'G'
  417. if (DSSPType.is(f, DSSPType.Flag.I)) return 'I'
  418. if (DSSPType.is(f, DSSPType.Flag.T)) return 'T'
  419. if (DSSPType.is(f, DSSPType.Flag.S)) return 'S'
  420. return '-'
  421. }
  422. /** Version 2.1.0 priority: I,H,B,E,G,T,S */
  423. function getUpdatedResidueFlag(f: DSSPType) {
  424. if (DSSPType.is(f, DSSPType.Flag.I)) return SecondaryStructureType.SecondaryStructureDssp.I
  425. if (DSSPType.is(f, DSSPType.Flag.H)) return SecondaryStructureType.SecondaryStructureDssp.H
  426. if (DSSPType.is(f, DSSPType.Flag.E)) return SecondaryStructureType.SecondaryStructureDssp.E
  427. if (DSSPType.is(f, DSSPType.Flag.B)) return SecondaryStructureType.SecondaryStructureDssp.B
  428. if (DSSPType.is(f, DSSPType.Flag.G)) return SecondaryStructureType.SecondaryStructureDssp.G
  429. if (DSSPType.is(f, DSSPType.Flag.T)) return SecondaryStructureType.SecondaryStructureDssp.T
  430. if (DSSPType.is(f, DSSPType.Flag.S)) return SecondaryStructureType.SecondaryStructureDssp.S
  431. return SecondaryStructureType.Flag.None
  432. }
  433. function getUpdatedFlagName(f: DSSPType) {
  434. if (DSSPType.is(f, DSSPType.Flag.I)) return 'I'
  435. if (DSSPType.is(f, DSSPType.Flag.H)) return 'H'
  436. if (DSSPType.is(f, DSSPType.Flag.E)) return 'E'
  437. if (DSSPType.is(f, DSSPType.Flag.B)) return 'B'
  438. if (DSSPType.is(f, DSSPType.Flag.G)) return 'G'
  439. if (DSSPType.is(f, DSSPType.Flag.T)) return 'T'
  440. if (DSSPType.is(f, DSSPType.Flag.S)) return 'S'
  441. return '-'
  442. }
  443. function getDSSPAssignment(flags: Uint32Array, getResidueFlag: (f: DSSPType) => SecondaryStructureType) {
  444. const type = new Uint32Array(flags.length)
  445. for (let i = 0, il = flags.length; i < il; ++i) {
  446. const f = DSSPType.create(flags[i])
  447. type[i] = getResidueFlag(f)
  448. }
  449. return type as unknown as ArrayLike<SecondaryStructureType>
  450. }
  451. /**
  452. * E = Q * (1/r(ON) + l/r(CH) - l/r(OH) - l/r(CN))
  453. */
  454. function calcHbondEnergy(oPos: Vec3, cPos: Vec3, nPos: Vec3, hPos: Vec3) {
  455. const distOH = Vec3.distance(oPos, hPos)
  456. const distCH = Vec3.distance(cPos, hPos)
  457. const distCN = Vec3.distance(cPos, nPos)
  458. const distON = Vec3.distance(oPos, nPos)
  459. const e1 = Q / distOH - Q / distCH
  460. const e2 = Q / distCN - Q / distON
  461. const e = e1 + e2
  462. // cap lowest possible energy
  463. if (e < hbondEnergyMinimal)
  464. return hbondEnergyMinimal
  465. return e
  466. }
  467. /**
  468. * The basic turn pattern is a single H bond of type (i, i + n).
  469. * We assign an n-turn at residue i if there is an H bond from CO(i) to NH(i + n),
  470. * i.e., “n-turn(i)=: Hbond(i, i + n), n = 3, 4, 5.”
  471. *
  472. * Type: T
  473. */
  474. function assignTurns(ctx: DSSPContext) {
  475. const { proteinResidues, hbonds, flags, hierarchy } = ctx
  476. const { chains, residueAtomSegments, chainAtomSegments } = hierarchy
  477. const { label_asym_id } = chains
  478. const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
  479. for (let idx = 0; idx < 3; idx++) {
  480. for (let i = 0, il = proteinResidues.length - 1; i < il; ++i) {
  481. const rI = proteinResidues[i]
  482. const cI = chainAtomSegments.index[residueAtomSegments.offsets[rI]]
  483. // TODO should take sequence gaps into account
  484. const rN = proteinResidues[i + idx + 3]
  485. const cN = chainAtomSegments.index[residueAtomSegments.offsets[rN]]
  486. // check if on same chain
  487. if (!label_asym_id.areValuesEqual(cI, cN)) continue
  488. // check if hbond exists
  489. if (hbonds.getDirectedEdgeIndex(i, i + idx + 3) !== -1) {
  490. flags[i] |= turnFlag[idx + 3] | turnFlag[idx]
  491. if (ctx.params.oldDefinition) {
  492. for (let k = 1; k < idx + 3; ++k) {
  493. flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T
  494. }
  495. } else {
  496. for (let k = 0; k <= idx + 3; ++k) {
  497. flags[i + k] |= turnFlag[idx + 3] | DSSPType.Flag.T
  498. }
  499. }
  500. }
  501. }
  502. }
  503. }
  504. /**
  505. * Two nonoverlapping stretches of three residues each, i - 1, i, i + 1 and j - 1, j, j + 1,
  506. * form either a parallel or antiparallel bridge, depending on which of
  507. * two basic patterns is matched. We assign a bridge between residues i and j
  508. * if there are two H bonds characteristic of P-structure; in particular,
  509. *
  510. * Parallel Bridge(i, j) =:
  511. * [Hbond(i - 1, j) and Hbond(j, i + 1)] or
  512. * [Hbond(j - 1, i) and Hbond(i, j + 1)]
  513. *
  514. * Antiparallel Bridge(i, j) =:
  515. * [Hbond(i, j) and Hbond(j, i)] or
  516. * [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)]
  517. *
  518. * Type: B
  519. */
  520. function assignBridges(ctx: DSSPContext) {
  521. const { proteinResidues, hbonds, flags, bridges } = ctx
  522. const { offset, b } = hbonds
  523. let i: number, j: number
  524. for (let k = 0, kl = proteinResidues.length; k < kl; ++k) {
  525. for (let t = offset[k], _t = offset[k + 1]; t < _t; t++) {
  526. const l = b[t]
  527. if (k > l) continue
  528. // Parallel Bridge(i, j) =: [Hbond(i - 1, j) and Hbond(j, i + 1)]
  529. i = k + 1 // k is i - 1
  530. j = l
  531. if (i !== j && hbonds.getDirectedEdgeIndex(j, i + 1) !== -1) {
  532. flags[i] |= DSSPType.Flag.B
  533. flags[j] |= DSSPType.Flag.B
  534. // TODO move to constructor, actually omit object all together
  535. bridges[bridges.length] = new Bridge(i, j, BridgeType.PARALLEL)
  536. }
  537. // Parallel Bridge(i, j) =: [Hbond(j - 1, i) and Hbond(i, j + 1)]
  538. i = k
  539. j = l - 1 // l is j + 1
  540. if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i) !== -1) {
  541. flags[i] |= DSSPType.Flag.B
  542. flags[j] |= DSSPType.Flag.B
  543. bridges[bridges.length] = new Bridge(j, i, BridgeType.PARALLEL)
  544. }
  545. // Antiparallel Bridge(i, j) =: [Hbond(i, j) and Hbond(j, i)]
  546. i = k
  547. j = l
  548. if (i !== j && hbonds.getDirectedEdgeIndex(j, i) !== -1) {
  549. flags[i] |= DSSPType.Flag.B
  550. flags[j] |= DSSPType.Flag.B
  551. bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL)
  552. }
  553. // Antiparallel Bridge(i, j) =: [Hbond(i - 1, j + 1) and Hbond(j - 1, i + l)]
  554. i = k + 1
  555. j = l - 1
  556. if (i !== j && hbonds.getDirectedEdgeIndex(j - 1, i + 1) !== -1) {
  557. flags[i] |= DSSPType.Flag.B
  558. flags[j] |= DSSPType.Flag.B
  559. bridges[bridges.length] = new Bridge(j, i, BridgeType.ANTI_PARALLEL)
  560. }
  561. }
  562. }
  563. bridges.sort((a, b) => a.partner1 > b.partner1 ? 1 : a.partner1 < b.partner1 ? -1 : 0)
  564. }
  565. /**
  566. * A minimal helix is defined by two consecutive n-turns.
  567. * For example, a 4-helix, of minimal length 4 from residues i to i + 3,
  568. * requires 4-turns at residues i - 1 and i,
  569. *
  570. * 3-helix(i,i + 2)=: [3-turn(i - 1) and 3-turn(i)]
  571. * 4-helix(i,i + 3)=: [4-turn(i - 1) and 4-turn(i)]
  572. * 5-helix(i,i + 4)=: [5-turn(i - 1) and 5-turn(i)]
  573. *
  574. * Type: G (n=3), H (n=4), I (n=5)
  575. */
  576. function assignHelices(ctx: DSSPContext) {
  577. const { proteinResidues, flags } = ctx
  578. const turnFlag = [DSSPType.Flag.T3S, DSSPType.Flag.T4S, DSSPType.Flag.T5S, DSSPType.Flag.T3, DSSPType.Flag.T4, DSSPType.Flag.T5]
  579. const helixFlag = [0, 0, 0, DSSPType.Flag.G, DSSPType.Flag.H, DSSPType.Flag.I]
  580. const helixCheckOrder = ctx.params.oldOrdering ? [4, 3, 5] : [3, 4, 5]
  581. for (let ni = 0; ni < helixCheckOrder.length; ni++) {
  582. const n = helixCheckOrder[ni]
  583. for (let i = 1, il = proteinResidues.length - n; i < il; i++) {
  584. const fI = DSSPType.create(flags[i])
  585. const fI1 = DSSPType.create(flags[i - 1])
  586. const fI2 = DSSPType.create(flags[i + 1])
  587. // TODO rework to elegant solution which will not break instantly
  588. if (ctx.params.oldOrdering) {
  589. if ((n === 3 && (DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.H)) || // for 3-10 yield to alpha helix
  590. (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices
  591. continue
  592. }
  593. } else {
  594. if ((n === 4 && (DSSPType.is(fI, DSSPType.Flag.G) || DSSPType.is(fI2, DSSPType.Flag.G)) || // for alpha helix yield to 3-10
  595. (n === 5 && ((DSSPType.is(fI, DSSPType.Flag.H) || DSSPType.is(fI, DSSPType.Flag.G)) || (DSSPType.is(fI2, DSSPType.Flag.H) || DSSPType.is(fI2, DSSPType.Flag.G)))))) { // for pi yield to all other helices
  596. continue
  597. }
  598. }
  599. if (DSSPType.is(fI, turnFlag[n]) && DSSPType.is(fI, turnFlag[n - 3]) && // check fI for turn start of proper type
  600. DSSPType.is(fI1, turnFlag[n]) && DSSPType.is(fI1, turnFlag[n - 3])) { // check fI1 accordingly
  601. if (ctx.params.oldDefinition) {
  602. for (let k = 0; k < n; k++) {
  603. flags[i + k] |= helixFlag[n]
  604. }
  605. } else {
  606. for (let k = -1; k <= n; k++) {
  607. flags[i + k] |= helixFlag[n]
  608. }
  609. }
  610. }
  611. }
  612. }
  613. }
  614. /**
  615. * ladder=: set of one or more consecutive bridges of identical type
  616. *
  617. * Type: E
  618. */
  619. function assignLadders(ctx: DSSPContext) {
  620. const { bridges, ladders } = ctx
  621. // create ladders
  622. for (let bridgeIndex = 0; bridgeIndex < bridges.length; bridgeIndex++) {
  623. const bridge = bridges[bridgeIndex]
  624. let found = false
  625. for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) {
  626. const ladder = ladders[ladderIndex]
  627. if (shouldExtendLadder(ladder, bridge)) {
  628. found = true
  629. ladder.firstEnd++
  630. if (bridge.type === BridgeType.PARALLEL) {
  631. ladder.secondEnd++
  632. } else {
  633. ladder.secondStart--
  634. }
  635. }
  636. }
  637. // no suitable assignment: create new ladder with single bridge as content
  638. if (!found) {
  639. ladders[ladders.length] = {
  640. previousLadder: 0,
  641. nextLadder: 0,
  642. firstStart: bridge.partner1,
  643. firstEnd: bridge.partner1,
  644. secondStart: bridge.partner2,
  645. secondEnd: bridge.partner2,
  646. type: bridge.type
  647. }
  648. }
  649. }
  650. // connect ladders
  651. for (let ladderIndex1 = 0; ladderIndex1 < ladders.length; ladderIndex1++) {
  652. const ladder1 = ladders[ladderIndex1]
  653. for (let ladderIndex2 = ladderIndex1; ladderIndex2 < ladders.length; ladderIndex2++) {
  654. const ladder2 = ladders[ladderIndex2]
  655. if (resemblesBulge(ladder1, ladder2)) {
  656. ladder1.nextLadder = ladderIndex2
  657. ladder2.previousLadder = ladderIndex1
  658. }
  659. }
  660. }
  661. }
  662. /**
  663. * For beta structures, we define: a bulge-linked ladder consists of two ladders or bridges of the same type
  664. * connected by at most one extra residue of one strand and at most four extra residues on the other strand,
  665. * all residues in bulge-linked ladders are marked E, including any extra residues.
  666. */
  667. function resemblesBulge(ladder1: Ladder, ladder2: Ladder) {
  668. if (!(ladder1.type === ladder2.type && ladder2.firstStart - ladder1.firstEnd < 6 &&
  669. ladder1.firstStart < ladder2.firstStart && ladder2.nextLadder === 0)) return false
  670. if (ladder1.type === BridgeType.PARALLEL) {
  671. return bulgeCriterion2(ladder1, ladder2)
  672. } else {
  673. return bulgeCriterion2(ladder2, ladder1)
  674. }
  675. }
  676. function bulgeCriterion2(ladder1: Ladder, ladder2: Ladder) {
  677. return ladder2.secondStart - ladder1.secondEnd > 0 && ((ladder2.secondStart - ladder1.secondEnd < 6 &&
  678. ladder2.firstStart - ladder1.firstEnd < 3) || ladder2.secondStart - ladder1.secondEnd < 3)
  679. }
  680. function shouldExtendLadder(ladder: Ladder, bridge: Bridge): boolean {
  681. // in order to extend ladders, same type must be present
  682. if (bridge.type !== ladder.type) return false
  683. // only extend if residue 1 is sequence neighbor with regard to ladder
  684. if (bridge.partner1 !== ladder.firstEnd + 1) return false
  685. if (bridge.type === BridgeType.PARALLEL) {
  686. if (bridge.partner2 === ladder.secondEnd + 1) {
  687. return true
  688. }
  689. } else {
  690. if (bridge.partner2 === ladder.secondStart - 1) {
  691. return true
  692. }
  693. }
  694. return false
  695. }
  696. function isHelixType(f: DSSPType) {
  697. return DSSPType.is(f, DSSPType.Flag.G) || DSSPType.is(f, DSSPType.Flag.H) || DSSPType.is(f, DSSPType.Flag.I)
  698. }
  699. /**
  700. * sheet=: set of one or more ladders connected by shared residues
  701. *
  702. * Type: E
  703. */
  704. function assignSheets(ctx: DSSPContext) {
  705. const { ladders, flags } = ctx
  706. for (let ladderIndex = 0; ladderIndex < ladders.length; ladderIndex++) {
  707. const ladder = ladders[ladderIndex]
  708. for (let lcount = ladder.firstStart; lcount <= ladder.firstEnd; lcount++) {
  709. const diff = ladder.firstStart - lcount
  710. const l2count = ladder.secondStart - diff
  711. if (ladder.firstStart !== ladder.firstEnd) {
  712. flags[lcount] |= DSSPType.Flag.E
  713. flags[l2count] |= DSSPType.Flag.E
  714. } else {
  715. if (!isHelixType(flags[lcount]) && DSSPType.is(flags[lcount], DSSPType.Flag.E)) {
  716. flags[lcount] |= DSSPType.Flag.B
  717. }
  718. if (!isHelixType(flags[l2count]) && DSSPType.is(flags[l2count], DSSPType.Flag.E)) {
  719. flags[l2count] |= DSSPType.Flag.B
  720. }
  721. }
  722. }
  723. if (ladder.nextLadder === 0) continue
  724. const conladder = ladders[ladder.nextLadder]
  725. for (let lcount = ladder.firstStart; lcount <= conladder.firstEnd; lcount++) {
  726. flags[lcount] |= DSSPType.Flag.E
  727. }
  728. if (ladder.type === BridgeType.PARALLEL) {
  729. for (let lcount = ladder.secondStart; lcount <= conladder.secondEnd; lcount++) {
  730. flags[lcount] |= DSSPType.Flag.E
  731. }
  732. } else {
  733. for (let lcount = conladder.secondEnd; lcount <= ladder.secondStart; lcount++) {
  734. flags[lcount] |= DSSPType.Flag.E
  735. }
  736. }
  737. }
  738. }