FairwayPlan produces itineraries. Feed it a travel window, a set of NZ regions, a budget, a few preferences, it hands back a solved day-by-day schedule. Course, tee time, cost, expected weather conditions. What follows is an account of how it actually works that out.
The obvious first attempt is a greedy heuristic. Score every course, pick the highest-scorer for Day 1, then Day 2, keep going until you exhaust the days or the money. Fast. Also wrong in ways that aren't immediately obvious. Greedy treats each day as independent, which it isn't. A top-ranked course in Queenstown is a terrible Day 2 pick if you played Arrowtown yesterday and there's three hours of driving between them. Greedy also can't reason about how to spread budget across a multi-day trip: what you commit on Day 1 constrains what's available on Day 3. These interactions are exactly what a day-by-day approach misses.
The right framing is a Mixed Integer Programme. What follows is the formulation.
Decision variables
Let D be the set of trip days and C the set of candidate courses in the selected regions. Define binary decision variables:
where xd,c = 1 means course c is scheduled on day d, zero otherwise. The solver's job: find an assignment that maximises the objective subject to all constraints. Integrality is what makes it hard, specifically what separates this from a continuous linear programme. You can't play 0.7 of a round. The binary requirement means a relaxed LP solution isn't directly usable; the solver has to work through integer feasibility explicitly, which is where the computational cost lives.
Objective function
The objective is to maximise the total composite score across all scheduled rounds:
where the per-course-per-day score sc,d is a weighted combination of three components:
rc is the normalised course rating (slope-adjusted), pc a prestige score drawn from editorial rankings and visitor green fee tier, and wc,dthe weather score for course c on day d, computed from the three-tier weather pipeline. Weights α, β, γ sum to 1 and are calibrated to reflect the relative pull of course quality, prestige, and conditions. Fixed for now; per-user customisation is something I intend to add.
Worth noting what wc,d actually captures. It's a normalised composite of forecast temperature, precipitation probability, and wind speed, but weighted toward conditions that produce playable golf specifically. 14°C, overcast, low wind: fine. A 28°C afternoon with a 40 km/h crosswind: meteorologically pleasant, golfer's nightmare. The scoring reflects that distinction.
The prestige term pc needs a word of explanation. It is emphatically not green fee. Tarras Golf Club in Central Otago will cost you around NZ$30 for a visitor round. Tara Iti, appearing on best-in-the-world shortlists with some regularity, runs to about NZ$600. Both are, in their own way, genuinely worth playing. Collapsing that distinction into price would be the wrong model. Prestige is encoded separately so the solver can tell the difference between a cheap course that earns its place in the itinerary and an expensive one that's just expensive.
Constraints
At most one round per day
The most basic structural constraint. A golfer can play at most one course per day:
Budget
Total green fees across the trip must not exceed the stated budget B:
where fc is the applicable green fee for course c, which depends on affiliation status: standard visitor, NZ Golf affiliate, or temporary member. That question is resolved upstream of the solve. The planner pre-computes whether a temporary NZ Golf membership nets out positive across the full candidate set, then passes whichever fee schedule is cheaper to the solver. The solver doesn't need to know how that decision was reached.
Drive time between consecutive rounds
This one is what makes the problem interesting. If course c is played on day d and course k on day d+1, the drive between them has to fall within the user's stated tolerance:
where drive(c, k) is road distance in minutes between the two courses, and Tmaxthe user's stated tolerance. In practice this constraint tears through the feasible region, it's the main reason greedy selection generates terrible routes. Pick the best-scoring course for each day independently and you'll routinely end up with a sequence that puts four or five hours of driving between consecutive rounds. The constraint forces the solver to reason about the whole sequence at once, which is the point.
Drive times are precomputed across all course pairs within each region. Cross-region pairs use centroid-to-centroid road estimates. The precision is good enough. Someone planning a golf trip cares whether the drive is 20 minutes or two hours, not whether it's 47 minutes versus 51.
Sunset constraint
A round has to finish before dark. Simple enough to state, but the constraint is more interesting than it looks, because sunset times vary enormously across New Zealand's 12-degree latitudinal spread. Auckland in January: light until around 20:45. Invercargill in July: dark by 17:00. An afternoon tee time that's routine in summer Northland is a physical impossibility in winter Southland. The constraint has to be computed per course, per date.
Let tc denote the preferred tee time (in fractional hours), δ the estimated round duration, and Sd the sunset time for day d at the latitude of course c:
Sunset times are computed from the NOAA solar position algorithm given each course's latitude, longitude, and date (Meeus, 1998). A 30-minute buffer is enforced, because finishing the 18th hole in the dark is not the plan.
Hilliness tolerance
Walking preference and terrain interact in an obvious way. Each course carries a hilliness rating on a 1–5 scale derived from SRTM elevation data (Farr et al., 2007). If someone indicates they want to walk, courses exceeding their stated tolerance Hmax are excluded:
Implemented as a preprocessing filter on the candidate set rather than an explicit constraint, same effect, smaller problem for the solver to actually work through.
Why HiGHS
The solver engine is HiGHS, open-source, developed at the University of Edinburgh, and legitimately competitive with commercial alternatives on LP relaxations (Huangfu & Hall, 2018). For the MIP side, it runs branch-and-bound with cutting planes in the standard fashion (Wolsey, 1998). Not exotic. Just good.
The practical reason to use it over something like Gurobi or CPLEX: it's open-source, free to embed, and ships as a Python package (highspy) that wraps the C++ library without much ceremony. At the scale FairwayPlan operates, typically 20–40 binary variables and 15–30 constraints, any competent solver finds the optimum in under a second. The choice is honestly as much about licensing as technical merit.
The same structure in other domains
Worth stepping back for a moment. The formulation above is specific to golf, but the skeleton underneath it (select a subset of discrete items, sequence them subject to pairwise transition costs, maximise a composite score, stay within a global budget) turns up constantly in applied work. A few direct analogies from fields I've spent time in.
Banking and finance: constrained portfolio construction
The Markowitz (1952) mean-variance framework starts as a continuous quadratic programme, allocate weights wi ∈ [0, 1] across assets to maximise expected return at a given risk level. Clean, tractable. The moment you add real-world constraints, the problem changes character entirely.
Define yi ∈ { 0, 1 } as a binary indicator of whether asset i is held at all. Cardinality constraints, "hold at most K positions", are expressed as:
Minimum lot size requirements couple the binary and continuous variables:
Sector allocation caps, liquidity requirements, regulatory limits (Basel III capital adequacy rules for specific instrument classes) pile additional linear constraints onto the same binary skeleton. The result is a Mixed Integer Quadratic Programme, a MIQP, typically solved via branch-and-bound on the QP relaxation (Cornuéjols & Tütüncü, 2006).
The mapping to FairwayPlan is direct. Assets are courses. Portfolio weights are green fees. The cardinality constraint is one-round-per-day. Sector caps are regional diversity preferences. The objective, maximise some risk-adjusted composite score, is formally the same problem structure as maximising prestige-rating-weather. Same machinery, different vocabulary.
Media and entertainment: content scheduling and acquisition
Two problems in media worth pulling apart. The first is content acquisition: you have a licensing budget and a catalogue of candidate titles; decide which to bring in-house. Define yi ∈ { 0, 1 } as the binary acquisition decision for title i, with expected viewing-hours score vi and licensing cost li:
Genre quotas and territory licensing restrictions layer covering constraints on top. This is a 0–1 knapsack problem, NP-hard in theory, tractable at realistic catalogue sizes in practice (Kellerer et al., 2004).
The second problem: theatrical release scheduling. Studios don't want two films chasing the same audience in the same weekend. If films i and jare close substitutes and zi,w = 1 denotes the release of film iin week w, the non-competition constraint is:
Structurally identical to FairwayPlan's drive-time constraint. Two courses that are geographically incompatible can't appear on consecutive days; two competing films can't drop in the same release window. Binary mutual exclusion, same pattern, same formulation, different domain. Nemhauser and Trick (1998) cover integer programming for sports and entertainment scheduling in some depth if you want to go further.
Supply chain: vehicle routing and warehouse slotting
FairwayPlan's drive-time constraint is, at its core, a restricted Travelling Salesman Problem embedded inside a larger MIP. Scale that up industrially and you get the Vehicle Routing Problem, first formulated as an integer programme by Dantzig and Ramser (1959), which is about as foundational a cite as you can make in combinatorial optimisation. In the capacitated VRP, binary variables xi,j,k ∈ { 0, 1 } encode whether vehicle k travels directly from location i to j. Objective: minimise total distance. Constraints: vehicle capacity limits, time windows at each stop, route continuity:
Every location visited exactly once, by exactly one vehicle. The time-window constraint, delivery must occur between [ai, bi], is the same idea as FairwayPlan's sunset constraint. The activity has to happen within a feasible window that shifts by location and date. Same constraint, different context.
A second supply chain application: warehouse slotting. You're assigning SKUs to bin locations to minimise pick-path length. Define ys,b ∈ { 0, 1 } as the assignment of SKU s to bin b. One bin per SKU. One SKU per bin. Weight and volume limits per aisle section as side constraints. Objective: maximise throughput-weighted proximity, high-velocity items should live closest to the dispatch zone. The formulation is a Quadratic Assignment Problem, and it's genuinely one of the nastier combinatorial problems you can run into (Loiola et al., 2007).
The pattern that cuts across all three: discrete selection under a budget, pairwise interaction costs between sequential choices, a composite objective encoding whatever the domain thinks "good" means. Golf, finance, media, logistics. The maths doesn't care which one it is. Only the vocabulary changes.
Does it work
Short answer: yes, in the ways that matter. Better than doing it by hand, which is the real baseline for the target user, not some theoretical optimum. In practice the solver reliably clusters courses sensibly, doesn't blow the budget, and generates tee times that are actually viable given whatever the day's sunset window is.
The harder question is whether α, β, γ are right. They encode my own weighting of course quality versus prestige versus weather, which is not necessarily anyone else's weighting. A golfer who doesn't care about prestige at all, who'd happily play the local muni every day if the forecast is clean, wants different numbers. Per-user customisation of those weights is something I intend to add.
The interesting thing about building a solver for golf is that golf already has strong opinions about what "good" means. You are not starting from scratch, you are encoding those opinions into mathematics and then arguing with yourself about whether you got them right.
References
- Cornuéjols, G., & Tütüncü, R. (2006). Optimization Methods in Finance. Cambridge University Press.
- Dantzig, G.B., & Ramser, J.H. (1959). The truck dispatching problem. Management Science, 6(1), 80–91.
- Farr, T.G., et al. (2007). The Shuttle Radar Topography Mission. Reviews of Geophysics, 45(2).
- Huangfu, Q., & Hall, J.A.J. (2018). Parallelizing the dual revised simplex method. Mathematical Programming Computation, 10(1), 119–142.
- Kellerer, H., Pferschy, U., & Pisinger, D. (2004). Knapsack Problems. Springer.
- Loiola, E.M., et al. (2007). A survey for the quadratic assignment problem. European Journal of Operational Research, 176(2), 657–690.
- Markowitz, H. (1952). Portfolio selection. The Journal of Finance, 7(1), 77–91.
- Meeus, J. (1998). Astronomical Algorithms (2nd ed.). Willmann-Bell.
- Nemhauser, G.L., & Trick, M.A. (1998). Scheduling a major college basketball conference. Operations Research, 46(1), 1–8.
- Toth, P., & Vigo, D. (Eds.). (2014). Vehicle Routing: Problems, Methods, and Applications (2nd ed.). SIAM.
- Wolsey, L.A. (1998). Integer Programming. Wiley.