pycgt 0.1.0__tar.gz

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.
pycgt-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nehalahmedshaikh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
pycgt-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,153 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycgt
3
+ Version: 0.1.0
4
+ Summary: Combinatorial game theory in pure Python: exact canonical forms, thermography, and game values
5
+ Author: nehalahmedshaikh
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/nehalahmedshaikh/pycgt
8
+ Project-URL: Repository, https://github.com/nehalahmedshaikh/pycgt
9
+ Project-URL: Issues, https://github.com/nehalahmedshaikh/pycgt/issues
10
+ Project-URL: Changelog, https://github.com/nehalahmedshaikh/pycgt/blob/main/CHANGELOG.md
11
+ Keywords: combinatorial-game-theory,canonical-form,surreal-numbers,thermography,temperature,domineering,nim,partizan-games,exact-arithmetic
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Education
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
20
+ Classifier: Typing :: Typed
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7; extra == "dev"
26
+ Requires-Dist: pytest-cov>=4; extra == "dev"
27
+ Requires-Dist: mypy>=1.8; extra == "dev"
28
+ Requires-Dist: ruff>=0.3; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ # pycgt
32
+
33
+ **Combinatorial game theory in pure Python — exact canonical forms, thermography, and game values.**
34
+
35
+ ```python
36
+ >>> from pycgt import parse, render, temperature, mean
37
+ >>> from pycgt.rulesets import domineering
38
+
39
+ >>> render(domineering.rectangle(2, 4))
40
+ 'Miny(2)'
41
+ >>> temperature(domineering.rectangle(2, 3))
42
+ Fraction(5, 4)
43
+ >>> render(parse("{1|-1}") + parse("{1|-1}"))
44
+ '0'
45
+ ```
46
+
47
+ `pip install pycgt`. Python 3.11+, **no dependencies**.
48
+
49
+ ---
50
+
51
+ ## The one idea everything rests on
52
+
53
+ Short games are **exact objects built from the empty game up**. Two values are
54
+ equal, or one is greater, or they are *confused* with each other — there is no
55
+ tolerance to set, no rounding, and no floating point anywhere. Temperatures are
56
+ `Fraction`s found by bisection on an exact dyadic grid.
57
+
58
+ The operation that makes this usable is **canonical form**. Two games of the
59
+ same value reduce to *identical* canonical forms, so structural equality
60
+ becomes value equality and comparison becomes a dictionary lookup. Every `Game`
61
+ returned by this library is canonical, which is why `==` means what you want.
62
+
63
+ ## Why it exists
64
+
65
+ [CGSuite](https://www.cgsuite.org/) is the established tool in this field and
66
+ is far more complete than this. But it is a Java desktop application: not
67
+ `pip install`-able, awkward to drive from a script, and unavailable in a
68
+ notebook or in CI. Getting it to answer one batch of questions headlessly took
69
+ a from-source Maven build and a JDK version pin.
70
+
71
+ `pycgt` is for when you want game values inside ordinary Python. It has no
72
+ dependencies, so it also runs unchanged under Pyodide in a browser.
73
+
74
+ ## What's here
75
+
76
+ | | |
77
+ |---|---|
78
+ | **Core** | canonical form, the partial order, disjunctive sums, negation, birthdays |
79
+ | **Numbers** | dyadic rationals both ways, Conway's simplicity rule |
80
+ | **Named values** | `*`, `^`, `v`, nimbers, switches, `tiny`, `miny` |
81
+ | **Stops** | left/right stops, confusion intervals, infinitesimality, hot/tepid tests |
82
+ | **Reduced form** | reduced canonical form, and `ish` — the infinitesimal remainder |
83
+ | **Thermography** | heating, overheating, cooling, temperature, mean value |
84
+ | **Notation** | render to readable text, and parse it back |
85
+ | **Rulesets** | Domineering, Cram, Nim, Blue-Red Hackenbush |
86
+
87
+ Games are only **partially** ordered, so `<=` and `>=` are defined but `<` and
88
+ `>` deliberately are not — `not (G <= H)` does not imply `G > H`. Use
89
+ `compare()` for the four-way answer, which includes *confused*.
90
+
91
+ ```python
92
+ >>> from pycgt import compare, STAR, ZERO
93
+ >>> compare(STAR, ZERO)
94
+ <Relation.CONFUSED: '||'>
95
+ ```
96
+
97
+ ## A worked example
98
+
99
+ [`examples/berlekamp_1988.py`](examples/berlekamp_1988.py) reproduces
100
+ Berlekamp's *Blockbusting and Domineering* (1988) — his Table III sequences,
101
+ their period-5/saltus-1 structure, and the overheating bound from Appendix B.1 —
102
+ and checks the results against the paper.
103
+
104
+ ```console
105
+ $ python examples/berlekamp_1988.py
106
+ period 5, saltus 1: holds
107
+ x_1 is the only non-number: 1*
108
+ ok G_2 = tiny-2
109
+ ok G_3 = +-1 + 2*tiny-2
110
+ ```
111
+
112
+ ## Correctness
113
+
114
+ The interesting values in this field are easy to get subtly wrong, so nothing
115
+ here is trusted because it looks right. The test suite validates against
116
+ **external** sources:
117
+
118
+ - **CGSuite** — canonical forms, temperatures and means of Domineering 2×n
119
+ boards, generated by driving CGSuite 2.2 headlessly.
120
+ - **Berlekamp (1988)**, *Blockbusting and Domineering*, Appendix B.1 — his
121
+ exact values for the 2-wide Domineering rectangles, `G₂ = tiny-2` and
122
+ `G₃ = ±1 + 2·tiny-2`.
123
+ - **Wolfe**, via Guy's Problem 4 in *Games of No Chance* — the 4×5 board is `1`.
124
+ - **Uiterwijk**, [arXiv:1305.3257](https://arxiv.org/pdf/1305.3257) — the
125
+ value of the 11×2 board.
126
+ - **Closed forms** — Nim sums follow exclusive-or; Hackenbush strings give the
127
+ expected dyadic rationals; Cram values are nimbers.
128
+
129
+ Plus internal laws that catch real bugs: `G + (−G) = 0`, transposing a
130
+ Domineering board negates its value, `ish(G)` is *always* infinitesimal, and
131
+ canonical form is idempotent.
132
+
133
+ ## Provenance
134
+
135
+ Implemented clean-room from the mathematics — *Winning Ways*, Siegel's
136
+ *Combinatorial Game Theory*, and Berlekamp (1988). CGSuite is GPL; its source
137
+ was consulted **only** for the API needed to run it as a test oracle, and none
138
+ of its implementation was used or ported. This library is MIT.
139
+
140
+ ## Limitations
141
+
142
+ Honest about scope:
143
+
144
+ - **Short games only.** No loopy games, stoppers, or `on`/`off`.
145
+ - **No misère theory** and no misère quotients.
146
+ - **Slower than CGSuite**, which is a JVM program with far more optimisation.
147
+ Expect exact Domineering values up to about 2×16 comfortably.
148
+ - **The number-temperature convention** (a number with denominator `2**k` has
149
+ temperature `−1/2**k`) is checked against CGSuite for `0` and `1/2` only.
150
+
151
+ ## Licence
152
+
153
+ MIT.
pycgt-0.1.0/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # pycgt
2
+
3
+ **Combinatorial game theory in pure Python — exact canonical forms, thermography, and game values.**
4
+
5
+ ```python
6
+ >>> from pycgt import parse, render, temperature, mean
7
+ >>> from pycgt.rulesets import domineering
8
+
9
+ >>> render(domineering.rectangle(2, 4))
10
+ 'Miny(2)'
11
+ >>> temperature(domineering.rectangle(2, 3))
12
+ Fraction(5, 4)
13
+ >>> render(parse("{1|-1}") + parse("{1|-1}"))
14
+ '0'
15
+ ```
16
+
17
+ `pip install pycgt`. Python 3.11+, **no dependencies**.
18
+
19
+ ---
20
+
21
+ ## The one idea everything rests on
22
+
23
+ Short games are **exact objects built from the empty game up**. Two values are
24
+ equal, or one is greater, or they are *confused* with each other — there is no
25
+ tolerance to set, no rounding, and no floating point anywhere. Temperatures are
26
+ `Fraction`s found by bisection on an exact dyadic grid.
27
+
28
+ The operation that makes this usable is **canonical form**. Two games of the
29
+ same value reduce to *identical* canonical forms, so structural equality
30
+ becomes value equality and comparison becomes a dictionary lookup. Every `Game`
31
+ returned by this library is canonical, which is why `==` means what you want.
32
+
33
+ ## Why it exists
34
+
35
+ [CGSuite](https://www.cgsuite.org/) is the established tool in this field and
36
+ is far more complete than this. But it is a Java desktop application: not
37
+ `pip install`-able, awkward to drive from a script, and unavailable in a
38
+ notebook or in CI. Getting it to answer one batch of questions headlessly took
39
+ a from-source Maven build and a JDK version pin.
40
+
41
+ `pycgt` is for when you want game values inside ordinary Python. It has no
42
+ dependencies, so it also runs unchanged under Pyodide in a browser.
43
+
44
+ ## What's here
45
+
46
+ | | |
47
+ |---|---|
48
+ | **Core** | canonical form, the partial order, disjunctive sums, negation, birthdays |
49
+ | **Numbers** | dyadic rationals both ways, Conway's simplicity rule |
50
+ | **Named values** | `*`, `^`, `v`, nimbers, switches, `tiny`, `miny` |
51
+ | **Stops** | left/right stops, confusion intervals, infinitesimality, hot/tepid tests |
52
+ | **Reduced form** | reduced canonical form, and `ish` — the infinitesimal remainder |
53
+ | **Thermography** | heating, overheating, cooling, temperature, mean value |
54
+ | **Notation** | render to readable text, and parse it back |
55
+ | **Rulesets** | Domineering, Cram, Nim, Blue-Red Hackenbush |
56
+
57
+ Games are only **partially** ordered, so `<=` and `>=` are defined but `<` and
58
+ `>` deliberately are not — `not (G <= H)` does not imply `G > H`. Use
59
+ `compare()` for the four-way answer, which includes *confused*.
60
+
61
+ ```python
62
+ >>> from pycgt import compare, STAR, ZERO
63
+ >>> compare(STAR, ZERO)
64
+ <Relation.CONFUSED: '||'>
65
+ ```
66
+
67
+ ## A worked example
68
+
69
+ [`examples/berlekamp_1988.py`](examples/berlekamp_1988.py) reproduces
70
+ Berlekamp's *Blockbusting and Domineering* (1988) — his Table III sequences,
71
+ their period-5/saltus-1 structure, and the overheating bound from Appendix B.1 —
72
+ and checks the results against the paper.
73
+
74
+ ```console
75
+ $ python examples/berlekamp_1988.py
76
+ period 5, saltus 1: holds
77
+ x_1 is the only non-number: 1*
78
+ ok G_2 = tiny-2
79
+ ok G_3 = +-1 + 2*tiny-2
80
+ ```
81
+
82
+ ## Correctness
83
+
84
+ The interesting values in this field are easy to get subtly wrong, so nothing
85
+ here is trusted because it looks right. The test suite validates against
86
+ **external** sources:
87
+
88
+ - **CGSuite** — canonical forms, temperatures and means of Domineering 2×n
89
+ boards, generated by driving CGSuite 2.2 headlessly.
90
+ - **Berlekamp (1988)**, *Blockbusting and Domineering*, Appendix B.1 — his
91
+ exact values for the 2-wide Domineering rectangles, `G₂ = tiny-2` and
92
+ `G₃ = ±1 + 2·tiny-2`.
93
+ - **Wolfe**, via Guy's Problem 4 in *Games of No Chance* — the 4×5 board is `1`.
94
+ - **Uiterwijk**, [arXiv:1305.3257](https://arxiv.org/pdf/1305.3257) — the
95
+ value of the 11×2 board.
96
+ - **Closed forms** — Nim sums follow exclusive-or; Hackenbush strings give the
97
+ expected dyadic rationals; Cram values are nimbers.
98
+
99
+ Plus internal laws that catch real bugs: `G + (−G) = 0`, transposing a
100
+ Domineering board negates its value, `ish(G)` is *always* infinitesimal, and
101
+ canonical form is idempotent.
102
+
103
+ ## Provenance
104
+
105
+ Implemented clean-room from the mathematics — *Winning Ways*, Siegel's
106
+ *Combinatorial Game Theory*, and Berlekamp (1988). CGSuite is GPL; its source
107
+ was consulted **only** for the API needed to run it as a test oracle, and none
108
+ of its implementation was used or ported. This library is MIT.
109
+
110
+ ## Limitations
111
+
112
+ Honest about scope:
113
+
114
+ - **Short games only.** No loopy games, stoppers, or `on`/`off`.
115
+ - **No misère theory** and no misère quotients.
116
+ - **Slower than CGSuite**, which is a JVM program with far more optimisation.
117
+ Expect exact Domineering values up to about 2×16 comfortably.
118
+ - **The number-temperature convention** (a number with denominator `2**k` has
119
+ temperature `−1/2**k`) is checked against CGSuite for `0` and `1/2` only.
120
+
121
+ ## Licence
122
+
123
+ MIT.
@@ -0,0 +1,80 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pycgt"
7
+ version = "0.1.0"
8
+ description = "Combinatorial game theory in pure Python: exact canonical forms, thermography, and game values"
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "nehalahmedshaikh" }]
13
+ keywords = [
14
+ "combinatorial-game-theory",
15
+ "canonical-form",
16
+ "surreal-numbers",
17
+ "thermography",
18
+ "temperature",
19
+ "domineering",
20
+ "nim",
21
+ "partizan-games",
22
+ "exact-arithmetic",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 3 - Alpha",
26
+ "Intended Audience :: Science/Research",
27
+ "Intended Audience :: Education",
28
+ "License :: OSI Approved :: MIT License",
29
+ "Programming Language :: Python :: 3.11",
30
+ "Programming Language :: Python :: 3.12",
31
+ "Programming Language :: Python :: 3.13",
32
+ "Topic :: Scientific/Engineering :: Mathematics",
33
+ "Typing :: Typed",
34
+ ]
35
+ # Intentionally empty and meant to stay that way: the library must run
36
+ # unchanged under Pyodide, so it works in a browser and in notebooks.
37
+ dependencies = []
38
+
39
+ [project.optional-dependencies]
40
+ dev = ["pytest>=7", "pytest-cov>=4", "mypy>=1.8", "ruff>=0.3"]
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/nehalahmedshaikh/pycgt"
44
+ Repository = "https://github.com/nehalahmedshaikh/pycgt"
45
+ Issues = "https://github.com/nehalahmedshaikh/pycgt/issues"
46
+ Changelog = "https://github.com/nehalahmedshaikh/pycgt/blob/main/CHANGELOG.md"
47
+
48
+ [tool.setuptools.packages.find]
49
+ where = ["src"]
50
+
51
+ [tool.setuptools.package-data]
52
+ pycgt = ["py.typed"]
53
+
54
+ [tool.pytest.ini_options]
55
+ testpaths = ["tests"]
56
+ pythonpath = ["src"]
57
+ addopts = "--strict-markers"
58
+ markers = ["slow: takes more than a second to run"]
59
+
60
+ [tool.mypy]
61
+ python_version = "3.11"
62
+ strict = true
63
+ files = ["src/pycgt"]
64
+
65
+ [tool.ruff]
66
+ line-length = 88
67
+ target-version = "py311"
68
+
69
+ [tool.ruff.lint]
70
+ select = ["E", "F", "I", "N", "UP", "B", "C4", "RUF"]
71
+ ignore = [
72
+ # Single letters are the notation of the field: G, H, L, R, s, t, n.
73
+ "E741",
74
+ # __all__ is grouped by topic with comments, which is far more useful for
75
+ # a public API than alphabetical order.
76
+ "RUF022",
77
+ ]
78
+
79
+ [tool.ruff.lint.per-file-ignores]
80
+ "tests/*" = ["E501"]
pycgt-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,130 @@
1
+ """pycgt: combinatorial game theory in pure Python.
2
+
3
+ Short games are exact objects built from the empty game up, so nothing here
4
+ approximates: two values are equal, or one is greater, or they are *confused*
5
+ with each other. There is no tolerance to set and no rounding.
6
+
7
+ >>> from pycgt import parse, render
8
+ >>> render(parse("{1|-1}") + parse("{1|-1}"))
9
+ '0'
10
+ >>> from pycgt.rulesets import domineering
11
+ >>> render(domineering.rectangle(2, 4))
12
+ 'Miny(2)'
13
+ >>> from pycgt import temperature
14
+ >>> temperature(domineering.rectangle(2, 3))
15
+ Fraction(5, 4)
16
+
17
+ Every :class:`Game` returned by this package is in canonical form, so ``==``
18
+ means value equality.
19
+ """
20
+
21
+ from .game import (
22
+ ZERO,
23
+ Game,
24
+ Outcome,
25
+ Relation,
26
+ add,
27
+ birthday,
28
+ canonical,
29
+ compare,
30
+ confused,
31
+ equals,
32
+ game,
33
+ geq,
34
+ greater,
35
+ leq,
36
+ multiple,
37
+ negate,
38
+ outcome,
39
+ )
40
+ from .notation import parse, render
41
+ from .reduced import is_reduced, ish, reduced_canonical_form
42
+ from .stops import (
43
+ confusion_interval,
44
+ is_hot,
45
+ is_infinitesimal,
46
+ is_tepid,
47
+ left_stop,
48
+ number_part,
49
+ right_stop,
50
+ stops,
51
+ )
52
+ from .thermal import cool, heat, mean, overheat, temperature, thermograph
53
+ from .values import (
54
+ DOWN,
55
+ STAR,
56
+ UP,
57
+ as_number,
58
+ integer,
59
+ is_number,
60
+ miny,
61
+ nimber,
62
+ number,
63
+ plus_minus,
64
+ simplest_between,
65
+ switch,
66
+ tiny,
67
+ up_multiple,
68
+ )
69
+
70
+ __version__ = "0.1.0"
71
+
72
+ __all__ = [
73
+ # core
74
+ "Game",
75
+ "Outcome",
76
+ "Relation",
77
+ "ZERO",
78
+ "game",
79
+ "canonical",
80
+ "add",
81
+ "negate",
82
+ "multiple",
83
+ "birthday",
84
+ # order
85
+ "geq",
86
+ "leq",
87
+ "equals",
88
+ "greater",
89
+ "confused",
90
+ "compare",
91
+ "outcome",
92
+ # numbers and named values
93
+ "number",
94
+ "integer",
95
+ "as_number",
96
+ "is_number",
97
+ "simplest_between",
98
+ "STAR",
99
+ "UP",
100
+ "DOWN",
101
+ "nimber",
102
+ "up_multiple",
103
+ "switch",
104
+ "plus_minus",
105
+ "tiny",
106
+ "miny",
107
+ # stops
108
+ "left_stop",
109
+ "right_stop",
110
+ "stops",
111
+ "confusion_interval",
112
+ "is_infinitesimal",
113
+ "is_hot",
114
+ "is_tepid",
115
+ "number_part",
116
+ # reduced canonical form
117
+ "reduced_canonical_form",
118
+ "ish",
119
+ "is_reduced",
120
+ # thermography
121
+ "heat",
122
+ "overheat",
123
+ "cool",
124
+ "temperature",
125
+ "mean",
126
+ "thermograph",
127
+ # notation
128
+ "render",
129
+ "parse",
130
+ ]