Source code for abce.agent

# Copyright 2012 Davoud Taghawi-Nejad
#
# Module Author: Davoud Taghawi-Nejad
#
# ABCE is open-source software. If you are using ABCE for your research you are
# requested the quote the use of this software.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
# use this file except in compliance with the License and quotation of the
# author. You may obtain a copy of the License at
#       http://www.apache.org/licenses/LICENSE-2.0
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations under
# the License.
"""
The :py:class:`abce.Agent` class is the basic class for creating your agents.
It automatically handles the possession of goods of an agent. In order to
produce/transforme goods you also need to subclass the :py:class:`abce.Firm` or
to create a consumer the :py:class:`abce.Household`.

For detailed documentation on:

Trading, see :doc:`Trade`

Logging and data creation, see :doc:`Database`.

Messaging between agents, see :doc:`Messaging`.
"""
import time
import random
from collections import OrderedDict, defaultdict
from pprint import pprint
import abce
from .database import Database
from .trade import Trade
from .messaging import Messaging
from .inventory import Inventory


[docs]class DummyContracts: def _advance_round(self, round): pass
[docs]class Agent(Database, Trade, Messaging): """ Every agent has to inherit this class. It connects the agent to the simulation and to other agent. The :class:`abce.Trade`, :class:`abce.Database` and :class:`abce.Messaging` classes are included. An agent can also inheriting from :class:`abce.Firm`, :class:`abce.FirmMultiTechnologies` or :class:`abce.Household` classes. Every method can return parameters to the simulation. For example:: class Household(abce.Agent, abce.Household): def init(self, simulation_parameters, agent_parameters): self.num_firms = simulation_parameters['num_firms'] self.type = agent_parameters['type'] ... def selling(self): for i in range(self.num_firms): self.sell('firm', i, 'good', quantity=1, price=1) ... def return_quantity_of_good(self): return['good'] ... simulation = Simulation() households = Simulation.build_agents(household, 'household', parameters={...}, agent_parameters=[{'type': 'a'}, {'type': 'b'}]) for r in range(10): simulation.advance_round(r) households.selling() print(households.return_quantity_of_good()) """ def __init__(self, id, group, trade_logging, database, random_seed, num_managers, agent_parameters, simulation_parameters, check_unchecked_msgs, start_round=None): """ Do not overwrite __init__ instead use a method called init instead. init is called whenever the agent are build. """ self.id = id """ self.id returns the agents id READ ONLY""" self.name = (group, id) """ self.name returns the agents name, which is the group name and the id """ self.name_without_colon = '%s_%i' % (group, id) self.group = group """ self.group returns the agents group or type READ ONLY! """ # TODO should be group_address(group), but it would not work # when fired manual + ':' and manual group_address need to be removed self.database_connection = database self.trade_logging = {'individual': 1, 'group': 2, 'off': 0}[trade_logging] self.num_managers = num_managers self._out = [[] for _ in range(self.num_managers + 1)] self._inventory = Inventory(self.name) # TODO make defaultdict; delete all key errors regarding self._inventory as # defaultdict, does not have missing keys self._msgs = {} self.given_offers = OrderedDict() self._open_offers_buy = defaultdict(dict) self._open_offers_sell = defaultdict(dict) self._polled_offers = {} self._offer_count = 0 self._reject_offers_retrieved_end_subround = [] self._trade_log = defaultdict(int) self._data_to_observe = {} self._data_to_log_1 = {} self._quotes = {} self.round = start_round """ self.round is depreciated""" self.time = start_round """ self.time, contains the time set with simulation.advance_round(time) you can set time to anything you want an integer or (12, 30, 21, 09, 1979) or 'monday' """ self._resources = [] self.variables_to_track_panel = [] self.variables_to_track_aggregate = [] self.inbox = [] random.seed(random_seed) try: self._add_contracts_list() except AttributeError: self.contracts = DummyContracts() if hasattr(abce, 'conditional_logging'): self.conditional_logging = True self.log_rounds = abce.conditional_logging else: self.conditional_logging = False self.log_this_round = True self._check_every_round_for_lost_messages = check_unchecked_msgs
[docs] def init(self, parameters, agent_parameters): """ This method is called when the agents are build. It can be overwritten by the user, to initialize the agents. parameters and agent_parameters are the parameters given in :py:meth:`abce.Simulation.build_agents` """ pass
[docs] def possession(self, good): """ returns how much of good an agent possesses. Returns: A number. possession does not return a dictionary for self.log(...), you can use self.possessions([...]) (plural) with self.log. Example:: if self['money'] < 1: self.financial_crisis = True if not(is_positive(self['money']): self.bancrupcy = True """ print("depreciated use self[good] or self.not_reserved[good]") return self._inventory[good]
[docs] def possessions(self): """ returns all possessions """ return self._inventory.possessions()
def _offer_counter(self): """ returns a unique number for an offer (containing the agent's name) """ self._offer_count += 1 return hash((self.name, self._offer_count)) def _check_for_lost_messages(self): for offer in list(self.given_offers.values()): if offer.made < self.round: print(("in agent %s this offers have not been retrieved:" % self.name_without_colon)) for offer in list(self.given_offers.values()): if offer.made < self.round: print((offer.__repr__())) raise Exception('%s_%i: There are offers have been made before' 'last round and not been retrieved in this' 'round get_offer(.)' % (self.group, self.id)) if sum([len(offers) for offers in list(self._open_offers_buy.values())]): pprint(dict(self._open_offers_buy)) raise Exception('%s_%i: There are offers an agent send that have ' 'not been retrieved in this round get_offer(.)' % (self.group, self.id)) if sum([len(offers) for offers in list(self._open_offers_sell.values())]): pprint(dict(self._open_offers_sell)) raise Exception('%s_%i: There are offers an agent send that have ' 'not been retrieved in this round get_offer(.)' % (self.group, self.id)) if sum([len(offers) for offers in list(self._msgs.values())]): pprint(dict(self._msgs)) raise Exception('(%s, %i): There are messages an agent send that ' 'have not been retrieved in this round ' 'get_messages(.)' % (self.group, self.id)) def _advance_round(self, time): self._inventory._advance_round() self.contracts._advance_round(self.round) if self._check_every_round_for_lost_messages: self._check_for_lost_messages() for ingredient, units, product in self._resources: self._inventory.create(product, self[ingredient] * units) self.round = time self.time = time self.log_in_subround_serial = 0 if self.conditional_logging: if self.round in self.log_rounds: print(("***", self.round)) self.log_this_round = True else: self.log_this_round = False if self.trade_logging > 0: self.database_connection.put( ["trade_log", self._trade_log, self.round]) self._trade_log = defaultdict(int)
[docs] def create(self, good, quantity): """ creates quantity of the good out of nothing Use create with care, as long as you use it only for labor and natural resources your model is macro-economically complete. Args: 'good': is the name of the good quantity: number """ self._inventory.create(good, quantity)
[docs] def create_timestructured(self, good, quantity): """ creates quantity of the time structured good out of nothing. For example:: self.creat_timestructured('capital', [10,20,30]) Creates capital. 10 units are 2 years old 20 units are 1 year old and 30 units are new. It can alse be used with a quantity instead of an array. In this case the amount is equally split on the years.:: self.create_timestructured('capital', 60) In this case 20 units are 2 years old 20 units are 1 year old and 20 units are new. Args: 'good': is the name of the good quantity: an arry or number """ self._inventory.create_timestructured(good, quantity)
def _declare_expiring(self, good, duration): """ creates a good that has a limited duration """ self._inventory._declare_expiring(good, duration)
[docs] def not_reserved(self, good): return self._inventory.not_reserved(good)
[docs] def destroy(self, good, quantity=None): """ destroys quantity of the good. If quantity is omitted destroys all Args:: 'good': is the name of the good quantity (optional): number Raises:: NotEnoughGoods: when goods are insufficient """ self._inventory.destroy(good, quantity)
def _execute(self, command, args, kwargs): self._out = [[] for _ in range(self.num_managers + 1)] try: self._clearing__end_of_subround(list(self.inbox)) self._begin_subround() self._out[-1] = getattr(self, command)(*args, **kwargs) self._end_subround() self._reject_polled_but_not_accepted_offers() except KeyboardInterrupt: return None except: time.sleep(random.random()) print(('command', command)) print(('args', args)) print(('kwargs', kwargs)) raise self.inbox.clear() return self._out def _begin_subround(self): """ Overwrite this to make ABCE plugins, that need to do something at the beginning of every subround """ pass def _end_subround(self): """ Overwrite this to make ABCE plugins, that need to do something at the beginning of every subround """ pass def _register_resource(self, resource, units, product): self._resources.append((resource, units, product)) def _register_perish(self, good): self._inventory._perishable.append(good) def _send(self, receiver_group, receiver_id, typ, msg): """ sends a message to 'receiver_group', who can be an agent, a group or 'all'. The agents receives it at the begin of each round in self.messages(typ) is 'm' for mails. typ =(_o,c,u,r) are reserved for internally processed offers. """ self._out[receiver_id % self.num_managers].append( (receiver_group, receiver_id, (typ, msg))) def __getitem__(self, good): return self._inventory.haves[good] def __del__(self): self._check_for_lost_messages()