learn-prescriptive-analytics-with-phoebe / Practitioner session 1 of 10
Learn Prescriptive Analytics with Phoebe · Practitioner session 1 of 10

Writing a problem down

A solver answers Ravensworth's week in a few thousandths of a second. Everything difficult happens before that call, in the statement: the quantities the mill is free to set and the units they are in, the single number being made big, and the ceilings, each written in the unit of the thing it limits. Tonight builds that statement from nothing to the three arrays a solver will accept, on a mill whose numbers were measured rather than argued for.

🟡 Foundations Anyone who will write the model Comfortable reading a little JavaScript 45 min
0-3 · Setup 3-17 · Three parts 17-29 · c, A and b 29-45 · Build-along and Q&A
Part 0

The statement is the work

Ravensworth Mills blends and bags three feed products, Standard, Dairy and Show. One mill, one blending line, one protein store, shared by all three, so every tonne of one is hours and protein the other two will not get. Margins are 38, 54 and 71 a tonne. Milling, blending and premium protein each have a rate per tonne and a fixed amount available in the week. That is the whole mill, and none of it is a model yet. A model is those facts written in a form that has exactly one meaning.

Live = presented in the 45 minutes Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with tonight. Ravensworth written as c, A and b, typed by you, solved in the page. Three habits that catch most of the errors people make in their first year: give every variable a unit, refuse an objective until somebody names which number, and check that each constraint row lands in its own ceiling's unit. And one uncomfortable demonstration: a model with a row left out returns a confident optimal plan and says nothing at all about the hole.

LiveThree parts, and only three4 min▶

Every linear optimisation problem, however large, is three objects. Decision variables, the quantities you are free to set, each with a unit. An objective, one number built from those variables, to be made as large or as small as possible. Constraints, each a weighted sum of the variables held below a ceiling. Write those three and the problem is answerable by arithmetic. Leave any one of them unwritten and it is answerable only by argument.

The uncomfortable part is that the three are not equally hard. The variables are usually obvious. The constraints take a week of asking people for numbers they have never had to state. The objective takes a decision that somebody senior has been avoiding, which is why it is often the last thing written and the first thing to go wrong.

Real world

Nothing tonight builds or judges a demand forecast. Ravensworth has one and believes it. The question here starts one step later: given what is coming, and given what the plant can physically do, how many tonnes of each product should the week run.

Self-studyThe words, and where each one is worked4 min▶
  • Decision variable. A quantity you set, with a unit. Tonight: tonnes of Standard, tonnes of Dairy, tonnes of Show.
  • Objective. One number to maximise or minimise. Here, total margin, from c = [38, 54, 71].
  • Constraint. A rate per unit of each variable against a fixed ceiling. Three of them tonight, forming A and b.
  • Feasible region. Every plan that breaks no constraint. Its geometry, and why the best plan sits on a corner of it, is session 2.
  • Simplex. The method that walks those corners. Session 3, at the level of the tableau.
  • Shadow price. What one more unit of a ceiling would be worth, and how far that rate holds. Session 4.
  • Integrality. The mill bags whole tonnes. Rounding the continuous answer costs 60.00 and points the wrong way. Session 5.
  • Formulation error. Units, big-M, and the cases where the model rather than the data is the bug. Session 6.
  • Monte Carlo over a plan. Drawing uncertain demand and re-running the plan, sessions 7 and 8. Not posterior sampling, which belongs to the Bayesian course.
  • Solver verification. How this page's own solver was wrong twice while passing its tests, session 9, and the full bench in session 10.
Part 1 · covers variables, objective, constraints

What a tonne of Dairy actually does

18 min live

Start with one unit of one product and follow it. A tonne of Dairy spends 1.5 milling hours, 0.9 blending hours and 0.4 tonnes of premium protein, and it earns 54. Four numbers, four different units, and the whole formulation is that pattern repeated for each product. Get the pattern right once and the matrix writes itself; get a unit wrong once and every number downstream is wrong in a way no error message will mention.

One tonne of Dairy, and every row it touches DECISION VARIABLE 1 tonne of Dairy a quantity the mill sets units: tonnes Milling hours 1.5 hours for each tonne of Dairy 131 hours in the week A[0], b[0] row 1 of the matrix Blending hours 0.9 hours for each tonne of Dairy 77 hours in the week A[1], b[1] row 2 of the matrix Premium protein 0.4 tonnes for each tonne of Dairy 29 tonnes in the store A[2], b[2] row 3 of the matrix And it earns 54, which is the one number the plan is trying to make big. Four rows, four units: pounds, milling hours, blending hours, tonnes of protein.
🔍 Click to zoom - the marks on the bars are one tonne against a week's ceiling. The plan is made of thousands of those.
LiveA variable without a unit is not a variable4 min▶

