!!!!!!!Created by Rebekka Resell, spring 2017

model MaritimePickupDelivery

uses "mmxprs";!gain access to the Xpress-Optimizer solver

options explterm
options noimplicit

uses "mmxprs", "mmodbc","mmsystem";

parameters
    Datafile = "Case2.txt";
    SolutionTime = 10800;
end-parameters

! Enable message printing by the Optimizer 
setparam("xprs_verbose", true);
! Turn off the automatic cut generation
setparam("xprs_cutstrategy", 0);
! Turn off the automatic heuristics
setparam("xprs_heurstrategy", 0);

!****************************************************************************
!   Declaring sets
!****************************************************************************

declarations 
    nCargoes:               integer;        !number of nodes/cargoes
    nTanks:                 integer;        !number of tanks on the vessel
    Nodes:                  set of integer;
    PickupNodes:            set of integer; 
    DeliveryNodes:          set of integer; 
    Suppliers:              set of integer;     ![1 2 3 4] hypothetical
    CargoConditions:        set of integer;     ![clean, dirty], [1,2]
    CargoNodes:             set of integer;
    solutionTime1:          real;
end-declarations 

initializations from Datafile
    nCargoes;
    nTanks;
    Nodes;
    PickupNodes;
    DeliveryNodes;
    Suppliers;
    CargoConditions;
    CargoNodes;
end-initializations



finalize(Nodes);
finalize(PickupNodes);
finalize(DeliveryNodes);
finalize(Suppliers);
finalize(CargoConditions);
finalize(CargoNodes);

!****************************************************************************
!   Declaring parameters
!****************************************************************************

declarations
    VesselCapacity:         real;
    TankCapacity:           real; !Assuming equally sized tanks
    TotalDeliveryLoad:      dynamic array(Suppliers)                    of real;
    TotalPickupLoad:        dynamic array(Suppliers)                    of real;
    CargoQuantityDel:       dynamic array(DeliveryNodes, Suppliers)     of real;
    CargoQuantityPick:      dynamic array(PickupNodes, Suppliers)       of real;
    CQPick:                 dynamic array(PickupNodes)                  of real;
    CQDel:                  dynamic array(DeliveryNodes)                of real;
    VesselSpeedkmh:         real;   
    SailingDistanceskm:     dynamic array(Nodes, Nodes)                 of real;
    SailingTime:            dynamic array(Nodes, Nodes)                 of real;
    DemandTime:             dynamic array(CargoNodes)                   of real;
    LoadingRate:            real;                                       
    LoadingTimeDel:         dynamic array(DeliveryNodes)                of real;
    LoadingTimePick:        dynamic array(PickupNodes)                  of real;
    
    A:                      dynamic array(Nodes, Nodes)                 of boolean;
    BigM:                   integer;!               of real;
    BigB1:                  real;                   
    BigB2:                  real;
    teller:                 integer;
    index:                  integer;
    !a:                     real;   !Acts as temporary variable 
    !b:                     real;   !Acts as a temporary variable
end-declarations

initializations from Datafile           
    TankCapacity;
    CargoQuantityDel;
    CargoQuantityPick;
    VesselSpeedkmh;
    SailingDistanceskm;
    DemandTime;
    LoadingRate;
    BigM;
    A;
end-initializations


!***************************************************************************
!Preprocessing of Parameters
!***************************************************************************

!Vessel Capacity
VesselCapacity := TankCapacity*nTanks;

!Total Delivery Load
forall(ss in Suppliers) do
    TotalDeliveryLoad(ss) := sum(ii in DeliveryNodes) CargoQuantityDel(ii,ss);
end-do

!Total Pickup Load
forall(ss in Suppliers) do
    TotalPickupLoad(ss) := sum(ii in PickupNodes) CargoQuantityPick(ii,ss);
end-do


!Big B1 and Big B2
BigB1 := sum(ss in Suppliers) TotalDeliveryLoad(ss);
BigB2 := sum(ss in Suppliers) TotalPickupLoad(ss);


