mirror of
https://github.com/13hannes11/bachelor_thesis_m.recommend.git
synced 2024-09-04 01:11:00 +02:00
add recommender to repository
This commit is contained in:
133
.gitignore
vendored
Normal file
133
.gitignore
vendored
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
*.pdf
|
||||||
|
|
||||||
|
out/
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
pip-wheel-metadata/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
.python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
32
.vscode/launch.json
vendored
Normal file
32
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python: app.py",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/src/app.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {"PYTHONPATH": "${workspaceRoot}/src/"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: evaluation",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/eval.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {"PYTHONPATH": "${workspaceRoot}/"}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Python: visualization",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/vis.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"env": {"PYTHONPATH": "${workspaceRoot}/"}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
1
data_gen/gen.bat
Normal file
1
data_gen/gen.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python gen_diagram.py
|
||||||
115
data_gen/gen_diagram.py
Normal file
115
data_gen/gen_diagram.py
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
|
||||||
|
def setAxLinesBW(ax):
|
||||||
|
"""
|
||||||
|
Take each Line2D in the axes, ax, and convert the line style to be
|
||||||
|
suitable for black and white viewing.
|
||||||
|
"""
|
||||||
|
MARKERSIZE = 3
|
||||||
|
|
||||||
|
COLORMAP = {
|
||||||
|
'#ff7f0e': {'marker': None, 'dash': [3,4]},
|
||||||
|
'#1f77b4': {'marker': None, 'dash': [1,1]},
|
||||||
|
'#2ca02c': {'marker': None, 'dash': (None,None)}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lines_to_adjust = ax.get_lines()
|
||||||
|
try:
|
||||||
|
lines_to_adjust += ax.get_legend().get_lines()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for line in lines_to_adjust:
|
||||||
|
origColor = line.get_color()
|
||||||
|
line.set_color('black')
|
||||||
|
line.set_dashes(COLORMAP[origColor]['dash'])
|
||||||
|
line.set_marker(COLORMAP[origColor]['marker'])
|
||||||
|
line.set_markersize(MARKERSIZE)
|
||||||
|
|
||||||
|
def setFigLinesBW(fig):
|
||||||
|
"""
|
||||||
|
Take each axes in the figure, and for each line in the axes, make the
|
||||||
|
line viewable in black and white.
|
||||||
|
"""
|
||||||
|
for ax in fig.get_axes():
|
||||||
|
setAxLinesBW(ax)
|
||||||
|
|
||||||
|
def load_data_frame(path):
|
||||||
|
frame = pd.read_csv(path, index_col=0).T
|
||||||
|
frame.index = frame.index.astype(int)
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def new_fig(subplot_row=1, subplot_column=2, dpi=300, title="Untitled"):
|
||||||
|
figure, axes = plt.subplots(subplot_row, subplot_column, sharey=True)
|
||||||
|
#figure.tight_layout(pad=1.5)
|
||||||
|
for axis in axes:
|
||||||
|
axis.tick_params(
|
||||||
|
axis="y", # both major and minor ticks are affected
|
||||||
|
left=True,
|
||||||
|
labelleft=True,
|
||||||
|
labelright=True)
|
||||||
|
figure.canvas.set_window_title(title)
|
||||||
|
figure.dpi = dpi
|
||||||
|
figure.set_figwidth(4 * subplot_column)
|
||||||
|
figure.set_figheight(4 * subplot_row)
|
||||||
|
|
||||||
|
return axes
|
||||||
|
|
||||||
|
|
||||||
|
happy_dictator = load_data_frame("./{}".format("happy_dictator.csv"))
|
||||||
|
unhappy_dictator = load_data_frame("./{}".format("unhappy_dictator.csv"))
|
||||||
|
|
||||||
|
axes = new_fig()
|
||||||
|
|
||||||
|
axes[0].set_title("satisfaction")
|
||||||
|
#axes[0].set_xlim(x_lim)
|
||||||
|
axes[0].set_xlabel("tc")
|
||||||
|
axes[0].set_ylabel("number of people")
|
||||||
|
|
||||||
|
happy_dictator.plot(ax=axes[0])
|
||||||
|
|
||||||
|
setAxLinesBW(axes[0])
|
||||||
|
|
||||||
|
axes[1].set_title("dissatisfaction")
|
||||||
|
axes[1].set_xlabel("tc")
|
||||||
|
axes[1].set_ylabel("number of people")
|
||||||
|
#axes[1].set_xlim(x_lim)
|
||||||
|
unhappy_dictator.plot(ax=axes[1])
|
||||||
|
|
||||||
|
setAxLinesBW(axes[1])
|
||||||
|
|
||||||
|
plt.savefig("./dictator.pdf",format="pdf")
|
||||||
|
|
||||||
|
|
||||||
|
happy_change = load_data_frame("./{}".format("happy_change.csv"))
|
||||||
|
unhappy_change = load_data_frame("./{}".format("unhappy_change.csv"))
|
||||||
|
|
||||||
|
axes = new_fig()
|
||||||
|
|
||||||
|
axes[0].set_title("satisfaction change")
|
||||||
|
#axes[0].set_xlim(x_lim)
|
||||||
|
axes[0].set_xlabel("tc")
|
||||||
|
axes[0].set_ylabel("number of people")
|
||||||
|
|
||||||
|
happy_change.plot(ax=axes[0])
|
||||||
|
|
||||||
|
setAxLinesBW(axes[0])
|
||||||
|
|
||||||
|
axes[1].set_title("dissatisfaction change")
|
||||||
|
axes[1].set_xlabel("tc")
|
||||||
|
axes[1].set_ylabel("number of people")
|
||||||
|
#axes[1].set_xlim(x_lim)
|
||||||
|
unhappy_change.plot(ax=axes[1])
|
||||||
|
|
||||||
|
setAxLinesBW(axes[1])
|
||||||
|
|
||||||
|
plt.savefig("./change.pdf",format="pdf")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
plt.show()
|
||||||
|
plt.close()
|
||||||
4
data_gen/happy_change.csv
Normal file
4
data_gen/happy_change.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
,10,20,30,40,50,60,63,65,67,70,73,75,77,80,81,82,83,84,85,87,90,91,92,93,94
|
||||||
|
heterogenous,0.079,0.166,0.256,0.232,0.375,0.631,0.625,0.605,0.561,0.501,0.427,0.351,0.255,0.153,0.083,0.062,-0.002,-0.048,-0.14,-0.282,-0.442,-0.568,-0.633,-0.802,-0.895
|
||||||
|
random,0.01,0.05,0.099,0.153,0.266,0.533,0.611,0.668,0.716,0.773,0.831,0.858,0.869,0.853,0.848,0.83,0.804,0.769,0.737,0.643,0.405,0.243,0.137,-0.155,-0.337
|
||||||
|
homogenous,0,0,0,0,0,0,0,0,0,0,0.004,0.005,0.006,0.011,0.011,0.014,0.026,0.031,0.048,0.078,0.164,0.21,0.267,0.303,0.271
|
||||||
|
4
data_gen/happy_dictator.csv
Normal file
4
data_gen/happy_dictator.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
,10,20,30,40,50,60,63,65,67,70,73,75,77,80,81,82,83,84,85,87,90,91,92,93,94
|
||||||
|
heterogenous,3.7541875,3.805,3.654,3.594,3.343,2.807,2.683,2.61,2.535,2.387,2.263,2.192,2.136,2.014,1.948,1.916,1.837,1.814,1.746,1.638,1.448,1.352,1.312,1.235,1.186
|
||||||
|
random,3.989,3.945,3.878,3.8,3.655,3.262,3.138,3.041,2.938,2.781,2.618,2.51,2.404,2.229,2.141,2.102,2.024,1.992,1.88,1.727,1.513,1.396,1.324,1.183,1.121
|
||||||
|
homogenous,4,4,4,4,4,4,4,4,4,4,3.996,3.995,3.994,3.989,3.988,3.985,3.971,3.963,3.944,3.905,3.756,3.596,3.443,3.025,2.674
|
||||||
|
4
data_gen/unhappy_change.csv
Normal file
4
data_gen/unhappy_change.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
,10,20,30,40,50,60,63,65,67,70,73,75,77,80,81,82,83,84,85,87,90,91,92,93,94
|
||||||
|
heterogenous,0,-0.079,-0.166,-0.256,-0.232,-0.375,-0.417,-0.474,-0.589,-0.631,-0.625,-0.605,-0.561,-0.501,-0.501,-0.464,-0.427,-0.377,-0.351,-0.255,-0.153,-0.083,-0.062,0.002,0.048
|
||||||
|
random,0,-0.01,-0.05,-0.099,-0.153,-0.266,-0.336,-0.377,-0.441,-0.533,-0.611,-0.668,-0.716,-0.773,-0.783,-0.818,-0.831,-0.841,-0.858,-0.869,-0.853,-0.848,-0.83,-0.804,-0.769
|
||||||
|
homogenous,0,0,0,0,0,0,0,0,0,0,0,0,0,-0.002,-0.002,-0.003,-0.004,-0.004,-0.005,-0.006,-0.011,-0.011,-0.014,-0.026,-0.031
|
||||||
|
4
data_gen/unhappy_dictator.csv
Normal file
4
data_gen/unhappy_dictator.csv
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
,10,20,30,40,50,60,63,65,67,70,73,75,77,80,81,82,83,84,85,87,90,91,92,93,94
|
||||||
|
heterogenous,0,0.081,0.195,0.346,0.406,0.657,0.762,0.859,1.052,1.193,1.317,1.39,1.465,1.613,1.65,1.71,1.737,1.793,1.808,1.864,1.986,2.052,2.084,2.163,2.186
|
||||||
|
random,0,0.011,0.055,0.122,0.2,0.345,0.448,0.513,0.604,0.738,0.862,0.959,1.062,1.219,1.253,1.34,1.382,1.451,1.49,1.596,1.771,1.859,1.898,1.976,2.008
|
||||||
|
homogenous,0,0,0,0,0,0,0,0,0,0,0,0,0,0.002,0.002,0.003,0.004,0.004,0.005,0.006,0.011,0.012,0.015,0.029,0.037
|
||||||
|
426
eval.py
Normal file
426
eval.py
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import datetime
|
||||||
|
import pandas as pd
|
||||||
|
import multiprocessing
|
||||||
|
import itertools
|
||||||
|
|
||||||
|
sys.path.append("./src")
|
||||||
|
sys.path.append("./evaluation")
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from managers.recommendation_manager import SimpleConfigurationMaxSelector
|
||||||
|
from scoring.scoring_functions import ReduceScoringFunctionFactory
|
||||||
|
from user_type_mappings import TYPE_ATHLETE, TYPE_CONSUMER, TYPE_ENVIRONMENTALIST, TYPE_OWNER, TYPE_RANDOM
|
||||||
|
import operator
|
||||||
|
import time
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as pp
|
||||||
|
import random
|
||||||
|
import math
|
||||||
|
import json
|
||||||
|
with open('./evaluation/product_structure.json') as json_file:
|
||||||
|
data = json.load(json_file)
|
||||||
|
product_structure = ProductStructureModel(data)
|
||||||
|
|
||||||
|
from tinydb import TinyDB
|
||||||
|
|
||||||
|
|
||||||
|
def DB():
|
||||||
|
return TinyDB('eval.json')
|
||||||
|
|
||||||
|
def DB_CONFIG():
|
||||||
|
return DB().table('CONFIG')
|
||||||
|
|
||||||
|
def DB_PRODUCT_STRUCTURE():
|
||||||
|
return DB().table('PRODUCT_STRUCTURE')
|
||||||
|
|
||||||
|
CONFIGURATIONS_UNFINISHED = []
|
||||||
|
PREFERENCES_RANDOM_MEMBER = []
|
||||||
|
PREFERENCES_ALL = []
|
||||||
|
|
||||||
|
def generate_group_preferences(user_type_mappings, amount = 1000):
|
||||||
|
global PREFERENCES_RANDOM_MEMBER
|
||||||
|
global PREFERENCES_ALL
|
||||||
|
|
||||||
|
characteristics = product_structure.get_list_of_characteristics()
|
||||||
|
|
||||||
|
PREFERENCES_ALL = []
|
||||||
|
PREFERENCES_RANDOM_MEMBER = []
|
||||||
|
for i in range(amount):
|
||||||
|
users = []
|
||||||
|
single_user = []
|
||||||
|
counter = random.randint(0, len(user_type_mappings) - 1)
|
||||||
|
for mapping in user_type_mappings:
|
||||||
|
ratings = []
|
||||||
|
for char in characteristics:
|
||||||
|
value = mapping[char.elementId].generateNumber()
|
||||||
|
ratings.append({
|
||||||
|
"code": char.elementId,
|
||||||
|
"value": value,
|
||||||
|
})
|
||||||
|
user = {
|
||||||
|
"user": mapping['name'],
|
||||||
|
"ratings": ratings,
|
||||||
|
}
|
||||||
|
users.append(user)
|
||||||
|
if counter == 0:
|
||||||
|
single_user.append(user)
|
||||||
|
counter -= 1
|
||||||
|
|
||||||
|
PREFERENCES_ALL.append( Preferences({'preferences' : users}) )
|
||||||
|
PREFERENCES_RANDOM_MEMBER.append( Preferences({'preferences' : single_user}) )
|
||||||
|
return PREFERENCES_ALL
|
||||||
|
|
||||||
|
def generate_unfinished_configurations(fullness=0.3, amount=1000):
|
||||||
|
configurations = TinyDB('./evaluation/eval.json').table('CONFIG').all()
|
||||||
|
global CONFIGURATIONS_UNFINISHED
|
||||||
|
|
||||||
|
characteristics = list(map(lambda x: x.elementId,ProductStructureModel(data).get_list_of_characteristics()))
|
||||||
|
|
||||||
|
CONFIGURATIONS_UNFINISHED = []
|
||||||
|
for i in range(amount):
|
||||||
|
final_config = configurations[random.randint(0, len(configurations) - 1)]
|
||||||
|
codes = list(filter(lambda x: x in characteristics, final_config['configuration']))
|
||||||
|
conf_size = math.ceil(len(codes) * fullness)
|
||||||
|
|
||||||
|
unfishied_config = random.sample(codes, conf_size)
|
||||||
|
|
||||||
|
CONFIGURATIONS_UNFINISHED.append(ConfigurationModel({
|
||||||
|
"configuration": unfishied_config,
|
||||||
|
"variables": []
|
||||||
|
}))
|
||||||
|
return CONFIGURATIONS_UNFINISHED
|
||||||
|
|
||||||
|
def get_ratings(requests, finished_configurations, product_structure, scoring_function=None):
|
||||||
|
if scoring_function == None :
|
||||||
|
scoring_function = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
["penalty_ratio", "pref_product_simpleSelectedCharacterstics_average"],
|
||||||
|
#["pref_average_flat"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul
|
||||||
|
)
|
||||||
|
|
||||||
|
list_ofScoreLists = []
|
||||||
|
for (preference, config) in requests:
|
||||||
|
list_ofScoreLists.append(list(map(lambda to_rate: scoring_function.calc_score(config, preference, to_rate), finished_configurations)))
|
||||||
|
return list_ofScoreLists
|
||||||
|
|
||||||
|
def plot_at_y(arr, val):
|
||||||
|
pp.plot(arr, np.zeros_like(arr) + val, 'x')
|
||||||
|
|
||||||
|
def get_scores_for_one(configurationState, preference, finished_configurations, product_structure, scoring_function=None):
|
||||||
|
if scoring_function == None:
|
||||||
|
scoring_function = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
["penalty_ratio", "pref_product_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul
|
||||||
|
)
|
||||||
|
return list(map(lambda to_rate: scoring_function.calc_score(configurationState, preference, to_rate), finished_configurations))
|
||||||
|
|
||||||
|
def get_scoring_functions():
|
||||||
|
product = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
["penalty_ratio", "pref_product_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul)
|
||||||
|
|
||||||
|
misery = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
["penalty_ratio", "pref_min_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul)
|
||||||
|
|
||||||
|
average = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
["penalty_ratio", "pref_average_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul)
|
||||||
|
|
||||||
|
return [("multiplication",product), ("least misery", misery), ("best average", average)]
|
||||||
|
|
||||||
|
def main(amount=1000, fullness=0.1, center=50, threshold_distance_from_centre = 0, group_type='heterogeneous', outdir="./out"):
|
||||||
|
global CONFIGURATIONS_UNFINISHED
|
||||||
|
global PREFERENCES_RANDOM_MEMBER
|
||||||
|
global PREFERENCES_ALL
|
||||||
|
print("Started Evaluation")
|
||||||
|
|
||||||
|
if group_type == 'homogenous':
|
||||||
|
group_type_mappings = [TYPE_OWNER, TYPE_OWNER, TYPE_OWNER, TYPE_OWNER]
|
||||||
|
elif group_type == 'random':
|
||||||
|
group_type_mappings = [TYPE_RANDOM, TYPE_RANDOM, TYPE_RANDOM, TYPE_RANDOM]
|
||||||
|
else:
|
||||||
|
group_type='heterogeneous'
|
||||||
|
group_type_mappings = [TYPE_ATHLETE, TYPE_CONSUMER, TYPE_ENVIRONMENTALIST, TYPE_OWNER]
|
||||||
|
|
||||||
|
settings = "amount-{}__center-{}__tdistance-{}__fullness-{}__group-{}".format(amount, center, threshold_distance_from_centre, fullness, group_type)
|
||||||
|
outdir += "/{}__{}".format(datetime.datetime.utcnow().strftime("%Y_%m_%d_T%H-%M-%S%z"), settings)
|
||||||
|
|
||||||
|
# check the directory does not exist
|
||||||
|
if not(os.path.exists(outdir)):
|
||||||
|
# create the directory you want to save to
|
||||||
|
os.mkdir(outdir)
|
||||||
|
if not(os.path.exists("{}/data".format(outdir))):
|
||||||
|
os.mkdir("{}/data".format(outdir))
|
||||||
|
if not(os.path.exists("{}/fig".format(outdir))):
|
||||||
|
os.mkdir("{}/fig".format(outdir))
|
||||||
|
|
||||||
|
random.seed(10924892319)
|
||||||
|
np.random.seed(seed=956109142)
|
||||||
|
|
||||||
|
start_total = start = time.time()
|
||||||
|
|
||||||
|
# Generating preferences and unfinished configurations
|
||||||
|
generate_group_preferences(group_type_mappings, amount=amount)
|
||||||
|
generate_unfinished_configurations(fullness=0.1, amount = amount)
|
||||||
|
|
||||||
|
requests_random_member = list(zip(PREFERENCES_RANDOM_MEMBER, CONFIGURATIONS_UNFINISHED))
|
||||||
|
requests_all = list(zip(PREFERENCES_ALL, CONFIGURATIONS_UNFINISHED))
|
||||||
|
end = time.time()
|
||||||
|
print("Done generating data! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
finished_configurations = list(map(lambda x: ConfigurationModel(x), TinyDB('./evaluation/eval.json').table('CONFIG').all()))
|
||||||
|
random.shuffle(finished_configurations)
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
print("Done loading finished configurations! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
scoring_function_list = get_scoring_functions()
|
||||||
|
|
||||||
|
results_happiness_db_size_avg_diff = []
|
||||||
|
results_unhappiness_db_size_avg_diff = []
|
||||||
|
|
||||||
|
results_happiness_db_size_avg_total_all = []
|
||||||
|
results_unhappiness_db_size_avg_total_all = []
|
||||||
|
|
||||||
|
piece_counts = [16, 8, 4, 2, 1]
|
||||||
|
scoring_function_labels = list(map(lambda x: x[0], scoring_function_list))
|
||||||
|
db_sizes_label = list(map(lambda x: len(finished_configurations) // x, piece_counts))
|
||||||
|
|
||||||
|
for label, scoring_function in scoring_function_list:
|
||||||
|
print("!!! Starting evaluation of: {} !!!".format(label))
|
||||||
|
|
||||||
|
# Rate configurations
|
||||||
|
start = time.time()
|
||||||
|
np_scores_random = np.array(get_ratings(requests_random_member,finished_configurations,product_structure, scoring_function=scoring_function))
|
||||||
|
np_scores_all = np.array(get_ratings(requests_all,finished_configurations,product_structure, scoring_function=scoring_function))
|
||||||
|
end = time.time()
|
||||||
|
print("Done rating stored configurations! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
happiness_db_size_avg_diff = []
|
||||||
|
unhappiness_db_size_avg_diff = []
|
||||||
|
happiness_db_size_avg_total_all = []
|
||||||
|
unhappiness_db_size_avg_total_all = []
|
||||||
|
|
||||||
|
happiness_db_size_stdd = []
|
||||||
|
unhappiness_db_size_stdd = []
|
||||||
|
|
||||||
|
for piece_count in piece_counts:
|
||||||
|
|
||||||
|
happiness_diff_list = []
|
||||||
|
unhappiness_diff_list = []
|
||||||
|
happiness_all_list = []
|
||||||
|
unhappiness_all_list = []
|
||||||
|
|
||||||
|
step_size = len(finished_configurations) // piece_count
|
||||||
|
residual = len(finished_configurations) % piece_count
|
||||||
|
|
||||||
|
for run_count in range(piece_count):
|
||||||
|
print("Starting run {} of {} with {} as store size.".format(run_count, (piece_count - 1) ,step_size))
|
||||||
|
offset_start = 0
|
||||||
|
offset_end = 0
|
||||||
|
if residual > 0:
|
||||||
|
residual -= 1
|
||||||
|
offset_end = 1
|
||||||
|
|
||||||
|
start_pos = run_count * step_size + offset_start
|
||||||
|
end_pos = (run_count + 1) * step_size + offset_start + offset_end
|
||||||
|
|
||||||
|
offset_start += offset_end
|
||||||
|
|
||||||
|
|
||||||
|
start = time.time()
|
||||||
|
|
||||||
|
# Filtering data
|
||||||
|
|
||||||
|
modifier_random = np.zeros(np_scores_random.shape)
|
||||||
|
modifier_all = np.zeros(np_scores_all.shape)
|
||||||
|
|
||||||
|
modifier_random[:,start_pos:end_pos] += 1
|
||||||
|
modifier_all[:,start_pos:end_pos] += 1
|
||||||
|
|
||||||
|
#np_scores_modified_random = np.multiply(np_scores_random[:], modifier_random)
|
||||||
|
np_scores_modified_random = np_scores_random[:]
|
||||||
|
np_scores_modified_all = np.multiply(np_scores_all[:], modifier_all)
|
||||||
|
|
||||||
|
index_max_random = np.argmax(np_scores_modified_random, axis=1)
|
||||||
|
index_max_all = np.argmax(np_scores_modified_all, axis=1)
|
||||||
|
|
||||||
|
|
||||||
|
end = time.time()
|
||||||
|
print("Done getting recommendations! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
# Generate individual scores
|
||||||
|
start = time.time()
|
||||||
|
scores_individual = [[[] for i in range(len(group_type_mappings))] for i in range(amount)]
|
||||||
|
j = 0
|
||||||
|
for preference, configurationState in requests_all:
|
||||||
|
individuals = preference.getIndividualPreferences()
|
||||||
|
i = 0
|
||||||
|
for individual in individuals:
|
||||||
|
scores_individual[j][i] = get_scores_for_one(configurationState, individual, finished_configurations, product_structure, scoring_function=scoring_function)
|
||||||
|
i += 1
|
||||||
|
j += 1
|
||||||
|
end = time.time()
|
||||||
|
print("Done generating individual scores! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
|
||||||
|
#Generate hapiness level
|
||||||
|
start = time.time()
|
||||||
|
avg_happy_diff = 0
|
||||||
|
avg_unhappy_diff = 0
|
||||||
|
avg_happy_all = 0
|
||||||
|
avg_unhappy_all = 0
|
||||||
|
|
||||||
|
individual_index = 0
|
||||||
|
for individuals_scores in scores_individual:
|
||||||
|
unhappy_rand = 0
|
||||||
|
unhappy_all = 0
|
||||||
|
happy_rand = 0
|
||||||
|
happy_all = 0
|
||||||
|
|
||||||
|
for individual_score in individuals_scores:
|
||||||
|
np_individual_score = np.array(individual_score)
|
||||||
|
unhappy_threshold = np.percentile(np_individual_score, center - threshold_distance_from_centre)
|
||||||
|
happy_threshold = np.percentile(np_individual_score, center + threshold_distance_from_centre)
|
||||||
|
|
||||||
|
score_rand = np_individual_score[index_max_random[individual_index]]
|
||||||
|
score_all = np_individual_score[index_max_all[individual_index]]
|
||||||
|
|
||||||
|
if score_all > happy_threshold:
|
||||||
|
happy_all += 1
|
||||||
|
elif score_all < unhappy_threshold:
|
||||||
|
unhappy_all += 1
|
||||||
|
if score_rand > happy_threshold:
|
||||||
|
happy_rand += 1
|
||||||
|
elif score_rand < unhappy_threshold:
|
||||||
|
unhappy_rand += 1
|
||||||
|
avg_happy_diff += happy_all - happy_rand
|
||||||
|
avg_unhappy_diff += unhappy_all - unhappy_rand
|
||||||
|
avg_happy_all += happy_all
|
||||||
|
avg_unhappy_all += unhappy_all
|
||||||
|
|
||||||
|
individual_index += 1
|
||||||
|
|
||||||
|
avg_happy_diff /= amount
|
||||||
|
avg_unhappy_diff /= amount
|
||||||
|
avg_happy_all /= amount
|
||||||
|
avg_unhappy_all /= amount
|
||||||
|
|
||||||
|
happiness_diff_list.append(avg_happy_diff)
|
||||||
|
unhappiness_diff_list.append(avg_unhappy_diff)
|
||||||
|
|
||||||
|
happiness_all_list.append(avg_happy_all)
|
||||||
|
unhappiness_all_list.append(avg_unhappy_all)
|
||||||
|
|
||||||
|
print("-- Average increase in happiness: {} | Average increase in unhappiness: {}".format(avg_happy_diff, avg_unhappy_diff))
|
||||||
|
print("-- Average happiness: {} | Average unhappiness: {}".format(avg_happy_all, avg_unhappy_all))
|
||||||
|
end = time.time()
|
||||||
|
print("Done rating recommendations! It took: {} seconds".format(end - start))
|
||||||
|
|
||||||
|
happiness_db_size_avg_diff.append(np.average(np.array(happiness_diff_list)))
|
||||||
|
unhappiness_db_size_avg_diff.append(np.average(np.array(unhappiness_diff_list)))
|
||||||
|
|
||||||
|
happiness_db_size_avg_total_all.append(np.average(np.array(happiness_all_list)))
|
||||||
|
unhappiness_db_size_avg_total_all.append(np.average(np.array(unhappiness_all_list)))
|
||||||
|
|
||||||
|
results_happiness_db_size_avg_diff.append(happiness_db_size_avg_diff)
|
||||||
|
results_unhappiness_db_size_avg_diff.append(unhappiness_db_size_avg_diff)
|
||||||
|
|
||||||
|
results_happiness_db_size_avg_total_all.append(happiness_db_size_avg_total_all)
|
||||||
|
results_unhappiness_db_size_avg_total_all.append(unhappiness_db_size_avg_total_all)
|
||||||
|
|
||||||
|
column_names = db_sizes_label
|
||||||
|
row_names = scoring_function_labels
|
||||||
|
pd.DataFrame(results_happiness_db_size_avg_diff, index=row_names, columns=column_names).to_csv("{}/data/_happy_increase.csv".format(outdir), index=True, header=True, sep=',')
|
||||||
|
pd.DataFrame(results_unhappiness_db_size_avg_diff, index=row_names, columns=column_names).to_csv("{}/data/_unhappy_increase.csv".format(outdir).format(outdir), index=True, header=True, sep=',')
|
||||||
|
pd.DataFrame(results_happiness_db_size_avg_total_all, index=row_names, columns=column_names).to_csv("{}/data/_happy_total_all.csv".format(outdir), index=True, header=True, sep=',')
|
||||||
|
pd.DataFrame(results_unhappiness_db_size_avg_total_all, index=row_names, columns=column_names).to_csv("{}/data/_unhappy_total_all.csv".format(outdir).format(outdir), index=True, header=True, sep=',')
|
||||||
|
|
||||||
|
|
||||||
|
end_total = time.time()
|
||||||
|
print("Done! Total time: {} seconds".format(end_total - start_total))
|
||||||
|
|
||||||
|
axis=[0,150, -1, 0.5]
|
||||||
|
pp.figure(figsize=(8,4), dpi=300)
|
||||||
|
pp.subplots_adjust(hspace = 0.8, wspace=0.4)
|
||||||
|
pp.subplot(1, 2, 1, title="happiness increase average", )
|
||||||
|
|
||||||
|
for result_happy in results_happiness_db_size_avg_diff:
|
||||||
|
pp.plot(db_sizes_label, result_happy)
|
||||||
|
|
||||||
|
pp.legend(scoring_function_labels)
|
||||||
|
pp.xlabel("number of stored configurations")
|
||||||
|
pp.ylabel("number of people")
|
||||||
|
pp.axis(axis)
|
||||||
|
|
||||||
|
pp.subplot(1, 2, 2, title="unhappiness increase average")
|
||||||
|
|
||||||
|
|
||||||
|
for result_unhappy in results_unhappiness_db_size_avg_diff:
|
||||||
|
pp.plot(db_sizes_label, result_unhappy)
|
||||||
|
|
||||||
|
pp.legend(scoring_function_labels)
|
||||||
|
pp.xlabel("number of stored configurations")
|
||||||
|
pp.ylabel("number of people")
|
||||||
|
pp.axis(axis)
|
||||||
|
|
||||||
|
pp.savefig("{}/fig/happy_unhappy_increase.pdf".format(outdir),format="pdf")
|
||||||
|
pp.figure(figsize=(8,4), dpi=300)
|
||||||
|
|
||||||
|
|
||||||
|
axis=[0,150, 0, 4]
|
||||||
|
pp.subplots_adjust(hspace = 0.8, wspace=0.4)
|
||||||
|
pp.subplot(1, 2, 1, title="happiness absolute average", )
|
||||||
|
|
||||||
|
for result_happy in results_happiness_db_size_avg_total_all:
|
||||||
|
pp.plot(db_sizes_label, result_happy)
|
||||||
|
|
||||||
|
pp.legend(scoring_function_labels)
|
||||||
|
pp.xlabel("number of stored configurations")
|
||||||
|
pp.ylabel("number of people")
|
||||||
|
pp.axis(axis)
|
||||||
|
|
||||||
|
pp.subplot(1, 2, 2, title="unhappiness absolute average")
|
||||||
|
|
||||||
|
|
||||||
|
for result_unhappy in results_unhappiness_db_size_avg_total_all:
|
||||||
|
pp.plot(db_sizes_label, result_unhappy)
|
||||||
|
|
||||||
|
pp.legend(scoring_function_labels)
|
||||||
|
pp.xlabel("number of stored configurations")
|
||||||
|
pp.ylabel("number of people")
|
||||||
|
pp.axis(axis)
|
||||||
|
|
||||||
|
pp.savefig("{}/fig/happy_unhappy_total_all.pdf".format(outdir),format="pdf")
|
||||||
|
|
||||||
|
def main_tuple(param):
|
||||||
|
print("----------------------------------------------------------------------------------------")
|
||||||
|
print("----------------------Starting: {}----------------------".format(param))
|
||||||
|
print("----------------------------------------------------------------------------------------")
|
||||||
|
main(amount=param[0], fullness=param[1],center=param[2] ,threshold_distance_from_centre = param[3], group_type= param[4])
|
||||||
|
return True
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
num_cores = multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
amounts = [1]
|
||||||
|
fullnesses = [0.1]
|
||||||
|
centers = [10, 20, 30, 40, 50, 60, 70, 80, 90]
|
||||||
|
dists = [5]
|
||||||
|
g_types = ["heterogeneous", "random", "homogenous"]
|
||||||
|
|
||||||
|
params = list(itertools.product(amounts, fullnesses, centers, dists, g_types))
|
||||||
|
|
||||||
|
pool = multiprocessing.Pool(processes=num_cores)
|
||||||
|
res = pool.map(main_tuple, params)
|
||||||
|
|
||||||
138
evaluation/combinations.py
Normal file
138
evaluation/combinations.py
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
from enum import Enum
|
||||||
|
class HEIMISCH(Enum):
|
||||||
|
NIEDRIG = "62bcc15f-5f39-4239-963a-455498c34f79"
|
||||||
|
MITTEL = "066372f5-9b21-46a3-a62a-76be0afd8f4e"
|
||||||
|
HOCH = "2e94ef17-7ea1-42e0-b070-bba3a8debfd8"
|
||||||
|
|
||||||
|
class KLIMA(Enum):
|
||||||
|
NIEDRIG = "1a02a295-5afd-427a-bf1e-2b8065687380"
|
||||||
|
MITTEL = "6f3a5204-1276-40f9-84dc-c8e139e5402d"
|
||||||
|
HOCH = "5bd172ba-0076-456e-9ee2-0b81780a5da0"
|
||||||
|
|
||||||
|
class VERWERTBAR(Enum):
|
||||||
|
NIEDRIG = "79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8"
|
||||||
|
MITTEL = "b17eceb1-1cd5-4c11-b398-c6834e4e2ed1"
|
||||||
|
HOCH = "3fb4ae3d-c892-4025-815f-6b6074ac3d3c"
|
||||||
|
|
||||||
|
class AUFWAND(Enum):
|
||||||
|
NIEDRIG = "a755ba0d-fb8d-475a-a0f9-5ca267fd479f"
|
||||||
|
MITTEL = "653b4832-6647-426d-a18e-ee444ba67979"
|
||||||
|
HOCH = "dcb34d08-06f2-4426-a3a9-039cec1e6f6d"
|
||||||
|
|
||||||
|
class MENGE(Enum):
|
||||||
|
NIEDRIG = "9e70ea9a-1311-48db-9238-cbc98da1ed2b"
|
||||||
|
MITTEL = "5de4ee9e-14a5-4b50-aa36-6efc39cdc24c"
|
||||||
|
HOCH = "09594573-6fc5-4c62-9d5c-84b4ccf817a1"
|
||||||
|
|
||||||
|
class PREIS(Enum):
|
||||||
|
NIEDRIG = "8d2f5efe-db35-4b4d-9591-cf797335e3ba"
|
||||||
|
MITTEL = "c7b0f02c-9afe-4ef6-9af0-b7c193b08e93"
|
||||||
|
HOCH = "c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853"
|
||||||
|
|
||||||
|
class ERFAHRUNG(Enum):
|
||||||
|
NIEDRIG = "d59c52cf-0eb1-4ad9-9228-c5100c2b6237"
|
||||||
|
MITTEL = "311389a2-c4b7-4a13-bf5a-04a1befad0e9"
|
||||||
|
HOCH = "f08b7cd1-c470-4d6e-951d-a69a02b04849"
|
||||||
|
|
||||||
|
not_with = [
|
||||||
|
(HEIMISCH.MITTEL, KLIMA.HOCH),
|
||||||
|
(HEIMISCH.MITTEL, VERWERTBAR.HOCH),
|
||||||
|
(HEIMISCH.HOCH, KLIMA.HOCH),
|
||||||
|
(HEIMISCH.HOCH, VERWERTBAR.MITTEL),
|
||||||
|
(HEIMISCH.HOCH, VERWERTBAR.HOCH),
|
||||||
|
(HEIMISCH.HOCH, MENGE.HOCH),
|
||||||
|
(HEIMISCH.HOCH, PREIS.NIEDRIG),
|
||||||
|
|
||||||
|
(KLIMA.MITTEL, VERWERTBAR.HOCH),
|
||||||
|
(KLIMA.HOCH, VERWERTBAR.MITTEL),
|
||||||
|
(KLIMA.HOCH, VERWERTBAR.HOCH),
|
||||||
|
(KLIMA.HOCH, MENGE.HOCH),
|
||||||
|
(KLIMA.HOCH, PREIS.MITTEL),
|
||||||
|
(KLIMA.HOCH, PREIS.NIEDRIG),
|
||||||
|
|
||||||
|
(VERWERTBAR.NIEDRIG, MENGE.HOCH),
|
||||||
|
(VERWERTBAR.NIEDRIG, PREIS.MITTEL),
|
||||||
|
(VERWERTBAR.NIEDRIG, PREIS.NIEDRIG),
|
||||||
|
(VERWERTBAR.HOCH, ERFAHRUNG.HOCH),
|
||||||
|
|
||||||
|
(AUFWAND.NIEDRIG, MENGE.HOCH),
|
||||||
|
(AUFWAND.NIEDRIG, PREIS.MITTEL),
|
||||||
|
(AUFWAND.NIEDRIG, PREIS.NIEDRIG),
|
||||||
|
(AUFWAND.MITTEL, ERFAHRUNG.MITTEL),
|
||||||
|
(AUFWAND.MITTEL, ERFAHRUNG.HOCH),
|
||||||
|
(AUFWAND.HOCH, ERFAHRUNG.MITTEL),
|
||||||
|
(AUFWAND.HOCH, ERFAHRUNG.HOCH),
|
||||||
|
|
||||||
|
(MENGE.NIEDRIG, PREIS.NIEDRIG),
|
||||||
|
(MENGE.NIEDRIG, PREIS.MITTEL),
|
||||||
|
(MENGE.MITTEL, PREIS.NIEDRIG),
|
||||||
|
(MENGE.MITTEL, PREIS.MITTEL),
|
||||||
|
(MENGE.HOCH, ERFAHRUNG.MITTEL),
|
||||||
|
(MENGE.HOCH, ERFAHRUNG.HOCH),
|
||||||
|
|
||||||
|
]
|
||||||
|
counter = 0
|
||||||
|
string = ""
|
||||||
|
|
||||||
|
for heimisch in [HEIMISCH.NIEDRIG, HEIMISCH.MITTEL, HEIMISCH.HOCH]:
|
||||||
|
for klima in [KLIMA.NIEDRIG, KLIMA.MITTEL, KLIMA.HOCH]:
|
||||||
|
for verwertbar in [VERWERTBAR.NIEDRIG, VERWERTBAR.MITTEL, VERWERTBAR.HOCH]:
|
||||||
|
for aufwand in [AUFWAND.NIEDRIG, AUFWAND.MITTEL, AUFWAND.HOCH]:
|
||||||
|
for menge in [MENGE.NIEDRIG, MENGE.MITTEL, MENGE.HOCH]:
|
||||||
|
for preis in [PREIS.NIEDRIG, PREIS.MITTEL, PREIS.HOCH]:
|
||||||
|
for erfahrung in [ERFAHRUNG.NIEDRIG, ERFAHRUNG.MITTEL, ERFAHRUNG.HOCH]:
|
||||||
|
plus = True
|
||||||
|
if (heimisch, klima) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (heimisch, verwertbar) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (heimisch, aufwand) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (heimisch, menge) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (heimisch, preis) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (heimisch, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
|
||||||
|
if (klima, verwertbar) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (klima, aufwand) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (klima, menge) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (klima, preis) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (klima, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
|
||||||
|
if (verwertbar, aufwand) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (verwertbar, menge) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (verwertbar, preis) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (verwertbar, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
|
||||||
|
if (aufwand, menge) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (aufwand, preis) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (aufwand, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
|
||||||
|
if (menge, preis) in not_with:
|
||||||
|
plus = False
|
||||||
|
if (menge, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
|
||||||
|
if (preis, erfahrung) in not_with:
|
||||||
|
plus = False
|
||||||
|
if plus:
|
||||||
|
counter += 1
|
||||||
|
print("{}, {}, {}, {}, {}, {}, {}".format(heimisch, klima, verwertbar, aufwand, menge, preis, erfahrung))
|
||||||
|
string += '"' + str(counter) + '":' + "{ 'configuration': [" + heimisch.value + "," + klima.value + "," + verwertbar.value + "," + aufwand.value + "," + menge.value + "," + preis.value + "," + erfahrung.value + "], 'variables': []},"
|
||||||
|
|
||||||
|
print(counter)
|
||||||
|
#print(string)
|
||||||
1
evaluation/eval.json
Normal file
1
evaluation/eval.json
Normal file
File diff suppressed because one or more lines are too long
3203
evaluation/product_structure.json
Normal file
3203
evaluation/product_structure.json
Normal file
File diff suppressed because it is too large
Load Diff
223
evaluation/user_type_mappings.py
Normal file
223
evaluation/user_type_mappings.py
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
from enum import Enum
|
||||||
|
import numpy as np
|
||||||
|
import random
|
||||||
|
|
||||||
|
class Attitude(Enum):
|
||||||
|
NEUTRAL = (0.5, 0.05)
|
||||||
|
POSITIVE = (0.75, 0.1)
|
||||||
|
NEGATIVE = (0.25, 0.1)
|
||||||
|
|
||||||
|
class NormalDistribution:
|
||||||
|
def __init__(self, attitude : Attitude):
|
||||||
|
self.mean = attitude.value[0]
|
||||||
|
self.std_deviation = attitude.value[1]
|
||||||
|
|
||||||
|
def generateNumber(self):
|
||||||
|
val = np.random.normal(loc=self.mean, scale=self.std_deviation)
|
||||||
|
|
||||||
|
val = val * 2 - 1 #value has to be changed to [-1,1]
|
||||||
|
if val > 1:
|
||||||
|
return 1
|
||||||
|
elif val < -1:
|
||||||
|
return -1
|
||||||
|
else:
|
||||||
|
return val
|
||||||
|
|
||||||
|
class UniformDistribution:
|
||||||
|
def __init__(self, min_val=-1, max_val=1):
|
||||||
|
self.min_val = min_val
|
||||||
|
self.max_val = max_val
|
||||||
|
|
||||||
|
def generateNumber(self):
|
||||||
|
val = random.uniform(self.min_val, self.max_val)
|
||||||
|
return val
|
||||||
|
|
||||||
|
TYPE_RANDOM = {
|
||||||
|
"name": "random",
|
||||||
|
# local trees (3c0b0b79-2b8c-4df9-95b9-4505443b3638)
|
||||||
|
"62bcc15f-5f39-4239-963a-455498c34f79": UniformDistribution(), #Low
|
||||||
|
"066372f5-9b21-46a3-a62a-76be0afd8f4e": UniformDistribution(), #Medium
|
||||||
|
"2e94ef17-7ea1-42e0-b070-bba3a8debfd8": UniformDistribution(), #High
|
||||||
|
|
||||||
|
# climate resilient trees (290951c4-ed76-4d5c-8d26-0ecc8ca42e59)
|
||||||
|
"1a02a295-5afd-427a-bf1e-2b8065687380": UniformDistribution(), #Low
|
||||||
|
"6f3a5204-1276-40f9-84dc-c8e139e5402d": UniformDistribution(), #Medium
|
||||||
|
"5bd172ba-0076-456e-9ee2-0b81780a5da0": UniformDistribution(), #High
|
||||||
|
|
||||||
|
# usable trees (5fee0b16-9ba2-4162-a98b-5c6170ab200e)
|
||||||
|
"79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8": UniformDistribution(), #Low
|
||||||
|
"b17eceb1-1cd5-4c11-b398-c6834e4e2ed1": UniformDistribution(), #Medium
|
||||||
|
"3fb4ae3d-c892-4025-815f-6b6074ac3d3c": UniformDistribution(), #High
|
||||||
|
|
||||||
|
# harvesting effort (8c7bd4d7-b91e-4518-99d4-7564df1d4207)
|
||||||
|
"a755ba0d-fb8d-475a-a0f9-5ca267fd479f": UniformDistribution(), # Manual
|
||||||
|
"653b4832-6647-426d-a18e-ee444ba67979": UniformDistribution(), # Harvester
|
||||||
|
"dcb34d08-06f2-4426-a3a9-039cec1e6f6d": UniformDistribution(), # Self-driving Harvester
|
||||||
|
|
||||||
|
# harvesting amount (c5b446ce-455a-44b5-b8be-178eef2848c2)
|
||||||
|
"9e70ea9a-1311-48db-9238-cbc98da1ed2b": UniformDistribution(), #No harvest
|
||||||
|
"5de4ee9e-14a5-4b50-aa36-6efc39cdc24c": UniformDistribution(), #Low harvest
|
||||||
|
"09594573-6fc5-4c62-9d5c-84b4ccf817a1": UniformDistribution(), #High harvest
|
||||||
|
|
||||||
|
# wood price (3a375746-288f-4147-8065-9f6966389772)
|
||||||
|
"8d2f5efe-db35-4b4d-9591-cf797335e3ba": UniformDistribution(), #Low
|
||||||
|
"c7b0f02c-9afe-4ef6-9af0-b7c193b08e93": UniformDistribution(), #Medium
|
||||||
|
"c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853": UniformDistribution(), #High
|
||||||
|
|
||||||
|
# public accessability (5d8ab41c-02bd-4478-9106-64e80bdb9728)
|
||||||
|
"d59c52cf-0eb1-4ad9-9228-c5100c2b6237": UniformDistribution(), #Almost none
|
||||||
|
"311389a2-c4b7-4a13-bf5a-04a1befad0e9": UniformDistribution(), #Low intensity
|
||||||
|
"f08b7cd1-c470-4d6e-951d-a69a02b04849": UniformDistribution(), #High intensity
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_ATHLETE = {
|
||||||
|
"name": "athlete",
|
||||||
|
# local trees (3c0b0b79-2b8c-4df9-95b9-4505443b3638)
|
||||||
|
"62bcc15f-5f39-4239-963a-455498c34f79": NormalDistribution(Attitude.NEGATIVE), #Low
|
||||||
|
"066372f5-9b21-46a3-a62a-76be0afd8f4e": NormalDistribution(Attitude.POSITIVE), #Medium
|
||||||
|
"2e94ef17-7ea1-42e0-b070-bba3a8debfd8": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# climate resilient trees (290951c4-ed76-4d5c-8d26-0ecc8ca42e59)
|
||||||
|
"1a02a295-5afd-427a-bf1e-2b8065687380": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"6f3a5204-1276-40f9-84dc-c8e139e5402d": NormalDistribution(Attitude.POSITIVE), #Medium
|
||||||
|
"5bd172ba-0076-456e-9ee2-0b81780a5da0": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# usable trees (5fee0b16-9ba2-4162-a98b-5c6170ab200e)
|
||||||
|
"79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"b17eceb1-1cd5-4c11-b398-c6834e4e2ed1": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"3fb4ae3d-c892-4025-815f-6b6074ac3d3c": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# harvesting effort (8c7bd4d7-b91e-4518-99d4-7564df1d4207)
|
||||||
|
"a755ba0d-fb8d-475a-a0f9-5ca267fd479f": NormalDistribution(Attitude.NEUTRAL), # Manual
|
||||||
|
"653b4832-6647-426d-a18e-ee444ba67979": NormalDistribution(Attitude.NEGATIVE), # Harvester
|
||||||
|
"dcb34d08-06f2-4426-a3a9-039cec1e6f6d": NormalDistribution(Attitude.NEGATIVE), # Self-driving Harvester
|
||||||
|
|
||||||
|
# harvesting amount (c5b446ce-455a-44b5-b8be-178eef2848c2)
|
||||||
|
"9e70ea9a-1311-48db-9238-cbc98da1ed2b": NormalDistribution(Attitude.POSITIVE), #No harvest
|
||||||
|
"5de4ee9e-14a5-4b50-aa36-6efc39cdc24c": NormalDistribution(Attitude.NEUTRAL), #Low harvest
|
||||||
|
"09594573-6fc5-4c62-9d5c-84b4ccf817a1": NormalDistribution(Attitude.NEGATIVE), #High harvest
|
||||||
|
|
||||||
|
# wood price (3a375746-288f-4147-8065-9f6966389772)
|
||||||
|
"8d2f5efe-db35-4b4d-9591-cf797335e3ba": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"c7b0f02c-9afe-4ef6-9af0-b7c193b08e93": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853": NormalDistribution(Attitude.NEUTRAL), #High
|
||||||
|
|
||||||
|
# public accessability (5d8ab41c-02bd-4478-9106-64e80bdb9728)
|
||||||
|
"d59c52cf-0eb1-4ad9-9228-c5100c2b6237": NormalDistribution(Attitude.NEGATIVE), #Almost none
|
||||||
|
"311389a2-c4b7-4a13-bf5a-04a1befad0e9": NormalDistribution(Attitude.NEUTRAL), #Low intensity
|
||||||
|
"f08b7cd1-c470-4d6e-951d-a69a02b04849": NormalDistribution(Attitude.POSITIVE), #High intensity
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_OWNER = {
|
||||||
|
"name": "owner",
|
||||||
|
# local trees (3c0b0b79-2b8c-4df9-95b9-4505443b3638)
|
||||||
|
"62bcc15f-5f39-4239-963a-455498c34f79": NormalDistribution(Attitude.POSITIVE), #Low
|
||||||
|
"066372f5-9b21-46a3-a62a-76be0afd8f4e": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"2e94ef17-7ea1-42e0-b070-bba3a8debfd8": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# climate resilient trees (290951c4-ed76-4d5c-8d26-0ecc8ca42e59)
|
||||||
|
"1a02a295-5afd-427a-bf1e-2b8065687380": NormalDistribution(Attitude.POSITIVE), #Low
|
||||||
|
"6f3a5204-1276-40f9-84dc-c8e139e5402d": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"5bd172ba-0076-456e-9ee2-0b81780a5da0": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# usable trees (5fee0b16-9ba2-4162-a98b-5c6170ab200e)
|
||||||
|
"79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"b17eceb1-1cd5-4c11-b398-c6834e4e2ed1": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"3fb4ae3d-c892-4025-815f-6b6074ac3d3c": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# harvesting effort (8c7bd4d7-b91e-4518-99d4-7564df1d4207)
|
||||||
|
"a755ba0d-fb8d-475a-a0f9-5ca267fd479f": NormalDistribution(Attitude.NEUTRAL), # Manual
|
||||||
|
"653b4832-6647-426d-a18e-ee444ba67979": NormalDistribution(Attitude.POSITIVE), # Harvester
|
||||||
|
"dcb34d08-06f2-4426-a3a9-039cec1e6f6d": NormalDistribution(Attitude.POSITIVE), # Self-driving Harvester
|
||||||
|
|
||||||
|
# harvesting amount (c5b446ce-455a-44b5-b8be-178eef2848c2)
|
||||||
|
"9e70ea9a-1311-48db-9238-cbc98da1ed2b": NormalDistribution(Attitude.NEGATIVE), #No harvest
|
||||||
|
"5de4ee9e-14a5-4b50-aa36-6efc39cdc24c": NormalDistribution(Attitude.POSITIVE), #Low harvest
|
||||||
|
"09594573-6fc5-4c62-9d5c-84b4ccf817a1": NormalDistribution(Attitude.POSITIVE), #High harvest
|
||||||
|
|
||||||
|
# wood price (3a375746-288f-4147-8065-9f6966389772)
|
||||||
|
"8d2f5efe-db35-4b4d-9591-cf797335e3ba": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"c7b0f02c-9afe-4ef6-9af0-b7c193b08e93": NormalDistribution(Attitude.POSITIVE), #Medium
|
||||||
|
"c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# public accessability (5d8ab41c-02bd-4478-9106-64e80bdb9728)
|
||||||
|
"d59c52cf-0eb1-4ad9-9228-c5100c2b6237": NormalDistribution(Attitude.POSITIVE), #Almost none
|
||||||
|
"311389a2-c4b7-4a13-bf5a-04a1befad0e9": NormalDistribution(Attitude.NEUTRAL), #Low intensity
|
||||||
|
"f08b7cd1-c470-4d6e-951d-a69a02b04849": NormalDistribution(Attitude.NEGATIVE), #High intensity
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_ENVIRONMENTALIST = {
|
||||||
|
"name": "environmentalist",
|
||||||
|
# local trees (3c0b0b79-2b8c-4df9-95b9-4505443b3638)
|
||||||
|
"62bcc15f-5f39-4239-963a-455498c34f79": NormalDistribution(Attitude.NEGATIVE), #Low
|
||||||
|
"066372f5-9b21-46a3-a62a-76be0afd8f4e": NormalDistribution(Attitude.NEGATIVE), #Medium
|
||||||
|
"2e94ef17-7ea1-42e0-b070-bba3a8debfd8": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# climate resilient trees (290951c4-ed76-4d5c-8d26-0ecc8ca42e59)
|
||||||
|
"1a02a295-5afd-427a-bf1e-2b8065687380": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"6f3a5204-1276-40f9-84dc-c8e139e5402d": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"5bd172ba-0076-456e-9ee2-0b81780a5da0": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# usable trees (5fee0b16-9ba2-4162-a98b-5c6170ab200e)
|
||||||
|
"79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"b17eceb1-1cd5-4c11-b398-c6834e4e2ed1": NormalDistribution(Attitude.NEGATIVE), #Medium
|
||||||
|
"3fb4ae3d-c892-4025-815f-6b6074ac3d3c": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# harvesting effort (8c7bd4d7-b91e-4518-99d4-7564df1d4207)
|
||||||
|
"a755ba0d-fb8d-475a-a0f9-5ca267fd479f": NormalDistribution(Attitude.POSITIVE), # Manual
|
||||||
|
"653b4832-6647-426d-a18e-ee444ba67979": NormalDistribution(Attitude.NEGATIVE), # Harvester
|
||||||
|
"dcb34d08-06f2-4426-a3a9-039cec1e6f6d": NormalDistribution(Attitude.NEGATIVE), # Self-driving Harvester
|
||||||
|
|
||||||
|
# harvesting amount (c5b446ce-455a-44b5-b8be-178eef2848c2)
|
||||||
|
"9e70ea9a-1311-48db-9238-cbc98da1ed2b": NormalDistribution(Attitude.POSITIVE), #No harvest
|
||||||
|
"5de4ee9e-14a5-4b50-aa36-6efc39cdc24c": NormalDistribution(Attitude.NEUTRAL), #Low harvest
|
||||||
|
"09594573-6fc5-4c62-9d5c-84b4ccf817a1": NormalDistribution(Attitude.NEGATIVE), #High harvest
|
||||||
|
|
||||||
|
# wood price (3a375746-288f-4147-8065-9f6966389772)
|
||||||
|
"8d2f5efe-db35-4b4d-9591-cf797335e3ba": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"c7b0f02c-9afe-4ef6-9af0-b7c193b08e93": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853": NormalDistribution(Attitude.NEUTRAL), #High
|
||||||
|
|
||||||
|
# public accessability (5d8ab41c-02bd-4478-9106-64e80bdb9728)
|
||||||
|
"d59c52cf-0eb1-4ad9-9228-c5100c2b6237": NormalDistribution(Attitude.POSITIVE), #Almost none
|
||||||
|
"311389a2-c4b7-4a13-bf5a-04a1befad0e9": NormalDistribution(Attitude.NEUTRAL), #Low intensity
|
||||||
|
"f08b7cd1-c470-4d6e-951d-a69a02b04849": NormalDistribution(Attitude.NEGATIVE), #High intensity
|
||||||
|
}
|
||||||
|
|
||||||
|
TYPE_CONSUMER = {
|
||||||
|
"name": "consumer",
|
||||||
|
# local trees (3c0b0b79-2b8c-4df9-95b9-4505443b3638)
|
||||||
|
"62bcc15f-5f39-4239-963a-455498c34f79": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"066372f5-9b21-46a3-a62a-76be0afd8f4e": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"2e94ef17-7ea1-42e0-b070-bba3a8debfd8": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# climate resilient trees (290951c4-ed76-4d5c-8d26-0ecc8ca42e59)
|
||||||
|
"1a02a295-5afd-427a-bf1e-2b8065687380": NormalDistribution(Attitude.NEUTRAL), #Low
|
||||||
|
"6f3a5204-1276-40f9-84dc-c8e139e5402d": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"5bd172ba-0076-456e-9ee2-0b81780a5da0": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# usable trees (5fee0b16-9ba2-4162-a98b-5c6170ab200e)
|
||||||
|
"79cb7c2f-6b90-4991-8c9c-9a28f1b31cc8": NormalDistribution(Attitude.NEGATIVE), #Low
|
||||||
|
"b17eceb1-1cd5-4c11-b398-c6834e4e2ed1": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"3fb4ae3d-c892-4025-815f-6b6074ac3d3c": NormalDistribution(Attitude.POSITIVE), #High
|
||||||
|
|
||||||
|
# harvesting effort (8c7bd4d7-b91e-4518-99d4-7564df1d4207)
|
||||||
|
"a755ba0d-fb8d-475a-a0f9-5ca267fd479f": NormalDistribution(Attitude.NEGATIVE), # Manual
|
||||||
|
"653b4832-6647-426d-a18e-ee444ba67979": NormalDistribution(Attitude.NEUTRAL), # Harvester
|
||||||
|
"dcb34d08-06f2-4426-a3a9-039cec1e6f6d": NormalDistribution(Attitude.NEUTRAL), # Self-driving Harvester
|
||||||
|
|
||||||
|
# harvesting amount (c5b446ce-455a-44b5-b8be-178eef2848c2)
|
||||||
|
"9e70ea9a-1311-48db-9238-cbc98da1ed2b": NormalDistribution(Attitude.NEGATIVE), #No harvest
|
||||||
|
"5de4ee9e-14a5-4b50-aa36-6efc39cdc24c": NormalDistribution(Attitude.NEGATIVE), #Low harvest
|
||||||
|
"09594573-6fc5-4c62-9d5c-84b4ccf817a1": NormalDistribution(Attitude.POSITIVE), #High harvest
|
||||||
|
|
||||||
|
# wood price (3a375746-288f-4147-8065-9f6966389772)
|
||||||
|
"8d2f5efe-db35-4b4d-9591-cf797335e3ba": NormalDistribution(Attitude.POSITIVE), #Low
|
||||||
|
"c7b0f02c-9afe-4ef6-9af0-b7c193b08e93": NormalDistribution(Attitude.NEUTRAL), #Medium
|
||||||
|
"c6bd1fd3-8f0f-4d7d-aa8a-86ec00cb7853": NormalDistribution(Attitude.NEGATIVE), #High
|
||||||
|
|
||||||
|
# public accessability (5d8ab41c-02bd-4478-9106-64e80bdb9728)
|
||||||
|
"d59c52cf-0eb1-4ad9-9228-c5100c2b6237": NormalDistribution(Attitude.NEUTRAL), #Almost none
|
||||||
|
"311389a2-c4b7-4a13-bf5a-04a1befad0e9": NormalDistribution(Attitude.NEUTRAL), #Low intensity
|
||||||
|
"f08b7cd1-c470-4d6e-951d-a69a02b04849": NormalDistribution(Attitude.NEUTRAL), #High intensity
|
||||||
|
}
|
||||||
4
profiler.py
Normal file
4
profiler.py
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import cProfile
|
||||||
|
import eval
|
||||||
|
|
||||||
|
cProfile.run('eval.main()')
|
||||||
7
requirements.txt
Normal file
7
requirements.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
flask-restplus
|
||||||
|
tinydb
|
||||||
|
tinyrecord
|
||||||
|
numpy
|
||||||
|
pytest
|
||||||
|
coverage
|
||||||
|
pandas
|
||||||
3
run_coverage.bat
Normal file
3
run_coverage.bat
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
coverage run --source "./src" -m pytest
|
||||||
|
coverage report -m
|
||||||
|
PAUSE
|
||||||
1
run_eval.bat
Normal file
1
run_eval.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python ./eval.py
|
||||||
2
run_tests.bat
Normal file
2
run_tests.bat
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pytest
|
||||||
|
PAUSE
|
||||||
1
run_vis.bat
Normal file
1
run_vis.bat
Normal file
@@ -0,0 +1 @@
|
|||||||
|
python ./vis.py
|
||||||
15
src/apis/__init__.py
Normal file
15
src/apis/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from flask_restplus import Api
|
||||||
|
|
||||||
|
from .config import api as config_api
|
||||||
|
from .recommender import api as recommender_api
|
||||||
|
from .product_structure import api as prod_structure_api
|
||||||
|
|
||||||
|
api = Api(
|
||||||
|
title='Configuration Recommendation API',
|
||||||
|
version='1.0',
|
||||||
|
description='A simple recommendation API',
|
||||||
|
)
|
||||||
|
|
||||||
|
api.add_namespace(recommender_api)
|
||||||
|
api.add_namespace(config_api)
|
||||||
|
api.add_namespace(prod_structure_api)
|
||||||
35
src/apis/config.py
Normal file
35
src/apis/config.py
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
from flask_restplus import Namespace, Resource, fields
|
||||||
|
from daos.config_dao import ConfigurationDAO
|
||||||
|
|
||||||
|
api = Namespace('config', description='Configuration related operations')
|
||||||
|
|
||||||
|
variable_model = api.model('config_variable', {
|
||||||
|
'value': fields.Float(required=True, description='The contained variable value'),
|
||||||
|
'code': fields.String(required=True, description='The contained variable code')
|
||||||
|
})
|
||||||
|
|
||||||
|
config_model = api.model('config', {
|
||||||
|
'configuration': fields.List(fields.String(), required=True, description='The contained codes'),
|
||||||
|
'variables': fields.List(fields.Nested(variable_model), required=True, description='The contained variables')
|
||||||
|
})
|
||||||
|
|
||||||
|
@api.route('/')
|
||||||
|
class ConfigList(Resource):
|
||||||
|
|
||||||
|
@api.doc('list_configs')
|
||||||
|
@api.marshal_list_with(config_model)
|
||||||
|
def get(self):
|
||||||
|
'''List all stored configurations'''
|
||||||
|
return ConfigurationDAO.getInstance().getAll()
|
||||||
|
|
||||||
|
@api.doc('add_config')
|
||||||
|
@api.expect(config_model)
|
||||||
|
@api.marshal_with(config_model, code=201)
|
||||||
|
def post(self):
|
||||||
|
'''Put configuration'''
|
||||||
|
config = api.payload
|
||||||
|
if not ConfigurationDAO.getInstance().exists(config):
|
||||||
|
ConfigurationDAO.getInstance().add(api.payload)
|
||||||
|
return api.payload, 201
|
||||||
|
|
||||||
|
|
||||||
29
src/apis/product_structure.py
Normal file
29
src/apis/product_structure.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
from flask_restplus import Namespace, Resource, fields
|
||||||
|
from daos.product_structure_dao import ProductStructureDAO
|
||||||
|
import copy
|
||||||
|
|
||||||
|
api = Namespace('product_structure', description='Product structure related operations')
|
||||||
|
|
||||||
|
product_structure_model = api.model('Product Structure', {
|
||||||
|
#'ProductStructure': fields.List(fields.Wildcard(), required=True, example="{}", description='The array containing the product structure elements'),
|
||||||
|
})
|
||||||
|
|
||||||
|
DAO = ProductStructureDAO.getInstance()
|
||||||
|
|
||||||
|
@api.route('/')
|
||||||
|
class ProductStructure(Resource):
|
||||||
|
@api.doc('show_product_structure')
|
||||||
|
#@api.marshal_list_with(product_structure_model)
|
||||||
|
def get(self):
|
||||||
|
'''Return product structure'''
|
||||||
|
return DAO.get()
|
||||||
|
|
||||||
|
@api.doc('replace_product_structure')
|
||||||
|
@api.expect(product_structure_model)
|
||||||
|
#@api.marshal_with(product_structure_model, code=201)
|
||||||
|
def put(self):
|
||||||
|
'''replace product structure'''
|
||||||
|
DAO.replace(api.payload)
|
||||||
|
return DAO.get(), 201
|
||||||
|
|
||||||
|
|
||||||
38
src/apis/recommender.py
Normal file
38
src/apis/recommender.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from flask_restplus import Namespace, Resource, fields
|
||||||
|
from .config import config_model
|
||||||
|
from managers.recommendation_manager import RecommendationManager
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
|
||||||
|
api = Namespace('recommender', description='Recommendation related operations')
|
||||||
|
|
||||||
|
rating_model = api.model('Rating', {
|
||||||
|
'code': fields.String(required=True, description='The code that was rated'),
|
||||||
|
'value': fields.Float(required=True, description='The rating value'),
|
||||||
|
})
|
||||||
|
|
||||||
|
preference_model = api.model('Preference', {
|
||||||
|
'user': fields.String(required=True, description='The user identifier'),
|
||||||
|
'ratings': fields.List(fields.Nested(rating_model),required=True, description='The list of ratings of this user'),
|
||||||
|
})
|
||||||
|
|
||||||
|
recommendation_request_model = api.model('Recommendation Request', {
|
||||||
|
'configuration': fields.Nested(config_model, required=True, description='The user identifier'),
|
||||||
|
'preferences': fields.List(fields.Nested(preference_model),required=True, description='The list of ratings of this user'),
|
||||||
|
})
|
||||||
|
|
||||||
|
@api.route('/')
|
||||||
|
class Recommendation(Resource):
|
||||||
|
manager = RecommendationManager()
|
||||||
|
@api.doc('get_recommendation')
|
||||||
|
@api.expect(recommendation_request_model)
|
||||||
|
@api.marshal_list_with(config_model)
|
||||||
|
def post(self):
|
||||||
|
'''Get recommendation'''
|
||||||
|
result = self.manager.getRecommendation(Preferences(api.payload), ConfigurationModel(api.payload['configuration']))
|
||||||
|
response = result
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
11
src/app.py
Normal file
11
src/app.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from werkzeug.middleware.proxy_fix import ProxyFix
|
||||||
|
|
||||||
|
from apis import api
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.wsgi_app = ProxyFix(app.wsgi_app)
|
||||||
|
|
||||||
|
api.init_app(app)
|
||||||
|
|
||||||
|
app.run(debug=True)
|
||||||
0
src/conftest.py
Normal file
0
src/conftest.py
Normal file
0
src/daos/__init__.py
Normal file
0
src/daos/__init__.py
Normal file
38
src/daos/config_dao.py
Normal file
38
src/daos/config_dao.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from daos.db import DB_CONFIG
|
||||||
|
from tinyrecord import transaction
|
||||||
|
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
|
||||||
|
class ConfigurationDAO:
|
||||||
|
|
||||||
|
__instance = None
|
||||||
|
def add(self, config):
|
||||||
|
trans = DB_CONFIG()
|
||||||
|
with transaction(trans) as tr:
|
||||||
|
tr.insert(config)
|
||||||
|
def getAll(self):
|
||||||
|
return DB_CONFIG().all()
|
||||||
|
|
||||||
|
def getAll_as_objects(self):
|
||||||
|
configurations = []
|
||||||
|
for conf in self.getAll():
|
||||||
|
configurations.append(ConfigurationModel(conf))
|
||||||
|
|
||||||
|
def exists(self, config):
|
||||||
|
for conf in DB_CONFIG().all():
|
||||||
|
if len(set(conf['configuration']).symmetric_difference(set(config['configuration']))) == 0:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
@staticmethod
|
||||||
|
def getInstance():
|
||||||
|
""" Static access method. """
|
||||||
|
if ConfigurationDAO.__instance == None:
|
||||||
|
ConfigurationDAO()
|
||||||
|
return ConfigurationDAO.__instance
|
||||||
|
def __init__(self):
|
||||||
|
""" Virtually private constructor. """
|
||||||
|
if ConfigurationDAO.__instance != None:
|
||||||
|
raise Exception("This class is a singleton!")
|
||||||
|
else:
|
||||||
|
ConfigurationDAO.__instance = self
|
||||||
|
|
||||||
10
src/daos/db.py
Normal file
10
src/daos/db.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from tinydb import TinyDB
|
||||||
|
|
||||||
|
def DB():
|
||||||
|
return TinyDB('db.json')
|
||||||
|
|
||||||
|
def DB_CONFIG():
|
||||||
|
return DB().table('CONFIG')
|
||||||
|
|
||||||
|
def DB_PRODUCT_STRUCTURE():
|
||||||
|
return DB().table('PRODUCT_STRUCTURE')
|
||||||
48
src/daos/product_structure_dao.py
Normal file
48
src/daos/product_structure_dao.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
from daos.db import DB_PRODUCT_STRUCTURE
|
||||||
|
from tinydb import Query
|
||||||
|
from tinyrecord import transaction
|
||||||
|
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
|
||||||
|
rmList = []
|
||||||
|
|
||||||
|
class ProductStructureDAO(object):
|
||||||
|
__instance = None
|
||||||
|
|
||||||
|
def get_as_objects(self) -> ProductStructureModel:
|
||||||
|
return ProductStructureModel(self.get())
|
||||||
|
|
||||||
|
def get(self):
|
||||||
|
highest_id = self._get_highest_id()
|
||||||
|
return DB_PRODUCT_STRUCTURE().get(doc_id=highest_id)
|
||||||
|
|
||||||
|
def replace(self, structure):
|
||||||
|
highest_id = self._get_highest_id()
|
||||||
|
rmList = list(range(0,highest_id + 1))
|
||||||
|
trans = DB_PRODUCT_STRUCTURE()
|
||||||
|
with transaction(trans) as tr:
|
||||||
|
tr.insert(structure)
|
||||||
|
tr.remove(doc_ids=rmList)
|
||||||
|
return structure
|
||||||
|
def _get_highest_id(self):
|
||||||
|
all = DB_PRODUCT_STRUCTURE().all()
|
||||||
|
max = 0
|
||||||
|
for element in all:
|
||||||
|
if element.doc_id > max:
|
||||||
|
max = element.doc_id
|
||||||
|
return max
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def getInstance():
|
||||||
|
""" Static access method. """
|
||||||
|
if ProductStructureDAO.__instance == None:
|
||||||
|
ProductStructureDAO()
|
||||||
|
return ProductStructureDAO.__instance
|
||||||
|
def __init__(self):
|
||||||
|
""" Virtually private constructor. """
|
||||||
|
if ProductStructureDAO.__instance != None:
|
||||||
|
raise Exception("This class is a singleton!")
|
||||||
|
else:
|
||||||
|
ProductStructureDAO.__instance = self
|
||||||
|
|
||||||
|
|
||||||
0
src/managers/__init__.py
Normal file
0
src/managers/__init__.py
Normal file
89
src/managers/recommendation_manager.py
Normal file
89
src/managers/recommendation_manager.py
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
from daos.config_dao import ConfigurationDAO
|
||||||
|
from daos.product_structure_dao import ProductStructureDAO
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
from scoring.scoring_functions import ReduceScoringFunctionFactory, ScoringFunction
|
||||||
|
import numpy as np
|
||||||
|
import operator
|
||||||
|
|
||||||
|
class RecommendationManager:
|
||||||
|
def getRecommendation(self, preferences: Preferences , current_config : ConfigurationModel,
|
||||||
|
scoring_methods = "avg",
|
||||||
|
penalty_function = "penalty_ratio",
|
||||||
|
product_structure = ProductStructureDAO.getInstance().get_as_objects(),
|
||||||
|
configurations = ConfigurationDAO.getInstance().getAll()):
|
||||||
|
avg = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
[penalty_function, "pref_average_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul
|
||||||
|
)
|
||||||
|
lm = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
[penalty_function, "pref_min_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul
|
||||||
|
)
|
||||||
|
multi = ReduceScoringFunctionFactory.build_scoring_function(
|
||||||
|
[penalty_function, "pref_product_simpleSelectedCharacterstics_average"],
|
||||||
|
product_structure,
|
||||||
|
oper = operator.mul
|
||||||
|
)
|
||||||
|
|
||||||
|
default = SimpleConfigurationMaxSelector( avg )
|
||||||
|
switcher = {
|
||||||
|
"avg" : default,
|
||||||
|
"multi": SimpleConfigurationMaxSelector(multi),
|
||||||
|
"lm": SimpleConfigurationMaxSelector( lm ),
|
||||||
|
"avg-lm": PipeFilterMax(ConfigurationFilter(avg), SimpleConfigurationMaxSelector( lm )),
|
||||||
|
"lm-avg": PipeFilterMax(ConfigurationFilter(lm), SimpleConfigurationMaxSelector( avg ))
|
||||||
|
}
|
||||||
|
max_selector = switcher.get(scoring_methods, default)
|
||||||
|
|
||||||
|
return max_selector.getMax(preferences, current_config, configurations)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationMaxSelector:
|
||||||
|
def getMax(self, preferences: Preferences, current_config : ConfigurationModel, configurations):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PipeFilterMax(ConfigurationMaxSelector):
|
||||||
|
def __init__(self, configuration_filter : 'ConfigurationFilter', max_selector : ConfigurationMaxSelector):
|
||||||
|
self.configuration_filter = configuration_filter
|
||||||
|
self.max_selector = max_selector
|
||||||
|
|
||||||
|
def getMax(self, preferences: Preferences, current_config : ConfigurationModel, configurations):
|
||||||
|
list = self.configuration_filter.filter(preferences, current_config, configurations)
|
||||||
|
return self.max_selector.getMax(preferences, current_config, list)
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationFilter:
|
||||||
|
def __init__(self, scoring_function : ScoringFunction, percentile = 50):
|
||||||
|
assert percentile <= 100
|
||||||
|
assert percentile >= 0
|
||||||
|
self.scoring_function = scoring_function
|
||||||
|
self.percentile = percentile
|
||||||
|
def filter(self,
|
||||||
|
preferences: Preferences,
|
||||||
|
current_config : ConfigurationModel,
|
||||||
|
configurations):
|
||||||
|
|
||||||
|
scores = list(map(lambda x: self.scoring_function.calc_score(current_config, preferences, ConfigurationModel(x)), configurations))
|
||||||
|
|
||||||
|
barrier = np.percentile(np.array(scores), self.percentile)
|
||||||
|
return list(filter(lambda x: self.scoring_function.calc_score(current_config, preferences, ConfigurationModel(x)) > barrier, configurations))
|
||||||
|
|
||||||
|
class SimpleConfigurationMaxSelector(ConfigurationMaxSelector):
|
||||||
|
def __init__(self, scoring_function : ScoringFunction):
|
||||||
|
self.scoring_function = scoring_function
|
||||||
|
def getMax(self, preferences: Preferences, current_config : ConfigurationModel, configurations):
|
||||||
|
best_rating = float("-inf")
|
||||||
|
best = None
|
||||||
|
|
||||||
|
for to_rate in configurations:
|
||||||
|
score = self.scoring_function.calc_score(current_config, preferences, ConfigurationModel(to_rate))
|
||||||
|
if score > best_rating:
|
||||||
|
best = to_rate
|
||||||
|
best_rating = score
|
||||||
|
print('Best rating: {}'.format(best_rating))
|
||||||
|
return best
|
||||||
|
|
||||||
0
src/model/__init__.py
Normal file
0
src/model/__init__.py
Normal file
17
src/model/configuration_model.py
Normal file
17
src/model/configuration_model.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
class ConfigurationVariablesModel:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.value : str = data['value']
|
||||||
|
self.code : str = data['code']
|
||||||
|
|
||||||
|
class ConfigurationModel:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.configuration : List[str] = []
|
||||||
|
self.variables : List[ConfigurationVariablesModel] = []
|
||||||
|
if data is not None:
|
||||||
|
self.configuration = data['configuration']
|
||||||
|
if 'variables' in data:
|
||||||
|
for v in data['variables']:
|
||||||
|
self.variables.append(ConfigurationVariablesModel(v))
|
||||||
|
|
||||||
57
src/model/preferences_model.py
Normal file
57
src/model/preferences_model.py
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
from typing import List
|
||||||
|
|
||||||
|
class Rating:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.code = data['code']
|
||||||
|
self.value = float(data['value'])
|
||||||
|
if self.value < 0 or self.value > 1:
|
||||||
|
raise ValueError("Value of rating has to be in interval [0,1]")
|
||||||
|
def getValue(self):
|
||||||
|
""" Returns rating value """
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
class UserPreference:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.ratings : List[Rating] = []
|
||||||
|
self.user : str = data['user']
|
||||||
|
for rat in data['ratings']:
|
||||||
|
self.ratings.append(Rating(rat))
|
||||||
|
def getAllRatings(self) -> List[Rating]:
|
||||||
|
return self.ratings
|
||||||
|
def getRatingByCode(self, code : str) -> Rating:
|
||||||
|
return next(filter(lambda x : x.code == code, self.ratings), Rating({'code': code, 'value': 0.5 }))
|
||||||
|
|
||||||
|
class Preferences:
|
||||||
|
def __init__(self, data={ 'preferences' : [] }):
|
||||||
|
self.preferences : List[UserPreference] = []
|
||||||
|
for pref in data['preferences']:
|
||||||
|
self.preferences.append(UserPreference(pref))
|
||||||
|
def getAllUserPreferences(self) -> List[UserPreference]:
|
||||||
|
return self.preferences
|
||||||
|
def getAllRatingsByCode(self, code) -> List[Rating]:
|
||||||
|
list = []
|
||||||
|
for user_pref in self.preferences:
|
||||||
|
list.append(user_pref.getRatingByCode('code'))
|
||||||
|
return list
|
||||||
|
|
||||||
|
def getAllUsers(self) -> List[str] :
|
||||||
|
list = []
|
||||||
|
for userPref in self.preferences:
|
||||||
|
if userPref.user not in list :
|
||||||
|
list.append(userPref.user)
|
||||||
|
return list
|
||||||
|
def getRatingValueByUserAndCode(self, user, code) -> float:
|
||||||
|
for userPref in self.preferences:
|
||||||
|
if userPref.user == user :
|
||||||
|
for rating in userPref.ratings:
|
||||||
|
if rating.code == code:
|
||||||
|
return rating.getValue()
|
||||||
|
return 0.5
|
||||||
|
|
||||||
|
def getIndividualPreferences(self):
|
||||||
|
return list(map(lambda x: _create_preferences_and_add_user_pref(x), self.preferences))
|
||||||
|
|
||||||
|
def _create_preferences_and_add_user_pref(userPref):
|
||||||
|
tmp = Preferences()
|
||||||
|
tmp.preferences.append(userPref)
|
||||||
|
return tmp
|
||||||
71
src/model/product_structure_model.py
Normal file
71
src/model/product_structure_model.py
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
from typing import List
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
class ProductStructureTypeEnum(Enum):
|
||||||
|
CHARACTERISTIC = "CHARACTERISTIC",
|
||||||
|
FEATURE = "FEATURE",
|
||||||
|
CLUSTER = "CLUSTER",
|
||||||
|
VARIABLE = "VARIABLE",
|
||||||
|
|
||||||
|
|
||||||
|
class ProductStructureElementModel:
|
||||||
|
def __init__(self, data):
|
||||||
|
self.children = []
|
||||||
|
self.type = None
|
||||||
|
self.elementId = data['elementId']
|
||||||
|
self.type = data['type']
|
||||||
|
self.name = data['name']
|
||||||
|
self.additionalData = data['additionalData']
|
||||||
|
|
||||||
|
for element in data['children']:
|
||||||
|
self.children.append(ProductStructureElementModel(element))
|
||||||
|
|
||||||
|
def get_list_of_all(self, type : ProductStructureTypeEnum) :
|
||||||
|
tmp_list = []
|
||||||
|
for child in self.children :
|
||||||
|
tmp_list = tmp_list + child.get_list_of_all(type)
|
||||||
|
if ProductStructureTypeEnum[self.type] == type:
|
||||||
|
tmp_list.append(self)
|
||||||
|
return tmp_list
|
||||||
|
|
||||||
|
def get_children_characteristics(self):
|
||||||
|
tmp_list = self.get_list_of_all(ProductStructureTypeEnum.CHARACTERISTIC)
|
||||||
|
if self in tmp_list:
|
||||||
|
tmp_list.remove(self)
|
||||||
|
return tmp_list
|
||||||
|
|
||||||
|
|
||||||
|
class ProductStructureModel:
|
||||||
|
list_of_features = []
|
||||||
|
list_of_characteristics = []
|
||||||
|
def __init__(self, data):
|
||||||
|
self.productStructure : List[ProductStructureElementModel] = []
|
||||||
|
for element in data['ProductStructure']:
|
||||||
|
child = ProductStructureElementModel(element)
|
||||||
|
self.productStructure.append(child)
|
||||||
|
|
||||||
|
def get_list_of_features(self):
|
||||||
|
if (self.list_of_features == []) :
|
||||||
|
tmp_list = []
|
||||||
|
for element in self.productStructure:
|
||||||
|
tmp_list = tmp_list + element.get_list_of_all(ProductStructureTypeEnum.FEATURE)
|
||||||
|
self.list_of_features = tmp_list
|
||||||
|
|
||||||
|
return self.list_of_features
|
||||||
|
|
||||||
|
def get_list_of_characteristics(self):
|
||||||
|
if (self.list_of_features == []) :
|
||||||
|
tmp_list = []
|
||||||
|
for element in self.productStructure:
|
||||||
|
tmp_list = tmp_list + element.get_list_of_all(ProductStructureTypeEnum.CHARACTERISTIC)
|
||||||
|
self.list_of_characteristics = tmp_list
|
||||||
|
|
||||||
|
return self.list_of_characteristics
|
||||||
|
|
||||||
|
def isCharacteristic(self, code: str) -> bool:
|
||||||
|
list = self.get_list_of_characteristics()
|
||||||
|
return any(map(lambda x: x.elementId == code , list))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
0
src/scoring/__init__.py
Normal file
0
src/scoring/__init__.py
Normal file
30
src/scoring/list_functions.py
Normal file
30
src/scoring/list_functions.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
from typing import List
|
||||||
|
from functools import reduce
|
||||||
|
import operator
|
||||||
|
|
||||||
|
class ListFunction:
|
||||||
|
pass
|
||||||
|
class ListToListFunction(ListFunction):
|
||||||
|
def applyToList(self, list : List[float]) -> List[float]:
|
||||||
|
pass
|
||||||
|
class ListToValueFunction(ListFunction):
|
||||||
|
def convertToFloat(self, list : List[float]) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Average(ListToValueFunction):
|
||||||
|
def convertToFloat(self, list : List[float]) -> float:
|
||||||
|
score = len(list)
|
||||||
|
if score == 0:
|
||||||
|
score = 1
|
||||||
|
if list:
|
||||||
|
return reduce(operator.add, list) / score
|
||||||
|
else:
|
||||||
|
return 0.0
|
||||||
|
|
||||||
|
class Min(ListToValueFunction):
|
||||||
|
def convertToFloat(self, list : List[float]) -> float:
|
||||||
|
return min(list)
|
||||||
|
|
||||||
|
class Product(ListToValueFunction):
|
||||||
|
def convertToFloat(self, list : List[float]) -> float:
|
||||||
|
return reduce(operator.mul, list)
|
||||||
76
src/scoring/preferences_functions.py
Normal file
76
src/scoring/preferences_functions.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from typing import List
|
||||||
|
from model.preferences_model import Preferences, Rating
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
|
||||||
|
from scoring.list_functions import ListToValueFunction
|
||||||
|
from scoring.value_functions import MapToPercent
|
||||||
|
|
||||||
|
class PreferencesToListFunction:
|
||||||
|
def convertToList(self, preferences : Preferences, toRate : ConfigurationModel) -> List[float]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
class FlattenPreferencesToListFunction(PreferencesToListFunction):
|
||||||
|
def convertToList(self, preferences : Preferences, toRate : ConfigurationModel) -> List[float]:
|
||||||
|
list : List[Rating] = []
|
||||||
|
for user_pref in preferences.getAllUserPreferences():
|
||||||
|
for rating in user_pref.getAllRatings():
|
||||||
|
if rating.code in toRate.configuration:
|
||||||
|
list.append(rating.getValue())
|
||||||
|
else :
|
||||||
|
list.append(1 - rating.getValue())
|
||||||
|
return list
|
||||||
|
|
||||||
|
class SimplePerUserToListFunction(PreferencesToListFunction):
|
||||||
|
def __init__(self, listToValue : ListToValueFunction):
|
||||||
|
self.listToValueFunction = listToValue
|
||||||
|
|
||||||
|
def convertToList(self, preferences : Preferences, toRate : ConfigurationModel) -> List[float]:
|
||||||
|
list = []
|
||||||
|
for user_pref in preferences.getAllUserPreferences():
|
||||||
|
user_list : List[float] = []
|
||||||
|
for rating in user_pref.getAllRatings():
|
||||||
|
if rating.code in toRate.configuration:
|
||||||
|
user_list.append(rating.getValue())
|
||||||
|
else :
|
||||||
|
user_list.append(1 - rating.getValue())
|
||||||
|
list.append(self.listToValueFunction.convertToFloat(user_list))
|
||||||
|
return list
|
||||||
|
|
||||||
|
class SimpleSelectedCharacteristicsToListFunction(PreferencesToListFunction):
|
||||||
|
def __init__(self, listToValue : ListToValueFunction):
|
||||||
|
self.listToValueFunction = listToValue
|
||||||
|
|
||||||
|
def convertToList(self, preferences : Preferences, toRate : ConfigurationModel) -> List[float]:
|
||||||
|
list = []
|
||||||
|
for user_pref in preferences.getAllUserPreferences():
|
||||||
|
user_list : List[float] = []
|
||||||
|
for code in toRate.configuration:
|
||||||
|
user_list.append(user_pref.getRatingByCode(code).getValue())
|
||||||
|
list.append(self.listToValueFunction.convertToFloat(user_list))
|
||||||
|
return list
|
||||||
|
class PerUserPerFeatureDistanceAverageToListFunction(PreferencesToListFunction):
|
||||||
|
def __init__(self, featureListToValue : ListToValueFunction, product_structure : ProductStructureModel):
|
||||||
|
self.featureListToValueFunction = featureListToValue
|
||||||
|
self.product_structure = product_structure
|
||||||
|
|
||||||
|
def convertToList(self, preferences : Preferences, toRate : ConfigurationModel) -> List[float]:
|
||||||
|
user_preferences = preferences.getAllUserPreferences()
|
||||||
|
feature_list = self.product_structure.get_list_of_features()
|
||||||
|
user_scores = []
|
||||||
|
for user_pref in user_preferences:
|
||||||
|
feature_scores = []
|
||||||
|
for feature in feature_list:
|
||||||
|
char_list = feature.get_children_characteristics()
|
||||||
|
in_to_rate_rating = 0
|
||||||
|
avg = 0
|
||||||
|
for char in char_list:
|
||||||
|
if char.elementId in toRate.configuration:
|
||||||
|
in_to_rate_rating = user_pref.getRatingByCode(char.elementId).getValue()
|
||||||
|
avg += user_pref.getRatingByCode(char.elementId).getValue()
|
||||||
|
if len(char_list) > 0 :
|
||||||
|
avg = avg / len(char_list)
|
||||||
|
map_function = MapToPercent(1,-1)
|
||||||
|
feature_scores.append(map_function.applyToValue(in_to_rate_rating - avg))
|
||||||
|
user_scores.append(self.featureListToValueFunction.convertToFloat(feature_scores))
|
||||||
|
return user_scores
|
||||||
162
src/scoring/scoring_functions.py
Normal file
162
src/scoring/scoring_functions.py
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
from typing import List
|
||||||
|
from functools import reduce
|
||||||
|
import operator
|
||||||
|
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
|
||||||
|
from scoring.list_functions import ListToListFunction, ListToValueFunction, Average, Min, Product
|
||||||
|
from scoring.preferences_functions import PreferencesToListFunction, FlattenPreferencesToListFunction, SimplePerUserToListFunction, PerUserPerFeatureDistanceAverageToListFunction, SimpleSelectedCharacteristicsToListFunction
|
||||||
|
from scoring.value_functions import ValueToValueFunction, MapToPercent, PowerFunction
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
|
||||||
|
class ScoringFunctionFactory:
|
||||||
|
@staticmethod
|
||||||
|
def build_scoring_function(params : List[str]) -> 'ScoringFunction':
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ReduceScoringFunctionFactory(ScoringFunctionFactory):
|
||||||
|
@staticmethod
|
||||||
|
def build_scoring_function(params : List[str], product_structure : ProductStructureModel, oper = operator.add) -> 'SumScoring':
|
||||||
|
list = []
|
||||||
|
|
||||||
|
for param in params:
|
||||||
|
switcher = {
|
||||||
|
"penalty_ratio" : RatioCharacteristicConfigurationPenalty(product_structure, [PowerFunction(0.5)]),
|
||||||
|
"penealty_average_weightedFeature_average": WeightedFeaturePenalty(
|
||||||
|
product_structure,
|
||||||
|
Average(),
|
||||||
|
Average()
|
||||||
|
),
|
||||||
|
"pref_average_flat": PreferenceScoring(
|
||||||
|
FlattenPreferencesToListFunction(),
|
||||||
|
Average()
|
||||||
|
),
|
||||||
|
"pref_average_perUser_Average": PreferenceScoring(
|
||||||
|
SimplePerUserToListFunction(Average()),
|
||||||
|
Average()
|
||||||
|
),
|
||||||
|
"pref_average_simpleSelectedCharacterstics_average": PreferenceScoring(
|
||||||
|
SimpleSelectedCharacteristicsToListFunction(Average()),
|
||||||
|
Average()
|
||||||
|
),
|
||||||
|
"pref_min_simpleSelectedCharacterstics_average": PreferenceScoring(
|
||||||
|
SimpleSelectedCharacteristicsToListFunction(Average()),
|
||||||
|
Min()
|
||||||
|
),
|
||||||
|
"pref_product_simpleSelectedCharacterstics_average": PreferenceScoring(
|
||||||
|
SimpleSelectedCharacteristicsToListFunction(Average()),
|
||||||
|
Product()
|
||||||
|
),
|
||||||
|
"pref_min_perUserPerFeatureDistance_average" : PreferenceScoring(
|
||||||
|
PerUserPerFeatureDistanceAverageToListFunction(Average(), product_structure),
|
||||||
|
Min()
|
||||||
|
),
|
||||||
|
"pref_average_perUserPerFeatureDistance_average" : PreferenceScoring(
|
||||||
|
PerUserPerFeatureDistanceAverageToListFunction(Average(), product_structure),
|
||||||
|
Average()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
value = switcher.get(param, None)
|
||||||
|
if value != None:
|
||||||
|
list.append(value)
|
||||||
|
|
||||||
|
return ReduceScoring(list, reduce_operator=oper)
|
||||||
|
|
||||||
|
|
||||||
|
class ScoringFunction:
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class PreferenceScoring(ScoringFunction):
|
||||||
|
def __init__(self, preferenceToList : PreferencesToListFunction, listToValue : ListToValueFunction, listToList: List[ListToListFunction] = [], valueToValue: List[ValueToValueFunction] = []):
|
||||||
|
self.preferenceToListFunction = preferenceToList
|
||||||
|
self.listToValueFunction = listToValue
|
||||||
|
self.listToListFunctions = listToList
|
||||||
|
self.valueToValueFunctions = valueToValue
|
||||||
|
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
list : List[float] = self.preferenceToListFunction.convertToList(preferences, toRate)
|
||||||
|
|
||||||
|
for function in self.listToListFunctions :
|
||||||
|
list = function.applyToList(list)
|
||||||
|
|
||||||
|
value = self.listToValueFunction.convertToFloat(list)
|
||||||
|
|
||||||
|
for function in self.valueToValueFunctions :
|
||||||
|
value = function.applyToValue(value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
class ConfigurationPenalty(ScoringFunction):
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
pass
|
||||||
|
|
||||||
|
class RatioCharacteristicConfigurationPenalty(ConfigurationPenalty):
|
||||||
|
def __init__(self, product_structure : ProductStructureModel, valToValFunctions : List[ValueToValueFunction]):
|
||||||
|
self.product_structure = product_structure
|
||||||
|
self.valToValFunctions = valToValFunctions
|
||||||
|
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
inCount : float = 0
|
||||||
|
charCount : float = 0
|
||||||
|
for code in currentConfiguration.configuration:
|
||||||
|
if self.product_structure.isCharacteristic(code):
|
||||||
|
charCount += 1
|
||||||
|
if code in toRate.configuration:
|
||||||
|
inCount += 1
|
||||||
|
if charCount > 0:
|
||||||
|
res = inCount / charCount
|
||||||
|
else :
|
||||||
|
res = 1
|
||||||
|
|
||||||
|
for function in self.valToValFunctions:
|
||||||
|
res = function.applyToValue(res)
|
||||||
|
return res
|
||||||
|
|
||||||
|
class WeightedFeaturePenalty(ScoringFunction):
|
||||||
|
def __init__(self, product_structure : ProductStructureModel, per_feature_aggregation : ListToValueFunction, per_feature_per_user_aggregation : ListToValueFunction):
|
||||||
|
self.product_structure = product_structure
|
||||||
|
self.feature_aggregation = per_feature_aggregation
|
||||||
|
self.user_aggregation = per_feature_per_user_aggregation
|
||||||
|
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
features = self.product_structure.get_list_of_features()
|
||||||
|
|
||||||
|
feature_scores = []
|
||||||
|
for feature in features:
|
||||||
|
code_in_current = None
|
||||||
|
code_in_to_rate = None
|
||||||
|
for characteristic in feature.children:
|
||||||
|
if characteristic.elementId in currentConfiguration.configuration :
|
||||||
|
code_in_current = characteristic.elementId
|
||||||
|
if characteristic.elementId in toRate.configuration:
|
||||||
|
code_in_to_rate = characteristic.elementId
|
||||||
|
users = preferences.getAllUsers()
|
||||||
|
|
||||||
|
if code_in_current == None:
|
||||||
|
break
|
||||||
|
if code_in_current == code_in_to_rate:
|
||||||
|
feature_scores.append(1)
|
||||||
|
else:
|
||||||
|
user_scores = []
|
||||||
|
for user in users:
|
||||||
|
rating_to_rate = preferences.getRatingValueByUserAndCode(user, code_in_to_rate)
|
||||||
|
rating_current = preferences.getRatingValueByUserAndCode(user, code_in_current)
|
||||||
|
user_scores.append(rating_to_rate - rating_current)
|
||||||
|
feature_scores.append(self.user_aggregation.convertToFloat(user_scores))
|
||||||
|
map_to_percent = MapToPercent(1, -1)
|
||||||
|
return map_to_percent.applyToValue(self.feature_aggregation.convertToFloat(feature_scores))
|
||||||
|
|
||||||
|
|
||||||
|
class ReduceScoring(ScoringFunction):
|
||||||
|
def __init__(self, scoringFunctions : List[ScoringFunction], reduce_operator = operator.add):
|
||||||
|
self.scoringFunctions = scoringFunctions
|
||||||
|
self.reduce_operator = reduce_operator
|
||||||
|
def calc_score(self, currentConfiguration : ConfigurationModel, preferences : Preferences, toRate : ConfigurationModel) -> float:
|
||||||
|
if len(self.scoringFunctions) > 0:
|
||||||
|
score = reduce(self.reduce_operator, map(lambda x: x.calc_score(currentConfiguration, preferences, toRate), self.scoringFunctions))
|
||||||
|
return score
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
39
src/scoring/value_functions.py
Normal file
39
src/scoring/value_functions.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
class ValueToValueFunction:
|
||||||
|
def applyToValue(self, value : float) -> float :
|
||||||
|
return value
|
||||||
|
|
||||||
|
class MapToPercent(ValueToValueFunction):
|
||||||
|
def __init__(self, max_val : float, min_val : float):
|
||||||
|
self.max_val = max_val
|
||||||
|
self.min_val = min_val
|
||||||
|
def applyToValue(self, value : float) -> float :
|
||||||
|
return (value - self.min_val) / abs(self.min_val - self.max_val)
|
||||||
|
|
||||||
|
class HighpassFilterFunction(ValueToValueFunction):
|
||||||
|
def __init__(self, cutoff : float, floor_value=0):
|
||||||
|
self.cutoff = cutoff
|
||||||
|
self.floor_value = floor_value
|
||||||
|
def applyToValue(self, value : float) -> float :
|
||||||
|
if value < self.cutoff:
|
||||||
|
return self.floor_value
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
class LowpassFilterFunction(ValueToValueFunction):
|
||||||
|
def __init__(self, cutoff : float, ceiling_value=1):
|
||||||
|
self.cutoff = cutoff
|
||||||
|
self.ceiling_value = ceiling_value
|
||||||
|
def applyToValue(self, value : float) -> float :
|
||||||
|
if value > self.cutoff:
|
||||||
|
return self.ceiling_value
|
||||||
|
else:
|
||||||
|
return value
|
||||||
|
|
||||||
|
class PowerFunction(ValueToValueFunction):
|
||||||
|
def __init__(self, power : float):
|
||||||
|
self.power = power
|
||||||
|
|
||||||
|
def applyToValue(self, value : float) -> float :
|
||||||
|
return math.pow(value, self.power)
|
||||||
28
tests/test_model/test_configuration_model.py
Normal file
28
tests/test_model/test_configuration_model.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from model.configuration_model import ConfigurationModel, ConfigurationVariablesModel
|
||||||
|
import math
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class TestConfigurationModel:
|
||||||
|
def test_simple_parsing(self):
|
||||||
|
data = {
|
||||||
|
'configuration': ['code1', 'code2'],
|
||||||
|
'variables': [
|
||||||
|
{
|
||||||
|
'code': 'abc',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
conf = ConfigurationModel(data)
|
||||||
|
assert len(conf.configuration) == 2
|
||||||
|
assert len(conf.variables) == 1
|
||||||
|
|
||||||
|
class TestConfigurationVariableModel:
|
||||||
|
def test_simple_parsing(self):
|
||||||
|
data = {
|
||||||
|
'code': 'abc',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
var = ConfigurationVariablesModel(data)
|
||||||
|
assert var.code == 'abc'
|
||||||
|
assert var.value == 1
|
||||||
113
tests/test_model/test_preference_model.py
Normal file
113
tests/test_model/test_preference_model.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
from model.preferences_model import Preferences, Rating, UserPreference
|
||||||
|
import math
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
class TestRating:
|
||||||
|
def test_range_conversion_minus_one_to_zero(self):
|
||||||
|
data = {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': 0
|
||||||
|
}
|
||||||
|
assert math.isclose(0, Rating(data).getValue())
|
||||||
|
def test_range_conversion_one_to_one(self):
|
||||||
|
data = {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
assert math.isclose(1, Rating(data).getValue())
|
||||||
|
def test_range_conversion_zero_to_half(self):
|
||||||
|
data = {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': 0.5
|
||||||
|
}
|
||||||
|
assert math.isclose(0.5, Rating(data).getValue())
|
||||||
|
def test_value_to_large(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
data = {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': 1.1
|
||||||
|
}
|
||||||
|
rating = Rating(data)
|
||||||
|
def test_value_to_small(self):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
data = {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': -0.1
|
||||||
|
}
|
||||||
|
rating = Rating(data)
|
||||||
|
|
||||||
|
class TestUserPreference:
|
||||||
|
data = {
|
||||||
|
'user': "user0",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'abs',
|
||||||
|
'value': 0
|
||||||
|
}, {
|
||||||
|
'code': '2',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': '3',
|
||||||
|
'value': 0.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
def test_get_all_ratings(self):
|
||||||
|
user_pref = UserPreference(self.data)
|
||||||
|
assert len(user_pref.getAllRatings()) == 3
|
||||||
|
def test_get_rating_by_code(self):
|
||||||
|
user_pref = UserPreference(self.data)
|
||||||
|
rating = user_pref.getRatingByCode('2')
|
||||||
|
assert rating.code == '2'
|
||||||
|
assert rating.getValue() == 1
|
||||||
|
def test_get_rating_by_code_default(self):
|
||||||
|
user_pref = UserPreference(self.data)
|
||||||
|
rating = user_pref.getRatingByCode('notFOUND')
|
||||||
|
assert rating.code == 'notFOUND'
|
||||||
|
assert rating.getValue() == 0.5
|
||||||
|
|
||||||
|
class TestPreferences:
|
||||||
|
preferences = Preferences({
|
||||||
|
'preferences': [
|
||||||
|
{
|
||||||
|
'user': "user0",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'in_both',
|
||||||
|
'value': 0
|
||||||
|
}, {
|
||||||
|
'code': 'only_in_one',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': '3',
|
||||||
|
'value': 0.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user': "user1",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'in_both',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': '3',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
def test_get_all_user_preferences(self):
|
||||||
|
assert len(self.preferences.getAllUserPreferences()) == 2
|
||||||
|
def test_get_all_rating_by_code(self):
|
||||||
|
assert len(self.preferences.getAllRatingsByCode('only_in_one')) == 2
|
||||||
|
assert len(self.preferences.getAllRatingsByCode('in_both')) == 2
|
||||||
|
def test_get_all_users(self):
|
||||||
|
assert len(self.preferences.getAllUsers()) == 2
|
||||||
|
assert "user0" in self.preferences.getAllUsers()
|
||||||
|
assert "user1" in self.preferences.getAllUsers()
|
||||||
|
def test_get_rating_by_user_and_code(self):
|
||||||
|
assert self.preferences.getRatingValueByUserAndCode("user0", "only_in_one") == 1
|
||||||
|
def test_empty_preferences(self):
|
||||||
|
assert len(Preferences({ 'preferences' : []}).getAllUsers()) == 0
|
||||||
|
def test_getIndividual_preferences(self):
|
||||||
|
assert len(self.preferences.getIndividualPreferences()) == 2
|
||||||
|
|
||||||
100
tests/test_model/test_product_structure_model.py
Normal file
100
tests/test_model/test_product_structure_model.py
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
from model.product_structure_model import ProductStructureModel, ProductStructureElementModel, ProductStructureTypeEnum
|
||||||
|
|
||||||
|
class TestProductStructureModelEnum:
|
||||||
|
def test_string_characteristic(self):
|
||||||
|
assert ProductStructureTypeEnum['CHARACTERISTIC'] == ProductStructureTypeEnum.CHARACTERISTIC
|
||||||
|
def test_string_cluster(self):
|
||||||
|
assert ProductStructureTypeEnum['CLUSTER'] == ProductStructureTypeEnum.CLUSTER
|
||||||
|
def test_string_variable(self):
|
||||||
|
assert ProductStructureTypeEnum['VARIABLE'] == ProductStructureTypeEnum.VARIABLE
|
||||||
|
def test_string_feature(self):
|
||||||
|
assert ProductStructureTypeEnum['FEATURE'] == ProductStructureTypeEnum.FEATURE
|
||||||
|
|
||||||
|
class TestProductStructureElementModel:
|
||||||
|
def test_get_list_of_all(self):
|
||||||
|
data = {
|
||||||
|
'elementId': 'parent',
|
||||||
|
'name': 'parent_element',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'child',
|
||||||
|
'name': 'child',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
element = ProductStructureElementModel(data)
|
||||||
|
assert len(element.get_list_of_all(ProductStructureTypeEnum.CHARACTERISTIC)) == 1
|
||||||
|
|
||||||
|
class TestProductStructureModel:
|
||||||
|
def test_get_list_of_features(self):
|
||||||
|
data = {
|
||||||
|
'ProductStructure': [
|
||||||
|
{
|
||||||
|
'elementId': 'parent',
|
||||||
|
'name': 'parent_element',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'child',
|
||||||
|
'name': 'child',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ps_structure = ProductStructureModel(data)
|
||||||
|
assert len(ps_structure.get_list_of_features()) == 1
|
||||||
|
def test_get_list_of_characteristics(self):
|
||||||
|
data = {
|
||||||
|
'ProductStructure': [
|
||||||
|
{
|
||||||
|
'elementId': 'parent',
|
||||||
|
'name': 'parent_element',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'child',
|
||||||
|
'name': 'child',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ps_structure = ProductStructureModel(data)
|
||||||
|
assert len(ps_structure.get_list_of_characteristics()) == 1
|
||||||
|
def test_is_characteristic(self):
|
||||||
|
data = {
|
||||||
|
'ProductStructure': [
|
||||||
|
{
|
||||||
|
'elementId': 'parent',
|
||||||
|
'name': 'parent_element',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'child',
|
||||||
|
'name': 'child',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
ps_structure = ProductStructureModel(data)
|
||||||
|
assert ps_structure.isCharacteristic('child') == True
|
||||||
|
assert ps_structure.isCharacteristic('parent') == False
|
||||||
14
tests/test_scoring/test_list_functions.py
Normal file
14
tests/test_scoring/test_list_functions.py
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
from scoring.list_functions import Average, Product
|
||||||
|
import math
|
||||||
|
|
||||||
|
class TestListToValueFunctionAverage:
|
||||||
|
def test_simple_average(self):
|
||||||
|
function = Average()
|
||||||
|
list = [0.0, 1.0]
|
||||||
|
assert math.isclose(0.5, function.convertToFloat(list))
|
||||||
|
|
||||||
|
class TestListToValueFunctionProduct:
|
||||||
|
def test_simple_product(self):
|
||||||
|
function = Product()
|
||||||
|
list = [0.5, 0.5]
|
||||||
|
assert math.isclose(0.25, function.convertToFloat(list))
|
||||||
114
tests/test_scoring/test_preference_functions.py
Normal file
114
tests/test_scoring/test_preference_functions.py
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
from scoring.preferences_functions import PerUserPerFeatureDistanceAverageToListFunction, SimplePerUserToListFunction, PreferencesToListFunction, FlattenPreferencesToListFunction
|
||||||
|
from scoring.list_functions import Min
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
|
||||||
|
preferences = Preferences({
|
||||||
|
'preferences': [
|
||||||
|
{
|
||||||
|
'user': "user0",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'A1',
|
||||||
|
'value': 0
|
||||||
|
}, {
|
||||||
|
'code': 'A2',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': 'B1',
|
||||||
|
'value': 0.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user': "user1",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'A1',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': 'B2',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
toRate = ConfigurationModel({
|
||||||
|
'configuration': ['A1', 'B2'],
|
||||||
|
'variables': []
|
||||||
|
})
|
||||||
|
|
||||||
|
product_structure = ProductStructureModel({
|
||||||
|
'ProductStructure': [
|
||||||
|
{
|
||||||
|
'elementId': 'A',
|
||||||
|
'name': 'parent_element A',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'A1',
|
||||||
|
'name': 'child A1',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'elementId': 'A2',
|
||||||
|
'name': 'child A2',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},{
|
||||||
|
'elementId': 'B',
|
||||||
|
'name': 'parent_element B',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'B1',
|
||||||
|
'name': 'child B1',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'elementId': 'B2',
|
||||||
|
'name': 'child B2',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
def float_lists_same(first, second):
|
||||||
|
for element in first:
|
||||||
|
if element not in second:
|
||||||
|
return False
|
||||||
|
second.remove(element)
|
||||||
|
if len(second) != 0:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
class TestPreferencesToListFunction:
|
||||||
|
def test_should_be_empty_list(self):
|
||||||
|
function = PreferencesToListFunction()
|
||||||
|
assert len(function.convertToList(preferences, toRate)) == 0
|
||||||
|
|
||||||
|
class TestFlattenPreferencesToListFunction():
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = FlattenPreferencesToListFunction()
|
||||||
|
assert float_lists_same([1.0,0.5,0.0,0.0,1.0], function.convertToList(preferences, toRate))
|
||||||
|
|
||||||
|
class TestSimplePerUserToListFunction():
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = SimplePerUserToListFunction(Min())
|
||||||
|
assert float_lists_same([0.0, 1.0], function.convertToList(preferences, toRate))
|
||||||
|
|
||||||
|
class TestPerUserPerFeatureDistanceAverageToListFunction():
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = PerUserPerFeatureDistanceAverageToListFunction(Min(), product_structure)
|
||||||
|
assert float_lists_same([0.25, 0.625], function.convertToList(preferences, toRate))
|
||||||
124
tests/test_scoring/test_scoring_functions.py
Normal file
124
tests/test_scoring/test_scoring_functions.py
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
from scoring.scoring_functions import PreferenceScoring, RatioCharacteristicConfigurationPenalty, WeightedFeaturePenalty, ReduceScoring
|
||||||
|
from scoring.value_functions import ValueToValueFunction
|
||||||
|
from model.configuration_model import ConfigurationModel
|
||||||
|
from model.preferences_model import Preferences
|
||||||
|
from scoring.list_functions import Min, Average
|
||||||
|
from scoring.preferences_functions import FlattenPreferencesToListFunction
|
||||||
|
from model.product_structure_model import ProductStructureModel
|
||||||
|
|
||||||
|
preferences = Preferences({
|
||||||
|
'preferences': [
|
||||||
|
{
|
||||||
|
'user': "user0",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'A1',
|
||||||
|
'value': 0
|
||||||
|
}, {
|
||||||
|
'code': 'A2',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': 'B1',
|
||||||
|
'value': 0.5
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'user': "user1",
|
||||||
|
'ratings':[ {
|
||||||
|
'code': 'A1',
|
||||||
|
'value': 1
|
||||||
|
}, {
|
||||||
|
'code': 'B2',
|
||||||
|
'value': 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
currentConfiguration = ConfigurationModel({
|
||||||
|
'configuration': ['A2', 'B2'],
|
||||||
|
'variables': []
|
||||||
|
})
|
||||||
|
toRate = ConfigurationModel({
|
||||||
|
'configuration': ['A1', 'B2'],
|
||||||
|
'variables': []
|
||||||
|
})
|
||||||
|
|
||||||
|
product_structure = ProductStructureModel({
|
||||||
|
'ProductStructure': [
|
||||||
|
{
|
||||||
|
'elementId': 'A',
|
||||||
|
'name': 'parent_element A',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'A1',
|
||||||
|
'name': 'child A1',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'elementId': 'A2',
|
||||||
|
'name': 'child A2',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},{
|
||||||
|
'elementId': 'B',
|
||||||
|
'name': 'parent_element B',
|
||||||
|
'type': "FEATURE",
|
||||||
|
'additionalData': [],
|
||||||
|
'children': [
|
||||||
|
{
|
||||||
|
'elementId': 'B1',
|
||||||
|
'name': 'child B1',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'elementId': 'B2',
|
||||||
|
'name': 'child B2',
|
||||||
|
'children': [],
|
||||||
|
'additionalData': [],
|
||||||
|
'type': "CHARACTERISTIC"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
class TestRatioCharacteristicConfigurationPenalty:
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = RatioCharacteristicConfigurationPenalty(product_structure, [ValueToValueFunction()])
|
||||||
|
assert 0.5 == function.calc_score(currentConfiguration, preferences, toRate)
|
||||||
|
|
||||||
|
|
||||||
|
class TestWeightedFeaturePenalty:
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = WeightedFeaturePenalty(product_structure, Min(), Average())
|
||||||
|
assert 0.375 == function.calc_score(currentConfiguration, preferences, toRate)
|
||||||
|
|
||||||
|
class TestReduceScoring:
|
||||||
|
def test_combined(self):
|
||||||
|
function = ReduceScoring([
|
||||||
|
RatioCharacteristicConfigurationPenalty(product_structure, [ValueToValueFunction()]),
|
||||||
|
WeightedFeaturePenalty(product_structure, Min(), Average())
|
||||||
|
])
|
||||||
|
assert 0.875 == function.calc_score(currentConfiguration, preferences, toRate)
|
||||||
|
def test_none(self):
|
||||||
|
function = ReduceScoring([])
|
||||||
|
assert 0 == function.calc_score(currentConfiguration, preferences, toRate)
|
||||||
|
|
||||||
|
class TestPreferenceScoring:
|
||||||
|
def test_simple_example(self):
|
||||||
|
function = PreferenceScoring(
|
||||||
|
FlattenPreferencesToListFunction(),
|
||||||
|
Min()
|
||||||
|
)
|
||||||
|
assert 0 == function.calc_score(currentConfiguration, preferences, toRate)
|
||||||
|
|
||||||
44
tests/test_scoring/test_value_functions.py
Normal file
44
tests/test_scoring/test_value_functions.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
from scoring.value_functions import MapToPercent, ValueToValueFunction, HighpassFilterFunction, LowpassFilterFunction, PowerFunction
|
||||||
|
import math
|
||||||
|
|
||||||
|
class TestMapToPercent:
|
||||||
|
def test_range_conversion(self):
|
||||||
|
function = MapToPercent(-20, -40)
|
||||||
|
assert math.isclose(1, function.applyToValue(-20))
|
||||||
|
assert math.isclose(0, function.applyToValue(-40))
|
||||||
|
assert math.isclose(0.5, function.applyToValue(-30))
|
||||||
|
|
||||||
|
class TestValueToValueFunction:
|
||||||
|
def test_same_value(self):
|
||||||
|
function = ValueToValueFunction()
|
||||||
|
assert math.isclose(10, function.applyToValue(10))
|
||||||
|
|
||||||
|
class TestHighpassFilterFunction:
|
||||||
|
def test_higher_value(self):
|
||||||
|
function = HighpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(0.8, function.applyToValue(0.8))
|
||||||
|
def test_lower_value(self):
|
||||||
|
function = HighpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(0, function.applyToValue(0.3))
|
||||||
|
def test_same_value(self):
|
||||||
|
function = HighpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(0.5, function.applyToValue(0.5))
|
||||||
|
|
||||||
|
class TestLowpassFilterFunction:
|
||||||
|
def test_higher_value(self):
|
||||||
|
function = LowpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(1, function.applyToValue(0.8))
|
||||||
|
def test_lower_value(self):
|
||||||
|
function = LowpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(0.3, function.applyToValue(0.3))
|
||||||
|
def test_same_value(self):
|
||||||
|
function = LowpassFilterFunction(0.5)
|
||||||
|
assert math.isclose(0.5, function.applyToValue(0.5))
|
||||||
|
|
||||||
|
class TestPowerFunction:
|
||||||
|
def test_power_one(self):
|
||||||
|
function = PowerFunction(10)
|
||||||
|
assert math.isclose(1, function.applyToValue(1))
|
||||||
|
def test_power_small_number(self):
|
||||||
|
function = PowerFunction(4)
|
||||||
|
assert math.isclose(0.0001, function.applyToValue(0.1))
|
||||||
3
tests/test_simple.py
Normal file
3
tests/test_simple.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
class TestSimple:
|
||||||
|
def test_simple(self):
|
||||||
|
assert True
|
||||||
179
vis.py
Normal file
179
vis.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import pandas as pd
|
||||||
|
import os
|
||||||
|
|
||||||
|
def setAxLinesBW(ax):
|
||||||
|
"""
|
||||||
|
Take each Line2D in the axes, ax, and convert the line style to be
|
||||||
|
suitable for black and white viewing.
|
||||||
|
"""
|
||||||
|
MARKERSIZE = 3
|
||||||
|
|
||||||
|
COLORMAP = {
|
||||||
|
'#1f77b4': {'marker': None, 'dash': [5,2]},
|
||||||
|
'#ff7f0e': {'marker': None, 'dash': [3,4]},
|
||||||
|
'#2ca02c': {'marker': None, 'dash': [1,1]},
|
||||||
|
'k': {'marker': None, 'dash': (None,None)},
|
||||||
|
"#d62728": {'marker': None, 'dash': (None,None)},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
lines_to_adjust = ax.get_lines()
|
||||||
|
try:
|
||||||
|
lines_to_adjust += ax.get_legend().get_lines()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
for line in lines_to_adjust:
|
||||||
|
origColor = line.get_color()
|
||||||
|
line.set_color('black')
|
||||||
|
line.set_dashes(COLORMAP[origColor]['dash'])
|
||||||
|
line.set_marker(COLORMAP[origColor]['marker'])
|
||||||
|
line.set_markersize(MARKERSIZE)
|
||||||
|
|
||||||
|
def setFigLinesBW(fig):
|
||||||
|
"""
|
||||||
|
Take each axes in the figure, and for each line in the axes, make the
|
||||||
|
line viewable in black and white.
|
||||||
|
"""
|
||||||
|
for ax in fig.get_axes():
|
||||||
|
setAxLinesBW(ax)
|
||||||
|
|
||||||
|
|
||||||
|
def save_figs(folder):
|
||||||
|
happiness_diff = load_data_frame("{}/data/{}".format(folder, "_happy_increase.csv"))
|
||||||
|
unhappiness_diff = load_data_frame("{}/data/{}".format(folder, "_unhappy_increase.csv"))
|
||||||
|
|
||||||
|
happiness_diff['dictator'] = 0
|
||||||
|
unhappiness_diff['dictator'] = 0
|
||||||
|
|
||||||
|
happiness_total_all = load_data_frame("{}/data/{}".format(folder, "_happy_total_all.csv"))
|
||||||
|
unhappiness_total_all = load_data_frame("{}/data/{}".format(folder, "_unhappy_total_all.csv"))
|
||||||
|
|
||||||
|
column = happiness_total_all.columns[0]
|
||||||
|
index = happiness_total_all.index[0]
|
||||||
|
dictator_y_happy = happiness_total_all[column][index] - happiness_diff[column][index]
|
||||||
|
dictator_y_unhappy = unhappiness_total_all[column][index] - unhappiness_diff[column][index]
|
||||||
|
|
||||||
|
|
||||||
|
figure, axes = new_fig(title="{} Figure 2".format(folder))
|
||||||
|
|
||||||
|
x_lim=[0,150]
|
||||||
|
|
||||||
|
axes[0].set_title("satisfaction")
|
||||||
|
axes[0].set_xlim(x_lim)
|
||||||
|
axes[0].set_xlabel("number of stored configurations")
|
||||||
|
axes[0].set_ylabel("number of people")
|
||||||
|
axes[0].axhline(y=dictator_y_happy,linewidth=1, color='k')
|
||||||
|
|
||||||
|
happiness_total_all.plot(ax=axes[0])
|
||||||
|
y_labels_happy_total =axes[0].get_yticks().tolist()
|
||||||
|
|
||||||
|
axes[1].set_title("dissatisfaction")
|
||||||
|
axes[1].set_xlabel("number of stored configurations")
|
||||||
|
axes[1].set_ylabel("number of people")
|
||||||
|
axes[1].set_xlim(x_lim)
|
||||||
|
axes[1].axhline(y=dictator_y_unhappy,linewidth=1, color='k')
|
||||||
|
|
||||||
|
unhappiness_total_all.plot(ax=axes[1])
|
||||||
|
y_labels_unhappy_total =axes[1].get_yticks().tolist()
|
||||||
|
|
||||||
|
setFigLinesBW(figure)
|
||||||
|
|
||||||
|
#plt.savefig("{}/fig/vis_happy_unhappy_number.pdf".format(folder),format="pdf")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
figure, axes = new_fig(title="{} Figure 1".format(folder))
|
||||||
|
|
||||||
|
x_lim=[0,150]
|
||||||
|
|
||||||
|
left_y_label = "change in number of people"
|
||||||
|
rigt_y_label = "number of people"
|
||||||
|
x_label = "number of stored configurations"
|
||||||
|
|
||||||
|
axes[0].set_title("satisfaction")
|
||||||
|
axes[0].set_xlim(x_lim)
|
||||||
|
axes[0].set_xlabel(x_label)
|
||||||
|
axes[0].set_ylabel(left_y_label)
|
||||||
|
#axes[0].axhline(y=0, linewidth=1, color='k')
|
||||||
|
twin0 = axes[0].twinx()
|
||||||
|
twin0.set_ylabel(rigt_y_label)
|
||||||
|
|
||||||
|
happiness_diff.plot(ax=axes[0])
|
||||||
|
|
||||||
|
axes[1].set_title("dissatisfaction")
|
||||||
|
axes[1].set_xlabel(x_label)
|
||||||
|
axes[1].set_ylabel(left_y_label)
|
||||||
|
axes[1].set_xlim(x_lim)
|
||||||
|
#axes[1].axhline(y=0, linewidth=1, color='k')
|
||||||
|
twin1 = axes[1].twinx()
|
||||||
|
twin1.set_ylabel(rigt_y_label)
|
||||||
|
|
||||||
|
|
||||||
|
unhappiness_diff.plot(ax=axes[1])
|
||||||
|
|
||||||
|
y_labels_happy = list(map(lambda x: process_label(x, show_plus=True), axes[0].get_yticks().tolist()))
|
||||||
|
y_labels_unhappy = list(map(lambda x: process_label(x, show_plus=True), axes[1].get_yticks().tolist()))
|
||||||
|
|
||||||
|
y_labels_secondary_happy = list(map(lambda x: process_label(x + dictator_y_happy), axes[0].get_yticks().tolist()))
|
||||||
|
y_labels_secondary_unhappy = list(map(lambda x: process_label(x + dictator_y_unhappy), axes[1].get_yticks().tolist()))
|
||||||
|
|
||||||
|
align_labels(axes[0], twin0)
|
||||||
|
align_labels(axes[1], twin1)
|
||||||
|
|
||||||
|
axes[0].set_yticklabels(y_labels_happy)
|
||||||
|
twin0.set_yticklabels(y_labels_secondary_happy)
|
||||||
|
|
||||||
|
axes[1].set_yticklabels(y_labels_unhappy)
|
||||||
|
twin1.set_yticklabels(y_labels_secondary_unhappy)
|
||||||
|
|
||||||
|
|
||||||
|
setFigLinesBW(figure)
|
||||||
|
|
||||||
|
#plt.show()
|
||||||
|
|
||||||
|
plt.savefig("{}/fig/vis_happy_unhappy_combined.pdf".format(folder),format="pdf")
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
def process_label(label, show_plus=False, round_digits = 2):
|
||||||
|
n_label = round(label, round_digits)
|
||||||
|
if label > 0 and show_plus:
|
||||||
|
n_label = "+{}".format(n_label)
|
||||||
|
else:
|
||||||
|
n_label = "{}".format(n_label)
|
||||||
|
return n_label
|
||||||
|
|
||||||
|
def align_labels(origin, to_align):
|
||||||
|
y_low, y_high = origin.get_ylim()
|
||||||
|
to_align.set_ylim(y_low, y_high)
|
||||||
|
to_align.set_yticklabels(origin.get_yticks().tolist())
|
||||||
|
|
||||||
|
|
||||||
|
def load_data_frame(path):
|
||||||
|
frame = pd.read_csv(path, index_col=0).T
|
||||||
|
frame.index = frame.index.astype(int)
|
||||||
|
return frame
|
||||||
|
|
||||||
|
def new_fig(subplot_row=1, subplot_column=2,aspect_ratio=1.3 ,dpi=300, title="Untitled"):
|
||||||
|
figure, axes = plt.subplots(subplot_row, subplot_column, sharey=False)
|
||||||
|
figure.canvas.set_window_title(title)
|
||||||
|
figure.dpi = dpi
|
||||||
|
figure.set_figwidth(4 * subplot_column * aspect_ratio)
|
||||||
|
figure.set_figheight(4 * subplot_row)
|
||||||
|
plt.subplots_adjust(wspace=0.45)
|
||||||
|
|
||||||
|
return figure, axes
|
||||||
|
|
||||||
|
|
||||||
|
def main(dir = "./out"):
|
||||||
|
for subdir in os.listdir(dir):
|
||||||
|
path = "{}/{}".format(dir,subdir)
|
||||||
|
if os.path.isdir(path):
|
||||||
|
try:
|
||||||
|
save_figs(path)
|
||||||
|
print("Generated Figures for: {}".format(subdir))
|
||||||
|
except OSError as e:
|
||||||
|
print("Files Not Found in: {}".format(subdir))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user