///|
/// The tests of the conditional independence of the columns of a table.
///
/// This is the port of `dowhy.utils.cit`, the module of the upstream that the
/// graph refuter of the upstream builds on.
///
/// The upstream takes a `pandas.DataFrame` and the names of its columns. The
/// port takes the columns themselves and the index of every one of them,
/// because the `utils` package is below the `api` package, which owns the
/// frame of the library.

///|
/// The partial correlation of two columns, given the other columns.
///
/// This is the port of the dictionary `dowhy.utils.cit.partial_corr` returns.
pub struct PartialCorrelation {
  /// The number of the rows the correlation was computed on.
  n : Int32
  /// The partial correlation coefficient.
  r : Double
  /// The lower bound of the confidence interval of the coefficient.
  ci_lower : Double
  /// The upper bound of the confidence interval of the coefficient.
  ci_upper : Double
  /// The p-value of the test of the independence of the two columns.
  p_value : Double
}

///|
/// The confidence interval of a correlation coefficient.
///
/// This is the port of `dowhy.utils.cit.compute_ci`. The interval is the one
/// of the Fisher z transform: the coefficient is transformed, the interval is
/// computed around it and it is transformed back.
///
/// The upstream takes the number of the rows of the two columns, but it only
/// uses the number of the rows of the first one, so the port takes one number.
pub fn compute_ci(
  r : Double,
  nx : Int32,
  confidence : Double,
) -> (Double, Double) {
  let z = @math.atanh(r)
  let se = 1.0 / (nx.to_double() - 3.0).sqrt()
  let crit = normal_ppf((1.0 - confidence) / 2.0).abs()
  let lower = @math.tanh(z - crit * se)
  let upper = @math.tanh(z + crit * se)
  (lower, upper)
}

///|
/// The partial correlation of two columns, given the other columns.
///
/// This is the port of `dowhy.utils.cit.partial_corr`. The correlation is the
/// correlation of the residuals of the two regressions of the columns on the
/// other ones, and the upstream computes it with the pseudo inverse of the
/// covariance matrix of the columns.
///
/// The `correlation` is `"spearman"` for the correlation of the ranks of the
/// columns and `"pearson"` for the correlation of the columns themselves.
/// The upstream treats every kind of correlation but `"spearman"` as the
/// Pearson correlation.
///
/// The rows that hold a missing value in one of the columns are dropped, like
/// the upstream drops them.
pub fn partial_corr(
  columns : Array[Array[Double]],
  x : Int32,
  y : Int32,
  z : Array[Int32],
  correlation : String,
) -> PartialCorrelation {
  let selected = selected_columns(x, y, z)
  let kept = complete_columns(columns, selected)
  let n = if kept.length() > 0 { kept[0].length() } else { 0 }
  let k = selected.length() - 2
  let values = if correlation == "spearman" {
    let ranked : Array[Array[Double]] = []
    for column in kept {
      ranked.push(average_rank(column))
    }
    ranked
  } else {
    kept
  }
  let covariance = covariance_matrix(values)
  let inverse = hermitian_pinv(covariance)
  let scale : Array[Double] = []
  for i in 0.. Double {
  let counts : Map[String, Int32] = Map([])
  for value in values {
    let count = match counts.get(value) {
      Some(number) => number
      None => 0
    }
    counts.set(value, count + 1)
  }
  let total = values.length().to_double()
  let mut result = 0.0
  for value in counts.keys() {
    let count = match counts.get(value) {
      Some(number) => number
      None => 0
    }
    let probability = count.to_double() / total
    result = result - probability * (ln(probability) / ln(2.0))
  }
  result
}

///|
/// The conditional mutual information of the upstream.
///
/// This is the port of `dowhy.utils.cit.conditional_MI`.
///
/// The upstream reads the *names* of the columns of the two variables and not
/// their values: it indexes the frame with a list, which gives a frame and not
/// a column, and it walks the names of that frame. The entropy of the pairs
/// and of the triples is therefore the entropy of a single category, which is
/// zero, and the conditional mutual information of the upstream is the
/// negative entropy of the conditioning columns.
///
/// The port reproduces that number, because the decision of the graph refuter
/// of the upstream depends on it: the conditional independence of the discrete
/// data is always satisfied. The upstream also takes the two variables, but
/// its number does not depend on them, so the port takes the conditioning
/// columns alone.
pub fn conditional_mi(z : Array[Array[String]]) -> Double {
  let h_xz = entropy([""])
  let h_yz = entropy([""])
  let h_xyz = entropy([""])
  let keys : Array[String] = []
  let rows = if z.length() > 0 { z[0].length() } else { 0 }
  for row in 0.. String {
  let mut key = ""
  for i in 0.. 0 {
      key = key + "/"
    }
    key = key + columns[i][row]
  }
  key
}

///|
/// The columns of the partial correlation, the two of them first.
fn selected_columns(x : Int32, y : Int32, z : Array[Int32]) -> Array[Int32] {
  let selected = [x, y]
  for index in z {
    selected.push(index)
  }
  selected
}

///|
/// The given columns, without the rows that hold a missing value in one
/// of them.
fn complete_columns(
  columns : Array[Array[Double]],
  selected : Array[Int32],
) -> Array[Array[Double]] {
  let rows = if selected.length() > 0 {
    columns[selected[0]].length()
  } else {
    0
  }
  let mut complete = true
  for index in selected {
    let column = columns[index]
    for row in 0.. Matrix {
  let n = columns.length()
  let matrix = Matrix::zeros(n, n)
  let means : Array[Double] = []
  for column in columns {
    means.push(mean(column))
  }
  for i in 0.. Double {
  let n = x.length()
  if n < 2 {
    return 0.0
  }
  let mut s = 0.0
  for i in 0.. Matrix {
  let (values, vectors) = jacobi_eigen(a)
  let n = values.length()
  let mut largest = 0.0
  for i in 0.. largest {
      largest = value
    }
  }
  let tolerance = 1.0e-15 * largest
  let inverse = Matrix::zeros(n, n)
  for k in 0..