Browse Source

完成库位分配核心算法

flower_mr 1 month ago
parent
commit
0900458de1

BIN
bin/__pycache__/models.cpython-38.pyc


BIN
bin/__pycache__/views.cpython-38.pyc


+ 60 - 0
bin/migrations/0009_alloction_pre_allocation_history.py

@@ -0,0 +1,60 @@
+# Generated by Django 4.1.2 on 2025-04-18 09:27
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('container', '0001_initial'),
+        ('bin', '0008_alter_locationmodel_current_quantity'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='alloction_pre',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('batch_number', models.CharField(max_length=255, verbose_name='批次号')),
+                ('layer1_pre', models.IntegerField(default=0, verbose_name='第一层预留')),
+                ('layer2_pre', models.IntegerField(default=0, verbose_name='第二层预留')),
+                ('layer3_pre', models.IntegerField(default=0, verbose_name='第三层预留')),
+                ('layer1_pre_type', models.JSONField(default=list, verbose_name='第一层预留类型')),
+                ('layer1_pre_type_number', models.JSONField(default=list, verbose_name='第一层预留数目')),
+                ('layer2_pre_type', models.JSONField(default=list, verbose_name='第二层预留')),
+                ('layer2_pre_type_number', models.JSONField(default=list, verbose_name='第二层预留数目')),
+                ('layer3_pre_type', models.JSONField(default=list, verbose_name='第三层预留')),
+                ('layer3_pre_type_number', models.JSONField(default=list, verbose_name='第三层预留数目')),
+                ('layer1_pressure', models.IntegerField(default=0, verbose_name='第一层工作压力')),
+                ('layer2_pressure', models.IntegerField(default=0, verbose_name='第二层工作压力')),
+                ('layer3_pressure', models.IntegerField(default=0, verbose_name='第三层工作压力')),
+                ('create_time', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')),
+            ],
+            options={
+                'verbose_name': 'Allocation_pre',
+                'verbose_name_plural': 'Allocation_pre',
+                'db_table': 'allocation_pre',
+                'ordering': ['-id'],
+            },
+        ),
+        migrations.CreateModel(
+            name='allocation_history',
+            fields=[
+                ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('operation_type', models.CharField(max_length=10, verbose_name='操作类型')),
+                ('timestamp', models.DateTimeField(auto_now_add=True, verbose_name='操作时间')),
+                ('container', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='container.containerlistmodel', verbose_name='托盘')),
+                ('location', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bin.locationmodel', verbose_name='库位')),
+                ('related_location', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='related_logs_history', to='bin.locationmodel', verbose_name='关联库位')),
+                ('task_detail', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='container.taskmodel', verbose_name='批次详情')),
+                ('task_wcs', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='container.containerwcsmodel', verbose_name='WCS任务')),
+            ],
+            options={
+                'verbose_name': 'Allocation_history',
+                'verbose_name_plural': 'Allocation_history',
+                'db_table': 'allocation_history',
+                'ordering': ['-id'],
+            },
+        ),
+    ]

+ 54 - 0
bin/migrations/0010_remove_alloction_pre_layer1_pre_and_more.py

@@ -0,0 +1,54 @@
+# Generated by Django 4.1.2 on 2025-04-19 22:31
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('bin', '0009_alloction_pre_allocation_history'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer1_pre',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer1_pre_type',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer1_pre_type_number',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer2_pre',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer2_pre_type',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer2_pre_type_number',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer3_pre',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer3_pre_type',
+        ),
+        migrations.RemoveField(
+            model_name='alloction_pre',
+            name='layer3_pre_type_number',
+        ),
+        migrations.AddField(
+            model_name='alloction_pre',
+            name='layer_pre_type',
+            field=models.JSONField(default=list, verbose_name='预留方案'),
+        ),
+    ]

BIN
bin/migrations/__pycache__/0009_alloction_pre_allocation_history.cpython-38.pyc


BIN
bin/migrations/__pycache__/0010_remove_alloction_pre_layer1_pre_and_more.cpython-38.pyc


+ 3 - 11
bin/models.py

@@ -924,17 +924,9 @@ class LocationChangeLog(models.Model):
 class alloction_pre(models.Model):
 
     batch_number = models.CharField(max_length=255, verbose_name='批次号')
