1 | from decimal import Decimal, ROUND_HALF_UP
|
---|
2 | from typing import cast, Optional
|
---|
3 |
|
---|
4 | from geniusweb.issuevalue.NumberValue import NumberValue
|
---|
5 | from geniusweb.issuevalue.NumberValueSet import NumberValueSet
|
---|
6 | from geniusweb.issuevalue.Value import Value
|
---|
7 | from geniusweb.issuevalue.ValueSet import ValueSet
|
---|
8 | from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
|
---|
9 |
|
---|
10 |
|
---|
11 | class NumberValueSetUtilities (ValueSetUtilities ):
|
---|
12 | '''
|
---|
13 | The low and high values (from a {@link NumberValueSet} are given each a
|
---|
14 | different utility. This linearly interpolates in-between utility values.
|
---|
15 | '''
|
---|
16 |
|
---|
17 |
|
---|
18 | def __init__(self, lowValue:Decimal,\
|
---|
19 | lowUtility:Decimal, highValue: Decimal, highUtility:Decimal) :
|
---|
20 | '''
|
---|
21 | @param lowValue the low value of the {@link Range}
|
---|
22 | @param lowUtility the utility of the {@link #lowValue}
|
---|
23 | @param highValue the high value of the {@link Range}. Must be
|
---|
24 | >lowValue.
|
---|
25 | @param highUtility the utility of the {@link #highValue}
|
---|
26 | '''
|
---|
27 | if lowValue == None or highValue == None or lowUtility == None \
|
---|
28 | or highUtility == None :
|
---|
29 | raise ValueError(\
|
---|
30 | "arguments lowValue, lowUtility, highValue and highUtility must be non-null");
|
---|
31 |
|
---|
32 | if not self._isInZeroOne(lowUtility):
|
---|
33 | raise ValueError("lowUtility must be in [0,1]")
|
---|
34 |
|
---|
35 | if not self._isInZeroOne(highUtility):
|
---|
36 | raise ValueError("highUtility must be in [0,1]")
|
---|
37 |
|
---|
38 | if highValue<=lowValue:
|
---|
39 | raise ValueError("highValue must be > lowValue")
|
---|
40 |
|
---|
41 | self._lowValue = lowValue
|
---|
42 | self._highValue = highValue
|
---|
43 | self._lowUtility = lowUtility
|
---|
44 | self._highUtility = highUtility
|
---|
45 |
|
---|
46 | #Override
|
---|
47 | def getUtility(self, value:Optional[Value] ) -> Decimal :
|
---|
48 | if not isinstance(value, NumberValue):
|
---|
49 | return Decimal("0");
|
---|
50 |
|
---|
51 | x:Decimal = value.getValue()
|
---|
52 | if x<self._lowValue or x>self._highValue:
|
---|
53 | return Decimal("0")
|
---|
54 | # we need to be careful to avoid round errors from divides.
|
---|
55 | # so we return lowU + deltaU * (x-lowV) /deltaV
|
---|
56 | deltaU:Decimal = self._highUtility- self._lowUtility
|
---|
57 | deltaV:Decimal = self._highValue-self._lowValue
|
---|
58 |
|
---|
59 | return (self._lowUtility + deltaU *(x-self._lowValue)/deltaV )\
|
---|
60 | .quantize(Decimal('1.00000000'), rounding=ROUND_HALF_UP)
|
---|
61 |
|
---|
62 |
|
---|
63 | #Override
|
---|
64 | def isFitting(self, valueset: ValueSet ) -> Optional[str]:
|
---|
65 | if not isinstance(valueset, NumberValueSet):
|
---|
66 | return "The utilities are for a number valueset but the given values are "\
|
---|
67 | + str(valueset);
|
---|
68 |
|
---|
69 | numvalset= cast(NumberValueSet, valueset)
|
---|
70 | if numvalset.getRange().getLow() != self._lowValue:
|
---|
71 | return "the utilities are specified down to " + str(self._lowValue) \
|
---|
72 | + " but the valueset starts at " \
|
---|
73 | + str(numvalset.getRange().getLow())
|
---|
74 |
|
---|
75 | if numvalset.getRange().getHigh() != self._highValue:
|
---|
76 | return "the utilities are specified up to " + str(self._highValue) \
|
---|
77 | + " but the valueset ends at " \
|
---|
78 | + str(numvalset.getRange().getHigh())
|
---|
79 | return None
|
---|
80 |
|
---|
81 | def getLowValue(self) -> Decimal :
|
---|
82 | '''
|
---|
83 | @return the lowest value
|
---|
84 | '''
|
---|
85 | return self._lowValue;
|
---|
86 |
|
---|
87 |
|
---|
88 | def getHighValue(self) ->Decimal :
|
---|
89 | '''
|
---|
90 | @return the highest value
|
---|
91 | '''
|
---|
92 | return self._highValue
|
---|
93 |
|
---|
94 | def getLowUtility(self) ->Decimal :
|
---|
95 | '''
|
---|
96 | @return the utility of the lowest value
|
---|
97 | '''
|
---|
98 | return self._lowUtility;
|
---|
99 |
|
---|
100 | def getHighUtility(self) ->Decimal:
|
---|
101 | '''
|
---|
102 | @return the utility of the highest value
|
---|
103 | '''
|
---|
104 | return self._highUtility
|
---|
105 |
|
---|
106 | def __repr__(self):
|
---|
107 | return "NumberValueSetUtilities[" + str(self._lowValue) + "->" + str(self._lowUtility) + "," \
|
---|
108 | + str(self._highValue) + "->" + str(self._highUtility) + "]"
|
---|
109 |
|
---|
110 | def __hash__(self):
|
---|
111 | return hash((self._lowUtility, self._lowValue,self._highUtility, self._highValue))
|
---|
112 |
|
---|
113 | def __eq__(self, other):
|
---|
114 | return isinstance(other, self.__class__) \
|
---|
115 | and self._highUtility == other._highUtility \
|
---|
116 | and self._highValue == other._highValue \
|
---|
117 | and self._lowUtility == other._lowUtility \
|
---|
118 | and self._lowValue == other._lowValue
|
---|
119 |
|
---|
120 | def _isInZeroOne(self , value:Decimal) -> bool:
|
---|
121 | '''
|
---|
122 | Check if value is in range [0,1]
|
---|
123 |
|
---|
124 | @param value
|
---|
125 | @return true if in range.
|
---|
126 | '''
|
---|
127 | return value >= Decimal("0") and value <= Decimal("1")
|
---|
128 |
|
---|