class_docstr: Quasi-Gaussian HJM model for term-structure modeling.
  
  Heath-Jarrow-Morton (HJM) model for the interest rate term-structre
  modelling specifies the dynamics of the instantaneus forward rate `f(t,T)`
  with maturity `T` at time `t` as follows:
  
  ```None
    df(t,T) = mu(t,T) dt + sum_i sigma_i(t,  T) * dW_i(t),
    1 <= i <= n,
  ```
  where `mu(t,T)` and `sigma_i(t,T)` denote the drift and volatility
  for the forward rate and `W_i` are Brownian motions with instantaneous
  correlation `Rho`. The model above represents an `n-factor` HJM model. Under
  the risk-neutral measure, the drift `mu(t,T)` is computed as
  
  ```
    mu(t,T) = sum_i sigma_i(t,T)  int_t^T sigma_(t,u) du
  ```
  Using the separability condition, the HJM model above can be formulated as
  the following Markovian model:
  
  ```None
    sigma(t,T) = sigma(t) * h(T)    (Separability condition)
  ```
  A common choice for the function h(t) is `h(t) = exp(-kt)`. Using the above
  parameterization of sigma(t,T), we obtain the following Markovian
  formulation of the HJM model [1]:
  
  ```None
    HJM Model
    dx_i(t) = (sum_j [y_ij(t)] - k_i * x_i(t)) dt + sigma_i(t) dW_i
    dy_ij(t) = (rho_ij * sigma_i(t)*sigma_j(t) - (k_i + k_j) * y_ij(t)) dt
    r(t) = sum_i x_i(t) + f(0, t)
  ```
  where `x` is an `n`-dimensional vector and `y` is an `nxn` dimensional
  matrix. The HJM class implements the model outlined above by jointly
  simulating the state [x_t, y_t].
  
  The price at time `t` of a zero-coupon bond maturing at `T` is given by
  (Ref. [1]):
  
  ```None
  P(t,T) = P(0,T) / P(0,t) *
           exp(-x(t) * G(t,T) - 0.5 * y(t) * G(t,T)^2)
  ```
  
  The HJM model implentation supports constant mean-reversion rate `k` and
  `sigma(t)` can be an arbitrary function of `t` and `r`. The implementation
  uses Euler discretization to simulate the HJM model.
  
  #### Example. Simulate a 4-factor HJM process.
  
  ```python
  import numpy as np
  import tensorflow.compat.v2 as tf
  import tf_quant_finance as tff
  
  dtype = tf.float64
  def discount_fn(x):
    return 0.01 * tf.ones_like(x, dtype=dtype)
  
  process = tff.models.hjm.QuasiGaussianHJM(
      dim=4,
      mean_reversion=[0.03, 0.01, 0.02, 0.005],  # constant mean-reversion
      volatility=[0.01, 0.011, 0.015, 0.008],  # constant volatility
      initial_discount_rate_fn=discount_fn,
      dtype=dtype)
  times = np.array([0.1, 1.0, 2.0, 3.0])
  short_rate_paths, discount_paths, _, _ = process.sample_paths(
      times,
      num_samples=100000,
      time_step=0.1,
      random_type=tff.math.random.RandomType.STATELESS_ANTITHETIC,
      seed=[1, 2],
      skip=1000000)
  ```
  
  #### References:
    [1]: Leif B. G. Andersen and Vladimir V. Piterbarg. Interest Rate Modeling.
    Volume II: Term Structure Models.
docstr: Returns a sample of paths from the process.
docstr_tok: ["Returns","a","sample","of","paths","from","the","process","."]
code: def _sample_paths(
      self,
      times,
      time_step,
      num_time_steps,
      num_samples,
      random_type,
      skip,
      seed,
  ):
      """Returns a sample of paths from the process."""
      # Initial state should be broadcastable to batch_shape + [num_samples, dim]
      initial_state = tf.zeros(
          self._batch_shape + [1, self._dim], dtype=self._dtype
      )
      # Note that we need a finer simulation grid (determnied by `dt`) to compute
      # discount factors accurately. The `times` input might not be granular
      # enough for accurate calculations.
      time_step_internal = time_step
      if num_time_steps is not None:
          num_time_steps = tf.convert_to_tensor(
              num_time_steps, dtype=tf.int32, name="num_time_steps"
          )
          time_step_internal = times[-1] / tf.cast(
              num_time_steps, dtype=self._dtype
          )
  
      times, _, time_indices = utils.prepare_grid(
          times=times,
          time_step=time_step_internal,
          dtype=self._dtype,
          num_time_steps=num_time_steps,
      )
      # Add zeros as a starting location
      dt = times[1:] - times[:-1]
  
      # xy_paths.shape = (num_samples, num_times, nfactors+nfactors^2)
      xy_paths = euler_sampling.sample(
          self._dim,
          self._drift_fn,
          self._volatility_fn,
          times,
          num_samples=num_samples,
          initial_state=initial_state,
          random_type=random_type,
          seed=seed,
          time_step=time_step,
          num_time_steps=num_time_steps,
          skip=skip,
      )
  
      x_paths = xy_paths[..., : self._factors]
      y_paths = xy_paths[..., self._factors :]
  
      # shape=(batch_shape, num_times)
      f_0_t = self._instant_forward_rate_fn(times)
      # shape=(batch_shape, num_samples, num_times)
      rate_paths = tf.math.reduce_sum(
          x_paths, axis=-1
      ) + tf.expand_dims(f_0_t, axis=-2)
  
      dt = tf.concat(
          [tf.convert_to_tensor([0.0], dtype=self._dtype), dt], axis=0
      )
      discount_factor_paths = tf.math.exp(
          -utils.cumsum_using_matvec(rate_paths * dt)
      )
      return (
          tf.gather(rate_paths, time_indices, axis=-1),
          tf.gather(discount_factor_paths, time_indices, axis=-1),
          tf.gather(x_paths, time_indices, axis=self._batch_rank + 1),
          tf.gather(y_paths, time_indices, axis=self._batch_rank + 1),
      )
  
file_path: tf-quant-finance/tf_quant_finance/models/hjm/quasi_gaussian_hjm.py
imports: from typing import Callable, Union, Tuple
  import tensorflow.compat.v2
  from tf_quant_finance import types
  from tf_quant_finance.math import gradient
  from tf_quant_finance.math import random
  from tf_quant_finance.models import euler_sampling
  from tf_quant_finance.models import generic_ito_process
  from tf_quant_finance.models import utils
label_desc: Use image node_id 4 for calling the QuasiGaussianHJM obj's underlying member method code with example usage: obj._sample_paths(times, time_step, num_time_steps, num_samples, random_type, skip, seed) and returns: tf,  tf,  tf,  tf