运筹系列87:julia求解随机动态规划问题入门

1. 入门案例:LinearPolicyGraph

看一个简单的数值优化的例子:
在这里插入图片描述
我们将其建立为一个N阶段的问题:
在这里插入图片描述
初始值为M。
使用SDDP.jl进行求解:

using SDDP
import IpoptM, N = 5, 3model = SDDP.LinearPolicyGraph(stages = N,lower_bound = 0.0,optimizer = Ipopt.Optimizer,
) do subproblem, node@variable(subproblem, s >= 0, SDDP.State, initial_value = M)@variable(subproblem, x >= 0)@stageobjective(subproblem, x^2)@constraint(subproblem, x <= s.in)@constraint(subproblem, s.out == s.in - x)if node == Nfix(s.out, 0.0; force = true)endreturn
endSDDP.train(model)
println(SDDP.calculate_bound(model))
simulations = SDDP.simulate(model, 1, [:x])
for data in simulations[1]println("x_$(data[:node_index]) = $(data[:x])")
end

结果为

8.333333297473812
x_1 = 1.6666655984419778
x_2 = 1.6666670256548375
x_3 = 1.6666673693365108

非常接近理论最优值。

2 报童模型:加入随机变量

报童每天早上进一批报纸,需求有个大致的数,但是并不确定。那么报童要进多少报纸合适?
变量如下:

  • 报纸成本c,零售价v
  • 残值s,缺货损失p
  • 需求为随机变量x,概率密度为f
  • 决策变量为订货量Q
  • 目标函数为max(利润-成本),假如x>Q,则利润=vQ,成本=cQ+p(x-Q)
  • 最佳订货量Q满足到Q的概率积分=(v+p-c)/(v+p-s),推倒过程这里忽略

.我们缺货无损失,残值为0,零售价30.5,订货价20.5,则最佳订货量满足积分=0.328。若f为均值300,方差50的正态分布函数,则最佳订货量 = -0.445*50+300 = 278
我们使用Julia进行求解,首先要将正态分布离散化:

using Distributions,StatsPlots
D = Distributions.Normal(300,50)
N = 1000
d = rand(D, N)
P = fill(1 / N, N)
StatsPlots.histogram(d; bins = 80, label = "", xlabel = "Demand")

在这里插入图片描述
接下来是一个trick,这里是先决策再实现随机变量,因此把问题建成两阶段的模型,第一阶段决策为购买量u_make,第二阶段实现随机变量ω,随后决策为销售量u_sell。
模型如下:

using SDDP, HiGHS
model = SDDP.LinearPolicyGraph(;stages = 2,sense = :Max,upper_bound = 20 * maximum(d),  # The `M` in θ <= Moptimizer = HiGHS.Optimizer,
) do subproblem::JuMP.Model, stage::Int@variable(subproblem, x >= 0, SDDP.State, initial_value = 0)if stage == 1@variable(subproblem, u_make >= 0, Int)@constraint(subproblem, x.out == x.in + u_make)@stageobjective(subproblem, -20.5 * u_make)else@variable(subproblem, u_sell >= 0, Int)@constraint(subproblem, u_sell <= x.in)@constraint(subproblem, x.out == x.in - u_sell)# 把连续随机变量离散化SDDP.parameterize(subproblem, d, P) do ωset_upper_bound(u_sell, ω)returnend@stageobjective(subproblem, 30.5 * u_sell)end
end
SDDP.train(model;)
rule = SDDP.DecisionRule(model; node = 1)
SDDP.evaluate(rule; incoming_state = Dict(:x => 0.0),controls_to_record = [:u_make])

输出为:
(stage_objective = -5699.0, outgoing_state = Dict(:x => 278.0), controls = Dict(:u_make => 278.0))
接下来进行仿真观察:

simulations = SDDP.simulate(model,10,  #= number of replications =#[:x, :u_make, :u_sell];  #= variables to record =#skip_undefined_variables = true,
);
simulations[1][1]

在这里插入图片描述
收益如下:
在这里插入图片描述

3 探索迷宫:UnicyclicGraph

首先构造一个迷宫:

