Thanks!
Here's a way to solve the problem with the NLP solver in PROC OPTMODEL by using PROC FCMP to define the rampLimitFunction:
data loadData;
input t load;
datalines;
1 100
2 100
3 100
4 110
5 120
6 130
7 170
8 210
9 220
10 230
11 240
12 235
13 245
14 250
15 260
16 270
17 300
18 300
19 290
20 280
21 270
22 260
23 200
24 150
;
proc fcmp outlib=work.funcs.test;
function rampLimitFunction(g,x);
if g = 1 then do;
if x <= 200 then limit = 25 + ((25-5)/(200-50))*(x-200);
else limit = 25 + ((10-25)/(300-200))*(x-200);
end;
else if g = 2 then do;
if x <= 100 then limit = 50 + ((50-30)/(100-10))*(x-100);
else limit = 50 + ((30-50)/(200-100))*(x-100);
end;
return (limit);
endsub;
run;
option cmplib=work.funcs;
data fcmpTest1;
do x = 50 to 300;
y = rampLimitFunction(1,x);
output;
end;
run;
proc sgplot data=fcmpTest1;
series x=x y=y;
run;
data fcmpTest2;
do x = 10 to 200;
y = rampLimitFunction(2,x);
output;
end;
run;
proc sgplot data=fcmpTest2;
series x=x y=y;
run;
proc optmodel;
set GENERATORS = 1..2;
set PERIODS;
num tmax = max {t in PERIODS} t;
num q {GENERATORS} = [0.1 0.2];
num c {GENERATORS} = [20 30];
num load {PERIODS};
read data loadData into PERIODS=[t] load;
var Output {GENERATORS, PERIODS};
min Objective = sum {g in GENERATORS, t in PERIODS}
(q[g]*Output[g,t]^2 + c[g]*Output[g,t]);
con MeetLoad {t in PERIODS}:
sum {g in GENERATORS} Output[g,t] = load[t];
for {t in PERIODS} do;
Output[1,t].lb = 50;
Output[1,t].ub = 300;
Output[2,t].lb = 10;
Output[2,t].ub = 200;
end;
con RampLower {g in GENERATORS, t in PERIODS diff {tmax}}:
-rampLimitFunction(g,Output[g,t]) <= Output[g,t+1] - Output[g,t];
con RampUpper {g in GENERATORS, t in PERIODS diff {tmax}}:
rampLimitFunction(g,Output[g,t]) >= Output[g,t+1] - Output[g,t];
solve;
print Output;
quit;
Hi Rob, thank you very much for the reply. I created a small case to show the kind of problem I am solving.
Are G1[t] and G2[t] required to take integer values?
Here's a way to solve the problem with the NLP solver in PROC OPTMODEL by using PROC FCMP to define the rampLimitFunction:
data loadData;
input t load;
datalines;
1 100
2 100
3 100
4 110
5 120
6 130
7 170
8 210
9 220
10 230
11 240
12 235
13 245
14 250
15 260
16 270
17 300
18 300
19 290
20 280
21 270
22 260
23 200
24 150
;
proc fcmp outlib=work.funcs.test;
function rampLimitFunction(g,x);
if g = 1 then do;
if x <= 200 then limit = 25 + ((25-5)/(200-50))*(x-200);
else limit = 25 + ((10-25)/(300-200))*(x-200);
end;
else if g = 2 then do;
if x <= 100 then limit = 50 + ((50-30)/(100-10))*(x-100);
else limit = 50 + ((30-50)/(200-100))*(x-100);
end;
return (limit);
endsub;
run;
option cmplib=work.funcs;
data fcmpTest1;
do x = 50 to 300;
y = rampLimitFunction(1,x);
output;
end;
run;
proc sgplot data=fcmpTest1;
series x=x y=y;
run;
data fcmpTest2;
do x = 10 to 200;
y = rampLimitFunction(2,x);
output;
end;
run;
proc sgplot data=fcmpTest2;
series x=x y=y;
run;
proc optmodel;
set GENERATORS = 1..2;
set PERIODS;
num tmax = max {t in PERIODS} t;
num q {GENERATORS} = [0.1 0.2];
num c {GENERATORS} = [20 30];
num load {PERIODS};
read data loadData into PERIODS=[t] load;
var Output {GENERATORS, PERIODS};
min Objective = sum {g in GENERATORS, t in PERIODS}
(q[g]*Output[g,t]^2 + c[g]*Output[g,t]);
con MeetLoad {t in PERIODS}:
sum {g in GENERATORS} Output[g,t] = load[t];
for {t in PERIODS} do;
Output[1,t].lb = 50;
Output[1,t].ub = 300;
Output[2,t].lb = 10;
Output[2,t].ub = 200;
end;
con RampLower {g in GENERATORS, t in PERIODS diff {tmax}}:
-rampLimitFunction(g,Output[g,t]) <= Output[g,t+1] - Output[g,t];
con RampUpper {g in GENERATORS, t in PERIODS diff {tmax}}:
rampLimitFunction(g,Output[g,t]) >= Output[g,t+1] - Output[g,t];
solve;
print Output;
quit;
Thank you very much! Your model is clearer and easier than I thought because your rampLimitFunction has if statements. I originally thought I will need to introduce a integer variable for each segment of the rampLimit function for each hour each generator. But is it easy to extend the model to include 1000 generators and each rampLimitFunction can have up to 10 segments? Looks like the rampLimitFunction will be very large and hard to write manually.
Here's an alternative approach that uses implicit variables instead of PROC FCMP. First populate a data set:
data rampData;
input generator breakpoint output rate;
datalines;
1 1 50 5
1 2 200 25
1 3 300 10
2 1 10 30
2 2 100 50
2 3 200 30
;
And then use the IMPVAR statement in PROC OPTMODEL:
set BREAKPOINTS = 1..3;
num output_gb {GENERATORS, BREAKPOINTS};
num rate_gb {GENERATORS, BREAKPOINTS};
read data rampData into [generator breakpoint] output_gb=output rate_gb=rate;
impvar RampLimitFunction {g in GENERATORS, t in PERIODS} =
if Output[g,t] <= output_gb[g,2]
then rate_gb[g,1] + ((rate_gb[g,2]-rate_gb[g,1])/(output_gb[g,2]-output_gb[g,1]))*(Output[g,t]-output_gb[g,1])
else rate_gb[g,2] + ((rate_gb[g,3]-rate_gb[g,2])/(output_gb[g,3]-output_gb[g,2]))*(Output[g,t]-output_gb[g,2]);
con RampLower {g in GENERATORS, t in PERIODS diff {tmax}}:
-RampLimitFunction[g,t] <= Output[g,t+1] - Output[g,t];
con RampUpper {g in GENERATORS, t in PERIODS diff {tmax}}:
RampLimitFunction[g,t] >= Output[g,t+1] - Output[g,t];
For either approach, if you have more generators and segments, you can use the SAS macro language to generate the RampLimitFunction code.
RobPratt, thank you! It is amazing that impvar can have if statement too, which simplify the model a lot.
Glad to help. I should mention that you can make the code more data driven by introducing a data set:
data generatorData;
input generator q c lb ub;
datalines;
1 0.1 20 50 300
2 0.2 30 10 200
;
And then reading the data in PROC OPTMODEL:
set GENERATORS;
num q {GENERATORS};
num c {GENERATORS};
num lb {GENERATORS};
num ub {GENERATORS};
read data generatorData into GENERATORS=[generator] q c lb ub;
Then you can include the bounds in the VAR statement:
var Output {g in GENERATORS, PERIODS} >= lb[g] <= ub[g];
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.