Repository: SDXorg/pysd (language: python)

Issue:
Split Vensim views in separate Python files
This pull requests gives the user the option to get a translated model file organised by Vensim views. In the translated model, Vensim views can be seen as Python modules.

This option is controlled by the `split_modules` argument in the `read_vensim` function.

By default, the argument is set to False; hence, unless explicitly modified, the user gets the usual behaviour. 

This new functionality has been added by adding a new grammar to parse the necessary bits from the Vensim sketch.


The option to split the model in views is particularly interesting for large models with tens of views. Translating those models into a single file may make the resulting Python model difficult to read and maintain.

In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_modules` to True would create the following tree inside the directory where the `.mdl` model is located:

| main-folder
| ├── modules_many_views_model
| │ ├── _modules.json
| │ ├── view_1.py
| │ ├── view_2.py
| │ └── view_3.py
| ├── _namespace_many_views_model.json
| ├── _subscripts_dict_many_views_model.json
| ├── many_views_model.py
|
|
If macros are present, they will be self-contained in files named as the macro itself. The macro inner variables will be placed inside the module that corresponds with the view in which they were defined.


Split Vensim views in separate Python files
This pull requests gives the user the option to get a translated model file organised by Vensim views. In the translated model, Vensim views can be seen as Python modules.

This option is controlled by the `split_modules` argument in the `read_vensim` function.

By default, the argument is set to False; hence, unless explicitly modified, the user gets the usual behaviour. 

This new functionality has been added by adding a new grammar to parse the necessary bits from the Vensim sketch.


The option to split the model in views is particularly interesting for large models with tens of views. Translating those models into a single file may make the resulting Python model difficult to read and maintain.

In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_modules` to True would create the following tree inside the directory where the `.mdl` model is located:

| main-folder
| ├── modules_many_views_model
| │ ├── _modules.json
| │ ├── view_1.py
| │ ├── view_2.py
| │ └── view_3.py
| ├── _namespace_many_views_model.json
| ├── _subscripts_dict_many_views_model.json
| ├── many_views_model.py
|
|
If macros are present, they will be self-contained in files named as the macro itself. The macro inner variables will be placed inside the module that corresponds with the view in which they were defined.




Discussion / hints:
Hello @rogersamso! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:













* In the file [`pysd/py_backend/vensim/vensim2py.py`](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py):

> [Line 494:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L494): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (117 > 79 characters)
> [Line 498:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L498): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (115 > 79 characters)
> [Line 499:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L499): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (252 > 79 characters)
> [Line 512:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L512): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (190 > 79 characters)
> [Line 533:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L533): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (156 > 79 characters)















##### Comment last updated at 2021-07-07 07:11:31 UTC

