''' This module contains classes that implement various
objects for storing solutions.
Attributes:
_solution_id (int): global variable used for generating
solution IDs.
'''
from pulp import LpStatus, LpStatusOptimal
import pickle
import os
from pyDEA.core.utils.dea_utils import is_efficient, TMP_FOLDER
_solution_id = 0
[docs]class Solution(object):
''' This class implements basic solution.
Attributes:
_solution_id (int): solution ID.
orientation (str): problem orientation, can take values
input or output.
_input_data (InputData): object that stores input data.
efficiency_scores (dict of str to double): dictionary that maps
DMU code to efficiency spyDEA.core.
lp_status (dict of str to pulp.LpStatus): dictionary that maps
DMU code to LP status (optimal, unbounded, etc).
input_duals (dict of str to dict of str to double): dictionary
that maps DMU code to another dictionary that maps input
category name to value of dual variable.
output_duals (dict of str to dict of str to double): dictionary
that maps DMU code to another dictionary that maps output
category name to value of dual variable.
return_to_scale (dict of str to str): dictionary that maps DMU code
to the return-to-scale of the DMU
Args:
input_data (InputData): object that stores input data.
'''
def __init__(self, input_data):
global _solution_id
_solution_id += 1
self._solution_id = _solution_id
self.orientation = ''
self._input_data = input_data
self.efficiency_scores = dict()
self.lp_status = dict()
self.input_duals = dict()
self.output_duals = dict()
self.return_to_scale = dict()
for dmu_code in input_data.DMU_codes:
self.input_duals[dmu_code] = dict()
self.output_duals[dmu_code] = dict()
if not os.path.exists(TMP_FOLDER):
os.makedirs(TMP_FOLDER)
[docs] def add_efficiency_score(self, dmu_code, efficiency_score):
''' Adds efficiency score of a given DMU to internal
data structure.
Args:
dmu_code (str): DMU code.
efficiency_score (double): efficiency spyDEA.core.
Raises:
ValueError: if dmu_code does not exist or has invalid
value.
'''
self._check_efficiency_score(efficiency_score)
self._check_if_dmu_code_exists(dmu_code)
self.efficiency_scores[dmu_code] = efficiency_score
def _check_efficiency_score(self, efficiency_score):
''' Checks if efficiency score has a valid value.
Args:
efficiency_score (double): efficiency spyDEA.core.
Raises:
ValueError: if efficiency score has invalid value.
'''
if efficiency_score < 0 or efficiency_score > 1:
raise ValueError('Efficiency score must be within [0, 1]')
[docs] def get_efficiency_score(self, dmu_code):
''' Returns efficiency score of a given DMU.
Args:
dmu_code (str): DMU code.
Returns:
double: efficiency spyDEA.core.
'''
return self.efficiency_scores[dmu_code]
[docs] def is_efficient(self, dmu_code, lambda_variables=None):
''' Checks if a given DMU is efficient.
Args:
dmu_code (str): DMU code.
lambda_variables (dict of str to double, optional): dictionary
that maps DMU codes to the corresponding value
of lambda variables. If it is not given, it will be loaded
from a pickled file.
Returns:
bool: True if a given DMU is efficient, False otherwise.
'''
if self.lp_status[dmu_code] != LpStatusOptimal:
return False
file_name = self._get_pickle_name(dmu_code)
if not lambda_variables:
lambda_variables = pickle.load(open(file_name, 'rb'))
return is_efficient(self.get_efficiency_score(dmu_code),
lambda_variables.get(dmu_code, 0))
[docs] def add_lambda_variables(self, dmu_code, variables):
''' Adds lambda variables corresponding to a given DMU
to pickled file.
Args:
dmu_code (str): DMU code.
variables (dict of str to double): dictionary
that maps DMU codes to the corresponding value
of lambda variables.
Raises:
ValueError: if DMU code does not exist or
if number of lambda variables is not equal
to total number of DMU codes, or
if variables contain keys that are not existing DMU codes.
'''
self._check_if_dmu_code_exists(dmu_code)
self._validate_lambda_variables(variables)
pickle.dump(variables, open(self._get_pickle_name(dmu_code), 'wb'))
[docs] def get_lambda_variables(self, dmu_code):
''' Returns lambda variables corresponding to a given DMU.
Args:
dmu_code (str): DMU code.
Returns:
dict of str to double: lambda variables.
'''
file_name = self._get_pickle_name(dmu_code)
return pickle.load(open(file_name, 'rb'))
def _get_pickle_name(self, dmu_code):
''' Generates a unique name for pickled file with lambda
variables.
Args:
dmu_code (str): DMU code.
Returns:
str: generated file name.
'''
file_name = 'lambda{0}_{1}.p'.format(self._solution_id, dmu_code)
return os.path.join(TMP_FOLDER, file_name)
def _check_if_dmu_code_exists(self, dmu_code):
''' Checks if a given DMU code exists.
Args:
dmu_code (str): DMU code.
Raises:
ValueError: if a given DMU code does not exist.
'''
if dmu_code not in self._input_data.DMU_codes:
raise ValueError('DMU code {dmu} does not exist'.format(
dmu=dmu_code))
def _validate_lambda_variables(self, variables):
''' Checks if variables contain existing DMU codes as keys.
Args:
variables (dict of str to double): dictionary
that maps DMU codes to the corresponding value
of lambda variables.
Raises:
ValueError: if variables contain non-existing DMU codes.
'''
for key in variables.keys():
self._check_if_dmu_code_exists(key)
[docs] def add_output_dual(self, dmu_code, output_category, dual_value):
''' Adds value of a dual variable associated with a given output
category and DMU code to internal data structure.
Args:
dmu_code (str): DMU code.
output_category (str): output category name.
dual_value (double): value of a dual variable.
Raises:
ValueError: if a given category is not a valid output category.
'''
self._check_if_dmu_code_exists(dmu_code)
if output_category not in self._input_data.output_categories:
raise ValueError('{category} is not a valid output category'.format(
category=output_category))
self.output_duals[dmu_code][output_category] = dual_value
[docs] def get_output_dual(self, dmu_code, output_category):
''' Returns dual variable value corresponding to a given DMU and
output category.
Args:
dmu_code (str): DMU code.
output_category (str): output category name.
Returns:
double: dual variable value.
'''
return self.output_duals[dmu_code][output_category]
[docs] def add_lp_status(self, dmu_code, lp_status):
''' Adds LP status corresponding to a given DMU to internal
data structure.
Args:
dmu_code (str): DMU code.
lp_status (pulp.LpStatus): LP status.
'''
self._check_if_dmu_code_exists(dmu_code)
self.lp_status[dmu_code] = lp_status
def _print_for_one_dmu(self, dmu_code):
''' Prints on screen all information available for a given DMU.
Args:
dmu_code (str): DMU code.
'''
print('DMU: {dmu}'.format(
dmu=self._input_data.get_dmu_user_name(dmu_code)))
print('code: ', dmu_code)
if self.lp_status.get(dmu_code):
print('LP status: {status}'.format(
status=LpStatus[self.lp_status.get(dmu_code)]))
if self.lp_status.get(dmu_code) == LpStatusOptimal:
print('Efficiency score: {score}'.format(
score=self.efficiency_scores.get(dmu_code)))
print('Lambda variables: {vars}'.format(
vars=self.get_lambda_variables(dmu_code)))
print('Input duals: {duals}'.format(
duals=self.input_duals.get(dmu_code)))
print('Output duals: {duals}'.format(
duals=self.output_duals.get(dmu_code)))
[docs] def print_solution(self):
''' Prints all data on the screen.
'''
for dmu_code in self._input_data.DMU_codes:
self._print_for_one_dmu(dmu_code)
[docs]class SolutionWithVRS(object):
''' This class decorate Solution with VRS variables to store their values
for VRS DEA models.
Attributes:
_model_solution (Solution): solution that should be decorated
with VRS variables.
vrs_duals (dict of str to double): dictionary that maps DMU code
to VRS variable value.
Args:
model_solution (Solution): solution that should be decorated
with VRS variables.
'''
def __init__(self, model_solution):
self._model_solution = model_solution
self.vrs_duals = dict()
def __getattr__(self, name):
return getattr(self._model_solution, name)
[docs] def add_VRS_dual(self, dmu_code, value):
''' Adds VRS variable value corresponding to a given DMU to
internal data structure.
Args:
dmu_code (str): DMU code.
value (double): value of the VRS variable.
'''
self._model_solution._check_if_dmu_code_exists(dmu_code)
self.vrs_duals[dmu_code] = value
[docs] def get_VRS_dual(self, dmu_code):
''' Returns value of a VRS variable corresponding to a given
DMU.
Args:
dmu_code (str): DMU code.
Returns:
double: value of the VRS variable.
'''
return self.vrs_duals[dmu_code]
def _print_for_one_dmu(self, dmu_code):
''' Prints on screen all information available for a given DMU.
Args:
dmu_code (str): DMU code.
'''
self._model_solution._print_for_one_dmu(dmu_code)
print('VRS variable: {vrs}'.format(vrs=self.vrs_duals.get(dmu_code)))
[docs] def print_solution(self):
''' Prints all data on the screen.
'''
for dmu_code in self._input_data.DMU_codes:
self._print_for_one_dmu(dmu_code)
[docs]class SolutionWithSuperEfficiency(Solution):
''' This class redefines one method of :class:`Solution` to accept
efficiency scores greater than 1.
'''
def _check_efficiency_score(self, efficiency_score):
''' Redefines method of :class:`Solution` to accept
efficiency scores greater than 1.
Args:
efficiency_score (double): efficiency score
Raises:
ValueError: if efficiency score is less than 0
'''
if efficiency_score < 0:
raise ValueError('Efficiency score must be >= 0')
def _check_if_dmu_code_exists(self, dmu_code):
''' In the case of super efficiency, current DMU for which LP
is being created cannot be peer to itself. This method
does not raise
an error if given DMU code does not belong to the set of DMUs.
'''
pass
[docs] def is_efficient(self, dmu_code, lambda_variables=None):
''' Returns True if a given DMU is efficient,
False otherwise.
We have to redefine this method in the case of super efficiency,
because now any DMU with efficiency score >= 1 is considered
efficient.
Args:
dmu_code (str): internal code of DMU.
lambda_variables (dict of str to double, optional): dictionary
that maps DMU codes to the corresponding value
of lambda variables. It is ignored in this class.
Returns:
bool: True if a given DMU is efficient, False otherwise.
Example:
>>> s = SolutionWithSuperEfficiency()
>>> s.add_efficiency_score('dmu_1', 1.2)
>>> s.is_efficient('dmu_1')
True
>>> s.add_efficiency_score('dmu_2', 0.5)
>>> s.is_efficient('dmu_2')
False
>>> s.add_efficiency_score('dmu_3', 0.9999999)
>>> s.is_efficient('dmu_3')
False
'''
if self.get_efficiency_score(dmu_code) >= 1:
return True
return False