Esempio n. 1
0
 def test_valid_insert_operators_5(self):
     # Define A and cache
     A = np.zeros_like(self.true_A)
     A[2, 4], A[4, 2] = 1, 1  # x2 - x4
     A[4, 3] = 1  # x4 -> x3
     cache = GaussObsL0Pen(self.obs_data)
     # Should fail as 2,4 are adjacent
     try:
         ges.score_valid_insert_operators(2, 4, A, cache, debug=False)
         self.fail()
     except ValueError as e:
         print("OK:", e)
     try:
         ges.score_valid_insert_operators(4, 2, A, cache, debug=False)
         self.fail()
     except ValueError as e:
         print("OK:", e)
         # Should fail as 3,4 are adjacent
     try:
         ges.score_valid_insert_operators(3, 4, A, cache, debug=False)
         self.fail()
     except ValueError as e:
         print("OK:", e)
     try:
         ges.score_valid_insert_operators(4, 3, A, cache, debug=False)
         self.fail()
     except ValueError as e:
         print("OK:", e)
Esempio n. 2
0
 def test_valid_delete_operators_5(self):
     A = np.array([[0, 1, 1, 1],
                   [0, 0, 1, 1],
                   [1, 1, 0, 0],
                   [1, 1, 0, 0]])
     print("out:", utils.is_clique({2, 3}, A))
     cache = GaussObsL0Pen(self.obs_data)
     # Removing the edge X0 - X1 should yield three valid operators
     # operators, for:
     #   0. Invalid H = Ø, as NA_yx \ Ø = {X2,X3} is not a clique
     #   1. H = {X2}, as NA_yx \ H = {X3} is a clique
     #   2. H = {X3}, as NA_yx \ H = {X2} is a clique
     #   3. H = {X2,X3}, as NA_yx \ H = Ø is a clique
     output = ges.score_valid_delete_operators(0, 1, A, cache)
     print(output)
     self.assertEqual(3, len(output))
     # v-structure on X2, i.e orient X0 -> X2, X1 -> X2
     A1 = np.array([[0, 0, 1, 1],
                    [0, 0, 1, 1],
                    [0, 0, 0, 0],
                    [1, 1, 0, 0]])
     # v-structure on X3, i.e. orient X0 -> X3, X1 -> X3
     A2 = np.array([[0, 0, 1, 1],
                    [0, 0, 1, 1],
                    [1, 1, 0, 0],
                    [0, 0, 0, 0]])
     # v-structures on X2 and X3
     A3 = np.array([[0, 0, 1, 1],
                    [0, 0, 1, 1],
                    [0, 0, 0, 0],
                    [0, 0, 0, 0]])
     self.assertTrue(utils.member([op[1] for op in output], A1) is not None)
     self.assertTrue(utils.member([op[1] for op in output], A2) is not None)
     self.assertTrue(utils.member([op[1] for op in output], A3) is not None)
Esempio n. 3
0
 def test_parameters_obs(self):
     # Fails if data is not ndarray
     try:
         GaussObsL0Pen([self.obs_data])
         self.fail()
     except TypeError:
         pass
     except Exception:
         self.fail()
Esempio n. 4
0
 def test_valid_insert_operators_8(self):
     A = np.array([[0, 0, 1, 0],
                   [0, 0, 0, 1],
                   [1, 1, 0, 0],
                   [0, 0, 0, 0]])
     data = self.obs_data[:, 0:4]
     cache = GaussObsL0Pen(data)
     # There should no valid operator for x2 -> x3
     #   1. na_yx = set(), T0 = {0}
     #   2. for T=set(), na_yx U T = set() which is a clique, but does not
     #   contain a node in the semi-directed path 2->1->3
     #   3. for T = {0}, na_yx U T = {0} which is a clique, but does not
     #   contain a node in the semi-directed path 2->1->3
     valid_operators = ges.score_valid_insert_operators(3, 2, A, cache, debug=False)
     self.assertEqual(0, len(valid_operators))