Write x1 and you have written nothing. Write "tonnes of Standard produced this week" and you have made four decisions at once: the thing being counted, the unit it is counted in, the period it covers, and whether it is production or sales. Every one of those will matter later, and every one of them is impossible to recover from the symbol.

The test is whether somebody who was not in the room could check a number against the plant. "Tonnes of Standard produced this week" can be checked against the weighbridge. "Standard output" cannot, because nobody can tell whether it is bags, tonnes, pallets or orders. The variable list is the place where a model stops being private.

Ravensworth's three are deliberately dull: tonnes of Standard, tonnes of Dairy, tonnes of Show, all produced in one week, all continuous for now. The mill bags whole tonnes, which is a real restriction and a real change to the answer, and it is held back to session 5 so that the effect can be measured rather than assumed.

Live"Maximise profit" is not an objective yet5 min▶

Somebody has to say which profit. Gross margin before fixed costs, contribution after variable packaging, margin net of a transfer price, margin including the penalty for a missed Show order. Each is defensible, each gives a different set of c, and each can produce a different plan. A brief that says "maximise profit" has not specified the problem, and the person who picks up the work will pick one of those definitions silently, usually the one that is easiest to get out of the finance system.

The second half of the same discipline is that the objective must be one number. Ravensworth would like high margin, full shelves, short lead times and a happy Show customer. Four wishes are not an objective, because the moment two of them pull apart there is no rule for which gives way. Collapsing them into one number forces an exchange rate into the open before the week starts, which is uncomfortable and is exactly where the argument belongs.

Here c = [38, 54, 71] is margin per tonne, and the objective is 38 S + 54 D + 71 W. Notice what it is not. It is not a ranking of the products, it is a rate at which each tonne converts into the one number. The ranking that matters turns out to be a different calculation entirely, and it does not agree with this one.

LiveEvery row in the unit of its own ceiling4 min▶

A constraint row is a sentence with a unit on both sides. 1.1 S + 1.5 D + 2.3 W <= 131 reads: hours per tonne times tonnes gives hours, and hours must stay under 131 hours. If the left side comes out in hours and the right side is in shifts, the row is wrong by whatever a shift is, and the solver will accept it happily and answer a different mill.

This is the most common error in a first formulation and it has no symptom. There is no type checker between you and the matrix. The habit that catches it is to write the unit next to every row before writing any code, then read the row aloud with the units in it: "one point one hours per tonne, times tonnes of Standard, plus ... , at most one hundred and thirty one hours". A row that cannot be read aloud that way is not finished.

Two more things belong in a row and are easy to forget. The ceiling has to be for the same period as the variables, so a weekly plan needs weekly capacity rather than annual capacity divided by fifty two in somebody's head. And every product that consumes the resource needs a coefficient, including the ones that consume very little, because a zero you meant and a zero you forgot look identical in the matrix.

Self-studyWhere serious people disagree5 min▶

There is an unresolved argument about how much to write down in the first version, and it is not a beginner's argument.

One camp writes the smallest honest model that could possibly be wrong. Three variables, three rows, solve it, show somebody the answer and let them tell you what is missing. Every extra row is a number somebody has to defend, and a model nobody can defend line by line is a model nobody will act on. On this view the first answer's job is to be refuted quickly, and a small model gets refuted faster than a big one.

The other camp writes every limit it knows about, on the grounds that an omitted constraint is the one error class with no symptom. A wrong coefficient produces a strange answer that somebody queries. A missing row produces a perfectly reasonable answer that is simply not achievable, and non-binding rows cost nothing to carry: if a limit turns out not to matter, the solver reports slack on it and the plan is unchanged.

Both are right about the failure they are avoiding, and they are avoiding different failures. The minimal model risks a confident plan that the floor cannot run. The maximal model risks a plan built on ceilings that were guessed, which is the same confidence with a different cause. Knowing which risk you are taking is the actual skill; picking a side once and for all is not.

Part 2 · covers c, A and b

From four sentences to three arrays

16 min live

The solver in this page takes a problem in one fixed shape: maximise c . x subject to A x <= b with x >= 0. That is not a limitation of this particular solver so much as the standard form the whole method is built on. Translating the mill into it is mechanical once the sentences are written, and the mechanical step is where the ordering errors live.

