From 19e69b7bf9d2c996164b370c2e15302ca6464084 Mon Sep 17 00:00:00 2001
From: VIGNET Pierre <pierre.vignet@irisa.fr>
Date: Mon, 25 Nov 2019 02:29:17 +0100
Subject: [PATCH] [lib] Refactor tests for CLUnfolder; add tests for
 init_forward_unfolding and shift

---
 .../clause_constraints/mcl/TestCLUnfolder.py  | 574 ++++++++++++++++--
 1 file changed, 533 insertions(+), 41 deletions(-)

diff --git a/library/cadbiom/models/clause_constraints/mcl/TestCLUnfolder.py b/library/cadbiom/models/clause_constraints/mcl/TestCLUnfolder.py
index ea729b5..38655e7 100644
--- a/library/cadbiom/models/clause_constraints/mcl/TestCLUnfolder.py
+++ b/library/cadbiom/models/clause_constraints/mcl/TestCLUnfolder.py
@@ -39,20 +39,49 @@
 ##
 ## Contributor(s):
 ##
-'''
-Unitary Test of the unfolder
-'''
+"""Unitary Test of the unfolder"""
 from __future__ import print_function
 import unittest
-
+import pytest
+import pkg_resources
 
 from cadbiom.models.guard_transitions.chart_model import ChartModel
 from cadbiom.models.clause_constraints.CLDynSys import Literal, Clause
 from cadbiom.models.guard_transitions.analyser.ana_visitors import TableVisitor
 from cadbiom.models.clause_constraints.CLDynSys import CLDynSys
-from cadbiom.models.clause_constraints.mcl.MCLTranslators import  GT2Clauses
+from cadbiom.models.clause_constraints.mcl.MCLTranslators import GT2Clauses
 from cadbiom.models.clause_constraints.mcl.CLUnfolder import CLUnfolder
 
+from cadbiom.models.clause_constraints.mcl.MCLAnalyser import MCLAnalyser
+from cadbiom.models.clause_constraints.mcl.MCLQuery import MCLSimpleQuery
+
+
+class ErrorReporter(object):
+    """error reporter of the compil type"""
+    def __init__(self):
+        self.context = ""
+        self.error = False
+        pass
+
+    def display(self, mess):
+        """
+        just printing
+        """
+        self.error = True
+        print('\n>> '+self.context+"  "+mess)
+
+    def display_info(self, mess):
+        """
+        just printing
+        """
+        print('\n-- '+mess)
+
+    def set_context(self, cont):
+        """
+        for transition compiling
+        """
+        self.context = cont
+
 # helper functions
 
 def string_to_clause(text_clause):
@@ -87,8 +116,7 @@ def model1():
 
 def model2():
     """
-    A simple ChartModel with two nodes and a transition
-    and a cycle without start
+    ChartModel model1 + 3 nodes and 2 transitions and a cycle without start
     """
     # build dynamical system
     model = model1()
@@ -103,8 +131,7 @@ def model2():
 
 def model3():
     """
-    A simple ChartModel with two nodes and a transition
-    and a cycle without start
+    ChartModel model2 + 1 node + 1 start node and a transition
     """
     # build dynamical system
     model = model2()
@@ -116,7 +143,11 @@ def model3():
 
 def model4():
     """
-    Simple chart model with one input and two free clocks
+    ChartModel with:
+        - 1 input node: in1
+        - 5 nodes: n1, n2, n3, n4, n5
+        - 5 transitions: n1-n2, n3-n4, n4-n5, n5-n3, in1-n1
+        - 2 free clocks: hh1: (n3 or n4) on n1-n2, hh2: (n1 and n3) on n4-n5
     """
     model = ChartModel("Test")
     root = model.get_root()
@@ -145,9 +176,7 @@ def model4():
     return model
 
 def create_unfolder(model):
-    """
-    returns an unfolder for model
-    """
+    """Return an unfolder for the given model"""
     tvisit = TableVisitor(None) # no error display
     model.accept(tvisit)
     cld = CLDynSys( tvisit.tab_symb, None)
@@ -157,21 +186,37 @@ def create_unfolder(model):
     # unfolder
     return CLUnfolder(cld)
 
