Пример #1
0
def test_constructor():
    @parametric
    class A:
        def __init__(self, x, *, y=3):
            self.x = x
            self.y = y

    assert A.parametric
    assert not A.concrete
    assert not A.runtime_type_of
    with pytest.raises(RuntimeError):
        A.type_parameter

    assert A[float].parametric
    assert A[float].concrete
    assert not A[float].runtime_type_of
    assert A[float].type_parameter == float

    a1 = A[float](5.0)
    a2 = A(5.0)

    assert a1.x == 5.0
    assert a2.x == 5.0
    assert a1.y == 3
    assert a2.y == 3

    assert type_parameter(a1) == float
    assert type_parameter(a2) == float
    assert type(a1) == type(a2)
    assert type(a1).__name__ == type(a2).__name__ == f"A[{float}]"

    @parametric(runtime_type_of=True)
    class B:
        def __init__(self, x, y):
            self.x = x
            self.y = y

    assert B.parametric
    assert not B.concrete
    assert B.runtime_type_of
    with pytest.raises(RuntimeError):
        B.type_parameter

    assert B[float].parametric
    assert B[float].concrete
    assert B[float].runtime_type_of
    assert B[float].type_parameter == float

    b1 = B[float, int](5.0, 3)
    b2 = B(5.0, 3)

    assert b1.x == 5.0
    assert b2.x == 5.0
    assert b1.y == 3
    assert b2.y == 3

    assert type_parameter(b1) == (float, int)
    assert type_parameter(b2) == (float, int)
    assert type(b1) == type(b2)
    assert type(b1).__name__ == type(b2).__name__ == f"B[{float}, {int}]"
Пример #2
0
def test_shorthands():
    model = Graph()
    p = GP(EQ(), graph=model)

    # Construct a normal distribution that serves as in input.
    x = p(1)
    assert isinstance(x, At)
    assert type_parameter(x) is p
    assert x.get() == 1
    assert str(p(x)) == '{}({})'.format(str(p), str(x))
    assert repr(p(x)) == '{}({})'.format(repr(p), repr(x))

    # Construct a normal distribution that does not serve as an input.
    x = Normal(np.ones((1, 1)))
    with pytest.raises(RuntimeError):
        type_parameter(x)
    with pytest.raises(RuntimeError):
        x.get()
    with pytest.raises(RuntimeError):
        p | (x, 1)

    # Test shorthands for stretching and selection.
    p = GP(EQ(), graph=Graph())
    assert str(p > 2) == str(p.stretch(2))
    assert str(p[0]) == str(p.select(0))
Пример #3
0
    def __init__(self, z, e, x, y, ref=None):
        Observations.__init__(self, x, y, ref=ref)

        # Extract processes.
        p_x, x = type_parameter(self.x), self.x.get()
        z = ensure_at(z, self._ref)
        p_z, z = type_parameter(z), z.get()

        # Construct the necessary kernel matrices.
        K_zx = self.graph.kernels[p_z, p_x](z, x)
        K_z = self.graph.kernels[p_z](z)

        # Evaluating `e.kernel(x)` will yield incorrect results if `x` is a
        # `MultiInput`, because `x` then still designates the particular
        # components of `f`. Fix that by instead designating the elements of
        # `e`.
        if isinstance(x, MultiInput):
            x_n = MultiInput(*(p(xi.get())
                               for p, xi in zip(e.kernel.ps, x.get())))
        else:
            x_n = x

        # Construct the noise kernel matrix.
        K_n = e.kernel(x_n)

        # The approximation can only handle diagonal noise matrices.
        if not isinstance(K_n, Diagonal):
            raise RuntimeError('Kernel matrix of noise must be diagonal.')

        # And construct the components for the inducing point approximation.
        L_z = B.cholesky(matrix(K_z))
        A = B.eye_from(K_z) + B.qf(K_n, B.transpose(B.trisolve(L_z, K_zx)))
        y_bar = uprank(self.y) - e.mean(x_n) - self.graph.means[p_x](x)
        prod_y_bar = B.trisolve(L_z, B.qf(K_n, B.transpose(K_zx), y_bar))

        # Compute the optimal mean.
        mean = self.graph.means[p_z](z) + \
               B.qf(A, B.trisolve(L_z, K_z), prod_y_bar)

        # Compute the ELBO.
        # NOTE: The calculation of `trace_part` asserts that `K_n` is diagonal.
        #       The rest, however, is completely generic.
        trace_part = B.ratio(
            Diagonal(self.graph.kernels[p_x].elwise(x)[:, 0]) -
            Diagonal(B.qf_diag(K_z, K_zx)), K_n)
        det_part = B.logdet(2 * B.pi * K_n) + B.logdet(A)
        qf_part = B.qf(K_n, y_bar)[0, 0] - B.qf(A, prod_y_bar)[0, 0]
        elbo = -0.5 * (trace_part + det_part + qf_part)

        # Store relevant quantities.
        self.elbo = elbo
        self.A = A

        # Update observations to reflect pseudo-points.
        self.x = p_z(z)
        self.y = mean
