def test_paths_time_dependent(self):
        """Tests path properties with time dependent drift and variance."""
        def vol_fn(t):
            return tf.expand_dims(0.2 - 0.1 * tf.exp(-t), axis=-1)

        def variance_fn(t0, t1):
            # The instantaneous volatility is 0.2 - 0.1 e^(-t).
            tot_var = (t1 - t0) * 0.04 - (tf.exp(-2 * t1) -
                                          tf.exp(-2 * t0)) * 0.005
            tot_var += 0.04 * (tf.exp(-t1) - tf.exp(-t0))
            return tf.reshape(tot_var, [-1, 1, 1])

        process = BrownianMotion(dim=1,
                                 drift=0.1,
                                 volatility=vol_fn,
                                 total_covariance_fn=variance_fn)
        times = np.array([0.2, 0.33, 0.7, 0.9, 1.88])
        num_samples = 10000
        paths = self.evaluate(
            process.sample_paths(times,
                                 num_samples=num_samples,
                                 initial_state=np.array(0.1),
                                 seed=12134))

        self.assertArrayEqual(paths.shape, (num_samples, 5, 1))
        self.assertArrayNear(
            np.mean(paths, axis=0).reshape([-1]), 0.1 + times * 0.1, 0.05)

        covars = np.cov(paths.reshape([num_samples, 5]), rowvar=False)
        # Expected covariances are: cov_{ij} = variance_fn(0, min(t_i, t_j))
        min_times = np.minimum(times.reshape([-1, 1]),
                               times.reshape([1, -1])).reshape([-1])
        expected_covars = self.evaluate(
            variance_fn(tf.zeros_like(min_times), min_times))
        self.assertArrayNear(covars.reshape([-1]), expected_covars, 0.005)
    def test_default_construction_2d(self):
        """Tests the default parameters for 2 dimensional Brownian Motion."""
        process = BrownianMotion(dim=2)
        self.assertEqual(process.dim(), 2)
        drift_fn = process.total_drift_fn()
        # Drifts should be zero.
        t0 = np.array([0.2, 0.7, 0.9])
        delta_t = np.array([0.1, 0.8, 0.3])
        t1 = t0 + delta_t
        drifts = self.evaluate(drift_fn(t0, t1))
        self.assertEqual(drifts.shape, (3, 2))
        self.assertArrayNear(drifts.reshape([-1]), np.zeros([3 * 2]), 1e-10)
        variances = self.evaluate(process.total_covariance_fn()(t0, t1))
        self.assertEqual(variances.shape, (3, 2, 2))
        expected_variances = np.eye(2) * delta_t.reshape([-1, 1, 1])
        print(variances, expected_variances)

        self.assertArrayNear(variances.reshape([-1]),
                             expected_variances.reshape([-1]), 1e-10)
    def test_path_properties_1d(self):
        """Tests path samples have the right properties."""
        process = BrownianMotion()
        times = np.array([0.2, 0.33, 0.7, 0.9, 1.88])
        num_samples = 10000
        paths = self.evaluate(
            process.sample_paths(times,
                                 num_samples=num_samples,
                                 initial_state=np.array(0.1),
                                 seed=1234))
        self.assertArrayEqual(paths.shape, (num_samples, 5, 1))
        self.assertArrayNear(
            np.mean(paths, axis=0).reshape([-1]),
            np.zeros(5) + 0.1, 0.05)

        covars = np.cov(paths.reshape([num_samples, 5]), rowvar=False)
        # Expected covariances are: cov_{ij} = min(t_i, t_j)
        expected = np.minimum(times.reshape([-1, 1]), times.reshape([1, -1]))
        self.assertArrayNear(covars.reshape([-1]), expected.reshape([-1]),
                             0.05)
    def test_time_dependent_construction(self):
        """Tests with time dependent drift and variance."""
        def vol_fn(t):
            return tf.expand_dims(0.2 - 0.1 * tf.exp(-t), axis=-1)

        def variance_fn(t0, t1):
            # The instantaneous volatility is 0.2 - 0.1 e^(-t).
            tot_var = (t1 - t0) * 0.04 - (tf.exp(-2 * t1) -
                                          tf.exp(-2 * t0)) * 0.005
            tot_var += 0.04 * (tf.exp(-t1) - tf.exp(-t0))
            return tf.reshape(tot_var, [-1, 1, 1])

        process = BrownianMotion(dim=1,
                                 drift=0.1,
                                 volatility=vol_fn,
                                 total_covariance_fn=variance_fn)
        t0 = np.array([0.2, 0.7, 0.9])
        delta_t = np.array([0.1, 0.8, 0.3])
        t1 = t0 + delta_t
        drifts = self.evaluate(process.total_drift_fn()(t0, t1))
        self.assertArrayNear(drifts, 0.1 * delta_t, 1e-10)
        variances = self.evaluate(process.total_covariance_fn()(t0, t1))
        self.assertArrayNear(variances.reshape([-1]),
                             [0.00149104, 0.02204584, 0.00815789], 1e-8)
 def test_default_construction_1d(self):
     """Tests the default parameters."""
     process = BrownianMotion()
     self.assertEqual(process.dim(), 1)
     drift_fn = process.drift_fn()
     # Drift should be zero.
     t0 = np.array([0.2, 0.7, 0.9])
     t1 = t0 + [0.1, 0.8, 0.3]
     drifts = self.evaluate(drift_fn(t0, None))
     total_drift_fn = process.total_drift_fn()
     self.assertAlmostEqual(self.evaluate(total_drift_fn(0.4, 0.5)),
                            0.0,
                            places=7)
     self.assertArrayNear(drifts, np.zeros([3]), 1e-10)
     variances = self.evaluate(process.total_covariance_fn()(t0, t1))
     self.assertArrayNear(variances, t1 - t0, 1e-10)
     self.assertAlmostEqual(self.evaluate(process.total_covariance_fn()(
         0.41, 0.55)),
                            0.14,
                            places=7)
 def test_negative_times_is_error(self):
     """Tests that supplying negative times in sample_paths is an error."""
     process = BrownianMotion()
     with self.assertRaises(tf.errors.InvalidArgumentError):
         self.evaluate(
             process.sample_paths([-0.1, 0.09, 1.0], num_samples=1))
    def test_paths_multi_dim(self):
        """Tests path properties for 2 dimensional brownian motion.

    We construct the following 2 dimensional time dependent brownian motion.

    dX_1 = mu_1 dt + s11 dW_1 + s12 dW_2
    dX_2 = mu_2 dt + s21 dW_1 + s22 dW_2

    mu_1, mu_2 are constants. s11, s12, s21, s22 are all linear functions of
    time. Let s11 = a11 t + b11 and similarly for the other three coefficients.
    Define the matrices:
      A = [[a11, a12], [a21, a22]], B = [[b11, b12], [b21, b22]]

    Then the total covariance from 0 to time T is:

    Total Covariance(0,T) = A.A' T**3 / 3 + (A.B'+B.A') T**2 / 2 + B.B' T

    where A', B' are the transposes of A and B.
    """

        mu = np.array([0.2, 0.7])
        a_mat = np.array([[0.4, 0.1], [0.3, 0.2]])
        b_mat = np.array([[0.33, -0.03], [0.21, 0.5]])
        c1 = np.matmul(a_mat, a_mat.transpose()) / 3
        c2 = np.matmul(a_mat, b_mat.transpose()) / 2
        c2 += c2.transpose()
        c3 = np.matmul(b_mat, b_mat.transpose())

        def vol_fn(t):
            return a_mat * tf.reshape(t, [-1, 1, 1]) + b_mat

        def tot_cov_fn(t0, t1):
            t0 = tf.reshape(t0, [-1, 1, 1])
            t1 = tf.reshape(t1, [-1, 1, 1])
            return c1 * (t1**3 - t0**3) + c2 * (t1**2 - t0**2) + c3 * (t1 - t0)

        process = BrownianMotion(dim=2,
                                 drift=mu,
                                 volatility=vol_fn,
                                 total_covariance_fn=tot_cov_fn)
        times = np.array([0.1, 0.21, 0.32, 0.43, 0.55])
        num_samples = 10000
        initial_state = np.array([0.1, -1.1])
        paths = self.evaluate(
            process.sample_paths(times,
                                 num_samples=num_samples,
                                 initial_state=initial_state,
                                 seed=12134))

        self.assertArrayEqual(paths.shape, (num_samples, 5, 2))
        expected_means = np.reshape(times, [-1, 1]) * mu + initial_state
        self.assertArrayNear(
            np.mean(paths, axis=0).reshape([-1]), expected_means.reshape([-1]),
            0.005)
        # For the covariance comparison, it is much simpler to transpose the
        # time axis with event dimension.
        paths = np.transpose(paths, [0, 2, 1])
        # covars is of shape [10, 10].
        # It has the block structure [ [<X,X>, <X,Y>], [<Y,X>, <Y,Y>]]
        # where X is the first component of the brownian motion and Y is
        # second component. <X,X> means the 5x5 covariance matrix of X(t1), ...X(t5)
        # and similarly for the other components.
        covars = np.cov(paths.reshape([num_samples, -1]), rowvar=False)

        # Expected covariances are: cov_{ij} = variance_fn(0, min(t_i, t_j))
        min_times = np.minimum(times.reshape([-1, 1]),
                               times.reshape([1, -1])).reshape([-1])
        # Of shape [25, 2, 2]. We need to do a transpose.
        expected_covars = self.evaluate(
            tot_cov_fn(tf.zeros_like(min_times), min_times))
        expected_covars = np.transpose(expected_covars, (1, 2, 0))

        xx_actual = covars[:5, :5].reshape([-1])
        xx_expected = expected_covars[0, 0, :]
        self.assertArrayNear(xx_actual, xx_expected, 0.005)

        xy_actual = covars[:5, 5:].reshape([-1])
        xy_expected = expected_covars[0, 1, :]
        self.assertArrayNear(xy_actual, xy_expected, 0.005)

        yy_actual = covars[5:, 5:].reshape([-1])
        yy_expected = expected_covars[1, 1, :]

        self.assertArrayNear(yy_actual, yy_expected, 0.005)