The same four facts, written twice WRITTEN AS A SENTENCE WRITTEN AS THE SOLVER TAKES IT Maximise total margin 38 S + 54 D + 71 W Milling cannot exceed 131 hours 1.1 S + 1.5 D + 2.3 W <= 131 Blending cannot exceed 77 hours 0.6 S + 0.9 D + 1.7 W <= 77 Protein cannot exceed 29 tonnes 0.1 S + 0.4 D + 1.0 W <= 29 Nothing can be negative S, D, W >= 0, which the solver assumes for you var c = [38, 54, 71]; var A = [[1.1, 1.5, 2.3], [0.6, 0.9, 1.7], [0.1, 0.4, 1.0]]; var b = [131, 77, 29]; var r = OPT.solveLP(c, A, b); r.status r.x r.objective margin a tonne milling hours blending hours protein tonnes the ceilings "optimal" tonnes of each total margin Three products across, three constraints down. The columns of A are the products, in the same order as c. Swap two of them and every array still has the right shape, the solver still says optimal, and the answer is a different mill.
🔍 Click to zoom - the shape of the arrays is checkable by a machine. The meaning of the columns is not.
The whole statement, in the page's own solver
var c = [38, 54, 71];                 // margin a tonne: Standard, Dairy, Show
var A = [[1.1, 1.5, 2.3],             // milling hours a tonne
         [0.6, 0.9, 1.7],             // blending hours a tonne
         [0.1, 0.4, 1.0]];            // premium protein, tonnes a tonne
var b = [131, 77, 29];                // available in the week

var r = OPT.solveLP(c, A, b);

r.status;      // "optimal", or "unbounded", or "needs-phase-one"
r.x;           // tonnes of Standard, Dairy, Show
r.objective;   // total margin for that plan
r.slack;       // how much of each ceiling the plan leaves unused
r.binding;     // true where the plan has used a ceiling up
r.shadow;      // what one more unit of each ceiling is worth (session 4)

OPT.selfTest().ok;   // the solver's own hand-checkable cases, run here
Live The real solver, running in this page. Leave a constraint out and watch the answer stay confident.

Rows to include in the statement

...

...
A row left out does not leave a gap in the answer THE PROBLEM AS WRITTEN, TWO ROWS Milling hours Blending hours status: "optimal" A plan, a margin, a slack column and three prices. Everything the solver owes you. same plan THE MILL AS IT IS, THREE ROWS Milling hours Premium protein, never written down the mark is the ceiling the store actually has CANNOT BE RUN Discovered at the store, on the day, by a person. A missing constraint has no symptom in the output. Nothing is blank, nothing is flagged, the status still reads optimal, and the slack column shows only the rows you wrote. A wrong coefficient at least produces a strange answer somebody queries. An absent row produces a reasonable one.
🔍 Click to zoom - the amber bar is the row nobody wrote. The solver had no way to know it existed.
LiveReading the reply4 min▶

A solved problem hands back more than a plan. r.x is the quantities and r.objective is what they earn, and those are the two things anyone asks for. The other three are where the understanding is. r.slack says how much of each ceiling went unused. r.binding says which ceilings the plan ran out of. r.shadow says what one more unit of each ceiling would be worth.

Check r.status first, every time, before reading anything else. An unbounded problem has no best plan, because some variable can grow forever without breaking a row, and that almost always means a constraint is missing rather than that the mill has found free money. Try it in the box above by unchecking all three rows.

LiveWhat the checkbox demonstration is really showing4 min▶

Uncheck premium protein and solve. The status is optimal. The plan is specific. The margin is higher than the true answer, which is exactly what makes it attractive in a meeting. Nothing about the output hints that a whole row of the mill was never mentioned, because the output can only describe the problem it was given.

Then look at the second table, which takes that plan and measures it against all three of the mill's real ceilings. That comparison is the only thing that catches the omission, and notice that it needs a fact from outside the model. The model cannot audit itself for a row nobody wrote. Somebody who knows the plant has to read the variable list and the row list and say what is missing, and that review is not a formality.

Self-studyNaming, ordering and the things that stay in your head4 min▶

The arrays carry numbers and lose meaning. c[1] = 54 knows nothing about Dairy, and A[2] knows nothing about the protein store. Every mistake of the swapped-column kind survives every shape check you can write, because the shapes are still right.

The cheap defence is to keep the names beside the numbers and build the arrays from them, so the order is written once: a list of product names in the same order as c, a list of constraint names in the same order as the rows of A. Then print the plan with names attached and read it back to someone who knows the plant. "Thirty three tonnes of Standard and no Show at all" is a sentence a plant manager can react to. A vector of three numbers is not.

The second defence is a unit column. Keep a table with one row per constraint holding the name, the unit of the coefficients, the unit of the ceiling, and where the ceiling came from. It takes ten minutes and it is the document that survives you leaving the project.

Part 3 · build-along

Write one down, then break it on purpose

22 min hands on

Six steps, using the live box above and a page console. The aim is to leave able to turn a described operation into c, A and b without hesitating, and to distrust your own first statement in the right places.

Type the mill in, from the sentences only

Open the console on this page and build c, A and b from the four sentences in the diagram, without copying the code panel. Call OPT.solveLP(c, A, b). Check r.status before you look at anything else, then read r.x and r.objective.

