-
Notifications
You must be signed in to change notification settings - Fork 16
Add functions to calculate cycle closure #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 19 commits
2276ea7
32338ff
ec1d008
e48668a
5edf55e
2c68605
31477ce
6b2dfd0
0b8329f
675184a
bbbca3f
7f1d209
ddafe13
054b372
ec405d8
ccd83ee
6bbca0a
b24329e
e8099c7
378ed29
f69667c
790ce3a
1813795
5c538e2
7a990c6
5c70d2d
2362af4
fc26bbf
6739d54
cc96b78
7e9f804
2d126b7
030ac30
7c73c45
67d7c42
594a498
d839374
5666bb5
2318569
3669f55
0d8dbb1
082cd6c
a42ca92
dade9fd
3673bca
c1ff1e5
a76a12e
fe608b0
706637b
f4c6990
4c8448f
1838ac4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| """ | ||
|
|
||
| import copy | ||
| import math | ||
| import itertools | ||
| import pathlib | ||
| import warnings | ||
|
|
@@ -745,3 +746,117 @@ def draw_graph(self, title: str = "", filename: Union[str, None] = None): | |
| plt.show() | ||
| else: | ||
| plt.savefig(filename, bbox_inches="tight") | ||
|
|
||
| def get_cycle_closure(self, max_cycle_length: int = 5) -> list[tuple[str, float]]: | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| """ | ||
| Calculate cycle closure errors for all cycles in the network. | ||
|
|
||
| Parameters | ||
| ---------- | ||
| max_cycle_length : int, optional | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| Only consider cycles up to this length. Default 5. | ||
|
|
||
| Returns | ||
| ------- | ||
| pd.DataFrame | ||
| DataFrame with columns 'cycle', 'cc (kcal/mol)', sorted by | ||
| cycle closure error descending. | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| """ | ||
| network = self.to_legacy_graph() | ||
| edge_ddg = {(a, b): d["calc_DDG"] for a, b, d in network.edges(data=True)} | ||
| edge_uncertainty = {(a, b): d["calc_dDDG"] for a, b, d in network.edges(data=True)} | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
|
|
||
| # Find all ligand cycles | ||
| cycles = [c for c in nx.simple_cycles(network.to_undirected()) if len(c) <= max_cycle_length] | ||
|
|
||
| # Loop over cycles, calculate sum of DG along cycle | ||
| rows = [] | ||
| for cycle in cycles: | ||
| # Store DDG values along the cycle | ||
| sum_ddgs = 0.0 | ||
| sum_var = 0.0 | ||
| for inx, ligand in enumerate(cycle): | ||
| lig_a = ligand | ||
| lig_b = cycle[inx + 1] if inx < len(cycle) - 1 else cycle[0] | ||
|
|
||
| # depending on the direction the edge was calculated, | ||
| # the sign of the DDG has to change | ||
| if (lig_a, lig_b) in edge_ddg: | ||
| sum_ddgs += edge_ddg[(lig_a, lig_b)] | ||
| sum_var += edge_uncertainty[(lig_a, lig_b)] ** 2 | ||
| elif (lig_b, lig_a) in edge_ddg: | ||
| sum_ddgs -= edge_ddg[(lig_b, lig_a)] | ||
| sum_var += edge_uncertainty[(lig_b, lig_a)] ** 2 | ||
| else: | ||
| # Edge missing from network; skip this cycle | ||
| break | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
|
|
||
| else: | ||
| # Normalize by sqrt(cycle length) to allow comparison across | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
hannahbaumann marked this conversation as resolved.
Outdated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we have some previously published information of someone using this? This roughly makes sense, but it's not immediately clear to me that it's standard practice.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would be me in the SepTop paper =) |
||
| # different cycle lengths | ||
| cc = abs(sum_ddgs / math.sqrt(len(cycle))) | ||
| cc_uncertainty_normalized = abs(sum_ddgs) / math.sqrt(sum_var) | ||
| rows.append( | ||
| { | ||
| "cycle": tuple(cycle), | ||
| "cc (kcal/mol)": round(cc, 2), | ||
| "cc_unc_normalized (kcal/mol)": round(cc_uncertainty_normalized, 2), | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| } | ||
| ) | ||
|
|
||
| df = ( | ||
| pd.DataFrame(rows, columns=["cycle", "cc (kcal/mol)", "cc_unc_normalized (kcal/mol)"]) | ||
| .sort_values("cc (kcal/mol)", ascending=False) | ||
| .reset_index(drop=True) | ||
| ) | ||
|
|
||
| return df | ||
|
|
||
| def get_cc_based_edge_statistics(self, max_cycle_length: int = 5) -> pd.DataFrame: | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| """ | ||
| For each edge, report how many cycles it appears in and | ||
| the mean and max cycle closure error of those cycles. | ||
| Edges with high mean closure error across many cycles are | ||
| likely candidates for re-simulation. | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
|
|
||
| Parameters | ||
| ---------- | ||
| max_cycle_length : int, optional | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| Only consider cycles up to this length. Defaults to 5. | ||
|
|
||
| Returns | ||
| ------- | ||
| pd.DataFrame | ||
| DataFrame with columns 'ligandA', 'ligandB', 'n_cycles', | ||
| 'mean_cc (kcal/mol)', 'max_cc (kcal/mol)', sorted by | ||
| mean cycle closure error descending. | ||
| """ | ||
| from collections import defaultdict | ||
|
|
||
| cc_df = self.get_cycle_closure(max_cycle_length=max_cycle_length) | ||
| network = self.to_legacy_graph() | ||
| edge_ddg = {(a, b): d["calc_DDG"] for a, b, d in network.edges(data=True)} | ||
|
|
||
| edge_cycles: dict[tuple, list[float]] = defaultdict(list) | ||
| for _, row in cc_df.iterrows(): | ||
| cycle = list(row["cycle"]) | ||
| cc = row["cc (kcal/mol)"] | ||
| for i, lig in enumerate(cycle): | ||
| lig_a = lig | ||
| lig_b = cycle[i + 1] if i < len(cycle) - 1 else cycle[0] | ||
| edge = (lig_a, lig_b) if (lig_a, lig_b) in edge_ddg else (lig_b, lig_a) | ||
| edge_cycles[edge].append(cc) | ||
|
|
||
| rows = [] | ||
| for (a, b), ccs in edge_cycles.items(): | ||
| rows.append( | ||
| { | ||
| "ligandA": a, | ||
| "ligandB": b, | ||
| "n_cycles": len(ccs), | ||
| "mean_cc (kcal/mol)": round(sum(ccs) / len(ccs), 3), | ||
| "max_cc (kcal/mol)": round(max(ccs), 3), | ||
|
hannahbaumann marked this conversation as resolved.
Outdated
|
||
| } | ||
| ) | ||
|
|
||
| return pd.DataFrame(rows).sort_values("mean_cc (kcal/mol)", ascending=False).reset_index(drop=True) | ||
Uh oh!
There was an error while loading. Please reload this page.