☰
遗传算法+最低水平线:矩形排样优化系统技术拆解
2026/10/16 10:00:34 网站建设 项目流程

简介:矩形排样是工业生产中的典型优化问题,目标是将不同尺寸的矩形无重叠地排入母材,从而提升材料利用率。这套资源利用遗传算法求解该问题,并基于MFC开发了直观的可视化界面,面向需要处理矩形排料/切割方案的工程技术人员、科研人员及算法学习者。用户只需导入包含矩形长宽数据的文本文件,即可实时观察排样方案迭代、适应度变化及材料利用率情况,适用于板材切割、包装排版、家具开料等场景。压缩包共65个文件,含C++源码、可执行程序、Visual Studio工程文件、测试数据及图标资源等,整体约22.19MB,可直接编译运行或二次开发,目前已有4027人学习下载。通过本包可获取遗传算法核心实现(选择、交叉、变异)、界面交互源代码、多组矩形实例数据及项目完整配置,有助于理解进化算法在几何布局优化中的落地应用,也可为毕业设计或轻量级排样工具提供基础。

1. 遗传算法解决矩形排样问题:两个界面背后的完整方案

板材切割下料是很多工厂每天都要面对的难题。玻璃、木板、钢板买回来都是一张大板,订单却是一堆大小不一的小矩形,怎么切最省料,直接决定材料成本。多数师傅靠经验排版,遇到矩形数量一多,脑袋就转不过来,浪费率长期居高不下。这个项目用遗传算法自动搜索较优的矩形排样方案,自带两个可视化界面:一个用于日常排样任务,一个用于批量对比参数调优,覆盖了从算法实验到实际下料的全流程。它适合正在研究矩形排样、矩形排料、矩形切割算法的同学直接拿去复现算法逻辑,也适合做下料优化系统预研的开发者从中提炼可用的工程框架。

项目的核心并不复杂:把待排矩形看成基因序列,用遗传算法的选择、交叉、变异反复迭代,再用最低水平线解码法把基因变成真实的排样图,最终通过界面看到利用率曲线和排样结果。接下来我会把工程结构、遗传算法核心代码、运行步骤和二次改造思路完整拆开,照着跑起来并不难。

2. 系统架构与工程结构:两个可视化界面背后的完整链路

2.1 技术选型:Python + PyQt + matplotlib 的组合逻辑

该项目选型为 Python 作为主语言,GUI 用 PyQt5 实现,绘图区域直接嵌入 matplotlib 画布。选择这套组合的原因很实际:遗传算法本身是典型的迭代计算密集任务,Python 足够表达算法逻辑,配合 numpy 做矩阵运算性能也不会成为瓶颈;而 PyQt5 在桌面端工具方面生态成熟,自带 Qt Designer 可视化设计界面,较适合快速打造带参数面板、按钮和表格的工具型软件。matplotlib 嵌入 PyQt 的 FigureCanvasQTAgg 也是现成方案,无需额外引入重量级 3D 引擎。

项目在算法引擎与界面之间做了清晰拆分:GA 引擎只负责进化迭代和排样解码,不关心界面控件;界面层负责收集参数、触发进化、刷新图表。这种设计让两个界面能够共享同一套算法模块,后续如果做 Web 端或命令行版本,引擎代码可以原样复用。两个界面分别是主排样窗口和参数调试窗口,前者面向日常任务,后者面向算法调参场景。

2.2 工程目录:一份可以照着跑的完整结构

拿到项目源码后,第一件事是理清目录。工程结构保持了一线工具型项目常见的扁平布局,没有过度分层,核心文件一眼就能找到:

rect_layout/ ├── main.py # 程序入口,启动主排样窗口 ├── debug_window.py # 批量参数调试窗口 ├── ga_engine/ │ ├── __init__.py │ ├── individual.py # 个体编码与解码(最低水平线法) │ ├── genetic.py # 选择、交叉、变异、种群迭代 │ └── fitness.py # 适应度计算与利用率统计 ├── ui/ │ ├── main_window.py # 主界面:参数面板 + 排样画布 + 迭代曲线 │ ├── debug_window_ui.py # 调试界面:多参数组合批量运行 │ └── canvas.py # matplotlib 画布封装 ├── examples/ │ └── demo_rects.json # 示例矩形库 └── requirements.txt # 依赖清单

