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        }