From 8464f1d534e2a4627fe70703ad53ba59de8ae689 Mon Sep 17 00:00:00 2001
From: abaucher <achille.baucher@inria.fr>
Date: Mon, 21 Mar 2022 12:56:10 +0100
Subject: [PATCH] Useful functions inside System, and politics in an other file

---
 afaire.md                       |   4 +-
 pydynamo/__init__.py            |   4 +-
 pydynamo/core/parse_system.py   | 228 ++------------------------------
 pydynamo/core/plot_system.py    | 166 +++++++++++++++++++----
 pydynamo/core/politics.py       | 124 +++++++++++++++++
 pydynamo/core/psdsystem.py      |   2 +-
 pydynamo/core/system.py         | 184 ++++++++++++++++++++------
 pydynamo/world2/__init__.py     |   2 -
 pydynamo/world3/world3_class.py | 167 ++---------------------
 9 files changed, 431 insertions(+), 450 deletions(-)
 create mode 100644 pydynamo/core/politics.py

diff --git a/afaire.md b/afaire.md
index 2c3af311..1b88ec5a 100644
--- a/afaire.md
+++ b/afaire.md
@@ -3,4 +3,6 @@
 - [ ] Générer la documentation pydynamo
 - [ ] tabhl se nomme avec le nom de la variable attribuée
 - [ ] Une seule fonction spéciale DYNAMO admise dans une équation
-- [ ] New table politic, c'est la variable qui est rentréer en paramètre.
+- [ ] New table politic, c'est la variable qui est rentrée en paramètre.
+- [x] Assertions que le system est bien run
+- [ ] Deux change politic marche pas 
diff --git a/pydynamo/__init__.py b/pydynamo/__init__.py
index db99ea1a..1133fcad 100644
--- a/pydynamo/__init__.py
+++ b/pydynamo/__init__.py
@@ -2,9 +2,7 @@
 
 __version__ = "0.1"
 
-from pydynamo.core.parse_system import new_cst_politic, new_table_politic, new_var_politic,new_system, system_from_lines, system_from_fun, system_from_file
-# import pydynamo.core.system
-from pydynamo.core.plot_system import *
+from pydynamo.core.system import System
 # import pydynamo.core.dynamo_converter
 from pydynamo.core import psdsystem
 from .world3 import World3
diff --git a/pydynamo/core/parse_system.py b/pydynamo/core/parse_system.py
index 63af1a83..dfe972c8 100644
--- a/pydynamo/core/parse_system.py
+++ b/pydynamo/core/parse_system.py
@@ -1,22 +1,20 @@
 """Functions to parse an entire pydynamo code and generate a System object. Also defines every political changes.
 """
 import ast
-import re
 import inspect
 
 from .parse_equations import *
 from .parse_dynamo_functions import get_dynamo_fun_params
 from .specials import step, clip
-from .system import System
 
-def get_comment(line):
+def comment_from_equation(line):
     """Retrieve comment by removing '#' and additional spaces.
     """
     if '#' in line:
         return line.split('#', 1)[1].strip()
     return ''
 
-def get_nodes_eqs_dicts(lines):
+def get_system_dicts(lines):
     """
 
     Parameters
@@ -52,7 +50,7 @@ def get_nodes_eqs_dicts(lines):
                     raise Exception("Error while parsing line:\n"+l)
                 
                 # Add comment
-                com = get_comment(l)
+                com = comment_from_equation(l)
                 if com != '' or node not in comments:
                     comments[node] = com
 
@@ -81,87 +79,27 @@ def get_nodes_eqs_dicts(lines):
         if root.body and not type_is_identified:
             raise(SyntaxError(f"Invalid equation:\n {l}"))
 
-
     return all_nodes, all_eqs, comments
 
-def system_from_nodes_eqs(nodes, eqs, comments, s=None, prepare=True):
-    """Get a system from nodes, equations and comments dictionnaries. If a system is given, just add this equations, nodes and comments to it.
-
-    Parameters
-    ----------
-    nodes : dict
-        Node (variables, constants or functions) names.
-    eqs : dict
-        Equations (constant, initialisation or updates).
-    comments : dict(str: str)
-        Comments of each node.
-    s : System
-        System to which we add changes.
-    prepare : bool
-        If True, set all constants, and functions (constants, update and init), but not special functions.
-    """
-    # In case a System is given, eventually change variables to constants and vice-versa
-    if s:
-        for n, args in eqs['cst'].items():
-            if n in s.eqs['update']:
-                change_var_to_cst(s, n)
-        for n, args in eqs['update'].items():
-            if n in s.eqs['cst']:
-                change_cst_to_var(s, n)
-    if not s:
-        s = System()
-    
-    s.add_all_nodes(nodes)
-    s.add_all_eqs(eqs)
-    s.add_comments(comments)
-
-    if prepare:
-        s.prepare()
-
-    return s
-
-def system_from_lines(lines, s=None, prepare=True):
-    """Get a system from a list of pydynamo equations. If a system is given, just add this equations to it.
-
-    Parameters
-    ----------
-    lines : iterable(str)
-        List of every pydynamo equations.
-    s : System
-        System to which we add changes.
-    prepare : bool
-        If True, set all constants, and functions (constants, update and init), but not special functions.
-    """
-    nodes, eqs, comments = get_nodes_eqs_dicts(lines)
-    return system_from_nodes_eqs(nodes, eqs, comments, s, prepare)
-
-def system_from_file(filename, s=None, prepare=True):
-    """Get a system from a file with pydynamo equations. If a system is given, just add this equations to it.
 