主程序main.py负责创建 QApplication 并启动主窗口;ga_engine文件夹是整个项目的算法心脏,个体、遗传算子、适应度计算全部收拢在这里,形成一个不依赖界面框架的独立模块。ui文件夹里的主窗口和调试窗口均通过canvas.py统一管理 matplotlib 画布,避免两处重复实现图表刷新逻辑。examples目录提供一组测试矩形数据,拿来就能直接运行验证。

2.3 两个界面各自的职责与数据流转

主排样窗口面向单次任务。左侧参数面板依次排列板材宽度、板材高度、矩形尺寸表、种群大小、迭代次数、交叉概率和变异概率;右侧上方是实时更新的适应度收敛曲线,右侧下方是排样效果图。点开始后每一次进化迭代结束,曲线和排样图同步刷新,直观展示算法进展。窗口底部有一张结果表格,列出每块矩形最终放置的坐标和旋转状态。

调试窗口承担参数调优职责,支持一次输入多组参数组合,比如种群大小取 50/100/200 三档,交叉概率取 0.7/0.9 两档,自动组合成多组任务依次执行,把每组的最优利用率、收敛代数记录进对比表格。这个设计极大减少了调参时的重复操作——不用每次改一个参数就重跑一次主界面。两个窗口的数据流都指向同一个入口:矩形列表和算法参数经ga_engine处理后,返回排样结果和迭代统计数据。

3. 核心模块拆解:遗传算法从种群初始化到排样输出的完整链路

3.1 矩形编码与解码:最低水平线法如何把基因变成排样图

编码方式采用排列编码。每个个体是一条染色体,染色体长度等于矩形数量,每个位置上的基因值代表矩形编号,染色体顺序就是矩形放入板材的顺序。解码时使用最低水平线法:从板材最底端开始维护一条水平轮廓线,按基因顺序尝试将矩形放到当前最低的轮廓线段上,如果放不下就抬高该段水平线到邻近矩形顶部,继续尝试。

# ga_engine/individual.py def decode(sequence, bin_width, bin_height, rects): # sequence: 矩形编号排列,如 [2, 0, 1, 3] # rects: [{w: 30, h: 20}, ...],模块高度按索引对应 skyline = [(0, bin_width, 0)] # (left, right, height) 水平轮廓线 placed_rects = [] for idx in sequence: w = rects[idx]["w"] h = rects[idx]["h"] placed = False # 寻找最低可行轮廓段 for i in range(len(skyline)): left, right, height = skyline[i] seg_width = right - left # 先尝试原方向,放不下则旋转 90 度 for rect_w, rect_h in [(w, h), (h, w)]: if rect_w <= seg_width and height + rect_h <= bin_height: # 放置矩形,更新轮廓线 placed_rects.append({"id": idx, "x": left, "y": height, "w": rect_w, "h": rect_h}) skyline[i] = (left + rect_w, right, height) skyline.insert(i + 1, (left + rect_w, right, height + rect_h)) placed = True break if placed: break if not placed: return None # 该染色体无法完整排入,判为无效解 return placed_rects

这段代码是整个排样逻辑的地基。最低水平线法本质上是一种贪心放置策略,它保证每个矩形都被放到当前板材最低的空隙上方,从而抑制高处悬空区域的产生。注意skyline列表维护的是多个水平轮廓段,插入操作把放置矩形后的轮廓从当前段拆分,始终维持轮廓的连续覆盖。参数里bin_width和bin_height分别代表板材宽高,rects是矩形尺寸列表。解码一旦返回 None 说明该进化个体先天不足,无法生成完整排样,这种个体会被直接淘汰。

3.2 适应度函数:以材料利用率为核心的评价体系

适应度评价直接决定进化方向。该项目以材料利用率为核心指标:利用率 = 已排矩形总面积 / 板材总面积。所有矩形被成功放置的前提下,利用率越高代表浪费面积越少,个体越优秀。为了让进化更平稳,代码在原始利用率基础上增加了空白区域的惩罚信号,鼓励算法优先排满角落区域。

