passagemath-polyhedra 10.6.37__cp314-cp314-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (209) hide show
  1. passagemath_polyhedra/__init__.py +3 -0
  2. passagemath_polyhedra-10.6.37.dist-info/METADATA +367 -0
  3. passagemath_polyhedra-10.6.37.dist-info/METADATA.bak +369 -0
  4. passagemath_polyhedra-10.6.37.dist-info/RECORD +209 -0
  5. passagemath_polyhedra-10.6.37.dist-info/WHEEL +5 -0
  6. passagemath_polyhedra-10.6.37.dist-info/top_level.txt +3 -0
  7. passagemath_polyhedra.libs/libgcc_s-0cd532bd.so.1 +0 -0
  8. passagemath_polyhedra.libs/libgmp-0e7fc84e.so.10.5.0 +0 -0
  9. passagemath_polyhedra.libs/libgomp-8949ffbe.so.1.0.0 +0 -0
  10. passagemath_polyhedra.libs/libstdc++-5d72f927.so.6.0.33 +0 -0
  11. sage/all__sagemath_polyhedra.py +50 -0
  12. sage/game_theory/all.py +8 -0
  13. sage/game_theory/catalog.py +6 -0
  14. sage/game_theory/catalog_normal_form_games.py +923 -0
  15. sage/game_theory/cooperative_game.py +844 -0
  16. sage/game_theory/matching_game.py +1181 -0
  17. sage/game_theory/normal_form_game.py +2697 -0
  18. sage/game_theory/parser.py +275 -0
  19. sage/geometry/all__sagemath_polyhedra.py +22 -0
  20. sage/geometry/cone.py +6940 -0
  21. sage/geometry/cone_catalog.py +847 -0
  22. sage/geometry/cone_critical_angles.py +1027 -0
  23. sage/geometry/convex_set.py +1119 -0
  24. sage/geometry/fan.py +3743 -0
  25. sage/geometry/fan_isomorphism.py +389 -0
  26. sage/geometry/fan_morphism.py +1884 -0
  27. sage/geometry/hasse_diagram.py +202 -0
  28. sage/geometry/hyperplane_arrangement/affine_subspace.py +390 -0
  29. sage/geometry/hyperplane_arrangement/all.py +1 -0
  30. sage/geometry/hyperplane_arrangement/arrangement.py +3905 -0
  31. sage/geometry/hyperplane_arrangement/check_freeness.py +145 -0
  32. sage/geometry/hyperplane_arrangement/hyperplane.py +773 -0
  33. sage/geometry/hyperplane_arrangement/library.py +825 -0
  34. sage/geometry/hyperplane_arrangement/ordered_arrangement.py +642 -0
  35. sage/geometry/hyperplane_arrangement/plot.py +520 -0
  36. sage/geometry/integral_points.py +35 -0
  37. sage/geometry/integral_points_generic_dense.cpython-314-x86_64-linux-musl.so +0 -0
  38. sage/geometry/integral_points_generic_dense.pyx +7 -0
  39. sage/geometry/lattice_polytope.py +5894 -0
  40. sage/geometry/linear_expression.py +773 -0
  41. sage/geometry/newton_polygon.py +767 -0
  42. sage/geometry/point_collection.cpython-314-x86_64-linux-musl.so +0 -0
  43. sage/geometry/point_collection.pyx +1008 -0
  44. sage/geometry/polyhedral_complex.py +2616 -0
  45. sage/geometry/polyhedron/all.py +8 -0
  46. sage/geometry/polyhedron/backend_cdd.py +460 -0
  47. sage/geometry/polyhedron/backend_cdd_rdf.py +231 -0
  48. sage/geometry/polyhedron/backend_field.py +347 -0
  49. sage/geometry/polyhedron/backend_normaliz.py +2503 -0
  50. sage/geometry/polyhedron/backend_number_field.py +168 -0
  51. sage/geometry/polyhedron/backend_polymake.py +765 -0
  52. sage/geometry/polyhedron/backend_ppl.py +582 -0
  53. sage/geometry/polyhedron/base.py +1206 -0
  54. sage/geometry/polyhedron/base0.py +1444 -0
  55. sage/geometry/polyhedron/base1.py +886 -0
  56. sage/geometry/polyhedron/base2.py +812 -0
  57. sage/geometry/polyhedron/base3.py +1845 -0
  58. sage/geometry/polyhedron/base4.py +1262 -0
  59. sage/geometry/polyhedron/base5.py +2700 -0
  60. sage/geometry/polyhedron/base6.py +1741 -0
  61. sage/geometry/polyhedron/base7.py +997 -0
  62. sage/geometry/polyhedron/base_QQ.py +1258 -0
  63. sage/geometry/polyhedron/base_RDF.py +98 -0
  64. sage/geometry/polyhedron/base_ZZ.py +934 -0
  65. sage/geometry/polyhedron/base_mutable.py +215 -0
  66. sage/geometry/polyhedron/base_number_field.py +122 -0
  67. sage/geometry/polyhedron/cdd_file_format.py +155 -0
  68. sage/geometry/polyhedron/combinatorial_polyhedron/all.py +1 -0
  69. sage/geometry/polyhedron/combinatorial_polyhedron/base.cpython-314-x86_64-linux-musl.so +0 -0
  70. sage/geometry/polyhedron/combinatorial_polyhedron/base.pxd +76 -0
  71. sage/geometry/polyhedron/combinatorial_polyhedron/base.pyx +3859 -0
  72. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.cpython-314-x86_64-linux-musl.so +0 -0
  73. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pxd +39 -0
  74. sage/geometry/polyhedron/combinatorial_polyhedron/combinatorial_face.pyx +1038 -0
  75. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.cpython-314-x86_64-linux-musl.so +0 -0
  76. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pxd +9 -0
  77. sage/geometry/polyhedron/combinatorial_polyhedron/conversions.pyx +501 -0
  78. sage/geometry/polyhedron/combinatorial_polyhedron/face_data_structure.pxd +207 -0
  79. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.cpython-314-x86_64-linux-musl.so +0 -0
  80. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pxd +102 -0
  81. sage/geometry/polyhedron/combinatorial_polyhedron/face_iterator.pyx +2274 -0
  82. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.cpython-314-x86_64-linux-musl.so +0 -0
  83. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pxd +370 -0
  84. sage/geometry/polyhedron/combinatorial_polyhedron/face_list_data_structure.pyx +84 -0
  85. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.cpython-314-x86_64-linux-musl.so +0 -0
  86. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pxd +31 -0
  87. sage/geometry/polyhedron/combinatorial_polyhedron/list_of_faces.pyx +587 -0
  88. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.cpython-314-x86_64-linux-musl.so +0 -0
  89. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pxd +52 -0
  90. sage/geometry/polyhedron/combinatorial_polyhedron/polyhedron_face_lattice.pyx +560 -0
  91. sage/geometry/polyhedron/constructor.py +773 -0
  92. sage/geometry/polyhedron/double_description.py +753 -0
  93. sage/geometry/polyhedron/double_description_inhomogeneous.py +564 -0
  94. sage/geometry/polyhedron/face.py +1060 -0
  95. sage/geometry/polyhedron/generating_function.py +1810 -0
  96. sage/geometry/polyhedron/lattice_euclidean_group_element.py +178 -0
  97. sage/geometry/polyhedron/library.py +3502 -0
  98. sage/geometry/polyhedron/misc.py +121 -0
  99. sage/geometry/polyhedron/modules/all.py +1 -0
  100. sage/geometry/polyhedron/modules/formal_polyhedra_module.py +155 -0
  101. sage/geometry/polyhedron/palp_database.py +447 -0
  102. sage/geometry/polyhedron/parent.py +1279 -0
  103. sage/geometry/polyhedron/plot.py +1986 -0
  104. sage/geometry/polyhedron/ppl_lattice_polygon.py +556 -0
  105. sage/geometry/polyhedron/ppl_lattice_polytope.py +1257 -0
  106. sage/geometry/polyhedron/representation.py +1723 -0
  107. sage/geometry/pseudolines.py +515 -0
  108. sage/geometry/relative_interior.py +445 -0
  109. sage/geometry/toric_plotter.py +1103 -0
  110. sage/geometry/triangulation/all.py +2 -0
  111. sage/geometry/triangulation/base.cpython-314-x86_64-linux-musl.so +0 -0
  112. sage/geometry/triangulation/base.pyx +963 -0
  113. sage/geometry/triangulation/data.h +147 -0
  114. sage/geometry/triangulation/data.pxd +4 -0
  115. sage/geometry/triangulation/element.py +914 -0
  116. sage/geometry/triangulation/functions.h +10 -0
  117. sage/geometry/triangulation/functions.pxd +4 -0
  118. sage/geometry/triangulation/point_configuration.py +2256 -0
  119. sage/geometry/triangulation/triangulations.h +49 -0
  120. sage/geometry/triangulation/triangulations.pxd +7 -0
  121. sage/geometry/voronoi_diagram.py +319 -0
  122. sage/interfaces/all__sagemath_polyhedra.py +1 -0
  123. sage/interfaces/polymake.py +2028 -0
  124. sage/numerical/all.py +13 -0
  125. sage/numerical/all__sagemath_polyhedra.py +11 -0
  126. sage/numerical/backends/all.py +1 -0
  127. sage/numerical/backends/all__sagemath_polyhedra.py +1 -0
  128. sage/numerical/backends/cvxopt_backend.cpython-314-x86_64-linux-musl.so +0 -0
  129. sage/numerical/backends/cvxopt_backend.pyx +1006 -0
  130. sage/numerical/backends/cvxopt_backend_test.py +19 -0
  131. sage/numerical/backends/cvxopt_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  132. sage/numerical/backends/cvxopt_sdp_backend.pyx +382 -0
  133. sage/numerical/backends/cvxpy_backend.cpython-314-x86_64-linux-musl.so +0 -0
  134. sage/numerical/backends/cvxpy_backend.pxd +41 -0
  135. sage/numerical/backends/cvxpy_backend.pyx +934 -0
  136. sage/numerical/backends/cvxpy_backend_test.py +13 -0
  137. sage/numerical/backends/generic_backend_test.py +24 -0
  138. sage/numerical/backends/interactivelp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  139. sage/numerical/backends/interactivelp_backend.pxd +36 -0
  140. sage/numerical/backends/interactivelp_backend.pyx +1231 -0
  141. sage/numerical/backends/interactivelp_backend_test.py +12 -0
  142. sage/numerical/backends/logging_backend.py +391 -0
  143. sage/numerical/backends/matrix_sdp_backend.cpython-314-x86_64-linux-musl.so +0 -0
  144. sage/numerical/backends/matrix_sdp_backend.pxd +15 -0
  145. sage/numerical/backends/matrix_sdp_backend.pyx +478 -0
  146. sage/numerical/backends/ppl_backend.cpython-314-x86_64-linux-musl.so +0 -0
  147. sage/numerical/backends/ppl_backend.pyx +1126 -0
  148. sage/numerical/backends/ppl_backend_test.py +13 -0
  149. sage/numerical/backends/scip_backend.cpython-314-x86_64-linux-musl.so +0 -0
  150. sage/numerical/backends/scip_backend.pxd +22 -0
  151. sage/numerical/backends/scip_backend.pyx +1289 -0
  152. sage/numerical/backends/scip_backend_test.py +13 -0
  153. sage/numerical/interactive_simplex_method.py +5338 -0
  154. sage/numerical/knapsack.py +665 -0
  155. sage/numerical/linear_functions.cpython-314-x86_64-linux-musl.so +0 -0
  156. sage/numerical/linear_functions.pxd +31 -0
  157. sage/numerical/linear_functions.pyx +1648 -0
  158. sage/numerical/linear_tensor.py +470 -0
  159. sage/numerical/linear_tensor_constraints.py +448 -0
  160. sage/numerical/linear_tensor_element.cpython-314-x86_64-linux-musl.so +0 -0
  161. sage/numerical/linear_tensor_element.pxd +6 -0
  162. sage/numerical/linear_tensor_element.pyx +459 -0
  163. sage/numerical/mip.cpython-314-x86_64-linux-musl.so +0 -0
  164. sage/numerical/mip.pxd +40 -0
  165. sage/numerical/mip.pyx +3667 -0
  166. sage/numerical/sdp.cpython-314-x86_64-linux-musl.so +0 -0
  167. sage/numerical/sdp.pxd +39 -0
  168. sage/numerical/sdp.pyx +1433 -0
  169. sage/rings/all__sagemath_polyhedra.py +3 -0
  170. sage/rings/polynomial/all__sagemath_polyhedra.py +10 -0
  171. sage/rings/polynomial/omega.py +982 -0
  172. sage/schemes/all__sagemath_polyhedra.py +2 -0
  173. sage/schemes/toric/all.py +10 -0
  174. sage/schemes/toric/chow_group.py +1248 -0
  175. sage/schemes/toric/divisor.py +2082 -0
  176. sage/schemes/toric/divisor_class.cpython-314-x86_64-linux-musl.so +0 -0
  177. sage/schemes/toric/divisor_class.pyx +322 -0
  178. sage/schemes/toric/fano_variety.py +1606 -0
  179. sage/schemes/toric/homset.py +650 -0
  180. sage/schemes/toric/ideal.py +451 -0
  181. sage/schemes/toric/library.py +1322 -0
  182. sage/schemes/toric/morphism.py +1958 -0
  183. sage/schemes/toric/points.py +1032 -0
  184. sage/schemes/toric/sheaf/all.py +1 -0
  185. sage/schemes/toric/sheaf/constructor.py +302 -0
  186. sage/schemes/toric/sheaf/klyachko.py +921 -0
  187. sage/schemes/toric/toric_subscheme.py +905 -0
  188. sage/schemes/toric/variety.py +3460 -0
  189. sage/schemes/toric/weierstrass.py +1078 -0
  190. sage/schemes/toric/weierstrass_covering.py +457 -0
  191. sage/schemes/toric/weierstrass_higher.py +288 -0
  192. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.info +10 -0
  193. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v03 +0 -0
  194. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v04 +0 -0
  195. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v05 +1 -0
  196. sage_wheels/share/reflexive_polytopes/Full2d/zzdb.v06 +1 -0
  197. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.info +22 -0
  198. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v04 +0 -0
  199. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v05 +0 -0
  200. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v06 +0 -0
  201. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v07 +0 -0
  202. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v08 +0 -0
  203. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v09 +0 -0
  204. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v10 +0 -0
  205. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v11 +1 -0
  206. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v12 +1 -0
  207. sage_wheels/share/reflexive_polytopes/Full3d/zzdb.v13 +1 -0
  208. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_2d +80 -0
  209. sage_wheels/share/reflexive_polytopes/reflexive_polytopes_3d +37977 -0
