From a8d388a1c131c341b8659b4eae830950a5233f9f Mon Sep 17 00:00:00 2001
From: Caroline DE POURTALES <cdepourt@montana.irit.fr>
Date: Mon, 25 Apr 2022 15:07:51 +0200
Subject: [PATCH] update

---
 Procfile                                      |   1 +
 app.py                                        |  25 +-
 assets/course_data_format.html                |  40 +++
 assets/course_decision_tree.html              |  14 +
 assets/header.css                             |   2 +
 assets/welcome.html                           |  12 +
 callbacks.py                                  |  86 +++---
 .../DecisionTree/DecisionTreeComponent.py     |   1 +
 pages/application/DecisionTree/utils/dtree.py |  66 ++--
 pages/application/DecisionTree/utils/dtviz.py | 289 ++++++++----------
 pages/application/application.py              |  34 +--
 requirements.txt                              |  41 +--
 runtime.txt                                   |   1 +
 utils.py                                      |   7 +-
 14 files changed, 359 insertions(+), 260 deletions(-)
 create mode 100644 Procfile
 create mode 100644 assets/course_data_format.html
 create mode 100644 assets/course_decision_tree.html
 create mode 100644 assets/welcome.html
 create mode 100644 runtime.txt

diff --git a/Procfile b/Procfile
new file mode 100644
index 0000000..b308fd8
--- /dev/null
+++ b/Procfile
@@ -0,0 +1 @@
+web: gunicorn app:server
\ No newline at end of file
diff --git a/app.py b/app.py
index d4becfd..41087dc 100644
--- a/app.py
+++ b/app.py
@@ -10,7 +10,7 @@ from callbacks import register_callbacks
 from pages.application.application import Application, Model, View
 from utils import extract_data
 
