<-- Home |--matlab |--optimization

Optimization 101优化入门

一个简单的问题

一个非常简单的问题,如何用最少的材料做一个容积为1m³的圆柱形容器?在把圆柱形容器简化为等厚的金属板并忽略接头的成本和材料耗损可以把问题描述为:一个圆柱体容器,在容积为1m³的条件下,表面积最小,应该如何选择尺寸?

1app = BucketOptApp;
2exportgraphics(app.ContainerAxes, "bucket.png")

bukcet problem

这个问题的对象(系统)非常简单,是一个静态对象,可以用两个参数来描述,分别是底面半径和高度,这都是很自然的几何参数。

$$ S_p = \{r, h\} $$

我们要问的问题或者说要求解的问题是一个最小化的问题,当然难点在于还有一个约束。

$$ \begin{split} \arg\min_{S_p} \quad & A \\ \text{s.t.} \quad & V = 1 \end{split} $$

通过简单的几何知识,有如下数学模型:

$$\left\{ \begin{split} &V = \pi r^2 h \\ &A = 2\pi r h + 2 \pi r^2 \end{split} \right. $$

符号求解

简单问题简单解

这个题目相当简单,在约束关系$V=1$中可以求解得到$h=\frac{1}{\pi r^2}$,代入$A$的表达式中,可以得到:

$$ A = 2\pi r \frac{1}{\pi r^2} + 2 \pi r^2= \frac{2}{r} + 2 \pi r^2 $$

这是一个二次函数,类似于一个碗,最小值点很容易通过求解方程$\frac{dA}{dr}=0$得到。

$$ r_\text{min} = \sqrt[3]{\frac{1}{2\pi}} \approx 0.5419 $$

对应的$h_\text{min}$自然可以得到。

$$ h_\text{min} = 2 \sqrt[3]{\frac{1}{2\pi}} = 2 r \approx 1.0839 $$

自行上难度用Matlab

那我们非要用Matlab做怎么整呢?符号运算工具箱还是很好用的(现在)。

 1%% define symbols
 2syms r h V(r,h) A(r, h)
 3V(r, h) = pi * r * r * h;
 4A(r, h) = 2 * pi * r * h + 2 * pi * r * r;
 5
 6% 这两个很节省计算,也算是灵巧的小手,一定要学会约束条件提前给进去
 7assume(r > 0);
 8assume(h > 0);
 9
10constraint = V == 1;
11
12%% solve h and subs to A
13hr = solve(constraint, h);
14Ar = subs(A, h, hr);
15
16r_min = solve(diff(Ar, r) == 0);
17
18assert(subs(diff(Ar, r, r), r, r_min) > 0);
19
20%% report result
21h_min = subs(hr, r_min);
22
23pretty(simplify(h_min))
24pretty(simplify(r_min))

首先,我们在第2行到第4行把问题输进去;第7~8行我们做了一个约束,两个几何量都大于零,这是为了去掉那些复数的解。下面这个例子,可以看到约束函数assume的作用。

1    >> syms x; solve(x^3 == 1)
2    ans =
3                        1
4    - (3^(1/2)*1i)/2 - 1/2
5    (3^(1/2)*1i)/2 - 1/2
6    >> syms r; assume(r>0); solve(r^3 == 1)
7    ans =
8    1

第10行中,我们定义了一个约束。符号工具箱中,用==来表示方程中的等于。这在下面描述要求解的方程中也能用得到。

首先我们符号求解$h$(第12-13行)。

1    >> hr
2    hr =
3    1/(r^2*pi)

然后第14行,把求解得到的$h(r)$代入面积公式,求解极值点(第16行),最后还要验算是否是最小值点(第18行)。

最后,我们把$h_\text{min}$也求出来,并打印在终端。

Lagrange multiplier a.k.a. 拉格朗日乘子法

当然,对于约束优化问题,还有一个常用的方式就是拉格朗日乘子法。

$$ \begin{split} \arg\min_{x} \quad & f(x) \\ \text{s.t.} \quad & g(x) = 0 \end{split} $$

我们利用拉格朗日乘子$\lambda$把问题写为无约束优化:

$$ \arg\min_{x} \quad f(x) + \lambda g(x) $$
1%% solve with constraints
2syms lambda
3L(r, h, lambda) = A + lambda * (V - 1);
4
5result = solve(diff(L, r)==0, diff(L, h)==0, diff(L, lambda)==0)

我们非常暴力地把约束条件和优化目标写在一起构成一个新的函数:

$$ L(r, h, \lambda) = A(r, h) + \lambda \left[V(r, h) - 1\right] $$

然后求解方程组:

$$ \begin{cases} \frac{dL}{dr} = 0 \\ \frac{dL}{dh} = 0 \\ \frac{dL}{d\lambda} = 0 \end{cases} $$

最后得到结果:

1    >> result
2    result = 
3    struct with fields:
4
5            h: 2*(1/(2*pi))^(1/3)
6        lambda: -4*pi*(1/(2*pi))^(2/3)
7            r: (1/(2*pi))^(1/3)

实际上,我甚至能够无聊到编一个界面来演示这个桶的优化的过程。

演示App

界面

bukcet problem

在这个界面里,我们可以用滑块来调节容器的底面半径,界面上同步显示容器的形状,和高度、表面积的值。UI右边是一个坐标系,分别绘制高度和面积曲线,调节过程中,红色与蓝色的圈圈表示当前半径对应的值。

1exportgraphics(app.PlotAxes, "curve.png")

bukcet problem

当然,我们还提供了一个设置显示最优解的按钮。只能说,无聊的人类会干出无聊的事情。

拖动半径滑块,看看当半径变得非常大后,容器的高度会变得非常小,而表面积会变得非常大。

1exportapp(app.UIFigure, "flat.png")

bukcet problem

源代码

  1classdef BucketOptApp < matlab.apps.AppBase
  2    % 一个显示简单容器优化的App
  3    % 整个界面分为两栏
  4    % 界面左边是圆柱形容器的两个参数的滑动调节和容器3D显示
  5    % 界面右边是设计数值图形,是一个两个y坐标的图
  6    %   横坐标为地面半径
  7    %   左边的纵坐标是圆柱体的表面积的大小
  8    %   右边的纵坐标是高度
  9    % 约束条件:圆柱体体积为1
 10    
 11    % Properties that correspond to app components
 12    properties (Access = public)
 13        UIFigure           matlab.ui.Figure
 14        LeftPanel         matlab.ui.container.Panel
 15        RightPanel        matlab.ui.container.Panel
 16        LeftGrid          matlab.ui.container.GridLayout
 17        ContainerAxes     matlab.ui.control.UIAxes
 18        ControlPanel      matlab.ui.container.Panel
 19        RadiusSlider      matlab.ui.control.Slider
 20        RadiusLabel       matlab.ui.control.Label
 21        RadiusValue       matlab.ui.control.Label
 22        HeightValue       matlab.ui.control.Label
 23        SurfaceAreaValue  matlab.ui.control.Label
 24        OptimalButton     matlab.ui.control.Button
 25        PlotAxes          matlab.ui.control.UIAxes
 26    end
 27    
 28    properties (Access = private)
 29        % 默认参数
 30        DefaultRadius = 0.5  % 默认半径
 31        MaxRadius = 2.0      % 最大半径
 32        MinRadius = 0.1      % 最小半径
 33        Volume = 1.0         % 固定体积为1
 34        HasPDEToolbox        % 是否安装了PDE工具箱
 35        OptimalRadius        % 最优半径
 36    end
 37    
 38    methods (Access = private)
 39        % 根据半径计算高度(保持体积为1)
 40        function h = calculateHeight(app, r)
 41            h = app.Volume / (pi * r^2);
 42        end
 43        
 44        % 计算表面积
 45        function A = calculateSurfaceArea(app, r, h)
 46            A = 2 * pi * r * h + 2 * pi * r^2;
 47        end
 48        
 49        % 更新3D容器显示
 50        function updateContainer(app)
 51            r = app.RadiusSlider.Value;
 52            h = calculateHeight(app, r);
 53            A = calculateSurfaceArea(app, r, h);
 54            
 55            % 清除当前axes
 56            cla(app.ContainerAxes)
 57            
 58            if app.HasPDEToolbox
 59                % 创建圆柱体几何体
 60                gm = multicylinder(r, h);
 61                % 绘制几何体
 62                pdegplot(gm, 'Parent', app.ContainerAxes,  'FaceAlpha', 0.5)
 63            else
 64                % 使用基本绘图函数绘制圆柱体
 65                % 创建圆柱体侧面
 66                [X,Y,Z] = cylinder(r, 50);
 67                Z = Z * h;
 68                
 69                % 绘制侧面
 70                surf(app.ContainerAxes, X, Y, Z, 'FaceAlpha', 0.5)
 71                hold(app.ContainerAxes, 'on')
 72                
 73                % 绘制底面
 74                theta = linspace(0, 2*pi, 50);
 75                [X,Y] = pol2cart(theta, r);
 76                Z = zeros(size(X));
 77                fill3(app.ContainerAxes, X, Y, Z, 'b', 'FaceAlpha', 0.5)
 78                
 79                % 绘制顶面
 80                Z = ones(size(X)) * h;
 81                fill3(app.ContainerAxes, X, Y, Z, 'b', 'FaceAlpha', 0.5)
 82                hold(app.ContainerAxes, 'off')
 83            end
 84            
 85            % 设置视角和标题
 86            view(app.ContainerAxes, 3)
 87            title(app.ContainerAxes, '容器3D视图')
 88            xlabel(app.ContainerAxes, 'X')
 89            ylabel(app.ContainerAxes, 'Y')
 90            zlabel(app.ContainerAxes, 'Z')
 91            axis(app.ContainerAxes, 'equal')
 92            grid(app.ContainerAxes, 'on')
 93            
 94            % 更新显示值
 95            app.RadiusValue.Text = sprintf('半径: %.3f m', r);
 96            app.HeightValue.Text = sprintf('高度: %.3f m', h);
 97            app.SurfaceAreaValue.Text = sprintf('表面积: %.3f m²', A);
 98        end
 99        
100        % 更新数值图表
101        function updatePlot(app)
102            r = app.RadiusSlider.Value;
103            h = calculateHeight(app, r);
104            
105            % 清除当前axes
106            cla(app.PlotAxes)
107            
108            % 创建半径数组
109            r_array = linspace(app.MinRadius, app.MaxRadius, 100);
110            
111            % 计算对应的表面积和高度
112            h_array = app.Volume ./ (pi * r_array.^2);  % 根据体积约束计算高度
113            A_array = 2 * pi * r_array .* h_array + 2 * pi * r_array.^2;  % 表面积 = 2πrh + 2πr²
114            
115            % 创建双y轴图
116            yyaxis(app.PlotAxes, 'left')
117            p1 = plot(app.PlotAxes, r_array, A_array, 'b-', 'LineWidth', 2);
118            ylabel(app.PlotAxes, '表面积 (m²)')
119            
120            yyaxis(app.PlotAxes, 'right')
121            p2 = plot(app.PlotAxes, r_array, h_array, 'r-', 'LineWidth', 2);
122            ylabel(app.PlotAxes, '高度 (m)')
123            
124            xlabel(app.PlotAxes, '半径 (m)')
125            title(app.PlotAxes, '容器参数关系图 (体积固定为1m³)')
126            grid(app.PlotAxes, 'on')
127            
128            % 标记当前点
129            hold(app.PlotAxes, 'on')
130            yyaxis(app.PlotAxes, 'left')
131            p3 = plot(app.PlotAxes, r, 2 * pi * r * h + 2 * pi * r^2, 'bo', 'MarkerSize', 10);
132            yyaxis(app.PlotAxes, 'right')
133            p4 = plot(app.PlotAxes, r, h, 'ro', 'MarkerSize', 10);
134            
135            % 标记最优点
136            r_opt = app.OptimalRadius;
137            h_opt = calculateHeight(app, r_opt);
138            A_opt = calculateSurfaceArea(app, r_opt, h_opt);
139            yyaxis(app.PlotAxes, 'left')
140            p5 = plot(app.PlotAxes, r_opt, A_opt, 'g*', 'MarkerSize', 15);
141            yyaxis(app.PlotAxes, 'right')
142            p6 = plot(app.PlotAxes, r_opt, h_opt, 'g*', 'MarkerSize', 15);
143            hold(app.PlotAxes, 'off')
144            
145            % 添加图例
146            legend(app.PlotAxes, [p1, p2, p3, p4, p5, p6], ...
147                '表面积曲线', '高度曲线', '当前表面积', '当前高度', '最优表面积', '最优高度', ...
148                'Location', 'best')
149        end
150    end
151    
152    % Callbacks that handle component events
153    methods (Access = private)
154        % 半径滑块值改变回调
155        function RadiusSliderValueChanged(app, ~)
156            updateContainer(app)
157            updatePlot(app)
158        end
159        
160        % 最优解按钮回调
161        function OptimalButtonPushed(app, ~)
162            app.RadiusSlider.Value = app.OptimalRadius;
163            updateContainer(app)
164            updatePlot(app)
165        end
166    end
167    
168    % Component initialization
169    methods (Access = private)
170        % 创建UI组件
171        function createComponents(app)
172            % 检查是否安装了PDE工具箱
173            app.HasPDEToolbox = license('test', 'PDE_Toolbox');
174            
175            % 计算最优半径
176            app.OptimalRadius = (1/(2*pi))^(1/3);
177            
178            % 创建UIFigure
179            app.UIFigure = uifigure('Visible', 'off');
180            app.UIFigure.Position = [100 100 1200 600];
181            app.UIFigure.Name = '容器优化App (体积固定为1m³)';
182            
183            % 创建左右面板
184            app.LeftPanel = uipanel(app.UIFigure);
185            app.LeftPanel.Position = [0 0 600 600];
186            
187            app.RightPanel = uipanel(app.UIFigure);
188            app.RightPanel.Position = [600 0 600 600];
189            
190            % 创建左侧网格布局
191            app.LeftGrid = uigridlayout(app.LeftPanel, [2 1]);
192            app.LeftGrid.RowHeight = {'fit', 'fit'};
193            app.LeftGrid.Padding = [10 10 10 10];
194            app.LeftGrid.RowSpacing = 10;
195            
196            % 创建3D显示区域
197            app.ContainerAxes = uiaxes(app.LeftGrid);
198            app.ContainerAxes.Layout.Row = 2;
199            app.ContainerAxes.Layout.Column = 1;
200            
201            % 创建控制面板
202            app.ControlPanel = uipanel(app.LeftGrid);
203            app.ControlPanel.Layout.Row = 1;
204            app.ControlPanel.Layout.Column = 1;
205            app.ControlPanel.Title = '参数控制';
206            
207            % 创建控制面板网格布局
208            controlGrid = uigridlayout(app.ControlPanel, [6 1]);
209            controlGrid.RowHeight = {'1x', 'fit', '1x', '1x', '1x', 'fit'};
210            controlGrid.Padding = [10 10 10 10];
211            controlGrid.RowSpacing = 5;
212            
213            % 创建参数显示区域
214            app.RadiusLabel = uilabel(controlGrid);
215            app.RadiusLabel.Layout.Row = 1;
216            app.RadiusLabel.Layout.Column = 1;
217            app.RadiusLabel.Text = '半径调节:';
218            
219            app.RadiusSlider = uislider(controlGrid);
220            app.RadiusSlider.Layout.Row = 2;
221            app.RadiusSlider.Layout.Column = 1;
222            app.RadiusSlider.Limits = [app.MinRadius app.MaxRadius];
223            app.RadiusSlider.Value = app.DefaultRadius;
224            app.RadiusSlider.ValueChangedFcn = @(s,e) RadiusSliderValueChanged(app, e);
225            
226            % 创建参数值显示标签
227            app.RadiusValue = uilabel(controlGrid);
228            app.RadiusValue.Layout.Row = 3;
229            app.RadiusValue.Layout.Column = 1;
230            app.RadiusValue.Text = sprintf('半径: %.3f m', app.DefaultRadius);
231            
232            app.HeightValue = uilabel(controlGrid);
233            app.HeightValue.Layout.Row = 4;
234            app.HeightValue.Layout.Column = 1;
235            app.HeightValue.Text = sprintf('高度: %.3f m', calculateHeight(app, app.DefaultRadius));
236            
237            app.SurfaceAreaValue = uilabel(controlGrid);
238            app.SurfaceAreaValue.Layout.Row = 5;
239            app.SurfaceAreaValue.Layout.Column = 1;
240            app.SurfaceAreaValue.Text = sprintf('表面积: %.3f m²', calculateSurfaceArea(app, app.DefaultRadius, calculateHeight(app, app.DefaultRadius)));
241            
242            % 创建最优解按钮
243            app.OptimalButton = uibutton(controlGrid);
244            app.OptimalButton.Layout.Row = 6;
245            app.OptimalButton.Layout.Column = 1;
246            app.OptimalButton.Text = '显示最优解';
247            app.OptimalButton.ButtonPushedFcn = @(s,e) OptimalButtonPushed(app, e);
248            
249            % 创建图表显示区域
250            app.PlotAxes = uiaxes(app.RightPanel);
251            app.PlotAxes.Position = [50 50 500 500];
252        end
253    end
254    
255    % App creation and startup functions
256    methods (Access = public)
257        % 创建UIFigure和组件
258        function createUIFigure(app)
259            createComponents(app)
260            
261            % 显示UI
262            app.UIFigure.Visible = 'on';
263        end
264    end
265    
266    % Component initialization
267    methods (Access = private)
268        % 初始化App
269        function startupFcn(app)
270            updateContainer(app)
271            updatePlot(app)
272        end
273    end
274    
275    % App creation and startup functions
276    methods (Access = public)
277        % 构造函数
278        function app = BucketOptApp
279            % 创建UIFigure和组件
280            createUIFigure(app)
281            
282            % 运行startup函数
283            startupFcn(app)
284        end
285    end
286end

模型的碎碎念

这么简单的问题,为什么要纠结?通过这个例子,我们可以窥见目前科学和工程技术研究中解决问题的基本思路:建模。

模型:如果研究者$B$用对象$A^*$来解决对象$A$的某个问题,则称$A^*$为$A$的模型。

这里的定义是一个一般的形式定义,使用的也是一般的用语。这里面,研究者$B$就是建模的主体,而某个问题就是特指的特定问题,而非是泛化的问题,通常是分析、设计、控制、优化等。

而关于系统$A$的模型$A^*$,则挺有意思的。

  • 模型$A^*$是关于系统$A$仅仅对解决特定问题有用的简化描述;
  • 模型并非总是越复杂越好,恰恰相反,最佳模型是1)符合目的(解决问题)的2)最简单模型;
  • 简化是建立模型的基本手段;
  • 解决具体问题,总是从最简单的模型出发,逐步增加复杂度,直到模型满足解决该问题的要求。

通常,会采用一个称为$\{S,Q,M\}$的元组来描述一个模型,其中$S$是系统,$Q$是问题,$M$是数学描述。

$$ A^* = \{S, Q, M\} $$

对我们上面的对象,毫无疑问,$Q$是一个优化问题,也就是$Q=\arg\min_{S_p} \quad A$。而这里$S$的数值表达形式:$S_p$,包含了简化的描述系统$S$的参数,也就是$S_p=\{r,h\}$。

而$M$,则是$S$和$Q$的数学描述,也就是$M=\{V,A\}$。

$$ M=\{V,A\} $$

那么对于简单的优化问题,我们可以采用上面的符号推导的方式来进行求解,对于问题中的约束条件,则可以采用拉格朗日乘子法来进行求解。

对于更加复杂($S$复杂、$Q$复杂、$M$复杂或者多个条件复杂)的问题,则需要采用更加复杂的优化算法,比如牛顿法、拟牛顿法、共轭梯度法、内点法、外点法等经典方法;或者采用智能优化算法,比如遗传算法、粒子群算法等。


文章标签

|-->matlab |-->symbolic |-->Lagrange multiplier |-->syms |-->assume |-->diff |-->subs |-->solve |-->assert |-->pretty |-->vpa


GitHub