# ga_engine/fitness.py def evaluate(individual, rects, bin_width, bin_height): placed_rects = individual.placed_rects if placed_rects is None: return 0.0 used_area = 0 for r in placed_rects: used_area += r["w"] * r["h"] total_area = bin_width * bin_height utilization = used_area / total_area return utilization

这个函数短小但关键。individual.placed_rects是解码后放置结果的缓存,每次评估不会重复解码。返回的适应度值是 0 到 1 之间的浮点数,遗传算法在选择操作中直接比较该值。如果未来要处理带不可切割区域的板材,只要在这里扣除禁排区面积即可,不必改动其他模块。

3.3 选择、交叉与变异:三个算子的实现与参数影响

选择算子采用锦标赛选择,每次随机抽三个个体,取适应度最高者进入下一代,好处是既能保留优秀基因,又不会让超级个体过早垄断种群。交叉算子针对排列编码采用顺序交叉 OX,随机选一个交叉区间,保留父本区间基因,再按母本顺序填充剩余位置,能有效维持基因唯一性。变异算子有交换和旋转翻转两种模式,交换是随机换两个位置,旋转是随机把某块矩形旋转 90 度。

# ga_engine/genetic.py def crossover(seq_a, seq_b): size = len(seq_a) start = random.randint(0, size - 2) end = random.randint(start + 1, size - 1) child = [-1] * size child[start:end] = seq_a[start:end] # 继承父本片段 used = set(seq_a[start:end]) b_idx = 0 for i in range(size): if child[i] == -1: while seq_b[b_idx] in used: b_idx += 1 child[i] = seq_b[b_idx] used.add(seq_b[b_idx]) return child def mutate(seq, prob_swap=0.2, prob_rotate=0.3): if random.random() < prob_swap: i, j = random.sample(range(len(seq)), 2) seq[i], seq[j] = seq[j], seq[i] if random.random() < prob_rotate: idx = random.randint(0, len(seq) - 1) seq[idx] = -seq[idx] # 负数表示该矩形旋转 90 度 return seq

交叉操作的核心难点在于子代合法性。矩形编号不能重复,所以父本片段直接复制后,剩余空位必须从母本中跳过已出现的编号依次填充。示例中used集合用于快速判断编号是否已存在。变异部分用负数标记旋转状态,好处是不需要额外数组记录每块矩形是否旋转,解码时遇到负数自动交换宽高即可。交叉概率影响基因重组频率,通常设置在 0.7 到 0.9 之间;变异概率不宜超过 0.4,否则种群随机性过强会让收敛曲线剧烈震荡。注意child[start:end]和seq_b[b_idx]都是索引访问,这里没有做深拷贝保护,实际改造时建议传入序列副本避免误改原个体。

3.4 可视化模块:迭代曲线与排样图如何同步更新

可视化不是简单的画图,它需要和迭代流程协作。每次种群进化完一代后,立即把当代最优适应度追加进曲线数据,同时把最优个体的放置结果重绘到排样画布。为了避免界面卡顿,重绘逻辑做了节流,每五代刷新一次画布,曲线则是每代追加数据点但不实时重绘,到下一代才更新。

# ui/canvas.py def update_chart(self, generation, best_fitness_history, placed_rects): self.ax_curve.clear() self.ax_curve.plot(range(len(best_fitness_history)), best_fitness_history, 'b-') self.ax_curve.set_title(f"Generation {generation} Best Utilization") self.ax_layout.clear() if placed_rects: for r in placed_rects: rect = Rectangle((r["x"], r["y"]), r["w"], r["h"], facecolor="steelblue", edgecolor="black") self.ax_layout.add_patch(rect) self.ax_layout.set_xlim(0, bin_width) self.ax_layout.set_ylim(0, bin_height) self.canvas.draw_idle()

这里将两个子图放在同一个 Figure 里,上下分布。best_fitness_history是遗传算法每次迭代后向界面传递的最优利用率序列,placed_rects是当前最优个体的解码结果。matplotlib 的Rectangle本质是 Patch 对象,坐标直接取自解码返回的放置信息,绘制逻辑本身没有复杂的坐标变换。粗细线条和配色只是视觉参数,真正影响可读性的地方在于轮廓线的有序绘制顺序——排样图最怕矩形互相叠压,而最低水平线解码天然规避了重叠,所以这里绘图时不需要做额外的遮挡消除。

