Алгоритм минимизации количества печатных форм при печати листов наклеек

Я работаю над алгоритмом оптимизации затрат полиграфической компании путем решения проблемы, с которой она сталкивается. Проблема аналогична задаче о рюкзаке, но с одной особенностью: вместо того, чтобы минимизировать количество контейнеров для соответствия содержимому, нам нужно оптимизировать количество уникальных контейнеров, которые нам нужно использовать. Проблема в том, что полиграфической компании сначала необходимо спроектировать и разработать печатные формы, которые затем используются для печати листов. Печатные листы дешевы, но печатные формы дороги, поэтому компании приходится минимизировать количество форм, которые им необходимо создать, чтобы достичь желаемого результата. Проще говоря, существует X различных типов наклеек, которые необходимо напечатать в определенном количестве. На каждой печатной форме можно разместить N наклеек. Нам нужен алгоритм, который сможет рассчитать минимальное количество пластин, которые нам понадобятся для печати заданных наклеек (печатную форму можно использовать бесконечное количество раз для печати листа). Простой неоптимизированный подход к этому заключается в создании X печатных форм и размещении N наклеек на каждой пластине, т. е. если размер пластины равен 10 и существует 2 разных типа наклеек, и нам нужно напечатать A (200) и B (300), мы можем просто создать 2 пластины: первую с 10 наклейками A и вторую с 10 наклейками B, а затем напечатать их в соответствии с необходимым количеством (20 листов для пластины 1 и 30 листов для пластины 2). Однако лучшим подходом было бы использовать 1 пластину с 4 наклейками A и 6 наклейками B, а затем напечатать 50 листов для достижения необходимого количества.

When plate size = 10 and required quantities = {A: 200, B: 300}

Unoptimized Approach:

 _________               _________
|A|A|A|A|A| x 20        |B|B|B|B|B| x 30
|A|A|A|A|A|             |B|B|B|B|B|
 ---------               ---------

Optimized Approach:
 _________
|A|A|A|A|B| x 50
|B|B|B|B|B|
 ---------

Любая помощь будет принята с благодарностью!

Мой первый грубый подход к этой проблеме заключался в том, чтобы отсортировать наклейки по их необходимому количеству, затем вычислить их соотношения и назначить их печатной форме на основе их соотношений, а затем печатать их до тех пор, пока наименьшее количество не станет равным 0 или отрицательным (печать дополнительных листов может быть затруднительной задачей). потери, но в небольших количествах это не так уж и важно). Однако это не дало оптимального ответа, и поэтому теперь я застрял. Пытался найти похожие проблемы, но не нашел. Пытался задать вопрос LLM, но он всегда отвечал общим подходом к задаче о рюкзаке.

Можно ли выбрасывать части листов? Или нужно точно сложить?

btilly 10.06.2024 10:43

Вам нужно как-то выразить стоимость печати дополнительных листов. В противном случае решение тривиально — жадно расфасовывайте «виды» по тарелкам, и в итоге у вас всегда будет ceiling(types_of_stickers/plate_size). Редактировать: Или, может быть, это то, чего вы действительно хотите — можно ли всегда стремиться к этому ceil(types_of_stickers/plate_size), а затем просто оптимизировать соотношение стикеров?

Rafał Dowgird 10.06.2024 11:18

@btilly Да, вы можете выбросить части листа. Я назвал это расточительством, и это нормально, если не слишком много.

Huzaifa Imran 10.06.2024 11:22

@RafałDowgird Мы можем назначить значения для Plate_cost и Sheet_cost, если это поможет алгоритму работать лучше, но я застрял в основной логике того, как мы можем подойти к этой проблеме. Допустим, мы присвоили Plate_cost значение 100, а значение Sheet_cost равное 1 (плита в 100 раз дороже листов), как мы тогда подойдем к этой проблеме?

Huzaifa Imran 10.06.2024 11:22

