From 3405f32110cf8f1688c6cfc45d0e6183019ff288 Mon Sep 17 00:00:00 2001 From: bussilabbot Date: Tue, 26 Nov 2024 16:21:23 +0000 Subject: [PATCH] Update to bussilab/MDRefine@9afcc05 --- .nojekyll | 0 MDRefine.pdf | Bin 0 -> 93384 bytes MDRefine/MDRefinement.html | 132 + MDRefine/data_loading.html | 602 + MDRefine/hyperminimizer.html | 265 + MDRefine/index.html | 104 + MDRefine/loss_and_minimizer.html | 660 + README.md | 13 + examples/Tutorial.html | 20296 +++++++++++++++++++++++++++++ examples/Tutorial_2.html | 9984 ++++++++++++++ examples/Tutorial_3.html | 13437 +++++++++++++++++++ examples/index.html | 42 + examples/load_data.html | 9596 ++++++++++++++ index.html | 10 + 14 files changed, 55141 insertions(+) create mode 100644 .nojekyll create mode 100644 MDRefine.pdf create mode 100644 MDRefine/MDRefinement.html create mode 100644 MDRefine/data_loading.html create mode 100644 MDRefine/hyperminimizer.html create mode 100644 MDRefine/index.html create mode 100644 MDRefine/loss_and_minimizer.html create mode 100644 README.md create mode 100644 examples/Tutorial.html create mode 100644 examples/Tutorial_2.html create mode 100644 examples/Tutorial_3.html create mode 100644 examples/index.html create mode 100644 examples/load_data.html create mode 100644 index.html diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/MDRefine.pdf b/MDRefine.pdf new file mode 100644 index 0000000000000000000000000000000000000000..1241fcaf87b3f39bcd192bd2918317c7aeb53956 GIT binary patch literal 93384 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 + + + + + +MDRefine.MDRefinement API documentation + + + + + + + + + + + + + + + diff --git a/MDRefine/data_loading.html b/MDRefine/data_loading.html new file mode 100644 index 0000000..9e5b3d1 --- /dev/null +++ b/MDRefine/data_loading.html @@ -0,0 +1,602 @@ + + + + + + +MDRefine.data_loading API documentation + + + + + + + + + + + +
+
+
+

Module MDRefine.data_loading

+
+
+

Tools n. 1: data_loading. +It loads data into the data object.

+
+
+
+
+
+
+

Functions

+
+
+def check_and_skip(data, *, stride=1) +
+
+

This function is an internal tool used in load_data() to modify input data:

+
    +
  • +

    weights are normalized;

    +
  • +
  • +

    it appends observables computed through forward models (if any) to data.mol[name_sys].g;

    +
  • +
  • +

    if hasattr(data.mol[name_sys], 'selected_obs'): it removes non-selected observables from data.mol[name_sys].forward_qs;

    +
  • +
  • +

    select frames with given stride;

    +
  • +
  • +

    count n. experiments and n. frames (data.mol[name_sys].n_frames and data.mol[name_sys].n_experiments) +and check corresponding matching.

    +
  • +
+
+
+def load_data(infos, *, stride=1) +
+
+

This tool loads data from specified directory as indicated by the user in infos +to a dictionary data of classes, which includes data.properties (global properties) and data[system_name]; +for alchemical calculations, there is also data[cycle_name].

+
+
+
+
+

Classes

+
+
+class data_class +(info, path_directory, name_sys) +
+
+

Data object of a molecular system.

+

Parameters

+
+
info : dict
+
Dictionary for the information about the data of name_sys molecular system in path_directory.
+
path_directory : str
+
String for the path of the directory with data of the molecular system name_sys.
+
name_sys : str
+
Name of the molecular system taken into account.
+
+
+

Returns