Esempio n. 5
0
 def test_valid_delete_operators_2(self):
     A = np.array([[0, 0, 1, 0, 0],
                   [0, 0, 1, 0, 0],
                   [0, 0, 0, 1, 1],
                   [0, 0, 1, 0, 1],
                   [0, 0, 1, 1, 0]])
     cache = GaussObsL0Pen(self.obs_data)
     # Removing the edge X1 - X2 should yield one valid
     # operator, for:
     #   1. H = Ø, as NA_yx \ Ø = {X3, X4} is a clique
     output = ges.score_valid_delete_operators(1, 2, A, cache)
     self.assertEqual(1, len(output))
     true_A = A.copy()
     # Remove X1 - X2
     true_A[1, 2] = 0
     self.assertTrue((true_A == output[0][1]).all())
Esempio n. 6
0
 def test_valid_insert_operators_1b(self):
     # Define A and cache
     A = np.zeros_like(self.true_A)
     A[2, 4], A[4, 2] = 1, 1  # x2 - x4
     A[4, 3] = 1  # x4 -> x3
     cache = GaussObsL0Pen(self.obs_data)
     # there should only be one valid operator, as
     #   1. X0 has no neighbors in A, so T0 = {set()}
     #   2. na_yx is also an empty set, thus na_yx U T is a clique
     #   3. there are no semi-directed paths from y to x
     valid_operators = ges.score_valid_insert_operators(1, 0, A, cache, debug=False)
     self.assertEqual(1, len(valid_operators))
     _, new_A, _, _, _ = valid_operators[0]
     true_new_A = A.copy()
     true_new_A[1, 0] = 1
     self.assertTrue((new_A == true_new_A).all())
Esempio n. 7
0
 def test_valid_insert_operators_4b(self):
     # Define A and cache
     A = np.zeros_like(self.true_A)
     A[2, 4], A[4, 2] = 1, 1  # x2 - x4
     A[4, 3] = 1  # x4 -> x3
     cache = GaussObsL0Pen(self.obs_data)
     # there should be one valid operator, as T0 = set(), na_yx = set()
     #   1. insert(X2,X3,set()) should be valid
     #   2. na_yx U T = set() should be a clique
     #   3. there are no semi-directed paths between X3 and X2
     valid_operators = ges.score_valid_insert_operators(2, 3, A, cache, debug=False)
     self.assertEqual(1, len(valid_operators))
     # Test outcome of insert(2,3,set())
     _, new_A, _, _, _ = valid_operators[0]
     true_new_A = A.copy()
     true_new_A[2, 3] = 1
     self.assertTrue((new_A == true_new_A).all())
Esempio n. 8
0
 def test_valid_insert_operators_4a(self):
     # Define A and cache
     A = np.zeros_like(self.true_A)
     A[2, 4], A[4, 2] = 1, 1  # x2 - x4
     A[4, 3] = 1  # x4 -> x3
     cache = GaussObsL0Pen(self.obs_data)
     # there should be one valid operator, as T0 = set(), na_yx = {4}
     #   1. insert(X0,X2,set()) should be valid
     #   2. na_yx U T = {X4} should be a clique
     #   3. the semi-directed path X2-X4->X3 contains one node in na_yx U T
     valid_operators = ges.score_valid_insert_operators(3, 2, A, cache, debug=False)
     self.assertEqual(1, len(valid_operators))
     # Test outcome of insert(3,2,set())
     _, new_A, _, _, _ = valid_operators[0]
     true_new_A = A.copy()
     true_new_A[3, 2] = 1
     self.assertTrue((new_A == true_new_A).all())
Esempio n. 9
0
 def test_valid_turn_operators_10(self):
     # Check that all valid turn operators result in a different
     # essential graph
     G = 10
     p = 20
     for i in range(G):
         A = sempler.generators.dag_avg_deg(p, 3, 1, 1)
         cpdag = utils.dag_to_cpdag(A)
         W = A * np.random.uniform(1, 2, A.shape)
         obs_sample = sempler.LGANM(W, (0, 0), (0.5, 1)).sample(n=1000)
         cache = GaussObsL0Pen(obs_sample)
         fro, to = np.where(cpdag != 0)
         for (x, y) in zip(to, fro):
             valid_operators = ges.score_valid_turn_operators(x, y, cpdag, cache)
             # print(i,len(valid_operators))
             for (_, new_A, _, _, _) in valid_operators:
                 new_cpdag = ges.utils.pdag_to_cpdag(new_A)
                 self.assertFalse((cpdag == new_cpdag).all())
     print("\nChecked that valid turn operators result in different MEC for %i CPDAGs" % (i + 1))
