本周说明及总结:
本周我继续学习了Flexsim的相关基础操作,同时,我本周主要探索了Flexsim与MySQL的互通,以及Flexsim在强化学习和调度策略优化上的内容。
主要目的是探索一下Flexsim在编程方面的自由度以及可以与我们目前的系统交互的程度。
目前明确的信息有:
- Flexsim支持与MySQL的互通,支持SQL语句,内部可以进行查询和过滤,具备一定的实时通信能力,但是可靠性、通信效率还需要进一步验证
- Flexsim支持编程式的任务调度,灵活性尚可。
- Flexsim 2019支持深度学习和强化学习,目前2019没有找到非试用版的资源,所以这个功能目前无法体验,但是根据其他博文和官网的说明,flexsim强化学习和部分数据分析的实现方式是python,可以通过自训练模型,自行编程的方式进行优化和定制,示例见正文。
目前的学习和研究方向:
- Flexsim可以实现的调度算法和策略优化,以及其对python的支持
- Flexsim与MySQL通信的可靠性、查询的效率
- 进一步对C++相关语言和flexsim的任务调度模块进行熟悉,争取下周实现一个模块。
Flexsim与Python连接样例:
import gym import os import subprocess import socket import json from gym import error, spaces, utils from gym.utils import seeding import numpy as np class FlexSimEnv(gym.Env): metadata = {'render.modes': ['human', 'rgb_array', 'ansi']} def __init__(self, flexsimPath, modelPath, address='localhost', port=5005, verbose=False, visible=False): self.flexsimPath = flexsimPath self.modelPath = modelPath self.address = address self.port = port self.verbose = verbose self.visible = visible self.lastObservation = "" self._launch_flexsim() self.action_space = self._get_action_space() self.observation_space = self._get_observation_space() def reset(self): self._reset_flexsim() state, reward, done = self._get_observation() return state def step(self, action): self._take_action(action) state, reward, done = self._get_observation() info = {} return state, reward, done, info def render(self, mode='human'): if mode == 'rgb_array': return np.array([0,0,0]) elif mode == 'human': print(self.lastObservation) elif mode == 'ansi': return self.lastObservation else: super(FlexSimEnv, self).render(mode=mode) def close(self): self._close_flexsim() def seed(self, seed=None): self.seedNum = seed return self.seedNum def _launch_flexsim(self): if self.verbose: print("Launching " + self.flexsimPath + " " + self.modelPath) args = [self.flexsimPath, self.modelPath, "-training", self.address + ':' + str(self.port)] if self.visible == False: args.append("-maintenance") args.append("nogui") self.flexsimProcess = subprocess.Popen(args) self._socket_init(self.address, self.port) def _close_flexsim(self): self.flexsimProcess.kill() def _release_flexsim(self): if self.verbose: print("Sending StopWaiting message") self._socket_send(b"StopWaiting?") def _get_action_space(self): self._socket_send(b"ActionSpace?") if self.verbose: print("Waiting for ActionSpace message") actionSpaceBytes = self._socket_recv() return self._convert_to_gym_space(actionSpaceBytes) def _get_observation_space(self): self._socket_send(b"ObservationSpace?") if self.verbose: print("Waiting for ObservationSpace message") observationSpaceBytes = self._socket_recv() return self._convert_to_gym_space(observationSpaceBytes) def _reset_flexsim(self): if self.verbose: print("Sending Reset message") resetString = "Reset?" if hasattr(self, "seedNum"): resetString = "Reset:" + str(self.seedNum) + "?" self._socket_send(resetString.encode()) def _get_observation(self): if self.verbose: print("Waiting for Observation message") observationBytes = self._socket_recv() self.lastObservation = observationBytes.decode('utf-8') state, reward, done = self._convert_to_observation(observationBytes) return state, reward, done def _take_action(self, action): actionStr = json.dumps(action, cls=NumpyEncoder) if self.verbose: print("Sending Action message: " + actionStr) actionMessage = "TakeAction:" + actionStr + "?" self._socket_send(actionMessage.encode()) def _socket_init(self, host, port): if self.verbose: print("Waiting for FlexSim to connect to socket on " + self.address + ":" + str(self.port)) self.serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.serversocket.bind((host, port)) self.serversocket.listen(); (self.clientsocket, self.socketaddress) = self.serversocket.accept() if self.verbose: print("Socket connected") if self.verbose: print("Waiting for READY message") message = self._socket_recv() if self.verbose: print(message.decode('utf-8')) if message != b"READY": raise RuntimeError("Did not receive READY! message") def _socket_send(self, msg): totalsent = 0 while totalsent < len(msg): sent = self.clientsocket.send(msg[totalsent:]) if sent == 0: raise RuntimeError("Socket connection broken") totalsent = totalsent + sent def _socket_recv(self): chunks = [] while 1: chunk = self.clientsocket.recv(2048) if chunk == b'': raise RuntimeError("Socket connection broken") if chunk[-1] == ord('!'): chunks.append(chunk[:-1]) break; else: chunks.append(chunk) return b''.join(chunks) def _convert_to_gym_space(self, spaceBytes): paramsStartIndex = spaceBytes.index(ord('(')) paramsEndIndex = spaceBytes.index(ord(')'), paramsStartIndex) type = spaceBytes[:paramsStartIndex] params = json.loads(spaceBytes[paramsStartIndex+1:paramsEndIndex]) if type == b'Discrete': return gym.spaces.Discrete(params) elif type == b'Box': return gym.spaces.Box(np.array(params[0]), np.array(params[1])) elif type == b'MultiDiscrete': return gym.spaces.MultiDiscrete(params) elif type == b'MultiBinary': return gym.spaces.MultiBinary(params) raise RuntimeError("Could not parse gym space string") def _convert_to_observation(self, spaceBytes): observation = json.loads(spaceBytes) state = observation["state"] if isinstance(state, list): state = np.array(observation["state"]) reward = observation["reward"] done = (observation["done"] == 1) return state, reward, done class NumpyEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, np.integer): return int(obj) elif isinstance(obj, np.floating): return float(obj) elif isinstance(obj, np.ndarray): return obj.tolist() return json.JSONEncoder.default(self, obj) def main(): env = FlexSimEnv( flexsimPath = "C:/Program Files/FlexSim 2022/program/flexsim.exe", modelPath = "E:/Flexsim/demo/ChangeoverTimesRL.fsm", verbose = True, visible = True ) for i in range(2): env.seed(i) observation = env.reset() env.render() done = False rewards = [] while not done: action = env.action_space.sample() observation, reward, done, info = env.step(action) env.render() rewards.append(reward) if done: cumulative_reward = sum(rewards) print("Reward: ", cumulative_reward, "\n") env._release_flexsim() input("Waiting for input to close FlexSim...") env.close() if __name__ == "__main__": main()
Flexsim与MySQL数据库连接
可以通过查询语句对数据进行过滤,将其配置到flexsim的全局表中,以备后续的处理。
- flexsim本身内部支持查询和过滤,但仍然建议在导入数据源时进行过滤,因为数据库引擎的查询过滤速度比flexsim内部快得多
示例:
Database.Connection con=Database.Connection("DBConnector1");//“DBConnector1”是Database Connectors 上连接的名字。
con.connect();//连接
Table table=Model.find("Source1>variables/schedule");//获取发生器Schedule的表结构。
Database.ResultSet set1=con.query("SELECT * FROM new_table");//new_table 是在SQL数据库里创建的一个Table的名字。
int rowCount=1;
while(set1.fetchNext())//自动调到数据库下一行数据(如果没有下一行就跳出)
{
if(table.numRows<rowCount)
{
table.addRow();
}
for(int i=1;i<=set1.numFields;i++)//numFields是数据库中Table的字段数。
{
Variant val = set1[i];
table[rowCount][i]=val;
}
rowCount++;
}
con.disconnect();//断开连接
部分触发和任务调度样例
//进入触发
if (port == 2)
{
int de=item.B_ID;
Table thelist = getvarnode(current, "componentlist");
treenode thesum = getvarnode(current, "targetcomponentsum");
// thesum.value = 0;
for(int index = 1; index <= thelist.numRows; index++)
{
thelist[index][1] = item.B_ID;
//inc(thesum, item.B_ID);
}
}
//离开触发
item.labels.assert("B_ID").value = item.first.B_ID;
item.labels.assert("ID").value = item.first.ID;
//把产品上的标识赋值给下面的托盘。
treenode BF=Model.find("BasicFR1");//就是之前拉入的BasicFR,这边需要注意名字要一样,或者改成你model中BF的名字;
int Queue_num=token.Q_01.subnodes.length;//再次获取缓存区中产品的数量值,确定for循环的次数。
Array save_item=Model.find("BasicFR1").save_item;//获取BF上的Array数组(以下简称BF);
Array Save=up(token.Pallet).save;//获取Tote存放的Queue上的Array数组(以下简称Qu);
for(int i=1;i<=Queue_num;i++)
{
treenode item=token.Q_01.subnodes[i];
string item_id=item.ID;
for(int j=1;j<=Save.length;j++)
{
string order_id=Save[j];
if(item_id==order_id)//如果缓存区中有产品属于当前订单组成部分,则把Qu中该产品编号从数组中剔除出去,并在BF中添加被剔除的产品编号
{
Save.splice(j,1);
save_item.append([order_id]);
break;
}
}
}
int array_num=save_item.length;
if(array_num == 4)//获取BF数组长度,如果长度==4,说明目前产品已满足订单需求;如果长度==0,说明目前产品不属于该订单;
{
BF.panduan = 1;
}
else if(array_num ==0)
{
BF.panduan = 3;
}
else
{
BF.panduan = 2;
}