M, N = 3, 4
initial_square = (1, 1)
reward, illegal_squares, penalties = (3, 4), [(2, 2)], [(3, 1), (2, 4)]
path = fill("⋅", M, N)
path[initial_square...] = "1"
for (k, v) in (illegal_squares => "▩", penalties => "†", [reward] => "*")for (i, j) in kpath[i, j] = vend
end
print(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n'))

在这里插入图片描述
定义P为所有满足可以移动到 (i, j)的(a, b) 集合。
使用SDDP.UnicyclicGraph,定义一个无限的循环图

discount_factor = 0.9
graph = SDDP.UnicyclicGraph(discount_factor)

定义了一个不断自循环的图:

Root0
Nodes1
Arcs0 => 1 w.p. 1.01 => 1 w.p. 0.9
import HiGHSmodel = SDDP.PolicyGraph(graph;sense = :Max,upper_bound = 1 / (1 - discount_factor),optimizer = HiGHS.Optimizer,
) do sp, _# Our state is a binary variable for each square@variable(sp,x[i = 1:M, j = 1:N],Bin,SDDP.State, # 每一阶段都是M*N的状态变量initial_value = (i, j) == initial_square, # 初始位置(1,1))# 只能在一个位置@constraint(sp, sum(x[i, j].out for i in 1:M, j in 1:N) == 1)# Incur rewards and penalties# 这里reward是终点位置,penalties是所有惩罚点的位置@stageobjective(sp, x[reward...].out - sum(x[i, j].out for (i, j) in penalties))# Constraints on valid movesfor i in 1:M, j in 1:N# 允许移动的位置moves = [(i - 1, j), (i + 1, j), (i, j), (i, j + 1), (i, j - 1)]filter!(v -> 1 <= v[1] <= M && 1 <= v[2] <= N && !(v in illegal_squares), moves)# 移动出去的值不能大于其他位置进去的值的和。由于都是Bin变量,因此等价于说从i,j移动到了某个允许的a,b上@constraint(sp, x[i, j].out <= sum(x[a, b].in for (a, b) in moves))endreturn
end
SDDP.train(model)
simulations = SDDP.simulate(model,1,[:x];sampling_scheme = SDDP.InSampleMonteCarlo(max_depth = 10,#terminate_on_dummy_leaf = false,),
);
for (t, data) in enumerate(simulations[1]), i in 1:M, j in 1:Nif data[:x][i, j].in > 0.5path[i, j] = "$t"end
endprint(join([join(path[i, :], ' ') for i in 1:size(path, 1)], '\n'))

注意Simulating a cyclic policy graph requires an explicit sampling_scheme that does not terminate early based on the cycle probability。结果为:

1 2 3 ⋅
⋅ ▩ 4 †
† ⋅ 5 *

4 牛奶制造:MarkovianGraph

使用MarkovianGraph可以接受一个仿真器graph = SDDP.MarkovianGraph(simulator; budget = 30, scenarios = 10_000);
buget是node的个数,scenarios是计算转移概率时的采样个数。这里使用Markovian过程来模拟一个随时间变化的过程,node的个数需要大于stage的个数,node越多,模拟越精确。

这里问题描述如下:

  • 产奶量不确定,牛奶市场价格不确定
  • 需求无限。未售出的牛奶可以制成奶粉
  • 公司也可以接订单,按照当前价格收款,但是4个月之后交付。届时产能不够的话,需要额外购买牛奶。
  • 决策为需要接多少订单

首先我们用预测模型给出牛奶的价格,假设模型如下:

function simulator()residuals = [0.0987, 0.199, 0.303, 0.412, 0.530, 0.661, 0.814, 1.010, 1.290]residuals = 0.1 * vcat(-residuals, 0.0, residuals)scenario = zeros(12)y, μ, α = 4.5, 6.0, 0.05for t in 1:12y = exp((1 - α) * log(y) + α * log(μ) + rand(residuals))scenario[t] = clamp(y, 3.0, 9.0)endreturn scenario
endplot = Plots.plot([simulator() for _ in 1:500];color = "gray",opacity = 0.2,legend = false,xlabel = "Month",ylabel = "Price [\$/kg]",xlims = (1, 12),ylims = (3, 9),
)

在这里插入图片描述

用30个点模拟出来的转移概率图如下:

for ((t, price), edges) in graph.nodesfor ((t′, price′), probability) in edgesPlots.plot!(plot,[t, t′],[price, price′];color = "red",width = 3 * probability,)end
endplot

在这里插入图片描述

接下来给定具体数值:

  • 产奶量不确定,为0.1-0.2之间的均匀分布。生产没有成本。
  • 牛奶市场价格按照node进行概率转移,售出价格1倍
  • 需求无限。未售出的牛奶可以制成奶粉然后在下个月以同样价格售出。
  • 公司也可以接订单,按照当前价格收款,但是4个月之后交付。成本为绝对值0.01,四个月后售出价格为1.05倍
  • 届时产能不够的话,需要额外购买牛奶。费用1.5倍
  • 决策为需要接多少订单

建模如下:

model = SDDP.PolicyGraph(graph;# 将price用graph随机过程来模拟,不用再单独建随机变量来模拟sense = :Max,upper_bound = 1e2,optimizer = HiGHS.Optimizer,
) do sp, nodet, price = node::Tuple{Int,Float64}c_buy_premium = 1.5Ω_production = range(0.1, 0.2; length = 5)c_max_production = 12 * maximum(Ω_production)# 两个状态变量@variable(sp, 0 <= x_stock, SDDP.State, initial_value = 0) # track库存变量@variable(sp, 0 <= x_forward[1:4], SDDP.State, initial_value = 0) # track forward变化# 三个核心决策变量@variable(sp, 0 <= u_spot_sell <= c_max_production)@variable(sp, 0 <= u_spot_buy <= c_max_production);c_max_futures = t <= 8 ? c_max_production : 0.0@variable(sp, 0 <= u_forward_sell <= c_max_futures)# 一个随机变量@variable(sp, ω_production)# Forward contracting constraints:@constraint(sp, [i in 1:3], x_forward[i].out == x_forward[i+1].in)@constraint(sp, x_forward[4].out == u_forward_sell)@constraint(sp, x_stock.out == x_stock.in + ω_production + u_spot_buy - x_forward[1].in - u_spot_sell)Ω = [(price, p) for p in Ω_production]SDDP.parameterize(sp, Ω) do ω::Tuple{Float64,Float64}fix(ω_production, ω[2])@stageobjective(sp,  ω[1] * (u_spot_sell - 1.5 * u_spot_buy) +(ω[1] * 1.05 - 0.01) * u_forward_sell)returnendreturn
end

SDDP.SimulatorSamplingScheme is used in the forward pass. It generates an out-of-sample sequence of prices using simulator and traverses the closest sequence of nodes in the policy graph.翻译过来就是SimulatorSamplingScheme会根据产生的随机变量,寻找最为接近的node路径。
AVaR(β) Computes the expectation of the β fraction of worst outcomes. β must be in [0, 1]. When β=1, this is equivalent to the Expectation risk measure. When β=0, this is equivalent to the WorstCase risk measure. 翻译过来就是AVaR综合了最差情况和均值。

SDDP.train(model;time_limit = 20,risk_measure = 0.5 * SDDP.Expectation() + 0.5 * SDDP.AVaR(0.25),sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),
)

我们仿真一次:

simulations = SDDP.simulate(model,200,Symbol[:x_stock, :u_forward_sell, :u_spot_sell, :u_spot_buy];sampling_scheme = SDDP.SimulatorSamplingScheme(simulator),
);
simulations[1][12]

在这里插入图片描述
可以发现,node_index里面的price下标是7.78204, SDDP.parameterize的实际用的price是7.66547

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/283713.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HTTP协议在Linux系统中的运用与代码示范

在Linux系统中&#xff0c;HTTP协议的应用非常广泛&#xff0c;它被用于Web开发、网络爬虫、API调用等场景。了解并掌握HTTP协议&#xff0c;对于Linux系统的开发和使用都非常重要。下面&#xff0c;我们将为您介绍HTTP协议在Linux系统中的运用&#xff0c;并通过代码示范来帮助…

Qt for Android设置安卓程序默认横屏+全屏

我的qt版本是5.14.1&#xff0c;网上查到的方法是&#xff0c;把编译出的build文件夹中的AndroidManifest.xml文件复制出来然后修改&#xff0c;然后把修改后的xml文件加入pro文件&#xff0c;语法为ANDROID_PACKAGE_SOURCE_DIR $$PWD/AndroidManifest.xml&#xff08;具体&am…

我与Datawhale的故事之长篇

Datawhale成员 作者&#xff1a;Datawhale团队成员 前 言 上周五周年文章发出后大家反响比较热烈&#xff1a; 我们与Datawhale背后的故事&#xff01; 本期给大家带来三篇长篇回忆 胡锐峰 我与Datawhale的故事 题记&#xff1a;我和你的相遇就像春风拂面&#xff0c;就像夏雨…

【MATLAB】数据拟合第11期-基于粒子群迭代的拟合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 基于粒子群迭代的拟合算法是一种优化技术&#xff0c;它基于粒子群优化算法&#xff08;PSO&#xff09;的基本思想。该算法通过群体中个体之间的协作和信息共享来寻找最优解。 在基于粒…

【数据结构】八大排序之直接插入排序算法

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:数据结构 ⚙️操作环境:Visual Studio 2022 一.直接插入排序简介及思路 直接插入排序(Straight Insertion Sort)是一种简单直观的插入排序算法. 它的基本操作是: 将一个数据插入到已经排好的有序表中,从而得到一个新的,数…

HarmonyOS--基础组件Button

Button组件 可以包含单个子组件。 Button(label?: ResourceStr, options?: { type?: ButtonType, stateEffect?: boolean }) 1&#xff1a;文字按钮 Button(‘点击’) 2&#xff1a;自定义按钮,嵌套其它组件 Button() {Image(https://) }.type(ButtonType.Circle)

强化学习------Policy Gradient算法公式推导

目录 一、前言二、公式推导基线 三、代码实现四、参考 一、前言 Policy Gradient 算法是一种基于策略的强化学习算法&#xff0c;与基于值的方法&#xff08;如Q-learning和DQN&#xff09;不同。基于值的方法主要关注于学习值函数&#xff08;如状态值函数或者动作值函数&…

Notepad++插件:格式化JSON

一、问题描述 最近有这么一串json字符串&#xff1a; 你想看吗&#xff1f; 是不是觉得密密匝匝滴&#xff0c;很不想看呢&#xff1f; 下面是经过处理的json字符串&#xff1a; 你喜欢哪种格式的json字符串展示呢&#xff1f; 反正我喜欢已经格式化的&#xff0c;也就是第二…

分享66个JavaGame源码总有一个是你想要的

分享66个JavaGame源码总有一个是你想要的 学习知识费力气&#xff0c;收集整理更不易。 知识付费甚欢喜&#xff0c;为咱码农谋福利。 游戏下载链接&#xff1a;https://pan.baidu.com/s/1BUVmun2RhAY4vAMJwcY0mQ?pwd6666 提取码&#xff1a;6666 游戏项目名称 java实现…

echarts 进度条类型柱状图

父组件&#xff1a; <barChartProfit :opt"avgProfit" />import barChartProfit from "./components/barChartProfit";data() {return {avgProfit: {xData: [2019, 2020, 2021, 2022, 2023],totalData: [30,30,30,30,30],seriesData: [10,11,12,21,…

Repo sync 时出现fatal_ couldn‘t find remote ref refs_heads_master问题解决

repo sync默认的origin分支是master&#xff0c;它默认会依赖master&#xff0c;但是我们的manifests分支是main&#xff0c;需要解决这个问题主要执行下面的几步&#xff1a; 更新repo到最新版本 cd .repo/repo git pull # 更新repo前往git库创建origin master 在manifests…

08-工厂方法

意图 定义一个用于创建对象的接口&#xff0c;让子类决定实例化哪一个类 类图 适用性 在下列情况可以使用工厂方法模式&#xff1a; 当一个类不知道它所必须创建的对象的类的时候。当一个类希望由它的子类来指定它所创建的对象的时候。当类将创建对象的职责委托给多个帮助子…