docs: def add_dispatcher(self, dsp, inputs, outputs, dsp_id=None,
                         input_domain=None, weight=None, inp_weight=None,
                         description=None, include_defaults=False,
                         await_domain=None, **kwargs):
          """
          Add a single sub-dispatcher node to dispatcher.
  
          :param dsp:
              Child dispatcher that is added as sub-dispatcher node to the parent
              dispatcher.
          :type dsp: Dispatcher | dict[str, list]
  
          :param inputs:
              Inputs mapping. Data node ids from parent dispatcher to child
              sub-dispatcher.
          :type inputs: dict[str, str | list[str]] | tuple[str] | 
                        (str, ..., dict[str, str | list[str]])
  
          :param outputs:
              Outputs mapping. Data node ids from child sub-dispatcher to parent
              dispatcher.
          :type outputs: dict[str, str | list[str]] | tuple[str] | 
                         (str, ..., dict[str, str | list[str]])
  
          :param dsp_id:
              Sub-dispatcher node id.
              If None will be assigned as <dsp.name>.
          :type dsp_id: str, optional
  
          :param input_domain:
              A function that checks if input values satisfy the function domain.
              This can be any function that takes the a dictionary with the inputs
              of the sub-dispatcher node and returns True if input values satisfy
              the domain, otherwise False.
  
              .. note:: This function is invoked every time that a data node reach
                 the sub-dispatcher node.
          :type input_domain: (dict) -> bool, optional
  
          :param weight:
              Node weight. It is a weight coefficient that is used by the dispatch
              algorithm to estimate the minimum workflow.
          :type weight: float, int, optional
  
          :param inp_weight:
              Edge weights from data nodes to the sub-dispatcher node.
              It is a dictionary (key=data node id) with the weight coefficients
              used by the dispatch algorithm to estimate the minimum workflow.
          :type inp_weight: dict[str, int | float], optional
  
          :param description:
              Sub-dispatcher node's description.
          :type description: str, optional
  
          :param include_defaults:
              If True the default values of the sub-dispatcher are added to the
              current dispatcher.
          :type include_defaults: bool, optional
  
          :param await_domain:
              If True the Dispatcher waits all input results before executing the
              `input_domain` function. If a number is defined this is used as
              `timeout` for `Future.result` method [default: True]. Note this is
              used when asynchronous or parallel execution is enable.
          :type await_domain: bool|int|float, optional
  
          :param kwargs:
              Set additional node attributes using key=value.
          :type kwargs: keyword arguments, optional
  
          :return:
              Sub-dispatcher node id.
          :rtype: str
  
          .. seealso:: :func:`add_data`, :func:`add_func`, :func:`add_function`,
             :func:`add_from_lists`
  
          **--------------------------------------------------------------------**
  
          **Example**:
  
          .. testsetup::
              >>> dsp = Dispatcher(name='Dispatcher')
  
          Create a sub-dispatcher::
  
              >>> sub_dsp = Dispatcher()
              >>> sub_dsp.add_function('max', max, ['a', 'b'], ['c'])
              'max'
  
          Add the sub-dispatcher to the parent dispatcher::
  
              >>> dsp.add_dispatcher(dsp_id='Sub-Dispatcher', dsp=sub_dsp,
              ...                    inputs={'A': 'a', 'B': 'b'},
              ...                    outputs={'c': 'C'})
              'Sub-Dispatcher'
  
          Add a sub-dispatcher node with domain::
  
  
              >>> def my_domain(kwargs):
              ...     return kwargs['C'] > 3
              ...
              >>> dsp.add_dispatcher(dsp_id='Sub-Dispatcher with domain',
              ...                    dsp=sub_dsp, inputs={'C': 'a', 'D': 'b'},
              ...                    outputs={('c', 'b'): ('E', 'E1')},
              ...                    input_domain=my_domain)
              'Sub-Dispatcher with domain'
          """
          from .utils.blue import _init
          dsp = _init(dsp)
  
          if not isinstance(dsp, self.__class__):
              kw = dsp
              dsp = self.__class__(
                  name=dsp_id or 'unknown',
                  executor=self.executor
              )
              dsp.add_from_lists(**kw)
  
          if not dsp_id:  # Get the dsp id.
              dsp_id = dsp.name or 'unknown'
  
          if description is None:  # Get description.
              description = dsp.__doc__ or None
  
          if not isinstance(inputs, dict):  # Create the inputs dict.
              inputs = kk_dict(*inputs)
  
          if not isinstance(outputs, dict):  # Create the outputs dict.
              outputs = kk_dict(*outputs)
  
          # Set zero as default input distances.
          # noinspection PyTypeChecker
          _weight_from = dict.fromkeys(inputs.keys(), 0.0)
          _weight_from.update(inp_weight or {})
  
          from .utils.alg import _nodes
  
          # Return dispatcher node id.
          dsp_id = self.add_function(
              dsp_id, dsp, sorted(_nodes(inputs)),
              sorted(_nodes(outputs.values())), input_domain, weight,
              _weight_from, type='dispatcher', description=description,
              wait_inputs=False, await_domain=await_domain, **kwargs
          )
  
          # Set proper inputs.
          self.nodes[dsp_id]['inputs'] = inputs
  
          # Set proper outputs.
          self.nodes[dsp_id]['outputs'] = outputs
  
          if SINK not in dsp.nodes and \
                  SINK in _nodes(inputs.values()).union(_nodes(outputs)):
              dsp.add_data(SINK)  # Add sink node.
  
          # Import default values from sub-dispatcher.
          if include_defaults:
              dsp_dfl = dsp.default_values  # Namespace shortcut.
  
              remove = set()  # Set of nodes to remove after the import.
  
              # Set default values.
              for k, v in inputs.items():
                  if isinstance(v, str):
                      if v in dsp_dfl:
                          self.set_default_value(k, **dsp_dfl.pop(v))
                  else:
                      if v[0] in dsp_dfl:
                          self.set_default_value(k, **dsp_dfl.pop(v[0]))
                          remove.update(v[1:])
  
              # Remove default values.
              for k in remove:
                  dsp_dfl.pop(k, None)
  
          return dsp_id