Repository: hu-macsy/simexpal

Issue:
Allow extra_args for instance repositories
Allow `extra_args:` in `repo:` blocks. For example, this would be useful to allow users to specify file formats on a per-repo basis.

Discussion:

Patch:
diff --git a/docs/instances.rst b/docs/instances.rst
index 027bb31..ebf8b65 100644
--- a/docs/instances.rst
+++ b/docs/instances.rst
@@ -190,15 +190,41 @@ key to specify our extra arguments.
 Extra Arguments
 ---------------
 
-We can set extra arguments for instances, which can be appended to the experiment arguments when the
-respective instance is used. In order to specify such instances, we use the
+We can set extra arguments for instance blocks and individual instances, which can be appended to the
+experiment arguments when the respective instance is used. In order to specify such instances, we use the
 
 - ``extra_args``: list of extra arguments
 
 key.
 
-For :ref:`LocalInstances`, :ref:`RemoteInstances` and :ref:`MultipleExtensions` we need to change
-the ``items`` key from a list of instances to a list of dictionaries containing the
+.. note::
+   It is possible to have instances with common extra arguments and additional individual extra arguments
+   by adding the ``extra_args`` key to the respective places (see below).
+
+In order to specify common extra arguments for instance blocks, we simply add the ``extra_args`` key to them.
+
+.. code-block:: YAML
+   :linenos:
+   :caption: How to list instances with common extra arguments in the experiments.yml file.
+
+    instdir: "<path_to_instance_directory>"
+    instances:
+      - repo: local
+        extra_args: ['some','extra_args']
+        ...
+        items:
+          - instance1
+          - instance2
+      - repo: local
+        extra_args: ['some','extra_args']
+        ...
+        items:
+          - instance3
+          - instance4
+
+In order to specify individual extra arguments for :ref:`LocalInstances`, :ref:`RemoteInstances` and
+:ref:`MultipleExtensions` we need to change the ``items`` key from a list of instances to a list of
+dictionaries containing the
 
 - ``name``: name of the instance `and`
 - ``extra_args``
@@ -207,7 +233,8 @@ key, e.g.,
 
 .. code-block:: YAML
    :linenos:
-   :caption: How to list local/remote/multiple extension instances with extra arguments in the experiments.yml file.
+   :caption: How to list local/remote/multiple extension instances with individual
+             extra arguments in the experiments.yml file.
 
    instances:
      - repo: local # local instances with extra arguments
@@ -219,15 +246,15 @@ key, e.g.,
      - repo: snap # remote instances with extra arguments
        items:
          - name: facebook_combined
-           extra_args: ['some', 'extra', 'argument']
+           extra_args: ['some', 'extra_args']
          - name: wiki-Vote
-           extra_args: ['some', 'extra', 'argument']
+           extra_args: ['some', 'extra_args']
      - repo: local # multiple extension instances with extra args
        items:
          - name: inst3
-           extra_args: ['some', 'extra', 'argument']
+           extra_args: ['some', 'extra_args']
          - name: inst4
-           extra_args: ['some', 'extra', 'argument']
+           extra_args: ['some', 'extra_args']
        extensions: [ext1, ext2]
 
 For :ref:`ArbitraryInputFiles` we only need to add the ``extra_args`` key
@@ -235,7 +262,8 @@ to the dictionaries of the instances, e.g,
 
 .. code-block:: YAML
    :linenos:
-   :caption: How to list arbitrary input file instances with extra arguments in the experiments.yml file.
+   :caption: How to list arbitrary input file instances with individual extra arguments
+             in the experiments.yml file.
 
     instances:
      - repo: local
diff --git a/simexpal/base.py b/simexpal/base.py
index 9e549db..c69cced 100644
--- a/simexpal/base.py
+++ b/simexpal/base.py
@@ -602,9 +602,10 @@ class Instance:
 
 	@property
 	def extra_args(self):
+		extra_args = self._inst_yml.get('extra_args', [])
 		if isinstance(self._inst_yml['items'][self.index], dict):
-			return self._inst_yml['items'][self.index].get('extra_args', [])
-		return []
+			return extra_args + self._inst_yml['items'][self.index].get('extra_args', [])
+		return extra_args
 
 	@property
 	def config(self):
diff --git a/simexpal/schemes/experiments.json b/simexpal/schemes/experiments.json
index e08f587..50d796e 100644
--- a/simexpal/schemes/experiments.json
+++ b/simexpal/schemes/experiments.json
@@ -102,6 +102,7 @@
 									},
 									"additionalProperties": false
 								},