-app = dash.Dash(external_stylesheets=[dbc.themes.LUX], suppress_callback_exceptions=True,
+app = dash.Dash(__name__, external_stylesheets=[dbc.themes.LUX], suppress_callback_exceptions=True,
                 meta_tags=[{'name': 'viewport', 'content': 'width=device-width, initial-scale=1'}])
 
 #################################################################################
@@ -20,15 +20,34 @@ models_data = open('data_retriever.json')
 data = json.load(models_data)["data"]
 
 # For home directory
-page_home = dbc.Row([html.H3("Welcome")])
+welcome_message = html.Div(html.Iframe(
+    src=app.get_asset_url("welcome.html"),
+    style={"height": "1067px", "width": "100%"},
+))
+page_home = dbc.Row([welcome_message])
+
 # For course directory
-page_course = dbc.Row([])
+course_data_format = html.Div(html.Iframe(
+    src=app.get_asset_url("course_data_format.html"),
+    style={"height": "1067px", "width": "100%"},
+))
+course_decision_tree = html.Iframe(
+    src="assets/course_decision_tree.html",
+    style={"height": "1067px", "width": "100%"},
+)
+main_course = dcc.Tabs(children=[
+    dcc.Tab(label='Data format', children=[course_data_format]),
+    dcc.Tab(label='Course Decision Tree', children=[course_decision_tree])])
+page_course = dbc.Row([main_course])
+
 # For the application
 names_models, dict_components, dic_solvers, dic_xtypes = extract_data(data)
 model_application = Model(names_models, dict_components, dic_solvers, dic_xtypes)
 view_application = View(model_application)
 page_application = Application(view_application)
 
+server = app.server
+
 app.layout = html.Div([
     dcc.Location(id='url', refresh=False),
     html.Nav(id='navbar-container',
diff --git a/assets/course_data_format.html b/assets/course_data_format.html
new file mode 100644
index 0000000..59a3085
--- /dev/null
+++ b/assets/course_data_format.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>What kinf of model, data, or instance can I upload ?</title>
+</head>
+<body>
+
+<h1> Data format requirements</h1>
+
+<h2> An option to add information on model </h2>
+
+<h3> Why ? </h3>
+<p> There is a switch button, you can use it when you want to attach the csv you trained your model on or a feature mapping. This is useful when the values are categorical.
+</p>
+
+<h3> How ? </h3>
+
+<p> If you decide to upload the dataset, it should be a .csv.
+If you decide to upload the feature mapping, it is a .txt file.
+</p>
+
+<h2> What kind of model can I upload ? </h2>
+
+<p> You can only import .pkl models.</p>
+
+<h2> What should the format of the instance be ? </h2>
+
+<p> You can either upload a .txt file containing hte instance with the format : feature1=value1,feature2=value2,... where feature1, feature2 are the names of the columns.
+
+But you can also upload a json of your instance.</p>
+
+<p> Attention !!!! If you never mention the feature names in your model or additional information, the instance should be of format : f1=...,f2=... precisely</p>
+
+<h1> What are the advanced parameters ? </h1>
+
+<p> These are specific to the kind of model you selected, it would mainly be how the explanation should be computed and the kind of explanation.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/assets/course_decision_tree.html b/assets/course_decision_tree.html
new file mode 100644
index 0000000..b07a373
--- /dev/null
+++ b/assets/course_decision_tree.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>What kinf of model, data, or instance can I upload ?</title>
+</head>
+<body>
+
+<h1> What library am I able to use on the platform ?</h1>
+
+<p> Only models from scikit-learn are allowed.</p>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/assets/header.css b/assets/header.css
index 3a882ea..9005062 100644
--- a/assets/header.css
+++ b/assets/header.css
@@ -1,3 +1,5 @@
+
+
 /* NAVBAR */
 
 .navbar-dark .navbar-brand {
diff --git a/assets/welcome.html b/assets/welcome.html
new file mode 100644
index 0000000..ff97227
--- /dev/null
+++ b/assets/welcome.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <title>What kinf of model, data, or instance can I upload ?</title>
+</head>
+<body>
+
+<h1> Welcome </h1>
+
+</body>
+</html>
\ No newline at end of file
diff --git a/callbacks.py b/callbacks.py
index 438d511..d832b6e 100644
--- a/callbacks.py
+++ b/callbacks.py
@@ -28,19 +28,40 @@ def register_callbacks(page_home, page_course, page_application, app):
         return active_link[0], active_link[1], active_link[2]
 
     @app.callback(Output('solver_sat', 'options'),
+                  Output('solver_sat', 'value'),
                   Output('explanation_type', 'options'),
+                  Output('explanation_type', 'value'),
                   Input('ml_model_choice', 'value'),
                   prevent_initial_call=True
                   )
     def update_ml_type_options(value_ml_model):
         model_application = page_application.model
         model_application.update_ml_model(value_ml_model)
-        return model_application.solvers, model_application.xtypes
+        return model_application.solvers, model_application.solvers[0], model_application.xtypes, [
+            list(model_application.xtypes.keys())[0]]
+
+    @app.callback(Output('pretrained_model_filename', 'children'),
+                  Input('ml_pretrained_model_choice', 'filename'),
+                  prevent_initial_call=True
+                  )
+    def update_model_prertrained_name(pretrained_model_filename):
+        return pretrained_model_filename
+
+    @app.callback(Output('info_filename', 'children'),
+                  Input('model_info_choice', 'filename'),
+                  prevent_initial_call=True
+                  )
+    def update_model_info_filename(model_info_filename):
+        return model_info_filename
+
+    @app.callback(Output('instance_filename', 'children'),
+                  Input('ml_instance_choice', 'filename'),
+                  prevent_initial_call=True
+                  )
+    def update_instance_filename(instance_filename):
+        return instance_filename
 
     @app.callback(
-        Output('pretrained_model_filename', 'children'),
-        Output('info_filename', 'children'),
-        Output('instance_filename', 'children'),
         Output('graph', 'children'),
         Output('explanation', 'children'),
         Input('ml_model_choice', 'value'),
@@ -68,7 +89,7 @@ def register_callbacks(page_home, page_course, page_application, app):
             # Choice of model
             if ihm_id == 'ml_model_choice':
                 model_application.update_ml_model(value_ml_model)
-                return None, None, None, None, None
+                return None, None
 
             # Choice of pkl pretrained model
             elif ihm_id == 'ml_pretrained_model_choice':
@@ -78,17 +99,22 @@ def register_callbacks(page_home, page_course, page_application, app):
                 model_application.update_pretrained_model(graph)
                 if not model_application.add_info:
                     model_application.update_pretrained_model_layout()
-                    return pretrained_model_filename, None, None, model_application.component.network, None
+                    return model_application.component.network, None
                 else:
-                    return pretrained_model_filename, None, None, None, None
+                    if model_application.model_info is None:
+                        raise PreventUpdate
+                    model_application.update_pretrained_model_layout_with_info(model_application.model_info,
+                                                                               model_info_filename)
+                    return model_application.component.network, None
 
             # Choice of information for the model
             elif ihm_id == 'model_info_choice':
-                if model_application.ml_model is None:
-                    raise PreventUpdate
                 model_info = parse_contents_data(model_info, model_info_filename)
+                model_application.model_info = model_info
+                if model_application.ml_model is None or model_application.pretrained_model is None:
+                    raise PreventUpdate
                 model_application.update_pretrained_model_layout_with_info(model_info, model_info_filename)
-                return pretrained_model_filename, model_info_filename, None, model_application.component.network, None
+                return model_application.component.network, None
 
             # Choice of instance to explain
             elif ihm_id == 'ml_instance_choice':
@@ -96,50 +122,42 @@ def register_callbacks(page_home, page_course, page_application, app):
                     raise PreventUpdate
                 instance = parse_contents_instance(instance_contents, instance_filename)
                 model_application.update_instance(instance)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
-                # Choice of number of expls
+            # Choice of number of expls
             elif ihm_id == 'number_explanations':
-                if model_application.ml_model is None or model_application.pretrained_model is None or len(
-                        model_application.instance) == 0 or model_application.xtype is None:
+                if model_application.ml_model is None or model_application.pretrained_model is None or model_application.instance is None or model_application.solver is None or model_application.xtype is None:
                     raise PreventUpdate
                 model_application.update_enum(enum)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
-                # Choice of AxP or CxP
+            # Choice of AxP or CxP
             elif ihm_id == 'explanation_type':
-                if model_application.ml_model is None or model_application.pretrained_model is None or len(
-                        model_application.instance) == 0 or model_application.enum <= 0:
+                if model_application.ml_model is None or model_application.pretrained_model is None or model_application.instance is None or model_application.enum <= 0 or model_application.solver is None:
                     raise PreventUpdate
                 model_application.update_xtype(xtype)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
-            # Choice of solver 
+            # Choice of solver
             elif ihm_id == 'solver_sat':
-                if model_application.ml_model is None or model_application.pretrained_model is None or len(
-                        model_application.instance) == 0 or model_application.enum <= 0 or len(
-                        model_application.xtype) == 0:
+                if model_application.ml_model is None or model_application.pretrained_model is None or model_application.instance is None or model_application.enum <= 0 or model_application.xtype is None:
                     raise PreventUpdate
                 model_application.update_solver(solver)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
-                # Choice of AxP to draw
+            # Choice of AxP to draw
             elif ihm_id == 'expl_choice':
-                if model_application.ml_model is None or model_application.pretrained_model is None or len(
-                        model_application.instance) == 0 or model_application.enum <= 0 or len(
-                        model_application.xtype) == 0:
+                if model_application.ml_model is None or model_application.pretrained_model is None or model_application.instance is None or model_application.enum <= 0 or model_application.xtype is None:
                     raise PreventUpdate
                 model_application.update_expl(expl_choice)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
-                # Choice of CxP to draw
+            # Choice of CxP to draw
             elif ihm_id == 'cont_expl_choice':
-                if model_application.ml_model is None or model_application.pretrained_model is None or len(
-                        model_application.instance) == 0 or model_application.enum <= 0 or len(
-                        model_application.xtype) == 0:
+                if model_application.ml_model is None or model_application.pretrained_model is None or model_application.instance is None or model_application.enum <= 0 or model_application.xtype is None:
                     raise PreventUpdate
                 model_application.update_cont_expl(cont_expl_choice)
-                return pretrained_model_filename, model_info_filename, instance_filename, model_application.component.network, model_application.component.explanation
+                return model_application.component.network, model_application.component.explanation
 
     @app.callback(
         Output('explanation', 'hidden'),
diff --git a/pages/application/DecisionTree/DecisionTreeComponent.py b/pages/application/DecisionTree/DecisionTreeComponent.py
index c177652..edb89a4 100644
--- a/pages/application/DecisionTree/DecisionTreeComponent.py
+++ b/pages/application/DecisionTree/DecisionTreeComponent.py
@@ -119,6 +119,7 @@ class DecisionTreeComponent():
         instance_translated = self.translate_instance(instance)
         self.explanation = []
         list_explanations_path = []
+        list_contrastive_explanations_path = []
         explanation = self.dt.explain(instance_translated, enum=enum, xtype=xtype, solver=solver)
 
         dot_source = visualize_instance(self.dt, instance_translated)
diff --git a/pages/application/DecisionTree/utils/dtree.py b/pages/application/DecisionTree/utils/dtree.py
index c2775df..b67d26d 100644
--- a/pages/application/DecisionTree/utils/dtree.py
+++ b/pages/application/DecisionTree/utils/dtree.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-#-*- coding:utf-8 -*-
+# -*- coding:utf-8 -*-
 ##
 ## dtree.py
 ##
@@ -9,7 +9,7 @@
 ##
 
 #
-#==============================================================================
+# ==============================================================================
 from __future__ import print_function
 import collections
 from functools import reduce
@@ -25,6 +25,7 @@ except ImportError:  # for Python3
 from sklearn.tree import _tree
 import numpy as np
 
+
 class Node():
     """
         Node class.
@@ -38,8 +39,9 @@ class Node():
         self.feat = feat
         self.vals = vals
 
+
 #
-#==============================================================================
+# ==============================================================================
 class DecisionTree():
     """
         Simple decision tree class.
@@ -62,14 +64,14 @@ class DecisionTree():
         self.feids = {}
         self.fdoms = {}
         self.fvmap = {}
-        self.feature_names = {f'f{i}' : feature_names[i] for i, f in enumerate(feature_names)}
+        self.feature_names = {f'f{i}': feature_names[i] for i, f in enumerate(feature_names)}
 
         # OHE mapping
         OHEMap = collections.namedtuple('OHEMap', ['dir', 'opp'])
         self.ohmap = OHEMap(dir={}, opp={})
 
         if from_dt:
-            self.from_dt(from_dt)     
+            self.from_dt(from_dt)
 
         if mapfile:
             self.parse_mapping(mapfile)
@@ -103,7 +105,7 @@ class DecisionTree():
         self.terms = {}
         for i in range(self.nof_terms):
             nd, _, t = lines[i + 4].strip().split()
-            self.terms[int(nd)] = t #int(t)
+            self.terms[int(nd)] = t  # int(t)
 
         # finally, reading the nodes
         self.nodes = collections.defaultdict(lambda: Node(feat='', vals={}))
@@ -132,7 +134,7 @@ class DecisionTree():
 
         # simplifying the features and their domains
         self.feats = sorted(self.feats)
-        #self.feids = {f: i for i, f in enumerate(self.feats)}
+        # self.feids = {f: i for i, f in enumerate(self.feats)}
         self.fdoms = {f: sorted(self.fdoms[f]) for f in self.fdoms}
 
         # here we assume all features are present in the tree
@@ -175,10 +177,10 @@ class DecisionTree():
         for line in lines[1:]:
             feat, val, real = line.split()
             self.fvmap[tuple([feat, int(val)])] = '{0}{1}'.format(self.feature_names[feat], real)
-            #if feat not in self.feids:
+            # if feat not in self.feids:
             #    self.feids[feat] = len(self.feids)
 
-        #assert len(self.feids) == self.nof_feats
+        # assert len(self.feids) == self.nof_feats
 
     def convert_to_multiedges(self):
         """
@@ -324,34 +326,38 @@ class DecisionTree():
         # returning the set of sets with no duplicates
         return list(dict.fromkeys(sets))
 
-    def explain(self, inst, enum=1, pathlits=False, xtype = ["AXp"], solver='g3', htype='sorted'):
+    def explain(self, inst, enum=1, pathlits=False, xtype=["AXp"], solver='g3', htype='sorted'):
         """
             Compute a given number of explanations.
         """
-        #contaiins all the elements for explanation
+        # contaiins all the elements for explanation
         explanation_dic = {}
 
         self.feids = {f'f{i}': i for i, f in enumerate(inst)}
-        inst = [(f'f{i}', int(inst[i][1])) for i,f in enumerate(inst)]
+        inst = [(f'f{i}', int(inst[i][1])) for i, f in enumerate(inst)]
         path, term, depth = self.execute(inst, pathlits)
 
-        #decision path
-        decision_path_str = 'IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[inst[self.feids[self.nodes[n].feat]]] for n in path]), term)
+        # decision path
+        decision_path_str = 'IF {0} THEN class={1}'.format(
+            ' AND '.join([self.fvmap[inst[self.feids[self.nodes[n].feat]]] for n in path]), term)
         explanation_dic["Decision path of instance : "] = decision_path_str
-        explanation_dic["Decision path length : "] = 'Path length is :'+ str(depth)
+        explanation_dic["Decision path length : "] = 'Path length is :' + str(depth)
 
         if self.ohmap.dir:
             f2v = {fv[0]: fv[1] for fv in inst}
             # updating fvmap for printing ohe features
             for fo, fis in self.ohmap.dir.items():
-                self.fvmap[tuple([fo, None])] = '(' + ' AND '.join([self.fvmap[tuple([fi, f2v[fi]])] for fi in fis]) + ')'
+                self.fvmap[tuple([fo, None])] = '(' + ' AND '.join(
+                    [self.fvmap[tuple([fi, f2v[fi]])] for fi in fis]) + ')'
 
         # computing the sets to hit
         to_hit = self.prepare_sets(inst, term)
-        for type in xtype :
+        explanation_dic["List of path explanation(s)"] = []
+        explanation_dic["List of path contrastive explanation(s)"] = []
+        for type in xtype:
             if type == "AXp":
                 explanation_dic.update(self.enumerate_abductive(to_hit, enum, solver, htype, term))
-            else :
+            else:
                 explanation_dic.update(self.enumerate_contrastive(to_hit, term))
 
         return explanation_dic
@@ -367,7 +373,8 @@ class DecisionTree():
             expls = []
             for i, expl in enumerate(hitman.enumerate(), 1):
                 list_expls.append([self.fvmap[p] for p in sorted(expl, key=lambda p: p[0])])
-                list_expls_str.append('Explanation: IF {0} THEN class={1}'.format(' AND '.join([self.fvmap[p] for p in sorted(expl, key=lambda p: p[0])]), term))
+                list_expls_str.append('Explanation: IF {0} THEN class={1}'.format(
+                    ' AND '.join([self.fvmap[p] for p in sorted(expl, key=lambda p: p[0])]), term))
 
                 expls.append(expl)
                 if i == enum:
@@ -375,9 +382,10 @@ class DecisionTree():
             explanation["List of path explanation(s)"] = list_expls
             explanation["List of abductive explanation(s)"] = list_expls_str
             explanation["Number of abductive explanation(s) : "] = str(i)
-            explanation["Minimal abductive explanation : "] = str( min([len(e) for e in expls]))
-            explanation["Maximal abductive explanation : "] = str( max([len(e) for e in expls]))
-            explanation["Average abductive explanation : "] = '{0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))
+            explanation["Minimal abductive explanation : "] = str(min([len(e) for e in expls]))
+            explanation["Maximal abductive explanation : "] = str(max([len(e) for e in expls]))
+            explanation["Average abductive explanation : "] = '{0:.2f}'.format(
+                sum([len(e) for e in expls]) / len(expls))
 
         return explanation
 
@@ -385,6 +393,7 @@ class DecisionTree():
         """
             Enumerate contrastive explanations.
         """
+
         def process_set(done, target):
             for s in done:
                 if s <= target:
@@ -401,14 +410,15 @@ class DecisionTree():
         list_expls_str = []
         explanation = {}
         for expl in expls:
-            list_contrastive_expls.append([self.fvmap[(p[0],1-p[1])] for p in sorted(expl, key=lambda p: p[0])])
-            list_expls_str.append('Contrastive: IF {0} THEN class!={1}'.format(' OR '.join(['!{0}'.format(self.fvmap[p]) for p in sorted(expl, key=lambda p: p[0])]), term))
+            list_contrastive_expls.append([self.fvmap[(p[0], 1 - p[1])] for p in sorted(expl, key=lambda p: p[0])])
+            list_expls_str.append('Contrastive: IF {0} THEN class!={1}'.format(
+                ' OR '.join(['!{0}'.format(self.fvmap[p]) for p in sorted(expl, key=lambda p: p[0])]), term))
 
         explanation["List of path contrastive explanation(s)"] = list_contrastive_expls
         explanation["List of contrastive explanation(s)"] = list_expls_str
-        explanation["Number of contrastive explanation(s) : "]=str(len(expls))
-        explanation["Minimal contrastive explanation : "]= str( min([len(e) for e in expls]))
-        explanation["Maximal contrastive explanation : "]= str( max([len(e) for e in expls]))
-        explanation["Average contrastive explanation : "]='{0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))
+        explanation["Number of contrastive explanation(s) : "] = str(len(expls))
+        explanation["Minimal contrastive explanation : "] = str(min([len(e) for e in expls]))
+        explanation["Maximal contrastive explanation : "] = str(max([len(e) for e in expls]))
+        explanation["Average contrastive explanation : "] = '{0:.2f}'.format(sum([len(e) for e in expls]) / len(expls))
 
         return explanation
diff --git a/pages/application/DecisionTree/utils/dtviz.py b/pages/application/DecisionTree/utils/dtviz.py
index cf71d2b..02aa847 100755
--- a/pages/application/DecisionTree/utils/dtviz.py
+++ b/pages/application/DecisionTree/utils/dtviz.py
@@ -1,5 +1,5 @@
 #!/usr/bin/env python
-#-*- coding:utf-8 -*-
+# -*- coding:utf-8 -*-
 ##
 ## dtviz.py
 ##
@@ -9,255 +9,234 @@
 ##
 
 #
-#==============================================================================
+# ==============================================================================
 import getopt
-import pygraphviz
+import pydot
 import ast
 import re
+
+
 #
-#==============================================================================
-def create_legend(g):
-    legend = g.subgraphs()[-1]
-    legend.graph_attr.update(size="2,2")    
-    legend.add_node("a", style = "invis")
-    legend.add_node("b", style = "invis")
-    legend.add_node("c", style = "invis")
-    legend.add_node("d", style = "invis")
-    legend.add_node("e", style = "invis")
-    legend.add_node("f", style = "invis")
-
-    legend.add_edge("a","b")
-    edge = legend.get_edge("a","b")
-    edge.attr["label"] = "instance"
-    edge.attr["style"] = "dashed" 
-
-    legend.add_edge("c","d")  
-    edge = legend.get_edge("c","d")
-    edge.attr["label"] = "instance with explanation"
-    edge.attr["color"] = "blue"
-    edge.attr["style"] = "dashed"
-
-    legend.add_edge("e","f")  
-    edge = legend.get_edge("e","f")
-    edge.attr["label"] = "contrastive explanation"
-    edge.attr["color"] = "red"
+# ==============================================================================
+def create_legend(G):
+    legend = pydot.Cluster('legend', rankdir="TB")
+
+    # non-terminal nodes
+    for n in ["a", "b", "c", "d", "e", "f"]:
+        node = pydot.Node(n, label=n)
+        legend.add_node(node)
+
+    edge = pydot.Edge("a", "b")
+    edge.obj_dict['attributes']["label"] = "instance"
+    edge.obj_dict['attributes']["style"] = "dashed"
+    legend.add_edge(edge)
+
+    edge = pydot.Edge("e", "f")
+    edge.obj_dict['attributes']["label"] = "contrastive explanation"
+    edge.obj_dict['attributes']["color"] = "red"
+    edge.obj_dict['attributes']["style"] = "dashed"
+    legend.add_edge(edge)
+
+    edge = pydot.Edge("c", "d")
+    edge.obj_dict['attributes']["label"] = "instance with explanation"
+    edge.obj_dict['attributes']["color"] = "blue"
+    edge.obj_dict['attributes']["style"] = "dashed"
+    legend.add_edge(edge)
+
+    G.add_subgraph(legend)
+
+
 #
-#==============================================================================
+# ==============================================================================
 def visualize(dt):
     """
         Visualize a DT with graphviz.
     """
 
-    g = pygraphviz.AGraph(directed=True, strict=True)
-    g.edge_attr['dir'] = 'forward'
-    g.graph_attr['rankdir'] = 'TB'
+    G = pydot.Dot('tree_total', graph_type='graph')
+
+    g = pydot.Cluster('tree', graph_type='graph')
 
     # non-terminal nodes
     for n in dt.nodes:
-        g.add_node(n, label=dt.feature_names[dt.nodes[n].feat])
-        node = g.get_node(n)
-        node.attr['shape'] = 'circle'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.feature_names[dt.nodes[n].feat], shape="circle")
+        g.add_node(node)
 
     # terminal nodes
     for n in dt.terms:
-        g.add_node(n, label=dt.terms[n])
-        node = g.get_node(n)
-        node.attr['shape'] = 'square'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.terms[n], shape="square")
+        g.add_node(node)
 
     # transitions
     for n1 in dt.nodes:
         for v in dt.nodes[n1].vals:
             n2 = dt.nodes[n1].vals[v]
-            g.add_edge(n1, n2)
-            edge = g.get_edge(n1, n2)
+            edge = pydot.Edge(n1, n2)
             if len(v) == 1:
-                edge.attr['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
+                edge.obj_dict['attributes']['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
             else:
-                edge.attr['label'] = '{0}'.format('\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
-            edge.attr['fontsize'] = 10
-            edge.attr['arrowsize'] = 0.8
+                edge.obj_dict['attributes']['label'] = '{0}'.format(
+                    '\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
+            edge.obj_dict['attributes']['fontsize'] = 10
+            edge.obj_dict['attributes']['arrowsize'] = 0.8
+
+            g.add_edge(edge)
+
+    G.add_subgraph(g)
+
+    return G.to_string()
 
-    # saving file
-    g.layout(prog='dot')
-    return(g.to_string())
 
 #
-#==============================================================================
+# ==============================================================================
 def visualize_instance(dt, instance):
     """
         Visualize a DT with graphviz and plot the running instance.
     """
-    #path that follows the instance - colored in blue
+    # path that follows the instance - colored in blue
     path, term, depth = dt.execute(instance)
     edges_instance = []
-    for i in range (len(path)-1) :
-        edges_instance.append((path[i], path[i+1]))
-    edges_instance.append((path[-1],"term:"+term))
+    for i in range(len(path) - 1):
+        edges_instance.append((path[i], path[i + 1]))
+    edges_instance.append((path[-1], "term:" + term))
+
+    G = pydot.Dot('tree_total', graph_type='graph')
 
-    g = pygraphviz.AGraph(directed=True, strict=True)
-    g.edge_attr['dir'] = 'forward'
-    g.graph_attr['rankdir'] = 'TB'
+    g = pydot.Cluster('tree', graph_type='graph')
 
     # non-terminal nodes
     for n in dt.nodes:
-        g.add_node(n, label=dt.feature_names[dt.nodes[n].feat])
-        node = g.get_node(n)
-        node.attr['shape'] = 'circle'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.feature_names[dt.nodes[n].feat], shape="circle")
+        g.add_node(node)
 
     # terminal nodes
     for n in dt.terms:
-        g.add_node(n, label=dt.terms[n])
-        node = g.get_node(n)
-        node.attr['shape'] = 'square'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.terms[n], shape="square")
+        g.add_node(node)
 
     # transitions
     for n1 in dt.nodes:
         for v in dt.nodes[n1].vals:
             n2 = dt.nodes[n1].vals[v]
-            n2_type = g.get_node(n2).attr['shape']
-
-            g.add_edge(n1, n2)
-            edge = g.get_edge(n1, n2)
+            n2_type = g.get_node(str(n2))[0].obj_dict['attributes']['shape']
+            edge = pydot.Edge(n1, n2)
             if len(v) == 1:
-                edge.attr['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
+                edge.obj_dict['attributes']['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
             else:
-                edge.attr['label'] = '{0}'.format('\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
-            
-            #instance path in dashed
-            if ((n1,n2) in edges_instance) or (n2_type=='square' and (n1, "term:"+ dt.terms[n2]) in edges_instance): 
-                edge.attr['style'] = 'dashed'
+                edge.obj_dict['attributes']['label'] = '{0}'.format(
+                    '\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
+            edge.obj_dict['attributes']['fontsize'] = 10
+            edge.obj_dict['attributes']['arrowsize'] = 0.8
 
-            edge.attr['fontsize'] = 10
-            edge.attr['arrowsize'] = 0.8
+            # instance path in dashed
+            if ((n1, n2) in edges_instance) or (n2_type == 'square' and (n1, "term:" + dt.terms[n2]) in edges_instance):
+                edge.obj_dict['attributes']['style'] = 'dashed'
 
-    g.add_subgraph(name='legend')
-    create_legend(g)
+            g.add_edge(edge)
 
-    # saving file
-    g.layout(prog='dot')
-    return(g.to_string())
+    create_legend(G)
+    G.add_subgraph(g)
 
-#==============================================================================
+    return G.to_string()
+
+
+# ==============================================================================
 def visualize_expl(dt, instance, expl):
     """
         Visualize a DT with graphviz and plot the running instance.
     """
-    #path that follows the instance - colored in blue
+    # path that follows the instance - colored in blue
     path, term, depth = dt.execute(instance)
     edges_instance = []
-    for i in range (len(path)-1) :
-        edges_instance.append((path[i], path[i+1]))
-    edges_instance.append((path[-1],"term:"+term))
-
-    g = pygraphviz.AGraph(directed=True, strict=True)
-    g.edge_attr['dir'] = 'forward'
+    for i in range(len(path) - 1):
+        edges_instance.append((path[i], path[i + 1]))
+    edges_instance.append((path[-1], "term:" + term))
 
-    g.graph_attr['rankdir'] = 'TB'
+    g = pydot.Dot('my_graph', graph_type='graph')
 
     # non-terminal nodes
     for n in dt.nodes:
-        g.add_node(n, label=dt.feature_names[dt.nodes[n].feat])
-        node = g.get_node(n)
-        node.attr['shape'] = 'circle'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.feature_names[dt.nodes[n].feat], shape="circle")
+        g.add_node(node)
 
     # terminal nodes
     for n in dt.terms:
-        g.add_node(n, label=dt.terms[n])
-        node = g.get_node(n)
-        node.attr['shape'] = 'square'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.terms[n], shape="square")
+        g.add_node(node)
 
     # transitions
     for n1 in dt.nodes:
         for v in dt.nodes[n1].vals:
             n2 = dt.nodes[n1].vals[v]
-            n2_type = g.get_node(n2).attr['shape']
-            g.add_edge(n1, n2)
-            edge = g.get_edge(n1, n2)
+            n2_type = g.get_node(str(n2))[0].obj_dict['attributes']['shape']
+            edge = pydot.Edge(n1, n2)
             if len(v) == 1:
-                edge.attr['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
+                edge.obj_dict['attributes']['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
             else:
-                edge.attr['label'] = '{0}'.format('\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
-            
-            #instance path in dashed
-            if ((n1,n2) in edges_instance) or (n2_type=='square' and (n1, "term:"+ dt.terms[n2]) in edges_instance): 
-                edge.attr['style'] = 'dashed'
-            for label in edge.attr['label'].split('\n'):
+                edge.obj_dict['attributes']['label'] = '{0}'.format(
+                    '\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
+            edge.obj_dict['attributes']['fontsize'] = 10
+            edge.obj_dict['attributes']['arrowsize'] = 0.8
+
+            # instance path in dashed
+            if ((n1, n2) in edges_instance) or (n2_type == 'square' and (n1, "term:" + dt.terms[n2]) in edges_instance):
+                edge.obj_dict['attributes']['style'] = 'dashed'
+
+            for label in edge.obj_dict['attributes']['label'].split('\n'):
                 if label in expl:
-                    edge.attr['color'] = 'blue'
+                    edge.obj_dict['attributes']['color'] = 'blue'
 
-            edge.attr['fontsize'] = 10
-            edge.attr['arrowsize'] = 0.8
+            g.add_edge(edge)
 
-    g.add_subgraph(name='legend')
-    create_legend(g)
+    return g.to_string()
 
-    # saving file
-    g.layout(prog='dot')
-    return(g.to_string())
 
-#==============================================================================
+# ==============================================================================
 def visualize_contrastive_expl(dt, instance, cont_expl):
     """
         Visualize a DT with graphviz and plot the running instance.
     """
-    #path that follows the instance - colored in blue
+    # path that follows the instance - colored in blue
     path, term, depth = dt.execute(instance)
     edges_instance = []
-    for i in range (len(path)-1) :
-        edges_instance.append((path[i], path[i+1]))
-    edges_instance.append((path[-1],"term:"+term))
-
-    g = pygraphviz.AGraph(directed=True, strict=True)
-    g.edge_attr['dir'] = 'forward'
+    for i in range(len(path) - 1):
+        edges_instance.append((path[i], path[i + 1]))
+    edges_instance.append((path[-1], "term:" + term))
 
-    g.graph_attr['rankdir'] = 'TB'
+    g = pydot.Dot('my_graph', graph_type='graph')
 
     # non-terminal nodes
     for n in dt.nodes:
-        g.add_node(n, label=dt.feature_names[dt.nodes[n].feat])
-        node = g.get_node(n)
-        node.attr['shape'] = 'circle'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.feature_names[dt.nodes[n].feat], shape="circle")
+        g.add_node(node)
 
     # terminal nodes
     for n in dt.terms:
-        g.add_node(n, label=dt.terms[n])
-        node = g.get_node(n)
-        node.attr['shape'] = 'square'
-        node.attr['fontsize'] = 13
+        node = pydot.Node(n, label=dt.terms[n], shape="square")
+        g.add_node(node)
 
     # transitions
     for n1 in dt.nodes:
         for v in dt.nodes[n1].vals:
             n2 = dt.nodes[n1].vals[v]
-            n2_type = g.get_node(n2).attr['shape']
-            g.add_edge(n1, n2)
-            edge = g.get_edge(n1, n2)
+            n2_type = g.get_node(str(n2))[0].obj_dict['attributes']['shape']
+            edge = pydot.Edge(n1, n2)
             if len(v) == 1:
-                edge.attr['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
+                edge.obj_dict['attributes']['label'] = dt.fvmap[tuple([dt.nodes[n1].feat, tuple(v)[0]])]
             else:
-                edge.attr['label'] = '{0}'.format('\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
-            
-            #instance path in dashed
-            if ((n1,n2) in edges_instance) or (n2_type=='square' and (n1, "term:"+ dt.terms[n2]) in edges_instance): 
-                edge.attr['style'] = 'dashed'
-                    
-            for label in edge.attr['label'].split('\n'):
-                if label in cont_expl:
-                    edge.attr['color'] = 'red'
+                edge.obj_dict['attributes']['label'] = '{0}'.format(
+                    '\n'.join([dt.fvmap[tuple([dt.nodes[n1].feat, val])] for val in tuple(v)]))
+            edge.obj_dict['attributes']['fontsize'] = 10
+            edge.obj_dict['attributes']['arrowsize'] = 0.8
+
+            # instance path in dashed
+            if ((n1, n2) in edges_instance) or (n2_type == 'square' and (n1, "term:" + dt.terms[n2]) in edges_instance):
+                edge.obj_dict['attributes']['style'] = 'dashed'
 
-            edge.attr['fontsize'] = 10
-            edge.attr['arrowsize'] = 0.8
+            for label in edge.obj_dict['attributes']['label'].split('\n'):
+                if label in cont_expl:
+                    edge.obj_dict['attributes']['color'] = 'red'
 
-    g.add_subgraph(name='legend')
-    create_legend(g)
+            g.add_edge(edge)
 
-    # saving file
-    g.layout(prog='dot')
-    return(g.to_string())
+    return g.to_string()
diff --git a/pages/application/application.py b/pages/application/application.py
index 65475b9..6d33030 100644
--- a/pages/application/application.py
+++ b/pages/application/application.py
@@ -6,16 +6,14 @@ from pages.application.DecisionTree.DecisionTreeComponent import DecisionTreeCom
 from pages.application.NaiveBayes.NaiveBayesComponent import NaiveBayesComponent
 from pages.application.RandomForest.RandomForestComponent import RandomForestComponent
 
-import subprocess
 
-
-class Application():
+class Application:
     def __init__(self, view):
         self.view = view
         self.model = view.model
 
 
-class Model():
+class Model:
 
     def __init__(self, names_models, dict_components, dic_solvers, dic_xtypes):
         self.dict_components = dict_components
@@ -23,38 +21,40 @@ class Model():
         self.dic_xtypes = dic_xtypes
 
         self.ml_models = names_models
-        self.ml_model = ''
+        self.ml_model = None
 
-        self.pretrained_model = ''
+        self.pretrained_model = None
 
         self.add_info = False
-        self.model_info = ''
+        self.model_info = None
 
         self.enum = 1
 
         self.xtypes = []
-        self.xtype = ""
+        self.xtype = None
 
         self.solvers = []
-        self.solver = ""
+        self.solver = None
 
-        self.instance = ''
+        self.instance = None
 
         self.list_expls = []
         self.list_cont_expls = []
 
-        self.expl = ''
-        self.cont_expl = ''
+        self.expl = None
+        self.cont_expl = None
 
-        self.component_class = ''
-        self.component = ''
+        self.component_class = None
+        self.component = None
 
     def update_ml_model(self, ml_model_update):
         self.ml_model = ml_model_update
         self.component_class = self.dict_components[self.ml_model]
         self.component_class = globals()[self.component_class]
         self.solvers = self.dic_solvers[self.ml_model]
+        self.solver = self.solvers[0]
         self.xtypes = self.dic_xtypes[self.ml_model]
+        self.xtype = [list(self.xtypes.keys())[0]]
 
     def update_pretrained_model(self, pretrained_model_update):
         self.pretrained_model = pretrained_model_update
@@ -99,7 +99,7 @@ class Model():
         self.component.draw_contrastive_explanation(self.instance, cont_expl)
 
 
-class View():
+class View:
 
     def __init__(self, model):
         self.model = model
@@ -128,7 +128,7 @@ class View():
 
         self.add_model_info_choice = html.Div([
             html.Hr(),
-            html.Label("Do you wish to upload more info for your model ? : "),
+            html.Label("Upload more data model (only for model with categorical variables) ?"),
             html.Br(),
             daq.BooleanSwitch(id='add_info_model_choice', on=False, color="#000000", )])
 
@@ -192,9 +192,9 @@ class View():
         self.sidebar = dcc.Tabs(children=[
             dcc.Tab(label='Basic Parameters', children=[
                 self.ml_menu_models,
-                self.pretrained_model_upload,
                 self.add_model_info_choice,
                 self.model_info,
+                self.pretrained_model_upload,
                 self.instance_upload], className="sidebar"),
             dcc.Tab(label='Advanced Parameters', children=[
                 html.Br(),
diff --git a/requirements.txt b/requirements.txt
index 8f12a33..0731570 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,24 +1,27 @@
-# Core
-gunicorn>=19.8.1
-dash>=1.0.0
-
-# Additional
 colorlover>=0.2.1
-numpy>=1.16.2
-pandas>=0.24.2
-scikit-learn>=0.20.3
 scipy>=1.2.1
-dash_bootstrap_components
-dash_interactive_graphviz
 python-sat[pblib,aiger]
-pygraphviz
+matplotlib==3.5.1
+xgboost==1.6.0
+lime==0.2.0.1
+shap==0.40.0
+anchor-exp==0.0.2.0
+pysmt==0.9.0
 anytree==2.8.0
+dash==2.1.0
+dash_bootstrap_components==1.0.3
 dash_daq==0.5.0
-matplotlib
-six
-xgboost
-lime
-shap
-anchor-exp
-pysmt
-anytree
+dash_interactive_graphviz==0.3.0
+Jinja2==3.0.3
+joblib==1.1.0
+json2html==1.3.0
+numpy==1.21
+pandas==1.4.1
+pysat==3.0.1
+pytest==7.0.1
+python_sat==0.1.7.dev15
+scikit_learn==1.0.2
+six==1.16.0
+gunicorn==20.1.0
+Werkzeug==2.0.1
+pydot==1.4.2
\ No newline at end of file
diff --git a/runtime.txt b/runtime.txt
new file mode 100644
index 0000000..f96c0fa
--- /dev/null
+++ b/runtime.txt
@@ -0,0 +1 @@
+python-3.8.10
\ No newline at end of file
diff --git a/utils.py b/utils.py
index 9fd8a54..87f4fa3 100644
--- a/utils.py
+++ b/utils.py
@@ -8,20 +8,19 @@ from dash import html
 
 from pages.application.RandomForest.utils import xrf
 from pages.application.RandomForest.utils.xrf import *
+
 sys.modules['xrf'] = xrf
 from pages.application.RandomForest.utils import options
 from pages.application.RandomForest.utils.options import *
+
 sys.modules['options'] = options
 
-def parse_contents_graph(contents, filename):
 
+def parse_contents_graph(contents, filename):
     content_type, content_string = contents.split(',')
     decoded = base64.b64decode(content_string)
     try:
         if 'mod.pkl' in filename:
-            print("in")
-            print(io.BytesIO(decoded))
-            print(pickle.load(io.BytesIO(decoded)))
             data = pickle.load(io.BytesIO(decoded))
         elif '.pkl' in filename:
             data = joblib.load(io.BytesIO(decoded))
-- 
GitLab