Say every row aloud with its units in it

"One point one hours per tonne times tonnes of Standard, plus one point five times tonnes of Dairy, plus two point three times tonnes of Show, at most one hundred and thirty one hours." Do all three. Any row you cannot say that way has a unit problem you have not found yet.

Swap two columns and solve again

Exchange the Dairy and Show coefficients in all three rows of A, leaving c alone. Solve. Note that the status is still optimal, the shapes are still right, and the plan is different. Write down what would have told you, other than knowing.

Drop a row and follow the consequence

In the live box, uncheck premium protein. Read the plan and the margin. Then read the second table, which measures that plan against all three real ceilings, and say by how much the store is overdrawn. Re-check the box and watch the margin fall to the honest figure.

Make it unbounded, then diagnose it

Uncheck all three rows. The status changes to unbounded. Say in one sentence what unbounded means physically here, and why it is nearly always a missing constraint rather than an opportunity.

Write your own three parts for one real decision

Pick a recurring decision from your own work. List the variables with units and period, name the single number, and write each constraint as a rate and a ceiling with both units stated. Leave a blank wherever you do not know a number and count the blanks. That count is the honest status of the model.

The habit to keep. Before any solve, read the statement back as sentences with units in them, and ask one other person what is missing from the row list. The two error classes that survive every automatic check are a swapped column and an absent row, and both are caught by a human reading the model out loud rather than by anything the solver can tell you.

Check yourself

Three questions

A colleague hands you a brief: "maximise profit across the three products, subject to capacity". What is the first thing wrong with it?

An objective needs one named number, because gross margin, contribution and margin net of a transfer price give different sets of c and different plans. A constraint needs two measured halves, a rate per tonne and a ceiling in the same unit. The brief supplies neither, so the work has not started.

A model of Ravensworth is written with the milling and blending rows but the premium protein row is never entered. What does the solver report?

The output describes the problem it was given. A missing row has no symptom in the answer: nothing is blank, nothing is flagged, and the slack column only ever lists the rows you wrote. Catching it needs a person who knows the plant to read the row list, or a check of the plan against the real ceilings from outside the model.

Why does a constraint row have to be in the unit of the thing it limits?

There is no type checker between you and the matrix. Hours per tonne times tonnes gives hours, which is comparable with 131 hours. Put shifts on the right instead and the row is wrong by whatever a shift is, the shape still checks out, and the answer is confident and false.

Before session 2

Homework

Thirty minutes, half of it writing and half of it asking somebody questions.

Formulate one real decision end to end

Variables with units and period, one named objective, every constraint as a rate and a ceiling with both units written. Then do the unit read-aloud on each row and fix what it catches. Bring the blank count and the row you were least sure about.

Predict the mill's answer, in writing

With 38, 54 and 71 a tonne in front of you and the three capacity rows underneath, write down how many tonnes of each product you think the optimal plan runs, before session 2 draws the region. Keep the paper. The plan is not close to what most people write, and the reason is geometric.

Covered tonight

What this session covers

✓Decision variables with unitsTonnes of Standard, Dairy and Show, produced in one week.
✓One objective, namedMargin a tonne of 38, 54 and 71, and why four wishes are not an objective.
✓Constraints as a rate and a ceilingMilling 131, blending 77, premium protein 29, each row in its own unit.
✓The arrays c, A and bBuilt from the sentences and solved live with OPT.solveLP.
✓Reading the solver's replystatus, x, objective, slack and binding, and checking status first.
✓A missing constraint has no symptomDemonstrated live: the status still reads optimal and the margin goes up.
◐Why the optimal plan is what it isNamed tonight as a geometric fact. Session 2 draws the region.
◐Shadow pricesNamed as a field on the reply. Derived and measured in session 4.
○How the solver finds the answerSlack variables, tableaus and pivots are session 3.
○Whole tonnesThe mill bags integers, which changes the answer. Session 5.
○Building or judging the forecastModel choice and evaluation belong to the prediction courses.
Take this with you

Cheat sheet

Three parts, only three. Variables you set, one number to make big, ceilings you may not cross.
Every variable gets a unit. Tonnes of Standard produced this week, not "Standard output".
Which profit. "Maximise profit" is not an objective until somebody names the number that goes into c.
Units on both sides. Hours a tonne times tonnes gives hours, and the ceiling has to be in hours too.
The mill in three arrays. c = [38, 54, 71], A rows 1.1/1.5/2.3, 0.6/0.9/1.7, 0.1/0.4/1.0, b = [131, 77, 29].
Check status first. Unbounded almost always means a constraint is missing, not that money is free.
A missing row is invisible. Optimal status, specific plan, higher margin, no warning anywhere.
Columns carry no names. Swap two and every shape check still passes. Keep the names beside the numbers.