+								"extra_args": {"$ref": "#/definitions/str_list"},
 								"set": {"$ref": "#/definitions/str_or_str_list"},
 								"items": {
 									"oneOf": [
@@ -138,6 +139,7 @@
 							"properties": {
 								"repo": {"const": "local"},
 								"extensions": {"$ref": "#/definitions/str_list"},
+								"extra_args": {"$ref": "#/definitions/str_list"},
 								"set": {"$ref": "#/definitions/str_or_str_list"},
 								"items": {
 									"oneOf": [
@@ -182,6 +184,7 @@
 							"required": ["repo", "items"],
 							"properties": {
 								"repo": {"const": "local"},
+								"extra_args": {"$ref": "#/definitions/str_list"},
 								"set": {"$ref": "#/definitions/str_or_str_list"},
 								"items": {
 									"type": "array",
@@ -243,6 +246,7 @@
 								},
 								"repo-subdir": {"type": "string"},
 								"postprocess": {"const": "to_edgelist"},
+								"extra_args": {"$ref": "#/definitions/str_list"},
 								"set": {"$ref": "#/definitions/str_or_str_list"},
 								"items": {
 									"oneOf": [


Test patch:
diff --git a/tests/validation/invalid_ymls/repo-invalid_value.yml b/tests/validation/experiments_ymls/invalid/repo-invalid_value.yml
similarity index 100%
rename from tests/validation/invalid_ymls/repo-invalid_value.yml
rename to tests/validation/experiments_ymls/invalid/repo-invalid_value.yml
diff --git a/tests/validation/invalid_ymls/top_level-additional_property.yml b/tests/validation/experiments_ymls/invalid/top_level-additional_property.yml
similarity index 100%
rename from tests/validation/invalid_ymls/top_level-additional_property.yml
rename to tests/validation/experiments_ymls/invalid/top_level-additional_property.yml
diff --git a/tests/validation/experiments_ymls/valid/instances/extra_args.yml b/tests/validation/experiments_ymls/valid/instances/extra_args.yml
new file mode 100644
index 0000000..cc1fc30
--- /dev/null
+++ b/tests/validation/experiments_ymls/valid/instances/extra_args.yml
@@ -0,0 +1,43 @@
+# This file contains 'extra_args' in instance blocks for local, SNAP,
+# KONECT, multiple extension, multiple file and fileless instances.
+instances:
+  - repo: local   # local instances
+    extra_args : ['some', 'extra_args']
+    items:
+      - inst1
+      - inst2
+  - repo: snap    # SNAP instances
+    extra_args : ['some', 'extra_args']
+    items:
+      - facebook_combined
+      - wiki-Vote
+  - repo: konect  # KONECT instances
+    extra_args : ['some', 'extra_args']
+    items:
+      - dolphins
+      - ucidata-zachary
+  - repo: local   # multiple extension instances
+    extra_args : ['some', 'extra_args']
+    extensions:
+      - graph
+      - xyz
+    items:
+      - foo
+      - bar
+  - repo: local   # multiple file instances
+    extra_args : ['some', 'extra_args']
+    items:
+      - name: inst3
+        files:
+          - file1
+          - file2
+      - name: inst4
+        files:
+          - file3
+          - file4
+  - repo: local   # fileless instance
+    extra_args : ['some', 'extra_args']
+    items:
+      - name: inst5
+        files: []
+        extra_args: ['some', 'extra_args']
diff --git a/tests/validation/test_experiments_yml.py b/tests/validation/test_experiments_yml.py
index 436365a..3f0a9f2 100644
--- a/tests/validation/test_experiments_yml.py
+++ b/tests/validation/test_experiments_yml.py
@@ -7,10 +7,11 @@ from simexpal import util
 file_dir = os.path.abspath(os.path.dirname(__file__))
 valid_experiments_ymls = ['/../../examples/sorting/experiments.yml',
                           '/../../examples/sorting_cpp/experiments.yml',
-                          '/../../examples/download_instances/experiments.yml']
+                          '/../../examples/download_instances/experiments.yml',
+                          '/experiments_ymls/valid/instances/extra_args.yml']
 
-invalid_experiments_ymls = ['/invalid_ymls/top_level-additional_property.yml',
-                            '/invalid_ymls/repo-invalid_value.yml']
+invalid_experiments_ymls = ['/experiments_ymls/invalid/top_level-additional_property.yml',
+                            '/experiments_ymls/invalid/repo-invalid_value.yml']
 
 @pytest.mark.parametrize('rel_yml_path', valid_experiments_ymls)
 def test_valid_experiments_yml(rel_yml_path):