!Time of unloading delivery cargo i
forall(ii in DeliveryNodes) do
CQDel(ii) := sum(ss in Suppliers) CargoQuantityDel(ii,ss); !Bound in the assumption of only ONE supplier of cargo to an installation
LoadingTimeDel(ii):= CQDel(ii)/LoadingRate;
end-do

!Time of loading pickup cargo i
forall(ii in PickupNodes) do
CQPick(ii) := sum(ss in Suppliers) CargoQuantityPick(ii,ss);
LoadingTimePick(ii) := CQPick(ii)/LoadingRate;
end-do

!************************************************
!Calculating sailing times
!************************************************

!Calculate sailing time
forall(ii in Nodes, jj in Nodes | ii<>nCargoes+1 and jj<>0) do
SailingTime(ii,jj) := SailingDistanceskm(ii,jj)/VesselSpeedkmh;
end-do


!****************************************************************************
!   Create variables
!****************************************************************************

declarations
    x: dynamic array(Nodes, Nodes)                          of mpvar;
    t: dynamic array(Nodes)                                 of mpvar;
    l: dynamic array(Nodes, Suppliers, CargoConditions)     of mpvar;
    h: dynamic array(Nodes, Suppliers, CargoConditions)     of mpvar;
    y: dynamic array(CargoNodes)                            of mpvar;
    w: dynamic array(Nodes)                                 of mpvar;                                               !of mpvar;  !!***
end-declarations    

!x-variable, binary - 1 if vessel v sails directly from node i to node j. 
!Except when nodes are equal, from end-base, and to start-base
forall(ii in Nodes, jj in Nodes | ii<>nCargoes+1 and jj<>0) do
    if (A(ii,jj)) then
        create(x(ii,jj));
        x(ii,jj) is_binary;
    end-if
end-do

!time-variable, continuous
forall(ii in Nodes) do
    create(t(ii));
end-do

!load-variable, continuous
forall(ii in Nodes, ss in Suppliers, cc in CargoConditions | ii<>nCargoes+1) do
    create(l(ii,ss,cc));
end-do

!occupied tanks-variable, integer
forall(ii in Nodes, ss in Suppliers, cc in CargoConditions) do
    create(h(ii,ss,cc));
    h(ii,ss,cc) is_integer;
end-do

!gap-variable, continuous
forall(ii in CargoNodes) do
    create(y(ii));
end-do

!available tanks-variable, integer
forall(ii in CargoNodes) do
    create(w(ii)); 
    w(ii) is_integer;
end-do

!****************************************************************************
!   Declare Constraints
!****************************************************************************

 
declarations
    Gap:                    linctr;
    AllNodesVisited:        dynamic array(Nodes)                            of linctr;
    FlowFromOrigin:         linctr;
    FlowConservation1:      dynamic array(Nodes)                            of linctr;
    FlowConservation2:      dynamic array(Nodes)                            of linctr;
    FlowToDestination:      linctr;
    TimeConstraint1:        dynamic array(DeliveryNodes, Nodes)             of linctr;
    TimeConstraint2:        dynamic array(PickupNodes, Nodes)               of linctr;
    StartNodeFirst:         dynamic array(Nodes)                            of linctr;
    EndNodeLast:            dynamic array(Nodes)                            of linctr;
    TimeWindowMin:          dynamic array(Nodes)                            of linctr;
    TimeWindowMax:          dynamic array(Nodes)                            of linctr;
    LoadConstraintDel:      dynamic array(Nodes, DeliveryNodes, Suppliers)  of linctr;
    LoadConstraintDel1:     dynamic array(Nodes, DeliveryNodes, Suppliers)  of linctr;
    LoadConstraintDel2:     dynamic array(Nodes, DeliveryNodes, Suppliers)  of linctr;
    LoadConstraintPick:     dynamic array(Nodes, PickupNodes, Suppliers)    of linctr;
    LoadConstraintPick1:    dynamic array(Nodes, PickupNodes, Suppliers)    of linctr;
    LoadConstraintPick2:    dynamic array(Nodes, PickupNodes, Suppliers)    of linctr;
    LoadOrigin:             dynamic array(Suppliers, CargoConditions)       of linctr;
    OccTanks1:              dynamic array(Nodes, Suppliers, CargoConditions) of linctr; 
    NoTanks:                dynamic array(Nodes) of linctr;!linctr;                             !!***
    LimOccupiedTanks:       dynamic array(Nodes)                            of linctr;  
    GapConstraint:          dynamic array(CargoNodes)                       of linctr;
