OSCR

Penetrating potential evaluation of cyclotriphosphazenes target blood-brain barrier via computational toxicology methods.

Code ↔ Paper

2 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 2 matches
  1. [1] § Results › BBB penetrating mechanisms of CTPs › Stable binding of HPCTP with two SLC transporters ↔ GMXMMPBSA/amber_outputs.py, lines 1780–1899 · score 0.59 · van der Waals, polar solvation, electrostatic, Binding
  2. [2] § STAR★Methods › Method details › Molecular dynamics simulations ↔ GMXMMPBSA/radii.py, lines 1–47 · score 0.54 · TIP3P, force field, solvation, Amber

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 2,028 lines · 99 KB · GPL-3.0 · 1 match

  1. """
  2. This module contains all the classes and code to collect data and calculate
  3. statistics from the output files of various calculation types. Each calculation
  4. type needs its own class.
  5. All data is stored in a special class derived from the list.
  6. """
  7. # ##############################################################################
  8. # GPLv3 LICENSE INFO #
  9. # #
  10. # Copyright (C) 2020 Mario S. Valdés-Tresanco and Mario E. Valdés-Tresanco #
  11. # Copyright (C) 2014 Jason Swails, Bill Miller III, and Dwight McGee #
  12. # #
  13. # Project: https://github.com/Valdes-Tresanco-MS/gmx_MMPBSA #
  14. # #
  15. # This program is free software; you can redistribute it and/or modify it #
  16. # under the terms of the GNU General Public License version 3 as published #
  17. # by the Free Software Foundation. #
  18. # #
  19. # This program is distributed in the hope that it will be useful, but #
  20. # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY #
  21. # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License #
  22. # for more details. #
  23. # ##############################################################################
  24. import logging
  25. from copy import deepcopy
  26. from math import sqrt
  27. from GMXMMPBSA.exceptions import (OutputError, LengthError, DecompError, GMXMMPBSA_ERROR)
  28. from GMXMMPBSA.utils import EnergyVector, block_statistics, get_std
  29. from types import SimpleNamespace
  30. import numpy as np
  31. import sys
  32. import re
  33. from csv import writer
  34. idecompString = ['idecomp = 0: No decomposition analysis',
  35. 'idecomp = 1: Per-residue decomp adding 1-4 interactions to Internal.',
  36. 'idecomp = 2: Per-residue decomp adding 1-4 interactions to EEL and VDW.',
  37. 'idecomp = 3: Pairwise decomp adding 1-4 interactions to Internal.',
  38. 'idecomp = 4: Pairwise decomp adding 1-4 interactions to EEL and VDW.']
  39. # Keep the summary rule aligned with the current eight-column statistics rows.
  40. sep = '-' * 101
  41. data_key_owner = {'BOND': ['GGAS', 'TOTAL'], 'ANGLE': ['GGAS', 'TOTAL'], 'DIHED': ['GGAS', 'TOTAL'],
  42. 'VDWAALS': ['GGAS', 'TOTAL'], 'EEL': ['GGAS', 'TOTAL'], '1-4 VDW': ['GGAS', 'TOTAL'],
  43. '1-4 EEL': ['GGAS', 'TOTAL'],
  44. # charmm
  45. 'UB': ['GGAS', 'TOTAL'], 'IMP': ['GGAS', 'TOTAL'], 'CMAP': ['GGAS', 'TOTAL'],
  46. # non lineal PB
  47. 'EEL+EPB': ['TOTAL'],
  48. # PB
  49. 'EPB': ['GSOLV', 'TOTAL'], 'ENPOLAR': ['GSOLV', 'TOTAL'], 'EDISPER': ['GSOLV', 'TOTAL'],
  50. # GB
  51. 'EGB': ['GSOLV', 'TOTAL'], 'ESURF': ['GSOLV', 'TOTAL'],
  52. # QM/GB
  53. 'ESCF': ['GGAS', 'TOTAL'],
  54. # RISM
  55. 'POLAR SOLV': ['GSOLV', 'TOTAL'], 'APOLAR SOLV': ['GSOLV', 'TOTAL'],
  56. 'ERISM': ['GSOLV', 'TOTAL'],
  57. # NMODE and QH
  58. 'TRANSLATIONAL': ['TOTAL'], 'ROTATIONAL': ['TOTAL'], 'VIBRATIONAL': ['TOTAL']
  59. }
  60. def _vector_statistics(vector):
  61. """Return legacy and block statistics for one energy vector."""
  62. block_sd = block_sem = float('nan')
  63. _, _, block_sd, block_sem = block_statistics(vector)
  64. return (float(vector.mean()), float(vector.stdev()), float(vector.std()),
  65. float(vector.semp()), float(vector.sem()), block_sd, block_sem)
  66. class AmberOutput(dict):
  67. """
  68. Base Amber output class. It takes a basename as a file name and parses
  69. through all of the thread-specific output files (assumed to have the suffix
  70. .# where # spans from 0 to num_files - 1
  71. """
  72. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1, '1-4 VDW': 2, '1-4 EEL': 2, 'EPOL': 1}
  73. def __init__(self, mol: str, INPUT, chamber=False, **kwargs):
  74. super(AmberOutput, self).__init__(**kwargs)
  75. self.numframes = None
  76. self.mol = mol
  77. self.INPUT = INPUT
  78. self.chamber = chamber
  79. self.basename = None
  80. self.num_files = None
  81. self.frame_idx = 0
  82. self.extraframe_idx = 0
  83. self.is_read = False
  84. self.apbs = INPUT['pb']['sander_apbs']
  85. # This variable is used to get if the nmode calculation hasn't at least one frame
  86. self.no_nmode_convergence = False
  87. self.data_keys = ['BOND', 'ANGLE', 'DIHED', 'VDWAALS', 'EEL', '1-4 VDW', '1-4 EEL']
  88. self.chamber_keys = ['CMAP', 'IMP', 'UB']
  89. self.composite_keys = ['GGAS', 'GSOLV', 'TOTAL']
  90. if self.chamber:
  91. for key in self.chamber_keys:
  92. if key not in self.data_keys:
  93. self.data_keys.insert(3, key)
  94. def parse_from_file(self, basename, num_files=1, numframes=1):
  95. self.num_files = num_files
  96. self.basename = basename
  97. self.temperature = self.INPUT['general']['temperature']
  98. self.numframes = numframes
  99. for key in self.data_keys:
  100. self[key] = EnergyVector(numframes)
  101. for key in self.composite_keys:
  102. self[key] = EnergyVector(numframes)
  103. AmberOutput._read(self)
  104. self._fill_composite_terms()
  105. def _print_vectors(self, csvwriter):
  106. """ Prints the energy vectors to a CSV file for easy viewing
  107. in spreadsheets
  108. """
  109. print_keys = list(self.data_keys)
  110. # Add on the composite keys
  111. print_keys += self.composite_keys
  112. # write the header
  113. csvwriter.writerow(['Frame #'] + print_keys)
  114. # write out each frame
  115. c = self.INPUT['nmode']['nmstartframe'] if self.__class__ == NMODEout else self.INPUT['general']['startframe']
  116. for i in range(self.numframes):
  117. csvwriter.writerow([c] + [round(self[key][i], 2) for key in print_keys])
  118. c += self.INPUT['nmode']['nminterval'] if self.__class__ == NMODEout else self.INPUT['general']['interval']
  119. def set_frame_range(self, start=None, end=None, interval=None):
  120. d = deepcopy(self)
  121. for key in d.data_keys:
  122. d[key] = d[key][start:end:interval]
  123. d._fill_composite_terms()
  124. return d
  125. def summary_output(self):
  126. if not self.is_read:
  127. raise OutputError('Cannot print summary before reading output files')
  128. text = [f'{self.mol.capitalize()}:']
  129. summary = self.summary()
  130. for c, row in enumerate(summary, start=1):
  131. key, avg, stdev, std, semp, sem, block_sd, block_sem = row
  132. if key in ['GGAS', 'TOTAL']:
  133. text.append('')
  134. if isinstance(avg, str):
  135. text.extend(
  136. (
  137. f'{key:16s} {avg:>13s} {stdev:>13s} {std:>10s} {semp:>12s} {sem:>10s} '
  138. f'{block_sd:>10s} {block_sem:>10s}',
  139. sep,
  140. )
  141. )
  142. else:
  143. text.append(f'{key:16s} {avg:13.2f} {stdev:13.2f} {std:10.2f} {semp:12.2f} {sem:10.2f} '
  144. f'{block_sd:10.2f} {block_sem:10.2f}')
  145. return '\n'.join(text) + '\n\n'
  146. def summary(self):
  147. """ Returns a formatted string that can be printed directly to the
  148. output file
  149. """
  150. if not self.is_read:
  151. raise OutputError('Cannot print summary before reading output files')
  152. comp_name = 'Entropy Component' if self.__class__ in [NMODEout, QHout] else 'Energy Component'
  153. summary_list = [[comp_name, 'Average', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM']]
  154. for key in self.data_keys:
  155. # Skip the composite terms, since we print those at the end
  156. if key in self.composite_keys:
  157. continue
  158. avg = float(self[key].mean())
  159. stdev = float(self[key].stdev())
  160. semp = float(self[key].semp())
  161. std = float(self[key].std())
  162. sem = float(self[key].sem())
  163. _, _, block_sd, block_sem = block_statistics(self[key])
  164. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  165. for key in self.composite_keys:
  166. # Now print out the composite terms
  167. avg = float(self[key].mean())
  168. stdev = float(self[key].stdev())
  169. semp = float(self[key].semp())
  170. std = float(self[key].std())
  171. sem = float(self[key].sem())
  172. _, _, block_sd, block_sem = block_statistics(self[key])
  173. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  174. return summary_list
  175. def _read(self):
  176. """ Internal reading function. This should be called at the end of __init__"""
  177. if self.is_read:
  178. return None # don't read through them twice
  179. # Loop through all filenames
  180. for fileno in range(self.num_files):
  181. with open('%s.%d' % (self.basename, fileno)) as output_file:
  182. self._get_energies(output_file)
  183. self._extra_reading(fileno)
  184. self._fill_nmode_values()
  185. self.is_read = True
  186. def _get_energies(self, output_file):
  187. pass
  188. def _extra_reading(self, fileno):
  189. pass
  190. def _fill_nmode_values(self):
  191. pass
  192. def _fill_composite_terms(self):
  193. """
  194. Fills in the composite terms WITHOUT adding in terms we're not printing.
  195. This should be called after the final verbosity level has been set (based
  196. on whether or not certain terms need to be added in)
  197. """
  198. for key in self.composite_keys:
  199. self[key] = EnergyVector(self.numframes)
  200. for key in self.data_keys:
  201. for component in data_key_owner[key]:
  202. self[component] = self[key] + self[component]
  203. class IEout(dict):
  204. """
  205. Interaction Entropy output
  206. """
  207. def __init__(self, INPUT, method, **kwargs):
  208. super(IEout, self).__init__(**kwargs)
  209. self.INPUT = INPUT
  210. self.method = method
  211. self.block_analysis = []
  212. def parse_from_dict(self, d: dict):
  213. values = dict(d)
  214. self.block_analysis = values.pop('block_analysis', [])
  215. self.update(values)
  216. def parse_from_file(self, filename, numframes=1):
  217. self['data'] = EnergyVector(numframes)
  218. with open(filename) as of:
  219. c = 0
  220. f = 0
  221. while line := of.readline():
  222. f += 1
  223. if line.startswith('| BLOCK IE '):
  224. fields = line.split()
  225. self.block_analysis.append({
  226. 'block_size': int(fields[3]),
  227. 'nblocks': int(fields[4]),
  228. 'used_frames': int(fields[5]),
  229. 'mean': float(fields[6]),
  230. 'std': float(fields[7]),
  231. 'sem': float(fields[8]),
  232. 'p025': float(fields[9]),
  233. 'p975': float(fields[10]),
  234. })
  235. continue
  236. if line.startswith('|') or not line.split():
  237. # Legacy 1.6.x wrote "| Interaction Entropy (-TΔS): mean +/- sd"
  238. # on a comment-style line; still parse that primary value.
  239. stripped = line.lstrip('| ').strip()
  240. if stripped.startswith('Interaction Entropy (-TΔS):') and 'ie_value' not in self:
  241. tokens = stripped.split(':', 1)[1].split()
  242. self['ie_value'] = float(tokens[0])
  243. if len(tokens) >= 3 and tokens[1] == '+/-':
  244. self['tail_std'] = float(tokens[2])
  245. continue
  246. if line.startswith('IE-frames:'):
  247. self['ieframes'] = int(line.strip('\n').split()[-1])
  248. elif line.startswith('Internal Energy SD (sigma):'):
  249. self['sigma'] = float(line.strip('\n').split()[-1])
  250. elif line.startswith('Full-ensemble Interaction Entropy (-TΔS):'):
  251. self['ie_value'] = float(line.split()[-1])
  252. elif line.startswith('Interaction Entropy (-TΔS):'):
  253. # Legacy header without the leading comment marker.
  254. tokens = line.split(':', 1)[1].split()
  255. self['ie_value'] = float(tokens[0])
  256. if len(tokens) >= 3 and tokens[1] == '+/-':
  257. self['tail_std'] = float(tokens[2])
  258. elif line.startswith('Tail convergence mean'):
  259. self['tail_mean'] = float(line.split()[-1])
  260. elif line.startswith('Tail convergence SD'):
  261. self['tail_std'] = float(line.split()[-1])
  262. elif line.startswith('Block diagnostic:'):
  263. fields = line.split()
  264. self['block_size'] = int(fields[3])
  265. self['block_nblocks'] = int(fields[5])
  266. self['block_std'] = float(fields[7])
  267. self['block_sem'] = float(fields[9])
  268. elif line.startswith('Frame'):
  269. continue
  270. else:
  271. frame, value = line.strip('\n').split()
  272. self['data'][c] = float(value)
  273. c += 1
  274. f += 1
  275. self['iedata'] = self['data'][-self['ieframes']:]
  276. # Prefer the explicit primary value. For legacy files without
  277. # Full-ensemble / header text, restore the 1.6.x primary (tail mean).
  278. self['ie_value'] = self.get('ie_value', float(self['iedata'].mean()))
  279. self['tail_mean'] = self.get('tail_mean', float(self['iedata'].mean()))
  280. self['tail_std'] = self.get('tail_std', float(self['iedata'].std()))
  281. self['block_size'] = self.get('block_size', 0)
  282. self['block_nblocks'] = self.get('block_nblocks', 0)
  283. self['block_std'] = self.get('block_std', self['tail_std'])
  284. self['block_sem'] = self.get(
  285. 'block_sem', self['tail_std'] / sqrt(self['ieframes'])
  286. )
  287. def _print_vectors(self, csvwriter):
  288. """ Prints the energy vectors to a CSV file for easy viewing
  289. in spreadsheets
  290. """
  291. csvwriter.writerow(['Frame #', 'Interaction Entropy'])
  292. f = self.INPUT['general']['startframe']
  293. for d in self['data']:
  294. csvwriter.writerow([f] + [round(d, 2)])
  295. f += self.INPUT['general']['interval']
  296. csvwriter.writerow([])
  297. def summary_output(self):
  298. summary = self.summary()
  299. text = []
  300. for row in summary:
  301. met, key, sigma, avg, std, sem = row
  302. if isinstance(avg, str):
  303. text.extend((
  304. f'{met:16s} {key:>13s} {sigma:>13s} {avg:>10s} {std:>12s} {sem:>10s}',
  305. sep,
  306. ))
  307. else:
  308. text.append(f'{met:16s} {key:>13s} {sigma:13.2f} {avg:10.2f} {std:12.2f} {sem:10.2f}')
  309. if self.get('block_size'):
  310. text.append(
  311. f"Block diagnostic: {self['block_nblocks']} nonoverlapping blocks "
  312. f"of {self['block_size']} frames"
  313. )
  314. text.append(
  315. f"Tail convergence diagnostic: {self['tail_mean']:.2f} +/- {self['tail_std']:.2f} "
  316. f"over the last {self['ieframes']} prefixes"
  317. )
  318. return '\n'.join(text) + '\n\n'
  319. def summary(self):
  320. """ Formatted summary of Interaction Entropy results """
  321. avg = float(self.get('ie_value', self['iedata'].mean()))
  322. stdev = float(self.get('block_std', self['data'][-self['ieframes']:].stdev()))
  323. sem = float(self.get('block_sem', self['data'][-self['ieframes']:].sem()))
  324. return [
  325. [
  326. 'Energy Method',
  327. 'Entropy',
  328. 'σ(Int. Energy)',
  329. 'Full IE',
  330. 'Block SD',
  331. 'Block SEM'
  332. ],
  333. [self.method.upper(), 'IE', self['sigma'], avg, stdev, sem]
  334. ]
  335. class C2out(dict):
  336. """
  337. C2 Entropy output
  338. """
  339. def __init__(self, method, **kwargs):
  340. super(C2out, self).__init__(**kwargs)
  341. self.method = method
  342. self.block_analysis = []
  343. def parse_from_dict(self, d):
  344. values = dict(d)
  345. self.block_analysis = values.pop('block_analysis', [])
  346. self.update(values)
  347. def parse_from_file(self, filename):
  348. with open(filename) as of:
  349. while line := of.readline():
  350. if line.startswith('| BLOCK C2 '):
  351. fields = line.split()
  352. self.block_analysis.append({
  353. 'block_size': int(fields[3]),
  354. 'nblocks': int(fields[4]),
  355. 'used_frames': int(fields[5]),
  356. 'mean': float(fields[6]),
  357. 'std': float(fields[7]),
  358. 'sem': float(fields[8]),
  359. 'p025': float(fields[9]),
  360. 'p975': float(fields[10]),
  361. })
  362. continue
  363. if line.startswith('|') or not line:
  364. continue
  365. if line.startswith('C2 Entropy (-TΔS):'):
  366. self['c2data'] = float(line.strip('\n').split()[-1])
  367. elif line.startswith(('C2 Entropy SD:', 'C2 Block SD:')):
  368. self['c2_std'] = float(line.strip('\n').split()[-1])
  369. elif line.startswith('C2 Block SEM:'):
  370. self['c2_sem'] = float(line.strip('\n').split()[-1])
  371. elif line.startswith('Internal Energy SD (sigma):'):
  372. self['sigma'] = float(line.strip('\n').split()[-1])
  373. elif line.startswith(('C2 Entropy CI:', 'C2 Block P2.5-P97.5:')):
  374. self['c2_ci'] = [float(line.strip('\n').split()[-2]), float(line.strip('\n').split()[-1])]
  375. elif line.startswith('Block diagnostic:'):
  376. fields = line.split()
  377. self['block_size'] = int(fields[3])
  378. self['block_nblocks'] = int(fields[5])
  379. self['c2_sem'] = self.get('c2_sem', self['c2_std'])
  380. self['block_size'] = self.get('block_size', 0)
  381. self['block_nblocks'] = self.get('block_nblocks', 0)
  382. def summary_output(self):
  383. summary = self.summary()
  384. text = []
  385. for row in summary:
  386. met, key, sigma, avg, std, sem, ci = row
  387. if isinstance(avg, str):
  388. text.extend((f'{met:16s} {key:>13s} {sigma:>13s} {avg:>10s} {std:>8s} {sem:>8s} {ci:>14s}', sep))
  389. else:
  390. text.append(f"{met:16s} {key:>13s} {sigma:13.2f} {avg:10.2f} {std:8.2f} {sem:8.2f} {ci:>14s}")
  391. if self.get('block_size'):
  392. text.append(
  393. f"Block diagnostic: {self['block_nblocks']} nonoverlapping blocks "
  394. f"of {self['block_size']} frames"
  395. )
  396. return '\n'.join(text) + '\n\n'
  397. def summary(self):
  398. """ Formatted summary of C2 Entropy results """
  399. return [
  400. [
  401. 'Energy Method',
  402. 'Entropy',
  403. 'σ(Int. Energy)',
  404. 'C2 Value',
  405. 'Block SD',
  406. 'Block SEM',
  407. 'Block P2.5-P97.5'
  408. ],
  409. [self.method.upper(), 'C2', float(self['sigma']), float(self['c2data']), float(self['c2_std']),
  410. float(self.get('c2_sem', self['c2_std'])), f"{self['c2_ci'][0]:.2f}-{self['c2_ci'][1]:.2f}",]
  411. ]
  412. class QHout(dict):
  413. """ Quasi-harmonic output file class. QH output files are strange so we won't
  414. derive from AmberOutput
  415. """
  416. def __init__(self, filename=None, temp=298.15, **kwargs):
  417. super(QHout, self).__init__(**kwargs)
  418. self.filename = filename
  419. self.temperature = temp
  420. self.stability = False
  421. self._read()
  422. def summary_output(self):
  423. text = [' TRANSLATIONAL ROTATIONAL VIBRATIONAL TOTAL',
  424. 'Complex %13.4f %15.4f %16.4f %15.4f' % (self['complex']['TRANSLATIONAL'],
  425. self['complex']['ROTATIONAL'],
  426. self['complex']['VIBRATIONAL'],
  427. self['complex']['TOTAL'],)]
  428. if not self.stability:
  429. text.extend(['Receptor %13.4f %15.4f %16.4f %15.4f' % (self['receptor']['TRANSLATIONAL'],
  430. self['receptor']['ROTATIONAL'],
  431. self['receptor']['VIBRATIONAL'],
  432. self['receptor']['TOTAL']),
  433. 'Ligand %13.4f %15.4f %16.4f %15.4f' % (self['ligand']['TRANSLATIONAL'],
  434. self['ligand']['ROTATIONAL'],
  435. self['ligand']['VIBRATIONAL'],
  436. self['ligand']['TOTAL']),
  437. '',
  438. 'Delta %13.4f %15.4f %16.4f %15.4f' % (self['delta']['TRANSLATIONAL'],
  439. self['delta']['ROTATIONAL'],
  440. self['delta']['VIBRATIONAL'],
  441. self['delta']['TOTAL'])])
  442. return '\n'.join(text) + '\n\n'
  443. def summary(self):
  444. """ Formatted summary of quasi-harmonic results """
  445. summry_list = [['', 'TRANSLATIONAL', 'ROTATIONAL', 'VIBRATIONAL', 'TOTAL'],
  446. ['Complex', self['complex']['TRANSLATIONAL'], self['complex']['ROTATIONAL'],
  447. self['complex']['VIBRATIONAL'], self['complex']['TOTAL']]]
  448. if not self.stability:
  449. summry_list.extend([['Receptor',
  450. self['receptor']['TRANSLATIONAL'],
  451. self['receptor']['ROTATIONAL'],
  452. self['receptor']['VIBRATIONAL'],
  453. self['receptor']['TOTAL']],
  454. ['Ligand',
  455. self['ligand']['TRANSLATIONAL'],
  456. self['ligand']['ROTATIONAL'],
  457. self['ligand']['VIBRATIONAL'],
  458. self['ligand']['TOTAL']],
  459. ['-TΔS',
  460. self['delta']['TRANSLATIONAL'],
  461. self['delta']['ROTATIONAL'],
  462. self['delta']['VIBRATIONAL'],
  463. self['delta']['TOTAL']]])
  464. return summry_list
  465. def _read(self):
  466. """ Parses the output files and fills the data arrays """
  467. with open(self.filename, 'r') as output:
  468. rawline = output.readline()
  469. self['complex'] = {}
  470. self['receptor'] = {'TOTAL': 0}
  471. self['ligand'] = {'TOTAL': 0}
  472. self['delta'] = {}
  473. comdone = False # if we've done the complex yet (filled in self.com)
  474. recdone = False # if we've done the receptor yet (filled in self.rec)
  475. # Try to fill in all found entropy values. If we can only find 1 set,
  476. # we're doing stability calculations
  477. while rawline:
  478. if rawline[:6] == " Total":
  479. if not comdone:
  480. self['complex']['TOTAL'] = (float(rawline.split()[3]) * self.temperature / 1000 * -1)
  481. self['complex']['TRANSLATIONAL'] = (
  482. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  483. self['complex']['ROTATIONAL'] = (
  484. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  485. self['complex']['VIBRATIONAL'] = (
  486. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  487. comdone = True
  488. elif not recdone:
  489. self['receptor']['TOTAL'] = (float(rawline.split()[3]) * self.temperature / 1000 * -1)
  490. self['receptor']['TRANSLATIONAL'] = (
  491. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  492. self['receptor']['ROTATIONAL'] = (
  493. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  494. self['receptor']['VIBRATIONAL'] = (
  495. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  496. recdone = True
  497. else:
  498. self['ligand']['TOTAL'] = (float(rawline.split()[3]) * self.temperature / 1000 * -1)
  499. self['ligand']['TRANSLATIONAL'] = (
  500. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  501. self['ligand']['ROTATIONAL'] = (
  502. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  503. self['ligand']['VIBRATIONAL'] = (
  504. float(output.readline().split()[3]) * self.temperature / 1000 * -1)
  505. break
  506. rawline = output.readline()
  507. # end while rawline
  508. self.stability = not recdone
  509. # fill the delta if not stability
  510. if not self.stability:
  511. self['delta']['TOTAL'] = self['complex']['TOTAL'] - self['receptor']['TOTAL'] - self['ligand']['TOTAL']
  512. self['delta']['TRANSLATIONAL'] = (self['complex']['TRANSLATIONAL'] - self['receptor']['TRANSLATIONAL'] -
  513. self['ligand']['TRANSLATIONAL'])
  514. self['delta']['ROTATIONAL'] = (self['complex']['ROTATIONAL'] - self['receptor']['ROTATIONAL'] -
  515. self['ligand']['ROTATIONAL'])
  516. self['delta']['VIBRATIONAL'] = (self['complex']['VIBRATIONAL'] - self['receptor']['VIBRATIONAL'] -
  517. self['ligand']['VIBRATIONAL'])
  518. class DeltaDeltaQH(dict):
  519. def __init__(self, mut, norm, **kwargs):
  520. super(DeltaDeltaQH, self).__init__(**kwargs)
  521. self.mut = mut
  522. self.norm = norm
  523. self._delta()
  524. def _delta(self):
  525. for key, mut_values in self.mut.items():
  526. norm_values = self.norm.get(key, {})
  527. self[key] = {
  528. term: value - norm_values[term]
  529. for term, value in mut_values.items()
  530. if term in norm_values
  531. }
  532. def summary_output(self):
  533. text = [' TRANSLATIONAL ROTATIONAL VIBRATIONAL TOTAL',
  534. 'Complex %13.4f %15.4f %16.4f %15.4f' % (self['complex']['TRANSLATIONAL'],
  535. self['complex']['ROTATIONAL'],
  536. self['complex']['VIBRATIONAL'],
  537. self['complex']['TOTAL'],)]
  538. if not self.norm.stability:
  539. text.extend(['Receptor %13.4f %15.4f %16.4f %15.4f' % (self['receptor']['TRANSLATIONAL'],
  540. self['receptor']['ROTATIONAL'],
  541. self['receptor']['VIBRATIONAL'],
  542. self['receptor']['TOTAL']),
  543. 'Ligand %13.4f %15.4f %16.4f %15.4f' % (self['ligand']['TRANSLATIONAL'],
  544. self['ligand']['ROTATIONAL'],
  545. self['ligand']['VIBRATIONAL'],
  546. self['ligand']['TOTAL']),
  547. '',
  548. 'Delta %13.4f %15.4f %16.4f %15.4f' % (self['delta']['TRANSLATIONAL'],
  549. self['delta']['ROTATIONAL'],
  550. self['delta']['VIBRATIONAL'],
  551. self['delta']['TOTAL'])])
  552. return '\n'.join(text) + '\n\n'
  553. def summary(self):
  554. """ Formatted summary of quasi-harmonic results """
  555. summry_list = [['', 'TRANSLATIONAL', 'ROTATIONAL', 'VIBRATIONAL', 'TOTAL'],
  556. ['Complex', self['complex']['TRANSLATIONAL'], self['complex']['ROTATIONAL'],
  557. self['complex']['VIBRATIONAL'], self['complex']['TOTAL']]]
  558. if not self.norm.stability:
  559. summry_list.extend([['Receptor',
  560. self['receptor']['TRANSLATIONAL'],
  561. self['receptor']['ROTATIONAL'],
  562. self['receptor']['VIBRATIONAL'],
  563. self['receptor']['TOTAL']],
  564. ['Ligand',
  565. self['ligand']['TRANSLATIONAL'],
  566. self['ligand']['ROTATIONAL'],
  567. self['ligand']['VIBRATIONAL'],
  568. self['ligand']['TOTAL']],
  569. ['-TΔS',
  570. self['delta']['TRANSLATIONAL'],
  571. self['delta']['ROTATIONAL'],
  572. self['delta']['VIBRATIONAL'],
  573. self['delta']['TOTAL']]])
  574. return summry_list
  575. class NMODEout(AmberOutput):
  576. """ Normal mode entropy approximation output class """
  577. print_levels = {'TRANSLATIONAL': 1, 'ROTATIONAL': 1, 'VIBRATIONAL': 1, 'TOTAL': 1}
  578. def __init__(self, mol: str, INPUT, chamber=False, **kwargs):
  579. super(NMODEout, self).__init__(mol, INPUT, chamber, **kwargs)
  580. # Ordered list of keys in the data dictionary
  581. self.data_keys = ['TRANSLATIONAL', 'ROTATIONAL', 'VIBRATIONAL']
  582. # Other aspects of AmberOutputs, which are just blank arrays
  583. self.composite_keys = ['TOTAL']
  584. def _get_energies(self, outfile):
  585. """ Parses the energy terms from the output file. This will parse 1 line
  586. at a time in order to minimize the memory requirements (we should only
  587. have to store a single line at a time in addition to the arrays of
  588. data)
  589. """
  590. while rawline := outfile.readline():
  591. if "|---- Entropy not Calculated---|" in rawline:
  592. self['TOTAL'][self.frame_idx] = np.nan
  593. self['TRANSLATIONAL'][self.frame_idx] = np.nan
  594. self['ROTATIONAL'][self.frame_idx] = np.nan
  595. self['VIBRATIONAL'][self.frame_idx] = np.nan
  596. self.frame_idx += 1
  597. if rawline[:6] == 'Total:':
  598. self['TOTAL'][self.frame_idx] = float(rawline.split()[3]) * self.temperature / 1000 * -1
  599. self['TRANSLATIONAL'][self.frame_idx] = (float(outfile.readline().split()[3]) * self.temperature /
  600. 1000 * -1)
  601. self['ROTATIONAL'][self.frame_idx] = (float(outfile.readline().split()[3]) * self.temperature / 1000
  602. * -1)
  603. self['VIBRATIONAL'][self.frame_idx] = (float(outfile.readline().split()[3]) * self.temperature / 1000
  604. * -1)
  605. self.frame_idx += 1
  606. def _fill_nmode_values(self):
  607. """Leave unconverged NMODE frames as NaN; do not impute values.
  608. Averages and uncertainties omit non-finite frames. If every frame
  609. failed minimization, NMODE is disabled for the run.
  610. """
  611. nonfinite_frames = np.zeros(self.numframes, dtype=bool)
  612. for term in self.data_keys + ['TOTAL']:
  613. nonfinite_frames |= ~np.isfinite(self[term])
  614. unconverged = int(nonfinite_frames.sum())
  615. if unconverged == 0:
  616. return
  617. if np.isnan(self['TOTAL']).all():
  618. logging.warning(
  619. f'{self.mol.capitalize()}: {unconverged} of {self.numframes} NMODE frames did not satisfy the '
  620. 'minimized-energy-gradient convergence criterion. No converged values are available. '
  621. 'Increase drms or maxcyc in the NMODE settings.')
  622. self.no_nmode_convergence = True
  623. return
  624. logging.warning(
  625. f'{self.mol.capitalize()}: {unconverged} of {self.numframes} NMODE frames did not satisfy the '
  626. 'minimized-energy-gradient convergence criterion. Those frames remain NaN and are omitted from '
  627. 'NMODE averages and uncertainties. Increase drms or maxcyc if more frames should converge.')
  628. def conv_float(word):
  629. if '*' in word:
  630. GMXMMPBSA_ERROR('Some energy terms are undefined. Please, check the input structure and trajectory. Check this '
  631. 'section the docs for more info '
  632. 'https://valdes-tresanco-ms.github.io/gmx_MMPBSA/dev/Q%26A/calculations/#possible-solutions')
  633. elif 'nan' in word.lower():
  634. GMXMMPBSA_ERROR('Some energy terms are undefined. Please, check the input structure and trajectory. Check this '
  635. 'section the docs for more info '
  636. 'https://valdes-tresanco-ms.github.io/gmx_MMPBSA/dev/Q%26A/calculations/#possible-solutions')
  637. else:
  638. return float(word)
  639. class GBout(AmberOutput):
  640. """ Amber output class for normal generalized Born simulations """
  641. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1, '1-4 VDW': 2, '1-4 EEL': 2, 'EGB': 1,
  642. 'ESURF': 1}
  643. # Ordered list of keys in the data dictionary
  644. def __init__(self, mol, INPUT, chamber=False, **kwargs):
  645. AmberOutput.__init__(self, mol, INPUT, chamber, **kwargs)
  646. self.data_keys.extend(['EGB', 'ESURF'])
  647. def _get_energies(self, outfile):
  648. """ Parses the mdout files for the GB potential terms """
  649. while rawline := outfile.readline():
  650. if rawline[:5] == ' BOND':
  651. words = rawline.split()
  652. self['BOND'][self.frame_idx] = conv_float(words[2])
  653. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  654. self['DIHED'][self.frame_idx] = conv_float(words[8])
  655. words = outfile.readline().split()
  656. if self.chamber:
  657. self['UB'][self.frame_idx] = conv_float(words[2])
  658. self['IMP'][self.frame_idx] = conv_float(words[5])
  659. self['CMAP'][self.frame_idx] = conv_float(words[8])
  660. words = outfile.readline().split()
  661. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  662. self['EEL'][self.frame_idx] = conv_float(words[5])
  663. self['EGB'][self.frame_idx] = conv_float(words[8])
  664. words = outfile.readline().split()
  665. self['1-4 VDW'][self.frame_idx] = conv_float(words[3])
  666. self['1-4 EEL'][self.frame_idx] = conv_float(words[7])
  667. self.frame_idx += 1
  668. def _extra_reading(self, fileno):
  669. # Load the ESURF data from the cpptraj output
  670. fname = '%s.%d' % (self.basename, fileno)
  671. fname = fname.replace('gb.mdout', 'gb_surf.dat')
  672. surf_data = _get_cpptraj_surf(fname)
  673. for sd in surf_data:
  674. self['ESURF'][self.extraframe_idx] = sd * self.INPUT['gb']['surften'] + self.INPUT['gb']['surfoff']
  675. self.extraframe_idx += 1
  676. class GBNSR6out(AmberOutput):
  677. """ Amber output class for normal generalized Born simulations """
  678. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1, '1-4 VDW': 2, '1-4 EEL': 2, 'EGB': 1,
  679. 'ESURF': 1}
  680. # Ordered list of keys in the data dictionary
  681. def __init__(self, mol, INPUT, chamber=False, **kwargs):
  682. AmberOutput.__init__(self, mol, INPUT, chamber, **kwargs)
  683. # As the MM terms will be updated, in order to maintain order, we need to initialize these keys
  684. self.data_keys.extend(['EGB', 'ESURF'])
  685. def _get_energies(self, outfile):
  686. """ Parses the mdout files for the GB potential terms """
  687. while rawline := outfile.readline():
  688. if rawline[:5] == ' BOND':
  689. words = rawline.split()
  690. self['BOND'][self.frame_idx] = conv_float(words[2])
  691. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  692. self['DIHED'][self.frame_idx] = conv_float(words[8])
  693. words = outfile.readline().split()
  694. if self.chamber:
  695. self['UB'][self.frame_idx] = conv_float(words[2])
  696. self['IMP'][self.frame_idx] = conv_float(words[5])
  697. self['CMAP'][self.frame_idx] = conv_float(words[8])
  698. words = outfile.readline().split()
  699. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  700. self['EEL'][self.frame_idx] = conv_float(words[5])
  701. self['EGB'][self.frame_idx] = conv_float(words[8])
  702. words = outfile.readline().split()
  703. self['1-4 VDW'][self.frame_idx] = conv_float(words[3])
  704. self['1-4 EEL'][self.frame_idx] = conv_float(words[7])
  705. words = outfile.readline().split()
  706. self['ESURF'][self.frame_idx] = conv_float(words[2])
  707. self.frame_idx += 1
  708. class MMout(AmberOutput):
  709. """ Amber output class for normal MM simulations """
  710. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1, '1-4 VDW': 2, '1-4 EEL': 2}
  711. # Ordered list of keys in the data dictionary
  712. def __init__(self, mol, INPUT, chamber=False, **kwargs):
  713. AmberOutput.__init__(self, mol, INPUT, chamber, **kwargs)
  714. def _get_energies(self, outfile):
  715. """ Parses the mdout files for the GB potential terms """
  716. while rawline := outfile.readline():
  717. if rawline[:5] == ' BOND':
  718. words = rawline.split()
  719. self['BOND'][self.frame_idx] = conv_float(words[2])
  720. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  721. self['DIHED'][self.frame_idx] = conv_float(words[8])
  722. words = outfile.readline().split()
  723. if self.chamber:
  724. self['UB'][self.frame_idx] = conv_float(words[2])
  725. self['IMP'][self.frame_idx] = conv_float(words[5])
  726. self['CMAP'][self.frame_idx] = conv_float(words[8])
  727. words = outfile.readline().split()
  728. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  729. self['EEL'][self.frame_idx] = conv_float(words[5])
  730. words = outfile.readline().split()
  731. self['1-4 VDW'][self.frame_idx] = conv_float(words[3])
  732. self['1-4 EEL'][self.frame_idx] = conv_float(words[7])
  733. class PBout(AmberOutput):
  734. # What the value of verbosity must be to print out this data
  735. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1,
  736. '1-4 VDW': 2, '1-4 EEL': 2, 'EPB': 1, 'ENPOLAR': 1, 'EDISPER': 1}
  737. def __init__(self, mol, INPUT, chamber=False, **kwargs):
  738. AmberOutput.__init__(self, mol, INPUT, chamber, **kwargs)
  739. # EPB is still parsed when eneopt=1/P3M (incl. Amber-forced NLPB): Amber reports EPB≈0 and
  740. # folds RF+Coulomb into EEL. GGAS/GSOLV labels are then not a separable partition; TOTAL is.
  741. self.data_keys.extend(['EPB', 'ENPOLAR', 'EDISPER'])
  742. def _get_energies(self, outfile):
  743. """ Parses the energy values from the output files """
  744. while rawline := outfile.readline():
  745. if rawline[:5] == ' BOND':
  746. words = rawline.split()
  747. self['BOND'][self.frame_idx] = conv_float(words[2])
  748. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  749. self['DIHED'][self.frame_idx] = conv_float(words[8])
  750. words = outfile.readline().split()
  751. if self.chamber:
  752. self['UB'][self.frame_idx] = conv_float(words[2])
  753. self['IMP'][self.frame_idx] = conv_float(words[5])
  754. self['CMAP'][self.frame_idx] = conv_float(words[8])
  755. words = outfile.readline().split()
  756. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  757. self['EEL'][self.frame_idx] = conv_float(words[5])
  758. self['EPB'][self.frame_idx] = conv_float(words[8])
  759. words = outfile.readline().split()
  760. self['1-4 VDW'][self.frame_idx] = conv_float(words[3])
  761. self['1-4 EEL'][self.frame_idx] = conv_float(words[7])
  762. words = outfile.readline().split()
  763. # electrostatic solvation free energy will not report in amber ouput
  764. # when `ipb=0`. in such case, set it as 0 would be reasonable
  765. if self.INPUT['pb']['ipb'] == 0:
  766. self['ENPOLAR'][self.frame_idx] = 0
  767. else:
  768. self['ENPOLAR'][self.frame_idx] = conv_float(words[2])
  769. if self.INPUT['pb']['inp'] == 2 and not self.apbs:
  770. self['EDISPER'][self.frame_idx] = conv_float(words[5])
  771. self.frame_idx += 1
  772. class RISMout(AmberOutput):
  773. # Which of those keys belong to the gas phase energy contributions
  774. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1,
  775. '1-4 VDW': 2, '1-4 EEL': 2, 'ERISM': 1}
  776. def __init__(self, mol, INPUT, chamber=False, solvtype=0, **kwargs):
  777. AmberOutput.__init__(self, mol, INPUT, chamber)
  778. self.solvtype = solvtype
  779. self.data_keys.extend(['ERISM'])
  780. def _get_energies(self, outfile):
  781. """ Parses the RISM output file for energy terms """
  782. # Getting the RISM solvation energies requires some decision-making.
  783. # There are 2 possibilities (right now):
  784. #
  785. # 1. Standard free energy (solvtype==0)
  786. # 2. GF free energy (solvtype==1)
  787. # 3. PC+ GF free energy (solvtype==2)
  788. while rawline := outfile.readline():
  789. if re.match(r'(solute_epot|solutePotentialEnergy)', rawline):
  790. words = rawline.split()
  791. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  792. self['EEL'][self.frame_idx] = conv_float(words[3])
  793. self['BOND'][self.frame_idx] = conv_float(words[4])
  794. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  795. self['DIHED'][self.frame_idx] = conv_float(words[6])
  796. self['1-4 VDW'][self.frame_idx] = conv_float(words[7])
  797. self['1-4 EEL'][self.frame_idx] = conv_float(words[8])
  798. elif self.solvtype == 0 and re.match(r'(rism_exchem|rism_excessChemicalPotential)\s', rawline):
  799. self['ERISM'][self.frame_idx] = conv_float(rawline.split()[1])
  800. self.frame_idx += 1
  801. elif self.solvtype == 1 and re.match(r'(rism_exchGF|rism_excessChemicalPotentialGF)\s', rawline):
  802. self['ERISM'][self.frame_idx] = conv_float(rawline.split()[1])
  803. self.frame_idx += 1
  804. elif self.solvtype == 2 and re.match(r'(rism_exchPCPLUS|rism_excessChemicalPotentialPCPLUS)\s', rawline):
  805. self['ERISM'][self.frame_idx] = conv_float(rawline.split()[1])
  806. self.frame_idx += 1
  807. class RISM_std_Out(RISMout):
  808. """ No polar decomp RISM output file for standard free energy """
  809. def __init__(self, mol, INPUT, chamber=False):
  810. RISMout.__init__(self, mol, INPUT, chamber, 0)
  811. class RISM_gf_Out(RISMout):
  812. """ No polar decomp RISM output file for Gaussian Fluctuation free energy """
  813. def __init__(self, mol, INPUT, chamber=False):
  814. RISMout.__init__(self, mol, INPUT, chamber, 1)
  815. class RISM_pcplus_Out(RISMout):
  816. """ No polar decomp RISM output file for PC+ free energy """
  817. def __init__(self, mol, INPUT, chamber=False):
  818. RISMout.__init__(self, mol, INPUT, chamber, 2)
  819. class PolarRISMout(RISMout):
  820. # Which of those keys belong to the gas phase energy contributions
  821. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1,
  822. '1-4 VDW': 2, '1-4 EEL': 2, 'POLAR SOLV': 1, 'APOLAR SOLV': 1}
  823. def __init__(self, mol, INPUT, chamber=False, solvtype=0, **kwargs):
  824. AmberOutput.__init__(self, mol, INPUT, chamber)
  825. self.solvtype = solvtype
  826. self.data_keys.extend(['POLAR SOLV', 'APOLAR SOLV'])
  827. def _get_energies(self, outfile):
  828. """ Parses the RISM output file for energy terms """
  829. # Getting the RISM solvation energies requires some decision-making.
  830. # There are 2 possibilities (right now):
  831. #
  832. # 1. Standard free energy (solvtype==0)
  833. # 2. GF free energy (solvtype==1)
  834. while rawline := outfile.readline():
  835. if re.match(r'(solute_epot|solutePotentialEnergy)', rawline):
  836. words = rawline.split()
  837. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  838. self['EEL'][self.frame_idx] = conv_float(words[3])
  839. self['BOND'][self.frame_idx] = conv_float(words[4])
  840. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  841. self['DIHED'][self.frame_idx] = conv_float(words[6])
  842. self['1-4 VDW'][self.frame_idx] = conv_float(words[8])
  843. self['1-4 EEL'][self.frame_idx] = conv_float(words[8])
  844. elif self.solvtype == 0 and re.match(
  845. r'(rism_polar|rism_polarExcessChemicalPotential)\s', rawline):
  846. self['POLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  847. elif self.solvtype == 0 and re.match(
  848. r'(rism_apolar|rism_apolarExcessChemicalPotential)\s', rawline):
  849. self['APOLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  850. self.frame_idx += 1
  851. elif self.solvtype == 1 and re.match(
  852. r'(rism_polGF|rism_polarExcessChemicalPotentialGF)\s', rawline):
  853. self['POLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  854. elif self.solvtype == 1 and re.match(
  855. r'(rism_apolGF|rism_apolarExcessChemicalPotentialGF)\s', rawline):
  856. self['APOLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  857. self.frame_idx += 1
  858. elif self.solvtype == 2 and re.match(
  859. r'(rism_polPCPLUS|rism_polarExcessChemicalPotentialPCPLUS)\s', rawline):
  860. self['POLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  861. elif self.solvtype == 2 and re.match(
  862. r'(rism_apolPCPLUS|rism_apolarExcessChemicalPotentialPCPLUS)\s', rawline):
  863. self['APOLAR SOLV'][self.frame_idx] = conv_float(rawline.split()[1])
  864. self.frame_idx += 1
  865. class PolarRISM_std_Out(PolarRISMout):
  866. """ Polar decomp RISM output file for standard free energy """
  867. def __init__(self, mol, INPUT, chamber=False):
  868. PolarRISMout.__init__(self, mol, INPUT, chamber, 0)
  869. class PolarRISM_gf_Out(PolarRISMout):
  870. """ Polar decomp RISM output file for Gaussian Fluctuation free energy """
  871. def __init__(self, mol, INPUT, chamber=False):
  872. PolarRISMout.__init__(self, mol, INPUT, chamber, 1)
  873. class PolarRISM_pcplus_Out(PolarRISMout):
  874. """ Polar decomp RISM output file for PC+ free energy """
  875. def __init__(self, mol, INPUT, chamber=False):
  876. PolarRISMout.__init__(self, mol, INPUT, chamber, 2)
  877. class QMMMout(GBout):
  878. """ Class for QM/MM GBSA output files """
  879. # What the value of verbosity must be to print out this data
  880. print_levels = {'BOND': 2, 'ANGLE': 2, 'DIHED': 2, 'VDWAALS': 1, 'EEL': 1,
  881. '1-4 VDW': 2, '1-4 EEL': 2, 'EGB': 1, 'ESURF': 1, 'ESCF': 1}
  882. def __init__(self, mol, INPUT, chamber=False, **kwargs):
  883. GBout.__init__(self, mol, INPUT, chamber, **kwargs)
  884. self.data_keys.extend(['ESCF'])
  885. def _get_energies(self, outfile):
  886. """ Parses the energies from a QM/MM output file. NOTE, however, that a
  887. QMMMout *could* just be a GBout with ESCF==0 if the QM region lies
  888. entirely outside this system
  889. """
  890. while rawline := outfile.readline():
  891. if rawline[:5] == ' BOND':
  892. words = rawline.split()
  893. self['BOND'][self.frame_idx] = conv_float(words[2])
  894. self['ANGLE'][self.frame_idx] = conv_float(words[5])
  895. self['DIHED'][self.frame_idx] = conv_float(words[8])
  896. words = outfile.readline().split()
  897. if self.chamber:
  898. self['UB'][self.frame_idx] = conv_float(words[2])
  899. self['IMP'][self.frame_idx] = conv_float(words[5])
  900. self['CMAP'][self.frame_idx] = conv_float(words[8])
  901. words = outfile.readline().split()
  902. self['VDWAALS'][self.frame_idx] = conv_float(words[2])
  903. self['EEL'][self.frame_idx] = conv_float(words[5])
  904. self['EGB'][self.frame_idx] = conv_float(words[8])
  905. words = outfile.readline().split()
  906. self['1-4 VDW'][self.frame_idx] = conv_float(words[3])
  907. self['1-4 EEL'][self.frame_idx] = conv_float(words[7])
  908. words = outfile.readline().split()
  909. # This is where ESCF will be. Since ESCF can differ based on which
  910. # qmtheory was chosen, we just check to see if it's != ESURF:
  911. if words[0] == 'minimization':
  912. continue
  913. elif words[0].endswith('='):
  914. self['ESCF'][self.frame_idx] = conv_float(words[1])
  915. else:
  916. self['ESCF'][self.frame_idx] = conv_float(words[2])
  917. self.frame_idx += 1
  918. class BindingStatistics(dict):
  919. """ Base class for compiling the binding statistics """
  920. st_null = ['BOND', 'ANGLE', 'DIHED', '1-4 VDW', '1-4 EEL']
  921. def __init__(self, com, rec, lig, chamber=False, traj_protocol='STP', **kwargs):
  922. super(BindingStatistics, self).__init__(**kwargs)
  923. self.com = com
  924. self.rec = rec
  925. self.lig = lig
  926. self.mol = 'delta'
  927. self.numframes = self.com.numframes
  928. self.INPUT = self.com.INPUT
  929. self.chamber = chamber
  930. self.traj_protocol = traj_protocol
  931. self.inconsistent = False
  932. self.missing_terms = False
  933. self.data_keys = self.com.data_keys
  934. self.composite_keys = []
  935. try:
  936. self._delta()
  937. self.missing_terms = False
  938. except LengthError:
  939. self._delta2()
  940. self.missing_terms = True
  941. def _delta(self):
  942. """
  943. Calculates the delta statistics. Should check for any consistencies that
  944. would cause verbosity levels to change, and it should change them
  945. accordingly in the child classes
  946. """
  947. # First thing we do is check to make sure that all of the terms that
  948. # should *not* be printed actually cancel out (i.e. bonded terms)
  949. if not isinstance(self.com, NMODEout) and self.traj_protocol == 'STP':
  950. TINY = 0.005
  951. for key in self.st_null:
  952. diff = self.com[key] - self.rec[key] - self.lig[key]
  953. if diff.abs_gt(TINY):
  954. self.inconsistent = True
  955. logging.warning(f"{key} component is reported as inconsistent. Please, check the output file "
  956. f"for more details")
  957. break
  958. for key in self.com.data_keys:
  959. if self.traj_protocol == 'STP':
  960. temp = self.com[key].corr_sub(self.rec[key])
  961. self[key] = temp.corr_sub(self.lig[key])
  962. else:
  963. self[key] = self.com[key] - self.rec[key] - self.lig[key]
  964. for key in self.com.composite_keys:
  965. self[key] = EnergyVector(self.numframes)
  966. self.composite_keys.append(key)
  967. for key in self.com.data_keys:
  968. if self.traj_protocol == 'STP' and key in self.st_null:
  969. continue
  970. for component in data_key_owner[key]:
  971. self[component] = self[key] + self[component]
  972. def _print_vectors(self, csvwriter):
  973. """ Output all of the energy terms including the differences if we're
  974. doing a single trajectory simulation and there are no missing terms
  975. """
  976. term_text = 'Entropy' if isinstance(self.com, NMODEout) else 'Energy'
  977. csvwriter.writerow([f'Complex {term_text} Terms'])
  978. self.com._print_vectors(csvwriter)
  979. csvwriter.writerow([])
  980. csvwriter.writerow([f'Receptor {term_text} Terms'])
  981. self.rec._print_vectors(csvwriter)
  982. csvwriter.writerow([])
  983. csvwriter.writerow([f'Ligand {term_text} Terms'])
  984. self.lig._print_vectors(csvwriter)
  985. csvwriter.writerow([])
  986. csvwriter.writerow([f'Delta {term_text} Terms'])
  987. print_keys = list(self.data_keys)
  988. # Add on the composite keys
  989. print_keys += self.composite_keys
  990. # write the header
  991. csvwriter.writerow(['Frame #'] + print_keys)
  992. # write out each frame
  993. c = self.com.INPUT['nmode']['nmstartframe'] if isinstance(self.com, NMODEout) else self.com.INPUT['general'][
  994. 'startframe']
  995. for i in range(self.numframes):
  996. csvwriter.writerow([c] + [round(self[key][i], 2) for key in print_keys])
  997. c += self.com.INPUT['nmode']['nminterval'] if isinstance(self.com, NMODEout) else self.com.INPUT['general'][
  998. 'interval']
  999. csvwriter.writerow([])
  1000. def report_inconsistency(self, output_format: str = 'ascii'):
  1001. _output_format = 0 if output_format == 'ascii' else 1
  1002. text = []
  1003. if _output_format:
  1004. text.append(['WARNING: INCONSISTENCIES EXIST WITHIN INTERNAL POTENTIAL TERMS AND\n'
  1005. 'THE VALIDITY OF THESE RESULTS ARE HIGHLY QUESTIONABLE!\n'
  1006. '\n'
  1007. 'Some absolute differences in the internal potential terms are greater than 0.005.\n'
  1008. 'This should not happen when using Single Trajectory Protocol!\n'
  1009. '\n'
  1010. 'You can generate a detailed *.csv file with all the terms and differences as follows:\n'
  1011. 'gmx_MMPBSA --rewrite-output -eo energy_terms_differences.csv'])
  1012. else:
  1013. text.append('WARNING: INCONSISTENCIES EXIST WITHIN INTERNAL POTENTIAL TERMS AND\n'
  1014. 'THE VALIDITY OF THESE RESULTS ARE HIGHLY QUESTIONABLE!\n'
  1015. '\n'
  1016. 'Some absolute differences in the internal potential terms are greater than 0.005.\n'
  1017. 'This should not happen when using Single Trajectory Protocol!\n'
  1018. '\n'
  1019. 'You can generate a detailed *.csv file with all the terms and differences as follows:\n'
  1020. 'gmx_MMPBSA --rewrite-output -eo energy_terms_differences.csv')
  1021. return text if _output_format else '\n'.join(text) + '\n'
  1022. def summary_output(self, output_format: str = 'ascii'):
  1023. _output_format = 0 if output_format == 'ascii' else 1
  1024. summary = self.summary()
  1025. text = []
  1026. if _output_format:
  1027. text.append(['Delta (Complex - Receptor - Ligand):'])
  1028. else:
  1029. text.append('Delta (Complex - Receptor - Ligand):')
  1030. for c, row in enumerate(summary, start=1):
  1031. # Skip the composite terms, since we print those at the end
  1032. if _output_format:
  1033. text.append(row)
  1034. else:
  1035. key, avg, stdev, std, semp, sem, block_sd, block_sem = row
  1036. if key in ['GGAS', 'TOTAL']:
  1037. text.append('')
  1038. if isinstance(avg, str):
  1039. text.extend(
  1040. (
  1041. f'{key:16s} {avg:>13s} {stdev:>13s} {std:>10s} {semp:>12s} {sem:>10s} '
  1042. f'{block_sd:>10s} {block_sem:>10s}',
  1043. sep,
  1044. )
  1045. )
  1046. else:
  1047. text.append(f'{f"Δ{key}":16s} {avg:13.2f} {stdev:13.2f} {std:10.2f} {semp:12.2f} {sem:10.2f} '
  1048. f'{block_sd:10.2f} {block_sem:10.2f}')
  1049. return text if _output_format else '\n'.join(text) + '\n'
  1050. def summary(self):
  1051. """ Returns a string printing the summary of the binding statistics """
  1052. if isinstance(self.com, NMODEout):
  1053. col_name = '%-16s' % 'Entropy Term'
  1054. else:
  1055. col_name = '%-16s' % 'Energy Component'
  1056. summary_list = [
  1057. [col_name] + ['Average', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM']
  1058. ]
  1059. for key in self.data_keys:
  1060. # Skip the composite terms, since we print those at the end
  1061. if key in self.composite_keys:
  1062. continue
  1063. # Now print out the stats
  1064. stdev = float(self[key].stdev())
  1065. avg = float(self[key].mean())
  1066. std = float(self[key].std())
  1067. semp = float(self[key].semp())
  1068. sem = float(self[key].sem())
  1069. _, _, block_sd, block_sem = block_statistics(self[key])
  1070. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  1071. for key in self.composite_keys:
  1072. # Now print out the composite terms
  1073. stdev = float(self[key].stdev())
  1074. avg = float(self[key].mean())
  1075. std = float(self[key].std())
  1076. semp = float(self[key].semp())
  1077. sem = float(self[key].sem())
  1078. _, _, block_sd, block_sem = block_statistics(self[key])
  1079. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  1080. return summary_list
  1081. class DeltaDeltaStatistics(dict):
  1082. """ Base class for compiling the binding statistics. Include """
  1083. st_null = ['BOND', 'ANGLE', 'DIHED', '1-4 VDW', '1-4 EEL']
  1084. def __init__(self, mut, norm, **kwargs):
  1085. super(DeltaDeltaStatistics, self).__init__(**kwargs)
  1086. self.mut = mut
  1087. self.norm = norm
  1088. self.numframes = self.norm.numframes
  1089. self.mol = self.norm.mol
  1090. self.data_keys = self.norm.data_keys
  1091. self.composite_keys = ['GGAS', 'GSOLV', 'TOTAL']
  1092. self.term_text = 'Entropy' if 'ROTATIONAL' in self.data_keys else 'Energy'
  1093. self._delta()
  1094. def _delta(self):
  1095. """
  1096. Calculates the delta statistics. Should check for any consistencies that
  1097. would cause verbosity levels to change, and it should change them
  1098. accordingly in the child classes
  1099. """
  1100. for key in self.norm:
  1101. if key in self.composite_keys:
  1102. continue
  1103. self[key] = self.mut[key].corr_sub(self.norm[key])
  1104. for key in self.composite_keys:
  1105. self[key] = EnergyVector(self.numframes)
  1106. # self.composite_keys.append(key)
  1107. for key in self.data_keys:
  1108. for component in data_key_owner[key]:
  1109. self[component] = self[key] + self[component]
  1110. def _print_vectors(self, csvwriter):
  1111. """ Output all of the energy terms including the differences if we're
  1112. doing a single trajectory simulation and there are no missing terms
  1113. """
  1114. csvwriter.writerow([f'Delta Delta {self.term_text} Terms (Mutant - Normal)'])
  1115. # write the header
  1116. csvwriter.writerow(['Frame #'] + list(self.keys()))
  1117. # write out each frame
  1118. c = self.norm.INPUT['nmode']['nmstartframe'] if isinstance(self.norm, NMODEout) else \
  1119. self.norm.INPUT['general']['startframe']
  1120. for i in range(self.numframes):
  1121. csvwriter.writerow([c] + [round(self[key][i], 2) for key in self])
  1122. c += self.norm.INPUT['nmode']['nminterval'] if isinstance(self.norm, NMODEout) else \
  1123. self.norm.INPUT['general']['interval']
  1124. csvwriter.writerow([])
  1125. def summary_output(self):
  1126. summary = self.summary()
  1127. text = ['Delta Delta (Mutant - Normal):']
  1128. for c, row in enumerate(summary, start=1):
  1129. # Skip the composite terms, since we print those at the end
  1130. key, avg, stdev, std, semp, sem, block_sd, block_sem = row
  1131. if key in ['GGAS', 'TOTAL']:
  1132. text.append('')
  1133. if isinstance(avg, str):
  1134. text.extend(
  1135. (
  1136. f'{key:16s} {avg:>13s} {stdev:>13s} {std:>10s} {semp:>12s} {sem:>10s} '
  1137. f'{block_sd:>10s} {block_sem:>10s}',
  1138. sep,
  1139. )
  1140. )
  1141. else:
  1142. text.append(f'{f"ΔΔ{key}":16s} {avg:13.2f} {stdev:13.2f} {std:10.2f} {semp:12.2f} {sem:10.2f} '
  1143. f'{block_sd:10.2f} {block_sem:10.2f}')
  1144. return '\n'.join(text) + '\n'
  1145. def summary(self):
  1146. """ Returns a string printing the summary of the binding statistics """
  1147. summary_list = [
  1148. [f'{self.term_text} Component'] + ['Average', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM']
  1149. ]
  1150. for key in self.norm.data_keys:
  1151. # Now print out the stats
  1152. stdev = float(self[key].stdev())
  1153. avg = float(self[key].mean())
  1154. std = float(self[key].std())
  1155. sem = float(self[key].sem())
  1156. semp = float(self[key].semp())
  1157. _, _, block_sd, block_sem = block_statistics(self[key])
  1158. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  1159. for key in self.composite_keys:
  1160. # Now print out the composite terms
  1161. stdev = float(self[key].stdev())
  1162. avg = float(self[key].mean())
  1163. std = float(self[key].std())
  1164. sem = float(self[key].sem())
  1165. semp = float(self[key].semp())
  1166. _, _, block_sd, block_sem = block_statistics(self[key])
  1167. summary_list.append([key, avg, stdev, std, semp, sem, block_sd, block_sem])
  1168. return summary_list
  1169. class DeltaIEC2Statistic(dict):
  1170. def __init__(self, mut, norm, **kwargs):
  1171. super(DeltaIEC2Statistic, self).__init__(**kwargs)
  1172. self.mut = mut
  1173. self.norm = norm
  1174. self.meth = 'C2' if 'c2data' in self.norm else 'IE'
  1175. self._delta()
  1176. def _delta(self):
  1177. """
  1178. Calculates the delta statistics. Should check for any consistencies that
  1179. would cause verbosity levels to change, and it should change them
  1180. accordingly in the child classes
  1181. """
  1182. for key in self.norm:
  1183. if key == 'sigma':
  1184. self[key] = (self.mut[key] + self.norm[key]) / 2
  1185. elif key == 'ieframes':
  1186. self[key] = self.norm[key]
  1187. elif key in ('ie_value', 'tail_mean'):
  1188. self[key] = self.mut[key] - self.norm[key]
  1189. elif key in ('tail_std', 'block_std', 'block_sem'):
  1190. self[key] = get_std(self.mut[key], self.norm[key])
  1191. elif key in ('block_size', 'block_nblocks'):
  1192. self[key] = min(self.mut[key], self.norm[key])
  1193. elif key == 'c2data':
  1194. self[key] = self.mut[key] - self.norm[key]
  1195. elif key in ('c2_std', 'c2_sem'):
  1196. self[key] = get_std(self.mut[key], self.norm[key])
  1197. elif key == 'c2_ci':
  1198. continue
  1199. else:
  1200. self[key] = self.mut[key].corr_sub(self.norm[key])
  1201. def _print_vectors(self, csvwriter):
  1202. """ Output all of the energy terms including the differences if we're
  1203. doing a single trajectory simulation and there are no missing terms
  1204. """
  1205. csvwriter.writerow(['Delta Delta Entropy Terms (Mutant - Normal)'])
  1206. # write the header
  1207. csvwriter.writerow(['Frame #'] + list(self.keys()))
  1208. # write out each frame
  1209. c = self.norm.INPUT['nmode']['nmstartframe'] if isinstance(self.norm, NMODEout) else\
  1210. self.norm.INPUT['general']['startframe']
  1211. for i in range(self.numframes):
  1212. csvwriter.writerow([c] + [round(self[key][i], 2) for key in self])
  1213. c += self.norm.INPUT['nmode']['nminterval'] if isinstance(self.norm, NMODEout) else\
  1214. self.norm.INPUT['general']['interval']
  1215. csvwriter.writerow([])
  1216. def summary_output(self):
  1217. summary = self.summary()
  1218. text = []
  1219. for row in summary:
  1220. key, sigma, avg, std, sem = row
  1221. if isinstance(avg, str):
  1222. text.extend((f'{key:15s} {sigma:>14s} {avg:>16s} {std:>14s} {sem:>14s}', sep))
  1223. else:
  1224. text.append(f"Δ{key:14s} {sigma:14.2f} {avg:16.2f} {std:14.2f} {sem:14.2f}")
  1225. return '\n'.join(text) + '\n\n'
  1226. def summary(self):
  1227. """ Returns a string printing the summary of the binding statistics """
  1228. if self.meth == 'C2':
  1229. return [
  1230. [
  1231. 'Method',
  1232. 'σ(Int. Energy)',
  1233. 'C2 Value',
  1234. 'Block SD',
  1235. 'Block SEM'
  1236. ],
  1237. ['C2', float(self['sigma']), float(self['c2data']), float(self['c2_std']),
  1238. float(self.get('c2_sem', self['c2_std']))]
  1239. ]
  1240. avg = float(self.get('ie_value', self['iedata'].mean()))
  1241. stdev = float(self.get('block_std', self['data'][-self['ieframes']:].stdev()))
  1242. return [
  1243. [
  1244. 'Method',
  1245. 'σ(Int. Energy)',
  1246. 'Full IE',
  1247. 'Block SD',
  1248. 'Block SEM'
  1249. ],
  1250. ['IE', self['sigma'], avg, stdev,
  1251. float(self.get('block_sem', self['data'][-self['ieframes']:].sem()))]
  1252. ]
  1253. class DecompOut(dict):
  1254. """ Class for decomposition output file to collect statistics and output them """
  1255. indicator = " PRINT DECOMP - TOTAL ENERGIES"
  1256. descriptions = {'TDC': 'Total Energy Decomposition:',
  1257. 'SDC': 'Sidechain Energy Decomposition:',
  1258. 'BDC': 'Backbone Energy Decomposition:'}
  1259. def __init__(self, mol: str, **kwargs):
  1260. super(DecompOut, self).__init__(**kwargs)
  1261. self.numframes = None
  1262. self.mut = None
  1263. self.mol = mol
  1264. self.decfile = None
  1265. self.num_terms = None
  1266. self.allowed_tokens = ('TDC',)
  1267. self.verbose = None
  1268. self.num_files = None
  1269. self.resl = None
  1270. self.basename = None
  1271. self.csvwriter = None
  1272. self.surften = None
  1273. self.frame_idx = 0
  1274. self.current_file = 0 # File counter
  1275. def set_frame_range(self, start=0, end=None, interval=1):
  1276. frames_updated = False
  1277. for term in self.allowed_tokens:
  1278. for res in self[term]:
  1279. for et in self[term][res]:
  1280. self[term][res][et] = self[term][res][et][start:end:interval]
  1281. if not frames_updated:
  1282. self.numframes = len(self[term][res][et])
  1283. frames_updated = True
  1284. self._fill_composite_terms()
  1285. def parse_from_file(self, basename, resl, INPUT, surften, num_files=1, numframes=1, mut=False):
  1286. self.basename = basename # base name of output files
  1287. self.resl = resl
  1288. self.mut = mut
  1289. self.numframes = numframes
  1290. self.num_files = num_files # how many MPI files we created
  1291. self.INPUT = INPUT
  1292. self.verbose = INPUT['decomp']['dec_verbose']
  1293. self.surften = surften # explicitly defined since is for GB and PB models
  1294. if self.verbose in [1, 3]:
  1295. self.allowed_tokens = 'TDC', 'SDC', 'BDC'
  1296. try:
  1297. self.num_terms = int(self._get_num_terms())
  1298. except TypeError:
  1299. raise OutputError('DecompOut: Not a decomp output file')
  1300. for token in self.allowed_tokens:
  1301. self[token] = {}
  1302. self._read()
  1303. self._fill_composite_terms()
  1304. def _get_num_terms(self):
  1305. """ Gets the number of terms in the output file """
  1306. with open('%s.%d' % (self.basename, 0), 'r') as decfile:
  1307. lines = decfile.readlines()
  1308. num_terms = 0
  1309. flag = False
  1310. for line in lines:
  1311. if line[:3] == 'TDC':
  1312. num_terms += 1
  1313. flag = True
  1314. elif flag:
  1315. break
  1316. # We've now gotten to the end of the Total Decomp Contribution,
  1317. # so we know how many terms we have
  1318. if not flag:
  1319. raise TypeError(f"{self.basename}.0 have 0 TDC starts")
  1320. return num_terms
  1321. def _read(self):
  1322. """
  1323. Internal reading function. This should be called at the end of __init__.
  1324. It loops through all of the output files to populate the arrays
  1325. """
  1326. for fileno in range(self.num_files):
  1327. with open('%s.%d' % (self.basename, fileno)) as output_file:
  1328. self._get_decomp_energies(output_file)
  1329. def _get_decomp_energies(self, outfile):
  1330. while line := outfile.readline():
  1331. if self.frame_idx == self.numframes:
  1332. self.frame_idx = 0
  1333. if line[:3] in self.allowed_tokens:
  1334. if self.mut and self.resl[int(line[4:10])].is_mutant():
  1335. resnum = self.resl[int(line[4:10])].mutant_string
  1336. else:
  1337. resnum = self.resl[int(line[4:10])].string
  1338. internal = float(line[11:20])
  1339. vdw = float(line[21:30])
  1340. eel = float(line[31:40])
  1341. pol = float(line[41:50])
  1342. sas = float(line[51:60]) * self.surften
  1343. if resnum not in self[line[:3]]:
  1344. self[line[:3]][resnum] = {}
  1345. for term in ['int', 'vdw', 'eel', 'pol', 'sas']:
  1346. self[line[:3]][resnum][term] = EnergyVector(self.numframes)
  1347. self[line[:3]][resnum]['int'][self.frame_idx] = internal
  1348. self[line[:3]][resnum]['vdw'][self.frame_idx] = vdw
  1349. self[line[:3]][resnum]['eel'][self.frame_idx] = eel
  1350. self[line[:3]][resnum]['pol'][self.frame_idx] = pol
  1351. self[line[:3]][resnum]['sas'][self.frame_idx] = sas
  1352. if line[:3] == self.allowed_tokens[-1] and resnum == list(self.resl.values())[-1].string:
  1353. self.frame_idx += 1
  1354. def _print_vectors(self, csvwriter):
  1355. tokens = {'TDC': 'Total Decomposition Contribution (TDC)',
  1356. 'SDC': 'Sidechain Decomposition Contribution (SDC)',
  1357. 'BDC': 'Backbone Decomposition Contribution (BDC)'}
  1358. for term in self.allowed_tokens:
  1359. csvwriter.writerow([tokens[term]])
  1360. csvwriter.writerow(['Frame #', 'Residue', 'Internal', 'van der Waals', 'Electrostatic', 'Polar Solvation',
  1361. 'Non-Polar Solv.', 'TOTAL'])
  1362. c = self.INPUT['general']['startframe']
  1363. for i in range(self.numframes):
  1364. for res in self[term]:
  1365. csvwriter.writerow([c, res] + [round(self[term][res][key][i], 2) for key in self[term][res]])
  1366. c += self.INPUT['general']['interval']
  1367. def _fill_composite_terms(self):
  1368. for term in self:
  1369. for res in self[term]:
  1370. item = self[term][res][list(self[term][res].keys())[0]]
  1371. # pair decomp scheme
  1372. if isinstance(item, dict):
  1373. for res2 in self[term][res]:
  1374. tot = EnergyVector(self.numframes)
  1375. for e in self[term][res][res2]:
  1376. tot = tot + self[term][res][res2][e]
  1377. self[term][res][res2]['tot'] = tot
  1378. else:
  1379. tot = EnergyVector(self.numframes)
  1380. for e in self[term][res]:
  1381. tot = tot + self[term][res][e]
  1382. self[term][res]['tot'] = tot
  1383. def summary(self, output_format: str = 'ascii'):
  1384. """ Writes the summary in ASCII format to and open output_file """
  1385. _output_format = 0 if output_format == 'ascii' else 1
  1386. text = []
  1387. if _output_format:
  1388. text.append([f'{self.mol.capitalize()}:'])
  1389. else:
  1390. text.append(f'{self.mol.capitalize()}:')
  1391. for term in self:
  1392. if _output_format:
  1393. text.extend([[self.descriptions[term]],
  1394. ['Residue', 'Internal', '', '', 'van der Waals', '',
  1395. '', 'Electrostatic', '', '', 'Polar Solvation', '',
  1396. '', 'Non-Polar Solv.', '', '', 'TOTAL', '', ''],
  1397. [''] + ['Avg.', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM'] * 6])
  1398. else:
  1399. text.extend([self.descriptions[term],
  1400. 'Residue | Internal Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1401. '| van der Waals Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1402. '| Electrostatic Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1403. '| Polar Solvation Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1404. '| Non-Polar Solv. Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1405. '| TOTAL Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD',
  1406. '-------------------------------------------------------------------------------------'
  1407. '-------------------------------------------------------------'])
  1408. for res in self[term]:
  1409. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem = _vector_statistics(self[term][res]['int'])
  1410. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem = _vector_statistics(self[term][res]['vdw'])
  1411. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem = _vector_statistics(self[term][res]['eel'])
  1412. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem = _vector_statistics(self[term][res]['pol'])
  1413. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem = _vector_statistics(self[term][res]['sas'])
  1414. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem = _vector_statistics(self[term][res]['tot'])
  1415. if _output_format:
  1416. text.append([res,
  1417. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem,
  1418. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem,
  1419. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem,
  1420. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem,
  1421. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem,
  1422. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem])
  1423. else:
  1424. text.append(f"{res:14s} "
  1425. f"|{int_avg:9.3f} +/- {int_block_sem:6.3f} [{int_stdev:6.3f}/{int_std:6.3f}/{int_sem:6.3f}] / {int_block_sd:6.3f} "
  1426. f"|{vdw_avg:9.3f} +/- {vdw_block_sem:6.3f} [{vdw_stdev:6.3f}/{vdw_std:6.3f}/{vdw_sem:6.3f}] / {vdw_block_sd:6.3f} "
  1427. f"|{eel_avg:9.3f} +/- {eel_block_sem:6.3f} [{eel_stdev:6.3f}/{eel_std:6.3f}/{eel_sem:6.3f}] / {eel_block_sd:6.3f} "
  1428. f"|{pol_avg:9.3f} +/- {pol_block_sem:6.3f} [{pol_stdev:6.3f}/{pol_std:6.3f}/{pol_sem:6.3f}] / {pol_block_sd:6.3f} "
  1429. f"|{sas_avg:9.3f} +/- {sas_block_sem:6.3f} [{sas_stdev:6.3f}/{sas_std:6.3f}/{sas_sem:6.3f}] / {sas_block_sd:6.3f} "
  1430. f"|{tot_avg:9.3f} +/- {tot_block_sem:6.3f} [{tot_stdev:6.3f}/{tot_std:6.3f}/{tot_sem:6.3f}] / {tot_block_sd:6.3f}")
  1431. if _output_format:
  1432. text.append([])
  1433. else:
  1434. text.append('')
  1435. return text if _output_format else '\n'.join(text)
  1436. class PairDecompOut(DecompOut):
  1437. """ Same as DecompOut, but for Pairwise decomposition """
  1438. indicator = " PRINT PAIR DECOMP - TOTAL ENERGIES"
  1439. def set_frame_range(self, start=0, end=None, interval=1):
  1440. frames_updated = False
  1441. for term in self.allowed_tokens:
  1442. for res in self[term]:
  1443. for res2 in self[term][res]:
  1444. for et in self[term][res][res2]:
  1445. self[term][res][res2][et] = self[term][res][res2][et][start:end:interval]
  1446. if not frames_updated:
  1447. self.numframes = len(self[term][res][res2][et])
  1448. frames_updated = True
  1449. self._fill_composite_terms()
  1450. def _get_decomp_energies(self, outfile):
  1451. while line := outfile.readline():
  1452. if line[:3] in self.allowed_tokens:
  1453. if self.mut and self.resl[int(line[4:11])].is_mutant():
  1454. resnum = self.resl[int(line[4:11])].mutant_string
  1455. else:
  1456. resnum = self.resl[int(line[4:11])].string
  1457. if self.mut and self.resl[int(line[13:20])].is_mutant():
  1458. resnum2 = self.resl[int(line[13:20])].mutant_string
  1459. else:
  1460. resnum2 = self.resl[int(line[13:20])].string
  1461. internal = float(line[21:33])
  1462. vdw = float(line[34:46])
  1463. eel = float(line[47:59])
  1464. pol = float(line[60:72])
  1465. sas = float(line[73:85]) * self.surften
  1466. if resnum not in self[line[:3]]:
  1467. self[line[:3]][resnum] = {}
  1468. if resnum2 not in self[line[:3]][resnum]:
  1469. self[line[:3]][resnum][resnum2] = {}
  1470. for term in ['int', 'vdw', 'eel', 'pol', 'sas']:
  1471. self[line[:3]][resnum][resnum2][term] = EnergyVector(self.numframes)
  1472. self[line[:3]][resnum][resnum2]['int'][self.frame_idx] = internal
  1473. self[line[:3]][resnum][resnum2]['vdw'][self.frame_idx] = vdw
  1474. self[line[:3]][resnum][resnum2]['eel'][self.frame_idx] = eel
  1475. self[line[:3]][resnum][resnum2]['pol'][self.frame_idx] = pol
  1476. self[line[:3]][resnum][resnum2]['sas'][self.frame_idx] = sas
  1477. if line[:3] == self.allowed_tokens[-1] and resnum == list(self.resl.values())[-1].string == resnum2:
  1478. self.frame_idx += 1
  1479. def _print_vectors(self, csvwriter):
  1480. tokens = {'TDC': 'Total Decomposition Contribution (TDC)',
  1481. 'SDC': 'Sidechain Decomposition Contribution (SDC)',
  1482. 'BDC': 'Backbone Decomposition Contribution (BDC)'}
  1483. for term in self.allowed_tokens:
  1484. csvwriter.writerow([tokens[term]])
  1485. csvwriter.writerow(['Frame #', 'Resid 1', 'Resid 2', 'Internal', 'van der Waals', 'Electrostatic',
  1486. 'Polar Solvation', 'Non-Polar Solv.', 'TOTAL'])
  1487. c = self.INPUT['general']['startframe']
  1488. for i in range(self.numframes):
  1489. for res in self[term]:
  1490. for res2 in self[term][res]:
  1491. csvwriter.writerow([c, res, res2] +
  1492. [round(self[term][res][res2][key][i], 2) for key in self[term][res][res2]])
  1493. c += self.INPUT['general']['interval']
  1494. def summary(self, output_format: str = 'ascii'):
  1495. """ Writes the summary in ASCII format to and open output_file """
  1496. _output_format = 0 if output_format == 'ascii' else 1
  1497. text = []
  1498. if _output_format:
  1499. text.extend([[f'{self.mol.capitalize()}:']])
  1500. else:
  1501. text.extend([f'{self.mol.capitalize()}:'])
  1502. for term in self:
  1503. if _output_format:
  1504. text.extend([[self.descriptions[term]],
  1505. ['Resid 1', 'Resid 2', 'Internal', '', '', 'van der Waals', '', '', 'Electrostatic',
  1506. '', '', 'Polar Solvation', '', '', 'Non-Polar Solv.', '', '', 'TOTAL', '', ''],
  1507. [''] * 2 + ['Avg.', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM'] * 6])
  1508. else:
  1509. text.append(self.descriptions[term] + '\n' +
  1510. 'Resid 1 | Resid 2 | Internal Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | '
  1511. 'van der Waals Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | Electrostatic Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1512. '| Polar Solvation Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | Non-Polar Solv. Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1513. '| TOTAL Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD\n' +
  1514. '-----------------------------------------------------------------------------'
  1515. '--------------------------------------------------------------------------------------')
  1516. for res in self[term]:
  1517. for res2 in self[term][res]:
  1518. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem = _vector_statistics(self[term][res][res2]['int'])
  1519. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem = _vector_statistics(self[term][res][res2]['vdw'])
  1520. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem = _vector_statistics(self[term][res][res2]['eel'])
  1521. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem = _vector_statistics(self[term][res][res2]['pol'])
  1522. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem = _vector_statistics(self[term][res][res2]['sas'])
  1523. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem = _vector_statistics(self[term][res][res2]['tot'])
  1524. if _output_format:
  1525. text.append([res, res2,
  1526. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem,
  1527. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem,
  1528. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem,
  1529. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem,
  1530. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem,
  1531. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem])
  1532. else:
  1533. text.append(f"{res:14s} | {res2:14s} "
  1534. f"|{int_avg:9.3f} +/- {int_block_sem:6.3f} [{int_stdev:6.3f}/{int_std:6.3f}/{int_sem:6.3f}] / {int_block_sd:6.3f} "
  1535. f"|{vdw_avg:9.3f} +/- {vdw_block_sem:6.3f} [{vdw_stdev:6.3f}/{vdw_std:6.3f}/{vdw_sem:6.3f}] / {vdw_block_sd:6.3f} "
  1536. f"|{eel_avg:9.3f} +/- {eel_block_sem:6.3f} [{eel_stdev:6.3f}/{eel_std:6.3f}/{eel_sem:6.3f}] / {eel_block_sd:6.3f} "
  1537. f"|{pol_avg:9.3f} +/- {pol_block_sem:6.3f} [{pol_stdev:6.3f}/{pol_std:6.3f}/{pol_sem:6.3f}] / {pol_block_sd:6.3f} "
  1538. f"|{sas_avg:9.3f} +/- {sas_block_sem:6.3f} [{sas_stdev:6.3f}/{sas_std:6.3f}/{sas_sem:6.3f}] / {sas_block_sd:6.3f} "
  1539. f"|{tot_avg:9.3f} +/- {tot_block_sem:6.3f} [{tot_stdev:6.3f}/{tot_std:6.3f}/{tot_sem:6.3f}] / {tot_block_sd:6.3f}")
  1540. if _output_format:
  1541. text.append([])
  1542. else:
  1543. text.append('')
  1544. return text if _output_format else '\n'.join(text) + '\n\n'
  1545. class DecompBinding(dict):
  1546. """ Class for decomposition binding (per-residue) """
  1547. def __init__(self, com, rec, lig, INPUT, desc=None, **kwargs):
  1548. """
  1549. output should be an open file and csvfile should be a csv.writer class. If
  1550. the output format is specified as csv, then output should be a csv.writer
  1551. class as well.
  1552. """
  1553. super(DecompBinding, self).__init__(**kwargs)
  1554. self.com, self.rec, self.lig = com, rec, lig
  1555. self.num_terms = self.com.num_terms
  1556. self.desc = desc # Description
  1557. self.INPUT = INPUT
  1558. self.idecomp = INPUT['decomp']['idecomp']
  1559. self.verbose = INPUT['decomp']['dec_verbose']
  1560. # Set up the data for the DELTAs
  1561. if self.verbose in [1, 3]:
  1562. self.allowed_tokens = 'TDC', 'SDC', 'BDC'
  1563. else:
  1564. self.allowed_tokens = ('TDC',)
  1565. for token in self.allowed_tokens:
  1566. self[token] = {}
  1567. # Parse everything
  1568. self._parse_all_begin()
  1569. def _print_vectors(self, csvwriter):
  1570. tokens = {'TDC': 'Total Decomposition Contribution (TDC)',
  1571. 'SDC': 'Sidechain Decomposition Contribution (SDC)',
  1572. 'BDC': 'Backbone Decomposition Contribution (BDC)'}
  1573. for term in self.allowed_tokens:
  1574. csvwriter.writerow([tokens[term]])
  1575. csvwriter.writerow(['Frame #', 'Residue', 'Internal', 'van der Waals', 'Electrostatic', 'Polar Solvation',
  1576. 'Non-Polar Solv.', 'TOTAL'])
  1577. c = self.INPUT['general']['startframe']
  1578. for i in range(self.com.numframes):
  1579. for res in self[term]:
  1580. csvwriter.writerow([c, res] + [round(self[term][res][key][i], 2) for key in self[term][res]])
  1581. c += self.INPUT['general']['interval']
  1582. def _parse_all_begin(self):
  1583. """ Parses through all of the terms in all of the frames, but doesn't
  1584. do any printing
  1585. """
  1586. # For per-residue decomp, we need terms to match up
  1587. if self.com.num_terms != (self.rec.num_terms + self.lig.num_terms):
  1588. raise DecompError('Mismatch in number of decomp terms!')
  1589. for term in self.com:
  1590. for res in self.com[term]:
  1591. self[term][res] = {}
  1592. other_token = self.rec[term][res] if res.startswith('R') else self.lig[term][res]
  1593. for e in self.com[term][res]:
  1594. self[term][res][e] = self.com[term][res][e] - other_token[e]
  1595. def summary(self, output_format: str = 'ascii'):
  1596. _output_format = 0 if output_format == 'ascii' else 1
  1597. text = []
  1598. if _output_format:
  1599. text.extend([[idecompString[self.idecomp]], [self.desc], []])
  1600. else:
  1601. text.extend((idecompString[self.idecomp], self.desc))
  1602. if self.verbose > 1:
  1603. if _output_format:
  1604. text.extend(self.com.summary(output_format))
  1605. text.extend(self.rec.summary(output_format))
  1606. text.extend(self.lig.summary(output_format))
  1607. else:
  1608. text.extend((self.com.summary(), self.rec.summary(), self.lig.summary()))
  1609. if _output_format:
  1610. text.append(['DELTAS:'])
  1611. else:
  1612. text.append('DELTAS:')
  1613. for term in self:
  1614. if _output_format:
  1615. text.extend([[DecompOut.descriptions[term]],
  1616. ['Residue', 'Internal', '', '', 'van der Waals', '', '', 'Electrostatic',
  1617. '', '', 'Polar Solvation', '', '', 'Non-Polar Solv.', '', '', 'TOTAL', '', ''],
  1618. [''] + ['Avg.', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM'] * 6])
  1619. else:
  1620. text.extend([DecompOut.descriptions[term],
  1621. 'Residue | Internal Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1622. '| van der Waals Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1623. '| Electrostatic Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1624. '| Polar Solvation Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1625. '| Non-Polar Solv. Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1626. '| TOTAL Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD',
  1627. '----------------------------------------------------------------------------------'
  1628. '----------------------------------------------------------------'])
  1629. for res in self[term]:
  1630. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem = _vector_statistics(self[term][res]['int'])
  1631. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem = _vector_statistics(self[term][res]['vdw'])
  1632. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem = _vector_statistics(self[term][res]['eel'])
  1633. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem = _vector_statistics(self[term][res]['pol'])
  1634. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem = _vector_statistics(self[term][res]['sas'])
  1635. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem = _vector_statistics(self[term][res]['tot'])
  1636. if _output_format:
  1637. text.append([res,
  1638. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem,
  1639. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem,
  1640. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem,
  1641. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem,
  1642. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem,
  1643. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem])
  1644. else:
  1645. text.append(f"{res:14s} "
  1646. f"|{int_avg:9.3f} +/- {int_block_sem:6.3f} [{int_stdev:6.3f}/{int_std:6.3f}/{int_sem:6.3f}] / {int_block_sd:6.3f} "
  1647. f"|{vdw_avg:9.3f} +/- {vdw_block_sem:6.3f} [{vdw_stdev:6.3f}/{vdw_std:6.3f}/{vdw_sem:6.3f}] / {vdw_block_sd:6.3f} "
  1648. f"|{eel_avg:9.3f} +/- {eel_block_sem:6.3f} [{eel_stdev:6.3f}/{eel_std:6.3f}/{eel_sem:6.3f}] / {eel_block_sd:6.3f} "
  1649. f"|{pol_avg:9.3f} +/- {pol_block_sem:6.3f} [{pol_stdev:6.3f}/{pol_std:6.3f}/{pol_sem:6.3f}] / {pol_block_sd:6.3f} "
  1650. f"|{sas_avg:9.3f} +/- {sas_block_sem:6.3f} [{sas_stdev:6.3f}/{sas_std:6.3f}/{sas_sem:6.3f}] / {sas_block_sd:6.3f} "
  1651. f"|{tot_avg:9.3f} +/- {tot_block_sem:6.3f} [{tot_stdev:6.3f}/{tot_std:6.3f}/{tot_sem:6.3f}] / {tot_block_sd:6.3f}")
  1652. if _output_format:
  1653. text.append([])
  1654. else:
  1655. text.append('')
  1656. return text if _output_format else '\n'.join(text)
  1657. class PairDecompBinding(DecompBinding):
  1658. """ Class for decomposition binding (pairwise) """
  1659. def _parse_all_begin(self):
  1660. """ Parses through all of the terms in all of the frames, but doesn't
  1661. do any printing
  1662. """
  1663. for term in self.com:
  1664. for res in self.com[term]:
  1665. self[term][res] = {}
  1666. for res2 in self.com[term][res]:
  1667. self[term][res][res2] = {}
  1668. if res.startswith('R') and res2.startswith('R'):
  1669. # Both residues are in the receptor -- pull the next one
  1670. other_token = self.rec[term][res][res2]
  1671. elif (
  1672. res.startswith('R')
  1673. and not res2.startswith('R')
  1674. or not res.startswith('R')
  1675. and not res2.startswith('L')
  1676. ):
  1677. other_token = {}
  1678. else:
  1679. # Both residues are in the ligand -- pull the next one
  1680. other_token = self.lig[term][res][res2]
  1681. for e in self.com[term][res][res2]:
  1682. if other_token:
  1683. self[term][res][res2][e] = self.com[term][res][res2][e] - other_token[e]
  1684. else:
  1685. self[term][res][res2][e] = self.com[term][res][res2][e]
  1686. def _print_vectors(self, csvwriter):
  1687. tokens = {'TDC': 'Total Decomposition Contribution (TDC)',
  1688. 'SDC': 'Sidechain Decomposition Contribution (SDC)',
  1689. 'BDC': 'Backbone Decomposition Contribution (BDC)'}
  1690. for term in self.allowed_tokens:
  1691. csvwriter.writerow([tokens[term]])
  1692. csvwriter.writerow(['Frame #', 'Resid 1', 'Resid 2', 'Internal', 'van der Waals', 'Electrostatic',
  1693. 'Polar Solvation', 'Non-Polar Solv.', 'TOTAL'])
  1694. c = self.com.INPUT['general']['startframe']
  1695. for i in range(self.com.numframes):
  1696. for res in self[term]:
  1697. for res2 in self[term][res]:
  1698. csvwriter.writerow([c, res, res2] +
  1699. [round(self[term][res][res2][key][i], 2) for key in self[term][res][res2]])
  1700. c += self.com.INPUT['general']['interval']
  1701. def summary(self, output_format: str = 'ascii'):
  1702. _output_format = 0 if output_format == 'ascii' else 1
  1703. text = []
  1704. if _output_format:
  1705. text.extend([[idecompString[self.idecomp]], [self.desc], []])
  1706. else:
  1707. text.extend((idecompString[self.idecomp], self.desc, ''))
  1708. if self.verbose > 1:
  1709. if _output_format:
  1710. text.extend(self.com.summary(output_format))
  1711. text.extend(self.rec.summary(output_format))
  1712. text.extend(self.lig.summary(output_format))
  1713. else:
  1714. text.extend((self.com.summary(), self.rec.summary(), self.lig.summary()))
  1715. if _output_format:
  1716. text.append(['DELTAS:'])
  1717. else:
  1718. text.append('DELTAS:')
  1719. for term in self:
  1720. if _output_format:
  1721. text.extend([[DecompOut.descriptions[term]],
  1722. ['Resid 1', 'Resid 2', 'Internal', '', '', 'van der Waals', '', '', 'Electrostatic', '',
  1723. '', 'Polar Solvation', '', '', 'Non-Polar Solv.', '', '', 'TOTAL', '', ''],
  1724. ['', ''] + ['Avg.', 'SD(Prop.)', 'SD', 'SEM(Prop.)', 'SEM', 'Block SD', 'Block SEM'] * 6])
  1725. else:
  1726. text.extend([DecompOut.descriptions[term],
  1727. 'Resid 1 | Resid 2 | Internal Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | '
  1728. 'van der Waals Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | Electrostatic Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1729. '| Polar Solvation Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD | Non-Polar Solv. Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD '
  1730. '| TOTAL Avg +/- Block SEM [SD(Prop.)/SD/SEM] / Block SD',
  1731. '-----------------------------------------------------------------------------------------'
  1732. '--------------------------------------------------------------------------'])
  1733. for res in self[term]:
  1734. for res2 in self[term][res]:
  1735. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem = _vector_statistics(self[term][res][res2]['int'])
  1736. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem = _vector_statistics(self[term][res][res2]['vdw'])
  1737. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem = _vector_statistics(self[term][res][res2]['eel'])
  1738. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem = _vector_statistics(self[term][res][res2]['pol'])
  1739. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem = _vector_statistics(self[term][res][res2]['sas'])
  1740. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem = _vector_statistics(self[term][res][res2]['tot'])
  1741. if _output_format:
  1742. text.append([res, res2,
  1743. int_avg, int_stdev, int_std, int_semp, int_sem, int_block_sd, int_block_sem,
  1744. vdw_avg, vdw_stdev, vdw_std, vdw_semp, vdw_sem, vdw_block_sd, vdw_block_sem,
  1745. eel_avg, eel_stdev, eel_std, eel_semp, eel_sem, eel_block_sd, eel_block_sem,
  1746. pol_avg, pol_stdev, pol_std, pol_semp, pol_sem, pol_block_sd, pol_block_sem,
  1747. sas_avg, sas_stdev, sas_std, sas_semp, sas_sem, sas_block_sd, sas_block_sem,
  1748. tot_avg, tot_stdev, tot_std, tot_semp, tot_sem, tot_block_sd, tot_block_sem])
  1749. else:
  1750. text.append(f"{res:14s} | {res2:14s} "
  1751. f"|{int_avg:9.3f} +/- {int_block_sem:6.3f} [{int_stdev:6.3f}/{int_std:6.3f}/{int_sem:6.3f}] / {int_block_sd:6.3f} "
  1752. f"|{vdw_avg:9.3f} +/- {vdw_block_sem:6.3f} [{vdw_stdev:6.3f}/{vdw_std:6.3f}/{vdw_sem:6.3f}] / {vdw_block_sd:6.3f} "
  1753. f"|{eel_avg:9.3f} +/- {eel_block_sem:6.3f} [{eel_stdev:6.3f}/{eel_std:6.3f}/{eel_sem:6.3f}] / {eel_block_sd:6.3f} "
  1754. f"|{pol_avg:9.3f} +/- {pol_block_sem:6.3f} [{pol_stdev:6.3f}/{pol_std:6.3f}/{pol_sem:6.3f}] / {pol_block_sd:6.3f} "
  1755. f"|{sas_avg:9.3f} +/- {sas_block_sem:6.3f} [{sas_stdev:6.3f}/{sas_std:6.3f}/{sas_sem:6.3f}] / {sas_block_sd:6.3f} "
  1756. f"|{tot_avg:9.3f} +/- {tot_block_sem:6.3f} [{tot_stdev:6.3f}/{tot_std:6.3f}/{tot_sem:6.3f}] / {tot_block_sd:6.3f}")
  1757. if _output_format:
  1758. text.append([])
  1759. else:
  1760. text.append('')
  1761. return text if _output_format else '\n'.join(text) + '\n\n'
  1762. def _get_cpptraj_surf(fname):
  1763. """
  1764. This function will parse out the surface areas printed out by cpptraj in a
  1765. standard data file and return it as an EnergyVector instance.
  1766. """
  1767. f = open(fname, 'r')
  1768. vec = EnergyVector()
  1769. for line in f:
  1770. if line.startswith('#'):
  1771. continue
  1772. vec = vec.append(float(line.split()[1]))
  1773. return vec

amber_outputs.py at commit 6db310d, under GPL-3.0 · at the source

Overview

Authors: Qiyu Yang1, Jinming Zhang1, Yuxuan Dao1, Zhengrui He1, Rui Lu1, Rummana Jaman1, Yaole Wu1, Shuqi Liu1, Conglin Zhang2, Zhibi Zhang3, Jiaqi Zhou4
ORCID iDs: Jiaqi Zhou
  1. College of Clinical Medicine, Dali University, Dali, Yunnan 671000, China
  2. College of Sports Science, Dali University, Dali, Yunnan 671000, China
  3. Yunnan Key Laboratory of Breast Cancer Precision Medicine, Academy of Biomedical Engineering, Kunming Medical University, Kunming, Yunnan 650500, China
  4. School of Basic Medical Sciences, Dali University, Dali, Yunnan 671000, China
Institutions: Dali University (China); Kunming Medical University (China)
Journal: iScience, volume 29, issue 9, article 117147
Dates: received 25 January 2026; accepted 25 July 2026; published online 13 August 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.isci.2026.117147 · PMID 42633227 · PMCID PMC13499207 · OpenAlex W7202370640
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: methods / tools (subfield)
Methods: Statistics
Keywords: cyclotriphosphazenes, toxicological evaluation, blood-brain barrier, computational toxicology methods
Topic: Cholinesterase and Neurodegenerative Diseases (Pharmacology, Medicine), according to OpenAlex
Funding: Dali University (KY2396120340); Yunnan Provincial Department of Education (KY2414107040, S202510679099)
Citations: not cited yet (Europe PMC); 57 references in the paper

Abstract

Cyclotriphosphazenes (CTPs), widely used as lithium-ion battery electrolyte additives, are increasingly recognized as environmental contaminants with potential neurotoxicity. We developed an integrated computational framework to evaluate the blood-brain barrier (BBB) penetration potential and neurotoxic mechanisms of CTPs. Six representative CTPs were predicted to penetrate the BBB via passive diffusion and impair BBB function. Among them, HPCTP, PFPCTP, HCCTP, and HFCTP primarily disrupted BBB function through inhibition of ATP-binding cassette transporters. HPCTP exhibited the strongest BBB-disrupting potential, primarily through interactions with SLC2A1 and SLC6A3 and through the modulation of BBB-related gene expression. Experimental validation in hCMEC/D3 cells showed that HPCTP impaired glucose transport and utilization and significantly downregulated SLC2A1 expression. These findings provide a strategy for assessing BBB penetration of exogenous chemicals and highlight the potential CNS risks associated with environmental CTP exposure.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.

Valdes-Tresanco-MS/gmx_MMPBSA

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 6db310d5d48074d442ebac6348ccb135567d95a7, 13 September 2026
Languages: Python (114), JavaScript (4), Shell (2), Jupyter (2)
Size: 802 files, 122 scripts
Software Heritage: not archived
Found in: the text, “Key resources table”
Holds: README, license file, environment (setup.cfg, setup.py, docs/env.yml, docs/requirements.txt), tests, continuous integration, documentation, 2 notebooks
Not found: CITATION.cff
Tools: NumPy (15 files), pandas (11 files), Matplotlib (7 files), seaborn (5 files), SciPy (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
124 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 122 scripts, each with its path and the digest of its content;
  • 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data and code availability

All data generated or analyzed during this study are included in this article and its supplemental information. Additional raw data supporting the findings of this study are available from the corresponding authors upon reasonable request. No original code was developed for this study; all computational analyses were performed using publicly available software and online databases as described in the STAR Methods section.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 3, 28 September 2026

  • Authors: added Jiaqi Zhou (0009-0004-5802-5697); removed Jiaqi Zhou

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 11 authors, 4 keywords, 2 funders, 57 references.

Cite

This paper

Yang, Q., Zhang, J., Dao, Y., He, Z., Lu, R., Jaman, R., Wu, Y., Liu, S., Zhang, C., Zhang, Z., & Zhou, J. (2026). Penetrating potential evaluation of cyclotriphosphazenes target blood-brain barrier via computational toxicology methods. iScience, 29(9), 117147. https://doi.org/10.1016/j.isci.2026.117147

BibTeX

@article{yang2026penetrating,
author = {Yang, Qiyu and Zhang, Jinming and Dao, Yuxuan and He, Zhengrui and Lu, Rui and Jaman, Rummana and Wu, Yaole and Liu, Shuqi and Zhang, Conglin and Zhang, Zhibi and Zhou, Jiaqi},
title = {{Penetrating potential evaluation of cyclotriphosphazenes target blood-brain barrier via computational toxicology methods}},
journal = {iScience},
year = {2026},
month = aug,
volume = {29},
number = {9},
pages = {117147},
publisher = {Elsevier},
issn = {2589-0042},
doi = {10.1016/j.isci.2026.117147},
url = {https://doi.org/10.1016/j.isci.2026.117147},
pmid = {42633227},
pmcid = {PMC13499207}
}

RIS

TY - JOUR
AU - Yang, Qiyu
AU - Zhang, Jinming
AU - Dao, Yuxuan
AU - He, Zhengrui
AU - Lu, Rui
AU - Jaman, Rummana
AU - Wu, Yaole
AU - Liu, Shuqi
AU - Zhang, Conglin
AU - Zhang, Zhibi
AU - Zhou, Jiaqi
TI - Penetrating potential evaluation of cyclotriphosphazenes target blood-brain barrier via computational toxicology methods
T2 - iScience
J2 - iScience
PY - 2026
DA - 2026/08/13
VL - 29
IS - 9
SP - 117147
SN - 2589-0042
PB - Elsevier
DO - 10.1016/j.isci.2026.117147
UR - https://doi.org/10.1016/j.isci.2026.117147
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.isci.2026.117147",
"type": "article-journal",
"title": "Penetrating potential evaluation of cyclotriphosphazenes target blood-brain barrier via computational toxicology methods",
"container-title": "iScience",
"author": [
{
"family": "Yang",
"given": "Qiyu"
},
{
"family": "Zhang",
"given": "Jinming"
},
{
"family": "Dao",
"given": "Yuxuan"
},
{
"family": "He",
"given": "Zhengrui"
},
{
"family": "Lu",
"given": "Rui"
},
{
"family": "Jaman",
"given": "Rummana"
},
{
"family": "Wu",
"given": "Yaole"
},
{
"family": "Liu",
"given": "Shuqi"
},
{
"family": "Zhang",
"given": "Conglin"
},
{
"family": "Zhang",
"given": "Zhibi"
},
{
"family": "Zhou",
"given": "Jiaqi"
}
],
"container-title-short": "iScience",
"volume": "29",
"issue": "9",
"page": "117147",
"DOI": "10.1016/j.isci.2026.117147",
"PMID": "42633227",
"PMCID": "PMC13499207",
"ISSN": "2589-0042",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.isci.2026.117147",
"language": "en",
"issued": {
"date-parts": [
[
2026,
8,
13
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.3390/pharmaceutics18060670
Predicting Blood-Brain Barrier Permeability from Experimental Data: An Interpretable and Externally Validated Machine Learning Framework.
Journal: Pharmaceutics
In common: methods / tools, 3 references
[2] doi:10.1186/s12916-026-04903-y [code]
Structural connectome architecture and biological vulnerability shape cortical atrophy in cocaine use disorder.
Journal: BMC medicine
In common: seaborn, pandas, SciPy, 2 other tools, 1 reference
[3] doi:10.1038/s41586-026-10323-y [code]
Genetically encoded assembly recorder temporally resolves cellular history.
Journal: Nature
In common: seaborn, pandas, SciPy, 2 other tools, 1 reference
[4] doi:10.1038/s41598-026-48239-2 [code]
LigGen-a GEN-AI based ligand generation approach for de-novo drug design.
Journal: Scientific reports
In common: SciPy, Matplotlib, NumPy, 2 references
[5] doi:10.1038/s41467-026-71391-2 [code]
Accelerating Leigh syndrome drug discovery through deep learning screening in brain organoids.
Journal: Nature communications
In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
[6] doi:10.1186/s13321-026-01191-9 [code]
Multiscale analysis and optimal glioma therapeutic candidate discovery using the CANDO platform.
Journal: Journal of cheminformatics
In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
[7] doi:10.3390/biomedicines14050998 [code]
Integrative Multi-Omics and Machine Learning Analysis Identifies Therapeutic Targets and Drug Repurposing Candidates for Alzheimer's Disease.
Journal: Biomedicines
In common: 3 references
[8] doi:10.1039/d6ra04733e
V600E biases the BRAF kinase domain toward an activation-compatible conformational ensemble through long-range dynamic rewiring.
Journal: RSC advances
In common: 3 references
[9] doi:10.1038/s41467-026-75444-4 [code]
Structural insights enable drug discovery for the neuronal NBCn2 carbonate transporter.
Journal: Nature communications
In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference
[10] doi:10.1038/s41598-026-53415-5 [code]
Computational design and immunoinformatics validation of a T cell multi-epitope vaccine targeting glioblastoma stem cells.
Journal: Scientific reports
In common: pandas, SciPy, Matplotlib, 1 other tool, 1 reference

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.