def test_repeated_updates_converge_with_nonzero_delta(self):
        """Test repeated updates converge to a fixed point with non-zero delta."""

        random_seed = 0
        random_state = check_random_state(random_seed)

        n_features = 30
        n_components = 11
        n_samples = 320
        max_iter = 1000
        tolerance = 1e-4

        X = random_state.uniform(size=(n_samples, n_features))
        K = X.dot(X.T)

        C = right_stochastic_matrix((n_components, n_samples),
                                    random_state=random_state)
        S = right_stochastic_matrix((n_samples, n_components),
                                    random_state=random_state)

        self.assertTrue(np.allclose(C.sum(axis=1), 1, tolerance))
        self.assertTrue(np.allclose(S.sum(axis=1), 1, tolerance))

        delta = 0.3
        aa = KernelAA(n_components=n_components, delta=delta)

        aa.K = K
        aa.C = C
        aa.S = S

        aa._initialize_workspace()

        cost_delta = 1 + tolerance
        old_cost = aa._evaluate_cost()
        new_cost = old_cost
        n_iter = 0
        while abs(cost_delta) > tolerance and n_iter < max_iter:
            old_cost = new_cost
            error = aa._update_weights()
            self.assertEqual(error, 0)
            new_cost = aa._evaluate_cost()

            cost_delta = new_cost - old_cost

            self.assertTrue(cost_delta <= 0)

            n_iter += 1

        self.assertTrue(n_iter < max_iter)

        updated_S = aa.S
        self.assertTrue(np.allclose(updated_S.sum(axis=1), 1, 1e-12))
    def test_single_weights_update_reduces_cost_with_nonzero_delta(self):
        """Test single weights update reduces cost function with non-zero delta."""

        random_seed = 0
        random_state = check_random_state(random_seed)

        n_features = 50
        n_components = 5
        n_samples = 400

        X = random_state.uniform(size=(n_samples, n_features))
        K = X.dot(X.T)

        C = right_stochastic_matrix((n_components, n_samples),
                                    random_state=random_state)
        S = right_stochastic_matrix((n_samples, n_components),
                                    random_state=random_state)

        self.assertTrue(np.allclose(C.sum(axis=1), 1, 1e-12))
        self.assertTrue(np.allclose(S.sum(axis=1), 1, 1e-12))

        delta = 2.3
        aa = KernelAA(n_components=n_components, delta=delta)

        aa.K = K
        aa.C = C
        aa.S = S

        aa._initialize_workspace()

        initial_cost = aa._evaluate_cost()

        error = aa._update_weights()

        self.assertEqual(error, 0)

        final_cost = aa._evaluate_cost()

        self.assertTrue(final_cost <= initial_cost)
    def test_exact_solution_with_zero_delta_is_fixed_point(self):
        """Test exact solution for weights is fixed point of update step."""

        random_seed = 0
        random_state = check_random_state(random_seed)

        n_features = 30
        n_components = 10
        n_samples = 130
        tolerance = 1e-12

        basis = random_state.uniform(size=(n_components, n_features))

        S = right_stochastic_matrix((n_samples, n_components),
                                    random_state=random_state)

        archetype_indices = np.zeros(n_components, dtype='i8')
        for i in range(n_components):
            new_index = False
            current_index = 0

            while not new_index:
                new_index = True

                current_index = random_state.randint(low=0, high=n_samples)

                for index in archetype_indices:
                    if current_index == index:
                        new_index = False

            archetype_indices[i] = current_index

        C = np.zeros((n_components, n_samples))
        component = 0
        for index in archetype_indices:
            C[component, index] = 1.0
            for i in range(n_components):
                if i == component:
                    S[index, i] = 1.0
                else:
                    S[index, i] = 0.0
            component += 1

        X = S.dot(basis)
        basis_projection = C.dot(X)

        self.assertTrue(np.allclose(basis_projection, basis, tolerance))
        self.assertTrue(np.linalg.norm(X - S.dot(C.dot(X))) < tolerance)

        K = X.dot(X.T)

        delta = 0
        aa = KernelAA(n_components=n_components, delta=delta)

        aa.K = K
        aa.C = C
        aa.S = S

        aa._initialize_workspace()

        initial_cost = aa._evaluate_cost()

        error = aa._update_weights()

        self.assertEqual(error, 0)

        final_cost = aa._evaluate_cost()

        self.assertTrue(abs(final_cost - initial_cost) < tolerance)

        updated_S = aa.S

        self.assertTrue(np.allclose(updated_S.sum(axis=1), 1, 1e-12))
        self.assertTrue(np.allclose(updated_S, S, tolerance))