Relief Allocation

Advanced
SUM(D: expr) DECIDE Table.var PER WHEN BETWEEN MINIMIZE

A relief agency ships supplies from depots to regions along fixed routes. Opening a depot costs money once; shipping along a route costs money per unit. A depot can only ship what it has in stock, and a region marked critical must have its demand met. The goal is to cover the critical demand at the lowest total cost. The depot decision belongs to the depot, not to each route it serves, so its opening cost must be charged once no matter how many routes the join produces.

CREATE TABLE Depots (
    depotID VARCHAR, stock INTEGER, opening_cost INTEGER
);
INSERT INTO Depots VALUES
    ('D1', 800, 12000),
    ('D2', 500, 8000);

CREATE TABLE Routes (
    routeID VARCHAR, depotID VARCHAR, regionID VARCHAR,
    capacity INTEGER, unit_cost INTEGER
);
INSERT INTO Routes VALUES
    ('T1', 'D1', 'R1', 500, 6),
    ('T2', 'D1', 'R2', 350, 6),
    ('T3', 'D2', 'R2', 300, 3);

CREATE TABLE Regions (
    regionID VARCHAR, demand INTEGER, priority VARCHAR
);
INSERT INTO Regions VALUES
    ('R1', 450, 'critical'),
    ('R2', 600, 'standard');
SELECT routeID, depotID, regionID, open, ship
FROM Depots D JOIN Routes T USING (depotID) JOIN Regions R USING (regionID)
DECIDE D.open(BOOL), T.ship(INT)
SUCH THAT ship BETWEEN 0 AND capacity * open
  AND SUM(ship) <= stock PER depotID
  AND SUM(ship) >= demand WHEN priority = 'critical' PER regionID
MINIMIZE SUM(unit_cost * ship) + SUM(D: opening_cost * open)
ORDER BY routeID;
1
DECIDE D.open(BOOL), T.ship(INT) — Two table-scoped decisions. open has one value per depot and ship one value per route, however many rows the three-way join produces.
2
ship BETWEEN 0 AND capacity * open — A route ships nothing unless its depot is open, and never more than the route's capacity. A decision may appear in the bound of a BETWEEN.
3
SUM(ship) <= stock PER depotID — One constraint per depot, bounded by that depot's own stock column.
4
SUM(ship) >= demand WHEN priority = 'critical' PER regionID — WHEN keeps only the critical regions, then PER writes one constraint for each of them.
5
SUM(D: opening_cost * open) — The relation qualifier D: reduces over the depots themselves, so each depot contributes its opening cost once. The unqualified SUM(opening_cost * open) would charge D1 once for each route it serves.
routeID depotID regionID open ship
T1D1R11450
T2D1R210
T3D2R200
R1 is the only critical region, and only route T1 reaches it, so T1 must carry the full demand of 450 — within its capacity of 500 and within D1's stock of 800. Opening D1 is therefore unavoidable, and its 12,000 is paid once. R2 is standard, so nothing has to reach it: T2 ships 0, and D2 stays shut rather than paying 8,000 to open. Total cost is 12,000 for D1 plus 450 × 6 = 2,700 of shipping. Because open is scoped to Depots, both rows carrying D1 report the same value, and SUM(D: opening_cost * open) counts that depot once.
← Table-Scoped Variables Back to Examples →