code: def split_multi(
      ds, keep_star=False, left_aligned=False, *, permit_shuffle=False
  ):
      """Split multiallelic variants.
  
      Warning
      -------
      In order to support a wide variety of data types, this function splits only
      the variants on a :class:`.MatrixTable`, but **not the genotypes**. Use
      :func:`.split_multi_hts` if possible, or split the genotypes yourself using
      one of the entry modification methods: :meth:`.MatrixTable.annotate_entries`,
      :meth:`.MatrixTable.select_entries`, :meth:`.MatrixTable.transmute_entries`.
  
      The resulting dataset will be keyed by the split locus and alleles.
  
      :func:`.split_multi` adds the following fields:
  
       - `was_split` (*bool*) -- ``True`` if this variant was originally
         multiallelic, otherwise ``False``.
  
       - `a_index` (*int*) -- The original index of this alternate allele in the
         multiallelic representation (NB: 1 is the first alternate allele or the
         only alternate allele in a biallelic variant). For example, 1:100:A:T,C
         splits into two variants: 1:100:A:T with ``a_index = 1`` and 1:100:A:C
         with ``a_index = 2``.
  
       - `old_locus` (*locus*) -- The original, unsplit locus.
  
       - `old_alleles` (*array<str>*) -- The original, unsplit alleles.
  
       All other fields are left unchanged.
  
      Warning
      -------
      This method assumes `ds` contains at most one non-split variant per locus. This assumption permits the
      most efficient implementation of the splitting algorithm. If your queries involving `split_multi`
      crash with errors about out-of-order keys, this assumption may be violated. Otherwise, this
      warning likely does not apply to your dataset.
  
      If each locus in `ds` contains one multiallelic variant and one or more biallelic variants, you
      can filter to the multiallelic variants, split those, and then combine the split variants with
      the original biallelic variants.
  
      For example, the following code splits a dataset `mt` which contains a mixture of split and
      non-split variants.
  
      >>> bi = mt.filter_rows(hl.len(mt.alleles) == 2)
      >>> bi = bi.annotate_rows(a_index=1, was_split=False, old_locus=bi.locus, old_alleles=bi.alleles)
      >>> multi = mt.filter_rows(hl.len(mt.alleles) > 2)
      >>> split = hl.split_multi(multi)
      >>> mt = split.union_rows(bi)
  
      Example
      -------
  
      :func:`.split_multi_hts`, which splits multiallelic variants for the HTS
      genotype schema and updates the entry fields by downcoding the genotype, is
      implemented as:
  
      >>> sm = hl.split_multi(ds)
      >>> pl = hl.or_missing(
      ...      hl.is_defined(sm.PL),
      ...      (hl.range(0, 3).map(lambda i: hl.min(hl.range(0, hl.len(sm.PL))
      ...                     .filter(lambda j: hl.downcode(hl.unphased_diploid_gt_index_call(j), sm.a_index) == hl.unphased_diploid_gt_index_call(i))
      ...                     .map(lambda j: sm.PL[j])))))
      >>> split_ds = sm.annotate_entries(
      ...     GT=hl.downcode(sm.GT, sm.a_index),
      ...     AD=hl.or_missing(hl.is_defined(sm.AD),
      ...                     [hl.sum(sm.AD) - sm.AD[sm.a_index], sm.AD[sm.a_index]]),
      ...     DP=sm.DP,
      ...     PL=pl,
      ...     GQ=hl.gq_from_pl(pl)).drop('old_locus', 'old_alleles')
  
      See Also
      --------
      :func:`.split_multi_hts`
  
      Parameters
      ----------
      ds : :class:`.MatrixTable` or :class:`.Table`
          An unsplit dataset.
      keep_star : :obj:`bool`
          Do not filter out * alleles.
      left_aligned : :obj:`bool`
          If ``True``, variants are assumed to be left aligned and have unique
          loci. This avoids a shuffle. If the assumption is violated, an error
          is generated.
      permit_shuffle : :obj:`bool`
          If ``True``, permit a data shuffle to sort out-of-order split results.
          This will only be required if input data has duplicate loci, one of
          which contains more than one alternate allele.
  
      Returns
      -------
      :class:`.MatrixTable` or :class:`.Table`
      """
  
      require_row_key_variant(ds, "split_multi")
      new_id = Env.get_uid()
      is_table = isinstance(ds, Table)
  
      old_row = ds.row if is_table else ds._rvrow
      kept_alleles = hl.range(1, hl.len(old_row.alleles))
      if not keep_star:
          kept_alleles = kept_alleles.filter(
              lambda i: old_row.alleles[i] != "*"
          )
  
      def new_struct(variant, i):
          return hl.struct(
              alleles=variant.alleles,
              locus=variant.locus,
              a_index=i,
              was_split=hl.len(old_row.alleles) > 2,
          )
  
      def split_rows(expr, rekey):
          if isinstance(ds, MatrixTable):
              mt = ds.annotate_rows(**{new_id: expr}).explode_rows(
                  new_id
              )
              if rekey:
                  mt = mt.key_rows_by()
              else:
                  mt = mt.key_rows_by("locus")
              new_row_expr = mt._rvrow.annotate(
                  locus=mt[new_id]["locus"],
                  alleles=mt[new_id]["alleles"],
                  a_index=mt[new_id]["a_index"],
                  was_split=mt[new_id]["was_split"],
                  old_locus=mt.locus,
                  old_alleles=mt.alleles,
              ).drop(new_id)
  
              mt = mt._select_rows("split_multi", new_row_expr)
              if rekey:
                  return mt.key_rows_by("locus", "alleles")
              else:
                  return MatrixTable(
                      ir.MatrixKeyRowsBy(
                          mt._mir, ["locus", "alleles"], is_sorted=True
                      )
                  )
          else:
              assert isinstance(ds, Table)
              ht = ds.annotate(**{new_id: expr}).explode(new_id)
              if rekey:
                  ht = ht.key_by()
              else:
                  ht = ht.key_by("locus")
              new_row_expr = ht.row.annotate(
                  locus=ht[new_id]["locus"],
                  alleles=ht[new_id]["alleles"],
                  a_index=ht[new_id]["a_index"],
                  was_split=ht[new_id]["was_split"],
                  old_locus=ht.locus,
                  old_alleles=ht.alleles,
              ).drop(new_id)
  
              ht = ht._select("split_multi", new_row_expr)
              if rekey:
                  return ht.key_by("locus", "alleles")
              else:
                  return Table(
                      ir.TableKeyBy(
                          ht._tir, ["locus", "alleles"], is_sorted=True
                      )
                  )
  
      if left_aligned:
  
          def make_struct(i):
              def error_on_moved(v):
                  return (
                      hl.case()
                      .when(v.locus == old_row.locus, new_struct(v, i))
                      .or_error(
                          "Found non-left-aligned variant in split_multi"
                      )
                  )
  
              return hl.bind(
                  error_on_moved,
                  hl.min_rep(
                      old_row.locus,
                      [old_row.alleles[0], old_row.alleles[i]],
                  ),
              )
  
          return split_rows(
              hl.sorted(kept_alleles.map(make_struct)), permit_shuffle
          )
      else:
  
          def make_struct(i, cond):
              def struct_or_empty(v):
                  return (
                      hl.case()
                      .when(cond(v.locus), hl.array([new_struct(v, i)]))
                      .or_missing()
                  )
  
              return hl.bind(
                  struct_or_empty,
                  hl.min_rep(
                      old_row.locus,
                      [old_row.alleles[0], old_row.alleles[i]],
                  ),
              )
  
          def make_array(cond):
              return hl.sorted(
                  kept_alleles.flatmap(lambda i: make_struct(i, cond))
              )
  
          left = split_rows(
              make_array(lambda locus: locus == ds["locus"]),
              permit_shuffle,
          )
          moved = split_rows(
              make_array(lambda locus: locus != ds["locus"]), True
          )
      return (
          left.union(moved)
          if is_table
          else left.union_rows(moved, _check_cols=False)
      )
  
