///////////////////////////////////////////////////////////////////////////////
// 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 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.IKnowledge;
import edu.cmu.tetrad.data.Knowledge;
import edu.cmu.tetrad.graph.*;
import edu.cmu.tetrad.search.indtest.IndependenceTest;
import edu.cmu.tetrad.util.ChoiceGenerator;
import edu.cmu.tetrad.util.TetradLogger;

import java.io.PrintStream;
import java.util.*;

import static java.lang.Math.sqrt;

/**
* Machines an IMaGES pattern.
*
* @author Jose Vargas-Murphy.
*/
public final class Images2 extends Images {
private IndependenceTest independenceTest;
private PrintStream out = System.out; // TODO use TetradLogger.getInstance().getOut();
private double penaltyDiscount = getPenaltyDiscount();

/**
* The SepsetMap being used, same as IMaGES.
*/
private SepsetMap sepsetMap;

/**
* Constructs a new IMaGES2.
*
* @param independenceTest – The independence test the the algorithm uses, c.f. IMAGES.
*/
public Images2(IndependenceTest independenceTest) {
if (independenceTest == null) throw new IllegalArgumentException();
this.independenceTest = independenceTest;
this.sepsetMap = new SepsetMap();
}

//========================PUBLIC METHODS===========================//

/**
* Gets knowledge took into account during search.
*/
public IKnowledge getKnowledge() {
return knowledge;
}

/**
* Sets knowledge took into account during search.
* @param knowledge
*/
public void setKnowledge(IKnowledge knowledge) {
if (knowledge == null) {
throw new NullPointerException(“Cannot set a null knowledge.”);
}
this.knowledge = knowledge;
}

public void search() {
images2(getIndependenceTest());
}

//========================PRIVATE METHODS===========================//

/**
* The SearchAlgorithm for IMAGES.
*
* @param indTest – The IndependenceTest to be used to decide whether or not
* to add edges to the graph.
*/
private void images2(IndependenceTest indTest) {
out.println(“nIMAGES2: Penalty Discount = ” + penaltyDiscount);

List variables = indTest.getVariables();
double penalty = 0;
Graph graph;

DagIterator iterator = new DagIterator(variables);
while (iterator.hasNext()) {
graph = iterator.next();

SearchGraphUtils.pcOrientbk(knowledge, graph, variables);
SearchGraphUtils.orientCollidersUsingSepsets(sepsetMap, knowledge, graph, getPenaltyDiscount());
List unoriented = graph.getUnorientedEdges();

double dBreak = -penaltyDiscount;
// Add in edges(I),delta > 0
while (true) {
if (unoriented.isEmpty()) {
break;
}
Edge maxEdge = getMaxEdge(unoriented, indTest);
if (maxEdge == null) {
break;
}

double delta = maxEdgeDelta(maxEdge, indTest);

if (delta > dBreak) {
graph.fullyConnect(maxEdge.getNode1(), maxEdge.getNode2(),
Endpoint.TAIL, Endpoint.TAIL);
penalty += delta;
fireGraphChanged(graph);
}
unoriented.remove(maxEdge);
}
penalty += calculateModelPenalty(graph);
if (penalty < getBestScore()) {
setBestScore(penalty);
setGraph(graph);
}
}
}

/**
* Calculates the penalty term for the model, using the number of
* parameters and the sampleSize.
*
* @param model
* @return
*/
private double calculateModelPenalty(Graph model) {
return Math.pow(sqrt(model.getNumEdges())
+ model.getNumErrorTerms() + 1, penaltyDiscount);
}

/**
* Returns the Edge for which the delta value calculated by the IndTest is
* maximum.
*
* @param edges The List of edges to test.
* @param indTest The IndTest used to compute delta.
* @return The Edge with the maximum delta or null if the list is empty.
*/
private Edge getMaxEdge(List edges, IndependenceTest indTest) {
if (edges.isEmpty()) {
return null;
}
double max = 0.0d;
Edge maxEdge = null;

for (Edge edge : edges) {
double delta = maxEdgeDelta(edge, indTest);
if (delta > max) {
max = delta;
maxEdge = edge;
}
}
return maxEdge;
}

/**
* Returns the delta value for edge i-j. This is computed using the test and
* the sepsetMap.
*
* @param edge The Edge of which to calculate delta.
* @param indTest The IndependenceTest used to compute delta.
* @return delta or Double.NEGATIVE_INFINITY if they were independent.
*/
private double maxEdgeDelta(Edge edge, IndependenceTest indTest) {
int i = edge.getNode1().getIndex(); int j = edge.getNode2().getIndex();
List conditioning Variables;
double pValue = indTest.getPValue();

// Test i _||_ j | S for each S in Sepset(i,j).
List sepset = sepsetMap.get(edge.getNode1(), edge.getNode2());

if (sepset == null)
conditioningVariables = new ArrayList();
else
conditioningVariables = new ArrayList(sepset);

if (independenceTest.isIndependent(edge.getNode1(),
edge.getNode2(), conditioningVariables)) {
return Double.NEGATIVE_INFINITY;
}
else {
return -1 * Math.log(pValue);
}
}

public IndependenceTest getIndependenceTest() {
return independenceTest;
}

public void setIndependenceTest(IndependenceTest independenceTest) {
this.independenceTest = independenceTest;
}
}