diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Bfci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Bfci.java index cff5b5fe98..b647b3e8be 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Bfci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Bfci.java @@ -122,9 +122,6 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); search.setVerbose(parameters.getBoolean(Params.VERBOSE)); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - search.setKnowledge(knowledge); search.setNumStarts(parameters.getInt(Params.NUM_STARTS)); @@ -191,9 +188,6 @@ public List getParameters() { // Parameters params.add(Params.NUM_STARTS); - // Ablation - params.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - return params; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Cfci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Cfci.java index 44dd05ae80..09dbb407a9 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Cfci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Cfci.java @@ -103,9 +103,6 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setMaxPathLength(parameters.getInt(Params.MAX_PATH_LENGTH)); search.setVerbose(parameters.getBoolean(Params.VERBOSE)); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -159,9 +156,6 @@ public List getParameters() { parameters.add(Params.VERBOSE); - // Ablation - parameters.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - return parameters; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Fci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Fci.java index e1fdc0f13b..6fcb9df5c0 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Fci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Fci.java @@ -112,9 +112,6 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setStable(parameters.getBoolean(Params.STABLE_FAS)); search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -169,9 +166,6 @@ public List getParameters() { parameters.add(Params.TIME_LAG); parameters.add(Params.REPAIR_FAULTY_PAG); - // Ablation - parameters.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - parameters.add(Params.VERBOSE); return parameters; diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/FciMax.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/FciMax.java index 8debb13db8..5ebac13b12 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/FciMax.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/FciMax.java @@ -108,11 +108,9 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setDoDiscriminatingPathColliderRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE)); search.setPossibleMsepSearchDone(parameters.getBoolean(Params.POSSIBLE_MSEP_DONE)); search.setPcHeuristicType(pcHeuristicType); + search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); search.setVerbose(parameters.getBoolean(Params.VERBOSE)); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -162,14 +160,12 @@ public List getParameters() { parameters.add(Params.DO_DISCRIMINATING_PATH_TAIL_RULE); parameters.add(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE); parameters.add(Params.POSSIBLE_MSEP_DONE); + parameters.add(Params.REPAIR_FAULTY_PAG); // parameters.add(Params.PC_HEURISTIC); parameters.add(Params.TIME_LAG); parameters.add(Params.VERBOSE); - // Ablation - parameters.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - return parameters; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Gfci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Gfci.java index 8f8916e1e2..37214de1c6 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Gfci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Gfci.java @@ -105,12 +105,9 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setDoDiscriminatingPathTailRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_TAIL_RULE)); search.setDoDiscriminatingPathColliderRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE)); search.setNumThreads(parameters.getInt(Params.NUM_THREADS)); - search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); + search.setRepairFaultyPag(parameters.getBoolean(Params.REMOVE_ALMOST_CYCLES)); search.setOut(System.out); - // Ablation - search.setAblationLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -166,12 +163,9 @@ public List getParameters() { parameters.add(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE); parameters.add(Params.POSSIBLE_MSEP_DONE); parameters.add(Params.TIME_LAG); - parameters.add(Params.REPAIR_FAULTY_PAG); + parameters.add(Params.REMOVE_ALMOST_CYCLES); parameters.add(Params.NUM_THREADS); - // Ablation - parameters.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - parameters.add(Params.VERBOSE); return parameters; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/GraspFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/GraspFci.java index b16dc07198..432d6cd028 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/GraspFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/GraspFci.java @@ -132,12 +132,9 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { // General search.setVerbose(parameters.getBoolean(Params.VERBOSE)); - search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); + search.setRepairFaultyPag(parameters.getBoolean(Params.REMOVE_ALMOST_CYCLES)); search.setKnowledge(this.knowledge); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -204,12 +201,9 @@ public List getParameters() { // General params.add(Params.TIME_LAG); params.add(Params.SEED); - params.add(Params.REPAIR_FAULTY_PAG); + params.add(Params.REMOVE_ALMOST_CYCLES); params.add(Params.VERBOSE); - // Ablation - params.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - return params; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/LvLite.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/LvLite.java index 965bf78202..57b95581f3 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/LvLite.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/LvLite.java @@ -153,11 +153,11 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setDepth(parameters.getInt(Params.DEPTH)); search.setMaxDdpPathLength(parameters.getInt(Params.MAX_PATH_LENGTH)); search.setTestTimeout(parameters.getLong(Params.TEST_TIMEOUT)); + search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); // Ablation - search.setAblationLeaveOutTestingStep(parameters.getBoolean(Params.ABLATION_LEAVE_OUT_TESTING_STEP)); -// search.ablationSetLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - + search.setAblationLeaveOutScoringStep(parameters.getBoolean(Params.ABLATION_LEAVE_OUT_SCORING_STEP)); + search.setAblationLeaveOutTestingStep(parameters.getBoolean(Params.ABLATION_LEAVE_OUT_TESTING_STEPS)); if (parameters.getInt(Params.LV_LITE_STARTS_WITH) == 1) { search.setStartWith(edu.cmu.tetrad.search.LvLite.START_WITH.BOSS); @@ -170,7 +170,6 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { // General search.setVerbose(parameters.getBoolean(Params.VERBOSE)); search.setKnowledge(this.knowledge); - search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); return search.search(); } @@ -231,12 +230,13 @@ public List getParameters() { params.add(Params.GRASP_DEPTH); params.add(Params.MAX_BLOCKING_PATH_LENGTH); params.add(Params.DEPTH); - params.add(Params.ABLATION_LEAVE_OUT_TESTING_STEP); + params.add(Params.ABLATION_LEAVE_OUT_SCORING_STEP); + params.add(Params.ABLATION_LEAVE_OUT_TESTING_STEPS); params.add(Params.MAX_PATH_LENGTH); + params.add(Params.REPAIR_FAULTY_PAG); // General params.add(Params.TIME_LAG); - params.add(Params.REPAIR_FAULTY_PAG); params.add(Params.VERBOSE); params.add(Params.TEST_TIMEOUT); diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Rfci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Rfci.java index a6988eb962..a78a887e16 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Rfci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/Rfci.java @@ -98,9 +98,6 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setMaxPathLength(parameters.getInt(Params.MAX_PATH_LENGTH)); search.setVerbose(parameters.getBoolean(Params.VERBOSE)); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -146,9 +143,6 @@ public List getParameters() { parameters.add(Params.VERBOSE); - // Ablation - parameters.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - return parameters; } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/SpFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/SpFci.java index 52ca04030f..f349f04415 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/SpFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/algcomparison/algorithm/oracle/pag/SpFci.java @@ -112,14 +112,12 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { search.setKnowledge(this.knowledge); search.setMaxPathLength(parameters.getInt(Params.MAX_PATH_LENGTH)); search.setCompleteRuleSetUsed(parameters.getBoolean(Params.COMPLETE_RULE_SET_USED)); + search.setDoDiscriminatingPathColliderRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE)); search.setDoDiscriminatingPathTailRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_TAIL_RULE)); - search.setDoDiscriminatingPathCollideRule(parameters.getBoolean(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE)); + search.setRepairFaultyPag(parameters.getBoolean(Params.REPAIR_FAULTY_PAG)); search.setVerbose(parameters.getBoolean(Params.VERBOSE)); search.setOut(System.out); - // Ablation - search.setLeaveOutFinalOrientation(parameters.getBoolean(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION)); - return search.search(); } @@ -127,7 +125,7 @@ public Graph runSearch(DataModel dataModel, Parameters parameters) { * Returns the comparison graph created by converting a true directed graph into a partially directed acyclic graph * (PAG). * - * @param graph The true directed graph, if there is one. + * @param graph The true, directed graph, if there is one. * @return The comparison graph as a partially directed acyclic graph (PAG). */ @Override @@ -168,15 +166,13 @@ public List getParameters() { params.add(Params.SEPSET_FINDER_METHOD); params.add(Params.MAX_PATH_LENGTH); params.add(Params.COMPLETE_RULE_SET_USED); - params.add(Params.DO_DISCRIMINATING_PATH_TAIL_RULE); params.add(Params.DO_DISCRIMINATING_PATH_COLLIDER_RULE); + params.add(Params.DO_DISCRIMINATING_PATH_TAIL_RULE); params.add(Params.DEPTH); params.add(Params.TIME_LAG); + params.add(Params.REPAIR_FAULTY_PAG); params.add(Params.VERBOSE); - // Ablation - params.add(Params.ABLATATION_LEAVE_OUT_FINAL_ORIENTATION); - // Flags params.add(Params.VERBOSE); diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/graph/GraphUtils.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/graph/GraphUtils.java index cded8359ef..535db213b5 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/graph/GraphUtils.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/graph/GraphUtils.java @@ -37,6 +37,8 @@ import java.util.concurrent.RecursiveTask; import java.util.concurrent.TimeUnit; +import static edu.cmu.tetrad.search.utils.DagToPag.getFinalStrategyUsingDsep; + /** * Utility class for working with graphs. */ @@ -139,10 +141,7 @@ public static Graph getMarkovBlanketSubgraphWithTargetNode(Graph graph, Node tar EdgeListGraph g = new EdgeListGraph(graph); Set mbNodes = GraphUtils.markovBlanket(target, g); mbNodes.add(target); - Graph res = g.subgraph(new ArrayList<>(mbNodes)); -// System.out.println( target + " Node's MB Nodes list: " + res.getNodes()); -// System.out.println("Graph result: " + res); - return res; + return g.subgraph(new ArrayList<>(mbNodes)); } /** @@ -337,7 +336,7 @@ public static String pathString(Graph graph, List path, Set conditio buf.append(path.get(0).toString()); } - String conditioningSymbol = "\u2714"; + String conditioningSymbol = "✔"; if (conditioningVars.contains(path.get(0))) { buf.append(conditioningSymbol); @@ -1934,9 +1933,11 @@ public static Graph getComparisonGraph(Graph graph, Parameters params) { * @param cpdag The reference graph, a CPDAG obtained using such an algorithm. * @param nodes The nodes in the graph. * @param sepsets A SepsetProducer that will do the sepset search operation described. + * @param depth The depth of the sepset search. * @param verbose Whether to print verbose output. */ - public static void gfciExtraEdgeRemovalStep(Graph graph, Graph cpdag, List nodes, SepsetProducer sepsets, boolean verbose) { + public static void gfciExtraEdgeRemovalStep(Graph graph, Graph cpdag, List nodes, + SepsetProducer sepsets, int depth, boolean verbose) { if (verbose) { TetradLogger.getInstance().log("Starting extra-edge removal step."); } @@ -1964,7 +1965,7 @@ public static void gfciExtraEdgeRemovalStep(Graph graph, Graph cpdag, List Node c = adjacentNodes.get(combination[1]); if (graph.isAdjacentTo(a, c) && cpdag.isAdjacentTo(a, c)) { - Set sepset = sepsets.getSepset(a, c, -1); + Set sepset = sepsets.getSepset(a, c, depth); if (sepset != null) { graph.removeEdge(a, c); @@ -2517,14 +2518,15 @@ public static Graph convert(String spec) { * Applies the GFCI-R0 algorithm to orient edges in a pag based on a reference CPDAG, sepsets, and knowledge. This * method modifies the given pag by changing the orientation of edges. Due to Spirtes. * - * @param pag The pag to be modified. - * @param cpdag The reference CPDAG to guide the orientation of edges. - * @param sepsets The sepsets used to determine the orientation of edges. - * @param knowledge The knowledge used to determine the orientation of edges. - * @param verbose Whether to print verbose output. + * @param pag The pag to be modified. + * @param cpdag The reference CPDAG to guide the orientation of edges. + * @param sepsets The sepsets used to determine the orientation of edges. + * @param knowledge The knowledge used to determine the orientation of edges. + * @param verbose Whether to print verbose output. + * @param unshieldedTriples */ public static void gfciR0(Graph pag, Graph cpdag, SepsetProducer sepsets, Knowledge knowledge, - boolean verbose) { + boolean verbose, Set unshieldedTriples) { if (verbose) { TetradLogger.getInstance().log("Starting GFCI-R0."); } @@ -2554,6 +2556,8 @@ public static void gfciR0(Graph pag, Graph cpdag, SepsetProducer sepsets, Knowle pag.setEndpoint(x, y, Endpoint.ARROW); pag.setEndpoint(z, y, Endpoint.ARROW); + unshieldedTriples.add(new Triple(x, y, z)); + if (verbose) { TetradLogger.getInstance().log("Copied " + x + " *-> " + y + " <-* " + z + " from CPDAG."); @@ -2577,6 +2581,8 @@ public static void gfciR0(Graph pag, Graph cpdag, SepsetProducer sepsets, Knowle pag.setEndpoint(x, y, Endpoint.ARROW); pag.setEndpoint(z, y, Endpoint.ARROW); + unshieldedTriples.add(new Triple(x, y, z)); + if (verbose) { double p = sepsets.getPValue(x, z, sepset); String _p = p < 0.0001 ? "< 0.0001" : String.format("%.4f", p); @@ -2599,20 +2605,6 @@ public static void gfciR0(Graph pag, Graph cpdag, SepsetProducer sepsets, Knowle } } - - /** - * Checks if three nodes are connected in a graph. - * - * @param graph the graph to check for connectivity - * @param a the first node - * @param b the second node - * @param c the third node - * @return {@code true} if all three nodes are connected, {@code false} otherwise - */ - private static boolean triple(Graph graph, Node a, Node b, Node c) { - return graph.isAdjacentTo(a, b) && graph.isAdjacentTo(b, c); - } - /** * Checks if three nodes in a graph form an unshielded triple. An unshielded triple is a configuration where node a * is adjacent to node b, node b is adjacent to node c, but node a is not adjacent to node c. @@ -2627,22 +2619,6 @@ private static boolean unshieldedTriple(Graph graph, Node a, Node b, Node c) { return graph.isAdjacentTo(a, b) && graph.isAdjacentTo(b, c) && !graph.isAdjacentTo(a, c); } - /** - * Determines if the collider is allowed. - * - * @param pag The Graph representing the PAG. - * @param x The Node object representing the first node. - * @param b The Node object representing the second node. - * @param y The Node object representing the third node. - * @return true if the collider is allowed, false otherwise. - */ - private static boolean colliderAllowed(Graph pag, Node x, Node b, Node y, Knowledge knowledge) { - if (true) return true; - - return FciOrient.isArrowheadAllowed(x, b, pag, knowledge) - && FciOrient.isArrowheadAllowed(y, b, pag, knowledge); - } - /** * Checks if the given nodes are unshielded colliders when considering the given graph. * @@ -2917,51 +2893,74 @@ public static boolean isCorrectBidirectedEdge(Edge edge, Graph trueGraph) { /** * Repairs a faulty PAG (Partially Directed Acyclic Graph). *

- * If the estimated PAG contains a directed cycle, an IllegalArgumentException is thrown, as this type of estimated - * PAG cannot be repaired. - *

- * Otherwise, two types of repairs are attempted. First, if there is an edge x <-> y with a path x ~~> y, - * then the edge is oriented to x --> y. Such an edge cannot be x <-- y on pain of a cycle. Also, it cannot be - * x <-> y because the bidirected-edge semantics prohibits it (the problem we're trying to fix). So it must - * actually be x --> y. The basic issue here is that to know the edge is not bidirected, we need to be able to - * "peer into the future" of the orientation process, which we can't do. As it turns out, this edge can't have been - * bidirected in the first place, because it would have been oriented to x --> y in the first place had we known - * that x ~~> y. So it's making a claim about non-causality that can't be supported. So we just fix it in - * post-processing. + * Two types of repairs are attempted. First, if there is an edge x <-> y with a path x ~~> y, then the + * unshielded colldiers into x are removed and the graph is rebuilt. *

- * Second, if there is an inducing path between two non-adjacent nodes x and y, then a nondirected edge x o-o y is - * added between them. In a PAG, x and y are adjacent if and only if there is an inducing path between x and y, so - * this is an error that should be fixed. It's possible the final orientation will orient it, but it's also possible - * that it will remain nondirected. + * Second, if there is an inducing path between two non-adjacent nodes x and y, then an edge x *-*.y is added. + * Arrows are included at x and y if including them prevents almost cycles. *

- * The final orientation is then done using the supplied FciOrient object, which should be configured to have the - * desired behavior. + * The final orientation is then done using the FCI orient from DAG to PAG (using DSEP). *

- * As changes that are made above may imply further changes, the process is repeated until no further changes are - * made. - *

- * The end result of this repair process may not be a legal PAG if additional edges are oriented by knowledge or by - * unfaithfulness in the original estimated PAG. However, it will be a PAG for which some knowledge-based - * orientation process could have been applied. - * - * @param pag the faulty PAG to be repaired - * @param fciOrient the FciOrient object used for final orientation - * @param knowledge the knowledge object used for orientation - * @param unshieldedColliders the set of unshielded colliders to be updated - * @param verbose indicates whether or not to print verbose output - * @param ablationLeaveOutFinalOrientation indicates whether or not to leave out the final orientation + * TODO: this method is in a bit of a state of flux as various ideas are tried for repairing PAGs + * + * @param pag the faulty PAG to be repaired + * @param fciOrient the FciOrient object used for final orientation + * @param knowledge the knowledge object used for orientation + * @param unshieldedColliders the set of unshielded colliders to be updated + * @param checkCyclicity indicates whether or not to check for cyclicity + * @param verbose indicates whether or not to print verbose output * @throws IllegalArgumentException if the estimated PAG contains a directed cycle */ - public static void repairFaultyPag(Graph pag, FciOrient fciOrient, Knowledge knowledge, - Set unshieldedColliders, boolean verbose, boolean ablationLeaveOutFinalOrientation) { + public static Graph repairFaultyPag(Graph pag, FciOrient fciOrient, Knowledge knowledge, + Set unshieldedColliders, boolean checkCyclicity, boolean verbose) { if (verbose) { TetradLogger.getInstance().log("Repairing faulty PAG..."); } + pag = new EdgeListGraph(pag); fciOrient.setKnowledge(knowledge); +// anyChange = resolveAlmostCycles1(pag, knowledge, unshieldedColliders, verbose, anyChange); + boolean anyChange = removeAlmostCycles2(unshieldedColliders, fciOrient, pag, knowledge, verbose); + + if (checkCyclicity) { + anyChange = removeCycles(unshieldedColliders, fciOrient, pag, knowledge, verbose) || anyChange; + } + + // This is not necessary if I'm going to follow with the DSEP R0 step. +// anyChange = repairMaximality(pag, verbose, anyChange) || anyChange; + + if (verbose) { + TetradLogger.getInstance().log("Doing final orientation..."); + } + + // Use the final R0R4 strategy from DAG to PAG, which does final orientation using DSEP for both R0 and + // R4. This is the DAG to PAG strategy, which we repeat here for clarity. jdramsey 2024-8-13. + Graph mag = GraphTransforms.zhangMagFromPag(pag); + FciOrient _fciOrient = new FciOrient(getFinalStrategyUsingDsep(mag, pag, knowledge, verbose)); + _fciOrient.setVerbose(verbose); + + // This is R0 using DSEP + _fciOrient.ruleR0(pag, new HashSet<>()); + + // This uses the discriminating pth rule using DSEP. + _fciOrient.finalOrientation(pag); + + if (!anyChange) { + if (verbose) { + TetradLogger.getInstance().log("NO FAULTY PAG CORRECTIONS MADE."); + } + } else { + if (verbose) { + TetradLogger.getInstance().log("Faulty PAG repaired."); + } + } + + return pag; + } + + private static boolean resolveAlmostCycles1(Graph pag, Knowledge knowledge, Set unshieldedColliders, boolean verbose, boolean anyChange) { boolean changed; - boolean anyChange = false; do { changed = false; @@ -3030,41 +3029,36 @@ public static void repairFaultyPag(Graph pag, FciOrient fciOrient, Knowledge kno } } } + } while (changed); + return anyChange; + } - for (Node x : pag.getNodes()) { - for (Node y : pag.getNodes()) { - if (x != y && !pag.isAdjacentTo(x, y) && pag.paths().existsInducingPath(x, y)) { + private static boolean repairMaximality(Graph pag, boolean verbose, boolean anyChange) { + // Repair maximality. + for (Node x : pag.getNodes()) { + for (Node y : pag.getNodes()) { + if (x != y && !pag.isAdjacentTo(x, y) && pag.paths().existsInducingPath(x, y)) { // pag.addNondirectedEdge(x, y); - pag.addBidirectedEdge(x, y); // Zhang 2008 + pag.addNondirectedEdge(x, y); // Zhang 2008 - if (verbose) { - TetradLogger.getInstance().log("FAULTY PAG CORRECTION: Added nondirected edge " + x + " o-o " + y + "."); - } - - changed = true; - anyChange = true; + if (!pag.paths().existsDirectedPath(x, y)) { + pag.setEndpoint(y, x, Endpoint.ARROW); } - } - } - if (!ablationLeaveOutFinalOrientation) { - fciOrient.finalOrientation(pag); - } - } while (changed); + if (!pag.paths().existsDirectedPath(y, x)) { + pag.setEndpoint(x, y, Endpoint.ARROW); + } - if (verbose) { - TetradLogger.getInstance().log("Doing final orientation..."); - } + if (verbose) { + TetradLogger.getInstance().log("FAULTY PAG CORRECTION: Added bidirected edge " + x + " <-> " + y + "."); + } - if (!anyChange) { - if (verbose) { - TetradLogger.getInstance().log("NO FAULTY PAG CORRECTIONS MADE."); - } - } else { - if (verbose) { - TetradLogger.getInstance().log("Faulty PAG repaired."); +// changed = true; + anyChange = true; + } } } + return anyChange; } @@ -3400,6 +3394,324 @@ private static void dsepFollowPath2(Node a, Node x, Node y, Set dsep, Set< path.remove(a); } + public static boolean removeAlmostCycles2(Set unshieldedColliders, FciOrient fciOrient, + Graph pag, Knowledge knowledge, boolean verbose) { + if (verbose) { + TetradLogger.getInstance().log("Removing almost cycles."); + } + + boolean anyChange = false; + + fciOrient.setInitialAllowedColliders(new HashSet<>()); + fciOrient.finalOrientation(pag); + unshieldedColliders.addAll(fciOrient.getInitialAllowedColliders()); + fciOrient.setInitialAllowedColliders(null); + + while (true) { + Graph mag = GraphTransforms.zhangMagFromPag(pag); + + // Make a list of all where x ↔ y and x ~~> y. + Set almostCyclesSet = new HashSet<>(); + + for (Edge edge : mag.getEdges()) { + if (Edges.isBidirectedEdge(edge)) { + if (mag.paths().existsDirectedPath(edge.getNode1(), edge.getNode2())) { + Edge e = Edges.directedEdge(edge.getNode1(), edge.getNode2()); + almostCyclesSet.add(e); + } else if (mag.paths().existsDirectedPath(edge.getNode2(), edge.getNode1())) { + Edge e = Edges.directedEdge(edge.getNode2(), edge.getNode1()); + almostCyclesSet.add(e); + } + } + } + + if (almostCyclesSet.isEmpty()) { + break; + } + + if (verbose) { + StringBuilder sb = new StringBuilder(); + sb.append("Almost cycles: "); + + for (Edge _almostCycle : almostCyclesSet) { + sb.append(_almostCycle.getNode1()).append(" ~~> ").append(_almostCycle.getNode2()).append(" "); + } + + TetradLogger.getInstance().log(sb.toString()); + + TetradLogger.getInstance().log("# almost cycles = " + almostCyclesSet.size()); + } + + for (Edge almostCycle : almostCyclesSet) { + + Node x = almostCycle.getNode1(); + Node y = almostCycle.getNode2(); + + // Find all unshielded triples z *→ x ↔ y in subsequentUnshieldedColliders + Set unshieldedTriplesIntoX = new HashSet<>(); + + for (Triple triple : new HashSet<>(unshieldedColliders)) { + if (triple.getY().equals(x) && triple.getZ().equals(y)) { + if (mag.getNodesInTo(x, Endpoint.ARROW).contains(triple.getX())) { + unshieldedColliders.remove(triple); + unshieldedTriplesIntoX.add(triple); + anyChange = true; + } + } else if (triple.getY().equals(x) && triple.getX().equals(y)) { + if (mag.getNodesInTo(x, Endpoint.ARROW).contains(triple.getZ())) { + unshieldedColliders.remove(triple); + unshieldedTriplesIntoX.add(triple); + anyChange = true; + } + } + } + + // Remove any unshielded collider in unshieldedTriplesIntoX from the _unshieldedColliders. + if (!unshieldedColliders.isEmpty()) { + if (verbose) { + TetradLogger.getInstance().log("Removing almost cycle " + almostCycle.getNode1() + " ~~> " + almostCycle.getNode2()); + TetradLogger.getInstance().log("Removing triples : " + unshieldedTriplesIntoX); + } + } + } + + if (verbose) { + TetradLogger.getInstance().log("Done removing almost cycles this round."); + } + + // Rebuild the PAG with this new unshielded collider set. + + if (verbose) { + TetradLogger.getInstance().log("Rebuilding graph."); + } + + reorientWithCircles(pag, verbose); + doRequiredOrientations(fciOrient, pag, pag.getNodes(), knowledge, verbose); + recallUnshieldedTriples(pag, unshieldedColliders, knowledge); + + if (verbose) { + TetradLogger.getInstance().log("Finished rebuilding graph."); + } + + if (verbose) { + TetradLogger.getInstance().log("Final orientation."); + } + + fciOrient.setVerbose(false); + fciOrient.setAllowedColliders(unshieldedColliders); + fciOrient.finalOrientation(pag); + + if (verbose) { + TetradLogger.getInstance().log("Finished final orientation."); + } + } + + if (verbose) { + TetradLogger.getInstance().log("All done removing almost cycles."); + } + + return anyChange; + } + + public static boolean removeCycles(Set unshieldedColliders, FciOrient fciOrient, + Graph pag, Knowledge knowledge, boolean verbose) { + if (verbose) { + TetradLogger.getInstance().log("Removing cycles."); + } + + boolean anyChange = false; + + fciOrient.setInitialAllowedColliders(new HashSet<>()); + fciOrient.finalOrientation(pag); + unshieldedColliders.addAll(fciOrient.getInitialAllowedColliders()); + fciOrient.setInitialAllowedColliders(null); + + boolean removedCycle = true; + + while (removedCycle) { + removedCycle = false; + Map> cycleTriples = new HashMap<>(); + + for (Node x : pag.getNodes()) { + if (pag.paths().existsDirectedPath(x, x)) { + Set unshieldedTriplesIntoX = new HashSet<>(); + + for (Triple triple : new HashSet<>(unshieldedColliders)) { + if (triple.getY().equals(x) || triple.getZ().equals(x)) { + if (pag.getNodesInTo(x, Endpoint.ARROW).contains(triple.getX())) { + unshieldedColliders.remove(triple); + unshieldedTriplesIntoX.add(triple); + anyChange = true; + } + } + } + + cycleTriples.put(x, unshieldedTriplesIntoX); + } + } + + // Find the element of cycleTriples that is mapped to the fewest triples. + Node x = null; + int min = Integer.MAX_VALUE; + + for (Map.Entry> entry : cycleTriples.entrySet()) { + if (entry.getValue().size() < min) { + x = entry.getKey(); + min = entry.getValue().size(); + } + } + + if (x != null) { + Set unshieldedTriplesIntoX = cycleTriples.get(x); + + if (!unshieldedTriplesIntoX.isEmpty()) { + unshieldedColliders.removeAll(unshieldedTriplesIntoX); + + if (verbose) { + TetradLogger.getInstance().log("Removing cycle at " + x); + TetradLogger.getInstance().log("Removing triples : " + unshieldedTriplesIntoX); + } + + removedCycle = true; + } + } + + // Rebuild the PAG with this new unshielded collider set. + + if (verbose) { + TetradLogger.getInstance().log("Rebuilding graph."); + } + + reorientWithCircles(pag, verbose); + doRequiredOrientations(fciOrient, pag, pag.getNodes(), knowledge, verbose); + recallUnshieldedTriples(pag, unshieldedColliders, knowledge); + + if (verbose) { + TetradLogger.getInstance().log("Finished rebuilding graph."); + } + + if (verbose) { + TetradLogger.getInstance().log("Final orientation."); + } + + fciOrient.setVerbose(false); + fciOrient.setAllowedColliders(unshieldedColliders); + fciOrient.finalOrientation(pag); + + if (verbose) { + TetradLogger.getInstance().log("Finished final orientation."); + } + } + + if (verbose) { + TetradLogger.getInstance().log("All done removing cycles."); + } + + return anyChange; + } + + /** + * Reorients all edges in a Graph as o-o. This method is used to apply the o-o orientation to all edges in the given + * Graph following the PAG (Partially Ancestral Graph) structure. + * + * @param pag The Graph to be reoriented. + * @param verbose A boolean value indicating whether verbose output should be printed. + */ + public static void reorientWithCircles(Graph pag, boolean verbose) { + if (verbose) { + TetradLogger.getInstance().log("Orient all edges in PAG as o-o:"); + } + pag.reorientAllWith(Endpoint.CIRCLE); + } + + /** + * Recall unshielded triples in a given graph. + * + * @param pag The graph to recall unshielded triples from. + * @param unshieldedColliders The set of unshielded colliders that need to be recalled. + * @param knowledge the knowledge object. + */ + public static void recallUnshieldedTriples(Graph pag, Set unshieldedColliders, Knowledge knowledge) { + for (Triple triple : unshieldedColliders) { + Node x = triple.getX(); + Node b = triple.getY(); + Node y = triple.getZ(); + + // We can avoid creating almost cycles here, but this does not solve the problem, as we can still + // creat almost cycles in final orientation. + if (colliderAllowed(pag, x, b, y, knowledge) && triple(pag, x, b, y) && !couldCreateAlmostCycle(pag, x, y)) { + pag.setEndpoint(x, b, Endpoint.ARROW); + pag.setEndpoint(y, b, Endpoint.ARROW); + pag.removeEdge(x, y); + } + } + } + + /** + * Checks if creating an almost cycle between nodes x, b, and y is possible in a given graph. + * + * @param pag The graph to check if the almost cycle can be created. + * @param x The first node of the almost cycle. + * @param y The third node of the almost cycle. + * @return True if creating the almost cycle is possible, false otherwise. + */ + public static boolean couldCreateAlmostCycle(Graph pag, Node x, Node y) { + return pag.paths().isAncestorOf(x, y) || pag.paths().isAncestorOf(y, x); + } + + /** + * Checks if three nodes are connected in a graph. + * + * @param graph the graph to check for connectivity + * @param a the first node + * @param b the second node + * @param c the third node + * @return {@code true} if all three nodes are connected, {@code false} otherwise + */ + public static boolean triple(Graph graph, Node a, Node b, Node c) { + return distinct(a, b, c) && graph.isAdjacentTo(a, b) && graph.isAdjacentTo(b, c); + } + + /** + * Determines if the collider is allowed. + * + * @param pag The Graph representing the PAG. + * @param x The Node object representing the first node. + * @param b The Node object representing the second node. + * @param y The Node object representing the third node. + * @return true if the collider is allowed, false otherwise. + */ + public static boolean colliderAllowed(Graph pag, Node x, Node b, Node y, Knowledge knowledge) { + return FciOrient.isArrowheadAllowed(x, b, pag, knowledge) && FciOrient.isArrowheadAllowed(y, b, pag, knowledge); + } + + /** + * Orient required edges in PAG. + * + * @param fciOrient The FciOrient object used for orienting the edges. + * @param pag The Graph representing the PAG. + * @param best The list of Node objects representing the best nodes. + */ + public static void doRequiredOrientations(FciOrient fciOrient, Graph pag, List best, Knowledge knowledge, boolean verbose) { + if (verbose) { + TetradLogger.getInstance().log("Orient required edges in PAG:"); + } + + fciOrient.fciOrientbk(knowledge, pag, best); + } + + /** + * Determines whether three {@link Node} objects are distinct. + * + * @param x the first Node object + * @param b the second Node object + * @param y the third Node object + * @return true if x, b, and y are distinct; false otherwise + */ + public static boolean distinct(Node x, Node b, Node y) { + return x != b && y != b && x != y; + } + /** * The GraphType enum represents the types of graphs that can be used in the application. */ diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/BFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/BFci.java index 5c260a6914..91cc05ff1d 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/BFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/BFci.java @@ -21,17 +21,16 @@ package edu.cmu.tetrad.search; import edu.cmu.tetrad.data.Knowledge; -import edu.cmu.tetrad.graph.EdgeListGraph; -import edu.cmu.tetrad.graph.Graph; -import edu.cmu.tetrad.graph.GraphUtils; -import edu.cmu.tetrad.graph.Node; +import edu.cmu.tetrad.graph.*; import edu.cmu.tetrad.search.score.Score; import edu.cmu.tetrad.search.test.MsepTest; import edu.cmu.tetrad.search.utils.*; import edu.cmu.tetrad.util.RandomUtil; import edu.cmu.tetrad.util.TetradLogger; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static edu.cmu.tetrad.graph.GraphUtils.gfciExtraEdgeRemovalStep; @@ -185,6 +184,7 @@ public Graph search() { subAlg.setUseBes(bossUseBes); subAlg.setNumStarts(this.numStarts); subAlg.setNumThreads(numThreads); + subAlg.setVerbose(verbose); PermutationSearch alg = new PermutationSearch(subAlg); alg.setKnowledge(this.knowledge); @@ -205,11 +205,16 @@ public Graph search() { throw new IllegalArgumentException("Invalid sepset finder method: " + sepsetFinderMethod); } - gfciExtraEdgeRemovalStep(graph, referenceDag, nodes, sepsets, verbose); - GraphUtils.gfciR0(graph, referenceDag, sepsets, knowledge, verbose); + Set unshieldedTriples = new HashSet<>(); + + gfciExtraEdgeRemovalStep(graph, referenceDag, nodes, sepsets, depth, verbose); + GraphUtils.gfciR0(graph, referenceDag, sepsets, knowledge, verbose, unshieldedTriples); FciOrient fciOrient = new FciOrient( - R0R4StrategyTestBased.defaultConfiguration(independenceTest, new Knowledge())); + R0R4StrategyTestBased.specialConfiguration(independenceTest, knowledge, doDiscriminatingPathTailRule, + doDiscriminatingPathColliderRule, verbose)); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); if (!ablationLeaveOutFinalOrientation) { fciOrient.finalOrientation(graph); @@ -218,7 +223,7 @@ public Graph search() { GraphUtils.replaceNodes(graph, this.independenceTest.getVariables()); if (repairFaultyPag) { - GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, null, verbose, ablationLeaveOutFinalOrientation); + graph = GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, unshieldedTriples, false, verbose); } return graph; diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/Fci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/Fci.java index c955ad5b4c..08e84e80ec 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/Fci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/Fci.java @@ -22,10 +22,7 @@ package edu.cmu.tetrad.search; import edu.cmu.tetrad.data.Knowledge; -import edu.cmu.tetrad.graph.Endpoint; -import edu.cmu.tetrad.graph.Graph; -import edu.cmu.tetrad.graph.GraphUtils; -import edu.cmu.tetrad.graph.Node; +import edu.cmu.tetrad.graph.*; import edu.cmu.tetrad.search.utils.*; import edu.cmu.tetrad.util.MillisecondTimes; import edu.cmu.tetrad.util.TetradLogger; @@ -213,6 +210,7 @@ public Graph search() { Graph graph = fas.search(); this.sepsets = fas.getSepsets(); + Set unshieldedTriples = new HashSet<>(); if (verbose) { TetradLogger.getInstance().log("Reorienting with o-o."); @@ -223,7 +221,10 @@ public Graph search() { // The original FCI, with or without JiJi Zhang's orientation rules // Optional step: Possible Msep. (Needed for correctness but very time-consuming.) FciOrient fciOrient = new FciOrient( - R0R4StrategyTestBased.defaultConfiguration(independenceTest, knowledge)); + R0R4StrategyTestBased.specialConfiguration(independenceTest, knowledge, doDiscriminatingPathTailRule, + doDiscriminatingPathColliderRule, verbose)); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); fciOrient.setVerbose(verbose); if (this.possibleMsepSearchDone) { @@ -235,7 +236,7 @@ public Graph search() { TetradLogger.getInstance().log("Doing R0."); } - fciOrient.ruleR0(graph); + fciOrient.ruleR0(graph, unshieldedTriples); if (verbose) { TetradLogger.getInstance().log("Removing by possible d-sep."); @@ -257,7 +258,7 @@ public Graph search() { TetradLogger.getInstance().log("Doing R0."); } - fciOrient.ruleR0(graph); + fciOrient.ruleR0(graph, unshieldedTriples); if (verbose) { TetradLogger.getInstance().log("Doing Final Orientation."); @@ -268,7 +269,7 @@ public Graph search() { } if (repairFaultyPag) { - GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, null, verbose, ablationLeaveOutFinalOrientation); + graph = GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, unshieldedTriples, false, verbose); } long stop = MillisecondTimes.timeMillis(); diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/FciMax.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/FciMax.java index f8db0616ab..1b06e50719 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/FciMax.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/FciMax.java @@ -33,10 +33,7 @@ import edu.cmu.tetrad.util.SublistGenerator; import edu.cmu.tetrad.util.TetradLogger; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; @@ -131,6 +128,7 @@ public final class FciMax implements IGraphSearch { * Whether the final orientation step should be left out. */ private boolean ablationLeaveOutFinalOrientation = false; + private boolean repairFaultyPag; /** * Constructor. @@ -190,12 +188,19 @@ public Graph search() { R0R4StrategyTestBased.defaultConfiguration(independenceTest, new Knowledge())); fciOrient.fciOrientbk(this.knowledge, graph, graph.getNodes()); - addColliders(graph); + + Set unshieldedColldiders = new HashSet<>(); + + addColliders(graph, unshieldedColldiders); if (!ablationLeaveOutFinalOrientation) { fciOrient.finalOrientation(graph); } + if (repairFaultyPag) { + graph = GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, unshieldedColldiders, false, verbose); + } + long stop = MillisecondTimes.timeMillis(); this.elapsedTime = stop - start; @@ -339,9 +344,10 @@ public void setDoDiscriminatingPathTailRule(boolean doDiscriminatingPathTailRule /** * Adds colliders to the given graph. * - * @param graph The graph to which colliders should be added. + * @param graph The graph to which colliders should be added. + * @param unshieldedColliders */ - private void addColliders(Graph graph) { + private void addColliders(Graph graph, Set unshieldedColliders) { Map scores = new ConcurrentHashMap<>(); List nodes = graph.getNodes(); @@ -399,6 +405,8 @@ protected Boolean compute() { graph.setEndpoint(a, b, Endpoint.ARROW); graph.setEndpoint(c, b, Endpoint.ARROW); + + unshieldedColliders.add(new Triple(a, b, c)); } } @@ -488,6 +496,10 @@ public void setDoDiscriminatingPathColliderRule(boolean doDiscriminatingPathColl public void setLeaveOutFinalOrientation(boolean ablationLeaveOutFinalOrientation) { this.ablationLeaveOutFinalOrientation = ablationLeaveOutFinalOrientation; } + + public void setRepairFaultyPag(boolean repairFaultyPag) { + this.repairFaultyPag = repairFaultyPag; + } } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GFci.java index 0895d05bb6..8a26b2469a 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GFci.java @@ -29,7 +29,9 @@ import java.io.PrintStream; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static edu.cmu.tetrad.graph.GraphUtils.gfciExtraEdgeRemovalStep; @@ -197,22 +199,27 @@ public Graph search() { throw new IllegalArgumentException("Invalid sepset finder method: " + sepsetFinderMethod); } - gfciExtraEdgeRemovalStep(graph, cpdag, nodes, sepsets, verbose); - GraphUtils.gfciR0(graph, cpdag, sepsets, knowledge, verbose); + Set unshieldedTriples = new HashSet<>(); + + gfciExtraEdgeRemovalStep(graph, cpdag, nodes, sepsets, depth, verbose); + GraphUtils.gfciR0(graph, cpdag, sepsets, knowledge, verbose, unshieldedTriples); if (verbose) { TetradLogger.getInstance().log("Starting final FCI orientation."); } FciOrient fciOrient = new FciOrient( - R0R4StrategyTestBased.defaultConfiguration(independenceTest, new Knowledge())); + R0R4StrategyTestBased.specialConfiguration(independenceTest, knowledge, doDiscriminatingPathTailRule, + doDiscriminatingPathColliderRule, verbose)); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); if (!ablationLeaveOutFinalOrientation) { fciOrient.finalOrientation(graph); } if (repairFaultyPag) { - GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, null, verbose, ablationLeaveOutFinalOrientation); + graph = GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, unshieldedTriples, false, verbose); } return graph; @@ -335,23 +342,7 @@ public void setNumThreads(int numThreads) { this.numThreads = numThreads; } - /** - * Sets whether the discriminating path tail rule should be used. - * - * @param doDiscriminatingPathTailRule True, if so. - */ - public void setDoDiscriminatingPathTailRule(boolean doDiscriminatingPathTailRule) { - this.doDiscriminatingPathTailRule = doDiscriminatingPathTailRule; - } - /** - * Sets whether the discriminating path collider rule should be used. - * - * @param doDiscriminatingPathColliderRule True, if so. - */ - public void setDoDiscriminatingPathColliderRule(boolean doDiscriminatingPathColliderRule) { - this.doDiscriminatingPathColliderRule = doDiscriminatingPathColliderRule; - } /** * Sets the flag indicating whether to repair faulty PAG. diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GraspFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GraspFci.java index 6d4d4a1c2b..d3c502c71a 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GraspFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/GraspFci.java @@ -21,16 +21,15 @@ package edu.cmu.tetrad.search; import edu.cmu.tetrad.data.Knowledge; -import edu.cmu.tetrad.graph.EdgeListGraph; -import edu.cmu.tetrad.graph.Graph; -import edu.cmu.tetrad.graph.GraphUtils; -import edu.cmu.tetrad.graph.Node; +import edu.cmu.tetrad.graph.*; import edu.cmu.tetrad.search.score.Score; import edu.cmu.tetrad.search.test.MsepTest; import edu.cmu.tetrad.search.utils.*; import edu.cmu.tetrad.util.TetradLogger; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static edu.cmu.tetrad.graph.GraphUtils.gfciExtraEdgeRemovalStep; @@ -213,18 +212,23 @@ public Graph search() { throw new IllegalArgumentException("Invalid sepset finder method: " + sepsetFinderMethod); } - gfciExtraEdgeRemovalStep(pag, referenceCpdag, nodes, sepsets, verbose); - GraphUtils.gfciR0(pag, referenceCpdag, sepsets, knowledge, verbose); + Set unshieldedTriples = new HashSet<>(); + + gfciExtraEdgeRemovalStep(pag, referenceCpdag, nodes, sepsets, depth, verbose); + GraphUtils.gfciR0(pag, referenceCpdag, sepsets, knowledge, verbose, unshieldedTriples); FciOrient fciOrient = new FciOrient( - R0R4StrategyTestBased.defaultConfiguration(independenceTest, new Knowledge())); + R0R4StrategyTestBased.specialConfiguration(independenceTest, knowledge, doDiscriminatingPathTailRule, + doDiscriminatingPathColliderRule, verbose)); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); if (!ablationLeaveOutFinalOrientation) { fciOrient.finalOrientation(pag); } if (repairFaultyPag) { - GraphUtils.repairFaultyPag(pag, fciOrient, knowledge, null, verbose, ablationLeaveOutFinalOrientation); + pag = GraphUtils.repairFaultyPag(pag, fciOrient, knowledge, unshieldedTriples, false, verbose); } GraphUtils.replaceNodes(pag, this.independenceTest.getVariables()); diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/LvLite.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/LvLite.java index 1c4574b598..c38d57f579 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/LvLite.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/LvLite.java @@ -33,15 +33,14 @@ import java.util.*; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ForkJoinPool; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * The LV-Lite algorithm implements a search algorithm for learning the structure of a graphical model from - * observational data with latent variables. The algorithm uses the BOSS or GRaSP algorithm to obtain an initial CPDAG, - * then uses scoring steps to infer some unshielded colliders in the graph, then finishes with a testing step to remove - * extra edges and orient more unshielded colliders. Finally, the final FCI orientation is applied to the graph. + * observational data with latent variables. The algorithm uses the BOSS or GRaSP algorithm to get an initial CPDAG. + * Then it uses scoring steps to infer some unshielded colliders in the graph, then finishes with a testing step to + * remove extra edges and orient more unshielded colliders. Finally, the final FCI orientation is applied to the graph. * * @author josephramsey */ @@ -59,7 +58,7 @@ public final class LvLite implements IGraphSearch { */ private Knowledge knowledge = new Knowledge(); /** - * The algorithm to use to obtain the initial CPDAG. + * The algorithm to use to get the initial CPDAG. */ private START_WITH startWith = START_WITH.BOSS; /** @@ -70,10 +69,6 @@ public final class LvLite implements IGraphSearch { * The number of starts for GRaSP. */ private int numStarts = 1; - /** - * The maximum score drop for tucking. - */ - private double maxScoreDrop = -1; /** * The depth of the GRaSP if it is used. */ @@ -123,17 +118,26 @@ public final class LvLite implements IGraphSearch { */ private boolean verbose = false; /** - * Determines if testing is allowed. Default value is true. + * This private boolean variable determines whether the ablation leave-out scoring step is enabled or disabled. If + * this variable is set to true, the ablation leave-out scoring step is enabled. If this variable is set to false, + * the ablation leave-out-scoring step is disabled. + *

+ * Note that if the scoring step (BOSS or GRaSP) is left out, the algorithm will start with an initial complete + * connected graph with all edges oriented a nondirected (o-o), since the subsequent steps require an initial graph + * that is Markov. + */ + private boolean ablationLeaveOutScoringStep; + /** + * This variable represents whether the ablation leave out testing step is enabled or disabled. Ablation leave out + * testing step is a part of a software development process where certain components or features are temporarily + * removed or disabled for the purpose of testing or evaluating their impact on the overall system. By default, the + * ablation leave-out-testing step is disabled (false). */ private boolean ablationLeaveOutTestingStep = false; /** * The maximum length of any discriminating path. */ private int maxDdpPathLength = -1; - /** - * ABLATION: The flag indicating whether to leave out the final orientation. - */ - private boolean ablationLeaveOutFinalOrientation; /** * The style for removing extra edges. */ @@ -180,76 +184,92 @@ public Graph search() { TetradLogger.getInstance().log("===Starting LV-Lite==="); } - List best; + Graph pag; Graph dag; + List best; - if (startWith == START_WITH.BOSS) { + if (ablationLeaveOutScoringStep) { if (verbose) { - TetradLogger.getInstance().log("Running BOSS..."); + TetradLogger.getInstance().log("Ablation: Leave out scoring step."); } - long start = MillisecondTimes.wallTimeMillis(); + pag = new EdgeListGraph(nodes); + pag.fullyConnect(Endpoint.CIRCLE); + best = new ArrayList<>(nodes); + Collections.shuffle(best); + TeyssierScorer scorer = new TeyssierScorer(test, score); + scorer.score(best); + dag = scorer.getGraph(false); + } else { - var permutationSearch = getBossSearch(); - dag = permutationSearch.search(false); - best = permutationSearch.getOrder(); - best = dag.paths().getValidOrder(best, true); + if (startWith == START_WITH.BOSS) { - long stop = MillisecondTimes.wallTimeMillis(); + if (verbose) { + TetradLogger.getInstance().log("Running BOSS..."); + } - if (verbose) { - TetradLogger.getInstance().log("BOSS took " + (stop - start) + " ms."); - } + long start = MillisecondTimes.wallTimeMillis(); - if (verbose) { - TetradLogger.getInstance().log("Initializing PAG to BOSS CPDAG."); - TetradLogger.getInstance().log("Initializing scorer with BOSS best order."); - } - } else if (startWith == START_WITH.GRASP) { - if (verbose) { - TetradLogger.getInstance().log("Running GRaSP..."); - } + var permutationSearch = getBossSearch(); + dag = permutationSearch.search(false); + best = permutationSearch.getOrder(); + best = dag.paths().getValidOrder(best, true); - long start = MillisecondTimes.wallTimeMillis(); + long stop = MillisecondTimes.wallTimeMillis(); - Grasp grasp = getGraspSearch(); - best = grasp.bestOrder(nodes); - dag = grasp.getGraph(false); + if (verbose) { + TetradLogger.getInstance().log("BOSS took " + (stop - start) + " ms."); + } - long stop = MillisecondTimes.wallTimeMillis(); + if (verbose) { + TetradLogger.getInstance().log("Initializing PAG to BOSS CPDAG."); + TetradLogger.getInstance().log("Initializing scorer with BOSS best order."); + } + } else if (startWith == START_WITH.GRASP) { + if (verbose) { + TetradLogger.getInstance().log("Running GRaSP..."); + } - if (verbose) { - TetradLogger.getInstance().log("GRaSP took " + (stop - start) + " ms."); + long start = MillisecondTimes.wallTimeMillis(); + + Grasp grasp = getGraspSearch(); + best = grasp.bestOrder(nodes); + dag = grasp.getGraph(false); + + long stop = MillisecondTimes.wallTimeMillis(); + + if (verbose) { + TetradLogger.getInstance().log("GRaSP took " + (stop - start) + " ms."); + } + + if (verbose) { + TetradLogger.getInstance().log("Initializing PAG to GRaSP CPDAG."); + TetradLogger.getInstance().log("Initializing scorer with GRaSP best order."); + } + } else { + throw new IllegalArgumentException("Unknown startWith algorithm: " + startWith); } if (verbose) { - TetradLogger.getInstance().log("Initializing PAG to GRaSP CPDAG."); - TetradLogger.getInstance().log("Initializing scorer with GRaSP best order."); + TetradLogger.getInstance().log("Best order: " + best); } - } else { - throw new IllegalArgumentException("Unknown startWith algorithm: " + startWith); - } - - if (verbose) { - TetradLogger.getInstance().log("Best order: " + best); } var scorer = new TeyssierScorer(test, score); + scorer.score(best); scorer.setKnowledge(knowledge); - double bestScore = scorer.score(best); scorer.bookmark(); // We initialize the estimated PAG to the BOSS/GRaSP CPDAG. - Graph pag = new EdgeListGraph(dag); + pag = new EdgeListGraph(dag); if (verbose) { TetradLogger.getInstance().log("Initializing PAG to BOSS CPDAG."); TetradLogger.getInstance().log("Initializing scorer with BOSS best order."); } - R0R4Strategy strategy = R0R4StrategyTestBased.specialConfiguration( - test, knowledge, doDiscriminatingPathTailRule, doDiscriminatingPathColliderRule, false); + R0R4Strategy strategy = R0R4StrategyTestBased.specialConfiguration(test, knowledge, doDiscriminatingPathTailRule, doDiscriminatingPathColliderRule, false); FciOrient fciOrient = new FciOrient(strategy); fciOrient.setMaxPathLength(maxDdpPathLength); @@ -265,9 +285,10 @@ public Graph search() { Set unshieldedColliders = new HashSet<>(); Set checked = new HashSet<>(); - reorientWithCircles(pag, verbose); + scorer.score(best); + GraphUtils.reorientWithCircles(pag, verbose); - // We're looking for unshielded colliders in these next steps that we can detect without using only + // We're looking for unshielded colliders in these next steps that we can detect without using only // the scorer. We do this by looking at the structure of the DAG implied by the BOSS graph and nearby graphs // that can be reached by constrained tucking. The BOSS graph should be edge minimal, so should have the // highest number of unshielded colliders to copy to the PAG. Nearby graphs should have fewer unshielded @@ -279,8 +300,8 @@ public Graph search() { for (Node x : adj) { for (Node y : adj) { - if (distinct(x, b, y) && !checked.contains(new Triple(x, b, y))) { - checkUntucked(x, b, y, pag, dag, scorer, bestScore, unshieldedColliders, checked); + if (GraphUtils.distinct(x, b, y) && !checked.contains(new Triple(x, b, y))) { + checkUntucked(x, b, y, pag, dag, scorer, unshieldedColliders, checked); } } } @@ -290,16 +311,19 @@ public Graph search() { // cycles; it's the subsequent testing steps that cause them. So we do not need to remove any // unshielded colliders that are in this set to resolve almost-cycles. - // These will be the unshielded colldiers that are found in the subsequent steps. + // These will be the unshielded colliders that are found in the subsequent steps. Set subsequentUnshieldedColliders = new HashSet<>(); - reorientWithCircles(pag, verbose); - doRequiredOrientations(fciOrient, pag, best, knowledge, false); - recallUnshieldedTriples(pag, unshieldedColliders, knowledge); + GraphUtils.reorientWithCircles(pag, verbose); + GraphUtils.doRequiredOrientations(fciOrient, pag, best, knowledge, false); + GraphUtils.recallUnshieldedTriples(pag, unshieldedColliders, knowledge); Map> extraSepsets; - if (!ablationLeaveOutTestingStep) { + if (ablationLeaveOutTestingStep) { + fciOrient.setDoDiscriminatingPathColliderRule(false); + fciOrient.setDoDiscriminatingPathTailRule(false); + } else { // Remove extra edges using a test by examining paths in the BOSS/GRaSP DAG. The goal of this is to find a // sufficient set of sepsets to test for extra edges in the PAG that is small, preferably just one test @@ -311,9 +335,9 @@ public Graph search() { TetradLogger.getInstance().log("Doing implied orientation after extra sepsets found"); } - reorientWithCircles(pag, verbose); - doRequiredOrientations(fciOrient, pag, best, knowledge, verbose); - recallUnshieldedTriples(pag, unshieldedColliders, knowledge); + GraphUtils.reorientWithCircles(pag, verbose); + GraphUtils.doRequiredOrientations(fciOrient, pag, best, knowledge, verbose); + GraphUtils.recallUnshieldedTriples(pag, unshieldedColliders, knowledge); if (verbose) { TetradLogger.getInstance().log("Finished implied orientation after extra sepsets found"); @@ -333,738 +357,472 @@ public Graph search() { } // Final FCI orientation. - if (verbose) { TetradLogger.getInstance().log("Doing implied orientation, grabbing unshielded colliders from FciOrient."); } fciOrient.setInitialAllowedColliders(new HashSet<>()); fciOrient.finalOrientation(pag); - unshieldedColliders.addAll(fciOrient.getInitialAllowedColliders()); - subsequentUnshieldedColliders.addAll(fciOrient.getInitialAllowedColliders()); - fciOrient.setInitialAllowedColliders(null); if (verbose) { TetradLogger.getInstance().log("Finished implied orientation."); } - if (verbose) { - TetradLogger.getInstance().log("Removing almost cycles."); - } - - Set _unshieldedColliders = new HashSet<>(unshieldedColliders); - - while (true) { - Graph mag = GraphTransforms.zhangMagFromPag(pag); - - // Make a list of all where x <-> y and x ~~> y. - Set almostCyclesSet = new HashSet<>(); - - for (Edge edge : mag.getEdges()) { - if (Edges.isBidirectedEdge(edge)) { - if (mag.paths().existsDirectedPath(edge.getNode1(), edge.getNode2())) { - Edge e = Edges.directedEdge(edge.getNode1(), edge.getNode2()); - almostCyclesSet.add(e); - } else if (mag.paths().existsDirectedPath(edge.getNode2(), edge.getNode1())) { - Edge e = Edges.directedEdge(edge.getNode2(), edge.getNode1()); - almostCyclesSet.add(e); - } - } - } - - if (almostCyclesSet.isEmpty()) { - break; - } - - if (verbose) { - StringBuilder sb = new StringBuilder(); - sb.append("Almost cycles: "); - - for (Edge _almostCycle : almostCyclesSet) { - sb.append(_almostCycle.getNode1()).append(" ~~> ").append(_almostCycle.getNode2()).append(" "); - } - - TetradLogger.getInstance().log(sb.toString()); - - TetradLogger.getInstance().log("# almost cycles = " + almostCyclesSet.size()); - } - - for (Edge almostCycle : almostCyclesSet) { - - Node x = almostCycle.getNode1(); - Node y = almostCycle.getNode2(); - - // Find all unshielded triples z *-> x <-> y in subsequentUnshieldedColliders - Set unshieldedTriplesIntoX = new HashSet<>(); - - for (Triple triple : new HashSet<>(_unshieldedColliders)) { - if (triple.getY().equals(x) && triple.getZ().equals(y)) { - if (mag.getNodesInTo(x, Endpoint.ARROW).contains(triple.getX())) { - _unshieldedColliders.remove(triple); - unshieldedTriplesIntoX.add(triple); - } - } else if (triple.getY().equals(x) && triple.getX().equals(y)) { - if (mag.getNodesInTo(x, Endpoint.ARROW).contains(triple.getZ())) { - _unshieldedColliders.remove(triple); - unshieldedTriplesIntoX.add(triple); - } - } - } - - // Remove any unshielded collider in unshieldedTriplesIntoX from the _unshieldedColliders. - if (!unshieldedColliders.isEmpty()) { - if (verbose) { - TetradLogger.getInstance().log("Removing almost cycle " + almostCycle.getNode1() + " ~~> " + almostCycle.getNode2()); - TetradLogger.getInstance().log("Removing triples : " + unshieldedTriplesIntoX); - } - } - } - - if (verbose) { - TetradLogger.getInstance().log("Done removing almost cycles this round."); - } - - // Rebuild the PAG with this new unshielded collider set. - - if (verbose) { - TetradLogger.getInstance().log("Rebuilding graph."); - } - - reorientWithCircles(pag, verbose); - doRequiredOrientations(fciOrient, pag, best, knowledge, verbose); - recallUnshieldedTriples(pag, _unshieldedColliders, knowledge); - - if (verbose) { - TetradLogger.getInstance().log("Finished rebuilding graph."); - } - - if (verbose) { - TetradLogger.getInstance().log("Final orientation."); - } - - fciOrient.setVerbose(false); - fciOrient.setAllowedColliders(_unshieldedColliders); - fciOrient.finalOrientation(pag); - - if (verbose) { - TetradLogger.getInstance().log("Finished final orientation."); - } + if (repairFaultyPag) { + pag = GraphUtils.repairFaultyPag(pag, fciOrient, knowledge, unshieldedColliders, false, verbose); } if (verbose) { - TetradLogger.getInstance().log("All done removing almost cycles."); + TetradLogger.getInstance().log("LV-Lite finished."); } -// Graph mag = GraphTransforms.zhangMagFromPag(pag); -// -// for (Node node : mag.getNodes()) { -// if (mag.paths().existsDirectedPath(node, node)) { -// for (Triple triple : new HashSet<>(_unshieldedColliders)) { -// List nodesInTo = mag.getNodesInTo(node, Endpoint.ARROW); -// -// if (nodesInTo.contains(triple.getX()) && nodesInTo.contains(triple.getZ())) { -// _unshieldedColliders.remove(triple); -// } -// } -// } -// } -// -// // Rebuild the PAG with this new unshielded collider set. -// reorientWithCircles(pag, verbose); -// doRequiredOrientations(fciOrient, pag, best, knowledge, verbose); -// recallUnshieldedTriples(pag, _unshieldedColliders, knowledge); -// fciOrient.setVerbose(false); -// fciOrient.setAllowedColliders(_unshieldedColliders); -// fciOrient.finalOrientation(pag); - - if (repairFaultyPag) { - GraphUtils.repairFaultyPag(pag, fciOrient, knowledge, unshieldedColliders, verbose, ablationLeaveOutFinalOrientation); - } + return GraphUtils.replaceNodes(pag, this.score.getVariables()); + } - if (verbose) { - TetradLogger.getInstance().log("LV-Lite finished."); - } + /** + * Try adding an unshielded collider by checking the BOSS/GRaSP DAG. + * + * @param x Node - The first node. + * @param b Node - The second node. + * @param y Node - The third node. + * @param pag Graph - The graph to operate on. + * @param scorer The scorer to use for scoring the colliders. + * @param unshieldedColliders The set to store unshielded colliders. + * @param checked The set to store already checked nodes. + */ + private void checkUntucked(Node x, Node b, Node y, Graph pag, Graph cpdag, TeyssierScorer scorer, Set unshieldedColliders, Set checked) { + tryAddingCollider(x, b, y, pag, cpdag, scorer, unshieldedColliders, checked, knowledge, verbose); + } - return GraphUtils.replaceNodes(pag, this.score.getVariables()); - } + /** + * Parameterizes and returns a new BOSS search. + * + * @return A new BOSS search. + */ + private @NotNull PermutationSearch getBossSearch() { + var suborderSearch = new Boss(score); + suborderSearch.setResetAfterBM(true); + suborderSearch.setResetAfterRS(true); + suborderSearch.setVerbose(false); + suborderSearch.setUseBes(useBes); + suborderSearch.setUseDataOrder(useDataOrder); + suborderSearch.setNumStarts(numStarts); + suborderSearch.setVerbose(verbose); + var permutationSearch = new PermutationSearch(suborderSearch); + permutationSearch.setKnowledge(knowledge); + permutationSearch.search(); + return permutationSearch; + } - /** - * Try adding an unshielded collider by checking the BOSS/GRaSP DAG. - * - * @param x Node - The first node. - * @param b Node - The second node. - * @param y Node - The third node. - * @param pag Graph - The graph to operate on. - * @param scorer The scorer to use for scoring the colliders. - * @param bestScore double - The best score obtained so far. - * @param unshieldedColliders The set to store unshielded colliders. - * @param checked The set to store already checked nodes. - */ - private void checkUntucked (Node x, Node b, Node y, Graph pag, Graph cpdag, TeyssierScorer scorer, - double bestScore, Set unshieldedColliders, Set < Triple > checked){ - tryAddingCollider(x, b, y, pag, cpdag, false, scorer, bestScore, bestScore, unshieldedColliders, checked, knowledge, verbose); - } + /** + * Parameterizes and returns a new GRaSP search. + * + * @return A new GRaSP search. + */ + private @NotNull Grasp getGraspSearch() { + Grasp grasp = new Grasp(test, score); + + grasp.setSeed(-1); + grasp.setDepth(recursionDepth); + grasp.setUncoveredDepth(1); + grasp.setNonSingularDepth(1); + grasp.setOrdered(true); + grasp.setUseScore(true); + grasp.setUseRaskuttiUhler(false); + grasp.setUseDataOrder(useDataOrder); + grasp.setAllowInternalRandomness(true); + grasp.setVerbose(false); + + grasp.setNumStarts(numStarts); + grasp.setKnowledge(this.knowledge); + return grasp; + } - /** - * Parameterizes and returns a new BOSS search. - * - * @return A new BOSS search. - */ - private @NotNull PermutationSearch getBossSearch () { - var suborderSearch = new Boss(score); - suborderSearch.setResetAfterBM(true); - suborderSearch.setResetAfterRS(true); - suborderSearch.setVerbose(false); - suborderSearch.setUseBes(useBes); - suborderSearch.setUseDataOrder(useDataOrder); - suborderSearch.setNumStarts(numStarts); - suborderSearch.setVerbose(verbose); - var permutationSearch = new PermutationSearch(suborderSearch); - permutationSearch.setKnowledge(knowledge); - permutationSearch.search(); - return permutationSearch; + /** + * Sets the maximum length of any discriminating path. + * + * @param maxBlockingPathLength the maximum length of any discriminating path, or -1 if unlimited. + */ + public void setMaxBlockingPathLength(int maxBlockingPathLength) { + if (maxBlockingPathLength < -1) { + throw new IllegalArgumentException("Max path length must be -1 (unlimited) or >= 0: " + maxBlockingPathLength); } - /** - * Parameterizes and returns a new GRaSP search. - * - * @return A new GRaSP search. - */ - private @NotNull Grasp getGraspSearch () { - Grasp grasp = new Grasp(test, score); - - grasp.setSeed(-1); - grasp.setDepth(recursionDepth); - grasp.setUncoveredDepth(1); - grasp.setNonSingularDepth(1); - grasp.setOrdered(true); - grasp.setUseScore(true); - grasp.setUseRaskuttiUhler(false); - grasp.setUseDataOrder(useDataOrder); - grasp.setAllowInternalRandomness(true); - grasp.setVerbose(false); - - grasp.setNumStarts(numStarts); - grasp.setKnowledge(this.knowledge); - return grasp; - } + this.maxBlockingPathLength = maxBlockingPathLength; + } - /** - * Sets the maximum length of any discriminating path. - * - * @param maxBlockingPathLength the maximum length of any discriminating path, or -1 if unlimited. - */ - public void setMaxBlockingPathLength ( int maxBlockingPathLength){ - if (maxBlockingPathLength < -1) { - throw new IllegalArgumentException("Max path length must be -1 (unlimited) or >= 0: " + maxBlockingPathLength); - } + /** + * Sets the depth of the GRaSP if it is used. + * + * @param recursionDepth The depth of the GRaSP. + */ + public void setRecursionDepth(int recursionDepth) { + this.recursionDepth = recursionDepth; + } - this.maxBlockingPathLength = maxBlockingPathLength; - } + /** + * Sets whether to repair a faulty PAG. + * + * @param repairFaultyPag true if a faulty PAGs should be repaired, false otherwise + */ + public void setRepairFaultyPag(boolean repairFaultyPag) { + this.repairFaultyPag = repairFaultyPag; + } - /** - * Sets the allowable score drop used in the process triples step. Higher bounds may orient more colliders. - * - * @param maxScoreDrop the new equality threshold value - */ - public void setMaxScoreDrop ( double maxScoreDrop){ - if (Double.isNaN(maxScoreDrop) || Double.isInfinite(maxScoreDrop)) { - throw new IllegalArgumentException("Equality threshold must be a finite number: " + maxScoreDrop); - } + /** + * Sets the algorithm to use to obtain the initial CPDAG. + * + * @param startWith the algorithm to use to obtain the initial CPDAG. + */ + public void setStartWith(START_WITH startWith) { + this.startWith = startWith; + } - if (maxScoreDrop < 0) { - throw new IllegalArgumentException("Equality threshold must be >= 0: " + maxScoreDrop); - } + /** + * Sets the knowledge used in search. + * + * @param knowledge This knowledge. + */ + public void setKnowledge(Knowledge knowledge) { + this.knowledge = new Knowledge(knowledge); + } - this.maxScoreDrop = maxScoreDrop; - } + /** + * Sets whether the complete rule set should be used during the search algorithm. By default, the complete rule set + * is not used. + * + * @param completeRuleSetUsed true if the complete rule set should be used, false otherwise + */ + public void setCompleteRuleSetUsed(boolean completeRuleSetUsed) { + this.completeRuleSetUsed = completeRuleSetUsed; + } - /** - * Sets the depth of the GRaSP if it is used. - * - * @param recursionDepth The depth of the GRaSP. - */ - public void setRecursionDepth ( int recursionDepth){ - this.recursionDepth = recursionDepth; - } + /** + * Sets the verbosity level of the search algorithm. + * + * @param verbose true to enable verbose mode, false to disable it + */ + public void setVerbose(boolean verbose) { + this.verbose = verbose; + } - /** - * Sets whether to repair a faulty PAG. - * - * @param repairFaultyPag true if a faulty PAG should be repaired, false otherwise - */ - public void setRepairFaultyPag ( boolean repairFaultyPag){ - this.repairFaultyPag = repairFaultyPag; - } + /** + * Sets the number of starts for BOSS. + * + * @param numStarts The number of starts. + */ + public void setNumStarts(int numStarts) { + this.numStarts = numStarts; + } - /** - * Sets the algorithm to use to obtain the initial CPDAG. - * - * @param startWith the algorithm to use to obtain the initial CPDAG. - */ - public void setStartWith (START_WITH startWith){ - this.startWith = startWith; - } + /** + * Sets whether the discriminating path tail rule should be used. + * + * @param doDiscriminatingPathTailRule True, if so. + */ + public void setDoDiscriminatingPathTailRule(boolean doDiscriminatingPathTailRule) { + this.doDiscriminatingPathTailRule = doDiscriminatingPathTailRule; + } - /** - * Sets the knowledge used in search. - * - * @param knowledge This knowledge. - */ - public void setKnowledge (Knowledge knowledge){ - this.knowledge = new Knowledge(knowledge); - } + /** + * Sets whether the discriminating path collider rule should be used. + * + * @param doDiscriminatingPathColliderRule True, if so. + */ + public void setDoDiscriminatingPathColliderRule(boolean doDiscriminatingPathColliderRule) { + this.doDiscriminatingPathColliderRule = doDiscriminatingPathColliderRule; + } - /** - * Sets whether the complete rule set should be used during the search algorithm. By default, the complete rule set - * is not used. - * - * @param completeRuleSetUsed true if the complete rule set should be used, false otherwise - */ - public void setCompleteRuleSetUsed ( boolean completeRuleSetUsed){ - this.completeRuleSetUsed = completeRuleSetUsed; - } + /** + * Sets whether to use the BES (Backward Elimination Search) algorithm during the search. + * + * @param useBes true to use the BES algorithm, false otherwise + */ + public void setUseBes(boolean useBes) { + this.useBes = useBes; + } - /** - * Sets the verbosity level of the search algorithm. - * - * @param verbose true to enable verbose mode, false to disable it - */ - public void setVerbose ( boolean verbose){ - this.verbose = verbose; - } + /** + * Sets the flag indicating whether to use data order. + * + * @param useDataOrder {@code true} if the data order should be used, {@code false} otherwise. + */ + public void setUseDataOrder(boolean useDataOrder) { + this.useDataOrder = useDataOrder; + } - /** - * Sets the number of starts for BOSS. - * - * @param numStarts The number of starts. - */ - public void setNumStarts ( int numStarts){ - this.numStarts = numStarts; + /** + * Tries removing extra edges from the PAG using a test with sepsets obtained by examining the BOSS/GRaSP DAG. + * + * @param pag The graph in which to remove extra edges. + * @param unshieldedColliders A set to store the unshielded colliders found during the removal process. + * @return A map of edges to remove to sepsets used to remove them. The sepsets are the conditioning sets used to + * remove the edges. These can be used to do orientation of common adjacents, as x *->: b <-* y just in case b + * is not in this sepset. + */ + private Map> removeExtraEdges(Graph pag, Set unshieldedColliders) { + if (verbose) { + TetradLogger.getInstance().log("Checking for additional sepsets:"); } - /** - * Sets whether the discriminating path tail rule should be used. - * - * @param doDiscriminatingPathTailRule True, if so. - */ - public void setDoDiscriminatingPathTailRule ( boolean doDiscriminatingPathTailRule){ - this.doDiscriminatingPathTailRule = doDiscriminatingPathTailRule; - } + // Note that we can use the MAG here instead of the DAG. + Map> extraSepsets = new ConcurrentHashMap<>(); - /** - * Sets whether the discriminating path collider rule should be used. - * - * @param doDiscriminatingPathColliderRule True, if so. - */ - public void setDoDiscriminatingPathColliderRule ( boolean doDiscriminatingPathColliderRule){ - this.doDiscriminatingPathColliderRule = doDiscriminatingPathColliderRule; - } + // TODO: Explore the speed and accuracy implications for doing the extra edge removal in parallel or + // in serial. + if (extraEdgeRemovalStyle == ExtraEdgeRemovalStyle.PARALLEL) { + List>>> tasks = new ArrayList<>(); - /** - * Sets whether to use the BES (Backward Elimination Search) algorithm during the search. - * - * @param useBes true to use the BES algorithm, false otherwise - */ - public void setUseBes ( boolean useBes){ - this.useBes = useBes; - } + for (Edge edge : pag.getEdges()) { + tasks.add(() -> { + Set sepset = SepsetFinder.getSepsetPathBlockingOutOfX(pag, edge.getNode1(), edge.getNode2(), test, maxBlockingPathLength, depth, true, new HashSet<>()); - /** - * Sets the flag indicating whether to use data order. - * - * @param useDataOrder {@code true} if the data order should be used, {@code false} otherwise. - */ - public void setUseDataOrder ( boolean useDataOrder){ - this.useDataOrder = useDataOrder; - } + return Pair.of(edge, sepset); + }); + } - /** - * Reorients all edges in a Graph as o-o. This method is used to apply the o-o orientation to all edges in the given - * Graph following the PAG (Partially Ancestral Graph) structure. - * - * @param pag The Graph to be reoriented. - * @param verbose A boolean value indicating whether verbose output should be printed. - */ - private void reorientWithCircles (Graph pag,boolean verbose){ - if (verbose) { - TetradLogger.getInstance().log("Orient all edges in PAG as o-o:"); + List>> results; + + if (testTimeout == -1) { + results = tasks.parallelStream().map(task -> { + try { + return task.call(); + } catch (Exception e) { + return null; + } + }).toList(); + } else if (testTimeout > 0) { + results = tasks.parallelStream().map(task -> GraphSearchUtils.runWithTimeout(task, testTimeout, TimeUnit.MILLISECONDS)).toList(); + } else { + throw new IllegalArgumentException("Test timeout must be -1 (unlimited) or > 0: " + testTimeout); } - pag.reorientAllWith(Endpoint.CIRCLE); - } - /** - * Recall unshielded triples in a given graph. - * - * @param pag The graph to recall unshielded triples from. - * @param unshieldedColliders The set of unshielded colliders that need to be recalled. - * @param knowledge the knowledge object. - */ - private void recallUnshieldedTriples (Graph pag, Set < Triple > unshieldedColliders, Knowledge knowledge){ - for (Triple triple : unshieldedColliders) { - Node x = triple.getX(); - Node b = triple.getY(); - Node y = triple.getZ(); - - // We can avoid creating almost cycles here, but this does not solve the problem, as we can still - // creat almost cycles in final orientation. - if (colliderAllowed(pag, x, b, y, knowledge) && triple(pag, x, b, y) && !couldCreateAlmostCycle(pag, x, y)) { - pag.setEndpoint(x, b, Endpoint.ARROW); - pag.setEndpoint(y, b, Endpoint.ARROW); - pag.removeEdge(x, y); + for (Pair> _edge : results) { + if (_edge != null && _edge.getRight() != null) { + extraSepsets.put(_edge.getLeft(), _edge.getRight()); } } - } - - /** - * Checks if creating an almost cycle between nodes x, b, and y is possible in a given graph. - * - * @param pag The graph to check if the almost cycle can be created. - * @param x The first node of the almost cycle. - * @param y The third node of the almost cycle. - * @return True if creating the almost cycle is possible, false otherwise. - */ - private boolean couldCreateAlmostCycle (Graph pag, Node x, Node y){ - return pag.paths().isAncestorOf(x, y) || pag.paths().isAncestorOf(y, x); - } - /** - * Tries removing extra edges from the PAG using a test with sepsets obtained by examining the BOSS/GRaSP DAG. - * - * @param pag The graph in which to remove extra edges. - * @param unshieldedColliders A set to store the unshielded colliders found during the removal process. - * @return A map of edges to remove to sepsets used to remove them. The sepsets are the conditioning sets used to - * remove the edges. These can be used to do orientation of common adjacents, as x *->: b <-* y just in case b - * is not in this sepset. - */ - private Map> removeExtraEdges (Graph pag, Set < Triple > unshieldedColliders){ - if (verbose) { - TetradLogger.getInstance().log("Checking for additional sepsets:"); + for (Pair> _edge : results) { + if (_edge != null && _edge.getRight() != null) { + orientCommonAdjacents(_edge.getLeft(), pag, unshieldedColliders, extraSepsets); + } } + } else if (extraEdgeRemovalStyle == ExtraEdgeRemovalStyle.SERIAL) { - ForkJoinPool executor = new ForkJoinPool(); - - // Note that we can use the MAG here instead of the DAG. - Map> extraSepsets = new ConcurrentHashMap<>(); + Set edges = new HashSet<>(pag.getEdges()); + Set visited = new HashSet<>(); + Deque toVisit = new LinkedList<>(edges); - // TODO: Explore the speed and accuracy implications for doing the extra edge removal in parallel or - // in serial. - if (extraEdgeRemovalStyle == ExtraEdgeRemovalStyle.PARALLEL) { - List>>> tasks = new ArrayList<>(); + // Sort edges x *-* y in toVisit by |adj(x)| + |adj(y)|. + toVisit = toVisit.stream().sorted(Comparator.comparingInt(edge -> pag.getAdjacentNodes(edge.getNode1()).size() + pag.getAdjacentNodes(edge.getNode2()).size())).collect(Collectors.toCollection(LinkedList::new)); - for (Edge edge : pag.getEdges()) { - tasks.add(() -> { - Set sepset = SepsetFinder.getSepsetPathBlockingOutOfX(pag, edge.getNode1(), - edge.getNode2(), test, maxBlockingPathLength, depth, true, - new HashSet<>()); + while (!toVisit.isEmpty()) { + Edge edge = toVisit.removeFirst(); + visited.add(edge); -// System.out.println("Sepset for edge " + edge + " = " + sepset); + Set sepset = SepsetFinder.getSepsetPathBlockingOutOfX(pag, edge.getNode1(), edge.getNode2(), test, maxBlockingPathLength, depth, true, new HashSet<>()); - return Pair.of(edge, sepset); - }); + if (verbose) { + TetradLogger.getInstance().log("For edge " + edge + " sepset: " + sepset); } - List>> results; - - if (testTimeout == -1) { - results = tasks.parallelStream() - .map(task -> { - try { - return task.call(); - } catch (Exception e) { -// e.printStackTrace(); - return null; - } - }).toList(); - } else if (testTimeout > 0) { - results = tasks.parallelStream() - .map(task -> GraphSearchUtils.runWithTimeout(task, testTimeout, TimeUnit.MILLISECONDS)) - .toList(); - } else { - throw new IllegalArgumentException("Test timeout must be -1 (unlimited) or > 0: " + testTimeout); - } + if (sepset != null) { + extraSepsets.put(edge, sepset); + pag.removeEdge(edge.getNode1(), edge.getNode2()); + orientCommonAdjacents(edge, pag, unshieldedColliders, extraSepsets); - for (Pair> _edge : results) { - if (_edge != null && _edge.getRight() != null) { - extraSepsets.put(_edge.getLeft(), _edge.getRight()); + for (Node node : pag.getAdjacentNodes(edge.getNode1())) { + Edge adjacentEdge = pag.getEdge(node, edge.getNode1()); + if (!visited.contains(adjacentEdge)) { + toVisit.remove(adjacentEdge); + toVisit.addFirst(adjacentEdge); + } } - } - for (Pair> _edge : results) { - if (_edge != null && _edge.getRight() != null) { - orientCommonAdjacents(_edge.getLeft(), pag, unshieldedColliders, extraSepsets); + for (Node node : pag.getAdjacentNodes(edge.getNode2())) { + Edge adjacentEdge = pag.getEdge(node, edge.getNode2()); + if (!visited.contains(adjacentEdge)) { + toVisit.remove(adjacentEdge); + toVisit.addFirst(adjacentEdge); + } } } - } else if (extraEdgeRemovalStyle == ExtraEdgeRemovalStyle.SERIAL) { + } + } - Set edges = new HashSet<>(pag.getEdges()); - Set visited = new HashSet<>(); - Deque toVisit = new LinkedList<>(edges); + if (verbose) { + TetradLogger.getInstance().log("Done checking for additional sepsets max length = " + maxBlockingPathLength + "."); + } - // Sort edges x *-* y in toVisit by |adj(x)| + |adj(y)|. - toVisit = toVisit.stream().sorted(Comparator.comparingInt( - edge -> pag.getAdjacentNodes(edge.getNode1()).size() + pag.getAdjacentNodes( - edge.getNode2()).size())).collect(Collectors.toCollection(LinkedList::new)); + return extraSepsets; + } - while (!toVisit.isEmpty()) { - Edge edge = toVisit.removeFirst(); - visited.add(edge); + /** + * Orients an unshielded collider in a graph based on a sepset from a test and adds the unshielded collider to the + * set of unshielded colliders. + * + * @param edge The edge to remove the adjacency for. + * @param pag The graph in which to orient the unshielded collider. + * @param unshieldedColliders The set of unshielded colliders to add the new unshielded collider to. + * @param extraSepsets The map of edges to sepsets used to remove them. + */ + private void orientCommonAdjacents(Edge edge, Graph pag, Set unshieldedColliders, Map> extraSepsets) { - Set sepset = SepsetFinder.getSepsetPathBlockingOutOfX(pag, edge.getNode1(), - edge.getNode2(), test, maxBlockingPathLength, depth, true, - new HashSet<>()); + List common = pag.getAdjacentNodes(edge.getNode1()); + common.retainAll(pag.getAdjacentNodes(edge.getNode2())); - if (verbose) { - TetradLogger.getInstance().log("For edge " + edge + " sepset: " + sepset); - } + pag.removeEdge(edge.getNode1(), edge.getNode2()); - if (sepset != null) { - extraSepsets.put(edge, sepset); - pag.removeEdge(edge.getNode1(), edge.getNode2()); - orientCommonAdjacents(edge, pag, unshieldedColliders, extraSepsets); - - for (Node node : pag.getAdjacentNodes(edge.getNode1())) { - Edge adjacentEdge = pag.getEdge(node, edge.getNode1()); - if (!visited.contains(adjacentEdge)) { - toVisit.remove(adjacentEdge); - toVisit.addFirst(adjacentEdge); - } - } + for (Node node : common) { + if (!extraSepsets.get(edge).contains(node)) { + pag.setEndpoint(edge.getNode1(), node, Endpoint.ARROW); + pag.setEndpoint(edge.getNode2(), node, Endpoint.ARROW); - for (Node node : pag.getAdjacentNodes(edge.getNode2())) { - Edge adjacentEdge = pag.getEdge(node, edge.getNode2()); - if (!visited.contains(adjacentEdge)) { - toVisit.remove(adjacentEdge); - toVisit.addFirst(adjacentEdge); - } - } - } + if (verbose) { + TetradLogger.getInstance().log("Oriented " + edge.getNode1() + " *-> " + node + " <-* " + edge.getNode2() + " in PAG."); } - } - if (verbose) { - TetradLogger.getInstance().log("Done checking for additional sepsets max length = " + maxBlockingPathLength + "."); + unshieldedColliders.add(new Triple(edge.getNode1(), node, edge.getNode2())); } - - return extraSepsets; } - /** - * Orients an unshielded collider in a graph based on a sepset from a test and adds the unshielded collider to the - * set of unshielded colliders. - * - * @param edge The edge to remove the adjacency for. - * @param pag The graph in which to orient the unshielded collider. - * @param unshieldedColliders The set of unshielded colliders to add the new unshielded collider to. - * @param extraSepsets The map of edges to sepsets used to remove them. - */ - private void orientCommonAdjacents (Edge edge, Graph - pag, Set < Triple > unshieldedColliders, Map < Edge, Set < Node >> extraSepsets){ - - List common = pag.getAdjacentNodes(edge.getNode1()); - common.retainAll(pag.getAdjacentNodes(edge.getNode2())); - - pag.removeEdge(edge.getNode1(), edge.getNode2()); - - for (Node node : common) { - if (!extraSepsets.get(edge).contains(node)) { - pag.setEndpoint(edge.getNode1(), node, Endpoint.ARROW); - pag.setEndpoint(edge.getNode2(), node, Endpoint.ARROW); - - if (verbose) { - TetradLogger.getInstance().log("Oriented " + edge.getNode1() + " *-> " + node + " <-* " + edge.getNode2() + " in PAG."); - } + } - unshieldedColliders.add(new Triple(edge.getNode1(), node, edge.getNode2())); + /** + * Adds a collider if it's a collider in the current scorer and knowledge permits it in the current PAG. + * + * @param x The first node of the unshielded collider. + * @param b The second node of the unshielded collider. + * @param y The third node of the unshielded collider. + * @param pag The graph in which to add the unshielded collider. + * @param scorer The scorer to use for scoring the unshielded collider. + * @param unshieldedColliders The set of unshielded colliders to add the new unshielded collider to. + * @param checked The set of checked unshielded colliders. + * @param knowledge The knowledge object. + * @param verbose A boolean flag indicating whether verbose output should be printed. + */ + private void tryAddingCollider(Node x, Node b, Node y, Graph pag, Graph cpdag, TeyssierScorer scorer, Set unshieldedColliders, Set checked, Knowledge knowledge, boolean verbose) { + if (cpdag != null) { + if (cpdag.isDefCollider(x, b, y) && !cpdag.isAdjacentTo(x, y)) { + unshieldedColliders.add(new Triple(x, b, y)); + checked.add(new Triple(x, b, y)); + + if (verbose) { + TetradLogger.getInstance().log("Copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); } } + } else if (GraphUtils.colliderAllowed(pag, x, b, y, knowledge)) { + if (scorer.unshieldedCollider(x, b, y)) { + unshieldedColliders.add(new Triple(x, b, y)); + checked.add(new Triple(x, b, y)); - } - - /** - * Adds a collider if it's a collider in the current scorer and knowledge permits it in the current PAG. - * - * @param x The first node of the unshielded collider. - * @param b The second node of the unshielded collider. - * @param y The third node of the unshielded collider. - * @param pag The graph in which to add the unshielded collider. - * @param tucked A boolean flag indicating whether the unshielded collider is tucked. - * @param scorer The scorer to use for scoring the unshielded collider. - * @param newScore The new score of the unshielded collider. - * @param bestScore The best score of the unshielded collider. - * @param unshieldedColliders The set of unshielded colliders to add the new unshielded collider to. - * @param checked The set of checked unshielded colliders. - * @param knowledge The knowledge object. - * @param verbose A boolean flag indicating whether verbose output should be printed. - */ - private void tryAddingCollider (Node x, Node b, Node y, Graph pag, Graph cpdag,boolean tucked, TeyssierScorer - scorer,double newScore, double bestScore, Set unshieldedColliders, Set < Triple > checked, Knowledge - knowledge,boolean verbose){ - if (cpdag != null) { - if (cpdag.isDefCollider(x, b, y) && !cpdag.isAdjacentTo(x, y)) { - unshieldedColliders.add(new Triple(x, b, y)); - checked.add(new Triple(x, b, y)); - - if (verbose) { - if (tucked) { - TetradLogger.getInstance().log("AFTER TUCKING copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); - } else { - TetradLogger.getInstance().log("Copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); - } - } - } - } else if (colliderAllowed(pag, x, b, y, knowledge)) { - if (scorer.unshieldedCollider(x, b, y) && (maxScoreDrop == -1 || newScore >= bestScore - maxScoreDrop)) { - unshieldedColliders.add(new Triple(x, b, y)); - checked.add(new Triple(x, b, y)); - - if (verbose) { - if (tucked) { - TetradLogger.getInstance().log("AFTER TUCKING copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); - } else { - TetradLogger.getInstance().log("Copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); - } - } + if (verbose) { + TetradLogger.getInstance().log("Copied " + x + " *-> " + b + " <-* " + y + " from CPDAG to PAG."); } } } + } - /** - * Checks if three nodes are connected in a graph. - * - * @param graph the graph to check for connectivity - * @param a the first node - * @param b the second node - * @param c the third node - * @return {@code true} if all three nodes are connected, {@code false} otherwise - */ - private boolean triple (Graph graph, Node a, Node b, Node c){ - return distinct(a, b, c) && graph.isAdjacentTo(a, b) && graph.isAdjacentTo(b, c); - } + /** + * Sets the maximum size of the separating set used in the graph search algorithm. + * + * @param depth the maximum size of the separating set + */ + public void setDepth(int depth) { + this.depth = depth; + } - /** - * Determines if the collider is allowed. - * - * @param pag The Graph representing the PAG. - * @param x The Node object representing the first node. - * @param b The Node object representing the second node. - * @param y The Node object representing the third node. - * @return true if the collider is allowed, false otherwise. - */ - private boolean colliderAllowed (Graph pag, Node x, Node b, Node y, Knowledge knowledge){ - return FciOrient.isArrowheadAllowed(x, b, pag, knowledge) && FciOrient.isArrowheadAllowed(y, b, pag, knowledge); + /** + * Sets whether the scoring step (BOSS or GRASP) should be left out during ablation. If this step is left out, the + * algorithm will start with a completely connected nondirected (o-o) graph, since the subsequent steps require an + * initial graph that is Markov. This will make the algorithm slow. + *

+ * One cannot leave out both the testing and scoring steps of the algorithm; one or the other must be enabled. + * + * @param ablationLeaveOutScoringStep True iff the scoring step should be left out. + */ + public void setAblationLeaveOutScoringStep(boolean ablationLeaveOutScoringStep) { + if (this.ablationLeaveOutTestingStep && ablationLeaveOutScoringStep) { + throw new IllegalArgumentException("Cannot leave out both the testing and scoring steps of the algorithm."); } - /** - * Orient required edges in PAG. - * - * @param fciOrient The FciOrient object used for orienting the edges. - * @param pag The Graph representing the PAG. - * @param best The list of Node objects representing the best nodes. - */ - private void doRequiredOrientations (FciOrient fciOrient, Graph pag, List < Node > best, Knowledge knowledge, - boolean verbose){ - if (verbose) { - TetradLogger.getInstance().log("Orient required edges in PAG:"); - } + this.ablationLeaveOutScoringStep = ablationLeaveOutScoringStep; + } - fciOrient.fciOrientbk(knowledge, pag, best); + /** + * Sets whether to the testing steps (extra edge removal and discriminating path steps) should be left out during + * ablation. If these stepw are left out, the algorithm will not remove extra edges or do discriminating path + * steps. + *

+ * One cannot leave out both the testing and scoring steps of the algorithm; one or the other must be enabled. + * + * @param ablationLeaveOutTestingStep the flag indicating whether to enable the ablation leave-out testing step. + */ + public void setAblationLeaveOutTestingStep(boolean ablationLeaveOutTestingStep) { + if (this.ablationLeaveOutScoringStep && ablationLeaveOutTestingStep) { + throw new IllegalArgumentException("Cannot leave out both the testing and scoring steps of the algorithm."); } - /** - * Determines whether three {@link Node} objects are distinct. - * - * @param x the first Node object - * @param b the second Node object - * @param y the third Node object - * @return true if x, b, and y are distinct; false otherwise - */ - private boolean distinct (Node x, Node b, Node y){ - return x != b && y != b && x != y; - } + this.ablationLeaveOutTestingStep = ablationLeaveOutTestingStep; + } - /** - * Sets the maximum size of the separating set used in the graph search algorithm. - * - * @param depth the maximum size of the separating set - */ - public void setDepth ( int depth){ - this.depth = depth; - } + /** + * Sets the maximum DDP path length. + * + * @param maxDdpPathLength the maximum DDP path length to set + */ + public void setMaxDdpPathLength(int maxDdpPathLength) { + this.maxDdpPathLength = maxDdpPathLength; + } - /** - * Sets whether testing is allowed or not. - * - * @param ablationLeaveOutTestingStep true if testing is allowed, false otherwise - */ - public void setAblationLeaveOutTestingStep ( boolean ablationLeaveOutTestingStep){ - this.ablationLeaveOutTestingStep = ablationLeaveOutTestingStep; - } + /** + * Sets the style for removing extra edges. + * + * @param extraEdgeRemovalStyle the style for removing extra edges + */ + public void setExtraEdgeRemovalStyle(ExtraEdgeRemovalStyle extraEdgeRemovalStyle) { + this.extraEdgeRemovalStyle = extraEdgeRemovalStyle; + } - /** - * Sets the maximum DDP path length. - * - * @param maxDdpPathLength the maximum DDP path length to set - */ - public void setMaxDdpPathLength ( int maxDdpPathLength){ - this.maxDdpPathLength = maxDdpPathLength; - } + /** + * Sets the timeout for the testing steps, for the extra edge removal steps and the discriminating path steps. + * + * @param testTimeout the timeout for the testing steps, for the extra edge removal steps and the discriminating + * path steps. + */ + public void setTestTimeout(long testTimeout) { + this.testTimeout = testTimeout; + } - /** - * ABLATION: Sets whether to leave out the final orientation. - * - * @param leaveOutFinalOrientation true if the final orientation should be left out, false otherwise - */ - public void ablationSetLeaveOutFinalOrientation ( boolean leaveOutFinalOrientation){ - this.ablationLeaveOutFinalOrientation = leaveOutFinalOrientation; - } + /** + * Enumeration representing different start options. + */ + public enum START_WITH { /** - * Sets the style for removing extra edges. - * - * @param extraEdgeRemovalStyle the style for removing extra edges + * Start with BOSS. */ - public void setExtraEdgeRemovalStyle (ExtraEdgeRemovalStyle extraEdgeRemovalStyle){ - this.extraEdgeRemovalStyle = extraEdgeRemovalStyle; - } - + BOSS, /** - * Sets the timeout for the testing steps, for the extra edge removal steps and the discriminating path steps. - * - * @param testTimeout the timeout for the testing steps, for the extra edge removal steps and the discriminating - * path steps. + * Start with GRaSP. */ - public void setTestTimeout ( long testTimeout){ - this.testTimeout = testTimeout; - } + GRASP + } + + /** + * The ExtraEdgeRemovalStyle enum specifies the styles for removing extra edges. + */ + public enum ExtraEdgeRemovalStyle { /** - * Enumeration representing different start options. + * Remove extra edges in parallel. */ - public enum START_WITH { - /** - * Start with BOSS. - */ - BOSS, - /** - * Start with GRaSP. - */ - GRASP - } + PARALLEL, /** - * The ExtraEdgeRemovalStyle enum specifies the styles for removing extra edges. + * Remove extra edges in serial. */ - public enum ExtraEdgeRemovalStyle { - - /** - * Remove extra edges in parallel. - */ - PARALLEL, - - /** - * Remove extra edges in serial. - */ - SERIAL, - } + SERIAL, } +} diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/MarkovCheck.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/MarkovCheck.java index c3247f3805..7318cf1fde 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/MarkovCheck.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/MarkovCheck.java @@ -1320,6 +1320,8 @@ public Pair, Set> call() { if (parallelized) { int parallelism = Runtime.getRuntime().availableProcessors(); + TetradLogger.getInstance().log("Parallelism: " + parallelism); + ForkJoinPool pool = new ForkJoinPool(parallelism); List, Set>>> theseResults; diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SpFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SpFci.java index e426dffc45..bb9250cabd 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SpFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SpFci.java @@ -21,10 +21,7 @@ package edu.cmu.tetrad.search; import edu.cmu.tetrad.data.Knowledge; -import edu.cmu.tetrad.graph.EdgeListGraph; -import edu.cmu.tetrad.graph.Graph; -import edu.cmu.tetrad.graph.GraphUtils; -import edu.cmu.tetrad.graph.Node; +import edu.cmu.tetrad.graph.*; import edu.cmu.tetrad.search.score.Score; import edu.cmu.tetrad.search.test.MsepTest; import edu.cmu.tetrad.search.utils.*; @@ -32,7 +29,9 @@ import edu.cmu.tetrad.util.TetradLogger; import java.io.PrintStream; +import java.util.HashSet; import java.util.List; +import java.util.Set; import static edu.cmu.tetrad.graph.GraphUtils.gfciExtraEdgeRemovalStep; @@ -101,14 +100,6 @@ public final class SpFci implements IGraphSearch { * (-1 in this case) indicates unlimited depth. */ private int depth = -1; - /** - * Determines whether the search algorithm should use the Discriminating Path Tail Rule. - */ - private boolean doDiscriminatingPathTailRule = true; - /** - * Determines whether the search algorithm should use the Discriminating Path Collider Rule. - */ - private boolean doDiscriminatingPathTCollideRule = true; /** * True iff verbose output should be printed. */ @@ -117,14 +108,12 @@ public final class SpFci implements IGraphSearch { * True iff the search should repair a faulty PAG. */ private boolean repairFaultyPag = false; - /** - * True iff the final orientation should be left out. - */ - private boolean ablationLeaveOutFinalOrientation; /** * The method to use for finding sepsets, 1 = greedy, 2 = min-p., 3 = max-p, default min-p. */ private int sepsetFinderMethod; + private boolean doDiscriminatingPathTailRule = true; + private boolean doDiscriminatingPathColliderRule = true; /** * Constructor; requires by ta test and a score, over the same variables. @@ -180,20 +169,21 @@ public Graph search() { throw new IllegalArgumentException("Invalid sepset finder method: " + sepsetFinderMethod); } - gfciExtraEdgeRemovalStep(graph, referenceDag, nodes, sepsets, verbose); - GraphUtils.gfciR0(graph, referenceDag, sepsets, knowledge, verbose); + Set unshieldedTriples = new HashSet<>(); - FciOrient fciOrient = new FciOrient( - R0R4StrategyTestBased.defaultConfiguration(independenceTest, new Knowledge())); + gfciExtraEdgeRemovalStep(graph, referenceDag, nodes, sepsets, depth, verbose); + GraphUtils.gfciR0(graph, referenceDag, sepsets, knowledge, verbose, unshieldedTriples); - if (!ablationLeaveOutFinalOrientation) { - fciOrient.finalOrientation(graph); - } + FciOrient fciOrient = new FciOrient( + R0R4StrategyTestBased.specialConfiguration(independenceTest, knowledge, doDiscriminatingPathTailRule, + doDiscriminatingPathColliderRule, verbose)); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); GraphUtils.replaceNodes(graph, this.independenceTest.getVariables()); if (repairFaultyPag) { - GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, null, verbose, ablationLeaveOutFinalOrientation); + graph = GraphUtils.repairFaultyPag(graph, fciOrient, knowledge, unshieldedTriples, false, verbose); } return graph; @@ -318,47 +308,38 @@ public void setDepth(int depth) { } /** - * Sets whether the discriminating path tail rule is done. - * - * @param doDiscriminatingPathTailRule True, if so. - */ - public void setDoDiscriminatingPathTailRule(boolean doDiscriminatingPathTailRule) { - this.doDiscriminatingPathTailRule = doDiscriminatingPathTailRule; - } - - /** - * Sets whether the discriminating path collider rule is done. + * Sets whether the search should repair a faulty PAG. * - * @param doDiscriminatingPathTCollideRule True, if so. + * @param repairFaultyPag True, if so. */ - public void setDoDiscriminatingPathCollideRule(boolean doDiscriminatingPathTCollideRule) { - this.doDiscriminatingPathTCollideRule = doDiscriminatingPathTCollideRule; + public void setRepairFaultyPag(boolean repairFaultyPag) { + this.repairFaultyPag = repairFaultyPag; } /** - * Sets whether the search should repair a faulty PAG. + * Sets the method to use for finding sepsets, 1 = greedy, 2 = min-p., 3 = max-p, default min-p. * - * @param repairFaultyPag True, if so. + * @param sepsetFinderMethod the method to use for finding sepsets */ - public void setRepairFaultyPag(boolean repairFaultyPag) { - this.repairFaultyPag = repairFaultyPag; + public void setSepsetFinderMethod(int sepsetFinderMethod) { + this.sepsetFinderMethod = sepsetFinderMethod; } /** - * Sets whether to leave out the final orientation in the search algorithm. + * Sets whether the discriminating path tail rule should be used. * - * @param ablationLeaveOutFinalOrientation true to leave out the final orientation, false otherwise. + * @param doDiscriminatingPathTailRule True, if so. */ - public void setLeaveOutFinalOrientation(boolean ablationLeaveOutFinalOrientation) { - this.ablationLeaveOutFinalOrientation = ablationLeaveOutFinalOrientation; + public void setDoDiscriminatingPathTailRule(boolean doDiscriminatingPathTailRule) { + this.doDiscriminatingPathTailRule = doDiscriminatingPathTailRule; } /** - * Sets the method to use for finding sepsets, 1 = greedy, 2 = min-p., 3 = max-p, default min-p. + * Sets whether the discriminating path collider rule should be used. * - * @param sepsetFinderMethod the method to use for finding sepsets + * @param doDiscriminatingPathColliderRule True, if so. */ - public void setSepsetFinderMethod(int sepsetFinderMethod) { - this.sepsetFinderMethod = sepsetFinderMethod; + public void setDoDiscriminatingPathColliderRule(boolean doDiscriminatingPathColliderRule) { + this.doDiscriminatingPathColliderRule = doDiscriminatingPathColliderRule; } } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SvarFci.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SvarFci.java index 2005c67371..cd9b6276b4 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SvarFci.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/SvarFci.java @@ -96,6 +96,7 @@ public final class SvarFci implements IGraphSearch { * Represents whether to resolve almost cyclic paths during the search. */ private boolean resolveAlmostCyclicPaths; + private boolean repairFaultyPag; /** * Constructs a new FCI search for the given independence test and background knowledge. @@ -164,6 +165,7 @@ public Graph search(IFas fas) { fas.setVerbose(this.verbose); this.graph = fas.search(); this.sepsets = fas.getSepsets(); + Set unshieldedTriples = new HashSet<>(); this.graph.reorientAllWith(Endpoint.CIRCLE); @@ -174,7 +176,7 @@ public Graph search(IFas fas) { fciOrient.setKnowledge(this.knowledge); fciOrient.setEndpointStrategy(new SvarSetEndpointStrategy(this.independenceTest, this.knowledge)); - fciOrient.ruleR0(this.graph); + fciOrient.ruleR0(this.graph, unshieldedTriples); for (Edge edge : new ArrayList<>(this.graph.getEdges())) { Node x = edge.getNode1(); @@ -207,9 +209,13 @@ public Graph search(IFas fas) { fciOrient.setCompleteRuleSetUsed(this.completeRuleSetUsed); fciOrient.setMaxPathLength(this.maxPathLength); fciOrient.setKnowledge(this.knowledge); - fciOrient.ruleR0(this.graph); + fciOrient.ruleR0(this.graph, unshieldedTriples); fciOrient.finalOrientation(this.graph); + if (repairFaultyPag) { + this.graph = GraphUtils.repairFaultyPag(this.graph, fciOrient, knowledge, unshieldedTriples, false, verbose); + } + if (resolveAlmostCyclicPaths) { for (Edge edge : graph.getEdges()) { if (Edges.isBidirectedEdge(edge)) { @@ -507,6 +513,10 @@ public String getNameNoLag(Object obj) { public void setResolveAlmostCyclicPaths(boolean resolveAlmostCyclicPaths) { this.resolveAlmostCyclicPaths = resolveAlmostCyclicPaths; } + + public void setRepairFaultyPag(boolean repairFaultyPag) { + this.repairFaultyPag = repairFaultyPag; + } } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/DagToPag.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/DagToPag.java index 4cacdb5f71..f5821f470d 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/DagToPag.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/DagToPag.java @@ -28,6 +28,7 @@ import org.apache.commons.lang3.tuple.Pair; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -123,18 +124,31 @@ public Graph convert() { pag.reorientAllWith(Endpoint.CIRCLE); + FciOrient fciOrient = new FciOrient(getFinalStrategyUsingDsep(mag, pag, knowledge, verbose)); + fciOrient.setVerbose(verbose); + + fciOrient.ruleR0(pag, new HashSet<>()); + fciOrient.finalOrientation(pag); + +// finalOrientation(mag, pag, knowledge, verbose); + + return pag; + } + + public static R0R4StrategyTestBased getFinalStrategyUsingDsep(Graph mag, Graph pag, Knowledge knowledge, boolean verbose) { + // Note that we will re-use FCIOrient but override the R0 and discriminating path rules to use D-SEP(A,B) or D-SEP(B,A) // to find the d-separating set between A and B. - R0R4StrategyTestBased strategy = new R0R4StrategyTestBased(new MsepTest(mag)) { + return new R0R4StrategyTestBased(new MsepTest(mag)) { @Override public boolean isUnshieldedCollider(Graph graph, Node i, Node j, Node k) { - Graph mag = ((MsepTest) getTest()).getGraph(); + Graph mag1 = ((MsepTest) getTest()).getGraph(); // Could copy the unshielded colliders from the mag but we will use D-SEP. // return mag.isDefCollider(i, j, k) && !mag.isAdjacentTo(i, k); - Set dsepi = mag.paths().dsep(i, k); - Set dsepk = mag.paths().dsep(k, i); + Set dsepi = mag1.paths().dsep(i, k); + Set dsepk = mag1.paths().dsep(k, i); if (getTest().checkIndependence(i, k, dsepi).isIndependent()) { return !dsepi.contains(j); @@ -168,10 +182,10 @@ public Pair doDiscriminatingPathOrientation(Discrim throw new IllegalArgumentException("e and c must not be adjacent"); } - Graph mag = ((MsepTest) getTest()).getGraph(); + Graph mag1 = ((MsepTest) getTest()).getGraph(); - Set dsepe = GraphUtils.dsep(e, c, mag); - Set dsepc = GraphUtils.dsep(c, e, mag); + Set dsepe = GraphUtils.dsep(e, c, mag1); + Set dsepc = GraphUtils.dsep(c, e, mag1); Set sepset = null; @@ -230,13 +244,6 @@ public void setAllowedColliders(Set allowedColliders) { // Ignore. } }; - - FciOrient fciOrient = new FciOrient(strategy); - fciOrient.setVerbose(verbose); - fciOrient.orient(pag); - fciOrient.setTestTimeout(-1); - - return pag; } /** diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/FciOrient.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/FciOrient.java index 1e4829aff3..23caaa5ba5 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/FciOrient.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/FciOrient.java @@ -191,15 +191,16 @@ public static boolean isArrowheadAllowed(Node x, Node y, Graph graph, Knowledge * Performs FCI orientation on the given graph, including R0 and either the Spirtes or Zhang final orientation * rules. * - * @param graph The graph to orient. + * @param graph The graph to orient. + * @param unshieldedTriples The set of unshielded triples oriented by R0. This set is updated with new triples. */ - public void orient(Graph graph) { + public void orient(Graph graph, Set unshieldedTriples) { if (verbose) { this.logger.log("Starting FCI orientation."); } - ruleR0(graph); + ruleR0(graph, unshieldedTriples); if (this.verbose) { logger.log("R0"); @@ -207,10 +208,6 @@ public void orient(Graph graph) { // Step CI D. (Zhang's step R4.) finalOrientation(graph); - - if (this.verbose) { - this.logger.log("Returning graph: " + graph); - } } /** @@ -248,9 +245,10 @@ public void setCompleteRuleSetUsed(boolean completeRuleSetUsed) { /** * Orients unshielded colliders in the graph. (FCI Step C, Zhang's step F3, rule R0.) * - * @param graph The graph to orient. + * @param graph The graph to orient. + * @param unshieldedTriples */ - public void ruleR0(Graph graph) { + public void ruleR0(Graph graph, Set unshieldedTriples) { graph.reorientAllWith(Endpoint.CIRCLE); fciOrientbk(this.knowledge, graph, graph.getNodes()); @@ -300,6 +298,8 @@ public void ruleR0(Graph graph) { setEndpoint(graph, a, b, Endpoint.ARROW); setEndpoint(graph, c, b, Endpoint.ARROW); + unshieldedTriples.add(new Triple(a, b, c)); + if (this.verbose) { this.logger.log(LogUtilsSearch.colliderOrientedMsg(a, b, c)); } diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/TsDagToPag.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/TsDagToPag.java index 5067eb9f95..a7936f12f5 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/TsDagToPag.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/search/utils/TsDagToPag.java @@ -206,6 +206,8 @@ public Graph convert() { FciOrient fciOrient = new FciOrient( R0R4StrategyTestBased.defaultConfiguration(dag, new Knowledge())); + fciOrient.setCompleteRuleSetUsed(completeRuleSetUsed); + fciOrient.setMaxPathLength(maxPathLength); fciOrient.finalOrientation(graph); if (this.verbose) { diff --git a/tetrad-lib/src/main/java/edu/cmu/tetrad/util/Params.java b/tetrad-lib/src/main/java/edu/cmu/tetrad/util/Params.java index 71f7f4e849..9f26229f99 100644 --- a/tetrad-lib/src/main/java/edu/cmu/tetrad/util/Params.java +++ b/tetrad-lib/src/main/java/edu/cmu/tetrad/util/Params.java @@ -892,9 +892,13 @@ public final class Params { */ public static final String ABLATION_LEAVE_OUT_TUCKING_STEP = "ablationLeaveOutTuckingStep"; /** - * Constant ALLOW_TESTING="ABLATION_LEAVE_OUT_TESTING_STEP = "ablationLeaveOutTestingStep"" + * Constant ALLOW_TESTING="ABLATION_LEAVE_OUT_SCORING_STEP = "ablationLeaveOutScoringStep"" */ - public static final String ABLATION_LEAVE_OUT_TESTING_STEP = "ablationLeaveOutTestingStep"; + public static final String ABLATION_LEAVE_OUT_SCORING_STEP = "ablationLeaveOutScoringStep"; + /** + * Constant ALLOW_TESTING="ABLATION_LEAVE_OUT_TESTING_STEPS = "ablationLeaveOutTestingSteps"" + */ + public static final String ABLATION_LEAVE_OUT_TESTING_STEPS = "ablationLeaveOutTestingSteps"; /** * Constant MAX_SCORE_DROP="maxScoreDrop" */ @@ -904,14 +908,9 @@ public final class Params { */ public static final String REPAIR_FAULTY_PAG = "repairFaultyPag"; /** - * Represents the final orientation setting for ablation leave-out. - * - *

- * The ABLATATION_LEAVE_OUT_FINAL_ORIENTATION variable is a constant string used to specify the final orientation setting - * for ablation leave-out. It is used in the context of a specific application or system. - *

+ * Constant REMOVE_ALMOST_CYCLES="removeAlmostCycles" */ - public static final String ABLATATION_LEAVE_OUT_FINAL_ORIENTATION = "ablationLeaveOutFinalOrientation"; + public static final String REMOVE_ALMOST_CYCLES = "removeAlmostCycles"; /** * Constant MIN_COUNT_PER_CELL="minCountPerCell" */ diff --git a/tetrad-lib/src/main/resources/docs/manual/index.html b/tetrad-lib/src/main/resources/docs/manual/index.html index 57ca989a66..65f934f710 100755 --- a/tetrad-lib/src/main/resources/docs/manual/index.html +++ b/tetrad-lib/src/main/resources/docs/manual/index.html @@ -6447,28 +6447,53 @@

ia

ablationLeaveOutTestingStep

+ id="ablationLeaveOutScoringStep">ablationLeaveOutScoringStep
  • Short Description: - ABLATION: Yes, if the testing step should be left out for the LV-Lite procedure + id="ablationLeaveOutScoringStep_short_desc"> + ABLATION: Yes, if the scoring step should be left out for the LV-Lite procedure
  • Long Description: - Allowing testing can sometimes lead to lower arrowhead accuracies, - even though it is theoretically correct. + id="ablationLeaveOutScoringStep_long_desc"> + If true, this leaves out the scoring steps and being the algorithm with + a complete nondirected graph (which is Markov).
  • Default Value: false
  • + id="ablationLeaveOutScoringStep_default_value">false
  • Lower Bound:
  • + id="ablationLeaveOutScoringStep_lower_bound">
  • Upper Bound:
  • + id="ablationLeaveOutScoringStep_upper_bound">
  • Value Type: Boolean
  • + id="ablationLeaveOutScoringStep_value_type">Boolean +
+ +

ablationLeaveOutTestingSteps

+
    +
  • Short Description: + ABLATION: Yes, if the testing steps should be left out for the LV-Lite procedure +
  • +
  • Long Description: + If true, this leaves out all testing steps from the algorithm and bases + the result on just the scoring steps. +
  • +
  • Default Value: false
  • +
  • Lower Bound:
  • +
  • Upper + Bound:
  • +
  • Value + Type: Boolean

ia

  • Long Description: - Replaces x <-> y, x ~~> y with x -> y; for ~adj(x, y) with an inducing - path between x and y, adds x o-o y; runs final orientation rules. - This often generates a legal PAG where errors exist in PAG estimated - by the algorithm. + Repairs errors in PAGs due to almost cyclic paths or non-maximalities.
  • Default Value: False
  • @@ -6642,6 +6664,31 @@

    ia

    id="repairFaultyPag_value_type">Boolean +

    removeAlmostCycles

    +
      +
    • Short Description: + Yes if almost-cycles should be removed from the PAG. +
    • +
    • Long Description: + When x <-> y, x ~~> y, removes any unshielded triples into x and + rebuilds the PAG. +
    • +
    • Default Value: False
    • +
    • Lower Bound:
    • +
    • Upper + Bound:
    • +
    • Value + Type: Boolean
    • +
    +

    ablationLeaveOutFinalOrientation