source: profile/src/main/java/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.java

Last change on this file was 53, checked in by ruud, 7 months ago

All TimeDependent parties now support the nonlinear SumOfGroupsUtilitySpace. Example Nonlinear space in the computer domain

File size: 7.9 KB
Line 
1package geniusweb.profile.utilityspace;
2
3import java.math.BigDecimal;
4import java.util.Collections;
5import java.util.HashMap;
6import java.util.Map;
7
8import com.fasterxml.jackson.annotation.JsonAutoDetect;
9import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility;
10import com.fasterxml.jackson.annotation.JsonCreator;
11import com.fasterxml.jackson.annotation.JsonProperty;
12
13import geniusweb.issuevalue.Bid;
14import geniusweb.issuevalue.Domain;
15import geniusweb.issuevalue.Value;
16
17/**
18 * Defines a UtilitySpace in terms of a weighted sum of per-issue preferences.
19 * immutable. A {@link LinearAdditiveUtilitySpace} works with complete bids.
20 *
21 * Constructor guarantees that
22 * <ul>
23 * <li>weights are normalized to 1
24 * <li>the issues in the utility map and weights map match those in the domain
25 * <li>The utilities for each issue are proper {@link ValueSetUtilities} objects
26 * </ul>
27 */
28@JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE, isGetterVisibility = Visibility.NONE)
29public class LinearAdditiveUtilitySpace implements LinearAdditive {
30
31 private final String name;
32 /**
33 * the key is the issue, the value is the valuesetutilities for this issue.
34 * Should be immutable so do not return direct access to this field.
35 */
36 private final HashMap<String, ValueSetUtilities> issueUtilities = new HashMap<>();
37
38 /**
39 * The key is the issue, the value is the weight of the utilities for that
40 * issue (utility as returned from the ValueSetUtilities for that issue).
41 */
42 private final HashMap<String, BigDecimal> issueWeights = new HashMap<>();
43
44 private final Domain domain;
45
46 private final Bid reservationBid;
47
48 /**
49 * @param domain the {@link Domain} in which this profile is defined.
50 * @param name the name of this profile. Must be simple name (a-Z, 0-9)
51 * @param issueUtilities a map with key: issue names (String) and value: the values
52 * for that issue. There MUST NOT be a null issue. All values
53 * MUST NOT be null.
54 * @param issueWeights the weight of each issue in the computation of the
55 * weighted sum. The issues must be the same as those in the
56 * utils map. All weights MUST NOT be null. The weights MUST
57 * sum to 1.
58 * @param reservationBid the reservation bid. Only bids that are
59 * {@link #isPreferredOrEqual(Bid, Bid)} should be accepted.
60 * Can be null, meaning that there is no reservation bid and
61 * any agreement is better than no agreement.
62 * @throws NullPointerException if values are incorrectly null.
63 * @throws IllegalArgumentException if preconditions not met.
64 */
65 @JsonCreator
66 public LinearAdditiveUtilitySpace(@JsonProperty("domain") Domain domain,
67 @JsonProperty("name") String name,
68 @JsonProperty("issueUtilities") Map<String, ValueSetUtilities> issueUtilities,
69 @JsonProperty("issueWeights") Map<String, BigDecimal> issueWeights,
70 @JsonProperty("reservationBid") Bid reservationBid) {
71
72 if (domain == null) {
73 throw new NullPointerException("domain=null");
74 }
75 if (issueUtilities == null) {
76 throw new NullPointerException("utils=null");
77 }
78 if (issueWeights == null) {
79 throw new NullPointerException("weights=null");
80 }
81 if (issueUtilities.values().contains(null)) {
82 throw new NullPointerException(
83 "One of the ValueSetUtilities in issueUtilitiesis null:"
84 + issueUtilities);
85 }
86 if (issueWeights.values().contains(null)) {
87 throw new NullPointerException("One of the weights is null");
88 }
89 if (issueUtilities.keySet().contains(null)) {
90 throw new NullPointerException("One of the issue names is null");
91 }
92 if (name == null || !(name.matches("[a-zA-Z0-9]+"))) {
93 throw new IllegalArgumentException(
94 "Name must be simple (a-Z, 0-9) but got " + name);
95 }
96 if (!(issueUtilities.keySet().equals(domain.getIssues()))) {
97 throw new IllegalArgumentException(
98 "The issues in utilityspace and domain do not match: utilityspace has issues "
99 + issueUtilities.keySet() + " but domain contains "
100 + domain.getIssues());
101 }
102 if (!(issueWeights.keySet().equals(domain.getIssues()))) {
103 throw new IllegalArgumentException(
104 "The issues in weights and domain do not match: weights has "
105 + issueWeights.keySet() + " but domain contains "
106 + domain.getIssues());
107 }
108 for (String issue : issueUtilities.keySet()) {
109 String message = issueUtilities.get(issue)
110 .isFitting(domain.getValues(issue));
111 if (message != null)
112 throw new IllegalArgumentException(message);
113 }
114
115 BigDecimal sum = issueWeights.values().stream().reduce(BigDecimal.ZERO,
116 BigDecimal::add);
117 if (BigDecimal.ONE.compareTo(sum) != 0) { // equals does NOT work for
118 // comparing BigDecimals!!
119 throw new IllegalArgumentException("The sum of the weights ("
120 + issueWeights.values() + ") must be 1");
121 }
122 if (reservationBid != null) {
123 String message = domain.isFitting(reservationBid);
124 if (message != null)
125 throw new IllegalArgumentException(
126 "reservationbid is not fitting domain: " + message);
127 }
128
129 this.domain = domain;
130 this.name = name;
131 this.reservationBid = reservationBid;
132 this.issueUtilities.putAll(issueUtilities);
133 this.issueWeights.putAll(issueWeights);
134
135 }
136
137 @Override
138 public BigDecimal getUtility(Bid bid) {
139 return issueWeights.keySet().stream()
140 .map(iss -> util(iss, bid.getValue(iss)))
141 .reduce(BigDecimal.ZERO, BigDecimal::add);
142 }
143
144 @Override
145 public BigDecimal getWeight(String issue) {
146 return issueWeights.get(issue);
147 }
148
149 @Override
150 public String toString() {
151 return "LinearAdditive[" + issueUtilities + "," + issueWeights + ","
152 + reservationBid + "]";
153 }
154
155 @Override
156 public Bid getReservationBid() {
157 return reservationBid;
158 }
159
160 @Override
161 public int hashCode() {
162 final int prime = 31;
163 int result = 1;
164 result = prime * result + ((domain == null) ? 0 : domain.hashCode());
165 result = prime * result
166 + ((issueUtilities == null) ? 0 : issueUtilities.hashCode());
167 result = prime * result
168 + ((issueWeights == null) ? 0 : issueWeights.hashCode());
169 result = prime * result + ((name == null) ? 0 : name.hashCode());
170 result = prime * result
171 + ((reservationBid == null) ? 0 : reservationBid.hashCode());
172 return result;
173 }
174
175 @Override
176 public boolean equals(Object obj) {
177 if (this == obj)
178 return true;
179 if (obj == null)
180 return false;
181 if (getClass() != obj.getClass())
182 return false;
183 LinearAdditiveUtilitySpace other = (LinearAdditiveUtilitySpace) obj;
184 if (domain == null) {
185 if (other.domain != null)
186 return false;
187 } else if (!domain.equals(other.domain))
188 return false;
189 if (issueUtilities == null) {
190 if (other.issueUtilities != null)
191 return false;
192 } else if (!issueUtilities.equals(other.issueUtilities))
193 return false;
194 if (issueWeights == null) {
195 if (other.issueWeights != null)
196 return false;
197 } else if (!issueWeights.equals(other.issueWeights))
198 return false;
199 if (name == null) {
200 if (other.name != null)
201 return false;
202 } else if (!name.equals(other.name))
203 return false;
204 if (reservationBid == null) {
205 if (other.reservationBid != null)
206 return false;
207 } else if (!reservationBid.equals(other.reservationBid))
208 return false;
209 return true;
210 }
211
212 @Override
213 public Domain getDomain() {
214 return domain;
215 }
216
217 @Override
218 public String getName() {
219 return name;
220 }
221
222 @Override
223 public Map<String, ValueSetUtilities> getUtilities() {
224 return Collections.unmodifiableMap(issueUtilities);
225 }
226
227 @Override
228 public Map<String, BigDecimal> getWeights() {
229 return Collections.unmodifiableMap(issueWeights);
230 }
231
232 /**
233 *
234 * @param issue the issue to get weighted util for
235 * @param value the issue value to use (typically coming from a bid)
236 * @return weighted util of just the issue value:
237 * issueUtilities[issue].utility(value) * issueWeights[issue)
238 */
239 private BigDecimal util(String issue, Value value) {
240 return issueWeights.get(issue)
241 .multiply(issueUtilities.get(issue).getUtility(value));
242 }
243
244}
Note: See TracBrowser for help on using the repository browser.