model.DecomposableModel.java Source code

Java tutorial

Introduction

Here is the source code for model.DecomposableModel.java

Source

/*******************************************************************************
 * Copyright (C) 2015 Francois Petitjean
 * 
 * This file is part of Chordalysis.
 * 
 * Chordalysis 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, version 3 of the License.
 * 
 * Chordalysis 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 Chordalysis.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package model;

import graph.ChordalGraph;
import graph.CliqueGraphEdge;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.swing.JFrame;

import model.GraphAction.ActionType;

import org.apache.commons.math3.util.FastMath;
import org.jgrapht.experimental.dag.DirectedAcyclicGraph;
import org.jgrapht.experimental.dag.DirectedAcyclicGraph.CycleFoundException;
import org.jgrapht.graph.DefaultEdge;

import com.mxgraph.layout.mxOrganicLayout;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.swing.util.mxMorphing;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxEvent;
import com.mxgraph.util.mxEventObject;
import com.mxgraph.util.mxEventSource.mxIEventListener;
import com.mxgraph.view.mxGraph;

import stats.EntropyComputer;
import stats.MessageLengthFactorialComputer;
import stats.MyPriorityQueue;

/**
 * This class makes it possible to represent a decomposable log-linear model. A
 * decomposable log-linear model is linked to a chordal graph representation.
 */
public class DecomposableModel {
    /**
     * The graph representing the decomposable log-linear model
     */
    public ChordalGraph graph;
    TreeMap<GraphAction, List<GraphAction>> actionsForInteraction;
    Double entropy;
    Double encodingLength;
    boolean entropyComputed = false;
    long nbParameters = -1;
    int[] dimensionsForVariables;

    /**
     * Creates a new decomposable log-linear model, considering no interactions
     * (but independence).
     * 
     * @param variables
     *            list of the variables named after their number
     * @param dimensionsForVariables
     *            the number of possible values for every variable
     */
    public DecomposableModel(int[] variables, int[] dimensionsForVariables) {
        graph = new ChordalGraph();
        for (int var : variables) {
            graph.addVertex(var);
        }
        graph.initStructures();
        this.actionsForInteraction = new TreeMap<GraphAction, List<GraphAction>>();
        for (int i = 0; i < variables.length; i++) {
            for (int j = i + 1; j < variables.length; j++) {
                Couple<Integer> edge = new Couple<Integer>(i, j);
                ArrayList<GraphAction> actionsList = new ArrayList<GraphAction>();
                GraphAction action = new GraphAction(ActionType.ADD, edge);
                actionsList.add(action);
                this.actionsForInteraction.put(action, actionsList);
            }
        }

        this.dimensionsForVariables = dimensionsForVariables;
    }

    /**
     * Creates a new decomposable log-linear model by copy from another model.
     * 
     * @param model
     *            the model to copy
     */
    public DecomposableModel(DecomposableModel model) {
        this.graph = (ChordalGraph) model.graph.clone();
        this.actionsForInteraction = null;
        this.entropy = model.entropy;
        this.entropyComputed = model.entropyComputed;
        this.dimensionsForVariables = model.dimensionsForVariables;

    }

    /**
     * Performs a modification of the graph (add an edge to the graph).
     * 
     * @param actionToPerform
     *            the action to perform
     * @param the
     *            model from which the current one has been created
     */
    public void performAction(GraphAction actionToPerform, DecomposableModel fromModel) {
        List<GraphAction> actionsToPerform = fromModel.actionsForInteraction.get(actionToPerform);
        for (GraphAction action : actionsToPerform) {
            switch (action.type) {
            case ADD:
                graph.addSecuredEdge(action.getV1(), action.getV2());
                break;
            case REMOVE:
                graph.removeSecuredEdge(action.getV1(), action.getV2());
                break;
            default:
                break;
            }
        }
        entropyComputed = false;
        entropy = Double.NaN; // invalidate the entropy
        nbParameters = -1; // invalidate the nb degrees of freedom
    }

