Adding Special Constraints

SupplyChainOptimization contains many built-in concepts that enable the modeling of many scenarios. In some special cases specific constraints may need to be considered that are not part of the built-in concepts. SupplyChainOptimization allows for the direct manipulation of the optimization model to add these constraints.

Beware!

Directly manipulating the optimization model requires advanced mathematical modeling knowledge and is not recommended unless absolutely necessary.

When the minimize_costs! function is called it calls two other functions: create_network_cost_minimization_model! and optimize_network_optimization_model!. create_network_cost_minimization_model! creates an optimization model and stores it in the supply chain optimization_model attribute (see Optimization Model). This process is seamless and usually operates behind the scene. However there are cases where knowing about this process can be helpful. One such case is if a specific constraint needs to be added to the optimization model.

In the example below we will use the same setup as in the multi-period network optimization example (see Multi-period Optimization). The one difference is that we now want to control how many nodes can be opened in each time period. Such a constraint may be needed in real-life because the real-estate team, labor team or supply management team cannot handle too many opening at the same time. We will modify the usual call to the optimizer and break it into three parts: the creation of the optimization model, the addition of the constraint on openings, the call to the optimizer.

SupplyChainOptimization.create_network_cost_minimization_model!(sc, HiGHS.Optimizer)

@constraint(sc.optimization_model, sum(sc.optimization_model[:opening][:,1]) == 2)
@constraint(sc.optimization_model, sum(sc.optimization_model[:opening][:,2]) == 1)
@constraint(sc.optimization_model, sum(sc.optimization_model[:opening][:,3]) == 1)

SupplyChainOptimization.optimize_network_optimization_model!(sc)

In the first period we allow two opening (we need at least a plant and a storage location). In the subsequent periods, we allow one opening per period. A list of available variables in the optimization model is provided in the Optimization Model section.

The full code is as below. Notice the use of JuMP, the optimization library, to add the additional constraints.

using CSV using DataFrames using JuMP using HiGHS using SupplyChainModeling using SupplyChainOptimization

nm = tempname() url = "https://raw.githubusercontent.com/plotly/datasets/master/2014uscities.csv" download(url, nm) us_cities = CSV.read(nm, DataFrame) rm(nm)

sort!(us_cities, [:pop], rev=true)

sc = SupplyChain(3)

product1 = Product("Product 1") product2 = Product("Product 2") addproduct!(sc, product1) addproduct!(sc, product2)

for r in eachrow(first(uscities, 10)) supplier = Supplier("Supplier r.name", Location(r.lat + 0.2, r.lon - 0.2, r.name)) addproduct!(supplier, product1; unitcost=1.0) addsupplier!(sc, supplier) end

for r in eachrow(first(uscities, 10)) plant = Plant("Plant r.name", Location(r.lat - 0.2, r.lon - 0.2, r.name); fixedcost= 6000000 + r.pop / 2, initialopened=false) addproduct!(plant, product2; billofmaterial=Dict{Product, Float64}(product1 => 1.0), unitcost=1.0) addplant!(sc, plant) end

for r in eachrow(first(uscities, 10)) storage = Storage("Storage r.name", Location(r.lat + 0.2, r.lon + 0.2, r.name); fixedcost= 2000000 + r.pop / 2, initialopened=false) addproduct!(storage, product2; initialinventory=0, unitholdingcost=0.01) addstorage!(sc, storage) end

for (i, r) in enumerate(eachrow(first(uscities, 100))) customer = Customer("customer i", Location(r.lat, r.lon, r.name)) addcustomer!(sc, customer) adddemand!(sc, customer, product2, [r.pop / 8000 for i in 1:3]) end

for s in sc.suppliers, p in sc.plants addlane!(sc, Lane(s, p; unitcost=haversine(s.location, p.location) / 750)) end

for p in sc.plants, s in sc.storages addlane!(sc, Lane(p, s; unitcost=haversine(p.location, s.location) / 750)) end

for c in sc.customers, s in sc.storages addlane!(sc, Lane(s, c; unitcost=haversine(s.location, c.location) / 250)) end

SupplyChainOptimization.createnetworkcostminimizationmodel!(sc, HiGHS.Optimizer)

@constraint(sc.optimizationmodel, sum(sc.optimizationmodel[:opening][:,1]) == 2) @constraint(sc.optimizationmodel, sum(sc.optimizationmodel[:opening][:,2]) == 1) @constraint(sc.optimizationmodel, sum(sc.optimizationmodel[:opening][:,3]) == 1)

SupplyChainOptimization.optimizenetworkoptimization_model!(sc)