lib/archeometer/analysis/dsm/utils.ex

defmodule Archeometer.Analysis.DSM.Utils do
  @moduledoc """
  Functions to iterate over the DSM matrix to find the coordinates
  of the modules that have cyclic dependencies.
  """
  use Memoize

  @doc """
  Get coordinates of the modules that have cyclic dependencies.
  On the matrix, thse are the modules above the diagonal.

  ## Parameters

  - `dsm` the dependency structure matrix.

  ## Returns

  - ``r`` and ``c`` coordinates in the DSM.
  """
  def upper_right_group_corners(dsm) do
    # This line is needed because of memoization
    Application.ensure_all_started(:memoize)

    coord_mtx = to_coord_mtx(dsm)
    mtx_size = length(dsm.nodes)

    coord_mtx
    |> Enum.filter(&(above_diagonal?(&1) and upper_right_empty?(&1, coord_mtx, mtx_size)))
    |> Enum.map(fn {{r, c}, _v} -> {r, c} end)
  end

  defp above_diagonal?({{row, col}, _v}) do
    col > row
  end

  defmemo upper_right_empty?({{row, col}, _v}, coord_mtx, mtx_size) do
    upper_right_positions =
      for r <- 0..row,
          c <- col..(mtx_size - 1),
          not (r == row and c == col),
          do: {r, c}

    not Enum.any?(upper_right_positions, fn {r, c} -> Map.get(coord_mtx, {r, c}) end)
  end

  @doc """
  Generates a map where the keys are the matrix coordinates of all
  dependencies in the DSM, and the values the type of dependency.

  ## Parameters

  - `dsm` the dependency structure matrix.

  ## Returns

  - A ``map`` with the module coordinates and the type of dependency as a value.
  """
  def to_coord_mtx(dsm) do
    mod_indexes = dsm.nodes |> Enum.with_index() |> Enum.into(%{})

    dsm.edges
    |> Enum.map(fn {{callee, caller}, val} ->
      row = Map.get(mod_indexes, callee)
      col = Map.get(mod_indexes, caller)
      {{row, col}, val}
    end)
    |> Enum.into(%{})
  end
end