Repository: Yu-Group/veridical-flow

Issue:
Build_vset not building vfuncs properly
Call to `build_vset` is failing to set the `vfuncs` attribute of `Vset` correctly. It is not combining the parameters in `param_dict` accurately.

```
param_dict = {
    'n_estimators': [100, 200, 300],
    'min_samples_split': [2, 10],  # default value comes first
    'max_features': ['sqrt', 'log2']
}
rf_set = build_vset('RF', RandomForestRegressor, param_dict, criterion = 'absolute_error')
assert len(rf_set.modules) == 3*2*2
```

Returns 
```
AssertionError                            Traceback (most recent call last)
Input In [2], in <module>
      1 param_dict = {
      2     'n_estimators': [100, 200, 300],
      3     'min_samples_split': [2, 10],  # default value comes first
      4     'max_features': ['sqrt', 'log2']
      5 }
      6 rf_set = build_vset('RF', RandomForestRegressor, param_dict, criterion = 'absolute_error')
----> 7 assert len(rf_set.modules) == 3*2*2
```

Discussion:

Patch:
diff --git a/docs/helpers.html b/docs/helpers.html
index d6db935..9b6ff64 100644
--- a/docs/helpers.html
+++ b/docs/helpers.html
@@ -62,48 +62,53 @@ def init_args(args_tuple: Union[tuple, list], names=None):
     return output_dicts
 
 
