001 /*
002 Copyright (c) 1996-2009, Damon Hart-Davis
003 All rights reserved.
004
005 Redistribution and use in source and binary forms, with or without
006 modification, are permitted provided that the following conditions are
007 met:
008
009 * Redistributions of source code must retain the above copyright
010 notice, this list of conditions and the following disclaimer.
011
012 * Redistributions in binary form must reproduce the above copyright
013 notice, this list of conditions and the following disclaimer in the
014 documentation and/or other materials provided with the
015 distribution.
016
017 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
018 IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
019 TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
020 PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
021 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
022 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
023 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
024 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
025 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
026 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
027 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028 */
029
030 package org.hd.d.pg2k.ai.scorer;
031
032 import java.util.Comparator;
033
034 import org.hd.d.pg2k.svrCore.ExhibitPropsComputableMutable;
035 import org.hd.d.pg2k.svrCore.MemoryTools;
036
037 /**Immutable (and Internable) return type from scorer method.
038 * Allowed values run from -MAX (bad) through 0 (neutral) to +MAX (good) for score,
039 * and from 0 (no confidence) to +MAX (full) for confidence.
040 * <p>
041 * Note that the score values are symmetrical about zero.
042 * <p>
043 * The low-ish precision of the elements is deliberate
044 * to reflect the fact that scoring is bound to be fairly imprecise
045 * and to save a little space.
046 */
047 public final class ScoreAndConf implements MemoryTools.Internable
048 {
049 /**Maximum positive or negative value of any element; strictly positive. */
050 public static final short MAX = Short.MAX_VALUE;
051
052 /**Score: -MAX (bad) through 0 (neutral) to +MAX (good). */
053 public final short score;
054 /**Confidence: 0 (no confidence) to +MAX (full) for confidence. */
055 public final short confidence;
056
057 /**Construct an instance. */
058 public ScoreAndConf(final short score, final short confidence)
059 {
060 // We only need limited validation since we hit representation limits on +ve side.
061 if((score < -MAX) || (confidence < 0)) { throw new IllegalArgumentException(); }
062 this.score = score;
063 this.confidence = confidence;
064 }
065
066 /**Construct an instance using int values; requires extra checking. */
067 public ScoreAndConf(final int score, final int confidence)
068 {
069 this((short) score, (short) confidence);
070 // Validate post-hoc in case there were invalid msbits set.
071 if((score < -MAX) || (confidence < 0)) { throw new IllegalArgumentException(); }
072 if((score > +MAX) || (confidence > +MAX)) { throw new IllegalArgumentException(); }
073 }
074
075 /**The hash depends on all elements. */
076 @Override public int hashCode() { return((score * 32797) + confidence); }
077 /**Equality depends on all elements. */
078 @Override public boolean equals(final Object obj)
079 {
080 if(this == obj) { return(true); }
081 if(!(obj instanceof ScoreAndConf)) { return(false); }
082 final ScoreAndConf other = (ScoreAndConf) obj;
083 if(confidence != other.confidence) { return(false); }
084 if(score != other.score) { return(false); }
085 return(true);
086 }
087
088 /**Compute human-readable summary; never null.
089 * This is also designed to be parsable to (re)construct an instance
090 * as an alternative to serialisation.
091 * <p>
092 * This includes the scale (maximum) for non-zero values
093 * to allow for robust reconstruction in future versions.
094 */
095 @Override public String toString()
096 {
097 final StringBuilder sb = new StringBuilder(32);
098 sb.append("score=").append(score);
099 if(score != 0) { sb.append('/').append(MAX); }
100 sb.append(":confidence=").append(confidence);
101 if(confidence != 0) { sb.append('/').append(MAX); }
102 return(sb.toString());
103 }
104
105 /**Parse toString() representation to return ScoreAndConf value; never null.
106 * @throws IllegalArgumentException if the input is unparsable
107 */
108 public static ScoreAndConf fromString(final String s)
109 {
110 // Very quick initial check.
111 if((s == null) || !s.startsWith("score="))
112 { throw new IllegalArgumentException(); }
113 // Should be exactly one separator ':'.
114 final int firstColon = s.indexOf(':');
115 if((firstColon == -1) || (firstColon != s.lastIndexOf(':')))
116 { throw new IllegalArgumentException("should be exactly one ':'"); }
117
118 // Parse score component...
119 short score = 0;
120 // If the score is zero, then it has a fixed form without a divisor,
121 // else we must check that the divisor is correct and the value in range.
122 // TODO: we may be prepared to scale values with a different divisor in future.
123 if(!s.startsWith("score=0:"))
124 {
125 final int firstSlash = s.indexOf('/');
126 if((firstSlash == -1) || (firstSlash > firstColon))
127 { throw new IllegalArgumentException("missing / in non-zero score"); }
128 final short max = Short.parseShort(s.substring(firstSlash+1, firstColon), 10);
129 if(max != MAX)
130 { throw new IllegalArgumentException("score divisor does not match MAX"); }
131 score = Short.parseShort(s.substring(6, firstSlash), 10);
132 if((score < -MAX) || (score > +MAX))
133 { throw new IllegalArgumentException("score value out of range"); }
134 }
135
136 // Parse confidence component...
137 short conf = 0;
138 // If the confidence is zero, then it has a fixed form without a divisor,
139 // else we must check that the divisor is correct and the value in range.
140 // TODO: we may be prepared to scale values with a different divisor in future.
141 if(!s.endsWith(":confidence=0"))
142 {
143 final int lastSlash = s.lastIndexOf('/');
144 if((lastSlash == -1) || (lastSlash < firstColon))
145 { throw new IllegalArgumentException("missing / in non-zero confidence"); }
146 final short max = Short.parseShort(s.substring(lastSlash+1), 10);
147 if(max != MAX)
148 { throw new IllegalArgumentException("confidence divisor does not match MAX"); }
149 conf = Short.parseShort(s.substring(firstColon+12, lastSlash), 10);
150 if((conf < -MAX) || (conf > +MAX))
151 { throw new IllegalArgumentException("confidence value out of range"); }
152 }
153
154 if((score == 0) && (conf == 0)) { return(NO_OPINION); /* Avoid one object creation. */ }
155 return(new ScoreAndConf(score, conf));
156 }
157
158 /**Find out if this is notably good or bad using the same thresholds as for voting (etc) in the rest of the Gallery.
159 * Returns TRUE if rated good, FALSE if bad,
160 * null if not significantly either (ie too close to neutral) or if unknown.
161 * <p>
162 * This is based on the product of score and confidence.
163 */
164 public Boolean isGood()
165 {
166 final int goodness = (score * confidence * 2); // Normalise for compare against int threshold.
167 //System.err.println("goodness = " + goodness);
168 if((goodness < ExhibitPropsComputableMutable.GOODBAD_LIMIT_INT) &&
169 (goodness > -ExhibitPropsComputableMutable.GOODBAD_LIMIT_INT)) { return(null); }
170 return((goodness >= 0) ? Boolean.TRUE : Boolean.FALSE);
171 }
172
173 /**All-zeros value used to mean "no idea" / "no opinion" when returned by a Scorer. */
174 public static final ScoreAndConf NO_OPINION = new ScoreAndConf(0, 0);
175
176
177 /**Max (+ve) "goodness" value returned by computeScorerGoodness(); strictly positive. */
178 public static final int MAX_GOODNESS = MAX * MAX;
179
180 /**Compute Scorer "goodness" from its ScoreAndConfidence value.
181 * We want Scorers with a good correlation (+ve) and good confidence.
182 * <p>
183 * This is a unitless relative value used to sort Scorers
184 * in the range <i>[-MAX^2,+MAX^2]</i> ie <i>[-MAX_GOODNESS,+MAX_GOODNESS</i>
185 * where -ve is "wrongheaded", 0 is "useless", and +ve is "good".
186 */
187 public static int computeScorerGoodness(final ScoreAndConf sac)
188 {
189 if(sac.confidence == 0) { return(0); } // Relatively common case.
190 return(sac.score * sac.confidence);
191 }
192
193 /**A static Comparator that sorts by goodness. */
194 public static final Comparator<ScoreAndConf> ByGoodness = (new Comparator<ScoreAndConf>()
195 {
196 public int compare(final ScoreAndConf o1, final ScoreAndConf o2)
197 {
198 final int g1 = computeScorerGoodness(o1);
199 final int g2 = computeScorerGoodness(o2);
200 if(g1 < g2) { return(-1); }
201 if(g1 > g2) { return(+1); }
202 return(0);
203 }
204 });
205
206 /**A static Comparator that sorts by confidence. */
207 public static final Comparator<ScoreAndConf> ByConfidence = (new Comparator<ScoreAndConf>()
208 {
209 public int compare(final ScoreAndConf o1, final ScoreAndConf o2)
210 { return(o1.confidence - o2.confidence); }
211 });
212 }