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

Last change on this file since 874 was 869, checked in by wouter, 7 months ago

more annotations

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