#\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #\\ SAGE Class for Ethiopian Dinner Game v. 1.0 \\ #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #\\ Lionel Levine and Katherine E. Stange \\ #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ #\\ This is a class for use in SAGE which implements ethiopian dinner \\ #\\ games. \\ #\\ \\ #\\ For info, see http://math.katestange.net or contact \\ #\\ or . \\ #\\ \\ #\\ An ethiopian dinner (a "game") is a set of tuples (a,b) \\ #\\ representing morsels, where a is the value of the morsel to Alice \\ #\\ and b is the value of the morsel to Bob. We use the convention \\ #\\ that Bob always goes last, so that the first player depends on the \\ #\\ parity of the number of morsels in the game. \\ #\\ \\ #\\ Feel free to distribute this script. If you alter it, add a \\ #\\ description of the alteration. Acknowledge the original version. \\ #\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\ from sage.sets.set import is_Set class Dinner: """An ethiopian dinner game.""" my_crossout_score = None; my_all_scores = None; my_is_pareto = None; my_is_weak_pareto = None; my_crossout_move = None; my_bob_vs_crossout = None; my_alice_vs_crossout = None; my_is_nash = None; ########################################## ### The initialisation and representation ########################################## def __init__(self, *indata): if isinstance(indata[0], sage.rings.integer.Integer): n = indata[0]; self.my_size = indata[0]; p = Permutations(n).random_element(); G = Set( [] ); for i in range(1,n+1): G = G.union( Set( [ tuple([i,p(i)]) ] ) ); self.my_morsels = G; elif type(indata[0]) == sage.combinat.permutation.Permutation_class: n = len(indata[0]); G = Set( [] ); for i in range(1,n+1): G = G.union( Set( [ tuple([i,indata[0](i)]) ] ) ); self.my_morsels = G; self.my_size = n; elif is_Set(indata[0]): self.my_morsels = indata[0]; self.my_size = indata[0].cardinality(); else: print "Input an integer (for a random game of that size), a permutation, or a set of tuples." if self.my_size % 2 == 0: self.my_is_even = True; self.my_is_odd = False; else: self.my_is_even = False; self.my_is_odd = True; def __repr__(self): return "An ethiopian Dinner having %s morsels: %s" % (self.size(), self.morsels()) #################################### ### The following are user functions #################################### def morsels(self): ### Returns the morsels of the game as a set of tuples. return self.my_morsels def size(self): ### Returns the size of the game (number of morsels). return self.my_size def is_even(self): ### Returns True if the game is of even size (Alice goes first). return self.my_is_even def is_odd(self): ### Returns True if the game is of odd size (Bob goes first). return self.my_is_odd def crossout_score(self, verbose=False): ### Returns the score of this game played Crossout-vs-Crossout if verbose or self.my_crossout_score is None: self.my_crossout_score = self.crossout_score_internal(0, 0, verbose); return self.my_crossout_score; else: return self.my_crossout_score; def all_scores(self, verbose=False): ### Returns all possible scores if verbose or (self.my_all_scores is None): self.my_all_scores = self.all_scores_internal(verbose); return self.my_all_scores; else: return self.my_all_scores; def is_pareto(self): ### Returns True if the game is Pareto optimal if self.my_is_pareto is None: self.is_pareto_internal(); return self.my_is_pareto; else: return self.my_is_pareto; def is_weak_pareto(self): ### Returns True if the game is weakly Pareto optimal if self.my_is_pareto is None: self.is_pareto_internal(); return self.my_is_weak_pareto; else: return self.my_is_weak_pareto; def crossout_move(self, verbose=False): ### Returns the first player's next move according to crossout if verbose or self.my_crossout_move is None: self.my_crossout_move = self.crossout_move_internal(verbose); return self.my_crossout_move; else: return self.my_crossout_move; def bob_vs_crossout(self, verbose=False): ### Returns the set of scores of all games played against Alice's crossout if verbose or self.my_bob_vs_crossout is None: self.my_bob_vs_crossout = self.bob_vs_crossout_internal(verbose); return self.my_bob_vs_crossout; else: return self.my_bob_vs_crossout; def alice_vs_crossout(self, verbose=False): ### Returns the set of scores of all games played against Bob's crossout if self.my_alice_vs_crossout is None: self.my_alice_vs_crossout = self.alice_vs_crossout_internal(verbose); return self.my_alice_vs_crossout; else: return self.my_alice_vs_crossout; def is_nash(self): ### Returns True if the game is a Nash equilibrium for both players if self.my_is_nash is None: self.my_is_nash = self.is_nash_internal(); return self.my_is_nash; else: return self.my_is_nash; def show_all_scores(self): print self.morsels(); return self.set_show(self.all_scores(),"blue"); def show_bob_vs_crossout(self): print self.morsels(); return self.set_show(self.bob_vs_crossout(),"orange"); def show_alice_vs_crossout(self): print self.morsels(); return self.set_show(self.alice_vs_crossout(),"orange"); def show_all(self): print self.morsels(); b = self.bob_vs_crossout(); a = self.alice_vs_crossout(); p1 = self.set_show(self.all_scores(),"black"); p2 = self.set_show(b,"orange"); p3 = self.set_show(a,Color("green").lighter().lighter()); p4 = self.set_show(b.intersection(a),Color("green").lighter().lighter().blend(Color("orange"),0.55)); return(show(p1+p2+p3+p4,aspect_ratio=1)); #################################################### ### The following are meant to be internal functions #################################################### def crossout_score_internal(self, ascore, bscore, verbose=False): g = self.morsels(); if verbose: print "The game is: ", g, ", with Alice's score: ", ascore, ", and Bob's score: ", bscore, "."; if g.cardinality() == 0: return [ascore, bscore]; m = g.an_element(); for i in g: if i[0] < m[0]: m = i; minset = Set([m]); if verbose: print "Alice crosses off: ", m, "and Bob gets ", m[1], "points."; bscore = bscore + m[1]; g = g.symmetric_difference(minset); #print "The remaining game is: ", g; if g.cardinality() == 0: return [ascore, bscore]; m = g.an_element(); for i in g: if i[1] < m[1]: m = i; minset = Set([m]); if verbose: print "Bob crosses off: ", m, "and Alice gets ", m[0], "points."; ascore = ascore + m[0]; g = g.symmetric_difference(minset); if verbose: print "Current score: ", [ascore, bscore]; return( Dinner(g).crossout_score_internal(ascore, bscore, verbose) ); def all_scores_internal(self, verbose=False): g = self.morsels(); s = self.size(); if s%2 == 0: s = s/2; else: s = (s-1)/2; # the number of morsels Alice eats in a game scoreset = Set([]); for r in g.subsets(s): ascore = 0; bscore = 0; for m in r: ascore = ascore + m[0]; for m in g.symmetric_difference(r): bscore = bscore + m[1]; if verbose: print "Subset ", r, "has score", [ascore, bscore]; scoreset = scoreset.union(Set([tuple([ascore,bscore])])); return(scoreset); def is_pareto_internal(self): g = self.morsels(); scores = self.all_scores(); c = self.crossout_score(); self.my_is_pareto = True; self.my_is_weak_pareto = True; for s in scores: if s[0] > c[0] and s[1] >= c[1]: self.my_is_pareto = False; if s[1] > c[1] and s[0] >= c[0]: self.my_is_pareto = False; if s[0] > c[0] and s[1] > c[1]: self.my_is_weak_pareto = False; return; def crossout_move_internal(self, verbose=False): g = self.morsels(); if verbose: print "game is", g; m = g.an_element(); for i in g: if i[0] < m[0]: m = i; minset = Set([m]); g = g.symmetric_difference(minset); if verbose: print "alice crossed", m, "game is", g; if g.cardinality() == 0: return m; m = g.an_element(); for i in g: if i[1] < m[1]: m = i; minset = Set([m]); g = g.symmetric_difference(minset); if verbose: print "bob crossed", m, "game is", g; if g.cardinality() == 0: return m; return( Dinner(g).crossout_move_internal(verbose) ); def bob_vs_crossout_internal(self, verbose=False): g = self.morsels(); gsize = self.size(); if gsize == 0: return(Set([tuple([0,0])])); ascore = 0; if gsize%2 == 0: # if the game is even, take Alice's move m = Dinner(g).crossout_move_internal(); g = g.symmetric_difference(Set([m])); if verbose: print g; ascore = m[0]; global_scoreset = Set([]); for i in g: bscore = i[1]; local_scoreset = Dinner(g.symmetric_difference(Set([i]))).bob_vs_crossout_internal(verbose); for s in local_scoreset: newa = s[0]+ascore; newb = s[1]+bscore; local_scoreset = local_scoreset.symmetric_difference(Set([s])); local_scoreset = local_scoreset.union(Set([tuple([newa,newb])])); if verbose: print "local_scoreset: ", local_scoreset; global_scoreset = global_scoreset.union(local_scoreset); return(global_scoreset); def alice_vs_crossout_internal(self, verbose=False): g = self.morsels(); gsize = self.size(); if gsize == 0: return(Set([tuple([0,0])])); bscore = 0; if gsize%2 == 1: # if the game is odd, take Bob's move m = Dinner(g).crossout_move_internal(); g = g.symmetric_difference(Set([m])); if verbose: print g; bscore = m[1]; global_scoreset = Set([]); if g.cardinality() == 0: return( Set([tuple([0,bscore])]) ); for i in g: ascore = i[0]; local_scoreset = Dinner(g.symmetric_difference(Set([i]))).alice_vs_crossout_internal(verbose); for s in local_scoreset: newa = s[0]+ascore; newb = s[1]+bscore; local_scoreset = local_scoreset.symmetric_difference(Set([s])); local_scoreset = local_scoreset.union(Set([tuple([newa,newb])])); if verbose: print "local_scoreset: ", local_scoreset; global_scoreset = global_scoreset.union(local_scoreset); return(global_scoreset); def is_nash_internal(self): c = self.crossout_score(); for s in self.bob_vs_crossout(): if s[1] > c[1]: return False; for s in self.alice_vs_crossout(): if s[0] > c[0]: return False; return True; def set_show(self, scores, col="blue"): p = sage.plot.plot.Graphics(); for s in scores: p = p + point(s, color=col); p = p + point( self.crossout_score(), color="red"); return(p); ################################################ ### FUNCTIONS THAT YOU MIGHT USE WITH THIS CLASS ################################################ ### CHECKS OVER ALL GAMES OF A GIVEN SIZE def check_pareto(n): for s in Permutations(n): G = Dinner(s); if G.is_pareto() == False: print G; return "Done"; def check_weak_pareto(n): for s in Permutations(n): G = Dinner(s); if G.is_weak_pareto() == False: print G; return "Done"; def check_nash(n): for s in Permutations(n): G = Dinner(s); if G.is_nash() == False: print G; return "Done";