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