-    layer1_pre = models.IntegerField(default=0, verbose_name='第一层预留')
-    layer2_pre = models.IntegerField(default=0, verbose_name='第二层预留')
-    layer3_pre = models.IntegerField(default=0, verbose_name='第三层预留')
-
-    layer1_pre_type = models.JSONField(default=list, verbose_name='第一层预留类型')
-    layer1_pre_type_number = models.JSONField(default=list, verbose_name='第一层预留数目')
-    layer2_pre_type = models.JSONField(default=list, verbose_name='第二层预留')
-    layer2_pre_type_number = models.JSONField(default=list, verbose_name='第二层预留数目')
-    layer3_pre_type = models.JSONField(default=list, verbose_name='第三层预留')
-    layer3_pre_type_number = models.JSONField(default=list, verbose_name='第三层预留数目')
- 
+
+    layer_pre_type = models.JSONField(default=list, verbose_name='预留方案')
+
     layer1_pressure = models.IntegerField(default=0, verbose_name='第一层工作压力')
     layer2_pressure = models.IntegerField(default=0, verbose_name='第二层工作压力')
     layer3_pressure = models.IntegerField(default=0, verbose_name='第三层工作压力')

+ 159 - 95
bin/views.py

@@ -21,6 +21,9 @@ from .serializers import LocationGroupListSerializer,LocationGroupPostSerializer
 # 以后添加模块时,只需要在这里添加即可
 from rest_framework.permissions import AllowAny
 from container.models import ContainerListModel,ContainerDetailModel,ContainerOperationModel,TaskModel
+import copy
+import json
+from collections import defaultdict
 logger = logging.getLogger(__name__)
 
 # 库位分配
@@ -208,7 +211,7 @@ class LocationAllocation:
     # fun:get_batch_status: 获取批次状态
     # fun:get_batch: 获取批次
     # fun:get_location_list_remainder: 获取可用库位的c_number列表
-
+    # fun
     # fun:get_location_by_type_remainder: 根据库位类型获取库位
     # fun:get_location_by_type: 第一次入库,根据库位类型获取库位
     # fun:get_location_by_status: 根据库位状态获取库位
@@ -227,7 +230,6 @@ class LocationAllocation:
 
         if not container:
             logger.error(f"托盘 {container_code} 不存在")
-            print(f"托盘 {container_code} 不存在")
             return None
         # 2. 获取关联的批次明细
         container_detail = ContainerDetailModel.objects.filter(
@@ -235,11 +237,9 @@ class LocationAllocation:
             status=1
         ).first()
         if not container_detail:
-            print (f"容器 {container_code} 未组盘")
             logger.error(f"容器 {container_code} 未组盘")
             return None
