docs for maze-dataset v1.3.2
View Source on GitHub

maze_dataset

Maze Dataset Logo

maze-dataset

PyPI   Docs   Examples   arXiv

Diagram

PyPI   Python Version   Checks   Coverage   code size, bytes   GitHub commit activity   GitHub closed issues   GitHub closed pull requests   PyPI - Downloads

maze-dataset

This package provides utilities for generation, filtering, solving, visualizing, and processing of mazes for training or evaluating ML systems. Primarily built for the maze-transformer interpretability project. You can find our paper on it here: http://arxiv.org/abs/2309.10498

This package includes a variety of maze generation algorithms, including randomized depth first search, Wilson's algorithm for uniform spanning trees, and percolation. Datasets can be filtered to select mazes of a certain length or complexity, remove duplicates, and satisfy custom properties. A variety of output formats for visualization and training ML models are provided.

Maze generated via percolation Maze generated via constrained randomized depth first search Maze with random heatmap MazePlot with solution

You can view and search through a wide variety of example mazes here: understanding-search.github.io/maze-dataset/examples/maze_examples

Citing

If you use this code in your research, please cite our paper:

@misc{maze-dataset,
    title={A Configurable Library for Generating and Manipulating Maze Datasets}, 
    author={Michael Igorevich Ivanitskiy and Rusheb Shah and Alex F. Spies and Tilman Räuker and Dan Valentine and Can Rager and Lucia Quirke and Chris Mathwin and Guillaume Corlouer and Cecilia Diniz Behn and Samy Wu Fung},
    year={2023},
    eprint={2309.10498},
    archivePrefix={arXiv},
    primaryClass={cs.LG},
    url={http://arxiv.org/abs/2309.10498}
}

Installation

This package is available on PyPI, and can be installed via

pip install maze-dataset

Docs

The full hosted documentation is available at https://understanding-search.github.io/maze-dataset/.

Additionally, our notebooks serve as a good starting point for understanding the package.

Usage

Creating a dataset

To create a MazeDataset, which inherits from torch.utils.data.Dataset, you first create a MazeDatasetConfig:

from maze_dataset import MazeDataset, MazeDatasetConfig
from maze_dataset.generation import LatticeMazeGenerators
cfg: MazeDatasetConfig = MazeDatasetConfig(
    name="test", # name is only for you to keep track of things
    grid_n=5, # number of rows/columns in the lattice
    n_mazes=4, # number of mazes to generate
    maze_ctor=LatticeMazeGenerators.gen_dfs, # algorithm to generate the maze
    maze_ctor_kwargs=dict(do_forks=False), # additional parameters to pass to the maze generation algorithm
)

and then pass this config to the MazeDataset.from_config method:

dataset: MazeDataset = MazeDataset.from_config(cfg)

This method can search for whether a dataset with matching config hash already exists on your filesystem in the expected location, and load it if so. It can also generate a dataset on the fly if needed.

Conversions to useful formats

The elements of the dataset are SolvedMaze objects:

>>> m = dataset[0]
>>> type(m)
SolvedMaze

Which can be converted to a variety of formats:

# visual representation as ascii art
m.as_ascii() 
# RGB image, optionally without solution or endpoints, suitable for CNNs
m.as_pixels() 
# text format for autoreregressive transformers
from maze_dataset.tokenization import MazeTokenizerModular, TokenizationMode
m.as_tokens(maze_tokenizer=MazeTokenizerModular(
    tokenization_mode=TokenizationMode.AOTP_UT_rasterized, max_grid_size=100,
))
# advanced visualization with many features
from maze_dataset.plotting import MazePlot
MazePlot(maze).plot()

textual and visual output formats

Development

we use this makefile template with slight modifications for our development workflow.

  • clone with git clone https://github.com/understanding-search/maze-dataset
  • make dep to install all dependencies
  • make help will print all available commands
  • make test will run basic tests to ensure the package is working
  • make format will run ruff to format and check the code

 1""".. include:: ../README.md"""
 2
 3from maze_dataset.constants import (
 4	SPECIAL_TOKENS,
 5	VOCAB,
 6	VOCAB_LIST,
 7	VOCAB_TOKEN_TO_INDEX,
 8	Connection,
 9	ConnectionArray,
10	ConnectionList,
11	Coord,
12	CoordArray,
13	CoordList,
14	CoordTup,
15)
16from maze_dataset.dataset.collected_dataset import (
17	MazeDatasetCollection,
18	MazeDatasetCollectionConfig,
19)
20from maze_dataset.dataset.filters import register_maze_filter
21from maze_dataset.dataset.maze_dataset import (
22	MazeDataset,
23	MazeDatasetConfig,
24)
25from maze_dataset.dataset.maze_dataset_config import set_serialize_minimal_threshold
26from maze_dataset.generation.generators import LatticeMazeGenerators
27from maze_dataset.maze.lattice_maze import LatticeMaze, SolvedMaze, TargetedLatticeMaze
28
29__all__ = [
30	# submodules (with sub-submodules)
31	"benchmark",
32	"dataset",
33	"generation",
34	"maze",
35	"plotting",
36	"tokenization",
37	# submodules
38	"constants",
39	"testing_utils",
40	"token_utils",
41	"utils",
42	# main
43	"SolvedMaze",
44	"MazeDatasetConfig",
45	"MazeDataset",
46	# dataset classes
47	"MazeDatasetCollection",
48	"MazeDatasetCollectionConfig",
49	# maze classes
50	"TargetedLatticeMaze",
51	"LatticeMaze",
52	# other
53	"set_serialize_minimal_threshold",
54	"register_maze_filter",
55	"LatticeMazeGenerators",
56	# types
57	"Coord",
58	"CoordTup",
59	"CoordList",
60	"CoordArray",
61	"Connection",
62	"ConnectionList",
63	"ConnectionArray",
64	# constants
65	"SPECIAL_TOKENS",
66	"VOCAB",
67	"VOCAB_LIST",
68	"VOCAB_TOKEN_TO_INDEX",
69]

@serializable_dataclass(frozen=True, kw_only=True)
class SolvedMaze(maze_dataset.TargetedLatticeMaze):
1265@serializable_dataclass(frozen=True, kw_only=True)
1266class SolvedMaze(TargetedLatticeMaze):  # type: ignore[misc]
1267	"""Stores a maze and a solution"""
1268
1269	solution: CoordArray = serializable_field(  # type: ignore[misc]
1270		assert_type=False,
1271	)
1272
1273	def __init__(
1274		self,
1275		connection_list: ConnectionList,
1276		solution: CoordArray,
1277		generation_meta: dict | None = None,
1278		start_pos: Coord | None = None,
1279		end_pos: Coord | None = None,
1280		allow_invalid: bool = False,
1281	) -> None:
1282		"""Create a SolvedMaze from a connection list and a solution
1283
1284		> DOCS: better documentation for this init method
1285		"""
1286		# figure out the solution
1287		solution_valid: bool = False
1288		if solution is not None:
1289			solution = np.array(solution)
1290			# note that a path length of 1 here is valid, since the start and end pos could be the same
1291			if (solution.shape[0] > 0) and (solution.shape[1] == 2):  # noqa: PLR2004
1292				solution_valid = True
1293
1294		if not solution_valid and not allow_invalid:
1295			err_msg: str = f"invalid solution: {solution.shape = } {solution = } {solution_valid = } {allow_invalid = }"
1296			raise ValueError(
1297				err_msg,
1298				f"{connection_list = }",
1299			)
1300
1301		# init the TargetedLatticeMaze
1302		super().__init__(
1303			connection_list=connection_list,
1304			generation_meta=generation_meta,
1305			# TODO: the argument type is stricter than the expected type but it still fails?
1306			# error: Argument "start_pos" to "__init__" of "TargetedLatticeMaze" has incompatible type
1307			# "ndarray[tuple[int, ...], dtype[Any]] | None"; expected "ndarray[Any, Any]"  [arg-type]
1308			start_pos=np.array(solution[0]) if solution_valid else None,  # type: ignore[arg-type]
1309			end_pos=np.array(solution[-1]) if solution_valid else None,  # type: ignore[arg-type]
1310		)
1311
1312		self.__dict__["solution"] = solution
1313
1314		# adjust the endpoints
1315		if not allow_invalid:
1316			if start_pos is not None:
1317				assert np.array_equal(np.array(start_pos), self.start_pos), (
1318					f"when trying to create a SolvedMaze, the given start_pos does not match the one in the solution: given={start_pos}, solution={self.start_pos}"
1319				)
1320			if end_pos is not None:
1321				assert np.array_equal(np.array(end_pos), self.end_pos), (
1322					f"when trying to create a SolvedMaze, the given end_pos does not match the one in the solution: given={end_pos}, solution={self.end_pos}"
1323				)
1324			# TODO: assert the path does not backtrack, walk through walls, etc?
1325
1326	def __eq__(self, other: object) -> bool:
1327		"check equality, calls parent class equality check"
1328		return super().__eq__(other)
1329
1330	def __hash__(self) -> int:
1331		"hash the `SolvedMaze` by hashing a tuple of the connection list and solution arrays as bytes"
1332		return hash((self.connection_list.tobytes(), self.solution.tobytes()))
1333
1334	def _get_solution_tokens(self) -> list[str | CoordTup]:
1335		return [
1336			SPECIAL_TOKENS.PATH_START,
1337			*[tuple(c) for c in self.solution],
1338			SPECIAL_TOKENS.PATH_END,
1339		]
1340
1341	def get_solution_tokens(self) -> list[str | CoordTup]:
1342		"(deprecated!) return the solution as a list of tokens"
1343		warnings.warn(
1344			"`LatticeMaze.get_solution_tokens` is deprecated.",
1345			TokenizerDeprecationWarning,
1346		)
1347		return self._get_solution_tokens()
1348
1349	# for backwards compatibility
1350	@property
1351	def maze(self) -> LatticeMaze:
1352		"(deprecated!) return the maze without the solution"
1353		warnings.warn(
1354			"`maze` is deprecated, SolvedMaze now inherits from LatticeMaze.",
1355			DeprecationWarning,
1356		)
1357		return LatticeMaze(connection_list=self.connection_list)
1358
1359	# type ignore here since we're overriding a method with a different signature
1360	@classmethod
1361	def from_lattice_maze(  # type: ignore[override]
1362		cls,
1363		lattice_maze: LatticeMaze,
1364		solution: list[CoordTup] | CoordArray,
1365	) -> "SolvedMaze":
1366		"get a `SolvedMaze` from a `LatticeMaze` by specifying a solution"
1367		return cls(
1368			connection_list=lattice_maze.connection_list,
1369			solution=np.array(solution),
1370			generation_meta=lattice_maze.generation_meta,
1371		)
1372
1373	@classmethod
1374	def from_targeted_lattice_maze(
1375		cls,
1376		targeted_lattice_maze: TargetedLatticeMaze,
1377		solution: list[CoordTup] | CoordArray | None = None,
1378	) -> "SolvedMaze":
1379		"""solves the given targeted lattice maze and returns a SolvedMaze"""
1380		if solution is None:
1381			solution = targeted_lattice_maze.find_shortest_path(
1382				targeted_lattice_maze.start_pos,
1383				targeted_lattice_maze.end_pos,
1384			)
1385		return cls(
1386			connection_list=targeted_lattice_maze.connection_list,
1387			solution=np.array(solution),
1388			generation_meta=targeted_lattice_maze.generation_meta,
1389		)
1390
1391	def get_solution_forking_points(
1392		self,
1393		always_include_endpoints: bool = False,
1394	) -> tuple[list[int], CoordArray]:
1395		"""coordinates and their indicies from the solution where a fork is present
1396
1397		- if the start point is not a dead end, this counts as a fork
1398		- if the end point is not a dead end, this counts as a fork
1399		"""
1400		output_idxs: list[int] = list()
1401		output_coords: list[CoordTup] = list()
1402
1403		for idx, coord in enumerate(self.solution):
1404			# more than one choice for first coord, or more than 2 for any other
1405			# since the previous coord doesn't count as a choice
1406			is_endpoint: bool = idx == 0 or idx == self.solution.shape[0] - 1
1407			theshold: int = 1 if is_endpoint else 2
1408			if self.get_coord_neighbors(coord).shape[0] > theshold or (
1409				is_endpoint and always_include_endpoints
1410			):
1411				output_idxs.append(idx)
1412				output_coords.append(coord)
1413
1414		return output_idxs, np.array(output_coords)
1415
1416	def get_solution_path_following_points(self) -> tuple[list[int], CoordArray]:
1417		"""coordinates from the solution where there is only a single (non-backtracking) point to move to
1418
1419		returns the complement of `get_solution_forking_points` from the path
1420		"""
1421		forks_idxs, _ = self.get_solution_forking_points()
1422		# HACK: idk why type ignore here
1423		return (  # type: ignore[return-value]
1424			np.delete(np.arange(self.solution.shape[0]), forks_idxs, axis=0),
1425			np.delete(self.solution, forks_idxs, axis=0),
1426		)

Stores a maze and a solution

SolvedMaze( connection_list: jaxtyping.Bool[ndarray, 'lattice_dim=2 row col'], solution: jaxtyping.Int8[ndarray, 'coord row_col=2'], generation_meta: dict | None = None, start_pos: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None, end_pos: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None, allow_invalid: bool = False)
1273	def __init__(
1274		self,
1275		connection_list: ConnectionList,
1276		solution: CoordArray,
1277		generation_meta: dict | None = None,
1278		start_pos: Coord | None = None,
1279		end_pos: Coord | None = None,
1280		allow_invalid: bool = False,
1281	) -> None:
1282		"""Create a SolvedMaze from a connection list and a solution
1283
1284		> DOCS: better documentation for this init method
1285		"""
1286		# figure out the solution
1287		solution_valid: bool = False
1288		if solution is not None:
1289			solution = np.array(solution)
1290			# note that a path length of 1 here is valid, since the start and end pos could be the same
1291			if (solution.shape[0] > 0) and (solution.shape[1] == 2):  # noqa: PLR2004
1292				solution_valid = True
1293
1294		if not solution_valid and not allow_invalid:
1295			err_msg: str = f"invalid solution: {solution.shape = } {solution = } {solution_valid = } {allow_invalid = }"
1296			raise ValueError(
1297				err_msg,
1298				f"{connection_list = }",
1299			)
1300
1301		# init the TargetedLatticeMaze
1302		super().__init__(
1303			connection_list=connection_list,
1304			generation_meta=generation_meta,
1305			# TODO: the argument type is stricter than the expected type but it still fails?
1306			# error: Argument "start_pos" to "__init__" of "TargetedLatticeMaze" has incompatible type
1307			# "ndarray[tuple[int, ...], dtype[Any]] | None"; expected "ndarray[Any, Any]"  [arg-type]
1308			start_pos=np.array(solution[0]) if solution_valid else None,  # type: ignore[arg-type]
1309			end_pos=np.array(solution[-1]) if solution_valid else None,  # type: ignore[arg-type]
1310		)
1311
1312		self.__dict__["solution"] = solution
1313
1314		# adjust the endpoints
1315		if not allow_invalid:
1316			if start_pos is not None:
1317				assert np.array_equal(np.array(start_pos), self.start_pos), (
1318					f"when trying to create a SolvedMaze, the given start_pos does not match the one in the solution: given={start_pos}, solution={self.start_pos}"
1319				)
1320			if end_pos is not None:
1321				assert np.array_equal(np.array(end_pos), self.end_pos), (
1322					f"when trying to create a SolvedMaze, the given end_pos does not match the one in the solution: given={end_pos}, solution={self.end_pos}"
1323				)
1324			# TODO: assert the path does not backtrack, walk through walls, etc?

Create a SolvedMaze from a connection list and a solution

DOCS: better documentation for this init method

solution: jaxtyping.Int8[ndarray, 'coord row_col=2']
def get_solution_tokens(self) -> list[str | tuple[int, int]]:
1341	def get_solution_tokens(self) -> list[str | CoordTup]:
1342		"(deprecated!) return the solution as a list of tokens"
1343		warnings.warn(
1344			"`LatticeMaze.get_solution_tokens` is deprecated.",
1345			TokenizerDeprecationWarning,
1346		)
1347		return self._get_solution_tokens()

(deprecated!) return the solution as a list of tokens

maze: LatticeMaze
1350	@property
1351	def maze(self) -> LatticeMaze:
1352		"(deprecated!) return the maze without the solution"
1353		warnings.warn(
1354			"`maze` is deprecated, SolvedMaze now inherits from LatticeMaze.",
1355			DeprecationWarning,
1356		)
1357		return LatticeMaze(connection_list=self.connection_list)

(deprecated!) return the maze without the solution

@classmethod
def from_lattice_maze( cls, lattice_maze: LatticeMaze, solution: list[tuple[int, int]] | jaxtyping.Int8[ndarray, 'coord row_col=2']) -> SolvedMaze:
1360	@classmethod
1361	def from_lattice_maze(  # type: ignore[override]
1362		cls,
1363		lattice_maze: LatticeMaze,
1364		solution: list[CoordTup] | CoordArray,
1365	) -> "SolvedMaze":
1366		"get a `SolvedMaze` from a `LatticeMaze` by specifying a solution"
1367		return cls(
1368			connection_list=lattice_maze.connection_list,
1369			solution=np.array(solution),
1370			generation_meta=lattice_maze.generation_meta,
1371		)

get a SolvedMaze from a LatticeMaze by specifying a solution

@classmethod
def from_targeted_lattice_maze( cls, targeted_lattice_maze: TargetedLatticeMaze, solution: list[tuple[int, int]] | jaxtyping.Int8[ndarray, 'coord row_col=2'] | None = None) -> SolvedMaze:
1373	@classmethod
1374	def from_targeted_lattice_maze(
1375		cls,
1376		targeted_lattice_maze: TargetedLatticeMaze,
1377		solution: list[CoordTup] | CoordArray | None = None,
1378	) -> "SolvedMaze":
1379		"""solves the given targeted lattice maze and returns a SolvedMaze"""
1380		if solution is None:
1381			solution = targeted_lattice_maze.find_shortest_path(
1382				targeted_lattice_maze.start_pos,
1383				targeted_lattice_maze.end_pos,
1384			)
1385		return cls(
1386			connection_list=targeted_lattice_maze.connection_list,
1387			solution=np.array(solution),
1388			generation_meta=targeted_lattice_maze.generation_meta,
1389		)

solves the given targeted lattice maze and returns a SolvedMaze

def get_solution_forking_points( self, always_include_endpoints: bool = False) -> tuple[list[int], jaxtyping.Int8[ndarray, 'coord row_col=2']]:
1391	def get_solution_forking_points(
1392		self,
1393		always_include_endpoints: bool = False,
1394	) -> tuple[list[int], CoordArray]:
1395		"""coordinates and their indicies from the solution where a fork is present
1396
1397		- if the start point is not a dead end, this counts as a fork
1398		- if the end point is not a dead end, this counts as a fork
1399		"""
1400		output_idxs: list[int] = list()
1401		output_coords: list[CoordTup] = list()
1402
1403		for idx, coord in enumerate(self.solution):
1404			# more than one choice for first coord, or more than 2 for any other
1405			# since the previous coord doesn't count as a choice
1406			is_endpoint: bool = idx == 0 or idx == self.solution.shape[0] - 1
1407			theshold: int = 1 if is_endpoint else 2
1408			if self.get_coord_neighbors(coord).shape[0] > theshold or (
1409				is_endpoint and always_include_endpoints
1410			):
1411				output_idxs.append(idx)
1412				output_coords.append(coord)
1413
1414		return output_idxs, np.array(output_coords)

coordinates and their indicies from the solution where a fork is present

  • if the start point is not a dead end, this counts as a fork
  • if the end point is not a dead end, this counts as a fork
def get_solution_path_following_points(self) -> tuple[list[int], jaxtyping.Int8[ndarray, 'coord row_col=2']]:
1416	def get_solution_path_following_points(self) -> tuple[list[int], CoordArray]:
1417		"""coordinates from the solution where there is only a single (non-backtracking) point to move to
1418
1419		returns the complement of `get_solution_forking_points` from the path
1420		"""
1421		forks_idxs, _ = self.get_solution_forking_points()
1422		# HACK: idk why type ignore here
1423		return (  # type: ignore[return-value]
1424			np.delete(np.arange(self.solution.shape[0]), forks_idxs, axis=0),
1425			np.delete(self.solution, forks_idxs, axis=0),
1426		)

coordinates from the solution where there is only a single (non-backtracking) point to move to

returns the complement of get_solution_forking_points from the path

def serialize(self) -> dict[str, typing.Any]:
714        def serialize(self) -> dict[str, Any]:
715            result: dict[str, Any] = {
716                _FORMAT_KEY: f"{self.__class__.__name__}(SerializableDataclass)"
717            }
718            # for each field in the class
719            for field in dataclasses.fields(self):  # type: ignore[arg-type]
720                # need it to be our special SerializableField
721                if not isinstance(field, SerializableField):
722                    raise NotSerializableFieldException(
723                        f"Field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__} is not a `SerializableField`, "
724                        f"but a {type(field)} "
725                        "this state should be inaccessible, please report this bug!"
726                    )
727
728                # try to save it
729                if field.serialize:
730                    try:
731                        # get the val
732                        value = getattr(self, field.name)
733                        # if it is a serializable dataclass, serialize it
734                        if isinstance(value, SerializableDataclass):
735                            value = value.serialize()
736                        # if the value has a serialization function, use that
737                        if hasattr(value, "serialize") and callable(value.serialize):
738                            value = value.serialize()
739                        # if the field has a serialization function, use that
740                        # it would be nice to be able to override a class's `.serialize()`, but that could lead to some inconsistencies!
741                        elif field.serialization_fn:
742                            value = field.serialization_fn(value)
743
744                        # store the value in the result
745                        result[field.name] = value
746                    except Exception as e:
747                        raise FieldSerializationError(
748                            "\n".join(
749                                [
750                                    f"Error serializing field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__}",
751                                    f"{field = }",
752                                    f"{value = }",
753                                    f"{self = }",
754                                ]
755                            )
756                        ) from e
757
758            # store each property if we can get it
759            for prop in self._properties_to_serialize:
760                if hasattr(cls, prop):
761                    value = getattr(self, prop)
762                    result[prop] = value
763                else:
764                    raise AttributeError(
765                        f"Cannot serialize property '{prop}' on class {self.__class__.__module__}.{self.__class__.__name__}"
766                        + f"but it is in {self._properties_to_serialize = }"
767                        + f"\n{self = }"
768                    )
769
770            return result

returns the class as a dict, implemented by using @serializable_dataclass decorator

@classmethod
def load(cls, data: Union[dict[str, Any], ~T]) -> Type[~T]:
777        @classmethod  # type: ignore[misc]
778        def load(cls, data: dict[str, Any] | T) -> Type[T]:
779            # HACK: this is kind of ugly, but it fixes a lot of issues for when we do recursive loading with ZANJ
780            if isinstance(data, cls):
781                return data
782
783            assert isinstance(
784                data, typing.Mapping
785            ), f"When loading {cls.__name__ = } expected a Mapping, but got {type(data) = }:\n{data = }"
786
787            cls_type_hints: dict[str, Any] = get_cls_type_hints(cls)
788
789            # initialize dict for keeping what we will pass to the constructor
790            ctor_kwargs: dict[str, Any] = dict()
791
792            # iterate over the fields of the class
793            for field in dataclasses.fields(cls):
794                # check if the field is a SerializableField
795                assert isinstance(
796                    field, SerializableField
797                ), f"Field '{field.name}' on class {cls.__name__} is not a SerializableField, but a {type(field)}. this state should be inaccessible, please report this bug!\nhttps://github.com/mivanit/muutils/issues/new"
798
799                # check if the field is in the data and if it should be initialized
800                if (field.name in data) and field.init:
801                    # get the value, we will be processing it
802                    value: Any = data[field.name]
803
804                    # get the type hint for the field
805                    field_type_hint: Any = cls_type_hints.get(field.name, None)
806
807                    # we rely on the init of `SerializableField` to check that only one of `loading_fn` and `deserialize_fn` is set
808                    if field.deserialize_fn:
809                        # if it has a deserialization function, use that
810                        value = field.deserialize_fn(value)
811                    elif field.loading_fn:
812                        # if it has a loading function, use that
813                        value = field.loading_fn(data)
814                    elif (
815                        field_type_hint is not None
816                        and hasattr(field_type_hint, "load")
817                        and callable(field_type_hint.load)
818                    ):
819                        # if no loading function but has a type hint with a load method, use that
820                        if isinstance(value, dict):
821                            value = field_type_hint.load(value)
822                        else:
823                            raise FieldLoadingError(
824                                f"Cannot load value into {field_type_hint}, expected {type(value) = } to be a dict\n{value = }"
825                            )
826                    else:
827                        # assume no loading needs to happen, keep `value` as-is
828                        pass
829
830                    # store the value in the constructor kwargs
831                    ctor_kwargs[field.name] = value
832
833            # create a new instance of the class with the constructor kwargs
834            output: cls = cls(**ctor_kwargs)
835
836            # validate the types of the fields if needed
837            if on_typecheck_mismatch != ErrorMode.IGNORE:
838                fields_valid: dict[str, bool] = (
839                    SerializableDataclass__validate_fields_types__dict(
840                        output,
841                        on_typecheck_error=on_typecheck_error,
842                    )
843                )
844
845                # if there are any fields that are not valid, raise an error
846                if not all(fields_valid.values()):
847                    msg: str = (
848                        f"Type mismatch in fields of {cls.__name__}:\n"
849                        + "\n".join(
850                            [
851                                f"{k}:\texpected {cls_type_hints[k] = }, but got value {getattr(output, k) = }, {type(getattr(output, k)) = }"
852                                for k, v in fields_valid.items()
853                                if not v
854                            ]
855                        )
856                    )
857
858                    on_typecheck_mismatch.process(
859                        msg, except_cls=FieldTypeMismatchError
860                    )
861
862            # return the new instance
863            return output

takes in an appropriately structured dict and returns an instance of the class, implemented by using @serializable_dataclass decorator

def validate_fields_types( self: muutils.json_serialize.serializable_dataclass.SerializableDataclass, on_typecheck_error: muutils.errormode.ErrorMode = ErrorMode.Except) -> bool:
283def SerializableDataclass__validate_fields_types(
284    self: SerializableDataclass,
285    on_typecheck_error: ErrorMode = _DEFAULT_ON_TYPECHECK_ERROR,
286) -> bool:
287    """validate the types of all the fields on a `SerializableDataclass`. calls `SerializableDataclass__validate_field_type` for each field"""
288    return all(
289        SerializableDataclass__validate_fields_types__dict(
290            self, on_typecheck_error=on_typecheck_error
291        ).values()
292    )

validate the types of all the fields on a SerializableDataclass. calls SerializableDataclass__validate_field_type for each field

@serializable_dataclass(kw_only=True, methods_no_override=['serialize'])
class MazeDatasetConfig(maze_dataset.dataset.maze_dataset_config.MazeDatasetConfig_base):
257@serializable_dataclass(kw_only=True, methods_no_override=["serialize"])
258class MazeDatasetConfig(MazeDatasetConfig_base):  # type: ignore[misc]
259	"""config object which is passed to `MazeDataset.from_config` to generate or load a dataset
260
261	# Parameters:
262	- `name : str`
263		name of the dataset -- this can be anything, but should be filesystem safe since we use it in the `fname`
264	- `grid_n : int`
265		grid size of the maze (number of rows/columns)
266	- `n_mazes : int`
267		number of mazes to request. For some combinations of `endpoint_kwargs` and `maze_ctor`, not all mazes might successfully generate.
268		see `EndpointKwargsType` for more details.
269	- `maze_ctor : Callable`
270		maze generator function. This should be a function that takes a grid size and returns a maze.
271		This will usually be one of the functions in `LatticeMazeGenerators`.
272	- `maze_ctor_kwargs : dict`
273		keyword arguments to pass to the maze generator function. Specific to the `maze_ctor` you are using.
274	- `endpoint_kwargs : EndpointKwargsType`
275		keyword arguments passed to `LatticeMaze.generate_random_path()`. see `EndpointKwargsType` for more info.
276	- `applied_filters : list[dict]`
277		list of filters that have been applied to the dataset. We recommend applying filters to datasets directly,
278		but these are stored with the config in case you want to re-generate the dataset with the same filters.
279
280	"""
281
282	@property
283	def config_version(self) -> str:
284		"""return the version of the config. added in maze_dataset v1.3.0, previous versions had no dataset config"""
285		return "1.0"
286
287	@property
288	def versions(self) -> dict:
289		"""return the versions of the config and the maze_dataset"""
290		return dict(
291			config=self.config_version,
292			maze_dataset=importlib.metadata.version("maze_dataset"),
293		)
294
295	def serialize(self) -> dict:
296		"serialize the MazeDatasetConfig with all fields and fname"
297		return {
298			**self._serialize_base(
299				applied_filters__skip__collect_generation_meta=False
300			),
301			"fname": self.to_fname(),
302			"versions": self.versions,
303		}
304
305	def summary(self) -> dict:
306		"""return a summary of the config"""
307		# do we run this to make sure it doesn't error?
308		super_summary: dict = super().summary()
309		assert super_summary
310		self_ser: dict = self.serialize()
311		return dict(
312			name=self.name,
313			fname=self.to_fname(),
314			sdc_hash=self.stable_hash_cfg(),
315			seed=self.seed,
316			seq_len_min=self.seq_len_min,
317			seq_len_max=self.seq_len_max,
318			applied_filters=self.applied_filters,
319			grid_n=self_ser["grid_n"],
320			n_mazes=self_ser["n_mazes"],
321			maze_ctor_name=self_ser["maze_ctor"]["__name__"],
322			maze_ctor_kwargs=self_ser["maze_ctor_kwargs"],
323			endpoint_kwargs=self_ser["endpoint_kwargs"],
324		)
325
326	def _to_ps_array(self) -> _PercolationSuccessArray:
327		"""Convert this config to a [p, grid_n, deadends, endpoints_not_equal, generator_func] vector.
328
329		used in predicting the success rate
330		"""
331		try:
332			assert self.maze_ctor.__name__ in _GENERATORS_PERCOLATED, (
333				f"generator not supported, must be a percolation generator\n{self.maze_ctor.__name__ = }, {_GENERATORS_PERCOLATED = }"
334			)
335			assert "p" in self.maze_ctor_kwargs, (
336				f"maze_ctor_kwargs must have a 'p' (percolation value) key: {self.maze_ctor_kwargs = }"
337			)
338			assert not self.endpoint_kwargs.get("except_on_no_valid_endpoint", True), (
339				f"except_on_no_valid_endpoint must be False, or else if any maze fails to generate, the whole dataset will fail: {self.endpoint_kwargs = }"
340			)
341		except AssertionError as e:
342			err_msg: str = f"invalid config for percolation success prediction: {self.summary() = }"
343			raise NoPercolationInConfigError(
344				err_msg,
345			) from e
346
347		endpoints_unique_flag: int = int(
348			# we are pretty sure it will be an int or bool here
349			self.endpoint_kwargs.get("endpoints_not_equal", True),  # type: ignore[arg-type]
350		)
351
352		# adjustment for bknutson0
353		if not (
354			self.endpoint_kwargs.get("deadend_start", False)
355			and self.endpoint_kwargs.get("deadend_end", False)
356		):
357			# we didnt train on this, but if either endpoint is not required to be in a dead end
358			# then  requiring the endpoints to be unique does not really affect the success rate
359			# (except for very small percolation values, pure percolation generation)
360			endpoints_unique_flag = 0
361
362		return np.array(
363			[
364				float(self.maze_ctor_kwargs["p"]),
365				float(self.grid_n),
366				float(
367					int(
368						self.endpoint_kwargs.get("deadend_start", False)  # type: ignore[arg-type]
369						or self.endpoint_kwargs.get("deadend_end", False),
370					),
371				),
372				float(endpoints_unique_flag),
373				float(_GENERATORS_PERCOLATED.index(self.maze_ctor.__name__)),
374			],
375			dtype=np.float64,
376		)
377
378	@classmethod
379	def _from_ps_array(
380		cls,
381		arr: _PercolationSuccessArray,
382		name: str = "predict",
383		n_mazes: int = 100,
384		**kwargs,
385	) -> "MazeDatasetConfig":
386		"""Reconstruct a config from an array [p, grid_n, deadends, endpoints_not_equal, generator_func] and other config parameters.
387
388		# Returns:
389		- `MazeDatasetConfig`
390			Config corresponding to `arr`
391		"""
392		return cls(
393			name=name,
394			grid_n=int(arr[1]),
395			n_mazes=n_mazes,
396			maze_ctor=GENERATORS_MAP[_GENERATORS_PERCOLATED[int(arr[4])]],
397			maze_ctor_kwargs={"p": float(arr[0])},
398			endpoint_kwargs=dict(
399				deadend_start=bool(arr[2]),
400				deadend_end=bool(arr[2]),
401				endpoints_not_equal=bool(arr[3]),
402				except_on_no_valid_endpoint=False,
403			),
404			**kwargs,
405		)
406
407	def success_fraction_estimate(
408		self,
409		except_if_all_success_expected: bool = False,
410	) -> float:
411		"""Estimate the success fraction of this config.
412
413		only valid when the generator is a percolation generator,
414		and endpoints are enforced to be dead ends
415
416		more information on where this comes from can be found in
417		- `cfg_success_predict_fn()` from `maze_dataset.dataset.success_predict_math`
418		- `estimate_dataset_fractions.ipynb`
419		- `maze_dataset.benchmarks.sweep_fit`
420
421		# Parameters:
422		- `except_if_all_success_expected : bool`
423			if `True`, don't raise an error if the success fraction is below the threshold.
424			will always return `1.0` if the config is not expected to fail
425
426		# Returns:
427		- `float`
428			estimated success fraction
429
430		# Raises:
431		- `NoPercolationInConfigError` : if the config is not expected to fail, and `except_if_all_success_expected` is `False`
432		"""
433		try:
434			return cfg_success_predict_fn(self)
435
436		except NoPercolationInConfigError as e:
437			if except_if_all_success_expected:
438				raise e  # noqa: TRY201
439			return 1.0
440
441	def success_fraction_compensate(
442		self,
443		safety_margin: float = 1.2,
444		except_if_all_success_expected: bool = False,
445		epsilon: float = 1e-2,
446	) -> "MazeDatasetConfig":
447		"""return a new `MazeDatasetConfig` like this one with `n_mazes` adjusted to compensate for the success fraction
448
449		calls `MazeDatasetConfig.success_fraction_estimate()` to get the success fraction, and then
450		computes the new number of mazes as `n_mazes = n_mazes * safety_margin / success_fraction + 1`
451
452		more information on where this comes from can be found in
453		- `cfg_success_predict_fn()` from `maze_dataset.dataset.success_predict_math`
454		- `estimate_dataset_fractions.ipynb`
455		- `maze_dataset.benchmarks.sweep_fit`
456
457		# Parameters:
458		- `safety_margin : float`
459			safety margin to apply to the success fraction estimate
460			(defaults to `1.2`, or 20% more mazes than estimated)
461		- `except_if_all_success_expected : bool`
462			if `True`, don't raise an error if the success fraction is below the threshold.
463			this is passed to `MazeDatasetConfig.success_fraction_estimate`.
464			if your config isn't expected to fail, passing this might mean you generate more mazes than needed
465			since `safety_margin` is still applied.
466			(defaults to `False`)
467		- `epsilon : float`
468			raise `SuccessChanceTooSmallError` if the success fraction is below this threshold
469			(defaults to `1e-2`)
470
471		# Returns:
472		- `MazeDatasetConfig`
473			new config with adjusted `n_mazes`
474
475		# Raises:
476		- `SuccessChanceTooSmallError` : if the computed success fraction is below `epsilon`
477		"""
478		# compute and check the success fraction
479		success_fraction: float = self.success_fraction_estimate(
480			except_if_all_success_expected=except_if_all_success_expected,
481		)
482		if success_fraction < epsilon:
483			err_msg: str = (
484				f"{success_fraction = } is below the threshold of {epsilon = }"
485			)
486			raise SuccessChanceTooSmallError(
487				err_msg,
488			)
489
490		# compute the new number of mazes
491		n_mazes: int = self.n_mazes
492		new_n_mazes: int = int((n_mazes * safety_margin) / success_fraction) + 1
493
494		# put it in a new config and return
495		cfg_dict: dict = self.serialize()
496		cfg_dict["n_mazes"] = new_n_mazes
497		return MazeDatasetConfig.load(cfg_dict)

config object which is passed to MazeDataset.from_config to generate or load a dataset

Parameters:

  • name : str name of the dataset -- this can be anything, but should be filesystem safe since we use it in the fname
  • grid_n : int grid size of the maze (number of rows/columns)
  • n_mazes : int number of mazes to request. For some combinations of endpoint_kwargs and maze_ctor, not all mazes might successfully generate. see EndpointKwargsType for more details.
  • maze_ctor : Callable maze generator function. This should be a function that takes a grid size and returns a maze. This will usually be one of the functions in LatticeMazeGenerators.
  • maze_ctor_kwargs : dict keyword arguments to pass to the maze generator function. Specific to the maze_ctor you are using.
  • endpoint_kwargs : EndpointKwargsType keyword arguments passed to LatticeMaze.generate_random_path(). see EndpointKwargsType for more info.
  • applied_filters : list[dict] list of filters that have been applied to the dataset. We recommend applying filters to datasets directly, but these are stored with the config in case you want to re-generate the dataset with the same filters.
MazeDatasetConfig( *, name: str, seq_len_min: int = 1, seq_len_max: int = 512, seed: int | None = 42, applied_filters: list[dict[typing.Literal['name', 'args', 'kwargs'], str | list | tuple | dict]] = <factory>, grid_n: int, n_mazes: int, maze_ctor: Callable = <function LatticeMazeGenerators.gen_dfs>, maze_ctor_kwargs: dict = <factory>, endpoint_kwargs: dict[typing.Literal['allowed_start', 'allowed_end', 'deadend_start', 'deadend_end', 'endpoints_not_equal', 'except_on_no_valid_endpoint'], bool | None | list[tuple[int, int]]] = <factory>, _fname_loaded: str | None = None)
config_version: str
282	@property
283	def config_version(self) -> str:
284		"""return the version of the config. added in maze_dataset v1.3.0, previous versions had no dataset config"""
285		return "1.0"

return the version of the config. added in maze_dataset v1.3.0, previous versions had no dataset config

versions: dict
287	@property
288	def versions(self) -> dict:
289		"""return the versions of the config and the maze_dataset"""
290		return dict(
291			config=self.config_version,
292			maze_dataset=importlib.metadata.version("maze_dataset"),
293		)

return the versions of the config and the maze_dataset

def serialize(self) -> dict:
295	def serialize(self) -> dict:
296		"serialize the MazeDatasetConfig with all fields and fname"
297		return {
298			**self._serialize_base(
299				applied_filters__skip__collect_generation_meta=False
300			),
301			"fname": self.to_fname(),
302			"versions": self.versions,
303		}

serialize the MazeDatasetConfig with all fields and fname

def summary(self) -> dict:
305	def summary(self) -> dict:
306		"""return a summary of the config"""
307		# do we run this to make sure it doesn't error?
308		super_summary: dict = super().summary()
309		assert super_summary
310		self_ser: dict = self.serialize()
311		return dict(
312			name=self.name,
313			fname=self.to_fname(),
314			sdc_hash=self.stable_hash_cfg(),
315			seed=self.seed,
316			seq_len_min=self.seq_len_min,
317			seq_len_max=self.seq_len_max,
318			applied_filters=self.applied_filters,
319			grid_n=self_ser["grid_n"],
320			n_mazes=self_ser["n_mazes"],
321			maze_ctor_name=self_ser["maze_ctor"]["__name__"],
322			maze_ctor_kwargs=self_ser["maze_ctor_kwargs"],
323			endpoint_kwargs=self_ser["endpoint_kwargs"],
324		)

return a summary of the config

def success_fraction_estimate(self, except_if_all_success_expected: bool = False) -> float:
407	def success_fraction_estimate(
408		self,
409		except_if_all_success_expected: bool = False,
410	) -> float:
411		"""Estimate the success fraction of this config.
412
413		only valid when the generator is a percolation generator,
414		and endpoints are enforced to be dead ends
415
416		more information on where this comes from can be found in
417		- `cfg_success_predict_fn()` from `maze_dataset.dataset.success_predict_math`
418		- `estimate_dataset_fractions.ipynb`
419		- `maze_dataset.benchmarks.sweep_fit`
420
421		# Parameters:
422		- `except_if_all_success_expected : bool`
423			if `True`, don't raise an error if the success fraction is below the threshold.
424			will always return `1.0` if the config is not expected to fail
425
426		# Returns:
427		- `float`
428			estimated success fraction
429
430		# Raises:
431		- `NoPercolationInConfigError` : if the config is not expected to fail, and `except_if_all_success_expected` is `False`
432		"""
433		try:
434			return cfg_success_predict_fn(self)
435
436		except NoPercolationInConfigError as e:
437			if except_if_all_success_expected:
438				raise e  # noqa: TRY201
439			return 1.0

Estimate the success fraction of this config.

only valid when the generator is a percolation generator, and endpoints are enforced to be dead ends

more information on where this comes from can be found in

Parameters:

  • except_if_all_success_expected : bool if True, don't raise an error if the success fraction is below the threshold. will always return 1.0 if the config is not expected to fail

Returns:

  • float estimated success fraction

Raises:

  • NoPercolationInConfigError : if the config is not expected to fail, and except_if_all_success_expected is False
def success_fraction_compensate( self, safety_margin: float = 1.2, except_if_all_success_expected: bool = False, epsilon: float = 0.01) -> MazeDatasetConfig:
441	def success_fraction_compensate(
442		self,
443		safety_margin: float = 1.2,
444		except_if_all_success_expected: bool = False,
445		epsilon: float = 1e-2,
446	) -> "MazeDatasetConfig":
447		"""return a new `MazeDatasetConfig` like this one with `n_mazes` adjusted to compensate for the success fraction
448
449		calls `MazeDatasetConfig.success_fraction_estimate()` to get the success fraction, and then
450		computes the new number of mazes as `n_mazes = n_mazes * safety_margin / success_fraction + 1`
451
452		more information on where this comes from can be found in
453		- `cfg_success_predict_fn()` from `maze_dataset.dataset.success_predict_math`
454		- `estimate_dataset_fractions.ipynb`
455		- `maze_dataset.benchmarks.sweep_fit`
456
457		# Parameters:
458		- `safety_margin : float`
459			safety margin to apply to the success fraction estimate
460			(defaults to `1.2`, or 20% more mazes than estimated)
461		- `except_if_all_success_expected : bool`
462			if `True`, don't raise an error if the success fraction is below the threshold.
463			this is passed to `MazeDatasetConfig.success_fraction_estimate`.
464			if your config isn't expected to fail, passing this might mean you generate more mazes than needed
465			since `safety_margin` is still applied.
466			(defaults to `False`)
467		- `epsilon : float`
468			raise `SuccessChanceTooSmallError` if the success fraction is below this threshold
469			(defaults to `1e-2`)
470
471		# Returns:
472		- `MazeDatasetConfig`
473			new config with adjusted `n_mazes`
474
475		# Raises:
476		- `SuccessChanceTooSmallError` : if the computed success fraction is below `epsilon`
477		"""
478		# compute and check the success fraction
479		success_fraction: float = self.success_fraction_estimate(
480			except_if_all_success_expected=except_if_all_success_expected,
481		)
482		if success_fraction < epsilon:
483			err_msg: str = (
484				f"{success_fraction = } is below the threshold of {epsilon = }"
485			)
486			raise SuccessChanceTooSmallError(
487				err_msg,
488			)
489
490		# compute the new number of mazes
491		n_mazes: int = self.n_mazes
492		new_n_mazes: int = int((n_mazes * safety_margin) / success_fraction) + 1
493
494		# put it in a new config and return
495		cfg_dict: dict = self.serialize()
496		cfg_dict["n_mazes"] = new_n_mazes
497		return MazeDatasetConfig.load(cfg_dict)

return a new MazeDatasetConfig like this one with n_mazes adjusted to compensate for the success fraction

calls MazeDatasetConfig.success_fraction_estimate() to get the success fraction, and then computes the new number of mazes as n_mazes = n_mazes * safety_margin / success_fraction + 1

more information on where this comes from can be found in

Parameters:

  • safety_margin : float safety margin to apply to the success fraction estimate (defaults to 1.2, or 20% more mazes than estimated)
  • except_if_all_success_expected : bool if True, don't raise an error if the success fraction is below the threshold. this is passed to MazeDatasetConfig.success_fraction_estimate. if your config isn't expected to fail, passing this might mean you generate more mazes than needed since safety_margin is still applied. (defaults to False)
  • epsilon : float raise SuccessChanceTooSmallError if the success fraction is below this threshold (defaults to 1e-2)

Returns:

Raises:

  • SuccessChanceTooSmallError : if the computed success fraction is below epsilon
@classmethod
def load(cls, data: Union[dict[str, Any], ~T]) -> Type[~T]:
777        @classmethod  # type: ignore[misc]
778        def load(cls, data: dict[str, Any] | T) -> Type[T]:
779            # HACK: this is kind of ugly, but it fixes a lot of issues for when we do recursive loading with ZANJ
780            if isinstance(data, cls):
781                return data
782
783            assert isinstance(
784                data, typing.Mapping
785            ), f"When loading {cls.__name__ = } expected a Mapping, but got {type(data) = }:\n{data = }"
786
787            cls_type_hints: dict[str, Any] = get_cls_type_hints(cls)
788
789            # initialize dict for keeping what we will pass to the constructor
790            ctor_kwargs: dict[str, Any] = dict()
791
792            # iterate over the fields of the class
793            for field in dataclasses.fields(cls):
794                # check if the field is a SerializableField
795                assert isinstance(
796                    field, SerializableField
797                ), f"Field '{field.name}' on class {cls.__name__} is not a SerializableField, but a {type(field)}. this state should be inaccessible, please report this bug!\nhttps://github.com/mivanit/muutils/issues/new"
798
799                # check if the field is in the data and if it should be initialized
800                if (field.name in data) and field.init:
801                    # get the value, we will be processing it
802                    value: Any = data[field.name]
803
804                    # get the type hint for the field
805                    field_type_hint: Any = cls_type_hints.get(field.name, None)
806
807                    # we rely on the init of `SerializableField` to check that only one of `loading_fn` and `deserialize_fn` is set
808                    if field.deserialize_fn:
809                        # if it has a deserialization function, use that
810                        value = field.deserialize_fn(value)
811                    elif field.loading_fn:
812                        # if it has a loading function, use that
813                        value = field.loading_fn(data)
814                    elif (
815                        field_type_hint is not None
816                        and hasattr(field_type_hint, "load")
817                        and callable(field_type_hint.load)
818                    ):
819                        # if no loading function but has a type hint with a load method, use that
820                        if isinstance(value, dict):
821                            value = field_type_hint.load(value)
822                        else:
823                            raise FieldLoadingError(
824                                f"Cannot load value into {field_type_hint}, expected {type(value) = } to be a dict\n{value = }"
825                            )
826                    else:
827                        # assume no loading needs to happen, keep `value` as-is
828                        pass
829
830                    # store the value in the constructor kwargs
831                    ctor_kwargs[field.name] = value
832
833            # create a new instance of the class with the constructor kwargs
834            output: cls = cls(**ctor_kwargs)
835
836            # validate the types of the fields if needed
837            if on_typecheck_mismatch != ErrorMode.IGNORE:
838                fields_valid: dict[str, bool] = (
839                    SerializableDataclass__validate_fields_types__dict(
840                        output,
841                        on_typecheck_error=on_typecheck_error,
842                    )
843                )
844
845                # if there are any fields that are not valid, raise an error
846                if not all(fields_valid.values()):
847                    msg: str = (
848                        f"Type mismatch in fields of {cls.__name__}:\n"
849                        + "\n".join(
850                            [
851                                f"{k}:\texpected {cls_type_hints[k] = }, but got value {getattr(output, k) = }, {type(getattr(output, k)) = }"
852                                for k, v in fields_valid.items()
853                                if not v
854                            ]
855                        )
856                    )
857
858                    on_typecheck_mismatch.process(
859                        msg, except_cls=FieldTypeMismatchError
860                    )
861
862            # return the new instance
863            return output

takes in an appropriately structured dict and returns an instance of the class, implemented by using @serializable_dataclass decorator

def validate_fields_types( self: muutils.json_serialize.serializable_dataclass.SerializableDataclass, on_typecheck_error: muutils.errormode.ErrorMode = ErrorMode.Except) -> bool:
283def SerializableDataclass__validate_fields_types(
284    self: SerializableDataclass,
285    on_typecheck_error: ErrorMode = _DEFAULT_ON_TYPECHECK_ERROR,
286) -> bool:
287    """validate the types of all the fields on a `SerializableDataclass`. calls `SerializableDataclass__validate_field_type` for each field"""
288    return all(
289        SerializableDataclass__validate_fields_types__dict(
290            self, on_typecheck_error=on_typecheck_error
291        ).values()
292    )

validate the types of all the fields on a SerializableDataclass. calls SerializableDataclass__validate_field_type for each field

113class MazeDataset(GPTDataset[MazeDatasetConfig]):
114	"""a maze dataset class. This is a collection of solved mazes, and should be initialized via `MazeDataset.from_config`"""
115
116	def __init__(
117		self,
118		cfg: MazeDatasetConfig,
119		mazes: typing.Sequence[SolvedMaze],
120		generation_metadata_collected: dict | None = None,
121	) -> None:
122		"""initialize a maze dataset from a config and a list of solved mazes"""
123		super().__init__()
124		self.cfg: MazeDatasetConfig = cfg
125		self.mazes: list[SolvedMaze] = list(mazes)
126		self.generation_metadata_collected: dict | None = generation_metadata_collected
127
128	# TYPING: error: Return type "MazeDataset" of "from_config" incompatible with return type "T_Dataset" in supertype "GPTDataset"  [override]
129	@classmethod
130	def from_config(  # type: ignore[override]
131		cls,
132		# TYPING: error: Argument 1 of "from_config" is incompatible with supertype "GPTDataset"; supertype defines the argument type as "T_DatasetConfig"  [override]
133		cfg: MazeDatasetConfig,  # type: ignore[override]
134		do_generate: bool = True,
135		load_local: bool = True,
136		save_local: bool = True,
137		zanj: ZANJ | None = None,
138		do_download: bool = True,
139		local_base_path: Path = Path("data/maze_dataset"),
140		except_on_config_mismatch: bool = True,
141		allow_generation_metadata_filter_mismatch: bool = True,
142		verbose: bool = False,
143		**kwargs,
144	) -> "MazeDataset":
145		"""create a maze dataset from a config
146
147		priority of loading:
148		1. load from local
149		2. download
150		3. generate
151
152		"""
153		return cast(
154			MazeDataset,
155			super().from_config(
156				cfg=cfg,
157				do_generate=do_generate,
158				load_local=load_local,
159				save_local=save_local,
160				zanj=zanj,
161				do_download=do_download,
162				local_base_path=local_base_path,
163				except_on_config_mismatch=except_on_config_mismatch,
164				allow_generation_metadata_filter_mismatch=allow_generation_metadata_filter_mismatch,
165				verbose=verbose,
166				**kwargs,
167			),
168		)
169
170	def data_hash(self) -> int:
171		"""return a hash of the data"""
172		return stable_hash(str(tuple([x.serialize() for x in self.mazes])))
173
174	def __getitem__(self, i: int) -> SolvedMaze:
175		"""get a maze by index"""
176		return self.mazes[i]
177
178	def __iter__(self) -> typing.Iterator[SolvedMaze]:
179		"""iterate over the mazes"""
180		return iter(self.mazes)
181
182	def __deepcopy__(self, memo) -> "MazeDataset":  # noqa: ANN001
183		"""deepcopy the dataset
184
185		FIX: this isnt actually a deepcopy I think?
186		"""
187		return MazeDataset.load(self._serialize_full())
188
189	# TYPING: get type hints on the tokenizer here
190	@overload
191	def as_tokens(
192		self,
193		maze_tokenizer,  # noqa: ANN001
194		limit: int | None = None,
195		join_tokens_individual_maze: Literal[False] = False,
196	) -> list[list[str]]: ...
197	@overload
198	def as_tokens(
199		self,
200		maze_tokenizer,  # noqa: ANN001
201		limit: int | None = None,
202		join_tokens_individual_maze: Literal[True] = True,
203	) -> list[str]: ...
204	def as_tokens(
205		self,
206		maze_tokenizer,  # TODO: MazeTokenizer
207		limit: int | None = None,
208		join_tokens_individual_maze: bool = False,
209	) -> list[list[str]] | list[str]:
210		"""return the dataset as tokens according to the passed `maze_tokenizer`
211
212		the `maze_tokenizer` should be either a `MazeTokenizer` or a `MazeTokenizerModular`
213
214		if `join_tokens_individual_maze` is True, then the tokens of each maze are
215		joined with a space, and the result is a list of strings.
216		i.e.:
217
218			>>> dataset.as_tokens(join_tokens_individual_maze=False)
219			[["a", "b", "c"], ["d", "e", "f"]]
220			>>> dataset.as_tokens(join_tokens_individual_maze=True)
221			["a b c", "d e f"]
222		"""
223		output: list[list[str]] = [
224			maze.as_tokens(maze_tokenizer) for maze in self.mazes[:limit]
225		]
226		if join_tokens_individual_maze:
227			return [" ".join(tokens) for tokens in output]
228		else:
229			return output
230
231	def __len__(self) -> int:
232		"""return the number of mazes in the dataset"""
233		return len(self.mazes)
234
235	def __eq__(self, other: object) -> bool:
236		"""compare two datasets"""
237		if not isinstance(other, MazeDataset):
238			raise NotImplementedError(
239				"can only compare with other MazeDataset objects",
240			)
241		# TODO: compare hashes of data instead of the data itself?
242		return self.cfg == other.cfg and self.mazes == other.mazes
243
244	def assert_equal(self, other: "MazeDataset") -> None:
245		"""assert that two datasets are equal"""
246		assert isinstance(other, MazeDataset)
247		assert self.cfg == other.cfg, f"{self.cfg.diff(other.cfg) = }"
248		assert self.mazes == other.mazes, f"{self.mazes = }, {other.mazes = }"
249
250	@classmethod
251	def generate(
252		cls,
253		cfg: MazeDatasetConfig,
254		gen_parallel: bool = False,
255		pool_kwargs: dict | None = None,
256		verbose: bool = False,
257		# TODO: what to do when unexpected kwargs are passed?
258		**kwargs,  # noqa: ARG003
259	) -> "MazeDataset":
260		"""Generate a maze dataset given a config and some generation parameters"""
261		# Copy the config to avoid modifying the original
262		cfg_cpy: MazeDatasetConfig = MazeDatasetConfig.load(
263			json.loads(json.dumps(cfg.serialize())),
264		)
265
266		if pool_kwargs is None:
267			pool_kwargs = dict()
268		maze_indexes: Int[np.ndarray, " maze_index"] = np.arange(cfg_cpy.n_mazes)  # type: ignore[assignment]
269
270		solved_mazes: list[SolvedMaze | None]
271		# Configure tqdm for progress bar
272		tqdm_kwargs: dict = dict(
273			total=cfg_cpy.n_mazes,
274			unit="maze",
275			desc="generating & solving mazes",
276			disable=not verbose,
277		)
278		# TODO: don't use the global unless generating in parallel!
279		if gen_parallel:
280			with multiprocessing.Pool(
281				**pool_kwargs,
282				initializer=_maze_gen_init_worker,
283				initargs=(cfg_cpy,),
284			) as pool:
285				solved_mazes = list(
286					tqdm.tqdm(
287						pool.imap(_generate_maze_helper, maze_indexes),
288						**tqdm_kwargs,
289					),
290				)
291
292		else:
293			_maze_gen_init_worker(cfg_cpy)
294			solved_mazes = list(
295				tqdm.tqdm(
296					map(
297						# TYPING:  error: Argument 1 to "map" has incompatible type "Callable[[int], SolvedMaze | None]"; expected "Callable[[str], SolvedMaze | None]"  [arg-type]
298						# why does it think tolist() returns a string?
299						_generate_maze_helper,  # type: ignore[arg-type]
300						maze_indexes.tolist(),
301					),
302					**tqdm_kwargs,
303				),
304			)
305
306		# Filter out None values explicitly after ensuring all results are collected
307		solved_mazes_: list[SolvedMaze] = [
308			maze for maze in solved_mazes if maze is not None
309		]
310		# solved_mazes_ = list(filter(lambda x: x is not None, solved_mazes))
311
312		# Update the config with the actual number of mazes
313		cfg_cpy.n_mazes = len(solved_mazes_)
314
315		dataset: MazeDataset = cls(
316			cfg=cfg_cpy,
317			mazes=solved_mazes_,
318		)
319
320		dataset.update_self_config()  # Call `update_self_config()` to ensure the dataset's config reflects changes
321
322		np.random.seed(cfg_cpy.seed)  # Reset the seed to the value in the config copy
323
324		return dataset
325
326	@classmethod
327	def download(cls, cfg: MazeDatasetConfig, **kwargs) -> "MazeDataset":
328		"(not implemented yet!) download a maze dataset from the internet"
329		raise NotImplementedError("not implemented yet")
330
331	@classmethod
332	def load(cls: "type[MazeDataset]", data: JSONdict) -> "MazeDataset":
333		"""load from zanj/json"""
334		if data[_FORMAT_KEY] == "MazeDataset:minimal":
335			return cls._load_minimal(data)
336		elif data[_FORMAT_KEY] == "MazeDataset:minimal_soln_cat":
337			return cls._load_minimal_soln_cat(data)
338		elif data[_FORMAT_KEY] == "MazeDataset":
339			if (
340				SERIALIZE_MINIMAL_THRESHOLD == -1
341			):  # Allow access to `_load_legacy` for profiling
342				return cls._load_legacy(data)
343			return cls._load_full(data)
344		else:
345			err_msg: str = f"`_FORMAT_KEY` string {data[_FORMAT_KEY] = } is not a recognized `MazeDataset` format. ({_FORMAT_KEY = })"
346			raise KeyError(
347				err_msg,
348			)
349
350	@classmethod
351	def _load_full(cls, data: JSONdict) -> "MazeDataset":
352		assert data[_FORMAT_KEY] == "MazeDataset"
353		return cls(
354			cfg=MazeDatasetConfig.load(data["cfg"]),  # type: ignore[arg-type]
355			mazes=load_item_recursive(data["mazes"], tuple()),
356			generation_metadata_collected=data["generation_metadata_collected"],  # type: ignore[arg-type]
357		)
358
359	@classmethod
360	def _load_minimal(cls, data: JSONdict) -> "MazeDataset":
361		assert data[_FORMAT_KEY] == "MazeDataset:minimal"
362		return cls(
363			cfg=MazeDatasetConfig.load(data["cfg"]),  # type: ignore[arg-type]
364			generation_metadata_collected=data["generation_metadata_collected"],  # type: ignore[arg-type]
365			mazes=[
366				SolvedMaze(
367					clist,
368					soln[:slen, ...],
369				)
370				for clist, slen, soln in zip(
371					load_item_recursive(data["maze_connection_lists"], tuple()),
372					load_item_recursive(data["maze_solution_lengths"], tuple()),
373					load_item_recursive(data["maze_solutions"], tuple()),
374					strict=False,
375					# load_item_recursive(data["maze_endpoints"], tuple()),
376				)
377			],
378		)
379
380	@classmethod
381	def _load_minimal_soln_cat(cls, data: JSONdict) -> "MazeDataset":
382		assert data[_FORMAT_KEY] == "MazeDataset:minimal_soln_cat"
383
384		maze_solution_lengths = load_item_recursive(
385			data["maze_solution_lengths"],
386			tuple(),
387		)
388		maze_solutions_concat = load_item_recursive(
389			data["maze_solutions_concat"],
390			tuple(),
391		)
392		maze_solutions = np.split(
393			maze_solutions_concat,
394			np.cumsum(maze_solution_lengths)[:-1],
395			axis=0,
396		)
397
398		return cls(
399			cfg=load_item_recursive(data["cfg"], tuple()),
400			generation_metadata_collected=load_item_recursive(
401				data["generation_metadata_collected"],
402				tuple(),
403			),
404			mazes=[
405				SolvedMaze(
406					connection_list=clist,
407					solution=soln,
408				)
409				for clist, soln in zip(
410					load_item_recursive(data["maze_connection_lists"], tuple()),
411					# load_item_recursive(data["maze_endpoints"], tuple()),
412					maze_solutions,
413					strict=False,
414				)
415			],
416		)
417
418	@classmethod
419	def _load_legacy(cls, data: JSONdict) -> "MazeDataset":
420		"""Legacy `load` method from <0.5.2. Used exclusively for profiling comparison."""
421		assert data[_FORMAT_KEY] == "MazeDataset"
422		return cls(
423			**{
424				key: load_item_recursive(data[key], tuple())
425				for key in ["cfg", "mazes", "generation_metadata_collected"]
426			},
427		)
428
429	def serialize(self) -> JSONdict:
430		"""serialize to zanj/json"""
431		if (
432			SERIALIZE_MINIMAL_THRESHOLD is not None
433			and len(self) >= SERIALIZE_MINIMAL_THRESHOLD
434		):
435			return self._serialize_minimal()
436		return self._serialize_full()
437
438	def _serialize_full(self) -> JSONdict:
439		return {
440			_FORMAT_KEY: "MazeDataset",
441			"cfg": json_serialize(self.cfg),
442			"fname": self.cfg.to_fname(),
443			"mazes": json_serialize(self.mazes),
444			"generation_metadata_collected": json_serialize(
445				self.generation_metadata_collected,
446			),
447		}
448
449	def _serialize_minimal(self) -> JSONdict:
450		"alternate serialization where metadata is collected and mazes are stored in concatenated form"
451		filtered_meta: MazeDataset
452		if self.generation_metadata_collected is None:
453			filtered_meta = self.filter_by.collect_generation_meta()
454		else:
455			filtered_meta = self
456
457		max_solution_len: int = max(m.solution.shape[0] for m in filtered_meta.mazes)
458		n_mazes: int = len(filtered_meta.mazes)
459		grid_n: int = filtered_meta.cfg.grid_n
460
461		maze_connection_lists: np.ndarray = np.empty(
462			(n_mazes, 2, grid_n, grid_n),
463			dtype=np.bool_,
464		)
465		# maze_endpoints: np.ndarray = np.empty((n_mazes, 2, 2), dtype=np.int8)
466		maze_solution_lengths: np.ndarray = np.empty((n_mazes,), dtype=np.int32)
467		maze_solutions: np.ndarray = np.empty(
468			(n_mazes, max_solution_len, 2),
469			dtype=np.int8,
470		)
471
472		for idx, maze in enumerate(filtered_meta.mazes):
473			maze_connection_lists[idx] = maze.connection_list
474			# maze_endpoints[idx] = np.array([maze.start_pos, maze.end_pos])
475			maze_solution_lengths[idx] = maze.solution.shape[0]
476			maze_solutions[idx, : maze.solution.shape[0]] = maze.solution
477
478		return {
479			_FORMAT_KEY: "MazeDataset:minimal",
480			"cfg": json_serialize(filtered_meta.cfg),
481			"fname": filtered_meta.cfg.to_fname(),
482			"generation_metadata_collected": json_serialize(
483				filtered_meta.generation_metadata_collected,
484			),
485			"maze_connection_lists": maze_connection_lists,  # type: ignore[dict-item]
486			# "maze_endpoints": maze_endpoints,
487			"maze_solution_lengths": maze_solution_lengths,  # type: ignore[dict-item]
488			"maze_solutions": maze_solutions,  # type: ignore[dict-item]
489		}
490
491	def _serialize_minimal_soln_cat(self: "MazeDataset") -> JSONdict:
492		"alternate serialization where metadata is collected, and mazes and their solutions are stored in concatenated form"
493		filtered_meta: MazeDataset
494		if self.generation_metadata_collected is None:
495			filtered_meta = self.filter_by.collect_generation_meta()
496		else:
497			filtered_meta = self
498
499		maze_solution_lengths: np.ndarray = np.array(
500			[m.solution.shape[0] for m in filtered_meta.mazes],
501			dtype=np.int32,
502		)
503		n_mazes: int = len(filtered_meta.mazes)
504		grid_n: int = filtered_meta.cfg.grid_n
505		total_solution_len: int = np.sum(maze_solution_lengths)
506
507		maze_connection_lists: np.ndarray = np.empty(
508			(n_mazes, 2, grid_n, grid_n),
509			dtype=np.bool_,
510		)
511		maze_endpoints: np.ndarray = np.empty((n_mazes, 2, 2), dtype=np.int8)
512		maze_solutions_concat: np.ndarray = np.empty(
513			(total_solution_len, 2),
514			dtype=np.int8,
515		)
516
517		solutions_running_idx: int = 0
518		for idx, maze in enumerate(filtered_meta.mazes):
519			maze_connection_lists[idx] = maze.connection_list
520			maze_endpoints[idx] = np.array([maze.start_pos, maze.end_pos])
521			soln_len: int = maze.solution.shape[0]
522			maze_solution_lengths[idx] = soln_len
523			maze_solutions_concat[
524				solutions_running_idx : solutions_running_idx + soln_len
525			] = maze.solution
526			solutions_running_idx += soln_len
527
528		return {
529			_FORMAT_KEY: "MazeDataset:minimal_soln_cat",
530			"cfg": json_serialize(filtered_meta.cfg),
531			"fname": filtered_meta.cfg.to_fname(),
532			"generation_metadata_collected": json_serialize(
533				filtered_meta.generation_metadata_collected,
534			),
535			"maze_connection_lists": maze_connection_lists,  # type: ignore[dict-item]
536			"maze_endpoints": maze_endpoints,  # type: ignore[dict-item]
537			"maze_solution_lengths": maze_solution_lengths,  # type: ignore[dict-item]
538			"maze_solutions_concat": maze_solutions_concat,  # type: ignore[dict-item]
539		}
540
541	def update_self_config(self) -> None:
542		"""update the config to match the current state of the dataset (number of mazes, such as after filtering)"""
543		if self.cfg.n_mazes != len(self.mazes):
544			warnings.warn(
545				f"updating config n_mazes from {self.cfg.n_mazes} to {len(self.mazes)}",
546			)
547			self.cfg.n_mazes = len(self.mazes)
548
549	def custom_maze_filter(
550		self,
551		method: typing.Callable[[SolvedMaze], bool],
552		**kwargs,
553	) -> "MazeDataset":
554		"""filter the dataset using a custom method"""
555		output: MazeDataset = MazeDataset(
556			cfg=copy.deepcopy(self.cfg),
557			mazes=[m for m in self.mazes if method(m, **kwargs)],
558		)
559		output.cfg.applied_filters.append(
560			{
561				"name": f"__custom__:{method.__name__}",
562				"kwargs": kwargs,
563			},
564		)
565		output.update_self_config()
566		return output

a maze dataset class. This is a collection of solved mazes, and should be initialized via MazeDataset.from_config

MazeDataset( cfg: MazeDatasetConfig, mazes: Sequence[SolvedMaze], generation_metadata_collected: dict | None = None)
116	def __init__(
117		self,
118		cfg: MazeDatasetConfig,
119		mazes: typing.Sequence[SolvedMaze],
120		generation_metadata_collected: dict | None = None,
121	) -> None:
122		"""initialize a maze dataset from a config and a list of solved mazes"""
123		super().__init__()
124		self.cfg: MazeDatasetConfig = cfg
125		self.mazes: list[SolvedMaze] = list(mazes)
126		self.generation_metadata_collected: dict | None = generation_metadata_collected

initialize a maze dataset from a config and a list of solved mazes

mazes: list[SolvedMaze]
generation_metadata_collected: dict | None
@classmethod
def from_config( cls, cfg: MazeDatasetConfig, do_generate: bool = True, load_local: bool = True, save_local: bool = True, zanj: zanj.zanj.ZANJ | None = None, do_download: bool = True, local_base_path: pathlib.Path = PosixPath('data/maze_dataset'), except_on_config_mismatch: bool = True, allow_generation_metadata_filter_mismatch: bool = True, verbose: bool = False, **kwargs) -> MazeDataset:
129	@classmethod
130	def from_config(  # type: ignore[override]
131		cls,
132		# TYPING: error: Argument 1 of "from_config" is incompatible with supertype "GPTDataset"; supertype defines the argument type as "T_DatasetConfig"  [override]
133		cfg: MazeDatasetConfig,  # type: ignore[override]
134		do_generate: bool = True,
135		load_local: bool = True,
136		save_local: bool = True,
137		zanj: ZANJ | None = None,
138		do_download: bool = True,
139		local_base_path: Path = Path("data/maze_dataset"),
140		except_on_config_mismatch: bool = True,
141		allow_generation_metadata_filter_mismatch: bool = True,
142		verbose: bool = False,
143		**kwargs,
144	) -> "MazeDataset":
145		"""create a maze dataset from a config
146
147		priority of loading:
148		1. load from local
149		2. download
150		3. generate
151
152		"""
153		return cast(
154			MazeDataset,
155			super().from_config(
156				cfg=cfg,
157				do_generate=do_generate,
158				load_local=load_local,
159				save_local=save_local,
160				zanj=zanj,
161				do_download=do_download,
162				local_base_path=local_base_path,
163				except_on_config_mismatch=except_on_config_mismatch,
164				allow_generation_metadata_filter_mismatch=allow_generation_metadata_filter_mismatch,
165				verbose=verbose,
166				**kwargs,
167			),
168		)

create a maze dataset from a config

priority of loading:

  1. load from local
  2. download
  3. generate
def data_hash(self) -> int:
170	def data_hash(self) -> int:
171		"""return a hash of the data"""
172		return stable_hash(str(tuple([x.serialize() for x in self.mazes])))

return a hash of the data

def as_tokens( self, maze_tokenizer, limit: int | None = None, join_tokens_individual_maze: bool = False) -> list[list[str]] | list[str]:
204	def as_tokens(
205		self,
206		maze_tokenizer,  # TODO: MazeTokenizer
207		limit: int | None = None,
208		join_tokens_individual_maze: bool = False,
209	) -> list[list[str]] | list[str]:
210		"""return the dataset as tokens according to the passed `maze_tokenizer`
211
212		the `maze_tokenizer` should be either a `MazeTokenizer` or a `MazeTokenizerModular`
213
214		if `join_tokens_individual_maze` is True, then the tokens of each maze are
215		joined with a space, and the result is a list of strings.
216		i.e.:
217
218			>>> dataset.as_tokens(join_tokens_individual_maze=False)
219			[["a", "b", "c"], ["d", "e", "f"]]
220			>>> dataset.as_tokens(join_tokens_individual_maze=True)
221			["a b c", "d e f"]
222		"""
223		output: list[list[str]] = [
224			maze.as_tokens(maze_tokenizer) for maze in self.mazes[:limit]
225		]
226		if join_tokens_individual_maze:
227			return [" ".join(tokens) for tokens in output]
228		else:
229			return output

return the dataset as tokens according to the passed maze_tokenizer

the maze_tokenizer should be either a MazeTokenizer or a MazeTokenizerModular

if join_tokens_individual_maze is True, then the tokens of each maze are joined with a space, and the result is a list of strings. i.e.:

    >>> dataset.as_tokens(join_tokens_individual_maze=False)
    [["a", "b", "c"], ["d", "e", "f"]]
    >>> dataset.as_tokens(join_tokens_individual_maze=True)
    ["a b c", "d e f"]
def assert_equal(self, other: MazeDataset) -> None:
244	def assert_equal(self, other: "MazeDataset") -> None:
245		"""assert that two datasets are equal"""
246		assert isinstance(other, MazeDataset)
247		assert self.cfg == other.cfg, f"{self.cfg.diff(other.cfg) = }"
248		assert self.mazes == other.mazes, f"{self.mazes = }, {other.mazes = }"

assert that two datasets are equal

@classmethod
def generate( cls, cfg: MazeDatasetConfig, gen_parallel: bool = False, pool_kwargs: dict | None = None, verbose: bool = False, **kwargs) -> MazeDataset:
250	@classmethod
251	def generate(
252		cls,
253		cfg: MazeDatasetConfig,
254		gen_parallel: bool = False,
255		pool_kwargs: dict | None = None,
256		verbose: bool = False,
257		# TODO: what to do when unexpected kwargs are passed?
258		**kwargs,  # noqa: ARG003
259	) -> "MazeDataset":
260		"""Generate a maze dataset given a config and some generation parameters"""
261		# Copy the config to avoid modifying the original
262		cfg_cpy: MazeDatasetConfig = MazeDatasetConfig.load(
263			json.loads(json.dumps(cfg.serialize())),
264		)
265
266		if pool_kwargs is None:
267			pool_kwargs = dict()
268		maze_indexes: Int[np.ndarray, " maze_index"] = np.arange(cfg_cpy.n_mazes)  # type: ignore[assignment]
269
270		solved_mazes: list[SolvedMaze | None]
271		# Configure tqdm for progress bar
272		tqdm_kwargs: dict = dict(
273			total=cfg_cpy.n_mazes,
274			unit="maze",
275			desc="generating & solving mazes",
276			disable=not verbose,
277		)
278		# TODO: don't use the global unless generating in parallel!
279		if gen_parallel:
280			with multiprocessing.Pool(
281				**pool_kwargs,
282				initializer=_maze_gen_init_worker,
283				initargs=(cfg_cpy,),
284			) as pool:
285				solved_mazes = list(
286					tqdm.tqdm(
287						pool.imap(_generate_maze_helper, maze_indexes),
288						**tqdm_kwargs,
289					),
290				)
291
292		else:
293			_maze_gen_init_worker(cfg_cpy)
294			solved_mazes = list(
295				tqdm.tqdm(
296					map(
297						# TYPING:  error: Argument 1 to "map" has incompatible type "Callable[[int], SolvedMaze | None]"; expected "Callable[[str], SolvedMaze | None]"  [arg-type]
298						# why does it think tolist() returns a string?
299						_generate_maze_helper,  # type: ignore[arg-type]
300						maze_indexes.tolist(),
301					),
302					**tqdm_kwargs,
303				),
304			)
305
306		# Filter out None values explicitly after ensuring all results are collected
307		solved_mazes_: list[SolvedMaze] = [
308			maze for maze in solved_mazes if maze is not None
309		]
310		# solved_mazes_ = list(filter(lambda x: x is not None, solved_mazes))
311
312		# Update the config with the actual number of mazes
313		cfg_cpy.n_mazes = len(solved_mazes_)
314
315		dataset: MazeDataset = cls(
316			cfg=cfg_cpy,
317			mazes=solved_mazes_,
318		)
319
320		dataset.update_self_config()  # Call `update_self_config()` to ensure the dataset's config reflects changes
321
322		np.random.seed(cfg_cpy.seed)  # Reset the seed to the value in the config copy
323
324		return dataset

Generate a maze dataset given a config and some generation parameters

@classmethod
def download( cls, cfg: MazeDatasetConfig, **kwargs) -> MazeDataset:
326	@classmethod
327	def download(cls, cfg: MazeDatasetConfig, **kwargs) -> "MazeDataset":
328		"(not implemented yet!) download a maze dataset from the internet"
329		raise NotImplementedError("not implemented yet")

(not implemented yet!) download a maze dataset from the internet

@classmethod
def load( cls: type[MazeDataset], data: Dict[str, Union[bool, int, float, str, NoneType, List[Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]], Dict[str, Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]]]]) -> MazeDataset:
331	@classmethod
332	def load(cls: "type[MazeDataset]", data: JSONdict) -> "MazeDataset":
333		"""load from zanj/json"""
334		if data[_FORMAT_KEY] == "MazeDataset:minimal":
335			return cls._load_minimal(data)
336		elif data[_FORMAT_KEY] == "MazeDataset:minimal_soln_cat":
337			return cls._load_minimal_soln_cat(data)
338		elif data[_FORMAT_KEY] == "MazeDataset":
339			if (
340				SERIALIZE_MINIMAL_THRESHOLD == -1
341			):  # Allow access to `_load_legacy` for profiling
342				return cls._load_legacy(data)
343			return cls._load_full(data)
344		else:
345			err_msg: str = f"`_FORMAT_KEY` string {data[_FORMAT_KEY] = } is not a recognized `MazeDataset` format. ({_FORMAT_KEY = })"
346			raise KeyError(
347				err_msg,
348			)

load from zanj/json

def serialize( self) -> Dict[str, Union[bool, int, float, str, NoneType, List[Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]], Dict[str, Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]]]]:
429	def serialize(self) -> JSONdict:
430		"""serialize to zanj/json"""
431		if (
432			SERIALIZE_MINIMAL_THRESHOLD is not None
433			and len(self) >= SERIALIZE_MINIMAL_THRESHOLD
434		):
435			return self._serialize_minimal()
436		return self._serialize_full()

serialize to zanj/json

def update_self_config(self) -> None:
541	def update_self_config(self) -> None:
542		"""update the config to match the current state of the dataset (number of mazes, such as after filtering)"""
543		if self.cfg.n_mazes != len(self.mazes):
544			warnings.warn(
545				f"updating config n_mazes from {self.cfg.n_mazes} to {len(self.mazes)}",
546			)
547			self.cfg.n_mazes = len(self.mazes)

update the config to match the current state of the dataset (number of mazes, such as after filtering)

def custom_maze_filter( self, method: Callable[[SolvedMaze], bool], **kwargs) -> MazeDataset:
549	def custom_maze_filter(
550		self,
551		method: typing.Callable[[SolvedMaze], bool],
552		**kwargs,
553	) -> "MazeDataset":
554		"""filter the dataset using a custom method"""
555		output: MazeDataset = MazeDataset(
556			cfg=copy.deepcopy(self.cfg),
557			mazes=[m for m in self.mazes if method(m, **kwargs)],
558		)
559		output.cfg.applied_filters.append(
560			{
561				"name": f"__custom__:{method.__name__}",
562				"kwargs": kwargs,
563			},
564		)
565		output.update_self_config()
566		return output

filter the dataset using a custom method

class MazeDatasetCollection(typing.Generic[~T_DatasetConfig]):
 84class MazeDatasetCollection(GPTDataset):
 85	"""a collection of maze datasets"""
 86
 87	def __init__(
 88		self,
 89		cfg: MazeDatasetCollectionConfig,
 90		maze_datasets: list[MazeDataset],
 91		generation_metadata_collected: dict | None = None,
 92	) -> None:
 93		"initialize the dataset collection from a `MazeDatasetCollectionConfig` and a list of `MazeDataset`s"
 94		super().__init__()
 95		self.cfg: MazeDatasetCollectionConfig = cfg
 96		self.maze_datasets: list[MazeDataset] = list(maze_datasets)
 97		for c, ds in zip(
 98			self.cfg.maze_dataset_configs,
 99			self.maze_datasets,
100			strict=False,
101		):
102			assert c.name == ds.cfg.name
103			assert c == ds.cfg
104
105		self.generation_metadata_collected: dict | None = generation_metadata_collected
106
107	@property
108	def dataset_lengths(self) -> list[int]:
109		"""return the lengths of each dataset in the collection"""
110		return [len(dataset) for dataset in self.maze_datasets]
111
112	@property
113	def dataset_cum_lengths(self) -> Int[np.ndarray, " indices"]:
114		"""return the cumulative lengths of each dataset in the collection"""
115		return np.array(list(itertools.accumulate(self.dataset_lengths)))
116
117	@cached_property
118	def mazes(self) -> list[LatticeMaze]:
119		"single list of all mazes in the collection"
120		return list(
121			itertools.chain.from_iterable(
122				dataset.mazes for dataset in self.maze_datasets
123			),
124		)
125
126	def __len__(self) -> int:
127		"""return the total number of mazes in the collection"""
128		return sum(len(dataset) for dataset in self.maze_datasets)
129
130	def __getitem__(self, index: int) -> LatticeMaze:
131		"get a maze by index"
132		# find which dataset the index belongs to
133		# we add 1, since np.searchsorted returns the
134		# index of the last element that is strictly less than the target
135		# while we want the index of the last element less than or equal to the target
136		dataset_idx: int = int(np.searchsorted(self.dataset_cum_lengths, index + 1))
137		index_adjusted: int = index
138		if dataset_idx > 0:
139			# if the index is 0, `dataset_idx - 1` will be -1.
140			# We just want to use the base index
141			index_adjusted -= self.dataset_cum_lengths[dataset_idx - 1]
142		return self.maze_datasets[dataset_idx][index_adjusted]
143
144	@classmethod
145	def generate(
146		cls,
147		cfg: MazeDatasetCollectionConfig,
148		**kwargs,
149	) -> "MazeDatasetCollection":
150		"""generate a dataset collection from a config"""
151		datasets = [
152			MazeDataset.generate(config, **kwargs)
153			for config in cfg.maze_dataset_configs
154		]
155		return cls(cfg, datasets)
156
157	@classmethod
158	def download(
159		cls,
160		cfg: MazeDatasetCollectionConfig,
161		**kwargs,
162	) -> "MazeDatasetCollection":
163		"(not implemented!) download a dataset collection from a config"
164		datasets = [
165			MazeDataset.download(config, **kwargs)
166			for config in cfg.maze_dataset_configs
167		]
168		return cls(cfg, datasets)
169
170	def serialize(self) -> JSONdict:
171		"""serialize the dataset collection"""
172		return {
173			_FORMAT_KEY: "MazeDatasetCollection",
174			"cfg": self.cfg.serialize(),
175			"maze_datasets": [dataset.serialize() for dataset in self.maze_datasets],
176			"generation_metadata_collected": json_serialize(
177				self.generation_metadata_collected,
178			),
179		}
180
181	@classmethod
182	def load(cls, data: JSONdict) -> "MazeDatasetCollection":
183		"""load the dataset collection from the representation created by `serialize`"""
184		assert data[_FORMAT_KEY] == "MazeDatasetCollection"
185		return cls(
186			**{
187				key: load_item_recursive(data[key], tuple())
188				for key in ["cfg", "maze_datasets", "generation_metadata_collected"]
189			},
190		)
191
192	# TODO: remove duplication with MazeDatasetConfig().as_tokens() somehow?
193	def as_tokens(
194		self,
195		# TODO: MazeTokenizer
196		maze_tokenizer,  # noqa: ANN001
197		limit: int | None = None,
198		join_tokens_individual_maze: bool = False,
199	) -> list[list[str]] | list[str]:
200		"""return the dataset as tokens
201
202		if join_tokens_individual_maze is True, then the tokens of each maze are
203		joined with a space, and the result is a list of strings.
204		i.e.:
205		>>> dataset.as_tokens(join_tokens_individual_maze=False)
206		[["a", "b", "c"], ["d", "e", "f"]]
207		>>> dataset.as_tokens(join_tokens_individual_maze=True)
208		["a b c", "d e f"]
209		"""
210		output: list[list[str]] = [
211			maze.as_tokens(maze_tokenizer) for maze in self.mazes[:limit]
212		]
213		if join_tokens_individual_maze:
214			return [" ".join(tokens) for tokens in output]
215		else:
216			return output
217
218	def update_self_config(self) -> None:
219		"update the config to match the number of mazes, and update the underlying configs of each dataset"
220		# TODO: why cant we set this directly? its not frozen, and it seems to work in a regular MazeDataset
221		self.cfg.__dict__["n_mazes"] = len(self)
222		for dataset in self.maze_datasets:
223			dataset.update_self_config()
224
225		self.cfg.maze_dataset_configs = [dataset.cfg for dataset in self.maze_datasets]

a collection of maze datasets

MazeDatasetCollection( cfg: MazeDatasetCollectionConfig, maze_datasets: list[MazeDataset], generation_metadata_collected: dict | None = None)
 87	def __init__(
 88		self,
 89		cfg: MazeDatasetCollectionConfig,
 90		maze_datasets: list[MazeDataset],
 91		generation_metadata_collected: dict | None = None,
 92	) -> None:
 93		"initialize the dataset collection from a `MazeDatasetCollectionConfig` and a list of `MazeDataset`s"
 94		super().__init__()
 95		self.cfg: MazeDatasetCollectionConfig = cfg
 96		self.maze_datasets: list[MazeDataset] = list(maze_datasets)
 97		for c, ds in zip(
 98			self.cfg.maze_dataset_configs,
 99			self.maze_datasets,
100			strict=False,
101		):
102			assert c.name == ds.cfg.name
103			assert c == ds.cfg
104
105		self.generation_metadata_collected: dict | None = generation_metadata_collected

initialize the dataset collection from a MazeDatasetCollectionConfig and a list of MazeDatasets

maze_datasets: list[MazeDataset]
generation_metadata_collected: dict | None
dataset_lengths: list[int]
107	@property
108	def dataset_lengths(self) -> list[int]:
109		"""return the lengths of each dataset in the collection"""
110		return [len(dataset) for dataset in self.maze_datasets]

return the lengths of each dataset in the collection

dataset_cum_lengths: jaxtyping.Int[ndarray, 'indices']
112	@property
113	def dataset_cum_lengths(self) -> Int[np.ndarray, " indices"]:
114		"""return the cumulative lengths of each dataset in the collection"""
115		return np.array(list(itertools.accumulate(self.dataset_lengths)))

return the cumulative lengths of each dataset in the collection

mazes: list[LatticeMaze]
117	@cached_property
118	def mazes(self) -> list[LatticeMaze]:
119		"single list of all mazes in the collection"
120		return list(
121			itertools.chain.from_iterable(
122				dataset.mazes for dataset in self.maze_datasets
123			),
124		)

single list of all mazes in the collection

@classmethod
def generate( cls, cfg: MazeDatasetCollectionConfig, **kwargs) -> MazeDatasetCollection:
144	@classmethod
145	def generate(
146		cls,
147		cfg: MazeDatasetCollectionConfig,
148		**kwargs,
149	) -> "MazeDatasetCollection":
150		"""generate a dataset collection from a config"""
151		datasets = [
152			MazeDataset.generate(config, **kwargs)
153			for config in cfg.maze_dataset_configs
154		]
155		return cls(cfg, datasets)

generate a dataset collection from a config

@classmethod
def download( cls, cfg: MazeDatasetCollectionConfig, **kwargs) -> MazeDatasetCollection:
157	@classmethod
158	def download(
159		cls,
160		cfg: MazeDatasetCollectionConfig,
161		**kwargs,
162	) -> "MazeDatasetCollection":
163		"(not implemented!) download a dataset collection from a config"
164		datasets = [
165			MazeDataset.download(config, **kwargs)
166			for config in cfg.maze_dataset_configs
167		]
168		return cls(cfg, datasets)

(not implemented!) download a dataset collection from a config

def serialize( self) -> Dict[str, Union[bool, int, float, str, NoneType, List[Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]], Dict[str, Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]]]]:
170	def serialize(self) -> JSONdict:
171		"""serialize the dataset collection"""
172		return {
173			_FORMAT_KEY: "MazeDatasetCollection",
174			"cfg": self.cfg.serialize(),
175			"maze_datasets": [dataset.serialize() for dataset in self.maze_datasets],
176			"generation_metadata_collected": json_serialize(
177				self.generation_metadata_collected,
178			),
179		}

serialize the dataset collection

@classmethod
def load( cls, data: Dict[str, Union[bool, int, float, str, NoneType, List[Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]], Dict[str, Union[bool, int, float, str, NoneType, List[Any], Dict[str, Any]]]]]) -> MazeDatasetCollection:
181	@classmethod
182	def load(cls, data: JSONdict) -> "MazeDatasetCollection":
183		"""load the dataset collection from the representation created by `serialize`"""
184		assert data[_FORMAT_KEY] == "MazeDatasetCollection"
185		return cls(
186			**{
187				key: load_item_recursive(data[key], tuple())
188				for key in ["cfg", "maze_datasets", "generation_metadata_collected"]
189			},
190		)

load the dataset collection from the representation created by serialize

def as_tokens( self, maze_tokenizer, limit: int | None = None, join_tokens_individual_maze: bool = False) -> list[list[str]] | list[str]:
193	def as_tokens(
194		self,
195		# TODO: MazeTokenizer
196		maze_tokenizer,  # noqa: ANN001
197		limit: int | None = None,
198		join_tokens_individual_maze: bool = False,
199	) -> list[list[str]] | list[str]:
200		"""return the dataset as tokens
201
202		if join_tokens_individual_maze is True, then the tokens of each maze are
203		joined with a space, and the result is a list of strings.
204		i.e.:
205		>>> dataset.as_tokens(join_tokens_individual_maze=False)
206		[["a", "b", "c"], ["d", "e", "f"]]
207		>>> dataset.as_tokens(join_tokens_individual_maze=True)
208		["a b c", "d e f"]
209		"""
210		output: list[list[str]] = [
211			maze.as_tokens(maze_tokenizer) for maze in self.mazes[:limit]
212		]
213		if join_tokens_individual_maze:
214			return [" ".join(tokens) for tokens in output]
215		else:
216			return output

return the dataset as tokens

if join_tokens_individual_maze is True, then the tokens of each maze are joined with a space, and the result is a list of strings. i.e.:

>>> dataset.as_tokens(join_tokens_individual_maze=False)
[["a", "b", "c"], ["d", "e", "f"]]
>>> dataset.as_tokens(join_tokens_individual_maze=True)
["a b c", "d e f"]
def update_self_config(self) -> None:
218	def update_self_config(self) -> None:
219		"update the config to match the number of mazes, and update the underlying configs of each dataset"
220		# TODO: why cant we set this directly? its not frozen, and it seems to work in a regular MazeDataset
221		self.cfg.__dict__["n_mazes"] = len(self)
222		for dataset in self.maze_datasets:
223			dataset.update_self_config()
224
225		self.cfg.maze_dataset_configs = [dataset.cfg for dataset in self.maze_datasets]

update the config to match the number of mazes, and update the underlying configs of each dataset

@serializable_dataclass(kw_only=True)
class MazeDatasetCollectionConfig(maze_dataset.dataset.dataset.GPTDatasetConfig):
31@serializable_dataclass(kw_only=True)
32class MazeDatasetCollectionConfig(GPTDatasetConfig):
33	"""maze dataset collection configuration, including tokenizers and shuffle"""
34
35	# Attributes without a default cannot follow attributes with one  [misc]
36	maze_dataset_configs: list[MazeDatasetConfig] = serializable_field(  # type: ignore[misc]
37		serialization_fn=lambda configs: [config.serialize() for config in configs],
38		loading_fn=lambda data: [
39			MazeDatasetConfig.load(config) for config in data["maze_dataset_configs"]
40		],
41	)
42
43	def summary(self) -> dict:
44		"""return a summary of the config"""
45		return dict(
46			n_mazes=self.n_mazes,
47			max_grid_n=self.max_grid_n,
48			max_grid_shape=self.max_grid_shape,
49			fname=self.to_fname(),
50			cfg_summaries=[c.summary() for c in self.maze_dataset_configs],
51		)
52
53	@property
54	def n_mazes(self) -> int:
55		"""return the total number of mazes in the collection across all dataset"""
56		return sum(config.n_mazes for config in self.maze_dataset_configs)
57
58	@property
59	def max_grid_n(self) -> int:
60		"""return the maximum grid size of the mazes in the collection"""
61		return max(config.grid_n for config in self.maze_dataset_configs)
62
63	@property
64	def max_grid_shape(self) -> CoordTup:
65		"""return the maximum grid shape of the mazes in the collection"""
66		return (self.max_grid_n, self.max_grid_n)
67
68	@property
69	def max_grid_shape_np(self) -> Coord:
70		"""return the maximum grid shape of the mazes in the collection as a numpy array"""
71		return np.array(self.max_grid_shape, dtype=np.int32)
72
73	def stable_hash_cfg(self) -> int:
74		"""return a stable hash of the config"""
75		return stable_hash(json.dumps(self.serialize()))
76
77	def to_fname(self) -> str:
78		"""convert config to a filename"""
79		return sanitize_fname(
80			f"collected-{self.name}-n{shorten_numerical_to_str(self.n_mazes)}-h{self.stable_hash_cfg() % 10**5}",
81		)

maze dataset collection configuration, including tokenizers and shuffle

MazeDatasetCollectionConfig( *, name: str, seq_len_min: int = 1, seq_len_max: int = 512, seed: int | None = 42, applied_filters: list[dict[typing.Literal['name', 'args', 'kwargs'], str | list | tuple | dict]] = <factory>, maze_dataset_configs: list[MazeDatasetConfig])
maze_dataset_configs: list[MazeDatasetConfig]
def summary(self) -> dict:
43	def summary(self) -> dict:
44		"""return a summary of the config"""
45		return dict(
46			n_mazes=self.n_mazes,
47			max_grid_n=self.max_grid_n,
48			max_grid_shape=self.max_grid_shape,
49			fname=self.to_fname(),
50			cfg_summaries=[c.summary() for c in self.maze_dataset_configs],
51		)

return a summary of the config

n_mazes: int
53	@property
54	def n_mazes(self) -> int:
55		"""return the total number of mazes in the collection across all dataset"""
56		return sum(config.n_mazes for config in self.maze_dataset_configs)

return the total number of mazes in the collection across all dataset

max_grid_n: int
58	@property
59	def max_grid_n(self) -> int:
60		"""return the maximum grid size of the mazes in the collection"""
61		return max(config.grid_n for config in self.maze_dataset_configs)

return the maximum grid size of the mazes in the collection

max_grid_shape: tuple[int, int]
63	@property
64	def max_grid_shape(self) -> CoordTup:
65		"""return the maximum grid shape of the mazes in the collection"""
66		return (self.max_grid_n, self.max_grid_n)

return the maximum grid shape of the mazes in the collection

max_grid_shape_np: jaxtyping.Int8[ndarray, 'row_col=2']
68	@property
69	def max_grid_shape_np(self) -> Coord:
70		"""return the maximum grid shape of the mazes in the collection as a numpy array"""
71		return np.array(self.max_grid_shape, dtype=np.int32)

return the maximum grid shape of the mazes in the collection as a numpy array

def stable_hash_cfg(self) -> int:
73	def stable_hash_cfg(self) -> int:
74		"""return a stable hash of the config"""
75		return stable_hash(json.dumps(self.serialize()))

return a stable hash of the config

def to_fname(self) -> str:
77	def to_fname(self) -> str:
78		"""convert config to a filename"""
79		return sanitize_fname(
80			f"collected-{self.name}-n{shorten_numerical_to_str(self.n_mazes)}-h{self.stable_hash_cfg() % 10**5}",
81		)

convert config to a filename

def serialize(self) -> dict[str, typing.Any]:
714        def serialize(self) -> dict[str, Any]:
715            result: dict[str, Any] = {
716                _FORMAT_KEY: f"{self.__class__.__name__}(SerializableDataclass)"
717            }
718            # for each field in the class
719            for field in dataclasses.fields(self):  # type: ignore[arg-type]
720                # need it to be our special SerializableField
721                if not isinstance(field, SerializableField):
722                    raise NotSerializableFieldException(
723                        f"Field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__} is not a `SerializableField`, "
724                        f"but a {type(field)} "
725                        "this state should be inaccessible, please report this bug!"
726                    )
727
728                # try to save it
729                if field.serialize:
730                    try:
731                        # get the val
732                        value = getattr(self, field.name)
733                        # if it is a serializable dataclass, serialize it
734                        if isinstance(value, SerializableDataclass):
735                            value = value.serialize()
736                        # if the value has a serialization function, use that
737                        if hasattr(value, "serialize") and callable(value.serialize):
738                            value = value.serialize()
739                        # if the field has a serialization function, use that
740                        # it would be nice to be able to override a class's `.serialize()`, but that could lead to some inconsistencies!
741                        elif field.serialization_fn:
742                            value = field.serialization_fn(value)
743
744                        # store the value in the result
745                        result[field.name] = value
746                    except Exception as e:
747                        raise FieldSerializationError(
748                            "\n".join(
749                                [
750                                    f"Error serializing field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__}",
751                                    f"{field = }",
752                                    f"{value = }",
753                                    f"{self = }",
754                                ]
755                            )
756                        ) from e
757
758            # store each property if we can get it
759            for prop in self._properties_to_serialize:
760                if hasattr(cls, prop):
761                    value = getattr(self, prop)
762                    result[prop] = value
763                else:
764                    raise AttributeError(
765                        f"Cannot serialize property '{prop}' on class {self.__class__.__module__}.{self.__class__.__name__}"
766                        + f"but it is in {self._properties_to_serialize = }"
767                        + f"\n{self = }"
768                    )
769
770            return result

returns the class as a dict, implemented by using @serializable_dataclass decorator

@classmethod
def load(cls, data: Union[dict[str, Any], ~T]) -> Type[~T]:
777        @classmethod  # type: ignore[misc]
778        def load(cls, data: dict[str, Any] | T) -> Type[T]:
779            # HACK: this is kind of ugly, but it fixes a lot of issues for when we do recursive loading with ZANJ
780            if isinstance(data, cls):
781                return data
782
783            assert isinstance(
784                data, typing.Mapping
785            ), f"When loading {cls.__name__ = } expected a Mapping, but got {type(data) = }:\n{data = }"
786
787            cls_type_hints: dict[str, Any] = get_cls_type_hints(cls)
788
789            # initialize dict for keeping what we will pass to the constructor
790            ctor_kwargs: dict[str, Any] = dict()
791
792            # iterate over the fields of the class
793            for field in dataclasses.fields(cls):
794                # check if the field is a SerializableField
795                assert isinstance(
796                    field, SerializableField
797                ), f"Field '{field.name}' on class {cls.__name__} is not a SerializableField, but a {type(field)}. this state should be inaccessible, please report this bug!\nhttps://github.com/mivanit/muutils/issues/new"
798
799                # check if the field is in the data and if it should be initialized
800                if (field.name in data) and field.init:
801                    # get the value, we will be processing it
802                    value: Any = data[field.name]
803
804                    # get the type hint for the field
805                    field_type_hint: Any = cls_type_hints.get(field.name, None)
806
807                    # we rely on the init of `SerializableField` to check that only one of `loading_fn` and `deserialize_fn` is set
808                    if field.deserialize_fn:
809                        # if it has a deserialization function, use that
810                        value = field.deserialize_fn(value)
811                    elif field.loading_fn:
812                        # if it has a loading function, use that
813                        value = field.loading_fn(data)
814                    elif (
815                        field_type_hint is not None
816                        and hasattr(field_type_hint, "load")
817                        and callable(field_type_hint.load)
818                    ):
819                        # if no loading function but has a type hint with a load method, use that
820                        if isinstance(value, dict):
821                            value = field_type_hint.load(value)
822                        else:
823                            raise FieldLoadingError(
824                                f"Cannot load value into {field_type_hint}, expected {type(value) = } to be a dict\n{value = }"
825                            )
826                    else:
827                        # assume no loading needs to happen, keep `value` as-is
828                        pass
829
830                    # store the value in the constructor kwargs
831                    ctor_kwargs[field.name] = value
832
833            # create a new instance of the class with the constructor kwargs
834            output: cls = cls(**ctor_kwargs)
835
836            # validate the types of the fields if needed
837            if on_typecheck_mismatch != ErrorMode.IGNORE:
838                fields_valid: dict[str, bool] = (
839                    SerializableDataclass__validate_fields_types__dict(
840                        output,
841                        on_typecheck_error=on_typecheck_error,
842                    )
843                )
844
845                # if there are any fields that are not valid, raise an error
846                if not all(fields_valid.values()):
847                    msg: str = (
848                        f"Type mismatch in fields of {cls.__name__}:\n"
849                        + "\n".join(
850                            [
851                                f"{k}:\texpected {cls_type_hints[k] = }, but got value {getattr(output, k) = }, {type(getattr(output, k)) = }"
852                                for k, v in fields_valid.items()
853                                if not v
854                            ]
855                        )
856                    )
857
858                    on_typecheck_mismatch.process(
859                        msg, except_cls=FieldTypeMismatchError
860                    )
861
862            # return the new instance
863            return output

takes in an appropriately structured dict and returns an instance of the class, implemented by using @serializable_dataclass decorator

def validate_fields_types( self: muutils.json_serialize.serializable_dataclass.SerializableDataclass, on_typecheck_error: muutils.errormode.ErrorMode = ErrorMode.Except) -> bool:
283def SerializableDataclass__validate_fields_types(
284    self: SerializableDataclass,
285    on_typecheck_error: ErrorMode = _DEFAULT_ON_TYPECHECK_ERROR,
286) -> bool:
287    """validate the types of all the fields on a `SerializableDataclass`. calls `SerializableDataclass__validate_field_type` for each field"""
288    return all(
289        SerializableDataclass__validate_fields_types__dict(
290            self, on_typecheck_error=on_typecheck_error
291        ).values()
292    )

validate the types of all the fields on a SerializableDataclass. calls SerializableDataclass__validate_field_type for each field

Inherited Members
maze_dataset.dataset.dataset.GPTDatasetConfig
name
seq_len_min
seq_len_max
seed
applied_filters
muutils.json_serialize.serializable_dataclass.SerializableDataclass
validate_field_type
diff
update_from_nested_dict
@serializable_dataclass(frozen=True, kw_only=True)
class TargetedLatticeMaze(maze_dataset.LatticeMaze):
1176@serializable_dataclass(frozen=True, kw_only=True)
1177class TargetedLatticeMaze(LatticeMaze):  # type: ignore[misc]
1178	"""A LatticeMaze with a start and end position"""
1179
1180	# this jank is so that SolvedMaze can inherit from this class without needing arguments for start_pos and end_pos
1181	# type ignore here because even though its a kw-only dataclass,
1182	# mypy doesn't like that non-default arguments are after default arguments
1183	start_pos: Coord = serializable_field(  # type: ignore[misc]
1184		assert_type=False,
1185	)
1186	end_pos: Coord = serializable_field(  # type: ignore[misc]
1187		assert_type=False,
1188	)
1189
1190	def __post_init__(self) -> None:
1191		"post init converts start and end pos to numpy arrays, checks they exist and are in bounds"
1192		# make things numpy arrays (very jank to override frozen dataclass)
1193		self.__dict__["start_pos"] = np.array(self.start_pos)
1194		self.__dict__["end_pos"] = np.array(self.end_pos)
1195		assert self.start_pos is not None
1196		assert self.end_pos is not None
1197		# check that start and end are in bounds
1198		if (
1199			self.start_pos[0] >= self.grid_shape[0]
1200			or self.start_pos[1] >= self.grid_shape[1]
1201		):
1202			err_msg: str = f"start_pos {self.start_pos} is out of bounds for grid shape {self.grid_shape}"
1203			raise ValueError(
1204				err_msg,
1205			)
1206		if (
1207			self.end_pos[0] >= self.grid_shape[0]
1208			or self.end_pos[1] >= self.grid_shape[1]
1209		):
1210			err_msg = f"end_pos {self.end_pos = } is out of bounds for grid shape {self.grid_shape = }"
1211			raise ValueError(
1212				err_msg,
1213			)
1214
1215	def __eq__(self, other: object) -> bool:
1216		"check equality, calls parent class equality check"
1217		return super().__eq__(other)
1218
1219	def _get_start_pos_tokens(self) -> list[str | CoordTup]:
1220		return [
1221			SPECIAL_TOKENS.ORIGIN_START,
1222			tuple(self.start_pos),
1223			SPECIAL_TOKENS.ORIGIN_END,
1224		]
1225
1226	def get_start_pos_tokens(self) -> list[str | CoordTup]:
1227		"(deprecated!) return the start position as a list of tokens"
1228		warnings.warn(
1229			"`TargetedLatticeMaze.get_start_pos_tokens` will be removed from the public API in a future release.",
1230			TokenizerDeprecationWarning,
1231		)
1232		return self._get_start_pos_tokens()
1233
1234	def _get_end_pos_tokens(self) -> list[str | CoordTup]:
1235		return [
1236			SPECIAL_TOKENS.TARGET_START,
1237			tuple(self.end_pos),
1238			SPECIAL_TOKENS.TARGET_END,
1239		]
1240
1241	def get_end_pos_tokens(self) -> list[str | CoordTup]:
1242		"(deprecated!) return the end position as a list of tokens"
1243		warnings.warn(
1244			"`TargetedLatticeMaze.get_end_pos_tokens` will be removed from the public API in a future release.",
1245			TokenizerDeprecationWarning,
1246		)
1247		return self._get_end_pos_tokens()
1248
1249	@classmethod
1250	def from_lattice_maze(
1251		cls,
1252		lattice_maze: LatticeMaze,
1253		start_pos: Coord | CoordTup,
1254		end_pos: Coord | CoordTup,
1255	) -> "TargetedLatticeMaze":
1256		"get a `TargetedLatticeMaze` from a `LatticeMaze` by specifying start and end positions"
1257		return cls(
1258			connection_list=lattice_maze.connection_list,
1259			start_pos=np.array(start_pos),
1260			end_pos=np.array(end_pos),
1261			generation_meta=lattice_maze.generation_meta,
1262		)

A LatticeMaze with a start and end position

TargetedLatticeMaze( *, connection_list: jaxtyping.Bool[ndarray, 'lattice_dim=2 row col'], generation_meta: dict | None = None, start_pos: jaxtyping.Int8[ndarray, 'row_col=2'], end_pos: jaxtyping.Int8[ndarray, 'row_col=2'])
start_pos: jaxtyping.Int8[ndarray, 'row_col=2']
end_pos: jaxtyping.Int8[ndarray, 'row_col=2']
def get_start_pos_tokens(self) -> list[str | tuple[int, int]]:
1226	def get_start_pos_tokens(self) -> list[str | CoordTup]:
1227		"(deprecated!) return the start position as a list of tokens"
1228		warnings.warn(
1229			"`TargetedLatticeMaze.get_start_pos_tokens` will be removed from the public API in a future release.",
1230			TokenizerDeprecationWarning,
1231		)
1232		return self._get_start_pos_tokens()

(deprecated!) return the start position as a list of tokens

def get_end_pos_tokens(self) -> list[str | tuple[int, int]]:
1241	def get_end_pos_tokens(self) -> list[str | CoordTup]:
1242		"(deprecated!) return the end position as a list of tokens"
1243		warnings.warn(
1244			"`TargetedLatticeMaze.get_end_pos_tokens` will be removed from the public API in a future release.",
1245			TokenizerDeprecationWarning,
1246		)
1247		return self._get_end_pos_tokens()

(deprecated!) return the end position as a list of tokens

@classmethod
def from_lattice_maze( cls, lattice_maze: LatticeMaze, start_pos: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], end_pos: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int]) -> TargetedLatticeMaze:
1249	@classmethod
1250	def from_lattice_maze(
1251		cls,
1252		lattice_maze: LatticeMaze,
1253		start_pos: Coord | CoordTup,
1254		end_pos: Coord | CoordTup,
1255	) -> "TargetedLatticeMaze":
1256		"get a `TargetedLatticeMaze` from a `LatticeMaze` by specifying start and end positions"
1257		return cls(
1258			connection_list=lattice_maze.connection_list,
1259			start_pos=np.array(start_pos),
1260			end_pos=np.array(end_pos),
1261			generation_meta=lattice_maze.generation_meta,
1262		)

get a TargetedLatticeMaze from a LatticeMaze by specifying start and end positions

def serialize(self) -> dict[str, typing.Any]:
714        def serialize(self) -> dict[str, Any]:
715            result: dict[str, Any] = {
716                _FORMAT_KEY: f"{self.__class__.__name__}(SerializableDataclass)"
717            }
718            # for each field in the class
719            for field in dataclasses.fields(self):  # type: ignore[arg-type]
720                # need it to be our special SerializableField
721                if not isinstance(field, SerializableField):
722                    raise NotSerializableFieldException(
723                        f"Field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__} is not a `SerializableField`, "
724                        f"but a {type(field)} "
725                        "this state should be inaccessible, please report this bug!"
726                    )
727
728                # try to save it
729                if field.serialize:
730                    try:
731                        # get the val
732                        value = getattr(self, field.name)
733                        # if it is a serializable dataclass, serialize it
734                        if isinstance(value, SerializableDataclass):
735                            value = value.serialize()
736                        # if the value has a serialization function, use that
737                        if hasattr(value, "serialize") and callable(value.serialize):
738                            value = value.serialize()
739                        # if the field has a serialization function, use that
740                        # it would be nice to be able to override a class's `.serialize()`, but that could lead to some inconsistencies!
741                        elif field.serialization_fn:
742                            value = field.serialization_fn(value)
743
744                        # store the value in the result
745                        result[field.name] = value
746                    except Exception as e:
747                        raise FieldSerializationError(
748                            "\n".join(
749                                [
750                                    f"Error serializing field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__}",
751                                    f"{field = }",
752                                    f"{value = }",
753                                    f"{self = }",
754                                ]
755                            )
756                        ) from e
757
758            # store each property if we can get it
759            for prop in self._properties_to_serialize:
760                if hasattr(cls, prop):
761                    value = getattr(self, prop)
762                    result[prop] = value
763                else:
764                    raise AttributeError(
765                        f"Cannot serialize property '{prop}' on class {self.__class__.__module__}.{self.__class__.__name__}"
766                        + f"but it is in {self._properties_to_serialize = }"
767                        + f"\n{self = }"
768                    )
769
770            return result

returns the class as a dict, implemented by using @serializable_dataclass decorator

@classmethod
def load(cls, data: Union[dict[str, Any], ~T]) -> Type[~T]:
777        @classmethod  # type: ignore[misc]
778        def load(cls, data: dict[str, Any] | T) -> Type[T]:
779            # HACK: this is kind of ugly, but it fixes a lot of issues for when we do recursive loading with ZANJ
780            if isinstance(data, cls):
781                return data
782
783            assert isinstance(
784                data, typing.Mapping
785            ), f"When loading {cls.__name__ = } expected a Mapping, but got {type(data) = }:\n{data = }"
786
787            cls_type_hints: dict[str, Any] = get_cls_type_hints(cls)
788
789            # initialize dict for keeping what we will pass to the constructor
790            ctor_kwargs: dict[str, Any] = dict()
791
792            # iterate over the fields of the class
793            for field in dataclasses.fields(cls):
794                # check if the field is a SerializableField
795                assert isinstance(
796                    field, SerializableField
797                ), f"Field '{field.name}' on class {cls.__name__} is not a SerializableField, but a {type(field)}. this state should be inaccessible, please report this bug!\nhttps://github.com/mivanit/muutils/issues/new"
798
799                # check if the field is in the data and if it should be initialized
800                if (field.name in data) and field.init:
801                    # get the value, we will be processing it
802                    value: Any = data[field.name]
803
804                    # get the type hint for the field
805                    field_type_hint: Any = cls_type_hints.get(field.name, None)
806
807                    # we rely on the init of `SerializableField` to check that only one of `loading_fn` and `deserialize_fn` is set
808                    if field.deserialize_fn:
809                        # if it has a deserialization function, use that
810                        value = field.deserialize_fn(value)
811                    elif field.loading_fn:
812                        # if it has a loading function, use that
813                        value = field.loading_fn(data)
814                    elif (
815                        field_type_hint is not None
816                        and hasattr(field_type_hint, "load")
817                        and callable(field_type_hint.load)
818                    ):
819                        # if no loading function but has a type hint with a load method, use that
820                        if isinstance(value, dict):
821                            value = field_type_hint.load(value)
822                        else:
823                            raise FieldLoadingError(
824                                f"Cannot load value into {field_type_hint}, expected {type(value) = } to be a dict\n{value = }"
825                            )
826                    else:
827                        # assume no loading needs to happen, keep `value` as-is
828                        pass
829
830                    # store the value in the constructor kwargs
831                    ctor_kwargs[field.name] = value
832
833            # create a new instance of the class with the constructor kwargs
834            output: cls = cls(**ctor_kwargs)
835
836            # validate the types of the fields if needed
837            if on_typecheck_mismatch != ErrorMode.IGNORE:
838                fields_valid: dict[str, bool] = (
839                    SerializableDataclass__validate_fields_types__dict(
840                        output,
841                        on_typecheck_error=on_typecheck_error,
842                    )
843                )
844
845                # if there are any fields that are not valid, raise an error
846                if not all(fields_valid.values()):
847                    msg: str = (
848                        f"Type mismatch in fields of {cls.__name__}:\n"
849                        + "\n".join(
850                            [
851                                f"{k}:\texpected {cls_type_hints[k] = }, but got value {getattr(output, k) = }, {type(getattr(output, k)) = }"
852                                for k, v in fields_valid.items()
853                                if not v
854                            ]
855                        )
856                    )
857
858                    on_typecheck_mismatch.process(
859                        msg, except_cls=FieldTypeMismatchError
860                    )
861
862            # return the new instance
863            return output

takes in an appropriately structured dict and returns an instance of the class, implemented by using @serializable_dataclass decorator

def validate_fields_types( self: muutils.json_serialize.serializable_dataclass.SerializableDataclass, on_typecheck_error: muutils.errormode.ErrorMode = ErrorMode.Except) -> bool:
283def SerializableDataclass__validate_fields_types(
284    self: SerializableDataclass,
285    on_typecheck_error: ErrorMode = _DEFAULT_ON_TYPECHECK_ERROR,
286) -> bool:
287    """validate the types of all the fields on a `SerializableDataclass`. calls `SerializableDataclass__validate_field_type` for each field"""
288    return all(
289        SerializableDataclass__validate_fields_types__dict(
290            self, on_typecheck_error=on_typecheck_error
291        ).values()
292    )

validate the types of all the fields on a SerializableDataclass. calls SerializableDataclass__validate_field_type for each field

@serializable_dataclass(frozen=True, kw_only=True, properties_to_serialize=['lattice_dim', 'generation_meta'])
class LatticeMaze(muutils.json_serialize.serializable_dataclass.SerializableDataclass):
 120@serializable_dataclass(
 121	frozen=True,
 122	kw_only=True,
 123	properties_to_serialize=["lattice_dim", "generation_meta"],
 124)
 125class LatticeMaze(SerializableDataclass):
 126	"""lattice maze (nodes on a lattice, connections only to neighboring nodes)
 127
 128	Connection List represents which nodes (N) are connected in each direction.
 129
 130	First and second elements represent rightward and downward connections,
 131	respectively.
 132
 133	Example:
 134		Connection list:
 135			[
 136				[ # down
 137					[F T],
 138					[F F]
 139				],
 140				[ # right
 141					[T F],
 142					[T F]
 143				]
 144			]
 145
 146		Nodes with connections
 147			N T N F
 148			F	T
 149			N T N F
 150			F	F
 151
 152		Graph:
 153			N - N
 154				|
 155			N - N
 156
 157	Note: the bottom row connections going down, and the
 158	right-hand connections going right, will always be False.
 159
 160	"""
 161
 162	connection_list: ConnectionList
 163	generation_meta: dict | None = serializable_field(default=None, compare=False)
 164
 165	lattice_dim = property(lambda self: self.connection_list.shape[0])
 166	grid_shape = property(lambda self: self.connection_list.shape[1:])
 167	n_connections = property(lambda self: self.connection_list.sum())
 168
 169	@property
 170	def grid_n(self) -> int:
 171		"grid size as int, raises `AssertionError` if not square"
 172		assert self.grid_shape[0] == self.grid_shape[1], "only square mazes supported"
 173		return self.grid_shape[0]
 174
 175	# ============================================================
 176	# basic methods
 177	# ============================================================
 178
 179	def __eq__(self, other: object) -> bool:
 180		"equality check calls super"
 181		return super().__eq__(other)
 182
 183	@staticmethod
 184	def heuristic(a: CoordTup, b: CoordTup) -> float:
 185		"""return manhattan distance between two points"""
 186		return np.abs(a[0] - b[0]) + np.abs(a[1] - b[1])
 187
 188	def __hash__(self) -> int:
 189		"""hash the connection list by converting connection list to bytes"""
 190		return hash(self.connection_list.tobytes())
 191
 192	def nodes_connected(self, a: Coord, b: Coord, /) -> bool:
 193		"""returns whether two nodes are connected"""
 194		delta: Coord = b - a
 195		if np.abs(delta).sum() != 1:
 196			# return false if not even adjacent
 197			return False
 198		else:
 199			# test for wall
 200			dim: int = int(np.argmax(np.abs(delta)))
 201			clist_node: Coord = a if (delta.sum() > 0) else b
 202			return self.connection_list[dim, clist_node[0], clist_node[1]]
 203
 204	def is_valid_path(self, path: CoordArray, empty_is_valid: bool = False) -> bool:
 205		"""check if a path is valid"""
 206		# check path is not empty
 207		if len(path) == 0:
 208			return empty_is_valid
 209
 210		# check all coords in bounds of maze
 211		if not np.all((path >= 0) & (path < self.grid_shape)):
 212			return False
 213
 214		# check all nodes connected
 215		for i in range(len(path) - 1):
 216			if not self.nodes_connected(path[i], path[i + 1]):
 217				return False
 218		return True
 219
 220	def coord_degrees(self) -> Int8[np.ndarray, "row col"]:
 221		"""Returns an array with the connectivity degree of each coord.
 222
 223		I.e., how many neighbors each coord has.
 224		"""
 225		int_conn: Int8[np.ndarray, "lattice_dim=2 row col"] = (
 226			self.connection_list.astype(np.int8)
 227		)
 228		degrees: Int8[np.ndarray, "row col"] = np.sum(
 229			int_conn,
 230			axis=0,
 231		)  # Connections to east and south
 232		degrees[:, 1:] += int_conn[1, :, :-1]  # Connections to west
 233		degrees[1:, :] += int_conn[0, :-1, :]  # Connections to north
 234		return degrees
 235
 236	def get_coord_neighbors(self, c: Coord | CoordTup) -> CoordArray:
 237		"""Returns an array of the neighboring, connected coords of `c`."""
 238		c = np.array(c)  # type: ignore[assignment]
 239		neighbors: list[Coord] = [
 240			neighbor
 241			for neighbor in (c + NEIGHBORS_MASK)
 242			if (
 243				(0 <= neighbor[0] < self.grid_shape[0])  # in x bounds
 244				and (0 <= neighbor[1] < self.grid_shape[1])  # in y bounds
 245				and self.nodes_connected(c, neighbor)  # connected
 246			)
 247		]
 248
 249		output: CoordArray = np.array(neighbors)
 250		if len(neighbors) > 0:
 251			assert output.shape == (
 252				len(neighbors),
 253				2,
 254			), (
 255				f"invalid shape: {output.shape}, expected ({len(neighbors)}, 2))\n{c = }\n{neighbors = }\n{self.as_ascii()}"
 256			)
 257		return output
 258
 259	def gen_connected_component_from(self, c: Coord) -> CoordArray:
 260		"""return the connected component from a given coordinate"""
 261		# Stack for DFS
 262		stack: list[Coord] = [c]
 263
 264		# Set to store visited nodes
 265		visited: set[CoordTup] = set()
 266
 267		while stack:
 268			current_node: Coord = stack.pop()
 269			# this is fine since we know current_node is a coord and thus of length 2
 270			visited.add(tuple(current_node))  # type: ignore[arg-type]
 271
 272			# Get the neighbors of the current node
 273			neighbors = self.get_coord_neighbors(current_node)
 274
 275			# Iterate over neighbors
 276			for neighbor in neighbors:
 277				if tuple(neighbor) not in visited:
 278					stack.append(neighbor)
 279
 280		return np.array(list(visited))
 281
 282	def find_shortest_path(
 283		self,
 284		c_start: CoordTup | Coord,
 285		c_end: CoordTup | Coord,
 286	) -> CoordArray:
 287		"""find the shortest path between two coordinates, using A*"""
 288		c_start = tuple(c_start)  # type: ignore[assignment]
 289		c_end = tuple(c_end)  # type: ignore[assignment]
 290
 291		g_score: dict[CoordTup, float] = (
 292			dict()
 293		)  # cost of cheapest path to node from start currently known
 294		f_score: dict[CoordTup, float] = {
 295			c_start: 0.0,
 296		}  # estimated total cost of path thru a node: f_score[c] := g_score[c] + heuristic(c, c_end)
 297
 298		# init
 299		g_score[c_start] = 0.0
 300		g_score[c_start] = self.heuristic(c_start, c_end)
 301
 302		closed_vtx: set[CoordTup] = set()  # nodes already evaluated
 303		# nodes to be evaluated
 304		# we need a set of the tuples, dont place the ints in the set
 305		open_vtx: set[CoordTup] = set([c_start])  # noqa: C405
 306		source: dict[CoordTup, CoordTup] = (
 307			dict()
 308		)  # node immediately preceding each node in the path (currently known shortest path)
 309
 310		while open_vtx:
 311			# get lowest f_score node
 312			# mypy cant tell that c is of length 2
 313			c_current: CoordTup = min(open_vtx, key=lambda c: f_score[tuple(c)])  # type: ignore[index]
 314			# f_current: float = f_score[c_current]
 315
 316			# check if goal is reached
 317			if c_end == c_current:
 318				path: list[CoordTup] = [c_current]
 319				p_current: CoordTup = c_current
 320				while p_current in source:
 321					p_current = source[p_current]
 322					path.append(p_current)
 323				# ----------------------------------------------------------------------
 324				# this is the only return statement
 325				return np.array(path[::-1])
 326				# ----------------------------------------------------------------------
 327
 328			# close current node
 329			closed_vtx.add(c_current)
 330			open_vtx.remove(c_current)
 331
 332			# update g_score of neighbors
 333			_np_neighbor: Coord
 334			for _np_neighbor in self.get_coord_neighbors(c_current):
 335				neighbor: CoordTup = tuple(_np_neighbor)
 336
 337				if neighbor in closed_vtx:
 338					# already checked
 339					continue
 340				g_temp: float = g_score[c_current] + 1  # always 1 for maze neighbors
 341
 342				if neighbor not in open_vtx:
 343					# found new vtx, so add
 344					open_vtx.add(neighbor)
 345
 346				elif g_temp >= g_score[neighbor]:
 347					# if already knew about this one, but current g_score is worse, skip
 348					continue
 349
 350				# store g_score and source
 351				source[neighbor] = c_current
 352				g_score[neighbor] = g_temp
 353				f_score[neighbor] = g_score[neighbor] + self.heuristic(neighbor, c_end)
 354
 355		raise ValueError(
 356			"A solution could not be found!",
 357			f"{c_start = }, {c_end = }",
 358			self.as_ascii(),
 359		)
 360
 361	def get_nodes(self) -> CoordArray:
 362		"""return a list of all nodes in the maze"""
 363		rows: Int[np.ndarray, "x y"]
 364		cols: Int[np.ndarray, "x y"]
 365		rows, cols = np.meshgrid(
 366			range(self.grid_shape[0]),
 367			range(self.grid_shape[1]),
 368			indexing="ij",
 369		)
 370		nodes: CoordArray = np.vstack((rows.ravel(), cols.ravel())).T
 371		return nodes
 372
 373	def get_connected_component(self) -> CoordArray:
 374		"""get the largest (and assumed only nonsingular) connected component of the maze
 375
 376		TODO: other connected components?
 377		"""
 378		if (self.generation_meta is None) or (
 379			self.generation_meta.get("fully_connected", False)
 380		):
 381			# for fully connected case, pick any two positions
 382			return self.get_nodes()
 383		else:
 384			# if metadata provided, use visited cells
 385			visited_cells: set[CoordTup] | None = self.generation_meta.get(
 386				"visited_cells",
 387				None,
 388			)
 389			if visited_cells is None:
 390				# TODO: dynamically generate visited_cells?
 391				err_msg: str = f"a maze which is not marked as fully connected must have a visited_cells field in its generation_meta: {self.generation_meta}\n{self}\n{self.as_ascii()}"
 392				raise ValueError(
 393					err_msg,
 394				)
 395			visited_cells_np: Int[np.ndarray, "N 2"] = np.array(list(visited_cells))
 396			return visited_cells_np
 397
 398	@typing.overload
 399	def generate_random_path(
 400		self,
 401		allowed_start: CoordList | None = None,
 402		allowed_end: CoordList | None = None,
 403		deadend_start: bool = False,
 404		deadend_end: bool = False,
 405		endpoints_not_equal: bool = False,
 406		except_on_no_valid_endpoint: typing.Literal[True] = True,
 407	) -> CoordArray: ...
 408	@typing.overload
 409	def generate_random_path(
 410		self,
 411		allowed_start: CoordList | None = None,
 412		allowed_end: CoordList | None = None,
 413		deadend_start: bool = False,
 414		deadend_end: bool = False,
 415		endpoints_not_equal: bool = False,
 416		except_on_no_valid_endpoint: typing.Literal[False] = False,
 417	) -> typing.Optional[CoordArray]: ...
 418	def generate_random_path(  # noqa: C901
 419		self,
 420		allowed_start: CoordList | None = None,
 421		allowed_end: CoordList | None = None,
 422		deadend_start: bool = False,
 423		deadend_end: bool = False,
 424		endpoints_not_equal: bool = False,
 425		except_on_no_valid_endpoint: bool = True,
 426	) -> typing.Optional[CoordArray]:
 427		"""return a path between randomly chosen start and end nodes within the connected component
 428
 429		Note that setting special conditions on start and end positions might cause the same position to be selected as both start and end.
 430
 431		# Parameters:
 432		- `allowed_start : CoordList | None`
 433			a list of allowed start positions. If `None`, any position in the connected component is allowed
 434			(defaults to `None`)
 435		- `allowed_end : CoordList | None`
 436			a list of allowed end positions. If `None`, any position in the connected component is allowed
 437			(defaults to `None`)
 438		- `deadend_start : bool`
 439			whether to ***force*** the start position to be a deadend (defaults to `False`)
 440			(defaults to `False`)
 441		- `deadend_end : bool`
 442			whether to ***force*** the end position to be a deadend (defaults to `False`)
 443			(defaults to `False`)
 444		- `endpoints_not_equal : bool`
 445			whether to ensure tha the start and end point are not the same
 446			(defaults to `False`)
 447		- `except_on_no_valid_endpoint : bool`
 448			whether to raise an error if no valid start or end positions are found
 449			if this is `False`, the function might return `None` and this must be handled by the caller
 450			(defaults to `True`)
 451
 452		# Returns:
 453		- `CoordArray`
 454			a path between the selected start and end positions
 455
 456		# Raises:
 457		- `NoValidEndpointException` : if no valid start or end positions are found, and `except_on_no_valid_endpoint` is `True`
 458		"""
 459		# we can't create a "path" in a single-node maze
 460		assert self.grid_shape[0] > 1 and self.grid_shape[1] > 1, (  # noqa: PT018
 461			f"can't create path in single-node maze: {self.as_ascii()}"
 462		)
 463
 464		# get connected component
 465		connected_component: CoordArray = self.get_connected_component()
 466
 467		# initialize start and end positions
 468		positions: Int[np.int8, "2 2"]
 469
 470		# if no special conditions on start and end positions
 471		if (allowed_start, allowed_end, deadend_start, deadend_end) == (
 472			None,
 473			None,
 474			False,
 475			False,
 476		):
 477			try:
 478				positions = connected_component[  # type: ignore[assignment]
 479					np.random.choice(
 480						len(connected_component),
 481						size=2,
 482						replace=False,
 483					)
 484				]
 485			except ValueError as e:
 486				if except_on_no_valid_endpoint:
 487					err_msg: str = f"No valid start or end positions found because we could not sample from {connected_component = }"
 488					raise NoValidEndpointException(
 489						err_msg,
 490					) from e
 491				return None
 492
 493			return self.find_shortest_path(positions[0], positions[1])  # type: ignore[index]
 494
 495		# handle special conditions
 496		connected_component_set: set[CoordTup] = set(map(tuple, connected_component))
 497		# copy connected component set
 498		allowed_start_set: set[CoordTup] = connected_component_set.copy()
 499		allowed_end_set: set[CoordTup] = connected_component_set.copy()
 500
 501		# filter by explicitly allowed start and end positions
 502		# '# type: ignore[assignment]' here because the returned tuple can be of any length
 503		if allowed_start is not None:
 504			allowed_start_set = set(map(tuple, allowed_start)) & connected_component_set  # type: ignore[assignment]
 505
 506		if allowed_end is not None:
 507			allowed_end_set = set(map(tuple, allowed_end)) & connected_component_set  # type: ignore[assignment]
 508
 509		# filter by forcing deadends
 510		if deadend_start:
 511			allowed_start_set = set(
 512				filter(
 513					lambda x: len(self.get_coord_neighbors(x)) == 1,
 514					allowed_start_set,
 515				),
 516			)
 517
 518		if deadend_end:
 519			allowed_end_set = set(
 520				filter(
 521					lambda x: len(self.get_coord_neighbors(x)) == 1,
 522					allowed_end_set,
 523				),
 524			)
 525
 526		# check we have valid positions
 527		if len(allowed_start_set) == 0 or len(allowed_end_set) == 0:
 528			if except_on_no_valid_endpoint:
 529				err_msg = f"No valid start (or end?) positions found: {allowed_start_set = }, {allowed_end_set = }"
 530				raise NoValidEndpointException(
 531					err_msg,
 532				)
 533			return None
 534
 535		# randomly select start and end positions
 536		try:
 537			# ignore assignment here since `tuple()` returns a tuple of any length, but we know it will be ok
 538			start_pos: CoordTup = tuple(  # type: ignore[assignment]
 539				list(allowed_start_set)[np.random.randint(0, len(allowed_start_set))],
 540			)
 541			if endpoints_not_equal:
 542				# remove start position from end positions
 543				allowed_end_set.discard(start_pos)
 544			end_pos: CoordTup = tuple(  # type: ignore[assignment]
 545				list(allowed_end_set)[np.random.randint(0, len(allowed_end_set))],
 546			)
 547		except ValueError as e:
 548			if except_on_no_valid_endpoint:
 549				err_msg = f"No valid start or end positions found, maybe can't find an endpoint after we removed the start point: {allowed_start_set = }, {allowed_end_set = }"
 550				raise NoValidEndpointException(
 551					err_msg,
 552				) from e
 553			return None
 554
 555		return self.find_shortest_path(start_pos, end_pos)
 556
 557	# ============================================================
 558	# to and from adjacency list
 559	# ============================================================
 560	def as_adj_list(
 561		self,
 562		shuffle_d0: bool = True,
 563		shuffle_d1: bool = True,
 564	) -> Int8[np.ndarray, "conn start_end coord"]:
 565		"""return the maze as an adjacency list, wraps `maze_dataset.token_utils.connection_list_to_adj_list`"""
 566		return connection_list_to_adj_list(self.connection_list, shuffle_d0, shuffle_d1)
 567
 568	@classmethod
 569	def from_adj_list(
 570		cls,
 571		adj_list: Int8[np.ndarray, "conn start_end coord"],
 572	) -> "LatticeMaze":
 573		"""create a LatticeMaze from a list of connections
 574
 575		> [!NOTE]
 576		> This has only been tested for square mazes. Might need to change some things if rectangular mazes are needed.
 577		"""
 578		# this is where it would probably break for rectangular mazes
 579		grid_n: int = adj_list.max() + 1
 580
 581		connection_list: ConnectionList = np.zeros(
 582			(2, grid_n, grid_n),
 583			dtype=np.bool_,
 584		)
 585
 586		for c_start, c_end in adj_list:
 587			# check that exactly 1 coordinate matches
 588			if (c_start == c_end).sum() != 1:
 589				raise ValueError("invalid connection")
 590
 591			# get the direction
 592			d: int = (c_start != c_end).argmax()
 593
 594			x: int
 595			y: int
 596			# pick whichever has the lesser value in the direction `d`
 597			if c_start[d] < c_end[d]:
 598				x, y = c_start
 599			else:
 600				x, y = c_end
 601
 602			connection_list[d, x, y] = True
 603
 604		return LatticeMaze(
 605			connection_list=connection_list,
 606		)
 607
 608	def as_adj_list_tokens(self) -> list[str | CoordTup]:
 609		"""(deprecated!) turn the maze into adjacency list tokens, use `MazeTokenizerModular` instead"""
 610		warnings.warn(
 611			"`LatticeMaze.as_adj_list_tokens` will be removed from the public API in a future release.",
 612			TokenizerDeprecationWarning,
 613		)
 614		return [
 615			SPECIAL_TOKENS.ADJLIST_START,
 616			*chain.from_iterable(  # type: ignore[list-item]
 617				[
 618					[
 619						tuple(c_s),
 620						SPECIAL_TOKENS.CONNECTOR,
 621						tuple(c_e),
 622						SPECIAL_TOKENS.ADJACENCY_ENDLINE,
 623					]
 624					for c_s, c_e in self.as_adj_list()
 625				],
 626			),
 627			SPECIAL_TOKENS.ADJLIST_END,
 628		]
 629
 630	def _as_adj_list_tokens(self) -> list[str | CoordTup]:
 631		return [
 632			SPECIAL_TOKENS.ADJLIST_START,
 633			*chain.from_iterable(  # type: ignore[list-item]
 634				[
 635					[
 636						tuple(c_s),
 637						SPECIAL_TOKENS.CONNECTOR,
 638						tuple(c_e),
 639						SPECIAL_TOKENS.ADJACENCY_ENDLINE,
 640					]
 641					for c_s, c_e in self.as_adj_list()
 642				],
 643			),
 644			SPECIAL_TOKENS.ADJLIST_END,
 645		]
 646
 647	def _as_coords_and_special_AOTP(self) -> list[CoordTup | str]:
 648		"""turn the maze into adjacency list, origin, target, and solution -- keep coords as tuples"""
 649		output: list[CoordTup | str] = self._as_adj_list_tokens()
 650		# if getattr(self, "start_pos", None) is not None:
 651		if isinstance(self, TargetedLatticeMaze):
 652			output += self._get_start_pos_tokens()
 653		if isinstance(self, TargetedLatticeMaze):
 654			output += self._get_end_pos_tokens()
 655		if isinstance(self, SolvedMaze):
 656			output += self._get_solution_tokens()
 657		return output
 658
 659	def _as_tokens(
 660		self,
 661		maze_tokenizer: "MazeTokenizer | TokenizationMode",
 662	) -> list[str]:
 663		# type ignores here fine since we check the instance
 664		if isinstance_by_type_name(maze_tokenizer, "TokenizationMode"):
 665			maze_tokenizer = maze_tokenizer.to_legacy_tokenizer()  # type: ignore[union-attr]
 666		if (
 667			isinstance_by_type_name(maze_tokenizer, "MazeTokenizer")
 668			and maze_tokenizer.is_AOTP()  # type: ignore[union-attr]
 669		):
 670			coords_raw: list[CoordTup | str] = self._as_coords_and_special_AOTP()
 671			coords_processed: list[str] = maze_tokenizer.coords_to_strings(  # type: ignore[union-attr]
 672				coords=coords_raw,
 673				when_noncoord="include",
 674			)
 675			return coords_processed
 676		else:
 677			err_msg: str = f"Unsupported tokenizer type: {maze_tokenizer}"
 678			raise NotImplementedError(err_msg)
 679
 680	def as_tokens(
 681		self,
 682		maze_tokenizer: "MazeTokenizer | TokenizationMode | MazeTokenizerModular",
 683	) -> list[str]:
 684		"""serialize maze and solution to tokens"""
 685		if isinstance_by_type_name(maze_tokenizer, "MazeTokenizerModular"):
 686			return maze_tokenizer.to_tokens(self)  # type: ignore[union-attr]
 687		else:
 688			return self._as_tokens(maze_tokenizer)  # type: ignore[union-attr,arg-type]
 689
 690	@classmethod
 691	def _from_tokens_AOTP(
 692		cls,
 693		tokens: list[str],
 694		maze_tokenizer: "MazeTokenizer | MazeTokenizerModular",
 695	) -> "LatticeMaze | TargetedLatticeMaze | SolvedMaze":
 696		"""create a LatticeMaze from a list of tokens"""
 697		# figure out what input format
 698		# ========================================
 699		if tokens[0] == SPECIAL_TOKENS.ADJLIST_START:
 700			adj_list_tokens = get_adj_list_tokens(tokens)
 701		else:
 702			# If we're not getting a "complete" tokenized maze, assume it's just a the adjacency list tokens
 703			adj_list_tokens = tokens
 704			warnings.warn(
 705				"Assuming input is just adjacency list tokens, no special tokens found",
 706			)
 707
 708		# process edges for adjacency list
 709		# ========================================
 710		edges: list[list[str]] = list_split(
 711			adj_list_tokens,
 712			SPECIAL_TOKENS.ADJACENCY_ENDLINE,
 713		)
 714
 715		coordinates: list[tuple[CoordTup, CoordTup]] = list()
 716		for e in edges:
 717			# skip last endline
 718			if len(e) != 0:
 719				# convert to coords, split start and end
 720				e_coords: list[str | CoordTup] = maze_tokenizer.strings_to_coords(
 721					e,
 722					when_noncoord="include",
 723				)
 724				# this assertion depends on the tokenizer having exactly one token for the connector
 725				# which is also why we "include" above
 726				# the connector token is discarded below
 727				assert len(e_coords) == 3, f"invalid edge: {e = } {e_coords = }"  # noqa: PLR2004
 728				assert e_coords[1] == SPECIAL_TOKENS.CONNECTOR, (
 729					f"invalid edge: {e = } {e_coords = }"
 730				)
 731				e_coords_first: CoordTup = e_coords[0]  # type: ignore[assignment]
 732				e_coords_last: CoordTup = e_coords[-1]  # type: ignore[assignment]
 733				coordinates.append((e_coords_first, e_coords_last))
 734
 735		assert all(len(c) == DIM_2 for c in coordinates), (
 736			f"invalid coordinates: {coordinates = }"
 737		)
 738		adj_list: Int8[np.ndarray, "conn start_end coord"] = np.array(coordinates)
 739		assert tuple(adj_list.shape) == (
 740			len(coordinates),
 741			2,
 742			2,
 743		), f"invalid adj_list: {adj_list.shape = } {coordinates = }"
 744
 745		output_maze: LatticeMaze = cls.from_adj_list(adj_list)
 746
 747		# add start and end positions
 748		# ========================================
 749		is_targeted: bool = False
 750		if all(
 751			x in tokens
 752			for x in (
 753				SPECIAL_TOKENS.ORIGIN_START,
 754				SPECIAL_TOKENS.ORIGIN_END,
 755				SPECIAL_TOKENS.TARGET_START,
 756				SPECIAL_TOKENS.TARGET_END,
 757			)
 758		):
 759			start_pos_list: list[CoordTup] = maze_tokenizer.strings_to_coords(
 760				get_origin_tokens(tokens),
 761				when_noncoord="error",
 762			)
 763			end_pos_list: list[CoordTup] = maze_tokenizer.strings_to_coords(
 764				get_target_tokens(tokens),
 765				when_noncoord="error",
 766			)
 767			assert len(start_pos_list) == 1, (
 768				f"invalid start_pos_list: {start_pos_list = }"
 769			)
 770			assert len(end_pos_list) == 1, f"invalid end_pos_list: {end_pos_list = }"
 771
 772			start_pos: CoordTup = start_pos_list[0]
 773			end_pos: CoordTup = end_pos_list[0]
 774
 775			output_maze = TargetedLatticeMaze.from_lattice_maze(
 776				lattice_maze=output_maze,
 777				start_pos=start_pos,
 778				end_pos=end_pos,
 779			)
 780
 781			is_targeted = True
 782
 783		if all(
 784			x in tokens for x in (SPECIAL_TOKENS.PATH_START, SPECIAL_TOKENS.PATH_END)
 785		):
 786			assert is_targeted, "maze must be targeted to have a solution"
 787			solution: list[CoordTup] = maze_tokenizer.strings_to_coords(
 788				get_path_tokens(tokens, trim_end=True),
 789				when_noncoord="error",
 790			)
 791			output_maze = SolvedMaze.from_targeted_lattice_maze(
 792				# HACK: I think this is fine, but im not sure
 793				targeted_lattice_maze=output_maze,  # type: ignore[arg-type]
 794				solution=solution,
 795			)
 796
 797		return output_maze
 798
 799	# TODO: any way to get return type hinting working for this?
 800	@classmethod
 801	def from_tokens(
 802		cls,
 803		tokens: list[str],
 804		maze_tokenizer: "MazeTokenizer | TokenizationMode | MazeTokenizerModular",
 805	) -> "LatticeMaze | TargetedLatticeMaze | SolvedMaze":
 806		"""Constructs a maze from a tokenization.
 807
 808		Only legacy tokenizers and their `MazeTokenizerModular` analogs are supported.
 809		"""
 810		# HACK: type ignores here fine since we check the instance
 811		if isinstance_by_type_name(maze_tokenizer, "TokenizationMode"):
 812			maze_tokenizer = maze_tokenizer.to_legacy_tokenizer()  # type: ignore[union-attr]
 813		if (
 814			isinstance_by_type_name(maze_tokenizer, "MazeTokenizerModular")
 815			and not maze_tokenizer.is_legacy_equivalent()  # type: ignore[union-attr]
 816		):
 817			err_msg: str = f"Only legacy tokenizers and their exact `MazeTokenizerModular` analogs supported, not {maze_tokenizer}."
 818			raise NotImplementedError(
 819				err_msg,
 820			)
 821
 822		if isinstance(tokens, str):
 823			tokens = tokens.split()
 824
 825		if maze_tokenizer.is_AOTP():  # type: ignore[union-attr]
 826			return cls._from_tokens_AOTP(tokens, maze_tokenizer)  # type: ignore[arg-type]
 827		else:
 828			raise NotImplementedError("only AOTP tokenization is supported")
 829
 830	# ============================================================
 831	# to and from pixels
 832	# ============================================================
 833	def _as_pixels_bw(self) -> BinaryPixelGrid:
 834		assert self.lattice_dim == DIM_2, "only 2D mazes are supported"
 835		# Create an empty pixel grid with walls
 836		pixel_grid: Int[np.ndarray, "x y"] = np.full(
 837			(self.grid_shape[0] * 2 + 1, self.grid_shape[1] * 2 + 1),
 838			False,
 839			dtype=np.bool_,
 840		)
 841
 842		# Set white nodes
 843		pixel_grid[1::2, 1::2] = True
 844
 845		# Set white connections (downward)
 846		for i, row in enumerate(self.connection_list[0]):
 847			for j, connected in enumerate(row):
 848				if connected:
 849					pixel_grid[i * 2 + 2, j * 2 + 1] = True
 850
 851		# Set white connections (rightward)
 852		for i, row in enumerate(self.connection_list[1]):
 853			for j, connected in enumerate(row):
 854				if connected:
 855					pixel_grid[i * 2 + 1, j * 2 + 2] = True
 856
 857		return pixel_grid
 858
 859	def as_pixels(
 860		self,
 861		show_endpoints: bool = True,
 862		show_solution: bool = True,
 863	) -> PixelGrid:
 864		"""convert the maze to a pixel grid
 865
 866		- useful as a simpler way of plotting the maze than the more complex `MazePlot`
 867		- the same underlying representation as `as_ascii` but as an image
 868		- used in `RasterizedMazeDataset`, which mimics the mazes in https://github.com/aks2203/easy-to-hard-data
 869		"""
 870		# HACK: lots of `# type: ignore[attr-defined]` here since its defined for any `LatticeMaze`
 871		# but solution, start_pos, end_pos not always defined
 872		# but its fine since we explicitly check the type
 873		if show_solution and not show_endpoints:
 874			raise ValueError("show_solution=True requires show_endpoints=True")
 875		# convert original bool pixel grid to RGB
 876		pixel_grid_bw: BinaryPixelGrid = self._as_pixels_bw()
 877		pixel_grid: PixelGrid = np.full(
 878			(*pixel_grid_bw.shape, 3),
 879			PixelColors.WALL,
 880			dtype=np.uint8,
 881		)
 882		pixel_grid[pixel_grid_bw == True] = PixelColors.OPEN  # noqa: E712
 883
 884		if self.__class__ == LatticeMaze:
 885			return pixel_grid
 886
 887		# set endpoints for TargetedLatticeMaze
 888		if self.__class__ == TargetedLatticeMaze:
 889			if show_endpoints:
 890				pixel_grid[self.start_pos[0] * 2 + 1, self.start_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
 891					PixelColors.START
 892				)
 893				pixel_grid[self.end_pos[0] * 2 + 1, self.end_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
 894					PixelColors.END
 895				)
 896			return pixel_grid
 897
 898		# set solution -- we only reach this part if `self.__class__ == SolvedMaze`
 899		if show_solution:
 900			for coord in self.solution:  # type: ignore[attr-defined]
 901				pixel_grid[coord[0] * 2 + 1, coord[1] * 2 + 1] = PixelColors.PATH
 902
 903			# set pixels between coords
 904			for index, coord in enumerate(self.solution[:-1]):  # type: ignore[attr-defined]
 905				next_coord = self.solution[index + 1]  # type: ignore[attr-defined]
 906				# check they are adjacent using norm
 907				assert np.linalg.norm(np.array(coord) - np.array(next_coord)) == 1, (
 908					f"Coords {coord} and {next_coord} are not adjacent"
 909				)
 910				# set pixel between them
 911				pixel_grid[
 912					coord[0] * 2 + 1 + next_coord[0] - coord[0],
 913					coord[1] * 2 + 1 + next_coord[1] - coord[1],
 914				] = PixelColors.PATH
 915
 916			# set endpoints (again, since path would overwrite them)
 917			pixel_grid[self.start_pos[0] * 2 + 1, self.start_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
 918				PixelColors.START
 919			)
 920			pixel_grid[self.end_pos[0] * 2 + 1, self.end_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
 921				PixelColors.END
 922			)
 923
 924		return pixel_grid
 925
 926	@classmethod
 927	def _from_pixel_grid_bw(
 928		cls,
 929		pixel_grid: BinaryPixelGrid,
 930	) -> tuple[ConnectionList, tuple[int, int]]:
 931		grid_shape: tuple[int, int] = (
 932			pixel_grid.shape[0] // 2,
 933			pixel_grid.shape[1] // 2,
 934		)
 935		connection_list: ConnectionList = np.zeros((2, *grid_shape), dtype=np.bool_)
 936
 937		# Extract downward connections
 938		connection_list[0] = pixel_grid[2::2, 1::2]
 939
 940		# Extract rightward connections
 941		connection_list[1] = pixel_grid[1::2, 2::2]
 942
 943		return connection_list, grid_shape
 944
 945	@classmethod
 946	def _from_pixel_grid_with_positions(
 947		cls,
 948		pixel_grid: PixelGrid | BinaryPixelGrid,
 949		marked_positions: dict[str, RGB],
 950	) -> tuple[ConnectionList, tuple[int, int], dict[str, CoordArray]]:
 951		# Convert RGB pixel grid to Bool pixel grid
 952		# error: Incompatible types in assignment (expression has type
 953		# "numpy.bool[builtins.bool] | ndarray[tuple[int, ...], dtype[numpy.bool[builtins.bool]]]",
 954		# variable has type "ndarray[Any, Any]")  [assignment]
 955		pixel_grid_bw: BinaryPixelGrid = ~np.all(  # type: ignore[assignment]
 956			pixel_grid == PixelColors.WALL,
 957			axis=-1,
 958		)
 959		connection_list: ConnectionList
 960		grid_shape: tuple[int, int]
 961		connection_list, grid_shape = cls._from_pixel_grid_bw(pixel_grid_bw)
 962
 963		# Find any marked positions
 964		out_positions: dict[str, CoordArray] = dict()
 965		for key, color in marked_positions.items():
 966			pos_temp: Int[np.ndarray, "x y"] = np.argwhere(
 967				np.all(pixel_grid == color, axis=-1),
 968			)
 969			pos_save: list[CoordTup] = list()
 970			for pos in pos_temp:
 971				# if it is a coordinate and not connection (transform position, %2==1)
 972				if pos[0] % 2 == 1 and pos[1] % 2 == 1:
 973					pos_save.append((pos[0] // 2, pos[1] // 2))
 974
 975			out_positions[key] = np.array(pos_save)
 976
 977		return connection_list, grid_shape, out_positions
 978
 979	@classmethod
 980	def from_pixels(
 981		cls,
 982		pixel_grid: PixelGrid,
 983	) -> "LatticeMaze":
 984		"""create a LatticeMaze from a pixel grid. reverse of `as_pixels`
 985
 986		# Raises:
 987		- `ValueError` : if the pixel grid cannot be cast to a `LatticeMaze` -- it's probably a `TargetedLatticeMaze` or `SolvedMaze`
 988		"""
 989		connection_list: ConnectionList
 990		grid_shape: tuple[int, int]
 991
 992		# if a binary pixel grid, return regular LatticeMaze
 993		if len(pixel_grid.shape) == 2:  # noqa: PLR2004
 994			connection_list, grid_shape = cls._from_pixel_grid_bw(pixel_grid)
 995			return LatticeMaze(connection_list=connection_list)
 996
 997		# otherwise, detect and check it's valid
 998		cls_detected: typing.Type[LatticeMaze] = detect_pixels_type(pixel_grid)
 999		if cls not in cls_detected.__mro__:
1000			err_msg: str = f"Pixel grid cannot be cast to {cls.__name__ = }, detected type {cls_detected.__name__ = }"
1001			raise ValueError(
1002				err_msg,
1003			)
1004
1005		(
1006			connection_list,
1007			grid_shape,
1008			marked_pos,
1009		) = cls._from_pixel_grid_with_positions(
1010			pixel_grid=pixel_grid,
1011			marked_positions=dict(
1012				start=PixelColors.START,
1013				end=PixelColors.END,
1014				solution=PixelColors.PATH,
1015			),
1016		)
1017		# if we wanted a LatticeMaze, return it
1018		if cls == LatticeMaze:
1019			return LatticeMaze(connection_list=connection_list)
1020
1021		# otherwise, keep going
1022		temp_maze: LatticeMaze = LatticeMaze(connection_list=connection_list)
1023
1024		# start and end pos
1025		start_pos_arr, end_pos_arr = marked_pos["start"], marked_pos["end"]
1026		assert start_pos_arr.shape == (
1027			1,
1028			2,
1029		), (
1030			f"start_pos_arr {start_pos_arr} has shape {start_pos_arr.shape}, expected shape (1, 2) -- a single coordinate"
1031		)
1032		assert end_pos_arr.shape == (
1033			1,
1034			2,
1035		), (
1036			f"end_pos_arr {end_pos_arr} has shape {end_pos_arr.shape}, expected shape (1, 2) -- a single coordinate"
1037		)
1038
1039		start_pos: Coord = start_pos_arr[0]
1040		end_pos: Coord = end_pos_arr[0]
1041
1042		# return a TargetedLatticeMaze if that's what we wanted
1043		if cls == TargetedLatticeMaze:
1044			return TargetedLatticeMaze(
1045				connection_list=connection_list,
1046				start_pos=start_pos,
1047				end_pos=end_pos,
1048			)
1049
1050		# raw solution, only contains path elements and not start or end
1051		solution_raw: CoordArray = marked_pos["solution"]
1052		if len(solution_raw.shape) == 2:  # noqa: PLR2004
1053			assert solution_raw.shape[1] == 2, (  # noqa: PLR2004
1054				f"solution {solution_raw} has shape {solution_raw.shape}, expected shape (n, 2)"
1055			)
1056		elif solution_raw.shape == (0,):
1057			# the solution and end should be immediately adjacent
1058			assert np.sum(np.abs(start_pos - end_pos)) == 1, (
1059				f"start_pos {start_pos} and end_pos {end_pos} are not adjacent, but no solution was given"
1060			)
1061
1062		# order the solution, by creating a list from the start to the end
1063		# add end pos, since we will iterate over all these starting from the start pos
1064		solution_raw_list: list[CoordTup] = [tuple(c) for c in solution_raw] + [
1065			tuple(end_pos),
1066		]
1067		# solution starts with start point
1068		solution: list[CoordTup] = [tuple(start_pos)]
1069		while solution[-1] != tuple(end_pos):
1070			# use `get_coord_neighbors` to find connected neighbors
1071			neighbors: CoordArray = temp_maze.get_coord_neighbors(solution[-1])
1072			# TODO: make this less ugly
1073			assert (len(neighbors.shape) == 2) and (neighbors.shape[1] == 2), (  # noqa: PT018, PLR2004
1074				f"neighbors {neighbors} has shape {neighbors.shape}, expected shape (n, 2)\n{neighbors = }\n{solution = }\n{solution_raw = }\n{temp_maze.as_ascii()}"
1075			)
1076			# neighbors = neighbors[:, [1, 0]]
1077			# filter out neighbors that are not in the raw solution
1078			neighbors_filtered: CoordArray = np.array(
1079				[
1080					coord
1081					for coord in neighbors
1082					if (
1083						tuple(coord) in solution_raw_list
1084						and tuple(coord) not in solution
1085					)
1086				],
1087			)
1088			# assert only one element is left, and then add it to the solution
1089			assert neighbors_filtered.shape == (
1090				1,
1091				2,
1092			), (
1093				f"neighbors_filtered has shape {neighbors_filtered.shape}, expected shape (1, 2)\n{neighbors = }\n{neighbors_filtered = }\n{solution = }\n{solution_raw_list = }\n{temp_maze.as_ascii()}"
1094			)
1095			solution.append(tuple(neighbors_filtered[0]))
1096
1097		# assert the solution is complete
1098		assert solution[0] == tuple(start_pos), (
1099			f"solution {solution} does not start at start_pos {start_pos}"
1100		)
1101		assert solution[-1] == tuple(end_pos), (
1102			f"solution {solution} does not end at end_pos {end_pos}"
1103		)
1104
1105		return cls(
1106			connection_list=np.array(connection_list),
1107			solution=np.array(solution),  # type: ignore[call-arg]
1108		)
1109
1110	# ============================================================
1111	# to and from ASCII
1112	# ============================================================
1113	def _as_ascii_grid(self) -> Shaped[np.ndarray, "x y"]:
1114		# Get the pixel grid using to_pixels().
1115		pixel_grid: Bool[np.ndarray, "x y"] = self._as_pixels_bw()
1116
1117		# Replace pixel values with ASCII characters.
1118		ascii_grid: Shaped[np.ndarray, "x y"] = np.full(
1119			pixel_grid.shape,
1120			AsciiChars.WALL,
1121			dtype=str,
1122		)
1123		ascii_grid[pixel_grid == True] = AsciiChars.OPEN  # noqa: E712
1124
1125		return ascii_grid
1126
1127	def as_ascii(
1128		self,
1129		show_endpoints: bool = True,
1130		show_solution: bool = True,
1131	) -> str:
1132		"""return an ASCII grid of the maze
1133
1134		useful for debugging in the terminal, or as it's own format
1135
1136		can be reversed with `LatticeMaze.from_ascii()`
1137		"""
1138		ascii_grid: Shaped[np.ndarray, "x y"] = self._as_ascii_grid()
1139		pixel_grid: PixelGrid = self.as_pixels(
1140			show_endpoints=show_endpoints,
1141			show_solution=show_solution,
1142		)
1143
1144		chars_replace: tuple = tuple()
1145		if show_endpoints:
1146			chars_replace += (AsciiChars.START, AsciiChars.END)
1147		if show_solution:
1148			chars_replace += (AsciiChars.PATH,)
1149
1150		for ascii_char, pixel_color in ASCII_PIXEL_PAIRINGS.items():
1151			if ascii_char in chars_replace:
1152				ascii_grid[(pixel_grid == pixel_color).all(axis=-1)] = ascii_char
1153
1154		return "\n".join("".join(row) for row in ascii_grid)
1155
1156	@classmethod
1157	def from_ascii(cls, ascii_str: str) -> "LatticeMaze":
1158		"get a `LatticeMaze` from an ASCII representation (reverses `LaticeMaze.as_ascii`)"
1159		lines: list[str] = ascii_str.strip().split("\n")
1160		lines = [line.strip() for line in lines]
1161		ascii_grid: Shaped[np.ndarray, "x y"] = np.array(
1162			[list(line) for line in lines],
1163			dtype=str,
1164		)
1165		pixel_grid: PixelGrid = np.zeros((*ascii_grid.shape, 3), dtype=np.uint8)
1166
1167		for ascii_char, pixel_color in ASCII_PIXEL_PAIRINGS.items():
1168			pixel_grid[ascii_grid == ascii_char] = pixel_color
1169
1170		return cls.from_pixels(pixel_grid)

lattice maze (nodes on a lattice, connections only to neighboring nodes)

Connection List represents which nodes (N) are connected in each direction.

First and second elements represent rightward and downward connections, respectively.

Example: Connection list: [ [ # down [F T], [F F] ], [ # right [T F], [T F] ] ]

    Nodes with connections
            N T N F
            F       T
            N T N F
            F       F

    Graph:
            N - N
                    |
            N - N

Note: the bottom row connections going down, and the right-hand connections going right, will always be False.

LatticeMaze( *, connection_list: jaxtyping.Bool[ndarray, 'lattice_dim=2 row col'], generation_meta: dict | None = None)
connection_list: jaxtyping.Bool[ndarray, 'lattice_dim=2 row col']
generation_meta: dict | None = None
lattice_dim
165	lattice_dim = property(lambda self: self.connection_list.shape[0])
grid_shape
166	grid_shape = property(lambda self: self.connection_list.shape[1:])
n_connections
167	n_connections = property(lambda self: self.connection_list.sum())
grid_n: int
169	@property
170	def grid_n(self) -> int:
171		"grid size as int, raises `AssertionError` if not square"
172		assert self.grid_shape[0] == self.grid_shape[1], "only square mazes supported"
173		return self.grid_shape[0]

grid size as int, raises AssertionError if not square

@staticmethod
def heuristic(a: tuple[int, int], b: tuple[int, int]) -> float:
183	@staticmethod
184	def heuristic(a: CoordTup, b: CoordTup) -> float:
185		"""return manhattan distance between two points"""
186		return np.abs(a[0] - b[0]) + np.abs(a[1] - b[1])

return manhattan distance between two points

def nodes_connected( self, a: jaxtyping.Int8[ndarray, 'row_col=2'], b: jaxtyping.Int8[ndarray, 'row_col=2'], /) -> bool:
192	def nodes_connected(self, a: Coord, b: Coord, /) -> bool:
193		"""returns whether two nodes are connected"""
194		delta: Coord = b - a
195		if np.abs(delta).sum() != 1:
196			# return false if not even adjacent
197			return False
198		else:
199			# test for wall
200			dim: int = int(np.argmax(np.abs(delta)))
201			clist_node: Coord = a if (delta.sum() > 0) else b
202			return self.connection_list[dim, clist_node[0], clist_node[1]]

returns whether two nodes are connected

def is_valid_path( self, path: jaxtyping.Int8[ndarray, 'coord row_col=2'], empty_is_valid: bool = False) -> bool:
204	def is_valid_path(self, path: CoordArray, empty_is_valid: bool = False) -> bool:
205		"""check if a path is valid"""
206		# check path is not empty
207		if len(path) == 0:
208			return empty_is_valid
209
210		# check all coords in bounds of maze
211		if not np.all((path >= 0) & (path < self.grid_shape)):
212			return False
213
214		# check all nodes connected
215		for i in range(len(path) - 1):
216			if not self.nodes_connected(path[i], path[i + 1]):
217				return False
218		return True

check if a path is valid

def coord_degrees(self) -> jaxtyping.Int8[ndarray, 'row col']:
220	def coord_degrees(self) -> Int8[np.ndarray, "row col"]:
221		"""Returns an array with the connectivity degree of each coord.
222
223		I.e., how many neighbors each coord has.
224		"""
225		int_conn: Int8[np.ndarray, "lattice_dim=2 row col"] = (
226			self.connection_list.astype(np.int8)
227		)
228		degrees: Int8[np.ndarray, "row col"] = np.sum(
229			int_conn,
230			axis=0,
231		)  # Connections to east and south
232		degrees[:, 1:] += int_conn[1, :, :-1]  # Connections to west
233		degrees[1:, :] += int_conn[0, :-1, :]  # Connections to north
234		return degrees

Returns an array with the connectivity degree of each coord.

I.e., how many neighbors each coord has.

def get_coord_neighbors( self, c: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int]) -> jaxtyping.Int8[ndarray, 'coord row_col=2']:
236	def get_coord_neighbors(self, c: Coord | CoordTup) -> CoordArray:
237		"""Returns an array of the neighboring, connected coords of `c`."""
238		c = np.array(c)  # type: ignore[assignment]
239		neighbors: list[Coord] = [
240			neighbor
241			for neighbor in (c + NEIGHBORS_MASK)
242			if (
243				(0 <= neighbor[0] < self.grid_shape[0])  # in x bounds
244				and (0 <= neighbor[1] < self.grid_shape[1])  # in y bounds
245				and self.nodes_connected(c, neighbor)  # connected
246			)
247		]
248
249		output: CoordArray = np.array(neighbors)
250		if len(neighbors) > 0:
251			assert output.shape == (
252				len(neighbors),
253				2,
254			), (
255				f"invalid shape: {output.shape}, expected ({len(neighbors)}, 2))\n{c = }\n{neighbors = }\n{self.as_ascii()}"
256			)
257		return output

Returns an array of the neighboring, connected coords of c.

def gen_connected_component_from( self, c: jaxtyping.Int8[ndarray, 'row_col=2']) -> jaxtyping.Int8[ndarray, 'coord row_col=2']:
259	def gen_connected_component_from(self, c: Coord) -> CoordArray:
260		"""return the connected component from a given coordinate"""
261		# Stack for DFS
262		stack: list[Coord] = [c]
263
264		# Set to store visited nodes
265		visited: set[CoordTup] = set()
266
267		while stack:
268			current_node: Coord = stack.pop()
269			# this is fine since we know current_node is a coord and thus of length 2
270			visited.add(tuple(current_node))  # type: ignore[arg-type]
271
272			# Get the neighbors of the current node
273			neighbors = self.get_coord_neighbors(current_node)
274
275			# Iterate over neighbors
276			for neighbor in neighbors:
277				if tuple(neighbor) not in visited:
278					stack.append(neighbor)
279
280		return np.array(list(visited))

return the connected component from a given coordinate

def find_shortest_path( self, c_start: tuple[int, int] | jaxtyping.Int8[ndarray, 'row_col=2'], c_end: tuple[int, int] | jaxtyping.Int8[ndarray, 'row_col=2']) -> jaxtyping.Int8[ndarray, 'coord row_col=2']:
282	def find_shortest_path(
283		self,
284		c_start: CoordTup | Coord,
285		c_end: CoordTup | Coord,
286	) -> CoordArray:
287		"""find the shortest path between two coordinates, using A*"""
288		c_start = tuple(c_start)  # type: ignore[assignment]
289		c_end = tuple(c_end)  # type: ignore[assignment]
290
291		g_score: dict[CoordTup, float] = (
292			dict()
293		)  # cost of cheapest path to node from start currently known
294		f_score: dict[CoordTup, float] = {
295			c_start: 0.0,
296		}  # estimated total cost of path thru a node: f_score[c] := g_score[c] + heuristic(c, c_end)
297
298		# init
299		g_score[c_start] = 0.0
300		g_score[c_start] = self.heuristic(c_start, c_end)
301
302		closed_vtx: set[CoordTup] = set()  # nodes already evaluated
303		# nodes to be evaluated
304		# we need a set of the tuples, dont place the ints in the set
305		open_vtx: set[CoordTup] = set([c_start])  # noqa: C405
306		source: dict[CoordTup, CoordTup] = (
307			dict()
308		)  # node immediately preceding each node in the path (currently known shortest path)
309
310		while open_vtx:
311			# get lowest f_score node
312			# mypy cant tell that c is of length 2
313			c_current: CoordTup = min(open_vtx, key=lambda c: f_score[tuple(c)])  # type: ignore[index]
314			# f_current: float = f_score[c_current]
315
316			# check if goal is reached
317			if c_end == c_current:
318				path: list[CoordTup] = [c_current]
319				p_current: CoordTup = c_current
320				while p_current in source:
321					p_current = source[p_current]
322					path.append(p_current)
323				# ----------------------------------------------------------------------
324				# this is the only return statement
325				return np.array(path[::-1])
326				# ----------------------------------------------------------------------
327
328			# close current node
329			closed_vtx.add(c_current)
330			open_vtx.remove(c_current)
331
332			# update g_score of neighbors
333			_np_neighbor: Coord
334			for _np_neighbor in self.get_coord_neighbors(c_current):
335				neighbor: CoordTup = tuple(_np_neighbor)
336
337				if neighbor in closed_vtx:
338					# already checked
339					continue
340				g_temp: float = g_score[c_current] + 1  # always 1 for maze neighbors
341
342				if neighbor not in open_vtx:
343					# found new vtx, so add
344					open_vtx.add(neighbor)
345
346				elif g_temp >= g_score[neighbor]:
347					# if already knew about this one, but current g_score is worse, skip
348					continue
349
350				# store g_score and source
351				source[neighbor] = c_current
352				g_score[neighbor] = g_temp
353				f_score[neighbor] = g_score[neighbor] + self.heuristic(neighbor, c_end)
354
355		raise ValueError(
356			"A solution could not be found!",
357			f"{c_start = }, {c_end = }",
358			self.as_ascii(),
359		)

find the shortest path between two coordinates, using A*

def get_nodes(self) -> jaxtyping.Int8[ndarray, 'coord row_col=2']:
361	def get_nodes(self) -> CoordArray:
362		"""return a list of all nodes in the maze"""
363		rows: Int[np.ndarray, "x y"]
364		cols: Int[np.ndarray, "x y"]
365		rows, cols = np.meshgrid(
366			range(self.grid_shape[0]),
367			range(self.grid_shape[1]),
368			indexing="ij",
369		)
370		nodes: CoordArray = np.vstack((rows.ravel(), cols.ravel())).T
371		return nodes

return a list of all nodes in the maze

def get_connected_component(self) -> jaxtyping.Int8[ndarray, 'coord row_col=2']:
373	def get_connected_component(self) -> CoordArray:
374		"""get the largest (and assumed only nonsingular) connected component of the maze
375
376		TODO: other connected components?
377		"""
378		if (self.generation_meta is None) or (
379			self.generation_meta.get("fully_connected", False)
380		):
381			# for fully connected case, pick any two positions
382			return self.get_nodes()
383		else:
384			# if metadata provided, use visited cells
385			visited_cells: set[CoordTup] | None = self.generation_meta.get(
386				"visited_cells",
387				None,
388			)
389			if visited_cells is None:
390				# TODO: dynamically generate visited_cells?
391				err_msg: str = f"a maze which is not marked as fully connected must have a visited_cells field in its generation_meta: {self.generation_meta}\n{self}\n{self.as_ascii()}"
392				raise ValueError(
393					err_msg,
394				)
395			visited_cells_np: Int[np.ndarray, "N 2"] = np.array(list(visited_cells))
396			return visited_cells_np

get the largest (and assumed only nonsingular) connected component of the maze

TODO: other connected components?

def generate_random_path( self, allowed_start: list[tuple[int, int]] | None = None, allowed_end: list[tuple[int, int]] | None = None, deadend_start: bool = False, deadend_end: bool = False, endpoints_not_equal: bool = False, except_on_no_valid_endpoint: bool = True) -> Optional[jaxtyping.Int8[ndarray, 'coord row_col=2']]:
418	def generate_random_path(  # noqa: C901
419		self,
420		allowed_start: CoordList | None = None,
421		allowed_end: CoordList | None = None,
422		deadend_start: bool = False,
423		deadend_end: bool = False,
424		endpoints_not_equal: bool = False,
425		except_on_no_valid_endpoint: bool = True,
426	) -> typing.Optional[CoordArray]:
427		"""return a path between randomly chosen start and end nodes within the connected component
428
429		Note that setting special conditions on start and end positions might cause the same position to be selected as both start and end.
430
431		# Parameters:
432		- `allowed_start : CoordList | None`
433			a list of allowed start positions. If `None`, any position in the connected component is allowed
434			(defaults to `None`)
435		- `allowed_end : CoordList | None`
436			a list of allowed end positions. If `None`, any position in the connected component is allowed
437			(defaults to `None`)
438		- `deadend_start : bool`
439			whether to ***force*** the start position to be a deadend (defaults to `False`)
440			(defaults to `False`)
441		- `deadend_end : bool`
442			whether to ***force*** the end position to be a deadend (defaults to `False`)
443			(defaults to `False`)
444		- `endpoints_not_equal : bool`
445			whether to ensure tha the start and end point are not the same
446			(defaults to `False`)
447		- `except_on_no_valid_endpoint : bool`
448			whether to raise an error if no valid start or end positions are found
449			if this is `False`, the function might return `None` and this must be handled by the caller
450			(defaults to `True`)
451
452		# Returns:
453		- `CoordArray`
454			a path between the selected start and end positions
455
456		# Raises:
457		- `NoValidEndpointException` : if no valid start or end positions are found, and `except_on_no_valid_endpoint` is `True`
458		"""
459		# we can't create a "path" in a single-node maze
460		assert self.grid_shape[0] > 1 and self.grid_shape[1] > 1, (  # noqa: PT018
461			f"can't create path in single-node maze: {self.as_ascii()}"
462		)
463
464		# get connected component
465		connected_component: CoordArray = self.get_connected_component()
466
467		# initialize start and end positions
468		positions: Int[np.int8, "2 2"]
469
470		# if no special conditions on start and end positions
471		if (allowed_start, allowed_end, deadend_start, deadend_end) == (
472			None,
473			None,
474			False,
475			False,
476		):
477			try:
478				positions = connected_component[  # type: ignore[assignment]
479					np.random.choice(
480						len(connected_component),
481						size=2,
482						replace=False,
483					)
484				]
485			except ValueError as e:
486				if except_on_no_valid_endpoint:
487					err_msg: str = f"No valid start or end positions found because we could not sample from {connected_component = }"
488					raise NoValidEndpointException(
489						err_msg,
490					) from e
491				return None
492
493			return self.find_shortest_path(positions[0], positions[1])  # type: ignore[index]
494
495		# handle special conditions
496		connected_component_set: set[CoordTup] = set(map(tuple, connected_component))
497		# copy connected component set
498		allowed_start_set: set[CoordTup] = connected_component_set.copy()
499		allowed_end_set: set[CoordTup] = connected_component_set.copy()
500
501		# filter by explicitly allowed start and end positions
502		# '# type: ignore[assignment]' here because the returned tuple can be of any length
503		if allowed_start is not None:
504			allowed_start_set = set(map(tuple, allowed_start)) & connected_component_set  # type: ignore[assignment]
505
506		if allowed_end is not None:
507			allowed_end_set = set(map(tuple, allowed_end)) & connected_component_set  # type: ignore[assignment]
508
509		# filter by forcing deadends
510		if deadend_start:
511			allowed_start_set = set(
512				filter(
513					lambda x: len(self.get_coord_neighbors(x)) == 1,
514					allowed_start_set,
515				),
516			)
517
518		if deadend_end:
519			allowed_end_set = set(
520				filter(
521					lambda x: len(self.get_coord_neighbors(x)) == 1,
522					allowed_end_set,
523				),
524			)
525
526		# check we have valid positions
527		if len(allowed_start_set) == 0 or len(allowed_end_set) == 0:
528			if except_on_no_valid_endpoint:
529				err_msg = f"No valid start (or end?) positions found: {allowed_start_set = }, {allowed_end_set = }"
530				raise NoValidEndpointException(
531					err_msg,
532				)
533			return None
534
535		# randomly select start and end positions
536		try:
537			# ignore assignment here since `tuple()` returns a tuple of any length, but we know it will be ok
538			start_pos: CoordTup = tuple(  # type: ignore[assignment]
539				list(allowed_start_set)[np.random.randint(0, len(allowed_start_set))],
540			)
541			if endpoints_not_equal:
542				# remove start position from end positions
543				allowed_end_set.discard(start_pos)
544			end_pos: CoordTup = tuple(  # type: ignore[assignment]
545				list(allowed_end_set)[np.random.randint(0, len(allowed_end_set))],
546			)
547		except ValueError as e:
548			if except_on_no_valid_endpoint:
549				err_msg = f"No valid start or end positions found, maybe can't find an endpoint after we removed the start point: {allowed_start_set = }, {allowed_end_set = }"
550				raise NoValidEndpointException(
551					err_msg,
552				) from e
553			return None
554
555		return self.find_shortest_path(start_pos, end_pos)

return a path between randomly chosen start and end nodes within the connected component

Note that setting special conditions on start and end positions might cause the same position to be selected as both start and end.

Parameters:

  • allowed_start : CoordList | None a list of allowed start positions. If None, any position in the connected component is allowed (defaults to None)
  • allowed_end : CoordList | None a list of allowed end positions. If None, any position in the connected component is allowed (defaults to None)
  • deadend_start : bool whether to force the start position to be a deadend (defaults to False) (defaults to False)
  • deadend_end : bool whether to force the end position to be a deadend (defaults to False) (defaults to False)
  • endpoints_not_equal : bool whether to ensure tha the start and end point are not the same (defaults to False)
  • except_on_no_valid_endpoint : bool whether to raise an error if no valid start or end positions are found if this is False, the function might return None and this must be handled by the caller (defaults to True)

Returns:

  • CoordArray a path between the selected start and end positions

Raises:

  • NoValidEndpointException : if no valid start or end positions are found, and except_on_no_valid_endpoint is True
def as_adj_list( self, shuffle_d0: bool = True, shuffle_d1: bool = True) -> jaxtyping.Int8[ndarray, 'conn start_end coord']:
560	def as_adj_list(
561		self,
562		shuffle_d0: bool = True,
563		shuffle_d1: bool = True,
564	) -> Int8[np.ndarray, "conn start_end coord"]:
565		"""return the maze as an adjacency list, wraps `maze_dataset.token_utils.connection_list_to_adj_list`"""
566		return connection_list_to_adj_list(self.connection_list, shuffle_d0, shuffle_d1)

return the maze as an adjacency list, wraps maze_dataset.token_utils.connection_list_to_adj_list

@classmethod
def from_adj_list( cls, adj_list: jaxtyping.Int8[ndarray, 'conn start_end coord']) -> LatticeMaze:
568	@classmethod
569	def from_adj_list(
570		cls,
571		adj_list: Int8[np.ndarray, "conn start_end coord"],
572	) -> "LatticeMaze":
573		"""create a LatticeMaze from a list of connections
574
575		> [!NOTE]
576		> This has only been tested for square mazes. Might need to change some things if rectangular mazes are needed.
577		"""
578		# this is where it would probably break for rectangular mazes
579		grid_n: int = adj_list.max() + 1
580
581		connection_list: ConnectionList = np.zeros(
582			(2, grid_n, grid_n),
583			dtype=np.bool_,
584		)
585
586		for c_start, c_end in adj_list:
587			# check that exactly 1 coordinate matches
588			if (c_start == c_end).sum() != 1:
589				raise ValueError("invalid connection")
590
591			# get the direction
592			d: int = (c_start != c_end).argmax()
593
594			x: int
595			y: int
596			# pick whichever has the lesser value in the direction `d`
597			if c_start[d] < c_end[d]:
598				x, y = c_start
599			else:
600				x, y = c_end
601
602			connection_list[d, x, y] = True
603
604		return LatticeMaze(
605			connection_list=connection_list,
606		)

create a LatticeMaze from a list of connections

Note

This has only been tested for square mazes. Might need to change some things if rectangular mazes are needed.

def as_adj_list_tokens(self) -> list[str | tuple[int, int]]:
608	def as_adj_list_tokens(self) -> list[str | CoordTup]:
609		"""(deprecated!) turn the maze into adjacency list tokens, use `MazeTokenizerModular` instead"""
610		warnings.warn(
611			"`LatticeMaze.as_adj_list_tokens` will be removed from the public API in a future release.",
612			TokenizerDeprecationWarning,
613		)
614		return [
615			SPECIAL_TOKENS.ADJLIST_START,
616			*chain.from_iterable(  # type: ignore[list-item]
617				[
618					[
619						tuple(c_s),
620						SPECIAL_TOKENS.CONNECTOR,
621						tuple(c_e),
622						SPECIAL_TOKENS.ADJACENCY_ENDLINE,
623					]
624					for c_s, c_e in self.as_adj_list()
625				],
626			),
627			SPECIAL_TOKENS.ADJLIST_END,
628		]

(deprecated!) turn the maze into adjacency list tokens, use MazeTokenizerModular instead

680	def as_tokens(
681		self,
682		maze_tokenizer: "MazeTokenizer | TokenizationMode | MazeTokenizerModular",
683	) -> list[str]:
684		"""serialize maze and solution to tokens"""
685		if isinstance_by_type_name(maze_tokenizer, "MazeTokenizerModular"):
686			return maze_tokenizer.to_tokens(self)  # type: ignore[union-attr]
687		else:
688			return self._as_tokens(maze_tokenizer)  # type: ignore[union-attr,arg-type]

serialize maze and solution to tokens

800	@classmethod
801	def from_tokens(
802		cls,
803		tokens: list[str],
804		maze_tokenizer: "MazeTokenizer | TokenizationMode | MazeTokenizerModular",
805	) -> "LatticeMaze | TargetedLatticeMaze | SolvedMaze":
806		"""Constructs a maze from a tokenization.
807
808		Only legacy tokenizers and their `MazeTokenizerModular` analogs are supported.
809		"""
810		# HACK: type ignores here fine since we check the instance
811		if isinstance_by_type_name(maze_tokenizer, "TokenizationMode"):
812			maze_tokenizer = maze_tokenizer.to_legacy_tokenizer()  # type: ignore[union-attr]
813		if (
814			isinstance_by_type_name(maze_tokenizer, "MazeTokenizerModular")
815			and not maze_tokenizer.is_legacy_equivalent()  # type: ignore[union-attr]
816		):
817			err_msg: str = f"Only legacy tokenizers and their exact `MazeTokenizerModular` analogs supported, not {maze_tokenizer}."
818			raise NotImplementedError(
819				err_msg,
820			)
821
822		if isinstance(tokens, str):
823			tokens = tokens.split()
824
825		if maze_tokenizer.is_AOTP():  # type: ignore[union-attr]
826			return cls._from_tokens_AOTP(tokens, maze_tokenizer)  # type: ignore[arg-type]
827		else:
828			raise NotImplementedError("only AOTP tokenization is supported")

Constructs a maze from a tokenization.

Only legacy tokenizers and their MazeTokenizerModular analogs are supported.

def as_pixels( self, show_endpoints: bool = True, show_solution: bool = True) -> jaxtyping.Int[ndarray, 'x y rgb']:
859	def as_pixels(
860		self,
861		show_endpoints: bool = True,
862		show_solution: bool = True,
863	) -> PixelGrid:
864		"""convert the maze to a pixel grid
865
866		- useful as a simpler way of plotting the maze than the more complex `MazePlot`
867		- the same underlying representation as `as_ascii` but as an image
868		- used in `RasterizedMazeDataset`, which mimics the mazes in https://github.com/aks2203/easy-to-hard-data
869		"""
870		# HACK: lots of `# type: ignore[attr-defined]` here since its defined for any `LatticeMaze`
871		# but solution, start_pos, end_pos not always defined
872		# but its fine since we explicitly check the type
873		if show_solution and not show_endpoints:
874			raise ValueError("show_solution=True requires show_endpoints=True")
875		# convert original bool pixel grid to RGB
876		pixel_grid_bw: BinaryPixelGrid = self._as_pixels_bw()
877		pixel_grid: PixelGrid = np.full(
878			(*pixel_grid_bw.shape, 3),
879			PixelColors.WALL,
880			dtype=np.uint8,
881		)
882		pixel_grid[pixel_grid_bw == True] = PixelColors.OPEN  # noqa: E712
883
884		if self.__class__ == LatticeMaze:
885			return pixel_grid
886
887		# set endpoints for TargetedLatticeMaze
888		if self.__class__ == TargetedLatticeMaze:
889			if show_endpoints:
890				pixel_grid[self.start_pos[0] * 2 + 1, self.start_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
891					PixelColors.START
892				)
893				pixel_grid[self.end_pos[0] * 2 + 1, self.end_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
894					PixelColors.END
895				)
896			return pixel_grid
897
898		# set solution -- we only reach this part if `self.__class__ == SolvedMaze`
899		if show_solution:
900			for coord in self.solution:  # type: ignore[attr-defined]
901				pixel_grid[coord[0] * 2 + 1, coord[1] * 2 + 1] = PixelColors.PATH
902
903			# set pixels between coords
904			for index, coord in enumerate(self.solution[:-1]):  # type: ignore[attr-defined]
905				next_coord = self.solution[index + 1]  # type: ignore[attr-defined]
906				# check they are adjacent using norm
907				assert np.linalg.norm(np.array(coord) - np.array(next_coord)) == 1, (
908					f"Coords {coord} and {next_coord} are not adjacent"
909				)
910				# set pixel between them
911				pixel_grid[
912					coord[0] * 2 + 1 + next_coord[0] - coord[0],
913					coord[1] * 2 + 1 + next_coord[1] - coord[1],
914				] = PixelColors.PATH
915
916			# set endpoints (again, since path would overwrite them)
917			pixel_grid[self.start_pos[0] * 2 + 1, self.start_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
918				PixelColors.START
919			)
920			pixel_grid[self.end_pos[0] * 2 + 1, self.end_pos[1] * 2 + 1] = (  # type: ignore[attr-defined]
921				PixelColors.END
922			)
923
924		return pixel_grid

convert the maze to a pixel grid

@classmethod
def from_pixels( cls, pixel_grid: jaxtyping.Int[ndarray, 'x y rgb']) -> LatticeMaze:
 979	@classmethod
 980	def from_pixels(
 981		cls,
 982		pixel_grid: PixelGrid,
 983	) -> "LatticeMaze":
 984		"""create a LatticeMaze from a pixel grid. reverse of `as_pixels`
 985
 986		# Raises:
 987		- `ValueError` : if the pixel grid cannot be cast to a `LatticeMaze` -- it's probably a `TargetedLatticeMaze` or `SolvedMaze`
 988		"""
 989		connection_list: ConnectionList
 990		grid_shape: tuple[int, int]
 991
 992		# if a binary pixel grid, return regular LatticeMaze
 993		if len(pixel_grid.shape) == 2:  # noqa: PLR2004
 994			connection_list, grid_shape = cls._from_pixel_grid_bw(pixel_grid)
 995			return LatticeMaze(connection_list=connection_list)
 996
 997		# otherwise, detect and check it's valid
 998		cls_detected: typing.Type[LatticeMaze] = detect_pixels_type(pixel_grid)
 999		if cls not in cls_detected.__mro__:
1000			err_msg: str = f"Pixel grid cannot be cast to {cls.__name__ = }, detected type {cls_detected.__name__ = }"
1001			raise ValueError(
1002				err_msg,
1003			)
1004
1005		(
1006			connection_list,
1007			grid_shape,
1008			marked_pos,
1009		) = cls._from_pixel_grid_with_positions(
1010			pixel_grid=pixel_grid,
1011			marked_positions=dict(
1012				start=PixelColors.START,
1013				end=PixelColors.END,
1014				solution=PixelColors.PATH,
1015			),
1016		)
1017		# if we wanted a LatticeMaze, return it
1018		if cls == LatticeMaze:
1019			return LatticeMaze(connection_list=connection_list)
1020
1021		# otherwise, keep going
1022		temp_maze: LatticeMaze = LatticeMaze(connection_list=connection_list)
1023
1024		# start and end pos
1025		start_pos_arr, end_pos_arr = marked_pos["start"], marked_pos["end"]
1026		assert start_pos_arr.shape == (
1027			1,
1028			2,
1029		), (
1030			f"start_pos_arr {start_pos_arr} has shape {start_pos_arr.shape}, expected shape (1, 2) -- a single coordinate"
1031		)
1032		assert end_pos_arr.shape == (
1033			1,
1034			2,
1035		), (
1036			f"end_pos_arr {end_pos_arr} has shape {end_pos_arr.shape}, expected shape (1, 2) -- a single coordinate"
1037		)
1038
1039		start_pos: Coord = start_pos_arr[0]
1040		end_pos: Coord = end_pos_arr[0]
1041
1042		# return a TargetedLatticeMaze if that's what we wanted
1043		if cls == TargetedLatticeMaze:
1044			return TargetedLatticeMaze(
1045				connection_list=connection_list,
1046				start_pos=start_pos,
1047				end_pos=end_pos,
1048			)
1049
1050		# raw solution, only contains path elements and not start or end
1051		solution_raw: CoordArray = marked_pos["solution"]
1052		if len(solution_raw.shape) == 2:  # noqa: PLR2004
1053			assert solution_raw.shape[1] == 2, (  # noqa: PLR2004
1054				f"solution {solution_raw} has shape {solution_raw.shape}, expected shape (n, 2)"
1055			)
1056		elif solution_raw.shape == (0,):
1057			# the solution and end should be immediately adjacent
1058			assert np.sum(np.abs(start_pos - end_pos)) == 1, (
1059				f"start_pos {start_pos} and end_pos {end_pos} are not adjacent, but no solution was given"
1060			)
1061
1062		# order the solution, by creating a list from the start to the end
1063		# add end pos, since we will iterate over all these starting from the start pos
1064		solution_raw_list: list[CoordTup] = [tuple(c) for c in solution_raw] + [
1065			tuple(end_pos),
1066		]
1067		# solution starts with start point
1068		solution: list[CoordTup] = [tuple(start_pos)]
1069		while solution[-1] != tuple(end_pos):
1070			# use `get_coord_neighbors` to find connected neighbors
1071			neighbors: CoordArray = temp_maze.get_coord_neighbors(solution[-1])
1072			# TODO: make this less ugly
1073			assert (len(neighbors.shape) == 2) and (neighbors.shape[1] == 2), (  # noqa: PT018, PLR2004
1074				f"neighbors {neighbors} has shape {neighbors.shape}, expected shape (n, 2)\n{neighbors = }\n{solution = }\n{solution_raw = }\n{temp_maze.as_ascii()}"
1075			)
1076			# neighbors = neighbors[:, [1, 0]]
1077			# filter out neighbors that are not in the raw solution
1078			neighbors_filtered: CoordArray = np.array(
1079				[
1080					coord
1081					for coord in neighbors
1082					if (
1083						tuple(coord) in solution_raw_list
1084						and tuple(coord) not in solution
1085					)
1086				],
1087			)
1088			# assert only one element is left, and then add it to the solution
1089			assert neighbors_filtered.shape == (
1090				1,
1091				2,
1092			), (
1093				f"neighbors_filtered has shape {neighbors_filtered.shape}, expected shape (1, 2)\n{neighbors = }\n{neighbors_filtered = }\n{solution = }\n{solution_raw_list = }\n{temp_maze.as_ascii()}"
1094			)
1095			solution.append(tuple(neighbors_filtered[0]))
1096
1097		# assert the solution is complete
1098		assert solution[0] == tuple(start_pos), (
1099			f"solution {solution} does not start at start_pos {start_pos}"
1100		)
1101		assert solution[-1] == tuple(end_pos), (
1102			f"solution {solution} does not end at end_pos {end_pos}"
1103		)
1104
1105		return cls(
1106			connection_list=np.array(connection_list),
1107			solution=np.array(solution),  # type: ignore[call-arg]
1108		)

create a LatticeMaze from a pixel grid. reverse of as_pixels

Raises:

def as_ascii(self, show_endpoints: bool = True, show_solution: bool = True) -> str:
1127	def as_ascii(
1128		self,
1129		show_endpoints: bool = True,
1130		show_solution: bool = True,
1131	) -> str:
1132		"""return an ASCII grid of the maze
1133
1134		useful for debugging in the terminal, or as it's own format
1135
1136		can be reversed with `LatticeMaze.from_ascii()`
1137		"""
1138		ascii_grid: Shaped[np.ndarray, "x y"] = self._as_ascii_grid()
1139		pixel_grid: PixelGrid = self.as_pixels(
1140			show_endpoints=show_endpoints,
1141			show_solution=show_solution,
1142		)
1143
1144		chars_replace: tuple = tuple()
1145		if show_endpoints:
1146			chars_replace += (AsciiChars.START, AsciiChars.END)
1147		if show_solution:
1148			chars_replace += (AsciiChars.PATH,)
1149
1150		for ascii_char, pixel_color in ASCII_PIXEL_PAIRINGS.items():
1151			if ascii_char in chars_replace:
1152				ascii_grid[(pixel_grid == pixel_color).all(axis=-1)] = ascii_char
1153
1154		return "\n".join("".join(row) for row in ascii_grid)

return an ASCII grid of the maze

useful for debugging in the terminal, or as it's own format

can be reversed with LatticeMaze.from_ascii()

@classmethod
def from_ascii(cls, ascii_str: str) -> LatticeMaze:
1156	@classmethod
1157	def from_ascii(cls, ascii_str: str) -> "LatticeMaze":
1158		"get a `LatticeMaze` from an ASCII representation (reverses `LaticeMaze.as_ascii`)"
1159		lines: list[str] = ascii_str.strip().split("\n")
1160		lines = [line.strip() for line in lines]
1161		ascii_grid: Shaped[np.ndarray, "x y"] = np.array(
1162			[list(line) for line in lines],
1163			dtype=str,
1164		)
1165		pixel_grid: PixelGrid = np.zeros((*ascii_grid.shape, 3), dtype=np.uint8)
1166
1167		for ascii_char, pixel_color in ASCII_PIXEL_PAIRINGS.items():
1168			pixel_grid[ascii_grid == ascii_char] = pixel_color
1169
1170		return cls.from_pixels(pixel_grid)

get a LatticeMaze from an ASCII representation (reverses LaticeMaze.as_ascii)

def serialize(self) -> dict[str, typing.Any]:
714        def serialize(self) -> dict[str, Any]:
715            result: dict[str, Any] = {
716                _FORMAT_KEY: f"{self.__class__.__name__}(SerializableDataclass)"
717            }
718            # for each field in the class
719            for field in dataclasses.fields(self):  # type: ignore[arg-type]
720                # need it to be our special SerializableField
721                if not isinstance(field, SerializableField):
722                    raise NotSerializableFieldException(
723                        f"Field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__} is not a `SerializableField`, "
724                        f"but a {type(field)} "
725                        "this state should be inaccessible, please report this bug!"
726                    )
727
728                # try to save it
729                if field.serialize:
730                    try:
731                        # get the val
732                        value = getattr(self, field.name)
733                        # if it is a serializable dataclass, serialize it
734                        if isinstance(value, SerializableDataclass):
735                            value = value.serialize()
736                        # if the value has a serialization function, use that
737                        if hasattr(value, "serialize") and callable(value.serialize):
738                            value = value.serialize()
739                        # if the field has a serialization function, use that
740                        # it would be nice to be able to override a class's `.serialize()`, but that could lead to some inconsistencies!
741                        elif field.serialization_fn:
742                            value = field.serialization_fn(value)
743
744                        # store the value in the result
745                        result[field.name] = value
746                    except Exception as e:
747                        raise FieldSerializationError(
748                            "\n".join(
749                                [
750                                    f"Error serializing field '{field.name}' on class {self.__class__.__module__}.{self.__class__.__name__}",
751                                    f"{field = }",
752                                    f"{value = }",
753                                    f"{self = }",
754                                ]
755                            )
756                        ) from e
757
758            # store each property if we can get it
759            for prop in self._properties_to_serialize:
760                if hasattr(cls, prop):
761                    value = getattr(self, prop)
762                    result[prop] = value
763                else:
764                    raise AttributeError(
765                        f"Cannot serialize property '{prop}' on class {self.__class__.__module__}.{self.__class__.__name__}"
766                        + f"but it is in {self._properties_to_serialize = }"
767                        + f"\n{self = }"
768                    )
769
770            return result

returns the class as a dict, implemented by using @serializable_dataclass decorator

@classmethod
def load(cls, data: Union[dict[str, Any], ~T]) -> Type[~T]:
777        @classmethod  # type: ignore[misc]
778        def load(cls, data: dict[str, Any] | T) -> Type[T]:
779            # HACK: this is kind of ugly, but it fixes a lot of issues for when we do recursive loading with ZANJ
780            if isinstance(data, cls):
781                return data
782
783            assert isinstance(
784                data, typing.Mapping
785            ), f"When loading {cls.__name__ = } expected a Mapping, but got {type(data) = }:\n{data = }"
786
787            cls_type_hints: dict[str, Any] = get_cls_type_hints(cls)
788
789            # initialize dict for keeping what we will pass to the constructor
790            ctor_kwargs: dict[str, Any] = dict()
791
792            # iterate over the fields of the class
793            for field in dataclasses.fields(cls):
794                # check if the field is a SerializableField
795                assert isinstance(
796                    field, SerializableField
797                ), f"Field '{field.name}' on class {cls.__name__} is not a SerializableField, but a {type(field)}. this state should be inaccessible, please report this bug!\nhttps://github.com/mivanit/muutils/issues/new"
798
799                # check if the field is in the data and if it should be initialized
800                if (field.name in data) and field.init:
801                    # get the value, we will be processing it
802                    value: Any = data[field.name]
803
804                    # get the type hint for the field
805                    field_type_hint: Any = cls_type_hints.get(field.name, None)
806
807                    # we rely on the init of `SerializableField` to check that only one of `loading_fn` and `deserialize_fn` is set
808                    if field.deserialize_fn:
809                        # if it has a deserialization function, use that
810                        value = field.deserialize_fn(value)
811                    elif field.loading_fn:
812                        # if it has a loading function, use that
813                        value = field.loading_fn(data)
814                    elif (
815                        field_type_hint is not None
816                        and hasattr(field_type_hint, "load")
817                        and callable(field_type_hint.load)
818                    ):
819                        # if no loading function but has a type hint with a load method, use that
820                        if isinstance(value, dict):
821                            value = field_type_hint.load(value)
822                        else:
823                            raise FieldLoadingError(
824                                f"Cannot load value into {field_type_hint}, expected {type(value) = } to be a dict\n{value = }"
825                            )
826                    else:
827                        # assume no loading needs to happen, keep `value` as-is
828                        pass
829
830                    # store the value in the constructor kwargs
831                    ctor_kwargs[field.name] = value
832
833            # create a new instance of the class with the constructor kwargs
834            output: cls = cls(**ctor_kwargs)
835
836            # validate the types of the fields if needed
837            if on_typecheck_mismatch != ErrorMode.IGNORE:
838                fields_valid: dict[str, bool] = (
839                    SerializableDataclass__validate_fields_types__dict(
840                        output,
841                        on_typecheck_error=on_typecheck_error,
842                    )
843                )
844
845                # if there are any fields that are not valid, raise an error
846                if not all(fields_valid.values()):
847                    msg: str = (
848                        f"Type mismatch in fields of {cls.__name__}:\n"
849                        + "\n".join(
850                            [
851                                f"{k}:\texpected {cls_type_hints[k] = }, but got value {getattr(output, k) = }, {type(getattr(output, k)) = }"
852                                for k, v in fields_valid.items()
853                                if not v
854                            ]
855                        )
856                    )
857
858                    on_typecheck_mismatch.process(
859                        msg, except_cls=FieldTypeMismatchError
860                    )
861
862            # return the new instance
863            return output

takes in an appropriately structured dict and returns an instance of the class, implemented by using @serializable_dataclass decorator

def validate_fields_types( self: muutils.json_serialize.serializable_dataclass.SerializableDataclass, on_typecheck_error: muutils.errormode.ErrorMode = ErrorMode.Except) -> bool:
283def SerializableDataclass__validate_fields_types(
284    self: SerializableDataclass,
285    on_typecheck_error: ErrorMode = _DEFAULT_ON_TYPECHECK_ERROR,
286) -> bool:
287    """validate the types of all the fields on a `SerializableDataclass`. calls `SerializableDataclass__validate_field_type` for each field"""
288    return all(
289        SerializableDataclass__validate_fields_types__dict(
290            self, on_typecheck_error=on_typecheck_error
291        ).values()
292    )

validate the types of all the fields on a SerializableDataclass. calls SerializableDataclass__validate_field_type for each field

Inherited Members
muutils.json_serialize.serializable_dataclass.SerializableDataclass
validate_field_type
diff
update_from_nested_dict
def set_serialize_minimal_threshold(threshold: int | None) -> None:
56def set_serialize_minimal_threshold(threshold: int | None) -> None:
57	"get the global SERIALIZE_MINIMAL_THRESHOLD"
58	global SERIALIZE_MINIMAL_THRESHOLD  # noqa: PLW0603
59	SERIALIZE_MINIMAL_THRESHOLD = threshold

get the global SERIALIZE_MINIMAL_THRESHOLD

def register_maze_filter( method: Callable[[SolvedMaze, Any], bool]) -> Callable[Concatenate[~T_Dataset, ~P_FilterKwargs], ~T_Dataset]:
21def register_maze_filter(
22	method: typing.Callable[[SolvedMaze, typing.Any], bool],
23) -> DatasetFilterFunc:
24	"""register a maze filter, casting it to operate over the whole list of mazes
25
26	method should be a staticmethod of a namespace class registered with `register_filter_namespace_for_dataset`
27
28	this is a more restricted version of `register_dataset_filter` that removes the need for boilerplate for operating over the arrays
29	"""
30
31	@functools.wraps(method)
32	def wrapper(dataset: MazeDataset, *args, **kwargs) -> MazeDataset:
33		# copy and filter
34		new_dataset: MazeDataset = copy.deepcopy(
35			MazeDataset(
36				cfg=dataset.cfg,
37				mazes=[m for m in dataset.mazes if method(m, *args, **kwargs)],
38			),
39		)
40		# update the config
41		new_dataset.cfg.applied_filters.append(
42			dict(name=method.__name__, args=args, kwargs=kwargs),
43		)
44		new_dataset.update_self_config()
45		return new_dataset
46
47	return wrapper

register a maze filter, casting it to operate over the whole list of mazes

method should be a staticmethod of a namespace class registered with register_filter_namespace_for_dataset

this is a more restricted version of register_dataset_filter that removes the need for boilerplate for operating over the arrays

class LatticeMazeGenerators:
 54class LatticeMazeGenerators:
 55	"""namespace for lattice maze generation algorithms
 56
 57	examples of generated mazes can be found here:
 58	https://understanding-search.github.io/maze-dataset/examples/maze_examples.html
 59	"""
 60
 61	@staticmethod
 62	def gen_dfs(
 63		grid_shape: Coord | CoordTup,
 64		lattice_dim: int = 2,
 65		accessible_cells: float | None = None,
 66		max_tree_depth: float | None = None,
 67		do_forks: bool = True,
 68		randomized_stack: bool = False,
 69		start_coord: Coord | None = None,
 70	) -> LatticeMaze:
 71		"""generate a lattice maze using depth first search, iterative
 72
 73		# Arguments
 74		- `grid_shape: Coord`: the shape of the grid
 75		- `lattice_dim: int`: the dimension of the lattice
 76			(default: `2`)
 77		- `accessible_cells: int | float |None`: the number of accessible cells in the maze. If `None`, defaults to the total number of cells in the grid. if a float, asserts it is <= 1 and treats it as a proportion of **total cells**
 78			(default: `None`)
 79		- `max_tree_depth: int | float | None`: the maximum depth of the tree. If `None`, defaults to `2 * accessible_cells`. if a float, asserts it is <= 1 and treats it as a proportion of the **sum of the grid shape**
 80			(default: `None`)
 81		- `do_forks: bool`: whether to allow forks in the maze. If `False`, the maze will be have no forks and will be a simple hallway.
 82		- `start_coord: Coord | None`: the starting coordinate of the generation algorithm. If `None`, defaults to a random coordinate.
 83
 84		# algorithm
 85		1. Choose the initial cell, mark it as visited and push it to the stack
 86		2. While the stack is not empty
 87			1. Pop a cell from the stack and make it a current cell
 88			2. If the current cell has any neighbours which have not been visited
 89				1. Push the current cell to the stack
 90				2. Choose one of the unvisited neighbours
 91				3. Remove the wall between the current cell and the chosen cell
 92				4. Mark the chosen cell as visited and push it to the stack
 93		"""
 94		# Default values if no constraints have been passed
 95		grid_shape_: Coord = np.array(grid_shape)
 96		n_total_cells: int = int(np.prod(grid_shape_))
 97
 98		n_accessible_cells: int
 99		if accessible_cells is None:
100			n_accessible_cells = n_total_cells
101		elif isinstance(accessible_cells, float):
102			assert accessible_cells <= 1, (
103				f"accessible_cells must be an int (count) or a float in the range [0, 1] (proportion), got {accessible_cells}"
104			)
105
106			n_accessible_cells = int(accessible_cells * n_total_cells)
107		else:
108			assert isinstance(accessible_cells, int)
109			n_accessible_cells = accessible_cells
110
111		if max_tree_depth is None:
112			max_tree_depth = (
113				2 * n_total_cells
114			)  # We define max tree depth counting from the start coord in two directions. Therefore we divide by two in the if clause for neighboring sites later and multiply by two here.
115		elif isinstance(max_tree_depth, float):
116			assert max_tree_depth <= 1, (
117				f"max_tree_depth must be an int (count) or a float in the range [0, 1] (proportion), got {max_tree_depth}"
118			)
119
120			max_tree_depth = int(max_tree_depth * np.sum(grid_shape_))
121
122		# choose a random start coord
123		start_coord = _random_start_coord(grid_shape_, start_coord)
124
125		# initialize the maze with no connections
126		connection_list: ConnectionList = np.zeros(
127			(lattice_dim, grid_shape_[0], grid_shape_[1]),
128			dtype=np.bool_,
129		)
130
131		# initialize the stack with the target coord
132		visited_cells: set[tuple[int, int]] = set()
133		visited_cells.add(tuple(start_coord))  # this wasnt a bug after all lol
134		stack: list[Coord] = [start_coord]
135
136		# initialize tree_depth_counter
137		current_tree_depth: int = 1
138
139		# loop until the stack is empty or n_connected_cells is reached
140		while stack and (len(visited_cells) < n_accessible_cells):
141			# get the current coord from the stack
142			current_coord: Coord
143			if randomized_stack:
144				current_coord = stack.pop(random.randint(0, len(stack) - 1))
145			else:
146				current_coord = stack.pop()
147
148			# filter neighbors by being within grid bounds and being unvisited
149			unvisited_neighbors_deltas: list[tuple[Coord, Coord]] = [
150				(neighbor, delta)
151				for neighbor, delta in zip(
152					current_coord + NEIGHBORS_MASK,
153					NEIGHBORS_MASK,
154					strict=False,
155				)
156				if (
157					(tuple(neighbor) not in visited_cells)
158					and (0 <= neighbor[0] < grid_shape_[0])
159					and (0 <= neighbor[1] < grid_shape_[1])
160				)
161			]
162
163			# don't continue if max_tree_depth/2 is already reached (divide by 2 because we can branch to multiple directions)
164			if unvisited_neighbors_deltas and (
165				current_tree_depth <= max_tree_depth / 2
166			):
167				# if we want a maze without forks, simply don't add the current coord back to the stack
168				if do_forks and (len(unvisited_neighbors_deltas) > 1):
169					stack.append(current_coord)
170
171				# choose one of the unvisited neighbors
172				chosen_neighbor, delta = random.choice(unvisited_neighbors_deltas)
173
174				# add connection
175				dim: int = int(np.argmax(np.abs(delta)))
176				# if positive, down/right from current coord
177				# if negative, up/left from current coord (down/right from neighbor)
178				clist_node: Coord = (
179					current_coord if (delta.sum() > 0) else chosen_neighbor
180				)
181				connection_list[dim, clist_node[0], clist_node[1]] = True
182
183				# add to visited cells and stack
184				visited_cells.add(tuple(chosen_neighbor))
185				stack.append(chosen_neighbor)
186
187				# Update current tree depth
188				current_tree_depth += 1
189			else:
190				current_tree_depth -= 1
191
192		return LatticeMaze(
193			connection_list=connection_list,
194			generation_meta=dict(
195				func_name="gen_dfs",
196				grid_shape=grid_shape_,
197				start_coord=start_coord,
198				n_accessible_cells=int(n_accessible_cells),
199				max_tree_depth=int(max_tree_depth),
200				# oh my god this took so long to track down. its almost 5am and I've spent like 2 hours on this bug
201				# it was checking that len(visited_cells) == n_accessible_cells, but this means that the maze is
202				# treated as fully connected even when it is most certainly not, causing solving the maze to break
203				fully_connected=bool(len(visited_cells) == n_total_cells),
204				visited_cells={tuple(int(x) for x in coord) for coord in visited_cells},
205			),
206		)
207
208	@staticmethod
209	def gen_prim(
210		grid_shape: Coord | CoordTup,
211		lattice_dim: int = 2,
212		accessible_cells: float | None = None,
213		max_tree_depth: float | None = None,
214		do_forks: bool = True,
215		start_coord: Coord | None = None,
216	) -> LatticeMaze:
217		"(broken!) generate a lattice maze using Prim's algorithm"
218		warnings.warn(
219			"gen_prim does not correctly implement prim's algorithm, see issue: https://github.com/understanding-search/maze-dataset/issues/12",
220		)
221		return LatticeMazeGenerators.gen_dfs(
222			grid_shape=grid_shape,
223			lattice_dim=lattice_dim,
224			accessible_cells=accessible_cells,
225			max_tree_depth=max_tree_depth,
226			do_forks=do_forks,
227			start_coord=start_coord,
228			randomized_stack=True,
229		)
230
231	@staticmethod
232	def gen_wilson(
233		grid_shape: Coord | CoordTup,
234		**kwargs,
235	) -> LatticeMaze:
236		"""Generate a lattice maze using Wilson's algorithm.
237
238		# Algorithm
239		Wilson's algorithm generates an unbiased (random) maze
240		sampled from the uniform distribution over all mazes, using loop-erased random walks. The generated maze is
241		acyclic and all cells are part of a unique connected space.
242		https://en.wikipedia.org/wiki/Maze_generation_algorithm#Wilson's_algorithm
243		"""
244		assert not kwargs, (
245			f"gen_wilson does not take any additional arguments, got {kwargs = }"
246		)
247
248		grid_shape_: Coord = np.array(grid_shape)
249
250		# Initialize grid and visited cells
251		connection_list: ConnectionList = np.zeros((2, *grid_shape_), dtype=np.bool_)
252		visited: Bool[np.ndarray, "x y"] = np.zeros(grid_shape_, dtype=np.bool_)
253
254		# Choose a random cell and mark it as visited
255		start_coord: Coord = _random_start_coord(grid_shape_, None)
256		visited[start_coord[0], start_coord[1]] = True
257		del start_coord
258
259		while not visited.all():
260			# Perform loop-erased random walk from another random cell
261
262			# Choose walk_start only from unvisited cells
263			unvisited_coords: CoordArray = np.column_stack(np.where(~visited))
264			walk_start: Coord = unvisited_coords[
265				np.random.choice(unvisited_coords.shape[0])
266			]
267
268			# Perform the random walk
269			path: list[Coord] = [walk_start]
270			current: Coord = walk_start
271
272			# exit the loop once the current path hits a visited cell
273			while not visited[current[0], current[1]]:
274				# find a valid neighbor (one always exists on a lattice)
275				neighbors: CoordArray = get_neighbors_in_bounds(current, grid_shape_)
276				next_cell: Coord = neighbors[np.random.choice(neighbors.shape[0])]
277
278				# Check for loop
279				loop_exit: int | None = None
280				for i, p in enumerate(path):
281					if np.array_equal(next_cell, p):
282						loop_exit = i
283						break
284
285				# erase the loop, or continue the walk
286				if loop_exit is not None:
287					# this removes everything after and including the loop start
288					path = path[: loop_exit + 1]
289					# reset current cell to end of path
290					current = path[-1]
291				else:
292					path.append(next_cell)
293					current = next_cell
294
295			# Add the path to the maze
296			for i in range(len(path) - 1):
297				c_1: Coord = path[i]
298				c_2: Coord = path[i + 1]
299
300				# find the dimension of the connection
301				delta: Coord = c_2 - c_1
302				dim: int = int(np.argmax(np.abs(delta)))
303
304				# if positive, down/right from current coord
305				# if negative, up/left from current coord (down/right from neighbor)
306				clist_node: Coord = c_1 if (delta.sum() > 0) else c_2
307				connection_list[dim, clist_node[0], clist_node[1]] = True
308				visited[c_1[0], c_1[1]] = True
309				# we dont add c_2 because the last c_2 will have already been visited
310
311		return LatticeMaze(
312			connection_list=connection_list,
313			generation_meta=dict(
314				func_name="gen_wilson",
315				grid_shape=grid_shape_,
316				fully_connected=True,
317			),
318		)
319
320	@staticmethod
321	def gen_percolation(
322		grid_shape: Coord | CoordTup,
323		p: float = 0.4,
324		lattice_dim: int = 2,
325		start_coord: Coord | None = None,
326	) -> LatticeMaze:
327		"""generate a lattice maze using simple percolation
328
329		note that p in the range (0.4, 0.7) gives the most interesting mazes
330
331		# Arguments
332		- `grid_shape: Coord`: the shape of the grid
333		- `lattice_dim: int`: the dimension of the lattice (default: `2`)
334		- `p: float`: the probability of a cell being accessible (default: `0.5`)
335		- `start_coord: Coord | None`: the starting coordinate for the connected component (default: `None` will give a random start)
336		"""
337		assert p >= 0 and p <= 1, f"p must be between 0 and 1, got {p}"  # noqa: PT018
338		grid_shape_: Coord = np.array(grid_shape)
339
340		start_coord = _random_start_coord(grid_shape_, start_coord)
341
342		connection_list: ConnectionList = np.random.rand(lattice_dim, *grid_shape_) < p
343
344		connection_list = _fill_edges_with_walls(connection_list)
345
346		output: LatticeMaze = LatticeMaze(
347			connection_list=connection_list,
348			generation_meta=dict(
349				func_name="gen_percolation",
350				grid_shape=grid_shape_,
351				percolation_p=p,
352				start_coord=start_coord,
353			),
354		)
355
356		# generation_meta is sometimes None, but not here since we just made it a dict above
357		output.generation_meta["visited_cells"] = output.gen_connected_component_from(  # type: ignore[index]
358			start_coord,
359		)
360
361		return output
362
363	@staticmethod
364	def gen_dfs_percolation(
365		grid_shape: Coord | CoordTup,
366		p: float = 0.4,
367		lattice_dim: int = 2,
368		accessible_cells: int | None = None,
369		max_tree_depth: int | None = None,
370		start_coord: Coord | None = None,
371	) -> LatticeMaze:
372		"""dfs and then percolation (adds cycles)"""
373		grid_shape_: Coord = np.array(grid_shape)
374		start_coord = _random_start_coord(grid_shape_, start_coord)
375
376		# generate initial maze via dfs
377		maze: LatticeMaze = LatticeMazeGenerators.gen_dfs(
378			grid_shape=grid_shape_,
379			lattice_dim=lattice_dim,
380			accessible_cells=accessible_cells,
381			max_tree_depth=max_tree_depth,
382			start_coord=start_coord,
383		)
384
385		# percolate
386		connection_list_perc: np.ndarray = (
387			np.random.rand(*maze.connection_list.shape) < p
388		)
389		connection_list_perc = _fill_edges_with_walls(connection_list_perc)
390
391		maze.__dict__["connection_list"] = np.logical_or(
392			maze.connection_list,
393			connection_list_perc,
394		)
395
396		# generation_meta is sometimes None, but not here since we just made it a dict above
397		maze.generation_meta["func_name"] = "gen_dfs_percolation"  # type: ignore[index]
398		maze.generation_meta["percolation_p"] = p  # type: ignore[index]
399		maze.generation_meta["visited_cells"] = maze.gen_connected_component_from(  # type: ignore[index]
400			start_coord,
401		)
402
403		return maze
404
405	@staticmethod
406	def gen_kruskal(
407		grid_shape: "Coord | CoordTup",
408		lattice_dim: int = 2,
409		start_coord: "Coord | None" = None,
410	) -> "LatticeMaze":
411		"""Generate a maze using Kruskal's algorithm.
412
413		This function generates a random spanning tree over a grid using Kruskal's algorithm.
414		Each cell is treated as a node, and all valid adjacent edges are listed and processed
415		in random order. An edge is added (i.e. its passage carved) only if it connects two cells
416		that are not already connected. The resulting maze is a perfect maze (i.e. a spanning tree)
417		without cycles.
418
419		https://en.wikipedia.org/wiki/Kruskal's_algorithm
420
421		# Parameters:
422		- `grid_shape : Coord | CoordTup`
423			The shape of the maze grid (for example, `(n_rows, n_cols)`).
424		- `lattice_dim : int`
425			The lattice dimension (default is `2`).
426		- `start_coord : Coord | None`
427			Optionally, specify a starting coordinate. If `None`, a random coordinate will be chosen.
428		- `**kwargs`
429			Additional keyword arguments (currently unused).
430
431		# Returns:
432		- `LatticeMaze`
433			A maze represented by a connection list, generated as a spanning tree using Kruskal's algorithm.
434
435		# Usage:
436		```python
437		maze = gen_kruskal((10, 10))
438		```
439		"""
440		assert lattice_dim == 2, (  # noqa: PLR2004
441			"Kruskal's algorithm is only implemented for 2D lattices."
442		)
443		# Convert grid_shape to a tuple of ints
444		grid_shape_: CoordTup = tuple(int(x) for x in grid_shape)  # type: ignore[assignment]
445		n_rows, n_cols = grid_shape_
446
447		# Initialize union-find data structure.
448		parent: dict[tuple[int, int], tuple[int, int]] = {}
449
450		def find(cell: tuple[int, int]) -> tuple[int, int]:
451			while parent[cell] != cell:
452				parent[cell] = parent[parent[cell]]
453				cell = parent[cell]
454			return cell
455
456		def union(cell1: tuple[int, int], cell2: tuple[int, int]) -> None:
457			root1 = find(cell1)
458			root2 = find(cell2)
459			parent[root2] = root1
460
461		# Initialize each cell as its own set.
462		for i in range(n_rows):
463			for j in range(n_cols):
464				parent[(i, j)] = (i, j)
465
466		# List all possible edges.
467		# For vertical edges (i.e. connecting a cell to its right neighbor):
468		edges: list[tuple[tuple[int, int], tuple[int, int], int]] = []
469		for i in range(n_rows):
470			for j in range(n_cols - 1):
471				edges.append(((i, j), (i, j + 1), 1))
472		# For horizontal edges (i.e. connecting a cell to its bottom neighbor):
473		for i in range(n_rows - 1):
474			for j in range(n_cols):
475				edges.append(((i, j), (i + 1, j), 0))
476
477		# Shuffle the list of edges.
478		import random
479
480		random.shuffle(edges)
481
482		# Initialize connection_list with no connections.
483		# connection_list[0] stores downward connections (from cell (i,j) to (i+1,j)).
484		# connection_list[1] stores rightward connections (from cell (i,j) to (i,j+1)).
485		import numpy as np
486
487		connection_list = np.zeros((2, n_rows, n_cols), dtype=bool)
488
489		# Process each edge; if it connects two different trees, union them and carve the passage.
490		for cell1, cell2, direction in edges:
491			if find(cell1) != find(cell2):
492				union(cell1, cell2)
493				if direction == 0:
494					# Horizontal edge: connection is stored in connection_list[0] at cell1.
495					connection_list[0, cell1[0], cell1[1]] = True
496				else:
497					# Vertical edge: connection is stored in connection_list[1] at cell1.
498					connection_list[1, cell1[0], cell1[1]] = True
499
500		if start_coord is None:
501			start_coord = tuple(np.random.randint(0, n) for n in grid_shape_)  # type: ignore[assignment]
502
503		generation_meta: dict = dict(
504			func_name="gen_kruskal",
505			grid_shape=grid_shape_,
506			start_coord=start_coord,
507			algorithm="kruskal",
508			fully_connected=True,
509		)
510		return LatticeMaze(
511			connection_list=connection_list, generation_meta=generation_meta
512		)
513
514	@staticmethod
515	def gen_recursive_division(
516		grid_shape: "Coord | CoordTup",
517		lattice_dim: int = 2,
518		start_coord: "Coord | None" = None,
519	) -> "LatticeMaze":
520		"""Generate a maze using the recursive division algorithm.
521
522		This function generates a maze by recursively dividing the grid with walls and carving a single
523		passage through each wall. The algorithm begins with a fully connected grid (i.e. every pair of adjacent
524		cells is connected) and then removes connections along a chosen division line—leaving one gap as a passage.
525		The resulting maze is a perfect maze, meaning there is exactly one path between any two cells.
526
527		# Parameters:
528		- `grid_shape : Coord | CoordTup`
529			The shape of the maze grid (e.g., `(n_rows, n_cols)`).
530		- `lattice_dim : int`
531			The lattice dimension (default is `2`).
532		- `start_coord : Coord | None`
533			Optionally, specify a starting coordinate. If `None`, a random coordinate is chosen.
534		- `**kwargs`
535			Additional keyword arguments (currently unused).
536
537		# Returns:
538		- `LatticeMaze`
539			A maze represented by a connection list, generated using recursive division.
540
541		# Usage:
542		```python
543		maze = gen_recursive_division((10, 10))
544		```
545		"""
546		assert lattice_dim == 2, (  # noqa: PLR2004
547			"Recursive division algorithm is only implemented for 2D lattices."
548		)
549		# Convert grid_shape to a tuple of ints.
550		grid_shape_: CoordTup = tuple(int(x) for x in grid_shape)  # type: ignore[assignment]
551		n_rows, n_cols = grid_shape_
552
553		# Initialize connection_list as a fully connected grid.
554		# For horizontal connections: for each cell (i,j) with i in [0, n_rows-2], set connection to True.
555		# For vertical connections: for each cell (i,j) with j in [0, n_cols-2], set connection to True.
556		connection_list = np.zeros((2, n_rows, n_cols), dtype=bool)
557		connection_list[0, : n_rows - 1, :] = True
558		connection_list[1, :, : n_cols - 1] = True
559
560		def divide(x: int, y: int, width: int, height: int) -> None:
561			"""Recursively divide the region starting at (x, y) with the given width and height.
562
563			Removes connections along the chosen division line except for one randomly chosen gap.
564			"""
565			if width < 2 or height < 2:  # noqa: PLR2004
566				return
567
568			if width > height:
569				# Vertical division.
570				wall_col = random.randint(x + 1, x + width - 1)
571				gap_row = random.randint(y, y + height - 1)
572				for row in range(y, y + height):
573					if row == gap_row:
574						continue
575					# Remove the vertical connection between (row, wall_col-1) and (row, wall_col).
576					if wall_col - 1 < n_cols - 1:
577						connection_list[1, row, wall_col - 1] = False
578				# Recurse on the left and right subregions.
579				divide(x, y, wall_col - x, height)
580				divide(wall_col, y, x + width - wall_col, height)
581			else:
582				# Horizontal division.
583				wall_row = random.randint(y + 1, y + height - 1)
584				gap_col = random.randint(x, x + width - 1)
585				for col in range(x, x + width):
586					if col == gap_col:
587						continue
588					# Remove the horizontal connection between (wall_row-1, col) and (wall_row, col).
589					if wall_row - 1 < n_rows - 1:
590						connection_list[0, wall_row - 1, col] = False
591				# Recurse on the top and bottom subregions.
592				divide(x, y, width, wall_row - y)
593				divide(x, wall_row, width, y + height - wall_row)
594
595		# Begin the division on the full grid.
596		divide(0, 0, n_cols, n_rows)
597
598		if start_coord is None:
599			start_coord = tuple(np.random.randint(0, n) for n in grid_shape_)  # type: ignore[assignment]
600
601		generation_meta: dict = dict(
602			func_name="gen_recursive_division",
603			grid_shape=grid_shape_,
604			start_coord=start_coord,
605			algorithm="recursive_division",
606			fully_connected=True,
607		)
608		return LatticeMaze(
609			connection_list=connection_list, generation_meta=generation_meta
610		)

namespace for lattice maze generation algorithms

examples of generated mazes can be found here: https://understanding-search.github.io/maze-dataset/examples/maze_examples.html

@staticmethod
def gen_dfs( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], lattice_dim: int = 2, accessible_cells: float | None = None, max_tree_depth: float | None = None, do_forks: bool = True, randomized_stack: bool = False, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
 61	@staticmethod
 62	def gen_dfs(
 63		grid_shape: Coord | CoordTup,
 64		lattice_dim: int = 2,
 65		accessible_cells: float | None = None,
 66		max_tree_depth: float | None = None,
 67		do_forks: bool = True,
 68		randomized_stack: bool = False,
 69		start_coord: Coord | None = None,
 70	) -> LatticeMaze:
 71		"""generate a lattice maze using depth first search, iterative
 72
 73		# Arguments
 74		- `grid_shape: Coord`: the shape of the grid
 75		- `lattice_dim: int`: the dimension of the lattice
 76			(default: `2`)
 77		- `accessible_cells: int | float |None`: the number of accessible cells in the maze. If `None`, defaults to the total number of cells in the grid. if a float, asserts it is <= 1 and treats it as a proportion of **total cells**
 78			(default: `None`)
 79		- `max_tree_depth: int | float | None`: the maximum depth of the tree. If `None`, defaults to `2 * accessible_cells`. if a float, asserts it is <= 1 and treats it as a proportion of the **sum of the grid shape**
 80			(default: `None`)
 81		- `do_forks: bool`: whether to allow forks in the maze. If `False`, the maze will be have no forks and will be a simple hallway.
 82		- `start_coord: Coord | None`: the starting coordinate of the generation algorithm. If `None`, defaults to a random coordinate.
 83
 84		# algorithm
 85		1. Choose the initial cell, mark it as visited and push it to the stack
 86		2. While the stack is not empty
 87			1. Pop a cell from the stack and make it a current cell
 88			2. If the current cell has any neighbours which have not been visited
 89				1. Push the current cell to the stack
 90				2. Choose one of the unvisited neighbours
 91				3. Remove the wall between the current cell and the chosen cell
 92				4. Mark the chosen cell as visited and push it to the stack
 93		"""
 94		# Default values if no constraints have been passed
 95		grid_shape_: Coord = np.array(grid_shape)
 96		n_total_cells: int = int(np.prod(grid_shape_))
 97
 98		n_accessible_cells: int
 99		if accessible_cells is None:
100			n_accessible_cells = n_total_cells
101		elif isinstance(accessible_cells, float):
102			assert accessible_cells <= 1, (
103				f"accessible_cells must be an int (count) or a float in the range [0, 1] (proportion), got {accessible_cells}"
104			)
105
106			n_accessible_cells = int(accessible_cells * n_total_cells)
107		else:
108			assert isinstance(accessible_cells, int)
109			n_accessible_cells = accessible_cells
110
111		if max_tree_depth is None:
112			max_tree_depth = (
113				2 * n_total_cells
114			)  # We define max tree depth counting from the start coord in two directions. Therefore we divide by two in the if clause for neighboring sites later and multiply by two here.
115		elif isinstance(max_tree_depth, float):
116			assert max_tree_depth <= 1, (
117				f"max_tree_depth must be an int (count) or a float in the range [0, 1] (proportion), got {max_tree_depth}"
118			)
119
120			max_tree_depth = int(max_tree_depth * np.sum(grid_shape_))
121
122		# choose a random start coord
123		start_coord = _random_start_coord(grid_shape_, start_coord)
124
125		# initialize the maze with no connections
126		connection_list: ConnectionList = np.zeros(
127			(lattice_dim, grid_shape_[0], grid_shape_[1]),
128			dtype=np.bool_,
129		)
130
131		# initialize the stack with the target coord
132		visited_cells: set[tuple[int, int]] = set()
133		visited_cells.add(tuple(start_coord))  # this wasnt a bug after all lol
134		stack: list[Coord] = [start_coord]
135
136		# initialize tree_depth_counter
137		current_tree_depth: int = 1
138
139		# loop until the stack is empty or n_connected_cells is reached
140		while stack and (len(visited_cells) < n_accessible_cells):
141			# get the current coord from the stack
142			current_coord: Coord
143			if randomized_stack:
144				current_coord = stack.pop(random.randint(0, len(stack) - 1))
145			else:
146				current_coord = stack.pop()
147
148			# filter neighbors by being within grid bounds and being unvisited
149			unvisited_neighbors_deltas: list[tuple[Coord, Coord]] = [
150				(neighbor, delta)
151				for neighbor, delta in zip(
152					current_coord + NEIGHBORS_MASK,
153					NEIGHBORS_MASK,
154					strict=False,
155				)
156				if (
157					(tuple(neighbor) not in visited_cells)
158					and (0 <= neighbor[0] < grid_shape_[0])
159					and (0 <= neighbor[1] < grid_shape_[1])
160				)
161			]
162
163			# don't continue if max_tree_depth/2 is already reached (divide by 2 because we can branch to multiple directions)
164			if unvisited_neighbors_deltas and (
165				current_tree_depth <= max_tree_depth / 2
166			):
167				# if we want a maze without forks, simply don't add the current coord back to the stack
168				if do_forks and (len(unvisited_neighbors_deltas) > 1):
169					stack.append(current_coord)
170
171				# choose one of the unvisited neighbors
172				chosen_neighbor, delta = random.choice(unvisited_neighbors_deltas)
173
174				# add connection
175				dim: int = int(np.argmax(np.abs(delta)))
176				# if positive, down/right from current coord
177				# if negative, up/left from current coord (down/right from neighbor)
178				clist_node: Coord = (
179					current_coord if (delta.sum() > 0) else chosen_neighbor
180				)
181				connection_list[dim, clist_node[0], clist_node[1]] = True
182
183				# add to visited cells and stack
184				visited_cells.add(tuple(chosen_neighbor))
185				stack.append(chosen_neighbor)
186
187				# Update current tree depth
188				current_tree_depth += 1
189			else:
190				current_tree_depth -= 1
191
192		return LatticeMaze(
193			connection_list=connection_list,
194			generation_meta=dict(
195				func_name="gen_dfs",
196				grid_shape=grid_shape_,
197				start_coord=start_coord,
198				n_accessible_cells=int(n_accessible_cells),
199				max_tree_depth=int(max_tree_depth),
200				# oh my god this took so long to track down. its almost 5am and I've spent like 2 hours on this bug
201				# it was checking that len(visited_cells) == n_accessible_cells, but this means that the maze is
202				# treated as fully connected even when it is most certainly not, causing solving the maze to break
203				fully_connected=bool(len(visited_cells) == n_total_cells),
204				visited_cells={tuple(int(x) for x in coord) for coord in visited_cells},
205			),
206		)

generate a lattice maze using depth first search, iterative

Arguments

  • grid_shape: Coord: the shape of the grid
  • lattice_dim: int: the dimension of the lattice (default: 2)
  • accessible_cells: int | float |None: the number of accessible cells in the maze. If None, defaults to the total number of cells in the grid. if a float, asserts it is <= 1 and treats it as a proportion of total cells (default: None)
  • max_tree_depth: int | float | None: the maximum depth of the tree. If None, defaults to 2 * accessible_cells. if a float, asserts it is <= 1 and treats it as a proportion of the sum of the grid shape (default: None)
  • do_forks: bool: whether to allow forks in the maze. If False, the maze will be have no forks and will be a simple hallway.
  • start_coord: Coord | None: the starting coordinate of the generation algorithm. If None, defaults to a random coordinate.

algorithm

  1. Choose the initial cell, mark it as visited and push it to the stack
  2. While the stack is not empty
    1. Pop a cell from the stack and make it a current cell
    2. If the current cell has any neighbours which have not been visited
      1. Push the current cell to the stack
      2. Choose one of the unvisited neighbours
      3. Remove the wall between the current cell and the chosen cell
      4. Mark the chosen cell as visited and push it to the stack
@staticmethod
def gen_prim( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], lattice_dim: int = 2, accessible_cells: float | None = None, max_tree_depth: float | None = None, do_forks: bool = True, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
208	@staticmethod
209	def gen_prim(
210		grid_shape: Coord | CoordTup,
211		lattice_dim: int = 2,
212		accessible_cells: float | None = None,
213		max_tree_depth: float | None = None,
214		do_forks: bool = True,
215		start_coord: Coord | None = None,
216	) -> LatticeMaze:
217		"(broken!) generate a lattice maze using Prim's algorithm"
218		warnings.warn(
219			"gen_prim does not correctly implement prim's algorithm, see issue: https://github.com/understanding-search/maze-dataset/issues/12",
220		)
221		return LatticeMazeGenerators.gen_dfs(
222			grid_shape=grid_shape,
223			lattice_dim=lattice_dim,
224			accessible_cells=accessible_cells,
225			max_tree_depth=max_tree_depth,
226			do_forks=do_forks,
227			start_coord=start_coord,
228			randomized_stack=True,
229		)

(broken!) generate a lattice maze using Prim's algorithm

@staticmethod
def gen_wilson( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], **kwargs) -> LatticeMaze:
231	@staticmethod
232	def gen_wilson(
233		grid_shape: Coord | CoordTup,
234		**kwargs,
235	) -> LatticeMaze:
236		"""Generate a lattice maze using Wilson's algorithm.
237
238		# Algorithm
239		Wilson's algorithm generates an unbiased (random) maze
240		sampled from the uniform distribution over all mazes, using loop-erased random walks. The generated maze is
241		acyclic and all cells are part of a unique connected space.
242		https://en.wikipedia.org/wiki/Maze_generation_algorithm#Wilson's_algorithm
243		"""
244		assert not kwargs, (
245			f"gen_wilson does not take any additional arguments, got {kwargs = }"
246		)
247
248		grid_shape_: Coord = np.array(grid_shape)
249
250		# Initialize grid and visited cells
251		connection_list: ConnectionList = np.zeros((2, *grid_shape_), dtype=np.bool_)
252		visited: Bool[np.ndarray, "x y"] = np.zeros(grid_shape_, dtype=np.bool_)
253
254		# Choose a random cell and mark it as visited
255		start_coord: Coord = _random_start_coord(grid_shape_, None)
256		visited[start_coord[0], start_coord[1]] = True
257		del start_coord
258
259		while not visited.all():
260			# Perform loop-erased random walk from another random cell
261
262			# Choose walk_start only from unvisited cells
263			unvisited_coords: CoordArray = np.column_stack(np.where(~visited))
264			walk_start: Coord = unvisited_coords[
265				np.random.choice(unvisited_coords.shape[0])
266			]
267
268			# Perform the random walk
269			path: list[Coord] = [walk_start]
270			current: Coord = walk_start
271
272			# exit the loop once the current path hits a visited cell
273			while not visited[current[0], current[1]]:
274				# find a valid neighbor (one always exists on a lattice)
275				neighbors: CoordArray = get_neighbors_in_bounds(current, grid_shape_)
276				next_cell: Coord = neighbors[np.random.choice(neighbors.shape[0])]
277
278				# Check for loop
279				loop_exit: int | None = None
280				for i, p in enumerate(path):
281					if np.array_equal(next_cell, p):
282						loop_exit = i
283						break
284
285				# erase the loop, or continue the walk
286				if loop_exit is not None:
287					# this removes everything after and including the loop start
288					path = path[: loop_exit + 1]
289					# reset current cell to end of path
290					current = path[-1]
291				else:
292					path.append(next_cell)
293					current = next_cell
294
295			# Add the path to the maze
296			for i in range(len(path) - 1):
297				c_1: Coord = path[i]
298				c_2: Coord = path[i + 1]
299
300				# find the dimension of the connection
301				delta: Coord = c_2 - c_1
302				dim: int = int(np.argmax(np.abs(delta)))
303
304				# if positive, down/right from current coord
305				# if negative, up/left from current coord (down/right from neighbor)
306				clist_node: Coord = c_1 if (delta.sum() > 0) else c_2
307				connection_list[dim, clist_node[0], clist_node[1]] = True
308				visited[c_1[0], c_1[1]] = True
309				# we dont add c_2 because the last c_2 will have already been visited
310
311		return LatticeMaze(
312			connection_list=connection_list,
313			generation_meta=dict(
314				func_name="gen_wilson",
315				grid_shape=grid_shape_,
316				fully_connected=True,
317			),
318		)

Generate a lattice maze using Wilson's algorithm.

Algorithm

Wilson's algorithm generates an unbiased (random) maze sampled from the uniform distribution over all mazes, using loop-erased random walks. The generated maze is acyclic and all cells are part of a unique connected space. https://en.wikipedia.org/wiki/Maze_generation_algorithm#Wilson's_algorithm

@staticmethod
def gen_percolation( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], p: float = 0.4, lattice_dim: int = 2, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
320	@staticmethod
321	def gen_percolation(
322		grid_shape: Coord | CoordTup,
323		p: float = 0.4,
324		lattice_dim: int = 2,
325		start_coord: Coord | None = None,
326	) -> LatticeMaze:
327		"""generate a lattice maze using simple percolation
328
329		note that p in the range (0.4, 0.7) gives the most interesting mazes
330
331		# Arguments
332		- `grid_shape: Coord`: the shape of the grid
333		- `lattice_dim: int`: the dimension of the lattice (default: `2`)
334		- `p: float`: the probability of a cell being accessible (default: `0.5`)
335		- `start_coord: Coord | None`: the starting coordinate for the connected component (default: `None` will give a random start)
336		"""
337		assert p >= 0 and p <= 1, f"p must be between 0 and 1, got {p}"  # noqa: PT018
338		grid_shape_: Coord = np.array(grid_shape)
339
340		start_coord = _random_start_coord(grid_shape_, start_coord)
341
342		connection_list: ConnectionList = np.random.rand(lattice_dim, *grid_shape_) < p
343
344		connection_list = _fill_edges_with_walls(connection_list)
345
346		output: LatticeMaze = LatticeMaze(
347			connection_list=connection_list,
348			generation_meta=dict(
349				func_name="gen_percolation",
350				grid_shape=grid_shape_,
351				percolation_p=p,
352				start_coord=start_coord,
353			),
354		)
355
356		# generation_meta is sometimes None, but not here since we just made it a dict above
357		output.generation_meta["visited_cells"] = output.gen_connected_component_from(  # type: ignore[index]
358			start_coord,
359		)
360
361		return output

generate a lattice maze using simple percolation

note that p in the range (0.4, 0.7) gives the most interesting mazes

Arguments

  • grid_shape: Coord: the shape of the grid
  • lattice_dim: int: the dimension of the lattice (default: 2)
  • p: float: the probability of a cell being accessible (default: 0.5)
  • start_coord: Coord | None: the starting coordinate for the connected component (default: None will give a random start)
@staticmethod
def gen_dfs_percolation( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], p: float = 0.4, lattice_dim: int = 2, accessible_cells: int | None = None, max_tree_depth: int | None = None, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
363	@staticmethod
364	def gen_dfs_percolation(
365		grid_shape: Coord | CoordTup,
366		p: float = 0.4,
367		lattice_dim: int = 2,
368		accessible_cells: int | None = None,
369		max_tree_depth: int | None = None,
370		start_coord: Coord | None = None,
371	) -> LatticeMaze:
372		"""dfs and then percolation (adds cycles)"""
373		grid_shape_: Coord = np.array(grid_shape)
374		start_coord = _random_start_coord(grid_shape_, start_coord)
375
376		# generate initial maze via dfs
377		maze: LatticeMaze = LatticeMazeGenerators.gen_dfs(
378			grid_shape=grid_shape_,
379			lattice_dim=lattice_dim,
380			accessible_cells=accessible_cells,
381			max_tree_depth=max_tree_depth,
382			start_coord=start_coord,
383		)
384
385		# percolate
386		connection_list_perc: np.ndarray = (
387			np.random.rand(*maze.connection_list.shape) < p
388		)
389		connection_list_perc = _fill_edges_with_walls(connection_list_perc)
390
391		maze.__dict__["connection_list"] = np.logical_or(
392			maze.connection_list,
393			connection_list_perc,
394		)
395
396		# generation_meta is sometimes None, but not here since we just made it a dict above
397		maze.generation_meta["func_name"] = "gen_dfs_percolation"  # type: ignore[index]
398		maze.generation_meta["percolation_p"] = p  # type: ignore[index]
399		maze.generation_meta["visited_cells"] = maze.gen_connected_component_from(  # type: ignore[index]
400			start_coord,
401		)
402
403		return maze

dfs and then percolation (adds cycles)

@staticmethod
def gen_kruskal( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], lattice_dim: int = 2, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
405	@staticmethod
406	def gen_kruskal(
407		grid_shape: "Coord | CoordTup",
408		lattice_dim: int = 2,
409		start_coord: "Coord | None" = None,
410	) -> "LatticeMaze":
411		"""Generate a maze using Kruskal's algorithm.
412
413		This function generates a random spanning tree over a grid using Kruskal's algorithm.
414		Each cell is treated as a node, and all valid adjacent edges are listed and processed
415		in random order. An edge is added (i.e. its passage carved) only if it connects two cells
416		that are not already connected. The resulting maze is a perfect maze (i.e. a spanning tree)
417		without cycles.
418
419		https://en.wikipedia.org/wiki/Kruskal's_algorithm
420
421		# Parameters:
422		- `grid_shape : Coord | CoordTup`
423			The shape of the maze grid (for example, `(n_rows, n_cols)`).
424		- `lattice_dim : int`
425			The lattice dimension (default is `2`).
426		- `start_coord : Coord | None`
427			Optionally, specify a starting coordinate. If `None`, a random coordinate will be chosen.
428		- `**kwargs`
429			Additional keyword arguments (currently unused).
430
431		# Returns:
432		- `LatticeMaze`
433			A maze represented by a connection list, generated as a spanning tree using Kruskal's algorithm.
434
435		# Usage:
436		```python
437		maze = gen_kruskal((10, 10))
438		```
439		"""
440		assert lattice_dim == 2, (  # noqa: PLR2004
441			"Kruskal's algorithm is only implemented for 2D lattices."
442		)
443		# Convert grid_shape to a tuple of ints
444		grid_shape_: CoordTup = tuple(int(x) for x in grid_shape)  # type: ignore[assignment]
445		n_rows, n_cols = grid_shape_
446
447		# Initialize union-find data structure.
448		parent: dict[tuple[int, int], tuple[int, int]] = {}
449
450		def find(cell: tuple[int, int]) -> tuple[int, int]:
451			while parent[cell] != cell:
452				parent[cell] = parent[parent[cell]]
453				cell = parent[cell]
454			return cell
455
456		def union(cell1: tuple[int, int], cell2: tuple[int, int]) -> None:
457			root1 = find(cell1)
458			root2 = find(cell2)
459			parent[root2] = root1
460
461		# Initialize each cell as its own set.
462		for i in range(n_rows):
463			for j in range(n_cols):
464				parent[(i, j)] = (i, j)
465
466		# List all possible edges.
467		# For vertical edges (i.e. connecting a cell to its right neighbor):
468		edges: list[tuple[tuple[int, int], tuple[int, int], int]] = []
469		for i in range(n_rows):
470			for j in range(n_cols - 1):
471				edges.append(((i, j), (i, j + 1), 1))
472		# For horizontal edges (i.e. connecting a cell to its bottom neighbor):
473		for i in range(n_rows - 1):
474			for j in range(n_cols):
475				edges.append(((i, j), (i + 1, j), 0))
476
477		# Shuffle the list of edges.
478		import random
479
480		random.shuffle(edges)
481
482		# Initialize connection_list with no connections.
483		# connection_list[0] stores downward connections (from cell (i,j) to (i+1,j)).
484		# connection_list[1] stores rightward connections (from cell (i,j) to (i,j+1)).
485		import numpy as np
486
487		connection_list = np.zeros((2, n_rows, n_cols), dtype=bool)
488
489		# Process each edge; if it connects two different trees, union them and carve the passage.
490		for cell1, cell2, direction in edges:
491			if find(cell1) != find(cell2):
492				union(cell1, cell2)
493				if direction == 0:
494					# Horizontal edge: connection is stored in connection_list[0] at cell1.
495					connection_list[0, cell1[0], cell1[1]] = True
496				else:
497					# Vertical edge: connection is stored in connection_list[1] at cell1.
498					connection_list[1, cell1[0], cell1[1]] = True
499
500		if start_coord is None:
501			start_coord = tuple(np.random.randint(0, n) for n in grid_shape_)  # type: ignore[assignment]
502
503		generation_meta: dict = dict(
504			func_name="gen_kruskal",
505			grid_shape=grid_shape_,
506			start_coord=start_coord,
507			algorithm="kruskal",
508			fully_connected=True,
509		)
510		return LatticeMaze(
511			connection_list=connection_list, generation_meta=generation_meta
512		)

Generate a maze using Kruskal's algorithm.

This function generates a random spanning tree over a grid using Kruskal's algorithm. Each cell is treated as a node, and all valid adjacent edges are listed and processed in random order. An edge is added (i.e. its passage carved) only if it connects two cells that are not already connected. The resulting maze is a perfect maze (i.e. a spanning tree) without cycles.

https://en.wikipedia.org/wiki/Kruskal's_algorithm

Parameters:

  • grid_shape : Coord | CoordTup The shape of the maze grid (for example, (n_rows, n_cols)).
  • lattice_dim : int The lattice dimension (default is 2).
  • start_coord : Coord | None Optionally, specify a starting coordinate. If None, a random coordinate will be chosen.
  • **kwargs Additional keyword arguments (currently unused).

Returns:

  • LatticeMaze A maze represented by a connection list, generated as a spanning tree using Kruskal's algorithm.

Usage:

maze = gen_kruskal((10, 10))
@staticmethod
def gen_recursive_division( grid_shape: jaxtyping.Int8[ndarray, 'row_col=2'] | tuple[int, int], lattice_dim: int = 2, start_coord: jaxtyping.Int8[ndarray, 'row_col=2'] | None = None) -> LatticeMaze:
514	@staticmethod
515	def gen_recursive_division(
516		grid_shape: "Coord | CoordTup",
517		lattice_dim: int = 2,
518		start_coord: "Coord | None" = None,
519	) -> "LatticeMaze":
520		"""Generate a maze using the recursive division algorithm.
521
522		This function generates a maze by recursively dividing the grid with walls and carving a single
523		passage through each wall. The algorithm begins with a fully connected grid (i.e. every pair of adjacent
524		cells is connected) and then removes connections along a chosen division line—leaving one gap as a passage.
525		The resulting maze is a perfect maze, meaning there is exactly one path between any two cells.
526
527		# Parameters:
528		- `grid_shape : Coord | CoordTup`
529			The shape of the maze grid (e.g., `(n_rows, n_cols)`).
530		- `lattice_dim : int`
531			The lattice dimension (default is `2`).
532		- `start_coord : Coord | None`
533			Optionally, specify a starting coordinate. If `None`, a random coordinate is chosen.
534		- `**kwargs`
535			Additional keyword arguments (currently unused).
536
537		# Returns:
538		- `LatticeMaze`
539			A maze represented by a connection list, generated using recursive division.
540
541		# Usage:
542		```python
543		maze = gen_recursive_division((10, 10))
544		```
545		"""
546		assert lattice_dim == 2, (  # noqa: PLR2004
547			"Recursive division algorithm is only implemented for 2D lattices."
548		)
549		# Convert grid_shape to a tuple of ints.
550		grid_shape_: CoordTup = tuple(int(x) for x in grid_shape)  # type: ignore[assignment]
551		n_rows, n_cols = grid_shape_
552
553		# Initialize connection_list as a fully connected grid.
554		# For horizontal connections: for each cell (i,j) with i in [0, n_rows-2], set connection to True.
555		# For vertical connections: for each cell (i,j) with j in [0, n_cols-2], set connection to True.
556		connection_list = np.zeros((2, n_rows, n_cols), dtype=bool)
557		connection_list[0, : n_rows - 1, :] = True
558		connection_list[1, :, : n_cols - 1] = True
559
560		def divide(x: int, y: int, width: int, height: int) -> None:
561			"""Recursively divide the region starting at (x, y) with the given width and height.
562
563			Removes connections along the chosen division line except for one randomly chosen gap.
564			"""
565			if width < 2 or height < 2:  # noqa: PLR2004
566				return
567
568			if width > height:
569				# Vertical division.
570				wall_col = random.randint(x + 1, x + width - 1)
571				gap_row = random.randint(y, y + height - 1)
572				for row in range(y, y + height):
573					if row == gap_row:
574						continue
575					# Remove the vertical connection between (row, wall_col-1) and (row, wall_col).
576					if wall_col - 1 < n_cols - 1:
577						connection_list[1, row, wall_col - 1] = False
578				# Recurse on the left and right subregions.
579				divide(x, y, wall_col - x, height)
580				divide(wall_col, y, x + width - wall_col, height)
581			else:
582				# Horizontal division.
583				wall_row = random.randint(y + 1, y + height - 1)
584				gap_col = random.randint(x, x + width - 1)
585				for col in range(x, x + width):
586					if col == gap_col:
587						continue
588					# Remove the horizontal connection between (wall_row-1, col) and (wall_row, col).
589					if wall_row - 1 < n_rows - 1:
590						connection_list[0, wall_row - 1, col] = False
591				# Recurse on the top and bottom subregions.
592				divide(x, y, width, wall_row - y)
593				divide(x, wall_row, width, y + height - wall_row)
594
595		# Begin the division on the full grid.
596		divide(0, 0, n_cols, n_rows)
597
598		if start_coord is None:
599			start_coord = tuple(np.random.randint(0, n) for n in grid_shape_)  # type: ignore[assignment]
600
601		generation_meta: dict = dict(
602			func_name="gen_recursive_division",
603			grid_shape=grid_shape_,
604			start_coord=start_coord,
605			algorithm="recursive_division",
606			fully_connected=True,
607		)
608		return LatticeMaze(
609			connection_list=connection_list, generation_meta=generation_meta
610		)

Generate a maze using the recursive division algorithm.

This function generates a maze by recursively dividing the grid with walls and carving a single passage through each wall. The algorithm begins with a fully connected grid (i.e. every pair of adjacent cells is connected) and then removes connections along a chosen division line—leaving one gap as a passage. The resulting maze is a perfect maze, meaning there is exactly one path between any two cells.

Parameters:

  • grid_shape : Coord | CoordTup The shape of the maze grid (e.g., (n_rows, n_cols)).
  • lattice_dim : int The lattice dimension (default is 2).
  • start_coord : Coord | None Optionally, specify a starting coordinate. If None, a random coordinate is chosen.
  • **kwargs Additional keyword arguments (currently unused).

Returns:

  • LatticeMaze A maze represented by a connection list, generated using recursive division.

Usage:

maze = gen_recursive_division((10, 10))
Coord = <class 'jaxtyping.Int8[ndarray, 'row_col=2']'>
CoordTup = tuple[int, int]
CoordList = list[tuple[int, int]]
CoordArray = <class 'jaxtyping.Int8[ndarray, 'coord row_col=2']'>
Connection = <class 'jaxtyping.Int8[ndarray, 'coord=2 row_col=2']'>
ConnectionList = <class 'jaxtyping.Bool[ndarray, 'lattice_dim=2 row col']'>
ConnectionArray = <class 'jaxtyping.Int8[ndarray, 'edges leading_trailing_coord=2 row_col=2']'>
SPECIAL_TOKENS = _SPECIAL_TOKENS_BASE(ADJLIST_START='<ADJLIST_START>', ADJLIST_END='<ADJLIST_END>', TARGET_START='<TARGET_START>', TARGET_END='<TARGET_END>', ORIGIN_START='<ORIGIN_START>', ORIGIN_END='<ORIGIN_END>', PATH_START='<PATH_START>', PATH_END='<PATH_END>', CONNECTOR='<-->', ADJACENCY_ENDLINE=';', PADDING='<PADDING>')
VOCAB = _VOCAB_BASE(ADJLIST_START='<ADJLIST_START>', ADJLIST_END='<ADJLIST_END>', TARGET_START='<TARGET_START>', TARGET_END='<TARGET_END>', ORIGIN_START='<ORIGIN_START>', ORIGIN_END='<ORIGIN_END>', PATH_START='<PATH_START>', PATH_END='<PATH_END>', CONNECTOR='<-->', ADJACENCY_ENDLINE=';', PADDING='<PADDING>', COORD_PRE='(', COORD_INTRA=',', COORD_POST=')', TARGET_INTRA='=', TARGET_POST='||', PATH_INTRA=':', PATH_POST='THEN', NEGATIVE='-', UNKNOWN='<UNK>', TARGET_A='TARGET_A', TARGET_B='TARGET_B', TARGET_C='TARGET_C', TARGET_D='TARGET_D', TARGET_E='TARGET_E', TARGET_F='TARGET_F', TARGET_G='TARGET_G', TARGET_H='TARGET_H', TARGET_I='TARGET_I', TARGET_J='TARGET_J', TARGET_K='TARGET_K', TARGET_L='TARGET_L', TARGET_M='TARGET_M', TARGET_N='TARGET_N', TARGET_O='TARGET_O', TARGET_P='TARGET_P', TARGET_Q='TARGET_Q', TARGET_R='TARGET_R', TARGET_S='TARGET_S', TARGET_T='TARGET_T', TARGET_U='TARGET_U', TARGET_V='TARGET_V', TARGET_W='TARGET_W', TARGET_X='TARGET_X', TARGET_Y='TARGET_Y', TARGET_Z='TARGET_Z', TARGET_NORTH='TARGET_NORTH', TARGET_SOUTH='TARGET_SOUTH', TARGET_EAST='TARGET_EAST', TARGET_WEST='TARGET_WEST', TARGET_NORTHEAST='TARGET_NORTHEAST', TARGET_NORTHWEST='TARGET_NORTHWEST', TARGET_SOUTHEAST='TARGET_SOUTHEAST', TARGET_SOUTHWEST='TARGET_SOUTHWEST', TARGET_CENTER='TARGET_CENTER', PATH_NORTH='NORTH', PATH_SOUTH='SOUTH', PATH_EAST='EAST', PATH_WEST='WEST', PATH_FORWARD='FORWARD', PATH_BACKWARD='BACKWARD', PATH_LEFT='LEFT', PATH_RIGHT='RIGHT', PATH_STAY='STAY', I_000='+0', I_001='+1', I_002='+2', I_003='+3', I_004='+4', I_005='+5', I_006='+6', I_007='+7', I_008='+8', I_009='+9', I_010='+10', I_011='+11', I_012='+12', I_013='+13', I_014='+14', I_015='+15', I_016='+16', I_017='+17', I_018='+18', I_019='+19', I_020='+20', I_021='+21', I_022='+22', I_023='+23', I_024='+24', I_025='+25', I_026='+26', I_027='+27', I_028='+28', I_029='+29', I_030='+30', I_031='+31', I_032='+32', I_033='+33', I_034='+34', I_035='+35', I_036='+36', I_037='+37', I_038='+38', I_039='+39', I_040='+40', I_041='+41', I_042='+42', I_043='+43', I_044='+44', I_045='+45', I_046='+46', I_047='+47', I_048='+48', I_049='+49', I_050='+50', I_051='+51', I_052='+52', I_053='+53', I_054='+54', I_055='+55', I_056='+56', I_057='+57', I_058='+58', I_059='+59', I_060='+60', I_061='+61', I_062='+62', I_063='+63', I_064='+64', I_065='+65', I_066='+66', I_067='+67', I_068='+68', I_069='+69', I_070='+70', I_071='+71', I_072='+72', I_073='+73', I_074='+74', I_075='+75', I_076='+76', I_077='+77', I_078='+78', I_079='+79', I_080='+80', I_081='+81', I_082='+82', I_083='+83', I_084='+84', I_085='+85', I_086='+86', I_087='+87', I_088='+88', I_089='+89', I_090='+90', I_091='+91', I_092='+92', I_093='+93', I_094='+94', I_095='+95', I_096='+96', I_097='+97', I_098='+98', I_099='+99', I_100='+100', I_101='+101', I_102='+102', I_103='+103', I_104='+104', I_105='+105', I_106='+106', I_107='+107', I_108='+108', I_109='+109', I_110='+110', I_111='+111', I_112='+112', I_113='+113', I_114='+114', I_115='+115', I_116='+116', I_117='+117', I_118='+118', I_119='+119', I_120='+120', I_121='+121', I_122='+122', I_123='+123', I_124='+124', I_125='+125', I_126='+126', I_127='+127', I_128='+128', I_129='+129', I_130='+130', I_131='+131', I_132='+132', I_133='+133', I_134='+134', I_135='+135', I_136='+136', I_137='+137', I_138='+138', I_139='+139', I_140='+140', I_141='+141', I_142='+142', I_143='+143', I_144='+144', I_145='+145', I_146='+146', I_147='+147', I_148='+148', I_149='+149', I_150='+150', I_151='+151', I_152='+152', I_153='+153', I_154='+154', I_155='+155', I_156='+156', I_157='+157', I_158='+158', I_159='+159', I_160='+160', I_161='+161', I_162='+162', I_163='+163', I_164='+164', I_165='+165', I_166='+166', I_167='+167', I_168='+168', I_169='+169', I_170='+170', I_171='+171', I_172='+172', I_173='+173', I_174='+174', I_175='+175', I_176='+176', I_177='+177', I_178='+178', I_179='+179', I_180='+180', I_181='+181', I_182='+182', I_183='+183', I_184='+184', I_185='+185', I_186='+186', I_187='+187', I_188='+188', I_189='+189', I_190='+190', I_191='+191', I_192='+192', I_193='+193', I_194='+194', I_195='+195', I_196='+196', I_197='+197', I_198='+198', I_199='+199', I_200='+200', I_201='+201', I_202='+202', I_203='+203', I_204='+204', I_205='+205', I_206='+206', I_207='+207', I_208='+208', I_209='+209', I_210='+210', I_211='+211', I_212='+212', I_213='+213', I_214='+214', I_215='+215', I_216='+216', I_217='+217', I_218='+218', I_219='+219', I_220='+220', I_221='+221', I_222='+222', I_223='+223', I_224='+224', I_225='+225', I_226='+226', I_227='+227', I_228='+228', I_229='+229', I_230='+230', I_231='+231', I_232='+232', I_233='+233', I_234='+234', I_235='+235', I_236='+236', I_237='+237', I_238='+238', I_239='+239', I_240='+240', I_241='+241', I_242='+242', I_243='+243', I_244='+244', I_245='+245', I_246='+246', I_247='+247', I_248='+248', I_249='+249', I_250='+250', I_251='+251', I_252='+252', I_253='+253', I_254='+254', I_255='+255', CTT_0='0', CTT_1='1', CTT_2='2', CTT_3='3', CTT_4='4', CTT_5='5', CTT_6='6', CTT_7='7', CTT_8='8', CTT_9='9', CTT_10='10', CTT_11='11', CTT_12='12', CTT_13='13', CTT_14='14', CTT_15='15', CTT_16='16', CTT_17='17', CTT_18='18', CTT_19='19', CTT_20='20', CTT_21='21', CTT_22='22', CTT_23='23', CTT_24='24', CTT_25='25', CTT_26='26', CTT_27='27', CTT_28='28', CTT_29='29', CTT_30='30', CTT_31='31', CTT_32='32', CTT_33='33', CTT_34='34', CTT_35='35', CTT_36='36', CTT_37='37', CTT_38='38', CTT_39='39', CTT_40='40', CTT_41='41', CTT_42='42', CTT_43='43', CTT_44='44', CTT_45='45', CTT_46='46', CTT_47='47', CTT_48='48', CTT_49='49', CTT_50='50', CTT_51='51', CTT_52='52', CTT_53='53', CTT_54='54', CTT_55='55', CTT_56='56', CTT_57='57', CTT_58='58', CTT_59='59', CTT_60='60', CTT_61='61', CTT_62='62', CTT_63='63', CTT_64='64', CTT_65='65', CTT_66='66', CTT_67='67', CTT_68='68', CTT_69='69', CTT_70='70', CTT_71='71', CTT_72='72', CTT_73='73', CTT_74='74', CTT_75='75', CTT_76='76', CTT_77='77', CTT_78='78', CTT_79='79', CTT_80='80', CTT_81='81', CTT_82='82', CTT_83='83', CTT_84='84', CTT_85='85', CTT_86='86', CTT_87='87', CTT_88='88', CTT_89='89', CTT_90='90', CTT_91='91', CTT_92='92', CTT_93='93', CTT_94='94', CTT_95='95', CTT_96='96', CTT_97='97', CTT_98='98', CTT_99='99', CTT_100='100', CTT_101='101', CTT_102='102', CTT_103='103', CTT_104='104', CTT_105='105', CTT_106='106', CTT_107='107', CTT_108='108', CTT_109='109', CTT_110='110', CTT_111='111', CTT_112='112', CTT_113='113', CTT_114='114', CTT_115='115', CTT_116='116', CTT_117='117', CTT_118='118', CTT_119='119', CTT_120='120', CTT_121='121', CTT_122='122', CTT_123='123', CTT_124='124', CTT_125='125', CTT_126='126', CTT_127='127', I_N256='-256', I_N255='-255', I_N254='-254', I_N253='-253', I_N252='-252', I_N251='-251', I_N250='-250', I_N249='-249', I_N248='-248', I_N247='-247', I_N246='-246', I_N245='-245', I_N244='-244', I_N243='-243', I_N242='-242', I_N241='-241', I_N240='-240', I_N239='-239', I_N238='-238', I_N237='-237', I_N236='-236', I_N235='-235', I_N234='-234', I_N233='-233', I_N232='-232', I_N231='-231', I_N230='-230', I_N229='-229', I_N228='-228', I_N227='-227', I_N226='-226', I_N225='-225', I_N224='-224', I_N223='-223', I_N222='-222', I_N221='-221', I_N220='-220', I_N219='-219', I_N218='-218', I_N217='-217', I_N216='-216', I_N215='-215', I_N214='-214', I_N213='-213', I_N212='-212', I_N211='-211', I_N210='-210', I_N209='-209', I_N208='-208', I_N207='-207', I_N206='-206', I_N205='-205', I_N204='-204', I_N203='-203', I_N202='-202', I_N201='-201', I_N200='-200', I_N199='-199', I_N198='-198', I_N197='-197', I_N196='-196', I_N195='-195', I_N194='-194', I_N193='-193', I_N192='-192', I_N191='-191', I_N190='-190', I_N189='-189', I_N188='-188', I_N187='-187', I_N186='-186', I_N185='-185', I_N184='-184', I_N183='-183', I_N182='-182', I_N181='-181', I_N180='-180', I_N179='-179', I_N178='-178', I_N177='-177', I_N176='-176', I_N175='-175', I_N174='-174', I_N173='-173', I_N172='-172', I_N171='-171', I_N170='-170', I_N169='-169', I_N168='-168', I_N167='-167', I_N166='-166', I_N165='-165', I_N164='-164', I_N163='-163', I_N162='-162', I_N161='-161', I_N160='-160', I_N159='-159', I_N158='-158', I_N157='-157', I_N156='-156', I_N155='-155', I_N154='-154', I_N153='-153', I_N152='-152', I_N151='-151', I_N150='-150', I_N149='-149', I_N148='-148', I_N147='-147', I_N146='-146', I_N145='-145', I_N144='-144', I_N143='-143', I_N142='-142', I_N141='-141', I_N140='-140', I_N139='-139', I_N138='-138', I_N137='-137', I_N136='-136', I_N135='-135', I_N134='-134', I_N133='-133', I_N132='-132', I_N131='-131', I_N130='-130', I_N129='-129', I_N128='-128', I_N127='-127', I_N126='-126', I_N125='-125', I_N124='-124', I_N123='-123', I_N122='-122', I_N121='-121', I_N120='-120', I_N119='-119', I_N118='-118', I_N117='-117', I_N116='-116', I_N115='-115', I_N114='-114', I_N113='-113', I_N112='-112', I_N111='-111', I_N110='-110', I_N109='-109', I_N108='-108', I_N107='-107', I_N106='-106', I_N105='-105', I_N104='-104', I_N103='-103', I_N102='-102', I_N101='-101', I_N100='-100', I_N099='-99', I_N098='-98', I_N097='-97', I_N096='-96', I_N095='-95', I_N094='-94', I_N093='-93', I_N092='-92', I_N091='-91', I_N090='-90', I_N089='-89', I_N088='-88', I_N087='-87', I_N086='-86', I_N085='-85', I_N084='-84', I_N083='-83', I_N082='-82', I_N081='-81', I_N080='-80', I_N079='-79', I_N078='-78', I_N077='-77', I_N076='-76', I_N075='-75', I_N074='-74', I_N073='-73', I_N072='-72', I_N071='-71', I_N070='-70', I_N069='-69', I_N068='-68', I_N067='-67', I_N066='-66', I_N065='-65', I_N064='-64', I_N063='-63', I_N062='-62', I_N061='-61', I_N060='-60', I_N059='-59', I_N058='-58', I_N057='-57', I_N056='-56', I_N055='-55', I_N054='-54', I_N053='-53', I_N052='-52', I_N051='-51', I_N050='-50', I_N049='-49', I_N048='-48', I_N047='-47', I_N046='-46', I_N045='-45', I_N044='-44', I_N043='-43', I_N042='-42', I_N041='-41', I_N040='-40', I_N039='-39', I_N038='-38', I_N037='-37', I_N036='-36', I_N035='-35', I_N034='-34', I_N033='-33', I_N032='-32', I_N031='-31', I_N030='-30', I_N029='-29', I_N028='-28', I_N027='-27', I_N026='-26', I_N025='-25', I_N024='-24', I_N023='-23', I_N022='-22', I_N021='-21', I_N020='-20', I_N019='-19', I_N018='-18', I_N017='-17', I_N016='-16', I_N015='-15', I_N014='-14', I_N013='-13', I_N012='-12', I_N011='-11', I_N010='-10', I_N009='-9', I_N008='-8', I_N007='-7', I_N006='-6', I_N005='-5', I_N004='-4', I_N003='-3', I_N002='-2', I_N001='-1', PATH_PRE='STEP', ADJLIST_PRE='ADJ_GROUP', ADJLIST_INTRA='&', ADJLIST_WALL='<XX>', RESERVE_708='<RESERVE_708>', RESERVE_709='<RESERVE_709>', RESERVE_710='<RESERVE_710>', RESERVE_711='<RESERVE_711>', RESERVE_712='<RESERVE_712>', RESERVE_713='<RESERVE_713>', RESERVE_714='<RESERVE_714>', RESERVE_715='<RESERVE_715>', RESERVE_716='<RESERVE_716>', RESERVE_717='<RESERVE_717>', RESERVE_718='<RESERVE_718>', RESERVE_719='<RESERVE_719>', RESERVE_720='<RESERVE_720>', RESERVE_721='<RESERVE_721>', RESERVE_722='<RESERVE_722>', RESERVE_723='<RESERVE_723>', RESERVE_724='<RESERVE_724>', RESERVE_725='<RESERVE_725>', RESERVE_726='<RESERVE_726>', RESERVE_727='<RESERVE_727>', RESERVE_728='<RESERVE_728>', RESERVE_729='<RESERVE_729>', RESERVE_730='<RESERVE_730>', RESERVE_731='<RESERVE_731>', RESERVE_732='<RESERVE_732>', RESERVE_733='<RESERVE_733>', RESERVE_734='<RESERVE_734>', RESERVE_735='<RESERVE_735>', RESERVE_736='<RESERVE_736>', RESERVE_737='<RESERVE_737>', RESERVE_738='<RESERVE_738>', RESERVE_739='<RESERVE_739>', RESERVE_740='<RESERVE_740>', RESERVE_741='<RESERVE_741>', RESERVE_742='<RESERVE_742>', RESERVE_743='<RESERVE_743>', RESERVE_744='<RESERVE_744>', RESERVE_745='<RESERVE_745>', RESERVE_746='<RESERVE_746>', RESERVE_747='<RESERVE_747>', RESERVE_748='<RESERVE_748>', RESERVE_749='<RESERVE_749>', RESERVE_750='<RESERVE_750>', RESERVE_751='<RESERVE_751>', RESERVE_752='<RESERVE_752>', RESERVE_753='<RESERVE_753>', RESERVE_754='<RESERVE_754>', RESERVE_755='<RESERVE_755>', RESERVE_756='<RESERVE_756>', RESERVE_757='<RESERVE_757>', RESERVE_758='<RESERVE_758>', RESERVE_759='<RESERVE_759>', RESERVE_760='<RESERVE_760>', RESERVE_761='<RESERVE_761>', RESERVE_762='<RESERVE_762>', RESERVE_763='<RESERVE_763>', RESERVE_764='<RESERVE_764>', RESERVE_765='<RESERVE_765>', RESERVE_766='<RESERVE_766>', RESERVE_767='<RESERVE_767>', RESERVE_768='<RESERVE_768>', RESERVE_769='<RESERVE_769>', RESERVE_770='<RESERVE_770>', RESERVE_771='<RESERVE_771>', RESERVE_772='<RESERVE_772>', RESERVE_773='<RESERVE_773>', RESERVE_774='<RESERVE_774>', RESERVE_775='<RESERVE_775>', RESERVE_776='<RESERVE_776>', RESERVE_777='<RESERVE_777>', RESERVE_778='<RESERVE_778>', RESERVE_779='<RESERVE_779>', RESERVE_780='<RESERVE_780>', RESERVE_781='<RESERVE_781>', RESERVE_782='<RESERVE_782>', RESERVE_783='<RESERVE_783>', RESERVE_784='<RESERVE_784>', RESERVE_785='<RESERVE_785>', RESERVE_786='<RESERVE_786>', RESERVE_787='<RESERVE_787>', RESERVE_788='<RESERVE_788>', RESERVE_789='<RESERVE_789>', RESERVE_790='<RESERVE_790>', RESERVE_791='<RESERVE_791>', RESERVE_792='<RESERVE_792>', RESERVE_793='<RESERVE_793>', RESERVE_794='<RESERVE_794>', RESERVE_795='<RESERVE_795>', RESERVE_796='<RESERVE_796>', RESERVE_797='<RESERVE_797>', RESERVE_798='<RESERVE_798>', RESERVE_799='<RESERVE_799>', RESERVE_800='<RESERVE_800>', RESERVE_801='<RESERVE_801>', RESERVE_802='<RESERVE_802>', RESERVE_803='<RESERVE_803>', RESERVE_804='<RESERVE_804>', RESERVE_805='<RESERVE_805>', RESERVE_806='<RESERVE_806>', RESERVE_807='<RESERVE_807>', RESERVE_808='<RESERVE_808>', RESERVE_809='<RESERVE_809>', RESERVE_810='<RESERVE_810>', RESERVE_811='<RESERVE_811>', RESERVE_812='<RESERVE_812>', RESERVE_813='<RESERVE_813>', RESERVE_814='<RESERVE_814>', RESERVE_815='<RESERVE_815>', RESERVE_816='<RESERVE_816>', RESERVE_817='<RESERVE_817>', RESERVE_818='<RESERVE_818>', RESERVE_819='<RESERVE_819>', RESERVE_820='<RESERVE_820>', RESERVE_821='<RESERVE_821>', RESERVE_822='<RESERVE_822>', RESERVE_823='<RESERVE_823>', RESERVE_824='<RESERVE_824>', RESERVE_825='<RESERVE_825>', RESERVE_826='<RESERVE_826>', RESERVE_827='<RESERVE_827>', RESERVE_828='<RESERVE_828>', RESERVE_829='<RESERVE_829>', RESERVE_830='<RESERVE_830>', RESERVE_831='<RESERVE_831>', RESERVE_832='<RESERVE_832>', RESERVE_833='<RESERVE_833>', RESERVE_834='<RESERVE_834>', RESERVE_835='<RESERVE_835>', RESERVE_836='<RESERVE_836>', RESERVE_837='<RESERVE_837>', RESERVE_838='<RESERVE_838>', RESERVE_839='<RESERVE_839>', RESERVE_840='<RESERVE_840>', RESERVE_841='<RESERVE_841>', RESERVE_842='<RESERVE_842>', RESERVE_843='<RESERVE_843>', RESERVE_844='<RESERVE_844>', RESERVE_845='<RESERVE_845>', RESERVE_846='<RESERVE_846>', RESERVE_847='<RESERVE_847>', RESERVE_848='<RESERVE_848>', RESERVE_849='<RESERVE_849>', RESERVE_850='<RESERVE_850>', RESERVE_851='<RESERVE_851>', RESERVE_852='<RESERVE_852>', RESERVE_853='<RESERVE_853>', RESERVE_854='<RESERVE_854>', RESERVE_855='<RESERVE_855>', RESERVE_856='<RESERVE_856>', RESERVE_857='<RESERVE_857>', RESERVE_858='<RESERVE_858>', RESERVE_859='<RESERVE_859>', RESERVE_860='<RESERVE_860>', RESERVE_861='<RESERVE_861>', RESERVE_862='<RESERVE_862>', RESERVE_863='<RESERVE_863>', RESERVE_864='<RESERVE_864>', RESERVE_865='<RESERVE_865>', RESERVE_866='<RESERVE_866>', RESERVE_867='<RESERVE_867>', RESERVE_868='<RESERVE_868>', RESERVE_869='<RESERVE_869>', RESERVE_870='<RESERVE_870>', RESERVE_871='<RESERVE_871>', RESERVE_872='<RESERVE_872>', RESERVE_873='<RESERVE_873>', RESERVE_874='<RESERVE_874>', RESERVE_875='<RESERVE_875>', RESERVE_876='<RESERVE_876>', RESERVE_877='<RESERVE_877>', RESERVE_878='<RESERVE_878>', RESERVE_879='<RESERVE_879>', RESERVE_880='<RESERVE_880>', RESERVE_881='<RESERVE_881>', RESERVE_882='<RESERVE_882>', RESERVE_883='<RESERVE_883>', RESERVE_884='<RESERVE_884>', RESERVE_885='<RESERVE_885>', RESERVE_886='<RESERVE_886>', RESERVE_887='<RESERVE_887>', RESERVE_888='<RESERVE_888>', RESERVE_889='<RESERVE_889>', RESERVE_890='<RESERVE_890>', RESERVE_891='<RESERVE_891>', RESERVE_892='<RESERVE_892>', RESERVE_893='<RESERVE_893>', RESERVE_894='<RESERVE_894>', RESERVE_895='<RESERVE_895>', RESERVE_896='<RESERVE_896>', RESERVE_897='<RESERVE_897>', RESERVE_898='<RESERVE_898>', RESERVE_899='<RESERVE_899>', RESERVE_900='<RESERVE_900>', RESERVE_901='<RESERVE_901>', RESERVE_902='<RESERVE_902>', RESERVE_903='<RESERVE_903>', RESERVE_904='<RESERVE_904>', RESERVE_905='<RESERVE_905>', RESERVE_906='<RESERVE_906>', RESERVE_907='<RESERVE_907>', RESERVE_908='<RESERVE_908>', RESERVE_909='<RESERVE_909>', RESERVE_910='<RESERVE_910>', RESERVE_911='<RESERVE_911>', RESERVE_912='<RESERVE_912>', RESERVE_913='<RESERVE_913>', RESERVE_914='<RESERVE_914>', RESERVE_915='<RESERVE_915>', RESERVE_916='<RESERVE_916>', RESERVE_917='<RESERVE_917>', RESERVE_918='<RESERVE_918>', RESERVE_919='<RESERVE_919>', RESERVE_920='<RESERVE_920>', RESERVE_921='<RESERVE_921>', RESERVE_922='<RESERVE_922>', RESERVE_923='<RESERVE_923>', RESERVE_924='<RESERVE_924>', RESERVE_925='<RESERVE_925>', RESERVE_926='<RESERVE_926>', RESERVE_927='<RESERVE_927>', RESERVE_928='<RESERVE_928>', RESERVE_929='<RESERVE_929>', RESERVE_930='<RESERVE_930>', RESERVE_931='<RESERVE_931>', RESERVE_932='<RESERVE_932>', RESERVE_933='<RESERVE_933>', RESERVE_934='<RESERVE_934>', RESERVE_935='<RESERVE_935>', RESERVE_936='<RESERVE_936>', RESERVE_937='<RESERVE_937>', RESERVE_938='<RESERVE_938>', RESERVE_939='<RESERVE_939>', RESERVE_940='<RESERVE_940>', RESERVE_941='<RESERVE_941>', RESERVE_942='<RESERVE_942>', RESERVE_943='<RESERVE_943>', RESERVE_944='<RESERVE_944>', RESERVE_945='<RESERVE_945>', RESERVE_946='<RESERVE_946>', RESERVE_947='<RESERVE_947>', RESERVE_948='<RESERVE_948>', RESERVE_949='<RESERVE_949>', RESERVE_950='<RESERVE_950>', RESERVE_951='<RESERVE_951>', RESERVE_952='<RESERVE_952>', RESERVE_953='<RESERVE_953>', RESERVE_954='<RESERVE_954>', RESERVE_955='<RESERVE_955>', RESERVE_956='<RESERVE_956>', RESERVE_957='<RESERVE_957>', RESERVE_958='<RESERVE_958>', RESERVE_959='<RESERVE_959>', RESERVE_960='<RESERVE_960>', RESERVE_961='<RESERVE_961>', RESERVE_962='<RESERVE_962>', RESERVE_963='<RESERVE_963>', RESERVE_964='<RESERVE_964>', RESERVE_965='<RESERVE_965>', RESERVE_966='<RESERVE_966>', RESERVE_967='<RESERVE_967>', RESERVE_968='<RESERVE_968>', RESERVE_969='<RESERVE_969>', RESERVE_970='<RESERVE_970>', RESERVE_971='<RESERVE_971>', RESERVE_972='<RESERVE_972>', RESERVE_973='<RESERVE_973>', RESERVE_974='<RESERVE_974>', RESERVE_975='<RESERVE_975>', RESERVE_976='<RESERVE_976>', RESERVE_977='<RESERVE_977>', RESERVE_978='<RESERVE_978>', RESERVE_979='<RESERVE_979>', RESERVE_980='<RESERVE_980>', RESERVE_981='<RESERVE_981>', RESERVE_982='<RESERVE_982>', RESERVE_983='<RESERVE_983>', RESERVE_984='<RESERVE_984>', RESERVE_985='<RESERVE_985>', RESERVE_986='<RESERVE_986>', RESERVE_987='<RESERVE_987>', RESERVE_988='<RESERVE_988>', RESERVE_989='<RESERVE_989>', RESERVE_990='<RESERVE_990>', RESERVE_991='<RESERVE_991>', RESERVE_992='<RESERVE_992>', RESERVE_993='<RESERVE_993>', RESERVE_994='<RESERVE_994>', RESERVE_995='<RESERVE_995>', RESERVE_996='<RESERVE_996>', RESERVE_997='<RESERVE_997>', RESERVE_998='<RESERVE_998>', RESERVE_999='<RESERVE_999>', RESERVE_1000='<RESERVE_1000>', RESERVE_1001='<RESERVE_1001>', RESERVE_1002='<RESERVE_1002>', RESERVE_1003='<RESERVE_1003>', RESERVE_1004='<RESERVE_1004>', RESERVE_1005='<RESERVE_1005>', RESERVE_1006='<RESERVE_1006>', RESERVE_1007='<RESERVE_1007>', RESERVE_1008='<RESERVE_1008>', RESERVE_1009='<RESERVE_1009>', RESERVE_1010='<RESERVE_1010>', RESERVE_1011='<RESERVE_1011>', RESERVE_1012='<RESERVE_1012>', RESERVE_1013='<RESERVE_1013>', RESERVE_1014='<RESERVE_1014>', RESERVE_1015='<RESERVE_1015>', RESERVE_1016='<RESERVE_1016>', RESERVE_1017='<RESERVE_1017>', RESERVE_1018='<RESERVE_1018>', RESERVE_1019='<RESERVE_1019>', RESERVE_1020='<RESERVE_1020>', RESERVE_1021='<RESERVE_1021>', RESERVE_1022='<RESERVE_1022>', RESERVE_1023='<RESERVE_1023>', RESERVE_1024='<RESERVE_1024>', RESERVE_1025='<RESERVE_1025>', RESERVE_1026='<RESERVE_1026>', RESERVE_1027='<RESERVE_1027>', RESERVE_1028='<RESERVE_1028>', RESERVE_1029='<RESERVE_1029>', RESERVE_1030='<RESERVE_1030>', RESERVE_1031='<RESERVE_1031>', RESERVE_1032='<RESERVE_1032>', RESERVE_1033='<RESERVE_1033>', RESERVE_1034='<RESERVE_1034>', RESERVE_1035='<RESERVE_1035>', RESERVE_1036='<RESERVE_1036>', RESERVE_1037='<RESERVE_1037>', RESERVE_1038='<RESERVE_1038>', RESERVE_1039='<RESERVE_1039>', RESERVE_1040='<RESERVE_1040>', RESERVE_1041='<RESERVE_1041>', RESERVE_1042='<RESERVE_1042>', RESERVE_1043='<RESERVE_1043>', RESERVE_1044='<RESERVE_1044>', RESERVE_1045='<RESERVE_1045>', RESERVE_1046='<RESERVE_1046>', RESERVE_1047='<RESERVE_1047>', RESERVE_1048='<RESERVE_1048>', RESERVE_1049='<RESERVE_1049>', RESERVE_1050='<RESERVE_1050>', RESERVE_1051='<RESERVE_1051>', RESERVE_1052='<RESERVE_1052>', RESERVE_1053='<RESERVE_1053>', RESERVE_1054='<RESERVE_1054>', RESERVE_1055='<RESERVE_1055>', RESERVE_1056='<RESERVE_1056>', RESERVE_1057='<RESERVE_1057>', RESERVE_1058='<RESERVE_1058>', RESERVE_1059='<RESERVE_1059>', RESERVE_1060='<RESERVE_1060>', RESERVE_1061='<RESERVE_1061>', RESERVE_1062='<RESERVE_1062>', RESERVE_1063='<RESERVE_1063>', RESERVE_1064='<RESERVE_1064>', RESERVE_1065='<RESERVE_1065>', RESERVE_1066='<RESERVE_1066>', RESERVE_1067='<RESERVE_1067>', RESERVE_1068='<RESERVE_1068>', RESERVE_1069='<RESERVE_1069>', RESERVE_1070='<RESERVE_1070>', RESERVE_1071='<RESERVE_1071>', RESERVE_1072='<RESERVE_1072>', RESERVE_1073='<RESERVE_1073>', RESERVE_1074='<RESERVE_1074>', RESERVE_1075='<RESERVE_1075>', RESERVE_1076='<RESERVE_1076>', RESERVE_1077='<RESERVE_1077>', RESERVE_1078='<RESERVE_1078>', RESERVE_1079='<RESERVE_1079>', RESERVE_1080='<RESERVE_1080>', RESERVE_1081='<RESERVE_1081>', RESERVE_1082='<RESERVE_1082>', RESERVE_1083='<RESERVE_1083>', RESERVE_1084='<RESERVE_1084>', RESERVE_1085='<RESERVE_1085>', RESERVE_1086='<RESERVE_1086>', RESERVE_1087='<RESERVE_1087>', RESERVE_1088='<RESERVE_1088>', RESERVE_1089='<RESERVE_1089>', RESERVE_1090='<RESERVE_1090>', RESERVE_1091='<RESERVE_1091>', RESERVE_1092='<RESERVE_1092>', RESERVE_1093='<RESERVE_1093>', RESERVE_1094='<RESERVE_1094>', RESERVE_1095='<RESERVE_1095>', RESERVE_1096='<RESERVE_1096>', RESERVE_1097='<RESERVE_1097>', RESERVE_1098='<RESERVE_1098>', RESERVE_1099='<RESERVE_1099>', RESERVE_1100='<RESERVE_1100>', RESERVE_1101='<RESERVE_1101>', RESERVE_1102='<RESERVE_1102>', RESERVE_1103='<RESERVE_1103>', RESERVE_1104='<RESERVE_1104>', RESERVE_1105='<RESERVE_1105>', RESERVE_1106='<RESERVE_1106>', RESERVE_1107='<RESERVE_1107>', RESERVE_1108='<RESERVE_1108>', RESERVE_1109='<RESERVE_1109>', RESERVE_1110='<RESERVE_1110>', RESERVE_1111='<RESERVE_1111>', RESERVE_1112='<RESERVE_1112>', RESERVE_1113='<RESERVE_1113>', RESERVE_1114='<RESERVE_1114>', RESERVE_1115='<RESERVE_1115>', RESERVE_1116='<RESERVE_1116>', RESERVE_1117='<RESERVE_1117>', RESERVE_1118='<RESERVE_1118>', RESERVE_1119='<RESERVE_1119>', RESERVE_1120='<RESERVE_1120>', RESERVE_1121='<RESERVE_1121>', RESERVE_1122='<RESERVE_1122>', RESERVE_1123='<RESERVE_1123>', RESERVE_1124='<RESERVE_1124>', RESERVE_1125='<RESERVE_1125>', RESERVE_1126='<RESERVE_1126>', RESERVE_1127='<RESERVE_1127>', RESERVE_1128='<RESERVE_1128>', RESERVE_1129='<RESERVE_1129>', RESERVE_1130='<RESERVE_1130>', RESERVE_1131='<RESERVE_1131>', RESERVE_1132='<RESERVE_1132>', RESERVE_1133='<RESERVE_1133>', RESERVE_1134='<RESERVE_1134>', RESERVE_1135='<RESERVE_1135>', RESERVE_1136='<RESERVE_1136>', RESERVE_1137='<RESERVE_1137>', RESERVE_1138='<RESERVE_1138>', RESERVE_1139='<RESERVE_1139>', RESERVE_1140='<RESERVE_1140>', RESERVE_1141='<RESERVE_1141>', RESERVE_1142='<RESERVE_1142>', RESERVE_1143='<RESERVE_1143>', RESERVE_1144='<RESERVE_1144>', RESERVE_1145='<RESERVE_1145>', RESERVE_1146='<RESERVE_1146>', RESERVE_1147='<RESERVE_1147>', RESERVE_1148='<RESERVE_1148>', RESERVE_1149='<RESERVE_1149>', RESERVE_1150='<RESERVE_1150>', RESERVE_1151='<RESERVE_1151>', RESERVE_1152='<RESERVE_1152>', RESERVE_1153='<RESERVE_1153>', RESERVE_1154='<RESERVE_1154>', RESERVE_1155='<RESERVE_1155>', RESERVE_1156='<RESERVE_1156>', RESERVE_1157='<RESERVE_1157>', RESERVE_1158='<RESERVE_1158>', RESERVE_1159='<RESERVE_1159>', RESERVE_1160='<RESERVE_1160>', RESERVE_1161='<RESERVE_1161>', RESERVE_1162='<RESERVE_1162>', RESERVE_1163='<RESERVE_1163>', RESERVE_1164='<RESERVE_1164>', RESERVE_1165='<RESERVE_1165>', RESERVE_1166='<RESERVE_1166>', RESERVE_1167='<RESERVE_1167>', RESERVE_1168='<RESERVE_1168>', RESERVE_1169='<RESERVE_1169>', RESERVE_1170='<RESERVE_1170>', RESERVE_1171='<RESERVE_1171>', RESERVE_1172='<RESERVE_1172>', RESERVE_1173='<RESERVE_1173>', RESERVE_1174='<RESERVE_1174>', RESERVE_1175='<RESERVE_1175>', RESERVE_1176='<RESERVE_1176>', RESERVE_1177='<RESERVE_1177>', RESERVE_1178='<RESERVE_1178>', RESERVE_1179='<RESERVE_1179>', RESERVE_1180='<RESERVE_1180>', RESERVE_1181='<RESERVE_1181>', RESERVE_1182='<RESERVE_1182>', RESERVE_1183='<RESERVE_1183>', RESERVE_1184='<RESERVE_1184>', RESERVE_1185='<RESERVE_1185>', RESERVE_1186='<RESERVE_1186>', RESERVE_1187='<RESERVE_1187>', RESERVE_1188='<RESERVE_1188>', RESERVE_1189='<RESERVE_1189>', RESERVE_1190='<RESERVE_1190>', RESERVE_1191='<RESERVE_1191>', RESERVE_1192='<RESERVE_1192>', RESERVE_1193='<RESERVE_1193>', RESERVE_1194='<RESERVE_1194>', RESERVE_1195='<RESERVE_1195>', RESERVE_1196='<RESERVE_1196>', RESERVE_1197='<RESERVE_1197>', RESERVE_1198='<RESERVE_1198>', RESERVE_1199='<RESERVE_1199>', RESERVE_1200='<RESERVE_1200>', RESERVE_1201='<RESERVE_1201>', RESERVE_1202='<RESERVE_1202>', RESERVE_1203='<RESERVE_1203>', RESERVE_1204='<RESERVE_1204>', RESERVE_1205='<RESERVE_1205>', RESERVE_1206='<RESERVE_1206>', RESERVE_1207='<RESERVE_1207>', RESERVE_1208='<RESERVE_1208>', RESERVE_1209='<RESERVE_1209>', RESERVE_1210='<RESERVE_1210>', RESERVE_1211='<RESERVE_1211>', RESERVE_1212='<RESERVE_1212>', RESERVE_1213='<RESERVE_1213>', RESERVE_1214='<RESERVE_1214>', RESERVE_1215='<RESERVE_1215>', RESERVE_1216='<RESERVE_1216>', RESERVE_1217='<RESERVE_1217>', RESERVE_1218='<RESERVE_1218>', RESERVE_1219='<RESERVE_1219>', RESERVE_1220='<RESERVE_1220>', RESERVE_1221='<RESERVE_1221>', RESERVE_1222='<RESERVE_1222>', RESERVE_1223='<RESERVE_1223>', RESERVE_1224='<RESERVE_1224>', RESERVE_1225='<RESERVE_1225>', RESERVE_1226='<RESERVE_1226>', RESERVE_1227='<RESERVE_1227>', RESERVE_1228='<RESERVE_1228>', RESERVE_1229='<RESERVE_1229>', RESERVE_1230='<RESERVE_1230>', RESERVE_1231='<RESERVE_1231>', RESERVE_1232='<RESERVE_1232>', RESERVE_1233='<RESERVE_1233>', RESERVE_1234='<RESERVE_1234>', RESERVE_1235='<RESERVE_1235>', RESERVE_1236='<RESERVE_1236>', RESERVE_1237='<RESERVE_1237>', RESERVE_1238='<RESERVE_1238>', RESERVE_1239='<RESERVE_1239>', RESERVE_1240='<RESERVE_1240>', RESERVE_1241='<RESERVE_1241>', RESERVE_1242='<RESERVE_1242>', RESERVE_1243='<RESERVE_1243>', RESERVE_1244='<RESERVE_1244>', RESERVE_1245='<RESERVE_1245>', RESERVE_1246='<RESERVE_1246>', RESERVE_1247='<RESERVE_1247>', RESERVE_1248='<RESERVE_1248>', RESERVE_1249='<RESERVE_1249>', RESERVE_1250='<RESERVE_1250>', RESERVE_1251='<RESERVE_1251>', RESERVE_1252='<RESERVE_1252>', RESERVE_1253='<RESERVE_1253>', RESERVE_1254='<RESERVE_1254>', RESERVE_1255='<RESERVE_1255>', RESERVE_1256='<RESERVE_1256>', RESERVE_1257='<RESERVE_1257>', RESERVE_1258='<RESERVE_1258>', RESERVE_1259='<RESERVE_1259>', RESERVE_1260='<RESERVE_1260>', RESERVE_1261='<RESERVE_1261>', RESERVE_1262='<RESERVE_1262>', RESERVE_1263='<RESERVE_1263>', RESERVE_1264='<RESERVE_1264>', RESERVE_1265='<RESERVE_1265>', RESERVE_1266='<RESERVE_1266>', RESERVE_1267='<RESERVE_1267>', RESERVE_1268='<RESERVE_1268>', RESERVE_1269='<RESERVE_1269>', RESERVE_1270='<RESERVE_1270>', RESERVE_1271='<RESERVE_1271>', RESERVE_1272='<RESERVE_1272>', RESERVE_1273='<RESERVE_1273>', RESERVE_1274='<RESERVE_1274>', RESERVE_1275='<RESERVE_1275>', RESERVE_1276='<RESERVE_1276>', RESERVE_1277='<RESERVE_1277>', RESERVE_1278='<RESERVE_1278>', RESERVE_1279='<RESERVE_1279>', RESERVE_1280='<RESERVE_1280>', RESERVE_1281='<RESERVE_1281>', RESERVE_1282='<RESERVE_1282>', RESERVE_1283='<RESERVE_1283>', RESERVE_1284='<RESERVE_1284>', RESERVE_1285='<RESERVE_1285>', RESERVE_1286='<RESERVE_1286>', RESERVE_1287='<RESERVE_1287>', RESERVE_1288='<RESERVE_1288>', RESERVE_1289='<RESERVE_1289>', RESERVE_1290='<RESERVE_1290>', RESERVE_1291='<RESERVE_1291>', RESERVE_1292='<RESERVE_1292>', RESERVE_1293='<RESERVE_1293>', RESERVE_1294='<RESERVE_1294>', RESERVE_1295='<RESERVE_1295>', RESERVE_1296='<RESERVE_1296>', RESERVE_1297='<RESERVE_1297>', RESERVE_1298='<RESERVE_1298>', RESERVE_1299='<RESERVE_1299>', RESERVE_1300='<RESERVE_1300>', RESERVE_1301='<RESERVE_1301>', RESERVE_1302='<RESERVE_1302>', RESERVE_1303='<RESERVE_1303>', RESERVE_1304='<RESERVE_1304>', RESERVE_1305='<RESERVE_1305>', RESERVE_1306='<RESERVE_1306>', RESERVE_1307='<RESERVE_1307>', RESERVE_1308='<RESERVE_1308>', RESERVE_1309='<RESERVE_1309>', RESERVE_1310='<RESERVE_1310>', RESERVE_1311='<RESERVE_1311>', RESERVE_1312='<RESERVE_1312>', RESERVE_1313='<RESERVE_1313>', RESERVE_1314='<RESERVE_1314>', RESERVE_1315='<RESERVE_1315>', RESERVE_1316='<RESERVE_1316>', RESERVE_1317='<RESERVE_1317>', RESERVE_1318='<RESERVE_1318>', RESERVE_1319='<RESERVE_1319>', RESERVE_1320='<RESERVE_1320>', RESERVE_1321='<RESERVE_1321>', RESERVE_1322='<RESERVE_1322>', RESERVE_1323='<RESERVE_1323>', RESERVE_1324='<RESERVE_1324>', RESERVE_1325='<RESERVE_1325>', RESERVE_1326='<RESERVE_1326>', RESERVE_1327='<RESERVE_1327>', RESERVE_1328='<RESERVE_1328>', RESERVE_1329='<RESERVE_1329>', RESERVE_1330='<RESERVE_1330>', RESERVE_1331='<RESERVE_1331>', RESERVE_1332='<RESERVE_1332>', RESERVE_1333='<RESERVE_1333>', RESERVE_1334='<RESERVE_1334>', RESERVE_1335='<RESERVE_1335>', RESERVE_1336='<RESERVE_1336>', RESERVE_1337='<RESERVE_1337>', RESERVE_1338='<RESERVE_1338>', RESERVE_1339='<RESERVE_1339>', RESERVE_1340='<RESERVE_1340>', RESERVE_1341='<RESERVE_1341>', RESERVE_1342='<RESERVE_1342>', RESERVE_1343='<RESERVE_1343>', RESERVE_1344='<RESERVE_1344>', RESERVE_1345='<RESERVE_1345>', RESERVE_1346='<RESERVE_1346>', RESERVE_1347='<RESERVE_1347>', RESERVE_1348='<RESERVE_1348>', RESERVE_1349='<RESERVE_1349>', RESERVE_1350='<RESERVE_1350>', RESERVE_1351='<RESERVE_1351>', RESERVE_1352='<RESERVE_1352>', RESERVE_1353='<RESERVE_1353>', RESERVE_1354='<RESERVE_1354>', RESERVE_1355='<RESERVE_1355>', RESERVE_1356='<RESERVE_1356>', RESERVE_1357='<RESERVE_1357>', RESERVE_1358='<RESERVE_1358>', RESERVE_1359='<RESERVE_1359>', RESERVE_1360='<RESERVE_1360>', RESERVE_1361='<RESERVE_1361>', RESERVE_1362='<RESERVE_1362>', RESERVE_1363='<RESERVE_1363>', RESERVE_1364='<RESERVE_1364>', RESERVE_1365='<RESERVE_1365>', RESERVE_1366='<RESERVE_1366>', RESERVE_1367='<RESERVE_1367>', RESERVE_1368='<RESERVE_1368>', RESERVE_1369='<RESERVE_1369>', RESERVE_1370='<RESERVE_1370>', RESERVE_1371='<RESERVE_1371>', RESERVE_1372='<RESERVE_1372>', RESERVE_1373='<RESERVE_1373>', RESERVE_1374='<RESERVE_1374>', RESERVE_1375='<RESERVE_1375>', RESERVE_1376='<RESERVE_1376>', RESERVE_1377='<RESERVE_1377>', RESERVE_1378='<RESERVE_1378>', RESERVE_1379='<RESERVE_1379>', RESERVE_1380='<RESERVE_1380>', RESERVE_1381='<RESERVE_1381>', RESERVE_1382='<RESERVE_1382>', RESERVE_1383='<RESERVE_1383>', RESERVE_1384='<RESERVE_1384>', RESERVE_1385='<RESERVE_1385>', RESERVE_1386='<RESERVE_1386>', RESERVE_1387='<RESERVE_1387>', RESERVE_1388='<RESERVE_1388>', RESERVE_1389='<RESERVE_1389>', RESERVE_1390='<RESERVE_1390>', RESERVE_1391='<RESERVE_1391>', RESERVE_1392='<RESERVE_1392>', RESERVE_1393='<RESERVE_1393>', RESERVE_1394='<RESERVE_1394>', RESERVE_1395='<RESERVE_1395>', RESERVE_1396='<RESERVE_1396>', RESERVE_1397='<RESERVE_1397>', RESERVE_1398='<RESERVE_1398>', RESERVE_1399='<RESERVE_1399>', RESERVE_1400='<RESERVE_1400>', RESERVE_1401='<RESERVE_1401>', RESERVE_1402='<RESERVE_1402>', RESERVE_1403='<RESERVE_1403>', RESERVE_1404='<RESERVE_1404>', RESERVE_1405='<RESERVE_1405>', RESERVE_1406='<RESERVE_1406>', RESERVE_1407='<RESERVE_1407>', RESERVE_1408='<RESERVE_1408>', RESERVE_1409='<RESERVE_1409>', RESERVE_1410='<RESERVE_1410>', RESERVE_1411='<RESERVE_1411>', RESERVE_1412='<RESERVE_1412>', RESERVE_1413='<RESERVE_1413>', RESERVE_1414='<RESERVE_1414>', RESERVE_1415='<RESERVE_1415>', RESERVE_1416='<RESERVE_1416>', RESERVE_1417='<RESERVE_1417>', RESERVE_1418='<RESERVE_1418>', RESERVE_1419='<RESERVE_1419>', RESERVE_1420='<RESERVE_1420>', RESERVE_1421='<RESERVE_1421>', RESERVE_1422='<RESERVE_1422>', RESERVE_1423='<RESERVE_1423>', RESERVE_1424='<RESERVE_1424>', RESERVE_1425='<RESERVE_1425>', RESERVE_1426='<RESERVE_1426>', RESERVE_1427='<RESERVE_1427>', RESERVE_1428='<RESERVE_1428>', RESERVE_1429='<RESERVE_1429>', RESERVE_1430='<RESERVE_1430>', RESERVE_1431='<RESERVE_1431>', RESERVE_1432='<RESERVE_1432>', RESERVE_1433='<RESERVE_1433>', RESERVE_1434='<RESERVE_1434>', RESERVE_1435='<RESERVE_1435>', RESERVE_1436='<RESERVE_1436>', RESERVE_1437='<RESERVE_1437>', RESERVE_1438='<RESERVE_1438>', RESERVE_1439='<RESERVE_1439>', RESERVE_1440='<RESERVE_1440>', RESERVE_1441='<RESERVE_1441>', RESERVE_1442='<RESERVE_1442>', RESERVE_1443='<RESERVE_1443>', RESERVE_1444='<RESERVE_1444>', RESERVE_1445='<RESERVE_1445>', RESERVE_1446='<RESERVE_1446>', RESERVE_1447='<RESERVE_1447>', RESERVE_1448='<RESERVE_1448>', RESERVE_1449='<RESERVE_1449>', RESERVE_1450='<RESERVE_1450>', RESERVE_1451='<RESERVE_1451>', RESERVE_1452='<RESERVE_1452>', RESERVE_1453='<RESERVE_1453>', RESERVE_1454='<RESERVE_1454>', RESERVE_1455='<RESERVE_1455>', RESERVE_1456='<RESERVE_1456>', RESERVE_1457='<RESERVE_1457>', RESERVE_1458='<RESERVE_1458>', RESERVE_1459='<RESERVE_1459>', RESERVE_1460='<RESERVE_1460>', RESERVE_1461='<RESERVE_1461>', RESERVE_1462='<RESERVE_1462>', RESERVE_1463='<RESERVE_1463>', RESERVE_1464='<RESERVE_1464>', RESERVE_1465='<RESERVE_1465>', RESERVE_1466='<RESERVE_1466>', RESERVE_1467='<RESERVE_1467>', RESERVE_1468='<RESERVE_1468>', RESERVE_1469='<RESERVE_1469>', RESERVE_1470='<RESERVE_1470>', RESERVE_1471='<RESERVE_1471>', RESERVE_1472='<RESERVE_1472>', RESERVE_1473='<RESERVE_1473>', RESERVE_1474='<RESERVE_1474>', RESERVE_1475='<RESERVE_1475>', RESERVE_1476='<RESERVE_1476>', RESERVE_1477='<RESERVE_1477>', RESERVE_1478='<RESERVE_1478>', RESERVE_1479='<RESERVE_1479>', RESERVE_1480='<RESERVE_1480>', RESERVE_1481='<RESERVE_1481>', RESERVE_1482='<RESERVE_1482>', RESERVE_1483='<RESERVE_1483>', RESERVE_1484='<RESERVE_1484>', RESERVE_1485='<RESERVE_1485>', RESERVE_1486='<RESERVE_1486>', RESERVE_1487='<RESERVE_1487>', RESERVE_1488='<RESERVE_1488>', RESERVE_1489='<RESERVE_1489>', RESERVE_1490='<RESERVE_1490>', RESERVE_1491='<RESERVE_1491>', RESERVE_1492='<RESERVE_1492>', RESERVE_1493='<RESERVE_1493>', RESERVE_1494='<RESERVE_1494>', RESERVE_1495='<RESERVE_1495>', RESERVE_1496='<RESERVE_1496>', RESERVE_1497='<RESERVE_1497>', RESERVE_1498='<RESERVE_1498>', RESERVE_1499='<RESERVE_1499>', RESERVE_1500='<RESERVE_1500>', RESERVE_1501='<RESERVE_1501>', RESERVE_1502='<RESERVE_1502>', RESERVE_1503='<RESERVE_1503>', RESERVE_1504='<RESERVE_1504>', RESERVE_1505='<RESERVE_1505>', RESERVE_1506='<RESERVE_1506>', RESERVE_1507='<RESERVE_1507>', RESERVE_1508='<RESERVE_1508>', RESERVE_1509='<RESERVE_1509>', RESERVE_1510='<RESERVE_1510>', RESERVE_1511='<RESERVE_1511>', RESERVE_1512='<RESERVE_1512>', RESERVE_1513='<RESERVE_1513>', RESERVE_1514='<RESERVE_1514>', RESERVE_1515='<RESERVE_1515>', RESERVE_1516='<RESERVE_1516>', RESERVE_1517='<RESERVE_1517>', RESERVE_1518='<RESERVE_1518>', RESERVE_1519='<RESERVE_1519>', RESERVE_1520='<RESERVE_1520>', RESERVE_1521='<RESERVE_1521>', RESERVE_1522='<RESERVE_1522>', RESERVE_1523='<RESERVE_1523>', RESERVE_1524='<RESERVE_1524>', RESERVE_1525='<RESERVE_1525>', RESERVE_1526='<RESERVE_1526>', RESERVE_1527='<RESERVE_1527>', RESERVE_1528='<RESERVE_1528>', RESERVE_1529='<RESERVE_1529>', RESERVE_1530='<RESERVE_1530>', RESERVE_1531='<RESERVE_1531>', RESERVE_1532='<RESERVE_1532>', RESERVE_1533='<RESERVE_1533>', RESERVE_1534='<RESERVE_1534>', RESERVE_1535='<RESERVE_1535>', RESERVE_1536='<RESERVE_1536>', RESERVE_1537='<RESERVE_1537>', RESERVE_1538='<RESERVE_1538>', RESERVE_1539='<RESERVE_1539>', RESERVE_1540='<RESERVE_1540>', RESERVE_1541='<RESERVE_1541>', RESERVE_1542='<RESERVE_1542>', RESERVE_1543='<RESERVE_1543>', RESERVE_1544='<RESERVE_1544>', RESERVE_1545='<RESERVE_1545>', RESERVE_1546='<RESERVE_1546>', RESERVE_1547='<RESERVE_1547>', RESERVE_1548='<RESERVE_1548>', RESERVE_1549='<RESERVE_1549>', RESERVE_1550='<RESERVE_1550>', RESERVE_1551='<RESERVE_1551>', RESERVE_1552='<RESERVE_1552>', RESERVE_1553='<RESERVE_1553>', RESERVE_1554='<RESERVE_1554>', RESERVE_1555='<RESERVE_1555>', RESERVE_1556='<RESERVE_1556>', RESERVE_1557='<RESERVE_1557>', RESERVE_1558='<RESERVE_1558>', RESERVE_1559='<RESERVE_1559>', RESERVE_1560='<RESERVE_1560>', RESERVE_1561='<RESERVE_1561>', RESERVE_1562='<RESERVE_1562>', RESERVE_1563='<RESERVE_1563>', RESERVE_1564='<RESERVE_1564>', RESERVE_1565='<RESERVE_1565>', RESERVE_1566='<RESERVE_1566>', RESERVE_1567='<RESERVE_1567>', RESERVE_1568='<RESERVE_1568>', RESERVE_1569='<RESERVE_1569>', RESERVE_1570='<RESERVE_1570>', RESERVE_1571='<RESERVE_1571>', RESERVE_1572='<RESERVE_1572>', RESERVE_1573='<RESERVE_1573>', RESERVE_1574='<RESERVE_1574>', RESERVE_1575='<RESERVE_1575>', RESERVE_1576='<RESERVE_1576>', RESERVE_1577='<RESERVE_1577>', RESERVE_1578='<RESERVE_1578>', RESERVE_1579='<RESERVE_1579>', RESERVE_1580='<RESERVE_1580>', RESERVE_1581='<RESERVE_1581>', RESERVE_1582='<RESERVE_1582>', RESERVE_1583='<RESERVE_1583>', RESERVE_1584='<RESERVE_1584>', RESERVE_1585='<RESERVE_1585>', RESERVE_1586='<RESERVE_1586>', RESERVE_1587='<RESERVE_1587>', RESERVE_1588='<RESERVE_1588>', RESERVE_1589='<RESERVE_1589>', RESERVE_1590='<RESERVE_1590>', RESERVE_1591='<RESERVE_1591>', RESERVE_1592='<RESERVE_1592>', RESERVE_1593='<RESERVE_1593>', RESERVE_1594='<RESERVE_1594>', RESERVE_1595='<RESERVE_1595>', UT_00_00='(0,0)', UT_00_01='(0,1)', UT_01_00='(1,0)', UT_01_01='(1,1)', UT_00_02='(0,2)', UT_02_00='(2,0)', UT_01_02='(1,2)', UT_02_01='(2,1)', UT_02_02='(2,2)', UT_00_03='(0,3)', UT_03_00='(3,0)', UT_03_01='(3,1)', UT_02_03='(2,3)', UT_03_02='(3,2)', UT_01_03='(1,3)', UT_03_03='(3,3)', UT_00_04='(0,4)', UT_02_04='(2,4)', UT_04_00='(4,0)', UT_01_04='(1,4)', UT_04_01='(4,1)', UT_04_02='(4,2)', UT_03_04='(3,4)', UT_04_03='(4,3)', UT_04_04='(4,4)', UT_00_05='(0,5)', UT_05_00='(5,0)', UT_05_01='(5,1)', UT_02_05='(2,5)', UT_05_02='(5,2)', UT_05_03='(5,3)', UT_04_05='(4,5)', UT_05_04='(5,4)', UT_01_05='(1,5)', UT_03_05='(3,5)', UT_05_05='(5,5)', UT_00_06='(0,6)', UT_02_06='(2,6)', UT_04_06='(4,6)', UT_06_00='(6,0)', UT_01_06='(1,6)', UT_06_01='(6,1)', UT_06_02='(6,2)', UT_03_06='(3,6)', UT_06_03='(6,3)', UT_06_04='(6,4)', UT_05_06='(5,6)', UT_06_05='(6,5)', UT_06_06='(6,6)', UT_00_07='(0,7)', UT_07_00='(7,0)', UT_07_01='(7,1)', UT_02_07='(2,7)', UT_07_02='(7,2)', UT_07_03='(7,3)', UT_04_07='(4,7)', UT_07_04='(7,4)', UT_07_05='(7,5)', UT_06_07='(6,7)', UT_07_06='(7,6)', UT_01_07='(1,7)', UT_03_07='(3,7)', UT_05_07='(5,7)', UT_07_07='(7,7)', UT_00_08='(0,8)', UT_02_08='(2,8)', UT_04_08='(4,8)', UT_06_08='(6,8)', UT_08_00='(8,0)', UT_01_08='(1,8)', UT_08_01='(8,1)', UT_08_02='(8,2)', UT_03_08='(3,8)', UT_08_03='(8,3)', UT_08_04='(8,4)', UT_05_08='(5,8)', UT_08_05='(8,5)', UT_08_06='(8,6)', UT_07_08='(7,8)', UT_08_07='(8,7)', UT_08_08='(8,8)', UT_00_09='(0,9)', UT_09_00='(9,0)', UT_09_01='(9,1)', UT_02_09='(2,9)', UT_09_02='(9,2)', UT_09_03='(9,3)', UT_04_09='(4,9)', UT_09_04='(9,4)', UT_09_05='(9,5)', UT_06_09='(6,9)', UT_09_06='(9,6)', UT_09_07='(9,7)', UT_08_09='(8,9)', UT_09_08='(9,8)', UT_01_09='(1,9)', UT_03_09='(3,9)', UT_05_09='(5,9)', UT_07_09='(7,9)', UT_09_09='(9,9)', UT_00_10='(0,10)', UT_02_10='(2,10)', UT_04_10='(4,10)', UT_06_10='(6,10)', UT_08_10='(8,10)', UT_10_00='(10,0)', UT_01_10='(1,10)', UT_10_01='(10,1)', UT_10_02='(10,2)', UT_03_10='(3,10)', UT_10_03='(10,3)', UT_10_04='(10,4)', UT_05_10='(5,10)', UT_10_05='(10,5)', UT_10_06='(10,6)', UT_07_10='(7,10)', UT_10_07='(10,7)', UT_10_08='(10,8)', UT_09_10='(9,10)', UT_10_09='(10,9)', UT_10_10='(10,10)', UT_00_11='(0,11)', UT_11_00='(11,0)', UT_11_01='(11,1)', UT_02_11='(2,11)', UT_11_02='(11,2)', UT_11_03='(11,3)', UT_04_11='(4,11)', UT_11_04='(11,4)', UT_11_05='(11,5)', UT_06_11='(6,11)', UT_11_06='(11,6)', UT_11_07='(11,7)', UT_08_11='(8,11)', UT_11_08='(11,8)', UT_11_09='(11,9)', UT_10_11='(10,11)', UT_11_10='(11,10)', UT_01_11='(1,11)', UT_03_11='(3,11)', UT_05_11='(5,11)', UT_07_11='(7,11)', UT_09_11='(9,11)', UT_11_11='(11,11)', UT_00_12='(0,12)', UT_02_12='(2,12)', UT_04_12='(4,12)', UT_06_12='(6,12)', UT_08_12='(8,12)', UT_10_12='(10,12)', UT_12_00='(12,0)', UT_01_12='(1,12)', UT_12_01='(12,1)', UT_12_02='(12,2)', UT_03_12='(3,12)', UT_12_03='(12,3)', UT_12_04='(12,4)', UT_05_12='(5,12)', UT_12_05='(12,5)', UT_12_06='(12,6)', UT_07_12='(7,12)', UT_12_07='(12,7)', UT_12_08='(12,8)', UT_09_12='(9,12)', UT_12_09='(12,9)', UT_12_10='(12,10)', UT_11_12='(11,12)', UT_12_11='(12,11)', UT_12_12='(12,12)', UT_00_13='(0,13)', UT_13_00='(13,0)', UT_13_01='(13,1)', UT_02_13='(2,13)', UT_13_02='(13,2)', UT_13_03='(13,3)', UT_04_13='(4,13)', UT_13_04='(13,4)', UT_13_05='(13,5)', UT_06_13='(6,13)', UT_13_06='(13,6)', UT_13_07='(13,7)', UT_08_13='(8,13)', UT_13_08='(13,8)', UT_13_09='(13,9)', UT_10_13='(10,13)', UT_13_10='(13,10)', UT_13_11='(13,11)', UT_12_13='(12,13)', UT_13_12='(13,12)', UT_01_13='(1,13)', UT_03_13='(3,13)', UT_05_13='(5,13)', UT_07_13='(7,13)', UT_09_13='(9,13)', UT_11_13='(11,13)', UT_13_13='(13,13)', UT_00_14='(0,14)', UT_02_14='(2,14)', UT_04_14='(4,14)', UT_06_14='(6,14)', UT_08_14='(8,14)', UT_10_14='(10,14)', UT_12_14='(12,14)', UT_14_00='(14,0)', UT_01_14='(1,14)', UT_14_01='(14,1)', UT_14_02='(14,2)', UT_03_14='(3,14)', UT_14_03='(14,3)', UT_14_04='(14,4)', UT_05_14='(5,14)', UT_14_05='(14,5)', UT_14_06='(14,6)', UT_07_14='(7,14)', UT_14_07='(14,7)', UT_14_08='(14,8)', UT_09_14='(9,14)', UT_14_09='(14,9)', UT_14_10='(14,10)', UT_11_14='(11,14)', UT_14_11='(14,11)', UT_14_12='(14,12)', UT_13_14='(13,14)', UT_14_13='(14,13)', UT_14_14='(14,14)', UT_00_15='(0,15)', UT_15_00='(15,0)', UT_15_01='(15,1)', UT_02_15='(2,15)', UT_15_02='(15,2)', UT_15_03='(15,3)', UT_04_15='(4,15)', UT_15_04='(15,4)', UT_15_05='(15,5)', UT_06_15='(6,15)', UT_15_06='(15,6)', UT_15_07='(15,7)', UT_08_15='(8,15)', UT_15_08='(15,8)', UT_15_09='(15,9)', UT_10_15='(10,15)', UT_15_10='(15,10)', UT_15_11='(15,11)', UT_12_15='(12,15)', UT_15_12='(15,12)', UT_15_13='(15,13)', UT_14_15='(14,15)', UT_15_14='(15,14)', UT_01_15='(1,15)', UT_03_15='(3,15)', UT_05_15='(5,15)', UT_07_15='(7,15)', UT_09_15='(9,15)', UT_11_15='(11,15)', UT_13_15='(13,15)', UT_15_15='(15,15)', UT_00_16='(0,16)', UT_02_16='(2,16)', UT_04_16='(4,16)', UT_06_16='(6,16)', UT_08_16='(8,16)', UT_10_16='(10,16)', UT_12_16='(12,16)', UT_14_16='(14,16)', UT_16_00='(16,0)', UT_01_16='(1,16)', UT_16_01='(16,1)', UT_16_02='(16,2)', UT_03_16='(3,16)', UT_16_03='(16,3)', UT_16_04='(16,4)', UT_05_16='(5,16)', UT_16_05='(16,5)', UT_16_06='(16,6)', UT_07_16='(7,16)', UT_16_07='(16,7)', UT_16_08='(16,8)', UT_09_16='(9,16)', UT_16_09='(16,9)', UT_16_10='(16,10)', UT_11_16='(11,16)', UT_16_11='(16,11)', UT_16_12='(16,12)', UT_13_16='(13,16)', UT_16_13='(16,13)', UT_16_14='(16,14)', UT_15_16='(15,16)', UT_16_15='(16,15)', UT_16_16='(16,16)', UT_00_17='(0,17)', UT_17_00='(17,0)', UT_17_01='(17,1)', UT_02_17='(2,17)', UT_17_02='(17,2)', UT_17_03='(17,3)', UT_04_17='(4,17)', UT_17_04='(17,4)', UT_17_05='(17,5)', UT_06_17='(6,17)', UT_17_06='(17,6)', UT_17_07='(17,7)', UT_08_17='(8,17)', UT_17_08='(17,8)', UT_17_09='(17,9)', UT_10_17='(10,17)', UT_17_10='(17,10)', UT_17_11='(17,11)', UT_12_17='(12,17)', UT_17_12='(17,12)', UT_17_13='(17,13)', UT_14_17='(14,17)', UT_17_14='(17,14)', UT_17_15='(17,15)', UT_16_17='(16,17)', UT_17_16='(17,16)', UT_01_17='(1,17)', UT_03_17='(3,17)', UT_05_17='(5,17)', UT_07_17='(7,17)', UT_09_17='(9,17)', UT_11_17='(11,17)', UT_13_17='(13,17)', UT_15_17='(15,17)', UT_17_17='(17,17)', UT_00_18='(0,18)', UT_02_18='(2,18)', UT_04_18='(4,18)', UT_06_18='(6,18)', UT_08_18='(8,18)', UT_10_18='(10,18)', UT_12_18='(12,18)', UT_14_18='(14,18)', UT_16_18='(16,18)', UT_18_00='(18,0)', UT_01_18='(1,18)', UT_18_01='(18,1)', UT_18_02='(18,2)', UT_03_18='(3,18)', UT_18_03='(18,3)', UT_18_04='(18,4)', UT_05_18='(5,18)', UT_18_05='(18,5)', UT_18_06='(18,6)', UT_07_18='(7,18)', UT_18_07='(18,7)', UT_18_08='(18,8)', UT_09_18='(9,18)', UT_18_09='(18,9)', UT_18_10='(18,10)', UT_11_18='(11,18)', UT_18_11='(18,11)', UT_18_12='(18,12)', UT_13_18='(13,18)', UT_18_13='(18,13)', UT_18_14='(18,14)', UT_15_18='(15,18)', UT_18_15='(18,15)', UT_18_16='(18,16)', UT_17_18='(17,18)', UT_18_17='(18,17)', UT_18_18='(18,18)', UT_00_19='(0,19)', UT_19_00='(19,0)', UT_19_01='(19,1)', UT_02_19='(2,19)', UT_19_02='(19,2)', UT_19_03='(19,3)', UT_04_19='(4,19)', UT_19_04='(19,4)', UT_19_05='(19,5)', UT_06_19='(6,19)', UT_19_06='(19,6)', UT_19_07='(19,7)', UT_08_19='(8,19)', UT_19_08='(19,8)', UT_19_09='(19,9)', UT_10_19='(10,19)', UT_19_10='(19,10)', UT_19_11='(19,11)', UT_12_19='(12,19)', UT_19_12='(19,12)', UT_19_13='(19,13)', UT_14_19='(14,19)', UT_19_14='(19,14)', UT_19_15='(19,15)', UT_16_19='(16,19)', UT_19_16='(19,16)', UT_19_17='(19,17)', UT_18_19='(18,19)', UT_19_18='(19,18)', UT_01_19='(1,19)', UT_03_19='(3,19)', UT_05_19='(5,19)', UT_07_19='(7,19)', UT_09_19='(9,19)', UT_11_19='(11,19)', UT_13_19='(13,19)', UT_15_19='(15,19)', UT_17_19='(17,19)', UT_19_19='(19,19)', UT_00_20='(0,20)', UT_02_20='(2,20)', UT_04_20='(4,20)', UT_06_20='(6,20)', UT_08_20='(8,20)', UT_10_20='(10,20)', UT_12_20='(12,20)', UT_14_20='(14,20)', UT_16_20='(16,20)', UT_18_20='(18,20)', UT_20_00='(20,0)', UT_01_20='(1,20)', UT_20_01='(20,1)', UT_20_02='(20,2)', UT_03_20='(3,20)', UT_20_03='(20,3)', UT_20_04='(20,4)', UT_05_20='(5,20)', UT_20_05='(20,5)', UT_20_06='(20,6)', UT_07_20='(7,20)', UT_20_07='(20,7)', UT_20_08='(20,8)', UT_09_20='(9,20)', UT_20_09='(20,9)', UT_20_10='(20,10)', UT_11_20='(11,20)', UT_20_11='(20,11)', UT_20_12='(20,12)', UT_13_20='(13,20)', UT_20_13='(20,13)', UT_20_14='(20,14)', UT_15_20='(15,20)', UT_20_15='(20,15)', UT_20_16='(20,16)', UT_17_20='(17,20)', UT_20_17='(20,17)', UT_20_18='(20,18)', UT_19_20='(19,20)', UT_20_19='(20,19)', UT_20_20='(20,20)', UT_00_21='(0,21)', UT_21_00='(21,0)', UT_21_01='(21,1)', UT_02_21='(2,21)', UT_21_02='(21,2)', UT_21_03='(21,3)', UT_04_21='(4,21)', UT_21_04='(21,4)', UT_21_05='(21,5)', UT_06_21='(6,21)', UT_21_06='(21,6)', UT_21_07='(21,7)', UT_08_21='(8,21)', UT_21_08='(21,8)', UT_21_09='(21,9)', UT_10_21='(10,21)', UT_21_10='(21,10)', UT_21_11='(21,11)', UT_12_21='(12,21)', UT_21_12='(21,12)', UT_21_13='(21,13)', UT_14_21='(14,21)', UT_21_14='(21,14)', UT_21_15='(21,15)', UT_16_21='(16,21)', UT_21_16='(21,16)', UT_21_17='(21,17)', UT_18_21='(18,21)', UT_21_18='(21,18)', UT_21_19='(21,19)', UT_20_21='(20,21)', UT_21_20='(21,20)', UT_01_21='(1,21)', UT_03_21='(3,21)', UT_05_21='(5,21)', UT_07_21='(7,21)', UT_09_21='(9,21)', UT_11_21='(11,21)', UT_13_21='(13,21)', UT_15_21='(15,21)', UT_17_21='(17,21)', UT_19_21='(19,21)', UT_21_21='(21,21)', UT_00_22='(0,22)', UT_02_22='(2,22)', UT_04_22='(4,22)', UT_06_22='(6,22)', UT_08_22='(8,22)', UT_10_22='(10,22)', UT_12_22='(12,22)', UT_14_22='(14,22)', UT_16_22='(16,22)', UT_18_22='(18,22)', UT_20_22='(20,22)', UT_22_00='(22,0)', UT_01_22='(1,22)', UT_22_01='(22,1)', UT_22_02='(22,2)', UT_03_22='(3,22)', UT_22_03='(22,3)', UT_22_04='(22,4)', UT_05_22='(5,22)', UT_22_05='(22,5)', UT_22_06='(22,6)', UT_07_22='(7,22)', UT_22_07='(22,7)', UT_22_08='(22,8)', UT_09_22='(9,22)', UT_22_09='(22,9)', UT_22_10='(22,10)', UT_11_22='(11,22)', UT_22_11='(22,11)', UT_22_12='(22,12)', UT_13_22='(13,22)', UT_22_13='(22,13)', UT_22_14='(22,14)', UT_15_22='(15,22)', UT_22_15='(22,15)', UT_22_16='(22,16)', UT_17_22='(17,22)', UT_22_17='(22,17)', UT_22_18='(22,18)', UT_19_22='(19,22)', UT_22_19='(22,19)', UT_22_20='(22,20)', UT_21_22='(21,22)', UT_22_21='(22,21)', UT_22_22='(22,22)', UT_00_23='(0,23)', UT_23_00='(23,0)', UT_23_01='(23,1)', UT_02_23='(2,23)', UT_23_02='(23,2)', UT_23_03='(23,3)', UT_04_23='(4,23)', UT_23_04='(23,4)', UT_23_05='(23,5)', UT_06_23='(6,23)', UT_23_06='(23,6)', UT_23_07='(23,7)', UT_08_23='(8,23)', UT_23_08='(23,8)', UT_23_09='(23,9)', UT_10_23='(10,23)', UT_23_10='(23,10)', UT_23_11='(23,11)', UT_12_23='(12,23)', UT_23_12='(23,12)', UT_23_13='(23,13)', UT_14_23='(14,23)', UT_23_14='(23,14)', UT_23_15='(23,15)', UT_16_23='(16,23)', UT_23_16='(23,16)', UT_23_17='(23,17)', UT_18_23='(18,23)', UT_23_18='(23,18)', UT_23_19='(23,19)', UT_20_23='(20,23)', UT_23_20='(23,20)', UT_23_21='(23,21)', UT_22_23='(22,23)', UT_23_22='(23,22)', UT_01_23='(1,23)', UT_03_23='(3,23)', UT_05_23='(5,23)', UT_07_23='(7,23)', UT_09_23='(9,23)', UT_11_23='(11,23)', UT_13_23='(13,23)', UT_15_23='(15,23)', UT_17_23='(17,23)', UT_19_23='(19,23)', UT_21_23='(21,23)', UT_23_23='(23,23)', UT_00_24='(0,24)', UT_02_24='(2,24)', UT_04_24='(4,24)', UT_06_24='(6,24)', UT_08_24='(8,24)', UT_10_24='(10,24)', UT_12_24='(12,24)', UT_14_24='(14,24)', UT_16_24='(16,24)', UT_18_24='(18,24)', UT_20_24='(20,24)', UT_22_24='(22,24)', UT_24_00='(24,0)', UT_01_24='(1,24)', UT_24_01='(24,1)', UT_24_02='(24,2)', UT_03_24='(3,24)', UT_24_03='(24,3)', UT_24_04='(24,4)', UT_05_24='(5,24)', UT_24_05='(24,5)', UT_24_06='(24,6)', UT_07_24='(7,24)', UT_24_07='(24,7)', UT_24_08='(24,8)', UT_09_24='(9,24)', UT_24_09='(24,9)', UT_24_10='(24,10)', UT_11_24='(11,24)', UT_24_11='(24,11)', UT_24_12='(24,12)', UT_13_24='(13,24)', UT_24_13='(24,13)', UT_24_14='(24,14)', UT_15_24='(15,24)', UT_24_15='(24,15)', UT_24_16='(24,16)', UT_17_24='(17,24)', UT_24_17='(24,17)', UT_24_18='(24,18)', UT_19_24='(19,24)', UT_24_19='(24,19)', UT_24_20='(24,20)', UT_21_24='(21,24)', UT_24_21='(24,21)', UT_24_22='(24,22)', UT_23_24='(23,24)', UT_24_23='(24,23)', UT_24_24='(24,24)', UT_00_25='(0,25)', UT_25_00='(25,0)', UT_25_01='(25,1)', UT_02_25='(2,25)', UT_25_02='(25,2)', UT_25_03='(25,3)', UT_04_25='(4,25)', UT_25_04='(25,4)', UT_25_05='(25,5)', UT_06_25='(6,25)', UT_25_06='(25,6)', UT_25_07='(25,7)', UT_08_25='(8,25)', UT_25_08='(25,8)', UT_25_09='(25,9)', UT_10_25='(10,25)', UT_25_10='(25,10)', UT_25_11='(25,11)', UT_12_25='(12,25)', UT_25_12='(25,12)', UT_25_13='(25,13)', UT_14_25='(14,25)', UT_25_14='(25,14)', UT_25_15='(25,15)', UT_16_25='(16,25)', UT_25_16='(25,16)', UT_25_17='(25,17)', UT_18_25='(18,25)', UT_25_18='(25,18)', UT_25_19='(25,19)', UT_20_25='(20,25)', UT_25_20='(25,20)', UT_25_21='(25,21)', UT_22_25='(22,25)', UT_25_22='(25,22)', UT_25_23='(25,23)', UT_24_25='(24,25)', UT_25_24='(25,24)', UT_01_25='(1,25)', UT_03_25='(3,25)', UT_05_25='(5,25)', UT_07_25='(7,25)', UT_09_25='(9,25)', UT_11_25='(11,25)', UT_13_25='(13,25)', UT_15_25='(15,25)', UT_17_25='(17,25)', UT_19_25='(19,25)', UT_21_25='(21,25)', UT_23_25='(23,25)', UT_25_25='(25,25)', UT_00_26='(0,26)', UT_02_26='(2,26)', UT_04_26='(4,26)', UT_06_26='(6,26)', UT_08_26='(8,26)', UT_10_26='(10,26)', UT_12_26='(12,26)', UT_14_26='(14,26)', UT_16_26='(16,26)', UT_18_26='(18,26)', UT_20_26='(20,26)', UT_22_26='(22,26)', UT_24_26='(24,26)', UT_26_00='(26,0)', UT_01_26='(1,26)', UT_26_01='(26,1)', UT_26_02='(26,2)', UT_03_26='(3,26)', UT_26_03='(26,3)', UT_26_04='(26,4)', UT_05_26='(5,26)', UT_26_05='(26,5)', UT_26_06='(26,6)', UT_07_26='(7,26)', UT_26_07='(26,7)', UT_26_08='(26,8)', UT_09_26='(9,26)', UT_26_09='(26,9)', UT_26_10='(26,10)', UT_11_26='(11,26)', UT_26_11='(26,11)', UT_26_12='(26,12)', UT_13_26='(13,26)', UT_26_13='(26,13)', UT_26_14='(26,14)', UT_15_26='(15,26)', UT_26_15='(26,15)', UT_26_16='(26,16)', UT_17_26='(17,26)', UT_26_17='(26,17)', UT_26_18='(26,18)', UT_19_26='(19,26)', UT_26_19='(26,19)', UT_26_20='(26,20)', UT_21_26='(21,26)', UT_26_21='(26,21)', UT_26_22='(26,22)', UT_23_26='(23,26)', UT_26_23='(26,23)', UT_26_24='(26,24)', UT_25_26='(25,26)', UT_26_25='(26,25)', UT_26_26='(26,26)', UT_00_27='(0,27)', UT_27_00='(27,0)', UT_27_01='(27,1)', UT_02_27='(2,27)', UT_27_02='(27,2)', UT_27_03='(27,3)', UT_04_27='(4,27)', UT_27_04='(27,4)', UT_27_05='(27,5)', UT_06_27='(6,27)', UT_27_06='(27,6)', UT_27_07='(27,7)', UT_08_27='(8,27)', UT_27_08='(27,8)', UT_27_09='(27,9)', UT_10_27='(10,27)', UT_27_10='(27,10)', UT_27_11='(27,11)', UT_12_27='(12,27)', UT_27_12='(27,12)', UT_27_13='(27,13)', UT_14_27='(14,27)', UT_27_14='(27,14)', UT_27_15='(27,15)', UT_16_27='(16,27)', UT_27_16='(27,16)', UT_27_17='(27,17)', UT_18_27='(18,27)', UT_27_18='(27,18)', UT_27_19='(27,19)', UT_20_27='(20,27)', UT_27_20='(27,20)', UT_27_21='(27,21)', UT_22_27='(22,27)', UT_27_22='(27,22)', UT_27_23='(27,23)', UT_24_27='(24,27)', UT_27_24='(27,24)', UT_27_25='(27,25)', UT_26_27='(26,27)', UT_27_26='(27,26)', UT_01_27='(1,27)', UT_03_27='(3,27)', UT_05_27='(5,27)', UT_07_27='(7,27)', UT_09_27='(9,27)', UT_11_27='(11,27)', UT_13_27='(13,27)', UT_15_27='(15,27)', UT_17_27='(17,27)', UT_19_27='(19,27)', UT_21_27='(21,27)', UT_23_27='(23,27)', UT_25_27='(25,27)', UT_27_27='(27,27)', UT_00_28='(0,28)', UT_02_28='(2,28)', UT_04_28='(4,28)', UT_06_28='(6,28)', UT_08_28='(8,28)', UT_10_28='(10,28)', UT_12_28='(12,28)', UT_14_28='(14,28)', UT_16_28='(16,28)', UT_18_28='(18,28)', UT_20_28='(20,28)', UT_22_28='(22,28)', UT_24_28='(24,28)', UT_26_28='(26,28)', UT_28_00='(28,0)', UT_01_28='(1,28)', UT_28_01='(28,1)', UT_28_02='(28,2)', UT_03_28='(3,28)', UT_28_03='(28,3)', UT_28_04='(28,4)', UT_05_28='(5,28)', UT_28_05='(28,5)', UT_28_06='(28,6)', UT_07_28='(7,28)', UT_28_07='(28,7)', UT_28_08='(28,8)', UT_09_28='(9,28)', UT_28_09='(28,9)', UT_28_10='(28,10)', UT_11_28='(11,28)', UT_28_11='(28,11)', UT_28_12='(28,12)', UT_13_28='(13,28)', UT_28_13='(28,13)', UT_28_14='(28,14)', UT_15_28='(15,28)', UT_28_15='(28,15)', UT_28_16='(28,16)', UT_17_28='(17,28)', UT_28_17='(28,17)', UT_28_18='(28,18)', UT_19_28='(19,28)', UT_28_19='(28,19)', UT_28_20='(28,20)', UT_21_28='(21,28)', UT_28_21='(28,21)', UT_28_22='(28,22)', UT_23_28='(23,28)', UT_28_23='(28,23)', UT_28_24='(28,24)', UT_25_28='(25,28)', UT_28_25='(28,25)', UT_28_26='(28,26)', UT_27_28='(27,28)', UT_28_27='(28,27)', UT_28_28='(28,28)', UT_00_29='(0,29)', UT_29_00='(29,0)', UT_29_01='(29,1)', UT_02_29='(2,29)', UT_29_02='(29,2)', UT_29_03='(29,3)', UT_04_29='(4,29)', UT_29_04='(29,4)', UT_29_05='(29,5)', UT_06_29='(6,29)', UT_29_06='(29,6)', UT_29_07='(29,7)', UT_08_29='(8,29)', UT_29_08='(29,8)', UT_29_09='(29,9)', UT_10_29='(10,29)', UT_29_10='(29,10)', UT_29_11='(29,11)', UT_12_29='(12,29)', UT_29_12='(29,12)', UT_29_13='(29,13)', UT_14_29='(14,29)', UT_29_14='(29,14)', UT_29_15='(29,15)', UT_16_29='(16,29)', UT_29_16='(29,16)', UT_29_17='(29,17)', UT_18_29='(18,29)', UT_29_18='(29,18)', UT_29_19='(29,19)', UT_20_29='(20,29)', UT_29_20='(29,20)', UT_29_21='(29,21)', UT_22_29='(22,29)', UT_29_22='(29,22)', UT_29_23='(29,23)', UT_24_29='(24,29)', UT_29_24='(29,24)', UT_29_25='(29,25)', UT_26_29='(26,29)', UT_29_26='(29,26)', UT_29_27='(29,27)', UT_28_29='(28,29)', UT_29_28='(29,28)', UT_01_29='(1,29)', UT_03_29='(3,29)', UT_05_29='(5,29)', UT_07_29='(7,29)', UT_09_29='(9,29)', UT_11_29='(11,29)', UT_13_29='(13,29)', UT_15_29='(15,29)', UT_17_29='(17,29)', UT_19_29='(19,29)', UT_21_29='(21,29)', UT_23_29='(23,29)', UT_25_29='(25,29)', UT_27_29='(27,29)', UT_29_29='(29,29)', UT_00_30='(0,30)', UT_02_30='(2,30)', UT_04_30='(4,30)', UT_06_30='(6,30)', UT_08_30='(8,30)', UT_10_30='(10,30)', UT_12_30='(12,30)', UT_14_30='(14,30)', UT_16_30='(16,30)', UT_18_30='(18,30)', UT_20_30='(20,30)', UT_22_30='(22,30)', UT_24_30='(24,30)', UT_26_30='(26,30)', UT_28_30='(28,30)', UT_30_00='(30,0)', UT_01_30='(1,30)', UT_30_01='(30,1)', UT_30_02='(30,2)', UT_03_30='(3,30)', UT_30_03='(30,3)', UT_30_04='(30,4)', UT_05_30='(5,30)', UT_30_05='(30,5)', UT_30_06='(30,6)', UT_07_30='(7,30)', UT_30_07='(30,7)', UT_30_08='(30,8)', UT_09_30='(9,30)', UT_30_09='(30,9)', UT_30_10='(30,10)', UT_11_30='(11,30)', UT_30_11='(30,11)', UT_30_12='(30,12)', UT_13_30='(13,30)', UT_30_13='(30,13)', UT_30_14='(30,14)', UT_15_30='(15,30)', UT_30_15='(30,15)', UT_30_16='(30,16)', UT_17_30='(17,30)', UT_30_17='(30,17)', UT_30_18='(30,18)', UT_19_30='(19,30)', UT_30_19='(30,19)', UT_30_20='(30,20)', UT_21_30='(21,30)', UT_30_21='(30,21)', UT_30_22='(30,22)', UT_23_30='(23,30)', UT_30_23='(30,23)', UT_30_24='(30,24)', UT_25_30='(25,30)', UT_30_25='(30,25)', UT_30_26='(30,26)', UT_27_30='(27,30)', UT_30_27='(30,27)', UT_30_28='(30,28)', UT_29_30='(29,30)', UT_30_29='(30,29)', UT_30_30='(30,30)', UT_00_31='(0,31)', UT_31_00='(31,0)', UT_31_01='(31,1)', UT_02_31='(2,31)', UT_31_02='(31,2)', UT_31_03='(31,3)', UT_04_31='(4,31)', UT_31_04='(31,4)', UT_31_05='(31,5)', UT_06_31='(6,31)', UT_31_06='(31,6)', UT_31_07='(31,7)', UT_08_31='(8,31)', UT_31_08='(31,8)', UT_31_09='(31,9)', UT_10_31='(10,31)', UT_31_10='(31,10)', UT_31_11='(31,11)', UT_12_31='(12,31)', UT_31_12='(31,12)', UT_31_13='(31,13)', UT_14_31='(14,31)', UT_31_14='(31,14)', UT_31_15='(31,15)', UT_16_31='(16,31)', UT_31_16='(31,16)', UT_31_17='(31,17)', UT_18_31='(18,31)', UT_31_18='(31,18)', UT_31_19='(31,19)', UT_20_31='(20,31)', UT_31_20='(31,20)', UT_31_21='(31,21)', UT_22_31='(22,31)', UT_31_22='(31,22)', UT_31_23='(31,23)', UT_24_31='(24,31)', UT_31_24='(31,24)', UT_31_25='(31,25)', UT_26_31='(26,31)', UT_31_26='(31,26)', UT_31_27='(31,27)', UT_28_31='(28,31)', UT_31_28='(31,28)', UT_31_29='(31,29)', UT_30_31='(30,31)', UT_31_30='(31,30)', UT_01_31='(1,31)', UT_03_31='(3,31)', UT_05_31='(5,31)', UT_07_31='(7,31)', UT_09_31='(9,31)', UT_11_31='(11,31)', UT_13_31='(13,31)', UT_15_31='(15,31)', UT_17_31='(17,31)', UT_19_31='(19,31)', UT_21_31='(21,31)', UT_23_31='(23,31)', UT_25_31='(25,31)', UT_27_31='(27,31)', UT_29_31='(29,31)', UT_31_31='(31,31)', UT_00_32='(0,32)', UT_02_32='(2,32)', UT_04_32='(4,32)', UT_06_32='(6,32)', UT_08_32='(8,32)', UT_10_32='(10,32)', UT_12_32='(12,32)', UT_14_32='(14,32)', UT_16_32='(16,32)', UT_18_32='(18,32)', UT_20_32='(20,32)', UT_22_32='(22,32)', UT_24_32='(24,32)', UT_26_32='(26,32)', UT_28_32='(28,32)', UT_30_32='(30,32)', UT_32_00='(32,0)', UT_01_32='(1,32)', UT_32_01='(32,1)', UT_32_02='(32,2)', UT_03_32='(3,32)', UT_32_03='(32,3)', UT_32_04='(32,4)', UT_05_32='(5,32)', UT_32_05='(32,5)', UT_32_06='(32,6)', UT_07_32='(7,32)', UT_32_07='(32,7)', UT_32_08='(32,8)', UT_09_32='(9,32)', UT_32_09='(32,9)', UT_32_10='(32,10)', UT_11_32='(11,32)', UT_32_11='(32,11)', UT_32_12='(32,12)', UT_13_32='(13,32)', UT_32_13='(32,13)', UT_32_14='(32,14)', UT_15_32='(15,32)', UT_32_15='(32,15)', UT_32_16='(32,16)', UT_17_32='(17,32)', UT_32_17='(32,17)', UT_32_18='(32,18)', UT_19_32='(19,32)', UT_32_19='(32,19)', UT_32_20='(32,20)', UT_21_32='(21,32)', UT_32_21='(32,21)', UT_32_22='(32,22)', UT_23_32='(23,32)', UT_32_23='(32,23)', UT_32_24='(32,24)', UT_25_32='(25,32)', UT_32_25='(32,25)', UT_32_26='(32,26)', UT_27_32='(27,32)', UT_32_27='(32,27)', UT_32_28='(32,28)', UT_29_32='(29,32)', UT_32_29='(32,29)', UT_32_30='(32,30)', UT_31_32='(31,32)', UT_32_31='(32,31)', UT_32_32='(32,32)', UT_00_33='(0,33)', UT_33_00='(33,0)', UT_33_01='(33,1)', UT_02_33='(2,33)', UT_33_02='(33,2)', UT_33_03='(33,3)', UT_04_33='(4,33)', UT_33_04='(33,4)', UT_33_05='(33,5)', UT_06_33='(6,33)', UT_33_06='(33,6)', UT_33_07='(33,7)', UT_08_33='(8,33)', UT_33_08='(33,8)', UT_33_09='(33,9)', UT_10_33='(10,33)', UT_33_10='(33,10)', UT_33_11='(33,11)', UT_12_33='(12,33)', UT_33_12='(33,12)', UT_33_13='(33,13)', UT_14_33='(14,33)', UT_33_14='(33,14)', UT_33_15='(33,15)', UT_16_33='(16,33)', UT_33_16='(33,16)', UT_33_17='(33,17)', UT_18_33='(18,33)', UT_33_18='(33,18)', UT_33_19='(33,19)', UT_20_33='(20,33)', UT_33_20='(33,20)', UT_33_21='(33,21)', UT_22_33='(22,33)', UT_33_22='(33,22)', UT_33_23='(33,23)', UT_24_33='(24,33)', UT_33_24='(33,24)', UT_33_25='(33,25)', UT_26_33='(26,33)', UT_33_26='(33,26)', UT_33_27='(33,27)', UT_28_33='(28,33)', UT_33_28='(33,28)', UT_33_29='(33,29)', UT_30_33='(30,33)', UT_33_30='(33,30)', UT_33_31='(33,31)', UT_32_33='(32,33)', UT_33_32='(33,32)', UT_01_33='(1,33)', UT_03_33='(3,33)', UT_05_33='(5,33)', UT_07_33='(7,33)', UT_09_33='(9,33)', UT_11_33='(11,33)', UT_13_33='(13,33)', UT_15_33='(15,33)', UT_17_33='(17,33)', UT_19_33='(19,33)', UT_21_33='(21,33)', UT_23_33='(23,33)', UT_25_33='(25,33)', UT_27_33='(27,33)', UT_29_33='(29,33)', UT_31_33='(31,33)', UT_33_33='(33,33)', UT_00_34='(0,34)', UT_02_34='(2,34)', UT_04_34='(4,34)', UT_06_34='(6,34)', UT_08_34='(8,34)', UT_10_34='(10,34)', UT_12_34='(12,34)', UT_14_34='(14,34)', UT_16_34='(16,34)', UT_18_34='(18,34)', UT_20_34='(20,34)', UT_22_34='(22,34)', UT_24_34='(24,34)', UT_26_34='(26,34)', UT_28_34='(28,34)', UT_30_34='(30,34)', UT_32_34='(32,34)', UT_34_00='(34,0)', UT_01_34='(1,34)', UT_34_01='(34,1)', UT_34_02='(34,2)', UT_03_34='(3,34)', UT_34_03='(34,3)', UT_34_04='(34,4)', UT_05_34='(5,34)', UT_34_05='(34,5)', UT_34_06='(34,6)', UT_07_34='(7,34)', UT_34_07='(34,7)', UT_34_08='(34,8)', UT_09_34='(9,34)', UT_34_09='(34,9)', UT_34_10='(34,10)', UT_11_34='(11,34)', UT_34_11='(34,11)', UT_34_12='(34,12)', UT_13_34='(13,34)', UT_34_13='(34,13)', UT_34_14='(34,14)', UT_15_34='(15,34)', UT_34_15='(34,15)', UT_34_16='(34,16)', UT_17_34='(17,34)', UT_34_17='(34,17)', UT_34_18='(34,18)', UT_19_34='(19,34)', UT_34_19='(34,19)', UT_34_20='(34,20)', UT_21_34='(21,34)', UT_34_21='(34,21)', UT_34_22='(34,22)', UT_23_34='(23,34)', UT_34_23='(34,23)', UT_34_24='(34,24)', UT_25_34='(25,34)', UT_34_25='(34,25)', UT_34_26='(34,26)', UT_27_34='(27,34)', UT_34_27='(34,27)', UT_34_28='(34,28)', UT_29_34='(29,34)', UT_34_29='(34,29)', UT_34_30='(34,30)', UT_31_34='(31,34)', UT_34_31='(34,31)', UT_34_32='(34,32)', UT_33_34='(33,34)', UT_34_33='(34,33)', UT_34_34='(34,34)', UT_00_35='(0,35)', UT_35_00='(35,0)', UT_35_01='(35,1)', UT_02_35='(2,35)', UT_35_02='(35,2)', UT_35_03='(35,3)', UT_04_35='(4,35)', UT_35_04='(35,4)', UT_35_05='(35,5)', UT_06_35='(6,35)', UT_35_06='(35,6)', UT_35_07='(35,7)', UT_08_35='(8,35)', UT_35_08='(35,8)', UT_35_09='(35,9)', UT_10_35='(10,35)', UT_35_10='(35,10)', UT_35_11='(35,11)', UT_12_35='(12,35)', UT_35_12='(35,12)', UT_35_13='(35,13)', UT_14_35='(14,35)', UT_35_14='(35,14)', UT_35_15='(35,15)', UT_16_35='(16,35)', UT_35_16='(35,16)', UT_35_17='(35,17)', UT_18_35='(18,35)', UT_35_18='(35,18)', UT_35_19='(35,19)', UT_20_35='(20,35)', UT_35_20='(35,20)', UT_35_21='(35,21)', UT_22_35='(22,35)', UT_35_22='(35,22)', UT_35_23='(35,23)', UT_24_35='(24,35)', UT_35_24='(35,24)', UT_35_25='(35,25)', UT_26_35='(26,35)', UT_35_26='(35,26)', UT_35_27='(35,27)', UT_28_35='(28,35)', UT_35_28='(35,28)', UT_35_29='(35,29)', UT_30_35='(30,35)', UT_35_30='(35,30)', UT_35_31='(35,31)', UT_32_35='(32,35)', UT_35_32='(35,32)', UT_35_33='(35,33)', UT_34_35='(34,35)', UT_35_34='(35,34)', UT_01_35='(1,35)', UT_03_35='(3,35)', UT_05_35='(5,35)', UT_07_35='(7,35)', UT_09_35='(9,35)', UT_11_35='(11,35)', UT_13_35='(13,35)', UT_15_35='(15,35)', UT_17_35='(17,35)', UT_19_35='(19,35)', UT_21_35='(21,35)', UT_23_35='(23,35)', UT_25_35='(25,35)', UT_27_35='(27,35)', UT_29_35='(29,35)', UT_31_35='(31,35)', UT_33_35='(33,35)', UT_35_35='(35,35)', UT_00_36='(0,36)', UT_02_36='(2,36)', UT_04_36='(4,36)', UT_06_36='(6,36)', UT_08_36='(8,36)', UT_10_36='(10,36)', UT_12_36='(12,36)', UT_14_36='(14,36)', UT_16_36='(16,36)', UT_18_36='(18,36)', UT_20_36='(20,36)', UT_22_36='(22,36)', UT_24_36='(24,36)', UT_26_36='(26,36)', UT_28_36='(28,36)', UT_30_36='(30,36)', UT_32_36='(32,36)', UT_34_36='(34,36)', UT_36_00='(36,0)', UT_01_36='(1,36)', UT_36_01='(36,1)', UT_36_02='(36,2)', UT_03_36='(3,36)', UT_36_03='(36,3)', UT_36_04='(36,4)', UT_05_36='(5,36)', UT_36_05='(36,5)', UT_36_06='(36,6)', UT_07_36='(7,36)', UT_36_07='(36,7)', UT_36_08='(36,8)', UT_09_36='(9,36)', UT_36_09='(36,9)', UT_36_10='(36,10)', UT_11_36='(11,36)', UT_36_11='(36,11)', UT_36_12='(36,12)', UT_13_36='(13,36)', UT_36_13='(36,13)', UT_36_14='(36,14)', UT_15_36='(15,36)', UT_36_15='(36,15)', UT_36_16='(36,16)', UT_17_36='(17,36)', UT_36_17='(36,17)', UT_36_18='(36,18)', UT_19_36='(19,36)', UT_36_19='(36,19)', UT_36_20='(36,20)', UT_21_36='(21,36)', UT_36_21='(36,21)', UT_36_22='(36,22)', UT_23_36='(23,36)', UT_36_23='(36,23)', UT_36_24='(36,24)', UT_25_36='(25,36)', UT_36_25='(36,25)', UT_36_26='(36,26)', UT_27_36='(27,36)', UT_36_27='(36,27)', UT_36_28='(36,28)', UT_29_36='(29,36)', UT_36_29='(36,29)', UT_36_30='(36,30)', UT_31_36='(31,36)', UT_36_31='(36,31)', UT_36_32='(36,32)', UT_33_36='(33,36)', UT_36_33='(36,33)', UT_36_34='(36,34)', UT_35_36='(35,36)', UT_36_35='(36,35)', UT_36_36='(36,36)', UT_00_37='(0,37)', UT_37_00='(37,0)', UT_37_01='(37,1)', UT_02_37='(2,37)', UT_37_02='(37,2)', UT_37_03='(37,3)', UT_04_37='(4,37)', UT_37_04='(37,4)', UT_37_05='(37,5)', UT_06_37='(6,37)', UT_37_06='(37,6)', UT_37_07='(37,7)', UT_08_37='(8,37)', UT_37_08='(37,8)', UT_37_09='(37,9)', UT_10_37='(10,37)', UT_37_10='(37,10)', UT_37_11='(37,11)', UT_12_37='(12,37)', UT_37_12='(37,12)', UT_37_13='(37,13)', UT_14_37='(14,37)', UT_37_14='(37,14)', UT_37_15='(37,15)', UT_16_37='(16,37)', UT_37_16='(37,16)', UT_37_17='(37,17)', UT_18_37='(18,37)', UT_37_18='(37,18)', UT_37_19='(37,19)', UT_20_37='(20,37)', UT_37_20='(37,20)', UT_37_21='(37,21)', UT_22_37='(22,37)', UT_37_22='(37,22)', UT_37_23='(37,23)', UT_24_37='(24,37)', UT_37_24='(37,24)', UT_37_25='(37,25)', UT_26_37='(26,37)', UT_37_26='(37,26)', UT_37_27='(37,27)', UT_28_37='(28,37)', UT_37_28='(37,28)', UT_37_29='(37,29)', UT_30_37='(30,37)', UT_37_30='(37,30)', UT_37_31='(37,31)', UT_32_37='(32,37)', UT_37_32='(37,32)', UT_37_33='(37,33)', UT_34_37='(34,37)', UT_37_34='(37,34)', UT_37_35='(37,35)', UT_36_37='(36,37)', UT_37_36='(37,36)', UT_01_37='(1,37)', UT_03_37='(3,37)', UT_05_37='(5,37)', UT_07_37='(7,37)', UT_09_37='(9,37)', UT_11_37='(11,37)', UT_13_37='(13,37)', UT_15_37='(15,37)', UT_17_37='(17,37)', UT_19_37='(19,37)', UT_21_37='(21,37)', UT_23_37='(23,37)', UT_25_37='(25,37)', UT_27_37='(27,37)', UT_29_37='(29,37)', UT_31_37='(31,37)', UT_33_37='(33,37)', UT_35_37='(35,37)', UT_37_37='(37,37)', UT_00_38='(0,38)', UT_02_38='(2,38)', UT_04_38='(4,38)', UT_06_38='(6,38)', UT_08_38='(8,38)', UT_10_38='(10,38)', UT_12_38='(12,38)', UT_14_38='(14,38)', UT_16_38='(16,38)', UT_18_38='(18,38)', UT_20_38='(20,38)', UT_22_38='(22,38)', UT_24_38='(24,38)', UT_26_38='(26,38)', UT_28_38='(28,38)', UT_30_38='(30,38)', UT_32_38='(32,38)', UT_34_38='(34,38)', UT_36_38='(36,38)', UT_38_00='(38,0)', UT_01_38='(1,38)', UT_38_01='(38,1)', UT_38_02='(38,2)', UT_03_38='(3,38)', UT_38_03='(38,3)', UT_38_04='(38,4)', UT_05_38='(5,38)', UT_38_05='(38,5)', UT_38_06='(38,6)', UT_07_38='(7,38)', UT_38_07='(38,7)', UT_38_08='(38,8)', UT_09_38='(9,38)', UT_38_09='(38,9)', UT_38_10='(38,10)', UT_11_38='(11,38)', UT_38_11='(38,11)', UT_38_12='(38,12)', UT_13_38='(13,38)', UT_38_13='(38,13)', UT_38_14='(38,14)', UT_15_38='(15,38)', UT_38_15='(38,15)', UT_38_16='(38,16)', UT_17_38='(17,38)', UT_38_17='(38,17)', UT_38_18='(38,18)', UT_19_38='(19,38)', UT_38_19='(38,19)', UT_38_20='(38,20)', UT_21_38='(21,38)', UT_38_21='(38,21)', UT_38_22='(38,22)', UT_23_38='(23,38)', UT_38_23='(38,23)', UT_38_24='(38,24)', UT_25_38='(25,38)', UT_38_25='(38,25)', UT_38_26='(38,26)', UT_27_38='(27,38)', UT_38_27='(38,27)', UT_38_28='(38,28)', UT_29_38='(29,38)', UT_38_29='(38,29)', UT_38_30='(38,30)', UT_31_38='(31,38)', UT_38_31='(38,31)', UT_38_32='(38,32)', UT_33_38='(33,38)', UT_38_33='(38,33)', UT_38_34='(38,34)', UT_35_38='(35,38)', UT_38_35='(38,35)', UT_38_36='(38,36)', UT_37_38='(37,38)', UT_38_37='(38,37)', UT_38_38='(38,38)', UT_00_39='(0,39)', UT_39_00='(39,0)', UT_39_01='(39,1)', UT_02_39='(2,39)', UT_39_02='(39,2)', UT_39_03='(39,3)', UT_04_39='(4,39)', UT_39_04='(39,4)', UT_39_05='(39,5)', UT_06_39='(6,39)', UT_39_06='(39,6)', UT_39_07='(39,7)', UT_08_39='(8,39)', UT_39_08='(39,8)', UT_39_09='(39,9)', UT_10_39='(10,39)', UT_39_10='(39,10)', UT_39_11='(39,11)', UT_12_39='(12,39)', UT_39_12='(39,12)', UT_39_13='(39,13)', UT_14_39='(14,39)', UT_39_14='(39,14)', UT_39_15='(39,15)', UT_16_39='(16,39)', UT_39_16='(39,16)', UT_39_17='(39,17)', UT_18_39='(18,39)', UT_39_18='(39,18)', UT_39_19='(39,19)', UT_20_39='(20,39)', UT_39_20='(39,20)', UT_39_21='(39,21)', UT_22_39='(22,39)', UT_39_22='(39,22)', UT_39_23='(39,23)', UT_24_39='(24,39)', UT_39_24='(39,24)', UT_39_25='(39,25)', UT_26_39='(26,39)', UT_39_26='(39,26)', UT_39_27='(39,27)', UT_28_39='(28,39)', UT_39_28='(39,28)', UT_39_29='(39,29)', UT_30_39='(30,39)', UT_39_30='(39,30)', UT_39_31='(39,31)', UT_32_39='(32,39)', UT_39_32='(39,32)', UT_39_33='(39,33)', UT_34_39='(34,39)', UT_39_34='(39,34)', UT_39_35='(39,35)', UT_36_39='(36,39)', UT_39_36='(39,36)', UT_39_37='(39,37)', UT_38_39='(38,39)', UT_39_38='(39,38)', UT_01_39='(1,39)', UT_03_39='(3,39)', UT_05_39='(5,39)', UT_07_39='(7,39)', UT_09_39='(9,39)', UT_11_39='(11,39)', UT_13_39='(13,39)', UT_15_39='(15,39)', UT_17_39='(17,39)', UT_19_39='(19,39)', UT_21_39='(21,39)', UT_23_39='(23,39)', UT_25_39='(25,39)', UT_27_39='(27,39)', UT_29_39='(29,39)', UT_31_39='(31,39)', UT_33_39='(33,39)', UT_35_39='(35,39)', UT_37_39='(37,39)', UT_39_39='(39,39)', UT_00_40='(0,40)', UT_02_40='(2,40)', UT_04_40='(4,40)', UT_06_40='(6,40)', UT_08_40='(8,40)', UT_10_40='(10,40)', UT_12_40='(12,40)', UT_14_40='(14,40)', UT_16_40='(16,40)', UT_18_40='(18,40)', UT_20_40='(20,40)', UT_22_40='(22,40)', UT_24_40='(24,40)', UT_26_40='(26,40)', UT_28_40='(28,40)', UT_30_40='(30,40)', UT_32_40='(32,40)', UT_34_40='(34,40)', UT_36_40='(36,40)', UT_38_40='(38,40)', UT_40_00='(40,0)', UT_01_40='(1,40)', UT_40_01='(40,1)', UT_40_02='(40,2)', UT_03_40='(3,40)', UT_40_03='(40,3)', UT_40_04='(40,4)', UT_05_40='(5,40)', UT_40_05='(40,5)', UT_40_06='(40,6)', UT_07_40='(7,40)', UT_40_07='(40,7)', UT_40_08='(40,8)', UT_09_40='(9,40)', UT_40_09='(40,9)', UT_40_10='(40,10)', UT_11_40='(11,40)', UT_40_11='(40,11)', UT_40_12='(40,12)', UT_13_40='(13,40)', UT_40_13='(40,13)', UT_40_14='(40,14)', UT_15_40='(15,40)', UT_40_15='(40,15)', UT_40_16='(40,16)', UT_17_40='(17,40)', UT_40_17='(40,17)', UT_40_18='(40,18)', UT_19_40='(19,40)', UT_40_19='(40,19)', UT_40_20='(40,20)', UT_21_40='(21,40)', UT_40_21='(40,21)', UT_40_22='(40,22)', UT_23_40='(23,40)', UT_40_23='(40,23)', UT_40_24='(40,24)', UT_25_40='(25,40)', UT_40_25='(40,25)', UT_40_26='(40,26)', UT_27_40='(27,40)', UT_40_27='(40,27)', UT_40_28='(40,28)', UT_29_40='(29,40)', UT_40_29='(40,29)', UT_40_30='(40,30)', UT_31_40='(31,40)', UT_40_31='(40,31)', UT_40_32='(40,32)', UT_33_40='(33,40)', UT_40_33='(40,33)', UT_40_34='(40,34)', UT_35_40='(35,40)', UT_40_35='(40,35)', UT_40_36='(40,36)', UT_37_40='(37,40)', UT_40_37='(40,37)', UT_40_38='(40,38)', UT_39_40='(39,40)', UT_40_39='(40,39)', UT_40_40='(40,40)', UT_00_41='(0,41)', UT_41_00='(41,0)', UT_41_01='(41,1)', UT_02_41='(2,41)', UT_41_02='(41,2)', UT_41_03='(41,3)', UT_04_41='(4,41)', UT_41_04='(41,4)', UT_41_05='(41,5)', UT_06_41='(6,41)', UT_41_06='(41,6)', UT_41_07='(41,7)', UT_08_41='(8,41)', UT_41_08='(41,8)', UT_41_09='(41,9)', UT_10_41='(10,41)', UT_41_10='(41,10)', UT_41_11='(41,11)', UT_12_41='(12,41)', UT_41_12='(41,12)', UT_41_13='(41,13)', UT_14_41='(14,41)', UT_41_14='(41,14)', UT_41_15='(41,15)', UT_16_41='(16,41)', UT_41_16='(41,16)', UT_41_17='(41,17)', UT_18_41='(18,41)', UT_41_18='(41,18)', UT_41_19='(41,19)', UT_20_41='(20,41)', UT_41_20='(41,20)', UT_41_21='(41,21)', UT_22_41='(22,41)', UT_41_22='(41,22)', UT_41_23='(41,23)', UT_24_41='(24,41)', UT_41_24='(41,24)', UT_41_25='(41,25)', UT_26_41='(26,41)', UT_41_26='(41,26)', UT_41_27='(41,27)', UT_28_41='(28,41)', UT_41_28='(41,28)', UT_41_29='(41,29)', UT_30_41='(30,41)', UT_41_30='(41,30)', UT_41_31='(41,31)', UT_32_41='(32,41)', UT_41_32='(41,32)', UT_41_33='(41,33)', UT_34_41='(34,41)', UT_41_34='(41,34)', UT_41_35='(41,35)', UT_36_41='(36,41)', UT_41_36='(41,36)', UT_41_37='(41,37)', UT_38_41='(38,41)', UT_41_38='(41,38)', UT_41_39='(41,39)', UT_40_41='(40,41)', UT_41_40='(41,40)', UT_01_41='(1,41)', UT_03_41='(3,41)', UT_05_41='(5,41)', UT_07_41='(7,41)', UT_09_41='(9,41)', UT_11_41='(11,41)', UT_13_41='(13,41)', UT_15_41='(15,41)', UT_17_41='(17,41)', UT_19_41='(19,41)', UT_21_41='(21,41)', UT_23_41='(23,41)', UT_25_41='(25,41)', UT_27_41='(27,41)', UT_29_41='(29,41)', UT_31_41='(31,41)', UT_33_41='(33,41)', UT_35_41='(35,41)', UT_37_41='(37,41)', UT_39_41='(39,41)', UT_41_41='(41,41)', UT_00_42='(0,42)', UT_02_42='(2,42)', UT_04_42='(4,42)', UT_06_42='(6,42)', UT_08_42='(8,42)', UT_10_42='(10,42)', UT_12_42='(12,42)', UT_14_42='(14,42)', UT_16_42='(16,42)', UT_18_42='(18,42)', UT_20_42='(20,42)', UT_22_42='(22,42)', UT_24_42='(24,42)', UT_26_42='(26,42)', UT_28_42='(28,42)', UT_30_42='(30,42)', UT_32_42='(32,42)', UT_34_42='(34,42)', UT_36_42='(36,42)', UT_38_42='(38,42)', UT_40_42='(40,42)', UT_42_00='(42,0)', UT_01_42='(1,42)', UT_42_01='(42,1)', UT_42_02='(42,2)', UT_03_42='(3,42)', UT_42_03='(42,3)', UT_42_04='(42,4)', UT_05_42='(5,42)', UT_42_05='(42,5)', UT_42_06='(42,6)', UT_07_42='(7,42)', UT_42_07='(42,7)', UT_42_08='(42,8)', UT_09_42='(9,42)', UT_42_09='(42,9)', UT_42_10='(42,10)', UT_11_42='(11,42)', UT_42_11='(42,11)', UT_42_12='(42,12)', UT_13_42='(13,42)', UT_42_13='(42,13)', UT_42_14='(42,14)', UT_15_42='(15,42)', UT_42_15='(42,15)', UT_42_16='(42,16)', UT_17_42='(17,42)', UT_42_17='(42,17)', UT_42_18='(42,18)', UT_19_42='(19,42)', UT_42_19='(42,19)', UT_42_20='(42,20)', UT_21_42='(21,42)', UT_42_21='(42,21)', UT_42_22='(42,22)', UT_23_42='(23,42)', UT_42_23='(42,23)', UT_42_24='(42,24)', UT_25_42='(25,42)', UT_42_25='(42,25)', UT_42_26='(42,26)', UT_27_42='(27,42)', UT_42_27='(42,27)', UT_42_28='(42,28)', UT_29_42='(29,42)', UT_42_29='(42,29)', UT_42_30='(42,30)', UT_31_42='(31,42)', UT_42_31='(42,31)', UT_42_32='(42,32)', UT_33_42='(33,42)', UT_42_33='(42,33)', UT_42_34='(42,34)', UT_35_42='(35,42)', UT_42_35='(42,35)', UT_42_36='(42,36)', UT_37_42='(37,42)', UT_42_37='(42,37)', UT_42_38='(42,38)', UT_39_42='(39,42)', UT_42_39='(42,39)', UT_42_40='(42,40)', UT_41_42='(41,42)', UT_42_41='(42,41)', UT_42_42='(42,42)', UT_00_43='(0,43)', UT_43_00='(43,0)', UT_43_01='(43,1)', UT_02_43='(2,43)', UT_43_02='(43,2)', UT_43_03='(43,3)', UT_04_43='(4,43)', UT_43_04='(43,4)', UT_43_05='(43,5)', UT_06_43='(6,43)', UT_43_06='(43,6)', UT_43_07='(43,7)', UT_08_43='(8,43)', UT_43_08='(43,8)', UT_43_09='(43,9)', UT_10_43='(10,43)', UT_43_10='(43,10)', UT_43_11='(43,11)', UT_12_43='(12,43)', UT_43_12='(43,12)', UT_43_13='(43,13)', UT_14_43='(14,43)', UT_43_14='(43,14)', UT_43_15='(43,15)', UT_16_43='(16,43)', UT_43_16='(43,16)', UT_43_17='(43,17)', UT_18_43='(18,43)', UT_43_18='(43,18)', UT_43_19='(43,19)', UT_20_43='(20,43)', UT_43_20='(43,20)', UT_43_21='(43,21)', UT_22_43='(22,43)', UT_43_22='(43,22)', UT_43_23='(43,23)', UT_24_43='(24,43)', UT_43_24='(43,24)', UT_43_25='(43,25)', UT_26_43='(26,43)', UT_43_26='(43,26)', UT_43_27='(43,27)', UT_28_43='(28,43)', UT_43_28='(43,28)', UT_43_29='(43,29)', UT_30_43='(30,43)', UT_43_30='(43,30)', UT_43_31='(43,31)', UT_32_43='(32,43)', UT_43_32='(43,32)', UT_43_33='(43,33)', UT_34_43='(34,43)', UT_43_34='(43,34)', UT_43_35='(43,35)', UT_36_43='(36,43)', UT_43_36='(43,36)', UT_43_37='(43,37)', UT_38_43='(38,43)', UT_43_38='(43,38)', UT_43_39='(43,39)', UT_40_43='(40,43)', UT_43_40='(43,40)', UT_43_41='(43,41)', UT_42_43='(42,43)', UT_43_42='(43,42)', UT_01_43='(1,43)', UT_03_43='(3,43)', UT_05_43='(5,43)', UT_07_43='(7,43)', UT_09_43='(9,43)', UT_11_43='(11,43)', UT_13_43='(13,43)', UT_15_43='(15,43)', UT_17_43='(17,43)', UT_19_43='(19,43)', UT_21_43='(21,43)', UT_23_43='(23,43)', UT_25_43='(25,43)', UT_27_43='(27,43)', UT_29_43='(29,43)', UT_31_43='(31,43)', UT_33_43='(33,43)', UT_35_43='(35,43)', UT_37_43='(37,43)', UT_39_43='(39,43)', UT_41_43='(41,43)', UT_43_43='(43,43)', UT_00_44='(0,44)', UT_02_44='(2,44)', UT_04_44='(4,44)', UT_06_44='(6,44)', UT_08_44='(8,44)', UT_10_44='(10,44)', UT_12_44='(12,44)', UT_14_44='(14,44)', UT_16_44='(16,44)', UT_18_44='(18,44)', UT_20_44='(20,44)', UT_22_44='(22,44)', UT_24_44='(24,44)', UT_26_44='(26,44)', UT_28_44='(28,44)', UT_30_44='(30,44)', UT_32_44='(32,44)', UT_34_44='(34,44)', UT_36_44='(36,44)', UT_38_44='(38,44)', UT_40_44='(40,44)', UT_42_44='(42,44)', UT_44_00='(44,0)', UT_01_44='(1,44)', UT_44_01='(44,1)', UT_44_02='(44,2)', UT_03_44='(3,44)', UT_44_03='(44,3)', UT_44_04='(44,4)', UT_05_44='(5,44)', UT_44_05='(44,5)', UT_44_06='(44,6)', UT_07_44='(7,44)', UT_44_07='(44,7)', UT_44_08='(44,8)', UT_09_44='(9,44)', UT_44_09='(44,9)', UT_44_10='(44,10)', UT_11_44='(11,44)', UT_44_11='(44,11)', UT_44_12='(44,12)', UT_13_44='(13,44)', UT_44_13='(44,13)', UT_44_14='(44,14)', UT_15_44='(15,44)', UT_44_15='(44,15)', UT_44_16='(44,16)', UT_17_44='(17,44)', UT_44_17='(44,17)', UT_44_18='(44,18)', UT_19_44='(19,44)', UT_44_19='(44,19)', UT_44_20='(44,20)', UT_21_44='(21,44)', UT_44_21='(44,21)', UT_44_22='(44,22)', UT_23_44='(23,44)', UT_44_23='(44,23)', UT_44_24='(44,24)', UT_25_44='(25,44)', UT_44_25='(44,25)', UT_44_26='(44,26)', UT_27_44='(27,44)', UT_44_27='(44,27)', UT_44_28='(44,28)', UT_29_44='(29,44)', UT_44_29='(44,29)', UT_44_30='(44,30)', UT_31_44='(31,44)', UT_44_31='(44,31)', UT_44_32='(44,32)', UT_33_44='(33,44)', UT_44_33='(44,33)', UT_44_34='(44,34)', UT_35_44='(35,44)', UT_44_35='(44,35)', UT_44_36='(44,36)', UT_37_44='(37,44)', UT_44_37='(44,37)', UT_44_38='(44,38)', UT_39_44='(39,44)', UT_44_39='(44,39)', UT_44_40='(44,40)', UT_41_44='(41,44)', UT_44_41='(44,41)', UT_44_42='(44,42)', UT_43_44='(43,44)', UT_44_43='(44,43)', UT_44_44='(44,44)', UT_00_45='(0,45)', UT_45_00='(45,0)', UT_45_01='(45,1)', UT_02_45='(2,45)', UT_45_02='(45,2)', UT_45_03='(45,3)', UT_04_45='(4,45)', UT_45_04='(45,4)', UT_45_05='(45,5)', UT_06_45='(6,45)', UT_45_06='(45,6)', UT_45_07='(45,7)', UT_08_45='(8,45)', UT_45_08='(45,8)', UT_45_09='(45,9)', UT_10_45='(10,45)', UT_45_10='(45,10)', UT_45_11='(45,11)', UT_12_45='(12,45)', UT_45_12='(45,12)', UT_45_13='(45,13)', UT_14_45='(14,45)', UT_45_14='(45,14)', UT_45_15='(45,15)', UT_16_45='(16,45)', UT_45_16='(45,16)', UT_45_17='(45,17)', UT_18_45='(18,45)', UT_45_18='(45,18)', UT_45_19='(45,19)', UT_20_45='(20,45)', UT_45_20='(45,20)', UT_45_21='(45,21)', UT_22_45='(22,45)', UT_45_22='(45,22)', UT_45_23='(45,23)', UT_24_45='(24,45)', UT_45_24='(45,24)', UT_45_25='(45,25)', UT_26_45='(26,45)', UT_45_26='(45,26)', UT_45_27='(45,27)', UT_28_45='(28,45)', UT_45_28='(45,28)', UT_45_29='(45,29)', UT_30_45='(30,45)', UT_45_30='(45,30)', UT_45_31='(45,31)', UT_32_45='(32,45)', UT_45_32='(45,32)', UT_45_33='(45,33)', UT_34_45='(34,45)', UT_45_34='(45,34)', UT_45_35='(45,35)', UT_36_45='(36,45)', UT_45_36='(45,36)', UT_45_37='(45,37)', UT_38_45='(38,45)', UT_45_38='(45,38)', UT_45_39='(45,39)', UT_40_45='(40,45)', UT_45_40='(45,40)', UT_45_41='(45,41)', UT_42_45='(42,45)', UT_45_42='(45,42)', UT_45_43='(45,43)', UT_44_45='(44,45)', UT_45_44='(45,44)', UT_01_45='(1,45)', UT_03_45='(3,45)', UT_05_45='(5,45)', UT_07_45='(7,45)', UT_09_45='(9,45)', UT_11_45='(11,45)', UT_13_45='(13,45)', UT_15_45='(15,45)', UT_17_45='(17,45)', UT_19_45='(19,45)', UT_21_45='(21,45)', UT_23_45='(23,45)', UT_25_45='(25,45)', UT_27_45='(27,45)', UT_29_45='(29,45)', UT_31_45='(31,45)', UT_33_45='(33,45)', UT_35_45='(35,45)', UT_37_45='(37,45)', UT_39_45='(39,45)', UT_41_45='(41,45)', UT_43_45='(43,45)', UT_45_45='(45,45)', UT_00_46='(0,46)', UT_02_46='(2,46)', UT_04_46='(4,46)', UT_06_46='(6,46)', UT_08_46='(8,46)', UT_10_46='(10,46)', UT_12_46='(12,46)', UT_14_46='(14,46)', UT_16_46='(16,46)', UT_18_46='(18,46)', UT_20_46='(20,46)', UT_22_46='(22,46)', UT_24_46='(24,46)', UT_26_46='(26,46)', UT_28_46='(28,46)', UT_30_46='(30,46)', UT_32_46='(32,46)', UT_34_46='(34,46)', UT_36_46='(36,46)', UT_38_46='(38,46)', UT_40_46='(40,46)', UT_42_46='(42,46)', UT_44_46='(44,46)', UT_46_00='(46,0)', UT_01_46='(1,46)', UT_46_01='(46,1)', UT_46_02='(46,2)', UT_03_46='(3,46)', UT_46_03='(46,3)', UT_46_04='(46,4)', UT_05_46='(5,46)', UT_46_05='(46,5)', UT_46_06='(46,6)', UT_07_46='(7,46)', UT_46_07='(46,7)', UT_46_08='(46,8)', UT_09_46='(9,46)', UT_46_09='(46,9)', UT_46_10='(46,10)', UT_11_46='(11,46)', UT_46_11='(46,11)', UT_46_12='(46,12)', UT_13_46='(13,46)', UT_46_13='(46,13)', UT_46_14='(46,14)', UT_15_46='(15,46)', UT_46_15='(46,15)', UT_46_16='(46,16)', UT_17_46='(17,46)', UT_46_17='(46,17)', UT_46_18='(46,18)', UT_19_46='(19,46)', UT_46_19='(46,19)', UT_46_20='(46,20)', UT_21_46='(21,46)', UT_46_21='(46,21)', UT_46_22='(46,22)', UT_23_46='(23,46)', UT_46_23='(46,23)', UT_46_24='(46,24)', UT_25_46='(25,46)', UT_46_25='(46,25)', UT_46_26='(46,26)', UT_27_46='(27,46)', UT_46_27='(46,27)', UT_46_28='(46,28)', UT_29_46='(29,46)', UT_46_29='(46,29)', UT_46_30='(46,30)', UT_31_46='(31,46)', UT_46_31='(46,31)', UT_46_32='(46,32)', UT_33_46='(33,46)', UT_46_33='(46,33)', UT_46_34='(46,34)', UT_35_46='(35,46)', UT_46_35='(46,35)', UT_46_36='(46,36)', UT_37_46='(37,46)', UT_46_37='(46,37)', UT_46_38='(46,38)', UT_39_46='(39,46)', UT_46_39='(46,39)', UT_46_40='(46,40)', UT_41_46='(41,46)', UT_46_41='(46,41)', UT_46_42='(46,42)', UT_43_46='(43,46)', UT_46_43='(46,43)', UT_46_44='(46,44)', UT_45_46='(45,46)', UT_46_45='(46,45)', UT_46_46='(46,46)', UT_00_47='(0,47)', UT_47_00='(47,0)', UT_47_01='(47,1)', UT_02_47='(2,47)', UT_47_02='(47,2)', UT_47_03='(47,3)', UT_04_47='(4,47)', UT_47_04='(47,4)', UT_47_05='(47,5)', UT_06_47='(6,47)', UT_47_06='(47,6)', UT_47_07='(47,7)', UT_08_47='(8,47)', UT_47_08='(47,8)', UT_47_09='(47,9)', UT_10_47='(10,47)', UT_47_10='(47,10)', UT_47_11='(47,11)', UT_12_47='(12,47)', UT_47_12='(47,12)', UT_47_13='(47,13)', UT_14_47='(14,47)', UT_47_14='(47,14)', UT_47_15='(47,15)', UT_16_47='(16,47)', UT_47_16='(47,16)', UT_47_17='(47,17)', UT_18_47='(18,47)', UT_47_18='(47,18)', UT_47_19='(47,19)', UT_20_47='(20,47)', UT_47_20='(47,20)', UT_47_21='(47,21)', UT_22_47='(22,47)', UT_47_22='(47,22)', UT_47_23='(47,23)', UT_24_47='(24,47)', UT_47_24='(47,24)', UT_47_25='(47,25)', UT_26_47='(26,47)', UT_47_26='(47,26)', UT_47_27='(47,27)', UT_28_47='(28,47)', UT_47_28='(47,28)', UT_47_29='(47,29)', UT_30_47='(30,47)', UT_47_30='(47,30)', UT_47_31='(47,31)', UT_32_47='(32,47)', UT_47_32='(47,32)', UT_47_33='(47,33)', UT_34_47='(34,47)', UT_47_34='(47,34)', UT_47_35='(47,35)', UT_36_47='(36,47)', UT_47_36='(47,36)', UT_47_37='(47,37)', UT_38_47='(38,47)', UT_47_38='(47,38)', UT_47_39='(47,39)', UT_40_47='(40,47)', UT_47_40='(47,40)', UT_47_41='(47,41)', UT_42_47='(42,47)', UT_47_42='(47,42)', UT_47_43='(47,43)', UT_44_47='(44,47)', UT_47_44='(47,44)', UT_47_45='(47,45)', UT_46_47='(46,47)', UT_47_46='(47,46)', UT_01_47='(1,47)', UT_03_47='(3,47)', UT_05_47='(5,47)', UT_07_47='(7,47)', UT_09_47='(9,47)', UT_11_47='(11,47)', UT_13_47='(13,47)', UT_15_47='(15,47)', UT_17_47='(17,47)', UT_19_47='(19,47)', UT_21_47='(21,47)', UT_23_47='(23,47)', UT_25_47='(25,47)', UT_27_47='(27,47)', UT_29_47='(29,47)', UT_31_47='(31,47)', UT_33_47='(33,47)', UT_35_47='(35,47)', UT_37_47='(37,47)', UT_39_47='(39,47)', UT_41_47='(41,47)', UT_43_47='(43,47)', UT_45_47='(45,47)', UT_47_47='(47,47)', UT_00_48='(0,48)', UT_02_48='(2,48)', UT_04_48='(4,48)', UT_06_48='(6,48)', UT_08_48='(8,48)', UT_10_48='(10,48)', UT_12_48='(12,48)', UT_14_48='(14,48)', UT_16_48='(16,48)', UT_18_48='(18,48)', UT_20_48='(20,48)', UT_22_48='(22,48)', UT_24_48='(24,48)', UT_26_48='(26,48)', UT_28_48='(28,48)', UT_30_48='(30,48)', UT_32_48='(32,48)', UT_34_48='(34,48)', UT_36_48='(36,48)', UT_38_48='(38,48)', UT_40_48='(40,48)', UT_42_48='(42,48)', UT_44_48='(44,48)', UT_46_48='(46,48)', UT_48_00='(48,0)', UT_01_48='(1,48)', UT_48_01='(48,1)', UT_48_02='(48,2)', UT_03_48='(3,48)', UT_48_03='(48,3)', UT_48_04='(48,4)', UT_05_48='(5,48)', UT_48_05='(48,5)', UT_48_06='(48,6)', UT_07_48='(7,48)', UT_48_07='(48,7)', UT_48_08='(48,8)', UT_09_48='(9,48)', UT_48_09='(48,9)', UT_48_10='(48,10)', UT_11_48='(11,48)', UT_48_11='(48,11)', UT_48_12='(48,12)', UT_13_48='(13,48)', UT_48_13='(48,13)', UT_48_14='(48,14)', UT_15_48='(15,48)', UT_48_15='(48,15)', UT_48_16='(48,16)', UT_17_48='(17,48)', UT_48_17='(48,17)', UT_48_18='(48,18)', UT_19_48='(19,48)', UT_48_19='(48,19)', UT_48_20='(48,20)', UT_21_48='(21,48)', UT_48_21='(48,21)', UT_48_22='(48,22)', UT_23_48='(23,48)', UT_48_23='(48,23)', UT_48_24='(48,24)', UT_25_48='(25,48)', UT_48_25='(48,25)', UT_48_26='(48,26)', UT_27_48='(27,48)', UT_48_27='(48,27)', UT_48_28='(48,28)', UT_29_48='(29,48)', UT_48_29='(48,29)', UT_48_30='(48,30)', UT_31_48='(31,48)', UT_48_31='(48,31)', UT_48_32='(48,32)', UT_33_48='(33,48)', UT_48_33='(48,33)', UT_48_34='(48,34)', UT_35_48='(35,48)', UT_48_35='(48,35)', UT_48_36='(48,36)', UT_37_48='(37,48)', UT_48_37='(48,37)', UT_48_38='(48,38)', UT_39_48='(39,48)', UT_48_39='(48,39)', UT_48_40='(48,40)', UT_41_48='(41,48)', UT_48_41='(48,41)', UT_48_42='(48,42)', UT_43_48='(43,48)', UT_48_43='(48,43)', UT_48_44='(48,44)', UT_45_48='(45,48)', UT_48_45='(48,45)', UT_48_46='(48,46)', UT_47_48='(47,48)', UT_48_47='(48,47)', UT_48_48='(48,48)', UT_00_49='(0,49)', UT_49_00='(49,0)', UT_49_01='(49,1)', UT_02_49='(2,49)', UT_49_02='(49,2)', UT_49_03='(49,3)', UT_04_49='(4,49)', UT_49_04='(49,4)', UT_49_05='(49,5)', UT_06_49='(6,49)', UT_49_06='(49,6)', UT_49_07='(49,7)', UT_08_49='(8,49)', UT_49_08='(49,8)', UT_49_09='(49,9)', UT_10_49='(10,49)', UT_49_10='(49,10)', UT_49_11='(49,11)', UT_12_49='(12,49)', UT_49_12='(49,12)', UT_49_13='(49,13)', UT_14_49='(14,49)', UT_49_14='(49,14)', UT_49_15='(49,15)', UT_16_49='(16,49)', UT_49_16='(49,16)', UT_49_17='(49,17)', UT_18_49='(18,49)', UT_49_18='(49,18)', UT_49_19='(49,19)', UT_20_49='(20,49)', UT_49_20='(49,20)', UT_49_21='(49,21)', UT_22_49='(22,49)', UT_49_22='(49,22)', UT_49_23='(49,23)', UT_24_49='(24,49)', UT_49_24='(49,24)', UT_49_25='(49,25)', UT_26_49='(26,49)', UT_49_26='(49,26)', UT_49_27='(49,27)', UT_28_49='(28,49)', UT_49_28='(49,28)', UT_49_29='(49,29)', UT_30_49='(30,49)', UT_49_30='(49,30)', UT_49_31='(49,31)', UT_32_49='(32,49)', UT_49_32='(49,32)', UT_49_33='(49,33)', UT_34_49='(34,49)', UT_49_34='(49,34)', UT_49_35='(49,35)', UT_36_49='(36,49)', UT_49_36='(49,36)', UT_49_37='(49,37)', UT_38_49='(38,49)', UT_49_38='(49,38)', UT_49_39='(49,39)', UT_40_49='(40,49)', UT_49_40='(49,40)', UT_49_41='(49,41)', UT_42_49='(42,49)', UT_49_42='(49,42)', UT_49_43='(49,43)', UT_44_49='(44,49)', UT_49_44='(49,44)', UT_49_45='(49,45)', UT_46_49='(46,49)', UT_49_46='(49,46)', UT_49_47='(49,47)', UT_48_49='(48,49)', UT_49_48='(49,48)', UT_01_49='(1,49)', UT_03_49='(3,49)', UT_05_49='(5,49)', UT_07_49='(7,49)', UT_09_49='(9,49)', UT_11_49='(11,49)', UT_13_49='(13,49)', UT_15_49='(15,49)', UT_17_49='(17,49)', UT_19_49='(19,49)', UT_21_49='(21,49)', UT_23_49='(23,49)', UT_25_49='(25,49)', UT_27_49='(27,49)', UT_29_49='(29,49)', UT_31_49='(31,49)', UT_33_49='(33,49)', UT_35_49='(35,49)', UT_37_49='(37,49)', UT_39_49='(39,49)', UT_41_49='(41,49)', UT_43_49='(43,49)', UT_45_49='(45,49)', UT_47_49='(47,49)', UT_49_49='(49,49)')
VOCAB_LIST = ['<ADJLIST_START>', '<ADJLIST_END>', '<TARGET_START>', '<TARGET_END>', '<ORIGIN_START>', '<ORIGIN_END>', '<PATH_START>', '<PATH_END>', '<-->', ';', '<PADDING>', '(', ',', ')', '=', '||', ':', 'THEN', '-', '<UNK>', 'TARGET_A', 'TARGET_B', 'TARGET_C', 'TARGET_D', 'TARGET_E', 'TARGET_F', 'TARGET_G', 'TARGET_H', 'TARGET_I', 'TARGET_J', 'TARGET_K', 'TARGET_L', 'TARGET_M', 'TARGET_N', 'TARGET_O', 'TARGET_P', 'TARGET_Q', 'TARGET_R', 'TARGET_S', 'TARGET_T', 'TARGET_U', 'TARGET_V', 'TARGET_W', 'TARGET_X', 'TARGET_Y', 'TARGET_Z', 'TARGET_NORTH', 'TARGET_SOUTH', 'TARGET_EAST', 'TARGET_WEST', 'TARGET_NORTHEAST', 'TARGET_NORTHWEST', 'TARGET_SOUTHEAST', 'TARGET_SOUTHWEST', 'TARGET_CENTER', 'NORTH', 'SOUTH', 'EAST', 'WEST', 'FORWARD', 'BACKWARD', 'LEFT', 'RIGHT', 'STAY', '+0', '+1', '+2', '+3', '+4', '+5', '+6', '+7', '+8', '+9', '+10', '+11', '+12', '+13', '+14', '+15', '+16', '+17', '+18', '+19', '+20', '+21', '+22', '+23', '+24', '+25', '+26', '+27', '+28', '+29', '+30', '+31', '+32', '+33', '+34', '+35', '+36', '+37', '+38', '+39', '+40', '+41', '+42', '+43', '+44', '+45', '+46', '+47', '+48', '+49', '+50', '+51', '+52', '+53', '+54', '+55', '+56', '+57', '+58', '+59', '+60', '+61', '+62', '+63', '+64', '+65', '+66', '+67', '+68', '+69', '+70', '+71', '+72', '+73', '+74', '+75', '+76', '+77', '+78', '+79', '+80', '+81', '+82', '+83', '+84', '+85', '+86', '+87', '+88', '+89', '+90', '+91', '+92', '+93', '+94', '+95', '+96', '+97', '+98', '+99', '+100', '+101', '+102', '+103', '+104', '+105', '+106', '+107', '+108', '+109', '+110', '+111', '+112', '+113', '+114', '+115', '+116', '+117', '+118', '+119', '+120', '+121', '+122', '+123', '+124', '+125', '+126', '+127', '+128', '+129', '+130', '+131', '+132', '+133', '+134', '+135', '+136', '+137', '+138', '+139', '+140', '+141', '+142', '+143', '+144', '+145', '+146', '+147', '+148', '+149', '+150', '+151', '+152', '+153', '+154', '+155', '+156', '+157', '+158', '+159', '+160', '+161', '+162', '+163', '+164', '+165', '+166', '+167', '+168', '+169', '+170', '+171', '+172', '+173', '+174', '+175', '+176', '+177', '+178', '+179', '+180', '+181', '+182', '+183', '+184', '+185', '+186', '+187', '+188', '+189', '+190', '+191', '+192', '+193', '+194', '+195', '+196', '+197', '+198', '+199', '+200', '+201', '+202', '+203', '+204', '+205', '+206', '+207', '+208', '+209', '+210', '+211', '+212', '+213', '+214', '+215', '+216', '+217', '+218', '+219', '+220', '+221', '+222', '+223', '+224', '+225', '+226', '+227', '+228', '+229', '+230', '+231', '+232', '+233', '+234', '+235', '+236', '+237', '+238', '+239', '+240', '+241', '+242', '+243', '+244', '+245', '+246', '+247', '+248', '+249', '+250', '+251', '+252', '+253', '+254', '+255', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100', '101', '102', '103', '104', '105', '106', '107', '108', '109', '110', '111', '112', '113', '114', '115', '116', '117', '118', '119', '120', '121', '122', '123', '124', '125', '126', '127', '-256', '-255', '-254', '-253', '-252', '-251', '-250', '-249', '-248', '-247', '-246', '-245', '-244', '-243', '-242', '-241', '-240', '-239', '-238', '-237', '-236', '-235', '-234', '-233', '-232', '-231', '-230', '-229', '-228', '-227', '-226', '-225', '-224', '-223', '-222', '-221', '-220', '-219', '-218', '-217', '-216', '-215', '-214', '-213', '-212', '-211', '-210', '-209', '-208', '-207', '-206', '-205', '-204', '-203', '-202', '-201', '-200', '-199', '-198', '-197', '-196', '-195', '-194', '-193', '-192', '-191', '-190', '-189', '-188', '-187', '-186', '-185', '-184', '-183', '-182', '-181', '-180', '-179', '-178', '-177', '-176', '-175', '-174', '-173', '-172', '-171', '-170', '-169', '-168', '-167', '-166', '-165', '-164', '-163', '-162', '-161', '-160', '-159', '-158', '-157', '-156', '-155', '-154', '-153', '-152', '-151', '-150', '-149', '-148', '-147', '-146', '-145', '-144', '-143', '-142', '-141', '-140', '-139', '-138', '-137', '-136', '-135', '-134', '-133', '-132', '-131', '-130', '-129', '-128', '-127', '-126', '-125', '-124', '-123', '-122', '-121', '-120', '-119', '-118', '-117', '-116', '-115', '-114', '-113', '-112', '-111', '-110', '-109', '-108', '-107', '-106', '-105', '-104', '-103', '-102', '-101', '-100', '-99', '-98', '-97', '-96', '-95', '-94', '-93', '-92', '-91', '-90', '-89', '-88', '-87', '-86', '-85', '-84', '-83', '-82', '-81', '-80', '-79', '-78', '-77', '-76', '-75', '-74', '-73', '-72', '-71', '-70', '-69', '-68', '-67', '-66', '-65', '-64', '-63', '-62', '-61', '-60', '-59', '-58', '-57', '-56', '-55', '-54', '-53', '-52', '-51', '-50', '-49', '-48', '-47', '-46', '-45', '-44', '-43', '-42', '-41', '-40', '-39', '-38', '-37', '-36', '-35', '-34', '-33', '-32', '-31', '-30', '-29', '-28', '-27', '-26', '-25', '-24', '-23', '-22', '-21', '-20', '-19', '-18', '-17', '-16', '-15', '-14', '-13', '-12', '-11', '-10', '-9', '-8', '-7', '-6', '-5', '-4', '-3', '-2', '-1', 'STEP', 'ADJ_GROUP', '&', '<XX>', '<RESERVE_708>', '<RESERVE_709>', '<RESERVE_710>', '<RESERVE_711>', '<RESERVE_712>', '<RESERVE_713>', '<RESERVE_714>', '<RESERVE_715>', '<RESERVE_716>', '<RESERVE_717>', '<RESERVE_718>', '<RESERVE_719>', '<RESERVE_720>', '<RESERVE_721>', '<RESERVE_722>', '<RESERVE_723>', '<RESERVE_724>', '<RESERVE_725>', '<RESERVE_726>', '<RESERVE_727>', '<RESERVE_728>', '<RESERVE_729>', '<RESERVE_730>', '<RESERVE_731>', '<RESERVE_732>', '<RESERVE_733>', '<RESERVE_734>', '<RESERVE_735>', '<RESERVE_736>', '<RESERVE_737>', '<RESERVE_738>', '<RESERVE_739>', '<RESERVE_740>', '<RESERVE_741>', '<RESERVE_742>', '<RESERVE_743>', '<RESERVE_744>', '<RESERVE_745>', '<RESERVE_746>', '<RESERVE_747>', '<RESERVE_748>', '<RESERVE_749>', '<RESERVE_750>', '<RESERVE_751>', '<RESERVE_752>', '<RESERVE_753>', '<RESERVE_754>', '<RESERVE_755>', '<RESERVE_756>', '<RESERVE_757>', '<RESERVE_758>', '<RESERVE_759>', '<RESERVE_760>', '<RESERVE_761>', '<RESERVE_762>', '<RESERVE_763>', '<RESERVE_764>', '<RESERVE_765>', '<RESERVE_766>', '<RESERVE_767>', '<RESERVE_768>', '<RESERVE_769>', '<RESERVE_770>', '<RESERVE_771>', '<RESERVE_772>', '<RESERVE_773>', '<RESERVE_774>', '<RESERVE_775>', '<RESERVE_776>', '<RESERVE_777>', '<RESERVE_778>', '<RESERVE_779>', '<RESERVE_780>', '<RESERVE_781>', '<RESERVE_782>', '<RESERVE_783>', '<RESERVE_784>', '<RESERVE_785>', '<RESERVE_786>', '<RESERVE_787>', '<RESERVE_788>', '<RESERVE_789>', '<RESERVE_790>', '<RESERVE_791>', '<RESERVE_792>', '<RESERVE_793>', '<RESERVE_794>', '<RESERVE_795>', '<RESERVE_796>', '<RESERVE_797>', '<RESERVE_798>', '<RESERVE_799>', '<RESERVE_800>', '<RESERVE_801>', '<RESERVE_802>', '<RESERVE_803>', '<RESERVE_804>', '<RESERVE_805>', '<RESERVE_806>', '<RESERVE_807>', '<RESERVE_808>', '<RESERVE_809>', '<RESERVE_810>', '<RESERVE_811>', '<RESERVE_812>', '<RESERVE_813>', '<RESERVE_814>', '<RESERVE_815>', '<RESERVE_816>', '<RESERVE_817>', '<RESERVE_818>', '<RESERVE_819>', '<RESERVE_820>', '<RESERVE_821>', '<RESERVE_822>', '<RESERVE_823>', '<RESERVE_824>', '<RESERVE_825>', '<RESERVE_826>', '<RESERVE_827>', '<RESERVE_828>', '<RESERVE_829>', '<RESERVE_830>', '<RESERVE_831>', '<RESERVE_832>', '<RESERVE_833>', '<RESERVE_834>', '<RESERVE_835>', '<RESERVE_836>', '<RESERVE_837>', '<RESERVE_838>', '<RESERVE_839>', '<RESERVE_840>', '<RESERVE_841>', '<RESERVE_842>', '<RESERVE_843>', '<RESERVE_844>', '<RESERVE_845>', '<RESERVE_846>', '<RESERVE_847>', '<RESERVE_848>', '<RESERVE_849>', '<RESERVE_850>', '<RESERVE_851>', '<RESERVE_852>', '<RESERVE_853>', '<RESERVE_854>', '<RESERVE_855>', '<RESERVE_856>', '<RESERVE_857>', '<RESERVE_858>', '<RESERVE_859>', '<RESERVE_860>', '<RESERVE_861>', '<RESERVE_862>', '<RESERVE_863>', '<RESERVE_864>', '<RESERVE_865>', '<RESERVE_866>', '<RESERVE_867>', '<RESERVE_868>', '<RESERVE_869>', '<RESERVE_870>', '<RESERVE_871>', '<RESERVE_872>', '<RESERVE_873>', '<RESERVE_874>', '<RESERVE_875>', '<RESERVE_876>', '<RESERVE_877>', '<RESERVE_878>', '<RESERVE_879>', '<RESERVE_880>', '<RESERVE_881>', '<RESERVE_882>', '<RESERVE_883>', '<RESERVE_884>', '<RESERVE_885>', '<RESERVE_886>', '<RESERVE_887>', '<RESERVE_888>', '<RESERVE_889>', '<RESERVE_890>', '<RESERVE_891>', '<RESERVE_892>', '<RESERVE_893>', '<RESERVE_894>', '<RESERVE_895>', '<RESERVE_896>', '<RESERVE_897>', '<RESERVE_898>', '<RESERVE_899>', '<RESERVE_900>', '<RESERVE_901>', '<RESERVE_902>', '<RESERVE_903>', '<RESERVE_904>', '<RESERVE_905>', '<RESERVE_906>', '<RESERVE_907>', '<RESERVE_908>', '<RESERVE_909>', '<RESERVE_910>', '<RESERVE_911>', '<RESERVE_912>', '<RESERVE_913>', '<RESERVE_914>', '<RESERVE_915>', '<RESERVE_916>', '<RESERVE_917>', '<RESERVE_918>', '<RESERVE_919>', '<RESERVE_920>', '<RESERVE_921>', '<RESERVE_922>', '<RESERVE_923>', '<RESERVE_924>', '<RESERVE_925>', '<RESERVE_926>', '<RESERVE_927>', '<RESERVE_928>', '<RESERVE_929>', '<RESERVE_930>', '<RESERVE_931>', '<RESERVE_932>', '<RESERVE_933>', '<RESERVE_934>', '<RESERVE_935>', '<RESERVE_936>', '<RESERVE_937>', '<RESERVE_938>', '<RESERVE_939>', '<RESERVE_940>', '<RESERVE_941>', '<RESERVE_942>', '<RESERVE_943>', '<RESERVE_944>', '<RESERVE_945>', '<RESERVE_946>', '<RESERVE_947>', '<RESERVE_948>', '<RESERVE_949>', '<RESERVE_950>', '<RESERVE_951>', '<RESERVE_952>', '<RESERVE_953>', '<RESERVE_954>', '<RESERVE_955>', '<RESERVE_956>', '<RESERVE_957>', '<RESERVE_958>', '<RESERVE_959>', '<RESERVE_960>', '<RESERVE_961>', '<RESERVE_962>', '<RESERVE_963>', '<RESERVE_964>', '<RESERVE_965>', '<RESERVE_966>', '<RESERVE_967>', '<RESERVE_968>', '<RESERVE_969>', '<RESERVE_970>', '<RESERVE_971>', '<RESERVE_972>', '<RESERVE_973>', '<RESERVE_974>', '<RESERVE_975>', '<RESERVE_976>', '<RESERVE_977>', '<RESERVE_978>', '<RESERVE_979>', '<RESERVE_980>', '<RESERVE_981>', '<RESERVE_982>', '<RESERVE_983>', '<RESERVE_984>', '<RESERVE_985>', '<RESERVE_986>', '<RESERVE_987>', '<RESERVE_988>', '<RESERVE_989>', '<RESERVE_990>', '<RESERVE_991>', '<RESERVE_992>', '<RESERVE_993>', '<RESERVE_994>', '<RESERVE_995>', '<RESERVE_996>', '<RESERVE_997>', '<RESERVE_998>', '<RESERVE_999>', '<RESERVE_1000>', '<RESERVE_1001>', '<RESERVE_1002>', '<RESERVE_1003>', '<RESERVE_1004>', '<RESERVE_1005>', '<RESERVE_1006>', '<RESERVE_1007>', '<RESERVE_1008>', '<RESERVE_1009>', '<RESERVE_1010>', '<RESERVE_1011>', '<RESERVE_1012>', '<RESERVE_1013>', '<RESERVE_1014>', '<RESERVE_1015>', '<RESERVE_1016>', '<RESERVE_1017>', '<RESERVE_1018>', '<RESERVE_1019>', '<RESERVE_1020>', '<RESERVE_1021>', '<RESERVE_1022>', '<RESERVE_1023>', '<RESERVE_1024>', '<RESERVE_1025>', '<RESERVE_1026>', '<RESERVE_1027>', '<RESERVE_1028>', '<RESERVE_1029>', '<RESERVE_1030>', '<RESERVE_1031>', '<RESERVE_1032>', '<RESERVE_1033>', '<RESERVE_1034>', '<RESERVE_1035>', '<RESERVE_1036>', '<RESERVE_1037>', '<RESERVE_1038>', '<RESERVE_1039>', '<RESERVE_1040>', '<RESERVE_1041>', '<RESERVE_1042>', '<RESERVE_1043>', '<RESERVE_1044>', '<RESERVE_1045>', '<RESERVE_1046>', '<RESERVE_1047>', '<RESERVE_1048>', '<RESERVE_1049>', '<RESERVE_1050>', '<RESERVE_1051>', '<RESERVE_1052>', '<RESERVE_1053>', '<RESERVE_1054>', '<RESERVE_1055>', '<RESERVE_1056>', '<RESERVE_1057>', '<RESERVE_1058>', '<RESERVE_1059>', '<RESERVE_1060>', '<RESERVE_1061>', '<RESERVE_1062>', '<RESERVE_1063>', '<RESERVE_1064>', '<RESERVE_1065>', '<RESERVE_1066>', '<RESERVE_1067>', '<RESERVE_1068>', '<RESERVE_1069>', '<RESERVE_1070>', '<RESERVE_1071>', '<RESERVE_1072>', '<RESERVE_1073>', '<RESERVE_1074>', '<RESERVE_1075>', '<RESERVE_1076>', '<RESERVE_1077>', '<RESERVE_1078>', '<RESERVE_1079>', '<RESERVE_1080>', '<RESERVE_1081>', '<RESERVE_1082>', '<RESERVE_1083>', '<RESERVE_1084>', '<RESERVE_1085>', '<RESERVE_1086>', '<RESERVE_1087>', '<RESERVE_1088>', '<RESERVE_1089>', '<RESERVE_1090>', '<RESERVE_1091>', '<RESERVE_1092>', '<RESERVE_1093>', '<RESERVE_1094>', '<RESERVE_1095>', '<RESERVE_1096>', '<RESERVE_1097>', '<RESERVE_1098>', '<RESERVE_1099>', '<RESERVE_1100>', '<RESERVE_1101>', '<RESERVE_1102>', '<RESERVE_1103>', '<RESERVE_1104>', '<RESERVE_1105>', '<RESERVE_1106>', '<RESERVE_1107>', '<RESERVE_1108>', '<RESERVE_1109>', '<RESERVE_1110>', '<RESERVE_1111>', '<RESERVE_1112>', '<RESERVE_1113>', '<RESERVE_1114>', '<RESERVE_1115>', '<RESERVE_1116>', '<RESERVE_1117>', '<RESERVE_1118>', '<RESERVE_1119>', '<RESERVE_1120>', '<RESERVE_1121>', '<RESERVE_1122>', '<RESERVE_1123>', '<RESERVE_1124>', '<RESERVE_1125>', '<RESERVE_1126>', '<RESERVE_1127>', '<RESERVE_1128>', '<RESERVE_1129>', '<RESERVE_1130>', '<RESERVE_1131>', '<RESERVE_1132>', '<RESERVE_1133>', '<RESERVE_1134>', '<RESERVE_1135>', '<RESERVE_1136>', '<RESERVE_1137>', '<RESERVE_1138>', '<RESERVE_1139>', '<RESERVE_1140>', '<RESERVE_1141>', '<RESERVE_1142>', '<RESERVE_1143>', '<RESERVE_1144>', '<RESERVE_1145>', '<RESERVE_1146>', '<RESERVE_1147>', '<RESERVE_1148>', '<RESERVE_1149>', '<RESERVE_1150>', '<RESERVE_1151>', '<RESERVE_1152>', '<RESERVE_1153>', '<RESERVE_1154>', '<RESERVE_1155>', '<RESERVE_1156>', '<RESERVE_1157>', '<RESERVE_1158>', '<RESERVE_1159>', '<RESERVE_1160>', '<RESERVE_1161>', '<RESERVE_1162>', '<RESERVE_1163>', '<RESERVE_1164>', '<RESERVE_1165>', '<RESERVE_1166>', '<RESERVE_1167>', '<RESERVE_1168>', '<RESERVE_1169>', '<RESERVE_1170>', '<RESERVE_1171>', '<RESERVE_1172>', '<RESERVE_1173>', '<RESERVE_1174>', '<RESERVE_1175>', '<RESERVE_1176>', '<RESERVE_1177>', '<RESERVE_1178>', '<RESERVE_1179>', '<RESERVE_1180>', '<RESERVE_1181>', '<RESERVE_1182>', '<RESERVE_1183>', '<RESERVE_1184>', '<RESERVE_1185>', '<RESERVE_1186>', '<RESERVE_1187>', '<RESERVE_1188>', '<RESERVE_1189>', '<RESERVE_1190>', '<RESERVE_1191>', '<RESERVE_1192>', '<RESERVE_1193>', '<RESERVE_1194>', '<RESERVE_1195>', '<RESERVE_1196>', '<RESERVE_1197>', '<RESERVE_1198>', '<RESERVE_1199>', '<RESERVE_1200>', '<RESERVE_1201>', '<RESERVE_1202>', '<RESERVE_1203>', '<RESERVE_1204>', '<RESERVE_1205>', '<RESERVE_1206>', '<RESERVE_1207>', '<RESERVE_1208>', '<RESERVE_1209>', '<RESERVE_1210>', '<RESERVE_1211>', '<RESERVE_1212>', '<RESERVE_1213>', '<RESERVE_1214>', '<RESERVE_1215>', '<RESERVE_1216>', '<RESERVE_1217>', '<RESERVE_1218>', '<RESERVE_1219>', '<RESERVE_1220>', '<RESERVE_1221>', '<RESERVE_1222>', '<RESERVE_1223>', '<RESERVE_1224>', '<RESERVE_1225>', '<RESERVE_1226>', '<RESERVE_1227>', '<RESERVE_1228>', '<RESERVE_1229>', '<RESERVE_1230>', '<RESERVE_1231>', '<RESERVE_1232>', '<RESERVE_1233>', '<RESERVE_1234>', '<RESERVE_1235>', '<RESERVE_1236>', '<RESERVE_1237>', '<RESERVE_1238>', '<RESERVE_1239>', '<RESERVE_1240>', '<RESERVE_1241>', '<RESERVE_1242>', '<RESERVE_1243>', '<RESERVE_1244>', '<RESERVE_1245>', '<RESERVE_1246>', '<RESERVE_1247>', '<RESERVE_1248>', '<RESERVE_1249>', '<RESERVE_1250>', '<RESERVE_1251>', '<RESERVE_1252>', '<RESERVE_1253>', '<RESERVE_1254>', '<RESERVE_1255>', '<RESERVE_1256>', '<RESERVE_1257>', '<RESERVE_1258>', '<RESERVE_1259>', '<RESERVE_1260>', '<RESERVE_1261>', '<RESERVE_1262>', '<RESERVE_1263>', '<RESERVE_1264>', '<RESERVE_1265>', '<RESERVE_1266>', '<RESERVE_1267>', '<RESERVE_1268>', '<RESERVE_1269>', '<RESERVE_1270>', '<RESERVE_1271>', '<RESERVE_1272>', '<RESERVE_1273>', '<RESERVE_1274>', '<RESERVE_1275>', '<RESERVE_1276>', '<RESERVE_1277>', '<RESERVE_1278>', '<RESERVE_1279>', '<RESERVE_1280>', '<RESERVE_1281>', '<RESERVE_1282>', '<RESERVE_1283>', '<RESERVE_1284>', '<RESERVE_1285>', '<RESERVE_1286>', '<RESERVE_1287>', '<RESERVE_1288>', '<RESERVE_1289>', '<RESERVE_1290>', '<RESERVE_1291>', '<RESERVE_1292>', '<RESERVE_1293>', '<RESERVE_1294>', '<RESERVE_1295>', '<RESERVE_1296>', '<RESERVE_1297>', '<RESERVE_1298>', '<RESERVE_1299>', '<RESERVE_1300>', '<RESERVE_1301>', '<RESERVE_1302>', '<RESERVE_1303>', '<RESERVE_1304>', '<RESERVE_1305>', '<RESERVE_1306>', '<RESERVE_1307>', '<RESERVE_1308>', '<RESERVE_1309>', '<RESERVE_1310>', '<RESERVE_1311>', '<RESERVE_1312>', '<RESERVE_1313>', '<RESERVE_1314>', '<RESERVE_1315>', '<RESERVE_1316>', '<RESERVE_1317>', '<RESERVE_1318>', '<RESERVE_1319>', '<RESERVE_1320>', '<RESERVE_1321>', '<RESERVE_1322>', '<RESERVE_1323>', '<RESERVE_1324>', '<RESERVE_1325>', '<RESERVE_1326>', '<RESERVE_1327>', '<RESERVE_1328>', '<RESERVE_1329>', '<RESERVE_1330>', '<RESERVE_1331>', '<RESERVE_1332>', '<RESERVE_1333>', '<RESERVE_1334>', '<RESERVE_1335>', '<RESERVE_1336>', '<RESERVE_1337>', '<RESERVE_1338>', '<RESERVE_1339>', '<RESERVE_1340>', '<RESERVE_1341>', '<RESERVE_1342>', '<RESERVE_1343>', '<RESERVE_1344>', '<RESERVE_1345>', '<RESERVE_1346>', '<RESERVE_1347>', '<RESERVE_1348>', '<RESERVE_1349>', '<RESERVE_1350>', '<RESERVE_1351>', '<RESERVE_1352>', '<RESERVE_1353>', '<RESERVE_1354>', '<RESERVE_1355>', '<RESERVE_1356>', '<RESERVE_1357>', '<RESERVE_1358>', '<RESERVE_1359>', '<RESERVE_1360>', '<RESERVE_1361>', '<RESERVE_1362>', '<RESERVE_1363>', '<RESERVE_1364>', '<RESERVE_1365>', '<RESERVE_1366>', '<RESERVE_1367>', '<RESERVE_1368>', '<RESERVE_1369>', '<RESERVE_1370>', '<RESERVE_1371>', '<RESERVE_1372>', '<RESERVE_1373>', '<RESERVE_1374>', '<RESERVE_1375>', '<RESERVE_1376>', '<RESERVE_1377>', '<RESERVE_1378>', '<RESERVE_1379>', '<RESERVE_1380>', '<RESERVE_1381>', '<RESERVE_1382>', '<RESERVE_1383>', '<RESERVE_1384>', '<RESERVE_1385>', '<RESERVE_1386>', '<RESERVE_1387>', '<RESERVE_1388>', '<RESERVE_1389>', '<RESERVE_1390>', '<RESERVE_1391>', '<RESERVE_1392>', '<RESERVE_1393>', '<RESERVE_1394>', '<RESERVE_1395>', '<RESERVE_1396>', '<RESERVE_1397>', '<RESERVE_1398>', '<RESERVE_1399>', '<RESERVE_1400>', '<RESERVE_1401>', '<RESERVE_1402>', '<RESERVE_1403>', '<RESERVE_1404>', '<RESERVE_1405>', '<RESERVE_1406>', '<RESERVE_1407>', '<RESERVE_1408>', '<RESERVE_1409>', '<RESERVE_1410>', '<RESERVE_1411>', '<RESERVE_1412>', '<RESERVE_1413>', '<RESERVE_1414>', '<RESERVE_1415>', '<RESERVE_1416>', '<RESERVE_1417>', '<RESERVE_1418>', '<RESERVE_1419>', '<RESERVE_1420>', '<RESERVE_1421>', '<RESERVE_1422>', '<RESERVE_1423>', '<RESERVE_1424>', '<RESERVE_1425>', '<RESERVE_1426>', '<RESERVE_1427>', '<RESERVE_1428>', '<RESERVE_1429>', '<RESERVE_1430>', '<RESERVE_1431>', '<RESERVE_1432>', '<RESERVE_1433>', '<RESERVE_1434>', '<RESERVE_1435>', '<RESERVE_1436>', '<RESERVE_1437>', '<RESERVE_1438>', '<RESERVE_1439>', '<RESERVE_1440>', '<RESERVE_1441>', '<RESERVE_1442>', '<RESERVE_1443>', '<RESERVE_1444>', '<RESERVE_1445>', '<RESERVE_1446>', '<RESERVE_1447>', '<RESERVE_1448>', '<RESERVE_1449>', '<RESERVE_1450>', '<RESERVE_1451>', '<RESERVE_1452>', '<RESERVE_1453>', '<RESERVE_1454>', '<RESERVE_1455>', '<RESERVE_1456>', '<RESERVE_1457>', '<RESERVE_1458>', '<RESERVE_1459>', '<RESERVE_1460>', '<RESERVE_1461>', '<RESERVE_1462>', '<RESERVE_1463>', '<RESERVE_1464>', '<RESERVE_1465>', '<RESERVE_1466>', '<RESERVE_1467>', '<RESERVE_1468>', '<RESERVE_1469>', '<RESERVE_1470>', '<RESERVE_1471>', '<RESERVE_1472>', '<RESERVE_1473>', '<RESERVE_1474>', '<RESERVE_1475>', '<RESERVE_1476>', '<RESERVE_1477>', '<RESERVE_1478>', '<RESERVE_1479>', '<RESERVE_1480>', '<RESERVE_1481>', '<RESERVE_1482>', '<RESERVE_1483>', '<RESERVE_1484>', '<RESERVE_1485>', '<RESERVE_1486>', '<RESERVE_1487>', '<RESERVE_1488>', '<RESERVE_1489>', '<RESERVE_1490>', '<RESERVE_1491>', '<RESERVE_1492>', '<RESERVE_1493>', '<RESERVE_1494>', '<RESERVE_1495>', '<RESERVE_1496>', '<RESERVE_1497>', '<RESERVE_1498>', '<RESERVE_1499>', '<RESERVE_1500>', '<RESERVE_1501>', '<RESERVE_1502>', '<RESERVE_1503>', '<RESERVE_1504>', '<RESERVE_1505>', '<RESERVE_1506>', '<RESERVE_1507>', '<RESERVE_1508>', '<RESERVE_1509>', '<RESERVE_1510>', '<RESERVE_1511>', '<RESERVE_1512>', '<RESERVE_1513>', '<RESERVE_1514>', '<RESERVE_1515>', '<RESERVE_1516>', '<RESERVE_1517>', '<RESERVE_1518>', '<RESERVE_1519>', '<RESERVE_1520>', '<RESERVE_1521>', '<RESERVE_1522>', '<RESERVE_1523>', '<RESERVE_1524>', '<RESERVE_1525>', '<RESERVE_1526>', '<RESERVE_1527>', '<RESERVE_1528>', '<RESERVE_1529>', '<RESERVE_1530>', '<RESERVE_1531>', '<RESERVE_1532>', '<RESERVE_1533>', '<RESERVE_1534>', '<RESERVE_1535>', '<RESERVE_1536>', '<RESERVE_1537>', '<RESERVE_1538>', '<RESERVE_1539>', '<RESERVE_1540>', '<RESERVE_1541>', '<RESERVE_1542>', '<RESERVE_1543>', '<RESERVE_1544>', '<RESERVE_1545>', '<RESERVE_1546>', '<RESERVE_1547>', '<RESERVE_1548>', '<RESERVE_1549>', '<RESERVE_1550>', '<RESERVE_1551>', '<RESERVE_1552>', '<RESERVE_1553>', '<RESERVE_1554>', '<RESERVE_1555>', '<RESERVE_1556>', '<RESERVE_1557>', '<RESERVE_1558>', '<RESERVE_1559>', '<RESERVE_1560>', '<RESERVE_1561>', '<RESERVE_1562>', '<RESERVE_1563>', '<RESERVE_1564>', '<RESERVE_1565>', '<RESERVE_1566>', '<RESERVE_1567>', '<RESERVE_1568>', '<RESERVE_1569>', '<RESERVE_1570>', '<RESERVE_1571>', '<RESERVE_1572>', '<RESERVE_1573>', '<RESERVE_1574>', '<RESERVE_1575>', '<RESERVE_1576>', '<RESERVE_1577>', '<RESERVE_1578>', '<RESERVE_1579>', '<RESERVE_1580>', '<RESERVE_1581>', '<RESERVE_1582>', '<RESERVE_1583>', '<RESERVE_1584>', '<RESERVE_1585>', '<RESERVE_1586>', '<RESERVE_1587>', '<RESERVE_1588>', '<RESERVE_1589>', '<RESERVE_1590>', '<RESERVE_1591>', '<RESERVE_1592>', '<RESERVE_1593>', '<RESERVE_1594>', '<RESERVE_1595>', '(0,0)', '(0,1)', '(1,0)', '(1,1)', '(0,2)', '(2,0)', '(1,2)', '(2,1)', '(2,2)', '(0,3)', '(3,0)', '(3,1)', '(2,3)', '(3,2)', '(1,3)', '(3,3)', '(0,4)', '(2,4)', '(4,0)', '(1,4)', '(4,1)', '(4,2)', '(3,4)', '(4,3)', '(4,4)', '(0,5)', '(5,0)', '(5,1)', '(2,5)', '(5,2)', '(5,3)', '(4,5)', '(5,4)', '(1,5)', '(3,5)', '(5,5)', '(0,6)', '(2,6)', '(4,6)', '(6,0)', '(1,6)', '(6,1)', '(6,2)', '(3,6)', '(6,3)', '(6,4)', '(5,6)', '(6,5)', '(6,6)', '(0,7)', '(7,0)', '(7,1)', '(2,7)', '(7,2)', '(7,3)', '(4,7)', '(7,4)', '(7,5)', '(6,7)', '(7,6)', '(1,7)', '(3,7)', '(5,7)', '(7,7)', '(0,8)', '(2,8)', '(4,8)', '(6,8)', '(8,0)', '(1,8)', '(8,1)', '(8,2)', '(3,8)', '(8,3)', '(8,4)', '(5,8)', '(8,5)', '(8,6)', '(7,8)', '(8,7)', '(8,8)', '(0,9)', '(9,0)', '(9,1)', '(2,9)', '(9,2)', '(9,3)', '(4,9)', '(9,4)', '(9,5)', '(6,9)', '(9,6)', '(9,7)', '(8,9)', '(9,8)', '(1,9)', '(3,9)', '(5,9)', '(7,9)', '(9,9)', '(0,10)', '(2,10)', '(4,10)', '(6,10)', '(8,10)', '(10,0)', '(1,10)', '(10,1)', '(10,2)', '(3,10)', '(10,3)', '(10,4)', '(5,10)', '(10,5)', '(10,6)', '(7,10)', '(10,7)', '(10,8)', '(9,10)', '(10,9)', '(10,10)', '(0,11)', '(11,0)', '(11,1)', '(2,11)', '(11,2)', '(11,3)', '(4,11)', '(11,4)', '(11,5)', '(6,11)', '(11,6)', '(11,7)', '(8,11)', '(11,8)', '(11,9)', '(10,11)', '(11,10)', '(1,11)', '(3,11)', '(5,11)', '(7,11)', '(9,11)', '(11,11)', '(0,12)', '(2,12)', '(4,12)', '(6,12)', '(8,12)', '(10,12)', '(12,0)', '(1,12)', '(12,1)', '(12,2)', '(3,12)', '(12,3)', '(12,4)', '(5,12)', '(12,5)', '(12,6)', '(7,12)', '(12,7)', '(12,8)', '(9,12)', '(12,9)', '(12,10)', '(11,12)', '(12,11)', '(12,12)', '(0,13)', '(13,0)', '(13,1)', '(2,13)', '(13,2)', '(13,3)', '(4,13)', '(13,4)', '(13,5)', '(6,13)', '(13,6)', '(13,7)', '(8,13)', '(13,8)', '(13,9)', '(10,13)', '(13,10)', '(13,11)', '(12,13)', '(13,12)', '(1,13)', '(3,13)', '(5,13)', '(7,13)', '(9,13)', '(11,13)', '(13,13)', '(0,14)', '(2,14)', '(4,14)', '(6,14)', '(8,14)', '(10,14)', '(12,14)', '(14,0)', '(1,14)', '(14,1)', '(14,2)', '(3,14)', '(14,3)', '(14,4)', '(5,14)', '(14,5)', '(14,6)', '(7,14)', '(14,7)', '(14,8)', '(9,14)', '(14,9)', '(14,10)', '(11,14)', '(14,11)', '(14,12)', '(13,14)', '(14,13)', '(14,14)', '(0,15)', '(15,0)', '(15,1)', '(2,15)', '(15,2)', '(15,3)', '(4,15)', '(15,4)', '(15,5)', '(6,15)', '(15,6)', '(15,7)', '(8,15)', '(15,8)', '(15,9)', '(10,15)', '(15,10)', '(15,11)', '(12,15)', '(15,12)', '(15,13)', '(14,15)', '(15,14)', '(1,15)', '(3,15)', '(5,15)', '(7,15)', '(9,15)', '(11,15)', '(13,15)', '(15,15)', '(0,16)', '(2,16)', '(4,16)', '(6,16)', '(8,16)', '(10,16)', '(12,16)', '(14,16)', '(16,0)', '(1,16)', '(16,1)', '(16,2)', '(3,16)', '(16,3)', '(16,4)', '(5,16)', '(16,5)', '(16,6)', '(7,16)', '(16,7)', '(16,8)', '(9,16)', '(16,9)', '(16,10)', '(11,16)', '(16,11)', '(16,12)', '(13,16)', '(16,13)', '(16,14)', '(15,16)', '(16,15)', '(16,16)', '(0,17)', '(17,0)', '(17,1)', '(2,17)', '(17,2)', '(17,3)', '(4,17)', '(17,4)', '(17,5)', '(6,17)', '(17,6)', '(17,7)', '(8,17)', '(17,8)', '(17,9)', '(10,17)', '(17,10)', '(17,11)', '(12,17)', '(17,12)', '(17,13)', '(14,17)', '(17,14)', '(17,15)', '(16,17)', '(17,16)', '(1,17)', '(3,17)', '(5,17)', '(7,17)', '(9,17)', '(11,17)', '(13,17)', '(15,17)', '(17,17)', '(0,18)', '(2,18)', '(4,18)', '(6,18)', '(8,18)', '(10,18)', '(12,18)', '(14,18)', '(16,18)', '(18,0)', '(1,18)', '(18,1)', '(18,2)', '(3,18)', '(18,3)', '(18,4)', '(5,18)', '(18,5)', '(18,6)', '(7,18)', '(18,7)', '(18,8)', '(9,18)', '(18,9)', '(18,10)', '(11,18)', '(18,11)', '(18,12)', '(13,18)', '(18,13)', '(18,14)', '(15,18)', '(18,15)', '(18,16)', '(17,18)', '(18,17)', '(18,18)', '(0,19)', '(19,0)', '(19,1)', '(2,19)', '(19,2)', '(19,3)', '(4,19)', '(19,4)', '(19,5)', '(6,19)', '(19,6)', '(19,7)', '(8,19)', '(19,8)', '(19,9)', '(10,19)', '(19,10)', '(19,11)', '(12,19)', '(19,12)', '(19,13)', '(14,19)', '(19,14)', '(19,15)', '(16,19)', '(19,16)', '(19,17)', '(18,19)', '(19,18)', '(1,19)', '(3,19)', '(5,19)', '(7,19)', '(9,19)', '(11,19)', '(13,19)', '(15,19)', '(17,19)', '(19,19)', '(0,20)', '(2,20)', '(4,20)', '(6,20)', '(8,20)', '(10,20)', '(12,20)', '(14,20)', '(16,20)', '(18,20)', '(20,0)', '(1,20)', '(20,1)', '(20,2)', '(3,20)', '(20,3)', '(20,4)', '(5,20)', '(20,5)', '(20,6)', '(7,20)', '(20,7)', '(20,8)', '(9,20)', '(20,9)', '(20,10)', '(11,20)', '(20,11)', '(20,12)', '(13,20)', '(20,13)', '(20,14)', '(15,20)', '(20,15)', '(20,16)', '(17,20)', '(20,17)', '(20,18)', '(19,20)', '(20,19)', '(20,20)', '(0,21)', '(21,0)', '(21,1)', '(2,21)', '(21,2)', '(21,3)', '(4,21)', '(21,4)', '(21,5)', '(6,21)', '(21,6)', '(21,7)', '(8,21)', '(21,8)', '(21,9)', '(10,21)', '(21,10)', '(21,11)', '(12,21)', '(21,12)', '(21,13)', '(14,21)', '(21,14)', '(21,15)', '(16,21)', '(21,16)', '(21,17)', '(18,21)', '(21,18)', '(21,19)', '(20,21)', '(21,20)', '(1,21)', '(3,21)', '(5,21)', '(7,21)', '(9,21)', '(11,21)', '(13,21)', '(15,21)', '(17,21)', '(19,21)', '(21,21)', '(0,22)', '(2,22)', '(4,22)', '(6,22)', '(8,22)', '(10,22)', '(12,22)', '(14,22)', '(16,22)', '(18,22)', '(20,22)', '(22,0)', '(1,22)', '(22,1)', '(22,2)', '(3,22)', '(22,3)', '(22,4)', '(5,22)', '(22,5)', '(22,6)', '(7,22)', '(22,7)', '(22,8)', '(9,22)', '(22,9)', '(22,10)', '(11,22)', '(22,11)', '(22,12)', '(13,22)', '(22,13)', '(22,14)', '(15,22)', '(22,15)', '(22,16)', '(17,22)', '(22,17)', '(22,18)', '(19,22)', '(22,19)', '(22,20)', '(21,22)', '(22,21)', '(22,22)', '(0,23)', '(23,0)', '(23,1)', '(2,23)', '(23,2)', '(23,3)', '(4,23)', '(23,4)', '(23,5)', '(6,23)', '(23,6)', '(23,7)', '(8,23)', '(23,8)', '(23,9)', '(10,23)', '(23,10)', '(23,11)', '(12,23)', '(23,12)', '(23,13)', '(14,23)', '(23,14)', '(23,15)', '(16,23)', '(23,16)', '(23,17)', '(18,23)', '(23,18)', '(23,19)', '(20,23)', '(23,20)', '(23,21)', '(22,23)', '(23,22)', '(1,23)', '(3,23)', '(5,23)', '(7,23)', '(9,23)', '(11,23)', '(13,23)', '(15,23)', '(17,23)', '(19,23)', '(21,23)', '(23,23)', '(0,24)', '(2,24)', '(4,24)', '(6,24)', '(8,24)', '(10,24)', '(12,24)', '(14,24)', '(16,24)', '(18,24)', '(20,24)', '(22,24)', '(24,0)', '(1,24)', '(24,1)', '(24,2)', '(3,24)', '(24,3)', '(24,4)', '(5,24)', '(24,5)', '(24,6)', '(7,24)', '(24,7)', '(24,8)', '(9,24)', '(24,9)', '(24,10)', '(11,24)', '(24,11)', '(24,12)', '(13,24)', '(24,13)', '(24,14)', '(15,24)', '(24,15)', '(24,16)', '(17,24)', '(24,17)', '(24,18)', '(19,24)', '(24,19)', '(24,20)', '(21,24)', '(24,21)', '(24,22)', '(23,24)', '(24,23)', '(24,24)', '(0,25)', '(25,0)', '(25,1)', '(2,25)', '(25,2)', '(25,3)', '(4,25)', '(25,4)', '(25,5)', '(6,25)', '(25,6)', '(25,7)', '(8,25)', '(25,8)', '(25,9)', '(10,25)', '(25,10)', '(25,11)', '(12,25)', '(25,12)', '(25,13)', '(14,25)', '(25,14)', '(25,15)', '(16,25)', '(25,16)', '(25,17)', '(18,25)', '(25,18)', '(25,19)', '(20,25)', '(25,20)', '(25,21)', '(22,25)', '(25,22)', '(25,23)', '(24,25)', '(25,24)', '(1,25)', '(3,25)', '(5,25)', '(7,25)', '(9,25)', '(11,25)', '(13,25)', '(15,25)', '(17,25)', '(19,25)', '(21,25)', '(23,25)', '(25,25)', '(0,26)', '(2,26)', '(4,26)', '(6,26)', '(8,26)', '(10,26)', '(12,26)', '(14,26)', '(16,26)', '(18,26)', '(20,26)', '(22,26)', '(24,26)', '(26,0)', '(1,26)', '(26,1)', '(26,2)', '(3,26)', '(26,3)', '(26,4)', '(5,26)', '(26,5)', '(26,6)', '(7,26)', '(26,7)', '(26,8)', '(9,26)', '(26,9)', '(26,10)', '(11,26)', '(26,11)', '(26,12)', '(13,26)', '(26,13)', '(26,14)', '(15,26)', '(26,15)', '(26,16)', '(17,26)', '(26,17)', '(26,18)', '(19,26)', '(26,19)', '(26,20)', '(21,26)', '(26,21)', '(26,22)', '(23,26)', '(26,23)', '(26,24)', '(25,26)', '(26,25)', '(26,26)', '(0,27)', '(27,0)', '(27,1)', '(2,27)', '(27,2)', '(27,3)', '(4,27)', '(27,4)', '(27,5)', '(6,27)', '(27,6)', '(27,7)', '(8,27)', '(27,8)', '(27,9)', '(10,27)', '(27,10)', '(27,11)', '(12,27)', '(27,12)', '(27,13)', '(14,27)', '(27,14)', '(27,15)', '(16,27)', '(27,16)', '(27,17)', '(18,27)', '(27,18)', '(27,19)', '(20,27)', '(27,20)', '(27,21)', '(22,27)', '(27,22)', '(27,23)', '(24,27)', '(27,24)', '(27,25)', '(26,27)', '(27,26)', '(1,27)', '(3,27)', '(5,27)', '(7,27)', '(9,27)', '(11,27)', '(13,27)', '(15,27)', '(17,27)', '(19,27)', '(21,27)', '(23,27)', '(25,27)', '(27,27)', '(0,28)', '(2,28)', '(4,28)', '(6,28)', '(8,28)', '(10,28)', '(12,28)', '(14,28)', '(16,28)', '(18,28)', '(20,28)', '(22,28)', '(24,28)', '(26,28)', '(28,0)', '(1,28)', '(28,1)', '(28,2)', '(3,28)', '(28,3)', '(28,4)', '(5,28)', '(28,5)', '(28,6)', '(7,28)', '(28,7)', '(28,8)', '(9,28)', '(28,9)', '(28,10)', '(11,28)', '(28,11)', '(28,12)', '(13,28)', '(28,13)', '(28,14)', '(15,28)', '(28,15)', '(28,16)', '(17,28)', '(28,17)', '(28,18)', '(19,28)', '(28,19)', '(28,20)', '(21,28)', '(28,21)', '(28,22)', '(23,28)', '(28,23)', '(28,24)', '(25,28)', '(28,25)', '(28,26)', '(27,28)', '(28,27)', '(28,28)', '(0,29)', '(29,0)', '(29,1)', '(2,29)', '(29,2)', '(29,3)', '(4,29)', '(29,4)', '(29,5)', '(6,29)', '(29,6)', '(29,7)', '(8,29)', '(29,8)', '(29,9)', '(10,29)', '(29,10)', '(29,11)', '(12,29)', '(29,12)', '(29,13)', '(14,29)', '(29,14)', '(29,15)', '(16,29)', '(29,16)', '(29,17)', '(18,29)', '(29,18)', '(29,19)', '(20,29)', '(29,20)', '(29,21)', '(22,29)', '(29,22)', '(29,23)', '(24,29)', '(29,24)', '(29,25)', '(26,29)', '(29,26)', '(29,27)', '(28,29)', '(29,28)', '(1,29)', '(3,29)', '(5,29)', '(7,29)', '(9,29)', '(11,29)', '(13,29)', '(15,29)', '(17,29)', '(19,29)', '(21,29)', '(23,29)', '(25,29)', '(27,29)', '(29,29)', '(0,30)', '(2,30)', '(4,30)', '(6,30)', '(8,30)', '(10,30)', '(12,30)', '(14,30)', '(16,30)', '(18,30)', '(20,30)', '(22,30)', '(24,30)', '(26,30)', '(28,30)', '(30,0)', '(1,30)', '(30,1)', '(30,2)', '(3,30)', '(30,3)', '(30,4)', '(5,30)', '(30,5)', '(30,6)', '(7,30)', '(30,7)', '(30,8)', '(9,30)', '(30,9)', '(30,10)', '(11,30)', '(30,11)', '(30,12)', '(13,30)', '(30,13)', '(30,14)', '(15,30)', '(30,15)', '(30,16)', '(17,30)', '(30,17)', '(30,18)', '(19,30)', '(30,19)', '(30,20)', '(21,30)', '(30,21)', '(30,22)', '(23,30)', '(30,23)', '(30,24)', '(25,30)', '(30,25)', '(30,26)', '(27,30)', '(30,27)', '(30,28)', '(29,30)', '(30,29)', '(30,30)', '(0,31)', '(31,0)', '(31,1)', '(2,31)', '(31,2)', '(31,3)', '(4,31)', '(31,4)', '(31,5)', '(6,31)', '(31,6)', '(31,7)', '(8,31)', '(31,8)', '(31,9)', '(10,31)', '(31,10)', '(31,11)', '(12,31)', '(31,12)', '(31,13)', '(14,31)', '(31,14)', '(31,15)', '(16,31)', '(31,16)', '(31,17)', '(18,31)', '(31,18)', '(31,19)', '(20,31)', '(31,20)', '(31,21)', '(22,31)', '(31,22)', '(31,23)', '(24,31)', '(31,24)', '(31,25)', '(26,31)', '(31,26)', '(31,27)', '(28,31)', '(31,28)', '(31,29)', '(30,31)', '(31,30)', '(1,31)', '(3,31)', '(5,31)', '(7,31)', '(9,31)', '(11,31)', '(13,31)', '(15,31)', '(17,31)', '(19,31)', '(21,31)', '(23,31)', '(25,31)', '(27,31)', '(29,31)', '(31,31)', '(0,32)', '(2,32)', '(4,32)', '(6,32)', '(8,32)', '(10,32)', '(12,32)', '(14,32)', '(16,32)', '(18,32)', '(20,32)', '(22,32)', '(24,32)', '(26,32)', '(28,32)', '(30,32)', '(32,0)', '(1,32)', '(32,1)', '(32,2)', '(3,32)', '(32,3)', '(32,4)', '(5,32)', '(32,5)', '(32,6)', '(7,32)', '(32,7)', '(32,8)', '(9,32)', '(32,9)', '(32,10)', '(11,32)', '(32,11)', '(32,12)', '(13,32)', '(32,13)', '(32,14)', '(15,32)', '(32,15)', '(32,16)', '(17,32)', '(32,17)', '(32,18)', '(19,32)', '(32,19)', '(32,20)', '(21,32)', '(32,21)', '(32,22)', '(23,32)', '(32,23)', '(32,24)', '(25,32)', '(32,25)', '(32,26)', '(27,32)', '(32,27)', '(32,28)', '(29,32)', '(32,29)', '(32,30)', '(31,32)', '(32,31)', '(32,32)', '(0,33)', '(33,0)', '(33,1)', '(2,33)', '(33,2)', '(33,3)', '(4,33)', '(33,4)', '(33,5)', '(6,33)', '(33,6)', '(33,7)', '(8,33)', '(33,8)', '(33,9)', '(10,33)', '(33,10)', '(33,11)', '(12,33)', '(33,12)', '(33,13)', '(14,33)', '(33,14)', '(33,15)', '(16,33)', '(33,16)', '(33,17)', '(18,33)', '(33,18)', '(33,19)', '(20,33)', '(33,20)', '(33,21)', '(22,33)', '(33,22)', '(33,23)', '(24,33)', '(33,24)', '(33,25)', '(26,33)', '(33,26)', '(33,27)', '(28,33)', '(33,28)', '(33,29)', '(30,33)', '(33,30)', '(33,31)', '(32,33)', '(33,32)', '(1,33)', '(3,33)', '(5,33)', '(7,33)', '(9,33)', '(11,33)', '(13,33)', '(15,33)', '(17,33)', '(19,33)', '(21,33)', '(23,33)', '(25,33)', '(27,33)', '(29,33)', '(31,33)', '(33,33)', '(0,34)', '(2,34)', '(4,34)', '(6,34)', '(8,34)', '(10,34)', '(12,34)', '(14,34)', '(16,34)', '(18,34)', '(20,34)', '(22,34)', '(24,34)', '(26,34)', '(28,34)', '(30,34)', '(32,34)', '(34,0)', '(1,34)', '(34,1)', '(34,2)', '(3,34)', '(34,3)', '(34,4)', '(5,34)', '(34,5)', '(34,6)', '(7,34)', '(34,7)', '(34,8)', '(9,34)', '(34,9)', '(34,10)', '(11,34)', '(34,11)', '(34,12)', '(13,34)', '(34,13)', '(34,14)', '(15,34)', '(34,15)', '(34,16)', '(17,34)', '(34,17)', '(34,18)', '(19,34)', '(34,19)', '(34,20)', '(21,34)', '(34,21)', '(34,22)', '(23,34)', '(34,23)', '(34,24)', '(25,34)', '(34,25)', '(34,26)', '(27,34)', '(34,27)', '(34,28)', '(29,34)', '(34,29)', '(34,30)', '(31,34)', '(34,31)', '(34,32)', '(33,34)', '(34,33)', '(34,34)', '(0,35)', '(35,0)', '(35,1)', '(2,35)', '(35,2)', '(35,3)', '(4,35)', '(35,4)', '(35,5)', '(6,35)', '(35,6)', '(35,7)', '(8,35)', '(35,8)', '(35,9)', '(10,35)', '(35,10)', '(35,11)', '(12,35)', '(35,12)', '(35,13)', '(14,35)', '(35,14)', '(35,15)', '(16,35)', '(35,16)', '(35,17)', '(18,35)', '(35,18)', '(35,19)', '(20,35)', '(35,20)', '(35,21)', '(22,35)', '(35,22)', '(35,23)', '(24,35)', '(35,24)', '(35,25)', '(26,35)', '(35,26)', '(35,27)', '(28,35)', '(35,28)', '(35,29)', '(30,35)', '(35,30)', '(35,31)', '(32,35)', '(35,32)', '(35,33)', '(34,35)', '(35,34)', '(1,35)', '(3,35)', '(5,35)', '(7,35)', '(9,35)', '(11,35)', '(13,35)', '(15,35)', '(17,35)', '(19,35)', '(21,35)', '(23,35)', '(25,35)', '(27,35)', '(29,35)', '(31,35)', '(33,35)', '(35,35)', '(0,36)', '(2,36)', '(4,36)', '(6,36)', '(8,36)', '(10,36)', '(12,36)', '(14,36)', '(16,36)', '(18,36)', '(20,36)', '(22,36)', '(24,36)', '(26,36)', '(28,36)', '(30,36)', '(32,36)', '(34,36)', '(36,0)', '(1,36)', '(36,1)', '(36,2)', '(3,36)', '(36,3)', '(36,4)', '(5,36)', '(36,5)', '(36,6)', '(7,36)', '(36,7)', '(36,8)', '(9,36)', '(36,9)', '(36,10)', '(11,36)', '(36,11)', '(36,12)', '(13,36)', '(36,13)', '(36,14)', '(15,36)', '(36,15)', '(36,16)', '(17,36)', '(36,17)', '(36,18)', '(19,36)', '(36,19)', '(36,20)', '(21,36)', '(36,21)', '(36,22)', '(23,36)', '(36,23)', '(36,24)', '(25,36)', '(36,25)', '(36,26)', '(27,36)', '(36,27)', '(36,28)', '(29,36)', '(36,29)', '(36,30)', '(31,36)', '(36,31)', '(36,32)', '(33,36)', '(36,33)', '(36,34)', '(35,36)', '(36,35)', '(36,36)', '(0,37)', '(37,0)', '(37,1)', '(2,37)', '(37,2)', '(37,3)', '(4,37)', '(37,4)', '(37,5)', '(6,37)', '(37,6)', '(37,7)', '(8,37)', '(37,8)', '(37,9)', '(10,37)', '(37,10)', '(37,11)', '(12,37)', '(37,12)', '(37,13)', '(14,37)', '(37,14)', '(37,15)', '(16,37)', '(37,16)', '(37,17)', '(18,37)', '(37,18)', '(37,19)', '(20,37)', '(37,20)', '(37,21)', '(22,37)', '(37,22)', '(37,23)', '(24,37)', '(37,24)', '(37,25)', '(26,37)', '(37,26)', '(37,27)', '(28,37)', '(37,28)', '(37,29)', '(30,37)', '(37,30)', '(37,31)', '(32,37)', '(37,32)', '(37,33)', '(34,37)', '(37,34)', '(37,35)', '(36,37)', '(37,36)', '(1,37)', '(3,37)', '(5,37)', '(7,37)', '(9,37)', '(11,37)', '(13,37)', '(15,37)', '(17,37)', '(19,37)', '(21,37)', '(23,37)', '(25,37)', '(27,37)', '(29,37)', '(31,37)', '(33,37)', '(35,37)', '(37,37)', '(0,38)', '(2,38)', '(4,38)', '(6,38)', '(8,38)', '(10,38)', '(12,38)', '(14,38)', '(16,38)', '(18,38)', '(20,38)', '(22,38)', '(24,38)', '(26,38)', '(28,38)', '(30,38)', '(32,38)', '(34,38)', '(36,38)', '(38,0)', '(1,38)', '(38,1)', '(38,2)', '(3,38)', '(38,3)', '(38,4)', '(5,38)', '(38,5)', '(38,6)', '(7,38)', '(38,7)', '(38,8)', '(9,38)', '(38,9)', '(38,10)', '(11,38)', '(38,11)', '(38,12)', '(13,38)', '(38,13)', '(38,14)', '(15,38)', '(38,15)', '(38,16)', '(17,38)', '(38,17)', '(38,18)', '(19,38)', '(38,19)', '(38,20)', '(21,38)', '(38,21)', '(38,22)', '(23,38)', '(38,23)', '(38,24)', '(25,38)', '(38,25)', '(38,26)', '(27,38)', '(38,27)', '(38,28)', '(29,38)', '(38,29)', '(38,30)', '(31,38)', '(38,31)', '(38,32)', '(33,38)', '(38,33)', '(38,34)', '(35,38)', '(38,35)', '(38,36)', '(37,38)', '(38,37)', '(38,38)', '(0,39)', '(39,0)', '(39,1)', '(2,39)', '(39,2)', '(39,3)', '(4,39)', '(39,4)', '(39,5)', '(6,39)', '(39,6)', '(39,7)', '(8,39)', '(39,8)', '(39,9)', '(10,39)', '(39,10)', '(39,11)', '(12,39)', '(39,12)', '(39,13)', '(14,39)', '(39,14)', '(39,15)', '(16,39)', '(39,16)', '(39,17)', '(18,39)', '(39,18)', '(39,19)', '(20,39)', '(39,20)', '(39,21)', '(22,39)', '(39,22)', '(39,23)', '(24,39)', '(39,24)', '(39,25)', '(26,39)', '(39,26)', '(39,27)', '(28,39)', '(39,28)', '(39,29)', '(30,39)', '(39,30)', '(39,31)', '(32,39)', '(39,32)', '(39,33)', '(34,39)', '(39,34)', '(39,35)', '(36,39)', '(39,36)', '(39,37)', '(38,39)', '(39,38)', '(1,39)', '(3,39)', '(5,39)', '(7,39)', '(9,39)', '(11,39)', '(13,39)', '(15,39)', '(17,39)', '(19,39)', '(21,39)', '(23,39)', '(25,39)', '(27,39)', '(29,39)', '(31,39)', '(33,39)', '(35,39)', '(37,39)', '(39,39)', '(0,40)', '(2,40)', '(4,40)', '(6,40)', '(8,40)', '(10,40)', '(12,40)', '(14,40)', '(16,40)', '(18,40)', '(20,40)', '(22,40)', '(24,40)', '(26,40)', '(28,40)', '(30,40)', '(32,40)', '(34,40)', '(36,40)', '(38,40)', '(40,0)', '(1,40)', '(40,1)', '(40,2)', '(3,40)', '(40,3)', '(40,4)', '(5,40)', '(40,5)', '(40,6)', '(7,40)', '(40,7)', '(40,8)', '(9,40)', '(40,9)', '(40,10)', '(11,40)', '(40,11)', '(40,12)', '(13,40)', '(40,13)', '(40,14)', '(15,40)', '(40,15)', '(40,16)', '(17,40)', '(40,17)', '(40,18)', '(19,40)', '(40,19)', '(40,20)', '(21,40)', '(40,21)', '(40,22)', '(23,40)', '(40,23)', '(40,24)', '(25,40)', '(40,25)', '(40,26)', '(27,40)', '(40,27)', '(40,28)', '(29,40)', '(40,29)', '(40,30)', '(31,40)', '(40,31)', '(40,32)', '(33,40)', '(40,33)', '(40,34)', '(35,40)', '(40,35)', '(40,36)', '(37,40)', '(40,37)', '(40,38)', '(39,40)', '(40,39)', '(40,40)', '(0,41)', '(41,0)', '(41,1)', '(2,41)', '(41,2)', '(41,3)', '(4,41)', '(41,4)', '(41,5)', '(6,41)', '(41,6)', '(41,7)', '(8,41)', '(41,8)', '(41,9)', '(10,41)', '(41,10)', '(41,11)', '(12,41)', '(41,12)', '(41,13)', '(14,41)', '(41,14)', '(41,15)', '(16,41)', '(41,16)', '(41,17)', '(18,41)', '(41,18)', '(41,19)', '(20,41)', '(41,20)', '(41,21)', '(22,41)', '(41,22)', '(41,23)', '(24,41)', '(41,24)', '(41,25)', '(26,41)', '(41,26)', '(41,27)', '(28,41)', '(41,28)', '(41,29)', '(30,41)', '(41,30)', '(41,31)', '(32,41)', '(41,32)', '(41,33)', '(34,41)', '(41,34)', '(41,35)', '(36,41)', '(41,36)', '(41,37)', '(38,41)', '(41,38)', '(41,39)', '(40,41)', '(41,40)', '(1,41)', '(3,41)', '(5,41)', '(7,41)', '(9,41)', '(11,41)', '(13,41)', '(15,41)', '(17,41)', '(19,41)', '(21,41)', '(23,41)', '(25,41)', '(27,41)', '(29,41)', '(31,41)', '(33,41)', '(35,41)', '(37,41)', '(39,41)', '(41,41)', '(0,42)', '(2,42)', '(4,42)', '(6,42)', '(8,42)', '(10,42)', '(12,42)', '(14,42)', '(16,42)', '(18,42)', '(20,42)', '(22,42)', '(24,42)', '(26,42)', '(28,42)', '(30,42)', '(32,42)', '(34,42)', '(36,42)', '(38,42)', '(40,42)', '(42,0)', '(1,42)', '(42,1)', '(42,2)', '(3,42)', '(42,3)', '(42,4)', '(5,42)', '(42,5)', '(42,6)', '(7,42)', '(42,7)', '(42,8)', '(9,42)', '(42,9)', '(42,10)', '(11,42)', '(42,11)', '(42,12)', '(13,42)', '(42,13)', '(42,14)', '(15,42)', '(42,15)', '(42,16)', '(17,42)', '(42,17)', '(42,18)', '(19,42)', '(42,19)', '(42,20)', '(21,42)', '(42,21)', '(42,22)', '(23,42)', '(42,23)', '(42,24)', '(25,42)', '(42,25)', '(42,26)', '(27,42)', '(42,27)', '(42,28)', '(29,42)', '(42,29)', '(42,30)', '(31,42)', '(42,31)', '(42,32)', '(33,42)', '(42,33)', '(42,34)', '(35,42)', '(42,35)', '(42,36)', '(37,42)', '(42,37)', '(42,38)', '(39,42)', '(42,39)', '(42,40)', '(41,42)', '(42,41)', '(42,42)', '(0,43)', '(43,0)', '(43,1)', '(2,43)', '(43,2)', '(43,3)', '(4,43)', '(43,4)', '(43,5)', '(6,43)', '(43,6)', '(43,7)', '(8,43)', '(43,8)', '(43,9)', '(10,43)', '(43,10)', '(43,11)', '(12,43)', '(43,12)', '(43,13)', '(14,43)', '(43,14)', '(43,15)', '(16,43)', '(43,16)', '(43,17)', '(18,43)', '(43,18)', '(43,19)', '(20,43)', '(43,20)', '(43,21)', '(22,43)', '(43,22)', '(43,23)', '(24,43)', '(43,24)', '(43,25)', '(26,43)', '(43,26)', '(43,27)', '(28,43)', '(43,28)', '(43,29)', '(30,43)', '(43,30)', '(43,31)', '(32,43)', '(43,32)', '(43,33)', '(34,43)', '(43,34)', '(43,35)', '(36,43)', '(43,36)', '(43,37)', '(38,43)', '(43,38)', '(43,39)', '(40,43)', '(43,40)', '(43,41)', '(42,43)', '(43,42)', '(1,43)', '(3,43)', '(5,43)', '(7,43)', '(9,43)', '(11,43)', '(13,43)', '(15,43)', '(17,43)', '(19,43)', '(21,43)', '(23,43)', '(25,43)', '(27,43)', '(29,43)', '(31,43)', '(33,43)', '(35,43)', '(37,43)', '(39,43)', '(41,43)', '(43,43)', '(0,44)', '(2,44)', '(4,44)', '(6,44)', '(8,44)', '(10,44)', '(12,44)', '(14,44)', '(16,44)', '(18,44)', '(20,44)', '(22,44)', '(24,44)', '(26,44)', '(28,44)', '(30,44)', '(32,44)', '(34,44)', '(36,44)', '(38,44)', '(40,44)', '(42,44)', '(44,0)', '(1,44)', '(44,1)', '(44,2)', '(3,44)', '(44,3)', '(44,4)', '(5,44)', '(44,5)', '(44,6)', '(7,44)', '(44,7)', '(44,8)', '(9,44)', '(44,9)', '(44,10)', '(11,44)', '(44,11)', '(44,12)', '(13,44)', '(44,13)', '(44,14)', '(15,44)', '(44,15)', '(44,16)', '(17,44)', '(44,17)', '(44,18)', '(19,44)', '(44,19)', '(44,20)', '(21,44)', '(44,21)', '(44,22)', '(23,44)', '(44,23)', '(44,24)', '(25,44)', '(44,25)', '(44,26)', '(27,44)', '(44,27)', '(44,28)', '(29,44)', '(44,29)', '(44,30)', '(31,44)', '(44,31)', '(44,32)', '(33,44)', '(44,33)', '(44,34)', '(35,44)', '(44,35)', '(44,36)', '(37,44)', '(44,37)', '(44,38)', '(39,44)', '(44,39)', '(44,40)', '(41,44)', '(44,41)', '(44,42)', '(43,44)', '(44,43)', '(44,44)', '(0,45)', '(45,0)', '(45,1)', '(2,45)', '(45,2)', '(45,3)', '(4,45)', '(45,4)', '(45,5)', '(6,45)', '(45,6)', '(45,7)', '(8,45)', '(45,8)', '(45,9)', '(10,45)', '(45,10)', '(45,11)', '(12,45)', '(45,12)', '(45,13)', '(14,45)', '(45,14)', '(45,15)', '(16,45)', '(45,16)', '(45,17)', '(18,45)', '(45,18)', '(45,19)', '(20,45)', '(45,20)', '(45,21)', '(22,45)', '(45,22)', '(45,23)', '(24,45)', '(45,24)', '(45,25)', '(26,45)', '(45,26)', '(45,27)', '(28,45)', '(45,28)', '(45,29)', '(30,45)', '(45,30)', '(45,31)', '(32,45)', '(45,32)', '(45,33)', '(34,45)', '(45,34)', '(45,35)', '(36,45)', '(45,36)', '(45,37)', '(38,45)', '(45,38)', '(45,39)', '(40,45)', '(45,40)', '(45,41)', '(42,45)', '(45,42)', '(45,43)', '(44,45)', '(45,44)', '(1,45)', '(3,45)', '(5,45)', '(7,45)', '(9,45)', '(11,45)', '(13,45)', '(15,45)', '(17,45)', '(19,45)', '(21,45)', '(23,45)', '(25,45)', '(27,45)', '(29,45)', '(31,45)', '(33,45)', '(35,45)', '(37,45)', '(39,45)', '(41,45)', '(43,45)', '(45,45)', '(0,46)', '(2,46)', '(4,46)', '(6,46)', '(8,46)', '(10,46)', '(12,46)', '(14,46)', '(16,46)', '(18,46)', '(20,46)', '(22,46)', '(24,46)', '(26,46)', '(28,46)', '(30,46)', '(32,46)', '(34,46)', '(36,46)', '(38,46)', '(40,46)', '(42,46)', '(44,46)', '(46,0)', '(1,46)', '(46,1)', '(46,2)', '(3,46)', '(46,3)', '(46,4)', '(5,46)', '(46,5)', '(46,6)', '(7,46)', '(46,7)', '(46,8)', '(9,46)', '(46,9)', '(46,10)', '(11,46)', '(46,11)', '(46,12)', '(13,46)', '(46,13)', '(46,14)', '(15,46)', '(46,15)', '(46,16)', '(17,46)', '(46,17)', '(46,18)', '(19,46)', '(46,19)', '(46,20)', '(21,46)', '(46,21)', '(46,22)', '(23,46)', '(46,23)', '(46,24)', '(25,46)', '(46,25)', '(46,26)', '(27,46)', '(46,27)', '(46,28)', '(29,46)', '(46,29)', '(46,30)', '(31,46)', '(46,31)', '(46,32)', '(33,46)', '(46,33)', '(46,34)', '(35,46)', '(46,35)', '(46,36)', '(37,46)', '(46,37)', '(46,38)', '(39,46)', '(46,39)', '(46,40)', '(41,46)', '(46,41)', '(46,42)', '(43,46)', '(46,43)', '(46,44)', '(45,46)', '(46,45)', '(46,46)', '(0,47)', '(47,0)', '(47,1)', '(2,47)', '(47,2)', '(47,3)', '(4,47)', '(47,4)', '(47,5)', '(6,47)', '(47,6)', '(47,7)', '(8,47)', '(47,8)', '(47,9)', '(10,47)', '(47,10)', '(47,11)', '(12,47)', '(47,12)', '(47,13)', '(14,47)', '(47,14)', '(47,15)', '(16,47)', '(47,16)', '(47,17)', '(18,47)', '(47,18)', '(47,19)', '(20,47)', '(47,20)', '(47,21)', '(22,47)', '(47,22)', '(47,23)', '(24,47)', '(47,24)', '(47,25)', '(26,47)', '(47,26)', '(47,27)', '(28,47)', '(47,28)', '(47,29)', '(30,47)', '(47,30)', '(47,31)', '(32,47)', '(47,32)', '(47,33)', '(34,47)', '(47,34)', '(47,35)', '(36,47)', '(47,36)', '(47,37)', '(38,47)', '(47,38)', '(47,39)', '(40,47)', '(47,40)', '(47,41)', '(42,47)', '(47,42)', '(47,43)', '(44,47)', '(47,44)', '(47,45)', '(46,47)', '(47,46)', '(1,47)', '(3,47)', '(5,47)', '(7,47)', '(9,47)', '(11,47)', '(13,47)', '(15,47)', '(17,47)', '(19,47)', '(21,47)', '(23,47)', '(25,47)', '(27,47)', '(29,47)', '(31,47)', '(33,47)', '(35,47)', '(37,47)', '(39,47)', '(41,47)', '(43,47)', '(45,47)', '(47,47)', '(0,48)', '(2,48)', '(4,48)', '(6,48)', '(8,48)', '(10,48)', '(12,48)', '(14,48)', '(16,48)', '(18,48)', '(20,48)', '(22,48)', '(24,48)', '(26,48)', '(28,48)', '(30,48)', '(32,48)', '(34,48)', '(36,48)', '(38,48)', '(40,48)', '(42,48)', '(44,48)', '(46,48)', '(48,0)', '(1,48)', '(48,1)', '(48,2)', '(3,48)', '(48,3)', '(48,4)', '(5,48)', '(48,5)', '(48,6)', '(7,48)', '(48,7)', '(48,8)', '(9,48)', '(48,9)', '(48,10)', '(11,48)', '(48,11)', '(48,12)', '(13,48)', '(48,13)', '(48,14)', '(15,48)', '(48,15)', '(48,16)', '(17,48)', '(48,17)', '(48,18)', '(19,48)', '(48,19)', '(48,20)', '(21,48)', '(48,21)', '(48,22)', '(23,48)', '(48,23)', '(48,24)', '(25,48)', '(48,25)', '(48,26)', '(27,48)', '(48,27)', '(48,28)', '(29,48)', '(48,29)', '(48,30)', '(31,48)', '(48,31)', '(48,32)', '(33,48)', '(48,33)', '(48,34)', '(35,48)', '(48,35)', '(48,36)', '(37,48)', '(48,37)', '(48,38)', '(39,48)', '(48,39)', '(48,40)', '(41,48)', '(48,41)', '(48,42)', '(43,48)', '(48,43)', '(48,44)', '(45,48)', '(48,45)', '(48,46)', '(47,48)', '(48,47)', '(48,48)', '(0,49)', '(49,0)', '(49,1)', '(2,49)', '(49,2)', '(49,3)', '(4,49)', '(49,4)', '(49,5)', '(6,49)', '(49,6)', '(49,7)', '(8,49)', '(49,8)', '(49,9)', '(10,49)', '(49,10)', '(49,11)', '(12,49)', '(49,12)', '(49,13)', '(14,49)', '(49,14)', '(49,15)', '(16,49)', '(49,16)', '(49,17)', '(18,49)', '(49,18)', '(49,19)', '(20,49)', '(49,20)', '(49,21)', '(22,49)', '(49,22)', '(49,23)', '(24,49)', '(49,24)', '(49,25)', '(26,49)', '(49,26)', '(49,27)', '(28,49)', '(49,28)', '(49,29)', '(30,49)', '(49,30)', '(49,31)', '(32,49)', '(49,32)', '(49,33)', '(34,49)', '(49,34)', '(49,35)', '(36,49)', '(49,36)', '(49,37)', '(38,49)', '(49,38)', '(49,39)', '(40,49)', '(49,40)', '(49,41)', '(42,49)', '(49,42)', '(49,43)', '(44,49)', '(49,44)', '(49,45)', '(46,49)', '(49,46)', '(49,47)', '(48,49)', '(49,48)', '(1,49)', '(3,49)', '(5,49)', '(7,49)', '(9,49)', '(11,49)', '(13,49)', '(15,49)', '(17,49)', '(19,49)', '(21,49)', '(23,49)', '(25,49)', '(27,49)', '(29,49)', '(31,49)', '(33,49)', '(35,49)', '(37,49)', '(39,49)', '(41,49)', '(43,49)', '(45,49)', '(47,49)', '(49,49)']
VOCAB_TOKEN_TO_INDEX = {'<ADJLIST_START>': 0, '<ADJLIST_END>': 1, '<TARGET_START>': 2, '<TARGET_END>': 3, '<ORIGIN_START>': 4, '<ORIGIN_END>': 5, '<PATH_START>': 6, '<PATH_END>': 7, '<-->': 8, ';': 9, '<PADDING>': 10, '(': 11, ',': 12, ')': 13, '=': 14, '||': 15, ':': 16, 'THEN': 17, '-': 18, '<UNK>': 19, 'TARGET_A': 20, 'TARGET_B': 21, 'TARGET_C': 22, 'TARGET_D': 23, 'TARGET_E': 24, 'TARGET_F': 25, 'TARGET_G': 26, 'TARGET_H': 27, 'TARGET_I': 28, 'TARGET_J': 29, 'TARGET_K': 30, 'TARGET_L': 31, 'TARGET_M': 32, 'TARGET_N': 33, 'TARGET_O': 34, 'TARGET_P': 35, 'TARGET_Q': 36, 'TARGET_R': 37, 'TARGET_S': 38, 'TARGET_T': 39, 'TARGET_U': 40, 'TARGET_V': 41, 'TARGET_W': 42, 'TARGET_X': 43, 'TARGET_Y': 44, 'TARGET_Z': 45, 'TARGET_NORTH': 46, 'TARGET_SOUTH': 47, 'TARGET_EAST': 48, 'TARGET_WEST': 49, 'TARGET_NORTHEAST': 50, 'TARGET_NORTHWEST': 51, 'TARGET_SOUTHEAST': 52, 'TARGET_SOUTHWEST': 53, 'TARGET_CENTER': 54, 'NORTH': 55, 'SOUTH': 56, 'EAST': 57, 'WEST': 58, 'FORWARD': 59, 'BACKWARD': 60, 'LEFT': 61, 'RIGHT': 62, 'STAY': 63, '+0': 64, '+1': 65, '+2': 66, '+3': 67, '+4': 68, '+5': 69, '+6': 70, '+7': 71, '+8': 72, '+9': 73, '+10': 74, '+11': 75, '+12': 76, '+13': 77, '+14': 78, '+15': 79, '+16': 80, '+17': 81, '+18': 82, '+19': 83, '+20': 84, '+21': 85, '+22': 86, '+23': 87, '+24': 88, '+25': 89, '+26': 90, '+27': 91, '+28': 92, '+29': 93, '+30': 94, '+31': 95, '+32': 96, '+33': 97, '+34': 98, '+35': 99, '+36': 100, '+37': 101, '+38': 102, '+39': 103, '+40': 104, '+41': 105, '+42': 106, '+43': 107, '+44': 108, '+45': 109, '+46': 110, '+47': 111, '+48': 112, '+49': 113, '+50': 114, '+51': 115, '+52': 116, '+53': 117, '+54': 118, '+55': 119, '+56': 120, '+57': 121, '+58': 122, '+59': 123, '+60': 124, '+61': 125, '+62': 126, '+63': 127, '+64': 128, '+65': 129, '+66': 130, '+67': 131, '+68': 132, '+69': 133, '+70': 134, '+71': 135, '+72': 136, '+73': 137, '+74': 138, '+75': 139, '+76': 140, '+77': 141, '+78': 142, '+79': 143, '+80': 144, '+81': 145, '+82': 146, '+83': 147, '+84': 148, '+85': 149, '+86': 150, '+87': 151, '+88': 152, '+89': 153, '+90': 154, '+91': 155, '+92': 156, '+93': 157, '+94': 158, '+95': 159, '+96': 160, '+97': 161, '+98': 162, '+99': 163, '+100': 164, '+101': 165, '+102': 166, '+103': 167, '+104': 168, '+105': 169, '+106': 170, '+107': 171, '+108': 172, '+109': 173, '+110': 174, '+111': 175, '+112': 176, '+113': 177, '+114': 178, '+115': 179, '+116': 180, '+117': 181, '+118': 182, '+119': 183, '+120': 184, '+121': 185, '+122': 186, '+123': 187, '+124': 188, '+125': 189, '+126': 190, '+127': 191, '+128': 192, '+129': 193, '+130': 194, '+131': 195, '+132': 196, '+133': 197, '+134': 198, '+135': 199, '+136': 200, '+137': 201, '+138': 202, '+139': 203, '+140': 204, '+141': 205, '+142': 206, '+143': 207, '+144': 208, '+145': 209, '+146': 210, '+147': 211, '+148': 212, '+149': 213, '+150': 214, '+151': 215, '+152': 216, '+153': 217, '+154': 218, '+155': 219, '+156': 220, '+157': 221, '+158': 222, '+159': 223, '+160': 224, '+161': 225, '+162': 226, '+163': 227, '+164': 228, '+165': 229, '+166': 230, '+167': 231, '+168': 232, '+169': 233, '+170': 234, '+171': 235, '+172': 236, '+173': 237, '+174': 238, '+175': 239, '+176': 240, '+177': 241, '+178': 242, '+179': 243, '+180': 244, '+181': 245, '+182': 246, '+183': 247, '+184': 248, '+185': 249, '+186': 250, '+187': 251, '+188': 252, '+189': 253, '+190': 254, '+191': 255, '+192': 256, '+193': 257, '+194': 258, '+195': 259, '+196': 260, '+197': 261, '+198': 262, '+199': 263, '+200': 264, '+201': 265, '+202': 266, '+203': 267, '+204': 268, '+205': 269, '+206': 270, '+207': 271, '+208': 272, '+209': 273, '+210': 274, '+211': 275, '+212': 276, '+213': 277, '+214': 278, '+215': 279, '+216': 280, '+217': 281, '+218': 282, '+219': 283, '+220': 284, '+221': 285, '+222': 286, '+223': 287, '+224': 288, '+225': 289, '+226': 290, '+227': 291, '+228': 292, '+229': 293, '+230': 294, '+231': 295, '+232': 296, '+233': 297, '+234': 298, '+235': 299, '+236': 300, '+237': 301, '+238': 302, '+239': 303, '+240': 304, '+241': 305, '+242': 306, '+243': 307, '+244': 308, '+245': 309, '+246': 310, '+247': 311, '+248': 312, '+249': 313, '+250': 314, '+251': 315, '+252': 316, '+253': 317, '+254': 318, '+255': 319, '0': 320, '1': 321, '2': 322, '3': 323, '4': 324, '5': 325, '6': 326, '7': 327, '8': 328, '9': 329, '10': 330, '11': 331, '12': 332, '13': 333, '14': 334, '15': 335, '16': 336, '17': 337, '18': 338, '19': 339, '20': 340, '21': 341, '22': 342, '23': 343, '24': 344, '25': 345, '26': 346, '27': 347, '28': 348, '29': 349, '30': 350, '31': 351, '32': 352, '33': 353, '34': 354, '35': 355, '36': 356, '37': 357, '38': 358, '39': 359, '40': 360, '41': 361, '42': 362, '43': 363, '44': 364, '45': 365, '46': 366, '47': 367, '48': 368, '49': 369, '50': 370, '51': 371, '52': 372, '53': 373, '54': 374, '55': 375, '56': 376, '57': 377, '58': 378, '59': 379, '60': 380, '61': 381, '62': 382, '63': 383, '64': 384, '65': 385, '66': 386, '67': 387, '68': 388, '69': 389, '70': 390, '71': 391, '72': 392, '73': 393, '74': 394, '75': 395, '76': 396, '77': 397, '78': 398, '79': 399, '80': 400, '81': 401, '82': 402, '83': 403, '84': 404, '85': 405, '86': 406, '87': 407, '88': 408, '89': 409, '90': 410, '91': 411, '92': 412, '93': 413, '94': 414, '95': 415, '96': 416, '97': 417, '98': 418, '99': 419, '100': 420, '101': 421, '102': 422, '103': 423, '104': 424, '105': 425, '106': 426, '107': 427, '108': 428, '109': 429, '110': 430, '111': 431, '112': 432, '113': 433, '114': 434, '115': 435, '116': 436, '117': 437, '118': 438, '119': 439, '120': 440, '121': 441, '122': 442, '123': 443, '124': 444, '125': 445, '126': 446, '127': 447, '-256': 448, '-255': 449, '-254': 450, '-253': 451, '-252': 452, '-251': 453, '-250': 454, '-249': 455, '-248': 456, '-247': 457, '-246': 458, '-245': 459, '-244': 460, '-243': 461, '-242': 462, '-241': 463, '-240': 464, '-239': 465, '-238': 466, '-237': 467, '-236': 468, '-235': 469, '-234': 470, '-233': 471, '-232': 472, '-231': 473, '-230': 474, '-229': 475, '-228': 476, '-227': 477, '-226': 478, '-225': 479, '-224': 480, '-223': 481, '-222': 482, '-221': 483, '-220': 484, '-219': 485, '-218': 486, '-217': 487, '-216': 488, '-215': 489, '-214': 490, '-213': 491, '-212': 492, '-211': 493, '-210': 494, '-209': 495, '-208': 496, '-207': 497, '-206': 498, '-205': 499, '-204': 500, '-203': 501, '-202': 502, '-201': 503, '-200': 504, '-199': 505, '-198': 506, '-197': 507, '-196': 508, '-195': 509, '-194': 510, '-193': 511, '-192': 512, '-191': 513, '-190': 514, '-189': 515, '-188': 516, '-187': 517, '-186': 518, '-185': 519, '-184': 520, '-183': 521, '-182': 522, '-181': 523, '-180': 524, '-179': 525, '-178': 526, '-177': 527, '-176': 528, '-175': 529, '-174': 530, '-173': 531, '-172': 532, '-171': 533, '-170': 534, '-169': 535, '-168': 536, '-167': 537, '-166': 538, '-165': 539, '-164': 540, '-163': 541, '-162': 542, '-161': 543, '-160': 544, '-159': 545, '-158': 546, '-157': 547, '-156': 548, '-155': 549, '-154': 550, '-153': 551, '-152': 552, '-151': 553, '-150': 554, '-149': 555, '-148': 556, '-147': 557, '-146': 558, '-145': 559, '-144': 560, '-143': 561, '-142': 562, '-141': 563, '-140': 564, '-139': 565, '-138': 566, '-137': 567, '-136': 568, '-135': 569, '-134': 570, '-133': 571, '-132': 572, '-131': 573, '-130': 574, '-129': 575, '-128': 576, '-127': 577, '-126': 578, '-125': 579, '-124': 580, '-123': 581, '-122': 582, '-121': 583, '-120': 584, '-119': 585, '-118': 586, '-117': 587, '-116': 588, '-115': 589, '-114': 590, '-113': 591, '-112': 592, '-111': 593, '-110': 594, '-109': 595, '-108': 596, '-107': 597, '-106': 598, '-105': 599, '-104': 600, '-103': 601, '-102': 602, '-101': 603, '-100': 604, '-99': 605, '-98': 606, '-97': 607, '-96': 608, '-95': 609, '-94': 610, '-93': 611, '-92': 612, '-91': 613, '-90': 614, '-89': 615, '-88': 616, '-87': 617, '-86': 618, '-85': 619, '-84': 620, '-83': 621, '-82': 622, '-81': 623, '-80': 624, '-79': 625, '-78': 626, '-77': 627, '-76': 628, '-75': 629, '-74': 630, '-73': 631, '-72': 632, '-71': 633, '-70': 634, '-69': 635, '-68': 636, '-67': 637, '-66': 638, '-65': 639, '-64': 640, '-63': 641, '-62': 642, '-61': 643, '-60': 644, '-59': 645, '-58': 646, '-57': 647, '-56': 648, '-55': 649, '-54': 650, '-53': 651, '-52': 652, '-51': 653, '-50': 654, '-49': 655, '-48': 656, '-47': 657, '-46': 658, '-45': 659, '-44': 660, '-43': 661, '-42': 662, '-41': 663, '-40': 664, '-39': 665, '-38': 666, '-37': 667, '-36': 668, '-35': 669, '-34': 670, '-33': 671, '-32': 672, '-31': 673, '-30': 674, '-29': 675, '-28': 676, '-27': 677, '-26': 678, '-25': 679, '-24': 680, '-23': 681, '-22': 682, '-21': 683, '-20': 684, '-19': 685, '-18': 686, '-17': 687, '-16': 688, '-15': 689, '-14': 690, '-13': 691, '-12': 692, '-11': 693, '-10': 694, '-9': 695, '-8': 696, '-7': 697, '-6': 698, '-5': 699, '-4': 700, '-3': 701, '-2': 702, '-1': 703, 'STEP': 704, 'ADJ_GROUP': 705, '&': 706, '<XX>': 707, '<RESERVE_708>': 708, '<RESERVE_709>': 709, '<RESERVE_710>': 710, '<RESERVE_711>': 711, '<RESERVE_712>': 712, '<RESERVE_713>': 713, '<RESERVE_714>': 714, '<RESERVE_715>': 715, '<RESERVE_716>': 716, '<RESERVE_717>': 717, '<RESERVE_718>': 718, '<RESERVE_719>': 719, '<RESERVE_720>': 720, '<RESERVE_721>': 721, '<RESERVE_722>': 722, '<RESERVE_723>': 723, '<RESERVE_724>': 724, '<RESERVE_725>': 725, '<RESERVE_726>': 726, '<RESERVE_727>': 727, '<RESERVE_728>': 728, '<RESERVE_729>': 729, '<RESERVE_730>': 730, '<RESERVE_731>': 731, '<RESERVE_732>': 732, '<RESERVE_733>': 733, '<RESERVE_734>': 734, '<RESERVE_735>': 735, '<RESERVE_736>': 736, '<RESERVE_737>': 737, '<RESERVE_738>': 738, '<RESERVE_739>': 739, '<RESERVE_740>': 740, '<RESERVE_741>': 741, '<RESERVE_742>': 742, '<RESERVE_743>': 743, '<RESERVE_744>': 744, '<RESERVE_745>': 745, '<RESERVE_746>': 746, '<RESERVE_747>': 747, '<RESERVE_748>': 748, '<RESERVE_749>': 749, '<RESERVE_750>': 750, '<RESERVE_751>': 751, '<RESERVE_752>': 752, '<RESERVE_753>': 753, '<RESERVE_754>': 754, '<RESERVE_755>': 755, '<RESERVE_756>': 756, '<RESERVE_757>': 757, '<RESERVE_758>': 758, '<RESERVE_759>': 759, '<RESERVE_760>': 760, '<RESERVE_761>': 761, '<RESERVE_762>': 762, '<RESERVE_763>': 763, '<RESERVE_764>': 764, '<RESERVE_765>': 765, '<RESERVE_766>': 766, '<RESERVE_767>': 767, '<RESERVE_768>': 768, '<RESERVE_769>': 769, '<RESERVE_770>': 770, '<RESERVE_771>': 771, '<RESERVE_772>': 772, '<RESERVE_773>': 773, '<RESERVE_774>': 774, '<RESERVE_775>': 775, '<RESERVE_776>': 776, '<RESERVE_777>': 777, '<RESERVE_778>': 778, '<RESERVE_779>': 779, '<RESERVE_780>': 780, '<RESERVE_781>': 781, '<RESERVE_782>': 782, '<RESERVE_783>': 783, '<RESERVE_784>': 784, '<RESERVE_785>': 785, '<RESERVE_786>': 786, '<RESERVE_787>': 787, '<RESERVE_788>': 788, '<RESERVE_789>': 789, '<RESERVE_790>': 790, '<RESERVE_791>': 791, '<RESERVE_792>': 792, '<RESERVE_793>': 793, '<RESERVE_794>': 794, '<RESERVE_795>': 795, '<RESERVE_796>': 796, '<RESERVE_797>': 797, '<RESERVE_798>': 798, '<RESERVE_799>': 799, '<RESERVE_800>': 800, '<RESERVE_801>': 801, '<RESERVE_802>': 802, '<RESERVE_803>': 803, '<RESERVE_804>': 804, '<RESERVE_805>': 805, '<RESERVE_806>': 806, '<RESERVE_807>': 807, '<RESERVE_808>': 808, '<RESERVE_809>': 809, '<RESERVE_810>': 810, '<RESERVE_811>': 811, '<RESERVE_812>': 812, '<RESERVE_813>': 813, '<RESERVE_814>': 814, '<RESERVE_815>': 815, '<RESERVE_816>': 816, '<RESERVE_817>': 817, '<RESERVE_818>': 818, '<RESERVE_819>': 819, '<RESERVE_820>': 820, '<RESERVE_821>': 821, '<RESERVE_822>': 822, '<RESERVE_823>': 823, '<RESERVE_824>': 824, '<RESERVE_825>': 825, '<RESERVE_826>': 826, '<RESERVE_827>': 827, '<RESERVE_828>': 828, '<RESERVE_829>': 829, '<RESERVE_830>': 830, '<RESERVE_831>': 831, '<RESERVE_832>': 832, '<RESERVE_833>': 833, '<RESERVE_834>': 834, '<RESERVE_835>': 835, '<RESERVE_836>': 836, '<RESERVE_837>': 837, '<RESERVE_838>': 838, '<RESERVE_839>': 839, '<RESERVE_840>': 840, '<RESERVE_841>': 841, '<RESERVE_842>': 842, '<RESERVE_843>': 843, '<RESERVE_844>': 844, '<RESERVE_845>': 845, '<RESERVE_846>': 846, '<RESERVE_847>': 847, '<RESERVE_848>': 848, '<RESERVE_849>': 849, '<RESERVE_850>': 850, '<RESERVE_851>': 851, '<RESERVE_852>': 852, '<RESERVE_853>': 853, '<RESERVE_854>': 854, '<RESERVE_855>': 855, '<RESERVE_856>': 856, '<RESERVE_857>': 857, '<RESERVE_858>': 858, '<RESERVE_859>': 859, '<RESERVE_860>': 860, '<RESERVE_861>': 861, '<RESERVE_862>': 862, '<RESERVE_863>': 863, '<RESERVE_864>': 864, '<RESERVE_865>': 865, '<RESERVE_866>': 866, '<RESERVE_867>': 867, '<RESERVE_868>': 868, '<RESERVE_869>': 869, '<RESERVE_870>': 870, '<RESERVE_871>': 871, '<RESERVE_872>': 872, '<RESERVE_873>': 873, '<RESERVE_874>': 874, '<RESERVE_875>': 875, '<RESERVE_876>': 876, '<RESERVE_877>': 877, '<RESERVE_878>': 878, '<RESERVE_879>': 879, '<RESERVE_880>': 880, '<RESERVE_881>': 881, '<RESERVE_882>': 882, '<RESERVE_883>': 883, '<RESERVE_884>': 884, '<RESERVE_885>': 885, '<RESERVE_886>': 886, '<RESERVE_887>': 887, '<RESERVE_888>': 888, '<RESERVE_889>': 889, '<RESERVE_890>': 890, '<RESERVE_891>': 891, '<RESERVE_892>': 892, '<RESERVE_893>': 893, '<RESERVE_894>': 894, '<RESERVE_895>': 895, '<RESERVE_896>': 896, '<RESERVE_897>': 897, '<RESERVE_898>': 898, '<RESERVE_899>': 899, '<RESERVE_900>': 900, '<RESERVE_901>': 901, '<RESERVE_902>': 902, '<RESERVE_903>': 903, '<RESERVE_904>': 904, '<RESERVE_905>': 905, '<RESERVE_906>': 906, '<RESERVE_907>': 907, '<RESERVE_908>': 908, '<RESERVE_909>': 909, '<RESERVE_910>': 910, '<RESERVE_911>': 911, '<RESERVE_912>': 912, '<RESERVE_913>': 913, '<RESERVE_914>': 914, '<RESERVE_915>': 915, '<RESERVE_916>': 916, '<RESERVE_917>': 917, '<RESERVE_918>': 918, '<RESERVE_919>': 919, '<RESERVE_920>': 920, '<RESERVE_921>': 921, '<RESERVE_922>': 922, '<RESERVE_923>': 923, '<RESERVE_924>': 924, '<RESERVE_925>': 925, '<RESERVE_926>': 926, '<RESERVE_927>': 927, '<RESERVE_928>': 928, '<RESERVE_929>': 929, '<RESERVE_930>': 930, '<RESERVE_931>': 931, '<RESERVE_932>': 932, '<RESERVE_933>': 933, '<RESERVE_934>': 934, '<RESERVE_935>': 935, '<RESERVE_936>': 936, '<RESERVE_937>': 937, '<RESERVE_938>': 938, '<RESERVE_939>': 939, '<RESERVE_940>': 940, '<RESERVE_941>': 941, '<RESERVE_942>': 942, '<RESERVE_943>': 943, '<RESERVE_944>': 944, '<RESERVE_945>': 945, '<RESERVE_946>': 946, '<RESERVE_947>': 947, '<RESERVE_948>': 948, '<RESERVE_949>': 949, '<RESERVE_950>': 950, '<RESERVE_951>': 951, '<RESERVE_952>': 952, '<RESERVE_953>': 953, '<RESERVE_954>': 954, '<RESERVE_955>': 955, '<RESERVE_956>': 956, '<RESERVE_957>': 957, '<RESERVE_958>': 958, '<RESERVE_959>': 959, '<RESERVE_960>': 960, '<RESERVE_961>': 961, '<RESERVE_962>': 962, '<RESERVE_963>': 963, '<RESERVE_964>': 964, '<RESERVE_965>': 965, '<RESERVE_966>': 966, '<RESERVE_967>': 967, '<RESERVE_968>': 968, '<RESERVE_969>': 969, '<RESERVE_970>': 970, '<RESERVE_971>': 971, '<RESERVE_972>': 972, '<RESERVE_973>': 973, '<RESERVE_974>': 974, '<RESERVE_975>': 975, '<RESERVE_976>': 976, '<RESERVE_977>': 977, '<RESERVE_978>': 978, '<RESERVE_979>': 979, '<RESERVE_980>': 980, '<RESERVE_981>': 981, '<RESERVE_982>': 982, '<RESERVE_983>': 983, '<RESERVE_984>': 984, '<RESERVE_985>': 985, '<RESERVE_986>': 986, '<RESERVE_987>': 987, '<RESERVE_988>': 988, '<RESERVE_989>': 989, '<RESERVE_990>': 990, '<RESERVE_991>': 991, '<RESERVE_992>': 992, '<RESERVE_993>': 993, '<RESERVE_994>': 994, '<RESERVE_995>': 995, '<RESERVE_996>': 996, '<RESERVE_997>': 997, '<RESERVE_998>': 998, '<RESERVE_999>': 999, '<RESERVE_1000>': 1000, '<RESERVE_1001>': 1001, '<RESERVE_1002>': 1002, '<RESERVE_1003>': 1003, '<RESERVE_1004>': 1004, '<RESERVE_1005>': 1005, '<RESERVE_1006>': 1006, '<RESERVE_1007>': 1007, '<RESERVE_1008>': 1008, '<RESERVE_1009>': 1009, '<RESERVE_1010>': 1010, '<RESERVE_1011>': 1011, '<RESERVE_1012>': 1012, '<RESERVE_1013>': 1013, '<RESERVE_1014>': 1014, '<RESERVE_1015>': 1015, '<RESERVE_1016>': 1016, '<RESERVE_1017>': 1017, '<RESERVE_1018>': 1018, '<RESERVE_1019>': 1019, '<RESERVE_1020>': 1020, '<RESERVE_1021>': 1021, '<RESERVE_1022>': 1022, '<RESERVE_1023>': 1023, '<RESERVE_1024>': 1024, '<RESERVE_1025>': 1025, '<RESERVE_1026>': 1026, '<RESERVE_1027>': 1027, '<RESERVE_1028>': 1028, '<RESERVE_1029>': 1029, '<RESERVE_1030>': 1030, '<RESERVE_1031>': 1031, '<RESERVE_1032>': 1032, '<RESERVE_1033>': 1033, '<RESERVE_1034>': 1034, '<RESERVE_1035>': 1035, '<RESERVE_1036>': 1036, '<RESERVE_1037>': 1037, '<RESERVE_1038>': 1038, '<RESERVE_1039>': 1039, '<RESERVE_1040>': 1040, '<RESERVE_1041>': 1041, '<RESERVE_1042>': 1042, '<RESERVE_1043>': 1043, '<RESERVE_1044>': 1044, '<RESERVE_1045>': 1045, '<RESERVE_1046>': 1046, '<RESERVE_1047>': 1047, '<RESERVE_1048>': 1048, '<RESERVE_1049>': 1049, '<RESERVE_1050>': 1050, '<RESERVE_1051>': 1051, '<RESERVE_1052>': 1052, '<RESERVE_1053>': 1053, '<RESERVE_1054>': 1054, '<RESERVE_1055>': 1055, '<RESERVE_1056>': 1056, '<RESERVE_1057>': 1057, '<RESERVE_1058>': 1058, '<RESERVE_1059>': 1059, '<RESERVE_1060>': 1060, '<RESERVE_1061>': 1061, '<RESERVE_1062>': 1062, '<RESERVE_1063>': 1063, '<RESERVE_1064>': 1064, '<RESERVE_1065>': 1065, '<RESERVE_1066>': 1066, '<RESERVE_1067>': 1067, '<RESERVE_1068>': 1068, '<RESERVE_1069>': 1069, '<RESERVE_1070>': 1070, '<RESERVE_1071>': 1071, '<RESERVE_1072>': 1072, '<RESERVE_1073>': 1073, '<RESERVE_1074>': 1074, '<RESERVE_1075>': 1075, '<RESERVE_1076>': 1076, '<RESERVE_1077>': 1077, '<RESERVE_1078>': 1078, '<RESERVE_1079>': 1079, '<RESERVE_1080>': 1080, '<RESERVE_1081>': 1081, '<RESERVE_1082>': 1082, '<RESERVE_1083>': 1083, '<RESERVE_1084>': 1084, '<RESERVE_1085>': 1085, '<RESERVE_1086>': 1086, '<RESERVE_1087>': 1087, '<RESERVE_1088>': 1088, '<RESERVE_1089>': 1089, '<RESERVE_1090>': 1090, '<RESERVE_1091>': 1091, '<RESERVE_1092>': 1092, '<RESERVE_1093>': 1093, '<RESERVE_1094>': 1094, '<RESERVE_1095>': 1095, '<RESERVE_1096>': 1096, '<RESERVE_1097>': 1097, '<RESERVE_1098>': 1098, '<RESERVE_1099>': 1099, '<RESERVE_1100>': 1100, '<RESERVE_1101>': 1101, '<RESERVE_1102>': 1102, '<RESERVE_1103>': 1103, '<RESERVE_1104>': 1104, '<RESERVE_1105>': 1105, '<RESERVE_1106>': 1106, '<RESERVE_1107>': 1107, '<RESERVE_1108>': 1108, '<RESERVE_1109>': 1109, '<RESERVE_1110>': 1110, '<RESERVE_1111>': 1111, '<RESERVE_1112>': 1112, '<RESERVE_1113>': 1113, '<RESERVE_1114>': 1114, '<RESERVE_1115>': 1115, '<RESERVE_1116>': 1116, '<RESERVE_1117>': 1117, '<RESERVE_1118>': 1118, '<RESERVE_1119>': 1119, '<RESERVE_1120>': 1120, '<RESERVE_1121>': 1121, '<RESERVE_1122>': 1122, '<RESERVE_1123>': 1123, '<RESERVE_1124>': 1124, '<RESERVE_1125>': 1125, '<RESERVE_1126>': 1126, '<RESERVE_1127>': 1127, '<RESERVE_1128>': 1128, '<RESERVE_1129>': 1129, '<RESERVE_1130>': 1130, '<RESERVE_1131>': 1131, '<RESERVE_1132>': 1132, '<RESERVE_1133>': 1133, '<RESERVE_1134>': 1134, '<RESERVE_1135>': 1135, '<RESERVE_1136>': 1136, '<RESERVE_1137>': 1137, '<RESERVE_1138>': 1138, '<RESERVE_1139>': 1139, '<RESERVE_1140>': 1140, '<RESERVE_1141>': 1141, '<RESERVE_1142>': 1142, '<RESERVE_1143>': 1143, '<RESERVE_1144>': 1144, '<RESERVE_1145>': 1145, '<RESERVE_1146>': 1146, '<RESERVE_1147>': 1147, '<RESERVE_1148>': 1148, '<RESERVE_1149>': 1149, '<RESERVE_1150>': 1150, '<RESERVE_1151>': 1151, '<RESERVE_1152>': 1152, '<RESERVE_1153>': 1153, '<RESERVE_1154>': 1154, '<RESERVE_1155>': 1155, '<RESERVE_1156>': 1156, '<RESERVE_1157>': 1157, '<RESERVE_1158>': 1158, '<RESERVE_1159>': 1159, '<RESERVE_1160>': 1160, '<RESERVE_1161>': 1161, '<RESERVE_1162>': 1162, '<RESERVE_1163>': 1163, '<RESERVE_1164>': 1164, '<RESERVE_1165>': 1165, '<RESERVE_1166>': 1166, '<RESERVE_1167>': 1167, '<RESERVE_1168>': 1168, '<RESERVE_1169>': 1169, '<RESERVE_1170>': 1170, '<RESERVE_1171>': 1171, '<RESERVE_1172>': 1172, '<RESERVE_1173>': 1173, '<RESERVE_1174>': 1174, '<RESERVE_1175>': 1175, '<RESERVE_1176>': 1176, '<RESERVE_1177>': 1177, '<RESERVE_1178>': 1178, '<RESERVE_1179>': 1179, '<RESERVE_1180>': 1180, '<RESERVE_1181>': 1181, '<RESERVE_1182>': 1182, '<RESERVE_1183>': 1183, '<RESERVE_1184>': 1184, '<RESERVE_1185>': 1185, '<RESERVE_1186>': 1186, '<RESERVE_1187>': 1187, '<RESERVE_1188>': 1188, '<RESERVE_1189>': 1189, '<RESERVE_1190>': 1190, '<RESERVE_1191>': 1191, '<RESERVE_1192>': 1192, '<RESERVE_1193>': 1193, '<RESERVE_1194>': 1194, '<RESERVE_1195>': 1195, '<RESERVE_1196>': 1196, '<RESERVE_1197>': 1197, '<RESERVE_1198>': 1198, '<RESERVE_1199>': 1199, '<RESERVE_1200>': 1200, '<RESERVE_1201>': 1201, '<RESERVE_1202>': 1202, '<RESERVE_1203>': 1203, '<RESERVE_1204>': 1204, '<RESERVE_1205>': 1205, '<RESERVE_1206>': 1206, '<RESERVE_1207>': 1207, '<RESERVE_1208>': 1208, '<RESERVE_1209>': 1209, '<RESERVE_1210>': 1210, '<RESERVE_1211>': 1211, '<RESERVE_1212>': 1212, '<RESERVE_1213>': 1213, '<RESERVE_1214>': 1214, '<RESERVE_1215>': 1215, '<RESERVE_1216>': 1216, '<RESERVE_1217>': 1217, '<RESERVE_1218>': 1218, '<RESERVE_1219>': 1219, '<RESERVE_1220>': 1220, '<RESERVE_1221>': 1221, '<RESERVE_1222>': 1222, '<RESERVE_1223>': 1223, '<RESERVE_1224>': 1224, '<RESERVE_1225>': 1225, '<RESERVE_1226>': 1226, '<RESERVE_1227>': 1227, '<RESERVE_1228>': 1228, '<RESERVE_1229>': 1229, '<RESERVE_1230>': 1230, '<RESERVE_1231>': 1231, '<RESERVE_1232>': 1232, '<RESERVE_1233>': 1233, '<RESERVE_1234>': 1234, '<RESERVE_1235>': 1235, '<RESERVE_1236>': 1236, '<RESERVE_1237>': 1237, '<RESERVE_1238>': 1238, '<RESERVE_1239>': 1239, '<RESERVE_1240>': 1240, '<RESERVE_1241>': 1241, '<RESERVE_1242>': 1242, '<RESERVE_1243>': 1243, '<RESERVE_1244>': 1244, '<RESERVE_1245>': 1245, '<RESERVE_1246>': 1246, '<RESERVE_1247>': 1247, '<RESERVE_1248>': 1248, '<RESERVE_1249>': 1249, '<RESERVE_1250>': 1250, '<RESERVE_1251>': 1251, '<RESERVE_1252>': 1252, '<RESERVE_1253>': 1253, '<RESERVE_1254>': 1254, '<RESERVE_1255>': 1255, '<RESERVE_1256>': 1256, '<RESERVE_1257>': 1257, '<RESERVE_1258>': 1258, '<RESERVE_1259>': 1259, '<RESERVE_1260>': 1260, '<RESERVE_1261>': 1261, '<RESERVE_1262>': 1262, '<RESERVE_1263>': 1263, '<RESERVE_1264>': 1264, '<RESERVE_1265>': 1265, '<RESERVE_1266>': 1266, '<RESERVE_1267>': 1267, '<RESERVE_1268>': 1268, '<RESERVE_1269>': 1269, '<RESERVE_1270>': 1270, '<RESERVE_1271>': 1271, '<RESERVE_1272>': 1272, '<RESERVE_1273>': 1273, '<RESERVE_1274>': 1274, '<RESERVE_1275>': 1275, '<RESERVE_1276>': 1276, '<RESERVE_1277>': 1277, '<RESERVE_1278>': 1278, '<RESERVE_1279>': 1279, '<RESERVE_1280>': 1280, '<RESERVE_1281>': 1281, '<RESERVE_1282>': 1282, '<RESERVE_1283>': 1283, '<RESERVE_1284>': 1284, '<RESERVE_1285>': 1285, '<RESERVE_1286>': 1286, '<RESERVE_1287>': 1287, '<RESERVE_1288>': 1288, '<RESERVE_1289>': 1289, '<RESERVE_1290>': 1290, '<RESERVE_1291>': 1291, '<RESERVE_1292>': 1292, '<RESERVE_1293>': 1293, '<RESERVE_1294>': 1294, '<RESERVE_1295>': 1295, '<RESERVE_1296>': 1296, '<RESERVE_1297>': 1297, '<RESERVE_1298>': 1298, '<RESERVE_1299>': 1299, '<RESERVE_1300>': 1300, '<RESERVE_1301>': 1301, '<RESERVE_1302>': 1302, '<RESERVE_1303>': 1303, '<RESERVE_1304>': 1304, '<RESERVE_1305>': 1305, '<RESERVE_1306>': 1306, '<RESERVE_1307>': 1307, '<RESERVE_1308>': 1308, '<RESERVE_1309>': 1309, '<RESERVE_1310>': 1310, '<RESERVE_1311>': 1311, '<RESERVE_1312>': 1312, '<RESERVE_1313>': 1313, '<RESERVE_1314>': 1314, '<RESERVE_1315>': 1315, '<RESERVE_1316>': 1316, '<RESERVE_1317>': 1317, '<RESERVE_1318>': 1318, '<RESERVE_1319>': 1319, '<RESERVE_1320>': 1320, '<RESERVE_1321>': 1321, '<RESERVE_1322>': 1322, '<RESERVE_1323>': 1323, '<RESERVE_1324>': 1324, '<RESERVE_1325>': 1325, '<RESERVE_1326>': 1326, '<RESERVE_1327>': 1327, '<RESERVE_1328>': 1328, '<RESERVE_1329>': 1329, '<RESERVE_1330>': 1330, '<RESERVE_1331>': 1331, '<RESERVE_1332>': 1332, '<RESERVE_1333>': 1333, '<RESERVE_1334>': 1334, '<RESERVE_1335>': 1335, '<RESERVE_1336>': 1336, '<RESERVE_1337>': 1337, '<RESERVE_1338>': 1338, '<RESERVE_1339>': 1339, '<RESERVE_1340>': 1340, '<RESERVE_1341>': 1341, '<RESERVE_1342>': 1342, '<RESERVE_1343>': 1343, '<RESERVE_1344>': 1344, '<RESERVE_1345>': 1345, '<RESERVE_1346>': 1346, '<RESERVE_1347>': 1347, '<RESERVE_1348>': 1348, '<RESERVE_1349>': 1349, '<RESERVE_1350>': 1350, '<RESERVE_1351>': 1351, '<RESERVE_1352>': 1352, '<RESERVE_1353>': 1353, '<RESERVE_1354>': 1354, '<RESERVE_1355>': 1355, '<RESERVE_1356>': 1356, '<RESERVE_1357>': 1357, '<RESERVE_1358>': 1358, '<RESERVE_1359>': 1359, '<RESERVE_1360>': 1360, '<RESERVE_1361>': 1361, '<RESERVE_1362>': 1362, '<RESERVE_1363>': 1363, '<RESERVE_1364>': 1364, '<RESERVE_1365>': 1365, '<RESERVE_1366>': 1366, '<RESERVE_1367>': 1367, '<RESERVE_1368>': 1368, '<RESERVE_1369>': 1369, '<RESERVE_1370>': 1370, '<RESERVE_1371>': 1371, '<RESERVE_1372>': 1372, '<RESERVE_1373>': 1373, '<RESERVE_1374>': 1374, '<RESERVE_1375>': 1375, '<RESERVE_1376>': 1376, '<RESERVE_1377>': 1377, '<RESERVE_1378>': 1378, '<RESERVE_1379>': 1379, '<RESERVE_1380>': 1380, '<RESERVE_1381>': 1381, '<RESERVE_1382>': 1382, '<RESERVE_1383>': 1383, '<RESERVE_1384>': 1384, '<RESERVE_1385>': 1385, '<RESERVE_1386>': 1386, '<RESERVE_1387>': 1387, '<RESERVE_1388>': 1388, '<RESERVE_1389>': 1389, '<RESERVE_1390>': 1390, '<RESERVE_1391>': 1391, '<RESERVE_1392>': 1392, '<RESERVE_1393>': 1393, '<RESERVE_1394>': 1394, '<RESERVE_1395>': 1395, '<RESERVE_1396>': 1396, '<RESERVE_1397>': 1397, '<RESERVE_1398>': 1398, '<RESERVE_1399>': 1399, '<RESERVE_1400>': 1400, '<RESERVE_1401>': 1401, '<RESERVE_1402>': 1402, '<RESERVE_1403>': 1403, '<RESERVE_1404>': 1404, '<RESERVE_1405>': 1405, '<RESERVE_1406>': 1406, '<RESERVE_1407>': 1407, '<RESERVE_1408>': 1408, '<RESERVE_1409>': 1409, '<RESERVE_1410>': 1410, '<RESERVE_1411>': 1411, '<RESERVE_1412>': 1412, '<RESERVE_1413>': 1413, '<RESERVE_1414>': 1414, '<RESERVE_1415>': 1415, '<RESERVE_1416>': 1416, '<RESERVE_1417>': 1417, '<RESERVE_1418>': 1418, '<RESERVE_1419>': 1419, '<RESERVE_1420>': 1420, '<RESERVE_1421>': 1421, '<RESERVE_1422>': 1422, '<RESERVE_1423>': 1423, '<RESERVE_1424>': 1424, '<RESERVE_1425>': 1425, '<RESERVE_1426>': 1426, '<RESERVE_1427>': 1427, '<RESERVE_1428>': 1428, '<RESERVE_1429>': 1429, '<RESERVE_1430>': 1430, '<RESERVE_1431>': 1431, '<RESERVE_1432>': 1432, '<RESERVE_1433>': 1433, '<RESERVE_1434>': 1434, '<RESERVE_1435>': 1435, '<RESERVE_1436>': 1436, '<RESERVE_1437>': 1437, '<RESERVE_1438>': 1438, '<RESERVE_1439>': 1439, '<RESERVE_1440>': 1440, '<RESERVE_1441>': 1441, '<RESERVE_1442>': 1442, '<RESERVE_1443>': 1443, '<RESERVE_1444>': 1444, '<RESERVE_1445>': 1445, '<RESERVE_1446>': 1446, '<RESERVE_1447>': 1447, '<RESERVE_1448>': 1448, '<RESERVE_1449>': 1449, '<RESERVE_1450>': 1450, '<RESERVE_1451>': 1451, '<RESERVE_1452>': 1452, '<RESERVE_1453>': 1453, '<RESERVE_1454>': 1454, '<RESERVE_1455>': 1455, '<RESERVE_1456>': 1456, '<RESERVE_1457>': 1457, '<RESERVE_1458>': 1458, '<RESERVE_1459>': 1459, '<RESERVE_1460>': 1460, '<RESERVE_1461>': 1461, '<RESERVE_1462>': 1462, '<RESERVE_1463>': 1463, '<RESERVE_1464>': 1464, '<RESERVE_1465>': 1465, '<RESERVE_1466>': 1466, '<RESERVE_1467>': 1467, '<RESERVE_1468>': 1468, '<RESERVE_1469>': 1469, '<RESERVE_1470>': 1470, '<RESERVE_1471>': 1471, '<RESERVE_1472>': 1472, '<RESERVE_1473>': 1473, '<RESERVE_1474>': 1474, '<RESERVE_1475>': 1475, '<RESERVE_1476>': 1476, '<RESERVE_1477>': 1477, '<RESERVE_1478>': 1478, '<RESERVE_1479>': 1479, '<RESERVE_1480>': 1480, '<RESERVE_1481>': 1481, '<RESERVE_1482>': 1482, '<RESERVE_1483>': 1483, '<RESERVE_1484>': 1484, '<RESERVE_1485>': 1485, '<RESERVE_1486>': 1486, '<RESERVE_1487>': 1487, '<RESERVE_1488>': 1488, '<RESERVE_1489>': 1489, '<RESERVE_1490>': 1490, '<RESERVE_1491>': 1491, '<RESERVE_1492>': 1492, '<RESERVE_1493>': 1493, '<RESERVE_1494>': 1494, '<RESERVE_1495>': 1495, '<RESERVE_1496>': 1496, '<RESERVE_1497>': 1497, '<RESERVE_1498>': 1498, '<RESERVE_1499>': 1499, '<RESERVE_1500>': 1500, '<RESERVE_1501>': 1501, '<RESERVE_1502>': 1502, '<RESERVE_1503>': 1503, '<RESERVE_1504>': 1504, '<RESERVE_1505>': 1505, '<RESERVE_1506>': 1506, '<RESERVE_1507>': 1507, '<RESERVE_1508>': 1508, '<RESERVE_1509>': 1509, '<RESERVE_1510>': 1510, '<RESERVE_1511>': 1511, '<RESERVE_1512>': 1512, '<RESERVE_1513>': 1513, '<RESERVE_1514>': 1514, '<RESERVE_1515>': 1515, '<RESERVE_1516>': 1516, '<RESERVE_1517>': 1517, '<RESERVE_1518>': 1518, '<RESERVE_1519>': 1519, '<RESERVE_1520>': 1520, '<RESERVE_1521>': 1521, '<RESERVE_1522>': 1522, '<RESERVE_1523>': 1523, '<RESERVE_1524>': 1524, '<RESERVE_1525>': 1525, '<RESERVE_1526>': 1526, '<RESERVE_1527>': 1527, '<RESERVE_1528>': 1528, '<RESERVE_1529>': 1529, '<RESERVE_1530>': 1530, '<RESERVE_1531>': 1531, '<RESERVE_1532>': 1532, '<RESERVE_1533>': 1533, '<RESERVE_1534>': 1534, '<RESERVE_1535>': 1535, '<RESERVE_1536>': 1536, '<RESERVE_1537>': 1537, '<RESERVE_1538>': 1538, '<RESERVE_1539>': 1539, '<RESERVE_1540>': 1540, '<RESERVE_1541>': 1541, '<RESERVE_1542>': 1542, '<RESERVE_1543>': 1543, '<RESERVE_1544>': 1544, '<RESERVE_1545>': 1545, '<RESERVE_1546>': 1546, '<RESERVE_1547>': 1547, '<RESERVE_1548>': 1548, '<RESERVE_1549>': 1549, '<RESERVE_1550>': 1550, '<RESERVE_1551>': 1551, '<RESERVE_1552>': 1552, '<RESERVE_1553>': 1553, '<RESERVE_1554>': 1554, '<RESERVE_1555>': 1555, '<RESERVE_1556>': 1556, '<RESERVE_1557>': 1557, '<RESERVE_1558>': 1558, '<RESERVE_1559>': 1559, '<RESERVE_1560>': 1560, '<RESERVE_1561>': 1561, '<RESERVE_1562>': 1562, '<RESERVE_1563>': 1563, '<RESERVE_1564>': 1564, '<RESERVE_1565>': 1565, '<RESERVE_1566>': 1566, '<RESERVE_1567>': 1567, '<RESERVE_1568>': 1568, '<RESERVE_1569>': 1569, '<RESERVE_1570>': 1570, '<RESERVE_1571>': 1571, '<RESERVE_1572>': 1572, '<RESERVE_1573>': 1573, '<RESERVE_1574>': 1574, '<RESERVE_1575>': 1575, '<RESERVE_1576>': 1576, '<RESERVE_1577>': 1577, '<RESERVE_1578>': 1578, '<RESERVE_1579>': 1579, '<RESERVE_1580>': 1580, '<RESERVE_1581>': 1581, '<RESERVE_1582>': 1582, '<RESERVE_1583>': 1583, '<RESERVE_1584>': 1584, '<RESERVE_1585>': 1585, '<RESERVE_1586>': 1586, '<RESERVE_1587>': 1587, '<RESERVE_1588>': 1588, '<RESERVE_1589>': 1589, '<RESERVE_1590>': 1590, '<RESERVE_1591>': 1591, '<RESERVE_1592>': 1592, '<RESERVE_1593>': 1593, '<RESERVE_1594>': 1594, '<RESERVE_1595>': 1595, '(0,0)': 1596, '(0,1)': 1597, '(1,0)': 1598, '(1,1)': 1599, '(0,2)': 1600, '(2,0)': 1601, '(1,2)': 1602, '(2,1)': 1603, '(2,2)': 1604, '(0,3)': 1605, '(3,0)': 1606, '(3,1)': 1607, '(2,3)': 1608, '(3,2)': 1609, '(1,3)': 1610, '(3,3)': 1611, '(0,4)': 1612, '(2,4)': 1613, '(4,0)': 1614, '(1,4)': 1615, '(4,1)': 1616, '(4,2)': 1617, '(3,4)': 1618, '(4,3)': 1619, '(4,4)': 1620, '(0,5)': 1621, '(5,0)': 1622, '(5,1)': 1623, '(2,5)': 1624, '(5,2)': 1625, '(5,3)': 1626, '(4,5)': 1627, '(5,4)': 1628, '(1,5)': 1629, '(3,5)': 1630, '(5,5)': 1631, '(0,6)': 1632, '(2,6)': 1633, '(4,6)': 1634, '(6,0)': 1635, '(1,6)': 1636, '(6,1)': 1637, '(6,2)': 1638, '(3,6)': 1639, '(6,3)': 1640, '(6,4)': 1641, '(5,6)': 1642, '(6,5)': 1643, '(6,6)': 1644, '(0,7)': 1645, '(7,0)': 1646, '(7,1)': 1647, '(2,7)': 1648, '(7,2)': 1649, '(7,3)': 1650, '(4,7)': 1651, '(7,4)': 1652, '(7,5)': 1653, '(6,7)': 1654, '(7,6)': 1655, '(1,7)': 1656, '(3,7)': 1657, '(5,7)': 1658, '(7,7)': 1659, '(0,8)': 1660, '(2,8)': 1661, '(4,8)': 1662, '(6,8)': 1663, '(8,0)': 1664, '(1,8)': 1665, '(8,1)': 1666, '(8,2)': 1667, '(3,8)': 1668, '(8,3)': 1669, '(8,4)': 1670, '(5,8)': 1671, '(8,5)': 1672, '(8,6)': 1673, '(7,8)': 1674, '(8,7)': 1675, '(8,8)': 1676, '(0,9)': 1677, '(9,0)': 1678, '(9,1)': 1679, '(2,9)': 1680, '(9,2)': 1681, '(9,3)': 1682, '(4,9)': 1683, '(9,4)': 1684, '(9,5)': 1685, '(6,9)': 1686, '(9,6)': 1687, '(9,7)': 1688, '(8,9)': 1689, '(9,8)': 1690, '(1,9)': 1691, '(3,9)': 1692, '(5,9)': 1693, '(7,9)': 1694, '(9,9)': 1695, '(0,10)': 1696, '(2,10)': 1697, '(4,10)': 1698, '(6,10)': 1699, '(8,10)': 1700, '(10,0)': 1701, '(1,10)': 1702, '(10,1)': 1703, '(10,2)': 1704, '(3,10)': 1705, '(10,3)': 1706, '(10,4)': 1707, '(5,10)': 1708, '(10,5)': 1709, '(10,6)': 1710, '(7,10)': 1711, '(10,7)': 1712, '(10,8)': 1713, '(9,10)': 1714, '(10,9)': 1715, '(10,10)': 1716, '(0,11)': 1717, '(11,0)': 1718, '(11,1)': 1719, '(2,11)': 1720, '(11,2)': 1721, '(11,3)': 1722, '(4,11)': 1723, '(11,4)': 1724, '(11,5)': 1725, '(6,11)': 1726, '(11,6)': 1727, '(11,7)': 1728, '(8,11)': 1729, '(11,8)': 1730, '(11,9)': 1731, '(10,11)': 1732, '(11,10)': 1733, '(1,11)': 1734, '(3,11)': 1735, '(5,11)': 1736, '(7,11)': 1737, '(9,11)': 1738, '(11,11)': 1739, '(0,12)': 1740, '(2,12)': 1741, '(4,12)': 1742, '(6,12)': 1743, '(8,12)': 1744, '(10,12)': 1745, '(12,0)': 1746, '(1,12)': 1747, '(12,1)': 1748, '(12,2)': 1749, '(3,12)': 1750, '(12,3)': 1751, '(12,4)': 1752, '(5,12)': 1753, '(12,5)': 1754, '(12,6)': 1755, '(7,12)': 1756, '(12,7)': 1757, '(12,8)': 1758, '(9,12)': 1759, '(12,9)': 1760, '(12,10)': 1761, '(11,12)': 1762, '(12,11)': 1763, '(12,12)': 1764, '(0,13)': 1765, '(13,0)': 1766, '(13,1)': 1767, '(2,13)': 1768, '(13,2)': 1769, '(13,3)': 1770, '(4,13)': 1771, '(13,4)': 1772, '(13,5)': 1773, '(6,13)': 1774, '(13,6)': 1775, '(13,7)': 1776, '(8,13)': 1777, '(13,8)': 1778, '(13,9)': 1779, '(10,13)': 1780, '(13,10)': 1781, '(13,11)': 1782, '(12,13)': 1783, '(13,12)': 1784, '(1,13)': 1785, '(3,13)': 1786, '(5,13)': 1787, '(7,13)': 1788, '(9,13)': 1789, '(11,13)': 1790, '(13,13)': 1791, '(0,14)': 1792, '(2,14)': 1793, '(4,14)': 1794, '(6,14)': 1795, '(8,14)': 1796, '(10,14)': 1797, '(12,14)': 1798, '(14,0)': 1799, '(1,14)': 1800, '(14,1)': 1801, '(14,2)': 1802, '(3,14)': 1803, '(14,3)': 1804, '(14,4)': 1805, '(5,14)': 1806, '(14,5)': 1807, '(14,6)': 1808, '(7,14)': 1809, '(14,7)': 1810, '(14,8)': 1811, '(9,14)': 1812, '(14,9)': 1813, '(14,10)': 1814, '(11,14)': 1815, '(14,11)': 1816, '(14,12)': 1817, '(13,14)': 1818, '(14,13)': 1819, '(14,14)': 1820, '(0,15)': 1821, '(15,0)': 1822, '(15,1)': 1823, '(2,15)': 1824, '(15,2)': 1825, '(15,3)': 1826, '(4,15)': 1827, '(15,4)': 1828, '(15,5)': 1829, '(6,15)': 1830, '(15,6)': 1831, '(15,7)': 1832, '(8,15)': 1833, '(15,8)': 1834, '(15,9)': 1835, '(10,15)': 1836, '(15,10)': 1837, '(15,11)': 1838, '(12,15)': 1839, '(15,12)': 1840, '(15,13)': 1841, '(14,15)': 1842, '(15,14)': 1843, '(1,15)': 1844, '(3,15)': 1845, '(5,15)': 1846, '(7,15)': 1847, '(9,15)': 1848, '(11,15)': 1849, '(13,15)': 1850, '(15,15)': 1851, '(0,16)': 1852, '(2,16)': 1853, '(4,16)': 1854, '(6,16)': 1855, '(8,16)': 1856, '(10,16)': 1857, '(12,16)': 1858, '(14,16)': 1859, '(16,0)': 1860, '(1,16)': 1861, '(16,1)': 1862, '(16,2)': 1863, '(3,16)': 1864, '(16,3)': 1865, '(16,4)': 1866, '(5,16)': 1867, '(16,5)': 1868, '(16,6)': 1869, '(7,16)': 1870, '(16,7)': 1871, '(16,8)': 1872, '(9,16)': 1873, '(16,9)': 1874, '(16,10)': 1875, '(11,16)': 1876, '(16,11)': 1877, '(16,12)': 1878, '(13,16)': 1879, '(16,13)': 1880, '(16,14)': 1881, '(15,16)': 1882, '(16,15)': 1883, '(16,16)': 1884, '(0,17)': 1885, '(17,0)': 1886, '(17,1)': 1887, '(2,17)': 1888, '(17,2)': 1889, '(17,3)': 1890, '(4,17)': 1891, '(17,4)': 1892, '(17,5)': 1893, '(6,17)': 1894, '(17,6)': 1895, '(17,7)': 1896, '(8,17)': 1897, '(17,8)': 1898, '(17,9)': 1899, '(10,17)': 1900, '(17,10)': 1901, '(17,11)': 1902, '(12,17)': 1903, '(17,12)': 1904, '(17,13)': 1905, '(14,17)': 1906, '(17,14)': 1907, '(17,15)': 1908, '(16,17)': 1909, '(17,16)': 1910, '(1,17)': 1911, '(3,17)': 1912, '(5,17)': 1913, '(7,17)': 1914, '(9,17)': 1915, '(11,17)': 1916, '(13,17)': 1917, '(15,17)': 1918, '(17,17)': 1919, '(0,18)': 1920, '(2,18)': 1921, '(4,18)': 1922, '(6,18)': 1923, '(8,18)': 1924, '(10,18)': 1925, '(12,18)': 1926, '(14,18)': 1927, '(16,18)': 1928, '(18,0)': 1929, '(1,18)': 1930, '(18,1)': 1931, '(18,2)': 1932, '(3,18)': 1933, '(18,3)': 1934, '(18,4)': 1935, '(5,18)': 1936, '(18,5)': 1937, '(18,6)': 1938, '(7,18)': 1939, '(18,7)': 1940, '(18,8)': 1941, '(9,18)': 1942, '(18,9)': 1943, '(18,10)': 1944, '(11,18)': 1945, '(18,11)': 1946, '(18,12)': 1947, '(13,18)': 1948, '(18,13)': 1949, '(18,14)': 1950, '(15,18)': 1951, '(18,15)': 1952, '(18,16)': 1953, '(17,18)': 1954, '(18,17)': 1955, '(18,18)': 1956, '(0,19)': 1957, '(19,0)': 1958, '(19,1)': 1959, '(2,19)': 1960, '(19,2)': 1961, '(19,3)': 1962, '(4,19)': 1963, '(19,4)': 1964, '(19,5)': 1965, '(6,19)': 1966, '(19,6)': 1967, '(19,7)': 1968, '(8,19)': 1969, '(19,8)': 1970, '(19,9)': 1971, '(10,19)': 1972, '(19,10)': 1973, '(19,11)': 1974, '(12,19)': 1975, '(19,12)': 1976, '(19,13)': 1977, '(14,19)': 1978, '(19,14)': 1979, '(19,15)': 1980, '(16,19)': 1981, '(19,16)': 1982, '(19,17)': 1983, '(18,19)': 1984, '(19,18)': 1985, '(1,19)': 1986, '(3,19)': 1987, '(5,19)': 1988, '(7,19)': 1989, '(9,19)': 1990, '(11,19)': 1991, '(13,19)': 1992, '(15,19)': 1993, '(17,19)': 1994, '(19,19)': 1995, '(0,20)': 1996, '(2,20)': 1997, '(4,20)': 1998, '(6,20)': 1999, '(8,20)': 2000, '(10,20)': 2001, '(12,20)': 2002, '(14,20)': 2003, '(16,20)': 2004, '(18,20)': 2005, '(20,0)': 2006, '(1,20)': 2007, '(20,1)': 2008, '(20,2)': 2009, '(3,20)': 2010, '(20,3)': 2011, '(20,4)': 2012, '(5,20)': 2013, '(20,5)': 2014, '(20,6)': 2015, '(7,20)': 2016, '(20,7)': 2017, '(20,8)': 2018, '(9,20)': 2019, '(20,9)': 2020, '(20,10)': 2021, '(11,20)': 2022, '(20,11)': 2023, '(20,12)': 2024, '(13,20)': 2025, '(20,13)': 2026, '(20,14)': 2027, '(15,20)': 2028, '(20,15)': 2029, '(20,16)': 2030, '(17,20)': 2031, '(20,17)': 2032, '(20,18)': 2033, '(19,20)': 2034, '(20,19)': 2035, '(20,20)': 2036, '(0,21)': 2037, '(21,0)': 2038, '(21,1)': 2039, '(2,21)': 2040, '(21,2)': 2041, '(21,3)': 2042, '(4,21)': 2043, '(21,4)': 2044, '(21,5)': 2045, '(6,21)': 2046, '(21,6)': 2047, '(21,7)': 2048, '(8,21)': 2049, '(21,8)': 2050, '(21,9)': 2051, '(10,21)': 2052, '(21,10)': 2053, '(21,11)': 2054, '(12,21)': 2055, '(21,12)': 2056, '(21,13)': 2057, '(14,21)': 2058, '(21,14)': 2059, '(21,15)': 2060, '(16,21)': 2061, '(21,16)': 2062, '(21,17)': 2063, '(18,21)': 2064, '(21,18)': 2065, '(21,19)': 2066, '(20,21)': 2067, '(21,20)': 2068, '(1,21)': 2069, '(3,21)': 2070, '(5,21)': 2071, '(7,21)': 2072, '(9,21)': 2073, '(11,21)': 2074, '(13,21)': 2075, '(15,21)': 2076, '(17,21)': 2077, '(19,21)': 2078, '(21,21)': 2079, '(0,22)': 2080, '(2,22)': 2081, '(4,22)': 2082, '(6,22)': 2083, '(8,22)': 2084, '(10,22)': 2085, '(12,22)': 2086, '(14,22)': 2087, '(16,22)': 2088, '(18,22)': 2089, '(20,22)': 2090, '(22,0)': 2091, '(1,22)': 2092, '(22,1)': 2093, '(22,2)': 2094, '(3,22)': 2095, '(22,3)': 2096, '(22,4)': 2097, '(5,22)': 2098, '(22,5)': 2099, '(22,6)': 2100, '(7,22)': 2101, '(22,7)': 2102, '(22,8)': 2103, '(9,22)': 2104, '(22,9)': 2105, '(22,10)': 2106, '(11,22)': 2107, '(22,11)': 2108, '(22,12)': 2109, '(13,22)': 2110, '(22,13)': 2111, '(22,14)': 2112, '(15,22)': 2113, '(22,15)': 2114, '(22,16)': 2115, '(17,22)': 2116, '(22,17)': 2117, '(22,18)': 2118, '(19,22)': 2119, '(22,19)': 2120, '(22,20)': 2121, '(21,22)': 2122, '(22,21)': 2123, '(22,22)': 2124, '(0,23)': 2125, '(23,0)': 2126, '(23,1)': 2127, '(2,23)': 2128, '(23,2)': 2129, '(23,3)': 2130, '(4,23)': 2131, '(23,4)': 2132, '(23,5)': 2133, '(6,23)': 2134, '(23,6)': 2135, '(23,7)': 2136, '(8,23)': 2137, '(23,8)': 2138, '(23,9)': 2139, '(10,23)': 2140, '(23,10)': 2141, '(23,11)': 2142, '(12,23)': 2143, '(23,12)': 2144, '(23,13)': 2145, '(14,23)': 2146, '(23,14)': 2147, '(23,15)': 2148, '(16,23)': 2149, '(23,16)': 2150, '(23,17)': 2151, '(18,23)': 2152, '(23,18)': 2153, '(23,19)': 2154, '(20,23)': 2155, '(23,20)': 2156, '(23,21)': 2157, '(22,23)': 2158, '(23,22)': 2159, '(1,23)': 2160, '(3,23)': 2161, '(5,23)': 2162, '(7,23)': 2163, '(9,23)': 2164, '(11,23)': 2165, '(13,23)': 2166, '(15,23)': 2167, '(17,23)': 2168, '(19,23)': 2169, '(21,23)': 2170, '(23,23)': 2171, '(0,24)': 2172, '(2,24)': 2173, '(4,24)': 2174, '(6,24)': 2175, '(8,24)': 2176, '(10,24)': 2177, '(12,24)': 2178, '(14,24)': 2179, '(16,24)': 2180, '(18,24)': 2181, '(20,24)': 2182, '(22,24)': 2183, '(24,0)': 2184, '(1,24)': 2185, '(24,1)': 2186, '(24,2)': 2187, '(3,24)': 2188, '(24,3)': 2189, '(24,4)': 2190, '(5,24)': 2191, '(24,5)': 2192, '(24,6)': 2193, '(7,24)': 2194, '(24,7)': 2195, '(24,8)': 2196, '(9,24)': 2197, '(24,9)': 2198, '(24,10)': 2199, '(11,24)': 2200, '(24,11)': 2201, '(24,12)': 2202, '(13,24)': 2203, '(24,13)': 2204, '(24,14)': 2205, '(15,24)': 2206, '(24,15)': 2207, '(24,16)': 2208, '(17,24)': 2209, '(24,17)': 2210, '(24,18)': 2211, '(19,24)': 2212, '(24,19)': 2213, '(24,20)': 2214, '(21,24)': 2215, '(24,21)': 2216, '(24,22)': 2217, '(23,24)': 2218, '(24,23)': 2219, '(24,24)': 2220, '(0,25)': 2221, '(25,0)': 2222, '(25,1)': 2223, '(2,25)': 2224, '(25,2)': 2225, '(25,3)': 2226, '(4,25)': 2227, '(25,4)': 2228, '(25,5)': 2229, '(6,25)': 2230, '(25,6)': 2231, '(25,7)': 2232, '(8,25)': 2233, '(25,8)': 2234, '(25,9)': 2235, '(10,25)': 2236, '(25,10)': 2237, '(25,11)': 2238, '(12,25)': 2239, '(25,12)': 2240, '(25,13)': 2241, '(14,25)': 2242, '(25,14)': 2243, '(25,15)': 2244, '(16,25)': 2245, '(25,16)': 2246, '(25,17)': 2247, '(18,25)': 2248, '(25,18)': 2249, '(25,19)': 2250, '(20,25)': 2251, '(25,20)': 2252, '(25,21)': 2253, '(22,25)': 2254, '(25,22)': 2255, '(25,23)': 2256, '(24,25)': 2257, '(25,24)': 2258, '(1,25)': 2259, '(3,25)': 2260, '(5,25)': 2261, '(7,25)': 2262, '(9,25)': 2263, '(11,25)': 2264, '(13,25)': 2265, '(15,25)': 2266, '(17,25)': 2267, '(19,25)': 2268, '(21,25)': 2269, '(23,25)': 2270, '(25,25)': 2271, '(0,26)': 2272, '(2,26)': 2273, '(4,26)': 2274, '(6,26)': 2275, '(8,26)': 2276, '(10,26)': 2277, '(12,26)': 2278, '(14,26)': 2279, '(16,26)': 2280, '(18,26)': 2281, '(20,26)': 2282, '(22,26)': 2283, '(24,26)': 2284, '(26,0)': 2285, '(1,26)': 2286, '(26,1)': 2287, '(26,2)': 2288, '(3,26)': 2289, '(26,3)': 2290, '(26,4)': 2291, '(5,26)': 2292, '(26,5)': 2293, '(26,6)': 2294, '(7,26)': 2295, '(26,7)': 2296, '(26,8)': 2297, '(9,26)': 2298, '(26,9)': 2299, '(26,10)': 2300, '(11,26)': 2301, '(26,11)': 2302, '(26,12)': 2303, '(13,26)': 2304, '(26,13)': 2305, '(26,14)': 2306, '(15,26)': 2307, '(26,15)': 2308, '(26,16)': 2309, '(17,26)': 2310, '(26,17)': 2311, '(26,18)': 2312, '(19,26)': 2313, '(26,19)': 2314, '(26,20)': 2315, '(21,26)': 2316, '(26,21)': 2317, '(26,22)': 2318, '(23,26)': 2319, '(26,23)': 2320, '(26,24)': 2321, '(25,26)': 2322, '(26,25)': 2323, '(26,26)': 2324, '(0,27)': 2325, '(27,0)': 2326, '(27,1)': 2327, '(2,27)': 2328, '(27,2)': 2329, '(27,3)': 2330, '(4,27)': 2331, '(27,4)': 2332, '(27,5)': 2333, '(6,27)': 2334, '(27,6)': 2335, '(27,7)': 2336, '(8,27)': 2337, '(27,8)': 2338, '(27,9)': 2339, '(10,27)': 2340, '(27,10)': 2341, '(27,11)': 2342, '(12,27)': 2343, '(27,12)': 2344, '(27,13)': 2345, '(14,27)': 2346, '(27,14)': 2347, '(27,15)': 2348, '(16,27)': 2349, '(27,16)': 2350, '(27,17)': 2351, '(18,27)': 2352, '(27,18)': 2353, '(27,19)': 2354, '(20,27)': 2355, '(27,20)': 2356, '(27,21)': 2357, '(22,27)': 2358, '(27,22)': 2359, '(27,23)': 2360, '(24,27)': 2361, '(27,24)': 2362, '(27,25)': 2363, '(26,27)': 2364, '(27,26)': 2365, '(1,27)': 2366, '(3,27)': 2367, '(5,27)': 2368, '(7,27)': 2369, '(9,27)': 2370, '(11,27)': 2371, '(13,27)': 2372, '(15,27)': 2373, '(17,27)': 2374, '(19,27)': 2375, '(21,27)': 2376, '(23,27)': 2377, '(25,27)': 2378, '(27,27)': 2379, '(0,28)': 2380, '(2,28)': 2381, '(4,28)': 2382, '(6,28)': 2383, '(8,28)': 2384, '(10,28)': 2385, '(12,28)': 2386, '(14,28)': 2387, '(16,28)': 2388, '(18,28)': 2389, '(20,28)': 2390, '(22,28)': 2391, '(24,28)': 2392, '(26,28)': 2393, '(28,0)': 2394, '(1,28)': 2395, '(28,1)': 2396, '(28,2)': 2397, '(3,28)': 2398, '(28,3)': 2399, '(28,4)': 2400, '(5,28)': 2401, '(28,5)': 2402, '(28,6)': 2403, '(7,28)': 2404, '(28,7)': 2405, '(28,8)': 2406, '(9,28)': 2407, '(28,9)': 2408, '(28,10)': 2409, '(11,28)': 2410, '(28,11)': 2411, '(28,12)': 2412, '(13,28)': 2413, '(28,13)': 2414, '(28,14)': 2415, '(15,28)': 2416, '(28,15)': 2417, '(28,16)': 2418, '(17,28)': 2419, '(28,17)': 2420, '(28,18)': 2421, '(19,28)': 2422, '(28,19)': 2423, '(28,20)': 2424, '(21,28)': 2425, '(28,21)': 2426, '(28,22)': 2427, '(23,28)': 2428, '(28,23)': 2429, '(28,24)': 2430, '(25,28)': 2431, '(28,25)': 2432, '(28,26)': 2433, '(27,28)': 2434, '(28,27)': 2435, '(28,28)': 2436, '(0,29)': 2437, '(29,0)': 2438, '(29,1)': 2439, '(2,29)': 2440, '(29,2)': 2441, '(29,3)': 2442, '(4,29)': 2443, '(29,4)': 2444, '(29,5)': 2445, '(6,29)': 2446, '(29,6)': 2447, '(29,7)': 2448, '(8,29)': 2449, '(29,8)': 2450, '(29,9)': 2451, '(10,29)': 2452, '(29,10)': 2453, '(29,11)': 2454, '(12,29)': 2455, '(29,12)': 2456, '(29,13)': 2457, '(14,29)': 2458, '(29,14)': 2459, '(29,15)': 2460, '(16,29)': 2461, '(29,16)': 2462, '(29,17)': 2463, '(18,29)': 2464, '(29,18)': 2465, '(29,19)': 2466, '(20,29)': 2467, '(29,20)': 2468, '(29,21)': 2469, '(22,29)': 2470, '(29,22)': 2471, '(29,23)': 2472, '(24,29)': 2473, '(29,24)': 2474, '(29,25)': 2475, '(26,29)': 2476, '(29,26)': 2477, '(29,27)': 2478, '(28,29)': 2479, '(29,28)': 2480, '(1,29)': 2481, '(3,29)': 2482, '(5,29)': 2483, '(7,29)': 2484, '(9,29)': 2485, '(11,29)': 2486, '(13,29)': 2487, '(15,29)': 2488, '(17,29)': 2489, '(19,29)': 2490, '(21,29)': 2491, '(23,29)': 2492, '(25,29)': 2493, '(27,29)': 2494, '(29,29)': 2495, '(0,30)': 2496, '(2,30)': 2497, '(4,30)': 2498, '(6,30)': 2499, '(8,30)': 2500, '(10,30)': 2501, '(12,30)': 2502, '(14,30)': 2503, '(16,30)': 2504, '(18,30)': 2505, '(20,30)': 2506, '(22,30)': 2507, '(24,30)': 2508, '(26,30)': 2509, '(28,30)': 2510, '(30,0)': 2511, '(1,30)': 2512, '(30,1)': 2513, '(30,2)': 2514, '(3,30)': 2515, '(30,3)': 2516, '(30,4)': 2517, '(5,30)': 2518, '(30,5)': 2519, '(30,6)': 2520, '(7,30)': 2521, '(30,7)': 2522, '(30,8)': 2523, '(9,30)': 2524, '(30,9)': 2525, '(30,10)': 2526, '(11,30)': 2527, '(30,11)': 2528, '(30,12)': 2529, '(13,30)': 2530, '(30,13)': 2531, '(30,14)': 2532, '(15,30)': 2533, '(30,15)': 2534, '(30,16)': 2535, '(17,30)': 2536, '(30,17)': 2537, '(30,18)': 2538, '(19,30)': 2539, '(30,19)': 2540, '(30,20)': 2541, '(21,30)': 2542, '(30,21)': 2543, '(30,22)': 2544, '(23,30)': 2545, '(30,23)': 2546, '(30,24)': 2547, '(25,30)': 2548, '(30,25)': 2549, '(30,26)': 2550, '(27,30)': 2551, '(30,27)': 2552, '(30,28)': 2553, '(29,30)': 2554, '(30,29)': 2555, '(30,30)': 2556, '(0,31)': 2557, '(31,0)': 2558, '(31,1)': 2559, '(2,31)': 2560, '(31,2)': 2561, '(31,3)': 2562, '(4,31)': 2563, '(31,4)': 2564, '(31,5)': 2565, '(6,31)': 2566, '(31,6)': 2567, '(31,7)': 2568, '(8,31)': 2569, '(31,8)': 2570, '(31,9)': 2571, '(10,31)': 2572, '(31,10)': 2573, '(31,11)': 2574, '(12,31)': 2575, '(31,12)': 2576, '(31,13)': 2577, '(14,31)': 2578, '(31,14)': 2579, '(31,15)': 2580, '(16,31)': 2581, '(31,16)': 2582, '(31,17)': 2583, '(18,31)': 2584, '(31,18)': 2585, '(31,19)': 2586, '(20,31)': 2587, '(31,20)': 2588, '(31,21)': 2589, '(22,31)': 2590, '(31,22)': 2591, '(31,23)': 2592, '(24,31)': 2593, '(31,24)': 2594, '(31,25)': 2595, '(26,31)': 2596, '(31,26)': 2597, '(31,27)': 2598, '(28,31)': 2599, '(31,28)': 2600, '(31,29)': 2601, '(30,31)': 2602, '(31,30)': 2603, '(1,31)': 2604, '(3,31)': 2605, '(5,31)': 2606, '(7,31)': 2607, '(9,31)': 2608, '(11,31)': 2609, '(13,31)': 2610, '(15,31)': 2611, '(17,31)': 2612, '(19,31)': 2613, '(21,31)': 2614, '(23,31)': 2615, '(25,31)': 2616, '(27,31)': 2617, '(29,31)': 2618, '(31,31)': 2619, '(0,32)': 2620, '(2,32)': 2621, '(4,32)': 2622, '(6,32)': 2623, '(8,32)': 2624, '(10,32)': 2625, '(12,32)': 2626, '(14,32)': 2627, '(16,32)': 2628, '(18,32)': 2629, '(20,32)': 2630, '(22,32)': 2631, '(24,32)': 2632, '(26,32)': 2633, '(28,32)': 2634, '(30,32)': 2635, '(32,0)': 2636, '(1,32)': 2637, '(32,1)': 2638, '(32,2)': 2639, '(3,32)': 2640, '(32,3)': 2641, '(32,4)': 2642, '(5,32)': 2643, '(32,5)': 2644, '(32,6)': 2645, '(7,32)': 2646, '(32,7)': 2647, '(32,8)': 2648, '(9,32)': 2649, '(32,9)': 2650, '(32,10)': 2651, '(11,32)': 2652, '(32,11)': 2653, '(32,12)': 2654, '(13,32)': 2655, '(32,13)': 2656, '(32,14)': 2657, '(15,32)': 2658, '(32,15)': 2659, '(32,16)': 2660, '(17,32)': 2661, '(32,17)': 2662, '(32,18)': 2663, '(19,32)': 2664, '(32,19)': 2665, '(32,20)': 2666, '(21,32)': 2667, '(32,21)': 2668, '(32,22)': 2669, '(23,32)': 2670, '(32,23)': 2671, '(32,24)': 2672, '(25,32)': 2673, '(32,25)': 2674, '(32,26)': 2675, '(27,32)': 2676, '(32,27)': 2677, '(32,28)': 2678, '(29,32)': 2679, '(32,29)': 2680, '(32,30)': 2681, '(31,32)': 2682, '(32,31)': 2683, '(32,32)': 2684, '(0,33)': 2685, '(33,0)': 2686, '(33,1)': 2687, '(2,33)': 2688, '(33,2)': 2689, '(33,3)': 2690, '(4,33)': 2691, '(33,4)': 2692, '(33,5)': 2693, '(6,33)': 2694, '(33,6)': 2695, '(33,7)': 2696, '(8,33)': 2697, '(33,8)': 2698, '(33,9)': 2699, '(10,33)': 2700, '(33,10)': 2701, '(33,11)': 2702, '(12,33)': 2703, '(33,12)': 2704, '(33,13)': 2705, '(14,33)': 2706, '(33,14)': 2707, '(33,15)': 2708, '(16,33)': 2709, '(33,16)': 2710, '(33,17)': 2711, '(18,33)': 2712, '(33,18)': 2713, '(33,19)': 2714, '(20,33)': 2715, '(33,20)': 2716, '(33,21)': 2717, '(22,33)': 2718, '(33,22)': 2719, '(33,23)': 2720, '(24,33)': 2721, '(33,24)': 2722, '(33,25)': 2723, '(26,33)': 2724, '(33,26)': 2725, '(33,27)': 2726, '(28,33)': 2727, '(33,28)': 2728, '(33,29)': 2729, '(30,33)': 2730, '(33,30)': 2731, '(33,31)': 2732, '(32,33)': 2733, '(33,32)': 2734, '(1,33)': 2735, '(3,33)': 2736, '(5,33)': 2737, '(7,33)': 2738, '(9,33)': 2739, '(11,33)': 2740, '(13,33)': 2741, '(15,33)': 2742, '(17,33)': 2743, '(19,33)': 2744, '(21,33)': 2745, '(23,33)': 2746, '(25,33)': 2747, '(27,33)': 2748, '(29,33)': 2749, '(31,33)': 2750, '(33,33)': 2751, '(0,34)': 2752, '(2,34)': 2753, '(4,34)': 2754, '(6,34)': 2755, '(8,34)': 2756, '(10,34)': 2757, '(12,34)': 2758, '(14,34)': 2759, '(16,34)': 2760, '(18,34)': 2761, '(20,34)': 2762, '(22,34)': 2763, '(24,34)': 2764, '(26,34)': 2765, '(28,34)': 2766, '(30,34)': 2767, '(32,34)': 2768, '(34,0)': 2769, '(1,34)': 2770, '(34,1)': 2771, '(34,2)': 2772, '(3,34)': 2773, '(34,3)': 2774, '(34,4)': 2775, '(5,34)': 2776, '(34,5)': 2777, '(34,6)': 2778, '(7,34)': 2779, '(34,7)': 2780, '(34,8)': 2781, '(9,34)': 2782, '(34,9)': 2783, '(34,10)': 2784, '(11,34)': 2785, '(34,11)': 2786, '(34,12)': 2787, '(13,34)': 2788, '(34,13)': 2789, '(34,14)': 2790, '(15,34)': 2791, '(34,15)': 2792, '(34,16)': 2793, '(17,34)': 2794, '(34,17)': 2795, '(34,18)': 2796, '(19,34)': 2797, '(34,19)': 2798, '(34,20)': 2799, '(21,34)': 2800, '(34,21)': 2801, '(34,22)': 2802, '(23,34)': 2803, '(34,23)': 2804, '(34,24)': 2805, '(25,34)': 2806, '(34,25)': 2807, '(34,26)': 2808, '(27,34)': 2809, '(34,27)': 2810, '(34,28)': 2811, '(29,34)': 2812, '(34,29)': 2813, '(34,30)': 2814, '(31,34)': 2815, '(34,31)': 2816, '(34,32)': 2817, '(33,34)': 2818, '(34,33)': 2819, '(34,34)': 2820, '(0,35)': 2821, '(35,0)': 2822, '(35,1)': 2823, '(2,35)': 2824, '(35,2)': 2825, '(35,3)': 2826, '(4,35)': 2827, '(35,4)': 2828, '(35,5)': 2829, '(6,35)': 2830, '(35,6)': 2831, '(35,7)': 2832, '(8,35)': 2833, '(35,8)': 2834, '(35,9)': 2835, '(10,35)': 2836, '(35,10)': 2837, '(35,11)': 2838, '(12,35)': 2839, '(35,12)': 2840, '(35,13)': 2841, '(14,35)': 2842, '(35,14)': 2843, '(35,15)': 2844, '(16,35)': 2845, '(35,16)': 2846, '(35,17)': 2847, '(18,35)': 2848, '(35,18)': 2849, '(35,19)': 2850, '(20,35)': 2851, '(35,20)': 2852, '(35,21)': 2853, '(22,35)': 2854, '(35,22)': 2855, '(35,23)': 2856, '(24,35)': 2857, '(35,24)': 2858, '(35,25)': 2859, '(26,35)': 2860, '(35,26)': 2861, '(35,27)': 2862, '(28,35)': 2863, '(35,28)': 2864, '(35,29)': 2865, '(30,35)': 2866, '(35,30)': 2867, '(35,31)': 2868, '(32,35)': 2869, '(35,32)': 2870, '(35,33)': 2871, '(34,35)': 2872, '(35,34)': 2873, '(1,35)': 2874, '(3,35)': 2875, '(5,35)': 2876, '(7,35)': 2877, '(9,35)': 2878, '(11,35)': 2879, '(13,35)': 2880, '(15,35)': 2881, '(17,35)': 2882, '(19,35)': 2883, '(21,35)': 2884, '(23,35)': 2885, '(25,35)': 2886, '(27,35)': 2887, '(29,35)': 2888, '(31,35)': 2889, '(33,35)': 2890, '(35,35)': 2891, '(0,36)': 2892, '(2,36)': 2893, '(4,36)': 2894, '(6,36)': 2895, '(8,36)': 2896, '(10,36)': 2897, '(12,36)': 2898, '(14,36)': 2899, '(16,36)': 2900, '(18,36)': 2901, '(20,36)': 2902, '(22,36)': 2903, '(24,36)': 2904, '(26,36)': 2905, '(28,36)': 2906, '(30,36)': 2907, '(32,36)': 2908, '(34,36)': 2909, '(36,0)': 2910, '(1,36)': 2911, '(36,1)': 2912, '(36,2)': 2913, '(3,36)': 2914, '(36,3)': 2915, '(36,4)': 2916, '(5,36)': 2917, '(36,5)': 2918, '(36,6)': 2919, '(7,36)': 2920, '(36,7)': 2921, '(36,8)': 2922, '(9,36)': 2923, '(36,9)': 2924, '(36,10)': 2925, '(11,36)': 2926, '(36,11)': 2927, '(36,12)': 2928, '(13,36)': 2929, '(36,13)': 2930, '(36,14)': 2931, '(15,36)': 2932, '(36,15)': 2933, '(36,16)': 2934, '(17,36)': 2935, '(36,17)': 2936, '(36,18)': 2937, '(19,36)': 2938, '(36,19)': 2939, '(36,20)': 2940, '(21,36)': 2941, '(36,21)': 2942, '(36,22)': 2943, '(23,36)': 2944, '(36,23)': 2945, '(36,24)': 2946, '(25,36)': 2947, '(36,25)': 2948, '(36,26)': 2949, '(27,36)': 2950, '(36,27)': 2951, '(36,28)': 2952, '(29,36)': 2953, '(36,29)': 2954, '(36,30)': 2955, '(31,36)': 2956, '(36,31)': 2957, '(36,32)': 2958, '(33,36)': 2959, '(36,33)': 2960, '(36,34)': 2961, '(35,36)': 2962, '(36,35)': 2963, '(36,36)': 2964, '(0,37)': 2965, '(37,0)': 2966, '(37,1)': 2967, '(2,37)': 2968, '(37,2)': 2969, '(37,3)': 2970, '(4,37)': 2971, '(37,4)': 2972, '(37,5)': 2973, '(6,37)': 2974, '(37,6)': 2975, '(37,7)': 2976, '(8,37)': 2977, '(37,8)': 2978, '(37,9)': 2979, '(10,37)': 2980, '(37,10)': 2981, '(37,11)': 2982, '(12,37)': 2983, '(37,12)': 2984, '(37,13)': 2985, '(14,37)': 2986, '(37,14)': 2987, '(37,15)': 2988, '(16,37)': 2989, '(37,16)': 2990, '(37,17)': 2991, '(18,37)': 2992, '(37,18)': 2993, '(37,19)': 2994, '(20,37)': 2995, '(37,20)': 2996, '(37,21)': 2997, '(22,37)': 2998, '(37,22)': 2999, '(37,23)': 3000, '(24,37)': 3001, '(37,24)': 3002, '(37,25)': 3003, '(26,37)': 3004, '(37,26)': 3005, '(37,27)': 3006, '(28,37)': 3007, '(37,28)': 3008, '(37,29)': 3009, '(30,37)': 3010, '(37,30)': 3011, '(37,31)': 3012, '(32,37)': 3013, '(37,32)': 3014, '(37,33)': 3015, '(34,37)': 3016, '(37,34)': 3017, '(37,35)': 3018, '(36,37)': 3019, '(37,36)': 3020, '(1,37)': 3021, '(3,37)': 3022, '(5,37)': 3023, '(7,37)': 3024, '(9,37)': 3025, '(11,37)': 3026, '(13,37)': 3027, '(15,37)': 3028, '(17,37)': 3029, '(19,37)': 3030, '(21,37)': 3031, '(23,37)': 3032, '(25,37)': 3033, '(27,37)': 3034, '(29,37)': 3035, '(31,37)': 3036, '(33,37)': 3037, '(35,37)': 3038, '(37,37)': 3039, '(0,38)': 3040, '(2,38)': 3041, '(4,38)': 3042, '(6,38)': 3043, '(8,38)': 3044, '(10,38)': 3045, '(12,38)': 3046, '(14,38)': 3047, '(16,38)': 3048, '(18,38)': 3049, '(20,38)': 3050, '(22,38)': 3051, '(24,38)': 3052, '(26,38)': 3053, '(28,38)': 3054, '(30,38)': 3055, '(32,38)': 3056, '(34,38)': 3057, '(36,38)': 3058, '(38,0)': 3059, '(1,38)': 3060, '(38,1)': 3061, '(38,2)': 3062, '(3,38)': 3063, '(38,3)': 3064, '(38,4)': 3065, '(5,38)': 3066, '(38,5)': 3067, '(38,6)': 3068, '(7,38)': 3069, '(38,7)': 3070, '(38,8)': 3071, '(9,38)': 3072, '(38,9)': 3073, '(38,10)': 3074, '(11,38)': 3075, '(38,11)': 3076, '(38,12)': 3077, '(13,38)': 3078, '(38,13)': 3079, '(38,14)': 3080, '(15,38)': 3081, '(38,15)': 3082, '(38,16)': 3083, '(17,38)': 3084, '(38,17)': 3085, '(38,18)': 3086, '(19,38)': 3087, '(38,19)': 3088, '(38,20)': 3089, '(21,38)': 3090, '(38,21)': 3091, '(38,22)': 3092, '(23,38)': 3093, '(38,23)': 3094, '(38,24)': 3095, '(25,38)': 3096, '(38,25)': 3097, '(38,26)': 3098, '(27,38)': 3099, '(38,27)': 3100, '(38,28)': 3101, '(29,38)': 3102, '(38,29)': 3103, '(38,30)': 3104, '(31,38)': 3105, '(38,31)': 3106, '(38,32)': 3107, '(33,38)': 3108, '(38,33)': 3109, '(38,34)': 3110, '(35,38)': 3111, '(38,35)': 3112, '(38,36)': 3113, '(37,38)': 3114, '(38,37)': 3115, '(38,38)': 3116, '(0,39)': 3117, '(39,0)': 3118, '(39,1)': 3119, '(2,39)': 3120, '(39,2)': 3121, '(39,3)': 3122, '(4,39)': 3123, '(39,4)': 3124, '(39,5)': 3125, '(6,39)': 3126, '(39,6)': 3127, '(39,7)': 3128, '(8,39)': 3129, '(39,8)': 3130, '(39,9)': 3131, '(10,39)': 3132, '(39,10)': 3133, '(39,11)': 3134, '(12,39)': 3135, '(39,12)': 3136, '(39,13)': 3137, '(14,39)': 3138, '(39,14)': 3139, '(39,15)': 3140, '(16,39)': 3141, '(39,16)': 3142, '(39,17)': 3143, '(18,39)': 3144, '(39,18)': 3145, '(39,19)': 3146, '(20,39)': 3147, '(39,20)': 3148, '(39,21)': 3149, '(22,39)': 3150, '(39,22)': 3151, '(39,23)': 3152, '(24,39)': 3153, '(39,24)': 3154, '(39,25)': 3155, '(26,39)': 3156, '(39,26)': 3157, '(39,27)': 3158, '(28,39)': 3159, '(39,28)': 3160, '(39,29)': 3161, '(30,39)': 3162, '(39,30)': 3163, '(39,31)': 3164, '(32,39)': 3165, '(39,32)': 3166, '(39,33)': 3167, '(34,39)': 3168, '(39,34)': 3169, '(39,35)': 3170, '(36,39)': 3171, '(39,36)': 3172, '(39,37)': 3173, '(38,39)': 3174, '(39,38)': 3175, '(1,39)': 3176, '(3,39)': 3177, '(5,39)': 3178, '(7,39)': 3179, '(9,39)': 3180, '(11,39)': 3181, '(13,39)': 3182, '(15,39)': 3183, '(17,39)': 3184, '(19,39)': 3185, '(21,39)': 3186, '(23,39)': 3187, '(25,39)': 3188, '(27,39)': 3189, '(29,39)': 3190, '(31,39)': 3191, '(33,39)': 3192, '(35,39)': 3193, '(37,39)': 3194, '(39,39)': 3195, '(0,40)': 3196, '(2,40)': 3197, '(4,40)': 3198, '(6,40)': 3199, '(8,40)': 3200, '(10,40)': 3201, '(12,40)': 3202, '(14,40)': 3203, '(16,40)': 3204, '(18,40)': 3205, '(20,40)': 3206, '(22,40)': 3207, '(24,40)': 3208, '(26,40)': 3209, '(28,40)': 3210, '(30,40)': 3211, '(32,40)': 3212, '(34,40)': 3213, '(36,40)': 3214, '(38,40)': 3215, '(40,0)': 3216, '(1,40)': 3217, '(40,1)': 3218, '(40,2)': 3219, '(3,40)': 3220, '(40,3)': 3221, '(40,4)': 3222, '(5,40)': 3223, '(40,5)': 3224, '(40,6)': 3225, '(7,40)': 3226, '(40,7)': 3227, '(40,8)': 3228, '(9,40)': 3229, '(40,9)': 3230, '(40,10)': 3231, '(11,40)': 3232, '(40,11)': 3233, '(40,12)': 3234, '(13,40)': 3235, '(40,13)': 3236, '(40,14)': 3237, '(15,40)': 3238, '(40,15)': 3239, '(40,16)': 3240, '(17,40)': 3241, '(40,17)': 3242, '(40,18)': 3243, '(19,40)': 3244, '(40,19)': 3245, '(40,20)': 3246, '(21,40)': 3247, '(40,21)': 3248, '(40,22)': 3249, '(23,40)': 3250, '(40,23)': 3251, '(40,24)': 3252, '(25,40)': 3253, '(40,25)': 3254, '(40,26)': 3255, '(27,40)': 3256, '(40,27)': 3257, '(40,28)': 3258, '(29,40)': 3259, '(40,29)': 3260, '(40,30)': 3261, '(31,40)': 3262, '(40,31)': 3263, '(40,32)': 3264, '(33,40)': 3265, '(40,33)': 3266, '(40,34)': 3267, '(35,40)': 3268, '(40,35)': 3269, '(40,36)': 3270, '(37,40)': 3271, '(40,37)': 3272, '(40,38)': 3273, '(39,40)': 3274, '(40,39)': 3275, '(40,40)': 3276, '(0,41)': 3277, '(41,0)': 3278, '(41,1)': 3279, '(2,41)': 3280, '(41,2)': 3281, '(41,3)': 3282, '(4,41)': 3283, '(41,4)': 3284, '(41,5)': 3285, '(6,41)': 3286, '(41,6)': 3287, '(41,7)': 3288, '(8,41)': 3289, '(41,8)': 3290, '(41,9)': 3291, '(10,41)': 3292, '(41,10)': 3293, '(41,11)': 3294, '(12,41)': 3295, '(41,12)': 3296, '(41,13)': 3297, '(14,41)': 3298, '(41,14)': 3299, '(41,15)': 3300, '(16,41)': 3301, '(41,16)': 3302, '(41,17)': 3303, '(18,41)': 3304, '(41,18)': 3305, '(41,19)': 3306, '(20,41)': 3307, '(41,20)': 3308, '(41,21)': 3309, '(22,41)': 3310, '(41,22)': 3311, '(41,23)': 3312, '(24,41)': 3313, '(41,24)': 3314, '(41,25)': 3315, '(26,41)': 3316, '(41,26)': 3317, '(41,27)': 3318, '(28,41)': 3319, '(41,28)': 3320, '(41,29)': 3321, '(30,41)': 3322, '(41,30)': 3323, '(41,31)': 3324, '(32,41)': 3325, '(41,32)': 3326, '(41,33)': 3327, '(34,41)': 3328, '(41,34)': 3329, '(41,35)': 3330, '(36,41)': 3331, '(41,36)': 3332, '(41,37)': 3333, '(38,41)': 3334, '(41,38)': 3335, '(41,39)': 3336, '(40,41)': 3337, '(41,40)': 3338, '(1,41)': 3339, '(3,41)': 3340, '(5,41)': 3341, '(7,41)': 3342, '(9,41)': 3343, '(11,41)': 3344, '(13,41)': 3345, '(15,41)': 3346, '(17,41)': 3347, '(19,41)': 3348, '(21,41)': 3349, '(23,41)': 3350, '(25,41)': 3351, '(27,41)': 3352, '(29,41)': 3353, '(31,41)': 3354, '(33,41)': 3355, '(35,41)': 3356, '(37,41)': 3357, '(39,41)': 3358, '(41,41)': 3359, '(0,42)': 3360, '(2,42)': 3361, '(4,42)': 3362, '(6,42)': 3363, '(8,42)': 3364, '(10,42)': 3365, '(12,42)': 3366, '(14,42)': 3367, '(16,42)': 3368, '(18,42)': 3369, '(20,42)': 3370, '(22,42)': 3371, '(24,42)': 3372, '(26,42)': 3373, '(28,42)': 3374, '(30,42)': 3375, '(32,42)': 3376, '(34,42)': 3377, '(36,42)': 3378, '(38,42)': 3379, '(40,42)': 3380, '(42,0)': 3381, '(1,42)': 3382, '(42,1)': 3383, '(42,2)': 3384, '(3,42)': 3385, '(42,3)': 3386, '(42,4)': 3387, '(5,42)': 3388, '(42,5)': 3389, '(42,6)': 3390, '(7,42)': 3391, '(42,7)': 3392, '(42,8)': 3393, '(9,42)': 3394, '(42,9)': 3395, '(42,10)': 3396, '(11,42)': 3397, '(42,11)': 3398, '(42,12)': 3399, '(13,42)': 3400, '(42,13)': 3401, '(42,14)': 3402, '(15,42)': 3403, '(42,15)': 3404, '(42,16)': 3405, '(17,42)': 3406, '(42,17)': 3407, '(42,18)': 3408, '(19,42)': 3409, '(42,19)': 3410, '(42,20)': 3411, '(21,42)': 3412, '(42,21)': 3413, '(42,22)': 3414, '(23,42)': 3415, '(42,23)': 3416, '(42,24)': 3417, '(25,42)': 3418, '(42,25)': 3419, '(42,26)': 3420, '(27,42)': 3421, '(42,27)': 3422, '(42,28)': 3423, '(29,42)': 3424, '(42,29)': 3425, '(42,30)': 3426, '(31,42)': 3427, '(42,31)': 3428, '(42,32)': 3429, '(33,42)': 3430, '(42,33)': 3431, '(42,34)': 3432, '(35,42)': 3433, '(42,35)': 3434, '(42,36)': 3435, '(37,42)': 3436, '(42,37)': 3437, '(42,38)': 3438, '(39,42)': 3439, '(42,39)': 3440, '(42,40)': 3441, '(41,42)': 3442, '(42,41)': 3443, '(42,42)': 3444, '(0,43)': 3445, '(43,0)': 3446, '(43,1)': 3447, '(2,43)': 3448, '(43,2)': 3449, '(43,3)': 3450, '(4,43)': 3451, '(43,4)': 3452, '(43,5)': 3453, '(6,43)': 3454, '(43,6)': 3455, '(43,7)': 3456, '(8,43)': 3457, '(43,8)': 3458, '(43,9)': 3459, '(10,43)': 3460, '(43,10)': 3461, '(43,11)': 3462, '(12,43)': 3463, '(43,12)': 3464, '(43,13)': 3465, '(14,43)': 3466, '(43,14)': 3467, '(43,15)': 3468, '(16,43)': 3469, '(43,16)': 3470, '(43,17)': 3471, '(18,43)': 3472, '(43,18)': 3473, '(43,19)': 3474, '(20,43)': 3475, '(43,20)': 3476, '(43,21)': 3477, '(22,43)': 3478, '(43,22)': 3479, '(43,23)': 3480, '(24,43)': 3481, '(43,24)': 3482, '(43,25)': 3483, '(26,43)': 3484, '(43,26)': 3485, '(43,27)': 3486, '(28,43)': 3487, '(43,28)': 3488, '(43,29)': 3489, '(30,43)': 3490, '(43,30)': 3491, '(43,31)': 3492, '(32,43)': 3493, '(43,32)': 3494, '(43,33)': 3495, '(34,43)': 3496, '(43,34)': 3497, '(43,35)': 3498, '(36,43)': 3499, '(43,36)': 3500, '(43,37)': 3501, '(38,43)': 3502, '(43,38)': 3503, '(43,39)': 3504, '(40,43)': 3505, '(43,40)': 3506, '(43,41)': 3507, '(42,43)': 3508, '(43,42)': 3509, '(1,43)': 3510, '(3,43)': 3511, '(5,43)': 3512, '(7,43)': 3513, '(9,43)': 3514, '(11,43)': 3515, '(13,43)': 3516, '(15,43)': 3517, '(17,43)': 3518, '(19,43)': 3519, '(21,43)': 3520, '(23,43)': 3521, '(25,43)': 3522, '(27,43)': 3523, '(29,43)': 3524, '(31,43)': 3525, '(33,43)': 3526, '(35,43)': 3527, '(37,43)': 3528, '(39,43)': 3529, '(41,43)': 3530, '(43,43)': 3531, '(0,44)': 3532, '(2,44)': 3533, '(4,44)': 3534, '(6,44)': 3535, '(8,44)': 3536, '(10,44)': 3537, '(12,44)': 3538, '(14,44)': 3539, '(16,44)': 3540, '(18,44)': 3541, '(20,44)': 3542, '(22,44)': 3543, '(24,44)': 3544, '(26,44)': 3545, '(28,44)': 3546, '(30,44)': 3547, '(32,44)': 3548, '(34,44)': 3549, '(36,44)': 3550, '(38,44)': 3551, '(40,44)': 3552, '(42,44)': 3553, '(44,0)': 3554, '(1,44)': 3555, '(44,1)': 3556, '(44,2)': 3557, '(3,44)': 3558, '(44,3)': 3559, '(44,4)': 3560, '(5,44)': 3561, '(44,5)': 3562, '(44,6)': 3563, '(7,44)': 3564, '(44,7)': 3565, '(44,8)': 3566, '(9,44)': 3567, '(44,9)': 3568, '(44,10)': 3569, '(11,44)': 3570, '(44,11)': 3571, '(44,12)': 3572, '(13,44)': 3573, '(44,13)': 3574, '(44,14)': 3575, '(15,44)': 3576, '(44,15)': 3577, '(44,16)': 3578, '(17,44)': 3579, '(44,17)': 3580, '(44,18)': 3581, '(19,44)': 3582, '(44,19)': 3583, '(44,20)': 3584, '(21,44)': 3585, '(44,21)': 3586, '(44,22)': 3587, '(23,44)': 3588, '(44,23)': 3589, '(44,24)': 3590, '(25,44)': 3591, '(44,25)': 3592, '(44,26)': 3593, '(27,44)': 3594, '(44,27)': 3595, '(44,28)': 3596, '(29,44)': 3597, '(44,29)': 3598, '(44,30)': 3599, '(31,44)': 3600, '(44,31)': 3601, '(44,32)': 3602, '(33,44)': 3603, '(44,33)': 3604, '(44,34)': 3605, '(35,44)': 3606, '(44,35)': 3607, '(44,36)': 3608, '(37,44)': 3609, '(44,37)': 3610, '(44,38)': 3611, '(39,44)': 3612, '(44,39)': 3613, '(44,40)': 3614, '(41,44)': 3615, '(44,41)': 3616, '(44,42)': 3617, '(43,44)': 3618, '(44,43)': 3619, '(44,44)': 3620, '(0,45)': 3621, '(45,0)': 3622, '(45,1)': 3623, '(2,45)': 3624, '(45,2)': 3625, '(45,3)': 3626, '(4,45)': 3627, '(45,4)': 3628, '(45,5)': 3629, '(6,45)': 3630, '(45,6)': 3631, '(45,7)': 3632, '(8,45)': 3633, '(45,8)': 3634, '(45,9)': 3635, '(10,45)': 3636, '(45,10)': 3637, '(45,11)': 3638, '(12,45)': 3639, '(45,12)': 3640, '(45,13)': 3641, '(14,45)': 3642, '(45,14)': 3643, '(45,15)': 3644, '(16,45)': 3645, '(45,16)': 3646, '(45,17)': 3647, '(18,45)': 3648, '(45,18)': 3649, '(45,19)': 3650, '(20,45)': 3651, '(45,20)': 3652, '(45,21)': 3653, '(22,45)': 3654, '(45,22)': 3655, '(45,23)': 3656, '(24,45)': 3657, '(45,24)': 3658, '(45,25)': 3659, '(26,45)': 3660, '(45,26)': 3661, '(45,27)': 3662, '(28,45)': 3663, '(45,28)': 3664, '(45,29)': 3665, '(30,45)': 3666, '(45,30)': 3667, '(45,31)': 3668, '(32,45)': 3669, '(45,32)': 3670, '(45,33)': 3671, '(34,45)': 3672, '(45,34)': 3673, '(45,35)': 3674, '(36,45)': 3675, '(45,36)': 3676, '(45,37)': 3677, '(38,45)': 3678, '(45,38)': 3679, '(45,39)': 3680, '(40,45)': 3681, '(45,40)': 3682, '(45,41)': 3683, '(42,45)': 3684, '(45,42)': 3685, '(45,43)': 3686, '(44,45)': 3687, '(45,44)': 3688, '(1,45)': 3689, '(3,45)': 3690, '(5,45)': 3691, '(7,45)': 3692, '(9,45)': 3693, '(11,45)': 3694, '(13,45)': 3695, '(15,45)': 3696, '(17,45)': 3697, '(19,45)': 3698, '(21,45)': 3699, '(23,45)': 3700, '(25,45)': 3701, '(27,45)': 3702, '(29,45)': 3703, '(31,45)': 3704, '(33,45)': 3705, '(35,45)': 3706, '(37,45)': 3707, '(39,45)': 3708, '(41,45)': 3709, '(43,45)': 3710, '(45,45)': 3711, '(0,46)': 3712, '(2,46)': 3713, '(4,46)': 3714, '(6,46)': 3715, '(8,46)': 3716, '(10,46)': 3717, '(12,46)': 3718, '(14,46)': 3719, '(16,46)': 3720, '(18,46)': 3721, '(20,46)': 3722, '(22,46)': 3723, '(24,46)': 3724, '(26,46)': 3725, '(28,46)': 3726, '(30,46)': 3727, '(32,46)': 3728, '(34,46)': 3729, '(36,46)': 3730, '(38,46)': 3731, '(40,46)': 3732, '(42,46)': 3733, '(44,46)': 3734, '(46,0)': 3735, '(1,46)': 3736, '(46,1)': 3737, '(46,2)': 3738, '(3,46)': 3739, '(46,3)': 3740, '(46,4)': 3741, '(5,46)': 3742, '(46,5)': 3743, '(46,6)': 3744, '(7,46)': 3745, '(46,7)': 3746, '(46,8)': 3747, '(9,46)': 3748, '(46,9)': 3749, '(46,10)': 3750, '(11,46)': 3751, '(46,11)': 3752, '(46,12)': 3753, '(13,46)': 3754, '(46,13)': 3755, '(46,14)': 3756, '(15,46)': 3757, '(46,15)': 3758, '(46,16)': 3759, '(17,46)': 3760, '(46,17)': 3761, '(46,18)': 3762, '(19,46)': 3763, '(46,19)': 3764, '(46,20)': 3765, '(21,46)': 3766, '(46,21)': 3767, '(46,22)': 3768, '(23,46)': 3769, '(46,23)': 3770, '(46,24)': 3771, '(25,46)': 3772, '(46,25)': 3773, '(46,26)': 3774, '(27,46)': 3775, '(46,27)': 3776, '(46,28)': 3777, '(29,46)': 3778, '(46,29)': 3779, '(46,30)': 3780, '(31,46)': 3781, '(46,31)': 3782, '(46,32)': 3783, '(33,46)': 3784, '(46,33)': 3785, '(46,34)': 3786, '(35,46)': 3787, '(46,35)': 3788, '(46,36)': 3789, '(37,46)': 3790, '(46,37)': 3791, '(46,38)': 3792, '(39,46)': 3793, '(46,39)': 3794, '(46,40)': 3795, '(41,46)': 3796, '(46,41)': 3797, '(46,42)': 3798, '(43,46)': 3799, '(46,43)': 3800, '(46,44)': 3801, '(45,46)': 3802, '(46,45)': 3803, '(46,46)': 3804, '(0,47)': 3805, '(47,0)': 3806, '(47,1)': 3807, '(2,47)': 3808, '(47,2)': 3809, '(47,3)': 3810, '(4,47)': 3811, '(47,4)': 3812, '(47,5)': 3813, '(6,47)': 3814, '(47,6)': 3815, '(47,7)': 3816, '(8,47)': 3817, '(47,8)': 3818, '(47,9)': 3819, '(10,47)': 3820, '(47,10)': 3821, '(47,11)': 3822, '(12,47)': 3823, '(47,12)': 3824, '(47,13)': 3825, '(14,47)': 3826, '(47,14)': 3827, '(47,15)': 3828, '(16,47)': 3829, '(47,16)': 3830, '(47,17)': 3831, '(18,47)': 3832, '(47,18)': 3833, '(47,19)': 3834, '(20,47)': 3835, '(47,20)': 3836, '(47,21)': 3837, '(22,47)': 3838, '(47,22)': 3839, '(47,23)': 3840, '(24,47)': 3841, '(47,24)': 3842, '(47,25)': 3843, '(26,47)': 3844, '(47,26)': 3845, '(47,27)': 3846, '(28,47)': 3847, '(47,28)': 3848, '(47,29)': 3849, '(30,47)': 3850, '(47,30)': 3851, '(47,31)': 3852, '(32,47)': 3853, '(47,32)': 3854, '(47,33)': 3855, '(34,47)': 3856, '(47,34)': 3857, '(47,35)': 3858, '(36,47)': 3859, '(47,36)': 3860, '(47,37)': 3861, '(38,47)': 3862, '(47,38)': 3863, '(47,39)': 3864, '(40,47)': 3865, '(47,40)': 3866, '(47,41)': 3867, '(42,47)': 3868, '(47,42)': 3869, '(47,43)': 3870, '(44,47)': 3871, '(47,44)': 3872, '(47,45)': 3873, '(46,47)': 3874, '(47,46)': 3875, '(1,47)': 3876, '(3,47)': 3877, '(5,47)': 3878, '(7,47)': 3879, '(9,47)': 3880, '(11,47)': 3881, '(13,47)': 3882, '(15,47)': 3883, '(17,47)': 3884, '(19,47)': 3885, '(21,47)': 3886, '(23,47)': 3887, '(25,47)': 3888, '(27,47)': 3889, '(29,47)': 3890, '(31,47)': 3891, '(33,47)': 3892, '(35,47)': 3893, '(37,47)': 3894, '(39,47)': 3895, '(41,47)': 3896, '(43,47)': 3897, '(45,47)': 3898, '(47,47)': 3899, '(0,48)': 3900, '(2,48)': 3901, '(4,48)': 3902, '(6,48)': 3903, '(8,48)': 3904, '(10,48)': 3905, '(12,48)': 3906, '(14,48)': 3907, '(16,48)': 3908, '(18,48)': 3909, '(20,48)': 3910, '(22,48)': 3911, '(24,48)': 3912, '(26,48)': 3913, '(28,48)': 3914, '(30,48)': 3915, '(32,48)': 3916, '(34,48)': 3917, '(36,48)': 3918, '(38,48)': 3919, '(40,48)': 3920, '(42,48)': 3921, '(44,48)': 3922, '(46,48)': 3923, '(48,0)': 3924, '(1,48)': 3925, '(48,1)': 3926, '(48,2)': 3927, '(3,48)': 3928, '(48,3)': 3929, '(48,4)': 3930, '(5,48)': 3931, '(48,5)': 3932, '(48,6)': 3933, '(7,48)': 3934, '(48,7)': 3935, '(48,8)': 3936, '(9,48)': 3937, '(48,9)': 3938, '(48,10)': 3939, '(11,48)': 3940, '(48,11)': 3941, '(48,12)': 3942, '(13,48)': 3943, '(48,13)': 3944, '(48,14)': 3945, '(15,48)': 3946, '(48,15)': 3947, '(48,16)': 3948, '(17,48)': 3949, '(48,17)': 3950, '(48,18)': 3951, '(19,48)': 3952, '(48,19)': 3953, '(48,20)': 3954, '(21,48)': 3955, '(48,21)': 3956, '(48,22)': 3957, '(23,48)': 3958, '(48,23)': 3959, '(48,24)': 3960, '(25,48)': 3961, '(48,25)': 3962, '(48,26)': 3963, '(27,48)': 3964, '(48,27)': 3965, '(48,28)': 3966, '(29,48)': 3967, '(48,29)': 3968, '(48,30)': 3969, '(31,48)': 3970, '(48,31)': 3971, '(48,32)': 3972, '(33,48)': 3973, '(48,33)': 3974, '(48,34)': 3975, '(35,48)': 3976, '(48,35)': 3977, '(48,36)': 3978, '(37,48)': 3979, '(48,37)': 3980, '(48,38)': 3981, '(39,48)': 3982, '(48,39)': 3983, '(48,40)': 3984, '(41,48)': 3985, '(48,41)': 3986, '(48,42)': 3987, '(43,48)': 3988, '(48,43)': 3989, '(48,44)': 3990, '(45,48)': 3991, '(48,45)': 3992, '(48,46)': 3993, '(47,48)': 3994, '(48,47)': 3995, '(48,48)': 3996, '(0,49)': 3997, '(49,0)': 3998, '(49,1)': 3999, '(2,49)': 4000, '(49,2)': 4001, '(49,3)': 4002, '(4,49)': 4003, '(49,4)': 4004, '(49,5)': 4005, '(6,49)': 4006, '(49,6)': 4007, '(49,7)': 4008, '(8,49)': 4009, '(49,8)': 4010, '(49,9)': 4011, '(10,49)': 4012, '(49,10)': 4013, '(49,11)': 4014, '(12,49)': 4015, '(49,12)': 4016, '(49,13)': 4017, '(14,49)': 4018, '(49,14)': 4019, '(49,15)': 4020, '(16,49)': 4021, '(49,16)': 4022, '(49,17)': 4023, '(18,49)': 4024, '(49,18)': 4025, '(49,19)': 4026, '(20,49)': 4027, '(49,20)': 4028, '(49,21)': 4029, '(22,49)': 4030, '(49,22)': 4031, '(49,23)': 4032, '(24,49)': 4033, '(49,24)': 4034, '(49,25)': 4035, '(26,49)': 4036, '(49,26)': 4037, '(49,27)': 4038, '(28,49)': 4039, '(49,28)': 4040, '(49,29)': 4041, '(30,49)': 4042, '(49,30)': 4043, '(49,31)': 4044, '(32,49)': 4045, '(49,32)': 4046, '(49,33)': 4047, '(34,49)': 4048, '(49,34)': 4049, '(49,35)': 4050, '(36,49)': 4051, '(49,36)': 4052, '(49,37)': 4053, '(38,49)': 4054, '(49,38)': 4055, '(49,39)': 4056, '(40,49)': 4057, '(49,40)': 4058, '(49,41)': 4059, '(42,49)': 4060, '(49,42)': 4061, '(49,43)': 4062, '(44,49)': 4063, '(49,44)': 4064, '(49,45)': 4065, '(46,49)': 4066, '(49,46)': 4067, '(49,47)': 4068, '(48,49)': 4069, '(49,48)': 4070, '(1,49)': 4071, '(3,49)': 4072, '(5,49)': 4073, '(7,49)': 4074, '(9,49)': 4075, '(11,49)': 4076, '(13,49)': 4077, '(15,49)': 4078, '(17,49)': 4079, '(19,49)': 4080, '(21,49)': 4081, '(23,49)': 4082, '(25,49)': 4083, '(27,49)': 4084, '(29,49)': 4085, '(31,49)': 4086, '(33,49)': 4087, '(35,49)': 4088, '(37,49)': 4089, '(39,49)': 4090, '(41,49)': 4091, '(43,49)': 4092, '(45,49)': 4093, '(47,49)': 4094, '(49,49)': 4095}