+def list_from_file(filename, s=None, prepare=True):
+    """Get a list of equatinos from a file with pydynamo equations.
     Parameters
     ----------
     filename : str
         Files in which every pydynamo equations are written.
-    s : System
-        System to which we add changes.
-    prepare : bool
-        If True, set all constants, and functions (constants, update and init), but not special functions.
     """
     check_file(filename)
     with open(filename, 'r') as f:
-        return system_from_lines(f.readlines(), s, prepare)
+        return f.readlines()
 
-def system_from_fun(fun, s=None, prepare=True):
-    """Get a system from a function which lists pydynamo equations. If a system is given, just add this equations to it.
+def list_from_function(fun, s=None, prepare=True):
+    """Get a list of equations from a function which lists pydynamo equations.
 
     Parameters
     ----------
     fun : function
         Function in which every pydynamo equations are written.
-    s : System
-        System to which we add changes.
-    prepare : bool
-        If True, set all constants, and functions (constants, update and init), but not special functions.
 
     Examples 
     --------
@@ -169,11 +107,9 @@ def system_from_fun(fun, s=None, prepare=True):
     >>> def custom_equations():
     >>>     pop.i = 100
     >>>     pop.k = pop.j /2 # Population
-    >>> custom_system = system_from_fun(custom_equations)
+    >>> list_of_equations = list_from_function(custom_equations)
     """
-    lines =[l.strip()
-            for l in inspect.getsource(fun).split('\n')[1:]]
-    return system_from_lines(lines, s, prepare) 
+    return [l.strip() for l in inspect.getsource(fun).split('\n')[1:]]
 
 def check_file(filename):
     """Check if every line in the file can be parsed by the ast module. If not, raise an error.
@@ -190,147 +126,3 @@ def check_file(filename):
                 e.filename = filename
                 e.lineno = i + 1
                 raise
-
-def new_system(raw, s=None):
-    """Create a new system from either a file, a list, or a function, including pydynamo equations.
-
-    Parameters
-    ----------
-    raw : str, iterable(str) or function
-        If str, open the file named `raw` and read pydynamo equations inside (see parse_system.system_from_file`). If iterable(str), each element is a pydynamo equations (see `parse_system.system_from_lines`). If function, read pydynamo equations written inside (see `parse_system.system_from_fun`).
-
-    Returns
-    -------
-    System
-        System object.
-    """
-    if callable(raw):
-        return system_from_fun(raw, s=s)
-    elif isinstance(raw, list):
-        return system_from_lines(raw, s=s)
-    elif isinstance(raw, str):
-        return system_from_file(raw, s=s)
-
-def new_cst_politic(s, cst, date, new_value):
-    """Add a new equations to `s` for the constant `cst`, which becomes a variable changing from its former value to `new_value` after the `date`.
-    
-    Parameters
-    ----------
-    s : System
-        The system to implement the new politic.
-    cst : str
-        Constant name.
-    date : float
-        Date of politic application.
-    new_value : float
-        The new value that `cst` will take after the date `date`.
-    """
-    assert cst in s.eqs['cst'], f"{cst} is not a constant"
-    initval= s.eqs['cst'][cst]['line']
-    del s.eqs['cst'][cst]
-
-    # New equations
-    eq_clip = f"{cst}.k = clip({cst}2, {cst}1, time.k, {cst}_date) # {s.get_comment(cst)} " 
-    eq_date = f"{cst}_date = {date} # Date of {cst} change"
-    eq_cst1 = f"{cst}1 = {initval} # {s.get_comment({cst})} before {cst}_date"
-    eq_cst2 = f"{cst}2 = {new_value} # {s.get_comment({cst})} after {cst}_date"
-
-    # Change calls from cst to var
-    change_cst_to_var(s, cst)         
-
-    # Insert new equations
-    system_from_lines([eq_clip, eq_date, eq_cst1, eq_cst2], s=s, prepare=True)
-
-
-def new_table_politic(s, var, date, new_value):
-    """Add a new equations to `s` for the table used by the variable `var`. The table changes from its former value to `new_value` after the `date`.
-    
-    Parameters
-    ----------
-    s : System
-        The system to implement the new politic.
-    var : str
-        Variable name.
-    date : float
-        Date of politic application.
-    new_value : list(float)
-        The new value that the table will take after the date `date`.
-    """
-    assert f'tabhl' in s.eqs['update'][var]['args']['fun'], f"{var} hasn't tabhl function"
-    table_name = s.eqs['update'][var]['args']['fun']['tabhl']['table']
-    table_init_val = s.eqs['cst'][table_name]['line']
-    var_line = s.eqs['update'][var]['raw_line']
-
-    # Define new equations
-    eq_table_1 = f"{table_name}1 = {table_init_val} # {s.get_comment(table_name)} before {date}"
-    eq_table_2 = f"{table_name}2 = {list(new_value)} # {s.get_comment(table_name)} after {date}"    
-    eq_var_1 = var_line.replace(f'{var}.k', f'{var}1.k').replace(table_name, table_name + '1')
-    eq_var_2 = var_line.replace(f'{var}.k', f'{var}2.k').replace(table_name, table_name + '2')
-    eq_var = f"{var}.k = clip({var}2.k, {var}1.k, time.k, {date}) # {s.get_comment(var)}"
-
-    # Insert new equations
-    system_from_lines([eq_table_1, eq_table_2, eq_var_1, eq_var_2, eq_var], s=s)    
-
-def new_var_politic(s, var, date, eq2):
-    """Add a new equations to `s` for the variable `cst`, which changes from its former value to `new_value` after the `date`.
-    
-    Parameters
-    ----------
-    s : System
-        The system to implement the new politic.
-    var : str
-        Variable name.
-    date : float
-        Date of politic application.
-    new_value : str
-        The new value, written with the pydynamo syntax, that the variable `var` will take after the date `date`.
-    """
-    assert var in s.eqs['update'], f"{var} is not a variable"
-    first_line = s.eqs['update'][var]['raw_line'].split('=')[1]
-    line_init = None
-    if var in s.eqs['init']:
-        line_init = s.eqs['init'][var]['raw_line'].split('=')[1]
-    del s.eqs['update'][var]
-
-    # Define new equations
-    eq_clip = f"{var}.k = clip({var}2.k, {var}1.k, time.k, {var}_date) # {s.get_comment(var)}"
-    eq_date = f"{var}_date = {date} # Date of {var} change"
-    eq_var1 = f"{var}1.k = {first_line} # {s.get_comment(var)} before {var}_date"
-    eq_var2 = f"{var}2.k = {eq2} # {s.get_comment(var)} after {var}_date"
-    eq_init1 = ''
-    eq_init2 = ''
-    if line_init:
-        eq_init1 = f"{var}1.i = {line_init}"
-    eq_init2 = f"{var}2.i = 0"
-
-    # Insert new equations
-    system_from_lines([eq_clip, eq_date, eq_var1, eq_var2, eq_init1, eq_init2], s=s, prepare=True)
-
-
-def change_cst_to_var(s, cst):
-    # TODO: FAIRE AVEC INIT
-    """Change a constant for a variable. TODO: Write documentation."""
-    s.nodes['cst'].remove(cst)
-    for v, args in s.eqs['update'].items():
-        if cst in args['args']['cst']:
-            args['args']['cst'].remove(cst)
-            args['args']['var'].add((cst, 'k'))
-            new_line = re.sub(f'(?<!\w){cst}(?!\w)', f'{cst}_k', args['line'])
-            new_raw_line = re.sub(f'(?<!\w){cst}(?!\w)', f'{cst}.k', args['raw_line'])
-            args['raw_line'] = new_raw_line
-            args['line'] = new_line   
-
-
-def change_var_to_cst(s, var):
-    """Change a variable for a constant. TODO: Write documentation."""
-    pass
-    
-    # s.nodes['var'].remove(var)
-    # for v, args in s.eqs['cst'].items():
-    #     if var in args['args']['var']:
-    #         args['args']['var'].remove(var)
-    #         args['args']['cst'].add((var, 'k'))
-    #         new_line = re.sub(f'(?<!\w){var}(?!\w)', f'{var}_k', args['line'])
-    #         new_raw_line = re.sub(f'(?<!\w){var}(?!\w)', f'{var}.k', args['raw_line'])
-    #         args['raw_line'] = new_raw_line
-    #         args['line'] = new_line   
diff --git a/pydynamo/core/plot_system.py b/pydynamo/core/plot_system.py
index a98d81ab..ca3ddf35 100644
--- a/pydynamo/core/plot_system.py
+++ b/pydynamo/core/plot_system.py
@@ -1,21 +1,66 @@
+"""Functions used by a System instance to plot curves.
+
+"""
+
 import matplotlib.pyplot as plt