end-declarations

!****************************************************************************
!   Objective Function
!****************************************************************************

Gap := 
    sum(ii in CargoNodes)y(ii) 
    - 
    sum(ii in CargoNodes)w(ii) ;

!****************************************************************************
!   Flow Constraints
!****************************************************************************

!All nodes/cargoes are visited/handled only once
forall (ii in Nodes | ii<>0 and ii<>(nCargoes+1)) do 
    AllNodesVisited(ii) := sum(jj in Nodes) x(ii,jj)= 1;
end-do

!Only one arc from origin
FlowFromOrigin := sum(jj in Nodes) x(0,jj) =1;

!Only one visit per node, has to be an arc out from node i
forall (ii in Nodes | ii<>0 and ii <>(nCargoes+1)) do
    FlowConservation1(ii) := sum(jj in Nodes) x(ii,jj) = 1;
end-do
!Only one visit per node, has to be an arc to node j
forall (jj in Nodes | jj<>0 and jj <>(nCargoes+1)) do
    FlowConservation2(jj) := sum(ii in Nodes) x(ii,jj) = 1;
end-do

!Only one arc to destination 
FlowToDestination := sum(ii in Nodes) x(ii,(nCargoes+1))=1;

!****************************************************************************
!   Time constraints
!****************************************************************************

!Visits start node (node 0) before all other nodes
forall (ii in Nodes | ii<>0) do
    StartNodeFirst(ii) := t(ii) - t(0) >= 0;
end-do

!Time for arrival in j is to be bigger than the time for arrival in i + time spent in i
forall (ii in DeliveryNodes, jj in Nodes | ii<>jj and ii<>nCargoes+1 and jj<>0) do
    TimeConstraint1(ii,jj) := t(ii) + LoadingTimeDel(ii) + SailingTime(ii,jj) - t(jj) - BigM*(1-x(ii,jj)) <= 0;
end-do

!Time for arrival in j is to be bigger than the time for arrival in i + time spent in i
forall (ii in PickupNodes, jj in Nodes | ii<>jj and ii<>nCargoes+1 and jj<>0) do
    TimeConstraint2(ii,jj) := t(ii) + LoadingTimePick(ii) + SailingTime(ii,jj) - t(jj) - BigM*(1-x(ii,jj)) <= 0;
end-do

!Visits end node (node nCargoes+1) after all other nodes
forall (ii in Nodes | ii<>(nCargoes+1)) do
    EndNodeLast(ii) := t(nCargoes+1) - t(ii) >= 0;
end-do



!****************************************************************************
!   Load constraints
!****************************************************************************

!forall(ii in Nodes, ss in Suppliers | ii=0) do
forall(ss in Suppliers) do
    LoadOrigin(ss,1) := l(0,ss,1) = sum(jj in DeliveryNodes) CargoQuantityDel(jj,ss);
end-do


forall(ii in Nodes, jj in DeliveryNodes, ss in Suppliers | ii<>jj and ii<>nCargoes+1)   do
    LoadConstraintDel(ii,jj,ss) := l(ii,ss,1) - CargoQuantityDel(jj,ss) - l(jj,ss,1) - BigB1*(1-x(ii,jj)) <= 0;
    LoadConstraintDel1(ii,jj,ss) := l(ii,ss,2) - l(jj,ss,2) + VesselCapacity*x(ii,jj) <= VesselCapacity;
    LoadConstraintDel2(ii,jj,ss) := l(ii,ss,2) - l(jj,ss,2) - VesselCapacity*x(ii,jj) >= -VesselCapacity;
