All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.cmu.tetrad.search.Fasd Maven / Gradle / Ivy

///////////////////////////////////////////////////////////////////////////////
// For information as to what this class does, see the Javadoc, below.       //
// Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,       //
// 2007, 2008, 2009, 2010, 2014, 2015, 2022 by Peter Spirtes, Richard        //
// Scheines, Joseph Ramsey, and Clark Glymour.                               //
//                                                                           //
// This program is free software; you can redistribute it and/or modify      //
// it under the terms of the GNU General Public License as published by      //
// the Free Software Foundation; either version 2 of the License, or         //
// (at your option) any later version.                                       //
//                                                                           //
// This program is distributed in the hope that it will be useful,           //
// but WITHOUT ANY WARRANTY; without even the implied warranty of            //
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             //
// GNU General Public License for more details.                              //
//                                                                           //
// You should have received a copy of the GNU General Public License         //
// along with this program; if not, write to the Free Software               //
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA //
///////////////////////////////////////////////////////////////////////////////

package edu.cmu.tetrad.search;

import edu.cmu.tetrad.data.Knowledge;
import edu.cmu.tetrad.graph.*;
import edu.cmu.tetrad.search.test.IndependenceResult;
import edu.cmu.tetrad.search.utils.LogUtilsSearch;
import edu.cmu.tetrad.search.utils.SepsetMap;
import edu.cmu.tetrad.util.ChoiceGenerator;
import edu.cmu.tetrad.util.TetradLogger;

import java.io.PrintStream;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.*;

