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)
複製代碼

文中若有錯誤,歡迎指出。

參考文獻

相關文章
相關標籤/搜索