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)
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)
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
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)
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
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)
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
def complex_exp(self, real, imag): tensor = tf.complex(real, imag) exp = tf.exp(tensor) return tf.math.real(exp)
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]))
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)
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)
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)