-        else:
-            print (f"容器 {container_code} 已组盘")
+ 
         batch_container = ContainerDetailModel.objects.filter(
              batch = container_detail.batch.id,
              status = 1
@@ -297,51 +297,7 @@ class LocationAllocation:
             print(f"获取库位组剩余数量失败:{str(e)}")
             return None
 
-    def get_location_type(self,container_code):
-        """
-        :param container_code: 托盘码
-        :return: 库位类型列表
-        """
-        batch = self.get_batch(container_code)
-        if not batch:
-            print(f"类型分配中批次号获取失败!")
-            return None
-        location_solution = alloction_pre.objects.filter(batch_number=batch).first()
-        if not location_solution:
-            # 1. 获取托盘码对应批次号下所有的托盘数目
-            container_number = self.get_pallet_count_by_batch(container_code)
-            print (f"该批次下托盘总数:{container_number}")
-            # 2. 计算每层剩余的库位数目
-            layer_number=self.get_pallet_count_by_batch()
-            # 3. 查看最新库位分配方案
-            location_solution_last = alloction_pre.objects.filter().order_by('-id').first()
-            if not location_solution_last:
-                layer1_pressure =0
-                layer2_pressure =0
-                layer3_pressure =0
-            else:
-                layer1_pressure = location_solution_last.layer1_pressure
-                layer2_pressure = location_solution_last.layer2_pressure
-                layer3_pressure = location_solution_last.layer3_pressure
-            # 4. 根据工作压力和库位类型,使用贪心算法,(AGV只有两个库位,故只给考虑1 2 层工作压力,第三层仅作为1,2层的补充,不考虑压力)获取库位类型列表,完成任务之后更新压力/TODO:需要考虑库位类型和库位数量的关系
-            if layer1_pressure <=layer2_pressure:
-
-            print(f"库位类型:{location_type}")
-            # 5. 保存库位分配方案
-            alloction_pre.objects.create(
-                batch_number=batch,
-                layer1_pressure=layer1_pressure,
-                layer2_pressure=layer2_pressure,
-                layer3_pressure=layer3_pressure,
-
-                location_type=','.join(location_type)
-            )
-            return location_type
-        else:
-            print(f"库位类型:{location_solution.location_type}")
-            return location_solution.location_type.split(",")
 
-        
     @transaction.atomic
     def update_location_container_link(self,location_code,container_code):
         """
@@ -511,7 +467,6 @@ class LocationAllocation:
             logger.error(f"更新批次状态失败:{str(e)}")
             print(f"更新批次状态失败:{str(e)}")
             return False  
-
     def get_batch_status(self,container_code):
         """
         获取批次状态
@@ -636,8 +591,159 @@ class LocationAllocation:
             ordered_groups = location_groups.none()
 
         locations = []
-        locations.extend(ordered_groups.first().location_items.all())  # 假设location_items是关联字段
+        locations.extend(ordered_groups.first().location_items.all()) 
         return locations if locations else None
+       
+    def get_location_type(self, container_code):
+        """
+        智能库位分配核心算法
+        :param container_code: 托盘码
+        :return: 库位类型分配方案
+        """
+        try:
+            batch = self.get_batch(container_code)
+            if not batch:
+                logger.error("批次信息获取失败")
+                return None
+
+            # 检查已有分配方案
+            existing_solution = alloction_pre.objects.filter(batch_number=batch).first()
+            if existing_solution:
+                return existing_solution.layer_pre_type
+
+            # 获取关键参数
+            total_pallets = self.get_pallet_count_by_batch(container_code)
+            layer_capacity = self.get_left_locationGroup_number_by_type()
+            current_pressure = self.get_current_pressure()
+
+            # 测试参数
+            # total_pallets = 3
+            # layer_capacity = [{'T1': 29, 'T2': 14, 'S4': 10, 'T4': 27, 'T5': 27}, {'T1': 0, 'T2': 0, 'S4': 0, 'T4': 0, 'T5': 21}, {'T1': 29, 'T2': 14, 'S4': 10, 'T4': 27, 'T5': 27}]
+            # current_pressure = [1,0,0]
+            print(f"[1]托盘数目: {total_pallets}")
+            print(f"[2]层容量: {layer_capacity}")
+            # print(f"[3]当前压力: {current_pressure}")
+
+            # 定义库位容量表
+            LOCATION_CAPACITY = {'T1':1, 'T2':2, 'S4':4, 'T4':4, 'T5':5}
+            
+            def allocate(remain, path, pressure,layer_capacity_state, depth=0):
+                # 终止条件
+                if remain <= 0:
+                    return [path,pressure]
+                # 深拷贝当前层容量状态
+                new_layer_capacity = copy.deepcopy(layer_capacity_state)
+                # print(f"[2]当前剩余: {new_layer_capacity}")
+                # 压力平衡系数
+                balance_factor = 1.0 - (0.1 * min(depth, 5))
+                # 层选择策略
+                print (f"[3]当前压力: {pressure}")
+                layer_priority = sorted(
+                    [(0, pressure[0]), (1, pressure[1]), (2, pressure[2])],
+                    key=lambda x: (x[1] * balance_factor, x[0])
+                )
+
+                for layer, _ in layer_priority:
+                    # 生成候选库位类型(按效率和容量排序)
+                    # 排序键函数 :
+                    # min(x[1], remain) 计算当前库位类型的容量 c 和剩余数量 remain 中的较小值。
+                    # -min(x[1], remain) 和 -x[1] 都使用了负号,这意味着排序是按降序进行的。
+                    # 首先按 -min(x[1], remain) 排序,即优先选择容量与剩余数量更接近的库位类型。
+                    # 如果有多个库位类型的容量与剩余数量相同,则按 -x[1] 排序,即优先选择容量更大的库位类型。
+                    print(f"[4]当前层: {layer+1}, 剩余: {remain}, 容量状态: {new_layer_capacity[layer]}")
+                    candidates = sorted(
+                        [(t, c) for t, c in LOCATION_CAPACITY.items() 
+                         if new_layer_capacity[layer].get(t,0) > 0],
+                        key=lambda x: (abs(x[1]-remain), -x[1])
+                    )
+
+                    print(f"[4]候选库位类型: {candidates}")
+                    for loc_type, cap in candidates:
+                        # 更新容量状态
+                        updated_capacity = copy.deepcopy(new_layer_capacity)
+                        updated_capacity[layer][loc_type] -= 1  # 占用一个库位组
+                        # 允许适度空间浪费(当剩余<2时)
+                        # effective_cap = min(cap, remain) if (cap - remain) < 2 else cap
+                        effective_cap = min(cap, remain) 
+                        
+                        if effective_cap <= remain:
+                            new_remain = remain - effective_cap
+                            new_pressure = pressure.copy()
+                            new_pressure[layer] += effective_cap  # 按实际存放数计算压力
+                            result = allocate(
+                                new_remain, 
+                                path + [f"{layer+1}_{loc_type}"],
+                                new_pressure,
+                                updated_capacity,
+                                depth + 1
+                            )
+                            if result:
+                                print (f"[5]分配方案: {result}")
+                                return result
+
+                return None
+
+            # 执行分配
+            allocation = allocate(total_pallets, [], [current_pressure[0], current_pressure[1],current_pressure[2]], layer_capacity)
+            
+            if not allocation:
+                logger.error("无法生成有效分配方案")
+                return None
+
+            # 保存分配方案
+            allocation_json = self.divide_solution_by_layer(allocation[0])
+            print(f"[6]分配方案: {allocation_json}")
+            solution = alloction_pre(
+                batch_number=batch,
+                layer_pre_type =allocation_json,
+                layer1_pressure=allocation[1][0],
+                layer2_pressure=allocation[1][1],
+                layer3_pressure=allocation[1][2],
+            )
+          
+            solution.save()
+
+            return [loc.split('_')[1] for loc in allocation[0]]
+
+        except Exception as e:
+            logger.error(f"分配算法异常:{str(e)}")
+            return None
+
+    def divide_solution_by_layer(self, data):
+
+        # 统计所有存在的层级
+        layer_counts = defaultdict(lambda: defaultdict(int))
+        existing_layers = set()
+        
+        for item in data:
+            # 分割层级和类型
+            try:
+                layer, loc_type = item.split('_')
+                layer_num = int(layer)
+                existing_layers.add(layer_num)
+                layer_counts[layer_num][loc_type] += 1
+            except (ValueError, IndexError):
+                continue  # 跳过无效格式的数据
+
+        # 确定最大层级(至少包含1层)
+        max_layer = max(existing_layers) if existing_layers else 1
+
+        # 构建包含所有层级的最终结果
+        final_result = {}
+        for layer in range(1, max_layer + 1):
+            final_result[str(layer)] = dict(layer_counts.get(layer, {}))
+        
+        return json.dumps(final_result, indent=2)
+
+    def get_current_pressure(self):
+        """获取实时工作压力"""
+        last_solution = alloction_pre.objects.order_by('-id').first()
+        return [
+            last_solution.layer1_pressure if last_solution else 0,
+            last_solution.layer2_pressure if last_solution else 0,
+            last_solution.layer3_pressure if last_solution else 0,
+        ]
+
     @transaction.atomic
     def get_location_by_status(self,container_code,start_location,layer):
         """
@@ -647,13 +753,12 @@ class LocationAllocation:
         :param layer: 层数 限定层数
         :return: 库位列表
         """
-        # 1. 获取批次状态 102 为已组盘 103 为部分入库 104 为全部入库
+        # 1. 获取批次状态 1 为已组盘 2 为部分入库 3 为全部入库
         status = self.get_batch_status(container_code)
         # 
         if status == 1:
             # 2. 获取库位组
             print (f"第一次入库")
-           
             location_type_list = self.get_location_type(container_code)
             print(f"库位类型:{location_type_list}")
             location_list = self.get_location_by_type(location_type_list,start_location,layer)
@@ -685,46 +790,5 @@ class LocationAllocation:
             return location_list
             
   
-        """
-        根据库位状态清除库位
-        :param container_code: 托盘码
-        :param start_location: 起始库位 if in1 优先考虑left_priority, if in2 优先考虑right_priority 就是获取库位组列表之后进行排序
-        :param layer: 层数 限定层数
-        :return: 库位列表
-        """
-        # 1. 获取批次状态 102 为已组盘 103 为部分入库 104 为全部入库
-        status = self.get_batch_status(container_code)
-        # 
-        if status == 1:
-            # 2. 获取库位组
-            print (f"第一次入库")
-            container_number = self.get_pallet_count_by_batch(container_code)
-            print (f"该批次下托盘总数:{container_number}")
-            location_type_list = self.get_location_type(container_number)
-            print(f"库位类型:{location_type_list}")
-            location_list = self.get_location_by_type(location_type_list,start_location,layer)
-            print(f"库位列表:{location_list}")
-            location_min_index = self.get_location_list_remainder(location_list)
-            print(f"库位安排到第{location_min_index+1}个库位:{location_list[location_min_index]}")
-            if not location_list[location_min_index]:
-                # 库位已满,返回None
-                return None
-            else:
-                location_code = location_list[location_min_index].location_code
-                self.update_location_status(location_code,'occupied')
-                return location_list[location_min_index]
-        elif status == 2:
-            print(f"部分入库")
-            # 2. 获取库位组
-            location_list = self.get_location_by_type_remainder(self.get_batch(container_code),layer)
-            print(f"库位列表:{location_list}")
-            location_min_index = 1
-            print(f"库位安排到第{location_min_index+1}个库位:{location_list[location_min_index]}")
-            if not location_list[location_min_index]:
-                # 库位已满,返回None
-                return None
-            else:
-                location_code = location_list[location_min_index].location_code
-                self.update_location_status(location_code,'occupied')
-                return location_list[location_min_index]
+      
   

BIN
container/__pycache__/views.cpython-38.pyc


+ 28 - 18
data_base/test_allocation.py

@@ -27,25 +27,35 @@ def main():
         # 从正确的应用导入模型
         from bin.views import LocationAllocation
         
-        container_code = "12345"
-        print(f"开始生成库位,托盘编码:{container_code}")
-       
-        
-        # 调用生成方法
-        # 修改调用部分的代码
+        container_code =["12346", "12345", "1", "2", "7"]
+        # container_code =["1"]
         allocator = LocationAllocation()  # 创建实例
-        number=allocator.get_pallet_count_by_batch(container_code)  # 通过实例调用)
-        if number is None:
-            print("❌ 该批次下无托盘,请检查托盘编码")
-            return
-        print(f"该批次下托盘总数:{number}")
-
-        # 
-        left_number = allocator.get_left_locationGroup_number_by_type()  # 获取每层库位组剩余数量
-        if not left_number:
-            print("❌ 库位组剩余数量获取失败,请检查库位类型")
-            return
-        print(f"库位组剩余数量:{left_number}")
+
+        for code in container_code:  # Iterate through each container code
+            print(f"开始生成库位,托盘编码:{code}")
+            location_type = allocator.get_location_type(code)  # 获取库位类型
+            if not location_type:
+                print("❌ 库位类型获取失败,请检查托盘数量")
+                return
+            print(f"库位类型:{location_type}")
+      
+        
+        # # 调用生成方法
+        # # 修改调用部分的代码
+        # number=allocator.get_pallet_count_by_batch(container_code)  # 通过实例调用)
+        # if number is None:
+        #     print("❌ 该批次下无托盘,请检查托盘编码")
+        #     return
+        # print(f"该批次下托盘总数:{number}")
+
+        # # 
+        # left_number = allocator.get_left_locationGroup_number_by_type()  # 获取每层库位组剩余数量
+        # if not left_number:
+        #     print("❌ 库位组剩余数量获取失败,请检查库位类型")
+        #     return
+        # print(f"库位组剩余数量:{left_number}")
+
+
         
         # location_type = allocator.get_location_type(number)  # 获取库位类型
         # if not location_type:

BIN
db.sqlite3