[![Coverage Status](https://coveralls.io/builds/41172186/badge)](https://coveralls.io/builds/41172186)

Coverage increased (+0.04%) to 98.543% when pulling **ce3f35967063814c285df341c0a3089b3e904086 on rogersamso:building_split_model_merge** into **86b109fc99669dc1b291f7bd8aeaaae159961e74 on JamesPHoughton:master**.

Other changes included in this PR:
- LICENSE added to MANIFEST as [suggested by conda-forge maintainer](https://github.com/conda-forge/staged-recipes/pull/15455#issuecomment-873163068)
- Added conda version badge to README and installation instructions to docs
- Splitting option working with CLI as well
- Add some tests for subscripts range parsing error
> Hello @rogersamso! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:
> 
>     * In the file [`pysd/py_backend/vensim/vensim2py.py`](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py):
> 
> 
> > [Line 503:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L503): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (111 > 79 characters)
> > [Line 507:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L507): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (115 > 79 characters)
> > [Line 508:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L508): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (252 > 79 characters)
> > [Line 521:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L521): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (190 > 79 characters)
> > [Line 542:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L542): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (156 > 79 characters)
> 
> ##### Comment last updated at 2021-07-05 12:12:28 UTC

Sorry about this. I think a good solution for it would be to move all grammars into a separate file.
Hello @rogersamso! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:













* In the file [`pysd/py_backend/vensim/vensim2py.py`](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py):

> [Line 494:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L494): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (117 > 79 characters)
> [Line 498:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L498): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (115 > 79 characters)
> [Line 499:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L499): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (252 > 79 characters)
> [Line 512:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L512): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (190 > 79 characters)
> [Line 533:80](https://github.com/JamesPHoughton/pysd/blob/ce3f35967063814c285df341c0a3089b3e904086/pysd/py_backend/vensim/vensim2py.py#L533): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (156 > 79 characters)















##### Comment last updated at 2021-07-07 07:11:31 UTC

[![Coverage Status](https://coveralls.io/builds/41172186/badge)](https://coveralls.io/builds/41172186)

Coverage increased (+0.04%) to 98.543% when pulling **ce3f35967063814c285df341c0a3089b3e904086 on rogersamso:building_split_model_merge** into **86b109fc99669dc1b291f7bd8aeaaae159961e74 on JamesPHoughton:master**.

Other changes included in this PR:
- LICENSE added to MANIFEST as [suggested by conda-forge maintainer](https://github.com/conda-forge/staged-recipes/pull/15455#issuecomment-873163068)
- Added conda version badge to README and installation instructions to docs
- Splitting option working with CLI as well
- Add some tests for subscripts range parsing error
> Hello @rogersamso! Thanks for updating this PR. We checked the lines you've touched for [PEP 8](https://www.python.org/dev/peps/pep-0008) issues, and found:
> 
>     * In the file [`pysd/py_backend/vensim/vensim2py.py`](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py):
> 
> 
> > [Line 503:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L503): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (111 > 79 characters)
> > [Line 507:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L507): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (115 > 79 characters)
> > [Line 508:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L508): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (252 > 79 characters)
> > [Line 521:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L521): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (190 > 79 characters)
> > [Line 542:80](https://github.com/JamesPHoughton/pysd/blob/cdf4499d16525df731895ff61c86f2af6114baf5/pysd/py_backend/vensim/vensim2py.py#L542): [E501](https://duckduckgo.com/?q=pep8%20E501) line too long (156 > 79 characters)
> 
> ##### Comment last updated at 2021-07-05 12:12:28 UTC

Sorry about this. I think a good solution for it would be to move all grammars into a separate file.

Fix patch:
diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst
index b8d5f7a8..04f56341 100644
--- a/docs/advanced_usage.rst
+++ b/docs/advanced_usage.rst
@@ -42,16 +42,16 @@ We can substitute this function directly for the heat_loss_to_room model compone
 If you want to replace a subscripted variable, you need to ensure that the output from the new function is the same as the previous one. You can check the current coordinates and dimensions of a component by using :py:data:`.get_coords(variable_name)` as it is explained in :doc:`basic usage <../basic_usage>`.
 
 
-Splitting Vensim views in different files
------------------------------------------
-In order to replicate the Vensim views in translated models, the user can set the `split_modules` argument to True in the :py:func:`read_vensim` function::
+Splitting Vensim views in separate Python files (modules)
+---------------------------------------------------------
+In order to replicate the Vensim views in translated models, the user can set the `split_views` argument to True in the :py:func:`read_vensim` function::
 
-   read_vensim("many_views_model.mdl", split_modules=True)
+   read_vensim("many_views_model.mdl", split_views=True)
 
 
 The option to split the model in views is particularly interesting for large models with tens of views. Translating those models into a single file may make the resulting Python model difficult to read and maintain.
 
-In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_modules` to True would create the following tree inside the directory where the `.mdl` model is located:
+In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_views` to True would create the following tree inside the directory where the `.mdl` model is located:
 
 | main-folder
 | ├── modules_many_views_model
@@ -64,6 +64,13 @@ In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3
 | ├── many_views_model.py
 |
 |
+
+.. note ::
+    Often, modelers wish to organise views further. To that end, a common practice is to include a particular character in the View name to indicate that what comes after it is the name of the subview. For instance, we could name one view as `ENERGY.Supply` and another one as `ENERGY.Demand`.
+    In that particular case, setting the `subview_sep` kwarg equal to `"."`, as in the code below, would name the translated views as `demand.py` and `supply.py` and place them inside the `ENERGY` folder::
+    
+      read_vensim("many_views_model.mdl", split_views=True, subview_sep=".")
+
 If macros are present, they will be self-contained in files named as the macro itself. The macro inner variables will be placed inside the module that corresponds with the view in which they were defined.
 
 
diff --git a/pysd/_version.py b/pysd/_version.py
index 2d986fc5..0a0a43a5 100644
--- a/pysd/_version.py
+++ b/pysd/_version.py
@@ -1,1 +1,1 @@
-__version__ = "1.8.1"
+__version__ = "1.9.0"
diff --git a/pysd/cli/main.py b/pysd/cli/main.py
index e5a4c3cd..216b8e66 100644
--- a/pysd/cli/main.py
+++ b/pysd/cli/main.py
@@ -26,7 +26,7 @@ def main(args):
     options = parser.parse_args(args)
 
     model = load(options.model_file, options.missing_values,
-                 options.split_modules)
+                 options.split_views, subview_sep=options.subview_sep)
 
     if not options.run:
         print("\nFinished!")
@@ -44,7 +44,7 @@ def main(args):
     sys.exit()
 
 
-def load(model_file, missing_values, split_modules):
+def load(model_file, missing_values, split_views, **kwargs):
     """
     Translate and load model file.
 
@@ -53,6 +53,19 @@ def load(model_file, missing_values, split_modules):
     model_file: str
         Vensim, Xmile or PySD model file.
 
+    split_views: bool (optional)
+        If True, the sketch is parsed to detect model elements in each
+        model view, and then translate each view in a separate python
+        file. Setting this argument to True is recommended for large
+        models split in many different views. Default is False.
+
+    **kwargs: (optional)
+        Additional keyword arguments.
+        subview_sep:(str)
+            Character used to separate views and subviews. If provided,
+            and split_views=True, each submodule will be placed inside the
+            folder of the parent view.
+
     Returns
     -------
     pysd.model
@@ -62,7 +75,7 @@ def load(model_file, missing_values, split_modules):
         print("\nTranslating model file...\n")
         return pysd.read_vensim(model_file, initialize=False,
                                 missing_values=missing_values,
-                                split_modules=split_modules)
+                                split_views=split_views, **kwargs)
     elif model_file.lower().endswith('.xmile'):
         print("\nTranslating model file...\n")
         return pysd.read_xmile(model_file, initialize=False,
diff --git a/pysd/cli/parser.py b/pysd/cli/parser.py
index a38929eb..df734e1d 100644
--- a/pysd/cli/parser.py
+++ b/pysd/cli/parser.py
@@ -218,9 +218,9 @@ def __call__(self, parser, namespace, values, option_string=None):
          '--saveper will be ignored')
 
 
-###################
-# Model arguments #
-###################
+#########################
+# Translation arguments #
+#########################
 
 trans_arguments = parser.add_argument_group(
     'translation arguments',
@@ -233,10 +233,18 @@ def __call__(self, parser, namespace, values, option_string=None):
          'it does not run it after translation')
 
 trans_arguments.add_argument(
-    '--split-modules', dest='split_modules',
+    '--split-views', dest='split_views',
     action='store_true', default=False,
     help='parse the sketch to detect model elements in each model view,'
-         ' and then translate each view in a separate python file')
+         ' and then translate each view in a separate Python file')
+
+trans_arguments.add_argument(
+    '--subview-sep', dest='subview_sep',
+    action='store', type=str, default="", metavar='STRING',
+    help='further division of views split in subviews, by identifying the'
+         'separator string in the view name, only availabe if --split-views'
+         ' is used')
+
 
 #######################
 # Warnings and errors #
diff --git a/pysd/py_backend/builder.py b/pysd/py_backend/builder.py
index 4fe22e28..36204229 100644
--- a/pysd/py_backend/builder.py
+++ b/pysd/py_backend/builder.py
@@ -117,20 +117,19 @@ def reset(cls):
 build_names = set()
 
 
-def build_modular_model(
-    elements, subscript_dict, namespace, main_filename, elements_per_module
-):
+def build_modular_model(elements, subscript_dict, namespace, main_filename,
+                        elements_per_view):
 
     """
     This is equivalent to the build function, but is used when the
-    split_modules parameter is set to True in the read_vensim function.
+    split_views parameter is set to True in the read_vensim function.
     The main python model file will be named as the original model file,
     and stored in the same folder. The modules will be stored in a separate
     folder named modules + original_model_name. Three extra json files will
     be generated, containing the namespace, subscripts_dict and the module
     names plus the variables included in each module, respectively.
 
-    Setting split_modules=True is recommended for large models with many
+    Setting split_views=True is recommended for large models with many
     different views.
 
     Parameters
@@ -164,32 +163,57 @@ def build_modular_model(
     # create modules directory if it does not exist
     os.makedirs(modules_dir, exist_ok=True)
 
-    modules_list = elements_per_module.keys()
+    # check if there are subviews or only main views
+    subviews = all(isinstance(n, dict) for n in elements_per_view.values())
+
+    all_views = elements_per_view.keys()
     # creating the rest of files per module (this needs to be run before the
     # main module, as it updates the import_modules)
     processed_elements = []
-    for module in modules_list:
-        module_elems = []
-        for element in elements:
-            if element.get("py_name", None) in elements_per_module[module] or\
-               element.get("parent_name", None) in elements_per_module[module]:
-                module_elems.append(element)
+    for view_name in all_views:
+        view_elems = []
+        if not subviews:  # only main views
+            for element in elements:
+                if element.get("py_name", None) in \
+                   elements_per_view[view_name] or \
+                   element.get("parent_name", None) in \
+                   elements_per_view[view_name]:
+                    view_elems.append(element)
+
+            _build_separate_module(view_elems, subscript_dict, view_name,
+                                   modules_dir)
+
+        else:
+            # create subdirectory
+            view_dir = os.path.join(modules_dir, view_name)
+            os.makedirs(view_dir, exist_ok=True)
 
-        _build_separate_module(module_elems, subscript_dict,
-                               module, modules_dir)
+            for subview_name in elements_per_view[view_name].keys():
+                subview_elems = []
+                for element in elements:
+                    if element.get("py_name", None) in \
+                       elements_per_view[view_name][subview_name] or \
+                       element.get("parent_name", None) in \
+                       elements_per_view[view_name][subview_name]:
+                        subview_elems.append(element)
 
-        processed_elements += module_elems
+                _build_separate_module(subview_elems, subscript_dict,
+                                       subview_name, view_dir)
+                view_elems += subview_elems
+
+        processed_elements += view_elems
 
     # the unprocessed will go in the main file
     unprocessed_elements = [
         element for element in elements if element not in processed_elements
     ]
     # building main file using the build function
-    _build_main_module(unprocessed_elements, subscript_dict, main_filename)
+    _build_main_module(unprocessed_elements, subscript_dict,
+                       main_filename, subviews)
 
     # create json file for the modules and corresponding model elements
     with open(os.path.join(modules_dir, "_modules.json"), "w") as outfile:
-        json.dump(elements_per_module, outfile, indent=4, sort_keys=True)
+        json.dump(elements_per_view, outfile, indent=4, sort_keys=True)
 
     # create single namespace in a separate json file
     with open(
@@ -203,13 +227,11 @@ def build_modular_model(
     ) as outfile:
         json.dump(subscript_dict, outfile, indent=4, sort_keys=True)
 
-    return None
-
 
-def _build_main_module(elements, subscript_dict, file_name):
+def _build_main_module(elements, subscript_dict, file_name, subviews):
     """
     Constructs and writes the python representation of the main model
-    module, when the split_modules=True in the read_vensim function.
+    module, when the split_views=True in the read_vensim function.
 
     Parameters
     ----------
@@ -230,6 +252,10 @@ def _build_main_module(elements, subscript_dict, file_name):
     file_name: str
         Path of the file where the main module will be stored.
 
+    subviews: bool
+        True or false depending on whether the views are split in subviews or
+        not.
+
     Returns
     -------
     None or text: None or str
@@ -276,15 +302,25 @@ def _build_main_module(elements, subscript_dict, file_name):
 
     text += _get_control_vars(control_vars)
 
-    text += textwrap.dedent("""
-    # load modules from the modules_%(outfile)s directory
-    for module in _modules:
-        exec(open_module(_root, "%(outfile)s", module))
+    if not subviews:
+        text += textwrap.dedent("""
+        # load modules from the modules_%(outfile)s directory
+        for module in _modules:
+            exec(open_module(_root, "%(outfile)s", module))
 
-    """ % {
-        "outfile": os.path.basename(file_name).split(".")[0],
-
-    })
+        """ % {
+            "outfile": os.path.basename(file_name).split(".")[0],
+        })
+    else:
+        text += textwrap.dedent("""
+        # load submodules from subdirs in modules_%(outfile)s directory
+        for mod_name, mod_submods in _modules.items():
+            for submod_name in mod_submods.keys():
+                exec(open_module(_root, "%(outfile)s", mod_name, submod_name))
+
+        """ % {
+            "outfile": os.path.basename(file_name).split(".")[0],
+        })
 
     text += funcs
     text = black.format_file_contents(text, fast=True, mode=black.FileMode())
@@ -292,10 +328,6 @@ def _build_main_module(elements, subscript_dict, file_name):
     # Needed for various sessions
     build_names.clear()
 
-    # this is used for testing
-    if file_name == "return":
-        return text
-
     with open(file_name, "w", encoding="UTF-8") as out:
         out.write(text)
 
@@ -303,7 +335,7 @@ def _build_main_module(elements, subscript_dict, file_name):
 def _build_separate_module(elements, subscript_dict, module_name, module_dir):
     """
     Constructs and writes the python representation of a specific model
-    module, when the split_modules=True in the read_vensim function
+    module, when the split_views=True in the read_vensim function
 
     Parameters
     ----------
@@ -1544,14 +1576,16 @@ def add_ext_data(identifier, file_name, tab, time_row_or_col, cell, subs,
         List of element construction dictionaries for the builder to assemble.
 
     """
-    coords = utils.make_coord_dict(subs, subscript_dict, terse=False)
+    Imports.add("external", "ExtData")
+
+    coords = utils.simplify_subscript_input(
+        utils.make_coord_dict(subs, subscript_dict, terse=False),
+        subscript_dict, return_full=False)
     keyword = (
         "'%s'" % keyword.strip(":").lower() if isinstance(keyword, str) else
         keyword)
     name = utils.make_python_identifier("_ext_data_%s" % identifier)[0]
 
-    Imports.add("external", "ExtData")
-
     # Check if the object already exists
     if name in build_names:
         # Create a new py_name with ADD_# ending
@@ -1626,7 +1660,9 @@ def add_ext_constant(identifier, file_name, tab, cell, subs, subscript_dict):
     """
     Imports.add("external", "ExtConstant")
 
-    coords = utils.make_coord_dict(subs, subscript_dict, terse=False)
+    coords = utils.simplify_subscript_input(
+        utils.make_coord_dict(subs, subscript_dict, terse=False),
+        subscript_dict, return_full=False)
     name = utils.make_python_identifier("_ext_constant_%s" % identifier)[0]
 
     # Check if the object already exists
@@ -1663,9 +1699,8 @@ def add_ext_constant(identifier, file_name, tab, cell, subs, subscript_dict):
     return "%s()" % external["py_name"], [external]
 
 
-def add_ext_lookup(
-    identifier, file_name, tab, x_row_or_col, cell, subs, subscript_dict
-):
+def add_ext_lookup(identifier, file_name, tab, x_row_or_col, cell,
+                   subs, subscript_dict):
     """
     Constructs a external object for handling Vensim's GET XLS LOOKUPS and
     GET DIRECT LOOKUPS functionality.
@@ -1707,7 +1742,9 @@ def add_ext_lookup(
     """
     Imports.add("external", "ExtLookup")
 
-    coords = utils.make_coord_dict(subs, subscript_dict, terse=False)
+    coords = utils.simplify_subscript_input(
+        utils.make_coord_dict(subs, subscript_dict, terse=False),
+        subscript_dict, return_full=False)
     name = utils.make_python_identifier("_ext_lookup_%s" % identifier)[0]
 
     # Check if the object already exists
diff --git a/pysd/py_backend/external.py b/pysd/py_backend/external.py
index 436799bd..10035aed 100644
--- a/pysd/py_backend/external.py
+++ b/pysd/py_backend/external.py
@@ -405,13 +405,20 @@ def _initialize_data(self, element_type):
                   + "\t{}:\t{}\n".format(series_across, self.x_row_or_col)
                   )
 
-        # Check if the lookup/time dimension is strictly monotonous
-        if np.any(np.diff(series) <= 0) and self.missing != "keep":
-            raise ValueError(self.py_name + "\n"
-                  + "Dimension given in:\n"
-                  + self._file_sheet
-                  + "\t{}:\t{}\n".format(series_across, self.x_row_or_col)
-                  + " is not strictly monotonous")
+        # reorder data with increasing series
+        if not np.all(np.diff(series) > 0) and self.missing != "keep":
+            order = np.argsort(series)
+            series = series[order]
+            data = data[order]
+            # Check if the lookup/time dimension is well defined
+            if np.any(np.diff(series) == 0):
+                raise ValueError(self.py_name + "\n"
+                                 + "Dimension given in:\n"
+                                 + self._file_sheet
+                                 + "\t{}:\t{}\n".format(
+                                    series_across, self.x_row_or_col)
+                                 + " has repeated values")
+
 
         # Check for missing values in data
         if np.any(np.isnan(data)) and self.missing != "keep":
diff --git a/pysd/py_backend/utils.py b/pysd/py_backend/utils.py
index 0df26058..f9dab9ab 100644
--- a/pysd/py_backend/utils.py
+++ b/pysd/py_backend/utils.py
@@ -714,6 +714,49 @@ def round_(x):
     return round(x)
 
 
+def simplify_subscript_input(coords, subscript_dict,
+                             return_full=True):
+    """
+    Parameters
+    ----------
+    coords: dict
+        Coordinates to write in the model file.
+
+    subscript_dict: dict
+        The subscript dictionary of the model file.
+
+    return_full: bool (optional)
+        If True the when coords == subscript_dict, '_subscript_dict'
+        will be returned. Default is True
+
+    partial: bool (optional)
+        If True "_subscript_dict" will not be returned as possible dict.
+        Used when subscript_dict is not the full dictionary. Default is False.
+
+    Returns
+    -------
+    coords: str
+        The equations to generate the coord dicttionary in the model file.
+
+    """
+
+    if coords == subscript_dict and return_full:
+        # variable defined with all the subscripts
+        return "_subscript_dict"
+
+    coordsp = []
+    for dim, coord in coords.items():
+        # find dimensions can be retrieved from _subscript_dict
+        if coord == subscript_dict[dim]:
+            # use _subscript_dict
+            coordsp.append(f"'{dim}': _subscript_dict['{dim}']")
+        else:
+            # write whole dict
+            coordsp.append(f"'{dim}': {coord}")
+
+    return "{" + ", ".join(coordsp) + "}"
+
+
 def add_entries_underscore(*dictionaries):
     """
     Expands dictionaries adding new keys underscoring the white spaces
@@ -785,10 +828,10 @@ def load_model_data(root_dir, model_name):
     return namespace, subscripts, modules
 
 
-def open_module(root_dir, model_name, module):
+def open_module(root_dir, model_name, module, submodule=None):
     """
     Used to load model modules from the main model file, when
-    split_modules=True in the read_vensim function.
+    split_views=True in the read_vensim function.
 
     Parameters
     ----------
@@ -799,17 +842,46 @@ def open_module(root_dir, model_name, module):
         Name of the model without file type extension (e.g. "my_model").
 
     module: str
-        Name of the module to open.
+        Name of the module folder or file to open.
+
+    sub_module: str (optional)
+        Name of the submodule to open.
 
     Returns
     -------
     str:
         Model file content.
-
     """
+    if not submodule:
+        rel_file_path = module + ".py"
+    else:
+        rel_file_path = os.path.join(module, submodule + ".py")
+
     return open(
-        os.path.join(root_dir, "modules_" + model_name, module + ".py")
-        ).read()
+        os.path.join(root_dir, "modules_" + model_name, rel_file_path)).read()
+
+
+def clean_file_names(*args):
+    """
+    Removes special characters and makes clean file names
+
+    Parameters
+    ----------
+    *args: tuple
+        Any number of strings to to clean
+
+    Returns
+    -------
+    clean: list
+        List containing the clean strings
+    """
+    clean = []
+    for name in args:
+        clean.append(re.sub(
+                            r"[\W]+", "", name.replace(" ", "_")
+                            ).lstrip("0123456789")
+                     )
+    return clean
 
 
 class ProgressBar:
diff --git a/pysd/py_backend/vensim/vensim2py.py b/pysd/py_backend/vensim/vensim2py.py
index 1eb1ed79..ceaa4cb4 100644
--- a/pysd/py_backend/vensim/vensim2py.py
+++ b/pysd/py_backend/vensim/vensim2py.py
@@ -4,6 +4,7 @@
 knowledge of vensim syntax should be here.
 """
 
+from inspect import cleandoc
 import os
 import re
 import warnings
@@ -491,12 +492,12 @@ def parse_sketch_line(sketch_line, namespace):
 
     sketch_grammar = _include_common_grammar(
         r"""
-    line = var_definition / module_intro / module_title / module_definition / arrow / flow / other_objects / anything
-    module_intro = ~r"\s*Sketch.*?names$" / ~r"^V300.*?ignored$"
-    module_title = "*" module_name
-    module_name = ~r"(?<=\*)[^\n]+$"
-    module_definition = "$" color "," digit "," font_properties "|" ( ( color / ones_and_dashes ) "|")* module_code
-    var_definition = var_code "," var_number "," var_name "," position "," var_box_type "," arrows_in_allowed "," hide_level "," var_face "," var_word_position "," var_thickness "," var_rest_conf ","? ( ( ones_and_dashes / color) ",")* font_properties?
+    line = var_definition / view_intro / view_title / view_definition / arrow / flow / other_objects / anything
+    view_intro = ~r"\s*Sketch.*?names$" / ~r"^V300.*?ignored$"
+    view_title = "*" view_name
+    view_name = ~r"(?<=\*)[^\n]+$"
+    view_definition = "$" color "," digit "," font_properties "|" ( ( color / ones_and_dashes ) "|")* view_code
+    var_definition = var_code "," var_number "," var_name "," position "," var_box_type "," arrows_in_allowed "," hide_level "," var_face "," var_word_position "," var_thickness "," var_rest_conf ","? ( ( ones_and_dashes / color) ",")* font_properties? ","? extra_bytes?
     # elements used in a line defining the properties of a variable or stock
     var_name = element
     var_name = ~r"(?<=,)[^,]+(?=,)"
@@ -509,6 +510,7 @@ def parse_sketch_line(sketch_line, namespace):
     var_word_position = ~r"(?<=,)\-*\d+(?=,)"
     var_thickness = digit
     var_rest_conf = digit "," ~r"\d+"
+    extra_bytes = ~r"\d+,\d+,\d+,\d+,\d+,\d+" # required since Vensim 8.2.1
     arrow = arrow_code "," digit "," origin_var "," destination_var "," (digit ",")+ (ones_and_dashes ",")?  ((color ",") / ("," ~r"\d+") / (font_properties "," ~r"\d+"))* "|(" position ")|"
     # arrow origin and destination (this may be useful if further
     # parsing is required)
@@ -537,7 +539,7 @@ def parse_sketch_line(sketch_line, namespace):
     var_code = ~r"^10(?=,)"
     multipurpose_code = ~r"^12(?=,)" # source, sink, plot, comment
     other_objects_code = ~r"^(30|31)(?=,)"
-    module_code = ~r"\d+" "," digit "," digit "," ~r"\d+" # code at
+    view_code = ~r"\d+" "," digit "," digit "," ~r"\d+" # code at
 
     digit = ~r"(?<=,)\d+(?=,)" # comma separated value/s
     ones_and_dashes = ~r"\-1\-\-1\-\-1"
@@ -550,22 +552,22 @@ def parse_sketch_line(sketch_line, namespace):
     class SketchParser(parsimonious.NodeVisitor):
         def __init__(self, ast, namespace):
             self.namespace = namespace
-            self.module_or_var = {"variable_name": "", "module_name": ""}
+            self.view_or_var = {"variable_name": "", "view_name": ""}
             self.visit(ast)
 
-        def visit_module_name(self, n, vc):
-            self.module_or_var["module_name"] = n.text
+        def visit_view_name(self, n, vc):
+            self.view_or_var["view_name"] = n.text
 
         def visit_var_definition(self, n, vc):
             if int(vc[10]) % 2 != 0:  # not a shadow variable
-                self.module_or_var["variable_name"] = self.namespace.get(vc[4],
-                                                                         "")
+                self.view_or_var["variable_name"] = self.namespace.get(vc[4],
+                                                                       "")
 
         def generic_visit(self, n, vc):
             return "".join(filter(None, vc)) or n.text or ""
-
+        
     tree = parser.parse(sketch_line)
-    return SketchParser(tree, namespace=namespace).module_or_var
+    return SketchParser(tree, namespace=namespace).view_or_var
 
 
 def parse_units(units_str):
@@ -1263,43 +1265,17 @@ def visit_array(self, n, vc):
                 else:
                     datastr = n.text
 
-                # Following implementation makes cleaner the model file
-                if list(coords) == element["subs"]:
-                    if len(coords) == len(subscript_dict):
-                        # variable is defined with all subscrips
-                        return builder.build_function_call(
-                            functions_utils["DataArray"],
-                            [datastr, "_subscript_dict", repr(list(coords))],
-                        )
-
-                from_dict, no_from_dict = [], {}
-                for coord, sub in zip(coords, element["subs"]):
-                    # find dimensions can be retrieved from _subscript_dict
-                    if coord == sub:
-                        from_dict.append(coord)
-                    else:
-                        no_from_dict[coord] = coords[coord]
-
-                if from_dict and no_from_dict:
-                    # some dimensons can be retrieved from _subscript_dict
-                    coordsp = (
-                        "{**{dim: _subscript_dict[dim] for dim in %s}, "
-                        % from_dict
-                        + repr(no_from_dict)[1:]
-                    )
-                elif from_dict:
-                    # all dimensons can be retrieved from _subscript_dict
-                    coordsp = "{dim: _subscript_dict[dim] for dim in %s}" \
-                               % from_dict
-                else:
-                    # no dimensons can be retrieved from _subscript_dict
-                    coordsp = repr(no_from_dict)
+                # Create a cleaner dictionary of coords using _subscript_dict
+                # when possible
+                utils.simplify_subscript_input(coords, subscript_dict,
+                                               return_full=True)
 
                 return builder.build_function_call(
-                    functions_utils["DataArray"], [datastr, coordsp, repr(list(
-                        coords))]
-                )
-
+                    functions_utils["DataArray"],
+                    [datastr,
+                     utils.simplify_subscript_input(coords, subscript_dict),
+                     repr(list(coords))]
+                    )
             else:
                 return n.text.replace(" ", "")
 
@@ -1477,7 +1453,7 @@ def generic_visit(self, n, vc):
     )
 
 
-def translate_section(section, macro_list, sketch, root_path):
+def translate_section(section, macro_list, sketch, root_path, subview_sep=""):
 
     model_elements = get_model_elements(section["string"])
 
@@ -1577,16 +1553,15 @@ def translate_section(section, macro_list, sketch, root_path):
     ]
 
     # macros are built in their own separate files, and their inputs and
-    # outputs are put in modules
+    # outputs are put in views/subviews
     if sketch and (section["name"] == "_main_"):
-
-        module_elements = _classify_elements_by_module(sketch, namespace)
-
-        if len(module_elements.keys()) == 1:
+        module_elements = _classify_elements_by_module(sketch, namespace,
+                                                       subview_sep)
+        if (len(module_elements.keys()) == 1) \
+           and (isinstance(module_elements[list(module_elements)[0]], list)):
             warnings.warn(
-                "Only one module was detected. The model will be built "
-                "in a single file."
-            )
+                "Only a single view with no subviews was detected. The model"
+                " will be built in a single file.")
         else:
             builder.build_modular_model(
                 build_elements,
@@ -1603,11 +1578,11 @@ def translate_section(section, macro_list, sketch, root_path):
     return section["file_name"]
 
 
-def _classify_elements_by_module(sketch, namespace):
+def _classify_elements_by_module(sketch, namespace, subview_sep):
     """
     Takes the Vensim sketch as a string, parses it (line by line) and
-    returns a list of the model elements that belong to each vensim view
-    (here we call the modules).
+    returns a dictionary containing the views/subviews as keys and the model
+    elements that belong to each view/subview inside a list as values.
 
     Parameters
     ----------
@@ -1618,48 +1593,68 @@ def _classify_elements_by_module(sketch, namespace):
         Translation from original model element names (keys) to python
         safe function identifiers (values).
 
+    subview_sep: str
+        Character used to split view names into view + subview
+        (e.g. if a view is named ENERGY.Demand and suview_sep is set to ".",
+        then the Demand subview would be placed inside the ENERGY directory)
+
     Returns
     -------
-    module_elements_: dict
-        Dictionary containing view (module) names as keys and a list of
-        the corresponding variables as values.
+    views_dict: dict
+        Dictionary containing view names as keys and a list of the
+        corresponding variables as values. If the subview_sep is defined,
+        then the dictionary will have a nested dict containing the subviews.
 
     """
-    # TODO how about macros??? are they also put in the sketch?
-
-    # splitting the sketch in different modules
+    # split the sketch in different views
     sketch = list(map(lambda x: x.strip(), sketch.split("\\\\\\---/// ")))
 
-    modules_list = []  # list of all modules
-    module_elements = {}
-
+    view_elements = {}
     for module in sketch:
         for sketch_line in module.split("\n"):
+            # line is a dict with keys "variable_name" and "view_name"
             line = parse_sketch_line(sketch_line.strip(), namespace)
-            # When a module name is found, the "new_module" becomes True.
-            # When a variable name is found, the "new_module" is set back to
-            # False
-            if line["module_name"]:
-                # remove characters that are not [a-zA-Z0-9_] from the module
-                # name
-                module_name = re.sub(
-                    r"[\W]+", "", line["module_name"].replace(" ", "_")
-                ).lstrip(
-                    "0123456789"
-                )  # there's probably a more elegant way to do it with regex
-                module_elements[module_name] = []
-                if module_name not in modules_list:
-                    modules_list.append(module_name)
+
+            if line["view_name"]:
+                view_name = line["view_name"]
+                view_elements[view_name] = []
 
             if line["variable_name"]:
-                if line["variable_name"] not in module_elements[module_name]:
-                    module_elements[module_name].append(line["variable_name"])
-    # removes modules that do not include any variable in them
-    module_elements_ = {
-        key.lower(): value for key, value in module_elements.items() if value
+                if line["variable_name"] not in view_elements[view_name]:
+                    view_elements[view_name].append(line["variable_name"])
+
+    # removes views that do not include any variable in them
+    non_empty_views = {
+        key.lower(): value for key, value in view_elements.items() if value
     }
 
-    return module_elements_
+    # split into subviews, if subview_sep is provided
+    views_dict = {}
+
+    if subview_sep and any(filter(lambda x: subview_sep in x,
+                           non_empty_views.keys())):
+        for name, elements in non_empty_views.items():
+            # split and clean view/subview names as they are not yet safe
+            view_subview = name.split(subview_sep)
+
+            if len(view_subview) == 2:
+                view, subview = utils.clean_file_names(*view_subview)
+            else:
+                view = utils.clean_file_names(*view_subview)[0]
+                subview = ""
+
+            if view.upper() not in views_dict.keys():
+                views_dict[view.upper()] = {}
+            if not subview:
+                views_dict[view.upper()][view.lower()] = elements
+            else:
+                views_dict[view.upper()][subview.lower()] = elements
+    else:
+        # clean file names
+        for view_name, elements in non_empty_views.items():
+            views_dict[utils.clean_file_names(view_name)[0]] = elements
+
+    return views_dict
 
 
 def _split_sketch(text):
@@ -1694,7 +1689,7 @@ def _split_sketch(text):
     return text, sketch
 
 
-def translate_vensim(mdl_file, split_modules):
+def translate_vensim(mdl_file, split_views, **kwargs):
     """
     Translate a vensim file.
 
@@ -1703,11 +1698,14 @@ def translate_vensim(mdl_file, split_modules):
     mdl_file: str
         File path of a vensim model file to translate to python.
 
-    split_modules: bool
+    split_views: bool
         If True, the sketch is parsed to detect model elements in each
         model view, and then translate each view in a separate python
         file. Setting this argument to True is recommended for large
-        models split in many different views.
+        models that are split in many different views.
+
+    **kwargs: (optional)
+        Additional parameters passed to the translate_vensim function
 
     Returns
     -------
@@ -1719,6 +1717,9 @@ def translate_vensim(mdl_file, split_modules):
     >>> translate_vensim('../tests/test-models/tests/subscript_3d_arrays/test_subscript_3d_arrays.mdl')
 
     """
+    # character used to place subviews in the parent view folder
+    subview_sep = kwargs.get("subview_sep", "")
+
     root_path = os.path.split(mdl_file)[0]
     with open(mdl_file, "r", encoding="UTF-8") as in_file:
         text = in_file.read()
@@ -1734,7 +1735,7 @@ def translate_vensim(mdl_file, split_modules):
     outfile_name = mdl_insensitive.sub(".py", mdl_file)
     out_dir = os.path.dirname(outfile_name)
 
-    if split_modules:
+    if split_views:
         text, sketch = _split_sketch(text)
     else:
         sketch = ""
@@ -1752,6 +1753,6 @@ def translate_vensim(mdl_file, split_modules):
     macro_list = [s for s in file_sections if s["name"] != "_main_"]
 
     for section in file_sections:
-        translate_section(section, macro_list, sketch, root_path)
+        translate_section(section, macro_list, sketch, root_path, subview_sep)
 
     return outfile_name
diff --git a/pysd/pysd.py b/pysd/pysd.py
index aeaacd6b..f6f21ee6 100644
--- a/pysd/pysd.py
+++ b/pysd/pysd.py
@@ -7,7 +7,7 @@
 
 import sys
 
-if sys.version_info[:2] < (3, 7):
+if sys.version_info[:2] < (3, 7):  # pragma: no cover
     raise RuntimeError(
         "\n\n"
         + "Your Python version is not longer supported by PySD.\n"
@@ -61,9 +61,8 @@ def read_xmile(xmile_file, initialize=True, missing_values="warning"):
     return model
 
 
-def read_vensim(
-    mdl_file, initialize=True, missing_values="warning", split_modules=False
-):
+def read_vensim(mdl_file, initialize=True, missing_values="warning",
+                split_views=False, **kwargs):
     """
     Construct a model from Vensim `.mdl` file.
 
@@ -84,12 +83,20 @@ def read_vensim(
         the missing values, this option may cause the integration to
         fail, but it may be used to check the quality of the data.
 
-    split_modules: bool (optional)
+    split_views: bool (optional)
         If True, the sketch is parsed to detect model elements in each
         model view, and then translate each view in a separate python
         file. Setting this argument to True is recommended for large
         models split in many different views. Default is False.
 
+    **kwargs: (optional)
+        Additional keyword arguments.
+        subview_sep:(str)
+            Character used to separate views and subviews. If provided,
+            and split_views=True, each submodule will be placed inside the
+            folder of the parent view.
+
+
     Returns
     -------
     model: a PySD class object
@@ -103,7 +110,7 @@ def read_vensim(
     """
     from .py_backend.vensim.vensim2py import translate_vensim
 
-    py_model_file = translate_vensim(mdl_file, split_modules)
+    py_model_file = translate_vensim(mdl_file, split_views, **kwargs)
     model = load(py_model_file, initialize, missing_values)
     model.mdl_file = mdl_file
     return model


Test patch:
diff --git a/tests/data/input.xlsx b/tests/data/input.xlsx
index 4f04faee..46a5695f 100644
Binary files a/tests/data/input.xlsx and b/tests/data/input.xlsx differ
diff --git a/tests/more-tests/split_model/test_split_model_subviews.mdl b/tests/more-tests/split_model/test_split_model_subviews.mdl
new file mode 100644
index 00000000..e302c23c
--- /dev/null
+++ b/tests/more-tests/split_model/test_split_model_subviews.mdl
@@ -0,0 +1,105 @@
+{UTF-8}
+another var=
+	3*Stock
+	~	
+	~		|
+
+"rate-1"=
+	"var-n"
+	~	
+	~		|
+
+"var-n"=
+	5
+	~	
+	~		|
+
+"variable-x"=
+	6*another var
+	~	
+	~		|
+
+Stock= INTEG (
+	"rate-1",
+		1)
+	~	
+	~		|
+
+********************************************************
+	.Control
+********************************************************~
+		Simulation Control Parameters
+	|
+
+FINAL TIME  = 100
+	~	Month
+	~	The final time for the simulation.
+	|
+
+INITIAL TIME  = 0
+	~	Month
+	~	The initial time for the simulation.
+	|
+
+SAVEPER  = 
+        TIME STEP
+	~	Month [0,?]
+	~	The frequency with which output is stored.
+	|
+
+TIME STEP  = 1
+	~	Month [0,?]
+	~	The time step for the simulation.
+	|
+
+\\\---/// Sketch information - do not modify anything except names
+V300  Do not put anything below this section - it will be ignored
+*View 1.Submodule 1
+$255-128-0,0,Times New Roman|12||0-0-0|0-0-0|0-192-192|-1--1--1|-1--1--1|96,96,100,0
+10,1,Stock,497,237,40,20,3,3,0,0,0,0,0,0
+12,2,48,297,243,10,8,0,3,0,0,-1,0,0,0
+1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(422,243)|
+1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(341,243)|
+11,5,48,382,243,6,8,34,3,0,0,1,0,0,0
+10,6,"rate-1",382,262,21,11,40,3,0,0,-1,0,0,0
+12,7,0,1141,258,150,150,3,12,0,0,1,0,0,0
+Stock
+10,8,"var-n",207,367,18,11,8,3,0,0,0,0,0,0
+1,9,8,6,0,0,0,0,0,128,0,-1--1--1,,1|(288,318)|
+\\\---/// Sketch information - do not modify anything except names
+V300  Do not put anything below this section - it will be ignored
+*View 1.Submodule 2
+$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0
+10,1,another var,89,168,36,11,8,3,0,0,0,0,0,0
+10,2,Stock,334,243,29,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128
+1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(221,209)|
+\\\---/// Sketch information - do not modify anything except names
+V300  Do not put anything below this section - it will be ignored
+*View 2
+$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0
+10,1,"variable-x",191,176,32,11,8,3,0,0,0,0,0,0
+10,2,another var,223,395,45,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128
+12,3,0,461,148,43,11,8,7,0,0,-1,0,0,0
+This is view 2
+1,4,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(208,292)|
+///---\\\
+:L<%^E!@
+1:Current.vdf
+9:Current
+15:0,0,0,0,0,0
+19:100,0
+27:0,
+34:0,
+4:Time
+5:another var
+35:Date
+36:YYYY-MM-DD
+37:2000
+38:1
+39:1
+40:2
+41:0
+42:1
+24:0
+25:100
+26:100
diff --git a/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl b/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl
new file mode 100644
index 00000000..d645c2d6
--- /dev/null
+++ b/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl
@@ -0,0 +1,143 @@
+{UTF-8}
+Heating=
+	(Cream Temperature - Room Temperature)  / Characteristic Time
+	~	
+	~		|
+
+Cream Temperature= INTEG (
+	-Heating,
+		10)
+	~	Degrees
+	~		|
+
+Room Temperature=
+	70
+	~	Degrees
+	~		|
+
+Transfer Coef=
+	0.37
+	~	
+	~		|
+
+Heat Loss to Room=
+	(Teacup Temperature - Room Temperature)  * Transfer Coef / Characteristic Time
+	~	Degrees/Minute
+	~	This is the rate at which heat flows from the cup into the room. We can \
+		ignore it at this point.
+	|
+
+Characteristic Time=
+	10
+	~	Minutes
+	~		|
+
+Teacup Temperature= INTEG (
+	-Heat Loss to Room,
+		100)
+	~	Degrees
+	~		|
+
+********************************************************
+	.Control
+********************************************************~
+		Simulation Control Parameters
+	|
+
+FINAL TIME  = 30
+	~	Minute
+	~	The final time for the simulation.
+	|
+
+INITIAL TIME  = 0
+	~	Minute
+	~	The initial time for the simulation.
+	|
+
+SAVEPER  = 
+        TIME STEP
+	~	Minute [0,?]
+	~	The frequency with which output is stored.
+	|
+
+TIME STEP  = 0.125
+	~	Minute [0,?]
+	~	The time step for the simulation.
+	|
+
+\\\---/// Sketch information - do not modify anything except names
+V300  Do not put anything below this section - it will be ignored
+*TeaCup
+$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|72,72,100,0
+10,1,Teacup Temperature,307,224,40,20,3,3,0,0,0,0,0,0,0,0,0,0,0,0
+12,2,48,605,221,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0
+1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(508,220)|
+1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(377,220)|
+11,5,48,413,220,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0
+10,6,Heat Loss to Room,413,232,49,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0
+10,7,Room Temperature,504,373,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0
+10,8,Characteristic Time,408,164,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0
+1,9,8,5,0,0,0,0,0,64,0,-1--1--1,,1|(412,187)|
+1,10,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(393,308)|
+1,11,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(477,336)|
+10,12,Transfer Coef,541,164,33,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0
+1,13,12,6,0,0,0,0,0,64,0,-1--1--1,,1|(484,195)|
+\\\---/// Sketch information - do not modify anything except names
+V300  Do not put anything below this section - it will be ignored
+*Cream
+$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|72,72,100,0
+10,1,Cream Temperature,363,287,56,15,3,131,0,0,0,0,0,0,0,0,0,0,0,0
+12,2,48,680,284,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0
+1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(611,284)|
+1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(480,284)|
+11,5,0,545,284,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0
+10,6,Heating,545,296,19,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0
+10,7,Room Temperature,532,407,35,16,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128,0,0,0,0,0,0
+1,8,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(608,364)|
+10,9,Characteristic Time,544,188,35,17,8,130,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128,0,0,0,0,0,0
+1,10,9,6,1,0,0,0,0,64,0,-1--1--1,,1|(593,241)|
+1,11,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(460,341)|
+///---\\\
+:L<%^E!@
+4:Time
+5:Teacup Temperature
+9:Current
+19:100,0
+24:0
+25:30
+26:30
+22:$,Dollar,Dollars,$s
+22:Hour,Hours
+22:Month,Months
+22:Person,People,Persons
+22:Unit,Units
+22:Week,Weeks
+22:Year,Years
+22:Day,Days
+23:0
+15:0,0,0,0,0,0
+27:0,
+34:0,
+42:1
+72:0
+73:0
+35:Date
+36:YYYY-MM-DD
+37:2000
+38:1
+39:1
+40:6
+41:0
+95:0
+96:0
+77:0
+78:0
+93:0
+94:0
+92:0
+91:0
+90:0
+87:0
+75:
+43:
+
diff --git a/tests/unit_test_cli.py b/tests/unit_test_cli.py
index eb1d51dd..941721fe 100644
--- a/tests/unit_test_cli.py
+++ b/tests/unit_test_cli.py
@@ -207,7 +207,7 @@ def test_read_vensim_split_model(self):
         modules_dirname = "modules_" + model_name
         model_name_mdl = root_dir + model_name + ".mdl"
 
-        command = f'{call} --translate --split-modules {model_name_mdl}'
+        command = f'{call} --translate --split-views {model_name_mdl}'
         out = subprocess.run(split_bash(command), capture_output=True)
         self.assertEqual(out.returncode, 0)
 
@@ -240,6 +240,66 @@ def test_read_vensim_split_model(self):
         # remove newly created modules folder
         shutil.rmtree(root_dir + modules_dirname)
 
+    def test_read_vensim_split_model_subviews(self):
+        import pysd
+        from pysd.tools.benchmarking import assert_frames_close
+
+        root_dir = "more-tests/split_model/"
+
+        model_name = "test_split_model_subviews"
+        model_name_mdl = root_dir + model_name + ".mdl"
+
+        model_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=True,
+            subview_sep="."
+        )
+
+        namespace_filename = "_namespace_" + model_name + ".json"
+        subscript_dict_filename = "_subscripts_" + model_name + ".json"
+        modules_dirname = "modules_" + model_name
+
+        separator = "."
+        command = f'{call} --translate --split-views '\
+                  f'--subview-sep={separator} {model_name_mdl}'
+        out = subprocess.run(split_bash(command), capture_output=True)
+        self.assertEqual(out.returncode, 0)
+
+        # check that the modules folders were created
+        self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_1"))
+        self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_2"))
+
+        # check creation of module files
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" +
+                           "submodule_1.py"))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" +
+                           "submodule_2.py"))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_2/" +
+                           "view_2.py"))
+
+        # check that the results of the split model are the same than those
+        # without splitting
+        model_non_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=False
+        )
+
+        result_split = model_split.run()
+        result_non_split = model_non_split.run()
+
+        # results of a split model are the same that those of the regular
+        # model (un-split)
+        assert_frames_close(result_split, result_non_split, atol=0, rtol=0)
+
+        # remove newly created files
+        os.remove(root_dir + model_name + ".py")
+        os.remove(root_dir + namespace_filename)
+        os.remove(root_dir + subscript_dict_filename)
+
+        # remove newly created modules folder
+        shutil.rmtree(root_dir + modules_dirname)
+
     def test_run_return_timestamps(self):
 
         timestamps = np.round(np.random.rand(5).cumsum(), 4).astype(str)
diff --git a/tests/unit_test_external.py b/tests/unit_test_external.py
index b6164737..8bae027c 100644
--- a/tests/unit_test_external.py
+++ b/tests/unit_test_external.py
@@ -2684,10 +2684,10 @@ def test_data_interp_vnss(self):
             data.initialize()
 
     # Following test are independent of the reading option
-    def test_data_interp_hnnm(self):
+    def test_data_interp_hnnwd(self):
         """
         Test for error in series when the series is not
-        strictly monotonous
+        well defined
         """
         import pysd
 
@@ -2697,7 +2697,7 @@ def test_data_interp_hnnm(self):
         cell = "data_1d"
         coords = {}
         interp = None
-        py_name = "test_data_interp_hnnm"
+        py_name = "test_data_interp_hnnwd"
 
         data = pysd.external.ExtData(file_name=file_name,
                                      sheet=sheet,
@@ -2708,9 +2708,64 @@ def test_data_interp_hnnm(self):
                                      interp=interp,
                                      py_name=py_name)
 
-        with self.assertRaises(ValueError):
+        with self.assertRaises(ValueError) as err:
             data.initialize()
 
+        self.assertIn("has repeated values", str(err.exception))
+
+    def test_data_raw_hnnm(self):
+        """
+        Test for error in series when the series is not monotonous
+        """
+        import pysd
+
+        file_name = "data/input.xlsx"
+        sheet = "No monotonous"
+        time_row_or_col = "10"
+        cell = "C12"
+        coords = {}
+        interp = None
+        py_name = "test_data_interp_hnnnm"
+
+        data = pysd.external.ExtData(file_name=file_name,
+                                     sheet=sheet,
+                                     time_row_or_col=time_row_or_col,
+                                     root=_root,
+                                     cell=cell,
+                                     coords=coords,
+                                     interp=interp,
+                                     py_name=py_name)
+
+        data.initialize()
+
+        expected = {-1: 2, 0: 2, 1: 2, 2: 3,
+                    3: -1, 4: -1, 5: 1, 6: 1,
+                    7: 0, 8: 0, 9: 0}
+
+        for i in range(-1, 9):
+            self.assertEqual(data(i), expected[i])
+
+        time_row_or_col = "11"
+        py_name = "test_data_interp_hnnnm2"
+
+        data = pysd.external.ExtData(file_name=file_name,
+                                     sheet=sheet,
+                                     time_row_or_col=time_row_or_col,
+                                     root=_root,
+                                     cell=cell,
+                                     coords=coords,
+                                     interp=interp,
+                                     py_name=py_name)
+
+        data.initialize()
+
+        expected = {-1: 0, 0: 0, 1: 0, 2: 1,
+                    3: 2, 4: 3, 5: -1, 6: -1,
+                    7: 1, 8: 2, 9: 2}
+
+        for i in range(-1, 9):
+            self.assertEqual(data(i), expected[i])
+
     def test_data_h3d_interpnv(self):
         """
         ExtData test for error when the interpolation method is not valid
diff --git a/tests/unit_test_pysd.py b/tests/unit_test_pysd.py
index a47d07af..32c43e3a 100644
--- a/tests/unit_test_pysd.py
+++ b/tests/unit_test_pysd.py
@@ -93,7 +93,7 @@ def test_read_vensim_split_model(self):
 
         model_name = "test_split_model"
         model_split = pysd.read_vensim(
-            root_dir + model_name + ".mdl", split_modules=True
+            root_dir + model_name + ".mdl", split_views=True
         )
 
         namespace_filename = "_namespace_" + model_name + ".json"
@@ -127,10 +127,99 @@ def test_read_vensim_split_model(self):
         self.assertIn("view2", model_split.components._modules.keys())
         self.assertIsInstance(model_split.components._subscript_dict, dict)
 
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are not defined in the main file
+        self.assertNotIn("def another_var()", file_content)
+        self.assertNotIn("def rate1()", file_content)
+        self.assertNotIn("def varn()", file_content)
+        self.assertNotIn("def variablex()", file_content)
+        self.assertNotIn("def stock()", file_content)
+
+        # check that the results of the split model are the same than those
+        # without splitting
+        model_non_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=False
+        )
+
+        result_split = model_split.run()
+        result_non_split = model_non_split.run()
+
+        # results of a split model are the same that those of the regular
+        # model (un-split)
+        assert_frames_close(result_split, result_non_split, atol=0, rtol=0)
+
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are in the main file for regular trans
+        self.assertIn("def another_var()", file_content)
+        self.assertIn("def rate1()", file_content)
+        self.assertIn("def varn()", file_content)
+        self.assertIn("def variablex()", file_content)
+        self.assertIn("def stock()", file_content)
+
+        # remove newly created files
+        os.remove(root_dir + model_name + ".py")
+        os.remove(root_dir + namespace_filename)
+        os.remove(root_dir + subscript_dict_filename)
+
+        # remove newly created modules folder
+        shutil.rmtree(root_dir + modules_dirname)
+
+    def test_read_vensim_split_model_vensim_8_2_1(self):
+        import pysd
+        from pysd.tools.benchmarking import assert_frames_close
+
+        root_dir = "more-tests/split_model_vensim_8_2_1/"
+
+        model_name = "test_split_model_vensim_8_2_1"
+        model_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=True, subview_sep="."
+        )
+
+        namespace_filename = "_namespace_" + model_name + ".json"
+        subscript_dict_filename = "_subscripts_" + model_name + ".json"
+        modules_filename = "_modules.json"
+        modules_dirname = "modules_" + model_name
+
+        # check that _namespace and _subscript_dict json files where created
+        self.assertTrue(os.path.isfile(root_dir + namespace_filename))
+        self.assertTrue(os.path.isfile(root_dir + subscript_dict_filename))
+
+        # check that the main model file was created
+        self.assertTrue(os.path.isfile(root_dir + model_name + ".py"))
+
+        # check that the modules folder was created
+        self.assertTrue(os.path.isdir(root_dir + modules_dirname))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/" + modules_filename)
+        )
+
+        # check creation of module files
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/" + "teacup.py"))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/" + "cream.py"))
+
+        # check dictionaries
+        self.assertIn("Cream Temperature",
+                      model_split.components._namespace.keys())
+        self.assertIn("cream", model_split.components._modules.keys())
+        self.assertIsInstance(model_split.components._subscript_dict, dict)
+
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are not defined in the main file
+        self.assertNotIn("def teacup_temperature()", file_content)
+        self.assertNotIn("def cream_temperature()", file_content)
+
         # check that the results of the split model are the same than those
         # without splitting
         model_non_split = pysd.read_vensim(
-            root_dir + model_name + ".mdl", split_modules=False
+            root_dir + model_name + ".mdl", split_views=False
         )
 
         result_split = model_split.run()
@@ -140,6 +229,85 @@ def test_read_vensim_split_model(self):
         # model (un-split)
         assert_frames_close(result_split, result_non_split, atol=0, rtol=0)
 
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are in the main file for regular trans
+        self.assertIn("def teacup_temperature()", file_content)
+        self.assertIn("def cream_temperature()", file_content)
+
+        # remove newly created files
+        os.remove(root_dir + model_name + ".py")
+        os.remove(root_dir + namespace_filename)
+        os.remove(root_dir + subscript_dict_filename)
+
+        # remove newly created modules folder
+        shutil.rmtree(root_dir + modules_dirname)
+
+    def test_read_vensim_split_model_subviews(self):
+        import pysd
+        from pysd.tools.benchmarking import assert_frames_close
+
+        root_dir = "more-tests/split_model/"
+
+        model_name = "test_split_model_subviews"
+        model_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=True,
+            subview_sep="."
+        )
+
+        namespace_filename = "_namespace_" + model_name + ".json"
+        subscript_dict_filename = "_subscripts_" + model_name + ".json"
+        modules_dirname = "modules_" + model_name
+
+        # check that the modules folders were created
+        self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_1"))
+        self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_2"))
+
+        # check creation of module files
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" +
+                           "submodule_1.py"))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" +
+                           "submodule_2.py"))
+        self.assertTrue(
+            os.path.isfile(root_dir + modules_dirname + "/VIEW_2/" +
+                           "view_2.py"))
+
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are not defined in the main file
+        self.assertNotIn("def another_var()", file_content)
+        self.assertNotIn("def rate1()", file_content)
+        self.assertNotIn("def varn()", file_content)
+        self.assertNotIn("def variablex()", file_content)
+        self.assertNotIn("def stock()", file_content)
+
+        # check that the results of the split model are the same than those
+        # without splitting
+        model_non_split = pysd.read_vensim(
+            root_dir + model_name + ".mdl", split_views=False
+        )
+
+        result_split = model_split.run()
+        result_non_split = model_non_split.run()
+
+        # results of a split model are the same that those of the regular
+        # model (un-split)
+        assert_frames_close(result_split, result_non_split, atol=0, rtol=0)
+
+        with open(root_dir + model_name + ".py", 'r') as file:
+            file_content = file.read()
+
+        # assert that the functions are in the main file for regular trans
+        self.assertIn("def another_var()", file_content)
+        self.assertIn("def rate1()", file_content)
+        self.assertIn("def varn()", file_content)
+        self.assertIn("def variablex()", file_content)
+        self.assertIn("def stock()", file_content)
+
         # remove newly created files
         os.remove(root_dir + model_name + ".py")
         os.remove(root_dir + namespace_filename)
@@ -156,7 +324,7 @@ def test_read_vensim_split_model_with_macro(self):
 
         model_name = "test_split_model_with_macro"
         model_split = pysd.read_vensim(
-            root_dir + model_name + ".mdl", split_modules=True
+            root_dir + model_name + ".mdl", split_views=True
         )
 
         namespace_filename = "_namespace_" + model_name + ".json"
@@ -166,7 +334,7 @@ def test_read_vensim_split_model_with_macro(self):
         # check that the results of the split model are the same
         # than those without splitting
         model_non_split = pysd.read_vensim(
-            root_dir + model_name + ".mdl", split_modules=False
+            root_dir + model_name + ".mdl", split_views=False
         )
 
         result_split = model_split.run()
@@ -186,18 +354,19 @@ def test_read_vensim_split_model_with_macro(self):
 
     def test_read_vensim_split_model_warning(self):
         import pysd
-        # setting the split_modules=True when the model has a single
+        # setting the split_views=True when the model has a single
         # view should generate a warning
         with catch_warnings(record=True) as ws:
             pysd.read_vensim(
-                test_model, split_modules=True
+                test_model, split_views=True
             )  # set stock value using params
 
         wu = [w for w in ws if issubclass(w.category, UserWarning)]
 
         self.assertEqual(len(wu), 1)
         self.assertTrue(
-            "Only one module was detected" in str(wu[0].message)
+            "Only a single view with no subviews was detected" in str(
+                wu[0].message)
         )  # check that warning references the stock
 
     def test_run_includes_last_value(self):
diff --git a/tests/unit_test_vensim2py.py b/tests/unit_test_vensim2py.py
index ecb8a368..a969ffe6 100644
--- a/tests/unit_test_vensim2py.py
+++ b/tests/unit_test_vensim2py.py
@@ -557,9 +557,7 @@ def test_subscript_float_initialization(self):
         # hoewever eval is not detecting _subscript_dict variable
         self.assertEqual(
             string,
-            "xr.DataArray(3.32,{dim: "
-            + "_subscript_dict[dim] for dim in "
-            + "['Dim1']},['Dim1'])",
+            "xr.DataArray(3.32,{'Dim1': _subscript_dict['Dim1']},['Dim1'])",
         )
         a = xr.DataArray(
             3.32, {dim: _subscript_dict[dim] for dim in ["Dim1"]}, ["Dim1"]
@@ -580,11 +578,10 @@ def test_subscript_1d_constant(self):
         string = element[0]["py_expr"]
         # TODO we should use a = eval(string)
         # hoewever eval is not detecting _subscript_dict variable
-        self.assertTrue(
+        self.assertEqual(
             string,
-            "xr.DataArray([1.,2.,3.],"
-            + "{dim: _subscript_dict[dim]"
-            + " for dim in ['Dim1']}, ['Dim1'])",
+            "xr.DataArray([1.,2.,3.],{'Dim1': _subscript_dict['Dim1']},"
+            "['Dim1'])",
         )
         a = xr.DataArray([1.0, 2.0, 3.0],
                          {dim: _subscript_dict[dim] for dim in ["Dim1"]},
@@ -787,7 +784,7 @@ def test_parse_sketch_line(self):
         for num, line in enumerate(lines):
             res = parse_sketch_line(line.strip(), namespace)
             self.assertEqual(res["variable_name"], expected_var[num])
-            self.assertEqual(res["module_name"], expected_mod[num])
+            self.assertEqual(res["view_name"], expected_mod[num])
 
 
 class TestParse_private_functions(unittest.TestCase):