Пример #4
0
    def __init__(self, zs, e, x, y, ref=None):
        # Ensure `At` everywhere.
        zs = [ensure_at(z, ref=ref) for z in zs]

        # Extract graph.
        graph = type_parameter(zs[0]).graph

        # Create a representative multi-output process.
        p_z = graph.cross(*(type_parameter(z) for z in zs))

        SparseObservations.__init__(self,
                                    p_z(MultiInput(*zs)),
                                    e, x, y, ref=ref)
Пример #5
0
 def K_x(self):
     """Kernel matrix of the data."""
     # Cache computation of the kernel matrix.
     if self._K_x is None:
         p_x, x = type_parameter(self.x), self.x.get()
         self._K_x = matrix(self.graph.kernels[p_x](x))
     return self._K_x
Пример #6
0
    def _compute(self):
        # Extract processes.
        p_x, x = type_parameter(self.x), self.x.get()
        p_z, z = type_parameter(self.z), self.z.get()

        # Construct the necessary kernel matrices.
        K_zx = self.graph.kernels[p_z, p_x](z, x)
        self._K_z = matrix(self.graph.kernels[p_z](z))

        # Evaluating `e.kernel(x)` will yield incorrect results if `x` is a
        # `MultiInput`, because `x` then still designates the particular
        # components of `f`. Fix that by instead designating the elements of
        # `e`.
        if isinstance(x, MultiInput):
            x_n = MultiInput(*(p(xi.get())
                               for p, xi in zip(self.e.kernel.ps, x.get())))
        else:
            x_n = x

        # Construct the noise kernel matrix.
        K_n = self.e.kernel(x_n)

        # The approximation can only handle diagonal noise matrices.
        if not isinstance(K_n, Diagonal):
            raise RuntimeError('Kernel matrix of noise must be diagonal.')

        # And construct the components for the inducing point approximation.
        L_z = B.cholesky(self._K_z)
        self._A = B.eye(self._K_z) + \
                  B.qf(K_n, B.transpose(B.trisolve(L_z, K_zx)))
        y_bar = uprank(self.y) - self.e.mean(x_n) - self.graph.means[p_x](x)
        prod_y_bar = B.trisolve(L_z, B.qf(K_n, B.transpose(K_zx), y_bar))

        # Compute the optimal mean.
        self._mu = self.graph.means[p_z](z) + \
                   B.qf(self._A, B.trisolve(L_z, self._K_z), prod_y_bar)

        # Compute the ELBO.
        # NOTE: The calculation of `trace_part` asserts that `K_n` is diagonal.
        #       The rest, however, is completely generic.
        trace_part = B.ratio(Diagonal(self.graph.kernels[p_x].elwise(x)[:, 0]) -
                             Diagonal(B.qf_diag(self._K_z, K_zx)), K_n)
        det_part = B.logdet(2 * B.pi * K_n) + B.logdet(self._A)
        qf_part = B.qf(K_n, y_bar)[0, 0] - B.qf(self._A, prod_y_bar)[0, 0]
        self._elbo = -0.5 * (trace_part + det_part + qf_part)
Пример #7
0
 def posterior_kernel(self, p_i, p_j):
     p_z, z = type_parameter(self.z), self.z.get()
     return PosteriorKernel(self.graph.kernels[p_i, p_j],
                            self.graph.kernels[p_z, p_i],
                            self.graph.kernels[p_z, p_j],
                            z, self.K_z) + \
            CorrectiveKernel(self.graph.kernels[p_z, p_i],
                             self.graph.kernels[p_z, p_j],
                             z, self.A, self.K_z)
