## lib/algorithms/payment_matrix.ex

``````defmodule Descisionex.PaymentMatrix do
@moduledoc """
https://en.wikipedia.org/wiki/Decision-matrix_method
"""

alias Descisionex.{PaymentMatrix, Helper}

defstruct matrix: [],
variants: [],
variants_num: 0,
possible_steps: [],
possible_steps_num: 0,
wald_criterion: %{},
laplace_criterion: %{},
savage_criterion: %{},
hurwitz_criterion: %{},
generalized_criterion: %{}

@doc """
Set variants for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{} |> Descisionex.PaymentMatrix.set_variants(["some", "variants"])
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: ["some", "variants"],
variants_num: 2,
wald_criterion: %{}
}

"""
def set_variants(%PaymentMatrix{} = data, variants) do
data
|> Map.put(:variants, variants)
|> Map.put(:variants_num, Enum.count(variants))
end

@doc """
Set steps for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{} |> Descisionex.PaymentMatrix.set_steps(["some", "steps"])
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [],
possible_steps: ["some", "steps"],
possible_steps_num: 2,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

"""
def set_steps(%PaymentMatrix{} = data, steps) do
data
|> Map.put(:possible_steps, steps)
|> Map.put(:possible_steps_num, Enum.count(steps))
end

@doc """
Set Hurwitz additional value for payment matrix (range from 0.1 to 0.9), defaults to 0.5.

## Examples

%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

** (ArgumentError) Hurwitz additional value incorrect (number range must be from 0.1 to 0.9)

"""
def set_hurwitz_additional_value(%PaymentMatrix{} = data, value) do
if 0.1 <= value && value <= 0.9 do
else
raise ArgumentError,
message: "Hurwitz additional value incorrect (number range must be from 0.1 to 0.9)"
end
end

@doc """
Set Generalized additional value for payment matrix (range from 0.1 to 0.9), defaults to 0.5.

## Examples

%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

** (ArgumentError) Generalized additional value incorrect (number range must be from 0.1 to 0.9)

"""
def set_generalized_additional_value(%PaymentMatrix{} = data, value) do
if 0.1 <= value && value <= 0.9 do
else
raise ArgumentError,
message: "Generalized additional value incorrect (number range must be from 0.1 to 0.9)"
end
end

@doc """
Calculates Wald criterion for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3, 4]]} |> Descisionex.PaymentMatrix.calculate_wald_criterion()
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{criterion: 4, strategy_index: 1}
}

"""
def calculate_wald_criterion(%PaymentMatrix{} = data) do
all_criteria = Enum.map(data.matrix, fn row -> Enum.max(row) end)
{wald_criterion, strategy_index} = Helper.find_max_criteria(all_criteria)

Map.put(data, :wald_criterion, %{criterion: wald_criterion, strategy_index: strategy_index})
end

@doc """
Calculates Laplace criterion for payment matrix (variants must be set).

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3, 4]]} |> Descisionex.PaymentMatrix.calculate_laplace_criterion()
** (ArgumentError) For Laplace criterion variants must be set!
iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3, 4]]} |> Descisionex.PaymentMatrix.set_variants(["some", "variants"]) |> Descisionex.PaymentMatrix.calculate_laplace_criterion()
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{criterion: 3.5, strategy_index: 1},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: ["some", "variants"],
variants_num: 2,
wald_criterion: %{}
}

"""
def calculate_laplace_criterion(%PaymentMatrix{} = data) do
variant_rows = data.variants_num

if variant_rows == 0,
do: raise(ArgumentError, message: "For Laplace criterion variants must be set!")

all_criteria =
data.matrix
|> Enum.map(fn row ->
Enum.map(row, fn element ->
Float.round(element / variant_rows, 3)
end)
end)
|> Enum.map(fn row -> Enum.sum(row) end)

{laplace_criterion, strategy_index} = Helper.find_max_criteria(all_criteria)

Map.put(data, :laplace_criterion, %{
criterion: laplace_criterion,
strategy_index: strategy_index
})
end

