rl-language 0.1.0__py3-none-any.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 (57) hide show
  1. command_line/__init__.py +15 -0
  2. command_line/utils.py +93 -0
  3. loader/__init__.py +10 -0
  4. loader/simulation.py +237 -0
  5. ml/__init__.py +12 -0
  6. ml/action_oracle.py +61 -0
  7. ml/raylib/__init__.py +0 -0
  8. ml/raylib/action_mask.py +87 -0
  9. ml/raylib/environment.py +159 -0
  10. ml/raylib/module_config.py +122 -0
  11. ml/raylib/pebble_runner.py +462 -0
  12. ml/raylib/raylib_runner.py +139 -0
  13. ml/training_data.py +121 -0
  14. ml/transformer.py +268 -0
  15. ml/utils.py +16 -0
  16. rl_language-0.1.0.data/data/bin/rlc +0 -0
  17. rl_language-0.1.0.data/data/bin/rlc-lsp +0 -0
  18. rl_language-0.1.0.data/data/lib/libruntime.a +0 -0
  19. rl_language-0.1.0.data/data/lib/rlc/stdlib/.action.rl.swp +0 -0
  20. rl_language-0.1.0.data/data/lib/rlc/stdlib/.machine_learning.rl.swp +0 -0
  21. rl_language-0.1.0.data/data/lib/rlc/stdlib/.string.rl.swp +0 -0
  22. rl_language-0.1.0.data/data/lib/rlc/stdlib/action.rl +287 -0
  23. rl_language-0.1.0.data/data/lib/rlc/stdlib/algorithms/equal.rl +29 -0
  24. rl_language-0.1.0.data/data/lib/rlc/stdlib/bounded_arg.rl +143 -0
  25. rl_language-0.1.0.data/data/lib/rlc/stdlib/collections/.graph.rl.swp +0 -0
  26. rl_language-0.1.0.data/data/lib/rlc/stdlib/collections/.vector.rl.swp +0 -0
  27. rl_language-0.1.0.data/data/lib/rlc/stdlib/collections/graph.rl +179 -0
  28. rl_language-0.1.0.data/data/lib/rlc/stdlib/collections/hidden_information.rl +17 -0
  29. rl_language-0.1.0.data/data/lib/rlc/stdlib/collections/vector.rl +121 -0
  30. rl_language-0.1.0.data/data/lib/rlc/stdlib/machine_learning.rl +43 -0
  31. rl_language-0.1.0.data/data/lib/rlc/stdlib/math/numeric.rl +23 -0
  32. rl_language-0.1.0.data/data/lib/rlc/stdlib/none.rl +12 -0
  33. rl_language-0.1.0.data/data/lib/rlc/stdlib/serialization/print.rl +34 -0
  34. rl_language-0.1.0.data/data/lib/rlc/stdlib/serialization/to_byte_vector.rl +168 -0
  35. rl_language-0.1.0.data/data/lib/rlc/stdlib/string.rl +396 -0
  36. rl_language-0.1.0.dist-info/METADATA +33 -0
  37. rl_language-0.1.0.dist-info/RECORD +57 -0
  38. rl_language-0.1.0.dist-info/WHEEL +5 -0
  39. rl_language-0.1.0.dist-info/entry_points.txt +3 -0
  40. rl_language-0.1.0.dist-info/top_level.txt +6 -0
  41. solvers/__init__.py +11 -0
  42. solvers/ender.py +59 -0
  43. solvers/monte_carlo.py +103 -0
  44. test/__init__.py +9 -0
  45. test/example_test.py +11 -0
  46. test/monte_carlo_test.py +91 -0
  47. tool/__init__.py +0 -0
  48. tool/action.py +113 -0
  49. tool/disassembly.py +59 -0
  50. tool/gen_dataset.py +71 -0
  51. tool/learn.py +87 -0
  52. tool/predict.py +75 -0
  53. tool/print_dataset.py +45 -0
  54. tool/rlc.py +9 -0
  55. tool/rlc_lsp.py +10 -0
  56. tool/solve.py +29 -0
  57. tool/test.py +54 -0
@@ -0,0 +1,15 @@
1
+ #
2
+ # This file is part of the RLC project.
3
+ #
4
+ # RLC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
5
+ #
6
+ # RLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7
+ #
8
+ # You should have received a copy of the GNU General Public License along with RLC. If not, see <https://www.gnu.org/licenses/>.
9
+ #
10
+ from .utils import (
11
+ make_rlc_argparse,
12
+ load_simulation_from_args,
13
+ load_dataset,
14
+ load_network,
15
+ )
command_line/utils.py ADDED
@@ -0,0 +1,93 @@
1
+ #
2
+ # This file is part of the RLC project.
3
+ #
4
+ # RLC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
5
+ #
6
+ # RLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7
+ #
8
+ # You should have received a copy of the GNU General Public License along with RLC. If not, see <https://www.gnu.org/licenses/>.
9
+ #
10
+ from shutil import which
11
+ import argparse
12
+ from loader import Simulation, compile
13
+ import pickle
14
+ from ml import RLCTransformer
15
+ import torch
16
+
17
+
18
+ def load_network(path, ntokens, device):
19
+ if path == "":
20
+ return RLCTransformer(ntokens=ntokens, device=device)
21
+ return torch.load(path)
22
+
23
+
24
+ def load_dataset(path):
25
+ to_return = []
26
+ with open(path, "rb") as f:
27
+ while True:
28
+ try:
29
+ loaded = pickle.load(f)
30
+ for (state, actions_and_scores) in loaded:
31
+ to_return.append((state, actions_and_scores))
32
+ except Exception as e:
33
+ break
34
+ return to_return
35
+
36
+
37
+ def make_rlc_argparse(name, description):
38
+ parser = argparse.ArgumentParser(name, description=description)
39
+ parser.add_argument(
40
+ "--wrapper",
41
+ "-w",
42
+ type=str,
43
+ nargs="?",
44
+ help="path to wrapper to load",
45
+ default="",
46
+ )
47
+ parser.add_argument(
48
+ "--include",
49
+ "-i",
50
+ type=str,
51
+ nargs="*",
52
+ help="path to folder where rl files can be found",
53
+ default=[],
54
+ )
55
+ parser.add_argument(
56
+ "--rlc",
57
+ "-c",
58
+ type=str,
59
+ nargs="?",
60
+ help="path to rlc compiler",
61
+ default="rlc",
62
+ )
63
+ parser.add_argument(
64
+ "--source",
65
+ "-rl",
66
+ type=str,
67
+ nargs="?",
68
+ help="path to .rl source file",
69
+ )
70
+ parser.add_argument(
71
+ "--runtime",
72
+ "-rt",
73
+ type=str,
74
+ default="",
75
+ nargs="?",
76
+ help="path to runtime library",
77
+ )
78
+ return parser
79
+
80
+
81
+ def load_simulation_from_args(args):
82
+ assert (
83
+ which(args.rlc) is not None
84
+ ), "could not find executable {}, use --rlc <path_to_rlc> to configure it".format(
85
+ args.rlc
86
+ )
87
+
88
+ sim = (
89
+ Simulation(args.wrapper)
90
+ if args.wrapper != ""
91
+ else compile(args.source, args.rlc, args.include, args.runtime)
92
+ )
93
+ return sim
loader/__init__.py ADDED
@@ -0,0 +1,10 @@
1
+ #
2
+ # This file is part of the RLC project.
3
+ #
4
+ # RLC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
5
+ #
6
+ # RLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7
+ #
8
+ # You should have received a copy of the GNU General Public License along with RLC. If not, see <https://www.gnu.org/licenses/>.
9
+ #
10
+ from .simulation import Simulation, Action, State, compile
loader/simulation.py ADDED
@@ -0,0 +1,237 @@
1
+ #
2
+ # Copyright 2024 Massimo Fioravanti
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ from importlib import import_module, machinery, util
17
+ import inspect
18
+ from math import log2, ceil, floor
19
+ from collections import defaultdict
20
+ from tempfile import TemporaryDirectory
21
+ from subprocess import run
22
+ from ctypes import Structure, Array
23
+ from sys import stdout, stderr
24
+
25
+
26
+ def import_file(name, file_path):
27
+ loader = machinery.SourceFileLoader(name, file_path)
28
+ spec = util.spec_from_loader(name, loader)
29
+ mod = util.module_from_spec(spec)
30
+ loader.exec_module(mod)
31
+ return mod
32
+
33
+
34
+ class Action:
35
+ def __init__(self, action, simulation):
36
+ self.action = action
37
+ self.simulation = simulation
38
+ self.functions = simulation.module.functions
39
+
40
+ def __str__(self) -> str:
41
+ return self.simulation.to_python_string(self.functions.to_string(self.action))
42
+
43
+ def to_bytes(self) -> bytes:
44
+ result = self.functions.as_byte_vector(self.action)
45
+ real_content = []
46
+ for i in range(getattr(result, "__size")):
47
+ real_content.append(getattr(result, "__data")[i] + 128)
48
+ return bytes(real_content)
49
+
50
+ def can_run(self, state) -> bool:
51
+ return self.functions.can_apply_impl(self.action, state.state)
52
+
53
+ def run(self, state):
54
+ self.functions.apply(self.action, state.state)
55
+ return state
56
+
57
+ def copy(self):
58
+ return Action(self.state.copy(), self.simulation)
59
+
60
+
61
+ class ActionType:
62
+ def __init__(self, action_type, action_id, simulation):
63
+ self.simulation = simulation
64
+ self.action_type = action_type
65
+ self.action_id = action_id
66
+
67
+ def get_arg_min_maxs(self):
68
+ return [(0, 3), (0, 3)]
69
+
70
+ def materialize(self, *args):
71
+ empty_action = Action(self.simulation.any_action(), self.simulation)
72
+ current_action = self.action_type()
73
+ for ((name, type), value) in zip(getattr(current_action, "_fields_"), args):
74
+ setattr(current_action, name, value)
75
+
76
+ self.simulation.module.functions.assign(empty_action.action, current_action)
77
+ return empty_action
78
+
79
+
80
+ class State:
81
+ def __init__(self, simulation, state):
82
+ self.simulation = simulation
83
+ self.state = state
84
+
85
+ def copy(self):
86
+ return State(self.simulation, self.state.copy())
87
+
88
+ def is_done(self) -> bool:
89
+ return self.state.resume_index == -1
90
+
91
+ def __str__(self) -> str:
92
+ return self.simulation.to_python_string(
93
+ self.simulation.module.functions.to_string(self.state)
94
+ )
95
+
96
+ def from_string(self, string: str) -> bool:
97
+ rl_string = self.simulation.to_rl_string(str)
98
+ return self.simulation.module.functions.from_string(self.state, rl_string)
99
+
100
+ @property
101
+ def actions(self) -> [Action]:
102
+ return self.simulation.get_actions()
103
+
104
+ def as_byte_vector(self):
105
+ result = self.simulation.module.functions.as_byte_vector(self.state)
106
+ real_content = []
107
+ for i in range(getattr(result, "__size")):
108
+ real_content.append(getattr(result, "__data")[i] + 128)
109
+ return bytes(real_content)
110
+
111
+ def from_byte_vector(self, byte_vector):
112
+ vector = self.simulation.module.VectorTint8_tT()
113
+ for byte in byte_vector:
114
+ self.simulation.module.functions.append(vector, byte - 128)
115
+ self.simulation.module.functions.from_byte_vector(self.state, vector)
116
+
117
+ def write_binary(self, path: str):
118
+ with open(path, mode="wb") as file:
119
+ file.write(self.as_byte_vector())
120
+ file.flush()
121
+
122
+ def load_binary(self, path: str):
123
+ with open(path, mode="rb") as file:
124
+ bytes = file.read()
125
+ self.from_byte_vector(bytes)
126
+
127
+ def load(self, path: str) -> bool:
128
+ with open(path, mode="r") as file:
129
+ bytes = file.read()
130
+ return self.from_string(bytes)
131
+
132
+ def score(self):
133
+ return self.state.score
134
+
135
+
136
+ class Simulation:
137
+ def __init__(self, wrapper: str):
138
+ self.wrapper_path = wrapper
139
+ self.module = import_file("sim", wrapper)
140
+
141
+ if "play" in self.module.actionToAnyFunctionType:
142
+ self.any_action = self.module.actionToAnyFunctionType["play"]
143
+ any_action_union = getattr(self.any_action(), "content")
144
+ alternatives = getattr(any_action_union, "_fields_")
145
+
146
+ self.actions = [
147
+ ActionType(action_type, i, self)
148
+ for (i, (_, action_type)) in enumerate(alternatives)
149
+ ]
150
+
151
+ def get_actions(self) -> [Action]:
152
+ return self.actions
153
+
154
+ def start(self, name: str, *args):
155
+ return State(self, getattr(self.module.functions, name)(*args))
156
+
157
+ def parse_action(self, action: str) -> Action:
158
+ any_action = self.module.actionToAnyFunctionType["play"]()
159
+ rl_string = self.to_rl_string(action)
160
+ if self.module.functions.from_string(any_action, rl_string):
161
+ return Action(any_action, self)
162
+ else:
163
+ return None
164
+
165
+ def parse_actions_from_binary_buffer(self, actions: bytes) -> [list]:
166
+ vector = self._bytes_to_byte_vector(actions)
167
+ any_action = self.module.actionToAnyFunctionType["play"]()
168
+ vec = self.module.functions.parse_actions(any_action, vector)
169
+ to_return = []
170
+ for i in range(getattr(vec, "__size")):
171
+ to_return.append(getattr(vec, "__data")[i])
172
+ return to_return
173
+
174
+ def _bytes_to_byte_vector(self, byte_vector: bytes):
175
+ vector = self.module.VectorTint8_tT()
176
+ for byte in byte_vector:
177
+ self.module.functions.append(vector, byte)
178
+ return vector
179
+
180
+ @property
181
+ def action_names(self) -> [str]:
182
+ return [name for name in self.module.actions.keys()]
183
+
184
+ def dump(self):
185
+ for name in self.action_names:
186
+ print(name)
187
+
188
+ def to_rl_string(self, string):
189
+ return self.module.rl_s__strlit_r_String(string)
190
+
191
+ def to_python_string(self, string):
192
+ first_character = getattr(getattr(string, "__data"), "__data")
193
+ return self.module.cast(first_character, self.module.c_char_p).value.decode(
194
+ "utf-8"
195
+ )
196
+
197
+ def action_from_string(self, string: str) -> Action:
198
+ rl_string = self.to_rl_string(str)
199
+ action = self.any_action()
200
+ if not self.module.functions.from_string(action, rl_string):
201
+ return None
202
+ return Action(action, self)
203
+
204
+ def action_from_byte_vector(self, byte_vector) -> Action:
205
+ action = self.any_action()
206
+ vector = self.module.VectorTint8_tT()
207
+ for byte in byte_vector:
208
+ self.module.functions.append(vector, byte - 128)
209
+ if not self.module.functions.parse_action_optimized(action, vector, 0):
210
+ return None
211
+ return Action(action, self)
212
+
213
+
214
+ def compile(source, rlc_compiler="rlc", rlc_includes=[], rlc_runtime_lib=""):
215
+ include_args = []
216
+ for arg in rlc_includes:
217
+ include_args.append("-i")
218
+ include_args.append(arg)
219
+ with TemporaryDirectory() as tmp_dir:
220
+ assert (
221
+ run(
222
+ [
223
+ rlc_compiler,
224
+ source,
225
+ "--python",
226
+ "-o",
227
+ "{}/wrapper.py".format(tmp_dir),
228
+ ]
229
+ + include_args
230
+ ).returncode
231
+ == 0
232
+ )
233
+ args = [rlc_compiler, source, "--shared", "-o", "{}/lib.so".format(tmp_dir)]
234
+ if rlc_runtime_lib != "":
235
+ args = args + ["--runtime-lib", rlc_runtime_lib]
236
+ assert run(args + include_args).returncode == 0
237
+ return Simulation(tmp_dir + "/wrapper.py")
ml/__init__.py ADDED
@@ -0,0 +1,12 @@
1
+ #
2
+ # This file is part of the RLC project.
3
+ #
4
+ # RLC is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation.
5
+ #
6
+ # RLC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
7
+ #
8
+ # You should have received a copy of the GNU General Public License along with RLC. If not, see <https://www.gnu.org/licenses/>.
9
+ #
10
+ from .training_data import TrainingData
11
+ from .transformer import RLCTransformer, run_once, next_probabilities
12
+ from .action_oracle import ActionOracle
ml/action_oracle.py ADDED
@@ -0,0 +1,61 @@
1
+ #
2
+ # Copyright 2024 Massimo Fioravanti
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ from loader import State
17
+ import torch
18
+ from ml import RLCTransformer, run_once
19
+
20
+
21
+ class ActionOracle:
22
+ def __init__(self, transformer: RLCTransformer, action_table):
23
+ self.transformer = transformer
24
+ self.action_table = action_table
25
+
26
+ def __call__(self, states: State):
27
+ non_null_states = [state for state in states if state is not None]
28
+ tensor = (
29
+ torch.IntTensor(
30
+ [[int(x) for x in state.as_byte_vector()] for state in non_null_states]
31
+ )
32
+ .t()
33
+ .to(self.transformer.device)
34
+ )
35
+ result_bytes = run_once(
36
+ self.transformer.model,
37
+ tensor,
38
+ True,
39
+ 3,
40
+ self.transformer.device,
41
+ self.transformer.ntokens,
42
+ )
43
+
44
+ result = []
45
+ index = 0
46
+ for state in states:
47
+ if state is None:
48
+ result.append(None)
49
+ else:
50
+ num = result_bytes[0, index]
51
+ num = num % len(self.action_table)
52
+ if num >= len(self.action_table) or num < 0:
53
+ result.append(None)
54
+ else:
55
+ action = self.action_table[num]
56
+ if not action.can_run(state):
57
+ result.append(None)
58
+ else:
59
+ result.append(action)
60
+ index = index + 1
61
+ return result
ml/raylib/__init__.py ADDED
File without changes
@@ -0,0 +1,87 @@
1
+ from ray.rllib.core.rl_module.rl_module import RLModule, RLModuleConfig
2
+ from ray.rllib.policy.sample_batch import SampleBatch
3
+ from ray.rllib.algorithms.ppo.torch.ppo_torch_rl_module import PPOTorchRLModule
4
+
5
+ class ActionMaskRLMBase(RLModule):
6
+ def __init__(self, config: RLModuleConfig):
7
+ if not isinstance(config.observation_space, gym.spaces.Dict):
8
+ super().__init__(config)
9
+ return
10
+ # We need to adjust the observation space for this RL Module so that, when
11
+ # building the default models, the RLModule does not "see" the action mask but
12
+ # only the original observation space without the action mask. This tricks it
13
+ # into building models that are compatible with the original observation space.
14
+ config.observation_space = config.observation_space["observations"]
15
+
16
+ # The PPORLModule, in its constructor, will build models for the modified
17
+ # observation space.
18
+ super().__init__(config)
19
+
20
+ def _check_batch(batch):
21
+ """Check whether the batch contains the required keys."""
22
+ if "action_mask" not in batch[SampleBatch.OBS]:
23
+ raise ValueError(
24
+ "Action mask not found in observation. This model requires "
25
+ "the environment to provide observations that include an "
26
+ "action mask (i.e. an observation space of the Dict space "
27
+ "type that looks as follows: \n"
28
+ "{'action_mask': Box(0.0, 1.0, shape=(self.action_space.n,)),"
29
+ "'observations': <observation_space>}"
30
+ )
31
+ if "observations" not in batch[SampleBatch.OBS]:
32
+ raise ValueError(
33
+ "Observations not found in observation.This model requires "
34
+ "the environment to provide observations that include a "
35
+ " (i.e. an observation space of the Dict space "
36
+ "type that looks as follows: \n"
37
+ "{'action_mask': Box(0.0, 1.0, shape=(self.action_space.n,)),"
38
+ "'observations': <observation_space>}"
39
+ )
40
+
41
+
42
+ class TorchActionMaskRLM(ActionMaskRLMBase, PPOTorchRLModule):
43
+ def _forward_inference(self, batch, **kwargs):
44
+ return mask_forward_fn_torch(super()._forward_inference, batch, **kwargs)
45
+
46
+ def _forward_train(self, batch, *args, **kwargs):
47
+ return mask_forward_fn_torch(super()._forward_train, batch, **kwargs)
48
+
49
+ def _forward_exploration(self, batch, *args, **kwargs):
50
+ return mask_forward_fn_torch(super()._forward_exploration, batch, **kwargs)
51
+
52
+ def _compute_values(self, batch, device=None):
53
+ _check_batch(batch)
54
+
55
+ # Extract the available actions tensor from the observation.
56
+ action_mask = batch[SampleBatch.OBS]["action_mask"]
57
+
58
+ # Modify the incoming batch so that the default models can compute logits and
59
+ # values as usual.
60
+ batch[SampleBatch.OBS] = batch[SampleBatch.OBS]["observations"]
61
+ return super()._compute_values(batch, device)
62
+
63
+
64
+ def mask_forward_fn_torch(forward_fn, batch, **kwargs):
65
+ _check_batch(batch)
66
+
67
+ # Extract the available actions tensor from the observation.
68
+ action_mask = batch[SampleBatch.OBS]["action_mask"]
69
+
70
+ # Modify the incoming batch so that the default models can compute logits and
71
+ # values as usual.
72
+ batch[SampleBatch.OBS] = batch[SampleBatch.OBS]["observations"]
73
+
74
+ outputs = forward_fn(batch, **kwargs)
75
+
76
+ # Mask logits
77
+ logits = outputs[SampleBatch.ACTION_DIST_INPUTS]
78
+ # Convert action_mask into a [0.0 || -inf]-type mask.
79
+ inf_mask = torch.clamp(torch.log(action_mask), min=FLOAT_MIN)
80
+ masked_logits = logits + inf_mask
81
+
82
+ # Replace original values with masked values.
83
+ outputs[SampleBatch.ACTION_DIST_INPUTS] = masked_logits
84
+
85
+ return outputs
86
+
87
+
@@ -0,0 +1,159 @@
1
+ from ray.rllib.env.multi_agent_env_runner import MultiAgentEnvRunner
2
+ from ray.rllib.core.rl_module.marl_module import (
3
+ MultiAgentRLModuleSpec,
4
+ MultiAgentRLModule,
5
+ MultiAgentRLModuleConfig,
6
+ )
7
+
8
+ class Specs:
9
+ def __init__(self, max_steps=100):
10
+ self.max_episode_steps = max_steps
11
+ self.id = random.randint(0, 10)
12
+
13
+ def get_env(wrapper):
14
+ class RLCEnvironment(MultiAgentEnv):
15
+
16
+ def __init__(self, render_mode=None, size=5):
17
+ action = wrapper.AnyGameAction()
18
+ self._actions = wrapper.functions.enumerate(action)
19
+ self.state_size = (
20
+ wrapper.functions.observation_tensor_size(wrapper.Game()).value * 2
21
+ )
22
+ self.actions = []
23
+ for i in range(wrapper.functions.size(self._actions).value):
24
+ self.actions.append(wrapper.functions.get(self._actions, i).contents)
25
+
26
+ self.num_actions = len(self.actions)
27
+ self.unwrapper_space = spaces.Dict(
28
+ {
29
+ "observations": spaces.Box(0, 1, shape=(self.state_size,), dtype=int),
30
+ "action_mask": spaces.Box(0, 1, shape=(self.num_actions,), dtype=int),
31
+ }
32
+ )
33
+ self.observation_space = spaces.Dict(
34
+ {i: self.unwrapper_space for i in range(2)}
35
+ )
36
+ self.action_space = spaces.Dict(
37
+ {i: spaces.Discrete(self.num_actions) for i in range(2)}
38
+ )
39
+
40
+ self.spec = Specs(10000)
41
+
42
+ self.state = wrapper.functions.play()
43
+ self.current_score = [
44
+ wrapper.functions.score(self.state, 0),
45
+ wrapper.functions.score(self.state, 1),
46
+ ]
47
+ self.last_score = self.current_score
48
+ self._agent_ids = [0, 1]
49
+ self.num_agents = 2
50
+ self._skip_env_checking = True
51
+ super().__init__()
52
+ self._obs_space_in_preferred_format = True
53
+ self._action_space_in_preferred_format = True
54
+
55
+ @property
56
+ def legal_actions(self):
57
+ # Convert NumPy arrays to nested tuples to make them hashable.
58
+ x = []
59
+ for i, action in enumerate(self.actions):
60
+ if wrapper.functions.can_apply_impl(action, self.state):
61
+ x.append(1)
62
+ else:
63
+ x.append(0)
64
+ return np.array(x, dtype=np.int8)
65
+
66
+ def legal_actions_list(self):
67
+ # Convert NumPy arrays to nested tuples to make them hashable.
68
+ x = []
69
+ for action in self.actions:
70
+ if wrapper.functions.can_apply_impl(action, self.state):
71
+ x.append(action)
72
+ return x
73
+
74
+ def _get_done_winner(self):
75
+ # if self.state.resume_index == -1:
76
+ # if wrapper.functions.three_in_a_line_player(self.state.board, 1):
77
+ # return (True, 0.0)
78
+ # if wrapper.functions.three_in_a_line_player(self.state.board, 2):
79
+ # return (True, 0.0)
80
+ # return (True, 1.0)
81
+ is_done = {i: self.state.resume_index == -1 for i in range(2)}
82
+ is_done["__all__"] = self.state.resume_index == -1
83
+ scores = {
84
+ i: (self.current_score[i] - self.last_score[i]) for i in range(2)
85
+ }
86
+
87
+ return is_done, scores # return (False, 0.0)
88
+
89
+ def _get_info(self):
90
+ done, reward = self._get_done_winner()
91
+ return {"reward": reward}
92
+
93
+ def reset(self, seed=None, options=None):
94
+ # print("RESET")
95
+ self.state = wrapper.functions.play()
96
+ observation = self._current_state()
97
+ info = self._get_info()
98
+ self.current_score = [
99
+ wrapper.functions.score(self.state, 0),
100
+ wrapper.functions.score(self.state, 1),
101
+ ]
102
+ self.last_score = self.current_score
103
+
104
+ return observation, info
105
+
106
+ def step(self, action):
107
+ to_apply = action[self.current_player()]
108
+ # wrapper.functions.print(self.actions[to_apply])
109
+ if not wrapper.functions.can_apply_impl(self.actions[to_apply], self.state):
110
+ wrapper.functions.apply(random.choice(self.legal_actions_list()), self.state)
111
+ else:
112
+ wrapper.functions.apply(self.actions[to_apply], self.state)
113
+
114
+ while self.current_player() == -1: # random player
115
+ action = random.choice(self.legal_actions_list())
116
+ # wrapper.functions.print(action)
117
+ assert wrapper.functions.can_apply_impl(action, self.state)
118
+ wrapper.functions.apply(action, self.state)
119
+
120
+ self.last_score = self.current_score
121
+ self.current_score = [
122
+ wrapper.functions.score(self.state, 0),
123
+ wrapper.functions.score(self.state, 1),
124
+ ]
125
+
126
+ done, reward = self._get_done_winner()
127
+ observation = self._current_state()
128
+ info = self._get_info()
129
+
130
+ truncated = {i: False for i in range(2)}
131
+ truncated["__all__"] = False
132
+ info["current_player"] = self.current_player
133
+ return observation, reward, done, truncated, info
134
+
135
+ def current_player(self):
136
+ return wrapper.functions.get_current_player(self.state).value
137
+
138
+ def _current_state(self):
139
+
140
+ serialized = wrapper.VectorTdoubleT()
141
+ wrapper.functions.resize(serialized, self.state_size)
142
+ # seriazed2 = wrapper.functions.as_byte_vector(self.state)
143
+ wrapper.functions.to_observation_tensor(self.state, serialized)
144
+
145
+ vec = np.rint(
146
+ np.ctypeslib.as_array(
147
+ wrapper.functions.get(serialized, 0), shape=(self.state_size,)
148
+ )
149
+ ).astype(int)
150
+ return {
151
+ i: {
152
+ "observations": vec,
153
+ "action_mask": self.legal_actions,
154
+ }
155
+ for i in range(2)
156
+ }
157
+
158
+ return RLCEnvironment
159
+