根据Python mystic中的条件最大化总和

根据Python mystic中的条件最大化总和,python,numpy,scipy,mystic,Python,Numpy,Scipy,Mystic,我正试图构建一个基于的白皮书的实现。如果此链接在将来消失,我将在此处的相关部分粘贴: 到目前为止,我目前的实现是相当大的破坏,因为我真的不完全理解如何解决非线性最大化方程 # magical lookup table that returns demand based on those inputs # this will eventually be a db lookup against past years rental activity and not hardcoded to a sp

我正试图构建一个基于的白皮书的实现。如果此链接在将来消失,我将在此处的相关部分粘贴:

到目前为止,我目前的实现是相当大的破坏,因为我真的不完全理解如何解决非线性最大化方程

# magical lookup table that returns demand based on those inputs
# this will eventually be a db lookup against past years rental activity and not hardcoded to a specific value
def demand(dateFirstNight, duration):
    return 1

# magical function that fetches the price we have allocated for a room on this date to existing customers
# this should be a db lookup against previous stays, and not hardcoded to a specific value
def getPrice(date):
    return 75

# Typical room base price
# Defined as: Nominal price of the hotel (usually the average historical price)
nominalPrice = 89

# from the white paper, but perhaps needs to be adjusted in the future using the methods they explain
priceElasticity = 2

# this is an adjustable constant it depends how far forward we want to look into the future when optimizing the prices
# likely this will effect how long this will take to run, so it will be a balancing game with regards to accuracy vs runtime
numberOfDays = 30

def roomsAlocated(dateFirstNight, duration)
    roomPriceSum = 0.0
    for date in range(dateFirstNight, dateFirstNight+duration-1):
        roomPriceSum += getPrice(date)

    return demand(dateFirstNight, duration) * (roomPriceSum/(nominalPrice*duration))**priceElasticity


def roomsReserved(date):
    # find all stays that contain this date, this 


def maximizeRevenue(dateFirstNight):
    # we are inverting the price sum which is to be maximized because mystic only does minimization
    # and when you minimize the inverse you are maximizing!
    return (sum([getPrice(date)*roomsReserved(date) for date in range(dateFirstNight, dateFirstNight+numberOfDays)]))**-1


def constraint(x): # Ol - totalNumberOfRoomsInHotel <= 0
    return roomsReserved(date) - totalNumberOfRoomsInHotel

from mystic.penalty import quadratic_inequality
@quadratic_inequality(constraint, k=1e4)
def penalty(x):
  return 0.0

from mystic.solvers import diffev2
from mystic.monitors import VerboseMonitor
mon = VerboseMonitor(10)