Esempio n. 10
0
 def test_valid_delete_operators_4(self):
     A = np.array([[0, 1, 1, 0],
                   [0, 0, 1, 0],
                   [0, 1, 0, 1],
                   [0, 0, 1, 0]])
     cache = GaussObsL0Pen(self.obs_data)
     # Removing the edge X0 - X2 should yield two valid operators
     # operators, for:
     #   1. H = Ø, as NA_yx \ Ø = {X1} is a clique
     #   2. H = {1}, as NA_yx \ {X1} = Ø is a clique
     output = ges.score_valid_delete_operators(0, 2, A, cache)
     self.assertEqual(2, len(output))
     A1, A2 = A.copy(), A.copy()
     # Remove X2 - X4
     A1[0, 2], A2[0, 2] = 0, 0
     # Orient X2 -> X1
     A2[1, 2] = 0
     self.assertTrue(utils.member([op[1] for op in output], A1) is not None)
     self.assertTrue(utils.member([op[1] for op in output], A2) is not None)
Esempio n. 11
0
 def test_valid_insert_operators_6(self):
     A = np.array([[0, 0, 1, 0],
                   [0, 0, 1, 1],
                   [1, 1, 0, 0],
                   [0, 0, 0, 0]])
     data = self.obs_data[:, 0:4]
     cache = GaussObsL0Pen(data)
     # There should be one valid operator for x3 -> x2
     #   1. na_yx = {1}, T0 = {0}
     #   2. for T=set(), na_yx U T = {1} which is a clique, and
     #   contains a node in the semi-directed path 2-1->3
     #   3. for T = {0}, na_yx U T = {0,1} which is not a clique
     valid_operators = ges.score_valid_insert_operators(3, 2, A, cache, debug=False)
     self.assertEqual(1, len(valid_operators))
     # Test outcome of insert(3,2,set())
     _, new_A, _, _, _ = valid_operators[0]
     true_new_A = A.copy()
     true_new_A[3, 2] = 1
     self.assertTrue((new_A == true_new_A).all())
Esempio n. 12
0
 def test_valid_delete_operators_3(self):
     # Check symmetry of the delete operator when X - Y
     G = 100
     p = 20
     for i in range(G):
         A = sempler.generators.dag_avg_deg(p, 3, 1, 1)
         cpdag = utils.dag_to_cpdag(A)
         W = A * np.random.uniform(1, 2, A.shape)
         obs_sample = sempler.LGANM(W, (0, 0), (0.5, 1)).sample(n=1000)
         cache = GaussObsL0Pen(obs_sample)
         fro, to = np.where(utils.only_undirected(cpdag))
         # Test the operator to all undirected edges
         for (x, y) in zip(fro, to):
             output_a = ges.score_valid_delete_operators(x, y, cpdag, cache)
             output_b = ges.score_valid_delete_operators(y, x, cpdag, cache)
             for (op_a, op_b) in zip(output_a, output_b):
                 # Check resulting state is the same
                 self.assertTrue((op_a[1] == op_b[1]).all())
                 self.assertAlmostEqual(op_a[0], op_b[0])
     print("\nChecked equality of delete operator on undirected edges in %i CPDAGS" % (i + 1))
