Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: use model classes and also refactor original config impleme… #48

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.DS_Store
.idea
.ipynb_checkpoints/
__pycache__/
output_files
25 changes: 21 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,10 @@ effect.


### Requirements

As this project is now a submodule in ranking-service, it must be run from the root directory i.e. outside of `ProMCDA` folder.
You would need to create a virtual environment outside ProMCDA.

On Windows:
```bash
conda create --name <choose-a-name-like-Promcda> python=3.9
Expand All @@ -213,14 +217,27 @@ From the root dir, via command line
- on Windows:
```bash
activate.bat <your-env>
python3 -m mcda.mcda_run -c configuration.json
python3 -m ProMCDA.mcda.mcda_run -c ProMCDA/configuration.json
```
- on Mac and Linux:
```bash
source activate <your-env>
python3 -m mcda.mcda_run -c configuration.json
python3 -m ProMCDA.mcda.mcda_run -c ProMCDA/configuration.json
```
where an example of configuration file can be found in `./configuration.json`.
where an example of configuration file can be found in `ProMCDA/configuration.json`.


### Run ProMCDA for ranking service

Once you have set up virtual environment and installed all the requirements from requirements.txt, you can run the following command to run ProMCDA with sample data

```bash
python3 -m ProMCDA.mcda.mcda_ranking_run -c ProMCDA/input_files/toy_example/car-data-input-sample-from-ranking-service.json
```
where
- ProMCDA/input_files/toy_example/car-data-input-sample-from-ranking-service.json is the config file required to run mcda for ranking service where input matrix is part of the configuration
- ProMCDA.mcda.mcda_ranking_run is the main program that is run


We tested ProMCDA on Windows, Linux and Mac. We identified a possible issue with some Windows machines caused by the
library ```kaleido``` (used to generate static images) and reported [here](https://github.com/plotly/Kaleido/issues/126).
Expand All @@ -240,7 +257,7 @@ Change the configuration file for your needs.

### Running the tests
```bash
python3 -m pytest -s tests/unit_tests -vv
python3 -m pytest -s ProMCDA/tests/unit_tests -vv
```
### Toy example
```ProMCDA``` contains a toy example, a simple case to test run the package.
Expand Down
6 changes: 3 additions & 3 deletions configuration.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"input_matrix_path": "toy_example/car_data.csv",
"input_matrix_path": "ProMCDA/input_files/toy_example/car_data.csv",
"polarity_for_each_indicator": ["+","+","-","+","+","+"],

"sensitivity": {
Expand All @@ -21,5 +21,5 @@
"marginal_distribution_for_each_indicator": ["normal", "normal", "normal", "normal", "normal", "normal"]},


"output_directory_path": "toy_example"
}
"output_directory_path": "ProMCDA/input_files/toy_example"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"inputMatrix": {
"Fuel Efficiency":{"A":30.0,"B":25.0,"C":35.0,"D":40.0,"E":38.0,"F":40.0},
"Safety Rating":{"A":5.0,"B":4.0,"C":5.0,"D":3.0,"E":1.0,"F":4.0},
"Price":{"A":25000.0,"B":30000.0,"C":20000.0,"D":24000.0,"E":15000.0,"F":28000.0},
"Cargo 'Space":{"A":15.0,"B":20.0,"C":10.0,"D":30.0,"E":10.0,"F":15.0},
"Acceleration":{"A":8.0,"B":6.0,"C":10.0,"D":7.0,"E":10.0,"F":9.0},
"Warranty":{"A":3.0,"B":5.0,"C":2.0,"D":1.0,"E":3.0,"F":4.0}
},
"weights": [
0.8, 0.9, 0.5, 0.5, 0.1, 0.7
],
"polarity": [
"+","+","-","+","+","+"
],
"sensitivity": {
"sensitivityOn": "yes",
"normalization": "minmax",
"aggregation": "weighted_sum"
},
"robustness": {
"robustness": "weights",
"onWeightsLevel": "all",
"givenWeights": [
0.8, 0.9, 0.5, 0.5, 0.1, 0.7
]
},
"monteCarloSampling": {
"monteCarloRuns": 10000,
"marginalDistributions": [
"normal", "normal", "normal", "normal", "normal", "normal"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"inputMatrix": {
"Fuel Efficiency":{"A":30.0,"B":25.0,"C":35.0,"D":40.0,"E":38.0,"F":40.0},
"Safety Rating":{"A":5.0,"B":4.0,"C":5.0,"D":3.0,"E":1.0,"F":4.0},
"Price":{"A":25000.0,"B":30000.0,"C":20000.0,"D":24000.0,"E":15000.0,"F":28000.0},
"Cargo 'Space":{"A":15.0,"B":20.0,"C":10.0,"D":30.0,"E":10.0,"F":15.0},
"Acceleration":{"A":8.0,"B":6.0,"C":10.0,"D":7.0,"E":10.0,"F":9.0},
"Warranty":{"A":3.0,"B":5.0,"C":2.0,"D":1.0,"E":3.0,"F":4.0}
},
"weights": [
0.8, 0.9, 0.5, 0.5, 0.1, 0.7
],
"polarity": [
"+","+","-","+","+","+"
],
"sensitivity": {
"sensitivityOn": "no",
"normalization": "minmax",
"aggregation": "weighted_sum"
},
"robustness": {
"robustness": "none",
"onWeightsLevel": "none",
"givenWeights": [
0.8, 0.9, 0.5, 0.5, 0.1, 0.7
]
},
"monteCarloSampling": {
"monteCarloRuns": 10000,
"marginalDistributions": [
"normal", "normal", "normal", "normal", "normal", "normal"
]
}
}
2 changes: 1 addition & 1 deletion input_files/toy_example/car_data.csv
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ B,25,4,30000,20,6,5
C,35,5,20000,10,10,2
D,40,3,24000,30,7,1
E,38,1,15000,10,10,3
F,40,4,28000,15,9,4
F,40,4,28000,15,9,4
2 changes: 1 addition & 1 deletion mcda/configuration/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def __init__(self, input_config: dict):
# keys_of_dict_values = self._keys_of_dict_values

self._validate(input_config, valid_keys, str_values,
int_values, list_values, dict_values)
int_values, list_values, dict_values)
self._config = copy.deepcopy(input_config)

def _validate(self, input_config, valid_keys, str_values, int_values, list_values, dict_values):
Expand Down
124 changes: 124 additions & 0 deletions mcda/mcda_ranking_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
#! /usr/bin/env python3

"""
This script serves as the main entry point for running all pieces of functionality in a consequential way by
following the settings given in the configuration file 'configuration.json'.

Usage (from root directory):
$ python3 -m ProMCDA.mcda.mcda_ranking_run -c ProMCDA/input_files/toy_example/car-data-input-sample-from-ranking-service.json
"""

import time
from ProMCDA.mcda.utils.utils_for_main import *
from ProMCDA.mcda.utils.utils_for_plotting import *
from ProMCDA.mcda.utils.utils_for_parallelization import *

log = logging.getLogger(__name__)

FORMATTER: str = '%(levelname)s: %(asctime)s - %(name)s - %(message)s'
logging.basicConfig(stream=sys.stdout, level=logging.DEBUG, format=FORMATTER)
logger = logging.getLogger("ProMCDA")

# randomly assign seed if not specified as environment variable
RANDOM_SEED = int(os.environ.get('RANDOM_SEED')) if os.environ.get('RANDOM_SEED') else 67
NUM_CORES = int(os.environ.get('NUM_CORES')) if os.environ.get('NUM_CORES') else 1


# noinspection PyTypeChecker
def main(input_config: dict) -> dict:
"""
Execute the ProMCDA (Probabilistic Multi-Criteria Decision Analysis) process.

Parameters:
- input_config : Configuration parameters for the ProMCDA process.

Raises:
- ValueError: If there are issues with the input matrix, weights, or indicators.

This function performs the ProMCDA process based on the provided configuration.
It handles various aspects such as the sensitivity analysis and the robustness analysis.
The results are saved in output files, and plots are generated to visualize the scores and rankings.

Note: Ensure that the input matrix, weights, polarities and indicators (with or without uncertainty)
are correctly specified in the input configuration.

:param input_config: dict
:return: None
"""
is_robustness_indicators = 0
is_robustness_weights = 0
f_norm = None
f_agg = None

# Extracting relevant configuration values
config = Configuration.from_dict(input_config)
input_matrix = pd.DataFrame(config.input_matrix)
index_column_name = input_matrix.index.name
index_column_values = input_matrix.index.tolist()
polar = config.polarity
robustness = config.robustness.robustness
mc_runs = config.monte_carlo_sampling.monte_carlo_runs
marginal_pdf = config.monte_carlo_sampling.marginal_distributions

f_agg, f_norm, is_robustness_indicators, is_robustness_weights = verify_input(config, f_agg, f_norm,
is_robustness_indicators,
is_robustness_weights,
mc_runs,
robustness,
RANDOM_SEED)

# Check the input matrix for duplicated rows in the alternatives, rescale negative indicator values and
# drop the column containing the alternatives
input_matrix_no_alternatives = check_input_matrix(input_matrix)

if is_robustness_indicators == 0:
num_indicators = input_matrix_no_alternatives.shape[1]
# Process indicators and weights based on input parameters in the configuration
polar, weights = get_polar_and_weights(config, input_matrix_no_alternatives, is_robustness_indicators,
is_robustness_weights, mc_runs, num_indicators, polar)
return run_mcda_without_indicator_uncertainty(config, index_column_name, index_column_values,
input_matrix_no_alternatives, weights, f_norm, f_agg,
is_robustness_weights)
else:
num_non_exact_and_non_poisson = len(marginal_pdf) - marginal_pdf.count('exact') - marginal_pdf.count('poisson')
num_indicators = (input_matrix_no_alternatives.shape[1] - num_non_exact_and_non_poisson)
polar, weights = get_polar_and_weights(config, input_matrix_no_alternatives, is_robustness_indicators,
is_robustness_weights, mc_runs, num_indicators, polar)
return run_mcda_with_indicator_uncertainty(config, input_matrix_no_alternatives, index_column_name,
index_column_values, mc_runs, RANDOM_SEED,
config.sensitivity.sensitivity_on, f_agg, f_norm,
weights, polar, marginal_pdf)


def get_polar_and_weights(config, input_matrix_no_alternatives, is_robustness_indicators, is_robustness_weights,
mc_runs, num_indicators, polar):
# Process indicators and weights based on input parameters in the configuration
polar, weights = process_indicators_and_weights(config, input_matrix_no_alternatives, is_robustness_indicators,
is_robustness_weights, polar, mc_runs, num_indicators)
try:
check_indicator_weights_polarities(num_indicators, polar, config)
except ValueError as e:
logging.error(str(e), stack_info=True)
raise
return polar, weights


def read_matrix_from_file(column_names_list: list[str], file_from_stream) -> {}:
kapil-agnihotri marked this conversation as resolved.
Show resolved Hide resolved
result_dict = utils_for_main.read_matrix_from_file(file_from_stream).to_dict()
if len(column_names_list) != len(result_dict.keys()):
return {"error": "Number of provided column names does not match the CSV columns"}, 400
return {col: result_dict[col] for col in column_names_list if col in result_dict}


if __name__ == '__main__':
Flaminietta marked this conversation as resolved.
Show resolved Hide resolved
'''
Entry point to run code with ranking-service input configuration
command to run
> python3 -m ProMCDA.mcda.mcda_ranking_run -c ProMCDA/input_files/toy_example/car-data-input-sample-from-ranking-service.json
'''
t = time.time()
config_path = parse_args()
input_config = get_config(config_path)
main(input_config)
elapsed = time.time() - t
logger.info("All calculations finished in seconds {}".format(elapsed))
Loading
Loading