Esempio n. 1
0
 def decorator(*args, **kwargs):
     inputs = []
     for real, imag in zip(args[::2], args[1::2]):
         inputs.append(tf.complex(real, imag))
     result = function(*inputs, **kwargs)
     # TODO(meadowlark): Support returning complex numbers.
     return tf.math.real(result) + tf.math.imag(result)
Esempio n. 2
0
def _amplitude(mass, l, energy, decay_width_0):
    """
    Calculate relativistic Breit-Wigner amplitude at given mass

    Args:
        mass (tensor): Rank-0 tensor of mass
        l (int): Angular momentum (1 for P-wave, 0 for S-wave)
        energy (tensor): Rank-0 tensor of parent K mass
        decay_width_0 (tensor): Rank-0 tensor of parent K particle with no mass dependence
    """
    def _q(_mass):  # Momentum of K+/Pi- daughters in rest frame of resonance
        return (tf.sqrt(
            ((_mass**2) - ((mass_k_plus + mass_pi_minus)**2)) *
            ((_mass**2) - ((mass_k_plus - mass_pi_minus)**2)))) / (2.0 * _mass)

    q = _q(mass)
    q_0 = _q(energy)

    if l == 1:
        # P-wave
        r = tf.constant(4.0)  # Radius of hadron. 4 Gev^-1 / ~ 0.8 fm
        z = tf.math.abs(q) * r
        z_0 = tf.math.abs(q_0) * r
        barrier = tf.sqrt((1.0 + (z_0**2)) / (1.0 + (z**2)))
    elif l == 0:
        # S-wave
        barrier = tf.constant(1.0)
    else:
        raise ValueError('l must be 0 or 1. Found: {}'.format(l))

    decay_width_mass_dependent = decay_width_0 * ((q / q_0)**(
        (2 * l) + 1)) * (energy / mass) * (barrier**2)

    return 1.0 / tf.complex(
        (energy**2) - (mass**2), -energy * decay_width_mass_dependent)
Esempio n. 3
0
def frequency_impulse_response(magnitudes: tf.Tensor,
                               window_size: int = 0) -> tf.Tensor:
    """Get windowed impulse responses using the frequency sampling method.

  Follows the approach in:
  https://ccrma.stanford.edu/~jos/sasp/Windowing_Desired_Impulse_Response.html

  Args:
    magnitudes: Frequency transfer curve. Float32 Tensor of shape [batch,
      n_frames, n_frequencies] or [batch, n_frequencies]. The frequencies of the
      last dimension are ordered as [0, f_nyqist / (n_frames -1), ...,
      f_nyquist], where f_nyquist is (sample_rate / 2). Automatically splits the
      audio into equally sized frames to match frames in magnitudes.
    window_size: Size of the window to apply in the time domain. If window_size
      is less than 1, it defaults to the impulse_response size.

  Returns:
    impulse_response: Time-domain FIR filter of shape
      [batch, frames, window_size] or [batch, window_size].

  Raises:
    ValueError: If window size is larger than fft size.
  """
    # Get the IR (zero-phase form).
    magnitudes = tf.complex(magnitudes, tf.zeros_like(magnitudes))
    impulse_response = tf.signal.irfft(magnitudes)

    # Window and put in causal form.
    impulse_response = apply_window_to_impulse_response(
        impulse_response, window_size)

    return impulse_response