Esempio n. 13
0
 def test_valid_insert_operators_3a(self):
     # Define A and cache
     A = np.zeros_like(self.true_A)
     A[2, 4], A[4, 2] = 1, 1  # x2 - x4
     A[4, 3] = 1  # x4 -> x3
     cache = GaussObsL0Pen(self.obs_data)
     # there should be two valid operators, as T0 = {X4}
     #   1. insert(X1,X2,set()) should be valid
     #   2. and also insert(X1,X2,{X4}), as na_yx U T = {X4} and is a clique
     #   3. there are no semi-directed paths from y to x
     valid_operators = ges.score_valid_insert_operators(1, 2, A, cache, debug=False)
     self.assertEqual(2, len(valid_operators))
     # Test outcome of insert(0,2,set())
     _, new_A, _, _, _ = valid_operators[0]
     true_new_A = A.copy()
     true_new_A[1, 2] = 1
     self.assertTrue((new_A == true_new_A).all())
     # Test outcome of insert(1,2,4)
     _, new_A, _, _, _ = valid_operators[1]
     true_new_A = A.copy()
     true_new_A[1, 2], true_new_A[2, 4] = 1, 0
     self.assertTrue((new_A == true_new_A).all())
Esempio n. 14
0
class ScoreTests(unittest.TestCase):
    np.random.seed(12)
    true_A = np.array([[0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 1, 1],
                       [0, 0, 0, 0, 1], [0, 0, 0, 0, 0]])
    factorization = [(4, (2, 3)), (3, (2, )), (2, (0, 1)), (0, ()), (1, ())]
    true_B = true_A * np.random.uniform(1, 2, size=true_A.shape)
    scm = sempler.LGANM(true_B, (0, 0), (0.3, 0.4))
    p = len(true_A)
    n = 10000
    obs_data = scm.sample(n=n)
    obs_score = GaussObsL0Pen(obs_data)
    obs_score_raw = GaussObsL0Pen(obs_data, method='raw')

    # ------------------------------------------------------
    # White-box tests:
    #   testing the inner workings of the ges.scores module, e.g. the
    #   intermediate functions used to compute the likelihoods

    def test_mle_obs(self):
        # Check that the parameters are correctly estimated when
        # passing a subgraph to GaussObsL0Pen._mle_full
        for score in [self.obs_score, self.obs_score_raw]:
            print("Testing %s" % score)
            local_B = np.zeros_like(self.true_B)
            local_omegas = np.zeros(self.p)
            for (x, pa) in self.factorization:
                local_B[:, x], local_omegas[x] = score._mle_local(x, pa)
            full_B, full_omegas = score._mle_full(self.true_A)
            print("Locally estimated", local_B, local_omegas)
            print("Fully estimated", full_B, full_omegas)
            print("Truth", self.true_B, self.scm.variances)
            # Make sure zeros are respected
            self.assertTrue((local_B[self.true_A == 0] == 0).all())
            self.assertTrue((full_B[self.true_A == 0] == 0).all())
            # Make sure estimation of weights is similar
            self.assertTrue((local_B == full_B).all())
            # Make sure estimation of noise variances is similar
            self.assertTrue((local_omegas == full_omegas).all())
            # Compare with true model
            self.assertTrue(np.allclose(self.true_B, local_B, atol=5e-2))
            self.assertTrue(np.allclose(self.true_B, full_B, atol=5e-2))
            self.assertTrue(
                np.allclose(self.scm.variances, local_omegas, atol=1e-1))
            self.assertTrue(
                np.allclose(self.scm.variances, full_omegas, atol=1e-1))

    # ------------------------------------------------------
    # Black-box tests:
    #   Testing the behaviour of the "API" functions, i.e. the
    #   functions to compute the full/local
    #   observational/interventional BIC scores from a given DAG
    #   structure and the data

    def test_parameters_obs(self):
        # Fails if data is not ndarray
        try:
            GaussObsL0Pen([self.obs_data])
            self.fail()
        except TypeError:
            pass
        except Exception:
            self.fail()

    def test_full_score_obs(self):
        # Verify that the true adjacency yields a higher score than
        # the empty graph Compute score of true adjacency
        for score_fun in [self.obs_score, self.obs_score_raw]:
            print("Testing %s" % score_fun)
            true_score = score_fun.full_score(self.true_A)
            self.assertIsInstance(true_score, float)
            # Compute score of unconnected graph
            score = score_fun.full_score(np.zeros((self.p, self.p)))
            self.assertIsInstance(score, float)
            self.assertGreater(true_score, score)

    def test_score_decomposability_obs(self):
        # As a black-box test, make sure the score functions
        # preserve decomposability
        for score_fun in [self.obs_score, self.obs_score_raw]:
            print("Decomposability of observational score")
            print("Testing %s" % score_fun)
            full_score = score_fun.full_score(self.true_A)
            acc = 0
            for (j, pa) in self.factorization:
                local_score = score_fun.local_score(j, pa)
                print("  ", j, pa, local_score)
                acc += local_score
            print("Full vs. acc:", full_score, acc)
            self.assertAlmostEqual(full_score, acc, places=2)
