* Supply chain coordination problem
 * OPL model for an intermediate agent, 
 * i.e. an agent that receives components from, and delivers products to other agents in the problem.
 * The products of this agent are the components of the receiving agent.
 * The components of this agent are the products of the agent supplying this agent.


// Time horizon
int numPeriods = ...; 
range horizon 1..numPeriods;
range time 0..numPeriods;

// Number of product configurations
int numProducts = ...; 
range products 1..numProducts;

// Number of components
int numComponents = ...; 
range components 1..numComponents;

// Number of orders
int maxNumProductOrders[products]  = ...;
int maxNumComponentOrders[components]  = ...;

// Bill Of Materials
int bom[products,components] = ...; 

// The batchsize of a delivery for a component/product
int productBatchSize[products] = ...;
int componentBatchSize[components] = ...;

// The lead time/delivery time for a particular product
// I.e. the number of periods between an order being shipped 
// and the order arriving at the customer
int leadtime[products] = ...;
// Number of processing cycles for each configuration
int cycles[products]= ...; 

// Number of cycles required to setup a configuration
int setupCycles[products] = ...;

// Factory capacity
int capacity[horizon] = ...;  

// Starting product inventory
int openingProductInventory[products] = ...;

// Starting component inventory
int openingComponentInventory[components] = ...;

// Holding costs
int productHoldingCost[products] = ...;
int componentHoldingCost[components] = ...;

// Setup costs
int setupCosts[products] = ...;

// Constants
int maxManufacture = max(t in horizon) capacity[t];
int maxComponentOrders = max(c in components) maxNumComponentOrders[c];
int maxProductOrders = max(p in products) maxNumProductOrders[p];

 * (variables constrained with other agents)

// The number of orders for a each product to be delivered 
// to other agents in each period.
var int productDeliverySchedule[products,horizon] in 0..maxProductOrders;

// The number of orders for a each component to be delivered 
// by other agents to this agent in each period.
int componentDeliverySchedule[components,horizon] = 0..maxComponentOrders;

 * (variables not constrained with other agents)

// 0/1 variable indicating whether or not an order is made for a component in a particular period
var int isorder[horizon,components]  in 0..1; 

// 0/1 variable indicating whether or not a product will be built in a particular period
var int isbuilt[horizon,products]  in 0..1; 

// The number of a product built in a particular period
var int manufacture[horizon,products] in 0..maxManufacture; 

 * (additional variables used to simplify the model specification)

// Expected inventory arriving for each component in the time horizon
var float+ componentArrivals[horizon,components];

// Remaining product inventory after each period
var float+ productInventory[time,products]; 

// Remaining component inventory after each period
var float+ componentInventory[time,components]; 

// The quantity of product delivered in each period
var float+ deliveryQuantity[horizon, products]; 

// Components needed on a particular day to produce everything in manufacture
var float+ componentsUsed[horizon, components]; 


   // Total cost
   sum (t in horizon, p in products) (
      (productHoldingCost[p]*productInventory[t,p]) +
                                  ) +   
   sum (t in horizon, c in components) (
      (componentHoldingCost[c]*componentInventory[t,c]) +
      (isorder[t,c] * deliveryCost[c])

subject to
   // Component arrivals
   forall(t in horizon, c in components) componentArrivals[t,c] = componentBatchSize[c] * componentDeliverySchedule[c,t];
   // Set the isorder variable correctly 
   forall(t in horizon, c in components) maxNumComponentOrders[c] * isorder[t,c] >= componentDeliverySchedule[c,t];
   // Opening product inventory
   forall(p in products) productInventory[0,p]=openingProductInventory[p];
   // Opening component inventory
   forall(c in components) componentInventory[0,c]=openingComponentInventory[c];

   // Calculate the number of products delivered
   forall(p in products) forall (t in 1..numPeriods-leadtime[p]) deliveryQuantity[t,p] = productBatchSize[p] * productDeliverySchedule[p,t+leadtime[p]];
   forall(p in products) forall (t in [numPeriods-leadtime[p]+1..numPeriods]) deliveryQuantity[t,p] = 0;   
   // Set the isbuilt variable correctly - should be 1 if any of that product is built on that day
   forall(t in horizon, p in products) maxManufacture * isbuilt[t,p] >= manufacture[t,p];

   // Capacity constraint for manufacturing decision
   // The factory's capacity for each day cannot be exceeded
   forall(t in horizon) sum(p in products) (manufacture[t,p]*cycles[p] + isbuilt[t,p]*setupCycles[p]) <= capacity[t];

   // The number of components needed is calculated by multiplying manufacturing decision by BOM
   forall(t in horizon, c in components) componentsUsed[t,c] = sum(p in products) manufacture[t,p]*bom[p,c];
   // Calculate the expected excess product inventory of each period
   forall(t in horizon, p in products) productInventory[t,p] = productInventory[t-1,p] + manufacture[t,p] - deliveryQuantity[t,p];     

   // Calculate the expected excess component inventory of each period
   forall(t in horizon, c in components) componentInventory[t,c] = componentInventory[t-1,c] + componentArrivals[t,c] - componentsUsed[t,c];