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