267 lines
8.3 KiB
Python
267 lines
8.3 KiB
Python
import random
|
|
from .commoditys import commoditys as cm
|
|
from .exchange import Exchange
|
|
from .business import Price_Believe_Business
|
|
import uuid
|
|
import time
|
|
import logging
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
import log
|
|
class Simulation():
|
|
"""
|
|
Class for controlling the different calculation steps of the Simulation.
|
|
"""
|
|
def __init__(self) -> None:
|
|
self.timings={}
|
|
self.tick_funcs={}
|
|
self.reset_funcs={}
|
|
self.tick_count=0
|
|
self.episode_count=-1
|
|
self.cells={}
|
|
self.populated_cells={}
|
|
self.businesses=[]
|
|
self.production_util={}
|
|
self.taskpool=ThreadPoolExecutor()
|
|
|
|
self.setup_timing(["dss","dsa","dab","dbl","dt"])
|
|
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
|
|
self.populated_cells[c.name]=0
|
|
|
|
def set_businesses(self,bus):
|
|
self.businesses=bus
|
|
def seed(self,a):
|
|
"""
|
|
Sets the random seed
|
|
"""
|
|
random.seed(a)
|
|
def register_agent(self,id,tickfunc,resetfunc):
|
|
|
|
self.tick_funcs[id]=tickfunc
|
|
self.reset_funcs[id]=resetfunc
|
|
|
|
def unregister_agent(self,id):
|
|
"""
|
|
Unregisters agent
|
|
"""
|
|
self.tick_funcs.pop(id,None)
|
|
self.reset_funcs.pop(id,None)
|
|
|
|
|
|
def tick_agents_random_order(self):
|
|
"""
|
|
Ticks all agents in a Random Order
|
|
"""
|
|
keys=list(self.tick_funcs.keys())
|
|
random.shuffle(keys)
|
|
tasks=[]
|
|
for k in keys:
|
|
fun=self.tick_funcs[k]
|
|
fun(self.tick_count,self.episode_count)
|
|
self.taskpool.map(self._execute_tick,tasks)
|
|
|
|
def _execute_tick(args):
|
|
fun=args[0]
|
|
fun(args[1],args[2])
|
|
|
|
def tick(self,episode_length):
|
|
"""
|
|
Executes a tick of the simulation
|
|
"""
|
|
start=time.time()
|
|
|
|
for cell_id,c in self.cells.items():
|
|
c.setup_demand_for_step(episode_length)
|
|
setup=time.time()
|
|
self.tick_agents_random_order()
|
|
agentstep=time.time()
|
|
for b in self.businesses:
|
|
b.tick(self.tick_count)
|
|
|
|
busstep=time.time()
|
|
self.tick_count+=1
|
|
self.log_cxs_tick(episode_length)
|
|
#self.log_business_tick(episode_length)
|
|
logstep=time.time()
|
|
self.timings["dss"]+=setup-start
|
|
self.timings["dsa"]+=agentstep-setup
|
|
self.timings["dsap"]=self.timings["dsa"]/(len(self.tick_funcs)+1)
|
|
self.timings["dab"]+=(busstep-agentstep)/(len(self.businesses)+1)
|
|
self.timings["dbl"]+=logstep-busstep
|
|
self.timings["dt"]+=logstep-start
|
|
|
|
|
|
|
|
|
|
def reset(self):
|
|
"""
|
|
Resets all agents to new Episode
|
|
"""
|
|
self.log_cxs_episode()
|
|
self.log_business_episode()
|
|
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()
|
|
toclose=[]
|
|
for b in self.businesses:
|
|
b.tick_episode(self.episode_count)
|
|
if b.balance<=0:
|
|
toclose.append(b.id)
|
|
for close in toclose:
|
|
self.remove_business(close)
|
|
self.timings["numBus"]=len(self.businesses)
|
|
self.timings["tickfuncs"]=len(self.tick_funcs)
|
|
self.timings["episode"]=self.episode_count
|
|
log.PerformanceData.append(self.timings.copy())
|
|
self.setup_timing(["dss","dsa","dsap","dab","dbl","dt"])
|
|
self.tick_count=0
|
|
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 v<threshold:
|
|
under.append(k)
|
|
return under
|
|
|
|
|
|
def create_bussiness(self,min_available_per_prod):
|
|
"""
|
|
Create a new business based on given econemy state.
|
|
Return Business
|
|
"""
|
|
selected_prod=None
|
|
# Create a business for every production rule out there
|
|
under_prods=self.get_underutelized_prods(min_available_per_prod)
|
|
if len(under_prods)>0:
|
|
# 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.UUID(int=random.getrandbits(128)),prod,1000,cxs,self,cell_id)
|
|
self.businesses.append(business)
|
|
self.production_util[selected_prod]+=1
|
|
self.populated_cells[cell_id]+=1
|
|
return business
|
|
|
|
def remove_business(self,id):
|
|
bus=None
|
|
for busl in self.businesses:
|
|
if busl.id==id:
|
|
bus=busl
|
|
break
|
|
|
|
bus.close_business()
|
|
self.businesses.remove(bus)
|
|
self.production_util[bus.production["id"]]-=1
|
|
self.populated_cells[bus.location]-=1
|
|
|
|
|
|
def get_max_profit_prod(self):
|
|
"""
|
|
Returns the prod with the highest profit in last episode
|
|
"""
|
|
profit={}
|
|
|
|
for b in self.businesses:
|
|
id=b.production["id"]
|
|
diff=b.balance-b.balance_history[-2]
|
|
if id not in profit:
|
|
profit[id]=0
|
|
profit[id]+=diff
|
|
best=None
|
|
bestv=-1000000000000
|
|
for k,v in profit.items():
|
|
if bestv<v:
|
|
bestv=v
|
|
best=k
|
|
return best
|
|
|
|
|
|
|
|
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 key,val in prod["prod"].items():
|
|
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"])
|
|
b=cx.get_total_supply(prod["name"])
|
|
cell_score[cell.name]+=a
|
|
cell_score[cell.name]-=b
|
|
max_keys = [key for key, value in cell_score.items() if value == max(cell_score.values())]
|
|
return max_keys
|
|
|
|
def log_cxs_tick(self,epi_length):
|
|
items=[]
|
|
for id,_ in self.populated_cells.items():
|
|
cell=self.cells[id]
|
|
lcx=cell.exchange
|
|
name="lcx_{}".format(id)
|
|
items.append([lcx,name,self.episode_count,epi_length,self.tick_count])
|
|
results=map(self.launch_cxs_log,items)
|
|
for result in results:
|
|
for id,item in result.items():
|
|
log.EXBooksData.append(item)
|
|
self.cx.log_step("cx",self.episode_count,epi_length,self.tick_count)
|
|
|
|
def launch_cxs_log(self,args):
|
|
"""
|
|
Runs in a conncurrent map
|
|
"""
|
|
cx=args[0]
|
|
name=args[1]
|
|
epi=args[2]
|
|
epilen=args[3]
|
|
tick=args[4]
|
|
return cx.log_step(name,epi,epilen,tick,False)
|
|
|
|
def log_cxs_episode(self):
|
|
for id,v in self.populated_cells.items():
|
|
if v>0:
|
|
cell=self.cells[id]
|
|
lcx=cell.exchange
|
|
name="lcx_{}".format(id)
|
|
lcx.log_episode(name,self.episode_count)
|
|
self.cx.log_episode("cx",self.episode_count)
|
|
|
|
def log_business_tick(self,episode_length):
|
|
for bus in self.businesses:
|
|
bus.log(self.episode_count,episode_length,self.tick_count)
|
|
def log_business_episode(self):
|
|
for bus in self.businesses:
|
|
bus.log(self.episode_count,1,0)
|
|
|
|
def setup_timing(self,metrics):
|
|
for met in metrics:
|
|
self.timings[met]=0 |