imports: import builtins
  import itertools
  import math
  from typing import Dict, Callable, Optional, Union, Tuple, List
  import hail
  import hail
  import hail.expr.aggregators
  from hail import ir
  from hail.expr import Expression, ExpressionException, expr_float64, expr_call, expr_any, expr_numeric, expr_locus, analyze, raise_unless_entry_indexed, raise_unless_row_indexed, matrix_table_source, table_source, raise_unless_column_indexed
  from hail.expr.types import tbool, tarray, tfloat64, tint32
  from hail.genetics.reference_genome import reference_genome_type
  from hail.linalg import BlockMatrix
  from hail.matrixtable import MatrixTable
  from hail.methods.misc import require_biallelic, require_row_key_variant
  from hail.stats import LinearMixedModel
  from hail.table import Table
  from hail.typecheck import typecheck, nullable, numeric, oneof, sized_tupleof, sequenceof, enumeration, anytype
  from hail.utils import wrap_to_list, new_temp_file, FatalError
  from hail.utils.java import Env, info, warning
  from .None import pca
  from .None import relatedness
  from ..backend.spark_backend import SparkBackend
label_desc: Use image node_id 26 for calling a global function with example usage: split_multi(ds, keep_star, left_aligned) and returns: unknown, hl, split_rows, hl, hl, hl, mt, MatrixTable, ht, Table, hl, hl