Example #1
0
  def testFindsAnyRoots(self):
    objective_fn = lambda x: (63 * x**5 - 70 * x**3 + 15 * x + 2) / 8.

    left_bracket = [-10, 1]
    right_bracket = [10, -1]
    expected_num_iterations = [7, 6]

    expected_num_iterations, result = self.evaluate([
        tf.constant(expected_num_iterations, dtype=tf.int32),
        root_search.brentq(
            objective_fn,
            tf.constant(left_bracket, dtype=tf.float64),
            tf.constant(right_bracket, dtype=tf.float64),
            stopping_policy_fn=tf.reduce_any)
    ])

    roots, value_at_roots, num_iterations, _ = result

    expected_roots = [
        optimize.brentq(objective_fn, left_bracket[0], right_bracket[0]),
        optimize.brentq(objective_fn, left_bracket[1], right_bracket[1])
    ]

    self.assertNotAllClose(roots[0], expected_roots[0])
    self.assertAllClose(roots[1], expected_roots[1])

    self.assertNotAllClose(value_at_roots[0], 0.)
    self.assertAllClose(value_at_roots[0], objective_fn(roots[0]))
    self.assertAllClose(value_at_roots[1], 0.)

    self.assertAllEqual(num_iterations, expected_num_iterations)
Example #2
0
  def testFindsRootForFlatFunction(self):
    # Flat in the [-0.5, 0.5] range.
    objective_fn = lambda x: 0 if x == 0 else x * exp(-1 / x**2)

    left_bracket = [-10]
    right_bracket = [1]
    expected_num_iterations = [13]

    expected_num_iterations, result = self.evaluate([
        tf.constant(expected_num_iterations, dtype=tf.int32),
        root_search.brentq(objective_fn,
                           tf.constant(left_bracket, dtype=tf.float64),
                           tf.constant(right_bracket, dtype=tf.float64))
    ])

    _, value_at_roots, num_iterations, _ = result

    # Simply check that the objective function is close to the root for the
    # returned estimate. Do not check the estimate itself.
    # Unlike Brent's original algorithm (and the SciPy implementation), this
    # implementation stops the search as soon as a good enough root estimate is
    # found. As a result, the estimate may significantly differ from the one
    # returned by SciPy for functions which are extremely flat around the root.
    self.assertAllClose(value_at_roots, [0.])
    self.assertAllEqual(num_iterations, expected_num_iterations)
    def testWithValueAtPositionssOfSameSign(self):
        f = lambda x: x**2

        # Should fail: The objective function has the same sign at both positions.
        with self.assertRaises(tf.errors.InvalidArgumentError):
            self.evaluate(
                root_search.brentq(f,
                                   tf.constant(-1, dtype=tf.float64),
                                   tf.constant(1, dtype=tf.float64),
                                   validate_args=True))
Example #4
0
    def body(right_bracket, converged, t_star):
        t_star, value_at_t_star, num_iterations, converged = root_search.brentq(
            fixed_point_function, left_bracket, right_bracket, None, None,
            2e-12)

        t_star = t_star - value_at_t_star

        right_bracket = right_bracket * tf.constant(2.0, ztypes.float)

        return right_bracket, converged, t_star
    def testWithInvalidMaxIterations(self):
        f = lambda x: x**3

        # Should fail: Maximum number of iterations is negative.
        with self.assertRaises(tf.errors.InvalidArgumentError):
            self.evaluate(
                root_search.brentq(f,
                                   tf.constant(-1, dtype=tf.float64),
                                   tf.constant(1, dtype=tf.float64),
                                   max_iterations=-1,
                                   validate_args=True))
    def testWithInvalidValueTolerance(self):
        f = lambda x: x**3

        # Should fail: Value tolerance is negative.
        with self.assertRaises(tf.errors.InvalidArgumentError):
            self.evaluate(
                root_search.brentq(f,
                                   tf.constant(-1, dtype=tf.float64),
                                   tf.constant(1, dtype=tf.float64),
                                   function_tolerance=-2e-7,
                                   validate_args=True))
    def testWithInvalidAbsoluteRootTolerance(self):
        f = lambda x: x**3

        # Should fail: Absolute root tolerance is negative.
        with self.assertRaises(tf.errors.InvalidArgumentError):
            self.evaluate(
                root_search.brentq(f,
                                   tf.constant(-2, dtype=tf.float64),
                                   tf.constant(2, dtype=tf.float64),
                                   absolute_root_tolerance=-2e-7,
                                   validate_args=True))