-from pyvis.network import Network
-import numpy as np
+from pyvis.network import Network as pyvisNetwork
+
+def plot(self, v_names=None, rescale=False, show_comments=True, filter_no=None, scales=None, colors=None, title='', linestyle='-', outside_legend_number=2, legend=True):
+    """Plot the curves of the last simulation for indicated variables.
+
+    Parameters
+    ----------
+    v_names : iterable(str)
+        Variable to plot names. If None, all variables are plotted.
+
+    rescale : bool
+        If yes, all curves are normalized between 0 and 1.
+
+    show_comments: bool
+        If yes, comments are shown in the legend.
+
+    filter_no: iterable(str)
+        Names of variables that won't appear in the plot.
+
+    scales: dict(str, float)
+        Scales of variables. Variables are divided by their respective scales on the plot.
+
+    colors: dict(str, str)
+        Colors of each variable.
+
+    title: str
+        Title of the plot.
+
+    linestyle: str
+        Linestyle of the plot.
+
+    outside_legend_number: int
+        Number of lines from which legend is plotted outside the graph.
+
+    legend: bool
+        If yes, the legend is drawn.
+    """
+
+    assert 'time' in dir(self), "No simulation have been run for the system !"
 
-def plot_system(s, v_names=None, rescale=False, com=True, filter_no=None, scales=None, colors=None, title='', linestyle='-', outside_legend_number=2, legend=True):
-    assert 'time' in dir(s), "Aucune simulation n'a été lancée pour le système !"
     if not v_names:
-        v_names = s.get_all_variable_names()
+        v_names = self.get_all_variable_names()
+        
     if isinstance(v_names, str):
         v_names = [v_names]
+        
     if filter_no:
         v_names = [n for n in v_names if n not in filter_no]
+        
     for name in v_names:
-        v = getattr(s, name)
+        v = getattr(self, name)
+
+        # Case it's a constant
         try:
             v[0]
         except:
-            v = v+s.get_time()*0
+            v = v+self.get_time()*0
+            
         if scales and name in scales:
             try:
                 v = v/scales[name]