-# simple reporter: register errors
-class ErrorReporter(object):
-    """
-    Simple reporter for tests
-    """
-    def __init__(self):
-        self.error = False
-
+@pytest.fixture()
+def textual_properties():
+    """start, invariant, final"""
+    return (
+        ("M", "L", "C"), # No solution (M activated)
+        ("", "L", "C and K"), # Solution: I E D F
+        ("", "", "C"), # Solutions: F E L D, I E D F
+    )
+
+@pytest.fixture()
+def numerical_properties():
+
+    return (
+        ([[13]], [[12]], [[3]]),
+        ([], [[12]], [[3, -47], [11, -47], [-3, -11, 47], [47]]),
+    )
+
+@pytest.fixture()
+def feed_mclanalyser():
+    """Setup MCLAnalyser with a bcx model"""
+    rep = ErrorReporter()
+    # Force __var_list to be sorted at the output of base_var_set from
+    # CLDynSys for reproductibility in tests (literals naming)
+    mcla = MCLAnalyser(rep, debug=True)
+    filename = pkg_resources.resource_filename(
+        __name__, # package name
+        "examples/example_model.bcx"
+    )
+    mcla.build_from_chart_file(filename)
+    return mcla
 
-    def display(self, mess):
-        """
-        as said
-        """
-        self.error = True
-        #print('\n>> '+mess)
 
 class TestCLUnfolder(unittest.TestCase):
     """
@@ -179,7 +224,7 @@ class TestCLUnfolder(unittest.TestCase):
     """
     def test_var_name(self):
         """
-        Test of variables uncoding and decoding
+        Test of variables at initial state uncoding and decoding
         """
         model = model1()
         unfolder = create_unfolder(model)
@@ -215,7 +260,7 @@ class TestCLUnfolder(unittest.TestCase):
         """
         Test frontier computation and encoding
         """
-        # model1
+        ## model1
         model = model1()
         unfolder = create_unfolder(model)
         # test frontier: should be n1
@@ -224,7 +269,7 @@ class TestCLUnfolder(unittest.TestCase):
         res = res and (len(unfolder.frontier_values) == 1)
         self.assert_(res,'Error in frontier: model1')
 
-        # model2 (one cycle without start)
+        ## model2 (one cycle without start)
         model = model2()
         unfolder = create_unfolder(model)
 
@@ -234,7 +279,7 @@ class TestCLUnfolder(unittest.TestCase):
         res = res and (len(unfolder.frontier_values) == 1)
         self.assert_(res,'Error in frontier: model2')
 
-        # model3: same as model2 but with a start on n3
+        ## model3: same as model2 but with a start on n3
         model = model3()
         unfolder = create_unfolder(model)
 
@@ -245,11 +290,10 @@ class TestCLUnfolder(unittest.TestCase):
         res = res and unfolder.get_var_name(frontier_values[1]) == 'n3'
         self.assert_(res,'Error in frontier: model3')
 
-        # model4
+        ## model4 - No frontiers (even if we can get input node in1 in the solutions)
         model = model4()
         unfolder = create_unfolder(model)
-        frontier_values = unfolder.frontier_values
-        res = len(frontier_values) == 0
+        res = len(unfolder.frontier_values) == 0
         self.assert_(res,'Error in frontier: model4')
 
 
@@ -281,16 +325,464 @@ class TestCLUnfolder(unittest.TestCase):
         res = found_names == {'in1'}
         self.assert_(res,'Error in inputs: model4')
 
-    @unittest.skip("Test is not implemented")
-    def test_init_forward(self):
-        """
-        test forward initialization for various models and property
-        """
-        pass
-        # Dommage...
 
 
 