/**
 * 

Adjusts FAS (see) for the deterministic case by refusing to removed edges * based on conditional independence tests that are judged to be deterministic. That is, if X _||_ Y | Z, but Z * determines X or Y, then the edge X---Y is not removed.

* *

This class is configured to respect knowledge of forbidden and required * edges, including knowledge of temporal tiers.

* * @author peterspirtes * @author josephramsey. * @see Fas * @see Knowledge */ public class Fasd implements IFas { /** * The independence test. This should be appropriate to the types */ private final IndependenceTest test; /** * The logger, by default the empty logger. */ private final TetradLogger logger = TetradLogger.getInstance(); private final NumberFormat nf = new DecimalFormat("0.00E0"); /** * The search graph. It is assumed going in that all the true adjacencies of x are in this graph for every node * x. It is hoped (i.e., true in the large sample limit) that true adjacencies are never removed. */ private final Graph graph; /** * Specification of which edges are forbidden or required. */ private Knowledge knowledge = new Knowledge(); /** * The maximum number of variables conditioned on in any conditional independence test. If the depth is -1, it will * be taken to be the maximum value, which is 1000. Otherwise, it should be set to a non-negative integer. */ private int depth = 1000; /** * The number of independence tests. */ private int numIndependenceTests; /** * The sepsets found during the search. */ private SepsetMap sepset = new SepsetMap(); /** * The depth 0 graph, specified initially. */ private Graph externalGraph; /** * True iff verbose output should be printed. */ private boolean verbose; private PrintStream out = System.out; /** * Constructs a new FastAdjacencySearch. * * @param test A test to use as a conditional independence oracle. */ public Fasd(IndependenceTest test) { this.graph = new EdgeListGraph(test.getVariables()); this.test = test; } /** * Discovers all adjacencies in data. The procedure is to remove edges in the graph which connect pairs of * variables which are independent, conditional on some other set of variables in the graph (the "sepset"). These are * removed in tiers. First, edges which are independent conditional on zero other variables are removed, then edges * which are independent conditional on one other variable are removed, then two, then three, and so on, until no * more edges can be removed from the graph. The edges which remain in the graph after this procedure are the * adjacencies in the data. * * @return a graph which indicates which variables are independent conditional on which other variables */ public Graph search() { this.logger.log("info", "Starting Fast Adjacency Search."); this.graph.removeEdges(this.graph.getEdges()); this.sepset = new SepsetMap(); int _depth = this.depth; if (_depth == -1) { _depth = 1000; } Map> adjacencies = new HashMap<>(); List nodes = this.graph.getNodes(); for (Node node : nodes) { adjacencies.put(node, new TreeSet<>()); } for (int d = 0; d <= _depth; d++) { boolean more; if (d == 0) { more = searchAtDepth0(nodes, this.test, adjacencies); } else { more = searchAtDepth(nodes, this.test, adjacencies, d); } if (!more) { break; } } for (int i = 0; i < nodes.size(); i++) { for (int j = i + 1; j < nodes.size(); j++) { Node x = nodes.get(i); Node y = nodes.get(j); if (adjacencies.get(x).contains(y)) { this.graph.addUndirectedEdge(x, y); } } } this.logger.log("info", "Finishing Fast Adjacency Search."); return this.graph; } /** * Sets the maximum number of variables conditioned on in any test. * * @param depth This maximum. */ public void setDepth(int depth) { if (depth < -1) { throw new IllegalArgumentException( "Depth must be -1 (unlimited) or >= 0."); } this.depth = depth; } /** * Sets the knowledge to be used in the search. * * @param knowledge This knowledge. */ public void setKnowledge(Knowledge knowledge) { if (knowledge == null) { throw new NullPointerException("Cannot set knowledge to null"); } this.knowledge = knowledge; } /** * Returns the number of conditional independence tests done in the course of search. * * @return This number. */ public int getNumIndependenceTests() { return this.numIndependenceTests; } /** * Returns the map of node pairs to sepsets from the search. * * @return This map. */ public SepsetMap getSepsets() { return this.sepset; } /** * Sets the external graph. Adjacencies not in this external graph will not be judged adjacent in the search * result. * * @param externalGraph This graph. */ public void setExternalGraph(Graph externalGraph) { this.externalGraph = externalGraph; } public boolean isVerbose() { return this.verbose; } /** * Sets whether verbose output will be printed. * * @param verbose True, if so. */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Returns the elapsed time. * * @return This time. */ @Override public long getElapsedTime() { return 0; } /** * Returns the nodes being searched over. * * @return This list. */ @Override public List getNodes() { return this.test.getVariables(); } /** * Returns an empty list. Not used. * * @param node Whichever node. * @return An empty list. */ @Override public List getAmbiguousTriples(Node node) { return new ArrayList<>(); } /** * Sets the output to send prints to. * * @param out This print stream. */ @Override public void setOut(PrintStream out) { this.out = out; } private boolean searchAtDepth0(List nodes, IndependenceTest test, Map> adjacencies) { Set empty = Collections.emptySet(); for (int i = 0; i < nodes.size(); i++) { if (this.verbose) { if ((i + 1) % 100 == 0) this.out.println("Node # " + (i + 1)); } Node x = nodes.get(i); for (int j = i + 1; j < nodes.size(); j++) { Node y = nodes.get(j); if (this.externalGraph != null) { Node x2 = this.externalGraph.getNode(x.getName()); Node y2 = this.externalGraph.getNode(y.getName()); if (!this.externalGraph.isAdjacentTo(x2, y2)) { continue; } } IndependenceResult result; try { this.numIndependenceTests++; result = test.checkIndependence(x, y, empty); } catch (Exception e) { e.printStackTrace(); result = new IndependenceResult(new IndependenceFact(x, y, empty), false, Double.NaN, Double.NaN); } boolean noEdgeRequired = this.knowledge.noEdgeRequired(x.getName(), y.getName()); if (result.isIndependent() && noEdgeRequired) { getSepsets().set(x, y, empty); TetradLogger.getInstance().log("independencies", LogUtilsSearch.independenceFact(x, y, empty) + " p = " + this.nf.format(result.getPValue())); if (this.verbose) { this.out.println(LogUtilsSearch.independenceFact(x, y, empty) + " p = " + this.nf.format(result.getPValue())); } } else if (!forbiddenEdge(x, y)) { adjacencies.get(x).add(y); adjacencies.get(y).add(x); TetradLogger.getInstance().log("dependencies", LogUtilsSearch.independenceFact(x, y, empty) + " p = " + this.nf.format(result.getPValue())); } } } return freeDegree(nodes, adjacencies) > 0; } private int freeDegree(List nodes, Map> adjacencies) { int max = 0; for (Node x : nodes) { Set opposites = adjacencies.get(x); for (Node y : opposites) { Set adjx = new HashSet<>(opposites); adjx.remove(y); if (adjx.size() > max) { max = adjx.size(); } } } return max; } private boolean forbiddenEdge(Node x, Node y) { String name1 = x.getName(); String name2 = y.getName(); if (this.knowledge.isForbidden(name1, name2) && this.knowledge.isForbidden(name2, name1)) { this.logger.log("edgeRemoved", "Removed " + Edges.undirectedEdge(x, y) + " because it was " + "forbidden by background knowledge."); return true; } return false; } private boolean searchAtDepth(List nodes, IndependenceTest test, Map> adjacencies, int depth) { int count = 0; List facts = new ArrayList<>(); for (Node x : nodes) { if (this.verbose) { if (++count % 100 == 0) this.out.println("count " + count + " of " + nodes.size()); } List adjx = new ArrayList<>(adjacencies.get(x)); EDGE: for (Node y : adjx) { List _adjx = new ArrayList<>(adjacencies.get(x)); _adjx.remove(y); List ppx = possibleParents(x, _adjx, this.knowledge); if (ppx.size() >= depth) { ChoiceGenerator cg = new ChoiceGenerator(ppx.size(), depth); int[] choice; while ((choice = cg.next()) != null) { Set condSet = GraphUtils.asSet(choice, ppx); IndependenceFact fact = new IndependenceFact(x, y, condSet); if (facts.contains(fact)) continue; facts.add(fact); boolean independent; try { this.numIndependenceTests++; independent = test.checkIndependence(x, y, condSet).isIndependent(); } catch (Exception e) { independent = false; } boolean noEdgeRequired = this.knowledge.noEdgeRequired(x.getName(), y.getName()); if (independent && noEdgeRequired) { adjacencies.get(x).remove(y); adjacencies.get(y).remove(x); getSepsets().set(x, y, condSet); continue EDGE; } } } } } return freeDegree(nodes, adjacencies) > depth; } private List possibleParents(Node x, List adjx, Knowledge knowledge) { List possibleParents = new LinkedList<>(); String _x = x.getName(); for (Node z : adjx) { String _z = z.getName(); if (possibleParentOf(_z, _x, knowledge)) { possibleParents.add(z); } } return possibleParents; } private boolean possibleParentOf(String z, String x, Knowledge knowledge) { return !knowledge.isForbidden(z, x) && !knowledge.isRequired(x, z); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy