def __init__(self, tax_rate, welfare, tax_rate_increment, welfare_increment, starting_welfare_req): self._state = {'cash': 0} self.tax_rate = tax_rate self.tax_rate_increment = tax_rate_increment self.welfare = welfare self.welfare_increment = welfare_increment self.welfare_req = starting_welfare_req self.subsidies = { ConsumerGoodFirm: 0, CapitalEquipmentFirm: 0, RawMaterialFirm: 0, Hospital: 0 } self.altruism = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(3)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.5) # keep track of previous step's quality of life for comparison self.prev_qol = 0
def __init__(self, labor_cost_per_good, material_cost_per_good, labor_per_equipment, labor_per_worker, supply_increment, profit_increment, wage_increment): self._super(Firm, self).__init__(state={ 'desired_supply': 1, 'desired_equipment': 0, 'worker_change': 0, 'workers': [], 'cash': 50000, 'revenue': 0, 'costs': 0, 'price': 0, 'profit': 0, 'prev_profit': 0, 'leftover': 0, 'supply': 0, 'n_sold': 0, 'profit_margin': 1, 'equipment': 0, 'materials': 0, }) self.material_cost_per_good = material_cost_per_good self.labor_cost_per_good = labor_cost_per_good self.labor_per_equipment = labor_per_equipment self.labor_per_worker = labor_per_worker self.supply_increment = supply_increment self.profit_increment = profit_increment self.wage_increment = wage_increment # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.1, learning_rate=0.8)
def __init__(self, labor_cost_per_good, material_cost_per_good, labor_per_equipment, labor_per_worker, supply_increment, profit_increment, wage_increment): self._super(Firm, self).__init__( state={ 'desired_supply': 1, 'desired_equipment': 0, 'worker_change': 0, 'workers': [], 'cash': 50000, 'revenue': 0, 'costs': 0, 'price': 0, 'profit': 0, 'prev_profit': 0, 'leftover': 0, 'supply': 0, 'n_sold': 0, 'profit_margin': 1, 'equipment': 0, 'materials': 0, }) self.material_cost_per_good = material_cost_per_good self.labor_cost_per_good = labor_cost_per_good self.labor_per_equipment = labor_per_equipment self.labor_per_worker = labor_per_worker self.supply_increment = supply_increment self.profit_increment = profit_increment self.wage_increment = wage_increment # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.1, learning_rate=0.8)
def __init__(self, owner): self.owner = owner self.owner._state['firm_owner'] = True self.owner.firm = self self.desired_supply = 1 # initialize self.workers = [] self.revenue = 0 self.costs = 0 self.supply = 0 self.n_sold = 0 self.profit_margin = 1 self.equipment = 0 self.materials = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.8)
class Firm(Agent): config = {} def __init__(self, owner): self.owner = owner self.owner._state['firm_owner'] = True self.owner.firm = self self.desired_supply = 1 # initialize self.workers = [] self.revenue = 0 self.costs = 0 self.supply = 0 self.n_sold = 0 self.profit_margin = 1 self.equipment = 0 self.materials = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.8) @property def id(self): return self.owner.id @property def public(self): return type(self.owner).__name__ == 'Government' def change_owner(self, owner): self.owner._state['firm_owner'] = False self.owner.firm = None owner._state['firm_owner'] = True owner.firm = self self.owner = owner def pay(self, cost): self.cash -= cost self.costs += cost @property def cash(self): return self.owner._state['cash'] @cash.setter def cash(self, value): self.owner._state['cash'] = value @property def profit(self): return self.revenue - self.costs def __repr__(self): return '{}\'s {}'.format(self.owner.name, type(self).__name__) @property def production_capacity(self): return math.floor(self.labor/self.config['labor_cost_per_good']) @property def worker_labor(self): return self.config['labor_per_worker'] * len(self.workers) @property def equipment_labor(self): return min(len(self.workers), self.equipment) * self.config['labor_per_equipment'] @property def labor(self): return self.worker_labor + self.equipment_labor def _labor(self, equipment): return self.worker_labor + min(len(self.workers), equipment) * self.config['labor_per_equipment'] def fire(self, worker): worker.employer = None worker.wage = 0 self.workers.remove(worker) logger.info('person:{}'.format(json.dumps({ 'event': 'fired', 'id': worker.id }))) def hire(self, applicants, wage, world): hired = [] while self.worker_change > 0 and applicants: # based on employment prob apps = [] for a in applicants: ref = 'friend' if set(a.friends).intersection(self.workers) else 'ad_or_cold_call' p = offer_prob(world['year'], world['month'], a.sex, a.race, ref, precomputed_emp_dist=emp_dist) apps.append((a, p)) apps_mass = sum(p for a, p in apps) apps = [(a, pr/apps_mass) for a, pr in apps] worker = random_choice(apps) if worker.employer is not None: worker.employer.fire(worker) worker.wage = wage worker.employer = self applicants.remove(worker) self.workers.append(worker) hired.append(worker) logger.info('person:{}'.format(json.dumps({ 'event': 'hired', 'id': worker.id }))) self.worker_change -= 1 # increase wage to attract more employees if self.worker_change > 0: wage += self.config['wage_increment'] * (1.1+self.owner.altruism) return hired, self.worker_change, wage def close(self): self.owner._state['firm_owner'] = False self.owner.firm = None for worker in self.workers: self.fire(worker) def produce(self, world): """produce the firm's product. the firm will produce the desired supply if possible, otherwise, they will produce as much as they can.""" # limit desired supply to what can be produced given current capacity self.supply = max(1, min(self.desired_supply, self.production_capacity)) # set desired price wages = sum(w.wage for w in self.workers) self.costs += wages self.costs += self.building.rent/30 # approximately spread out rent cost self.cash -= wages cost_per_unit = self.costs/self.supply self.price = max(0, cost_per_unit + self.profit_margin) return self.supply, self.price def sell(self, quantity): n_sold = min(self.supply, quantity) self.supply -= n_sold self.n_sold += n_sold self.revenue = self.price * n_sold self.cash += self.revenue @property def current_state(self): """represent as a discrete state""" if self.n_sold == 0: return 0 elif self.n_sold > 0 and self.leftover > 0: return 1 elif self.n_sold > 0 and self.leftover == 0 and self.profit <= 0: return 2 elif self.n_sold > 0 and self.leftover == 0 and self.profit > 0 and self.profit - self.prev_profit < 0: return 3 elif self.n_sold > 0 and self.leftover == 0 and self.profit > 0 and self.profit - self.prev_profit >= 0: return 4 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state @property def actions(self): """these actions are possible from any state""" return [ {'supply': self.config['supply_increment']}, {'supply': -self.config['supply_increment']}, {'supply': self.config['supply_increment'], 'profit_margin': self.config['profit_increment']}, {'supply': self.config['supply_increment'], 'profit_margin': -self.config['profit_increment']}, {'supply': -self.config['supply_increment'], 'profit_margin': self.config['profit_increment']}, {'supply': -self.config['supply_increment'], 'profit_margin': -self.config['profit_increment']} ] def assess_assets(self, required_labor, mean_wage, mean_equip_price): """identify desired mixture of productive assets, i.e. workers, equipment, and wage""" down_wage_pressure = (-self.owner.altruism+1.1) * -self.config['wage_increment'] def objective(x): n_workers, wage, n_equipment = x return n_workers * wage + n_equipment * mean_equip_price def constraint(x): n_workers, wage, n_equipment = x equip_labor = min(n_workers * self.config['labor_per_equipment'], n_equipment * self.config['labor_per_equipment']) return n_workers * self.config['labor_per_worker'] + equip_labor - required_labor results = optimize.minimize(objective, (1,0,0), constraints=[ {'type': 'ineq', 'fun': constraint}, {'type': 'ineq', 'fun': lambda x: x[0]}, {'type': 'ineq', 'fun': lambda x: x[1] - (mean_wage - down_wage_pressure)}, {'type': 'ineq', 'fun': lambda x: x[2]} ], options={'maxiter':20}) n_workers, wage, n_equipment = np.ceil(results.x).astype(np.int) return n_workers, wage, n_equipment def purchase_equipment(self, supplier): total_equipment_cost = (self.desired_equipment - self.equipment) * supplier.price if not total_equipment_cost: n_equipment = max(0, self.desired_equipment - self.equipment) else: equipment_budget = max(0, min(self.cash, total_equipment_cost)) # how much equipment can be purchased n_equipment = math.floor(equipment_budget/supplier.price) to_purchase = min(supplier.supply, n_equipment) supplier.sell(to_purchase) self.equipment += to_purchase cost = to_purchase * supplier.price self.cash -= cost self.costs += cost return self.desired_equipment - self.equipment, to_purchase def set_production_target(self, world): """firm decides on how much supply they want to produce this step, and what they need to do to accomplish that""" # assess previous day's results self.prev_profit = self.profit self.leftover = self.supply # adjust production action = self.learner.choose_action(self.current_state) action = self.actions[action] self.desired_supply = max(1, self.desired_supply + action.get('supply', 0)) self.profit_margin += action.get('profit_margin', 0) # supply expires every day self.supply = 0 # unused materials expire every day self.materials = 0 # resets every day self.n_sold = 0 self.revenue = 0 self.costs = 0 # fire workers that are being paid too much for worker in self.workers: if worker.wage >= world['mean_wage'] + (self.config['extravagant_wage_range'] * (1.1+self.owner.altruism)): self.fire(worker) # figure out labor goal required_labor = self.desired_supply * self.config['labor_cost_per_good'] n_workers, wage, n_equip = self.assess_assets(required_labor, world['mean_wage'], world['mean_equip_price']) # sometimes optimization function returns a huge negative value for # workers, need to look into that further if n_workers < 0: n_workers = 0 self.worker_change = n_workers - len(self.workers) self.desired_equipment = self.equipment + max(0, n_equip - self.equipment) # fire workers if necessary while self.worker_change < 0: # weighted random choice by unemployment prob ws = [] for w in self.workers: pu = emp_dist[world['year']][world['month'] - 1][w.race.name][w.sex.name] ws.append((w, pu['unemployed'])) ws_mass = sum(p for w, p in ws) ws = [(w, p/ws_mass) for w, p in ws] worker = random_choice(ws) self.fire(worker) self.worker_change += 1 # job vacancies return self.worker_change, wage
class Firm(Agent): def __init__(self, labor_cost_per_good, material_cost_per_good, labor_per_equipment, labor_per_worker, supply_increment, profit_increment, wage_increment): self._super(Firm, self).__init__(state={ 'desired_supply': 1, 'desired_equipment': 0, 'worker_change': 0, 'workers': [], 'cash': 50000, 'revenue': 0, 'costs': 0, 'price': 0, 'profit': 0, 'prev_profit': 0, 'leftover': 0, 'supply': 0, 'n_sold': 0, 'profit_margin': 1, 'equipment': 0, 'materials': 0, }) self.material_cost_per_good = material_cost_per_good self.labor_cost_per_good = labor_cost_per_good self.labor_per_equipment = labor_per_equipment self.labor_per_worker = labor_per_worker self.supply_increment = supply_increment self.profit_increment = profit_increment self.wage_increment = wage_increment # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.1, learning_rate=0.8) def pay(self, cost): self['cash'] -= cost self['costs'] += cost @property def _production_capacity(self): """how many goods can be produced given current labor power""" return math.floor(self._labor/self.labor_cost_per_good) @property def _worker_labor(self): """labor from workers, not counting equipment""" return self.labor_per_worker * len(self['workers']) @property def _equipment_labor(self): """how much labor can be generated by owned equipment, limited by number of workers (one worker is required to operate one piece of equipment)""" return min(len(self['workers']), self['equipment']) * self.labor_per_equipment @property def _labor(self): """total productive labor""" return self._worker_labor + self._equipment_labor def _labor_for_equipment(self, equipment): """hypothetical labor that could be produced by some amount of equipment, limited by number of workers""" return self._worker_labor + min(len(self['workers']), equipment) * self.labor_per_equipment @asyncio.coroutine def fire(self, worker): self['workers'].remove(worker) yield from worker.call('quit') @asyncio.coroutine def hire(self, applicants, wage): hired = [] while self['worker_change'] > 0 and applicants: worker = random.choice(applicants) employer = yield from worker.get('employer') if employer is not None: yield from employer.call('fire', worker) yield from worker.call('hire', AgentProxy(self), wage) applicants.remove(worker) self['workers'].append(worker) hired.append(worker) self['worker_change'] -= 1 # increase wage to attract more employees if self['worker_change'] > 0: wage += self.wage_increment return hired, self['worker_change'], wage @asyncio.coroutine def shutdown(self): for worker in self['workers']: yield from self.fire(worker) def produce(self, world): """produce the firm's product. the firm will produce the desired supply if possible, otherwise, they will produce as much as they can.""" # limit desired supply to what can be produced given current capacity self['supply'] = max(1, min(self['desired_supply'], self._production_capacity)) # set desired price wages = 0 for w in self['workers']: wages += (yield from w.get('wage')) self['costs'] += wages self['cash'] -= wages cost_per_unit = self['costs']/self['supply'] self['price'] = max(0, cost_per_unit + self['profit_margin']) return self['supply'], self['price'] @asyncio.coroutine def sell(self, quantity): n_sold = min(self['supply'], quantity) self['supply'] -= n_sold self['n_sold'] += n_sold self['revenue'] = self['price'] * n_sold self['cash'] += self['revenue'] return n_sold @property def curren(self): """represent as a discrete state""" if self['n_sold'] == 0: return 0 elif self['n_sold'] > 0 and self['leftover'] > 0: return 1 elif self['n_sold'] > 0 and self['leftover'] == 0 and self['profit'] <= 0: return 2 elif self['n_sold'] > 0 and self['leftover'] == 0 and self['profit'] > 0 and self['profit'] - self['prev_profit'] < 0: return 3 elif self['n_sold'] > 0 and self['leftover'] == 0 and self['profit'] > 0 and self['profit'] - self['prev_profit'] >= 0: return 4 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state @property def actions(self): """these actions are possible from any state""" return [ {'supply': self.supply_increment}, {'supply': -self.supply_increment}, {'supply': self.supply_increment, 'profit_margin': self.profit_increment}, {'supply': self.supply_increment, 'profit_margin': -self.profit_increment}, {'supply': -self.supply_increment, 'profit_margin': self.profit_increment}, {'supply': -self.supply_increment, 'profit_margin': -self.profit_increment} ] def assess_assets(self, required_labor, mean_wage, mean_equip_price): """identify desired mixture of productive assets, i.e. workers, equipment, and wage""" down_wage_pressure = self.wage_increment def objective(x): n_workers, wage, n_equipment = x return n_workers * wage + n_equipment * mean_equip_price def constraint(x): n_workers, wage, n_equipment = x equip_labor = min(n_workers * self.labor_per_equipment, n_equipment * self.labor_per_equipment) return n_workers * self.labor_per_worker + equip_labor - required_labor results = optimize.minimize(objective, (1,0,0), constraints=[ {'type': 'ineq', 'fun': constraint}, {'type': 'ineq', 'fun': lambda x: x[0]}, {'type': 'ineq', 'fun': lambda x: x[1] - (mean_wage - down_wage_pressure)}, {'type': 'ineq', 'fun': lambda x: x[2]} ], options={'maxiter':20}) n_workers, wage, n_equipment = np.ceil(results.x).astype(np.int) return n_workers, wage, n_equipment @asyncio.coroutine def purchase_equipment(self, supplier): price, supply = yield from supplier.get('price', 'supply') total_equipment_cost = (self['desired_equipment'] - self['equipment']) * price if not total_equipment_cost: n_equipment = max(0, self['desired_equipment'] - self['equipment']) else: equipment_budget = max(0, min(self['cash'], total_equipment_cost)) # how much equipment can be purchased n_equipment = math.floor(equipment_budget/price) to_purchase = min(supply, n_equipment) yield from supplier.call('sell', to_purchase) self['equipment'] += to_purchase cost = to_purchase * price self.pay(cost) return self['desired_equipment'] - self['equipment'], to_purchase @asyncio.coroutine def set_production_target(self, world): """firm decides on how much supply they want to produce this step, and what they need to do to accomplish that""" # assess previous day's results self['prev_profit'] = self['profit'] self['leftover'] = self['supply'] # adjust production action = self.learner.choose_action(self.curren) action = self.actions[action] self['desired_supply'] = max(1, self['desired_supply'] + action.get('supply', 0)) self['profit_margin'] += action.get('profit_margin', 0) # supply expires every day self['supply'] = 0 # unused materials expire every day self['materials'] = 0 # resets every day self['n_sold'] = 0 self['revenue'] = 0 self['costs'] = 0 # figure out labor goal required_labor = self['desired_supply'] * self.labor_cost_per_good n_workers, wage, n_equip = self.assess_assets(required_labor, world['mean_wage'], world['mean_equip_price']) # sometimes optimization function returns a huge negative value for # workers, need to look into that further n_workers = max(n_workers, 0) self['worker_change'] = n_workers - len(self['workers']) self['desired_equipment'] = self['equipment'] + max(0, n_equip - self['equipment']) # fire workers if necessary while self['worker_change'] < 0: worker = random.choice(self['workers']) yield from self.fire(worker) self['worker_change'] += 1 # job vacancies return self['worker_change'], wage
class Firm(Agent): config = {} def __init__(self, owner): self.owner = owner self.owner._state['firm_owner'] = True self.owner.firm = self self.desired_supply = 1 # initialize self.workers = [] self.revenue = 0 self.costs = 0 self.supply = 0 self.n_sold = 0 self.profit_margin = 1 self.equipment = 0 self.materials = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.8) @property def id(self): return self.owner.id @property def public(self): return type(self.owner).__name__ == 'Government' def change_owner(self, owner): self.owner._state['firm_owner'] = False self.owner.firm = None owner._state['firm_owner'] = True owner.firm = self self.owner = owner def pay(self, cost): self.cash -= cost self.costs += cost @property def cash(self): return self.owner._state['cash'] @cash.setter def cash(self, value): self.owner._state['cash'] = value @property def profit(self): return self.revenue - self.costs def __repr__(self): return '{}\'s {}'.format(self.owner.name, type(self).__name__) @property def production_capacity(self): return math.floor(self.labor / self.config['labor_cost_per_good']) @property def worker_labor(self): return self.config['labor_per_worker'] * len(self.workers) @property def equipment_labor(self): return min(len(self.workers), self.equipment) * self.config['labor_per_equipment'] @property def labor(self): return self.worker_labor + self.equipment_labor def _labor(self, equipment): return self.worker_labor + min(len( self.workers), equipment) * self.config['labor_per_equipment'] def fire(self, worker): worker.employer = None worker.wage = 0 self.workers.remove(worker) logger.info('person:{}'.format( json.dumps({ 'event': 'fired', 'id': worker.id }))) def hire(self, applicants, wage, world): hired = [] while self.worker_change > 0 and applicants: # based on employment prob apps = [] for a in applicants: ref = 'friend' if set(a.friends).intersection( self.workers) else 'ad_or_cold_call' p = offer_prob(world['year'], world['month'], a.sex, a.race, ref, precomputed_emp_dist=emp_dist) apps.append((a, p)) apps_mass = sum(p for a, p in apps) apps = [(a, pr / apps_mass) for a, pr in apps] worker = random_choice(apps) if worker.employer is not None: worker.employer.fire(worker) worker.wage = wage worker.employer = self applicants.remove(worker) self.workers.append(worker) hired.append(worker) logger.info('person:{}'.format( json.dumps({ 'event': 'hired', 'id': worker.id }))) self.worker_change -= 1 # increase wage to attract more employees if self.worker_change > 0: wage += self.config['wage_increment'] * (1.1 + self.owner.altruism) return hired, self.worker_change, wage def close(self): self.owner._state['firm_owner'] = False self.owner.firm = None for worker in self.workers: self.fire(worker) def produce(self, world): """produce the firm's product. the firm will produce the desired supply if possible, otherwise, they will produce as much as they can.""" # limit desired supply to what can be produced given current capacity self.supply = max(1, min(self.desired_supply, self.production_capacity)) # set desired price wages = sum(w.wage for w in self.workers) self.costs += wages self.costs += self.building.rent / 30 # approximately spread out rent cost self.cash -= wages cost_per_unit = self.costs / self.supply self.price = max(0, cost_per_unit + self.profit_margin) return self.supply, self.price def sell(self, quantity): n_sold = min(self.supply, quantity) self.supply -= n_sold self.n_sold += n_sold self.revenue = self.price * n_sold self.cash += self.revenue @property def current_state(self): """represent as a discrete state""" if self.n_sold == 0: return 0 elif self.n_sold > 0 and self.leftover > 0: return 1 elif self.n_sold > 0 and self.leftover == 0 and self.profit <= 0: return 2 elif self.n_sold > 0 and self.leftover == 0 and self.profit > 0 and self.profit - self.prev_profit < 0: return 3 elif self.n_sold > 0 and self.leftover == 0 and self.profit > 0 and self.profit - self.prev_profit >= 0: return 4 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state @property def actions(self): """these actions are possible from any state""" return [{ 'supply': self.config['supply_increment'] }, { 'supply': -self.config['supply_increment'] }, { 'supply': self.config['supply_increment'], 'profit_margin': self.config['profit_increment'] }, { 'supply': self.config['supply_increment'], 'profit_margin': -self.config['profit_increment'] }, { 'supply': -self.config['supply_increment'], 'profit_margin': self.config['profit_increment'] }, { 'supply': -self.config['supply_increment'], 'profit_margin': -self.config['profit_increment'] }] def assess_assets(self, required_labor, mean_wage, mean_equip_price): """identify desired mixture of productive assets, i.e. workers, equipment, and wage""" down_wage_pressure = (-self.owner.altruism + 1.1) * -self.config['wage_increment'] def objective(x): n_workers, wage, n_equipment = x return n_workers * wage + n_equipment * mean_equip_price def constraint(x): n_workers, wage, n_equipment = x equip_labor = min(n_workers * self.config['labor_per_equipment'], n_equipment * self.config['labor_per_equipment']) return n_workers * self.config[ 'labor_per_worker'] + equip_labor - required_labor results = optimize.minimize(objective, (1, 0, 0), constraints=[{ 'type': 'ineq', 'fun': constraint }, { 'type': 'ineq', 'fun': lambda x: x[0] }, { 'type': 'ineq', 'fun': lambda x: x[1] - (mean_wage - down_wage_pressure) }, { 'type': 'ineq', 'fun': lambda x: x[2] }], options={'maxiter': 20}) n_workers, wage, n_equipment = np.ceil(results.x).astype(np.int) return n_workers, wage, n_equipment def purchase_equipment(self, supplier): total_equipment_cost = (self.desired_equipment - self.equipment) * supplier.price if not total_equipment_cost: n_equipment = max(0, self.desired_equipment - self.equipment) else: equipment_budget = max(0, min(self.cash, total_equipment_cost)) # how much equipment can be purchased n_equipment = math.floor(equipment_budget / supplier.price) to_purchase = min(supplier.supply, n_equipment) supplier.sell(to_purchase) self.equipment += to_purchase cost = to_purchase * supplier.price self.cash -= cost self.costs += cost return self.desired_equipment - self.equipment, to_purchase def set_production_target(self, world): """firm decides on how much supply they want to produce this step, and what they need to do to accomplish that""" # assess previous day's results self.prev_profit = self.profit self.leftover = self.supply # adjust production action = self.learner.choose_action(self.current_state) action = self.actions[action] self.desired_supply = max( 1, self.desired_supply + action.get('supply', 0)) self.profit_margin += action.get('profit_margin', 0) # supply expires every day self.supply = 0 # unused materials expire every day self.materials = 0 # resets every day self.n_sold = 0 self.revenue = 0 self.costs = 0 # fire workers that are being paid too much for worker in self.workers: if worker.wage >= world['mean_wage'] + ( self.config['extravagant_wage_range'] * (1.1 + self.owner.altruism)): self.fire(worker) # figure out labor goal required_labor = self.desired_supply * self.config[ 'labor_cost_per_good'] n_workers, wage, n_equip = self.assess_assets( required_labor, world['mean_wage'], world['mean_equip_price']) # sometimes optimization function returns a huge negative value for # workers, need to look into that further if n_workers < 0: n_workers = 0 self.worker_change = n_workers - len(self.workers) self.desired_equipment = self.equipment + max(0, n_equip - self.equipment) # fire workers if necessary while self.worker_change < 0: # weighted random choice by unemployment prob ws = [] for w in self.workers: pu = emp_dist[world['year']][world['month'] - 1][w.race.name][w.sex.name] ws.append((w, pu['unemployed'])) ws_mass = sum(p for w, p in ws) ws = [(w, p / ws_mass) for w, p in ws] worker = random_choice(ws) self.fire(worker) self.worker_change += 1 # job vacancies return self.worker_change, wage
class Firm(Agent): def __init__(self, labor_cost_per_good, material_cost_per_good, labor_per_equipment, labor_per_worker, supply_increment, profit_increment, wage_increment): self._super(Firm, self).__init__( state={ 'desired_supply': 1, 'desired_equipment': 0, 'worker_change': 0, 'workers': [], 'cash': 50000, 'revenue': 0, 'costs': 0, 'price': 0, 'profit': 0, 'prev_profit': 0, 'leftover': 0, 'supply': 0, 'n_sold': 0, 'profit_margin': 1, 'equipment': 0, 'materials': 0, }) self.material_cost_per_good = material_cost_per_good self.labor_cost_per_good = labor_cost_per_good self.labor_per_equipment = labor_per_equipment self.labor_per_worker = labor_per_worker self.supply_increment = supply_increment self.profit_increment = profit_increment self.wage_increment = wage_increment # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(5)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.1, learning_rate=0.8) def pay(self, cost): self['cash'] -= cost self['costs'] += cost @property def _production_capacity(self): """how many goods can be produced given current labor power""" return math.floor(self._labor / self.labor_cost_per_good) @property def _worker_labor(self): """labor from workers, not counting equipment""" return self.labor_per_worker * len(self['workers']) @property def _equipment_labor(self): """how much labor can be generated by owned equipment, limited by number of workers (one worker is required to operate one piece of equipment)""" return min(len(self['workers']), self['equipment']) * self.labor_per_equipment @property def _labor(self): """total productive labor""" return self._worker_labor + self._equipment_labor def _labor_for_equipment(self, equipment): """hypothetical labor that could be produced by some amount of equipment, limited by number of workers""" return self._worker_labor + min(len(self['workers']), equipment) * self.labor_per_equipment @asyncio.coroutine def fire(self, worker): self['workers'].remove(worker) yield from worker.call('quit') @asyncio.coroutine def hire(self, applicants, wage): hired = [] while self['worker_change'] > 0 and applicants: worker = random.choice(applicants) employer = yield from worker.get('employer') if employer is not None: yield from employer.call('fire', worker) yield from worker.call('hire', AgentProxy(self), wage) applicants.remove(worker) self['workers'].append(worker) hired.append(worker) self['worker_change'] -= 1 # increase wage to attract more employees if self['worker_change'] > 0: wage += self.wage_increment return hired, self['worker_change'], wage @asyncio.coroutine def shutdown(self): for worker in self['workers']: yield from self.fire(worker) def produce(self, world): """produce the firm's product. the firm will produce the desired supply if possible, otherwise, they will produce as much as they can.""" # limit desired supply to what can be produced given current capacity self['supply'] = max( 1, min(self['desired_supply'], self._production_capacity)) # set desired price wages = 0 for w in self['workers']: wages += (yield from w.get('wage')) self['costs'] += wages self['cash'] -= wages cost_per_unit = self['costs'] / self['supply'] self['price'] = max(0, cost_per_unit + self['profit_margin']) return self['supply'], self['price'] @asyncio.coroutine def sell(self, quantity): n_sold = min(self['supply'], quantity) self['supply'] -= n_sold self['n_sold'] += n_sold self['revenue'] = self['price'] * n_sold self['cash'] += self['revenue'] return n_sold @property def curren(self): """represent as a discrete state""" if self['n_sold'] == 0: return 0 elif self['n_sold'] > 0 and self['leftover'] > 0: return 1 elif self['n_sold'] > 0 and self[ 'leftover'] == 0 and self['profit'] <= 0: return 2 elif self['n_sold'] > 0 and self['leftover'] == 0 and self[ 'profit'] > 0 and self['profit'] - self['prev_profit'] < 0: return 3 elif self['n_sold'] > 0 and self['leftover'] == 0 and self[ 'profit'] > 0 and self['profit'] - self['prev_profit'] >= 0: return 4 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state @property def actions(self): """these actions are possible from any state""" return [{ 'supply': self.supply_increment }, { 'supply': -self.supply_increment }, { 'supply': self.supply_increment, 'profit_margin': self.profit_increment }, { 'supply': self.supply_increment, 'profit_margin': -self.profit_increment }, { 'supply': -self.supply_increment, 'profit_margin': self.profit_increment }, { 'supply': -self.supply_increment, 'profit_margin': -self.profit_increment }] def assess_assets(self, required_labor, mean_wage, mean_equip_price): """identify desired mixture of productive assets, i.e. workers, equipment, and wage""" down_wage_pressure = self.wage_increment def objective(x): n_workers, wage, n_equipment = x return n_workers * wage + n_equipment * mean_equip_price def constraint(x): n_workers, wage, n_equipment = x equip_labor = min(n_workers * self.labor_per_equipment, n_equipment * self.labor_per_equipment) return n_workers * self.labor_per_worker + equip_labor - required_labor results = optimize.minimize(objective, (1, 0, 0), constraints=[{ 'type': 'ineq', 'fun': constraint }, { 'type': 'ineq', 'fun': lambda x: x[0] }, { 'type': 'ineq', 'fun': lambda x: x[1] - (mean_wage - down_wage_pressure) }, { 'type': 'ineq', 'fun': lambda x: x[2] }], options={'maxiter': 20}) n_workers, wage, n_equipment = np.ceil(results.x).astype(np.int) return n_workers, wage, n_equipment @asyncio.coroutine def purchase_equipment(self, supplier): price, supply = yield from supplier.get('price', 'supply') total_equipment_cost = (self['desired_equipment'] - self['equipment']) * price if not total_equipment_cost: n_equipment = max(0, self['desired_equipment'] - self['equipment']) else: equipment_budget = max(0, min(self['cash'], total_equipment_cost)) # how much equipment can be purchased n_equipment = math.floor(equipment_budget / price) to_purchase = min(supply, n_equipment) yield from supplier.call('sell', to_purchase) self['equipment'] += to_purchase cost = to_purchase * price self.pay(cost) return self['desired_equipment'] - self['equipment'], to_purchase @asyncio.coroutine def set_production_target(self, world): """firm decides on how much supply they want to produce this step, and what they need to do to accomplish that""" # assess previous day's results self['prev_profit'] = self['profit'] self['leftover'] = self['supply'] # adjust production action = self.learner.choose_action(self.curren) action = self.actions[action] self['desired_supply'] = max( 1, self['desired_supply'] + action.get('supply', 0)) self['profit_margin'] += action.get('profit_margin', 0) # supply expires every day self['supply'] = 0 # unused materials expire every day self['materials'] = 0 # resets every day self['n_sold'] = 0 self['revenue'] = 0 self['costs'] = 0 # figure out labor goal required_labor = self['desired_supply'] * self.labor_cost_per_good n_workers, wage, n_equip = self.assess_assets( required_labor, world['mean_wage'], world['mean_equip_price']) # sometimes optimization function returns a huge negative value for # workers, need to look into that further n_workers = max(n_workers, 0) self['worker_change'] = n_workers - len(self['workers']) self['desired_equipment'] = self['equipment'] + max( 0, n_equip - self['equipment']) # fire workers if necessary while self['worker_change'] < 0: worker = random.choice(self['workers']) yield from self.fire(worker) self['worker_change'] += 1 # job vacancies return self['worker_change'], wage
class Government(Agent): def __init__(self, tax_rate, welfare, tax_rate_increment, welfare_increment, starting_welfare_req): self._state = {'cash': 0} self.tax_rate = tax_rate self.tax_rate_increment = tax_rate_increment self.welfare = welfare self.welfare_increment = welfare_increment self.welfare_req = starting_welfare_req self.subsidies = { ConsumerGoodFirm: 0, CapitalEquipmentFirm: 0, RawMaterialFirm: 0, Hospital: 0 } self.altruism = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(3)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.5) # keep track of previous step's quality of life for comparison self.prev_qol = 0 @property def cash(self): return self._state['cash'] @cash.setter def cash(self, value): self._state['cash'] = value @property def actions(self): """these actions are possible from any state""" return [ {'tax_rate': self.tax_rate_increment}, {'tax_rate': -self.tax_rate_increment}, {'tax_rate': self.tax_rate_increment, 'welfare': self.welfare_increment}, {'tax_rate': self.tax_rate_increment, 'welfare': -self.welfare_increment}, {'tax_rate': -self.tax_rate_increment, 'welfare': self.welfare_increment}, {'tax_rate': -self.tax_rate_increment, 'welfare': -self.welfare_increment} ] def current_state(self, households): """represent as a discrete state""" qol = sum(h.quality_of_life for h in households)/len(households) if households else 0 if qol <= 0: return 0 elif qol > 0 and qol - self.prev_qol <= 0: return 1 elif qol > 0 and qol - self.prev_qol > 0: return 2 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state def adjust(self, households): action = self.learner.choose_action(self.current_state(households)) action = self.actions[action] self.tax_rate = min(1, max(0, self.tax_rate + action.get('tax_rate', 0))) max_per_person = self.cash/sum(len(h.people) for h in households if h.income <= self.welfare_req) if households else 0 self.welfare = min(max(0, self.welfare + action.get('welfare', 0)), max_per_person) self.prev_qol = sum(h.quality_of_life for h in households)/len(households) if households else 0 def apply_proposal(self, proposal, world): t = proposal['type'] v = float(proposal['value']) if proposal.get('value') is not None else None if t == ProposalType.nationalize.name: industry = proposal['target'] firm = random.choice(world.firms_of_type(industries[industry])) firm.change_owner(self) elif t == ProposalType.privatize.name: industry = proposal['target'] firm = random.choice(world.firms_of_type(industries[industry])) # randomly pick new owner # we pick the person with the most money who does not already have a firm candidates = sorted([p for p in world.people if not p._state['firm_owner']], key=lambda p: p._state['cash'], reverse=True) new_owner = candidates[0] firm.change_owner(new_owner) elif t == ProposalType.tax_rate.name: self.tax_rate = v elif t == ProposalType.welfare.name: self.welfare = v elif t == ProposalType.welfare_req.name: self.welfare_req = v elif t == ProposalType.subsidy.name: self.subsidies[industries[proposal['target']]] = v def proposal_options(self, world): options = [{ 'type': ProposalType.tax_rate.name, 'name': 'adjust tax rate', 'description': 'Adjust the tax rate for all income and corporate profits', 'values': [0, 1], 'targets': None, 'value': self.tax_rate }, { 'type': ProposalType.welfare.name, 'name': 'adjust welfare', 'description': 'Set the amount of cash distributed to every citizen who makes less than the welfare requirement', 'values': [0, None], 'targets': None, 'value': self.welfare }, { 'type': ProposalType.welfare_req.name, 'name': 'adjust welfare income threshold', 'description': 'Citizens who make less than this income will receive welfare', 'values': [0, None], 'targets': None, 'value': self.welfare_req }, { 'type': ProposalType.subsidy.name, 'name': 'adjust industry subsidy', 'description': 'Set the amount of cash government gives a particular industry', 'values': [0, None], 'targets': list(industries.keys()), 'value': None }] private_industries = self.filter_industries(world, public=False) public_industries = self.filter_industries(world, public=True) if public_industries: options.append({ 'type': ProposalType.privatize.name, 'name': 'privatize a public firm', 'description': 'Release a national firm into private management', 'values': None, 'targets': public_industries, 'value': None }) if private_industries: options.append({ 'type': ProposalType.nationalize.name, 'name': 'nationalize a private firm', 'description': 'Put a private firm into the control of the people', 'values': None, 'targets': private_industries, 'value': None }) return options def filter_industries(self, world, public=False): """return industries that have firms in them""" return [ind for ind, typ in industries.items() if [f for f in world.firms_of_type(typ) if f.public == public]] def as_json(self): return { 'tax_rate': self.tax_rate, 'welfare': self.welfare, 'welfare_req': self.welfare_req, 'subsidies': {k.__name__: v for k, v in self.subsidies.items()} }
class Government(Agent): def __init__(self, tax_rate, welfare, tax_rate_increment, welfare_increment, starting_welfare_req): self._state = {'cash': 0} self.tax_rate = tax_rate self.tax_rate_increment = tax_rate_increment self.welfare = welfare self.welfare_increment = welfare_increment self.welfare_req = starting_welfare_req self.subsidies = { ConsumerGoodFirm: 0, CapitalEquipmentFirm: 0, RawMaterialFirm: 0, Hospital: 0 } self.altruism = 0 # all states map to the same actions action_ids = [i for i in range(len(self.actions))] states_actions = {s: action_ids for s in range(3)} self.learner = QLearner(states_actions, self.reward, discount=0.5, explore=0.01, learning_rate=0.5) # keep track of previous step's quality of life for comparison self.prev_qol = 0 @property def cash(self): return self._state['cash'] @cash.setter def cash(self, value): self._state['cash'] = value @property def actions(self): """these actions are possible from any state""" return [{ 'tax_rate': self.tax_rate_increment }, { 'tax_rate': -self.tax_rate_increment }, { 'tax_rate': self.tax_rate_increment, 'welfare': self.welfare_increment }, { 'tax_rate': self.tax_rate_increment, 'welfare': -self.welfare_increment }, { 'tax_rate': -self.tax_rate_increment, 'welfare': self.welfare_increment }, { 'tax_rate': -self.tax_rate_increment, 'welfare': -self.welfare_increment }] def current_state(self, households): """represent as a discrete state""" qol = sum(h.quality_of_life for h in households) / len(households) if households else 0 if qol <= 0: return 0 elif qol > 0 and qol - self.prev_qol <= 0: return 1 elif qol > 0 and qol - self.prev_qol > 0: return 2 def reward(self, state): """the discrete states we map to are the reward values, so just return that""" return state def adjust(self, households): action = self.learner.choose_action(self.current_state(households)) action = self.actions[action] self.tax_rate = min(1, max(0, self.tax_rate + action.get('tax_rate', 0))) max_per_person = self.cash / sum( len(h.people) for h in households if h.income <= self.welfare_req) if households else 0 self.welfare = min(max(0, self.welfare + action.get('welfare', 0)), max_per_person) self.prev_qol = sum( h.quality_of_life for h in households) / len(households) if households else 0 def apply_proposal(self, proposal, world): t = proposal['type'] v = float( proposal['value']) if proposal.get('value') is not None else None if t == ProposalType.nationalize.name: industry = proposal['target'] firm = random.choice(world.firms_of_type(industries[industry])) firm.change_owner(self) elif t == ProposalType.privatize.name: industry = proposal['target'] firm = random.choice(world.firms_of_type(industries[industry])) # randomly pick new owner # we pick the person with the most money who does not already have a firm candidates = sorted( [p for p in world.people if not p._state['firm_owner']], key=lambda p: p._state['cash'], reverse=True) new_owner = candidates[0] firm.change_owner(new_owner) elif t == ProposalType.tax_rate.name: self.tax_rate = v elif t == ProposalType.welfare.name: self.welfare = v elif t == ProposalType.welfare_req.name: self.welfare_req = v elif t == ProposalType.subsidy.name: self.subsidies[industries[proposal['target']]] = v def proposal_options(self, world): options = [{ 'type': ProposalType.tax_rate.name, 'name': 'adjust tax rate', 'description': 'Adjust the tax rate for all income and corporate profits', 'values': [0, 1], 'targets': None, 'value': self.tax_rate }, { 'type': ProposalType.welfare.name, 'name': 'adjust welfare', 'description': 'Set the amount of cash distributed to every citizen who makes less than the welfare requirement', 'values': [0, None], 'targets': None, 'value': self.welfare }, { 'type': ProposalType.welfare_req.name, 'name': 'adjust welfare income threshold', 'description': 'Citizens who make less than this income will receive welfare', 'values': [0, None], 'targets': None, 'value': self.welfare_req }, { 'type': ProposalType.subsidy.name, 'name': 'adjust industry subsidy', 'description': 'Set the amount of cash government gives a particular industry', 'values': [0, None], 'targets': list(industries.keys()), 'value': None }] private_industries = self.filter_industries(world, public=False) public_industries = self.filter_industries(world, public=True) if public_industries: options.append({ 'type': ProposalType.privatize.name, 'name': 'privatize a public firm', 'description': 'Release a national firm into private management', 'values': None, 'targets': public_industries, 'value': None }) if private_industries: options.append({ 'type': ProposalType.nationalize.name, 'name': 'nationalize a private firm', 'description': 'Put a private firm into the control of the people', 'values': None, 'targets': private_industries, 'value': None }) return options def filter_industries(self, world, public=False): """return industries that have firms in them""" return [ ind for ind, typ in industries.items() if [f for f in world.firms_of_type(typ) if f.public == public] ] def as_json(self): return { 'tax_rate': self.tax_rate, 'welfare': self.welfare, 'welfare_req': self.welfare_req, 'subsidies': {k.__name__: v for k, v in self.subsidies.items()} }