-def build_vset(name: str, obj, *args, param_dict=None, reps: int = 1,
+def build_vset(name: str, func, param_dict=None, reps: int = 1,
                is_async: bool = False, output_matching: bool = False,
                lazy: bool = False, cache_dir: str = None, verbose: bool = True,
                tracking_dir: str = None, **kwargs) -&gt; Vset:
-    &#34;&#34;&#34;Builds a Vset by currying callable obj with all combinations of parameters in param_dict.
+    &#34;&#34;&#34;Builds a new Vset by currying or instantiating callable `func` with all
+    combinations of parameters in `param_dict` and optional additional `**kwargs`.
 
     Parameters
     ----------
-    name: str
-        a name for the output Vset
-    obj: callable
-        a callable to use as the base for Vfuncs in the output Vset
-    param_dict: dict[str, list]
-        keys are obj kwarg names and values in the dict are lists of params to try
-    *args
-        additional fixed arguments to pass to obj
-    reps: int (optional)
-        the number of times to repeat the obj in the output Vset&#39;s modules for
-        each combination of params in param_dict
-    is_async: bool (optional)
-        if True, modules are computed asynchronously
-    output_matching: bool (optional)
-        if True, then output keys from Vset will be matched when used
-        in other Vsets
-    cache_dir: str (optional)
-        if provided, do caching and use cache_dir as the data store for
-        joblib.Memory
-    verbose : bool (optional)
-        if True, modules are named with param_dict items as tuples of str(&#34;param_name=param_val&#34;)
-    tracking_dir: str (optional)
-        if provided, use the mlflow.tracking api to log outputs as metrics
-        with params determined by input keys
+    name : str
+        A name for the output Vset.
+    func : callable
+        A callable to use as the base for Vfuncs in the output Vset. Can also be
+        a class object, in which case the class is immediately instantiated with
+        the parameter combinations from `param_dict`.
+    param_dict : dict[str, list], optional
+        A dict with string keys corresponding to argument names of `func` and
+        entries which are lists of values to pass to `func` at run time (or when
+        instantiating `func` if it&#39;s a class object).
+    reps : int, optional
+        The number of times to repeat `func` in the output Vset&#39;s modules for
+        each combination of the parameters in `param_dict`.
+    is_async : bool, optional
+        If True, modules are computed asynchronously.
+    output_matching : bool, optional
+        If True, then output keys from Vset will be matched when used in other
+        Vsets.
+    cache_dir : str, optional
+        If provided, do caching and use `cache_dir` as the data store for
+        joblib.Memory.
+    verbose : bool, optional
+        If True, modules are named with `param_dict` items as tuples of
+        str(&#34;param_name=param_val&#34;).
+    tracking_dir : str, optional
+        If provided, use the mlflow.tracking API to log outputs as metrics with
+        parameters determined by input keys.
     **kwargs
-        additional fixed keyword arguments to pass to obj
+        Additional fixed keyword arguments to pass to `func`.
 
     Returns
     -------
-    new_vset : Vset
+    new_vset : vflow.vset.Vset
+
     &#34;&#34;&#34;
     if param_dict is None:
         param_dict = {}
-    assert callable(obj), &#39;obj must be callable&#39;
+    assert callable(func), &#39;func must be callable&#39;
 
     vfuncs = []
     vkeys = []
@@ -124,13 +129,13 @@ def build_vset(name: str, obj, *args, param_dict=None, reps: int = 1,
                 vkeys.append((f&#39;rep={i}&#39;, ) + vkey_tup)
             else:
                 vkeys.append(vkey_tup)
-            # check if obj is a class
-            if isinstance(obj, type):
-                # instantiate obj
-                vfuncs.append(Vfunc(module=obj(*args, **kwargs_dict), name=str(vkey_tup)))
+            # check if func is a class
+            if isinstance(func, type):
+                # instantiate func
+                vfuncs.append(Vfunc(module=func(**kwargs_dict), name=str(vkey_tup)))
             else:
-                # use partial to wrap obj
-                vfuncs.append(Vfunc(module=partial(obj, *args, **kwargs_dict), name=str(vkey_tup)))
+                # use partial to wrap func
+                vfuncs.append(Vfunc(module=partial(func, **kwargs_dict), name=str(vkey_tup)))
     if not verbose or (len(param_dict) == 0 and reps == 1):
         vkeys = None
     return Vset(name, vfuncs, is_async=is_async, module_keys=vkeys,
@@ -245,90 +250,99 @@ def cum_acc_by_uncertainty(mean_preds, std_preds, true_labels):
 <h2 class="section-title" id="header-functions">Functions</h2>
 <dl>
 <dt id="vflow.helpers.build_vset"><code class="name flex">
-<span>def <span class="ident">build_vset</span></span>(<span>name: str, obj, *args, param_dict=None, reps: int = 1, is_async: bool = False, output_matching: bool = False, lazy: bool = False, cache_dir: str = None, verbose: bool = True, tracking_dir: str = None, **kwargs) ‑> <a title="vflow.vset.Vset" href="vset.html#vflow.vset.Vset">Vset</a></span>
+<span>def <span class="ident">build_vset</span></span>(<span>name: str, func, param_dict=None, reps: int = 1, is_async: bool = False, output_matching: bool = False, lazy: bool = False, cache_dir: str = None, verbose: bool = True, tracking_dir: str = None, **kwargs) ‑> <a title="vflow.vset.Vset" href="vset.html#vflow.vset.Vset">Vset</a></span>
 </code></dt>
 <dd>
-<div class="desc"><p>Builds a Vset by currying callable obj with all combinations of parameters in param_dict.</p>
+<div class="desc"><p>Builds a new Vset by currying or instantiating callable <code>func</code> with all
+combinations of parameters in <code>param_dict</code> and optional additional <code>**kwargs</code>.</p>
 <h2 id="parameters">Parameters</h2>
 <dl>
 <dt><strong><code>name</code></strong> :&ensp;<code>str</code></dt>
-<dd>a name for the output Vset</dd>
-<dt><strong><code>obj</code></strong> :&ensp;<code>callable</code></dt>
-<dd>a callable to use as the base for Vfuncs in the output Vset</dd>
-<dt><strong><code>param_dict</code></strong> :&ensp;<code>dict[str, list]</code></dt>
-<dd>keys are obj kwarg names and values in the dict are lists of params to try</dd>
-<dt><strong><code>*args</code></strong></dt>
-<dd>additional fixed arguments to pass to obj</dd>
-<dt><strong><code>reps</code></strong> :&ensp;<code>int (optional)</code></dt>
-<dd>the number of times to repeat the obj in the output Vset's modules for
-each combination of params in param_dict</dd>
-<dt><strong><code>is_async</code></strong> :&ensp;<code>bool (optional)</code></dt>
-<dd>if True, modules are computed asynchronously</dd>
-<dt><strong><code>output_matching</code></strong> :&ensp;<code>bool (optional)</code></dt>
-<dd>if True, then output keys from Vset will be matched when used
-in other Vsets</dd>
-<dt><strong><code>cache_dir</code></strong> :&ensp;<code>str (optional)</code></dt>
-<dd>if provided, do caching and use cache_dir as the data store for
-joblib.Memory</dd>
-<dt><strong><code>verbose</code></strong> :&ensp;<code>bool (optional)</code></dt>
-<dd>if True, modules are named with param_dict items as tuples of str("param_name=param_val")</dd>
-<dt><strong><code>tracking_dir</code></strong> :&ensp;<code>str (optional)</code></dt>
-<dd>if provided, use the mlflow.tracking api to log outputs as metrics
-with params determined by input keys</dd>
+<dd>A name for the output Vset.</dd>
+<dt><strong><code>func</code></strong> :&ensp;<code>callable</code></dt>
+<dd>A callable to use as the base for Vfuncs in the output Vset. Can also be
+a class object, in which case the class is immediately instantiated with
+the parameter combinations from <code>param_dict</code>.</dd>
+<dt><strong><code>param_dict</code></strong> :&ensp;<code>dict[str, list]</code>, optional</dt>
+<dd>A dict with string keys corresponding to argument names of <code>func</code> and
+entries which are lists of values to pass to <code>func</code> at run time (or when
+instantiating <code>func</code> if it's a class object).</dd>
+<dt><strong><code>reps</code></strong> :&ensp;<code>int</code>, optional</dt>
+<dd>The number of times to repeat <code>func</code> in the output Vset's modules for
+each combination of the parameters in <code>param_dict</code>.</dd>
+<dt><strong><code>is_async</code></strong> :&ensp;<code>bool</code>, optional</dt>
+<dd>If True, modules are computed asynchronously.</dd>
+<dt><strong><code>output_matching</code></strong> :&ensp;<code>bool</code>, optional</dt>
+<dd>If True, then output keys from Vset will be matched when used in other
+Vsets.</dd>
+<dt><strong><code>cache_dir</code></strong> :&ensp;<code>str</code>, optional</dt>
+<dd>If provided, do caching and use <code>cache_dir</code> as the data store for
+joblib.Memory.</dd>
+<dt><strong><code>verbose</code></strong> :&ensp;<code>bool</code>, optional</dt>
+<dd>If True, modules are named with <code>param_dict</code> items as tuples of
+str("param_name=param_val").</dd>
+<dt><strong><code>tracking_dir</code></strong> :&ensp;<code>str</code>, optional</dt>
+<dd>If provided, use the mlflow.tracking API to log outputs as metrics with
+parameters determined by input keys.</dd>
 <dt><strong><code>**kwargs</code></strong></dt>
-<dd>additional fixed keyword arguments to pass to obj</dd>
+<dd>Additional fixed keyword arguments to pass to <code>func</code>.</dd>
 </dl>
 <h2 id="returns">Returns</h2>
 <dl>
-<dt><strong><code>new_vset</code></strong> :&ensp;<code>Vset</code></dt>
+<dt><strong><code>new_vset</code></strong> :&ensp;<code><a title="vflow.vset.Vset" href="vset.html#vflow.vset.Vset">Vset</a></code></dt>
 <dd>&nbsp;</dd>
 </dl></div>
 <details class="source">
 <summary>
 <span>Expand source code</span>
 </summary>
-<pre><code class="python">def build_vset(name: str, obj, *args, param_dict=None, reps: int = 1,
+<pre><code class="python">def build_vset(name: str, func, param_dict=None, reps: int = 1,
                is_async: bool = False, output_matching: bool = False,
                lazy: bool = False, cache_dir: str = None, verbose: bool = True,
                tracking_dir: str = None, **kwargs) -&gt; Vset:
-    &#34;&#34;&#34;Builds a Vset by currying callable obj with all combinations of parameters in param_dict.
+    &#34;&#34;&#34;Builds a new Vset by currying or instantiating callable `func` with all
+    combinations of parameters in `param_dict` and optional additional `**kwargs`.
 
     Parameters
     ----------
-    name: str
-        a name for the output Vset
-    obj: callable
-        a callable to use as the base for Vfuncs in the output Vset
-    param_dict: dict[str, list]
-        keys are obj kwarg names and values in the dict are lists of params to try
-    *args
-        additional fixed arguments to pass to obj
-    reps: int (optional)
-        the number of times to repeat the obj in the output Vset&#39;s modules for
-        each combination of params in param_dict
-    is_async: bool (optional)
-        if True, modules are computed asynchronously
-    output_matching: bool (optional)
-        if True, then output keys from Vset will be matched when used
-        in other Vsets
-    cache_dir: str (optional)
-        if provided, do caching and use cache_dir as the data store for
-        joblib.Memory
-    verbose : bool (optional)
-        if True, modules are named with param_dict items as tuples of str(&#34;param_name=param_val&#34;)
-    tracking_dir: str (optional)
-        if provided, use the mlflow.tracking api to log outputs as metrics
-        with params determined by input keys
+    name : str
+        A name for the output Vset.
+    func : callable
+        A callable to use as the base for Vfuncs in the output Vset. Can also be
+        a class object, in which case the class is immediately instantiated with
+        the parameter combinations from `param_dict`.
+    param_dict : dict[str, list], optional
+        A dict with string keys corresponding to argument names of `func` and
+        entries which are lists of values to pass to `func` at run time (or when
+        instantiating `func` if it&#39;s a class object).
+    reps : int, optional
+        The number of times to repeat `func` in the output Vset&#39;s modules for
+        each combination of the parameters in `param_dict`.
+    is_async : bool, optional
+        If True, modules are computed asynchronously.
+    output_matching : bool, optional
+        If True, then output keys from Vset will be matched when used in other
+        Vsets.
+    cache_dir : str, optional
+        If provided, do caching and use `cache_dir` as the data store for
+        joblib.Memory.
+    verbose : bool, optional
+        If True, modules are named with `param_dict` items as tuples of
+        str(&#34;param_name=param_val&#34;).
+    tracking_dir : str, optional
+        If provided, use the mlflow.tracking API to log outputs as metrics with
+        parameters determined by input keys.
     **kwargs
-        additional fixed keyword arguments to pass to obj
+        Additional fixed keyword arguments to pass to `func`.
 
     Returns
     -------
-    new_vset : Vset
+    new_vset : vflow.vset.Vset
+
     &#34;&#34;&#34;
     if param_dict is None:
         param_dict = {}
-    assert callable(obj), &#39;obj must be callable&#39;
+    assert callable(func), &#39;func must be callable&#39;
 
     vfuncs = []
     vkeys = []
@@ -349,13 +363,13 @@ with params determined by input keys</dd>
                 vkeys.append((f&#39;rep={i}&#39;, ) + vkey_tup)
             else:
                 vkeys.append(vkey_tup)
-            # check if obj is a class
-            if isinstance(obj, type):
-                # instantiate obj
-                vfuncs.append(Vfunc(module=obj(*args, **kwargs_dict), name=str(vkey_tup)))
+            # check if func is a class
+            if isinstance(func, type):
+                # instantiate func
+                vfuncs.append(Vfunc(module=func(**kwargs_dict), name=str(vkey_tup)))
             else:
-                # use partial to wrap obj
-                vfuncs.append(Vfunc(module=partial(obj, *args, **kwargs_dict), name=str(vkey_tup)))
+                # use partial to wrap func
+                vfuncs.append(Vfunc(module=partial(func, **kwargs_dict), name=str(vkey_tup)))
     if not verbose or (len(param_dict) == 0 and reps == 1):
         vkeys = None
     return Vset(name, vfuncs, is_async=is_async, module_keys=vkeys,
diff --git a/docs/index.html b/docs/index.html
index fea26aa..940264a 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -32,8 +32,7 @@
 <p align="center">
 <img src="https://img.shields.io/badge/license-mit-blue.svg" alt="mit license">
 <img src="https://img.shields.io/badge/python-3.6+-blue" alt="python3.6+">
-<a href="https://github.com/Yu-group/pcs-pipeline/actions"><img src="https://github.com/Yu-group/pcs-pipeline/workflows/tests/badge.svg" alt="tests"></a>
-<img src="https://img.shields.io/github/checks-status/Yu-group/pcs-pipeline/master" alt="checks">
+<a href="https://github.com/Yu-Group/veridical-flow/actions"><img src="https://github.com/Yu-Group/veridical-flow/workflows/tests/badge.svg" alt="tests"></a>
 <a href="https://joss.theoj.org/papers/10.21105/joss.03895"><img src="https://joss.theoj.org/papers/10.21105/joss.03895/status.svg" alt="joss"></a>
 <img src="https://img.shields.io/pypi/v/vflow?color=orange" alt="downloads">
 </p>
@@ -105,14 +104,23 @@ binary_metrics = binary_metrics_set.evaluate(preds_test, y_test)
 <p><a href="https://github.com/Yu-Group/rule-vetting">Clinical decision rule vetting</a></p>
 </blockquote>
 <h2 id="installation">Installation</h2>
-<p>Install with <code>pip install <a title="vflow" href="#vflow">vflow</a></code> (see <a href="https://github.com/Yu-Group/pcs-pipeline/blob/master/docs/troubleshooting.md">here</a> for help). For dev version (unstable), clone the repo and run <code>python setup.py develop</code> from the repo directory.</p>
+<p>Install with <code>pip install <a title="vflow" href="#vflow">vflow</a></code> (see <a href="https://github.com/Yu-Group/veridical-flow/blob/master/docs/troubleshooting.md">here</a> for help). For dev version (unstable), clone the repo and run <code>python setup.py develop</code> from the repo directory.</p>
 <h1 id="references">References</h1>
 <ul>
 <li>interface: easily build on <a href="https://scikit-learn.org/stable/index.html">scikit-learn</a> and <a href="https://dvc.org/">dvc</a> (data version control)</li>
 <li>computation: integration with <a href="https://www.ray.io/">ray</a> and caching with <a href="https://joblib.readthedocs.io/en/latest/">joblib</a></li>
 <li>tracking: <a href="https://mlflow.org/">mlflow</a></li>
-<li>pull requests very welcome! (see <a href="https://github.com/Yu-Group/pcs-pipeline/blob/master/docs/contributing.md">contributing.md</a>)</li>
+<li>pull requests very welcome! (see <a href="https://github.com/Yu-Group/veridical-flow/blob/master/docs/contributing.md">contributing.md</a>)</li>
 </ul>
+<pre><code class="language-r">@software{duncan2020vflow,
+   author = {Duncan, James and Kapoor, Rush and Agarwal, Abhineet and Singh, Chandan and Yu, Bin},
+   doi = {10.21105/joss.03895},
+   month = {1},
+   title = {{VeridicalFlow: a Python package for building trustworthy data science pipelines with PCS}},
+   url = {https://doi.org/10.21105/joss.03895},
+   year = {2022}
+}
+</code></pre>
 <details class="source">
 <summary>
 <span>Expand source code</span>
diff --git a/docs/vset.html b/docs/vset.html
index fa167d2..f745ee2 100644
--- a/docs/vset.html
+++ b/docs/vset.html
@@ -78,7 +78,6 @@ class Vset:
             If provided, use the `mlflow.tracking` api to log outputs as metrics
             with params determined by input keys.
 
-        .. todo:: include prev and next and change functions to include that.
         &#34;&#34;&#34;
         self.name = name
         self._fitted = False
@@ -464,11 +463,7 @@ in other Vsets</dd>
 <dt><strong><code>tracking_dir</code></strong> :&ensp;<code>str (optional)</code></dt>
 <dd>If provided, use the <code>mlflow.tracking</code> api to log outputs as metrics
 with params determined by input keys.</dd>
-</dl>
-<div class="admonition todo">
-<p class="admonition-title">TODO</p>
-<p>include prev and next and change functions to include that.</p>
-</div></div>
+</dl></div>
 <details class="source">
 <summary>
 <span>Expand source code</span>
@@ -502,7 +497,6 @@ with params determined by input keys.</dd>
             If provided, use the `mlflow.tracking` api to log outputs as metrics
             with params determined by input keys.
 
-        .. todo:: include prev and next and change functions to include that.
         &#34;&#34;&#34;
         self.name = name
         self._fitted = False
diff --git a/vflow/helpers.py b/vflow/helpers.py
index 6fd63f5..80a522b 100644
--- a/vflow/helpers.py
+++ b/vflow/helpers.py
@@ -33,48 +33,53 @@ def init_args(args_tuple: Union[tuple, list], names=None):
     return output_dicts
 
 
-def build_vset(name: str, obj, *args, param_dict=None, reps: int = 1,
+def build_vset(name: str, func, param_dict=None, reps: int = 1,
                is_async: bool = False, output_matching: bool = False,
                lazy: bool = False, cache_dir: str = None, verbose: bool = True,
                tracking_dir: str = None, **kwargs) -> Vset:
-    """Builds a Vset by currying callable obj with all combinations of parameters in param_dict.
+    """Builds a new Vset by currying or instantiating callable `func` with all
+    combinations of parameters in `param_dict` and optional additional `**kwargs`.
 
     Parameters
     ----------
-    name: str
-        a name for the output Vset
-    obj: callable
-        a callable to use as the base for Vfuncs in the output Vset
-    param_dict: dict[str, list]
-        keys are obj kwarg names and values in the dict are lists of params to try
-    *args
-        additional fixed arguments to pass to obj
-    reps: int (optional)
-        the number of times to repeat the obj in the output Vset's modules for
-        each combination of params in param_dict
-    is_async: bool (optional)
-        if True, modules are computed asynchronously
-    output_matching: bool (optional)
-        if True, then output keys from Vset will be matched when used
-        in other Vsets
-    cache_dir: str (optional)
-        if provided, do caching and use cache_dir as the data store for
-        joblib.Memory
-    verbose : bool (optional)
-        if True, modules are named with param_dict items as tuples of str("param_name=param_val")
-    tracking_dir: str (optional)
-        if provided, use the mlflow.tracking api to log outputs as metrics
-        with params determined by input keys
+    name : str
+        A name for the output Vset.
+    func : callable
+        A callable to use as the base for Vfuncs in the output Vset. Can also be
+        a class object, in which case the class is immediately instantiated with
+        the parameter combinations from `param_dict`.
+    param_dict : dict[str, list], optional
+        A dict with string keys corresponding to argument names of `func` and
+        entries which are lists of values to pass to `func` at run time (or when
+        instantiating `func` if it's a class object).
+    reps : int, optional
+        The number of times to repeat `func` in the output Vset's modules for
+        each combination of the parameters in `param_dict`.
+    is_async : bool, optional
+        If True, modules are computed asynchronously.
+    output_matching : bool, optional
+        If True, then output keys from Vset will be matched when used in other
+        Vsets.
+    cache_dir : str, optional
+        If provided, do caching and use `cache_dir` as the data store for
+        joblib.Memory.
+    verbose : bool, optional
+        If True, modules are named with `param_dict` items as tuples of
+        str("param_name=param_val").
+    tracking_dir : str, optional
+        If provided, use the mlflow.tracking API to log outputs as metrics with
+        parameters determined by input keys.
     **kwargs
-        additional fixed keyword arguments to pass to obj
+        Additional fixed keyword arguments to pass to `func`.
 
     Returns
     -------
-    new_vset : Vset
+    new_vset : vflow.vset.Vset
+
     """
     if param_dict is None:
         param_dict = {}
-    assert callable(obj), 'obj must be callable'
+    assert callable(func), 'func must be callable'
 
     vfuncs = []
     vkeys = []
@@ -95,13 +100,13 @@ def build_vset(name: str, obj, *args, param_dict=None, reps: int = 1,
                 vkeys.append((f'rep={i}', ) + vkey_tup)
             else:
                 vkeys.append(vkey_tup)
-            # check if obj is a class
-            if isinstance(obj, type):
-                # instantiate obj
-                vfuncs.append(Vfunc(module=obj(*args, **kwargs_dict), name=str(vkey_tup)))
+            # check if func is a class
+            if isinstance(func, type):
+                # instantiate func
+                vfuncs.append(Vfunc(module=func(**kwargs_dict), name=str(vkey_tup)))
             else:
-                # use partial to wrap obj
-                vfuncs.append(Vfunc(module=partial(obj, *args, **kwargs_dict), name=str(vkey_tup)))
+                # use partial to wrap func
+                vfuncs.append(Vfunc(module=partial(func, **kwargs_dict), name=str(vkey_tup)))
     if not verbose or (len(param_dict) == 0 and reps == 1):
         vkeys = None
     return Vset(name, vfuncs, is_async=is_async, module_keys=vkeys,
diff --git a/vflow/vset.py b/vflow/vset.py
index 4e57a3b..d5c7bff 100644
--- a/vflow/vset.py
+++ b/vflow/vset.py
@@ -47,7 +47,6 @@ class Vset:
             If provided, use the `mlflow.tracking` api to log outputs as metrics
             with params determined by input keys.
 
-        .. todo:: include prev and next and change functions to include that.
         """
         self.name = name
         self._fitted = False


Test patch:
diff --git a/tests/test_helpers.py b/tests/test_helpers.py
index 8736c53..6c71da8 100644
--- a/tests/test_helpers.py
+++ b/tests/test_helpers.py
@@ -4,6 +4,194 @@ from vflow.helpers import *
 
 class TestHelpers:
 
+    def test_build_vset(self):
+
+        def my_func(param1: str, param2: str, param3: str='a'):
+            return (param1, param2, param3)
+
+        param_dict1 = { 'param1': ['hello', 'foo'], 'param2': ['world', 'bar'] }
+        param_dict2 = { 'param1': ['hello'], 'param2': ['world', 'there']}
+
+        # my_func without param_dict
+        vset = build_vset("vset", my_func, param1='hello', param2='world', param3='b')
+        assert len(vset) == 1, \
+            'build_vset with my_func fails'
+        d_key = [key[0] for key in list(vset.modules.keys())][0]
+        assert d_key.value == 'vset_0', \
+            'build_vset with my_func fails'
+        d_keyword = [val.module.keywords for val in list(vset.modules.values())][0]
+        assert d_keyword == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func fails'
+
+        # my_func without param_dict, reps
+        vset = build_vset("vset", my_func, reps=2, param1='hello', param2='world', param3='b')
+        assert len(vset) == 2, \
+            'build_vset with my_func + reps fails'
+        d_keys = [key[0].value[0] for key in list(vset.modules.keys())]
+        assert d_keys[0] == 'rep=0', \
+            'build_vset with my_func + reps fails'
+        assert d_keys[1] == 'rep=1', \
+            'build_vset with my_func + reps fails'
+        d_keywords = [val.module.keywords for val in list(vset.modules.values())]
+        assert d_keywords[0] == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + reps fails'
+        assert d_keywords[1] == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + reps fails'
+
+        # my_func with param_dict1
+        vset = build_vset("vset", my_func, param_dict1, param3='b')
+        assert len(vset) == 4, \
+            'build_vset with my_func + param_dict1 fails'
+        d_keys = [key[0] for key in list(vset.modules.keys())]
+        assert d_keys[0].value == ('param1=hello', 'param2=world'), \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keys[1].value == ('param1=hello', 'param2=bar'), \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keys[2].value == ('param1=foo', 'param2=world'), \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keys[3].value == ('param1=foo', 'param2=bar'), \
+            'build_vset with my_func + param_dict1 fails'
+        d_keywords = [val.module.keywords for val in list(vset.modules.values())]
+        assert d_keywords[0] == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keywords[1] == {'param1': 'hello', 'param2': 'bar', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keywords[2] == {'param1': 'foo', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict1 fails'
+        assert d_keywords[3] == {'param1': 'foo', 'param2': 'bar', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict1 fails'
+
+        # my_func with param_dict2, reps
+        vset = build_vset("vset", my_func, param_dict2, reps=2, lazy=True, param3='b')
+        assert vset._lazy, \
+            'build_vset with my_func + param_dict2 + reps fails'
+        assert len(vset) == 4, \
+            'build_vset with my_func + param_dict2 + reps fails'
+        d_keys = [key[0] for key in list(vset.modules.keys())]
+        assert d_keys[0].value == ('rep=0', 'param1=hello', 'param2=world'), \
+            'build_vset with my_func + param_dict2 + reps fails'
+        assert d_keys[1].value == ('rep=1', 'param1=hello', 'param2=world'), \
+            'build_vset with my_func + param_dict2 + reps fails'
+        assert d_keys[2].value == ('rep=0', 'param1=hello', 'param2=there'), \
+            'build_vset with my_func + param_dict2 + reps fails'
+        assert d_keys[3].value == ('rep=1', 'param1=hello', 'param2=there'), \
+            'build_vset with my_func + param_dict2 + reps fails'
+        d_keywords = [val.module.keywords for val in list(vset.modules.values())]
+        assert d_keywords[0] == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict2 fails'
+        assert d_keywords[1] == {'param1': 'hello', 'param2': 'world', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict2 fails'
+        assert d_keywords[2] == {'param1': 'hello', 'param2': 'there', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict2 fails'
+        assert d_keywords[3] == {'param1': 'hello', 'param2': 'there', 'param3': 'b'}, \
+            'build_vset with my_func + param_dict2 fails'
+
+        class my_class:
+            def __init__(self, param1, param2, param3: str='a'):
+                self.param1 = param1
+                self.param2 = param2
+                self.param3 = param3
+
+            def fit(self, arg1: str):
+                self.arg1 = arg1
+
+        # my_class without param_dict
+        vset = build_vset("vset", my_class, param1='hello', param2='world', param3='b')
+        assert len(vset) == 1, \
+            'build_vset with my_class fails'
+        d_key = [key[0] for key in list(vset.modules.keys())][0]
+        assert d_key.value == 'vset_0', \
+            'build_vset with my_class fails'
+        d_val = [val.module for val in list(vset.modules.values())][0]
+        assert isinstance(d_val, my_class), \
+            'build_vset with my_class fails'
+        assert (d_val.param1, d_val.param2, d_val.param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class fails'
+
+        # my_class without param_dict, reps
+        vset = build_vset("vset", my_class, reps=2, param1='hello', param2='world', param3='b')
+        assert len(vset) == 2, \
+            'build_vset with my_class + reps fails'
+        d_keys = [key[0].value[0] for key in list(vset.modules.keys())]
+        assert d_keys[0] == 'rep=0', \
+            'build_vset with my_class + reps fails'
+        assert d_keys[1] == 'rep=1', \
+            'build_vset with my_class + reps fails'
+        d_vals = [val.module for val in list(vset.modules.values())]
+        assert isinstance(d_vals[0], my_class), \
+            'build_vset with my_class + reps fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + reps fails'
+        assert (d_vals[0].param1, d_vals[0].param2, d_vals[0].param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class + reps fails'
+        assert (d_vals[1].param1, d_vals[1].param2, d_vals[1].param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class + reps fails'
+
+        # my_class with param_dict1
+        vset = build_vset("vset", my_class, param_dict1, param3='b')
+        assert len(vset) == 4, \
+            'build_vset with my_class + param_dict1 fails'
+        d_keys = [key[0] for key in list(vset.modules.keys())]
+        assert d_keys[0].value == ('param1=hello', 'param2=world'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert d_keys[1].value == ('param1=hello', 'param2=bar'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert d_keys[2].value == ('param1=foo', 'param2=world'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert d_keys[3].value == ('param1=foo', 'param2=bar'), \
+            'build_vset with my_class + param_dict1 fails'
+        d_vals = [val.module for val in list(vset.modules.values())]
+        assert isinstance(d_vals[0], my_class), \
+            'build_vset with my_class + param_dict1 fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict1 fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict1 fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict1 fails'
+        assert (d_vals[0].param1, d_vals[0].param2, d_vals[0].param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert (d_vals[1].param1, d_vals[1].param2, d_vals[1].param3) == ('hello', 'bar', 'b'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert (d_vals[2].param1, d_vals[2].param2, d_vals[2].param3) == ('foo', 'world', 'b'), \
+            'build_vset with my_class + param_dict1 fails'
+        assert (d_vals[3].param1, d_vals[3].param2, d_vals[3].param3) == ('foo', 'bar', 'b'), \
+            'build_vset with my_class + param_dict1 fails'
+
+        # my_class with param_dict2, reps
+        vset = build_vset("vset", my_class, param_dict2, reps=2, lazy=True, param3='b')
+        assert vset._lazy, \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert len(vset) == 4, \
+            'build_vset with my_class + param_dict2 + reps fails'
+        d_keys = [key[0] for key in list(vset.modules.keys())]
+        assert d_keys[0].value == ('rep=0', 'param1=hello', 'param2=world'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert d_keys[1].value == ('rep=1', 'param1=hello', 'param2=world'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert d_keys[2].value == ('rep=0', 'param1=hello', 'param2=there'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert d_keys[3].value == ('rep=1', 'param1=hello', 'param2=there'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        d_vals = [val.module for val in list(vset.modules.values())]
+        assert isinstance(d_vals[0], my_class), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert isinstance(d_vals[1], my_class), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert (d_vals[0].param1, d_vals[0].param2, d_vals[0].param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert (d_vals[1].param1, d_vals[1].param2, d_vals[1].param3) == ('hello', 'world', 'b'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert (d_vals[2].param1, d_vals[2].param2, d_vals[2].param3) == ('hello', 'there', 'b'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+        assert (d_vals[3].param1, d_vals[3].param2, d_vals[3].param3) == ('hello', 'there', 'b'), \
+            'build_vset with my_class + param_dict2 + reps fails'
+
+
     def test_cum_acc_by_uncertainty(self):
         mean_dict = {'group_0': np.array([[0.2, 0.8], [0.25, 0.75], [0.1, 0.9]]),
                      'group_1': np.array([[0.4, 0.6], [0.5, 0.5], [0.45, 0.55]])}
@@ -22,4 +210,4 @@ class TestHelpers:
         assert_equal(c0[0], [1, 1/2, 2/3])
         assert_equal(c0[1], [0, 0, 1/3])
         assert_equal(idx0[0], [2, 0, 1])
-        assert_equal(idx0[1], [0, 1, 2])
\ No newline at end of file
+        assert_equal(idx0[1], [0, 1, 2])