5
5
from openfecli import OFECommandPlugin
6
6
from openfecli .clicktypes import HyphenAwareChoice
7
7
import pathlib
8
+ import warnings
8
9
9
10
10
11
def _get_column (val ):
@@ -85,40 +86,90 @@ def legacy_get_type(res_fn):
85
86
return 'complex'
86
87
87
88
88
- def _get_ddgs (legs ):
89
+ def _generate_bad_legs_error_message (set_vals , ligpair ):
90
+ expected_rbfe = {'complex' , 'solvent' }
91
+ expected_rhfe = {'solvent' , 'vacuum' }
92
+ maybe_rhfe = bool (set_vals & expected_rhfe )
93
+ maybe_rbfe = bool (set_vals & expected_rbfe )
94
+ if maybe_rhfe and not maybe_rbfe :
95
+ msg = (
96
+ "This appears to be an RHFE calculation, but we're "
97
+ f"missing { expected_rhfe - set_vals } runs for the "
98
+ f"edge with ligands { ligpair } ."
99
+ )
100
+ elif maybe_rbfe and not maybe_rhfe :
101
+ msg = (
102
+ "This appears to be an RBFE calculation, but we're "
103
+ f"missing { expected_rbfe - set_vals } runs for the "
104
+ f"edge with ligands { ligpair } ."
105
+ )
106
+ elif maybe_rbfe and maybe_rhfe :
107
+ msg = (
108
+ "Unable to determine whether this is an RBFE "
109
+ f"or an RHFE calculation. Found legs { set_vals } "
110
+ f"for ligands { ligpair } . Those ligands are missing one "
111
+ f"of: { (expected_rhfe | expected_rbfe ) - set_vals } ."
112
+ )
113
+ else : # -no-cov-
114
+ # this should never happen
115
+ msg = (
116
+ "Something went very wrong while determining the type "
117
+ f"of RFE calculation. For the ligand pair { ligpair } , "
118
+ f"we found legs labelled { set_vals } . We expected either "
119
+ f"{ expected_rhfe } or { expected_rbfe } ."
120
+ )
121
+
122
+ msg += (
123
+ "\n \n You can force partial gathering of results, without "
124
+ "problematic edges, by using the --allow-partial flag of the gather "
125
+ "command. Note that this may cause problems with predicting "
126
+ "absolute free energies from the relative free energies."
127
+ )
128
+ return msg
129
+
130
+
131
+ def _get_ddgs (legs , error_on_missing = True ):
89
132
import numpy as np
90
133
DDGs = []
91
134
for ligpair , vals in sorted (legs .items ()):
135
+ set_vals = set (vals )
92
136
DDGbind = None
93
137
DDGhyd = None
94
138
bind_unc = None
95
139
hyd_unc = None
96
140
97
- if 'complex' in vals and 'solvent' in vals :
141
+ do_rbfe = (len (set_vals & {'complex' , 'solvent' }) == 2 )
142
+ do_rhfe = (len (set_vals & {'vacuum' , 'solvent' }) == 2 )
143
+
144
+ if do_rbfe :
98
145
DG1_mag , DG1_unc = vals ['complex' ]
99
146
DG2_mag , DG2_unc = vals ['solvent' ]
100
147
if not ((DG1_mag is None ) or (DG2_mag is None )):
101
148
# DDG(2,1)bind = DG(1->2)complex - DG(1->2)solvent
102
149
DDGbind = (DG1_mag - DG2_mag ).m
103
150
bind_unc = np .sqrt (np .sum (np .square ([DG1_unc .m , DG2_unc .m ])))
104
- elif 'solvent' in vals and 'vacuum' in vals :
151
+
152
+ if do_rhfe :
105
153
DG1_mag , DG1_unc = vals ['solvent' ]
106
154
DG2_mag , DG2_unc = vals ['vacuum' ]
107
155
if not ((DG1_mag is None ) or (DG2_mag is None )):
108
156
DDGhyd = (DG1_mag - DG2_mag ).m
109
157
hyd_unc = np .sqrt (np .sum (np .square ([DG1_unc .m , DG2_unc .m ])))
110
- else :
111
- raise RuntimeError ("Unable to determine type of RFE calculation "
112
- f"for edges with labels { list (vals )} for "
113
- f"ligands { ligpair } " )
158
+
159
+ if not do_rbfe and not do_rhfe :
160
+ msg = _generate_bad_legs_error_message (set_vals , ligpair )
161
+ if error_on_missing :
162
+ raise RuntimeError (msg )
163
+ else :
164
+ warnings .warn (msg )
114
165
115
166
DDGs .append ((* ligpair , DDGbind , bind_unc , DDGhyd , hyd_unc ))
116
167
117
168
return DDGs
118
169
119
170
120
- def _write_ddg (legs , writer ):
121
- DDGs = _get_ddgs (legs )
171
+ def _write_ddg (legs , writer , allow_partial ):
172
+ DDGs = _get_ddgs (legs , error_on_missing = not allow_partial )
122
173
writer .writerow (["ligand_i" , "ligand_j" , "DDG(i->j) (kcal/mol)" ,
123
174
"uncertainty (kcal/mol)" ])
124
175
for ligA , ligB , DDGbind , bind_unc , DDGhyd , hyd_unc in DDGs :
@@ -133,7 +184,7 @@ def _write_ddg(legs, writer):
133
184
writer .writerow ([ligA , ligB , DDGhyd , hyd_unc ])
134
185
135
186
136
- def _write_dg_raw (legs , writer ):
187
+ def _write_dg_raw (legs , writer , allow_partial ):
137
188
writer .writerow (["leg" , "ligand_i" , "ligand_j" , "DG(i->j) (kcal/mol)" ,
138
189
"uncertainty (kcal/mol)" ])
139
190
for ligpair , vals in sorted (legs .items ()):
@@ -146,11 +197,11 @@ def _write_dg_raw(legs, writer):
146
197
writer .writerow ([simtype , * ligpair , m , u ])
147
198
148
199
149
- def _write_dg_mle (legs , writer ):
200
+ def _write_dg_mle (legs , writer , allow_partial ):
150
201
import networkx as nx
151
202
import numpy as np
152
203
from cinnabar .stats import mle
153
- DDGs = _get_ddgs (legs )
204
+ DDGs = _get_ddgs (legs , error_on_missing = not allow_partial )
154
205
MLEs = []
155
206
# 4b) perform MLE
156
207
g = nx .DiGraph ()
@@ -219,7 +270,14 @@ def _write_dg_mle(legs, writer):
219
270
@click .option ('output' , '-o' ,
220
271
type = click .File (mode = 'w' ),
221
272
default = '-' )
222
- def gather (rootdir , output , report ):
273
+ @click .option (
274
+ '--allow-partial' , is_flag = True , default = False ,
275
+ help = (
276
+ "Do not raise errors is results are missing parts for some edges. "
277
+ "(Skip those edges and issue warning instead.)"
278
+ )
279
+ )
280
+ def gather (rootdir , output , report , allow_partial ):
223
281
"""Gather simulation result jsons of relative calculations to a tsv file
224
282
225
283
This walks ROOTDIR recursively and finds all result JSON files from the
@@ -287,7 +345,7 @@ def gather(rootdir, output, report):
287
345
'ddg' : _write_ddg ,
288
346
'dg-raw' : _write_dg_raw ,
289
347
}[report .lower ()]
290
- writing_func (legs , writer )
348
+ writing_func (legs , writer , allow_partial )
291
349
292
350
293
351
PLUGIN = OFECommandPlugin (
0 commit comments