Practice example: Car dealership

A practice example for GMPL skills with increasing difficulty.

Simple warm-up example

We are the collectors of old eastern european cars, namely, we want to buy the following cars one by one during the 5 weekdays:

Each day we can buy exactly one of them, and a geany has told us, how the prices of these cars will change on the market in advance:

Cars/Days Monday Tuesday Wednesday Thursday Friday
Wartburg 6000065000610006600060000
Lada 5000055000630006000052000
Fiat 3000032000330003000027000
Trabant 70000650007700085000100000
Skoda 6500070000750009000070000

The goal is simple: buy all of the cars, one at a day, so that we spend the smallest amount of money possible. Reveal solution

set Days;
set Cars;
param price{Cars,Days};

var buy{Cars,Days} binary;

s.t. ExactlyOneCarEachDay{d in Days}:
    sum{c in Cars} buy[c,d] = 1;
 
s.t. EachCarExactlyOnce{c in Cars}:
    sum{d in Days} buy[c,d] =1;

minimize totalCost:
    sum{c in Cars, d in Days} buy[c,d]*price[c,d];

solve;

printf "\n\n";

for{c in Cars, d in Days:buy[c,d]==1}
    printf "%10s:\t%s - %d\n",c,d,price[c,d];

printf "\n\n";

data;

set Days:= Monday Tuesday Wednesday Thursday Friday;
set Cars:= Wartburg Lada Fiat Trabant Skoda;

param price:
            Monday  Tuesday Wednesday   Thursday    Friday :=
Wartburg    60000   65000   61000       66000       60000
Lada        50000   55000   63000       60000       52000
Fiat        30000   32000   33000       30000       27000
Trabant     70000   65000   77000       85000       100000
Skoda       65000   70000   75000       90000       70000
;

Making things a bit more complex: trading

Let us make the problem more realistic (and complex). Remove the restriction of only buying one car at a day. Let us have an initial budget of 200000, and we can decide how and when we want to use it. We can buy cars, and sell them a day or two later. The only restriction is, that we have 4 garages, where we can store cars during the night.

The goal is to maximize our budget by the end of the week. Reveal solution

param nDays;
set Days:=1..nDays;
set Cars;
param price{Cars,Days};
param initialbudget;
param garagecount;

var buy{Cars,Days} integer;

s.t. MustHavePositiveBalanceAtTheEndOfEachDay{d in Days}:
    initialbudget - sum{d2 in 1..d, c in Cars} buy[c,d2]*price[c,d2] >= 0;
    
s.t. CannotHaveMoreCarsThanGarageSpace{d in Days}:
    sum{d2 in 1..d, c in Cars} buy[c,d2] <= garagecount;


s.t. CannotSellWhatWeDontHave {d in Days, c in Cars}:
    sum{d2 in 1..d} buy[c,d2] >= 0;

maximize endbudget:
    initialbudget - sum{d in Days, c in Cars} buy[c,d]*price[c,d]
    ;

solve;

for{d in Days}{
    printf "Day %d\n",d;
    printf "\tSell: ";
    for{c in Cars:buy[c,d]<0}
        printf "%s(%d) ",c,-buy[c,d];
    printf "\n\tBuy: ";
    for{c in Cars:buy[c,d]>0}
        printf "%s(%d) ",c,buy[c,d];
    printf "\n";
    printf "\tBudget: %d\n", initialbudget - sum{d2 in 1..d, c in Cars} buy[c,d2]*price[c,d2];
}

data;

param nDays:= 5;
set Cars:= Wartburg Lada Fiat Trabant Skoda;

param price:
            1       2       3       4           5 :=
Wartburg    60000   65000   61000       66000       60000
Lada        50000   55000   63000       60000       52000
Fiat        30000   32000   33000       30000       27000
Trabant     70000   65000   77000       85000       100000
Skoda       65000   70000   75000       90000       70000
;

param initialbudget:=200000;
param garagecount:=4;

Final step: money loans and garage renting

Finally, let us assume, that we can rent an additional garage for one night for 4000, and we can also take loans from the bank with a 4% daily interest.

The goal is the same: maximize our budget by the end of the week. Reveal solution

param nDays;
set Days:=1..nDays;
set Cars;
param price{Cars,Days};
param initialbudget;
param garagecount;
param interest;
param garagepriceperday;

var buy{Cars,Days} integer;
var budget{0..nDays} >= 0;
var caringarage{Cars,0..nDays} >= 0;
var loan{Days};
var bankbalance{0..nDays} <= 0, >= -200000;
var rentgarage{Days} >= 0, integer;

s.t. InitializeBudget:
    budget[0]=initialbudget;

s.t. InitializeGarage{c in Cars}:
    caringarage[c,0]=0;

s.t. InitialBankBalande:
    bankbalance[0]=0;

s.t. BalanceChange{d in Days}:
    budget[d]=budget[d-1] - sum{c in Cars} buy[c,d]*price[c,d]+loan[d]-rentgarage[d]*garagepriceperday;

s.t. BankBalanceChange{d in Days}:
    bankbalance[d]=bankbalance[d-1]*(1+interest)-loan[d];

s.t. NoLoansAtTheEnd:
    bankbalance[nDays] = 0;

s.t. CarAvailability{d in Days, c in Cars}:
    caringarage[c,d]=caringarage[c,d-1]+buy[c,d];
    
s.t. CannotHaveMoreCarsThanGarageSpace{d in Days}:
    sum{c in Cars} caringarage[c,d] <= garagecount + rentgarage[d];

maximize endbudget: budget[nDays];

solve;

for{d in Days}{
    printf "Day %d\n",d;
    printf "\tSell: ";
    for{c in Cars:buy[c,d]<0}
        printf "%s(%d) ",c,-buy[c,d];
    printf "\n\tBuy: ";
    for{c in Cars:buy[c,d]>0}
        printf "%s(%d) ",c,buy[c,d];
    printf "\n";
    printf "\tBank: %d\n",bankbalance[d-1]*(1+interest);
    printf "\tLoan: %d\n",loan[d];
    printf "\tGarage rent: %d\n",rentgarage[d];
    printf "\tBudget: %d\n", budget[d];
    printf "\tBank: %d\n",bankbalance[d];
}

data;

param nDays:= 5;
set Cars:= Wartburg Lada Fiat Trabant Skoda;

param price:
            1       2       3       4           5 :=
Wartburg    60000   65000   61000       66000       60000
Lada        50000   55000   63000       60000       52000
Fiat        30000   32000   33000       30000       27000
Trabant     70000   65000   77000       85000       100000
Skoda       65000   70000   75000       90000       70000
;

param initialbudget:=200000;
param garagecount:=4;
param interest:=0.05;
param garagepriceperday:=4000;