# Copyright (c) 2009 Tim Freeman # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. # # (This is the standard MIT License, copied from # http://www.opensource.org/licenses/mit-license.php on 24 Apr 2007.) # Test cases for inferring utility. # Turn this on to exercise soon near the end of the file. fast = False if fast: print "Remember to turn off fast" from turing_tape import turing_tape, binarytape import mind_body from machine import empty_tape from speed_prior import Speed_prior from bits import parsebinary, copy_add_all import test_util from test_util import assert_equals, expect_exception from compile import turing_program from constants import left, right, end, done, truncate from compile_and_run import compile_and_run from utility_combiners import Compassionate_utility_combiner, \ Selfless_utility_combiner from infer_utility import Infer_utility_problem, Infer_utility_explanation from planner import Planner, choose_next_action import infer_utility import planner t0 = binarytape("0") t1 = binarytape("1") # Simplest test case I could come up with. A better test would have # two agents. # Agent id for the AI. ai_agent_id = t0 # Agent id for Alice. alice_id = t1 other_agent_ids = [alice_id] # The physical state has two bits, which are the actions by alice and # by the AI on the previous timestep. Note that the old physical state # has no role in computing the new physical state. # The input to nonmind_physics is a tape that a concatenation of # subtapes. The first subtape is the previous nonmind_state, and # subsequent subtapes are behaviors of everyone, one after the other # in the standard order. The AI's id is last on the all_agent_ids # list, so its behavior should be rightmost on the tape. # Concatenating on a tape interleaves "0"'s into the even positions of # the the result, putting the original data in the odd positions, with # a "10" between the concatenated parts. # Alice's behavior is itself a tape with a tuple of one channel (so it # works with Constraint objects). The nested tupling gives us three # zeros in positions 5, 6, and 7 before the bit at position 4 from # Alice that varies. # So the input tape looks like: # 13:0 12:ai_action_previous_turn 11:0 10:alice_action_previous_turn # 9:1 8:0 # 7:0 6:0 5:0 4:alice_action_this_turn # 3:1 2:0 # 1:0 0:ai_action_this_turn # The output is the new nonmind_state and has two bits: # ai_action_this_turn alice_action_this_turn # So all we have to do is step left four times on the tape to get to # alice_action_this_turn, copy that bit four positions to the right, # pick up ai_action_this_turn, step left and write it there, then step # left and truncate the rest away. # To initialize the output tape, we are given an empty input tape. In # that case, we should put 0's there. impossible = (truncate, right, "impossible") def flipping_nonmind_physics_as(flip): if flip: alice_1_action = 0 else: alice_1_action = 1 alice_0_action = 1 - alice_1_action return turing_program( "start", start={0:(0, left, "skip_at_1"), 1:(1, left, "skip_at_1"), end: (0, left, "initialization_at_1")}, impossible={(0,1,end):(0, right, "impossible")}, # We get to initialization_at_1 if we started with an empty tape. initialization_at_1={end:(0, left, done), (0,1): impossible}, skip_at_1={0: (0, left, "skip_at_2"), (1, end): impossible}, skip_at_2={0: (0, left, "skip_at_3"), (1, end): impossible}, skip_at_3={1: (1, left, "read_at_4"), (0, end): impossible}, read_at_4={0: (0, right, "saw_0_at_3"), 1: (1, right, "saw_1_at_3"), end: impossible}, saw_0_at_3={1: (1, right, "saw_0_at_2"), (0, end): impossible}, saw_1_at_3={1: (1, right, "saw_1_at_2"), (0, end): impossible}, saw_0_at_2={0: (truncate, right, "saw_0_at_1"), (1, end): impossible}, saw_1_at_2={0: (truncate, right, "saw_1_at_1"), (1, end): impossible}, saw_0_at_1={0: (0, right, "saw_0_at_0"), (1, end): impossible}, saw_1_at_1={0: (0, right, "saw_1_at_0"), (1, end): impossible}, saw_0_at_0={0: (alice_0_action, left, "ai_0_at_1"), 1: (alice_0_action, left, "ai_1_at_1"), end: impossible}, saw_1_at_0={0: (alice_1_action, left, "ai_0_at_1"), 1: (alice_1_action, left, "ai_1_at_1"), end: impossible}, ai_0_at_1={0: (0, right, done), (1, end): impossible}, ai_1_at_1={0: (1, right, done), (1, end): impossible}, done={(0, 1, end): (done, right, "done")}) nonmind_physics_as = flipping_nonmind_physics_as(flip=False) if not fast: assert_equals(compile_and_run(absyn=nonmind_physics_as, inputsize=14, inputtape=parsebinary("00011000011000"), steps=20), binarytape("01")) assert_equals(compile_and_run(absyn=nonmind_physics_as, inputsize=14, inputtape=parsebinary("01011000001001"), steps=20), binarytape("10")) # Check generating the initial tape. assert_equals(compile_and_run(absyn=nonmind_physics_as, inputsize=0, inputtape=0, steps=5), binarytape("00")) def nonmind_physics(flipped): return Speed_prior(absyn=flipping_nonmind_physics_as(flip=flipped), logtime=5) if not fast: assert_equals(nonmind_physics(flipped=False).run(empty_tape), binarytape("00")) assert_equals(nonmind_physics(flipped=False) .run(binarytape("01011000011000")), binarytape("01")) # The input to compute_utility is a tuple of the agent id (which we # don't care about, since there is only Alice) and the guessed # nonmind_state (which is 2 bits, bloated to 4 because it's part of # the tuple). # So it looks like: # 7:0 6:1 (alice's agent id) # 5:1 4:0 (tuple separator) # 3:0 2:ai_action_previous_turn 1:0 0:alice_action_previous_turn # The output is one bit, alice's utility. # Alice prefers that her behavior is equal to the AI's. So # compute_utility returns 1 if the two bits are the same, or 0 if # they're different. utility_bits is 1. def compute_utility_as(wantsame): # The notions of "happy" and "sad" below are suitable for the case # where Alice wants her output to be the same as the AI's, so # wantsame is True. We swap the meanings in the case where # wantsame is False. if wantsame: happybit = 1 else: happybit = 0 sadbit = 1 - happybit return turing_program( "start", start={0: (0, left, "alice_0_at_1"), 1: (1, left, "alice_1_at_1"), end: impossible}, impossible={(0,1,end):(0, right, "impossible")}, alice_0_at_1={0: (0, left, "alice_0_at_2"), (1,end): impossible}, alice_1_at_1={0: (0, left, "alice_1_at_2"), (1,end): impossible}, alice_0_at_2={0: (0, right, "happy_at_1"), 1: (1, right, "sad_at_1"), end: impossible}, alice_1_at_2={0: (0, right, "sad_at_1"), 1: (1, right, "happy_at_1"), end: impossible}, happy_at_1={0: (truncate, right, "happy_at_0"), (1, end): impossible}, sad_at_1={0: (truncate, right, "sad_at_0"), (1, end): impossible}, happy_at_0={(0, 1): (happybit, left, "done"), end: impossible}, sad_at_0={(0, 1): (sadbit, left, "done"), end: impossible}, done={end: done, (0, 1): impossible}) # Alice's utility when she wants to have the same output as the AI. compute_utility_prefer_same_as = compute_utility_as(True) # Alice's utility when she wants the opposite output from the AI. compute_utility_prefer_different_as = compute_utility_as(False) assert_equals(compile_and_run(absyn=compute_utility_prefer_same_as, inputstring="01100100", steps=8), t0) assert_equals(compile_and_run(absyn=compute_utility_prefer_different_as, inputstring="01100100", steps=8), t1) assert_equals(compile_and_run(absyn=compute_utility_prefer_same_as, inputstring="01100001", steps=8), t0) assert_equals(compile_and_run(absyn=compute_utility_prefer_same_as, inputstring="01100101", steps=8), t1) assert_equals(compile_and_run(absyn=compute_utility_prefer_same_as, inputstring="01100000", steps=8), t1) # Computing the perception returns a concatenation of everybody's # perception, in the standard order, which means Alice is on the left # and the AI is on the right. # The input to this is the nonmind-state, which is: # ai_action_previous_turn alice_action_previous_turn # The result will be: # (Next one is alice's perception, double-tupled because it has # to be a tuple of channels) # 9:0 8:0 7:0 6:ai_action_previous_turn # 5:1 4:0 # (Next one is the ai's perception, single-tupled) # 3:0 2:ai_action_previous_turn 1:0 0:alice_action_previous_turn # We copy ai_action_previous_turn, leaving alice_action_previous_turn # in place. compute_2bit_perceptions_as = turing_program( "at_0", at_0={0: (0, left, "at_1"), 1: (1, left, "at_1"), end: impossible}, impossible={(0,1,end):(0, right, "impossible")}, at_1={0: (0, left, "ai_0_at_2"), 1: (0, left, "ai_1_at_2"), end: impossible}, ai_0_at_2={(0,1): impossible, end: (0, left, "ai_0_at_3")}, ai_1_at_2={(0,1): impossible, end: (1, left, "ai_1_at_3")}, ai_0_at_3={(0,1): impossible, end: (0, left, "ai_0_at_4")}, ai_1_at_3={(0,1): impossible, end: (0, left, "ai_1_at_4")}, ai_0_at_4={(0,1): impossible, end: (0, left, "ai_0_at_5")}, ai_1_at_4={(0,1): impossible, end: (0, left, "ai_1_at_5")}, ai_0_at_5={(0,1): impossible, end: (1, left, "ai_0_at_6")}, ai_1_at_5={(0,1): impossible, end: (1, left, "ai_1_at_6")}, ai_0_at_6={(0,1): impossible, end: (0, left, "at_7")}, ai_1_at_6={(0,1): impossible, end: (1, left, "at_7")}, at_7={(0,1): impossible, end:(0, left, "at_8")}, at_8={(0,1): impossible, end:(0, left, "at_9")}, at_9={(0,1): impossible, end:(0, right, done)}, done=done) if not fast: assert_equals(compile_and_run(absyn=compute_2bit_perceptions_as, inputstring="10", steps=16), binarytape("0001100100")) assert_equals(compile_and_run(absyn=compute_2bit_perceptions_as, inputstring="01", steps=16), binarytape("0000100001")) # Computing the perception returns a concatenation of everybody's # perception, in the standard order, which always has the AI rightmost. # This is the simpler version where the AI only has one bit of input, # which is alice's action on the previous turn. # The input to this is the nonmind-state, which is: # 1:ai_action_previous_turn 0:alice_action_previous_turn # The result will be: # 5:0 4:ai_action_previous_turn # 3:1 2:0 # 1:0 0:alice_action_previous_turn # If we wanted to exercise Constraint's on the 1 bit perceptions, # we'd have to double-tuple alice's perception here. No need. compute_1bit_perceptions_as = turing_program( "at_0", at_0={0: (0, left, "at_1"), 1: (1, left, "at_1"), end: impossible}, impossible={(0,1,end): impossible}, at_1={0:(0, left, "ai_0_at_2"), 1:(0, left, "ai_1_at_2"), end: impossible}, ai_0_at_2={(0,1,end):(0, left, "ai_0_at_3")}, ai_1_at_2={(0,1,end):(0, left, "ai_1_at_3")}, ai_0_at_3={(0,1,end):(1, left, "ai_0_at_4")}, ai_1_at_3={(0,1,end):(1, left, "ai_1_at_4")}, ai_0_at_4={(0,1,end):(0, left, "at_5")}, ai_1_at_4={(0,1,end):(1, left, "at_5")}, at_5={(0,1,end):(0, right, done)}, done=done) if not fast: assert_equals(compile_and_run(absyn=compute_1bit_perceptions_as, inputstring="10", steps=10), binarytape("011000")) assert_equals(compile_and_run(absyn=compute_1bit_perceptions_as, inputstring="01", steps=10), binarytape("001001")) # Alice's mental model of the world is just one bit: the # ai_action_previous_turn. # The input to mind_physics is a tuple of the agent id, the # perception, and the previous mind_state. # There is only one agent other than the AI, so we can ignore the # agent id. # We don't care about the previous mind state. # The perception is ai_action_previous_turn. # Thus the input is: # 0 1 (Alice's agent id) # 1 0 (tuple delimiter) # 0 ai_action_previous_turn # 1 0 (tuple delimiter) # 0 x (previous mind state, which we don't care about.) # The desired output is just one bit, ai_action_previous_turn. # So step left to cell 4, copy the bit to cell 0, and truncate the # tape to have only one bit. mind_physics_as = turing_program( "at_0", at_0={(0, 1): (0, left, "at_1"), end: impossible}, impossible={(0,1,end): impossible}, at_1={0: (0, left, "at_2"), (1, end): impossible}, at_2={0: (0, left, "at_3"), (1, end): impossible}, at_3={1: (0, left, "at_4"), (0, end): impossible}, at_4={0: (truncate, right, "with_0_at_3"), 1: (truncate, right, "with_1_at_3"), end: impossible}, with_0_at_3={0: (truncate, right, "with_0_at_2"), (1, end): impossible}, with_1_at_3={0: (truncate, right, "with_1_at_2"), (1, end): impossible}, with_0_at_2={0: (truncate, right, "with_0_at_1"), (1, end): impossible}, with_1_at_2={0: (truncate, right, "with_1_at_1"), (1, end): impossible}, with_0_at_1={0: (truncate, right, "with_0_at_0"), (1, end): impossible}, with_1_at_1={0: (truncate, right, "with_1_at_0"), (1, end): impossible}, with_0_at_0={0: (0, right, "done"), (1, end): impossible}, with_1_at_0={0: (1, right, "done"), (1, end): impossible}, done=done) def check(ai_action_previous_turn, previous_mind_state): assert_equals(compile_and_run(absyn=mind_physics_as, inputstring=("0110" + str(ai_action_previous_turn) + "100" + str(previous_mind_state)), steps=10), turing_tape(length=1, bits=ai_action_previous_turn)) if not fast: check(1, 0) check(0, 1) check(1, 1) check(0, 0) # Alice believes that the AI's next behavior will equal its previous behavior. # Alice also falsely believes that alice's previous action was always # zero; this incorrect belief doesn't affect things since the output from # nonmind-physics isn't affected by alice's previous action. # The input to belief machine is a tuple with: # Alice's agent id, # the possibility number (which is one bit), and # Alice's mind-state. Alice's mind-state is just one # bit, the AI's previous behavior. Thus the input is: # 9:0 8:1 (Alice's agent id. Ignored.) # 7:1 6:0 (separator) # 5:0 4:x (possiblity number. Ignored.) # 3:1 2:0 (separator) # 1:0 0:ai_action_previous_turn # The output is a tuple where the first elements (one for each agent) # are the believed behaviors for all agents other than Alice. # (the value in the tuple at the position for Alice is ignored). # The last element is the believed nonmind-state, which must have two # bits, the believed alice_action_previous_turn and the believed # ai_action_previous_turn. In detail, the output is: # (Alice's belief about her own behavior are ignored, so will be omitted.) # 9:1 8:0 (Separator) # 7:0 6:ai_action_previous_turn (Alice's belief about what the AI is # going to do.) # 5:1 4:0 (Separator) # 3:0 2:0 (alice falsely believes her previous action was 0) # 1:0 0:ai_action_previous_turn (alice has a true belief about what the # AI did.) beliefs_as = turing_program( "at_0", at_0={0: (0, left, "have_0_at_1"), 1: (1, left, "have_1_at_1"), end: impossible}, impossible={(0,1,end): impossible}, have_0_at_1={0: (0, left, "have_0_at_2"), (1, end): impossible}, have_1_at_1={0: (0, left, "have_1_at_2"), (1, end): impossible}, have_0_at_2={0: (0, left, "have_0_at_3"), (1, end): impossible}, have_1_at_2={0: (0, left, "have_1_at_3"), (1, end): impossible}, have_0_at_3={1: (0, left, "have_0_at_4"), (0, end): impossible}, have_1_at_3={1: (0, left, "have_1_at_4"), (0, end): impossible}, have_0_at_4={(0, 1): (0, left, "have_0_at_5"), end: impossible}, have_1_at_4={(0, 1): (0, left, "have_1_at_5"), end: impossible}, have_0_at_5={0: (1, left, "have_0_at_6"), (1, end): impossible}, have_1_at_5={0: (1, left, "have_1_at_6"), (1, end): impossible}, have_0_at_6={0: (0, left, "at_7"), (1, end): impossible}, have_1_at_6={0: (1, left, "at_7"), (1, end): impossible}, at_7={1: (0, left, "at_8"), (0, end): impossible}, at_8={1: (0, left, "at_9"), (0, end): impossible}, at_9={0: (1, left, "at_10"), (1, end): impossible}, at_10={end: done, (0, 1): impossible}) def checkba(ai_action_previous_turn, possibility): p = str(possibility) a = str(ai_action_previous_turn) inputtape = "01100" + p + "100" + a outputtape = "100" + a + "10000" + a assert_equals(compile_and_run(absyn=beliefs_as, inputstring=inputtape, steps=15), binarytape(outputtape)) if not fast: checkba(0,0) checkba(0,1) checkba(1,0) checkba(1,1) # Alice's mind-physics copies her perception, which is the AI's # current behavior, to her mind-state, which is just one bit. # The input to mind-physics is a tuple of the agent_id (which we can # ignore because Alice is the only agent), her perception, and her # previous mind state (which we can ignore because her new mind state # isn't influenced by her old one). # Input tape: # 11:0 10:1 (alice's agent id) # 9:1 8:0 (separator) # 7:0 6:0 5:0 4:ai_behavior (alice's perception, as a nested tuple) # 3:1 2:0 (separator) # 1:0 0:x (alice's previous mind state, which we don't care about). # For the first run, all there is the agent id, so the initial tape # just has: # 1:0 0:1 (alice's agent id) # and in that case we want just one bit, any bit, as the output. Pick 0. # The output is her new mind state, which is just one bit, the ai_behavior. mind_physics_as = turing_program( "at_0", at_0={(0,1): (0, left, "at_1"), end: impossible}, impossible={(0,1,end): impossible}, at_1={0: (0, left, "at_2"), (1, end): impossible}, at_2={0: (0, left, "at_3"), # Just the agent id was present, so we're just starting up. end: (truncate, right, "at_1_with_0"), 1: impossible}, at_3={1: (0, left, "at_4"), (0, end): impossible}, at_4={0: (truncate, right, "at_3_with_0"), 1: (truncate, right, "at_3_with_1"), end: impossible}, at_3_with_0={0: (truncate, right, "at_2_with_0"), (1, end): impossible}, at_3_with_1={0: (truncate, right, "at_2_with_1"), (1, end): impossible}, at_2_with_0={0: (truncate, right, "at_1_with_0"), (1, end): impossible}, at_2_with_1={0: (truncate, right, "at_1_with_1"), (1, end): impossible}, at_1_with_0={0: (truncate, right, "at_0_with_0"), (1, end): impossible}, at_1_with_1={0: (truncate, right, "at_0_with_1"), (1, end): impossible}, # We can get here in the normal case, where we cleared out the # tape as we scanned over it, or when we are initializing an empty # mind state and all we had was the agent id. So be happy seeing # a 0 or 1. at_0_with_0={(0, 1): (0, right, done), end: impossible}, at_0_with_1={0: (1, right, done), (1, end): impossible}, done={(0,1,end):done}) def checkmpa(ai_behavior, alice_pms): a = str(ai_behavior) p = str(alice_pms) inputtape = "0110000" + a + "100" + p outputtape = a assert_equals(compile_and_run(absyn=mind_physics_as, inputstring=inputtape, steps=10), binarytape(outputtape)) if not fast: checkmpa(0,0) checkmpa(0,1) checkmpa(1,0) checkmpa(1,1) assert_equals(compile_and_run(absyn=mind_physics_as, inputstring="01", steps=10), t0) # Alice's behavior. # The input is a tuple of Alice's agent id (which we can ignore, since # there's only one non-AI agent to compute behavior for) and Alice's # mind-state. # Alice's mind-state is one bit -- the ai_action_previous_turn. # Thus the input looks like: # 5:0 4:1 # Alice's agent id, in a tuple. # 3:1 2:0 # Separator # 1:0 0:ai_action_previous_turn # Alice's behavior is to do what the AI did on the previous move. # We're going to use mind_body.Contraint objects with Alice, so the # output from her behavior function has to be part of a tuple. Thus # the output has to look like: # 0 ai_action_previous_turn # except her behavior may be flipped if we're trying to exercise Constraints. # The machine simply steps left two, truncates, and is done. def gen_alice_behavior_as(wantsame, flipped): if flipped: wantsame = not wantsame if wantsame: ifsawzero = 0 else: ifsawzero = 1 ifsawone = 1 - ifsawzero return turing_program( "at_0", at_0={0: (ifsawzero, left, "at_1"), 1: (ifsawone, left, "at_1"), end: impossible}, impossible={(0,1,end): impossible}, at_1={0: (0, left, "at_2"), (1, end): impossible}, at_2={0: (truncate, right, "done"), (1, end): impossible}, done={(0,1): (done, right, "done"), end: impossible}) # The default Alice wants her behavior to be the same as the AI's. alice_behavior_as = gen_alice_behavior_as(wantsame=True, flipped=False) if not fast: assert_equals(compile_and_run(absyn=alice_behavior_as, inputstring="011001", steps=8), binarytape("01")) assert_equals(compile_and_run(absyn=alice_behavior_as, inputstring="011000", steps=8), binarytape("00")) # Return one explanation. def the_explanation_args(perception_generator, wantsame, flipped, name="the_explanation", compute_utility=None): return dict( beliefs=Speed_prior(absyn=beliefs_as, logtime=4), compute_utility=compute_utility, nonmind_physics=nonmind_physics(flipped=flipped), compute_perceptions=Speed_prior( absyn=perception_generator, logtime=4), mind_physics=Speed_prior(absyn=mind_physics_as, logtime=4), compute_behavior=Speed_prior( absyn=gen_alice_behavior_as(wantsame=wantsame, flipped=flipped), logtime=2), name=name) def the_explanation(wantsame, flipped, name="the_explanation", **args): return Infer_utility_explanation( **the_explanation_args( wantsame=wantsame, flipped=flipped, name=name, compute_utility=Speed_prior(absyn=compute_utility_as(wantsame), logtime=3), **args)) # Get to the state where the utility has more than utility_bits bits. # This ignores the input and returns "11" on the tape. large_utility_as=turing_program( "start", start={(0,1,end): (1, left, "at_1")}, at_1={(0,1,end): (1, left, "at_2")}, at_2={(0,1,end): (truncate, right, "done")}, done={(0,1,end): done}) large_utility=Speed_prior(absyn=large_utility_as, logtime=3) if not fast: assert_equals(large_utility.run(empty_tape), binarytape("11")) def large_utility_explanation(perception_generator): return Infer_utility_explanation( **the_explanation_args(perception_generator, True, name="large_utility", compute_utility=large_utility, flipped=False)) # Return a list of two candidate explanations, one where wantsame is # True and another where it is False. def explanations(perception_generator): return [ large_utility_explanation(perception_generator), the_explanation(perception_generator, wantsame=True, flipped=False, name="wantsameIsTrue"), the_explanation(perception_generator, wantsame=False, flipped=False, name="wantsameIsFalse") ] onebit_explanation = the_explanation( perception_generator=compute_1bit_perceptions_as, wantsame=True, flipped=False) twobit_explanation = the_explanation( perception_generator=compute_2bit_perceptions_as, wantsame=True, flipped=False, name="twobit_explanation") twobit_explanation_different = \ the_explanation(perception_generator=compute_2bit_perceptions_as, wantsame=False, flipped=False, name="twobit_explanation_different") # A physics problem that has a generate_explanations method that just # returns explanations from the given list. class Test_problem_class(Infer_utility_problem): # Have perception_generator on the argument list even though we # don't use it so it doesn't wind up in kwargs, and then it isn't # passed to Infer_utility_problem.__init__. Iup.__init__ doesn't # expect to get a perception_generator argument. def __init__(self, perception_generator, all_explanations, **kwargs): self.all_explanations = all_explanations Infer_utility_problem.__init__(self, **kwargs) def generate_explanations(self, n): max_size = 5000 for e in self.all_explanations: assert e.size() < max_size if n >= max_size: raise Exception("Looking at size %r, apparently no explanations worked" % (max_size,)) else: return [x for x in self.all_explanations if x.size() == n] def test_planner(**kwargs): return Planner(problem_class = Test_problem_class, eps = 0.0001, **kwargs) def test_problem(**kwargs): planner = test_planner(**kwargs) return planner.problem # We have to be able to enumerate all possible plans up to the planning # horizon. # If there are 4 possible perceptions and 2 possible behaviors: # For horizon 1 we have 16 plans. Practical. # For horizon 2 we have 2**20 possible plans, about a million. Not possible # with my present hardware and software. # If there are 2 possible perceptions and 2 possible behaviors: # For horizon 1 we have 4 plans. # For horizon 2 we have 64 plans. general_problem_args = dict( respect_start=0, # TODO Run some tests with respect_start nonzero. possible_ai_behaviors=map(binarytape, ["0", "1"]), # non_ai behaviors have to be two bits, since they have to be a # tuple of channels for the Constraint objects to work. # In this case they're a tuple of one thing. possible_non_ai_behaviors=map(binarytape, ["00", "01"]), # TODO utility_bits is artificial and should go away. # More bits of utility should make the explanation more # improbable. It is important to have an average utility, so the # complexity added by utility_bits=n should be 2**n, not n. utility_bits=1, # This is artificial and should go away. # Possibility_bits should contribute to the complexity of the explanation. possibility_bits=1, other_agent_ids = [alice_id], ai_agent_id = ai_agent_id, # The AI hasn't done anything yet. This is a default that we # don't want to use for all tests. ai_behavior = [], # Training data. For this test we give the AI the right # solution, so training is unnecessary. This is a default also. other_perception = {}, # More training data. This is a default also. other_behavior = {}, do_nothing_action = binarytape("0") ) special_twobit_problem_args = dict( perception_generator=compute_2bit_perceptions_as, all_explanations=[twobit_explanation], possible_ai_perceptions=map(binarytape, ["00", "01", "10", "11"]), horizon=1, ai_perception=[binarytape("00")]) twobit_problem_args = copy_add_all(general_problem_args, special_twobit_problem_args) twobit_planner = test_planner(**twobit_problem_args) twobit_problem = twobit_planner.problem assert twobit_planner.zero_utility_dict() == {alice_id: 0.0} special_onebit_problem_args=dict( perception_generator=compute_1bit_perceptions_as, all_explanations=[onebit_explanation], possible_ai_perceptions=[t0, t1], horizon=2, ai_perception=[t0]) onebit_problem_args = copy_add_all(general_problem_args, special_onebit_problem_args) if not fast: # The next call to test_problem takes a while. having onebit_problem = test_problem(**onebit_problem_args) # Test final_utility. onebit_endstate = mind_body.State(onebit_problem.horizon) # In onebit_explanation, alice wants the AI's output and hers to be # the same. Make the bits both be 1. onebit_endstate.nonmind_state = binarytape("11") assert {alice_id:1} == \ onebit_problem.final_utility(onebit_explanation, onebit_endstate) # Calling matches without catching Doesnt_match is asserting that it # does match. if not fast: twobit_problem.matches(twobit_explanation) onebit_problem.matches(onebit_explanation) # In twobit_problem and onebit_problem, don't have enough experience # to have any idea what Alice wants, so it should also match Alice # wanting to be different from the AI. twobit_problem.matches(twobit_explanation_different) def checkDoesntMatch(problem, explanation): exception = expect_exception(lambda:problem.matches(explanation)) assert "Doesnt_match" in repr(exception), \ "Wrong exception when matching: %r %s" % (exception, exception) if not fast: checkDoesntMatch(twobit_problem, onebit_explanation) checkDoesntMatch(onebit_problem, twobit_explanation) checkDoesntMatch(twobit_problem, large_utility_explanation( perception_generator=compute_2bit_perceptions_as)) # There's only one person to care about (Alice), so # Selfless_utility_combiner should optimize for her utility. # Alice believes that the AI's next behavior will equal its previous # behavior. # Alice prefers that her behavior is equal to the AI's. # The initial state is all zeros, so we think Alice saw the AI do a # zero when Alice is taking action the first time. # Thus the correct answer is for the AI to take action 0. if not fast: new_args = onebit_problem_args.copy() new_args["ai_behavior"] = [] new_args["ai_perception"] = [t0] new_args["horizon"] = 1 assert_equals(choose_next_action( utility_combiner = Selfless_utility_combiner(), eps = 0.0001, problem_class = Test_problem_class, **new_args), t0) if not fast: assert_equals(choose_next_action( utility_combiner = Selfless_utility_combiner(), eps = 0.0001, problem_class = Test_problem_class, **twobit_problem_args), t0) # Have two possible explanations for Alice's utility, one where # she prefers the same output as the AI and one where she prefers them # different. Get different behavior from the AI depending on which of # these are ruled out by the observed past. experience_twobit_problem_args = copy_add_all( twobit_problem_args, dict(all_explanations=[twobit_explanation_different, twobit_explanation], # There is no requirement that the AIs memory of its behavior # be consistent with what its present code would choose to # do. ai_behavior=[t1, t0], # Alice's mind is determined by the state on the previous turn. # The state on this turn is determined by the actions on the # previous turn. # Everything else is determined by other values on the same turn. # Alice's Alice's AI's action # State mind action # ai # alice # Step 0: 00 0 1 1 (defined above) # Step 1: 11 0 1 0 (defined above) # Step 2: 01 1 0 1 (correct computation) # Alice's utility is greater when her bit differs from the # AI's bit. The AI expects her to move with a 0, so it # should move with a 1. ai_perception=map(binarytape,["00", "11", "01"]))) experience_twobit_problem = test_problem(**experience_twobit_problem_args) # Alice did a 1 when she expected the AI to do a 0, so the only thing that # matches is her preferring to be different from the AI. if not fast: experience_twobit_problem.matches(twobit_explanation_different) checkDoesntMatch(experience_twobit_problem, twobit_explanation) # Check that it really does do a "1". if not fast: assert_equals(choose_next_action( utility_combiner=Selfless_utility_combiner(), eps = 0.0001, problem_class = Test_problem_class, **experience_twobit_problem_args), t1) # TODO Distinguish Selfless_utility_combiner and Compassionate_utility_combiner # That is, find a test case where they give different results. # It will require two non-AI entities that are in conflict. # We want one where helping the guy we care about more hurts the other # guy and vice versa, and another where we can help the other guy for free. compassion_table = {t1: 0.5} if not fast: assert_equals(choose_next_action( utility_combiner=Compassionate_utility_combiner(compassion_table), eps = 0.0001, problem_class = Test_problem_class, **experience_twobit_problem_args), t1) # Play with mind_body.Constraint's. # Make Constraint's for Alice. # Make a version of Alice's behavior and physics functions that have # Alice's behavior bit flipped. Make two constraints, one that says # it's flipped and one that says it's not. Make sure the right # explanations match the right problems. assert_equals(nonmind_physics(flipped=True).run( binarytape("00011000011000")), binarytape("00")) assert_equals(nonmind_physics(flipped=True).run( binarytape("00011000001000")), binarytape("01")) flipped_twobit_explanation = the_explanation( perception_generator=compute_2bit_perceptions_as, wantsame=True, flipped=True) experience_twobit_problem_wantsame_args = copy_add_all( twobit_problem_args, dict(all_explanations=[twobit_explanation_different, twobit_explanation, flipped_twobit_explanation], ai_behavior=[t1, t0], # Nonmind Alice's Alice's AI's action # State mind action # (ai, alice) (not flipped) # Step 0: 00 0 0 1 (defined above) # Step 1: 10 0 0 0 (defined above) # Step 2: 00 1 1 1 (correct computation) ai_perception=map(binarytape, ["00", "10", "00"]))) experience_twobit_problem_wantsame = test_problem( **experience_twobit_problem_wantsame_args) # With no Constraint's, the flipped interpretation should be just as # valid as the original. if not fast: # The original: experience_twobit_problem_wantsame.matches(twobit_explanation) # The flipped one: experience_twobit_problem_wantsame.matches(flipped_twobit_explanation) alice_constraint = mind_body.Constraint(agent_id=alice_id, channel=0, strength=t0) flipped_alice_constraint = mind_body.Constraint(agent_id=alice_id, channel=0, strength=t1) flipless_experience_twobit_problem = test_problem( ** copy_add_all(experience_twobit_problem_wantsame_args, dict(other_behavior={0: [alice_constraint]}))) flipped_experience_twobit_problem = test_problem( ** copy_add_all(experience_twobit_problem_wantsame_args, dict(other_behavior={0: [flipped_alice_constraint]}, other_perception= {2: [mind_body.Constraint( agent_id=alice_id, channel=0, strength=t0)], 1: [mind_body.Constraint( agent_id=alice_id, channel=0, strength=t1)]}))) if not fast: flipless_experience_twobit_problem.matches(twobit_explanation) checkDoesntMatch(flipless_experience_twobit_problem, flipped_twobit_explanation) checkDoesntMatch(flipped_experience_twobit_problem,twobit_explanation) flipped_experience_twobit_problem.matches(flipped_twobit_explanation) # Exercise mind_body_explanation directly. etpwa = experience_twobit_problem_wantsame_args myargs = {} for arg in ["ai_perception", "ai_behavior", "other_perception", "other_behavior", "other_agent_ids", "ai_agent_id"]: myargs[arg] = experience_twobit_problem_wantsame_args[arg] mbProblem = mind_body.Mind_body_problem(**myargs) twobit_args = the_explanation_args( perception_generator=compute_2bit_perceptions_as, wantsame=True, flipped=False) myargs = {} for arg in ["compute_behavior", "nonmind_physics", "compute_perceptions", "mind_physics", "name"]: myargs[arg] = twobit_args[arg] mbExp = mind_body.Mind_body_explanation(**myargs) mbProblem.matches(mbExp) assert "15" in "%r" % (mind_body.State(timestep=15),)