def __init__(self) -> None: self._state = State(KnowledgeBase.default().logic) self._entities = OrderedDict() self._rooms = [] self._objects = [] self._update() self._player_room = None
def test_is_sequence_applicable(): state = State(KnowledgeBase.default().logic, [ Proposition.parse("at(P, r_1: r)"), Proposition.parse("empty(r_2: r)"), Proposition.parse("empty(r_3: r)"), ]) assert state.is_sequence_applicable([ Action.parse( "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)" ), Action.parse( "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)" ), ]) assert not state.is_sequence_applicable([ Action.parse( "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)" ), Action.parse( "go :: at(P, r_1: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_1: r)" ), ]) assert not state.is_sequence_applicable([ Action.parse( "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)" ), Action.parse( "go :: at(P, r_3: r) & empty(r_1: r) -> at(P, r_1: r) & empty(r_3: r)" ), ])
def __init__(self) -> None: self._state = State() self._entities = OrderedDict() self._rooms = [] self._objects = [] self._update() self._player_room = None
def test_is_sequence_applicable(): state = State([ Proposition.parse("at(P, r_1: r)"), Proposition.parse("empty(r_2: r)"), Proposition.parse("empty(r_3: r)"), ]) assert state.is_sequence_applicable([ Action.parse( "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)" ), Action.parse( "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)" ), ]) assert not state.is_sequence_applicable([ Action.parse( "go :: at(P, r_1: r) & empty(r_2: r) -> at(P, r_2: r) & empty(r_1: r)" ), Action.parse( "go :: at(P, r_1: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_1: r)" ), ]) assert not state.is_sequence_applicable([ Action.parse( "go :: at(P, r_2: r) & empty(r_3: r) -> at(P, r_3: r) & empty(r_2: r)" ), Action.parse( "go :: at(P, r_3: r) & empty(r_1: r) -> at(P, r_1: r) & empty(r_3: r)" ), ])
def test_going_through_door(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State(KnowledgeBase.default().logic) state.add_facts([ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("free", [kitchen, room]), Proposition("free", [room, kitchen]), Proposition("south_of", [room, kitchen]) ]) options = ChainingOptions() options.backward = True options.max_depth = 3 options.max_length = 3 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"]], KnowledgeBase.default().rules.get_matching("go.*"), [KnowledgeBase.default().rules["open/d"]], ] chains = list(get_chains(state, options)) assert len(chains) == 18
def __init__(self, kb: Optional[KnowledgeBase] = None) -> None: self.kb = kb or KnowledgeBase.default() self._state = State(self.kb.logic) self._entities = OrderedDict() self._rooms = [] self._objects = [] self._update() self._player_room = None
def test_parallel_quests(): logic = GameLogic.parse(""" type foo { rules { do_a :: not_a(foo) & $not_c(foo) -> a(foo); do_b :: not_b(foo) & $not_c(foo) -> b(foo); do_c :: $a(foo) & $b(foo) & not_c(foo) -> c(foo); } constraints { a_or_not_a :: a(foo) & not_a(foo) -> fail(); b_or_not_b :: b(foo) & not_b(foo) -> fail(); c_or_not_c :: c(foo) & not_c(foo) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State(kb.logic, [ Proposition.parse("a(foo)"), Proposition.parse("b(foo)"), Proposition.parse("c(foo)"), ]) options = ChainingOptions() options.backward = True options.kb = kb options.max_depth = 3 options.max_breadth = 1 options.max_length = 3 chains = list(get_chains(state, options)) assert len(chains) == 2 options.max_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 3 options.min_breadth = 2 chains = list(get_chains(state, options)) assert len(chains) == 1 assert len(chains[0].actions) == 3 assert chains[0].nodes[0].depth == 2 assert chains[0].nodes[0].breadth == 2 assert chains[0].nodes[0].parent == chains[0].nodes[2] assert chains[0].nodes[1].depth == 2 assert chains[0].nodes[1].breadth == 1 assert chains[0].nodes[1].parent == chains[0].nodes[2] assert chains[0].nodes[2].depth == 1 assert chains[0].nodes[2].breadth == 1 assert chains[0].nodes[2].parent is None options.min_breadth = 1 options.create_variables = True state = State(kb.logic) chains = list(get_chains(state, options)) assert len(chains) == 5
def test_room_connections(): kb = KnowledgeBase.default() room0 = Variable("room0", "r") room1 = Variable("room1", "r") room2 = Variable("room2", "r") # Only one connection can exist between two rooms. # r1 # | # r0 - r1 state = State(kb.logic, [ Proposition("north_of", [room1, room0]), Proposition("south_of", [room0, room1]), Proposition("east_of", [room1, room0]), Proposition("west_of", [room0, room1]) ]) assert not check_state(state) # Non Cartesian layout are allowed. # r1 # | # r0 - r2 - r1 state = State(kb.logic, [ Proposition("north_of", [room1, room0]), Proposition("south_of", [room0, room1]), Proposition("east_of", [room2, room0]), Proposition("west_of", [room0, room2]), Proposition("east_of", [room1, room2]), Proposition("west_of", [room2, room1]) ]) assert check_state(state) # A room cannot have more than 4 'link' propositions. room3 = Variable("room3", "r") room4 = Variable("room4", "r") room5 = Variable("room5", "r") door1 = Variable("door1", "d") door2 = Variable("door2", "d") door3 = Variable("door3", "d") door4 = Variable("door4", "d") door5 = Variable("door5", "d") state = State(kb.logic, [ Proposition("link", [room0, door1, room1]), Proposition("link", [room0, door2, room2]), Proposition("link", [room0, door3, room3]), Proposition("link", [room0, door4, room4]), Proposition("link", [room0, door5, room5]) ]) assert not check_state(state)
def test_count(self): rng = np.random.RandomState(1234) types_counts = {t: rng.randint(2, 10) for t in self.types.variables} state = State() for t in self.types.variables: v = Variable(get_new(t, types_counts), t) state.add_fact(Proposition("dummy", [v])) counts = self.types.count(state) for t in self.types.variables: assert counts[t] == types_counts[t], (counts[t], types_counts[t])
def check_state(self, state: State) -> bool: """Check that a state satisfies the constraints.""" fail = Proposition("fail", []) constraints = state.all_applicable_actions(self.constraints) for constraint in constraints: copy = state.apply_on_copy(constraint) if copy and copy.is_fact(fail): return False return True
def test_all_instantiations(): state = State([ Proposition.parse("at(P, kitchen: r)"), Proposition.parse("in(key: o, kitchen: r)"), Proposition.parse("in(egg: o, kitchen: r)"), Proposition.parse("in(book: o, study: r)"), Proposition.parse("in(book: o, study: r)"), Proposition.parse("in(map: o, I)"), ]) take = Rule.parse("take :: $at(P, r) & in(o, r) -> in(o, I)") actions = set(state.all_instantiations(take)) assert actions == { Action.parse("take :: $at(P, kitchen: r) & in(key: o, kitchen: r) -> in(key: o, I)"), Action.parse("take :: $at(P, kitchen: r) & in(egg: o, kitchen: r) -> in(egg: o, I)"), } drop = take.inverse(name="drop") actions = set(state.all_instantiations(drop)) assert actions == { Action.parse("drop :: $at(P, kitchen: r) & in(map: o, I) -> in(map: o, kitchen: r)"), } state.apply(*actions) actions = set(state.all_instantiations(drop)) assert len(actions) == 0 # The state is no longer aware of the I variable, so there are no solutions actions = set(state.all_instantiations(take)) assert len(actions) == 0
def test_going_through_door(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State() state.add_facts([ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("free", [kitchen, room]), Proposition("free", [room, kitchen]), Proposition("south_of", [room, kitchen]) ]) # Sample quests. chains = [] rules_per_depth = { 0: [data.get_rules()["take/c"], data.get_rules()["take/s"]], 1: data.get_rules().get_matching("go.*"), 2: [data.get_rules()["open/d"]] } tree_of_possible = chaining.get_chains(state, max_depth=3, allow_partial_match=True, exceptions=[], rules_per_depth=rules_per_depth, backward=True) chains = list(tree_of_possible.traverse_preorder(subquests=True)) # chaining.print_chains(chains) # 1. take/c(P, room, c_0, o_0, I) # 2. take/c(P, room, c_0, o_0, I) -> go/north(P, r_0, room) # 3. take/c(P, room, c_0, o_0, I) -> go/north(P, r_0, room) -> open/d(P, r_0, d_0, room) # 4. take/c(P, room, c_0, o_0, I) -> go/south(P, kitchen, room) # 5. take/c(P, room, c_0, o_0, I) -> go/south(P, kitchen, room) -> open/d(P, kitchen, d_0, room) # 6. take/c(P, room, c_0, o_0, I) -> go/east(P, r_0, room) # 7. take/c(P, room, c_0, o_0, I) -> go/east(P, r_0, room) -> open/d(P, r_0, d_0, room) # 8. take/c(P, room, c_0, o_0, I) -> go/west(P, r_0, room) # 9. take/c(P, room, c_0, o_0, I) -> go/west(P, r_0, room) -> open/d(P, r_0, d_0, room) # 10. take/s(P, room, s_0, o_0, I) # 11. take/s(P, room, s_0, o_0, I) -> go/north(P, r_0, room) # 12. take/s(P, room, s_0, o_0, I) -> go/north(P, r_0, room) -> open/d(P, r_0, d_0, room) # 13. take/s(P, room, s_0, o_0, I) -> go/south(P, kitchen, room) # 14. take/s(P, room, s_0, o_0, I) -> go/south(P, kitchen, room) -> open/d(P, kitchen, d_0, room) # 15. take/s(P, room, s_0, o_0, I) -> go/east(P, r_0, room) # 16. take/s(P, room, s_0, o_0, I) -> go/east(P, r_0, room) -> open/d(P, r_0, d_0, room) # 17. take/s(P, room, s_0, o_0, I) -> go/west(P, r_0, room) # 18. take/s(P, room, s_0, o_0, I) -> go/west(P, r_0, room) -> open/d(P, r_0, d_0, room) assert len(chains) == 18
def process_full_facts(info_game, facts): state = State(info_game.kb.logic, facts) state = convert_link_predicates( state) # applies all applicable actions for fully observable graph inventory_facts = set(find_predicates_in_inventory(state)) # recipe_facts = set(find_predicates_in_recipe(state)) return set(state.facts) | inventory_facts #| recipe_facts
def check_state(self, state: State) -> bool: """Check that a state satisfies the constraints.""" fail = Proposition("fail", []) constraints = state.all_applicable_actions(self.constraints) for constraint in constraints: if state.is_applicable(constraint): # Optimistically delay copying the state copy = state.copy() copy.apply(constraint) if copy.is_fact(fail): return False return True
def process_local_facts(info_game, info_facts): state = State(info_game.kb.logic, info_facts) scope_facts = set(find_predicates_in_scope(state)) exit_facts = set(find_exits_in_scope(state)) inventory_facts = set(find_predicates_in_inventory(state)) recipe_facts = set(find_predicates_in_recipe(state)) return scope_facts | exit_facts | inventory_facts | recipe_facts
def build_state(locked_door=False): # Set up a world with two rooms and a few objecs. P = Variable("P") I = Variable("I") bedroom = Variable("bedroom", "r") kitchen = Variable("kitchen", "r") rusty_key = Variable("rusty key", "k") small_key = Variable("small key", "k") wooden_door = Variable("wooden door", "d") chest = Variable("chest", "c") cabinet = Variable("cabinet", "c") robe = Variable("robe", "o") state = State(KnowledgeBase.default().logic, [ Proposition("at", [P, bedroom]), Proposition("south_of", [kitchen, bedroom]), Proposition("north_of", [bedroom, kitchen]), Proposition("link", [bedroom, wooden_door, kitchen]), Proposition("link", [kitchen, wooden_door, bedroom]), Proposition("locked" if locked_door else "closed", [wooden_door]), Proposition("in", [rusty_key, I]), Proposition("match", [rusty_key, chest]), Proposition("locked", [chest]), Proposition("at", [chest, kitchen]), Proposition("in", [small_key, chest]), Proposition("match", [small_key, cabinet]), Proposition("locked", [cabinet]), Proposition("at", [cabinet, bedroom]), Proposition("in", [robe, cabinet]), ]) return state
def process_step_facts(prev_facts, info_game, info_facts, info_last_action, cmd): kb = info_game.kb if prev_facts is None or cmd == "restart" or len(prev_facts) == 0: new_facts = set() else: if cmd == "inventory": # Bypassing TextWorld's action detection. new_facts = set( find_predicates_in_inventory(State(kb.logic, info_facts))) return prev_facts | new_facts elif info_last_action is None: return prev_facts # Invalid action, nothing has changed. # Check recipe from cookbook here elif info_last_action.name == "examine" and "cookbook" in [ v.name for v in info_last_action.variables ]: new_facts = set( find_predicates_in_recipe(State(kb.logic, info_facts))) return prev_facts | new_facts state = State(kb.logic, prev_facts | set(info_last_action.preconditions)) success = state.apply(info_last_action) assert success new_facts = set(state.facts) new_facts |= set(find_predicates_in_scope(State(kb.logic, info_facts))) new_facts |= set(find_exits_in_scope(State(kb.logic, info_facts))) return new_facts
def process_facts(prev_facts, info_game, info_facts, info_last_action, cmd): kb = info_game.kb if prev_facts is None or cmd == "restart": facts = set() else: if cmd == "inventory": # Bypassing TextWorld's action detection. facts = set(find_predicates_in_inventory(State(kb.logic, info_facts))) return prev_facts | facts elif info_last_action is None : return prev_facts # Invalid action, nothing has changed. elif info_last_action.name == "examine" and "cookbook" in [v.name for v in info_last_action.variables]: facts = set(find_predicates_in_recipe(State(kb.logic, info_facts))) return prev_facts | facts state = State(kb.logic, prev_facts | set(info_last_action.preconditions)) success = state.apply(info_last_action) assert success facts = set(state.facts) # Always add facts in sight. facts |= set(find_predicates_in_scope(State(kb.logic, info_facts))) facts |= set(find_exits_in_scope(State(kb.logic, info_facts))) return facts
def state(self) -> State: """ Current state of the world. """ facts = [] for room in self.rooms: facts += room.facts for path in self.paths: facts += path.facts facts += self.inventory.facts facts += self._distractors_facts return State(facts)
def test_get_reverse_action(): kb = KnowledgeBase.default() for rule in kb.rules.values(): empty_state = State(KnowledgeBase.default().logic) action = maybe_instantiate_variables(rule, kb.types.constants_mapping.copy(), empty_state) r_action = kb.get_reverse_action(action) if rule.name.startswith("eat"): assert r_action is None else: assert r_action is not None # Check if that when applying the reverse rule we can re-obtain # the previous state. state = State(KnowledgeBase.default().logic, action.preconditions) new_state = state.copy() assert new_state.apply(action) r_state = new_state.copy() r_state.apply(r_action) assert state == r_state
def check_action(self, state: State, action: Action) -> bool: """ Check if an action should be allowed in this state. The default implementation disallows actions that would create new facts that don't mention any new variables. Args: state: The current state. action: The action being applied. Returns: Whether that action should be allowed. """ for prop in action.preconditions: if not state.is_fact(prop): if all(state.has_variable(var) for var in prop.arguments): # Don't allow creating new predicates without any new variables return False return True
def test_backward_chaining(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State([ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("south_of", [room, kitchen]), ]) rules_per_depth = { 0: [data.get_rules()["take/c"], data.get_rules()["take/s"]], 1: [data.get_rules()["open/c"]] } tree_of_possible = chaining.get_chains(state, max_depth=2, allow_partial_match=True, exceptions=['d'], rules_per_depth=rules_per_depth, backward=True) chains = list(tree_of_possible.traverse_preorder(subquests=True)) assert len(chains) == 3 for chain in chains: for depth, action in enumerate(chain): assert action.action.name in [ rule.name for rule in rules_per_depth[depth] ] rules_per_depth = { 0: [data.get_rules()["put"]], 1: [data.get_rules()["go/north"]], 2: [data.get_rules()["take/c"]] } tree_of_possible = chaining.get_chains(state, max_depth=3, allow_partial_match=True, exceptions=['d'], rules_per_depth=rules_per_depth, backward=True) chains = list(tree_of_possible.traverse_preorder(subquests=True)) assert len(chains) == 3 for chain in chains: for depth, action in enumerate(chain): assert action.action.name in [ rule.name for rule in rules_per_depth[depth] ]
def test_get_reverse_rules(verbose=False): for rule in data.get_rules().values(): r_rule = data.get_reverse_rules(rule) if verbose: print(rule, r_rule) if rule.name.startswith("eat"): assert r_rule is None else: # Check if that when applying the reverse rule we can reobtain # the previous state. action = maybe_instantiate_variables( rule, data.get_types().constants_mapping.copy(), State([])) state = State(action.preconditions) new_state = state.copy() assert new_state.apply(action) assert r_rule is not None actions = list( new_state.all_applicable_actions( [r_rule], data.get_types().constants_mapping)) if len(actions) != 1: print(actions) print(r_rule) print(new_state) print( list( new_state.all_instantiations( r_rule, data.get_types().constants_mapping))) assert len(actions) == 1 r_state = new_state.copy() r_state.apply(actions[0]) assert state == r_state
def __init__(self) -> None: """ Creates an empty world, with a player and an empty inventory. """ self._entities = {} self._quests = [] self.rooms = [] self.paths = [] self._types_counts = data.get_types().count(State()) self.player = self.new(type='P') self.inventory = self.new(type='I') self.grammar = textworld.generator.make_grammar() self._game = None self._distractors_facts = []
def process_facts(prev_facts, info_game, info_facts, info_last_action, cmd): kb = textworld.Game.deserialize(info_game).kb if prev_facts is None: facts = set() else: if cmd == "inventory": # Bypassing TextWorld's action detection. facts = set( find_predicates_in_inventory(State(kb.logic, info_facts))) return prev_facts | facts elif info_last_action is None: return prev_facts # Invalid action, nothing has changed. state = State(kb.logic, prev_facts | set(info_last_action.preconditions)) success = state.apply(info_last_action) assert success facts = set(state.facts) # Always add facts in sight. facts |= set(find_predicates_in_scope(State(kb.logic, info_facts))) return facts
def __init__(self) -> None: """ Creates an empty world, with a player and an empty inventory. """ self._entities = {} self._named_entities = {} self.quests = [] self.rooms = [] self.paths = [] self._types_counts = KnowledgeBase.default().types.count(State()) self.player = self.new(type='P') self.inventory = self.new(type='I') self.nowhere = [] self.grammar = textworld.generator.make_grammar() self._game = None self._distractors_facts = []
def __init__(self, options: Optional[GameOptions] = None) -> None: """ Creates an empty world, with a player and an empty inventory. """ self.options = options or GameOptions() self._entities = {} self._named_entities = {} self.quests = [] self.rooms = [] self.paths = [] self._kb = self.options.kb self._types_counts = self._kb.types.count(State(self._kb.logic)) self.player = self.new(type='P') self.inventory = self.new(type='I') self.nowhere = [] self._game = None self._distractors_facts = []
def test_backward_chaining(): P = Variable("P", "P") room = Variable("room", "r") kitchen = Variable("kitchen", "r") state = State(KnowledgeBase.default().logic, [ Proposition("at", [P, room]), Proposition("north_of", [kitchen, room]), Proposition("south_of", [room, kitchen]), ]) options = ChainingOptions() options.backward = True options.max_depth = 2 options.max_length = 2 options.subquests = True options.create_variables = True options.rules_per_depth = [ [ KnowledgeBase.default().rules["take/c"], KnowledgeBase.default().rules["take/s"] ], [KnowledgeBase.default().rules["open/c"]], ] options.restricted_types = {"d"} chains = list(get_chains(state, options)) assert len(chains) == 3 options = ChainingOptions() options.backward = True options.max_depth = 3 options.max_length = 3 options.subquests = True options.create_variables = True options.rules_per_depth = [ [KnowledgeBase.default().rules["put"]], [KnowledgeBase.default().rules["go/north"]], [KnowledgeBase.default().rules["take/c"]], ] options.restricted_types = {"d"} chains = list(get_chains(state, options)) assert len(chains) == 3
def deserialize(cls, data: Mapping) -> "Game": """ Creates a `Game` from serialized data. Args: data: Serialized data with the needed information to build a `Game` object. """ world = World.deserialize(data["world"]) grammar = None if "grammar" in data: grammar = Grammar(data["grammar"]) quests = [Quest.deserialize(d) for d in data["quests"]] game = cls(world, grammar, quests) game._infos = {k: EntityInfo.deserialize(v) for k, v in data["infos"]} game.state = State.deserialize(data["state"]) game._rules = {k: Rule.deserialize(v) for k, v in data["rules"]} game._types = VariableTypeTree.deserialize(data["types"]) game.metadata = data.get("metadata", {}) return game
def try_instantiate(self, state: State, partial: _PartialAction) -> Optional[Action]: """ Try to instantiate a partial action, by creating new variables if necessary. """ rule, mapping = partial.rule, partial.mapping if self.create_variables: type_counts = Counter({ph.type: len(state.variables_of_type(ph.type)) for ph in rule.placeholders}) for ph in rule.placeholders: if mapping.get(ph) is None: var = self.create_variable(state, ph, type_counts) if var: mapping[ph] = var else: return None return rule.instantiate(mapping)