Browse Source

Re-implement history behavior to fix error in deep history test. Using bitmap operations where possible.

Joeri Exelmans 5 years ago
parent
commit
3090e1255f

+ 1 - 1
src/sccd/action_lang/static/scope.py

@@ -5,6 +5,7 @@ from inspect import signature
 from sccd.action_lang.static.types import *
 from sccd.action_lang.static.exceptions import *
 import itertools
+import termcolor
 
 
 class ScopeError(ModelError):
@@ -22,7 +23,6 @@ class _Variable(ABC):
 
   @property
   def name(self):
-    import termcolor
     return termcolor.colored(self._name, 'yellow')
 
   def __str__(self):

+ 1 - 1
src/sccd/action_lang/static/types.py

@@ -1,6 +1,7 @@
 from abc import *
 from dataclasses import *
 from typing import *
+import termcolor
 
 class SCCDType(ABC):
     @abstractmethod
@@ -8,7 +9,6 @@ class SCCDType(ABC):
         pass
         
     def __str__(self):
-        import termcolor
         return termcolor.colored(self._str(), 'cyan')
         # return self._str()
 

+ 3 - 2
src/sccd/statechart/dynamic/builtin_scope.py

@@ -10,20 +10,21 @@ BuiltIn.declare("log", SCCDFunction([SCCDString]), const=True)
 BuiltIn.declare("int_to_str", SCCDFunction([SCCDInt], SCCDString), const=True)
 
 def load_builtins(memory: MemoryInterface, state):
+  import math
+  import termcolor
+  
   memory.push_frame(BuiltIn)
 
   def in_state(memory: MemoryInterface, state_list: List[str]) -> bool:
     return state.in_state(state_list)
 
   def log10(memory: MemoryInterface, i: int) -> float:
-    import math
     return math.log10(i)
 
   def float_to_int(memory: MemoryInterface, x: float) -> int:
     return int(x)
 
   def log(memory: MemoryInterface, s: str) -> None:
-    import termcolor
     print_debug(termcolor.colored("log: ",'blue')+s)
 
   def int_to_str(memory: MemoryInterface, i: int) -> str:

+ 5 - 5
src/sccd/statechart/dynamic/round.py

@@ -21,7 +21,7 @@ class CandidatesGeneratorCurrentConfigBased(CandidatesGenerator):
         except KeyError:
             candidates = self.cache[key] = [
                 t for s in state.configuration
-                    if (not forbidden_arenas & s.gen.state_id_bitmap)
+                    if (not forbidden_arenas & s.opt.state_id_bitmap)
                     for t in s.transitions
                 ]
             if self.reverse:
@@ -40,9 +40,9 @@ class CandidatesGeneratorEventBased(CandidatesGenerator):
             candidates = self.cache[key]
         except KeyError:
             candidates = self.cache[key] = [
-                t for t in state.model.tree.transition_list
+                t for t in state.statechart.tree.transition_list
                     if (not t.trigger or t.trigger.check(events_bitmap)) # todo: check port?
-                    and (not forbidden_arenas & t.source.gen.state_id_bitmap)
+                    and (not forbidden_arenas & t.source.opt.state_id_bitmap)
                 ]
             if self.reverse:
                 candidates.reverse()
@@ -195,7 +195,7 @@ class SmallStep(Round):
 
             candidates = self.generator.generate(self.state, enabled_events, forbidden_arenas |  extra_forbidden)
 
-            if is_debug():
+            if DEBUG:
                 candidates = list(candidates) # convert generator to list (gotta do this, otherwise the generator will be all used up by our debug printing
                 if candidates:
                     print_debug("")
@@ -214,7 +214,7 @@ class SmallStep(Round):
         t = next(candidates, None)
         timer.stop("get candidate")
         while t:
-            arena = t.gen.arena_bitmap
+            arena = t.opt.arena_bitmap
             if not (arenas & arena):
                 self.state.fire_transition(enabled_events, t)
                 arenas |= arena

+ 47 - 58
src/sccd/statechart/dynamic/statechart_execution.py

@@ -19,16 +19,11 @@ class StatechartExecution:
         self.raise_internal = None
         self.raise_next_bs = None
 
-        # these 2 fields have the same information
-        self.configuration: List[State] = []
+        # set of current states
         self.configuration_bitmap: Bitmap = Bitmap()
 
-        self.eventless_states = 0 # number of states in current configuration that have at least one eventless outgoing transition.
-
-        # mapping from configuration_bitmap (=int) to configuration (=List[State])
-        self.config_mem = {}
         # mapping from history state id to states to enter if history is target of transition
-        self.history_values: Dict[int, List[State]] = {}
+        self.history_values: Dict[int, Bitmap] = {}
 
         # For each AfterTrigger in the statechart tree, we keep an expected 'id' that is
         # a parameter to a future 'after' event. This 'id' is incremented each time a timer
@@ -40,60 +35,66 @@ class StatechartExecution:
 
     # enter default states
     def initialize(self):
-        states = self.statechart.tree.root.target_states(self, True)
-        self.configuration.extend(self.statechart.tree.state_list[id] for id in states.items())
+        states = self.statechart.tree.root.target_states(self)
         self.configuration_bitmap = states
 
         ctx = EvalContext(current_state=self, events=[], memory=self.rhs_memory)
         if self.statechart.datamodel is not None:
             self.statechart.datamodel.exec(self.rhs_memory)
 
-        for state in self.configuration:
-            print_debug(termcolor.colored('  ENTER %s'%state.gen.full_name, 'green'))
-            self.eventless_states += state.gen.has_eventless_transitions
+        for state in self._ids_to_states(states.items()):
+            print_debug(termcolor.colored('  ENTER %s'%state.opt.full_name, 'green'))
             self._perform_actions(ctx, state.enter)
-            self._start_timers(state.gen.after_triggers)
+            self._start_timers(state.opt.after_triggers)
 
         self.rhs_memory.flush_transition()
         self.rhs_memory.flush_round()
         self.gc_memory.flush_round()
 
+    def _ids_to_states(self, id_iter):
+        return (self.statechart.tree.state_list[id] for id in id_iter)
+
     # events: list SORTED by event id
     def fire_transition(self, events: List[Event], t: Transition):
         try:
+            # print("arena is:", t.opt.arena)
             timer.start("transition")
 
             timer.start("exit states")
-            # Exit set is the intersection between self.configuration and t.gen.arena.descendants.
-
-            # The following was found to have better performance than reverse-iterating and filtering self.configuration or t.arena.gen.descendants lists, despite the fact that Bitmap.reverse_items() isn't very efficient.
-            exit_ids = self.configuration_bitmap & t.gen.arena.gen.descendants_bitmap
-            exit_set = (self.statechart.tree.state_list[id] for id in exit_ids.reverse_items())
+            # Sequence of exit states is the intersection between set of current states and the arena's descendants.
+            exit_ids = self.configuration_bitmap & t.opt.arena.opt.descendants_bitmap
+            exit_set = self._ids_to_states(exit_ids.reverse_items())
 
             # Alternative implementation:
-            # if len(self.configuration) < len(t.gen.arena.gen.descendants):
-            #     exit_set = (s for s in self.configuration if s.gen.state_id_bitmap & t.gen.arena.gen.descendants_bitmap)
+            # if len(self.configuration) < len(t.opt.arena.opt.descendants):
+            #     exit_set = (s for s in self.configuration if s.opt.state_id_bitmap & t.opt.arena.opt.descendants_bitmap)
             # else:
-            #     exit_set = (s for s in t.gen.arena.gen.descendants if s.gen.state_id_bitmap & self.configuration_bitmap)
+            #     exit_set = (s for s in t.opt.arena.opt.descendants if s.opt.state_id_bitmap & self.configuration_bitmap)
             timer.stop("exit states")
 
 
             timer.start("enter states")
+            # Sequence of enter states is more complex. As a start, we calculate the enter path:
             # Enter path is the intersection between:
-            #   1) the transitions target + the target's ancestors and
+            #   1) the transitions target and its ancestors, and
             #   2) the arena's descendants
-            enter_path = (t.targets[0].gen.ancestors_bitmap | t.targets[0].gen.state_id_bitmap) & t.gen.arena.gen.descendants_bitmap
+            enter_path = (t.targets[0].opt.state_id_bitmap | t.targets[0].opt.ancestors_bitmap) & t.opt.arena.opt.descendants_bitmap
             # Now, along the enter path, there may be AND-states whose children we don't explicitly enter, but should enter.
-            # That's why we call 'target_states' on every state on the path and join the results.
-            items = enter_path.items()
-            shifted = itertools.chain(enter_path.items(), [-1])
-            next(shifted) # throw away first value
-            pairwise = zip(items, shifted)
-
+            # That's why we call 'additional_target_states' on every state on the path and join the results.
+            # Finally, on the actual target itself, we call 'target_states' and add it to the results as well.
+            enter_path_iter = enter_path.items()
+            state_id = next(enter_path_iter, None)
             enter_ids = Bitmap()
