code: def least_square_mc(
      sample_paths: types.RealTensor,
      exercise_times: types.IntTensor,
      payoff_fn: Callable[..., types.RealTensor],
      basis_fn: Callable[..., types.RealTensor],
      discount_factors: types.RealTensor = None,
      num_calibration_samples: int = None,
      dtype: tf.DType = None,
      name: str = None,
  ) -> types.RealTensor:
      """Values Amercian style options using the LSM algorithm.
  
      The Least-Squares Monte-Carlo (LSM) algorithm is a Monte-Carlo approach to
      valuation of American style options. Using the sample paths of underlying
      assets, and a user supplied payoff function it attempts to find the optimal
      exercise point along each sample path. With optimal exercise points known,
      the option is valued as the average payoff assuming optimal exercise
      discounted to present value.
  
      #### Example. American put option price through Monte Carlo
      ```python
      # Let the underlying model be a Black-Scholes process
      # dS_t / S_t = rate dt + sigma**2 dW_t, S_0 = 1.0
      # with `rate = 0.1`, and volatility `sigma = 1.0`.
      # Define drift and volatility functions for log(S_t)
      rate = 0.1
      def drift_fn(_, x):
        return rate - tf.ones_like(x) / 2.
      def vol_fn(_, x):
        return tf.expand_dims(tf.ones_like(x), axis=-1)
      # Use Euler scheme to propagate 100000 paths for 1 year into the future
      times = np.linspace(0., 1, num=50)
      num_samples = 100000
      log_paths = tf.function(tff.models.euler_sampling.sample)(
              dim=1,
              drift_fn=drift_fn, volatility_fn=vol_fn,
              random_type=tff.math.random.RandomType.PSEUDO_ANTITHETIC,
              times=times, num_samples=num_samples, seed=42, time_step=0.01)
      # Compute exponent to get samples of `S_t`
      paths = tf.math.exp(log_paths)
      # American put option price for strike 1.1 and expiry 1 (assuming actual day
      # count convention and no settlement adjustment)
      strike = [1.1]
      exercise_times = tf.range(times.shape[-1])
      discount_factors = tf.exp(-rate * times)
      payoff_fn = make_basket_put_payoff(strike)
      basis_fn = make_polynomial_basis(10)
      least_square_mc(paths, exercise_times, payoff_fn, basis_fn,
                      discount_factors=discount_factors)
      # Expected value: [0.397]
      # European put option price
      tff.black_scholes.option_price(volatilities=[1], strikes=strikes,
                                     expiries=[1], spots=[1.],
                                     discount_factors=discount_factors[-1],
                                     is_call_options=False,
                                     dtype=tf.float64)
      # Expected value: [0.379]
      ```
      #### References
  
      [1] Longstaff, F.A. and Schwartz, E.S., 2001. Valuing American options by
      simulation: a simple least-squares approach. The review of financial studies,
      14(1), pp.113-147.
  
      Args:
        sample_paths: A `Tensor` of either shape `[num_samples, num_times, dim]` or
          `[batch_size, num_samples, num_times, dim]`, the sample paths of the
          underlying ito process of dimension `dim` at `num_times` different points.
          The `batch_size` allows multiple options to be valued in parallel.
        exercise_times: An `int32` `Tensor` of shape `[num_exercise_times]`.
          Contents must be a subset of the integers `[0,...,num_times - 1]`,
          representing the time indices at which the option may be exercised.
        payoff_fn: Callable from a `Tensor` of shape `[num_samples, S, dim]`
          (where S <= num_times) to a `Tensor` of shape `[num_samples, batch_size]`
          of the same dtype as `samples`. The output represents the payout resulting
          from exercising the option at time `S`. The `batch_size` allows multiple
          options to be valued in parallel.
        basis_fn: Callable from a `Tensor` of the same shape and `dtype` as
          `sample_paths` and a positive integer `Tenor` (representing a current
          time index) to a `Tensor` of shape `[batch_size, basis_size, num_samples]`
          of the same dtype as `sample_paths`. The result being the design matrix
          used in regression of the continuation value of options.
        discount_factors: A `Tensor` of shape `[num_exercise_times]` or of rank 3
          and compatible with `[num_samples, batch_size, num_exercise_times]`.
          The `dtype` should be the same as of `samples`.
          Default value: `None` which maps to a one-`Tensor` of the same `dtype`
            as `samples` and shape `[num_exercise_times]`.
        num_calibration_samples: An optional integer less or equal to `num_samples`.
          The number of sampled trajectories used for the LSM regression step.
          Note that only the last`num_samples - num_calibration_samples` of the
          sampled paths are used to determine the price of the option.
          Default value: `None`, which means that all samples are used for
            regression and option pricing.
        dtype: Optional `dtype`. Either `tf.float32` or `tf.float64`. The `dtype`
          If supplied, represents the `dtype` for the input and output `Tensor`s.
          Default value: `None`, which means that the `dtype` inferred by TensorFlow
          is used.
        name: Python `str` name prefixed to Ops created by this function.
          Default value: `None` which is mapped to the default name
          'least_square_mc'.
  
      Returns:
        A `Tensor` of shape `[num_samples, batch_size]` of the same dtype as
        `samples`.
      """
      name = name or "least_square_mc"
      with tf.name_scope(name):
          # Conversion of the inputs to tensors
          sample_paths = tf.convert_to_tensor(
              sample_paths, dtype=dtype, name="sample_paths"
          )
          dtype = sample_paths.dtype
          exercise_times = tf.convert_to_tensor(
              exercise_times, name="exercise_times"
          )
          num_times = tf.shape(exercise_times)[-1]
          if discount_factors is None:
              discount_factors = tf.ones(
                  shape=exercise_times.shape,
                  dtype=dtype,
                  name="discount_factors",
              )
          else:
              discount_factors = tf.convert_to_tensor(
                  discount_factors, dtype=dtype, name="discount_factors"
              )
          if discount_factors.shape.rank == 0:
              discount_factors = tf.reshape(discount_factors, [1, 1, 1])
          if discount_factors.shape.rank == 1:
              discount_factors = tf.reshape(
                  discount_factors, [1, 1, -1]
              )
          elif discount_factors.shape.rank == 2:
              discount_factors = tf.reshape(discount_factors, [1, -1])
          discount_factors = tf.pad(
              discount_factors,
              2 * [[0, 0]] + [[1, 0]],
              constant_values=1,
          )
          # Shape [num_exercise_times + 1, num_samples, batch_size]
          discount_factors = tf.transpose(discount_factors, [2, 0, 1])
          # Initialise cashflow as the payoff at final sample.
          time_index = exercise_times[num_times - 1]
          # Calculate the payoff of each path if exercised now. Shape
          # [num_samples, batch_size]
          exercise_value = payoff_fn(sample_paths, time_index)
  
          # Initialize option values at the terminal time.
          # Shape [num_samples, batch_size]
          initial_values = tf.zeros_like(exercise_value)
          calibration_indices = None
          if num_calibration_samples is not None:
              # Calibration indices to perform regression
              calibration_indices = tf.range(num_calibration_samples)
          # Starting state for loop iteration.
          lsm_loop_vars = LsmLoopVars(
              exercise_index=num_times - 1,
              cashflow=exercise_value,
              values=initial_values,
          )
  
          def loop_body(exercise_index, cashflow, option_values):
              return _lsm_loop_body(
                  sample_paths=sample_paths,
                  exercise_times=exercise_times,
                  discount_factors=discount_factors,
                  payoff_fn=payoff_fn,
                  basis_fn=basis_fn,
                  exercise_index=exercise_index,
                  cashflow=cashflow,
                  option_values=option_values,
                  calibration_indices=calibration_indices,
              )
  
          # Number of iterations of the algorithm
          num_iterations = tf.shape(sample_paths)[-2]
          loop_value = tf.while_loop(
              _lsm_loop_cond,
              loop_body,
              lsm_loop_vars,
              maximum_iterations=num_iterations,
          )
          present_values = _apply_discount(
              loop_value.cashflow + loop_value.values,
              discount_factors,
              0,
          )
          if num_calibration_samples is not None:
              # Skip num_calibration_samples to reduce bias
              present_values = present_values[num_calibration_samples:]
          return tf.math.reduce_mean(present_values, axis=0)
  
file_path: tf-quant-finance/tf_quant_finance/models/longstaff_schwartz/lsm.py
imports: from typing import Callable
  import tensorflow.compat.v2
  from tf_quant_finance import types
  from tf_quant_finance import utils
label_desc: Use image node_id 2 for calling a global function with example usage: least_square_mc(sample_paths, exercise_times, payoff_fn, basis_fn, discount_factors, num_calibration_samples, dtype, name) and returns: tf, _lsm_loop_body