Example #8
0
  def _testFindsAllRoots(self,
                         objective_fn,
                         left_bracket,
                         right_bracket,
                         expected_num_iterations,
                         dtype=tf.float64,
                         absolute_root_tolerance=2e-7,
                         relative_root_tolerance=None,
                         function_tolerance=2e-7):
    assert len(left_bracket) == len(
        right_bracket), "Brackets have different sizes"

    if relative_root_tolerance is None:
      relative_root_tolerance = root_search.default_relative_root_tolerance(
          dtype)

    expected_num_iterations, result = self.evaluate([
        tf.constant(expected_num_iterations, dtype=tf.int32),
        root_search.brentq(
            objective_fn,
            tf.constant(left_bracket, dtype=dtype),
            tf.constant(right_bracket, dtype=dtype),
            absolute_root_tolerance=absolute_root_tolerance,
            relative_root_tolerance=relative_root_tolerance,
            function_tolerance=function_tolerance)
    ])

    roots, value_at_roots, num_iterations, converged = result

    expected_roots = []
    for i in range(0, len(left_bracket)):
      expected_roots.append(
          optimize.brentq(
              objective_fn,
              left_bracket[i],
              right_bracket[i],
              xtol=absolute_root_tolerance,
              rtol=relative_root_tolerance))

    zeros = [0.] * len(left_bracket)

    # The output of SciPy and Tensorflow implementation should match for
    # well-behaved functions.
    self.assertAllClose(
        roots,
        expected_roots,
        atol=2 * absolute_root_tolerance,
        rtol=2 * relative_root_tolerance)
    self.assertAllClose(value_at_roots, zeros, atol=10 * function_tolerance)
    self.assertAllEqual(num_iterations, expected_num_iterations)
    self.assertAllEqual(
        converged,
        [abs(value) <= function_tolerance for value in value_at_roots])
    def _testFindsAllRoots(self,
                           objective_fn,
                           left_bracket,
                           right_bracket,
                           expected_roots,
                           expected_num_iterations,
                           dtype=tf.float64,
                           absolute_root_tolerance=2e-7,
                           relative_root_tolerance=None,
                           function_tolerance=2e-7,
                           assert_check_for_num_iterations=True):
        # expected_roots are pre-calculated as follows:
        # import scipy.optimize as optimize
        # roots = optimize.brentq(objective_fn,
        #                         left_bracket[i],
        #                         right_bracket[i],
        #                         xtol=absolute_root_tolerance,
        #                         rtol=relative_root_tolerance)

        assert len(left_bracket) == len(
            right_bracket), "Brackets have different sizes"

        if relative_root_tolerance is None:
            relative_root_tolerance = root_search.default_relative_root_tolerance(
                dtype)

        expected_num_iterations, result = self.evaluate([
            tf.constant(expected_num_iterations, dtype=tf.int32),
            root_search.brentq(objective_fn,
                               tf.constant(left_bracket, dtype=dtype),
                               tf.constant(right_bracket, dtype=dtype),
                               absolute_root_tolerance=absolute_root_tolerance,
                               relative_root_tolerance=relative_root_tolerance,
                               function_tolerance=function_tolerance)
        ])

        roots, value_at_roots, num_iterations, converged = result
        zeros = [0.] * len(left_bracket)

        # The output of SciPy and Tensorflow implementation should match for
        # well-behaved functions.
        self.assertAllClose(roots,
                            expected_roots,
                            atol=2 * absolute_root_tolerance,
                            rtol=2 * relative_root_tolerance)
        self.assertAllClose(value_at_roots,
                            zeros,
                            atol=10 * function_tolerance)
        if assert_check_for_num_iterations:
            self.assertAllEqual(num_iterations, expected_num_iterations)
        self.assertAllEqual(
            converged,
            [abs(value) <= function_tolerance for value in value_at_roots])
Example #10
0
  def testWithNoIteration(self):
    left_bracket = [-10, 1]
    right_bracket = [10, -1]

    first_guess = tf.constant(left_bracket, dtype=tf.float64)
    second_guess = tf.constant(right_bracket, dtype=tf.float64)

    # Skip iteration entirely.
    # Should return a Tensor built from the best guesses in input positions.
    guess, result = self.evaluate([
        tf.constant([-10, -1], dtype=tf.float64),
        root_search.brentq(
            polynomial5, first_guess, second_guess, max_iterations=0)
    ])

    self.assertAllEqual(result.estimated_root, guess)
Example #11
0
  def testFindsAllRootsUsingFloat16(self):
    left_bracket = [-2, 1]
    right_bracket = [2, -1]
    expected_num_iterations = [9, 4]

    expected_num_iterations, result = self.evaluate([
        tf.constant(expected_num_iterations, dtype=tf.int32),
        root_search.brentq(polynomial5,
                           tf.constant(left_bracket, dtype=tf.float16),
                           tf.constant(right_bracket, dtype=tf.float16))
    ])

    _, value_at_roots, num_iterations, _ = result

    # Simply check that the objective function is close to the root for the
    # returned estimates. Do not check the estimates themselves.
    # Using float16 may yield root estimates which differ from those returned
    # by the SciPy implementation.
    self.assertAllClose(value_at_roots, [0., 0.], atol=1e-3)
    self.assertAllEqual(num_iterations, expected_num_iterations)
