Originally posted by: Rodders
Hi there.
I'm trying to implement a constraint that some of you have probably dealt with. I wonder if I'm on the right path here.
I have an ATM with 3 cassettes of the same type of note. I have some cash that I can use to replenish the ATM. That cash is the end balance at the vault on the previous day. Each cassette will have a balance at the end of the previous day, as well. I have the forecasted consumption on the ATM, and I need to assign that consumption to each cassette on the order of most depleted. So, if cassettes 1, 2, 3 had 20, 26 and 15 dollars in them, and the day's forecasted consumption were 23 dollars, I would want to consume 15 dollars from cassette 3 and subsequently consume 8 dollars from cassette 1.
If I decide to replenish any cassette I have to ship back to the vault the amount currently existing in the cassette. The replenishment happens at the end of the day.
Right before the "total_cost" decision expression I explain the errors I'm getting.
If someone could explain to me why I get those errors, that would be great. But my main question is - am I implementing the consumption constraint correctly?
Thank you for any help you can provide.
In the model below, day 1 is just to initialize the cash balances at the vault and at the cassettes. I receive cash regularly at the vault.
Regards.
/*********************************************
* OPL 12.5 Model
*********************************************/
float bignum = 10000.0;
setof (int) days = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
setof (int) cassettes = { 1, 2, 3 };
float IRR = 0.0002;
float var_cost = 0.0004;
float residual_cost = 0.0003;
float BB[cassettes] = [ 24.0, 21.0, 33.0 ];
float RHS[days] = [ 0.0, 25.0, 23.0, 16.0, 5.0, 26.0, 32.0, 12.0, 24.0 ];
float vault_ins[days] = [ 70.0, 0.0, 69.0, 0.0, 0.0, 30.0, 0.0, 35.0, 0.0 ];
float max_SI[cassettes] = [ 40.0, 40.0, 40.0 ];
dvar boolean consumed[cassettes, days];
dvar boolean depleted[cassettes, days];
dvar boolean replenished[cassettes, days];
dvar float+ consumption[cassettes, days];
dvar float+ EB[cassettes, days];
dvar float+ SI[cassettes, days];
dvar float+ SO[cassettes, days];
dvar float+ tot_SI[days];
dvar float+ tot_SO[days];
dvar float+ vault_EB[days];
// when I try to run the model, it tells me that Name "d" doesn't exist, and underlines the "d" in tot_SI[d].
// if I simplify the total_cost as sum (d in days) Vault_EB[d], it shows the error CPLEX Error 5002: Q in 'q1' is not positive semi-definite
// However, this is clearly not a quadratic problem...
dexpr float total_cost = sum (d in days)
vault_EB[d] * IRR + (tot_SI[d] + tot_SO[d]) * var_cost + tot_SO[d] * residual_cost;
constraint total_SI;
constraint total_SO;
constraint max_total_SI;
constraint vault_end_bal1;
constraint vault_end_bal2;
constraint add_up;
constraint consume;
constraint was_consumed;
constraint was_depleted;
constraint sorted_cons;
constraint max_SI_rule;
constraint calc_SO;
constraint calc_EB1;
constraint calc_EB2;
constraint did_replenish;
constraint no_SO_if_no_replenish;
constraint SO_all_residual;
/*constraint ;
constraint ;
constraint ;
constraint ;
*/
minimize total_cost;
subject to {
total_SI =
forall (d in days)
tot_SI[d] == sum (c in cassettes) SI[c, d];
total_SO =
forall (d in days)
tot_SO[d] == sum (c in cassettes) SO[c, d];
// can't send more money to ATM than what was available at the vault on the previous night
max_total_SI =
forall (d in days diff {1})
tot_SI[d] <= vault_EB[d-1];
// putting an initial balance at the vault
vault_end_bal1 =
vault_EB[1] == vault_ins[1];
// tot_SI is the sum of replenishments for all cassettes
// tot_SO is the sum of cash coming back from all cassettes
vault_end_bal2 =
forall (d in days diff {1})
vault_EB[d] == vault_EB[d-1] - tot_SI[d] + tot_SO[d];
// the withdrawals from all cassettes have to add up to the expected day's withdrawals
add_up =
forall (d in days)
sum (c in cassettes) consumption[c, d] == RHS[d];
// one can't withdraw more from a cassette than what was available at the previous night
// I'm assuming there will not be withdrawals against a cassette that was replenished
consume =
forall (c in cassettes, d in days diff {1})
consumption[c, d] <= EB[c, d-1];
// turning the boolean "consumed" on
was_consumed =
forall (c in cassettes, d in days diff {1})
consumption[c, d] <= consumed[c, d] * bignum;
// turning the boolean "depleted" on
was_depleted =
forall (c in cassettes, d in days diff {1})
EB[c, d-1] - consumption[c, d] <= (1 - depleted[c, d]) * bignum;
// this is the key constraint: consumption occurs from cassettes in the ascending order
// of how much was in each cassette on the previous night
sorted_cons =
forall (c1 in cassettes, c2 in cassettes, d in days diff {1})
EB[c1, d-1] - EB[c2, d-1] <= (depleted[c2, d] - consumed[c1, d] + 1) * bignum;
max_SI_rule =
forall (c in cassettes, d in days diff {1})
SI[c, d] <= max_SI[c];
// SO is the residual amount in a cassette when it gets replenished
// it can be zero (if the cassette was depleted)
// it can't be higher than what was available on the previous night minus the day's consumption
calc_SO =
forall (c in cassettes, d in days diff {1})
SO[c, d] <= EB[c, d-1] - consumption[c, d];
// putting an initial balance in the cassettes
calc_EB1 =
forall (c in cassettes)
EB[c, 1] == BB[c];
// conservation of total cash flows
calc_EB2 =
forall (c in cassettes, d in days diff {1})
EB[c, d] == EB[c, d-1] - consumption[c, d] + SI[c, d] - SO[c, d];
// turning the "replenished" boolean on
did_replenish =
forall (c in cassettes, d in days diff {1})
SI[c, d] <= replenished[c, d] * bignum;
// if SI = 0, SO has to be zero
// also, one does not remove cash from a cassette
no_SO_if_no_replenish =
forall (c in cassettes, d in days diff {1})
SO[c, d] <= SI[c, d];
// SO has to be the residual, in case there was a replenishment
// residual is calculated as previous balance minus the day's consumption
SO_all_residual =
forall (c in cassettes, d in days diff {1})
SO[c, d] == replenished[c, d] * (EB[c, d-1] - consumption[c, d]);
}
execute write_results {
var fd = new IloOplOutputFile("shipments.txt");
for (var c in cassettes) {
for (var d in DAYs) {
fd.write(c);
fd.write(" ");
fd.write(d);
fd.write(" EB = ");
fd.write(EB[c][d]);
fd.write(" cons = ");
fd.write(consumption[c][d]);
fd.write(" SI = ");
fd.write(SI[c][d]);
fd.write(" SO = ");
fd.write(SO[c][d]);
}
}
}
#CPLEXOptimizers#DecisionOptimization