docs: def group_states(self, state_ids, scoped_variable_ids=None):
          """ Group states and scoped variables into a new hierarchy state and remain internal connections.
              Interconnecting transitions and data flows to parent and other child states are removed, at the moment.
  
          :param state_ids: state_id's of all states that are to be grouped.
          :param scoped_variable_ids: data_port_id's of all scoped variables that are to be grouped, too.
          :return:
          """
          # TODO remember changed state or state element ids and provide them for the model functionalities
          assert all([state_id in self.states.keys() for state_id in state_ids])
          if scoped_variable_ids is None:
              scoped_variable_ids = []
          assert all([p_id in self.scoped_variables.keys() for p_id in scoped_variable_ids])
          from rafcon.core.states.barrier_concurrency_state import DeciderState
          if any(isinstance(self.states[child_state_id], DeciderState) for child_state_id in state_ids):
              raise ValueError("State of type DeciderState can not be grouped.")
  
          def create_name(name_str, used_names):
              number_str = ""
              number_of_str = 0
              while name_str + number_str in used_names:
                  number_str = "_{}".format(number_of_str)
                  number_of_str += 1
              return name_str + number_str
  
          [related_transitions, related_data_flows] = self.related_linkage_states_and_scoped_variables(state_ids,
                                                                                                       scoped_variable_ids)
  
          def assign_ingoing_outgoing(df, going_data_linkage_for_port, ingoing=True):
              internal = 'internal' if ingoing else 'external'
              external = 'external' if ingoing else 'internal'
              if (df.to_state, df.to_key) in going_data_linkage_for_port['to']:
                  going_data_linkage_for_port['to'][(df.to_state, df.to_key)][external].append(df)
              else:
                  going_data_linkage_for_port['to'][(df.to_state, df.to_key)] = {external: [df], internal: [df]}
              if (df.from_state, df.from_key) in going_data_linkage_for_port['from']:
                  going_data_linkage_for_port['from'][(df.from_state, df.from_key)][internal].append(df)
              else:
                  going_data_linkage_for_port['from'][(df.from_state, df.from_key)] = {external: [df], internal: [df]}
  
          def print_df_from_and_to(going_data_linkage_for_port):
              logger.verbose('data linkage FROM: ')
              for port, port_dfs in going_data_linkage_for_port['from'].items():
                  logger.verbose("\tport: {0} {1}".format(port, '' if 'args' not in port_dfs else port_dfs['args']))
                  logger.verbose("\t\texternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['external']]))
                  logger.verbose("\t\tinternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['internal']]))
              logger.verbose('data linkage TO: ')
              for port, port_dfs in going_data_linkage_for_port['to'].items():
                  logger.verbose("\tport: {0} {1}".format(port, '' if 'args' not in port_dfs else port_dfs['args']))
                  logger.verbose("\t\texternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['external']]))
                  logger.verbose("\t\tinternal: \n\t\t\t" + "\n\t\t\t".join([str(df) for df in port_dfs['internal']]))
  
          def reduce_dfs(port_data_linkages, df_id):
              for port_key in list(port_data_linkages.keys()):
                  port_df = port_data_linkages[port_key]
                  port_df['internal'] = [df for df in port_df['internal'] if df.data_flow_id != df_id]
                  port_df['external'] = [df for df in port_df['external'] if df.data_flow_id != df_id]
                  if not port_df['internal'] and not port_df['external']:
                      del port_data_linkages[port_key]
  
          def do_prior_in_out_going(going_data_linkage_for_port, prior_port_key, prior_locate_key):
              # logger.info("PRIOR IN OUT {0}, {1}".format(prior_port_key, prior_locate_key))
              minor_port_key = 'from' if prior_port_key == 'to' else 'to'
              minor_locate_key = 'external' if prior_locate_key == 'internal' else 'internal'
              for port_key in list(going_data_linkage_for_port[prior_port_key].keys()):
                  port_dfs = going_data_linkage_for_port[prior_port_key][port_key]
                  # print(prior_port_key, ": check: ", port_key, " length: ", len(port_dfs[prior_locate_key]))
                  if len(port_dfs[prior_locate_key]) > 1:
                      for df in port_dfs[prior_locate_key]:
                          # print("remove: ", df.data_flow_id, prior_locate_key, minor_port_key)
                          reduce_dfs(going_data_linkage_for_port[minor_port_key], df.data_flow_id)
              for port_key in list(going_data_linkage_for_port[minor_port_key].keys()):
                  port_dfs = going_data_linkage_for_port[minor_port_key][port_key]
                  # print(minor_port_key, ": check: ", port_key, " length: ", len(port_dfs[minor_locate_key]))
                  if len(port_dfs[minor_locate_key]) > 1:
                      for df in port_dfs[minor_locate_key]:
                          # print("remove: ", df.data_flow_id, minor_locate_key, prior_port_key)
                          reduce_dfs(going_data_linkage_for_port[prior_port_key], df.data_flow_id)
              for port_key in list(going_data_linkage_for_port[prior_port_key].keys()):
                  port_dfs = going_data_linkage_for_port[prior_port_key][port_key]
                  # print(prior_port_key, ": check: ", port_key, " length: ", len(port_dfs[prior_locate_key]))
                  if len(port_dfs[prior_locate_key]) == 1:
                      for df in port_dfs[prior_locate_key]:
                          # print("remove: ", df.data_flow_id, prior_locate_key, minor_port_key)
                          reduce_dfs(going_data_linkage_for_port[minor_port_key], df.data_flow_id)
  
          def create_data_port_args(going_data_linkage_for_port):
              names = []
              for goal, data_port_linkage in going_data_linkage_for_port['to'].items():
                  state = self.states[goal[0]] if goal[0] != self.state_id else self
                  port = state.get_data_port_by_id(goal[1])
                  data_port_linkage['args'] = port.state_element_to_dict(port)
                  data_port_linkage['args']['name'] = create_name(data_port_linkage['args']['name'], names)
                  names.append(data_port_linkage['args']['name'])
              for goal, data_port_linkage in going_data_linkage_for_port['from'].items():
                  state = self.states[goal[0]] if goal[0] != self.state_id else self
                  port = state.get_data_port_by_id(goal[1])
                  data_port_linkage['args'] = port.state_element_to_dict(port)
                  data_port_linkage['args']['name'] = create_name(data_port_linkage['args']['name'], names)
                  names.append(data_port_linkage['args']['name'])
  
          ################## IDENTIFY/PRE-PROCESS INGOING DATA FLOWS ###################
          # ingoing data linkage to rebuild -> overview of all with duplicates
          ingoing_data_linkage_for_port = {'from': {}, 'to': {}}
          for df in related_data_flows['ingoing']:
              assign_ingoing_outgoing(df, ingoing_data_linkage_for_port)
  
          # logger.info("GROUP DATA INGOING BEFORE")
          # print_df_from_and_to(ingoing_data_linkage_for_port)
  
          # prior to-linkage-merge for ingoing over from-linkage-merge -> less internal ingoing data flows
          do_prior_in_out_going(ingoing_data_linkage_for_port, prior_port_key='to', prior_locate_key='external')
  
          # logger.info("GROUPED INGOING DATA AFTER")
          # print_df_from_and_to(ingoing_data_linkage_for_port)
  
          # get name and args for ports
          create_data_port_args(ingoing_data_linkage_for_port)
          # logger.info("GROUPED INGOING DATA PORTS")
          # print_df_from_and_to(ingoing_data_linkage_for_port)
  
          ################## IDENTIFY/PRE-PROCESS OUTGOING DATA FLOWS ###################
          # outgoing data linkage to rebuild -> overview of all with duplicates
          outgoing_data_linkage_for_port = {'from': {}, 'to': {}}
          for df in related_data_flows['outgoing']:
              assign_ingoing_outgoing(df, outgoing_data_linkage_for_port, ingoing=False)
  
          # logger.info("GROUP DATA OUTGOING BEFORE")
          # print_df_from_and_to(outgoing_data_linkage_for_port)
  
          # prior from-linkage-merge for outgoing over to-linkage-merge -> less outgoing data flows
          do_prior_in_out_going(outgoing_data_linkage_for_port, prior_port_key='from', prior_locate_key='external')
  
          # logger.info("GROUPED OUTGOING DATA AFTER")
          # print_df_from_and_to(outgoing_data_linkage_for_port)
  
          # get name and args for ports
          create_data_port_args(outgoing_data_linkage_for_port)
          # logger.info("GROUPED OUTGOING DATA PORTS")
          # print_df_from_and_to(outgoing_data_linkage_for_port)
  
          ############################# CREATE NEW STATE #############################
          # all internal transitions
          transitions_internal = {t.transition_id: self.remove_transition(t.transition_id, destroy=False)
                                  for t in related_transitions['enclosed']}
          # all internal data flows
          data_flows_internal = {df.data_flow_id: self.remove_data_flow(df.data_flow_id, destroy=False)
                                 for df in related_data_flows['enclosed']}
          # TODO add warning if a data-flow is connected to scoped variables which has ingoing and outgoing data flows
          # TODO linked with selected states -> group would change behavior and scoped would need to be selected or
          # TODO                                local scoped variable need to be introduced in new hierarchy state
          # all internal scoped variables
          scoped_variables_to_group = {dp_id: self.remove_scoped_variable(dp_id, destroy=False)
                                       for dp_id in scoped_variable_ids}
          # all states
          states_to_group = {state_id: self.remove_state(state_id, recursive=False, destroy=False)
                             for state_id in state_ids}
          from rafcon.core.states.hierarchy_state import HierarchyState
          # secure state id conflicts for the taken transitions
          from rafcon.core.id_generator import state_id_generator
          state_id = state_id_generator(used_state_ids=state_ids + [self.state_id])
          # if scoped variables are used all data flows have to be checked if those link to those and correct the state_id
          if scoped_variable_ids:
              for data_flow in data_flows_internal.values():
                  if data_flow.from_state == self.state_id:
                      data_flow.from_state = state_id
                  if data_flow.to_state == self.state_id:
                      data_flow.to_state = state_id
          s = HierarchyState(states=states_to_group, transitions=transitions_internal, data_flows=data_flows_internal,
                             scoped_variables=scoped_variables_to_group, state_id=state_id)
          state_id = self.add_state(s)
  
          def find_logical_destinations_of_transitions(transitions):
              destinations = {}
              for t in transitions:
                  if (t.to_state, t.to_outcome) in destinations:
                      destinations[(t.to_state, t.to_outcome)].append(t)
                  else:
                      destinations[(t.to_state, t.to_outcome)] = [t]
              return destinations
  
          ################## IDENTIFY TRANSITIONS #####################
          # transition from ingoing transition
          ingoing_logical_destinations = find_logical_destinations_of_transitions(related_transitions['ingoing'])
          if len(ingoing_logical_destinations) > 1:
              logger.warning("There is only one ingoing transition on a state possible. \n"
                             "The following transitions are removed by 'group_states': \n{}"
                             "".format('\n'.join([str(destination) for destination in ingoing_logical_destinations.items()[1:]])))
          ingoing_transitions = None
          if len(ingoing_logical_destinations) > 0:
              ingoing_transitions = list(ingoing_logical_destinations.items())[0][1]
          # transitions from outgoing transitions
          transitions_outgoing = {t.transition_id: t for t in related_transitions['outgoing']}
          outgoing_logical_destinations = find_logical_destinations_of_transitions(related_transitions['outgoing'])
  
          ################## DO INGOING TRANSITIONS ###################
          if ingoing_transitions:
              t = ingoing_transitions[0]
              s.add_transition(None, None, t.to_state, t.to_outcome)
              for t in ingoing_transitions:
                  self.add_transition(t.from_state, t.from_outcome, s.state_id, None)
  
          ################## DO OUTGOING OUTCOMES ###################
          # outcomes from outgoing transitions
          outcomes_outgoing_transitions = {}
          new_outcome_ids = {}
          state_outcomes_by_name = {oc.name: oc_id for oc_id, oc in s.outcomes.items()}
          for goal, transitions in list(outgoing_logical_destinations.items()):
              t = transitions[0]
              name = s.states[t.from_state].outcomes[t.from_outcome].name
              # print((t.to_state, t.to_outcome))
              # print(outcomes_outgoing_transitions)
              if goal in outcomes_outgoing_transitions:
                  # logger.info("old outcome {}".format((t.to_state, t.to_outcome)))
                  name = outcomes_outgoing_transitions[goal]
              else:
                  name = create_name(name, list(new_outcome_ids.keys()))
                  outcomes_outgoing_transitions[goal] = name
              # print(outcomes_outgoing_transitions, "\n", new_outcome_ids)
              if name not in new_outcome_ids:
                  if name in state_outcomes_by_name:
                      new_outcome_ids[name] = state_outcomes_by_name[name]
                      # logger.info("old outcome_id {0}\n{1}".format(state_outcomes_by_name[name], new_outcome_ids))
                  else:
                      new_outcome_ids[name] = s.add_outcome(name=name)
                      # logger.info("new outcome_id {0}\n{1}".format(new_outcome_ids[name], new_outcome_ids))
              # else:
              #     logger.info("name {0} in {1} -> {2}".format(name, new_outcome_ids, outcomes_outgoing_transitions))
  
          ################## DO OUTGOING TRANSITIONS ###################
          # external outgoing transitions
          # print("external transitions to create", outcomes_outgoing_transitions)
          for goal, name in outcomes_outgoing_transitions.items():
              try:
                  # avoid to use a outcome twice
                  if any([t for t in s.parent.transitions.values()
                          if t.from_state == s.state_id and t.from_outcome == new_outcome_ids[name]]):
                      continue
                  # add the transition for the outcome
                  self.add_transition(s.state_id, new_outcome_ids[name], goal[0], goal[1])
              except ValueError:
                  logger.exception("Error while recreation of logical linkage.")
          # internal outgoing transitions
          # print("internal transitions to create", transitions_outgoing)
          for t_id, t in transitions_outgoing.items():
              name = outcomes_outgoing_transitions[(t.to_state, t.to_outcome)]
              s.add_transition(t.from_state, t.from_outcome, s.state_id, new_outcome_ids[name], t_id)
  
          new_state_ids = {self.state_id: s.state_id}
          ############## REBUILD INGOING DATA LINKAGE ################
          full_linkage = copy(ingoing_data_linkage_for_port['from'])
          full_linkage.update(ingoing_data_linkage_for_port['to'])
          for port, data_port_linkage in full_linkage.items():
              # input data ports from ingoing data flows
              args = data_port_linkage['args']
              args['data_port_id'] = None
              args['data_port_id'] = s.add_input_data_port(**args)
              # internal data flows from ingoing data flows
              # print("ingoing internal data flows")
              for df in data_port_linkage['internal']:
                  # print(df)
                  s.add_data_flow(from_state_id=s.state_id, from_data_port_id=args['data_port_id'],
                                  to_state_id=new_state_ids.get(df.to_state, df.to_state),
                                  to_data_port_id=df.to_key, data_flow_id=df.data_flow_id)
              # external data flows from ingoing data flows
              # print("ingoing external data flows")
              for df in data_port_linkage['external']:
                  # print(df)
                  self.add_data_flow(from_state_id=df.from_state,
                                     from_data_port_id=df.from_key, to_state_id=s.state_id,
                                     to_data_port_id=args['data_port_id'], data_flow_id=df.data_flow_id)
  
          ############## REBUILD OUTGOING DATA LINKAGE ################
          full_linkage = copy(outgoing_data_linkage_for_port['from'])
          full_linkage.update(outgoing_data_linkage_for_port['to'])
          for port, data_port_linkage in full_linkage.items():
              # output data ports from outgoing data flows
              args = data_port_linkage['args']
              args['data_port_id'] = None
              args['data_port_id'] = s.add_output_data_port(**args)
              # internal data flows from outgoing data flows
              # print("outgoing internal data flows")
              for df in data_port_linkage['internal']:
                  # print(df)
                  s.add_data_flow(from_state_id=new_state_ids.get(df.from_state, df.from_state),
                                  from_data_port_id=df.from_key, to_state_id=s.state_id,
                                  to_data_port_id=args['data_port_id'], data_flow_id=df.data_flow_id)
              # external data flows from outgoing data flows
              # print("outgoing external data flows")
              for df in data_port_linkage['external']:
                  # print(df)
                  self.add_data_flow(from_state_id=s.state_id, from_data_port_id=args['data_port_id'],
                                     to_state_id=df.to_state, to_data_port_id=df.to_key, data_flow_id=df.data_flow_id)
  
          return self.states[state_id]