Esempio n. 4
0
 def char_fun(u):
     # Using 'second formula' for the (first) characteristic function of
     # log( spot_T / forwards )
     # (noted 'phi_2' in 'The Little Heston Trap', (Albrecher))
     u_real = tf.complex(u, tf.zeros_like(u))
     u_imag = tf.complex(tf.zeros_like(u), u)
     s = rhos_real * sigmas_real * u_imag
     # TODO(b/156221007): investigate why s_kappa = (s - kappas_real)**2 leads
     # to a wrong result in graph mode.
     s_kappa = (s - kappas_real) * s - (s - kappas_real) * kappas_real
     d = s_kappa - sigmas_real**2 * (-u_imag - u_real**2)
     d = tf.math.sqrt(d)
     g = (kappas_real - s - d) / (kappas_real - s + d)
     a = kappas_real * thetas_real
     h = g * tf.math.exp(-d * expiries_real)
     m = 2 * tf.math.log((1 - h) / (1 - g))
     c = (a / sigmas_real**2) * (
         (kappas_real - s - d) * expiries_real - m)
     e = (1 - tf.math.exp(-d * expiries_real))
     d_new = (kappas_real - s - d) / sigmas_real**2 * (e / (1 - h))
     return tf.math.exp(c + d_new * variances_real)
 def test_trapezoid_supports_complex64(self):
     """
     Check that trapezoid() supports integrals that return tf.complex64
     """
     actual = bmfi.trapezoid(
         lambda x: tf.complex(x + 3.0, x + 4.0),
         -5.0,
         5.0,
         0.1,
     )
     self.assertEqual(tf.complex64, actual.dtype,
                      'Check returned dtype is complex64')
     # Check within tolerance as we're using bins
     nt.assert_allclose(30.0,
                        tf.math.real(actual).numpy(),
                        atol=1e-4,
                        rtol=0.001)
     nt.assert_allclose(40.0,
                        tf.math.imag(actual).numpy(),
                        atol=1e-4,
                        rtol=0.001)
Esempio n. 6
0
def get_spectral_matrix(n,
                        num_spec_bins=256,
                        use_mel_scale=True,
                        sample_rate=24000):
    """DFT matrix in overcomplete basis returned as a TF tensor.

  Args:
    n: Int. Frame length for the spectral matrix.
    num_spec_bins: Int. Number of bins to use in the spectrogram
    use_mel_scale: Bool. Equally spaced on Mel-scale or Hertz-scale?
    sample_rate: Int. Sample rate of the waveform audio.

  Returns:
    Constructed spectral matrix.
  """
    sample_rate = float(sample_rate)
    upper_edge_hertz = sample_rate / 2.
    lower_edge_hertz = sample_rate / n

    if use_mel_scale:
        upper_edge_mel = hertz_to_mel(upper_edge_hertz)
        lower_edge_mel = hertz_to_mel(lower_edge_hertz)
        mel_frequencies = tf.linspace(lower_edge_mel, upper_edge_mel,
                                      num_spec_bins)
        hertz_frequencies = mel_to_hertz(mel_frequencies)
    else:
        hertz_frequencies = tf.linspace(lower_edge_hertz, upper_edge_hertz,
                                        num_spec_bins)

    time_col_vec = (tf.reshape(tf.range(n, dtype=tf.float32), [n, 1]) *
                    np.cast[np.float32](2. * np.pi / sample_rate))
    tmat = tf.reshape(hertz_frequencies, [1, num_spec_bins]) * time_col_vec
    dct_mat = tf.math.cos(tmat)
    dst_mat = tf.math.sin(tmat)
    dft_mat = tf.complex(real=dct_mat, imag=-dst_mat)
    return dft_mat