-            for state_id, next_state_id in pairwise:
-                enter_ids |= self.statechart.tree.state_list[state_id].target_states(self, next_state_id == -1)
-            enter_set = (self.statechart.tree.state_list[id] for id in enter_ids.items())
+            while state_id:
+                next_state_id = next(enter_path_iter, None)
+                if next_state_id:
+                    # an intermediate state on the path from arena to target
+                    enter_ids |= self.statechart.tree.state_list[state_id].additional_target_states(self)
+                else:
+                    # the actual target of the transition
+                    enter_ids |= self.statechart.tree.state_list[state_id].target_states(self)
+                state_id = next_state_id
+            enter_set = self._ids_to_states(enter_ids.items())
             timer.stop("enter states")
 
 
@@ -104,51 +105,37 @@ class StatechartExecution:
             timer.start("exit states")
             # exit states...
             for s in exit_set:
+                print_debug(termcolor.colored('  EXIT %s' % s.opt.full_name, 'green'))
                 # remember which state(s) we were in if a history state is present
-                for h in s.gen.history:
-                    if isinstance(h, DeepHistoryState):
-                        f = lambda s0: not s0.gen.descendants_bitmap and s0.gen.state_id_bitmap & s.gen.descendants_bitmap
-                    else:
-                        f = lambda s0: s0.gen.ancestors_bitmap and s0.parent == s
-                    self.history_values[h.gen.state_id] = list(filter(f, self.configuration))
-
-                print_debug(termcolor.colored('  EXIT %s' % s.gen.full_name, 'green'))
-                self.eventless_states -= s.gen.has_eventless_transitions
-                # execute exit action(s)
+                for h, mask in s.opt.history:
+                    self.history_values[h.opt.state_id] = exit_ids & mask
+                    # print(list(self._ids_to_states(self.history_values[h.opt.state_id].items())))
                 self._perform_actions(ctx, s.exit)
-                # self.rhs_memory.pop_local_scope(s.scope)
-                self.configuration_bitmap &= ~s.gen.state_id_bitmap
+                self.configuration_bitmap &= ~s.opt.state_id_bitmap
             timer.stop("exit states")
 
             # execute transition action(s)
-            timer.start("actions")
             self.rhs_memory.push_frame(t.scope) # make room for event parameters on stack
             if t.trigger:
                 t.trigger.copy_params_to_stack(ctx)
             self._perform_actions(ctx, t.actions)
             self.rhs_memory.pop_frame()
-            timer.stop("actions")
 
             timer.start("enter states")
             # enter states...
             for s in enter_set:
-                print_debug(termcolor.colored('  ENTER %s' % s.gen.full_name, 'green'))
-                self.eventless_states += s.gen.has_eventless_transitions
-                self.configuration_bitmap |= s.gen.state_id_bitmap
-                # execute enter action(s)
+                print_debug(termcolor.colored('  ENTER %s' % s.opt.full_name, 'green'))
+                self.configuration_bitmap |= s.opt.state_id_bitmap
                 self._perform_actions(ctx, s.enter)
-                self._start_timers(s.gen.after_triggers)
+                self._start_timers(s.opt.after_triggers)
             timer.stop("enter states")
-            
-            try:
-                self.configuration = self.config_mem[self.configuration_bitmap]
-            except KeyError:
-                self.configuration = self.config_mem[self.configuration_bitmap] = [s for s in self.statechart.tree.state_list if self.configuration_bitmap & s.gen.state_id_bitmap]
 
             self.rhs_memory.flush_transition()
 
             timer.stop("transition")
 
+            # input(">")
+
         except SCCDRuntimeException as e:
             e.args = ("During execution of transition %s:\n" % str(t) +str(e),)
             raise
@@ -177,12 +164,14 @@ class StatechartExecution:
             raise
 
     def check_source(self, t) -> bool:
-        return self.configuration_bitmap & t.source.gen.state_id_bitmap
+        return self.configuration_bitmap & t.source.opt.state_id_bitmap
 
     @staticmethod
     def _perform_actions(ctx: EvalContext, actions: List[Action]):
+        timer.start("actions")
         for a in actions:
             a.exec(ctx)
+        timer.stop("actions")
 
     def _start_timers(self, triggers: List[AfterTrigger]):
         for after in triggers:
@@ -197,7 +186,7 @@ class StatechartExecution:
 
     # Return whether the current configuration includes ALL the states given.
     def in_state(self, state_strings: List[str]) -> bool:
-        state_ids_bitmap = Bitmap.from_list((self.statechart.tree.state_dict[state_string].gen.state_id for state_string in state_strings))
+        state_ids_bitmap = states_to_bitmap((self.statechart.tree.state_dict[state_string] for state_string in state_strings))
         in_state = self.configuration_bitmap.has_all(state_ids_bitmap)
         # if in_state:
         #     print_debug("in state"+str(state_strings))

+ 2 - 2
src/sccd/statechart/dynamic/statechart_instance.py

@@ -37,8 +37,8 @@ class StatechartInstance(Instance):
         reverse = semantics.priority == Priority.SOURCE_CHILD
 
         # 2 transition candidate generation algorithms to choose from!
-        generator = CandidatesGeneratorCurrentConfigBased(reverse)
-        # generator = CandidatesGeneratorEventBased(reverse)
+        # generator = CandidatesGeneratorCurrentConfigBased(reverse)
+        generator = CandidatesGeneratorEventBased(reverse)
 
         # Big step + combo step maximality semantics
 

+ 1 - 1
src/sccd/statechart/parser/xml.py

@@ -19,6 +19,7 @@ def check_duration_type(type):
 
 
 def create_statechart_parser(globals, src_file, load_external = True, parse = parse_f) -> Rules:
+  import os
   def parse_statechart(el):
     ext_file = el.get("src")
     if ext_file is None:
@@ -33,7 +34,6 @@ def create_statechart_parser(globals, src_file, load_external = True, parse = pa
     else:
       if not load_external:
         raise SkipFile("Parser configured not to load statecharts from external files.")
-      import os
       ext_file_path = os.path.join(os.path.dirname(src_file), ext_file)
       statechart = parse(ext_file_path, create_statechart_parser(globals, ext_file_path))
 

+ 138 - 157
src/sccd/statechart/static/tree.py

@@ -3,6 +3,7 @@ from typing import *
 from sccd.statechart.static.action import *
 from sccd.util.bitmap import *
 from sccd.util import timer
+from sccd.util.visit_tree import *
 
 @dataclass
 class State:
@@ -20,109 +21,81 @@ class State:
     enter: List[Action] = field(default_factory=list)
     exit: List[Action] = field(default_factory=list)
 
-    gen: Optional['StateOptimization'] = None
+    opt: Optional['StateOptimization'] = None
 
     def __post_init__(self):
         if self.parent is not None:
             self.parent.children.append(self)
 
+    def target_states(self, instance) -> Bitmap:
+        return self.opt.static_ts_bitmap | functools.reduce(lambda x,y: x|y, (s.target_states(instance) for s in self.opt.dynamic_ts), Bitmap())
 
-    def target_states(self, instance, end_of_path) -> Bitmap:
-        if end_of_path:
-            return self.gen.static_ts_bitmap | functools.reduce(lambda x,y: x|y, (s._target_states(instance) for s in self.gen.dynamic_ts), Bitmap())
-        else:
-            return self.gen.state_id_bitmap
-
-    def _target_states(self, instance) -> Bitmap:
-        # targets = [self]
-        targets = self.gen.state_id_bitmap
-        if self.default_state:
-            targets |= self.default_state._target_states(instance)
-        return targets
-
-    def _static_ts(self) -> Tuple[List['State'], List['State']]:
-        if self.default_state:
-            static, dynamic = self.default_state._static_ts()
-            return ([self] + static, dynamic)
-        else:
-            return ([self], [])
+    def additional_target_states(self, instance) -> Bitmap:
+        return self.opt.state_id_bitmap
 
     def __repr__(self):
-        return "State(\"%s\")" % (self.gen.full_name)
+        return "State(\"%s\")" % (self.opt.full_name)
 
 # Generated fields (for optimization) of a state
-@dataclass(frozen=True)
+@dataclass
 class StateOptimization:
-    state_id: int
-    state_id_bitmap: Bitmap
-    full_name: str
-    ancestors: List[State] # order: close to far away, i.e. first element is parent
-    ancestors_bitmap: Bitmap
-    descendants: List[State]  # order: breadth-first
-    descendants_bitmap: Bitmap
-    history: List[State] # subset of children
-
-    static_ts_bitmap: Bitmap # Bitmap of all descendants that are always part of the 'effective targets states'
-    dynamic_ts: List[State] # Subset of descendants containing possible target-states
+    full_name: str = ""
 
-    has_eventless_transitions: bool
-    after_triggers: List['AfterTrigger']
+    state_id: int = -1
+    state_id_bitmap: Bitmap = Bitmap() # bitmap with only state_id-bit set
 
+    ancestors: List[State] = field(default_factory=list) # order: close to far away, i.e. first element is parent
+    ancestors_bitmap: Bitmap = Bitmap()
+    descendants: List[State] = field(default_factory=list)  # order: depth-first
+    descendants_bitmap: Bitmap = Bitmap()
 
-class HistoryState(State):
+    history: List[Tuple[State, Bitmap]] = field(default_factory=list) # subset of children
+    static_ts_bitmap: Bitmap = Bitmap() # Subset of descendants that are always entered when this state is the target of a transition
+    dynamic_ts: List[State] = field(default_factory=list) # Subset of descendants that MAY be entered when this state is the target of a transition, depending on the history values.
 
-    def target_states(self, instance, end_of_path) -> Bitmap:
-        return Bitmap.from_list(s.gen.state_id_bitmap for s in self._target_states(instance))
+    after_triggers: List['AfterTrigger'] = field(default_factory=list)
 
+class HistoryState(State):
+    # Set of states that may be history values.
     @abstractmethod
-    def _target_states(self, instance) -> Bitmap:
+    def history_mask(self) -> Bitmap:
         pass
 
-    def _static_ts(self) -> Tuple[List[State], Bitmap]:
-        return ([], [self])
+    def target_states(self, instance) -> Bitmap:
+        try:
+            return instance.history_values[self.opt.state_id]
+        except KeyError:
+            # Parent's target states, but not the parent itself:
+            return self.parent.target_states(instance) & ~self.parent.opt.state_id_bitmap
+
+    def additional_target_states(self, instance) -> Bitmap:
+        return Bitmap()
 
 class ShallowHistoryState(HistoryState):
 
-    def _target_states(self, instance) -> Bitmap:
-        try:
-            targets = Bitmap()
-            for hv in instance.history_values[self.state_id]:
-                targets |= hv.target_states(instance, True)
-            return targets
-        except KeyError:
-            # TODO: is it correct that in this case, the parent itself is also entered? -> Joeri: Nope!
-            return self.parent._target_states(instance)
+    def history_mask(self) -> Bitmap:
+        # Only direct children of parent:
+        return states_to_bitmap(self.parent.children)
+
+    def __repr__(self):
+        return "ShallowHistoryState(\"%s\")" % (self.opt.full_name)
 
 class DeepHistoryState(HistoryState):
-        
-    def _target_states(self, instance) -> Bitmap:
-        try:
-            return Bitmap.from_list(s.state_id for s in instance.history_values[self.state_id])
-        except KeyError:
-            # TODO: is it correct that in this case, the parent itself is also entered?
-            return self.parent._target_states(instance)
-        
+
+    def history_mask(self) -> Bitmap:
+        # All descendants of parent:
+        return self.parent.opt.descendants_bitmap
+
+    def __repr__(self):
+        return "DeepHistoryState(\"%s\")" % (self.opt.full_name)
+
 class ParallelState(State):
 
-    def target_states(self, instance, end_of_path) -> Bitmap:
-        return self.gen.static_ts_bitmap | Bitmap.from_list(s._target_states(instance) for s in self.gen.dynamic_ts)
-
-    def _target_states(self, instance) -> Bitmap:
-        targets = [self]
-        for c in self.children:
-            if not isinstance(c, HistoryState):
-                targets.extend(c._target_states(instance))
-        return targets
-
-    def _static_ts(self) -> Tuple[List[State], Bitmap]:
-        static = [self]
-        dynamic = []
-        for c in self.children:
-            if not isinstance(c, HistoryState):
-                c_static, c_dynamic = c._static_ts()
-                static.extend(c_static)
-                dynamic.extend(c_dynamic)
-        return static, dynamic
+    def additional_target_states(self, instance) -> Bitmap:
+        return self.target_states(instance)
+
+    def __repr__(self):
+        return "ParallelState(\"%s\")" % (self.opt.full_name)
 
 @dataclass
 class EventDecl:
@@ -198,6 +171,7 @@ class AfterTrigger(Trigger):
     def render(self) -> str:
         return "after("+self.delay.render()+")"
 
+    # Override.
     # An 'after'-event also has 1 parameter, but it is not accessible to the user,
     # hence the override.
     def copy_params_to_stack(self, ctx: EvalContext):
@@ -215,19 +189,22 @@ class Transition:
     actions: List[Action] = field(default_factory=list)
     trigger: Optional[Trigger] = None
 
-    gen: Optional['TransitionOptimization'] = None        
+    opt: Optional['TransitionOptimization'] = None        
                     
     def __repr__(self):
-        return termcolor.colored("%s 🡪 %s" % (self.source.gen.full_name, self.targets[0].gen.full_name), 'green')
+        return termcolor.colored("%s 🡪 %s" % (self.source.opt.full_name, self.targets[0].opt.full_name), 'green')
 
 # Generated fields (for optimization) of a transition
 @dataclass(frozen=True)
 class TransitionOptimization:
-    lca: State
-    lca_bitmap: Bitmap
     arena: State
     arena_bitmap: Bitmap
 
+# Reduce a list of states to a set of states, as a bitmap
+def states_to_bitmap(state_list: List[State]) -> Bitmap:
+    return reduce(lambda x,y: x|y, (s.opt.state_id_bitmap for s in state_list), Bitmap())
+
+
 # @dataclass
 class StateTree:
 
@@ -235,99 +212,103 @@ class StateTree:
     #       except the 'gen' fields. This function will fill in the 'gen' fields.
     def __init__(self, root: State):
         timer.start("optimize tree")
-        self.state_dict = {} # mapping from 'full name' to State
-        self.state_list = [] # depth-first list of states
-        self.transition_list = [] # all transitions in the tree, sorted by source state, depth-first
-        self.after_triggers = []
-        self.stable_bitmap = Bitmap() # bitmap of state IDs of states that are stable. Only used for SYNTACTIC-maximality semantics.
-
-        next_id = 0
-
-        def init_state(state: State, parent_full_name: str, ancestors: List[State], ancestors_bitmap):
-            nonlocal next_id
 
-            state_id = next_id
-            next_id += 1
-            state_id_bitmap = bit(state_id)
+        self.root = root
 
+        self.transition_list = []
+        self.after_triggers = []
+        def init_opt():
+            next_id = 0
+            def f(state: State, _=None):
+                state.opt = StateOptimization()
+
+                nonlocal next_id
+                state.opt.state_id = next_id
+                state.opt.state_id_bitmap = bit(next_id)
+                next_id += 1
+
+                for t in state.transitions:
+                    self.transition_list.append(t)
+                    if t.trigger and isinstance(t.trigger, AfterTrigger):
+                        state.opt.after_triggers.append(t.trigger)
+                        self.after_triggers.append(t.trigger)
+            return f
+
+        def assign_full_name(state: State, parent_full_name: str = ""):
             if state is root:
                 full_name = '/'
             elif state.parent is root:
                 full_name = '/' + state.short_name
             else:
                 full_name = parent_full_name + '/' + state.short_name
-
-            self.state_dict[full_name] = state
+            state.opt.full_name = full_name
+            return full_name
+
+        self.state_dict = {}
+        self.state_list = []
+        self.stable_bitmap = Bitmap()
+        def add_to_list(state: State ,_=None):
+            self.state_dict[state.opt.full_name] = state
             self.state_list.append(state)
+            if state.stable:
+                self.stable_bitmap |= state.opt.state_id_bitmap
+
+        def set_ancestors(state: State, ancestors=[]):
+            state.opt.ancestors = ancestors
+            state.opt.ancestors_bitmap = states_to_bitmap(ancestors)
+            return ancestors + [state]
+
+        def set_descendants(state: State, children_descendants):
+            # flatten list of lists
+            descendants = list(itertools.chain.from_iterable(children_descendants))
+            state.opt.descendants = descendants
+            state.opt.descendants_bitmap = states_to_bitmap(descendants)
+            return [state] + descendants
+
+        def set_static_target_states(state: State, _):
+            if isinstance(state, ParallelState):
+                state.opt.static_ts_bitmap = reduce(lambda x,y: x|y, (s.opt.static_ts_bitmap for s in state.children), state.opt.state_id_bitmap)
+                state.opt.dynamic_ts = list(itertools.chain.from_iterable(c.opt.dynamic_ts for c in state.children if not isinstance(c, HistoryState)))
+            elif isinstance(state, HistoryState):
+                state.opt.static_ts_bitmap = Bitmap()
+                state.opt.dynamic_ts = state.children
+            else: # "regular" state:
+                if state.default_state:
+                    state.opt.static_ts_bitmap = state.opt.state_id_bitmap | state.default_state.opt.static_ts_bitmap
+                    state.opt.dynamic_ts = state.default_state.opt.dynamic_ts
+                else:
+                    state.opt.static_ts_bitmap = state.opt.state_id_bitmap
+                    state.opt.dynamic_ts = []
 
-            descendants = []
-            history = []
-            has_eventless_transitions = False
-            after_triggers = []
-            static_ts, dynamic_ts = state._static_ts()
-
-            for t in state.transitions:
-                self.transition_list.append(t)
-                if t.trigger is None:
-                    has_eventless_transitions = True
-                elif isinstance(t.trigger, AfterTrigger):
-                    after_triggers.append(t.trigger)
-                    self.after_triggers.append(t.trigger)
-
+        def add_history(state: State, _= None):
             for c in state.children:
-                init_state(c, full_name, [state] + ancestors, state_id_bitmap | ancestors_bitmap)
                 if isinstance(c, HistoryState):
-                    history.append(c)
-
-            descendants.extend(state.children)
-            for c in state.children:
-                descendants.extend(c.gen.descendants)
-
-            descendants_bitmap = Bitmap.from_list(s.gen.state_id for s in descendants)
-            static_ts_bitmap = Bitmap.from_list(s.gen.state_id for s in static_ts if s.gen) | state_id_bitmap
-
-            state.gen = StateOptimization(
-                state_id=state_id,
-                state_id_bitmap=state_id_bitmap,
-                full_name=full_name,
-                ancestors=ancestors,
-                ancestors_bitmap=ancestors_bitmap,
-                descendants=descendants,
-                descendants_bitmap=descendants_bitmap,
-                history=history,
-                static_ts_bitmap=static_ts_bitmap,
-                dynamic_ts=dynamic_ts,
-                has_eventless_transitions=has_eventless_transitions,
-                after_triggers=after_triggers)
-
-            if state.stable:
-                self.stable_bitmap |= bit(state_id)
-
+                    state.opt.history.append((c, c.history_mask()))
+
+        visit_tree(root, lambda s: s.children,
+            before_children=[
+                init_opt(),
+                assign_full_name,
+                add_to_list,
+                set_ancestors,
+            ],
+            after_children=[
+                set_descendants,
+                add_history,
+                set_static_target_states,
+            ])
 
-        init_state(root, "", [], Bitmap())
-        self.root = root
 
         for t in self.transition_list:
-            # the least-common ancestor can be computed statically
-            if t.source in t.targets[0].gen.ancestors:
-                lca = t.source
-            else:
-                lca = t.source.parent
-                target = t.targets[0]
-                if t.source.parent != target.parent: # external
-                    for a in t.source.gen.ancestors:
-                        if a in target.gen.ancestors:
-                            lca = a
-                            break
-
+            # intersection between source & target ancestors, last member in depth-first sorted state list.
+            lca_id = (t.source.opt.ancestors_bitmap & t.targets[0].opt.ancestors_bitmap).highest_bit()
+            lca = self.state_list[lca_id]
             arena = lca
-            while isinstance(arena, ParallelState):
+            while isinstance(arena, (ParallelState, HistoryState)):
                 arena = arena.parent
 
-            t.gen = TransitionOptimization(
-                lca=lca,
-                lca_bitmap=lca.gen.descendants_bitmap | lca.gen.state_id_bitmap,
+            t.opt = TransitionOptimization(
                 arena=arena,
-                arena_bitmap=arena.gen.descendants_bitmap | arena.gen.state_id_bitmap)
+                arena_bitmap=arena.opt.descendants_bitmap | arena.opt.state_id_bitmap)
 
-        timer.stop("optimize tree")
+        timer.stop("optimize tree")

+ 6 - 7
src/sccd/util/debug.py

@@ -5,10 +5,9 @@ try:
 except KeyError:
   DEBUG = False
   
-def print_debug(msg):
-    if DEBUG:
-        print(msg)
-
-
-def is_debug() -> bool:
-  return DEBUG
+if DEBUG:
+  def print_debug(msg):
+    print(msg)
+else:
+  def print_debug(msg):
+    pass

+ 3 - 4
src/sccd/util/timer.py

@@ -9,9 +9,8 @@ if TIMINGS:
   import time
   import atexit
 
-  timings = {}
-
   timers = {}
+  timings = {}
 
   def start(what):
     timers[what] = time.time()
@@ -19,17 +18,17 @@ if TIMINGS:
   def stop(what):
     end = time.time()
     begin = timers[what]
-    del timers[what]
     duration = end - begin
     old_val = timings.setdefault(what, 0)
     timings[what] = old_val + duration
 
   def _print_stats():
-      print("timings:")
+      print("\ntimings:")
       for key,val in timings.items():
         print("  %s: %f ms" % (key,val*1000))
 
   atexit.register(_print_stats)
+
 else:
   def start(what):
     pass

+ 48 - 0
src/sccd/util/visit_tree.py

@@ -0,0 +1,48 @@
+import functools
+import itertools
+
+# A generic depth-first tree-visit function that can let multiple visitor functions do their thing in only a single pass.
+# It accepts 2 lists of visitor functions:
+# 'before_children' is a list of callbacks that will be called with 1 or 2 parameters (and therefore the 2nd parameter of the callback should have a default value): 1) the current element and 2) the value of the callback returned by the parent element if the current element is not the root element.
+# 'after_children' is a list of callbacks that will be called with 2 parameters: 2) the current element and 2) An empty list for all the leaf elements or a list with the responses of the children of that element for the callback.
+def visit_tree(node, get_children, before_children=[], after_children=[], parent_values=None):
+    # Most parameters unchanged for recursive calls
+    visit = functools.partial(visit_tree, get_children=get_children, before_children=before_children, after_children=after_children)
+
+    if parent_values is None:
+        parent_values = [f(node) for f in before_children]
+    else:
+        parent_values = [f(node, p) for f,p in zip(before_children, parent_values)]
+
+
+    child_responses = (visit(node=c, parent_values=parent_values) for c in get_children(node))
+    # child_responses is a list of len(children)-many lists C, where for every child of node, C is the 'to_parent' value returned by every child
+
+    to_parent = [f(node, [cs[i] for cs in child_responses]) for i,f in enumerate(after_children)]
+    # 'to_parent' is the mapping from our after_children-functions to those functions called on the responses we got from our children
+
+    return to_parent
+
+
+# def visit_tree2(node, get_children, before_child=[], after_child=[], parent_values=None):
+#     # Most parameters unchanged for recursive calls
+#     visit = functools.partial(visit_tree2, get_children=get_children, before_child=before_child, after_child=after_child)
+
+#     if parent_values is None:
+#         parent_values = [before_f(node) for before_f in before_child]
+#     else:
+#         parent_values = [before_f(node, p) for before_f,p in zip(before_child, parent_values)]
+
+#     children_responses = [visit(node=c, parent_values=parent_values) for c in get_children(node)]
+#     children_responses2 = [list(itertools.chain.from_iterable(cr[i] for cr in children_responses)) for i,after_f in enumerate(after_child)]
+
+#     to_parent = []
+#     for after_f, res in zip(after_child, children_responses2):
+#         ls = []
+#         after_f(node, res, ls)
+#         to_parent.append(ls)
+
+#     print("visit_tree2, node=",node,"children_responses:",children_responses2)
+#     print("to parent:", to_parent)
+
+#     return to_parent

+ 7 - 7
test/render.py

@@ -71,7 +71,7 @@ if __name__ == '__main__':
         w = IndentingWriter(f)
 
         def name_to_label(state):
-          label = state.gen.full_name.split('/')[-1]
+          label = state.opt.full_name.split('/')[-1]
           if state.stable:
             label += " ✓"
           return label if len(label) else "root"
@@ -99,7 +99,7 @@ if __name__ == '__main__':
 
         def write_state(s, hide=False):
           if not hide:
-            w.write(name_to_name(s.gen.full_name))
+            w.write(name_to_name(s.opt.full_name))
             w.extendWrite(' [label="')
             w.extendWrite(name_to_label(s))
             w.extendWrite('"')
@@ -124,8 +124,8 @@ if __name__ == '__main__':
               w.extendWrite(' {')
               w.indent()
             if s.default_state:
-              w.write(name_to_name(s.gen.full_name)+'_initial [type=initial],')
-              transitions.append(PseudoTransition(source=PseudoState(s.gen.full_name+'/initial'), targets=[s.default_state]))
+              w.write(name_to_name(s.opt.full_name)+'_initial [type=initial],')
+              transitions.append(PseudoTransition(source=PseudoState(s.opt.full_name+'/initial'), targets=[s.default_state]))
             s.children.reverse() # this appears to put orthogonal components in the right order :)
             for i, c in enumerate(s.children):
               write_state(c)
@@ -148,17 +148,17 @@ if __name__ == '__main__':
             label += ' '.join(a.render() for a in t.actions)
 
           if len(t.targets) == 1:
-            w.write(name_to_name(t.source.gen.full_name) + ' -> ' + name_to_name(t.targets[0].gen.full_name))
+            w.write(name_to_name(t.source.opt.full_name) + ' -> ' + name_to_name(t.targets[0].opt.full_name))
             if label:
               w.extendWrite(': '+label)
             w.extendWrite(';')
           else:
-            w.write(name_to_name(t.source.gen.full_name) + ' -> ' + ']split'+str(ctr))
+            w.write(name_to_name(t.source.opt.full_name) + ' -> ' + ']split'+str(ctr))
             if label:
                 w.extendWrite(': '+label)
             w.extendWrite(';')
             for tt in t.targets:
-              w.write(']split'+str(ctr) + ' -> ' + name_to_name(tt.gen.full_name))
+              w.write(']split'+str(ctr) + ' -> ' + name_to_name(tt.opt.full_name))
               w.extendWrite(';')
             ctr += 1
 

+ 97 - 0
test/test_files/features/history/test_history.svg

@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: state transitions Pages: 1 -->
+<svg width="205pt" height="356pt"
+ viewBox="0.00 0.00 204.50 356.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 352)">
+<title>state transitions</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-352 200.5,-352 200.5,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster__composite_1</title>
+<path fill="none" stroke="#000000" stroke-width="2" d="M44.5,-8C44.5,-8 176.5,-8 176.5,-8 182.5,-8 188.5,-14 188.5,-20 188.5,-20 188.5,-262 188.5,-262 188.5,-268 182.5,-274 176.5,-274 176.5,-274 44.5,-274 44.5,-274 38.5,-274 32.5,-268 32.5,-262 32.5,-262 32.5,-20 32.5,-20 32.5,-14 38.5,-8 44.5,-8"/>
+<text text-anchor="start" x="76.4914" y="-255.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">composite_1</text>
+</g>
+<!-- __initial -->
+<g id="node1" class="node">
+<title>__initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="173.5" cy="-325" rx="5.5" ry="5.5"/>
+</g>
+<!-- _composite_1 -->
+<!-- __initial&#45;&gt;_composite_1 -->
+<g id="edge1" class="edge">
+<title>__initial&#45;&gt;_composite_1</title>
+<path fill="none" stroke="#000000" d="M172.5826,-319.5468C171.3707,-312.3426 169.0948,-298.8137 166.5903,-283.9259"/>
+<polygon fill="#000000" stroke="#000000" points="170.0309,-283.2799 164.9204,-273.9991 163.1279,-284.4412 170.0309,-283.2799"/>
+<text text-anchor="middle" x="168.8895" y="-285" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _state_3 -->
+<g id="node2" class="node">
+<title>_state_3</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="139,-348 0,-348 0,-302 139,-302 139,-348"/>
+<text text-anchor="start" x="50.3236" y="-331.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">state_3</text>
+<text text-anchor="start" x="6.1612" y="-311.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_state_3</text>
+<polygon fill="#000000" stroke="#000000" points=".5,-325 .5,-325 139.5,-325 139.5,-325 .5,-325"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M13,-303C13,-303 126,-303 126,-303 132,-303 138,-309 138,-315 138,-315 138,-335 138,-335 138,-341 132,-347 126,-347 126,-347 13,-347 13,-347 7,-347 1,-341 1,-335 1,-335 1,-315 1,-315 1,-309 7,-303 13,-303"/>
+</g>
+<!-- _composite_1_composite_history -->
+<g id="node7" class="node">
+<title>_composite_1_composite_history</title>
+<ellipse fill="transparent" stroke="#000000" stroke-width="2" cx="61.5" cy="-218" rx="18" ry="18"/>
+<text text-anchor="middle" x="61.5" y="-214.4" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">H</text>
+</g>
+<!-- _state_3&#45;&gt;_composite_1_composite_history -->
+<g id="edge2" class="edge">
+<title>_state_3&#45;&gt;_composite_1_composite_history</title>
+<path fill="none" stroke="#000000" d="M67.7641,-301.7826C66.5463,-285.4946 64.9111,-263.623 63.6167,-246.311"/>
+<polygon fill="#000000" stroke="#000000" points="67.0976,-245.9229 62.8616,-236.2117 60.117,-246.4449 67.0976,-245.9229"/>
+<text text-anchor="middle" x="68.8895" y="-285" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _composite_1&#45;&gt;_state_3 -->
+<g id="edge5" class="edge">
+<title>_composite_1&#45;&gt;_state_3</title>
+<path fill="none" stroke="#000000" d="M127.5,-274C120.0845,-280.7079 114.6559,-276.2547 106.472,-282 101.5164,-285.479 96.808,-289.7681 92.5141,-294.2772"/>
+<polygon fill="#000000" stroke="#000000" points="89.7135,-292.1607 85.7086,-301.9695 94.9562,-296.799 89.7135,-292.1607"/>
+<text text-anchor="start" x="105.5" y="-285" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_state_3 &#160;&#160;</text>
+</g>
+<!-- _composite_1_initial -->
+<g id="node4" class="node">
+<title>_composite_1_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="110.5" cy="-218" rx="5.5" ry="5.5"/>
+</g>
+<!-- _composite_1_state_1 -->
+<g id="node6" class="node">
+<title>_composite_1_state_1</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="180,-154 41,-154 41,-108 180,-108 180,-154"/>
+<text text-anchor="start" x="91.3236" y="-137.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">state_1</text>
+<text text-anchor="start" x="47.1612" y="-117.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_state_1</text>
+<polygon fill="#000000" stroke="#000000" points="41.5,-131 41.5,-131 180.5,-131 180.5,-131 41.5,-131"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M54,-109C54,-109 167,-109 167,-109 173,-109 179,-115 179,-121 179,-121 179,-141 179,-141 179,-147 173,-153 167,-153 167,-153 54,-153 54,-153 48,-153 42,-147 42,-141 42,-141 42,-121 42,-121 42,-115 48,-109 54,-109"/>
+</g>
+<!-- _composite_1_initial&#45;&gt;_composite_1_state_1 -->
+<g id="edge3" class="edge">
+<title>_composite_1_initial&#45;&gt;_composite_1_state_1</title>
+<path fill="none" stroke="#000000" d="M110.5,-212.2917C110.5,-202.6218 110.5,-182.3234 110.5,-164.4303"/>
+<polygon fill="#000000" stroke="#000000" points="114.0001,-164.2715 110.5,-154.2715 107.0001,-164.2716 114.0001,-164.2715"/>
+<text text-anchor="middle" x="111.8895" y="-174" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _composite_1_state_2 -->
+<g id="node5" class="node">
+<title>_composite_1_state_2</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="180,-62 41,-62 41,-16 180,-16 180,-62"/>
+<text text-anchor="start" x="91.3236" y="-45.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">state_2</text>
+<text text-anchor="start" x="47.1612" y="-25.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">onentry/ ^out.in_state_2</text>
+<polygon fill="#000000" stroke="#000000" points="41.5,-39 41.5,-39 180.5,-39 180.5,-39 41.5,-39"/>
+<path fill="none" stroke="#000000" stroke-width="2" d="M54,-17C54,-17 167,-17 167,-17 173,-17 179,-23 179,-29 179,-29 179,-49 179,-49 179,-55 173,-61 167,-61 167,-61 54,-61 54,-61 48,-61 42,-55 42,-49 42,-49 42,-29 42,-29 42,-23 48,-17 54,-17"/>
+</g>
+<!-- _composite_1_state_1&#45;&gt;_composite_1_state_2 -->
+<g id="edge4" class="edge">
+<title>_composite_1_state_1&#45;&gt;_composite_1_state_2</title>
+<path fill="none" stroke="#000000" d="M110.5,-107.7845C110.5,-97.1067 110.5,-84.2376 110.5,-72.5333"/>
+<polygon fill="#000000" stroke="#000000" points="114.0001,-72.208 110.5,-62.2081 107.0001,-72.2081 114.0001,-72.208"/>
+<text text-anchor="start" x="110.5" y="-82" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_state_2 &#160;&#160;</text>
+</g>
+</g>
+</svg>

+ 62 - 0
test/test_files/features/history/test_history.xml

@@ -0,0 +1,62 @@
+<test>
+  <!-- based on test originally written by Glenn De Jonghe -->
+  <statechart>
+    <inport name="in">
+      <event name="to_state_2"/>
+      <event name="to_state_3"/>
+    </inport>
+
+    <outport name="out">
+      <event name="in_state_1"/>
+      <event name="in_state_2"/>
+      <event name="in_state_3"/>
+    </outport>
+
+    <root initial="composite_1">
+      <state id="composite_1" initial="state_1">
+        <state id="state_1">
+          <onentry>
+            <raise event="in_state_1"/>
+          </onentry>
+          <transition event="to_state_2" target="../state_2"/>
+        </state>
+        <state id="state_2">
+          <onentry>
+            <raise event="in_state_2"/>
+          </onentry>
+        </state>
+        <history id="composite_history">
+          <!-- <transition target="../state_1"/> -->
+        </history>
+        <transition event="to_state_3" target="../state_3"/>
+      </state>
+      <state id="state_3">
+        <onentry>
+          <raise event="in_state_3"/>
+        </onentry>
+        <transition target="/composite_1/composite_history"/>
+      </state>
+    </root>
+  </statechart>
+
+  <input>
+    <event port="in" name="to_state_2" time="0 d"/>
+    <event port="in" name="to_state_3" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <!-- initialization -->
+      <event port="out" name="in_state_1"/>
+    </big_step>
+    <big_step>
+      <!-- big step with input 'to_state_2' -->
+      <event port="out" name="in_state_2"/>
+    </big_step>
+    <big_step>
+      <!-- big step with input 'to_state_3' -->
+      <event port="out" name="in_state_3"/>
+      <event port="out" name="in_state_2"/>
+    </big_step>
+  </output>
+</test>

+ 253 - 0
test/test_files/features/history/test_history_deep.svg

@@ -0,0 +1,253 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<!-- Generated by graphviz version 2.40.1 (20161225.0304)
+ -->
+<!-- Title: state transitions Pages: 1 -->
+<svg width="730pt" height="1600pt"
+ viewBox="0.00 0.00 730.00 1599.97" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1595.9656)">
+<title>state transitions</title>
+<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1595.9656 726,-1595.9656 726,4 -4,4"/>
+<g id="clust1" class="cluster">
+<title>cluster__parallel</title>
+<path fill="none" stroke="#000000" stroke-width="2" d="M20,-8C20,-8 702,-8 702,-8 708,-8 714,-14 714,-20 714,-20 714,-1540.9656 714,-1540.9656 714,-1546.9656 708,-1552.9656 702,-1552.9656 702,-1552.9656 20,-1552.9656 20,-1552.9656 14,-1552.9656 8,-1546.9656 8,-1540.9656 8,-1540.9656 8,-20 8,-20 8,-14 14,-8 20,-8"/>
+<text text-anchor="start" x="341.6668" y="-1534.1656" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">parallel</text>
+</g>
+<g id="clust2" class="cluster">
+<title>cluster__parallel_orthogonal_tester</title>
+<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="300,-740 300,-1502.4656 706,-1502.4656 706,-740 300,-740"/>
+<text text-anchor="start" x="456.8176" y="-1483.6656" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">orthogonal_tester</text>
+</g>
+<g id="clust3" class="cluster">
+<title>cluster__parallel_orthogonal</title>
+<polygon fill="none" stroke="#000000" stroke-dasharray="5,2" points="24,-16 24,-1514.9656 292,-1514.9656 292,-16 24,-16"/>
+<text text-anchor="start" x="129.656" y="-1496.1656" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">orthogonal</text>
+</g>
+<g id="clust4" class="cluster">
+<title>cluster__parallel_orthogonal_wrapper</title>
+<path fill="none" stroke="#000000" stroke-width="2" d="M44,-24C44,-24 272,-24 272,-24 278,-24 284,-30 284,-36 284,-36 284,-1346.9656 284,-1346.9656 284,-1352.9656 278,-1358.9656 272,-1358.9656 272,-1358.9656 44,-1358.9656 44,-1358.9656 38,-1358.9656 32,-1352.9656 32,-1346.9656 32,-1346.9656 32,-36 32,-36 32,-30 38,-24 44,-24"/>
+<text text-anchor="start" x="136.8322" y="-1340.1656" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">wrapper</text>
+</g>
+<g id="clust5" class="cluster">
+<title>cluster__parallel_orthogonal_wrapper_state_2</title>
+<path fill="none" stroke="#000000" stroke-width="2" d="M194,-32C194,-32 264,-32 264,-32 270,-32 276,-38 276,-44 276,-44 276,-797.5 276,-797.5 276,-803.5 270,-809.5 264,-809.5 264,-809.5 194,-809.5 194,-809.5 188,-809.5 182,-803.5 182,-797.5 182,-797.5 182,-44 182,-44 182,-38 188,-32 194,-32"/>
+<text text-anchor="start" x="209.8236" y="-790.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">state_2</text>
+</g>
+<g id="clust6" class="cluster">
+<title>cluster__parallel_orthogonal_wrapper_state_1</title>
+<path fill="none" stroke="#000000" stroke-width="2" d="M52,-553C52,-553 162,-553 162,-553 168,-553 174,-559 174,-565 174,-565 174,-1071.5 174,-1071.5 174,-1077.5 168,-1083.5 162,-1083.5 162,-1083.5 52,-1083.5 52,-1083.5 46,-1083.5 40,-1077.5 40,-1071.5 40,-1071.5 40,-565 40,-565 40,-559 46,-553 52,-553"/>
+<text text-anchor="start" x="87.8236" y="-1064.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">state_1</text>
+</g>
+<!-- __initial -->
+<g id="node1" class="node">
+<title>__initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="16" cy="-1586.4656" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel -->
+<!-- __initial&#45;&gt;_parallel -->
+<g id="edge1" class="edge">
+<title>__initial&#45;&gt;_parallel</title>
+<path fill="none" stroke="#000000" d="M16,-1580.6559C16,-1576.58 16,-1570.5868 16,-1563.4116"/>
+<polygon fill="#000000" stroke="#000000" points="19.5001,-1562.9638 16,-1552.9638 12.5001,-1562.9638 19.5001,-1562.9638"/>
+<text text-anchor="middle" x="17.3895" y="-1563.9656" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_tester -->
+<!-- _parallel_orthogonal_tester_initial -->
+<g id="node4" class="node">
+<title>_parallel_orthogonal_tester_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="336" cy="-1458.9656" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel_orthogonal_tester_start -->
+<g id="node9" class="node">
+<title>_parallel_orthogonal_tester_start</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="364,-1320.2328 308,-1320.2328 308,-1284.2328 364,-1284.2328 364,-1320.2328"/>
+<text text-anchor="start" x="324.3324" y="-1298.4328" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">start</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M320.3333,-1285.2328C320.3333,-1285.2328 351.6667,-1285.2328 351.6667,-1285.2328 357.3333,-1285.2328 363,-1290.8995 363,-1296.5661 363,-1296.5661 363,-1307.8995 363,-1307.8995 363,-1313.5661 357.3333,-1319.2328 351.6667,-1319.2328 351.6667,-1319.2328 320.3333,-1319.2328 320.3333,-1319.2328 314.6667,-1319.2328 309,-1313.5661 309,-1307.8995 309,-1307.8995 309,-1296.5661 309,-1296.5661 309,-1290.8995 314.6667,-1285.2328 320.3333,-1285.2328"/>
+</g>
+<!-- _parallel_orthogonal_tester_initial&#45;&gt;_parallel_orthogonal_tester_start -->
+<g id="edge2" class="edge">
+<title>_parallel_orthogonal_tester_initial&#45;&gt;_parallel_orthogonal_tester_start</title>
+<path fill="none" stroke="#000000" d="M336,-1453.4315C336,-1446.4476 336,-1434.0663 336,-1423.4656 336,-1423.4656 336,-1423.4656 336,-1376.4656 336,-1361.3136 336,-1344.4219 336,-1330.5842"/>
+<polygon fill="#000000" stroke="#000000" points="339.5001,-1330.2331 336,-1320.2331 332.5001,-1330.2331 339.5001,-1330.2331"/>
+<text text-anchor="middle" x="337.3895" y="-1396.9656" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_tester_end -->
+<g id="node5" class="node">
+<title>_parallel_orthogonal_tester_end</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="364,-784 308,-784 308,-748 364,-748 364,-784"/>
+<text text-anchor="start" x="325.9938" y="-762.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">end</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M320.3333,-749C320.3333,-749 351.6667,-749 351.6667,-749 357.3333,-749 363,-754.6667 363,-760.3333 363,-760.3333 363,-771.6667 363,-771.6667 363,-777.3333 357.3333,-783 351.6667,-783 351.6667,-783 320.3333,-783 320.3333,-783 314.6667,-783 309,-777.3333 309,-771.6667 309,-771.6667 309,-760.3333 309,-760.3333 309,-754.6667 314.6667,-749 320.3333,-749"/>
+</g>
+<!-- _parallel_orthogonal_tester_step3 -->
+<g id="node6" class="node">
+<title>_parallel_orthogonal_tester_step3</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="364,-927.5 308,-927.5 308,-891.5 364,-891.5 364,-927.5"/>
+<text text-anchor="start" x="321.3264" y="-905.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">step3</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M320.3333,-892.5C320.3333,-892.5 351.6667,-892.5 351.6667,-892.5 357.3333,-892.5 363,-898.1667 363,-903.8333 363,-903.8333 363,-915.1667 363,-915.1667 363,-920.8333 357.3333,-926.5 351.6667,-926.5 351.6667,-926.5 320.3333,-926.5 320.3333,-926.5 314.6667,-926.5 309,-920.8333 309,-915.1667 309,-915.1667 309,-903.8333 309,-903.8333 309,-898.1667 314.6667,-892.5 320.3333,-892.5"/>
+</g>
+<!-- _parallel_orthogonal_tester_step3&#45;&gt;_parallel_orthogonal_tester_end -->
+<g id="edge3" class="edge">
+<title>_parallel_orthogonal_tester_step3&#45;&gt;_parallel_orthogonal_tester_end</title>
+<path fill="none" stroke="#000000" d="M336,-891.4402C336,-885.8497 336,-879.6701 336,-874 336,-874 336,-874 336,-827 336,-816.3104 336,-804.5672 336,-794.263"/>
+<polygon fill="#000000" stroke="#000000" points="339.5001,-794.1503 336,-784.1503 332.5001,-794.1504 339.5001,-794.1503"/>
+<text text-anchor="start" x="336" y="-847.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">[INSTATE([&quot;/parallel/orthogonal/wrapper/state_2/inner_4&quot;])]^out.check3 &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_tester_step2 -->
+<g id="node7" class="node">
+<title>_parallel_orthogonal_tester_step2</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="364,-1045.5 308,-1045.5 308,-1009.5 364,-1009.5 364,-1045.5"/>
+<text text-anchor="start" x="321.3264" y="-1023.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">step2</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M320.3333,-1010.5C320.3333,-1010.5 351.6667,-1010.5 351.6667,-1010.5 357.3333,-1010.5 363,-1016.1667 363,-1021.8333 363,-1021.8333 363,-1033.1667 363,-1033.1667 363,-1038.8333 357.3333,-1044.5 351.6667,-1044.5 351.6667,-1044.5 320.3333,-1044.5 320.3333,-1044.5 314.6667,-1044.5 309,-1038.8333 309,-1033.1667 309,-1033.1667 309,-1021.8333 309,-1021.8333 309,-1016.1667 314.6667,-1010.5 320.3333,-1010.5"/>
+</g>
+<!-- _parallel_orthogonal_tester_step2&#45;&gt;_parallel_orthogonal_tester_step3 -->
+<g id="edge4" class="edge">
+<title>_parallel_orthogonal_tester_step2&#45;&gt;_parallel_orthogonal_tester_step3</title>
+<path fill="none" stroke="#000000" d="M336,-1009.4402C336,-1003.8497 336,-997.6701 336,-992 336,-992 336,-992 336,-945 336,-942.6079 336,-940.1252 336,-937.6342"/>
+<polygon fill="#000000" stroke="#000000" points="339.5001,-937.5597 336,-927.5598 332.5001,-937.5598 339.5001,-937.5597"/>
+<text text-anchor="start" x="336" y="-965.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">[INSTATE([&quot;/parallel/orthogonal/outer&quot;])]^out.check2 ^to_history &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_tester_step1 -->
+<g id="node8" class="node">
+<title>_parallel_orthogonal_tester_step1</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="364,-1201.5 308,-1201.5 308,-1165.5 364,-1165.5 364,-1201.5"/>
+<text text-anchor="start" x="321.3264" y="-1179.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">step1</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M320.3333,-1166.5C320.3333,-1166.5 351.6667,-1166.5 351.6667,-1166.5 357.3333,-1166.5 363,-1172.1667 363,-1177.8333 363,-1177.8333 363,-1189.1667 363,-1189.1667 363,-1194.8333 357.3333,-1200.5 351.6667,-1200.5 351.6667,-1200.5 320.3333,-1200.5 320.3333,-1200.5 314.6667,-1200.5 309,-1194.8333 309,-1189.1667 309,-1189.1667 309,-1177.8333 309,-1177.8333 309,-1172.1667 314.6667,-1166.5 320.3333,-1166.5"/>
+</g>
+<!-- _parallel_orthogonal_tester_step1&#45;&gt;_parallel_orthogonal_tester_step2 -->
+<g id="edge5" class="edge">
+<title>_parallel_orthogonal_tester_step1&#45;&gt;_parallel_orthogonal_tester_step2</title>
+<path fill="none" stroke="#000000" d="M331.7033,-1165.1741C330.7416,-1159.6833 330,-1153.6255 330,-1148 330,-1148 330,-1148 330,-1101 330,-1086.0487 331.2486,-1069.4431 332.6066,-1055.8038"/>
+<polygon fill="#000000" stroke="#000000" points="336.1156,-1055.9048 333.6974,-1045.5897 329.1552,-1055.1614 336.1156,-1055.9048"/>
+<text text-anchor="start" x="330" y="-1121.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">[INSTATE([&quot;/parallel/orthogonal/wrapper/state_2/inner_4&quot;])]^out.check1 ^to_outer &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_tester_start&#45;&gt;_parallel_orthogonal_tester_step1 -->
+<g id="edge6" class="edge">
+<title>_parallel_orthogonal_tester_start&#45;&gt;_parallel_orthogonal_tester_step1</title>
+<path fill="none" stroke="#000000" d="M336,-1284.1715C336,-1278.3688 336,-1271.913 336,-1266 336,-1266 336,-1266 336,-1219 336,-1216.6079 336,-1214.1252 336,-1211.6342"/>
+<polygon fill="#000000" stroke="#000000" points="339.5001,-1211.5597 336,-1201.5598 332.5001,-1211.5598 339.5001,-1211.5597"/>
+<text text-anchor="start" x="336" y="-1239.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">^to_state_2 ^to_inner_4 &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal -->
+<!-- _parallel_orthogonal_initial -->
+<g id="node11" class="node">
+<title>_parallel_orthogonal_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="247" cy="-1458.9656" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel_orthogonal_wrapper -->
+<!-- _parallel_orthogonal_initial&#45;&gt;_parallel_orthogonal_wrapper -->
+<g id="edge7" class="edge">
+<title>_parallel_orthogonal_initial&#45;&gt;_parallel_orthogonal_wrapper</title>
+<path fill="none" stroke="#000000" d="M247,-1453.4315C247,-1446.4476 247,-1434.0663 247,-1423.4656 247,-1423.4656 247,-1423.4656 247,-1376.4656 247,-1374.0264 246.8734,-1371.5662 246.6379,-1369.0998"/>
+<polygon fill="#000000" stroke="#000000" points="250.0607,-1368.3257 245.1016,-1358.9632 243.1397,-1369.3748 250.0607,-1368.3257"/>
+<text text-anchor="middle" x="248.3895" y="-1396.9656" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_outer -->
+<g id="node12" class="node">
+<title>_parallel_orthogonal_outer</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="139,-1476.9656 83,-1476.9656 83,-1440.9656 139,-1440.9656 139,-1476.9656"/>
+<text text-anchor="start" x="97.329" y="-1455.1656" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">outer</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M95.3333,-1441.9656C95.3333,-1441.9656 126.6667,-1441.9656 126.6667,-1441.9656 132.3333,-1441.9656 138,-1447.6323 138,-1453.2989 138,-1453.2989 138,-1464.6323 138,-1464.6323 138,-1470.2989 132.3333,-1475.9656 126.6667,-1475.9656 126.6667,-1475.9656 95.3333,-1475.9656 95.3333,-1475.9656 89.6667,-1475.9656 84,-1470.2989 84,-1464.6323 84,-1464.6323 84,-1453.2989 84,-1453.2989 84,-1447.6323 89.6667,-1441.9656 95.3333,-1441.9656"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_history -->
+<g id="node15" class="node">
+<title>_parallel_orthogonal_wrapper_history</title>
+<ellipse fill="transparent" stroke="#000000" stroke-width="2" cx="62" cy="-1302.2328" rx="18.9685" ry="18.9685"/>
+<text text-anchor="middle" x="62" y="-1298.6328" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">H*</text>
+</g>
+<!-- _parallel_orthogonal_outer&#45;&gt;_parallel_orthogonal_wrapper_history -->
+<g id="edge8" class="edge">
+<title>_parallel_orthogonal_outer&#45;&gt;_parallel_orthogonal_wrapper_history</title>
+<path fill="none" stroke="#000000" d="M82.7804,-1441.2694C71.8881,-1433.9563 62,-1426.4858 62,-1423.4656 62,-1423.4656 62,-1423.4656 62,-1376.4656 62,-1361.6186 62,-1345.1014 62,-1331.4237"/>
+<polygon fill="#000000" stroke="#000000" points="65.5001,-1331.1495 62,-1321.1495 58.5001,-1331.1496 65.5001,-1331.1495"/>
+<text text-anchor="start" x="62" y="-1396.9656" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_history &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_wrapper&#45;&gt;_parallel_orthogonal_outer -->
+<g id="edge14" class="edge">
+<title>_parallel_orthogonal_wrapper&#45;&gt;_parallel_orthogonal_outer</title>
+<path fill="none" stroke="#000000" d="M183,-1358.9656C173.5087,-1368.654 158,-1362.9028 158,-1376.4656 158,-1423.4656 158,-1423.4656 158,-1423.4656 158,-1431.4465 153.6315,-1437.8607 147.5582,-1442.9217"/>
+<polygon fill="#000000" stroke="#000000" points="145.4635,-1440.1133 139.1541,-1448.6246 149.3941,-1445.9056 145.4635,-1440.1133"/>
+<text text-anchor="start" x="158" y="-1396.9656" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_outer &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_wrapper_initial -->
+<g id="node14" class="node">
+<title>_parallel_orthogonal_wrapper_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="166" cy="-1302.2328" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1 -->
+<!-- _parallel_orthogonal_wrapper_initial&#45;&gt;_parallel_orthogonal_wrapper_state_1 -->
+<g id="edge9" class="edge">
+<title>_parallel_orthogonal_wrapper_initial&#45;&gt;_parallel_orthogonal_wrapper_state_1</title>
+<path fill="none" stroke="#000000" d="M166,-1296.5844C166,-1289.4564 166,-1276.8195 166,-1266 166,-1266 166,-1266 166,-1101 166,-1098.6063 166,-1096.1687 166,-1093.7066"/>
+<polygon fill="#000000" stroke="#000000" points="169.5001,-1093.4956 166,-1083.4957 162.5001,-1093.4957 169.5001,-1093.4956"/>
+<text text-anchor="middle" x="167.3895" y="-1180.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_2 -->
+<!-- _parallel_orthogonal_wrapper_state_2_initial -->
+<g id="node17" class="node">
+<title>_parallel_orthogonal_wrapper_state_2_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="204" cy="-766" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_2_inner_3 -->
+<g id="node19" class="node">
+<title>_parallel_orthogonal_wrapper_state_2_inner_3</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="252,-410 190,-410 190,-374 252,-374 252,-410"/>
+<text text-anchor="start" x="200.9942" y="-388.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">inner_3</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M202.3333,-375C202.3333,-375 239.6667,-375 239.6667,-375 245.3333,-375 251,-380.6667 251,-386.3333 251,-386.3333 251,-397.6667 251,-397.6667 251,-403.3333 245.3333,-409 239.6667,-409 239.6667,-409 202.3333,-409 202.3333,-409 196.6667,-409 191,-403.3333 191,-397.6667 191,-397.6667 191,-386.3333 191,-386.3333 191,-380.6667 196.6667,-375 202.3333,-375"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_2_initial&#45;&gt;_parallel_orthogonal_wrapper_state_2_inner_3 -->
+<g id="edge10" class="edge">
+<title>_parallel_orthogonal_wrapper_state_2_initial&#45;&gt;_parallel_orthogonal_wrapper_state_2_inner_3</title>
+<path fill="none" stroke="#000000" d="M206.4284,-760.7712C209.9348,-752.7293 216,-736.7582 216,-722.5 216,-722.5 216,-722.5 216,-427.5 216,-425.2243 216.102,-422.8746 216.2768,-420.5183"/>
+<polygon fill="#000000" stroke="#000000" points="219.7801,-420.6846 217.4194,-410.3561 212.8239,-419.9024 219.7801,-420.6846"/>
+<text text-anchor="middle" x="217.3895" y="-576" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_2_inner_4 -->
+<g id="node18" class="node">
+<title>_parallel_orthogonal_wrapper_state_2_inner_4</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="252,-76 190,-76 190,-40 252,-40 252,-76"/>
+<text text-anchor="start" x="200.9942" y="-54.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">inner_4</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M202.3333,-41C202.3333,-41 239.6667,-41 239.6667,-41 245.3333,-41 251,-46.6667 251,-52.3333 251,-52.3333 251,-63.6667 251,-63.6667 251,-69.3333 245.3333,-75 239.6667,-75 239.6667,-75 202.3333,-75 202.3333,-75 196.6667,-75 191,-69.3333 191,-63.6667 191,-63.6667 191,-52.3333 191,-52.3333 191,-46.6667 196.6667,-41 202.3333,-41"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_2_inner_3&#45;&gt;_parallel_orthogonal_wrapper_state_2_inner_4 -->
+<g id="edge11" class="edge">
+<title>_parallel_orthogonal_wrapper_state_2_inner_3&#45;&gt;_parallel_orthogonal_wrapper_state_2_inner_4</title>
+<path fill="none" stroke="#000000" d="M214.555,-373.7962C213.1124,-368.3088 212,-362.2224 212,-356.5 212,-356.5 212,-356.5 212,-93.5 212,-91.0859 212.198,-88.607 212.5352,-86.1355"/>
+<polygon fill="#000000" stroke="#000000" points="215.9918,-86.7008 214.555,-76.2038 209.1322,-85.3057 215.9918,-86.7008"/>
+<text text-anchor="start" x="212" y="-222" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_inner_4 &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1&#45;&gt;_parallel_orthogonal_wrapper_state_2 -->
+<g id="edge13" class="edge">
+<title>_parallel_orthogonal_wrapper_state_1&#45;&gt;_parallel_orthogonal_wrapper_state_2</title>
+<path fill="none" stroke="#000000" d="M173.9989,-1023.7913C184.3297,-1018.3846 201,-1007.2911 201,-992 201,-992 201,-992 201,-827 201,-819.0643 207.2423,-818.4384 213.2106,-815.8277"/>
+<polygon fill="#000000" stroke="#000000" points="215.4451,-818.5219 221,-809.5 211.0314,-813.0887 215.4451,-818.5219"/>
+<text text-anchor="start" x="201" y="-906.5" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000">to_state_2 &#160;&#160;</text>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1_initial -->
+<g id="node21" class="node">
+<title>_parallel_orthogonal_wrapper_state_1_initial</title>
+<ellipse fill="#000000" stroke="#000000" stroke-width="2" cx="138" cy="-1027.5" rx="5.5" ry="5.5"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1_inner_1 -->
+<g id="node23" class="node">
+<title>_parallel_orthogonal_wrapper_state_1_inner_1</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="166,-597 104,-597 104,-561 166,-561 166,-597"/>
+<text text-anchor="start" x="114.9942" y="-575.2" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">inner_1</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M116.3333,-562C116.3333,-562 153.6667,-562 153.6667,-562 159.3333,-562 165,-567.6667 165,-573.3333 165,-573.3333 165,-584.6667 165,-584.6667 165,-590.3333 159.3333,-596 153.6667,-596 153.6667,-596 116.3333,-596 116.3333,-596 110.6667,-596 105,-590.3333 105,-584.6667 105,-584.6667 105,-573.3333 105,-573.3333 105,-567.6667 110.6667,-562 116.3333,-562"/>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1_initial&#45;&gt;_parallel_orthogonal_wrapper_state_1_inner_1 -->
+<g id="edge12" class="edge">
+<title>_parallel_orthogonal_wrapper_state_1_initial&#45;&gt;_parallel_orthogonal_wrapper_state_1_inner_1</title>
+<path fill="none" stroke="#000000" d="M137.4953,-1021.9713C136.9028,-1014.993 136,-1002.6175 136,-992 136,-992 136,-992 136,-614.5 136,-612.107 135.977,-609.6235 135.938,-607.1322"/>
+<polygon fill="#000000" stroke="#000000" points="139.4354,-606.9735 135.7054,-597.057 132.4372,-607.1351 139.4354,-606.9735"/>
+<text text-anchor="middle" x="137.3895" y="-763" font-family="Helvetica,sans-Serif" font-size="10.00" fill="#000000"> </text>
+</g>
+<!-- _parallel_orthogonal_wrapper_state_1_inner_2 -->
+<g id="node22" class="node">
+<title>_parallel_orthogonal_wrapper_state_1_inner_2</title>
+<polygon fill="transparent" stroke="transparent" stroke-width="2" points="110,-1045.5 48,-1045.5 48,-1009.5 110,-1009.5 110,-1045.5"/>
+<text text-anchor="start" x="58.9942" y="-1023.7" font-family="Helvetica,sans-Serif" font-size="12.00" fill="#000000">inner_2</text>
+<path fill="none" stroke="#000000" stroke-width="2" d="M60.3333,-1010.5C60.3333,-1010.5 97.6667,-1010.5 97.6667,-1010.5 103.3333,-1010.5 109,-1016.1667 109,-1021.8333 109,-1021.8333 109,-1033.1667 109,-1033.1667 109,-1038.8333 103.3333,-1044.5 97.6667,-1044.5 97.6667,-1044.5 60.3333,-1044.5 60.3333,-1044.5 54.6667,-1044.5 49,-1038.8333 49,-1033.1667 49,-1033.1667 49,-1021.8333 49,-1021.8333 49,-1016.1667 54.6667,-1010.5 60.3333,-1010.5"/>
+</g>
+</g>
+</svg>