@RafałDowgird Оптимальным ответом определенно будет потолок (sticker_types/plate_size), но нам нужно учитывать потери, а также конструкцию пластины. Еслиsticker_types равен 11, а Plate_size равен 10, мы должны использовать дополнительные 9 слотов на 2-й пластине для наклеек с большим количеством. Аналогично, если, например, для 5 типов наклеек требуется количество около 200, а для 5 типов наклеек требуется около 400, было бы лучше иметь 2 пластины вместо того, чтобы объединять их в одну и печатать 400 листов (что приводит к большим потерям).

Huzaifa Imran 10.06.2024 11:35

Если вы не собираетесь четко определять такие понятия, как «учет потерь», никто не сможет ответить на ваш вопрос. Вы можете оптимизировать вывод только одной функции, поэтому вам нужно решить, что это за функция.

Matt Timmermans 10.06.2024 18:43

Каковы разумные размеры количества различных наклеек и количества листов?

Christian Sloper 10.06.2024 19:55

@MattTimmermans Если мы определим затраты как на пластины, так и на листы, мы сможем оптимизировать затраты вместо оптимизации только одной вещи. В противном случае ответ был бы тривиальным, как указано в комментарии выше. Оптимизация только пластин приведет к потолку (типам/размерам), а оптимизация только листов — это классическая задача о рюкзаке. Проблема заключается в том, чтобы определить базовый подход, который следует использовать, даже если мы учитываем обе затраты.

Huzaifa Imran 11.06.2024 08:35

@ChristianSloper Что касается проблемы, которую я пытаюсь решить, компания обычно выполняет это для 20-30 клиентов одновременно, что означает 20-30 различных типов наклеек. Количество листов зависит от Plate_size. Если Plate_size равен 10, это означает, что на каждом листе будет 10 наклеек, и мы можем разделить общее количество наклеек, необходимых на размер пластины, чтобы выяснить это. Это также зависит от потерь из-за конструкции пластины, поэтому, если, например, на одной пластине 10 наклеек одного типа, а нам нужно сделать только 11 наклеек, нам все равно понадобится 2 листа, даже если это приведет к потере 9 наклеек.

Huzaifa Imran 11.06.2024 08:39

Рассматривали ли вы возможность использования библиотеки целочисленного программирования? например Ortools или что-то подобное?

Christian Sloper 11.06.2024 20:18