@@ -0,0 +1,1181 @@
1
+ # sage_setup: distribution = sagemath-polyhedra
2
+ # sage.doctest: needs sage.graphs
3
+ """
4
+ Matching games
5
+
6
+ This module implements a class for matching games (stable marriage problems)
7
+ [DI1989]_. At present the extended Gale-Shapley algorithm is implemented
8
+ which can be used to obtain stable matchings.
9
+
10
+ AUTHORS:
11
+
12
+ - James Campbell and Vince Knight 06-2014: Original version
13
+ """
14
+
15
+ # ****************************************************************************
16
+ # Copyright (C) 2014 James Campbell james.campbell@tanti.org.uk
17
+ #
18
+ # This program is free software: you can redistribute it and/or modify
19
+ # it under the terms of the GNU General Public License as published by
20
+ # the Free Software Foundation, either version 3 of the License, or
21
+ # (at your option) any later version.
22
+ # https://www.gnu.org/licenses/
23
+ # ****************************************************************************
24
+ from sage.structure.sage_object import SageObject
25
+ from sage.rings.integer_ring import ZZ
26
+ from copy import deepcopy
27
+ from sage.graphs.bipartite_graph import BipartiteGraph
28
+
29
+
30
+ class MatchingGame(SageObject):
31
+ r"""
32
+ A matching game.
33
+
34
+ A matching game (also called a stable matching problem) models a situation
35
+ in a population of `N` suitors and `N` reviewers. Suitors and reviewers
36
+ rank their preferences and attempt to find a match.
37
+
38
+ Formally, a matching game of size `N` is defined by two disjoint sets `S`
39
+ and `R` of size `N`. Associated to each element of `S` and `R` is a
40
+ preference list:
41
+
42
+ .. MATH::
43
+
44
+ f : S \to R^N
45
+ \text{ and }
46
+ g : R \to S^N.
47
+
48
+ Here is an example of matching game on 4 players:
49
+
50
+ .. MATH::
51
+
52
+ S = \{J, K, L, M\}, \\
53
+ R = \{A, B, C, D\}.
54
+
55
+ With preference functions:
56
+
57
+ .. MATH::
58
+
59
+ f(s) = \begin{cases}
60
+ (A, D, C, B) & \text{ if } s=J,\\
61
+ (A, B, C, D) & \text{ if } s=K,\\
62
+ (B, D, C, A) & \text{ if } s=L,\\
63
+ (C, A, B, D) & \text{ if } s=M,\\
64
+ \end{cases}
65
+
66
+ g(s) = \begin{cases}
67
+ (L, J, K, M) & \text{ if } s=A,\\
68
+ (J, M, L, K) & \text{ if } s=B,\\
69
+ (K, M, L, J) & \text{ if } s=C,\\
70
+ (M, K, J, L) & \text{ if } s=D.\\
71
+ \end{cases}
72
+
73
+ INPUT:
74
+
75
+ Two potential inputs are accepted (see below to see the effect of each):
76
+
77
+ - ``reviewer/suitors_preferences`` -- dictionary containing the
78
+ preferences of all players:
79
+
80
+ * key - each reviewer/suitors
81
+ * value - a tuple of suitors/reviewers
82
+
83
+ OR:
84
+
85
+ - ``integer`` -- integer simply representing the number of reviewers
86
+ and suitors
87
+
88
+ To implement the above game in Sage::
89
+
90
+ sage: suitr_pref = {'J': ('A', 'D', 'C', 'B'),
91
+ ....: 'K': ('A', 'B', 'C', 'D'),
92
+ ....: 'L': ('B', 'D', 'C', 'A'),
93
+ ....: 'M': ('C', 'A', 'B', 'D')}
94
+ sage: reviewr_pref = {'A': ('L', 'J', 'K', 'M'),
95
+ ....: 'B': ('J', 'M', 'L', 'K'),
96
+ ....: 'C': ('K', 'M', 'L', 'J'),
97
+ ....: 'D': ('M', 'K', 'J', 'L')}
98
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
99
+ sage: m
100
+ A matching game with 4 suitors and 4 reviewers
101
+ sage: m.suitors()
102
+ ('J', 'K', 'L', 'M')
103
+ sage: m.reviewers()
104
+ ('A', 'B', 'C', 'D')
105
+
106
+ A matching `M` is any bijection between `S` and `R`. If `s \in S` and
107
+ `r \in R` are matched by `M` we denote:
108
+
109
+ .. MATH::
110
+
111
+ M(s) = r.
112
+
113
+ On any given matching game, one intends to find a matching that is stable.
114
+ In other words, so that no one individual has an incentive to break their
115
+ current match.
116
+
117
+ Formally, a stable matching is a matching that has no blocking pairs.
118
+ A blocking pair is any pair `(s, r)` such that `M(s) \neq r` but `s`
119
+ prefers `r` to `M(r)` and `r` prefers `s` to `M^{-1}(r)`.
120
+
121
+ To obtain the stable matching in Sage we use the ``solve`` method which
122
+ uses the extended Gale-Shapley algorithm [DI1989]_::
123
+
124
+ sage: m.solve()
125
+ {'J': 'A', 'K': 'C', 'L': 'D', 'M': 'B'}
126
+
127
+ Matchings have a natural representations as bipartite graphs::
128
+
129
+ sage: plot(m) # needs sage.plot
130
+ Graphics object consisting of 13 graphics primitives
131
+
132
+ The above plots the bipartite graph associated with the matching.
133
+ This plot can be accessed directly::
134
+
135
+ sage: graph = m.bipartite_graph()
136
+ sage: graph
137
+ Bipartite graph on 8 vertices
138
+
139
+ It is possible to initiate a matching game without having to name each
140
+ suitor and reviewer::
141
+
142
+ sage: n = 8
143
+ sage: big_game = MatchingGame(n)
144
+ sage: big_game.suitors()
145
+ (1, 2, 3, 4, 5, 6, 7, 8)
146
+ sage: big_game.reviewers()
147
+ (-1, -2, -3, -4, -5, -6, -7, -8)
148
+
149
+ If we attempt to obtain the stable matching for the above game,
150
+ without defining the preference function we obtain an error::
151
+
152
+ sage: big_game.solve()
153
+ Traceback (most recent call last):
154
+ ...
155
+ ValueError: suitor preferences are not complete
156
+
157
+ To continue we have to populate the preference dictionary. Here
158
+ is one example where the preferences are simply the corresponding
159
+ element of the permutation group::
160
+
161
+ sage: from itertools import permutations
162
+ sage: suitr_preferences = list(permutations([-i-1 for i in range(n)]))
163
+ sage: revr_preferences = list(permutations([i+1 for i in range(n)]))
164
+ sage: for player in range(n):
165
+ ....: big_game.suitors()[player].pref = suitr_preferences[player]
166
+ ....: big_game.reviewers()[player].pref = revr_preferences[-player]
167
+ sage: big_game.solve()
168
+ {1: -1, 2: -8, 3: -6, 4: -7, 5: -5, 6: -4, 7: -3, 8: -2}
169
+
170
+ Note that we can also combine the two ways of creating a game. For example
171
+ here is an initial matching game::
172
+
173
+ sage: suitrs = {'Romeo': ('Juliet', 'Rosaline'),
174
+ ....: 'Mercutio': ('Juliet', 'Rosaline')}
175
+ sage: revwrs = {'Juliet': ('Romeo', 'Mercutio'),
176
+ ....: 'Rosaline': ('Mercutio', 'Romeo')}
177
+ sage: g = MatchingGame(suitrs, revwrs)
178
+
179
+ Let us assume that all of a sudden a new pair of suitors and reviewers is
180
+ added but their names are not known::
181
+
182
+ sage: g.add_reviewer()
183
+ sage: g.add_suitor()
184
+ sage: g.reviewers()
185
+ (-3, 'Juliet', 'Rosaline')
186
+ sage: g.suitors()
187
+ (3, 'Mercutio', 'Romeo')
188
+
189
+ Note that when adding a reviewer or a suitor all preferences are wiped::
190
+
191
+ sage: [s.pref for s in g.suitors()]
192
+ [[], [], []]
193
+ sage: [r.pref for r in g.reviewers()]
194
+ [[], [], []]
195
+
196
+ If we now try to solve the game we will get an error as we have not
197
+ specified the preferences which will need to be updated::
198
+
199
+ sage: g.solve()
200
+ Traceback (most recent call last):
201
+ ...
202
+ ValueError: suitor preferences are not complete
203
+
204
+ Here we update the preferences so that the new reviewers and suitors
205
+ do not affect things too much (they prefer each other and are the least
206
+ preferred of the others)::
207
+
208
+ sage: g.suitors()[1].pref = suitrs['Mercutio'] + (-3,)
209
+ sage: g.suitors()[2].pref = suitrs['Romeo'] + (-3,)
210
+ sage: g.suitors()[0].pref = (-3, 'Juliet', 'Rosaline')
211
+ sage: g.reviewers()[2].pref = revwrs['Rosaline'] + (3,)
212
+ sage: g.reviewers()[1].pref = revwrs['Juliet'] + (3,)
213
+ sage: g.reviewers()[0].pref = (3, 'Romeo', 'Mercutio')
214
+
215
+ Now the game can be solved::
216
+
217
+ sage: D = g.solve()
218
+ sage: D['Mercutio']
219
+ 'Rosaline'
220
+ sage: D['Romeo']
221
+ 'Juliet'
222
+ sage: D[3]
223
+ -3
224
+
225
+ Note that the above could be equivalently (and more simply) carried out
226
+ by simply updated the original preference dictionaries::
227
+
228
+ sage: for key in suitrs:
229
+ ....: suitrs[key] = suitrs[key] + (-3,)
230
+ sage: for key in revwrs:
231
+ ....: revwrs[key] = revwrs[key] + (3,)
232
+ sage: suitrs[3] = (-3, 'Juliet', 'Rosaline')
233
+ sage: revwrs[-3] = (3, 'Romeo', 'Mercutio')
234
+ sage: g = MatchingGame(suitrs, revwrs)
235
+ sage: D = g.solve()
236
+ sage: D['Mercutio']
237
+ 'Rosaline'
238
+ sage: D['Romeo']
239
+ 'Juliet'
240
+ sage: D[3]
241
+ -3
242
+
243
+ It can be shown that the Gale-Shapley algorithm will return the stable
244
+ matching that is optimal from the point of view of the suitors and is in
245
+ fact the worst possible matching from the point of view of the reviewers.
246
+ To quickly obtain the matching that is optimal for the reviewers we
247
+ use the ``solve`` method with the ``invert=True`` option::
248
+
249
+ sage: left_dict = {'a': ('A', 'B', 'C'),
250
+ ....: 'b': ('B', 'C', 'A'),
251
+ ....: 'c': ('B', 'A', 'C')}
252
+ sage: right_dict = {'A': ('b', 'c', 'a'),
253
+ ....: 'B': ('a', 'c', 'b'),
254
+ ....: 'C': ('a', 'b', 'c')}
255
+ sage: quick_game = MatchingGame([left_dict, right_dict])
256
+ sage: quick_game.solve()
257
+ {'a': 'A', 'b': 'C', 'c': 'B'}
258
+ sage: quick_game.solve(invert=True)
259
+ {'A': 'c', 'B': 'a', 'C': 'b'}
260
+
261
+ EXAMPLES:
262
+
263
+ 8 player letter game::
264
+
265
+ sage: suitr_pref = {'J': ('A', 'D', 'C', 'B'),
266
+ ....: 'K': ('A', 'B', 'C', 'D'),
267
+ ....: 'L': ('B', 'D', 'C', 'A'),
268
+ ....: 'M': ('C', 'A', 'B', 'D')}
269
+ sage: reviewr_pref = {'A': ('L', 'J', 'K', 'M'),
270
+ ....: 'B': ('J', 'M', 'L', 'K'),
271
+ ....: 'C': ('K', 'M', 'L', 'J'),
272
+ ....: 'D': ('M', 'K', 'J', 'L')}
273
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
274
+ sage: m.suitors()
275
+ ('J', 'K', 'L', 'M')
276
+ sage: m.reviewers()
277
+ ('A', 'B', 'C', 'D')
278
+
279
+ Also works for numbers::
280
+
281
+ sage: suit = {0: (3, 4),
282
+ ....: 1: (3, 4)}
283
+ sage: revr = {3: (0, 1),
284
+ ....: 4: (1, 0)}
285
+ sage: g = MatchingGame([suit, revr])
286
+
287
+ Can create a game from an integer. This gives default set of preference
288
+ functions::
289
+
290
+ sage: g = MatchingGame(3)
291
+ sage: g
292
+ A matching game with 3 suitors and 3 reviewers
293
+
294
+ We have an empty set of preferences for a default named set of
295
+ preferences::
296
+
297
+ sage: for s in g.suitors():
298
+ ....: s, s.pref
299
+ (1, [])
300
+ (2, [])
301
+ (3, [])
302
+ sage: for r in g.reviewers():
303
+ ....: r, r.pref
304
+ (-1, [])
305
+ (-2, [])
306
+ (-3, [])
307
+
308
+ Before trying to solve such a game the algorithm will check if it is
309
+ complete or not::
310
+
311
+ sage: g.solve()
312
+ Traceback (most recent call last):
313
+ ...
314
+ ValueError: suitor preferences are not complete
315
+
316
+ To be able to obtain the stable matching we must input the preferences::
317
+
318
+ sage: for s in g.suitors():
319
+ ....: s.pref = (-1, -2, -3)
320
+ sage: for r in g.reviewers():
321
+ ....: r.pref = (1, 2, 3)
322
+ sage: g.solve()
323
+ {1: -1, 2: -2, 3: -3}
324
+ """
325
+ def __init__(self, generator, revr=None):
326
+ r"""
327
+ Initialize a matching game and check the inputs.
328
+
329
+ TESTS::
330
+
331
+ sage: suit = {0: (3, 4), 1: (3, 4)}
332
+ sage: revr = {3: (0, 1), 4: (1, 0)}
333
+ sage: g = MatchingGame([suit, revr])
334
+ sage: TestSuite(g).run()
335
+
336
+ sage: g = MatchingGame(3)
337
+ sage: TestSuite(g).run()
338
+
339
+ sage: g2 = MatchingGame(QQ(3))
340
+ sage: g == g2
341
+ True
342
+
343
+ The above shows that the input can be either two dictionaries
344
+ or an integer::
345
+
346
+ sage: g = MatchingGame(suit, 3)
347
+ Traceback (most recent call last):
348
+ ...
349
+ TypeError: generator must be an integer or a pair of 2 dictionaries
350
+
351
+ sage: g = MatchingGame(matrix(2, [1, 2, 3, 4]))
352
+ Traceback (most recent call last):
353
+ ...
354
+ TypeError: generator must be an integer or a pair of 2 dictionaries
355
+
356
+ sage: g = MatchingGame('1,2,3', 'A,B,C')
357
+ Traceback (most recent call last):
358
+ ...
359
+ TypeError: generator must be an integer or a pair of 2 dictionaries
360
+ """
361
+ self._suitors = []
362
+ self._reviewers = []
363
+ if revr is not None:
364
+ generator = [generator, revr]
365
+
366
+ if generator in ZZ:
367
+ for i in range(generator):
368
+ self.add_suitor()
369
+ self.add_reviewer()
370
+ elif isinstance(generator[0], dict) and isinstance(generator[1], dict):
371
+ for i in generator[0]:
372
+ self.add_suitor(i)
373
+ for k in generator[1]:
374
+ self.add_reviewer(k)
375
+
376
+ for i in self._suitors:
377
+ i.pref = generator[0][i._name]
378
+ for k in self._reviewers:
379
+ k.pref = generator[1][k._name]
380
+ else:
381
+ raise TypeError("generator must be an integer or a pair of 2 dictionaries")
382
+
383
+ def _repr_(self):
384
+ r"""
385
+ Return a basic representation of the game stating how many
386
+ players are in the game.
387
+
388
+ EXAMPLES:
389
+
390
+ Matching game with 2 reviewers and 2 suitors::
391
+
392
+ sage: M = MatchingGame(2)
393
+ sage: M
394
+ A matching game with 2 suitors and 2 reviewers
395
+ """
396
+ txt = 'A matching game with {} suitors and {} reviewers'
397
+ return txt.format(len(self._suitors), len(self._reviewers))
398
+
399
+ def _latex_(self):
400
+ r"""
401
+ Create the LaTeX representation of the dictionaries for suitors
402
+ and reviewers.
403
+
404
+ EXAMPLES::
405
+
406
+ sage: suit = {0: (3, 4), 1: (3, 4)}
407
+ sage: revr = {3: (0, 1), 4: (1, 0)}
408
+ sage: g = MatchingGame([suit, revr])
409
+ sage: latex(g)
410
+ \text{Suitors:}
411
+ \begin{aligned}
412
+ \\ 0 & \to (3, 4)
413
+ \\ 1 & \to (3, 4)
414
+ \end{aligned}
415
+ \text{Reviewers:}
416
+ \begin{aligned}
417
+ \\ 3 & \to (0, 1)
418
+ \\ 4 & \to (1, 0)
419
+ \end{aligned}
420
+ """
421
+ output = "\\text{Suitors:}\n\\begin{aligned}"
422
+ for suitor in self._suitors:
423
+ output += "\n\\\\ %s & \\to %s" % (suitor, suitor.pref)
424
+ output += "\n\\end{aligned}\n\\text{Reviewers:}\n\\begin{aligned}"
425
+ for reviewer in self._reviewers:
426
+ output += "\n\\\\ %s & \\to %s" % (reviewer, reviewer.pref)
427
+ return output + "\n\\end{aligned}"
428
+
429
+ def __eq__(self, other):
430
+ """
431
+ Check equality.
432
+
433
+ sage: suit = {0: (3, 4), 1: (3, 4)}
434
+ sage: revr = {3: (0, 1), 4: (1, 0)}
435
+ sage: g = MatchingGame([suit, revr])
436
+ sage: g2 = MatchingGame([suit, revr])
437
+ sage: g == g2
438
+ True
439
+
440
+ Here the two sets of suitors have different preferences::
441
+
442
+ sage: suit1 = {0: (3, 4), 1: (3, 4)}
443
+ sage: revr1 = {3: (1, 0), 4: (1, 0)}
444
+ sage: g1 = MatchingGame([suit1, revr1])
445
+ sage: suit2 = {0: (4, 3), 1: (3, 4)}
446
+ sage: revr2 = {3: (1, 0), 4: (1, 0)}
447
+ sage: g2 = MatchingGame([suit2, revr2])
448
+ sage: g == g2
449
+ False
450
+
451
+ Here the two sets of reviewers have different preferences::
452
+
453
+ sage: suit1 = {0: (3, 4), 1: (3, 4)}
454
+ sage: revr1 = {3: (0, 1), 4: (1, 0)}
455
+ sage: g1 = MatchingGame([suit1, revr1])
456
+ sage: suit2 = {0: (3, 4), 1: (3, 4)}
457
+ sage: revr2 = {3: (1, 0), 4: (0, 1)}
458
+ sage: g2 = MatchingGame([suit2, revr2])
459
+ sage: g == g2
460
+ False
461
+
462
+ Note that if two games are created with players ordered differently
463
+ they can still be equal::
464
+
465
+ sage: g1 = MatchingGame(1)
466
+ sage: g1.add_reviewer(-2)
467
+ sage: g1.add_reviewer(-3)
468
+ sage: g1.add_suitor(3)
469
+ sage: g1.add_suitor(2)
470
+ sage: g1.reviewers()
471
+ (-1, -2, -3)
472
+ sage: g1.suitors()
473
+ (1, 2, 3)
474
+
475
+ sage: g2 = MatchingGame(1)
476
+ sage: g2.add_reviewer(-2)
477
+ sage: g2.add_reviewer(-3)
478
+ sage: g2.add_suitor(2)
479
+ sage: g2.add_suitor(3)
480
+ sage: g2.reviewers()
481
+ (-1, -2, -3)
482
+ sage: g2.suitors()
483
+ (1, 2, 3)
484
+
485
+ sage: g1 == g2
486
+ True
487
+ """
488
+ return (isinstance(other, MatchingGame)
489
+ and set(self._suitors) == set(other._suitors)
490
+ and set(self._reviewers) == set(other._reviewers)
491
+ and all(r1.pref == r2.pref for r1, r2 in
492
+ zip(set(self._reviewers), set(other._reviewers)))
493
+ and all(s1.pref == s2.pref for s1, s2 in
494
+ zip(set(self._suitors), set(other._suitors))))
495
+
496
+ __hash__ = None
497
+ # not hashable because this is mutable.
498
+
499
+ def plot(self):
500
+ r"""
501
+ Create the plot representing the stable matching for the game.
502
+ Note that the game must be solved for this to work.
503
+
504
+ EXAMPLES:
505
+
506
+ An error is returned if the game is not solved::
507
+
508
+ sage: suit = {0: (3, 4),
509
+ ....: 1: (3, 4)}
510
+ sage: revr = {3: (0, 1),
511
+ ....: 4: (1, 0)}
512
+ sage: g = MatchingGame([suit, revr])
513
+ sage: plot(g) # needs sage.plot
514
+ Traceback (most recent call last):
515
+ ...
516
+ ValueError: game has not been solved yet
517
+
518
+ sage: g.solve()
519
+ {0: 3, 1: 4}
520
+ sage: plot(g) # needs sage.plot
521
+ Graphics object consisting of 7 graphics primitives
522
+ """
523
+ pl = self.bipartite_graph()
524
+ return pl.plot()
525
+
526
+ def bipartite_graph(self):
527
+ r"""
528
+ Construct a ``BipartiteGraph`` Object of the game.
529
+ This method is similar to the plot method.
530
+ Note that the game must be solved for this to work.
531
+
532
+ EXAMPLES:
533
+
534
+ An error is returned if the game is not solved::
535
+
536
+ sage: suit = {0: (3, 4),
537
+ ....: 1: (3, 4)}
538
+ sage: revr = {3: (0, 1),
539
+ ....: 4: (1, 0)}
540
+ sage: g = MatchingGame([suit, revr])
541
+ sage: g.bipartite_graph()
542
+ Traceback (most recent call last):
543
+ ...
544
+ ValueError: game has not been solved yet
545
+
546
+ sage: g.solve()
547
+ {0: 3, 1: 4}
548
+ sage: g.bipartite_graph()
549
+ Bipartite graph on 4 vertices
550
+ """
551
+ self._is_solved()
552
+ graph = BipartiteGraph(self._sol_dict)
553
+ return graph
554
+
555
+ def _is_solved(self):
556
+ r"""
557
+ Raise an error if the game has not been solved yet.
558
+
559
+ EXAMPLES::
560
+
561
+ sage: suit = {0: (3, 4),
562
+ ....: 1: (3, 4)}
563
+ sage: revr = {3: (0, 1),
564
+ ....: 4: (1, 0)}
565
+ sage: g = MatchingGame([suit, revr])
566
+ sage: g._is_solved()
567
+ Traceback (most recent call last):
568
+ ...
569
+ ValueError: game has not been solved yet
570
+ sage: g.solve()
571
+ {0: 3, 1: 4}
572
+ sage: g._is_solved()
573
+ """
574
+ suitor_check = all(s.partner for s in self._suitors)
575
+ reviewer_check = all(r.partner for r in self._reviewers)
576
+ if not suitor_check or not reviewer_check:
577
+ raise ValueError("game has not been solved yet")
578
+
579
+ def _is_complete(self):
580
+ r"""
581
+ Raise an error if all players do not have acceptable preferences.
582
+
583
+ EXAMPLES:
584
+
585
+ Not enough reviewers::
586
+
587
+ sage: suit = {0: (3, 4),
588
+ ....: 1: (3, 4)}
589
+ sage: revr = {3: (0, 1)}
590
+ sage: g = MatchingGame([suit, revr])
591
+ sage: g._is_complete()
592
+ Traceback (most recent call last):
593
+ ...
594
+ ValueError: must have the same number of reviewers as suitors
595
+
596
+ Not enough suitors::
597
+
598
+ sage: suit = {0: (3, 4)}
599
+ sage: revr = {1: (0, 2),
600
+ ....: 3: (0, 1)}
601
+ sage: g = MatchingGame([suit, revr])
602
+ sage: g._is_complete()
603
+ Traceback (most recent call last):
604
+ ...
605
+ ValueError: must have the same number of reviewers as suitors
606
+
607
+ Suitors preferences are incomplete::
608
+
609
+ sage: suit = {0: (3, 8),
610
+ ....: 1: (0, 0)}
611
+ sage: revr = {3: (0, 1),
612
+ ....: 4: (1, 0)}
613
+ sage: g = MatchingGame([suit, revr])
614
+ sage: g._is_complete()
615
+ Traceback (most recent call last):
616
+ ...
617
+ ValueError: suitor preferences are not complete
618
+
619
+ Reviewer preferences are incomplete::
620
+
621
+ sage: suit = {0: (3, 4),
622
+ ....: 1: (3, 4)}
623
+ sage: revr = {3: (0, 2, 1),
624
+ ....: 4: (1, 0)}
625
+ sage: g = MatchingGame([suit, revr])
626
+ sage: g._is_complete()
627
+ Traceback (most recent call last):
628
+ ...
629
+ ValueError: reviewer preferences are not complete
630
+
631
+ Suitor preferences have repetitions::
632
+
633
+ sage: suit = {0: (3, 4),
634
+ ....: 1: (3, 4)}
635
+ sage: revr = {3: (0, 0, 1),
636
+ ....: 4: (1, 0)}
637
+ sage: g = MatchingGame([suit, revr])
638
+ sage: g._is_complete()
639
+ Traceback (most recent call last):
640
+ ...
641
+ ValueError: reviewer preferences contain repetitions
642
+
643
+ Reviewer preferences have repetitions::
644
+
645
+ sage: suit = {0: (3, 4, 3),
646
+ ....: 1: (3, 4)}
647
+ sage: revr = {3: (0, 1),
648
+ ....: 4: (1, 0)}
649
+ sage: g = MatchingGame([suit, revr])
650
+ sage: g._is_complete()
651
+ Traceback (most recent call last):
652
+ ...
653
+ ValueError: suitor preferences contain repetitions
654
+ """
655
+ if len(self._suitors) != len(self._reviewers):
656
+ raise ValueError("must have the same number of reviewers as suitors")
657
+
658
+ for suitor in self._suitors:
659
+ if set(suitor.pref) != set(self._reviewers):
660
+ raise ValueError("suitor preferences are not complete")
661
+
662
+ for reviewer in self._reviewers:
663
+ if set(reviewer.pref) != set(self._suitors):
664
+ raise ValueError("reviewer preferences are not complete")
665
+
666
+ for reviewer in self._reviewers:
667
+ if len(set(reviewer.pref)) < len(reviewer.pref):
668
+ raise ValueError("reviewer preferences contain repetitions")
669
+
670
+ for suitor in self._suitors:
671
+ if len(set(suitor.pref)) < len(suitor.pref):
672
+ raise ValueError("suitor preferences contain repetitions")
673
+
674
+ def add_suitor(self, name=None):
675
+ r"""
676
+ Add a suitor to the game.
677
+
678
+ INPUT:
679
+
680
+ - ``name`` -- can be a string or a number; if left blank will
681
+ automatically generate an integer
682
+
683
+ EXAMPLES:
684
+
685
+ Creating a two player game::
686
+
687
+ sage: g = MatchingGame(2)
688
+ sage: g.suitors()
689
+ (1, 2)
690
+
691
+ Adding a suitor without specifying a name::
692
+
693
+ sage: g.add_suitor()
694
+ sage: g.suitors()
695
+ (1, 2, 3)
696
+
697
+ Adding a suitor while specifying a name::
698
+
699
+ sage: g.add_suitor('D')
700
+ sage: g.suitors()
701
+ (1, 2, 3, 'D')
702
+
703
+ Note that now our game is no longer complete::
704
+
705
+ sage: g._is_complete()
706
+ Traceback (most recent call last):
707
+ ...
708
+ ValueError: must have the same number of reviewers as suitors
709
+
710
+ Note that an error is raised if one tries to add a suitor
711
+ with a name that already exists::
712
+
713
+ sage: g.add_suitor('D')
714
+ Traceback (most recent call last):
715
+ ...
716
+ ValueError: a suitor with name "D" already exists
717
+
718
+ If we add a suitor without passing a name then the name
719
+ of the suitor will not use one that is already chosen::
720
+
721
+ sage: suit = {0: (-1, -2),
722
+ ....: 2: (-2, -1)}
723
+ sage: revr = {-1: (0, 1),
724
+ ....: -2: (1, 0)}
725
+ sage: g = MatchingGame([suit, revr])
726
+ sage: g.suitors()
727
+ (0, 2)
728
+
729
+ sage: g.add_suitor()
730
+ sage: g.suitors()
731
+ (0, 2, 3)
732
+ """
733
+ if name is None:
734
+ name = len(self._suitors) + 1
735
+ while name in self._suitors:
736
+ name += 1
737
+ if any(s._name == name for s in self._suitors):
738
+ raise ValueError('a suitor with name "{}" already exists'.format(name))
739
+
740
+ new_suitor = Player(name)
741
+ self._suitors.append(new_suitor)
742
+ for r in self._reviewers:
743
+ r.pref = []
744
+
745
+ def add_reviewer(self, name=None):
746
+ r"""
747
+ Add a reviewer to the game.
748
+
749
+ INPUT:
750
+
751
+ - ``name`` -- can be a string or number; if left blank will
752
+ automatically generate an integer
753
+
754
+ EXAMPLES:
755
+
756
+ Creating a two player game::
757
+
758
+ sage: g = MatchingGame(2)
759
+ sage: g.reviewers()
760
+ (-1, -2)
761
+
762
+ Adding a suitor without specifying a name::
763
+
764
+ sage: g.add_reviewer()
765
+ sage: g.reviewers()
766
+ (-1, -2, -3)
767
+
768
+ Adding a suitor while specifying a name::
769
+
770
+ sage: g.add_reviewer(10)
771
+ sage: g.reviewers()
772
+ (-1, -2, -3, 10)
773
+
774
+ Note that now our game is no longer complete::
775
+
776
+ sage: g._is_complete()
777
+ Traceback (most recent call last):
778
+ ...
779
+ ValueError: must have the same number of reviewers as suitors
780
+
781
+ Note that an error is raised if one tries to add a reviewer
782
+ with a name that already exists::
783
+
784
+ sage: g.add_reviewer(10)
785
+ Traceback (most recent call last):
786
+ ...
787
+ ValueError: a reviewer with name "10" already exists
788
+
789
+ If we add a reviewer without passing a name then the name
790
+ of the reviewer will not use one that is already chosen::
791
+
792
+ sage: suit = {0: (-1, -3),
793
+ ....: 1: (-3, -1)}
794
+ sage: revr = {-1: (0, 1),
795
+ ....: -3: (1, 0)}
796
+ sage: g = MatchingGame([suit, revr])
797
+ sage: g.reviewers()
798
+ (-1, -3)
799
+
800
+ sage: g.add_reviewer()
801
+ sage: g.reviewers()
802
+ (-1, -3, -4)
803
+ """
804
+ if name is None:
805
+ name = -len(self._reviewers) - 1
806
+ while name in self._reviewers:
807
+ name -= 1
808
+ if any(r._name == name for r in self._reviewers):
809
+ raise ValueError('a reviewer with name "{}" already exists'.format(name))
810
+
811
+ new_reviewer = Player(name)
812
+ self._reviewers.append(new_reviewer)
813
+ for s in self._suitors:
814
+ s.pref = []
815
+
816
+ def suitors(self):
817
+ """
818
+ Return the suitors of ``self``.
819
+
820
+ EXAMPLES::
821
+
822
+ sage: g = MatchingGame(2)
823
+ sage: g.suitors()
824
+ (1, 2)
825
+ """
826
+ return tuple(sorted(self._suitors, key=lambda s:str(s._name)))
827
+
828
+ def reviewers(self):
829
+ """
830
+ Return the reviewers of ``self``.
831
+
832
+ EXAMPLES::
833
+
834
+ sage: g = MatchingGame(2)
835
+ sage: g.reviewers()
836
+ (-1, -2)
837
+ """
838
+ return tuple(sorted(self._reviewers, key=lambda r:str(r._name)))
839
+
840
+ def solve(self, invert=False):
841
+ r"""
842
+ Compute a stable matching for the game using the Gale-Shapley
843
+ algorithm.
844
+
845
+ EXAMPLES::
846
+
847
+ sage: suitr_pref = {'J': ('A', 'D', 'C', 'B'),
848
+ ....: 'K': ('A', 'B', 'C', 'D'),
849
+ ....: 'L': ('B', 'C', 'D', 'A'),
850
+ ....: 'M': ('C', 'A', 'B', 'D')}
851
+ sage: reviewr_pref = {'A': ('L', 'J', 'K', 'M'),
852
+ ....: 'B': ('J', 'M', 'L', 'K'),
853
+ ....: 'C': ('M', 'K', 'L', 'J'),
854
+ ....: 'D': ('M', 'K', 'J', 'L')}
855
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
856
+ sage: m.solve()
857
+ {'J': 'A', 'K': 'D', 'L': 'B', 'M': 'C'}
858
+
859
+ sage: suitr_pref = {'J': ('A', 'D', 'C', 'B'),
860
+ ....: 'K': ('A', 'B', 'C', 'D'),
861
+ ....: 'L': ('B', 'C', 'D', 'A'),
862
+ ....: 'M': ('C', 'A', 'B', 'D')}
863
+ sage: reviewr_pref = {'A': ('L', 'J', 'K', 'M'),
864
+ ....: 'B': ('J', 'M', 'L', 'K'),
865
+ ....: 'C': ('M', 'K', 'L', 'J'),
866
+ ....: 'D': ('M', 'K', 'J', 'L')}
867
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
868
+ sage: m.solve(invert=True)
869
+ {'A': 'L', 'B': 'J', 'C': 'M', 'D': 'K'}
870
+
871
+ sage: suitr_pref = {1: (-1,)}
872
+ sage: reviewr_pref = {-1: (1,)}
873
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
874
+ sage: m.solve()
875
+ {1: -1}
876
+
877
+ sage: suitr_pref = {}
878
+ sage: reviewr_pref = {}
879
+ sage: m = MatchingGame([suitr_pref, reviewr_pref])
880
+ sage: m.solve()
881
+ {}
882
+
883
+ TESTS:
884
+
885
+ This also works for players who are both a suitor and reviewer::
886
+
887
+ sage: suit = {0: (3,4,2), 1: (3,4,2), 2: (2,3,4)}
888
+ sage: revr = {2: (2,0,1), 3: (0,1,2), 4: (1,0,2)}
889
+ sage: g = MatchingGame(suit, revr)
890
+ sage: g.solve()
891
+ {0: 3, 1: 4, 2: 2}
892
+ """
893
+ self._is_complete()
894
+
895
+ for s in self._suitors:
896
+ s.partner = None
897
+ for r in self._reviewers:
898
+ r.partner = None
899
+
900
+ if invert:
901
+ reviewers = deepcopy(self._suitors)
902
+ suitors = deepcopy(self._reviewers)
903
+ else:
904
+ suitors = deepcopy(self._suitors)
905
+ reviewers = deepcopy(self._reviewers)
906
+
907
+ while any(s.partner is None for s in suitors):
908
+ s = None
909
+ for x in suitors:
910
+ if x.partner is None:
911
+ s = x
912
+ break
913
+ r = next((x for x in reviewers if x == s.pref[0]), None)
914
+ if r.partner is None:
915
+ r.partner = s
916
+ s.partner = r
917
+ elif r.pref.index(s._name) < r.pref.index(r.partner._name):
918
+ r.partner.partner = None
919
+ r.partner = s
920
+ s.partner = r
921
+ else:
922
+ s.pref = s.pref[1:]
923
+
924
+ if invert:
925
+ suitors, reviewers = reviewers, suitors
926
+
927
+ for i, j in zip(self._suitors, suitors):
928
+ i.partner = j.partner
929
+ for i, j in zip(self._reviewers, reviewers):
930
+ i.partner = j.partner
931
+
932
+ self._sol_dict = {}
933
+ for s in self._suitors:
934
+ self._sol_dict[s] = [s.partner]
935
+ for r in self._reviewers:
936
+ self._sol_dict[r] = [r.partner]
937
+
938
+ if invert:
939
+ return {key: self._sol_dict[key][0] for key in self._reviewers}
940
+ return {key: self._sol_dict[key][0] for key in self._suitors}
941
+
942
+
943
+ class Player:
944
+ r"""
945
+ A class to act as a data holder for the players used of the
946
+ matching games.
947
+
948
+ These instances are used when initiating players and to keep track of
949
+ whether or not partners have a preference.
950
+ """
951
+ def __init__(self, name):
952
+ r"""
953
+ TESTS::
954
+
955
+ sage: from sage.game_theory.matching_game import Player
956
+ sage: p = Player(10)
957
+ sage: p
958
+ 10
959
+ sage: p.pref
960
+ []
961
+ sage: p.partner is None
962
+ True
963
+ """
964
+ self._name = name
965
+ self.pref = []
966
+ self.partner = None
967
+
968
+ def __hash__(self):
969
+ r"""
970
+ TESTS::
971
+
972
+ sage: from sage.game_theory.matching_game import Player
973
+ sage: p = Player(10)
974
+ sage: d = {p : (1, 2, 3)}
975
+ sage: d
976
+ {10: (1, 2, 3)}
977
+ """
978
+ return hash(self._name)
979
+
980
+ def __repr__(self):
981
+ r"""
982
+ TESTS::
983
+
984
+ sage: from sage.game_theory.matching_game import Player
985
+ sage: p = Player(10)
986
+ sage: p
987
+ 10
988
+
989
+ sage: p = Player('Karl')
990
+ sage: p
991
+ 'Karl'
992
+ """
993
+ return repr(self._name)
994
+
995
+ def __eq__(self, other):
996
+ r"""
997
+
998
+ Tests equality of two players. This only checks the name of the player
999
+ and not their preferences.
1000
+
1001
+ TESTS::
1002
+
1003
+ sage: from sage.game_theory.matching_game import Player
1004
+ sage: p = Player(10)
1005
+ sage: q = Player('Karl')
1006
+ sage: p == q
1007
+ False
1008
+
1009
+ sage: from sage.game_theory.matching_game import Player
1010
+ sage: p = Player(10)
1011
+ sage: q = Player(10)
1012
+ sage: p == q
1013
+ True
1014
+
1015
+ sage: from sage.game_theory.matching_game import Player
1016
+ sage: p = Player(10)
1017
+ sage: q = Player(10)
1018
+ sage: p.pref = (1, 2)
1019
+ sage: p.pref = (2, 1)
1020
+ sage: p == q
1021
+ True
1022
+ """
1023
+ if isinstance(other, Player):
1024
+ return self._name == other._name
1025
+ return self._name == other
1026
+
1027
+ def __lt__(self, other):
1028
+ """
1029
+ Test less than inequality of two players. Allows for players to be
1030
+ sorted on their names.
1031
+
1032
+ TESTS::
1033
+
1034
+ sage: from sage.game_theory.matching_game import Player
1035
+ sage: p = Player('A')
1036
+ sage: q = Player('B')
1037
+ sage: p < q
1038
+ True
1039
+ sage: q < p
1040
+ False
1041
+
1042
+ sage: p = Player(0)
1043
+ sage: q = Player(1)
1044
+ sage: p < q
1045
+ True
1046
+ sage: q < p
1047
+ False
1048
+ """
1049
+ if isinstance(other, Player):
1050
+ return self._name < other._name
1051
+ return self._name < other
1052
+
1053
+ def __gt__(self, other):
1054
+ """
1055
+ Test greater than inequality of two players. Allows for players to be
1056
+ sorted on their names.
1057
+
1058
+ TESTS::
1059
+
1060
+ sage: from sage.game_theory.matching_game import Player
1061
+ sage: p = Player('A')
1062
+ sage: q = Player('B')
1063
+ sage: p > q
1064
+ False
1065
+ sage: q > p
1066
+ True
1067
+
1068
+ sage: p = Player(0)
1069
+ sage: q = Player(1)
1070
+ sage: p > q
1071
+ False
1072
+ sage: q > p
1073
+ True
1074
+ """
1075
+ if isinstance(other, Player):
1076
+ return self._name > other._name
1077
+ return self._name > other
1078
+
1079
+ def __ge__(self, other):
1080
+ """
1081
+ Test greater than or equal inequality of two players. Allows for
1082
+ players to be sorted on their names.
1083
+
1084
+ TESTS::
1085
+
1086
+ sage: from sage.game_theory.matching_game import Player
1087
+ sage: p = Player('A')
1088
+ sage: q = Player('B')
1089
+ sage: p >= q
1090
+ False
1091
+ sage: q >= p
1092
+ True
1093
+
1094
+ sage: p = Player(0)
1095
+ sage: q = Player(1)
1096
+ sage: p >= q
1097
+ False
1098
+ sage: q >= p
1099
+ True
1100
+
1101
+ sage: p = Player(0)
1102
+ sage: q = Player(0)
1103
+ sage: p >= q
1104
+ True
1105
+
1106
+ sage: p = Player('C')
1107
+ sage: q = Player('C')
1108
+ sage: p >= q
1109
+ True
1110
+ """
1111
+ if isinstance(other, Player):
1112
+ return self._name >= other._name
1113
+ return self._name >= other
1114
+
1115
+ def __le__(self, other):
1116
+ """
1117
+ Test less than or equal inequality of two players. Allows for
1118
+ players to be sorted on their names.
1119
+
1120
+ TESTS::
1121
+
1122
+ sage: from sage.game_theory.matching_game import Player
1123
+ sage: p = Player('A')
1124
+ sage: q = Player('B')
1125
+ sage: p <= q
1126
+ True
1127
+ sage: q <= p
1128
+ False
1129
+
1130
+ sage: p = Player(0)
1131
+ sage: q = Player(1)
1132
+ sage: p <= q
1133
+ True
1134
+ sage: q <= p
1135
+ False
1136
+
1137
+ sage: p = Player(0)
1138
+ sage: q = Player(0)
1139
+ sage: p <= q
1140
+ True
1141
+
1142
+ sage: p = Player('C')
1143
+ sage: q = Player('C')
1144
+ sage: p <= q
1145
+ True
1146
+ """
1147
+ if isinstance(other, Player):
1148
+ return self._name <= other._name
1149
+ return self._name <= other
1150
+
1151
+ def __ne__(self, other):
1152
+ """
1153
+ Test inequality of two players. Allows for
1154
+ players to be sorted on their names.
1155
+
1156
+ TESTS::
1157
+
1158
+ sage: from sage.game_theory.matching_game import Player
1159
+ sage: p = Player('A')
1160
+ sage: q = Player('B')
1161
+ sage: p != q
1162
+ True
1163
+
1164
+ sage: p = Player(0)
1165
+ sage: q = Player(1)
1166
+ sage: p != q
1167
+ True
1168
+
1169
+ sage: p = Player(0)
1170
+ sage: q = Player(0)
1171
+ sage: p != q
1172
+ False
1173
+
1174
+ sage: p = Player('C')
1175
+ sage: q = Player('C')
1176
+ sage: p != q
1177
+ False
1178
+ """
1179
+ if isinstance(other, Player):
1180
+ return self._name != other._name
1181
+ return self._name != other