├── LICENSE ├── README.md ├── example.py └── topsis.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Shivanjan Chakravorty 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TOPSIS-Python 2 | 3 | [![Linkedin Badge](https://img.shields.io/badge/-Shivanjan%20Chakravorty-blue?style=plastic&logo=Linkedin&logoColor=white&link=https://www.linkedin.com/in/shivanjan/)](https://www.linkedin.com/in/shivanjan/) [![Gmail Badge](https://img.shields.io/badge/-schakravorty846-c14438?style=plastic&logo=Gmail&logoColor=white&link=mailto:schakravorty846@gmail.com)](mailto:schakravorty846@gmail.com) [![Github Badge](https://img.shields.io/github/followers/Glitchfix?label=Glitchfix&logo=github&style=plastic)](https://github.com/Glitchfix) 4 | 5 | Source code for TOPSIS optimization algorithm in python. 6 | 7 | TOPSIS is a method of compensatory aggregation that compares a set of alternatives by identifying weights for each criterion, normalising scores for each criterion and calculating the geometric distance between each alternative and the ideal alternative, which is the best score in each criterion. An assumption of TOPSIS is that the criteria are monotonically increasing or decreasing. Normalisation is usually required as the parameters or criteria are often of incongruous dimensions in multi-criteria problems. Compensatory methods such as TOPSIS allow trade-offs between criteria, where a poor result in one criterion can be negated by a good result in another criterion. This provides a more realistic form of modelling than non-compensatory methods, which include or exclude alternative solutions based on hard cut-offs. An example of application on nuclear power plants is provided in. 8 | 9 | 10 | ### Sample usage 11 | 12 | ```py 13 | from topsis import Topsis 14 | import numpy as np 15 | 16 | evaluation_matrix = np.array([ 17 | [1,2,3,4], 18 | [4,3,2,1], 19 | [3,3,3,3], 20 | ]) 21 | 22 | weights = [5, 5, 9, 0] 23 | 24 | ''' 25 | if higher value is preferred - True 26 | if lower value is preferred - False 27 | ''' 28 | criterias = np.array([True, True, True, True]) 29 | 30 | t = Topsis(evaluation_matrix, weights, criterias) 31 | 32 | t.calc() 33 | 34 | print("best_distance\t", t.best_distance) 35 | print("worst_distance\t", t.worst_distance) 36 | 37 | # print("weighted_normalized",t.weighted_normalized) 38 | 39 | print("worst_similarity\t", t.worst_similarity) 40 | print("rank_to_worst_similarity\t", t.rank_to_worst_similarity()) 41 | 42 | print("best_similarity\t", t.best_similarity) 43 | print("rank_to_best_similarity\t", t.rank_to_best_similarity()) 44 | 45 | ``` 46 | 47 | ```sh 48 | \~ python3 example.py 49 | Step 1 50 | [[1. 2. 3. 4.] 51 | [4. 3. 2. 1.] 52 | [3. 3. 3. 3.]] 53 | 54 | Step 2 55 | [[0.19611614 0.42640143 0.63960215 0.78446454] 56 | [0.78446454 0.63960215 0.42640143 0.19611614] 57 | [0.58834841 0.63960215 0.63960215 0.58834841]] 58 | 59 | Step 3 60 | [[0.05160951 0.1122109 0.30296944 0. ] 61 | [0.20643804 0.16831636 0.20197963 0. ] 62 | [0.15482853 0.16831636 0.30296944 0. ]] 63 | 64 | Step 4 65 | [0.05160951 0.1122109 0.20197963 0. ] [0.20643804 0.16831636 0.30296944 0. ] 66 | 67 | [[0.05160951 0.1122109 0.30296944 0. ] 68 | [0.20643804 0.16831636 0.20197963 0. ] 69 | [0.15482853 0.16831636 0.30296944 0. ]] 70 | Step 5 71 | [0.10098981 0.16468058 0.15492233] [0.16468058 0.10098981 0.05160951] 72 | 73 | Step 6 74 | [0.38013198 0.61986802 0.75011355] [0.61986802 0.38013198 0.24988645] 75 | 76 | best_distance [0.16468058 0.10098981 0.05160951] 77 | worst_distance [0.10098981 0.16468058 0.15492233] 78 | worst_similarity [0.38013198 0.61986802 0.75011355] 79 | rank_to_worst_similarity [1 2 3] 80 | best_similarity [0.61986802 0.38013198 0.24988645] 81 | rank_to_best_similarity [3 2 1] 82 | ``` 83 | 84 | ##### Resources: 85 | [WikiPedia](https://en.wikipedia.org/wiki/TOPSIS) 86 | [sample solution](http://www.jiem.org/index.php/jiem/article/view/573/498) 87 | -------------------------------------------------------------------------------- /example.py: -------------------------------------------------------------------------------- 1 | from topsis import Topsis 2 | import numpy as np 3 | 4 | evaluation_matrix = np.array([ 5 | [1,2,3,4], 6 | [4,3,2,1], 7 | [3,3,3,3], 8 | ]) 9 | 10 | weights = [5, 5, 9, 0] 11 | 12 | ''' 13 | if higher value is preferred - True 14 | if lower value is preferred - False 15 | ''' 16 | criterias = np.array([True, True, True, True]) 17 | 18 | t = Topsis(evaluation_matrix, weights, criterias) 19 | 20 | t.calc() 21 | 22 | print("best_distance\t", t.best_distance) 23 | print("worst_distance\t", t.worst_distance) 24 | 25 | # print("weighted_normalized",t.weighted_normalized) 26 | 27 | print("worst_similarity\t", t.worst_similarity) 28 | print("rank_to_worst_similarity\t", t.rank_to_worst_similarity()) 29 | 30 | print("best_similarity\t", t.best_similarity) 31 | print("rank_to_best_similarity\t", t.rank_to_best_similarity()) 32 | -------------------------------------------------------------------------------- /topsis.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import warnings 3 | 4 | 5 | class Topsis(): 6 | evaluation_matrix = np.array([]) # Matrix 7 | weighted_normalized = np.array([]) # Weight matrix 8 | normalized_decision = np.array([]) # Normalisation matrix 9 | M = 0 # Number of rows 10 | N = 0 # Number of columns 11 | 12 | ''' 13 | Create an evaluation matrix consisting of m alternatives and n criteria, 14 | with the intersection of each alternative and criteria given as {\displaystyle x_{ij}}x_{ij}, 15 | we therefore have a matrix {\displaystyle (x_{ij})_{m\times n}}(x_{{ij}})_{{m\times n}}. 16 | ''' 17 | 18 | def __init__(self, evaluation_matrix, weight_matrix, criteria): 19 | # M×N matrix 20 | self.evaluation_matrix = np.array(evaluation_matrix, dtype="float") 21 | 22 | # M alternatives (options) 23 | self.row_size = len(self.evaluation_matrix) 24 | 25 | # N attributes/criteria 26 | self.column_size = len(self.evaluation_matrix[0]) 27 | 28 | # N size weight matrix 29 | self.weight_matrix = np.array(weight_matrix, dtype="float") 30 | self.weight_matrix = self.weight_matrix/sum(self.weight_matrix) 31 | self.criteria = np.array(criteria, dtype="float") 32 | 33 | ''' 34 | # Step 2 35 | The matrix {\displaystyle (x_{ij})_{m\times n}}(x_{{ij}})_{{m\times n}} is then normalised to form the matrix 36 | ''' 37 | 38 | def step_2(self): 39 | # normalized scores 40 | self.normalized_decision = np.copy(self.evaluation_matrix) 41 | sqrd_sum = np.zeros(self.column_size) 42 | for i in range(self.row_size): 43 | for j in range(self.column_size): 44 | sqrd_sum[j] += self.evaluation_matrix[i, j]**2 45 | for i in range(self.row_size): 46 | for j in range(self.column_size): 47 | self.normalized_decision[i, 48 | j] = self.evaluation_matrix[i, j]/(sqrd_sum[j]**0.5) 49 | 50 | ''' 51 | # Step 3 52 | Calculate the weighted normalised decision matrix 53 | ''' 54 | 55 | def step_3(self): 56 | from pdb import set_trace 57 | self.weighted_normalized = np.copy(self.normalized_decision) 58 | for i in range(self.row_size): 59 | for j in range(self.column_size): 60 | self.weighted_normalized[i, j] *= self.weight_matrix[j] 61 | 62 | ''' 63 | # Step 4 64 | Determine the worst alternative {\displaystyle (A_{w})}(A_{w}) and the best alternative {\displaystyle (A_{b})}(A_{b}): 65 | ''' 66 | 67 | def step_4(self): 68 | self.worst_alternatives = np.zeros(self.column_size) 69 | self.best_alternatives = np.zeros(self.column_size) 70 | for i in range(self.column_size): 71 | if self.criteria[i]: 72 | self.worst_alternatives[i] = min( 73 | self.weighted_normalized[:, i]) 74 | self.best_alternatives[i] = max(self.weighted_normalized[:, i]) 75 | else: 76 | self.worst_alternatives[i] = max( 77 | self.weighted_normalized[:, i]) 78 | self.best_alternatives[i] = min(self.weighted_normalized[:, i]) 79 | 80 | ''' 81 | # Step 5 82 | Calculate the L2-distance between the target alternative {\displaystyle i}i and the worst condition {\displaystyle A_{w}}A_{w} 83 | {\displaystyle d_{iw}={\sqrt {\sum _{j=1}^{n}(t_{ij}-t_{wj})^{2}}},\quad i=1,2,\ldots ,m,} 84 | and the distance between the alternative {\displaystyle i}i and the best condition {\displaystyle A_{b}}A_b 85 | {\displaystyle d_{ib}={\sqrt {\sum _{j=1}^{n}(t_{ij}-t_{bj})^{2}}},\quad i=1,2,\ldots ,m} 86 | where {\displaystyle d_{iw}}d_{{iw}} and {\displaystyle d_{ib}}d_{{ib}} are L2-norm distances 87 | from the target alternative {\displaystyle i}i to the worst and best conditions, respectively. 88 | ''' 89 | 90 | def step_5(self): 91 | self.worst_distance = np.zeros(self.row_size) 92 | self.best_distance = np.zeros(self.row_size) 93 | 94 | self.worst_distance_mat = np.copy(self.weighted_normalized) 95 | self.best_distance_mat = np.copy(self.weighted_normalized) 96 | 97 | for i in range(self.row_size): 98 | for j in range(self.column_size): 99 | self.worst_distance_mat[i][j] = (self.weighted_normalized[i][j]-self.worst_alternatives[j])**2 100 | self.best_distance_mat[i][j] = (self.weighted_normalized[i][j]-self.best_alternatives[j])**2 101 | 102 | self.worst_distance[i] += self.worst_distance_mat[i][j] 103 | self.best_distance[i] += self.best_distance_mat[i][j] 104 | 105 | for i in range(self.row_size): 106 | self.worst_distance[i] = self.worst_distance[i]**0.5 107 | self.best_distance[i] = self.best_distance[i]**0.5 108 | 109 | ''' 110 | # Step 6 111 | Calculate the similarity 112 | ''' 113 | 114 | def step_6(self): 115 | np.seterr(all='ignore') 116 | self.worst_similarity = np.zeros(self.row_size) 117 | self.best_similarity = np.zeros(self.row_size) 118 | 119 | for i in range(self.row_size): 120 | # calculate the similarity to the worst condition 121 | self.worst_similarity[i] = self.worst_distance[i] / \ 122 | (self.worst_distance[i]+self.best_distance[i]) 123 | 124 | # calculate the similarity to the best condition 125 | self.best_similarity[i] = self.best_distance[i] / \ 126 | (self.worst_distance[i]+self.best_distance[i]) 127 | 128 | def ranking(self, data): 129 | return [i+1 for i in data.argsort()] 130 | 131 | def rank_to_worst_similarity(self): 132 | # return rankdata(self.worst_similarity, method="min").astype(int) 133 | return self.ranking(self.worst_similarity) 134 | 135 | def rank_to_best_similarity(self): 136 | # return rankdata(self.best_similarity, method='min').astype(int) 137 | return self.ranking(self.best_similarity) 138 | 139 | def calc(self): 140 | print("Step 1\n", self.evaluation_matrix, end="\n\n") 141 | self.step_2() 142 | print("Step 2\n", self.normalized_decision, end="\n\n") 143 | self.step_3() 144 | print("Step 3\n", self.weighted_normalized, end="\n\n") 145 | self.step_4() 146 | print("Step 4\n", self.worst_alternatives, 147 | self.best_alternatives, end="\n\n") 148 | self.step_5() 149 | print("Step 5\n", self.worst_distance, self.best_distance, end="\n\n") 150 | self.step_6() 151 | print("Step 6\n", self.worst_similarity, 152 | self.best_similarity, end="\n\n") 153 | --------------------------------------------------------------------------------