Пример #8
0
    def posterior_mean(self, p):
        """Get the posterior kernel of a process.

        Args:
            p (:class:`.graph.GP`): Process.

        Returns:
            :class:`.mean.Mean`: Posterior mean of `p`.
        """
        p_x, x = type_parameter(self.x), self.x.get()
        return PosteriorMean(self.graph.means[p], self.graph.means[p_x],
                             self.graph.kernels[p_x, p], x, self.K_x, self.y)
Пример #9
0
def test_shorthands():
    model = Graph()
    p = GP(EQ(), graph=model)

    # Construct a normal distribution that serves as in input.
    x = p(1)
    yield assert_instance, x, At
    yield ok, type_parameter(x) is p
    yield eq, x.get(), 1
    yield eq, str(p(x)), '{}({})'.format(str(p), str(x))
    yield eq, repr(p(x)), '{}({})'.format(repr(p), repr(x))

    # Construct a normal distribution that does not serve as an input.
    x = Normal(np.ones((1, 1)))
    yield raises, RuntimeError, lambda: type_parameter(x)
    yield raises, RuntimeError, lambda: x.get()
    yield raises, RuntimeError, lambda: p | (x, 1)

    # Test shorthands for stretching and selection.
    p = GP(EQ(), graph=Graph())
    yield eq, str(p > 2), str(p.stretch(2))
    yield eq, str(p[0]), str(p.select(0))
Пример #10
0
def test_parametric():
    class Base1:
        pass

    class Base2:
        pass

    @parametric
    class A(Base1):
        pass

    assert issubclass(A, Base1)
    assert not issubclass(A, Base2)

    assert A[1] == A[1]
    assert A[2] == A[2]
    assert A[1] != A[2]

    a1 = A[1]()
    a2 = A[2]()

    assert type(a1) == A[1]
    assert type(a2) == A[2]
    assert isinstance(a1, A[1])
    assert not isinstance(a1, A[2])
    assert issubclass(type(a1), A)
    assert issubclass(type(a1), Base1)
    assert not issubclass(type(a1), Base2)

    # Test multiple type parameters
    assert A[1, 2] == A[1, 2]

    def tuple_elements_are_identical(tup1, tup2):
        if len(tup1) != len(tup2):
            return False
        for x, y in zip(tup1, tup2):
            if x is not y:
                return False
        return True

    # Test type parameter extraction.
    assert type_parameter(A[1]()) == 1
    assert type_parameter(A["1"]()) == "1"
    assert type_parameter(A[1.0]()) == 1.0
    assert type_parameter(A[1, 2]()) == (1, 2)
    assert type_parameter(A[a1]()) is a1
    assert tuple_elements_are_identical(type_parameter(A[a1, a2]()), (a1, a2))
    assert tuple_elements_are_identical(type_parameter(A[1, a2]()), (1, a2))

    # Test that an error is raised if type parameters are specified twice.
    T = A[1]
    with pytest.raises(TypeError):
        T[1]
Пример #11
0
 def __check_init_invariants__(self, **kwargs):
     T = type_parameter(self)
     if not (issubclass(type(T), type) and issubclass(T, GridType)):
         raise TypeError("Type parameter must be a subclass of GridType.")
     if len(kwargs) > 1 or (len(kwargs) == 1 and "periodic" not in kwargs):
         raise ValueError("Invalid keyword argument")
     periodic = kwargs.get("periodic", T is Periodic)
     if type(periodic) is not bool:
         raise TypeError("`periodic` must be a bool.")
     type_kw_mismatch = (
         (not periodic and T is Periodic)
         or (periodic and issubclass(Union[T], Union[Regular, Chebyshev])))
     if type_kw_mismatch:
         raise ValueError(
             "Incompatible type parameter and keyword argument")
Пример #12
0
    def posterior_kernel(self, p_i, p_j):
        """Get the posterior kernel between two processes.

        Args:
            p_i (:class:`.graph.GP`): First process.
            p_j (:class:`.graph.GP`): Second process.

        Returns:
            :class:`.kernel.Kernel`: Posterior kernel between the first and
                second process.
        """
        p_x, x = type_parameter(self.x), self.x.get()
        return PosteriorKernel(self.graph.kernels[p_i, p_j],
                               self.graph.kernels[p_x, p_i],
                               self.graph.kernels[p_x, p_j], x, self.K_x)