Example #12
0
def find_practical_support_bandwidth(kernel, bandwidth, absolute_tolerance=10e-5):
    """
    Return the support for practical purposes. Used to find a support value
    for computations for kernel functions without finite (bounded) support.
    """
    absolute_root_tolerance = 1e-3
    relative_root_tolerance = root_search.default_relative_root_tolerance(ztypes.float)
    function_tolerance = 0
    
    kernel_instance = kernel(loc=0, scale=bandwidth)

    def objective_fn(x): 
        return kernel_instance.prob(x) - tf.constant(absolute_tolerance, ztypes.float)

    roots, value_at_roots, num_iterations, converged = root_search.brentq(
        objective_fn,
        tf.constant(0.0, dtype=ztypes.float),
        tf.constant(8.0, dtype=ztypes.float) * bandwidth,
        absolute_root_tolerance=absolute_root_tolerance,
        relative_root_tolerance=relative_root_tolerance,
        function_tolerance=function_tolerance
    )

    return roots + absolute_root_tolerance
Example #13
0
def _jamshidian_decomposition(hw_model,
                              expiries,
                              maturities,
                              coefficients,
                              dtype,
                              name=None):
  """Jamshidian decomposition for European swaption valuation.

  Jamshidian decomposition is a widely used technique for the valuation of
  European swaptions (and options on coupon bearing bonds) when the underlying
  models for the term structure are short rate models (such as Hull-White
  model). The method transforms the swaption valuation to the valuation of a
  portfolio of call (put) options on zero-coupon bonds.

  Consider the following swaption payoff(assuming unit notional) at the
  exipration time (under a single curve valuation):

  ```None
  payoff = max(1 - P(T0, TN, r(T0)) - sum_1^N tau_i * X_i * P(T0, Ti, r(T0)), 0)
         = max(1 - sum_0^N alpha_i * P(T0, Ti, r(T0)), 0)
  ```

  where `T0` denotes the swaption expiry, P(T0, Ti, r(T0)) denotes the price
  of the zero coupon bond at `T0` with maturity `Ti` and `r(T0)` is the short
  rate at time `T0`. If `r*` (or breakeven short rate) is the solution of the
  following equation:

  ```None
  1 - sum_0^N alpha_i * P(T0, Ti, r*) = 0            (1)
  ```

  Then the swaption payoff can be expressed as the following (Ref. [1]):

  ```None
  payoff = sum_1^N alpha_i max(P(T0, Ti, r*) - P(T0, Ti), 0)
  ```
  where in the above formulation the swaption payoff is the same as that of
  a portfolio of bond options with strikes `P(T0, Ti, r*)`.

  The function accepts relevant inputs for the above computation and returns
  the strikes of the bond options computed using the Jamshidian decomposition.

  #### References:
    [1]: Leif B. G. Andersen and Vladimir V. Piterbarg. Interest Rate Modeling.
    Volume II: Term Structure Models. Chapter 10.


  Args:
    hw_model: An instance of `VectorHullWhiteModel`. The model used for the
      valuation.
    expiries: A real `Tensor` of any shape and dtype. The the time to
      expiration of the swaptions.
    maturities: A real `Tensor` of same shape and dtype as `expiries`. The
      payment times for fixed payments of the underlying swaptions.
    coefficients: A real `Tensor` of shape `expiries.shape + [n]` where `n`
      denotes the number of payments in the fixed leg of the underlying swaps.
    dtype: The default dtype to use when converting values to `Tensor`s.
    name: Python string. The name to give to the ops created by this function.
      Default value: `None` which maps to the default name
      `jamshidian_decomposition`.

  Returns:
    A real `Tensor` of shape expiries.shape + [dim] containing the forward
    bond prices computed at the breakeven short rate using the Jamshidian
    decomposition. `dim` stands for the dimensionality of the Hull-White
    process.
  """

  name = name or 'jamshidian_decomposition'
  with tf.name_scope(name):
    dim = hw_model.dim()
    coefficients = tf.expand_dims(coefficients, axis=-1)

    def _zero_fun(x):
      # Get P(t0, t, r(t0)).
      p_t0_t = hw_model.discount_bond_price(x, expiries, maturities)
      # return_value.shape = batch_shape + [1] + [dim]
      return_value = tf.reduce_sum(
          coefficients * p_t0_t, axis=-2, keepdims=True) + [1.0]
      return return_value

    swap_shape = expiries.shape.as_list()[:-1] + [1] + [dim]
    lower_bound = -1 * tf.ones(swap_shape, dtype=dtype)
    upper_bound = 1 * tf.ones(swap_shape, dtype=dtype)
    # Solve Eq.(1)
    brent_results = root_search.brentq(_zero_fun, lower_bound, upper_bound)
    breakeven_short_rate = brent_results.estimated_root
    return hw_model.discount_bond_price(breakeven_short_rate, expiries,
                                        maturities)