实习的收获
在上海某公司实习了一个多月的时间,现在整理一下这一个多月所学的东西以及自己开发的新需求和解决的一些bug。
简单的使用cmake
三条命令:
创建构建目录并进入:
mkdir build && cd build
生成构建系统: (当代码修改多个文件时 链接出错一般重新cmake解决),如果要以调试版本发布,就需要命令
cmake -DCMAKE_BUILD_TYPE=Debug .. cmake ..
编译项目:
make
Git的使用
因为git代码克隆提交这些已经清楚,所学就是分支以及git 缓冲区的这些操作。
git clone 地址下代码后,查看分支信息:
git branch
切换分支:
git checkout 分支名 # 本地已存在的分支
创建本地分支并跟踪远程分支:这是创建一个新的分支,一般写代码都是在自己的新分支下写,再进行合并。也可以直接 git check -b develop_XINYI,创建新分支去编写代码。
git checkout -b develop_XINYI origin/develop_XINYI
Git将本地仓库划分为三个主要区域:工作区、暂存区和本地仓库。一般我们在vscode写的代码保存之后就在工作区
git restore .
:只恢复工作区(未暂存)文件,撤销修改或删除。
git restore --staged .
:把暂存区的文件撤销到工作区。
git reset --hard
:工作区和暂存区一起回退到最后一次提交,所有修改都会丢失。
第一个需求:膜系切换优化
当前已排双银膜系,优先选择的是切换消耗最少的,所以还是一个双银膜系,这个时候同时需要看下这个新选的双银膜系和三银膜系的交期是否存在较大的差距(膜系交期-阈值),如果超过阈值,则切换到三银膜系(即接受切换时间),反之亦然(即从三银切换回到双银)
(1)这个过程仅限于双银和三银之间的切换,不涉及单银、热反射的切换(但可能要考虑配置成可调整的参数)
(2)如果计划开始时间是在换靶开始时间之后的,还要同时兼顾三银的最早可生产时间,不能因为三银交期比较早就切换超过三银的最早可生产时间
(3)如果计划开始时间是在换靶开始时间之前7天内的(没到换靶开始时间),前面双银和三银的那个阈值(假设为7天)要做个消减,即原来超过7天才考虑换三银,这个时候可以只超过2天就要换三银,但三银切换到双银的依然要满足超7天阈值。
思路:就是在原有基础上,添加一个当双银三银膜系的交期差距小于阈值,执行大膜系切换。
在原有的膜系类里添加一个大膜系信息: boost::shared_ptr<std::string> big_coating; 从配置文件中读取时读取每个膜系信息时一并读取。 同时需要配置膜系切换规则,创建一个膜系切换类,该类读取配置文件到切换规则里。如下:
struct BigCoatingSwitch {struct BigCoatingInfo {std::string code;float tolerance_day;void setBind(cppbind::Binder* binder, bool load) {binder->bind("code", code);binder->bind("toleranceDay", tolerance_day);}};void setBind(cppbind::Binder* binder, bool load) {binder->bind("prevBigCoatingCode", prev_big_coating_code);binder->bind("currentBigCoating", current_big_coating);}std::string prev_big_coating_code; // 前膜系BigCoatingInfo current_big_coating; // 下一个待触发的膜系};
从切换规则里找到前一个大膜系是当前大膜系的 需要待选的大膜系current_select_big_coating,然后在剩余目标膜系中找到大模系为current_select_big_coating的膜系,计算膜系的可容忍交期看是否满足条件,核心函数:
// xwy添加,大膜系优化函数boost::shared_ptr<CoatingSeriesInfo> XinYiProcessModule::getNextBigCaoting(boost::shared_ptr<CoatingSeriesInfo> current_select_coating,const std::vector<boost::shared_ptr<CoatingSeriesInfo>>& left_caotings) {// 当前膜系码为空,函数直接返回if (!current_select_coating) {return current_select_coating;}// 1. 获取当前选中膜系的大膜系std::string current_select_big_coating = getBigCoating(current_select_coating->coating_code);// 2. 获取历史最后一个大膜系std::string last_big_coating = getLastBigCoating();// 3. 如果历史最后膜系与当前选中的不一致,不切换,直接返回if (last_big_coating != current_select_big_coating) {return current_select_coating;}// 4. 遍历配置的大膜系切换规则for (const auto& switch_rule : big_coating_switch_) {if (switch_rule.prev_big_coating_code != current_select_big_coating) {continue;}// 4.1 获取预切换的大膜系const std::string& next_big_coating = switch_rule.current_big_coating.code;// 4.2 遍历剩余膜系,查找目标大膜系for (const auto& left_coating : left_caotings) {if (!left_coating)continue;std::string current_left_coating_big = getBigCoating(left_coating->coating_code);if (current_left_coating_big != next_big_coating) {continue;}// 4.3 计算可容忍交期auto tolerence_due_date =left_coating->due_date - boost::posix_time::hours(static_cast<int>(switch_rule.current_big_coating.tolerance_day * 24));LOG_INFO_STREAM("[大膜系切换交期判断] 候选膜系=" << left_coating->coating_code << ",原交期=" << left_coating->due_date << ",容忍交期="<< tolerence_due_date << ";当前膜系交期=" << current_select_coating->due_date);// 4.4 如果目标膜系的容忍交期早于当前选中的交期,表示可以接受,执行切换if (tolerence_due_date < current_select_coating->due_date) {// 日志:大切换命中LOG_INFO_STREAM("[运行状态.大切换匹配] 匹配成功:"<< "当前膜系码 = " << current_select_coating->coating_code << "(大膜系 = " << current_select_big_coating << "),"<< "候选膜系码 = " << left_coating->coating_code << "(大膜系 = " << current_left_coating_big << "),"<< "切换规则 = " << current_select_big_coating << " → " << next_big_coating << ","<< "当前交期 = " << boost::posix_time::to_simple_string(current_select_coating->due_date)<< ",候选交期 = " << boost::posix_time::to_simple_string(left_coating->due_date)<< ",容忍交期 = " << boost::posix_time::to_simple_string(tolerence_due_date) << " → 可切换。");return left_coating;}}}// 5. 默认返回原始膜系return current_select_coating;}
之间遇到一个段错误的bug,因为 boost::shared_ptr类型进行绑定时,因为该类型解析出现了问题,改成std:shared_ptr或者std:string 即可。或者在底层重载boost::shared_ptr类型的decode和encode方法。
这个项目主要有两部分主要逻辑,外部逻辑主要涉及膜系的选择,主要目的是确保相同膜系(CoatingSeries
)的订单能够连续生产(减少换型时间)。内部逻辑主要涉及切磨刚合片中空夹层工序的具体排程。
第二个需求:夹具工序排程
工序排程:有五道工序(装板->巡检->老化->耐电压->拆板)需要用到夹具资源,夹具资源是有限的,进行订单中这五道工序的排程,夹具资源连续占用:思路:
排装板工序(带“装板”tag)时,弹出5道工序为一组,(最后一道拆板工序,带“拆板”tag),进行组装排程,一次排程,将5道一道道弹出排 - 排程逻辑
1. 先排装板,根据装板排程开始时间,找到此时可用夹具数目,根据可用夹具数反算可排数量trueQuantity所有子工序根据trueQuantity排产.
2. 所有子工序排成功后,到使用的夹具中找“装板开始-拆板结束”连续的可用时间
1. 找到连续可用时间,此次排程成功,从夹具中抠除“装板开始-拆板结束”时间段
2. 找不到连续可用时间,取消子工序,按照最小夹具数重新计算数量,重排
1. 按最小夹具量试排成功,校验当前使用的夹具能否找到“装板开始-拆板结束”连续的可用时间。如果找到连续可用时间,此次排程成功,从夹具中抠除“装板开始-拆板结束”时间段 如果找不到连续可用时间,取消子工序,查询夹具下一个可用时间块,设置为当前装板工序最早可用时间,继续循环a的步骤
核心代码:
void GroupScheduleModule::zhuangBanProcess(std::list<aps::Operation*>& operations, In* context) {auto first_op = operations.begin();auto new_param = context->getSchedulingParameter().get();// xwy修改 添加循环保护int retry_count = 0;while ((*first_op)->getLeftQuantity() > 0.0) {retry_count++;LOG_INFO_STREAM(u8"开始处理装板工序:" << (*first_op)->getCode() << ",剩余数量:" << (*first_op)->getLeftQuantity()<< ", retry count: " << retry_count);// 遍历组内所有工序,拆分出子工序bp::ptime base_begin_time; // 记录裝板工序任务开始时间// ??????std::vector<boost::shared_ptr<aps::Operation>> assign_operations;std::vector<boost::shared_ptr<aps::Operation>> sub_operations;// for (auto op : operations) {// sub_operations.push_back(op->split(op->getLeftQuantity()));// }for (auto op : operations) {auto sub_op = op->split(op->getLeftQuantity());if (!sub_op) {LOG_ERROR_STREAM("Split returned null for operation: " << op->getCode() << ", left=" << op->getLeftQuantity());continue;}sub_operations.push_back(sub_op);}// 将子工序先加到父工序上for (auto op : sub_operations) {op->makeTheSplitEffective();}assign_operations.push_back(*sub_operations.begin());// 先排第一道 拿到排程开始时间auto status = assignOperations(assign_operations, context);if (status) {// 获取装板开始时间auto first_operation = assign_operations.at(0);if (first_operation->getSchedule()) {base_begin_time = first_operation->getSchedule()->beginTime();// 查询可用夹具,反算数量auto matched_fixtures = getMatchedFixture(base_begin_time, first_operation);auto fixture_vec = getAvailableFixture(base_begin_time, matched_fixtures);auto fix_info = getFixtureInfo(first_operation);// 根据数量,排多道工序auto ava_count = fixture_vec.size();auto old_quantity = first_operation->getQuantity();auto old_dur = first_operation->getSchedule()->getDur();auto need_count = std::ceil(old_quantity / fix_info->getUnitQuantity());double true_quantity = old_quantity;// 取消装板unAssigned(assign_operations, false);LOG_INFO_STREAM("开始时间base_begin_time: " << base_begin_time << " 可用夹具:" << ava_count << " 需要夹具" << need_count);if (ava_count < need_count) { // 不够用if (ava_count >= fix_info->getMinCount()) { // xwy修改 10fix_info->getMinCount() 可用夹具数大于最小拆分夹具个数LOG_INFO_STREAM("夹具不够用,但满足最小拆分 可用夹具:" << ava_count << " 需要夹具: " << need_count);retry_count = 0;true_quantity = ava_count * fix_info->getUnitQuantity();// 所有子工序排产updateQuantity(sub_operations, true_quantity);auto tt = processByQuantiyAndFixture(sub_operations, fixture_vec, fix_info, context, true_quantity, base_begin_time);if (tt) {first_operation->getParentOperationPtr()->setExtraEarlistStart(tt);LOG_INFO_STREAM("按照 最小拆分 排产失败,更新 earliestStart,准备重排,重排时间为" << *tt);// 从父工序上剔除装板~拆板子工序// retrieveOperations(sub_operations);continue; // 重排装板} else {LOG_INFO_STREAM("当前工序按最小拆分 排程完毕,排程数量:" << true_quantity);}} else { // 不满足最小拆分LOG_INFO_STREAM("夹具不够用,且不满足最小拆分");// 找夾具下一个可用时间boost::shared_ptr<bp::ptime> earliest_begin_time;if (ava_count == 0) { // 0个夹具可用情况// 从当前有资格排夹具列表matched_fixtures,找下一个可用时间块earliest_begin_time = findFixtureNextAvaTime1(sub_operations, matched_fixtures, base_begin_time);LOG_INFO_STREAM("ava_count == 0 可用夹具:" << ava_count << " 需要夹具: " << need_count);} else {earliest_begin_time = findFixtureNextAvaTime1(sub_operations, fixture_vec, base_begin_time);LOG_INFO_STREAM("ava_count != 0 可用夹具:" << ava_count << " 需要夹具: " << need_count);}// xwy添加有效性检查, 无效时间则不更新if (!earliest_begin_time || *earliest_begin_time <= base_begin_time) {LOG_INFO_STREAM("Invalid next available time found, breaking loop :" << *earliest_begin_time);if (!earliest_begin_time) {LOG_INFO_STREAM("..........NULL........");}retrieveOperations(sub_operations);continue; // 继续下一次循环尝试}first_operation->getParentOperationPtr()->setExtraEarlistStart(earliest_begin_time);// 从父工序上剔除装板~拆板子工序retrieveOperations(sub_operations);continue; // 重排装板}} else { // 当前夹具数够用LOG_INFO_STREAM("夹具数量够用: 可用夹具:" << ava_count << " 需要夹具:" << need_count);retry_count = 0;std::vector<std::pair<std::string, utility::TimeBlockV2<utility::TimeSlot>>> cur_fixtures;cur_fixtures.assign(fixture_vec.begin(), fixture_vec.begin() + need_count);auto tt = processByQuantiyAndFixture(sub_operations, cur_fixtures, fix_info, context, true_quantity, base_begin_time);if (tt && *tt > base_begin_time) {LOG_INFO_STREAM("按照 需要夹具数量进行 排产失败 ,更新 earliestStart,准备重排: " << *tt);auto parentOp = first_operation->getParentOperationPtr();if (parentOp) {parentOp->setExtraEarlistStart(tt);} else {LOG_INFO_STREAM("Warning: parentOperationPtr is null, skip setting ExtraEarlistStart.");}retrieveOperations(sub_operations);continue;} else {LOG_INFO_STREAM("当前工序按照需要夹具量 排程完毕,排程数量:" << true_quantity);}}}}}}
出现 bug1:
运行失败,出现段错误。
原因:在可用夹具数目 小于 最小可拆分夹具数目的情况下,可能出现当前可用夹具为0的情况,如果此时在0个夹具里找下一个可用时间,则报错。因此当可用夹具为0的情况下,需要在所有匹配的 夹具里寻找最早可用时间,作为下一次装板时间。
if (ava_count == 0) { // 0个夹具可用情况// 从当前有资格排夹具列表matched_fixtures,找下一个可用时间块earliest_begin_time = findFixtureNextAvaTime1(sub_operations, matched_fixtures, base_begin_time);LOG_INFO_STREAM("ava_count == 0 可用夹具:" << ava_count << " 需要夹具: " << need_count);} else {earliest_begin_time = findFixtureNextAvaTime1(sub_operations, fixture_vec, base_begin_time);LOG_INFO_STREAM("ava_count != 0 可用夹具:" << ava_count << " 需要夹具: " << need_count);}
小需求2:
GroupScheduleModule要做修改:装板~拆板工序,夹具按照后端传入的固定时长扣减可用时间。在原先夹具文件fixtureToolsLimit.json文件新增字段"fixedDur":"06:00:00,接收固定时长。
思路:removeFixtureTimeSlot()方法和hasContinuesFixture方法 里这个end_time修改为该工序的开始时间加上固定时长。
// 使用固定夹具时长auto fixed_hours = ftool_limit_.getFixtureFixedDurationHours();auto end_time = begin_time + boost::posix_time::hours(fixed_hours);
bug3:死循环问题
原因:在匹配夹具中寻找下一个可用时间的逻辑 与 在可用夹具中寻找下一个可用时间的逻辑有所不同,应该是两个不同的函数。
在匹配夹具中寻找下一个可用时间的逻辑:在所有匹配夹具中,寻找在base_time之后最早的那个可用时间段的 begin_time返回即可。
在可用夹具中寻找下一个可用时间的逻辑:在所有可用夹具中,寻找在base_time之后最早的那个可用时间段的 begin_time的下一分钟返回即可。这样才可以保证下一个循环的时间在不断推进。
bug4:夹具排程结果不合理
原因:有一些夹具在跨天的前提下为空闲时间。
分析:现有夹具可用时间有些时按照一天一天给出的,逻辑判断夹具可用时间段就是按照一个个时间段的(begin_time,end_time)的与 (base_time,base_time+use_hour)的关系来判断夹具是否有连续可用。 正确包含可用时间段函数:
bool GroupScheduleModule::containsTime(const utility::TimeBlockV2<utility::TimeSlot>& block, const bp::ptime& base_time) {auto fixed_hours = ftool_limit_.getFixtureFixedDurationHours();auto end_time = base_time + boost::posix_time::hours(fixed_hours);// 检查是否存在连续的时间段覆盖整个 [base_time, end_time] 区间bool found_continuous_coverage = false;bp::ptime current_start = base_time;while (current_start < end_time) {bool found_slot = false;// 查找能覆盖 current_start 的时段for (const auto& slot : block.getSlots()) {if (slot.getBeginTime() <= current_start && current_start < slot.getEndTime()) {// 推进当前检查点到该时段的结束时间(或目标结束时间)current_start = std::min(slot.getEndTime(), end_time);found_slot = true;break;}}if (!found_slot) {// LOG_INFO_STREAM("Time gap found at: " << current_start);return false;}// 如果已经到达目标结束时间if (current_start >= end_time) {found_continuous_coverage = true;break;}}LOG_INFO_STREAM((found_continuous_coverage ? "Valid" : "Invalid") << " time range: " << base_time << " to " << end_time);return found_continuous_coverage;}
第三个需求:炉资源约束
老化工序需要进行合炉生产,如下图:
整体排程逻辑是:老化资源维护成多重能力资源,
先走上面的GroupScheduleModule模块,将所有工序排一遍,包含老化、拆板工序
增加个老化工序特殊后处理模块"AgingProcessModule",排程走到AgingProcessModule模块先取消老化、耐电压、拆板及其他后工序;再把老化工序能组成一炉的组一炉,排老化
同时老化后工序全都拉到一个列表平移重排
具体解决方案:
将炉资源作为多重能力传递到算法,同时需要在对应资源的tags上配置"老化资源"以便识别。
在工序文件中增加关于温度,电压,板位数,筛选时长的规则信息
在conf.json的groupScheduleModule后配置组炉模块,配置如下
{"agingProcessModule": {"furnaceOperationTag": "老化资源","maxFurnaceDur": "06:00:00","furnaceCheckSpecs": ["温度","电压","板位数","筛选时长"]}},
主要逻辑函数:
void ReAssignFurnaceModule::processSpec(In* context) {this->assign_helper_->init(context);updateLastInfo(context); // 更新待排工序的上次排程时间// 获取所有炉工序auto all_furnace_operations = getAllFurnaceOperations(context);// 获取所有炉工序后工序auto all_next_operations = getAllNexts(all_furnace_operations);// 取消所有工序以及后工序this->unassignAll(all_next_operations);this->unassignAll(all_furnace_operations);need_dump_context_ = true;if (need_dump_context_) {context->dump4Debug(context->dump_prefix + "/beforeFurnace");}// 排同炉工序this->assignFurnace(all_furnace_operations, context);if (need_dump_context_) {context->dump4Debug(context->dump_prefix + "/afterFurnace");}// 排剩余工序this->assignAllLeftOperations(all_next_operations, context);// 炉资源上单工序存入待排context->setDummyOperations(operations_need_schedule_parent);}
bug1:
组炉成功时运行失败
原因:迭代器失效,使用for循环作为外层条件,同时++iter,而内部删除iter也会跳过一个元素。
// xwy修改std::list<std::list<boost::shared_ptr<aps::Operation>>> ReAssignFurnaceModule::toFurnaceOperations(std::list<boost::shared_ptr<aps::Operation>> operations,const aps::Resource* resource) const {operations.sort([](const boost::shared_ptr<aps::Operation>& a, const boost::shared_ptr<aps::Operation>& b) {return a->getSchedule()->processSchedule()->getEarlistBeginTime() < b->getSchedule()->processSchedule()->getEarlistBeginTime();});LOG_INFO_STREAM(u8"开始处理资源" << resource->getId() << u8"上的组炉工序,共" << operations.size() << u8"个工序");std::list<std::list<boost::shared_ptr<aps::Operation>>> all_furnace_operations;// 外层使用while循环而不是for循环,避免迭代器失效问题while (!operations.empty()) {auto first_operation = operations.front();std::list<boost::shared_ptr<aps::Operation>> furnace_operations = {first_operation};auto current_left_quantity = resource->getMaxBatchSize() - first_operation->getQuantity();LOG_INFO_STREAM(u8"处理工序" << first_operation->getId() << u8",最早开始时间:"<< first_operation->getSchedule()->processSchedule()->getEarlistBeginTime() << u8",剩余容量:"<< current_left_quantity);operations.pop_front(); // 移除第一个元素auto iter = operations.begin();while (iter != operations.end() && current_left_quantity > 0) {auto current_operation = *iter;bp::time_duration time_diff = current_operation->getSchedule()->processSchedule()->getEarlistBeginTime()- first_operation->getSchedule()->processSchedule()->getEarlistBeginTime();if (time_diff > this->max_furnace_dur_) {LOG_INFO_STREAM(u8"aaaaaaaaaaaa");break;}if (!this->canFurnace(first_operation, current_operation)) {++iter;continue;}if (current_left_quantity >= current_operation->getQuantity()) {furnace_operations.push_back(current_operation);current_left_quantity -= current_operation->getQuantity();iter = operations.erase(iter); // 注意这里iter会被erase更新} else {++iter;}}std::stringstream ssm;if (furnace_operations.size() > 1) {ssm << ".组炉成功]:";} else {ssm << ".组炉失败]:";}for (auto furnace_operation : furnace_operations) {ssm << furnace_operation->getId() << ",";}LOG_INFO_STREAM(u8"[运行状态.同炉校验" << ssm.str());all_furnace_operations.emplace_back(std::move(furnace_operations));}return all_furnace_operations;}
第四个需求:温冲工序组炉
a)温度冲击工序:温度相同合炉生产
约束:限制重量限制8公斤或承载批数54。 (即)
排程要求:交期靠前先排,按订单排序的顺序,进行合炉;
场景描述:
温冲工序共两个炉资源,每个炉资源要求温度相同,温度参数共两组(-55,125),(-55,85),即可视作第一组为温度条件A,第二组为温度条件B,炉资源温度以第一个排至该炉的订单要求温度为当前温冲温度;
后端将mes传来的:(5*循环时间*2+1)作为该工序制造时间,单位为h,(循环次数/5-1)*(5*循环时间*2+1)+筛选表中间隔时间作为该工序间隔时间,单位为h,同步至工艺路径;
后端进行更新产能时,匹配到筛选产能维护表的单重,单重*制造订单的数量=订单总重
以后端传来制造时间进行排程,以同温冲温度作为合炉约束
后端传至算法参数:制造时间,间隔时间,温冲温度,订单总重
实现方案:后端把温压工序跟其他工序分开排程,即调用两次算法,第一次排温压工序,第二次排其他工序,两次调用不同的配置,conf.json中的配置是不一样的
将炉资源作为多重能力传递到算法,同时需要在resoure.json中对应的温冲资源的tags上配置"温冲资源"
在工序中增加关于温度冲击工序的规则信息,包括温度和该订单下工序的总重量,如同上面老化工序增加的规格信息。
在conf.json的(groupScheduleModule删除) "ruleLoader" 后配置温冲组炉模块
代码方案:逻辑与老化工序类似。
新写一个类WenYaProcessModule继承自ReAssignFurnaceModule
增加总量组炉上限配置double max_wegiht_
增加工工序组炉上限(承载批数) int max_operation_count_
重写canFurnace函数,需要额外增加校验规则
判断约束“限制重量限制8公斤或者承载批数54。 ”
重量入参。候选需要把重量设置的工序规格上,ke=重量,name=重量,value=实际重量
获取operation跟other的"重量"和,看是否超max_wegiht_,参考现有的canFurnace或者规格值值的方式,获取出来后转成double就行
重写setBind函数解析配置中的组炉上限,参考ReAssignFurnaceModule::setBind,需要有以下几行代码
binder->bind("maxWeight",max_wegiht_);
binder->bind("maxOperationCount",max_operation_count_);
ReAssignFurnaceModule::setBind(binder, load)
重写组炉函数toFurnaceOperations,主要有以下几个点需要修改
现在的operations按照最早开始时间排序的,即"a->getSchedule()->processSchedule()->getEarlistBeginTime()",需要修改成按照制造开始时间,即"a->getSchedule()->processSchedule()->beginTime()"
现在的current_left_quantity是判断数量,现在需要修改成剩余承载批数,即“auto current_left_count =max_operation_count_-1”,对应下面减法的时候,直接减1即可,因为是按照订单量计算的,一个工序即一个订单
ModuleRegister中注册, REGISTER_CLASS_WITH_NAME(algorithm::WenYaProcessModule, aps::RegistersKey::MODULE,"WenYaProcessModule");
该需求出现的bug均为底层框架不熟悉造成(不了解),还有一些就是由于粗心造成的小错误。如两个规格值为null时判断相等,或者不以制造时长相同为同炉条件等。