+
+
temperature : float
+
Value for the temperature at which the trajectory is simulated.
+
gexp : dict
+
Dictionary of Numpy 2-dimensional arrays (N x 2); gexp[j,0] is the experimental value of the j-th observable, gexp[j,1] is the corresponding uncertainty; +the size N depends on the type of observable.
+
names : dict
+
Dictionary of Numpy 1-dimensional arrays of length N with the names of the observables of each type.
+
ref : dict
+
Dictionary of strings with signs `'=', '>', '<', '><' used to define the chi2 to compute, +depending on the observable type.
+
g : dict
+
Dictionary of Numpy 2-dimensional arrays (M x N), where g[name][i,j] is the j-th observable of that type computed in the i-th frame.
+
forward_qs : dict
+
Dictionary of Numpy 2-dimensional arrays (M x N) with the quantities required for the forward model.
+
forward_model : function
+
Function for the forward model, whose input variables are the forward-model coefficients fm_coeffs and the forward_qs dictionary; +a third optional argument is the selected_obs (dictionary with indices of selected observables).
+
weights : array_like
+
Numpy 1-dimensional array of length M with the weights (not required to be normalized).
+
f : array_like
+
Numpy 2-dimensional array (M x P) of terms required to compute the force-field correction, +where P is the n. of parameters pars and M is the n. of frames.
+
ff_correction : function
+
Function for the force-field correction, whose input variables are the force-field correction parameters pars and the f array (sorted consistently with each other).
+
+
+ +Expand source code + +
class data_class:
+    """
+    Data object of a molecular system.
+
+    Parameters
+    ----------
+    info: dict
+        Dictionary for the information about the data of `name_sys` molecular system in `path_directory`. 
+
+    path_directory: str
+        String for the path of the directory with data of the molecular system `name_sys`.
+
+    name_sys: str
+        Name of the molecular system taken into account.
+    
+    --------
+
+    Returns
+    --------
+    temperature : float
+        Value for the temperature at which the trajectory is simulated.
+    
+    gexp : dict
+        Dictionary of Numpy 2-dimensional arrays (N x 2); `gexp[j,0]` is the experimental value of the j-th observable, `gexp[j,1]` is the corresponding uncertainty;
+        the size N depends on the type of observable.
+    
+    names : dict
+        Dictionary of Numpy 1-dimensional arrays of length N with the names of the observables of each type.
+    
+    ref : dict
+        Dictionary of strings with signs `'=', '>', '<', '><' used to define the chi2 to compute,
+        depending on the observable type.
+    
+    g : dict
+        Dictionary of Numpy 2-dimensional arrays (M x N), where `g[name][i,j]` is the j-th observable of that type computed in the i-th frame.
+    
+    forward_qs : dict
+        Dictionary of Numpy 2-dimensional arrays (M x N) with the quantities required for the forward model.
+    
+    forward_model: function
+        Function for the forward model, whose input variables are the forward-model coefficients `fm_coeffs` and the `forward_qs` dictionary;
+        a third optional argument is the `selected_obs` (dictionary with indices of selected observables).
+    
+    weights: array_like
+        Numpy 1-dimensional array of length M with the weights (not required to be normalized).
+    
+    f: array_like
+        Numpy 2-dimensional array (M x P) of terms required to compute the force-field correction,
+        where P is the n. of parameters `pars` and M is the n. of frames.
+    
+    ff_correction: function
+        Function for the force-field correction, whose input variables are the force-field correction parameters `pars` and the `f` array (sorted consistently with each other).
+    """
+    def __init__(self, info, path_directory, name_sys):
+
+        # 0. temperature
+
+        if 'temperature' in info.keys():
+            self.temperature = info['temperature']
+            """`float` value for the temperature"""
+        else:
+            self.temperature = 1.0
+
+        # 1. gexp (experimental values) and names of the observables
+
+        if 'g_exp' in info.keys():
+
+            self.gexp = {}
+            """dictionary of `numpy.ndarray` containing gexp values and uncertainties"""
+            self.names = {}
+            """dictionary of `numpy.ndarray` containing names of experimental observables"""
+            self.ref = {}  # if data.gexp are boundary or puntual values
+            """dictionary of `numpy.ndarray` containing references"""
+
+            if info['g_exp'] is None:
+                if info['DDGs']['if_DDGs'] is False:
+                    print('error, some experimental data is missing')
+            else:
+                if info['g_exp'] == []:
+                    info['g_exp'] = [f[:-4] for f in os.listdir(path_directory+'%s/g_exp' % name_sys)]
+
+                for name in info['g_exp']:
+                    if type(name) is tuple:
+                        if len(name) == 5:
+                            for i in range(2):
+                                if name[2*i+2] == '>':
+                                    s = ' LOWER'
+                                elif name[2*i+2] == '<':
+                                    s = ' UPPER'
+                                else:
+                                    print('error in the sign of gexp')
+                                    return
+
+                                if os.path.isfile(path_directory+'%s/g_exp/%s%s.npy' % (name_sys, name[0], name[2*i+1])):
+                                    self.gexp[name[0]+s] = np.load(
+                                        path_directory+'%s/g_exp/%s%s.npy' % (name_sys, name[0], name[2*i+1]))
+                                elif os.path.isfile(path_directory+'%s/g_exp/%s%s' % (name_sys, name[0], name[2*i+1])):
+                                    self.gexp[name[0]+s] = numpy.loadtxt(
+                                        path_directory+'%s/g_exp/%s%s' % (name_sys, name[0], name[2*i+1]))
+
+                            self.ref[name[0]] = '><'
+
+                        elif name[1] == '=' or name[1] == '>' or name[1] == '<':
+                            if os.path.isfile(path_directory+'%s/g_exp/%s.npy' % (name_sys, name[0])):
+                                self.gexp[name[0]] = np.load(path_directory+'%s/g_exp/%s.npy' % (name_sys, name[0]))
+                            elif os.path.isfile(path_directory+'%s/g_exp/%s' % (name_sys, name[0])):
+                                self.gexp[name[0]] = numpy.loadtxt(path_directory+'%s/g_exp/%s' % (name_sys, name[0]))
+                            self.ref[name[0]] = name[1]
+
+                        else:
+                            print('error on specified sign of gexp')
+                            return
+
+                    else:
+                        if os.path.isfile(path_directory+'%s/g_exp/%s.npy' % (name_sys, name)):
+                            self.gexp[name] = np.load(path_directory+'%s/g_exp/%s.npy' % (name_sys, name))
+                        elif os.path.isfile(path_directory+'%s/g_exp/%s' % (name_sys, name)):
+                            self.gexp[name] = numpy.loadtxt(path_directory+'%s/g_exp/%s' % (name_sys, name))
+                        self.ref[name] = '='
+
+                    if type(name) is tuple:
+                        name = name[0]
+                    if os.path.isfile(path_directory+'%s/names/%s.npy' % (name_sys, name)):
+                        self.names[name] = np.load(path_directory+'%s/names/%s.npy' % (name_sys, name))
+                    elif os.path.isfile(path_directory+'%s/names/%s' % (name_sys, name)):
+                        self.names[name] = numpy.loadtxt(path_directory+'%s/names/%s' % (name_sys, name))
+
+        # 2. g (observables)
+
+        if 'obs' in info.keys():
+
+            self.g = {}
+
+            if info['obs'] is not None:
+                if info['obs'] == []:
+                    info['obs'] = [f[:-4] for f in os.listdir(path_directory+'%s/observables' % name_sys)]
+                for name in info['obs']:
+                    if os.path.isfile(path_directory+'%s/observables/%s.npy' % (name_sys, name)):
+                        self.g[name] = np.load(path_directory+'%s/observables/%s.npy' % (name_sys, name), mmap_mode='r')
+                    elif os.path.isfile(path_directory+'%s/observables/%s' % (name_sys, name)):
+                        self.g[name] = numpy.loadtxt(path_directory+'%s/observables/%s' % (name_sys, name))
+
+        # 3. forward_qs (quantities for the forward model) and forward_model
+
+        if 'forward_qs' in info.keys():
+
+            # in this way, you can define forward model either with or without selected_obs (c)
+            def my_forward_model(a, b, c=None):
+                try:
+                    out = info['forward_model'](a, b, c)
+                    for s in c.keys():
+                        if c[s] == []:
+                            del out[s]
+                except:
+                    assert c is None, 'you have selected_obs but the forward model is not suitably defined!'
+                    out = info['forward_model'](a, b)
+                return out
+
+            self.forward_model = my_forward_model  # info['forward_model']
+
+            self.forward_qs = {}
+
+            for name in info['forward_qs']:
+                if info['forward_qs'] is not None:
+                    if info['forward_qs'] == []:
+                        info['forward_qs'] = [f[:-4] for f in os.listdir(path_directory+'%s/forward_qs' % name_sys)]
+                    for name in info['forward_qs']:
+                        if os.path.isfile(path_directory+'%s/forward_qs/%s.npy' % (name_sys, name)):
+                            self.forward_qs[name] = np.load(
+                                path_directory+'%s/forward_qs/%s.npy' % (name_sys, name), mmap_mode='r')
+                        elif os.path.isfile(path_directory+'%s/forward_qs/%s' % (name_sys, name)):
+                            self.forward_qs[name] = numpy.loadtxt(path_directory+'%s/forward_qs/%s' % (name_sys, name))
+
+        # 4. weights (normalized)
+
+        if os.path.isfile(path_directory+'%s/weights.npy' % name_sys):
+            self.weights = np.load(path_directory+'%s/weights.npy' % name_sys)
+        elif os.path.isfile(path_directory+'%s/weights' % name_sys):
+            self.weights = numpy.loadtxt(path_directory+'%s/weights' % name_sys)
+        else:
+            if ('obs' in info.keys()) and not (info['obs'] is None):
+                name = list(self.g.keys())[0]
+                self.weights = np.ones(len(self.g[name]))
+            elif ('forward_qs' in info.keys()) and not (info['forward_qs'] is None):
+                name = list(self.forward_qs.keys())[0]
+                self.weights = np.ones(len(self.forward_qs[info['forward_qs'][0]]))
+            else:
+                print('error: missing MD data for %s!' % name_sys)
+
+        self.weights = self.weights/np.sum(self.weights)
+
+        # 5. f (force field correction terms) and function
+
+        if ('ff_correction' in info.keys()) and (info['ff_correction'] is not None):
+
+            if info['ff_correction'] == 'linear':
+                self.ff_correction = lambda pars, f: np.matmul(f, pars)
+            else:
+                self.ff_correction = info['ff_correction']
+
+            ff_path = path_directory + '%s/ff_terms' % name_sys
+            self.f = np.load(ff_path + '.npy')
+
+
+
+class data_cycle_class +(cycle_name, DDGs_exp, info) +
+
+

Data object of a thermodynamic cycle.

+

Parameters

+
+
cycle_name : str
+
String with the name of the thermodynamic cycle taken into account.
+
DDGs_exp : pandas.DataFrame
+
Pandas.DataFrame with the experimental values and uncertainties of Delta Delta G in labelled thermodynamic cycles.
+
info : dict
+
Dictionary for the information about the temperature of cycle_name thermodynamic cycle.
+
+
+

Returns

+
+
gexp_DDG : list
+
List of two elements: the experimental value and uncertainty of the Delta Delta G.
+
temperature : float
+
Value of temperature.
+
+
+ +Expand source code + +
class data_cycle_class:
+    """
+    Data object of a thermodynamic cycle.
+    
+    Parameters
+    ----------
+    cycle_name : str
+        String with the name of the thermodynamic cycle taken into account.
+    
+    DDGs_exp : pandas.DataFrame
+        Pandas.DataFrame with the experimental values and uncertainties of Delta Delta G in labelled thermodynamic cycles.
+
+    info: dict
+        Dictionary for the information about the temperature of `cycle_name` thermodynamic cycle. 
+
+    --------
+    Returns
+    --------
+    gexp_DDG : list
+        List of two elements: the experimental value and uncertainty of the Delta Delta G.
+    
+    temperature : float
+        Value of temperature.
+    """
+    def __init__(self, cycle_name, DDGs_exp, info):
+
+        self.gexp_DDG = [DDGs_exp.loc[:, cycle_name].iloc[0], DDGs_exp.loc[:, cycle_name].iloc[1]]
+
+        if 'temperature' in info.keys():
+            self.temperature = info['temperature']
+            """Temperature."""
+        else:
+            self.temperature = 1.0
+            """Temperature"""
+
+
+
+class datapropertiesclass +(info_global, path_directory) +
+
+

Global data, common to all the investigated molecular systems.

+

Parameters

+
+
info_global : dict
+
Dictionary with global information: +info_global['system_names'] with list of names of the molecular systems; +info_global['cycle_names'] with list of names of the thermodynamic cycles; +info_global['forward_coeffs'] with string for the file name of forward coefficients; +info_global['names_ff_pars'] with list of names of the force-field correction coefficients.
+
path_directory : str
+
String with the path of the directory with input files.
+
+
+

Returns

+
+
system_names : list
+
List of names of the investigated molecular systems.
+
forward_coeffs_0 : list
+
List of the forward-model coefficients.
+
names_ff_pars : list
+
List of names of the force-field correction parameters.
+
cycle_names : list
+
List of names of the investigated thermodynamic cycles.
+
+
+ +Expand source code + +
class datapropertiesclass:
+    """Global data, common to all the investigated molecular systems.
+    
+    Parameters
+    ----------
+
+    info_global: dict
+        Dictionary with global information:
+        `info_global['system_names']` with list of names of the molecular systems;
+        `info_global['cycle_names']` with list of names of the thermodynamic cycles;
+        `info_global['forward_coeffs']` with string for the file name of forward coefficients;
+        `info_global['names_ff_pars']` with list of names of the force-field correction coefficients.
+
+    path_directory: str
+        String with the path of the directory with input files.
+
+    --------
+    Returns
+    --------
+    system_names : list
+        List of names of the investigated molecular systems.
+    
+    forward_coeffs_0 : list
+        List of the forward-model coefficients.
+    
+    names_ff_pars : list
+        List of names of the force-field correction parameters.
+
+    cycle_names : list
+        List of names of the investigated thermodynamic cycles.
+    """
+    def __init__(self, info_global, path_directory):
+
+        self.system_names = info_global['system_names']
+
+        if 'forward_coeffs' in info_global.keys():
+            temp = pandas.read_csv(path_directory + info_global['forward_coeffs'], header=None)
+            temp.index = temp.iloc[:, 0]
+            self.forward_coeffs_0 = temp.iloc[:, 1]
+
+            # temp = pandas.read_csv(path_directory+'%s' % info_global['forward_coeffs'], index_col=0)
+            # if temp.shape[0] == 1:
+            #     self.forward_coeffs_0 = temp.iloc[:, 0]
+            # else:
+            #     self.forward_coeffs_0 = temp.squeeze()
+
+        if 'names_ff_pars' in info_global.keys():
+            self.names_ff_pars = info_global['names_ff_pars']
+        
+        if 'cycle_names' in info_global.keys():
+            self.cycle_names = info_global['cycle_names']
+
+    def tot_n_experiments(self, data):
+        """This method computes the total n. of experiments."""
+        
+        tot = 0
+
+        for k in self.system_names:
+            for item in data.mol[k].n_experiments.values():
+                tot += item
+        return tot
+
+

Methods

+
+
+def tot_n_experiments(self, data) +
+
+

This method computes the total n. of experiments.

+
+
+
+
+class my_data +(infos) +
+
+
+
+ +Expand source code + +
class my_data:
+    def __init__(self, infos):
+        
+        system_names = infos['global']['system_names']
+        path_directory = infos['global']['path_directory']
+        if not path_directory[-1] == '/': path_directory += '/'
+        
+        # global data
+        self.properties = datapropertiesclass(infos['global'], path_directory)
+
+        # data for each molecular system
+
+        self.mol = {}
+
+        for name_sys in system_names:
+
+            print('loading ', name_sys)
+            
+            if name_sys in infos.keys():
+                info = {**infos[name_sys], **infos['global']}
+            else:
+                info = infos['global']
+    
+            self.mol[name_sys] = data_class(info, path_directory, name_sys)
+
+        # data for thermodynamic cycles (alchemical calculations)
+
+        if 'cycle_names' in infos['global'].keys():
+
+            logZs = pandas.read_csv(path_directory + 'alchemical/logZs', index_col=0, header=None)
+
+            for name in infos['global']['cycle_names']:
+                for s in ['MD', 'MS', 'AD', 'AS']:
+                    key = name + '_' + s
+                    if key in logZs.index:
+                        self.mol[key].logZ = logZs.loc[key][1]
+                    else:
+                        self.mol[key].logZ = 0.0
+
+            self.cycle = {}
+
+            DDGs_exp = pandas.read_csv(path_directory + 'alchemical/DDGs', index_col=0)
+
+            for name in infos['global']['cycle_names']:
+                if name in infos.keys():
+                    info = {**infos[name], **infos['global']}
+                else:
+                    info = infos['global']
+
+                self.cycle[name] = data_cycle_class(name, DDGs_exp, info)
+
+
+
+
+
+ +
+ + + diff --git a/MDRefine/hyperminimizer.html b/MDRefine/hyperminimizer.html new file mode 100644 index 0000000..ac77433 --- /dev/null +++ b/MDRefine/hyperminimizer.html @@ -0,0 +1,265 @@ + + + + + + +MDRefine.hyperminimizer API documentation + + + + + + + + + + + +
+
+
+

Module MDRefine.hyperminimizer

+
+
+

Tools n. 3: hyperminimizer. +It performs the automatic search for the optimal hyperparameters.

+
+
+
+
+
+
+

Functions

+
+
+def compute_chi2_tot(pars_ff_fm, lambdas, data, regularization, alpha, beta, gamma, which_set) +
+
+

This function is an internal tool used in compute_hypergradient() and hyper_minimizer() +to compute the total chi2 (float variable) for the training or test data set and its derivatives +(with respect to pars_ff_fm and lambdas). The choice of the data set is indicated by which_set +(which_set = 'training' for chi2 on the training set, 'validation' for chi2 on training observables and test frames, +'test' for chi2 on test observables and test frames, through validation function).

+

Parameters

+
+
pars_ff_fm, lambdas : array_like
+
Numpy arrays for (force-field + forward-model) parameters and lambdas parameters, respectively.
+
data : dict
+
Dictionary of data set object.
+
regularization : dict
+
Specified regularizations of force-field and forward-model corrections (see in MDRefinement).
+
alpha, beta, gamma : float
+
Values of the hyperparameters.
+
which_set : str
+
String variable, chosen among 'training', 'validation' or 'test' as explained above.
+
+
+
+def compute_hyperderivatives(pars_ff_fm,
lambdas,
data,
regularization,
derivatives_funs,
log10_alpha=inf,
log10_beta=inf,
log10_gamma=inf)
+
+
+

This is an internal tool of compute_hypergradient() which computes the derivatives of parameters with respect to hyperparameters, +which are going to be used later to compute the derivatives of chi2 w.r.t. hyperparameters. +It returns an instance of the class derivatives, which includes as attributes the numerical values of +the derivatives dlambdas_dlogalpha, dlambdas_dpars, dpars_dlogalpha, dpars_dlogbeta, dpars_dloggamma.

+

Parameters

+
+
pars_ff_fm : array_like
+
Numpy array for force-field and forward-model coefficients.
+
lambdas : array_like
+
Numpy array for lambdas coefficients (those for ensemble refinement).
+
data : dict
+
The data object.
+
regularization : dict
+
The regularization of force-field and forward-model corrections (see in MDRefinement).
+
derivatives_funs : class instance
+
Instance of the derivatives_funs_class class of derivatives functions computed by Jax.
+
log10_alpha, log10_beta, log10_gamma : floats
+
Logarithms (in base 10) of the corresponding hyperparameters alpha, beta, gamma (np.inf by default).
+
+
+
+def compute_hypergradient(pars_ff_fm,
lambdas,
log10_alpha,
log10_beta,
log10_gamma,
data_train,
regularization,
which_set,
data_test,
derivatives_funs)
+
+
+

This is an internal tool of mini_and_chi2_and_grad(), which employs previously defined functions (compute_hyperderivatives(), compute_chi2_tot(), +put_together()) to return selected chi2 and its gradient w.r.t hyperparameters.

+

Parameters

+
+
pars_ff_fm : array_like
+
Numpy array of (force-field and forward-model) parameters.
+
lambdas : dict
+
Dictionary of dictionaries with lambda coefficients (corresponding to Ensemble Refinement).
+
log10_alpha, log10_beta, log10_gamma : floats
+
Logarithms (in base 10) of the hyperparameters alpha, beta, gamma.
+
data_train : class instance
+
The training data set object, which is anyway required to compute the derivatives of parameters w.r.t. hyper-parameters.
+
regularization : dict
+
Specified regularizations (see in MDRefinement).
+
which_set : str
+
String indicating which set defines the chi2 to minimize in order to get the optimal hyperparameters (see in compute_chi2_tot()).
+
data_test : class instance
+
The test data set object, which is required to compute the chi2 on the test set (when which_set == 'validation' or 'test'; +otherwise, if which_set = 'training', it is useless, so it can be set to None).
+
derivatives_funs : class instance
+
Instance of the derivatives_funs_class class of derivatives functions computed by Jax Autodiff (they include those employed in compute_hyperderivatives() +and dchi2_dpars and/or dchi2_dlambdas).
+
+
+
+def hyper_function(log10_hyperpars,
map_hyperpars,
data,
regularization,
test_obs,
test_frames,
which_set,
derivatives_funs,
starting_pars,
n_parallel_jobs)
+
+
+

This function is an internal tool of hyper_minimizer() which determines the optimal parameters by minimizing the loss function at given hyperparameters; +then, it computes chi2 and its gradient w.r.t hyperparameters (for the optimal parameters).

+

Parameters

+
+
log10_hyperpars : array_like
+
Numpy array for log10 hyperparameters alpha, beta, gamma (in this order, when present).
+
map_hyperpars : list
+
Legend for log10_hyperpars (they refer to alpha, beta, gamma in this order, +but some of them may not be present, if fixed to +np.inf).
+
data : class instance
+
Class instance for data object.
+
regularization : dict
+
Dictionaries for regularization object.
+
test_obs, test_frames : dicts
+
Dictionaries for test observables and test frames, indicized by seeds.
+
which_set : str
+
String, see for compute_chi2_tot().
+
derivatives_funs : class instance
+
Derivative functions computed by Jax and employed in compute_hypergradient().
+
starting_pars : float
+
Starting values of the parameters, if user-defined; None otherwise.
+
n_parallel_jobs : int
+
Number of parallel jobs.
+
+
+

Returns

+
+
tot_chi2 : float
+
Float value of total chi2.
+
tot_gradient : array_like
+
Numpy array for gradient of total chi2 with respect to the hyperparameters.
+
Results : class instance
+
Results given by minimizer.
+
+
+

Global variable: hyper_intermediate, in order to follow steps of minimization.

+
+
+def hyper_minimizer(data,
starting_alpha=inf,
starting_beta=inf,
starting_gamma=inf,
regularization=None,
random_states=1,
replica_infos=None,
which_set='validation',
gtol=0.5,
ftol=0.05,
starting_pars=None,
n_parallel_jobs=None)
+
+
+

This tool optimizes the hyperparameters by minimizing the selected chi2 (training, validation or test) +over several (randomly) splits of the full data set into training/test set.

+

Parameters

+
+
data : class instance
+
Object data, with the full data set previously loaded.
+
starting_alpha, starting_beta, starting_gamma : floats
+
Starting points of the hyperparameters (+np.inf by default, namely no refinement in that direction).
+
regularization : dict
+
Dictionary for the defined regularizations of force-field and forward-model corrections (None by default); see for MDRefinement.
+
replica_infos : dict
+
Dictionary with information required to split frames following continuous trajectories in replica exchange ("demuxing"); see select_traintest for further details.
+
random_states : int or list
+
Random states (i.e., seeds) used in select_traintest to split the data set into training and test set (see MDRefinement); 1 by default.
+
which_set : str
+
String choosen among 'training', 'validation', 'test' (see in MDRefinement); validation by default.
+
gtol : float
+
Tolerance gtol of scipy.optimize.minimize (0.5 by default).
+
ftol : float
+
Tolerance ftol of scipy.optimize.minimize (0.05 by default).
+
starting_pars : array_like
+
Numpy array of starting values for the minimization of parameters pars_ff_fm (None by default).
+
n_parallel_jobs : int
+
Number of jobs run in parallel (None by default).
+
+
+
+def mini_and_chi2_and_grad(data,
test_frames,
test_obs,
regularization,
alpha,
beta,
gamma,
starting_pars,
which_set,
derivatives_funs)
+
+
+

This is an internal tool of hyper_function() which minimizes the loss function at given hyperparameters, computes the chi2 and +its gradient w.r.t. the hyperparameters.

+

Parameters

+
+
data : class instance
+
Class instance which constitutes the data object.
+
test_frames, test_obs : dicts
+
Dictionaries for test frames and test observables (for a given random_state).
+
regularization : dict
+
Dictionary for the regularizations (see in MDRefinement).
+
alpha, beta, gamma : floats
+
Values of the hyperparameters.
+
starting_pars : array_like
+
Numpy 1-dimensional array for starting values of the coefficients in minimizer.
+
which_set : str
+
String among 'training', 'validation' or 'test' (see in MDRefinement).
+
derivatives_funs : class instance
+
Instance of the derivatives_funs_class class of derivatives functions computed by Jax Autodiff.
+
+
+
+def put_together(dchi2_dpars, dchi2_dlambdas, derivatives) +
+
+

This is an internal tool of compute_hypergradient() which applies the chain rule in order to get the derivatives of chi2 w.r.t hyperparameters from +derivatives of chi2 w.r.t. parameters and derivatives of parameters w.r.t. hyperparameters.

+

Parameters

+
+
dchi2_dpars : array-like
+
Numpy 1-dimensional array with derivatives of chi2 w.r.t. pars_ff_fm (force-field and forward-model parameters).
+
dchi2_dlambdas : array-like
+
Numpy 1-dimensional array with derivatives of chi2 w.r.t. lambdas (same order of lambdas in dchi2_dlambdas and in derivatives).
+
derivatives : class instance
+
Class instance with derivatives of pars_ff_fm and lambdas w.r.t. hyperparameters (determined in compute_hyperderivatives()).
+
+
+

Returns

+
+
out : class instance
+
Class instance whose attributes can include dchi2_dlogalpha, dchi2_dlogbeta, dchi2_dloggamma,
+
+

depending on which hyperparameters are not fixed to +np.inf.

+
+
+
+
+
+
+ +
+ + + diff --git a/MDRefine/index.html b/MDRefine/index.html new file mode 100644 index 0000000..04b7dff --- /dev/null +++ b/MDRefine/index.html @@ -0,0 +1,104 @@ + + + + + + +MDRefine API documentation + + + + + + + + + + + +
+
+
+

Package MDRefine

+
+
+

A package to perform refinement of MD simulation trajectories.

+

Source code is on GitHub. +A test pdf manual is available here.

+

Examples

+

In the examples directory you can find a number of notebooks +that can be used as a source of inspiration.

+
+
+

Sub-modules

+
+
MDRefine.MDRefinement
+
+

Main tool: MDRefine.MDRefinement. +It refines MD-generated trajectories with customizable refinement.

+
+
MDRefine.data_loading
+
+

Tools n. 1: MDRefine.data_loading. +It loads data into the data object.

+
+
MDRefine.hyperminimizer
+
+

Tools n. 3: MDRefine.hyperminimizer. +It performs the automatic search for the optimal hyperparameters.

+
+
MDRefine.loss_and_minimizer
+
+

Tools n. 2: MDRefine.loss_and_minimizer. +It defines the loss functions and minimizes it. +It includes also select_traintest and validation.

+
+
+
+
+
+
+

Functions

+
+
+def get_version() +
+
+
+
+
+
+
+
+
+ +
+ + + diff --git a/MDRefine/loss_and_minimizer.html b/MDRefine/loss_and_minimizer.html new file mode 100644 index 0000000..6bf0ab8 --- /dev/null +++ b/MDRefine/loss_and_minimizer.html @@ -0,0 +1,660 @@ + + + + + + +MDRefine.loss_and_minimizer API documentation + + + + + + + + + + + +
+
+
+

Module MDRefine.loss_and_minimizer

+
+
+

Tools n. 2: loss_and_minimizer. +It defines the loss functions and minimizes it. +It includes also select_traintest() and validation().

+
+
+
+
+
+
+

Functions

+
+
+def compute_D_KL(weights_P: numpy.ndarray,
correction_ff: numpy.ndarray,
temperature: float,
logZ_P: float)
+
+
+

This tool computes the Kullback-Leibler divergence of P(x) = 1/Z P_0 (x) e^(-V(x)/T) +with respect to P_0 as av(V)/T + log Z where av(V) is the average value of the potential V(x) over P(x).

+

Parameters

+
+
weights_P : array_like
+
Numpy 1-dimensional array for the normalized weights P(x).
+
correction_ff : array_like
+
Numpy 1-dimensional array for the reweighting potential V(x).
+
temperature : float
+
The value of temperature T, in measure units consistently with V(x), namely, such that V(x)/T is adimensional.
+
logZ_P : float
+
The value of log Z.
+
+
+
+def compute_DeltaDeltaG_terms(data, logZ_P) +
+
+

This tool computes the chi2 for Delta Delta G (free-energy differences from thermodynamic cycles), +contributing to the loss function with alchemical calculations.

+

Parameters

+
+
data : class instance
+
Object data; here, data.properties has the attribute cycle_names (list of names of the thermodynamic cycles); +for s in data.properties.cycle_names: data.cycle[s] has attributes temperature (of the cycle) and gexp_DDG; +for s in my_list (where my_list is the list of system names associated to a thermodynamic cycle +my_list = [x2 for x in list(data.properties.cycle_names.values()) for x2 in x]): +data.mol[s] has attributes temperature (of the system) and logZ.
+
logZ_P : dict
+
+

Dictionary for logarithm of the partition function Z_P, namely, average value of exp(-V_phi(x)/temperature)

+

over the original ensemble; its keys are the selected system_names.

+
+
+

Returns

+
+
new_av_DG : dict
+
Dictionary of reweighted averages of Delta G.
+
chi2 : dict
+
Dictionary of chi2 (one for each thermodynamic cycle).
+
loss : float
+
Total contribution to the loss function from free-energy differences Delta Delta G, +given by 1/2 of the total chi2.
+
+
+
+def compute_chi2(ref, weights, g, gexp, if_separate=False) +
+
+

This tool computes the chi2 (for a given molecular system: +the input dictionaries are structured as the attributes of data.mol[mol_name]).

+

Parameters

+
+
ref : dict
+
Dictionary for references (=, >, <, ><) used to compute the appropriate chi2.
+
weights : array_like
+
Numpy 1-dimensional array of weights.
+
g : dict
+
Dictionary of observables specific for the given molecular system.
+
gexp : dict
+
Dictionary of experimental values specific for the given molecular system (coherently with g).
+
if_separate : bool
+
+

Boolean variable, True if you are distinguishing between LOWER and UPPER bounds (name_type + ' LOWER'

+

or name_type + ' UPPER'), needed for minimizations with double bounds.

+
+
+

Returns

+
+
This tool returns 4 variables: 3 dictionaries (with keys running over different kinds of observables) and 1 float:
+
 
+
av_g : dict
+
Dictionary of average values of the observables g.
+
chi2 : dict
+
Dictionary of chi2.
+
rel_diffs : dict
+
Dicionary of relative differences.
+
tot_chi2 : float
+
Total chi2 for the given molecular system.
+
+
+
+def compute_details_ER(weights_P, g, data, lambdas, alpha) +
+
+

This is an internal tool of loss_function() which computes explicitely the contribution to the loss function due to Ensemble Refinement +(namely, 1/2 chi2 + alpha D_KL) and compare this value with -alpha*Gamma (they are equal in the minimum: check). +It cycles over different systems. It acts after the minimization of the loss function inside loss_function() (not for the minimization +itself, since we exploit the Gamma function).

+

Be careful to use either: normalized values for lambdas and g (if hasattr(data.mol[name_mol],'normg_mean')) or non-normalized ones +(if not hasattr(data.mol[name_mol],'normg_mean')).

+

Parameters

+
+
weights_P : dict
+
Dictionary of Numpy arrays, namely, the weights on which Ensemble Refinement acts (those with force-field correction +in the fully combined refinement).
+
g : dict
+
Dictionary of dictionaries, like for data.mol[name_mol].g, corresponding to the observables (computed with updated forward-model coefficients).
+
data : dict
+
The original data object.
+
lambdas : dict
+
Dictionary of Numpy arrays, corresponding to the coefficients for Ensemble Refinement.
+
alpha : float
+
The alpha hyperparameter, for Ensemble Refinement.
+
+
+
+def compute_js(n_experiments) +
+
+

This tool computes the indices js (defined by cumulative sums) for lambdas corresponding to different molecular systems and +types of observables. Be careful to follow always the same order: let's choose it as that of data.n_experiments, +which is a dictionary n_experiments[name_mol][name].

+
+
+def compute_new_weights(weights: numpy.ndarray, correction: numpy.ndarray) +
+
+

This tool computes the new weights as weights*exp(-correction). +It modifies Parameters weights are normalized and correction is shifted by correction -= shift, where shift = np.min(correction). +It returns two variables: a Numpy array new_weights and a float logZ.

+
+
+def deconvolve_lambdas(data, lambdas: numpy.ndarray, if_denormalize: bool = True) +
+
+

This tool deconvolves lambdas from Numpy array to dictionary of dictionaries (corresponding to data.mol[name_mol].g); +if if_denormalize, then lambdas has been computed with normalized data, so use data.mol[name_mol].normg_std and data.mol[name_mol].normg_mean +in order to go back to corresponding lambdas for non-normalized data. The order of lambdas is the one described in compute_js().

+
+
+def gamma_function(lambdas: numpy.ndarray,
g: numpy.ndarray,
gexp: numpy.ndarray,
weights: numpy.ndarray,
alpha: float,
if_gradient: bool = False)
+
+
+

This tool computes gamma function and (if if_gradient) its derivatives and the average values of the observables av_g. +Make sure that lambdas follow the same order as g, gexp (let's use that of data.n_experiments).

+

Parameters

+
+
lambdas : array_like
+
Numpy 1-dimensional array of length N, where lambdas[j] is the lambda value for the j-th observable.
+
g : array_like
+
Numpy 2-dimensional array (M x N); g[i,j] is the j-th observable computed in the i-th frame.
+
gexp : array_like
+
Numpy 2-dimensional array (N x 2); gexp[j,0] is the experimental value of the j-th observable, gexp[j,1] is the associated experimental uncertainty.
+
weights : array_like
+
Numpy 1-dimensional array of length M; w[i] is the weight of the i-th frame (possibly non-normalized).
+
alpha : float
+
The value of the alpha hyperparameter.
+
if_gradient : bool
+
If true, return also the gradient of the gamma function.
+
+
+
+def l2_regularization(pars: numpy.ndarray, choice: str = 'plain l2') +
+
+

This tool computes the L2 regularization for the force-field correction coefficients pars as specified by choice. It includes:

+
    +
  • +

    'plain l2' (plain L2 regularization of pars);

    +
  • +
  • +

    L2 regularization for alchemical calculations with charges (as described by Valerio Piomponi et al., see main paper): +pars[:-1] are the charges and pars[-1] is V_eta; there is the constraint on the total charge, and there are 3 pars[4] charges in the molecule; +so, 'constraint 1' is the L2 regularization on charges, while 'constraint 2' is the L2 regularization on charges and on V_eta.

    +
  • +
+

Output values: lossf_reg and gradient (floats).

+
+
+def loss_function(pars_ff_fm: numpy.ndarray,
data: dict,
regularization: dict,
alpha: float = inf,
beta: float = inf,
gamma: float = inf,
fixed_lambdas: numpy.ndarray = None,
gtol_inn: float = 0.001,
if_save: bool = False,
bounds: dict = None)
+
+
+

This tool computes the fully-combined loss function (to minimize), taking advantage of the inner minimization with Gamma function.

+

If not np.isinf(alpha):

+
    +
  • +

    if fixed_lambdas == None, then do the inner minimization of Gamma (in this case, you have the global variable lambdas, +corresponding to the starting point of the minimization; it is a Numpy array sorted as in compute_js()).

    +
  • +
  • +

    else: lambdas is fixed (fixed_lambdas is not None) and the Gamma function is evaluated at this value of lambda, which must +correspond to its point of minimum, otherwise there is a mismatch between the Gamma function and the Ensemble Refinement loss.

    +
  • +
+

The order followed for lambdas is the one of compute_js(), which is not modified in any step.

+

If if_save: loss_function() returns Details class instance with the detailed results; otherwise, it returns just the loss value.

+

The input data are not modified by loss_function() (neither explicitely by loss_function() nor by its inner functions): +for forward-model updating, loss_function() defines a new variable g (through copy.deepcopy).

+

Parameters

+
+
pars_ff_fm : array_like
+
Numpy 1-dimensional array with parameters for force-field corrections and/or forward models. +These parameters are sorted as: first force-field correction (ff), then forward model (fm); +order for ff: names_ff_pars = []; for k in system_names: [names_ff_pars.append(x) for x in data[k].f.keys() if x not in names_ff_pars]; +order for fm: the same as data.forward_coeffs_0.
+
data : dict
+
Dictionary of class instances as organised in load_data, which constitutes the data object.
+
regularization : dict
+
Dictionary for the force-field and forward-model correction regularizations (see MDRefinement).
+
alpha, beta, gamma : floats
+
The hyperparameters of the three refinements (respectively, to: the ensemble, the force-field, the forward-model); +(+np.inf by default, namely no refinement in that direction).
+
fixed_lambdas : array_like
+
Numpy 1-dimensional array of fixed values of lambdas (coefficients for Ensemble Refinement, organized as in compute_js()). +(None by default).
+
gtol_inn : float
+
Tolerance gtol for the inner minimization of Gamma function (1e-3 by default).
+
if_save : bool
+
Boolean variable (False by default).
+
bounds : dict
+
Dictionary of boundaries for the inner minimization (None by default).
+
+
+
+def loss_function_and_grad(pars: numpy.ndarray,
data: dict,
regularization: dict,
alpha: float,
beta: float,
gamma: float,
gtol_inn: float,
boundaries: dict,
gradient_fun)
+
+
+

This tool returns loss_function() and its gradient; the gradient function, which is going to be evaluated, is computed by Jax and passed as input variable gradient_fun. +If not np.isinf(alpha), it appends also loss and lambdas to intermediates.loss and intermediates.lambdas, respectively.

+

Global variable: intermediates (intermediate values during the minimization steps of loss_function()).

+

Parameters

+
+
pars : array_like
+
Numpy array of parameters for force-field correction and forward model, respectively.
+
data, regularization : dicts
+
Dictionaries for data object and regularizations (see in MDRefinement).
+
alpha, beta, gamma : floats
+
Values of the hyperparameters.
+
gtol_inn : float
+
Tolerance gtol for the inner minimization in loss_function().
+
boundaries : dict
+
Dictionary of boundaries for the inner minimization in loss_function().
+
gradient_fun : function
+
Gradient function of loss_function(), computed by Jax.
+
+
+
+def minimizer(original_data,
*,
regularization: dict = None,
alpha: float = inf,
beta: float = inf,
gamma: float = inf,
gtol: float = 0.001,
gtol_inn: float = 0.001,
data_test: dict = None,
starting_pars: numpy.ndarray = None)
+
+
+

This tool minimizes loss_function on original_data and do validation() on data_test (if not None), at given hyperparameters.

+

Parameters

+
+
original_data : dict
+
Dictionary for data-like object employed for the minimization of loss_function().
+
regularization : dict
+
Dictionary for the regularizations (see in MDRefinement).
+
alpha, beta, gamma : floats
+
Values of the hyperparameters for combined refinement (+np.inf by default: no refinement in that direction).
+
gtol, gtol_inn : floats
+
Tolerances gtol for the minimizations of loss_function() and inner gamma_function(), respectively.
+
data_test : dict
+
Dictionary for data-like object employed as test set (None by default, namely no validation, just minimization).
+
starting_pars : array_like
+
Numpy 1-dimensional array for pre-defined starting point of loss_function() minimization (None by default).
+
+
+
+def normalize_observables(gexp, g, weights=None) +
+
+

This tool normalizes g and gexp. Since experimental observables have different units, it is better to normalize them, in order that +varying any lambda coefficient by the same value epsilon would result in comparable effects to the ensemble. +This results to be useful in the minimization of gamma_function().

+

Parameters

+
+
gexp, g : dicts
+
Dictionaries corresponding to data.mol[name_mol].gexp and data.mol[name_mol].g.
+
weights : array-like
+
+

 

+

Numpy 1-dimensional array, by default None (namely, equal weight for each frame).

+
+
+

Returns

+
+
norm_g, norm_gexp : dict
+
Dictionaries for normalized g and gexp.
+
norm_gmean, norm_gstd : dict
+
Dictionaries for the reference values for normalization (average and standard deviation).
+
+
+
+def select_traintest(data,
*,
test_frames_size: float = 0.2,
test_obs_size: float = 0.2,
random_state: int = None,
test_frames: dict = None,
test_obs: dict = None,
if_all_frames: bool = False,
replica_infos: dict = None)
+
+
+

This tool splits the data set into training and test set. You can either randomly select the frames and/or the observables (accordingly to test_frames_size, test_obs_size, random_state) or pass the dictionaries test_obs and/or test_frames.

+

Parameters

+
+
data : class instance
+
Class instance for the data object.
+
test_frames_size, test_obs_size : float
+
Values for the fractions of frames and observables for the test set, respectively. Each of them is a number in (0,1) (same fraction for every system), +by default 0.2.
+
random_state : int
+
The random state (or seed), used to make the same choice for different hyperparameters; if None, +it is randomly taken.
+
test_frames, test_obs : dicts
+
Dictionaries for the test frames and observables.
+
if_all_frames : bool
+
Boolean variable, False by default; if True, then use all the frames for the test observables in the test set, +otherwise just the test frames.
+
replica_infos : dict
+
+

Dictionary of information used to select frames based on continuous trajectories ("demuxing"), by default None (just randomly select frames). +It includes: n_temp_replica, path_directory, stride. If not None, select_traintest() will read replica_temp.npy files +with shape (n_frames, n_replicas) containing numbers from 0 to n_replicas - 1 which indicate corresponding temperatures (for each replica

+

index in axis=1).

+
+
+

Returns

+
+
data_train, data_test : class instances
+
Class instances for training and test data; data_test includes: +trained observables and non-trained (test) frames (where it is not specified new); +non-trained (test) observables and non-trained/all (accordingly to if_all_frames) frames (where specified new).
+
test_obs, test_frames : dicts
+
Dictionaries for the observables and frames selected for the test set.
+
+
+
+def validation(pars_ff_fm,
lambdas,
data_test,
*,
regularization=None,
alpha=inf,
beta=inf,
gamma=inf,
data_train=None,
which_return='details')
+
+
+

This tool evaluates loss_function() in detail over the test set; then,

+
    +
  • +

    if which_return == 'chi2 validation', it returns the total chi2 on the 'validation' data set (training observables, test frames); +this is required to compute the derivatives of the chi2 in 'validation' with Jax;

    +
  • +
  • +

    elif which_return == 'chi2 test', it returns the total chi2 on the 'test' data set (test observables, test frames +or all frames if data_train is not None); this is required to compute the derivatives of the chi2 in 'test' with Jax;

    +
  • +
  • +

    else, it returns Validation_values class instance, with all the computed values (both chi2 and regularizations).

    +
  • +
+

Parameters

+
+
pars_ff_fm : array_like
+
Numpy 1-dimensional array for the force-field and forward-model coefficients.
+
lambdas : array_like
+
Numpy 1-dimensional array of lambdas coefficients (those for ensemble refinement).
+
data_test : dict
+
Dictionary for the test data set, data-like object, as returned by select_traintest().
+
regularization : dict
+
Dictionary for the regularizations (see in MDRefinement), by default, None.
+
alpha, beta, gamma : floats
+
Values for the hyperparameters (by default, +np.inf, namely, no refinement).
+
data_train : dict
+
Dictionary for the training data set, data-like object, as returned by select_traintest() (None by default, +namely use only test frames for new observables).
+
which_return : str
+
String described above (by default 'details').
+
+
+
+
+
+

Classes

+
+
+class class_test +(data_mol, test_frames_mol, test_obs_mol, if_all_frames, data_train_mol) +
+
+

Class for test data set, with similar structure as data_class.

+
+ +Expand source code + +
class class_test:
+    """
+    Class for test data set, with similar structure as `data_class`.
+    """
+    def __init__(self, data_mol, test_frames_mol, test_obs_mol, if_all_frames, data_train_mol):
+
+        # A. split weights
+        try:
+            w = data_mol.weights[tuple(test_frames_mol)]
+        except:
+            try:
+                w = data_mol.weights[test_frames_mol]
+            except:
+                w = data_mol.weights[list(test_frames_mol)]
+        
+        self.logZ = np.log(np.sum(w))
+        self.weights = w/np.sum(w)
+        self.n_frames = np.shape(w)[0]
+
+        # B. split force-field terms
+        if hasattr(data_mol, 'f'):
+            self.ff_correction = data_mol.ff_correction
+            try:
+                self.f = data_mol.f[test_frames_mol, :]
+            except:
+                self.f = data_mol.f[list(test_frames_mol), :]
+
+        # C. split experimental values gexp, normg_mean and normg_std, observables g
+
+        if hasattr(data_mol, 'gexp'):
+            self.gexp_new = {}
+            self.n_experiments_new = {}
+
+            for name_type in data_mol.gexp.keys():
+
+                try:
+                    self.gexp_new[name_type] = data_mol.gexp[name_type][list(test_obs_mol[name_type])]
+                except:
+                    self.gexp_new[name_type] = data_mol.gexp[name_type][test_obs_mol[name_type]]
+
+                self.n_experiments_new[name_type] = len(test_obs_mol[name_type])
+
+        if hasattr(data_mol, 'names'):
+
+            self.names_new = {}
+
+            for name_type in data_mol.names.keys():
+                self.names_new[name_type] = data_mol.names[name_type][list(test_obs_mol[name_type])]
+
+        if hasattr(data_mol, 'g'):
+
+            self.g_new = {}
+            if if_all_frames:
+                self.g_new_old = {}
+            self.g = {}
+
+            for name_type in data_mol.g.keys():
+
+                # split g into: train, test1 (non-trained obs, all frames or only non-used ones),
+                # test2 (trained obs, non-used frames)
+                # if not test_obs[name_mol][name_type] == []:
+                self.g_new[name_type] = (data_mol.g[name_type][test_frames_mol, :].T)[test_obs_mol[name_type], :].T
+
+                if if_all_frames:  # new observables on trained frames
+                    self.g_new_old[name_type] = np.delete(
+                        data_mol.g[name_type], test_frames_mol, axis=0)[:, list(test_obs_mol[name_type])]
+
+                g3 = np.delete(data_mol.g[name_type], test_obs_mol[name_type], axis=1)
+                self.g[name_type] = g3[test_frames_mol, :]
+
+        if hasattr(data_mol, 'forward_qs'):
+
+            self.forward_qs = {}
+
+            for name_type in data_mol.forward_qs.keys():
+                self.forward_qs[name_type] = data_mol.forward_qs[name_type][list(test_frames_mol), :]
+
+            if if_all_frames:
+                self.forward_qs_trained = copy.deepcopy(data_train_mol.forward_qs)
+
+        if hasattr(data_mol, 'forward_model'):
+            self.forward_model = data_mol.forward_model
+
+        self.ref = copy.deepcopy(data_mol.ref)
+        self.ref_all = copy.deepcopy(data_mol.ref)
+        self.selected_obs = copy.deepcopy(data_train_mol.selected_obs)  # same observables as in training
+        self.selected_obs_new = test_obs_mol
+
+        self.gexp = copy.deepcopy(data_train_mol).gexp
+        self.n_experiments = copy.deepcopy(data_train_mol).n_experiments
+        self.temperature = data_mol.temperature
+
+
+
+class class_train +(data_mol, test_frames_mol, test_obs_mol) +
+
+

Class for training data set, with similar structure as data_class.

+
+ +Expand source code + +
class class_train:
+    """
+    Class for training data set, with similar structure as `data_class`.
+    """
+    def __init__(self, data_mol, test_frames_mol, test_obs_mol):
+
+        # training observables
+        train_obs = {}
+        for s in data_mol.n_experiments.keys():
+            train_obs[s] = [i for i in range(data_mol.n_experiments[s]) if i not in test_obs_mol[s]]
+        self.selected_obs = train_obs
+
+        # A. split weights
+        w = np.delete(data_mol.weights, test_frames_mol)
+        self.logZ = np.log(np.sum(w))
+        self.weights = w/np.sum(w)
+        self.n_frames = np.shape(w)[0]
+
+        # B. split force-field terms
+
+        if hasattr(data_mol, 'f'):
+            self.ff_correction = data_mol.ff_correction
+            self.f = np.delete(data_mol.f, test_frames_mol, axis=0)
+
+        # C. split experimental values gexp, normg_mean and normg_std, observables g
+
+        if hasattr(data_mol, 'gexp'):
+
+            self.gexp = {}
+            self.n_experiments = {}
+
+            for name_type in data_mol.gexp.keys():
+                self.gexp[name_type] = np.delete(data_mol.gexp[name_type], test_obs_mol[name_type], axis=0)
+                self.n_experiments[name_type] = np.shape(self.gexp[name_type])[0]
+
+        if hasattr(data_mol, 'names'):
+
+            self.names = {}
+
+            for name_type in data_mol.names.keys():
+                self.names[name_type] = data_mol.names[name_type][train_obs[name_type]]
+
+        if hasattr(data_mol, 'g'):
+
+            self.g = {}
+
+            for name_type in data_mol.g.keys():
+                train_g = np.delete(data_mol.g[name_type], test_frames_mol, axis=0)
+                self.g[name_type] = np.delete(train_g, test_obs_mol[name_type], axis=1)
+
+        if hasattr(data_mol, 'forward_qs'):
+
+            self.forward_qs = {}
+
+            for name_type in data_mol.forward_qs.keys():
+                self.forward_qs[name_type] = np.delete(data_mol.forward_qs[name_type], test_frames_mol, axis=0)
+
+        if hasattr(data_mol, 'forward_model'):
+            self.forward_model = data_mol.forward_model
+
+        self.ref = data_mol.ref
+
+        self.temperature = data_mol.temperature
+
+
+
+class intermediates_class +(alpha) +
+
+

Class for the intermediate steps of the minimization of the loss function.

+
+ +Expand source code + +
class intermediates_class:
+    """Class for the intermediate steps of the minimization of the loss function."""
+    def __init__(self, alpha):
+        
+        self.loss = []
+        self.pars = []
+
+        if not np.isinf(alpha):
+            self.lambdas = []
+            self.minis = []
+
+
+
+
+
+ +
+ + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..3df4b52 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +Precompiled manual for MDRefine +------------------------------- + +This repository hosts a precompiled manual for MDRefine +git revision [9afcc05](https://github.com/bussilab/MDRefine/commit/9afcc05). + +To browse the manual you should go [here](http://bussilab.github.io/doc-MDRefine). + +You can also download a full copy of the manual for offline access +at [this link](http://github.com/bussilab/doc-MDRefine/archive/master.zip). + +This manual has been compiled on [GitHub Actions](https://github.com/bussilab/MDRefine/actions) on Tue Nov 26 16:21:23 UTC 2024. + diff --git a/examples/Tutorial.html b/examples/Tutorial.html new file mode 100644 index 0000000..47d0e97 --- /dev/null +++ b/examples/Tutorial.html @@ -0,0 +1,20296 @@ + + + + + +Tutorial + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Results = {} + +for beta in betas: + print('beta: ', beta) + + Results[beta] = minimizer(data, regularization=regularization, beta=beta) + + clear_output() + + +!mkdir ../../Figures +!mkdir ../../Results + +Results = Parallel(n_jobs=10, verbose=1)(delayed(minimizer)( + data, regularization=regularization, beta=beta) for beta in betas) + +df.to_csv('Results/alchemical_calculations/values_L2charges') +df2.to_csv('Results/alchemical_calculations/values_DKL') + + + + +
+ + diff --git a/examples/Tutorial_2.html b/examples/Tutorial_2.html new file mode 100644 index 0000000..dd245b0 --- /dev/null +++ b/examples/Tutorial_2.html @@ -0,0 +1,9984 @@ + + + + + +Tutorial_2 + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + +
+ + diff --git a/examples/Tutorial_3.html b/examples/Tutorial_3.html new file mode 100644 index 0000000..5418002 --- /dev/null +++ b/examples/Tutorial_3.html @@ -0,0 +1,13437 @@ + + + + + +Tutorial_3 + + + + + + + + + + + + +
+ + + +def forward_model_fun(fm_coeffs, forward_qs): + + # 1. compute the cosine (which is the quantity you need in the forward model; + # you could do this just once before loading data) + forward_qs_cos = {} + + for type_name in forward_qs.keys(): + forward_qs_cos[type_name] = jnp.cos(forward_qs[type_name]) + + # # if you have selected_obs, compute only the corresponding observables + # if selected_obs is not None: + # for type_name in forward_qs.keys(): + # forward_qs_cos[type_name] = forward_qs_cos[type_name][:,selected_obs[type_name+'_3J']] + + # 2. compute observables (forward_qs_out) through forward model + forward_qs_out = { + 'backbone1_gamma_3J': fm_coeffs[0]*forward_qs_cos['backbone1_gamma']**2 + fm_coeffs[1]*forward_qs_cos['backbone1_gamma'] + fm_coeffs[2], + 'backbone2_beta_epsilon_3J': fm_coeffs[3]*forward_qs_cos['backbone2_beta_epsilon']**2 + fm_coeffs[4]*forward_qs_cos['backbone2_beta_epsilon'] + fm_coeffs[5], + 'sugar_3J': fm_coeffs[6]*forward_qs_cos['sugar']**2 + fm_coeffs[7]*forward_qs_cos['sugar'] + fm_coeffs[8] } + + return forward_qs_out +infos['global']['names_ff_pars'] = ['sin alpha', 'cos alpha', 'sin zeta', 'cos zeta'] + +def ff_correction(pars, f): + out = jnp.matmul(pars, (f[:,[0,6,3,9]]+f[:,[1,7,4,10]]+f[:,[2,8,5,11]]).T) + return out + +def ff_correction_hexamers(pars, f): + out = jnp.matmul(pars, (f[:,[0,10,5,15]]+f[:,[1,11,6,16]]+f[:,[2,12,7,17]]+f[:,[3,13,8,18]]+f[:,[4,14,9,19]]).T) + return out + +infos['global']['ff_correction'] = ff_correction +infos['UCAAUC'] = {'ff_correction': ff_correction_hexamers} + + + + +{'original': 0 +A_gamma 9.70 +B_gamma -1.80 +C_gamma 0.00 +A_beta 15.30 +B_beta -6.10 +C_beta 1.60 +A_sugar 9.67 +B_sugar -2.03 +C_sugar 0.00 +Name: 1, dtype: float64, 'Thorben': 0 +A_gamma 10.07 +B_gamma -1.87 +C_gamma -0.13 +A_beta 18.34 +B_beta -5.39 +C_beta 0.11 +A_sugar 7.81 +B_sugar -2.05 +C_sugar 0.25 +Name: 1, dtype: float64} + + + + + + + + + + + + + + + + + + +
+ + diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 0000000..79d773f --- /dev/null +++ b/examples/index.html @@ -0,0 +1,42 @@ + + + + + + + Directory Tree + + + +

Directory Tree

+ .
+ ├── Tutorial.html
+ ├── Tutorial_2.html
+ ├── Tutorial_3.html
+ ├── index.html
+ └── load_data.html
+


+

+ tree v2.0.2 © 1996 - 2022 by Steve Baker and Thomas Moore
+ HTML output hacked and copyleft © 1998 by Francesc Rocher
+ JSON output hacked and copyleft © 2014 by Florian Sesser
+ Charsets / OS/2 support © 2001 by Kyosuke Tokoro +

+ + diff --git a/examples/load_data.html b/examples/load_data.html new file mode 100644 index 0000000..49746aa --- /dev/null +++ b/examples/load_data.html @@ -0,0 +1,9596 @@ + + + + + +load_data + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + diff --git a/index.html b/index.html new file mode 100644 index 0000000..1fc106a --- /dev/null +++ b/index.html @@ -0,0 +1,10 @@ + + + +Page Auto Redirect + + + +This is an auto redirect page. + +
+
+
+

Module MDRefine.MDRefinement

+
+
+

Main tool: MDRefinement(). +It refines MD-generated trajectories with customizable refinement.

+
+
+
+
+
+
+

Functions

+
+
+def MDRefinement(infos: dict,
*,
regularization: dict = None,
stride: int = 1,
starting_alpha: float = inf,
starting_beta: float = inf,
starting_gamma: float = inf,
random_states=5,
which_set: str = 'validation',
gtol: float = 0.5,
ftol: float = 0.05,
results_folder_name: str = 'results',
n_parallel_jobs: int = None)
+
+
+

This is the main tool of the package: it loads data, searches for the optimal hyperparameters and minimizes the loss function on the whole data set +by using the opimized hyperparameters. The output variables are then saved in a folder; they include input values, min_lambdas (optimal lambda coefficients for Ensemble Refinement, when performed), +result, hyper_search (steps in the search for optimal hyperparameters) (.csv files) and the .npy arrays with the new weights determined in the refinement.

+

Parameters

+
+
infos : dict
+
A dictionary of information used to load data with load_data (see in the Examples directory).
+
regularization : dict
+
A dictionary which can include two keys: force_field_reg and forward_model_reg, to specify the regularizations to the force-field correction and the forward model, respectively; +the first key is either a string (among plain l2, constraint 1, constraint 2, KL divergence) or a user-defined +function which takes as input pars_ff and returns the regularization term to be multiplied by the hyperparameter beta; +the second key is a user-defined function which takes as input pars_fm and forward_coeffs_0 (current and refined forward-model coefficients) and +returns the regularization term to be multiplied by the hyperparameter gamma.
+
stride : int
+
The stride of the frames used to load data employed in search for optimal hyperparameters +(in order to reduce the computational cost, at the price of a lower representativeness of the ensembles).
+
starting_alpha, starting_beta, starting_gamma : floats
+
Starting values of the hyperparameters (np.inf by default, namely no refinement in that direction).
+
random_states : int or list of integers
+
Random states (i.e., seeds) used to split the data set in cross validation (if integer, then random_states = np.arange(random_states).
+
which_set : str
+
String chosen among 'training', 'validation' or 'test', which specifies how to determine optimal hyperparameters: +if minimizing the (average) chi2 on the training set for 'training', on training observables and test frames for 'validation', +on test observables for 'test'.
+
gtol : float
+
Tolerance gtol (on the gradient) of scipy.optimize.minimize (0.5 by default).
+
ftol : float
+
Tolerance ftol of scipy.optimize.minimize (0.05 by default).
+
results_folder_name : str
+
String for the prefix of the folder where to save results; the complete folder name is results_folder_name + '_' + time where time is the current time +when the algorithm has finished, in order to uniquely identify the folder with the results.
+
n_parallel_jobs : int
+
How many jobs are run in parallel (None by default).
+
+
+
+def save_txt(input_values, Result, coeff_names, folder_name='Result') +
+
+

This is an internal tool of MDRefinement() used to save input_values and output Result as csv and npy files in a folder whose name is +folder_name + '_' + date where date is the current time when the computation ended (it uses date_time +to generate unique file name, on the assumption of a single folder name at given time).

+

Parameters

+
+
input_values : dict
+
Dictionary with input values of the refinement, such as stride, starting values of the hyperparameters, random_states, which_set, tolerances (see MDRefinement()).
+
Result : class instance
+
Class instance with the results of minimizer and the search for the optimal hyperparameters.
+
coeff_names : list
+
List with the names of the coefficients (force-field and forward-model corrections).
+
folder_name : str
+
String for the prefix of the folder name (by default, 'Result').
+
+
+
+def unwrap_2dict(my_2dict) +
+
+

Tool to unwrap a 2-layer dictionary my_2dict into list of values and list of keys.

+
+
+
+
+
+
+ +