@@ -23,67 +68,132 @@ def plot_system(s, v_names=None, rescale=False, com=True, filter_no=None, scales
                 v = 1 + 0 * v
         elif rescale:
             v = v/max(abs(v))
-        label = name
-        if com:
-            label = label + ' (' + s.get_comment(name).split('\n')[0].capitalize() + ')'
+        
+        label = name     
+        if show_comments:
+            label = label + ' (' + self.definition(name).split('\n')[0].capitalize() + ')'
         try:
             color = colors[name]
         except:
             color = None
-        plt.plot(s.get_time(), v, label=label, color=color, linestyle=linestyle)
+            
+        plt.plot(self.get_time(), v, label=label, color=color, linestyle=linestyle)
+
     if rescale:
         plt.ylabel('Rescaled values')
+
     plt.xlabel('Time')
+    
     if legend:
         if len(v_names) > outside_legend_number:
             plt.legend(loc='center left', bbox_to_anchor=[1, 0.8])
         else:
             plt.legend(loc='center left')
+            
     plt.title(title)
         
-def show_pyvis(s, init_val=True, notebook=False, options=None, highlight=None, colors=None):
-    G = s.get_influence_graph()
-    Gq = Network(notebook=notebook, directed=True)
+def show_influence_graph(self, show_init_val=True, in_notebook=False, options=None, colors=None):
+    """Show variables influence newtork with the Pyvis library.
+    
+    Parameters
+    ----------
+    show_init_val : bool
+        If True, show the initial value for a variable.
+    
+    in_notebook : bool
+        If True, network appears as a Widget in the notebook.
+
+    options : dict
+        Pyvis options.
+    
+    colors : dict
+        Colors of each variable and constant.
+    """
+    
+    G = self.get_influence_graph()
+    Gq = pyvisNetwork(notebook=in_notebook, directed=True)
+    
     for a in G.nodes:
-        com = s.get_comment(a).split('\n')[0]
+        com = self.definition(a).split('\n')[0]
         node = a
-        title = f"{com}<br>= {s.get_eq(a)}"
+        title = f"{com}<br>{self.raw_equation(a)}"
         col = colors[a] if colors and a in colors else None
-        # if init_val == True and a in dir(s):
-        #     title = f'{title}<br>Init: {getattr(s, a)[0]:.2f}'
         
+        if show_init_val == True:
+            if a in self.nodes['var']:
+                title = f'{title}<br>Init: {getattr(self, a)[0]:.2f}'
+                    
         Gq.add_node(node, title=title, color=col)
+    
     for a, b in G.edges:
         nodea, nodeb = (a, b)
         Gq.add_edge(nodea, nodeb)
+    
     if options:
         Gq.show_buttons(options)
+    
     return Gq
 
 
-def plot_non_linearity(s, name):
-    assert 'time' in dir(s), "No simulation yet! Please run the system."
-    x, y, ylabel, xlabel, title = s.get_tabhl_args(name)
+def plot_non_linearity(self, var):
+    """Plot the non linear functions with which the variable is computed.
+
+    Parameters
+    ----------
+    name : str
+        Variable name.
+    """
+    
+    assert 'time' in dir(self), "No simulation yet! Please run the system."
+    
+    x, y, ylabel, xlabel, title = self.get_tabhl_args(var)
+    
     if x is not None and y is not None:
         plt.plot(x, y)
         plt.ylabel(ylabel)
         plt.xlabel(xlabel)
         plt.title(title)
 
-def compare_systems(s1, s2, v_names, scales=None, rescale=False, *args, **kwargs):
-    assert 'time' in dir(s1) and 'time' in dir(s2), "No simulation yet! Please run the system."
+def plot_compare(self, s2, v_names, scales=None, rescale=False, *args, **kwargs):
+    """Show the variables of 2 different systems.
+
+        Parameters
+        ----------
+        s2: System
+            Other system to compare whith.
+
+        v_names: iterable(str)
+            Names of variables or constant to plot.
+
+        scales: dict(str, float)
+            Scales of variables. Variables are divided by their respective scales on the plot.
+
+        rescale: bool
+            If yes, If yes, variables are normalized between 0 and 1.
+
+        *args
+            Argument list for the pydynamo.core.plot_system.plot function.
+
+        **kwargs
+            Argument dictionnary for the pydynamo.core.plot_system.plot function.
+        """
+
+    assert 'time' in dir(self) and 'time' in dir(s2), "No simulation yet! Please run the system before."
+    
     # remove tables
-    v_names = [v for v in v_names if not isinstance(getattr(s1,v), list)]
+    v_names = [v for v in v_names if not isinstance(getattr(self,v), list)]
+    
     if not scales and rescale:
         scales= {}
         for v in v_names:
             try:
-                scales[v] = max(max(getattr(s1, v)), max(getattr(s2, v)))
+                scales[v] = max(max(getattr(self, v)), max(getattr(s2, v)))
             except TypeError:
-                scales[v] = max(getattr(s1, v), getattr(s2, v))
-    plot_system(s1, v_names, *args, **kwargs, linestyle='-', scales=scales)
+                scales[v] = max(getattr(self, v), getattr(s2, v))
+    
+    plot(self, v_names, *args, **kwargs, linestyle='-', scales=scales)
     plt.gca().set_prop_cycle(None)
-    plot_system(s2, v_names, *args, **kwargs, linestyle='--', scales=scales, legend=False)
+    plot(s2, v_names, *args, **kwargs, linestyle='--', scales=scales, legend=False)
     
     
         
diff --git a/pydynamo/core/politics.py b/pydynamo/core/politics.py
new file mode 100644
index 00000000..a4a6c4ef
--- /dev/null
+++ b/pydynamo/core/politics.py
@@ -0,0 +1,124 @@
+import re
+
+def new_politic(self, name, date, new_val):
+        """Implements a new politic for some constant, table or variable from a certain date.
+
+        Parameters
+        ----------
+        name: str
+            Name of a constant, table or variable we want to change.
+
+        date: float
+            date from which the new value will be activated.
+
+        new_val: float, array or string
+            If name refers to a constant, a float with the new value. If name srefers to a table, an array of the sime size as the older one. If name refers to a variable, a string with the new value.
+
+        """
+        if name in self.nodes['cst']:
+            n = getattr(self, name)
+            if '__iter__' in dir(n):
+                self.new_table_politic(name[:-1], date, new_val)
+            else:
+                self.new_cst_politic(name, date, new_val)
+        elif name in self.nodes['var']:
+            self.new_var_politic(name, date, new_val)
+        else:
+            raise NameError(f'No variable or constant named {name} in the System')
+
+def new_cst_politic(self, cst, date, new_value):
+    """Add a new equations to `s` for the constant `cst`, which becomes a variable changing from its former value to `new_value` after the `date`.
+    
+    Parameters
+    ----------
+    s : System
+        The system to implement the new politic.
+    cst : str
+        Constant name.
+    date : float
+        Date of politic application.
+    new_value : float
+        The new value that `cst` will take after the date `date`.
+    """
+    assert cst in self.eqs['cst'], f"{cst} is not a constant"
+    initval= self.eqs['cst'][cst]['line']
+    del self.eqs['cst'][cst]
+
+    # New equations
+    eq_clip = f"{cst}.k = clip({cst}2, {cst}1, time.k, {cst}_date) # {self.definition(cst)} " 
+    eq_date = f"{cst}_date = {date} # Date of {cst} change"
+    eq_cst1 = f"{cst}1 = {initval} # {self.definition({cst})} before {cst}_date"
+    eq_cst2 = f"{cst}2 = {new_value} # {self.definition({cst})} after {cst}_date"
+
+    # Change every call of cst to a call of cst.k
+    for i in range(len(self.code_lines)):
+        self.code_lines[i] = re.sub(f'(?<!\w){cst}(?!\w)', f'{cst}.k', self.code_lines[i])
+        
+    self.add_equations([eq_clip, eq_date, eq_cst1, eq_cst2])
+    self.reset_eqs()
+
+
+def new_table_politic(s, var, date, new_value):
+    """Add a new equations to `s` for the table used by the variable `var`. The table changes from its former value to `new_value` after the `date`.
+    
+    Parameters
+    ----------
+    s : System
+        The system to implement the new politic.
+    var : str
+        Variable name.
+    date : float
+        Date of politic application.
+    new_value : list(float)
+        The new value that the table will take after the date `date`.
+    """
+    assert f'tabhl' in self.eqs['update'][var]['args']['fun'], f"{var} hasn't tabhl function"
+    table_name = self.eqs['update'][var]['args']['fun']['tabhl']['table']
+    table_init_val = self.eqs['cst'][table_name]['line']
+    var_line = self.eqs['update'][var]['raw_line']
+
+    # Define new equations
+    eq_table_1 = f"{table_name}1 = {table_init_val} # {self.definition(table_name)} before {date}"
+    eq_table_2 = f"{table_name}2 = {list(new_value)} # {self.definition(table_name)} after {date}"    
+    eq_var_1 = var_line.replace(f'{var}.k', f'{var}1.k').replace(table_name, table_name + '1')
+    eq_var_2 = var_line.replace(f'{var}.k', f'{var}2.k').replace(table_name, table_name + '2')
+    eq_var = f"{var}.k = clip({var}2.k, {var}1.k, time.k, {date}) # {self.definition(var)}"
+
+    self.add_equations([eq_table_1, eq_table_2, eq_var_1, eq_var_2, eq_var])
+    self.reset_eqs()
+
+def new_var_politic(self, var, date, eq2):
+    """Add a new equations to `s` for the variable `var`, which changes from its former value to `new_value` after the `date`.
+    
+    Parameters
+    ----------
+    s : System
+        The system to implement the new politic.
+    var : str
+        Variable name.
+    date : float
+        Date of politic application.
+    new_value : str
+        The new value, written with the pydynamo syntax, that the variable `var` will take after the date `date`.
+    """
+    assert var in self.eqs['update'], f"{var} is not a variable"
+    first_line = self.eqs['update'][var]['raw_line'].split('=')[1]
+    line_init = None
+    if var in self.eqs['init']:
+        line_init = self.eqs['init'][var]['raw_line'].split('=')[1]
+    del self.eqs['update'][var]
+
+    # Define new equations
+    eq_clip = f"{var}.k = clip({var}2.k, {var}1.k, time.k, {var}_date) # {self.definition(var)}"
+    eq_date = f"{var}_date = {date} # Date of {var} change"
+    eq_var1 = f"{var}1.k = {first_line} # {self.definition(var)} before {var}_date"
+    eq_var2 = f"{var}2.k = {eq2} # {self.definition(var)} after {var}_date"
+    eq_init1 = ''
+    eq_init2 = ''
+    if line_init:
+        eq_init1 = f"{var}1.i = {line_init}"
+    eq_init2 = f"{var}2.i = 0"
+
+    self.add_equations([eq_clip, eq_date, eq_var1, eq_var2, eq_init1, eq_init2])
+    self.reset_eqs()
+
diff --git a/pydynamo/core/psdsystem.py b/pydynamo/core/psdsystem.py
index 2d97191c..0d8de923 100644
--- a/pydynamo/core/psdsystem.py
+++ b/pydynamo/core/psdsystem.py
@@ -74,7 +74,7 @@ class PsdSystem(System):
     def run(self):
         self.df_run = self.model.run()
 
-    def get_eq(self, name):
+    def equation(self, name):
         try:
             return self.caracs[name]['Original Eqn']
         except:
diff --git a/pydynamo/core/system.py b/pydynamo/core/system.py
index 3c68050b..ec18245c 100644
--- a/pydynamo/core/system.py
+++ b/pydynamo/core/system.py
@@ -7,6 +7,7 @@ import re
 from .specials import clip, Sample, step, Interpol
 from .delays import Delay3, Dlinf3, Smooth
 from .parse_dynamo_functions import instance_fun_names
+from .parse_system import get_system_dicts, list_from_function, list_from_file
 from math import log, exp, sqrt
 np.seterr(all='raise')
 
@@ -22,22 +23,84 @@ class System:
     From this dictionnaries, it generates the updating pattern
     and run the simulation.
     """
+    from .plot_system import plot, plot_non_linearity, plot_compare, show_influence_graph
+    from .politics import new_cst_politic, new_var_politic, new_table_politic, new_politic
+    
+    def __init__(self, code=None, prepare=True):
+        """Initialise a System, empty or from pydynamo code.
 
-    def __init__(self):
-        """Initialise an empty System with nodes and equations dictionnaries"""
-        self.nodes = {
-            'cst': set(),
-            'var': set(),
-            'fun': set(),
-            }
+        Parameters
+        ----------
+        code : str, iterable(str) or function
+            If str, open the file named `code` and read pydynamo equations inside (see `parse_system.nec_from_file`). If iterable(str), each element is a pydynamo equations (see `parse_system.nec_from_lines`). If function, read pydynamo equations written inside the function (see `parse_system.nec_system_from_fun`).
+        
+        prepare : bool
+            If True, prepare the System to run (see `System.prepare`).
+
+        Examples
+        --------
+        With a file:
+        Suppose there is the following lines inside the file `pydynamo_equations.py`:
+        ```
+        pop.k = pop.j*2 # Population
+        pop.i = popi
+        popi = 25 # Initial population
+        ```
+        >>> s = System("pydynamo_equations.py')
+        
+        With a list of equations:
+        >>> equations_list = ['pop.k = pop.j*2', 'pop.i = popi', 'popi = 25']
+        >>> s = System(equations_list)
+
+        With a function:
+        >>> def equations_function():
+        >>>     pop.k = pop.j*2 # Population
+        >>>     pop.i = popi
+        >>>     popi = 25 # Initial population
+        >>> 
+        >>> s = System(equations_function)
+        """
 
-        self.eqs = {
-            'cst': dict(),
-            'update': dict(),
-            'init': dict()
-            }
-        self.comments = {}
+        # If code is given, create a System with equations
+        if code:
+            if isinstance(code, list):
+                self.code_lines = code
+            elif callable(code):
+                self.code_lines = list_from_function(code)
+            elif isinstance(code, str):
+                self.code_lines = list_from_file(code)
 
+            self.reset_eqs(prepare)
+            
+        # Otherwise, create an empty System
+        else:
+            self.nodes = {
+                'cst': set(),
+                'var': set(),
+                'fun': set(),
+            }
+            
+            self.eqs = {
+                'cst': dict(),
+                'update': dict(),
+                'init': dict()
+            }
+            self.comments = {}
+            self.code_lines = []
+            
+    def add_equations(self, new_code_lines):
+        """Add new equations to the older ones. 
+        In case there is a conflict for a same variable or constant, the last equation only is remembered.
+        """
+        self.code_lines = self.code_lines + new_code_lines
+        
+    def reset_eqs(self, prepare=True):
+        """Set all nodes, equations and comments.
+        """
+        self.nodes, self.eqs, self.comments = get_system_dicts(self.code_lines)
+        if prepare:
+            self.prepare()
+            
     # Modifiers
     def add_node(self, node, node_type):
         self.nodes[node_type].add(node)
@@ -63,9 +126,6 @@ class System:
     def add_comments(self, comments):
         for node, comment in comments.items():
             self.comments[node] = comment
-
-    def add_units(self, units):
-        self.units = units
         
     def add_function(self, fun, name=None):
         if not name:
@@ -163,10 +223,10 @@ class System:
         assert 'tabhl_' + name in dir(self), 'Error, no such tabhl function'
         f = getattr(self, 'tabhl_' + name)
         x = np.linspace(f.xl, f.xh, 100)
-        ylabel = name + '\n'+ self.get_comment(name)
+        ylabel = name + '\n'+ self.definition(name)
         argop = self.eqs['update'][name]['args']['fun']['tabhl']['val'].strip()
         argop = re.sub('^\(|\)$', '',re.sub('\.[jk]', '', argop)).strip()
-        xlabel = argop + ('\n'+ self.get_comment(argname) if argop==argname else '')
+        xlabel = argop + ('\n'+ self.definition(argname) if argop==argname else '')
         return x, f(x), ylabel, xlabel, f"{name} non-linear function"
 
     def iter_all_eqs(self):
@@ -178,29 +238,49 @@ class System:
 
         return self.eqs[eq_type]
 
-    def get_comment(self, node):
+    def definition(self, node):
+        """Get the definition of a node.
+        """
         try:
             return self.comments[node]
         except:
             return ''
 
-    def get_unit(self, node):
-        try:
-            return self.units[node]
-        except:
-            return ''
 
-    def get_eq(self, var):
+    def equation(self, node):
+        """Returns the reformatted equation of a variable or constant.
+
+        Parameters
+        ----------
+        node : str
+            Name of the variable or constant to plot the equation.
+        """
+        
         for t, eqq in self.eqs.items():
-            if var in eqq:
+            if node in eqq:
                 try:
-                    if 'tabhl' in eqq[var]['raw_line']:
-                        return f'{var}.k = ' + re.sub('tabhl', f'NLF_{var}t', re.sub('\_([jk])', '.\\1', eqq[var]['line']))
-                    return eqq[var]['raw_line']
+                    if 'tabhl' in eqq[node]['raw_line']:
+                        return f'{node}.k = ' + re.sub('tabhl', f'NLF_{node}t', re.sub('\_([jk])', '.\\1', eqq[node]['line']))
+                    return self.raw_equation(node)
                 except:
-                    print(var, eqq)
+                    print(node, eqq)
                     return ''
 
+    def raw_equation(self, node):
+        """Returns the pydynamo raw equation of a variable or constant.
+
+        Parameters
+        ----------
+        node : str
+            Name of the variable or constant to plot the equation.
+        """
+        for t, eqq in self.eqs.items():
+            try:
+                return eqq[node]['raw_line']
+            except:
+                pass
+        return ''
+
     # Infos
     def is_initialized(self, var):
         return var in self.eqs['init']
@@ -262,6 +342,8 @@ class System:
         return G
 
     def get_influence_graph(self):
+        """Get the graph of influences: an arrow from A to B if B needs A (at initialisation or updating step) to be computed.
+        """
         G = nx.DiGraph()
         for v in self.eqs['update']:
             G.add_node(v)
@@ -290,6 +372,7 @@ class System:
                 if ac in self.eqs['cst']:
                     G.add_edge(ac, c)
         return G
+
     # Assertions
     def assert_cst_defined(self):
         for c in self.nodes['cst']:
@@ -449,11 +532,14 @@ class System:
             fun = Sample(isam, self.time)
             setattr(self, p['fun'], fun)
 
-        if f_type == 'step':
+        if f_type == 'step' or type =='clip':
             pass
     
     def step(self, hght, sttm, k):
         return step(hght, sttm, self.time[k])
+
+    def clip(self, v2, v1, t, y):
+        return clip(v2, v1, t, y)
     
     def set_all_special_functions(self):
         """
@@ -539,7 +625,7 @@ class System:
                     line = self.eqs['update'][var]['line']
                     raise(AttributeError(f"In updating {var}:\n"
                                          f"{var} = {line}\n"
-                                         f"{e}")) from None
+                                         f"{e}")) from e
 
                 # Assert that there is no variables considered as constants etc.
                 tps = {'cst', 'var', 'fun'}
