Skip to content

Commit 32cafe5

Browse files
authored
Increased robustness of opconvertor.py when running on some experimental data (#456)
* Made build_nice_OPdict more robust in corner cases. - OP data for some experiments is lacking standard deviation values leading to an IndexError, added an additional length check - added a check wether the atom is actually contained in the mapping dict to avoid a KeyError, missing atoms are skipped - 'nice_OPdict' contain a lot of NaN, added a function to replace those because it almost certainly will be dumped to JSON * Formatting with Ruff * Added a rudimentary test to test_op.py to demonstrate the case. - Until we decide on test data, it cannot be run * Linting# * Trying to skip that test. * Trying to manually pass the CI env * cannot pass passenv twice * Still trying to skip it. * trying more things * testing * Skipping is working now, trying to skip in CI only * value checking for opconverter * reverted some of the changes as requested * Added a more defensive handling of the case where Atom formats are not matched by the renamer. We are now throwing a ValueException with speaking error message. This is a more defensive approach than simply risking NoneType is not subsettable errors. * Linting...
1 parent 65874d4 commit 32cafe5

2 files changed

Lines changed: 22 additions & 2 deletions

File tree

src/fairmd/lipids/auxiliary/opconvertor.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ def apply(cls, opdic: dict):
4747
def _initialize(cls):
4848
def _snX_c_renamer(row: dict) -> dict:
4949
match = re.match(r"M_G[12]C([0-9]{1,2})_M", row["C"])
50+
if not match or len(match.groups()) < 1:
51+
raise ValueError(f"Unexpected C format: {row['C']}")
5052
idx = int(match[1])
5153
row["C"] = str(idx - 1)
5254
return row
@@ -55,6 +57,8 @@ def _snX_c_renamer(row: dict) -> dict:
5557

5658
def _gbb_c_renamer(row: dict) -> dict:
5759
match = re.match(r"M_G([1-3])_M", row["C"])
60+
if not match or len(match.groups()) < 1:
61+
raise ValueError(f"Unexpected C format: {row['C']}")
5862
idx = int(match[1])
5963
row["C"] = f"g{idx}"
6064
return row
@@ -63,8 +67,11 @@ def _gbb_c_renamer(row: dict) -> dict:
6367

6468
def _h_renamer(row: dict) -> dict:
6569
match = re.match(r"M_.+H([1-4])", row["H"])
70+
if not match or len(match.groups()) < 1:
71+
raise ValueError(f"Unexpected H format: {row['H']}")
6672
idx = int(match[1])
6773
row["H"] = str(idx)
74+
6875
return row
6976

7077
cls._register("_all_", _h_renamer)
@@ -80,15 +87,27 @@ def build_nice_OPdict(src: dict, lipid: Lipid) -> dict:
8087
:return: nicely formatted OP dictionary
8188
"""
8289

83-
def _fragmentize(src, mdict):
90+
# Helper function to convert NaN to None for better
91+
# JSON compatibility in output
92+
def _rnan(x: float) -> float | None:
93+
return None if x != x else x
94+
95+
def _fragmentize(src: dict, mdict: dict) -> dict:
8496
r = {}
8597
for apair, opvals in src.items():
8698
atom_c, atom_h = apair.split(" ")
99+
if atom_c not in mdict:
100+
raise ValueError(f"Atom {atom_c} not found in mapping dictionary.")
87101
frag_c = mdict[atom_c].get("FRAGMENT", "total")
88102
if frag_c not in r:
89103
r[frag_c] = []
90104
r[frag_c].append(
91-
{"C": atom_c, "H": atom_h, "OP": opvals[0], "STD": opvals[1]},
105+
{
106+
"C": atom_c,
107+
"H": atom_h,
108+
"OP": opvals[0],
109+
"STD": _rnan(opvals[1]) if len(opvals) > 1 else None,
110+
},
92111
)
93112
r[frag_c].sort(key=lambda x: x["C"])
94113
return r

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ envlist =
99

1010
[testenv]
1111
passenv = *
12+
1213
lint_folders =
1314
"{toxinidir}/src/" \
1415
"{toxinidir}/tests/" \

0 commit comments

Comments
 (0)