I am trying to solve a supply chain problem using optimisation and linear programming.
I am not a optimisation expert and I am having trouble formulating a solution using variables, constraints and goals.
It is only a proof of concept, and I have tried Microsoft Solver Foundation and Optano to create a demonstration.
I need to deliver products to customers. I deliver on fixed days. I need to make sure the customer has minimum agreed stock level per day on their shelves.
The customer does a stock check once a week, and tells me the starting stock level for each product for the week. The average daily usage for each product is a known parameter.
So far, so good. I have a solution for this. This next requirement is where I am stuck.
For logistical reasons, the supplier would prefer each delivery to have roughly the same total quantity of products.
The stock level can drop below the usual agreed stock level on exceptional days. As a minimum it must be the average daily usage and by the end of the week the total amount delivered must be the agreed stock level for the week.
I have tried a number of experiments based on articles I have read and examples I have explored. I have not found a way to formulate the constraints and objectives to solve the requirement to equally distribute the quantities delivered each day.
I imagine this is a fairly common supply chain problem, I would really (really) appreciate some guidance?
UPDATE: This is the basic implementation using Microsoft Solver Foundation (solver services API). I am not tied to MSF. It calculates the quantity delivered each day and the amount of stock expected on the shelf at the end of each day.
SolverContext context = SolverContext.GetContext();
Model model = context.CreateModel();
// these are the quantities to be delivered each day
Decision qMon = new Decision(Domain.IntegerNonnegative, "monQuantity");
Decision qTue = new Decision(Domain.IntegerNonnegative, "tueQuantity");
Decision qWed = new Decision(Domain.IntegerNonnegative, "wedQuantity");
Decision qThu = new Decision(Domain.IntegerNonnegative, "thuQuantity");
Decision qFri = new Decision(Domain.IntegerNonnegative, "friQuantity");
Decision qSat = new Decision(Domain.IntegerNonnegative, "satQuantity");
Decision qSun = new Decision(Domain.IntegerNonnegative, "sunQuantity");
// these are the expected quantities to be found on the shelf
//at the end of each day
Decision sMon = new Decision(Domain.IntegerNonnegative, "monStock");
Decision sTue = new Decision(Domain.IntegerNonnegative, "tueStock");
Decision sWed = new Decision(Domain.IntegerNonnegative, "wedStock");
Decision sThu = new Decision(Domain.IntegerNonnegative, "thuStock");
Decision sFri = new Decision(Domain.IntegerNonnegative, "friStock");
Decision sSat = new Decision(Domain.IntegerNonnegative, "satStock");
Decision sSun = new Decision(Domain.IntegerNonnegative, "sunStock");
model.AddDecisions(qMon, qTue, qWed, qThu, qFri, qSat, qSun);
model.AddDecisions(sMon, sTue, sWed, sThu, sFri, sSat, sSun);
// this is the quantity from the stock count
var initialCount = 0;
// this is the average quantity used per day
var averageUsage = 10;
// the stock level must be greater than agreed minimum (150)
model.AddConstraints("stock",
150 <= sMon, 150 <= sTue,
150 <= sWed, 150 <= sThu,
150 <= sFri, 150 <= sSat,
150 <= sSun);
// apply constraint to calculate the stock left on the shelf
// use supply/demand formula
// a special rule for monday using the inital stock take
// the remaining days rely on stock left over from previous day
model.AddConstraint("initialStock",
sMon + averageUsage == qMon + initialCount);
model.AddConstraints("restStock",
sTue + averageUsage == qTue + sMon,
sWed + averageUsage == qWed + sTue,
sThu + averageUsage == qThu + sWed,
sFri + averageUsage == qFri + sThu,
sSat + averageUsage == qSat + sFri,
sSun + averageUsage == qSun + sSat
);
model.AddGoal("minimiseDeliveries",
GoalKind.Minimize,
qMon + qTue + qWed + qThu + qFri + qSat + qSun);
Solution solution = context.Solve(new SimplexDirective());
// a couple of checks that we found an optimal solution
Assert.Equal(SolverQuality.Optimal, solution.Quality);
Assert.True(sSun.GetDouble() >= 150);
I hope this gives more context to my problem.
Some notes:
BaselineDeliver + Over[t] - Under[t]
(with Over[t],Under[t]>=0
). Then add a term in the objective penalty * sum (Over[t]+Under[t])
.