@@ -589,6 +675,8 @@ class System:
 
 
     def prepare(self):
+        """Assert that all equations are well defined, ant that the updating graph is acyclic. Also set all updating functions and constants.
+"""
         self.assert_cst_defined()
         self.assert_init_defined()
         self.assert_update_defined()
@@ -685,16 +773,17 @@ class System:
             self._update_all_fast(k)
 
     def copy(self, prepare=True):
-        """Returns a copy of the model"""
-        s = System()
-        s.add_all_nodes(self.nodes)
-        s.add_all_eqs(self.eqs)
-        s.add_comments(self.comments)
-        if prepare:
-            s.prepare()
+        """Returns a copy of the model, with 
+
+        Parameters
+        ----------
+        prepare : bool
+            If yes, prepare the system to run.
+        """
+        
+        s = System(self.code_lines)
         for cst in self.nodes['cst']:
             setattr(s, cst, getattr(self, cst))
-
         return s
 
     def get_out_nodes(self, node, with_definitions=False):
@@ -711,7 +800,7 @@ class System:
         
         out_nodes = [b for (a, b) in self.get_influence_graph().out_edges(node)]
         if with_definitions:
-            return {a: self.get_comment(a) for a in out_nodes}
+            return {a: self.definition(a) for a in out_nodes}
         else:
             return out_nodes
         
