diff --git a/db/production.yml b/db/production.yml index 0cdc330..ad10190 100644 --- a/db/production.yml +++ b/db/production.yml @@ -1,7 +1,7 @@ kind: production spec: - name: Food - amount: 1 + amount: 100 prod: - "Grain": 1 - "Fruit": 1 diff --git a/econ/__pycache__/exchange.cpython-38.pyc b/econ/__pycache__/exchange.cpython-38.pyc index e5aed51..31b3e60 100644 Binary files a/econ/__pycache__/exchange.cpython-38.pyc and b/econ/__pycache__/exchange.cpython-38.pyc differ diff --git a/econ/__pycache__/simulation.cpython-38.pyc b/econ/__pycache__/simulation.cpython-38.pyc index 6345a1f..220c4a6 100644 Binary files a/econ/__pycache__/simulation.cpython-38.pyc and b/econ/__pycache__/simulation.cpython-38.pyc differ diff --git a/econ/agents/__pycache__/autoproduction.cpython-38.pyc b/econ/agents/__pycache__/autoproduction.cpython-38.pyc index 315daaf..944cf38 100644 Binary files a/econ/agents/__pycache__/autoproduction.cpython-38.pyc and b/econ/agents/__pycache__/autoproduction.cpython-38.pyc differ diff --git a/econ/agents/__pycache__/base_agent.cpython-38.pyc b/econ/agents/__pycache__/base_agent.cpython-38.pyc index 5e1658a..56c0c05 100644 Binary files a/econ/agents/__pycache__/base_agent.cpython-38.pyc and b/econ/agents/__pycache__/base_agent.cpython-38.pyc differ diff --git a/econ/agents/__pycache__/base_aquire_agent.cpython-38.pyc b/econ/agents/__pycache__/base_aquire_agent.cpython-38.pyc index ae4ad1d..877e7cf 100644 Binary files a/econ/agents/__pycache__/base_aquire_agent.cpython-38.pyc and b/econ/agents/__pycache__/base_aquire_agent.cpython-38.pyc differ diff --git a/econ/agents/__pycache__/price_believe_aquire.cpython-38.pyc b/econ/agents/__pycache__/price_believe_aquire.cpython-38.pyc index 68bf2cf..59d47da 100644 Binary files a/econ/agents/__pycache__/price_believe_aquire.cpython-38.pyc and b/econ/agents/__pycache__/price_believe_aquire.cpython-38.pyc differ diff --git a/econ/agents/__pycache__/price_believe_distribute.cpython-38.pyc b/econ/agents/__pycache__/price_believe_distribute.cpython-38.pyc index 7dc00d9..c4033b7 100644 Binary files a/econ/agents/__pycache__/price_believe_distribute.cpython-38.pyc and b/econ/agents/__pycache__/price_believe_distribute.cpython-38.pyc differ diff --git a/econ/agents/autoproduction.py b/econ/agents/autoproduction.py index 0951469..49f3f90 100644 --- a/econ/agents/autoproduction.py +++ b/econ/agents/autoproduction.py @@ -12,29 +12,33 @@ class AutoProductionAgent(BaseAgent): self.worker=worker self.employment_rate=employment_rate self.employment_index=worker + self.target=0 def set_worker(self,workers): if workers>1: self.worker=workers else: self.worker=1 - + def set_target(self,qty): + self.target=qty def can_produce(self): # If can produce item - for k,cost in self.prod["craft"].items(): - if cost > self.business.inventory[k]: - return False + for com in self.prod["prod"]: + for k,v in com.items(): + if v > self.business.inventory[k]: + return False + # check if we should produce + if self.business.resource_in_possesion()>self.target: + return False return True def tick(self,step,epi): for i in range(self.worker): if not self.can_produce(): - self.employment_index-=self.employment_rate continue - self.employment_index+=self.employment_rate - self.set_worker(int(self.employment_index)) # remove cost from inventory - for k,cost in self.prod["craft"].items(): - self.business.inventory[k]-=cost + for com in self.prod["prod"]: + for k,cost in com.items(): + self.business.inventory[k]-=cost # add commodity self.business.inventory[self.prod['name']]+=self.prod["amount"] diff --git a/econ/agents/base_agent.py b/econ/agents/base_agent.py index 117c352..b0f4c3b 100644 --- a/econ/agents/base_agent.py +++ b/econ/agents/base_agent.py @@ -18,7 +18,7 @@ class BaseAgent(ABC): assert "No Tick method has been provided" pass - def reset(self): + def reset(self,episode): """ Resets agent to new episode. """ diff --git a/econ/agents/base_aquire_agent.py b/econ/agents/base_aquire_agent.py index 47ce169..113c4c0 100644 --- a/econ/agents/base_aquire_agent.py +++ b/econ/agents/base_aquire_agent.py @@ -92,8 +92,9 @@ class Base_Aquire_Agent(BaseAgent,ABC): cx=self.exchanges[cx_id] orders=self.orders[cx_id] if len(orders)>0: - for o in orders: - trades.append(cx.order_trades_map[o.order_id]) + for k,o in orders.items(): + if k in cx.order_trades_map: + trades.append(cx.order_trades_map[k]) self.trades=trades return trades diff --git a/econ/agents/price_believe_aquire.py b/econ/agents/price_believe_aquire.py index 7411e9f..64532a0 100644 --- a/econ/agents/price_believe_aquire.py +++ b/econ/agents/price_believe_aquire.py @@ -1,5 +1,5 @@ from .base_aquire_agent import Base_Aquire_Agent - +import random class Price_Believe_Aquire_Agent(Base_Aquire_Agent): """ Aquire agent with internal price believe system. @@ -8,14 +8,13 @@ class Price_Believe_Aquire_Agent(Base_Aquire_Agent): super().__init__(simulation, business, resource, exchanges) self.lr=lr self.max_price_adj_rate=max_price_adj_rate - self.price_believe={i: -1 for i in range(len(self.exchanges))} + self.price_believe={i: 0 for i in range(len(self.exchanges))} self.open_orders={i: [] for i in range(len(self.exchanges))} self.open_qty=0 def tick(self,tick,episode): - if self.price_believe==-1: - self.price_believe=self.max_price + order_error=self.open_qty+self.target_error() if order_error<0: @@ -23,7 +22,7 @@ class Price_Believe_Aquire_Agent(Base_Aquire_Agent): cx_id=self.select_best_cx() order=self.order_resource(self.price_believe[cx_id],order_error*-1,cx_id) if not order==None: - self.register_order(0,order) + self.register_order(cx_id,order) else: # order failed due to missing balance.. we need to adjust our price believe self.collect_balance_from_cxs() @@ -32,14 +31,16 @@ class Price_Believe_Aquire_Agent(Base_Aquire_Agent): self.tick_open_orders() def select_best_cx(self): - best_id=0 + best_id=-1 best=0 for cx_id in range(len(self.exchanges)): cx=self.exchanges[cx_id] - potential=cx.total_supply[self.resource]*self.price_believe[cx_id] + potential=cx.get_total_supply(self.resource)*self.price_believe[cx_id] if potential0: # aquire based on current price belive cx_id=self.select_best_cx() - order=self.distribute_resource(self.price_believe,order_error,cx_id) - self.register_order(0,order) + order=self.distribute_resource(self.price_believe[cx_id],order_error,cx_id) + self.register_order(cx_id,order) self.tick_open_orders() def select_best_cx(self): - best_id=0 + best_id=-1 best=0 for cx_id in range(len(self.exchanges)): cx=self.exchanges[cx_id] - potential=cx.total_supply[self.resource]*self.price_believe[cx_id] + potential=cx.get_total_supply(self.resource)*self.price_believe[cx_id] if potential>best: best=potential best_id=cx_id + if best_id==-1: + best_id=random.randint(0,len(self.exchanges)-1) return best_id def register_order(self,cx_id,order): @@ -56,7 +56,7 @@ class Price_Believe_Distribiute_Agent(Base_Distribution_Agent): if o.leaves_qty==0: #order is done self.open_orders[cx_id].remove(i) # remove order from open - self.update_believe(1) # update price believe + self.update_believe(cx_id,1) # update price believe self.collect_balance_from_cxs() self.collect_resource_from_cxs(self.resource) continue @@ -68,7 +68,7 @@ class Price_Believe_Distribiute_Agent(Base_Distribution_Agent): cx.cancel_order(i["id"]) self.collect_balance_from_cxs() self.collect_resource_from_cxs(self.resource) - self.update_believe(-1) + self.update_believe(cx_id,-1) self.open_orders[cx_id].remove(i) @@ -83,8 +83,10 @@ class Price_Believe_Distribiute_Agent(Base_Distribution_Agent): If negative will sub lr to believe """ self.price_believe[cx_id]+=modifier*self.lr + if self.price_believe[cx_id]<0: + self.price_believe[cx_id]=0 - def reset(self): + def reset(self,episode): # Clean shop for today for cx_id in range(len(self.exchanges)): cx=self.exchanges[cx_id] @@ -96,4 +98,4 @@ class Price_Believe_Distribiute_Agent(Base_Distribution_Agent): # book keeping self.update_trades() - return super().reset() \ No newline at end of file + return super().reset(episode) \ No newline at end of file diff --git a/econ/business/Price_Believe_Business.py b/econ/business/Price_Believe_Business.py index 0f649fc..844f56b 100644 --- a/econ/business/Price_Believe_Business.py +++ b/econ/business/Price_Believe_Business.py @@ -6,20 +6,26 @@ class Price_Believe_Business(Business): def __init__(self, id, production, balance,exchange,simulation) -> None: super().__init__(id, production, balance) - self.distribute=Price_Believe_Distribiute_Agent(simulation,self,production["name"],exchange,0.1,50) + self.distribute=Price_Believe_Distribiute_Agent(simulation,self,production["name"],exchange,0.5,10) self.craft=AutoProductionAgent(simulation,self) self.aquire={} - for k,v in production["craft"].items(): - a=Price_Believe_Aquire_Agent(simulation,self,k,exchange,1,50) - a.set_target(v*2) - a.set_price_max(10) - self.aquire[k]=a + for l in production["prod"]: + for k,v in l.items(): + a=Price_Believe_Aquire_Agent(simulation,self,k,exchange,1,10) + a.set_target(v*2) + a.set_price_max(10) + self.aquire[k]=a self.distribute.set_price_min(10) self.distribute.set_target(0) - def step_business_decisions(self): - for k,v in self.production["craft"].items(): + def tick_business_decisions(self,step): + for comp in self.production["prod"]: + for k,v in comp.items(): modifier=self.craft.worker+1 self.aquire[k].set_target(v*modifier) + max_amount=self.production["amount"]*5 + self.craft.set_target(max_amount) + def resource_in_possesion(self): + return self.distribute.open_qty+self.inventory[self.production["name"]] diff --git a/econ/business/__pycache__/Price_Believe_Business.cpython-38.pyc b/econ/business/__pycache__/Price_Believe_Business.cpython-38.pyc index f64966f..a70e8ae 100644 Binary files a/econ/business/__pycache__/Price_Believe_Business.cpython-38.pyc and b/econ/business/__pycache__/Price_Believe_Business.cpython-38.pyc differ diff --git a/econ/business/__pycache__/base_business.cpython-38.pyc b/econ/business/__pycache__/base_business.cpython-38.pyc index d099202..dd9ea43 100644 Binary files a/econ/business/__pycache__/base_business.cpython-38.pyc and b/econ/business/__pycache__/base_business.cpython-38.pyc differ diff --git a/econ/business/base_business.py b/econ/business/base_business.py index d7f2e9c..8b4ff5d 100644 --- a/econ/business/base_business.py +++ b/econ/business/base_business.py @@ -13,14 +13,33 @@ class Business(ABC): """ self.id=id self.production=production + self.balance_history=[balance] self.balance=balance #Setup Inventory self.inventory={production["name"]: 0} - for k,v in production["craft"].items(): - self.inventory[k]=0 + for l in production["prod"]: + for k,v in l.items(): + self.inventory[k]=0 pass - - def step_business_decisions(self): + + def tick(self,step): + self.tick_business_decisions(step) + + def tick_business_decisions(self,step): assert "no business decision method has been created" pass - \ No newline at end of file + + def tick_episode(self,episode_count): + """ + Call for resetting for the next episode + """ + self.balance_history.append(self.balance) + self.tick_business_episode(episode_count) + + + def tick_business_episode(self,episode_count): + """ + Call for resetting for the next episode + """ + assert "no business episode tick method has been created" + diff --git a/econ/cells/__pycache__/cell.cpython-38.pyc b/econ/cells/__pycache__/cell.cpython-38.pyc index 7fd1dfe..12efeb2 100644 Binary files a/econ/cells/__pycache__/cell.cpython-38.pyc and b/econ/cells/__pycache__/cell.cpython-38.pyc differ diff --git a/econ/cells/cell.py b/econ/cells/cell.py index 77b56e8..1a7d61c 100644 --- a/econ/cells/cell.py +++ b/econ/cells/cell.py @@ -1,5 +1,6 @@ from ..exchange import Exchange from ..commoditys import commoditys as cm +from lightmatchingengine.lightmatchingengine import Side @@ -23,11 +24,12 @@ class Cell: exchange: None def __init__(self,name,x,y,area,pop,demand,world) -> None: + pop=pop*1000 self.name=name self.loc_x=x self.loc_y=y self.area=area - self.pop=pop*1000 + self.pop=pop self.demand_tags=demand self.demand={} self.world_tags=world @@ -55,6 +57,21 @@ class Cell: else: self.world[k]+=v*area + def setup_demand_for_step(self,episode_length): + pop_per_step=self.pop/episode_length + for demand_key,value in self.demand.items(): + qty=value/episode_length + comm=cm.commoditys[demand_key] + max_price=comm["max_world_price"]*qty + self.exchange.add_to_account(self.name,"balance",max_price) + self.exchange.submit_order(self.name,demand_key,qty,comm["max_world_price"],Side.BUY) + def setup_supply_for_episode(self): + self.exchange.reset() + for supply_key,value in self.world.items(): + self.exchange.add_to_account(self.name,supply_key,value) + self.exchange.submit_order(self.name,supply_key,value,0,Side.SELL) + + def create_cells_from_world_cells(cells) -> list: """ diff --git a/econ/commoditys/__pycache__/commoditys.cpython-38.pyc b/econ/commoditys/__pycache__/commoditys.cpython-38.pyc index f3b3e42..8190b48 100644 Binary files a/econ/commoditys/__pycache__/commoditys.cpython-38.pyc and b/econ/commoditys/__pycache__/commoditys.cpython-38.pyc differ diff --git a/econ/commoditys/commoditys.py b/econ/commoditys/commoditys.py index 9ecbb83..a0cdfe7 100644 --- a/econ/commoditys/commoditys.py +++ b/econ/commoditys/commoditys.py @@ -1,12 +1,13 @@ import os import yaml - +import uuid # create an empty dictionary to store the demand tags demand_tags = {} # demand tags with demands world_tags = {} # available resources tags with resources available commoditys = {} # commoditys by name productions = {} # list of production rules by commodity name +productions_id_map={} productions_uses = {} # list of productions that are a key commodity data={} def search_yaml_files(directory = "db"): @@ -34,9 +35,11 @@ def search_yaml_files(directory = "db"): commoditys[comm['name']] = comm elif data['kind'] == "production": for comm in data['spec']: + comm["id"]=uuid.uuid4() if comm['name'] not in productions: productions[comm['name']]=[] productions[comm['name']].append(comm) + productions_id_map[comm["id"]]=comm for comp in comm["prod"]: k=list(comp.keys())[0] if k not in productions_uses: diff --git a/econ/exchange.py b/econ/exchange.py index 0f0be67..fe31de2 100644 --- a/econ/exchange.py +++ b/econ/exchange.py @@ -7,6 +7,10 @@ class Exchange(): def __init__(self) -> None: + self.reset() + pass + + def reset(self): self.account={} self.escrow={} self.lme=LightMatchingEngine() @@ -21,8 +25,7 @@ class Exchange(): self.demand={} self.total_supply={} self.supply={} - pass - + def add_to_account(self,account_id,resource,amount): """ Adds resources to account escrow @@ -237,4 +240,13 @@ class Exchange(): if account_id in self.account: if resource in self.account[account_id]: return self.account[account_id][resource] + return 0 + + def get_total_demand(self,resource): + if resource in self.total_demand: + return self.total_demand[resource] + return 0 + def get_total_supply(self,resource): + if resource in self.total_supply: + return self.total_supply[resource] return 0 \ No newline at end of file diff --git a/econ/simulation.py b/econ/simulation.py index 895d9c4..2a421e5 100644 --- a/econ/simulation.py +++ b/econ/simulation.py @@ -1,4 +1,8 @@ import random +from .commoditys import commoditys as cm +from .exchange import Exchange +from .business import Price_Believe_Business +import uuid class Simulation(): """ Class for controlling the different calculation steps of the Simulation. @@ -8,8 +12,22 @@ class Simulation(): self.reset_funcs={} self.tick_count=0 self.episode_count=0 + self.cells={} + self.businesses=[] + self.production_util={} + for k in cm.productions.items(): + l=k[1] + for i in l: + self.production_util[i["id"]]=0 + self.cx=Exchange() pass + def set_cells(self,cells): + for c in cells: + self.cells[c.name]=c + + def set_businesses(self,bus): + self.businesses=bus def seed(self,a): """ Sets the random seed @@ -19,8 +37,8 @@ class Simulation(): self.tick_funcs[id]=tickfunc self.reset_funcs[id]=resetfunc - - def tick_random_order(self): + + def tick_agents_random_order(self): """ Ticks all agents in a Random Order """ @@ -29,6 +47,18 @@ class Simulation(): for k in keys: fun=self.tick_funcs[k] fun(self.tick_count,self.episode_count) + + + def tick(self,episode_length): + """ + Executes a tick of the simulation + """ + self.tick_agents_random_order() + for cell_id,c in self.cells.items(): + c.setup_demand_for_step(episode_length) + for b in self.businesses: + b.tick(self.tick_count) + self.tick_count+=1 def reset(self): @@ -37,5 +67,83 @@ class Simulation(): """ for k,v in self.reset_funcs.items(): v(self.episode_count) + for cell_id,cell in self.cells.items(): + cell.setup_supply_for_episode() self.tick_count=0 - self.episode_count+=1 \ No newline at end of file + self.episode_count+=1 + + def get_underutelized_prods(self,threshold): + """ + Returns all productions that fall under the given threshold + """ + under=[] + for k,v in self.production_util.items(): + if v0: + # we need to create more businesses + selected_prod=random.choice(under_prods) + else: + selected_prod=self.get_max_profit_prod() + + + prod=cm.productions_id_map[selected_prod] + # Now select cell to place business + cells=self.select_business_cell(prod) + cell_id=random.choice(cells) + cell=self.cells[cell_id] + cxs=[cell.exchange,self.cx] + business=Price_Believe_Business.Price_Believe_Business(uuid.uuid4(),prod,1000,cxs,self) + self.businesses.append(business) + self.production_util[selected_prod]+=1 + return business + + def get_max_profit_prod(self): + """ + Returns the prod with the highest profit in last episode + """ + profit=self.businesses[0].balance-self.businesses[0].balance_history[-1] + prod=self.businesses[0].production + + for b in self.businesses: + diff=b.balance-b.balance_history[-1] + if diff>profit: + profit=diff + prod=b.production + return prod + + + + def select_business_cell(self,prod): + """ + Calculate score for placement. Return cell with best score. + """ + cell_score={self.cells[k].name: 0 for k in self.cells} + + + for id,cell in self.cells.items(): + cx=cell.exchange + # Best prod resource availability + # Using the exchange in that cell, lookup supply or demand of resources. Supply +1 / Demand -1 + for prod_item in prod["prod"]: + key=list(prod_item.keys())[0] + val=prod_item[key] + a=cx.get_total_supply(key) + cell_score[cell.name]+=(cx.get_total_supply(key)/val) + cell_score[cell.name]-=(cx.get_total_demand(key)/val) + # If demand for prod item in cell then score +1/ score -1 if supply is already there + a=cx.get_total_demand(prod["name"]) + cell_score[cell.name]+=cx.get_total_demand(prod["name"]) + cell_score[cell.name]-=cx.get_total_supply(prod["name"]) + max_keys = [key for key, value in cell_score.items() if value == max(cell_score.values())] + return max_keys \ No newline at end of file diff --git a/main.py b/main.py index a5b26f2..d8a7982 100644 --- a/main.py +++ b/main.py @@ -28,14 +28,13 @@ cx.submit_order(w,"Wood",0,1000,Side.BUY) cx.submit_order(2,"Gem",1,10,Side.SELL) sim=Simulation() -bus=Price_Believe_Business(1,{ - 'name': 'Wood', - 'amount': 1, - 'craft': { - 'Raw_Wood': 1, - } - },50,cxs,sim) - -for i in range(100): - sim.tick_random_order() +sim.set_cells(cells) +sim.reset() +sim.tick(1) +# create info +while len(sim.get_underutelized_prods(1))>0: + sim.create_bussiness() +sim.reset() +for i in range(1000): + sim.tick(1000) print("help") \ No newline at end of file