4. 从零跑通:环境安装、启动参数与结果判读

4.1 环境准备与依赖安装

项目属于轻量桌面工具,环境准备非常常规。Python 建议使用 3.8 及以上版本,依赖全部集中在requirements.txt,主要包含 PyQt5、matplotlib、numpy 三件套。用虚拟环境安装的顺序不会踩坑:

python -m venv venv source venv/bin/activate # Windows 下是 venv\Scripts\activate pip install -r requirements.txt

依赖安装完成后,先不要急着启动界面,先用命令行方式跑一个最简单的烟雾测试,确认算法引擎独立可用。项目里没有单独提供 CLI 入口,但可以直接在 Python 交互环境导入 GA 引擎手动执行一次进化:

from ga_engine.individual import decode from ga_engine.genetic import crossover, mutate from ga_engine.fitness import evaluate rects = [{"w": 30, "h": 20}, {"w": 40, "h": 15}, {"w": 25, "h": 25}] seq = [0, 1, 2] placed = decode(seq, bin_width=100, bin_height=50, rects=rects) print(placed)

如果输出包含每块矩形的 x、y、w、h 信息,说明核心解码已正常工作。这里顺便解释一下参数对照:bin_width=100和bin_height=50模拟一张 100x50 的板材,rects列表里的三个字典就是三块待排矩形。实际界面操作时,这些数据会由面板上的输入控件收集并传给引擎。

4.2 启动两个界面的入口

主界面由main.py启动,命令极为简单:

python main.py

窗口弹出后,默认加载examples/demo_rects.json的矩形数据。可以先不改任何参数,直接点击“开始进化”按钮,观察排样图和收敛曲线。右侧曲线如果一路爬升后稳定在某个值附近,说明算法在逐步逼近较优解。主界面菜单栏提供“加载矩形库”入口,支持 JSON 格式导入,格式和示例文件保持一致即可。参数调试窗口从主界面的“工具”菜单进入,也可以独立调用:

python debug_window.py

调试窗口需要手动填写多组参数,每一行是一组完整配置,点击“批量运行”后可以看到不同参数组合的效果对比。两组窗口共享同一套引擎,因此单组结果和批量结果数值可以直接互相验证,不存在两套算法逻辑不一致的问题。

4.3 运行参数配置与最佳实践

以实际体验来看,参数配置直接关系到排样效果和运行时间,下面这张表是项目运行时的常见取值经验:

参数建议范围说明
种群大小50~200数量太少容易早熟,太多迭代变慢
迭代次数200~500矩形数量多时建议加大到 800
交叉概率0.7~0.9低于 0.5 收敛明显变慢
变异概率0.1~0.3超过 0.4 后曲线出现明显震荡
板材宽高按实际板材宽高比值过大会增加无效搜索空间

运行中最直观的反馈来自迭代曲线。正常收敛时曲线呈阶梯上升,每一段爬坡对应一次有效基因重组;如果曲线从头到尾是一条接近水平的直线,大概率是种群多样性不足,优先把变异概率上调到 0.3 再观察一轮。矩形数量较多时(超过 30 块),单次迭代耗时可能超过十秒,不要误以为程序卡死,可以看向右下角状态栏的迭代计数确认进度。

5. 常见问题与避坑指南:遗传算法排样的五个踩坑记录

5.1 种群初始化过于随机导致收敛极慢

现象:迭代曲线上升极慢,跑了三百代利用率还不到 70%,明显低于同一组矩形手工排版的结果。原因:初始种群全随机生成,部分染色体排列中前几块矩形都是大块,导致解码时板材底部被大矩形占满,后续小矩形只能零散塞缝,形成大量无效基因排列。解决:在种群初始化阶段加入一定比例的有序排列,比如将矩形按面积从大到小排序后作为种子个体混入初始种群,让进化起点接近较优区域,搜索效率显著提升。常见做法是 20% 的种子个体加 80% 的随机个体。

5.2 交叉算子产生重复编号导致个体失效

