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

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

#291 move annotation to above the javadoc

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