# Pooling example - nonlinear solving.
#
# This example models a problem of pooling crude oil from different sources
# to produce final products with specific properties considering flow and material
# balance constraints.
#
# (C) 1983-2025 Fair Isaac Corporation

import xpress as xp

p = xp.problem()

# Variables for crude oil sources.
crudeA = p.addVariable(name="A", lb=0.0)
crudeB = p.addVariable(name="B", lb=0.0)
crudeC = p.addVariable(name="C", lb=0.0)

# Flow variables.
crudeC_flowX = p.addVariable(name="CX", lb=0.0)
crudeC_flowY = p.addVariable(name="CY", lb=0.0)
pool_flowX   = p.addVariable(name="PX", lb=0.0)
pool_flowY   = p.addVariable(name="PY", lb=0.0)

# Variables for final products.
finalX = p.addVariable(name="X", lb=0.0, ub=100)
finalY = p.addVariable(name="Y", lb=0.0, ub=200)

# Pool quantity variable.
poolQ  = p.addVariable(name="poolQ", lb=0.0)

# Variables for cost and income.
cost    = p.addVariable(name="cost", lb=0.0)
income  = p.addVariable(name="income", lb=0.0)

# Cost and income constraints.
p.addConstraint(cost   == 6*crudeA + 16*crudeB + 10*crudeC,
                income == 9*finalX + 15*finalY)

# Flow balances:
#   Total amount of final products X and Y is the sum of the flow from the pool and the flow directly from crude C.
#   Total amount of crude C is distributed between the flows to final products X and Y.
#   Flow into the pool from crude A and crude B equals the flow out of the pool to final products X and Y.
p.addConstraint(finalX == pool_flowX + crudeC_flowX,
                finalY == pool_flowY + crudeC_flowY,
                crudeC == crudeC_flowX + crudeC_flowY,
                crudeA + crudeB == pool_flowX + pool_flowY)

# Material balances - ensure that the composition of the crude oils and the
# final products meet requirements regarding sulfur content.
pool_sulfur = 3*crudeA + crudeB == (pool_flowX + pool_flowY) * poolQ

p.addConstraint(pool_sulfur,
                pool_flowX * poolQ <= 0.5*crudeC_flowX + 2.5*crudeC_flowY,
                pool_flowY * poolQ <= 1.5*pool_flowY - 0.5*crudeC_flowY)

# Solve this problem with a local solver.
p.controls.nlpsolver = xp.constants.NLPSOLVER_LOCAL    # Solve with a local solver.
p.controls.localsolver = xp.constants.LOCALSOLVER_XSLP    # Choose SLP solver.

# Maximize the profit = difference between income and cost.
p.setObjective(income - cost,sense=xp.maximize)

p.optimize()

print('Solution: income is:', p.getSolution(income), 'and cost is:', p.getSolution(cost))