Esempio n. 7
0
def auto_correlation(x,
                     axis=-1,
                     max_lags=None,
                     center=True,
                     normalize=True,
                     name='auto_correlation'):
    """Auto correlation along one axis.

  Given a `1-D` wide sense stationary (WSS) sequence `X`, the auto correlation
  `RXX` may be defined as  (with `E` expectation and `Conj` complex conjugate)

  ```
  RXX[m] := E{ W[m] Conj(W[0]) } = E{ W[0] Conj(W[-m]) },
  W[n]   := (X[n] - MU) / S,
  MU     := E{ X[0] },
  S**2   := E{ (X[0] - MU) Conj(X[0] - MU) }.
  ```

  This function takes the viewpoint that `x` is (along one axis) a finite
  sub-sequence of a realization of (WSS) `X`, and then uses `x` to produce an
  estimate of `RXX[m]` as follows:

  After extending `x` from length `L` to `inf` by zero padding, the auto
  correlation estimate `rxx[m]` is computed for `m = 0, 1, ..., max_lags` as

  ```
  rxx[m] := (L - m)**-1 sum_n w[n + m] Conj(w[n]),
  w[n]   := (x[n] - mu) / s,
  mu     := L**-1 sum_n x[n],
  s**2   := L**-1 sum_n (x[n] - mu) Conj(x[n] - mu)
  ```

  The error in this estimate is proportional to `1 / sqrt(len(x) - m)`, so users
  often set `max_lags` small enough so that the entire output is meaningful.

  Note that since `mu` is an imperfect estimate of `E{ X[0] }`, and we divide by
  `len(x) - m` rather than `len(x) - m - 1`, our estimate of auto correlation
  contains a slight bias, which goes to zero as `len(x) - m --> infinity`.

  Args:
    x:  `float32` or `complex64` `Tensor`.
    axis:  Python `int`. The axis number along which to compute correlation.
      Other dimensions index different batch members.
    max_lags:  Positive `int` tensor.  The maximum value of `m` to consider (in
      equation above).  If `max_lags >= x.shape[axis]`, we effectively re-set
      `max_lags` to `x.shape[axis] - 1`.
    center:  Python `bool`.  If `False`, do not subtract the mean estimate `mu`
      from `x[n]` when forming `w[n]`.
    normalize:  Python `bool`.  If `False`, do not divide by the variance
      estimate `s**2` when forming `w[n]`.
    name:  `String` name to prepend to created ops.

  Returns:
    `rxx`: `Tensor` of same `dtype` as `x`.  `rxx.shape[i] = x.shape[i]` for
      `i != axis`, and `rxx.shape[axis] = max_lags + 1`.

  Raises:
    TypeError:  If `x` is not a supported type.
  """
    # Implementation details:
    # Extend length N / 2 1-D array x to length N by zero padding onto the end.
    # Then, set
    #   F[x]_k := sum_n x_n exp{-i 2 pi k n / N }.
    # It is not hard to see that
    #   F[x]_k Conj(F[x]_k) = F[R]_k, where
    #   R_m := sum_n x_n Conj(x_{(n - m) mod N}).
    # One can also check that R_m / (N / 2 - m) is an unbiased estimate of RXX[m].

    # Since F[x] is the DFT of x, this leads us to a zero-padding and FFT/IFFT
    # based version of estimating RXX.
    # Note that this is a special case of the Wiener-Khinchin Theorem.
    with tf.name_scope(name):
        x = tf.convert_to_tensor(x, name='x')

        # Rotate dimensions of x in order to put axis at the rightmost dim.
        # FFT op requires this.
        rank = ps.rank(x)
        if axis < 0:
            axis = rank + axis
        shift = rank - 1 - axis
        # Suppose x.shape[axis] = T, so there are T 'time' steps.
        #   ==> x_rotated.shape = B + [T],
        # where B is x_rotated's batch shape.
        x_rotated = distribution_util.rotate_transpose(x, shift)

        if center:
            x_rotated = x_rotated - tf.reduce_mean(
                x_rotated, axis=-1, keepdims=True)

        # x_len = N / 2 from above explanation.  The length of x along axis.
        # Get a value for x_len that works in all cases.
        x_len = ps.shape(x_rotated)[-1]

        # TODO(langmore) Investigate whether this zero padding helps or hurts.  At
        # the moment is necessary so that all FFT implementations work.
        # Zero pad to the next power of 2 greater than 2 * x_len, which equals
        # 2**(ceil(Log_2(2 * x_len))).  Note: Log_2(X) = Log_e(X) / Log_e(2).
        x_len_float64 = ps.cast(x_len, np.float64)
        target_length = ps.pow(np.float64(2.),
                               ps.ceil(ps.log(x_len_float64 * 2) / np.log(2.)))
        pad_length = ps.cast(target_length - x_len_float64, np.int32)

        # We should have:
        # x_rotated_pad.shape = x_rotated.shape[:-1] + [T + pad_length]
        #                     = B + [T + pad_length]
        x_rotated_pad = distribution_util.pad(x_rotated,
                                              axis=-1,
                                              back=True,
                                              count=pad_length)

        dtype = x.dtype
        if not dtype_util.is_complex(dtype):
            if not dtype_util.is_floating(dtype):
                raise TypeError(
                    'Argument x must have either float or complex dtype'
                    ' found: {}'.format(dtype))
            x_rotated_pad = tf.complex(
                x_rotated_pad,
                dtype_util.as_numpy_dtype(dtype_util.real_dtype(dtype))(0.))

        # Autocorrelation is IFFT of power-spectral density (up to some scaling).
        fft_x_rotated_pad = tf.signal.fft(x_rotated_pad)
        spectral_density = fft_x_rotated_pad * tf.math.conj(fft_x_rotated_pad)
        # shifted_product is R[m] from above detailed explanation.
        # It is the inner product sum_n X[n] * Conj(X[n - m]).
        shifted_product = tf.signal.ifft(spectral_density)

        # Cast back to real-valued if x was real to begin with.
        shifted_product = tf.cast(shifted_product, dtype)

        # Figure out if we can deduce the final static shape, and set max_lags.
        # Use x_rotated as a reference, because it has the time dimension in the far
        # right, and was created before we performed all sorts of crazy shape
        # manipulations.
        know_static_shape = True
        if not tensorshape_util.is_fully_defined(x_rotated.shape):
            know_static_shape = False
        if max_lags is None:
            max_lags = x_len - 1
        else:
            max_lags = tf.convert_to_tensor(max_lags, name='max_lags')
            max_lags_ = tf.get_static_value(max_lags)
            if max_lags_ is None or not know_static_shape:
                know_static_shape = False
                max_lags = tf.minimum(x_len - 1, max_lags)
            else:
                max_lags = min(x_len - 1, max_lags_)

        # Chop off the padding.
        # We allow users to provide a huge max_lags, but cut it off here.
        # shifted_product_chopped.shape = x_rotated.shape[:-1] + [max_lags]
        shifted_product_chopped = shifted_product[..., :max_lags + 1]

        # If possible, set shape.
        if know_static_shape:
            chopped_shape = tensorshape_util.as_list(x_rotated.shape)
            chopped_shape[-1] = min(x_len, max_lags + 1)
            tensorshape_util.set_shape(shifted_product_chopped, chopped_shape)

        # Recall R[m] is a sum of N / 2 - m nonzero terms x[n] Conj(x[n - m]).  The
        # other terms were zeros arising only due to zero padding.
        # `denominator = (N / 2 - m)` (defined below) is the proper term to
        # divide by to make this an unbiased estimate of the expectation
        # E[X[n] Conj(X[n - m])].
        x_len = ps.cast(x_len, dtype_util.real_dtype(dtype))
        max_lags = ps.cast(max_lags, dtype_util.real_dtype(dtype))
        denominator = x_len - ps.range(0., max_lags + 1.)
        denominator = ps.cast(denominator, dtype)
        shifted_product_rotated = shifted_product_chopped / denominator

        if normalize:
            shifted_product_rotated /= shifted_product_rotated[..., :1]

        # Transpose dimensions back to those of x.
        return distribution_util.rotate_transpose(shifted_product_rotated,
                                                  -shift)
