source: java2python/geniuswebtranslator/geniuswebsrc/geniusweb/profile/utilityspace/SumOfGroupsUtilitySpace.java@ 804

Last change on this file since 804 was 804, checked in by wouter, 6 months ago

#278 added NonNull annotation in many places in the geniusweb code

File size: 9.5 KB
Line 
1package geniusweb.profile.utilityspace;
2
3import java.math.BigDecimal;
4import java.util.Arrays;
5import java.util.HashMap;
6import java.util.HashSet;
7import java.util.LinkedList;
8import java.util.List;
9import java.util.Map;
10import java.util.Set;
11
12import org.eclipse.jdt.annotation.NonNull;
13
14import com.fasterxml.jackson.annotation.JsonAutoDetect;
15import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
16import com.fasterxml.jackson.annotation.JsonCreator;
17import com.fasterxml.jackson.annotation.JsonProperty;
18
19import geniusweb.issuevalue.Bid;
20import geniusweb.issuevalue.Domain;
21import geniusweb.issuevalue.NumberValueSet;
22import geniusweb.issuevalue.Value;
23import geniusweb.profile.DefaultProfile;
24
25/**
26 * This is a utility space that defines the utility of bids as a sum of the
27 * utilities of a number of subsets, or groups, of the issue values.
28 * <p>
29 * A group defines the utility of a non-empty subset of the issues in the
30 * domain. The parts are non-overlapping. Missing issue values have utility 0.
31 * <p>
32 * This space enables handling {@link UtilitySpace}s that are not simply linear
33 * additive, but with interacting issue values. For example, if you would like
34 * to have a car with good speakers but only if there is also a good hifi set,
35 * you can make a part that has {@link PartsUtilities} like
36 * <code> { { speakers:yes, hifi:yes }:1, {speakers, no: hifh:yes}:0.2, ...} </code>
37 * <p>
38 * NOTICE this space is completely discrete. There is no equivalent of the
39 * {@link NumberValueSet} here.
40 */
41@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
42public class SumOfGroupsUtilitySpace extends DefaultProfile
43 implements UtilitySpace {
44 /**
45 * the key is the part name, the value is the valuesetutilities for this
46 * issue. Should be immutable so do not return direct access to this field.
47 */
48 private final HashMap<String, PartsUtilities> partUtilities = new HashMap<>();
49
50 /**
51 * @param domain the {@link Domain} in which this profile is
52 * defined.
53 * @param name the name of this profile. Must be simple name (a-Z,
54 * 0-9)
55 * @param partUtilities a map with key: part names (String) and value: the
56 * {@link PartsUtilities} for that part. There MUST
57 * NOT be a null part name. All values MUST NOT be
58 * null. The PartsUtilities must match the
59 * @param reservationBid the reservation bid. Only bids that are
60 * {@link #isPreferredOrEqual(Bid, Bid)} should be
61 * accepted. Can be null, meaning that there is no
62 * reservation bid and any agreement is better than no
63 * agreement.
64 * @throws NullPointerException if values are incorrectly null.
65 * @throws IllegalArgumentException if preconditions not met.
66 */
67 @JsonCreator
68 public SumOfGroupsUtilitySpace(@JsonProperty("domain") Domain domain,
69 @JsonProperty("name") String name,
70 @JsonProperty("partUtilities") HashMap<String, PartsUtilities> partUtilities,
71 @JsonProperty("reservationBid") Bid reservationBid) {
72 super(name, domain, reservationBid);
73 this.partUtilities.putAll(partUtilities);
74
75 String err = checkParts();
76 if (err != null) {
77 throw new IllegalArgumentException(err);
78 }
79 }
80
81 /**
82 * Copy settings in las. This will have the exact same utilities as the las
83 * but this then gives you the power to change it into a non-linear space by
84 * grouping.
85 *
86 * @param las the {@link LinearAdditive} to be converted/copied.
87 *
88 */
89 public SumOfGroupsUtilitySpace(LinearAdditive las) {
90 this(las.getDomain(), las.getName(), las2parts(las),
91 las.getReservationBid());
92 }
93
94 @Override
95 public @NonNull BigDecimal getUtility(@NonNull Bid bid) {
96 //#PY return sum( [ self._util(partname, self._bid) for partname in self._partUtilities.keys() ] )
97 return partUtilities.keySet().stream()
98 .map(partname -> util(partname, bid))
99 .reduce(BigDecimal.ZERO, BigDecimal::add);
100 }
101
102 @Override
103 public @NonNull String toString() {
104 return "PartialAdditive[" + partUtilities + "," + getReservationBid()
105 + "]";
106 }
107
108 /**
109 *
110 * @param partnames the partnames to remove from this. There must be at
111 * least 2 parts
112 * @param newpartname the name of the new part that contains all partnames,
113 * grouped into 1 "issue". This name must not be an issue
114 * in this.
115 * @return new {@link SumOfGroupsUtilitySpace} that takes all partnames out
116 * of this, and makes a newaprtname that contains these.
117 */
118 public SumOfGroupsUtilitySpace group(List<String> partnames,
119 String newpartname) {
120 final Set<String> allpartnames = partUtilities.keySet();
121
122 if (partnames.size() < 2) {
123 throw new IllegalArgumentException(
124 "Group must contain at least 2 parts");
125 }
126 if (allpartnames.contains(newpartname)) {
127 throw new IllegalArgumentException(
128 "newpartname " + newpartname + " is already in use");
129 }
130 for (String name : partnames) {
131 if (!allpartnames.contains(name)) {
132 throw new IllegalArgumentException("Unknown part name " + name);
133 }
134 }
135
136 HashMap<String, PartsUtilities> newutils = new HashMap<String, PartsUtilities>();
137
138 PartsUtilities newpartutils = null;
139
140 for (String name : partUtilities.keySet()) {
141 if (partnames.contains(name)) {
142 if (newpartutils == null) {
143 newpartutils = partUtilities.get(name);
144 } else {
145 newpartutils = newpartutils.add(partUtilities.get(name));
146 }
147 } else {
148 newutils.put(name, partUtilities.get(name));
149 }
150 }
151 newutils.put(newpartname, newpartutils);
152
153 return new SumOfGroupsUtilitySpace(getDomain(), getName(), newutils,
154 getReservationBid());
155 }
156
157 @Override
158 public int hashCode() {
159 final int prime = 31;
160 int result = super.hashCode();
161 result = prime * result
162 + ((partUtilities == null) ? 0 : partUtilities.hashCode());
163 return result;
164 }
165
166 @Override
167 public boolean equals(Object obj) {
168 if (this == obj)
169 return true;
170 if (!super.equals(obj))
171 return false;
172 if (getClass() != obj.getClass())
173 return false;
174 SumOfGroupsUtilitySpace other = (SumOfGroupsUtilitySpace) obj;
175 if (partUtilities == null) {
176 if (other.partUtilities != null)
177 return false;
178 } else if (!partUtilities.equals(other.partUtilities))
179 return false;
180 return true;
181 }
182
183 /***************************** private funcs ***************************/
184 /**
185 *
186 * @param partname the name of the part to get the utility of
187 * @param bid the bid
188 * @return weighted util of just the part: utilities[part].getUtility(part
189 * of bid)
190 */
191 private BigDecimal util(String partname, Bid bid) {
192 PartsUtilities partutils = partUtilities.get(partname);
193 ProductOfValue value = collectValues(bid, partutils);
194 return partutils.getUtility(value);
195 }
196
197 /**
198 *
199 * @param bid a full bid
200 * @param partutils the part utils for which a list of values from the bid
201 * is needed
202 * @return a list of values from the given bid, ordered as indicated in
203 * partutils
204 */
205 private ProductOfValue collectValues(Bid bid, PartsUtilities partutils) {
206 List<Value> values = new LinkedList<>();
207 for (String issue : partutils.getIssues()) {
208 values.add(bid.getValue(issue));
209 }
210 return new ProductOfValue(values);
211 }
212
213 /**
214 *
215 * @param las a {@link LinearAdditive}
216 * @return a Map with partname-PartsUtilities. The partnames are identical
217 * to the issues in the given las.
218 */
219 private static HashMap<String, PartsUtilities> las2parts(
220 LinearAdditive las) {
221
222 HashMap<String, PartsUtilities> map = new HashMap<>();
223 for (String issue : las.getUtilities().keySet()) {
224 ValueSetUtilities valset = las.getUtilities().get(issue);
225 Map<ProductOfValue, BigDecimal> utilslist = new HashMap<>();
226 BigDecimal weight = las.getWeight(issue);
227 for (Value val : las.getDomain().getValues(issue)) {
228 BigDecimal util = valset.getUtility(val).multiply(weight);
229 if (BigDecimal.ZERO.compareTo(util) != 0) {
230 utilslist.put(new ProductOfValue(Arrays.asList(val)), util);
231 }
232 }
233 map.put(issue, new PartsUtilities(Arrays.asList(issue), utilslist));
234 }
235 return map;
236 }
237
238 /**
239 *
240 * @return error string, or null if no error (all parts seem fine)
241 */
242 private String checkParts() {
243
244 Set<String> collectedIssues = new HashSet<>();
245 for (String partname : partUtilities.keySet()) {
246 PartsUtilities part = partUtilities.get(partname);
247 if (part == null) {
248 return "partUtilities " + partname + " contains null value";
249 }
250 List<String> issues = part.getIssues();
251 HashSet<String> intersection = new HashSet<String>(collectedIssues);
252 intersection.retainAll(issues);
253 if (!intersection.isEmpty()) {
254 return "issues " + intersection + " occur multiple times";
255 }
256 collectedIssues.addAll(issues);
257 }
258
259 if (!collectedIssues.equals(getDomain().getIssuesValues())) {
260 return "parts must cover the domain issues "
261 + getDomain().getIssuesValues() + " but cover "
262 + collectedIssues;
263 }
264 if (getMaxUtility().compareTo(BigDecimal.ONE) > 0) {
265 return "Max utility of the space exceedds 1";
266 }
267 return null;
268
269 }
270
271 /**
272 *
273 * @return the max possible utility in this utility space.
274 */
275 private BigDecimal getMaxUtility() {
276 //#PY return sum( [ partutils.getMaxUtility() for partutils in self._partUtilities.values() ] )
277 return partUtilities.values().stream()
278 .map(partutils -> partutils.getMaxUtility())
279 .reduce(BigDecimal.ZERO, BigDecimal::add);
280 }
281
282}
Note: See TracBrowser for help on using the repository browser.