#! /usr/bin/env python3
# -*- coding: utf-8 -*-
# File : blocksworld.py
# Author : Jiayuan Mao
# Email : maojiayuan@gmail.com
# Date : 01/03/2024
#
# This file is part of Project Concepts.
# Distributed under terms of the MIT license.
from typing import Optional, List
import numpy as np
__all__ = ['Block', 'BlockWorld', 'random_generate_blocks_world']
[docs]
class Block(object):
[docs]
def __init__(self, index, father=None):
self.index = index
self.father = father
self.children = []
index: int
father: Optional['Block']
children: List['Block']
@property
def is_ground(self):
return self.father is None
@property
def placeable(self):
if self.is_ground:
return True
return len(self.children) == 0
@property
def moveable(self):
if self.is_ground:
return False
return len(self.children) == 0
[docs]
def remove_from_father(self):
assert self in self.father.children
self.father.children.remove(self)
self.father = None
[docs]
def add_to(self, other):
self.father = other
other.children.append(self)
[docs]
class BlockStorage(object):
[docs]
def __init__(self, blocks, random_order=None):
super().__init__()
self._blocks = blocks
self._random_order = None
self._inv_random_order = None
self.set_random_order(random_order)
[docs]
def __getitem__(self, item):
if self._random_order is None:
return self._blocks[item]
return self._blocks[self._random_order[item]]
[docs]
def __len__(self):
return len(self._blocks)
[docs]
def __iter__(self):
for i in range(len(self)):
yield self[i]
@property
def raw(self):
return self._blocks.copy()
@property
def random_order(self):
return self._random_order
[docs]
def set_random_order(self, random_order):
if random_order is None:
self._random_order = None
self._inv_random_order = None
return
self._random_order = random_order
self._inv_random_order = sorted(range(len(random_order)), key=lambda x: random_order[x])
[docs]
def index(self, i):
if self._random_order is None:
return i
return self._random_order[i]
[docs]
def inv_index(self, i):
if self._random_order is None:
return i
return self._inv_random_order[i]
[docs]
def permute(self, array):
if self._random_order is None:
return array
return [array[self._random_order[i]] for i in range(len(self._blocks))]
[docs]
class BlockWorld(object):
[docs]
def __init__(self, blocks, random_order=None):
super().__init__()
self.blocks = BlockStorage(blocks, random_order)
@property
def size(self):
return len(self.blocks)
[docs]
def move(self, x, y):
if x != y and self.moveable(x, y):
self.blocks[x].remove_from_father()
self.blocks[x].add_to(self.blocks[y])
[docs]
def moveable(self, x, y):
return self.blocks[x].moveable and self.blocks[y].placeable
[docs]
def get_world_string(self):
index_mapping = {b.index: i for i, b in enumerate(self.blocks)}
raw_blocks = self.blocks.raw
result = ''
def dfs(block, indent):
nonlocal result
result += '{}Block #{}: (IsGround={}, Moveable={}, Placeable={})\n'.format(
' ' * (indent * 2), index_mapping[block.index], block.is_ground, block.moveable, block.placeable
)
for c in block.children:
dfs(c, indent + 1)
dfs(raw_blocks[0], 0)
return result
[docs]
def get_coordinates(self, absolute=False):
coordinates = [None for _ in range(self.size)]
raw_blocks = self.blocks.raw
def dfs(block: Block):
if block.is_ground:
coordinates[block.index] = (0, 0)
for j, c in enumerate(block.children):
x = self.blocks.inv_index(c.index) if absolute else j
coordinates[c.index] = (x, 1)
dfs(c)
else:
coor = coordinates[block.index]
assert coor is not None
x, y = coor
for c in block.children:
coordinates[c.index] = (x, y + 1)
dfs(c)
dfs(raw_blocks[0])
coordinates = self.blocks.permute(coordinates)
return np.array(coordinates)
[docs]
def get_on_relation(self):
on = np.zeros((self.size, self.size), dtype=np.float32)
def dfs(block):
if block.is_ground:
for c in block.children:
on[c.index, block.index] = 1
dfs(c)
else:
for c in block.children:
on[c.index, block.index] = 1
dfs(c)
dfs(self.blocks.raw[0])
return on
[docs]
def get_is_ground(self):
return np.array([block.is_ground for block in self.blocks])
[docs]
def get_moveable(self):
return np.array([block.moveable for block in self.blocks])
[docs]
def get_placeable(self):
return np.array([block.placeable for block in self.blocks])
# similar to random tree generation, randomly sample a valid father for new nodes
[docs]
def random_generate_blocks_world(nr_blocks, random_order=False, one_stack=False, np_random: Optional[np.random.RandomState] = None):
if np_random is None:
np_random = np.random
blocks = [Block(0, None)]
leaves = [blocks[0]]
for i in range(1, nr_blocks + 1):
other = leaves[np_random.randint(len(leaves))]
this = Block(i)
this.add_to(other)
if not other.placeable or one_stack:
leaves.remove(other)
blocks.append(this)
leaves.append(this)
order = None
if random_order:
order = np_random.permutation(len(blocks))
return BlockWorld(blocks, random_order=order)