Hello. I recently posted a question on giving soft preferences to resource allocations but decided I might get a better response if I developed some example code. I have included the plain .sas files to generate the input data and execute the optimization. It's a small example, so the required data and optimization can be recreated in a few seconds. I am using SAS Enterprise Guide v8.3 (Base 9.4_M7).
TLDR: I need help defining two scheduling constraints in Proc Optmodel (MILP):
#1) If a product is scheduled on a given day, it cannot be scheduled again for the next N days.
#2) Given info about preferred time/day slots for a particular set of products, I would like the optimizer to schedule the products on these time/day slots if feasible but if it will result in an infeasible solution, then relax the condition so one/more/all of the products can be assigned to other times/days.
I am working on a resource scheduling problem, allocating a finite set of products to a finite set of time/day slots over a month. I have a table (INPUT_DATA) of product IDs (PROD_ID), and predicted sales amounts when the PROD_ID is sold during a particular time/day slot. I am using Proc Optmodel MILP to maximize the total predicted sales amount for the month by allocating the PROD_IDs to available slots.
Current Working Constraints:
Desired Constraints:
Thank you for any help you can provide.
Here's one way to enforce your first desired constraint:
num dayToNum {DayNumbers};
num count init 0;
for {d in DayNumbers} do;
count = count + 1;
dayToNum[d] = count;
end;
print dayToNum;
num N = 2;
con DesiredConstraint1 {<PROD_ID, slot, day> in PROD_SLOT_DAY, <(PROD_ID), slot2, day2> in PROD_SLOT_DAY: dayToNum[day2] in dayToNum[day]+1..dayToNum[day]+N}:
PROD_ASSIGN[PROD_ID, slot, day] + PROD_ASSIGN[PROD_ID, slot2, day2] <= 1;
But notice that this conflicts with your fixed assignments of AAA to days 6, 7, and 8, so the resulting problem is infeasible.
For your second desired constraint, you can use multiple objectives as follows:
max NumPreferences = sum {<PROD_ID, slot, day> in PREF_PROD_SHOWCD inter PROD_SLOT_DAY} PROD_ASSIGN[PROD_ID, slot, day];
solve obj NumPreferences;
con ObjectiveCut: NumPreferences >= NumPreferences.sol;
solve obj TotalPreferenceWeight with milp / primalin;
The idea is to first maximize the number of preferences that can be satisfied, then impose a constraint that enforces that maximum, and then maximize your original objective function. I have used the PRIMALIN option to warm start the second solve with the solution from the first solve.
Here's one way to enforce your first desired constraint:
num dayToNum {DayNumbers};
num count init 0;
for {d in DayNumbers} do;
count = count + 1;
dayToNum[d] = count;
end;
print dayToNum;
num N = 2;
con DesiredConstraint1 {<PROD_ID, slot, day> in PROD_SLOT_DAY, <(PROD_ID), slot2, day2> in PROD_SLOT_DAY: dayToNum[day2] in dayToNum[day]+1..dayToNum[day]+N}:
PROD_ASSIGN[PROD_ID, slot, day] + PROD_ASSIGN[PROD_ID, slot2, day2] <= 1;
But notice that this conflicts with your fixed assignments of AAA to days 6, 7, and 8, so the resulting problem is infeasible.
For your second desired constraint, you can use multiple objectives as follows:
max NumPreferences = sum {<PROD_ID, slot, day> in PREF_PROD_SHOWCD inter PROD_SLOT_DAY} PROD_ASSIGN[PROD_ID, slot, day];
solve obj NumPreferences;
con ObjectiveCut: NumPreferences >= NumPreferences.sol;
solve obj TotalPreferenceWeight with milp / primalin;
The idea is to first maximize the number of preferences that can be satisfied, then impose a constraint that enforces that maximum, and then maximize your original objective function. I have used the PRIMALIN option to warm start the second solve with the solution from the first solve.
@RobPratt Thank you so much. That was brilliant. Now that you point it out, I can see my toy example was infeasible given my requested constraints. However, I'm happy to report that your solution worked perfectly out-of-the box when I inserted it into my real-world project.
{<PROD_ID, slot, day> in PROD_SLOT_DAY, <(PROD_ID), slot2, day2> in PROD_SLOT_DAY: dayToNum[day2] in dayToNum[day]+1..dayToNum[day]+N}
I've been struggling with set notation in Proc Optmodel and you opened up a lot of possibilities to me with this. Please let me know if my pseudo-code understanding of this set is correct.
Your statement creates a single set from two sub-sets.
One, <PROD_ID, slot, day> in PROD_SLOT_DAY
and
Two, for each PROD_ID
above (thus the (PROD_ID)
notation):
extract slot2
and day2
from from the same set, PROD_SLOT_DAY
,
where the numeric equivalent of day2
is in [day+1
, day+6
]
Glad to help. Yes, your understanding is correct (assuming N=6). You can also use the EXPAND statement to help verify what is happening. For example:
expand DesiredConstraint1;
Here are the first several lines of the resulting output:
Constraint DesiredConstraint1[AAA,9,DAY_1,11,DAY_2]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,11,DAY_2] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,11,DAY_3]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,11,DAY_3] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,12,DAY_2]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,12,DAY_2] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,12,DAY_3]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,12,DAY_3] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,9,DAY_3]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,9,DAY_3] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,9,DAY_2]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,9,DAY_2] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,10,DAY_3]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,10,DAY_3] <= 1 Constraint DesiredConstraint1[AAA,9,DAY_1,10,DAY_2]: PROD_ASSIGN[AAA,9,DAY_1] + PROD_ASSIGN[AAA,10,DAY_2] <= 1 |
Registration is now open for SAS Innovate 2025 , our biggest and most exciting global event of the year! Join us in Orlando, FL, May 6-9.
Sign up by Dec. 31 to get the 2024 rate of just $495.
Register now!
Learn how to run multiple linear regression models with and without interactions, presented by SAS user Alex Chaplin.
Find more tutorials on the SAS Users YouTube channel.