1"""T-Copula."""
2import numpy
3from scipy import special
4import chaospy
5
6from ..baseclass import CopulaDistribution, Distribution
7
8
9class t_copula(Distribution):
10    """T-Copula."""
11
12    def __init__(self, df, covariance, rotation=None):
13        assert isinstance(df, float)
14        covariance = numpy.asarray(covariance)
15        assert covariance.ndim == 2, "Covariance must be a matrix"
16        assert covariance.shape[0] == covariance.shape[1], (
17            "Parameters 'covariance' not a square matrix.")
18
19        dependencies, _, rotation = chaospy.declare_dependencies(
20            self,
21            parameters=dict(covariance=covariance),
22            rotation=rotation,
23            dependency_type="accumulate",
24        )
25        correlation = covariance/numpy.sqrt(numpy.outer(
26            numpy.diag(covariance), numpy.diag(covariance)))
27        self._permute = numpy.eye(len(rotation), dtype=int)[rotation]
28        self._correlation = self._permute.dot(correlation).dot(self._permute.T)
29        cholesky = numpy.linalg.cholesky(self._correlation)
30        self._fwd_transform = self._permute.T.dot(numpy.linalg.inv(cholesky))
31        self._inv_transform = self._permute.T.dot(cholesky)
32
33        super(t_copula, self).__init__(
34            parameters=dict(df=df),
35            dependencies=dependencies,
36            rotation=rotation,
37            repr_args=[covariance.tolist()],
38        )
39
40    def _cdf(self, xloc, idx, df, cache):
41        dim = self._rotation.index(idx)
42        conditions = [self._get_cache(dim_, cache, get=0)
43                      for dim_ in self._rotation[:dim]]
44        assert not any([isinstance(condition, chaospy.Distribution)
45                        for condition in conditions])
46        xloc = numpy.vstack(conditions+[xloc])
47        zloc = self._fwd_transform[idx, :len(xloc)].dot(special.stdtrit(df, xloc))
48        out = special.stdtr(df, zloc)
49        return out
50
51    def _ppf(self, qloc, idx, df, cache):
52        dim = self._rotation.index(idx)
53        conditions = [self._get_cache(dim_, cache, get=1)
54                      for dim_ in self._rotation[:dim]]
55        assert not any([isinstance(condition, chaospy.Distribution)
56                        for condition in conditions])
57        qloc = numpy.vstack(conditions+[qloc])
58        zloc = special.stdtrit(df, qloc)
59        out = special.stdtr(df, self._inv_transform[idx, :len(qloc)].dot(zloc))
60        return out
61
62    def _pdf(self, xloc, idx, df, cache):
63        raise chaospy.UnsupportedFeature("Copula not supported.")
64
65    def _lower(self, idx, df, cache):
66        return 0.
67
68    def _upper(self, idx, df, cache):
69        return 1.
70
71
72class TCopula(CopulaDistribution):
73    """
74    T-Copula.
75
76    Examples:
77        >>> distribution = chaospy.TCopula(
78        ...     chaospy.Iid(chaospy.Uniform(-1, 1), 2),
79        ...     df=5, covariance=[[1, .5], [.5, 1]])
80        >>> distribution
81        TCopula(Iid(Uniform(lower=-1, upper=1), 2), 5.0, [[1.0, 0.5], [0.5, 1.0]])
82        >>> samples = distribution.sample(3)
83        >>> samples.round(4)
84        array([[ 0.3072, -0.77  ,  0.9006],
85               [ 0.1274,  0.3147,  0.1928]])
86        >>> distribution.pdf(samples).round(4)
87        array([0.2932, 0.1367, 0.1969])
88        >>> distribution.fwd(samples).round(4)
89        array([[0.6536, 0.115 , 0.9503],
90               [0.4822, 0.8725, 0.2123]])
91        >>> mesh = numpy.meshgrid([.4, .5, .6], [.4, .5, .6])
92        >>> distribution.inv(mesh).round(4)
93        array([[[-0.2   ,  0.    ,  0.2   ],
94                [-0.2   ,  0.    ,  0.2   ],
95                [-0.2   ,  0.    ,  0.2   ]],
96        <BLANKLINE>
97               [[-0.2699, -0.1738, -0.0741],
98                [-0.1011,  0.    ,  0.1011],
99                [ 0.0741,  0.1738,  0.2699]]])
100
101    """
102
103    def __init__(self, dist, df, covariance):
104        """
105        Args:
106            dist (Distribution):
107                The distribution to wrap in a copula.
108            R (numpy.ndarray):
109                Covariance matrix defining dependencies..
110            df (float):
111                The degree of freedom in the underlying student-t distribution.
112        """
113        assert len(dist) == len(covariance)
114        df = float(df)
115        covariance = numpy.asfarray(covariance)
116        super(TCopula, self).__init__(
117            dist=dist,
118            trans=t_copula(df, covariance, dist._rotation),
119            repr_args=[dist, df, covariance.tolist()],
120        )
121