def _parse_shocks(optim_paras, params): """Parse the shock parameters and create the Cholesky factor.""" if sum(f"shocks_{i}" in params.index for i in ["sdcorr", "cov", "chol"]) >= 2: raise ValueError("It is not allowed to define multiple shock matrices.") elif "shocks_sdcorr" in params.index: sorted_shocks = _sort_shocks_sdcorr(optim_paras, params.loc["shocks_sdcorr"]) cov = sdcorr_params_to_matrix(sorted_shocks) optim_paras["shocks_cholesky"] = robust_cholesky(cov) elif "shocks_cov" in params.index: sorted_shocks = _sort_shocks_cov_chol( optim_paras, params.loc["shocks_cov"], "cov" ) cov = cov_params_to_matrix(sorted_shocks) optim_paras["shocks_cholesky"] = robust_cholesky(cov) elif "shocks_chol" in params.index: sorted_shocks = _sort_shocks_cov_chol( optim_paras, params.loc["shocks_chol"], "chol" ) optim_paras["shocks_cholesky"] = chol_params_to_lower_triangular_matrix( sorted_shocks ) else: raise NotImplementedError return optim_paras
def covariance_from_internal_jacobian(internal_values, constr): r"""Jacobian of ``covariance_from_internal``. The following result is motivated by https://tinyurl.com/y4pbfxst, which is shortly presented again here. For notation see the explaination at the beginning of the module. Explaination of the result -------------------------- We want to differentiate the graph internal --> cholesky --> cov --> external Define :math:`x' := \text{vec}(X)` and :math:`c' := \text{vec}(S)`, where :math:`X` denotes the Cholesky factor of the covariance matrix :math:`S`. We then first differentiate the part "cholesky --> cov" using the result stated in the tinyurl above to get .. math:: J' := \frac{\mathrm{d}c'}{\mathrm{d}x'} = (I + K)(X \otimes I) \,, where :math:`K` denotes the commutation matrix. Using this intermediate result we can compute the jacobian as .. math:: \frac{\mathrm{d}c}{\mathrm{d}x} = L J' D \,, where :math:`c := \text{external}` and :math:`x := \text{internal}`. Args: internal_values (np.ndarray): Cholesky factors stored in an "internal" format. Returns: deriv: The Jacobian matrix. """ chol = chol_params_to_lower_triangular_matrix(internal_values) dim = len(chol) K = _commutation_matrix(dim) L = _elimination_matrix(dim) left = np.eye(dim ** 2) + K right = np.kron(chol, np.eye(dim)) intermediate = left @ right deriv = L @ intermediate @ L.T return deriv
def _parse_shocks(optim_paras, params): """Parse the shock parameters and create the Cholesky factor.""" if sum(f"shocks_{i}" in params.index for i in ["sdcorr", "cov", "chol"]) >= 2: raise ValueError("It is not allowed to define multiple shock matrices.") elif "shocks_sdcorr" in params.index: cov = sdcorr_params_to_matrix(params.loc["shocks_sdcorr"]) optim_paras["shocks_cholesky"] = robust_cholesky(cov) elif "shocks_cov" in params.index: cov = cov_params_to_matrix(params.loc["shocks_cov"]) optim_paras["shocks_cholesky"] = robust_cholesky(cov) elif "shocks_chol" in params.index: optim_paras["shocks_cholesky"] = chol_params_to_lower_triangular_matrix( params.loc["shocks_chol"] ) else: raise KeyError("No shock matrix is specified.") return optim_paras
def test_chol_params_to_lower_triangular_matrix(): calculated = chol_params_to_lower_triangular_matrix(pd.Series([1, 2, 3])) expected = np.array([[1, 0], [2, 3]]) aaae(calculated, expected)
def covariance_from_internal(internal_values, constr): """Undo a cholesky reparametrization.""" chol = chol_params_to_lower_triangular_matrix(internal_values) cov = chol @ chol.T return cov[np.tril_indices(len(chol))]
def sdcorr_from_internal_jacobian(internal_values, constr): r"""Derivative of ``sdcorr_from_internal``. The following result is motivated by https://tinyurl.com/y6ytlyd9; however since the question was formulated with an error the result here is adjusted slightly. In particular, in the answer by user 'greg', the matrix :math:`A` should have been defined as :math:`A = \text{diag}(||x_1||, \dots, ||x_n||)` , where :math:`||x_i||` denotes the euclidian norm of the the i-th row of :math:`X` (the Cholesky factor). For notation see the explaination at the beginning of the module or the question on the tinyurl. The variable names in this function are chosen to be consistent with the tinyurl link. Explaination on the result -------------------------- We want to differentiate the graph internal --> cholesky --> cov --> corr-mat --> mod. corr-mat --> external where mod. corr-mat denotes the modified correlation matrix which has the standard deviations stored on its diagonal. Let :math:`x := \text{internal}` and :math:`p := \text{external}`. Then we want to compute the quantity .. math:: \frac{\mathrm{d} p}{\mathrm{d} x} . As before we consider an intermediate result first. Namely we define :math:`A` as above, :math:`V := A^{-1}` and :math:`P := V S V + A - I`. The attentive reader might now notice that :math:`P` is the modified correlation matrix. At last we write :math:`x' := \text{vec}(X)` and :math:`p' := \text{vec}(P)`. Using the result stated in the tinyurl above, adjusted for the different matrix :math:`A`, we can compute the quantity :math:`(\mathrm{d} p'/ \mathrm{d} x')`. Finally, since we can define transformation matrices :math:`T` and :math:`L` to get :math:`p = T p'` and :math:`x = L x'` (where :math:`L` denotes the elimination matrix with corresponding duplication matrix :math:`D`), we can get our final result as .. math:: \frac{\mathrm{d}p}{\mathrm{d}x} = T \frac{\mathrm{d}p'}{\mathrm{d}x'} D Args: internal_values (np.ndarray): Cholesky factors stored in an "internal" format. Returns: deriv: The Jacobian matrix. """ X = chol_params_to_lower_triangular_matrix(internal_values) dim = len(X) identity = np.eye(dim) S = X @ X.T # the wrong formulation in the tinyurl stated: A = np.multiply(I, X) A = np.sqrt(np.multiply(identity, S)) V = np.linalg.inv(A) K = _commutation_matrix(dim) Y = np.diag(identity.ravel("F")) # with the wrong formulation in the tinyurl we would have had U = Y norms = np.sqrt((X ** 2).sum(axis=1).reshape(-1, 1)) XX = X / norms U = Y @ np.kron(identity, XX) @ K N = np.kron(identity, X) @ K + np.kron(X, identity) VS = V @ S B = np.kron(V, V) H = np.kron(VS, identity) J = np.kron(identity, VS) intermediate = U + B @ N - (H + J) @ B @ U T = _transformation_matrix(dim) D = _duplication_matrix(dim) deriv = T @ intermediate @ D return deriv
def sdcorr_from_internal(internal_values, constr): """Undo a cholesky reparametrization.""" chol = chol_params_to_lower_triangular_matrix(internal_values) cov = chol @ chol.T return cov_matrix_to_sdcorr_params(cov)