package geniusweb.profile.utilityspace; import java.math.BigDecimal; import java.math.RoundingMode; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import geniusweb.issuevalue.NumberValue; import geniusweb.issuevalue.NumberValueSet; import geniusweb.issuevalue.Value; import geniusweb.issuevalue.ValueSet; import tudelft.utilities.immutablelist.Range; /** * The low and high values (from a {@link NumberValueSet} are given each a * different utility. This linearly interpolates in-between utility values. * */ @JsonAutoDetect(fieldVisibility = Visibility.ANY, getterVisibility = Visibility.NONE, setterVisibility = Visibility.NONE) public class NumberValueSetUtilities implements ValueSetUtilities { /** lowest possible value and utility at that point */ private final BigDecimal lowValue; private final BigDecimal lowUtility; /** * highest possible value and utility at that point */ private final BigDecimal highValue; private final BigDecimal highUtility; /** * * @param lowValue the low value of the {@link Range} * @param lowUtility the utility of the {@link #lowValue} * @param highValue the high value of the {@link Range}. Must be * >lowValue. * @param highUtility the utility of the {@link #highValue} */ @JsonCreator public NumberValueSetUtilities( @JsonProperty("lowValue") BigDecimal lowValue, @JsonProperty("lowUtility") BigDecimal lowUtility, @JsonProperty("highValue") BigDecimal highValue, @JsonProperty("highUtility") BigDecimal highUtility) { if (lowValue == null || highValue == null || lowUtility == null || highUtility == null) { throw new NullPointerException( "arguments lowValue, lowUtility, highValue and highUtility must be non-null"); } if (!isInZeroOne(lowUtility)) { throw new IllegalArgumentException("lowUtility must be in [0,1]"); } if (!isInZeroOne(highUtility)) { throw new IllegalArgumentException("highUtility must be in [0,1]"); } if (highValue.compareTo(lowValue) <= 0) { throw new IllegalArgumentException("highValue must be > lowValue"); } this.lowValue = lowValue; this.highValue = highValue; this.lowUtility = lowUtility; this.highUtility = highUtility; } @Override public BigDecimal getUtility(Value value) { if (!(value instanceof NumberValue)) { return BigDecimal.ZERO; } BigDecimal x = ((NumberValue) value).getValue(); if (x.compareTo(lowValue) < 0 || x.compareTo(highValue) > 0) return BigDecimal.ZERO; // we need to be careful to avoid round errors from divides. // so we return lowU + deltaU * (x-lowV) /deltaV BigDecimal deltaU = highUtility.subtract(lowUtility); BigDecimal deltaV = highValue.subtract(lowValue); return lowUtility.add(deltaU.multiply( x.subtract(lowValue).divide(deltaV, 8, RoundingMode.HALF_UP))); } @Override public String isFitting(ValueSet valueset) { if (!(valueset instanceof NumberValueSet)) { return "The utilities are for a number valueset but the given values are " + valueset; } NumberValueSet numvalset = (NumberValueSet) valueset; if (numvalset.getRange().getLow().compareTo(lowValue) != 0) { return "the utilities are specified down to " + lowValue + " but the valueset starts at " + numvalset.getRange().getLow(); } if (numvalset.getRange().getHigh().compareTo(highValue) != 0) { return "the utilities are specified up to " + highValue + " but the valueset ends at " + numvalset.getRange().getHigh(); } return null; } /** * @return the lowest value */ public BigDecimal getLowValue() { return lowValue; } /** * * @return the highest value */ public BigDecimal getHighValue() { return highValue; } /** * * @return the utility of the lowest value */ public BigDecimal getLowUtility() { return lowUtility; } /** * * @return the utility of the highest value */ public BigDecimal getHighUtility() { return highUtility; } @Override public String toString() { return "NumberValueSetUtilities(" + lowValue + "->" + lowUtility + "," + highValue + "->" + highUtility + ")"; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((highUtility == null) ? 0 : highUtility.hashCode()); result = prime * result + ((highValue == null) ? 0 : highValue.hashCode()); result = prime * result + ((lowUtility == null) ? 0 : lowUtility.hashCode()); result = prime * result + ((lowValue == null) ? 0 : lowValue.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; NumberValueSetUtilities other = (NumberValueSetUtilities) obj; if (highUtility == null) { if (other.highUtility != null) return false; } else if (!highUtility.equals(other.highUtility)) return false; if (highValue == null) { if (other.highValue != null) return false; } else if (!highValue.equals(other.highValue)) return false; if (lowUtility == null) { if (other.lowUtility != null) return false; } else if (!lowUtility.equals(other.lowUtility)) return false; if (lowValue == null) { if (other.lowValue != null) return false; } else if (!lowValue.equals(other.lowValue)) return false; return true; } /** * Check if value is in range [0,1] * * @param value * @return true if in range. */ private static boolean isInZeroOne(BigDecimal value) { return value.compareTo(BigDecimal.ZERO) >= 0 && value.compareTo(BigDecimal.ONE) <= 0; } }