由于我使用 OR-Tools 的经验是使用 c#,而不是 Python,因此我在此处附上 c# 中的示例,说明如何实现对切割次数的约束。它实际上是我在评论 for j in range (k[1]) solver.Add(sum(x[i][j] for i in range(num_orders)) <= number_of_blades) 中提到的约束,除了用 c# 表示法制定。我不知道为什么这对你不起作用。
我扩展了这个概念,包括大卷中的最后一个“剩余”部分可用于订单的情况,即当卷的未使用宽度结果为 0 时。在这种情况下,您将有 7切割,但得到 8 卷可用于订单,因为最后一块与订单所需的宽度相匹配。
作为目标,我尽量减少所需的大卷数。
这是我的c#代码,希望你能把Python做成等价的。
using Google.OrTools.Sat;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
// Implementation for https://stackoverflow.com/questions/68848210/how-to-calculate-array-elements-in-or-tools-solver
namespace SO68841210
{
class Program
{
static void Main(string[] args)
{
try
{
Google.OrTools.Sat.CpModel model = new CpModel();
ORModel myModel = new ORModel();
myModel.initModel(model);
IntVar[] decisionVariables = myModel.decisionVariables;
// Creates a solver and solves the model.
CpSolver solver = new CpSolver();
VarArraySolutionPrinter solutionPrinter = new VarArraySolutionPrinter(myModel.variablesToPrintOut);
solver.SearchAllSolutions(model, solutionPrinter);
Console.WriteLine(String.Format("Number of solutions found: {0}",
solutionPrinter.SolutionCount()));
}
catch (Exception e)
{
Console.WriteLine(e.Message);
Console.WriteLine(e.StackTrace);
throw;
}
Console.WriteLine("OK");
Console.ReadKey();
}
}
class ORModel
{
// Orders
int num_orders;
long[] width;
long[] quantity;
int max_big_rolls;
long parent_width ;
long max_cutters;
// x[i][j] = number of cuts from roll j for order i
// x[i][j] = 3 means that small-roll width specified by i-th order
// must be cut from j-th big roll, 3 times
IntVar[][] x;
// Is roll j used or not?
IntVar[] y;
// widths_for_roll[i][j] = total width cut from big roll j for order i
IntVar[][] widths_for_roll;
// Unused width of each roll; unused_widths[j] = unused width for roll j
IntVar[] unused_widths;
// Number of big rolls actually used
IntVar number_of_big_rolls_used;
// Number of cuts; cuts[j] = number of cuts for roll j
IntVar[] cuts;
// Is the last piece usable for roll j?
IntVar[] last_piece_usable;
public ORModel()
{
// Order data
num_orders = 2;
width = new long[] { 4, 10 };
quantity = new long[] { 10, 2 };
// Solution domain
max_big_rolls = 4;
// Cutting machine data
parent_width = 100;
max_cutters = 7;
}
public void initModel(CpModel model)
{
// Create variable number of small rolls cut from big roll j for order i
x = new IntVar[num_orders][];
for (int i = 0; i < num_orders; i++)
{
x[i] = new IntVar[max_big_rolls];
for (int j = 0; j < max_big_rolls; j++)
{
x[i][j] = model.NewIntVar(0, quantity[i], string.Format("Small rolls cut from big roll {0} for order {1}", j, i));
}
}
// Is the roll used or not?
// y[j] = 1 when the roll is used, otherwise 0
y = new IntVar[max_big_rolls];
for (int j = 0; j < max_big_rolls; j++)
{
y[j] = model.NewBoolVar(string.Format("Big roll {0} is used", j));
}
// Symmettry breaking: use the first rolls first
for (int j = 0; j < (max_big_rolls - 1); j++)
{
model.Add(y[j] >= y[j + 1]);
}
// Constraint: demand fulfillment; the number of small rolls for order i must equal the order quantity
for (int i = 0; i < num_orders; i++)
{
model.Add(LinearExpr.Sum(x[i]) == quantity[i]);
}
// Constraint: Max size limit. The total width of small rolls cut from jth big roll may not exceed big roll's width
// First construct total width of all rolls cut from big rolls per order
// widths_for_roll[i][j] = total width cut from big roll j for order i
widths_for_roll = new IntVar[num_orders][];
for (int i = 0; i < num_orders; i++)
{
widths_for_roll[i] = new IntVar[max_big_rolls];
for (int j = 0; j < max_big_rolls; j++)
{
widths_for_roll[i][j] = model.NewIntVar(0, parent_width, string.Format("Total width cut from roll {0} for order {1}", j, i));
model.Add(widths_for_roll[i][j] == (x[i][j] * width[i]));
}
}
// Unused widths of each roll
unused_widths = new IntVar[max_big_rolls];
for (int j = 0; j < max_big_rolls; j++)
{
unused_widths[j] = model.NewIntVar(0, parent_width, string.Format("Unused width for roll {0}", j));
IntVar[] widths_per_order = new IntVar[num_orders];
for (int i = 0; i < num_orders; i++)
{
widths_per_order[i] = widths_for_roll[i][j];
}
// Unused width is total width of roll minus sum of orders cut from it
model.Add(unused_widths[j] == (parent_width - LinearExpr.Sum(widths_per_order)));
// Total width cut may not exceed width of big roll (unused width must be >= 0).
model.Add(unused_widths[j] >= 0);
}
// Number of big rolls actually used
number_of_big_rolls_used = model.NewIntVar(0, max_big_rolls, "Number of big rolls used");
model.Add(number_of_big_rolls_used == LinearExpr.Sum(y));
// Don't add this constraint for the time being
//if j < k[1] - 1: # k1 = total big rolls
// # total small rolls of i-th order cut from j-th big roll must be >=
// # totall small rolls of i-th order cut from j+1-th big roll
// solver.Add(sum(x[i][j] for i in range(num_orders))
// >= sum(x[i][j + 1] for i in range(num_orders)))
// Maximum number of cuts per roll must be <= number of cutters
// Number of cuts; cuts[j] = number of cuts for roll j
// We need to consider that an order could be fulfilled with no leftover piece. When
// the last piece cut results in unused_widths[j] == 0, then there would be 8 usable small rolls
// but only 7 cuts. If the last piece is not used for an order (unused_widths[j] > 0), there are 7 usable small rolls
// and 7 cuts, and an unused piece left over.
cuts = new IntVar[max_big_rolls];
last_piece_usable = new IntVar[max_big_rolls];
for (int j = 0; j < max_big_rolls; j++)
{
cuts[j] = model.NewIntVar(0, max_cutters, string.Format("Number of cuts for roll {0}", j));
IntVar[] small_rolls_per_order_for_this_big_roll = new IntVar[num_orders];
for (int i = 0; i < num_orders; i++)
{
small_rolls_per_order_for_this_big_roll[i] = x[i][j];
}
last_piece_usable[j] = model.NewBoolVar(string.Format("Last piece usable for roll {0}", j));
// We want last_piece_usable[j] to be equivalent to unused_widths[j] == 0
// See https://developers.google.com/optimization/cp/channeling example:
// Implement b == (a >= 5).
// model.Add(a >= 5).OnlyEnforceIf(b);
// model.Add(a < 5).OnlyEnforceIf(b.Not());
model.Add(unused_widths[j] > 0).OnlyEnforceIf(last_piece_usable[j].Not());
model.Add(unused_widths[j] == 0).OnlyEnforceIf(last_piece_usable[j]);
// If we can use the last small roll for an order, the number of cuts is one less than the number of small rolls
// But if we can't use the last piece, the number of cuts is equal to the number of small rolls from the big roll
model.Add(cuts[j] == (LinearExpr.Sum(small_rolls_per_order_for_this_big_roll) - last_piece_usable[j]));
// The number of cuts is limited by the machine
model.Add(cuts[j] <= max_cutters);
// A roll is considered used only if there are small rolls for orders being cut from it
model.Add(LinearExpr.Sum(small_rolls_per_order_for_this_big_roll) != 0).OnlyEnforceIf(y[j]);
model.Add(LinearExpr.Sum(small_rolls_per_order_for_this_big_roll) == 0).OnlyEnforceIf(y[j].Not());
}
// Objective: minimize the number of big rolls needed
model.Minimize(number_of_big_rolls_used);
}
public IntVar[] variablesToPrintOut
{
get
{
return decisionVariables;
}
}
public IntVar[] decisionVariables
{
get
{
List<IntVar> decisionVariables_ = new List<IntVar>();
for (int i = 0; i < num_orders; i++)
{
for (int j = 0; j < max_big_rolls; j++)
{
decisionVariables_.Add(x[i][j]);
decisionVariables_.Add(widths_for_roll[i][j]);
}
}
for (int j = 0; j < max_big_rolls; j++)
{
decisionVariables_.Add(y[j]);
decisionVariables_.Add(unused_widths[j]);
decisionVariables_.Add(last_piece_usable[j]);
decisionVariables_.Add(cuts[j]);
}
decisionVariables_.Add(number_of_big_rolls_used);
return decisionVariables_.ToArray();
}
}
}
public class VarArraySolutionPrinter : CpSolverSolutionCallback
{
private int solution_count_;
private IntVar[] variables;
public VarArraySolutionPrinter(IntVar[] variables)
{
this.variables = variables;
}
public override void OnSolutionCallback()
{
// using (StreamWriter sw = new StreamWriter(@"C:\temp\GoogleSATSolverExperiments.txt", true, Encoding.UTF8))
using (TextWriter sw = Console.Out)
{
sw.WriteLine(String.Format("Solution #{0}: time = {1:F2} s;",
solution_count_, WallTime()));
foreach (IntVar v in variables)
{
sw.Write(
String.Format(" {0} = {1}\r\n", v.ShortString(), Value(v)));
}
solution_count_++;
sw.WriteLine();
}
if (solution_count_ >= 10)
{
StopSearch();
}
}
public int SolutionCount()
{
return solution_count_;
}
}
}
这是找到的解决方案:
Solution #0: time = 0,01 s;
Small rolls cut from big roll 0 for order 0 = 5
Total width cut from roll 0 for order 0 = 20
Small rolls cut from big roll 1 for order 0 = 5
Total width cut from roll 1 for order 0 = 20
Small rolls cut from big roll 2 for order 0 = 0
Total width cut from roll 2 for order 0 = 0
Small rolls cut from big roll 3 for order 0 = 0
Total width cut from roll 3 for order 0 = 0
Small rolls cut from big roll 0 for order 1 = 0
Total width cut from roll 0 for order 1 = 0
Small rolls cut from big roll 1 for order 1 = 2
Total width cut from roll 1 for order 1 = 20
Small rolls cut from big roll 2 for order 1 = 0
Total width cut from roll 2 for order 1 = 0
Small rolls cut from big roll 3 for order 1 = 0
Total width cut from roll 3 for order 1 = 0
Big roll 0 is used = 1
Unused width for roll 0 = 80
Last piece usable for roll 0 = 0
Number of cuts for roll 0 = 5
Big roll 1 is used = 1
Unused width for roll 1 = 60
Last piece usable for roll 1 = 0
Number of cuts for roll 1 = 7
Big roll 2 is used = 0
Unused width for roll 2 = 100
Last piece usable for roll 2 = 0
Number of cuts for roll 2 = 0
Big roll 3 is used = 0
Unused width for roll 3 = 100
Last piece usable for roll 3 = 0
Number of cuts for roll 3 = 0
Number of big rolls used = 2
Number of solutions found: 1