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: Computes discount bond prices using Eq. 10.18 in Ref [2].
docstr_tok: ["Computes","discount","bond","prices","using","Eq",".","10.18","in","Ref","[","2","]","."]
code: def _bond_reconstitution(
      self,
      times,
      maturities,
      mean_reversion,
      x_t,
      y_t,
      num_samples,
      num_times,
  ):
      """Computes discount bond prices using Eq. 10.18 in Ref [2]."""
      times_expand = tf.expand_dims(times, axis=-1)
      maturities_expand = tf.expand_dims(maturities, axis=-1)
      # p_0_t.shape = (batch_size, 1, 1, num_sim_steps, 1)
      p_0_t = tf.math.exp(
          -self._initial_discount_rate_fn(times) * times
      )
      # p_0_t_tau.shape = (batch_size, 1, num_curve_times, num_sim_steps, 1)
      p_0_t_tau = (
          tf.math.exp(
              -self._initial_discount_rate_fn(maturities) * maturities
          )
          / p_0_t
      )
      # g_t_tau.shape = (batch_size, 1, num_curve_times, num_sim_steps, 1)
      g_t_tau = (
          1.0
          - tf.math.exp(
              -mean_reversion * (maturities_expand - times_expand)
          )
      ) / mean_reversion
      # term1.shape = (batch_size, num_samples, num_curve_times, num_sim_steps)
      term1 = tf.math.reduce_sum(x_t * g_t_tau, axis=-1)
      # y_t: (batch_size, num_samples, 1, num_times, nfactors**2) ->
      # (batch_size, num_samples, 1, num_times, nfactors, nfactors)
      y_t = tf.reshape(
          y_t,
          self._batch_shape
          + [num_samples, 1, num_times, self._factors, self._factors],
      )
      # now compute g_t_tau * y_t * g_t_tau
      # term2.shape = (batch_size, num_samples, num_curve_times, num_sim_steps)
      term2 = tf.math.reduce_sum(
          g_t_tau * tf.linalg.matvec(y_t, g_t_tau), axis=-1
      )
      p_t_tau = p_0_t_tau * tf.math.exp(-term1 - 0.5 * term2)
      # p_t_tau.shape=(batch_size, num_samples, num_curve_times, num_sim_steps)
      return p_t_tau
  
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 5 for calling the QuasiGaussianHJM obj's underlying member method code with example usage: obj._bond_reconstitution(times, maturities, mean_reversion, x_t, y_t, num_samples, num_times) and returns: p_t_tau