Repository: potassco/clingraph (language: python)

Issue:
Add a `--version` option?



Discussion / hints:

Fix patch:
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 2e86bca..b36034f 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -5,13 +5,18 @@ version: 2
 build:
   os: "ubuntu-20.04"
   tools:
-    python: "3.9"
+    python: "mambaforge-4.10"
 
 # Build from the docs/ directory with Sphinx
 sphinx:
   configuration: docs/conf.py
 
 # Explicitly set the version of Python and its requirements
+
+conda:
+  environment: environment.yml
+
 python:
   install:
-    - requirements: docs/requirements.txt
\ No newline at end of file
+    - method: pip
+      path: .
diff --git a/README.md b/README.md
index 1271ab7..42a35bc 100644
--- a/README.md
+++ b/README.md
@@ -8,13 +8,40 @@ It contains special features for integration with [clingo](https://potassco.org/
 
 ## Installation
 
-Standing on the root folder install all dependencies:
+### Requirements
+
+- Python (version 3.8, 3.9, or 3.10)
+
+##### Install with conda
+We advise the user to install the dependencies using the provided conda environment:
+
+```
+conda env create -f environment.yml
+conda activate clingraph
+```
+
+##### Manually install graphviz
+Otherwise the user must manually install the dependencies:
+
+This package requires the installation of 
+- [pygraphviz](https://pygraphviz.github.io/documentation/stable/install.html)
+  - [graphviz](https://www.graphviz.org) installed via [python-graphviz](https://anaconda.org/conda-forge/python-graphviz)
+
+### Package
+
+Standing on the root folder install the project and python dependencies:
 
 ```
 pip install .
 ```
 
-This will make the command `clingraph` available for usage.
+This will make the command `clingraph` available for usage as well as the python package with `import clingraph`.
+
+## Run tests
+
+```
+pytest -v
+```
 
 ## Usage
 
diff --git a/build/lib/clingraph/__init__.py b/build/lib/clingraph/__init__.py
new file mode 100644
index 0000000..9685714
--- /dev/null
+++ b/build/lib/clingraph/__init__.py
@@ -0,0 +1,261 @@
+"""
+    Package inports and command line functionality
+"""
+from ast import parse
+import sys
+import logging
+import argparse
+import textwrap
+import pkg_resources
+from clingraph.clingraph import Clingraph
+from clingraph.logger import setup_logger
+from clingraph.multigraph import MultiModelClingraph
+from clingraph.orm import ClingraphORM
+from clingraph.exception import InvalidSyntax
+version = pkg_resources.require("clingraph")[0].version
+
+def get_parser():
+    """
+    Get the parser for the command line
+    """
+    # pylint: disable=anomalous-backslash-in-string
+    parser = argparse.ArgumentParser(description=textwrap.dedent("""
+        _ _                         _
+     __| (_)_ _  __ _ _ _ __ _ _ __| |_
+    / _| | | ' \/ _` | '_/ _` | '_ \ ' \\ 
+    \__|_|_|_||_\__, |_| \__,_| .__/_||_|
+                |___/         |_|
+
+    Clingraph is a package to generate graph visualizations
+    based on facts that can be computed by logic programs.
+    Special features for integration with clingo.
+    """),
+    formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument('files', type=argparse.FileType('r'), nargs='*')
+    parser.add_argument('-q',
+                    help = """Flag to have a quiet output where the graphs soruce wont be rendered""",
+                    action='store_true')
+    parser.add_argument('-log', default="warning",
+                    choices=['debug', 'info', 'error', 'warning'],
+                    help=textwrap.dedent('''\
+                        Provide logging level.
+                        {debug|info|error|warning}
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar='')
+    parser.add_argument('--version','-v', action='version',
+                    version=f'%(prog)s {version}')
+
+
+    input_params = parser.add_argument_group('Graph generation')
+    input_params.add_argument('--type',
+                    default = 'graph',
+                    choices=['graph', 'digraph'],
+                    help = textwrap.dedent('''\
+                        The type of the graph: digraph or graph
+                        {graph|digraph}
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar='')
+    input_params.add_argument('--prefix',
+                    default = '',
+                    help = textwrap.dedent('''\
+                        Prefix expected in all the considered facts'''),
+                    type=str,
+                    metavar='')
+    input_params.add_argument('--default-graph',
+                    default = 'default',
+                    help = textwrap.dedent('''\
+                    The name of the default graph.
+                    All nodes and edges with arity 1 will be assigned to this graph
+                    (default: %(default)s)'''),
+                    type=str,
+                    metavar='')
+
+    render_params = parser.add_argument_group('Graph rendering')
+    render_params.add_argument('--render',
+                    help = """Flag to render the graphs and save in files""",
+                    action='store_true')
+
+    render_params.add_argument('--dir',
+                    default = 'out',
+                    help = textwrap.dedent('''\
+                        Directory for saving and rendering
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar='')
+
+    render_params.add_argument('--out-file-prefix',
+                    default = '',
+                    help = textwrap.dedent('''\
+                        A prefix for the names of the generated files
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar='')
+
+    render_params.add_argument('--format',
+                    default = 'pdf',
+                    choices=['pdf', 'png', 'svg'],
+                    help = textwrap.dedent('''\
+                        Format to save the graph
+                        {pdf|png|svg}
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar="")
+    render_params.add_argument('--engine',
+                    default = 'dot',
+                    choices=["dot","neato","twopi","circo","fdp","osage","patchwork","sfdp"],
+                    help = textwrap.dedent('''\
+                        Layout command used by graphviz
+                        {dot|neato|twopi|circo|fdp|osage|patchwork|sfdp}
+                            (default: %(default)s)'''),
+                    type=str,
+                    metavar="")
+
+    render_params.add_argument('--view',
+                action='store_true',
+                help = textwrap.dedent('''\
+                    Opens the generated files'''))
+
+    render_params.add_argument('--select-graph',
+                    help = textwrap.dedent('''\
+                        Select one of the graphs for output or rendering by name
+                        Can appear multiple times to select multiple graphs'''),
+                    type=str,
+                    nargs='+',
+                    metavar="")
+
+    render_params.add_argument('--render-param',
+                    default = '',
+                    help = textwrap.dedent('''\
+                    A string containing a parameter for graphviz rendering.
+                    String should have the form arg_name=arg_value '''),
+                    type=str,
+                    nargs='*',
+                    metavar='')
+
+    render_params.add_argument('--gif',
+                    help = """Flag to generate a gif from all the generated files""",
+                    action='store_true')
+
+    render_params.add_argument('--gif-name',
+                default = 'clingraph',
+                help = textwrap.dedent('''\
+                Name for the gif file that will be saved in the given directory'''),
+                type=str,
+                metavar='')
+
+    render_params.add_argument('--gif-param',
+                default = '',
+                help = textwrap.dedent('''\
+                A string containing a parameter for the gif generation by imageio.
+                String should have the form arg_name=arg_value '''),
+                type=str,
+                nargs='*',
+                metavar='')
+
+
+    render_params.add_argument('--tex',
+                    help = """Flag to generate a latex tex file""",
+                    action='store_true')
+
+    render_params.add_argument('--tex-param',
+                default = '',
+                help = textwrap.dedent('''\
+                A string containing a parameter for the tex file generation by dot2tex.
+                String should have the form arg_name=arg_value '''),
+                type=str,
+                nargs='*',
+                metavar='')
+
+    multi_params = parser.add_argument_group('Multi model graphs')
+
+    multi_params.add_argument('--json',
+                help = textwrap.dedent('''\
+                Flag to indicate the creation of multiple models from a json.
+                The graphs will be generated for each stable model.
+                The json is exptected to be the output of clingo using the option `--outf=2`'''),
+                action='store_true')
+
+    multi_params.add_argument('--select-model',
+                help = textwrap.dedent('''\
+                    Select only one of the models outputed by clingo defined by a number'''),
+                type=int,
+                nargs='?',
+                metavar="")
+    return parser
+
+def main():
+    '''
+    Runs the main function
+    '''
+    ####### Parser
+    parser= get_parser()
+    args = parser.parse_args()
+
+    ####### Logger
+    log = logging.getLogger('custom')
+    levels = {'error': logging.ERROR, 'warn': logging.WARNING,
+              'warning': logging.WARNING, 'info': logging.INFO, 'debug': logging.DEBUG}
+    setup_logger(levels.get(args.log.lower()))
+
+    log.debug(args)
+    ####### Load input
+    input_str=""
+    if not sys.stdin.isatty():
+        input_str = sys.stdin.read()
+
+    g = None
+    if not args.json:
+        g = Clingraph(type_ = args.type, prefix=args.prefix, default_graph=args.default_graph)
+
+        for f in args.files:
+            log.info("Adding file %s",f.name)
+            g.add_fact_file(f.name)
+
+        if input_str:
+            g.add_fact_string(input_str)
+
+    else:
+        log.info("Loading a multi model graph from json")
+
+        g = MultiModelClingraph(type_ = args.type, prefix=args.prefix, default_graph=args.default_graph)
+
+        g.load_json(input_str)
+
+    g.compute_graphs()
+
+    ####### Output
+    if not args.select_model is None and args.json:
+        g=g.get_clingraph(args.select_model)
+
+    if args.render:
+        render_params = []
+        if args.render_param is not None:
+            render_params = args.render_param
+        render_param_dic = { s.split('=')[0]:s.split('=')[1] for s in render_params}
+        g.save(directory=args.dir,format=args.format,name_prefix=args.out_file_prefix,
+            selected_graphs=args.select_graph,
+            view=args.view, engine=args.engine,
+            **render_param_dic)
+    if args.gif:
+        gif_params = []
+        if args.gif_param is not None:
+            gif_params = args.gif_param
+        gif_param_dic = { s.split('=')[0]:s.split('=')[1] for s in gif_params}
+
+        g.save_gif(directory=args.dir,name=args.gif_name,
+            engine=args.engine,
+            selected_graphs=args.select_graph, **gif_param_dic)
+    
+    if args.tex:
+        tex_params = []
+        if args.tex_param is not None:
+            tex_params = args.tex_param
+        tex_param_dic = { s.split('=')[0]:s.split('=')[1] for s in tex_params}
+
+        g.save_tex(directory=args.dir,
+            selected_graphs=args.select_graph, **tex_param_dic)
+
+    if not args.q:
+        print(g.source(args.select_graph))
diff --git a/build/lib/clingraph/__main__.py b/build/lib/clingraph/__main__.py
new file mode 100644
index 0000000..eb4448b
--- /dev/null
+++ b/build/lib/clingraph/__main__.py
@@ -0,0 +1,7 @@
+from . import main
+
+if __name__ == "__main__":
+    """
+    Runs the main function
+    """
+    main()
\ No newline at end of file
diff --git a/build/lib/clingraph/clingraph.py b/build/lib/clingraph/clingraph.py
new file mode 100644
index 0000000..53de604
--- /dev/null
+++ b/build/lib/clingraph/clingraph.py
@@ -0,0 +1,356 @@
+"""
+    Definition of Clingraphs
+"""
+import logging
+import os
+import dot2tex
+import imageio
+import networkx as nx
+from IPython import display
+from clingo import Model
+from graphviz import Graph, Digraph
+from clingraph.clorm_orm import ClormORM
+log = logging.getLogger('custom')
+
+class Clingraph:
+
+    """
+    Clingraph contains the functionalities to transform sets of facts into graphviz objects that can be redered.
+    """
+
+    def __init__(self, type_: str = "graph", prefix: str = "", default_graph: str = "default", orm_class=ClormORM):
+        """
+        Creates a Clingraph object with the basic arguments
+
+        Args:
+            type (str): Type of Graph, either `graph` or `digraph`
+            prefix (str): Prefix used in all the predicates \
+                cosidered in the module
+            default_graph (str): The name of the default graph \
+                to which all nodes and edges with arity 1 will be assigned
+            orm_class (ClingraphORM): A class that implements the :py:class:`ClingraphORM` class. \
+                By default an implementation based on the tool ``clorm`` is used.
+        """
+        self.type_ = type_
+        self.prefix = prefix
+        self.default_graph = default_graph
+        self._orm = orm_class(self.prefix, default_graph=default_graph)
+        self._computed = False
+
+        self._graphs = {}  # All main graphs with corresponding nested subgraphs
+
+    def __str__(self):
+        """
+        String representation of the clingraph
+        """
+
+        return self.source()
+
+    def add_fact_string(self,  program: str):
+        """
+        Adds a program to the graph
+
+        Args:
+            program (str): An string containing a list of facts separated with a dot(``.``)
+
+        Raises:
+            InvalidSyntax: If the string can't be read as facts
+        """
+        log.debug("Adding string: %s", program)
+        self._orm.add_fact_string(program)
+
+    def add_fact_file(self,  file: str):
+        """
+        Adds files to the graph
+
+        Args:
+            file (str): The path to a file containing facts separated with a a dot(``.``)
+
+        Raises:
+            InvalidSyntax: If the file contains something other than facts
+        """
+        self._orm.add_fact_file(file)
+
+    def add_model(self,  model: Model):
+        """
+        Adds a model to the graph. Can be use on the `on_model` function of clingo.
+        To handle multiple models see :py:class`MultiModelClingraph`.
+
+        Args:
+            model (clingo.Model): A clingo model
+        """
+        self._orm.add_clingo_model(model)
+
+    def _new_single_graph(self):
+        """
+        Creates a graphviz instance with the attribues
+        """
+        if self.type_ == 'graph':
+            GraphClass = Graph
+        else:
+            GraphClass = Digraph
+
+        return GraphClass()
+
+    def _check_computed(self):
+        """
+        Raises an error if the graphs haven't been computed yet
+        """
+
+        if not self._computed:
+            raise RuntimeError("Can't obtain the graphviz object before computing the graphs")
+
+    def get_graphviz(self, graph_name=None):
+        """
+        Obtains a graphviz object for the given graph name.
+        If no graph_name is passed then the default graph will be returned
+
+        Args:
+            graph_name (str): The graph name defined in predicate ``graph(NAME).``
+
+        Returns:
+            A graphviz object
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+            ValueError: If the graph name is not valid
+        """
+        self._check_computed()
+        if graph_name is None:
+            graph_name = self.default_graph
+        if not graph_name in self._graphs:
+            raise ValueError(f"No graph with name '{graph_name}' found.")
+        return self._graphs[graph_name]
+
+    def _get_graphvizs(self, graph_names=None):
+        """
+        Obtains a list of graphvizs by name
+
+        Args:
+            graph_names (list): A list with the graph names defined in predicate ``graph(NAME).``
+
+        Returns:
+            A dictionary of graphviz object. A subset of the graphs attribute.
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+            ValueError: If the graph name is not valid
+        """
+        self._check_computed()
+        if graph_names is None:
+            return self._graphs
+        return {str(g_name):self.get_graphviz(g_name) for g_name in graph_names}
+
+    def get_facts(self):
+        """
+        Returns the current set of facts as a string
+
+        Returns:
+            A string containing all the facts after prepocessing
+        """
+        return str(self._orm)
+
+    def source(self,selected_graphs=None):
+        """
+        Returns the source code of the select graphs.
+
+        Args:
+            selected_graphs (list): The names of the graphs to be shown. By default all are selected.
+
+        Returns:
+            A string containing the graphviz source code for the selected graphs.
+            If no graphs are selected will return the source of all graphs.
+        """
+        if not self._computed:
+            return "// Graph hasn't been computed yet\n"
+        if len(self._graphs) == 0:
+            return "// Empty clingraph\n"
+        graphs = self._get_graphvizs(selected_graphs)
+        s = ""
+        for g_name in graphs:
+            s += "//"+ "-"*10 + g_name + "-"*10 + "\n"
+            s += self._graphs[g_name].source
+
+        return s
+
+
+    def compute_graphs(self):
+        """
+        Compute the graphs for the current list of facts.
+        It creates a Graphviz instance for each graph defined by the facts.
+        Graphs can then be obtained by name using the method :py:meth:`get_graphviz`.
+        """
+        if self._computed:
+            log.warning("Graph already computed, but will be computed again")
+
+        graphs = self._orm.get_all_graphs()
+        all_graphs = {}
+        for g in graphs:
+            graph = self._new_single_graph()
+            graph.name = str(g).strip('"')
+
+            graph.node_attr = self._orm.get_graph_global_element_attr("node", g)
+            graph.edge_attr = self._orm.get_graph_global_element_attr("edge", g)
+            graph.graph_attr = self._orm.get_element_attr("graph", g)
+
+            elements = ["node", "edge"]
+            for e_type in elements:
+                element_ids = self._orm.get_graph_elements(e_type, g)
+                for e in element_ids:
+                    attr = self._orm.get_element_attr(e_type, e)
+                    if e_type == 'node':
+                        graph.node(str(e).strip('"'), **attr)
+                    else:
+                        graph.edge(str(e.symbol.arguments[0]).strip('"'),
+                                   str(e.symbol.arguments[1]).strip('"'), **attr)
+
+            all_graphs[graph.name] = graph
+
+        self._graphs = self._nest_graphs(all_graphs)
+        self._computed = True
+
+    def _nest_graphs(self, all_graphs):
+        """
+        Nests all_graphs by assigning subgraphs to graphs
+
+        Args:
+            all_graphs (dict): Dicionary of all the graphs to be nested
+        """
+        nested_graphs = {}
+        graphs_ids = self._orm.get_all_graphs()
+        parents_dic = {}
+        for g_id in graphs_ids:
+            g_name = str(g_id).strip('"')
+            g = all_graphs[g_name]
+            parent_id = self._orm.parent_graph(g_id)
+            parent_name = str(parent_id).strip('"')
+            parents_dic[g_name] = parent_name
+
+        nx_g = nx.DiGraph(parents_dic.items())
+        final_order = list(nx.topological_sort(nx_g))
+
+        for g_name in final_order:
+            if g_name == 'None':
+                continue
+            parent_name = parents_dic[g_name]
+            g = all_graphs[g_name]
+            if parent_name == 'None':
+                nested_graphs[g_name] = g
+                continue
+            g_parent = all_graphs[parent_name]
+            g.name = 'cluster_'+g.name
+            g_parent.subgraph(g)
+
+        return nested_graphs
+
+    def save(self, directory='out', selected_graphs=None, format="pdf", name_prefix="", **kwargs):
+        # pylint: disable=redefined-builtin
+
+        """
+        Saves the graphs in the given directory.
+        The files will be named using the graph identifier in the predicate.
+
+        Args:
+            directory (str): The path to save the files
+            selected_graphs (list): The names of the graphs to be saved. By default all are saved
+            format (str): Output format: `pdf`, `svg` or `png`
+            name_prefix (str): A prefix for the names of the file
+
+        Any additional arguments are passed to the `graphviz.render` method
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+
+        """
+        graphs = self._get_graphvizs(selected_graphs)
+
+        for graph_name, graph in graphs.items():
+            file_name = os.path.join(
+                directory, f"{name_prefix}{graph_name}.{format}")
+            graph.render(
+                format=format,
+                outfile=file_name,
+                **kwargs,
+                cleanup=True)
+            log.info("Image saved in %s",file_name)
+
+    def save_gif(self, directory='out', name="clingraph", engine="dot",selected_graphs=None, **kwargs):
+        """
+        Creates a gif of all the graphs
+
+        Args:
+            directory: The path to save the files
+            name: The name of the gif
+            engine: A valid graphviz engine
+
+            Any additional arguments are passed to the  imageio.mimsave method
+        """
+        graphs = self._get_graphvizs(selected_graphs)
+
+        images_dir = os.path.join(directory, 'images')
+        self.save(images_dir,selected_graphs=selected_graphs, format="png", engine=engine)
+        images = []
+        file_name = os.path.join(directory, f'{name}.gif')
+        for graph_name in graphs:
+            images.append(imageio.imread(
+                os.path.join(images_dir, graph_name+".png")))
+        imageio.mimsave(file_name,
+                        images, **kwargs)
+
+        log.info("Gif saved in %s", file_name)
+
+    def save_tex(self, directory, name_prefix="", selected_graphs=None, **kwargs):
+        """
+        Creates a tex file ussing dot2tex
+        """
+        graphs = self._get_graphvizs(selected_graphs)
+        for graph_name, g in graphs.items():
+            source = g.source
+
+            texcode = dot2tex.dot2tex(source, **kwargs)
+
+            file_name = os.path.join(directory, f'{name_prefix}{graph_name}.tex')
+
+            with open(file_name,'w') as f:
+                f.write(texcode)
+            
+            log.info("Latex file saved in %s",file_name)
+
+
+
+    def _show(self, selected_graphs=None, **kwargs):
+        """
+        Shows the graphs in a frontend, such as jupyter
+
+        Args:
+            selected_graphs: The names of the graphs to be shown. By default all are shown
+
+        """
+        graphs = self._get_graphvizs(selected_graphs)
+        images_dir = 'out'
+        self.save(images_dir, selected_graphs=selected_graphs, format="png", **kwargs)
+        d = []
+        for graph_name in graphs:
+            d.append(graph_name)
+            d.append(display.Image(os.path.join('out', graph_name+".png")))
+        return display.display(*tuple(d))
+
+    def _repr_html_(self):
+        """
+        Shows the graphs in a frontend, such as jupyter
+        """
+        return self._show()
+
+    def show_gif(self, selected_graphs=None, engine="dot", **kwargs):
+        """
+        Shows the a gif of the graphs
+
+        Args:
+            engine (str): A valid graphviz engine
+            selected_graphs (list): The names of the graphs to be shown. By default all are shown
+
+        Any additional arguments are passed to the ``imageio.mimsave`` method
+        """
+        images_dir = 'out'
+        self.save_gif(images_dir, engine=engine,selected_graphs=selected_graphs, **kwargs)
+        return display.Image(os.path.join('out', "clingraph.gif"))
diff --git a/build/lib/clingraph/clorm_orm.py b/build/lib/clingraph/clorm_orm.py
new file mode 100644
index 0000000..cf554cd
--- /dev/null
+++ b/build/lib/clingraph/clorm_orm.py
@@ -0,0 +1,309 @@
+"""
+    Defines an ORM for clingraphs using clorm
+"""
+import clorm
+from clorm import Predicate, RawField, ComplexTerm, refine_field, ConstantField, SimpleField, Raw, FactBase
+from clingo.symbol import Function
+from clingraph.orm import ClingraphORM
+from clingraph.exception import InvalidSyntax
+
+
+class AttrID(ComplexTerm):
+    # pylint: disable=missing-class-docstring
+    attr_name = SimpleField
+    attr_pos = SimpleField
+
+    class Meta:
+        is_tuple = True
+
+
+ElementType = refine_field(ConstantField,
+                           ["graph", "node", "edge", "graph_nodes", "graph_edges"])
+
+
+class ClormORM(ClingraphORM):
+    """
+    Uses clorm as an ORM to query the facts defining the graph
+    """
+    # pylint: disable=too-many-instance-attributes
+
+    def __init__(self, prefix: str = "", default_graph:str ="default"):
+        """
+        Defines the unifier classes based on the prefix
+
+        Args:
+            prefix (str): The prefix to all predicate names
+        """
+        # pylint: disable=missing-class-docstring
+        class Graph(Predicate):
+            id = RawField
+
+            class Meta:
+                name = prefix+"graph"
+
+        class SubGraph(Predicate):
+            id = RawField
+            graph = RawField
+
+            class Meta:
+                name = prefix+"graph"
+
+        class Node(Predicate):
+            id = RawField
+            graph = RawField
+
+            class Meta:
+                name = prefix+"node"
+
+        class Edge(Predicate):
+            id = RawField
+            graph = RawField
+
+            class Meta:
+                name = prefix+"edge"
+
+        class Attr(Predicate):
+            element_type = ElementType
+            element_id = RawField
+            attr_id = AttrID.Field
+            attr_value = RawField
+
+            class Meta:
+                name = prefix+"attr"
+
+        class AttrSugar(Predicate):
+            element_type = ElementType
+            element_id = RawField
+            attr_id = SimpleField
+            attr_value = RawField
+
+            class Meta:
+                name = prefix+"attr"
+
+        class NodeSugar(Predicate):
+            id = RawField
+
+            class Meta:
+                name = prefix+"node"
+
+        class EdgeSugar(Predicate):
+            id = RawField
+
+            class Meta:
+                name = prefix+"edge"
+        # pylint: disable=invalid-name
+
+        self.Graph = Graph
+        self.SubGraph = SubGraph
+        self.Node = Node
+        self.Edge = Edge
+        self.Attr = Attr
+        self.NodeSugar = NodeSugar
+        self.EdgeSugar = EdgeSugar
+        self.AttrSugar = AttrSugar
+
+        self.default_graph = default_graph
+        self.fb = FactBase()
+        ClingraphORM.__init__(self,prefix)
+
+    def __str__(self):
+        """
+        Returns the current set of facts as a string
+        """
+        return self.fb.asp_str()
+
+
+    @property
+    def unifiers(self):
+        """
+        The list of all unifiers
+        """
+        main_unifiers = [self.Graph, self.SubGraph,
+                         self.Node, self.Edge, self.Attr]
+        sugar_unifiers = [self.NodeSugar, self.EdgeSugar, self.AttrSugar]
+        return main_unifiers+sugar_unifiers
+
+    def get_element_class(self, element_type):
+        """
+        Obtains an element class for a type given as a string
+
+        Args:
+            element_type (str): graph, edge or node
+        """
+        if element_type == "edge":
+            return self.Edge
+        if element_type == "node":
+            return self.Node
+        if element_type == "graph":
+            return self.Graph
+
+        raise ValueError("Invailid element type")
+
+    def add_fact_string(self, program):
+        """
+        Adds a string containing facts to the database
+
+        Args:
+            program (str): A string consisting of only facts, devided by a '.'
+        """
+        program = program.replace( '\\' ,'\\\\')
+
+        try:
+            fb = clorm.parse_fact_string(program, self.unifiers,raise_nonfact=True)
+            self.add_to_fb(fb)
+        except clorm.orm.symbols_facts.NonFactError as e:
+            msg = "The input string contains a complex structure that is not a fact."
+            raise InvalidSyntax(msg,str(e)) from None
+        except RuntimeError as e:
+            msg = "Syntactic error the input string can't be read as facts."
+            raise InvalidSyntax(msg,str(e)) from None
+
+    def add_fact_file(self, file):
+        """
+        Adds a file containing facts to the database
+
+        Args:
+            file (str): The path to the file
+        """
+        try:
+            fb = clorm.parse_fact_files([file], self.unifiers,raise_nonfact=True)
+            self.add_to_fb(fb)
+        except clorm.orm.symbols_facts.NonFactError as e:
+            msg = "The file contains a complex structure that is not a fact."
+            raise InvalidSyntax(msg,str(e)) from None
+        except RuntimeError as e:
+            msg = "Syntactic error the file, can't be read as facts."
+            raise InvalidSyntax(msg,str(e)) from None
+
+    def add_clingo_model(self, model):
+        """
+        Adds a clingo model to the database
+
+        Args:
+            model (clingo.Model): A model retured by clingo
+        """
+        symbols = model.symbols(atoms=True, shown=True)
+        fb = clorm.unify(self.unifiers, symbols)
+        self.add_to_fb(fb)
+
+    def add_to_fb(self, fb):
+        """
+        Adds a fact base to the current factbase
+        """
+        desugarized_fb = self.desugar(fb)
+        self.fb = self.fb.union(desugarized_fb)
+
+    def desugar(self, fb):
+        """
+        Desugar factbase
+        - for each node(ID) add node(ID,default)  same for edge
+        - replace attr(E,ID,Name,Val) with attr(E,ID,(Name,-1),Val)
+        """
+        q = fb.query(self.AttrSugar)
+        for attr in set(q.all()):
+            e = self.Attr(element_type=attr.element_type,
+                          element_id=attr.element_id,
+                          attr_value=attr.attr_value,
+                          attr_id=AttrID(attr_name=attr.attr_id, attr_pos=-1))
+            fb.remove(attr)
+            fb.add(e)
+
+        basic_element_classes = [
+            (self.NodeSugar, self.Node), (self.EdgeSugar, self.Edge)]
+        using_default = False
+        for C_Sugar, C in basic_element_classes:
+            q = fb.query(C_Sugar)
+            for node in set(q.all()):
+                using_default = True
+                e = C(id=node.id,
+                      graph=Raw(Function(self.default_graph)))
+                fb.remove(node)
+                fb.add(e)
+        if using_default:
+            fb.add(self.Graph(id=Raw(Function(self.default_graph))))
+
+        return fb
+
+    def get_all_graphs(self):
+        """
+        Gets a list if the identifiers for all the graphs
+        """
+        graph_ids = set([])
+        q = self.fb.query(self.Graph).select(self.Graph.id)
+        graph_ids = set(q.all())
+        q = self.fb.query(self.SubGraph).select(self.SubGraph.id)
+        graph_ids = graph_ids.union(set(q.all()))
+        return graph_ids
+
+    def parent_graph(self, graph_id):
+        """
+        Gets the parent graph for a given graph_id.
+        Returns None if there is not such supertype
+
+        Args:
+            graph_id: Identifier of the subgraph
+        """
+        q = self.fb.query(self.SubGraph).where(self.SubGraph.id == graph_id)
+        q = q.select(self.SubGraph.graph)
+        if len(list(q.all())) == 0:
+            return None
+        return list(q.all())[0]
+
+    def get_graph_global_element_attr(self, element_type, graph_id):
+        """
+        Gets the attributes for a global element: graph_nodes or graph_edges.
+        Returns a dictionary where the keys are attribute name and values are
+        attribute values.
+
+        Args:
+            element_type (str): The element type: 'edge' or 'node'
+            graph_id: Identifier of the graph
+        """
+        full_element_type = f"graph_{element_type}s"
+        return self.get_element_attr(full_element_type, graph_id)
+
+    def get_graph_elements(self, element_type, graph_id):
+        """
+        Gets the list of elements for a graph
+
+        Args:
+            element_type (str): The element type: 'edge' or 'node'
+            graph_id: Identifier of the graph
+        """
+        C = self.get_element_class(element_type)
+        q = self.fb.query(C).where(C.graph == graph_id).select(C.id)
+        return set(q.all())
+
+    def get_element_attr(self, element_type, element_id):
+        """
+        Gets the attributes a specific element
+        Returns a dictionary where the keys are attribute name and values are
+        attribute values.
+
+        Args:
+            element_type (str): The element type: 'graph', 'edge' or 'node'
+            element_id: Identifier of the element
+        """
+        q = self.fb.query(self.Attr)
+        q = q.where(self.Attr.element_type == element_type,
+                    self.Attr.element_id == element_id)
+        # pylint: disable=no-member
+        q = q.group_by(self.Attr.attr_id.attr_name)
+        q = q.select(self.Attr.attr_id.attr_pos, self.Attr.attr_value)
+        attrs = {}
+        for name, list_opts in q.all():
+            info = {"set": [], "idxs": [], "sep": " "}
+            for opt, val in list_opts:
+                val_str = str(val).strip('"')
+                if opt == "sep":
+                    info["sep"] = val_str
+                elif opt == -1:
+                    info["set"].append(val_str)
+                else:
+                    i = int(opt)
+                    if i >= len(info["idxs"]):
+                        info["idxs"] = info["idxs"] + \
+                            [""]*(1+i-len(info["idxs"]))
+                    info["idxs"][i] = val_str
+            attrs[str(name)] = info["sep"].join(info["set"]+info["idxs"]).replace('\\\\','\\')
+        return attrs
diff --git a/build/lib/clingraph/exception.py b/build/lib/clingraph/exception.py
new file mode 100644
index 0000000..d65685d
--- /dev/null
+++ b/build/lib/clingraph/exception.py
@@ -0,0 +1,8 @@
+import logging
+LOG = logging.getLogger('custom')
+
+class InvalidSyntax(Exception):
+    """Returned when the input syntax is not expected"""
+    def __init__(self, *args):
+        LOG.error("\n".join(args))
+        super().__init__("\n".join(args))
diff --git a/build/lib/clingraph/logger.py b/build/lib/clingraph/logger.py
new file mode 100644
index 0000000..30220d7
--- /dev/null
+++ b/build/lib/clingraph/logger.py
@@ -0,0 +1,74 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""Custom Logger."""
+
+import sys
+import logging
+
+
+class SingleLevelFilter(logging.Filter):
+    """Filter levels"""
+
+    def __init__(self, passlevel, reject):
+        # pylint: disable=super-init-not-called
+        self.passlevel = passlevel
+        self.reject = reject
+
+    def filter(self, record):
+        if self.reject:
+            return record.levelno != self.passlevel
+
+        return record.levelno == self.passlevel
+
+
+GREY = '\033[90m'
+BLUE = '\033[94m'
+GREEN = '\033[92m'
+YELLOW = '\033[93m'
+RED = '\033[91m'
+NORMAL = '\033[0m'
+
+
+def setup_logger(level=logging.INFO):
+    """Logger setup."""
+
+    logger = logging.getLogger('custom')
+    logger.propagate = False
+    logger.setLevel(level)
+    # log_message_str = "{}%(levelname)s:{} %(filename)s:%(funcName)s:%(lineno)d - %(message)s{}"
+    log_message_str = "{}%(levelname)s:{}  - %(message)s{}"
+    # INFO stream handler
+    info_sh = logging.StreamHandler(sys.stderr)
+    info_sh.addFilter(SingleLevelFilter(logging.INFO, False))
+    info_sh.setLevel(logging.INFO)
+    formatter = logging.Formatter(
+        log_message_str.format(GREEN, GREY, NORMAL))
+    info_sh.setFormatter(formatter)
+    logger.addHandler(info_sh)
+
+    # WARNING stream handleer
+    warn_sh = logging.StreamHandler(sys.stderr)
+    warn_sh.addFilter(SingleLevelFilter(logging.WARNING, False))
+    warn_sh.setLevel(logging.WARN)
+    formatter = logging.Formatter(
+        log_message_str.format(YELLOW, GREY, NORMAL))
+    warn_sh.setFormatter(formatter)
+    logger.addHandler(warn_sh)
+
+    # DEBUG stream handler
+    debug_sh = logging.StreamHandler(sys.stderr)
+    debug_sh.addFilter(SingleLevelFilter(logging.DEBUG, False))
+    debug_sh.setLevel(logging.DEBUG)
+    formatter = logging.Formatter(
+        log_message_str.format(BLUE, GREY, NORMAL))
+    debug_sh.setFormatter(formatter)
+    logger.addHandler(debug_sh)
+
+    # ERROR stream handler
+    error_sh = logging.StreamHandler(sys.stderr)
+    error_sh.addFilter(SingleLevelFilter(logging.ERROR, False))
+    error_sh.setLevel(logging.ERROR)
+    formatter = logging.Formatter(
+        log_message_str.format(RED, GREY, NORMAL))
+    error_sh.setFormatter(formatter)
+    logger.addHandler(error_sh)
diff --git a/build/lib/clingraph/multigraph.py b/build/lib/clingraph/multigraph.py
new file mode 100644
index 0000000..a1f6762
--- /dev/null
+++ b/build/lib/clingraph/multigraph.py
@@ -0,0 +1,195 @@
+"""
+    Definition of a MultiModelClingraph
+"""
+import os
+import logging
+import json
+from clingraph.clingraph import Clingraph
+from clingraph.exception import InvalidSyntax
+log = logging.getLogger('custom')
+
+
+class MultiModelClingraph:
+    """
+    Holds multiple models in the same structure. Each model will be associated with a :py:class:`Clingraph` object.
+
+    The class is specially intended for integration with clingo.
+    """
+
+    def __init__(self, **kargs):
+        """
+        Constructs a new multi model clingraph.
+        Accepts all the arguments in the :py:class:`Clingraph` constructor which will be used in the creation of each :py:class:`Clingraph` object.
+        """
+        self.clingraphs = {}
+        # pylint: disable=unnecessary-lambda
+        self._new_clingraph = lambda: Clingraph(**kargs)
+
+    def __str__(self):
+        """
+        String representation
+        """
+        return self.source()
+
+    def _name(self, number, costs):
+        """
+        Returns the name of a clingraph related to a model
+
+        Args:
+            numeber (int): The model number. Will be used to access the model
+            costs (list): A list of costs for the model
+
+        Returns:
+            The name of a clingraph related to a model and cost
+        """
+        cost_name = ""
+        for cost in costs:
+            cost_name += f"-{cost}"
+        if cost_name != "":
+            cost_name = "cost"+cost_name+"__"
+        name = f"{cost_name}model-{number:04d}"
+        return name
+
+    def source(self, selected_graphs=None, selected_models=None):
+        """
+        Obtains the source code for the graphs in all the models.
+        A selecttion is also allowed as parameters, if no selection is
+        added all models and graphs are returned.
+
+        Args:
+            selected_graphs (list): List of the names of the graphs to be selected
+            selected_models (list): List of the model numbers to be selected
+
+        Returns:
+            A string containing the source graphviz code of the selected models and graphs.
+
+        Raises:
+            ValueError: If the model number is not valid
+        """
+
+        s = ""
+        if not selected_models:
+            selected_models = self.clingraphs.keys()
+        for m_num in selected_models:
+            m_num=int(m_num)
+            if m_num not in self.clingraphs:
+                raise ValueError(f"Invalid model number {m_num}")
+            g_dic = self.clingraphs[m_num]
+            s += "\n//"+"="*25 + "\n"
+            s += f"//\tModel: {m_num} Costs: {g_dic['costs']} \n"
+            s += "//"+"="*25 + "\n\n"
+            s += g_dic["clingraph"].source(selected_graphs=selected_graphs)
+        return s
+
+    def get_clingraph(self, model_number):
+        """
+        Returns the clingraph associated to the model number
+
+        Args:
+            model_number: The model number to be obtained
+
+        Returns:
+            A :py:class:`Clingraph` object associated to the model number
+
+        Raises:
+            ValueError: If the model number is not valid
+        """
+        model_number = int(model_number)
+        if model_number not in self.clingraphs:
+            raise ValueError("Invalid model number")
+        return self.clingraphs[model_number]['clingraph']
+
+    def add_model(self, model):
+        """
+        Creates a new clingraph based on a clingo model.
+
+        Args:
+            model (clingo.Model): A model returned by clingos solver
+
+        Example:
+            Create a multi model clingraph using clingos API::
+
+                from clingraph import MultiModelClingraph
+                from clingo import Control
+                ctl = Control(["-n2"])
+                g = MultiModelClingraph()
+                ctl.add("base", [], "1{node(a);node(b)}1.")
+                ctl.ground([("base", [])])
+                ctl.solve(on_model=g.add_model)
+        """
+        g = self._new_clingraph()
+        g.add_model(model)
+        name = self._name(model.number, model.cost)
+        self.clingraphs[int(model.number)] = {
+            "clingraph": g,
+            "name": name,
+            "costs": model.cost}
+
+    def load_json(self, json_str):
+        """
+        Loads multiple models from a json that is the output of clingo
+        The json oput is obtained using the option `--outf=2`
+
+        Args:
+            json_str (str): A string representing the json
+        """
+        try:
+
+            j = json.loads(json_str.encode())
+            log.debug("Loading json %s",json.dumps(j))
+            if "Call" not in j:
+                raise InvalidSyntax('The json should have key "Call"')
+            if not isinstance(j["Call"], list):
+                raise InvalidSyntax('The json key "Call" should have an array as value')
+            if len(j["Call"]) != 1:
+                raise InvalidSyntax('The json key "Call" should have an array as value with only one element')
+            if "Witnesses" not in j["Call"][0]:
+                raise InvalidSyntax('The json key "Call" should have an array as value with only one element with the key "Wintnesses"')
+
+            for i, w in enumerate(j["Call"][0]["Witnesses"]):
+                model_number = i+1
+                prg_str = "\n".join([f"{v}." for v in w["Value"]])
+                g = self._new_clingraph()
+                g.add_fact_string(prg_str)
+                costs = [] if not "Costs" in w else w["Costs"]
+                name = self._name(model_number, costs)
+                self.clingraphs[model_number] = {
+                    "clingraph": g,
+                    "name": name,
+                    "costs": costs}
+
+        except json.JSONDecodeError as e:
+            raise InvalidSyntax('The json can not be read.',str(e)) from None
+
+
+    def compute_graphs(self):
+        """
+        Computes all the clingraphs
+        """
+        for g_dic in self.clingraphs.values():
+            g_dic['clingraph'].compute_graphs()
+
+    def save(self, selected_models=None, directory='out', **kargs):
+        """
+        Saves all the clingraphs using one directory per model
+        A selecttion is also allowed as parameters, if no selection is
+        added all models and graphs are saved.
+
+        Args:
+            selected_models (list): List of the model numbers to be selected
+
+        Any additional arguments are passed the the :py:meth:`clingraph.Clingraph.save` function of each clingraph
+
+        Raises:
+            ValueError: If the model number in selected_models not valid
+        """
+        if not selected_models:
+            selected_models = self.clingraphs.keys()
+
+        for m_num in selected_models:
+            m_num=int(m_num)
+            if m_num not in self.clingraphs:
+                raise ValueError("Invalid model number")
+            g_dic = self.clingraphs[m_num]
+            new_directory = os.path.join(directory, g_dic['name'])
+            g_dic['clingraph'].save(new_directory, **kargs)
diff --git a/build/lib/clingraph/orm.py b/build/lib/clingraph/orm.py
new file mode 100644
index 0000000..5e7b307
--- /dev/null
+++ b/build/lib/clingraph/orm.py
@@ -0,0 +1,90 @@
+class ClingraphORM():
+    """
+    An interface for an ORM used in clingraph
+    """
+
+    def __init__(self, prefix: str = ""):
+        """
+        Creates an ORM that will serve as a database for the facts
+        
+        Args:
+            prefix (str): The prefix to all predicate names
+        """
+        pass
+
+    def add_fact_string(self, program):
+        """
+        Adds a string containing facts to the database
+        
+        Args:
+            program (str): A string consisting of only facts, devided by a '.'
+        """
+        pass
+
+    def add_fact_file(self, file):
+        """
+        Adds a file containing facts to the database
+        
+        Args:
+            file (str): The path to the file
+        """
+        pass
+
+    def add_clingo_model(self, model):
+        """
+        Adds a clingo model to the database
+        
+        Args:
+            model (clingo.Model): A model retured by clingo
+        """
+        pass
+
+    def get_all_graphs(self):
+        """
+        Gets a list if the identifiers for all the graphs
+        """
+        pass
+
+    def parent_graph(self, graph_id):
+        """
+        Gets the parent graph for a given graph_id.
+        Returns None if there is not such supertype
+        
+        Args:
+            graph_id: Identifier of the subgraph
+        """
+        pass
+
+    def get_graph_global_element_attr(self, element_type, graph_id):
+        """
+        Gets the attributes for a global element: graph_nodes or graph_edges.
+        Returns a dictionary where the keys are attribute name and values are
+        attribute values.
+        
+        Args:
+            element_type (str): The element type: 'edge' or 'node'
+            graph_id: Identifier of the graph
+        """
+        pass
+
+    def get_graph_elements(self, element_type, graph_id):
+        """
+        Gets the list of elements for a graph
+        
+        Args:
+            element_type (str): The element type: 'edge' or 'node'
+            graph_id: Identifier of the graph
+        """
+        pass
+
+    def get_element_attr(self, element_type, element_id):
+        """
+        Gets the attributes a specific element
+        Returns a dictionary where the keys are attribute name and values are
+        attribute values.
+        
+        Args:
+            element_type (str): The element type: 'graph', 'edge' or 'node'
+            element_id: Identifier of the element
+        """
+        pass
diff --git a/clingraph/__init__.py b/clingraph/__init__.py
index b68183e..972ae9a 100644
--- a/clingraph/__init__.py
+++ b/clingraph/__init__.py
@@ -1,30 +1,41 @@
+"""
+    Package inports and command line functionality
+"""
 from ast import parse
 import sys
-from clingraph.clingraph import Clingraph
 import logging
-import argparse, textwrap
+import argparse
+import textwrap
+import pkg_resources
+from clingraph.clingraph import Clingraph
 from clingraph.logger import setup_logger
 from clingraph.multigraph import MultiModelClingraph
 from clingraph.orm import ClingraphORM
+from clingraph.exception import InvalidSyntax
+version = pkg_resources.require("clingraph")[0].version
 
 def get_parser():
+    """
+    Get the parser for the command line
+    """
+    # pylint: disable=anomalous-backslash-in-string
     parser = argparse.ArgumentParser(description=textwrap.dedent("""
-        _ _                         _    
-    __| (_)_ _  __ _ _ _ __ _ _ __| |_  
-    / _| | | ' \/ _` | '_/ _` | '_ \ ' \ 
+        _ _                         _
+     __| (_)_ _  __ _ _ _ __ _ _ __| |_
+    / _| | | ' \/ _` | '_/ _` | '_ \ ' \\
     \__|_|_|_||_\__, |_| \__,_| .__/_||_|
-                |___/         |_|        
+                |___/         |_|
 
-    Clingraph is a package to generate graph visualizations 
+    Clingraph is a package to generate graph visualizations
     based on facts that can be computed by logic programs.
     Special features for integration with clingo.
     """),
     formatter_class=argparse.RawTextHelpFormatter)
     parser.add_argument('files', type=argparse.FileType('r'), nargs='*')
-    parser.add_argument('-q', 
+    parser.add_argument('-q',
                     help = """Flag to have a quiet output where the graphs soruce wont be rendered""",
                     action='store_true')
-    parser.add_argument('-log', default="warning", 
+    parser.add_argument('-log', default="warning",
                     choices=['debug', 'info', 'error', 'warning'],
                     help=textwrap.dedent('''\
                         Provide logging level.
@@ -32,6 +43,8 @@ def get_parser():
                             (default: %(default)s)'''),
                     type=str,
                     metavar='')
+    parser.add_argument('--version','-v', action='version',
+                    version=f'%(prog)s {version}')
 
 
     input_params = parser.add_argument_group('Graph generation')
@@ -49,7 +62,7 @@ def get_parser():
                     help = textwrap.dedent('''\
                         Prefix expected in all the considered facts'''),
                     type=str,
-                    metavar='') 
+                    metavar='')
     input_params.add_argument('--default-graph',
                     default = 'default',
                     help = textwrap.dedent('''\
@@ -60,7 +73,7 @@ def get_parser():
                     metavar='')
 
     render_params = parser.add_argument_group('Graph rendering')
-    render_params.add_argument('--render', 
+    render_params.add_argument('--render',
                     help = """Flag to render the graphs and save in files""",
                     action='store_true')
 
@@ -71,7 +84,7 @@ def get_parser():
                             (default: %(default)s)'''),
                     type=str,
                     metavar='')
-    
+
     render_params.add_argument('--out-file-prefix',
                     default = '',
                     help = textwrap.dedent('''\
@@ -121,8 +134,8 @@ def get_parser():
                     nargs='*',
                     metavar='')
 
-    render_params.add_argument('--gif', 
-                    help = """Flag to generate a giv from all the generated files""",
+    render_params.add_argument('--gif',
+                    help = """Flag to generate a gif from all the generated files""",
                     action='store_true')
 
     render_params.add_argument('--gif-name',
@@ -141,9 +154,23 @@ def get_parser():
                 nargs='*',
                 metavar='')
 
+
+    render_params.add_argument('--tex',
+                    help = """Flag to generate a latex tex file""",
+                    action='store_true')
+
+    render_params.add_argument('--tex-param',
+                default = '',
+                help = textwrap.dedent('''\
+                A string containing a parameter for the tex file generation by dot2tex.
+                String should have the form arg_name=arg_value '''),
+                type=str,
+                nargs='*',
+                metavar='')
+
     multi_params = parser.add_argument_group('Multi model graphs')
 
-    multi_params.add_argument('--json', 
+    multi_params.add_argument('--json',
                 help = textwrap.dedent('''\
                 Flag to indicate the creation of multiple models from a json.
                 The graphs will be generated for each stable model.
@@ -165,37 +192,37 @@ def main():
     ####### Parser
     parser= get_parser()
     args = parser.parse_args()
-    
+
     ####### Logger
-    LOG = logging.getLogger('custom')
+    log = logging.getLogger('custom')
     levels = {'error': logging.ERROR, 'warn': logging.WARNING,
               'warning': logging.WARNING, 'info': logging.INFO, 'debug': logging.DEBUG}
     setup_logger(levels.get(args.log.lower()))
 
-    LOG.info(args)
+    log.debug(args)
     ####### Load input
     input_str=""
     if not sys.stdin.isatty():
         input_str = sys.stdin.read()
-    
+
     g = None
     if not args.json:
         g = Clingraph(type_ = args.type, prefix=args.prefix, default_graph=args.default_graph)
 
         for f in args.files:
-            LOG.info(f"Adding file {f.name}")
+            log.info("Adding file %s",f.name)
             g.add_fact_file(f.name)
 
         if input_str:
             g.add_fact_string(input_str)
 
     else:
-        LOG.info("Loading a multi model graph from json")
+        log.info("Loading a multi model graph from json")
 
         g = MultiModelClingraph(type_ = args.type, prefix=args.prefix, default_graph=args.default_graph)
 
         g.load_json(input_str)
-    
+
     g.compute_graphs()
 
     ####### Output
@@ -207,15 +234,28 @@ def main():
         if args.render_param is not None:
             render_params = args.render_param
         render_param_dic = { s.split('=')[0]:s.split('=')[1] for s in render_params}
-        g.save(args.dir,format=args.format,name_prefix=args.out_file_prefix,selected_graphs=args.select_graph, view=args.view, engine=args.engine)
+        g.save(directory=args.dir,format=args.format,name_prefix=args.out_file_prefix,
+            selected_graphs=args.select_graph,
+            view=args.view, engine=args.engine,
+            **render_param_dic)
     if args.gif:
         gif_params = []
         if args.gif_param is not None:
             gif_params = args.gif_param
         gif_param_dic = { s.split('=')[0]:s.split('=')[1] for s in gif_params}
 
-        g.save_gif(args.dir,name=args.gif_name,engine=args.engine,selected_graphs=args.select_graph, **gif_param_dic)
-    
+        g.save_gif(directory=args.dir,name=args.gif_name,
+            engine=args.engine,
+            selected_graphs=args.select_graph, **gif_param_dic)
+
+    if args.tex:
+        tex_params = []
+        if args.tex_param is not None:
+            tex_params = args.tex_param
+        tex_param_dic = { s.split('=')[0]:s.split('=')[1] for s in tex_params}
+
+        g.save_tex(directory=args.dir,
+            selected_graphs=args.select_graph, **tex_param_dic)
+
     if not args.q:
         print(g.source(args.select_graph))
-
diff --git a/clingraph/__main__.py b/clingraph/__main__.py
index eb4448b..1738b15 100644
--- a/clingraph/__main__.py
+++ b/clingraph/__main__.py
@@ -1,7 +1,7 @@
+"""
+    Main file for the package
+"""
 from . import main
 
 if __name__ == "__main__":
-    """
-    Runs the main function
-    """
-    main()
\ No newline at end of file
+    main()
diff --git a/clingraph/clingraph.py b/clingraph/clingraph.py
index 19bd43f..3c27c13 100644
--- a/clingraph/clingraph.py
+++ b/clingraph/clingraph.py
@@ -1,34 +1,35 @@
 """
     Definition of Clingraphs
 """
-import clorm
-import imageio
 import logging
 import os
+import dot2tex
+import imageio
 import networkx as nx
 from IPython import display
-from fileinput import filename
-from clorm import FactBase
 from clingo import Model
 from graphviz import Graph, Digraph
 from clingraph.clorm_orm import ClormORM
-LOG = logging.getLogger('custom')
+log = logging.getLogger('custom')
 
 class Clingraph:
 
     """
-    Clingraph contains the functionalities to transform sets of facts into graphviz objects that can be redered. 
+    Clingraph contains the functionalities to transform sets of facts into graphviz objects that can be redered.
     """
 
     def __init__(self, type_: str = "graph", prefix: str = "", default_graph: str = "default", orm_class=ClormORM):
         """
         Creates a Clingraph object with the basic arguments
-        
+
         Args:
             type (str): Type of Graph, either `graph` or `digraph`
-            prefix (str): Prefix used in all the predicates cosidered in the module
-            default_graph (str): The name of the default graph to which all nodes and edges with arity 1 will be assigned
-            orm_class (ClingraphORM): A class that implements the :py:class:`ClingraphORM` class. By default an implementation based on the tool ``clorm`` is used.
+            prefix (str): Prefix used in all the predicates \
+                cosidered in the module
+            default_graph (str): The name of the default graph \
+                to which all nodes and edges with arity 1 will be assigned
+            orm_class (ClingraphORM): A class that implements the :py:class:`ClingraphORM` class. \
+                By default an implementation based on the tool ``clorm`` is used.
         """
         self.type_ = type_
         self.prefix = prefix
@@ -48,27 +49,33 @@ def __str__(self):
     def add_fact_string(self,  program: str):
         """
         Adds a program to the graph
-        
+
         Args:
             program (str): An string containing a list of facts separated with a dot(``.``)
+
+        Raises:
+            InvalidSyntax: If the string can't be read as facts
         """
-        LOG.debug(f"Adding string: {program}")
+        log.debug("Adding string: %s", program)
         self._orm.add_fact_string(program)
 
     def add_fact_file(self,  file: str):
         """
         Adds files to the graph
-        
+
         Args:
             file (str): The path to a file containing facts separated with a a dot(``.``)
+
+        Raises:
+            InvalidSyntax: If the file contains something other than facts
         """
-        self._orm.add_fact_files(file)
+        self._orm.add_fact_file(file)
 
     def add_model(self,  model: Model):
         """
         Adds a model to the graph. Can be use on the `on_model` function of clingo.
         To handle multiple models see :py:class`MultiModelClingraph`.
-        
+
         Args:
             model (clingo.Model): A clingo model
         """
@@ -76,7 +83,7 @@ def add_model(self,  model: Model):
 
     def _new_single_graph(self):
         """
-        Creates a graphviz instance with the attribues
+        Creates a graphviz instance with the attributes
         """
         if self.type_ == 'graph':
             GraphClass = Graph
@@ -92,7 +99,7 @@ def _check_computed(self):
 
         if not self._computed:
             raise RuntimeError("Can't obtain the graphviz object before computing the graphs")
-    
+
     def get_graphviz(self, graph_name=None):
         """
         Obtains a graphviz object for the given graph name.
@@ -100,24 +107,46 @@ def get_graphviz(self, graph_name=None):
 
         Args:
             graph_name (str): The graph name defined in predicate ``graph(NAME).``
-        
+
         Returns:
             A graphviz object
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+            ValueError: If the graph name is not valid
         """
+        self._check_computed()
         if graph_name is None:
             graph_name = self.default_graph
-        if not self._computed:
-            raise RuntimeError("Can't obtain the graphviz object before computing the graphs")
         if not graph_name in self._graphs:
             raise ValueError(f"No graph with name '{graph_name}' found.")
         return self._graphs[graph_name]
 
+    def _get_graphvizs(self, graph_names=None):
+        """
+        Obtains a list of graphvizs by name
+
+        Args:
+            graph_names (list): A list with the graph names defined in predicate ``graph(NAME).``
+
+        Returns:
+            A dictionary of graphviz object. A subset of the graphs attribute.
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+            ValueError: If the graph name is not valid
+        """
+        self._check_computed()
+        if graph_names is None:
+            return self._graphs
+        return {str(g_name):self.get_graphviz(g_name) for g_name in graph_names}
+
     def get_facts(self):
         """
         Returns the current set of facts as a string
 
         Returns:
-            A string containing all the facts after prepocessing
+            A string containing all the facts after preprocessing
         """
         return str(self._orm)
 
@@ -136,13 +165,9 @@ def source(self,selected_graphs=None):
             return "// Graph hasn't been computed yet\n"
         if len(self._graphs) == 0:
             return "// Empty clingraph\n"
-        if not selected_graphs:
-            selected_graphs = list(self._graphs.keys())
+        graphs = self._get_graphvizs(selected_graphs)
         s = ""
-        for g_name in selected_graphs:
-            if g_name not in self._graphs:
-                LOG.warn(f"Graph name: {g_name} not found, ignored")
-                continue
+        for g_name in graphs:
             s += "//"+ "-"*10 + g_name + "-"*10 + "\n"
             s += self._graphs[g_name].source
 
@@ -151,10 +176,13 @@ def source(self,selected_graphs=None):
 
     def compute_graphs(self):
         """
-        Compute the graphs for the current list of facts. 
+        Compute the graphs for the current list of facts.
         It creates a Graphviz instance for each graph defined by the facts.
         Graphs can then be obtained by name using the method :py:meth:`get_graphviz`.
         """
+        if self._computed:
+            log.warning("Graph already computed, but will be computed again")
+
         graphs = self._orm.get_all_graphs()
         all_graphs = {}
         for g in graphs:
@@ -184,9 +212,9 @@ def compute_graphs(self):
     def _nest_graphs(self, all_graphs):
         """
         Nests all_graphs by assigning subgraphs to graphs
-        
+
         Args:
-            all_graphs (dict): Dicionary of all the graphs to be nested
+            all_graphs (dict): Dictionary of all the graphs to be nested
         """
         nested_graphs = {}
         graphs_ids = self._orm.get_all_graphs()
@@ -215,24 +243,28 @@ def _nest_graphs(self, all_graphs):
 
         return nested_graphs
 
-    def save(self, directory, selected_graphs=None, format="pdf", name_prefix="", **kwargs):
+    def save(self, directory='out', selected_graphs=None, format="pdf", name_prefix="", **kwargs):
+        # pylint: disable=redefined-builtin
+
         """
         Saves the graphs in the given directory.
         The files will be named using the graph identifier in the predicate.
-        
+
         Args:
             directory (str): The path to save the files
             selected_graphs (list): The names of the graphs to be saved. By default all are saved
             format (str): Output format: `pdf`, `svg` or `png`
             name_prefix (str): A prefix for the names of the file
-        
+
         Any additional arguments are passed to the `graphviz.render` method
+
+        Raises:
+            RuntimeError: When the graph hasn't been computed yet
+
         """
-        if selected_graphs is not None:
-            selected_graphs = [str(s) for s in selected_graphs]
-        for graph_name, graph in self._graphs.items():
-            if selected_graphs and graph_name not in selected_graphs:
-                continue
+        graphs = self._get_graphvizs(selected_graphs)
+
+        for graph_name, graph in graphs.items():
             file_name = os.path.join(
                 directory, f"{name_prefix}{graph_name}.{format}")
             graph.render(
@@ -240,12 +272,12 @@ def save(self, directory, selected_graphs=None, format="pdf", name_prefix="", **
                 outfile=file_name,
                 **kwargs,
                 cleanup=True)
-            LOG.info(f"Image saved in {file_name}")
+            log.info("Image saved in %s",file_name)
 
-    def save_gif(self, directory, name="clingraph", engine="dot",selected_graphs=None, **kwargs):
+    def save_gif(self, directory='out', name="clingraph", engine="dot",selected_graphs=None, **kwargs):
         """
         Creates a gif of all the graphs
-        
+
         Args:
             directory: The path to save the files
             name: The name of the gif
@@ -253,47 +285,52 @@ def save_gif(self, directory, name="clingraph", engine="dot",selected_graphs=Non
 
             Any additional arguments are passed to the  imageio.mimsave method
         """
+        graphs = self._get_graphvizs(selected_graphs)
+
         images_dir = os.path.join(directory, 'images')
-        if selected_graphs is not None:
-            selected_graphs = [str(s) for s in selected_graphs]
         self.save(images_dir,selected_graphs=selected_graphs, format="png", engine=engine)
         images = []
         file_name = os.path.join(directory, f'{name}.gif')
-        for graph_name, _ in self._graphs.items():
-            if selected_graphs and graph_name not in selected_graphs:
-                continue
+        for graph_name in graphs:
             images.append(imageio.imread(
                 os.path.join(images_dir, graph_name+".png")))
         imageio.mimsave(file_name,
                         images, **kwargs)
 
-        LOG.info(f"Gif saved in {file_name}")
+        log.info("Gif saved in %s", file_name)
+
+    def save_tex(self, directory="out", name_prefix="", selected_graphs=None, **kwargs):
+        """
+        Creates a tex file ussing dot2tex
+        """
+        graphs = self._get_graphvizs(selected_graphs)
+        for graph_name, g in graphs.items():
+            source = g.source
+
+            texcode = dot2tex.dot2tex(source, **kwargs)
+
+            file_name = os.path.join(directory, f'{name_prefix}{graph_name}.tex')
+
+            with open(file_name,'w',encoding='utf-8') as f:
+                f.write(texcode)
+
+            log.info("Latex file saved in %s",file_name)
 
-    # def save_tex(self, directory, name_prefix=""):
-    #     """
-    #     Creates a tex file ussing dot2tex
-    #     """
-    #     return NotImplementedError
 
 
     def _show(self, selected_graphs=None, **kwargs):
         """
         Shows the graphs in a frontend, such as jupyter
-        
+
         Args:
             selected_graphs: The names of the graphs to be shown. By default all are shown
 
         """
-        if not self._computed:
-            return display.display("Clingraph not yet computed. Call compute_graphs().")
+        graphs = self._get_graphvizs(selected_graphs)
         images_dir = 'out'
-        if selected_graphs is not None:
-            selected_graphs = [str(s) for s in selected_graphs]
         self.save(images_dir, selected_graphs=selected_graphs, format="png", **kwargs)
         d = []
-        for graph_name, graph in self._graphs.items():
-            if selected_graphs and graph_name not in selected_graphs:
-                continue
+        for graph_name in graphs:
             d.append(graph_name)
             d.append(display.Image(os.path.join('out', graph_name+".png")))
         return display.display(*tuple(d))
@@ -307,11 +344,11 @@ def _repr_html_(self):
     def show_gif(self, selected_graphs=None, engine="dot", **kwargs):
         """
         Shows the a gif of the graphs
-        
+
         Args:
             engine (str): A valid graphviz engine
             selected_graphs (list): The names of the graphs to be shown. By default all are shown
-        
+
         Any additional arguments are passed to the ``imageio.mimsave`` method
         """
         images_dir = 'out'
diff --git a/clingraph/clorm_orm.py b/clingraph/clorm_orm.py
index 443ef0b..5a5fb07 100644
--- a/clingraph/clorm_orm.py
+++ b/clingraph/clorm_orm.py
@@ -1,16 +1,15 @@
 """
     Defines an ORM for clingraphs using clorm
 """
-
 import clorm
-import logging
-from clorm import Predicate, RawField, ComplexTerm, IntegerField, StringField, refine_field, ConstantField, SimpleField, Raw, FactBase
-from clingo.symbol import parse_term, Number, String, Function
+from clorm import Predicate, RawField, ComplexTerm, refine_field, ConstantField, SimpleField, Raw, FactBase
+from clingo.symbol import Function
 from clingraph.orm import ClingraphORM
-LOG = logging.getLogger('custom')
+from clingraph.exception import InvalidSyntax
 
 
 class AttrID(ComplexTerm):
+    # pylint: disable=missing-class-docstring
     attr_name = SimpleField
     attr_pos = SimpleField
 
@@ -26,14 +25,16 @@ class ClormORM(ClingraphORM):
     """
     Uses clorm as an ORM to query the facts defining the graph
     """
+    # pylint: disable=too-many-instance-attributes
 
     def __init__(self, prefix: str = "", default_graph:str ="default"):
         """
         Defines the unifier classes based on the prefix
-        
+
         Args:
             prefix (str): The prefix to all predicate names
         """
+        # pylint: disable=missing-class-docstring
         class Graph(Predicate):
             id = RawField
 
@@ -90,6 +91,7 @@ class EdgeSugar(Predicate):
 
             class Meta:
                 name = prefix+"edge"
+        # pylint: disable=invalid-name
 
         self.Graph = Graph
         self.SubGraph = SubGraph
@@ -102,6 +104,7 @@ class Meta:
 
         self.default_graph = default_graph
         self.fb = FactBase()
+        ClingraphORM.__init__(self,prefix)
 
     def __str__(self):
         """
@@ -123,7 +126,7 @@ def unifiers(self):
     def get_element_class(self, element_type):
         """
         Obtains an element class for a type given as a string
-        
+
         Args:
             element_type (str): graph, edge or node
         """
@@ -134,25 +137,31 @@ def get_element_class(self, element_type):
         if element_type == "graph":
             return self.Graph
 
+        raise ValueError("Invalid element type")
+
     def add_fact_string(self, program):
         """
         Adds a string containing facts to the database
-        
+
         Args:
-            program (str): A string consisting of only facts, devided by a '.'
+            program (str): A string consisting of only facts, divided by a '.'
         """
+        program = program.replace( '\\' ,'\\\\')
+
         try:
             fb = clorm.parse_fact_string(program, self.unifiers,raise_nonfact=True)
             self.add_to_fb(fb)
         except clorm.orm.symbols_facts.NonFactError as e:
-            LOG.warning(f"The input string contains a complex structure that is not a fact. The whole input string was ignored. \n{str(e)}")
+            msg = "The input string contains a complex structure that is not a fact."
+            raise InvalidSyntax(msg,str(e)) from None
         except RuntimeError as e:
-            LOG.warning(f"Syntactic error the input string can't be read as facts. The whole input string was ignored. \n{str(e)}")
+            msg = "Syntactic error the input string can't be read as facts."
+            raise InvalidSyntax(msg,str(e)) from None
 
-    def add_fact_files(self, file):
+    def add_fact_file(self, file):
         """
         Adds a file containing facts to the database
-        
+
         Args:
             file (str): The path to the file
         """
@@ -160,16 +169,18 @@ def add_fact_files(self, file):
             fb = clorm.parse_fact_files([file], self.unifiers,raise_nonfact=True)
             self.add_to_fb(fb)
         except clorm.orm.symbols_facts.NonFactError as e:
-            LOG.warning(f"The file {file} contains a complex structure that is not a fact. The file was ignored. \n{str(e)}")
+            msg = "The file contains a complex structure that is not a fact."
+            raise InvalidSyntax(msg,str(e)) from None
         except RuntimeError as e:
-            LOG.warning(f"Syntactic error the file can't be read as facts. The whole file was ignored. \n{str(e)}")
+            msg = "Syntactic error the file, can't be read as facts."
+            raise InvalidSyntax(msg,str(e)) from None
 
     def add_clingo_model(self, model):
         """
         Adds a clingo model to the database
-        
+
         Args:
-            model (clingo.Model): A model retured by clingo
+            model (clingo.Model): A model returned by clingo
         """
         symbols = model.symbols(atoms=True, shown=True)
         fb = clorm.unify(self.unifiers, symbols)
@@ -179,8 +190,8 @@ def add_to_fb(self, fb):
         """
         Adds a fact base to the current factbase
         """
-        desugarized_fb = self.desugar(fb)
-        self.fb = self.fb.union(desugarized_fb)
+        processed_fb = self.desugar(fb)
+        self.fb = self.fb.union(processed_fb)
 
     def desugar(self, fb):
         """
@@ -228,7 +239,7 @@ def parent_graph(self, graph_id):
         """
         Gets the parent graph for a given graph_id.
         Returns None if there is not such supertype
-        
+
         Args:
             graph_id: Identifier of the subgraph
         """
@@ -236,15 +247,14 @@ def parent_graph(self, graph_id):
         q = q.select(self.SubGraph.graph)
         if len(list(q.all())) == 0:
             return None
-        else:
-            return list(q.all())[0]
+        return list(q.all())[0]
 
     def get_graph_global_element_attr(self, element_type, graph_id):
         """
         Gets the attributes for a global element: graph_nodes or graph_edges.
         Returns a dictionary where the keys are attribute name and values are
         attribute values.
-        
+
         Args:
             element_type (str): The element type: 'edge' or 'node'
             graph_id: Identifier of the graph
@@ -255,7 +265,7 @@ def get_graph_global_element_attr(self, element_type, graph_id):
     def get_graph_elements(self, element_type, graph_id):
         """
         Gets the list of elements for a graph
-        
+
         Args:
             element_type (str): The element type: 'edge' or 'node'
             graph_id: Identifier of the graph
@@ -269,7 +279,7 @@ def get_element_attr(self, element_type, element_id):
         Gets the attributes a specific element
         Returns a dictionary where the keys are attribute name and values are
         attribute values.
-        
+
         Args:
             element_type (str): The element type: 'graph', 'edge' or 'node'
             element_id: Identifier of the element
@@ -277,11 +287,12 @@ def get_element_attr(self, element_type, element_id):
         q = self.fb.query(self.Attr)
         q = q.where(self.Attr.element_type == element_type,
                     self.Attr.element_id == element_id)
+        # pylint: disable=no-member
         q = q.group_by(self.Attr.attr_id.attr_name)
         q = q.select(self.Attr.attr_id.attr_pos, self.Attr.attr_value)
         attrs = {}
         for name, list_opts in q.all():
-            info = {"set": [], "idxs": [], "sep": " "}
+            info = {"set": [], "idx": [], "sep": " "}
             for opt, val in list_opts:
                 val_str = str(val).strip('"')
                 if opt == "sep":
@@ -290,9 +301,9 @@ def get_element_attr(self, element_type, element_id):
                     info["set"].append(val_str)
                 else:
                     i = int(opt)
-                    if i >= len(info["idxs"]):
-                        info["idxs"] = info["idxs"] + \
-                            [""]*(1+i-len(info["idxs"]))
-                    info["idxs"][i] = val_str
-            attrs[str(name)] = info["sep"].join(info["set"]+info["idxs"])
+                    if i >= len(info["idx"]):
+                        info["idx"] = info["idx"] + \
+                            [""]*(1+i-len(info["idx"]))
+                    info["idx"][i] = val_str
+            attrs[str(name)] = info["sep"].join(info["set"]+info["idx"]).replace('\\\\','\\')
         return attrs
diff --git a/clingraph/exception.py b/clingraph/exception.py
new file mode 100644
index 0000000..b5609ef
--- /dev/null
+++ b/clingraph/exception.py
@@ -0,0 +1,13 @@
+"""
+Custom exceptions
+"""
+import logging
+log = logging.getLogger('custom')
+
+class InvalidSyntax(Exception):
+    """
+    Exception returned when the input syntax is not expected
+    """
+    def __init__(self, *args):
+        log.error("\n".join(args))
+        super().__init__("\n".join(args))
diff --git a/clingraph/multigraph.py b/clingraph/multigraph.py
index b6e9177..d524cfc 100644
--- a/clingraph/multigraph.py
+++ b/clingraph/multigraph.py
@@ -4,23 +4,25 @@
 import os
 import logging
 import json
-from clingraph import Clingraph
-LOG = logging.getLogger('custom')
+from clingraph.clingraph import Clingraph
+from clingraph.exception import InvalidSyntax
+log = logging.getLogger('custom')
 
 
 class MultiModelClingraph:
     """
     Holds multiple models in the same structure. Each model will be associated with a :py:class:`Clingraph` object.
-    
+
     The class is specially intended for integration with clingo.
     """
 
     def __init__(self, **kargs):
         """
-        Constructs a new multi model clingraph. 
+        Constructs a new multi model clingraph.
         Accepts all the arguments in the :py:class:`Clingraph` constructor which will be used in the creation of each :py:class:`Clingraph` object.
         """
         self.clingraphs = {}
+        # pylint: disable=unnecessary-lambda
         self._new_clingraph = lambda: Clingraph(**kargs)
 
     def __str__(self):
@@ -32,11 +34,11 @@ def __str__(self):
     def _name(self, number, costs):
         """
         Returns the name of a clingraph related to a model
-        
+
         Args:
-            numeber (int): The model number. Will be used to access the model
+            number (int): The model number. Will be used to access the model
             costs (list): A list of costs for the model
-        
+
         Returns:
             The name of a clingraph related to a model and cost
         """
@@ -51,15 +53,18 @@ def _name(self, number, costs):
     def source(self, selected_graphs=None, selected_models=None):
         """
         Obtains the source code for the graphs in all the models.
-        A selecttion is also allowed as parameters, if no selection is
+        A selection is also allowed as parameters, if no selection is
         added all models and graphs are returned.
-        
+
         Args:
             selected_graphs (list): List of the names of the graphs to be selected
             selected_models (list): List of the model numbers to be selected
 
         Returns:
             A string containing the source graphviz code of the selected models and graphs.
+
+        Raises:
+            ValueError: If the model number is not valid
         """
 
         s = ""
@@ -68,8 +73,7 @@ def source(self, selected_graphs=None, selected_models=None):
         for m_num in selected_models:
             m_num=int(m_num)
             if m_num not in self.clingraphs:
-                LOG.warn(f'Invalid model number: {m_num}')
-                continue
+                raise ValueError(f"Invalid model number {m_num}")
             g_dic = self.clingraphs[m_num]
             s += "\n//"+"="*25 + "\n"
             s += f"//\tModel: {m_num} Costs: {g_dic['costs']} \n"
@@ -80,22 +84,25 @@ def source(self, selected_graphs=None, selected_models=None):
     def get_clingraph(self, model_number):
         """
         Returns the clingraph associated to the model number
-        
+
         Args:
             model_number: The model number to be obtained
 
         Returns:
             A :py:class:`Clingraph` object associated to the model number
+
+        Raises:
+            ValueError: If the model number is not valid
         """
         model_number = int(model_number)
-        if not model_number in self.clingraphs:
+        if model_number not in self.clingraphs:
             raise ValueError("Invalid model number")
         return self.clingraphs[model_number]['clingraph']
 
     def add_model(self, model):
         """
         Creates a new clingraph based on a clingo model.
-        
+
         Args:
             model (clingo.Model): A model returned by clingos solver
 
@@ -121,24 +128,39 @@ def add_model(self, model):
     def load_json(self, json_str):
         """
         Loads multiple models from a json that is the output of clingo
-        The json oput is obtained using the option `--outf=2`
-        
+        The json output is obtained using the option `--outf=2`
+
         Args:
             json_str (str): A string representing the json
         """
-        j = json.loads(json_str)
-        LOG.debug(f"Loading json {json.dumps(j)}")
-        for i, w in enumerate(j["Call"][0]["Witnesses"]):
-            model_number = i+1
-            prg_str = "\n".join([f"{v}." for v in w["Value"]])
-            g = self._new_clingraph()
-            g.add_fact_string(prg_str)
-            costs = [] if not "Costs" in w else w["Costs"]
-            name = self._name(model_number, costs)
-            self.clingraphs[model_number] = {
-                "clingraph": g,
-                "name": name,
-                "costs": costs}
+        try:
+
+            j = json.loads(json_str.encode())
+            log.debug("Loading json %s",json.dumps(j))
+            if "Call" not in j:
+                raise InvalidSyntax('The json should have key "Call"')
+            if not isinstance(j["Call"], list):
+                raise InvalidSyntax('The json key "Call" should have an array as value')
+            if len(j["Call"]) != 1:
+                raise InvalidSyntax('The json key "Call" should have an array as value with only one element')
+            if "Witnesses" not in j["Call"][0]:
+                raise InvalidSyntax('The json key "Call" should have an array as value with only one element with the key "Witnesses"')
+
+            for i, w in enumerate(j["Call"][0]["Witnesses"]):
+                model_number = i+1
+                prg_str = "\n".join([f"{v}." for v in w["Value"]])
+                g = self._new_clingraph()
+                g.add_fact_string(prg_str)
+                costs = [] if not "Costs" in w else w["Costs"]
+                name = self._name(model_number, costs)
+                self.clingraphs[model_number] = {
+                    "clingraph": g,
+                    "name": name,
+                    "costs": costs}
+
+        except json.JSONDecodeError as e:
+            raise InvalidSyntax('The json can not be read.',str(e)) from None
+
 
     def compute_graphs(self):
         """
@@ -147,13 +169,27 @@ def compute_graphs(self):
         for g_dic in self.clingraphs.values():
             g_dic['clingraph'].compute_graphs()
 
-    def save(self, directory, **kargs):
+    def save(self, selected_models=None, directory='out', **kargs):
         """
         Saves all the clingraphs using one directory per model
-        
+        A selection is also allowed as parameters, if no selection is
+        added all models and graphs are saved.
+
         Args:
-            directory (str): The directory for the outpus
+            selected_models (list): List of the model numbers to be selected
+
+        Any additional arguments are passed the the :py:meth:`clingraph.Clingraph.save` function of each clingraph
+
+        Raises:
+            ValueError: If the model number in selected_models not valid
         """
-        for g_dic in self.clingraphs.values():
+        if not selected_models:
+            selected_models = self.clingraphs.keys()
+
+        for m_num in selected_models:
+            m_num=int(m_num)
+            if m_num not in self.clingraphs:
+                raise ValueError("Invalid model number")
+            g_dic = self.clingraphs[m_num]
             new_directory = os.path.join(directory, g_dic['name'])
             g_dic['clingraph'].save(new_directory, **kargs)
diff --git a/clingraph/orm.py b/clingraph/orm.py
index 5e7b307..48d6d1b 100644
--- a/clingraph/orm.py
+++ b/clingraph/orm.py
@@ -1,90 +1,104 @@
+"""
+An interface for an ORM used in clingraph
+"""
+from abc import ABCMeta, abstractmethod
+
 class ClingraphORM():
     """
     An interface for an ORM used in clingraph
     """
+    __metaclass__ = ABCMeta
 
     def __init__(self, prefix: str = ""):
         """
         Creates an ORM that will serve as a database for the facts
-        
+
         Args:
             prefix (str): The prefix to all predicate names
         """
-        pass
+        self.prefix = prefix
 
+    @abstractmethod
     def add_fact_string(self, program):
         """
         Adds a string containing facts to the database
-        
+
         Args:
-            program (str): A string consisting of only facts, devided by a '.'
+            program (str): A string consisting of only facts, divided by a '.'
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def add_fact_file(self, file):
         """
         Adds a file containing facts to the database
-        
+
         Args:
             file (str): The path to the file
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def add_clingo_model(self, model):
         """
         Adds a clingo model to the database
-        
+
         Args:
-            model (clingo.Model): A model retured by clingo
+            model (clingo.Model): A model returned by clingo
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def get_all_graphs(self):
         """
         Gets a list if the identifiers for all the graphs
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def parent_graph(self, graph_id):
         """
         Gets the parent graph for a given graph_id.
         Returns None if there is not such supertype
-        
+
         Args:
             graph_id: Identifier of the subgraph
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def get_graph_global_element_attr(self, element_type, graph_id):
         """
         Gets the attributes for a global element: graph_nodes or graph_edges.
         Returns a dictionary where the keys are attribute name and values are
         attribute values.
-        
+
         Args:
             element_type (str): The element type: 'edge' or 'node'
             graph_id: Identifier of the graph
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def get_graph_elements(self, element_type, graph_id):
         """
         Gets the list of elements for a graph
-        
+
         Args:
             element_type (str): The element type: 'edge' or 'node'
             graph_id: Identifier of the graph
         """
-        pass
+        return NotImplementedError
 
+    @abstractmethod
     def get_element_attr(self, element_type, element_id):
         """
         Gets the attributes a specific element
         Returns a dictionary where the keys are attribute name and values are
         attribute values.
-        
+
         Args:
             element_type (str): The element type: 'graph', 'edge' or 'node'
             element_id: Identifier of the element
         """
-        pass
+        return NotImplementedError
diff --git a/docs/clingraph/console.rst b/docs/clingraph/console.rst
index 8a9846f..f5b3c09 100644
--- a/docs/clingraph/console.rst
+++ b/docs/clingraph/console.rst
@@ -1,77 +1,396 @@
-Console usage
-#############
+Command line functionality
+##########################
 
-Clingraphs also comes with command line functionality.
-Graphs can be from files or by a piping. 
-The output will be the dot source code of the graphs defined by the input.
-Graphs can also be rendered and saved with additional options for gif generation.
+Clingraph also comes with command line functionality.
+The graphs can be generated from files or by a piping some output.
+Then, clingraph will output be the source dot code of the graphs defined by the input.
+Graphs can also be rendered and saved, with additional options for gif generation.
 
 Special integration for `clingo <https://potassco.org/clingo/>`_ includes the creation of graphs for multiple stable models from a clingos’ json output format.
 
-For advanced examples on how to use the command line see our `examples folder  <https://github.com/potassco/clingraph/tree/master/examples>`_. Each subfolder contains a README that explains how to run the example.
+.. note:: For advanced examples on how to use the command line see our `examples folder  <https://github.com/potassco/clingraph/tree/master/examples>`_. Each subfolder contains a README that explains how to run the example.
 
 .. code:: shell
 
-   clingraph --help
+   $ clingraph --help
 
 ::
 
          _ _                         _
        __| (_)_ _  __ _ _ _ __ _ _ __| |_
-     / _| | | ' \/ _` | '_/ _` | '_ \ ' \
+     / _| | | ' \/ _` | '_/ _` | '_ \ '  \
      \__|_|_|_||_\__, |_| \__,_| .__/_||_|
                  |___/         |_|
 
-     Clingraph is a package to generate graph visualizations
-     based on facts that can be computed by logic programs.
-     Special features for integration with clingo!
-
-
-     positional arguments:
-       files
-
-     optional arguments:
-       -h, --help            show this help message and exit
-       -q                    Flag to have a quiet output where the graphs soruce wont be rendered
-       -log                  Provide logging level.
-                             {debug|info|error|warning}
-                                 (default: warning)
-
-     Graph generation:
-       --type                The type of the graph: digraph or graph
-                             {graph|digraph}
-                                 (default: graph)
-       --prefix              Prefix expected in all the considered facts
-       --default-graph       The name of the default graph.
-                             All nodes and edges with arity 1 will be assigned to this graph
-                             (default: default)
-
-     Graph rendering:
-       --render              Flag to render the graphs and save in files
-       --dir                 Directory for saving and rendering
-                                 (default: out)
-       --out-file-prefix     A prefix for the names of the generated files
-                                 (default: )
-       --format              Format to save the graph
-                             {pdf|png|svg}
-                                 (default: pdf)
-       --engine              Layout command used by graphviz
-                             {dot|neato|twopi|circo|fdp|osage|patchwork|sfdp}
-                                 (default: dot)
-       --view                Opens the generated files
-       --select-graph  [ ...]
-                             Select one of the graphs for output or rendering by name
-                             Can appear multiple times to select multiple graphs
-       --render-param [ ...]
-                             A string containing a parameter for graphviz rendering.
-                             String should have the form arg_name=arg_value
-       --gif                 Flag to generate a giv from all the generated files
-       --gif-name            Name for the gif file that will be saved in the given directory
-       --gif-param [ ...]    A string containing a parameter for the gif generation by imageio.
-                             String should have the form arg_name=arg_value
-
-     Multi model graphs:
-       --json                Flag to indicate the creation of multiple models from a json.
-                             The graphs will be generated for each stable model.
-                             The json is exptected to be the output of clingo using the option `--outf=2`
-       --select-model []     Select only one of the models outputed by clingo defined by a number
\ No newline at end of file
+        
+    Clingraph is a package to generate graph visualizations
+    based on facts that can be computed by logic programs.
+    Special features for integration with clingo.
+
+    positional arguments:
+      files
+
+    options:
+      -h, --help            show this help message and exit
+      -q                    Flag to have a quiet output where the graphs soruce wont be rendered
+      -log                  Provide logging level.
+                            {debug|info|error|warning}
+                                (default: warning)
+      --version, -v         show program's version number and exit
+
+    Graph generation:
+      --type                The type of the graph: digraph or graph
+                            {graph|digraph}
+                                (default: graph)
+      --prefix              Prefix expected in all the considered facts
+      --default-graph       The name of the default graph.
+                            All nodes and edges with arity 1 will be assigned to this graph
+                            (default: default)
+
+    Graph rendering:
+      --render              Flag to render the graphs and save in files
+      --dir                 Directory for saving and rendering
+                                (default: out)
+      --out-file-prefix     A prefix for the names of the generated files
+                                (default: )
+      --format              Format to save the graph
+                            {pdf|png|svg}
+                                (default: pdf)
+      --engine              Layout command used by graphviz
+                            {dot|neato|twopi|circo|fdp|osage|patchwork|sfdp}
+                                (default: dot)
+      --view                Opens the generated files
+      --select-graph  [ ...]
+                            Select one of the graphs for output or rendering by name
+                            Can appear multiple times to select multiple graphs
+      --render-param [ ...]
+                            A string containing a parameter for graphviz rendering.
+                            String should have the form arg_name=arg_value
+      --gif                 Flag to generate a gif from all the generated files
+      --gif-name            Name for the gif file that will be saved in the given directory
+      --gif-param [ ...]    A string containing a parameter for the gif generation by imageio.
+                            String should have the form arg_name=arg_value
+      --tex                 Flag to generate a latex tex file
+      --tex-param [ ...]    A string containing a parameter for the tex file generation by dot2tex.
+                            String should have the form arg_name=arg_value
+
+    Multi model graphs:
+      --json                Flag to indicate the creation of multiple models from a json.
+                            The graphs will be generated for each stable model.
+                            The json is exptected to be the output of clingo using the option `--outf=2`
+      --select-model []     Select only one of the models outputed by clingo defined by a number
+
+
+Basic example
+=============
+
+Loading facts from a file with a single graph
+----------------------------------------------
+
+- The file to load which contains only facts.
+
+.. code:: shell
+
+  $ cat examples/basic/example3/example_3.lp
+
+*Output:*
+
+.. code:: shell
+
+    graph(house).
+    graph(bathroom, house).
+    graph(bedroom, house).
+
+    node(toilet,bathroom).
+    node(bed,bedroom).
+    node(desk,bedroom).
+
+    edge((toilet,bed),house).
+
+
+    attr(graph, house, label, "Tom's House").
+    attr(graph, bathroom, style, dotted).
+    attr(graph, bathroom, label, "Bathroom").
+    attr(graph, bedroom, style, dotted).
+    attr(graph, bedroom, label, "Bedroom").
+
+    attr(graph_nodes, house, style, filled).
+    attr(graph_nodes, house, color, cyan).
+
+    attr(node, toilet, shape, circle).
+    attr(node, bed, shape, square).
+    attr(node, desk, shape, square).
+
+    attr(edge, (toilet,bed), color, red).
+
+
+
+- Run clingraph to obtain the graphviz representation.
+
+.. code:: shell
+
+  $ clingraph examples/basic/example3/example_3.lp
+
+*Output:*
+
+.. code:: shell
+
+   //----------house----------
+  graph house {
+    graph [label="Tom's House"]
+    node [color=cyan style=filled]
+    toilet -- bed [color=red]
+    subgraph cluster_bathroom {
+      graph [label=Bathroom style=dotted]
+      toilet [shape=circle]
+    }
+    subgraph cluster_bedroom {
+      graph [label=Bedroom style=dotted]
+      bed [shape=square]
+      desk [shape=square]
+    }
+  }
+
+
+Loading facts from a piped output with multiple graphs
+------------------------------------------------------
+
+- The file which contains only facts.
+
+.. code:: shell
+
+  $ cat examples/basic/example2/example_2.lp
+
+*Output:*
+
+.. code:: shell
+
+    graph(toms_family).
+    graph(bills_family).
+
+    node(tom, toms_family).
+    node(max, toms_family).
+    edge((tom, max), toms_family).
+
+    node(bill, bills_family).
+    node(jen, bills_family).
+    edge((bill, jen), bills_family).
+
+
+- Run clingraph to obtain the graphviz representation.
+
+.. code:: shell
+
+  $ cat examples/basic/example2/example_2.lp | clingraph
+
+*Output:*
+
+.. code:: shell
+    
+    //----------toms_family----------
+    graph toms_family {
+      tom
+      max
+      tom -- max
+    }
+    //----------bills_family----------
+    graph bills_family {
+      bill
+      jen
+      bill -- jen
+    }
+
+
+- Select only one graph from output
+
+.. code:: shell
+
+  $ cat examples/basic/example2/example_2.lp | clingraph --select-graph=toms_family
+
+*Output:*
+
+.. code:: shell
+    
+    //----------toms_family----------
+    graph toms_family {
+      tom
+      max
+      tom -- max
+    }
+
+- Render the graphviz and save it in a directory
+
+.. code:: shell
+
+  $ cat examples/basic/example2/example_2.lp | clingraph --select-graph=toms_family --render --format=pdf --dir='out' -log=info
+
+*Output:*
+
+.. code:: shell
+    
+    INFO:  - Image saved in out/toms_family.pdf
+    //----------toms_family----------
+    graph toms_family {
+      tom
+      max
+      tom -- max
+    }
+
+
+Clingo integration
+==================
+
+- The clingo program written by the user. Note that the choice will account to multiple stable models.
+  
+.. code:: shell
+
+  $ cat examples/basic/example5/example_5.lp
+
+*Output:*
+
+.. code:: shell
+    
+  1{node(a);node(b)}1.
+
+  attr(node,a,color,blue):-node(a).
+  attr(node,b,color,red):-node(b).
+
+- Run clingo to obtain the json output with option ``--outf=2```
+
+.. code:: shell
+
+  $ clingo examples/basic/example5/example_5.lp -n 0 --outf=2
+
+*Output:*
+
+.. code:: shell
+    
+  {
+    "Solver": "clingo version 5.5.0",
+    "Input": [
+      "examples/basic/example5/example_5.lp"
+    ],
+    "Call": [
+      {
+        "Witnesses": [
+          {
+            "Value": [
+              "attr(node,a,color,blue)", "node(a)"
+            ]
+          },
+          {
+            "Value": [
+              "attr(node,b,color,red)", "node(b)"
+            ]
+          }
+        ]
+      }
+    ],
+    "Result": "SATISFIABLE",
+    "Models": {
+      "Number": 2,
+      "More": "no"
+    },
+    "Calls": 1,
+    "Time": {
+      "Total": 0.001,
+      "Solve": 0.000,
+      "Model": 0.000,
+      "Unsat": 0.000,
+      "CPU": 0.001
+    }
+  }
+
+
+- Pipe clingos json to clingraph with the ``--json`` option
+
+.. code:: shell
+
+  $ clingo examples/basic/example5/example_5.lp -n 0 --outf=2 | clingraph --json
+
+*Output:*
+
+.. code:: shell
+    
+  //=========================
+  //	Model: 1 Costs: []
+  //=========================
+
+  //----------default----------
+  graph default {
+    a [color=blue]
+  }
+
+  //=========================
+  //	Model: 2 Costs: []
+  //=========================
+
+  //----------default----------
+  graph default {
+    b [color=red]
+  }
+
+
+
+- Select one of the models by number and save it
+
+.. code:: shell
+
+  $ clingo examples/basic/example5/example_5.lp -n 0 --outf=2 | clingraph --json --select-model=1 --render --format=png -log=info
+
+*Output:*
+
+.. code:: shell
+    
+  INFO:  - Loading a multi model graph from json
+  INFO:  - Image saved in out/default.png
+  //----------default----------
+  graph default {
+    a [color=blue]
+  }
+
+Latex integration
+=================
+
+.. code:: shell
+
+  $ cat examples/basic/example5/example_5.lp
+
+*Output:*
+
+.. code:: shell
+  
+  node(sum).
+  attr(node,sum,label,"Sum").
+  attr(node,sum,texlbl,"$\\Sigma $").
+  attr(node,sum,shape,circle).
+  attr(node,sum,color,blue).
+  edge((sum,sum)).
+  attr(edge,(sum,sum),texlbl,"$\\forall x\\in \\Theta$").
+  attr(edge,(sum,sum),label,"x in Theta").
+
+- Run cligraph to obtain the latex ``.tex`` file using the ``--tex`` option. We use additional (optional) parameters in ``--tex-param`` to crop the image.
+
+.. code:: shell
+
+  $ clingraph examples/basic/example6/example_6.lp --tex --render --type=digraph --tex-param="crop=True"
+
+This will save the normal ``pdf`` as before (because of the flag ``--render``), and a latex ``.tex`` file that can be compiled into a pdf. The compilation can be done using a package like ``pdflatex``:
+
+.. code:: shell
+
+  $ pdflatex out/default.tex ; open default.pdf
+
+Leading to two different pdfs:
+
+.. list-table:: 
+
+    * - .. figure:: ../../examples/basic/example6/default.png
+
+           *Graph pdf computed by graphviz*
+
+      - .. figure:: ../../examples/basic/example6/latex.png
+
+           *Graph pdf compiled by latex*
+
+.. warning:: To use math notation (``$``) in labels, we advise the user to use the ``texlbl`` special attribute for the latex label instead of the normal ``label`` attribute. This will avoid problems with the escape characters. Note that edges require a ``label`` attribute to be defined (even if it is empty) in order for the ``texlbl`` attribute to have an effect.
\ No newline at end of file
diff --git a/docs/clingraph/installation.rst b/docs/clingraph/installation.rst
index 56b8d70..386d1ec 100644
--- a/docs/clingraph/installation.rst
+++ b/docs/clingraph/installation.rst
@@ -1,17 +1,18 @@
 Installation
 ############
 
-Clingraph requires Python 3.9 (as of writing Clingo 5.5.0 is the
-latest version). 
+Clingraph requires Python 3.9.
 
 Installing from source
 ======================
 
 The project is hosted on github at https://github.com/potassco/clingraph and can
-also be installed using git:
+also be installed using git and conda:
 
 .. code-block:: bash
 
     $ git clone https://github.com/potassco/clingraph
+    $ conda env create -f environment.yml
+    $ conda activate clingraph
     $ cd clingraph
-    $ pip install .        # or `python setup.py install` if you don't have pip
\ No newline at end of file
+    $ pip install .        
\ No newline at end of file
diff --git a/docs/conf.py b/docs/conf.py
index 4a3c93f..f0e44ff 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -76,4 +76,4 @@
 # Add any paths that contain custom static files (such as style sheets) here,
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
\ No newline at end of file
+# html_static_path = ['_static']
\ No newline at end of file
diff --git a/docs/index.rst b/docs/index.rst
index 93d2503..6993dbf 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -6,9 +6,29 @@
 Clingraph: A declarative graph visualizer
 =========================================
 
-A visualizer for graphs defined as a set of facts computed from logic programs. Graphs are generated using `graphviz <https://graphviz.org>`_ defined via fixed predicates from a list of facts.
+A visualizer for graphs defined as a set of facts that can be computed from logic programs. The graphs are generated using `graphviz <https://graphviz.org>`_ but are defined via fixed predicates from a list of facts.
+
+The package also contains special features for integration with `clingo <https://potassco.org/clingo>`_ as well as to create gifs and latex code!
+
+**Here is a motivation example:**
+
+.. code-block::
+   
+   node(john).
+   node(jane).
+   edge((john,jane)).
+
+   attr(graph, default, label, "Does' family").
+
+   attr(graph_nodes, default, style, filled).
+   attr(node, john, label, "John Doe").
+   attr(node, jane, label, "Jane Doe").
+
+Then clingraph will load this facts and construct a graphviz object that can be rendered, saved and more! 
+
+.. figure:: ../examples/basic/example1/example_1.2.png
+
 
-It contains special features for integration with `clingo <https://potassco.org/clingo>`_!
 
 .. note:: Clingraph is part the Potassco umbrella (which is the home of Clingo and the other ASP tools)
 
diff --git a/docs/package_usage.ipynb b/docs/package_usage.ipynb
index 1e64a76..b971903 100644
--- a/docs/package_usage.ipynb
+++ b/docs/package_usage.ipynb
@@ -13,7 +13,9 @@
    "id": "c566e3f7-e7b2-4220-9f49-89520f4d0905",
    "metadata": {},
    "source": [
-    "We suggest that the user first gets familiarized with the accepted [syntax](Syntax)"
+    "We suggest that the user first gets familiarized with the accepted [syntax](https://clingraph.readthedocs.io/en/latest/clingraph/syntax.html).\n",
+    "\n",
+    "For the details and advanced options please go to the [api documentation](https://clingraph.readthedocs.io/en/latest/clingraph/api.html#clingraph)."
    ]
   },
   {
@@ -79,7 +81,7 @@
     },
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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\n",
       "text/plain": [
        "<IPython.core.display.Image object>"
       ]
@@ -90,7 +92,7 @@
     {
      "data": {
       "text/plain": [
-       "<clingraph.clingraph.Clingraph at 0x11239c3a0>"
+       "<clingraph.clingraph.Clingraph at 0x111887df0>"
       ]
      },
      "execution_count": 3,
@@ -122,8 +124,8 @@
      "text": [
       "//----------default----------\n",
       "graph default {\n",
-      "\toscar [label=Oscar]\n",
       "\tandres [label=Andres]\n",
+      "\toscar [label=Oscar]\n",
       "\toscar -- andres [label=friends]\n",
       "}\n",
       "\n"
@@ -154,37 +156,37 @@
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
        " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
-       "<!-- Generated by graphviz version 2.47.3 (20210619.1520)\n",
+       "<!-- Generated by graphviz version 2.50.0 (0)\n",
        " -->\n",
        "<!-- Title: default Pages: 1 -->\n",
-       "<svg width=\"84pt\" height=\"131pt\"\n",
-       " viewBox=\"0.00 0.00 84.05 131.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       "<svg width=\"82pt\" height=\"131pt\"\n",
+       " viewBox=\"0.00 0.00 82.40 131.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
        "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 127)\">\n",
        "<title>default</title>\n",
-       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-127 80.05,-127 80.05,4 -4,4\"/>\n",
-       "<!-- oscar -->\n",
+       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-127 78.4,-127 78.4,4 -4,4\"/>\n",
+       "<!-- andres -->\n",
        "<g id=\"node1\" class=\"node\">\n",
-       "<title>oscar</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"37.05\" cy=\"-105\" rx=\"31.7\" ry=\"18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"37.05\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\">Oscar</text>\n",
+       "<title>andres</title>\n",
+       "<ellipse fill=\"none\" stroke=\"black\" cx=\"36.4\" cy=\"-18\" rx=\"36.29\" ry=\"18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"36.4\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Andres</text>\n",
        "</g>\n",
-       "<!-- andres -->\n",
+       "<!-- oscar -->\n",
        "<g id=\"node2\" class=\"node\">\n",
-       "<title>andres</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"37.05\" cy=\"-18\" rx=\"37.09\" ry=\"18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"37.05\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Andres</text>\n",
+       "<title>oscar</title>\n",
+       "<ellipse fill=\"none\" stroke=\"black\" cx=\"36.4\" cy=\"-105\" rx=\"31.4\" ry=\"18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"36.4\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\">Oscar</text>\n",
        "</g>\n",
        "<!-- oscar&#45;&#45;andres -->\n",
        "<g id=\"edge1\" class=\"edge\">\n",
        "<title>oscar&#45;&#45;andres</title>\n",
-       "<path fill=\"none\" stroke=\"black\" d=\"M37.05,-86.8C37.05,-72.05 37.05,-50.92 37.05,-36.18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"56.55\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\">friends</text>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M36.4,-86.8C36.4,-72.05 36.4,-50.92 36.4,-36.18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"55.4\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\">friends</text>\n",
        "</g>\n",
        "</g>\n",
        "</svg>\n"
       ],
       "text/plain": [
-       "<graphviz.graphs.Graph at 0x1123b3f10>"
+       "<graphviz.graphs.Graph at 0x109904700>"
       ]
      },
      "execution_count": 5,
@@ -208,37 +210,37 @@
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
        " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
-       "<!-- Generated by graphviz version 2.47.3 (20210619.1520)\n",
+       "<!-- Generated by graphviz version 2.50.0 (0)\n",
        " -->\n",
        "<!-- Title: default Pages: 1 -->\n",
-       "<svg width=\"84pt\" height=\"131pt\"\n",
-       " viewBox=\"0.00 0.00 84.05 131.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
+       "<svg width=\"82pt\" height=\"131pt\"\n",
+       " viewBox=\"0.00 0.00 82.40 131.00\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\">\n",
        "<g id=\"graph0\" class=\"graph\" transform=\"scale(1 1) rotate(0) translate(4 127)\">\n",
        "<title>default</title>\n",
-       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-127 80.05,-127 80.05,4 -4,4\"/>\n",
-       "<!-- oscar -->\n",
+       "<polygon fill=\"white\" stroke=\"transparent\" points=\"-4,4 -4,-127 78.4,-127 78.4,4 -4,4\"/>\n",
+       "<!-- andres -->\n",
        "<g id=\"node1\" class=\"node\">\n",
-       "<title>oscar</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"37.05\" cy=\"-105\" rx=\"31.7\" ry=\"18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"37.05\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\">Oscar</text>\n",
+       "<title>andres</title>\n",
+       "<ellipse fill=\"none\" stroke=\"black\" cx=\"36.4\" cy=\"-18\" rx=\"36.29\" ry=\"18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"36.4\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Andres</text>\n",
        "</g>\n",
-       "<!-- andres -->\n",
+       "<!-- oscar -->\n",
        "<g id=\"node2\" class=\"node\">\n",
-       "<title>andres</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"37.05\" cy=\"-18\" rx=\"37.09\" ry=\"18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"37.05\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">Andres</text>\n",
+       "<title>oscar</title>\n",
+       "<ellipse fill=\"none\" stroke=\"black\" cx=\"36.4\" cy=\"-105\" rx=\"31.4\" ry=\"18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"36.4\" y=\"-101.3\" font-family=\"Times,serif\" font-size=\"14.00\">Oscar</text>\n",
        "</g>\n",
        "<!-- oscar&#45;&#45;andres -->\n",
        "<g id=\"edge1\" class=\"edge\">\n",
        "<title>oscar&#45;&#45;andres</title>\n",
-       "<path fill=\"none\" stroke=\"black\" d=\"M37.05,-86.8C37.05,-72.05 37.05,-50.92 37.05,-36.18\"/>\n",
-       "<text text-anchor=\"middle\" x=\"56.55\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\">friends</text>\n",
+       "<path fill=\"none\" stroke=\"black\" d=\"M36.4,-86.8C36.4,-72.05 36.4,-50.92 36.4,-36.18\"/>\n",
+       "<text text-anchor=\"middle\" x=\"55.4\" y=\"-57.8\" font-family=\"Times,serif\" font-size=\"14.00\">friends</text>\n",
        "</g>\n",
        "</g>\n",
        "</svg>\n"
       ],
       "text/plain": [
-       "<graphviz.graphs.Graph at 0x1123b3f10>"
+       "<graphviz.graphs.Graph at 0x109904700>"
       ]
      },
      "execution_count": 6,
@@ -268,7 +270,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 7,
    "id": "694ca63a-40f3-4623-b6a7-e07ddae2491e",
    "metadata": {},
    "outputs": [
@@ -290,8 +292,7 @@
     }
    ],
    "source": [
-    "%%bash\n",
-    "cat ../examples/basic/example2/example_2.lp"
+    "!cat ../examples/basic/example2/example_2.lp"
    ]
   },
   {
@@ -304,7 +305,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 8,
    "id": "1c647294-5ddb-4708-80dc-8b664acb245d",
    "metadata": {},
    "outputs": [],
@@ -323,7 +324,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 9,
    "id": "71d88c7f-4d7f-4f2a-9379-cd6d598a6da8",
    "metadata": {},
    "outputs": [
@@ -338,7 +339,7 @@
     },
     {
      "data": {
-      "image/png": "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\n",
+      "image/png": "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\n",
       "text/plain": [
        "<IPython.core.display.Image object>"
       ]
@@ -357,7 +358,7 @@
     },
     {
      "data": {
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAFMAAACbCAYAAAAeCafZAAAABmJLR0QA/wD/AP+gvaeTAAAMQklEQVR4nO2df0yV1R/H3+hFRFATUKg0URBQ0JppE1eo+aOW6cpsai6E1gi2rEZpa9ocLdrSmtayVc5iI5IhC7UpKJVKOfwRyPydIBkooPxqXEDuvXDf3z+O8FXvc+HCPQ/cW+e13bH7nPM8z+e+7/Oc89xzPoe3B0lCIYPdgwY6gn8TSkyJKDElYhjoAO6kowO4cUO8/vlHvDcagfZ2YNgwwMsL8PYG7rsPuP9+wM9voCO+mwER89Yt4NQp4MwZ4Nw54Px54MoV4OZNIaCjDB0KjB0LhIcDUVFAZCQwfTowZQrg4aFf/Pbw6I/e3GoFTpwADhwAjhwRQppM4srqFCE8XFxtDzwABAaKskGDgOHDAYMBaG0V+7S1AQ0NQFUVUF0NVFYCFy6IL+TiRcBsBkaPBp54Apg3D1i6FHjoIb0/IQBgt65iHjsGZGQAe/eKDx8SAsydC8yZI16yP2R7O1BSAhQUAEePildTE/Doo8ALLwCxseLL0ondoGSMRnL7dnLqVBIgp00jU1LIM2dkn6lnTCYyN5dMSCADAkiDgXz+efLQIV1OlyVNTKOR3LaNDAoihw4lX3yRzM+XdXTnMZnIrCxywQLSw0N8yVlZpNUq7RTOi9nRQX75JenvT44YQW7cSNbXy4hNP4qKyKVLhaiPPUaePCnlsM6JWVREzpxJenqS77zj+iLeS3ExOXcuOWgQmZhINjY6dbi+iWm1kh9/LESMiSHPnXMqiAHFaiXT00XzNH48eexYnw/VezEbGshFi4SQmzdLbXMGlNpacvFi0Ult3tynQ/ROzIoKMjKSHDdOWjvjUlit5KefkoMHk0lJZHt7r3Z3XMzSUnLsWPHIc+1ar+N0K3JySG9vcvly0mJxeDfHxKyqIidOFD2fk42021BQQA4bRr7yisNNWc9itrSQDz9MRkSIduW/xP79om/YtMmh6j2LmZhIjhpFXr3qbGjuyVdfiUenw4d7rNq9mPv2iQfb7GxZobkny5eL/qKHJs6+mCYTGRJCrl4tPTa3o6GBHD2aXLeu22r2xdy6VfRoFRXSY+P58+f5xhtvcMaMGd3Wa2ho4OTJk5mWlkaStFgszM7O5vz587lt2za72/Tg88/FmEM3zV2W5rSF1Qps3QokJQHjxskfq/rrr7+Ql5eHurq6busZDAb4+/vD19cXAHDt2jVUVVXhl19+gcVisbtND157DRgzBti+vZtKWhIfPCiGzy5e1O2L5sqVKxkcHNzr/erq6giAW7Zs6XabHmzaRAYGkmazZrH2lZmZCURHAxERunzJAABPT88+7Wcw2M60aG3Tg/h4MbVy+LB2uaaYv/0GLFyoZ1iAh4cHPG5P1OTl5eG9995DVlbWXXXa2tqQnp6OQ4cO3bWf1rH6g/HjgdBQ4PfftcttxKyrE5Nb0dF6hwaQxIYNG5CSkoL09HSsWLECL7/8MgDg0qVLWLVqFWJjY1FSUqJ/MA4yezZQWKhdZiPm338DpJjg0pv6+no899xzKCwsxJUrV7BgwQJ8//33yM/PR0REBLZ329oPDGFhwNWr2mWaVyYA+PvrGNFtAgICMHPmTACAl5cXEhISAKDrtu7sxV0Jf3+gvl67zEbMW7fEX29vPUPSZtGiRTAYDKiqqgIADBrkegknvr5AS4t2mU20o0aJv42NeoakzciRI+Ht7Y1Jkyb1/8kdpL7efiaJjZidt3dtrZ4haVNTUwOj0YiYmJj+P7mD1NbabwJtxJw0SaSdnD6td1hAa2srWltbu95v2bIFcXFxePLJJwEAzc3NAICWO+4rR7fpRXExMHWqnUKtR/noaDFsryfHjh3jrFmzGB4ezpSUFMbHxzM5OZmtra0kyYqKCiYlJREAp0yZwtzcXFZVVd21bc+ePZrb9MJqJf38yM8+0yzWHujYuFEMOfVyDqRPVFdX89SpU2xubtb/ZE5y5Ij4mX32rGZxlmauUXm5eNI/cAB4+mk9bxr3IjYWuHQJOHlSs1g7DXviRCAmRowcKQTXrgHZ2cCrr3ZTyd4lffSouKQPHtTpnnEz4uLI4GCyrc1ule6nLZYsEfPkt25Jj82tKCwUc+kZGd1W617MigoxmbZ2rdTY3AqjkZw0iXzqqR6nfHuendy1S0yqZWZKi89t6Oggly0TeUg1NT1WdywJ4a23SC8v8uefnY7PrUhKEvM+BQUOVXdMzI4OMUs5fDj5669OxecWWK3k22+LdjInx+HdHM81MpvJlSvFFbprV59idAtMJvKll8ghQ3rscO6ld1lwHR1kcrJoQ9991+7Ektty9So5e7bIgO5DCnnfkl137iR9fMhZs8iysr4cwfXYvVs8uURG9jl5t+9p2BcuiIQub2/ygw+6fZh1acrLyWefFT9QEhLI2+MsfcG5nHazWWTZ+vqSoaEinbk/BkdkcPOmaKq8vckpUxxKzOoJOUtXKivJNWtECnN4OPndd677q6miQuQM+fqSY8aINCBJbb+8dUCkyC6OixM9oZ+feD51hcUDZjP500/i5/HgweIh/JNPRO6pROSK2UlNDfnRR+SECaItioggN2wgT53qv2agqYncu1fcMX5+4glk/nyxkMpk0uWU2uOZsrBaxfrJH38EcnLEnPzIkcDjj4vX9OliIaqz6xnb24HSUrFC+PhxkZFy+rQ4f3Q0sGyZeAUHS/lY9tB3Ieq9nDsnFocWFIgUk9szuvDzE5P7QUEi627MGCG6l9f/15l3rjs3GsXi0spKsS69ogK4fFms5jUYgMmTxSLXmBjxCgzsr0/Xz2LeS309cPasWN5cVgbU1ADXrwuRmprEkujm5huwWM7C13cBPD3FkukRI4AHHxTijx0rEswiI8U6cy+vgfo0AyymI2RlZWHFihVw8TAB9d9j5KLElIgSUyJKTIkoMSWixJSIElMiSkyJKDElosSUiBJTIkpMiSgxJaLElIgSUyJKTIkoMSWixJSIElMiSkyJKDElosSUiBJTIkpMiSgxJaLElIgSUyJKTIkoMSWixJSIElMiSkyJKDElosSUiBJTIkpMiSgxJaLElIgSUyJKTIkoMSWixJSIElMiSkyJKDEl4vL+5p3/+HP/ftf3Nx+QJdK98zdvBFAO4FGb47iYv3n/rDdX/uYSUP7mTqL8zSWg/M2Vv7nyN5eF8jeXjPI31wHlby4Z5W+uA8rfXDLK31wyyt9cMsrfXDLK31wiLu1vTpL19fUMCwvjt99+q88JJOOy/uaArXe5q+PS/ubuiMv6m7sjLutvbjKZkJGRgby8PJuypqYm7NixA8nJyfjiiy+6TDoBoKysDBs3boTVakVpaSlSU1OxY8cOWCwWfQNGz/7mNrd5ba14HMrN1e92uXz5MpcvX04A/PDDD23KlixZwoMHD7KkpIRRUVEMCQlhY2Mj09LSGBgYSADct28fly1bxsWLFxMA33//ff0CvoM1a8QAswa2vfkff4j2srxc36Dq6uo0xVy4cCFz7nA0ys3NvUus9evXEwD37t3bVWfevHkMCwvTN+DbpKYKHw8NsmzmzfvL31yrF6+urkZ+fj4eeeQRnDhxAoDw4Z0xY0aXp6+Pjw8A4JlnnunaLyoqqqu+3nTnb24jZn/5m2t5l5eWlgIA1q9fj4CAAIf38/HxQXt7u9wA7eA2/uZDhgwBABQXF9uUGY3G/g5HE7fxNw8PD8fgwYOxadMmmM3mru21tbXIyMjo/4A0cFl/83sZNWoUEhMTcfz4ccyZMwc//PAD0tLSsHr1aqxatQoA0NDQAAC41dkeAWhvb4fFYoHJZNI9Rpf0NzebzQTA1NTUu7a3tLQwNjaWAAiAI0aM6Ord9+zZw+DgYALgm2++yfLycmZmZnLChAkEwHXr1vHGjRu6xeyy/uadYn7zzTea5bW1tSwqKuoyj3cFevI31/wFFB8v3E/y83W5UwCgq/cNDQ3VLA8ICMD06dPhrfdjRS/YuROYOVMkm2li71uYM0dM6cqkrKyMr7/+Oq9fv84///yT06ZNo9VN5oorK8WQ5Ndf261ifzxTD3/zkpIShoaGMigoiGvXrmWFXoOlOqD8zSWh/M0lofzNJaH8zSWi/M0loPzNJaH8zSWh/M0lofzNJaD8zSWg/M0loPzNnUT5mzuJ8jfvI8rfXPmby8URf/OWFiGUry+Uv/l/COVvLhMlpkSUmBIxANg90EH8Szj+P+jz3TbUQVWQAAAAAElFTkSuQmCC\n",
+      "image/png": "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\n",
       "text/plain": [
        "<IPython.core.display.Image object>"
       ]
@@ -368,10 +369,10 @@
     {
      "data": {
       "text/plain": [
-       "<clingraph.clingraph.Clingraph at 0x11243e8b0>"
+       "<clingraph.clingraph.Clingraph at 0x1118de800>"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 9,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -384,7 +385,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 10,
    "id": "745a7068-39fb-4791-9e7d-aacc326655da",
    "metadata": {},
    "outputs": [
@@ -394,7 +395,7 @@
        "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n",
        "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"\n",
        " \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n",
-       "<!-- Generated by graphviz version 2.47.3 (20210619.1520)\n",
+       "<!-- Generated by graphviz version 2.50.0 (0)\n",
        " -->\n",
        "<!-- Title: toms_family Pages: 1 -->\n",
        "<svg width=\"62pt\" height=\"116pt\"\n",
@@ -405,13 +406,13 @@
        "<!-- tom -->\n",
        "<g id=\"node1\" class=\"node\">\n",
        "<title>tom</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n",
+       "<ellipse fill=\"none\" stroke=\"red\" cx=\"27\" cy=\"-90\" rx=\"27\" ry=\"18\"/>\n",
        "<text text-anchor=\"middle\" x=\"27\" y=\"-86.3\" font-family=\"Times,serif\" font-size=\"14.00\">tom</text>\n",
        "</g>\n",
        "<!-- max -->\n",
        "<g id=\"node2\" class=\"node\">\n",
        "<title>max</title>\n",
-       "<ellipse fill=\"none\" stroke=\"black\" cx=\"27\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n",
+       "<ellipse fill=\"none\" stroke=\"red\" cx=\"27\" cy=\"-18\" rx=\"27\" ry=\"18\"/>\n",
        "<text text-anchor=\"middle\" x=\"27\" y=\"-14.3\" font-family=\"Times,serif\" font-size=\"14.00\">max</text>\n",
        "</g>\n",
        "<!-- tom&#45;&#45;max -->\n",
@@ -423,16 +424,16 @@
        "</svg>\n"
       ],
       "text/plain": [
-       "<graphviz.graphs.Graph at 0x11470d430>"
+       "<graphviz.graphs.Graph at 0x11191bcd0>"
       ]
      },
-     "execution_count": 7,
+     "execution_count": 10,
      "metadata": {},
      "output_type": "execute_result"
     }
    ],
    "source": [
-    "g.graphs['toms_family']"
+    "g.get_graphviz('toms_family')"
    ]
   },
   {
@@ -445,7 +446,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 11,
    "id": "6222bb4d-ec17-41cc-ba0b-3cef283e4e4c",
    "metadata": {},
    "outputs": [
@@ -454,6 +455,7 @@
      "output_type": "stream",
      "text": [
       "graph toms_family {\n",
+      "\tnode [color=red]\n",
       "\ttom\n",
       "\tmax\n",
       "\ttom -- max\n",
@@ -463,7 +465,7 @@
     }
    ],
    "source": [
-    "print(g.graphs['toms_family'].source)"
+    "print(g.get_graphviz('toms_family').source)"
    ]
   },
   {
@@ -476,7 +478,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 9,
+   "execution_count": 12,
    "id": "203d02cf-4d73-4176-a5ef-ddde47c4f9d4",
    "metadata": {},
    "outputs": [],
@@ -502,7 +504,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 13,
    "id": "0104243e-1e3f-4a96-95ad-e02275d9268a",
    "metadata": {},
    "outputs": [
@@ -539,7 +541,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 14,
    "id": "a219a6af-a9f3-4760-8dbd-8da535aa7da0",
    "metadata": {},
    "outputs": [],
@@ -549,7 +551,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 15,
    "id": "d63e7123-b5d8-4f0a-92b4-66185a535780",
    "metadata": {},
    "outputs": [
@@ -593,22 +595,22 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 8,
+   "execution_count": 16,
    "id": "2b1cb286-3c12-4137-896e-bdb4d4c26ab2",
    "metadata": {},
    "outputs": [
     {
      "data": {
       "text/plain": [
-       "{1: {'clingraph': <clingraph.clingraph.Clingraph at 0x106553c40>,\n",
+       "{1: {'clingraph': <clingraph.clingraph.Clingraph at 0x111926da0>,\n",
        "  'name': 'model-0001',\n",
        "  'costs': []},\n",
-       " 2: {'clingraph': <clingraph.clingraph.Clingraph at 0x104b7baf0>,\n",
+       " 2: {'clingraph': <clingraph.clingraph.Clingraph at 0x1119331f0>,\n",
        "  'name': 'model-0002',\n",
        "  'costs': []}}"
       ]
      },
-     "execution_count": 8,
+     "execution_count": 16,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -627,7 +629,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 12,
+   "execution_count": 17,
    "id": "228903d4-c15b-457a-aa80-8c7ca40ad128",
    "metadata": {},
    "outputs": [
@@ -653,10 +655,10 @@
     {
      "data": {
       "text/plain": [
-       "<clingraph.clingraph.Clingraph at 0x106553c40>"
+       "<clingraph.clingraph.Clingraph at 0x111926da0>"
       ]
      },
-     "execution_count": 12,
+     "execution_count": 17,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -675,7 +677,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 13,
+   "execution_count": 18,
    "id": "d7a27212-1373-4309-933c-ea459fed5030",
    "metadata": {},
    "outputs": [
@@ -701,10 +703,10 @@
     {
      "data": {
       "text/plain": [
-       "<clingraph.clingraph.Clingraph at 0x104b7baf0>"
+       "<clingraph.clingraph.Clingraph at 0x1119331f0>"
       ]
      },
-     "execution_count": 13,
+     "execution_count": 18,
      "metadata": {},
      "output_type": "execute_result"
     }
@@ -723,7 +725,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 15,
+   "execution_count": 19,
    "id": "0ddd8090-e4b3-48f7-858a-108ce1cfe1e6",
    "metadata": {},
    "outputs": [
@@ -750,7 +752,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 20,
    "id": "afb6164c-1598-46a2-8368-90c802a0f1a0",
    "metadata": {},
    "outputs": [
@@ -760,15 +762,23 @@
      "text": [
       "//----------default----------\n",
       "graph default {\n",
-      "\tb\n",
+      "\ta\n",
       "}\n",
       "\n"
      ]
     }
    ],
    "source": [
-    "print(g.get_clingraph(2).source())"
+    "print(g.get_clingraph(1).source())"
    ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "id": "4890da48-c1f0-460c-baf0-369837b899d4",
+   "metadata": {},
+   "outputs": [],
+   "source": []
   }
  ],
  "metadata": {
@@ -787,7 +797,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.9.7"
+   "version": "3.10.1"
   }
  },
  "nbformat": 4,
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..769b936
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,8 @@
+name: clingraph
+channels:
+  - conda-forge
+  - defaults
+  - potassco
+dependencies:
+  - pygraphviz
+  - python-graphviz=0.19.1
diff --git a/examples/basic/example1/example_1.lp b/examples/basic/example1/example_1.lp
index 8d33990..9624cef 100644
--- a/examples/basic/example1/example_1.lp
+++ b/examples/basic/example1/example_1.lp
@@ -2,7 +2,7 @@ node(john).
 node(jane).
 edge((john,jane)).
 
-attr(graph, default, label, "Does' family").
+attr(graph, default, label, "Does family").
 
 attr(graph_nodes, default, style, filled).
 attr(node, john, label, "John Doe").
diff --git a/examples/basic/example4/example_4.lp b/examples/basic/example4/example_4.lp
new file mode 100644
index 0000000..e8dcb0f
--- /dev/null
+++ b/examples/basic/example4/example_4.lp
@@ -0,0 +1,9 @@
+node(mike).
+attr(node, mike, (label,0), "Michel").
+attr(node, mike, (label,1), "Scott").
+node(toby).
+edge((mike,toby)).
+attr(edge, (mike,toby), label, "enemy").
+attr(edge, (mike,toby), label, "hate").
+attr(edge, (mike,toby), label, "boss").
+attr(edge, (mike,toby), (label,"sep"), "-").
\ No newline at end of file
diff --git a/examples/basic/example4/exanple_4.pdf b/examples/basic/example4/exanple_4.pdf
deleted file mode 100644
index 074e339..0000000
Binary files a/examples/basic/example4/exanple_4.pdf and /dev/null differ
diff --git a/examples/basic/example5/example_5.lp b/examples/basic/example5/example_5.lp
new file mode 100644
index 0000000..72b534f
--- /dev/null
+++ b/examples/basic/example5/example_5.lp
@@ -0,0 +1,4 @@
+1{node(a);node(b)}1.
+
+attr(node,a,color,blue):-node(a).
+attr(node,b,color,red):-node(b).
diff --git a/examples/basic/example6/default.png b/examples/basic/example6/default.png
new file mode 100644
index 0000000..c3072a2
Binary files /dev/null and b/examples/basic/example6/default.png differ
diff --git a/examples/basic/example6/example_6.lp b/examples/basic/example6/example_6.lp
new file mode 100644
index 0000000..45e5f1f
--- /dev/null
+++ b/examples/basic/example6/example_6.lp
@@ -0,0 +1,8 @@
+node(sum).
+attr(node,sum,label,"Sum").
+attr(node,sum,texlbl,"$\\Sigma $").
+attr(node,sum,shape,circle).
+attr(node,sum,color,blue).
+edge((sum,sum)).
+attr(edge,(sum,sum),texlbl,"$\\forall x\\in \\Theta$").
+attr(edge,(sum,sum),label,"x in Theta").
diff --git a/examples/basic/example6/latex.png b/examples/basic/example6/latex.png
new file mode 100644
index 0000000..3c8e514
Binary files /dev/null and b/examples/basic/example6/latex.png differ
diff --git a/examples/transition_system/README.md b/examples/transition_system/README.md
new file mode 100644
index 0000000..12f4615
--- /dev/null
+++ b/examples/transition_system/README.md
@@ -0,0 +1,16 @@
+### Run example
+
+**Features used:**
+- Latex integration
+- Clingo integration
+- Multi model rendering
+- Default graph
+
+
+`clingo examples/transition_system/light_system.lp examples/transition_system/viz.lp --outf=2 | clingraph --json  --type=digraph --select-model=1 --tex --default-graph=system --render`
+
+This command can be followed by:
+
+`pdflatex out/system.tex -output-directory out`
+
+To compile the latex file into a pdf.
\ No newline at end of file
diff --git a/examples/transition_system/light_system.lp b/examples/transition_system/light_system.lp
new file mode 100644
index 0000000..4472e34
--- /dev/null
+++ b/examples/transition_system/light_system.lp
@@ -0,0 +1,8 @@
+initial_state(green).
+state(green).
+state(red).
+state(yellow).
+transition(red,yellow,yellow).
+transition(green,yellow,yellow).
+transition(yellow,red,red).
+transition(yellow,green,green).
\ No newline at end of file
diff --git a/examples/transition_system/viz.lp b/examples/transition_system/viz.lp
new file mode 100644
index 0000000..d1c6718
--- /dev/null
+++ b/examples/transition_system/viz.lp
@@ -0,0 +1,34 @@
+
+graph(system).
+attr(graph,system,nodesep,"3").
+node(start,system).
+attr(node,start,texlbl,"start").
+attr(node,start,shape,none).
+edge((start,Q)):-initial_state(Q).
+attr(edge,(start,Q),arrowhead,vee):-initial_state(Q).
+
+attr(graph_nodes,system,shape,circle).
+attr(graph_nodes,system,width,"1").
+attr(graph_nodes,system,hight,"1").
+
+attr(graph_edges,system,style,dotted).
+node(Q,system):-state(Q).
+
+attr(node,green,label,"green"):-state(green).
+attr(node,green,texlbl,"$\\triangleright$"):-state(green).
+attr(node,green,color,green):-state(green).
+
+attr(node,yellow,label,"yellow"):-state(yellow).
+attr(node,yellow,texlbl,"$\\sim$"):-state(yellow).
+attr(node,yellow,color,yellow):-state(yellow).
+
+attr(node,red,label,"red"):-state(red).
+attr(node,red,texlbl,"$\\otimes$"):-state(red).
+attr(node,red,color,red):-state(red).
+
+
+edge((X,Z),system):-transition(X,Y,Z).
+attr(edge,(X,Z),label,Z):-transition(X,Y,Z).
+attr(edge,(X,Z),(texlbl,0),"$\\mathcal{C}_{\\textit{"):-transition(X,Y,Z).
+attr(edge,(X,Z),(texlbl,1),Z):-transition(X,Y,Z).
+attr(edge,(X,Z),(texlbl,2),"}}$"):-transition(X,Y,Z).
\ No newline at end of file
diff --git a/setup.cfg b/setup.cfg
index 6b023be..bcf55a7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,8 +1,8 @@
 [metadata]
 name = clingraph
 version = 0.1.0-dev
-author = Susana Hahn and Tobias Stolzmann
-author_email = hahnmartinlu@uni-potsdam.de tobias.stolzmann@uni-potsdam.de
+author = Susana Hahn
+author_email = hahnmartinlu@uni-potsdam.de
 description = A visualizer for graphs defined as logic programs
 url = https://github.com/potassco/clingraph
 
@@ -11,9 +11,16 @@ packages = clingraph
 install_requires =
     clingo
     graphviz
-    imageio
-    networkx
-    clorm
+    imageio==2.14.1
+    networkx==2.6.3
+    clorm==1.3.6
+    pytest==6.2.5
+    ipython==8.0.1
+    pylint==2.12.2
+    nbsphinx
+    sphinx_rtd_theme
+    sphinx
+    dot2tex
 
 [options.entry_points]
 console_scripts =


Test patch:
diff --git a/.github/workflows/ci-tests.yml b/.github/workflows/ci-tests.yml
new file mode 100644
index 0000000..e9f3603
--- /dev/null
+++ b/.github/workflows/ci-tests.yml
@@ -0,0 +1,38 @@
+name: CI Test for clingraph
+
+on: [push]
+
+jobs:
+  build-linux:
+    name: Test clingraph
+    runs-on: ${{ matrix.os }}
+    strategy:
+      fail-fast: false
+      matrix:
+        os: ['ubuntu-latest', 'macos-latest','windows-latest']
+        python-version: ["3.9","3.8"]
+    steps:
+      - name: Checkout code into workspace directory
+        uses: actions/checkout@v2
+      - name: setup miniconda
+        uses: conda-incubator/setup-miniconda@v2
+        with:
+          auto-update-conda: true
+          python-version: ${{ matrix.python-version }}
+          environment-file: environment.yml
+          activate-environment: clingraph
+          auto-activate-base: false
+      
+      - name: Install
+        shell: pwsh
+        run: pip install .
+
+      - name: Run tests
+        shell: pwsh
+        run: |  
+          pytest -v
+
+      - name: Run lintern
+        shell: pwsh
+        run: |  
+          pylint clingraph
\ No newline at end of file
diff --git a/tests/test_library.py b/tests/test_library.py
new file mode 100644
index 0000000..2679c53
--- /dev/null
+++ b/tests/test_library.py
@@ -0,0 +1,387 @@
+import os
+import shutil
+import pytest
+from graphviz import Graph, Digraph
+from clingraph import Clingraph, InvalidSyntax
+from clingraph import MultiModelClingraph
+from clingo import Control
+
+# ------ Utils
+def get_example_file(number):
+    return os.path.join("examples","basic",f"example{number}",f"example_{number}.lp")
+
+def get_example_str(number):
+    with open(get_example_file(number), 'r') as lpfile:
+        return lpfile.read()
+
+def make_file(content):
+    if not os.path.exists('out'):
+        os.makedirs('out')
+    with open('out/test_tmp.lp', 'w') as lpfile:
+        lpfile.write(content)
+
+    return 'out/test_tmp.lp'
+
+def clean_out():
+    folder = 'out'
+    for filename in os.listdir(folder):
+        file_path = os.path.join(folder, filename)
+        try:
+            if os.path.isfile(file_path) or os.path.islink(file_path):
+                os.unlink(file_path)
+            elif os.path.isdir(file_path):
+                shutil.rmtree(file_path)
+        except Exception as e:
+            print('Failed to delete %s. Reason: %s' % (file_path, e))
+
+# -----
+
+
+def test_init_default_graph():
+    cg = Clingraph()
+    cg.add_fact_string("node(a).")
+    assert not cg._computed
+    assert "node(a,default)." in cg.get_facts()
+
+    cg = Clingraph(default_graph='other')
+    cg.add_fact_string("node(a).")
+    assert not cg._computed
+    assert "node(a,other)." in cg.get_facts()
+
+def test_init_prefix():
+    cg = Clingraph()
+    cg.add_fact_string("pre_node(a).")
+    assert not "pre_node(a,default)." in cg.get_facts()
+
+    cg = Clingraph(prefix='pre_')
+    cg.add_fact_string("pre_node(a).")
+    assert "pre_node(a,default)." in cg.get_facts()
+
+def test_init_type():
+    cg = Clingraph()
+    g = cg._new_single_graph()
+    assert type(g) == Graph
+
+    cg = Clingraph(type_='digraph')
+    g = cg._new_single_graph()
+    assert type(g) == Digraph
+
+
+def test_from_str():
+    s = get_example_str(1)
+    cg = Clingraph()
+    cg.add_fact_string(s)
+    facts = cg.get_facts()
+    assert 'node(john,default).' in facts
+    assert 'node(jane,default).' in facts
+    assert 'edge((john,jane),default).' in facts
+    assert 'attr(graph,default,(label,-1),"Does family").' in facts
+    assert 'attr(graph_nodes,default,(style,-1),filled).' in facts
+    assert 'attr(node,john,(label,-1),"John Doe").' in facts
+    assert 'attr(node,jane,(label,-1),"Jane Doe").' in facts
+
+    cg = Clingraph(default_graph='other')
+    cg.add_fact_string(s)
+    facts = cg.get_facts()
+    assert 'node(john,other).' in facts
+    assert 'node(jane,other).' in facts
+    assert 'edge((john,jane),other).' in facts
+    assert 'attr(graph,default,(label,-1),"Does family").' in facts
+    assert 'attr(graph_nodes,default,(style,-1),filled).' in facts
+    assert 'attr(node,john,(label,-1),"John Doe").' in facts
+    assert 'attr(node,jane,(label,-1),"Jane Doe").' in facts
+
+    cg = Clingraph()
+    with pytest.raises(InvalidSyntax):
+        cg.add_fact_string("head:-body.node(a).")
+
+    cg = Clingraph()
+    with pytest.raises(InvalidSyntax):
+        cg.add_fact_string("def sjs(): .node(a).")
+
+
+
+def test_from_file():
+    s = get_example_file(1)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    facts = cg.get_facts()
+    assert 'node(john,default).' in facts
+    assert 'node(jane,default).' in facts
+    assert 'edge((john,jane),default).' in facts
+    assert 'attr(graph,default,(label,-1),"Does family").' in facts
+    assert 'attr(graph_nodes,default,(style,-1),filled).' in facts
+    assert 'attr(node,john,(label,-1),"John Doe").' in facts
+    assert 'attr(node,jane,(label,-1),"Jane Doe").' in facts
+
+    cg = Clingraph(default_graph='other')
+    cg.add_fact_file(s)
+    facts = cg.get_facts()
+    assert 'node(john,other).' in facts
+    assert 'node(jane,other).' in facts
+    assert 'edge((john,jane),other).' in facts
+    assert 'attr(graph,default,(label,-1),"Does family").' in facts
+    assert 'attr(graph_nodes,default,(style,-1),filled).' in facts
+    assert 'attr(node,john,(label,-1),"John Doe").' in facts
+    assert 'attr(node,jane,(label,-1),"Jane Doe").' in facts
+
+    cg = Clingraph()
+    file_name = make_file("head:-body.node(a).")
+    with pytest.raises(InvalidSyntax):
+        cg.add_fact_file(file_name)
+
+    cg = Clingraph()
+    file_name = make_file("def: __init__()")
+    with pytest.raises(InvalidSyntax):
+        cg.add_fact_file(file_name)
+
+def test_from_model():
+    cg = Clingraph()
+    ctl = Control(["-n2"])
+    ctl.add("base", [], "1{node(a);node(b)}1.")
+    ctl.ground([("base", [])])
+    ctl.solve(on_model=cg.add_model)
+    facts = cg.get_facts()
+    assert 'node(a,default).' in facts
+    assert 'node(b,default).' in facts
+
+def test_from_get_graphviz():
+    cg = Clingraph()
+    cg.add_fact_string("node(a).node(b,other).graph(other).")
+    with pytest.raises(RuntimeError):
+        cg.get_graphviz()
+    cg.compute_graphs()
+    assert cg.get_graphviz().name == 'default'
+    assert cg.get_graphviz('default').name == 'default'
+    assert cg.get_graphviz('other').name == 'other'
+    with pytest.raises(ValueError):
+        cg.get_graphviz('wrong')
+
+    cg = Clingraph(default_graph ='other')
+    cg.add_fact_string("node(b,other).graph(other).")
+    cg.compute_graphs()
+    assert cg.get_graphviz().name == 'other'
+
+
+def test_source():
+    s = get_example_file(1)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    source = cg.source()
+
+    assert "graph default {" in source
+    assert 'graph [label="Does family"]' in source
+    assert 'node [style=filled]' in source
+    assert 'jane [label="Jane Doe"]' in source
+    assert 'john [label="John Doe"]' in source
+    assert 'john -- jane' in source
+
+
+    s = get_example_file(2)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    source = cg.source()
+
+    assert '//----------toms_family----------' in source
+    assert 'graph toms_family {' in source
+    assert 'tom' in source
+    assert 'max' in source
+    assert 'tom -- max' in source
+    assert '//----------bills_family----------' in source
+    assert 'graph bills_family {' in source
+    assert 'bill' in source
+    assert 'jen' in source
+    assert 'bill -- jen' in source
+    assert '}' in source
+
+
+
+    s = get_example_file(3)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    source = cg.source()
+
+    assert 'graph house {' in source
+    assert '	graph [label="Tom\'s House"]' in source
+    assert '	node [color=cyan style=filled]' in source
+    assert '	toilet -- bed [color=red]' in source
+    assert '	subgraph cluster_bathroom {' in source
+    assert '		graph [label=Bathroom style=dotted]' in source
+    assert '		toilet [shape=circle]' in source
+    assert '	subgraph cluster_bedroom {' in source
+    assert '		graph [label=Bedroom style=dotted]' in source
+    assert '		bed [shape=square]' in source
+    assert '		desk [shape=square]' in source
+
+
+    s = get_example_file(4)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    source = cg.source()
+
+    assert 'graph default {' in source
+    assert '	mike [label="Michel Scott"]' in source
+    assert '	toby' in source
+    label_in = False
+    label_in |= '	mike -- toby [label="enemy-hate-boss"]' in source
+    label_in |= '	mike -- toby [label="enemy-boss-hate"]' in source
+    label_in |= '	mike -- toby [label="boss-enemy-hate"]' in source
+    label_in |= '	mike -- toby [label="boss-hate-enemy"]' in source
+    label_in |= '	mike -- toby [label="hate-enemy-boss"]' in source
+    label_in |= '	mike -- toby [label="hate-boss-enemy"]' in source
+
+    assert label_in
+    assert '}' in source
+
+
+def test_save():
+    clean_out()
+    s = get_example_file(1)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    cg.save(directory ='out')
+    assert os.path.isfile(os.path.join('out','default.pdf'))
+
+    clean_out()
+    s = get_example_file(1)
+    cg = Clingraph(default_graph ='other')
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    cg.save(directory ='out')
+    assert os.path.isfile(os.path.join('out','other.pdf'))
+
+    clean_out()
+    s = get_example_file(2)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    cg.save(directory ='out')
+    assert os.path.isfile('out/bills_family.pdf')
+    assert os.path.isfile(os.path.join('out','toms_family.pdf'))
+
+    clean_out()
+    s = get_example_file(2)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    cg.save(directory ='out', selected_graphs=['bills_family'])
+    assert os.path.isfile(os.path.join('out','bills_family.pdf'))
+    assert not os.path.isfile(os.path.join('out','toms_family.pdf'))
+
+    clean_out()
+    s = get_example_file(3)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    cg.compute_graphs()
+    cg.save(directory ='out',format='png')
+    assert os.path.isfile(os.path.join('out','house.png'))
+
+    clean_out()
+    cg.save(directory ='out',format='svg')
+    assert os.path.isfile(os.path.join('out','house.svg'))
+
+    clean_out()
+    cg.save(directory ='out',format='svg',name_prefix='pre_')
+    assert os.path.isfile(os.path.join('out','pre_house.svg'))
+
+    s = get_example_file(3)
+    cg = Clingraph()
+    cg.add_fact_file(s)
+    with pytest.raises(RuntimeError):
+        cg.save()
+
+
+def test_multi_model():
+    cg = MultiModelClingraph()
+    ctl = Control(["-n2"])
+    ctl.add("base", [], get_example_str(5))
+    ctl.ground([("base", [])])
+    ctl.solve(on_model=cg.add_model)
+
+    assert list(cg.clingraphs.keys())==[1,2]
+    assert 'node(a,default).' in cg.get_clingraph(1).get_facts()
+    assert 'node(b,default).' in cg.get_clingraph(2).get_facts()
+
+    cg.compute_graphs()
+    assert cg.get_clingraph(1)._computed
+    assert cg.get_clingraph(2)._computed
+
+    assert 'a [color=blue]' in cg.get_clingraph(1).source()
+    assert 'b [color=red]' in cg.get_clingraph(2).source()
+
+    cg = MultiModelClingraph(default_graph='other')
+    ctl = Control(["-n2"])
+    ctl.add("base", [], get_example_str(5))
+    ctl.ground([("base", [])])
+    ctl.solve(on_model=cg.add_model)
+
+    cg.compute_graphs()
+
+    clean_out()
+    cg.save(format='svg')
+    assert os.path.isfile(os.path.join('out','model-0001','other.svg'))
+    assert os.path.isfile(os.path.join('out','model-0002','other.svg'))
+
+    clean_out()
+    cg.save(format='svg',selected_models=[1])
+    assert os.path.isfile(os.path.join('out','model-0001','other.svg'))
+    assert not os.path.isfile(os.path.join('out','model-0002','other.svg'))
+
+
+    json = '''
+    {
+        "Call": [{"Witnesses": [
+                {"Value": ["attr(node,a,color,blue)", "node(a)"]},
+                {"Value": ["attr(node,b,color,red)", "node(b)"]}]
+            }]}
+    '''
+    cg = MultiModelClingraph()
+    cg.load_json(json)
+
+    assert list(cg.clingraphs.keys())==[1,2]
+    assert 'node(a,default).' in cg.get_clingraph(1).get_facts()
+    assert 'node(b,default).' in cg.get_clingraph(2).get_facts()
+
+    cg.compute_graphs()
+    assert cg.get_clingraph(1)._computed
+    assert cg.get_clingraph(2)._computed
+
+    assert 'a [color=blue]' in cg.get_clingraph(1).source()
+    assert 'b [color=red]' in cg.get_clingraph(2).source()
+
+    json = '''
+    {
+        "Missing Call": 1
+        }
+    '''
+    cg = MultiModelClingraph()
+    with pytest.raises(InvalidSyntax):
+        cg.load_json(json)
+
+
+    json = '''
+    {
+        "Call": 1}
+    '''
+    with pytest.raises(InvalidSyntax):
+        cg.load_json(json)
+
+    json = '''
+    {
+        "Call": []}
+    '''
+
+    with pytest.raises(InvalidSyntax):
+        cg.load_json(json)
+
+    json = '''
+    {
+        "Call": [{"Missing Wit":1}]}
+    '''
+    with pytest.raises(InvalidSyntax):
+        cg.load_json(json)
\ No newline at end of file