Source code for randomPerfTabs
#!/usr/bin/env python3
"""
Python implementation of digraphs
Module for generating random performance tableaux
Copyright (C) 2015-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: python3.10 $"
# $Source: /home/cvsroot/Digraph/randomPerfTabs.py,v $
from perfTabs import PerformanceTableau
from decimal import Decimal
from collections import OrderedDict
#########################################
# generators for random PerformanceTableaux
[docs]
class RandomPerformanceTableau(PerformanceTableau):
"""
For generating a random standard performance tableau.
Parameters:
* numberOfActions := nbr of decision actions.
* numberOfCriteria := number performance criteria.
* weightDistribution := 'random' (default) | 'fixed' | 'equisignificant'.
| If 'random', weights are uniformly selected randomly
| form the given weight scale;
| If 'fixed', the weightScale must provided a corresponding weights
| distribution;
| If 'equisignificant', all criterion weights are put to unity.
* weightScale := [Min,Max] (default =[1,numberOfCriteria].
* IntegerWeights := True (default) | False (normalized to proportions of 1.0).
* commonScale := [Min;Max]; common performance measuring scales (default = [0;100])
* commonThresholds := [(q0,q1),(p0,p1),(v0,v1)]; common indifference(q), preference (p) and considerable performance difference discrimination thresholds. q0, p0 and v0 are expressed in percentige of the common scale amplitude: Max - Min.
* commonMode := common random distribution of random performance measurements (default = ('beta',None,(2,2)) ):
| ('uniform',None,None), uniformly distributed between min and max values.
| ('normal',mu,sigma), truncated Gaussion distribution.
| ('triangular',mode,repartition), generalized triangular distribution
| ('beta',mod,(alpha,beta)), mode in ]0,1[.
* valueDigits := <integer>, precision of performance measurements
(2 decimal digits by default).
* missingDataProbability := 0 <= x <= 1.0; probability of missing performance evaluation on a criterion for an alternative (default 0.025).
* Default NA symbol == Decimal('-999')
Code example:
>>> from randomPerfTabs import RandomPerformanceTableau
>>> t = RandomPerformanceTableau(numberOfActions=3,numberOfCriteria=1,seed=100)
>>> t.actions
{'a1': {'comment': 'RandomPerformanceTableau() generated.', 'name': 'random decision action'},
'a2': {'comment': 'RandomPerformanceTableau() generated.', 'name': 'random decision action'},
'a3': {'comment': 'RandomPerformanceTableau() generated.', 'name': 'random decision action'}}
>>> t.criteria
{'g1': {'thresholds': {'ind' : (Decimal('10.0'), Decimal('0.0')),
'veto': (Decimal('80.0'), Decimal('0.0')),
'pref': (Decimal('20.0'), Decimal('0.0'))},
'scale': [0.0, 100.0],
'weight': Decimal('1'),
'name': 'digraphs.RandomPerformanceTableau() instance',
'comment': 'Arguments: ; weightDistribution=random;
weightScale=(1, 1); commonMode=None'}}
>>> t.evaluation
{'g01': {'a01': Decimal('45.95'),
'a02': Decimal('95.17'),
'a03': Decimal('17.47')
}
}
"""
def __init__(self,numberOfActions = 13,
actionNamePrefix = 'a',
numberOfCriteria = 7,
weightDistribution = 'equisignificant',
weightScale=None,
IntegerWeights=True,
commonScale = (0.0,100.0),
commonThresholds = ((2.5,0.0),(5.0,0.0),(80.0,0.0)),
commonMode = ('beta',None,(2,2)),
valueDigits = 2,
missingDataProbability = 0.025,
NA = -999,
BigData=False,
seed = None,
Debug = False):
import sys,time,math
from copy import copy
# fixing the seed (None by default)
self.randomSeed = seed
import random
random.seed(seed)
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
# seeting tableau name
self.name = 'randomperftab'
self.BigData = BigData
# generate actions
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(1,numberOfActions+1):
if BigData:
actionName = ('%s%%0%dd' % (actionNamePrefix,nd)) % (i)
actions[i] = {'name': actionName}
else:
actionKey = ('%s%%0%dd' % (actionNamePrefix,nd)) % (i)
actions[actionKey] = {'shortName':actionKey,
'name': 'action #%d' % i,
'comment': 'RandomPerformanceTableau() generated.' }
# # generate weights
if weightScale is None:
weightScale = (1,numberOfCriteria)
if weightDistribution == 'random':
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
weightsList.append(
Decimal(str(random.randint(weightScale[0],
weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'fixed':
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOCriteria):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
elif weightDistribution == 'equisignificant' \
or weightDistribution == 'equiobjectives':
weightScale = (1,1)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
else:
print('!!! Error: wrong criteria weight distribution mode: %s !!!!' \
% (weightDistribution))
self.sumWeights = sumWeights
# generate criteria dictionary
if commonMode is None:
#commonMode = ['uniform',None,None]
commonMode = ['beta',None,(2,2)]
ngd = len(str(numberOfCriteria))
criteria = OrderedDict()
commentString = 'Arguments: '
commentString += '; weightDistribution='+str(weightDistribution)
commentString += '; weightScale='+str(weightScale)
commentString += '; commonMode='+str(commonMode)
digits = valueDigits
commonAmplitude = commonScale[1] - commonScale[0]
for i in range(numberOfCriteria):
g = ('g%%0%dd' % ngd) % (i+1)
criteria[g] = {}
criteria[g]['name'] = 'RandomPerformanceTableau() instance'
criteria[g]['comment'] = commentString
criteria[g]['preferenceDirection'] = 'max'
try:
veto = round(commonThresholds[3][0]\
* commonAmplitude/100.0,digits)
ind = round(commonThresholds[0][0]\
* commonAmplitude/100.0,digits)
pref = round(commonThresholds[1][0]\
* commonAmplitude/100.0,digits)
weakVeto = round(commonThresholds[2][0]\
* commonAmplitude/100.0,digits)
indThresholds=(Decimal(str(ind)),
Decimal(str(commonThresholds[0][1])))
prefThresholds=(Decimal(str(pref)),
Decimal(str(commonThresholds[1][1])))
weakVetoThresholds=(Decimal(str(weakVeto)),
Decimal(str(commonThresholds[2][1])))
vetoThresholds=(Decimal(str(veto)),
Decimal(str(commonThresholds[3][1])))
criteria[g]['thresholds'] = {'ind':indThresholds,
'pref':prefThresholds,
'weakVeto':weakVetoThresholds,
'veto':vetoThresholds}
except:
ind = round((commonThresholds[0][0]/100.0)\
* commonAmplitude,digits)
pref = round(commonThresholds[1][0]\
* commonAmplitude/100.0,digits)
veto = round(commonThresholds[2][0]\
* commonAmplitude/100.0,digits)
indThresholds=(Decimal(str(ind)),
Decimal(str(commonThresholds[0][1])))
prefThresholds=(Decimal(str(pref)),
Decimal(str(commonThresholds[1][1])))
vetoThresholds=(Decimal(str(veto)),
Decimal(str(commonThresholds[2][1])))
criteria[g]['thresholds'] = {'ind':indThresholds,
'pref':prefThresholds,
'veto':vetoThresholds}
criteria[g]['scale'] = commonScale
if IntegerWeights:
criteria[g]['weight'] = weightsList[i]
else:
criteria[g]['weight'] = weightsList[i]/sumWeights
#self.criteria = criteria
# generate evaluations
digits=valueDigits
self.digits = digits
self.commonScale = commonScale
self.commonMode = commonMode
evaluation = {}
if str(commonMode[0]) == 'uniform':
for g in criteria:
evaluation[g] = {}
for a in actions:
## randeval = random.randint(commonScale[0],commonScale[1])
randeval = random.uniform(commonScale[0],commonScale[1])
evaluation[g][a] = Decimal(str(round(randeval,digits)))
elif str(commonMode[0]) == 'triangular':
m = commonScale[0]
M = commonScale[1]
if commonMode[1] is None:
xm = (commonScale[1]-commonScale[0])/2.0
else:
xm = commonMode[1]
if commonMode[2] is None:
r = 0.5
else:
r = commonMode[2]
rdseed = random.random()
rng = RNGTr(m,M,xm,r,seed=rdseed)
for g in criteria:
evaluation[g] = {}
for a in actions:
## u = random.random()
## if u < r:
## randeval = m + math.sqrt(u/r)*(xm-m)
## else:
## randeval = M - math.sqrt((1-u)/(1-r))*(M-xm)
## evaluation[g][a] = Decimal(str(round(randeval,digits)))
evaluation[g][a] = Decimal(str(round(rng.random(),digits)))
elif str(commonMode[0]) == 'beta':
m = commonScale[0]
M = commonScale[1]
if commonMode[1] is None:
xm = 0.5
else:
xm = commonMode[1]
if commonMode[2] is None:
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0/xm
else:
alpha = commonMode[2][0]
beta = commonMode[2][1]
if Debug:
print('alpha,beta', alpha,beta)
for g in criteria:
evaluation[g] = {}
for a in actions:
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
evaluation[g][a] = Decimal(str(round(randeval,digits)))
if Debug:
print('xm,alpha,beta,u,m,M,randeval',
xm,alpha,beta,u,m,M,randeval)
elif str(commonMode[0]) == 'normal':
if commonMode[1] is None:
mu = (commonScale[1]-commonScale[0])/2.0
else:
mu = commonMode[1]
if commonMode[2] is None:
sigma = (commonScale[1]-commonScale[0])/4.0
else:
sigma = commonMode[2]
for g in criteria:
evaluation[g] = {}
for a in actions:
notfound = True
while notfound:
randeval = random.normalvariate(mu,sigma)
if randeval >= commonScale[0] \
and randeval <= commonScale[1]:
notfound = False
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
print('mode error in random evaluation generator !!')
print(str(commonMode[0]))
#sys.exit(1)
# randomly insert missing data
NA = Decimal(NA)
self.NA = NA
self.missingDataProbability = missingDataProbability
for c in criteria:
for x in actions:
if random.random() < missingDataProbability:
evaluation[c][x] = NA
# store object dict
self.actions = actions
self.criteria = criteria
self.evaluation = evaluation
# store weights preorder
self.weightPreorder = self.computeWeightPreorder()
[docs]
class RandomPerformanceGenerator(object):
"""
Generic wrapper for generating new decision actions or performance tableaux
with random evaluations generated with a given performance tableau model of type:
RandomPerformanceTableau, RandomCBPerformanceTableau,
or Random3ObjectivesPerformanceTableau.
The return format of generated new set of random actions is schown below.
This return may be directly feeded to the PerformanceQuantiles.updateQuantiles() method.
>>> from randomPerfTabs import *
>>> t = RandomPerformanceTableau(seed=100)
>>> t
*------- PerformanceTableau instance description ------*
Instance class : RandomPerformanceTableau
Seed : 100
Instance name : randomperftab
Actions : 13
Criteria : 7
Attributes : [
'randomSeed', 'name', 'BigData', 'sumWeights', 'digits', 'commonScale',
'commonMode', 'missingDataProbability', 'actions', 'criteria',
'evaluation', 'weightPreorder']
>>> rpg = RandomPerformanceGenerator(t,seed= 100)
>>> newActions = rpg.randomActions(2)
>>> print(newActions)
{'actions': OrderedDict([
('a14', {'shortName': 'a14',
'name': 'random decision action',
'comment': 'RandomPerformanceGenerator'}),
('a15', {'shortName': 'a15',
'name': 'random decision action',
'comment': 'RandomPerformanceGenerator'})]),
'evaluation': {
'g1': {'a14': Decimal('15.17'), 'a15': Decimal('80.87')},
'g2': {'a14': Decimal('44.51'), 'a15': Decimal('62.74')},
'g3': {'a14': Decimal('57.87'), 'a15': Decimal('64.24')},
'g4': {'a14': Decimal('58.0'), 'a15': Decimal('26.99')},
'g5': {'a14': Decimal('24.22'), 'a15': Decimal('21.18')},
'g6': {'a14': Decimal('29.1'), 'a15': Decimal('73.09')},
'g7': {'a14': Decimal('96.58'), 'a15': Decimal('-999')}}}
>>> newTab = rpg.randomPerformanceTableau(2)
>>> newTab.showPerformanceTableau()
*---- performance tableau -----*
criteria | weights | 'a17' 'a18'
---------|-----------------------------------------
'g1' | 1 | 55.80 22.03
'g2' | 1 | 57.78 33.83
'g3' | 1 | 80.54 31.83
'g4' | 1 | 31.15 69.98
'g5' | 1 | 46.25 48.80
'g6' | 1 | 42.24 82.88
'g7' | 1 | 57.31 41.66
"""
def __init__(self,argPerfTab,actionNamePrefix='a',
instanceCounter=None,seed=None):
from randomPerfTabs import _RandomStdPerformanceGenerator,\
_RandomCBPerformanceGenerator,\
_Random3ObjectivesPerformanceGenerator
from performanceQuantiles import PerformanceQuantiles
if argPerfTab.__class__ == RandomPerformanceTableau:
self.__class__ = _RandomStdPerformanceGenerator
return _RandomStdPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
elif argPerfTab.__class__ == RandomCBPerformanceTableau:
self.__class__ = _RandomCBPerformanceGenerator
return _RandomCBPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
elif argPerfTab.__class__ == Random3ObjectivesPerformanceTableau:
self.__class__ = _Random3ObjectivesPerformanceGenerator
return _Random3ObjectivesPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
elif argPerfTab.__class__ == PerformanceQuantiles:
if argPerfTab.perfTabType == 'RandomPerformanceTableau':
self.__class__ = _RandomStdPerformanceGenerator
return _RandomStdPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
elif argPerfTab.perfTabType == 'RandomCBPerformanceTableau':
self.__class__ = _RandomCBPerformanceGenerator
return _RandomCBPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
elif argPerfTab.perfTabType == 'Random3ObjectivesPerformanceTableau':
self.__class__ = _Random3ObjectivesPerformanceGenerator
return _Random3ObjectivesPerformanceGenerator.__init__(self,argPerfTab,
actionNamePrefix=actionNamePrefix,
instanceCounter=instanceCounter,seed=seed)
[docs]
def randomActions(self,nbrOfRandomActions=1):
"""
Generates nbrOfRandomActions.
"""
from collections import OrderedDict
criteria = self.perfTab.criteria
n = self.counter + nbrOfRandomActions
self.nd = len(str(n))
newActions = OrderedDict()
newEvaluation ={}
for g in criteria:
newEvaluation[g] = {}
for i in range(nbrOfRandomActions):
newAction = self._randomAction()
newKey = newAction['action'].pop('key')
newActions[newKey] = newAction['action']
for g in criteria:
newEvaluation[g][newKey] = newAction['evaluation'][g]
return {'actions': newActions, 'evaluation': newEvaluation}
[docs]
def randomPerformanceTableau(self,nbrOfRandomActions=1):
"""
Generates nbrOfRandomActions.
"""
from collections import OrderedDict
from perfTabs import EmptyPerformanceTableau
from randomPerfTabs import RandomPerformanceTableau,\
RandomCBPerformanceTableau,\
Random3ObjectivesPerformanceTableau
newPerfTab = EmptyPerformanceTableau()
try:
if self.perfTab.perfTabType == 'PerformanceQuantiles':
if self.perfTab.perfTabType != 'RandomPerformanceTableau':
newPerfTab.__class__ = RandomPerformanceTableau
elif self.perfTab.perfTabType != 'RandomCBPerformanceTableau':
newPerfTab.__class__ = RandomCBPerformanceTableau
elif self.perfTab.perfTabType != 'Random3ObjectivesPerformanceTableau':
newPerfTab.__class__ = Random3ObjectivesPerformanceTableau
else:
print('Error')
return None
except:
newPerfTab.__class__ = self.perfTab.__class__
newPerfTab.name = self.perfTab.name
try:
newPerfTab.objectives = self.perfTab.objectives
except:
newPerfTab.objectives = OrderedDict()
criteria = self.perfTab.criteria
newPerfTab.criteria = criteria
newPerfTab.NA = self.perfTab.NA
newActions = OrderedDict()
newEvaluation ={}
for g in criteria:
newEvaluation[g] = {}
n = self.counter + nbrOfRandomActions
self.nd = len(str(n))
for i in range(nbrOfRandomActions):
newAction = self._randomAction()
newKey = newAction['action'].pop('key')
newActions[newKey] = newAction['action']
for g in criteria:
newEvaluation[g][newKey] = newAction['evaluation'][g]
newPerfTab.actions = newActions
newPerfTab.evaluation = newEvaluation
return newPerfTab
class _RandomStdPerformanceGenerator(RandomPerformanceGenerator):
"""
Generator for genrating new decision actions from a
given <RandomPerformanceTableau> model.
"""
def __init__(self,argPerfTab,actionNamePrefix='a',
instanceCounter=0,seed=None):
"""
Set the initial state of the random generator.
"""
import random
random.seed(seed)
self.random = random
self.perfTab = argPerfTab
self.actionNamePrefix = actionNamePrefix
if instanceCounter is None:
self.counter = len(argPerfTab.actions)
else:
self.counter = instanceCounter
self.nd = len(str(self.counter))
self.commonMode = argPerfTab.commonMode
if str(self.commonMode[0]) == 'triangular':
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
rdseed = random.random()
self.rng = RNGTr(m,M,xm,r,seed=rdseed)
self.commonScale = argPerfTab.commonScale
def _randomAction(self,Debug=False):
"""
Returns
``{'action': key, 'evaluation': {'g1': Decimal(...), 'g2': Decimal(...), ... }}``
"""
# generate action key
self.counter += 1
actionKey = ('%s%%0%dd' % (self.actionNamePrefix,self.nd)) % (self.counter)
action = {'shortName':actionKey,
'name': 'random decision action %s' % (actionKey),
'comment': 'RandomPerformanceGenerator',
#'type': actionType,
'key': actionKey}
# generate random evaluation
random = self.random
commonMode = self.commonMode
commonScale = self.commonScale
digits = self.perfTab.digits
criteria = self.perfTab.criteria
evaluation = {}
if str(commonMode[0]) == 'uniform':
for g in criteria:
evaluation[g] = {}
randeval = random.uniform(commonScale[0],commonScale[1])
evaluation[g] = Decimal(str(round(randeval,digits)))
elif str(self.commonMode[0]) == 'triangular':
for g in criteria:
evaluation[g] = Decimal(str(round(rng.random(),digits)))
elif str(commonMode[0]) == 'beta':
m = commonScale[0]
M = commonScale[1]
if commonMode[1] is None:
xm = 0.5
else:
xm = commonMode[1]
if commonMode[2] is None:
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0/xm
else:
alpha = commonMode[2][0]
beta = commonMode[2][1]
if Debug:
print('alpha,beta', alpha,beta)
for g in criteria:
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
evaluation[g] = Decimal(str(round(randeval,digits)))
elif str(commonMode[0]) == 'normal':
if commonMode[1] is None:
mu = (commonScale[1]-commonScale[0])/2.0
else:
mu = commonMode[1]
if commonMode[2] is None:
sigma = (commonScale[1]-commonScale[0])/4.0
else:
sigma = commonMode[2]
for g in criteria:
notfound = True
while notfound:
randeval = random.normalvariate(mu,sigma)
if randeval >= commonScale[0] and randeval <= commonScale[1]:
notfound = False
evaluation[g] = Decimal(str(round(randeval,digits)))
# randomly insert missing data
missingDataProbability = self.perfTab.missingDataProbability
NA = self.perfTab.NA
for c in criteria:
if random.random() < missingDataProbability:
evaluation[c] = NA
# return a new random decision alternative
return {'action': action,'evaluation':evaluation}
## def randomUpdate(self,nbrOfRandomActions=1):
## """
## Updates *self.perfTab* with *n* = *nbrOfActions* new random decision alternatives.
##
## .. note::
##
## The update will modify the generator's given performance tableau instance by,
## either adding new actions with their random evaluations,
## or updating the performances of already existing decision actions.
## """
## actions = self.perfTab.actions
## criteria = self.perfTab.criteria
## evaluation = self.perfTab.evaluation
## for i in range(nbrOfRandomActions):
## newAction = self._randomAction()
## newEvaluation = newAction['evaluation']
## newKey = newAction['action']['key']
## actions[newKey] = newAction['action']
## for g in criteria:
## evaluation[g][newKey] = newEvaluation[g]
##################
#-----------------
[docs]
class RandomAcademicPerformanceTableau(PerformanceTableau):
"""
For generating a temporary academic performance tableau with random grading results performances
of a number of students in different academic courses (see Lecture 4: Grading
of the Algorithmic decision Theory Course http://hdl.handle.net/10993/37933 )
*Parameters*:
* number of students,
* number of courses,
* weightDistribution := equisignificant | random (default, see RandomPerformanceTableau)
* weightScale := (1, 1 | numberOfCourses (default when random))
* IntegerWeights := Boolean (True = default)
* commonScale := (0,20) (default)
* ndigits := 0
* WithTypes := Boolean (False = default)
* commonMode := ('triangular',xm=14,r=0.25)
* commonThresholds (default) := {
| 'ind':(0,0),
| 'pref':(1,0),
| } (default)
When parameter *WithTypes* is set to *True*, the students are randomly allocated
to one of the four categories: *weak* (1/6), *fair* (1/3), *good* (1/3),
and *excellent* (1/3), in the bracketed proportions.
In a default 0-20 grading range, the random range of a weak student is 0-10,
of a fair student 4-16, of a good student 8-20, and of an excellent student 12-20.
The random grading generator follows a double triangular probablity law
with *mode* equal to the middle of the random range and *median repartition* of
probability each side of the mode.
>>> from randomPerfTabs import RandomAcademicPerformanceTableau
>>> t = RandomAcademicPerformanceTableau(numberOfStudents=7,
... numberOfCourses=5, missingDataProbability=0.03,
... WithTypes=True, seed=100)
>>> t
*------- PerformanceTableau instance description ------*
Instance class : RandomAcademicPerformanceTableau
Seed : 100
Instance name : randstudPerf
Actions : 7
Criteria : 5
Attributes : ['randomSeed', 'name', 'actions',
'criteria', 'evaluation', 'weightPreorder']
>>> t.showPerformanceTableau()
*---- performance tableau -----*
Courses | 'm1' 'm2' 'm3' 'm4' 'm5'
ECTS | 5 1 5 4 3
---------|--------------------------
's1f' | 12 10 14 14 13
's2g' | 14 12 16 12 14
's3g' | 13 10 NA 12 17
's4f' | 10 13 NA 13 12
's5e' | 17 12 16 17 12
's6g' | 17 17 12 16 14
's7e' | 12 13 13 16 NA
>>> t.weightPreorder
[['m2'], ['m5'], ['m4'], ['m1', 'm3']]
The random instance generated here with seed = 100 results in a set of only
excellent (2), good (3) and fair (2) student performances. We observe 3 missing grades (NA).
We may show a statistical summary per course (performance criterion) with more than 5 grades.
>>> t.showStatistics()
*-------- Performance tableau summary statistics -------*
Instance name : randstudPerf
#Actions : 7
#Criteria : 5
*Statistics per Criterion*
Criterion name : g1
Criterion weight : 5
criterion scale : 0.00 - 20.00
# missing evaluations : 0
mean evaluation : 13.57
standard deviation : 2.44
maximal evaluation : 17.00
quantile Q3 (x_75) : 17.00
median evaluation : 13.50
quantile Q1 (x_25) : 12.00
minimal evaluation : 10.00
mean absolute difference : 2.69
standard difference deviation : 3.45
Criterion name : g2
Criterion weight : 1
criterion scale : 0.00 - 20.00
# missing evaluations : 0
mean evaluation : 12.43
standard deviation : 2.19
maximal evaluation : 17.00
quantile Q3 (x_75) : 14.00
median evaluation : 12.50
quantile Q1 (x_25) : 11.50
minimal evaluation : 10.00
mean absolute difference : 2.29
standard difference deviation : 3.10
Criterion name : g3
Criterion weight : 5
criterion scale : 0.00 - 20.00
# missing evaluations : 2
Criterion name : g4
Criterion weight : 4
criterion scale : 0.00 - 20.00
# missing evaluations : 0
mean evaluation : 14.29
standard deviation : 1.91
maximal evaluation : 17.00
quantile Q3 (x_75) : 16.25
median evaluation : 15.00
quantile Q1 (x_25) : 12.75
minimal evaluation : 12.00
mean absolute difference : 2.12
standard difference deviation : 2.70
Criterion name : g5
Criterion weight : 3
criterion scale : 0.00 - 20.00
# missing evaluations : 1
mean evaluation : 13.67
standard deviation : 1.70
maximal evaluation : 17.00
quantile Q3 (x_75) : 15.50
median evaluation : 14.00
quantile Q1 (x_25) : 12.50
minimal evaluation : 12.00
mean absolute difference : 1.78
standard difference deviation : 2.40
"""
def __init__(self,numberOfStudents = 10, numberOfCourses = 5,
weightDistribution = 'random', weightScale = (1,5),
commonScale = (0,20), ndigits = 0,
WithTypes = False,
commonMode = ('triangular',12,0.25),
commonThresholds = None, IntegerWeights = True,
BigData = False, missingDataProbability = 0.0,
NA = -999,
seed = None,
Debug = False):
"""
"""
# didactic
if WithTypes:
types = ['weak','fair','fair','good','good','excellent']
nt = len(types)
# set random seed
self.randomSeed = seed
import random
random.seed(seed)
# set name
self.name = 'randstudPerf'
# generate actions
numberOfActions = numberOfStudents
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(numberOfActions):
if WithTypes:
ri = random.randint(0,nt-1)
if BigData:
actionName = ('s%%0%dd' % (nd)) % (i+1)
actions[i] = {'name': actionName}
if WithTypes:
actions[i]['type'] = types[ri]
actions[i]['name'] = '%s%s' % (actions[i]['name'],(actions[i]['type'])[0])
else:
actionKey = ('s%%0%dd' % (nd)) % (i+1)
if WithTypes:
actions[actionKey]= {'type': types[ri],
'shortName': '%s%s' % (actionKey,types[ri][0]),
'name': 'student %s%s' % (actionKey,types[ri][0]),
'comment': 'RandomAcademicPerformanceTableau() generated.'}
else:
actions[actionKey] = {'shortName':actionKey,
'name': 'student %s' % actionKey,
'comment': 'RandomAcademicPerformanceTableau() generated.' }
# generate the criteria weights
numberOfCriteria = numberOfCourses
if weightScale is None:
weightScale = (1,numberOfCriteria)
if weightDistribution == 'random':
weightsList = []
sumWeights = Decimal('0.0')
i = 0
for i in range(numberOfCriteria):
weightsList.append(Decimal(str(random.randint(weightScale[0],weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'equisignificant':
weightScale = (1,1)
weightsList = [Decimal(str(weightScale[0])) for i in range(numberOfCriteria)]
sumWeights = sum(weightsList)
else:
print('!!! Error: wrong course weight distribution mode: %s !!!!' % (weightDistribution))
# generate criteria dictionary
ngd = len(str(numberOfCriteria))
criteria = OrderedDict()
commentString = 'Arguments: '
commentString += '; weightDistribution='+str(weightDistribution)
commentString += '; weightScale='+str(weightScale)
commentString += '; IntegerWeights='+str(IntegerWeights)
commentString += '; commonThresholds='+str(commonThresholds)
commentString += '; missingDataProbability='+str(missingDataProbability)
commentString += '; WithTypes=='+str(WithTypes)
for i in range(numberOfCriteria):
g = ('m%%0%dd' % ngd) % (i+1)
criteria[g] = {}
criteria[g]['name']='Master course %d' % (i+1)
criteria[g]['comment']=commentString
try:
indThreshold =(Decimal(str(commonThresholds['ind'][0])),
Decimal(str(commonThresholds['ind'][1])))
prefThreshold =(Decimal(str(commonThresholds['pref'][0])),
Decimal(str(commonThresholds['pref'][1])))
## vetoThreshold =(Decimal(str(commonThresholds['veto'][0])),
## Decimal(str(commonThresholds['veto'][1])))
criteria[g]['thresholds'] = {'ind':indThreshold,
'pref':prefThreshold}
## 'veto':vetoThreshold}
except:
indThreshold = ( Decimal("0"), Decimal("0") )
prefThreshold = ( Decimal("1"), Decimal("0") )
# vetoThreshold = ( Decimal(str(numberOfActions)), Decimal("0") )
criteria[g]['thresholds'] = { 'ind':indThreshold,
'pref':prefThreshold,
# 'veto': vetoThreshold
}
criteria[g]['scale'] = commonScale
criteria[g]['preferenceDirection'] = 'max'
if IntegerWeights:
criteria[g]['weight'] = weightsList[i]
else:
criteria[g]['weight'] = weightsList[i]/sumWeights
# generate evaluations
digits = ndigits
evaluation = {}
# evaluation ranges for student types
if WithTypes:
randEvalRanges = {'weak':(commonScale[0],commonScale[1]*0.5),
'fair':(commonScale[0]+commonScale[1]*0.2,commonScale[1]*0.8),
'good':(commonScale[0]+commonScale[1]*0.4,commonScale[1]),
'excellent':(commonScale[0]+commonScale[1]*0.6,commonScale[1])}
# install the triangular RNG if necessary
if str(commonMode[0]) == 'triangular':
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
# generate all individual random evaluations
for g in criteria:
evaluation[g] = {}
for x in actions:
if WithTypes:
rangex = randEvalRanges[actions[x]['type']]
if Debug:
print(actions[x],rangex)
#--------
if str(commonMode[0]) == 'uniform':
if WithTypes:
randeval = random.uniform(rangex[0],rangex[1])
else:
randeval = random.uniform(commonScale[0],commonScale[1])
evaluation[g][x] = Decimal(str(round(randeval,digits)))
# ---------
elif str(commonMode[0]) == 'triangular':
#from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
if WithTypes:
rdseed = random.random()
rng = RNGTr(rangex[0],rangex[1],min(rangex[1],
commonMode[1]),commonMode[2],seed=rdseed)
else:
rdseed = random.random()
rng = RNGTr(commonScale[0],commonScale[1],
commonMode[1],commonMode[2],seed=rdseed)
evaluation[g][x] = Decimal(str(round(rng.random(),digits)))
#-------------------
elif str(commonMode[0]) == 'beta':
if WithTypes:
m = rangex[0]
M = rangex[1]
else:
m = commonScale[0]
M = commonScale[1]
if commonMode[1] is None:
xm = 0.5
else:
xm = commonMode[1]
if commonMode[2] is None:
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0/xm
else:
alpha = commonMode[2][0]
beta = commonMode[2][1]
if Debug:
print('alpha,beta', alpha,beta)
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
evaluation[g][x] = Decimal(str(round(randeval,digits)))
# randomly insert missing data
# self.missingDataProbability = missingDataProbability
NA = Decimal(NA)
for c in criteria:
for x in actions:
if random.random() < missingDataProbability:
evaluation[c][x] = NA
# install object items
self.actions = actions
self.criteria = criteria
self.evaluation = evaluation
self.NA = NA
self.weightPreorder = self.computeWeightPreorder()
[docs]
def showStudents(self,WithComments=False):
"""
Print a list of the students.
"""
actions = self.actions
print('"------------ Registered students -------*')
print('key: \t Name \t\t Type')
print('----------------------------------')
for st in actions:
x = actions[st]
if WithComments:
print('%s: \t %s \t %s\n %s' %\
(st,x['name'],x['type'],x['comment']) )
else:
print('%s: \t %s \t %s' %\
(st,x['name'],x['type']) )
[docs]
def showCourses(self,coursesSubset=None,ndigits=0,
pageTitle='List of Courses'):
"""
Print a list of the courses.
"""
self.showHTMLCriteria(criteriaSubset=coursesSubset,ndigits=ndigits,
title=pageTitle)
[docs]
def showPerformanceTableau(self,Transposed=False,studentsSubset=None,
fromIndex=None,toIndex=None,Sorted=True,ndigits=0):
"""
Print the performance Tableau.
"""
from decimal import Decimal
actions = self.actions
evaluation = self.evaluation
NA = self.NA
criteria = self.criteria
print('*---- performance tableau -----*')
criteriaList = list(criteria)
if Sorted:
criteriaList.sort()
if studentsSubset is None:
actionsList = list(actions)
if Sorted:
actionsList.sort()
else:
actionsList = list(studentsSubset)
if fromIndex is None:
fromIndex = 0
if toIndex is None:
toIndex=len(actionsList)
# view criteria x actions
if Transposed:
print(' Courses | ETCS |', end=' ')
for x in actionsList:
xName = self.actions[x]['shortName']
print('\''+xName+'\'', end=' ')
print('\n---------|-----------------------------------------')
formatString = ' %%0%d.%df ' % (2,ndigits)
for g in criteriaList:
print(' \'' +str(g)+'\' | '+str(criteria[g]['weight'])+' |', end=' ')
for i in range(fromIndex,toIndex):
x = actionsList[i]
evalgx = evaluation[g][x]
if evalgx == NA:
print(' NA ', end=' ')
else:
print(formatString % (evalgx), end=' ')
print()
# view actions x criteria
else:
print(' Courses | ', end=' ')
for g in criteriaList:
print('\''+str(g)+'\'', end=' ')
print('\n ECTS | ', end=' ')
for g in criteriaList:
print(' %s ' % str(criteria[g]['weight'] ), end=' ')
print('\n---------|-----------------------------------------')
formatString = ' %%0%d.%df ' % (2,ndigits)
for i in range(fromIndex,toIndex):
x = actionsList[i]
print(' \''+str(actions[x]['shortName'])+'\' |' , end=' ')
for g in criteriaList:
evalgx = evaluation[g][x]
if evalgx == NA:
print(' NA ', end=' ')
else:
print(formatString % (evalgx), end=' ')
print()
[docs]
def showHTMLPerformanceTableau(self,studentsSubset=None,isSorted=True,
Transposed=False,ndigits=0,
ContentCentered=True,title=None,
fromIndex=None,toIndex=None,
htmlFileName=None):
"""
shows the html version of the academic performance tableau in a browser window.
"""
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._htmlPerformanceTable(actions=studentsSubset,isSorted=isSorted,
Transposed=Transposed,
ndigits=ndigits,
ContentCentered=ContentCentered,
title=title,fromIndex=fromIndex,
toIndex=toIndex))
fo.close()
url = 'file://'+fileName
webbrowser.open(url,new=2)
def _htmlPerformanceTable(self,actions=None,criteria=None,isSorted=False,
Transposed=False,ndigits=0,
ContentCentered=True,
title=None,fromIndex=None,toIndex=None):
"""
Renders the performance table citerion x actions in html format.
"""
minMaxEvaluations = self.computeMinMaxEvaluations()
if title is None:
html = '<h1>Random Student Gradings</h1>'
else:
html = '<h1>%s</h1>' % title
# criteria
if criteria is None:
criteria = self.criteria
criteriaKeys = list(criteria.keys())
else:
criteriaKeys = [g for g in criteria]
if isSorted:
criteriaKeys.sort()
# actions
if actions is None:
actions = self.actions
actionsKeys = list(self.actions.keys())
else:
actionsKeys = [x for x in actions]
if isSorted:
actionsKeys.sort()
if fromIndex is None:
fromIndex = 0
if toIndex is None:
toIndex = len(actionsKeys)
# evaluations
evaluation = self.evaluation
NA = self.NA
if ContentCentered:
alignFormat = 'center'
else:
alignFormat = 'right'
if Transposed:
html += '<table style="background-color:White;" border="1">'
html += '<tr bgcolor="#9acd32"><th>Courses<br/>(ECTS)</th>'
for x in actionsKeys[fromIndex:toIndex]:
try:
xName = actions[x]['shortName']
except:
xName = str(x)
html += '<th bgcolor="#FFF79B">%s</th>' % (xName)
html += '</tr>'
for g in criteriaKeys:
try:
gName = '%s (%d)' % (criteria[g]['shortName'],criteria[g]['weight'])
except:
gName = '%s (%d)' % (g,int(criteria[g]['weight']))
html += '<tr><th bgcolor="#FFF79B">%s</th>' % (gName)
for x in actionsKeys[fromIndex:toIndex]:
if evaluation[g][x] != NA:
if evaluation[g][x] < Decimal('10'):
formatString = '<td bgcolor="#ffddff" align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
elif minMaxEvaluations[g]['minimum'] == minMaxEvaluations[g]['maximum']:
formatString = '<td align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
elif self.evaluation[g][x] == minMaxEvaluations[g]['maximum']:
formatString = '<td bgcolor="#ddffdd" align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
else:
formatString = '<td align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
else:
html += '<td align="center"><span style="color: LightGrey;font-size:75%; ">NA</span></td>'
html += '</tr>'
html += '</table>'
else:
html += '<table style="background-color:White;" border="1">'
html += '<tr bgcolor="#9acd32"><th>Courses</th>'
for g in criteriaKeys:
try:
gName = criteria[g]['shortName']
except:
gName = str(g)
html += '<th bgcolor="#FFF79B">%s</th>' % (gName)
html += '</tr>'
html += '<tr><th bgcolor="#9acd32" >ECTS</th>'
for g in criteriaKeys:
gweight = criteria[g]['weight']
html += '<td align="center" bgcolor="#FFF79B" ><i>%d</i></td>' % (int(gweight))
html += '</tr>'
for x in actionsKeys[fromIndex:toIndex]:
try:
xName = actions[x]['shortName']
except:
xName = str(x)
html += '<tr><th bgcolor="#FFF79B">%s</th>' % (xName)
for g in criteriaKeys:
if evaluation[g][x] != NA:
if evaluation[g][x] < Decimal('10'):
formatString = '<td bgcolor="#ffddff" align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
elif minMaxEvaluations[g]['minimum'] == minMaxEvaluations[g]['maximum']:
formatString = '<td align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
#elif self.evaluation[g][x] == minMaxEvaluations[g]['minimum']:
elif self.evaluation[g][x] == minMaxEvaluations[g]['maximum']:
formatString = '<td bgcolor="#ddffdd" align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
else:
formatString = '<td align="%s">%% .%df</td>' % (alignFormat,ndigits)
html += formatString % (evaluation[g][x])
else:
html += '<td align="center"><span style="color: LightGrey;font-size:75%;">NA</span></td>'
html += '</tr>'
html += '</table>'
return html
[docs]
def showCourseStatistics(self,courseID,Debug=False):
"""
show statistics concerning the grades' distributions in the given course.
"""
import math
criteriaKeys = [x for x in self.criteria]
criteriaKeys.sort()
nc = len(self.criteria)
evaluation = self.evaluation
NA = self.NA
actions = self.actions
g = courseID
n = len(actions)
print('*----- Summary performance statistics ------*')
print(' Course name :', g)
print(' Course weight :', self.criteria[g]['weight'])
print(' # Students :', n)
averageEvaluation = Decimal('0.0')
varianceEvaluation = Decimal('0.0')
Max = Decimal(str(self.criteria[g]['scale'][1]))
Min = Decimal(str(self.criteria[g]['scale'][0]))
minEvaluation = Max
maxEvaluation = Min
evaluationList = []
for x in actions:
if evaluation[g][x] != NA:
evaluationList.append(evaluation[g][x])
averageEvaluation += evaluation[g][x]
varianceEvaluation += evaluation[g][x]**Decimal('2')
if evaluation[g][x] < minEvaluation:
minEvaluation = evaluation[g][x]
if evaluation[g][x] > maxEvaluation:
maxEvaluation = evaluation[g][x]
evaluationList.sort()
na = len(evaluationList)
if Debug:
print(evaluationList)
try:
if self.criteria[g]['preferenceDirection'] == 'max':
print(' grading scale : %.2f - %.2f' % (Min, Max))
else:
print(' grading scale : %.2f - %.2f' % (-Max, Min))
except:
print(' grading scale : %.2f - %.2f' % (Min, Max))
print(' # missing evaluations : %d' % (n-na))
# !! index on evaluation List goes from 0 to na -1 !!
if na > 5:
rankQ1 = na / 4.0
lowRankQ1 = int(math.floor(rankQ1))
proportQ1 = Decimal(str(rankQ1 - lowRankQ1))
quantileQ1 = evaluationList[lowRankQ1] + (proportQ1 * (evaluationList[lowRankQ1+1]-evaluationList[lowRankQ1]) )
#print rankQ1, lowRankQ1, proportQ1
rankQ2 = na / 2.0
lowRankQ2 = int(math.floor(rankQ2))
proportQ2 = Decimal(str(rankQ2 - lowRankQ2))
quantileQ2 = evaluationList[lowRankQ2] + (proportQ2 * ( evaluationList[lowRankQ2+1] - evaluationList[lowRankQ2]) )
rankQ3 = (na * 3.0) / 4.0
lowRankQ3 = int(math.floor(rankQ3))
proportQ3 = Decimal(str(rankQ3 - lowRankQ3))
quantileQ3 = evaluationList[lowRankQ3] + ( proportQ3 * (evaluationList[lowRankQ3+1]-evaluationList[lowRankQ3]) )
averageEvaluation /= Decimal(str(na))
varianceEvaluation = varianceEvaluation/na - averageEvaluation**Decimal('2')
stdDevEvaluation = math.sqrt(varianceEvaluation)
print(' mean evaluation : %.2f' % (averageEvaluation))
print(' standard deviation : %.2f' % (stdDevEvaluation))
print(' maximal evaluation : %.2f' % (maxEvaluation))
print(' quantile Q3 (x_75) : %.2f' % (quantileQ3))
print(' median evaluation : %.2f' % (quantileQ2))
print(' quantile Q1 (x_25) : %.2f' % (quantileQ1))
print(' minimal evaluation : %.2f' % (minEvaluation))
averageAbsDiffEvaluation = Decimal('0.0')
varianceDiffEvaluation = Decimal('0.0')
nd = 0
for x in actions:
for y in actions:
if evaluation[g][x] != NA and evaluation[g][y] != NA:
diffxy = (evaluation[g][x] - evaluation[g][y])
averageAbsDiffEvaluation += abs(diffxy)
varianceDiffEvaluation += diffxy**Decimal('2')
nd += 1
# print ' Sum of evaluation differences = ', averageAbsDiffEvaluation
averageAbsDiffEvaluation /= Decimal(str(nd))
## averageDiffEvaluation == 0 per construction
varianceDiffEvaluation = varianceDiffEvaluation/Decimal(str(nd))
stdDevDiffEvaluation = math.sqrt(varianceDiffEvaluation)
print(' mean absolute difference : %.2f' % (averageAbsDiffEvaluation))
print(' standard difference deviation : %.2f' % (stdDevDiffEvaluation))
else:
print(' !! not enough evaluations !!')
[docs]
def showStatistics(self):
"""
Obsolete
"""
print('Obsolete ! use instaed self.showCourseStatistics(courseID)')
#-----------------
[docs]
class RandomRankPerformanceTableau(PerformanceTableau):
"""
For generating multiple-criteria ranked (without ties) performances of a given number of decision actions.
On each criterion, all decision actions are hence lineraly ordered.
The :py:class:`randomPerfTabs.RandomRankPerformanceTableau` class
is matching the :py:class:`votingDigraphs.RandomLinearVotingProfiles`
class (see http://hdl.handle.net/10993/37933 Lecture 2 : Voting of
the Algorithmic Decision Theory Course)
*Parameters*:
* number of actions,
* number of performance criteria,
* weightDistribution := equisignificant | random (default, see RandomPerformanceTableau)
* weightScale := (1, 1 | numberOfCriteria (default when random))
* IntegerWeights := Boolean (True = default). Weights are negative, as all the criteria preference directions are 'min', as the rank performance is to be minimized.
* commonThresholds (default) := {
| 'ind':(0,0),
| 'pref':(1,0),
| 'veto':(numberOfActions,0)
| } (default)
>>> t = RandomRankPerformanceTableau(numberOfActions=3,numberOfCriteria=2)
>>> t.showObjectives()
The performance tableau does not contain objectives.
>>> t.showCriteria()
*---- criteria -----*
g1 'Random criteria (voter)'
Scale = (Decimal('0'), Decimal('3'))
Weight = -1 # ranks to be minimal
Threshold ind : 0.00 + 0.00x ; percentile: 0.0
Threshold pref : 1.00 + 0.00x ; percentile: 0.667
Threshold veto : 3.00 + 0.00x ; percentile: 1.0
g2 'Random criteria (voter)'
Scale = (Decimal('0'), Decimal('3'))
Weight = -1 # ranks to be minimal
Threshold ind : 0.00 + 0.00x ; percentile: 0.0
Threshold pref : 1.00 + 0.00x ; percentile: 0.667
Threshold veto : 3.00 + 0.00x ; percentile: 1.0
>>> t.showActions()
*----- show decision action --------------*
key: a1
short name: a1
name: random decision action (candidate)
comment: RandomRankPerformanceTableau() generated.
key: a2
short name: a2
name: random decision action (candidate)
comment: RandomRankPerformanceTableau() generated.
key: a3
short name: a3
name: random decision action (candidate)
comment: RandomRankPerformanceTableau() generated.
>>> t.showPerformanceTableau()
*---- performance tableau -----*
criteria | weights | 'a1' 'a2' 'a3'
---------|--------------------------
'g1' | -1 | 3 1 2
'g2' | -1 | 2 1 3
"""
def __init__(self,numberOfActions = 13, numberOfCriteria = 7,
weightDistribution = 'equisignificant', weightScale=None,
commonThresholds = None, IntegerWeights=True,
BigData=False,
seed = None,
Debug = False):
"""
Constructor of random ranks performance tableaux.
"""
# set random seed
self.randomSeed = seed
import random
random.seed(seed)
# set name
self.name = 'randrankperftab'
# generate actions
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(numberOfActions):
if BigData:
actionName = ('a%%0%dd' % (nd)) % (i+1)
actions[i] = {'name': actionName}
else:
actionKey = ('a%%0%dd' % (nd)) % (i+1)
actions[actionKey] = {'shortName':actionKey,
'name': 'random decision actions (candidate)',
'comment': 'Random Ranks' }
# generate the criteria weights
if weightScale is None:
weightScale = (1,numberOfCriteria)
if weightDistribution == 'random':
weightsList = []
sumWeights = Decimal('0.0')
i = 0
for i in range(numberOfCriteria):
weightsList.append(Decimal(str(random.randint(weightScale[0],weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'equisignificant':
weightScale = (1,1)
weightsList = [Decimal(str(weightScale[0])) for i in range(numberOfCriteria)]
sumWeights = sum(weightsList)
else:
print('!!! Error: wrong criteria weight distribution mode: %s !!!!' % (weightDistribution))
# generate criteria dictionary
ngd = len(str(numberOfCriteria))
criteria = OrderedDict()
commentString = 'Arguments: '
commentString += '; weightDistribution='+str(weightDistribution)
commentString += '; weightScale='+str(weightScale)
commentString += '; IntegerWeights='+str(IntegerWeights)
commentString += '; commonThresholds='+str(commonThresholds)
for i in range(numberOfCriteria):
g = ('g%%0%dd' % ngd) % (i+1)
criteria[g] = {}
criteria[g]['name']='Random criteria (voter)'
criteria[g]['comment']=commentString
criteria[g]['preferenceDirection'] = 'min'
try:
indThreshold =(Decimal(str(commonThresholds['ind'][0])),
Decimal(str(commonThresholds['ind'][1])))
prefThreshold =(Decimal(str(commonThresholds['pref'][0])),
Decimal(str(commonThresholds['pref'][1])))
vetoThreshold =(Decimal(str(commonThresholds['veto'][0])),
Decimal(str(commonThresholds['veto'][1])))
criteria[g]['thresholds'] = {'ind':indThreshold,
'pref':prefThreshold,
'veto':vetoThreshold}
except:
indThreshold = ( Decimal("0"), Decimal("0") )
prefThreshold = ( Decimal("1"), Decimal("0") )
vetoThreshold = ( Decimal(str(numberOfActions)), Decimal("0") )
criteria[g]['thresholds'] = { 'ind':indThreshold,
'pref':prefThreshold,
'veto': vetoThreshold
}
commonScale = ( Decimal("0"), Decimal(numberOfActions) )
criteria[g]['scale'] = commonScale
# weights are negative
if IntegerWeights:
criteria[g]['weight'] = -weightsList[i]
else:
criteria[g]['weight'] = -weightsList[i]/sumWeights
# generate evaluations
evaluation = {}
for g in criteria:
evaluation[g] = {}
choiceRange = list(range(1,numberOfActions+1))
for a in actions:
randeval = random.choice(choiceRange)
evaluation[g][a] = Decimal( str(randeval) )
choiceRange.remove(randeval)
# install object items
self.actions = actions
self.criteria = criteria
self.evaluation = evaluation
self.NA = Decimal('-999')
self.sumWeights = sumWeights
self.weightPreorder = self.computeWeightPreorder()
# ------------------------------
class _FullRandomPerformanceTableau(PerformanceTableau):
"""
Full automatic generation of random performance tableaux
"""
def __init__(self,numberOfActions = None,
numberOfCriteria = None,
weightDistribution = None,
weightScale=None,
IntegerWeights = True,
commonScale = None,
commonThresholds = None,
commonMode = None,
valueDigits=2,
seed = None,
Debug = False):
# import OrderedDict container
from collections import OrderedDict
# set name
self.name = 'fullrandomperftab'
# set random seaad
self.randomSeed = seed
import random
random.seed(seed)
# generate random actions
if numberOfActions is None:
numberOfActions = random.randint(7,30)
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(numberOfActions):
actionKey = ('a%%0%dd' % (nd)) % (i+1)
actions[actionKey] = {'shortName':actionKey,
'name': 'random decision action',
'comment': 'RandomRankPerformanceTableau() generated.' }
self.actions = actions
actionsList = [x for x in actions.keys()]
# generate criterialist
if numberOfCriteria is None:
numberOfCriteria = random.randint(5,21)
ng = len(str(numberOfCriteria))
criteriaList = [('g%%0%dd' % ng) % (i+1) \
for i in range(numberOfCriteria)]
criteriaList.sort()
# generate random weights
if weightDistribution is None:
majorityWeight = numberOfCriteria + 1
weightModesList = [('fixed',[1,1],1),
('random',[1,3],2),
('random',[1,numberOfCriteria],3)]
weightMode = random.choice(weightModesList)
weightDistribution = weightMode[0]
weightScale = weightMode[1]
else:
if weightScale is None:
weightScale = (1,numberOfCriteria)
weightMode=[weightDistribution,weightScale]
if weightDistribution == 'random':
weightsList = []
sumWeights = 0.0
for i in range(len(criteriaList)):
g = criteriaList[i]
weightsList.append(random.randint(weightScale[0],weightScale[1]))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'fixed':
weightsList = []
sumWeights = 0.0
for i in range(len(criteriaList)):
if i == 0:
weightsList.append(weightScale[1])
sumWeights += weightScale[1]
else:
weightsList.append(weightScale[0])
sumWeights += weightScale[0]
weightsList.reverse()
elif weightDistribution == 'equisignificant' or weightDistribution == 'equiobjectives':
weightScale = (1,1)
weightsList = []
sumWeights = 0.0
for i in range(len(criteriaList)):
if i == 0:
weightsList.append(weightScale[1])
sumWeights += weightScale[1]
else:
weightsList.append(weightScale[0])
sumWeights += weightScale[0]
weightsList.reverse()
else:
print('!!! Error: wrong criteria weight distribution mode: %s !!!!' % (weightDistribution))
# generate criteria dictionary with random thresholds
if commonScale is None:
commonScale = [0.0,100.0]
criteria = OrderedDict()
for i in range(len(criteriaList)):
g = criteriaList[i]
criteria[g] = {}
criteria[g]['name'] = 'random criterion'
#t = time.time()
if commonThresholds is None:
thresholds = []
thresholds.append((round(random.uniform(0.0,commonScale[1]/5.0),valueDigits),0.0))
thresholds.append((round(random.uniform(thresholds[0][0],commonScale[1]/3.0),valueDigits),0.0))
thresholds.append((round(random.uniform(commonScale[1]*2.0/3.0,commonScale[1]),valueDigits),0.0))
thresholds.append((round(random.uniform(thresholds[2][0],commonScale[1]),valueDigits),0.0))
else:
thresholds = commonThresholds
## print thresholds
try:
criteria[g]['thresholds'] = {
'ind':(Decimal(str(thresholds[0][0])),Decimal(str(thresholds[0][1]))),
'pref':(Decimal(str(thresholds[1][0])),Decimal(str(thresholds[1][1]))),
'weakVeto':(Decimal(str(thresholds[2][0])),Decimal(str(thresholds[2][1]))),
'veto':(Decimal(str(thresholds[3][0])),Decimal(str(thresholds[3][1]))),
}
except:
criteria[g]['thresholds'] = {
'ind':(Decimal(str(thresholds[0][0])),Decimal(str(thresholds[0][1]))),
'pref':(Decimal(str(thresholds[1][0])),Decimal(str(thresholds[1][1]))),
'veto':(Decimal(str(thresholds[2][0])),Decimal(str(thresholds[2][1]))),
}
criteria[g]['scale'] = commonScale
if IntegerWeights:
criteria[g]['weight'] = Decimal(str(weightsList[i]))
else:
criteria[g]['weight'] = Decimal(str(weightsList[i]))/Decimal(str(sumWeights))
# generate random evaluations
x30=commonScale[1]*0.3
x50=commonScale[1]*0.5
x70=commonScale[1]*0.7
randomLawsList = [['uniform',commonScale[0],commonScale[1]],
('triangular',x30,0.33),('triangular',x30,0.50),('triangular',x30,0.75),
('triangular',x50,0.33),('triangular',x50,0.50),('triangular',x50,0.75),
('triangular',x70,0.33),('triangular',x70,0.50),('triangular',x70,0.75),
('normal',x30,20.0),('normal',x30,25.0),('normal',x30,30.0),
('normal',x50,20.0),('normal',x50,25.0),('normal',x50,30.0),
('normal',x70,20.0),('normal',x70,25.0),('normal',x70,30.0)]
evaluation = {}
for i in range(len(criteriaList)):
g = criteriaList[i]
evaluation[g] = {}
if commonMode is None:
randomMode = random.choice(randomLawsList)
else:
randomMode = commonMode
if randomMode[0] == 'uniform':
randomMode[1] = commonScale[0]
randomMode[2] = commonScale[1]
criteria[g]['randomMode'] = randomMode
if randomMode[0] != 'beta':
if randomMode[1] is None and randomMode[2] is None:
commentString = randomMode[0] + ', default, default'
elif randomMode[1] is not None and randomMode[2] is None:
commentString = randomMode[0]+', %.2f, default' % float(randomMode[1])
elif randomMode[1] is None and randomMode[2] is not None:
commentString = randomMode[0]+', default, %.2f' % (float(randomMode[2]))
else:
commentString = randomMode[0]+', %.2f, %.2f' % (float(randomMode[1]),float(randomMode[2]))
else:
if randomMode[1] is not None and randomMode[2] is not None:
commentString = randomMode[0]+', %.2f, (%.4f,%.4f)' % (float(randomMode[1]),float(randomMode[2][0]),float(randomMode[2][1]))
elif randomMode[1] is None and randomMode[2] is not None:
commentString = randomMode[0]+', default, (%.4f,%.4f)' % (float(randomMode[2][0]),float(randomMode[2][1]))
if randomMode[1] is not None and randomMode[2] is None:
commentString = randomMode[0]+', %.2f, default' % (float(randomMode[1]))
else:
commentString = randomMode[0]+', default,default'
criteria[g]['comment'] = 'Evaluation generator: '+commentString
digits = valueDigits
if str(randomMode[0]) == 'uniform':
evaluation[g] = {}
for a in actionsList:
randeval = random.uniform(commonScale[0],commonScale[1])
evaluation[g][a] = Decimal(str(round(randeval,digits)))
elif str(randomMode[0]) == 'triangular':
from randomNumbers import ExtendedTriangularRandomVariable as RNG
m = commonScale[0]
M = commonScale[1]
try:
if commonMode[1] is None:
xm = (M-m)/2.0
else:
xm = commonMode[1]
except:
xm = (M-m)/2.0
try:
if commonMode[2] is None:
r = 0.5
else:
r = commonMode[2]
except:
r = 0.5
rdseed = random.random()
rng = RNG(m,M,xm,r,seed=rdseed)
for a in actionsList:
## u = random.random()
## if u < r:
## randeval = m + math.sqrt(u/r)*(xm-m)
## else:
## randeval = M - math.sqrt((1-u)/(1-r))*(M-xm)
## evaluation[g][a] = Decimal(str(round(randeval,digits)))
evaluation[g][a] = Decimal(str(round(rng.random(),digits)))
elif str(randomMode[0]) == 'normal':
try:
if commonMode[1] is None:
mu = (commonScale[1]-commonScale[0])/2.0
else:
mu = commonMode[1]
except:
mu = (commonScale[1]-commonScale[0])/2.0
try:
if commonMode[2] is None:
sigma = (commonScale[1]-commonScale[0])/4.0
else:
sigma = commonMode[2]
except:
sigma = (commonScale[1]-commonScale[0])/4.0
for a in actionsList:
notfound = True
while notfound:
randeval = random.normalvariate(mu,sigma)
if randeval >= commonScale[0] and randeval <= commonScale[1]:
notfound = False
evaluation[g][a] = Decimal(str(round(randeval,digits)))
elif str(randomMode[0]) == 'beta':
m = commonScale[0]
M = commonScale[1]
if commonMode[1] is None:
xm = 0.5
else:
xm = commonMode[1]
if commonMode[2] is None:
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1.0 - xm)
else:
alpha = 2.0
beta = 1.0/xm
else:
alpha = commonMode[2][0]
beta = commonMode[2][1]
if Debug:
print('alpha,beta', alpha,beta)
for a in actionsList:
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
evaluation[g][a] = Decimal(str(round(randeval,digits)))
if Debug:
print('xm,alpha,beta,u,m,M,randeval',xm,alpha,beta,u,m,M,randeval)
# install self object attributes
self.criteriaWeightMode = weightMode
self.criteria = criteria
self.evaluation = evaluation
self.NA = Decimal('-999')
self.weightPreorder = self.computeWeightPreorder()
def showAll(self):
"""
Show fonction for performance tableau of full random outranking digraph.
"""
criteria = self.criteria
evaluation = self.evaluation
print('*-------- show performance tableau -------*')
print('Name :', self.name)
print('Actions :', self.actions)
print('Criteria :')
for g in criteria:
print(' criterion name:', g, end=' ')
print(', scale: ', criteria[g]['scale'], end=' ')
print(', weight: %.3f ' % (criteria[g]['weight']))
print(' thresholds:', criteria[g]['thresholds'])
print(' evaluations generation mode: ', criteria[g]['randomMode'])
print()
print(' Weights generation mode: ', self.criteriaWeightMode)
print(' Weights preorder : ', self.weightPreorder)
print('Evaluations :')
for g in evaluation:
print(g, evaluation[g])
class _RandomCoalitionsPerformanceTableau(PerformanceTableau):
"""
Full automatic generation of performance tableaux with random coalitions of criteria
Parameters:
| numberOf Actions := 20 (default)
| number of Criteria := 13 (default)
| weightDistribution := 'equisignificant' (default with all weights = 1.0), 'random', 'fixed' (default w_1 = numberOfCriteria-1, w_{i!=1} = 1
| weightScale := [1,numerOfCriteria] (random default), [w_1, w_{i!=1] (fixed)
| IntegerWeights := True (default) / False
| commonScale := (0.0, 100.0) (default)
| commonThresholds := [(1.0,0.0),(2.001,0.0),(8.001,0.0)] if OrdinalSacles, [(0.10001*span,0),(0.20001*span,0.0),(0.80001*span,0.0)] with span = commonScale[1] - commonScale[0].
| commonMode := ['triangular',50.0,0.50] (default), ['uniform',None,None], ['beta', None,None] (three alpha, beta combinations (5.8661,2.62203) chosen by default for high('+'), medium ('~') and low ('-') evaluations.
| valueDigits := 2 (default, for cardinal scales only)
| Coalitions := True (default)/False, three coalitions if True
| VariableGenerators := True (default) / False, variable high('+'), medium ('~') or low ('-') law generated evaluations.
| OrdinalScales := True / False (default)
| Debug := True / False (default)
| RandomCoalitions = True / False (default) zero or more than three coalitions if Coalitions == False.
| vetoProbability := x in ]0.0-1.0[ / None (default), probability that a cardinal criterion shows a veto preference discrimination threshold.
| Electre3 := True (default) / False, no weakveto if True (obsolete)
"""
def __init__(self,numberOfActions = None, numberOfCriteria = None,
weightDistribution = None, weightScale=None,
IntegerWeights = True, commonScale = None,
commonThresholds = None, commonMode = None,
valueDigits=2, Coalitions=True, VariableGenerators=True,
OrdinalScales=False, Debug=False, RandomCoalitions=False,
vetoProbability=None,
BigData=False,
seed= None,
Electre3=True):
# naming
self.name = 'randomCoalitionsPerfTab'
# randomizer init
self.randomSeed = seed
import random
random.seed(seed)
if RandomCoalitions:
Coalitions=False
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
from collections import OrderedDict
from operator import itemgetter
# generate actions
if numberOfActions is None:
numberOfActions = 13
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(numberOfActions):
actionKey = ('a%%0%dd' % (nd)) % (i+1)
if BigData:
actions[i] = {'shortName':actionKey,
'name': actionKey,
'generators': {}}
else:
actions[actionKey] = {'shortName':actionKey,
'name': 'random decision action',
'comment': 'RandomCoalitionsPerformanceTableau() generated.',
'generators': {}}
self.actions = actions
actionsList = [x for x in actions.keys()]
#actionsList.sort()
# generate criterialist
if numberOfCriteria is None:
numberOfCriteria = 7
ng = len(str(numberOfCriteria))
criteriaList = [('g%%0%dd' % ng) % (i+1) \
for i in range(numberOfCriteria)]
#criteriaList.sort()
# generate random weights
if weightDistribution is None:
## majorityWeight = numberOfCriteria + 1
## #weightModesList = [('fixed',[1,1],1),('random',[1,3],2), ('random',[1,numberOfCriteria],3),('fixed',[1,majorityWeight],4)]
## weightModesList = [('fixed',[1,1],1),('random',[1,3],2), ('random',[1,numberOfCriteria],3)]
## weightMode = random.choice(weightModesList)
weightMode = ('equisignificant',(1,1))
weightDistribution = weightMode[0]
weightScale = weightMode[1]
else:
weightMode=[weightDistribution,weightScale]
if weightDistribution == 'random':
if weightScale is None:
weightScale = (1,numberOfCriteria)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(len(criteriaList)):
weightsList.append(Decimal(str(random.randint(weightScale[0],
weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'fixed':
if weightScale is None:
weightScale = (1,numberOfCriteria)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(len(criteriaList)):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
elif weightDistribution == 'equisignificant' \
or weightDistribution == 'equiobjectives' \
or weightDistribution == 'equicoalitions':
weightScale = (1,1)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(len(criteriaList)):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
else:
print('!!! Error: wrong criteria weight distribution mode: %s !!!!' \
% (weightDistribution))
# generate criteria dictionary with random thresholds
if commonScale is None:
commonScale = (0.0,100.0)
if Coalitions:
criterionCoalitionsList=[('A',None),('B',None),('C',None)]
elif RandomCoalitions:
bin = {}
nbrcrit = len(criteriaList)
for i in range(nbrcrit):
bin[i] = set()
for i in range(nbrcrit):
p = random.randint(0,nbrcrit-1)
bin[p].add(criteriaList[i])
partition = []
for i in range(nbrcrit):
ni = len(bin[i])
if ni > 0:
partition.append((ni,bin[i]))
partition.sort(reverse=True,key=itemgetter(0))
criterionCoalitionsList = []
for i in range(len(partition)):
criterionCoalitionsList.append((chr(ord("A")+i),partition[i][1]))
Coalitions = True
if Debug:
print(criterionCoalitionsList)
criteria = OrderedDict()
for gi in range(len(criteriaList)):
g = criteriaList[gi]
criteria[g] = {}
if RandomCoalitions:
for criterionCoalition in criterionCoalitionsList:
if g in criterionCoalition[1]:
criteria[g]['coalition'] = criterionCoalition[0]
if Debug:
print('==>>>', criteria[g]['coalition'])
elif Coalitions:
criterionCoalition = random.choice(criterionCoalitionsList)
criteria[g]['coalition'] = criterionCoalition[0]
criteria[g]['preferenceDirection'] = 'max'
if Coalitions:
criteria[g]['name'] = 'random criterion of coalition %s' \
% (criteria[g]['coalition'])
else:
criteria[g]['name'] = 'random criterion'
if commonThresholds is None:
## thresholds = []
## thresholds.append((round(random.uniform(0.0,commonScale[1]/5.0),valueDigits),0.0))
## thresholds.append((round(random.uniform(thresholds[0][0],commonScale[1]/3.0),valueDigits),0.0))
## thresholds.append((round(random.uniform(commonScale[1]*2.0/3.0,commonScale[1]),valueDigits),0.0))
## thresholds.append((round(random.uniform(thresholds[2][0],commonScale[1]),valueDigits),0.0))
if OrdinalScales:
thresholds = [(1.0,0.0),(2.001,0.0),(8.001,0.0)]
else:
span = commonScale[1] - commonScale[0]
thresholds = [(0.05001*span,0),(0.10001*span,0.0),(0.60001*span,0.0)]
else:
thresholds = commonThresholds
## print thresholds
if Electre3:
thitems = ['ind','pref','veto']
else:
thitems = ['ind','pref','weakVeto','veto']
if vetoProbability is not None:
randVeto = random.uniform(0.0,1.0)
if randVeto > vetoProbability:
thitems = ['ind','pref']
criteria[g]['thresholds'] = {}
for t in range(len(thitems)):
criteria[g]['thresholds'][thitems[t]] \
= (Decimal(str(thresholds[t][0])),Decimal(str(thresholds[t][1])))
criteria[g]['scale'] = commonScale
if IntegerWeights:
criteria[g]['weight'] = weightsList[gi]
else:
criteria[g]['weight'] = weightsList[gi] / sumWeights
# determine equisignificant coalitions
coalitionsCardinality = {}
for gi in range(len(criteriaList)):
g = criteriaList[gi]
try:
coalitionsCardinality[criteria[g]['coalition']] += 1
except:
coalitionsCardinality[criteria[g]['coalition']] = 1
if Debug:
print(coalitionsCardinality)
weightsProduct = 1
for coalition in coalitionsCardinality:
weightsProduct *= coalitionsCardinality[coalition]
if Debug:
print(weightsProduct)
if weightDistribution == 'equicoalitions':
for gi in range(len(criteriaList)):
g = criteriaList[gi]
criteria[g]['weight'] \
= weightsProduct // coalitionsCardinality[criteria[g]['coalition']]
if Debug:
print(criteria[g]['weight'])
# allocate (criterion,action) to coalition supporting type
if Coalitions and VariableGenerators:
coalitionSupportingType = ['+','~','-']
for x in actionsList:
for c in criterionCoalitionsList:
if Debug:
print(criterionCoalitionsList,c)
self.actions[x][str(c[0])]=random.choice(coalitionSupportingType)
self.actions[x]['name'] \
= self.actions[x]['name'] + ' '+ str(c[0]) \
+ str(self.actions[x][str(c[0])])
# generate evaluations
evaluation = {}
for gi in range(len(criteriaList)):
g = criteriaList[gi]
evaluation[g] = {}
if commonMode is None:
#randomMode = random.choice(randomLawsList)
randomMode = ['triangular',50.0,0.50]
else:
randomMode = commonMode
if randomMode[0] == 'uniform':
randomMode[1] = commonScale[0]
randomMode[2] = commonScale[1]
criteria[g]['randomMode'] = randomMode
if Coalitions:
commentString = 'Variable '+randomMode[0]+(' performance generator with random low (-), medium (~) or high (+) parameters.')
else:
if randomMode[0] == 'triangular':
if VariableGenerators:
commentString = 'triangular law with variable mode (m) and probability repartition (p).'
else:
commentString = 'triangular law with constant mode (m) and probability repartition (p).'
else:
if VariableGenerators:
commentString = 'Variable '+randomMode[0]+(' law with randomly chosen low, medium or high parameters.')
## elif Coalitions:
## commentString = 'Coalition : %s ' % (criteria[g]['coalition'])
else:
commentString = 'Constant '+randomMode[0]+(' law with parameters = %s, %s' % (str(randomMode[1]),str(randomMode[2])))
criteria[g]['comment'] = commentString
digits = valueDigits
if str(randomMode[0]) == 'uniform':
for a in actionsList:
if VariableGenerators:
randomRangesList = [(commonScale[0],commonScale[1]),
(commonScale[0],commonScale[0]+0.3*(commonScale[1]-commonScale[0])),
(commonScale[0],commonScale[0]+0.7*(commonScale[1]-commonScale[0]))]
randomRange = random.choice(randomRangesList)
randeval = random.uniform(randomRange[0],randomRange[1])
else:
randomRange = (randomMode[1],randomMode[2])
randeval = random.uniform(randomMode[1],randomMode[2])
self.actions[a]['generators'][g] = (randomMode[0],randomRange)
if OrdinalScales:
randeval /= 10.0
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
elif str(randomMode[0]) == 'beta':
for a in actionsList:
m = commonScale[0]
M = commonScale[1]
if Coalitions:
if self.actions[a][criteria[g]['coalition']] == '+':
# mode = 75, stdev = 15
#xm = 75
alpha = 5.8661
beta = 2.62203
elif self.actions[a][criteria[g]['coalition']] == '~':
# nmode = 50, stdev = 15
#xm = 50
alpha = 5.05556
beta = 5.05556
elif self.actions[a][criteria[g]['coalition']] == '-':
# mode = 25, stdev = 15
# xm = 25
alpha = 2.62203
beta = 5.8661
else:
if VariableGenerators:
randomModesList = [0.3,0.5,0.7]
xm = random.choice(randomModesList)
else:
xm = randomMode[1]
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0 / xm
if Debug:
print('alpha,beta', alpha,beta)
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
if Debug:
print('xm,alpha,beta,u,m,M,randeval',xm,alpha,beta,u,m,M,randeval)
self.actions[a]['generators'][g] = ('beta',alpha,beta)
if OrdinalScales:
randeval /= 10.0
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
elif str(randomMode[0]) == 'triangular':
for a in actionsList:
m = commonScale[0]
M = commonScale[1]
if VariableGenerators:
span = commonScale[1]-commonScale[0]
randomModesList = [0.3*span,0.5*span,0.7*span]
xm = random.choice(randomModesList)
else:
xm = randomMode[1]
r = randomMode[2]
self.actions[a]['generators'][g] = (randomMode[0],xm,r)
# setting a speudo random seed
rdseed = random.random()
rngtr = RNGTr(m,M,xm,r,seed=rdseed)
randeval = rngtr.random()
if OrdinalScales:
randeval /= 10.0
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#print randeval, criteria[g]['preferenceDirection'], evaluation[g][a]
## elif str(randomMode[0]) == 'normal':
## mu = randomMode[1]
## sigma = randomMode[2]
## for a in actionsList:
## notfound = True
## while notfound:
## randeval = random.normalvariate(mu,sigma)
## if randeval >= commonScale[0] and randeval <= commonScale[1]:
## notfound = False
## evaluation[g][a] = Decimal(str(round(randeval,digits)))
# install self object attributes
self.criteriaWeightMode = weightMode
self.criteria = criteria
self.evaluation = evaluation
self.NA = Decimal('-999')
self.weightPreorder = self.computeWeightPreorder()
[docs]
class Random3ObjectivesPerformanceTableau(PerformanceTableau):
"""
For geneating random 3 objectives: *Eco*, *Soc* and *Env* multile-criteria performance records. Each decision action is qualified randomly as weak (-), fair (~) or good (+) on each of the three objectives.
Generator arguments:
* numberOf Actions := 20 (default)
* shortNamePrefix := 'a' (default)
* number of Criteria := 13 (default)
* weightDistribution := 'equiobjectives' (default)
| 'equisignificant' (weights set all to 1)
| 'random' (in the range 1 to numberOfCriteria)
* weightScale := [1,numerOfCriteria] (random default)
* IntegerWeights := True (default) / False
* OrdinalScales := True / False (default), if True commonScale is set to (0,10)
* NegativeWeights := True (default) / False. If False, evaluations to be minimized are negative.
* negativeWeightProbability := [0,1] (default 0.10), 'min' preference direction probability
* commonScale := (Min, Max)
| when commonScale is None, (Min=0.0,Max=10.0) by default if OrdinalScales == True and (Min=0.0,Max=100.0) by default otherwise
* commonThresholds := ((Ind,Ind_slope),(Pref,Pref_slope),(Veto,Veto_slope)) with
| Ind < Pref < Veto in [0.0,100.0] such that
| (Ind/100.0*span + Ind_slope*x) < (Pref/100.0*span + Pref_slope*x) < (Pref/100.0*span + Pref_slope*x)
| By default [(0.05*span,0.0),(0.10*span,0.0),(0.60*span,0.0)] if OrdinalScales=False
| By default [(0.1*span,0.0),(0.2*span,0.0),(0.8*span,0.0)] otherwise
| with span = commonScale[1] - commonScale[0].
* commonMode := ['triangular','variable',0.50] (default), A constant mode may be provided.
| ['uniform','variable',None], a constant range may be provided.
| ['beta','variable',None] (three alpha, beta combinations:
| (5.8661,2.62203),(5.05556,5.05556) and (2.62203, 5.8661)
| chosen by default for 'good', 'fair' and 'weak' evaluations.
| Constant parameters may be provided.
* valueDigits := 2 (default)
* vetoProbability := x in ]0.0-1.0[ (0.5 default), probability that a cardinal criterion shows a veto preference discrimination threshold.
* Debug := True / False (default)
.. warning::
Minimal number required of criteria is 3!
>>> from randomPerfTabs import Random3ObjectivesPerformanceTableau
>>> t = Random3ObjectivesPerformanceTableau(numberOfActions=5,numberOfCriteria=3,seed=1)
>>> t
*------- PerformanceTableau instance description ------*
Instance class : Random3ObjectivesPerformanceTableau
Seed : 1
Instance name : random3ObjectivesPerfTab
# Actions : 5
# Objectives : 3
# Criteria : 3
Attributes : ['name', 'valueDigits', 'BigData', 'OrdinalScales',
'missingDataProbability', 'negativeWeightProbability',
'randomSeed', 'sumWeights', 'valuationPrecision',
'commonScale', 'objectiveSupportingTypes',
'actions', 'objectives', 'criteriaWeightMode',
'criteria', 'evaluation', 'weightPreorder']
>>> t.showObjectives()
*------ show objectives -------"
Eco: Economical aspect
ec1 criterion of objective Eco 1
Total weight: 1.00 (1 criteria)
Soc: Societal aspect
so2 criterion of objective Soc 1
Total weight: 1.00 (1 criteria)
Env: Environmental aspect
en3 criterion of objective Env 1
Total weight: 1.00 (1 criteria)
>>> t.showActions()
*----- show decision action --------------*
key: p1
short name: p1
name: random public policy Eco+ Soc- Env+
profile: {'Eco': 'good', 'Soc': 'weak', 'Env': 'good'}
key: p2
short name: p2
name: random public policy Eco~ Soc+ Env~
profile: {'Eco': 'fair', 'Soc': 'good', 'Env': 'fair'}
key: p3
short name: p3
name: random public policy Eco~ Soc~ Env-
profile: {'Eco': 'fair', 'Soc': 'fair', 'Env': 'weak'}
key: p4
short name: p4
name: random public policy Eco~ Soc+ Env+
profile: {'Eco': 'fair', 'Soc': 'good', 'Env': 'good'}
key: p5
short name: p5
name: random public policy Eco~ Soc+ Env~
profile: {'Eco': 'fair', 'Soc': 'good', 'Env': 'fair'}
>>> t.showPerformanceTableau()
*---- performance tableau -----*
criteria | weights | 'p1' 'p2' 'p3' 'p4' 'p5'
---------|---------------------------------------------
'ec1' | 1 | 36.29 85.17 34.49 NA 56.58
'so2' | 1 | 55.00 56.33 NA 67.36 72.22
'en3' | 1 | 66.58 48.71 21.59 NA NA
>>>
"""
def __init__(self,numberOfActions=20,shortNamePrefix='p',numberOfCriteria=13,
weightDistribution='equiobjectives',weightScale=None,
IntegerWeights=True,OrdinalScales=False,
NegativeWeights=False,negativeWeightProbability=0.0,
commonScale=None,commonThresholds=None,commonMode=None,
valueDigits=2,
vetoProbability=0.5,
missingDataProbability = 0.05,
NA = -999,
BigData=False,
seed=None,
Debug=False):
# naming
self.name = 'random3ObjectivesPerfTab'
self.valueDigits = valueDigits
self.BigData = BigData
self.OrdinalScales = OrdinalScales
self.missingDataProbability = missingDataProbability
self.negativeWeightProbability = negativeWeightProbability
# randomizer init
self.randomSeed = seed
from random import Random
_random = Random(seed)
_random1 = Random(seed)
_random2 = Random(seed)
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
# generate actions
nd = len(str(numberOfActions))
actions = OrderedDict()
for i in range(1, numberOfActions+1):
actionKey = shortNamePrefix+('%%0%dd' % (nd)) % (i)
if BigData:
actions[i] = {'name': actionKey,'generators': {}}
else:
actions[actionKey] = {'shortName': '%s' % (actionKey),
'name': 'action %s' % actionKey,
'comment': 'random public policy',
'generators': {}}
## self.actions = actions
## actionsList = [x for x in self.actions]
## actionsList.sort()
# generate random weights
if weightDistribution == 'equisignificant':
weightMode = ('equisignificant',(1,1))
weightScale = weightMode[1]
weightsList = [weightScale[0] for i in range(numberOfCriteria)]
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
sumWeights += Decimal(str(weightScale[0]))
elif weightDistribution == 'random':
weightMode = ('random',(1,numberOfCriteria))
if weightScale is None:
weightScale = (1,numberOfCriteria)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
weightsList.append(Decimal(str(_random.randint(weightScale[0],
weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
else:
weightDistribution = 'equiobjectives'
weightMode = (weightDistribution,None)
weightScale = (1,1)
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
# store sum of weights and precision level
self.sumWeights = sumWeights
self.valuationPrecision = Decimal('0.1')/sumWeights
# generate objectives dictionary
objectives = OrderedDict([(
'Eco', {'name':'Economical aspect',
'comment': 'Random3ObjectivesPerformanceTableau generated'}),
('Soc', {'name': 'Societal aspect',
'comment': 'Random3ObjectivesPerformanceTableau generated'}),
('Env',{'name':'Environmental aspect',
'comment': 'Random3ObjectivesPerformanceTableau generated'})
])
# generate criteria dictionary with random thresholds
ng = len(str(numberOfCriteria))
if commonScale is None:
if OrdinalScales:
commonScale = (0,10)
else:
commonScale = (0.0,100.0)
self.commonScale = commonScale
criteria = OrderedDict()
objectivesKeys = [key for key in objectives]
#objectivesKeys.sort()
#randChoice1 = Random(seed)
ng = len(str(numberOfCriteria))
for i in range(numberOfCriteria):
if i == 0:
criterionObjective = 'Eco'
elif i == 1:
criterionObjective = 'Soc'
elif i == 2:
criterionObjective = 'Env'
else:
#criterionObjective = _random.choice(objectivesKeys)
objInd = _random1.randint(1,len(objectivesKeys))
criterionObjective = objectivesKeys[objInd-1]
if criterionObjective == 'Eco':
g = ('ec%%0%dd' % ng) % (i+1)
elif criterionObjective == 'Soc':
g = ('so%%0%dd' % ng) % (i+1)
else:
g = ('en%%0%dd' % ng) % (i+1)
criteria[g] = {}
#print(g,criterionObjective,objectivesKeys)
criteria[g]['objective'] = criterionObjective
if _random.random() > negativeWeightProbability:
criteria[g]['preferenceDirection'] = 'max'
else:
criteria[g]['preferenceDirection'] = 'min'
criteria[g]['name'] = 'criterion of objective %s' % (criterionObjective)
criteria[g]['shortName'] = g
#criteria[g]['shortName'] = g + criterionObjective[0:2]
span = commonScale[1] - commonScale[0]
if commonThresholds is None:
if OrdinalScales:
thresholds = [(0.1001*span,0.0),(0.2001*span,0.0),(0.8001*span,0.0)]
else:
#span = commonScale[1] - commonScale[0]
thresholds = [(0.05001*span,0.0),(0.10001*span,0.0),(0.60001*span,0.0)]
else:
#span = commonScale[1] - commonScale[0]
thresholds = [(commonThresholds[0][0]/100.0*span,commonThresholds[0][1]),
(commonThresholds[1][0]/100.0*span,commonThresholds[1][1]),
(commonThresholds[2][0]/100.0*span,commonThresholds[2][1])]
if Debug:
print(g,thresholds)
thitems = ['ind','pref','veto']
randVeto = _random.uniform(0.0,1.0)
if randVeto > vetoProbability or vetoProbability is None:
thitems = ['ind','pref']
criteria[g]['thresholds'] = {}
for t in range(len(thitems)):
criteria[g]['thresholds'][thitems[t]] \
= (Decimal(str(thresholds[t][0])),Decimal(str(thresholds[t][1])))
criteria[g]['scale'] = commonScale
if criteria[g]['preferenceDirection'] == 'max':
if IntegerWeights:
criteria[g]['weight'] = weightsList[i]
else:
criteria[g]['weight'] = weightsList[i] / sumWeights
else:
if NegativeWeights:
if IntegerWeights:
criteria[g]['weight'] = weightsList[i]
else:
criteria[g]['weight'] = weightsList[i] / sumWeights
else:
if IntegerWeights:
criteria[g]['weight'] = -weightsList[i]
else:
criteria[g]['weight'] = -weightsList[i] / sumWeights
# determine equisignificant objectives
if weightMode[0] == 'equiobjectives':
objectivesCardinality = {}
for g in criteria:
try:
objectivesCardinality[criteria[g]['objective']] += 1
except:
objectivesCardinality[criteria[g]['objective']] = 1
if Debug:
print(objectivesCardinality)
weightsProduct = 1
for oi in objectivesCardinality:
weightsProduct *= objectivesCardinality[oi]
if Debug:
print(weightsProduct)
for g in criteria:
gweight = weightsProduct // objectivesCardinality[criteria[g]['objective']]
if criteria[g]['preferenceDirection'] == 'max':
criteria[g]['weight'] = gweight
else:
if NegativeWeights:
criteria[g]['weight'] = -gweight
else:
criteria[g]['weight'] = gweight
if Debug:
print(weightsProduct)
print(objectivesCardinality[criteria[g]['objective']])
print(criteria[g]['weight'])
# allocate (criterion,action) to coalition supporting type
objectiveSupportingTypes = [('good','+'),('fair','~'),('weak','-')]
self.objectiveSupportingTypes = objectiveSupportingTypes
#randChoice2 = Random(seed)
for x in actions:
profile = {}
for obj in objectives:
if Debug:
print(objectives,obj)
ostInd = _random2.randint(1,len(objectiveSupportingTypes))
ost = objectiveSupportingTypes[ostInd-1]
actions[x][obj]=ost[0]
actions[x]['name'] \
= actions[x]['name'] + ' '+ str(obj) + ost[1]
profile[obj] = actions[x][obj]
actions[x]['profile'] = profile
if Debug:
print(x,actions[x])
# generate evaluations
evaluation = {}
for g in criteria:
evaluation[g] = {}
if commonMode is None:
randomMode = ['triangular','variable',0.50]
else:
randomMode = commonMode
if randomMode[0] == 'uniform' and randomMode[1] is None:
randomMode[1] = commonScale[0]
randomMode[2] = commonScale[1]
criteria[g]['randomMode'] = randomMode
if randomMode[1] == 'variable':
commentString = 'Variable '+randomMode[0]+(' performance generator with low (-), medium (~) or high (+) parameters.')
else:
commentString = 'Constant '+randomMode[0]+(' law with parameters = %s, %s' % (str(randomMode[1]),str(randomMode[2])))
criteria[g]['comment'] = commentString
digits = valueDigits
if str(randomMode[0]) == 'uniform':
for a in actions:
if randomMode[1] == 'variable':
aobj = criteria[g]['objective']
if actions[a]['profile'][aobj] == 'weak':
randomRange = (commonScale[0],
commonScale[0]+0.7*(commonScale[1]-commonScale[0]))
elif actions[a]['profile'][aobj] == 'fair':
randomRange = (commonScale[0]+0.3*(commonScale[1]-commonScale[0]),
commonScale[0]+0.7*(commonScale[1]-commonScale[0]))
elif actions[a]['profile'][aobj] == 'good':
randomRange = (commonScale[0]+0.3*(commonScale[1]-commonScale[0]),
commonScale[1])
actions[a]['comment'] += ': %s %s' % (randomMode[0],randomRange)
else:
randomRange = (commonScale[1],commonScale[2])
randeval = _random.uniform(randomRange[0],randomRange[1])
actions[a]['generators'][g] = (randomMode[0],randomRange)
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
elif str(randomMode[0]) == 'beta':
for a in actions:
m = commonScale[0]
M = commonScale[1]
if randomMode[1] == 'variable':
if actions[a][criteria[g]['objective']] == 'good':
# mode = 75, stdev = 15
#xm = 75
alpha = 5.8661
beta = 2.62203
elif actions[a][criteria[g]['objective']] == 'fair':
# nmode = 50, stdev = 15
#xm = 50
alpha = 5.05556
beta = 5.05556
elif actions[a][criteria[g]['objective']] == 'weak':
# mode = 25, stdev = 15
# xm = 25
alpha = 2.62203
beta = 5.8661
else:
xm = randomMode[1]
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0 / xm
if Debug:
print('alpha,beta', alpha,beta)
u = _random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
if Debug:
print('xm,alpha,beta,u,m,M,randeval',xm,alpha,beta,u,m,M,randeval)
actions[a]['generators'][g] = ('beta',alpha,beta)
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
elif str(randomMode[0]) == 'triangular':
for a in actions:
m = commonScale[0]
M = commonScale[1]
span = commonScale[1]-commonScale[0]
if randomMode[1] == 'variable':
if actions[a][criteria[g]['objective']] == 'good':
xm = 0.7*span
elif actions[a][criteria[g]['objective']] == 'fair':
xm = 0.5*span
elif actions[a][criteria[g]['objective']] == 'weak':
xm = 0.3*span
else:
xm = randomMode[1]
r = randomMode[2]
actions[a]['generators'][g] = (randomMode[0],xm,r)
# setting a speudo random seed
if seed is None:
rdseed = _random.randint(1,pow(2,32))
else:
try:
rdseed += 1
except:
rdseed = seed
rngtr = RNGTr(m,M,xm,r,seed=rdseed)
randeval = rngtr.random()
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,0)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,0)))
if Debug:
print(randeval, criteria[g]['preferenceDirection'], evaluation[g][a])
else:
print('Error: invalid random number generator %s !!!' % commonPar)
# install self object attributes
for obj in objectives:
objCriteria = [g for g in criteria if criteria[g]['objective'] == obj]
objectives[obj]['criteria'] = objCriteria
objWeight = Decimal('0')
for g in objCriteria:
objWeight += abs(criteria[g]['weight'])
objectives[obj]['weight'] = objWeight
# insert missing data
NA = Decimal(NA)
for g in criteria:
sevalg = evaluation[g]
for x in actions:
if _random.random() < missingDataProbability:
sevalg[x] = NA
# instantiate the peformance tableau slots
self.actions = actions
self.objectives = objectives
self.criteriaWeightMode = weightMode
self.criteria = criteria
self.weightPreorder = self.computeWeightPreorder()
self.evaluation = evaluation
self.NA = NA
def showObjectives(self):
print('*------ show objectives -------"')
for obj in self.objectives:
print('%s: %s' % (obj, self.objectives[obj]['name']))
for g in self.objectives[obj]['criteria']:
print(' ', g, self.criteria[g]['name'], self.criteria[g]['weight'])
print(' Total weight: %.2f (%d criteria)\n' \
% (self.objectives[obj]['weight'],
len(self.objectives[obj]['criteria'])))
[docs]
def showActions(self,Alphabetic=False):
print('*----- show decision action --------------*')
actions = self.actions
if Alphabetic:
actionsKeys = [x for x in dict.keys(actions)]
actionsKeys.sort()
for x in actionsKeys:
print('key: ',x)
try:
print(' short name: ',actions[x]['shortName'])
except:
pass
print(' name: ',actions[x]['name'])
print(' profile: ',actions[x]['profile'])
else:
for x in actions.keys():
print('key: ',x)
try:
print(' short name: ',actions[x]['shortName'])
except:
pass
print(' name: ',actions[x]['name'])
print(' profile: ',actions[x]['profile'])
class _Random3ObjectivesPerformanceGenerator(RandomPerformanceGenerator):
"""
Generator for new decision actions with random evaluation following a
given Random3ObjectivesPerformanceTableau model.
"""
def __init__(self,argPerfTab,actionNamePrefix='a',
instanceCounter=0,seed=None,Debug=False):
"""
Set the initial state of the random generator.
"""
import random
random.seed(seed)
self.random = random
from randomNumbers import ExtendedTriangularRandomVariable as RNGTr
self.RNGTr = RNGTr
self.perfTab = argPerfTab
self.actionNamePrefix = actionNamePrefix
if instanceCounter is None:
try:
self.counter = len(argPerfTab.actions)
except:
self.counter = 0
else:
self.counter = instanceCounter
self.nd = len(str(self.counter))
self.Debug = Debug
def _randomAction(self):
"""
Returns a dictionary with following content:
{ 'action': { 'key': actionKey, 'shortName': ..., 'name': ..., ... },
'evaluation': {'g1': Decimal(...), 'g2': Decimal(...), ... } }
"""
# generate random evaluation
Debug = self.Debug
random = self.random
try:
digits = self.perfTab.valueDigits
except:
digits = 2
criteria = self.perfTab.criteria
objectives = self.perfTab.objectives
objectiveSupportingTypes = self.perfTab.objectiveSupportingTypes
commonScale = self.perfTab.commonScale
OrdinalScales = self.perfTab.OrdinalScales
# generate action key and record
self.counter += 1
if self.perfTab.BigData:
actionName = ('%s%%0%dd' % (self.actionNamePrefix,self.nd)) % (self.counter)
actionKey = self.counter
action = {'shortName': actionName,
'name': actionName,
'key': actionKey,
'generators': {}}
else:
actionKey = ('%s%%0%dd' % (self.actionNamePrefix,self.nd)) % (self.counter)
action = {'shortName':actionKey,
'name': 'random decision action',
'comment': '3 Objectives',
'key': actionKey,
'generators': {}}
# allocate coalition supporting types
profile = {}
for obj in objectives:
if Debug:
print(objectives,obj)
ost = random.choice(objectiveSupportingTypes)
action[obj]=ost[0]
action['name'] \
= action['name'] + ' '+ str(obj) + ost[1]
profile[obj] = action[obj]
action['profile'] = profile
if Debug:
print(action)
# generate random evaluations
evaluation = {}
for g in criteria:
randomMode= criteria[g]['randomMode']
aobj = criteria[g]['objective']
# uniform distribution
if str(randomMode[0]) == 'uniform':
if randomMode[1] == 'variable':
#aobj = criteria[g]['objective']
if actions['profile'][aobj] == 'weak':
randomRange = (commonScale[0],
commonScale[0]+0.7*(commonScale[1]-commonScale[0]))
elif action['profile'][aobj] == 'fair':
randomRange = (commonScale[0]+0.3*(commonScale[1]-commonScale[0]),
commonScale[0]+0.7*(commonScale[1]-commonScale[0]))
elif action['profile'][aobj] == 'good':
randomRange = (commonScale[0]+0.3*(commonScale[1]-commonScale[0]),
commonScale[1])
action['comment'] += ': %s %s' % (randomMode[0],randomRange)
else:
randomRange = (commonScale[1],commonScale[2])
randeval = random.uniform(randomRange[0],randomRange[1])
action['generators'][g] = (randomMode[0],randomRange)
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,0)))
else:
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
# beta distribution
elif str(randomMode[0]) == 'beta':
m = commonScale[0]
M = commonScale[1]
if randomMode[1] == 'variable':
if action['profile'][aobj] == 'good':
# mode = 75, stdev = 15
#xm = 75
alpha = 5.8661
beta = 2.62203
elif action['profile'][aobj] == 'fair':
# nmode = 50, stdev = 15
#xm = 50
alpha = 5.05556
beta = 5.05556
elif action['profile'][aobj] == 'weak':
# mode = 25, stdev = 15
# xm = 25
alpha = 2.62203
beta = 5.8661
else:
xm = randomMode[1]
if xm > 0.5:
beta = 2.0
alpha = 1.0/(1-xm)
else:
alpha = 2.0
beta = 1.0 / xm
if Debug:
print('alpha,beta', alpha,beta)
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
if Debug:
print('xm,alpha,beta,u,m,M,randeval',xm,alpha,beta,u,m,M,randeval)
action['generators'][g] = ('beta',alpha,beta)
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,0)))
else:
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
# triangular
elif str(randomMode[0]) == 'triangular':
m = commonScale[0]
M = commonScale[1]
span = commonScale[1]-commonScale[0]
if randomMode[1] == 'variable':
if action['profile'][aobj] == 'good':
xm = 0.7*span
elif action['profile'][aobj] == 'fair':
xm = 0.5*span
elif action['profile'][aobj] == 'weak':
xm = 0.3*span
else:
xm = randomMode[1]
r = randomMode[2]
action['generators'][g] = (randomMode[0],xm,r)
# setting a speudo random seed
rdseed = random.random()
rngtr = self.RNGTr(m,M,xm,r,seed=rdseed)
randeval = rngtr.random()
if OrdinalScales:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,0)))
else:
evaluation[g] = Decimal(str(round(randeval,0)))
else:
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
if Debug:
print(randeval, criteria[g]['preferenceDirection'], evaluation[g])
if Debug:
print(evaluation)
# randomly insert missing data
NA = self.perfTab.NA
missingDataProbability = self.perfTab.missingDataProbability
for g in criteria:
if random.random() < missingDataProbability:
evaluation[g] = NA
# return a new random decision alternative
return {'action': action,'evaluation':evaluation}
#---------------
[docs]
class RandomCBPerformanceTableau(PerformanceTableau):
"""
Full automatic generation of random multiple-criteria Cost-Benefit performance tableaux.
Parameters:
* If numberOfActions is None, a uniform random number between 10 and 31 of cheap, neutral or advantageous actions (equal 1/3 probability each type) actions is instantiated.
* If numberOfCriteria is None, a uniform random number between 5 and 21 of cost or benefit criteria. Cost criteria have probability 1/3, whereas benefit criteria respectively 2/3 probability to be generated. However, at least one criterion of each kind is always instantiated.
* weightDistribution := {'equiobjectives' (default)|'fixed'|'random'|'equisignificant'}
By default, the sum of significance of the cost criteria is set equal to the sum of the significance of the benefit criteria.
* Default weightScale for 'random' weightDistribution is 1 - numberOfCriteria.
* If NegativeWeights = True | False (default), the performance evaluation of the criteria with a 'min' preference direction will be positive, otherwise they will be negative.
* Parameter commonScale is not used. The scale of cost criteria is cardinal or ordinal (0-10) with probability 1/4, respectively 3/4, whereas the scale of benefit criteria is ordinal or cardinal with probabilities 2/3, respectively 1/3.
* All cardinal criteria are evaluated with decimals between 0.0 and 100.0 wheras all ordinal criteria are evaluated with integers between 0 and 10.
* commonThresholds parameter is not used. Preference discrimination is specified as percentiles of concerned performance differences (see below).
* CommonPercentiles = {'ind':0.05, 'pref':0.10, 'veto':'95} are expressed in percentiles of the observed performance differences and only concern cardinal criteria.
.. note::
Minimal number required of criteria is 2, and minimal number
required of decision actions is 6!
>>> from randomPerfTabs import RandomCBPerformanceTableau
>>> pt = RandomCBPerformanceTableau(numberOfActions=6,numberOfCriteria=3,seed=2)
>>> pt
*------- PerformanceTableau instance description ------*
Instance class : RandomCBPerformanceTableau
Seed : 1
Instance name : randomCBperftab
Actions : 6
Objectives : 2
Criteria : 3
NaN proportion (%) : 0.0
Attributes : ['randomSeed', 'name', 'digits',
'BigData', 'missingDataProbability', 'NA',
'commonPercentiles', 'samplingSize', 'Debug',
'actionsTypesList', 'sumWeights',
'valuationPrecision', 'actions', 'objectives',
'criteriaWeightMode', 'criteria', 'evaluation',
'weightPreorder']
>>> pt.showObjetives()
*------ decision objectives -------"
C: Costs
c1 random cardinal cost criterion 1
c2 random cardinal cost criterion 1
Total weight: 2.00 (2 criteria)
B: Benefits
b1 random ordinal benefit criterion 2
Total weight: 2.00 (1 criteria)
>>> pt.showCriteria()
*---- criteria -----*
c1 random cardinal cost criterion
Preference direction: min
Scale = (0.00, 100.00)
Weight = 0.250
Threshold ind : 1.98 + 0.00x ; percentile: 6.67
Threshold pref : 8.48 + 0.00x ; percentile: 13.33
Threshold veto : 60.79 + 0.00x ; percentile: 100.00
b1 random ordinal benefit criterion
Preference direction: max
Scale = (0.00, 10.00)
Weight = 0.500
c2 random cardinal cost criterion
Preference direction: min
Scale = (0.00, 100.00)
Weight = 0.250
Threshold ind : 3.34 + 0.00x ; percentile: 6.67
Threshold pref : 4.99 + 0.00x ; percentile: 13.33
Threshold veto : 63.75 + 0.00x ; percentile: 100.00
>>> pt.showActions
*----- show decision action --------------*
key: a1
short name: a1c
name: action a1
comment: Cost-Benefit
key: a2
short name: a2c
name: action a2
comment: Cost-Benefit
key: a3
short name: a3c
name: action a3
comment: Cost-Benefit
key: a4
short name: a4n
name: action a4
comment: Cost-Benefit
key: a5
short name: a5c
name: action a5
comment: Cost-Benefit
key: a6
short name: a6a
name: action a6
comment: Cost-Benefit
>>> pt.showPerformanceTableau()
*---- performance tableau -----*
Criteria | 'b1' 'c1' 'c2'
Actions | 2 1 1
---------|-----------------------------------------
'a1c' | 9.00 -37.92 -7.03
'a2c' | 8.00 -35.94 -28.93
'a3c' | 3.00 -16.88 -23.94
'a4n' | 5.00 -46.40 -43.59
'a5c' | 2.00 -26.61 -67.44
'a6a' | 2.00 -77.67 -70.78
"""
def __init__(self,numberOfActions = 13,
numberOfCriteria = 7,
name = 'randomCBperftab',
weightDistribution = 'equiobjectives',
weightScale=None,
IntegerWeights = True,
NegativeWeights = False,
#commonScale = None, commonThresholds = None,
commonPercentiles= None,
samplingSize = 100000,
commonMode = None,
valueDigits = 2,
missingDataProbability = 0.01,
NA = -999,
BigData=False,
seed = None,
Debug=False,Comments=False):
"""
Constructor for RadomCBPerformanceTableau instances.
"""
import sys,math,copy
import random
# randomizer init
self.randomSeed = seed
random.seed(seed)
# store argument values
self.name = name
self.digits = valueDigits
self.BigData = BigData
self.missingDataProbability = missingDataProbability
self.NA = Decimal(NA)
self.commonPercentiles = commonPercentiles
self.samplingSize = samplingSize
self.Debug = Debug
# generate actions
if numberOfActions is None:
numberOfActions = random.randint(10,31)
if numberOfActions < 6:
print('!!! Error: the number of decision actions must be greater than 5')
return
nd = len(str(numberOfActions))
self.actionsTypesList = ['cheap','neutral','advantageous']
actions = OrderedDict()
actionsTypesList = self.actionsTypesList
for i in range(1,numberOfActions+1):
actionType = random.choice(actionsTypesList)
if BigData:
actionName = ('%%0%dd' % (nd)) % (i)
actions[i] = {'shortName':actionName+actionType[0],
'name':actionName+actionType[0],
'type': actionType}
else:
actionKey = ('a%%0%dd' % (nd)) % (i)
actions[actionKey] = {'shortName':actionKey+actionType[0],
'name': 'action %s' % (actionKey),
'comment': 'Cost-Benefit',
'type': actionType}
# generate objectives
objectives = OrderedDict([
('C', {'name': 'Costs', 'criteria':[]}),
('B', {'name': 'Benefits', 'criteria':[]}),
])
# generate criteria
if numberOfCriteria is None:
numberOfCriteria = random.randint(5,21)
ng = len(str(numberOfCriteria))
criterionTypesList = ['max','max','min']
minScaleTypesList = ['cardinal','cardinal','cardinal','ordinal']
maxScaleTypesList = ['ordinal','ordinal','cardinal']
criterionTypeCounter = {'min':0,'max':0}
criteria = OrderedDict()
for i in range(numberOfCriteria):
# at least one cost and one benefit criterion is selected
if i == 0:
criterionType = 'min'
elif i == 1:
criterionType = 'max'
else:
criterionType = random.choice(criterionTypesList)
criterionTypeCounter[criterionType] += 1
if criterionType == 'min':
g = ('c%%0%dd' % ng) % (criterionTypeCounter[criterionType])
else:
g = ('b%%0%dd' % ng) % (criterionTypeCounter[criterionType])
criteria[g] = {}
criteria[g]['preferenceDirection'] = criterionType
if criterionType == 'min':
scaleType = random.choice(minScaleTypesList)
else:
scaleType = random.choice(maxScaleTypesList)
criteria[g]['scaleType'] = scaleType
if criterionType == 'min':
criteria[g]['objective'] = 'C'
criteria[g]['shortName'] = g
objectives['C']['criteria'].append(g)
if scaleType == 'ordinal':
criteria[g]['name'] = 'random ordinal cost criterion'
else:
criteria[g]['name'] = 'random cardinal cost criterion'
else:
criteria[g]['objective'] = 'B'
objectives['B']['criteria'].append(g)
criteria[g]['shortName'] = g
if scaleType == 'ordinal':
criteria[g]['name'] = 'random ordinal benefit criterion'
else:
criteria[g]['name'] = 'random cardinal benefit criterion'
if Debug:
print("g, criteria[g]['scaleType'], criteria[g]['scale']", g, criteria[g]['scaleType'], end=' ')
# commonScale parameter is obsolete
commonScale = None
if criteria[g]['scaleType'] == 'cardinal':
criterionScale = (0.0, 100.0)
elif criteria[g]['scaleType'] == 'ordinal':
criterionScale = (0, 10)
else:
criterionScale = (0.0, 100.0)
criteria[g]['scale'] = criterionScale
if Debug:
print(criteria[g]['scale'])
# generate random weights
if weightDistribution is None:
weightDistribution = 'equiobjectives'
#weightDistribution = 'fixed'
weightScale = (1,1)
weightMode=[weightDistribution,weightScale]
else:
if weightScale is None:
weightScale = (1,numberOfCriteria)
weightMode=[weightDistribution,weightScale]
if weightDistribution == 'random':
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
weightsList.append(Decimal(str(random.randint(weightScale[0],weightScale[1]))))
sumWeights += weightsList[i]
weightsList.reverse()
elif weightDistribution == 'fixed':
weightsList = []
sumWeights = Decimal('0.0')
for i in range(numberOfCriteria):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
elif weightDistribution == 'equisignificant':
weightScale = (1,1)
weightMode=[weightDistribution,weightScale]
weightsList = []
sumWeights = Decimal('0.0')
for i in range(len(criteriaList)):
if i == 0:
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
weightsList.reverse()
elif weightDistribution == 'equiobjectives':
weightScale = (max(1,criterionTypeCounter['min']),max(1,criterionTypeCounter['max']))
weightMode=[weightDistribution,weightScale]
weightsList = []
sumWeights = Decimal('0.0')
for g in criteria:
if criteria[g]['preferenceDirection'] == 'min':
weightsList.append(Decimal(str(weightScale[1])))
sumWeights += weightScale[1]
else:
weightsList.append(Decimal(str(weightScale[0])))
sumWeights += weightScale[0]
else:
print('!!! Error: wrong criteria weight distribution mode: %s !!!!' % (weightDistribution))
if Debug:
print(weightsList, sumWeights)
# store sum of weights and valuation precision
self.sumWeights = sumWeights
self.valuationPrecision = Decimal('0.1')/sumWeights
for i,g in enumerate(criteria):
## if Debug:
## print 'criterionScale = ', criterionScale
if NegativeWeights:
if criteria[g]['preferenceDirection'] == "max":
Sgn = Decimal('1.0')
else:
Sgn = Decimal('-1.0')
if IntegerWeights:
criteria[g]['weight'] = Sgn * weightsList[i]
else:
criteria[g]['weight'] = Sgn * weightsList[i] / sumWeights
i += 1
else:
if IntegerWeights:
criteria[g]['weight'] = weightsList[i]
else:
criteria[g]['weight'] = weightsList[i] / sumWeights
i += 1
if Debug:
print(criteria[g])
for obj in objectives:
objectives[obj]['weight'] = sum([criteria[g]['weight'] \
for g in criteria if criteria[g]['objective'] == obj])
# generate random evaluations
evaluation = {}
for g in criteria:
criterionScale = criteria[g]['scale']
amplitude = criterionScale[1] - criterionScale[0]
x30=criterionScale[0] + amplitude*0.3
x50=criterionScale[0] + amplitude*0.5
x70=criterionScale[0] + amplitude*0.7
if Debug:
print('g, criterionx30,x50,x70', g, criteria[g], x30,x50,x70)
evaluation[g] = {}
if commonMode is None:
#randomMode = random.choice(randomLawsList)
randomMode = ['triangular',x50,0.50]
elif commonMode[0] is None:
#randomMode = random.choice(randomLawsList)
randomMode = ['triangular',x50,0.50]
else:
randomMode = commonMode
if randomMode[0] == 'uniform':
randomMode[1] = criterionScale[0]
randomMode[2] = criterionScale[1]
criteria[g]['randomMode'] = randomMode
if randomMode[0] == 'triangular':
commentString = 'triangular law with variable mode (m) and probability repartition (p = 0.5). Cheap actions: m = 30%; neutral actions: m = 50%; advantageous actions: m = 70%.'
elif randomMode[0] == 'normal':
commentString = 'truncated normal law with variable mean (mu) and standard deviation (stdev = 20%). Cheap actions: mu = 30%; neutral actions: mu = 50%; advantageous actions: mu = 70%.'
elif randomMode[0] == 'beta':
commentString = 'beta law with variable mode xm and standard deviation (stdev = 15%). Cheap actions: xm = 30%; neutral actions: xm = 50%; advantageous actions: xm = 70%.'
if Debug:
print('commonMode = ', commonMode)
print('randomMode = ', randomMode)
criteria[g]['comment'] = 'Evaluation generator: ' + commentString
digits = valueDigits
if str(randomMode[0]) == 'uniform':
evaluation[g] = {}
for a in actions:
randeval = random.uniform(criterionScale[0],criterionScale[1])
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if NegativeWeights:
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
elif str(randomMode[0]) == 'triangular':
for a in actions:
m = criterionScale[0]
M = criterionScale[1]
#r = randomMode[2]
#xm = randomMode[1]
if actions[a]['type'] == 'advantageous':
xm = x70
r = 0.50
elif actions[a]['type'] == 'cheap':
xm = x30
r = 0.50
else:
xm = x50
r = 0.50
deltaMinus = 1.0 - (criterionScale[0]/xm)
deltaPlus = (criterionScale[1]/xm) - 1.0
u = random.random()
#print 'm,xm,M,r,u', m,xm,M,r,u
if u < r:
#randeval = m + (math.sqrt(r*u*(m-xm)**2))/r
randeval = m + math.sqrt(u/r)*(xm-m)
else:
#randeval = (M*r - M + math.sqrt((-1+r)*(-1+u)*(M-xm)**2))/(-1+r)
randeval = M - math.sqrt((1-u)/(1-r))*(M-xm)
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if NegativeWeights:
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#print randeval, criteria[g]['preferenceDirection'], evaluation[g][a]
elif str(randomMode[0]) == 'normal':
#mu = randomMode[1]
#sigma = randomMode[2]
for a in actions:
## amplitude = criterionScale[1]-criterionScale[0]
## x70 = criterionScale[0] + 0.7 * amplitude
## x50 = criterionScale[0] + 0.5 * amplitude
## x30 = criterionScale[0] + 0.3 * amplitude
if actions[a]['type'] == 'advantageous':
mu = x70
sigma = 0.20 * amplitude
elif actions[a]['type'] == 'cheap':
mu = x30
sigma = 0.20 * amplitude
else:
mu = x50
sigma = 0.25 * amplitude
notfound = True
while notfound:
randeval = random.normalvariate(mu,sigma)
## if Debug:
## print 'g,commonScale,randeval', g,commonScale,randeval
if randeval >= criterionScale[0] and randeval <= criterionScale[1]:
notfound = False
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if NegativeWeights:
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#evaluation[g][a] = Decimal(str(-round(randeval,digits)))
elif str(randomMode[0]) == 'beta':
m = criterionScale[0]
M = criterionScale[1]
for a in actions:
if actions[a]['type'] == 'advantageous':
# xm = 0.7 sdtdev = 0.15
alpha = 5.8661
beta = 2.62203
elif actions[a]['type'] == 'cheap':
# xm = 0.3, stdev = 0.15
alpha = 2.62203
beta = 5.8661
else:
# xm = 0.5, stdev = 0.15
alpha = 5.05556
beta = 5.05556
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
if NegativeWeights:
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#evaluation[g][a] = Decimal(str(-round(randeval,digits)))
## if Debug:
## print 'alpha,beta,u,m,M,randeval',alpha,beta,u,m,M,randeval
if Debug:
print(evaluation)
# restrict ordinal criteria to integer values
for g in criteria:
if criteria[g]['scaleType'] == 'ordinal':
for a in actions:
if Debug:
print('-- >>', evaluation[g][a], end=' ')
evaluation[g][a] = Decimal(str(round(evaluation[g][a],0)))
if Debug:
print(evaluation[g][a])
# randomly insert missing data
NA = Decimal(NA)
for g in criteria:
for x in actions:
if random.random() < missingDataProbability:
evaluation[g][x] = NA
# final storage
self.actions = actions
self.objectives = objectives
self.criteriaWeightMode = weightMode
self.criteria = criteria
self.evaluation = evaluation
self.NA = NA
self.weightPreorder = self.computeWeightPreorder()
# compute discrimination thresholds from commonPercentiles
n = len(self.actions)
n2 = (n*n) - n
if n < 1000:
nbuf = 1000
else:
nbuf = n
if n2 < samplingSize:
samplingSize = n2
from randomNumbers import IncrementalQuantilesEstimator
est = IncrementalQuantilesEstimator(nbuf=nbuf)
if Debug:
print('commonPercentiles=', commonPercentiles)
if commonPercentiles is None:
quantile = OrderedDict({'ind':0.05, 'pref':0.10 , 'veto':0.95})
else:
quantile = commonPercentiles
for g in criteria:
criteria[g]['thresholds'] = OrderedDict()
if criteria[g]['scaleType'] == 'cardinal' and len(actions) > 1:
est.reset()
sample = 0
for x in actions.keys():
evx = self.evaluation[g][x]
if evx != NA:
for y in actions.keys():
evy = self.evaluation[g][y]
if x != y and evy != NA:
est.add( float( abs(evx-evy) ) )
sample += 1
if sample > samplingSize:
break
est._update()
for q in quantile:
if Comments:
print('-->', q, quantile[q], end=' ')
criteria[g]['thresholds'][q] = (Decimal(str(est.report(quantile[q]))),Decimal('0'))
if Comments:
print('criteria',g,' default thresholds:')
print(criteria[g]['thresholds'])
# update criteria
self.criteria = criteria
[docs]
def updateDiscriminationThresholds(self,Comments=False,Debug=False):
"""
Recomputes performance discrimination thresholds from commonPercentiles.
.. note::
Overwrites all previous criterion discrimination thresholds !
"""
actions = self.actions
n = len(actions)
n2 = (n*n) - n
if n < 1000:
nbuf = 1000
else:
nbuf = n
samplingSize = self.samplingSize
if n2 < samplingSize:
samplingSize = n2
from randomNumbers import IncrementalQuantilesEstimator
est = IncrementalQuantilesEstimator(nbuf=nbuf)
commonPercentiles = self.commonPercentiles
if Debug:
print('commonPercentiles=', commonPercentiles)
if commonPercentiles is None:
quantile = OrderedDict({'ind':0.05, 'pref':0.10 , 'veto':0.95})
else:
quantile = commonPercentiles
criteria = self.criteria
evaluation = self.evaluation
NA = self.NA
for g in criteria:
criteria[g]['thresholds'] = OrderedDict()
if criteria[g]['scaleType'] == 'cardinal' and n > 1:
est.reset()
sample = 0
for x in actions.keys():
evx = evaluation[g][x]
if evx != NA:
for y in actions.keys():
evy = evaluation[g][y]
if x != y and evy != NA:
est.add( float( abs(evx-evy) ) )
sample += 1
if sample > samplingSize:
break
est._update()
for q in quantile:
if Comments:
print('-->', q, quantile[q], end=' ')
criteria[g]['thresholds'][q] = (Decimal(str(est.report(quantile[q]))),Decimal('0'))
if Comments:
print('criteria',g,' default thresholds:')
print(criteria[g]['thresholds'])
class _RandomCBPerformanceGenerator(RandomPerformanceGenerator):
"""
Generator of new decision actions with random evaluations using
the model parameters provided by a given RandomCBPerformanceTableau instance.
"""
def __init__(self,argPerfTab,actionNamePrefix='a',
instanceCounter=None,seed=None):
"""
Set the initial state of the random generator.
"""
import random
random.seed(seed)
self.random = random
self.perfTab = argPerfTab
self.actionNamePrefix = actionNamePrefix
if instanceCounter is None:
self.counter = len(argPerfTab.actions)
else:
self.counter = instanceCounter
self.nd = len(str(self.counter))
def _randomAction(self):
"""
Returns a dictionary with following content::
{ 'action': { 'key': actionKey, 'shortName': ..., 'name': ...,
'type': 'neutral'|'advantageous'|'cheap'},
'evaluation': {'g1': Decimal(...),
'g2': Decimal(...), ... }}.
"""
# generate action key
self.counter += 1
actionType = self.random.choice(self.perfTab.actionsTypesList)
if self.perfTab.BigData:
actionName = ('%s%%0%dd' % (self.actionNamePrefix,self.nd)) % (self.counter)
actionKey = self.counter
action = {'shortName': actionName+actionType[0],
'name': actionName+actionType[0],
'type': actionType,
'key': actionKey}
else:
actionKey = ('%s%%0%dd' % (self.actionNamePrefix,self.nd)) % (self.counter)
action = {'shortName':actionKey+actionType[0],
'name': 'random %s decision action' % (actionType),
'comment': 'Cost-Benefit',
'type': actionType,
'key': actionKey}
# generate random evaluation
random = self.random
digits = self.perfTab.digits
criteria = self.perfTab.criteria
# generate random evaluations
Debug = self.perfTab.Debug
evaluation = {}
for g in criteria:
criterionScale = criteria[g]['scale']
amplitude = criterionScale[1] - criterionScale[0]
if amplitude < Decimal('11.0'):
digits = 0
x30=criterionScale[0] + amplitude*0.3
x50=criterionScale[0] + amplitude*0.5
x70=criterionScale[0] + amplitude*0.7
if Debug:
print('g, criterionx30,x50,x70', g, criteria[g], x30,x50,x70)
evaluation[g] = {}
randomMode = criteria[g]['randomMode']
if str(randomMode[0]) == 'uniform':
randeval = random.uniform(criterionScale[0],criterionScale[1])
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] < Decimal('0'):
evaluation[g][a] = Decimal(str(round(randeval,digits)))
else:
evaluation[g][a] = Decimal(str(-round(randeval,digits)))
#evaluation[g] = Decimal(str(-round(randeval,digits)))
elif str(randomMode[0]) == 'triangular':
from math import sqrt
m = criterionScale[0]
M = criterionScale[1]
#r = randomMode[2]
#xm = randomMode[1]
if action['type'] == 'advantageous':
xm = x70
r = 0.50
elif action['type'] == 'cheap':
xm = x30
r = 0.50
else:
xm = x50
r = 0.50
deltaMinus = 1.0 - (criterionScale[0]/xm)
deltaPlus = (criterionScale[1]/xm) - 1.0
u = random.random()
#print 'm,xm,M,r,u', m,xm,M,r,u
if u < r:
#randeval = m + (math.sqrt(r*u*(m-xm)**2))/r
randeval = m + sqrt(u/r)*(xm-m)
else:
#randeval = (M*r - M + math.sqrt((-1+r)*(-1+u)*(M-xm)**2))/(-1+r)
randeval = M - sqrt((1-u)/(1-r))*(M-xm)
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
#print randeval, criteria[g]['preferenceDirection'], evaluation[g][a]
elif str(randomMode[0]) == 'normal':
## amplitude = criterionScale[1]-criterionScale[0]
## x70 = criterionScale[0] + 0.7 * amplitude
## x50 = criterionScale[0] + 0.5 * amplitude
## x30 = criterionScale[0] + 0.3 * amplitude
if action['type'] == 'advantageous':
mu = x70
sigma = 0.20 * amplitude
elif action['type'] == 'cheap':
mu = x30
sigma = 0.20 * amplitude
else:
mu = x50
sigma = 0.25 * amplitude
notfound = True
while notfound:
randeval = random.normalvariate(mu,sigma)
## if Debug:
## print 'g,commonScale,randeval', g,commonScale,randeval
if randeval >= criterionScale[0] and randeval <= criterionScale[1]:
notfound = False
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
elif str(randomMode[0]) == 'beta':
m = criterionScale[0]
M = criterionScale[1]
if action['type'] == 'advantageous':
# xm = 0.7 sdtdev = 0.15
alpha = 5.8661
beta = 2.62203
elif action['type'] == 'cheap':
# xm = 0.3, stdev = 0.15
alpha = 2.62203
beta = 5.8661
else:
# xm = 0.5, stdev = 0.15
alpha = 5.05556
beta = 5.05556
u = random.betavariate(alpha,beta)
randeval = (u * (M-m)) + m
if criteria[g]['preferenceDirection'] == 'max':
evaluation[g] = Decimal(str(round(randeval,digits)))
else:
if criteria[g]['weight'] > Decimal('0'):
evaluation[g] = Decimal(str(-round(randeval,digits)))
else:
evaluation[g] = Decimal(str(round(randeval,digits)))
if Debug:
print('alpha,beta,u,m,M,randeval',alpha,beta,u,m,M,randeval)
if Debug:
print(evaluation)
# randomly insert missing data
NA = self.perfTab.NA
missingDataProbability = self.perfTab.missingDataProbability
for g in criteria:
if random.random() < missingDataProbability:
evaluation[g] = NA
# return a new random decision alternative
return {'action': action,'evaluation':evaluation}
## def randomUpdate(self,nbrOfRandomActions=1):
## """
## Updates *self.perfTab* with *n* = *nbrOfActions* new random decision alternatives.
##
## .. note::
##
## The update will modify the generator's given performance tableau instance by,
## either adding new actions with their random evaluations,
## or updating the performances of already existing decision actions.
## """
## actions = self.perfTab.actions
## criteria = self.perfTab.criteria
## evaluation = self.perfTab.evaluation
## for i in range(nbrOfRandomActions):
## newAction = self._randomAction()
## newEvaluation = newAction['evaluation']
## newKey = newAction['action'].pop('key')
## actions[newKey] = newAction['action']
## for g in criteria:
## evaluation[g][newKey] = newEvaluation[g]
##############################
class _RandomS3PerformanceTableau(_RandomCoalitionsPerformanceTableau):
"""
Obsolete dummy class for backports.
"""
#----------test Digraph class ----------------
if __name__ == "__main__":
print("""
****************************************************
* Digraph3 randomPerfTabs module *
* depends on BipolarOutrankingDigraph and *
* Copyright (C) 2010-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 -------')
from digraphs import *
from outrankingDigraphs import BipolarOutrankingDigraph
from randomPerfTabs import *
pt = RandomCBPerformanceTableau(numberOfActions=6,numberOfCriteria=3,seed=1)
print(pt)
pt.showObjectives()
pt.showCriteria()
pt.showActions()
pt.showPerformanceTableau()
## t = RandomAcademicPerformanceTableau(numberOfStudents=10,numberOfCourses=5,
## commonMode=('uniform',None,None),
## missingDataProbability=0.01)
## t = RandomAcademicPerformanceTableau(numberOfStudents=10,numberOfCourses=10,
## commonMode=('triangular',14,0.4),
## missingDataProbability=0.01,
## WithTypes=True,
## seed=1)
## t.showStudents()
## t.showCourses()
print('*------------------*')
print('If you see this line all tests were passed successfully :-)')
print('Enjoy !')
#############################