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([ 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 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 == None options.min_breadth = 1 options.create_variables = True chains = list(get_chains(State(), options)) assert len(chains) == 5
def test_state(): state = State() P = Variable.parse("P") kitchen = Variable.parse("kitchen: r") study = Variable.parse("study: r") stove = Variable.parse("stove: o") at_kitchen = Proposition.parse("at(P, kitchen: r)") in_kitchen = Proposition.parse("in(stove: o, kitchen: r)") at_study = Proposition.parse("at(P, study: r)") assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert len(state.variables) == 0 assert len(state.variables_of_type("P")) == 0 assert len(state.variables_of_type("r")) == 0 assert len(state.variables_of_type("o")) == 0 state.add_fact(at_kitchen) state.add_fact(in_kitchen) assert state.is_fact(at_kitchen) assert state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert set(state.variables) == {P, kitchen, stove} assert state.variables_of_type("P") == {P} assert state.variables_of_type("r") == {kitchen} assert state.variables_of_type("o") == {stove} state.remove_fact(at_kitchen) assert not state.is_fact(at_kitchen) assert state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert set(state.variables) == {kitchen, stove} assert len(state.variables_of_type("P")) == 0 assert state.variables_of_type("r") == {kitchen} assert state.variables_of_type("o") == {stove} state.remove_fact(in_kitchen) assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert not state.is_fact(at_study) assert len(state.variables) == 0 assert len(state.variables_of_type("P")) == 0 assert len(state.variables_of_type("r")) == 0 assert len(state.variables_of_type("o")) == 0 state.add_fact(at_study) assert not state.is_fact(at_kitchen) assert not state.is_fact(in_kitchen) assert state.is_fact(at_study) assert set(state.variables) == {P, study} assert state.variables_of_type("P") == {P} assert state.variables_of_type("r") == {study} assert len(state.variables_of_type("o")) == 0
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_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_logic_parsing(): P = Variable("P", "P") kitchen = Variable("kitchen", "r") egg = Variable("egg", "f") assert Variable.parse("P") == P assert Variable.parse("kitchen: r") == kitchen at_kitchen = Proposition("at", [P, kitchen]) in_kitchen = Proposition("in", [egg, kitchen]) raw_egg = Proposition("raw", [egg]) cooked_egg = Proposition("cooked", [egg]) assert Proposition.parse("at(P, kitchen: r)") == at_kitchen assert Signature.parse("at(P, r)") == at_kitchen.signature cook_egg = Action("cook", [at_kitchen, in_kitchen, raw_egg], [at_kitchen, in_kitchen, cooked_egg]) assert Action.parse("cook :: $at(P, kitchen: r) & $in(egg: f, kitchen: r) & raw(egg: f) -> cooked(egg: f)") == cook_egg P = Placeholder("P", "P") r = Placeholder("r", "r") d = Placeholder("d", "d") rp = Placeholder("r'", "r") assert Placeholder.parse("P") == P assert Placeholder.parse("r") == r assert Placeholder.parse("d") == d assert Placeholder.parse("r'") == rp at_r = Predicate("at", [P, r]) link = Predicate("link", [r, d, rp]) unlocked = Predicate("unlocked", [d]) at_rp = Predicate("at", [P, rp]) assert Predicate.parse("link(r, d, r')") == link go = Rule("go", [at_r, link, unlocked], [link, unlocked, at_rp]) assert Rule.parse("go :: at(P, r) & $link(r, d, r') & $unlocked(d) -> at(P, r')") == go # Make sure the types match in the whole expression assert_raises(ValueError, Rule.parse, "take :: $at(P, r) & $in(c, r) & in(o: k, c) -> in(o, I)")
def test_parallel_quests_navigation(): logic = GameLogic.parse(""" type P { } type I { } type r { rules { move :: at(P, r) & $free(r, r') -> at(P, r'); } constraints { atat :: at(P, r) & at(P, r') -> fail(); } } type o { rules { take :: $at(P, r) & at(o, r) -> in(o, I); } constraints { inat :: in(o, I) & at(o, r) -> fail(); } } type flour : o { } type eggs : o { } type cake { rules { bake :: in(flour, I) & in(eggs, I) -> in(cake, I) & in(flour, cake) & in(eggs, cake); } constraints { inincake :: in(o, I) & in(o, cake) -> fail(); atincake :: at(o, r) & in(o, cake) -> fail(); } } """) kb = KnowledgeBase(logic, "") state = State([ Proposition.parse("at(P, r3: r)"), Proposition.parse("free(r2: r, r3: r)"), Proposition.parse("free(r1: r, r2: r)"), ]) bake = [kb.logic.rules["bake"]] non_bake = [r for r in kb.logic.rules.values() if r.name != "bake"] options = ChainingOptions() options.backward = True options.create_variables = True options.min_depth = 3 options.max_depth = 3 options.min_breadth = 2 options.max_breadth = 2 options.kb = kb options.rules_per_depth = [bake, non_bake, non_bake] options.restricted_types = {"P", "r"} chains = list(get_chains(state, options)) assert len(chains) == 2