AnaHiePro is a module that allows solving various tasks of systems analysis using the Analytic Hierarchy Process (AHP).
AnaHiePro is a Python module designed to simplify the decision-making process by using the Analytic Hierarchy Process (AHP) method. This method allows you to structure complex problems in the form of a hierarchical model consisting of goals, criteria, and alternatives. AnaHiePro automatically calculates global priorities for the entire hierarchy.
The module provides a recursive traversal of the hierarchical tree, starting from the leaf nodes and moving up to the root. Each level of the hierarchy is processed by multiplying the matrix of local child vectors by the global parent vector, which allows you to determine the weight of each element at all levels. This makes AnaHiePro an ideal tool for analyzing complex systems and making informed decisions in a variety of fields, including business, project management, scientific research, and more.
Open the terminal window (Linux and macOS) or command line (Windows). Then use the pip
command to install the module:
pip install anahiepro
Before installing AnaHiePro, ensure you have Python 3.x and pip
installed on your system. You can download the latest version of Python from python.org.
After loading you can use all AnaHiePro's functionality, down below you can see the simplest way of using AnaHiePro.
from anahiepro.nodes import Problem, Criteria, Alternative
from anahiepro.models.model import Model
problem = Problem("Example Problem")
list_of_criterias = [
Criteria("Citeria_1"),
Criteria("Citeria_2"),
Criteria("Citeria_3")
]
alternatives = [
Alternative("Alternative_1"),
Alternative("Alternative_2")
]
model = Model(problem, list_of_criterias, alternatives)
print(model.show())
PairwiseComparisonMatrix
represents the pairwise comparison matrix. A pairwise comparison matrix is a tool used in decision-making processes. It helps compare different options or criteria by evaluating them in pairs. Each element of the matrix represents the comparison result between two options or criteria.
Method Name | Description |
---|---|
__init__(self, size, matrix) |
Initialize a pairwise comparison matrix with the given size or given matrix. |
set_comparison(self, i, j, value) |
Set the comparison value for the given indices. Might raise the ValueError exception when you try to set diagonal values to value, that not equal 1 . |
set_matrix(self, matrix) |
Set the entire matrix, ensuring it is a valid pairwise comparison matrix. Might raise the ValueError if the matrix is not consistent or not valid. |
get_matrix(self) |
Returns the current pairwise comparison matrix. |
calculate_priority_vector(self) |
Calculate the priority vector from the pairwise comparison matrix. |
calculate_consistency_ratio(self) |
Calculate the consistency ratio of the pairwise comparison matrix. |
__getitem__(self, key) |
Returns the value at the specified index in the matrix. |
__setitem__(self, key, value) |
Set the value at the specified index in the matrix. |
from anahiepro.pairwise import PairwiseComparisonMatrix
matrix = [
[1, 2, 3],
[1/2, 1, 2],
[1/3, 1/2, 1]
]
pairwise_matrix = PairwiseComparisonMatrix(matrix=matrix)
print(pairwise_matrix.get_matrix())
print("Consistency ratio:", pairwise_matrix.calculate_consistency_ratio())
print("Priority vector:", pairwise_matrix.calculate_priority_vector())
Output:
[[1. 2. 3. ]
[0.5 1. 2. ]
[0.33333333 0.5 1. ]]
Consistency ratio: 0.007933373029552656
Priority vector: [0.84679693 0.46601031 0.25645536]
AnaHiePro has three types of nodes: Problem, Criteria (also DummyCriteria, which use for normalizing the model) and Alternative. All of them is inherited from abstract class Node
.
NOTE: And we want to mentione that each class which is inhereted from
Node
has an id field.
As we mentioned before, Node
is a basic class for Problem
, Criteria
and Alternative
. Down below you can see all Node
's methods:
Method Name | Description |
---|---|
__init__(self, name, parents, children, id, pcm) |
Initialize the Node object with given name , list of its parents , list of children , identifier (id ) and pcm. |
get_name(self) |
Returns the name of the node. |
get_parents(self) |
Returns list of parents for the node. |
get_children(self) |
Returns list of children for the node. |
get_key(self) |
Returns the tuple object, which consists of name of a node and its id. |
add_child(self, child) |
Add child to the list of children. |
show(self) |
Returns str object, which represent all relations between nodes. |
compare(self, key: tuple) |
Compare the node with a given key, where key is a tuple object which has size that equal 2. key[0] is a name of node and key[1] is an identifier of the node. |
create_pcm(self) |
Create a pairwise comparison matrix (PCM) object for the node which shape is equal number of node's childrens. |
set_matrix(self, matrix) |
Attach given PCM to the node. If the self.pcm does not exist call the create_pcm method than checks if the shape of given matrix matchs, raise VlalueError if does not otherwise attach it. |
set_comparison(self, i, j, value) |
Set given value to the right place. Other words it is a wrapper above the PairwiseComparisonMatrix 's set_comparison method. |
get_priority_vector(self) |
Wrapper above PairwiseComparisonMatrix's get_priority_vector` method. |
get_consistency_ratio(self) |
Wrapper above PairwiseComparisonMatrix's get_consistency_ratio` method. |
get_pcm(self) |
Returns the pairwise comparison matrix of the node. |
__eq__(self, value) |
Compare two Node 's instance. |
def show(self) |
Show the node and its children in a hierarchical structure. |
__copy__(self) |
Copy the node. |
Problem
is a class that represents the problem that the user wants to solve. This class inherits from Node and has the same methods as its parent. However, it overrides some methods.
Method Name | Description |
---|---|
__init__(self, name, children, pcm) |
Initialize the Problem object with given name , list of its childern and pairwise comparison matrix. |
The remaining methods are the same as in the Node
class.
Criteria
represents the criteria which will be used for selection. This class inherits form Node
and has the same methods as his parrent, except of this it overrides some methods.
Method Name | Description |
---|---|
__init__(self, name, children, pcm) |
Initialize the Criteria object with given name , list of its childern and pairwise comparison matrix. |
The remaining methods are the same as in the Node
class.
DummyCriteria
class that inherited from Criteria
it is used for normalizing problem in VaryDepthModel
.
Alternative
represents alternatives between which the selection occurs. Since Alternative
is the final node in the hierarchy, it has no children, so the self.pcm field for it is deleted.
Method Name | Description |
---|---|
__init__(self, name) |
Initialize the Alternative object with given name . |
create_pcm(self) |
Not implemented for reasons which were mentioned. |
set_matrix(self, matrix) |
Not implemented and raise NotImplementedError exception. |
`set_comparison(self, i, j, value) | Not implemented and raise NotImplementedError exception. |
The remaining methods are the same as in the Node
class.
from anahiepro.nodes import Problem, Criteria, Alternative
# Create instance of each classes.
problem = Problem("Example Problem")
criteria1 = Criteria("Criteria_1")
criteria2 = Criteria("Criteria_2")
alternative1 = Alternative("Alternative_1")
alternative2 = Alternative("Alternative_2")
# Linking each instances.
problem.add_child(criteria1)
problem.add_child(criteria2)
criteria1.add_child(alternative1)
criteria1.add_child(alternative2)
criteria2.add_child(alternative1)
criteria2.add_child(alternative2)
# Print the problem hierarchy.
print(problem.show())
Output:
+Example Problem
+--Criteria_1
+----Alternative_1
+----Alternative_2
+--Criteria_2
+----Alternative_1
+----Alternative_2
AnaHiePro has two types of models that you can use to automatically solve the set problems: Model
and VaryDepthModel
.
These two classes are designed to solve different types of problems. Specifically, VaryDepthModel
is used for problems with varying depths, as shown in the image below.
On the other hand, Model
can solve problems that have a hierarchy with the same depth for each child, as illustrated in the next picture.
Each model class in AnaHiePro has methods that are described below.
Method Name | Description |
---|---|
__init__(self, problem: Problem, criterias, alternatives: list) |
Initialize the model with a problem, criteria, and alternatives. Also checks if the criterias has correct format, type and for Model - if the depth of the criterias hierarchy is the same depth. |
get_problem(self) |
Return the problem instance. |
get_alternatives(self) |
Return the list of alternatives. |
get_criterias_name_ids(self) |
Get the names and IDs of the criteria. |
find_criteria(self, key: tuple) |
Find criteria by (name, id) tuple. |
attach_criteria_pcm(self, key: tuple, pcm) |
Attach a pairwise comparison matrix to the criteria identified by the key. |
__getitem__(self, key: tuple) |
Get the criteria identified by the key. |
solve(self, showAlternatives=False) |
Solve the model to calculate the global priority vector. |
show(self) |
Display the problem. |
Model
and VaryDepthModel
can take the next format of the criterias in their __init__
method:
criterias = [Criteria(children=[Criteria()]),
Criteria(children=[Criteria()]),
Criteria(children=[Criteria()])]
or
criterias = [
{Criteria(): [
{Criteria(): None}
]},
{Criteria(): [
{Criteria(): None}
]},
{Criteria(): [
{Criteria(): None}
]}
]
Another formats of the criterias
param is not added (except of empty list).
Here you can see the simplest way how to create Model
instance:
from anahiepro.nodes import Problem, Criteria, Alternative
from anahiepro.models.model import Model
problem = Problem("Example Problem")
list_of_criterias = [
Criteria("Citeria_1", children=[
Criteria("Criteria_4")
]),
Criteria("Citeria_2", children=[
Criteria("Criteria_5")
]),
Criteria("Citeria_3", children=[
Criteria("Criteria_5")
]),
]
alternatives = [
Alternative("Alternative_1"),
Alternative("Alternative_2")
]
model = Model(problem, list_of_criterias, alternatives)
print(model.show())
Output:
+Example Problem
+--Citeria_1
+----Criteria_4
+------Alternative_1
+------Alternative_2
+--Citeria_2
+----Criteria_5
+------Alternative_1
+------Alternative_2
+--Citeria_3
+----Criteria_5
+------Alternative_1
+------Alternative_2
Now let's see how it works for VaryDepthModel
:
from anahiepro.nodes import Problem, Criteria, Alternative
from anahiepro.models.vary_depth_model import VaryDepthModel
problem = Problem("Example Problem")
list_of_criterias = [
Criteria("Citeria_1", children=[
Criteria("Criteria_4")
]),
Criteria("Citeria_2", children=[
Criteria("Criteria_5")
]),
Criteria("Citeria_3"), # <- Here Criteria_3 does not have children.
]
alternatives = [
Alternative("Alternative_1"),
Alternative("Alternative_2")
]
model = VaryDepthModel(problem, list_of_criterias, alternatives)
print(model.show())
Output:
+Example Problem
+--Citeria_1
+----Criteria_4
+------Alternative_1
+------Alternative_2
+--Citeria_2
+----Criteria_5
+------Alternative_1
+------Alternative_2
+--DummyCriteria0
+----Citeria_3
+------Alternative_1
+------Alternative_2
So, as you can see from the output, VaryDepthModel
normalized the hierarchy. And, yes, you can use VaryDepthModel
with the example for Model
class.
from anahiepro.nodes import Problem, Criteria, Alternative
from anahiepro.models.model import Model
problem = Problem("Example Problem", pcm=[[1, 2, 1/2],
[1/2, 1, 1/7],
[2, 7, 1]])
list_of_criterias = [
Criteria("Citeria_1", pcm=[[1, 2, 4],
[1/2, 1, 3],
[1/4, 1/3, 1]]),
Criteria("Citeria_2", pcm=[[1, 2, 1/5],
[1/2, 1, 3],
[5, 1/3, 1]]),
Criteria("Citeria_3", pcm=[[1, 1/3, 3],
[3, 1, 3],
[1/3, 1/3, 1]]),
]
alternatives = [
Alternative("Alternative_1"),
Alternative("Alternative_2"),
Alternative("Alternative_3")
]
model = Model(problem, list_of_criterias, alternatives)
print("Global vector without alternatives:")
print(model.solve())
print("Global vector with alternatives:")
print(model.solve(showAlternatives=True))
Output:
Global vector without alternatives:
[0.64557092 0.88998852 0.15336415]
Global vector with alternatives:
[(Alternative_1, np.float64(0.6455709201621959)), (Alternative_2, np.float64(0.8899885172373624)), (Alternative_3, np.float64(0.15336414859759606))]
- @danylevych - Idea & Initial work