Esempio n. 15
0
def fit_bic(data,
            A0=None,
            phases=['forward', 'backward', 'turning'],
            iterate=False,
            debug=0):
    """Run GES on the given data, using the Gaussian BIC score
    (l0-penalized Gaussian Likelihood). The data is not assumed to be
    centered, i.e. an intercept is fitted.

    To use a custom score, see ges.fit.

    Parameters
    ----------
    data : numpy.ndarray
        The n x p array containing the observations, where columns
        correspond to variables and rows to observations.
    A0 : numpy.ndarray, optional
        The initial CPDAG on which GES will run, where where `A0[i,j]
        != 0` implies the edge `i -> j` and `A[i,j] != 0 & A[j,i] !=
        0` implies the edge `i - j`. Defaults to the empty graph
        (i.e. matrix of zeros).
    phases : [{'forward', 'backward', 'turning'}*], optional
        Which phases of the GES procedure are run, and in which
        order. Defaults to `['forward', 'backward', 'turning']`.
    iterate : bool, default=False
        Indicates whether the given phases should be iterated more
        than once.
    debug : int, optional
        If larger than 0, debug are traces printed. Higher values
        correspond to increased verbosity.

    Returns
    -------
    estimate : numpy.ndarray
        The adjacency matrix of the estimated CPDAG.
    total_score : float
        The score of the estimate.

    Raises
    ------
    TypeError:
        If the type of some of the parameters was not expected,
        e.g. if data is not a numpy array.
    ValueError:
        If the value of some of the parameters is not appropriate,
        e.g. a wrong phase is specified.

    Example
    -------

    Data from a linear-gaussian SCM (generated using
    `sempler <https://github.com/juangamella/sempler>`__)

    >>> import numpy as np
    >>> data = np.array([[3.23125779, 3.24950062, 13.430682, 24.67939513],
    ...                  [1.90913354, -0.06843781, 6.93957057, 16.10164608],
    ...                  [2.68547149, 1.88351553, 8.78711076, 17.18557716],
    ...                  [0.16850822, 1.48067393, 5.35871419, 11.82895779],
    ...                  [0.07355872, 1.06857039, 2.05006096, 3.07611922]])

    Run GES using the gaussian BIC score:

    >>> import ges
    >>> ges.fit_bic(data)
    (array([[0, 1, 1, 0],
           [0, 0, 0, 0],
           [1, 1, 0, 1],
           [0, 1, 1, 0]]), 15.674267611628233)

    """
    # Initialize Gaussian BIC score (precomputes scatter matrices, sets up cache)
    cache = GaussObsL0Pen(data)
    # Unless indicated otherwise, initialize to the empty graph
    A0 = np.zeros((cache.p, cache.p)) if A0 is None else A0
    return fit(cache, None, A0, phases, iterate, debug)