end-do

forall(ii in Nodes, jj in PickupNodes, ss in Suppliers | ii<>jj and ii<>nCargoes+1) do
    LoadConstraintPick(ii,jj,ss) := l(ii,ss,2) + CargoQuantityPick(jj,ss) - l(jj,ss,2) - BigB2*(1-x(ii,jj)) <= 0;
    LoadConstraintPick1(ii,jj,ss) := l(ii,ss,1) - l(jj,ss,1) + VesselCapacity*x(ii,jj) <= VesselCapacity;
    LoadConstraintPick2(ii,jj,ss) := l(ii,ss,1) - l(jj,ss,1) - VesselCapacity*x(ii,jj) >= -VesselCapacity;
end-do




!****************************************************************************
!Tank constraints
!****************************************************************************

!Finding number of occupied tanks
forall(ii in Nodes, ss in Suppliers, cc in CargoConditions) do
    OccTanks1(ii,ss,cc) := h(ii,ss,cc) - (l(ii,ss,cc)/TankCapacity) >= 0;
end-do

!No more occupied tanks than there are tanks on the vessel
forall(ii in Nodes) do
    LimOccupiedTanks(ii) := sum(ss in Suppliers, cc in CargoConditions) h(ii,ss,cc) <= nTanks;
end-do

!Ensure correct no. of tanks are in use
forall(ii in Nodes) do
NoTanks(ii) := w(ii) + sum(ss in Suppliers, cc in CargoConditions) h(ii,ss,cc) = nTanks; !!***
end-do

!****************************************************************************
!Sequence constraints
!****************************************************************************

!Forcing the vessel to service the pickup cargo before the delivery cargo at the same installation
forall(ii in PickupNodes, jj in DeliveryNodes | SailingTime(ii,jj) = 0) do
    x(ii,jj) = 1;
end-do

forall(ii in DeliveryNodes, jj in PickupNodes | SailingTime(ii,jj) = 0) do
    x(ii,jj) = 0;
end-do

!****************************************************************************
!Gap constraint
!****************************************************************************

forall(ii in CargoNodes) do
    GapConstraint(ii) := t(ii) - DemandTime(ii) = y(ii);
end-do


!****************************************************************************
setparam('xprs_maxtime', SolutionTime);

solutionTime1:=gettime;
minimize(Gap);
!solutionTime2:=gettime;

declarations
    FileName:   string;
end-declarations

FileName := 'Output_' + Datafile + '.txt';

fopen(FileName, F_OUTPUT);

writeln("------------------------------------------------------------------------------");

if(TankCapacity=360) then
    writeln("Total delay is ",getsol(Gap), " hours for Far Scotsman");
else
    writeln("Total delay is ",getsol(Gap), " hours for Far Solitaire");
end-if

writeln("------------------------------------------------------------------------------");

index:=0;
teller:=1; 

while (teller < nCargoes+1) do
forall(jj in Nodes) do
if(getsol(x(index,jj)) > 0.5) then  
    if (index < nCargoes+1 and jj<>nCargoes+1) then
    write("The ship sails directly from ",index, " to ",jj);
    index:=jj;
        if(getsol(y(index)) > 0.5) then
        writeln("    The vessel starts service ", getsol(y(index)), " hours after the demand occurs in node ",index);
        teller:=teller+1;
        else
        writeln("    The vessel services node ",index," on time");
        teller:=teller+1;
        end-if
    else    
    writeln("The ship sails directly from ",index, " to ",jj);
    teller:=teller+1;
    writeln("------------------------------------------------------------------------------");
    end-if
end-if
end-do
end-do



teller:=0;
index:=0;
while (teller < nCargoes) do
forall(jj in CargoNodes) do
    if(getsol(x(index,jj)) > 0.5) then
    writeln("The vessel has ", getsol(w(jj)), " available tanks in node ", jj);
    index:=jj;
    teller:=teller+1;
    end-if
end-do
end-do



writeln("------------------------------------------------------------------------------");
fclose(F_OUTPUT);


end-model