code: def _andersen_lake(
      *,
      sigma,
      k_temp,
      tau,
      s_temp,
      r_temp,
      q_temp,
      is_call_options,
      grid_num_points,
      integration_num_points_kronrod,
      integration_num_points_legendre,
      max_iterations_exercise_boundary,
      max_depth_kronrod,
      tolerance_exercise_boundary,
      tolerance_kronrod,
      dtype,
  ):
      """Computes American option prices using the Andersen-Lake formula."""
      # Shape [num_options]
      eu_prices = vanilla_prices.option_price(
          volatilities=sigma,
          strikes=k_temp,
          expiries=tau,
          spots=s_temp,
          discount_rates=r_temp,
          dividend_rates=q_temp,
          is_call_options=is_call_options,
          dtype=dtype,
      )
  
      # Some alterations depeneding on whether it is a call or put option:
      # https://www.researchgate.net/publication/243657048_A_Parity_Result_for_American_Options
      # For call options spots (s) and strikes (k) need to be swapped
      k = tf.where(is_call_options, s_temp, k_temp)
      s = tf.where(is_call_options, k_temp, s_temp)
      # For call options discount rates (r) and dividend rates (q) need to be
      # swapped
      r = tf.where(is_call_options, q_temp, r_temp)
      q = tf.where(is_call_options, r_temp, q_temp)
  
      # Shape [num_options, grid_num_points]
      tau_grid = tf.linspace(
          tau / grid_num_points, tau, grid_num_points, axis=-1
      )
      epsilon = machine_eps(dtype)
      # Where r == q == 0 the exercise boundary should be infinite in theory, but in
      # practice it is numerically unstable, therefore we set r == q == 0.1 just for
      # calculating the exercise boundary, so that it converges.
      # The exercise boundary will be irrelevant for the final result in this case.
      r_e_b = tf.where(
          tf.math.abs(r) < epsilon,
          tf.where(
              tf.math.abs(q) < epsilon, tf.constant(0.1, dtype=dtype), r
          ),
          r,
      )
      q_e_b = tf.where(
          tf.math.abs(q) < epsilon,
          tf.where(
              tf.math.abs(r) < epsilon, tf.constant(0.1, dtype=dtype), q
          ),
          q,
      )
      exercise_boundary_fn = calculate_exercise_boundary(
          tau_grid,
          k,
          r_e_b,
          q_e_b,
          sigma,
          max_iterations_exercise_boundary,
          tolerance_exercise_boundary,
          integration_num_points_legendre,
          dtype,
      )
      # Shape [num_options, 1, 1]
      k_exp = k[:, tf.newaxis, tf.newaxis]
      r_exp = r[:, tf.newaxis, tf.newaxis]
      q_exp = q[:, tf.newaxis, tf.newaxis]
      sigma_exp = sigma[:, tf.newaxis, tf.newaxis]
      s_exp = s[:, tf.newaxis, tf.newaxis]
      tau_exp = tau[:, tf.newaxis, tf.newaxis]
  
      def get_ratio(u):
          u_shape = utils.get_shape(u)
          # Shape [num_options, n * integration_num_points_kronrod]
          # Need reshaping because exercise_boundary_fn expects a 2-dimensional input.
          u_reshaped = tf.reshape(
              u, [u_shape[0], u_shape[1] * u_shape[2]]
          )
          # Shape [num_options, n, integration_num_points_kronrod]
          return divide_with_positive_denominator(
              s_exp,
              tf.reshape(
                  exercise_boundary_fn(u_reshaped),
                  [u_shape[0], u_shape[1], u_shape[2]],
              ),
          )
  
      def func_1(u):
          # Shape [num_options, n, integration_num_points_kronrod]
          ratio = get_ratio(u)
          norm = standard_normal_cdf(
              -d_minus(tau_exp - u, ratio, r_exp, q_exp, sigma_exp)
          )
          return (
              r_exp * k_exp * tf.math.exp(-r_exp * (tau_exp - u)) * norm
          )
  
      # Shape [num_options]
      term1 = gauss_kronrod(
          func=func_1,
          lower=tf.zeros_like(tau),
          upper=tau,
          tolerance=tolerance_kronrod,
          num_points=integration_num_points_kronrod,
          max_depth=max_depth_kronrod,
          dtype=dtype,
      )
  
      def func_2(u):
          # Shape [num_options, n, integration_num_points_kronrod]
          ratio = get_ratio(u)
          norm = standard_normal_cdf(
              -d_plus(tau_exp - u, ratio, r_exp, q_exp, sigma_exp)
          )
          return (
              q_exp * s_exp * tf.math.exp(-q_exp * (tau_exp - u)) * norm
          )
  
      # Shape [num_options]
      term2 = gauss_kronrod(
          func=func_2,
          lower=tf.zeros_like(tau),
          upper=tau,
          tolerance=tolerance_kronrod,
          num_points=integration_num_points_kronrod,
          max_depth=max_depth_kronrod,
          dtype=dtype,
      )
      # Shape [num_options]
      return eu_prices + term1 - term2
  
file_path: tf-quant-finance/tf_quant_finance/experimental/american_option_pricing/andersen_lake.py
imports: import tensorflow.compat.v2
  from tf_quant_finance import types
  from tf_quant_finance import utils
  from tf_quant_finance.black_scholes import vanilla_prices
  from tf_quant_finance.experimental.american_option_pricing import common
  from tf_quant_finance.experimental.american_option_pricing import exercise_boundary
  from tf_quant_finance.math.integration import gauss_kronrod
label_desc: Use image node_id 2 for calling a global function with example usage: _andersen_lake() and returns: unknown, divide_with_positive_denominator, unknown, unknown