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

Last change on this file since 29 was 29, checked in by bart, 3 years ago

some minor fixes

File size: 7.6 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 }
128
129 @Override
130 public BigDecimal getUtility(Bid bid) {
131 return issueWeights.keySet().stream()
132 .map(iss -> util(iss, bid.getValue(iss)))
133 .reduce(BigDecimal.ZERO, BigDecimal::add);
134 }
135
136 @Override
137 public BigDecimal getWeight(String issue) {
138 return issueWeights.get(issue);
139 }
140
141 @Override
142 public String toString() {
143 return "LinearAdditive[" + issueUtilities + "," + issueWeights + ","
144 + reservationBid + "]";
145 }
146
147 @Override
148 public Bid getReservationBid() {
149 return reservationBid;
150 }
151
152 @Override
153 public int hashCode() {
154 final int prime = 31;
155 int result = 1;
156 result = prime * result + ((domain == null) ? 0 : domain.hashCode());
157 result = prime * result
158 + ((issueUtilities == null) ? 0 : issueUtilities.hashCode());
159 result = prime * result
160 + ((issueWeights == null) ? 0 : issueWeights.hashCode());
161 result = prime * result + ((name == null) ? 0 : name.hashCode());
162 result = prime * result
163 + ((reservationBid == null) ? 0 : reservationBid.hashCode());
164 return result;
165 }
166
167 @Override
168 public boolean equals(Object obj) {
169 if (this == obj)
170 return true;
171 if (obj == null)
172 return false;
173 if (getClass() != obj.getClass())
174 return false;
175 LinearAdditiveUtilitySpace other = (LinearAdditiveUtilitySpace) obj;
176 if (domain == null) {
177 if (other.domain != null)
178 return false;
179 } else if (!domain.equals(other.domain))
180 return false;
181 if (issueUtilities == null) {
182 if (other.issueUtilities != null)
183 return false;
184 } else if (!issueUtilities.equals(other.issueUtilities))
185 return false;
186 if (issueWeights == null) {
187 if (other.issueWeights != null)
188 return false;
189 } else if (!issueWeights.equals(other.issueWeights))
190 return false;
191 if (name == null) {
192 if (other.name != null)
193 return false;
194 } else if (!name.equals(other.name))
195 return false;
196 if (reservationBid == null) {
197 if (other.reservationBid != null)
198 return false;
199 } else if (!reservationBid.equals(other.reservationBid))
200 return false;
201 return true;
202 }
203
204 @Override
205 public Domain getDomain() {
206 return domain;
207 }
208
209 @Override
210 public String getName() {
211 return name;
212 }
213
214 @Override
215 public Map<String, ValueSetUtilities> getUtilities() {
216 return Collections.unmodifiableMap(issueUtilities);
217 }
218
219 @Override
220 public Map<String, BigDecimal> getWeights() {
221 return Collections.unmodifiableMap(issueWeights);
222 }
223
224 /**
225 *
226 * @param issue the issue to get weighted util for
227 * @param value the issue value to use (typically coming from a bid)
228 * @return weighted util of just the issue value:
229 * issueUtilities[issue].utility(value) * issueWeights[issue)
230 */
231 private BigDecimal util(String issue, Value value) {
232 return issueWeights.get(issue)
233 .multiply(issueUtilities.get(issue).getUtility(value));
234 }
235
236}
Note: See TracBrowser for help on using the repository browser.