@@ -729,13 +818,14 @@ class System:
         
         in_nodes =  [a for (a, b) in self.get_influence_graph().in_edges(node)]
         if with_definitions:
-            return {a: self.get_comment(a) for a in in_nodes}
+            return {a: self.definition(a) for a in in_nodes}
         else:
             return in_nodes
     
     def get_at(self, var, t):
         """Returns the value of var at time t, or an interpolation if between rwo timestep values."""
         assert var in self.nodes['var'], f"{var} is not a variable"
+        assert var in dir(self), "Simulation hasn't been run yet !"
         assert t >= self.initial_time and t <= self.final_time, "{t} is out of bounds {self.initial_time}, {s.final_time}"
 
         if t == self.final_time:
@@ -746,6 +836,14 @@ class System:
         v = getattr(self, var)
         return v[idx1]*(1 - dd) + v[idx1 + 1]*dd
 
+    def __getitem__(self, arg):
+        try:
+            name, year = arg
+            return self.get_at(name, year)
+
+        except TypeError:
+            return getattr(self, arg)
+    
     def get_different_csts(self):
         """Returns all variables which value is different thatn in the equations and their new values."""
         dic_cst = {cst: getattr(self, cst) for cst in self.nodes['cst'] if cst in dir(self)}
@@ -760,4 +858,4 @@ class System:
                 return False
             except: return any(a!=b)
         return {cst: (v1, v2) for cst, (v1, v2) in both.items() if diff(v1, v2)}
