autolayout中的线性规划算法 —— simplex

什么是auto layout

auto layout是苹果公司提供的一个基于约束布局,动态计算视图大小和位置的库,而且已经集成到xcode开发环境里。如下两个时间点须要注意。html

  1. 1997年,auto layout所用到的布局算法cassorwary被发明了出来。
  2. 2011年,苹果公司将cassowary算法运用到了自家的布局引擎auto layout中

autolayout 的生命周期

Auto Layout不仅有布局算法Casswory,还包含了布局在运行时的生命周期等一整套布局引擎系统,用来赞成管理布局的建立、更新和销毁。前端

这一整套布局引擎系统叫作Layout Engine,是Auto Layout的核心,主导着整个界面布局。算法

每一个视图在获得本身的布局前,Layout Engine会将试图、约束、优先级、固定大小经过计算转换成最终的大小和位置。每当约束发生变化,就会触发Deffered Layout Pass,完成后进入监听约束变化的状态。当再次监听到约束变化,就会进入到下一轮的循环中。过程以下图。xcode


UIStackView

UIStackView是一个容器决定排版的布局模式,容器大小的变化,会影响子项的排版。相似于前端体系中的flexbox的简化版。markdown

  1. UIStackView是一个虚拟容器,它的layer不被绘制,设置背景、边框、阴影这些外观是无效的。
  2. 若是咱们使用约束,不定义栈视图的大小,只定义位置,这个时候,栈视图不会再改变管理内容的大小,可是它会自动计算出本身须要的大小,也就是变成了不会换行的流逝布局。也就是说会生成一个固有大小,进行内容自适应的排版。

线性规划问题

线性规划(Linear programming,简称LP),是运筹学中研究较早、发展较快、应用普遍、方法较成熟的一个重要分支,它是辅助人们进行科学管理的一种数学方法。研究线性约束条件下线性目标函数的极值问题的数学理论和方法。函数

例以下面例子,就是一个线性规划问题:oop


要求所买食物中,至少有2000能量,55蛋白质,800钙,求最省钱的方案。

能够列出不等式组:布局


这就是一个线性规划问题。性能

可行域

来看一个例子,考虑以下线性规划:flex


能够画出以下的图:


灰色部分的区域称做为可行域,从途中能够直观的看出,x1 = 2, x2 = 6时,取到最大值为8。

这里有一个定理是,能够证实老是在交点处取到最大值。

标准形式

标准形式为:

min        cT X
s.t.       A X <= b

复制代码

松弛形式

min        cT X

s.t.       A X = b
		   X >= 0
复制代码

注意:

  1. 全部的线性规划形式都是>=或<=,等号不能够去掉,不然可能无解。
  2. 上述全部变量为列向量,cT表明c的转置矩阵。
  3. 松弛形式和松弛变量会在后文提到。
  4. 注意标准形式为<=,求min,若是是反过来的,只须要增长一个负号是不等式翻转便可。

单纯形算法

单纯形法是求解线性规划的经典方法,虽然它的执行时间在最坏的状况下是非多项式的(指数时间复杂度),可是在绝大部分状况或者说实际运行过程当中,它的确是多项式时间。

步骤:

  1. 找出一个初始的基本可行解。
  2. 不断执行旋转(pivot)操做。
  3. 重复步骤2直到结果不能改进为止。

例题

考虑以下线性规划问题:


引入松弛变量后的形式:


分离基本变量和非基本变量:


注:等号右边的叫基本变量,左边的叫非基本变量。即引入的松弛变量为基本变量,原变量为非基本变量。

考虑基本解为,将非基本变量设为0,并计算左边基本变量的值,这里很容易获得,基本解为:(0, 0, 0, 4, 2, 3, 6)T。通常而言基本解是可行的,咱们称之为基本可行解(不可行的问题后面讨论)。

如今来进行第二个步骤,旋转的操做:

每次选择一个在目标函数中系数为负数的非基本变量Xe,而后尽量的增长Xe而不违反约束,并选取基本变量Xi,而后将其位置互换。

例如,咱们选择替入变量为X1,替出变量为X5,而后替换两者的角色。执行一次转动的过程与以前所描述的线性规划是等价的。替换后以下:


一样的,将非基本变量设为0,因而获得解:(2, 0, 0, 2, 0, 3, 6)T,目标函数的值减小为-2。
继续转动,只能选取X2或者X3,不能选择X5,由于此时X5的系数是正的。假设选取替入变量X2,替出变量X4,将会获得如下结果:


此时基本解变为了(2, 2, 0, 0, 0, 3, 0)T,目标函数值为-30。

继续选择增大X5,选取最严格的等式4进行替换,得:


基本解变成了(2, 2, 0, 0, 0, 3, 0)T,目标函数值为-30。

接着还能够转动X3,过程省略,最后目标值为-32,已经没有能够转动的值了,所以获得目标值为-32。

特殊状况

退化

在旋转过程当中,可能会存在保持目标值不变的状况,这种现象称之为退化。好比上面的例子里出现了两次-30。不过没有产生循环的状况。

  1. 目标条件中,系数为负的第一个做为替入变量。
  2. 对全部约束条件中,选择对xe约束最近的第一个。
  3. 加入随机扰动。

无界

在运算过程当中可能会出现无界的状况,须要注意,例如做图出来是两条平行的线的状况

矩阵 & 程序

对于上方的例题来讲:


咱们能够获得以下几个矩阵:

C = (-1, -14, -6, 0, 0, 0, 0)
B = (4, 2, 3, 6)T

<img style="border-radius: 0.3125em;" 
src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20f1e8f4192a4b4ab499c1feab89a775~tplv-k3u1fbpfcp-zoom-1.image">
复制代码

> 矩阵A:约束条件的系数
> 矩阵B:约束函数的值
> 矩阵C:目标函数的系数

如今须要将这些矩阵拼接成一个矩阵:


这里不难看出,左下角放的是B,右上角放的是C,右下角放的是A,而左上角那一个数字,放的是-z。

能够看出,若是将等式改写成 基本变量 = F(非基本变量)的形式的话,B和C是不变的,A矩阵中,基本变量符号相同,非基本变量符号相反。

咱们这里选取X1和X5进行翻转,获得矩阵以下:


那么这里是怎么变换过来的呢?

首先看矩阵第三行,咱们须要改写成X1 = X5,其实很是简单,将该行每个元素除以X1的系数就能够了。

那么其它行呢,其余行实际上是将X1消去,例如第一行,其实是将X1的系数和第三行中X1的系数变成同样后做差。例如这里第一行* -1后减去第三行。其它行同理。

这里经过尝试就能够发现,左上角其实是-z,具体缘由能够在尝试中体会。

不断进行替入与替出,直到第一行中全部的系数都为正。

这里咱们贴出 demo 代码,你们能够自行尝试:

import numpy as np
 
class Simplex(object):
    def __init__(self, obj, max_mode=False):
        self.max_mode = max_mode
        self.mat = np.array([[0] + obj]) * (-1 if max_mode else 1)
     
    def add_constraint(self, a, b):
        self.mat = np.vstack([self.mat, [b] + a])
     
    def solve(self):
        m, n = self.mat.shape
        temp, B = np.vstack([np.zeros((1, m - 1)), np.eye(m - 1)]), list(range(n - 1, n + m - 1))  # add diagonal array
        mat = self.mat = np.hstack([self.mat, temp])
        while mat[0, 1:].min() < 0:
            col = np.where(mat[0, 1:] < 0)[0][0] + 1
            row = np.array([mat[i][0] / mat[i][col] if mat[i][col] > 0 else 0x7fffffff for i in range(1, mat.shape[0])]).argmin() + 1  # find the theta index
            if mat[row][col] <= 0: return None
            mat[row] /= mat[row][col]
            ids = np.arange(mat.shape[0]) != row
            mat[ids] -= mat[row] * mat[ids, col:col + 1]
            B[row] = col
        return mat[0][0] * (1 if self.max_mode else -1), {B[i]: mat[i, 0] for i in range(1, m) if B[i] < n}
复制代码
from Simplex import Simplex
 
t = Simplex([-1, -14, -6])
t.add_constraint([1, 1, 1], 4)
t.add_constraint([1, 0, 0], 2)
t.add_constraint([0, 0, 1], 3)
t.add_constraint([0, 3, 1], 6)
print(t.solve())
print(t.mat)
复制代码

文中若有错误,欢迎指出。

参考文献

相关文章
相关标签/搜索