From d627c399c72e9581dfbd4a7407242aeaff44673e Mon Sep 17 00:00:00 2001 From: Alexander Schlaich Date: Fri, 6 Nov 2020 10:59:55 +0100 Subject: [PATCH 01/13] Fix broken call to alchemlyb --- uncorrelate/statistical_inefficiency_dhdl.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/uncorrelate/statistical_inefficiency_dhdl.py b/uncorrelate/statistical_inefficiency_dhdl.py index 2a29dcc..12dae76 100644 --- a/uncorrelate/statistical_inefficiency_dhdl.py +++ b/uncorrelate/statistical_inefficiency_dhdl.py @@ -51,10 +51,7 @@ def uncorrelate(self, dfs, lower): uncorrelated_dfs = [] for dhdl_, l, df in zip(self.dhdls, dl, dfs): - ind = np.array(l, dtype=bool) - ind = np.array(ind, dtype=int) - dhdl_sum = dhdl_.dot(ind) - uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, dhdl_sum, lower, conservative=False)) + uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, lower=lower)) return pandas.concat(uncorrelated_dfs) From d9a472ef95cff3130f3853629eff63ffe34c6d48 Mon Sep 17 00:00:00 2001 From: Henrik Jaeger Date: Mon, 9 Nov 2020 15:40:42 +0100 Subject: [PATCH 02/13] Some spelling corrections and additions --- README.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c8651d6..80ef3e9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Flamel -The aim of the project is to develop a foundation for a new version of [alchemical-analysis](https://github.com/MobleyLab/alchemical-analysis) +The aim of this project is to develop the foundation for a new version of [alchemical-analysis](https://github.com/MobleyLab/alchemical-analysis) that uses the well tested [alchemlyb](https://github.com/alchemistry/alchemlyb) library. # Installation @@ -73,13 +73,13 @@ You should get a similar overview as [alchemical-analysis](https://github.com/Mo - Step 3: Estimate Free energy differences - Step 4: Output -Each step is performed in Plugins which can easyly be be replaced by other plugins. +Each step is performed in Plugins which can easily be replaced by other plugins. # Name -In the tradition to associate free energy estimations with alchemnistry it's named after: [Nicolas Flamel](https://en.wikipedia.org/wiki/Nicolas_Flamel) +In the tradition to associate free energy estimations with alchemy, the ancient craft of transmutating one element into another, it's named after: [Nicolas Flamel](https://en.wikipedia.org/wiki/Nicolas_Flamel). -# Sate of development: -Eventhoug alchemical-analysis is not fully covered by Flamel, it can already reproduce some results calculated using alchemical-analysis: +# State of development +Eventhough alchemical-analysis is not fully covered by Flamel, it can already reproduce some results calculated using alchemical-analysis: In fact for TI, BAR, MBAR you should get exactly the same results: @@ -111,8 +111,12 @@ Alchemical Analysis with the same input files: TOTAL: -29.154 +- 0.241 -29.067 +- 0.170 -29.074 +- 0.220 ``` -# Planed features: +# Planed features +- **pickle and txt output** +Add support for plotting the dHdls of states and the BAR/MBAR overlap matrix (preliminary feature in alchemlyb). +- **pickle and txt output** +alchemical-analysis outputs the simple result table as a text file as well as the full precision calculations as a numpy-compatible pickle file. - **Output of statistical inefficiencies** alchemical-analysis offers information about the statistical inefficiencies of the input datasets. - **Uncorrelation threshold** -In alchemical-analysis it is possible to specify a threshold for the number of samples to keep in the uncorrelation process. \ No newline at end of file +In alchemical-analysis it is possible to specify a threshold for the number of samples to keep in the uncorrelation process. From 801e057208aff312880fa469e67fd3b955e85174 Mon Sep 17 00:00:00 2001 From: Henrik Jaeger Date: Wed, 11 Nov 2020 12:41:29 +0100 Subject: [PATCH 03/13] Added several alchemical-analysis cli features. --- flamel.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/flamel.py b/flamel.py index c45fc3a..83d82ae 100755 --- a/flamel.py +++ b/flamel.py @@ -18,7 +18,7 @@ def get_available_plugin_ids(type): if type == 'uncorrelate': return ['statistical_inefficiency_dhdl', 'statistical_inefficiency_dhdl_all'] if type == 'output': - return ['simple', 'alchemical_analysis'] + return ['simple', 'alchemical_analysis', 'text', 'pickle'] if type == 'parser': return ['gmx'] @@ -90,14 +90,18 @@ def load_plugins(type, selected, *args): def main(): + parser = argparse.ArgumentParser(description=""" Collect data and estimate free energy differences """, formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('-t', '--temperature', dest='temperature', help="Temperature in K. Default: 298 K.", default=298.0, type=float) parser.add_argument('-p', '--prefix', dest='prefix', help='Prefix for datafile sets, i.e.\'dhdl\' (default).', default='dhdl') + parser.add_argument('-d', '--dir', dest = 'datafile_directory', help = 'Directory in which data files are stored. Default: Current directory.', default = '.') parser.add_argument('-q', '--suffix', dest='suffix', help='Suffix for datafile sets, i.e. \'xvg\' (default).', default='xvg') parser.add_argument('-e', dest='estimators', type=str, default=None, help="Comma separated Estimator methods") parser.add_argument('-n', '--uncorr', dest='uncorr', help='The observable to be used for the autocorrelation analysis; either \'dhdl_all\' (obtained as a sum over all energy components) or \'dhdl\' (obtained as a sum over those energy components that are changing; default) or \'dE\'. In the latter case the energy differences dE_{i,i+1} (dE_{i,i-1} for the last lambda) are used.', default='dhdl') + parser.add_argument('-j', '--resultfilename', dest = 'resultfilename', help = 'custom defined result filename prefix. Default: results', default = 'results') + parser.add_argument('-u', '--unit', dest = 'unit', help = 'Unit to report energies: \'kJ\', \'kcal\', and \'kT\'. Default: \'kJ\'', default = 'kJ') parser.add_argument('-r', '--decimal', dest='decimal', help='The number of decimal places the free energies are to be reported with. No worries, this is for the text output only; the full-precision data will be stored in \'results.pickle\'. Default: 3.', default=3, type=int) parser.add_argument('-o', '--output', dest='output', type=str, default=None, help="Output methods") parser.add_argument('-a', '--software', dest='software', help='Package\'s name the data files come from: Gromacs, Sire, Desmond, or AMBER. Default: Gromacs.', default='Gromacs') @@ -122,9 +126,9 @@ def main(): dhdls = None u_nks = None if do_dhdl: - dhdls = parser.get_dhdls() + dhdls = parser.get_dhdls(args) if do_u_nks: - u_nks = parser.get_u_nks() + u_nks = parser.get_u_nks(args) # Step 2: Uncorrelate the data if uncorrelator.needs_dhdls: From e08165995b6fa854502d01f10c608795e331732b Mon Sep 17 00:00:00 2001 From: Henrik Jaeger Date: Wed, 11 Nov 2020 12:44:33 +0100 Subject: [PATCH 04/13] Added several alchemical-analysis cli features. --- estimator/bar.py | 2 +- output/alchemical_analysis.py | 26 ++-- output/pickle.py | 130 ++++++++++++++++++++ output/simple.py | 17 ++- output/text.py | 223 ++++++++++++++++++++++++++++++++++ parser/gmx.py | 14 +-- 6 files changed, 393 insertions(+), 19 deletions(-) create mode 100644 output/pickle.py create mode 100644 output/text.py diff --git a/estimator/bar.py b/estimator/bar.py index 16d70f4..94b93f5 100644 --- a/estimator/bar.py +++ b/estimator/bar.py @@ -40,4 +40,4 @@ def get_plugin(): #c29613d34ffafa133c3dc5a90a92ce3a84cbcd0c #03649d469383a55c305c1daa55de7792c88a22d3 #2d3a3ffc3dcf66f311c5c03a8a3214c0d0158554 -#d38701718853261c7667ca50fcbe16ec501310b2 \ No newline at end of file +#d38701718853261c7667ca50fcbe16ec501310b2 diff --git a/output/alchemical_analysis.py b/output/alchemical_analysis.py index 4f445ff..be2a6fa 100644 --- a/output/alchemical_analysis.py +++ b/output/alchemical_analysis.py @@ -129,8 +129,18 @@ def output(self, estimators, args): :return: """ t = args.temperature + + if args.unit == 'kT': + conversion = 1.0 + args.unit = 'kT' + elif args.unit == 'kJ' or args.unit == 'kJ/mol': + conversion = 1.0 / (t * self.k_b) + args.unit = 'kJ/mol' + elif args.unit == 'kcal' or args.unit == 'kcal/mol': + conversion = 0.239006 / (t * self.k_b) + args.unit = 'kcal/mol' + seglen = 2 * args.decimal + 15 - beta = 1.0 / t / self.k_b out = '' segments = self.segments(estimators) @@ -143,7 +153,7 @@ def output(self, estimators, args): # Labels out += self.lenc('States', 12) for estimator in estimators: - out += self.lenr(estimator.name + ' (kJ/mol)' + ' '*args.decimal, seglen) + out += self.lenr(estimator.name + ' (' + args.unit + ')' + ' '*args.decimal, seglen) out += "\n" # Second ---- @@ -160,8 +170,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[i, i+1] / beta, args.decimal), - self.prepare_value(ddf.values[i, i+1] / beta, args.decimal) + self.prepare_value(df.values[i, i+1] / conversion, args.decimal), + self.prepare_value(ddf.values[i, i+1] / conversion, args.decimal) ), seglen) out += "\n" @@ -178,8 +188,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[segstart, segend] / beta, args.decimal), - self.prepare_value(ddf.values[segstart, segend] / beta, args.decimal) + self.prepare_value(df.values[segstart, segend] / conversion, args.decimal), + self.prepare_value(ddf.values[segstart, segend] / conversion, args.decimal) ), seglen) out += "\n" @@ -189,8 +199,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[0, -1] / beta, args.decimal), - self.prepare_value(ddf.values[0, -1] / beta, args.decimal) + self.prepare_value(df.values[0, -1] / conversion, args.decimal), + self.prepare_value(ddf.values[0, -1] / conversion, args.decimal) ), seglen) out += "\n" diff --git a/output/pickle.py b/output/pickle.py new file mode 100644 index 0000000..8752ad3 --- /dev/null +++ b/output/pickle.py @@ -0,0 +1,130 @@ +import alchemlyb.preprocessing +import pandas +import numpy as np +import pickle +import time +import os + +class Pickle: + name = 'pickle' + k_b = 8.3144621E-3 + + @classmethod + def ls(cls, estimators): + """ + Return a list of lambda values + :param estimators: Series + List of estimator plugins + :return: + The list of lambda values + """ + ls = [] + if estimators: + if estimators[0].needs_dhdls: + means = estimators[0].dhdls.mean(level=estimators[0].dhdls.index.names[1:]) + ls = np.array(means.reset_index()[means.index.names[:]]) + elif estimators[0].needs_u_nks: + means = estimators[0].u_nks.mean(level=estimators[0].u_nks.index.names[1:]) + ls = np.array(means.reset_index()[means.index.names[:]]) + + return ls + + @classmethod + def l_types(cls, estimators): + """ + Return a list of lambda types + :param estimators: Series + List of estimator plugins + :return: + The list of lambda types + """ + l_types = [] + if estimators: + if estimators[0].needs_dhdls: + l_types = estimators[0].dhdls.index.names[1:] + elif estimators[0].needs_u_nks: + l_types = estimators[0].u_nks.index.names[1:] + + return l_types + + @classmethod + def segments(cls, estimators): + """ + Collect and prepare values from different `estimators` into a series of values. + :param estimators: Series + List of estimator plugins + :return: + Segments of values to output + """ + segments = [] + l_types = cls.l_types(estimators) + ls = cls.ls(estimators) + if estimators: + segstart = 0 + ill = [0] * len(l_types) + nl = 0 + for i in range(len(ls)): + l = ls[i] + if (i < len(ls) - 1 and list(np.array(ls[i + 1], dtype=bool)).count(True) > nl) or i == len(ls) - 1: + if nl > 0: + inl = np.array(np.array(l, dtype=bool), dtype=int) + l_name = l_types[list(inl - ill).index(1)] + ill = inl + segments.append((segstart, i, l_name)) + + if i + 1 < len(ls): + nl = list(np.array(ls[i + 1], dtype=bool)).count(True) + segstart = i + return segments + + def output(self, estimators, args): + + t = args.temperature + + if args.unit == 'kT': + conversion = 1.0 + args.unit = 'kT' + elif args.unit == 'kJ' or args.unit == 'kJ/mol': + conversion = 1.0 / (t * self.k_b) + args.unit = 'kJ/mol' + elif args.unit == 'kcal' or args.unit == 'kcal/mol': + conversion = 0.239006 / (t * self.k_b) + args.unit = 'kcal/mol' + + P = args + + P.datafile_directory = os.getcwd() + P.when_analyzed = time.asctime() + P.dFs = {} + P.ddFs = {} + P.dF = {} + + segments = self.segments(estimators) + + for estimator in estimators: + + data = {} + + df = estimator.delta_f + ddf = estimator.d_delta_f + + for segstart, segend, l_name in reversed(segments): + data[l_name] = (df.values[segstart, segend], ddf.values[segstart, segend]) + + data['total'] = (df.values[0, -1], ddf.values[0, -1]) + + P.dFs[estimator.name] = df + P.ddFs[estimator.name] = ddf + + P.dF[estimator.name] = data + + pickle.dump(P,open(args.resultfilename + '.pickle','wb')) + + +def get_plugin(): + """ + Get simple output plugin + :return: + simple output plugin + """ + return Pickle() diff --git a/output/simple.py b/output/simple.py index 0bcd608..57fabdd 100644 --- a/output/simple.py +++ b/output/simple.py @@ -19,12 +19,23 @@ def output(self, estimators, args): :return: """ t = args.temperature + + if args.unit == 'kT': + conversion = 1.0 + args.unit = 'kT' + elif args.unit == 'kJ' or args.unit == 'kJ/mol': + conversion = 1.0 / (t * self.k_b) + args.unit = 'kJ/mol' + elif args.unit == 'kcal' or args.unit == 'kcal/mol': + conversion = 0.239006 / (t * self.k_b) + args.unit = 'kcal/mol' + for estimator in estimators: df = estimator.delta_f ddf = estimator.d_delta_f - beta = 1.0 / t / self.k_b - dfv = df.values[0, -1] / beta - ddfv = ddf.values[0, -1] / beta + + dfv = df.values[0, -1] * conversion + ddfv = ddf.values[0, -1] * conversion print("%s: %f +- %f" % (estimator.name, dfv, ddfv)) diff --git a/output/text.py b/output/text.py new file mode 100644 index 0000000..b59dc43 --- /dev/null +++ b/output/text.py @@ -0,0 +1,223 @@ +import numpy as np +import time +import os + +class Text: + name = 'text' + k_b = 8.3144621E-3 + + @classmethod + def lenr(cls, text, l=21): + """ + Right aligned text in a string with length `l` + :param text: str + The text to align + :param l: int + desired length + :return: str + aligned text + """ + return ' '*(l - len(text)) + text + ' ' + + @classmethod + def lenc(cls, text, l=21): + """ + Center text in a string with length `l` + :param text: str + The text to center + :param l: int + desired length + :return: str + centered text + """ + lr = int((l - len(text)) / 2) + ll = l - len(text) - lr + return ' '*ll + text + ' '*lr + ' ' + + @classmethod + def ls(cls, estimators): + """ + Return a list of lambda values + :param estimators: Series + List of estimator plugins + :return: + The list of lambda values + """ + ls = [] + if estimators: + if estimators[0].needs_dhdls: + means = estimators[0].dhdls.mean(level=estimators[0].dhdls.index.names[1:]) + ls = np.array(means.reset_index()[means.index.names[:]]) + elif estimators[0].needs_u_nks: + means = estimators[0].u_nks.mean(level=estimators[0].u_nks.index.names[1:]) + ls = np.array(means.reset_index()[means.index.names[:]]) + + return ls + + @classmethod + def l_types(cls, estimators): + """ + Return a list of lambda types + :param estimators: Series + List of estimator plugins + :return: + The list of lambda types + """ + l_types = [] + if estimators: + if estimators[0].needs_dhdls: + l_types = estimators[0].dhdls.index.names[1:] + elif estimators[0].needs_u_nks: + l_types = estimators[0].u_nks.index.names[1:] + + return l_types + + @classmethod + def segments(cls, estimators): + """ + Collect and prepare values from different `estimators` into a series of values. + :param estimators: Series + List of estimator plugins + :return: + Segments of values to output + """ + segments = [] + l_types = cls.l_types(estimators) + ls = cls.ls(estimators) + if estimators: + segstart = 0 + ill = [0] * len(l_types) + nl = 0 + for i in range(len(ls)): + l = ls[i] + if (i < len(ls) - 1 and list(np.array(ls[i + 1], dtype=bool)).count(True) > nl) or i == len(ls) - 1: + if nl > 0: + inl = np.array(np.array(l, dtype=bool), dtype=int) + l_name = l_types[list(inl - ill).index(1)] + ill = inl + segments.append((segstart, i, l_name)) + + if i + 1 < len(ls): + nl = list(np.array(ls[i + 1], dtype=bool)).count(True) + segstart = i + return segments + + @classmethod + def prepare_value(cls, value, decimal): + """ + Convert `value` to a str with `decimal` precision. + :param value: float + Value to convert + :param decimal: + Precision + :return: str + str of `value` with `decimal` precision + """ + value_str = str(round(value, decimal)) + if np.isnan(value): + return str(value) + ' '*(decimal - 1) + return value_str + '0'*(decimal - len(value_str.split('.')[1])) + + def output(self, estimators, args): + """ + Print a alchemical-analysis like output. + :param estimators: Series + Series of estimators + :param args: argparse obj + arguments from argparse + :param ls: Series + Lambdas + :return: + """ + t = args.temperature + + if args.unit == 'kT': + conversion = 1.0 + args.unit = 'kT' + elif args.unit == 'kJ' or args.unit == 'kJ/mol': + conversion = 1.0 / (t * self.k_b) + args.unit = 'kJ/mol' + elif args.unit == 'kcal' or args.unit == 'kcal/mol': + conversion = 0.239006 / (t * self.k_b) + args.unit = 'kcal/mol' + + seglen = 2 * args.decimal + 15 + out = '' + segments = self.segments(estimators) + + out += "Free Energy analysis; Text output from flamel.py invoked at: " + time.asctime() + + out += os.getcwd() + + # First ---- + out += self.lenc('-'*12, 12) + for _ in estimators: + out += self.lenc('-'*seglen) + out += "\n" + + # Labels + out += self.lenc('States', 12) + for estimator in estimators: + out += self.lenr(estimator.name + ' (' + args.unit + ')' + ' '*args.decimal, seglen) + out += "\n" + + # Second ---- + out += self.lenc('-'*12, 12) + for _ in estimators: + out += self.lenc('-'*seglen) + out += "\n" + + # Free Energy differences for each lambda state + for i, l in enumerate(self.ls(estimators)[:-1]): + out += self.lenc(str(i) + ' -- ' + str(i+1), 12) + + for estimator in estimators: + df = estimator.delta_f + ddf = estimator.d_delta_f + out += self.lenr('%s +- %s' % ( + self.prepare_value(df.values[i, i+1] * conversion, args.decimal), + self.prepare_value(ddf.values[i, i+1] * conversion, args.decimal) + ), seglen) + out += "\n" + + # Third ---- + out += self.lenc('-'*12, 12) + for _ in estimators: + out += self.lenc('-'*seglen) + out += "\n" + + for segstart, segend, l_name in reversed(segments): + # Segment Energies + out += self.lenr('%s: ' % l_name[:-7], 12) + for estimator in estimators: + df = estimator.delta_f + ddf = estimator.d_delta_f + out += self.lenr('%s +- %s' % ( + self.prepare_value(df.values[segstart, segend] * conversion, args.decimal), + self.prepare_value(ddf.values[segstart, segend] * conversion, args.decimal) + ), seglen) + out += "\n" + + # TOTAL Energies + out += self.lenr('TOTAL: ', 12) + for estimator in estimators: + df = estimator.delta_f + ddf = estimator.d_delta_f + out += self.lenr('%s +- %s' % ( + self.prepare_value(df.values[0, -1] * conversion, args.decimal), + self.prepare_value(ddf.values[0, -1] * conversion, args.decimal) + ), seglen) + out += "\n" + + txt_file = open(args.resultfilename+'.txt','w') + txt_file.write(out) + txt_file.close() + + +def get_plugin(): + """ + Get Alchemical analysis output plugin + :return: + Alchemical analysis output plugin + """ + return Text() diff --git a/parser/gmx.py b/parser/gmx.py index d1b884f..3c36a2f 100644 --- a/parser/gmx.py +++ b/parser/gmx.py @@ -25,13 +25,13 @@ def __init__(self, T, prefix, suffix): self.prefix = prefix self.suffix = suffix - def get_files(self): + def get_files(self, args): """ Get a list of files with the given prefix and suffix :return: Series The list of files """ - ls = os.listdir() + ls = os.listdir(args.datafile_directory) # Build a list of tuples with name and a number files = [] @@ -47,29 +47,29 @@ def get_files(self): # Return only the file names return [f[0] for f in sorted_files] - def get_dhdls(self): + def get_dhdls(self, args): """ Read dH/dl values from files :return: Series List of dH/dl data frames """ - files = self.get_files() + files = self.get_files(args) dhdls_ = [] for fname in files: print('Read dH/dl from %s' % fname) - dhdl_ = alchemlyb.parsing.gmx.extract_dHdl(fname, self.T) + dhdl_ = alchemlyb.parsing.gmx.extract_dHdl(args.datafile_directory + '/' + fname, self.T) dhdls_.append(dhdl_) return dhdls_ - def get_u_nks(self): + def get_u_nks(self, args): """ Read u_nk values from files :return: List of u_nk data frames """ - files = self.get_files() + files = self.get_files(args) uks_ = [] for fname in files: From d18aefa223b9764710f082933796ed3e8db51d2d Mon Sep 17 00:00:00 2001 From: Henrik Jaeger Date: Wed, 11 Nov 2020 12:51:27 +0100 Subject: [PATCH 05/13] Reflect features in README --- README.md | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 80ef3e9..4b0fd8d 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,9 @@ git clone git@github.com:alchemistry/flamel.git # Usage Currently only Gromacs parser and uncorrelation by dH/dl is supported! ``` -usage: flamel.py [-h] [-t TEMPERATURE] [-p PREFIX] [-q SUFFIX] [-e ESTIMATORS] - [-n UNCORR] [-r DECIMAL] [-o OUTPUT] [-a SOFTWARE] +usage: flamel.py [-h] [-t TEMPERATURE] [-p PREFIX] [-d DATAFILE_DIRECTORY] + [-q SUFFIX] [-e ESTIMATORS] [-n UNCORR] [-j RESULTFILENAME] + [-u UNIT] [-r DECIMAL] [-o OUTPUT] [-a SOFTWARE] [-s EQUILTIME] Collect data and estimate free energy differences @@ -31,6 +32,9 @@ optional arguments: -p PREFIX, --prefix PREFIX Prefix for datafile sets, i.e.'dhdl' (default). (default: dhdl) + -d DATAFILE_DIRECTORY, --dir DATAFILE_DIRECTORY + Directory in which data files are stored. Default: + Current directory. (default: .) -q SUFFIX, --suffix SUFFIX Suffix for datafile sets, i.e. 'xvg' (default). (default: xvg) @@ -43,6 +47,11 @@ optional arguments: default) or 'dE'. In the latter case the energy differences dE_{i,i+1} (dE_{i,i-1} for the last lambda) are used. (default: dhdl) + -j RESULTFILENAME, --resultfilename RESULTFILENAME + custom defined result filename prefix. Default: + results (default: results) + -u UNIT, --unit UNIT Unit to report energies: 'kJ', 'kcal', and 'kT'. + Default: 'kJ' (default: kJ) -r DECIMAL, --decimal DECIMAL The number of decimal places the free energies are to be reported with. No worries, this is for the text @@ -111,12 +120,12 @@ Alchemical Analysis with the same input files: TOTAL: -29.154 +- 0.241 -29.067 +- 0.170 -29.074 +- 0.220 ``` -# Planed features -- **pickle and txt output** +# Planned features +- [ ] **plotting** Add support for plotting the dHdls of states and the BAR/MBAR overlap matrix (preliminary feature in alchemlyb). -- **pickle and txt output** +- [x] **pickle and txt output** alchemical-analysis outputs the simple result table as a text file as well as the full precision calculations as a numpy-compatible pickle file. -- **Output of statistical inefficiencies** +- [ ] **Output of statistical inefficiencies** alchemical-analysis offers information about the statistical inefficiencies of the input datasets. -- **Uncorrelation threshold** +- [ ] **Uncorrelation threshold** In alchemical-analysis it is possible to specify a threshold for the number of samples to keep in the uncorrelation process. From 761cf82230d028253bd7316f77c22ed8f62f2be6 Mon Sep 17 00:00:00 2001 From: hejamu Date: Wed, 11 Nov 2020 15:25:25 +0100 Subject: [PATCH 06/13] Fixed kcal calculation error. --- output/alchemical_analysis.py | 14 +++++++------- output/simple.py | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/output/alchemical_analysis.py b/output/alchemical_analysis.py index be2a6fa..ea099b0 100644 --- a/output/alchemical_analysis.py +++ b/output/alchemical_analysis.py @@ -139,7 +139,7 @@ def output(self, estimators, args): elif args.unit == 'kcal' or args.unit == 'kcal/mol': conversion = 0.239006 / (t * self.k_b) args.unit = 'kcal/mol' - + seglen = 2 * args.decimal + 15 out = '' segments = self.segments(estimators) @@ -170,8 +170,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[i, i+1] / conversion, args.decimal), - self.prepare_value(ddf.values[i, i+1] / conversion, args.decimal) + self.prepare_value(df.values[i, i+1] * conversion, args.decimal), + self.prepare_value(ddf.values[i, i+1] * conversion, args.decimal) ), seglen) out += "\n" @@ -188,8 +188,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[segstart, segend] / conversion, args.decimal), - self.prepare_value(ddf.values[segstart, segend] / conversion, args.decimal) + self.prepare_value(df.values[segstart, segend] * conversion, args.decimal), + self.prepare_value(ddf.values[segstart, segend] * conversion, args.decimal) ), seglen) out += "\n" @@ -199,8 +199,8 @@ def output(self, estimators, args): df = estimator.delta_f ddf = estimator.d_delta_f out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[0, -1] / conversion, args.decimal), - self.prepare_value(ddf.values[0, -1] / conversion, args.decimal) + self.prepare_value(df.values[0, -1] * conversion, args.decimal), + self.prepare_value(ddf.values[0, -1] * conversion, args.decimal) ), seglen) out += "\n" diff --git a/output/simple.py b/output/simple.py index 57fabdd..d889a0d 100644 --- a/output/simple.py +++ b/output/simple.py @@ -29,11 +29,11 @@ def output(self, estimators, args): elif args.unit == 'kcal' or args.unit == 'kcal/mol': conversion = 0.239006 / (t * self.k_b) args.unit = 'kcal/mol' - + for estimator in estimators: df = estimator.delta_f ddf = estimator.d_delta_f - + dfv = df.values[0, -1] * conversion ddfv = ddf.values[0, -1] * conversion print("%s: %f +- %f" % (estimator.name, dfv, ddfv)) From fc7b500bf03675b6c5b1833bb70c0611df006af4 Mon Sep 17 00:00:00 2001 From: hejamu Date: Wed, 11 Nov 2020 15:41:30 +0100 Subject: [PATCH 07/13] Add information about the pickle file. --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 4b0fd8d..7a66551 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,28 @@ flamel.py -p lambda_ You should get a similar overview as [alchemical-analysis](https://github.com/MobleyLab/alchemical-analysis). +You also get a text file `results.txt` with the state overview, as well as a pickle file `results.pickle` with full precision values as well as complementary information about the analysis. + +Example: +``` +>>> import pandas as pd +>>> data = pd.read_pickle('results.pickle') +>>> data. +data.dF data.datafile_directory data.decimal data.estimators data.prefix data.software data.temperature data.unit +data.dFs data.ddFs data.equiltime data.output data.resultfilename data.suffix data.uncorr data.when_analyzed +>>> data.when_analyzed +'Wed Nov 11 15:22:32 2020' +>>> data.equiltime +0 +>>> data.software +'Gromacs' +>>> data.dF['TI'] +{'coul-lambda': (-15.633404527627823, 0.03466623342555742), 'vdw-lambda': (3.8237866774171514, 0.02952686840637163), 'total': (-11.809617850210671, 0.04553661930581169)} +>>> data.dF['MBAR']['coul-lambda'] +(-15.617280704605726, 0.03241377327730135) +>>> +`` + # How it works - Step 1: Read the necessary data - Step 2: Uncorrelate the data From 5c1070b22ff23b4b54623f29dbd9c6cbb1622d3a Mon Sep 17 00:00:00 2001 From: Henrik Jaeger Date: Wed, 11 Nov 2020 16:06:08 +0100 Subject: [PATCH 08/13] Fix closing backtick --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a66551..9b57d12 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,7 @@ data.dFs data.ddFs data.equiltime data. >>> data.dF['MBAR']['coul-lambda'] (-15.617280704605726, 0.03241377327730135) >>> -`` +``` # How it works - Step 1: Read the necessary data From 40578e558151b5cf099f56a9694dd637bae1eb53 Mon Sep 17 00:00:00 2001 From: hejamu Date: Mon, 16 Nov 2020 18:32:22 +0100 Subject: [PATCH 09/13] Fixed unit conversion error --- output/alchemical_analysis.py | 4 ++-- output/pickle.py | 4 ++-- output/simple.py | 4 ++-- output/text.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/output/alchemical_analysis.py b/output/alchemical_analysis.py index ea099b0..79a79fd 100644 --- a/output/alchemical_analysis.py +++ b/output/alchemical_analysis.py @@ -134,10 +134,10 @@ def output(self, estimators, args): conversion = 1.0 args.unit = 'kT' elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = 1.0 / (t * self.k_b) + conversion = 1.0 * t * self.k_b args.unit = 'kJ/mol' elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 / (t * self.k_b) + conversion = 0.239006 * t * self.k_b args.unit = 'kcal/mol' seglen = 2 * args.decimal + 15 diff --git a/output/pickle.py b/output/pickle.py index 8752ad3..0f8ae8a 100644 --- a/output/pickle.py +++ b/output/pickle.py @@ -85,10 +85,10 @@ def output(self, estimators, args): conversion = 1.0 args.unit = 'kT' elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = 1.0 / (t * self.k_b) + conversion = t * self.k_b args.unit = 'kJ/mol' elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 / (t * self.k_b) + conversion = 0.239006 * t * self.k_b args.unit = 'kcal/mol' P = args diff --git a/output/simple.py b/output/simple.py index d889a0d..5608914 100644 --- a/output/simple.py +++ b/output/simple.py @@ -24,10 +24,10 @@ def output(self, estimators, args): conversion = 1.0 args.unit = 'kT' elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = 1.0 / (t * self.k_b) + conversion = t * self.k_b args.unit = 'kJ/mol' elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 / (t * self.k_b) + conversion = 0.239006 * t * self.k_b args.unit = 'kcal/mol' for estimator in estimators: diff --git a/output/text.py b/output/text.py index b59dc43..0b490e1 100644 --- a/output/text.py +++ b/output/text.py @@ -135,10 +135,10 @@ def output(self, estimators, args): conversion = 1.0 args.unit = 'kT' elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = 1.0 / (t * self.k_b) + conversion = t * self.k_b args.unit = 'kJ/mol' elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 / (t * self.k_b) + conversion = 0.239006 * t * self.k_b args.unit = 'kcal/mol' seglen = 2 * args.decimal + 15 From 738dd8804241d92481289d9423fd3a86a75926c7 Mon Sep 17 00:00:00 2001 From: Alexander Schlaich Date: Wed, 30 Dec 2020 11:12:22 +0100 Subject: [PATCH 10/13] Add missing unit conversion in pickle output --- output/pickle.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/output/pickle.py b/output/pickle.py index 0f8ae8a..c6ca98a 100644 --- a/output/pickle.py +++ b/output/pickle.py @@ -109,12 +109,13 @@ def output(self, estimators, args): ddf = estimator.d_delta_f for segstart, segend, l_name in reversed(segments): - data[l_name] = (df.values[segstart, segend], ddf.values[segstart, segend]) + data[l_name] = (df.values[segstart, segend] * conversion, + ddf.values[segstart, segend] * conversion) - data['total'] = (df.values[0, -1], ddf.values[0, -1]) + data['total'] = (df.values[0, -1] * conversion, ddf.values[0, -1] * conversion) - P.dFs[estimator.name] = df - P.ddFs[estimator.name] = ddf + P.dFs[estimator.name] = df * conversion + P.ddFs[estimator.name] = ddf * conversion P.dF[estimator.name] = data From e3c916c10f216d69abfbaf590769bf0b3d304763 Mon Sep 17 00:00:00 2001 From: hejamu Date: Mon, 8 Mar 2021 15:27:28 +0100 Subject: [PATCH 11/13] Added support for recursive file search --- parser/gmx.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/parser/gmx.py b/parser/gmx.py index 3c36a2f..16eaf5c 100644 --- a/parser/gmx.py +++ b/parser/gmx.py @@ -32,11 +32,17 @@ def get_files(self, args): The list of files """ ls = os.listdir(args.datafile_directory) - # Build a list of tuples with name and a number files = [] for f in ls: - if f[0:len(self.prefix)] == self.prefix and f[-len(self.suffix):] == self.suffix: + if os.path.isdir(f): + ls_sub = os.listdir(args.datafile_directory + '/' + f) + for fsub in ls_sub: + if fsub[0:len(self.prefix)] == self.prefix and fsub[-len(self.suffix):] == self.suffix: + name = f + num = int(''.join([c for c in name if c.isdigit()])) + files.append((f+ '/'+ fsub, num)) + elif f[0:len(self.prefix)] == self.prefix and f[-len(self.suffix):] == self.suffix: name = f[len(self.prefix):-len(self.suffix)] num = int(''.join([c for c in name if c.isdigit()])) files.append((f, num)) From 60f4510a584449f8844cad199c2a08f9e4ac375c Mon Sep 17 00:00:00 2001 From: hejamu Date: Tue, 21 Sep 2021 18:26:20 +0200 Subject: [PATCH 12/13] Addressing comments by @orbeckst. Main changes: - make the `energy_unit` attribute propagate through the `uncorrelate` plugins. - use alchemlybs new conversion feature to facilitate easy unit conversion. - Removed `text` output plugin. This was just a file version of the `alchemical-analysis` output. Moved into the `alchemical-analysis` plugin --- flamel.py | 2 +- output/alchemical_analysis.py | 44 ++-- output/pickle.py | 37 +-- output/simple.py | 25 +- output/text.py | 223 ------------------ uncorrelate/statistical_inefficiency_de.py | 4 +- uncorrelate/statistical_inefficiency_dhdl.py | 5 +- .../statistical_inefficiency_dhdl_all.py | 4 +- 8 files changed, 44 insertions(+), 300 deletions(-) delete mode 100644 output/text.py diff --git a/flamel.py b/flamel.py index 83d82ae..f8bc66a 100755 --- a/flamel.py +++ b/flamel.py @@ -18,7 +18,7 @@ def get_available_plugin_ids(type): if type == 'uncorrelate': return ['statistical_inefficiency_dhdl', 'statistical_inefficiency_dhdl_all'] if type == 'output': - return ['simple', 'alchemical_analysis', 'text', 'pickle'] + return ['simple', 'alchemical_analysis', 'pickle'] if type == 'parser': return ['gmx'] diff --git a/output/alchemical_analysis.py b/output/alchemical_analysis.py index 79a79fd..9cef3ef 100644 --- a/output/alchemical_analysis.py +++ b/output/alchemical_analysis.py @@ -1,9 +1,8 @@ import numpy as np - +import alchemlyb.postprocessors.units as units class AlchemicalAnalysis: - name = 'alchemical-analysis' - k_b = 8.3144621E-3 + name = 'alchemical_analysis' @classmethod def lenr(cls, text, l=21): @@ -128,17 +127,6 @@ def output(self, estimators, args): Lambdas :return: """ - t = args.temperature - - if args.unit == 'kT': - conversion = 1.0 - args.unit = 'kT' - elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = 1.0 * t * self.k_b - args.unit = 'kJ/mol' - elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 * t * self.k_b - args.unit = 'kcal/mol' seglen = 2 * args.decimal + 15 out = '' @@ -167,11 +155,11 @@ def output(self, estimators, args): out += self.lenc(str(i) + ' -- ' + str(i+1), 12) for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f + df = units.get_unit_converter(args.unit)(estimator.delta_f) + ddf = units.get_unit_converter(args.unit)(estimator.d_delta_f) out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[i, i+1] * conversion, args.decimal), - self.prepare_value(ddf.values[i, i+1] * conversion, args.decimal) + self.prepare_value(df.values[i, i+1], args.decimal), + self.prepare_value(ddf.values[i, i+1], args.decimal) ), seglen) out += "\n" @@ -185,26 +173,28 @@ def output(self, estimators, args): # Segment Energies out += self.lenr('%s: ' % l_name[:-7], 12) for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f + df = units.get_unit_converter(args.unit)(estimator.delta_f) + ddf = units.get_unit_converter(args.unit)(estimator.d_delta_f) out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[segstart, segend] * conversion, args.decimal), - self.prepare_value(ddf.values[segstart, segend] * conversion, args.decimal) + self.prepare_value(df.values[segstart, segend], args.decimal), + self.prepare_value(ddf.values[segstart, segend], args.decimal) ), seglen) out += "\n" # TOTAL Energies out += self.lenr('TOTAL: ', 12) for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f + df = units.get_unit_converter(args.unit)(estimator.delta_f) + ddf = units.get_unit_converter(args.unit)(estimator.d_delta_f) out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[0, -1] * conversion, args.decimal), - self.prepare_value(ddf.values[0, -1] * conversion, args.decimal) + self.prepare_value(df.values[0, -1], args.decimal), + self.prepare_value(ddf.values[0, -1], args.decimal) ), seglen) out += "\n" - print(out) + txt_file = open(args.resultfilename + '.txt', 'w') + txt_file.write(out) + txt_file.close() def get_plugin(): diff --git a/output/pickle.py b/output/pickle.py index c6ca98a..0c307ed 100644 --- a/output/pickle.py +++ b/output/pickle.py @@ -1,13 +1,12 @@ -import alchemlyb.preprocessing -import pandas import numpy as np import pickle import time import os +import alchemlyb.postprocessors.units as units + class Pickle: name = 'pickle' - k_b = 8.3144621E-3 @classmethod def ls(cls, estimators): @@ -78,19 +77,7 @@ def segments(cls, estimators): return segments def output(self, estimators, args): - - t = args.temperature - - if args.unit == 'kT': - conversion = 1.0 - args.unit = 'kT' - elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = t * self.k_b - args.unit = 'kJ/mol' - elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 * t * self.k_b - args.unit = 'kcal/mol' - + P = args P.datafile_directory = os.getcwd() @@ -104,22 +91,22 @@ def output(self, estimators, args): for estimator in estimators: data = {} - - df = estimator.delta_f - ddf = estimator.d_delta_f + + df = units.get_unit_converter(args.unit)(estimator.delta_f) + ddf = units.get_unit_converter(args.unit)(estimator.d_delta_f) for segstart, segend, l_name in reversed(segments): - data[l_name] = (df.values[segstart, segend] * conversion, - ddf.values[segstart, segend] * conversion) + data[l_name] = (df.values[segstart, segend], + ddf.values[segstart, segend]) - data['total'] = (df.values[0, -1] * conversion, ddf.values[0, -1] * conversion) + data['total'] = (df.values[0, -1], ddf.values[0, -1]) - P.dFs[estimator.name] = df * conversion - P.ddFs[estimator.name] = ddf * conversion + P.dFs[estimator.name] = df + P.ddFs[estimator.name] = ddf P.dF[estimator.name] = data - pickle.dump(P,open(args.resultfilename + '.pickle','wb')) + pickle.dump(P, open(args.resultfilename + '.pickle', 'wb')) def get_plugin(): diff --git a/output/simple.py b/output/simple.py index 5608914..1fd973e 100644 --- a/output/simple.py +++ b/output/simple.py @@ -1,11 +1,7 @@ -import alchemlyb.preprocessing -import pandas -import numpy as np - +import alchemlyb.postprocessors.units as units class Simple: name = 'simple' - k_b = 8.3144621E-3 def output(self, estimators, args): """ @@ -18,24 +14,13 @@ def output(self, estimators, args): Lambdas :return: """ - t = args.temperature - - if args.unit == 'kT': - conversion = 1.0 - args.unit = 'kT' - elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = t * self.k_b - args.unit = 'kJ/mol' - elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 * t * self.k_b - args.unit = 'kcal/mol' for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f + df = units.get_unit_converter(args.unit)(estimator.delta_f) + ddf = units.get_unit_converter(args.unit)(estimator.d_delta_f) - dfv = df.values[0, -1] * conversion - ddfv = ddf.values[0, -1] * conversion + dfv = df.values[0, -1] + ddfv = ddf.values[0, -1] print("%s: %f +- %f" % (estimator.name, dfv, ddfv)) diff --git a/output/text.py b/output/text.py deleted file mode 100644 index 0b490e1..0000000 --- a/output/text.py +++ /dev/null @@ -1,223 +0,0 @@ -import numpy as np -import time -import os - -class Text: - name = 'text' - k_b = 8.3144621E-3 - - @classmethod - def lenr(cls, text, l=21): - """ - Right aligned text in a string with length `l` - :param text: str - The text to align - :param l: int - desired length - :return: str - aligned text - """ - return ' '*(l - len(text)) + text + ' ' - - @classmethod - def lenc(cls, text, l=21): - """ - Center text in a string with length `l` - :param text: str - The text to center - :param l: int - desired length - :return: str - centered text - """ - lr = int((l - len(text)) / 2) - ll = l - len(text) - lr - return ' '*ll + text + ' '*lr + ' ' - - @classmethod - def ls(cls, estimators): - """ - Return a list of lambda values - :param estimators: Series - List of estimator plugins - :return: - The list of lambda values - """ - ls = [] - if estimators: - if estimators[0].needs_dhdls: - means = estimators[0].dhdls.mean(level=estimators[0].dhdls.index.names[1:]) - ls = np.array(means.reset_index()[means.index.names[:]]) - elif estimators[0].needs_u_nks: - means = estimators[0].u_nks.mean(level=estimators[0].u_nks.index.names[1:]) - ls = np.array(means.reset_index()[means.index.names[:]]) - - return ls - - @classmethod - def l_types(cls, estimators): - """ - Return a list of lambda types - :param estimators: Series - List of estimator plugins - :return: - The list of lambda types - """ - l_types = [] - if estimators: - if estimators[0].needs_dhdls: - l_types = estimators[0].dhdls.index.names[1:] - elif estimators[0].needs_u_nks: - l_types = estimators[0].u_nks.index.names[1:] - - return l_types - - @classmethod - def segments(cls, estimators): - """ - Collect and prepare values from different `estimators` into a series of values. - :param estimators: Series - List of estimator plugins - :return: - Segments of values to output - """ - segments = [] - l_types = cls.l_types(estimators) - ls = cls.ls(estimators) - if estimators: - segstart = 0 - ill = [0] * len(l_types) - nl = 0 - for i in range(len(ls)): - l = ls[i] - if (i < len(ls) - 1 and list(np.array(ls[i + 1], dtype=bool)).count(True) > nl) or i == len(ls) - 1: - if nl > 0: - inl = np.array(np.array(l, dtype=bool), dtype=int) - l_name = l_types[list(inl - ill).index(1)] - ill = inl - segments.append((segstart, i, l_name)) - - if i + 1 < len(ls): - nl = list(np.array(ls[i + 1], dtype=bool)).count(True) - segstart = i - return segments - - @classmethod - def prepare_value(cls, value, decimal): - """ - Convert `value` to a str with `decimal` precision. - :param value: float - Value to convert - :param decimal: - Precision - :return: str - str of `value` with `decimal` precision - """ - value_str = str(round(value, decimal)) - if np.isnan(value): - return str(value) + ' '*(decimal - 1) - return value_str + '0'*(decimal - len(value_str.split('.')[1])) - - def output(self, estimators, args): - """ - Print a alchemical-analysis like output. - :param estimators: Series - Series of estimators - :param args: argparse obj - arguments from argparse - :param ls: Series - Lambdas - :return: - """ - t = args.temperature - - if args.unit == 'kT': - conversion = 1.0 - args.unit = 'kT' - elif args.unit == 'kJ' or args.unit == 'kJ/mol': - conversion = t * self.k_b - args.unit = 'kJ/mol' - elif args.unit == 'kcal' or args.unit == 'kcal/mol': - conversion = 0.239006 * t * self.k_b - args.unit = 'kcal/mol' - - seglen = 2 * args.decimal + 15 - out = '' - segments = self.segments(estimators) - - out += "Free Energy analysis; Text output from flamel.py invoked at: " + time.asctime() - - out += os.getcwd() - - # First ---- - out += self.lenc('-'*12, 12) - for _ in estimators: - out += self.lenc('-'*seglen) - out += "\n" - - # Labels - out += self.lenc('States', 12) - for estimator in estimators: - out += self.lenr(estimator.name + ' (' + args.unit + ')' + ' '*args.decimal, seglen) - out += "\n" - - # Second ---- - out += self.lenc('-'*12, 12) - for _ in estimators: - out += self.lenc('-'*seglen) - out += "\n" - - # Free Energy differences for each lambda state - for i, l in enumerate(self.ls(estimators)[:-1]): - out += self.lenc(str(i) + ' -- ' + str(i+1), 12) - - for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f - out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[i, i+1] * conversion, args.decimal), - self.prepare_value(ddf.values[i, i+1] * conversion, args.decimal) - ), seglen) - out += "\n" - - # Third ---- - out += self.lenc('-'*12, 12) - for _ in estimators: - out += self.lenc('-'*seglen) - out += "\n" - - for segstart, segend, l_name in reversed(segments): - # Segment Energies - out += self.lenr('%s: ' % l_name[:-7], 12) - for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f - out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[segstart, segend] * conversion, args.decimal), - self.prepare_value(ddf.values[segstart, segend] * conversion, args.decimal) - ), seglen) - out += "\n" - - # TOTAL Energies - out += self.lenr('TOTAL: ', 12) - for estimator in estimators: - df = estimator.delta_f - ddf = estimator.d_delta_f - out += self.lenr('%s +- %s' % ( - self.prepare_value(df.values[0, -1] * conversion, args.decimal), - self.prepare_value(ddf.values[0, -1] * conversion, args.decimal) - ), seglen) - out += "\n" - - txt_file = open(args.resultfilename+'.txt','w') - txt_file.write(out) - txt_file.close() - - -def get_plugin(): - """ - Get Alchemical analysis output plugin - :return: - Alchemical analysis output plugin - """ - return Text() diff --git a/uncorrelate/statistical_inefficiency_de.py b/uncorrelate/statistical_inefficiency_de.py index b882403..4c90d5c 100644 --- a/uncorrelate/statistical_inefficiency_de.py +++ b/uncorrelate/statistical_inefficiency_de.py @@ -33,7 +33,9 @@ def uncorrelate(self, dfs, lower): statinefs.append(statinef) i += 1 - return pandas.concat(uncorrelated_dfs) + uncorrelated_df = pandas.concat(uncorrelated_dfs) + uncorrelated_df.attrs = uncorrelated_dfs[0].attrs + return uncorrelated_df def get_plugin(*args): diff --git a/uncorrelate/statistical_inefficiency_dhdl.py b/uncorrelate/statistical_inefficiency_dhdl.py index 12dae76..ce47f70 100644 --- a/uncorrelate/statistical_inefficiency_dhdl.py +++ b/uncorrelate/statistical_inefficiency_dhdl.py @@ -52,8 +52,9 @@ def uncorrelate(self, dfs, lower): uncorrelated_dfs = [] for dhdl_, l, df in zip(self.dhdls, dl, dfs): uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, lower=lower)) - - return pandas.concat(uncorrelated_dfs) + uncorrelated_df = pandas.concat(uncorrelated_dfs) + uncorrelated_df.attrs = uncorrelated_dfs[0].attrs + return uncorrelated_df def get_plugin(*args): diff --git a/uncorrelate/statistical_inefficiency_dhdl_all.py b/uncorrelate/statistical_inefficiency_dhdl_all.py index fff38a5..21d6d24 100644 --- a/uncorrelate/statistical_inefficiency_dhdl_all.py +++ b/uncorrelate/statistical_inefficiency_dhdl_all.py @@ -33,7 +33,9 @@ def uncorrelate(self, dfs, lower): dhdl_sum = dhdl_.sum(axis=1) uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, dhdl_sum, lower, conservative=False)) - return pandas.concat(uncorrelated_dfs) + uncorrelated_df = pandas.concat(uncorrelated_dfs) + uncorrelated_df.attrs = uncorrelated_dfs[0].attrs + return uncorrelated_df def get_plugin(*args): From 33ad6c513290e70eaa9f7ec39d39d442f6fd4ca2 Mon Sep 17 00:00:00 2001 From: hejamu Date: Wed, 22 Sep 2021 15:36:38 +0200 Subject: [PATCH 13/13] reverts changes in d627c399c72e9581dfbd4a7407242aeaff44673e --- uncorrelate/statistical_inefficiency_dhdl.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/uncorrelate/statistical_inefficiency_dhdl.py b/uncorrelate/statistical_inefficiency_dhdl.py index ce47f70..dc6397a 100644 --- a/uncorrelate/statistical_inefficiency_dhdl.py +++ b/uncorrelate/statistical_inefficiency_dhdl.py @@ -51,7 +51,10 @@ def uncorrelate(self, dfs, lower): uncorrelated_dfs = [] for dhdl_, l, df in zip(self.dhdls, dl, dfs): - uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, lower=lower)) + ind = np.array(l, dtype=bool) + ind = np.array(ind, dtype=int) + dhdl_sum = dhdl_.dot(ind) + uncorrelated_dfs.append(alchemlyb.preprocessing.statistical_inefficiency(df, dhdl_sum, lower, conservative=False)) uncorrelated_df = pandas.concat(uncorrelated_dfs) uncorrelated_df.attrs = uncorrelated_dfs[0].attrs return uncorrelated_df