def run_univariate_function(name, symbol_fmt, func): print('\n*************************************') print('Testing {} function'.format(name)) # PsychSim elements world = World() agent = Agent('The Agent') world.addAgent(agent) # gets samples from real non-linear function x_params, y_params, sample_values = \ get_bivariate_samples(func, MIN_X, MAX_X, MIN_Y, MAX_Y, NUM_SAMPLES, NUM_SAMPLES) sample_mean = np.nanmean(sample_values) # create two features: one holding the variable, the other the result (dependent) var_x = world.defineState(agent.name, 'var_x', float, lo=MIN_X, hi=MAX_X) var_y = world.defineState(agent.name, 'var_y', float, lo=MIN_Y, hi=MAX_Y) result = world.defineState(agent.name, 'result', float, lo=np.min(sample_values), hi=np.max(sample_values)) world.setFeature(result, 0) # create action that is approximates the function, storing the result in the result feature action = agent.addAction({'verb': 'operation', 'action': name}) tree = makeTree( tree_from_bivariate_samples(result, var_x, var_y, x_params, y_params, sample_values)) world.setDynamics(result, action, tree) world.setOrder([agent.name]) np.random.seed(SEED) values_original = [] values_approx = [] for i in range(NUM_TEST_SAMPLES): # gets random sample parameters x = MIN_X + np.random.rand() * (MAX_X - MIN_X) y = MIN_Y + np.random.rand() * (MAX_Y - MIN_Y) # sets variable and updates result world.setFeature(var_x, x) world.setFeature(var_y, y) world.step() real = func(x, y) psych = world.getValue(result) print('{:3}: {:30} | Expected: {:10.2f} | PsychSim: {:10.2f}'.format( i, symbol_fmt.format(x, y), real, psych)) values_original.append(real) values_approx.append(psych) # gets error stats rmse = np.sqrt(np.mean((np.array(values_approx) - values_original)**2)) print('=====================================') print('RMSE = {:.3f}'.format(rmse)) print('\nPress \'Enter\' to continue...') input()
world = World() agent = Agent('Agent') world.addAgent(agent) # add feature to world feat = world.defineState(agent.name, 'x', float, lo=LOW, hi=HIGH) print('====================================') print('High:\t{}'.format(HIGH)) print('Low:\t{}'.format(LOW)) print('Bins:\t{}'.format(NUM_BINS)) print('\nSamples:') values_original = [] values_discrete = [] for i in range(NUM_SAMPLES): num = np.random.uniform(LOW, HIGH) world.setFeature(feat, num) before = world.getValue(feat) discretize_feature_in_place(world, feat, NUM_BINS) after = world.getValue(feat) print('{:.3f}\t-> {}'.format(before, after)) values_original.append(before) values_discrete.append(after) # calculates RMSE rmse = np.sqrt(np.mean((np.array(values_discrete) - values_original)**2)) print('\nRMSE: {:.3f}'.format(rmse))
def scenarioCreationUseCase(enemy='Sylvania', model='powell', web=False, fCollapse=None, sCollapse=None, maxRounds=15): """ An example of how to create a scenario @param enemy: the name of the agent-controlled side, i.e., Freedonia's opponent (default: Sylvania) @type enemy: str @param model: which model do we use (default is "powell") @type model: powell or slantchev @param web: if C{True}, then create the web-based experiment scenario (default: C{False}) @type web: bool @param fCollapse: the probability that Freedonia collapses (under powell, default: 0.1) or loses battle (under slantchev, default: 0.7) @type fCollapse: float @param sCollapse: the probability that Sylvania collapses, under powell (default: 0.1) @type sCollapse: float @param maxRounds: the maximum number of game rounds (default: 15) @type maxRounds: int @return: the scenario created @rtype: L{World} """ # Handle defaults for battle probabilities, under each model posLo = 0 posHi = 10 if fCollapse is None: if model == 'powell': fCollapse = 0.1 elif model == 'slantchev': fCollapse = 0.7 if sCollapse is None: sCollapse = 0.1 # Create scenario world = World() # Agents free = Agent('Freedonia') world.addAgent(free) sylv = Agent(enemy) world.addAgent(sylv) # User state world.defineState(free.name, 'troops', int, lo=0, hi=50000, description='Number of troops you have left') free.setState('troops', 40000) world.defineState( free.name, 'territory', int, lo=0, hi=100, description='Percentage of disputed territory owned by you') free.setState('territory', 15) world.defineState(free.name, 'cost', int, lo=0, hi=50000, description='Number of troops %s loses in an attack' % (free.name)) free.setState('cost', 2000) world.defineState( free.name, 'position', int, lo=posLo, hi=posHi, description='Current status of war (%d=%s is winner, %d=you are winner)' % (posLo, sylv.name, posHi)) free.setState('position', 5) world.defineState( free.name, 'offered', int, lo=0, hi=100, description= 'Percentage of disputed territory that %s last offered to you' % (sylv.name)) free.setState('offered', 0) if model == 'slantchev': # Compute new value for territory only *after* computing new value for position world.addDependency(stateKey(free.name, 'territory'), stateKey(free.name, 'position')) # Agent state world.defineState(sylv.name, 'troops', int, lo=0, hi=500000, description='Number of troops %s has left' % (sylv.name)) sylv.setState('troops', 30000) world.defineState(sylv.name, 'cost', int, lo=0, hi=50000, description='Number of troops %s loses in an attack' % (sylv.name)) sylv.setState('cost', 2000) world.defineState( sylv.name, 'offered', int, lo=0, hi=100, description= 'Percentage of disputed territory that %s last offered to %s' % (free.name, sylv.name)) sylv.setState('offered', 0) # World state world.defineState(None, 'treaty', bool, description='Have the two sides reached an agreement?') world.setState(None, 'treaty', False) # Stage of negotiation, illustrating the use of an enumerated state feature world.defineState( None, 'phase', list, ['offer', 'respond', 'rejection', 'end', 'paused', 'engagement'], description='The current stage of the negotiation game') world.setState(None, 'phase', 'paused') # Game model, static descriptor world.defineState(None, 'model', list, ['powell', 'slantchev'], description='The model underlying the negotiation game') world.setState(None, 'model', model) # Round of negotiation world.defineState(None, 'round', int, description='The current round of the negotiation') world.setState(None, 'round', 0) if not web: # Relationship value key = world.defineRelation(free.name, sylv.name, 'trusts') world.setFeature(key, 0.) # Game over if there is a treaty world.addTermination( makeTree({ 'if': trueRow(stateKey(None, 'treaty')), True: True, False: False })) # Game over if Freedonia has no territory world.addTermination( makeTree({ 'if': thresholdRow(stateKey(free.name, 'territory'), 1), True: False, False: True })) # Game over if Freedonia has all the territory world.addTermination( makeTree({ 'if': thresholdRow(stateKey(free.name, 'territory'), 99), True: True, False: False })) # Game over if number of rounds exceeds limit world.addTermination( makeTree({ 'if': thresholdRow(stateKey(None, 'round'), maxRounds), True: True, False: False })) # Turn order: Uncomment the following if you want agents to act in parallel # world.setOrder([set(world.agents.keys())]) # Turn order: Uncomment the following if you want agents to act sequentially world.setOrder([free.name, sylv.name]) # User actions freeBattle = free.addAction({'verb': 'attack', 'object': sylv.name}) for amount in range(20, 100, 20): free.addAction({ 'verb': 'offer', 'object': sylv.name, 'amount': amount }) if model == 'powell': # Powell has null stages freeNOP = free.addAction({'verb': 'continue'}) elif model == 'slantchev': # Slantchev has both sides receiving offers free.addAction({'verb': 'accept offer', 'object': sylv.name}) free.addAction({'verb': 'reject offer', 'object': sylv.name}) # Agent actions sylvBattle = sylv.addAction({'verb': 'attack', 'object': free.name}) sylvAccept = sylv.addAction({'verb': 'accept offer', 'object': free.name}) sylvReject = sylv.addAction({'verb': 'reject offer', 'object': free.name}) if model == 'powell': # Powell has null stages sylvNOP = sylv.addAction({'verb': 'continue'}) elif model == 'slantchev': # Slantchev has both sides making offers for amount in range(10, 100, 10): sylv.addAction({ 'verb': 'offer', 'object': free.name, 'amount': amount }) # Restrictions on when actions are legal, based on phase of game for action in filterActions({'verb': 'offer'}, free.actions | sylv.actions): agent = world.agents[action['subject']] agent.setLegal( action, makeTree({ 'if': equalRow(stateKey(None, 'phase'), 'offer'), True: True, # Offers are legal in the offer phase False: False })) # Offers are illegal in all other phases if model == 'powell': # Powell has a special rejection phase for action in [freeNOP, freeBattle]: free.setLegal( action, makeTree({ 'if': equalRow(stateKey(None, 'phase'), 'rejection'), True: True, # Attacking and doing nothing are legal only in rejection phase False: False }) ) # Attacking and doing nothing are illegal in all other phases # Once offered, agent can respond if model == 'powell': # Under Powell, only Sylvania has to respond, and it can attack responses = [sylvBattle, sylvAccept, sylvReject] elif model == 'slantchev': # Under Slantchev, only accept/reject responses = filterActions({'verb': 'accept offer'}, free.actions | sylv.actions) responses += filterActions({'verb': 'reject offer'}, free.actions | sylv.actions) for action in responses: agent = world.agents[action['subject']] agent.setLegal( action, makeTree({ 'if': equalRow(stateKey(None, 'phase'), 'respond'), True: True, # Offeree must act in the response phase False: False })) # Offeree cannot act in any other phase if model == 'powell': # NOP is legal in exactly opposite situations to all other actions sylv.setLegal( sylvNOP, makeTree({ 'if': equalRow(stateKey(None, 'phase'), 'end'), True: True, # Sylvania does not do anything in the null phase after Freedonia responds to rejection False: False })) # Sylvania must act in its other phases if model == 'slantchev': # Attacking legal only under engagement phase for action in filterActions({'verb': 'attack'}, free.actions | sylv.actions): agent = world.agents[action['subject']] agent.setLegal( action, makeTree({ 'if': equalRow(stateKey(None, 'phase'), 'engagement'), True: True, # Attacking legal only in engagement False: False })) # Attacking legal every other phase # Goals for Freedonia goalFTroops = maximizeFeature(stateKey(free.name, 'troops')) free.setReward(goalFTroops, 1.) goalFTerritory = maximizeFeature(stateKey(free.name, 'territory')) free.setReward(goalFTerritory, 1.) # Goals for Sylvania goalSTroops = maximizeFeature(stateKey(sylv.name, 'troops')) sylv.setReward(goalSTroops, 1.) goalSTerritory = minimizeFeature(stateKey(free.name, 'territory')) sylv.setReward(goalSTerritory, 1.) # Possible goals applicable to both goalAgreement = maximizeFeature(stateKey(None, 'treaty')) # Silly goal, provided as an example of an achievement goal goalAchieve = achieveFeatureValue(stateKey(None, 'phase'), 'respond') # Horizons if model == 'powell': free.setAttribute('horizon', 4) sylv.setAttribute('horizon', 4) elif model == 'slantchev': free.setAttribute('horizon', 6) sylv.setAttribute('horizon', 6) # Discount factors free.setAttribute('discount', -1) sylv.setAttribute('discount', -1) # Levels of belief free.setRecursiveLevel(2) sylv.setRecursiveLevel(2) # Dynamics of battle freeTroops = stateKey(free.name, 'troops') freeTerr = stateKey(free.name, 'territory') sylvTroops = stateKey(sylv.name, 'troops') # Effect of fighting for action in filterActions({'verb': 'attack'}, free.actions | sylv.actions): # Effect on troops (cost of battle) tree = makeTree( addFeatureMatrix(freeTroops, stateKey(free.name, 'cost'), -1.)) world.setDynamics(freeTroops, action, tree, enforceMin=not web) tree = makeTree( addFeatureMatrix(sylvTroops, stateKey(sylv.name, 'cost'), -1.)) world.setDynamics(sylvTroops, action, tree, enforceMin=not web) if model == 'powell': # Effect on territory (probability of collapse) tree = makeTree({ 'distribution': [ ( { 'distribution': [ (setToConstantMatrix(freeTerr, 100), 1. - fCollapse ), # Sylvania collapses, Freedonia does not (noChangeMatrix(freeTerr), fCollapse) ] }, # Both collapse sCollapse), ( { 'distribution': [ (setToConstantMatrix(freeTerr, 0), fCollapse ), # Freedonia collapses, Sylvania does not (noChangeMatrix(freeTerr), 1. - fCollapse) ] }, # Neither collapses 1. - sCollapse) ] }) world.setDynamics(freeTerr, action, tree) elif model == 'slantchev': # Effect on position pos = stateKey(free.name, 'position') tree = makeTree({ 'distribution': [ (incrementMatrix(pos, 1), 1. - fCollapse), # Freedonia wins battle (incrementMatrix(pos, -1), fCollapse) ] }) # Freedonia loses battle world.setDynamics(pos, action, tree) # Effect on territory tree = makeTree({ 'if': thresholdRow(pos, posHi - .5), True: setToConstantMatrix(freeTerr, 100), # Freedonia won False: { 'if': thresholdRow(pos, posLo + .5), True: noChangeMatrix(freeTerr), False: setToConstantMatrix(freeTerr, 0) } }) # Freedonia lost world.setDynamics(freeTerr, action, tree) # Dynamics of offers for index in range(2): atom = Action({ 'subject': world.agents.keys()[index], 'verb': 'offer', 'object': world.agents.keys()[1 - index] }) if atom['subject'] == free.name or model != 'powell': offer = stateKey(atom['object'], 'offered') amount = actionKey('amount') tree = makeTree({ 'if': trueRow(stateKey(None, 'treaty')), True: noChangeMatrix(offer), False: setToConstantMatrix(offer, amount) }) world.setDynamics(offer, atom, tree, enforceMax=not web) # Dynamics of treaties for action in filterActions({'verb': 'accept offer'}, free.actions | sylv.actions): # Accepting an offer means that there is now a treaty key = stateKey(None, 'treaty') tree = makeTree(setTrueMatrix(key)) world.setDynamics(key, action, tree) # Accepting offer sets territory offer = stateKey(action['subject'], 'offered') territory = stateKey(free.name, 'territory') if action['subject'] == free.name: # Freedonia accepts sets territory to last offer tree = makeTree(setToFeatureMatrix(territory, offer)) world.setDynamics(freeTerr, action, tree) else: # Sylvania accepts sets territory to 1-last offer tree = makeTree( setToFeatureMatrix(territory, offer, pct=-1., shift=100.)) world.setDynamics(freeTerr, action, tree) # Dynamics of phase phase = stateKey(None, 'phase') roundKey = stateKey(None, 'round') # OFFER -> RESPOND for index in range(2): action = Action({ 'subject': world.agents.keys()[index], 'verb': 'offer', 'object': world.agents.keys()[1 - index] }) if action['subject'] == free.name or model != 'powell': tree = makeTree(setToConstantMatrix(phase, 'respond')) world.setDynamics(phase, action, tree) # RESPOND -> REJECTION or ENGAGEMENT for action in filterActions({'verb': 'reject offer'}, free.actions | sylv.actions): if model == 'powell': tree = makeTree(setToConstantMatrix(phase, 'rejection')) elif model == 'slantchev': tree = makeTree(setToConstantMatrix(phase, 'engagement')) world.setDynamics(phase, action, tree) # accepting -> OFFER for action in filterActions({'verb': 'accept offer'}, free.actions | sylv.actions): tree = makeTree(setToConstantMatrix(phase, 'offer')) world.setDynamics(phase, action, tree) # attacking -> OFFER for action in filterActions({'verb': 'attack'}, free.actions | sylv.actions): tree = makeTree(setToConstantMatrix(phase, 'offer')) world.setDynamics(phase, action, tree) if action['subject'] == sylv.name or model == 'slantchev': tree = makeTree(incrementMatrix(roundKey, 1)) world.setDynamics(roundKey, action, tree) if model == 'powell': # REJECTION -> END for atom in [freeNOP, freeBattle]: tree = makeTree(setToConstantMatrix(phase, 'end')) world.setDynamics(phase, atom, tree) # END -> OFFER atom = Action({'subject': sylv.name, 'verb': 'continue'}) tree = makeTree(setToConstantMatrix(phase, 'offer')) world.setDynamics(phase, atom, tree) tree = makeTree(incrementMatrix(roundKey, 1)) world.setDynamics(roundKey, atom, tree) if not web: # Relationship dynamics: attacking is bad for trust atom = Action({ 'subject': sylv.name, 'verb': 'attack', 'object': free.name }) key = binaryKey(free.name, sylv.name, 'trusts') tree = makeTree(approachMatrix(key, 0.1, -1.)) world.setDynamics(key, atom, tree) # Handcrafted policy for Freedonia # free.setPolicy(makeTree({'if': equalRow('phase','respond'), # # Accept an offer greater than 50 # True: {'if': thresholdRow(stateKey(free.name,'offered'),50), # True: Action({'subject': free.name,'verb': 'accept offer','object': sylv.name}), # False: Action({'subject': free.name,'verb': 'reject offer','object': sylv.name})}, # False: {'if': equalRow('phase','engagement'), # # Attack during engagement phase # True: Action({'subject': free.name,'verb': 'attack','object': sylv.name}), # # Agent decides how what to do otherwise # False: False}})) # Mental models of enemy # Example of creating a model with incorrect reward all at once (a version of Freedonia who cares about reaching agreement as well) # sylv.addModel('false',R={goalSTroops: 10.,goalSTerritory: 1.,goalAgreement: 1.}, # rationality=1.,selection='distribution',parent=True) # Example of creating a model with incorrect beliefs sylv.addModel('false', rationality=10., selection='distribution', parent=True) key = stateKey(free.name, 'position') # Sylvania believes position to be fixed at 3 sylv.setBelief(key, 3, 'false') # Freedonia is truly unsure about position (50% chance of being 7, 50% of being 3) world.setModel(free.name, True) free.setBelief(key, Distribution({7: 0.5, 3: 0.5}), True) # Observations about military position tree = makeTree({ 'if': thresholdRow(key, 1), True: { 'if': thresholdRow(key, 9), True: { 'distribution': [(KeyedVector({key: 1}), 0.9), (KeyedVector({ key: 1, CONSTANT: -1 }), 0.1)] }, False: { 'distribution': [(KeyedVector({key: 1}), 0.8), (KeyedVector({ key: 1, CONSTANT: -1 }), 0.1), (KeyedVector({ key: 1, CONSTANT: 1 }), 0.1)] } }, False: { 'distribution': [(KeyedVector({key: 1}), 0.9), (KeyedVector({ key: 1, CONSTANT: 1 }), 0.1)] } }) free.defineObservation(key, tree) # Example of setting model parameters separately sylv.addModel('true', parent=True) sylv.setAttribute( 'rationality', 10., 'true') # Override real agent's rationality with this value sylv.setAttribute('selection', 'distribution', 'true') world.setMentalModel(free.name, sylv.name, {'false': 0.9, 'true': 0.1}) # Goal of fooling Sylvania goalDeception = achieveFeatureValue(modelKey(sylv.name), sylv.model2index('false')) return world
world.addAgent(agent1) agent2 = Agent('Agent 2') world.addAgent(agent2) agents_dec = [] agents = [agent1, agent2] for agent in agents: # set agent's params agent.setAttribute('discount', 1) agent.setAttribute('selection', TIEBREAK) # agent.setRecursiveLevel(1) # add "decision" variable (0 = didn't decide, 1 = Defected, 2 = Cooperated) dec = world.defineState(agent.name, 'decision', list, [NOT_DECIDED, DEFECTED, COOPERATED]) world.setFeature(dec, NOT_DECIDED) agents_dec.append(dec) # define agents' actions inspired on TIT-FOR-TAT: first decision is open, then retaliate non-cooperation. # as soon as one agent defects it will always defect from there on for i, agent in enumerate(agents): my_dec = agents_dec[i] other_dec = agents_dec[0 if i == 1 else 1] # defect (not legal if other has cooperated before, legal only if agent itself did not defect before) action = agent.addAction({ 'verb': '', 'action': 'defect' }, makeTree({ 'if': equalRow(other_dec, COOPERATED),
def scenarioCreationUseCase(enemy='Sylvania',model='powell',web=False, fCollapse=None,sCollapse=None,maxRounds=15): """ An example of how to create a scenario @param enemy: the name of the agent-controlled side, i.e., Freedonia's opponent (default: Sylvania) @type enemy: str @param model: which model do we use (default is "powell") @type model: powell or slantchev @param web: if C{True}, then create the web-based experiment scenario (default: C{False}) @type web: bool @param fCollapse: the probability that Freedonia collapses (under powell, default: 0.1) or loses battle (under slantchev, default: 0.7) @type fCollapse: float @param sCollapse: the probability that Sylvania collapses, under powell (default: 0.1) @type sCollapse: float @param maxRounds: the maximum number of game rounds (default: 15) @type maxRounds: int @return: the scenario created @rtype: L{World} """ # Handle defaults for battle probabilities, under each model posLo = 0 posHi = 10 if fCollapse is None: if model == 'powell': fCollapse = 0.1 elif model == 'slantchev': fCollapse = 0.7 if sCollapse is None: sCollapse = 0.1 # Create scenario world = World() # Agents free = Agent('Freedonia') world.addAgent(free) sylv = Agent(enemy) world.addAgent(sylv) # User state world.defineState(free.name,'troops',int,lo=0,hi=50000, description='Number of troops you have left') free.setState('troops',40000) world.defineState(free.name,'territory',int,lo=0,hi=100, description='Percentage of disputed territory owned by you') free.setState('territory',15) world.defineState(free.name,'cost',int,lo=0,hi=50000, description='Number of troops %s loses in an attack' % (free.name)) free.setState('cost',2000) world.defineState(free.name,'position',int,lo=posLo,hi=posHi, description='Current status of war (%d=%s is winner, %d=you are winner)' % (posLo,sylv.name,posHi)) free.setState('position',5) world.defineState(free.name,'offered',int,lo=0,hi=100, description='Percentage of disputed territory that %s last offered to you' % (sylv.name)) free.setState('offered',0) if model == 'slantchev': # Compute new value for territory only *after* computing new value for position world.addDependency(stateKey(free.name,'territory'),stateKey(free.name,'position')) # Agent state world.defineState(sylv.name,'troops',int,lo=0,hi=500000, description='Number of troops %s has left' % (sylv.name)) sylv.setState('troops',30000) world.defineState(sylv.name,'cost',int,lo=0,hi=50000, description='Number of troops %s loses in an attack' % (sylv.name)) sylv.setState('cost',2000) world.defineState(sylv.name,'offered',int,lo=0,hi=100, description='Percentage of disputed territory that %s last offered to %s' % (free.name,sylv.name)) sylv.setState('offered',0) # World state world.defineState(None,'treaty',bool, description='Have the two sides reached an agreement?') world.setState(None,'treaty',False) # Stage of negotiation, illustrating the use of an enumerated state feature world.defineState(None,'phase',list,['offer','respond','rejection','end','paused','engagement'], description='The current stage of the negotiation game') world.setState(None,'phase','paused') # Game model, static descriptor world.defineState(None,'model',list,['powell','slantchev'], description='The model underlying the negotiation game') world.setState(None,'model',model) # Round of negotiation world.defineState(None,'round',int,description='The current round of the negotiation') world.setState(None,'round',0) if not web: # Relationship value key = world.defineRelation(free.name,sylv.name,'trusts') world.setFeature(key,0.) # Game over if there is a treaty world.addTermination(makeTree({'if': trueRow(stateKey(None,'treaty')), True: True, False: False})) # Game over if Freedonia has no territory world.addTermination(makeTree({'if': thresholdRow(stateKey(free.name,'territory'),1), True: False, False: True}) ) # Game over if Freedonia has all the territory world.addTermination(makeTree({'if': thresholdRow(stateKey(free.name,'territory'),99), True: True, False: False})) # Game over if number of rounds exceeds limit world.addTermination(makeTree({'if': thresholdRow(stateKey(None,'round'),maxRounds), True: True, False: False})) # Turn order: Uncomment the following if you want agents to act in parallel # world.setOrder([set(world.agents.keys())]) # Turn order: Uncomment the following if you want agents to act sequentially world.setOrder([free.name,sylv.name]) # User actions freeBattle = free.addAction({'verb': 'attack','object': sylv.name}) for amount in range(20,100,20): free.addAction({'verb': 'offer','object': sylv.name,'amount': amount}) if model == 'powell': # Powell has null stages freeNOP = free.addAction({'verb': 'continue'}) elif model == 'slantchev': # Slantchev has both sides receiving offers free.addAction({'verb': 'accept offer','object': sylv.name}) free.addAction({'verb': 'reject offer','object': sylv.name}) # Agent actions sylvBattle = sylv.addAction({'verb': 'attack','object': free.name}) sylvAccept = sylv.addAction({'verb': 'accept offer','object': free.name}) sylvReject = sylv.addAction({'verb': 'reject offer','object': free.name}) if model == 'powell': # Powell has null stages sylvNOP = sylv.addAction({'verb': 'continue'}) elif model == 'slantchev': # Slantchev has both sides making offers for amount in range(10,100,10): sylv.addAction({'verb': 'offer','object': free.name,'amount': amount}) # Restrictions on when actions are legal, based on phase of game for action in filterActions({'verb': 'offer'},free.actions | sylv.actions): agent = world.agents[action['subject']] agent.setLegal(action,makeTree({'if': equalRow(stateKey(None,'phase'),'offer'), True: True, # Offers are legal in the offer phase False: False})) # Offers are illegal in all other phases if model == 'powell': # Powell has a special rejection phase for action in [freeNOP,freeBattle]: free.setLegal(action,makeTree({'if': equalRow(stateKey(None,'phase'),'rejection'), True: True, # Attacking and doing nothing are legal only in rejection phase False: False})) # Attacking and doing nothing are illegal in all other phases # Once offered, agent can respond if model == 'powell': # Under Powell, only Sylvania has to respond, and it can attack responses = [sylvBattle,sylvAccept,sylvReject] elif model == 'slantchev': # Under Slantchev, only accept/reject responses = filterActions({'verb': 'accept offer'},free.actions | sylv.actions) responses += filterActions({'verb': 'reject offer'},free.actions | sylv.actions) for action in responses: agent = world.agents[action['subject']] agent.setLegal(action,makeTree({'if': equalRow(stateKey(None,'phase'),'respond'), True: True, # Offeree must act in the response phase False: False})) # Offeree cannot act in any other phase if model == 'powell': # NOP is legal in exactly opposite situations to all other actions sylv.setLegal(sylvNOP,makeTree({'if': equalRow(stateKey(None,'phase'),'end'), True: True, # Sylvania does not do anything in the null phase after Freedonia responds to rejection False: False})) # Sylvania must act in its other phases if model == 'slantchev': # Attacking legal only under engagement phase for action in filterActions({'verb': 'attack'},free.actions | sylv.actions): agent = world.agents[action['subject']] agent.setLegal(action,makeTree({'if': equalRow(stateKey(None,'phase'),'engagement'), True: True, # Attacking legal only in engagement False: False})) # Attacking legal every other phase # Goals for Freedonia goalFTroops = maximizeFeature(stateKey(free.name,'troops')) free.setReward(goalFTroops,1.) goalFTerritory = maximizeFeature(stateKey(free.name,'territory')) free.setReward(goalFTerritory,1.) # Goals for Sylvania goalSTroops = maximizeFeature(stateKey(sylv.name,'troops')) sylv.setReward(goalSTroops,1.) goalSTerritory = minimizeFeature(stateKey(free.name,'territory')) sylv.setReward(goalSTerritory,1.) # Possible goals applicable to both goalAgreement = maximizeFeature(stateKey(None,'treaty')) # Silly goal, provided as an example of an achievement goal goalAchieve = achieveFeatureValue(stateKey(None,'phase'),'respond') # Horizons if model == 'powell': free.setAttribute('horizon',4) sylv.setAttribute('horizon',4) elif model == 'slantchev': free.setAttribute('horizon',6) sylv.setAttribute('horizon',6) # Discount factors free.setAttribute('discount',-1) sylv.setAttribute('discount',-1) # Levels of belief free.setRecursiveLevel(2) sylv.setRecursiveLevel(2) # Dynamics of battle freeTroops = stateKey(free.name,'troops') freeTerr = stateKey(free.name,'territory') sylvTroops = stateKey(sylv.name,'troops') # Effect of fighting for action in filterActions({'verb': 'attack'},free.actions | sylv.actions): # Effect on troops (cost of battle) tree = makeTree(addFeatureMatrix(freeTroops,stateKey(free.name,'cost'),-1.)) world.setDynamics(freeTroops,action,tree,enforceMin=not web) tree = makeTree(addFeatureMatrix(sylvTroops,stateKey(sylv.name,'cost'),-1.)) world.setDynamics(sylvTroops,action,tree,enforceMin=not web) if model == 'powell': # Effect on territory (probability of collapse) tree = makeTree({'distribution': [ ({'distribution': [(setToConstantMatrix(freeTerr,100),1.-fCollapse), # Sylvania collapses, Freedonia does not (noChangeMatrix(freeTerr), fCollapse)]}, # Both collapse sCollapse), ({'distribution': [(setToConstantMatrix(freeTerr,0),fCollapse), # Freedonia collapses, Sylvania does not (noChangeMatrix(freeTerr), 1.-fCollapse)]}, # Neither collapses 1.-sCollapse)]}) world.setDynamics(freeTerr,action,tree) elif model == 'slantchev': # Effect on position pos = stateKey(free.name,'position') tree = makeTree({'distribution': [(incrementMatrix(pos,1),1.-fCollapse), # Freedonia wins battle (incrementMatrix(pos,-1),fCollapse)]}) # Freedonia loses battle world.setDynamics(pos,action,tree) # Effect on territory tree = makeTree({'if': thresholdRow(pos,posHi-.5), True: setToConstantMatrix(freeTerr,100), # Freedonia won False: {'if': thresholdRow(pos,posLo+.5), True: noChangeMatrix(freeTerr), False: setToConstantMatrix(freeTerr,0)}}) # Freedonia lost world.setDynamics(freeTerr,action,tree) # Dynamics of offers for index in range(2): atom = Action({'subject': world.agents.keys()[index],'verb': 'offer', 'object': world.agents.keys()[1-index]}) if atom['subject'] == free.name or model != 'powell': offer = stateKey(atom['object'],'offered') amount = actionKey('amount') tree = makeTree({'if': trueRow(stateKey(None,'treaty')), True: noChangeMatrix(offer), False: setToConstantMatrix(offer,amount)}) world.setDynamics(offer,atom,tree,enforceMax=not web) # Dynamics of treaties for action in filterActions({'verb': 'accept offer'},free.actions | sylv.actions): # Accepting an offer means that there is now a treaty key = stateKey(None,'treaty') tree = makeTree(setTrueMatrix(key)) world.setDynamics(key,action,tree) # Accepting offer sets territory offer = stateKey(action['subject'],'offered') territory = stateKey(free.name,'territory') if action['subject'] == free.name: # Freedonia accepts sets territory to last offer tree = makeTree(setToFeatureMatrix(territory,offer)) world.setDynamics(freeTerr,action,tree) else: # Sylvania accepts sets territory to 1-last offer tree = makeTree(setToFeatureMatrix(territory,offer,pct=-1.,shift=100.)) world.setDynamics(freeTerr,action,tree) # Dynamics of phase phase = stateKey(None,'phase') roundKey = stateKey(None,'round') # OFFER -> RESPOND for index in range(2): action = Action({'subject': world.agents.keys()[index],'verb': 'offer', 'object': world.agents.keys()[1-index]}) if action['subject'] == free.name or model != 'powell': tree = makeTree(setToConstantMatrix(phase,'respond')) world.setDynamics(phase,action,tree) # RESPOND -> REJECTION or ENGAGEMENT for action in filterActions({'verb': 'reject offer'},free.actions | sylv.actions): if model == 'powell': tree = makeTree(setToConstantMatrix(phase,'rejection')) elif model == 'slantchev': tree = makeTree(setToConstantMatrix(phase,'engagement')) world.setDynamics(phase,action,tree) # accepting -> OFFER for action in filterActions({'verb': 'accept offer'},free.actions | sylv.actions): tree = makeTree(setToConstantMatrix(phase,'offer')) world.setDynamics(phase,action,tree) # attacking -> OFFER for action in filterActions({'verb': 'attack'},free.actions | sylv.actions): tree = makeTree(setToConstantMatrix(phase,'offer')) world.setDynamics(phase,action,tree) if action['subject'] == sylv.name or model == 'slantchev': tree = makeTree(incrementMatrix(roundKey,1)) world.setDynamics(roundKey,action,tree) if model == 'powell': # REJECTION -> END for atom in [freeNOP,freeBattle]: tree = makeTree(setToConstantMatrix(phase,'end')) world.setDynamics(phase,atom,tree) # END -> OFFER atom = Action({'subject': sylv.name,'verb': 'continue'}) tree = makeTree(setToConstantMatrix(phase,'offer')) world.setDynamics(phase,atom,tree) tree = makeTree(incrementMatrix(roundKey,1)) world.setDynamics(roundKey,atom,tree) if not web: # Relationship dynamics: attacking is bad for trust atom = Action({'subject': sylv.name,'verb': 'attack','object': free.name}) key = binaryKey(free.name,sylv.name,'trusts') tree = makeTree(approachMatrix(key,0.1,-1.)) world.setDynamics(key,atom,tree) # Handcrafted policy for Freedonia # free.setPolicy(makeTree({'if': equalRow('phase','respond'), # # Accept an offer greater than 50 # True: {'if': thresholdRow(stateKey(free.name,'offered'),50), # True: Action({'subject': free.name,'verb': 'accept offer','object': sylv.name}), # False: Action({'subject': free.name,'verb': 'reject offer','object': sylv.name})}, # False: {'if': equalRow('phase','engagement'), # # Attack during engagement phase # True: Action({'subject': free.name,'verb': 'attack','object': sylv.name}), # # Agent decides how what to do otherwise # False: False}})) # Mental models of enemy # Example of creating a model with incorrect reward all at once (a version of Freedonia who cares about reaching agreement as well) # sylv.addModel('false',R={goalSTroops: 10.,goalSTerritory: 1.,goalAgreement: 1.}, # rationality=1.,selection='distribution',parent=True) # Example of creating a model with incorrect beliefs sylv.addModel('false',rationality=10.,selection='distribution',parent=True) key = stateKey(free.name,'position') # Sylvania believes position to be fixed at 3 sylv.setBelief(key,3,'false') # Freedonia is truly unsure about position (50% chance of being 7, 50% of being 3) world.setModel(free.name,True) free.setBelief(key,Distribution({7: 0.5,3: 0.5}),True) # Observations about military position tree = makeTree({'if': thresholdRow(key,1), True: {'if': thresholdRow(key,9), True: {'distribution': [(KeyedVector({key: 1}),0.9), (KeyedVector({key: 1,CONSTANT: -1}),0.1)]}, False: {'distribution': [(KeyedVector({key: 1}),0.8), (KeyedVector({key: 1,CONSTANT: -1}),0.1), (KeyedVector({key: 1,CONSTANT: 1}),0.1)]}}, False: {'distribution': [(KeyedVector({key: 1}),0.9), (KeyedVector({key: 1,CONSTANT: 1}),0.1)]}}) free.defineObservation(key,tree) # Example of setting model parameters separately sylv.addModel('true',parent=True) sylv.setAttribute('rationality',10.,'true') # Override real agent's rationality with this value sylv.setAttribute('selection','distribution','true') world.setMentalModel(free.name,sylv.name,{'false': 0.9,'true': 0.1}) # Goal of fooling Sylvania goalDeception = achieveFeatureValue(modelKey(sylv.name),sylv.model2index('false')) return world
world = World() ag_producer = Agent('Producer') world.addAgent(ag_producer) ag_consumer = Agent('Consumer') world.addAgent(ag_consumer) agents = [ag_producer, ag_consumer] # agent settings ag_producer.setAttribute('discount', 1) ag_producer.setHorizon(HORIZON) ag_consumer.setAttribute('discount', 1) ag_consumer.setHorizon(HORIZON) # add variables (capacity and asked/received amounts) var_half_cap = world.defineState(ag_producer.name, 'half capacity', bool) world.setFeature(var_half_cap, False) var_ask_amnt = world.defineState(ag_producer.name, 'asked amount', int, lo=0, hi=100) world.setFeature(var_ask_amnt, 0) var_rcv_amnt = world.defineState(ag_consumer.name, 'received amount', int, lo=0, hi=100) world.setFeature(var_rcv_amnt, 0) # add producer actions # produce capacity: if half capacity then 0.5*asked amount else asked amount)
agent2 = Agent('Agent 2') world.addAgent(agent2) agents_dec = [] agents = [agent1, agent2] for agent in agents: # set agent's params agent.setAttribute('discount', 1) agent.setAttribute('selection', TIEBREAK) agent.setHorizon(1) # agent.setRecursiveLevel(1) # add "decision" variable (0 = didn't decide, 1 = went straight, 2 = swerved) dec = world.defineState(agent.name, 'decision', list, [NOT_DECIDED, WENT_STRAIGHT, SWERVED]) world.setFeature(dec, NOT_DECIDED) agents_dec.append(dec) # define agents' actions (defect and cooperate) action = agent.addAction({'verb': '', 'action': 'go straight'}) tree = makeTree(setToConstantMatrix(dec, WENT_STRAIGHT)) world.setDynamics(dec, action, tree) action = agent.addAction({'verb': '', 'action': 'swerve'}) tree = makeTree(setToConstantMatrix(dec, SWERVED)) world.setDynamics(dec, action, tree) # defines payoff matrices agent1.setReward(get_reward_tree(agent1, agents_dec[0], agents_dec[1]), 1) agent2.setReward(get_reward_tree(agent2, agents_dec[1], agents_dec[0]), 1) # define order
MAX_STEPS = 3 if __name__ == '__main__': # create world and add agent world = World() agent = Agent('Agent') world.addAgent(agent) # set parameters agent.setAttribute('discount', DISCOUNT) agent.setHorizon(HORIZON) # add position variable pos = world.defineState(agent.name, 'position', int, lo=-100, hi=100) world.setFeature(pos, 0) # define agents' actions (stay 0, left -1 and right +1) action = agent.addAction({'verb': 'move', 'action': 'nowhere'}) tree = makeTree(setToFeatureMatrix(pos, pos)) world.setDynamics(pos, action, tree) action = agent.addAction({'verb': 'move', 'action': 'left'}) tree = makeTree(incrementMatrix(pos, -1)) world.setDynamics(pos, action, tree) action = agent.addAction({'verb': 'move', 'action': 'right'}) tree = makeTree(incrementMatrix(pos, 1)) world.setDynamics(pos, action, tree) # define rewards (maximize position, i.e., always go right) agent.setReward(maximizeFeature(pos, agent.name), 1)
tree = makeTree( multi_set_matrix(var_counter, { var_counter: 1, CONSTANT: 1 })) world.setDynamics(var_counter, action, tree) # define second agent's action (var is copy from counter) action = agent2.addAction({'verb': '', 'action': 'copy'}) tree = makeTree(setToFeatureMatrix(var_copy, var_counter)) world.setDynamics(var_copy, action, tree) world.setOrder(turn_order) # resets vars world.setFeature(var_copy, 0) world.setFeature(var_counter, 0) print('====================================') print(label) # steps for i in range(4): print('Step {}, decision by: {}'.format( str(i), turn_order[i % len(turn_order)])) step = world.step() counter = next(iter(world.getFeature(var_counter).keys())) counter_cp = next(iter(world.getFeature(var_copy).keys())) print('Counter: {}\tCopy: {}'.format(counter, counter_cp)) # world.explain(step, level=4) # todo does not work, need outcomes information
def setup(): global args np.random.seed(args.seed) # create world and add agents world = World() world.memory = False world.parallel = args.parallel agents = [] agent_features = {} for ag in range(args.agents): agent = Agent('Agent' + str(ag)) world.addAgent(agent) agents.append(agent) # set agent's params agent.setAttribute('discount', 1) agent.setHorizon(args.horizon) # add features, initialize at random features = [] agent_features[agent] = features for f in range(args.features_agent): feat = world.defineState(agent.name, 'Feature{}'.format(f), int, lo=0, hi=1000) world.setFeature(feat, np.random.randint(0, MAX_FEATURE_VALUE)) features.append(feat) # set random reward function agent.setReward(maximizeFeature(np.random.choice(features), agent.name), 1) # add mental copy of true model and make it static (we do not have beliefs in the models) agent.addModel(get_fake_model_name(agent), parent=get_true_model_name(agent)) agent.setAttribute('static', True, get_fake_model_name(agent)) # add actions for ac in range(args.actions): action = agent.addAction({'verb': '', 'action': 'Action{}'.format(ac)}) i = ac while i + args.features_action < args.features_agent: weights = {} for j in range(args.features_action): weights[features[i + j + 1]] = 1 tree = makeTree(multi_set_matrix(features[i], weights)) world.setDynamics(features[i], action, tree) i += args.features_action # define order world.setOrder([set(ag.name for ag in agents)]) for agent in agents: # test belief update: # - set a belief in one feature to the actual initial value (should not change outcomes) # world.setModel(agent.name, Distribution({True: 1.0})) rand_feat = np.random.choice(agent_features[agent]) agent.setBelief(rand_feat, world.getValue(rand_feat)) print('{} will always observe {}={}'.format(agent.name, rand_feat, world.getValue(rand_feat))) # set mental model of each agent in all other agents for i in range(args.agents): for j in range(i + 1, args.agents): world.setMentalModel(agents[i].name, agents[j].name, Distribution({get_fake_model_name(agents[j]): 1})) world.setMentalModel(agents[j].name, agents[i].name, Distribution({get_fake_model_name(agents[i]): 1})) return world
class _ConverterBase(object): model: RDDL world: World turn_order: List[Set[str]] def __init__(self, normal_stds=NORMAL_STDS, normal_bins=NORMAL_BINS, poisson_exp_rate=POISSON_EXP_RATE): self.features: Dict[str, str] = {} self.constants: Dict[str, int or float or bool or str] = {} self.actions: Dict[str, Dict[str, ActionSet]] = OrderedDict( ) # order of agents is as given in RDDL instance self._fluent_param_types: Dict[str, List[str]] = {} self._fluent_levels: Dict[str, int] = {} # set distribution discretization params self._normal_bins = np.linspace( -normal_stds, normal_stds, normal_bins).tolist() # gets sample value centers self._normal_probs = stats.norm.pdf( self._normal_bins) # gets corresponding sample probabilities self._normal_probs = ( self._normal_probs / self._normal_probs.sum()).tolist() # normalize to sum 1 self._poisson_exp_rate = poisson_exp_rate def log_state(self, features: List[str] = None, log_actions: bool = False) -> None: """ Logs (INFO level) the current state of the PsychSim world. Only prints features that were converted from RDDL. :param List[str] features: the features whose current value are to be printed. `None` will print all :param bool log_actions: whether to also log agents' actions features on record. """ for f in self.features.values(): if features is None or f in features: val = str(self.world.getFeature(f)).replace('\n', '\t') logging.info(f'{f}: {val}') for ag_name in self.actions.keys(): if log_actions: f = actionKey(ag_name) val = str(self.world.getFeature(f)).replace('\n', '\t') logging.info(f'{f}: {val}') def get_agents(self): return list(self.world.agents.keys()) @staticmethod def get_feature_name(f: Tuple) -> str: """ Gets a PsychSim feature identifier name for the given (possibly parameterized) fluent. :param Tuple f: the (possibly parameterized) fluent, e.g., `('p', None)` or `('p', x1, y1)`. :rtype: str :return: the identifier string for the fluent. """ if isinstance(f, tuple): f = tuple(n for n in f if n is not None) if len(f) == 1: f = f[0] return re.sub(r'\'|"|@', '', str(f)) return str(f) def _is_feature(self, name: Tuple) -> bool: return self.get_feature_name(name) in self.features def _get_feature(self, name: Tuple) -> str: return self.features[self.get_feature_name(name)] def _is_action(self, name: Tuple, agent: Agent) -> bool: return agent.name in self.actions and self.get_feature_name( name) in self.actions[agent.name] def _get_action(self, name: Tuple, agent: Agent) -> ActionSet: return self.actions[agent.name][self.get_feature_name(name)] def _is_constant(self, name: Tuple) -> bool: return self.get_feature_name(name) in self.constants def _is_constant_value(self, val: str) -> bool: return val in self.constants.values() def _get_constant_value(self, name: Tuple) -> object: return self.constants[self.get_feature_name(name)] def _get_entity_name(self, name: Tuple) -> Tuple[str, Tuple]: name = list(name) # searches for agent name in (possibly parameterized) variable's name for n in name: if n in self.world.agents: name.remove(n) return n, tuple(name) return WORLD, tuple(name) # defaults to world def _is_enum(self, name: str) -> bool: for t_name, t_vals in self.model.domain.types: if t_name == name and isinstance(t_vals, list) and len(t_vals) > 0: return True return False def _is_enum_value(self, val: str) -> bool: for t_name, t_vals in self.model.domain.types: if self._is_enum(t_name) and (val in t_vals or '@' + val in t_vals): return True return False def _get_enum_values(self, name: str) -> List[str] or None: if not self._is_enum(name): return None for t_name, t_vals in self.model.domain.types: if t_name == name: return [_.replace('@', '') for _ in t_vals ] # strip "@" character from enum values return None def _get_domain(self, t_range: str): # checks normal types if t_range == 'int': return int, 0. if t_range == 'bool': return bool, 0. if t_range == 'real': return float, 0. # checks enumerated (domain-level constant) types domain = self._get_enum_values(t_range) if domain is not None: return list, domain # checks object (instance-level constant) types try: domain = self._get_param_values(t_range) if domain is not None: return list, domain except ValueError: pass raise ValueError(f'Could not get domain for range type: {t_range}!') def _get_param_types(self, name: str) -> List[str]: assert name in self._fluent_param_types, \ f'Could not get param types for fluent: {name}, feature not registered!' return self._fluent_param_types[name] def _get_param_values(self, param_type: str) -> List[str]: if self._is_enum(param_type): # check enum type return self._get_enum_values(param_type) for p_type, p_vals in self.model.non_fluents.objects: # check object instance type if p_type == param_type: return p_vals raise ValueError(f'Could not get values for param type: {param_type}!') def _get_all_param_combs(self, param_types: List[str]) -> List[Tuple]: param_vals = [self._get_param_values(p_type) for p_type in param_types] return list(itertools.product(*param_vals)) def _create_world_agents(self) -> None: # create world logging.info('__________________________________________________') self.world = World() # create agents from RDDL non-fluent definition, special object named "agent" for p_type, p_vals in self.model.non_fluents.objects: if p_type == 'agent': for ag_name in p_vals: self.world.addAgent(ag_name) break if len(self.world.agents) == 0: self.world.addAgent( 'AGENT') # create default agent if no agents defined # set agents' properties from instance for agent in self.world.agents.values(): if hasattr(self.model.instance, 'horizon'): agent.setAttribute('horizon', self.model.instance.horizon) if hasattr(self.model.instance, 'discount'): agent.setAttribute('discount', self.model.instance.discount) # TODO other world and agent attributes? agent.setAttribute('selection', 'random') model = agent.get_true_model() logging.info(f'Created agent "{agent.name}" with properties:') logging.info(f'\thorizon: {agent.getAttribute("horizon", model)}') logging.info( f'\tdiscount: {agent.getAttribute("discount", model)}') def _convert_constants(self): # first try to initialize non-fluents from definition's default value logging.info('__________________________________________________') self.constants = {} for nf in self.model.domain.non_fluents.values(): if nf.arity > 0: # gets all parameter combinations param_vals = self._get_all_param_combs(nf.param_types) nf_combs = [(nf.name, *p_vals) for p_vals in param_vals] else: nf_combs = [(nf.name, None)] # not-parameterized constant for nf_name in nf_combs: nf_name = self.get_feature_name(nf_name) def_val = nf.default if nf.default not in {None, 'none', 'null', 'None', 'Null'} else \ self._get_domain(nf.range)[1][0] if isinstance(def_val, str): def_val = def_val.replace( '@', '') # just in case it's an enum value self.constants[nf_name] = def_val logging.info( f'Initialized constant "{nf_name}" with default value "{def_val}"' ) # then set value of non-fluents from initialization definition if hasattr(self.model.non_fluents, 'init_non_fluent'): for nf, def_val in self.model.non_fluents.init_non_fluent: nf_name = nf if nf[1] is None else (nf[0], *nf[1]) nf_name = self.get_feature_name(nf_name) if nf_name not in self.constants: raise ValueError( f'Trying to initialize non-existing non-fluent: {nf_name}!' ) def_val = def_val if def_val not in {None, 'none', 'null', 'None', 'Null'} else \ self._get_domain(nf.range)[1][0] if isinstance(def_val, str): def_val = def_val.replace( '@', '') # just in case it's an enum value self.constants[nf_name] = def_val logging.info( f'Initialized constant "{nf_name}" in non-fluents to "{def_val}"' ) logging.info(f'Total {len(self.constants)} constants initialized') def _convert_variables(self): # create features from state fluents logging.info('__________________________________________________') self.features = {} for sf in self.model.domain.state_fluents.values(): self._create_features( sf, '' if len(self.model.domain.observ_fluents) == 0 else '__') # create features from intermediate fluents for sf in self.model.domain.intermediate_fluents.values(): self._create_features(sf, '_') # create features from observable fluents for sf in self.model.domain.observ_fluents.values(): self._create_features(sf) logging.info(f'Total {len(self.features)} features created') def _create_features(self, fluent: PVariable, prefix: str = '') -> List[str]: # registers types of parameters for this type of feature self._fluent_param_types[fluent.name] = fluent.param_types self._fluent_levels[fluent.name] = fluent.level if fluent.fluent_type == 'interm-fluent' else \ -1 if fluent.fluent_type == 'state-fluent' else MAX_LEVEL # to whom should this feature be associated, agent or world? if fluent.arity > 0: # gets all parameter combinations param_vals = self._get_all_param_combs(fluent.param_types) f_combs = [(fluent.name, *p_vals) for p_vals in param_vals] else: f_combs = [(fluent.name, None)] # not-parameterized feature # create and register features feats = [] domain = self._get_domain(fluent.range) for f_name in f_combs: entity, feat_name = self._get_entity_name( f_name) # tries to identify agent from fluent param comb name f = self.world.defineState( entity, prefix + self.get_feature_name(feat_name), *domain) f_name = self.get_feature_name( f_name ) # keep feature's original name for transparent later referencing self.features[f_name] = f # set to default value (if list assume first of list) lo = self.world.variables[f]['lo'] def_val = fluent.default if fluent.default not in {None, 'none', 'null', 'None', 'Null'} else \ lo if lo is not None else self.world.variables[f]['elements'][0] if isinstance(def_val, str): def_val = def_val.replace( '@', '') # just in case it's an enum value self.world.setFeature(f, def_val) logging.info( f'Created feature "{f}" from {fluent.fluent_type} "{fluent.name}" of type "{fluent.range}"' ) feats.append(f) return feats def _convert_actions(self): # create actions for agents (assume homogeneous agents) TODO maybe put constraints in RDDL for diff agents? logging.info('__________________________________________________') self.actions = {agent.name: {} for agent in self.world.agents.values()} for act_fluent in self.model.domain.action_fluents.values(): self._create_actions(act_fluent) logging.info( f'Total {sum(len(actions) for actions in self.actions.values())} actions created' ) def _create_actions(self, fluent: PVariable) -> List[ActionSet]: self._fluent_levels[ fluent. name] = -MAX_LEVEL # for dynamics, actions always come first if fluent.arity > 0: # gets all parameter combinations param_vals = self._get_all_param_combs(fluent.param_types) act_combs = [(fluent.name, *p_vals) for p_vals in param_vals] else: act_combs = [(fluent.name, None)] # not-parameterized action # create action for each param combination actions = [] for act_name in act_combs: entity, action_name = self._get_entity_name( act_name ) # tries to identify agent from fluent param comb name if entity == WORLD: agents = self.world.agents.values( ) # create action for all agents else: agents = [self.world.agents[entity] ] # create action just for this agent for agent in agents: # keep feature's original name for transparent later referencing act_name = self.get_feature_name(act_name) action = agent.addAction( {'verb': self.get_feature_name(action_name)}) actions.append(action) self.actions[agent.name][act_name] = action logging.info( f'Created action "{action}" for agent "{agent.name}" from action fluent: {fluent}' ) return actions def _initialize_variables(self): # initialize variables from instance def logging.info('__________________________________________________') for sf, val in self.model.instance.init_state: f_name = sf if sf[1] is None else (sf[0], *sf[1]) if any( self._is_action(f_name, agent) for agent in self.world.agents.values()): continue # skip action initialization assert self._is_feature( f_name ), f'Could not find feature "{f_name}" corresponding to fluent "{sf}"!' f = self._get_feature(f_name) if isinstance(val, str): val = val.replace('@', '') # just in case it's an enum value self.world.setFeature(f, val) logging.info(f'Initialized feature "{f}" to "{val}"') def _parse_requirements(self): logging.info('__________________________________________________') logging.info('Parsing requirements...') agents = self.world.agents requirements = self.model.domain.requirements # check concurrent multiagent actions, agent order is assumed by RDDL instance definition if len( agents ) > 1 and requirements is not None and 'concurrent' in requirements: if hasattr(self.model.instance, 'max_nondef_actions'): # creates groups of agents that act in parallel according to "max_nondef_actions" param num_parallel = self.model.instance.max_nondef_actions ag_list = list(self.actions.keys()) self.turn_order = [] for i in range(0, len(agents), num_parallel): self.turn_order.append( set(ag_list[i:min(i + num_parallel, len(agents))])) else: self.turn_order = [set(agents.keys()) ] # assumes all agents act in parallel else: self.turn_order = [{ag} for ag in agents.keys() ] # assumes all agents act sequentially self.world.setOrder(self.turn_order) # sets omega to observe only observ-fluents (ignore interm and state-fluents) # TODO assume homogeneous agents (partial observability affects all agents the same) if requirements is not None and 'partially-observed' in requirements: for agent in agents.values(): observable = [actionKey(agent.name) ] # todo what do we need to put here? for sf in self.model.domain.observ_fluents.values(): if self._is_feature(sf.name): observable.append(self._get_feature(sf.name)) agent.omega = observable logging.info( f'Setting partial observability for agent "{agent.name}", omega={agent.omega}' )
sides = [] rights = [] lefts = [] agents = [agent1, agent2] for agent in agents: # set agent's params agent.setAttribute('discount', 1) agent.setHorizon(1) agent.setAttribute('selection', TIEBREAK) # add 'side chosen' variable (0 = didn't decide, 1 = went left, 2 = went right) side = world.defineState(agent.name, 'side', list, [NOT_DECIDED, WENT_LEFT, WENT_RIGHT]) world.setFeature(side, NOT_DECIDED) sides.append(side) # define agents' actions (left and right) action = agent.addAction({'verb': '', 'action': 'go left'}) tree = makeTree(setToConstantMatrix(side, WENT_LEFT)) world.setDynamics(side, action, tree) lefts.append(action) action = agent.addAction({'verb': '', 'action': 'go right'}) tree = makeTree(setToConstantMatrix(side, WENT_RIGHT)) world.setDynamics(side, action, tree) rights.append(action) # create a new model for the agent agent.addModel(get_fake_model_name(agent),