#!/usr/bin/env python3
"""
Python3 implementation of outranking digraphs.
Copyright (C) 2006-2023 Raymond Bisdorff
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
"""
__version__ = "Revision: Py3.10"
from digraphsTools import *
from digraphs import *
from xmlrpc.client import ServerProxy
from outrankingDigraphs import *
from copy import copy, deepcopy
from io import BytesIO
from pickle import Pickler, dumps, loads, load
#from multiprocessing import Process, Lock,\
# active_children, cpu_count
import multiprocessing as mp
# mpctx = mp.get_context('spawn')
# Process = mpctx.Process
# active_children = mpctx.active_children
# cpu_count = mpctx.cpu_count
#-------------------------------------------
[docs]
class OutrankingDigraph(Digraph,PerformanceTableau):
"""
Abstract root class for **outranking digraphs** inheritating methods both from the generic :py:class:`digraphs.Digraph`
and from the generic :py:class:`perfTabs.PerformanceTableau` root classes.
As such, our genuine outranking digraph model is a hybrid object appearing on the one side as digraph with a
nodes set (the decision alternatives) and a binary relation (outranking situations) and, on the other side, as
a performance tableau with a set of decision alternatives, performance criteria and a table of performance measurements.
Provides common methods to all specialized models of outranking digraphs, the standard outranking digraph model being provided
by the :py:class:`outrankingDigraphs.BipolarOutrankingDigraph` class.
A given object of this class consists at least in:
1. a potential set of decision **actions** : an (ordered) dictionary describing the potential decision actions or alternatives with 'name' and 'comment' attributes,
2. a coherent family of **criteria**: an (ordered) dictionary of criteria functions used for measuring the performance of each potential decision action with respect to the preference dimension captured by each criterion,
3. the **evaluations**: a dictionary of performance evaluations for each decision action or alternative on each criterion function.
4. the digraph **valuationdomain**, a dictionary with three entries: the *minimum* (-100, means certainly no link), the *median* (0, means missing information) and the *maximum* characteristic value (+100, means certainly a link),
5. the **outranking relation** : a double dictionary defined on the Cartesian product of the set of decision alternatives capturing the credibility of the pairwise *outranking situation* computed on the basis of the performance differences observed between couples of decision alternatives on the given family if criteria functions.
.. warning::
Cannot be called directly ! No __init__(self,...) method defined.
"""
[docs]
def computeMarginalObjectiveCorrelation(self,args,Threading=False,
nbrOfCPUs=None,Debug=False,
Comments=False):
"""
Renders the ordinal correlation coefficient between
the marginal criterion relation and a
given normalized outranking relation.
args = (objective,relation)
"""
objective = args[0]
relation = args[1]
gc = BipolarOutrankingDigraph(self,Normalized=True,
#coalition=[criterion],
objectivesSubset=[objective],
CopyPerfTab=True,
Threading=Threading,
nbrCores=nbrOfCPUs,
Comments=Comments)
corr = gc.computeOrdinalCorrelationMP(relation,
Threading=Threading,
nbrOfCPUs=nbrOfCPUs)
if Debug:
print(corr)
return corr
[docs]
def computeMarginalCorrelation(self,args,Threading=False,
nbrOfCPUs=None,Debug=False,
Comments=False):
"""
Renders the ordinal correlation coefficient between
the marginal criterion relation and a
given normalized outranking relation.
args = (criterion,relation)
"""
criterion = args[0]
relation = args[1]
gc = BipolarOutrankingDigraph(self,Normalized=True,
coalition=[criterion],
CopyPerfTab=True,
Threading=Threading,
nbrCores=nbrOfCPUs,
Comments=Comments)
corr = gc.computeOrdinalCorrelationMP(relation,
Threading=Threading,
nbrOfCPUs=nbrOfCPUs)
if Debug:
print(corr)
return corr
[docs]
def showOutrankingConsensusQuality(self,Sorted=True,ValuedCorrelation=True,
Threading=False,nbrCores=None,
Comments=True):
""" Show method for the computeOutrankingConsensusQuality() method."""
self.computeOutrankingConsensusQuality(self,Sorted=Dorted,
ValuedCorrelation=ValuedCorrelation,
Threading=Threading,nbrCores=nbrCores,
Comments=Comments)
[docs]
def computeOutrankingConsensusQuality(self,Sorted=True,ValuedCorrelation=True,
Threading=False,nbrCores=None,
Comments=False):
"""
Renders the marginal criteria correlations with the corresponding global outranking relation with summary.
"""
from math import sqrt
# self is an OutrankingDigraph object
criteria = self.criteria
marginalCorrelations=\
self.computeMarginalVersusGlobalOutrankingCorrelations(
Sorted=Sorted,
ValuedCorrelation=ValuedCorrelation,
Threading=Threading,nbrCores=nbrCores,
Comments=False)
ncrit = Decimal(str(len(marginalCorrelations)))
meanMarginalCorrelation = Decimal('0.0')
varMarginalCorrelation = Decimal('0.0')
sumWeights = Decimal('0.0')
for cg in marginalCorrelations:
#if cg[0] < Decimal('0'):
sumWeights += abs(criteria[cg[1]]['weight'])
for cg in marginalCorrelations:
#if cg[0] < Decimal('0'):
cgw = abs(criteria[cg[1]]['weight'])/sumWeights
meanMarginalCorrelation += cg[0]*cgw
for cg in marginalCorrelations:
#if cg[0] < Decimal('0'):
cgw = abs(criteria[cg[1]]['weight'])/sumWeights
varMarginalCorrelation += ((cg[0]-meanMarginalCorrelation)**2)*cgw
#varMarginalCorrelation += (cg[0]**2)*cgw
# #meanMarginalCriteriaCorrelation /= ncrit
# #varMarginalCriteriaCorrelation /= ncrit
# #varMarginalCorrelation -= meanMarginalCorrelation*meanMarginalCriteriaCorrelation
sdMarginalCorrelation = sqrt(varMarginalCorrelation)
# # showing the results
if Comments:
print('Consensus quality of global outranking:')
if ValuedCorrelation:
print('criterion (weight): valued correlation')
print('--------------------------------------')
else:
print('criterion (weight): correlation')
print('-------------------------------')
for cg in marginalCorrelations:
print('%s (%.3f): %+.3f' % (
cg[1],abs(criteria[cg[1]]['weight'])/sumWeights,cg[0]) )
print('Summary:')
print('Weighted mean marginal correlation (a): %+.3f' %\
meanMarginalCorrelation)
print('Standard deviation (b) : %+.3f' %\
sdMarginalCorrelation)
print('Ranking fairness (a)-(b) : %+.3f' %\
(float(meanMarginalCorrelation) - sdMarginalCorrelation) )
return (marginalCorrelations,
meanMarginalCorrelation,
sdMarginalCorrelation)
#-----------------
[docs]
def showMarginalObjectivesVersusGlobalRankingCorrelations(self,ranking,
Sorted=True,ValuedCorrelation=False,
Threading=False,nbrCores=None):
"""
Corresponding compute method with Comments = True flag.
"""
self.computeMarginalObjectivesVersusGlobalRankingCorrelations(
ranking,Sorted=Sorted,ValuedCorrelation=ValuedCorrelation,
Threading=Threading,nbrCores=nbrCores,Comments=True)
[docs]
def computeMarginalObjectivesVersusGlobalRankingCorrelations(self,ranking,
Sorted=True,ValuedCorrelation=False,
Threading=False,nbrCores=None,
startMethod=None,
Comments=False):
"""
Method for computing correlations between each individual objective's outranking relation and the given global ranking relation.
Returns a list of tuples (correlation,objectiveKey) sorted by default in decreasing order of the correlation.
If Threading is True, a multiprocessing Pool class is used with a parallel equivalent of the built-in map function.
If nbrCores is not set, the os.cpu_count() function is used to determine the number of available cores.
*Usage example*:
>>> from outrankingDigraphs import *
>>> t = Random3ObjectivesPerformanceTableau(
numberOfActions=21,
numberOfCriteria=17,
vetoProbability=0.2,
seed=12)
>>> g = BipolarOutrankingDigraph(t)
>>> ranking = g.computeNetFlowsRanking()
>>> g.computeMarginalObjectivesVersusGlobalRankingCorrelations(
ranking,Threading=False,Comments=True)
Marginal objective ordinal correlation with given ranking
-------------------------------------------------
Given ranking: ['p04', 'p09', 'p01', 'p08', 'p16', 'p03',
'p13', 'p20', 'p15', 'p10', 'p18', 'p19',
'p06', 'p02', 'p07', 'p11', 'p05', 'p12',
'p14', 'p21', 'p17']
Objective (weight): correlation
Soc (135.00): +0.473
Eco (135.00): +0.457
Env (135.00): +0.326
"""
try:
objectives = [obj for obj in self.objectives]
except:
print('No objectives deined in this performance tableau!')
return
from copy import deepcopy
from operator import itemgetter
preorder = ranking2preorder(ranking)
preorderRelation = self.computePreorderRelation(preorder)
if self.valuationdomain['min'] != Decimal('-1') or\
self.valuationdomain['max'] != Decimal('1'):
origValuationdomain = deepcopy(self.valuationdomain)
self.recodeValuation(-1,1)
Normalizing = True
else:
Normalizing = False
if Threading:
#from multiprocessing import Pool
#from os import cpu_count
import multiprocessing as mp
if startMethod is None:
startMethod = 'spawn'
mpctx = mp.get_context(startMethod)
Pool = mpctx.Pool
cpu_count = mpctx.cpu_count
if nbrCores is None:
nbrCores= cpu_count()
argsList = [(x,preorderRelation) for x in self.objectives]
with Pool(nbrCores) as proc:
correlations = proc.map(self.computeMarginalObjectiveCorrelation,argsList)
if ValuedCorrelation:
objectivesCorrelation = [(correlations[i]['correlation']*correlations[i]['determination'],argsList[i][0]) for i in range(len(argsList))]
else:
objectivesCorrelation = [(correlations[i]['correlation'],argsList[i][0]) for i in range(len(argsList))]
else:
objectives = [c for c in self.objectives]
objectivesCorrelation = []
for c in objectives:
corr = self.computeMarginalObjectiveCorrelation((c,preorderRelation),Threading=False)
if ValuedCorrelation:
objectivesCorrelation.append((corr['correlation']*corr['determination'],c))
else:
objectivesCorrelation.append((corr['correlation'],c))
if Sorted:
objectivesCorrelation.sort(reverse=True,key=itemgetter(0))
if Normalizing:
self.recodeValuation(origValuationdomain['min'],origValuationdomain['max'])
if Comments:
if objectivesCorrelation != []:
if ValuedCorrelation:
print("Marginal objective valued correlation with given ranking")
else:
print("Marginal objective ordinal correlation with given ranking")
print('-------------------------------------------------')
print('Given ranking:',ranking)
print('Objective (weight): correlation')
for item in objectivesCorrelation:
if ValuedCorrelation:
print('%s (%.2f): \t %+.3f' % (
item[2],self.objectives[item[2]]['weight'],
(item[0]*item[1])) )
else:
print('%s (%.2f): \t %+.3f' % (
item[1],self.objectives[item[1]]['weight'],
item[0]) )
else:
print('No objectives defined !')
return objectivesCorrelation
def showMarginalVersusGlobalOutrankingCorrelation(self,Sorted=True,ValuedCorrelation=False,
Threading=False,
nbrOfCPUs=None,Comments=True):
"""
Show method for computeCriterionCorrelation results.
"""
from copy import deepcopy
from operator import itemgetter
if self.valuationdomain['min'] != Decimal('-1') or\
self.valuationdomain['max'] != Decimal('1'):
origValuationdomain = deepcopy(self.valuationdomain)
self.recodeValuation(-1,1)
Normalizing = True
else:
Normalizing = False
criteriaList = [x for x in self.criteria]
criteriaCorrelation = []
totCorrelation = Decimal('0.0')
for c in criteriaList:
corr = self.computeCriterionCorrelation(c,Threading=Threading,nbrOfCPUs=nbrOfCPUs)
if ValuedCorrelation:
totCorrelation += corr['correlation']*corr['determination']
criteriaCorrelation.append(((corr['correlation']*corr['determination']),c))
else:
totCorrelation += corr['correlation']
criteriaCorrelation.append((corr['correlation'],c))
if Sorted:
criteriaCorrelation.sort(reverse=True,key=itemgetter(0))
if Comments:
if ValuedCorrelation:
print('Marginal versus global outranking valued correlation')
else:
print('Marginal versus global outranking correlation')
print('criterion | weight\t correlation')
print('----------|---------------------------')
for x in criteriaCorrelation:
c = x[1]
print('%9s | %.2f \t %.3f' % (
c,self.criteria[c]['weight'],x[0]))
print('Sum(Correlations) : %.3f' % (totCorrelation))
print('Determinateness : %.3f' % (corr['determination']))
if Normalizing:
self.recodeValuation(origValuationdomain['min'],
origValuationdomain['max'])
return criteriaCorrelation
#---------------
[docs]
def computeMarginalVersusGlobalRankingCorrelations(self,
ranking,
Sorted=True,ValuedCorrelation=False,
Threading=False,nbrCores=None,
startMethod=None,
Comments=False):
"""
Method for computing correlations between each individual criterion relation with the corresponding global ranking relation.
Returns a list of tuples (correlation,criterionKey) sorted by default in decreasing order of the correlation.
If Threading is True, a multiprocessing Pool class is used with a parallel equivalent of the built-in map function.
If nbrCores is not set, the os.cpu_count() function is used to determine the number ofavailable cores.
"""
from copy import deepcopy
from operator import itemgetter
preorder = ranking2preorder(ranking)
preorderRelation = self.computePreorderRelation(preorder)
if self.valuationdomain['min'] != Decimal('-1') or\
self.valuationdomain['max'] != Decimal('1'):
origValuationdomain = deepcopy(self.valuationdomain)
self.recodeValuation(-1,1)
Normalizing = True
else:
Normalizing = False
if Threading:
#from multiprocessing import Pool
#from os import cpu_count
import multiprocessing as mp
if startMethod is None:
startMethod = 'spawn'
mpctx = mp.get_context(startMethod)
Pool = mpctx.Pool
cpu_count = mpctx.cpu_count
if nbrCores is None:
nbrCores= cpu_count()
argsList = [(x,preorderRelation) for x in self.criteria]
with Pool(nbrCores) as proc:
correlations = proc.map(self.computeMarginalCorrelation,argsList)
if ValuedCorrelation:
criteriaCorrelation = [(correlations[i]['correlation']*correlations[i]['determination'],argsList[i][0]) for i in range(len(argsList))]
else:
criteriaCorrelation = [(correlations[i]['correlation'],argsList[i][0]) for i in range(len(argsList))]
else:
#criteriaList = [x for x in self.criteria]
criteria = self.criteria
criteriaCorrelation = []
for c in dict.keys(criteria):
corr = self.computeMarginalCorrelation((c,preorderRelation),Threading=False)
if ValuedCorrelation:
criteriaCorrelation.append((corr['correlation']*corr['determination'],c))
else:
criteriaCorrelation.append((corr['correlation'],c))
if Sorted:
criteriaCorrelation.sort(reverse=True,key=itemgetter(0))
if Normalizing:
self.recodeValuation(origValuationdomain['min'],origValuationdomain['max'])
return criteriaCorrelation
[docs]
def computeCriterionCorrelation(self,criterion,Threading=False,
nbrOfCPUs=None,Debug=False,
Comments=False):
"""
Renders the ordinal correlation coefficient between
the global outranking and the marginal criterion relation.
Uses the digraphs.computeOrdinalCorrelationMP().
.. note::
Renders a dictionary with the key 'correlation' containing the actual bipolar correlation index and the key 'determination' containing the minimal determination level D of the self outranking and the marginal criterion relation.
D = sum_{x != y} min(abs(self.relation(x,y)),abs(marginalCriterionRelation(x,y)) / n(n-1)
where n is the number of actions considered.
The correlation index with a completely indeterminate relation
is by convention 0.0 at determination level 0.0 .
"""
gc = BipolarOutrankingDigraph(self,Normalized=True,coalition=[criterion],CopyPerfTab=True,
Threading=Threading,nbrCores=nbrOfCPUs,
Comments=Comments)
corr = self.computeOrdinalCorrelationMP(gc,Threading=Threading,nbrOfCPUs=nbrOfCPUs)
if Debug:
print(corr)
return corr
[docs]
def computeMarginalVersusGlobalOutrankingCorrelations(self,Sorted=True,ValuedCorrelation=False,
Threading=False,nbrCores=None,
startMethod=None,
Comments=False):
"""
Method for computing correlations between each individual criterion relation with the corresponding
global outranking relation.
Returns a list of tuples (correlation,criterionKey) sorted by default in decreasing order of the correlation.
If Threading is True, a multiprocessing Pool class is used with a parallel equivalent of the built-in map function.
If nbrCores is not set, the os.cpu_count() function is used to determine the number of
available cores.
"""
from copy import deepcopy
from operator import itemgetter
if self.valuationdomain['min'] != Decimal('-1') or\
self.valuationdomain['max'] != Decimal('1'):
origValuationdomain = deepcopy(self.valuationdomain)
self.recodeValuation(-1,1)
Normalizing = True
else:
Normalizing = False
if Threading:
#from multiprocessing import Pool
#from os import cpu_count
import multiprocessing as mp
if startMethod is None:
startMethod = 'spawn'
mpctx = mp.get_context(startMethod)
Pool = mpctx.Pool
cpu_count = mpctx.cpu_count
if nbrCores is None:
nbrCores= cpu_count()
criteriaList = [x for x in self.criteria]
with Pool(nbrCores) as proc:
correlations = proc.map(self.computeCriterionCorrelation,criteriaList)
if ValuedCorrelation:
criteriaCorrelation = [
(correlations[i]['correlation']\
* correlations[i]['determination'],
criteriaList[i]) for i in range(len(criteriaList))]
else:
criteriaCorrelation = [
(correlations[i]['correlation'],
criteriaList[i]) for i in range(len(criteriaList))]
else:
#criteriaList = [x for x in self.criteria]
criteria = self.criteria
criteriaCorrelation = []
for c in dict.keys(criteria):
corr = self.computeCriterionCorrelation(c,Threading=False)
if ValuedCorrelation:
criteriaCorrelation.append(
( (corr['correlation']*corr['determination']) ,c) )
else:
criteriaCorrelation.append((corr['correlation'],c))
if Sorted:
criteriaCorrelation.sort(reverse=True,key=itemgetter(0))
if Normalizing:
self.recodeValuation(origValuationdomain['min'],
origValuationdomain['max'])
return criteriaCorrelation
[docs]
def showMarginalVersusGlobalOutrankingCorrelation(self,
Sorted=True,ValuedCorrelation=False,
Threading=False,
nbrOfCPUs=None,Comments=True):
"""
Show method for computeCriterionCorrelation results.
"""
from copy import deepcopy
from operator import itemgetter
if self.valuationdomain['min'] != Decimal('-1') or\
self.valuationdomain['max'] != Decimal('1'):
origValuationdomain = deepcopy(self.valuationdomain)
self.recodeValuation(-1,1)
Normalizing = True
else:
Normalizing = False
criteriaList = [x for x in self.criteria]
criteriaCorrelation = []
totCorrelation = Decimal('0.0')
for c in criteriaList:
corr = self.computeCriterionCorrelation(c,Threading=Threading,nbrOfCPUs=nbrOfCPUs)
if ValuedCorrelation:
totCorrelation += corr['correlation']*corr['determination']
criteriaCorrelation.append(((corr['correlation']*corr['determination']),c))
else:
totCorrelation += corr['correlation']
criteriaCorrelation.append((corr['correlation'],c))
if Sorted:
criteriaCorrelation.sort(reverse=True,key=itemgetter(0))
if Comments:
if ValuedCorrelation:
print('Marginal versus global outranking valued correlation')
else:
print('Marginal versus global outranking correlation')
print('criterion | weight\t correlation')
print('----------|---------------------------')
for x in criteriaCorrelation:
c = x[1]
print('%9s | %.2f \t %.3f' % (
c,self.criteria[c]['weight'],x[0]) )
print('Sum(Correlations) : %.3f' % (totCorrelation) )
print('Determinateness : %.3f' % (corr['determination']) )
if Normalizing:
self.recodeValuation(
origValuationdomain['min'],origValuationdomain['max'])
return criteriaCorrelation
[docs]
def computeQuantileSortRelation(self,Debug=False):
"""
Renders the bipolar-valued relation obtained from
the self quantile sorting result.
"""
quantileSorting = self.computeQuantileSort()
if Debug:
print(quantileSorting)
Max = Decimal('1')
Med = Decimal('0')
Min = Decimal('-1')
actions = list(self.actions.keys())
n = len(actions)
rankingRelation = {}
for x in actions:
rankingRelation[x] = {}
rrx = rankingRelation[x]
for y in actions:
rrx[y] = Med
for i in range(n):
x = quantileSorting[i][1]
for j in range(i+1,n):
y = quantileSorting[j][1]
if Debug:
print(x, y, quantileSorting[i][0], quantileSorting[j][0])
if quantileSorting[i][0] > quantileSorting[j][0]:
rankingRelation[x][y] = Max
rankingRelation[y][x] = Min
elif quantileSorting[i][0] < quantileSorting[j][0]:
rankingRelation[x][y] = Min
rankingRelation[y][x] = Max
else:
rankingRelation[x][y] = Max
rankingRelation[y][x] = Max
if Debug:
print(rankingRelation[x][y],rankingRelation[y][x])
return rankingRelation
[docs]
def showCriterionRelationTable(self,criterion, actionsSubset= None):
"""
prints the relation valuation in actions X actions table format.
"""
if actionsSubset is None:
actions = self.actions
else:
actions = actionsSubset
print('* ---- Criterion %s Relation Table -----\n' % (
criterion), end=' ')
print(' S | ', end=' ')
#actions = [x for x in actions]
actionsList = [x for x in actions]
actionsList.sort()
for x in actionsList:
print("'"+x+"', ", end=' ')
print('\n-----|------------------------------------------------------------')
for x in actionsList:
print("'"+x+"' | ", end=' ')
for y in actionsList:
#print '%2.2f ' % (self.relation[x[1]][y[1]]),
print('%2.2f ' % (
self.computeCriterionRelation(criterion,x,y)), end=' ')
print()
print('\n')
[docs]
def computeAMPLData(self,OldValuation=False):
"""
renders the ampl data list
"""
actionsList = [x for x in self.actions]
actionsList.sort()
for x in actionsList:
for y in actionsList:
print(str(x)+str(y), end=' ')
for c in self.criteria:
value = self.computeCriterionRelation(c,x,y)
if OldValuation:
value = (value + Decimal("1.0"))/Decimal("2.0")
else:
value = self.computeCriterionRelation(c,x,y)
print('%.1f' % (value), end=' ')
print()
[docs]
def computeCriterionRelation(self,c,a,b):
"""
compute the outranking characteristic for actions x and y
on criterion c.
"""
criteria = self.criteria
evaluation = self.evaluation
try:
NA = self.NA
except:
NA = Decimal('-999')
if a == b:
return Decimal("1.0")
else:
if self.evaluation[c][a] != NA and self.evaluation[c][b] != NA:
try:
indx = self.criteria[c]['thresholds']['ind'][0]
indy = self.criteria[c]['thresholds']['ind'][1]
ind = indx +indy * self.evaluation[c][a]
except:
ind = None
try:
wpx = self.criteria[c]['thresholds']['weakPreference'][0]
wpy = self.criteria[c]['thresholds']['weakPreference'][1]
wp = wpx + wpy * self.evaluation[c][a]
except:
wp = None
try:
px = self.criteria[c]['thresholds']['pref'][0]
py = self.criteria[c]['thresholds']['pref'][1]
p = px + py * self.evaluation[c][a]
except:
p = None
if self.criteria[c]['weight'] > Decimal('0.0'):
d = self.evaluation[c][a] - self.evaluation[c][b]
else:
d = self.evaluation[c][b] - self.evaluation[c][a]
if ind is None:
return self._localConcordance(d,wp,p)
else:
return self._localConcordance(d,ind,p)
else:
return Decimal("0.5")
[docs]
def computeSingletonRanking(self,Comments = False, Debug = False):
"""
Renders the sorted bipolar net determinatation of outrankingness
minus outrankedness credibilities of all singleton choices.
res = ((netdet,singleton,dom,absorb)+)
"""
import copy
from operator import itemgetter
valuationdomain = copy.deepcopy(self.valuationdomain)
self.recodeValuation(0.0,100.0)
sigs = [x[0] for x in self.singletons()]
res = []
for i in range(len(sigs)):
if Debug:
print(sigs[i], self.domin(sigs[i]) - self.absorb(sigs[i]))
res.append((self.domin(sigs[i]) - self.absorb(sigs[i]),sigs[i],self.domin(sigs[i]),self.absorb(sigs[i])))
res.sort(reverse=True,key=itemgetter(0))
if Comments:
for x in res:
print("{%s} : %.3f " % (
[y for y in x[1]][0], (float(x[0]) + 100.0)/2.0 ))
if Debug:
print(res)
self.recodeValuation(valuationdomain['min'],valuationdomain['max'])
return res
[docs]
def showSingletonRanking(self,Comments = True, Debug = False):
"""
Calls self.computeSingletonRanking(comments=True,Debug = False).
Renders and prints the sorted bipolar net determinatation of outrankingness
minus outrankedness credibilities of all singleton choices.
res = ((netdet,sigleton,dom,absorb)+)
"""
res = self.computeSingletonRanking(Comments,Debug)
return res
[docs]
def defaultDiscriminationThresholds(self, quantile = {'ind':10,'pref':20,'weakVeto':60,'veto':80}, Debug = False, comments = False):
"""
updates the discrimination thresholds with the percentiles
from the performance differences.
Parameters:
quantile = {'ind': 10, 'pref': 20, 'weakVeto': 60, 'veto: 80}.
"""
self.computeDefaultDiscriminationThresholds(quantile,Debug,comments)
self.relation = self._constructRelation(self.criteria,self.evaluation)
[docs]
def computeWeightsConcentrationIndex(self):
"""
Renders the Gini concentration index of the weight distribution
Based on the triangle summation formula.
"""
import copy
weightSum = Decimal('0.0')
p= len(self.criteria)
#p = Decimal(str(len(self.criteria)))
criteriaList = list(copy.deepcopy(self.criteria))
criteria = []
for i in range(p):
criteria.append((self.criteria[criteriaList[i]]['weight'],criteriaList[i]))
criteria.sort()
for i in range(p):
weightSum += abs(self.criteria[criteriaList[i]]['weight'])
if weightSum != Decimal('0.0'):
Q = [Decimal('0.0') for i in range(p)]
F = [Decimal('0.0') for i in range(p)]
F[0] = Decimal('1.0')/Decimal(str(p))
i = 1
Q[0] = self.criteria[criteria[0][1]]['weight']/weightSum
for i in range(1,p):
qi = self.criteria[criteria[i][1]]['weight']/weightSum
Q[i] += Q[i-1] + qi
fi = Decimal('1.0')/Decimal(str(p))
F[i] += F[i-1] + fi
i += 1
gini = Decimal('0.0')
for i in range(p-1):
gini += (F[i]*Q[i+1]) - (Q[i]*F[i+1])
else:
gini = Decimal('-1')
return gini
def _constructRelation(self,criteria,evaluation,hasNoVeto=False):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
NA = self.NA
Min = Decimal(str(self.valuationdomain['min']))
Max = Decimal(str(self.valuationdomain['max']))
totalweight = Decimal('0.0')
for c in criteria:
totalweight = totalweight + abs(criteria[c]['weight'])
vetos = []
relation = {}
for a in actions:
relation[a] = {}
for b in actions:
if a == b:
relation[a][b] = Min
else:
nc = len(criteria)
counter = Decimal('0.0')
veto = Decimal('0')
abvetos = []
for c in criteria:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
ax = criteria[c]['thresholds']['ind'][0]
ay = criteria[c]['thresholds']['ind'][1]
ind = ax + ay * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
ind = None
try:
ax = criteria[c]['thresholds']['weakPreference'][0]
ay = criteria[c]['thresholds']['weakPreference'][1]
wp = ax + ay * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
wp = None
try:
bx = criteria[c]['thresholds']['pref'][0]
by = criteria[c]['thresholds']['pref'][1]
p = bx + by * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
p = None
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
v = None
if criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
counter = counter + (lc0 * abs(criteria[c]['weight']))
testveto = self._localVeto(d,v)
if criteria[c]['weight'] > Decimal('0'):
veto = veto + testveto
if testveto == Decimal('1'):
abvetos.append((c,v,d))
else:
counter = counter + Decimal('0.5') * abs(criteria[c]['weight'])
concordindex = ((counter / totalweight) * (Max-Min)) + Min
discordindex = Min
if veto == Decimal('0'):
relation[a][b] = concordindex
else:
relation[a][b] = discordindex
vetos.append(([a,b,concordindex],abvetos))
for a in actions:
relation[a][a] = Min
self.vetos = vetos
return relation
[docs]
def computePairwiseOddsMatrix(self):
"""
renders a double dictionary with odds:
(positive chaacteristics, negative characteristics)
per actions pair.
"""
actionsList = [x for x in self.actions]
n = len(actionsList)
criteriaList = [x for x in self.criteria]
oddsMatrix = {}
for a in actionsList:
oddsMatrix[a] = {}
for b in actionsList:
if a == b:
oddsMatrix[a][b] = (Decimal('0.0'),Decimal('0.0'))
else:
plusOdds = Decimal('0.0')
minusOdds = Decimal('0.0')
for c in criteriaList:
cab = self.criterionCharacteristicFunction(c,a,b)
if cab > Decimal('0'):
plusOdds += cab
else:
minusOdds += -cab
#print(c,plusodds,minusodds)
oddsMatrix[a][b] = (plusOdds,minusOdds)
return oddsMatrix
[docs]
def computePairwiseCompleteComparison(self,a,b,c):
"""
renders pairwise complete comparison parameters for actions a and b
on criterion c.
"""
Debug = False
evaluation = self.evaluation
NA = self.NA
criteria = self.criteria
actionsList = [x for x in self.actions]
# initialize Degenne vector
pairwiseComparison = {'v':0, 'wv':0, 'lt':0, 'leq':0, 'eq':0, 'geq':0, 'gt':0, 'gwvt':0, 'gvt':0}
# main loop
if evaluation[c][a] != NA or evaluation[c][b] != NA:
# compute discrimination thresholds
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
ind = Decimal('0')
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
p = None
try:
wvx = criteria[c]['thresholds']['weakVeto'][0]
wvy = criteria[c]['thresholds']['weakVeto'][1]
wv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
wv = Decimal(str(criteria[c]['scale'][1])) + Decimal('1')
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
v = Decimal(str(criteria[c]['scale'][1])) + Decimal('1')
# compute performance difference
if criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
# compute characteristic
if d > Decimal('0'): # positive difference
if d >= v:
pairwiseComparison['gvt'] = 1
elif d >= wv:
pairwiseComparison['gwvt'] = 1
elif p is not None:
if d >= p:
pairwiseComparison['gt'] = 1
elif wp is not None:
if d >= wp:
pairwiseComparison['geq'] = 1
else:
pairwiseComparison['eq'] = 1
else:
if d > ind:
pairwiseComparison['geq'] = 1
else:
pairwiseComparison['eq'] = 1
else:
pairwiseComparison['geq'] = 1
elif d == Decimal('0'): # zero difference
pairwiseComparison['eq'] = 1
else: # negative difference
if d <= -v:
pairwiseComparison['v'] = 1
elif d <= -wv:
pairwiseComparison['wv'] = 1
elif p is not None:
if d <= -p:
pairwiseComparison['lt'] = 1
elif wp is not None:
if d <= -wp:
pairwiseComparison['leq'] = 1
else:
pairwiseComparison['eq'] = 1
else:
if d < -ind:
pairwiseComparison['leq'] = 1
else:
pairwiseComparison['eq'] = 1
else:
pairwiseComparison['leq'] = 1
else:
# missing evaluation(s)
d = None
pairwiseComparison = None
if Debug:
print('>>> c,a,b,d', c,a,b,d)
print('ind,wp,p,wv,v', ind,wp,p,wv,v)
print('pairwiseComparison', pairwiseComparison)
return pairwiseComparison
[docs]
def computeActionsComparisonCorrelations(self):
"""
renders the comparison correlations between the actions
"""
from decimal import Decimal
criteriaList = [x for x in self.criteria]
actionsList = [x for x in self.actions]
#nPairs = Decimal(str(len(criteriaList)*((len(criteriaList)-1))))
actionsCorrelationIndex = {}
for a in actionsList:
#for gi in criteriaList:
actionsCorrelationIndex[a] = {}
#for gj in criteriaList:
for b in actionsList:
actionsCorrelationIndex[a][b] = Decimal('0.0')
nPairs = Decimal('0.0')
#for a in actionsList:
for gi in criteriaList:
#for b in actionsList:
for gj in criteriaList:
if gi != gj:
nPairs += Decimal('1.0')
compi = self.computePairwiseCompleteComparison(a,b,gi)
compj = self.computePairwiseCompleteComparison(a,b,gj)
if compi is not None and compj is not None:
if compi == compj:
actionsCorrelationIndex[a][b] += Decimal('1.0')
elif (compi['gvt']==1 or compi['gwvt']==1 or compi['gt']==1) and (compj['gvt']==1 or compj['gwvt']==1 or compj['gt']==1):
actionsCorrelationIndex[a][b] += Decimal('0.5')
elif (compi['v']==1 or compi['wv']==1 or compi['lt']==1) and (compj['v']==1 or compj['wv']==1 or compj['lt']==1):
actionsCorrelationIndex[a][b] += Decimal('0.5')
else:
actionsCorrelationIndex[a][b] += Decimal('0.5')
#print nPairs,criteriaCorrelationIndex[gi][gj]
actionsCorrelationIndex[a][b] = (Decimal('2.0') * actionsCorrelationIndex[a][b] - nPairs) / nPairs
return actionsCorrelationIndex
[docs]
def computeCriteriaCorrelations(self,ValuedCorrelation=False):
"""
renders the relation equivalence or correlation between the criteria
"""
from digraphs import EquivalenceDigraph
criteriaList = [x for x in self.criteria]
actionsList = [x for x in self.actions]
nPairs = self.order*(self.order-1)
#nPairs = self.order*(self.order)
tau = {}
d = {}
for gi in criteriaList:
tau[gi] = {}
d[gi] = {}
Pgi = BipolarOutrankingDigraph(self,coalition=[gi])
for gj in criteriaList:
M = Decimal('0.0')
D = Decimal('0.0')
Pgj = BipolarOutrankingDigraph(self,coalition=[gj])
EQ = EquivalenceDigraph(Pgi,Pgj)
#nPairs = 0.0
for x in actionsList:
for y in actionsList:
#if x != y:
M += EQ.relation[x][y]
D += abs(EQ.relation[x][y])
if ValuedCorrelation:
tau[gi][gj] = M / nPairs
elif D != Decimal('0.0'):
tau[gi][gj] = M / D
else:
tau[gi][gj] = Decimal('0.0')
d[gi][gj] = D / nPairs
return tau, d
[docs]
def computeCriteriaComparisonCorrelations(self):
"""
renders the comparison correlations between the criteria
"""
criteriaList = [x for x in self.criteria]
actionsList = [x for x in self.actions]
nPairs = Decimal(str(len(actionsList)*((len(actionsList)-1))))
criteriaCorrelationIndex = {}
for gi in criteriaList:
criteriaCorrelationIndex[gi] = {}
for gj in criteriaList:
criteriaCorrelationIndex[gi][gj] = Decimal('0.0')
#nPairs = 0.0
for a in actionsList:
for b in actionsList:
if a != b:
#nPairs += 1.0
compi = self.computePairwiseCompleteComparison(a,b,gi)
compj = self.computePairwiseCompleteComparison(a,b,gj)
if compi is not None and compj is not None:
if compi == compj:
criteriaCorrelationIndex[gi][gj] += Decimal('1.0')
elif (compi['gvt']==1 or compi['gwvt']==1 or compi['gt']==1) and (compj['gvt']==1 or compj['gwvt']==1 or compj['gt']==1):
criteriaCorrelationIndex[gi][gj] += Decimal('0.5')
elif (compi['v']==1 or compi['wv']==1 or compi['lt']==1) and (compj['v']==1 or compj['wv']==1 or compj['lt']==1):
criteriaCorrelationIndex[gi][gj] += Decimal('0.5')
else:
criteriaCorrelationIndex[gi][gj] += Decimal('0.5')
#print nPairs,criteriaCorrelationIndex[gi][gj]
criteriaCorrelationIndex[gi][gj] = (Decimal('2.0') * criteriaCorrelationIndex[gi][gj] - nPairs) / nPairs
return criteriaCorrelationIndex
[docs]
def showCriteriaCorrelationTable(self,ValuedCorrelation=False,isReturningHTML=False,ndigits=3):
"""
prints the ordinal correlation index tau between criteria in table format.
"""
criteriaList = [x for x in self.criteria]
criteriaList.sort()
n = len(self.actions)
nPairs = n*n
corr,deter = self.computeCriteriaCorrelations(ValuedCorrelation=ValuedCorrelation)
html = ''
# title
if isReturningHTML:
if ValuedCorrelation:
html += '<h1>Criteria valued ordinal correlation index</h1>'
else:
html += '<h1>Criteria ordinal correlation index</h1>'
else:
if ValuedCorrelation:
print('Criteria valued ordinal correlation index')
else:
print('Criteria ordinal correlation index')
# header row
if isReturningHTML:
html += '<table border=1><tr bgcolor="#9acd32"><th>τ</th>'
else:
print(' |', end=' ')
for x in criteriaList:
if isReturningHTML:
html += '<th bgcolor="#FFEEAA">%s</th>' % (x)
else:
print('%5s ' % (x), end=' ')
if isReturningHTML:
html += '</tr>'
else:
print()
hline = '-----|'
for i in range(len(criteriaList)+1):
hline += '-------'
print(hline)
# table body
for i in range(len(criteriaList)):
if isReturningHTML:
html += '<tr><th bgcolor="#FFEEAA">%s</th>' % (criteriaList[i])
else:
print('%4s |' %(criteriaList[i]), end=' ')
for j in range(len(criteriaList)):
if i <= j:
gi = criteriaList[i]
gj = criteriaList[j]
index = corr[gi][gj]
if isReturningHTML:
if index >= 0:
html += '<td bgcolor="#ddffdd">%+2.2f</td>' %\
(index)
else:
html += '<td bgcolor="#ffddff">%+2.2f</td>' %\
(index)
else:
print(('%%+2.%df ' % ndigits) % (index), end=' ')
else:
if isReturningHTML:
html += '<td> </td>'
else:
index = ' '
print(index, end=' ')
if isReturningHTML:
html += '</tr>'
else:
print()
if isReturningHTML:
html += '</table>'
# render the result
return html
[docs]
def computeActionsCorrelationDigraph(self):
"""
renders the pairwise actions comparison digraph
"""
from digraphs import Digraph
from copy import deepcopy
corr = self.computeActionsComparisonCorrelations()
n = self.order
g = EmptyDigraph(order=n)
g.__class__ = Digraph
g.name = 'actCorrGraph'
g.valuationdomain = {'min':Decimal('-1.0'),'med':Decimal('0.0'),'max':Decimal('1.0')}
Min = g.valuationdomain['min']
Med = g.valuationdomain['med']
Max = g.valuationdomain['max']
g.actions = deepcopy(self.actions)
g.relation = self.computeActionsComparisonCorrelations()
g.gamma = g.gammaSets()
g.notGamma = g.notGammaSets()
return g
[docs]
def computeCriteriaCorrelationDigraph(self,ValuedCorrelation=True,WithMedian=False):
"""
renders the ordinal criteria correlation digraph.
"""
from digraphs import Digraph
criteriaList = [x for x in self.criteria]
criteriaList.sort()
corr,d = self.computeCriteriaCorrelations(ValuedCorrelation=ValuedCorrelation)
n = len(criteriaList)
g = EmptyDigraph(order=n)
g.__class__ = Digraph
if ValuedCorrelation:
g.name = 'valCorrGraph'
else:
gname = 'corrGraph'
g.valuationdomain = {'min':Decimal('-1.0'),'med':Decimal('0.0'),'max':Decimal('1.0')}
Min = g.valuationdomain['min']
Med = g.valuationdomain['med']
Max = g.valuationdomain['max']
if WithMedian:
criteriaList.append('m')
relation = {}
for i in range(n):
relation[criteriaList[i]] = {}
for j in range(n):
relation[criteriaList[i]][criteriaList[j]] = corr[criteriaList[i]][criteriaList[j]]
if WithMedian:
margCorr = self.computeOutrankingConsensusQuality(ValuedCorrelation=ValuedCorrelation)
#print(margCorr)
relation['m'] = {}
relation['m']['m'] = self.computeDeterminateness()
for item in margCorr[0]:
relation[item[1]]['m'] = item[0]
relation['m'][item[1]] = item[0]
g.actions = criteriaList
g.relation = relation
#g.gamma = g.gammaSets()
#g.notGamma = g.notGammaSets()
return g
[docs]
def showCriteriaHierarchy(self):
"""
shows the Rubis clustering of the ordinal criteria correlation table
"""
criteriaList = [x for x in self.criteria]
criteriaList.sort()
corr,d = self.computeCriteriaCorrelations()
n = len(criteriaList)
g = RandomValuationDigraph(order=n)
g.name = 'corrGraph'
g.valuationdomain = {'min':Decimal('-1.0'),'med':Decimal('0.0'),'max':Decimal('1.0')}
Min = g.valuationdomain['min']
Med = g.valuationdomain['med']
Max = g.valuationdomain['max']
g.actions = criteriaList
relation = {}
for i in range(n):
relation[criteriaList[i]] = {}
for j in range(n):
relation[criteriaList[i]][criteriaList[j]] = -corr[criteriaList[i]][criteriaList[j]]
g.relation = relation
g.gamma = g.gammaSets()
g.notGamma = g.notGammaSets()
g.computePreKernels()
actions = set(g.actions)
criteriaHierarchy = []
for ker in g.dompreKernels:
cluster = [y for y in ker]
cluster.sort()
degintstab = g.intstab(ker)
degextstab = g.domin(ker)
cred = min(degintstab,degextstab)
criteriaHierarchy.append((-cred,degintstab,degextstab,cluster))
criteriaHierarchy.sort()
print('*------ criteria clustering hierarchy ------*')
clustered = set()
hierarchy = []
for ch in criteriaHierarchy:
hierarchy = hierarchy + [ch[3]]
clustered |= set(ch[3])
rest = actions - clustered
print('Cluster: %s\n Credibility level: %2.2f%%; Exterior stability: %2.2f%%; Interior stability: %2.2f%%' % (str(ch[3]),
(-ch[0]+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0'),
(ch[2]+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0'),
(ch[1]+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0')))
print(' Hierarchy: ', hierarchy)
print()
[docs]
def saveActionsCorrelationTable(self,fileName='tempcorr.prn',delimiter=' ',Bipolar=True,Silent=False,Centered=False):
"""
Delimited save of correlation table
"""
import math
actionsList = [x for x in self.actions]
actionsList.sort()
n = len(actionsList)
nd = Decimal(str(n))
corr = self.computeActionsComparisonCorrelations()
if not Bipolar:
for i in range(n):
for j in range(n):
corr[actionsList[i]][actionsList[j]] = Decimal('1.0') - (corr[actionsList[i]][actionsList[j]] + Decimal('1.0'))/Decimal('2.0')
if Centered:
centcorr = [Decimal('0.0') for x in range(n)]
for i in range(n):
for j in range(n):
centcorr[i] += corr[actionsList[i]][actionsList[j]]/nd
#print centcorr
for i in range(n):
for j in range(n):
corr[actionsList[i]][actionsList[j]] = (corr[actionsList[i]][actionsList[j]]-centcorr[i])/Decimal.sqrt(nd)
#print i, corr[actionsList[i]]
fo = open(fileName,'w')
for i in range(n):
for j in range(n-1):
## if Bipolar:
value = corr[actionsList[i]][actionsList[j]]
## else:
## value = 1.0 - (corr[actionsList[i]][actionsList[j]] + 1.0)/2.0
fo.write('%2.2f %s' % (value,delimiter)),
## if Bipolar:
value = corr[actionsList[i]][actionsList[n-1]]
## else:
## value = 1.0 - (corr[actionsList[i]][actionsList[len(actionsList)-1]] + 1.0)/2.0
fo.write('%2.2f\n' % (value))
fo.close()
if not Silent:
print('Actions Correlation saved on file %s' % (fileName))
[docs]
def saveCriteriaCorrelationTable(self,fileName='tempcorr.prn',delimiter=' ',
ValuedCorrelation=False,Bipolar=True,
Silent=False,Centered=False):
"""
Delimited save of correlation table
"""
import math
criteriaList = [x for x in self.criteria]
criteriaList.sort()
#print(criteriaList)
n = len(criteriaList)
nd = Decimal(str(n))
corr,d = self.computeCriteriaCorrelations(ValuedCorrelation=ValuedCorrelation)
#print(corr)
if not Bipolar:
for i in range(n):
for j in range(n):
corr[criteriaList[i]][criteriaList[j]] = Decimal('1.0') - (corr[criteriaList[i]][criteriaList[j]] + Decimal('1.0'))/Decimal('2.0')
if Centered:
centcorr = [Decimal('0.0') for x in range(n)]
for i in range(n):
for j in range(n):
centcorr[i] += corr[criteriaList[i]][criteriaList[j]]/nd
#print centcorr
for i in range(n):
for j in range(n):
corr[criteriaList[i]][criteriaList[j]] = (corr[criteriaList[i]][criteriaList[j]]-centcorr[i])/Decimal.sqrt(nd)
#print i, corr[criteriaList[i]]
fo = open(fileName,'w')
for i in range(n):
for j in range(n-1):
## if Bipolar:
value = corr[criteriaList[i]][criteriaList[j]]
## else:
## value = 1.0 - (corr[criteriaList[i]][criteriaList[j]] + 1.0)/2.0
fo.write('%2.2f %s' % (value,delimiter)),
## if Bipolar:
value = corr[criteriaList[i]][criteriaList[n-1]]
## else:
## value = 1.0 - (corr[criteriaList[i]][criteriaList[len(criteriaList)-1]] + 1.0)/2.0
fo.write('%2.2f\n' % (value))
fo.close()
if not Silent:
print('Criteria Correlation saved on file %s' % (fileName))
[docs]
def export3DplotOfCriteriaCorrelation(self,plotFileName="critCorr",
tempDir='.',
graphType=None,
pictureFormat='pdf',
bgcolor='cornsilk',
ValuedCorrelation=False,
WithMedian=False,
Comments=False):
"""
Using R for producing a plot (pdf format by default) of the principal components of
the criteria ordinal correlation table.
*Parameters*:
* *plotFileName* := name of the created R plot image,
* *pictureFormat* := 'png' (default) | 'pdf' | 'jpeg' | 'xfig',
* *graphType* := deprecated
* *bgcolor* := 'cornsilk' by default | None,
* *ValuedCorrelation* := False (tau by default) | True (r(<=>) otherwise,
* *WithMedian* includes the marginal correlation with the global outranking relation
* *tempDir* := '.' : default current working directory.
"""
cg = CriteriaCorrelationDigraph(self,ValuedCorrelation=ValuedCorrelation,WithMedian=WithMedian)
if graphType is not None:
pictureFormat = graphType
cg.exportPrincipalImage(plotFileName=plotFileName,
tempDir=tempDir,
pictureFormat=pictureFormat,
bgcolor=bgcolor,
Comments=Comments)
def _export3DplotOfCriteriaCorrelation(self,plotFileName="correlation",
ValuedCorrelation=False,Type="pdf",
graphType='pdf',bgcolor='cornsilk',
Comments=False,bipolarFlag=False,
_dist=True,_centeredFlag=False,
tempDir=None):
"""
Obsolete and deprecated
use Calmat and R for producing a plot of the principal components of
the criteria ordinal correlation table.
*Parameters*;
* *plotFileName* := name of the created R plot image.
* deprecated ! *Type*,
* *graphType* := format of graphics file: pdf, png, jpeg, xfig,
* *bgcolor* := 'cornsilk' by default | None
* *ValuedCorrelation* := False (tau by default) | True (r(<=>) otherwise
* *tempDir* : if None the tempfile.gettempdir() is used to get the platform independent temporary directory.
* *bipolarFlag*: obsolete
* *_dist*: internal Calmat flag
* *_centeredFlag* : internal Calmat flag
"""
import time
from tempfile import TemporaryDirectory, gettempdir
import os
currentDir = os.getcwd()
#print(currentDir)
plotFileName = '%s/%s' % (currentDir,plotFileName)
Type = graphType
if Comments:
print('3DplotFileName: %s' % plotFileName)
if tempDir is None:
tempDir = gettempdir()
with TemporaryDirectory(dir=tempDir) as tempDirName:
if Comments:
print('*---- export 3dplot of type %s -----' % (Type))
os.chdir(tempDirName)
criteriaList = [x for x in self.criteria]
criteriaList.sort()
n = len(criteriaList)
fo = open('criteriaLabels.prn','w')
for key in criteriaList:
fo.write('%s ' % (key))
fo.close()
self.saveCriteriaCorrelationTable(fileName='tempcorr.prn',Silent=True,
ValuedCorrelation=ValuedCorrelation,
Bipolar=bipolarFlag,Centered=_centeredFlag)
# create Calmat script and calmat execution (the prn extension is standard)
try:
if Comments:
if _dist:
cmd = 'env defdist.sh tempcorr '+str(n)+' '+str(n)
else:
cmd = 'env defdista.sh tempcorr '+str(n)+' '+str(n)
else:
if _dist:
cmd = 'env defdist.sh tempcorr '+str(n)+' '+str(n)+' > /dev/null'
else:
cmd = 'env defdista.sh tempcorr '+str(n)+' '+str(n)+' > /dev/null'
os.system(cmd)
if Comments:
os.system('env calmat tempcorr.prg')
else:
os.system('env calmat tempcorr.prg > /dev/null')
except:
print('Error: You need to install calmat !!!')
return
# create R 3d scatter plot script
if Type == "interactive":
fo = open('scatter.r','w')
fo.write('# 3d scatter plot RB April 2008\n')
fo.write('test.mat <- matrix(scan("compolg.prn"),ncol=%d,byrow=T)\n' % (n))
## fo.write('choose<-c(%d,%d,%d)\n' % (1,2,3))
fo.write('choose<-c(%d,%d,%d)\n' % (n,n-1,n-2))
fo.write('test.labels <- scan("criteriaLabels.prn",what="character")\n')
fo.write('valprop <- matrix(scan("val_prlg.prn"),ncol=2,byrow=T)\n')
fo.write('require(rgl)\n')
fo.write('open3d()\n')
fo.write('points3d(test.mat[,choose])\n')
fo.write('text3d(test.mat[,choose],text=test.labels,col="red3")\n')
fo.write('axes3d(edges=c("x","y","z"),pos=c(0,0,0),labels=F,ticks=F)\n')
fo.write('axes3d(edges=c("x","y","z"))\n')
fo.write('title3d(main="Criteria Ordinal Correlation",xlab=valprop[choose,2][1],ylab=valprop[choose,2][2],zlab=valprop[choose,2][3],col="blue",line=4)\n')
fo.write('rgl.viewpoint(1,1/4,interactive=T)\n')
fo.write('rgl.snapshot("%s.png")\n' % (plotFileName+'.png') )
fo.close()
else:
fo = open('scatter.r','w')
fo.write('# scatter plot RB April 2008\n')
fo.write('test.mat <- matrix(scan("compolg.prn"),ncol=%d,byrow=T)\n' % (n))
fo.write('lim1 <- max(test.mat)\n')
fo.write('lim2 <- min(test.mat)\n')
if _centeredFlag:
fo.write('choose12<-c(%d,%d)\n' % (1,2))
fo.write('choose23<-c(%d,%d)\n' % (2,3))
fo.write('choose13<-c(%d,%d)\n' % (1,3))
fo.write('choose21<-c(%d,%d)\n' % (2,1))
else:
fo.write('choose12<-c(%d,%d)\n' % (n,n-1))
fo.write('choose23<-c(%d,%d)\n' % (n-1,n-2))
fo.write('choose13<-c(%d,%d)\n' % (n,n-2))
fo.write('choose21<-c(%d,%d)\n' % (n-1,n))
fo.write('test.labels <- scan("criteriaLabels.prn",what="character")\n')
fo.write('valprop <- matrix(scan("val_prlg.prn"),ncol=2,byrow=T)\n')
if Type == "png":
fo.write('png("%s.png",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "jpeg":
fo.write('jpeg("%s.jpg",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "xfig":
fo.write('xfig("%s.fig",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "pdf":
if bgcolor is not None:
fo.write('pdf("%s.pdf",width=6,height=6,bg="%s",title="PCA of Criteria Correlation Index")\n' % (plotFileName,bgcolor) )
else:
fo.write('pdf("%s.pdf",width=6,height=6,title="PCA of Criteria Correlation Index")\n' % (plotFileName) )
else:
print('Error: Plotting device %s not defined !' % (Type))
return
fo.write('par(mfrow=c(2,2))\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose12,2][1]+valprop[choose12,2][2])*100,"%"),main="factors 1 and 2",col="blue")\n')
fo.write('text(test.mat[,choose12],labels=test.labels,col="red3",cex=1.0)\n')
## fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 2:",valprop[choose23,2][1]*100,"%"),ylab=paste("axis 3:",valprop[choose23,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose23,2][1]+valprop[choose23,2][2])*100,"%"),main="factors 2 and 3",col="blue")\n')
fo.write('text(test.mat[,choose23],labels=test.labels,col="red3",cex=1.0)\n')
## fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose13,2][1]*100,"%"),ylab=paste("axis 3:",valprop[choose13,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose13,2][1]+valprop[choose13,2][2])*100,"%"),main="factors 1 and 3",col="blue")\n')
fo.write('text(test.mat[,choose13],labels=test.labels,col="red3",cex=1.0)\n')
if _centeredFlag:
fo.write('barplot(valprop[1:nrow(valprop)-1,2]*100,names.arg=c(1:%d),main="Axis inertia (in %%)",col="orangered")\n' % (n-1))
else:
fo.write('barplot(valprop[nrow(valprop):2,2]*100,names.arg=c(1:%d),main="Axis inertia (in %%)",col="orangered")\n' % (n-1))
fo.write('dev.off()\n')
fo.close()
try:
## if Comments:
## os.system('/usr/bin/R -q --vanilla --verbose < scatter.r 2>&1')
## else:
## os.system('/usr/bin/R -q --vanilla < scatter.r > /dev/null 2> /dev/null')
if Comments:
os.system('env R -q --vanilla --verbose < scatter.r 2>&1')
else:
os.system('env R -q --vanilla < scatter.r > /dev/null 2> /dev/null')
time.sleep(3)
if Comments:
print('See %s.%s ! ' % (plotFileName,Type))
except:
print('Error: You need to install R !!!')
# reset current working dir !!!
os.chdir(currentDir)
[docs]
def export3DplotOfActionsCorrelation(self,plotFileName="actCorr",
graphType=None,
pictureFormat='pdf',
bgcolor='cornsilk', Comments=False):
"""
Using R for producing a plot -pdf format by default- of the principal components of
the actions ordinal correlation table.
See export3DplotCriteriaCorrelation()
"""
from copy import deepcopy
relationOrig = deepcopy(self.relation)
self.relation = self.computeActionsComparisonCorrelations()
self.exportPrincipalImage(plotFileName=plotFileName,
pictureFormat=pictureFormat,
bgcolor=bgcolor,
Comments=Comments)
self.relation = relationOrig
def _export3DplotOfActionsCorrelation(self,plotFileName="actionsCorrelati",
Type="pdf",graphType='pdf',
pictureFormat=None,bgcolor='cornsilk',
Comments=False,bipolarFlag=False,
_dist=True,_centeredFlag=False):
"""
use Calmat and R for producing a png plot of the principal components of
the the actions ordinal correlation table.
See export3DplotCriteriaCorrelation()
"""
import time
#from tempfile import TemporaryDirectory
#with TemporaryDirectory(dir=tempDir) as tempDirName:
if pictureFormat is not None:
Type = pictureFormat
else:
Type = graphType
if Comments:
print('*---- export 3dplot of type %s -----' % (graphType))
import os
actionsList = [x for x in self.actions]
actionsList.sort()
n = len(actionsList)
fo = open('actionsLabels.prn','w')
for key in actionsList:
fo.write('%s ' % (key))
fo.close()
self.saveActionsCorrelationTable(
fileName='tempcorr.prn',Silent=True,
Bipolar=bipolarFlag,Centered=_centeredFlag)
# create Calmat script and calmat execution
# (the prn extension is standard)
try:
if Comments:
if _dist:
cmd = 'env defdist.sh tempcorr '+str(n)+' '+str(n)
else:
cmd = 'env defdista.sh tempcorr '+str(n)+' '+str(n)
else:
if _dist:
cmd = 'env defdist.sh tempcorr '+str(n)+' '+str(n)+' > /dev/null'
else:
cmd = 'env defdista.sh tempcorr '+str(n)+' '+str(n)+' > /dev/null'
os.system(cmd)
if Comments:
os.system('env calmat tempcorr.prg')
else:
os.system('env calmat tempcorr.prg > /dev/null')
except:
print('Error: You need to install calmat !!!')
return
# create R 3d scatter plot script
if Type == "interactive":
fo = open('scatter.r','w')
fo.write('# 3d scatter plot RB April 2008\n')
fo.write('test.mat <- matrix(scan("compolg.prn"),ncol=%d,byrow=T)\n' % (n))
## fo.write('choose<-c(%d,%d,%d)\n' % (1,2,3))
fo.write('choose<-c(%d,%d,%d)\n' % (n,n-1,n-2))
fo.write('test.labels <- scan("actionsLabels.prn",what="character")\n')
fo.write('valprop <- matrix(scan("val_prlg.prn"),ncol=2,byrow=T)\n')
fo.write('require(rgl)\n')
fo.write('open3d()\n')
fo.write('points3d(test.mat[,choose])\n')
fo.write('text3d(test.mat[,choose],text=test.labels,col="red3")\n')
fo.write('axes3d(edges=c("x","y","z"),pos=c(0,0,0),labels=F,ticks=F)\n')
fo.write('axes3d(edges=c("x","y","z"))\n')
fo.write('title3d(main="Actions Ordinal Correlation",xlab=valprop[choose,2][1],ylab=valprop[choose,2][2],zlab=valprop[choose,2][3],col="blue",line=4)\n')
fo.write('rgl.viewpoint(1,1/4,interactive=T)\n')
fo.write('rgl.snapshot("%s.png")\n' % (plotFileName+'.png') )
fo.close()
else:
fo = open('scatter.r','w')
fo.write('# scatter plot RB April 2008\n')
fo.write('test.mat <- matrix(scan("compolg.prn"),ncol=%d,byrow=T)\n' % (n))
fo.write('lim1 <- max(test.mat)\n')
fo.write('lim2 <- min(test.mat)\n')
if _centeredFlag:
fo.write('choose12<-c(%d,%d)\n' % (1,2))
fo.write('choose23<-c(%d,%d)\n' % (2,3))
fo.write('choose13<-c(%d,%d)\n' % (1,3))
fo.write('choose21<-c(%d,%d)\n' % (2,1))
else:
fo.write('choose12<-c(%d,%d)\n' % (n,n-1))
fo.write('choose23<-c(%d,%d)\n' % (n-1,n-2))
fo.write('choose13<-c(%d,%d)\n' % (n,n-2))
fo.write('choose21<-c(%d,%d)\n' % (n-1,n))
fo.write('test.labels <- scan("actionsLabels.prn",what="character")\n')
fo.write('valprop <- matrix(scan("val_prlg.prn"),ncol=2,byrow=T)\n')
if Type == "png":
fo.write('png("%s.png",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "jpeg":
fo.write('jpeg("%s.jpg",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "xfig":
fo.write('xfig("%s.fig",width=480,height=480,bg="cornsilk")\n' % (plotFileName) )
elif Type == "pdf":
if bgcolor is not None:
fo.write('pdf("%s.pdf",width=6,height=6,bg="%s",title="PCA of Actions Correlation Index")\n' % (plotFileName,bgcolor) )
else:
fo.write('pdf("%s.pdf",width=6,height=6,title="PCA of Actions Correlation Index")\n' % (plotFileName) )
else:
print('Error: Plotting device %s not defined !' % (Type))
return
fo.write('par(mfrow=c(2,2))\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose12,2][1]+valprop[choose12,2][2])*100,"%"),main="factors 1 and 2",col="blue")\n')
fo.write('text(test.mat[,choose12],labels=test.labels,col="red3",cex=1.0)\n')
## fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 2:",valprop[choose23,2][1]*100,"%"),ylab=paste("axis 3:",valprop[choose23,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose23,2][1]+valprop[choose23,2][2])*100,"%"),main="factors 2 and 3",col="blue")\n')
fo.write('text(test.mat[,choose23],labels=test.labels,col="red3",cex=1.0)\n')
## fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose12,2][1]*100,"%"),ylab=paste("axis 2:",valprop[choose12,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('plot(test.mat[,choose12],xlab=paste("axis 1:",valprop[choose13,2][1]*100,"%"),ylab=paste("axis 3:",valprop[choose13,2][2]*100,"%"),type="n",asp=1)\n')
fo.write('abline(h=0,v=0,col="grey",lty="dotted")\n')
fo.write('title(sub=paste("total inertia:",(valprop[choose13,2][1]+valprop[choose13,2][2])*100,"%"),main="factors 1 and 3",col="blue")\n')
fo.write('text(test.mat[,choose13],labels=test.labels,col="red3",cex=1.0)\n')
if _centeredFlag:
fo.write('barplot(valprop[1:nrow(valprop)-1,2]*100,names.arg=c(1:%d),main="Axis inertia (in %%)",col="orangered")\n' % (n-1))
else:
fo.write('barplot(valprop[nrow(valprop):2,2]*100,names.arg=c(1:%d),main="Axis inertia (in %%)",col="orangered")\n' % (n-1))
fo.write('dev.off()\n')
fo.close()
try:
## if Comments:
## os.system('/usr/bin/R -q --vanilla --verbose < scatter.r 2>&1')
## else:
## os.system('/usr/bin/R -q --vanilla < scatter.r > /dev/null 2> /dev/null')
if Comments:
os.system('env R -q --vanilla --verbose < scatter.r 2>&1')
else:
os.system('env R -q --vanilla < scatter.r > /dev/null 2> /dev/null')
time.sleep(3)
if Comments:
print('See %s.%s ! ' % (plotFileName,Type))
except:
print('Error: You need to install R !!!')
[docs]
def computePairwiseComparisons(self,hasSymmetricThresholds=True):
"""
renders pairwise comparison parameters for all pairs of actions
"""
evaluation = self.evaluation
NA = self.NA
criteria = self.criteria
actionsList = [x for x in self.actions]
pairwiseComparisons = {}
for x in actionsList:
pairwiseComparisons[x] = {}
for y in actionsList:
pairwiseComparisons[x][y] = {'lt':0, 'leq':0, 'eq':0, 'geq':0, 'gt':0}
for a in actionsList:
for b in actionsList:
for c in criteria:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx + indy *\
max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
ind = indx + indy * abs(evaluation[c][a])
except:
ind = Decimal('0')
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy *\
max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = ind + Decimal("0.00000000000001")
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = wp
if criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
if evaluation[c][a] - p >= evaluation[c][b]:
pairwiseComparisons[a][b]['gt'] += abs(criteria[c]['weight'])
elif evaluation[c][a] - wp >= evaluation[c][b]:
pairwiseComparisons[a][b]['geq'] += abs(criteria[c]['weight'])
elif evaluation[c][a] + ind >= evaluation[c][b]:
pairwiseComparisons[a][b]['eq'] += abs(criteria[c]['weight'])
elif evaluation[c][a] + p > evaluation[c][b]:
pairwiseComparisons[a][b]['leq'] += abs(criteria[c]['weight'])
elif evaluation[c][a] + p <= evaluation[c][b]:
pairwiseComparisons[a][b]['lt'] += abs(criteria[c]['weight'])
else:
print("Error: a,b,c,d,ind,wp,p",a,b,c,d,ind,wp,p)
return pairwiseComparisons
[docs]
def showPairwiseComparisonsDistributions(self):
"""
Renders the lt,leq, eq, geq, gt distributions for all pairs
"""
a = [x for x in self.actions]
a.sort()
pc = self.computePairwiseComparisons()
print(' distribution of pairwise comparisons')
print(' a b | "<" "<=" "==" ">=" ">" | "S"')
print('-----------------------------')
for i in range(len(a)):
for j in range(i+1,len(a)):
print(' %s %s | %.2f %.2f %.2f %.2f %.2f | %.2f' % (
a[i],a[j],pc[a[i]][a[j]]['lt'],
pc[a[i]][a[j]]['leq'],pc[a[i]][a[j]]['eq'],
pc[a[i]][a[j]]['geq'],pc[a[i]][a[j]]['gt'],
self.relation[a[i]][a[j]]))
print(' %s %s | %.2f %.2f %.2f %.2f %.2f | %.2f' % (
a[j],a[i],pc[a[j]][a[i]]['lt'],
pc[a[j]][a[i]]['leq'],pc[a[j]][a[i]]['eq'],
pc[a[j]][a[i]]['geq'],pc[a[j]][a[i]]['gt'],
self.relation[a[j]][a[i]]))
[docs]
def showOldPairwiseComparison(self,a,b,
Debug=False,isReturningHTML=False,
hasSymmetricThresholds=True):
"""
Obsolete: Renders the pairwise comprison parameters on all criteria
with weak preference and weak veto thresholds.
"""
evaluation = self.evaluation
NA = self.NA
criteria = self.criteria
if Debug:
print('a,b =', a, b)
if a != b:
if isReturningHTML:
html = '<h1>Pairwise Comparison</h1>'
html += '<h2>Comparing actions : (%s,%s)</h2>' % (a,b)
html += '<table style="background-color:White" border="1">'
html += '<tr bgcolor="#9acd32">'
html += '<th>crit.</th><th>wght.</th> <th>g(x)</th> <th>g(y)</th> <th>diff</th> <th>ind</th> <th>wp</th> <th>p</th> <th>concord</th> <th>wv</th> <th>v</th> <th>polarisation</th>'
html += '</tr>'
else:
print('*------------ pairwise comparison ----*')
print('Comparing actions : (%s, %s)' % (a,b))
print('crit. wght. g(x) g(y) diff \t| ind wp p concord \t| wv v weak veto veto')
print('------------------------------- \t ----------------------------- \t ----------------')
concordance = 0
sumWeights = 0
criteriaList = [x for x in criteria]
criteriaList.sort()
for c in criteriaList:
sumWeights += abs(criteria[c]['weight'])
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if self.criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
if ind is not None:
ind = round(ind,2)
if wp is not None:
wp = round(wp,2)
if p is not None:
p = round(p,2)
if isReturningHTML:
html += '<tr>'
html += '<td bgcolor="#FFEEAA" align="center">%s</td> <td>%.2f</td> <td>%2.2f</td> <td>%2.2f</td> <td>%+2.2f</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%+.2f</td>' % (c,criteria[c]['weight'],evaluation[c][a],evaluation[c][b],d, str(ind),str(wp),str(p),lc0*abs(criteria[c]['weight']))
else:
print(c, ' %.2f %2.2f %2.2f %+2.2f \t| %s %s %s %+.2f \t|' % (criteria[c]['weight'],evaluation[c][a],evaluation[c][b],d, str(ind),str(wp),str(p),lc0*abs(criteria[c]['weight'])), end=' ')
concordance = concordance + (lc0 * abs(criteria[c]['weight']))
try:
wvx = criteria[c]['thresholds']['weakVeto'][0]
wvy = criteria[c]['thresholds']['weakVeto'][1]
if hasSymmetricThresholds:
wv = wvx + wvy * max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
wv = wvx + wvy * abs(evaluation[c][a])
except:
wv = None
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
veto = self._localVeto(d,wv,v)
try:
negativeVeto = self._localNegativeVeto(d,wv,v)
hasBipolarVeto = True
except:
hasBipolarVeto = False
if hasBipolarVeto:
if v is not None:
if d >= v:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, negativeVeto))
else:
html += '<td></td> <td> %2.2f</td> <td bgcolor="#ddffdd">%+2.2f</td>' % (v, negativeVeto)
elif d <= -v:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, -veto))
else:
html += '<td></td> <td> %2.2f</td> <td bgcolor="#ffddff">%+2.2f</td>' % (v, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
elif wv is not None:
if d >= wv:
if not isReturningHTML:
print('%2.2f %+2.2f' % (wv, negativeVeto))
else:
html += '<td>%2.2f</td><td></td> <td bgcolor="#ddffdd">%+2.2f</td>' % (wv, negativeVeto)
elif d <= -wv:
if not isReturningHTML:
print('%2.2f %+2.2f' % (wv, -veto))
else:
html += '<td>%2.2f</td><td></td> <td bgcolor="#ffddff">%+2.2f</td>' % (wv, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
## unipolar case Electre III for instance
if veto > Decimal("-1.0"):
if wv is not None:
if v is not None:
if not isReturningHTML:
print(' %2.2f %2.2f %+2.2f' % (wv, v, veto))
else:
html += '<td>%2.2f</td> <td> %2.2f</td> <td bgcolor="#ffddff">%+2.2f</td>' % (wv, v, veto)
else:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (wv, -veto))
else:
html += '<td>%2.2f</td> <td></td> <td bgcolor="#ffddff">%+2.2f</td>' % (wv, -veto)
else:
if v is not None:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, veto))
else:
html += '<td></td> <td>%2.2f</td> <td bgcolor="#ffddff">%+2.2f</td>' % (v, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
if evaluation[c][a] == NA:
eval_c_a = 'NA'
else:
eval_c_a = '%2.2f' % evaluation[c][a]
if evaluation[c][b] == NA:
eval_c_b = 'NA'
else:
eval_c_b = '%2.2f' % evaluation[c][b]
if not isReturningHTML:
print(c,' %s %s' % (eval_c_a,eval_c_b))
else:
html += '<tr><td bgcolor="#FFEEAA" align="center">%s</td> <td>%.2f</td><td>%s</td><td>%s</td><td></td><td></td><td></td><td></td><td>%.2f</td></tr>' %\
(c, criteria[c]['weight'],eval_c_a,eval_c_b, self.valuationdomain['med']*criteria[c]['weight'])
if not isReturningHTML:
print(' ----------------------------------------')
print(' Valuation in range: %+.2f to %+.2f; global concordance: %+.2f' % (-sumWeights,sumWeights,concordance))
else:
html += '</tr></table>'
html += '<b>Valuation in range: %+.2f to %+.2f; global concordance: %+.2f </b>' % (-sumWeights,sumWeights,concordance)
if isReturningHTML:
return html
[docs]
def showPairwiseComparison(self,a,b,
Debug=False,isReturningHTML=False,
hasSymmetricThresholds=True):
"""
Renders the pairwise comprison parameters on all criteria
in html format
"""
evaluation = self.evaluation
NA = self.NA
criteria = self.criteria
if Debug:
print('a,b =', a, b)
if a != b:
if isReturningHTML:
html = '<h1>Pairwise Comparison</h1>'
html += '<h2>Comparing actions : (%s,%s)</h2>' % (a,b)
html += '<table style="background-color:White" border="1">'
html += '<tr bgcolor="#9acd32">'
html += '<th>crit.</th><th>wght.</th> <th>g(x)</th> <th>g(y)</th> <th>diff</th> <th>ind</th> <th>pref</th> <th>concord</th> <th>v</th> <th>polarisation</th>'
html += '</tr>'
else:
print('*------------ pairwise comparison ----*')
print('Comparing actions : (%s, %s)' % (a,b))
print('crit. wght. g(x) g(y) diff \t| ind pref r() \t| v veto')
print('------------------------------- \t ----------------------------- \t ----------------')
concordance = 0
sumWeights = 0
criteriaList = [x for x in criteria]
criteriaList.sort()
for c in criteriaList:
sumWeights += abs(criteria[c]['weight'])
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),
abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if self.criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
if ind is not None:
ind = round(ind,2)
if wp is not None:
wp = round(wp,2)
if p is not None:
p = round(p,2)
if isReturningHTML:
html += '<tr>'
html += '<td bgcolor="#FFEEAA" align="center">%s</td> <td>%.2f</td> <td>%2.2f</td> <td>%+2.2f</td> <td>%s</td> <td>%s</td> <td>%s</td> <td>%+.2f</td>' %\
(c,criteria[c]['weight'],evaluation[c][a],
evaluation[c][b],d, str(ind),str(p),
lc0*abs(criteria[c]['weight']))
else:
print(c, ' %.2f %2.2f %2.2f %+2.2f \t| %s %s %+.2f \t|' %\
(criteria[c]['weight'],evaluation[c][a],
evaluation[c][b],d, str(ind),str(p),
lc0*abs(criteria[c]['weight'])), end=' ')
concordance = concordance + (lc0 * abs(criteria[c]['weight']))
wv = None
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
veto = self._localVeto(d,wv,v)
try:
negativeVeto = self._localNegativeVeto(d,wv,v)
hasBipolarVeto = True
except:
hasBipolarVeto = False
if hasBipolarVeto:
if v is not None:
if d >= v:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, negativeVeto))
else:
html += '<td></td> <td> %2.2f</td> <td bgcolor="#ddffdd">%+2.2f</td>' % (v, negativeVeto)
elif d <= -v:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, -veto))
else:
html += '<td></td> <td> %2.2f</td> <td bgcolor="#ffddff">%+2.2f</td>' % (v, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
elif wv is not None:
if d >= wv:
if not isReturningHTML:
print('%2.2f %+2.2f' % (wv, negativeVeto))
else:
html += '<td>%2.2f</td><td></td> <td bgcolor="#ddffdd">%+2.2f</td>' % (wv, negativeVeto)
elif d <= -wv:
if not isReturningHTML:
print('%2.2f %+2.2f' % (wv, -veto))
else:
html += '<td>%2.2f</td><td></td> <td bgcolor="#ffddff">%+2.2f</td>' % (wv, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
## unipolar case Electre III for instance
if veto > Decimal("-1.0"):
if v is not None:
if not isReturningHTML:
print(' %2.2f %+2.2f' % (v, veto))
else:
html += '<td></td> <td>%2.2f</td> <td bgcolor="#ffddff">%+2.2f</td>' % (v, -veto)
else:
if not isReturningHTML:
print()
else:
html += '</tr>'
else:
if evaluation[c][a] == NA:
eval_c_a = 'NA'
else:
eval_c_a = '%2.2f' % evaluation[c][a]
if evaluation[c][b] == NA:
eval_c_b = 'NA'
else:
eval_c_b = '%2.2f' % evaluation[c][b]
if not isReturningHTML:
print(c,' %s %s' % (eval_c_a,eval_c_b))
else:
html += '<tr><td bgcolor="#FFEEAA" align="center">%s</td> <td>%.2f</td><td>%s</td><td>%s</td><td></td><td></td><td></td><td></td><td>%.2f</td></tr>' %\
(c, criteria[c]['weight'],eval_c_a,eval_c_b,
self.valuationdomain['med']*criteria[c]['weight'])
if not isReturningHTML:
print(' ----------------------------------------')
print(' Valuation in range: %+.2f to %+.2f; global concordance: %+.2f' %\
(-sumWeights,sumWeights,concordance))
else:
html += '</tr></table>'
html += '<b>Valuation in range: %+.2f to %+.2f; global concordance: %+.2f </b>' %\
(-sumWeights,sumWeights,concordance)
if isReturningHTML:
return html
[docs]
def showHTMLPairwiseComparison(self,a,b,htmlFileName=None):
"""
Exporting the pairwise comparison table of actions a and b in the default system browser. A specific file name may be provided.
"""
import webbrowser
if htmlFileName == None:
from tempfile import NamedTemporaryFile
fileName = (NamedTemporaryFile(suffix='.html',
delete=False,dir='.')).name
else:
from os import getcwd
fileName = getcwd()+'/'+htmlFileName
# if fileName is None:
# fileName = 'pairwiseComparison_%s_%s.html' % (a,b)
fo = open(fileName,'w')
fo.write(self.showPairwiseComparison(a,b,isReturningHTML=True))
fo.close()
url = 'file://'+fileName
webbrowser.open(url,new=2)
[docs]
def showPairwiseOutrankings(self,a,b,
Debug=False,isReturningHTML=False,
hasSymmetricThresholds=True):
"""
Renders the pairwise outrankings table for actions *a* and *b*.
"""
self.showPairwiseComparison(a,b,\
Debug=Debug,isReturningHTML=isReturningHTML,\
hasSymmetricThresholds=hasSymmetricThresholds)
self.showPairwiseComparison(b,a,\
Debug=Debug,isReturningHTML=isReturningHTML,\
hasSymmetricThresholds=hasSymmetricThresholds)
[docs]
def showHTMLPairwiseOutrankings(self,a,b,htmlFileName=None):
"""
Exporting the pairwise outrankings table of actions a and b
in the default system browser. A specific file name may be provided.
"""
import webbrowser
if htmlFileName == None:
from tempfile import NamedTemporaryFile
fileName = (NamedTemporaryFile(suffix='.html',
delete=False,dir='.')).name
else:
from os import getcwd
fileName = getcwd()+'/'+htmlFileName
fo = open(fileName,'w')
fo.write(self.showPairwiseComparison(a,b,isReturningHTML=True))
fo.write(self.showPairwiseComparison(b,a,isReturningHTML=True))
fo.close()
url = 'file://'+fileName
webbrowser.open(url,new=2)
[docs]
def showShort(self):
"""
specialize the general showShort method with the criteria.
"""
Digraph.showShort(self)
self.showCriteria()
self.showPerformanceTableau()
[docs]
def showAll(self):
"""
specialize the general showAll method with criteria
and performance tableau output
"""
print('*----- show detail -------------*')
print('Digraph :', self.name)
print('*---- Actions ----*')
print(self.actions)
self.showCriteria()
self.showPerformanceTableau()
print('*---- Valuation domain ----*')
print(self.valuationdomain)
self.showRelationTable()
self.showComponents()
self.showPreKernels()
[docs]
def showRelationTable(self,IntegerValues=False,
actionsSubset= None,
rankingRule=None,
Sorted=False,
hasLPDDenotation=False,
OddsDenotation=False,
StabilityDenotation=False,
hasLatexFormat=False,
hasIntegerValuation=False,
relation=None,
ReflexiveTerms=True,
fromIndex=None,
toIndex=None):
"""
Prints the relation valuation in actions X actions table format.
Copeland and NetFlows ranking rule may be applied.
"""
if hasLPDDenotation:
try:
largePerformanceDifferencesCount = self.largePerformanceDifferencesCount
except:
hasLPDDenotation = False
if OddsDenotation:
try:
oddsMatrix = self.computePairwiseOddsMatrix()
except:
OddsDenotation = False
if StabilityDenotation:
try:
stability = self.stability
except AttributeError:
robustg = RobustOutrankingDigraph(self)
stability = robustg.stability
#except:
# hasStabilityDenotation = False
## if actionsSubset is None:
## actions = self.actions
## else:
## actions = actionsSubset
if actionsSubset is None:
if rankingRule is None:
actions = self.actions
else:
if rankingRule == 'Copeland':
computeRanking = self.computeCopelandRanking
elif rankingRule == 'NetFlows':
computeRanking = self.computeNetFlowsRanking
actions = computeRanking()
Sorted = False
else:
actions = actionsSubset
if relation is None:
if hasLPDDenotation:
relation = self.relation
else:
relation = self.relation
print('* ---- Relation Table -----\n', end=' ')
if OddsDenotation:
print('r/(pro:con)| ', end=' ')
elif StabilityDenotation:
print('r/(stab)| ', end=' ')
elif hasLPDDenotation:
print('r/(lpd)| ', end=' ')
else:
print(' r | ', end=' ')
actions = [x for x in actions]
if fromIndex is None:
fromIndex = 0
if toIndex is None:
toIndex = len(actions)
actionsList = []
for x in actions[fromIndex:toIndex]:
if isinstance(x,frozenset):
try:
actionsList += [(actions[x]['shortName'],x)]
except:
actionsList += [(actions[x]['name'],x)]
else:
try:
actionsList += [(actions[x]['shortName'],x)]
except:
actionsList += [(str(x),x)]
if Sorted:
actionsList.sort()
#print actionsList
#actionsList.sort()
try:
hasIntegerValuation = self.valuationdomain['hasIntegerValuation']
except KeyError:
hasIntegerValuation = IntegerValues
for x in actionsList:
print("'"+x[0]+"' ", end=' ')
print('\n-----|------------------------------------------------------------')
for x in actionsList:
if hasLatexFormat:
print("$"+x[0]+"$ & ", end=' ')
else:
print("'"+x[0]+"' | ", end=' ')
for y in actionsList:
if x == y:
if not ReflexiveTerms:
if hasLPDDenotation:
print(' - ', end=' ')
elif hasLatexFormat:
print('$\\;-\\;$ &', end=' ')
else:
print(' - ', end=' ')
else:
Max = self.valuationdomain['max']
if hasLPDDenotation:
print(' %+.2f ' % Max, end=' ')
elif hasLatexFormat:
print('$%.2f$ &' % Max, end=' ')
else:
print(' %+.2f ' % Max, end=' ')
else:
if hasIntegerValuation:
if hasLPDDenotation:
print('%+d ' % (relation[x[1]][y[1]]), end=' ')
elif hasLatexFormat:
print('$%+d$ &' % (relation[x[1]][y[1]]), end=' ')
else:
print('%+d ' % (relation[x[1]][y[1]]), end=' ')
else:
if hasLPDDenotation:
print('%+2.2f ' % (relation[x[1]][y[1]]), end=' ')
elif hasLatexFormat:
print('$%+2.2f$ & ' % (relation[x[1]][y[1]]), end=' ')
else:
print('%+2.2f ' % (relation[x[1]][y[1]]), end=' ')
if hasLatexFormat:
print(' \\cr')
else:
print()
if hasLPDDenotation:
print(" | ", end=' ')
for y in actionsList:
print('(%+d,%+d)' % (largePerformanceDifferencesCount[x[1]][y[1]]['positive'],
largePerformanceDifferencesCount[x[1]][y[1]]['negative']),
end=' ')
print()
elif OddsDenotation:
print(" | ", end=' ')
for y in actionsList:
print('(%.0f:%.0f)' % (oddsMatrix[x[1]][y[1]][0],oddsMatrix[x[1]][y[1]][1]), end=' ')
print()
elif StabilityDenotation:
print(" | ", end=' ')
for y in actionsList:
if x == y and StabilityDenotation:
print(' (+4) ', end=' ')
else:
print(' (%+d) ' % stability[x[1]][y[1]], end=' ')
print('')
print('Valuation domain: [%.3f; %.3f]' %\
(self.valuationdomain['min'],self.valuationdomain['max']) )
if rankingRule is not None:
print('RankingRule = %s' % rankingRule)
if StabilityDenotation:
print('Stability denotation semantics:')
print(' +4|-4 : unanimous outranking | outranked situation;')
print(' +3|-3 : validated outranking | outranked situation')
print(' in each coalition of equisignificant criteria;')
print(' +2|-2 : outranking | outranked situation validated')
print(' with all potential significance weights that are')
print(' compatible with the given significance preorder;')
print(' +1|-1 : validated outranking | outranked situation with')
print(' the given significance weights;')
print(' 0 : indeterminate relational situation.')
print()
[docs]
def computeVetosShort(self):
"""
renders the number of vetoes and real vetoes in an OutrankingDigraph.
"""
Med = self.valuationdomain['med']
nv = len(self.vetos)
realveto = 0
for i in range(nv):
if self.vetos[i][0][2] > Med:
realveto += 1
return nv,realveto
[docs]
def computeVetoesStatistics(self,level=None):
"""
renders the cut level vetos in dictionary format:
vetos = {'all': n0, 'strong: n1, 'weak':n2}.
"""
Max = self.valuationdomain['max']
Min = self.valuationdomain['min']
Med = self.valuationdomain['med']
if level is None:
level = Med
else:
level = Decimal(str(level))
negLevel = Max - level + Min
nv = len(self.vetos)
weakVeto = 0
strongVeto = 0
for i in range(nv):
if self.vetos[i][0][2] > level:
strongVeto += 1
elif self.vetos[i][0][2] > negLevel:
weakVeto += 1
vetos = {}
vetos['all'] = nv
vetos['strong'] = strongVeto
vetos['weak'] = weakVeto
return vetos
[docs]
def showPolarisations(self,cutLevel=None,realVetosOnly = False):
"""
prints all negative and positive polarised situations observed in the OutrankingDigraph instance.
"""
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
try:
lpdCount = self.largePerformanceDifferencesCount
except:
print('No large performance differences count has been stored')
return
try:
vetos = self.vetos
except:
vetos = []
print('*---- Negative polarisations ----*')
#nv, realveto = self.computeVetosShort()
#vetos.sort()
nv = len(vetos)
print('number of negative polarisations : %d ' % (nv))
for i in range(nv):
print('%d: r(%s >= %s) = %.2f' %\
(i+1,vetos[i][0][0],vetos[i][0][1],vetos[i][0][2]) )
for lpd in vetos[i][1]:
print('criterion: ' + str(lpd[0]))
print('Considerable performance difference : %.2f' % lpd[1][1] )
print('Veto discrimination threshold : %.2f' % -lpd[1][3] )
if lpdCount[vetos[i][0][0]][vetos[i][0][1]]['positive'] == 0:
if vetos[i][0][2] > Med:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Med) )
else:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Min) )
else:
print('Consirable contradictory performance differences!')
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Med) )
#print('number of real vetos: %d' % (realveto))
#return nv,realveto
try:
negativeVetos = self.negativeVetos
except:
negativeVetos = []
print('\n*---- Positive polarisations ----*')
#vetos.sort()
cv = len(negativeVetos)
print('number of positive polarisations: %d ' % (cv))
for i in range(cv):
print('%d: r(%s >= %s) = %.2f' %\
(i+1,negativeVetos[i][0][0],
negativeVetos[i][0][1],negativeVetos[i][0][2]) )
for lpd in negativeVetos[i][1]:
print('criterion: ' + str(lpd[0]))
print('Considerable performance difference : %.2f' % lpd[1][1] )
print('Counter-veto threshold : %.2f' % lpd[1][3] )
if lpdCount[negativeVetos[i][0][0]][negativeVetos[i][0][1]]['negative'] == 0:
if negativeVetos[i][0][2] < Med:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(negativeVetos[i][0][0],
negativeVetos[i][0][1],negativeVetos[i][0][2],Med) )
else:
print('Polarisation: r(%s >= %s) = %.2f ==> %+.2f' %\
(negativeVetos[i][0][0],
negativeVetos[i][0][1],negativeVetos[i][0][2],Max) )
else:
print('Consirable contradictory performance differences!')
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(negativeVetos[i][0][0],
negativeVetos[i][0][1],negativeVetos[i][0][2],Med) )
[docs]
def showVetos(self,cutLevel=None,realVetosOnly = False):
"""
prints all veto and counter-veto situations observed in the OutrankingDigraph instance.
"""
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
lpdCount = self.largePerformanceDifferencesCount
print('*---- Veto situations ---')
#nv, realveto = self.computeVetosShort()
vetos = self.vetos
#vetos.sort()
nv = len(vetos)
print('number of veto situations : %d ' % (nv))
for i in range(nv):
print('%d: r(%s >= %s) = %.2f' %\
(i+1,vetos[i][0][0],vetos[i][0][1],vetos[i][0][2]) )
for lpd in vetos[i][1]:
print('criterion: ' + str(lpd[0]))
print('Considerable performance difference : %.2f' % lpd[1][1] )
print('Veto discrimination threshold : %.2f' % -lpd[1][3] )
if lpdCount[vetos[i][0][0]][vetos[i][0][1]]['positive'] == 0:
if vetos[i][0][2] > Med:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Med) )
else:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Min) )
else:
print('Consirable contradictory performance differences!')
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(vetos[i][0][0],vetos[i][0][1],vetos[i][0][2],Med) )
#print('number of real vetos: %d' % (realveto))
#return nv,realveto
print('\n*---- Counter-veto situations ---')
negativeVetos = self.negativeVetos
#vetos.sort()
cv = len(negativeVetos)
print('number of counter-veto situations : %d ' % (nv))
for i in range(cv):
print('%d: r(%s >= %s) = %.2f' %\
(i+1,negativeVetos[i][0][0],\
negativeVetos[i][0][1],negativeVetos[i][0][2]) )
for lpd in negativeVetos[i][1]:
print('criterion: ' + str(lpd[0]))
print('Considerable performance difference : %.2f' % lpd[1][1] )
print('Counter-veto threshold : %.2f' % lpd[1][3] )
if lpdCount[negativeVetos[i][0][0]][negativeVetos[i][0][1]]['negative'] == 0:
if negativeVetos[i][0][2] < Med:
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(negativeVetos[i][0][0],\
negativeVetos[i][0][1],negativeVetos[i][0][2],Med) )
else:
print('Polarisation: r(%s >= %s) = %.2f ==> %+.2f' %\
(negativeVetos[i][0][0],\
negativeVetos[i][0][1],negativeVetos[i][0][2],Max) )
else:
print('Consirable contradictory performance differences!')
print('Polarisation: r(%s >= %s) = %.2f ==> %.2f' %\
(negativeVetos[i][0][0],\
negativeVetos[i][0][1],negativeVetos[i][0][2],Med) )
[docs]
def saveXMCDA2RubisChoiceRecommendation(self,fileName='temp',
category='Rubis',subcategory='Choice Recommendation',
author='digraphs Module (RB)',reference='saved from Python',
comment=True,servingD3=False,relationName='Stilde',
graphValuationType='bipolar',variant='standard',
instanceID='void',stringNA='NA',_OldCoca=True,
Debug=False):
"""
save complete Rubis problem and result in XMCDA 2.0 format with unicode encoding.
*Warning*: obsolete now!
"""
import codecs,copy
# save a copy of self
selfOrig = copy.deepcopy(self)
if Debug:
print('Debug sel orig:', self.__dict__.keys())
# the next command augments self with chordless circuits,
# the case given ! _OldCoca is True by default for compatibility reasons
# with the old Rubis web services in D3 and D4 for instance !
self.computeRubyChoice(_OldCoca=_OldCoca)
if Debug:
print('Debug after computeRubyChoice:', self.__dict__.keys())
if isinstance(self,RobustOutrankingDigraph):
category = 'Robust Rubis'
if comment:
print('*----- saving digraph in XMCDA 2.0 format -------------*')
nameExt = fileName+'.xmcda2'
#nameExt = fileName+'.xml'
fo = codecs.open(nameExt,'w',encoding='utf-8')
fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
if category=='Rubis':
if not servingD3:
fo.write('<?xml-stylesheet type="text/xsl" href="xmcda2RubisChoice.xsl"?>\n')
else:
fo.write('<!-- ?xml-stylesheet type="text/xsl" href="xmcda2RubisChoice.xsl"? -->\n')
elif category=='Robust Rubis':
if not servingD3:
fo.write('<?xml-stylesheet type="text/xsl" href="xmcda2RubisRobustChoice.xsl"?>\n')
else:
fo.write('<!-- ?xml-stylesheet type="text/xsl" href="xmcda2RubisRobustChoice.xsl"? -->\n')
else:
if not servingD3:
fo.write('<?xml-stylesheet type="text/xsl" href="xmcda2Rubis.xsl"?>\n')
else:
fo.write('<!-- ?xml-stylesheet type="text/xsl" href="xmcda2Rubis.xsl"? -->\n')
fo.write('<xmcda:XMCDA xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n xsi:schemaLocation="http://www.decision-deck.org/2009/XMCDA-2.0.0 http://www.decision-deck.org/xmcda/_downloads/XMCDA-2.0.0.xsd"\n xmlns:xmcda="http://www.decision-deck.org/2009/XMCDA-2.0.0" instanceID="%s">\n' % str(instanceID) )
# write description
try:
projectName = self.description['name']
except:
projectName = self.name
try:
projectID = self.description['id']
except:
projectID = fileName
fo.write('<projectReference id="%s" name="%s">\n' % (str(projectID),str(projectName)))
# titles
if category == 'Rubis':
title = 'Rubis Best Choice Recommendation'
elif category == 'Robust Rubis':
title = 'Condorcet Robustness of a Rubis Best Choice Recommendation'
fo.write('<%s>%s</%s>\n' % ('title', str(title),'title') )
try:
fo.write('<%s>%s</%s>\n' % ('subTitle', str(self.description['title']),'subTitle') )
except:
pass
try:
fo.write('<%s>%s</%s>\n' % ('subSubTitle', str(self.description['subTitle']),'subSubTitle') )
except:
pass
# rest of case description including the bibliography
try:
for entry in self.description:
if entry == 'bibliography':
fo.write('<bibliography>\n')
for bibEntry in [x for x in self.description[entry]]:
if bibEntry == 'description':
fo.write('<description><subSubTitle>%s</subSubTitle></description>\n' %\
(str(self.description['bibliography']['description']['subSubTitle'])) )
else:
fo.write('<bibEntry>%s</bibEntry>\n' %\
(str(self.description['bibliography'][bibEntry])) )
fo.write('</bibliography>\n')
elif entry != 'title' and entry != 'subTitle' and entry != 'subSubTitle' and entry != 'name' and entry != 'id' and entry != 'type':
fo.write('<%s>%s</%s>\n' % (entry, str(self.description[entry]),entry) )
except:
fo.write('<author>%s</author>\n' % (str(author)) )
fo.write('<version>%s</version>\n' % (str(reference)) )
fo.write('</projectReference>\n')
# write methodParameters
if category == 'Robust Rubis':
fo.write('<methodParameters id="%s" name="%s">\n' % ('Robust Rubis','Robustness analysis of Rubis best choice method'))
fo.write('<description>\n')
fo.write('<subTitle>Method data</subTitle>\n')
fo.write('<comment>Results of Condorcet robustness analysis in XMCDA format.</comment>\n')
else:
fo.write('<methodParameters id="%s" name="%s">\n' % ('Rubis','Rubis best choice method') )
fo.write('<description>\n')
fo.write('<subTitle>Method data</subTitle>\n')
fo.write('<comment>Rubis best choice recommendation in XMCDA format.</comment>\n')
fo.write('<version>%s</version>\n' % ('1.0'))
fo.write('</description>\n')
fo.write('<parameters>\n')
try:
variant = self.methodData['parameter']['variant']
fo.write('<parameter name="%s">\n' % ('variant'))
fo.write('<value>\n')
fo.write('<label>%s</label>\n' % (variant) )
fo.write('</value>\n')
fo.write('</parameter>\n')
except:
pass
try:
valuationType = self.methodData['parameter']['valuationType']
fo.write('<parameter name="%s">\n' % ('valuationType') )
fo.write('<value>\n')
fo.write('<label>%s</label>\n' % (valuationType) )
fo.write('</value>\n')
fo.write('</parameter>\n')
except:
pass
try:
vetoType = self.methodData['parameter']['vetoType']
fo.write('<parameter name="%s">\n' % ('vetoType') )
fo.write('<value>\n')
fo.write('<label>%s</label>\n' % (vetoType) )
fo.write('</value>\n')
fo.write('</parameter>\n')
except:
pass
fo.write('</parameters>\n')
fo.write('</methodParameters>\n')
# write potential actions
origActionsList = [x for x in self.actions_orig]
origActionsList.sort()
fo.write('<alternatives mcdaConcept="alternatives">\n')
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('List of Alternatives'))
fo.write('<subTitle>Potential decision actions.</subTitle>\n')
#fo.write('<type>%s</type>\n' % ('alternatives'))
fo.write('</description>\n')
for x in origActionsList:
try:
alternativeName=self.actions_orig[x]['name']
except:
alternativeName=x
fo.write('<alternative id="%s" name="%s">\n' % (str(x),str(alternativeName)) )
fo.write('<description>\n')
fo.write('<comment>')
try:
fo.write(str(self.actions_orig[x]['comment']))
except:
fo.write('potential decision actions')
fo.write('</comment>\n')
fo.write('</description>\n')
fo.write('<type>real</type>\n')
fo.write('<active>true</active>\n')
fo.write('</alternative>\n')
fo.write('</alternatives>\n')
# coca actions if any
cocaActionsList = [x for x in self.actions if isinstance(x,frozenset)]
if Debug:
print('Debug: cocaActionsList',cocaActionsList)
if cocaActionsList != []:
cocaActionsList.sort()
fo.write('<alternatives mcdaConcept="%s">\n' % ('cocaActions'))
fo.write('<description>\n')
fo.write('<subTitle>%s</subTitle>\n' % ('Coca digraph actions'))
#fo.write('<type>%s</type>\n' % ('cocaActions'))
fo.write('<comment>Chordless odd circuits added to the original outranking digraph.</comment>\n')
fo.write('</description>\n')
for x in cocaActionsList:
fo.write('<alternative id="%s" name="%s">\n' %\
(str(self.actions[x]['name']),str(self.actions[x]['name'])) )
fo.write('<description>\n')
fo.write('<comment>%s</comment>\n' % (str(self.actions[x]['comment'])) )
fo.write('</description>\n')
fo.write('<type>fictive</type>\n')
fo.write('</alternative>\n')
fo.write('</alternatives>\n')
# save objectives if there are any
try:
objectivesList = [x for x in self.objectives]
objectivesList.sort()
objectives = self.objectives
fo.write('<objectives mcdaConcept="objectives">\n')
fo.write('<description>\n')
fo.write('<subTitle>Set of decision objectives</subTitle>\n')
fo.write('</description>\n')
for obj in objectivesList:
try:
objectiveName = str(objectives[obj]['name'])
except:
objectiveName = str(obj)
fo.write('<objective id="%s" name="%s" mcdaConcept="%s">\n' % (str(obj),objectiveName,'objective' ) )
fo.write('<description>\n')
try:
fo.write('<comment>%s</comment>\n' % (str(objective[obj]['comment'])) )
except:
fo.write('<comment>%s</comment>\n' % ('No comment') )
fo.write('<version>%s</version>\n' % ('Rubis') )
fo.write('</description>\n')
fo.write('<active>true</active>\n')
fo.write('<weight><value><real>%.2f</real></value></weight>\n' %\
(objectives[obj]['weight']) )
try:
objCriteria = [x for x in self.criteria if self.criteria[x]['objective'] == obj]
fo.write('<objectiveCriteria>%s</objectiveCriteria>\n' %\
(str(objCriteria)) )
except:
pass
fo.write('</objective>\n')
fo.write('</objectives>\n')
except:
pass
# save criteria
criteriaList = [x for x in self.criteria]
criteriaList.sort()
criteria = self.criteria
fo.write('<criteria mcdaConcept="%s">\n' % ('criteria'))
fo.write('<description>\n')
fo.write('<title>Rubis family of criteria.</title>\n')
#fo.write('<type>%s</type>\n' % ('criteria'))
fo.write('</description>\n')
for g in criteriaList:
critg = criteria[g]
try:
criterionName = str(critg['name'])
except:
criterionName = 'nameless'
fo.write('<criterion id="%s" name="%s" mcdaConcept="%s">\n' %\
(g,criterionName,'criterion') )
fo.write('<description>\n')
try:
fo.write('<comment>%s</comment>\n' % (str(critg['comment'])) )
except:
fo.write('<comment>%s</comment>\n' % ('no comment') )
fo.write('<version>%s</version>\n' % ('performance') )
fo.write('</description>\n')
fo.write('<active>true</active>\n')
try:
fo.write('<criterionObjective>%s</criterionObjective>\n' %\
critg['objective'])
except:
pass
try:
if critg['IntegerWeights']:
fo.write('<criterionValue><value><integer>%d</integer></value></criterionValue>\n' %\
(critg['weight']) )
else:
fo.write('<criterionValue><value><real>%.2f</real></value></criterionValue>\n' %\
(critg['weight']) )
except:
fo.write('<criterionValue><value><real>%.2f</real></value></criterionValue>\n' %\
(critg['weight']) )
#fo.write('<criterionFunction category="%s" subCategory="%s" >\n' % ('Rubis','performance'))
fo.write('<scale>\n')
fo.write('<quantitative>\n')
try:
fo.write('<preferenceDirection>%s</preferenceDirection>\n' %\
(critg['preferenceDirection']) )
if critg['preferenceDirection'] == 'min':
#pdir = -1
pdir = 1
else:
pdir = 1
except:
fo.write('<preferenceDirection>%s</preferenceDirection>\n' % ('max') )
pdir = 1
fo.write('<minimum><real>%.2f</real></minimum>\n' % (critg['scale'][0]) )
fo.write('<maximum><real>%.2f</real></maximum>\n' % (critg['scale'][1]) )
fo.write('</quantitative>\n')
fo.write('</scale>\n')
fo.write('<thresholds>\n')
try:
if critg['thresholds']['ind'] is not None:
fo.write('<threshold id="%s">\n' % ('ind'))
if critg['thresholds']['ind'][1] != Decimal('0.0'):
fo.write('<linear>\n')
fo.write('<slope><real>%.2f</real></slope>\n' %\
(pdir*critg['thresholds']['ind'][1]) )
fo.write('<intercept><real>%.2f</real></intercept>\n' %\
(critg['thresholds']['ind'][0]) )
fo.write('</linear>\n')
else:
fo.write('<constant>\n')
fo.write('<real>%.2f</real>\n' % (critg['thresholds']['ind'][0]) )
fo.write('</constant>\n')
fo.write('</threshold>\n')
except:
pass
try:
if critg['thresholds']['weakPreference'] is not None:
fo.write('<threshold id="%s">\n' % ('weakPreference'))
if critg['thresholds']['weakPreference'][1] != Decimal('0.0'):
fo.write('<linear>\n')
fo.write('<slope><real>%.2f</real></slope>\n' %\
(pdir*critg['thresholds']['weakPreference'][1]) )
fo.write('<intercept><real>%.2f</real></intercept>\n' %\
(critg['thresholds']['weakPreference'][0]) )
fo.write('</linear>\n')
else:
fo.write('<constant>\n')
fo.write('<real>%.2f</real>\n' %\
(critg['thresholds']['weakPreference'][0]) )
fo.write('</constant>\n')
fo.write('</threshold>\n')
except:
pass
try:
if critg['thresholds']['pref'] is not None:
fo.write('<threshold id="%s">\n' % ('pref'))
if critg['thresholds']['pref'][1] != Decimal('0.0'):
fo.write('<linear>\n')
fo.write('<slope><real>%.2f</real></slope>\n' %\
(pdir*critg['thresholds']['pref'][1]) )
fo.write('<intercept><real>%.2f</real></intercept>\n' %\
(critg['thresholds']['pref'][0]) )
fo.write('</linear>\n')
else:
fo.write('<constant>\n')
fo.write('<real>%.2f</real>\n' %\
(critg['thresholds']['pref'][0]) )
fo.write('</constant>\n')
fo.write('</threshold>\n')
except:
pass
try:
if critg['thresholds']['weakVeto'] is not None:
fo.write('<threshold id="%s">\n' % ('weakVeto'))
if critg['thresholds']['weakVeto'][1] != Decimal('0.0'):
fo.write('<linear>\n')
fo.write('<slope><real>%.2f</real></slope>\n' %\
(pdir*critg['thresholds']['weakVeto'][1]) )
fo.write('<intercept><real>%.2f</real></intercept>\n' %\
(critg['thresholds']['weakVeto'][0]) )
fo.write('</linear>\n')
else:
fo.write('<constant>\n')
fo.write('<real>%.2f</real>\n' %\
(critg['thresholds']['weakVeto'][0]) )
fo.write('</constant>\n')
fo.write('</threshold>\n')
except:
pass
try:
if critg['thresholds']['veto'] is not None:
fo.write('<threshold id="%s">\n' % ('veto'))
if critg['thresholds']['veto'][1] != Decimal('0.0'):
fo.write('<linear>\n')
fo.write('<slope><real>%.2f</real></slope>\n' %\
(pdir*critg['thresholds']['veto'][1]) )
fo.write('<intercept><real>%.2f</real></intercept>\n' %\
(critg['thresholds']['veto'][0]) )
fo.write('</linear>\n')
else:
fo.write('<constant>\n')
fo.write('<real>%.2f</real>\n' %\
(critg['thresholds']['veto'][0]) )
fo.write('</constant>\n')
fo.write('</threshold>\n')
except:
pass
fo.write('</thresholds>\n')
#fo.write('</criterionFunction>\n')
fo.write('</criterion>\n')
fo.write('</criteria>\n')
# save performance table
evaluation = self.evaluation
NA = self.NA
fo.write('<performanceTable mcdaConcept="%s">\n' %('performanceTable') )
fo.write('<description>\n')
fo.write('<title>Rubis Performance Table</title>\n')
fo.write('</description>\n')
for i in range(len(origActionsList)):
fo.write('<alternativePerformances>\n')
fo.write('<alternativeID>'+str(origActionsList[i])+'</alternativeID>\n')
for g in criteriaList:
fo.write('<performance>\n')
fo.write('<criterionID>')
fo.write(g)
fo.write('</criterionID>\n')
val = evaluation[g][origActionsList[i]]
if val == NA:
fo.write('<value><NA>')
fo.write('%s' % stringNA )
fo.write('</NA></value>\n')
else:
try:
if self.criteria[g]['preferenceDirection'] == 'min':
if self.criteria[g]['weight'] > Decimal('0'):
pdir = Decimal('-1')
else:
pdir = Decimal('1')
else:
pdir = Decimal('1')
except:
pdir = Decimal('1')
fo.write('<value><real>')
fo.write('%.2f' % (pdir*evaluation[g][origActionsList[i]]) )
fo.write('</real></value>\n')
fo.write('</performance>\n')
fo.write('</alternativePerformances>\n')
fo.write('</performanceTable>\n')
# criteria ordinal correlation analysis
if category != 'Robust Rubis':
corr,d = selfOrig.computeCriteriaCorrelations()
criteriaList = [x for x in self.criteria]
cn = len(criteriaList)
criteriaList.sort()
criteria = self.criteria
fo.write('<criteriaComparisons mcdaConcept="%s">\n' % ('correlationTable'))
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('Ordinal Criteria Correlation Index'))
fo.write('<comment>%s</comment>\n' %\
('Generalisation of Kendall's τ to nested homogeneous semiorders.') )
fo.write('</description>\n')
fo.write('<comparisonType>%s</comparisonType>' % ('correlation'))
fo.write('<pairs>\n')
for ci in range(cn):
for cj in range(cn):
fo.write('<pair>\n')
fo.write('<initial><criterionID>')
fo.write(str(criteriaList[ci]))
fo.write('</criterionID></initial>\n')
fo.write('<terminal><criterionID>')
fo.write(str(criteriaList[cj]))
fo.write('</criterionID></terminal>\n')
fo.write('<value><real>%2.2f' %\
(corr[criteriaList[ci]][criteriaList[cj]]) )
fo.write('</real></value>\n')
fo.write('</pair>\n')
fo.write('</pairs>\n')
fo.write('</criteriaComparisons>\n')
# outranking digraph
if category != 'Robust Rubis':
fo.write('<alternativesComparisons name="%s" mcdaConcept="%s">\n' %\
(relationName,'outrankingDigraph'))
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('Bipolar-valued Outranking Relation'))
#fo.write('<name>%s</name>\n' % (relationName) )
#fo.write('<type>%s</type>\n' % ('outrankingDigraph'))
fo.write('<comment>%s %s Relation</comment>\n' % (category,subcategory) )
else:
fo.write('<alternativesComparisons name="%s" mcdaConcept="%s">\n' %\
(relationName,'CondorcetRobustness') )
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('Valued Outranking Relation'))
#fo.write('<name>%s</name>\n' % (relationName) )
#fo.write('<type>%s</type>\n' % ('outrankingDigraph'))
fo.write('<comment>%s</comment>\n' % ('with Condorcet Robustness denotation') )
fo.write('</description>\n')
fo.write('<valuation>\n')
fo.write('<description>\n')
fo.write('<subTitle>%s</subTitle>\n' % ('Valuation Domain'))
if category != 'Robust Rubis':
fo.write('<comment>%s</comment>\n' % ('Significance degrees') )
else:
fo.write('<comment>%s</comment>\n' % ('Condorcet robustness degrees') )
fo.write('</description>\n')
fo.write('<valuationType>%s</valuationType>\n' % (graphValuationType) )
Max = self.valuationdomain['max']
Min = self.valuationdomain['min']
fo.write('<quantitative>')
if category == 'Robust Rubis':
fo.write('<minimum><real>%d</real></minimum>' % (Min))
fo.write('<maximum><real>%d</real></maximum>' % (Max))
else:
fo.write('<minimum><real>%2.2f</real></minimum>' % (Min))
fo.write('<maximum><real>%2.2f</real></maximum>' % (Max))
fo.write('</quantitative>\n')
fo.write('</valuation>\n')
fo.write('<pairs>\n')
fo.write('<description>\n')
fo.write('<subTitle>%s</subTitle>\n' % ('Valued Adjacency Table'))
try:
category = self.category
subcategory = self.subcategory
except:
pass
if category == 'Robust Rubis':
fo.write('<comment>Pairwise outranking significance degrees in the range: %d to %d with Condorcet robustness degrees shown in brackets. </comment>\n' %\
(self.cardinalValuationdomain['min'],self.cardinalValuationdomain['max']) )
else:
fo.write('<comment>Pairwise outranking significance degrees in the range: %2.2f to %2.2f</comment>\n' % (Min,Max) )
fo.write('</description>\n')
relation = self.relation
if category == 'Robust Rubis':
cardinalRelation = self.cardinalRelation
for x in origActionsList:
for y in origActionsList:
fo.write('<pair>\n')
fo.write('<initial><alternativeID>')
fo.write(str(x))
fo.write('</alternativeID></initial>\n')
fo.write('<terminal><alternativeID>')
fo.write(str(y))
fo.write('</alternativeID></terminal>\n')
try:
if self.methodData['parameter']['valuationType'] == 'integer':
fo.write('<value><integer>%d</integer></value>' % (relation[x][y]) )
elif category == 'Robust Rubis':
fo.write('<values>\n')
if x != y:
fo.write('<value name="outranking"><integer>%d</integer></value>\n' %\
(cardinalRelation[x][y]) )
fo.write('<value name="robustness"><integer>%d</integer></value>\n' %\
(relation[x][y]) )
else:
fo.write('<value name="outranking"><integer>%d</integer></value>\n' % (100) )
fo.write('<value name="robustness"><integer>%d</integer></value>\n' % (3) )
fo.write('</values>\n')
else:
fo.write('<value><real>%2.2f</real></value>' % (relation[x][y]) )
except:
if category == 'Robust Rubis':
fo.write('<values>\n')
fo.write('<value name="outranking"><real>%2.2f</real></value>\n' % (cardinalRelation[x][y]) )
if x == y:
fo.write('<value name="outranking"><real>%2.2f</real></value>\n' % (100) )
fo.write('<value name="robustness"><integer>%d</integer></value>\n' % (3) )
else:
fo.write('<value name="outranking"><real>%2.2f</real></value>\n' %\
(cardinalRelation[x][y]) )
fo.write('<value name="robustness"><integer>%d</integer></value>\n' %\
(int(relation[x][y])) )
fo.write('</values>\n')
else:
fo.write('<value><real>%2.2f' % (relation[x][y]) )
fo.write('</real></value>\n')
fo.write('</pair>\n')
fo.write('</pairs>\n')
fo.write('</alternativesComparisons>\n')
# vetos if any
try:
vetos = self.vetos
if vetos != []:
Med = self.valuationdomain['med']
fo.write('<alternativesComparisons mcdaConcept="Vetoes">\n')
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('Vetoes'))
fo.write('</description>\n')
fo.write('<pairs>\n')
fo.write('<description>\n')
fo.write('<subTitle>%s</subTitle>\n' % ('Effective and potential veto situations'))
fo.write('</description>\n')
for veto in vetos:
arc = veto[0]
fo.write('<pair>\n')
fo.write('<description>\n')
fo.write('<comment>concordance degree:%.2f</comment>\n' % (arc[2]) )
fo.write('</description>\n')
fo.write('<initial><alternativeID>')
fo.write(str(arc[0]))
fo.write('</alternativeID></initial>\n')
fo.write('<terminal><alternativeID>')
fo.write(str(arc[1]))
fo.write('</alternativeID></terminal>\n')
situations = veto[1]
for v in situations:
fo.write('<values id="%s">\n' % ( str(v[0]) ) )
fo.write('<description>\n')
fo.write('<comment>')
if arc[2] > Med:
if v[1][0] > Decimal('0'):
fo.write('effective veto')
else:
fo.write('effective weak veto')
elif arc[2] == Med:
if v[1][0] > Decimal('0'):
fo.write('effective veto')
else:
fo.write('potential weak veto')
else:
if v[1][0] > Decimal('0'):
fo.write('potential veto')
else:
fo.write('potential weak veto')
fo.write('</comment>\n')
fo.write('</description>\n')
fo.write('<value name="%s">\n' % ('performanceDifference'))
fo.write('<real>%.2f</real>' % (v[1][1]))
fo.write('</value>\n')
fo.write('<value name="%s">\n' % ('vetoCharacteristic'))
fo.write('<real>%.2f</real>' % (v[1][0]))
fo.write('</value>\n')
fo.write('</values>\n')
fo.write('</pair>\n')
fo.write('</pairs>\n')
fo.write('</alternativesComparisons>\n')
except:
pass
# good choices
amplitude = (Max - Min) / Decimal('100.0')
fo.write('<alternativesSets mcdaConcept="%s">\n' % ('goodChoices'))
fo.write('<description>\n')
fo.write('<title>%s</title>\n' % ('Rubis Choice Recommendation'))
if category == 'Robust Rubis':
fo.write('<comment>In decreasing order of determinateness.</comment>\n')
else:
fo.write('<comment>In decreasing order of determinateness. All values expressed in %. </comment>\n')
fo.write('</description>\n')
nb = Decimal('0')
maxDet = Decimal('0.0')
for ch in self.goodChoices:
maxDet = max(maxDet,ch[0])
for ch in self.goodChoices:
#if ch[3] > -ch[4]:
nb += 1
fo.write('<alternativesSet id="good_%d" mcdaConcept="%s">\n' % (nb,'goodChoice') )
fo.write('<description>\n')
if category == 'Robust Rubis':
determ = (ch[0]*Decimal('6')) - Decimal('3')
if determ > Decimal('1'):
fo.write('<comment>Robust good choice</comment>\n')
else:
fo.write('<comment>Potential good choice</comment>\n')
else:
if maxDet == ch[0]:
fo.write('<comment>Best choice</comment>\n')
else:
fo.write('<comment>Potential good choice</comment>\n')
fo.write('</description>\n')
for x in ch[5]:
fo.write('<element>\n')
fo.write('<alternativeID>')
if isinstance(x,frozenset):
#print(self.actions[x])
fo.write(str(self.actions[x]['name']))
else:
fo.write(str(x))
fo.write('</alternativeID>\n')
fo.write('</element>\n')
if category == 'Robust Rubis':
fo.write('<values>\n')
independent = ch[2]
fo.write('<value name="choiceSet independence"><integer>%d</integer></value>\n' %(independent) )
outranking = ch[3]
fo.write('<value name="outranking"><integer>%d</integer></value>\n' %(outranking) )
outranked = ch[4]
fo.write('<value name="outranked"><integer>%d</integer></value>\n' % (outranked) )
determ = (ch[0]*Decimal('6'))-Decimal('3')
fo.write('<value name="determinateness"><real>%2.2f</real></value>\n' % (determ) )
fo.write('</values>\n')
else:
fo.write('<values>\n')
independent = (ch[2] - Min) / amplitude
fo.write('<value name="choiceSet independence"><real>%2.2f</real></value>\n' %(independent) )
outranking = (ch[3] - Min) / amplitude
fo.write('<value name="outranking"><real>%2.2f</real></value>\n' %(outranking) )
outranked = (ch[4] - Min) / amplitude
fo.write('<value name="outranked"><real>%2.2f</real></value>\n' %(outranked) )
determ = ch[0]*Decimal('100.0')
fo.write('<value name="determinateness"><real>%2.2f</real></value>\n' %(determ) )
fo.write('</values>\n')
fo.write('</alternativesSet>\n')
fo.write('</alternativesSets>\n')
# bad choices if any
if self.badChoices != []:
#amplitude = float(Max - Min)/float(100.0)
fo.write('<alternativesSets mcdaConcept="badChoices">\n')
fo.write('<description>\n')
fo.write('<subTitle>%s</subTitle>\n' % ('Potentially Bad Choices'))
if category != 'Robust Rubis':
fo.write('<comment>All values expressed in %.</comment>\n')
fo.write('</description>\n')
nb = 0
for ch in self.badChoices:
#if -ch[3] > ch[4]:
nb += 1
fo.write('<alternativesSet id="bad_%d" mcdaConcept="badChoice">\n' % (nb) )
fo.write('<description>\n')
if category == 'Robust Rubis':
determ = (ch[0]*Decimal('6'))-Decimal('3')
if determ > Decimal('1'):
fo.write('<comment>Robust bad choice</comment>\n')
else:
fo.write('<comment>Potential bad choice</comment>\n')
else:
if ch[4] > ch[3]:
fo.write('<comment>Bad choice</comment>\n')
else:
fo.write('<comment>Ambiguous choice</comment>\n')
fo.write('</description>\n')
for x in ch[5]:
fo.write('<element>\n')
fo.write('<alternativeID>')
if isinstance(x,frozenset):
fo.write(str(self.actions[x]['name']))
else:
fo.write(str(x))
fo.write('</alternativeID>\n')
fo.write('</element>\n')
if category == 'Robust Rubis':
fo.write('<values>\n')
independent = ch[2]
fo.write('<value name="choiceSet independence"><integer>%d</integer></value>\n' %\
(independent) )
outranking = ch[3]
fo.write('<value name="outranking"><integer>%d</integer></value>\n' %\
(outranking) )
outranked = ch[4]
fo.write('<value name="outranked"><integer>%d</integer></value>\n' %(outranked) )
determ = (ch[0]*Decimal('6')) - Decimal('3')
fo.write('<value name="determinateness"><real>%2.2f</real></value>\n' %(determ) )
fo.write('</values>\n')
else:
fo.write('<values>\n')
independent = (ch[2] - Min)/amplitude
fo.write('<value name="choiceSet independence"><real>%2.2f</real></value>\n' %\
(independent) )
outranking = (ch[3] - Min)/amplitude
fo.write('<value name="outranking"><real>%2.2f</real></value>\n' %(outranking) )
outranked = (ch[4] - Min)/amplitude
fo.write('<value name="outranked"><real>%2.2f</real></value>\n' %(outranked) )
determ = ch[0]*Decimal('100.0')
fo.write('<value name="determinateness"><real>%2.2f</real></value>\n' %(determ) )
fo.write('</values>\n')
fo.write('</alternativesSet>\n')
fo.write('</alternativesSets>\n')
# end of XMCDA 2.0 file
fo.write('</xmcda:XMCDA>\n')
fo.close()
if comment:
print('File: ' + nameExt + ' saved !')
# restore self to its original content
self.__dict__ = selfOrig.__dict__
class _Electre3OutrankingDigraph(OutrankingDigraph):
"""
Specialization of the standard OutrankingDigraph class for generating classical Electre III outranking digraphs (with vetoes and no counter-vetoes).
Parameters:
| performanceTableau (fileName of valid py code)
| optional, coalition (sublist of criteria)
"""
def __init__(self,argPerfTab=None,coalition=None,hasNoVeto=False):
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
self.name = 'rel_' + perfTab.name
self.actions = copy.deepcopy(perfTab.actions)
Min = Decimal('0')
Med = Decimal('50')
Max = Decimal('100')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
if coalition is None:
criteria = copy.deepcopy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
self.relation = self._constructRelation(criteria,perfTab.evaluation,hasNoVeto=hasNoVeto)
self.criteria = criteria
self.evaluation = copy.deepcopy(perfTab.evaluation)
try:
self.description = copy.deepcopy(perfTab.description)
except:
pass
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'normalized'
variant = 'electre3'
methodData['parameter'] = {'valuationType':valuationType,'variant':variant}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def showVetos(self,cutLevel=None,realVetosOnly = False,Comments=True):
"""
prints all veto situations observed in the OutrankingDigraph instance.
"""
if Comments:
print('*---- Veto situations ---')
nv, realveto = self.computeVetosShort()
vetos = self.vetos
vetos.sort()
if realVetosOnly:
if Comments:
print(self.valuationdomain)
cutveto = 0
if cutLevel is None:
cutLevel = self.valuationdomain['med']
else:
cutLevel = Decimal(str(cutLevel))
if cutLevel > self.valuationdomain['max']:
if Comments:
print("Error! min = %.3f, max = %.3f" %\
(self.valuationdomain['min'],self.valuationdomain['max']))
return None
if Comments:
print('Real vetos at cut level: %.3f' % (cutLevel))
for i in range(nv):
if self.vetos[i][0][2] > cutLevel:
if Comments:
print('self.vetos[i][0][2]=',self.vetos[i][0][2])
print(str(i)+': relation: '+str(vetos[i][0])+', criteria: ' + str(vetos[i][1]))
cutveto += 1
return nv,realveto,cutveto
else:
if Comments:
print('number of potential vetos: %d ' % (nv))
for i in range(nv):
if Comments:
print(str(i)+': relation: '+str(vetos[i][0])+', criteria: ' + str(vetos[i][1]))
if Comments:
print('number of real vetos: %d' % (realveto))
return nv,realveto
def computeVetos(self,cutLevel=None,realVetosOnly = False):
"""
prints all veto situations observed in the OutrankingDigraph instance.
"""
nv, realveto = self.computeVetosShort()
vetos = self.vetos
vetos.sort()
if realVetosOnly:
cutveto = 0
if cutLevel is None:
cutLevel = self.valuationdomain['med']
else:
cutLevel = Decimal(str(cutLevel))
if cutLevel > self.valuationdomain['max']:
return None
for i in range(nv):
if self.vetos[i][0][2] > cutLevel:
cutveto += 1
return nv,realveto,cutveto
else:
return nv,realveto
def _constructRelation(self,criteria,evaluation,hasNoVeto=False):
"""
Parameters: PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
NA = self.NA
totalweight = Decimal('0.0')
for c in criteria:
totalweight = totalweight + abs(criteria[c]['weight'])
relation = {}
vetos = []
for a in actions:
relation[a] = {}
for b in actions:
if a == b:
relation[a][b] = Decimal('0.0')
else:
nc = len(criteria)
counter = Decimal('0.0')
veto = {}
for c in criteria:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
ax = criteria[c]['thresholds']['ind'][0]
ay = criteria[c]['thresholds']['ind'][1]
h = ax + ay * abs(evaluation[c][a])
except:
h = None
try:
ax = criteria[c]['thresholds']['weakPreference'][0]
ay = criteria[c]['thresholds']['weakPreference'][1]
h = ax + ay * abs(evaluation[c][a])
except:
if h is None:
h = Decimal('0.0')
try:
bx = criteria[c]['thresholds']['pref'][0]
by = criteria[c]['thresholds']['pref'][1]
p = bx + by * abs(evaluation[c][a])
except:
p = None
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
if hasNoVeto:
v = None
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
if criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,h,h,p)
counter = counter + (lc0 * abs(criteria[c]['weight']))
veto[c] = (self._localVeto(d,p,v),d,v)
else:
counter = counter + Decimal('0.5') * criteria[c]['weight']
veto[c] = (Decimal('0.0'),None,None)
concordindex = counter/totalweight
discordindex = Decimal('1.0')
abvetos=[]
for c in criteria:
if veto[c][0] > concordindex:
discordindex = discordindex * (Decimal('1.0') - veto[c][0])/(Decimal('1.0') - concordindex)
abvetos.append((c,veto[c]))
if abvetos != []:
vetos.append(([a,b,concordindex*Decimal('100.0'),discordindex*Decimal('100.0')],abvetos))
outrankindex = concordindex * discordindex
relation[a][b] = outrankindex*Decimal('100.0')
self.vetos = vetos
return relation
def computeCriterionRelation(self,c,a,b,hasSymmetricThresholds=False):
"""
compute the outranking characteristic for actions x and y
on criterion c.
"""
criteria = self.criteria
evaluation = self.evaluation
NA = self.NA
if a == b:
return Decimal("1.0")
else:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx + indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx + indy * abs(evaluation[c][a])
except:
ind = Decimal("0.0")
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = wp
except:
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if criteria[c]['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
return self._localConcordance(d,ind,wp,p)
else:
return Decimal("0.5")
def _localConcordance(self,d,ind,wp,p):
"""
Parameters: d := diff observed, ind := indifference threshold,
wp := weak prefrence threshold, p := prefrence threshold,
Renders the concordance index per criteria.
"""
Debug = False
if Debug:
print('d,ind,wp,p', d,ind,wp,p)
if p is not None:
if d < -p:
return Decimal('0')
elif ind is not None:
if d < -ind:
return (p + d)/(p - ind)
else:
return Decimal('1')
else:
return Decimal('1')
else:
if ind is not None:
if d < -ind:
return Decimal('0')
else:
return Decimal('1')
else:
if d < Decimal('0'):
return Decimal('0')
else:
return Decimal('1')
def _localVeto(self, d, p, v):
"""
Parameters:
d := diff observed, v := veto threshold.
Renders the local veto state
"""
if v is not None:
if p is not None:
if d > -p:
return Decimal('0.0')
elif d < -v:
return Decimal('1.0')
else:
return (Decimal('-1'))*(d+p)/(v+p)
else:
if d < -v:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
return Decimal('0.0')
# multiprocessing thread for BipolarOutrankingDigraph class
class _myBODGThread(Process):
def __init__(self, target,args):
Process.__init__(self)
self.threadID = target
self.digraph = args[0]
self.InitialSplit = args[1]
self.workingDirectory = args[2]
self.splitActions = args[3]
self.hasNoVeto = args[4]
self.hasBipolarVeto = args[5]
self.hasSymmetricThresholds = args[6]
self.Debug = args[7]
def run(self):
from io import BytesIO
from pickle import Pickler, dumps, loads
from os import chdir
chdir(self.workingDirectory)
Debug = self.Debug
if Debug:
print("Starting working in %s on thread %s" % (self.workingDirectory, str(self.threadId)))
## fi = open('dumpSelf.py','rb')
## digraph = loads(fi.read())
## fi.close()
digraph = self.digraph
splitActions = self.splitActions
## fiName = 'splitActions-'+str(self.threadID)+'.py'
## fi = open(fiName,'rb')
## splitActions = loads(fi.read())
## fi.close()
# compute partiel relation
#if (not self.hasBipolarVeto) or WithConcordanceRelation or WithVetoCounts:
# constructRelation = BipolarOutrankingDigraph._constructRelation
#else:
constructRelation = BipolarOutrankingDigraph._constructRelationSimple
if self.InitialSplit:
#splitRelation = BipolarOutrankingDigraph._constructRelation(
splitRelation = constructRelation(
digraph,digraph.criteria,\
digraph.evaluation,
initial=splitActions,
#terminal=terminal,
hasNoVeto=self.hasNoVeto,
hasBipolarVeto=self.hasBipolarVeto,
WithConcordanceRelation=False,
WithVetoCounts=False,
Debug=False,
hasSymmetricThresholds=self.hasSymmetricThresholds)
else:
#splitRelation = BipolarOutrankingDigraph._constructRelation(
splitRelation = constructRelation(
digraph,digraph.criteria,\
digraph.evaluation,
#initial=initial,
terminal=splitActions,
hasNoVeto=self.hasNoVeto,
hasBipolarVeto=self.hasBipolarVeto,
WithConcordanceRelation=False,
WithVetoCounts=False,
Debug=False,
hasSymmetricThresholds=self.hasSymmetricThresholds)
# store partial relation
foName = 'splitRelation-'+str(self.threadID)+'.py'
fo = open(foName,'wb')
fo.write(dumps(splitRelation,-1))
fo.close()
# .......
[docs]
class BipolarOutrankingDigraph(OutrankingDigraph):
"""
Specialization of the abstract OutrankingDigraph root class for generating
bipolarly-valued outranking digraphs.
Parameters:
* argPerfTab: instance of PerformanceTableau class.
If a file name string is given, the performance tableau will directly be loaded first.
* coalition: subset of criteria to be used for contructing the outranking digraph.
* hasNoVeto: veto desactivation flag (False by default).
* hasBipolarVeto: bipolar versus electre veto activation (true by default).
* Normalized: the valuation domain is set by default to [-100,+100] (bipolar percents).
If True, the valuation domain is recoded to [-1.0,+1.0].
* WithConcordanceRelation: True by default when not threading.
The self.concordanceRelation contains the significance majority margin of the "at least as good relation as"
without the large performance difference polarization.
* WithVetoCounts: True by default when not threading. All vetos and countervetos
are stored in self.vetos and self.negativeVetos slots,
as well the counts of large performance differences in self.largePerformanceDifferencesCount slot.
* Threading: False by default. Allows to profit from SMP machines via the Python multiprocessing module.
* startMethod: 'spawn' (default), 'forkserver' or 'fork' (not safe against dead locks)
* nbrCores: controls the maximal number of cores that will be used in the multiprocessing phases.
If None is given, the os.cpu_count method is used in order to determine the number of available CPU cores on the SMP machine.
.. warning:: The multiprocessing :py:class:`~outrankingDigraphs.BipolarOutrankingDigraph` constructor uses
by default the 'spawn' start-mathod for threading.
When not using the 'fork' start method in a python script file,
the main entry code must hence be protected with the __name__=='__main__' test
in order to avoid a recursive re-execution of the whole script.
(see the documentation of the :py:mod:`multiprocessing` module.
If Threading is *True*, *WithConcordanceRelation* and *WithVetoCounts* flags
are automatically set both to False.
"""
def __repr__(self):
"""
Default presentation method for BipolarOutrankingDigraph instance.
"""
reprString = '*------- Object instance description ------*\n'
reprString += 'Instance class : %s\n' % self.__class__.__name__
reprString += 'Instance name : %s\n' % self.name
reprString += 'Actions : %d\n' % self.order
reprString += 'Criteria : %d\n' % len(self.criteria)
reprString += 'Size : %d\n' % self.computeSize()
try:
oppDeg = self.computeOppositeness(InPercents=True)
reprString += 'Oppositeness (%%) : %.2f\n' % (oppDeg['oppositeness'])
except:
pass
try:
if self.distribution == 'beta':
reprString += 'Uncertainty model : %s(a=%.1f,b=%.1f)\n' %\
(self.distribution,self.betaParameter,self.betaParameter)
else:
reprString += 'Uncertainty model : %s(a=%s,b=%s)\n' %\
(self.distribution,'0','2w')
reprString += 'Likelihood domain : [-1.0;+1.0]\n'
reprString += 'Confidence level : %.2f (%.1f%%)\n' %\
(self.bipolarConfidenceLevel,\
(self.bipolarConfidenceLevel+1.0)/2.0*100.0)
reprString += 'Confident majority : %.2f (%.1f%%)\n' %\
(self.confidenceCutLevel,\
(self.confidenceCutLevel+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0'))
except:
pass
reprString += 'Determinateness (%%) : %.2f\n' %\
self.computeDeterminateness(InPercents=True)
reprString += 'Valuation domain : [%.2f;%.2f]\n' \
% (self.valuationdomain['min'],self.valuationdomain['max'])
#reprString += 'Valuation domain : %s\n' % str(self.valuationdomain)
reprString += 'Attributes : %s\n' % list(self.__dict__.keys())
val1 = self.runTimes['totalTime']
val2 = self.runTimes['dataInput']
val3 = self.runTimes['computeRelation']
val4 = self.runTimes['gammaSets']
reprString += '---- Constructor run times (in sec.) ----\n'
try:
if self.nbrThreads > 0:
reprString += 'Threads : %d\n' % self.nbrThreads
except:
reprString += 'Threads : %d\n' % 0
try:
reprString += 'Start method : %s\n' % self.startMethod
except:
reprString += 'Start method : %s\n' % None
reprString += 'Total time : %.5f\n' % val1
reprString += 'Data input : %.5f\n' % val2
reprString += 'Compute relation : %.5f\n' % val3
reprString += 'Gamma sets : %.5f\n' % val4
return reprString
def __init__(self,argPerfTab=None,
objectivesSubset=None,
criteriaSubset=None,
coalition=None,
actionsSubset=None,
hasNoVeto=False,
hasBipolarVeto=True,
Normalized=True,
ndigits=4,
CopyPerfTab=True,
BigData=False,
Threading=False,
startMethod=None,
tempDir=None,
WithConcordanceRelation=True,
WithVetoCounts=True,
nbrCores=None,
Debug=False,Comments=False):
from copy import deepcopy
from time import time
#from digraphsTools import omax, omin
# set initial time stamp
tt = time()
# ---- performance tableau data input
if argPerfTab is None:
print('Performance tableau required !')
#perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
# set Threading parameters
if Threading:
WithConcordanceRelation = False
WithVetoCounts = False
# transfering the performance tableau data to self
self.name = 'rel_' + perfTab.name
# actions
if actionsSubset is None:
if isinstance(perfTab.actions,list):
actions = {}
for x in perfTab.actions:
actions[x] = {'name': str(x)}
self.actions = actions
else:
if CopyPerfTab:
self.actions = deepcopy(perfTab.actions)
else:
self.actions = perfTab.actions
else:
actions = {}
for x in actionsSubset:
actions[x] = {'name': str(x)}
self.actions = actions
# valuation domain
self.ndigits=ndigits
if Normalized:
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
else:
Min = Decimal('-100.0')
Med = Decimal('0.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,
'med':Med,
'max':Max,
'hasIntegerValuation':False}
try:
self.valuationdomain['precision'] = \
perfTab.valuationPrecision
except:
self.valuationdomain['precision'] = \
Decimal('1')/Decimal(str(ndigits))
# objectives
if objectivesSubset is None:
try:
if CopyPerfTab:
self.objectives = deepcopy(perfTab.objectives)
else:
self.objectives = perfTab.objectives
except:
pass
else:
objectives = OrderedDict()
for obj in objectivesSubset:
if CopyPerfTab:
objectives[obj] = deepcopy(perfTab.objectives[obj])
else:
objectives[obj] = perfTab.objectives[obj]
self.objectives = objectives
# criteria coalition
if criteriaSubset is None and objectivesSubset is None and coalition is None:
if CopyPerfTab:
self.criteria = deepcopy(perfTab.criteria)
else:
self.criteria = perfTab.criteria
else:
criteria = OrderedDict()
if criteriaSubset is None:
if objectivesSubset is None and coalition is None:
if criteriaSubset is None:
coalition = list(perfTab.criteria.keys())
else:
coalition = criteriaSubset
elif objectivesSubset is not None and coalition is None:
coalition = []
for obj in objectivesSubset:
objCrit = self.objectives[obj]['criteria']
coalition += objCrit
elif objectivesSubset is not None and coalition is not None:
print('Error: Objectives Subset and coalition given')
return
else:
for g in coalition:
if CopyPerfTab:
criteria[g] = deepcopy(perfTab.criteria[g])
else:
criteria[g] = perfTab.criteria[g]
else:
for g in criteriaSubset:
if CopyPerfTab:
criteria[g] = deepcopy(perfTab.criteria[g])
else:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
# convert criteria weights to Decimal format
criteria = OrderedDict()
if objectivesSubset is None:
if coalition is None:
if criteriaSubset is None:
coalition = list(perfTab.criteria.keys())
else:
coalition = criteriaSubset
else:
coalition = []
for obj in objectives:
objCrit = self.objectives[obj]['criteria']
coalition += objCrit
for g in coalition:
if CopyPerfTab:
criteria[g] = deepcopy(perfTab.criteria[g])
else:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
self.convertWeight2Decimal()
# install method Data and parameters
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'bipolar'
variant = 'standard'
methodData['parameter'] = {'valuationType': valuationType, 'variant': variant}
try:
vetoType = perfTab.parameter['vetoType']
methodData['parameter']['vetoType'] = vetoType
except:
vetoType = 'normal'
methodData['parameter']['vetoType'] = vetoType
if vetoType == 'bipolar':
hasBipolarVeto = True
self.methodData = methodData
# insert performance Data
if CopyPerfTab:
self.evaluation = deepcopy(perfTab.evaluation)
self.NA = deepcopy(perfTab.NA)
else:
self.evaluation = perfTab.evaluation
self.NA = perfTab.NA
if not BigData:
self.convertEvaluation2Decimal()
try:
if CopyPerfTab:
self.description = deepcopy(perfTab.description)
elif not BigData:
self.description = perfTab.description
except:
pass
# init general digraph Data
self.order = len(self.actions)
# finished data input time stamp
self.runTimes = {'dataInput': time()-tt }
# ---------- construct outranking relation
# initial time stamp
tcp = time()
actions = self.actions
criteria = self.criteria
evaluation = self.evaluation
actionsKeys = list(dict.keys(actions))
self.relation = self._constructRelationWithThreading(criteria,\
evaluation,\
initial=actionsKeys,\
terminal=actionsKeys,\
hasNoVeto=hasNoVeto,\
hasBipolarVeto=hasBipolarVeto,\
hasSymmetricThresholds=True,\
Threading=Threading,\
startMethod=startMethod,\
tempDir=tempDir,\
WithConcordanceRelation=WithConcordanceRelation,\
WithVetoCounts=WithVetoCounts,\
nbrCores=nbrCores,\
Debug=Debug,Comments=Comments)
# rounding up to ndigits
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
self.recodeValuation(Min,Max,ndigits)
# finished relation computing time stamp
self.runTimes['computeRelation'] = time() - tcp
# ---- computing the gamma sets
tg = time()
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
self.runTimes['gammaSets'] = time() - tg
# total constructor time
self.runTimes['totalTime'] = time() - tt
if Comments:
print(self)
[docs]
def computeCriterionRelation(self,c,a,b,hasSymmetricThresholds=True):
"""
Compute the outranking characteristic for actions x and y
on criterion c.
"""
criteria = self.criteria
evaluation = self.evaluation
NA = self.NA
if a == b:
return Decimal("1.0")
else:
if self.evaluation[c][a] != NA and self.evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if self.criteria[c]['weight'] > Decimal("0.0"):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
return self._localConcordance(d,ind,wp,p)
else:
return Decimal("0.0")
def _constructRelationWithThreading(self,criteria,
evaluation,
initial=None,
terminal=None,
hasNoVeto=False,
hasBipolarVeto=True,
Debug=False,
hasSymmetricThresholds=True,
Threading=False,
startMethod=None,
tempDir=None,
WithConcordanceRelation=True,
WithVetoCounts=True,
nbrCores=None,Comments=False):
"""
Specialization of the corresponding BipolarOutrankingDigraph method
"""
from multiprocessing import cpu_count
##
if not Threading or cpu_count() < 2:
# set threading parameter
if Comments:
print('Computing without threading ...')
self.nbrThreads = 0
self.startMethod = None
# !! concordance relation and veto counts need a complex constructor
if (not hasBipolarVeto) or WithConcordanceRelation or WithVetoCounts:
constructRelation = self._constructRelation
else:
constructRelation = self._constructRelationSimple
return constructRelation(criteria,\
evaluation,\
initial=initial,\
terminal=terminal,\
hasNoVeto=hasNoVeto,\
hasBipolarVeto=hasBipolarVeto,\
WithConcordanceRelation=WithConcordanceRelation,\
WithVetoCounts=WithVetoCounts,\
Debug=Debug,\
hasSymmetricThresholds=hasSymmetricThresholds)
##
else: # parallel computation
from copy import copy, deepcopy
from io import BytesIO
from pickle import Pickler, dumps, loads, load
# setting default start method
if startMethod is None:
startMethod = 'spawn'
mpctx = mp.get_context(startMethod)
Process = mpctx.Process
active_children = mpctx.active_children
cpu_count = mpctx.cpu_count
self.startMethod = mpctx.get_start_method()
if Comments:
print('Threading ...')
print(startMethod)
from tempfile import TemporaryDirectory
with TemporaryDirectory(dir=tempDir) as tempDirName:
from copy import copy, deepcopy
## #selfDp = copy(self)
## selfFileName = tempDirName +'/dumpSelf.py'
## if Debug:
## print('temDirName, selfFileName', tempDirName,selfFileName)
## fo = open(selfFileName,'wb')
## fo.write(dumps(self,-1))
## fo.close()
if nbrCores is None:
nbrCores = cpu_count()
if Comments:
print('Nbr of cpus = ',nbrCores)
# set number of threads
self.nbrThreads = nbrCores
ni = len(initial)
nt = len(terminal)
if ni < nt:
n = ni
actions2Split = list(initial)
InitialSplit = True
else:
n = nt
actions2Split = list(terminal)
InitialSplit = False
## if Debug:
## print('InitialSplit, actions2Split', InitialSplit, actions2Split)
nit = n//nbrCores
nbrOfJobs = nbrCores
if nit*nbrCores < n:
nit += 1
while nit*(nbrOfJobs-1) >= n:
nbrOfJobs -= 1
if Comments:
print('nbr of actions to split',n)
print('nbr of jobs = ',nbrOfJobs)
print('nbr of splitActions = ',nit)
relation = {}
for x in initial:
relation[x] = {}
for y in terminal:
relation[x][y] = self.valuationdomain['med']
i = 0
actionsRemain = set(actions2Split)
splitActionsList = []
for j in range(nbrOfJobs):
if Comments:
print('Thread = %d/%d' % (j+1,nbrOfJobs),end=" ")
splitActions=[]
for k in range(nit):
if j < (nbrOfJobs -1) and i < n:
splitActions.append(actions2Split[i])
else:
splitActions = list(actionsRemain)
i += 1
if Comments:
print('%d' % (len(splitActions)) )
## if Debug:
## print(splitActions)
actionsRemain = actionsRemain - set(splitActions)
## if Debug:
## print(actionsRemain)
splitActionsList.append(splitActions)
## foName = tempDirName+'/splitActions-'+str(j)+'.py'
## fo = open(foName,'wb')
## spa = dumps(splitActions,-1)
## fo.write(spa)
## fo.close()
splitThread = _myBODGThread(target=j,
args=(self,
InitialSplit,
tempDirName,splitActions,
hasNoVeto,hasBipolarVeto,
hasSymmetricThresholds,Debug))
splitThread.start()
while active_children() != []:
pass
if Comments:
print('Exiting computing threads')
for j in range(len(splitActionsList)):
#print('Post job-%d/%d processing' % (j+1,nbrOfJobs))
## if Debug:
## print('job',j)
## fiName = tempDirName+'/splitActions-'+str(j)+'.py'
## fi = open(fiName,'rb')
## splitActions = loads(fi.read())
## fi.close()
splitActions = splitActionsList[j]
## if Debug:
## print('splitActions',splitActions)
fiName = tempDirName+'/splitRelation-'+str(j)+'.py'
fi = open(fiName,'rb')
splitRelation = loads(fi.read())
## if Debug:
## print('splitRelation',splitRelation)
fi.close()
#relation update with splitRelation)
if InitialSplit:
#for x,y in product(splitActions,terminal):
for x in splitActions:
rx = relation[x]
sprx = splitRelation[x]
for y in terminal:
rx[y] = sprx[y]
else:
#for x,y in product(initial,splitActions):
for x in initial:
rx = relation[x]
sprx = splitRelation[x]
for y in splitActions:
rx[y] = sprx[y]
return relation
def _constructRelationSimple(self,criteria,
evaluation,
initial=None,
terminal=None,
hasNoVeto=False,
hasBipolarVeto=True,
WithConcordanceRelation=False,
WithVetoCounts=False,
hasSymmetricThresholds=True,
Debug=False):
"""
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
Parameters:
* PerfTab.criteria, PerfTab.evaluation,
* inital nodes, terminal nodes, for restricted purposes
"""
## default setting for digraphs
if initial is None:
initial = self.actions
if terminal is None:
terminal = self.actions
## totalweight = Decimal('0.0')
## for c in dict.keys(criteria):
## totalweight = totalweight + criteria[c]['weight']
totalweight = sum(abs(crit['weight']) for crit in criteria.values())
relation = {}
vetos = []
negativeVetos = []
#nc = len(criteria)
NA = self.NA
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
for a in initial:
relation[a] = {}
ra = relation[a]
for b in terminal:
if a == b:
ra[b] = Med
else:
concordance = Decimal('0.0')
veto = {}
abvetos=[]
negativeVeto = {}
abNegativeVetos=[]
for c,crit in criteria.items():
evalca = evaluation[c][a]
evalcb = evaluation[c][b]
maxAB = max(abs(evalca),abs(evalcb))
if evalca != NA and evalcb != NA:
try:
indx = crit['thresholds']['ind'][0]
indy = crit['thresholds']['ind'][1]
ind = indx +indy * maxAB
except KeyError:
ind = None
try:
wpx = crit['thresholds']['weakPreference'][0]
wpy = crit['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * maxAB
else:
wp = wpx + wpy * abs(evalca)
except KeyError:
wp = None
try:
px = crit['thresholds']['pref'][0]
py = crit['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * maxAB
else:
p = px + py * abs(evalca)
except KeyError:
p = None
if crit['weight'] > Decimal('0.0'):
d = evalca - evalcb
else:
d = evalcb - evalca
lc0 = self._localConcordance(d,ind,wp,p)
## print 'c,a,b,d,ind,wp,p,lco = ',c,a,b,d, ind,wp,p,lc0
concordance = concordance + (lc0 * abs(crit['weight']))
try:
wvx = crit['thresholds']['weakVeto'][0]
wvy = crit['thresholds']['weakVeto'][1]
if hasNoVeto:
wv = None
else:
if hasSymmetricThresholds:
wv = wvx + wvy * maxAB
else:
wv = wvx + wvy * abs(evalca)
except KeyError:
wv = None
try:
vx = crit['thresholds']['veto'][0]
vy = crit['thresholds']['veto'][1]
v = vx + vy * maxAB
except KeyError:
v = None
veto[c] = (self._localVeto(d,wv,v),d,wv,v)
if veto[c][0] > Decimal('-1.0'):
abvetos.append((c,veto[c]))
negativeVeto[c] = (self._localNegativeVeto(d,wv,v),d,wv,v)
if negativeVeto[c][0] > Decimal('-1.0'):
abNegativeVetos.append((c,negativeVeto[c]))
else:
concordance = concordance + Decimal('0.0') * crit['weight']
veto[c] = (Decimal('-1.0'),None,None,None)
negativeVeto[c] = (Decimal('-1.0'),None,None,None)
concordindex = concordance / totalweight
## init vetoes lists and indexes
abVetoes=[]
abNegativeVetoes=[]
# contradictory vetoes
for c in criteria.keys():
if veto[c][0] >= Decimal('0'):
abVetoes.append((c,veto[c]))
if negativeVeto[c][0] >= Decimal('0'):
abNegativeVetoes.append((c,negativeVeto[c]))
if not hasNoVeto:
vetoes = [-veto[c][0] for c in veto if veto[c][0] > Decimal('-1')]
negativeVetoes = [negativeVeto[c][0] for c in negativeVeto\
if negativeVeto[c][0] > Decimal('-1')]
else:
vetoes = []
negativeVetoes = []
## if Debug:
## print('vetoes = ', vetoes)
## print('negativeVetoes = ', negativeVetoes)
omaxList = [concordindex] + vetoes + negativeVetoes
outrankindex = omax(Med,omaxList,Debug=Debug)
if abVetoes != []:
vetos.append(([a,b,concordindex*Max],abVetoes))
if abNegativeVetoes != []:
negativeVetos.append(([a,b,concordindex*Max],abNegativeVetoes))
ra[b] = outrankindex*Max
# return outranking relation
return relation
def _constructRelation(self,criteria,
evaluation,
initial=None,
terminal=None,
hasNoVeto=False,
hasBipolarVeto=True,
WithConcordanceRelation=True,
WithVetoCounts=True,
Debug=False,
hasSymmetricThresholds=True):
"""
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
Parameters:
* PerfTab.criteria, PerfTab.evaluation,
* inital nodes, terminal nodes, for restricted purposes
Flags:
* hasNoVeto = True inhibits taking into account large performances differences
* hasBipolarVeto = False allows to revert (if False) to standard Electre veto handling
"""
from digraphsTools import omax,omin
## default setting for digraphs
if initial is None:
initial = self.actions
if terminal is None:
terminal = self.actions
## totalweight = Decimal('0.0')
## for c in dict.keys(criteria):
## totalweight = totalweight + criteria[c]['weight']
totalweight = sum(abs(criteria[c]['weight']) for c in criteria)
relation = {}
concordanceRelation = {}
vetos = []
if hasBipolarVeto:
negativeVetos = []
largePerformanceDifferencesCount = {}
for a in initial:
largePerformanceDifferencesCount[a] = {}
lpda = largePerformanceDifferencesCount[a]
for b in terminal:
lpda[b] = {'positive':0,'negative':0}
#nc = len(criteria)
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
NA = self.NA
for a in initial:
relation[a] = {}
ra = relation[a]
concordanceRelation[a] = {}
crda = concordanceRelation[a]
for b in terminal:
if a == b:
ra[b] = Med
crda[b] = Decimal('0.0')
else:
concordance = Decimal('0.0')
veto = {}
abvetos=[]
if hasBipolarVeto:
negativeVeto = {}
abNegativeVetos=[]
for c,crit in criteria.items():
evalca = evaluation[c][a]
evalcb = evaluation[c][b]
maxAB = max(abs(evalca),abs(evalcb))
if evalca != NA and evalcb != NA:
try:
indx = crit['thresholds']['ind'][0]
indy = crit['thresholds']['ind'][1]
ind = indx +indy * maxAB
except KeyError:
ind = None
try:
wpx = crit['thresholds']['weakPreference'][0]
wpy = crit['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * maxAB
else:
wp = wpx + wpy * abs(evalca)
except KeyError:
wp = None
try:
px = crit['thresholds']['pref'][0]
py = crit['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * maxAB
else:
p = px + py * abs(evalca)
except KeyError:
p = None
if crit['weight'] > Decimal('0.0'):
d = evalca - evalcb
else:
d = evalcb - evalca
lc0 = self._localConcordance(d,ind,wp,p)
## print 'c,a,b,d,ind,wp,p,lco = ',c,a,b,d, ind,wp,p,lc0
concordance = concordance + (lc0 * abs(crit['weight']))
try:
wvx = crit['thresholds']['weakVeto'][0]
wvy = crit['thresholds']['weakVeto'][1]
if hasNoVeto:
wv = None
else:
if hasSymmetricThresholds:
wv = wvx + wvy * maxAB
else:
wv = wvx + wvy * abs(evalca)
except KeyError:
wv = None
try:
vx = crit['thresholds']['veto'][0]
vy = crit['thresholds']['veto'][1]
if hasNoVeto:
v = None
else:
if hasSymmetricThresholds:
v = vx + vy * maxAB
else:
v = vx + vy * abs(evalca)
except KeyError:
v = None
veto[c] = (self._localVeto(d,wv,v),d,wv,v)
if veto[c][0] > Decimal('-1.0'):
abvetos.append((c,veto[c]))
largePerformanceDifferencesCount[a][b]['negative'] -= 1
## if d < -wv:
## print 'd,wv,v,veto[c]',d,wv,v,veto[c]
if hasBipolarVeto:
negativeVeto[c] = (self._localNegativeVeto(d,wv,v),d,wv,v)
if negativeVeto[c][0] > Decimal('-1.0'):
abNegativeVetos.append((c,negativeVeto[c]))
largePerformanceDifferencesCount[a][b]['positive'] += 1
## if d > wv:
## print 'd,wv,v,negativeVeto[c]',d,wv,v,negativeVeto[c]
else:
concordance = concordance + Decimal('0.0') * crit['weight']
veto[c] = (Decimal('-1.0'),None,None,None)
if hasBipolarVeto:
negativeVeto[c] = (Decimal('-1.0'),None,None,None)
if totalweight != Decimal('0.0'):
concordindex = concordance / totalweight
else:
concordindex = concordance
crda[b] = concordindex
## init vetoes lists and indexes
abVetoes=[]
abNegativeVetoes=[]
# contradictory vetoes
for c in criteria.keys():
if veto[c][0] >= Decimal('0'):
abVetoes.append((c,veto[c]))
if hasBipolarVeto:
if negativeVeto[c][0] >= Decimal('0'):
abNegativeVetoes.append((c,negativeVeto[c]))
if hasBipolarVeto:
vetoes = [-veto[c][0] for c in veto if veto[c][0] > Decimal('-1')]
negativeVetoes = [negativeVeto[c][0] for c in negativeVeto if negativeVeto[c][0] > Decimal('-1')]
if Debug:
print('vetoes = ', vetoes)
print('negativeVetoes = ', negativeVetoes)
omaxList = [concordindex] + vetoes + negativeVetoes
outrankindex = omax(Med,omaxList,Debug=Debug)
if Debug:
print('a b outrankindex = ', a,b, outrankindex)
else:
# hasBipolarVeto == False
vetoIndex = Decimal('-1.0')
for c in criteria:
vetoIndex = max(vetoIndex,veto[c][0])
outrankindex = min(concordindex,-vetoIndex)
if abVetoes != []:
vetos.append(([a,b,concordindex*Max],abVetoes))
if hasBipolarVeto:
if abNegativeVetoes != []:
negativeVetos.append(([a,b,concordindex*Max],abNegativeVetoes))
ra[b] = outrankindex*Max
# storing concordance relation and vetoes
if WithConcordanceRelation:
self.concordanceRelation = concordanceRelation
if WithVetoCounts:
self.vetos = vetos
if hasBipolarVeto:
self.negativeVetos = negativeVetos
self.largePerformanceDifferencesCount = largePerformanceDifferencesCount
# return outranking relation
return relation
[docs]
def criterionCharacteristicFunction(self,c,a,b,hasSymmetricThresholds=True):
"""
Renders the characteristic value of the comparison of a and b on criterion c.
"""
evalca = self.evaluation[c][a]
evalcb = self.evaluation[c][b]
maxAB = max(abs(evalca),abs(evalcb))
crit = self.criteria[c]
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
NA = self.NA
if evalca != NA and evalcb != NA:
try:
indx = crit['thresholds']['ind'][0]
indy = crit['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * maxAB
else:
ind = indx +indy * abs(evalca)
except:
ind = None
try:
wpx = crit['thresholds']['weakPreference'][0]
wpy = crit['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * maxAB
else:
wp = wpx + wpy * abs(evalca)
except:
wp = None
try:
px = crit['thresholds']['pref'][0]
py = crit['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * maxAB
else:
p = px + py * abs(evalca)
except:
p = None
if crit['weight'] > Decimal('0.0'):
d = evalca - evalcb
else:
d = evalcb - evalca
return self._localConcordance(d,ind,wp,p)
else:
return Decimal('0.0')
[docs]
def computeSingleCriteriaNetflows(self):
"""
renders the Promethee single criteria netflows matrix M
"""
actionsList = [x for x in self.actions]
actionsList.sort()
n = len(actionsList)
criteriaList = [x for x in self.criteria]
criteriaList.sort()
matrixM = {}
for a in actionsList:
matrixM[a] = {}
for c in criteriaList:
netflow= Decimal('0.0')
for b in actionsList:
if a != b:
cab = self.criterionCharacteristicFunction(c,a,b)
cba = self.criterionCharacteristicFunction(c,b,a)
netflow += cab - cba
netflow = float(netflow)/float(n-1)
matrixM[a][c] = netflow
return matrixM
[docs]
def saveSingleCriterionNetflows(self,fileName='tempnetflows.prn',delimiter=' ',Comments=True):
"""
Delimited save of single criteria netflows matrix
"""
actionsList = [x for x in self.actions]
actionsList.sort()
criteriaList = [x for x in self.criteria]
criteriaList.sort()
M = self.computeSingleCriteriaNetflows()
fo = open(fileName,'w')
for a in actionsList:
for c in criteriaList:
fo.write('%2.2f ' % (M[a][c]))
fo.write('\n')
fo.close()
if Comments:
print('Single Criteria Netflows saved on file %s' % (fileName))
def _localConcordance(self,d,ind,wp,p):
"""
Parameters: d := diff observed, wp := weak preference threshold,
ind := indiffrence threshold, p := prefrence threshold.
Renders the concordance index per criteria (-1,0,1)
"""
if p is not None:
if d <= -p:
return Decimal('-1.0')
elif ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if d < Decimal('0.0'):
return Decimal('-1.0')
else:
return Decimal('1.0')
else:
if ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('-1.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('-1.0')
else:
if d < Decimal("0.0"):
return Decimal('-1.0')
else:
return Decimal('1.0')
def _localVeto(self, d, wv, v):
"""
Parameters:
d := diff observed, v (wv) := (weak) veto threshold.
Renders the local veto state (-1,0,1).
"""
if v is not None:
if d <= - v:
return Decimal('1.0')
elif wv is not None:
if d <= - wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d <= -wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
def _localNegativeVeto(self, d, wv, v):
"""
Parameters:
d := diff observed, v (wv) := (weak) veto threshold.
Renders the local negative veto state (-1,0,1).
"""
if v is not None:
if d >= v:
return Decimal('1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
class _BipolarOutrankingDigraph(OutrankingDigraph):
"""
Specialization of the abstract OutrankingDigraph root class for generating
bipolarly-valued outranking digraphs.
Parameters:
* argPerfTab: instance of PerformanceTableau class.
If a file name string is given, the performance tableau will directly be loaded first.
* coalition: subset of criteria to be used for contruction the outranking digraph.
* hasNoVeto: veto desactivation flag (False by default).
* hasBipolarVeto: bipolar versus electre veto activation (true by default).
* Normalized: the valuation domain is set by default to [-100,+100] (bipolar percents).
If True, the valuation domain is recoded to [-1.0,+1.0].
* WithConcordanceRelation: True by default when not threading.
The self.concordanceRelation contains the significance majority margin of the "at least as good relation as"
without the large performance difference polarization.
* WithVetoCounts: True by default when not threading. All vetos and countervetos
are stored in self.vetos and self.negativeVetos slots,
as well the counts of large performance differences in self.largePerformanceDifferencesCount slot.
* Threading: False by default. Allows to profit from SMP machines via the Python multiprocessing module.
* nbrCores: controls the maximal number of cores that will be used in the multiprocessing phases.
If None is given, the os.cpu_count method is used in order to determine the number of availble cores on the SMP machine.
.. warning::
If Threading is True, WithConcordanceRelation and WithVetoCounts flags are automatically set both to False.
Removing this limitation is on the todo list and will be done soon.
"""
def __init__(self,argPerfTab=None,
coalition=None,
hasNoVeto=False,
hasBipolarVeto=True,
Normalized=False,
CopyPerfTab=True,
Threading=False,
WithConcordanceRelation=True,
WithVetoCounts=True,
nbrCores=None,
Debug=False,Comments=False):
from copy import deepcopy
if argPerfTab is None:
print('Performance tableau required !')
#perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
# set Threading parameters
if Threading:
WithConcordanceRelation = False
WithVetoCounts = False
#self.performanceTableau = perfTab
self.name = 'rel_' + perfTab.name
if isinstance(perfTab.actions,list):
actions = {}
for x in perfTab.actions:
actions[x] = {'name': str(x)}
self.actions = actions
else:
if CopyPerfTab:
self.actions = deepcopy(perfTab.actions)
else:
self.actions = perfTab.actions
if Normalized:
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
else:
Min = Decimal('-100.0')
Med = Decimal('0.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
if coalition is None:
try:
if CopyPerfTab:
self.objectives = deepcopy(perfTab.objectives)
else:
self.objectives = perfTab.objectives
except:
pass
if CopyPerfTab:
self.criteria = deepcopy(perfTab.criteria)
else:
self.criteria = perfTab.criteria
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
self.convertWeight2Decimal()
# install method Data and parameters
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'bipolar'
variant = 'standard'
methodData['parameter'] = {'valuationType': valuationType, 'variant': variant}
try:
vetoType = perfTab.parameter['vetoType']
methodData['parameter']['vetoType'] = vetoType
except:
vetoType = 'normal'
methodData['parameter']['vetoType'] = vetoType
if vetoType == 'bipolar':
hasBipolarVeto = True
self.methodData = methodData
# insert performance Data
if CopyPerfTab:
self.evaluation = deepcopy(perfTab.evaluation)
else:
self.evaluation = perfTab.evaluation
self.convertEvaluation2Decimal()
try:
if CopyPerfTab:
self.description = deepcopy(perfTab.description)
else:
self.description = perfTab.description
except:
pass
# init general digraph Data
self.order = len(self.actions)
# construct outranking relation
actions = self.actions
criteria = self.criteria
evaluation = self.evaluation
actionsKeys = list(dict.keys(actions))
self.relation = self._constructRelationWithThreading(criteria,\
evaluation,\
initial=actionsKeys,\
terminal=actionsKeys,\
hasNoVeto=hasNoVeto,\
hasBipolarVeto=hasBipolarVeto,\
hasSymmetricThresholds=True,\
Threading=Threading,\
WithConcordanceRelation=WithConcordanceRelation,\
WithVetoCounts=WithVetoCounts,\
nbrCores=nbrCores,\
Debug=Debug,Comments=Comments)
## if Normalized:
## self.recodeValuation(-1.0,1.0)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def computeCriterionRelation(self,c,a,b,hasSymmetricThresholds=True):
"""
Compute the outranking characteristic for actions x and y
on criterion c.
"""
criterion = self.criteria[c]
evaluation = self.evaluation
NA = self.NA
if a == b:
return Decimal("1.0")
else:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = self.criteria[c]['thresholds']['pref'][0]
py = self.criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]), abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if self.criteriion['weight'] > Decimal('0.0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
return self._localConcordance(d,ind,wp,p)
else:
return Decimal("0.0")
def _constructRelationWithThreading(self,criteria,
evaluation,
initial=None,
terminal=None,
hasNoVeto=False,
hasBipolarVeto=True,
Debug=False,
hasSymmetricThresholds=True,
Threading=False,
WithConcordanceRelation=True,
WithVetoCounts=True,
nbrCores=None,Comments=False):
"""
Specialization of the corresponding BipolarOutrankingDigraph method
"""
from multiprocessing import cpu_count
##
if not Threading or cpu_count() < 6:
return self._constructRelation(criteria,\
evaluation,\
initial=initial,\
terminal=terminal,\
hasNoVeto=hasNoVeto,\
hasBipolarVeto=hasBipolarVeto,\
WithConcordanceRelation=WithConcordanceRelation,\
WithVetoCounts=WithVetoCounts,\
Debug=Debug,\
hasSymmetricThresholds=hasSymmetricThresholds)
##
else: # parallel computation
from copy import copy, deepcopy
from io import BytesIO
from pickle import Pickler, dumps, loads, load
#from multiprocessing import Process, Lock,\
# active_children, cpu_count
import multiprocessing as mp
if startMethod is None:
startMethod = 'spawn'
mpctx = mp.get_context(startMethod)
Process = mpctx.Process
active_children = mpctx.active_children
cpu_count = mpctx.cpu_count
#Debug=True
class myThread(Process):
def __init__(self, threadID,
InitialSplit, tempDirName,
hasNoVeto, hasBipolarVeto,
hasSymmetricThresholds, Debug):
Process.__init__(self)
self.threadID = threadID
self.InitialSplit = InitialSplit
self.workingDirectory = tempDirName
self.hasNoVeto = hasNoVeto
self.hasBipolarVeto = hasBipolarVeto,
hasSymmetricThresholds = hasSymmetricThresholds,
self.Debug = Debug
def run(self):
from io import BytesIO
from pickle import Pickler, dumps, loads
from os import chdir
chdir(self.workingDirectory)
if Debug:
print("Starting working in %s on thread %s" %\
(self.workingDirectory, str(self.threadId)))
fi = open('dumpSelf.py','rb')
digraph = loads(fi.read())
fi.close()
fiName = 'splitActions-'+str(self.threadID)+'.py'
fi = open(fiName,'rb')
splitActions = loads(fi.read())
fi.close()
foName = 'splitRelation-'+str(self.threadID)+'.py'
fo = open(foName,'wb')
if self.InitialSplit:
splitRelation = BipolarOutrankingDigraph._constructRelation(
digraph,digraph.criteria,
digraph.evaluation,
initial=splitActions,
hasNoVeto=hasNoVeto,
hasBipolarVeto=hasBipolarVeto,
WithConcordanceRelation=False,
WithVetoCounts=False,
Debug=False,
hasSymmetricThresholds=hasSymmetricThresholds)
else:
splitRelation = BipolarOutrankingDigraph._constructRelation(digraph,digraph.criteria,\
digraph.evaluation,
terminal=splitActions,
hasNoVeto=hasNoVeto,
hasBipolarVeto=hasBipolarVeto,
WithConcordanceRelation=False,
WithVetoCounts=False,
Debug=False,
hasSymmetricThresholds=hasSymmetricThresholds)
buff = BytesIO()
pickler = Pickler(buff, -1)
pickler.fast = 1
pickler.dump(splitRelation)
buff.flush()
fo.write(buff.getvalue())
fo.close()
# .......
if Comments:
print('Threading ...')
from tempfile import TemporaryDirectory
with TemporaryDirectory() as tempDirName:
from copy import copy, deepcopy
#selfDp = copy(self)
selfFileName = tempDirName +'/dumpSelf.py'
if Debug:
print('temDirName, selfFileName', tempDirName,selfFileName)
fo = open(selfFileName,'wb')
pd = dumps(self,-1)
fo.write(pd)
## buff = BytesIO()
## pickler = Pickler(buff, -1)
## pickler.fast = 1
## pickler.dump(self)
## buff.flush()
## fo.write(buff.getvalue())
fo.close()
if nbrCores is None:
nbrCores = cpu_count()-1
if Comments:
print('Nbr of cpus = ',nbrCores)
ni = len(initial)
nt = len(terminal)
if ni < nt:
n = ni
actions2Split = list(initial)
InitialSplit = True
else:
n = nt
actions2Split = list(terminal)
InitialSplit = False
if Debug:
print('InitialSplit, actions2Split', InitialSplit, actions2Split)
nit = n//nbrCores
nbrOfJobs = nbrCores
if nit*nbrCores < n:
nit += 1
while nit*(nbrOfJobs-1) >= n:
nbrOfJobs -= 1
if Debug:
print('nbr of actions to split',n)
print('nbr of jobs = ',nbrOfJobs)
print('nbr of splitActions = ',nit)
relation = {}
for x in initial:
relation[x] = {}
for y in terminal:
relation[x][y] = self.valuationdomain['med']
i = 0
actionsRemain = set(actions2Split)
for j in range(nbrOfJobs):
if Comments:
print('Thread = %d/%d' % (j+1,nbrOfJobs),end=" ")
splitActions=[]
for k in range(nit):
if j < (nbrOfJobs -1) and i < n:
splitActions.append(actions2Split[i])
else:
splitActions = list(actionsRemain)
i += 1
if Comments:
print(len(splitActions))
if Debug:
print(splitActions)
actionsRemain = actionsRemain - set(splitActions)
if Debug:
print(actionsRemain)
foName = tempDirName+'/splitActions-'+str(j)+'.py'
fo = open(foName,'wb')
spa = dumps(splitActions,-1)
fo.write(spa)
## buff = BytesIO()
## pickler = Pickler(buff, -1)
## pickler.fast = 1
## pickler.dump(splitActions)
## buff.flush()
## fo.write(buff.getvalue())
fo.close()
fo.close()
splitThread = myThread(j,InitialSplit,
tempDirName,hasNoVeto,hasBipolarVeto,
hasSymmetricThresholds,Debug)
splitThread.start()
while active_children() != []:
pass
if Comments:
print('Exiting computing threads')
for j in range(nbrOfJobs):
#print('Post job-%d/%d processing' % (j+1,nbrOfJobs))
if Debug:
print('job',j)
fiName = tempDirName+'/splitActions-'+str(j)+'.py'
fi = open(fiName,'rb')
splitActions = loads(fi.read())
if Debug:
print('splitActions',splitActions)
fi.close()
fiName = tempDirName+'/splitRelation-'+str(j)+'.py'
fi = open(fiName,'rb')
splitRelation = loads(fi.read())
if Debug:
print('splitRelation',splitRelation)
fi.close()
#relation.update(splitRelation)
from itertools import product
if InitialSplit:
for x,y in product(splitActions,terminal):
#for x in splitActions:
# for y in terminal:
relation[x][y] = splitRelation[x][y]
else:
for x,y in product(initial,splitActions):
#for x in initial:
# for y in splitActions:
relation[x][y] = splitRelation[x][y]
return relation
def _constructRelation(self,criteria,
evaluation,
initial=None,
terminal=None,
hasNoVeto=False,
hasBipolarVeto=True,
WithConcordanceRelation=True,
WithVetoCounts=True,
Debug=False,
hasSymmetricThresholds=True):
"""
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
Parameters:
* PerfTab.criteria, PerfTab.evaluation,
* inital nodes, terminal nodes, for restricted purposes
Flags:
* hasNoVeto = True inhibits taking into account large performances differences
* hasBipolarVeto = False allows to revert (if False) to standard Electre veto handling
"""
## default setting for digraphs
if initial is None:
initial = self.actions
if terminal is None:
terminal = self.actions
## totalweight = Decimal('0.0')
## for c in dict.keys(criteria):
## totalweight = totalweight + criteria[c]['weight']
totalweight = sum([abs(criteria[c]['weight']) for c in criteria])
relation = {}
concordanceRelation = {}
vetos = []
if hasBipolarVeto:
negativeVetos = []
largePerformanceDifferencesCount = {}
for a in initial:
largePerformanceDifferencesCount[a] = {}
for b in terminal:
largePerformanceDifferencesCount[a][b] = {'positive':0,'negative':0}
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
NA = self.NA
for a in initial:
relation[a] = {}
concordanceRelation[a] = {}
for b in terminal:
if a == b:
relation[a][b] = Med
concordanceRelation[a][b] = Decimal('0.0')
else:
nc = len(criteria)
concordance = Decimal('0.0')
veto = {}
abvetos=[]
if hasBipolarVeto:
negativeVeto = {}
abNegativeVetos=[]
for c in criteria:
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criteria[c]['thresholds']['ind'][0]
indy = criteria[c]['thresholds']['ind'][1]
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
except:
ind = None
try:
wpx = criteria[c]['thresholds']['weakPreference'][0]
wpy = criteria[c]['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criteria[c]['thresholds']['pref'][0]
py = criteria[c]['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if criteria[g]['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
## print 'c,a,b,d,ind,wp,p,lco = ',c,a,b,d, ind,wp,p,lc0
concordance += (lc0 * criteria[c]['weight'])
try:
wvx = criteria[c]['thresholds']['weakVeto'][0]
wvy = criteria[c]['thresholds']['weakVeto'][1]
if hasNoVeto:
wv = None
else:
if hasSymmetricThresholds:
wv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wv = wvx + wvy * abs(evaluation[c][a])
except:
wv = None
try:
vx = criteria[c]['thresholds']['veto'][0]
vy = criteria[c]['thresholds']['veto'][1]
if hasNoVeto:
v = None
else:
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
veto[c] = (self._localVeto(d,wv,v),d,wv,v)
if veto[c][0] > Decimal('-1.0'):
abvetos.append((c,veto[c]))
largePerformanceDifferencesCount[a][b]['negative'] -= 1
## if d < -wv:
## print 'd,wv,v,veto[c]',d,wv,v,veto[c]
if hasBipolarVeto:
negativeVeto[c] = (self._localNegativeVeto(d,wv,v),d,wv,v)
if negativeVeto[c][0] > Decimal('-1.0'):
abNegativeVetos.append((c,negativeVeto[c]))
largePerformanceDifferencesCount[a][b]['positive'] += 1
## if d > wv:
## print 'd,wv,v,negativeVeto[c]',d,wv,v,negativeVeto[c]
else:
concordance += Decimal('0.0') * criteria[c]['weight']
veto[c] = (Decimal('-1.0'),None,None,None)
if hasBipolarVeto:
negativeVeto[c] = (Decimal('-1.0'),None,None,None)
concordindex = concordance / totalweight
concordanceRelation[a][b] = concordindex
## init vetoes lists and indexes
abVetoes=[]
abNegativeVetoes=[]
# contradictory vetoes
for c in criteria:
if veto[c][0] >= Decimal('0'):
abVetoes.append((c,veto[c]))
if hasBipolarVeto:
if negativeVeto[c][0] >= Decimal('0'):
abNegativeVetoes.append((c,negativeVeto[c]))
if hasBipolarVeto:
vetoes = [-veto[c][0] for c in veto if veto[c][0] > Decimal('-1')]
negativeVetoes = [negativeVeto[c][0] for c in negativeVeto if negativeVeto[c][0] > Decimal('-1')]
if Debug:
print('vetoes = ', vetoes)
print('negativeVetoes = ', negativeVetoes)
omaxList = [concordindex] + vetoes + negativeVetoes
outrankindex = omax(Med,omaxList,Debug=Debug)
if Debug:
print('a b outrankindex = ', a,b, outrankindex)
else:
# hasBipolarVeto == False
vetoIndex = Decimal('-1.0')
for c in criteria:
vetoIndex = max(vetoIndex,veto[c][0])
outrankindex = min(concordindex,-vetoIndex)
if abVetoes != []:
vetos.append(([a,b,concordindex*Max],abVetoes))
if hasBipolarVeto:
if abNegativeVetoes != []:
negativeVetos.append(([a,b,concordindex*Max],abNegativeVetoes))
relation[a][b] = outrankindex*Max
# storing concordance relation and vetoes
if WithConcordanceRelation:
self.concordanceRelation = concordanceRelation
if WithVetoCounts:
self.vetos = vetos
if hasBipolarVeto:
self.negativeVetos = negativeVetos
self.largePerformanceDifferencesCount = largePerformanceDifferencesCount
# return outranking relation
return relation
def criterionCharacteristicFunction(self,c,a,b,hasSymmetricThresholds=True):
"""
Renders the characteristic value of the comparison of a and b on criterion c.
"""
evaluation = self.evaluation
NA = self.NA
criterion = self.criteria[c]
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criterion['thresholds']['pref'][0]
py = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if criterion['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
return self._localConcordance(d,ind,wp,p)
else:
return Decimal('0.0')
def computeSingleCriteriaNetflows(self):
"""
renders the Promethee single criteria netflows matrix M
"""
actionsList = [x for x in self.actions]
actionsList.sort()
n = len(actionsList)
criteriaList = [x for x in self.criteria]
criteriaList.sort()
matrixM = {}
for a in actionsList:
matrixM[a] = {}
for c in criteriaList:
netflow= Decimal('0.0')
for b in actionsList:
if a != b:
cab = self.criterionCharacteristicFunction(c,a,b)
cba = self.criterionCharacteristicFunction(c,b,a)
netflow += cab - cba
netflow = float(netflow)/float(n-1)
matrixM[a][c] = netflow
return matrixM
def saveSingleCriterionNetflows(self,fileName='tempnetflows.prn',delimiter=' ',Comments=True):
"""
Delimited save of single criteria netflows matrix
"""
actionsList = [x for x in self.actions]
actionsList.sort()
criteriaList = [x for x in self.criteria]
criteriaList.sort()
M = self.computeSingleCriteriaNetflows()
fo = open(fileName,'w')
for a in actionsList:
for c in criteriaList:
fo.write('%2.2f ' % (M[a][c]))
fo.write('\n')
fo.close()
if Comments:
print('Single Criteria Netflows saved on file %s' % (fileName))
def _localConcordance(self,d,ind,wp,p):
"""
Parameters: d := diff observed, wp := weak preference threshold,
ind := indiffrence threshold, p := prefrence threshold.
Renders the concordance index per criteria (-1,0,1)
"""
if p is not None:
if d <= -p:
return Decimal('-1.0')
elif ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if d < Decimal('0.0'):
return Decimal('-1.0')
else:
return Decimal('1.0')
else:
if ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('-1.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('-1.0')
else:
if d < Decimal("0.0"):
return Decimal('-1.0')
else:
return Decimal('1.0')
def _localVeto(self, d, wv, v):
"""
Parameters:
d := diff observed, v (wv) := (weak) veto threshold.
Renders the local veto state (-1,0,1).
"""
if v is not None:
if d <= - v:
return Decimal('1.0')
elif wv is not None:
if d <= - wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d <= -wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
def _localNegativeVeto(self, d, wv, v):
"""
Parameters:
d := diff observed, v (wv) := (weak) veto threshold.
Renders the local negative veto state (-1,0,1).
"""
if v is not None:
if d >= v:
return Decimal('1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
class _BipolarPreferenceDigraph(BipolarOutrankingDigraph,PerformanceTableau):
"""
Parameters:
| performanceTableau (fileName of valid py code)
| optional, coalition (sublist of criteria)
Specialization of the standard BipolarOutrankingDigraph class for generating
new bipolar ordinal-valued outranking digraphs.
"""
def __init__(self,argPerfTab=None,coalition=None):
import copy
if argPerfTab is None:
perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
self.performanceTableau = perfTab
self.name = 'rel_' + perfTab.name
self.actions = copy.copy(perfTab.actions)
Min = Decimal('-2.0')
Med = Decimal('0.0')
Max = Decimal('2.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
if coalition is None:
criteria = copy.copy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = copy.copy(perfTab.criteria[g])
self.criteria = criteria
self.convertWeight2Decimal()
# install method Data and parameters
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'bipolar'
variant = 'standard'
methodData['parameter'] = {'valuationType': valuationType, 'variant': variant}
try:
vetoType = perfTab.parameter['vetoType']
methodData['parameter']['vetoType'] = vetoType
except:
vetoType = 'normal'
methodData['parameter']['vetoType'] = vetoType
if vetoType == 'bipolar':
hasBipolarVeto = True
self.methodData = methodData
# construct outranking relation
self.relation = self._constructRelation(criteria,perfTab.evaluation,hasNoVeto=hasNoVeto,hasBipolarVeto=hasBipolarVeto)
# insert performance Data
self.evaluation = copy.copy(perfTab.evaluation)
self.convertEvaluation2Decimal()
try:
self.description = copy.copy(perfTab.description)
except:
pass
# init general digraph Data
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,criteria,evaluation,hasNoVeto=False,hasBipolarVeto=False,hasSymmetricThresholds=True):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
NA = self.NA
totalweight = Decimal('0.0')
for c in criteria:
totalweight = totalweight + abs(criteria[c]['weight'])
relation = {}
vetos = []
if hasBipolarVeto:
negativeVetos = []
for a in actions:
relation[a] = {}
for b in actions:
if a == b:
relation[a][b] = Decimal('0.0')
else:
nc = len(criteria)
concordance = Decimal('0.0')
veto = {}
if hasBipolarVeto:
negativeVeto = {}
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criterion['thresholds']['pref'][0]
py = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if criterion['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
## print 'c,a,b,d,ind,wp,p,lco = ',c,a,b,d, ind,wp,p,lc0
concordance = concordance + (lc0 * criterion['weight'])
try:
wvx = criterion['thresholds']['weakVeto'][0]
wvy = criterion['thresholds']['weakVeto'][1]
if hasNoVeto:
wv = None
else:
if hasSymmetricThresholds:
wv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wv = wvx + wvy * abs(evaluation[c][a])
except:
wv = None
try:
vx = criterion['thresholds']['veto'][0]
vy = criterion['thresholds']['veto'][1]
if hasNoVeto:
v = None
else:
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
veto[c] = (self._localVeto(d,wv,v),d,wv,v)
## if d < -wv:
## print 'd,wv,v,veto[c]',d,wv,v,veto[c]
if hasBipolarVeto:
negativeVeto[c] = (self._localNegativeVeto(d,wv,v),d,wv,v)
## if d > wv:
## print 'd,wv,v,negativeVeto[c]',d,wv,v,negativeVeto[c]
else:
concordance = concordance + Decimal('0.0') * criterion['weight']
veto[c] = (Decimal('-1.0'),None,None,None)
if hasBipolarVeto:
negativeVeto[c] = (Decimal('-1.0'),None,None,None)
concordindex = concordance / totalweight
## init vetoes lists and indexes
abvetos=[]
abNegativeVetos=[]
vetoIndex = Decimal('-1.0')
negativeVetoIndex = Decimal('-1.0')
## compute bipolar vetoes indexes
for c in criteria:
vetoIndex = max(vetoIndex,veto[c][0])
if hasBipolarVeto:
negativeVetoIndex = max(negativeVetoIndex,negativeVeto[c][0])
# contradictory vetoes
if hasBipolarVeto:
# contradictory vetoes
if vetoIndex > Decimal('-1.0') and negativeVetoIndex > Decimal('-1.0'):
abvetos.append((c,veto[c]))
abNegativeVetos.append((c,negativeVeto[c]))
outrankindex = Decimal('0.0')
# veto
elif vetoIndex > Decimal('-1.0'):
abvetos.append((c,veto[c]))
if concordindex > Decimal('0.0'):
outrankindex = Decimal('0.0')
else:
outrankindex = min(concordindex,-vetoIndex)
# dictator
elif negativeVetoIndex > Decimal('-1.0'):
abNegativeVetos.append((c,negativeVeto[c]))
if concordindex < Decimal('0.0'):
outrankindex = Decimal('0.0')
else:
outrankindex = max(concordindex,negativeVetoIndex)
else:
# no vetoes or dictators
outrankindex = concordindex
else:
# hasBipolarVeto == False
outrankindex = min(concordindex,-vetoIndex)
if abvetos != []:
vetos.append(([a,b,concordindex*Decimal('100.0')],abvetos))
if hasBipolarVeto:
if abNegativeVetos != []:
negativeVetos.append(([a,b,concordindex*Decimal('100.0')],abNegativeVetos))
relation[a][b] = outrankindex*Decimal('100.0')
self.vetos = vetos
if hasBipolarVeto:
self.negativeVetos = negativeVetos
return relation
def criterionCharacteristicFunction(self,c,a,b,hasSymmetricThresholds=True):
"""
Renders the characteristic value of the comparison of a and b on criterion c.
"""
evaluation = self.evaluation
NA = self.NA
criterion = self.criteria[c]
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
except:
wp = None
try:
px = criterion['thresholds']['pref'][0]
py = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = px + py * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = px + py * abs(evaluation[c][a])
except:
p = None
if criterion['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
return self._localConcordance(d,ind,wp,p)
else:
return Decimal('0.0')
def computeSingleCriteriaNetflows(self):
"""
renders the Promethee single criteria netflows matrix M
"""
actionsList = [x for x in self.actions]
actionsList.sort()
n = len(actionsList)
criteriaList = [x for x in self.criteria]
criteriaList.sort()
matrixM = {}
for a in actionsList:
matrixM[a] = {}
for c in criteriaList:
netflow= Decimal('0.0')
for b in actionsList:
if a != b:
cab = self.criterionCharacteristicFunction(c,a,b)
cba = self.criterionCharacteristicFunction(c,b,a)
netflow += cab - cba
netflow = float(netflow)/float(n-1)
matrixM[a][c] = netflow
return matrixM
def saveSingleCriterionNetflows(self,fileName='tempnetflows.prn',delimiter=' ',Comments=True):
"""
Delimited save of single criteria netflows matrix
"""
actionsList = [x for x in self.actions]
actionsList.sort()
criteriaList = [x for x in self.criteria]
criteriaList.sort()
M = self.computeSingleCriteriaNetflows()
fo = open(fileName,'w')
for a in actionsList:
for c in criteriaList:
fo.write('%2.2f ' % (M[a][c]))
fo.write('\n')
fo.close()
if Comments:
print('Single Criteria Netflows saved on file %s' % (fileName))
def _localConcordance(self,d,ind,wp,p):
"""
Parameters: d := diff observed, wp := weak preference threshold,
ind := indiffrence threshold, p := prefrence threshold.
Renders the concordance index per criteria (-1,0,1)
"""
if p is not None:
if d <= -p:
return Decimal('-1.0')
elif ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if d < Decimal('0.0'):
return Decimal('-1.0')
else:
return Decimal('1.0')
else:
if ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('-1.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('-1.0')
else:
if d < Decimal("0.0"):
return Decimal('-1.0')
else:
return Decimal('1.0')
def _localVeto(self, d, wv, v):
"""
Parameters: d := diff observed, v (wv) := (weak) veto threshold.
Renders the local veto state (-1,0,1).
"""
if v is not None:
if d <= - v:
return Decimal('1.0')
elif wv is not None:
if d <= - wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d <= -wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
def _localNegativeVeto(self, d, wv, v):
"""
Parameters: d := diff observed, v (wv) := (weak) veto threshold.
Renders the local negative veto state (-1,0,1).
"""
if v is not None:
if d >= v:
return Decimal('1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
elif wv is not None:
if d >= wv:
return Decimal('0.0')
else:
return Decimal('-1.0')
else:
return Decimal('-1.0')
class _MedianBipolarOutrankingDigraph(BipolarOutrankingDigraph,PerformanceTableau):
"""
Parameters: performanceTableau (fileName of valid py code)
optional: coalition (sublist of criteria)
percentile as rational (n,d)
for instance (50,100) or (1,2) renders Q2, (1,4) = Q1
(1,10) = D1, (3,4) = Q3
Specialization of the standard OutrankingDigraph class for generating
a median bipolar outranking digraph.
"""
def __init__(self,argPerfTab=None,coalition=None,percentile=(1,2),Debug=False):
from copy import deepcopy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
self.name = 'rel_' + perfTab.name
if isinstance(argPerfTab.actions,list):
actions = {}
for x in argPerfTab.actions:
actions[x] = {'name': str(x)}
self.actions = actions
else:
self.actions = copy.copy(argPerfTab.actions)
Min = Decimal('-100.0')
Med = Decimal('0.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
if coalition is None:
criteria = deepcopy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
self.relation = self._constructRelation(perfTab,percentile,Debug)
self.criteria = criteria
self.evaluation = deepcopy(perfTab.evaluation)
try:
self.description = deepcopy(perfTab.description)
except:
pass
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'normalized'
variant = 'median'
methodData['parameter'] = {'valuationType':valuationType,'variant':variant}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,t,percentile,Debug=False):
"""
Parameters: PerfTab.criteria, quantile (0 - 100)
Renders the quantile-outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
from copy import deepcopy
Min = self.valuationdomain['min']
Med = self.valuationdomain['med']
Max = self.valuationdomain['max']
#criteriaKey = [x for x in t.criteria]
criteriaRelation = {}
criteriaWeight = {}
criteriaVeto = {}
for x in criteria.keys():
gx = BipolarOutrankingDigraph(t,coalition=[x])
if Debug:
print('criterion : ', x, t.criteria[x]['weight'])
gx.showRelationTable()
print(gx.vetos)
else:
pass
criteriaRelation[x] = deepcopy(gx.relation)
criteriaWeight[x] = t.criteria[x]['weight']
criteriaVeto[x] = deepcopy(gx.vetos)
veto = {}
for x in criteria.keys():
for v in criteriaVeto[x]:
if Debug:
print('===>>>> v :', v,v[0][1],v[0][2])
else:
pass
try:
veto[v[0][0]][v[0][1]] = min(veto[v[0][0]][v[0][1]],v[0][2])
except:
try:
veto[v[0][0]][v[0][1]] = v[0][2]
except:
veto[v[0][0]] = {}
veto[v[0][0]][v[0][1]] = v[0][2]
if Debug:
print('Vetoes ', veto)
else:
pass
#actionsKey = [x for x in t.actions]
relation = {}
for x in actions.keys():
relation[x] = {}
for y in actions.keys():
try:
if veto[x][y] == Decimal("-1.0"):
relation[x][y] = Min
except:
pass
characteristics = []
for c in criteria.keys():
for i in range(int(criteriaWeight[c])):
characteristics.append(criteriaRelation[c][x][y])
characteristics.sort(reverse=False)
n = len(characteristics)
## if n % 2 == 1:
m = (n+1)*percentile[0]
if m % percentile[1] == 0:
k = m // percentile[1]
quantile = characteristics[k-1]
else:
k = (m//percentile[1])
quantile0 = characteristics[k-1]
quantile1 = characteristics[k]
if quantile1 <= Med:
quantile = quantile1
elif quantile0 >= Med:
quantile = quantile0
else:
quantile = Med
if Debug:
print('x,y,n,k', x,y,n,k)
print(characteristics, end=' ')
else:
pass
try:
relation[x][y] = min(quantile,veto[x][y])
except:
relation[x][y] = characteristics[k-1]
#print relation[x][y]
return relation
class _BipolarIntegerOutrankingDigraph(BipolarOutrankingDigraph,PerformanceTableau):
"""
Parameters:
| performanceTableau (fileName of valid py code)
| optional, coalition (sublist of criteria)
Specialization of the standard OutrankingDigraph class for generating
bipolar integer-valued outranking digraphs.
"""
def __init__(self,argPerfTab=None,coalition=None,
hasBipolarVeto=True,
hasSymmetricThresholds=True):
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau(integerWeights=True)
else:
perfTab = PerformanceTableau(argPerfTab)
self.name = 'rel_' + perfTab.name
#self.performanceTableau = copy.deepcopy(perfTab)
self.actions = copy.copy(perfTab.actions)
if coalition is None:
criteria = copy.copy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
totalWeight = Decimal('0')
for c in criteria:
totalWeight += abs(Decimal(str(criteria[c]['weight'])))
self.criteria = criteria
try:
self.description = copy.copy(perfTab.description)
except:
pass
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
except:
valuationType = 'integer'
try:
variant = perfTab.parameter['variant']
except:
variant = 'standard'
try:
vetoType = perfTab.parameter['vetoType']
except:
vetoType = 'normal'
methodData['parameter'] = {'valuationType':valuationType,\
'variant':variant, 'vetoType':vetoType}
self.methodData = methodData
Min = -totalWeight
Med = Decimal('0')
Max = totalWeight
self.valuationdomain = {'min':Min,'med':Med,'max':Max,
'hasIntegerValuation':True}
evaluation = copy.copy(perfTab.evaluation)
if vetoType == "bipolar":
hasBipolarVeto = True
self.relation = self._constructRelation(criteria,evaluation,\
hasBipolarVeto=hasBipolarVeto,\
hasSymmetricThresholds=hasSymmetricThresholds)
self.evaluation = evaluation
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,criteria,evaluation,hasBipolarVeto=False,hasSymmetricThresholds=True):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
NA = self.NA
relation = {}
vetos = []
if hasBipolarVeto:
negativeVetos = []
largePerformanceDifferencesCount = {}
for a in actions:
largePerformanceDifferencesCount[a] = {}
for b in actions:
largePerformanceDifferencesCount[a][b] = {'positive':0,'negative':0}
for a in actions:
relation[a] = {}
for b in actions:
if a == b:
relation[a][b] = Decimal('0')
else:
nc = len(criteria)
concordance = Decimal('0')
veto = {}
abvetos=[]
if hasBipolarVeto:
negativeVeto = {}
abNegativeVetos=[]
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
wp = None
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = None
except:
wp = None
try:
prefx = criterion['thresholds']['pref'][0]
prefy = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = prefx + prefy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = prefx + prefy * abs(evaluation[c][a])
except:
p = None
if criteria[c]['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
#print 'a,b,c,w,d,ind,wp,p,localConcordance(d,ind,wp,p)',a,b,c,criteria[c]['weight'],d,ind,wp,p,lc0
concordance = concordance + (lc0 * criteria[c]['weight'])
try:
wvx = criterion['thresholds']['weakVeto'][0]
wvy = criterion['thresholds']['weakVeto'][1]
if hasSymmetricThresholds:
wv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wv = wvx + wvy * abs(evaluation[c][a])
except:
wv = None
try:
vx = criterion['thresholds']['veto'][0]
vy = criterion['thresholds']['veto'][1]
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
vetoindex = self._localVeto(d,wv,v)
veto[c] = (vetoindex,d,wv,v)
if veto[c][0] > Min:
abvetos.append((c,veto[c]))
largePerformanceDifferencesCount[a][b]['negative'] -= 1
if hasBipolarVeto:
negativeVetoindex = self._localNegativeVeto(d,wv,v)
negativeVeto[c] = (negativeVetoindex,d,wv,v)
if negativeVeto[c][0] > Min:
abNegativeVetos.append((c,negativeVeto[c]))
largePerformanceDifferencesCount[a][b]['positive'] += 1
#if d < -wv:
# print 'd,wv,v,veto[c]',d,wv,v,veto[c]
else:
veto[c] = (Min,None,None,None)
if hasBipolarVeto:
negativeVeto[c] = (Min,None,None,None)
#outrankindex = concordance
concordindex = concordance
## init vetoes lists and indexes
#abvetos=[]
#abNegativeVetos=[]
vetoIndex = Min
negativeVetoIndex = Min
## compute bipolar vetoes indexes
for c in criteria:
vetoIndex = max(vetoIndex,veto[c][0])
if hasBipolarVeto:
negativeVetoIndex = max(negativeVetoIndex,negativeVeto[c][0])
# contradictory vetoes
if hasBipolarVeto:
# contradictory vetoes
if vetoIndex > Min and negativeVetoIndex > Min:
#abvetos.append((c,veto[c]))
#abNegativeVetos.append((c,negativeVeto[c]))
outrankindex = Med
# veto
elif vetoIndex > Min:
#abvetos.append((c,veto[c]))
if concordindex > Med:
outrankindex = Med
else:
outrankindex = min(concordindex,Max - vetoIndex + Min)
# dictator
elif negativeVetoIndex > Min:
#abNegativeVetos.append((c,negativeVeto[c]))
if concordindex < Med:
outrankindex = Med
else:
outrankindex = max(concordindex,negativeVetoIndex)
else:
# no vetoes or dictators
outrankindex = concordindex
else:
# hasBipolarVeto == False
outrankindex = min(concordindex,Max - vetoIndex + Min)
if abvetos != []:
vetos.append(([a,b,concordance],abvetos))
if hasBipolarVeto:
if abNegativeVetos != []:
negativeVetos.append(([a,b,concordance],abNegativeVetos))
relation[a][b] = outrankindex
#print 'relation[a][b]', a,b, relation[a][b]
self.vetos = vetos
if hasBipolarVeto:
self.negativeVetos = negativeVetos
self.largePerformanceDifferencesCount = largePerformanceDifferencesCount
return relation
def _localConcordance(self,d,ind,wp,p):
"""
Parameters:
| d := diff observed, h := weak preference threshold,
| p := prefrence threshold.
Renders the concordance index per criteria (Min,Med,Max)
"""
Max = 1
Med = 0
Min = -1
if p is not None:
if d <= -p:
return Min
elif ind is not None:
if d >= -ind:
return Max
else:
return Med
elif wp is not None:
if d > -wp:
return Max
else:
return Med
else:
if d < Decimal("0.0"):
return Min
else:
return Max
else:
if ind is not None:
if d >= -ind:
return Max
else:
return Min
elif wp is not None:
if d > -wp:
return Max
else:
return Min
else:
if d < Decimal("0.0"):
return Min
else:
return Max
def _localVeto(self, d, wv, v):
"""
Parameters:
| d := diff observed, v (wv) := (weak) veto threshold.
Renders the local veto state (Min,Med,Max).
"""
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
if v is not None:
if d <= - v:
return Max
elif wv is not None:
if d <= - wv:
return Med
else:
return Min
else:
return Min
elif wv is not None:
if d <= -wv:
return Med
else:
return Min
else:
return Min
def _localNegativeVeto(self, d, wv, v):
"""
Parameters:
| d := diff observed, v (wv) := (weak) veto threshold.
Renders the local negative veto state (Min,Med,Max).
"""
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
if v is not None:
if d >= v:
return Max
elif wv is not None:
if d >= wv:
return Med
else:
return Min
else:
return Min
elif wv is not None:
if d >= wv:
return Med
else:
return Min
else:
return Min
def showRelation(self):
"""
prints the relation valuation in ##.## format.
"""
print('* ---- Relation -----', end=' ')
for x in self.actions:
print()
for y in self.actions:
print('('+str(x)+', '+str(y)+') = '+' %d ' % (self.relation[x][y]))
print()
def savePy2Gprolog(self,name='temp'):
"""
save digraph in gprolog version
"""
print('*----- saving digraph in gprolog format -------------*')
Name = name+'.pl'
fo = open(Name,'w')
fo.write('/*------- data set ---------*\n')
fo.write(' * digraph pl export *\n')
fo.write(' * RB May 2007 *\n')
fo.write(' * ---------------------....*/\n')
fo.write('% data set: ')
fo.write(Name)
fo.write('\n')
actions = list(self.actions)
fo.write('actions(')
fo.write(str(actions))
fo.write(').\n')
criteria = self.criteria
listCriteriaNames = []
for g in criteria:
listCriteriaNames.append(g)
criteriaScale = criteria[g]['scale'][1]
try:
weakPreferenceThreshold = criteria[g]['thresholds']['ind'][0]
except:
weakPreferenceThreshold = criteria[g]['thresholds']['weakPreference'][0]
preferenceThreshold = criteria[g]['thresholds']['pref'][0]
vetoThreshold = criteria[g]['thresholds']['veto'][0]
weakVetoThreshold = criteria[g]['thresholds']['weakVeto'][0]
fo.write('criteria(')
fo.write(str(listCriteriaNames))
fo.write(').\n')
fo.write('criteriaScale(')
fo.write(str(criteriaScale))
fo.write(').\n')
# changing valuation from a bipolar to a positive one #
Med = self.valuationdomain['max']
Min = self.valuationdomain['min'] + Med
Max = self.valuationdomain['max'] + Med
fo.write('valuationDomain([')
fo.write(str(Min)+', '+str(Med)+', '+str(Max))
fo.write(']).\n')
fo.write('sumWeights(')
fo.write(str(Max))
fo.write(').\n')
fo.write('weakPreferenceThreshold(')
fo.write(str(weakPreferenceThreshold))
fo.write(').\n')
fo.write('preferenceThreshold(')
fo.write(str(preferenceThreshold))
fo.write(').\n')
fo.write('weakVetoThreshold(')
fo.write(str(weakVetoThreshold))
fo.write(').\n')
fo.write('vetoThreshold(')
fo.write(str(vetoThreshold))
fo.write(').\n')
relation = self.relation
for x in actions:
resx = 'relation('+str(x)+','
for y in actions:
if x == y:
value = Min
else:
value = relation[x][y] + Med
resxy = resx + str(y)+','+str(value)+').\n'
fo.write(resxy)
fo.close()
class _RandomElectre3OutrankingDigraph(_Electre3OutrankingDigraph):
"""
Parameters:
| n := nbr of actions, p := number criteria, scale := [Min,Max],
| thresholds := [h,q,v]
Specialization of the OutrankingDigraph class for generating temporary
Digraphs from random performance tableaux.
"""
def __init__(self,numberOfActions=7, numberOfCriteria=7, weightDistribution='random', weightScale = [1,10], commonScale=[0.0,100.0], commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0)], commonMode=['uniform',None,None]):
import random,time
self.name = 'rel_randomperftab'
# generate random performance tableau
tb = RandomS3PerformanceTableau(numberOfActions=numberOfActions,numberOfCriteria=numberOfCriteria, weightDistribution=weightDistribution, weightScale=weightScale, commonScale=commonScale,commonThresholds = commonThresholds, commonMode=commonMode)
self.actions = tb.actions
self.criteria = tb.criteria
self.evaluation = tb.evaluation
# instantiate default valuation domain
Min = Decimal('0.0')
Med = Decimal('50.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
# generate relation
self.relation = self._constructRelation(tb.criteria,tb.evaluation)
# standard Digraph parameters initialization
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
[docs]
class RandomBipolarOutrankingDigraph(BipolarOutrankingDigraph):
"""
Specialization of the BipolarOutrankingDigraph class for generating temporary Digraphs from RandomPerformanceTableau instances.
*Parameters*:
See :py:class:`randomPerfTabs.RandomPerformanceTableau` class.
"""
def __init__(self,numberOfActions=7,
numberOfCriteria=7,
weightDistribution='random',
weightScale = [1,10],
commonScale=[0.0,100.0],
commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(80.0,0.0)],
commonMode=('uniform',None,None),
hasBipolarVeto=True,
Normalized=True,
seed=None):
# generate random performance tableau
from copy import deepcopy
tb = RandomPerformanceTableau(numberOfActions=numberOfActions,\
numberOfCriteria=numberOfCriteria,\
weightDistribution=weightDistribution,\
weightScale=weightScale,\
commonScale=commonScale,\
commonThresholds = commonThresholds,\
commonMode=commonMode,\
seed=seed)
g = BipolarOutrankingDigraph(tb,Normalized=Normalized,\
hasBipolarVeto=hasBipolarVeto)
self.__dict__ = deepcopy(g.__dict__)
# self.name = deepcopy(g.name)
# self.actions = deepcopy(g.actions)
# self.criteria = deepcopy(g.criteria)
# self.evaluation = deepcopy(g.evaluation)
# self.relation = deepcopy(g.relation)
# self.valuationdomain = deepcopy(g.valuationdomain)
# self.NA = deepcopy(g.NA)
# self.order = len(self.actions)
# self.gamma = self.gammaSets()
# self.notGamma = self.notGammaSets()
[docs]
class RandomOutrankingDigraph(RandomBipolarOutrankingDigraph):
"""
Dummy for obsolete RandomOutrankingDigraph Class
"""
[docs]
class PolarisedOutrankingDigraph(PolarisedDigraph,OutrankingDigraph):
"""
Specilised :py:class:`digraphs.PolarisedDigraph` instance for Outranking Digraphs.
.. warning::
If called with argument *digraph=None*, a RandomBipolarOutrankingDigraph instance is generated first.
"""
def __init__(self,digraph=None,level=None,KeepValues=True,AlphaCut=False,StrictCut=False):
import copy
if digraph is None:
digraph = RandomBipolarOutrankingDigraph()
PolarisedDigraph.__init__(self,digraph=digraph,
level=level,KeepValues=KeepValues,
AlphaCut=AlphaCut,StrictCut=StrictCut)
self.criteria = copy.copy(digraph.criteria)
self.evaluation = copy.copy(digraph.evaluation)
self.NA = copy.copy(digraph.NA)
#self.vetos = self.computeVetos(digraph,level)
try:
self.vetos = copy.copy(digraph.vetos)
except:
pass
[docs]
class EquiSignificanceMajorityOutrankingDigraph(BipolarOutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the general OutrankingDigraph class for
temporary outranking digraphs with equisignificant criteria.
"""
def __init__(self,argPerfTab=None,coalition=None,hasNoVeto=False):
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
#self.performanceTableau = perfTab
self.name = 'eqsignmajrel_' + perfTab.name
self.actions = copy.copy(perfTab.actions)
Min = Decimal('-1')
Med = Decimal('0')
Max = Decimal('+1')
self.valuationdomain = {'hasIntegerValuation':True, 'min':Min,'med':Med,'max':Max}
#self.weightPreorder = perfTab.computeWeightPreorder()
if coalition is None:
criteria = copy.copy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = copy.copy(perfTab.criteria[g])
#self.relation = self._constructRelation(criteria,perfTab.evaluation, self.weightPreorder)
self.criteria = criteria
self.convertWeight2Decimal()
self.evaluation = copy.copy(perfTab.evaluation)
self.convertEvaluation2Decimal()
self.relation = self._constructRelation(perfTab,hasNoVeto=hasNoVeto)
methodData = {}
methodData['parameter'] = {'valuationType':'integer','variant':'bipolar'}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,perfTab,hasNoVeto=False):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
Debug = False
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
weightPreorder = self.computeWeightPreorder()
k = len(weightPreorder)
actionsList = [x for x in self.actions]
characteristicVector = {}
for x in actionsList:
characteristicVector[x] = {}
for y in actionsList:
characteristicVector[x][y] = []
if Debug:
print(characteristicVector)
coalitionsRelation = {}
for i in range(k):
_g = BipolarOutrankingDigraph(perfTab,coalition=weightPreorder[i],hasNoVeto=hasNoVeto)
_g.recodeValuation(-1,1)
coalitionsRelation[i] = _g.relation
relation = {}
for x in actionsList:
relation[x] = {}
for y in actionsList:
isPosStable = True
isNegStable = True
for i in range(k):
if coalitionsRelation[i][x][y] <= Med:
isPosStable = False
if coalitionsRelation[i][x][y] >= Med:
isNegStable = False
if isPosStable:
relation[x][y] = Max
elif isNegStable:
relation[x][y] = Min
else:
relation[x][y] = Med
if Debug:
print(x,y,relation[x][y])
return relation
# ------------
class _OutrankingOddsDigraph(OutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the general OutrankingDigraph class for
temporary outranking digraphs with odds valuation,
i.e. ratio of the number of positive over the number
of negative marginal outranking situations.
"""
def __init__(self,argPerfTab=None):
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
#self.performanceTableau = perfTab
self.name = 'eqsignmajrel_' + perfTab.name
self.actions = copy.copy(perfTab.actions)
Min = Decimal('0')
Med = Decimal('1')
Max = Decimal('Infinity')
self.valuationdomain = {'hasIntegerValuation':False, 'min':Min,'med':Med,'max':Max}
#self.weightPreorder = perfTab.computeWeightPreorder()
criteria = copy.copy(perfTab.criteria)
#self.relation = self._constructRelation(criteria,perfTab.evaluation, self.weightPreorder)
self.criteria = criteria
self.convertWeight2Decimal()
self.NA = perfTab.NA
self.evaluation = copy.copy(perfTab.evaluation)
self.convertEvaluation2Decimal()
self.relation = self._constructRelation()
methodData = {}
methodData['parameter'] = {'valuationType':'exponential','variant':'standard'}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,hasSymmetricThresholds=True):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the outranking ods relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
criteria = self.criteria
evaluation = self.evaluation
valuationdomain = self.valuationdomain
Max = Decimal('1')
Med = Decimal('0')
Min = Decimal('-1')
NA = self.NA
relation = {}
vetos = []
negativeVetos = []
largePerformanceDifferencesCount = {}
for a in actions:
largePerformanceDifferencesCount[a] = {}
for b in actions:
largePerformanceDifferencesCount[a][b] = {'positive':0,'negative':0}
for a in actions:
relation[a] = {}
for b in actions:
if a == b:
relation[a][b] = valuationdomain['med']
else:
nc = len(criteria)
concordance = Decimal('0')
discordance = Decimal('0')
veto = {}
abvetos=[]
negativeVeto = {}
abNegativeVetos=[]
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
wp = None
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = None
except:
wp = None
try:
prefx = criterion['thresholds']['pref'][0]
prefy = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = prefx + prefy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = prefx + prefy * abs(evaluation[c][a])
except:
p = None
if criteria[c]['weight'] > Decimal('0'):
d = evaluation[c][a] - evaluation[c][b]
else:
d = evaluation[c][b] - evaluation[c][a]
lc0 = self._localConcordance(d,ind,wp,p)
#print 'a,b,c,w,d,ind,wp,p,localConcordance(d,ind,wp,p)',a,b,c,criteria[c]['weight'],d,ind,wp,p,lc0
if lc0 > Med:
concordance = concordance + (lc0 * criteria[c]['weight'])
elif lc0 < Med:
discordance = discordance - (lc0 * criteria[c]['weight'])
try:
wvx = criterion['thresholds']['weakVeto'][0]
wvy = criterion['thresholds']['weakVeto'][1]
if hasSymmetricThresholds:
wv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wv = wvx + wvy * abs(evaluation[c][a])
except:
wv = None
try:
vx = criterion['thresholds']['veto'][0]
vy = criterion['thresholds']['veto'][1]
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
except:
v = None
vetoindex = self._localVeto(d,wv,v)
veto[c] = (vetoindex,d,wv,v)
if veto[c][0] > Med:
abvetos.append((c,veto[c]))
largePerformanceDifferencesCount[a][b]['negative'] -= 1
negativeVetoindex = self._localNegativeVeto(d,wv,v)
negativeVeto[c] = (negativeVetoindex,d,wv,v)
if negativeVeto[c][0] > Med:
abNegativeVetos.append((c,negativeVeto[c]))
largePerformanceDifferencesCount[a][b]['positive'] += 1
#if d < -wv:
# print 'd,wv,v,veto[c]',d,wv,v,veto[c]
else:
veto[c] = (Min,None,None,None)
negativeVeto[c] = (Min,None,None,None)
#outrankindex = oddsindex
oddsindex = concordance / discordance
## init vetoes lists and indexes
#abvetos=[]
#abNegativeVetos=[]
vetoIndex = Med
negativeVetoIndex = Med
## compute bipolar vetoes indexes
for c in criteria:
vetoIndex = max(vetoIndex,veto[c][0])
negativeVetoIndex = max(negativeVetoIndex,negativeVeto[c][0])
# contradictory vetoes
if vetoIndex > Med and negativeVetoIndex > Med:
#abvetos.append((c,veto[c]))
#abNegativeVetos.append((c,negativeVeto[c]))
outrankindex = valuationdomain['med']
# veto
elif vetoIndex > Med:
#abvetos.append((c,veto[c]))
if oddsindex > valuationdomain['med']:
outrankindex = valuationdomain['med']
else:
if vetoIndex < Med:
outrankindex = valuationdomain['min']
else:
outrankindex = oddsindex
# dictator
elif negativeVetoIndex > Med:
#abNegativeVetos.append((c,negativeVeto[c]))
if oddsindex < Med:
outrankindex = valuationdomain['med']
else:
if negativeVetoIndex < Med:
outrankindex = valuationdomain['max']
else:
outrankindex = oddsindex
if abvetos != []:
vetos.append(([a,b,oddsindex],abvetos))
if abNegativeVetos != []:
negativeVetos.append(([a,b,oddsindex],abNegativeVetos))
else:
outrankindex = oddsindex
relation[a][b] = outrankindex
#print 'relation[a][b]', a,b, relation[a][b]
self.vetos = vetos
self.negativeVetos = negativeVetos
self.largePerformanceDifferencesCount = largePerformanceDifferencesCount
return relation
# ------------
[docs]
class OrdinalOutrankingDigraph(OutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the general OutrankingDigraph class for
temporary ordinal outranking digraphs
"""
def __init__(self,argPerfTab=None,coalition=None,hasNoVeto=False):
"""Generic constructor for many outranking digraph models."""
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
self.performanceTableau = perfTab
self.name = 'rel_' + perfTab.name
self.actions = copy.deepcopy(perfTab.actions)
Min = Decimal('0.0')
Med = Decimal('50.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
#self.weightPreorder = perfTab.computeWeightPreorder()
if coalition is None:
criteria = copy.deepcopy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
self.convertWeight2Decimal()
self.evaluation = copy.deepcopy(perfTab.evaluation)
self.NA = copy.deepcopy(perfTab.NA)
self.convertEvaluation2Decimal()
self.relation = self._constructRelation(criteria,perfTab.evaluation,hasNoVeto=hasNoVeto)
methodData = {}
methodData['parameter'] = {'valuationType':'decimal','variant':'none'}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,criteria,evaluation,hasSymmetricThresholds=True,hasNoVeto=False):
"""
Parameters:
PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
Debug = False
weightPreorder = self.computeWeightPreorder()
if Debug:
print('weightPreorder=', weightPreorder)
# constructing the bipolar semiotical references lsr
lsr = []
for c in criteria:
ci = c + '+'
cj = c + '-'
lsr.append(ci)
lsr.append(cj)
# constructing the bipolar ordering of lsr
losr = []
k = 1
for w in weightPreorder:
lowp = []
lown = []
for i in w:
wp = i + '+'
wn = i + '-'
lowp.append(wp)
lown.append(wn)
losr.append((k,lowp))
losr.append((-k,lown))
k = k + 1
losr.sort()
if Debug:
print('lsr', lsr)
print('losr', losr)
actions = [x for x in self.actions]
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
NA = self.NA
# computing weight distributions
relation = {}
for a in actions:
relation[a] = {}
for b in actions:
# positive distribution ----------------------
veto = Decimal('0')
v = {}
for i in lsr:
v[i] = Decimal('0.0')
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
wp = None
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = None
except:
wp = None
try:
prefx = criterion['thresholds']['pref'][0]
prefy = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = prefx + prefy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = prefx + prefy * abs(evaluation[c][a])
except:
p = None
try:
wvx = criterion['thresholds']['weakVeto'][0]
wvy = criterion['thresholds']['weakVeto'][1]
if hasSymmetricThresholds:
wvv = wvx + wvy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wvv = wvx + wvy * abs(evaluation[c][a])
except:
wvv = None
if hasNoVeto:
wvv = None
try:
vx = criterion['thresholds']['veto'][0]
vy = criterion['thresholds']['veto'][1]
if hasSymmetricThresholds:
vv = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
vv = vx + vy * abs(evaluation[c][a])
except:
vv = None
if hasNoVeto:
vv = None
if criterion['weight'] < Decimal('0'):
d = evaluation[c][b] - evaluation[c][a]
else:
d = evaluation[c][a] - evaluation[c][b]
veto = veto + self._localVeto(d,wvv,vv)
counter = self._localConcordance(d,ind,wp,p)
if Debug:
print('--> c,a,b,evaluation[c][a],evaluation[c][b], d', c,a,b,evaluation[c][a],evaluation[c][b], d)
print('ind, wp, p, counter, veto', ind, wp, p, counter, veto)
v[c + '+'] = v[c + '+'] + counter
v[c + '-'] = v[c + '-'] + (Decimal('1.0') - counter)
else:
v[c + '+'] = v[c + '+'] + Decimal('0.5')
v[c + '-'] = v[c + '-'] + Decimal('0.5')
da = []
for w in losr:
d = Decimal('0')
for i in w[1]:
d = d + v[i]
da.append((w, d))
nc = len(losr)
dcd = [da[0]]
k = 1
while k < nc:
dcv = dcd[k-1][1]+da[k][1]
dcd.append((da[k][0],dcv))
k = k + 1
if Debug:
print('dcd', dcd)
# negative distribution ------------------------
vn = {}
for i in lsr:
vn[i] = Decimal('0.0')
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
wp = None
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = None
except:
wp = None
try:
prefx = criterion['thresholds']['pref'][0]
prefy = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = prefx + prefy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = prefx + prefy * abs(evaluation[c][a])
except:
p = None
if criterion['weight'] < Decimal('0'):
d = evaluation[c][b] - evaluation[c][a]
else:
d = evaluation[c][a] - evaluation[c][b]
counter = self._localConcordance(d,ind,wp,p)
vn[c + '+'] = vn[c + '+'] - counter + Decimal('1.0')
vn[c + '-'] = vn[c + '-'] + counter
else:
vn[c + '+'] = vn[c + '+'] + Decimal('0.5')
vn[c + '-'] = vn[c + '-'] + Decimal('0.5')
dan = []
for w in losr:
d = Decimal('0')
for i in w[1]:
d = d + vn[i]
dan.append((w, d))
nc = len(losr)
dcg = [dan[0]]
k = 1
while k < nc:
dcv = dcg[k-1][1]+dan[k][1]
dcg.append((dan[k][0],dcv))
k = k + 1
if Debug:
print('dcg' , dcg)
# first order stochastic dominance
i = 0
j = 0
while i < nc:
if dcd[i][1] > dcg[i][1]:
break
elif dcd[i][1] < dcg[i][1]:
j += 1
i += 1
if i >= nc:
if j > 0 and veto == Decimal('0'):
relation[a][b] = Max
elif veto == Decimal('0'):
relation[a][b] = Med
else:
relation[a][b] = Min
else:
i = 0
j = 0
while i < nc:
if dcd[i][1] < dcg[i][1]:
break
elif dcd[i][1] > dcg[i][1]:
j += 1
i += 1
if i >= nc:
if j > 0 or veto == Decimal('1'):
relation[a][b] = Min
elif veto < Decimal('1'):
relation[a][b] = Med
else:
if veto > Decimal('0'):
relation[a][b] = Min
else:
relation[a][b] = Med
for a in actions:
relation[a][a] = Min
return relation
def _localConcordance(self,d,ind,wp,p):
"""
Parameters:
| d := diff observed, h := indifference threshold,
| p := prefrence threshold.
Renders the concordance index per criterion.
"""
Debug = False
if Debug:
print('ordinal concordance locla concordance')
print('d,ind,wp,p', d,ind,wp,p)
if p is not None:
if d <= -p:
return Decimal('0.0')
elif ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.5')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.5')
else:
if d > Decimal("0.0"):
return Decimal('1.0')
else:
return Decimal('0.5')
else:
if ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if d < Decimal("0.0"):
return Decimal('0.0')
else:
return Decimal('1.0')
def _localVeto(self, d, wv,v):
"""
Parameters: d := diff observed, v := veto threshold.
Renders the local veto state
"""
if v is not None:
if d <= - v:
return Decimal('1.0')
elif wv is not None:
if d <= - wv:
return Decimal('0.5')
else:
return Decimal('0.0')
else:
return Decimal('0.0')
elif wv is not None:
if d <= -wv:
return Decimal('0.5')
else:
return Decimal('0.0')
else:
return Decimal('0.0')
[docs]
class UnanimousOutrankingDigraph(OutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the general OutrankingDigraph class for
temporary unanimous outranking digraphs
"""
def __init__(self,argPerfTab=None,coalition=None,hasNoVeto=False):
"""Generic constructor for many outranking digraph models."""
import copy
if isinstance(argPerfTab, (PerformanceTableau,RandomPerformanceTableau)):
perfTab = argPerfTab
else:
if argPerfTab is None:
perfTab = RandomPerformanceTableau()
else:
perfTab = PerformanceTableau(argPerfTab)
self.performanceTableau = perfTab
self.name = 'rel_' + perfTab.name
self.actions = copy.deepcopy(perfTab.actions)
Min = Decimal('0.0')
Med = Decimal('50.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
#self.weightPreorder = perfTab.computeWeightPreorder()
if coalition is None:
criteria = copy.deepcopy(perfTab.criteria)
else:
criteria = {}
for g in coalition:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
self.convertWeight2Decimal()
self.evaluation = copy.deepcopy(perfTab.evaluation)
self.NA = copy.deepcopy(perfTab.NA)
self.convertEvaluation2Decimal()
self.relation = self._constructRelation(criteria,perfTab.evaluation,hasNoVeto=hasNoVeto)
methodData = {}
methodData['parameter'] = {'valuationType':'decimal','variant':'none'}
self.methodData = methodData
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,criteria,evaluation,hasSymmetricThresholds=True,hasNoVeto=True):
"""
Parameters: PerfTab.criteria, PerfTab.evaluation.
Renders the biploar valued outranking relation from the data
of a given performance tableau instantiation PerfTab.
"""
actions = self.actions
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
NA = self.NA
relation = {}
for a in actions:
relation[a] = {}
for b in actions:
nc = len(criteria)
nvc = nc
counter = 0
veto = 0
for c in criteria:
criterion = criteria[c]
if evaluation[c][a] != NA and evaluation[c][b] != NA:
#d = evaluation[c][a] - evaluation[c][b]
try:
indx = criterion['thresholds']['ind'][0]
indy = criterion['thresholds']['ind'][1]
if hasSymmetricThresholds:
ind = indx +indy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
ind = indx +indy * abs(evaluation[c][a])
wp = None
except:
ind = None
try:
wpx = criterion['thresholds']['weakPreference'][0]
wpy = criterion['thresholds']['weakPreference'][1]
if hasSymmetricThresholds:
wp = wpx + wpy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
wp = wpx + wpy * abs(evaluation[c][a])
ind = None
except:
wp = None
try:
prefx = criterion['thresholds']['pref'][0]
prefy = criterion['thresholds']['pref'][1]
if hasSymmetricThresholds:
p = prefx + prefy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
p = prefx + prefy * abs(evaluation[c][a])
except:
p = None
if criterion['weight'] < Decimal('0'):
d = evaluation[c][b] - evaluation[c][a]
else:
d = evaluation[c][a] - evaluation[c][b]
lc0 = self._localConcordance(d,ind,wp,p)
counter += lc0
if not hasNoVeto:
try:
vx = criterion['thresholds']['veto'][0]
vy = criterion['thresholds']['veto'][1]
if hasSymmetricThresholds:
v = vx + vy * max(abs(evaluation[c][a]),abs(evaluation[c][b]))
else:
v = vx + vy * abs(evaluation[c][a])
veto = veto + self._localVeto(d, v)
except:
veto = veto + Decimal("0")
else:
nvc = nvc - 1
# under the universality condition nvc not to be used.
if not hasNoVeto:
if veto == Decimal("0"):
if counter == nc:
relation[a][b] = Max
elif counter == -nc:
relation[a][b] = Min
else:
relation[a][b] = Med
else:
relation[a][b] = Min
else:
if counter == nc:
relation[a][b] = Max
elif counter == -nc:
relation[a][b] = Min
else:
relation[a][b] = Med
for a in actions:
relation[a][a] = Min
return relation
def _localConcordance(self,d,ind,wp,p):
"""
Parameters: d := diff observed, h := indifference threshold,
p := prefrence threshold.
Renders the concordance index per criterion.
"""
Debug = False
if Debug:
print('ordinal concordance locla concordance')
print('d,ind,wp,p', d,ind,wp,p)
if p is not None:
if d <= -p:
return Decimal('-1.0')
elif ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('0.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if d > Decimal("-1.0"):
return Decimal('1.0')
else:
return Decimal('0.0')
else:
if ind is not None:
if d >= -ind:
return Decimal('1.0')
else:
return Decimal('-1.0')
elif wp is not None:
if d > -wp:
return Decimal('1.0')
else:
return Decimal('-1.0')
else:
if d < Decimal("0.0"):
return Decimal('-1.0')
else:
return Decimal('1.0')
def _localVeto(self, d, v):
"""
Parameters: d := diff observed, v := veto threshold.
Renders the local veto state
"""
if d > -v:
return Decimal("0")
else:
return Decimal("1")
[docs]
class RobustOutrankingDigraph(BipolarOutrankingDigraph):
"""
*Parameters*:
performanceTableau (fileName or valid py code)
Specialization of the general OutrankingDigraph class for
new robustness analysis distinguishing between three nested stability levels:
* +4 (resp. -4) : unanimous outranking (resp. outranked;
* +3 (resp. -3) : majority outranking (resp. outranked) situation
in all coalitions of equi-ignificant citeria;
* +2 (resp. -2) : validated outranking (resp. outranked) situation
with all potential weights compatible with the criteria significance preorder;
* +1 (resp. -1) : validated outranking (resp. outranked) situation with
the given criteria significances;
* 0 : indeterminate preferential situation-
*Sematics*:
* alternative *x* robustly outranks alternative *y* when the
outranking situation is qualified greater or equal to level +2
and there is no considerable counter-performance
observed on a discordant criteria;
* alternative *x* robustly does not outrank alternative *y* when the
outranking situation is qualified lower or equal to level -2
and there is no considerable performance
observed on a discordant criteria.
*Usage example*:
>>> from outrankingDigraphs import *
>>> t = RandomPerformanceTableau(numberOfActions=7,weightDistribution='random',seed=100)
>>> t.showPerformanceTableau()
*---- performance tableau -----*
Criteria | 'g1' 'g2' 'g3' 'g4' 'g5' 'g6' 'g7'
Actions | 4 6 2 7 4 4 2
---------|-------------------------------------------------------
'a1' | 44.51 43.90 19.10 16.22 14.81 45.49 41.66
'a2' | NA 38.75 27.73 21.53 79.70 22.03 12.82
'a3' | 58.00 35.84 41.46 51.16 67.48 33.83 21.92
'a4' | 24.22 29.12 22.41 NA 13.97 31.83 75.74
'a5' | 29.10 34.79 21.52 39.35 NA 69.98 15.45
'a6' | 96.58 62.22 56.90 32.06 80.16 48.80 6.05
'a7' | 82.29 44.23 46.37 47.67 69.62 82.88 9.96
>>> t.showWeightPreorder()
*------- weights preordering --------*
['g3', 'g7'] (2) <
['g1', 'g5', 'g6'] (4) <
['g2'] (6) <
['g4'] (7)
>>> g = RobustOutrankingDigraph(t)
>>> g
*------- Object instance description ------*
Instance class : RobustOutrankingDigraph
Instance name : robust_randomperftab
Actions : 7
Criteria : 7
Size : 19
Determinateness (%) : 67.53
Valuation domain : [-1.00;1.00]
Attributes : ['name', 'methodData', 'actions',
'order', 'criteria', 'evaluation',
'vetos', 'valuationdomain', 'relation',
'concordanceRelation',
'largePerformanceDifferencesCount',
'ordinalRelation', 'equisignificantRelation',
'unanimousRelation', 'stability',
'gamma', 'notGamma']
>>> g.showRelationTable(StabilityDenotation=True)
* ---- Relation Table -----
r/(stab) | 'a1' 'a2' 'a3' 'a4' 'a5' 'a6' 'a7'
---------|------------------------------------------------------------
'a1' | +1.00 -0.03 -0.17 +0.55 +0.00 -0.72 -0.45
| (+4) (-2) (-2) (+2) (+1) (-2) (-2)
'a2' | +0.03 +1.00 -0.17 +0.21 -0.10 -0.45 -0.45
| (+2) (+4) (-2) (+2) (-2) (-2) (-2)
'a3' | +0.17 +0.38 +1.00 +0.62 +0.59 +0.00 +0.00
| (+2) (+2) (+4) (+2) (+2) (-1) (-1)
'a4' | -0.21 -0.21 -0.34 +1.00 -0.21 -0.62 -0.62
| (-2) (-2) (-2) (+4) (-2) (-2) (-2)
'a5' | +0.03 +0.38 -0.17 +0.48 +1.00 +0.03 -0.72
| (+2) (+2) (-2) (+2) (+4) (+2) (-2)
'a6' | +0.86 +0.72 +0.00 +0.62 -0.03 +1.00 +0.00
| (+2) (+2) (+1) (+2) (-2) (+4) (+1)
'a7' | +0.86 +0.52 +0.62 +0.62 +0.72 +0.00 +1.00
| (+2) (+2) (+2) (+2) (+2) (-1) (+4)
"""
def __init__(self, filePerfTab = None,Debug=False,hasNoVeto=True):
import copy
if filePerfTab is None:
filePerfTab = 'randomPerf'
t = RandomPerformanceTableau()
t.save(filePerfTab)
self.name='newrobust_randomPerf'
else:
self.name = 'robust_' + filePerfTab.name
cardinal = BipolarOutrankingDigraph(filePerfTab,Normalized=True,WithConcordanceRelation=True,WithVetoCounts=True)
ordinal = OrdinalOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
ordinal.recodeValuation(-1,1)
equisignificant = EquiSignificanceMajorityOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
equisignificant.recodeValuation(-1,1)
unanimous = UnanimousOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
unanimous.recodeValuation(-1,1)
if Debug:
print('unanimous')
print(unanimous.valuationdomain)
unanimous.showRelationTable(hasIntegerValuation=True)
print('equisignificant')
print(equisignificant.valuationdomain)
equisignificant.showRelationTable()
print('ordinal')
print(ordinal.valuationdomain)
ordinal.showRelationTable()
print('cardinal')
print(cardinal.valuationdomain)
cardinal.showRelationTable(hasLPDDenotation=True)
try:
self.description = copy.copy(cardinal.description)
except:
pass
try:
self.methodData = copy.copy(cardinal.methodData)
except:
pass
self.actions = copy.copy(cardinal.actions)
self.order = len(self.actions)
try:
self.objectives = copy.copy(cardinal.objectives)
except:
pass
self.criteria = copy.copy(cardinal.criteria)
self.evaluation = copy.copy(cardinal.evaluation)
self.NA = copy.copy(cardinal.NA)
self.vetos = copy.copy(cardinal.vetos)
self.valuationdomain = copy.copy(cardinal.valuationdomain)
self.relation = copy.copy(cardinal.relation)
self.concordanceRelation = copy.copy(cardinal.concordanceRelation)
self.largePerformanceDifferencesCount = copy.copy(cardinal.largePerformanceDifferencesCount)
self.ordinalRelation = copy.copy(ordinal.relation)
self.equisignificantRelation = copy.copy(equisignificant.relation)
self.unanimousRelation = copy.copy(unanimous.relation)
self.stability = self._constructRelation()
if Debug:
self.showRelationTable(hasStabilityDenotation=True)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self):
"""
Help method for constructing robust outranking relation.
"""
Debug = False
unanimousRelation = self.unanimousRelation
equisignificantRelation = self.equisignificantRelation
ordinalRelation = self.ordinalRelation
concordanceRelation = self.concordanceRelation
if Debug:
print(self.valuationdomain)
Min = self.valuationdomain['min']
Med = self.valuationdomain['med']
Max = self.valuationdomain['max']
One = Decimal("1")
MinusOne = Decimal("-1")
actions = [x for x in self.actions]
stability = {}
relation = {}
for a in actions:
stability[a] = {}
relation[a] = {}
for b in actions:
if a != b:
if unanimousRelation[a][b] == One:
stability[a][b] = 4
relation[a][b] = self.relation[a][b]
elif unanimousRelation[a][b] == MinusOne:
stability[a][b] = -4
relation[a][b] = self.relation[a][b]
elif equisignificantRelation[a][b] == One:
stability[a][b] = 3
relation[a][b] = self.relation[a][b]
elif equisignificantRelation[a][b] == MinusOne:
stability[a][b] = -3
relation[a][b] = self.relation[a][b]
elif ordinalRelation[a][b] == One:
stability[a][b] = 2
relation[a][b] = self.relation[a][b]
elif ordinalRelation[a][b] == MinusOne:
stability[a][b] = -2
relation[a][b] = self.relation[a][b]
elif concordanceRelation[a][b] > Med:
stability[a][b] = 1
relation[a][b] = Med
elif concordanceRelation[a][b] < Med:
stability[a][b] = -1
relation[a][b] = Med
else:
stability[a][b] = 0
relation[a][b] = Med
else:
stability[a][b] = 0
relation[a][b] = Med
self.relation = relation
return stability
[docs]
def showRelationTable(self,IntegerValues=False,
actionsSubset= None,
rankingRule=None,
Sorted=True,
hasLPDDenotation=False,
StabilityDenotation=True,
hasLatexFormat=False,
hasIntegerValuation=False,
relation=None,
ReflexiveTerms=True):
"""
prints the relation valuation in actions X actions table format by default with
the stability denotation in brackets:
+4 | -4 : unanimous outranking | outranked situation;
+3 | -3 : validated outranking | outranked situation in each coalition
of equisignificant criteria;
+2 | -2 : outranking | outranked situation validated with all potential significance weights
that are compatible with the given significance preorder;
+1 | -1 : validated outranking | outranked situation with the given significance weights;
0 : indeterminate relational situation.
"""
OutrankingDigraph.showRelationTable(self,
IntegerValues=IntegerValues,
actionsSubset= actionsSubset,
rankingRule=rankingRule,
Sorted=Sorted,
hasLPDDenotation=hasLPDDenotation,
StabilityDenotation=StabilityDenotation,
hasLatexFormat=hasLatexFormat,
hasIntegerValuation=hasIntegerValuation,
relation=relation,
ReflexiveTerms=ReflexiveTerms)
[docs]
class OldRobustOutrankingDigraph(BipolarOutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the general OutrankingDigraph class for
robustness analysis distinguishing 3 levels of stability.
"""
def __init__(self, filePerfTab = None,Debug=False,hasNoVeto=True):
import copy
if filePerfTab is None:
filePerfTab = 'randomPerf'
t = RandomPerformanceTableau()
t.save(filePerfTab)
self.name='robust_randomPerf'
else:
self.name = 'robust_' + filePerfTab.name
cardinal = BipolarOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
ordinal = OrdinalOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
unanimous = UnanimousOutrankingDigraph(filePerfTab,hasNoVeto=hasNoVeto)
if Debug:
print('unanimous')
print(unanimous.valuationdomain)
unanimous.showRelationTable()
print('ordinal')
print(ordinal.valuationdomain)
ordinal.showRelationTable()
print('cardinal')
print(cardinal.valuationdomain)
cardinal.showRelationTable()
try:
self.description = copy.deepcopy(cardinal.description)
except:
pass
try:
self.methodData = copy.deepcopy(cardinal.methodData)
except:
pass
self.actions = copy.copy(cardinal.actions)
self.order = len(self.actions)
self.criteria = copy.copy(cardinal.criteria)
self.evaluation = copy.copy(cardinal.evaluation)
self.NA = copy.copy(cardinal.NA)
self.vetos = copy.copy(cardinal.vetos)
self.valuationdomain = {'min':Decimal("-3"), 'med':Decimal("0"), 'max':Decimal("3")}
self.cardinalRelation = copy.copy(cardinal.relation)
self.cardinalValuationdomain = copy.copy(cardinal.valuationdomain)
self.relation = self._constructRelation(unanimous, ordinal, cardinal,hasNoVeto=hasNoVeto)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self, unanimous, ordinal, cardinal,hasNoVeto=True):
"""
Parameters: normal -, ordinal -, and unanimous outranking relation.
Help method for constructing robust outranking relation.
"""
Debug = False
if Debug:
print(self.valuationdomain)
uMin = unanimous.valuationdomain['min']
uMed = unanimous.valuationdomain['med']
uMax = unanimous.valuationdomain['max']
oMin = ordinal.valuationdomain['min']
oMed = ordinal.valuationdomain['med']
oMax = ordinal.valuationdomain['max']
cMin = cardinal.valuationdomain['min']
cMed = cardinal.valuationdomain['med']
cMax = cardinal.valuationdomain['max']
rMin = self.valuationdomain['min']
rMed = self.valuationdomain['med']
rMax = self.valuationdomain['max']
actions = [x for x in self.actions]
relation = {}
for a in actions:
relation[a] = {}
for b in actions:
if a != b:
if unanimous.relation[a][b] == uMax:
relation[a][b] = rMax
elif unanimous.relation[a][b] == uMin:
relation[a][b] = rMin
elif ordinal.relation[a][b] == oMax:
relation[a][b] = Decimal('2.0')
elif ordinal.relation[a][b] == oMin:
relation[a][b] = Decimal('-2.0')
elif cardinal.relation[a][b] > cMed:
relation[a][b] = Decimal('1.0')
elif cardinal.relation[a][b] < cMed:
relation[a][b] = Decimal('-1.0')
else:
relation[a][b] = rMed
else:
relation[a][b] = rMed
return relation
[docs]
def showRelationTable(self):
""" specialisation for integer values """
Digraph.showRelationTable(self,IntegerValues=True)
[docs]
def saveAMPLDataFile(self,name='temp',Unique=False,Comments=True):
"""
save the ampl reverse data for cplex
"""
actionsList = [x for x in self.actions]
actionsList.sort()
criteriaList = [x for x in self.criteria]
criteriaList.sort()
nc = len(criteriaList)
relation = self.relation
fileName = 'amplReverse'+name+'.dat'
fo = open(fileName,'w')
fo.write('data;\n\n')
fo.write('set CRITERIA := ')
for i in range(nc):
fo.write(str(criteriaList[i])+' ')
fo.write(';\n\n')
fo.write('set ALLPAIRS := \n')
for x in actionsList:
for y in actionsList:
if x != y:
fo.write(str(x)+str(y)+' ')
fo.write('\n')
fo.write(';\n\n')
if Unique:
pairwiseComparison = {}
for x in actionsList:
for y in actionsList:
if x != y:
xy = str(x)+str(y)
lineString = ''
for i in range(nc):
value = self.computeCriterionRelation(criteriaList[i],x,y)
value = (value + Decimal("1.0"))/Decimal("2.0")
lineString += '%.1f ' % (value)
pairwiseComparison[xy] = lineString
self.pairwiseComparison = pairwiseComparison
p2 = []
pm2 = []
p1 = []
pm1 = []
p0 = []
if Unique:
setP2 = set()
setPm2 = set()
setP1 = set()
setPm1 = set()
setP0 = set()
count2 = 0
countm2 = 0
count1 = 0
countm1 = 0
count0 = 0
countSave2 = 0
countSavem2 = 0
countSave1 = 0
countSavem1 = 0
countSave0 = 0
for x in actionsList:
for y in actionsList:
if x != y:
xy = str(x)+str(y)
if relation[x][y] == Decimal("2.0"):
count2 += 1
if pairwiseComparison[xy] not in setP2:
countSave2 += 1
p2.append(str(x)+str(y))
setP2.add(pairwiseComparison[xy])
elif relation[x][y] == Decimal("-2.0"):
countm2 += 1
if pairwiseComparison[xy] not in setPm2:
countSavem2 += 1
pm2.append(str(x)+str(y))
setPm2.add(pairwiseComparison[xy])
elif relation[x][y] == Decimal("1.0"):
count1 += 1
if pairwiseComparison[xy] not in setP1:
countSave1 += 1
p1.append(str(x)+str(y))
setP1.add(pairwiseComparison[xy])
elif relation[x][y] == Decimal("-1.0"):
countm1 += 1
if pairwiseComparison[xy] not in setPm1:
countSavem1 += 1
pm1.append(str(x)+str(y))
setPm1.add(pairwiseComparison[xy])
elif relation[x][y] == Decimal("0.0"):
count0 += 1
if pairwiseComparison[xy] not in setP0:
countSave0 += 1
p0.append(str(x)+str(y))
setP0.add(pairwiseComparison[xy])
if Comments:
print('counts: 2 \t-2 \t1 \t-1 \t0')
print(' %d \t%d \t%d \t%d \t%d\n' % (count2,countm2,count1,countm1,count0))
print(' %d \t%d \t%d \t%d \t%d\n' % (countSave2,countSavem2,countSave1,countSavem1,countSave0))
print(' -----------------------------------------------------------')
print(' %d \t%d \t%d \t%d \t%d\n' % (count2-countSave2,countm2-countSavem2,count1-countSave1,countm1-countSavem1,count0-countSave0))
totalcount = count2+ countm2+count1+ countm1+count0
totalcountSave = countSave2+countSavem2+countSave1+countSavem1+countSave0
print('totals: counts: %d, countsUnique: \t%d, reduction: %d\n' %(totalcount,totalcountSave,totalcount-totalcountSave))
else:
for x in actionsList:
for y in actionsList:
if x != y:
xy = str(x)+str(y)
if relation[x][y] == Decimal("2.0"):
p2.append(str(x)+str(y))
elif relation[x][y] == Decimal("-2.0"):
pm2.append(str(x)+str(y))
elif relation[x][y] == Decimal("1.0"):
p1.append(str(x)+str(y))
elif relation[x][y] == Decimal("-1.0"):
pm1.append(str(x)+str(y))
elif relation[x][y] == Decimal("0.0"):
p0.append(str(x)+str(y))
fo.write('set PAIRS2 :=\n')
i = 0
for x in p2:
i += 1
if i < 7:
fo.write(x+' ')
else:
fo.write('\n'+x+' ')
i = 0
fo.write(';\n\n')
fo.write('set PAIRSm2 :=\n')
i = 0
for x in pm2:
i += 1
if i < 7:
fo.write(x+' ')
else:
fo.write('\n'+x+' ')
i = 0
fo.write(';\n\n')
fo.write('set PAIRS1 :=\n')
for x in p1:
fo.write(x+' ')
fo.write(';\n\n')
fo.write('set PAIRSm1 :=\n')
for x in pm1:
fo.write(x+' ')
fo.write(';\n\n')
fo.write('set PAIRS0 :=\n')
for x in p0:
fo.write(x+' ')
fo.write(';\n\n')
fo.write('param S : ')
for i in range(nc):
fo.write(str(criteriaList[i])+' ')
fo.write(':= \n')
if Unique:
Sset = set()
for x in actionsList:
for y in actionsList:
if x != y:
xy = str(x)+str(y)
if pairwiseComparison[xy] not in Sset:
fo.write(xy+' '+pairwiseComparison[xy]+'\n')
Sset.add(pairwiseComparison[xy])
fo.write(';\n')
else:
for x in actionsList:
for y in actionsList:
if x != y:
fo.write(str(x)+str(y)+' ')
for i in range(nc):
value = self.computeCriterionRelation(criteriaList[i],x,y)
value = (value + Decimal("1.0"))/Decimal("2.0")
fo.write('%.1f ' % (value))
fo.write('\n')
fo.write(';\n')
fo.close()
[docs]
def saveXMLRubisOutrankingDigraph(self,name='temp',category='Rubis outranking robustness digraph',subcategory='Choice recommendation',author='digraphs Module (RB)',reference='saved from Python',comment=True,servingD3=True):
"""
save complete robust Rubis problem and result in XML format with unicode encoding.
"""
import codecs
self.computeRubyChoice()
if comment:
print('*----- saving outranking robustness digraph in XML format -------------*')
nameExt = name+'.xml'
fo = codecs.open(nameExt,'w',encoding='utf-8')
fo.write('<?xml version="1.0" encoding="UTF-8"?>\n')
fo.write('<!DOCTYPE rubisOutrankingDigraph SYSTEM "http://localhost/rubisServer/Schemas/rubisOutrankingDigraph-1.0/rubisOutrankingDigraph.dtd">\n')
if not servingD3:
fo.write('<?xml-stylesheet type="text/xsl" href="rubisOutrankingDigraph.xsl"?>\n')
else:
fo.write('<!-- ?xml-stylesheet type="text/xsl" href="robustRubisOutrankingDigraph.xsl"? -->\n')
fo.write('<rubisOutrankingDigraph xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="rubisOutrankingDigraph.xsd"')
fo.write(' category="' + str(category)+'" subcategory="'+str(subcategory)+'">\n')
fo.write('<header>\n')
fo.write('<comment>header declaration </comment>\n')
fo.write('<name>')
fo.write(str(nameExt))
fo.write('</name>\n')
fo.write('<author>')
fo.write(str(author))
fo.write('</author>\n')
fo.write('<reference>')
fo.write(str(reference))
fo.write('</reference>\n')
fo.write('</header>')
actionsOrigList = [x for x in self.actions_orig]
actionsOrigList.sort()
fo.write('<actions>\n')
fo.write('<comment>Potential decision actions </comment>\n')
for x in actionsOrigList:
fo.write('<action id="'+str(x)+'">\n')
fo.write('<name>')
try:
fo.write(str(self.actions_orig[x]['name']))
except:
pass
fo.write('</name>\n')
fo.write('<comment>')
try:
fo.write(str(self.actions_orig[x]['comment']))
except:
pass
fo.write('</comment>\n')
fo.write('</action>\n')
fo.write('</actions>\n')
fo.write('<criteria>\n')
fo.write('<comment>List of performance criteria </comment>\n')
criteriaList = [g for g in self.criteria]
criteriaList.sort()
for g in criteriaList:
fo.write('<criterion id="'+str(g)+'" category="performance">\n')
fo.write('<name>')
try:
fo.write(str(self.criteria[g]['name']))
except:
pass
fo.write('</name>\n')
fo.write('<comment>')
try:
fo.write(str(self.criteria[g]['comment']))
except:
pass
fo.write('</comment>\n')
fo.write('<scale>')
fo.write('<min>')
fo.write('%.2f' % (self.criteria[g]['scale'][0]))
fo.write('</min>')
fo.write('<max>')
fo.write('%.2f' % (self.criteria[g]['scale'][1]))
fo.write('</max>')
fo.write('</scale>\n')
fo.write('<thresholds>\n')
try:
th1,th2 = self.criteria[g]['thresholds']['ind']
fo.write('<indifference>'),fo.write('(%.2f,%.2f)' % (th1,th2) ), fo.write('</indifference>\n')
except:
try:
th1,th2 = self.criteria[g]['thresholds']['weakPreference']
fo.write('<weakPreference>'),fo.write('(%.2f,%.2f)' % (th1,th2) ),fo.write('</weakPreference>\n')
except:
pass
fo.write('<preference>')
fo.write('(%.2f,%.2f)' % (self.criteria[g]['thresholds']['pref'][0],self.criteria[g]['thresholds']['pref'][1]))
fo.write('</preference>\n')
try:
th1,th2 = self.criteria[g]['thresholds']['weakVeto']
fo.write('<weakVeto>'),fo.write('(%.2f,%.2f)' % (th1,th2) ),fo.write('</weakVeto>\n')
except:
pass
try:
th1,th2 = self.criteria[g]['thresholds']['veto']
fo.write('<veto>'),fo.write('(%.2f,%.2f)' % (th1,th2) ),fo.write('</veto>\n')
except:
pass
fo.write('</thresholds>')
fo.write('<weight>')
## fo.write(str(self.criteria[g]['weight']))
fo.write('%.2f' % (self.criteria[g]['weight']))
fo.write('</weight>')
fo.write('</criterion>\n')
fo.write('</criteria>\n')
evaluation = self.evaluation
fo.write('<evaluations>\n')
fo.write('<comment>performance table </comment>\n')
for g in criteriaList:
fo.write('<evaluation>\n')
fo.write('<criterionID>'+str(g)+'</criterionID>\n')
for x in actionsOrigList:
fo.write('<performance>\n')
fo.write('<actionID>')
fo.write(str(x))
fo.write('</actionID>\n')
fo.write('<value>')
## fo.write(str(evaluation[g][x]))
fo.write('%.2f' % (evaluation[g][x]))
fo.write('</value>\n')
fo.write('</performance>\n')
fo.write('</evaluation>\n')
fo.write('</evaluations>\n')
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
Min = self.valuationdomain['min']
fo.write('<valuationDomain category="bipolar" subcategory="robustness">\n')
fo.write('<comment>valuation domain declaration </comment>')
fo.write('<min>')
fo.write('%1d' % (int(Min)))
fo.write('</min>\n')
fo.write('<med>')
fo.write('%1d' % (int(Med)))
fo.write('</med>\n')
fo.write('<max>')
fo.write('%1d' % (int(Max)))
fo.write('</max>\n')
fo.write('</valuationDomain>\n')
fo.write('<relation>\n')
relation = self.relation_orig
fo.write('<comment>valued outranking relation declaration. </comment>')
for x in actionsOrigList:
for y in actionsOrigList:
fo.write('<arc>\n')
fo.write('<initialActionID>')
fo.write(str(x))
fo.write('</initialActionID>\n')
fo.write('<terminalActionID>')
fo.write(str(y))
fo.write('</terminalActionID>\n')
fo.write('<value>')
## fo.write(str(relation[x][y]))
fo.write('%1d' % (int(relation[x][y])))
fo.write('</value>\n')
fo.write('</arc>\n')
fo.write('</relation>\n')
fo.write('<vetos>\n')
fo.write('<comment>Effective and potential weto situations.</comment>\n')
try:
vetos = self.vetos
for veto in vetos:
fo.write('<veto>\n')
arc = veto[0]
fo.write('<arc>\n')
fo.write('<initialActionID>')
fo.write(str(arc[0]))
fo.write('</initialActionID>\n')
fo.write('<terminalActionID>')
fo.write(str(arc[1]))
fo.write('</terminalActionID>\n')
fo.write('<concordanceDegree>')
fo.write('%.2f' % (arc[2]))
fo.write('</concordanceDegree>\n')
fo.write('</arc>\n')
situations = veto[1]
fo.write('<vetoSituations>\n')
for v in situations:
fo.write('<vetoSituation>\n')
fo.write('<criterionID>')
fo.write(str(v[0]))
fo.write('</criterionID>\n')
fo.write('<performanceDifference>')
fo.write('%.2f' % (v[1][1]))
fo.write('</performanceDifference>\n')
fo.write('<vetoCharacteristic>')
fo.write('%.2f' % (v[1][0]))
fo.write('</vetoCharacteristic>\n')
fo.write('<comment>')
if arc[2] > Med:
if v[1][0] > 0:
fo.write('effective veto')
else:
fo.write('effective weak veto')
elif arc[2] == Med:
if v[1][0] > 0:
fo.write('effective veto')
else:
fo.write('potential weak veto')
else:
if v[1][0] > 0:
fo.write('potential veto')
else:
fo.write('potential weak veto')
fo.write('</comment>\n')
fo.write('</vetoSituation>\n')
fo.write('</vetoSituations>\n')
fo.write('</veto>\n')
except:
pass
fo.write('</vetos>\n')
fo.write('<choiceRecommendation category="Rubis">\n')
fo.write('<comment>List of good and bad choices following the Rubis methodology.</comment>\n')
cocaActionsList = [x for x in self.actions if isinstance(x,frozenset)]
if cocaActionsList != []:
cocaActionsList.sort()
fo.write('<cocaActions>')
fo.write("<comment>weak COCA digraph actions' declaration </comment>\n")
for x in cocaActionsList:
fo.write('<cocaAction id="'+str(self.actions[x]['name'])+'">\n')
fo.write('<name>')
fo.write('chordless odd circuit')
fo.write('</name>\n')
fo.write('<comment>')
fo.write('Rubis construction')
fo.write('</comment>\n')
fo.write('</cocaAction>\n')
fo.write('</cocaActions>\n')
amplitude = float(Max - Min)/float(100.0)
fo.write('<goodChoices>\n')
for ch in self.goodChoices:
## fo.write('<choiceSet independence="'+str(ch[2])+'" outranking="'+str(ch[3])+'" outranked="'+str(ch[4])+'" determinateness="'+str(-ch[0])+'" >')
if ch[3] > ch[4]:
#independent = float(ch[2])/amplitude
#outranking = float(ch[3])/amplitude
#outranked = float(ch[4])/amplitude
independent = ch[2]
outranking = ch[3]
outranked = ch[4]
#determ = -ch[0]*100.0
determ = -(ch[0]*6) - 3
fo.write('<choiceSet independence="%1d" outranking="%1d" outranked="%1d" determinateness="%1.2f" >\n' % (int(independent),int(outranking),int(outranked),determ))
fo.write('<choiceActions>\n')
for x in ch[5]:
fo.write('<actionID>')
fo.write(str(x))
fo.write('</actionID>\n')
fo.write('</choiceActions>\n')
fo.write('</choiceSet>\n')
fo.write('</goodChoices>\n')
fo.write('<badChoices>\n')
for ch in self.badChoices:
## fo.write('<choiceSet independence="'+str(ch[2])+'" outranking="'+str(ch[3])+'" outranked="'+str(ch[4])+'" determinateness="'+str(-ch[0])+'" >')
if ch[4] >= ch[3]:
#independent = float(ch[2])/float(amplitude)
#outranking = float(ch[3])/float(amplitude)
#outranked = float(ch[4])/float(amplitude)
independent = ch[2]
outranking = ch[3]
outranked = ch[4]
#determ = -ch[0]*100.0
determ = -(ch[0]*6) - 3
fo.write('<choiceSet independence="%1d" outranking="%1d" outranked="%1d" determinateness="%1.2f" >\n' % (int(independent),int(outranking),int(outranked),determ))
fo.write('<choiceActions>\n')
for x in ch[5]:
fo.write('<actionID>')
fo.write(str(x))
fo.write('</actionID>\n')
fo.write('</choiceActions>\n')
fo.write('</choiceSet>\n')
fo.write('</badChoices>\n')
fo.write('</choiceRecommendation>\n')
fo.write('</rubisOutrankingDigraph>\n')
fo.close()
if comment:
print('File: ' + nameExt + ' saved !')
[docs]
class DissimilarityOutrankingDigraph(OutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the OutrankingDigraph class for generating
temporary dissimilarity random graphs
"""
def __init__(self,argPerfTab=None,Debug=False):
if argPerfTab is None:
print('Performance tableau required !')
perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
import sys,copy
## if filePerfTab is None:
## t = RandomPerformanceTableau()
## filePerfTab = 'randomPerf'
## t.save(filePerfTab)
## perfTab = PerformanceTableau(filePerfTab)
self.name = 'rel_'+perfTab.name
self.actions = copy.copy(perfTab.actions)
self.criteria = copy.copy(perfTab.criteria)
self.evaluation = copy.copy(perfTab.evaluation)
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
weightPreorder = perfTab.computeWeightPreorder()
self.NA = copy.copy(perfTab.NA)
self.relation = self._constructRelation(Debug=Debug)
# for g in perfTab.actions:
# actions.append(g)
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self,Debug=False):
"""
Renders the valued dissimilarity relation between criteria.
"""
actions = self.actions
if Debug:
print(actions)
criteria = self.criteria
if Debug:
print(criteria)
evaluation = self.evaluation
if Debug:
print(evaluation)
NA = self.NA
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
relation = {}
for ga in criteria:
criteriona = criteria[ga]
relation[ga] = {}
for gb in criteria:
criterionb = criteria[gb]
if ga != gb:
na = len(actions)
counter = Decimal('0')
for x in actions:
if evaluation[ga][x] != NA and evaluation[gb][x] != NA:
d = abs(evaluation[ga][x] - evaluation[gb][x])
hgax = criteriona['thresholds']['ind'][0]
hgay = criteriona['thresholds']['ind'][1]
hga = hgax + hgay * evaluation[ga][x]
hgbx = criterionb['thresholds']['ind'][0]
hgby = criterionb['thresholds']['ind'][1]
hgb = hgbx + hgby * evaluation[gb][x]
qgax = criteriona['thresholds']['pref'][0]
qgay = criteriona['thresholds']['pref'][1]
qga = qgax + qgay * evaluation[ga][x]
qgbx = criterionb['thresholds']['pref'][0]
qgby = criterionb['thresholds']['pref'][1]
qgb = qgbx + qgby * evaluation[gb][x]
h = max(hga,hgb)
q = max(qga,qgb)
counter = counter + self._localDissimilarity(d, h, q)
else:
counter = counter + Decimal('0.5')
relation[ga][gb] = Decimal(str((round((counter/na*(Max-Min)))))) + Min
else:
relation[ga][ga] = Min
return relation
def _localDissimilarity(self, d, h, q):
"""
Renders local dissimilarity between two criterial evaluations
"""
dn = q - h
if d <= h:
return Decimal('0.0')
elif dn == Decimal('0'):
return Decimal('1.0')
elif d <= q:
return (d-h)/(q-h)
else:
return Decimal('1.0')
[docs]
def showAll(self):
"""
specialize the general showAll method for the dissimilarity case
"""
print('*----- show detail -------------*')
print('Digraph :', self.name)
print('Criteria :', self.actions)
print('Valuation domain :', self.valuationdomain)
print('*---- evaluated actions per criteria -----*')
print(self.criteria)
print('*---- evaluation tableau -----*')
for g in self.actions:
print(g, ': ', self.evaluation[g])
print('*---- dissimilarity between criteria -----*')
print(self.relation)
self.showComponents()
print('*---- neighbourhoods -----*')
print('Gamma :', self.gamma)
print('Not Gamma :', self.notGamma)
[docs]
class MultiCriteriaDissimilarityDigraph(OutrankingDigraph):
"""
Parameters:
performanceTableau (fileName of valid py code)
Specialization of the OutrankingDigraph class for generating
temporary multiple criteria based dissimilarity graphs.
"""
def __init__(self,perfTab=None,filePerfTab=None):
import sys,copy
if perfTab is None:
if filePerfTab is None:
perftab = RandomPerformanceTableau()
filePerfTab = 'randomPerf'
t.save(filePerfTab)
else:
perfTab = PerformanceTableau(filePerfTab)
self.name = 'rel_'+perfTab.name
self.actions = copy.copy(perfTab.actions)
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
weightPreorder = perfTab.computeWeightPreorder()
self.criteria = copy.copy(perfTab.criteria)
self.evaluation = copy.copy(perfTab.evaluation)
self.NA = copy.copy(perfTab.NA)
self.relation = self._constructRelation()
self.order = len(self.actions)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _constructRelation(self):
"""
Renders the valued dissimilarity relation between criteria.
"""
actions = self.actions
criteria = self.criteria
evaluation = self.evaluation
NA = self.NA
maxWeight = Decimal('0.0')
for g in criteria:
maxWeight += abs(criteria[g]['weight'])
Min = self.valuationdomain['min']
Max = self.valuationdomain['max']
Med = self.valuationdomain['med']
relation = {}
for a in actions:
relation[a] = {}
for b in actions:
if a != b:
nc = len(criteria)
counter = Decimal('0.0')
for g in criteria:
criterion = criteria[g]
if evaluation[g][a] != NA and evaluation[g][b] != NA:
if criterion['weight'] > Decimal('0'):
d = abs(evaluation[g][a] - evaluation[g][b])
else:
d = abs(evaluation[g][b] - evaluation[g][a])
try:
hx = criterion['thresholds']['ind'][0]
hy = criterion['thresholds']['ind'][1]
h = hx + hy * evaluation[g][a]
except:
h = None
try:
wpx = criterion['thresholds']['weakPref'][0]
wpy = criterion['thresholds']['weakPref'][1]
wp = wpx + wpy * evaluation[g][a]
except:
wp = None
try:
px = criterion['thresholds']['pref'][0]
py = criterion['thresholds']['pref'][1]
p = px + py * evaluation[g][a]
except:
p = None
index = self._localDissimilarity(d,h,wp,p)
## print d,h,wp,p
## print counter, index, criteria[g]['weight']
counter += index * criterion['weight']
else:
counter += Decimal('0.0')
## relation[a][b] = counter/maxWeight*(Max-Min) + Min
relation[a][b] = counter/maxWeight
else:
relation[a][b] = Med
return relation
def _localDissimilarity(self, d, h, wp, p):
"""
Renders local dissimilarity between two criterial evaluations
"""
if h is not None:
if d <= h:
return Decimal('-1.0')
elif p is not None:
if d < p:
return Decimal('0.0')
else:
return Decimal('1.0')
elif d > h:
return Decimal('1.0')
else:
print('Error: should never come here !!!')
elif wp is not None:
if d < wp:
return Decimal('-1.0')
elif p is not None:
if d < p:
return Decimal('1.0')
else:
return Decimal('0.0')
else:
return Decimal('1.0')
elif p is not None:
if d < p:
return Decimal('-1.0')
else:
return Decimal('1.0')
else:
if d > Decimal('0.0'):
return Decimal('1.0')
else:
return Decimal('-1.0')
[docs]
class ConfidentBipolarOutrankingDigraph(BipolarOutrankingDigraph):
"""
Confident bipolar outranking digraph based on multiple criteria of
uncertain significance.
The digraph's bipolar valuation represents the bipolar outranking relation
based on a sufficient likelihood of the at least as good as relation
that is outranking without veto and counterveto.
By default, each criterion i' significance weight is supposed to
be a triangular random variable of mode w_i in the range 0 to 2*w_i.
*Parameters*:
* argPerfTab: PerformanceTableau instance or the name (without extension) of a stored one. If None, a random instance is generated.
* distribution: {triangular|uniform|beta}, probability distribution used for generating random weights
* betaParameter: a = b (default = 2)
* confidence: required likelihood (in %) of the outranking relation
* other standard parameters from the BipolarOutrankingDigraph class (see documentation).
"""
def __init__(self,argPerfTab=None,
distribution = 'triangular',
betaParameter = 2,
confidence = 90.0,
coalition=None,
hasNoVeto=False,
hasBipolarVeto=True,
Normalized=True,
#Threading=False,
#nbrOfCPUs=1,
Debug=False,):
# getting module ressources and setting the random seed
from copy import copy, deepcopy
# getting performance tableau
if argPerfTab is None:
perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = copy(argPerfTab)
# initializing the bipolar outranking digraph
bodg = BipolarOutrankingDigraph(argPerfTab=perfTab,coalition=coalition,\
hasNoVeto=hasNoVeto,\
hasBipolarVeto=hasBipolarVeto,\
Normalized=Normalized,\
#Threading=Threading,\
#nbrCores=nbrOfCPUs,\
)
self.name = bodg.name + '_CLT'
self.bipolarConfidenceLevel = (confidence/100.0)*2.0 -1.0
self.distribution = distribution
self.betaParameter = betaParameter
self.actions = copy(bodg.actions)
self.order = len(self.actions)
self.valuationdomain = copy(bodg.valuationdomain)
self.criteria = copy(bodg.criteria)
self.evaluation = copy(bodg.evaluation)
self.NA = copy(bodg.NA)
#if not Threading:
self.concordanceRelation = copy(bodg.concordanceRelation)
self.vetos = copy(bodg.vetos)
self.negativeVetos = copy(bodg.negativeVetos)
self.largePerformanceDifferencesCount =\
copy(bodg.largePerformanceDifferencesCount)
self.likelihoods = self.computeCLTLikelihoods(distribution=distribution,
betaParameter=betaParameter,
#Threading=Threading,
Debug=Debug)
self.relation = self._computeConfidentRelation(
bodg.relation,
#likelihoodLevel=confidence,
Debug=Debug)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
def _computeConfidentRelation(self,
outrankingRelation,
likelihoodLevel=None,
Debug=False):
"""
Renders the relation cut at likelihood level.
"""
Med = self.valuationdomain['med']
Max = self.valuationdomain['max']
Min = self.valuationdomain['min']
if likelihoodLevel is None:
likelihoodLevel = self.bipolarConfidenceLevel
if Debug:
print('Likelihood level = %.2f%%' % ( (likelihoodLevel + 1.0)/2.0*100.0) )
confidenceCutLevel = Med
confidentRelation = {}
actionsList = [x for x in self.actions]
for x in actionsList:
confidentRelation[x] = {}
for y in actionsList:
if abs(self.likelihoods[x][y]) >= likelihoodLevel:
confidentRelation[x][y] = outrankingRelation[x][y]
else:
confidentRelation[x][y] = Med
level = abs(outrankingRelation[x][y])
if level < Max and level > confidenceCutLevel:
confidenceCutLevel = level
if Debug:
print(x,y,outrankingRelation[x][y],self.likelihoods[x][y])
self.confidenceCutLevel = confidenceCutLevel
return confidentRelation
def _recodeConcordanceValuation(self,oldRelation,sumWeights,Debug=False):
"""
Recodes the characteristic valuation according
to the parameters given.
"""
if Debug:
print(oldRelation,sumWeights)
from copy import copy as deepcopy
## oldMax = Decimal('1')
## oldMin = Decimal('-1')
## oldMed = Decimal('0')
oldMax = self.valuationdomain['max']
oldMin = self.valuationdomain['min']
oldMed = self.valuationdomain['med']
oldAmplitude = oldMax - oldMin
if Debug:
print('old: ',oldMin, oldMed, oldMax, oldAmplitude)
newMin = -sumWeights
newMax = sumWeights
newMed = Decimal('%.3f' % ((newMax + newMin)/Decimal('2.0')))
newAmplitude = newMax - newMin
if Debug:
print('new: ', newMin, newMed, newMax, newAmplitude)
actions = [x for x in self.actions]
newRelation = {}
for x in actions:
newRelation[x] = {}
for y in actions:
if oldRelation[x][y] == oldMax:
newRelation[x][y] = newMax
elif oldRelation[x][y] == oldMin:
newRelation[x][y] = newMin
elif oldRelation[x][y] == oldMed:
newRelation[x][y] = newMed
else:
newRelation[x][y] = newMin +\
((oldRelation[x][y] - oldMin)/oldAmplitude)*newAmplitude
if Debug:
print(x,y,oldRelation[x][y],newRelation[x][y])
return newRelation
def _myGaussCDF(self,mean,sigma,x,Bipolar=True):
"""
Bipolar error function of z = (x-mu)/sigma) divided by sqrt(2).
If Bipolar = False,
renders the Gauss cdf(z) = [erf( z ) + 1] / 2
sqrt(2) = 1.4142135623731
"""
from math import sqrt,erf
try:
z = (x - mean) / (sigma * 1.4142135623731)
except ZeroDivisionError:
z = 0.0
if Bipolar:
return erf(z)
else:
return 0.5 + 0.5*erf(z)
[docs]
def computeCLTLikelihoods(self,distribution="triangular",
betaParameter=None,
#Threading=False,
#nbrOfCPUs=1,
Debug=False):
"""
Renders the pairwise CLT likelihood of the at least as good as relation
neglecting all considerable large performance differences polarisations.
"""
from copy import copy as deepcopy
from decimal import Decimal
from math import sqrt
from random import gauss
actionsList = [x for x in self.actions]
sumWeights = Decimal('0')
criteriaList = [x for x in self.criteria]
m = len(criteriaList)
weightSquares = {}
for g in criteriaList:
gWeight = abs(self.criteria[g]['weight'])
## if Debug:
## print(g,gWeight)
weightSquares[g] = gWeight*gWeight
sumWeights += gWeight
## if Debug:
## print(sumWeights)
## print(weightSquares)
## if Threading:
## g = BipolarOutrankingDigraph(self,hasNoVeto=True,
## Threading=Threading,nbrCores=nbrOfCPUs)
## concordanceRelation = g.relation
## else:
concordanceRelation = self._recodeConcordanceValuation(\
self.concordanceRelation,sumWeights,Debug=Debug)
ccf = {}
if distribution == 'uniform':
varFactor = Decimal('1')/Decimal('3')
elif distribution == 'triangular':
varFactor = Decimal('1')/Decimal('6')
elif distribution == 'beta':
if betaParameter is not None:
a = Decimal(str(betaParameter))
else:
a = self.betaParameter
varFactor = Decimal('1')/(Decimal('2')*a + Decimal('1'))
## elif distribution == 'beta(4,4)':
## varFactor = Decimal('1')/Decimal('9')
for x in actionsList:
ccf[x] = {}
for y in actionsList:
ccf[x][y] = {'std': Decimal('0.0')}
for c in criteriaList:
ccf[x][y][c] = self.criterionCharacteristicFunction(c,x,y)
ccf[x][y]['std'] += abs(ccf[x][y][c])*weightSquares[c]
## if Debug:
## print(c,x,y,ccf[x][y][c])
ccf[x][y]['std'] = sqrt(varFactor*ccf[x][y]['std'])
## if Debug:
## print(x,y,ccf[x][y]['std'])
lh = {}
for x in actionsList:
lh[x] = {}
for y in actionsList:
mean = float(concordanceRelation[x][y])
std = float(ccf[x][y]['std'])
lh[x][y] = -self._myGaussCDF(mean,std,0.0)
if Debug:
print(x,y,lh[x][y])
return lh
[docs]
def showRelationTable(self,IntegerValues=False,
actionsSubset= None,
Sorted=True,
LikelihoodDenotation=True,
hasLatexFormat=False,
hasIntegerValuation=False,
relation=None,
Debug=False):
"""
prints the relation valuation in actions X actions table format.
"""
if LikelihoodDenotation:
try:
likelihoods = self.likelihoods
except:
LikelihoodDenotation = False
if Debug:
print(LikelihoodDenotation)
if actionsSubset is None:
actions = self.actions
else:
actions = actionsSubset
if relation is None:
relation = self.relation
print('* ---- Outranking Relation Table -----')
if LikelihoodDenotation:
print('r/(lh) | ', end=' ')
else:
print(' r() | ', end=' ')
#actions = [x for x in actions]
actionsList = []
for x in actions:
if isinstance(x,frozenset):
try:
actionsList += [(actions[x]['shortName'],x)]
except:
actionsList += [(actions[x]['name'],x)]
else:
actionsList += [(x,x)]
if Sorted:
actionsList.sort()
try:
hasIntegerValuation = self.valuationdomain['hasIntegerValuation']
except KeyError:
hasIntegerValuation = IntegerValues
for x in actionsList:
print("'"+x[0]+"'\t", end=' ')
print('\n-------|------------------------------------------------------------')
for x in actionsList:
if hasLatexFormat:
print("$"+x[0]+"$ & ", end=' ')
else:
print(" '"+x[0]+"' |", end=' ')
for y in actionsList:
if hasIntegerValuation:
if hasLatexFormat:
print('$%+d$ &' % (relation[x[1]][y[1]]), end=' ')
else:
print('%+d' % (relation[x[1]][y[1]]), end=' ')
else:
if hasLatexFormat:
print('$%+.2f$ & ' % (relation[x[1]][y[1]]), end=' ')
else:
print(' %+.2f ' % (relation[x[1]][y[1]]), end=' ')
if hasLatexFormat:
print(' \\cr')
else:
print()
if LikelihoodDenotation:
headString = "' "+x[0]+"' "
formatStr = ' ' * len(headString)
print(formatStr+'|', end=' ')
for y in actionsList:
if x != y:
print('(%+.2f)' % (likelihoods[x[1]][y[1]]), end=' ')
else:
print(' ( - ) ', end=' ')
print()
print('Valuation domain : [%+.3f; %+.3f] ' % (self.valuationdomain['min'],
self.valuationdomain['max']))
if self.distribution == 'beta':
print('Uncertainty model : %s(a=%.1f,b=%.1f) ' % (self.distribution,
self.betaParameter,
self.betaParameter)
)
else:
print('Uncertainty model : %s(a=%s,b=%s) ' % (self.distribution,'0','2w') )
print('Likelihood domain : [-1.0;+1.0] ')
print('Confidence level : %.2f (%.1f%%) ' % (self.bipolarConfidenceLevel,
(self.bipolarConfidenceLevel+1.0)/2.0*100.0))
print('Confident majority : %.2f (%.1f%%) ' % (self.confidenceCutLevel,\
(self.confidenceCutLevel+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0')))
deter = self.computeDeterminateness(InPercents=False)
print('Determinateness : %.2f (%.1f%%)' % (deter,\
(deter+Decimal('1.0'))/Decimal('2.0')*Decimal('100.0')))
print('\n')
#--------------------
[docs]
class StochasticBipolarOutrankingDigraph(BipolarOutrankingDigraph):
"""
Stochastic bipolar outranking digraph based on multiple criteria of uncertain significance.
The digraph's bipolar valuation represents the median of sampled outranking relations with a
sufficient likelihood (default = 90%) to remain positive, repectively negative,
over the possible criteria significance ranges.
Each criterion i' significance weight is supposed to
be a triangular random variables of mode w_i in the range 0 to 2*w_i.
*Parameters*:
* argPerfTab: PerformanceTableau instance or the name of a stored one.
If None, a random instance is generated.
* sampleSize: number of random weight vectors used for Monte Carlo simulation.
* distribution: {triangular|extTriangular|uniform|beta(2,2)|beta(4,4)}, probability distribution used for generating random weights
* spread: weight range = weight mode +- (weight mode * spread)
* likelihood: 1.0 - frequency of valuations of opposite sign compared to the median valuation.
* other standard parameters from the BipolarOutrankingDigraph class (see documentation).
"""
def __init__(self,argPerfTab=None,
sampleSize = 50,
samplingSeed = None,
distribution = 'triangular',
spread = 1.0,
likelihood = 0.9,
coalition=None,
hasNoVeto=False,
hasBipolarVeto=True,
Normalized=False,
Debug=False,
SeeSampleCounter=False):
# getting module ressources and setting the random seed
from copy import copy, deepcopy
if distribution == 'extTriangular':
from randomNumbers import ExtendedTriangularRandomVariable
else:
from random import triangular, uniform, betavariate
if samplingSeed is not None:
from random import seed
seed = samplingSeed
# getting performance tableau
if argPerfTab is None:
perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = deepcopy(argPerfTab)
# initializing the bipolar outranking digraph
bodg = BipolarOutrankingDigraph(argPerfTab=perfTab,coalition=coalition,\
hasNoVeto = hasNoVeto,\
hasBipolarVeto = hasBipolarVeto,\
Normalized=Normalized)
self.name = bodg.name + '_MC'
self.sampleSize = sampleSize
self.likelihood = likelihood
self.actions = copy(bodg.actions)
self.order = len(self.actions)
self.valuationdomain = copy(bodg.valuationdomain)
self.criteria = copy(bodg.criteria)
self.evaluation = copy(bodg.evaluation)
self.NA = copy(bodg.NA)
self.relation = copy(bodg.relation)
# normalize valuation to percentages
self.recodeValuation(-100.0,100.0)
Med = self.valuationdomain['med']
# bin breaks per percent unit
breaks = [(x,i) for i,x in enumerate(range(-100,101))]
# quantiles for cdf calls
quantilesId = dict(breaks)
if Debug:
print(quantilesId)
# initialize frequency and observation dictionaries
frequency = {}
valuationObservations = {}
for x in self.actions:
valuationObservations[x] = {}
frequency[x] = {}
for y in self.actions:
valuationObservations[x][y] = []
frequency[x][y] = [0 for i in range(len(breaks))]
# initialize the weight modes
weights = dict([(g,self.criteria[g]['weight']) for g in self.criteria])
spread = Decimal(spread)
if Debug:
print(weights)
for sample in range(sampleSize):
if SeeSampleCounter:
print(sample)
if Debug:
print('===>>> sample %d ' % (sample+1) )
for g in self.criteria:
lowerWeightLimit = float(weights[g]- (weights[g]*spread))
upperWeightLimit = float(weights[g] + (weights[g]*spread))
weightMode = float(weights[g])
weightRange = upperWeightLimit - lowerWeightLimit
if distribution == 'triangular':
rw = Decimal( '%.2f' % triangular(lowerWeightLimit,upperWeightLimit,weightMode) )
elif distribution == 'uniform':
rw = Decimal( '%.2f' % uniform(lowerWeightLimit,upperWeightLimit) )
elif distribution == 'beta(2,2)':
rw = Decimal( '%.2f' % (lowerWeightLimit+(betavariate(2,2)*weightRange)) )
elif distribution == 'beta(4,4)':
rw = Decimal( '%.2f' % (lowerWeightLimit+(betavariate(4,4)*weightRange)) )
elif distribution == 'extTriangular':
extTrRdv = ExtendedTriangularRandomVariable(lowLimit=float(weights[g])/2.0,
highLimit=float(weights[g])*2.0,
mode=weightMode,
seed=samplingSeed)
rw = Decimal( '%.2f' % ( extTrRdv.random() ) )
else:
print('Error: wrong distribution %s. Available laws: triangular (default), uniform, beta(2,2), beta(12,12)' % distribution)
perfTab.criteria[g]['weight'] = rw
## if Debug:
## print(self.criteria[g]['weight'],rw)
srelation = self._constructRelation(perfTab.criteria,\
perfTab.evaluation,\
hasNoVeto = hasNoVeto,\
hasBipolarVeto = hasBipolarVeto,\
)
for x in self.actions:
for y in self.actions:
valuationObservations[x][y].append(srelation[x][y])
for i in range(len(breaks)):
if srelation[x][y] <= breaks[i][0]:
frequency[x][y][i] += 1
self.relationStatistics = {}
for x in self.actions:
self.relationStatistics[x] = {}
for y in self.actions:
self.relationStatistics[x][y] = {}
valuationObservations[x][y].sort()
## if Debug and x == 'a04' and y == 'a05':
## print(x,y)
## print(valuationObservations[x][y])
## q = sampleSize//2
## if (sampleSize % 2) == 0:
## median = (valuationObservations[x][y][q] + valuationObservations[x][y][q+1])/Decimal('2')
## else:
## median = (valuationObservations[x][y][q])
## print(median,self._computeQuantile(0.5,valuationObservations[x][y]))
self.relationStatistics[x][y]['median'] = self._computeQuantile(0.5,valuationObservations[x][y])
if Debug and x == 'a04' and y == 'a05':
print(frequency[x][y])
print(quantilesId)
print(frequency[x][y][quantilesId[0]])
if self.relation[x][y] > Med:
self.relationStatistics[x][y]['likelihood'] = 1.0 - float(frequency[x][y][quantilesId[0]])/float(sampleSize)
elif self.relation[x][y] <= Med:
self.relationStatistics[x][y]['likelihood'] = float(frequency[x][y][quantilesId[0]])/float(sampleSize)
self._computeRelationStatistics(x,y,valuationObservations[x][y])
if Debug:
print(self.relationStatistics[x][y])
requiredLikelihood = likelihood
if self.relationStatistics[x][y]['likelihood'] < requiredLikelihood:
self.relation[x][y] = self.valuationdomain['med']
else:
self.relation[x][y] = Decimal('%.3f' % self.relationStatistics[x][y]['median'])
#self.relationStatistics = deepcopy(relationStatistics)
if Normalized:
self.recodeValuation(-1,1)
if Debug:
self.valuationObservations = deepcopy(valuationObservations)
# sampled valuations r(x,y) are observed in standard bipolar percentages, i.e. [-100,100]
# the breaks are left closed integers and go from -100 to 100
# the quantileId disctionary gives the
self.frequency = frequency
self.quantilesId = quantilesId
## if Debug:
## print(self.valuationObservations)
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
[docs]
def computeCDF(self,x,y,rValue):
"""
computes by interpolation the likelihood of a given rValue with respect to the sampled r(x,y) valuations.
*Parameters*:
* action key x
* action key y
* r(x,y)
"""
from math import floor
fval = (-100.0 + (float(rValue) - float(self.valuationdomain['min']))\
/(float(self.valuationdomain['max'] - self.valuationdomain['min']))*200.0)
val = int(floor(fval))
#print(fval,val)
if val < 100:
#print(self.frequency[x][y][self.quantilesId[val]],self.frequency[x][y][self.quantilesId[val+1]])
Fval = float(self.frequency[x][y][self.quantilesId[val]])/float(self.sampleSize)
Fval1 = float(self.frequency[x][y][self.quantilesId[val+1]])/float(self.sampleSize)
prob = Fval + ((fval - val)*(Fval1 - Fval))
else:
Fval = float(self.frequency[x][y][self.quantilesId[val]])/float(self.sampleSize)
prob = Fval
return prob
def _computeQuantile(self,p,observations):
"""
computes by interpolation the quantile of probability p of a list of sorted observations.
"""
from math import floor,ceil
n = len(observations)
if p <= 0.0:
quantile = observations[0]
elif p >= 1.0:
quantile = observations[-1]
else:
q = (n)*p
flq = floor(q)
clq = ceil(q)
quantile = float(observations[int(flq)])\
+ (q-flq) * float(observations[int(clq)]- observations[int(flq)])
return quantile
def _computeRelationStatistics(self,x,y,observations):
"""
Computes the pairwise relation statistics from the sampled observations.
Results are stored in the self.relationStatistics[x][y] dictionary
with keys = 'sd','Q0,'Q1,'Q3','Q4'.
"""
from math import sqrt
n = len(observations)
fn = float(n)
mean = 0.0
meansq2 = 0.0
for v in observations:
fv = float(v)
mean += fv
meansq2 += (fv * fv)
mean /= fn
self.relationStatistics[x][y]['mean'] = mean
variance = (meansq2 / fn) - (mean*mean)
self.relationStatistics[x][y]['sd'] = sqrt(variance)
self.relationStatistics[x][y]['Q0'] = float(min(observations))
self.relationStatistics[x][y]['Q1'] = self._computeQuantile(0.25,observations)
self.relationStatistics[x][y]['Q3'] = self._computeQuantile(0.75,observations)
self.relationStatistics[x][y]['Q4'] = float(max(observations))
[docs]
def showRelationTable(self,IntegerValues=False,
actionsSubset= None,
hasLPDDenotation=False,
hasLatexFormat=False,
hasIntegerValuation=False,
relation=None):
"""
specialising BipolarOutrankingDigraph.showRelationTable() for stochstic instances.
"""
print('Stochastic outranking digraph %s' % self.name)
print('Sampling size: %d' % self.sampleSize)
print('Likelihood of Condorcet digraph: %.2f' % self.likelihood)
BipolarOutrankingDigraph.showRelationTable(self,IntegerValues=IntegerValues,
actionsSubset= actionsSubset,
hasLPDDenotation=hasLPDDenotation,
hasLatexFormat=hasLatexFormat,
hasIntegerValuation=hasIntegerValuation,
relation=relation)
[docs]
def computeCLTLikelihoods(self,distribution="triangular",Debug=False):
"""
Renders the pairwise CLT likelihood of the at least as good as relation
neglecting all considerable large performance differences polarisations.
"""
from decimal import Decimal
from math import sqrt
from scipy import stats
from scipy.stats import norm
actionsList = [x for x in self.actions]
sumWeights = Decimal('0')
criteriaList = [x for x in self.criteria]
m = len(criteriaList)
weightSquares = {}
for g in criteriaList:
gWeight = abs(self.criteria[g]['weight'])
## if Debug:
## print(g,gWeight)
weightSquares[g] = gWeight*gWeight
sumWeights += gWeight
## if Debug:
## print(sumWeights)
## print(weightSquares)
g = BipolarOutrankingDigraph(self,hasNoVeto=True)
g.recodeValuation(-sumWeights,sumWeights)
if Debug:
g.showRelationTable()
ccf = {}
if distribution == 'uniform':
varFactor = Decimal('1')/Decimal('3')
elif distribution == 'triangular':
varFactor = Decimal('1')/Decimal('6')
elif distribution == 'beta(2,2)':
varFactor = Decimal('1')/Decimal('5')
elif distribution == 'beta(4,4)':
varFactor = Decimal('1')/Decimal('9')
for x in actionsList:
ccf[x] = {}
for y in actionsList:
ccf[x][y] = {'std': Decimal('0.0')}
for c in criteriaList:
ccf[x][y][c] = g.criterionCharacteristicFunction(c,x,y)
ccf[x][y]['std'] += abs(ccf[x][y][c])*weightSquares[c]
## if Debug:
## print(c,x,y,ccf[x][y][c])
ccf[x][y]['std'] = sqrt(varFactor*ccf[x][y]['std'])
## if Debug:
## print(x,y,ccf[x][y]['std'])
lh = {}
for x in actionsList:
lh[x] = {}
for y in actionsList:
n = norm(float(g.relation[x][y]),float(ccf[x][y]['std']))
lh[x][y] = {}
if g.relation[x][y] > g.valuationdomain['med']:
lh[x][y] = 1.0 - n.cdf(0.0)
else:
lh[x][y] = n.cdf(0.0)
if Debug:
print(x,y,lh[x][y])
return lh
[docs]
def showRelationStatistics(self,argument='likelihoods',
actionsSubset= None,
hasLatexFormat=False,
Bipolar=False):
"""
prints the relation statistics in actions X actions table format.
"""
from math import copysign
## if hasLPDDenotation:
## try:
## largePerformanceDifferencesCount = self.largePerformanceDifferencesCount
## gnv = BipolarOutrankingDigraph(self.performanceTableau,hasNoVeto=True)
## recodeValuation(self.valuationdomain['min'],self.valuationdomain['max'])
## except:
hasLPDDenotation = False
if actionsSubset is None:
actions = self.actions
else:
actions = actionsSubset
relation = {}
for x in actions:
relation[x] = {}
for y in actions:
if argument == 'likelihoods':
if Bipolar:
relation[x][y] =\
copysign( ((2.0 * self.relationStatistics[x][y]['likelihood']) - 1.0),
self.relation[x][y])
else:
relation[x][y] = self.relationStatistics[x][y]['likelihood']
elif argument == 'medians':
relation[x][y] = self.relationStatistics[x][y]['median']
elif argument == 'means':
relation[x][y] = self.relationStatistics[x][y]['mean']
print('* ---- Relation statistics -----\n', end=' ')
if argument == 'likelihoods':
print(' 0 p-val | ', end=' ')
elif argument == 'means':
print(' mean | ', end=' ')
elif argument == 'medians':
print(' med | ', end=' ')
#actions = [x for x in actions]
actionsList = []
for x in actions:
if isinstance(x,frozenset):
try:
actionsList += [(actions[x]['shortName'],x)]
except:
actionsList += [(actions[x]['name'],x)]
else:
actionsList += [(x,x)]
actionsList.sort()
## try:
## hasIntegerValuation = self.valuationdomain['hasIntegerValuation']
## except KeyError
hasIntegerValuation = False
for x in actionsList:
print("'"+x[0]+"', ", end=' ')
print('\n-----|------------------------------------------------------------')
for x in actionsList:
if hasLatexFormat:
print("$"+x[0]+"$ & ", end=' ')
else:
print("'"+x[0]+"' | ", end=' ')
for y in actionsList:
if hasIntegerValuation:
if hasLPDDenotation:
print('%+d ' % (relation[x[1]][y[1]]), end=' ')
elif hasLatexFormat:
print('$%+d$ &' % (relation[x[1]][y[1]]), end=' ')
else:
print('%+d ' % (relation[x[1]][y[1]]), end=' ')
else:
if hasLPDDenotation:
print('%+2.2f ' % (relation[x[1]][y[1]]), end=' ')
elif hasLatexFormat:
print('$%+2.2f$ & ' % (relation[x[1]][y[1]]), end=' ')
else:
print('%+2.2f ' % (relation[x[1]][y[1]]), end=' ')
if hasLatexFormat:
print(' \\cr')
else:
print()
if hasLPDDenotation:
print("'"+x[0]+"' | ", end=' ')
for y in actionsList:
print('(%+d,%+d)' % (largePerformanceDifferencesCount[x[1]][y[1]]['positive'],\
largePerformanceDifferencesCount[x[1]][y[1]]['negative']), end=' ')
print()
print('\n')
[docs]
class CoalitionsOutrankingsFusionDigraph(BipolarOutrankingDigraph):
"""
With a list of criteria coalitions, a fusion digraph is constructed form the fusion of the corresponding marginal coalitions restricted bipolar outranking digraphs.
When *coalitionsList* is None, an 'o-average' fusion of the decision objectives restricted outranking digraphs is produced (see *UnOpposedBipolarOutrankingDigraph* class).
"""
def __init__(self,argPerfTab,
coalitionsList=None,
actionsSubset=None,
CopyPerfTab=True,
Comments=False):
from copy import deepcopy
from time import time
# set initial time stamp
tt = time()
# ---- performance tableau data input
if argPerfTab is None:
print('Performance tableau required !')
#perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
# transfering the performance tableau data to self
self.name = 'objectivesFusion_' + perfTab.name
# actions
if actionsSubset is None:
if isinstance(perfTab.actions,list):
actions = {}
for x in perfTab.actions:
actions[x] = {'name': str(x)}
self.actions = actions
else:
if CopyPerfTab:
self.actions = deepcopy(perfTab.actions)
else:
self.actions = perfTab.actions
else:
actions = {}
for x in actionsSubset:
actions[x] = {'name': str(x)}
self.actions = actions
# valuation domain
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
try:
self.valuationdomain['precision'] = perfTab.valuationPrecision
except:
self.valuationdomain['precision'] = Decimal('0')
# objectives and criteria
try:
if CopyPerfTab:
self.objectives = deepcopy(perfTab.objectives)
self.criteria = deepcopy(perfTab.criteria)
else:
self.objectives = perfTab.objectives
self.criteria = perfTab.criteria
except:
pass
# install method Data and parameters
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'bipolar'
variant = 'standard'
methodData['parameter'] = {'valuationType': valuationType, 'variant': variant}
try:
vetoType = perfTab.parameter['vetoType']
methodData['parameter']['vetoType'] = vetoType
except:
vetoType = 'normal'
methodData['parameter']['vetoType'] = vetoType
if vetoType == 'bipolar':
hasBipolarVeto = True
self.methodData = methodData
# insert performance Data
if CopyPerfTab:
self.evaluation = deepcopy(perfTab.evaluation)
else:
self.evaluation = perfTab.evaluation
try:
if CopyPerfTab:
self.description = deepcopy(perfTab.description)
except:
pass
# init general digraph Data
self.order = len(self.actions)
# finished data input time stamp
self.runTimes = {'dataInput': time()-tt }
# ---------- construct outranking relation
# initial time stamp
tcp = time()
actions = self.actions
if coalitionsList is None:
try:
objectives = self.objectives
coalitionsList = [objectives[obj]['criteria'] for obj in objectives]
#print(coalitionsList)
except:
print('!!! Error: No coalitions given and performance tableau has no objectives')
return
marginalRelations = {}
marginalDigraphs = []
for coalition in coalitionsList:
mg = BipolarOutrankingDigraph(perfTab,criteriaSubset=coalition)
marginalRelations[tuple(coalition)] = deepcopy(mg.relation)
marginalDigraphs.append(mg)
weights = []
for coalition in coalitionsList:
sumMarginalWeights = Decimal('0')
for g in coalition:
sumMarginalWeights += self.criteria[g]['weight']
weights.append(sumMarginalWeights)
#weights = [objectives[obj]['weight'] for obj in objectives]
fg = FusionLDigraph(marginalDigraphs,'o-average',weights)
self.relation = deepcopy(fg.relation)
self.marginalRelationsRelations = marginalRelations
# finished relation computing time stamp
self.runTimes['computeRelation'] = time() - tcp
# ---- computing the gamma sets
tg = time()
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
self.runTimes['gammaSets'] = time() - tg
# total constructor time
self.runTimes['totalTime'] = time() - tt
if Comments:
print(self)
[docs]
class UnOpposedBipolarOutrankingDigraph(CoalitionsOutrankingsFusionDigraph):
"""
When operating an *o-average* fusion of the mariginal outranking digraphs restricted to the coalition of criteria supporting each decision objective, we obtain **unopposed** outranking situtations, namely *validated* by one or more decision objectives without being *invalidated* by any other decision objective.
These positive, as well as negative outranking characteristics, appear hence stable with respect to the importance of the decision objectives when proportional criteria significances are given.
Furthermore, polarising the outranking digraph with considerable performance differences is here restricted to each decision objective, which makes it easier to decide on veto discrimination thresholds.
"""
def __init__(self,argPerfTab,actionsSubset=None,
CopyPerfTab=True,Comments=False):
from copy import deepcopy
for att in argPerfTab.__dict__:
self.__dict__[att] = argPerfTab.__dict__[att]
try:
coalitions = [self.objectives[obj]['criteria'] for obj in self.objectives]
if Comments:
print('coalitions:',coalitions)
except:
print('!! Error: the given performance tableau does not contain objectives.')
return
uoo = CoalitionsOutrankingsFusionDigraph(argPerfTab,\
CopyPerfTab=False,\
coalitionsList=coalitions,\
actionsSubset=actionsSubset,\
Comments=Comments)
for att in uoo.__dict__:
self.__dict__[att] = uoo.__dict__[att]
self.name += '_unopposed_outrankings'
if Comments:
print(self)
[docs]
def computeOppositeness(self,InPercents=False):
"""
Computes the degree in *[0.0, 1.0]* of oppositeness --**preferential disagreement**-- of the multiple decision objectives.
Renders a dictionary with three entries:
- *standardSize* : size of the corresponding
standard bipolar-valued outranking digraph;
- *unopposedSize* : size of the the corresponding
unopposed bipolar-valued outranking digraph;
- *oppositeness* : (1.0 - unopposedSize/standardSize);
if *InPercents* has value True, the *oppositeness* is rendered in percents format.
"""
from outrankingDigraphs import BipolarOutrankingDigraph
du = self.computeSize()
g = BipolarOutrankingDigraph(self)
d = g.computeSize()
if InPercents:
op = (1.0 -(du/d))*100.0
else:
op = (1.0 -(du/d))
return {'standardSize': d, 'unopposedSize': du, 'oppositeness': op}
[docs]
class SymmetricAverageFusionOutrankingDigraph(BipolarOutrankingDigraph):
"""
in development !
"""
def __init__(self,argPerfTab,actionsSubset=None,coalition=None,
CopyPerfTab=True,Normalized=True,Comments=False):
from copy import deepcopy
from time import time
# set initial time stamp
tt = time()
# ---- performance tableau data input
if argPerfTab is None:
print('Performance tableau required !')
#perfTab = RandomPerformanceTableau(commonThresholds = [(10.0,0.0),(20.0,0.0),(80.0,0.0),(101.0,0.0)])
elif isinstance(argPerfTab,(str)):
perfTab = PerformanceTableau(argPerfTab)
else:
perfTab = argPerfTab
# transfering the performance tableau data to self
self.name = 'symAvFusion_' + perfTab.name
# actions
if actionsSubset is None:
if isinstance(perfTab.actions,list):
actions = {}
for x in perfTab.actions:
actions[x] = {'name': str(x)}
self.actions = actions
else:
if CopyPerfTab:
self.actions = deepcopy(perfTab.actions)
else:
self.actions = perfTab.actions
else:
actions = {}
for x in actionsSubset:
actions[x] = {'name': str(x)}
self.actions = actions
# valuation domain
if Normalized:
Min = Decimal('-1.0')
Med = Decimal('0.0')
Max = Decimal('1.0')
else:
Min = Decimal('-100.0')
Med = Decimal('0.0')
Max = Decimal('100.0')
self.valuationdomain = {'min':Min,'med':Med,'max':Max}
try:
self.valuationdomain['precision'] = perfTab.valuationPrecision
except:
self.valuationdomain['precision'] = Decimal('0')
# objectives and criteria
try:
if CopyPerfTab:
self.objectives = deepcopy(perfTab.objectives)
else:
self.objectives = perfTab.objectives
except:
pass
criteria = OrderedDict()
if coalition is None:
coalition = perfTab.criteria.keys()
for g in coalition:
if CopyPerfTab:
criteria[g] = deepcopy(perfTab.criteria[g])
else:
criteria[g] = perfTab.criteria[g]
self.criteria = criteria
self.convertWeight2Decimal()
# install method Data and parameters
methodData = {}
try:
valuationType = perfTab.parameter['valuationType']
variant = perfTab.parameter['variant']
except:
valuationType = 'bipolar'
variant = 'standard'
methodData['parameter'] = {'valuationType': valuationType, 'variant': variant}
try:
vetoType = perfTab.parameter['vetoType']
methodData['parameter']['vetoType'] = vetoType
except:
vetoType = 'normal'
methodData['parameter']['vetoType'] = vetoType
if vetoType == 'bipolar':
hasBipolarVeto = True
self.methodData = methodData
# insert performance Data
if CopyPerfTab:
self.evaluation = deepcopy(perfTab.evaluation)
else:
self.evaluation = perfTab.evaluation
try:
if CopyPerfTab:
self.description = deepcopy(perfTab.description)
except:
pass
# init general digraph Data
self.order = len(self.actions)
# finished data input time stamp
self.runTimes = {'dataInput': time()-tt }
# ---------- construct outranking relation
# initial time stamp
tcp = time()
actions = self.actions
criteria = self.criteria
margG = []
for g in criteria:
gg = BipolarOutrankingDigraph(perfTab,coalition=[g],\
CopyPerfTab=CopyPerfTab,\
Normalized=True)
margG.append(gg)
weights = [criteria[g]['weight'] for g in criteria]
fg = FusionLDigraph(margG,'o-average',weights)
self.relation = deepcopy(fg.relation)
# finished relation computing time stamp
self.runTimes['computeRelation'] = time() - tcp
# ---- computing the gamma sets
tg = time()
self.gamma = self.gammaSets()
self.notGamma = self.notGammaSets()
self.runTimes['gammaSets'] = time() - tg
# total constructor time
self.runTimes['totalTime'] = time() - tt
if Comments:
print(self)
class _RubisRestServer(ServerProxy):
"""
xmlrpc-cgi Proxy Server for accessing on-line
a Rubis Rest Solver.
Deprecated !
"""
def __init__(self,host="https://leopold-loewenheim.uni.lu/cgi-bin/xmlrpc_cgi.py",Debug=False):
"""
Rubis Rest Server connection.
"""
import sys,xmlrpc.client,ssl
self._server = xmlrpc.client.ServerProxy(host)
if Debug:
print("host=%s" % host)
try:
response = self._server.hello()
print (response['message'])
print("available service ports")
for m in self._server.system.listMethods():
if 'system' not in m:
print (m, self._server.system.methodHelp(m))
except xmlrpc.client.Fault as faultobj:
print ("Rubis Server error:", faultobj.faultCode)
print (">>> %s <<<" % faultobj.faultString)
return None
except:
print ("Client Error: '%s/%s'" % (sys.exc_info()[0],sys.exc_info()[1]))
return None
def ping(self,Debug=False):
response = self._server.hello()
print(response['message'])
if Debug:
print("available service ports")
for m in self._server.system.listMethods():
if 'system' not in m:
print (m, self._server.system.methodHelp(m))
def submitProblem(self,perfTab,\
valuation='bipolar',\
hasVeto=True,\
argTitle='XMCDA 2.0 encoding',\
Debug=False):
"""
Submit PerformanceTableau class instances.
*Parameters*:
* valuation: 'bipolar', 'robust', 'integer'
* hasVeto: Switch on or off vetoes
* argTitle: set specific application title
"""
self.name = perfTab.name
self.problemText = perfTab.saveXMCDA2(isStringIO=True,\
valuationType=valuation,\
servingD3=False,\
comment='Rubis Rest Server generated',\
hasVeto = hasVeto,
title = argTitle)
self.valuation = valuation
arg = {'problemFile': self.problemText}
if Debug:
print(arg)
answer = self._server.submitProblem(arg)
self.ticket = answer['ticket']
print(answer['message'])
print('Server ticket: %s' % answer['ticket'])
fileNameTicket = self.name+'Ticket.txt'
fo = open(fileNameTicket,'w')
fo.write(answer['ticket'])
fo.close()
def submitXMCDA2Problem(self,fileName,valuation=None,Debug=False):
"""
Submit stored XMCDA 2.0 encoded performance tableau.
.. warning::
An <_.xml> file extension is assumed !
"""
print("Calling submitXMCDA2Problem(%s)" % fileName)
self.name = fileName
fileNameExt = fileName + '.xml'
fi = open(fileNameExt,'r')
self.problemText = fi.read()
fi.close()
self.valuation = valuation
arg = {'problemFile': self.problemText}
if Debug:
print(arg)
answer = self._server.submitProblem(arg)
self.ticket = answer['ticket']
print(answer['message'])
print(answer['ticket'])
fileNameTicket = fileName+'Ticket.txt'
fo = open(fileNameTicket,'w')
fo.write(answer['ticket'])
fo.close()
def saveXMCDA2Solution(self,fileName=None,Debug=False):
"""
Save the solution in XMCDA 2.0 encoding.
"""
if fileName is None:
fileNameExt = "Solution"+str(self.ticket)+".xml"
else:
fileNameExt = fileName+"Solution.xml"
arg = {'ticket': self.ticket}
answer = self._server.requestSolution(arg)
try:
self.solution = answer['solution']
if Debug:
print(self.solution)
fo = open(fileNameExt,'w')
fo.write(self.solution)
fo.close()
print("saving solution in file %s" %fileNameExt)
except:
print(answer['message'])
def showHTMLSolution(self,ticket=None,valuation='bipolar'):
"""
Show XMCDA 2.0 solution in a default browser window.
The valuation parameter may set the correct style sheet.
*Parameter*:
* valuation: 'bipolar' or 'robust'.
By default the valuation type is set
automatically at problem submission.
"""
import os,webbrowser
newTab = 2 # open in a new tab if possible
if ticket is not None:
self.ticket = ticket
if valuation is None:
try:
valuation = self.valuation
if valuation is None:
valuation = 'bipolar'
except:
if valuation is None:
valuation = 'bipolar'
arg = {'ticket': self.ticket, 'valuation': valuation}
answer = self._server.requestSolutionHTML(arg)
try:
fileName = str(self.name)+str(self.ticket)+"Solution.html"
fo = open(fileName,'w')
fo.write(answer['html'])
fo.close()
url = "file://"+os.getcwd()+"/%s" % fileName
webbrowser.open(url,new=newTab)
except:
print(answer['message'])
################################################################
#----------test outrankingDigraphs classes ----------------
if __name__ == "__main__":
import copy
from time import time, sleep
from random import randint
from outrankingDigraphs import BipolarOutrankingDigraph
from transitiveDigraphs import RankingByChoosingDigraph
print("""
****************************************************
* Digraph3 outrankingDigraphs module *
* Copyright (C) 2011-2021 Raymond Bisdorff *
* The module comes with ABSOLUTELY NO WARRANTY *
* to the extent permitted by the applicable law. *
* This is free software, and you are welcome to *
* redistribute it if it remains free software. *
****************************************************
""")
print('*-------- Testing classes and methods -------')
t = RandomCBPerformanceTableau(numberOfActions=50,\
numberOfCriteria=21,\
weightDistribution='equiobjectives',
seed=100)
g = BipolarOutrankingDigraph(t,Normalized=True,Threading=True,startMethod='spawn')
print(g)
Threading = True
t0 = time()
criteriaCorrelations = g.computeMarginalVersusGlobalOutrankingCorrelations(Threading=Threading,startMethod='spawn')
print(time()-t0)
print(criteriaCorrelations)
# from randomPerfTabs import Random3ObjectivesPerformanceTableau
# t = Random3ObjectivesPerformanceTableau(
# numberOfActions=500,\
# numberOfCriteria=7,\
# vetoProbability=0.2,\
# seed=1)
# g = BipolarOutrankingDigraph(t,Threading=True,
# startMethod=None,
# nbrCores=10,Comments=True)
# print(g)
# t0 = time()
# g.showHTMLPerformanceHeatmap(Correlations=True,Threading=False)
# print(time()-t0)
#g.showPolarisations()
#g.showObjectives()
# ranking = g.computeNetFlowsRanking()
# sizeOfDiagonal = 5
# quasiRelation = {}
# Med = g.valuationdomain['med']
# for x in g.actions:
# quasiRelation[x] = {}
# xi = ranking.index(x)
# for y in g.actions:
# yj = ranking.index(y)
# #if abs(xi-yj) > sizeOfDiagonal:
# # quasiRelation[x][y] = g.relation[x][y]
# if xi >= yj:
# quasiRelation[x][y] = min(Med, g.relation[x][y])
# else:
# quasiRelation[x][y] = max(Med, g.relation[x][y])
# g.relation = quasiRelation
# gcd = ~(-g)
# gcd.showHTMLRelationHeatmap(actionsList=ranking,colorLevels=9)
#############################
############################