<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 def find_write_predecessors(self, address):
 """Returns all predecessor transaction ids for a write of the provided address. Arguments: address (str):
 the radix address Returns: a set of transaction ids """

        # A write operation must be preceded by:
        #   - The "enclosing writer", which is the writer at the address or
        #     the nearest writer higher (closer to the root) in the tree.
        #   - The "enclosing readers", which are the readers at the address
        #     or higher in the tree.
        #   - The "children writers", which include all writers which are
        #     lower in the tree than the address.
        #   - The "children readers", which include all readers which are
        #     lower in the tree than the address.
        #
        # The enclosing writer must be added as it may have modified a node
        # which must not happen after the current write.
        #
        # Writers which are higher in the tree than the enclosing writer may
        # have modified a node at or under the given address.  However, we do
        # not need to include them here as they will have been considered a
        # predecessor to the enclosing writer.
        #
        # Enclosing readers must be included.  Technically, we only need to add
        # enclosing readers which occurred after the enclosing writer, since
        # the readers preceding the writer will have been considered a
        # predecessor of the enclosing writer.  However, with the current
        # data structure we can not determine the difference between readers
        # so we specify them all; this is mostly harmless as it will not change
        # the eventual sort order generated by the scheduler.
        #
        # Children readers must be added, since their reads must happen prior
        # to the write.

        predecessors = set()

        enclosing_writer = None

        node_stream = self._tree.walk(address)

        address_len = len(address)

        # First, walk down from the root to the address, collecting all readers
        # and updating the enclosing_writer if needed.

        try:
            for node_address, node in node_stream:
                if node is not None:
                    predecessors.update(node.readers)

                    if node.writer is not None:
                        enclosing_writer = node.writer

                    if len(node_address) >= address_len:
                        break

        # If the address isn't on the tree, then there aren't any
        # predecessors below the node to worry about (because there
        # isn't anything at all), so return the predecessors that have
        # already been collected.
        except AddressNotInTree as err:
            if err.match is not None:
                return self.find_write_predecessors(err.match)

            return predecessors

        finally:
            if enclosing_writer is not None:
                predecessors.add(enclosing_writer)

        # Next, descend down the tree starting at the address node and
        # find all descendant readers and writers.

        for _, node in node_stream:
            if node is not None:
                if node.writer is not None:
                    predecessors.add(node.writer)

                predecessors.update(node.readers)

        return predecessors