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

Last change on this file was 52, checked in by ruud, 13 months ago

Fixed small issues in domaineditor.

File size: 7.8 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 utils 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 weights 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 resBid 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> utils,
69 @JsonProperty("issueWeights") Map<String, BigDecimal> weights,
70 @JsonProperty("reservationBid") Bid resBid) {
71 this.domain = domain;
72 this.name = name;
73 this.reservationBid = resBid;
74 this.issueUtilities.putAll(utils);
75 this.issueWeights.putAll(weights);
76
77 if (domain == null) {
78 throw new NullPointerException("domain=null");
79 }
80 if (utils == null) {
81 throw new NullPointerException("utils=null");
82 }
83 if (weights == null) {
84 throw new NullPointerException("weights=null");
85 }
86 if (utils.values().contains(null)) {
87 throw new NullPointerException(
88 "One of the ValueSetUtilities in issueUtilitiesis null:"
89 + utils);
90 }
91 if (weights.values().contains(null)) {
92 throw new NullPointerException("One of the weights is null");
93 }
94 if (utils.keySet().contains(null)) {
95 throw new NullPointerException("One of the issue names is null");
96 }
97 if (name == null || !(name.matches("[a-zA-Z0-9]+"))) {
98 throw new IllegalArgumentException(
99 "Name must be simple (a-Z, 0-9) but got " + name);
100 }
101 if (!(utils.keySet().equals(domain.getIssues()))) {
102 throw new IllegalArgumentException(
103 "The issues in utilityspace and domain do not match: utilityspace has issues "
104 + utils.keySet() + " but domain contains "
105 + domain.getIssues());
106 }
107 if (!(weights.keySet().equals(domain.getIssues()))) {
108 throw new IllegalArgumentException(
109 "The issues in weights and domain do not match: weights has "
110 + weights.keySet() + " but domain contains "
111 + domain.getIssues());
112 }
113 for (String issue : issueUtilities.keySet()) {
114 String message = issueUtilities.get(issue)
115 .isFitting(domain.getValues(issue));
116 if (message != null)
117 throw new IllegalArgumentException(message);
118 }
119
120 BigDecimal sum = weights.values().stream().reduce(BigDecimal.ZERO,
121 BigDecimal::add);
122 if (BigDecimal.ONE.compareTo(sum) != 0) { // equals does NOT work for
123 // comparing BigDecimals!!
124 throw new IllegalArgumentException("The sum of the weights ("
125 + weights.values() + ") must be 1");
126 }
127 if (resBid != null) {
128 String message = domain.isFitting(resBid);
129 if (message != null)
130 throw new IllegalArgumentException(
131 "reservationbid is not fitting domain: " + message);
132 }
133 }
134
135 @Override
136 public BigDecimal getUtility(Bid bid) {
137 return issueWeights.keySet().stream()
138 .map(iss -> util(iss, bid.getValue(iss)))
139 .reduce(BigDecimal.ZERO, BigDecimal::add);
140 }
141
142 @Override
143 public BigDecimal getWeight(String issue) {
144 return issueWeights.get(issue);
145 }
146
147 @Override
148 public String toString() {
149 return "LinearAdditive[" + issueUtilities + "," + issueWeights + ","
150 + reservationBid + "]";
151 }
152
153 @Override
154 public Bid getReservationBid() {
155 return reservationBid;
156 }
157
158 @Override
159 public int hashCode() {
160 final int prime = 31;
161 int result = 1;
162 result = prime * result + ((domain == null) ? 0 : domain.hashCode());
163 result = prime * result
164 + ((issueUtilities == null) ? 0 : issueUtilities.hashCode());
165 result = prime * result
166 + ((issueWeights == null) ? 0 : issueWeights.hashCode());
167 result = prime * result + ((name == null) ? 0 : name.hashCode());
168 result = prime * result
169 + ((reservationBid == null) ? 0 : reservationBid.hashCode());
170 return result;
171 }
172
173 @Override
174 public boolean equals(Object obj) {
175 if (this == obj)
176 return true;
177 if (obj == null)
178 return false;
179 if (getClass() != obj.getClass())
180 return false;
181 LinearAdditiveUtilitySpace other = (LinearAdditiveUtilitySpace) obj;
182 if (domain == null) {
183 if (other.domain != null)
184 return false;
185 } else if (!domain.equals(other.domain))
186 return false;
187 if (issueUtilities == null) {
188 if (other.issueUtilities != null)
189 return false;
190 } else if (!issueUtilities.equals(other.issueUtilities))
191 return false;
192 if (issueWeights == null) {
193 if (other.issueWeights != null)
194 return false;
195 } else if (!issueWeights.equals(other.issueWeights))
196 return false;
197 if (name == null) {
198 if (other.name != null)
199 return false;
200 } else if (!name.equals(other.name))
201 return false;
202 if (reservationBid == null) {
203 if (other.reservationBid != null)
204 return false;
205 } else if (!reservationBid.equals(other.reservationBid))
206 return false;
207 return true;
208 }
209
210 @Override
211 public Domain getDomain() {
212 return domain;
213 }
214
215 @Override
216 public String getName() {
217 return name;
218 }
219
220 @Override
221 public Map<String, ValueSetUtilities> getUtilities() {
222 return Collections.unmodifiableMap(issueUtilities);
223 }
224
225 @Override
226 public Map<String, BigDecimal> getWeights() {
227 return Collections.unmodifiableMap(issueWeights);
228 }
229
230 /**
231 *
232 * @param issue the issue to get weighted util for
233 * @param value the issue value to use (typically coming from a bid)
234 * @return weighted util of just the issue value:
235 * issueUtilities[issue].utility(value) * issueWeights[issue)
236 */
237 private BigDecimal util(String issue, Value value) {
238 return issueWeights.get(issue)
239 .multiply(issueUtilities.get(issue).getUtility(value));
240 }
241
242}
Note: See TracBrowser for help on using the repository browser.