code: def solve_triangular(a, b, lower=False):
      """
      Solve the equation `a x = b` for `x`, assuming a is a triangular matrix.
  
      Parameters
      ----------
      a : (M, M) array_like
          A triangular matrix
      b : (M,) or (M, N) array_like
          Right-hand side matrix in `a x = b`
      lower : bool, optional
          Use only data contained in the lower triangle of `a`.
          Default is to use upper triangle.
  
      Returns
      -------
      x : (M,) or (M, N) array
          Solution to the system `a x = b`. Shape of return matches `b`.
      """
  
      if a.ndim != 2:
          raise ValueError("a must be 2 dimensional")
      if b.ndim <= 2:
          if a.shape[1] != b.shape[0]:
              raise ValueError(
                  "a.shape[1] and b.shape[0] must be equal"
              )
          if a.chunks[1] != b.chunks[0]:
              msg = (
                  "a.chunks[1] and b.chunks[0] must be equal. "
                  "Use .rechunk method to change the size of chunks."
              )
              raise ValueError(msg)
      else:
          raise ValueError("b must be 1 or 2 dimensional")
  
      vchunks = len(a.chunks[1])
      hchunks = 1 if b.ndim == 1 else len(b.chunks[1])
      token = tokenize(a, b, lower)
      name = "solve-triangular-" + token
  
      # for internal calculation
      # (name, i, j, k, l) corresponds to a_ij.dot(b_kl)
      name_mdot = "solve-tri-dot-" + token
  
      def _b_init(i, j):
          if b.ndim == 1:
              return b.name, i
          else:
              return b.name, i, j
  
      def _key(i, j):
          if b.ndim == 1:
              return name, i
          else:
              return name, i, j
  
      dsk = {}
      if lower:
          for i in range(vchunks):
              for j in range(hchunks):
                  target = _b_init(i, j)
                  if i > 0:
                      prevs = []
                      for k in range(i):
                          prev = name_mdot, i, k, k, j
                          dsk[prev] = (
                              np.dot,
                              (a.name, i, k),
                              _key(k, j),
                          )
                          prevs.append(prev)
                      target = (operator.sub, target, (sum, prevs))
                  dsk[_key(i, j)] = (
                      _solve_triangular_lower,
                      (a.name, i, i),
                      target,
                  )
      else:
          for i in range(vchunks):
              for j in range(hchunks):
                  target = _b_init(i, j)
                  if i < vchunks - 1:
                      prevs = []
                      for k in range(i + 1, vchunks):
                          prev = name_mdot, i, k, k, j
                          dsk[prev] = (
                              np.dot,
                              (a.name, i, k),
                              _key(k, j),
                          )
                          prevs.append(prev)
                      target = (operator.sub, target, (sum, prevs))
                  dsk[_key(i, j)] = (
                      solve_triangular_safe,
                      (a.name, i, i),
                      target,
                  )
  
      graph = HighLevelGraph.from_collections(
          name, dsk, dependencies=[a, b]
      )
  
      a_meta = meta_from_array(a)
      b_meta = meta_from_array(b)
      res = _solve_triangular_lower(
          array_safe([[1, 0], [1, 2]], dtype=a.dtype, like=a_meta),
          array_safe([0, 1], dtype=b.dtype, like=b_meta),
      )
      meta = meta_from_array(a, b.ndim, dtype=res.dtype)
      return Array(
          graph, name, shape=b.shape, chunks=b.chunks, meta=meta
      )
  
imports: from __future__ import annotations
  import operator
  import warnings
  from functools import partial
  from numbers import Number
  import numpy
  import tlz
  from dask.array.core import Array, concatenate, dotmany, from_delayed
  from dask.array.creation import eye
  from dask.array.random import RandomState, default_rng
  from dask.array.utils import array_safe, meta_from_array, solve_triangular_safe, svd_flip
  from dask.base import tokenize, wait
  from dask.blockwise import blockwise
  from dask.delayed import delayed
  from dask.highlevelgraph import HighLevelGraph
  from dask.utils import apply, derived_from
label_desc: Use image node_id 14 for calling a global function with example usage: solve_triangular(a, b, lower) and returns: Array, b,  i, b,  i,  j, name,  i, name,  i,  j