docs: def parallel(self, width, routing=Routing.ROUND_ROBIN, func=None, name=None):
          """
          Split stream into channels and start a parallel region.
  
          Returns a new stream that will contain the contents of
          this stream with tuples distributed across its channels.
  
          The returned stream starts a parallel region where all
          downstream transforms are replicated across `width` channels.
          A parallel region is terminated by :py:meth:`end_parallel`
          or :py:meth:`for_each`.
  
          Any transform (such as :py:meth:`map`, :py:meth:`filter`, etc.) in
          a parallel region has a copy of its callable executing
          independently in parallel. Channels remain independent
          of other channels until the region is terminated.
  
          For example with this topology fragment a parallel region
          of width 3 is created::
  
              s = ...
              p = s.parallel(3)
              p = p.filter(F()).map(M())
              e = p.end_parallel()
              e.for_each(E())
  
          Tuples from ``p`` (parallelized ``s``)  are distributed
          across three channels, 0, 1 & 2
          and are independently processed by three instances of ``F`` and ``M``.
          The tuples that pass the filter ``F`` in channel 0 are then mapped
          by the instance of ``M`` in channel 0, and so on for channels 1 and 2.
  
          The channels are combined by ``end_parallel`` and so a single instance
          of ``E`` processes all the tuples from channels 0, 1 & 2.
  
          This stream instance (the original) is outside of the parallel region
          and so any downstream transforms are executed normally.
          Adding this `map` transform would result in tuples
          on ``s`` being processed by a single instance of ``N``::
  
              n = s.map(N())
  
          The number of channels is set by `width` which may be an `int` greater
          than zero or a submission parameter created by
          :py:meth:`Topology.create_submission_parameter`.
  
          With IBM Streams 4.3 or later the number of channels can be
          dynamically changed at runtime.
  
          Tuples are routed to channels based upon `routing`, see :py:class:`Routing`.
  
          A parallel region can have multiple termination points, for
          example when a stream within the stream has multiple transforms
          against it::
  
              s = ...
              p = s.parallel(3)
              m1p = p.map(M1())
              m2p = p.map(M2())
              p.for_each(E())
  
              m1 = m1p.end_parallel()
              m2 = m2p.end_parallel()
  
          Parallel regions can be nested, for example::
  
              s = ...
              m = s.parallel(2).map(MO()).parallel(3).map(MI()).end_parallel().end_parallel()
  
          In this case there will be two instances of ``MO`` (the outer region) and six (2x3) instances of ``MI`` (the inner region).
           
          Streams created by :py:meth:`~Topology.source` or
          :py:meth:`~Topology.subscribe` are placed in a parallel region
          by :py:meth:`set_parallel`.
          
          Args:
              width (int): Degree of parallelism.
              routing(Routing): Denotes what type of tuple routing to use.
              func: Optional function called when :py:const:`Routing.HASH_PARTITIONED` routing is specified.
                  The function provides an integer value to be used as the hash that determines
                  the tuple channel routing.
              name (str): The name to display for the parallel region.
  
          Returns:
              Stream: A stream for which subsequent transformations will be executed in parallel.
  
          .. seealso:: :py:meth:`set_parallel`, :py:meth:`end_parallel`
          """
          _name = name
          if _name is None:
              _name = self.name + '_parallel'
              
          _name = self.topology.graph._requested_name(_name, action='parallel', func=func)
  
          if routing is None or routing == Routing.ROUND_ROBIN or routing == Routing.BROADCAST:
              op2 = self.topology.graph.addOperator("$Parallel$", name=_name)
              if name is not None:
                  op2.config['regionName'] = _name
              op2.addInputPort(outputPort=self.oport)
              if routing == Routing.BROADCAST:
                  oport = op2.addOutputPort(width, schema=self.oport.schema, routing="BROADCAST", name=_name)
              else:
                  oport = op2.addOutputPort(width, schema=self.oport.schema, routing="ROUND_ROBIN", name=_name)
  
                  
              return Stream(self.topology, oport)
          elif routing == Routing.HASH_PARTITIONED:
  
              if (func is None):
                  if self.oport.schema == streamsx.topology.schema.CommonSchema.String:
                      keys = ['string']
                      parallel_input = self.oport
                  elif self.oport.schema == streamsx.topology.schema.CommonSchema.Python:
                      func = hash
                  else:
                      raise NotImplementedError("HASH_PARTITIONED for schema {0} requires a hash function.".format(self.oport.schema))
  
              if func is not None:
                  keys = ['__spl_hash']
                  stateful = self._determine_statefulness(func)
                  hash_adder = self.topology.graph.addOperator(self.topology.opnamespace+"::HashAdder", func, stateful=stateful)
                  hash_adder._op_def['hashAdder'] = True
                  hash_adder._layout(hidden=True)
                  hash_schema = self.oport.schema.extend(streamsx.topology.schema.StreamSchema("tuple<int64 __spl_hash>"))
                  hash_adder.addInputPort(outputPort=self.oport)
                  streamsx.topology.schema.StreamSchema._fnop_style(self.oport.schema, hash_adder, 'pyStyle')
                  parallel_input = hash_adder.addOutputPort(schema=hash_schema)
  
              parallel_op = self.topology.graph.addOperator("$Parallel$", name=_name)
              if name is not None:
                  parallel_op.config['regionName'] = _name
              parallel_op.addInputPort(outputPort=parallel_input)
              parallel_op_port = parallel_op.addOutputPort(oWidth=width, schema=parallel_input.schema, partitioned_keys=keys, routing="HASH_PARTITIONED")
  
              if func is not None:
                  # use the Functor passthru operator to remove the hash attribute by removing it from output port schema
                  hrop = self.topology.graph.addPassThruOperator()
                  hrop._layout(hidden=True)
                  hrop.addInputPort(outputPort=parallel_op_port)
                  parallel_op_port = hrop.addOutputPort(schema=self.oport.schema)
  
              return Stream(self.topology, parallel_op_port)
          else :
              raise TypeError("Invalid routing type supplied to the parallel operator")