mache 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.
- mache-0.1.0/LICENSE +21 -0
- mache-0.1.0/PKG-INFO +127 -0
- mache-0.1.0/README.md +113 -0
- mache-0.1.0/mache/__init__.py +27 -0
- mache-0.1.0/mache/book_slice.py +66 -0
- mache-0.1.0/mache/match_estimate.py +764 -0
- mache-0.1.0/mache/match_terminations.py +197 -0
- mache-0.1.0/mache/rating_estimate.py +334 -0
- mache-0.1.0/mache.egg-info/PKG-INFO +127 -0
- mache-0.1.0/mache.egg-info/SOURCES.txt +20 -0
- mache-0.1.0/mache.egg-info/dependency_links.txt +1 -0
- mache-0.1.0/mache.egg-info/entry_points.txt +5 -0
- mache-0.1.0/mache.egg-info/requires.txt +3 -0
- mache-0.1.0/mache.egg-info/top_level.txt +1 -0
- mache-0.1.0/pyproject.toml +54 -0
- mache-0.1.0/setup.cfg +4 -0
- mache-0.1.0/tests/test_action.py +71 -0
- mache-0.1.0/tests/test_book_slice.py +134 -0
- mache-0.1.0/tests/test_licence_headers.py +67 -0
- mache-0.1.0/tests/test_match_estimate.py +723 -0
- mache-0.1.0/tests/test_match_terminations.py +261 -0
- mache-0.1.0/tests/test_rating_estimate.py +285 -0
mache-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2026 Andrew Wright
|
|
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.
|
mache-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mache
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Read a sharded engine match: the pooled estimate and the ccrl fit
|
|
5
|
+
Author: Andrew Wright
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/aywrite/mache
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Provides-Extra: test
|
|
12
|
+
Requires-Dist: pytest~=9.1; extra == "test"
|
|
13
|
+
Dynamic: license-file
|
|
14
|
+
|
|
15
|
+
# mache
|
|
16
|
+
|
|
17
|
+
Measure A CHess Engine.
|
|
18
|
+
|
|
19
|
+
A collection of tools for measuring and benchmarking chess engines. From mache
|
|
20
|
+
(μάχη), Greek for battle.
|
|
21
|
+
|
|
22
|
+
## Why this exists
|
|
23
|
+
|
|
24
|
+
[OpenBench](https://github.com/AndyGrant/OpenBench) is how engine testing is
|
|
25
|
+
normally done: an instance hands out tests and client machines attach to it and
|
|
26
|
+
play the games. In almost every case using OpenBench is a far better choice.
|
|
27
|
+
|
|
28
|
+
This tool exists for two reasons.
|
|
29
|
+
|
|
30
|
+
The first is that it was not planned. It grew as I wrote
|
|
31
|
+
[arche](https://github.com/aywrite/arche), my first chess engine, starting as a
|
|
32
|
+
basic CI job that got out of hand.
|
|
33
|
+
|
|
34
|
+
The second is that OpenBench needs machines. There is a shared instance a good
|
|
35
|
+
many engines develop against, and a dozen or more projects run their own. mache
|
|
36
|
+
is for the case where you have neither. It has no instance and no clients, and
|
|
37
|
+
it runs on the hosted CI runners a repository already gets, as part of the pull
|
|
38
|
+
requests and releases it already runs, so testing an engine costs no machine you
|
|
39
|
+
have to own, administer or ask anyone to lend you. Both play their games with
|
|
40
|
+
fastchess underneath.
|
|
41
|
+
|
|
42
|
+
Hosted runners are the point of mache and also what make it awkward. They are
|
|
43
|
+
slow, they are noisy, they are shared, and a job is killed at a time limit often
|
|
44
|
+
long before a match worth reading has finished. So a match is split across jobs
|
|
45
|
+
that run at once and pooled afterwards, which is most of what the tools below
|
|
46
|
+
are for, and why they take the care described further down.
|
|
47
|
+
|
|
48
|
+
## What is here
|
|
49
|
+
|
|
50
|
+
`mache`, a Python package with four tools:
|
|
51
|
+
|
|
52
|
+
| Tool | What it answers |
|
|
53
|
+
| --- | --- |
|
|
54
|
+
| `match-estimate` | How much stronger, over the pooled games of every shard. Or, with bounds, whether |
|
|
55
|
+
| `rating-estimate` | Where an engine sits on a published rating scale, from a gauntlet |
|
|
56
|
+
| `match-terminations` | How the games actually ended |
|
|
57
|
+
| `book-slice` | Which openings a shard plays, so that no two shards share one |
|
|
58
|
+
|
|
59
|
+
`actions/setup`, a composite action that builds
|
|
60
|
+
[fastchess](https://github.com/Disservin/fastchess) at a pinned commit,
|
|
61
|
+
fetches an opening book and checks it against a recorded hash, and puts the
|
|
62
|
+
package on `PYTHONPATH`. Nothing is installed at match time.
|
|
63
|
+
|
|
64
|
+
## The part that is not obvious
|
|
65
|
+
|
|
66
|
+
A sharded match is not a long match cut up. Three things have to hold or the
|
|
67
|
+
number it produces is wrong.
|
|
68
|
+
|
|
69
|
+
**The estimate is over the pool.** fastchess prints one, but only for the
|
|
70
|
+
games its own process played. With five shards that is a fifth of the
|
|
71
|
+
evidence, and averaging five such figures is a different calculation.
|
|
72
|
+
`match-estimate` reads the games themselves.
|
|
73
|
+
|
|
74
|
+
**The error bar is over pairs, not games.** Under `-repeat` the two games of a
|
|
75
|
+
round are one opening with the colours reversed, so they are one observation.
|
|
76
|
+
Counting them as two understates the spread.
|
|
77
|
+
|
|
78
|
+
**A sequential test looks only at batch boundaries.** A per-shard SPRT that
|
|
79
|
+
stopped when its own games settled the question would be one look per shard at
|
|
80
|
+
a bound priced for one, on a sample chosen by what it said. Here the shards
|
|
81
|
+
play their slices out with nothing watching and the test is judged once over
|
|
82
|
+
all of them. A run is one batch, and `--prior-pairs` carries its pairs into
|
|
83
|
+
the next, so repeated runs accumulate into one test rather than several.
|
|
84
|
+
|
|
85
|
+
Openings follow from a seed rather than a shuffle, so a schedule can be played
|
|
86
|
+
again from what the run recorded.
|
|
87
|
+
|
|
88
|
+
## Reading a rating estimate
|
|
89
|
+
|
|
90
|
+
`rating-estimate` holds every opponent at its published figure and fits the one
|
|
91
|
+
free parameter, so the figure is the rating at which the expected score equals
|
|
92
|
+
the score actually made.
|
|
93
|
+
|
|
94
|
+
The `±` is a 95% interval and it describes the games and nothing else. Whether
|
|
95
|
+
one rating can describe the results at all is asked separately: when the
|
|
96
|
+
opponents disagree with each other by more than chance allows, a note says so,
|
|
97
|
+
and the interval is an understatement rather than an estimate.
|
|
98
|
+
|
|
99
|
+
**A placement against a published list carries a systematic error no number of
|
|
100
|
+
games reduces.** The opponents earned their ratings on other hardware at
|
|
101
|
+
slower time controls. Treat the figure as a placement worth about a hundred
|
|
102
|
+
points either way, not as a rating.
|
|
103
|
+
|
|
104
|
+
## Install
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
pip install mache
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The engine side needs no install. The action puts the package on the path.
|
|
111
|
+
|
|
112
|
+
## Status
|
|
113
|
+
|
|
114
|
+
Alpha. mache is used by [arche](https://github.com/aywrite/arche), which is
|
|
115
|
+
where it was written, and it has not yet been used by an engine that is not
|
|
116
|
+
arche. Until it has, expect the rough edges of a tool with one user.
|
|
117
|
+
|
|
118
|
+
mache is written with heavy AI assistance.
|
|
119
|
+
|
|
120
|
+
## Licence
|
|
121
|
+
|
|
122
|
+
MIT. See [LICENSE](LICENSE).
|
|
123
|
+
|
|
124
|
+
The generalized log likelihood ratio follows Van den Bergh's note on the
|
|
125
|
+
pentanomial model, written from the note and checked against fastchess: for
|
|
126
|
+
the same pairs the number here is the number it prints. Two test cases are
|
|
127
|
+
fastchess's own, attributed where they are used. fastchess is MIT.
|
mache-0.1.0/README.md
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# mache
|
|
2
|
+
|
|
3
|
+
Measure A CHess Engine.
|
|
4
|
+
|
|
5
|
+
A collection of tools for measuring and benchmarking chess engines. From mache
|
|
6
|
+
(μάχη), Greek for battle.
|
|
7
|
+
|
|
8
|
+
## Why this exists
|
|
9
|
+
|
|
10
|
+
[OpenBench](https://github.com/AndyGrant/OpenBench) is how engine testing is
|
|
11
|
+
normally done: an instance hands out tests and client machines attach to it and
|
|
12
|
+
play the games. In almost every case using OpenBench is a far better choice.
|
|
13
|
+
|
|
14
|
+
This tool exists for two reasons.
|
|
15
|
+
|
|
16
|
+
The first is that it was not planned. It grew as I wrote
|
|
17
|
+
[arche](https://github.com/aywrite/arche), my first chess engine, starting as a
|
|
18
|
+
basic CI job that got out of hand.
|
|
19
|
+
|
|
20
|
+
The second is that OpenBench needs machines. There is a shared instance a good
|
|
21
|
+
many engines develop against, and a dozen or more projects run their own. mache
|
|
22
|
+
is for the case where you have neither. It has no instance and no clients, and
|
|
23
|
+
it runs on the hosted CI runners a repository already gets, as part of the pull
|
|
24
|
+
requests and releases it already runs, so testing an engine costs no machine you
|
|
25
|
+
have to own, administer or ask anyone to lend you. Both play their games with
|
|
26
|
+
fastchess underneath.
|
|
27
|
+
|
|
28
|
+
Hosted runners are the point of mache and also what make it awkward. They are
|
|
29
|
+
slow, they are noisy, they are shared, and a job is killed at a time limit often
|
|
30
|
+
long before a match worth reading has finished. So a match is split across jobs
|
|
31
|
+
that run at once and pooled afterwards, which is most of what the tools below
|
|
32
|
+
are for, and why they take the care described further down.
|
|
33
|
+
|
|
34
|
+
## What is here
|
|
35
|
+
|
|
36
|
+
`mache`, a Python package with four tools:
|
|
37
|
+
|
|
38
|
+
| Tool | What it answers |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `match-estimate` | How much stronger, over the pooled games of every shard. Or, with bounds, whether |
|
|
41
|
+
| `rating-estimate` | Where an engine sits on a published rating scale, from a gauntlet |
|
|
42
|
+
| `match-terminations` | How the games actually ended |
|
|
43
|
+
| `book-slice` | Which openings a shard plays, so that no two shards share one |
|
|
44
|
+
|
|
45
|
+
`actions/setup`, a composite action that builds
|
|
46
|
+
[fastchess](https://github.com/Disservin/fastchess) at a pinned commit,
|
|
47
|
+
fetches an opening book and checks it against a recorded hash, and puts the
|
|
48
|
+
package on `PYTHONPATH`. Nothing is installed at match time.
|
|
49
|
+
|
|
50
|
+
## The part that is not obvious
|
|
51
|
+
|
|
52
|
+
A sharded match is not a long match cut up. Three things have to hold or the
|
|
53
|
+
number it produces is wrong.
|
|
54
|
+
|
|
55
|
+
**The estimate is over the pool.** fastchess prints one, but only for the
|
|
56
|
+
games its own process played. With five shards that is a fifth of the
|
|
57
|
+
evidence, and averaging five such figures is a different calculation.
|
|
58
|
+
`match-estimate` reads the games themselves.
|
|
59
|
+
|
|
60
|
+
**The error bar is over pairs, not games.** Under `-repeat` the two games of a
|
|
61
|
+
round are one opening with the colours reversed, so they are one observation.
|
|
62
|
+
Counting them as two understates the spread.
|
|
63
|
+
|
|
64
|
+
**A sequential test looks only at batch boundaries.** A per-shard SPRT that
|
|
65
|
+
stopped when its own games settled the question would be one look per shard at
|
|
66
|
+
a bound priced for one, on a sample chosen by what it said. Here the shards
|
|
67
|
+
play their slices out with nothing watching and the test is judged once over
|
|
68
|
+
all of them. A run is one batch, and `--prior-pairs` carries its pairs into
|
|
69
|
+
the next, so repeated runs accumulate into one test rather than several.
|
|
70
|
+
|
|
71
|
+
Openings follow from a seed rather than a shuffle, so a schedule can be played
|
|
72
|
+
again from what the run recorded.
|
|
73
|
+
|
|
74
|
+
## Reading a rating estimate
|
|
75
|
+
|
|
76
|
+
`rating-estimate` holds every opponent at its published figure and fits the one
|
|
77
|
+
free parameter, so the figure is the rating at which the expected score equals
|
|
78
|
+
the score actually made.
|
|
79
|
+
|
|
80
|
+
The `±` is a 95% interval and it describes the games and nothing else. Whether
|
|
81
|
+
one rating can describe the results at all is asked separately: when the
|
|
82
|
+
opponents disagree with each other by more than chance allows, a note says so,
|
|
83
|
+
and the interval is an understatement rather than an estimate.
|
|
84
|
+
|
|
85
|
+
**A placement against a published list carries a systematic error no number of
|
|
86
|
+
games reduces.** The opponents earned their ratings on other hardware at
|
|
87
|
+
slower time controls. Treat the figure as a placement worth about a hundred
|
|
88
|
+
points either way, not as a rating.
|
|
89
|
+
|
|
90
|
+
## Install
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
pip install mache
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The engine side needs no install. The action puts the package on the path.
|
|
97
|
+
|
|
98
|
+
## Status
|
|
99
|
+
|
|
100
|
+
Alpha. mache is used by [arche](https://github.com/aywrite/arche), which is
|
|
101
|
+
where it was written, and it has not yet been used by an engine that is not
|
|
102
|
+
arche. Until it has, expect the rough edges of a tool with one user.
|
|
103
|
+
|
|
104
|
+
mache is written with heavy AI assistance.
|
|
105
|
+
|
|
106
|
+
## Licence
|
|
107
|
+
|
|
108
|
+
MIT. See [LICENSE](LICENSE).
|
|
109
|
+
|
|
110
|
+
The generalized log likelihood ratio follows Van den Bergh's note on the
|
|
111
|
+
pentanomial model, written from the note and checked against fastchess: for
|
|
112
|
+
the same pairs the number here is the number it prints. Two test cases are
|
|
113
|
+
fastchess's own, attributed where they are used. fastchess is MIT.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (C) 2022-2026 Andrew Wright
|
|
3
|
+
|
|
4
|
+
"""The tools a match is read with: the pooled estimate, the ccrl fit, the
|
|
5
|
+
terminations count and the book slice.
|
|
6
|
+
|
|
7
|
+
They are one package because they read one thing. The fit owns the two regular
|
|
8
|
+
expressions a fastchess pgn is split into games with, and the estimate reads
|
|
9
|
+
the terminations count as well as the fit.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.1.0"
|
|
13
|
+
|
|
14
|
+
# The version of the --json shape, and the one thing every --json object says
|
|
15
|
+
# about itself. It is 1 because the shape is released: a tagged version is what
|
|
16
|
+
# another repository can pin, and a shape nobody can pin is not a contract.
|
|
17
|
+
# Fields are added from here on. None is removed, and none is given a new
|
|
18
|
+
# meaning under the name it already has. A change that cannot be made that way
|
|
19
|
+
# raises this number, and a reader that finds a number it does not know should
|
|
20
|
+
# say so rather than read the object anyway.
|
|
21
|
+
JSON_FORMAT = 1
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def tool(command: str) -> dict[str, str]:
|
|
25
|
+
"""What produced a --json object, so a figure can be read back against the
|
|
26
|
+
version of the tooling that produced it."""
|
|
27
|
+
return {"name": "mache", "version": __version__, "command": command}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# SPDX-License-Identifier: MIT
|
|
2
|
+
# Copyright (C) 2022-2026 Andrew Wright
|
|
3
|
+
|
|
4
|
+
"""Print the opening a shard of a match starts at.
|
|
5
|
+
|
|
6
|
+
The shards of a match play at the same time and are pooled afterwards, so no
|
|
7
|
+
two of them may play the same opening: a position played twice would be counted
|
|
8
|
+
twice and the games would not be the independent sample the estimate reads them
|
|
9
|
+
as. The book is taken in order rather than drawn, and each shard is given a
|
|
10
|
+
slice of its own, which is where its games begin.
|
|
11
|
+
|
|
12
|
+
The run's first shard starts at a remainder of the seed, so two runs of the same
|
|
13
|
+
size play different regions of a book far larger than either of them uses, and
|
|
14
|
+
each shard after it starts a slice further along. The starts are worked out from
|
|
15
|
+
the seed and the counts alone, so the manifest is enough to play the schedule
|
|
16
|
+
again, without depending on how fastchess draws its own openings.
|
|
17
|
+
|
|
18
|
+
The index printed is one based, which is what fastchess's `start=` takes.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import sys
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def first_opening(openings: int, wanted: int, seed: int) -> int:
|
|
26
|
+
"""Where the first shard begins. The offset leaves room for every shard, so
|
|
27
|
+
the last one still ends inside the book."""
|
|
28
|
+
room = openings - wanted
|
|
29
|
+
if room < 1:
|
|
30
|
+
return 1
|
|
31
|
+
return 1 + seed % room
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def start(openings: int, pairs: int, shards: int, shard: int, seed: int) -> int:
|
|
35
|
+
"""The opening this shard begins at, one based."""
|
|
36
|
+
return first_opening(openings, pairs * shards, seed) + shard * pairs
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def main() -> None:
|
|
40
|
+
parser = argparse.ArgumentParser(description=__doc__)
|
|
41
|
+
parser.add_argument("--openings", type=int, required=True, help="what the book has")
|
|
42
|
+
parser.add_argument("--pairs", type=int, required=True, help="openings per shard")
|
|
43
|
+
parser.add_argument("--shards", type=int, required=True, help="shards in the run")
|
|
44
|
+
parser.add_argument("--shard", type=int, required=True, help="which one, from zero")
|
|
45
|
+
parser.add_argument("--seed", type=int, required=True, help="the run's seed")
|
|
46
|
+
args = parser.parse_args()
|
|
47
|
+
|
|
48
|
+
if min(args.openings, args.pairs, args.shards) < 1 or args.seed < 0:
|
|
49
|
+
sys.exit("the counts are all at least one and the seed is not negative")
|
|
50
|
+
if not 0 <= args.shard < args.shards:
|
|
51
|
+
sys.exit(f"shard {args.shard} is not one of {args.shards}")
|
|
52
|
+
|
|
53
|
+
wanted = args.pairs * args.shards
|
|
54
|
+
if args.openings - wanted < 1:
|
|
55
|
+
# fastchess reads on around the end of the book, so the match still
|
|
56
|
+
# plays. It is the shards no longer being disjoint that is worth saying
|
|
57
|
+
print(
|
|
58
|
+
f"the book holds {args.openings} openings and the run wants {wanted},"
|
|
59
|
+
" so the shards repeat each other",
|
|
60
|
+
file=sys.stderr,
|
|
61
|
+
)
|
|
62
|
+
print(start(args.openings, args.pairs, args.shards, args.shard, args.seed))
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|