Skip to content

Commit fca590a

Browse files
authored
Add atomic point quality dataset generator (#495)
* add atomic point quality dataset generator * fix: a bug with experiment addition * fix: typo in comment and no-op np.mean * fix: replace strange list comprehension, remove ABC * fix: inherit constant for 0.02 exp error; minor fixes of naming and docstrings * move filename to class attribute
1 parent e6d5bdd commit fca590a

1 file changed

Lines changed: 180 additions & 0 deletions

File tree

developer/gen-atomicq-dataset.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
#!/usr/bin/env python3
2+
3+
import numpy as np
4+
import pandas as pd
5+
6+
from fairmd.lipids.api import get_OP
7+
from fairmd.lipids.core import System, initialize_databank
8+
from fairmd.lipids.experiment import ExperimentCollection, OPExperiment
9+
10+
11+
class OPQDataError(Exception):
12+
"""Our specific exception"""
13+
14+
15+
class OPQDataStorer():
16+
17+
18+
DEFAULT_OPQ_HDFNAME = "atomic-opq-dataset.h5"
19+
20+
def __init__(self, s: System, lname: str) -> None:
21+
self._s = s
22+
self._lname = lname
23+
self._exp_opdicts: list[pd.DataFrame] = []
24+
25+
def _cvt_op_df(self, opdict: dict, err_pos: int) -> pd.DataFrame:
26+
op_clean = pd.DataFrame(
27+
columns=["c", "h", "val", "err"],
28+
)
29+
smi2uname = self._get_smi2uname()
30+
for smid, uname in smi2uname.items():
31+
_c_dict = {
32+
k: [v[0], OPExperiment.DEFAULT_ERROR if err_pos >= len(v) else v[err_pos]] # check
33+
for k, v in opdict.items()
34+
if k.split()[0] == uname
35+
}
36+
_c_dict_len = len(_c_dict)
37+
if _c_dict_len == 0:
38+
continue
39+
vearr = np.array(list(_c_dict.values()))
40+
if _c_dict_len == 1: # one H
41+
op_clean.loc[len(op_clean)] = [smid, 1, vearr[0, 0], vearr[0, 1]]
42+
elif _c_dict_len == 3: # three H. They are always symmetric.
43+
op_clean.loc[len(op_clean)] = [smid, 1, np.mean(vearr[:, 0]), np.mean(vearr[:, 1])]
44+
elif _c_dict_len == 2: # two H. They could be asymmetric.
45+
vearr = vearr[np.argsort(np.abs(vearr[:, 0]))]
46+
op_clean.loc[len(op_clean)] = [smid, 1, vearr[0, 0], vearr[0, 1]]
47+
op_clean.loc[len(op_clean)] = [smid, 2, vearr[1, 0], vearr[1, 1]]
48+
else:
49+
msg = (
50+
f"Unexpected number of H for {uname} in "
51+
f"instance {self.ass_id} // {self._lname}: {_c_dict_len}."
52+
" Cannot store."
53+
)
54+
raise OPQDataError(msg)
55+
return op_clean.astype({"c": np.int64, "h": np.int64, "val": np.float64, "err": np.float64})
56+
57+
def prepare_sim_dataframe(self) -> None:
58+
"""Prepare dataframe for storing."""
59+
opdict = get_OP(self._s)[self._lname]
60+
self._sim_op = self._cvt_op_df(opdict, 2)
61+
62+
@property
63+
def ass_id(self) -> str:
64+
"""Get id of assoc object"""
65+
return self._s["ID"]
66+
67+
def _get_smi2uname(self) -> dict:
68+
mol = self._s.lipids[self._lname]
69+
s2u = {}
70+
for uname, aprops in mol.mapping_dict.items():
71+
if "SMILEIDX" in aprops:
72+
smid = int(aprops["SMILEIDX"])
73+
s2u[smid] = uname
74+
if not s2u:
75+
# NO SMILEIDX. Cannot store.
76+
msg = f"Instance {self.ass_id} // {self._lname} cannot be stored: we don't have SMILEIDX."
77+
raise OPQDataError(msg)
78+
return dict(sorted(s2u.items()))
79+
80+
def add_experiment_data(self, exp_opdict: dict) -> None:
81+
"""Add experimental OP data to the storer. We will use it for Q estimation."""
82+
self._exp_opdicts.append(self._cvt_op_df(exp_opdict, 1))
83+
84+
def average_experiment_data(self) -> None:
85+
"""Average experimental OP data if we have more than one."""
86+
concdf = pd.concat(self._exp_opdicts, ignore_index=True)
87+
self._exp_opdict_one: pd.DataFrame = concdf.groupby(["c", "h"]).mean().reset_index()
88+
89+
def compute_q_points(self) -> None:
90+
"""Compute mean(exp) datapoints and inherit sign from simulation OP value"""
91+
df1 = self._sim_op.merge(
92+
self._exp_opdict_one,
93+
on=["c", "h"],
94+
how="inner",
95+
suffixes=("_s", "_e"),
96+
)
97+
df1["val_e"] = df1["val_e"].abs() * np.sign(df1["val_s"])
98+
self._qpoints = df1
99+
100+
def store_to_hdf5(self, hdf_fname: str) -> None:
101+
"""Store the record"""
102+
_mcontent = self._s["COMPOSITION"]
103+
mcontent = {"name": [], "inchikey": [], "number": [], "asymmetry": []}
104+
for lname, lip in self._s.lipids.items():
105+
ik = lip.metadata["bioschema_properties"]["inChIKey"]
106+
cnt = _mcontent[lname]["COUNT"]
107+
if isinstance(cnt, int):
108+
asm = np.nan
109+
cnt = [cnt / 2, cnt / 2]
110+
else:
111+
asm = cnt[0] / sum(cnt)
112+
mcontent["name"] += [lname]
113+
mcontent["inchikey"] += [ik]
114+
mcontent["number"] += [sum(cnt) / 2]
115+
mcontent["asymmetry"] += [asm]
116+
hydration = self._s.get_hydration()
117+
scontent = self._s.solution_composition(basis="molar")
118+
temperature = self._s["TEMPERATURE"]
119+
inchikey = self._s.lipids[self._lname].metadata["bioschema_properties"]["inChIKey"]
120+
smiles = self._s.lipids[self._lname].metadata["bioschema_properties"]["smiles"]
121+
ff_name = self._s.readme.get("FF", False)
122+
# store all vars and df to the HDF5 table
123+
group = f"SIM_{self._s['ID']}__{self._lname}"
124+
with pd.HDFStore(hdf_fname, "a") as store:
125+
# DataFrame table
126+
store.put(f"{group}/op_values", self._qpoints, format="table", data_columns=True)
127+
store.put(f"{group}/simulation_table", pd.DataFrame(mcontent), format="table", data_columns=True)
128+
# Metadata attributes - I
129+
op_storer = store.get_storer(f"{group}/op_values")
130+
upd_attr = {
131+
"inchikey": inchikey,
132+
"smiles": smiles,
133+
"fmdl_simid": self._s["ID"],
134+
}
135+
for k, v in upd_attr.items():
136+
op_storer.attrs[k] = v
137+
# -//- II
138+
sample_storer = store.get_storer(f"{group}/simulation_table")
139+
upd_attr = {
140+
"temperature": temperature,
141+
"hydration": hydration,
142+
"solution": ", ".join([f"{k:<25} {v * 100:>6.1f}%" for k, v in sorted(scontent.items())]),
143+
}
144+
if ff_name:
145+
upd_attr["ff_name"] = ff_name
146+
for k, v in upd_attr.items():
147+
sample_storer.attrs[k] = v
148+
149+
150+
151+
152+
def load_sims() -> None:
153+
print("Generating OP dataset from simulations.")
154+
exps = ExperimentCollection.load_from_data("OPExperiment")
155+
sims = initialize_databank()
156+
for sim in sims:
157+
paired_opedict = sim["EXPERIMENT"].get("ORDERPARAMETER", {})
158+
for lname in sim.lipids:
159+
if len(paired_opedict.get(lname, [])) == 0:
160+
continue
161+
print(sim)
162+
163+
ods = OPQDataStorer(sim, lname)
164+
try:
165+
ods.prepare_sim_dataframe()
166+
except OPQDataError as e:
167+
print("ERROR: ", e)
168+
continue
169+
170+
for expid in paired_opedict[lname]:
171+
_exp = exps.loc(expid)
172+
ods.add_experiment_data(_exp.data[lname])
173+
ods.average_experiment_data()
174+
175+
ods.compute_q_points()
176+
ods.store_to_hdf5(OPQDataStorer.DEFAULT_OPQ_HDFNAME)
177+
178+
179+
if __name__ == "__main__":
180+
load_sims()

0 commit comments

Comments
 (0)