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()
Ejemplo n.º 2
0
    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))
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
    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),
Ejemplo n.º 5
0
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)
Ejemplo n.º 7
0
    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
Ejemplo n.º 8
0
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)
Ejemplo n.º 9
0
        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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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}'
                )
Ejemplo n.º 12
0
    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),