Я работаю над алгоритмом оптимизации затрат полиграфической компании путем решения проблемы, с которой она сталкивается. Проблема аналогична задаче о рюкзаке, но с одной особенностью: вместо того, чтобы минимизировать количество контейнеров для соответствия содержимому, нам нужно оптимизировать количество уникальных контейнеров, которые нам нужно использовать. Проблема в том, что полиграфической компании сначала необходимо спроектировать и разработать печатные формы, которые затем используются для печати листов. Печатные листы дешевы, но печатные формы дороги, поэтому компании приходится минимизировать количество форм, которые им необходимо создать, чтобы достичь желаемого результата. Проще говоря, существует 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, но он всегда отвечал общим подходом к задаче о рюкзаке.
Вам нужно как-то выразить стоимость печати дополнительных листов. В противном случае решение тривиально — жадно расфасовывайте «виды» по тарелкам, и в итоге у вас всегда будет ceiling(types_of_stickers/plate_size). Редактировать: Или, может быть, это то, чего вы действительно хотите — можно ли всегда стремиться к этому ceil(types_of_stickers/plate_size), а затем просто оптимизировать соотношение стикеров?
@btilly Да, вы можете выбросить части листа. Я назвал это расточительством, и это нормально, если не слишком много.
@RafałDowgird Мы можем назначить значения для Plate_cost и Sheet_cost, если это поможет алгоритму работать лучше, но я застрял в основной логике того, как мы можем подойти к этой проблеме. Допустим, мы присвоили Plate_cost значение 100, а значение Sheet_cost равное 1 (плита в 100 раз дороже листов), как мы тогда подойдем к этой проблеме?
@RafałDowgird Оптимальным ответом определенно будет потолок (sticker_types/plate_size), но нам нужно учитывать потери, а также конструкцию пластины. Еслиsticker_types равен 11, а Plate_size равен 10, мы должны использовать дополнительные 9 слотов на 2-й пластине для наклеек с большим количеством. Аналогично, если, например, для 5 типов наклеек требуется количество около 200, а для 5 типов наклеек требуется около 400, было бы лучше иметь 2 пластины вместо того, чтобы объединять их в одну и печатать 400 листов (что приводит к большим потерям).
Если вы не собираетесь четко определять такие понятия, как «учет потерь», никто не сможет ответить на ваш вопрос. Вы можете оптимизировать вывод только одной функции, поэтому вам нужно решить, что это за функция.
Каковы разумные размеры количества различных наклеек и количества листов?
@MattTimmermans Если мы определим затраты как на пластины, так и на листы, мы сможем оптимизировать затраты вместо оптимизации только одной вещи. В противном случае ответ был бы тривиальным, как указано в комментарии выше. Оптимизация только пластин приведет к потолку (типам/размерам), а оптимизация только листов — это классическая задача о рюкзаке. Проблема заключается в том, чтобы определить базовый подход, который следует использовать, даже если мы учитываем обе затраты.
@ChristianSloper Что касается проблемы, которую я пытаюсь решить, компания обычно выполняет это для 20-30 клиентов одновременно, что означает 20-30 различных типов наклеек. Количество листов зависит от Plate_size. Если Plate_size равен 10, это означает, что на каждом листе будет 10 наклеек, и мы можем разделить общее количество наклеек, необходимых на размер пластины, чтобы выяснить это. Это также зависит от потерь из-за конструкции пластины, поэтому, если, например, на одной пластине 10 наклеек одного типа, а нам нужно сделать только 11 наклеек, нам все равно понадобится 2 листа, даже если это приведет к потере 9 наклеек.
Рассматривали ли вы возможность использования библиотеки целочисленного программирования? например Ortools или что-то подобное?
Кажется, что генетический алгоритм может дать достаточно приличную оценку. В качестве входных данных у вас есть стикерыPerPlate, CostPerPlate, CostPerSheet, StickQuantityArray. Вам потребуются некоторые пользовательские функции преобразования (с ограничениями, например, каждый стикерQuantityIndex должен появиться хотя бы один раз). Вам нужно будет запустить генетический алгоритм с различным количеством выходных пластин, возможно, Ceil(Len(StickerQuantityIndex/StickersPerPlate), чтобы удвоить это число. Выходными данными будут пластины с индексами, представляющими типы наклеек и количество листов, которые делает каждая пластина. функция оценки...
.. Функция оценки должна будет определить оптимальное количество листов для каждой пластины, чтобы убедиться, что все количества стикеров удовлетворены.
@ChristianSloper Я ничего не знаю о том, что такое целочисленное программирование и почему оно используется, мне придется изучить его, чтобы увидеть, к чему оно меня приведет





Ее можно выразить как задачу целочисленного программирования, которую необходимо решить с помощью таких инструментов, как OR-Tools, хотя ограничения LP такие:
означают две вещи: 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-сложна, так что, вероятно, это лучшее, что можно сделать.
Ваш ответ выглядит довольно убедительным, насколько я мог понять. Прежде чем я смогу реализовать решение, мне придется изучить целочисленное программирование и инструменты ИЛИ, но спасибо, что нашли на это время. Высоко оценено!
Как заставить модель учитывать дополнительные наклейки, которые она печатает, из-за ограничения печати одинакового количества наклеек в каждом слоте? Я добавил дополнительное ограничение, согласно которому количество отпечатков не должно превышать требуемое_count+max_wastage и не должно быть меньше требуемого_count, но из-за этого модель не оптимизирует количество отпечатков, даже если это может понравиться в вашем примере, иногда это дает B(550) и C(550), поскольку допускается потеря 10%.
Я думаю, что самый простой способ — через целевую функцию. Просто минимизируйте сумму стоимости используемых пластин (умноженную на стоимость пластины) плюс дополнительную стоимость напечатанных листов. Это не учитывает явные потери, но если вы минимизируете количество напечатанных листов, вы минимизируете потери.
@RafałDowgird Имеет смысл. Вместо того, чтобы минимизировать пластины, я должен минимизировать стоимость, т. е. Plate_countplate_cost +sheet_countsheet_cost.
Да, для этого вам, вероятно, потребуется ввести еще один набор переменных для каждой пластины, чтобы отразить количество листов, которое является максимальным значением v-переменных для каждой пластины.
Чтобы помочь другим, кто ищет код для решения подобной проблемы, мне удалось реализовать решение этой проблемы благодаря принятому ответу. Вот код.
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.
Можно ли выбрасывать части листов? Или нужно точно сложить?