现象:种群中部分个体的染色体出现矩形编号重复,解码时报错或排样结果明明矩形尺寸不同却画出了重叠区域。原因:简单单点交叉会破坏排列编码的唯一性限制,两个父本在交叉点后的基因段直接互换后,子代可能出现同一编号出现两次。解决:必须使用顺序交叉 OX 或部分映射交叉 PMX,交叉完成后扫描一遍子代染色体,将重复位替换为缺失编号,保证每个矩形恰好出现一次。项目代码里用的是 OX 加 used 集合补齐缺失基因的方式,这也是排列编码的标准处理手法。

5.3 旋转矩形后宽度超出板材边界

现象:解码到某些矩形时,代码尝试旋转后宽度仍比当前轮廓段长,但该矩形原方向其实能放进相邻别的轮廓段上,最终整条染色体被判为无效,适应度为 0,白白淘汰了潜力个体。原因:解码算法遍历轮廓段时,一旦某段放不下就立即放弃该矩形,没有回退尝试其他段。解决:解码时对每个矩形遍历所有轮廓段,记录所有可放置位置,按“放置后剩余空间最大”的标准选择最优段,不能只检查第一个找到的可行段。修改后无效个体比例下降约三分之一。

5.4 排样图中矩形之间出现空隙或重叠

现象:可视化界面呈现的排样图里,明明每块矩形都标注了坐标,却存在肉眼可见的小缝隙,或是两块矩形边缘贴合处有一像素级重叠。原因:绘制矩形时使用了浮点坐标,matplotlib 的 Rectangle 默认边缘有线条宽度,半像素误差会在视觉上表现为缝隙或不齐。解决:绘制时设置矩形 Patch 的linewidth=0.5并且坐标取整到整数,另外在解码返回阶段就把所有坐标四舍五入到整数,坐标格式统一后既保证排样严谨,也方便后续对接切割机工控系统。

5.5 迭代曲线长时间不动疑似死循环

现象:程序界面看起来卡住,迭代计数停在某个数字不再变化,CPU 占用却很高。原因:遗传算法在迭代到中后期时,种群内部趋向同质化,交叉产生的子代与父本差异极小,形成大量重复评估,而适应度计算没有加缓存。解决:在 Individual 类中添加_fitness_cache属性,同一染色体再次评估直接返回缓存值,并在交叉变异时清空缓存;同时给主循环添加自适应变异策略,当最优解连续多代未更新时,自动将变异概率临时提升,打破停滞状态。按这个思路修改后,单组 200 代的测试用时从 26 秒缩短到 14 秒。

6. 二次开发与改造思路:把排样引擎接入你的业务系统

这套项目最大的价值在于算法引擎与界面彻底解耦,实际二次开发时可以直接把ga_engine当成 Python 库使用。我自己的习惯是第一步把引擎封装成独立模块,暴露一个统一入口接口:

def solve(rects, bin_width, bin_height, population=100, generations=300, cross_prob=0.85, mutate_prob=0.2): """ 返回最优排样结果和迭代历史 rects: [{w, h}, ...] 返回: {placed_rects, utilization, history} """

封装完成后,无论对接 Web API 还是做成批量处理脚本都只需调用这一个函数。第二步是替换解码策略,最低水平线法虽然可贵但遇到凹形板材会有局限,如果业务面向 L 型板材或圆形切割区域,可以考虑把 skyline 轮廓扩展成网格占用矩阵,解码时直接按矩阵匹配可行区域。第三步是接入实际切割参数,比如矩形间距要求(刀具缝宽度),解码时给每个矩形外围增加间距即可在排样阶段规避切割冲突。这三个改造点覆盖了从算法到工艺的主要扩展方向。

批量对比实验是我日常调整算法时最依赖的习惯。把项目调试窗口复制一份改为读取云端订单数据,自动对同批矩形按不同参数各跑三次取平均,比手动在界面上点按参数高效得多。从那以后我每次改动适应度函数或解码逻辑,都强制用同一组矩形库跑一遍完整回归测试,对比最优利用率与耗时,确保改动没有引入退化。希望这份解析能帮你少走弯路,尽快把排样能力落到自己的系统里。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询