edu.cmu.tetrad.graph.GraphTransforms Maven / Gradle / Ivy
The newest version!
package edu.cmu.tetrad.graph;
import edu.cmu.tetrad.data.Knowledge;
import edu.cmu.tetrad.search.utils.*;
import edu.cmu.tetrad.util.CombinationGenerator;
import edu.cmu.tetrad.util.RandomUtil;
import org.jetbrains.annotations.NotNull;
import java.util.*;
/**
* Transformations that transform one graph into another.
*
* @author josephramsey
* @version $Id: $Id
*/
public class GraphTransforms {
/**
* Private constructor to prevent instantiation.
*/
private GraphTransforms() {
}
/**
* Converts a completed partially directed acyclic graph (CPDAG) into a directed acyclic graph (DAG). If the given
* CPDAG is not a PDAG (Partially Directed Acyclic Graph), returns null.
*
* @param graph the CPDAG to be converted into a DAG
* @return a directed acyclic graph (DAG) obtained from the given CPDAG. If the given CPDAG is not a PDAG, returns
* null.
*/
public static Graph dagFromCpdag(Graph graph) {
return dagFromCpdag(graph, null, true, true);
}
/**
* Converts a completed partially directed acyclic graph (CPDAG) into a directed acyclic graph (DAG). If the given
* CPDAG is not a PDAG (Partially Directed Acyclic Graph), returns null.
*
* @param graph the CPDAG to be converted into a DAG
* @param verbose whether to print verbose output
* @return a directed acyclic graph (DAG) obtained from the given CPDAG. If the given CPDAG is not a PDAG, returns
* null.
*/
public static Graph dagFromCpdag(Graph graph, boolean verbose) {
return dagFromCpdag(graph, null, true, verbose);
}
/**
* Converts a completed partially directed acyclic graph (CPDAG) into a directed acyclic graph (DAG). If the given
* CPDAG is not a PDAG (Partially Directed Acyclic Graph), returns null.
*
* @param graph the CPDAG to be converted into a DAG
* @param meekPreventCycles whether to prevent cycles using the Meek rules by orienting additional arbitrary
* unshielded colliders in the graph
* @param verbose whether to print verbose output
* @return a directed acyclic graph (DAG) obtained from the given CPDAG. If the given CPDAG is not a PDAG, returns
* null.
*/
public static Graph dagFromCpdag(Graph graph, boolean meekPreventCycles, boolean verbose) {
return dagFromCpdag(graph, null, meekPreventCycles, verbose);
}
/**
* dagFromCpdag.
*
* @param graph a {@link edu.cmu.tetrad.graph.Graph}
* @param knowledge a {@link edu.cmu.tetrad.data.Knowledge}
* @return a {@link edu.cmu.tetrad.graph.Graph} object
*/
public static Graph dagFromCpdag(Graph graph, Knowledge knowledge) {
return dagFromCpdag(graph, knowledge, true, true);
}
/**
* Returns a random DAG from the given CPDAG. If the given CPDAG is not a PDAG, returns null.
*
* @param cpdag the CPDAG
* @param knowledge the knowledge
* @param meekPreventCycles whether to prevent cycles using the Meek rules by orienting additional arbitrary
* unshielded colliders in the graph.
* @param verbose whether to print verbose output.
* @return a DAG from the given CPDAG. If the given CPDAG is not a PDAG, returns null.
*/
public static Graph dagFromCpdag(Graph cpdag, Knowledge knowledge, boolean meekPreventCycles, boolean verbose) {
Graph dag = new EdgeListGraph(cpdag);
transformCpdagIntoRandomDag(dag, knowledge, meekPreventCycles, verbose);
return dag;
}
/**
* Transforms a completed partially directed acyclic graph (CPDAG) into a random directed acyclic graph (DAG) by
* randomly orienting the undirected edges in the CPDAG in shuffled order.
*
* @param graph The original graph from which the CPDAG was derived.
* @param knowledge The knowledge available to check if a potential DAG violates any constraints.
* @param meekPreventCycles Whether to prevent cycles using the Meek rules by orienting additional arbitrary
* unshielded colliders in the graph.
* @param verbose Whether to print verbose output.
*/
public static void transformCpdagIntoRandomDag(Graph graph, Knowledge knowledge, boolean meekPreventCycles,
boolean verbose) {
List undirectedEdges = new ArrayList<>();
for (Edge edge : graph.getEdges()) {
if (Edges.isUndirectedEdge(edge)) {
undirectedEdges.add(edge);
}
}
Collections.shuffle(undirectedEdges);
MeekRules rules = new MeekRules();
rules.setMeekPreventCycles(meekPreventCycles);
rules.setVerbose(verbose);
if (knowledge != null) {
rules.setKnowledge(knowledge);
}
rules.setRevertToUnshieldedColliders(false);
NEXT:
while (true) {
for (Edge edge : undirectedEdges) {
Node x = edge.getNode1();
Node y = edge.getNode2();
if (!Edges.isUndirectedEdge(graph.getEdge(x, y))) {
continue;
}
if (Edges.isUndirectedEdge(edge) && !graph.paths().isAncestorOf(y, x)) {
double d = RandomUtil.getInstance().nextDouble();
if (d < 0.5) {
direct(x, y, graph);
} else {
direct(y, x, graph);
}
rules.orientImplied(graph);
continue NEXT;
}
}
break;
}
}
/**
* Picks a random Maximal Ancestral Graph (MAG) from the given Partial Ancestral Graph (PAG) by randomly orienting
* the circle endpoints as either tail or arrow and then applying the final FCI orient algorithm after each change.
* The PAG graph type is not checked.
*
* @param pag The partially ancestral pag to transform.
* @return The maximally ancestral pag obtained from the PAG.
*/
public static Graph magFromPag(Graph pag) {
Graph mag = new EdgeListGraph(pag);
transormPagIntoRandomMag(mag);
return mag;
}
/**
* Transforms a partially ancestral graph (PAG) into a maximally ancestral graph (MAG) by randomly orienting the
* circle endpoints as either tail or arrow and then applying the final FCI orient algorithm after each change.
*
* @param pag The partially ancestral graph to transform.
*/
public static void transormPagIntoRandomMag(Graph pag) {
for (Edge e : pag.getEdges()) pag.addEdge(new Edge(e));
List nodePairs = new ArrayList<>();
for (Edge edge : pag.getEdges()) {
if (!pag.isAdjacentTo(edge.getNode1(), edge.getNode2())) continue;
nodePairs.add(new NodePair(edge.getNode1(), edge.getNode2()));
nodePairs.add(new NodePair(edge.getNode2(), edge.getNode1()));
}
Collections.shuffle(nodePairs);
for (NodePair edge : new ArrayList<>(nodePairs)) {
if (pag.getEndpoint(edge.getFirst(), edge.getSecond()).equals(Endpoint.CIRCLE)) {
double d = RandomUtil.getInstance().nextDouble();
if (d < 0.5) {
pag.setEndpoint(edge.getFirst(), edge.getSecond(), Endpoint.TAIL);
} else {
pag.setEndpoint(edge.getFirst(), edge.getSecond(), Endpoint.ARROW);
}
FciOrient fciOrient = new FciOrient(
R0R4StrategyTestBased.defaultConfiguration(pag, new Knowledge()));
fciOrient.finalOrientation(pag);
}
}
}
/**
* Transforms a partially ancestral graph (PAG) into a maximally ancestral graph (MAG) using Zhang's 2008 Theorem
* 2.
*
* @param pag The partially ancestral graph to transform.
* @return The maximally ancestral graph obtained from the PAG.
*/
public static Graph zhangMagFromPag(Graph pag) {
List nodes = pag.getNodes();
Graph pcafci = new EdgeListGraph(pag);
// pcafcic is the graph with only the circle-circle edges
Graph pcafcic = new EdgeListGraph(pag.getNodes());
for (Edge e : pcafci.getEdges()) {
if (Edges.isNondirectedEdge(e)) {
pcafcic.addUndirectedEdge(e.getNode1(), e.getNode2());
}
}
pcafcic = GraphTransforms.dagFromCpdag(pcafcic, new Knowledge(), false, false);
for (Edge e : pcafcic.getEdges()) {
pcafci.removeEdge(e.getNode1(), e.getNode2());
pcafci.addEdge(e);
}
Graph H = new EdgeListGraph(pcafci);
for (Node x : nodes) {
for (Node y : nodes) {
if (x.equals(y)) {
continue;
}
if (!H.isAdjacentTo(x, y)) {
continue;
}
if (H.getEndpoint(y, x) == Endpoint.CIRCLE && H.getEndpoint(x, y) == Endpoint.ARROW) {
H.setEndpoint(y, x, Endpoint.TAIL);
}
if (H.getEndpoint(y, x) == Endpoint.TAIL && H.getEndpoint(x, y) == Endpoint.CIRCLE) {
H.setEndpoint(x, y, Endpoint.ARROW);
}
}
}
return H;
}
/**
* Generates the list of DAGs in the given cpdag.
*
* @param cpdag a {@link edu.cmu.tetrad.graph.Graph} object
* @param orientBidirectedEdges a boolean
* @return a {@link java.util.List} object
*/
public static List generateCpdagDags(Graph cpdag, boolean orientBidirectedEdges) {
if (orientBidirectedEdges) {
cpdag = GraphUtils.removeBidirectedOrientations(cpdag);
}
return getDagsInCpdagMeek(cpdag, new Knowledge());
}
/**
* Retrieves a list of directed acyclic graphs (DAGs) within the given completed partially directed acyclic graph
* (CPDAG) using the Meek rules.
*
* @param cpdag The completed partially directed acyclic graph (CPDAG) from which to retrieve the DAGs.
* @param knowledge The knowledge available to check if a potential DAG violates any constraints.
* @return A {@link List} of {@link Graph} objects representing the DAGs within the CPDAG.
*/
public static List getDagsInCpdagMeek(Graph cpdag, Knowledge knowledge) {
DagInCpcagIterator iterator = new DagInCpcagIterator(cpdag, knowledge);
List dags = new ArrayList<>();
while (iterator.hasNext()) {
Graph graph = iterator.next();
try {
if (knowledge.isViolatedBy(graph)) {
continue;
}
dags.add(graph);
} catch (IllegalArgumentException e) {
System.out.println("Found a non-DAG: " + graph);
}
}
return dags;
}
/**
* Returns a list of all possible graphs obtained by directing undirected edges in the given graph.
*
* @param skeleton the graph to transform
* @return a list of all possible graphs obtained by directing undirected edges
*/
public static List getAllGraphsByDirectingUndirectedEdges(Graph skeleton) {
List graphs = new ArrayList<>();
List edges = new ArrayList<>(skeleton.getEdges());
List undirectedIndices = new ArrayList<>();
for (int i = 0; i < edges.size(); i++) {
if (Edges.isUndirectedEdge(edges.get(i))) {
undirectedIndices.add(i);
}
}
int[] dims = new int[undirectedIndices.size()];
for (int i = 0; i < undirectedIndices.size(); i++) {
dims[i] = 2;
}
CombinationGenerator gen = new CombinationGenerator(dims);
int[] comb;
while ((comb = gen.next()) != null) {
Graph graph = new EdgeListGraph(skeleton.getNodes());
for (Edge edge : edges) {
if (!Edges.isUndirectedEdge(edge)) {
graph.addEdge(edge);
}
}
for (int i = 0; i < undirectedIndices.size(); i++) {
Edge edge = edges.get(undirectedIndices.get(i));
Node node1 = edge.getNode1();
Node node2 = edge.getNode2();
if (comb[i] == 1) {
graph.addEdge(Edges.directedEdge(node1, node2));
} else {
graph.addEdge(Edges.directedEdge(node2, node1));
}
}
graphs.add(graph);
}
return graphs;
}
/**
* Returns the completed partially directed acyclic graph (CPDAG) for a given directed acyclic graph (DAG).
*
* @param dag The input DAG.
* @return The CPDAG resulting from applying Meek Rules to the input DAG.
*/
public static Graph dagToCpdag(Graph dag) {
Graph cpdag = new EdgeListGraph(dag);
MeekRules rules = new MeekRules();
rules.setRevertToUnshieldedColliders(true);
rules.orientImplied(cpdag);
return cpdag;
}
/**
* Converts a Directed Acyclic Graph (DAG) to a Partial Ancestral Graph (PAG) using the DagToPag algorithm.
*
* @param trueGraph The input DAG to be converted.
* @return The resulting PAG obtained from the input DAG.
*/
@NotNull
public static Graph dagToPag(Graph trueGraph) {
return new DagToPag(trueGraph).convert();
}
/**
* Directs an edge between two nodes in a graph.
*
* @param a the start node of the edge
* @param c the end node of the edge
* @param graph the graph in which the edge needs to be directed
*/
private static void direct(Node a, Node c, Graph graph) {
Edge before = graph.getEdge(a, c);
Edge after = Edges.directedEdge(a, c);
graph.removeEdge(before);
graph.addEdge(after);
}
/**
* Converts a Directed Acyclic Graph (DAG) to a Maximal Ancestral Graph (MAG) by adding arrows to the edges.
*
* @param dag The input DAG to be converted.
* @return The resulting MAG obtained from the input DAG.
*/
public static @NotNull Graph dagToMag(Graph dag) {
Map> ancestorMap = dag.paths().getAncestorMap();
Graph graph = DagToPag.calcAdjacencyGraph(dag);
graph.reorientAllWith(Endpoint.TAIL);
for (Edge edge : graph.getEdges()) {
Node x = edge.getNode1();
Node y = edge.getNode2();
// If not x ~~> y put an arrow at y. If not y ~~> x put an arrow at x.
if (!ancestorMap.get(y).contains(x)) {
graph.setEndpoint(x, y, Endpoint.ARROW);
}
if (!ancestorMap.get(x).contains(y)) {
graph.setEndpoint(y, x, Endpoint.ARROW);
}
}
return graph;
}
}