Originally posted by: SystemAdmin
[vamos said:]
//This is what i have done below, without having stockpiles it works fine. The periods are linear I just need to put any material that is not
//sent to the mill to be stockpiled and the grade changed to balance the stockpile inventory. And of course the stockpile can be used
//to feed the processing plant (mill) as well. Mine material is not homogenous hence the stockpile properties are dynamic based on what goes in and
//out.
//There are three general stockpile grade calculation methods, 1.Blend (as is this case), 2. First in First Out and 3. Last in first out.
//If you omit the constraint ctCalcGrade the optimisation fails with CPLEX Error 5002: Q in objective is not positive semi-definite.
{int} Periods = ...;
int NPeriods = ...;
int SL_NPeriods = NPeriods - 1;
range NPeriod = 1..NPeriods;
{int} SPPeriods = ...;
range ZPeriods = 0..NPeriods;
//Array types for mine and stockpile mill feed sources
//and plants that require filling
{string} FeedMaterialNames = ...;
{string} Campaigna = ...;
//Data arrays for properties of feed material and processing plants
tuple RockProperties {
string ID;
float Tonnes;
float Cu;
//float Cus;
//float Acido;
//float CO3;
//float Pot_Finos;
//float Mezcla;
int Period;
string CampaignType;
//float RehandleCosts;
}
{RockProperties} FeedData = ...;
tuple MillProperties {
string CampaignType;
float Tonnes;
//float maxCO3;
//float maxAcido;
//float maxMezcla;
//float Solub;
//float maxCu;
int Period;
}
{MillProperties} MillData = ...;
tuple PeriodMaterialName {
string ID;
int Period;
}
tuple PeriodMatName {
PeriodMaterialName PN;
string c;
}
{PeriodMatName} PeriodMatNames = { < <I,Period>, Campaign > | <I,tonnes,Cu,Period,Campaign> in FeedData };
{PeriodMaterialName} PeriodMaterialNames = { PN | <PN,c> in PeriodMatNames};
float Grade[PeriodMatNames] = [ <<m.ID, m.Period>,m.CampaignType> : m.Cu | m in FeedData ];
float Tonnes[PeriodMatNames] = [ <<m.ID, m.Period>,m.CampaignType> : m.Tonnes | m in FeedData ];
tuple Campaign{
string c;
int p;
}
{Campaign} Campaigns = { <c,p> | <c,t,p> in MillData};
float CampaignTonnes[Campaigns] = [ <c.CampaignType, c.Period> : c.Tonnes | c in MillData];
{PeriodMaterialName} CampaignPeriods[c in Campaigna] = { n | <n,c> in PeriodMatNames};
//Declare descision variable for holding of solution.
//tonnes that are optimal from each of the possible material sources
//and to which plants they should go to
dvar float+ MineFeed[PeriodMatNames];
dvar float+ SPFeed[PeriodMatNames];
dvar float+ SPGrade[PeriodMatNames];
dvar float+ SPBalance[PeriodMatNames];
maximize
sum(<<i,n>,c> in PeriodMatNames, p in 1..NPeriods)
(MineFeed[<<i,p>,c>] * Grade[<<i,p>,c>] + SPFeed[<<i,p>,c>]*SPGrade[<<i,p-1>,c>]);
//Constraints
subject to {
//Make sure do not feed more material than required for campaign
forall (c in Campaigna, p in Periods)
ctCampaignTonnes:
sum(<i,p> in CampaignPeriods[c]) (MineFeed[<<i,p>,c>] + SPFeed[<<i,p>,c>]) <= CampaignTonnes[<c,p>];
//Do not feed more material than available for each period.
forall (m in PeriodMatNames)
ctFeedTonnes:
MineFeed[m] <= Tonnes[m];<br />
forall (<<i,n>,c> in PeriodMatNames, p in 1..NPeriods)
ctSPFeedTonnes:
SPFeed[<<i,p>,c>] <= SPBalance[<<i,p-1>,c>];
forall (m in PeriodMatNames)
ctMaxSPFeed:
SPFeed[m] <= 400000;<br />
//Period 0 is intialisation of stockpile material
forall (<<i,0>,c> in PeriodMatNames)
ctMineFeedP0:
MineFeed[<<i,0>,c>] == 0;
forall (<<i,0>,c> in PeriodMatNames)
ctSPFeedP0:
sum(<i,0> in CampaignPeriods[c])
SPFeed[<<i,0>,c>] == 0;
forall (<<i,0>,c> in PeriodMatNames)
ctSPGradeP0:
sum(<i,0> in CampaignPeriods[c])
SPGrade[<<i,0>,c>] == Grade[<<i,0>,c>];
forall (<<i,0>,c> in PeriodMatNames)
ctSPBalanceP0:
SPBalance[<<i,0>,c>] == Tonnes[<<i,0>,c>];
forall (<<i,n>,c> in PeriodMatNames, p in 1..NPeriods)
ctCalcSPBalance:
(Tonnes[<<i,p>,c>]-MineFeed[<<i,p>,c>] - SPFeed[<<i,p>,c>] + SPBalance[<<i,p-1>,c>]) == SPBalance[<<i,p>,c>];
forall (<<i,n>,c> in PeriodMatNames, p in 1..NPeriods)
ctCalcGrade:
SPGrade[<<i,p>,c>]*SPBalance[<<i,p>,c>] - (SPBalance[<<i,p>,c>]
- Tonnes[<<i,p>,c>] + MineFeed[<<i,p>,c>])*SPGrade[<<i,p-1>,c>] -
(Tonnes[<<i,p>,c>]-MineFeed[<<i,p>,c>])*Grade[<<i,p>,c>]== 0 ;
}
tuple result {
int Period;
string Campaign;
string MineFeedMaterial;
float MineFeedTonnes;
//float StockpileFeedTonnes;
}
//send results to database
{result} Result =
{ <p,c,i,MineFeed[<<i,p>,c>]> | <<i,p>,c> in PeriodMatNames };
execute DISPLAY_RESULT{
writeln("Result = ",Result);
}
///////Test Data file/////////////
Periods = {0,1,2,3};
SPPeriods = {0,1,2};
NPeriods = 3;
FeedMaterialNames = {HGOX, HGSUL, LGOX, LGSUL};
Campaigna = {Oxido, Sulfuro};
FeedData = {<HGOX, 100000, 1.6, 0, Oxido>,<HGOX, 300000, 1.8, 1, Oxido>,<HGOX, 250000, 1.4, 2, Oxido>,<HGOX, 250000, 1.7, 3, Oxido>,
<HGSUL, 150000, 2.2, 0, Sulfuro>,<HGSUL, 300000, 2.0, 1, Sulfuro>,<HGSUL, 325000, 1.9, 2, Sulfuro>,<HGSUL, 325000, 1.9, 3, Sulfuro>,
<LGOX, 1350000, 0.5, 0, Oxido>,<LGOX, 350000, 0.65, 1, Oxido>,<LGOX, 100000, 0.43, 2, Oxido>,<LGOX, 120000, 0.43, 3, Oxido>,
<LGSUL, 327000, 0.5, 0, Sulfuro>,<LGSUL, 427000, 0.47, 1, Sulfuro>,<LGSUL, 234000, 0.56, 2, Sulfuro>,<LGSUL, 234000, 0.56, 3, Sulfuro>};
MillData = {<Oxido,0,0>,<Sulfuro,0,0>,<Oxido,425000,1>,<Sulfuro,425000,1>,<Oxido,425000,2>,<Sulfuro,425000,2>,<Oxido,425000,3>,<Sulfuro,425000,3>};
//Result to DBUpdate(db,INSERT INTO OptimisedMillFeed(Period,PlanID,Plant,FeedSourceID,Tonnes) VALUES(?,?,?,?,?));
#DecisionOptimization#OPLusingCPLEXOptimizer