    public void performAction(GraphAction actionToPerform, DecomposableModel fromModel, MyPriorityQueue pq) {
        List<GraphAction> actionsToPerform = fromModel.actionsForInteraction.get(actionToPerform);
        for (GraphAction action : actionsToPerform) {
            switch (action.type) {
            case ADD:
                graph.addSecuredEdge(action.getV1(), action.getV2(), pq);
                break;
            case REMOVE:
                // graph.removeSecuredEdge(action.getV1(),
                // action.getV2(),pq,scorer);
                break;
            default:
                break;
            }
        }
        entropyComputed = false;
        entropy = Double.NaN; // invalidate the entropy
        nbParameters = -1; // invalidate the nb degrees of freedom

    }

    /**
     * Computes the available modifications of the graph to keep it triangulated
     */
    public void computeAvailableModifications() {
        computeAvailableModifications(false);
    }

    /**
     * Computes the available modifications of the graph to keep it triangulated
     * 
     * @param backward
     *            set to true if want to include the backward modifications
     */
    public void computeAvailableModifications(boolean backward) {
        actionsForInteraction = new TreeMap<GraphAction, List<GraphAction>>();

        for (int v1 = 0; v1 < dimensionsForVariables.length; v1++) {
            for (int v2 = v1 + 1; v2 < dimensionsForVariables.length; v2++) {
                // can't add an already present edge
                ArrayList<GraphAction> actionsList = new ArrayList<GraphAction>();
                GraphAction currentAction = null;
                if (graph.isEdgeAddable(v1, v2)) {
                    currentAction = new GraphAction(ActionType.ADD, v1, v2);
                    actionsList.add(currentAction);
                }
                if (!actionsList.isEmpty()) {
                    actionsForInteraction.put(currentAction, actionsList);
                }

                // if (graph.isEdgeAddable(v1, v2)) { // if not already in
                // currentAction = new GraphAction(ActionType.ADD, v1, v2);
                // actionsList.add(currentAction);
                // actionsForInteraction.put(currentAction, actionsList);
                // }
            }
        }

    }

    /**
     * @return the list of modifications of the graph that can be performed
     */
    public TreeSet<GraphAction> getAvailableInteractions() {
        return new TreeSet<GraphAction>(actionsForInteraction.keySet());
    }

    public Double entropyDiffIfAdding(Integer a, Integer b, EntropyComputer computer) {
        return entropyDiffIfAdding(a, b, computer, false);
    }

    /**
     * Compute the difference in the entropy from this model, to one that would
     * add vertex1 and vertex2 to it
     * 
     * @param a
     * @param b
     * @param computer
     * @return
     */
    public Double entropyDiffIfAdding(Integer a, Integer b, EntropyComputer computer, boolean verbose) {
        // System.out.println("computing actual entropy");
        BitSet Sab = graph.getSeparator(a, b);
        BitSet Sabua = (BitSet) Sab.clone();
        BitSet Sabub = (BitSet) Sab.clone();
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabua.set(a);
        Sabub.set(b);
        Sabuaub.set(a);
        Sabuaub.set(b);

        Double entropy = 0.0;
        Double tmp;

        // Sab
        tmp = computer.computeEntropy(Sab);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy -= tmp;
        }
        if (verbose)
            System.out.println("-" + Sab + ":" + tmp);

