def test_sample_paths_wiener(self): """Tests paths properties for Wiener process (dX = dW).""" def drift_fn(_, x): return tf.zeros_like(x) def vol_fn(_, x): return tf.expand_dims(tf.ones_like(x), -1) times = np.array([0.1, 0.2, 0.3]) num_samples = 10000 paths = self.evaluate( euler_sampling.sample(dim=1, drift_fn=drift_fn, volatility_fn=vol_fn, times=times, num_samples=num_samples, seed=42, time_step=0.005)) means = np.mean(paths, axis=0).reshape([-1]) covars = np.cov(paths.reshape([num_samples, -1]), rowvar=False) expected_means = np.zeros((3, )) expected_covars = np.minimum(times.reshape([-1, 1]), times.reshape([1, -1])) self.assertAllClose(means, expected_means, rtol=1e-2, atol=1e-2) self.assertAllClose(covars, expected_covars, rtol=1e-2, atol=1e-2)
def sample_paths(self, times, num_samples=1, initial_state=None, random_type=None, seed=None, swap_memory=True, name=None, time_step=None): """Returns a sample of paths from the process using Euler sampling. The default implementation uses the Euler scheme. However, for particular types of Ito processes more efficient schemes can be used. Args: times: Rank 1 `Tensor` of increasing positive real values. The times at which the path points are to be evaluated. num_samples: Positive scalar `int`. The number of paths to draw. Default value: 1. initial_state: `Tensor` of shape `[dim]`. The initial state of the process. Default value: None which maps to a zero initial state. random_type: Enum value of `RandomType`. The type of (quasi)-random number generator to use to generate the paths. Default value: None which maps to the standard pseudo-random numbers. seed: Python `int`. The random seed to use. If not supplied, no seed is set. swap_memory: A Python bool. Whether GPU-CPU memory swap is enabled for this op. See an equivalent flag in `tf.while_loop` documentation for more details. Useful when computing a gradient of the op since `tf.while_loop` is used to propagate stochastic process in time. Default value: True. name: Python string. The name to give this op. Default value: `None` which maps to `sample_paths` is used. time_step: Real scalar `Tensor`. The maximal distance between time points in grid in Euler scheme. Returns: A real `Tensor` of shape `[num_samples, k, n]` where `k` is the size of the `times`, and `n` is the dimension of the process. """ default_name = self._name + '_sample_path' with tf.compat.v1.name_scope(name, default_name=default_name, values=[times, initial_state]): return euler_sampling.sample(self._dim, self._drift_fn, self._volatility_fn, times, num_samples=num_samples, initial_state=initial_state, random_type=random_type, time_step=time_step, seed=seed, swap_memory=swap_memory, dtype=self._dtype, name=name)
def test_antithetic_sample_paths_mean_2d(self): """Tests path properties for 2-dimentional anthithetic variates method. The same test as above but with `PSEUDO_ANTITHETIC` random type. We construct the following Ito processes. dX_1 = mu_1 sqrt(t) dt + s11 dW_1 + s12 dW_2 dX_2 = mu_2 sqrt(t) dt + s21 dW_1 + s22 dW_2 mu_1, mu_2 are constants. s_ij = a_ij t + b_ij For this process expected value at time t is (x_0)_i + 2/3 * mu_i * t^1.5. """ mu = np.array([0.2, 0.7]) a = np.array([[0.4, 0.1], [0.3, 0.2]]) b = np.array([[0.33, -0.03], [0.21, 0.5]]) def drift_fn(t, x): del x return mu * tf.sqrt(t) def vol_fn(t, x): del x return (a * t + b) * tf.ones([2, 2], dtype=t.dtype) times = np.array([0.1, 0.21, 0.32, 0.43, 0.55]) num_samples = 5000 x0 = np.array([0.1, -1.1]) paths = self.evaluate( euler_sampling.sample( dim=2, drift_fn=drift_fn, volatility_fn=vol_fn, times=times, num_samples=num_samples, initial_state=x0, random_type=random.RandomType.PSEUDO_ANTITHETIC, time_step=0.01, seed=12134)) self.assertAllClose(paths.shape, (num_samples, 5, 2), atol=0) means = np.mean(paths, axis=0) times = np.reshape(times, [-1, 1]) expected_means = x0 + (2.0 / 3.0) * mu * np.power(times, 1.5) # Antithetic variates method produces better estimate than the # estimate with the `PSEUDO` random type self.assertAllClose(means, expected_means, rtol=5e-3, atol=5e-3)
def test_sample_paths_2d(self, random_type): """Tests path properties for 2-dimentional Ito process. We construct the following Ito processes. dX_1 = mu_1 sqrt(t) dt + s11 dW_1 + s12 dW_2 dX_2 = mu_2 sqrt(t) dt + s21 dW_1 + s22 dW_2 mu_1, mu_2 are constants. s_ij = a_ij t + b_ij For this process expected value at time t is (x_0)_i + 2/3 * mu_i * t^1.5. Args: random_type: Random number type defined by tff.math.random.RandomType enum. """ mu = np.array([0.2, 0.7]) a = np.array([[0.4, 0.1], [0.3, 0.2]]) b = np.array([[0.33, -0.03], [0.21, 0.5]]) def drift_fn(t, x): return mu * tf.sqrt(t) * tf.ones_like(x, dtype=t.dtype) def vol_fn(t, x): del x return (a * t + b) * tf.ones([2, 2], dtype=t.dtype) num_samples = 10000 times = np.array([0.1, 0.21, 0.32, 0.43, 0.55]) x0 = np.array([0.1, -1.1]) paths = self.evaluate( euler_sampling.sample( dim=2, drift_fn=drift_fn, volatility_fn=vol_fn, times=times, num_samples=num_samples, initial_state=x0, time_step=0.01, seed=12134)) self.assertAllClose(paths.shape, (num_samples, 5, 2), atol=0) means = np.mean(paths, axis=0) times = np.reshape(times, [-1, 1]) expected_means = x0 + (2.0 / 3.0) * mu * np.power(times, 1.5) self.assertAllClose(means, expected_means, rtol=1e-2, atol=1e-2)
def test_sample_paths_dtypes(self): """Sampled paths have the expected dtypes.""" for dtype in [np.float32, np.float64]: drift_fn = lambda t, x: tf.sqrt(t) * tf.ones_like(x, dtype=t.dtype) vol_fn = lambda t, x: t * tf.ones([1, 1], dtype=t.dtype) paths = self.evaluate( euler_sampling.sample( dim=1, drift_fn=drift_fn, volatility_fn=vol_fn, times=[0.1, 0.2], num_samples=10, initial_state=[0.1], time_step=0.01, seed=123, dtype=dtype)) self.assertEqual(paths.dtype, dtype)
def test_sample_paths_1d(self): """Tests path properties for 1-dimentional Ito process. We construct the following Ito process. ```` dX = mu * sqrt(t) * dt + (a * t + b) dW ```` For this process expected value at time t is x_0 + 2/3 * mu * t^1.5 . """ mu = 0.2 a = 0.4 b = 0.33 def drift_fn(t, x): return mu * tf.sqrt(t) * tf.ones_like(x, dtype=t.dtype) def vol_fn(t, x): del x return (a * t + b) * tf.ones([1, 1], dtype=t.dtype) times = np.array([0.1, 0.21, 0.32, 0.43, 0.55]) num_samples = 10000 x0 = np.array([0.1]) paths = self.evaluate( euler_sampling.sample(dim=1, drift_fn=drift_fn, volatility_fn=vol_fn, times=times, num_samples=num_samples, initial_state=x0, time_step=0.01, seed=12134)) self.assertAllClose(paths.shape, (num_samples, 5, 1), atol=0) means = np.mean(paths, axis=0).reshape(-1) expected_means = x0 + (2.0 / 3.0) * mu * np.power(times, 1.5) self.assertAllClose(means, expected_means, rtol=1e-2, atol=1e-2)