Search code examples
optimizationmathematical-optimizationlinear-programmingms-solver-foundationoperations-research

Formulate algorithm to evenly distribute delivery quantities using linear programming


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.


Solution

  • Some notes:

    • Microsoft Solver Foundation has been discontinued years ago. If this is more than a one-off model, you may want to look at another tool.
    • Typically we use indexing for a number of related variables (like an array). A whole bunch of scalar variables and equations can become tedious very quickly.
    • Penalizing deviations from a single value can be modeled with slacks. E.g. BaselineDeliver + Over[t] - Under[t] (with Over[t],Under[t]>=0). Then add a term in the objective penalty * sum (Over[t]+Under[t]).
    • It often helps to write down the mathematical optimization model before starting coding. Sometimes starting with a piece of paper instead of a computer screen is a good idea.