-        
+    
diff --git a/pydynamo/world2/__init__.py b/pydynamo/world2/__init__.py
index f8b14630..95efb10a 100644
--- a/pydynamo/world2/__init__.py
+++ b/pydynamo/world2/__init__.py
@@ -14,5 +14,3 @@ def get_w2():
     w2.add_comments(w2_defs)
     return w2
 
-def plot_w2(w2, title=''):
-    plot_system(w2, scales_w2, scales=scales_w2, title=title)
diff --git a/pydynamo/world3/world3_class.py b/pydynamo/world3/world3_class.py
index 5d3b37ef..a5df591d 100644
--- a/pydynamo/world3/world3_class.py
+++ b/pydynamo/world3/world3_class.py
@@ -3,80 +3,33 @@ Define World3 class
 
 """
 from pydynamo.core.system import System
-from pydynamo.core.plot_system import *
-from pydynamo.core.parse_system import new_cst_politic, new_table_politic, new_var_politic,new_system, system_from_lines, system_from_fun, system_from_file
 from .data_world3 import w3_code, w3_defs, var_color
-from .plot_utils import plot_world_03, plot_world_with_scales
+from .plot_utils import plot_world_03
 from .scenarios_limits_to_growth import scenarios
 
-def get_w3():
-    w3 = system_from_lines(w3_code)
-    w3.add_comments(w3_defs)
-    return w3
-
-def get_scenario(number):
-    w3s = get_w3()
-    changes = scenarios[number - 1]['changes']
-    for c, v in changes.items():
-        setattr(w3s, c, v)
-    return w3s, scenarios[number - 1]['title']
-
-
 class World3(System):
     """
     A World3 object is a System object with more convenient functions and defaults, adapted for the manipulation of the World3 model 2003's version equations.
     """
-    def __init__(self, scenario_number=2, additional_equations=None, sys=None):
-        """Initialise a World3 object. By default, the scenario number is the second one, because it's the most "realistic" when we compare to the current situation."""
-        s, title = get_scenario(scenario_number)
-        if sys:
-            s = sys
-        self.eqs = s.eqs
-        self.nodes = s.nodes
-        self.comments = s.comments
-        self.prepare()
-        for cst in self.nodes['cst']:
-            setattr(self, cst, getattr(s, cst))
-        if additional_equations:
-            new_system(additional_equations, s=self)
-
-    def new_politic(self, name, date, new_val):
-        """Implements a new politic for some constant, table or variable from a certain date.
-
-        Parameters
-        ----------
-        name: str
-            Name of a constant, table or variable we want to change.
-
-        date: float
-            date from which the new value will be activated.
-
-        new_val: float, array or string
-            If name refers to a constant, a float with the new value. If name srefers to a table, an array of the sime size as the older one. If name refers to a variable, a string with the new value.
-
+    def __init__(self, scenario_number=2, sys=None):
+        """Initialise a World3 object. By default, the scenario number is the second one, because it's the most "realistic" when we compare to the current situation (in 2022).
         """
-        if name in self.nodes['cst']:
-            n = getattr(self, name)
-            if '__iter__' in dir(n):
-                new_table_politic(self, name[:-1], date, new_val)
-            else:
-                new_cst_politic(self, name, date, new_val)
-        elif name in self.nodes['var']:
-            new_var_politic(self, name, date, new_val)
-
+        self.code_lines = w3_code
+        changes = scenarios[scenario_number - 1]['changes']
+        for cst, eq in changes.items():
+            self.code_lines.append(f'{cst} = {eq}')
+        self.reset_eqs()
+        self.add_comments(w3_defs)
+        
     def copy(self):
-        """Returns a copy of the system, with same equations.
-
-        Returns
-        -------
-        World3:
-            Copy of the system.
+        """Returns a copy of the system, with the same equations and constant values.
         """
 
         return World3(sys=super().copy())
 
     def run(self, N=400, dt=0.5):