Esempio n. 8
0
def european_option_price(strikes=None,
                          expiries=None,
                          is_call_options=None,
                          variances=None,
                          kappas=None,
                          thetas=None,
                          sigmas=None,
                          rhos=None,
                          spots=None,
                          forwards=None,
                          discount_rates=None,
                          dividend_rates=None,
                          continuous_dividends=None,
                          cost_of_carries=None,
                          discount_factors=None,
                          integration_method=None,
                          dtype=None,
                          name=None,
                          **kwargs):
    """Calculates European option prices under the Heston model.

  Heston originally published in 1993 his eponymous model [3]. He provided
  a semi- analytical formula for pricing European option via Fourier transform
  under his model. However, as noted by Albrecher [1], the characteristic
  function used in Heston paper can suffer numerical issues because of the
  discontinuous nature of the square root function in the complex plane, and a
  second version of the characteric function which doesn't suffer this
  shortcoming should be used instead. Attari [2] further refined the numerical
  method by reducing the number of numerical integrations (only one Fourier
  transform instead of two) and with an integrand function decaying
  quadratically instead of linearly. Attari's numerical method is implemented
  here.

  Heston model:
  ```
    dF/F = sqrt(V) * dW_1
    dV = kappa * (theta - V) * dt * sigma * sqrt(V) * dW_2
    <dW_1,dW_2> = rho *dt
  ```
  The variance V follows a square root process.

  #### Example
  ```python
  import tf_quant_finance as tff
  import numpy as np
  prices = tff.models.heston.approximations.european_option_price(
      variances=0.11,
      strikes=102.0,
      expiries=1.2,
      forwards=100.0,
      is_call_options=True,
      kappas=2.0,
      thetas=0.5,
      sigmas=0.15,
      rhos=0.3,
      discount_factors=1.0,
      dtype=np.float64)
  # Expected print output of prices:
  # 24.82219619
  ```
  #### References
  [1] Hansjorg Albrecher, The Little Heston Trap
  https://perswww.kuleuven.be/~u0009713/HestonTrap.pdf
  [2] Mukarram Attari, Option Pricing Using Fourier Transforms: A Numerically
  Efficient Simplification
  https://papers.ssrn.com/sol3/papers.cfm?abstract_id=520042
  [3] Steven L. Heston, A Closed-Form Solution for Options with Stochastic
  Volatility with Applications to Bond and Currency Options
  http://faculty.baruch.cuny.edu/lwu/890/Heston93.pdf
  Args:
    strikes: A real `Tensor` of any shape and dtype. The strikes of the options
      to be priced.
    expiries: A real `Tensor` of the same dtype and compatible shape as
      `strikes`.  The expiry of each option.
    is_call_options: A boolean `Tensor` of a shape compatible with
      `strikes`. Indicates whether the option is a call (if True) or a put
      (if False). If not supplied, call options are assumed.
    variances: A real `Tensor` of the same dtype and compatible shape as
      `strikes`. The initial value of the variance.
    kappas: A real `Tensor` of the same dtype and compatible shape as
      `strikes`. The mean reversion strength of the variance square root
      process.
    thetas: A real `Tensor` of the same dtype and compatible shape as
      `strikes`. The mean reversion level of the variance square root process.
    sigmas: A real `Tensor` of the same dtype and compatible shape as
      `strikes`. The volatility of the variance square root process (volatility
      of volatility)
    rhos: A real `Tensor` of the same dtype and compatible shape as
      `strikes`. The correlation between spot and variance.
        spots: A real `Tensor` of any shape that broadcasts to the shape of the
      `volatilities`. The current spot price of the underlying. Either this
      argument or the `forwards` (but not both) must be supplied.
    forwards: A real `Tensor` of any shape that broadcasts to the shape of
      `strikes`. The forwards to maturity. Either this argument or the
      `spots` must be supplied but both must not be supplied.
    discount_rates: An optional real `Tensor` of same dtype as the
      `strikes` and of the shape that broadcasts with `strikes`.
      If not `None`, discount factors are calculated as e^(-rT),
      where r are the discount rates, or risk free rates. At most one of
      discount_rates and discount_factors can be supplied.
      Default value: `None`, equivalent to r = 0 and discount factors = 1 when
      discount_factors also not given.
    dividend_rates: An optional real `Tensor` of same dtype as the
      `strikes` and of the shape that broadcasts with `strikes`.
      If not `None`, `cost_of_carries` is calculated as r - q,
      where r are the `discount_rates` and q is `dividend_rates`. Either
      this or `cost_of_carries` can be given.
      Default value: `None`, equivalent to q = 0.
    continuous_dividends: `Tensor` equivalent to `dividend_rates`, to be
      deprecated.
    cost_of_carries: An optional real `Tensor` of same dtype as the
      `strikes` and of the shape that broadcasts with `strikes`.
      Cost of storing a physical commodity, the cost of interest paid when
      long, or the opportunity cost, or the cost of paying dividends when short.
      If not `None`, and `spots` is supplied, used to calculate forwards from
      `spots`: F = e^(bT) * S, where F is the forwards price, b is the cost of
      carries, T is expiries and S is the spot price. If `None`, value assumed
      to be equal to the `discount_rate` - `dividend_rates`
      Default value: `None`, equivalent to b = r.
    discount_factors: An optional real `Tensor` of same dtype as the
      `strikes`. If not `None`, these are the discount factors to expiry
      (i.e. e^(-rT)). Mutually exclusive with discount_rate and cost_of_carry.
      If neither is given, no discounting is applied (i.e. the undiscounted
      option price is returned). If `spots` is supplied and `discount_factors`
      is not `None` then this is also used to compute the forwards to expiry.
      At most one of discount_rates and discount_factors can be supplied.
      Default value: `None`, which maps to -log(discount_factors) / expiries
    integration_method: An instance of `math.integration.IntegrationMethod`.
      Default value: `None` which maps to the Simpsons integration rule.
    dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion
      of any supplied non-`Tensor` arguments to `Tensor`.
      Default value: None which maps to the default dtype inferred by
      TensorFlow.
    name: str. The name for the ops created by this function.
      Default value: None which is mapped to the default name
      `heston_price`.
    **kwargs: Additional parameters for the underlying integration method.
      If not supplied and `integration_method` is Simpson, then uses
      `IntegrationMethod.COMPOSITE_SIMPSONS_RULE` with `num_points=1001`, and
      bounds `lower=1e-9`, `upper=100`.
  Returns:
    A `Tensor` of the same shape as the input data which is the price of
    European options under the Heston model.
  """
    dividend_rates = deprecation.deprecated_argument_lookup(
        'dividend_rates', dividend_rates, 'continuous_dividends',
        continuous_dividends)
    if (spots is None) == (forwards is None):
        raise ValueError(
            'Either spots or forwards must be supplied but not both.')
    if (discount_rates is not None) and (discount_factors is not None):
        raise ValueError(
            'At most one of discount_rates and discount_factors may '
            'be supplied')
    if (dividend_rates is not None) and (cost_of_carries is not None):
        raise ValueError('At most one of dividend_rates and cost_of_carries '
                         'may be supplied')

    with tf.compat.v1.name_scope(name, default_name='eu_option_price'):
        strikes = tf.convert_to_tensor(strikes, dtype=dtype, name='strikes')
        dtype = strikes.dtype
        expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries')
        kappas = tf.convert_to_tensor(kappas, dtype=dtype, name='kappas')
        thetas = tf.convert_to_tensor(thetas, dtype=dtype, name='thetas')
        sigmas = tf.convert_to_tensor(sigmas, dtype=dtype, name='sigmas')
        rhos = tf.convert_to_tensor(rhos, dtype=dtype, name='rhos')
        variances = tf.convert_to_tensor(variances,
                                         dtype=dtype,
                                         name='variances')

        if discount_factors is not None:
            discount_factors = tf.convert_to_tensor(discount_factors,
                                                    dtype=dtype,
                                                    name='discount_factors')

        if discount_rates is not None:
            discount_rates = tf.convert_to_tensor(discount_rates,
                                                  dtype=dtype,
                                                  name='discount_rates')
        elif discount_factors is not None:
            discount_rates = -tf.math.log(discount_factors) / expiries
        else:
            discount_rates = tf.convert_to_tensor(0.0,
                                                  dtype=dtype,
                                                  name='discount_rates')

        if dividend_rates is None:
            dividend_rates = tf.convert_to_tensor(0.0,
                                                  dtype=dtype,
                                                  name='dividend_rates')

        if cost_of_carries is not None:
            cost_of_carries = tf.convert_to_tensor(cost_of_carries,
                                                   dtype=dtype,
                                                   name='cost_of_carries')
        else:
            cost_of_carries = discount_rates - dividend_rates

        if discount_factors is None:
            discount_factors = tf.exp(-discount_rates * expiries)  # pylint: disable=invalid-unary-operand-type

        if forwards is not None:
            forwards = tf.convert_to_tensor(forwards,
                                            dtype=dtype,
                                            name='forwards')
        else:
            spots = tf.convert_to_tensor(spots, dtype=dtype, name='spots')
            forwards = spots * tf.exp(cost_of_carries * expiries)

        # Cast as complex for the characteristic function calculation
        expiries_real = tf.complex(expiries, tf.zeros_like(expiries))
        kappas_real = tf.complex(kappas, tf.zeros_like(kappas))
        thetas_real = tf.complex(thetas, tf.zeros_like(thetas))
        sigmas_real = tf.complex(sigmas, tf.zeros_like(sigmas))
        rhos_real = tf.complex(rhos, tf.zeros_like(rhos))
        variances_real = tf.complex(variances, tf.zeros_like(variances))

        # Prepare inputs to build an integrand_function
        expiries_real = tf.expand_dims(expiries_real, -1)
        kappas_real = tf.expand_dims(kappas_real, -1)
        thetas_real = tf.expand_dims(thetas_real, -1)
        sigmas_real = tf.expand_dims(sigmas_real, -1)
        rhos_real = tf.expand_dims(rhos_real, -1)
        variances_real = tf.expand_dims(variances_real, -1)
        if integration_method is None:
            integration_method = _COMPOSITE_SIMPSONS_RULE
        if integration_method == _COMPOSITE_SIMPSONS_RULE:
            if 'num_points' not in kwargs:
                kwargs['num_points'] = 1001
            if 'lower' not in kwargs:
                kwargs['lower'] = 1e-9
            if 'upper' not in kwargs:
                kwargs['upper'] = 100

        def char_fun(u):
            # Using 'second formula' for the (first) characteristic function of
            # log( spot_T / forwards )
            # (noted 'phi_2' in 'The Little Heston Trap', (Albrecher))
            u_real = tf.complex(u, tf.zeros_like(u))
            u_imag = tf.complex(tf.zeros_like(u), u)
            s = rhos_real * sigmas_real * u_imag
            # TODO(b/156221007): investigate why s_kappa = (s - kappas_real)**2 leads
            # to a wrong result in graph mode.
            s_kappa = (s - kappas_real) * s - (s - kappas_real) * kappas_real
            d = s_kappa - sigmas_real**2 * (-u_imag - u_real**2)
            d = tf.math.sqrt(d)
            g = (kappas_real - s - d) / (kappas_real - s + d)
            a = kappas_real * thetas_real
            h = g * tf.math.exp(-d * expiries_real)
            m = 2 * tf.math.log((1 - h) / (1 - g))
            c = (a / sigmas_real**2) * (
                (kappas_real - s - d) * expiries_real - m)
            e = (1 - tf.math.exp(-d * expiries_real))
            d_new = (kappas_real - s - d) / sigmas_real**2 * (e / (1 - h))
            return tf.math.exp(c + d_new * variances_real)

        def integrand_function(u, k):
            # Note that with [2], integrand is in 1 / u**2,
            # which converges faster than Heston 1993 (which is in 1 /u)
            char_fun_complex = char_fun(u)
            char_fun_real_part = tf.math.real(char_fun_complex)
            char_fun_imag_part = tf.math.imag(char_fun_complex)

            a = (char_fun_real_part + char_fun_imag_part / u) * tf.math.cos(
                u * k)
            b = (char_fun_imag_part - char_fun_real_part / u) * tf.math.sin(
                u * k)

            return (a + b) / (1.0 + u * u)

        k = tf.expand_dims(tf.math.log(strikes / forwards), axis=-1)

        integral = integration.integrate(lambda u: integrand_function(u, k),
                                         method=integration_method,
                                         dtype=dtype,
                                         **kwargs)
        undiscounted_call_prices = forwards - strikes * (0.5 + integral / _PI_)

        if is_call_options is None:
            return undiscounted_call_prices * discount_factors
        else:
            is_call_options = tf.convert_to_tensor(is_call_options,
                                                   dtype=tf.bool,
                                                   name='is_call_options')
            # Use call-put parity for Put
            undiscounted_put_prices = undiscounted_call_prices - forwards + strikes

            undiscount_prices = tf.where(is_call_options,
                                         undiscounted_call_prices,
                                         undiscounted_put_prices)
            return undiscount_prices * discount_factors
