code: def predict_leaf_guest(
      ctx: Context, trees: List[DecisionTree], data: DataFrame
  ):
      predict_data = data
      tree_list = [tree.get_nodes() for tree in trees]
      max_node_num = max([len(tree) for tree in tree_list])
      map_func = functools.partial(
          generate_pos_array,
          tree_num=len(trees),
          max_node_num=max_node_num,
      )
  
      sample_pos = data.create_frame()
      sample_pos["sample_pos"] = data.apply_row(lambda x: map_func())
      result_sample_pos = sample_pos.empty_frame()
  
      sitename = ctx.local.name
  
      # start loop here
      comm_round = 0
  
      while True:
          sub_ctx = ctx.sub_ctx("predict_round").indexed_ctx(comm_round)
  
          if comm_round:
              predict_data = predict_data.loc(
                  indexer=sample_pos.get_indexer(target="sample_id"),
                  preserve_order=True,
              )
  
          sample_with_pos = DataFrame.hstack([predict_data, sample_pos])
          logger.info(
              "predict round {} has {} samples to predict".format(
                  comm_round, len(sample_with_pos)
              )
          )
          map_func = functools.partial(
              traverse_tree, trees=tree_list, sitename=sitename
          )
          new_pos = sample_with_pos.create_frame()
          new_pos["sample_pos"] = sample_with_pos.apply_row(map_func)
          done_sample_idx = new_pos.apply_row(
              lambda x: all_reach_leaf(x["sample_pos"])
          )  # samples that reach leaf node in all trees
          # not_finished_sample_idx = ~done_sample_idx
          not_finished_sample_idx = new_pos.apply_row(
              lambda x: not_finished(x["sample_pos"])
          )  # samples that not reach leaf node in all trees
  
          done_sample = new_pos.iloc(done_sample_idx)
          result_sample_pos = DataFrame.vstack(
              [result_sample_pos, done_sample]
          )
          if len(result_sample_pos) == len(data):
              sub_ctx.hosts.put("need_stop", True)
              break
  
          sub_ctx.hosts.put("need_stop", False)
          pending_samples = new_pos.iloc(not_finished_sample_idx)
  
          # send not-finished samples to host
          sub_ctx.hosts.put("pending_samples", (pending_samples))
          # get result from host and merge
          updated_pos = sub_ctx.hosts.get("updated_pos")
          sample_pos = _merge_pos(pending_samples, updated_pos)
          comm_round += 1
  
      logger.info("predict done")
  
      assert len(result_sample_pos) == len(
          data
      ), "result sample pos length not equal to data length, {} vs {}".format(
          len(result_sample_pos), len(data)
      )
      return result_sample_pos
  
file_path: FATE/python/fate/ml/ensemble/algo/secureboost/common/predict.py
imports: import pandas
  import numpy
  from typing import List
  from fate.arch import Context
  from fate.arch.dataframe import DataFrame
  import copy
  from fate.ml.ensemble.learner.decision_tree.tree_core.decision_tree import DecisionTree, _make_decision, Node
  import functools
  from logging import getLogger
label_desc: Use image node_id 9 for calling a global function with example usage: predict_leaf_guest(ctx, trees, data) and returns: result_sample_pos