Кажется, что генетический алгоритм может дать достаточно приличную оценку. В качестве входных данных у вас есть стикерыPerPlate, CostPerPlate, CostPerSheet, StickQuantityArray. Вам потребуются некоторые пользовательские функции преобразования (с ограничениями, например, каждый стикерQuantityIndex должен появиться хотя бы один раз). Вам нужно будет запустить генетический алгоритм с различным количеством выходных пластин, возможно, Ceil(Len(StickerQuantityIndex/StickersPerPlate), чтобы удвоить это число. Выходными данными будут пластины с индексами, представляющими типы наклеек и количество листов, которые делает каждая пластина. функция оценки...

Louis Ricci 11.06.2024 22:38

.. Функция оценки должна будет определить оптимальное количество листов для каждой пластины, чтобы убедиться, что все количества стикеров удовлетворены.

Louis Ricci 11.06.2024 22:40

@ChristianSloper Я ничего не знаю о том, что такое целочисленное программирование и почему оно используется, мне придется изучить его, чтобы увидеть, к чему оно меня приведет

Huzaifa Imran 19.06.2024 17:09
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
13
122
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Ее можно выразить как задачу целочисленного программирования, которую необходимо решить с помощью таких инструментов, как OR-Tools, хотя ограничения LP такие:

  • фиксированное количество переменных (F)
  • без умножения переменных (М)

означают две вещи: 1. Вы должны предполагать максимальное количество пластин из-за (F) и 2. Вы не можете явно смоделировать «количество листов, напечатанных с пластины», чтобы умножить ее на «количество прорезей на пластине с типом наклейки t», потому что из (М).

Хитрость в моделировании этого заключается в том, чтобы иметь отдельную переменную для каждой тройки {plate, slot, kind of sticker} со значением v, означающим «Мне нужны v наклейки определенного типа, напечатанные из этого слота. Например, оптимизированный подход из вашего вопроса будет следующим:

Slot:   0  1  2  3  4  5  6  7  8  9
A      50 50 50 50  0  0  0  0  0  0
B       0  0  0  0 50 50 50 50 50 50

и пластине придется напечатать 50 листов, потому что это максимум для отдельных слотов.

Очевидно, что это требует некоторых ограничений, чтобы избежать попыток печати нескольких разных типов наклеек из одного слота. К счастью, это можно выразить в целочисленном программировании.

Также в реальности вы печатаете одинаковое количество наклеек из каждого слота. К счастью, это просто означает, что для конкретной пластины вам нужно распечатать m листов, где m — это максимум переменных из всех слотов — хотя функция стоимости должна это учитывать.

Теперь о том, как предотвратить использование одного и того же слота для разных типов наклеек. Первая часть — ввести набор теневых целочисленных переменных, по одной для каждого v, каждая из которых имеет значение в диапазоне [0..1].

Теперь, если мы добавим набор ограничений, таких как v[p,s,k] < b[p,s,k] * 1000000, мы выразим, что конкретный v может быть ненулевым тогда и только тогда, когда соответствующий b равен 1. Теперь мы можем просто добавить ограничение для каждого слота, чтобы сумма b не превышала 1, что, в свою очередь, позволяет сделать только один v отличным от нуля для каждого слота на каждой пластине.

Очевидно, что также должны быть ограничения, выражающие, что сумма v по всем пластинам и слотам для каждого типа наклеек дает необходимое количество наклеек, но это просто.

Для целевой функции нам нужно количество использованных тарелок, которое снова можно решить с помощью переменной «логического» типа, по одной для каждой тарелки.

Я закодировал PoC для этого с помощью OR-инструментов, и это, кажется, работает, хотя для создания трех пластин и [20000, 500, 500] для наклеек требуется 8 секунд, чтобы создать первую пластину с напечатанным A * 10 1600 раз, вторую с напечатанными A * 8, B, C. 500 раз. В более крупных случаях кажется, что он быстро сходится к оптимальному или близкому к оптимальному решению, но «доказательство» его оптимальности занимает целую вечность.

Я почти уверен, что проблема NP-сложна, так что, вероятно, это лучшее, что можно сделать.

btilly 18.06.2024 19:32

Ваш ответ выглядит довольно убедительным, насколько я мог понять. Прежде чем я смогу реализовать решение, мне придется изучить целочисленное программирование и инструменты ИЛИ, но спасибо, что нашли на это время. Высоко оценено!

Huzaifa Imran 19.06.2024 17:00

Как заставить модель учитывать дополнительные наклейки, которые она печатает, из-за ограничения печати одинакового количества наклеек в каждом слоте? Я добавил дополнительное ограничение, согласно которому количество отпечатков не должно превышать требуемое_count+max_wastage и не должно быть меньше требуемого_count, но из-за этого модель не оптимизирует количество отпечатков, даже если это может понравиться в вашем примере, иногда это дает B(550) и C(550), поскольку допускается потеря 10%.

Huzaifa Imran 20.06.2024 12:18

Я думаю, что самый простой способ — через целевую функцию. Просто минимизируйте сумму стоимости используемых пластин (умноженную на стоимость пластины) плюс дополнительную стоимость напечатанных листов. Это не учитывает явные потери, но если вы минимизируете количество напечатанных листов, вы минимизируете потери.

Rafał Dowgird 20.06.2024 13:49

@RafałDowgird Имеет смысл. Вместо того, чтобы минимизировать пластины, я должен минимизировать стоимость, т. е. Plate_countplate_cost +sheet_countsheet_cost.

Huzaifa Imran 20.06.2024 15:44

Да, для этого вам, вероятно, потребуется ввести еще один набор переменных для каждой пластины, чтобы отразить количество листов, которое является максимальным значением v-переменных для каждой пластины.

Rafał Dowgird 20.06.2024 16:25

Чтобы помочь другим, кто ищет код для решения подобной проблемы, мне удалось реализовать решение этой проблемы благодаря принятому ответу. Вот код.

from ortools.linear_solver import pywraplp


def solve_sticker_problem(
    num_slots,
    required_stickers,
    max_sheets_per_plate,
    max_wastage_ratio,
):
    # Initialize the solver
    solver = pywraplp.Solver.CreateSolver("SAT")

    if not solver:
        return None

    num_sticker_types = len(required_stickers)
    max_plates = (
        num_sticker_types  # We will presume max plates to be equal to kinds of stickers
    )

    # Decision variables
    # v[p, s, t] - number of stickers of type t in slot s on plate p
    v = {}
    # b[p, s, t] - binary variable if slot s on plate p is used for sticker type t
    b = {}
    # u[p] - binary variable if plate p is used
    u = [solver.IntVar(0, 1, f"u[{p}]") for p in range(max_plates)]

    for p in range(max_plates):
        for s in range(num_slots):
            for t in range(num_sticker_types):
                v[p, s, t] = solver.IntVar(0, solver.infinity(), f"v[{p},{s},{t}]")
                b[p, s, t] = solver.IntVar(0, 1, f"b[{p},{s},{t}]")

    # Constraints
    # 1. Each slot on each plate can be used for at most one type of sticker
    for p in range(max_plates):
        for s in range(num_slots):
            solver.Add(sum(b[p, s, t] for t in range(num_sticker_types)) == 1)
            for t in range(num_sticker_types):
                solver.Add(v[p, s, t] <= b[p, s, t] * max_sheets_per_plate)

    # 2. Sum of stickers of each type over all plates and slots should meet the required count
    for t in range(num_sticker_types):
        sumOfStickers = sum(
            v[p, s, t] for p in range(max_plates) for s in range(num_slots)
        )
        solver.Add(sumOfStickers >= required_stickers[t])
        solver.Add(
            sumOfStickers
            <= required_stickers[t] + (required_stickers[t] * max_wastage_ratio)
        )

    # 3. If a plate is used, it should be counted
    for p in range(max_plates):
        for t in range(num_sticker_types):
            for s in range(num_slots):
                solver.Add(v[p, s, t] <= u[p] * max_sheets_per_plate)

    # 4. Use same number of stickers for every slot in a plate
    for p in range(max_plates):
        for s in range(num_slots - 1):
            solver.Add(
                sum(v[p, s, t] for t in range(num_sticker_types))
                == sum(v[p, s + 1, t] for t in range(num_sticker_types))
            )

    # Objective: Minimize the number of plates used
    solver.Minimize(solver.Sum(u[p] for p in range(max_plates)))

    # Solve the problem
    status = solver.Solve()

    if status == pywraplp.Solver.OPTIMAL:
        print("Solution:")
        for p in range(max_plates):
            if u[p].solution_value() == 1:
                prints = 0
                slots: dict[str, int] = {}
                for s in range(num_slots):
                    for t in range(num_sticker_types):
                        if b[p, s, t].solution_value() == 1:
                            slots[f"Type{t}"] = slots.get(f"Type{t}", 0) + 1
                            prints = v[p, s, t].solution_value()
                print(f"Plate {p} ({prints} sheets): {slots}")


# Example usage
num_slots = 10  # Number of slots per plate
required_stickers = [20000, 500, 500]  # Number of required stickers for each type
max_sheets_per_plate = 1000000  # Maximum sheets you can print from a plate
max_waste_percent = 0  # Percentage of max allowed wastage with respect to required stickers. Set to 0 to avoid wastage


solve_sticker_problem(
    num_slots,
    required_stickers,
    max_sheets_per_plate,
    max_waste_percent / 100,
)

Выход:

Solution:
Plate 1 (500.0 sheets): {'Type2': 1, 'Type0': 8, 'Type1': 1}
Plate 2 (1600.0 sheets): {'Type0': 10}

Примечание. Время увеличивается экспоненциально по мере увеличения количества типов наклеек. Вы можете увеличить потери, чтобы сократить затраченное время. Любые оптимизации, которые можно сделать здесь, приветствуются, поскольку я новичок в целочисленном программировании и инструментах OR.

Другие вопросы по теме