Esempio n. 9
0
 def complex_exp(self, real, imag):
     tensor = tf.complex(real, imag)
     exp = tf.exp(tensor)
     return tf.math.real(exp)
Esempio n. 10
0
 def decorator(*args, **kwargs):
     inputs = []
     for real, imag in zip(args[::2], args[1::2]):
         inputs.append(tf.complex(real, imag))
     result = function(*inputs, **kwargs)
     return tf.math.real(result), tf.math.imag(result)
def _complex_real(real):
  return tf.complex(real, tf.zeros_like(real))
 def _amplitude(c):
     return tf.complex(_anzatz(c[0:3]), _anzatz(c[3:6]))
Esempio n. 13
0
 def fft_imag(self, real_array, imag_array):
     complex_in = tf.complex(real_array, imag_array)
     complex_out = tf.signal.fft(complex_in)
     return tf.math.imag(complex_out)
Esempio n. 14
0
 def ifft_real(self, real_array, imag_array):
   complex_in = tf.complex(real_array, imag_array)
   complex_out = tf.signal.ifft(complex_in)
   return tf.math.real(complex_out)
Esempio n. 15
0
def matmul_real_with_complex(real_input, complex_matrix):
    real_part = tf.matmul(real_input, tf.math.real(complex_matrix))
    imag_part = tf.matmul(real_input, tf.math.imag(complex_matrix))
    return tf.complex(real_part, imag_part)