Source code: org/pokersource/enum/BeliefVector.java
1 // $Id: BeliefVector.java,v 1.5 2002/06/13 03:04:56 mjmaurer Exp $
2
3 package org.pokersource.enum;
4 import org.pokersource.game.Deck;
5 import java.util.HashSet;
6 import java.util.HashMap;
7 import java.util.Iterator;
8
9 /** Represents subjective beliefs about the possible hands held by a
10 player. Maintains a mapping from each hand to its probability of
11 occurrence.
12 @author Michael Maurer <mjmaurer@yahoo.com>
13 */
14
15 public abstract class BeliefVector {
16 /** Our string representation, from the constructor. */
17 private String myspec;
18
19 /** The universe of all possible hands. Should be set in the subclass'
20 constructor. Used by addRemaining() to know which hands haven't
21 yet been added. */
22 HandGroup universalGroup;
23
24 /** The belief probability (unconditioned by dead cards) of each hand group.
25 Hash key is HoldemHandGroup, value is Double. Positive value is the
26 relative probability compared to uniform Bayesian prior (so, a value of
27 +2 means hand from this group are twice as likely as would be expected
28 from a uniform distribution over all possible hands); a negative value
29 indicates an absolute probability (so -0.40 means that hands in this
30 group account for 40% of the total probability). Values must be either
31 all positive or all negative. */
32 private HashMap groupProb;
33
34 /** The bitmask of dead cards. This conditions the probabilities returned
35 by getBeliefProb(). */
36 private long deadCards;
37
38 /** The belief probability (unconditioned by dead cards) for each atomic
39 starting hand. Updated as needed by computeUnconditionedHandProb(). */
40 private HashMap uncondHandProb;
41
42 /** The belief probability (conditioned by dead cards) for each atomic
43 starting hand. Updated as needed by computeConditionedHandProb(). */
44 private HashMap condHandProb;
45
46 private boolean hasRelative = false;
47 private boolean hasAbsolute = false;
48
49 /** Instantiate self from string respresentation. Meant to be called from
50 subclass's constructor. The subclass constructor should then parse the
51 string spec and populate groupProb and deadCards. */
52 public BeliefVector(String spec) {
53 myspec = spec;
54 groupProb = new HashMap();
55 deadCards = 0;
56 uncondHandProb = null;
57 condHandProb = null;
58 }
59
60 /** Instantiate self from string respresentation. This method must be
61 implemented by all subclasses. Also, subclass constructors should call
62 super(spec) and then fromString(spec). */
63 public abstract void fromString(String spec);
64
65 /** Generate string representation of self; the inverse of fromString(). */
66 public String toString() {
67 StringBuffer buf = new StringBuffer();
68 for (Iterator iter = groupProb.keySet().iterator(); iter.hasNext(); ) {
69 HandGroup group = (HandGroup) iter.next();
70 if (buf.length() > 0)
71 buf.append(" ");
72 buf.append(group.toString());
73 double prob = ((Double) groupProb.get(group)).doubleValue();
74 String delim = (prob < 0) ? "=" : ":";
75 int percent = (int) Math.round(100 * Math.abs(prob));
76 buf.append(delim + percent);
77 }
78 if (deadCards != 0)
79 buf.append(" / " + Deck.cardMaskString(deadCards));
80 return buf.toString();
81 }
82
83 /** Generate a string representation of self that gives probability
84 details for all atomic hands, conditioned on the dead cards. */
85 public String toStringAtomic() {
86 StringBuffer buf = new StringBuffer();
87 for (Iterator iter = condHandProb.keySet().iterator(); iter.hasNext(); ) {
88 Long lhand = (Long) iter.next();
89 long hand = lhand.longValue();
90 double prob = ((Double) condHandProb.get(lhand)).doubleValue();
91 if (buf.length() > 1)
92 buf.append(" ");
93 buf.append(Deck.cardMaskString(hand, ""));
94 int percent = (int) Math.round(10000 * Math.abs(prob));
95 buf.append(":" + percent);
96 }
97 return buf.toString();
98 }
99
100
101 /** Return an array of bitmasks representing hands with nonzero probability
102 of occurring (conditioned on the dead cards). */
103 public long[] getHands() {
104 long[] hands = new long[condHandProb.size()];
105 int nhands = 0;
106 for (Iterator iter = condHandProb.keySet().iterator(); iter.hasNext(); )
107 hands[nhands++] = ((Long) iter.next()).longValue();
108 return hands;
109 }
110
111 /** Return the absolute probability that hand will occur, conditioned on
112 the dead cards. */
113 public double getBeliefProb(long hand) {
114 Double prob = (Double) condHandProb.get(new Long(hand));
115 if (prob == null)
116 return 0;
117 else
118 return prob.doubleValue();
119 }
120
121 private void computeConditionedHandProb() {
122 // adjust hand probabilities by eliminating hands that require a dead card
123 if (deadCards == 0) {
124 condHandProb = uncondHandProb;
125 return;
126 }
127 double deadProb = 0.0; // total prob of hands containing dead cards
128 for (Iterator iter = uncondHandProb.keySet().iterator(); iter.hasNext(); ) {
129 Long key = (Long) iter.next();
130 long hand = key.longValue();
131 Double value = (Double) uncondHandProb.get(key);
132 double hprob = value.doubleValue();
133 if ((hand & deadCards) != 0) { // hand uses a dead card
134 /*System.out.println(" condition: dead " + Deck.cardMaskString(hand) +
135 " hprob=" + hprob);*/
136 deadProb += hprob;
137 }
138 }
139 condHandProb = new HashMap();
140 if (deadProb > 0.999999)
141 throw new IllegalArgumentException("dead cards exclude all hands");
142 double condscale = 1 / (1 - deadProb);
143 /*System.out.println(" condition: deadProb=" + deadProb + ", condscale="
144 + condscale);*/
145 for (Iterator iter = uncondHandProb.keySet().iterator(); iter.hasNext(); ) {
146 Long key = (Long) iter.next();
147 long hand = key.longValue();
148 if ((hand & deadCards) == 0) { // hand does not use dead card
149 Double value = (Double) uncondHandProb.get(key);
150 double hprob = value.doubleValue();
151 double condprob = hprob * condscale;
152 /*System.out.println(" condition: update " + Deck.cardMaskString(hand) +
153 " condprob=" + condprob);*/
154 condHandProb.put(key, new Double(condprob));
155 }
156 }
157 }
158
159 private double totalRelativeProb() {
160 // sum over groups of each group's relative prob
161 // times the group's number of atomic hands
162 double totalRel = 0.0;
163 for (Iterator iter = groupProb.keySet().iterator(); iter.hasNext(); ) {
164 HandGroup group = (HandGroup) iter.next();
165 double gprob = ((Double) groupProb.get(group)).doubleValue();
166 int gsize = group.getHands().length;
167 if (gprob > 0)
168 totalRel += gprob * gsize;
169 }
170 return totalRel;
171 }
172
173 private double totalAbsoluteProb() {
174 // sum over groups of each group's absolte prob
175 double totalAbs = 0.0;
176 for (Iterator iter = groupProb.keySet().iterator(); iter.hasNext(); ) {
177 HandGroup group = (HandGroup) iter.next();
178 double gprob = ((Double) groupProb.get(group)).doubleValue();
179 if (gprob < 0)
180 totalAbs += -gprob;
181 }
182 return totalAbs;
183 }
184
185 private void computeUnconditionedHandProb() {
186 uncondHandProb = new HashMap();
187 double totalRel = totalRelativeProb();
188 /*System.out.println("recomputing: totalRel=" + totalRel);*/
189 for (Iterator iter = groupProb.keySet().iterator(); iter.hasNext(); ) {
190 HandGroup group = (HandGroup) iter.next();
191 long[] ghands = group.getHands();
192 double gprob = ((Double) groupProb.get(group)).doubleValue();
193 double hprob; // absolute probability of each hand in this group
194 if (gprob < 0) { // group has absolute probability
195 hprob = -gprob / ghands.length;
196 } else if (gprob > 0) { // group has relative probability
197 hprob = gprob / totalRel;
198 } else {
199 hprob = 0;
200 }
201 /*System.out.println(" : group=" + group.toString() +
202 ", gsize=" + ghands.length +
203 ", gprob=" + gprob +
204 ", hprob=" + hprob);*/
205 if (hprob > 0) {
206 for (int i=0; i<ghands.length; i++) {
207 Long key = new Long(ghands[i]);
208 Double value = new Double(hprob);
209 if (uncondHandProb.containsKey(key))
210 throw new IllegalArgumentException
211 ("duplicate hand: " + Deck.cardMaskString(ghands[i]));
212 uncondHandProb.put(key, value);
213 }
214 }
215 }
216 }
217
218 /** Set the "dead cards", cards that are known not to be available. This
219 sets the probability to zero of any hand including any of these cards
220 increases the probabilities of the other hands in proportion. */
221 public void setDeadCards(long cards) {
222 /*System.out.println("DEAD " + Deck.cardMaskString(cards));*/
223 deadCards = cards;
224 computeConditionedHandProb();
225 }
226
227 private void addHandGroup(HandGroup group, double prob) {
228 /*System.out.println("ADD group=" + group.toString() + ", prob=" + prob);*/
229 groupProb.put(group, new Double(prob));
230 computeUnconditionedHandProb();
231 computeConditionedHandProb();
232 }
233
234 /** During construction, add a new hand group with its probability of
235 occurrence relative to the Bayesian probability. Meant to be called by
236 subclass's fromString() */
237 void addHandGroupRelative(HandGroup group, double relativeProb) {
238 if (hasAbsolute)
239 throw new IllegalArgumentException("cannot mix relative and absolute probs");
240 addHandGroup(group, relativeProb);
241 hasRelative = true;
242 }
243
244 /** During construction, add a new hand group with its absolute probability
245 of occurrence. The sum of absolute probabilities over groups should be
246 1. Meant to be called by subclass's fromString(). */
247 void addHandGroupAbsolute(HandGroup group, double absoluteProb) {
248 if (hasRelative)
249 throw new IllegalArgumentException("cannot mix relative and absolute probs");
250 addHandGroup(group, -absoluteProb);
251 hasAbsolute = true;
252 }
253
254 private void addRemaining(double prob) {
255 /*System.out.println("ADD all remaining hands, prob=" + prob);*/
256 // Form a special hand group whose set of hands is the difference between
257 // the universe of possible hands and hands present in groups we have
258 // already added.
259 HandGroup others = new HandGroup();
260 others.myspec = "<other>";
261 others.myhands = new HashSet();
262 others.myhands.addAll(universalGroup.myhands);
263 for (Iterator iter = groupProb.keySet().iterator(); iter.hasNext(); ) {
264 HandGroup group = (HandGroup) iter.next();
265 others.myhands.removeAll(group.myhands);
266 }
267 groupProb.put(others, new Double(prob));
268 computeUnconditionedHandProb();
269 computeConditionedHandProb();
270 }
271
272 /** During construction, add each hand not yet added with its probability of
273 occurrence relative to its Bayesian probability. Meant to be called by
274 subclass's fromString() */
275 void addRemainingRelative(double relativeProb) {
276 if (hasAbsolute)
277 throw new IllegalArgumentException("cannot mix relative and absolute probs");
278 addRemaining(relativeProb);
279 hasRelative = true;
280 }
281
282 /** During construction, add each hand not yet added with the given absolute
283 probability of occurrence of the entire set of such hands. The sum of
284 absolute probabilities over groups should be 1. Meant to be called by
285 subclass's fromString(). */
286 void addRemainingAbsolute(double absoluteProb) {
287 if (hasRelative)
288 throw new IllegalArgumentException("cannot mix relative and absolute probs");
289 addRemaining(-absoluteProb);
290 hasAbsolute = true;
291 }
292
293 /** After construction, subclasses should call this for a sanity check. */
294 void validate() {
295 if (hasAbsolute) {
296 if (totalAbsoluteProb() != 1.0)
297 throw new IllegalArgumentException("absolute probabilities must sum to 1");
298 }
299 if (hasRelative) {
300 if (totalRelativeProb() <= 0)
301 throw new IllegalArgumentException("relative probabilities must be positive");
302 }
303 }
304 }