+ 92 - 0
test/test_files/features/history/test_history_deep.xml

@@ -0,0 +1,92 @@
+<?xml version="1.0" ?>
+<test>
+  <statechart>
+    <semantics
+      big_step_maximality="take_many"
+      combo_step_maximality="combo_take_many"
+      internal_event_lifeline="queue"/>
+
+    <inport name="in">
+      <event name="start"/>
+    </inport>
+
+    <outport name="out">
+      <event name="check1"/>
+      <event name="check2"/>
+      <event name="check3"/>
+    </outport>
+
+    <root>
+      <parallel id="parallel">
+        <state id="orthogonal" initial="wrapper">
+          <state id="wrapper" initial="state_1">
+
+            <state id="state_1" initial="inner_1">
+              <state id="inner_1"/>
+              <state id="inner_2"/>
+              <transition event="to_state_2" target="../state_2"/>
+            </state>
+
+            <state id="state_2" initial="inner_3">
+              <state id="inner_3">
+                <transition event="to_inner_4" target="../inner_4"/>
+              </state>
+              <state id="inner_4"/>
+            </state>
+
+            <history id="history" type="deep">
+              <!-- <transition target="../state_1"/> -->
+            </history>
+
+            <transition event="to_outer" target="../outer"/>
+          </state>
+          <state id="outer">
+            <transition event="to_history" target="../wrapper/history"/>
+          </state>
+        </state>
+        <state id="orthogonal_tester" initial="start">
+          <state id="start">
+            <transition target="../step1">
+              <raise event="to_state_2" />
+              <raise event="to_inner_4" />
+            </transition>
+          </state>
+          <state id="step1">
+            <transition cond='INSTATE(["/parallel/orthogonal/wrapper/state_2/inner_4"])' target="../step2">
+              <raise port="out" event="check1" />
+              <raise event="to_outer" />
+            </transition>
+          </state>
+          <state id="step2">
+            <transition cond='INSTATE(["/parallel/orthogonal/outer"])' target="../step3">
+              <raise port="out" event="check2" />
+              <raise event="to_history" />
+            </transition>
+          </state>
+          <state id="step3">
+            <transition cond='INSTATE(["/parallel/orthogonal/wrapper/state_2/inner_4"])' target="../end">
+              <raise port="out" event="check3" />
+            </transition>
+          </state>
+          <state id="end"/>
+        </state>
+      </parallel>
+    </root>
+  </statechart>
+
+  <input>
+    <event port="in" name="start" time="0 d"/>
+  </input>
+
+  <output>
+    <big_step>
+      <event port="out" name="check1"/>
+    </big_step>
+    <big_step>
+      <event port="out" name="check2"/>
+    </big_step>
+    <big_step>
+      <event port="out" name="check3"/>
+    </big_step>
+  </output>
+</test>