+def init_forward_unfolding_part_1(unfolder):
+    """Initialization step only"""
+
+    unfolder._CLUnfolder__shift_direction = 'FORWARD'
+    unfolder._CLUnfolder__current_step = 1
+    unfolder._CLUnfolder__shift_step = unfolder.shift_step_init  # back to basic!
+    unfolder._CLUnfolder__aux_code_table = dict()            # flush auxiliary variables
+    unfolder._CLUnfolder__aux_list = []                      # idem
+
+    # Init properties to generate all variable num codes
+    unfolder._CLUnfolder__init_initial_constraint_0()
+    unfolder._CLUnfolder__init_final_constraint_0()
+    unfolder._CLUnfolder__init_invariant_constraint_0()
+    unfolder._CLUnfolder__init_variant_constraints_0()
+
+def init_forward_unfolding_part_2(unfolder):
+    """Shift step only"""
+
+    # Now shifting is possible
+    unfolder._CLUnfolder__forward_init_dynamic()
+    unfolder._CLUnfolder__shift_final()
+    unfolder._CLUnfolder__shift_invariant()
+    # PS: __variant_constraints si already initialized for the first step.
+
+def test_init_unfolder(feed_mclanalyser):
+
+    mcla = feed_mclanalyser
+    unfolder = mcla.unfolder # shortcut
+
+    # Initialization step only
+    init_forward_unfolding_part_1(mcla.unfolder)
+
+    ## Test CLDynSys
+    # 15 places
+    assert unfolder.get_var_number() == 46
+
+    # __var_list is built on casted set; MCLAnalyser is un debug mode
+    # so the list is sorted
+    expected = [
+        '##',
+        'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'P',
+        '_h2', '_h3', '_h4', '_h5', '_h6', '_h7', '_h_0', '_h_1', '_lit0', '_lit1',
+        '_lit10', '_lit11', '_lit12', '_lit13', '_lit14', '_lit15', '_lit16',
+        '_lit17', '_lit18', '_lit19', '_lit2', '_lit20', '_lit21', '_lit22',
+        '_lit3', '_lit4', '_lit5', '_lit6', '_lit7', '_lit8', '_lit9'
+    ]
+
+    print(unfolder._CLUnfolder__var_list)
+    assert unfolder._CLUnfolder__var_list == expected
+
+    ## Test mapping of variables names to values
+    expected_var_code_table = {
+        '_lit22': 39, '_lit20': 37, '_lit21': 38, '_lit19': 35,
+        '_lit18': 34, '_lit6': 43, '_lit17': 33, '_lit13': 29, '_lit9': 46,
+        '_lit8': 45, '_h_0': 22, '_h_1': 23, '_lit16': 32, '_lit12': 28,
+        '_lit3': 40, '_lit2': 36, '_lit1': 25, '_lit0': 24, '_lit7': 44,
+        '_lit11': 27, '_lit5': 42, '_lit4': 41,
+        'A': 1, 'C': 3, 'B': 2, 'E': 5, 'D': 4, 'G': 7, 'F': 6, 'I': 9, 'H': 8,
+        'K': 11, 'J': 10, 'M': 13, 'L': 12, '_lit15': 31, 'N': 14, 'P': 15,
+        '_lit14': 30, '_h4': 18, '_h5': 19, '_h6': 20, '_h7': 21, '_h2': 16,
+        '_h3': 17, '_lit10': 26
+    }
+
+    # Check presence of all basic literals (not future ones with "`" added)
+    found_var_code_table = list(unfolder._CLUnfolder__var_code_table.items())
+    print(found_var_code_table)
+    for lit_name_val in expected_var_code_table.items():
+        assert lit_name_val in found_var_code_table
+
+    # var_code_table is twice as big as __var_list items
+    # (future literals with "`" are added)
+    assert len(found_var_code_table) == 2 * len(expected_var_code_table)
+
+    ## Test frontiers, places, values and names
+    print("Frontier values", unfolder.frontier_values)
+    assert set(unfolder.frontier_values) == {4, 5, 6, 7, 9, 12, 14} # D E F G I L N
+
+    frontiers_values_mapping = {
+        value: unfolder.get_var_name(value) for value in unfolder.frontier_values
+    }
+    print("Frontiers values mapping", frontiers_values_mapping)
+    assert frontiers_values_mapping == {4: 'D', 5: 'E', 6: 'F', 7: 'G', 9: 'I', 12: 'L', 14: 'N'}
+
+    found = unfolder.frontiers_negative_values
+    print("Frontiers negatives values", found)
+    assert found == frozenset([-14, -12, -9, -7, -6, -5, -4])
+
+    found = unfolder.frontiers_pos_and_neg
+    assert found == frozenset([4, 5, 6, 7, 9, 12, 14, -14, -12, -9, -7, -6, -5, -4])
+
+    # Check clocks
+    free_clocks = unfolder._CLUnfolder__free_clocks
+    print("Free clocks/events", free_clocks)
+    assert free_clocks == frozenset([16, 17, 18, 19, 20, 21, 22, 23])
+
+    clocks_values_mapping = {
+        value: unfolder.get_var_name(value) for value in unfolder._CLUnfolder__free_clocks
+    }
+    print("Clocks values mapping", clocks_values_mapping)
+    assert clocks_values_mapping == {16: '_h2', 17: '_h3', 18: '_h4', 19: '_h5', 20: '_h6', 21: '_h7', 22: '_h_0', 23: '_h_1'}
+
+    # Check input places
+    assert unfolder._CLUnfolder__inputs == frozenset()
+
+    # Not frontiers: A B C H J K M P
+    found = unfolder._CLUnfolder__no_frontier_init
+    print("Places not frontiers", found)
+    assert found == [[-1], [-2], [-3], [-8], [-10], [-11], [-13], [-15]]
+
+
+
+def test_init_forward(feed_mclanalyser, textual_properties, numerical_properties):
+    """Test forward initialization for various models and properties types
+
+    Specific call:
+    pytest cadbiom/models/clause_constraints/mcl/TestCLUnfolder.py::test_init_forward
+
+    Query attributes:
+    self.dim_start = []         # list<DClause>
+    self.dim_inv = []           # list<DClause>
+    self.dim_final = []         # list<DClause>
+    self.dim_variant_prop = []  # list<list<DClause>>
+
+    CLUnfolder attributes that contain query attributes:
+    self.__initial_property = None            # logical formula - literal boolean expression
+    self.__dimacs_initial = None              # list of DIMACS clauses
+    self.__final_property = None              # logical formula
+    self.__dimacs_final = None                # list of DIMACS clauses
+    self.__invariant_property = None          # logical formula
+    self.__dimacs_invariant = None            # list of DIMACS clauses
+    self.__variant_property = None            # list<logic formulas>
+    self.__dimacs_variant = None              # list<list<DIMACS clauses>>
+
+    CLUnfolder attributes that contain clauses:
+    self.__list_variant_constraints = None    # list<list<DIMACS clauses>>
+
+    dynamic_constraints => __dynamic_constraints
+    initial_constraints => __initial_constraints
+    invariant_constraints => __invariant_constraints
+    variant_constraints => __variant_constraints
+    final_constraints => __final_constraints
+
+    Mangling prefix: unfolder._CLUnfolder
+
+    TODO:
+        - search solutions for the given properties
+        - test variant properties
+    """
+    mcla = feed_mclanalyser
+
+    ## First properties
+    ### Textual properties
+    start, invariant, final = textual_properties[0]
+
+    query = MCLSimpleQuery(start, invariant, final)
+    mcla.unfolder.init_with_query(query)
+
+    init_forward_unfolding_solution_1(mcla)
+    ## TODO: search sol
+
+    ### DIMACS properties
+    dim_start, dim_inv, dim_final = numerical_properties[0]
+    query = MCLSimpleQuery(None, None, None)
+    query.dim_start = dim_start
+    query.dim_inv = dim_inv
+    query.dim_final = dim_final
+    mcla.unfolder.init_with_query(query)
+    init_forward_unfolding_solution_1(mcla)
+
+
+    ## Second properties
+    ### Textual properties
+    start, invariant, final = textual_properties[1]
+
+    query = MCLSimpleQuery(start, invariant, final)
+    mcla.unfolder.init_with_query(query)
+
+    init_forward_unfolding_solution_2(mcla)
+    ## TODO: search sol
+
+    ### DIMACS properties: TODO: marche pas: les litéraux supplémentaires ne sont pas pris en compte...
+    dim_start, dim_inv, dim_final = numerical_properties[1]
+    query = MCLSimpleQuery(None, None, None)
+    query.dim_start = dim_start
+    query.dim_inv = dim_inv
+    query.dim_final = dim_final
+    mcla.unfolder.init_with_query(query)
+    init_forward_unfolding_solution_2(mcla)
+
+
+
+def init_forward_unfolding_solution_1(mcla):
+    """
+    - Test first part of init_forward_unfolding: init of constraints
+    - Test second part of init_forward_unfolding: shift of initialized constraints
+
+    ("M", "L", "C"), # No solution (M activated)
+    """
+    unfolder = mcla.unfolder # shortcut
+    init_forward_unfolding_part_1(mcla.unfolder)
+
+    ############################################################################
+
+    print(unfolder.dynamic_constraints)
+    print(unfolder.initial_constraints)
+    print(unfolder.invariant_constraints)
+    print(unfolder.variant_constraints)
+    print(unfolder.final_constraints)
+
+    # No auxiliary variables
+    assert unfolder._CLUnfolder__aux_code_table == dict()
+    assert unfolder._CLUnfolder__aux_list == []
+
+    # MCLA could be reused, so __dynamic_constraints is not empty at this step
+    # artificial reset
+    # unfolder._CLUnfolder__dynamic_constraints = []
+    # assert unfolder.dynamic_constraints == []
+
+    # no frontiers + M (start place)
+    assert unfolder.initial_constraints == [[-1], [-2], [-3], [-8], [-10], [-11], [-13], [-15], [13]]
+
+    # L
+    assert unfolder.invariant_constraints == [[[12]]]
+
+    # No variant constraint
+    assert unfolder.variant_constraints == []
+
+    # C
+    assert unfolder.final_constraints == [[3]]
+
+    ############################################################################
+
+    init_forward_unfolding_part_2(unfolder)
+
+    print(unfolder.dynamic_constraints)
+    print(unfolder.invariant_constraints)
+    print(unfolder.final_constraints)
+
+    # Shift of the system clauses (and syst aux clauses)
+    # The maximum value is 46 since there is 46 literals
+    expected = [
+        [[-24, 16], [-24, 4], [-16, -4, 24], [-15, 42], [-12, 42], [15, 12, -42],
+        [-42, 41], [-11, 41], [42, 11, -41], [2, -40], [41, -40], [-2, -41, 40],
+        [40, -36], [-13, -36], [-40, 13, 36], [1, -25], [36, -25], [-1, -36, 25],
+        [-43, 22], [-43, 25], [-22, -25, 43], [-47, 24, 1], [-47, 24, -43],
+        [47, -24], [47, -1, 43], [5, -44], [6, -44], [-5, -6, 44], [-45, 17],
+        [-45, 44], [-17, -44, 45], [1, -27], [41, -27], [-1, -41, 27], [27, -26],
+        [-13, -26], [-27, 13, 26], [2, -46], [26, -46], [-2, -26, 46], [-30, 22],
+        [-30, 46], [-22, -46, 30], [-48, 45, 2], [-48, 45, -30], [48, -45],
+        [48, -2, 30], [31, -30], [31, -43], [30, 43, -31], [-49, 31, 3], [49, -31],
+        [49, -3], [-50, 4], [-50, -24], [50, -4, 24], [-51, 5], [-51, -45],
+        [51, -5, 45], [-52, 6], [52, -6], [-32, 21], [-32, 7], [-21, -7, 32],
+        [-53, 7], [-53, -32], [53, -7, 32], [-54, 32, 8], [54, -32], [54, -8],
+        [-33, 20], [-33, 11], [-20, -11, 33], [-34, 18], [-34, 9], [-18, -9, 34],
+        [-55, 33, 9], [-55, 33, -34], [55, -33], [55, -9, 34], [-35, 19], [-35, 10],
+        [-19, -10, 35], [-56, 34, 10], [-56, 34, -35], [56, -34], [56, -10, 35],
+        [-57, 35, 11], [-57, 35, -33], [57, -35], [57, -11, 33], [-58, 12],
+        [58, -12], [-37, 23], [-37, 14], [-23, -14, 37], [-38, 23], [-38, 14],
+        [-23, -14, 38], [39, -37], [39, -38], [37, 38, -39], [-60, 14], [-60, -39],
+        [60, -14, 39], [-59, 37, 13], [59, -37], [59, -13], [-61, 38, 15], [61, -38],
+        [61, -15], [-18, 9], [-19, 10], [-20, 11], [-21, 7], [-16, 4], [-17, 5],
+        [-22, 1, 2], [-23, 14, 14]]]
+
+    # Crap
+    assert unfolder.dynamic_constraints == expected
+
+    # L
+    assert unfolder.invariant_constraints == [[[12]], [[58]]]
+
+    # No variant constraint (not shifted in second part of init_forward_unfolding)
+    assert unfolder.variant_constraints == []
+
+    # C + 46
+    assert unfolder.final_constraints == [[49]]
+
+
+def init_forward_unfolding_solution_2(mcla):
+    """
+    - Test first part of init_forward_unfolding: init of constraints
+    - Test second part of init_forward_unfolding: shift of initialized constraints
+
+    ("", "L", "C and K") # I E D F
+    """
+    unfolder = mcla.unfolder # shortcut
+    init_forward_unfolding_part_1(mcla.unfolder)
+
+    ############################################################################
+
+    print(unfolder.dynamic_constraints)
+    print(unfolder.initial_constraints)
+    print(unfolder.invariant_constraints)
+    print(unfolder.variant_constraints)
+    print(unfolder.final_constraints)
+
+    # No auxiliary variables: "C and K" property is added
+    assert unfolder._CLUnfolder__aux_code_table == {'_lit47': 47}
+    assert unfolder._CLUnfolder__aux_list == ['_lit47']
+
+    # MCLA could be reused, so __dynamic_constraints is not empty at this step
+    # artificial reset
+    # unfolder._CLUnfolder__dynamic_constraints = []
+    # assert unfolder.dynamic_constraints == []
+
+    # no frontiers + nothing (no start place)
+    assert unfolder.initial_constraints == [[-1], [-2], [-3], [-8], [-10], [-11], [-13], [-15]]
+
+    # L
+    assert unfolder.invariant_constraints == [[[12]]]
+
+    # No variant constraint
+    assert unfolder.variant_constraints == []
+
+    # "C and K" = _lit47
+    # C, not _lit47
+    # K, not _lit47
+    # not C, not K, _lit47
+    # _lit47
+    assert unfolder.final_constraints == [[3, -47], [11, -47], [-3, -11, 47], [47]]
+
+    # PS:
+    # "C or K" = _lit47
+    # not C, _lit47
+    # not K, _lit47
+    # C, K, not _lit47
+    # _lit47
+    #[[-3, 47], [-11, 47], [3, 11, -47], [47]]
+
+    ############################################################################
+
+    init_forward_unfolding_part_2(unfolder)
+
+    print(unfolder.dynamic_constraints)
+    print(unfolder.invariant_constraints)
+    print(unfolder.final_constraints)
+
+    # Shift of the system clauses (and syst aux clauses)
+    # The maximum value is now 48 since there is 47 literals
+    assert unfolder.get_var_number() == 46
+    assert unfolder.get_shift_step() == 47
+    expected = [
+        [[-24, 16], [-24, 4], [-16, -4, 24], [-15, 42], [-12, 42], [15, 12, -42],
+        [-42, 41], [-11, 41], [42, 11, -41], [2, -40], [41, -40], [-2, -41, 40],
+        [40, -36], [-13, -36], [-40, 13, 36], [1, -25], [36, -25], [-1, -36, 25],
+        [-43, 22], [-43, 25], [-22, -25, 43], [-48, 24, 1], [-48, 24, -43],
+        [48, -24], [48, -1, 43], [5, -44], [6, -44], [-5, -6, 44], [-45, 17],
+        [-45, 44], [-17, -44, 45], [1, -27], [41, -27], [-1, -41, 27], [27, -26],
+        [-13, -26], [-27, 13, 26], [2, -46], [26, -46], [-2, -26, 46], [-30, 22],
+        [-30, 46], [-22, -46, 30], [-49, 45, 2], [-49, 45, -30], [49, -45],
+        [49, -2, 30], [31, -30], [31, -43], [30, 43, -31], [-50, 31, 3], [50, -31],
+        [50, -3], [-51, 4], [-51, -24], [51, -4, 24], [-52, 5], [-52, -45],
+        [52, -5, 45], [-53, 6], [53, -6], [-32, 21], [-32, 7], [-21, -7, 32],
+        [-54, 7], [-54, -32], [54, -7, 32], [-55, 32, 8], [55, -32], [55, -8],
+        [-33, 20], [-33, 11], [-20, -11, 33], [-34, 18], [-34, 9], [-18, -9, 34],
+        [-56, 33, 9], [-56, 33, -34], [56, -33], [56, -9, 34], [-35, 19], [-35, 10],
+        [-19, -10, 35], [-57, 34, 10], [-57, 34, -35], [57, -34], [57, -10, 35],
+        [-58, 35, 11], [-58, 35, -33], [58, -35], [58, -11, 33], [-59, 12],
+        [59, -12], [-37, 23], [-37, 14], [-23, -14, 37], [-38, 23], [-38, 14],
+        [-23, -14, 38], [39, -37], [39, -38], [37, 38, -39], [-61, 14], [-61, -39],
+        [61, -14, 39], [-60, 37, 13], [60, -37], [60, -13], [-62, 38, 15],
+        [62, -38], [62, -15], [-18, 9], [-19, 10], [-20, 11], [-21, 7], [-16, 4],
+        [-17, 5], [-22, 1, 2], [-23, 14, 14]]]
+
+    # Crap
+    assert unfolder.dynamic_constraints == expected
+
+    # L : 12 + shift_step (47)
+    assert unfolder.invariant_constraints == [[[12]], [[59]]]
+
+    # No variant constraint (not shifted in second part of init_forward_unfolding)
+    assert unfolder.variant_constraints == []
+
+    # "C and K" = _lit47 = 47
+    # Shift: [[3, -47], [11, -47], [-3, -11, 47], [47]] with shift_step (47)
+    assert unfolder.final_constraints == [[50, -94], [58, -94], [-50, -58, 94], [94]]
+
+
+def test_shift(feed_mclanalyser, textual_properties):
+    """Test shift of constraints during the solutions search"""
+
+    mcla = feed_mclanalyser
+    unfolder = mcla.unfolder # shortcut
+
+    start, invariant, final = textual_properties[0]
+
+    query = MCLSimpleQuery(start, invariant, final)
+    unfolder.init_with_query(query)
+
+    init_forward_unfolding_part_1(mcla.unfolder)
+    init_forward_unfolding_part_2(unfolder)
+
+    unfolder.shift()
+
+
+    print(unfolder.dynamic_constraints)
+    print(unfolder.initial_constraints) # Not changed
+    print(unfolder.invariant_constraints)
+    print(unfolder.variant_constraints) # No data for this test
+    print(unfolder.final_constraints)
+
+    expected = [
+        # Initial step
+        [[-24, 16], [-24, 4], [-16, -4, 24], [-15, 42], [-12, 42], [15, 12, -42],
+        [-42, 41], [-11, 41], [42, 11, -41], [2, -40], [41, -40], [-2, -41, 40],
+        [40, -36], [-13, -36], [-40, 13, 36], [1, -25], [36, -25], [-1, -36, 25],
+        [-43, 22], [-43, 25], [-22, -25, 43], [-47, 24, 1], [-47, 24, -43],
+        [47, -24], [47, -1, 43], [5, -44], [6, -44], [-5, -6, 44], [-45, 17],
+        [-45, 44], [-17, -44, 45], [1, -27], [41, -27], [-1, -41, 27], [27, -26],
+        [-13, -26], [-27, 13, 26], [2, -46], [26, -46], [-2, -26, 46], [-30, 22],
+        [-30, 46], [-22, -46, 30], [-48, 45, 2], [-48, 45, -30], [48, -45],
+        [48, -2, 30], [31, -30], [31, -43], [30, 43, -31], [-49, 31, 3], [49, -31],
+        [49, -3], [-50, 4], [-50, -24], [50, -4, 24], [-51, 5], [-51, -45],
+        [51, -5, 45], [-52, 6], [52, -6], [-32, 21], [-32, 7], [-21, -7, 32],
+        [-53, 7], [-53, -32], [53, -7, 32], [-54, 32, 8], [54, -32], [54, -8],
+        [-33, 20], [-33, 11], [-20, -11, 33], [-34, 18], [-34, 9], [-18, -9, 34],
+        [-55, 33, 9], [-55, 33, -34], [55, -33], [55, -9, 34], [-35, 19], [-35, 10],
+        [-19, -10, 35], [-56, 34, 10], [-56, 34, -35], [56, -34], [56, -10, 35],
+        [-57, 35, 11], [-57, 35, -33], [57, -35], [57, -11, 33], [-58, 12],
+        [58, -12], [-37, 23], [-37, 14], [-23, -14, 37], [-38, 23], [-38, 14],
+        [-23, -14, 38], [39, -37], [39, -38], [37, 38, -39], [-60, 14], [-60, -39],
+        [60, -14, 39], [-59, 37, 13], [59, -37], [59, -13], [-61, 38, 15], [61, -38],
+        [61, -15], [-18, 9], [-19, 10], [-20, 11], [-21, 7], [-16, 4], [-17, 5],
+        [-22, 1, 2], [-23, 14, 14]],
+        # Shifted step
+        [[-70, 62],
+        [-70, 50], [-62, -50, 70], [-61, 88], [-58, 88], [61, 58, -88], [-88, 87],
+        [-57, 87], [88, 57, -87], [48, -86], [87, -86], [-48, -87, 86], [86, -82],
+        [-59, -82], [-86, 59, 82], [47, -71], [82, -71], [-47, -82, 71], [-89, 68],
+        [-89, 71], [-68, -71, 89], [-93, 70, 47], [-93, 70, -89], [93, -70],
+        [93, -47, 89], [51, -90], [52, -90], [-51, -52, 90], [-91, 63], [-91, 90],
+        [-63, -90, 91], [47, -73], [87, -73], [-47, -87, 73], [73, -72],
+        [-59, -72], [-73, 59, 72], [48, -92], [72, -92], [-48, -72, 92], [-76, 68],
+        [-76, 92], [-68, -92, 76], [-94, 91, 48], [-94, 91, -76], [94, -91],
+        [94, -48, 76], [77, -76], [77, -89], [76, 89, -77], [-95, 77, 49], [95, -77],
+        [95, -49], [-96, 50], [-96, -70], [96, -50, 70], [-97, 51], [-97, -91],
+        [97, -51, 91], [-98, 52], [98, -52], [-78, 67], [-78, 53], [-67, -53, 78],
+        [-99, 53], [-99, -78], [99, -53, 78], [-100, 78, 54], [100, -78], [100, -54],
+        [-79, 66], [-79, 57], [-66, -57, 79], [-80, 64], [-80, 55], [-64, -55, 80],
+        [-101, 79, 55], [-101, 79, -80], [101, -79], [101, -55, 80], [-81, 65],
+        [-81, 56], [-65, -56, 81], [-102, 80, 56], [-102, 80, -81], [102, -80],
+        [102, -56, 81], [-103, 81, 57], [-103, 81, -79], [103, -81], [103, -57, 79],
+        [-104, 58], [104, -58], [-83, 69], [-83, 60], [-69, -60, 83], [-84, 69],
+        [-84, 60], [-69, -60, 84], [85, -83], [85, -84], [83, 84, -85], [-106, 60],
+        [-106, -85], [106, -60, 85], [-105, 83, 59], [105, -83], [105, -59],
+        [-107, 84, 61], [107, -84], [107, -61], [-64, 55], [-65, 56], [-66, 57],
+        [-67, 53], [-62, 50], [-63, 51], [-68, 47, 48], [-69, 60, 60]]]
+
+    assert unfolder.dynamic_constraints == expected
+
+    # L = 12: + 46 + 46 => 104
+    assert unfolder.invariant_constraints == [[[12]], [[58]], [[104]]]
+
+    # C = 3: + 46 + 46 =>
+    assert unfolder.final_constraints == [[95]]
+
+
 if __name__ == "__main__":
     unittest.main()
 
-- 
GitLab