cuju 0.1.0__py3-none-win_amd64.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.
- cuju/__init__.py +142 -0
- cuju/_batch/__init__.py +4 -0
- cuju/_batch/allocator.py +52 -0
- cuju/_batch/assets.py +68 -0
- cuju/_batch/environment.py +1502 -0
- cuju/_batch/frame_stack.py +114 -0
- cuju/_batch/library.py +276 -0
- cuju/_batch/log.py +96 -0
- cuju/_batch/memory.py +70 -0
- cuju/_batch/operators.py +55 -0
- cuju/_batch/pinned.py +39 -0
- cuju/_batch/state.py +77 -0
- cuju/_checksums.py +60 -0
- cuju/_python_players.py +215 -0
- cuju/_replays.py +411 -0
- cuju/_version.py +6 -0
- cuju/action_log.py +371 -0
- cuju/check.py +479 -0
- cuju/cuju_cpu.dll +0 -0
- cuju/cuju_cuda.dll +0 -0
- cuju/data/SHA256SUMS +20 -0
- cuju/data/animations.bin +0 -0
- cuju/data/assets/11_vs_11_competition_l0_r0.cjb +0 -0
- cuju/data/assets/11_vs_11_easy_stochastic_l0_r0.cjb +0 -0
- cuju/data/assets/11_vs_11_hard_stochastic_l0_r0.cjb +0 -0
- cuju/data/assets/11_vs_11_kaggle_l0_r0.cjb +0 -0
- cuju/data/assets/11_vs_11_stochastic_l0_r0.cjb +0 -0
- cuju/data/assets/1_vs_1_easy_l0_r0.cjb +0 -0
- cuju/data/assets/5_vs_5_l0_r0.cjb +0 -0
- cuju/data/assets/academy_3_vs_1_with_keeper_l0_r0.cjb +0 -0
- cuju/data/assets/academy_corner_l0_r0.cjb +0 -0
- cuju/data/assets/academy_counterattack_easy_l0_r0.cjb +0 -0
- cuju/data/assets/academy_counterattack_hard_l0_r0.cjb +0 -0
- cuju/data/assets/academy_empty_goal_close_l0_r0.cjb +0 -0
- cuju/data/assets/academy_empty_goal_l0_r0.cjb +0 -0
- cuju/data/assets/academy_pass_and_shoot_with_keeper_l0_r0.cjb +0 -0
- cuju/data/assets/academy_run_pass_and_shoot_with_keeper_l0_r0.cjb +0 -0
- cuju/data/assets/academy_run_to_score_l0_r0.cjb +0 -0
- cuju/data/assets/academy_run_to_score_with_keeper_l0_r0.cjb +0 -0
- cuju/data/assets/academy_single_goal_versus_lazy_l0_r0.cjb +0 -0
- cuju/data/assets/test_example_multiagent_l0_r0.cjb +0 -0
- cuju/data/digests.json +186 -0
- cuju/data/scenarios/11_vs_11_competition.py +45 -0
- cuju/data/scenarios/11_vs_11_easy_stochastic.py +44 -0
- cuju/data/scenarios/11_vs_11_hard_stochastic.py +44 -0
- cuju/data/scenarios/11_vs_11_kaggle.py +46 -0
- cuju/data/scenarios/11_vs_11_stochastic.py +44 -0
- cuju/data/scenarios/1_vs_1_easy.py +25 -0
- cuju/data/scenarios/5_vs_5.py +33 -0
- cuju/data/scenarios/academy_3_vs_1_with_keeper.py +28 -0
- cuju/data/scenarios/academy_corner.py +45 -0
- cuju/data/scenarios/academy_counterattack_easy.py +45 -0
- cuju/data/scenarios/academy_counterattack_hard.py +45 -0
- cuju/data/scenarios/academy_empty_goal.py +25 -0
- cuju/data/scenarios/academy_empty_goal_close.py +25 -0
- cuju/data/scenarios/academy_pass_and_shoot_with_keeper.py +27 -0
- cuju/data/scenarios/academy_run_pass_and_shoot_with_keeper.py +27 -0
- cuju/data/scenarios/academy_run_to_score.py +30 -0
- cuju/data/scenarios/academy_run_to_score_with_keeper.py +30 -0
- cuju/data/scenarios/academy_single_goal_versus_lazy.py +49 -0
- cuju/data/scenarios/test_example_multiagent.py +28 -0
- cuju/dumps.py +909 -0
- cuju/env.py +1130 -0
- cuju/errors.py +92 -0
- cuju/gfootball/__init__.py +30 -0
- cuju/gfootball/dump_to_txt.py +52 -0
- cuju/gfootball/env/__init__.py +36 -0
- cuju/gfootball/env/config.py +284 -0
- cuju/gfootball/env/football_action_set.py +158 -0
- cuju/gfootball/env/football_env.py +68 -0
- cuju/gfootball/env/observation_preprocessing.py +98 -0
- cuju/gfootball/env/player_base.py +44 -0
- cuju/gfootball/env/players/__init__.py +9 -0
- cuju/gfootball/env/players/replay.py +40 -0
- cuju/gfootball/env/scenario_builder.py +70 -0
- cuju/gfootball/env/script_helpers.py +102 -0
- cuju/gfootball/env/wrappers.py +523 -0
- cuju/gfootball/replay.py +80 -0
- cuju/gfootball/scenarios.py +38 -0
- cuju/kaggle.py +266 -0
- cuju/multi_device.py +382 -0
- cuju/players.py +260 -0
- cuju/raw.py +93 -0
- cuju/scenarios.py +657 -0
- cuju/wrappers.py +325 -0
- cuju-0.1.0.dist-info/METADATA +287 -0
- cuju-0.1.0.dist-info/RECORD +96 -0
- cuju-0.1.0.dist-info/WHEEL +5 -0
- cuju-0.1.0.dist-info/licenses/LICENSE +202 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/BSD-3-Clause.txt +31 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/BSL-1.0.txt +23 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/CC-BY-4.0.txt +396 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/LGPL-2.1-or-later.txt +502 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/MIT.txt +23 -0
- cuju-0.1.0.dist-info/licenses/LICENSES/SunPro.txt +8 -0
- cuju-0.1.0.dist-info/licenses/NOTICE +24 -0
cuju/__init__.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
# Copyright 2026 The cuju Authors
|
|
2
|
+
# Copyright 2019 Google LLC
|
|
3
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
4
|
+
|
|
5
|
+
"""Batched Google Research Football (GRF) environments on the GPU or the CPU, bit for bit.
|
|
6
|
+
|
|
7
|
+
`BatchEnvironment` steps many environments per call, and its outputs are PyTorch tensors that share
|
|
8
|
+
its buffers. The guide and the reference are in the repository's `docs/` (`index.md`, `api.md`).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import importlib.util
|
|
14
|
+
import sys
|
|
15
|
+
from typing import ClassVar
|
|
16
|
+
|
|
17
|
+
from ._batch.assets import bundled_assets, bundled_scenarios
|
|
18
|
+
from ._batch.environment import BatchEnvironment, memory_estimate
|
|
19
|
+
from ._batch.library import library_path
|
|
20
|
+
from ._batch.memory import _MemoryEstimate
|
|
21
|
+
from ._version import __version__
|
|
22
|
+
from .errors import (
|
|
23
|
+
ActionRangeError,
|
|
24
|
+
ArgumentError,
|
|
25
|
+
ChoiceError,
|
|
26
|
+
ClosedError,
|
|
27
|
+
ConfigurationError,
|
|
28
|
+
DeviceError,
|
|
29
|
+
EnvironmentIndexError,
|
|
30
|
+
Error,
|
|
31
|
+
LibraryError,
|
|
32
|
+
MissingFileError,
|
|
33
|
+
StateError,
|
|
34
|
+
UndefinedPathError,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
'ActionRangeError',
|
|
39
|
+
'ArgumentError',
|
|
40
|
+
'BatchEnvironment',
|
|
41
|
+
'ChoiceError',
|
|
42
|
+
'ClosedError',
|
|
43
|
+
'ConfigurationError',
|
|
44
|
+
'DeviceError',
|
|
45
|
+
'EnvironmentIndexError',
|
|
46
|
+
'Error',
|
|
47
|
+
'LibraryError',
|
|
48
|
+
'MissingFileError',
|
|
49
|
+
'MultiDeviceBatch',
|
|
50
|
+
'StateError',
|
|
51
|
+
'UndefinedPathError',
|
|
52
|
+
'__version__',
|
|
53
|
+
'bundled_assets',
|
|
54
|
+
'bundled_scenarios',
|
|
55
|
+
'library_path',
|
|
56
|
+
'memory_estimate',
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
# The names keep `cuju` as their module, so that their reprs and pickles name the public path.
|
|
60
|
+
for _name in (
|
|
61
|
+
BatchEnvironment,
|
|
62
|
+
bundled_assets,
|
|
63
|
+
bundled_scenarios,
|
|
64
|
+
library_path,
|
|
65
|
+
memory_estimate,
|
|
66
|
+
_MemoryEstimate,
|
|
67
|
+
):
|
|
68
|
+
_name.__module__ = __name__
|
|
69
|
+
del _name
|
|
70
|
+
|
|
71
|
+
# The digests and action logs build on `BatchEnvironment`.
|
|
72
|
+
from .action_log import ActionLog, Digest, digest, replay # noqa: E402
|
|
73
|
+
|
|
74
|
+
__all__ += ['ActionLog', 'Digest', 'digest', 'replay']
|
|
75
|
+
|
|
76
|
+
# Last, since it builds on `BatchEnvironment`.
|
|
77
|
+
from .multi_device import MultiDeviceBatch # noqa: E402
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class _RegisteringLoader:
|
|
81
|
+
"""Runs the loader of gymnasium or gym, then registers GRF's ids with the module."""
|
|
82
|
+
|
|
83
|
+
def __init__(self, loader, name):
|
|
84
|
+
self._loader = loader
|
|
85
|
+
self._name = name
|
|
86
|
+
|
|
87
|
+
def create_module(self, spec):
|
|
88
|
+
return self._loader.create_module(spec)
|
|
89
|
+
|
|
90
|
+
def exec_module(self, module):
|
|
91
|
+
self._loader.exec_module(module)
|
|
92
|
+
_RegistrationFinder.waiting.discard(self._name)
|
|
93
|
+
_register_ids(self._name)
|
|
94
|
+
|
|
95
|
+
def __getattr__(self, name):
|
|
96
|
+
return getattr(self._loader, name)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _RegistrationFinder:
|
|
100
|
+
"""Hands gymnasium and gym, when imported, a loader that then registers GRF's ids."""
|
|
101
|
+
|
|
102
|
+
waiting: ClassVar[set[str]] = set()
|
|
103
|
+
# The names being looked up by the other finders, which this one then leaves to them.
|
|
104
|
+
finding: ClassVar[set[str]] = set()
|
|
105
|
+
|
|
106
|
+
def find_spec(self, name, path=None, target=None):
|
|
107
|
+
if name not in self.waiting or name in self.finding:
|
|
108
|
+
return None
|
|
109
|
+
self.finding.add(name)
|
|
110
|
+
try:
|
|
111
|
+
spec = importlib.util.find_spec(name)
|
|
112
|
+
finally:
|
|
113
|
+
self.finding.discard(name)
|
|
114
|
+
if spec is not None and spec.loader is not None:
|
|
115
|
+
spec.loader = _RegisteringLoader(spec.loader, name)
|
|
116
|
+
return spec
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _register_ids(module):
|
|
120
|
+
# A `cuju.env` that is still importing registers the ids itself once it is done.
|
|
121
|
+
try:
|
|
122
|
+
from .env import _register_with
|
|
123
|
+
except ImportError:
|
|
124
|
+
return
|
|
125
|
+
_register_with(module)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _register_gymnasium_ids():
|
|
129
|
+
"""Registers GRF's ids with gymnasium and gym before 0.26, as importing `gfootball` does.
|
|
130
|
+
|
|
131
|
+
Each gets them once it is imported, so that `import cuju` imports neither.
|
|
132
|
+
"""
|
|
133
|
+
for module in ('gymnasium', 'gym'):
|
|
134
|
+
if module in sys.modules:
|
|
135
|
+
_register_ids(module)
|
|
136
|
+
else:
|
|
137
|
+
_RegistrationFinder.waiting.add(module)
|
|
138
|
+
if not any(isinstance(finder, _RegistrationFinder) for finder in sys.meta_path):
|
|
139
|
+
sys.meta_path.insert(0, _RegistrationFinder())
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
_register_gymnasium_ids()
|
cuju/_batch/__init__.py
ADDED
cuju/_batch/allocator.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Copyright 2026 The cuju Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
"""The device memory of a batch through PyTorch's caching allocator."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import ctypes
|
|
8
|
+
|
|
9
|
+
import torch
|
|
10
|
+
|
|
11
|
+
# The C signatures of `allocate` and `release` in `cuju_configuration`.
|
|
12
|
+
ALLOCATE = ctypes.CFUNCTYPE(ctypes.c_void_p, ctypes.c_size_t, ctypes.c_void_p)
|
|
13
|
+
RELEASE = ctypes.CFUNCTYPE(None, ctypes.c_void_p, ctypes.c_void_p)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class TorchAllocator:
|
|
17
|
+
"""The `allocate` and `release` functions that the library calls, over torch's allocator.
|
|
18
|
+
|
|
19
|
+
The memory comes from the default stream of the device, which the library's copies and fills
|
|
20
|
+
use, and the library releases it only once the device is idle, so torch may hand it out again
|
|
21
|
+
at once.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
allocate, release: The C functions, which must outlive the batch.
|
|
25
|
+
errors: The exceptions that torch raised in `allocate`, such as `torch.OutOfMemoryError`.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def __init__(self, device: torch.device) -> None:
|
|
29
|
+
stream = torch.cuda.default_stream(device)
|
|
30
|
+
errors: list[BaseException] = []
|
|
31
|
+
|
|
32
|
+
def allocate(size, _context):
|
|
33
|
+
try:
|
|
34
|
+
return torch.cuda.caching_allocator_alloc(size, device, stream)
|
|
35
|
+
# An exception cannot cross the library, so the batch raises it after the call.
|
|
36
|
+
except Exception as error:
|
|
37
|
+
errors.append(error)
|
|
38
|
+
return None
|
|
39
|
+
|
|
40
|
+
def release(pointer, _context):
|
|
41
|
+
torch.cuda.caching_allocator_delete(pointer)
|
|
42
|
+
|
|
43
|
+
self.allocate = ALLOCATE(allocate)
|
|
44
|
+
self.release = RELEASE(release)
|
|
45
|
+
self.errors = errors
|
|
46
|
+
|
|
47
|
+
def pointers(self) -> tuple[int, int]:
|
|
48
|
+
"""Returns the addresses of `allocate` and `release` for `cuju_configuration`."""
|
|
49
|
+
return (
|
|
50
|
+
ctypes.cast(self.allocate, ctypes.c_void_p).value,
|
|
51
|
+
ctypes.cast(self.release, ctypes.c_void_p).value,
|
|
52
|
+
)
|
cuju/_batch/assets.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Copyright 2026 The cuju Authors
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
|
|
4
|
+
"""The asset bundles that the package ships, and the bundle that a name or path asks for."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from ..errors import MissingFileError
|
|
12
|
+
|
|
13
|
+
_DATA_DIRECTORY = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def bundled_assets() -> list[str]:
|
|
17
|
+
"""Returns the names of the asset bundles that the package ships, such as `5_vs_5_l4_r0`.
|
|
18
|
+
|
|
19
|
+
`BatchEnvironment` takes such a name `<scenario>_l<left agents>_r<right agents>` in place of a
|
|
20
|
+
path, with any number of agents of a shipped scenario.
|
|
21
|
+
"""
|
|
22
|
+
directory = os.path.join(_DATA_DIRECTORY, 'assets')
|
|
23
|
+
if not os.path.isdir(directory):
|
|
24
|
+
return []
|
|
25
|
+
return sorted(name[: -len('.cjb')] for name in os.listdir(directory) if name.endswith('.cjb'))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _split_name(name):
|
|
29
|
+
"""Returns the scenario, left and right agents of `<scenario>_l<left>_r<right>`, or `None`."""
|
|
30
|
+
match = re.fullmatch(r'(.+)_l(\d+)_r(\d+)', name)
|
|
31
|
+
return None if match is None else (match.group(1), int(match.group(2)), int(match.group(3)))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def bundled_scenarios() -> list[str]:
|
|
35
|
+
"""Returns the scenarios of the asset bundles that the package ships, such as `5_vs_5`."""
|
|
36
|
+
return sorted({split[0] for split in map(_split_name, bundled_assets()) if split})
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _resolve_asset(asset: str) -> tuple[str, tuple[int, int]]:
|
|
40
|
+
"""Returns the bundle path of a path or name, and the agents per side it asks for (-1 keeps)."""
|
|
41
|
+
if callable(asset):
|
|
42
|
+
raise TypeError(
|
|
43
|
+
'expected the path of a scenario file or of a bundle, found a function; pass the path '
|
|
44
|
+
'of its file, or build a bundle with cuju.scenarios.build_bundle'
|
|
45
|
+
)
|
|
46
|
+
# GRF: `create_environment` gives a scenario file or a bare name one left agent.
|
|
47
|
+
if os.path.isfile(asset):
|
|
48
|
+
return asset, (1, 0) if asset.endswith('.py') else (-1, -1)
|
|
49
|
+
if asset.endswith(('.py', '.cjb')) or os.sep in asset or '/' in asset:
|
|
50
|
+
raise MissingFileError(f'expected a scenario file or a bundle at {asset!r}, found no file')
|
|
51
|
+
directory = os.path.join(_DATA_DIRECTORY, 'assets')
|
|
52
|
+
shipped = os.path.join(directory, asset + '.cjb')
|
|
53
|
+
if os.path.isfile(shipped):
|
|
54
|
+
return shipped, (-1, -1)
|
|
55
|
+
split = _split_name(asset)
|
|
56
|
+
if split is None and asset in bundled_scenarios():
|
|
57
|
+
split = (asset, 1, 0)
|
|
58
|
+
if split:
|
|
59
|
+
scenario, left, right = split
|
|
60
|
+
# The bundle with the fewest agents serves any counts; the library sets up the agents anew.
|
|
61
|
+
bases = [name for name in bundled_assets() if (_split_name(name) or ('',))[0] == scenario]
|
|
62
|
+
if bases:
|
|
63
|
+
base = min(bases, key=lambda name: sum(_split_name(name)[1:]))
|
|
64
|
+
return os.path.join(directory, base + '.cjb'), (left, right)
|
|
65
|
+
raise MissingFileError(
|
|
66
|
+
f'expected the path of an asset bundle or `<scenario>_l<left>_r<right>` of one of '
|
|
67
|
+
f'{bundled_scenarios()}, found {asset!r}'
|
|
68
|
+
)
|