def test_init_integer(self): """Tests initializations which only specify the order.""" # Checks for a zero order Lfd object lfd_0 = LinearDifferentialOperator(order=0) weightfd = [FDataBasis(Constant(domain_range=(0, 1)), 1)] self._assert_equal_weights( lfd_0.weights, weightfd, "Wrong list of weight functions of the linear operator") # Checks for a non zero order Lfd object lfd_3 = LinearDifferentialOperator(3) consfd = FDataBasis( Constant(domain_range=(0, 1)), [[0], [0], [0], [1]], ) bwtlist3 = list(consfd) self._assert_equal_weights( lfd_3.weights, bwtlist3, "Wrong list of weight functions of the linear operator") # Negative order must fail with np.testing.assert_raises(ValueError): LinearDifferentialOperator(-1)
def test_basis_fpca_transform_result(self): n_basis = 9 n_components = 3 fd_data = fetch_weather()['data'].coordinates[0] fd_data = FDataGrid(np.squeeze(fd_data.data_matrix), np.arange(0.5, 365, 1)) # initialize basis data basis = Fourier(n_basis=n_basis, domain_range=(0, 365)) fd_basis = fd_data.to_basis(basis) fpca = FPCA(n_components=n_components, regularization=TikhonovRegularization( LinearDifferentialOperator(2), regularization_parameter=1e5)) fpca.fit(fd_basis) scores = fpca.transform(fd_basis) # results obtained using Ramsay's R package results = [[-7.68307641e+01, 5.69034443e+01, -1.22440149e+01], [-9.02873996e+01, 1.46262257e+01, -1.78574536e+01], [-8.21155683e+01, 3.19159491e+01, -2.56212328e+01], [-1.14163637e+02, 3.66425562e+01, -1.00810836e+01], [-6.97263223e+01, 1.22817168e+01, -2.39417618e+01], [-6.41886364e+01, -1.07261045e+01, -1.10587407e+01], [1.35824412e+02, 2.03484658e+01, -9.04815324e+00], [-1.46816399e+01, -2.66867491e+01, -1.20233465e+01], [1.02507511e+00, -2.29840736e+01, -9.06081296e+00], [-3.62936903e+01, -2.09520442e+01, -1.14799951e+01], [-4.20649313e+01, -1.13618094e+01, -6.24909009e+00], [-7.38115985e+01, -3.18423866e+01, -1.50298626e+01], [-6.69822456e+01, -3.35518632e+01, -1.25167352e+01], [-1.03534763e+02, -1.29513941e+01, -1.49103879e+01], [-1.04542036e+02, -1.36794907e+01, -1.41555965e+01], [-7.35863347e+00, -1.41171956e+01, -2.97562788e+00], [7.28804530e+00, -5.34421830e+01, -3.39823418e+00], [5.59974094e+01, -4.02154080e+01, 3.78800103e-01], [1.80778702e+02, 1.87798201e+01, -1.99043247e+01], [-3.69700617e+00, -4.19441020e+01, 6.45820740e+00], [3.76527216e+01, -4.23056953e+01, 1.04221757e+01], [1.23850646e+02, -4.24648130e+01, -2.22336786e-01], [-7.23588457e+00, -1.20579536e+01, 2.07502089e+01], [-4.96871011e+01, 8.88483448e+00, 2.02882768e+01], [-1.36726355e+02, -1.86472599e+01, 1.89076217e+01], [-1.83878661e+02, 4.12118550e+01, 1.78960356e+01], [-1.81568820e+02, 5.20817910e+01, 2.01078870e+01], [-5.08775852e+01, 1.34600555e+01, 3.18602712e+01], [-1.37633866e+02, 7.50809631e+01, 2.42320782e+01], [4.98276375e+01, 1.33401270e+00, 3.50611066e+01], [1.51149934e+02, -5.47417776e+01, 3.97592325e+01], [1.58366096e+02, -3.80762686e+01, -5.62415023e+00], [2.17139548e+02, 6.34055987e+01, -1.98853635e+01], [2.33615480e+02, -7.90787574e-02, 2.69069525e+00], [3.45371437e+02, 9.58703622e+01, 8.47570770e+00]] results = np.array(results) # compare results np.testing.assert_allclose(scores, results, atol=1e-7)
def test_init_default(self): """Tests default initialization (do not penalize).""" lfd = LinearDifferentialOperator() weightfd = [FDataBasis(Constant((0, 1)), 0)] np.testing.assert_equal( lfd.weights, weightfd, "Wrong list of weight functions of the linear operator")
def test_init_wrong_params(self): # Check specifying both arguments fail with np.testing.assert_raises(ValueError): LinearDifferentialOperator(1, weights=[1, 1]) # Check invalid domain range monomial = Monomial((0, 1), n_basis=3) fdlist = [FDataBasis(monomial, [1, 2, 3])] with np.testing.assert_raises(ValueError): LinearDifferentialOperator(weights=fdlist, domain_range=(0, 2)) # Check wrong types fail with np.testing.assert_raises(ValueError): LinearDifferentialOperator(weights=['a']) with np.testing.assert_raises(ValueError): LinearDifferentialOperator(weights='a')
def _test_penalty(self, basis, linear_diff_op, atol=0, result=None): operator = LinearDifferentialOperator(linear_diff_op) penalty = gramian_matrix(operator, basis) numerical_penalty = gramian_matrix_numerical(operator, basis) np.testing.assert_allclose(penalty, numerical_penalty, atol=atol) if result is not None: np.testing.assert_allclose(penalty, result, atol=atol)
def test_vector_valued_smoothing(self): X, _ = skfda.datasets.fetch_weather(return_X_y=True) basis_dim = skfda.representation.basis.Fourier( n_basis=7, domain_range=X.domain_range) basis = skfda.representation.basis.VectorValued([basis_dim] * 2) for method in smoothing.BasisSmoother.SolverMethod: with self.subTest(method=method): basis_smoother = smoothing.BasisSmoother( basis, regularization=TikhonovRegularization( LinearDifferentialOperator(2)), return_basis=True, smoothing_parameter=1, method=method) basis_smoother_dim = smoothing.BasisSmoother( basis_dim, regularization=TikhonovRegularization( LinearDifferentialOperator(2)), return_basis=True, smoothing_parameter=1, method=method) X_basis = basis_smoother.fit_transform(X) self.assertEqual(X_basis.dim_codomain, 2) self.assertEqual(X_basis.coordinates[0].basis, basis_dim) np.testing.assert_allclose( X_basis.coordinates[0].coefficients, basis_smoother_dim.fit_transform( X.coordinates[0]).coefficients) self.assertEqual(X_basis.coordinates[1].basis, basis_dim) np.testing.assert_allclose( X_basis.coordinates[1].coefficients, basis_smoother_dim.fit_transform( X.coordinates[1]).coefficients)
def test_init_list_int(self): """Tests initializations with integer weights.""" coefficients = [1, 3, 4, 5, 6, 7] constant = Constant((0, 1)) fd = FDataBasis(constant, np.array(coefficients).reshape(-1, 1)) lfd = LinearDifferentialOperator(weights=coefficients) np.testing.assert_equal( lfd.weights, list(fd), "Wrong list of weight functions of the linear operator")
def test_init_list_fdatabasis(self): """Test initialization with functional weights.""" n_basis = 4 n_weights = 6 monomial = Monomial((0, 1), n_basis=n_basis) weights = np.arange(n_basis * n_weights).reshape((n_weights, n_basis)) fd = FDataBasis(monomial, weights) fdlist = [FDataBasis(monomial, w) for w in weights] lfd = LinearDifferentialOperator(weights=fdlist) np.testing.assert_equal( lfd.weights, list(fd), "Wrong list of weight functions of the linear operator") # Check failure if intervals do not match constant = Constant((0, 2)) fdlist.append(FDataBasis(constant, 1)) with np.testing.assert_raises(ValueError): LinearDifferentialOperator(weights=fdlist)
def test_bspline_penalty_special_case(self): basis = BSpline(n_basis=5) res = np.array([[1152., -2016., 1152., -288., 0.], [-2016., 3600., -2304., 1008., -288.], [1152., -2304., 2304., -2304., 1152.], [-288., 1008., -2304., 3600., -2016.], [0., -288., 1152., -2016., 1152.]]) operator = LinearDifferentialOperator(basis.order - 1) penalty = gramian_matrix(operator, basis) numerical_penalty = gramian_matrix_numerical(operator, basis) np.testing.assert_allclose(penalty, res) np.testing.assert_allclose(numerical_penalty, res)
def test_qr(self): t = np.linspace(0, 1, 5) x = np.sin(2 * np.pi * t) + np.cos(2 * np.pi * t) basis = BSpline((0, 1), n_basis=5) fd = FDataGrid(data_matrix=x, sample_points=t) smoother = smoothing.BasisSmoother( basis=basis, smoothing_parameter=10, regularization=TikhonovRegularization( LinearDifferentialOperator(2)), method='qr', return_basis=True) fd_basis = smoother.fit_transform(fd) np.testing.assert_array_almost_equal( fd_basis.coefficients.round(2), np.array([[0.60, 0.47, 0.20, -0.07, -0.20]]))
def test_basis_fpca_fit_result(self): n_basis = 9 n_components = 3 fd_data = fetch_weather()['data'].coordinates[0] fd_data = FDataGrid(np.squeeze(fd_data.data_matrix), np.arange(0.5, 365, 1)) # initialize basis data basis = Fourier(n_basis=n_basis, domain_range=(0, 365)) fd_basis = fd_data.to_basis(basis) fpca = FPCA(n_components=n_components, regularization=TikhonovRegularization( LinearDifferentialOperator(2), regularization_parameter=1e5)) fpca.fit(fd_basis) # results obtained using Ramsay's R package results = [[ 0.92407552, 0.13544888, 0.35399023, 0.00805966, -0.02148108, -0.01709549, -0.00208469, -0.00297439, -0.00308224 ], [ -0.33314436, -0.05116842, 0.89443418, 0.14673902, 0.21559073, 0.02046924, 0.02203431, -0.00787185, 0.00247492 ], [ -0.14241092, 0.92131899, 0.00514715, 0.23391411, -0.19497613, 0.09800817, 0.01754439, -0.00205874, 0.01438185 ]] results = np.array(results) # compare results obtained using this library. There are slight # variations due to the fact that we are in two different packages for i in range(n_components): if np.sign(fpca.components_.coefficients[i][0]) != np.sign( results[i][0]): results[i, :] *= -1 np.testing.assert_allclose(fpca.components_.coefficients, results, atol=1e-7)
def test_monomial_smoothing(self): # It does not have much sense to apply smoothing in this basic case # where the fit is very good but its just for testing purposes t = np.linspace(0, 1, 5) x = np.sin(2 * np.pi * t) + np.cos(2 * np.pi * t) basis = Monomial(n_basis=4) fd = FDataGrid(data_matrix=x, sample_points=t) smoother = smoothing.BasisSmoother( basis=basis, smoothing_parameter=1, regularization=TikhonovRegularization( LinearDifferentialOperator(2)), return_basis=True) fd_basis = smoother.fit_transform(fd) # These results where extracted from the R package fda np.testing.assert_array_almost_equal( fd_basis.coefficients.round(2), np.array([[0.61, -0.88, 0.06, 0.02]]))
def test_regression_mixed_regularization(self): multivariate = np.array([[0, 0], [2, 7], [1, 7], [3, 9], [4, 16], [2, 14], [3, 5]]) X = [ multivariate, FDataBasis(Monomial(n_basis=3), [[1, 0, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]]) ] # y = 2 + sum([3, 1] * array) + int(3 * function) intercept = 2 coefs_multivariate = np.array([3, 1]) y_integral = np.array([3, 3 / 2, 1, 4, 3, 3 / 2, 1]) y_sum = multivariate @ coefs_multivariate y = 2 + y_sum + y_integral scalar = LinearRegression(regularization=[ TikhonovRegularization(lambda x: x), TikhonovRegularization(LinearDifferentialOperator(2)) ]) scalar.fit(X, y) np.testing.assert_allclose(scalar.intercept_, intercept, atol=0.01) np.testing.assert_allclose(scalar.coef_[0], [2.536739, 1.072186], atol=0.01) np.testing.assert_allclose(scalar.coef_[1].coefficients, [[2.125676, 2.450782, 5.808745e-4]], atol=0.01) y_pred = scalar.predict(X) np.testing.assert_allclose(y_pred, [ 5.349035, 16.456464, 13.361185, 23.930295, 32.650965, 23.961766, 16.29029 ], atol=0.01)
def smooth_grids(self, param_values: list = None, smoother=None, scorer=LinearSmootherGeneralizedCVScorer(), return_history=False): ''' Search hyperparameter of user's estimator, then transform datagrid with it If no param_values specified, algorithm will try 100 values between 10**-8 et 10**8 smoother must be either a skfda.BasisSmoother or a list of skfda.BasisSmoother one by variable ''' if param_values is None: param_values = np.logspace(-8, 8, num=100) if smoother is None: print("Default Smoother used") smoother = BasisSmoother(basis, regularization=TikhonovRegularization( LinearDifferentialOperator(order=2))) if isinstance(smoother, list) or isinstance(smoother, np.ndarray): if len(smoother) != self._nVar: raise ValueError( "number of smoothers must be equal to the number of variable or equal to 1" ) else: smoother.domain_range = self.init_grid.domain_range smoother = [smoother] * self._nVar smoothed_grids = [] history = [] print("Smoothing data...") for i in range(self._nVar): data_grid = self.coordinates_grids[i].copy() grid_search = SmoothingParameterSearch(estimator=smoother[i], param_values=param_values, scoring=scorer) _ = grid_search.fit(data_grid) history.append(grid_search.cv_results_['mean_test_score']) best_est = grid_search.best_estimator_ smoothed_grids.append(best_est.fit_transform(data_grid)) print("Smoothing Done") self.coordinates_grids = smoothed_grids self._smoothed = True self.coordinates_grids_dx1 = [] self.coordinates_grids_dx2 = [] self.coefficients = [] for i in range(self._nVar): basis_representation = self.coordinates_grids[i].copy().to_basis( basis=smoother[i].basis) self.coefficients.append(basis_representation.coefficients) self.coordinates_grids_dx1.append( basis_representation.derivative(order=1).to_grid( self.sample_points)) self.coordinates_grids_dx2.append( basis_representation.derivative(order=2).to_grid( self.sample_points)) self.coefficients = np.array(self.coefficients) if return_history: return np.array(history) else: return None
def test_grid_fpca_regularization_fit_result(self): n_components = 1 fd_data = fetch_weather()['data'].coordinates[0] fd_data = FDataGrid(np.squeeze(fd_data.data_matrix), np.arange(0.5, 365, 1)) fpca = FPCA(n_components=n_components, weights=[1] * 365, regularization=TikhonovRegularization( LinearDifferentialOperator(2))) fpca.fit(fd_data) # results obtained using fda.usc for the first component results = [[ -0.06961236, -0.07027042, -0.07090496, -0.07138247, -0.07162215, -0.07202264, -0.07264893, -0.07279174, -0.07274672, -0.07300075, -0.07365471, -0.07489002, -0.07617455, -0.07658708, -0.07551923, -0.07375128, -0.0723776, -0.07138373, -0.07080555, -0.07111745, -0.0721514, -0.07395427, -0.07558341, -0.07650959, -0.0766541, -0.07641352, -0.07660864, -0.07669081, -0.0765396, -0.07640671, -0.07634668, -0.07626304, -0.07603638, -0.07549114, -0.07410347, -0.07181791, -0.06955356, -0.06824034, -0.06834077, -0.06944125, -0.07133598, -0.07341109, -0.07471501, -0.07568844, -0.07631904, -0.07647264, -0.07629453, -0.07598431, -0.07628157, -0.07654062, -0.07616026, -0.07527189, -0.07426683, -0.07267961, -0.07079998, -0.06927394, -0.068412, -0.06838534, -0.06888439, -0.0695309, -0.07005508, -0.07066637, -0.07167196, -0.07266978, -0.07275299, -0.07235183, -0.07207819, -0.07159814, -0.07077697, -0.06977026, -0.0691952, -0.06965756, -0.07058327, -0.07075751, -0.07025415, -0.06954233, -0.06899785, -0.06891026, -0.06887079, -0.06862183, -0.06830082, -0.06777765, -0.06700202, -0.06639394, -0.06582435, -0.06514987, -0.06467236, -0.06425272, -0.06359187, -0.062922, -0.06300068, -0.06325494, -0.06316979, -0.06296254, -0.06246343, -0.06136836, -0.0600936, -0.05910688, -0.05840872, -0.0576547, -0.05655684, -0.05546518, -0.05484433, -0.05465746, -0.05449286, -0.05397004, -0.05300742, -0.05196686, -0.05133129, -0.05064617, -0.04973418, -0.04855687, -0.04714356, -0.04588103, -0.04547284, -0.04571493, -0.04580704, -0.04523509, -0.04457293, -0.04405309, -0.04338468, -0.04243512, -0.04137278, -0.04047946, -0.03984531, -0.03931376, -0.0388847, -0.03888507, -0.03908662, -0.03877577, -0.03830952, -0.03802713, -0.03773521, -0.03752388, -0.03743759, -0.03714113, -0.03668387, -0.0363703, -0.03642288, -0.03633051, -0.03574618, -0.03486536, -0.03357797, -0.03209969, -0.0306837, -0.02963987, -0.029102, -0.0291513, -0.02932013, -0.02912619, -0.02869407, -0.02801974, -0.02732363, -0.02690451, -0.02676622, -0.0267323, -0.02664896, -0.02661708, -0.02637166, -0.02577496, -0.02490428, -0.02410813, -0.02340367, -0.02283356, -0.02246305, -0.0224229, -0.0225435, -0.02295603, -0.02324663, -0.02310005, -0.02266893, -0.02221522, -0.02168056, -0.02129419, -0.02064909, -0.02007801, -0.01979083, -0.01979541, -0.01978879, -0.01954269, -0.0191623, -0.01879572, -0.01849678, -0.01810297, -0.01769666, -0.01753802, -0.01794351, -0.01871307, -0.01930005, -0.01933, -0.01901017, -0.01873486, -0.01861838, -0.01870777, -0.01879, -0.01904219, -0.01945078, -0.0200607, -0.02076936, -0.02100213, -0.02071439, -0.02052113, -0.02076313, -0.02128468, -0.02175631, -0.02206387, -0.02201054, -0.02172142, -0.02143092, -0.02133647, -0.02144956, -0.02176286, -0.02212579, -0.02243861, -0.02278316, -0.02304113, -0.02313356, -0.02349275, -0.02417028, -0.0245954, -0.0244062, -0.02388557, -0.02374682, -0.02401071, -0.02431126, -0.02433125, -0.02427656, -0.02430442, -0.02424977, -0.02401619, -0.02402294, -0.02415424, -0.02413262, -0.02404076, -0.02397651, -0.0243893, -0.0253322, -0.02664395, -0.0278802, -0.02877936, -0.02927182, -0.02937318, -0.02926277, -0.02931632, -0.02957945, -0.02982133, -0.03023224, -0.03060406, -0.03066011, -0.03070932, -0.03116429, -0.03179009, -0.03198094, -0.03149462, -0.03082037, -0.03041594, -0.0303307, -0.03028465, -0.03052841, -0.0311837, -0.03199307, -0.03262025, -0.03345083, -0.03442665, -0.03521313, -0.0356433, -0.03606037, -0.03677406, -0.03735165, -0.03746578, -0.03744154, -0.03752143, -0.03780898, -0.03837639, -0.03903232, -0.03911629, -0.03857567, -0.03816592, -0.03819285, -0.03818405, -0.03801684, -0.03788493, -0.03823232, -0.03906142, -0.04023251, -0.04112434, -0.04188011, -0.04254759, -0.043, -0.04340181, -0.04412687, -0.04484482, -0.04577669, -0.04700832, -0.04781373, -0.04842662, -0.04923723, -0.05007637, -0.05037817, -0.05009794, -0.04994083, -0.05012712, -0.05094001, -0.05216065, -0.05350458, -0.05469781, -0.05566309, -0.05641011, -0.05688106, -0.05730818, -0.05759156, -0.05763771, -0.05760073, -0.05766117, -0.05794587, -0.05816696, -0.0584046, -0.05905105, -0.06014331, -0.06142231, -0.06270788, -0.06388225, -0.06426245, -0.06386721, -0.0634656, -0.06358049, -0.06442514, -0.06570047, -0.06694328, -0.0682621, -0.06897846, -0.06896583, -0.06854621, -0.06797142, -0.06763755, -0.06784024, -0.06844314, -0.06918567, -0.07021928, -0.07148473, -0.07232504, -0.07272276, -0.07287021, -0.07289836, -0.07271531, -0.07239956, -0.07214086, -0.07170078, -0.07081195, -0.06955202, -0.06825156, -0.06690167, -0.06617102, -0.06683291, -0.06887539, -0.07089424, -0.07174837, -0.07150888, -0.07070378, -0.06960066, -0.06842496, -0.06777666, -0.06728403, -0.06681262, -0.06679066 ]] results = np.array(results) # compare results obtained using this library. There are slight # variations due to the fact that we are in two different packages for i in range(n_components): if np.sign(fpca.components_.data_matrix[i][0]) != np.sign( results[i][0]): results[i, :] *= -1 np.testing.assert_allclose(fpca.components_.data_matrix.reshape( fpca.components_.data_matrix.shape[:-1]), results, rtol=1e-2)
def test_regression_regularization(self): x_basis = Monomial(n_basis=7) x_fd = FDataBasis(x_basis, np.identity(7)) beta_basis = Fourier(n_basis=5) beta_fd = FDataBasis(beta_basis, [1.0403, 0, 0, 0, 0]) y = [ 1.0000684777229512, 0.1623672257830915, 0.08521053851548224, 0.08514200869281137, 0.09529138749665378, 0.10549625973303875, 0.11384314859153018 ] y_pred_compare = [ 0.890341, 0.370162, 0.196773, 0.110079, 0.058063, 0.023385, -0.001384 ] scalar = LinearRegression(coef_basis=[beta_basis], regularization=TikhonovRegularization( LinearDifferentialOperator(2))) scalar.fit(x_fd, y) np.testing.assert_allclose(scalar.coef_[0].coefficients, beta_fd.coefficients, atol=1e-3) np.testing.assert_allclose(scalar.intercept_, -0.15, atol=1e-4) y_pred = scalar.predict(x_fd) np.testing.assert_allclose(y_pred, y_pred_compare, atol=1e-4) x_basis = Monomial(n_basis=3) x_fd = FDataBasis(x_basis, [[1, 0, 0], [0, 1, 0], [0, 0, 1], [2, 0, 1]]) beta_fd = FDataBasis(x_basis, [3, 2, 1]) y = [1 + 13 / 3, 1 + 29 / 12, 1 + 17 / 10, 1 + 311 / 30] # Non regularized scalar = LinearRegression() scalar.fit(x_fd, y) np.testing.assert_allclose(scalar.coef_[0].coefficients, beta_fd.coefficients) np.testing.assert_allclose(scalar.intercept_, 1) y_pred = scalar.predict(x_fd) np.testing.assert_allclose(y_pred, y) # Regularized beta_fd_reg = FDataBasis(x_basis, [2.812, 3.043, 0]) y_reg = [5.333, 3.419, 2.697, 11.366] scalar_reg = LinearRegression(regularization=TikhonovRegularization( LinearDifferentialOperator(2))) scalar_reg.fit(x_fd, y) np.testing.assert_allclose(scalar_reg.coef_[0].coefficients, beta_fd_reg.coefficients, atol=0.001) np.testing.assert_allclose(scalar_reg.intercept_, 0.998, atol=0.001) y_pred = scalar_reg.predict(x_fd) np.testing.assert_allclose(y_pred, y_reg, atol=0.001)
def get_fpca(fdo): """ Performs functional PCA on funtional data object and returns results. Args: fdo (object): Functional data object. Returns: dict: Functional PCA results, includes the following keys: - 'C' : Centered coefficients matrix 'C' - 'Cm' : Mean coefficients 'Cm' - 'inertia' : Inertia - 'W' : Function-to-discrete metric equivalence matrix 'W' - 'M' : Weighting matrix 'M' - 'values' : PCs values - 'pval' : Percentage of variance of PCs. - 'vecnotWM': PCs vectors - 'vectors' : PCs weighted vectors - 'axes' : ? #TODO Etienne? - 'pc' : PCs projected on the modes """ # get number of samples, basis, and dimensions nsam = fdo.n_samples nbas = fdo.n_basis ndim = len(fdo.dim_names) # if 3d-array coefficients, convert to a 2d-array (n_samples, n_basis) if ndim>1: C = np.zeros((nsam, nbas*ndim)) for k in range(ndim): j0 = nbas * k j1 = nbas * (k+1) C[:, j0:j1] = fdo.coefficients[:, :, k] else: C = fdo.coefficients # compute centered coefficients matrix by subtractig the mean Cm = np.mean(C, axis=0) Cc = C - Cm[np.newaxis,:] # get basis penalty matrix regularization = TikhonovRegularization(LinearDifferentialOperator(0)) penalty = regularization.penalty_matrix(fdo.basis) # compute crossed-covariance matrix of C and Inertia inertia = np.zeros(ndim) for k in range(ndim): j0 = nbas * k j1 = nbas * (k+1) V = Cc[:, j0:j1].T @ Cc[:, j0:j1] @ penalty / nsam inertia[k] = np.trace( V ) # compute weighting matrix 'M' to balance variables of different units M = np.zeros((ndim*nbas,ndim*nbas)) Mdeminv = M.copy() W = M.copy() aux = np.diag(np.ones(nbas)) for k in range(ndim): i0, j0 = nbas * k , nbas * k i1, j1 = nbas * (k+1), nbas * (k+1) M [i0:i1, j0:j1] = aux/inertia[k] Mdeminv[i0:i1, j0:j1] = aux*np.sqrt(inertia[k]) W [i0:i1, j0:j1] = penalty Mdem = np.sqrt(M); # compute function-to-discrete metric equivalence matrix 'W W = (W+W.T)/2. Wdem = np.linalg.cholesky(W).T Wdeminv = np.linalg.inv(Wdem) # compute crossed-covariance matrix 'V' V = Mdem @ Wdem @ Cc.T @ Cc @ Wdem.T @ Mdem / nsam # compute eigenvalues and eigenvectors pca_values, pca_vectors = np.linalg.eig(V) idx = pca_values.argsort()[::-1] pca_values = pca_values[idx] pca_vectors = pca_vectors[:,idx] pca_vectors_notWM = pca_vectors pca_vectors = Mdeminv @ Wdeminv @ pca_vectors # compute principal components projected on the modes pc = Cc @ W.T @ M @ pca_vectors # build PCA dictionary fpca = {} fpca['C' ] = C fpca['Cm' ] = Cm fpca['inertia' ] = inertia fpca['W' ] = W fpca['M' ] = M fpca['values' ] = pca_values fpca['pval' ] = 100 * pca_values.real / np.sum(pca_values.real) fpca['vecnotWM' ] = pca_vectors_notWM fpca['vectors' ] = pca_vectors fpca['axes' ] = pca_vectors * np.sqrt(pca_values)[:,np.newaxis].T fpca['pc' ] = pc.real # Warn if eigen values are not orthogonal v1 = fpca['vectors'][:,0].T @ W @ M @ fpca['vectors'][:,0] - 1. v2 = fpca['vectors'][:,0].T @ W @ M @ fpca['vectors'][:,1] if v1>1.e-10 or v2>1.e-10: print('Warning : Eigen values not orthogonal (%s, %s)' % (v1, v2)) return fpca