Ejemplo n.º 1
0
    def testNoTimeDependence(self):
        """Test for the case where all terms (quadratic, linear, shift) are null."""
        grid = grids.uniform_grid(minimums=[-10, -20],
                                  maximums=[10, 20],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        time_step = 0.1
        final_t = 1
        variance = 1

        final_cond = np.outer(_gaussian(ys, variance), _gaussian(xs, variance))
        final_values = tf.expand_dims(tf.constant(final_cond,
                                                  dtype=tf.float32),
                                      axis=0)
        bound_cond = [(_zero_boundary, _zero_boundary),
                      (_zero_boundary, _zero_boundary)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(start_time=final_t,
                                           end_time=0,
                                           coord_grid=grid,
                                           values_grid=final_values,
                                           time_step=time_step,
                                           one_step_fn=step_fn,
                                           boundary_conditions=bound_cond,
                                           dtype=grid[0].dtype)
        expected = final_cond  # No time dependence.
        self._assertClose(expected, result)
Ejemplo n.º 2
0
    def testEuropeanCallDynamicVol(self):
        """Price for the European Call option with time-dependent volatility."""
        num_equations = 1  # Number of PDE
        num_grid_points = 1024  # Number of grid points
        dtype = np.float64
        # Build a log-uniform grid
        s_max = 300.
        grid = grids.log_uniform_grid(minimums=[0.01],
                                      maximums=[s_max],
                                      sizes=[num_grid_points],
                                      dtype=dtype)
        # Specify volatilities and interest rates for the options
        expiry = 1.0
        strike = 50.0

        # Volatility is of the form  `sigma**2(t) = 1 / 6 + 1 / 2 * t**2`.
        def second_order_coeff_fn(t, location_grid):
            return [[(1. / 6 + t**2 / 2) * tf.square(location_grid[0]) / 2]]

        @dirichlet
        def lower_boundary_fn(t, location_grid):
            del t, location_grid
            return dtype([0.0])

        @dirichlet
        def upper_boundary_fn(t, location_grid):
            del t
            return location_grid[0][-1] - strike

        final_values = tf.nn.relu(grid[0] - strike)
        # Broadcast to the shape of value dimension, if necessary.
        final_values += tf.zeros([num_equations, num_grid_points], dtype=dtype)
        # Estimate European call option price
        estimate = fd_solvers.solve_backward(
            start_time=expiry,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            num_steps=None,
            start_step_count=0,
            time_step=tf.constant(0.01, dtype=dtype),
            one_step_fn=crank_nicolson_step,
            boundary_conditions=[(lower_boundary_fn, upper_boundary_fn)],
            values_transform_fn=None,
            second_order_coeff_fn=second_order_coeff_fn,
            dtype=dtype)[0]

        value_grid = self.evaluate(estimate)[0, :]
        # Get two grid locations (correspond to spot 51.9537332 and 106.25407758,
        # respectively).
        loc_1 = 849
        # True call option price (obtained using black_scholes_price function)
        call_price = 12.582092
        self.assertAllClose(call_price,
                            value_grid[loc_1],
                            rtol=1e-02,
                            atol=1e-02)
Ejemplo n.º 3
0
    def testAnisotropicDiffusion(self):
        """Tests solving 2d diffusion equation.

    The equation is `u_{t} + Dx u_{xx} + Dy u_{yy} = 0`.
    The final condition is a gaussian centered at (0, 0) with variance sigma.
    The variance along each dimension should evolve as
    `sigma + 2 Dx (t_final - t)` and `sigma + 2 Dy (t_final - t)`.
    """
        grid = grids.uniform_grid(minimums=[-10, -20],
                                  maximums=[10, 20],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        diff_coeff_x = 0.4  # Dx
        diff_coeff_y = 0.25  # Dy
        time_step = 0.1
        final_t = 1
        final_variance = 1

        def quadratic_coeff_fn(t, location_grid):
            del t, location_grid
            u_xx = diff_coeff_x
            u_yy = diff_coeff_y
            u_xy = None
            return [[u_yy, u_xy], [u_xy, u_xx]]

        final_values = tf.expand_dims(tf.constant(np.outer(
            _gaussian(ys, final_variance), _gaussian(xs, final_variance)),
                                                  dtype=tf.float32),
                                      axis=0)
        bound_cond = [(_zero_boundary, _zero_boundary),
                      (_zero_boundary, _zero_boundary)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=step_fn,
            boundary_conditions=bound_cond,
            second_order_coeff_fn=quadratic_coeff_fn,
            dtype=grid[0].dtype)

        variance_x = final_variance + 2 * diff_coeff_x * final_t
        variance_y = final_variance + 2 * diff_coeff_y * final_t
        expected = np.outer(_gaussian(ys, variance_y),
                            _gaussian(xs, variance_x))

        self._assertClose(expected, result)
Ejemplo n.º 4
0
    def testSimpleDrift(self):
        """Tests solving 2d drift equation.

    The equation is `u_{t} + vx u_{x} + vy u_{y} = 0`.
    The final condition is a gaussian centered at (0, 0) with variance sigma.
    The gaussian should drift with velocity `[vx, vy]`.
    """
        grid = grids.uniform_grid(minimums=[-10, -20],
                                  maximums=[10, 20],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        time_step = 0.01
        final_t = 3
        variance = 1
        vx = 0.1
        vy = 0.3

        def first_order_coeff_fn(t, location_grid):
            del t, location_grid
            return [vy, vx]

        final_values = tf.expand_dims(tf.constant(np.outer(
            _gaussian(ys, variance), _gaussian(xs, variance)),
                                                  dtype=tf.float32),
                                      axis=0)

        bound_cond = [(_zero_boundary, _zero_boundary),
                      (_zero_boundary, _zero_boundary)]

        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=douglas_adi_step(theta=0.5),
            boundary_conditions=bound_cond,
            first_order_coeff_fn=first_order_coeff_fn,
            dtype=grid[0].dtype)

        expected = np.outer(_gaussian(ys + vy * final_t, variance),
                            _gaussian(xs + vx * final_t, variance))

        self._assertClose(expected, result)
Ejemplo n.º 5
0
    def testShiftTerm(self):
        """Simple test for the shift term.

    The equation is `u_{t} + a u = 0`, the solution is
    `u(x, y, t) = exp(-a(t - t_final)) u(x, y, t_final)`
    """
        grid = grids.uniform_grid(minimums=[-10, -20],
                                  maximums=[10, 20],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        time_step = 0.1
        final_t = 1
        variance = 1
        a = 2

        def zeroth_order_coeff_fn(t, location_grid):
            del t, location_grid
            return a

        expected = (
            np.outer(_gaussian(ys, variance), _gaussian(xs, variance)) *
            np.exp(a * final_t))

        final_values = tf.expand_dims(tf.constant(np.outer(
            _gaussian(ys, variance), _gaussian(xs, variance)),
                                                  dtype=tf.float32),
                                      axis=0)
        bound_cond = [(_zero_boundary, _zero_boundary),
                      (_zero_boundary, _zero_boundary)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=step_fn,
            boundary_conditions=bound_cond,
            zeroth_order_coeff_fn=zeroth_order_coeff_fn,
            dtype=grid[0].dtype)

        self._assertClose(expected, result)
Ejemplo n.º 6
0
    def _testHeatEquation(self,
                          grid,
                          final_t,
                          time_step,
                          final_cond_fn,
                          expected_result_fn,
                          one_step_fn,
                          lower_boundary_fn,
                          upper_boundary_fn,
                          error_tolerance=1e-3):
        """Helper function with details of testing heat equation solving."""

        # Define coefficients for a PDE V_{t} + V_{XX} = 0.
        def second_order_coeff_fn(t, x):
            del t, x
            return [[1]]

        xs = self.evaluate(grid)[0]
        final_values = tf.constant([final_cond_fn(x) for x in xs],
                                   dtype=grid[0].dtype)

        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            num_steps=None,
            start_step_count=0,
            time_step=time_step,
            one_step_fn=one_step_fn,
            boundary_conditions=[(lower_boundary_fn, upper_boundary_fn)],
            values_transform_fn=None,
            second_order_coeff_fn=second_order_coeff_fn,
            dtype=grid[0].dtype)

        actual = self.evaluate(result[0])
        expected = self.evaluate(expected_result_fn(xs))
        self.assertLess(np.max(np.abs(actual - expected)), error_tolerance)
Ejemplo n.º 7
0
    def testAnisotropicDiffusion_WithRobinBoundaries(self):
        """Tests solving 2d diffusion equation with Robin boundary conditions."""
        time_step = 0.01
        final_t = 1
        x_min = -20
        x_max = 20
        y_min = -10
        y_max = 10

        grid = grids.uniform_grid(minimums=[y_min, x_min],
                                  maximums=[y_max, x_max],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        def second_order_coeff_fn(t, location_grid):
            del t, location_grid
            return [[2, None], [None, 1]]

        def lower_bound_x(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(ys / 2) * (np.sin(x_min / _SQRT2) -
                                              np.cos(x_min / _SQRT2) / _SQRT2)
            return 1, 1, tf.expand_dims(f, 0)

        def upper_bound_x(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(ys / 2) * (
                np.sin(x_max / _SQRT2) + 2 * np.cos(x_max / _SQRT2) / _SQRT2)
            return 1, 2, tf.expand_dims(f, 0)

        def lower_bound_y(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(
                xs / _SQRT2) * (np.sin(y_min / 2) - 3 * np.cos(y_min / 2) / 2)
            return 1, 3, tf.expand_dims(f, 0)

        def upper_bound_y(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(xs / _SQRT2) * (2 * np.sin(y_max / 2) +
                                                   3 * np.cos(y_max / 2) / 2)
            return 2, 3, tf.expand_dims(f, 0)

        expected = np.outer(np.sin(ys / 2), np.sin(xs / _SQRT2))

        final_values = tf.expand_dims(tf.constant(
            np.outer(np.sin(ys / 2), np.sin(xs / _SQRT2)) * np.exp(final_t),
            dtype=tf.float32),
                                      axis=0)
        bound_cond = [(lower_bound_y, upper_bound_y),
                      (lower_bound_x, upper_bound_x)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=step_fn,
            boundary_conditions=bound_cond,
            second_order_coeff_fn=second_order_coeff_fn,
            dtype=grid[0].dtype)

        self._assertClose(expected, result)
Ejemplo n.º 8
0
    def testAnisotropicDiffusion_WithDirichletBoundaries(self):
        """Tests solving 2d diffusion equation with Dirichlet boundary conditions.

    The equation is `u_{t} + u_{xx} + 2 u_{yy} = 0`.
    The final condition is `u(t=1, x, y) = e * sin(x/sqrt(2)) * cos(y / 2)`.
    The following function satisfies this PDE and final condition:
    `u(t, x, y) = exp(t) * sin(x / sqrt(2)) * cos(y / 2)`.
    We impose Dirichlet boundary conditions using this function:
    `u(t, x_min, y) = exp(t) * sin(x_min / sqrt(2)) * cos(y / 2)`, etc.
    The other tests below are similar, but with other types of boundary
    conditions.
    """
        time_step = 0.01
        final_t = 1
        x_min = -20
        x_max = 20
        y_min = -10
        y_max = 10

        grid = grids.uniform_grid(minimums=[y_min, x_min],
                                  maximums=[y_max, x_max],
                                  sizes=[201, 301],
                                  dtype=tf.float32)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        def second_order_coeff_fn(t, location_grid):
            del t, location_grid
            return [[2, None], [None, 1]]

        @dirichlet
        def lower_bound_x(t, location_grid):
            del location_grid
            f = tf.exp(t) * np.sin(x_min / _SQRT2) * tf.sin(ys / 2)
            return tf.expand_dims(f, 0)

        @dirichlet
        def upper_bound_x(t, location_grid):
            del location_grid
            f = tf.exp(t) * np.sin(x_max / _SQRT2) * tf.sin(ys / 2)
            return tf.expand_dims(f, 0)

        @dirichlet
        def lower_bound_y(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(xs / _SQRT2) * np.sin(y_min / 2)
            return tf.expand_dims(f, 0)

        @dirichlet
        def upper_bound_y(t, location_grid):
            del location_grid
            f = tf.exp(t) * tf.sin(xs / _SQRT2) * np.sin(y_max / 2)
            return tf.expand_dims(f, 0)

        expected = np.outer(np.sin(ys / 2), np.sin(xs / _SQRT2))

        final_values = tf.expand_dims(tf.constant(
            np.outer(np.sin(ys / 2), np.sin(xs / _SQRT2)) * np.exp(final_t),
            dtype=tf.float32),
                                      axis=0)
        bound_cond = [(lower_bound_y, upper_bound_y),
                      (lower_bound_x, upper_bound_x)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=step_fn,
            boundary_conditions=bound_cond,
            second_order_coeff_fn=second_order_coeff_fn,
            dtype=grid[0].dtype)

        self._assertClose(expected, result)
Ejemplo n.º 9
0
    def _testDiffusionInDiagonalDirection(self, pack_second_order_coeff_fn):
        """Tests solving 2d diffusion equation involving mixed terms.

    The equation is `u_{t} + D u_{xx} / 2 +  D u_{yy} / 2 + D u_{xy} = 0`.
    The final condition is a gaussian centered at (0, 0) with variance sigma.

    The equation can be rewritten as `u_{t} + D u_{zz} = 0`, where
    `z = (x + y) / sqrt(2)`.

    Thus variance should evolve as `sigma + 2D(t_final - t)` along z dimension
    and stay unchanged in the orthogonal dimension:
    `u(x, y, t) = gaussian((x + y)/sqrt(2), sigma + 2D(t_final - t)) *
    gaussian((x - y)/sqrt(2), sigma)`.
    """
        dtype = tf.float32

        grid = grids.uniform_grid(minimums=[-10, -20],
                                  maximums=[10, 20],
                                  sizes=[201, 301],
                                  dtype=dtype)
        ys = self.evaluate(grid[0])
        xs = self.evaluate(grid[1])

        diff_coeff = 1  # D
        time_step = 0.1
        final_t = 3
        final_variance = 1

        def second_order_coeff_fn(t, location_grid):
            del t, location_grid
            return pack_second_order_coeff_fn(diff_coeff / 2, diff_coeff / 2,
                                              diff_coeff / 2)

        variance_along_diagonal = final_variance + 2 * diff_coeff * final_t

        def expected_fn(x, y):
            return (_gaussian(
                (x + y) / _SQRT2, variance_along_diagonal) * _gaussian(
                    (x - y) / _SQRT2, final_variance))

        expected = np.array([[expected_fn(x, y) for x in xs] for y in ys])

        final_values = tf.expand_dims(tf.constant(np.outer(
            _gaussian(ys, final_variance), _gaussian(xs, final_variance)),
                                                  dtype=dtype),
                                      axis=0)
        bound_cond = [(_zero_boundary, _zero_boundary),
                      (_zero_boundary, _zero_boundary)]
        step_fn = douglas_adi_step(theta=0.5)
        result = fd_solvers.solve_backward(
            start_time=final_t,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            time_step=time_step,
            one_step_fn=step_fn,
            boundary_conditions=bound_cond,
            second_order_coeff_fn=second_order_coeff_fn,
            dtype=grid[0].dtype)

        self._assertClose(expected, result)
Ejemplo n.º 10
0
    def testDocStringExample(self):
        """Tests that the European Call option price is computed correctly."""
        num_equations = 2  # Number of PDE
        num_grid_points = 1024  # Number of grid points
        dtype = np.float64
        # Build a log-uniform grid
        s_max = 300.
        grid = grids.log_uniform_grid(minimums=[0.01],
                                      maximums=[s_max],
                                      sizes=[num_grid_points],
                                      dtype=dtype)
        # Specify volatilities and interest rates for the options
        volatility = np.array([0.3, 0.15], dtype=dtype).reshape([-1, 1])
        rate = np.array([0.01, 0.03], dtype=dtype).reshape([-1, 1])
        expiry = 1.0
        strike = np.array([50, 100], dtype=dtype).reshape([-1, 1])

        def second_order_coeff_fn(t, location_grid):
            del t
            return [[tf.square(volatility) * tf.square(location_grid[0]) / 2]]

        def first_order_coeff_fn(t, location_grid):
            del t
            return [rate * location_grid[0]]

        def zeroth_order_coeff_fn(t, location_grid):
            del t, location_grid
            return -rate

        @dirichlet
        def lower_boundary_fn(t, location_grid):
            del t, location_grid
            return dtype([0.0, 0.0])

        @dirichlet
        def upper_boundary_fn(t, location_grid):
            return tf.squeeze(location_grid[0][-1] -
                              strike * tf.exp(-rate * (expiry - t)))

        final_values = tf.nn.relu(grid[0] - strike)
        # Broadcast to the shape of value dimension, if necessary.
        final_values += tf.zeros([num_equations, num_grid_points], dtype=dtype)
        # Estimate European call option price
        estimate = fd_solvers.solve_backward(
            start_time=expiry,
            end_time=0,
            coord_grid=grid,
            values_grid=final_values,
            num_steps=None,
            start_step_count=0,
            time_step=tf.constant(0.01, dtype=dtype),
            one_step_fn=crank_nicolson_step,
            boundary_conditions=[(lower_boundary_fn, upper_boundary_fn)],
            values_transform_fn=None,
            second_order_coeff_fn=second_order_coeff_fn,
            first_order_coeff_fn=first_order_coeff_fn,
            zeroth_order_coeff_fn=zeroth_order_coeff_fn,
            dtype=dtype)[0]
        estimate = self.evaluate(estimate)
        # Extract estimates for some of the grid locations and compare to the
        # true option price
        value_grid_first_option = estimate[0, :]
        value_grid_second_option = estimate[1, :]
        # Get two grid locations (correspond to spot 51.9537332 and 106.25407758,
        # respectively).
        loc_1 = 849
        loc_2 = 920
        # True call option price (obtained using black_scholes_price function)
        call_price = [7.35192484, 11.75642136]

        self.assertAllClose(
            call_price,
            [value_grid_first_option[loc_1], value_grid_second_option[loc_2]],
            rtol=1e-03,
            atol=1e-03)