@doc """
Calculates Hurwitz criterion for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3, 4]]} |> Descisionex.PaymentMatrix.calculate_hurwitz_criterion()
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{criterion: 3.5, strategy_index: 1},
laplace_criterion: %{},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

"""
def calculate_hurwitz_criterion(%PaymentMatrix{} = data) do

max =
data.matrix
|> Enum.map(fn row -> Enum.max(row) end)
|> Enum.map(fn element ->
if is_float(num), do: Float.round(num, 3), else: num
end)
|> Enum.with_index()

min =
data.matrix
|> Enum.map(fn row -> Enum.min(row) end)
|> Enum.map(fn element ->
num = element * (1 - additional_value)
if is_float(num), do: Float.round(num, 3), else: num
end)

{hurwitz_criterion, strategy_index} =
max
|> Enum.map(fn {element, index} ->
element + Enum.at(min, index)
end)
|> Helper.find_max_criteria()

Map.put(data, :hurwitz_criterion, %{
criterion: hurwitz_criterion,
strategy_index: strategy_index
})
end

@doc """
Calculates Savage criterion for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3, 4]]} |> Descisionex.PaymentMatrix.calculate_savage_criterion()
%Descisionex.PaymentMatrix{
generalized_criterion: %{},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{criterion: 0, strategy_index: 1},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

"""
def calculate_savage_criterion(%PaymentMatrix{} = data) do
matrix = data.matrix

max =
matrix
|> Matrix.transpose()
|> Enum.map(fn row -> Enum.max(row) end)

all_criteria =
matrix
|> Enum.map(fn row ->
Enum.zip(max, row)
|> Enum.map(fn {risk, elem} ->
num = risk - elem
if is_float(num), do: Float.round(num, 3), else: num
end)
end)
|> Enum.map(fn row -> Enum.max(row) end)

{savage_criterion, strategy_index} = Helper.find_min_criteria(all_criteria)

Map.put(data, :savage_criterion, %{
criterion: savage_criterion,
strategy_index: strategy_index
})
end

@doc """
Calculates generalized criterion for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3,4]]} |> Descisionex.PaymentMatrix.calculate_generalized_criterion()
%Descisionex.PaymentMatrix{
generalized_criterion: %{criterion: 1.5, strategy_index: 0},
hurwitz_criterion: %{},
laplace_criterion: %{},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{},
variants: [],
variants_num: 0,
wald_criterion: %{}
}

"""
def calculate_generalized_criterion(%PaymentMatrix{} = data) do

max =
data.matrix
|> Enum.map(fn row -> Enum.max(row) end)
|> Enum.map(fn element ->
if is_float(num), do: Float.round(num, 3), else: num
end)
|> Enum.with_index()

min =
data.matrix
|> Enum.map(fn row -> Enum.min(row) end)
|> Enum.map(fn element ->
if is_float(num), do: Float.round(num, 3), else: num
end)

{generalized_criterion, strategy_index} =
max
|> Enum.map(fn {element, index} ->
element + Enum.at(min, index)
end)
|> Helper.find_min_criteria()

Map.put(data, :generalized_criterion, %{
criterion: generalized_criterion,
strategy_index: strategy_index
})
end

@doc """
Calculates all criteria for payment matrix.

## Examples

iex> %Descisionex.PaymentMatrix{matrix: [[1, 2], [3,4]]} |> Descisionex.PaymentMatrix.set_variants(["some", "variants"]) |> Descisionex.PaymentMatrix.calculate_criteria()
%Descisionex.PaymentMatrix{
generalized_criterion: %{criterion: 1.5, strategy_index: 0},
hurwitz_criterion: %{criterion: 3.5, strategy_index: 1},
laplace_criterion: %{criterion: 3.5, strategy_index: 1},
matrix: [[1, 2], [3, 4]],
possible_steps: [],
possible_steps_num: 0,
savage_criterion: %{criterion: 0, strategy_index: 1},
variants: ["some", "variants"],
variants_num: 2,
wald_criterion: %{criterion: 4, strategy_index: 1}
}

"""
def calculate_criteria(%PaymentMatrix{} = data) do
data
|> calculate_wald_criterion()
|> calculate_savage_criterion()
|> calculate_laplace_criterion()
|> calculate_hurwitz_criterion()
|> calculate_generalized_criterion()
end
end
``````