bounds = [0,1e4]*numberOfDays
result = diffev2(maximizeRevenue, x0=bounds, penalty=penalty, npop=10, gtol=200, disp=False, full_output=True, itermon=mon, maxiter=M*N*100)
#基于这些输入返回需求的神奇查找表
#这最终将是对过去几年租赁活动的db查询,而不是硬编码为特定值
def需求(dateFirstNight,持续时间):
返回1
#这是一个神奇的功能,它可以获取我们在这个日期为现有客户分配的房间价格
#这应该是针对先前停留的db查找,而不是硬编码为特定值
价格(日期):
返回75
#典型房间底价
#定义为:酒店的名义价格(通常为历史平均价格)
名义价格=89
#来自白皮书,但可能需要在将来使用他们解释的方法进行调整
价格弹性=2
#这是一个可调整的常数,这取决于我们在优化价格时对未来的展望
#这很可能会影响运行所需的时间,因此这将是一个在准确性和运行时间方面的平衡游戏
numberOfDays=30
def房间位置(dateFirstNight,持续时间)
roomPriceSum=0.0
对于范围内的日期(dateFirstNight、dateFirstNight+duration-1):
roomPriceSum+=getPrice(日期)
退货需求(dateFirstNight,持续时间)*(roomPriceSum/(名义价格*持续时间))**价格弹性
def房间已送达(日期):
#查找所有包含此日期、此
def最大化地点(dateFirstNight):
#我们将要最大化的价格总和倒置,因为mystic只做最小化
#当你最小化逆时,你就是最大化!
返回(总和([getPrice(日期)*房间服务(日期)范围内的日期(dateFirstNight,dateFirstNight+numberOfDays)])**-1

def constraint(x):#Ol-totalNumberOfRoomsInHotel当您要求使用库
mystic
时,您可能不需要在开始非线性优化时进行这种细粒度控制。模块
scipy
应该足够了。以下是一个或多或少完整的解决方案,纠正了我认为原始白皮书中关于定价界限的错误:

import numpy as np
from scipy.optimize import minimize

P_nom = 89
P_max = None
price_elasticity = 2
number_of_days = 7
demand = lambda a, L: 1./L
total_rooms = [5]*number_of_days

def objective(P, *args):
    return -np.dot(P, O(P, *args))

def worst_leftover(P, C, *args):
    return min(np.subtract(C, O(P, *args)))

def X(P, a, L, d, e, P_nom):
    return d(a, L)*(sum(P[a:a+L])/P_nom/L)**e

def d(a, L):
    return 1.

def O_l(P, l, l_max, *args):
    return sum([X(P, a, L, *args)
                for a in xrange(0, l)
                for L in xrange(l-a+1, l_max+1)])

def O(P, *args):
    return [O_l(P, l, *args) for l in xrange(len(P))]

result = minimize(
    objective,
    [P_nom]*number_of_days,
    args=(number_of_days-1, demand, price_elasticity, P_nom),
    method='SLSQP',
    bounds=[(0, P_max)]*number_of_days,
    constraints={
        'type': 'ineq',
        'fun': worst_leftover,
        'args': (total_rooms, number_of_days-1, demand, price_elasticity, P_nom)
    },
    tol=1e-1,
    options={'maxiter': 10**3}
)

print result.x
有两点值得一提:

  • 目标函数增加了一个减号,用于scipy的
    minimize()
    例程,与原始白皮书中引用的最大化相比。这将导致
    result.fun
    为负值,而不是表示总收入

  • 这个公式似乎对参数有点敏感。最小化是正确的(至少,当它说它执行正确时是正确的——检查
    result.success
    ),但是如果输入与实际情况相差太远,那么您可能会发现价格比预期的要高得多。您可能还希望使用比我在以下示例中更多的天数。你的白皮书似乎会引起周期性的影响

  • 我并不真正喜欢白皮书的命名方案,因为它涉及可读代码。我改变了一些东西,但有些东西确实很糟糕,应该被替换,比如小写的l很容易被数字1混淆

  • 我确实设定了界限,使价格是正的而不是负的。利用您的领域专业知识,您应该验证这是正确的决策

  • 您可能更喜欢比我指定的更严格的公差。这在某种程度上取决于您希望运行时是什么。请随意使用
    tol
    参数。此外,对于更严格的公差,您可能会发现
    选项
    参数中的
    'maxiter'
    必须增加,以使
    最小化()
    正确收敛

  • 我很确定,
    total_rooms
    应该是酒店中尚未预订的房间数量,因为白皮书中的索引是字母l,而不是您在原始代码中的常量。出于测试目的,我将其设置为一个常量列表

  • 该方法必须为“SLSQP”,以处理价格界限和房间数量界限。注意不要改变这个

  • 计算
    O_l()
    的方法效率极低。如果运行时是一个问题,我将采取的第一步是弄清楚如何缓存对
    X()
    的调用。所有这些实际上只是第一次通过,概念证明。它应该是合理的无错误和正确的,但它几乎是直接从白皮书中提取的,并且可以进行一些重新分解


  • 任何人,请尽情享受,并随时评论/PM/等任何进一步的问题。

    很抱歉,我回答这个问题迟到了,但我认为公认的答案并不能解决完整的问题,而且不能正确地解决问题。请注意,在局部极小化中,求解近似名义价格并不能给出最佳解

    让我们先建立一个
    酒店
    类:

    """
    This file is 'hotel.py'
    """
    import math
    
    class hotel(object):
        def __init__(self, rooms, price_ave, price_elastic):
            self.rooms = rooms
            self.price_ave = price_ave
            self.price_elastic = price_elastic
    
        def profit(self, P):
            # assert len(P) == len(self.rooms)
            return sum(j * self._reserved(P, i) for i,j in enumerate(P))
    
        def remaining(self, P): # >= 0
            C = self.rooms
            # assert len(P) == C
            return [C[i] - self._reserved(P, i) for i,j in enumerate(P)]
    
        def _reserved(self, P, day):
            max_days = len(self.rooms)
            As = range(0, day)
            return sum(self._allocated(P, a, L) for a in As
                       for L in range(day-a+1, max_days+1))
    
        def _allocated(self, P, a, L):
            P_nom = self.price_ave
            e = self.price_elastic
            return math.ceil(self._demand(a, L)*(sum(P[a:a+L])/(P_nom*L))**e)
    
        def _demand(self, a,L): #XXX: fictional non-constant demand function
            return abs(1-a)/L + 2*(a**2)/L**2
    
    这里有一种使用
    mystic
    解决的方法:

    """
    This file is 'local.py'
    """
    n_days = 7
    n_rooms = 50
    P_nom = 85
    P_bounds = 0,None
    P_elastic = 2
    
    import hotel
    h = hotel.hotel([n_rooms]*n_days, P_nom, P_elastic)
    
    def objective(price, hotel):
        return -hotel.profit(price)
    
    def constraint(price, hotel): # <= 0
        return -min(hotel.remaining(price))
    
    bounds = [P_bounds]*n_days
    guess = [P_nom]*n_days
    
    import mystic as my
    
    @my.penalty.quadratic_inequality(constraint, kwds=dict(hotel=h))
    def penalty(x):
        return 0.0
    
    # using a local optimizer, starting from the nominal price
    solver = my.solvers.fmin
    mon = my.monitors.VerboseMonitor(100)
    
    kwds = dict(disp=True, full_output=True, itermon=mon,
                args=(h,),  xtol=1e-8, ftol=1e-8, maxfun=10000, maxiter=2000)
    result = solver(objective, guess, bounds=bounds, penalty=penalty, **kwds)
    
    print([round(i,2) for i in result[0]])
    
    这里再次使用全局优化器:

    """
    This file is 'global.py'
    """
    n_days = 7
    n_rooms = 50
    P_nom = 85
    P_bounds = 0,None
    P_elastic = 2
    
    import hotel
    h = hotel.hotel([n_rooms]*n_days, P_nom, P_elastic)
    
    def objective(price, hotel):
        return -hotel.profit(price)
    
    def constraint(price, hotel): # <= 0
        return -min(hotel.remaining(price))
    
    bounds = [P_bounds]*n_days
    guess = [P_nom]*n_days
    
    import mystic as my
    
    @my.penalty.quadratic_inequality(constraint, kwds=dict(hotel=h))
    def penalty(x):
        return 0.0
    
    # try again using a global optimizer
    solver = my.solvers.diffev
    mon = my.monitors.VerboseMonitor(100)
    
    kwds = dict(disp=True, full_output=True, itermon=mon, npop=40,
                args=(h,),  gtol=250, ftol=1e-8, maxfun=30000, maxiter=2000)
    result = solver(objective, bounds, bounds=bounds, penalty=penalty, **kwds)
    
    print([round(i,2) for i in result[0]])
    

    我认为,为了获得更合理的定价,我会将
    p_界限
    值更改为更合理的值。

    如果您投票关闭,请提供原因,谢谢。感谢您花时间回复,您的回答很有帮助,但缺乏处理条件的方法。您已经解决了一个简单得多的问题,该问题没有问题所呈现的复杂条件。我不知道如何根据你的解来推断这些条件。我应该补充一点,这个问题有两个条件。一个可能受边界控制,但另一个条件在功能上是依赖的。我不知道
    """
    This file is 'global.py'
    """
    n_days = 7
    n_rooms = 50
    P_nom = 85
    P_bounds = 0,None
    P_elastic = 2
    
    import hotel
    h = hotel.hotel([n_rooms]*n_days, P_nom, P_elastic)
    
    def objective(price, hotel):
        return -hotel.profit(price)
    
    def constraint(price, hotel): # <= 0
        return -min(hotel.remaining(price))
    
    bounds = [P_bounds]*n_days
    guess = [P_nom]*n_days
    
    import mystic as my
    
    @my.penalty.quadratic_inequality(constraint, kwds=dict(hotel=h))
    def penalty(x):
        return 0.0
    
    # try again using a global optimizer
    solver = my.solvers.diffev
    mon = my.monitors.VerboseMonitor(100)
    
    kwds = dict(disp=True, full_output=True, itermon=mon, npop=40,
                args=(h,),  gtol=250, ftol=1e-8, maxfun=30000, maxiter=2000)
    result = solver(objective, bounds, bounds=bounds, penalty=penalty, **kwds)
    
    print([round(i,2) for i in result[0]])
    
    >$ python global.py 
    Generation 0 has Chi-Squared: 3684702.124765
    Generation 100 has Chi-Squared: -36493.709890
    Generation 200 has Chi-Squared: -36650.498677
    Generation 300 has Chi-Squared: -36651.722841
    Generation 400 has Chi-Squared: -36651.733274
    Generation 500 has Chi-Squared: -36651.733322
    Generation 600 has Chi-Squared: -36651.733361
    Generation 700 has Chi-Squared: -36651.733361
    Generation 800 has Chi-Squared: -36651.733361
    STOP("ChangeOverGeneration with {'tolerance': 1e-08, 'generations': 250}")
    Optimization terminated successfully.
             Current function value: -36651.733361
             Iterations: 896
             Function evaluations: 24237
    [861.07, 893.88, 398.68, 471.4, 9.44, 0.0, 244.67]