Пример #13
0
    def __init__(self, *pairs, **kw_args):
        # Check whether there's a reference.
        self._ref = kw_args['ref'] if 'ref' in kw_args else None

        # Ensure `At` for all pairs.
        pairs = [(ensure_at(x, self._ref), y) for x, y in pairs]

        # Get the graph from the first pair.
        self.graph = type_parameter(pairs[0][0]).graph

        # Extend the graph by the Cartesian product `p` of all processes.
        p = self.graph.cross(*self.graph.ps)

        # Condition on the newly created vector-valued GP.
        xs, ys = zip(*pairs)
        self.x = p(MultiInput(*xs))
        self.y = B.concat(*[uprank(y) for y in ys], axis=0)
def test():
    class Base1:
        pass

    class Base2:
        pass

    @parametric
    class A(Base1):
        pass

    assert issubclass(A, Base1)
    assert not issubclass(A, Base2)

    assert A(1) == A(1)
    assert A(2) == A(2)
    assert A(1) != A(2)

    a1 = A(1)()
    a2 = A(2)()

    assert type(a1) == A(1)
    assert type(a2) == A(2)
    assert isinstance(a1, A(1))
    assert not isinstance(a1, A(2))
    assert issubclass(type(a1), A)
    assert issubclass(type(a1), Base1)
    assert not issubclass(type(a1), Base2)

    # Test multiple type parameters
    assert A(1, 2) == A(1, 2)

    # Test type parameter extraction.
    assert type_parameter(A(1)()) == 1
    assert type_parameter(A('1')()) == '1'
    assert type_parameter(A(1.)()) == 1.
    assert type_parameter(A(1, 2)()) == (1, 2)
    assert type_parameter(A(a1)()) == id(a1)
    assert type_parameter(A(a1, a2)()) == (id(a1), id(a2))
    assert type_parameter(A(1, a2)()) == (1, id(a2))
Пример #15
0
 def __init__(self, x, y, ref=None):
     self._ref = ref
     self.x = ensure_at(x, self._ref)
     self.y = y
     self.graph = type_parameter(self.x).graph
Пример #16
0
 def posterior_mean(self, p):
     p_z, z = type_parameter(self.z), self.z.get()
     return PosteriorMean(self.graph.means[p],
                          self.graph.means[p_z],
                          self.graph.kernels[p_z, p],
                          z, self.K_z, self.mu)
Пример #17
0
 def __repr__(self):
     T = type_parameter(self)
     P = "" if T is Regular else f"[{T.__name__}]"
     return f"Grid{P} ({' x '.join(map(str, self.shape))})"
Пример #18
0
 def is_periodic(self):
     return type_parameter(self) is Periodic
Пример #19
0
 def posterior_mean(self, p):
     p_x, x = type_parameter(self.x), self.x.get()
     return PosteriorMean(self.graph.means[p],
                          self.graph.means[p_x],
                          self.graph.kernels[p_x, p],
                          x, self.K_x, self.y)
Пример #20
0
 def K_x(self):
     """Kernel matrix of the data."""
     if self._K_x is None:  # Cache computation.
         p_x, x = type_parameter(self.x), self.x.get()
         self._K_x = convert(self.graph.kernels[p_x](x), AbstractMatrix)
     return self._K_x
Пример #21
0
 def posterior_kernel(self, p_i, p_j):
     p_x, x = type_parameter(self.x), self.x.get()
     return Observations.posterior_kernel(self, p_i, p_j) + \
            CorrectiveKernel(self.graph.kernels[p_x, p_i],
                             self.graph.kernels[p_x, p_j],
                             x, self.A, self.K_x)
Пример #22
0
 def __call__(self, x, y):
     return self.kernels[type_parameter(x),
                         type_parameter(y)](x.get(), y.get())
Пример #23
0
 def __call__(self, x):
     return self.means[type_parameter(x)](x.get())
Пример #24
0
 def posterior_kernel(self, p_i, p_j):
     p_x, x = type_parameter(self.x), self.x.get()
     return PosteriorKernel(self.graph.kernels[p_i, p_j],
                            self.graph.kernels[p_x, p_i],
                            self.graph.kernels[p_x, p_j],
                            x, self.K_x)
Пример #25
0
 def elwise(self, x, y):
     return self.kernels[type_parameter(x),
                         type_parameter(y)].elwise(x.get(), y.get())