Esempio n. 16
0
class TurnOperatorTests(unittest.TestCase):
    true_A = np.array([[0, 0, 1, 0, 0],
                       [0, 0, 1, 0, 0],
                       [0, 0, 0, 1, 1],
                       [0, 0, 0, 0, 1],
                       [0, 0, 0, 0, 0]])
    factorization = [(4, (2, 3)), (3, (2,)), (2, (0, 1)), (0, ()), (1, ())]
    true_B = true_A * np.random.uniform(1, 2, size=true_A.shape)
    scm = sempler.LGANM(true_B, (0, 0), (0.3, 0.4))
    p = len(true_A)
    n = 10000
    obs_data = scm.sample(n=n)
    cache = GaussObsL0Pen(obs_data)
    # ------------------------------------------------------
    # Tests

    def test_turn_operator_1(self):
        A = np.array([[0, 1, 0, 0, 0],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        output = ges.turn(1, 2, {3}, A)
        # Orient X1 -> X2 and X3 -> X2
        A[2, 1], A[1, 2] = 0, 1
        A[2, 3] = 0
        self.assertTrue((A == output).all())

    def test_turn_operator_2(self):
        A = np.array([[0, 1, 1, 0, 0],
                      [1, 0, 1, 1, 1],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 0, 0],
                      [0, 1, 0, 0, 0]])
        # Turn edge X3 - X1 to X3 -> X1 with C = {X4, X0}
        output = ges.turn(3, 1, {0, 4}, A)
        truth = A.copy()
        truth[1, 3] = 0
        truth[1, 0], truth[1, 4] = 0, 0
        self.assertTrue((truth == output).all())
        # Turn edge X1 - X3 to X1 -> X3 with C = Ø
        output = ges.turn(1, 3, set(), A)
        truth = A.copy()
        truth[3, 1] = 0
        self.assertTrue((truth == output).all())
        # Turn edge X4 -> X1 with C = {X3}
        output = ges.turn(4, 1, {3}, A)
        truth = A.copy()
        truth[1, 4] = 0
        truth[1, 3] = 0
        self.assertTrue((truth == output).all())
        # Turn edge X2 -> X0 with C = {X1}
        output = ges.turn(2, 0, {1}, A)
        truth = A.copy()
        truth[0, 2], truth[2, 0] = 0, 1
        truth[0, 1] = 0
        self.assertTrue((truth == output).all())

    def test_turn_operator_preconditions(self):
        A = np.array([[0, 1, 1, 0, 0],
                      [1, 0, 1, 1, 1],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 0, 0],
                      [0, 1, 0, 0, 0]])
        # Trying to turn X1 -> X2 fails as edge already exists
        try:
            ges.turn(1, 2, set(), A)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Trying to turn X3 -> X4 fails as they are not adjacent
        try:
            ges.turn(3, 4, set(), A)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Trying to turn X3 <- X4 fails as they are not adjacent
        try:
            ges.turn(4, 3, set(), A)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Turning X0 -> X1 with C = {X3,X2} fails as X2 is not a neighbor of X1
        try:
            ges.turn(0, 1, {3, 2}, A)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Turning X3 -> X1 with C = {X4,X0,X3} should fail as X3 is contained in C
        try:
            ges.turn(3, 1, {0, 3, 4}, A)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)

    def test_valid_turn_operators_preconditions(self):
        # Test preconditions
        A = np.array([[0, 1, 1, 0, 0],
                      [1, 0, 1, 1, 1],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 0, 0],
                      [0, 1, 0, 0, 0]])
        # Trying to turn X1 -> X2 fails as edge already exists
        try:
            ges.score_valid_turn_operators(1, 2, A, self.cache)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Trying to turn X3 -> X4 fails as they are not adjacent
        try:
            ges.score_valid_turn_operators(3, 4, A, self.cache)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)
        # Trying to turn X3 <- X4 fails as they are not adjacent
        try:
            ges.score_valid_turn_operators(4, 3, A, self.cache)
            self.fail("Exception should have been thrown")
        except ValueError as e:
            print("OK:", e)

    def test_valid_turn_operators_1(self):
        A = np.array([[0, 1, 0, 0, 0],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # Turning the edge X1 <- X2 should yield one valid
        # operator, for:
        #   1. T = Ø, as NA_yx U Ø = {X3} is a clique
        output = ges.score_valid_turn_operators(1, 2, A, self.cache)
        self.assertEqual(1, len(output))
        true_A = A.copy()
        # Turn X1 <- X2 (and orient X3 -> X2)
        true_A[2, 1] = 0
        true_A[1, 2] = 1
        true_A[2, 3] = 0
        self.assertTrue((true_A == output[0][1]).all())

    def test_valid_turn_operators_2(self):
        # NOTE: Same graph as previous test
        A = np.array([[0, 1, 0, 0, 0],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # Turning the edge X1 <- X3 should yield one valid
        # operator, for:
        #   1. T = Ø, as NA_yx U Ø = {X2} is a clique
        output = ges.score_valid_turn_operators(1, 3, A, self.cache)
        self.assertEqual(1, len(output))
        true_A = A.copy()
        # Turn X1 <- X3 (and orient X2 -> X3)
        true_A[3, 1] = 0
        true_A[1, 3] = 1
        true_A[3, 2] = 0
        self.assertTrue((true_A == output[0][1]).all())

    def test_valid_turn_operators_3(self):
        # NOTE: Same graph as two previous tests (i.e. _3 and _2)
        A = np.array([[0, 1, 0, 0, 0],
                      [0, 0, 0, 0, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # Turning the edge X0 -> X1 should yield one valid
        # operator, for:
        #   1. T = Ø, as NA_yx U Ø = Ø is a clique
        output = ges.score_valid_turn_operators(1, 0, A, self.cache)
        self.assertEqual(1, len(output))
        true_A = A.copy()
        # Turn X1 <- X0
        true_A[0, 1] = 0
        true_A[1, 0] = 1
        self.assertTrue((true_A == output[0][1]).all())

    def test_valid_turn_operators_4(self):
        A = np.array([[0, 1, 1, 1, 1],
                      [0, 0, 0, 0, 0],
                      [1, 1, 0, 0, 0],
                      [1, 1, 0, 0, 0],
                      [1, 0, 0, 0, 0]])
        # Turning the edge X0 -> X1 should yield no valid
        # operators, for (note T0 = {X4})
        #   1. T = Ø, as C = NA_yx U Ø = {X2,X3} is not a clique
        #   2. T = {X4}, as C = NA_yx U {X4} = {X2,X3,X4} is not a clique
        output = ges.score_valid_turn_operators(1, 0, A, self.cache)
        self.assertEqual(0, len(output))

    def test_valid_turn_operators_5(self):
        A = np.array([[0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0],
                      [1, 0, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # Turning the edge X0 -> X1 should yield one valid
        # operator, for (note T0 = {X2})
        #   1. T = Ø, as C = NA_yx U Ø = Ø is a clique, but the path
        #   X0 - X2 - X3 -> X1 does not contain a node in C
        #   2. T = {X2}, as C = NA_yx U {X2} = {X2} is a clique and
        #   satisfies the path condition
        output = ges.score_valid_turn_operators(1, 0, A, self.cache)
        self.assertEqual(1, len(output))
        # Orient X1 -> X0 and X2 -> X0
        truth = A.copy()
        truth[0, 1], truth[1, 0] = 0, 1
        truth[0, 2] = 0
        self.assertTrue((truth == output[0][1]).all())

    def test_valid_turn_operators_6(self):
        A = np.array([[0, 1, 0, 0, 0],
                      [1, 0, 1, 1, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # Orienting the edge X1 -> X3 yields not valid operators, as
        # all neighbors of X1 are adjacent to X3
        output = ges.score_valid_turn_operators(1, 3, A, self.cache)
        self.assertEqual(0, len(output))

    def test_valid_turn_operators_7(self):
        A = np.array([[0, 1, 0, 0, 0],
                      [1, 0, 1, 1, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        output = ges.score_valid_turn_operators(3, 1, A, self.cache)
        # Orienting the edge X3 -> X1 yields only one valid operator,
        # as for (note ne(X1) = {X2, X0}
        #   C = Ø and C = {X2} condition (i) is not satisfied
        #   C = {X0, X2} is not a clique
        #   C = {X0} satisfies all three conditions
        self.assertEqual(1, len(output))
        truth = np.array([[0, 1, 0, 0, 0],
                          [0, 0, 1, 0, 0],
                          [0, 1, 0, 1, 0],
                          [0, 1, 1, 0, 0],
                          [0, 0, 0, 0, 0]])
        self.assertTrue((truth == output[0][1]).all())

    def test_valid_turn_operators_8(self):
        A = np.array([[0, 1, 0, 0, 0],
                      [1, 0, 1, 1, 0],
                      [0, 1, 0, 1, 0],
                      [0, 1, 1, 0, 0],
                      [0, 0, 0, 0, 0]])
        # For the edge X0 -> X1 three operators are valid
        #   C = Ø : does not satisfy condition 1
        #   C = {X2}, {X3}, {X2,X3} are valid
        output = ges.score_valid_turn_operators(0, 1, A, self.cache)
        self.assertEqual(3, len(output))
        truth_2 = np.array([[0, 1, 0, 0, 0],
                            [0, 0, 0, 1, 0],
                            [0, 1, 0, 1, 0],
                            [0, 1, 1, 0, 0],
                            [0, 0, 0, 0, 0]])
        truth_3 = np.array([[0, 1, 0, 0, 0],
                            [0, 0, 1, 0, 0],
                            [0, 1, 0, 1, 0],
                            [0, 1, 1, 0, 0],
                            [0, 0, 0, 0, 0]])
        truth_23 = np.array([[0, 1, 0, 0, 0],
                             [0, 0, 0, 0, 0],
                             [0, 1, 0, 1, 0],
                             [0, 1, 1, 0, 0],
                             [0, 0, 0, 0, 0]])
        for (_, new_A, _, _, C) in output:
            if C == {2}:
                self.assertTrue((new_A == truth_2).all())
            if C == {3}:
                self.assertTrue((new_A == truth_3).all())
            if C == {2, 3}:
                self.assertTrue((new_A == truth_23).all())

    def test_valid_turn_operators_9(self):
        A = np.array([[0, 1, 1, 0, 0, 1],
                      [1, 0, 1, 1, 0, 1],
                      [0, 0, 0, 0, 0, 0],
                      [0, 1, 1, 0, 0, 0],
                      [0, 0, 1, 0, 0, 0],
                      [1, 1, 0, 0, 0, 0]])
        # Orienting the edge X0 -> X1 there should be only one valid
        # operator. NA_yx = {X5} and ne(y) / {x} = {X3, X5}:
        #   C = Ø does not satisfy condition i
        #   C = {X3} is valid
        #   C = {X5} does not satisfy condition i
        #   C = {X3,X5} do not form a clique
        output = ges.score_valid_turn_operators(0, 1, A, self.cache)
        self.assertEqual(1, len(output))
        truth = np.array([[0, 1, 1, 0, 0, 1],
                          [0, 0, 1, 0, 0, 1],
                          [0, 0, 0, 0, 0, 0],
                          [0, 1, 1, 0, 0, 0],
                          [0, 0, 1, 0, 0, 0],
                          [1, 1, 0, 0, 0, 0]])
        self.assertTrue((truth == output[0][1]).all())

    def test_valid_turn_operators_10(self):
        # Check that all valid turn operators result in a different
        # essential graph
        G = 10
        p = 20
        for i in range(G):
            A = sempler.generators.dag_avg_deg(p, 3, 1, 1)
            cpdag = utils.dag_to_cpdag(A)
            W = A * np.random.uniform(1, 2, A.shape)
            obs_sample = sempler.LGANM(W, (0, 0), (0.5, 1)).sample(n=1000)
            cache = GaussObsL0Pen(obs_sample)
            fro, to = np.where(cpdag != 0)
            for (x, y) in zip(to, fro):
                valid_operators = ges.score_valid_turn_operators(x, y, cpdag, cache)
                # print(i,len(valid_operators))
                for (_, new_A, _, _, _) in valid_operators:
                    new_cpdag = ges.utils.pdag_to_cpdag(new_A)
                    self.assertFalse((cpdag == new_cpdag).all())
        print("\nChecked that valid turn operators result in different MEC for %i CPDAGs" % (i + 1))