def __init__(self, unique_id, model, liquid_assets, market_share): super().__init__(unique_id, model) self.group = None self.liquid_assets = d(liquid_assets) self.market_share = d(market_share)
class Firm(Agent): log = logging.getLogger(__name__) debt_stock = d(0) unit_production_cost = d(0) competitiveness = d(100) capital_employed = d(0) debt_employed = d(0) residual_assets = None available_debt = None bankrupt = False def __init__(self, unique_id, model, liquid_assets, market_share): super().__init__(unique_id, model) self.group = None self.liquid_assets = d(liquid_assets) self.market_share = d(market_share) def fix_price(self): return (1 + self.model.mark_up) * self.unit_production_cost def finance_operations(self, quantity, unit_cost): cost = unit_cost * quantity capital_employed = min(cost, self.residual_assets) debt_employed = min(cost - capital_employed, self.available_debt) return capital_employed, debt_employed
def __init__(self, unique_id, model, liquid_assets, market_share, **kwargs): super().__init__(unique_id, model, liquid_assets, market_share) self.group = 'capital_firm' self.machine = Machine( producer=unique_id, generation=1, lpc=d(100), price=d(1) )
def forecast_expansion_investment(self): trigger_level = d(int(self.capital_stock * (1 + self.model.trigger_rule))) planned_ei = 0 if self.desired_capital_stock >= trigger_level: # NOTE: in paper this is trigger_level planned_ei = trigger_level - self.capital_stock return planned_ei
def replace_and_add_machines(self): # TODO: add logging supplier = self.model.schedule.get_agent(self.supplier) if supplier.output < supplier.demand: # TODO: assign residual orders to firms received_orders = ( int(d(self.investment / supplier.orders) * supplier.output) ) self.reimburse_investment(received_orders) new_machines = self.expansion_investment for machine_type in self.want_to_scrap: stock = self.machines[machine_type]['stock'] if stock <= self.replacement_investment: self.machines.pop(machine_type) new_machines += stock else: self.machines[machine_type]['stock'] -= self.replacement_investment new_machines += self.replacement_investment break new_type = (supplier.unique_id, supplier.machine.generation) if new_type in self.machines: self.machines[new_type]['stock'] += new_machines return None self.machines[new_type] = { 'machine': deepcopy(supplier.machine), 'stock': new_machines }
def update_employment(self): self.employment = min(self.labour_demand, self.labour_supply) self.unemployment = max(0, self.labour_supply - self.employment) unemployment_rate = d(self.unemployment / self.labour_supply) delta_unemployment = unemployment_rate - self.unemployment_rate self.unemployment_rate = unemployment_rate self.delta_unemployment = delta_unemployment
def compute_average_productivity(self): productivities = [] machine_set = list(self.machines.keys()) for machine_type in machine_set: machine = self.machines[machine_type]['machine'] stock = self.machines[machine_type]['stock'] productivity = ( machine.labour_productivity_coefficient * stock ) productivities.append(productivity) avgp = sum(productivities) / self.capital_stock return d(float(avgp))
def compute_market_share(self): sector_avg_comp = self.model.avg_comp_competitiveness if self.unique_id == 50: self.log.info('computing market share') self.log.info(f'own competitiveness: {self.competitiveness}') self.log.info(f'sector average competitiveness: {sector_avg_comp}') ms = ( self.market_share * (1 + self.model.replicator_dynamics_coeff[0] * (self.competitiveness - sector_avg_comp) / sector_avg_comp) ) return d(max(0, ms))
def forecast_demand(self, myopic=True): if myopic: return self.demand firm_data = self.model.datacollector.get_table_dataframe( 'consumption_firm' ) # model_data = self.model.datacollector.get_model_vars_dataframe() past_demand = firm_data.loc[ firm_data.agent_id == self.unique_id, 'demand' ].tolist()[-4:] return sum([ b * d(t) for b, t in zip(self.model.betas, past_demand) ])
def innovate(self): if not self.model.innovation: return None epsilon = d(self.random.uniform(*self.model.distribution_bounds)) new_lpc = ( self.machine.labour_productivity_coefficient * (1 + epsilon) ) if new_lpc > self.machine.labour_productivity_coefficient: self.machine = Machine( producer=self.unique_id, generation=self.machine.generation + 1, lpc=new_lpc, price=self.machine.price )
def gov_base_consumption(model): government_consumption = ( d(model.wage_share) * model.market_wage * model.labour_supply ) return government_consumption
class ConsumptionGoodFirm(Firm): inventory = d(0) unfilled_demand_rate = d(0) average_productivity = d(0) price = d(0) expected_demand = d(0) desired_production = d(0) desired_capital_stock = d(0) production_rationed = False planned_production = d(0) want_to_scrap = [] desired_ei = d(0) desired_ri = d(0) expansion_investment = d(0) replacement_investment = d(0) labour_demand = d(0) profit = d(0) def __init__(self, unique_id, model, liquid_assets, capital_stock, market_share, supplier=None): super().__init__(unique_id, model, liquid_assets, market_share) self.group = 'consumption_firm' self.supplier = supplier # initial values self.capital_stock = capital_stock # calculated capital_firms = self.model.get_group('capital_firm') self.machines = { (supplier.unique_id, supplier.machine.generation): { 'machine': supplier.machine, 'stock': 40 } for supplier in capital_firms } self.demand = self.market_share * self.model.consumption # / self.model.cpi self.production = self.demand self.output = self.production self.sales = self.output row = { 'step': -1, 'agent_id': self.unique_id, 'market_share': self.market_share, 'demand': self.demand, 'production': self.production, 'output': self.output, 'inventory': self.inventory, 'sales': self.sales, 'liquid_assets': self.liquid_assets, 'capital_stock': self.capital_stock, 'debt_stock': self.debt_stock, 'supplier': self.supplier } self.model.datacollector.add_table_row( 'consumption_firm', row, ignore_missing=True ) @property def investment(self): return self.expansion_investment + self.replacement_investment @property def available_financing(self): return self.residual_assets + self.available_debt def compute_max_quantity(self, unit_cost): return d(int(self.available_financing / unit_cost)) def compute_unfilled_demand(self): if self.demand == 0: return 0 return 1 - self.sales / self.demand def compute_average_productivity(self): productivities = [] machine_set = list(self.machines.keys()) for machine_type in machine_set: machine = self.machines[machine_type]['machine'] stock = self.machines[machine_type]['stock'] productivity = ( machine.labour_productivity_coefficient * stock ) productivities.append(productivity) avgp = sum(productivities) / self.capital_stock return d(float(avgp)) def compute_unit_production_cost(self): return ( self.model.market_wage / self.average_productivity ) def adjust_competitiveness(self): w1, w2 = self.model.competitiveness_weights[0] adj = round(-w1 * self.price - w2 * self.unfilled_demand_rate, 2) self.competitiveness = max(0, self.competitiveness - adj) def compute_debt_availability(self): max_debt = self.model.max_debt_sales_ratio * self.sales return max(0, max_debt - self.debt_stock) def forecast_demand(self, myopic=True): if myopic: return self.demand firm_data = self.model.datacollector.get_table_dataframe( 'consumption_firm' ) # model_data = self.model.datacollector.get_model_vars_dataframe() past_demand = firm_data.loc[ firm_data.agent_id == self.unique_id, 'demand' ].tolist()[-4:] return sum([ b * d(t) for b, t in zip(self.model.betas, past_demand) ]) def forecast_production(self): demand_net = self.expected_demand - self.inventory return max(0, demand_net) def forecast_capital_stock(self): return d(int( self.desired_production / self.model.desired_capital_utilization )) def plan_production(self): production_max = self.compute_max_quantity( self.unit_production_cost ) production = min(self.desired_production, production_max) # check if production has to be rationed if production < self.desired_production: self.production_rationed = True else: self.production_rationed = False return production def plan_production_financing(self): return self.finance_operations( self.planned_production, self.unit_production_cost ) def compute_labour_demand(self): return self.planned_production / self.average_productivity def forecast_expansion_investment(self): trigger_level = d(int(self.capital_stock * (1 + self.model.trigger_rule))) planned_ei = 0 if self.desired_capital_stock >= trigger_level: # NOTE: in paper this is trigger_level planned_ei = trigger_level - self.capital_stock return planned_ei def choose_supplier(self, subset=10): # NOTE: in the paper, supplier is chosen based on market share current_supplier = self.model.schedule.get_agent(self.supplier, _raise=False) capital_firms = self.model.get_group('capital_firm') sample = self.random.sample(capital_firms, subset) ratios = [s.machine.lpc_price_ratio for s in sample] new_supplier_id = sample[ratios.index(max(ratios))].unique_id if not current_supplier or current_supplier.bankrupt: return new_supplier_id return ( new_supplier_id if max(ratios) > current_supplier.machine.lpc_price_ratio else self.supplier # NOTE: causes consolidation in cap market? ) def plan_replacement(self): supplier = self.model.schedule.get_agent(self.supplier) machine_set = [k for k, v in self.machines.items() if v['stock'] > 0] want_to_scrap = [] for machine_type in machine_set: machine = self.machines[machine_type]['machine'] unit_labour_cost = machine.compute_unit_labour_cost(self.model) if unit_labour_cost == supplier.unit_production_cost: continue x = ( supplier.price / (unit_labour_cost - supplier.unit_production_cost) ) if x <= self.model.payback_period_parameter: want_to_scrap.append(machine_type) return want_to_scrap def forecast_replacement_investment(self): return sum([self.machines[m]['stock'] for m in self.want_to_scrap]) def fix_investment(self): """Allocate between expanding capital stock and replacement """ if self.production_rationed: return 0, 0 machine_cost = self.model.schedule.get_agent(self.supplier).machine.price ce_q, de_q = self.plan_production_financing() self.residual_assets -= ce_q self.available_debt -= de_q investment_max = self.compute_max_quantity(machine_cost) expansion_i = min(self.desired_ei, investment_max) ce_ei, de_ei = self.finance_operations(expansion_i, machine_cost) self.residual_assets -= ce_ei self.available_debt -= de_ei replacement_i = min(self.desired_ri, investment_max - expansion_i) ce_ri, de_ri = self.finance_operations(replacement_i, machine_cost) self.residual_assets = self.residual_assets - ce_ri + ce_q self.available_debt = self.available_debt - de_ri + de_q self.capital_employed = ce_ei + ce_ri self.debt_employed = de_ei + de_ri return expansion_i, replacement_i def compute_market_share(self): sector_avg_comp = self.model.avg_comp_competitiveness if self.unique_id == 50: self.log.info('computing market share') self.log.info(f'own competitiveness: {self.competitiveness}') self.log.info(f'sector average competitiveness: {sector_avg_comp}') ms = ( self.market_share * (1 + self.model.replicator_dynamics_coeff[0] * (self.competitiveness - sector_avg_comp) / sector_avg_comp) ) return d(max(0, ms)) def compute_demand(self): # TODO: should be dependant on price? return self.model.consumption * self.market_share # / self.price def compute_labour_availability(self): return self.market_share * ( self.model.labour_supply - self.model.capital_labour_demand ) def fix_production(self): labour_res = self.compute_labour_availability() max_q = labour_res * self.average_productivity production = min(max_q, self.planned_production) ce_q, de_q = self.finance_operations(production, self.unit_production_cost) self.residual_assets -= ce_q self.available_debt -= de_q self.debt_stock += self.debt_employed + de_q return production def compute_sales(self): return min(self.demand, self.output) def compute_inventory(self): inventory = self.output - self.sales # deprecate inventory if inventory > 0: inventory = ( int(inventory * (1 - self.model.inventory_deprecation)) ) return inventory def compute_profit(self): revenue = self.price * self.sales production_cost = self.unit_production_cost * self.production debt_payments = self.model.interest_rate * self.debt_stock return revenue - production_cost - debt_payments def compute_liquid_assets(self): return self.liquid_assets + self.profit - self.capital_employed def reimburse_investment(self, received_orders): self.liquid_assets += ( received_orders * self.model.schedule.get_agent(self.supplier).price ) if received_orders < self.expansion_investment: self.expansion_investment = received_orders self.replacement_investment = 0 else: self.replacement_investment = received_orders - self.expansion_investment def replace_and_add_machines(self): # TODO: add logging supplier = self.model.schedule.get_agent(self.supplier) if supplier.output < supplier.demand: # TODO: assign residual orders to firms received_orders = ( int(d(self.investment / supplier.orders) * supplier.output) ) self.reimburse_investment(received_orders) new_machines = self.expansion_investment for machine_type in self.want_to_scrap: stock = self.machines[machine_type]['stock'] if stock <= self.replacement_investment: self.machines.pop(machine_type) new_machines += stock else: self.machines[machine_type]['stock'] -= self.replacement_investment new_machines += self.replacement_investment break new_type = (supplier.unique_id, supplier.machine.generation) if new_type in self.machines: self.machines[new_type]['stock'] += new_machines return None self.machines[new_type] = { 'machine': deepcopy(supplier.machine), 'stock': new_machines } def compute_capital_stock(self): return sum([mtype['stock'] for mtype in self.machines.values()]) # stage methods def stage_one(self): if self.bankrupt: return None self.residual_assets = self.liquid_assets self.available_debt = self.compute_debt_availability() self.unfilled_demand_rate = self.compute_unfilled_demand() self.average_productivity = self.compute_average_productivity() self.unit_production_cost = self.compute_unit_production_cost() self.price = self.fix_price() # if demand was underestimated, competitiveness decreases if self.output < self.demand: self.adjust_competitiveness() # self.debt_availability = self.compute_debt_availability() if self.unique_id == 50: self.log.info(messages.comp_stage_one.format( step=self.model.schedule.steps, stage=self.model.schedule.stage, group=self.group, agent_id=self.unique_id, res_assets=self.residual_assets, res_debt=self.available_debt, unfilled_demand=self.unfilled_demand_rate, avg_prod=self.average_productivity, upc=self.unit_production_cost, price=self.price, compet=self.competitiveness )) def stage_two(self): if self.bankrupt: return None self.expected_demand = self.forecast_demand() self.desired_production = self.forecast_production() self.desired_capital_stock = self.forecast_capital_stock() self.planned_production = self.plan_production() self.labour_demand = self.compute_labour_demand() self.supplier = self.choose_supplier() self.want_to_scrap = self.plan_replacement() self.desired_ei = self.forecast_expansion_investment() self.desired_ri = self.forecast_replacement_investment() self.expansion_investment, self.replacement_investment = self.fix_investment() # register order with supplier self.model.schedule.get_agent(self.supplier).orders += self.investment if self.unique_id == 50: self.log.info(messages.comp_stage_two.format( step=self.model.schedule.steps, stage=self.model.schedule.stage, group=self.group, agent_id=self.unique_id, demand_e=self.expected_demand, production_d=self.desired_production, cap_stock_d=self.desired_capital_stock, production_p=self.planned_production, labour_demand=self.labour_demand, supplier=self.supplier, scrap=pformat(self.want_to_scrap), expansion_i=self.expansion_investment, replacement_i=self.replacement_investment )) def stage_three(self): if self.bankrupt: return None pass def stage_four(self): if self.bankrupt: return None self.market_share = self.compute_market_share() self.demand = self.compute_demand() self.production = self.fix_production() # need to adjust labour demand here self.output = self.production + self.inventory self.sales = self.compute_sales() self.inventory = self.compute_inventory() self.profit = self.compute_profit() self.liquid_assets = self.compute_liquid_assets() if self.unique_id == 50: self.log.info(messages.comp_stage_four.format( step=self.model.schedule.steps, stage=self.model.schedule.stage, group=self.group, agent_id=self.unique_id, market_share=self.market_share, demand=self.demand, production=self.production, output=self.output, sales=self.sales, inventory=self.inventory, profit=self.profit, assets=self.liquid_assets )) def stage_five(self): row = { 'step': self.model.schedule.steps, 'agent_id': self.unique_id, 'competitiveness': self.competitiveness, 'expected_demand': self.expected_demand, 'market_share': self.market_share, 'demand': self.demand, 'desired_production': self.desired_production, 'desired_capital_stock': self.desired_capital_stock, 'labour_demand': self.labour_demand, 'desired_ei': self.desired_ei, 'desired_ri': self.desired_ri, 'supplier': self.supplier, 'expansion_investment': self.expansion_investment, 'replacement_investment': self.replacement_investment, 'production': self.production, 'output': self.output, 'inventory': self.inventory, 'sales': self.sales, 'profit': self.profit, 'liquid_assets': self.liquid_assets, 'capital_stock': self.capital_stock, 'debt_stock': self.debt_stock, 'price': self.price, 'upc': self.unit_production_cost, 'average_productivity': self.average_productivity, 'available_debt': self.available_debt, 'bankrupt': self.bankrupt } self.model.datacollector.add_table_row('consumption_firm', row) if self.bankrupt: return None self.replace_and_add_machines() self.capital_stock = self.compute_capital_stock()
def compute_market_share(self): investment = self.model.investment market_share = ( d(self.demand / investment) if investment > 0 else self.market_share ) return market_share
class ComplexEconomy(Model): log = logging.getLogger(__name__) groups = ['consumption_firm', 'capital_firm'] stages = ['stage_one', 'stage_two', 'stage_three', 'stage_four', 'stage_five'] stage_functions = { 'stage_one': [ 'update_average_prices', 'update_avg_ulc', 'update_average_labour_productivity', 'update_sector_competitiveness' ], 'stage_two': [ 'aggregate_investment' ], 'stage_three': [ 'update_labour_supply', 'aggregate_labour_demand', 'update_employment', 'update_market_wage', 'update_consumption' ], 'stage_four': [ 'aggregate_production', 'aggregate_inventories' ], 'stage_five': [ 'exit_and_entry' ] } avg_cap_price = d(0) avg_comp_competitiveness = d(0) avg_cap_competitiveness = d(0) average_unit_labour_cost = d(0) consumption_labour_demand = d(0) unemployment = d(0) delta_cpi = d(0) delta_productivity = d(0) delta_unemployment = d(0) expansion_investment = d(0) replacement_investment = d(0) agg_inventories = d(0) agg_production = d(0) def __init__(self, parameters, market_wage, cpi, avg_labour_productivity, liquid_assets, capital_stock, labour_supply, innovation, seed=None): self.schedule = GroupedActivation( self, self.groups, self.stages, interim_functions=self.stage_functions ) self.datacollector = DataCollector( model_reporters={ 'market_wage': 'market_wage', 'consumption': 'consumption', 'expansion_investment': 'expansion_investment', 'replacement_investment': 'replacement_investment', 'investment': 'investment', 'inventories': 'agg_inventories', 'production': 'agg_production', 'cpi': 'cpi', 'avg_cap_price': 'avg_cap_price', 'avg_labour_prod': 'avg_labour_prod', 'labour_supply': 'labour_supply', 'labour_demand': 'labour_demand', 'employment': 'employment', 'unemployment': 'unemployment', 'avg_comp_competitiveness': 'avg_comp_competitiveness', 'avg_cap_competitiveness': 'avg_cap_competitiveness', 'gdp': compute_gdp }, tables={ 'consumption_firm': { 'step': [], 'agent_id': [], 'competitiveness': [], 'expected_demand': [], 'market_share': [], 'demand': [], 'desired_production': [], 'desired_capital_stock': [], 'labour_demand': [], 'desired_ei': [], 'desired_ri': [], 'supplier': [], 'expansion_investment': [], 'replacement_investment': [], 'production': [], 'output': [], 'inventory': [], 'sales': [], 'profit': [], 'liquid_assets': [], 'capital_stock': [], 'debt_stock': [], 'price': [], 'upc': [], 'average_productivity': [], 'available_debt': [], 'bankrupt': [] }, 'capital_firm': { 'step': [], 'agent_id': [], 'competitiveness': [], 'demand': [], 'production': [], 'labour_demand': [], 'output': [], 'sales': [], 'profit': [], 'liquid_assets': [], 'debt_stock': [], 'market_share': [], 'machine_generation': [], 'price': [], 'upc': [], 'labour_productivity': [], 'available_debt': [], 'bankrupt': [] } } ) self.innovation = innovation self.social_policy = parameters['social_policy'] self.inventory_deprecation = parameters['inventory_deprecation'] # parameters # TODO: convert parameters to decimal n_consumption_firms = parameters['n_consumption_firms'] n_capital_firms = parameters['n_capital_firms'] replicators = parameters['replicator_dynamics_coeff'] self.replicator_dynamics_coeff = (d(replicators[0]), d(replicators[1])) comp_weights = parameters['competitiveness_weights'] self.competitiveness_weights = ( (d(comp_weights[0][0]), d(comp_weights[0][1])), (d(comp_weights[1][0]), d(comp_weights[1][1])) ) self.distribution_bounds = parameters['distribution_bounds'] self.labour_supply_growth = d(parameters['labour_supply_growth']) self.wage_setting = { k: d(v) for k, v in parameters['wage_setting'].items() } self.desired_capital_utilization = d(parameters['desired_capital_utilization']) self.trigger_rule = d(parameters['trigger_rule']) self.payback_period_parameter = d(parameters['payback_period_parameter']) self.mark_up = d(parameters['mark_up']) self.interest_rate = d(parameters['interest_rate']) self.wage_share = d(parameters['wage_share']) self.betas = [d(b) for b in parameters['betas']] # NOTE: max_debt_sales_ratio is not specified in the paper self.max_debt_sales_ratio = d(parameters['max_debt_sales_ratio']) # initial conditions self.market_wage = d(market_wage) self.cpi = d(cpi) self.avg_labour_prod = d(avg_labour_productivity) self.labour_supply = labour_supply # computed and derived self.max_capital_labour_share = d( n_capital_firms / (n_consumption_firms + n_capital_firms) ) init_market_share = (d(1 / n_consumption_firms), d(1 / n_capital_firms)) self.employment = labour_supply self.consumption = self.compute_consumption() self.unemployment_rate = d(self.unemployment / self.employment) # create capital good firms for i in range(n_capital_firms): f = CapitalGoodFirm(i, self, liquid_assets, init_market_share[1]) self.schedule.add(f) # create consumption good firms supplier = 0 for i in range(n_consumption_firms): if parameters['fix_supplier']: if i % 4 == 0 and i != 0: supplier += 1 else: supplier = None f = ConsumptionGoodFirm( i + n_capital_firms, self, liquid_assets, capital_stock, init_market_share[0], supplier=supplier ) self.schedule.add(f) self.running = True self.datacollector.collect(self) self.log.info(messages.model_init_message.format( wage=self.market_wage, cpi=self.cpi, avg_labour_prod=self.avg_labour_prod, labour_supply=self.labour_supply, comp_market_share=init_market_share[0], cap_market_share=init_market_share[1], employment=self.employment, consumption=self.consumption, unemployment_rate=self.unemployment_rate, parameters=pformat(parameters) )) @property def investment(self): return self.expansion_investment + self.replacement_investment @property def max_capital_labour(self): return self.max_capital_labour_share * self.labour_supply @property def capital_labour_demand(self): capital_firms = self.get_group('capital_firm') return sum([a.labour_demand for a in capital_firms]) @property def labour_demand(self): return self.capital_labour_demand + self.consumption_labour_demand def get_group(self, group, include_bankrupt=False, bankrupt_only=False): if include_bankrupt and bankrupt_only: raise ValueError('include_bankrupt and bankrupt_only are mutually exclusive') firms = [ a for a in self.schedule.agents if a.group == group ] if include_bankrupt: return firms elif bankrupt_only: return [f for f in firms if f.bankrupt] return [f for f in firms if not f.bankrupt] def compute_average_price(self, group, weighted=False): firms = self.get_group(group) if not weighted: prices = [firm.price for firm in firms] return sum(prices) / len(prices) return sum([firm.market_share * firm.price for firm in firms]) def update_average_prices(self): cpi = self.compute_average_price('consumption_firm') self.delta_cpi = (cpi - self.cpi) / self.cpi self.cpi = cpi self.avg_cap_price = self.compute_average_price('capital_firm') def update_avg_ulc(self): # currently not used capital_firms = self.get_group('capital_firm') lpcs = [a.machine.compute_unit_labour_cost(self) for a in capital_firms] self.average_unit_labour_cost = sum(lpcs) / len(lpcs) def update_average_labour_productivity(self, weighted=True): firms = self.get_group('capital_firm') if weighted: avg_labour_prod = sum([ firm.market_share * firm.machine.labour_productivity_coefficient for firm in firms ]) else: avg_labour_prod = ( sum([f.machine.labour_productivity_coefficient for f in firms]) / len(firms) ) self.delta_productivity = ( (avg_labour_prod - self.avg_labour_prod) / self.avg_labour_prod ) self.avg_labour_prod = avg_labour_prod def compute_sector_competitiveness(self, group): firms = self.get_group(group) comp = sum([ f.competitiveness * f.market_share for f in firms ]) return round(comp, 2) def update_sector_competitiveness(self): self.avg_comp_competitiveness = self.compute_sector_competitiveness('consumption_firm') self.avg_cap_competitiveness = self.compute_sector_competitiveness('capital_firm') def aggregate_investment(self): firms = self.get_group('consumption_firm') self.expansion_investment = sum([ f.expansion_investment for f in firms ]) self.replacement_investment = sum([ f.replacement_investment for f in firms ]) def update_labour_supply(self): self.labour_supply = ( self.labour_supply * (1 + self.labour_supply_growth) ) def aggregate_labour_demand(self): self.consumption_labour_demand = sum([ a.labour_demand for a in self.get_group('consumption_firm') ]) def update_employment(self): self.employment = min(self.labour_demand, self.labour_supply) self.unemployment = max(0, self.labour_supply - self.employment) unemployment_rate = d(self.unemployment / self.labour_supply) delta_unemployment = unemployment_rate - self.unemployment_rate self.unemployment_rate = unemployment_rate self.delta_unemployment = delta_unemployment def update_market_wage(self): psi1 = self.wage_setting['cpi_weight'] psi2 = self.wage_setting['avg_lp_weight'] psi3 = self.wage_setting['unemployment_weight'] self.market_wage = ( self.market_wage + ( # NOTE: in the paper, this is + 1 + psi1 * self.delta_cpi + psi2 * self.delta_productivity + psi3 * self.delta_unemployment ) ) def compute_consumption(self): household_consumption = self.market_wage * self.employment policy_func = social_policy.get(self.social_policy) gov_consumption = policy_func(self) return household_consumption + gov_consumption def update_consumption(self): self.consumption = self.compute_consumption() def aggregate_production(self): firms = self.get_group('consumption_firm') self.agg_production = sum([ f.production for f in firms ]) def aggregate_inventories(self): firms = self.get_group('consumption_firm') self.agg_inventories = sum([ f.inventory for f in firms ]) def exit_and_entry(self): for group in self.groups: # TODO: move setting bankrupt to firms self.log.info(f'entry and exit for group {group}') firms = self.get_group(group) dead_firms = [ f for f in firms if f.market_share <= 0 or f.liquid_assets < 0 ] self.log.info(f'Bankrupt firms: {len(dead_firms)}') alive_firms = [ f for f in firms if f not in dead_firms ] for firm in dead_firms: firm.bankrupt = True copy_firm = self.random.choice(alive_firms) next_id = max([a.unique_id for a in self.schedule.agents]) + 1 assets = copy_firm.liquid_assets market_share = copy_firm.market_share capital_stock = ( copy_firm.capital_stock if group == 'consumption_firm' else None ) constructor = { 'consumption_firm': ConsumptionGoodFirm, 'capital_firm': CapitalGoodFirm }.get(group) new_firm = constructor( int(next_id), self, assets, market_share=market_share, capital_stock=capital_stock ) if group == 'capital_firm': new_firm.machine.labour_productivity_coefficient = ( copy_firm.machine.labour_productivity_coefficient ) new_firm.machine.price = copy_firm.machine.price self.schedule.add(new_firm) self._calibrate_market_share(group) def _calibrate_market_share(self, group): # TODO: adjust market share after re-entry firms = self.get_group(group) market_shares_sum = [f.market_share for f in firms] if market_shares_sum != 1: for firm in firms: firm.market_share = firm.market_share / sum(market_shares_sum) def _distribute_leftover_machines(self): pass def step(self): # run all stages of a step self.schedule.step() # collect data self.datacollector.collect(self)
class CapitalGoodFirm(Firm): # debt_stock = 0 # unit_production_cost = 1 demand = d(0) output = d(0) sales = d(0) labour_demand = d(0) profit = d(0) orders = d(0) def __init__(self, unique_id, model, liquid_assets, market_share, **kwargs): super().__init__(unique_id, model, liquid_assets, market_share) self.group = 'capital_firm' self.machine = Machine( producer=unique_id, generation=1, lpc=d(100), price=d(1) ) @property def price(self): return self.machine.price @property def available_financing(self): return self.residual_assets + self.available_debt def compute_max_quantity(self, unit_cost): return self.available_financing // unit_cost @property def max_production(self): max_labour = self.market_share * self.model.max_capital_labour max_quantity = self.compute_max_quantity(self.unit_production_cost) return min( max_quantity, max_labour * self.machine.labour_productivity_coefficient ) def compute_unit_production_cost(self): return ( self.model.market_wage / self.machine.labour_productivity_coefficient ) def compute_competitiveness(self): w3, w4 = self.model.competitiveness_weights[1] return ( -w3 * self.machine.price + w4 * self.machine.labour_productivity_coefficient ) def compute_debt_availability(self): max_debt = self.model.max_debt_sales_ratio * self.sales return max(0, max_debt - self.debt_stock) def compute_demand(self): # consumption_firms = self.model.get_group('consumption_firm') # return sum([ # a.investment for a in consumption_firms # if a.supplier == self.unique_id # ]) return self.orders def fix_production(self): production = min(self.demand, self.max_production) ce, de = self.finance_operations(production, self.unit_production_cost) self.debt_stock += de return production def compute_labour_demand(self): return ( self.output / self.machine.labour_productivity_coefficient ) def compute_profit(self): return ( (self.price - self.unit_production_cost) * self.sales - self.model.interest_rate * self.debt_stock ) def compute_liquid_assets(self): return self.liquid_assets + self.profit def compute_market_share(self): investment = self.model.investment market_share = ( d(self.demand / investment) if investment > 0 else self.market_share ) return market_share def innovate(self): if not self.model.innovation: return None epsilon = d(self.random.uniform(*self.model.distribution_bounds)) new_lpc = ( self.machine.labour_productivity_coefficient * (1 + epsilon) ) if new_lpc > self.machine.labour_productivity_coefficient: self.machine = Machine( producer=self.unique_id, generation=self.machine.generation + 1, lpc=new_lpc, price=self.machine.price ) # stage methods def stage_one(self): if self.bankrupt: return None self.residual_assets = self.liquid_assets self.available_debt = self.compute_debt_availability() self.unit_production_cost = self.compute_unit_production_cost() self.machine.price = self.fix_price() self.competitiveness = self.compute_competitiveness() def stage_two(self): if self.bankrupt: return None def stage_three(self): if self.bankrupt: return None self.demand = self.compute_demand() self.output = self.sales = self.fix_production() self.labour_demand = self.compute_labour_demand() self.profit = self.compute_profit() self.liquid_assets = self.compute_liquid_assets() self.market_share = self.compute_market_share() def stage_four(self): if self.bankrupt: return None def stage_five(self): row = { 'step': self.model.schedule.steps, 'agent_id': self.unique_id, 'competitiveness': self.competitiveness, 'demand': self.demand, 'production': self.output, 'labour_demand': self.labour_demand, 'output': self.output, 'sales': self.sales, 'profit': self.profit, 'liquid_assets': self.liquid_assets, 'debt_stock': self.debt_stock, 'market_share': self.market_share, 'machine_generation': self.machine.generation, 'price': self.price, 'upc': self.unit_production_cost, 'labour_productivity': self.machine.labour_productivity_coefficient, 'available_debt': self.available_debt, 'bankrupt': self.bankrupt } self.model.datacollector.add_table_row('capital_firm', row) if self.bankrupt: return None self.innovate() self.orders = 0
def __init__(self, parameters, market_wage, cpi, avg_labour_productivity, liquid_assets, capital_stock, labour_supply, innovation, seed=None): self.schedule = GroupedActivation( self, self.groups, self.stages, interim_functions=self.stage_functions ) self.datacollector = DataCollector( model_reporters={ 'market_wage': 'market_wage', 'consumption': 'consumption', 'expansion_investment': 'expansion_investment', 'replacement_investment': 'replacement_investment', 'investment': 'investment', 'inventories': 'agg_inventories', 'production': 'agg_production', 'cpi': 'cpi', 'avg_cap_price': 'avg_cap_price', 'avg_labour_prod': 'avg_labour_prod', 'labour_supply': 'labour_supply', 'labour_demand': 'labour_demand', 'employment': 'employment', 'unemployment': 'unemployment', 'avg_comp_competitiveness': 'avg_comp_competitiveness', 'avg_cap_competitiveness': 'avg_cap_competitiveness', 'gdp': compute_gdp }, tables={ 'consumption_firm': { 'step': [], 'agent_id': [], 'competitiveness': [], 'expected_demand': [], 'market_share': [], 'demand': [], 'desired_production': [], 'desired_capital_stock': [], 'labour_demand': [], 'desired_ei': [], 'desired_ri': [], 'supplier': [], 'expansion_investment': [], 'replacement_investment': [], 'production': [], 'output': [], 'inventory': [], 'sales': [], 'profit': [], 'liquid_assets': [], 'capital_stock': [], 'debt_stock': [], 'price': [], 'upc': [], 'average_productivity': [], 'available_debt': [], 'bankrupt': [] }, 'capital_firm': { 'step': [], 'agent_id': [], 'competitiveness': [], 'demand': [], 'production': [], 'labour_demand': [], 'output': [], 'sales': [], 'profit': [], 'liquid_assets': [], 'debt_stock': [], 'market_share': [], 'machine_generation': [], 'price': [], 'upc': [], 'labour_productivity': [], 'available_debt': [], 'bankrupt': [] } } ) self.innovation = innovation self.social_policy = parameters['social_policy'] self.inventory_deprecation = parameters['inventory_deprecation'] # parameters # TODO: convert parameters to decimal n_consumption_firms = parameters['n_consumption_firms'] n_capital_firms = parameters['n_capital_firms'] replicators = parameters['replicator_dynamics_coeff'] self.replicator_dynamics_coeff = (d(replicators[0]), d(replicators[1])) comp_weights = parameters['competitiveness_weights'] self.competitiveness_weights = ( (d(comp_weights[0][0]), d(comp_weights[0][1])), (d(comp_weights[1][0]), d(comp_weights[1][1])) ) self.distribution_bounds = parameters['distribution_bounds'] self.labour_supply_growth = d(parameters['labour_supply_growth']) self.wage_setting = { k: d(v) for k, v in parameters['wage_setting'].items() } self.desired_capital_utilization = d(parameters['desired_capital_utilization']) self.trigger_rule = d(parameters['trigger_rule']) self.payback_period_parameter = d(parameters['payback_period_parameter']) self.mark_up = d(parameters['mark_up']) self.interest_rate = d(parameters['interest_rate']) self.wage_share = d(parameters['wage_share']) self.betas = [d(b) for b in parameters['betas']] # NOTE: max_debt_sales_ratio is not specified in the paper self.max_debt_sales_ratio = d(parameters['max_debt_sales_ratio']) # initial conditions self.market_wage = d(market_wage) self.cpi = d(cpi) self.avg_labour_prod = d(avg_labour_productivity) self.labour_supply = labour_supply # computed and derived self.max_capital_labour_share = d( n_capital_firms / (n_consumption_firms + n_capital_firms) ) init_market_share = (d(1 / n_consumption_firms), d(1 / n_capital_firms)) self.employment = labour_supply self.consumption = self.compute_consumption() self.unemployment_rate = d(self.unemployment / self.employment) # create capital good firms for i in range(n_capital_firms): f = CapitalGoodFirm(i, self, liquid_assets, init_market_share[1]) self.schedule.add(f) # create consumption good firms supplier = 0 for i in range(n_consumption_firms): if parameters['fix_supplier']: if i % 4 == 0 and i != 0: supplier += 1 else: supplier = None f = ConsumptionGoodFirm( i + n_capital_firms, self, liquid_assets, capital_stock, init_market_share[0], supplier=supplier ) self.schedule.add(f) self.running = True self.datacollector.collect(self) self.log.info(messages.model_init_message.format( wage=self.market_wage, cpi=self.cpi, avg_labour_prod=self.avg_labour_prod, labour_supply=self.labour_supply, comp_market_share=init_market_share[0], cap_market_share=init_market_share[1], employment=self.employment, consumption=self.consumption, unemployment_rate=self.unemployment_rate, parameters=pformat(parameters) ))
def compute_max_quantity(self, unit_cost): return d(int(self.available_financing / unit_cost))
def forecast_capital_stock(self): return d(int( self.desired_production / self.model.desired_capital_utilization ))