        // Sab + a
        tmp = computer.computeEntropy(Sabua);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy += tmp;
        }
        if (verbose)
            System.out.println("+" + Sabua + ":" + tmp);

        // Sab + b
        tmp = computer.computeEntropy(Sabub);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy += tmp;
        }
        if (verbose)
            System.out.println("+" + Sabub + ":" + tmp);

        // Sab + a + b
        tmp = computer.computeEntropy(Sabuaub);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy -= tmp;
        }
        if (verbose)
            System.out.println("-" + Sabuaub + ":" + tmp);

        return entropy;
    }

    /**
     * Compute the difference in the entropy from this model, to one that would
     * add vertex1 and vertex2 to it
     * 
     * @param a
     * @param b
     * @param computer
     * @return
     */
    public int treeWidthIfAdding(Integer a, Integer b) {
        // System.out.println("computing actual entropy");
        BitSet Sab = graph.getSeparator(a, b);
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabuaub.set(a);
        Sabuaub.set(b);
        return Sabuaub.cardinality();

    }

    public long nbParametersDiffIfAdding(Integer a, Integer b) {
        // System.out.println("computing actual entropy");
        BitSet Sab = graph.getSeparator(a, b);
        BitSet Sabua = (BitSet) Sab.clone();
        BitSet Sabub = (BitSet) Sab.clone();
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabua.set(a);
        Sabub.set(b);
        Sabuaub.set(a);
        Sabuaub.set(b);

        long diffNbParameters = 0;

        // Sab
        int tmpNBDF = 1;
        for (int var = Sab.nextSetBit(0); var >= 0; var = Sab.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters += tmpNBDF;

        // Sab + a
        tmpNBDF = 1;
        for (int var = Sabua.nextSetBit(0); var >= 0; var = Sabua.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters -= tmpNBDF;

        // Sab + a + b
        tmpNBDF = 1;
        for (int var = Sabuaub.nextSetBit(0); var >= 0; var = Sabuaub.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters += tmpNBDF;

        // Sab + b
        tmpNBDF = 1;
        for (int var = Sabub.nextSetBit(0); var >= 0; var = Sabub.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters -= tmpNBDF;

        return diffNbParameters;
    }

    public long nbParametersDiffIfAdding(Integer a, Integer b, BitSet Sab) {
        // System.out.println("computing actual entropy");
        BitSet Sabua = (BitSet) Sab.clone();
        BitSet Sabub = (BitSet) Sab.clone();
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabua.set(a);
        Sabub.set(b);
        Sabuaub.set(a);
        Sabuaub.set(b);

        long diffNbParameters = 0;

        // Sab
        int tmpNBDF = 1;
        for (int var = Sab.nextSetBit(0); var >= 0; var = Sab.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters += tmpNBDF;

        // Sab + a
        tmpNBDF = 1;
        for (int var = Sabua.nextSetBit(0); var >= 0; var = Sabua.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters -= tmpNBDF;

        // Sab + a + b
        tmpNBDF = 1;
        for (int var = Sabuaub.nextSetBit(0); var >= 0; var = Sabuaub.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters += tmpNBDF;

        // Sab + b
        tmpNBDF = 1;
        for (int var = Sabub.nextSetBit(0); var >= 0; var = Sabub.nextSetBit(var + 1)) {
            tmpNBDF *= dimensionsForVariables[var];
        }
        tmpNBDF = tmpNBDF - 1;
        diffNbParameters -= tmpNBDF;

        return diffNbParameters;
    }

    /**
     * @return the number of parameters of the models
     */
    public long getNbParameters() {
        if (nbParameters == -1) {
            List<BitSet> cliques = graph.getCliquesBFS();
            List<BitSet> separators = graph.getSeparatorsBFS();
            nbParameters = 0;

            for (BitSet clique : cliques) {
                int tmpNBDF = 1;
                for (int var = clique.nextSetBit(0); var >= 0; var = clique.nextSetBit(var + 1)) {
                    tmpNBDF *= dimensionsForVariables[var];
                }
                tmpNBDF = tmpNBDF - 1;
                nbParameters += tmpNBDF;
            }

            for (BitSet separator : separators) {
                int tmpNBDF = 1;
                for (int var = separator.nextSetBit(0); var >= 0; var = separator.nextSetBit(var + 1)) {
                    tmpNBDF *= dimensionsForVariables[var];
                }
                tmpNBDF = tmpNBDF - 1;
                nbParameters -= tmpNBDF;
            }

        }

        return nbParameters;

    }

    @Override
    public String toString() {
        List<BitSet> cliques = graph.getCliquesBFS();
        String res = "";
        for (BitSet clique : cliques) {
            res += "[";
            for (int var = clique.nextSetBit(0); var >= 0; var = clique.nextSetBit(var + 1)) {
                res += var + " ";
            }
            res += "]";
        }
        return res;
    }

    /**
     * @param variableNames
     *            names of the variables
     * @return a string representation of the model
     */
    public String toString(String[] variableNames) {
        List<BitSet> cliques = graph.getCliques();
        String res = "";
        for (BitSet clique : cliques) {
            res += "[";
            for (int var = clique.nextSetBit(0); var >= 0; var = clique.nextSetBit(var + 1)) {
                res += variableNames[var] + " ";
            }
            res += "]";
        }
        return res;
    }

    /**
     * Export a dot file representing the graph
     * 
     * @see {@linkplain http://www.graphviz.org/content/dot-language}
     * @param file
     *            the file to save the representation to
     * @param variableNames
     *            the names of the variables
     */
    public void exportDOT(File file, String[] variableNames) {
        // String[] simplVar = Arrays.copyOf(variableNames,
        // variableNames.length);
        // for(int i=0;i<simplVar.length;i++){
        // simplVar[i] = simplVar[i].replaceAll("[%()]","");
        // }
        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);
            out.println("graph G{");
            for (DefaultEdge edge : graph.edgeSet()) {
                if (variableNames == null) {
                    out.println(graph.getEdgeSource(edge) + "--" + graph.getEdgeTarget(edge));
                } else {
                    out.println("\"" + variableNames[graph.getEdgeSource(edge)] + "\"--\""
                            + variableNames[graph.getEdgeTarget(edge)] + "\"");
                }
            }
            out.println("}");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

    }

    /**
     * Export a JSON file representing the graph
     * 
     * @param file
     *            the file to save the representation to
     * @param variableNames
     *            the names of the variables
     */
    public void exportJSON(File file, String[] variableNames) {
        // String[] simplVar = Arrays.copyOf(variableNames,
        // variableNames.length);
        // for(int i=0;i<simplVar.length;i++){
        // simplVar[i] = simplVar[i].replaceAll("[%()]","");
        // }
        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);
            out.println("{");
            out.println("\t\"nodes\": [");

            ArrayList<Integer> listNodes = new ArrayList<Integer>(graph.vertexSet());

            Integer firstNode = listNodes.get(0);
            out.println("\t\t{");
            out.println("\t\t\t\"id\":\"" + firstNode + "\",");
            if (variableNames != null) {
                out.println("\t\t\t\"text\":\"" + variableNames[firstNode] + "\",");
            }
            out.println("\t\t\t\"Type\":\"Symptom\"");
            out.println("\t\t}");

            for (int i = 1; i < listNodes.size(); i++) {
                Integer node = listNodes.get(i);
                out.println("\t\t,{");
                out.println("\t\t\t\"id\":\"" + node + "\",");
                if (variableNames != null) {
                    out.println("\t\t\t\"text\":\"" + variableNames[node] + "\",");
                }
                out.println("\t\t\t\"Type\":\"Symptom\"");
                out.println("\t\t}");
            }

            out.println("\t],\"links\": [");

            ArrayList<DefaultEdge> listEdges = new ArrayList<DefaultEdge>(graph.edgeSet());
            if (!listEdges.isEmpty()) {
                DefaultEdge firstEdge = listEdges.get(0);
                Integer source = graph.getEdgeSource(firstEdge);
                Integer target = graph.getEdgeTarget(firstEdge);
                out.println("\t\t{");
                out.println("\t\t\t\"source\":\"" + source + "\",");
                out.println("\t\t\t\"target\":\"" + target + "\"");
                out.println("\t\t}");

                for (int i = 1; i < listEdges.size(); i++) {
                    DefaultEdge edge = listEdges.get(i);
                    source = graph.getEdgeSource(edge);
                    target = graph.getEdgeTarget(edge);
                    out.println("\t\t,{");
                    out.println("\t\t\t\"source\":\"" + source + "\",");
                    out.println("\t\t\t\"target\":\"" + target + "\"");
                    out.println("\t\t}");
                }
            }
            out.println("\t]");
            out.println("}");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

    }

    /**
     * Export a dot file representing the graph
     * 
     * @see {@linkplain http://www.graphviz.org/content/dot-language}
     * @param file
     *            the file to save the representation to
     * @param variableNames
     *            the names of the variables
     */
    public void exportDOTCG(File file, String[] variableNames) {
        // String[] simplVar = Arrays.copyOf(variableNames,
        // variableNames.length);
        // for(int i=0;i<simplVar.length;i++){
        // simplVar[i] = simplVar[i].replaceAll("[%()]","");
        // }
        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);
            out.println("graph G{");

            for (CliqueGraphEdge edge : graph.cg.edgeSet()) {
                BitSet source = graph.cg.getEdgeSource(edge);
                BitSet target = graph.cg.getEdgeTarget(edge);
                BitSet inter = (BitSet) source.clone();
                inter.and(target);
                if (variableNames == null) {
                    out.println(graph.cg.getEdgeSource(edge) + "--" + graph.cg.getEdgeTarget(edge));
                } else {
                    out.print("\"");
                    for (int v = source.nextSetBit(0); v >= 0; v = source.nextSetBit(v + 1)) {
                        out.print(variableNames[v] + ";");
                    }
                    out.print("\"--\"");
                    for (int v = target.nextSetBit(0); v >= 0; v = target.nextSetBit(v + 1)) {
                        out.print(variableNames[v] + ";");
                    }
                    out.print("\" [label = \"");
                    for (int v = inter.nextSetBit(0); v >= 0; v = inter.nextSetBit(v + 1)) {
                        out.print(variableNames[v] + ";");
                    }
                    out.println("\"]");
                }
            }

            out.println("}");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

    }

    /**
     * Export a Netica representation of a BN equivalent to the MRF
     * 
     * @param file
     *            the file to save the representation to
     * @param variableNames
     *            the names of the variables
     */
    public void exportBNNetica(File file, String[] variableNames, String[][] outcomes) {
        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);
            out.println("// ~->[DNET-1]->~");
            out.println("bnet Unnamed_1 {");
            out.println("\tautoupdate = FALSE;");
            DirectedAcyclicGraph<Integer, DefaultEdge> bn = graph.getBayesianNetwork();
            for (Integer varIndex : bn.vertexSet()) {
                out.print("\tnode ");
                out.print("v" + varIndex);
                out.println("{");
                if (variableNames != null) {
                    out.println("\t\ttitle = \"" + variableNames[varIndex] + "\";");
                }
                out.println("\t\tkind = NATURE;");
                out.println("\t\tdiscrete = TRUE;");
                out.print("\t\tstates = (");
                String outcome = "\"" + outcomes[varIndex][0] + "\"";
                out.print(outcome);
                for (int i = 1; i < outcomes[varIndex].length; i++) {
                    outcome = "\"" + outcomes[varIndex][i] + "\"";
                    out.print("," + outcome);
                }
                out.println(");");

                out.print("\t\tparents = (");
                String listOfParents = "";
                Set<DefaultEdge> edgesOfNode = bn.edgesOf(varIndex);
                for (DefaultEdge e : edgesOfNode) {
                    Integer source = graph.getEdgeSource(e);
                    if (!source.equals(varIndex)) {
                        listOfParents += ",v" + source;
                    }
                }
                if (listOfParents.length() > 0) {
                    out.println(listOfParents.substring(1) + ");");
                } else {
                    out.println(");");
                }
                out.println("\t};");
                out.println();

            }
            out.println("};");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } catch (CycleFoundException e) {
            e.printStackTrace();
        }

    }

    /**
     * @return a Bayesian Network that can represent the same joint (links only,
     *         no CPTs)
     * @throws CycleFoundException
     */
    public DirectedAcyclicGraph<Integer, DefaultEdge> getBayesianNetwork() throws CycleFoundException {
        return graph.getBayesianNetwork();
    }

    /**
     * @return the size of the biggest clique for the graph associated to this
     *         model
     * @see ChordalGraph#getTreeWidth()
     */
    public int getTreeWidth() {
        return graph.getTreeWidth();
    }

    /**
     * Returns if the model contains the given interaction
     * 
     * @param v1
     *            first variable
     * @param v2
     *            second variable
     * @return <b>true</b> if the model already considers this interaction
     */
    public boolean containsInteraction(Integer v1, Integer v2) {
        return graph.containsEdge(v1, v2);
    }

    /**
     * Return all the interactions considered by the model
     * 
     * @return all the interactions considered by the model
     */
    public TreeSet<Couple<Integer>> getInteractions() {
        TreeSet<Couple<Integer>> interactions = new TreeSet<Couple<Integer>>();
        for (int v1 = 0; v1 < dimensionsForVariables.length; v1++) {
            for (int v2 = v1 + 1; v2 < dimensionsForVariables.length; v2++) {
                if (containsInteraction(v1, v2)) {
                    interactions.add(new Couple<Integer>(v1, v2));
                }
            }
        }
        return interactions;
    }

    public ChordalGraph getGraph() {
        return graph;
    }

    public List<BitSet> getCliques() {
        return graph.getCliques();
    }

    public List<BitSet> getCliquesBFS() {
        return graph.getCliquesBFS();
    }

    public List<BitSet> getSeparators() {
        return graph.getSeparatorsBFS();
    }

    public void exportGEXF(File file, String[] variableNames, int[] frequencies,
            ArrayList<GraphAction> operationsPerformed) {

        // String[] simplVar = Arrays.copyOf(variableNames,
        // variableNames.length);
        // for(int i=0;i<simplVar.length;i++){
        // simplVar[i] = simplVar[i].replaceAll("[%()]","");
        // }
        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);

            out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            out.println(
                    "<gexf xmlns=\"http://www.gexf.net/1.2draft\" xmlns:viz=\"http://www.gexf.net/1.1draft/viz\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.gexf.net/1.2draft http://www.gexf.net/1.2draft/gexf.xsd\" version=\"1.2\">");
            out.println("\t<graph mode=\"static\" defaultedgetype=\"undirected\">");

            ArrayList<Integer> listNodes = new ArrayList<Integer>(graph.vertexSet());

            out.println("\t\t<nodes>");
            for (int i = 0; i < listNodes.size(); i++) {
                Integer node = listNodes.get(i);
                out.print("\t\t\t<node id=\"" + node + "\"");
                if (variableNames != null) {
                    out.print(" label=\"" + variableNames[node] + "\"");
                }
                out.println(">");
                if (frequencies != null) {
                    out.println("\t\t\t\t<viz:size value=\"" + FastMath.log(frequencies[node]) + "\"/>");
                } else {
                    out.println("\t\t\t\t<viz:size value=\"1\"/>");
                }
                out.print("\t\t\t</node>");
            }
            out.println("\t\t</nodes>");
            out.println("\t\t<edges>");

            if (!operationsPerformed.isEmpty()) {
                for (int i = 0; i < operationsPerformed.size(); i++) {
                    PValueScoredGraphAction action = (PValueScoredGraphAction) operationsPerformed.get(i);
                    Integer source = action.getV1();
                    Integer target = action.getV2();
                    out.print("\t\t\t<edge id=\"" + i + "\" source=\"" + source + "\" target=\"" + target
                            + "\" weight=\"" + action.entropy + "\" />");
                }
            }
            out.println("\t\t</edges>");
            out.println("\t</graph>");
            out.println("</gexf>");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }
    }

    public void exportDOT(File file, String[] variableNames, int[] frequencies,
            ArrayList<GraphAction> operationsPerformed) {

        try {
            PrintWriter out = new PrintWriter(new FileOutputStream(file), true);

            out.println("graph Chordalysis{");
            out.println("\tgraph [K=1.0 fontsize=14 overlap=\"10:\" splines=\"true\"];");
            out.println("\tratio = auto;");
            ArrayList<Integer> listNodes = new ArrayList<Integer>(graph.vertexSet());

            double minFreq = Double.MAX_VALUE, maxFreq = 0.0;
            if (frequencies != null) {
                // computing the normalisation
                for (int f : frequencies) {
                    double freq = Math.sqrt(f);
                    if (freq < minFreq) {
                        minFreq = freq;
                    }
                    if (maxFreq < freq) {
                        maxFreq = freq;
                    }
                }
            }

            for (int i = 0; i < listNodes.size(); i++) {
                Integer node = listNodes.get(i);
                out.print("\t\"" + node + "\"");
                out.print(" [shape=\"ellipse\" ");
                if (variableNames != null) {
                    out.print("label=\"" + variableNames[node] + "\" ");
                }
                double fontSize;
                if (frequencies != null) {
                    fontSize = 10.0 + (Math.sqrt(frequencies[node]) - minFreq) / (maxFreq - minFreq) * 20;
                } else {
                    fontSize = 10.0;
                }
                out.print("fontsize=" + fontSize + " ");
                out.println("];");
            }

            // computing the normalisation
            double minEntropy = Double.MAX_VALUE, maxEntropy = 0.0;
            if (operationsPerformed != null) {
                for (int i = 0; i < operationsPerformed.size(); i++) {
                    PValueScoredGraphAction action = (PValueScoredGraphAction) operationsPerformed.get(i);
                    double H = action.entropy;
                    if (H < minEntropy) {
                        minEntropy = H;
                    }
                    if (maxEntropy < H) {
                        maxEntropy = H;
                    }
                }
            }

            if (!operationsPerformed.isEmpty()) {
                for (int i = 0; i < operationsPerformed.size(); i++) {
                    PValueScoredGraphAction action = (PValueScoredGraphAction) operationsPerformed.get(i);
                    Integer source = action.getV1();
                    Integer target = action.getV2();
                    double lineWidth = 1.0 + (action.entropy - minEntropy) / (maxEntropy - minEntropy) * 10;
                    out.println("\t\"" + source + "\" -- \"" + target + "\" [ penwidth=" + lineWidth + " ];");
                }
            }
            out.println("}");

            out.close();
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        }

    }

    public BitSet findSab(GraphAction action) {
        int a = action.getV1();
        int b = action.getV2();

        BitSet Sab = null;
        for (CliqueGraphEdge e : graph.cg.edgeSet()) {
            BitSet clique1 = e.getClique1();
            BitSet clique2 = e.getClique2();
            if ((clique1.get(a) && clique2.get(b)) || (clique2.get(a) && clique1.get(b))) {
                Sab = e.getSeparator();
                break;
            }
        }
        if (Sab == null) {// disconnected components
            Sab = new BitSet(dimensionsForVariables.length);
        }

        if (!Sab.equals(graph.getSeparator(a, b))) {
            System.err.println("Ouch");
        }
        return Sab;

    }

    public Double entropyDiffIfAdding(int a, int b, BitSet Sab, EntropyComputer computer) {
        BitSet Sabua = (BitSet) Sab.clone();
        BitSet Sabub = (BitSet) Sab.clone();
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabua.set(a);
        Sabub.set(b);
        Sabuaub.set(a);
        Sabuaub.set(b);

        Double entropy = 0.0;
        Double tmp;

        // Sab
        tmp = computer.computeEntropy(Sab);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy -= tmp;
        }

        // Sab + a
        tmp = computer.computeEntropy(Sabua);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy += tmp;
        }

        // Sab + b
        tmp = computer.computeEntropy(Sabub);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy += tmp;
        }

        // Sab + a + b
        tmp = computer.computeEntropy(Sabuaub);
        if (tmp == null) {
            entropy = null;
            return entropy;
        } else {
            entropy -= tmp;
        }

        return entropy;
    }

    public double messageLengthDiffIfAdding(Integer a, Integer b, MessageLengthFactorialComputer computer,
            boolean verbose) {

        BitSet Sab = graph.getSeparator(a, b);
        BitSet Sabua = (BitSet) Sab.clone();
        BitSet Sabub = (BitSet) Sab.clone();
        BitSet Sabuaub = (BitSet) Sab.clone();
        Sabua.set(a);
        Sabub.set(b);
        Sabuaub.set(a);
        Sabuaub.set(b);

        double lengthFrequencies, lengthPositionData;
        // model
        // System.out.println("graph");

        //all graphs equally likely
        lengthFrequencies = this.nbParametersDiffIfAdding(a, b)
                * computer.getLogFromTable(computer.getNbInstances() + 1);

        lengthPositionData = 0.0;

        lengthPositionData += computer.computeLengthData(Sab);
        lengthPositionData -= computer.computeLengthData(Sabua);
        lengthPositionData -= computer.computeLengthData(Sabub);
        lengthPositionData += computer.computeLengthData(Sabuaub);
        assert lengthPositionData >= 0;
        if (verbose) {
            System.out.println("adding (" + a + "," + b + ")");
            System.out.println("#param diff =  " + this.nbParametersDiffIfAdding(a, b));
            System.out.println("diff length frequencies=" + lengthFrequencies);
            System.out.println("diff length data|freq=" + lengthPositionData);
        }
        encodingLength = lengthFrequencies + lengthPositionData;
        return encodingLength;
    }

    /**
     * Display the model in a Frame
     * 
     * @param variableNames
     *            the names of the variables
     */
    public void display(String[] variableNames) {
        JFrame f = new JFrame();
        f.setSize(500, 500);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        final mxGraph xGraph = new mxGraph();
        mxGraphComponent graphComponent = new mxGraphComponent(xGraph);
        f.getContentPane().add(BorderLayout.CENTER, graphComponent);
        f.setVisible(true);

        Object parent = xGraph.getDefaultParent();
        xGraph.getModel().beginUpdate();
        try {
            Object[] vertices = new Object[graph.vertexSet().size()];
            for (Integer i : graph.vertexSet()) {
                if (variableNames != null) {
                    vertices[i] = xGraph.insertVertex(parent, null, variableNames[i], 0, 0, 100, 30);
                } else {
                    vertices[i] = xGraph.insertVertex(parent, null, "" + i, 0, 0, 100, 30);
                }
            }
            for (DefaultEdge edge : graph.edgeSet()) {
                xGraph.insertEdge(parent, null, "", vertices[graph.getEdgeSource(edge)],
                        vertices[graph.getEdgeTarget(edge)], "startArrow=none;endArrow=none;");
            }

        } finally {
            xGraph.getModel().endUpdate();
        }

        // define layout
        mxOrganicLayout layout = new mxOrganicLayout(xGraph);
        layout.setOptimizeEdgeCrossing(true);
        layout.setOptimizeBorderLine(true);
        layout.setMaxIterations(10000);

        // layout using morphing
        xGraph.getModel().beginUpdate();
        try {
            layout.execute(xGraph.getDefaultParent());
        } finally {
            mxMorphing morph = new mxMorphing(graphComponent, 20, 1.2, 20);

            morph.addListener(mxEvent.DONE, new mxIEventListener() {

                @Override
                public void invoke(Object arg0, mxEventObject arg1) {
                    xGraph.getModel().endUpdate();
                    // fitViewport();
                }

            });

            morph.startAnimation();
        }

    }

    /**
     * Builds a graphical representation of the model (MRF)
     * 
     * @param variableNames
     *            the names of the variables
     * @return an image
     * @throws IOException
     */
    public BufferedImage getImage(String[] variableNames) throws IOException {
        mxGraph xGraph = new mxGraph();
        Object parent = xGraph.getDefaultParent();
        xGraph.getModel().beginUpdate();
        try {
            Object[] vertices = new Object[graph.vertexSet().size()];
            for (Integer i : graph.vertexSet()) {
                if (variableNames != null) {
                    vertices[i] = xGraph.insertVertex(parent, null, variableNames[i], 0, 0, 100, 30);
                } else {
                    vertices[i] = xGraph.insertVertex(parent, null, "" + i, 0, 0, 100, 30);
                }
            }
            for (DefaultEdge edge : graph.edgeSet()) {
                xGraph.insertEdge(parent, null, "", vertices[graph.getEdgeSource(edge)],
                        vertices[graph.getEdgeTarget(edge)], "startArrow=none;endArrow=none;");
            }

        } finally {
            xGraph.getModel().endUpdate();
        }

        // define layout
        mxOrganicLayout layout = new mxOrganicLayout(xGraph);
        layout.setFineTuning(true);
        layout.setOptimizeEdgeCrossing(true);
        layout.setOptimizeNodeDistribution(true);
        layout.setOptimizeEdgeLength(false);
        layout.setMaxIterations(1000);

        xGraph.getModel().beginUpdate();
        try {
            layout.execute(xGraph.getDefaultParent());
        } finally {
            xGraph.getModel().endUpdate();
        }
        return mxCellRenderer.createBufferedImage(xGraph, null, 1.0, Color.WHITE, false, null);
    }

    public void saveAssociations(String[] variableNames, File f) throws IOException {

        int nbVertices = variableNames.length;
        boolean[][] association = new boolean[nbVertices][nbVertices];

        for (DefaultEdge edge : graph.edgeSet()) {
            association[graph.getEdgeSource(edge)][graph.getEdgeTarget(edge)] = true;
            association[graph.getEdgeTarget(edge)][graph.getEdgeSource(edge)] = true;
        }

        PrintWriter out = new PrintWriter(f);

        out.print("name");
        for (int i = 0; i < variableNames.length; i++) {
            out.print("," + variableNames[i]);
        }
        out.println();
        for (int i = 0; i < nbVertices; i++) {
            out.print(variableNames[i]);
            for (int j = 0; j < nbVertices; j++) {
                if (association[i][j]) {
                    out.print(",y");
                } else {
                    out.print(",n");
                }
            }
            out.println();
        }
        out.close();

    }

}