Source code for pysensmcda.alternative.percentage

# Copyright (C) 2023 - 2024  Jakub Więckowski

import numpy as np
from itertools import product
from ..validator import Validator
from ..utils import memory_guard

[docs] @memory_guard def percentage_modification(matrix: np.ndarray, percentages: int | np.ndarray, direction: None | np.ndarray = None, indexes: None | np.ndarray = None, step: int | np.ndarray = 1) -> list[tuple[int, int | tuple, tuple, np.ndarray]]: """ Modify a decision matrix based on specified percentage changes, directions, indexes, and steps of percentage modifications. Parameters: ------------- matrix : ndarray 2D array representing the initial decision matrix. percentages : int | ndarray Percentage changes to be applied to the values in decision matrix. If int, the same percentage change is applied to all values. If ndarray, it specifies the percentage change for each column from matrix individually. direction : None | ndarray, optional, default=None Direction of the modification for each column in the matrix. If None, both increase and decrease directions are considered. If ndarray, it specifies the direction (1 for increase, -1 for decrease) for each criterion individually. indexes : None | ndarray, optional, default=None Indexes of the columns from matrix to be modified. If None, all columns are considered subsequently. If ndarray, it specifies the indexes or combinations of indexes for the columns to be modified. step : int | np.ndarray, optional, default=1 Step size for the percentage change. If int, all changes for columns are made with the same step. If ndarray, the modification step is adjusted for each column separately. Returns: ---------- List[Tuple[int, int | tuple, tuple, ndarray]] A list of tuples containing information about the modified alternative index, criteria index, percentage change, and the resulting decision matrix. Examples: ----------- Example 1: Modify decision matrix with a single percentage change >>> matrix = np.array([ ... [4, 1, 6], ... [2, 6, 3], ... [9, 5, 7], ... ]) >>> percentages = 5 >>> results = percentage_modification(matrix, percentages) >>> for r in results: ... print(r) Example 2: Modify matrix with percentages list >>> matrix = np.array([ ... [4, 1, 6], ... [2, 6, 3], ... [9, 5, 7], ... ]) >>> percentages = np.array([3, 5, 8]) >>> results = percentage_modification(matrix, percentages) >>> for r in results: ... print(r) Example 3: Modify matrix with percentages list and specific direction for each column >>> matrix = np.array([ ... [4, 1, 6], ... [2, 6, 3], ... [9, 5, 7], ... ]) >>> percentages = np.array([2, 4, 6]) >>> direction = np.array([-1, 1, -1]) >>> results = percentage_modification(matrix, percentages, direction) >>> for r in results: ... print(r) Example 4: Modify matrix with percentages list, and specific column indexes >>> matrix = np.array([ ... [4, 1, 6], ... [2, 6, 3], ... [9, 5, 7], ... ]) >>> percentages = np.array([1, 4, 2]) >>> indexes = np.array([[0, 2], 1], dtype='object') >>> results = percentage_modification(matrix, percentages, indexes) >>> for r in results: ... print(r) Example 5: Modify matrix with percentages list, and specific modification step >>> matrix = np.array([ ... [4, 1, 6], ... [2, 6, 3], ... [9, 5, 7], ... ]) >>> percentages = np.array([2, 4, 9]) >>> step = np.array([2, 2, 3]) >>> results = percentage_modification(matrix, percentages, step=step) >>> for r in results: ... print(r) """ def modify_matrix(matrix: np.ndarray, alt_idx: int, crit_idx: int, diff: float, direction_val: int) -> np.ndarray: new_matrix = matrix.copy().astype(float) new_matrix[alt_idx, crit_idx] = matrix[alt_idx, crit_idx] + diff * direction_val return new_matrix Validator.is_type_valid(matrix, np.ndarray, 'matrix') Validator.is_dimension_valid(matrix, 2, 'matrix') Validator.is_type_valid(percentages, (int, np.integer, np.ndarray), 'percentages') if isinstance(percentages, np.ndarray): # check if matrix and percentages have the same length Validator.is_shape_equal(matrix.shape[1], percentages.shape[0], custom_message="Number of columns in 'matrix' and length of 'percentages' are different") if direction is not None: Validator.is_type_valid(direction, np.ndarray, 'direction') # check if matrix and direction have the same length Validator.is_shape_equal(matrix.shape[1], direction.shape[0], custom_message="Number of columns in 'matrix' and length of 'direction' are different") Validator.is_type_valid(step, (int, np.integer, np.ndarray), 'step') if isinstance(step, np.ndarray): # check if matrix and step have the same length Validator.is_shape_equal(matrix.shape[1], step.shape[0], custom_message="Number of columns in 'matrix' and length of 'step' are different") if indexes is not None: Validator.is_type_valid(indexes, np.ndarray, 'indexes') Validator.are_indexes_valid(indexes, matrix.shape[1]) results = [] # size of changes of matrix values percentages_values = None if isinstance(percentages, (int, np.integer)): percentages_values = np.array([percentages] * matrix.shape[1]) if isinstance(percentages, np.ndarray): percentages_values = percentages # vectors with subsequent changes for criteria in matrix if isinstance(step, (int, np.integer)): percentages_changes = np.array([np.arange(step, p+step, step) / 100 for p in percentages_values], dtype='object') else: percentages_changes = np.array([np.arange(step[idx], p+step[idx], step[idx]) / 100 for idx, p in enumerate(percentages_values)], dtype='object') # increasing or decreasing matrix values direction_values = None if direction is None: direction_values = np.array([[-1, 1]] * matrix.shape[1]) else: direction_values = np.array([[val] for val in direction]) # criteria indexes to modify matrix values indexes_values = None if indexes is None: indexes_values = np.arange(0, matrix.shape[1], dtype=int) else: indexes_values = indexes alt_indexes = np.arange(0, matrix.shape[0], dtype=int) for alt_idx in alt_indexes: for crit_idx in indexes_values: if isinstance(crit_idx, (int, np.integer)): changes = percentages_changes[crit_idx] else: changes = list(product(*percentages_changes[crit_idx])) for change in changes: diff = matrix[alt_idx, crit_idx] * change change_direction = direction_values[crit_idx] if not isinstance(crit_idx, (int, np.integer)): change_direction = [direction_values[crit_idx][0]] for val in change_direction: if isinstance(val, (int, np.integer)): new_matrix = modify_matrix(matrix, alt_idx, crit_idx, diff, val) results.append((alt_idx, crit_idx, change * val, new_matrix)) else: for v in val: change_val = tuple(c * v for c in change) new_matrix = modify_matrix(matrix, alt_idx, crit_idx, diff, v) results.append((alt_idx, tuple(crit_idx), change_val, new_matrix)) return results