Model Format Reference
Lecture 1.1 introduces this form. This page is where to come back when a model gets complicated enough that the question is not what to write but which slot it goes in.
The form matters twice over. It is how models are presented in the lectures, so reading it fluently is reading the course. And it is how a model is handed to an AI assistant: a statement in this form is a specification the assistant can descend from, while a paragraph of prose is an invitation to guess.
Quick reference
Six keywords, in this order, and no others.
| Keyword | Holds |
|---|---|
minimize: / maximize: |
what is being optimized |
solve for: |
what is being decided, and over what values |
subject to: |
what restricts the solutions |
return: |
what comes back |
assumptions: |
what must be true about the world |
where: |
what was given: data and symbol meanings (formulation depth only) |
One word in that table needs introducing before it is used. A model can be written at up to three depths: the concept, in words only; the formulation, the same model committed to symbols or pseudocode; and the implementation, runnable Julia. Five of the six keywords are used at every depth. where: is the exception, because it defines symbols and the concept has none, so the first appearance of where: is the signal that a model has descended past the concept. Sec. 7 sets the depths out in full.
The pair to keep straight is subject to: against assumptions:. A constraint restricts the solutions; an assumption is something that has to be true about the world. A constraint cannot be untrue, because it is being imposed. An assumption can, which is why a false one leaves the wrong model rather than merely a worse answer.
Three rules govern the slots:
minimize:/maximize:andreturn:take exactly one entity. If a return seems to need three things, it is one composite thing: name it.solve for:,subject to:andassumptions:are always lettered lists,(a) (b) (c), one item per line, even at a single item.where:is a definition list keyed by symbol, not lettered, since every entry is a symbol and its meaning.
And the first line tells you what kind of model it is:
| Kind | Opens with | Decides? | Prefers? |
|---|---|---|---|
| Optimization | minimize: / maximize: |
yes | yes |
| Feasibility | solve for: |
yes | no |
| Descriptive | return: |
no | no |
Everything below is the detail behind these three tables. A model written this way can be checked mechanically; see Sec. 10.
1. Why the slots are shaped this way
The uniform silhouette is the point. Every model has the same slots in the same order, so a slot that is thin or missing is visible as a slot rather than as an absence nobody notices. That property is what makes this a good prompt as much as a good notation.
The always-a-list rule exists for the same reason. A single constraint written inline reads as a sentence; written as (a) it reads as a list with one entry, and the reader asks what (b) would have been.
A descriptive model computes something and decides nothing, so it has no solve for: and no objective. A feasibility model decides something but has no preference among the solutions that work. An optimization model decides something and prefers some solutions to others. Read the first line and you know which you are looking at.
Omitted, or present and empty
These are two different things, and the difference carries information.
A slot is omitted when the category does not apply to that kind of model. A descriptive model has nothing to decide, so solve for: is absent, and its absence is how the reader knows what kind of model it is.
A slot is written with none when the category applies but is empty. An optimization model with no constraints gets subject to: none, which is a real finding about the problem rather than an omission: the unconstrained economic-order-quantity model is the standard example, and reading none there tells you something worth knowing.
| Slot | Descriptive | Feasibility | Optimization |
|---|---|---|---|
minimize: / maximize: |
absent | absent | required |
solve for: |
absent | required | required |
subject to: |
absent | required | required, may be none |
return: |
required | required | required |
assumptions: |
required | required | required |
A feasibility model whose subject to: would read none is not a feasibility model at all: with nothing to satisfy, every candidate works and there is nothing to solve.
2. Which slot does it go in?
Ask these in order. The first yes is the answer.
1. Is it the single thing being made best? → the objective
minimize: or maximize:, and there is only one. Two competing objectives means either choosing one and turning the other into a constraint, or stating the resolution explicitly: in order of priority, weighted, or Pareto.
In the concept the objective is a phrase, not an equation with the symbols removed. “The total annual cost of opening hubs and serving every store” is a concept objective. “The sum over sites of fixed cost plus the sum over pairs of flow times distance” is a formulation objective wearing words.
2. Is it an unknown the model determines? → solve for:
This slot names each unknown and says what values it may take. That second half is what makes it a declaration rather than a list of nouns, and it is the half most often left out.
A useful tell: statements here are permissive. They name or enlarge the set of possibilities and never remove any. “The warehouse may be placed anywhere in the service region” removes nothing, so it is not a constraint however much it sounds like one.
3. Would it eliminate a solution that is otherwise available? → subject to:
A constraint binds the solution. Each one carries a short name before its text, because that name is what it will be called in the code:
subject to:
(a) demand: every store's demand is fully served;
(b) capacity: no hub exceeds its throughput limit;
(c) linkage: a site serves demand only if it is open
An empty list is legitimate and informative. subject to: none says the problem is unconstrained, which is a real fact about it.
4. Does the model take it as given rather than representing it? → assumptions:
An assumption is a condition about the world or the data that the model treats as settled, so that it does not have to be modeled. Every model simplifies something; this slot is where those simplifications are written down. That is its whole job: an assumption is what makes a simplification legitimate instead of hidden.
“Stops are uniformly distributed over the service region” is an assumption because the model does not represent where the stops actually are, it takes a claim about them instead. “Store demands are known and constant” is an assumption because the model does not represent demand varying, it assumes it away.
The test against a constraint is falsifiability. A constraint cannot be untrue: it is a condition being imposed. An assumption can, because it is a claim about how things are. So a constraint binds the solution; an assumption binds the model — and if an assumption turns out to be false, the consequence is not a worse answer but the wrong model.
That is also why the assumptions are what a result is checked against. When an answer looks wrong, the first question is usually not “was the arithmetic right?” but “is one of these still true?”
5. Is it what comes back? → return:
One entity. Naming it in words is real modeling work, and it is often the most consequential line in the concept, because it is where “what would I actually do with this answer?” gets settled.
return: is not a place for by-products. If a quantity is computed along the way and is worth reporting, it belongs inside the one composite thing being returned, described as part of it.
6. Is it a symbol definition or given data? → where:
where: never appears in the concept. Its first appearance is precisely the signal that the model has descended to a formulation. It holds given data and symbol meanings with units, never the unknowns, because the unknowns are declared in solve for:.
3. The three mistakes this form catches
A domain choice written as a constraint. If a warehouse must sit at one of eight existing stores, the wrong repair is adding a constraint. The right repair is changing what the unknown is, from a point in the plane to a choice among eight sites, in solve for:. That single change makes it a different kind of problem with a different solution method, and putting it in solve for: makes the consequence visible instead of burying it in a list.
An assumption written as a constraint. “The warehouse has no capacity limit” sounds restrictive and is not. It asserts something falsifiable about the world and licenses the absence of a capacity constraint. It is an assumption, and if it is wrong the model is wrong rather than merely the answer.
A decision hidden in return:. Writing “return: which six locations to open and the hub each demand point uses” puts the decision in the output slot and leaves solve for: empty. The decision goes in solve for:; return: names the one thing handed back.
4. Worked: a descriptive model
Nothing is decided; a quantity is computed.
return: the average distance from the depot to a stop
assumptions:
(a) stops are uniform over the service region;
(b) travel is straight-line, so road circuity is ignored
No objective and no solve for:, and their absence is the information: this model answers a question rather than making a choice.
5. Worked: a feasibility model
Something is decided, but no solution is preferred over another that also works.
solve for:
(a) the apartment location, a point along the corridor
subject to:
(a) equity: the two partners' weighted travel is equal
return: the apartment location
6. Worked: an optimization model, mixed variable types
The form does not change as models get harder; only the lists get longer.
Concept, words only.
minimize: the total annual cost of opening and serving
solve for:
(a) which of the five candidate sites to open;
(b) how much of each store's demand each open hub serves;
(c) the fleet size based at each open hub
subject to:
(a) demand: every store's demand is fully served;
(b) capacity: no hub exceeds its throughput limit;
(c) linkage: a site serves demand only if it is open
return: a hub plan: the opened sites, the demand each
serves, and its fleet size
assumptions:
(a) store demands are known and constant;
(b) transport cost is proportional to weighted distance
Formulation: the same slots, now with symbols, and where: appears.
solve for:
(a) yₖ ∈ {0,1} 1 if site k is opened, for each k ∈ K;
(b) xⱼₖ ≥ 0 demand of store j from site k, j ∈ J, k ∈ K;
(c) fₖ ∈ ℤ₊ vehicles based at site k, for each k ∈ K
where:
K the set of candidate sites
J the set of stores
dⱼ annual demand of store j, in tons
cₖ annual fixed cost of operating site k, in dollars
The unknowns stayed in solve for: and did not migrate into where:. The two slots answer different questions: what is being determined, and what was given.
7. The three depths
One model can be written at three depths. They are not three models and not three stages of a process: they are the same model committed to in increasing detail, and each is a complete statement of it at that detail.
| Depth | Content | What it settles |
|---|---|---|
| Concept | Words only, no symbols: the slots above, verbally | what is being decided and why |
| Formulation | Symbols and equations, or pseudocode; where: enters |
how it is represented and solved |
| Implementation | Runnable Julia | how it is computed |
Not every model goes all the way down, and how far it goes is shown by which parts are present rather than by any numbering. A concept alone is a legitimate model, and often the only one worth writing: it is what a decision needs stated before anyone argues about method. A concept with a formulation and no implementation is equally legitimate when the point is the mathematics rather than an answer.
The first appearance of where: is the signal that a model has descended. Nothing else marks the boundary, because the concept’s job is to be free of symbols and where: exists only to define them.
How the depths are titled
The concept carries no depth word. A model float is captioned “Model 3: Poisson arrival simulation” and nothing more, because the concept is what a model is; naming it would be like labelling a book “book.” The other two are labelled precisely to distinguish them from it, and from each other:
Model 3: Poisson arrival simulation <- the concept
Model 3 formulation: Poisson arrival simulation
Model 3 implementation: Poisson arrival simulation
The number and the name are the concept’s; the deeper parts borrow both and add only the depth word. So everything carrying “Model 3” is one model, and the reader never has to work out which parts belong together.
Descending is a fill-in, not a rewrite
Each depth commits detail the one above left open; it must not change what the one above said. If the formulation carries a constraint the concept never mentioned, something was decided silently, and that is worth stopping over rather than smoothing past. The same holds further down: an implementation that quietly assumes a bound the formulation did not state is not an implementation of that model.
This is the only reason the depths are worth separating at all. Written as one document they cannot be checked against each other; written as three, the check is a reading.
When one concept has more than one formulation
A concept can be approached more than one way, and the two approaches are not two models. They are two formulations under one concept, each with its own implementation, and they are told apart by a short parenthetical rather than a new number:
Model 5: single-facility minisum location
Model 5 formulation (analytic): ... Model 5 implementation (analytic): ...
Model 5 formulation (numerical): ... Model 5 implementation (numerical): ...
Keeping the number is the point. The concept did not change, so renumbering would claim a second problem where there is one problem and two methods, and the comparison between them is usually what the lecture is about.
Branching below the formulation is possible and is almost never what is wanted. Two implementations of one formulation are usually the same model written twice, which is a coding difference rather than a modeling one. The course’s habit is to parameterize instead: minisum(w, P, dist) takes the distance function as an argument, so one implementation serves straight-line and great-circle distance rather than spawning two. If a genuine split is ever needed, the parenthetical extends, but a long label is a fair warning that the distinction may not be a modeling distinction at all.
8. Two cases the five slots stretch to, and one they do not
More than one objective
minimize: still holds one entity, but that entity may be an ordered preference rather than a single quantity. Give the objectives in order and say how they are resolved:
minimize: first the number of vehicles used, then, among the solutions
that use the fewest, the total distance travelled
That is the lexicographic resolution, and it is the one this course uses most: the second objective is a tie-break among the answers that are already best on the first. The alternatives are a weighted sum, which trades the objectives against each other at a stated exchange rate, and a Pareto treatment, which declines to trade them and returns the set of answers where improving one means worsening another.
Whichever is used, say which. An unresolved list of objectives is not a model: it does not determine an answer, and a reader cannot tell whether a solution is right without knowing how the objectives were reconciled. Stating “first… then…” is the resolution, and it is why the ordering carries the whole meaning.
Models whose constraints contain another model
Some problems are genuinely two-storey: one decision is made in anticipation of how somebody else will then respond, and the second party’s response is itself the answer to an optimization. Siting facilities knowing that competitors will site theirs afterwards is the standard example, and the beach models of Lecture 2.1 are its simplest form.
The five slots do not currently express this, and that is an open question rather than a settled convention. The difficulty is real: the follower’s problem is not a constraint in the ordinary sense, because it is not a condition a solution satisfies but a whole model whose solution the leader’s constraints refer to. Writing it as an assumption is worse, since assumptions are conditions taken as given about the world and this one is derived.
The shape a convention would need is clear enough: the follower’s model stated in the same five slots, and the leader’s subject to: naming it as a constraint rather than restating it. What is not settled is how the two are labelled and numbered, and whether a reader can follow the nesting without it becoming heavier than the problem. If a bi-level model appears in the course, expect this section to be filled in rather than the model to be forced into the flat form.
9. Using this as a prompt
Hand an assistant a model written in this form and it has what it needs: what to optimize, what to determine, what must hold, what to give back, and what it may take for granted. Hand it a paragraph and it will supply all five itself, plausibly and invisibly.
The model does not go in the chat. It goes in a file, and the prompt points at the file. Keep it beside the work as model.md, and ask for what is wanted this time:
Read model.md, produce the formulation, then a Julia
script implementing it, and run it.
Four things follow from that arrangement, and only the first is convenience.
It is revisable. A model changes as it is understood. Editing one slot in a file and re-running beats retyping a paragraph and hoping the change was the only one.
It survives the session. A model stated once in conversation erodes as the window fills. A file is re-read whole, so the specification is exactly as precise on the hundredth turn as the first.
It separates the specification from the task. model.md says what the model is. The prompt says what to do with it now: formulate it, implement it, vary a parameter, check a result against it. Those are different things, and keeping them apart is what stops a prompt from quietly becoming the model.
It leaves a history. The file can be committed, so the model’s evolution is visible and a slot that was silently dropped can be found. A decision reached after an hour of argument is in the file, not in a conversation nobody can reopen.
The last of these is the one that matters most by the end of a project, and it is the same argument the course makes about session notes: what must survive has to be written down, and what changed is a question only version control can answer.
Where each depth lives
The concept and the formulation are one model at two depths, so they share one file. The formulation goes into model.md beneath the concept, exactly as a lecture’s model callout shows both in a single object.
The concept is the only depth written by hand. The assistant produces the formulation and the implementation; both are then checked.
| Rung | Written by | Lives in |
|---|---|---|
| Concept | the reader | model.md, written first |
| Formulation | the assistant, asked for | model.md, beneath the concept |
| Implementation | the assistant, asked for | a Julia script, because it has to run |
This is the division of labor the whole course rests on, so it is worth being blunt about: turning a stated model into symbols and then into code is translation, and translation is what the assistant is for. Deciding what is being optimized, what is being determined, what must hold and what may be assumed is not translation, and it is not delegated.
The implementation separates into its own file for one reason: it must be executable, and words and symbols are not. Give the script a header comment naming the model file it came from, so the pair does not drift apart.
Keeping both in one file is what makes the descent checkable. Descending is supposed to be a fill-in operation rather than a rewrite, and that claim can only be audited if the two sit side by side: a constraint present in the formulation and absent from the concept above it is then visible in a glance, and it means something was decided without being stated. Split across a file and a conversation, the same drift is invisible.
10. Checking a model mechanically
Because the form is strict, a program can check whether a model statement follows it. One ships with the course materials:
julia ../materials/env/check_model.jl model.mdRun from the folder holding the model, so the relative path reaches across to materials. It prints a line per finding with its line number, and exits nonzero when any finding is an error, so it can also be used inside a loop.
What it checks. That every keyword is one of the six and spelled as such; that the keywords appear in the order above; that minimize:, maximize: and return: carry exactly one entity; that solve for:, subject to: and assumptions: are lettered lists running from (a) without gaps, or read none; that every constraint carries a name; that where: is keyed by symbol rather than lettered; that a model has a return:; and that the slots are mutually coherent, so an objective without solve for: is an error and solve for: without subject to: is queried.
What it cannot check, and this is the important half. It reads the shape, not the modeling. It cannot tell that something written as a constraint is really an assumption, that a domain choice was buried in subject to: instead of solve for:, that a return: is naming a by-product, or that an assumption is false. Every one of those is a judgment, they are what the rest of this page is about, and a clean report says only that the statement is well formed:
OK model.md conforms to the model format.
Note: the format is checked, not the modeling. Whether a
constraint should have been an assumption is still yours.
That distinction is worth dwelling on, because it is the same one the course makes about AI-produced work. A mechanical check is cheap, so run it every time; but passing it is evidence about form alone, and reading a green result as approval of the model is the error the whole course exists to train out.