/**
   * Sets the covariance for the a<->b edge to the given covariance, if within range. Otherwise does
   * nothing.
   *
   * @param a a <-> b
   * @param b a <-> b
   * @param covar The covariance of a <-> b.
   * @return true if the coefficent was set (i.e. was within range), false if not.
   */
  public boolean setErrorCovariance(Node a, Node b, final double covar) {
    Edge edge = Edges.bidirectedEdge(semGraph.getExogenous(a), semGraph.getExogenous(b));

    if (edgeParameters.get(edge) == null) {
      throw new IllegalArgumentException("Not a covariance parameter in this model: " + edge);
    }

    if (editingEdge == null || !edge.equals(editingEdge)) {
      range = getParameterRange(edge);
      editingEdge = edge;
    }

    if (covar > range.getLow() && covar < range.getHigh()) {
      edgeParameters.put(edge, covar);
      return true;
    } else {
      return false;
    }

    //        if (!paramInBounds(edge, coef)) {
    //            edgeParameters.put(edge, d);
    //            return false;
    //        }
    //
    //        edgeParameters.put(edge, coef);
    //        return true;

    //        if (!paramInBounds(edge, covar)) {
    //            edgeParameters.put(edge, d);
    //            return false;
    //        }
    //
    //        edgeParameters.put(edge, covar);
    //        return true;
  }
  public boolean containsParameter(Edge edge) {
    if (Edges.isBidirectedEdge(edge)) {
      edge =
          Edges.bidirectedEdge(
              semGraph.getExogenous(edge.getNode1()), semGraph.getExogenous(edge.getNode2()));
    }

    return edgeParameters.keySet().contains(edge);
  }
  /**
   * @param a a->b
   * @param b a->b
   * @return The coefficient for a->b.
   */
  public double getErrorCovariance(Node a, Node b) {
    Edge edge = Edges.bidirectedEdge(semGraph.getExogenous(a), semGraph.getExogenous(b));
    Double d = edgeParameters.get(edge);

    if (d == null) {
      throw new IllegalArgumentException("Not a covariance parameter in this model: " + edge);
    }

    return d;
  }
  /**
   * @param edge a->b or a<->b.
   * @return the range of the covariance parameter for a->b or a<->b.
   */
  public ParameterRange getParameterRange(Edge edge) {
    if (Edges.isBidirectedEdge(edge)) {
      edge =
          Edges.bidirectedEdge(
              semGraph.getExogenous(edge.getNode1()), semGraph.getExogenous(edge.getNode2()));
    }

    if (!(edgeParameters.keySet().contains(edge))) {
      throw new IllegalArgumentException("Not an edge in this model: " + edge);
    }

    double initial = edgeParameters.get(edge);

    if (initial == Double.NEGATIVE_INFINITY) {
      initial = Double.MIN_VALUE;
    } else if (initial == Double.POSITIVE_INFINITY) {
      initial = Double.MAX_VALUE;
    }

    double value = initial;

    // look upward for a point that fails.
    double high = value + 1;

    while (paramInBounds(edge, high)) {
      high = value + 2 * (high - value);

      if (high == Double.POSITIVE_INFINITY) {
        break;
      }
    }

    // find the boundary using binary search.
    double rangeHigh;

    if (high == Double.POSITIVE_INFINITY) {
      rangeHigh = high;
    } else {
      double low = value;

      while (high - low > 1e-10) {
        double midpoint = (high + low) / 2.0;

        if (paramInBounds(edge, midpoint)) {
          low = midpoint;
        } else {
          high = midpoint;
        }
      }

      rangeHigh = (high + low) / 2.0;
    }

    // look downard for a point that fails.
    double low = value - 1;

    while (paramInBounds(edge, low)) {
      low = value - 2 * (value - low);

      if (low == Double.NEGATIVE_INFINITY) {
        break;
      }
    }

    double rangeLow;

    if (low == Double.NEGATIVE_INFINITY) {
      rangeLow = low;
    } else {

      // find the boundary using binary search.
      high = value;

      while (high - low > 1e-10) {
        double midpoint = (high + low) / 2.0;

        if (paramInBounds(edge, midpoint)) {
          high = midpoint;
        } else {
          low = midpoint;
        }
      }

      rangeLow = (high + low) / 2.0;
    }

    if (Edges.isDirectedEdge(edge)) {
      edgeParameters.put(edge, initial);
    } else if (Edges.isBidirectedEdge(edge)) {
      edgeParameters.put(edge, initial);
    }

    return new ParameterRange(edge, value, rangeLow, rangeHigh);
  }
 public ParameterRange getCovarianceRange(Node a, Node b) {
   return getParameterRange(
       Edges.bidirectedEdge(semGraph.getExogenous(a), semGraph.getExogenous(b)));
 }
  /**
   * Constructs a new standardized SEM IM from the freeParameters in the given SEM IM.
   *
   * @param im Stop asking me for these things! The given SEM IM!!!
   * @param initialization CALCULATE_FROM_SEM if the initial values will be calculated from the
   *     given SEM IM; INITIALIZE_FROM_DATA if data will be simulated from the given SEM,
   *     standardized, and estimated.
   */
  public StandardizedSemIm(SemIm im, Initialization initialization) {
    this.semPm = new SemPm(im.getSemPm());
    this.semGraph = new SemGraph(semPm.getGraph());
    semGraph.setShowErrorTerms(true);

    if (semGraph.existsDirectedCycle()) {
      throw new IllegalArgumentException("The cyclic case is not handled.");
    }

    if (initialization == Initialization.CALCULATE_FROM_SEM) {
      //         This code calculates the new coefficients directly from the old ones.
      edgeParameters = new HashMap<Edge, Double>();

      List<Node> nodes = im.getVariableNodes();
      TetradMatrix impliedCovar = im.getImplCovar(true);

      for (Parameter parameter : im.getSemPm().getParameters()) {
        if (parameter.getType() == ParamType.COEF) {
          Node a = parameter.getNodeA();
          Node b = parameter.getNodeB();
          int aindex = nodes.indexOf(a);
          int bindex = nodes.indexOf(b);
          double vara = impliedCovar.get(aindex, aindex);
          double stda = Math.sqrt(vara);
          double varb = impliedCovar.get(bindex, bindex);
          double stdb = Math.sqrt(varb);
          double oldCoef = im.getEdgeCoef(a, b);
          double newCoef = (stda / stdb) * oldCoef;
          edgeParameters.put(Edges.directedEdge(a, b), newCoef);
        } else if (parameter.getType() == ParamType.COVAR) {
          Node a = parameter.getNodeA();
          Node b = parameter.getNodeB();
          Node exoa = semGraph.getExogenous(a);
          Node exob = semGraph.getExogenous(b);
          double covar = im.getErrCovar(a, b) / Math.sqrt(im.getErrVar(a) * im.getErrVar(b));
          edgeParameters.put(Edges.bidirectedEdge(exoa, exob), covar);
        }
      }
    } else {

      // This code estimates the new coefficients from simulated data from the old model.
      DataSet dataSet = im.simulateData(1000, false);
      TetradMatrix _dataSet = dataSet.getDoubleData();
      _dataSet = DataUtils.standardizeData(_dataSet);
      DataSet dataSetStandardized = ColtDataSet.makeData(dataSet.getVariables(), _dataSet);

      SemEstimator estimator = new SemEstimator(dataSetStandardized, im.getSemPm());
      SemIm imStandardized = estimator.estimate();

      edgeParameters = new HashMap<Edge, Double>();

      for (Parameter parameter : imStandardized.getSemPm().getParameters()) {
        if (parameter.getType() == ParamType.COEF) {
          Node a = parameter.getNodeA();
          Node b = parameter.getNodeB();
          double coef = imStandardized.getEdgeCoef(a, b);
          edgeParameters.put(Edges.directedEdge(a, b), coef);
        } else if (parameter.getType() == ParamType.COVAR) {
          Node a = parameter.getNodeA();
          Node b = parameter.getNodeB();
          Node exoa = semGraph.getExogenous(a);
          Node exob = semGraph.getExogenous(b);
          double covar = -im.getErrCovar(a, b) / Math.sqrt(im.getErrVar(a) * im.getErrVar(b));
          edgeParameters.put(Edges.bidirectedEdge(exoa, exob), covar);
        }
      }
    }

    this.measuredNodes = Collections.unmodifiableList(semPm.getMeasuredNodes());
  }