0
votes

Let's say you own a fruit store and want to find the optimum number of apples to sell each day. However, there is a different price associated depending on how many apples are sold (you have no say in these bundled price pairs).

e.g.

                                  Day_1                            Day_2
Daily_price_offers = [[(5 apples,$8),(10 apples,$4)] , [(7 apples,$6),(10 apples,$4)], ...]

Now, suppose a customer wishes to purchase 7 apples on Day 1 and 9 apples on Day 2, then they are priced based on the previous bundled price (like a step-wise function). So, in this case they pay $8 on the first day and $6 on the second day.

The Goal

What is the best way to set this up in Pyomo, so that a step-wise function is applied when interpolating? I have created a solution that picks the highest value of the given bundles each day, but I am after the best way for finding the in-between values. Note, this would mean a new variable would need to be defined for the optimal number of apples sold each day.

This example uses integers, but it would be great to be able to handle floating point quantities as well (for different applications).

My Attempt

from pyomo.environ import *

# Inputs
daily_price_offer = [[(5,8),(10,4)],[(7,6),(10,4)],[(4,6),(10,3)]]
max_apples_total = 14
options_per_day = 2

# Define model and solver
model = ConcreteModel()
solver = SolverFactory('gurobi') # Can use gurobi or glpk

# Define variables
model.DAYS = range(len(daily_price_offer))
model.OPTIONS = range(options_per_day)
model.x = Var(model.DAYS, model.OPTIONS, within=Binary)

# Constraint functions
def daily_sale_constraint(model, i):
    "limited to 1 option in a given day"
    return sum(model.x[i,:]) <= 1

# Set constraints
model.weight = Constraint(expr = sum(daily_price_offer[i][j][0] * model.x[i,j] for i in model.DAYS for j in range(options_per_day)) <= max_apples_total)              
model.discharge = Constraint(model.DAYS, rule=daily_sale_constraint)

# Set objective
model.value = Objective(expr = sum(daily_price_offer[i][j][1] * daily_price_offer[i][j][0] * model.x[i,j] for i in model.DAYS for j in range(options_per_day)), sense = maximize)

# Solve
res = solver.solve(model)

# Print results
print('Chosen bundle:',[value(model.x[i,j]) for i in model.DAYS for j in range(options_per_day)])
print('Total revenue: $',model.value())
1
Explain your pricing, please... I don't see how you got to $8 for 7 apples on Day 1. Also, are you saying that the first 5 apples you buy costs a total of $8 or ??? very unclear. I don't see any interpolation here either. Whoever is buying will buy from the cheapest group first, correct?AirSquid
Hi @AirSquid, this a contrived example so apologies if it seems strange (the numbers don't make intuitive sense, so please don't read into that part too much). On Day_1, 5 apples costs $8, or you can buy 10 apples for $4 total. As 7 is between 5 and 10, we can are charged $8 (as this is the previous bundled price). It is like a step-wise functionJwem93
This makes no sense. By your logic, I could leave the store with 5 apples for $8, or get 9 apples for the same total $8 cost, or I could get 10 apples for $4. Why can you not develop a price scheme for each apple, even if it is non-constant.AirSquid

1 Answers

1
votes

Interpolation is often described as "piecewise linear functions". There are several ways to do this. The simplest is likely using so-called SOS2 variables (https://en.wikipedia.org/wiki/Special_ordered_set). There are many other ways, however. These approaches can be implemented yourself, or you can use the piecewise library https://pyomo.readthedocs.io/en/stable/library_reference/kernel/piecewise/piecewise.html. See also How to Formulate a Piecewise Step Function in pyomo for an example.

Online help is to be had using:

>>> from pyomo.core import *
>>> help(Piecewise)
Help on class Piecewise in module pyomo.core.base.piecewise:

class Piecewise(pyomo.core.base.block.Block)
 |  Piecewise(*args, **kwds)
 |
 |      Adds piecewise constraints to a Pyomo model for functions of the
 |      form, y = f(x).
 |
 |      Usage:
 |              model.const = Piecewise(index_1,...,index_n,yvar,xvar,**Keywords)
 |              model.const = Piecewise(yvar,xvar,**Keywords)
 |
 |      Keywords:
 |
 |  -pw_pts={},[],()
 |            A dictionary of lists (keys are index set) or a single list
 |            (for the non-indexed case or when an identical set of
 |            breakpoints is used across all indices) defining the set of
 |            domain breakpoints for the piecewise linear
 |            function. **ALWAYS REQUIRED**
 |
 |  -pw_repn=''
 |            Indicates the type of piecewise representation to use. This
 |            can have a major impact on solver performance.
 |            Choices: (Default 'SOS2')
 |
 |               ~ + 'SOS2'      - Standard representation using sos2 constraints
 |               ~   'BIGM_BIN'  - BigM constraints with binary variables.
 |                                 Theoretically tightest M values are automatically
 |                                 determined.
 |               ~   'BIGM_SOS1' - BigM constraints with sos1 variables.
 |                                 Theoretically tightest M values are automatically
 |                                 determined.
 |               ~*+ 'DCC'       - Disaggregated convex combination model
 |               ~*+ 'DLOG'      - Logarithmic disaggregated convex combination model
 |               ~*+ 'CC'        - Convex combination model
 |               ~*+ 'LOG'       - Logarithmic branching convex combination
 |               ~*  'MC'        - Multiple choice model
 |               ~*+ 'INC'       - Incremental (delta) method
 |
 |             + Supports step functions
 |             * Source: "Mixed-Integer Models for Non-separable Piecewise Linear
 |                        Optimization: Unifying framework and Extensions" (Vielma,
 |                        Nemhauser 2008)
 |             ~ Refer to the optional 'force_pw' keyword.
 |
 |  -pw_constr_type=''
 |            Indicates the bound type of the piecewise function.
 |            Choices:
-- More  --

Warning: the BIGM methods are implemented incorrectly. Don't use them.