-        """Run the system with 400 steps of 1/2 year"""
+        """Run the system with 400 steps of 1/2 year.
+        """
         super().run(N, dt)
 
     def plot_world(self, **kwargs):
@@ -84,97 +37,3 @@ class World3(System):
 
         plot_world_03(self, with_legend=True, **kwargs)
 
-    def plot(self, *args, **kwargs):
-        """Plot the given variables and constants of a system.
-
-        Parameters
-        ----------
-        v_names: iterable(str)
-            Name of variables
-
-        rescale: bool
-            If yes, variables are normalized between 0 and 1
-
-        com: bool
-            If yes, comments are shown in the legend
-
-        filter_no: iterable(str)
-            Names of variables that won't appear in the plot
-
-        scales: dict(str, float)
-            Scales of variables. Variables are divided by their respective scales on the plot.
-
-        colors: dict(str, str)
-            Colors of each variable
-
-        title: str
-            Title of the plot
-
-        linestyle: str
-            Linestyle of the plot
-
-        outside_legend_number: int
-            Number of lines from which legend is plotted outside the graph
-
-        legend: bool
-            If yes, the legend is drawn
-        """
-
-        plot_system(self, *args, **kwargs)
-
-    def definition(self, name):
-        """Returns the definition of a variable or constant."""
-        return self.get_comment(name)
-
-    def equation(self, name):
-        """Returns the pydynamo equation of a variable or constant."""
-        return self.get_eq(name)
-
-    def plot_non_linearity(self, var_name):
-        """Plot the non linear function used by the variable, if exists."""
-        plot_non_linearity(self, var_name)
-
-    def plot_compare(self, s, *args, **kwargs):
-        """Show the variables of 2 different systems.
-
-        Parameters
-        ----------
-        s: System
-            Other system to compare whith.
-
-        v_names: iterable(str)
-            Names of variables or constant to plot
-
-        scales: dict(str, float)
-            Scales of variables. Variables are divided by their respective scales on the plot.
-
-        rescale: bool
-            If yes, If yes, variables are normalized between 0 and 1
-
-        *args
-            Argument list for the pydynamo.core.plot_utils.plot_system function
-
-        **kwargs
-            Argument dictionnary for the pydynamo.core.plot_utils.plot_system function
-        """
-
-        compare_systems(self, s, *args, **kwargs)
-
-    def __getitem__(self, arg):
-        try:
-            name, year = arg
-            return self.get_at(name, year)
-
-        except TypeError:
-            return getattr(self, arg)
-
-    def add_equations(self, equations):
-        """Add new equations to the system. Each variable uses the newest equation.
-
-        Parameters
-        ----------
-        equations: list(str)
-            New equations to add.
-
-        """
-        new_system(equations, self)
-- 
GitLab